@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 standard UI elements to render pull requests in Releeph

Summary:
Ref T3718. Ref T3644. Ref T3092. Switches from the Releeph UI elements to standard ones. I'll attach some screenshots.

Also fixes CSRF against the request action endpoint.

Test Plan:
- Viewed request details.
- Took actions on a request from detail page.
- Viewed request list.
- Took actions on a request from list page.
- Used keyboard shortcuts to navigate list.
- Used keyboard shortcuts to take actions.
- Simulated errors.
- Viewed on devices.

Reviewers: chad, btrahan

Reviewed By: btrahan

Subscribers: grp, FacebookPOC, mattlqx, tala, beng, LegNeato, epriestley

Maniphest Tasks: T3718, T3092, T3644

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

+573 -1468
+15 -27
resources/celerity/map.php
··· 18 18 'maniphest.pkg.css' => 'f1887d71', 19 19 'maniphest.pkg.js' => '2fe8af22', 20 20 'rsrc/css/aphront/aphront-bars.css' => '231ac33c', 21 - 'rsrc/css/aphront/aphront-notes.css' => '6acadd3f', 22 21 'rsrc/css/aphront/context-bar.css' => '1c3b0529', 23 22 'rsrc/css/aphront/dark-console.css' => '6378ef3d', 24 23 'rsrc/css/aphront/dialog-view.css' => 'c01d24b4', ··· 95 94 'rsrc/css/application/ponder/vote.css' => '8ed6ed8b', 96 95 'rsrc/css/application/profile/profile-view.css' => '33e6f703', 97 96 'rsrc/css/application/projects/project-tag.css' => '095c9404', 98 - 'rsrc/css/application/releeph/releeph-branch.css' => 'b8821d2d', 99 - 'rsrc/css/application/releeph/releeph-colors.css' => '2d2d6aa8', 100 - 'rsrc/css/application/releeph/releeph-core.css' => '140b959d', 101 - 'rsrc/css/application/releeph/releeph-intents.css' => '39065521', 97 + 'rsrc/css/application/releeph/releeph-core.css' => '9b3c5733', 102 98 'rsrc/css/application/releeph/releeph-preview-branch.css' => 'b7a6f4a5', 103 - 'rsrc/css/application/releeph/releeph-project.css' => 'ee1f9f57', 104 99 'rsrc/css/application/releeph/releeph-request-differential-create-dialog.css' => '8d8b92cd', 105 100 'rsrc/css/application/releeph/releeph-request-typeahead.css' => '667a48ae', 106 - 'rsrc/css/application/releeph/releeph-status.css' => 'd119a005', 107 101 'rsrc/css/application/search/search-results.css' => 'f240504c', 108 102 'rsrc/css/application/settings/settings.css' => 'ea8f5915', 109 103 'rsrc/css/application/slowvote/slowvote.css' => '266df6a1', ··· 128 122 'rsrc/css/phui/calendar/phui-calendar-list.css' => 'c1d0ca59', 129 123 'rsrc/css/phui/calendar/phui-calendar-month.css' => 'a92e47d2', 130 124 'rsrc/css/phui/calendar/phui-calendar.css' => '5e1ad989', 131 - 'rsrc/css/phui/phui-box.css' => 'a36cf3a5', 125 + 'rsrc/css/phui/phui-box.css' => '7b3a2eed', 132 126 'rsrc/css/phui/phui-button.css' => '653ac588', 133 127 'rsrc/css/phui/phui-document.css' => '3b078dc0', 134 128 'rsrc/css/phui/phui-feed-story.css' => '3a59c2cf', ··· 410 404 'rsrc/js/application/projects/behavior-project-boards.js' => 'd8e135db', 411 405 'rsrc/js/application/projects/behavior-project-create.js' => '065227cc', 412 406 'rsrc/js/application/releeph/releeph-preview-branch.js' => '9eb2cedb', 413 - 'rsrc/js/application/releeph/releeph-request-state-change.js' => 'fe7fc914', 407 + 'rsrc/js/application/releeph/releeph-request-state-change.js' => 'd259e7c9', 414 408 'rsrc/js/application/releeph/releeph-request-typeahead.js' => 'cd9e7094', 415 409 'rsrc/js/application/repository/repository-crossreference.js' => '8ab282be', 416 410 'rsrc/js/application/search/behavior-reorder-queries.js' => '37871df4', ··· 493 487 'aphront-error-view-css' => '9f1d5518', 494 488 'aphront-list-filter-view-css' => 'ef989c67', 495 489 'aphront-multi-column-view-css' => '12f65921', 496 - 'aphront-notes' => '6acadd3f', 497 490 'aphront-pager-view-css' => '2e3539af', 498 491 'aphront-panel-view-css' => '5846dfa2', 499 492 'aphront-request-failure-view-css' => 'da14df31', ··· 621 614 'javelin-behavior-project-create' => '065227cc', 622 615 'javelin-behavior-refresh-csrf' => 'c4b31646', 623 616 'javelin-behavior-releeph-preview-branch' => '9eb2cedb', 624 - 'javelin-behavior-releeph-request-state-change' => 'fe7fc914', 617 + 'javelin-behavior-releeph-request-state-change' => 'd259e7c9', 625 618 'javelin-behavior-releeph-request-typeahead' => 'cd9e7094', 626 619 'javelin-behavior-remarkup-preview' => 'f7379f45', 627 620 'javelin-behavior-repository-crossreference' => '8ab282be', ··· 743 736 'phortune-credit-card-form-css' => 'b25b4beb', 744 737 'phrequent-css' => 'ffc185ad', 745 738 'phriction-document-css' => '7d7f0071', 746 - 'phui-box-css' => 'a36cf3a5', 739 + 'phui-box-css' => '7b3a2eed', 747 740 'phui-button-css' => '653ac588', 748 741 'phui-calendar-css' => '5e1ad989', 749 742 'phui-calendar-day-css' => 'de035c8a', ··· 779 772 'raphael-core' => '51ee6b43', 780 773 'raphael-g' => '40dde778', 781 774 'raphael-g-line' => '40da039e', 782 - 'releeph-branch' => 'b8821d2d', 783 - 'releeph-colors' => '2d2d6aa8', 784 - 'releeph-core' => '140b959d', 785 - 'releeph-intents' => '39065521', 775 + 'releeph-core' => '9b3c5733', 786 776 'releeph-preview-branch' => 'b7a6f4a5', 787 - 'releeph-project' => 'ee1f9f57', 788 777 'releeph-request-differential-create-dialog' => '8d8b92cd', 789 778 'releeph-request-typeahead-css' => '667a48ae', 790 - 'releeph-status' => 'd119a005', 791 779 'setup-issue-css' => '69e640e7', 792 780 'sprite-actions-css' => '969ad0e5', 793 781 'sprite-apps-css' => '6973a52b', ··· 1727 1715 array( 1728 1716 0 => 'javelin-util', 1729 1717 ), 1718 + 'd259e7c9' => 1719 + array( 1720 + 0 => 'javelin-behavior', 1721 + 1 => 'javelin-dom', 1722 + 2 => 'javelin-stratcom', 1723 + 3 => 'javelin-workflow', 1724 + 4 => 'javelin-util', 1725 + 5 => 'phabricator-keyboard-shortcut', 1726 + ), 1730 1727 'd4a14807' => 1731 1728 array( 1732 1729 0 => 'javelin-install', ··· 1949 1946 3 => 'javelin-vector', 1950 1947 4 => 'javelin-dom', 1951 1948 5 => 'javelin-magical-init', 1952 - ), 1953 - 'fe7fc914' => 1954 - array( 1955 - 0 => 'javelin-behavior', 1956 - 1 => 'javelin-dom', 1957 - 2 => 'javelin-stratcom', 1958 - 3 => 'javelin-request', 1959 - 4 => 'phabricator-keyboard-shortcut', 1960 - 5 => 'phabricator-notification', 1961 1949 ), 1962 1950 'fe80fb6d' => 1963 1951 array(
+2 -14
src/__phutil_library_map__.php
··· 65 65 'AphrontMoreView' => 'view/layout/AphrontMoreView.php', 66 66 'AphrontMultiColumnView' => 'view/layout/AphrontMultiColumnView.php', 67 67 'AphrontMySQLDatabaseConnectionTestCase' => 'infrastructure/storage/__tests__/AphrontMySQLDatabaseConnectionTestCase.php', 68 - 'AphrontNoteView' => 'view/widget/AphrontNoteView.php', 69 68 'AphrontNullView' => 'view/AphrontNullView.php', 70 69 'AphrontPHPHTTPSink' => 'aphront/sink/AphrontPHPHTTPSink.php', 71 70 'AphrontPageView' => 'view/page/AphrontPageView.php', ··· 1716 1715 'PhabricatorMySQLFileStorageEngine' => 'applications/files/engine/PhabricatorMySQLFileStorageEngine.php', 1717 1716 'PhabricatorNamedQuery' => 'applications/search/storage/PhabricatorNamedQuery.php', 1718 1717 'PhabricatorNamedQueryQuery' => 'applications/search/query/PhabricatorNamedQueryQuery.php', 1719 - 'PhabricatorNoteExample' => 'applications/uiexample/examples/PhabricatorNoteExample.php', 1720 1718 'PhabricatorNotificationAdHocFeedStory' => 'applications/notification/feed/PhabricatorNotificationAdHocFeedStory.php', 1721 1719 'PhabricatorNotificationBuilder' => 'applications/notification/builder/PhabricatorNotificationBuilder.php', 1722 1720 'PhabricatorNotificationClearController' => 'applications/notification/controller/PhabricatorNotificationClearController.php', ··· 2555 2553 'ReleephRequestController' => 'applications/releeph/controller/request/ReleephRequestController.php', 2556 2554 'ReleephRequestDifferentialCreateController' => 'applications/releeph/controller/request/ReleephRequestDifferentialCreateController.php', 2557 2555 'ReleephRequestEditController' => 'applications/releeph/controller/request/ReleephRequestEditController.php', 2558 - 'ReleephRequestHeaderListView' => 'applications/releeph/view/request/header/ReleephRequestHeaderListView.php', 2559 - 'ReleephRequestHeaderView' => 'applications/releeph/view/request/header/ReleephRequestHeaderView.php', 2560 - 'ReleephRequestIntentsView' => 'applications/releeph/view/request/ReleephRequestIntentsView.php', 2561 2556 'ReleephRequestMailReceiver' => 'applications/releeph/mail/ReleephRequestMailReceiver.php', 2562 2557 'ReleephRequestQuery' => 'applications/releeph/query/ReleephRequestQuery.php', 2563 2558 'ReleephRequestReplyHandler' => 'applications/releeph/mail/ReleephRequestReplyHandler.php', 2564 2559 'ReleephRequestSearchEngine' => 'applications/releeph/query/ReleephRequestSearchEngine.php', 2565 2560 'ReleephRequestStatus' => 'applications/releeph/constants/ReleephRequestStatus.php', 2566 - 'ReleephRequestStatusView' => 'applications/releeph/view/request/ReleephRequestStatusView.php', 2567 2561 'ReleephRequestTransaction' => 'applications/releeph/storage/ReleephRequestTransaction.php', 2568 2562 'ReleephRequestTransactionComment' => 'applications/releeph/storage/ReleephRequestTransactionComment.php', 2569 2563 'ReleephRequestTransactionQuery' => 'applications/releeph/query/ReleephRequestTransactionQuery.php', 2570 2564 'ReleephRequestTransactionalEditor' => 'applications/releeph/editor/ReleephRequestTransactionalEditor.php', 2571 2565 'ReleephRequestTypeaheadControl' => 'applications/releeph/view/request/ReleephRequestTypeaheadControl.php', 2572 2566 'ReleephRequestTypeaheadController' => 'applications/releeph/controller/request/ReleephRequestTypeaheadController.php', 2567 + 'ReleephRequestView' => 'applications/releeph/view/ReleephRequestView.php', 2573 2568 'ReleephRequestViewController' => 'applications/releeph/controller/request/ReleephRequestViewController.php', 2574 2569 'ReleephRequestorFieldSpecification' => 'applications/releeph/field/specification/ReleephRequestorFieldSpecification.php', 2575 2570 'ReleephRevisionFieldSpecification' => 'applications/releeph/field/specification/ReleephRevisionFieldSpecification.php', 2576 2571 'ReleephSeverityFieldSpecification' => 'applications/releeph/field/specification/ReleephSeverityFieldSpecification.php', 2577 - 'ReleephStatusFieldSpecification' => 'applications/releeph/field/specification/ReleephStatusFieldSpecification.php', 2578 2572 'ReleephSummaryFieldSpecification' => 'applications/releeph/field/specification/ReleephSummaryFieldSpecification.php', 2579 2573 'ShellLogView' => 'applications/harbormaster/view/ShellLogView.php', 2580 2574 'SlowvoteEmbedView' => 'applications/slowvote/view/SlowvoteEmbedView.php', ··· 2662 2656 'AphrontMoreView' => 'AphrontView', 2663 2657 'AphrontMultiColumnView' => 'AphrontView', 2664 2658 'AphrontMySQLDatabaseConnectionTestCase' => 'PhabricatorTestCase', 2665 - 'AphrontNoteView' => 'AphrontView', 2666 2659 'AphrontNullView' => 'AphrontView', 2667 2660 'AphrontPHPHTTPSink' => 'AphrontHTTPSink', 2668 2661 'AphrontPageView' => 'AphrontView', ··· 4531 4524 1 => 'PhabricatorPolicyInterface', 4532 4525 ), 4533 4526 'PhabricatorNamedQueryQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 4534 - 'PhabricatorNoteExample' => 'PhabricatorUIExample', 4535 4527 'PhabricatorNotificationAdHocFeedStory' => 'PhabricatorFeedStory', 4536 4528 'PhabricatorNotificationClearController' => 'PhabricatorNotificationController', 4537 4529 'PhabricatorNotificationConfigOptions' => 'PhabricatorApplicationConfigOptions', ··· 5574 5566 'ReleephRequestController' => 'ReleephController', 5575 5567 'ReleephRequestDifferentialCreateController' => 'ReleephController', 5576 5568 'ReleephRequestEditController' => 'ReleephBranchController', 5577 - 'ReleephRequestHeaderListView' => 'AphrontView', 5578 - 'ReleephRequestHeaderView' => 'AphrontView', 5579 - 'ReleephRequestIntentsView' => 'AphrontView', 5580 5569 'ReleephRequestMailReceiver' => 'PhabricatorObjectMailReceiver', 5581 5570 'ReleephRequestQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 5582 5571 'ReleephRequestReplyHandler' => 'PhabricatorMailReplyHandler', 5583 5572 'ReleephRequestSearchEngine' => 'PhabricatorApplicationSearchEngine', 5584 - 'ReleephRequestStatusView' => 'AphrontView', 5585 5573 'ReleephRequestTransaction' => 'PhabricatorApplicationTransaction', 5586 5574 'ReleephRequestTransactionComment' => 'PhabricatorApplicationTransactionComment', 5587 5575 'ReleephRequestTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 5588 5576 'ReleephRequestTransactionalEditor' => 'PhabricatorApplicationTransactionEditor', 5589 5577 'ReleephRequestTypeaheadControl' => 'AphrontFormControl', 5590 5578 'ReleephRequestTypeaheadController' => 'PhabricatorTypeaheadDatasourceController', 5579 + 'ReleephRequestView' => 'AphrontView', 5591 5580 'ReleephRequestViewController' => 'ReleephBranchController', 5592 5581 'ReleephRequestorFieldSpecification' => 'ReleephFieldSpecification', 5593 5582 'ReleephRevisionFieldSpecification' => 'ReleephFieldSpecification', 5594 5583 'ReleephSeverityFieldSpecification' => 'ReleephLevelFieldSpecification', 5595 - 'ReleephStatusFieldSpecification' => 'ReleephFieldSpecification', 5596 5584 'ReleephSummaryFieldSpecification' => 'ReleephFieldSpecification', 5597 5585 'ShellLogView' => 'AphrontView', 5598 5586 'SlowvoteEmbedView' => 'AphrontView',
+5 -6
src/applications/releeph/config/PhabricatorApplicationReleephConfigOptions.php
··· 14 14 public function getOptions() { 15 15 16 16 $default_fields = array( 17 - new ReleephCommitMessageFieldSpecification(), 18 17 new ReleephSummaryFieldSpecification(), 18 + new ReleephRequestorFieldSpecification(), 19 + new ReleephSeverityFieldSpecification(), 20 + new ReleephIntentFieldSpecification(), 19 21 new ReleephReasonFieldSpecification(), 20 22 new ReleephAuthorFieldSpecification(), 21 23 new ReleephRevisionFieldSpecification(), 22 - new ReleephRequestorFieldSpecification(), 23 - new ReleephSeverityFieldSpecification(), 24 24 new ReleephOriginalCommitFieldSpecification(), 25 - new ReleephDiffMessageFieldSpecification(), 26 - new ReleephStatusFieldSpecification(), 27 - new ReleephIntentFieldSpecification(), 28 25 new ReleephBranchCommitFieldSpecification(), 29 26 new ReleephDiffSizeFieldSpecification(), 30 27 new ReleephDiffChurnFieldSpecification(), 28 + new ReleephDiffMessageFieldSpecification(), 29 + new ReleephCommitMessageFieldSpecification(), 31 30 ); 32 31 33 32 $default = array();
+2 -2
src/applications/releeph/constants/ReleephRequestStatus.php
··· 15 15 self::STATUS_REQUESTED => pht('Requested'), 16 16 self::STATUS_REJECTED => pht('Rejected'), 17 17 self::STATUS_ABANDONED => pht('Abandoned'), 18 - self::STATUS_PICKED => pht('Picked'), 18 + self::STATUS_PICKED => pht('Pulled'), 19 19 self::STATUS_REVERTED => pht('Reverted'), 20 - self::STATUS_NEEDS_PICK => pht('Needs Pick'), 20 + self::STATUS_NEEDS_PICK => pht('Needs Pull'), 21 21 self::STATUS_NEEDS_REVERT => pht('Needs Revert'), 22 22 ); 23 23 return idx($descriptions, $status, '??');
+32 -11
src/applications/releeph/controller/branch/ReleephBranchViewController.php
··· 44 44 assert_instances_of($requests, 'ReleephRequest'); 45 45 $viewer = $this->getRequest()->getUser(); 46 46 47 - $branch = $this->getBranch(); 47 + // TODO: This is generally a bit sketchy, but we don't do this kind of 48 + // thing elsewhere at the moment. For the moment it shouldn't be hugely 49 + // costly, and we can batch things later. Generally, this commits fewer 50 + // sins than the old code did. 51 + 52 + $engine = id(new PhabricatorMarkupEngine()) 53 + ->setViewer($viewer); 54 + 55 + $list = array(); 56 + foreach ($requests as $pull) { 57 + $field_list = PhabricatorCustomField::getObjectFields( 58 + $pull, 59 + PhabricatorCustomField::ROLE_VIEW); 60 + 61 + $field_list 62 + ->setViewer($viewer) 63 + ->readFieldsFromStorage($pull); 64 + 65 + foreach ($field_list->getFields() as $field) { 66 + if ($field->shouldMarkup()) { 67 + $field->setMarkupEngine($engine); 68 + } 69 + } 48 70 49 - // TODO: Really really gross. 50 - $branch->populateReleephRequestHandles( 51 - $viewer, 52 - $requests); 71 + $list[] = id(new ReleephRequestView()) 72 + ->setUser($viewer) 73 + ->setCustomFields($field_list) 74 + ->setPullRequest($pull) 75 + ->setIsListView(true); 76 + } 53 77 54 - $list = id(new ReleephRequestHeaderListView()) 55 - ->setUser($viewer) 56 - ->setAphrontRequest($this->getRequest()) 57 - ->setReleephProject($branch->getProduct()) 58 - ->setReleephBranch($branch) 59 - ->setReleephRequests($requests); 78 + // This is quite sketchy, but the list has not actually rendered yet, so 79 + // this still allows us to batch the markup rendering. 80 + $engine->process(); 60 81 61 82 return $list; 62 83 }
+32 -17
src/applications/releeph/controller/request/ReleephRequestActionController.php
··· 15 15 $request = $this->getRequest(); 16 16 $viewer = $request->getUser(); 17 17 18 + $request->validateCSRF(); 19 + 18 20 $pull = id(new ReleephRequestQuery()) 19 21 ->setViewer($viewer) 20 22 ->withIDs(array($this->requestID)) ··· 25 27 26 28 $branch = $pull->getBranch(); 27 29 $product = $branch->getProduct(); 28 - 29 - $branch->populateReleephRequestHandles($viewer, array($pull)); 30 30 31 31 $action = $this->action; 32 32 ··· 93 93 94 94 $editor->applyTransactions($pull, $xactions); 95 95 96 - // If we're adding a new user to userIntents, we'll have to re-populate 97 - // request handles to load that user's data. 98 - // 99 - // This is cheap enough to do every time. 100 - $branch->populateReleephRequestHandles($viewer, array($pull)); 96 + if ($request->getBool('render')) { 97 + $field_list = PhabricatorCustomField::getObjectFields( 98 + $pull, 99 + PhabricatorCustomField::ROLE_VIEW); 100 + 101 + $field_list 102 + ->setViewer($viewer) 103 + ->readFieldsFromStorage($pull); 104 + 105 + // TODO: This should be more modern and general. 106 + $engine = id(new PhabricatorMarkupEngine()) 107 + ->setViewer($viewer); 108 + foreach ($field_list->getFields() as $field) { 109 + if ($field->shouldMarkup()) { 110 + $field->setMarkupEngine($engine); 111 + } 112 + } 113 + $engine->process(); 114 + 115 + $pull_box = id(new ReleephRequestView()) 116 + ->setUser($viewer) 117 + ->setCustomFields($field_list) 118 + ->setPullRequest($pull) 119 + ->setIsListView(true); 101 120 102 - $list = id(new ReleephRequestHeaderListView()) 103 - ->setReleephProject($product) 104 - ->setReleephBranch($branch) 105 - ->setReleephRequests(array($pull)) 106 - ->setUser($viewer) 107 - ->setAphrontRequest($request); 121 + return id(new AphrontAjaxResponse())->setContent( 122 + array( 123 + 'markup' => hsprintf('%s', $pull_box), 124 + )); 125 + } 108 126 109 - return id(new AphrontAjaxResponse())->setContent( 110 - array( 111 - 'markup' => hsprintf('%s', $list->renderInner()), 112 - )); 127 + return id(new AphrontRedirectResponse())->setURI($origin_uri); 113 128 } 114 129 }
+23 -13
src/applications/releeph/controller/request/ReleephRequestViewController.php
··· 33 33 // TODO: Break this 1:1 stuff? 34 34 $branch = $pull->getBranch(); 35 35 36 - // TODO: Very gross. 37 - $branch->populateReleephRequestHandles( 38 - $viewer, 39 - array($pull)); 36 + $field_list = PhabricatorCustomField::getObjectFields( 37 + $pull, 38 + PhabricatorCustomField::ROLE_VIEW); 40 39 41 - $rq_view = id(new ReleephRequestHeaderListView()) 42 - ->setReleephProject($branch->getProduct()) 43 - ->setReleephBranch($branch) 44 - ->setReleephRequests(array($pull)) 45 - ->setUser($viewer) 46 - ->setAphrontRequest($request) 47 - ->setReloadOnStateChange(true); 40 + $field_list 41 + ->setViewer($viewer) 42 + ->readFieldsFromStorage($pull); 48 43 44 + // TODO: This should be more modern and general. 49 45 $engine = id(new PhabricatorMarkupEngine()) 50 46 ->setViewer($viewer); 47 + foreach ($field_list->getFields() as $field) { 48 + if ($field->shouldMarkup()) { 49 + $field->setMarkupEngine($engine); 50 + } 51 + } 52 + $engine->process(); 53 + 54 + $pull_box = id(new ReleephRequestView()) 55 + ->setUser($viewer) 56 + ->setCustomFields($field_list) 57 + ->setPullRequest($pull); 51 58 52 59 $xactions = id(new ReleephRequestTransactionQuery()) 53 60 ->setViewer($viewer) ··· 85 92 return $this->buildStandardPageResponse( 86 93 array( 87 94 $crumbs, 88 - $rq_view, 95 + $pull_box, 89 96 $timeline, 90 97 $add_comment_form, 91 98 ), 92 99 array( 93 - 'title' => $title 100 + 'title' => $title, 101 + 'device' => true, 94 102 )); 95 103 } 104 + 105 + 96 106 }
-87
src/applications/releeph/field/selector/ReleephDefaultFieldSelector.php
··· 37 37 new ReleephFacebookSeverityFieldSpecification(), 38 38 new ReleephOriginalCommitFieldSpecification(), 39 39 new ReleephDiffMessageFieldSpecification(), 40 - new ReleephStatusFieldSpecification(), 41 40 new ReleephIntentFieldSpecification(), 42 41 new ReleephBranchCommitFieldSpecification(), 43 42 new ReleephDiffSizeFieldSpecification(), ··· 57 56 new ReleephSeverityFieldSpecification(), 58 57 new ReleephOriginalCommitFieldSpecification(), 59 58 new ReleephDiffMessageFieldSpecification(), 60 - new ReleephStatusFieldSpecification(), 61 59 new ReleephIntentFieldSpecification(), 62 60 new ReleephBranchCommitFieldSpecification(), 63 61 new ReleephDiffSizeFieldSpecification(), 64 62 new ReleephDiffChurnFieldSpecification(), 65 63 ); 66 - } 67 - } 68 - 69 - public function arrangeFieldsForHeaderView(array $fields) { 70 - if (self::isFacebook()) { 71 - return array( 72 - // Top group 73 - array( 74 - 'left' => self::selectFields($fields, array( 75 - 'ReleephAuthorFieldSpecification', 76 - 'ReleephRevisionFieldSpecification', 77 - 'ReleephOriginalCommitFieldSpecification', 78 - 'ReleephDiffSizeFieldSpecification', 79 - 'ReleephDiffChurnFieldSpecification', 80 - 'ReleephDependsOnFieldSpecification', 81 - 'ReleephFacebookTasksFieldSpecification', 82 - )), 83 - 'right' => self::selectFields($fields, array( 84 - 'ReleephRequestorFieldSpecification', 85 - 'ReleephFacebookKarmaFieldSpecification', 86 - 'ReleephFacebookSeverityFieldSpecification', 87 - 'ReleephFacebookTagFieldSpecification', 88 - 'ReleephStatusFieldSpecification', 89 - 'ReleephIntentFieldSpecification', 90 - 'ReleephBranchCommitFieldSpecification', 91 - )) 92 - ), 93 - 94 - // Bottom group 95 - array( 96 - 'left' => self::selectFields($fields, array( 97 - 'ReleephDiffMessageFieldSpecification', 98 - )), 99 - 'right' => self::selectFields($fields, array( 100 - 'ReleephReasonFieldSpecification', 101 - )) 102 - ) 103 - ); 104 - } else { 105 - return array( 106 - // Top group 107 - array( 108 - 'left' => self::selectFields($fields, array( 109 - 'ReleephAuthorFieldSpecification', 110 - 'ReleephRevisionFieldSpecification', 111 - 'ReleephOriginalCommitFieldSpecification', 112 - 'ReleephDiffSizeFieldSpecification', 113 - 'ReleephDiffChurnFieldSpecification', 114 - )), 115 - 'right' => self::selectFields($fields, array( 116 - 'ReleephRequestorFieldSpecification', 117 - 'ReleephSeverityFieldSpecification', 118 - 'ReleephStatusFieldSpecification', 119 - 'ReleephIntentFieldSpecification', 120 - 'ReleephBranchCommitFieldSpecification', 121 - )) 122 - ), 123 - 124 - // Bottom group 125 - array( 126 - 'left' => self::selectFields($fields, array( 127 - 'ReleephDiffMessageFieldSpecification', 128 - )), 129 - 'right' => self::selectFields($fields, array( 130 - 'ReleephReasonFieldSpecification', 131 - )) 132 - ) 133 - ); 134 - } 135 - } 136 - 137 - public function arrangeFieldsForSelectForm(array $fields) { 138 - if (self::isFacebook()) { 139 - return self::selectFields($fields, array( 140 - 'ReleephStatusFieldSpecification', 141 - 'ReleephFacebookSeverityFieldSpecification', 142 - 'ReleephRequestorFieldSpecification', 143 - 'ReleephFacebookTagFieldSpecification', 144 - )); 145 - } else { 146 - return self::selectFields($fields, array( 147 - 'ReleephStatusFieldSpecification', 148 - 'ReleephSeverityFieldSpecification', 149 - 'ReleephRequestorFieldSpecification', 150 - )); 151 64 } 152 65 } 153 66
-8
src/applications/releeph/field/selector/ReleephFieldSelector.php
··· 1 1 <?php 2 2 3 - /** 4 - * Control the rendering of ReleephRequestHeaderView, and the layout of the 5 - * ReleephRequest search dialog (in ReleephBranchViewController.) 6 - */ 7 3 abstract class ReleephFieldSelector { 8 4 9 5 final public function __construct() { ··· 11 7 } 12 8 13 9 abstract public function getFieldSpecifications(); 14 - 15 - abstract public function arrangeFieldsForHeaderView(array $fields); 16 - 17 - abstract public function arrangeFieldsForSelectForm(array $fields); 18 10 19 11 public function sortFieldsForCommitMessage(array $fields) { 20 12 assert_instances_of($fields, 'ReleephFieldSpecification');
+16 -10
src/applications/releeph/field/specification/ReleephAuthorFieldSpecification.php
··· 24 24 } 25 25 26 26 public function renderValueForHeaderView() { 27 - $rr = $this->getReleephRequest(); 28 - $author_phid = idx(self::$authorMap, $rr->getPHID()); 29 - if ($author_phid) { 30 - $handle = id(new PhabricatorHandleQuery()) 31 - ->setViewer($this->getUser()) 32 - ->withPHIDs(array($author_phid)) 33 - ->executeOne(); 34 - return $handle->renderLink(); 35 - } else { 36 - return 'Unknown Author'; 27 + $pull = $this->getReleephRequest(); 28 + $commit = $pull->loadPhabricatorRepositoryCommit(); 29 + if (!$commit) { 30 + return null; 37 31 } 32 + 33 + $author_phid = $commit->getAuthorPHID(); 34 + if (!$author_phid) { 35 + return null; 36 + } 37 + 38 + $handle = id(new PhabricatorHandleQuery()) 39 + ->setViewer($this->getUser()) 40 + ->withPHIDs(array($author_phid)) 41 + ->executeOne(); 42 + 43 + return $handle->renderLink(); 38 44 } 39 45 40 46 }
+4
src/applications/releeph/field/specification/ReleephCommitMessageFieldSpecification.php
··· 11 11 return '__only_for_commit_message!'; 12 12 } 13 13 14 + public function shouldAppearInPropertyView() { 15 + return false; 16 + } 17 + 14 18 public function shouldAppearOnCommitMessage() { 15 19 return true; 16 20 }
+5 -6
src/applications/releeph/field/specification/ReleephDiffMessageFieldSpecification.php
··· 15 15 return null; 16 16 } 17 17 18 + public function getStyleForPropertyView() { 19 + return 'block'; 20 + } 21 + 18 22 public function renderValueForHeaderView() { 19 - $markup = phutil_tag( 23 + return phutil_tag( 20 24 'div', 21 25 array( 22 26 'class' => 'phabricator-remarkup', 23 27 ), 24 28 $this->getMarkupEngineOutput()); 25 - 26 - return id(new AphrontNoteView()) 27 - ->setTitle('Commit Message') 28 - ->appendChild($markup) 29 - ->render(); 30 29 } 31 30 32 31 public function shouldMarkup() {
+36
src/applications/releeph/field/specification/ReleephFieldSpecification.php
··· 13 13 return $this; 14 14 } 15 15 16 + public function shouldAppearInPropertyView() { 17 + return true; 18 + } 19 + 20 + public function renderPropertyViewLabel() { 21 + return $this->getName(); 22 + } 23 + 24 + public function renderPropertyViewValue(array $handles) { 25 + $value = $this->renderValueForHeaderView(); 26 + if ($value === '') { 27 + return null; 28 + } 29 + return $value; 30 + } 31 + 32 + public function slowlyLoadHandle($phid) { 33 + // TODO: Remove this, it's transitional as fields modernize. 34 + return id(new PhabricatorHandleQuery()) 35 + ->withPHIDs(array($phid)) 36 + ->setViewer($this->getUser()) 37 + ->executeOne(); 38 + } 39 + 16 40 abstract public function getName(); 17 41 18 42 /* -( Storage )------------------------------------------------------------ */ ··· 148 172 } 149 173 150 174 final public function getReleephProject() { 175 + if (!$this->releephProject) { 176 + return $this->getReleephBranch()->getProduct(); 177 + } 151 178 return $this->releephProject; 152 179 } 153 180 154 181 final public function getReleephBranch() { 182 + if (!$this->releephBranch) { 183 + return $this->getReleephRequest()->getBranch(); 184 + } 155 185 return $this->releephBranch; 156 186 } 157 187 158 188 final public function getReleephRequest() { 189 + if (!$this->releephRequest) { 190 + return $this->getObject(); 191 + } 159 192 return $this->releephRequest; 160 193 } 161 194 162 195 final public function getUser() { 196 + if (!$this->user) { 197 + return $this->getViewer(); 198 + } 163 199 return $this->user; 164 200 } 165 201
+61 -4
src/applications/releeph/field/specification/ReleephIntentFieldSpecification.php
··· 12 12 } 13 13 14 14 public function renderValueForHeaderView() { 15 - return id(new ReleephRequestIntentsView()) 16 - ->setReleephRequest($this->getReleephRequest()) 17 - ->setReleephProject($this->getReleephProject()) 18 - ->render(); 15 + $pull = $this->getReleephRequest(); 16 + 17 + $intents = $pull->getUserIntents(); 18 + $product = $this->getReleephProject(); 19 + 20 + if (!$intents) { 21 + return null; 22 + } 23 + 24 + $user_phids = array_keys($intents); 25 + if ($user_phids) { 26 + $handles = id(new PhabricatorHandleQuery()) 27 + ->withPHIDs($user_phids) 28 + ->setViewer(PhabricatorUser::getOmnipotentUser()) 29 + ->execute(); 30 + } else { 31 + $handles = array(); 32 + } 33 + 34 + $pushers = array(); 35 + $others = array(); 36 + 37 + foreach ($intents as $phid => $intent) { 38 + if ($product->isAuthoritativePHID($phid)) { 39 + $pushers[$phid] = $intent; 40 + } else { 41 + $others[$phid] = $intent; 42 + } 43 + } 44 + 45 + $intents = $pushers + $others; 46 + 47 + $view = id(new PHUIStatusListView()); 48 + foreach ($intents as $phid => $intent) { 49 + switch ($intent) { 50 + case ReleephRequest::INTENT_WANT: 51 + $icon = 'accept-green'; 52 + $label = pht('Want'); 53 + break; 54 + case ReleephRequest::INTENT_PASS: 55 + $icon = 'reject-red'; 56 + $label = pht('Pass'); 57 + break; 58 + default: 59 + $icon = 'question'; 60 + $label = pht('Unknown Intent (%s)', $intent); 61 + break; 62 + } 63 + 64 + $target = $handles[$phid]->renderLink(); 65 + if ($product->isAuthoritativePHID($phid)) { 66 + $target = phutil_tag('strong', array(), $target); 67 + } 68 + 69 + $view->addItem( 70 + id(new PHUIStatusItemView()) 71 + ->setIcon($icon, $label) 72 + ->setTarget($target)); 73 + } 74 + 75 + return $view; 19 76 } 20 77 21 78 public function shouldAppearOnCommitMessage() {
+2 -3
src/applications/releeph/field/specification/ReleephOriginalCommitFieldSpecification.php
··· 12 12 } 13 13 14 14 public function renderValueForHeaderView() { 15 - $rr = $this->getReleephRequest(); 16 - $handles = $rr->getHandles(); 17 - return $handles[$rr->getRequestCommitPHID()]->renderLink(); 15 + $pull = $this->getReleephRequest(); 16 + return $this->slowlyLoadHandle($pull->getRequestCommitPHID())->renderLink(); 18 17 } 19 18 20 19 }
+9 -6
src/applications/releeph/field/specification/ReleephReasonFieldSpecification.php
··· 15 15 return 'reason'; 16 16 } 17 17 18 + public function getStyleForPropertyView() { 19 + return 'block'; 20 + } 21 + 18 22 public function renderLabelForHeaderView() { 19 23 return null; 24 + } 25 + 26 + public function getIconForPropertyView() { 27 + return PHUIPropertyListView::ICON_SUMMARY; 20 28 } 21 29 22 30 public function renderValueForHeaderView() { 23 - $markup = phutil_tag( 31 + return phutil_tag( 24 32 'div', 25 33 array( 26 34 'class' => 'phabricator-remarkup', 27 35 ), 28 36 $this->getMarkupEngineOutput()); 29 - 30 - return id(new AphrontNoteView()) 31 - ->setTitle('Reason') 32 - ->appendChild($markup) 33 - ->render(); 34 37 } 35 38 36 39 private $error = true;
-20
src/applications/releeph/field/specification/ReleephStatusFieldSpecification.php
··· 1 - <?php 2 - 3 - final class ReleephStatusFieldSpecification 4 - extends ReleephFieldSpecification { 5 - 6 - public function getFieldKey() { 7 - return 'status'; 8 - } 9 - 10 - public function getName() { 11 - return 'Status'; 12 - } 13 - 14 - public function renderValueForHeaderView() { 15 - return id(new ReleephRequestStatusView()) 16 - ->setReleephRequest($this->getReleephRequest()) 17 - ->render(); 18 - } 19 - 20 - }
+4
src/applications/releeph/field/specification/ReleephSummaryFieldSpecification.php
··· 5 5 6 6 const MAX_SUMMARY_LENGTH = 60; 7 7 8 + public function shouldAppearInPropertyView() { 9 + return false; 10 + } 11 + 8 12 public function getFieldKey() { 9 13 return 'summary'; 10 14 }
+6 -23
src/applications/releeph/storage/ReleephRequest.php
··· 187 187 return $reason; 188 188 } 189 189 190 - public function getSummary() { 191 - /** 192 - * Instead, you can use: 193 - * - getDetail('summary') // the actual user-chosen summary 194 - * - getSummaryForDisplay() // falls back to the original commit title 195 - * 196 - * Or for the fastidious: 197 - * - id(new ReleephSummaryFieldSpecification()) 198 - * ->setReleephRequest($rr) 199 - * ->getValue() // programmatic equivalent to getDetail() 200 - */ 201 - throw new Exception( 202 - "getSummary() has been deprecated!"); 203 - } 204 - 205 190 /** 206 191 * Allow a null summary, and fall back to the title of the commit. 207 192 */ 208 193 public function getSummaryForDisplay() { 209 194 $summary = $this->getDetail('summary'); 210 195 211 - if (!$summary) { 212 - $pr_commit_data = $this->loadPhabricatorRepositoryCommitData(); 213 - if ($pr_commit_data) { 214 - $message_lines = explode("\n", $pr_commit_data->getCommitMessage()); 215 - $message_lines = array_filter($message_lines); 216 - $summary = head($message_lines); 196 + if (!strlen($summary)) { 197 + $commit = $this->loadPhabricatorRepositoryCommit(); 198 + if ($commit) { 199 + $summary = $commit->getSummary(); 217 200 } 218 201 } 219 202 220 - if (!$summary) { 221 - $summary = '(no summary given and commit message empty or unparsed)'; 203 + if (!strlen($summary)) { 204 + $summary = pht('None'); 222 205 } 223 206 224 207 return $summary;
+249
src/applications/releeph/view/ReleephRequestView.php
··· 1 + <?php 2 + 3 + final class ReleephRequestView extends AphrontView { 4 + 5 + private $pullRequest; 6 + private $customFields; 7 + private $isListView; 8 + 9 + public function setIsListView($is_list_view) { 10 + $this->isListView = $is_list_view; 11 + return $this; 12 + } 13 + 14 + public function getIsListView() { 15 + return $this->isListView; 16 + } 17 + 18 + public function setCustomFields(PhabricatorCustomFieldList $custom_fields) { 19 + $this->customFields = $custom_fields; 20 + return $this; 21 + } 22 + 23 + public function getCustomFields() { 24 + return $this->customFields; 25 + } 26 + 27 + public function setPullRequest(ReleephRequest $pull_request) { 28 + $this->pullRequest = $pull_request; 29 + return $this; 30 + } 31 + 32 + public function getPullRequest() { 33 + return $this->pullRequest; 34 + } 35 + 36 + public function render() { 37 + $viewer = $this->getUser(); 38 + 39 + $field_list = $this->getCustomFields(); 40 + $pull = $this->getPullRequest(); 41 + 42 + $header = $this->buildHeader($pull); 43 + 44 + $action_list = $this->buildActionList($pull); 45 + 46 + $property_list = id(new PHUIPropertyListView()) 47 + ->setUser($viewer) 48 + ->setActionList($action_list); 49 + 50 + $field_list->appendFieldsToPropertyList( 51 + $pull, 52 + $viewer, 53 + $property_list); 54 + 55 + $warnings = $this->getWarnings($pull); 56 + 57 + if ($this->getIsListView()) { 58 + Javelin::initBehavior('releeph-request-state-change'); 59 + } 60 + 61 + return id(new PHUIObjectBoxView()) 62 + ->setHeader($header) 63 + ->setFormErrors($warnings) 64 + ->addSigil('releeph-request-box') 65 + ->setMetadata(array('uri' => '/'.$pull->getMonogram())) 66 + ->appendChild($property_list); 67 + } 68 + 69 + private function buildHeader(ReleephRequest $pull) { 70 + $header_text = $pull->getSummaryForDisplay(); 71 + if ($this->getIsListView()) { 72 + $header_text = phutil_tag( 73 + 'a', 74 + array( 75 + 'href' => '/'.$pull->getMonogram(), 76 + ), 77 + $header_text); 78 + } 79 + 80 + $header = id(new PHUIHeaderView()) 81 + ->setHeader($header_text) 82 + ->setUser($this->getUser()) 83 + ->setPolicyObject($pull); 84 + 85 + switch ($pull->getStatus()) { 86 + case ReleephRequestStatus::STATUS_REQUESTED: 87 + $icon = 'open'; 88 + $color = null; 89 + break; 90 + case ReleephRequestStatus::STATUS_REJECTED: 91 + $icon = 'reject'; 92 + $color = 'red'; 93 + break; 94 + case ReleephRequestStatus::STATUS_PICKED: 95 + $icon = 'accept'; 96 + $color = 'green'; 97 + break; 98 + case ReleephRequestStatus::STATUS_REVERTED: 99 + case ReleephRequestStatus::STATUS_ABANDONED: 100 + $icon = 'reject'; 101 + $color = 'dark'; 102 + break; 103 + case ReleephRequestStatus::STATUS_NEEDS_PICK: 104 + $icon = 'warning'; 105 + $color = 'green'; 106 + break; 107 + case ReleephRequestStatus::STATUS_NEEDS_REVERT: 108 + $icon = 'warning'; 109 + $color = 'red'; 110 + break; 111 + default: 112 + $icon = 'question'; 113 + $color = null; 114 + break; 115 + } 116 + $text = ReleephRequestStatus::getStatusDescriptionFor($pull->getStatus()); 117 + $header->setStatus($icon, $color, $text); 118 + 119 + if ($this->getIsListView()) { 120 + $header->setObjectName($pull->getMonogram()); 121 + } 122 + 123 + return $header; 124 + } 125 + 126 + private function buildActionList(ReleephRequest $pull) { 127 + $viewer = $this->getUser(); 128 + $id = $pull->getID(); 129 + 130 + $edit_uri = '/releeph/request/edit/'.$id.'/'; 131 + 132 + $view = id(new PhabricatorActionListView()) 133 + ->setUser($viewer); 134 + 135 + $product = $pull->getBranch()->getProduct(); 136 + $viewer_is_pusher = $product->isAuthoritativePHID($viewer->getPHID()); 137 + $viewer_is_requestor = ($pull->getRequestUserPHID() == $viewer->getPHID()); 138 + 139 + if ($viewer_is_pusher) { 140 + $yes_text = pht('Approve Pull'); 141 + $no_text = pht('Reject Pull'); 142 + $yes_icon = 'check'; 143 + $no_icon = 'delete'; 144 + } else if ($viewer_is_requestor) { 145 + $yes_text = pht('Request Pull'); 146 + $no_text = pht('Cancel Pull'); 147 + $yes_icon = 'ok'; 148 + $no_icon = 'delete'; 149 + } else { 150 + $yes_text = pht('Support Pull'); 151 + $no_text = pht('Discourage Pull'); 152 + $yes_icon = 'like'; 153 + $no_icon = 'dislike'; 154 + } 155 + 156 + $yes_href = '/releeph/request/action/want/'.$id.'/'; 157 + $no_href = '/releeph/request/action/pass/'.$id.'/'; 158 + 159 + $intents = $pull->getUserIntents(); 160 + $current_intent = idx($intents, $viewer->getPHID()); 161 + 162 + $yes_disabled = ($current_intent == ReleephRequest::INTENT_WANT); 163 + $no_disabled = ($current_intent == ReleephRequest::INTENT_PASS); 164 + 165 + $use_workflow = (!$this->getIsListView()); 166 + 167 + $view->addAction( 168 + id(new PhabricatorActionView()) 169 + ->setName($yes_text) 170 + ->setHref($yes_href) 171 + ->setWorkflow($use_workflow) 172 + ->setRenderAsForm($use_workflow) 173 + ->setDisabled($yes_disabled) 174 + ->addSigil('releeph-request-state-change') 175 + ->addSigil('want') 176 + ->setIcon($yes_icon)); 177 + 178 + $view->addAction( 179 + id(new PhabricatorActionView()) 180 + ->setName($no_text) 181 + ->setHref($no_href) 182 + ->setWorkflow($use_workflow) 183 + ->setRenderAsForm($use_workflow) 184 + ->setDisabled($no_disabled) 185 + ->addSigil('releeph-request-state-change') 186 + ->addSigil('pass') 187 + ->setIcon($no_icon)); 188 + 189 + 190 + if ($viewer_is_pusher || $viewer_is_requestor) { 191 + 192 + $pulled_href = '/releeph/request/action/mark-manually-picked/'.$id.'/'; 193 + $revert_href = '/releeph/request/action/mark-manually-reverted/'.$id.'/'; 194 + 195 + if ($pull->getInBranch()) { 196 + $view->addAction( 197 + id(new PhabricatorActionView()) 198 + ->setName(pht('Mark as Reverted')) 199 + ->setHref($revert_href) 200 + ->setWorkflow($use_workflow) 201 + ->setRenderAsForm($use_workflow) 202 + ->addSigil('releeph-request-state-change') 203 + ->addSigil('mark-manually-reverted') 204 + ->setIcon($no_icon)); 205 + } else { 206 + $view->addAction( 207 + id(new PhabricatorActionView()) 208 + ->setName(pht('Mark as Pulled')) 209 + ->setHref($pulled_href) 210 + ->setWorkflow($use_workflow) 211 + ->setRenderAsForm($use_workflow) 212 + ->addSigil('releeph-request-state-change') 213 + ->addSigil('mark-manually-picked') 214 + ->setIcon('warning')); 215 + } 216 + } 217 + 218 + 219 + if (!$this->getIsListView()) { 220 + $view->addAction( 221 + id(new PhabricatorActionView()) 222 + ->setName(pht('Edit Pull Request')) 223 + ->setIcon('edit') 224 + ->setHref($edit_uri)); 225 + } 226 + 227 + return $view; 228 + } 229 + 230 + private function getWarnings(ReleephRequest $pull) { 231 + $warnings = array(); 232 + 233 + switch ($pull->getStatus()) { 234 + case ReleephRequestStatus::STATUS_NEEDS_PICK: 235 + if ($pull->getPickStatus() == ReleephRequest::PICK_FAILED) { 236 + $warnings[] = pht('Last pull failed!'); 237 + } 238 + break; 239 + case ReleephRequestStatus::STATUS_NEEDS_REVERT: 240 + if ($pull->getPickStatus() == ReleephRequest::REVERT_FAILED) { 241 + $warnings[] = pht('Last revert failed!'); 242 + } 243 + break; 244 + } 245 + 246 + return $warnings; 247 + } 248 + 249 + }
-99
src/applications/releeph/view/request/ReleephRequestIntentsView.php
··· 1 - <?php 2 - 3 - final class ReleephRequestIntentsView extends AphrontView { 4 - 5 - private $releephRequest; 6 - private $releephProject; 7 - 8 - public function setReleephRequest(ReleephRequest $rq) { 9 - $this->releephRequest = $rq; 10 - return $this; 11 - } 12 - 13 - public function setReleephProject(ReleephProject $rp) { 14 - $this->releephProject = $rp; 15 - return $this; 16 - } 17 - 18 - public function render() { 19 - require_celerity_resource('releeph-intents'); 20 - 21 - return phutil_tag( 22 - 'div', 23 - array( 24 - 'class' => 'releeph-intents', 25 - ), 26 - array( 27 - $this->renderIntentList(ReleephRequest::INTENT_WANT), 28 - $this->renderIntentList(ReleephRequest::INTENT_PASS) 29 - )); 30 - } 31 - 32 - private function renderIntentList($render_intent) { 33 - if (!$this->releephProject) { 34 - throw new Exception("Must call setReleephProject() first!"); 35 - } 36 - 37 - $project = $this->releephProject; 38 - $request = $this->releephRequest; 39 - $handles = $request->getHandles(); 40 - 41 - $pusher_links = array(); 42 - $user_links = array(); 43 - 44 - $intents = $request->getUserIntents(); 45 - foreach ($intents as $user_phid => $user_intent) { 46 - if ($user_intent == $render_intent) { 47 - if ($project->isAuthoritativePHID($user_phid)) { 48 - $pusher_links[] = phutil_tag( 49 - 'span', 50 - array( 51 - 'class' => 'pusher' 52 - ), 53 - $handles[$user_phid]->renderLink()); 54 - } else { 55 - $class = 'bystander'; 56 - if ($request->getRequestUserPHID() == $user_phid) { 57 - $class = 'requestor'; 58 - } 59 - $user_links[] = phutil_tag( 60 - 'span', 61 - array( 62 - 'class' => $class, 63 - ), 64 - $handles[$user_phid]->renderLink()); 65 - } 66 - } 67 - } 68 - 69 - // Don't render anything 70 - if (!$pusher_links && !$user_links) { 71 - return null; 72 - } 73 - 74 - $links = array_merge($pusher_links, $user_links); 75 - if ($links) { 76 - $markup = $links; 77 - } else { 78 - $markup = array('&nbsp;'); 79 - } 80 - 81 - // Stick an arrow up front 82 - $arrow_class = 'arrow '.$render_intent; 83 - array_unshift($markup, phutil_tag( 84 - 'div', 85 - array( 86 - 'class' => $arrow_class, 87 - ), 88 - '')); 89 - 90 - return phutil_tag( 91 - 'div', 92 - array( 93 - 'class' => 'intents', 94 - ), 95 - $markup); 96 - } 97 - 98 - 99 - }
-53
src/applications/releeph/view/request/ReleephRequestStatusView.php
··· 1 - <?php 2 - 3 - final class ReleephRequestStatusView extends AphrontView { 4 - 5 - private $releephRequest; 6 - 7 - public function setReleephRequest(ReleephRequest $rq) { 8 - $this->releephRequest = $rq; 9 - return $this; 10 - } 11 - 12 - public function render() { 13 - require_celerity_resource('releeph-status'); 14 - 15 - $request = $this->releephRequest; 16 - $status = $request->getStatus(); 17 - $pick_status = $request->getPickStatus(); 18 - 19 - $description = ReleephRequestStatus::getStatusDescriptionFor($status); 20 - 21 - $warning = null; 22 - 23 - if ($status == ReleephRequestStatus::STATUS_NEEDS_PICK) { 24 - if ($pick_status == ReleephRequest::PICK_FAILED) { 25 - $warning = 'Last pick failed!'; 26 - } 27 - } elseif ($status == ReleephRequestStatus::STATUS_NEEDS_REVERT) { 28 - if ($pick_status == ReleephRequest::REVERT_FAILED) { 29 - $warning = 'Last revert failed!'; 30 - } 31 - } 32 - 33 - return phutil_tag( 34 - 'div', 35 - array( 36 - 'class' => 'releeph-status', 37 - ), 38 - array( 39 - phutil_tag( 40 - 'div', 41 - array( 42 - 'class' => 'description', 43 - ), 44 - $description), 45 - phutil_tag( 46 - 'div', 47 - array( 48 - 'class' => 'warning', 49 - ), 50 - $warning))); 51 - } 52 - 53 - }
-121
src/applications/releeph/view/request/header/ReleephRequestHeaderListView.php
··· 1 - <?php 2 - 3 - final class ReleephRequestHeaderListView 4 - extends AphrontView { 5 - 6 - private $releephProject; 7 - private $releephBranch; 8 - private $releephRequests; 9 - private $aphrontRequest; 10 - private $reload = false; 11 - 12 - private $errors = array(); 13 - 14 - public function setReleephProject(ReleephProject $rp) { 15 - $this->releephProject = $rp; 16 - return $this; 17 - } 18 - 19 - public function setReleephBranch(ReleephBranch $rb) { 20 - $this->releephBranch = $rb; 21 - return $this; 22 - } 23 - 24 - public function setReleephRequests(array $requests) { 25 - assert_instances_of($requests, 'ReleephRequest'); 26 - $this->releephRequests = $requests; 27 - return $this; 28 - } 29 - 30 - public function setAphrontRequest(AphrontRequest $request) { 31 - $this->aphrontRequest = $request; 32 - return $this; 33 - } 34 - 35 - public function setReloadOnStateChange($bool) { 36 - $this->reload = $bool; 37 - return $this; 38 - } 39 - 40 - public function render() { 41 - $views = $this->renderInner(); 42 - require_celerity_resource('phabricator-notification-css'); 43 - Javelin::initBehavior('releeph-request-state-change', array( 44 - 'reload' => $this->reload, 45 - )); 46 - 47 - $error_view = null; 48 - if ($this->errors) { 49 - $error_view = id(new AphrontErrorView()) 50 - ->setTitle('Bulk load errors') 51 - ->setSeverity(AphrontErrorView::SEVERITY_WARNING) 52 - ->setErrors($this->errors) 53 - ->render(); 54 - } 55 - 56 - $list = phutil_tag( 57 - 'div', 58 - array( 59 - 'data-sigil' => 'releeph-request-header-list', 60 - ), 61 - $views); 62 - 63 - return array($error_view, $list); 64 - } 65 - 66 - /** 67 - * Required for generating markup for ReleephRequestActionController. 68 - * 69 - * That controller just needs the markup, and doesn't need to start the 70 - * javelin behavior. 71 - */ 72 - public function renderInner() { 73 - $selector = $this->releephProject->getReleephFieldSelector(); 74 - $fields = $selector->getFieldSpecifications(); 75 - foreach ($fields as $field) { 76 - $field 77 - ->setReleephProject($this->releephProject) 78 - ->setReleephBranch($this->releephBranch) 79 - ->setUser($this->user); 80 - try { 81 - $field->bulkLoad($this->releephRequests); 82 - } catch (Exception $ex) { 83 - $this->errors[] = $ex; 84 - } 85 - } 86 - 87 - $engine = id(new PhabricatorMarkupEngine()) 88 - ->setViewer($this->getUser()); 89 - 90 - $views = array(); 91 - foreach ($this->releephRequests as $releeph_request) { 92 - $our_fields = array(); 93 - foreach ($fields as $key => $field) { 94 - $our_fields[$key] = clone $field; 95 - } 96 - 97 - foreach ($our_fields as $field) { 98 - if ($field->shouldMarkup()) { 99 - $field 100 - ->setReleephRequest($releeph_request) 101 - ->setMarkupEngine($engine); 102 - } 103 - } 104 - 105 - $our_field_groups = $selector->arrangeFieldsForHeaderView($our_fields); 106 - 107 - $views[] = id(new ReleephRequestHeaderView()) 108 - ->setUser($this->user) 109 - ->setAphrontRequest($this->aphrontRequest) 110 - ->setReleephProject($this->releephProject) 111 - ->setReleephBranch($this->releephBranch) 112 - ->setReleephRequest($releeph_request) 113 - ->setReleephFieldGroups($our_field_groups); 114 - } 115 - 116 - $engine->process(); 117 - 118 - return $views; 119 - } 120 - 121 - }
-323
src/applications/releeph/view/request/header/ReleephRequestHeaderView.php
··· 1 - <?php 2 - 3 - final class ReleephRequestHeaderView extends AphrontView { 4 - 5 - const THROW_PARAM = '__releeph_throw'; 6 - 7 - private $aphrontRequest; 8 - private $releephRequest; 9 - private $releephBranch; 10 - private $releephProject; 11 - private $fieldGroups; 12 - 13 - public function setAphrontRequest(AphrontRequest $request) { 14 - $this->aphrontRequest = $request; 15 - return $this; 16 - } 17 - 18 - public function setReleephProject(ReleephProject $rp) { 19 - $this->releephProject = $rp; 20 - return $this; 21 - } 22 - 23 - public function setReleephBranch(ReleephBranch $rb) { 24 - $this->releephBranch = $rb; 25 - return $this; 26 - } 27 - 28 - public function setReleephRequest(ReleephRequest $rr) { 29 - $this->releephRequest = $rr; 30 - return $this; 31 - } 32 - 33 - public function setReleephFieldGroups(array $field_groups) { 34 - $this->fieldGroups = $field_groups; 35 - return $this; 36 - } 37 - 38 - public function render() { 39 - require_celerity_resource('releeph-core'); 40 - $all_properties_table = $this->renderFields(); 41 - 42 - require_celerity_resource('releeph-colors'); 43 - $status = $this->releephRequest->getStatus(); 44 - $rr_div_class = 45 - 'releeph-request-header '. 46 - 'releeph-request-header-border '. 47 - 'releeph-border-color-'. 48 - ReleephRequestStatus::getStatusClassSuffixFor($status); 49 - 50 - $hidden_link = phutil_tag( 51 - 'a', 52 - array( 53 - 'href' => '/RQ'.$this->releephRequest->getID(), 54 - 'target' => '_blank', 55 - 'data-sigil' => 'hidden-link', 56 - ), 57 - ''); 58 - 59 - $focus_char = phutil_tag( 60 - 'div', 61 - array( 62 - 'class' => 'focus-char', 63 - 'data-sigil' => 'focus-char', 64 - ), 65 - "\xE2\x98\x86"); 66 - 67 - $rr_div = phutil_tag( 68 - 'div', 69 - array( 70 - 'data-sigil' => 'releeph-request-header', 71 - 'class' => $rr_div_class, 72 - ), 73 - array( 74 - phutil_tag( 75 - 'div', 76 - array(), 77 - array( 78 - phutil_tag( 79 - 'h1', 80 - array(), 81 - array( 82 - $focus_char, 83 - $this->renderTitleLink(), 84 - $hidden_link 85 - )), 86 - $all_properties_table, 87 - )), 88 - phutil_tag( 89 - 'div', 90 - array( 91 - 'class' => 'button-divider', 92 - ), 93 - $this->renderActionButtonsTable()))); 94 - 95 - return $rr_div; 96 - } 97 - 98 - private function renderFields() { 99 - $field_row_groups = $this->fieldGroups; 100 - 101 - $trs = array(); 102 - foreach ($field_row_groups as $field_column_group) { 103 - $tds = array(); 104 - foreach ($field_column_group as $side => $fields) { 105 - $rows = array(); 106 - foreach ($fields as $field) { 107 - $rows[] = $this->renderOneField($field); 108 - } 109 - $pane = phutil_tag( 110 - 'table', 111 - array( 112 - 'class' => 'fields', 113 - ), 114 - $rows); 115 - $tds[] = phutil_tag( 116 - 'td', 117 - array( 118 - 'class' => 'side '.$side, 119 - ), 120 - $pane); 121 - } 122 - $trs[] = phutil_tag( 123 - 'tr', 124 - array(), 125 - $tds); 126 - } 127 - 128 - return phutil_tag( 129 - 'table', 130 - array( 131 - 'class' => 'panes', 132 - ), 133 - $trs); 134 - } 135 - 136 - private function renderOneField(ReleephFieldSpecification $field) { 137 - $field 138 - ->setUser($this->user) 139 - ->setReleephProject($this->releephProject) 140 - ->setReleephBranch($this->releephBranch) 141 - ->setReleephRequest($this->releephRequest); 142 - 143 - $label = $field->renderLabelForHeaderView(); 144 - try { 145 - $value = $field->renderValueForHeaderView(); 146 - } catch (Exception $ex) { 147 - if ($this->aphrontRequest->getInt(self::THROW_PARAM)) { 148 - throw $ex; 149 - } else { 150 - $value = $this->renderExceptionIcon($ex); 151 - } 152 - } 153 - 154 - if ($value) { 155 - if (!$label) { 156 - return phutil_tag( 157 - 'tr', 158 - array(), 159 - phutil_tag('td', array('colspan' => 2), $value)); 160 - } else { 161 - return phutil_tag( 162 - 'tr', 163 - array(), 164 - array( 165 - phutil_tag('th', array(), $label), 166 - phutil_tag('td', array(), $value))); 167 - } 168 - } 169 - } 170 - 171 - private function renderExceptionIcon(Exception $ex) { 172 - Javelin::initBehavior('phabricator-tooltips'); 173 - require_celerity_resource('aphront-tooltip-css'); 174 - $throw_uri = $this 175 - ->aphrontRequest 176 - ->getRequestURI() 177 - ->setQueryParam(self::THROW_PARAM, 1); 178 - 179 - $message = $ex->getMessage(); 180 - if (!$message) { 181 - $message = get_class($ex).' with no message.'; 182 - } 183 - 184 - return javelin_tag( 185 - 'a', 186 - array( 187 - 'class' => 'releeph-field-error', 188 - 'sigil' => 'has-tooltip', 189 - 'meta' => array( 190 - 'tip' => $message, 191 - 'size' => 400, 192 - 'align' => 'E', 193 - ), 194 - 'href' => $throw_uri, 195 - ), 196 - '!!!'); 197 - } 198 - 199 - private function renderTitleLink() { 200 - $rq_id = $this->releephRequest->getID(); 201 - $summary = $this->releephRequest->getSummaryForDisplay(); 202 - return phutil_tag( 203 - 'a', 204 - array( 205 - 'href' => '/RQ'.$rq_id, 206 - ), 207 - hsprintf( 208 - 'RQ%d: %s', 209 - $rq_id, 210 - $summary)); 211 - } 212 - 213 - private function renderActionButtonsTable() { 214 - $left_buttons = array(); 215 - $right_buttons = array(); 216 - 217 - $user_phid = $this->user->getPHID(); 218 - $is_pusher = $this->releephProject->isAuthoritativePHID($user_phid); 219 - $is_requestor = $this->releephRequest->getRequestUserPHID() === $user_phid; 220 - 221 - $current_intent = idx( 222 - $this->releephRequest->getUserIntents(), 223 - $this->user->getPHID()); 224 - 225 - if ($is_pusher) { 226 - $left_buttons[] = $this->renderIntentButton(true, 'Approve', 'green'); 227 - $left_buttons[] = $this->renderIntentButton(false, 'Reject'); 228 - } else { 229 - if ($is_requestor) { 230 - $right_buttons[] = $this->renderIntentButton(true, 'Request'); 231 - $right_buttons[] = $this->renderIntentButton(false, 'Remove'); 232 - } else { 233 - $right_buttons[] = $this->renderIntentButton(true, 'Want'); 234 - $right_buttons[] = $this->renderIntentButton(false, 'Pass'); 235 - } 236 - } 237 - 238 - // Allow the pusher to mark a request as manually picked or reverted. 239 - if ($is_pusher || $is_requestor) { 240 - if ($this->releephRequest->getInBranch()) { 241 - $left_buttons[] = $this->renderActionButton( 242 - 'Mark Manually Reverted', 243 - 'mark-manually-reverted'); 244 - } else { 245 - $left_buttons[] = $this->renderActionButton( 246 - 'Mark Manually Picked', 247 - 'mark-manually-picked'); 248 - } 249 - } 250 - 251 - $right_buttons[] = phutil_tag( 252 - 'a', 253 - array( 254 - 'href' => '/releeph/request/edit/'.$this->releephRequest->getID().'/', 255 - 'class' => 'small blue button', 256 - ), 257 - 'Edit'); 258 - 259 - if (!$left_buttons && !$right_buttons) { 260 - return; 261 - } 262 - 263 - $cells = array(); 264 - foreach ($left_buttons as $button) { 265 - $cells[] = phutil_tag('td', array('align' => 'left'), $button); 266 - } 267 - $cells[] = phutil_tag('td', array('class' => 'wide'), ''); 268 - foreach ($right_buttons as $button) { 269 - $cells[] = phutil_tag('td', array('align' => 'right'), $button); 270 - } 271 - 272 - $table = phutil_tag( 273 - 'table', 274 - array( 275 - 'class' => 'buttons', 276 - ), 277 - phutil_tag( 278 - 'tr', 279 - array(), 280 - $cells)); 281 - 282 - return $table; 283 - } 284 - 285 - private function renderIntentButton($want, $name, $class = null) { 286 - $current_intent = idx( 287 - $this->releephRequest->getUserIntents(), 288 - $this->user->getPHID()); 289 - 290 - if ($current_intent) { 291 - // If this is a "want" button, and they already want it, disable the 292 - // button (and vice versa for the "pass" case.) 293 - if (($want && $current_intent == ReleephRequest::INTENT_WANT) || 294 - (!$want && $current_intent == ReleephRequest::INTENT_PASS)) { 295 - 296 - $class .= ' disabled'; 297 - } 298 - } 299 - 300 - $action = $want ? 'want' : 'pass'; 301 - return $this->renderActionButton($name, $action, $class); 302 - } 303 - 304 - private function renderActionButton($name, $action, $class=null) { 305 - $attributes = array( 306 - 'class' => 'small button '.$class, 307 - 'sigil' => 'releeph-request-state-change '.$action, 308 - 'meta' => null, 309 - ); 310 - 311 - if ($class != 'disabled') { 312 - // NB the trailing slash on $uri is critical, otherwise the URI will 313 - // redirect to one with a slash, which will turn our GET into a POST. 314 - $attributes['meta'] = sprintf( 315 - '/releeph/request/action/%s/%d/', 316 - $action, 317 - $this->releephRequest->getID()); 318 - } 319 - 320 - return javelin_tag('a', $attributes, $name); 321 - } 322 - 323 - }
-137
src/applications/uiexample/examples/PhabricatorNoteExample.php
··· 1 - <?php 2 - 3 - final class PhabricatorNoteExample extends PhabricatorUIExample { 4 - 5 - public function getName() { 6 - return "Notes"; 7 - } 8 - 9 - public function getDescription() { 10 - return pht('Bounded boxes of text.'); 11 - } 12 - 13 - public function renderExample() { 14 - $short_note = id(new AphrontNoteView()) 15 - ->setTitle(pht('Short note')) 16 - ->appendChild('xxxx xx x xxxxx xxxx'); 17 - 18 - $longer_note = id(new AphrontNoteView()) 19 - ->setTitle(pht('Longer note')) 20 - ->appendChild($this->buildParagraphs(2)); 21 - 22 - $wide_url = 'protocol://www.'.str_repeat('x', 100).'.com/'; 23 - 24 - $oversize_note = id(new AphrontNoteView()) 25 - ->setTitle(pht('Oversize note')) 26 - ->appendChild( 27 - $this->buildParagraphs(2). 28 - $wide_url."\n\n". 29 - $this->buildParagraphs(15)); 30 - 31 - $out = array(); 32 - 33 - $out[] = id(new AphrontPanelView()) 34 - ->setHeader(pht('Unbounded Oversize Note')) 35 - ->appendChild($oversize_note); 36 - 37 - $out[] = id(new AphrontPanelView()) 38 - ->setHeader(pht('Short notes')) 39 - ->appendChild( 40 - $this->renderTable( 41 - array(array($short_note, $short_note)))); 42 - 43 - $out[] = id(new AphrontPanelView()) 44 - ->setHeader(pht('Mixed notes')) 45 - ->appendChild( 46 - $this->renderTable( 47 - array( 48 - array($longer_note, $short_note), 49 - array($short_note, $short_note) 50 - ))); 51 - 52 - $out[] = id(new AphrontPanelView()) 53 - ->setHeader(pht('Oversize notes')) 54 - ->appendChild( 55 - $this->renderTable( 56 - array( 57 - array($oversize_note, $short_note), 58 - array($short_note, $oversize_note) 59 - ))); 60 - 61 - return $out; 62 - } 63 - 64 - private function renderTable($rows) { 65 - static $td_style = ' 66 - width: 50%; 67 - max-width: 1em; 68 - '; 69 - 70 - $trs = array(); 71 - foreach ($rows as $index => $row) { 72 - $count = $index + 1; 73 - list($left, $right) = $row; 74 - $trs[] = phutil_tag( 75 - 'tr', 76 - array(), 77 - array( 78 - phutil_tag( 79 - 'th', 80 - array(), 81 - "Row {$count}"), 82 - phutil_tag('td'))); 83 - 84 - $trs[] = phutil_tag( 85 - 'tr', 86 - array(), 87 - array( 88 - phutil_tag( 89 - 'td', 90 - array( 91 - 'style' => $td_style, 92 - ), 93 - $left->render()), 94 - phutil_tag( 95 - 'td', 96 - array( 97 - 'style' => $td_style, 98 - ), 99 - $right->render()))); 100 - } 101 - 102 - return phutil_tag( 103 - 'table', 104 - array( 105 - 'style' => 'width: 80%;' 106 - ), 107 - $trs); 108 - } 109 - 110 - private function buildParagraphs($num_paragraphs) { 111 - $body = ''; 112 - for ($pp = 0; $pp < $num_paragraphs; $pp++) { 113 - $scale = 50 * ($pp / 2); 114 - $num_words = 30 + self::getRandom(0, $scale); 115 - for ($ii = 0; $ii < $num_words; $ii++) { 116 - $word = str_repeat('x', self::getRandom(3, 8)); 117 - $body .= $word.' '; 118 - } 119 - $body .= "\n\n"; 120 - } 121 - return $body; 122 - } 123 - 124 - private static function getRandom($lower, $upper) { 125 - // The ZX Spectrum's PRNG! 126 - static $nn = 65537; 127 - static $gg = 75; 128 - static $ii = 1; 129 - $ii = ($ii * $gg) % $nn; 130 - if ($lower == $upper) { 131 - return $lower; 132 - } else { 133 - return $lower + ($ii % ($upper - $lower)); 134 - } 135 - } 136 - 137 - }
+23 -10
src/view/layout/PhabricatorActionView.php
··· 11 11 private $renderAsForm; 12 12 private $download; 13 13 private $objectURI; 14 + private $sigils = array(); 14 15 15 16 public function setObjectURI($object_uri) { 16 17 $this->objectURI = $object_uri; ··· 31 32 32 33 public function setHref($href) { 33 34 $this->href = $href; 35 + return $this; 36 + } 37 + 38 + public function addSigil($sigil) { 39 + $this->sigils[] = $sigil; 34 40 return $this; 35 41 } 36 42 ··· 98 104 } 99 105 100 106 if ($this->href) { 107 + 108 + $sigils = array(); 109 + if ($this->workflow) { 110 + $sigils[] = 'workflow'; 111 + } 112 + if ($this->download) { 113 + $sigils[] = 'download'; 114 + } 115 + 116 + if ($this->sigils) { 117 + $sigils = array_merge($sigils, $this->sigils); 118 + } 119 + 120 + $sigils = $sigils ? implode(' ', $sigils) : null; 121 + 101 122 if ($this->renderAsForm) { 102 123 if (!$this->user) { 103 124 throw new Exception( ··· 111 132 ), 112 133 $this->name); 113 134 114 - $sigils = array(); 115 - if ($this->workflow) { 116 - $sigils[] = 'workflow'; 117 - } 118 - if ($this->download) { 119 - $sigils[] = 'download'; 120 - } 121 - 122 135 $item = phabricator_form( 123 136 $this->user, 124 137 array( 125 138 'action' => $this->getHref(), 126 139 'method' => 'POST', 127 - 'sigil' => implode(' ', $sigils), 140 + 'sigil' => $sigils, 128 141 ), 129 142 $item); 130 143 } else { ··· 133 146 array( 134 147 'href' => $this->getHref(), 135 148 'class' => 'phabricator-action-view-item', 136 - 'sigil' => $this->workflow ? 'workflow' : null, 149 + 'sigil' => $sigils, 137 150 ), 138 151 $this->name); 139 152 }
+20
src/view/phui/PHUIObjectBoxView.php
··· 11 11 private $header; 12 12 private $flush; 13 13 private $id; 14 + private $sigils = array(); 15 + private $metadata; 14 16 15 17 private $tabs = array(); 16 18 private $propertyLists = array(); 19 + 20 + public function addSigil($sigil) { 21 + $this->sigils[] = $sigil; 22 + return $this; 23 + } 24 + 25 + public function setMetadata(array $metadata) { 26 + $this->metadata = $metadata; 27 + return $this; 28 + } 17 29 18 30 public function addPropertyList( 19 31 PHUIPropertyListView $property_list, ··· 244 256 245 257 if ($this->flush) { 246 258 $content->addClass('phui-object-box-flush'); 259 + } 260 + 261 + foreach ($this->sigils as $sigil) { 262 + $content->addSigil($sigil); 263 + } 264 + 265 + if ($this->metadata !== null) { 266 + $content->setMetadata($this->metadata); 247 267 } 248 268 249 269 return $content;
-38
src/view/widget/AphrontNoteView.php
··· 1 - <?php 2 - 3 - final class AphrontNoteView extends AphrontView { 4 - 5 - private $title; 6 - 7 - public function setTitle($title) { 8 - $this->title = $title; 9 - return $this; 10 - } 11 - 12 - public function render() { 13 - $title = phutil_tag( 14 - 'div', 15 - array( 16 - 'class' => 'title', 17 - ), 18 - $this->title); 19 - 20 - $inner = phutil_tag( 21 - 'div', 22 - array( 23 - 'class' => 'inner', 24 - ), 25 - $this->renderChildren()); 26 - 27 - require_celerity_resource('aphront-notes'); 28 - return phutil_tag( 29 - 'div', 30 - array( 31 - 'class' => 'aphront-note', 32 - ), 33 - array( 34 - $title, 35 - $inner)); 36 - } 37 - 38 - }
-26
webroot/rsrc/css/aphront/aphront-notes.css
··· 1 - /** 2 - * @provides aphront-notes 3 - */ 4 - 5 - div.aphront-note { 6 - margin: 1em; 7 - background: #ffc; 8 - border: 1px solid #ccc; 9 - } 10 - 11 - div.aphront-note div.title { 12 - margin: 0; 13 - padding: 0.2em 0.5em 0.2em; 14 - background: #ffd; 15 - border-bottom: 1px solid #ccc; 16 - color: {$lightgreytext}; 17 - font-size: smaller; 18 - } 19 - 20 - div.aphront-note div.inner { 21 - overflow: auto; 22 - padding: 0.5em; 23 - max-height: 25em; 24 - color: #333; 25 - font-size: smaller; 26 - }
-79
webroot/rsrc/css/application/releeph/releeph-branch.css
··· 1 - /** 2 - * @provides releeph-branch 3 - */ 4 - 5 - .releeph-branch-box { 6 - margin-bottom: .5em; 7 - padding: .5em .5em .5em; 8 - 9 - border: 2px solid #d5d5d5; 10 - /*border-top-color: #D5D5D5; 11 - border-right-color: #BBB; 12 - border-bottom-color: #A4A4A4; 13 - border-left-color: #BBB;*/ 14 - 15 - background: #bbb; 16 - } 17 - 18 - /* Types of branch */ 19 - 20 - .releeph-branch-box-named { 21 - background: #ddd; 22 - } 23 - 24 - .releeph-branch-box-latest { 25 - background: #ffd; 26 - } 27 - 28 - /* Branch symbolic name and full name */ 29 - 30 - .releeph-branch-box .names { 31 - width: 25em; 32 - float: left; 33 - margin-bottom: 1em; 34 - } 35 - 36 - .releeph-branch-box .names h1 { 37 - font-size: 125%; 38 - padding: 0px; 39 - } 40 - 41 - .releeph-branch-box .names h2 { 42 - font-weight: normal; 43 - font-size: 85%; 44 - } 45 - 46 - /* Date info */ 47 - 48 - .releeph-branch-box .date-info { 49 - width: 10%; 50 - float: left; 51 - color: #555; 52 - margin-bottom: .3em; 53 - } 54 - 55 - /* Statistics table */ 56 - 57 - .releeph-branch-box .request-statistics { 58 - float: right; 59 - padding-right: 2em; 60 - font-size: 85%; 61 - } 62 - 63 - .releeph-branch-box .request-statistics th { 64 - width: 1em; 65 - text-align: right; 66 - padding-right: .4em; 67 - padding-left: .4em; 68 - } 69 - 70 - .releeph-branch-box .request-statistics td { 71 - white-space: nowrap; 72 - font-style: italic; 73 - } 74 - 75 - /* Buttons */ 76 - 77 - .releeph-branch-box .buttons { 78 - float: right; 79 - }
-35
webroot/rsrc/css/application/releeph/releeph-colors.css
··· 1 - /** 2 - * @provides releeph-colors 3 - */ 4 - 5 - .releeph-border-color-failed { 6 - border-color: #d2d; 7 - } 8 - 9 - .releeph-border-color-requested { 10 - border-color: #ddd; 11 - } 12 - 13 - .releeph-border-color-comment { 14 - border-color: #ddd; 15 - } 16 - 17 - .releeph-border-color-needs-pick { 18 - border-color: #096; 19 - } 20 - 21 - .releeph-border-color-rejected { 22 - border-color: #d00; 23 - } 24 - 25 - .releeph-border-color-needs-revert { 26 - border-color: #d00; 27 - } 28 - 29 - .releeph-border-color-abandoned { 30 - border-color: #222; 31 - } 32 - 33 - .releeph-border-color-picked { 34 - border-color: #069; 35 - }
+4 -158
webroot/rsrc/css/application/releeph/releeph-core.css
··· 2 2 * @provides releeph-core 3 3 */ 4 4 5 - .releeph-request-header { 6 - margin: .5em 2em 3em; 7 - 8 - /** 9 - * Copied from the old .differential-panel, present in commit 10 - * f04d8ab1a747dc9719d378d9286088b677ce224c 11 - * 12 - * (As is the <h1> code below) 13 - */ 14 - max-width: 1120px; 15 - border: 1px solid {$greytext}622; 16 - background: #efefdf; 17 - padding: 15px 20px; 18 - font-size: 13px; 19 - } 20 - 21 - .releeph-request-header h1 { 22 - width: 100%; 23 - border-bottom: 1px solid #aaaa99; 24 - padding-bottom: 8px; 25 - margin-bottom: 8px; 26 - position: relative; 27 - } 28 - 29 - .releeph-request-header .focus-char { 30 - left: -10px; 31 - display: none; 32 - float: left; 33 - position: absolute; 34 - top: 0px; 35 - left: -1em; 36 - 37 - font-weight: bold; 38 - 39 - color: #880; 40 - font-family: "Hiragino Kaku Gothic Pro", "Osaka", "Zapf Dingbats"; 41 - } 42 - 43 - .releeph-request-header.focus .focus-char { 44 - display: block; 45 - } 46 - 47 - .releeph-request-header-border { 48 - border-width: 1px 10px 1px; 49 - border-color: #ddd; 50 - } 51 - 52 - 53 - /* Laying out properties / fields */ 54 - 55 - .releeph-request-header table.panes { 56 - width: 100%; 57 - } 58 - 59 - .releeph-request-header table.panes td.side { 60 - width: 50%; 61 - max-width: 1em; 62 - } 63 - 64 - .releeph-request-header table.panes td.side.left { 65 - padding-right: 20px; 66 - border-right: 3px solid #bbb; 67 - } 68 - 69 - .releeph-request-header table.panes td.side.right { 70 - padding-left: 20px; 71 - } 72 - 73 - .releeph-request-header table.panes td.side table.fields { 74 - width: 100%; 75 - } 76 - 77 - .releeph-request-header table.panes td.side table.fields tr { 78 - vertical-align: middle; 79 - } 80 - 81 - .releeph-request-header table.panes td.side table.fields th { 82 - font-weight: bold; 83 - text-align: right; 84 - padding-right: 1em; 85 - white-space: nowrap; 86 - } 87 - 88 - .releeph-request-header table.panes td.side table.fields td { 89 - width: 100%; /* wide! */ 90 - max-width: 1em; 91 - } 92 - 93 - 94 - /* Buttons */ 95 - 96 - .releeph-request-header .button-divider { 97 - clear: both; 98 - margin-top: 1.5em; 99 - border-top: 1px solid #bbb; 100 - } 101 - 102 - .releeph-request-header .buttons { 103 - width: 100%; 104 - } 105 - 106 - .releeph-request-header .buttons tr { 107 - padding: 1em; 108 - margin: 3em; 109 - } 110 - 111 - .releeph-request-header .buttons td { 112 - padding: 1em .5em 0.2em; 113 - } 114 - 115 - .releeph-request-header .buttons td.wide { 116 - width: 100%; 117 - } 118 5 119 6 /* Colors: match differential colors */ 120 7 ··· 133 20 134 21 /* The diff size bar */ 135 22 136 - .releeph-request-header .diff-bar { 23 + .diff-bar { 137 24 border: 0px; 138 25 } 139 26 140 - .releeph-request-header .diff-bar div { 27 + .diff-bar div { 141 28 width: 100px; 142 29 border: 1px solid; 143 30 border-top-color: #A4A4A4; ··· 149 36 margin-right: 1em; 150 37 } 151 38 152 - .releeph-request-header .diff-bar div div { 39 + .diff-bar div div { 153 40 height: 10px; 154 41 } 155 42 156 - .releeph-request-header .diff-bar span { 43 + .diff-bar span { 157 44 color: #555; 158 45 } 159 - 160 - /* Rendering pick / commit errors, etc. */ 161 - 162 - .releeph-request-pick-failed-event h1:before { 163 - content: '\2014 '; 164 - } 165 - 166 - .releeph-request-pick-failed-event h1:after { 167 - content: ' \2014'; 168 - } 169 - 170 - .releeph-request-pick-failed-event h1 { 171 - padding: 3px 10px 3px; 172 - margin-bottom: 0.5em; 173 - background: #ffb; 174 - font-size: small; 175 - } 176 - 177 - .releeph-request-pick-failed-event div { 178 - font-family: monospace; 179 - margin-bottom: 1.5em; 180 - padding-left: 1em; 181 - width: 70em; 182 - } 183 - 184 - /* History view of request */ 185 - 186 - .releeph-request-event-list { 187 - margin: .5em 2em .5em; 188 - } 189 - 190 - 191 - /* Shorten long header-text */ 192 - 193 - .releeph-header-text-truncated { 194 - width: 100%; 195 - float: left; 196 - white-space: nowrap; 197 - overflow: hidden; 198 - text-overflow: ellipsis; 199 - }
-37
webroot/rsrc/css/application/releeph/releeph-intents.css
··· 1 - /** 2 - * @provides releeph-intents 3 - */ 4 - 5 - .releeph-intents .intents { 6 - clear: left; 7 - width: 100%; 8 - margin-top: 3px; 9 - } 10 - 11 - .releeph-intents .arrow { 12 - float: left; 13 - clear: left; 14 - margin-right: 0.4em; 15 - padding: 8px; 16 - background: transparent 0 0 no-repeat; 17 - } 18 - 19 - .releeph-intents .arrow.want { 20 - /* TODO: Icon. */ 21 - } 22 - 23 - .releeph-intents .arrow.pass { 24 - /* TODO: Icon. */ 25 - } 26 - 27 - .releeph-intents a { 28 - margin-right: 0.4em; 29 - } 30 - 31 - .releeph-intents .pusher { 32 - font-weight: bold; 33 - } 34 - 35 - .releeph-intents .requestor { 36 - font-weight: normal; 37 - }
-25
webroot/rsrc/css/application/releeph/releeph-project.css
··· 1 - /** 2 - * @provides releeph-project 3 - */ 4 - 5 - /** 6 - * ...from aphront-transaction.css 7 - */ 8 - 9 - .releeph-pusher { 10 - background: 2px 2px no-repeat; 11 - margin-top: 1em; 12 - margin-bottom: 1.25em; 13 - margin-right: 1em; 14 - min-height: 50px; 15 - padding: 2px 0px; 16 - 17 - background-color: white; 18 - border: 2px solid gray; 19 - float: left; 20 - } 21 - 22 - .releeph-pusher-body { 23 - margin-left: 54px; 24 - padding: 1em; 25 - }
-27
webroot/rsrc/css/application/releeph/releeph-status.css
··· 1 - /** 2 - * @provides releeph-status 3 - */ 4 - 5 - .releeph-status .description { 6 - background: #d3d3d3; 7 - padding: 2px 6px 3px; 8 - margin-right: 4px; 9 - margin-bottom: 5px; 10 - display: block; 11 - float: left; 12 - border-radius: 8px; 13 - -moz-border-radius: 8px; 14 - -webkit-border-radius: 8px; 15 - text-decoration: none; 16 - } 17 - 18 - .releeph-status .warning { 19 - margin-top: 2px; 20 - margin-left: 0.8em; 21 - float: left; 22 - padding-left: 22px; 23 - background-repeat: no-repeat; 24 - background-size: 16px auto; 25 - 26 - /* TODO: This had a background that's still at Facebook? */ 27 - }
+4
webroot/rsrc/css/phui/phui-box.css
··· 7 7 border-bottom: 1px solid {$blueborder}; 8 8 background-color: #fff; 9 9 } 10 + 11 + .phui-box.focus { 12 + box-shadow: 0 0 5px 5px rgba(255, 255, 0, 0.90); 13 + }
+19 -43
webroot/rsrc/js/application/releeph/releeph-request-state-change.js
··· 3 3 * @requires javelin-behavior 4 4 * javelin-dom 5 5 * javelin-stratcom 6 - * javelin-request 6 + * javelin-workflow 7 + * javelin-util 7 8 * phabricator-keyboard-shortcut 8 - * phabricator-notification 9 9 */ 10 10 11 11 JX.behavior('releeph-request-state-change', function(config) { 12 - var root = JX.DOM.find(document, 'div', 'releeph-request-header-list'); 13 - 14 12 function getRequestHeaderNodes() { 15 - return JX.DOM.scry(root, 'div', 'releeph-request-header'); 13 + return JX.DOM.scry(document.body, 'div', 'releeph-request-box'); 16 14 } 17 15 18 - /** 19 - * Keyboard navigation 20 - */ 21 16 var keynav_cursor = -1; 22 - var notification = new JX.Notification(); 23 17 24 18 function keynavJump(manager, delta) { 25 19 // Calculate this everytime, because the DOM changes. ··· 68 62 function keynavNavigateToRequestPage() { 69 63 var headers = getRequestHeaderNodes(); 70 64 var header = headers[keynav_cursor]; 71 - JX.DOM.find(header, 'a', 'hidden-link').click(); 65 + window.open(JX.Stratcom.getData(header).uri); 72 66 } 73 67 74 68 new JX.KeyboardShortcut('j', 'Jump to next request.') ··· 95 89 }) 96 90 .register(); 97 91 98 - new JX.KeyboardShortcut('g', "Open selected request's page in a new tab.") 92 + new JX.KeyboardShortcut( 93 + ['g', 'return'], 94 + "Open selected request's page in a new tab.") 99 95 .setHandler(function(manager) { 100 96 keynavNavigateToRequestPage(); 101 97 }) 102 98 .register(); 103 99 104 - 105 - /** 106 - * AJAXy state changes for request buttons. 107 - */ 108 - function request_action(node, url) { 109 - var request = new JX.Request(url, function(response) { 110 - if (config.reload) { 111 - window.location.reload(); 112 - } else { 113 - var markup = JX.$H(response.markup); 114 - JX.DOM.replace(node, markup); 115 - keynavMarkup(); 116 - } 117 - }); 118 - 119 - request.send(); 100 + function onresponse(box, response) { 101 + JX.DOM.replace(box, JX.$H(response.markup)); 102 + keynavMarkup(); 120 103 } 121 104 122 105 JX.Stratcom.listen( 123 106 'click', 124 107 'releeph-request-state-change', 125 108 function(e) { 126 - var button = e.getNode('releeph-request-state-change'); 127 - var node = e.getNode('releeph-request-header'); 128 - var url = e.getNodeData('releeph-request-state-change'); 109 + e.kill(); 129 110 130 - // If this button has no action, or we've already responded to the first 131 - // click... 132 - if (!url || button.disabled) { 133 - return; 134 - } 111 + var box = e.getNode('releeph-request-box'); 112 + var link = e.getNode('releeph-request-state-change'); 135 113 136 - // There's a race condition here though :( 114 + box.style.opacity = '0.5'; 137 115 138 - JX.DOM.alterClass(button, 'disabled', true); 139 - button.disabled = true; 140 - 141 - e.prevent(); 142 - request_action(node, url); 143 - } 144 - ); 116 + JX.Workflow.newFromLink(link) 117 + .setData({render: true}) 118 + .setHandler(JX.bind(null, onresponse, box)) 119 + .start(); 120 + }); 145 121 });