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

Merge branch 'master' into phutil_tag

(Sync.)

+870 -340
+3
.gitignore
··· 24 24 /conf/local/local.json 25 25 /conf/local/ENVIRONMENT 26 26 /conf/local/VERSION 27 + 28 + # Impact Font 29 + /resources/font/impact.ttf
+5 -16
conf/default.conf.php
··· 798 798 // behalf, silencing the warning. 799 799 'phabricator.timezone' => null, 800 800 801 - // When unhandled exceptions occur, stack traces are hidden by default. 802 - // You can enable traces for development to make it easier to debug problems. 803 - 'phabricator.show-stack-traces' => false, 804 - 805 - // Shows an error callout if a page generated PHP errors, warnings or notices. 806 - // This makes it harder to miss problems while developing Phabricator. 807 - 'phabricator.show-error-callout' => false, 801 + // Show stack traces when unhandled exceptions occur, force reloading of 802 + // static resources (skipping the cache), show an error callout if a page 803 + // generated PHP errors, warnings, or notices, force disk reads when 804 + // reloading. This option should not be enabled in production. 805 + 'phabricator.developer-mode' => false, 808 806 809 807 // When users write comments which have URIs, they'll be automatically linked 810 808 // if the protocol appears in this set. This whitelist is primarily to prevent ··· 1219 1217 // caches. Unless you are doing Celerity development, it is exceptionally 1220 1218 // unlikely that you need to modify this. 1221 1219 'celerity.resource-hash' => 'd9455ea150622ee044f7931dabfa52aa', 1222 - 1223 - // In a development environment, it is desirable to force static resources 1224 - // (CSS and JS) to be read from disk on every request, so that edits to them 1225 - // appear when you reload the page even if you haven't updated the resource 1226 - // maps. This setting ensures requests will be verified against the state on 1227 - // disk. Generally, you should leave this off in production (caching behavior 1228 - // and performance improve with it off) but turn it on in development. (These 1229 - // settings are the defaults.) 1230 - 'celerity.force-disk-reads' => false, 1231 1220 1232 1221 // Minify static resources by removing whitespace and comments. You should 1233 1222 // enable this in production, but disable it in development.
+1 -4
conf/development.conf.php
··· 2 2 3 3 return array( 4 4 5 + 'phabricator.developer-mode' => true, 5 6 'darkconsole.enabled' => true, 6 - 'celerity.force-disk-reads' => true, 7 - 'phabricator.show-stack-traces' => true, 8 - 'phabricator.show-error-callout' => true, 9 - 'celerity.minify' => false, 10 7 11 8 ) + phabricator_read_config_file('default');
+22 -5
externals/javelinjs/src/core/Event.js
··· 133 133 var r = this.getRawEvent(); 134 134 return r.which == 3 || r.button == 2; 135 135 }, 136 - 137 - 136 + 138 137 /** 139 - * Determine if a click event is a normal click (left mouse button, no 138 + * Determine if a mouse event is a normal event (left mouse button, no 140 139 * modifier keys). 141 140 * 142 141 * @return bool 143 142 * @task info 144 143 */ 145 - isNormalClick : function() { 146 - if (this.getType() != 'click') { 144 + isNormalMouseEvent : function() { 145 + var supportedEvents = ['click','mouseup','mousedown']; 146 + 147 + if (supportedEvents.indexOf(this.getType()) == -1) { 147 148 return false; 148 149 } 149 150 ··· 161 162 } 162 163 163 164 return true; 165 + }, 166 + 167 + 168 + /** 169 + * Determine if a click event is a normal click (left mouse button, no 170 + * modifier keys). 171 + * 172 + * @return bool 173 + * @task info 174 + */ 175 + isNormalClick : function() { 176 + if (this.getType() != 'click') { 177 + return false; 178 + } 179 + 180 + return this.isNormalMouseEvent(); 164 181 }, 165 182 166 183
+2
src/__phutil_library_map__.php
··· 978 978 'PhabricatorMarkupInterface' => 'infrastructure/markup/PhabricatorMarkupInterface.php', 979 979 'PhabricatorMenuItemView' => 'view/layout/PhabricatorMenuItemView.php', 980 980 'PhabricatorMenuView' => 'view/layout/PhabricatorMenuView.php', 981 + 'PhabricatorMenuViewTestCase' => 'view/layout/__tests__/PhabricatorMenuViewTestCase.php', 981 982 'PhabricatorMercurialGraphStream' => 'applications/repository/daemon/PhabricatorMercurialGraphStream.php', 982 983 'PhabricatorMetaMTAAttachment' => 'applications/metamta/storage/PhabricatorMetaMTAAttachment.php', 983 984 'PhabricatorMetaMTAConfigOptions' => 'applications/config/option/PhabricatorMetaMTAConfigOptions.php', ··· 2395 2396 'PhabricatorMarkupCache' => 'PhabricatorCacheDAO', 2396 2397 'PhabricatorMenuItemView' => 'AphrontTagView', 2397 2398 'PhabricatorMenuView' => 'AphrontTagView', 2399 + 'PhabricatorMenuViewTestCase' => 'PhabricatorTestCase', 2398 2400 'PhabricatorMetaMTAConfigOptions' => 'PhabricatorApplicationConfigOptions', 2399 2401 'PhabricatorMetaMTAController' => 'PhabricatorController', 2400 2402 'PhabricatorMetaMTADAO' => 'PhabricatorLiskDAO',
+20
src/aphront/AphrontRequest.php
··· 228 228 $more_info = "(This was a web request, {$token_info}.)"; 229 229 } 230 230 231 + // Give a more detailed explanation of how to avoid the exception 232 + // in developer mode. 233 + if (PhabricatorEnv::getEnvConfig('phabricator.developer-mode')) { 234 + $more_info = $more_info . 235 + "To avoid this error, use phabricator_form() to construct forms. " . 236 + "If you are already using phabricator_form(), make sure the form " . 237 + "'action' uses a relative URI (i.e., begins with a '/'). Forms " . 238 + "using absolute URIs do not include CSRF tokens, to prevent " . 239 + "leaking tokens to external sites.\n\n" . 240 + "If this page performs writes which do not require CSRF " . 241 + "protection (usually, filling caches or logging), you can use " . 242 + "AphrontWriteGuard::beginScopedUnguardedWrites() to temporarily " . 243 + "bypass CSRF protection while writing. You should use this only " . 244 + "for writes which can not be protected with normal CSRF " . 245 + "mechanisms.\n\n" . 246 + "Some UI elements (like PhabricatorActionListView) also have " . 247 + "methods which will allow you to render links as forms (like " . 248 + "setRenderAsForm(true))."; 249 + } 250 + 231 251 // This should only be able to happen if you load a form, pull your 232 252 // internet for 6 hours, and then reconnect and immediately submit, 233 253 // but give the user some indication of what happened since the workflow
+1 -1
src/aphront/configuration/AphrontDefaultApplicationConfiguration.php
··· 238 238 "schema is up to date."; 239 239 } 240 240 241 - if (PhabricatorEnv::getEnvConfig('phabricator.show-stack-traces')) { 241 + if (PhabricatorEnv::getEnvConfig('phabricator.developer-mode')) { 242 242 $trace = $this->renderStackTrace($ex->getTrace(), $user); 243 243 } else { 244 244 $trace = null;
-1
src/applications/auth/application/PhabricatorApplicationAuth.php
··· 22 22 $item->setIcon('power'); 23 23 $item->setWorkflow(true); 24 24 $item->setHref('/logout/'); 25 - $item->setSortOrder(2.0); 26 25 $item->setSelected(($controller instanceof PhabricatorLogoutController)); 27 26 $items[] = $item; 28 27 }
+2 -1
src/applications/auth/view/PhabricatorOAuthFailureView.php
··· 70 70 71 71 $provider_key = $provider->getProviderKey(); 72 72 $diagnose = hsprintf( 73 - '<a href="/oauth/'.$provider_key.'/diagnose/" class="button green">'. 73 + '<a href="/oauth/%s/diagnose/" class="button green">'. 74 74 'Diagnose %s OAuth Problems'. 75 75 '</a>', 76 + $provider_key, 76 77 $provider_name); 77 78 } 78 79
+35 -20
src/applications/base/PhabricatorApplication.php
··· 66 66 $uninstalled = 67 67 PhabricatorEnv::getEnvConfig('phabricator.uninstalled-applications'); 68 68 69 - if (isset($uninstalled[get_class($this)])) { 69 + if (!$this->canUninstall()) { 70 + return true; 71 + } else if (isset($uninstalled[get_class($this)])) { 70 72 return false; 71 73 } else { 72 74 return true; ··· 227 229 228 230 /* -( Application Management )--------------------------------------------- */ 229 231 230 - public static function getAllApplications() { 232 + public static function getByClass($class_name) { 233 + 234 + $selected = null; 235 + $applications = PhabricatorApplication::getAllApplications(); 231 236 237 + foreach ($applications as $application) { 238 + if (get_class($application) == $class_name) { 239 + $selected = $application; 240 + break; 241 + } 242 + } 243 + return $selected; 244 + } 245 + 246 + public static function getAllApplications() { 232 247 $classes = id(new PhutilSymbolLoader()) 233 248 ->setAncestorClass(__CLASS__) 234 249 ->setConcreteOnly(true) ··· 241 256 $apps[] = $app; 242 257 } 243 258 259 + // Reorder the applications into "application order". Notably, this ensures 260 + // their event handlers register in application order. 261 + $apps = msort($apps, 'getApplicationOrder'); 262 + $apps = mgroup($apps, 'getApplicationGroup'); 263 + $apps = array_select_keys($apps, self::getApplicationGroups()) + $apps; 264 + $apps = array_mergev($apps); 265 + 244 266 return $apps; 245 267 } 246 268 ··· 254 276 PhabricatorEnv::getEnvConfig('phabricator.uninstalled-applications'); 255 277 256 278 257 - 258 279 if (empty($applications)) { 259 - $classes = id(new PhutilSymbolLoader()) 260 - ->setAncestorClass(__CLASS__) 261 - ->setConcreteOnly(true) 262 - ->selectAndLoadSymbols(); 263 - 280 + $all_applications = self::getAllApplications(); 264 281 $apps = array(); 265 - foreach ($classes as $class) { 282 + foreach ($all_applications as $app) { 283 + $class = get_class($app); 284 + if (isset($uninstalled[$class])) { 285 + continue; 286 + } 266 287 267 - if (isset($uninstalled[$class['name']])) { 268 - continue; 269 - } 270 - 271 - $app = newv($class['name'], array()); 272 - 273 - if (!$app->isEnabled()) { 288 + if (!$app->isEnabled()) { 274 289 continue; 275 - } 290 + } 276 291 277 - if (!$show_beta && $app->isBeta()) { 292 + if (!$show_beta && $app->isBeta()) { 278 293 continue; 279 - } 294 + } 280 295 281 - $apps[] = $app; 296 + $apps[] = $app; 282 297 } 283 298 284 299 $applications = $apps;
+9 -39
src/applications/config/option/PhabricatorDeveloperConfigOptions.php
··· 86 86 "data to look at eventually). In development, it may be useful to ". 87 87 "set it to 1 in order to debug performance problems.\n\n". 88 88 "NOTE: You must install XHProf for profiling to work.")), 89 - $this->newOption('phabricator.show-stack-traces', 'bool', false) 90 - ->setBoolOptions( 91 - array( 92 - pht('Show stack traces'), 93 - pht('Hide stack traces'), 94 - )) 95 - ->setSummary(pht("Show stack traces when unhandled exceptions occur.")) 96 - ->setDescription( 97 - pht( 98 - "When unhandled exceptions occur, stack traces are hidden by ". 99 - "default. You can enable traces for development to make it easier ". 100 - "to debug problems.")), 101 - $this->newOption('phabricator.show-error-callout', 'bool', false) 102 - ->setBoolOptions( 103 - array( 104 - pht('Show error callout'), 105 - pht('Hide error callout'), 106 - )) 107 - ->setSummary(pht("Show error callout.")) 108 - ->setDescription( 109 - pht( 110 - "Shows an error callout if a page generated PHP errors, warnings ". 111 - "or notices. This makes it harder to miss problems while ". 112 - "developing Phabricator. A callout is simply a red error at the ". 113 - "top of the page.")), 114 - $this->newOption('celerity.force-disk-reads', 'bool', false) 89 + $this->newOption('phabricator.developer-mode', 'bool', false) 115 90 ->setBoolOptions( 116 91 array( 117 - pht('Force disk reads'), 118 - pht("Don't force disk reads"), 92 + pht('Enable developer mode'), 93 + pht('Disable developer mode'), 119 94 )) 120 - ->setSummary(pht("Force Celerity to read from disk on every request.")) 121 - ->setDescription( 122 - pht( 123 - "In a development environment, it is desirable to force static ". 124 - "resources (CSS and JS) to be read from disk on every request, so ". 125 - "that edits to them appear when you reload the page even if you ". 126 - "haven't updated the resource maps. This setting ensures requests ". 127 - "will be verified against the state on disk. Generally, you ". 128 - "should leave this off in production (caching behavior and ". 129 - "performance improve with it off) but turn it on in development. ". 130 - "(These settings are the defaults.)")), 95 + ->setSummary(pht("Enable verbose error reporting and disk reads.")) 96 + ->setDescription( 97 + pht( 98 + "This option enables verbose error reporting (stack traces, ". 99 + "error callouts) and forces disk reads of static assets on ". 100 + "every reload.")), 131 101 $this->newOption('celerity.minify', 'bool', false) 132 102 ->setBoolOptions( 133 103 array(
+9
src/applications/config/response/PhabricatorConfigResponse.php
··· 10 10 } 11 11 12 12 public function buildResponseString() { 13 + // Check to make sure we aren't requesting this via ajax or conduit 14 + if (isset($_REQUEST['__ajax__']) || isset($_REQUEST['__conduit__'])) { 15 + // We don't want to flood the console with html, just return a simple 16 + // message for now. 17 + return pht( 18 + "This install has a fatal setup error, access the internet web ". 19 + "version to view details and resolve it."); 20 + } 21 + 13 22 $resources = $this->buildResources(); 14 23 15 24 $view = $this->view->render();
+15 -34
src/applications/conpherence/controller/ConpherenceUpdateController.php
··· 44 44 'ip' => $request->getRemoteAddr() 45 45 )); 46 46 $editor = id(new ConpherenceEditor()) 47 + ->setContinueOnNoEffect($request->isContinueRequest()) 47 48 ->setContentSource($content_source) 48 49 ->setActor($user); 49 50 ··· 55 56 $conpherence, 56 57 $message 57 58 ); 58 - $time = time(); 59 - $conpherence->openTransaction(); 60 - $xactions = $editor->applyTransactions($conpherence, $xactions); 61 - $last_xaction = end($xactions); 62 - $xaction_phid = $last_xaction->getPHID(); 63 - $behind = ConpherenceParticipationStatus::BEHIND; 64 - $up_to_date = ConpherenceParticipationStatus::UP_TO_DATE; 65 - $participants = $conpherence->getParticipants(); 66 - foreach ($participants as $phid => $participant) { 67 - if ($phid != $user->getPHID()) { 68 - if ($participant->getParticipationStatus() != $behind) { 69 - $participant->setBehindTransactionPHID($xaction_phid); 70 - } 71 - $participant->setParticipationStatus($behind); 72 - $participant->setDateTouched($time); 73 - } else { 74 - $participant->setParticipationStatus($up_to_date); 75 - $participant->setDateTouched($time); 76 - } 77 - $participant->save(); 78 - } 79 - $updated = $conpherence->saveTransaction(); 80 59 break; 81 60 case 'metadata': 82 61 $xactions = array(); ··· 112 91 ->setTransactionType(ConpherenceTransactionType::TYPE_TITLE) 113 92 ->setNewValue($title); 114 93 } 115 - 116 - if ($xactions) { 117 - $conpherence->openTransaction(); 118 - $xactions = $editor 119 - ->setContinueOnNoEffect(true) 120 - ->applyTransactions($conpherence, $xactions); 121 - $updated = $conpherence->saveTransaction(); 122 - } else if (empty($errors)) { 123 - $errors[] = pht( 124 - 'That was a non-update. Try cancel.' 125 - ); 126 - } 127 94 break; 128 95 default: 129 96 throw new Exception('Unknown action: '.$action); 130 97 break; 98 + } 99 + if ($xactions) { 100 + try { 101 + $xactions = $editor->applyTransactions($conpherence, $xactions); 102 + $updated = true; 103 + } catch (PhabricatorApplicationTransactionNoEffectException $ex) { 104 + return id(new PhabricatorApplicationTransactionNoEffectResponse()) 105 + ->setCancelURI($this->getApplicationURI($conpherence_id.'/')) 106 + ->setException($ex); 107 + } 108 + } else if (empty($errors)) { 109 + $errors[] = pht( 110 + 'That was a non-update. Try cancel.' 111 + ); 131 112 } 132 113 } 133 114
+1
src/applications/conpherence/controller/ConpherenceViewController.php
··· 128 128 129 129 $form = 130 130 id(new AphrontFormView()) 131 + ->setWorkflow(true) 131 132 ->setAction($this->getApplicationURI('update/'.$conpherence->getID().'/')) 132 133 ->setFlexible(true) 133 134 ->setUser($user)
+21
src/applications/conpherence/editor/ConpherenceEditor.php
··· 118 118 ); 119 119 } 120 120 $editor->save(); 121 + // fallthrough 122 + case PhabricatorTransactions::TYPE_COMMENT: 123 + $xaction_phid = $xaction->getPHID(); 124 + $behind = ConpherenceParticipationStatus::BEHIND; 125 + $up_to_date = ConpherenceParticipationStatus::UP_TO_DATE; 126 + $participants = $object->getParticipants(); 127 + $user = $this->getActor(); 128 + $time = time(); 129 + foreach ($participants as $phid => $participant) { 130 + if ($phid != $user->getPHID()) { 131 + if ($participant->getParticipationStatus() != $behind) { 132 + $participant->setBehindTransactionPHID($xaction_phid); 133 + } 134 + $participant->setParticipationStatus($behind); 135 + $participant->setDateTouched($time); 136 + } else { 137 + $participant->setParticipationStatus($up_to_date); 138 + $participant->setDateTouched($time); 139 + } 140 + $participant->save(); 141 + } 121 142 break; 122 143 case ConpherenceTransactionType::TYPE_PARTICIPANTS: 123 144 foreach ($xaction->getNewValue() as $participant) {
+1 -1
src/applications/differential/controller/DifferentialRevisionListController.php
··· 363 363 array_select_keys($handles, $params['participants']), 364 364 'getFullName'); 365 365 return id(new AphrontFormTokenizerControl()) 366 - ->setDatasource('/typeahead/common/allmailable/') 366 + ->setDatasource('/typeahead/common/accounts/') 367 367 ->setLabel($label) 368 368 ->setName('participants') 369 369 ->setValue($value);
+2 -4
src/applications/differential/controller/DifferentialRevisionViewController.php
··· 182 182 $warning->setSeverity(AphrontErrorView::SEVERITY_WARNING); 183 183 $warning->appendChild( 184 184 pht( 185 - 'This diff is very large and affects %2$s files. Load each file '. 185 + 'This diff is very large and affects %s files. Load each file '. 186 186 'individually.', 187 - $count, 188 - PhutilTranslator::getInstance()->formatNumber($count)). 187 + new PhutilNumber($count)). 189 188 " <strong>". 190 189 phutil_tag( 191 190 'a', ··· 499 498 'href' => "/differential/subscribe/{$action}/{$revision_id}/", 500 499 'name' => $viewer_is_cc ? pht('Unsubscribe') : pht('Subscribe'), 501 500 'instant' => true, 502 - 'sigil' => 'workflow', 503 501 ); 504 502 } else { 505 503 $links[] = array(
+2
src/applications/differential/storage/DifferentialDiff.php
··· 188 188 'id' => $this->getID(), 189 189 'parent' => $this->getParentRevisionID(), 190 190 'revisionID' => $this->getRevisionID(), 191 + 'dateCreated' => $this->getDateCreated(), 192 + 'dateModified' => $this->getDateModified(), 191 193 'sourceControlBaseRevision' => $this->getSourceControlBaseRevision(), 192 194 'sourceControlPath' => $this->getSourceControlPath(), 193 195 'sourceControlSystem' => $this->getSourceControlSystem(),
+1 -1
src/applications/differential/view/DifferentialChangesetListView.php
··· 229 229 $template = 230 230 '<table><tr>'. 231 231 '<th></th><td>%s</td>'. 232 - '<th></th><td colspan="2">%s</td>'. 232 + '<th></th><td colspan="3">%s</td>'. 233 233 '</tr></table>'; 234 234 235 235 return array(
+1
src/applications/differential/view/DifferentialRevisionDetailView.php
··· 52 52 ->setName($action['name']) 53 53 ->setHref(idx($action, 'href')) 54 54 ->setWorkflow(idx($action, 'sigil') == 'workflow') 55 + ->setRenderAsForm(!empty($action['instant'])) 55 56 ->setUser($user) 56 57 ->setDisabled(idx($action, 'disabled', false)); 57 58 $actions->addAction($obj);
+3 -3
src/applications/diffusion/controller/DiffusionBrowseFileController.php
··· 814 814 815 815 $size = strlen($data); 816 816 $properties->addTextContent( 817 - pht('This is a binary file. It is %2$s byte(s) in length.', 818 - $size, 819 - PhutilTranslator::getInstance()->formatNumber($size))); 817 + pht( 818 + 'This is a binary file. It is %s byte(s) in length.', 819 + new PhutilNumber($size))); 820 820 821 821 $actions = id(new PhabricatorActionListView()) 822 822 ->setUser($this->getRequest()->getUser())
+37 -26
src/applications/diffusion/controller/DiffusionCommitController.php
··· 116 116 $content[] = $this->buildAuditTable($commit, $audit_requests); 117 117 $content[] = $this->buildComments($commit); 118 118 119 + $hard_limit = 1000; 120 + 119 121 $change_query = DiffusionPathChangeQuery::newFromDiffusionRequest( 120 122 $drequest); 123 + $change_query->setLimit($hard_limit + 1); 121 124 $changes = $change_query->loadChanges(); 122 125 126 + $was_limited = (count($changes) > $hard_limit); 127 + if ($was_limited) { 128 + $changes = array_slice($changes, 0, $hard_limit); 129 + } 130 + 123 131 $content[] = $this->buildMergesTable($commit); 124 132 125 133 $owners_paths = array(); ··· 151 159 'r'.$callsign.$commit->getCommitIdentifier()); 152 160 } 153 161 154 - $pane_id = null; 155 162 if ($bad_commit) { 156 163 $error_panel = new AphrontErrorView(); 157 164 $error_panel->setTitle('Bad Commit'); ··· 175 182 "This commit hasn't been fully parsed yet (or doesn't affect any ". 176 183 "paths)."); 177 184 $content[] = $no_changes; 185 + } else if ($was_limited) { 186 + $huge_commit = new AphrontErrorView(); 187 + $huge_commit->setSeverity(AphrontErrorView::SEVERITY_WARNING); 188 + $huge_commit->setTitle(pht('Enormous Commit')); 189 + $huge_commit->appendChild( 190 + pht( 191 + 'This commit is enormous, and affects more than %d files. '. 192 + 'Changes are not shown.', 193 + $hard_limit)); 194 + $content[] = $huge_commit; 178 195 } else { 179 196 $change_panel = new AphrontPanelView(); 180 197 $change_panel->setHeader("Changes (".number_format($count).")"); 181 198 $change_panel->setID('toc'); 182 - 183 199 if ($count > self::CHANGES_LIMIT) { 184 200 $show_all_button = phutil_tag( 185 201 'a', ··· 296 312 } 297 313 $change_table->setRenderingReferences($change_references); 298 314 299 - // TODO: This is pretty awkward, unify the CSS between Diffusion and 300 - // Differential better. 301 - require_celerity_resource('differential-core-view-css'); 302 - $pane_id = celerity_generate_unique_node_id(); 303 - $add_comment_view = $this->renderAddCommentPanel($commit, 304 - $audit_requests, 305 - $pane_id); 306 - $main_pane = phutil_render_tag( 307 - 'div', 308 - array( 309 - 'id' => $pane_id 310 - ), 311 - $change_list->render(). 312 - id(new PhabricatorAnchorView()) 313 - ->setAnchorName('comment') 314 - ->setNavigationMarker(true) 315 - ->render(). 316 - $add_comment_view); 315 + $content[] = $change_list->render(); 316 + } 317 317 318 - $content[] = $main_pane; 319 - } 318 + $content[] = $this->renderAddCommentPanel($commit, $audit_requests); 320 319 321 320 $commit_id = 'r'.$callsign.$commit->getCommitIdentifier(); 322 321 $short_name = DiffusionView::nameCommit( ··· 577 576 578 577 private function renderAddCommentPanel( 579 578 PhabricatorRepositoryCommit $commit, 580 - array $audit_requests, 581 - $pane_id = null) { 579 + array $audit_requests) { 582 580 assert_instances_of($audit_requests, 'PhabricatorRepositoryAuditRequest'); 583 581 $user = $this->getRequest()->getUser(); 584 582 585 583 $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); 586 584 585 + $pane_id = celerity_generate_unique_node_id(); 587 586 Javelin::initBehavior( 588 587 'differential-keyboard-navigation', 589 588 array( ··· 693 692 </div> 694 693 </div>'; 695 694 696 - return 695 + // TODO: This is pretty awkward, unify the CSS between Diffusion and 696 + // Differential better. 697 + require_celerity_resource('differential-core-view-css'); 698 + 699 + return phutil_render_tag( 700 + 'div', 701 + array( 702 + 'id' => $pane_id, 703 + ), 697 704 phutil_render_tag( 698 705 'div', 699 706 array( 700 707 'class' => 'differential-add-comment-panel', 701 708 ), 709 + id(new PhabricatorAnchorView()) 710 + ->setAnchorName('comment') 711 + ->setNavigationMarker(true) 712 + ->render(). 702 713 $panel->render(). 703 - $preview_panel); 714 + $preview_panel)); 704 715 } 705 716 706 717 /**
+41 -13
src/applications/diffusion/query/pathchange/DiffusionPathChangeQuery.php
··· 3 3 final class DiffusionPathChangeQuery { 4 4 5 5 private $request; 6 + private $limit; 7 + 8 + public function setLimit($limit) { 9 + $this->limit = $limit; 10 + return $this; 11 + } 12 + 13 + public function getLimit() { 14 + return $this->limit; 15 + } 6 16 7 17 final private function __construct() { 8 18 // <private> ··· 31 41 32 42 $commit = $drequest->loadCommit(); 33 43 44 + $conn_r = $repository->establishConnection('r'); 45 + 46 + $limit = ''; 47 + if ($this->limit) { 48 + $limit = qsprintf( 49 + $conn_r, 50 + 'LIMIT %d', 51 + $this->limit + 1); 52 + } 53 + 34 54 $raw_changes = queryfx_all( 35 - $repository->establishConnection('r'), 55 + $conn_r, 36 56 'SELECT c.*, p.path pathName, t.path targetPathName, 37 57 i.commitIdentifier targetCommitIdentifier 38 58 FROM %T c 39 59 LEFT JOIN %T p ON c.pathID = p.id 40 60 LEFT JOIN %T t ON c.targetPathID = t.id 41 61 LEFT JOIN %T i ON c.targetCommitID = i.id 42 - WHERE c.commitID = %d AND isDirect = 1', 62 + WHERE c.commitID = %d AND isDirect = 1 %Q', 43 63 PhabricatorRepository::TABLE_PATHCHANGE, 44 64 PhabricatorRepository::TABLE_PATH, 45 65 PhabricatorRepository::TABLE_PATH, 46 66 $commit->getTableName(), 47 - $commit->getID()); 67 + $commit->getID(), 68 + $limit); 69 + 70 + $limited = $this->limit && (count($raw_changes) > $this->limit); 71 + if ($limited) { 72 + $raw_changes = array_slice($raw_changes, 0, $this->limit); 73 + } 48 74 49 75 $changes = array(); 50 76 ··· 68 94 $changes[$id] = $change; 69 95 } 70 96 71 - // Deduce the away paths by examining all the changes. 97 + // Deduce the away paths by examining all the changes, if we loaded them 98 + // all. 72 99 73 - $away = array(); 74 - foreach ($changes as $change) { 75 - if ($change->getTargetPath()) { 76 - $away[$change->getTargetPath()][] = $change->getPath(); 100 + if (!$limited) { 101 + $away = array(); 102 + foreach ($changes as $change) { 103 + if ($change->getTargetPath()) { 104 + $away[$change->getTargetPath()][] = $change->getPath(); 105 + } 77 106 } 78 - } 79 - foreach ($changes as $change) { 80 - if (isset($away[$change->getPath()])) { 81 - $change->setAwayPaths($away[$change->getPath()]); 107 + foreach ($changes as $change) { 108 + if (isset($away[$change->getPath()])) { 109 + $change->setAwayPaths($away[$change->getPath()]); 110 + } 82 111 } 83 112 } 84 - 85 113 86 114 return $changes; 87 115 }
+8 -9
src/applications/diffusion/view/DiffusionBrowseTableView.php
··· 95 95 96 96 $conn = $drequest->getRepository()->establishConnection('r'); 97 97 98 - $where = ''; 98 + $path = '/'.$drequest->getPath(); 99 + $where = (substr($path, -1) == '/' 100 + ? qsprintf($conn, 'AND path LIKE %>', $path) 101 + : qsprintf($conn, 'AND path = %s', $path)); 102 + 99 103 if ($drequest->getLint()) { 100 - $where = qsprintf( 101 - $conn, 102 - 'AND code = %s', 103 - $drequest->getLint()); 104 + $where .= qsprintf($conn, ' AND code = %s', $drequest->getLint()); 104 105 } 105 106 106 - $like = (substr($drequest->getPath(), -1) == '/' ? 'LIKE %>' : '= %s'); 107 107 return head(queryfx_one( 108 108 $conn, 109 - 'SELECT COUNT(*) FROM %T WHERE branchID = %d %Q AND path '.$like, 109 + 'SELECT COUNT(*) FROM %T WHERE branchID = %d %Q', 110 110 PhabricatorRepository::TABLE_LINTMESSAGE, 111 111 $branch->getID(), 112 - $where, 113 - '/'.$drequest->getPath())); 112 + $where)); 114 113 } 115 114 116 115 public function render() {
-2
src/applications/directory/controller/PhabricatorDirectoryController.php
··· 65 65 continue; 66 66 } 67 67 68 - $tile_group = msort($tile_group, 'getApplicationOrder'); 69 - 70 68 $is_small_tiles = ($tile_display == PhabricatorApplication::TILE_SHOW) || 71 69 ($tile_display == PhabricatorApplication::TILE_HIDE); 72 70
-1
src/applications/diviner/application/PhabricatorApplicationDiviner.php
··· 44 44 $item->setName(pht('%s Help', $application->getName())); 45 45 $item->setIcon('help'); 46 46 $item->setHref($application->getHelpURI()); 47 - $item->setSortOrder(0.1); 48 47 $items[] = $item; 49 48 } 50 49
+38 -12
src/applications/files/PhabricatorImageTransformer.php
··· 116 116 return $dst; 117 117 } 118 118 119 - private function generatePreview(PhabricatorFile $file, $size) { 119 + public static function getPreviewDimensions(PhabricatorFile $file, $size) { 120 120 $data = $file->loadFileData(); 121 121 $src = imagecreatefromstring($data); 122 122 ··· 128 128 $dx = max($size / 4, $scale * $x); 129 129 $dy = max($size / 4, $scale * $y); 130 130 131 + $sdx = $scale * $x; 132 + $sdy = $scale * $y; 133 + 134 + return array( 135 + 'x' => $x, 136 + 'y' => $y, 137 + 'dx' => $dx, 138 + 'dy' => $dy, 139 + 'sdx' => $sdx, 140 + 'sdy' => $sdy 141 + ); 142 + } 143 + 144 + private function generatePreview(PhabricatorFile $file, $size) { 145 + $data = $file->loadFileData(); 146 + $src = imagecreatefromstring($data); 147 + 148 + $dimensions = self::getPreviewDimensions($file, $size); 149 + $x = $dimensions['x']; 150 + $y = $dimensions['y']; 151 + $dx = $dimensions['dx']; 152 + $dy = $dimensions['dy']; 153 + $sdx = $dimensions['sdx']; 154 + $sdy = $dimensions['sdy']; 155 + 131 156 $dst = imagecreatetruecolor($dx, $dy); 132 157 imagesavealpha($dst, true); 133 158 imagefill($dst, 0, 0, imagecolorallocatealpha($dst, 255, 255, 255, 127)); 134 - 135 - $sdx = $scale * $x; 136 - $sdy = $scale * $y; 137 159 138 160 imagecopyresampled( 139 161 $dst, ··· 153 175 $data = $file->loadFileData(); 154 176 $img = imagecreatefromstring($data); 155 177 $phabricator_root = dirname(phutil_get_library_root('phabricator')); 156 - $font_path = $phabricator_root.'/resources/font/tuffy.ttf'; 157 - $white = imagecolorallocate($img, 255, 255, 255); 158 - $black = imagecolorallocate($img, 0, 0, 0); 159 - $border_width = 3; 178 + $font_root = $phabricator_root.'/resources/font/'; 179 + $font_path = $font_root.'tuffy.ttf'; 180 + if (Filesystem::pathExists($font_root.'impact.ttf')) { 181 + $font_path = $font_root.'impact.ttf'; 182 + } 183 + $text_color = imagecolorallocate($img, 255, 255, 255); 184 + $border_color = imagecolorallocatealpha($img, 0, 0, 0, 110); 185 + $border_width = 4; 160 186 $font_max = 200; 161 187 $font_min = 5; 162 188 for ($i = $font_max; $i > $font_min; $i--) { ··· 172 198 $i, 173 199 $x, 174 200 $y, 175 - $white, 176 - $black, 201 + $text_color, 202 + $border_color, 177 203 $border_width, 178 204 $font_path, 179 205 $upper_text); ··· 191 217 $i, 192 218 $x, 193 219 $y, 194 - $white, 195 - $black, 220 + $text_color, 221 + $border_color, 196 222 $border_width, 197 223 $font_path, 198 224 $lower_text);
+4 -4
src/applications/macro/controller/PhabricatorMacroEditController.php
··· 39 39 if (!strlen($macro->getName())) { 40 40 $errors[] = pht('Macro name is required.'); 41 41 $e_name = pht('Required'); 42 - } else if (!preg_match('/^[a-z0-9_-]{3,}$/', $macro->getName())) { 43 - $errors[] = pht('Macro must be at least three characters long and '. 44 - 'contain only lowercase letters, digits, hyphen and '. 45 - 'underscore.'); 42 + } else if (!preg_match('/^[a-z0-9:_-]{3,}$/', $macro->getName())) { 43 + $errors[] = pht( 44 + 'Macro must be at least three characters long and contain only '. 45 + 'lowercase letters, digits, hyphens, colons and underscores.'); 46 46 $e_name = pht('Invalid'); 47 47 } else { 48 48 $e_name = null;
+2 -2
src/applications/meta/application/PhabricatorApplicationApplications.php
··· 22 22 return "\xE0\xBC\x84"; 23 23 } 24 24 25 - public function shouldAppearInLaunchView() { 26 - return false; 25 + public function getApplicationGroup() { 26 + return self::GROUP_ADMIN; 27 27 } 28 28 29 29 public function getRoutes() {
+70 -50
src/applications/meta/controller/PhabricatorApplicationDetailViewController.php
··· 13 13 $request = $this->getRequest(); 14 14 $user = $request->getUser(); 15 15 16 - $selected = null; 17 - $applications = PhabricatorApplication::getAllApplications(); 18 - 19 - foreach ($applications as $application) { 20 - if (get_class($application) == $this->application) { 21 - $selected = $application; 22 - break; 23 - } 24 - } 16 + $selected = PhabricatorApplication::getByClass($this->application); 25 17 26 18 if (!$selected) { 27 19 return new Aphront404Response(); ··· 35 27 ->setName(pht('Applications')) 36 28 ->setHref($this->getApplicationURI())); 37 29 38 - $properties = $this->buildPropertyView($selected); 39 - $actions = $this->buildActionView($user, $selected); 40 - 41 - return $this->buildApplicationPage( 42 - array( 43 - $crumbs, 44 - id(new PhabricatorHeaderView())->setHeader($title), 45 - $actions, 46 - $properties, 47 - ), 48 - array( 49 - 'title' => $title, 50 - 'device' => true, 51 - )); 52 - } 30 + $header = id(new PhabricatorHeaderView()) 31 + ->setHeader($title); 53 32 54 - private function buildPropertyView(PhabricatorApplication $selected) { 55 - $properties = new PhabricatorPropertyListView(); 33 + $status_tag = id(new PhabricatorTagView()) 34 + ->setType(PhabricatorTagView::TYPE_STATE); 56 35 57 36 if ($selected->isInstalled()) { 58 - $properties->addProperty( 59 - pht('Status'), pht('Installed')); 37 + $status_tag->setName(pht('Installed')); 38 + $status_tag->setBackgroundColor(PhabricatorTagView::COLOR_GREEN); 60 39 61 40 } else { 62 - $properties->addProperty( 63 - pht('Status'), pht('Uninstalled')); 41 + $status_tag->setName(pht('Uninstalled')); 42 + $status_tag->setBackgroundColor(PhabricatorTagView::COLOR_RED); 64 43 } 65 44 66 - $properties->addProperty( 67 - pht('Description'), $selected->getShortDescription()); 45 + if ($selected->isBeta()) { 46 + $beta_tag = id(new PhabricatorTagView()) 47 + ->setType(PhabricatorTagView::TYPE_STATE) 48 + ->setName(pht('Beta')) 49 + ->setBackgroundColor(PhabricatorTagView::COLOR_GREY); 50 + $header->addTag($beta_tag); 51 + } 52 + 53 + 54 + $header->addTag($status_tag); 55 + 56 + $properties = $this->buildPropertyView($selected); 57 + $actions = $this->buildActionView($user, $selected); 58 + 59 + return $this->buildApplicationPage( 60 + array( 61 + $crumbs, 62 + $header, 63 + $actions, 64 + $properties, 65 + ), 66 + array( 67 + 'title' => $title, 68 + 'device' => true, 69 + )); 70 + } 71 + 72 + private function buildPropertyView(PhabricatorApplication $selected) { 73 + $properties = id(new PhabricatorPropertyListView()) 74 + ->addProperty( 75 + pht('Description'), $selected->getShortDescription() 76 + ); 68 77 69 78 return $properties; 70 79 } ··· 72 81 private function buildActionView( 73 82 PhabricatorUser $user, PhabricatorApplication $selected) { 74 83 84 + $view = id(new PhabricatorActionListView()) 85 + ->setUser($user); 86 + 75 87 if ($selected->canUninstall()) { 76 88 if ($selected->isInstalled()) { 77 - 78 - return id(new PhabricatorActionListView()) 79 - ->setUser($user) 80 - ->addAction( 81 - id(new PhabricatorActionView()) 82 - ->setName(pht('Uninstall')) 83 - ->setIcon('delete') 84 - ->setHref( 89 + $view->addAction( 90 + id(new PhabricatorActionView()) 91 + ->setName(pht('Uninstall')) 92 + ->setIcon('delete') 93 + ->setWorkflow(true) 94 + ->setHref( 85 95 $this->getApplicationURI(get_class($selected).'/uninstall/')) 86 - ); 96 + ); 87 97 } else { 88 - return id(new PhabricatorActionListView()) 89 - ->setUser($user) 90 - ->addAction( 91 - id(new PhabricatorActionView()) 92 - ->setName(pht('Install')) 93 - ->setIcon('new') 94 - ->setHref( 95 - $this->getApplicationURI(get_class($selected).'/install/')) 96 - ); 98 + $view->addAction( 99 + id(new PhabricatorActionView()) 100 + ->setName(pht('Install')) 101 + ->setIcon('new') 102 + ->setWorkflow(true) 103 + ->setHref( 104 + $this->getApplicationURI(get_class($selected).'/install/')) 105 + ); 97 106 } 107 + } else { 108 + $view->addAction( 109 + id(new PhabricatorActionView()) 110 + ->setName(pht('Uninstall')) 111 + ->setIcon('delete') 112 + ->setWorkflow(true) 113 + ->setDisabled(true) 114 + ->setHref( 115 + $this->getApplicationURI(get_class($selected).'/uninstall/')) 116 + ); 98 117 } 118 + return $view; 99 119 } 100 120 101 121 }
+36 -15
src/applications/meta/controller/PhabricatorApplicationUninstallController.php
··· 14 14 public function processRequest() { 15 15 $request = $this->getRequest(); 16 16 $user = $request->getUser(); 17 - $app_name = substr($this->application, strlen('PhabricatorApplication')); 17 + 18 + $selected = PhabricatorApplication::getByClass($this->application); 19 + 20 + if (!$selected) { 21 + return new Aphront404Response(); 22 + } 23 + 24 + $view_uri = $this->getApplicationURI('view/'.$this->application); 18 25 19 26 if ($request->isDialogFormPost()) { 20 27 $this->manageApplication(); 21 - return id(new AphrontRedirectResponse())->setURI('/applications/'); 28 + return id(new AphrontRedirectResponse())->setURI($view_uri); 22 29 } 23 30 31 + $dialog = id(new AphrontDialogView()) 32 + ->setUser($user) 33 + ->addCancelButton($view_uri); 34 + 24 35 if ($this->action == 'install') { 36 + if ($selected->canUninstall()) { 37 + $dialog->setTitle('Confirmation') 38 + ->appendChild( 39 + 'Install '. $selected->getName(). ' application ?' 40 + ) 41 + ->addSubmitButton('Install'); 25 42 26 - $dialog = id(new AphrontDialogView()) 27 - ->setUser($user) 28 - ->setTitle('Confirmation') 29 - ->appendChild('Install '. $app_name. ' application ?') 30 - ->addSubmitButton('Install') 31 - ->addCancelButton('/applications/view/'.$this->application); 43 + } else { 44 + $dialog->setTitle('Information') 45 + ->appendChild('You cannot install a installed application.'); 46 + } 32 47 } else { 33 - $dialog = id(new AphrontDialogView()) 34 - ->setUser($user) 35 - ->setTitle('Confirmation') 36 - ->appendChild('Really Uninstall '. $app_name. ' application ?') 37 - ->addSubmitButton('Uninstall') 38 - ->addCancelButton('/applications/view/'.$this->application); 48 + if ($selected->canUninstall()) { 49 + $dialog->setTitle('Confirmation') 50 + ->appendChild( 51 + 'Really Uninstall '. $selected->getName(). ' application ?' 52 + ) 53 + ->addSubmitButton('Uninstall'); 54 + } else { 55 + $dialog->setTitle('Information') 56 + ->appendChild( 57 + 'This application cannot be uninstalled, 58 + because it is required for Phabricator to work.' 59 + ); 60 + } 39 61 } 40 - 41 62 return id(new AphrontDialogResponse())->setDialog($dialog); 42 63 } 43 64
+12
src/applications/meta/controller/PhabricatorApplicationsListController.php
··· 47 47 private function buildInstalledApplicationsList(array $applications) { 48 48 $list = new PhabricatorObjectItemListView(); 49 49 50 + $applications = msort($applications, 'getName'); 51 + 50 52 foreach ($applications as $application) { 51 53 $item = id(new PhabricatorObjectItemView()) 52 54 ->setHeader($application->getName()) 53 55 ->setHref('/applications/view/'.get_class($application).'/') 54 56 ->addAttribute($application->getShortDescription()); 57 + 58 + if (!$application->isInstalled()) { 59 + $item->addIcon('delete', pht('Uninstalled')); 60 + } 61 + 62 + if ($application->isBeta()) { 63 + $item->addIcon('lint-warning', pht('Beta')); 64 + } 65 + 55 66 $list->addItem($item); 67 + 56 68 } 57 69 return $list; 58 70 }
+2 -3
src/applications/paste/controller/PhabricatorPasteListController.php
··· 95 95 96 96 $line_count = count(explode("\n", $paste->getContent())); 97 97 $line_count = pht( 98 - '%2$s Line(s)', 99 - $line_count, 100 - PhutilTranslator::getInstance()->formatNumber($line_count)); 98 + '%s Line(s)', 99 + new PhutilNumber($line_count)); 101 100 102 101 $item = id(new PhabricatorObjectItemView()) 103 102 ->setHeader($paste->getFullName())
-1
src/applications/people/application/PhabricatorApplicationPeople.php
··· 58 58 $item = new PhabricatorMenuItemView(); 59 59 $item->setName($user->getUsername()); 60 60 $item->setHref('/p/'.$user->getUsername().'/'); 61 - $item->setSortOrder(0.0); 62 61 $item->addClass('phabricator-core-menu-item-profile'); 63 62 64 63 $classes = array(
+26 -13
src/applications/pholio/view/PholioMockImagesView.php
··· 13 13 throw new Exception("Call setMock() before render()!"); 14 14 } 15 15 16 - $mockview = array(); 16 + $main_image_id = celerity_generate_unique_node_id(); 17 + require_celerity_resource('javelin-behavior-pholio-mock-view'); 18 + $config = array('mainID' => $main_image_id); 19 + Javelin::initBehavior('pholio-mock-view', $config); 17 20 18 - $file = head($this->mock->getImages())->getFile(); 21 + $mockview = ""; 19 22 20 - $main_image_id = celerity_generate_unique_node_id(); 23 + $main_image = head($this->mock->getImages()); 21 24 22 - $main_image_tag = phutil_tag( 25 + $main_image_tag = javelin_tag( 23 26 'img', 24 27 array( 25 - 'src' => $file->getBestURI(), 28 + 'id' => $main_image_id, 29 + 'src' => $main_image->getFile()->getBestURI(), 30 + 'sigil' => 'mock-image', 26 31 'class' => 'pholio-mock-image', 27 - 'id' => $main_image_id, 28 - )); 32 + 'meta' => array( 33 + 'fullSizeURI' => $main_image->getFile()->getBestURI(), 34 + 'imageID' => $main_image->getID(), 35 + ), 36 + )); 37 + 38 + $main_image_tag = javelin_tag( 39 + 'div', 40 + array( 41 + 'id' => 'mock-wrapper', 42 + 'sigil' => 'mock-wrapper', 43 + 'class' => 'pholio-mock-wrapper' 44 + ), 45 + $main_image_tag 46 + ); 29 47 30 48 $mockview[] = phutil_tag( 31 49 'div', ··· 35 53 $main_image_tag); 36 54 37 55 if (count($this->mock->getImages()) > 1) { 38 - require_celerity_resource('javelin-behavior-pholio-mock-view'); 39 - $config = array('mainID' => $main_image_id); 40 - Javelin::initBehavior('pholio-mock-view', $config); 41 - 42 56 $thumbnails = array(); 43 57 foreach ($this->mock->getImages() as $image) { 44 58 $thumbfile = $image->getFile(); ··· 51 65 'class' => 'pholio-mock-carousel-thumbnail', 52 66 'meta' => array( 53 67 'fullSizeURI' => $thumbfile->getBestURI(), 54 - 'imageID' => $image->getID(), 68 + 'imageID' => $image->getID() 55 69 ), 56 70 )); 57 71 $thumbnails[] = $tag; ··· 67 81 68 82 return $this->renderHTMLView($mockview); 69 83 } 70 - 71 84 }
+4 -1
src/applications/repository/worker/commitchangeparser/PhabricatorRepositorySvnCommitChangeParserWorker.php
··· 266 266 if (empty($raw_paths[$full_from]) && 267 267 empty($effects[$full_from])) { 268 268 if ($other_type == DifferentialChangeType::TYPE_COPY_AWAY) { 269 + // Add an indirect effect for the copied file, if we 270 + // don't already have an entry for it (e.g., a separate 271 + // change). 269 272 $effects[$full_from] = array( 270 273 'rawPath' => $full_from, 271 274 'rawTargetPath' => null, 272 275 'rawTargetCommit' => null, 273 - 'rawDirect' => true, 276 + 'rawDirect' => false, 274 277 275 278 'changeType' => $other_type, 276 279 'fileType' => $from_file_type,
+2 -1
src/applications/search/engine/PhabricatorSearchEngineMySQL.php
··· 161 161 if (strlen($q)) { 162 162 $join[] = qsprintf( 163 163 $conn_r, 164 - "{$t_field} field ON field.phid = document.phid"); 164 + '%T field ON field.phid = document.phid', 165 + $t_field); 165 166 $where[] = qsprintf( 166 167 $conn_r, 167 168 'MATCH(corpus) AGAINST (%s IN BOOLEAN MODE)',
-1
src/applications/settings/application/PhabricatorApplicationSettings.php
··· 44 44 $item->setIcon('settings'); 45 45 $item->setSelected($selected); 46 46 $item->setHref('/settings/'); 47 - $item->setSortOrder(0.90); 48 47 $items[] = $item; 49 48 } 50 49
+1 -1
src/docs/userguide/remarkup.diviner
··· 7 7 = Overview = 8 8 9 9 Phabricator uses a lightweight markup language called "Remarkup", similar to 10 - other lightweight markup langauges like Markdown and Wiki markup. 10 + other lightweight markup languages like Markdown and Wiki markup. 11 11 12 12 This document describes how to format text using Remarkup. 13 13
+3 -1
src/infrastructure/celerity/CelerityPhabricatorResourceController.php
··· 35 35 36 36 protected function buildResourceTransformer() { 37 37 $xformer = new CelerityResourceTransformer(); 38 - $xformer->setMinify(PhabricatorEnv::getEnvConfig('celerity.minify')); 38 + $xformer->setMinify( 39 + !PhabricatorEnv::getEnvConfig('phabricator.developer-mode') && 40 + PhabricatorEnv::getEnvConfig('celerity.minify')); 39 41 $xformer->setCelerityMap(CelerityResourceMap::getInstance()); 40 42 return $xformer; 41 43 }
+1 -1
src/infrastructure/celerity/CelerityResourceController.php
··· 35 35 } 36 36 37 37 if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && 38 - !PhabricatorEnv::getEnvConfig('celerity.force-disk-reads')) { 38 + !PhabricatorEnv::getEnvConfig('phabricator.developer-mode')) { 39 39 // Return a "304 Not Modified". We don't care about the value of this 40 40 // field since we never change what resource is served by a given URI. 41 41 return $this->makeResponseCacheable(new Aphront304Response());
+1 -1
src/infrastructure/celerity/CelerityResourceTransformer.php
··· 74 74 $bin = $root.'/externals/javelin/support/jsxmin/jsxmin'; 75 75 76 76 if (@file_exists($bin)) { 77 - $future = new ExecFuture("{$bin} __DEV__:0"); 77 + $future = new ExecFuture('%s __DEV__:0', $bin); 78 78 $future->write($data); 79 79 list($err, $result) = $future->resolve(); 80 80 if (!$err) {
+6 -6
src/infrastructure/internationalization/PhabricatorBaseEnglishTranslation.php
··· 167 167 '%d Lint Messages', 168 168 ), 169 169 170 - 'This is a binary file. It is %2$s byte(s) in length.' => array( 171 - 'This is a binary file. It is %2$s byte in length.', 172 - 'This is a binary file. It is %2$s bytes in length.', 170 + 'This is a binary file. It is %s byte(s) in length.' => array( 171 + 'This is a binary file. It is %s byte in length.', 172 + 'This is a binary file. It is %s bytes in length.', 173 173 ), 174 174 175 175 '%d Action(s) Have No Effect' => array( ··· 226 226 ), 227 227 ), 228 228 229 - '%2$s Line(s)' => array( 230 - '%2$s Line', 231 - '%2$s Lines', 229 + '%s Line(s)' => array( 230 + '%s Line', 231 + '%s Lines', 232 232 ), 233 233 234 234 "Indexing %d object(s) of type %s." => array(
+3
src/infrastructure/javelin/markup.php
··· 1 1 <?php 2 2 3 + /** 4 + * @deprecated Use javelin_tag(). 5 + */ 3 6 function javelin_render_tag( 4 7 $tag, 5 8 array $attributes = array(),
+1 -3
src/infrastructure/lint/linter/PhabricatorJavelinLinter.php
··· 187 187 } 188 188 189 189 private function newSymbolsFuture($path) { 190 - $javelinsymbols = 'javelinsymbols'; 191 - 192 - $future = new ExecFuture($javelinsymbols.' # '.escapeshellarg($path)); 190 + $future = new ExecFuture('javelinsymbols # %s', $path); 193 191 $future->write($this->getData($path)); 194 192 return $future; 195 193 }
+4 -1
src/infrastructure/markup/rule/PhabricatorRemarkupRuleEmbedFile.php
··· 68 68 case 'thumb': 69 69 default: 70 70 $attrs['src'] = $file->getPreview220URI(); 71 - $attrs['width'] = '220'; 71 + $dimensions = 72 + PhabricatorImageTransformer::getPreviewDimensions($file, 220); 73 + $attrs['width'] = $dimensions['sdx']; 74 + $attrs['height'] = $dimensions['sdy']; 72 75 $options['image_class'] = 'phabricator-remarkup-embed-image'; 73 76 break; 74 77 }
+1 -1
src/infrastructure/markup/rule/PhabricatorRemarkupRuleImageMacro.php
··· 10 10 11 11 public function apply($text) { 12 12 return preg_replace_callback( 13 - '@^([a-zA-Z0-9_\-]+)$@m', 13 + '@^([a-zA-Z0-9:_\-]+)$@m', 14 14 array($this, 'markupImageMacro'), 15 15 $text); 16 16 }
+4 -1
src/view/layout/AphrontSideNavFilterView.php
··· 121 121 } 122 122 123 123 public function addCustomBlock($block) { 124 - $this->menu->appendChild($block); 124 + $this->menu->addMenuItem( 125 + id(new PhabricatorMenuItemView()) 126 + ->setType(PhabricatorMenuItemView::TYPE_CUSTOM) 127 + ->appendChild($block)); 125 128 return $this; 126 129 } 127 130
+1 -10
src/view/layout/PhabricatorMenuItemView.php
··· 6 6 const TYPE_SPACER = 'type-spacer'; 7 7 const TYPE_LABEL = 'type-label'; 8 8 const TYPE_BUTTON = 'type-button'; 9 + const TYPE_CUSTOM = 'type-custom'; 9 10 10 11 private $name; 11 12 private $href; 12 13 private $type = self::TYPE_LINK; 13 14 private $isExternal; 14 15 private $key; 15 - private $sortOrder = 1.0; 16 16 private $icon; 17 17 private $selected; 18 18 ··· 86 86 87 87 public function getIsExternal() { 88 88 return $this->isExternal; 89 - } 90 - 91 - public function setSortOrder($order) { 92 - $this->sortOrder = $order; 93 - return $this; 94 - } 95 - 96 - public function getSortOrder() { 97 - return $this->sortOrder; 98 89 } 99 90 100 91 protected function getTagName() {
+81 -3
src/view/layout/PhabricatorMenuView.php
··· 4 4 5 5 private $items = array(); 6 6 7 + protected function canAppendChild() { 8 + return false; 9 + } 10 + 7 11 public function newLabel($name) { 8 12 $item = id(new PhabricatorMenuItemView()) 9 13 ->setType(PhabricatorMenuItemView::TYPE_LABEL) ··· 37 41 } 38 42 39 43 public function addMenuItem(PhabricatorMenuItemView $item) { 40 - $key = $item->getKey(); 41 - $this->items[] = $item; 42 - $this->appendChild($item); 44 + return $this->addMenuItemAfter(null, $item); 45 + } 46 + 47 + public function addMenuItemAfter($key, PhabricatorMenuItemView $item) { 48 + if ($key === null) { 49 + $this->items[] = $item; 50 + return $this; 51 + } 52 + 53 + if (!$this->getItem($key)) { 54 + throw new Exception("No such key '{$key}' to add menu item after!"); 55 + } 43 56 57 + $result = array(); 58 + foreach ($this->items as $other) { 59 + $result[] = $other; 60 + if ($other->getKey() == $key) { 61 + $result[] = $item; 62 + } 63 + } 64 + 65 + $this->items = $result; 44 66 return $this; 45 67 } 46 68 69 + public function addMenuItemBefore($key, PhabricatorMenuItemView $item) { 70 + if ($key === null) { 71 + array_unshift($this->items, $item); 72 + return $this; 73 + } 74 + 75 + $this->requireKey($key); 76 + 77 + $result = array(); 78 + foreach ($this->items as $other) { 79 + if ($other->getKey() == $key) { 80 + $result[] = $item; 81 + } 82 + $result[] = $other; 83 + } 84 + 85 + $this->items = $result; 86 + return $this; 87 + } 88 + 89 + public function addMenuItemToLabel($key, PhabricatorMenuItemView $item) { 90 + $this->requireKey($key); 91 + 92 + $other = $this->getItem($key); 93 + if ($other->getType() != PhabricatorMenuItemView::TYPE_LABEL) { 94 + throw new Exception("Menu item '{$key}' is not a label!"); 95 + } 96 + 97 + $seen = false; 98 + $after = null; 99 + foreach ($this->items as $other) { 100 + if (!$seen) { 101 + if ($other->getKey() == $key) { 102 + $seen = true; 103 + } 104 + } else { 105 + if ($other->getType() == PhabricatorMenuItemView::TYPE_LABEL) { 106 + break; 107 + } 108 + } 109 + $after = $other->getKey(); 110 + } 111 + 112 + return $this->addMenuItemAfter($after, $item); 113 + } 114 + 115 + private function requireKey($key) { 116 + if (!$this->getItem($key)) { 117 + throw new Exception("No menu item with key '{$key}' exists!"); 118 + } 119 + } 120 + 47 121 public function getItem($key) { 48 122 $key = (string)$key; 49 123 ··· 82 156 return array( 83 157 'class' => 'phabricator-menu-view', 84 158 ); 159 + } 160 + 161 + protected function getTagContent() { 162 + return $this->renderSingleView($this->items); 85 163 } 86 164 }
+141
src/view/layout/__tests__/PhabricatorMenuViewTestCase.php
··· 1 + <?php 2 + 3 + final class PhabricatorMenuViewTestCase extends PhabricatorTestCase { 4 + 5 + public function testAppend() { 6 + $menu = $this->newABCMenu(); 7 + 8 + $this->assertMenuKeys( 9 + array( 10 + 'a', 11 + 'b', 12 + 'c', 13 + ), 14 + $menu); 15 + } 16 + 17 + public function testAppendAfter() { 18 + $menu = $this->newABCMenu(); 19 + 20 + $caught = null; 21 + try { 22 + $menu->addMenuItemAfter('x', $this->newLink('test1')); 23 + } catch (Exception $ex) { 24 + $caught = $ex; 25 + } 26 + $this->assertEqual(true, $caught instanceof Exception); 27 + 28 + $menu->addMenuItemAfter('a', $this->newLink('test2')); 29 + $menu->addMenuItemAfter(null, $this->newLink('test3')); 30 + $menu->addMenuItemAfter('a', $this->newLink('test4')); 31 + $menu->addMenuItemAfter('test3', $this->newLink('test5')); 32 + 33 + $this->assertMenuKeys( 34 + array( 35 + 'a', 36 + 'test4', 37 + 'test2', 38 + 'b', 39 + 'c', 40 + 'test3', 41 + 'test5', 42 + ), 43 + $menu); 44 + } 45 + 46 + public function testAppendBefore() { 47 + $menu = $this->newABCMenu(); 48 + 49 + $caught = null; 50 + try { 51 + $menu->addMenuItemBefore('x', $this->newLink('test1')); 52 + } catch (Exception $ex) { 53 + $caught = $ex; 54 + } 55 + $this->assertEqual(true, $caught instanceof Exception); 56 + 57 + $menu->addMenuItemBefore('b', $this->newLink('test2')); 58 + $menu->addMenuItemBefore(null, $this->newLink('test3')); 59 + $menu->addMenuItemBefore('a', $this->newLink('test4')); 60 + $menu->addMenuItemBefore('test3', $this->newLink('test5')); 61 + 62 + $this->assertMenuKeys( 63 + array( 64 + 'test5', 65 + 'test3', 66 + 'test4', 67 + 'a', 68 + 'test2', 69 + 'b', 70 + 'c', 71 + ), 72 + $menu); 73 + } 74 + 75 + public function testAppendLabel() { 76 + $menu = new PhabricatorMenuView(); 77 + $menu->addMenuItem($this->newLabel('fruit')); 78 + $menu->addMenuItem($this->newLabel('animals')); 79 + 80 + $caught = null; 81 + try { 82 + $menu->addMenuItemToLabel('x', $this->newLink('test1')); 83 + } catch (Exception $ex) { 84 + $caught = $ex; 85 + } 86 + $this->assertEqual(true, $caught instanceof Exception); 87 + 88 + $menu->addMenuItemToLabel('fruit', $this->newLink('apple')); 89 + $menu->addMenuItemToLabel('fruit', $this->newLink('banana')); 90 + 91 + $menu->addMenuItemToLabel('animals', $this->newLink('dog')); 92 + $menu->addMenuItemToLabel('animals', $this->newLink('cat')); 93 + 94 + $menu->addMenuItemToLabel('fruit', $this->newLink('cherry')); 95 + 96 + $this->assertMenuKeys( 97 + array( 98 + 'fruit', 99 + 'apple', 100 + 'banana', 101 + 'cherry', 102 + 'animals', 103 + 'dog', 104 + 'cat', 105 + ), 106 + $menu); 107 + } 108 + 109 + private function newLink($key) { 110 + return id(new PhabricatorMenuItemView()) 111 + ->setKey($key) 112 + ->setHref('#') 113 + ->setName('Link'); 114 + } 115 + 116 + private function newLabel($key) { 117 + return id(new PhabricatorMenuItemView()) 118 + ->setType(PhabricatorMenuItemView::TYPE_LABEL) 119 + ->setKey($key) 120 + ->setName('Label'); 121 + } 122 + 123 + private function newABCMenu() { 124 + $menu = new PhabricatorMenuView(); 125 + 126 + $menu->addMenuItem($this->newLink('a')); 127 + $menu->addMenuItem($this->newLink('b')); 128 + $menu->addMenuItem($this->newLink('c')); 129 + 130 + return $menu; 131 + } 132 + 133 + private function assertMenuKeys(array $expect, PhabricatorMenuView $menu) { 134 + $items = $menu->getItems(); 135 + $keys = mpull($items, 'getKey'); 136 + $keys = array_values($keys); 137 + 138 + $this->assertEqual($expect, $keys); 139 + } 140 + 141 + }
+5 -1
src/view/page/PhabricatorBarePageView.php
··· 83 83 84 84 '<script type="text/javascript">'. 85 85 $framebust. 86 - 'window.__DEV__=1;'. 86 + 'window.__DEV__='. 87 + (PhabricatorEnv::getEnvConfig('phabricator.developer-mode') 88 + ? '1' 89 + : '0'). 90 + ';'. 87 91 '</script>', 88 92 89 93 $response->renderResourcesOfType('css'),
+1 -1
src/view/page/PhabricatorStandardPageView.php
··· 256 256 } 257 257 258 258 $developer_warning = null; 259 - if (PhabricatorEnv::getEnvConfig('phabricator.show-error-callout') && 259 + if (PhabricatorEnv::getEnvConfig('phabricator.developer-mode') && 260 260 DarkConsoleErrorLogPluginAPI::getErrors()) { 261 261 $developer_warning = phutil_tag( 262 262 'div',
-17
src/view/page/menu/PhabricatorMainMenuIconView.php
··· 5 5 private $classes = array(); 6 6 private $href; 7 7 private $name; 8 - private $sortOrder = 0.5; 9 8 private $workflow; 10 9 private $style; 11 10 ··· 40 39 public function addStyle($style) { 41 40 $this->style = $style; 42 41 return $this; 43 - } 44 - 45 - /** 46 - * Provide a float, where 0.0 is the profile item and 1.0 is the logout 47 - * item. Normally you should pick something between the two. 48 - * 49 - * @param float Sort order. 50 - * @return this 51 - */ 52 - public function setSortOrder($sort_order) { 53 - $this->sortOrder = $sort_order; 54 - return $this; 55 - } 56 - 57 - public function getSortOrder() { 58 - return $this->sortOrder; 59 42 } 60 43 61 44 public function render() {
+5 -4
src/view/page/menu/PhabricatorMainMenuView.php
··· 164 164 $controller = $this->getController(); 165 165 166 166 $applications = PhabricatorApplication::getAllInstalledApplications(); 167 - $applications = msort($applications, 'getName'); 168 167 169 168 $core = array(); 170 169 $more = array(); ··· 184 183 if ($application->getApplicationGroup() == $group_core) { 185 184 $core[] = $item; 186 185 } else { 187 - $more[] = $item; 186 + $more[$application->getName()] = $item; 188 187 } 189 188 } 190 189 ··· 200 199 $view->addClass('phabricator-core-menu'); 201 200 202 201 $search = $this->renderSearch(); 203 - $view->appendChild($search); 202 + if ($search) { 203 + $view->addMenuItem($search); 204 + } 204 205 205 206 $view 206 207 ->newLabel(pht('Home')) ··· 235 236 } 236 237 237 238 if ($actions) { 238 - $actions = msort($actions, 'getSortOrder'); 239 239 $view->addMenuItem( 240 240 id(new PhabricatorMenuItemView()) 241 241 ->addClass('phabricator-core-item-device') ··· 260 260 ->addClass('phabricator-core-item-device') 261 261 ->setType(PhabricatorMenuItemView::TYPE_LABEL) 262 262 ->setName(pht('More Applications'))); 263 + ksort($more); 263 264 foreach ($more as $item) { 264 265 $item->addClass('phabricator-core-item-device'); 265 266 $view->addMenuItem($item);
+14 -1
webroot/rsrc/css/application/pholio/pholio.css
··· 18 18 } 19 19 20 20 .pholio-mock-image { 21 + display: inline-block; 22 + } 23 + 24 + .pholio-mock-select { 25 + border: 1px solid #FF0000; 26 + position: absolute; 27 + } 28 + 29 + .pholio-mock-wrapper { 30 + position: relative; 31 + display: inline-block; 32 + cursor: crosshair; 33 + padding: 0px; 21 34 margin: 10px 0px; 22 - display: inline-block; 23 35 } 36 +
+5 -1
webroot/rsrc/css/layout/phabricator-header-view.css
··· 24 24 } 25 25 26 26 .phabricator-header-tags { 27 - margin-left: 1em; 27 + margin-left: 12px; 28 28 font-size: 13px; 29 29 } 30 + 31 + .phabricator-header-tags .phabricator-tag-view { 32 + margin-left: 4px; 33 + }
+44 -2
webroot/rsrc/js/application/differential/behavior-populate.js
··· 6 6 * javelin-dom 7 7 * javelin-stratcom 8 8 * javelin-behavior-device 9 + * javelin-vector 9 10 * phabricator-tooltip 10 11 */ 11 12 12 13 JX.behavior('differential-populate', function(config) { 13 14 14 - function onresponse(target, response) { 15 - JX.DOM.replace(JX.$(target), JX.$H(response.changeset)); 15 + function onresponse(target_id, response) { 16 + // As we populate the diff, we try to hold the document scroll position 17 + // steady, so that, e.g., users who want to leave a comment on a diff with a 18 + // large number of changes don't constantly have the text area scrolled off 19 + // the bottom of the screen until the entire diff loads. 20 + // 21 + // There are two three major cases here: 22 + // 23 + // - If we're near the top of the document, never scroll. 24 + // - If we're near the bottom of the document, always scroll. 25 + // - Otherwise, scroll if the changes were above the midline of the 26 + // viewport. 27 + var target = JX.$(target_id); 28 + 29 + var old_pos = JX.Vector.getScroll(); 30 + var old_view = JX.Vector.getViewport(); 31 + var old_dim = JX.Vector.getDocument(); 32 + 33 + // Number of pixels away from the top or bottom of the document which 34 + // count as "nearby". 35 + var sticky = 480; 36 + 37 + var near_top = (old_pos.y <= sticky); 38 + var near_bot = ((old_pos.y + old_view.y) >= (old_dim.y - sticky)); 39 + 40 + var target_pos = JX.Vector.getPos(target); 41 + var target_dim = JX.Vector.getDim(target); 42 + var target_mid = (target_pos.y + (target_dim.y / 2)); 43 + 44 + var view_mid = (old_pos.y + (old_view.y / 2)); 45 + var above_mid = (target_mid < view_mid); 46 + 47 + JX.DOM.replace(target, JX.$H(response.changeset)); 48 + 49 + if (!near_top) { 50 + if (near_bot || above_mid) { 51 + // Figure out how much taller the document got. 52 + var delta = (JX.Vector.getDocument().y - old_dim.y); 53 + 54 + window.scrollTo(old_pos.x, old_pos.y + delta); 55 + } 56 + } 57 + 16 58 if (response.coverage) { 17 59 for (var k in response.coverage) { 18 60 try {
+110
webroot/rsrc/js/application/pholio/behavior-pholio-mock-view.js
··· 2 2 * @provides javelin-behavior-pholio-mock-view 3 3 * @requires javelin-behavior 4 4 * javelin-stratcom 5 + * javelin-dom 6 + * javelin-vector 7 + * javelin-event 5 8 */ 6 9 JX.behavior('pholio-mock-view', function(config) { 10 + var is_dragging = false; 11 + var wrapper = JX.$('mock-wrapper'); 12 + var image; 13 + var imageData; 14 + var startPos; 15 + var endPos; 16 + var selection; 17 + 7 18 JX.Stratcom.listen( 8 19 'click', // Listen for clicks... 9 20 'mock-thumbnail', // ...on nodes with sigil "mock-thumbnail". ··· 11 22 var data = e.getNodeData('mock-thumbnail'); 12 23 13 24 var main = JX.$(config.mainID); 25 + JX.Stratcom.addData( 26 + main, 27 + { 28 + fullSizeURI: data['fullSizeURI'], 29 + imageID: data['imageID'] 30 + }); 31 + 14 32 main.src = data.fullSizeURI; 33 + 34 + JX.DOM.setContent(wrapper,main); 15 35 }); 36 + 37 + 38 + function draw_rectangle(node, current, init) { 39 + JX.$V( 40 + Math.abs(current.x-init.x), 41 + Math.abs(current.y-init.y)) 42 + .setDim(node); 43 + 44 + JX.$V( 45 + (current.x-init.x < 0) ? current.x:init.x, 46 + (current.y-init.y < 0) ? current.y:init.y) 47 + .setPos(node); 48 + } 49 + 50 + function getRealXY(parent, point) { 51 + var pos = {x: (point.x - parent.x), y: (point.y - parent.y)}; 52 + 53 + if (pos.x < 0) pos.x = 0; 54 + else if (pos.x > image.clientWidth) pos.x = image.clientWidth - 1; 55 + 56 + if (pos.y < 0) pos.y = 0; 57 + else if (pos.y > image.clientHeight) pos.y = image.clientHeight - 2; 58 + 59 + return pos; 60 + } 61 + 62 + JX.Stratcom.listen('mousedown', 'mock-wrapper', function(e) { 63 + if (!e.isNormalMouseEvent()) { 64 + return; 65 + } 66 + 67 + image = JX.$(config.mainID); 68 + imageData = JX.Stratcom.getData(image); 69 + 70 + e.getRawEvent().target.draggable = false; 71 + is_dragging = true; 72 + 73 + startPos = getRealXY(JX.$V(wrapper),JX.$V(e)); 74 + 75 + selection = JX.$N( 76 + 'div', 77 + {className: 'pholio-mock-select'} 78 + ); 79 + 80 + 81 + JX.$V(startPos.x,startPos.y).setPos(selection); 82 + 83 + JX.DOM.appendContent(wrapper, selection); 84 + 85 + 86 + }); 87 + 88 + JX.enableDispatch(document.body, 'mousemove'); 89 + JX.Stratcom.listen('mousemove',null, function(e) { 90 + if (!is_dragging) { 91 + return; 92 + } 93 + 94 + draw_rectangle(selection, getRealXY(JX.$V(wrapper), JX.$V(e)), startPos); 95 + }); 96 + 97 + JX.Stratcom.listen( 98 + 'mouseup', 99 + null, 100 + function(e) { 101 + if (!is_dragging) { 102 + return; 103 + } 104 + is_dragging = false; 105 + 106 + endPos = getRealXY(JX.$V(wrapper), JX.$V(e)); 107 + 108 + comment = window.prompt("Add your comment"); 109 + if (comment == null || comment == "") { 110 + selection.remove(); 111 + return; 112 + } 113 + 114 + selection.title = comment; 115 + 116 + console.log("ImageID: " + imageData['imageID'] + 117 + ", coords: (" + Math.min(startPos.x, endPos.x) + "," + 118 + Math.min(startPos.y, endPos.y) + ") -> (" + 119 + Math.max(startPos.x,endPos.x) + "," + Math.max(startPos.y,endPos.y) + 120 + "), comment: " + comment); 121 + 122 + }); 123 + 16 124 }); 17 125 126 + 127 +