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

Conpherence - make the durable column kind of work and stuff

Summary:
Ref T7014. This hooks up the durable column such that when you open it up it loads your most recent Conpherence. You can then switch amongst the various widgets and stuff and everything works nicely.

Except...

- scroll bar does not work
- also doesn't work at HEAD when I add a ton of text to the UI with no changes? (wrapped $copy in array_fill(0, 1000, $copy))
- "widget selector" does not collapse when you select something else
- this part wasn't really specified so I used the aphlict dropdown stuff. didn't want to keep working on that if this was the wrong UI choice
- can not edit title
- do we still want that to be done by clicking on the title, which pops a dialogue?
- can not add participants or calendar events
- what should this UI be? maybe just a button on the top for "participants" and a button on the bottom for calendar? both on top?
- this is not pixel perfect to the mock or two I've seen around. Aside from generally being bad at that, I definitely didn't get the name + timestamps formatting correctly, because the standard DOM of that has timestamp FIRST which appears second due to a "float right". Seemed like a lot of special-casing for what might not even be that important in the UI so I punted. (And again, there's likely many unknown ways in which this isn't pixel perfect)

There's also code quality issues

- `ConpherenceWidgetConfigConstants` is hopefully temporary or at least gets more sleek as we keep progressing here
- copied some CSS from main Conpherence app
- DOM structure is pretty different
- there's some minor CSS tweaks too given the different width (not to mention the DOM structure being different)
- copied some JS from behavior-pontificate.js to sync threads relative to aphlict updates
- JS in general is like a better version of existing JS; these should collapse I'd hope?
- maybe the aphlict-behavior-dropdown change was badsauce?

...but all that said, this definitely feels really nice and I feel like adding stuff is going to be really easy compared to how normal Conpherence is.

Also includes a bonus bug fix - we now correctly update participation. The user would encounter this issue if they were in a conpherence that got some updates and then they went to a different page; they would have unread status for the messages that were ajax'd in. This patch fixes that by making sure we mark participation up to date with the proper transaction in all cases.

Test Plan: hit "\" to invoke the column and saw nice loading UI and my latest conpherence load. sent messages and verified they received A-OK by looking in DOM console. toggled various widges and verified they rendered correctly. opened up a second browser with a second user on the thread, sent a message, and it was received in a nice asynchronous fashion

Reviewers: chad, epriestley

Reviewed By: epriestley

Subscribers: Korvin, epriestley

Maniphest Tasks: T7014

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

+1113 -294
+29 -28
resources/celerity/map.php
··· 7 7 */ 8 8 return array( 9 9 'names' => array( 10 - 'core.pkg.css' => 'd9fa6161', 11 - 'core.pkg.js' => '23d653bb', 10 + 'core.pkg.css' => '1a530a25', 11 + 'core.pkg.js' => 'a77025a1', 12 12 'darkconsole.pkg.js' => '8ab24e01', 13 13 'differential.pkg.css' => '4c3242f8', 14 14 'differential.pkg.js' => '7b5a4aa4', ··· 44 44 'rsrc/css/application/config/config-welcome.css' => '6abd79be', 45 45 'rsrc/css/application/config/setup-issue.css' => '22270af2', 46 46 'rsrc/css/application/config/unhandled-exception.css' => '37d4f9a2', 47 - 'rsrc/css/application/conpherence/durable-column.css' => '12846d25', 47 + 'rsrc/css/application/conpherence/durable-column.css' => '3b836442', 48 48 'rsrc/css/application/conpherence/menu.css' => '73774137', 49 49 'rsrc/css/application/conpherence/message-pane.css' => '17a9517f', 50 50 'rsrc/css/application/conpherence/notification.css' => '04a6e10a', ··· 107 107 'rsrc/css/core/core.css' => 'c8c5ecd2', 108 108 'rsrc/css/core/remarkup.css' => '2dbff225', 109 109 'rsrc/css/core/syntax.css' => '56c1ba38', 110 - 'rsrc/css/core/z-index.css' => '8239495e', 110 + 'rsrc/css/core/z-index.css' => '9ec70c03', 111 111 'rsrc/css/diviner/diviner-shared.css' => '38813222', 112 112 'rsrc/css/font/font-awesome.css' => 'ae9a7b4d', 113 113 'rsrc/css/font/font-source-sans-pro.css' => '0d859f60', ··· 346 346 'rsrc/image/texture/table_header_hover.png' => '038ec3b9', 347 347 'rsrc/image/texture/table_header_tall.png' => 'd56b434f', 348 348 'rsrc/js/application/aphlict/Aphlict.js' => '2be71d56', 349 - 'rsrc/js/application/aphlict/behavior-aphlict-dropdown.js' => '335470d7', 349 + 'rsrc/js/application/aphlict/behavior-aphlict-dropdown.js' => 'cc2d9c80', 350 350 'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => '851f167c', 351 351 'rsrc/js/application/aphlict/behavior-aphlict-status.js' => 'ea681761', 352 352 'rsrc/js/application/auth/behavior-persona-login.js' => '9414ff18', 353 353 'rsrc/js/application/config/behavior-reorder-fields.js' => '14a827de', 354 - 'rsrc/js/application/conpherence/behavior-durable-column.js' => '3d86ed6e', 354 + 'rsrc/js/application/conpherence/behavior-durable-column.js' => 'dc9fb293', 355 355 'rsrc/js/application/conpherence/behavior-menu.js' => '869e3445', 356 356 'rsrc/js/application/conpherence/behavior-pontificate.js' => '86df5915', 357 357 'rsrc/js/application/conpherence/behavior-widget-pane.js' => '40b1ff90', ··· 513 513 'changeset-view-manager' => '5eb5b98c', 514 514 'config-options-css' => '7fedf08b', 515 515 'config-welcome-css' => '6abd79be', 516 - 'conpherence-durable-column-view' => '12846d25', 516 + 'conpherence-durable-column-view' => '3b836442', 517 517 'conpherence-menu-css' => '73774137', 518 518 'conpherence-message-pane-css' => '17a9517f', 519 519 'conpherence-notification-css' => '04a6e10a', ··· 542 542 'inline-comment-summary-css' => 'eb5f8e8c', 543 543 'javelin-aphlict' => '2be71d56', 544 544 'javelin-behavior' => '61cbc29a', 545 - 'javelin-behavior-aphlict-dropdown' => '335470d7', 545 + 'javelin-behavior-aphlict-dropdown' => 'cc2d9c80', 546 546 'javelin-behavior-aphlict-listen' => '851f167c', 547 547 'javelin-behavior-aphlict-status' => 'ea681761', 548 548 'javelin-behavior-aphront-basic-tokenizer' => 'b3a4b884', ··· 584 584 'javelin-behavior-diffusion-locate-file' => '6d3e1947', 585 585 'javelin-behavior-diffusion-pull-lastmodified' => '2b228192', 586 586 'javelin-behavior-doorkeeper-tag' => 'e5822781', 587 - 'javelin-behavior-durable-column' => '3d86ed6e', 587 + 'javelin-behavior-durable-column' => 'dc9fb293', 588 588 'javelin-behavior-error-log' => '6882e80a', 589 589 'javelin-behavior-fancy-datepicker' => 'c51ae228', 590 590 'javelin-behavior-global-drag-and-drop' => '07f199d8', ··· 759 759 'phabricator-uiexample-reactor-select' => 'a155550f', 760 760 'phabricator-uiexample-reactor-sendclass' => '1def2711', 761 761 'phabricator-uiexample-reactor-sendproperties' => 'b1f0ccee', 762 - 'phabricator-zindex-css' => '8239495e', 762 + 'phabricator-zindex-css' => '9ec70c03', 763 763 'phame-css' => '88bd4705', 764 764 'pholio-css' => '95174bdd', 765 765 'pholio-edit-css' => '3ad9d1ee', ··· 1011 1011 '331b1611' => array( 1012 1012 'javelin-install', 1013 1013 ), 1014 - '335470d7' => array( 1015 - 'javelin-behavior', 1016 - 'javelin-request', 1017 - 'javelin-stratcom', 1018 - 'javelin-vector', 1019 - 'javelin-dom', 1020 - 'javelin-uri', 1021 - 'javelin-behavior-device', 1022 - 'phabricator-title', 1023 - ), 1024 1014 '3ab51e2c' => array( 1025 1015 'javelin-behavior', 1026 1016 'javelin-behavior-device', ··· 1036 1026 'javelin-workflow', 1037 1027 'javelin-util', 1038 1028 'javelin-uri', 1039 - ), 1040 - '3d86ed6e' => array( 1041 - 'javelin-behavior', 1042 - 'javelin-dom', 1043 - 'javelin-stratcom', 1044 - 'javelin-scrollbar', 1045 - 'javelin-quicksand', 1046 - 'phabricator-keyboard-shortcut', 1047 1029 ), 1048 1030 '3ee3408b' => array( 1049 1031 'javelin-behavior', ··· 1751 1733 'javelin-stratcom', 1752 1734 'phabricator-phtize', 1753 1735 ), 1736 + 'cc2d9c80' => array( 1737 + 'javelin-behavior', 1738 + 'javelin-request', 1739 + 'javelin-stratcom', 1740 + 'javelin-vector', 1741 + 'javelin-dom', 1742 + 'javelin-uri', 1743 + 'javelin-behavior-device', 1744 + 'phabricator-title', 1745 + ), 1754 1746 'd19198c8' => array( 1755 1747 'javelin-install', 1756 1748 'javelin-dom', ··· 1789 1781 'javelin-stratcom', 1790 1782 'javelin-dom', 1791 1783 'phabricator-busy', 1784 + ), 1785 + 'dc9fb293' => array( 1786 + 'javelin-behavior', 1787 + 'javelin-dom', 1788 + 'javelin-stratcom', 1789 + 'javelin-scrollbar', 1790 + 'javelin-quicksand', 1791 + 'phabricator-keyboard-shortcut', 1792 + 'javelin-behavior-conpherence-widget-pane', 1792 1793 ), 1793 1794 'de2e896f' => array( 1794 1795 'javelin-behavior',
+4
src/__phutil_library_map__.php
··· 221 221 'ConduitQueryConduitAPIMethod' => 'applications/conduit/method/ConduitQueryConduitAPIMethod.php', 222 222 'ConduitSSHWorkflow' => 'applications/conduit/ssh/ConduitSSHWorkflow.php', 223 223 'ConduitTokenGarbageCollector' => 'applications/conduit/garbagecollector/ConduitTokenGarbageCollector.php', 224 + 'ConpherenceColumnViewController' => 'applications/conpherence/controller/ConpherenceColumnViewController.php', 224 225 'ConpherenceConduitAPIMethod' => 'applications/conpherence/conduit/ConpherenceConduitAPIMethod.php', 225 226 'ConpherenceConfigOptions' => 'applications/conpherence/config/ConpherenceConfigOptions.php', 226 227 'ConpherenceConstants' => 'applications/conpherence/constants/ConpherenceConstants.php', ··· 263 264 'ConpherenceUpdateController' => 'applications/conpherence/controller/ConpherenceUpdateController.php', 264 265 'ConpherenceUpdateThreadConduitAPIMethod' => 'applications/conpherence/conduit/ConpherenceUpdateThreadConduitAPIMethod.php', 265 266 'ConpherenceViewController' => 'applications/conpherence/controller/ConpherenceViewController.php', 267 + 'ConpherenceWidgetConfigConstants' => 'applications/conpherence/constants/ConpherenceWidgetConfigConstants.php', 266 268 'ConpherenceWidgetController' => 'applications/conpherence/controller/ConpherenceWidgetController.php', 267 269 'ConpherenceWidgetView' => 'applications/conpherence/view/ConpherenceWidgetView.php', 268 270 'DarkConsoleController' => 'applications/console/controller/DarkConsoleController.php', ··· 3361 3363 'ConduitQueryConduitAPIMethod' => 'ConduitAPIMethod', 3362 3364 'ConduitSSHWorkflow' => 'PhabricatorSSHWorkflow', 3363 3365 'ConduitTokenGarbageCollector' => 'PhabricatorGarbageCollector', 3366 + 'ConpherenceColumnViewController' => 'ConpherenceController', 3364 3367 'ConpherenceConduitAPIMethod' => 'ConduitAPIMethod', 3365 3368 'ConpherenceConfigOptions' => 'PhabricatorApplicationConfigOptions', 3366 3369 'ConpherenceController' => 'PhabricatorController', ··· 3405 3408 'ConpherenceUpdateController' => 'ConpherenceController', 3406 3409 'ConpherenceUpdateThreadConduitAPIMethod' => 'ConpherenceConduitAPIMethod', 3407 3410 'ConpherenceViewController' => 'ConpherenceController', 3411 + 'ConpherenceWidgetConfigConstants' => 'ConpherenceConstants', 3408 3412 'ConpherenceWidgetController' => 'ConpherenceController', 3409 3413 'ConpherenceWidgetView' => 'AphrontView', 3410 3414 'DarkConsoleController' => 'PhabricatorController',
+1
src/applications/conpherence/application/PhabricatorConpherenceApplication.php
··· 34 34 '' => 'ConpherenceListController', 35 35 'thread/(?P<id>[1-9]\d*)/' => 'ConpherenceListController', 36 36 '(?P<id>[1-9]\d*)/' => 'ConpherenceViewController', 37 + 'columnview/' => 'ConpherenceColumnViewController', 37 38 'new/' => 'ConpherenceNewController', 38 39 'panel/' => 'ConpherenceNotificationPanelController', 39 40 'widget/(?P<id>[1-9]\d*)/' => 'ConpherenceWidgetController',
+59
src/applications/conpherence/constants/ConpherenceWidgetConfigConstants.php
··· 1 + <?php 2 + 3 + final class ConpherenceWidgetConfigConstants extends ConpherenceConstants { 4 + 5 + const UPDATE_URI = '/conpherence/update/'; 6 + 7 + public static function getWidgetPaneBehaviorConfig() { 8 + return array( 9 + 'widgetBaseUpdateURI' => self::UPDATE_URI, 10 + 'widgetRegistry' => self::getWidgetRegistry(), 11 + ); 12 + } 13 + 14 + public static function getWidgetRegistry() { 15 + return array( 16 + 'conpherence-message-pane' => array( 17 + 'name' => pht('Thread'), 18 + 'icon' => 'fa-comment', 19 + 'deviceOnly' => true, 20 + 'hasCreate' => false, 21 + ), 22 + 'widgets-people' => array( 23 + 'name' => pht('Participants'), 24 + 'icon' => 'fa-users', 25 + 'deviceOnly' => false, 26 + 'hasCreate' => true, 27 + 'createData' => array( 28 + 'refreshFromResponse' => true, 29 + 'action' => ConpherenceUpdateActions::ADD_PERSON, 30 + 'customHref' => null, 31 + ), 32 + ), 33 + 'widgets-files' => array( 34 + 'name' => pht('Files'), 35 + 'icon' => 'fa-files-o', 36 + 'deviceOnly' => false, 37 + 'hasCreate' => false, 38 + ), 39 + 'widgets-calendar' => array( 40 + 'name' => pht('Calendar'), 41 + 'icon' => 'fa-calendar', 42 + 'deviceOnly' => false, 43 + 'hasCreate' => true, 44 + 'createData' => array( 45 + 'refreshFromResponse' => false, 46 + 'action' => ConpherenceUpdateActions::ADD_STATUS, 47 + 'customHref' => '/calendar/event/create/', 48 + ), 49 + ), 50 + 'widgets-settings' => array( 51 + 'name' => pht('Settings'), 52 + 'icon' => 'fa-wrench', 53 + 'deviceOnly' => false, 54 + 'hasCreate' => false, 55 + ), 56 + ); 57 + } 58 + 59 + }
+59
src/applications/conpherence/controller/ConpherenceColumnViewController.php
··· 1 + <?php 2 + 3 + final class ConpherenceColumnViewController extends 4 + ConpherenceController { 5 + 6 + public function handleRequest(AphrontRequest $request) { 7 + $user = $request->getUser(); 8 + 9 + $conpherence = null; 10 + if ($request->getInt('id')) { 11 + $conpherence = id(new ConpherenceThreadQuery()) 12 + ->setViewer($user) 13 + ->withIDs(array($request->getInt('id'))) 14 + ->needTransactions(true) 15 + ->setTransactionLimit(ConpherenceThreadQuery::TRANSACTION_LIMIT) 16 + ->executeOne(); 17 + } else { 18 + // TODO - should be pulling more data than this to build the 19 + // icon bar, etc, kind of always 20 + $latest_participant = id(new ConpherenceParticipantQuery()) 21 + ->withParticipantPHIDs(array($user->getPHID())) 22 + ->setLimit(1) 23 + ->execute(); 24 + $participant = head($latest_participant); 25 + $conpherence = id(new ConpherenceThreadQuery()) 26 + ->setViewer($user) 27 + ->withPHIDs(array($participant->getConpherencePHID())) 28 + ->needTransactions(true) 29 + ->setTransactionLimit(ConpherenceThreadQuery::TRANSACTION_LIMIT) 30 + ->executeOne(); 31 + } 32 + 33 + if (!$conpherence) { 34 + return new Aphront404Response(); 35 + } 36 + $this->setConpherence($conpherence); 37 + 38 + $participant = $conpherence->getParticipant($user->getPHID()); 39 + $transactions = $conpherence->getTransactions(); 40 + $latest_transaction = head($transactions); 41 + $write_guard = AphrontWriteGuard::beginScopedUnguardedWrites(); 42 + $participant->markUpToDate($conpherence, $latest_transaction); 43 + unset($write_guard); 44 + 45 + $durable_column = id(new ConpherenceDurableColumnView()) 46 + ->setUser($user) 47 + ->setSelectedConpherence($conpherence) 48 + ->setStyle(null); 49 + 50 + $response = array( 51 + 'content' => hsprintf('%s', $durable_column), 52 + 'threadID' => $conpherence->getID(), 53 + 'threadPHID' => $conpherence->getPHID(), 54 + 'latestTransactionID' => $latest_transaction->getID(),); 55 + 56 + return id(new AphrontAjaxResponse())->setContent($response); 57 + } 58 + 59 + }
+9 -77
src/applications/conpherence/controller/ConpherenceController.php
··· 2 2 3 3 abstract class ConpherenceController extends PhabricatorController { 4 4 5 - private $conpherences; 5 + private $conpherence; 6 + 7 + public function setConpherence(ConpherenceThread $conpherence) { 8 + $this->conpherence = $conpherence; 9 + return $this; 10 + } 11 + public function getConpherence() { 12 + return $this->conpherence; 13 + } 6 14 7 15 public function buildApplicationMenu() { 8 16 $nav = new PHUIListView(); ··· 77 85 $crumbs, 78 86 )); 79 87 } 80 - 81 - protected function renderConpherenceTransactions( 82 - ConpherenceThread $conpherence) { 83 - 84 - $user = $this->getRequest()->getUser(); 85 - $transactions = $conpherence->getTransactions(); 86 - $oldest_transaction_id = 0; 87 - $too_many = ConpherenceThreadQuery::TRANSACTION_LIMIT + 1; 88 - if (count($transactions) == $too_many) { 89 - $last_transaction = end($transactions); 90 - unset($transactions[$last_transaction->getID()]); 91 - $oldest_transaction = end($transactions); 92 - $oldest_transaction_id = $oldest_transaction->getID(); 93 - } 94 - $transactions = array_reverse($transactions); 95 - $handles = $conpherence->getHandles(); 96 - $rendered_transactions = array(); 97 - $engine = id(new PhabricatorMarkupEngine()) 98 - ->setViewer($user); 99 - foreach ($transactions as $key => $transaction) { 100 - if ($transaction->shouldHide()) { 101 - unset($transactions[$key]); 102 - continue; 103 - } 104 - if ($transaction->getComment()) { 105 - $engine->addObject( 106 - $transaction->getComment(), 107 - PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT); 108 - } 109 - } 110 - $engine->process(); 111 - // we're going to insert a dummy date marker transaction for breaks 112 - // between days. some setup required! 113 - $previous_transaction = null; 114 - $date_marker_transaction = id(new ConpherenceTransaction()) 115 - ->setTransactionType(ConpherenceTransactionType::TYPE_DATE_MARKER) 116 - ->makeEphemeral(); 117 - $date_marker_transaction_view = id(new ConpherenceTransactionView()) 118 - ->setUser($user) 119 - ->setConpherenceTransaction($date_marker_transaction) 120 - ->setHandles($handles) 121 - ->setMarkupEngine($engine); 122 - foreach ($transactions as $transaction) { 123 - if ($previous_transaction) { 124 - $previous_day = phabricator_format_local_time( 125 - $previous_transaction->getDateCreated(), 126 - $user, 127 - 'Ymd'); 128 - $current_day = phabricator_format_local_time( 129 - $transaction->getDateCreated(), 130 - $user, 131 - 'Ymd'); 132 - // date marker transaction time! 133 - if ($previous_day != $current_day) { 134 - $date_marker_transaction->setDateCreated( 135 - $transaction->getDateCreated()); 136 - $rendered_transactions[] = $date_marker_transaction_view->render(); 137 - } 138 - } 139 - $rendered_transactions[] = id(new ConpherenceTransactionView()) 140 - ->setUser($user) 141 - ->setConpherenceTransaction($transaction) 142 - ->setHandles($handles) 143 - ->setMarkupEngine($engine) 144 - ->render(); 145 - $previous_transaction = $transaction; 146 - } 147 - $latest_transaction_id = $transaction->getID(); 148 - 149 - return array( 150 - 'transactions' => $rendered_transactions, 151 - 'latest_transaction_id' => $latest_transaction_id, 152 - 'oldest_transaction_id' => $oldest_transaction_id, 153 - ); 154 - } 155 - 156 88 }
+6 -1
src/applications/conpherence/controller/ConpherenceUpdateController.php
··· 309 309 ->executeOne(); 310 310 311 311 if ($need_transactions) { 312 - $data = $this->renderConpherenceTransactions($conpherence); 312 + $data = ConpherenceTransactionView::renderTransactions( 313 + $user, 314 + $conpherence, 315 + !$this->getRequest()->getExists('minimal_display')); 316 + $participant_obj = $conpherence->getParticipant($user->getPHID()); 317 + $participant_obj->markUpToDate($conpherence, $data['latest_transaction']); 313 318 } else { 314 319 $data = array(); 315 320 }
+15 -57
src/applications/conpherence/controller/ConpherenceViewController.php
··· 3 3 final class ConpherenceViewController extends 4 4 ConpherenceController { 5 5 6 - private $conpherenceID; 7 - private $conpherence; 8 - 9 - public function setConpherence(ConpherenceThread $conpherence) { 10 - $this->conpherence = $conpherence; 11 - return $this; 12 - } 13 - public function getConpherence() { 14 - return $this->conpherence; 15 - } 16 - 17 - public function setConpherenceID($conpherence_id) { 18 - $this->conpherenceID = $conpherence_id; 19 - return $this; 20 - } 21 - public function getConpherenceID() { 22 - return $this->conpherenceID; 23 - } 24 - 25 - public function willProcessRequest(array $data) { 26 - $this->setConpherenceID(idx($data, 'id')); 27 - } 28 - 29 - public function processRequest() { 30 - $request = $this->getRequest(); 6 + public function handleRequest(AphrontRequest $request) { 31 7 $user = $request->getUser(); 32 8 33 - $conpherence_id = $this->getConpherenceID(); 9 + $conpherence_id = $request->getURIData('id'); 34 10 if (!$conpherence_id) { 35 11 return new Aphront404Response(); 36 12 } ··· 53 29 54 30 $participant = $conpherence->getParticipant($user->getPHID()); 55 31 $transactions = $conpherence->getTransactions(); 56 - $latest_transaction = end($transactions); 32 + $latest_transaction = head($transactions); 57 33 $write_guard = AphrontWriteGuard::beginScopedUnguardedWrites(); 58 34 $participant->markUpToDate($conpherence, $latest_transaction); 59 35 unset($write_guard); 60 36 61 - $data = $this->renderConpherenceTransactions($conpherence); 62 - $messages = $this->renderMessagePaneContent( 37 + $data = ConpherenceTransactionView::renderTransactions( 38 + $user, 39 + $conpherence); 40 + $messages = ConpherenceTransactionView::renderMessagePaneContent( 63 41 $data['transactions'], 64 42 $data['oldest_transaction_id']); 65 43 if ($before_transaction_id) { ··· 76 54 ); 77 55 } 78 56 57 + $title = $conpherence->getTitle(); 58 + if (!$title) { 59 + $title = pht('[No Title]'); 60 + } 61 + $content['title'] = $title; 62 + 79 63 if ($request->isAjax()) { 80 64 return id(new AphrontAjaxResponse())->setContent($content); 81 65 } ··· 88 72 ->setReplyForm($form) 89 73 ->setRole('thread'); 90 74 91 - $title = $conpherence->getTitle(); 92 - if (!$title) { 93 - $title = pht('[No Title]'); 94 - } 95 - return $this->buildApplicationPage( 75 + return $this->buildApplicationPage( 96 76 $layout, 97 77 array( 98 78 'title' => $title, 99 79 'pageObjects' => array($conpherence->getPHID()), 100 80 )); 101 - } 102 - 103 - private function renderMessagePaneContent( 104 - array $transactions, 105 - $oldest_transaction_id) { 106 - 107 - $scrollbutton = ''; 108 - if ($oldest_transaction_id) { 109 - $scrollbutton = javelin_tag( 110 - 'a', 111 - array( 112 - 'href' => '#', 113 - 'mustcapture' => true, 114 - 'sigil' => 'show-older-messages', 115 - 'class' => 'conpherence-show-older-messages', 116 - 'meta' => array( 117 - 'oldest_transaction_id' => $oldest_transaction_id, 118 - ), 119 - ), 120 - pht('Show Older Messages')); 121 - } 122 - 123 - return hsprintf('%s%s', $scrollbutton, $transactions); 124 81 } 125 82 126 83 private function renderFormContent($latest_transaction_id) { ··· 167 124 168 125 return $form; 169 126 } 127 + 170 128 171 129 }
+42 -41
src/applications/conpherence/controller/ConpherenceWidgetController.php
··· 2 2 3 3 final class ConpherenceWidgetController extends ConpherenceController { 4 4 5 - private $conpherenceID; 6 - private $conpherence; 7 5 private $userPreferences; 8 6 9 7 public function setUserPreferences(PhabricatorUserPreferences $pref) { ··· 15 13 return $this->userPreferences; 16 14 } 17 15 18 - public function setConpherence(ConpherenceThread $conpherence) { 19 - $this->conpherence = $conpherence; 20 - return $this; 21 - } 22 - 23 - public function getConpherence() { 24 - return $this->conpherence; 25 - } 26 - 27 - public function setConpherenceID($conpherence_id) { 28 - $this->conpherenceID = $conpherence_id; 29 - return $this; 30 - } 31 - 32 - public function getConpherenceID() { 33 - return $this->conpherenceID; 34 - } 35 - 36 - public function willProcessRequest(array $data) { 37 - $this->setConpherenceID(idx($data, 'id')); 38 - } 39 - 40 - public function processRequest() { 16 + public function handleRequest(AphrontRequest $request) { 41 17 $request = $this->getRequest(); 42 18 $user = $request->getUser(); 43 19 44 - $conpherence_id = $this->getConpherenceID(); 20 + $conpherence_id = $request->getURIData('id'); 45 21 if (!$conpherence_id) { 46 22 return new Aphront404Response(); 47 23 } ··· 54 30 55 31 $this->setUserPreferences($user->loadPreferences()); 56 32 57 - $widgets = $this->renderWidgetPaneContent(); 58 - $content = $widgets; 33 + switch ($request->getStr('widget')) { 34 + case 'widgets-people': 35 + $content = $this->renderPeopleWidgetPaneContent(); 36 + break; 37 + case 'widgets-files': 38 + $content = $this->renderFileWidgetPaneContent(); 39 + break; 40 + case 'widgets-calendar': 41 + $widget = $this->renderCalendarWidgetPaneContent(); 42 + $content = phutil_implode_html('', $widget); 43 + break; 44 + case 'widgets-settings': 45 + $content = $this->renderSettingsWidgetPaneContent(); 46 + break; 47 + default: 48 + $widgets = $this->renderWidgetPaneContent(); 49 + $content = $widgets; 50 + break; 51 + } 59 52 return id(new AphrontAjaxResponse())->setContent($content); 60 53 } 61 54 ··· 89 82 'id' => 'widgets-people', 90 83 'sigil' => 'widgets-people', 91 84 ), 92 - id(new ConpherencePeopleWidgetView()) 93 - ->setUser($user) 94 - ->setConpherence($conpherence) 95 - ->setUpdateURI($this->getWidgetURI())); 96 - $widgets[] = javelin_tag( 85 + $this->renderPeopleWidgetPaneContent()); 86 + $widgets[] = javelin_tag( 97 87 'div', 98 88 array( 99 89 'class' => 'widgets-body', ··· 101 91 'sigil' => 'widgets-files', 102 92 'style' => 'display: none;', 103 93 ), 104 - id(new ConpherenceFileWidgetView()) 105 - ->setUser($user) 106 - ->setConpherence($conpherence) 107 - ->setUpdateURI($this->getWidgetURI())); 108 - $widgets[] = phutil_tag( 94 + $this->renderFileWidgetPaneContent()); 95 + $widgets[] = phutil_tag( 109 96 'div', 110 97 array( 111 98 'class' => 'widgets-body', ··· 127 114 return array('widgets' => phutil_implode_html('', $widgets)); 128 115 } 129 116 117 + private function renderPeopleWidgetPaneContent() { 118 + return id(new ConpherencePeopleWidgetView()) 119 + ->setUser($this->getViewer()) 120 + ->setConpherence($this->getConpherence()) 121 + ->setUpdateURI($this->getWidgetURI()); 122 + } 123 + 124 + private function renderFileWidgetPaneContent() { 125 + return id(new ConpherenceFileWidgetView()) 126 + ->setUser($this->getViewer()) 127 + ->setConpherence($this->getConpherence()) 128 + ->setUpdateURI($this->getWidgetURI()); 129 + } 130 + 130 131 private function renderSettingsWidgetPaneContent() { 131 - $user = $this->getRequest()->getUser(); 132 + $viewer = $this->getViewer(); 132 133 $conpherence = $this->getConpherence(); 133 134 $participants = $conpherence->getParticipants(); 134 - $participant = $participants[$user->getPHID()]; 135 + $participant = $participants[$viewer->getPHID()]; 135 136 $default = ConpherenceSettings::EMAIL_ALWAYS; 136 137 $preference = $this->getUserPreferences(); 137 138 if ($preference) { ··· 177 178 ); 178 179 179 180 return phabricator_form( 180 - $user, 181 + $viewer, 181 182 array( 182 183 'method' => 'POST', 183 184 'action' => $this->getWidgetURI(),
+214 -33
src/applications/conpherence/view/ConpherenceDurableColumnView.php
··· 2 2 3 3 final class ConpherenceDurableColumnView extends AphrontTagView { 4 4 5 + private $conpherences; 6 + private $selectedConpherence; 7 + private $transactions; 8 + 9 + public function setConpherences(array $conpherences) { 10 + assert_instances_of($conpherences, 'ConpherenceThread'); 11 + $this->conpherences = $conpherences; 12 + return $this; 13 + } 14 + 15 + public function getConpherences() { 16 + return $this->conpherences; 17 + } 18 + 19 + public function setSelectedConpherence( 20 + ConpherenceThread $conpherence = null) { 21 + $this->selectedConpherence = $conpherence; 22 + return $this; 23 + } 24 + 25 + public function getSelectedConpherence() { 26 + return $this->selectedConpherence; 27 + } 28 + 29 + public function setTransactions(array $transactions) { 30 + assert_instances_of($transactions, 'ConpherenceTransaction'); 31 + $this->transactions = $transactions; 32 + return $this; 33 + } 34 + 35 + public function getTransactions() { 36 + return $this->transactions; 37 + } 38 + 5 39 protected function getTagAttributes() { 6 40 return array( 7 - 'id' => 'durable-column', 41 + 'id' => 'conpherence-durable-column', 8 42 'class' => 'conpherence-durable-column', 43 + 'style' => 'display: none;', 44 + 'sigil' => 'conpherence-durable-column', 9 45 ); 10 46 } 11 47 12 48 protected function getTagContent() { 13 - Javelin::initBehavior('durable-column'); 14 - 15 49 $classes = array(); 16 50 $classes[] = 'conpherence-durable-column-header'; 17 51 $classes[] = 'sprite-main-header'; 18 52 $classes[] = 'main-header-'.PhabricatorEnv::getEnvConfig('ui.header-color'); 53 + 54 + $loading_mask = phutil_tag( 55 + 'div', 56 + array( 57 + 'class' => 'loading-mask', 58 + ), 59 + ''); 19 60 20 61 $header = phutil_tag( 21 62 'div', 22 63 array( 23 64 'class' => implode(' ', $classes), 24 65 ), 25 - phutil_tag( 26 - 'div', 27 - array( 28 - 'class' => 'conpherence-durable-column-header-text', 29 - ), 30 - pht('Column Prototype'))); 31 - 66 + $this->buildHeader()); 32 67 $icon_bar = phutil_tag( 33 68 'div', 34 69 array( 35 70 'class' => 'conpherence-durable-column-icon-bar', 36 71 ), 37 - null); // <-- TODO: Icon buttons go here. 72 + $this->buildIconBar()); 38 73 39 - $copy = pht( 40 - 'This is a very early prototype of a persistent column. It is not '. 41 - 'expected to work yet, and leaving it open will activate other new '. 42 - 'features which will break things. Press "\\" (backslash) on your '. 43 - 'keyboard to close it now.'); 74 + $transactions = $this->buildTransactions(); 44 75 45 76 $content = phutil_tag( 46 77 'div', ··· 53 84 'id' => 'conpherence-durable-column-content', 54 85 'class' => 'conpherence-durable-column-frame', 55 86 ), 56 - phutil_tag( 87 + javelin_tag( 57 88 'div', 58 89 array( 59 - 'class' => 'conpherence-durable-column-content', 90 + 'class' => 'conpherence-durable-column-transactions', 91 + 'sigil' => 'conpherence-durable-column-transactions', 60 92 ), 61 - $copy))); 93 + $transactions))); 62 94 63 - $input = phutil_tag( 64 - 'textarea', 65 - array( 66 - 'class' => 'conpherence-durable-column-textarea', 67 - 'placeholder' => pht('Box for text...'), 68 - )); 95 + $input = $this->buildTextInput(); 69 96 70 97 $footer = phutil_tag( 71 98 'div', ··· 73 100 'class' => 'conpherence-durable-column-footer', 74 101 ), 75 102 array( 76 - phutil_tag( 77 - 'button', 78 - array( 79 - 'class' => 'grey', 80 - ), 81 - pht('Send')), 103 + $this->buildSendButton(), 82 104 phutil_tag( 83 105 'div', 84 106 array( 85 107 'class' => 'conpherence-durable-column-status', 86 108 ), 87 - pht('Status Text')), 109 + $this->buildStatusText()), 88 110 )); 89 111 90 112 return array( 113 + $loading_mask, 91 114 $header, 92 - phutil_tag( 115 + javelin_tag( 93 116 'div', 94 117 array( 95 118 'class' => 'conpherence-durable-column-body', 119 + 'sigil' => 'conpherence-durable-column-body', 96 120 ), 97 121 array( 98 122 $icon_bar, ··· 101 125 $footer, 102 126 )), 103 127 ); 128 + } 129 + 130 + private function buildIconBar() { 131 + return null; 132 + } 133 + 134 + private function buildHeader() { 135 + $conpherence = $this->getSelectedConpherence(); 136 + 137 + if (!$conpherence) { 138 + 139 + $title = pht('Loading...'); 140 + $settings_button = null; 141 + $settings_menu = null; 142 + 143 + } else { 144 + 145 + $bubble_id = celerity_generate_unique_node_id(); 146 + $dropdown_id = celerity_generate_unique_node_id(); 147 + 148 + $settings_list = new PHUIListView(); 149 + $cw_registry = 150 + ConpherenceWidgetConfigConstants::getWidgetRegistry(); 151 + $first = true; 152 + foreach ($cw_registry as $widget => $config) { 153 + $settings_list->addMenuItem( 154 + id(new PHUIListItemView()) 155 + ->setHref('#') 156 + ->setDisabled($first) 157 + ->setName($config['name']) 158 + ->setIcon($config['icon']) 159 + ->addSigil('conpherence-durable-column-widget-selected') 160 + ->setMetadata(array( 161 + 'widget' => $widget, 162 + ))); 163 + $first = false; 164 + } 165 + 166 + $settings_menu = phutil_tag( 167 + 'div', 168 + array( 169 + 'id' => $dropdown_id, 170 + 'class' => 'phabricator-main-menu-dropdown phui-list-sidenav '. 171 + 'conpherence-settings-dropdown', 172 + 'sigil' => 'phabricator-notification-menu', 173 + 'style' => 'display: none', 174 + ), 175 + $settings_list); 176 + 177 + Javelin::initBehavior( 178 + 'aphlict-dropdown', 179 + array( 180 + 'bubbleID' => $bubble_id, 181 + 'dropdownID' => $dropdown_id, 182 + 'local' => true, 183 + 'containerDivID' => 'conpherence-durable-column', 184 + )); 185 + 186 + $item = id(new PHUIListItemView()) 187 + ->setName(pht('Settings')) 188 + ->setIcon('fa-bars') 189 + ->addClass('core-menu-item') 190 + ->addSigil('conpherence-settings-menu') 191 + ->setID($bubble_id) 192 + ->setAural(pht('Settings')) 193 + ->setOrder(300); 194 + $settings_button = id(new PHUIListView()) 195 + ->addMenuItem($item) 196 + ->addClass('phabricator-dark-menu') 197 + ->addClass('phabricator-application-menu'); 198 + 199 + $title = $conpherence->getTitle(); 200 + if (!$title) { 201 + $title = pht('[No Title]'); 202 + } 203 + } 204 + 205 + return 206 + phutil_tag( 207 + 'div', 208 + array( 209 + 'class' => 'conpherence-durable-column-header', 210 + ), 211 + array( 212 + phutil_tag( 213 + 'div', 214 + array( 215 + 'class' => 'conpherence-durable-column-header-text', 216 + ), 217 + $title), 218 + $settings_button, 219 + $settings_menu,)); 220 + 221 + } 222 + 223 + private function buildTransactions() { 224 + $conpherence = $this->getSelectedConpherence(); 225 + if (!$conpherence) { 226 + return pht('Loading...'); 227 + } 228 + 229 + $data = ConpherenceTransactionView::renderTransactions( 230 + $this->getUser(), 231 + $conpherence, 232 + $full_display = false); 233 + $messages = ConpherenceTransactionView::renderMessagePaneContent( 234 + $data['transactions'], 235 + $data['oldest_transaction_id']); 236 + 237 + return $messages; 238 + } 239 + 240 + private function buildTextInput() { 241 + $conpherence = $this->getSelectedConpherence(); 242 + $textarea = javelin_tag( 243 + 'textarea', 244 + array( 245 + 'name' => 'text', 246 + 'class' => 'conpherence-durable-column-textarea', 247 + 'sigil' => 'conpherence-durable-column-textarea', 248 + 'placeholder' => pht('Send a message...'), 249 + )); 250 + if (!$conpherence) { 251 + return $textarea; 252 + } 253 + 254 + $id = $conpherence->getID(); 255 + return phabricator_form( 256 + $this->getUser(), 257 + array( 258 + 'method' => 'POST', 259 + 'action' => '/conpherence/update/'.$id.'/', 260 + 'sigil' => 'conpherence-message-form', 261 + ), 262 + array( 263 + $textarea, 264 + phutil_tag( 265 + 'input', 266 + array( 267 + 'type' => 'hidden', 268 + 'name' => 'action', 269 + 'value' => ConpherenceUpdateActions::MESSAGE, 270 + )),)); 271 + } 272 + 273 + private function buildStatusText() { 274 + return null; 275 + } 276 + 277 + private function buildSendButton() { 278 + return javelin_tag( 279 + 'button', 280 + array( 281 + 'class' => 'grey', 282 + 'sigil' => 'conpherence-send-message', 283 + ), 284 + pht('Send')); 104 285 } 105 286 106 287 }
+1 -46
src/applications/conpherence/view/ConpherenceLayoutView.php
··· 79 79 80 80 $this->initBehavior( 81 81 'conpherence-widget-pane', 82 - array( 83 - 'widgetBaseUpdateURI' => $this->baseURI.'update/', 84 - 'widgetRegistry' => array( 85 - 'conpherence-message-pane' => array( 86 - 'name' => pht('Thread'), 87 - 'icon' => 'fa-comment', 88 - 'deviceOnly' => true, 89 - 'hasCreate' => false, 90 - ), 91 - 'widgets-people' => array( 92 - 'name' => pht('Participants'), 93 - 'icon' => 'fa-users', 94 - 'deviceOnly' => false, 95 - 'hasCreate' => true, 96 - 'createData' => array( 97 - 'refreshFromResponse' => true, 98 - 'action' => ConpherenceUpdateActions::ADD_PERSON, 99 - 'customHref' => null, 100 - ), 101 - ), 102 - 'widgets-files' => array( 103 - 'name' => pht('Files'), 104 - 'icon' => 'fa-files-o', 105 - 'deviceOnly' => false, 106 - 'hasCreate' => false, 107 - ), 108 - 'widgets-calendar' => array( 109 - 'name' => pht('Calendar'), 110 - 'icon' => 'fa-calendar', 111 - 'deviceOnly' => false, 112 - 'hasCreate' => true, 113 - 'createData' => array( 114 - 'refreshFromResponse' => false, 115 - 'action' => ConpherenceUpdateActions::ADD_STATUS, 116 - 'customHref' => '/calendar/event/create/', 117 - ), 118 - ), 119 - 'widgets-settings' => array( 120 - 'name' => pht('Settings'), 121 - 'icon' => 'fa-wrench', 122 - 'deviceOnly' => false, 123 - 'hasCreate' => false, 124 - ), 125 - ), 126 - )); 127 - 82 + ConpherenceWidgetConfigConstants::getWidgetPaneBehaviorConfig()); 128 83 129 84 return javelin_tag( 130 85 'div',
+132 -5
src/applications/conpherence/view/ConpherenceTransactionView.php
··· 5 5 private $conpherenceTransaction; 6 6 private $handles; 7 7 private $markupEngine; 8 + private $showImages = true; 9 + private $showContentSource = true; 8 10 9 11 public function setMarkupEngine(PhabricatorMarkupEngine $markup_engine) { 10 12 $this->markupEngine = $markup_engine; ··· 30 32 return $this->conpherenceTransaction; 31 33 } 32 34 35 + public function setShowImages($bool) { 36 + $this->showImages = $bool; 37 + return $this; 38 + } 39 + 40 + private function getShowImages() { 41 + return $this->showImages; 42 + } 43 + 44 + public function setShowContentSource($bool) { 45 + $this->showContentSource = $bool; 46 + return $this; 47 + } 48 + 49 + private function getShowContentSource() { 50 + return $this->showContentSource; 51 + } 52 + 33 53 public function render() { 34 54 $user = $this->getUser(); 35 55 $transaction = $this->getConpherenceTransaction(); ··· 59 79 $author = $handles[$transaction->getAuthorPHID()]; 60 80 $transaction_view = id(new PhabricatorTransactionView()) 61 81 ->setUser($user) 62 - ->setEpoch($transaction->getDateCreated()) 63 - ->setContentSource($transaction->getContentSource()); 82 + ->setEpoch($transaction->getDateCreated()); 83 + if ($this->getShowContentSource()) { 84 + $transaction_view->setContentSource($transaction->getContentSource()); 85 + } 64 86 65 87 $content = null; 66 88 $content_class = null; ··· 83 105 $comment, 84 106 PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT); 85 107 $content_class = 'conpherence-message phabricator-remarkup'; 86 - $transaction_view 87 - ->setImageURI($author->getImageURI()) 88 - ->setActions(array($author->renderLink())); 108 + if ($this->getShowImages()) { 109 + $transaction_view->setImageURI($author->getImageURI()); 110 + } 111 + $transaction_view->setActions(array($author->renderLink())); 89 112 break; 90 113 } 91 114 ··· 98 121 $content)); 99 122 100 123 return $transaction_view->render(); 124 + } 125 + 126 + public static function renderTransactions( 127 + PhabricatorUser $user, 128 + ConpherenceThread $conpherence, 129 + $full_display = true) { 130 + 131 + $transactions = $conpherence->getTransactions(); 132 + $oldest_transaction_id = 0; 133 + $too_many = ConpherenceThreadQuery::TRANSACTION_LIMIT + 1; 134 + if (count($transactions) == $too_many) { 135 + $last_transaction = end($transactions); 136 + unset($transactions[$last_transaction->getID()]); 137 + $oldest_transaction = end($transactions); 138 + $oldest_transaction_id = $oldest_transaction->getID(); 139 + } 140 + $transactions = array_reverse($transactions); 141 + $handles = $conpherence->getHandles(); 142 + $rendered_transactions = array(); 143 + $engine = id(new PhabricatorMarkupEngine()) 144 + ->setViewer($user); 145 + foreach ($transactions as $key => $transaction) { 146 + if ($transaction->shouldHide()) { 147 + unset($transactions[$key]); 148 + continue; 149 + } 150 + if ($transaction->getComment()) { 151 + $engine->addObject( 152 + $transaction->getComment(), 153 + PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT); 154 + } 155 + } 156 + $engine->process(); 157 + // we're going to insert a dummy date marker transaction for breaks 158 + // between days. some setup required! 159 + $previous_transaction = null; 160 + $date_marker_transaction = id(new ConpherenceTransaction()) 161 + ->setTransactionType(ConpherenceTransactionType::TYPE_DATE_MARKER) 162 + ->makeEphemeral(); 163 + $date_marker_transaction_view = id(new ConpherenceTransactionView()) 164 + ->setUser($user) 165 + ->setConpherenceTransaction($date_marker_transaction) 166 + ->setHandles($handles) 167 + ->setShowImages($full_display) 168 + ->setShowContentSource($full_display) 169 + ->setMarkupEngine($engine); 170 + foreach ($transactions as $transaction) { 171 + if ($previous_transaction) { 172 + $previous_day = phabricator_format_local_time( 173 + $previous_transaction->getDateCreated(), 174 + $user, 175 + 'Ymd'); 176 + $current_day = phabricator_format_local_time( 177 + $transaction->getDateCreated(), 178 + $user, 179 + 'Ymd'); 180 + // date marker transaction time! 181 + if ($previous_day != $current_day) { 182 + $date_marker_transaction->setDateCreated( 183 + $transaction->getDateCreated()); 184 + $rendered_transactions[] = $date_marker_transaction_view->render(); 185 + } 186 + } 187 + $rendered_transactions[] = id(new ConpherenceTransactionView()) 188 + ->setUser($user) 189 + ->setConpherenceTransaction($transaction) 190 + ->setHandles($handles) 191 + ->setMarkupEngine($engine) 192 + ->setShowImages($full_display) 193 + ->setShowContentSource($full_display) 194 + ->render(); 195 + $previous_transaction = $transaction; 196 + } 197 + $latest_transaction_id = $transaction->getID(); 198 + 199 + return array( 200 + 'transactions' => $rendered_transactions, 201 + 'latest_transaction' => $transaction, 202 + 'latest_transaction_id' => $latest_transaction_id, 203 + 'oldest_transaction_id' => $oldest_transaction_id, 204 + ); 205 + } 206 + 207 + public static function renderMessagePaneContent( 208 + array $transactions, 209 + $oldest_transaction_id) { 210 + 211 + $scrollbutton = ''; 212 + if ($oldest_transaction_id) { 213 + $scrollbutton = javelin_tag( 214 + 'a', 215 + array( 216 + 'href' => '#', 217 + 'mustcapture' => true, 218 + 'sigil' => 'show-older-messages', 219 + 'class' => 'conpherence-show-older-messages', 220 + 'meta' => array( 221 + 'oldest_transaction_id' => $oldest_transaction_id, 222 + ), 223 + ), 224 + pht('Show Older Messages')); 225 + } 226 + 227 + return hsprintf('%s%s', $scrollbutton, $transactions); 101 228 } 102 229 103 230 }
+14 -2
src/view/page/PhabricatorStandardPageView.php
··· 80 80 } 81 81 82 82 public function getShowDurableColumn() { 83 - return $this->showDurableColumn; 83 + $request = $this->getRequest(); 84 + if ($request) { 85 + $viewer = $request->getUser(); 86 + return PhabricatorApplication::isClassInstalledForViewer( 87 + 'PhabricatorConpherenceApplication', 88 + $viewer); 89 + } 90 + return false; 84 91 } 85 92 86 93 public function getTitle() { ··· 391 398 392 399 $durable_column = null; 393 400 if ($this->getShowDurableColumn()) { 394 - $durable_column = new ConpherenceDurableColumnView(); 401 + $durable_column = id(new ConpherenceDurableColumnView()) 402 + ->setSelectedConpherence(null) 403 + ->setUser($user); 404 + Javelin::initBehavior( 405 + 'durable-column', 406 + array()); 395 407 } 396 408 397 409 return phutil_tag(
+281 -1
webroot/rsrc/css/application/conpherence/durable-column.css
··· 13 13 right: 0; 14 14 width: 300px; 15 15 background: #fff; 16 + } 17 + 18 + .conpherence-durable-column .loading-mask { 19 + position: absolute; 20 + top: 0; 21 + bottom: 0; 22 + right: 0; 23 + width: 300px; 24 + background: #fff; 16 25 display: none; 26 + opacity: .6; 27 + z-index: 2; 28 + } 29 + 30 + .conpherence-durable-column.loading .loading-mask { 31 + display: block; 17 32 } 18 33 19 34 .conpherence-durable-column-header { ··· 21 36 border-left: 1px solid #000; 22 37 } 23 38 39 + .conpherence-durable-column-header .conpherence-settings-dropdown { 40 + z-index: 1; 41 + } 42 + 24 43 .conpherence-durable-column-header-text { 44 + float: left; 25 45 padding: 12px 16px; 26 46 font-size: 15px; 27 47 color: rgba(255,255,255,.8); 48 + width: 230px; 28 49 } 29 50 30 51 .conpherence-durable-column-header-text:hover { ··· 55 76 overflow: hidden; 56 77 } 57 78 58 - .conpherence-durable-column-content { 79 + .conpherence-durable-column-transactions { 59 80 padding: 8px 12px; 60 81 } 61 82 83 + .conpherence-durable-column-transactions .phabricator-transaction-view { 84 + background: none; 85 + margin: 0 10px 0 0; 86 + padding: 0; 87 + } 88 + 89 + .conpherence-durable-column-transactions .phabricator-transaction-detail { 90 + border: 0; 91 + margin: 0; 92 + } 93 + 94 + .conpherence-durable-column-transactions .phabricator-transaction-detail 95 + .phabricator-transaction-header { 96 + background: none; 97 + padding: 0; 98 + } 99 + .conpherence-durable-column-transactions .phabricator-transaction-detail 100 + .phabricator-transaction-header .phabricator-transaction-info { 101 + margin: 3px 0px 0px 0px; 102 + } 103 + 104 + .conpherence-durable-column-transactions .phabricator-transaction-detail 105 + .phabricator-transaction-content { 106 + padding: 0; 107 + } 108 + 62 109 .conpherence-durable-column-textarea { 63 110 position: absolute; 64 111 left: 0; ··· 80 127 border-top-color: {$sky}; 81 128 border-bottom-color: {$sky}; 82 129 box-shadow: none; 130 + } 131 + 132 + /* participants widget */ 133 + 134 + .conpherence-durable-column-body .person-entry { 135 + padding: 8px 0 0 8px; 136 + } 137 + 138 + .conpherence-durable-column-body .person-entry a { 139 + float: left; 140 + font-weight: bold; 141 + line-height: 20px; 142 + } 143 + 144 + .conpherence-durable-column-body .person-entry a img { 145 + height: 35px; 146 + width: 35px; 147 + } 148 + 149 + .conpherence-durable-column-body .person-entry .pic { 150 + float: left; 151 + margin: 0 8px 0 0; 152 + width: 35px; 153 + padding: 0; 154 + } 155 + 156 + .conpherence-durable-column-body .person-entry .remove { 157 + float: right; 158 + width: 20px; 159 + font-size: 18px; 160 + padding: 5px 0 8px 0; 161 + } 162 + 163 + .conpherence-durable-column-body .person-entry .remove:hover { 164 + text-decoration: none; 165 + } 166 + 167 + .conpherence-durable-column-body .person-entry .remove .close-icon { 168 + color: #bfbfbf; 169 + } 170 + 171 + .conpherence-durable-column-body .person-entry .remove:hover .close-icon { 172 + color: #000; 173 + } 174 + 175 + /* files widget */ 176 + 177 + .conpherence-durable-column-body .no-files { 178 + width: 100%; 179 + padding: 20px 0px; 180 + text-align: center; 181 + color: #555; 182 + } 183 + 184 + .conpherence-durable-column-body .file-entry { 185 + padding: 10px 0; 186 + margin: 0 5px 0 10px; 187 + border-bottom: 1px solid #e7e7e7; 188 + } 189 + .conpherence-durable-column-body .file-icon { 190 + width: 32px; 191 + height: 32px; 192 + float: left; 193 + font-size: 30px; 194 + } 195 + .conpherence-durable-column-body .file-title { 196 + display: block; 197 + position: relative; 198 + top: -4px; 199 + left: 2px; 200 + overflow-x: hidden; 201 + width: 180px; 202 + font-weight: bold; 203 + text-overflow: ellipsis; 204 + white-space: nowrap; 205 + } 206 + .conpherence-durable-column-body .file-uploaded-by { 207 + color: #a1a5a9; 208 + position: relative; 209 + top: 0px; 210 + left: 2px; 211 + width: 170px; 212 + font-size: 11px; 213 + } 214 + 215 + /* calendar widget */ 216 + 217 + .conpherence-durable-column-body .aphront-multi-column-view { 218 + width: 100%; 219 + } 220 + 221 + .conpherence-durable-column-body .aphront-multi-column-view 222 + .aphront-multi-column-column { 223 + background: white; 224 + border-right: 1px solid #bfbfbf; 225 + text-align: center; 226 + } 227 + 228 + .conpherence-durable-column-body .no-events { 229 + color: {$lightgreytext}; 230 + } 231 + 232 + .conpherence-durable-column-body .aphront-multi-column-view 233 + .aphront-multi-column-column-last { 234 + border-right: 0; 235 + } 236 + .conpherence-durable-column-body .aphront-multi-column-view 237 + .aphront-multi-column-column .day-column, 238 + .conpherence-durable-column-body .aphront-multi-column-view 239 + .aphront-multi-column-column .day-column-active { 240 + color: #bfbfbf; 241 + background-color: white; 242 + font-weight: bold; 243 + padding: 0px 0px 10px 0px; 244 + } 245 + .conpherence-durable-column-body .aphront-multi-column-view 246 + .aphront-multi-column-column .day-column-active { 247 + color: black; 248 + } 249 + .conpherence-durable-column-body .aphront-multi-column-view 250 + .aphront-multi-column-column .present , 251 + .conpherence-durable-column-body .aphront-multi-column-view 252 + .aphront-multi-column-column .sporadic , 253 + .conpherence-durable-column-body .aphront-multi-column-view 254 + .aphront-multi-column-column .away { 255 + height: 10px; 256 + margin: 5px 0px 5px 0px; 257 + width: 100%; 258 + } 259 + .conpherence-durable-column-body .aphront-multi-column-view 260 + .aphront-multi-column-column .present { 261 + background-color: white; 262 + } 263 + .conpherence-durable-column-body .aphront-multi-column-view 264 + .aphront-multi-column-column .sporadic { 265 + background-color: rgb(222, 226, 232); 266 + } 267 + .conpherence-durable-column-body .aphront-multi-column-view 268 + .aphront-multi-column-column .away { 269 + background-color: rgb(102, 204, 255); 270 + } 271 + .conpherence-durable-column-body .aphront-multi-column-view 272 + .day-name { 273 + padding: 5px 0px 0px 0px; 274 + font-size: 12px; 275 + } 276 + .conpherence-durable-column-body .aphront-multi-column-view 277 + .day-number { 278 + font-size: 16px; 279 + padding: 0 0 5px 0; 280 + } 281 + 282 + .conpherence-durable-column-body .day-header { 283 + overflow: hidden; 284 + background-image: linear-gradient(to bottom, #ffffff, #e6e6e6); 285 + background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6); 286 + border-top: 1px solid #e7e7e7; 287 + border-bottom: 1px solid #d7d7d7; 288 + padding: 5px 10px 5px 10px; 289 + } 290 + 291 + .conpherence-durable-column-body .day-header.today { 292 + background-image: linear-gradient(to bottom, #3b86c4, #2b628f); 293 + background-image: -webkit-linear-gradient(top, #3b86c4, #2b628f); 294 + border-top: none; 295 + border-bottom: none; 296 + } 297 + 298 + .conpherence-durable-column-body .day-header.today .day-name, 299 + .conpherence-durable-column-body .day-header.today .day-date { 300 + color: #fff; 301 + } 302 + 303 + .conpherence-durable-column-body .day-header .day-name { 304 + float: left; 305 + color: #555759; 306 + font-weight: bold; 307 + text-transform: uppercase; 308 + font-size: 11px; 309 + } 310 + 311 + .conpherence-durable-column-body .day-header .day-date { 312 + float: right; 313 + color: #555759; 314 + font-size: 11px; 315 + } 316 + 317 + .conpherence-durable-column-body .top-border { 318 + border-top: 1px solid #E7E7E7; 319 + } 320 + 321 + .conpherence-durable-column-body .user-status { 322 + padding: 10px 0px 10px 0px; 323 + margin: 0px 0px 0px 10px; 324 + } 325 + 326 + .conpherence-durable-column-body .user-status .icon { 327 + border-radius: 8px; 328 + height: 14px; 329 + width: 14px; 330 + margin-top: 7px; 331 + float: left; 332 + } 333 + 334 + .conpherence-durable-column-body .sporadic .icon { 335 + background-color: rgb(222, 226, 232); 336 + } 337 + 338 + .conpherence-durable-column-body .away .icon { 339 + background-color: rgb(102, 204, 255); 340 + } 341 + 342 + .conpherence-durable-column-body .user-status .description { 343 + width: 195px; 344 + text-overflow: ellipsis; 345 + margin: 0 0 0px 20px; 346 + } 347 + 348 + .conpherence-durable-column-body .user-status .participant { 349 + font-size: 11px; 350 + color: {$lightgreytext}; 351 + padding-top: 2px; 352 + } 353 + 354 + /* settings widget */ 355 + 356 + .conpherence-durable-column-body .notifications-update { 357 + margin: 0px 12px; 358 + } 359 + 360 + .conpherence-durable-column-body .aphront-form-input { 361 + margin: 8px 12px; 362 + width: 100%; 83 363 } 84 364 85 365 .conpherence-durable-column-footer {
+1
webroot/rsrc/css/core/z-index.css
··· 75 75 z-index: 5; 76 76 } 77 77 78 + .conpherence-durable-column-header, 78 79 .phabricator-main-menu { 79 80 z-index: 6; 80 81 }
+5 -1
webroot/rsrc/js/application/aphlict/behavior-aphlict-dropdown.js
··· 148 148 JX.DOM.show(dropdown); 149 149 150 150 p.y = null; 151 - if (config.right) { 151 + if (config.containerDivID) { 152 + var pc = JX.$V(JX.$(config.containerDivID)); 153 + p.x -= (JX.Vector.getDim(dropdown).x - JX.Vector.getDim(bubble).x + 154 + pc.x); 155 + } else if (config.right) { 152 156 p.x -= (JX.Vector.getDim(dropdown).x - JX.Vector.getDim(bubble).x); 153 157 } else { 154 158 p.x -= 6;
+241 -2
webroot/rsrc/js/application/conpherence/behavior-durable-column.js
··· 6 6 * javelin-scrollbar 7 7 * javelin-quicksand 8 8 * phabricator-keyboard-shortcut 9 + * javelin-behavior-conpherence-widget-pane 9 10 */ 10 11 11 12 JX.behavior('durable-column', function() { 12 13 14 + var shouldInit = true; 15 + var loadThreadID = null; 16 + var loadedThreadID = null; 17 + var loadedThreadPHID = null; 18 + var latestTransactionID = null; 19 + 13 20 var frame = JX.$('phabricator-standard-page'); 14 21 var quick = JX.$('phabricator-standard-page-body'); 15 22 var show = false; 16 23 17 - new JX.KeyboardShortcut('\\', 'Toggle Column (Prototype)') 24 + 25 + // TODO - this "upating" stuff is a copy from behavior-pontificate 26 + // TODO: This isn't very clean. When you submit a message, you may get a 27 + // notification about it back before you get the rendered message back. To 28 + // prevent this, we keep track of whether we're currently updating the 29 + // thread. If we are, we hold further updates until the response comes 30 + // back. 31 + 32 + // After the response returns, we'll do another update if we know about 33 + // a transaction newer than the one we got back from the server. 34 + var updating = null; 35 + // Copy continues with slight modifications for how we store data now 36 + JX.Stratcom.listen('aphlict-server-message', null, function(e) { 37 + var message = e.getData(); 38 + 39 + if (message.type != 'message') { 40 + // Not a message event. 41 + return; 42 + } 43 + 44 + if (message.threadPHID != loadedThreadPHID) { 45 + // Message event for some thread other than the visible one. 46 + return; 47 + } 48 + 49 + if (message.messageID <= latestTransactionID) { 50 + // Message event for something we already know about. 51 + return; 52 + } 53 + 54 + // If we're currently updating, wait for the update to complete. 55 + // If this notification tells us about a message which is newer than the 56 + // newest one we know to exist, keep track of it so we can update once 57 + // the in-flight update finishes. 58 + if (updating && updating.threadPHID == loadedThreadPHID) { 59 + if (message.messageID > updating.knownID) { 60 + updating.knownID = message.messageID; 61 + return; 62 + } 63 + } 64 + 65 + update_thread(); 66 + }); 67 + function update_thread() { 68 + var params = { 69 + action: 'load', 70 + latest_transaction_id: latestTransactionID, 71 + minimal_display: true 72 + }; 73 + 74 + var uri = '/conpherence/update/' + loadedThreadID + '/'; 75 + 76 + var workflow = new JX.Workflow(uri) 77 + .setData(params) 78 + .setHandler(function(r) { 79 + var messages = _getColumnMessagesNode(); 80 + JX.DOM.appendContent(messages, JX.$H(r.transactions)); 81 + messages.scrollTop = messages.scrollHeight; 82 + 83 + latestTransactionID = r.latest_transaction_id; 84 + }); 85 + 86 + sync_workflow(workflow); 87 + } 88 + function sync_workflow(workflow) { 89 + updating = { 90 + threadPHID: loadedThreadPHID, 91 + knownID: latestTransactionID 92 + }; 93 + workflow.listen('finally', function() { 94 + var need_sync = (updating.knownID > latestTransactionID); 95 + updating = null; 96 + if (need_sync) { 97 + update_thread(); 98 + } 99 + }); 100 + workflow.start(); 101 + } 102 + // end copy / hack of stuff with big ole TODO on it 103 + 104 + 105 + new JX.KeyboardShortcut('\\', 'Toggle Conpherence Column') 18 106 .setHandler(function() { 19 107 show = !show; 20 108 JX.DOM.alterClass(frame, 'with-durable-column', show); 21 - JX.$('durable-column').style.display = (show ? 'block' : 'none'); 109 + var column = JX.$('conpherence-durable-column'); 110 + if (show) { 111 + JX.DOM.show(column); 112 + loadThreadContent(loadThreadID); 113 + } else { 114 + JX.DOM.hide(column); 115 + } 22 116 JX.Stratcom.invoke('resize'); 23 117 JX.Quicksand.setFrame(show ? quick : null); 24 118 }) ··· 27 121 new JX.Scrollbar(JX.$('conpherence-durable-column-content')); 28 122 29 123 JX.Quicksand.start(); 124 + 125 + JX.Stratcom.listen( 126 + 'click', 127 + 'conpherence-durable-column-widget-selected', 128 + function (e) { 129 + e.kill(); 130 + var data = e.getNodeData('conpherence-durable-column-widget-selected'); 131 + var widget = data.widget; 132 + if (widget == 'conpherence-message-pane') { 133 + return loadThreadContent(loadThreadID); 134 + } 135 + 136 + _markLoading(true); 137 + var uri = '/conpherence/widget/' + loadThreadID + '/'; 138 + loadedThreadID = null; 139 + 140 + var params = { widget : widget }; 141 + new JX.Workflow(uri) 142 + .setData(params) 143 + .setHandler(function(r) { 144 + var body = _getColumnBodyNode(); 145 + JX.DOM.setContent(body, JX.$H(r)); 146 + new JX.Scrollbar(JX.$('conpherence-durable-column-content')); 147 + _markLoading(false); 148 + }) 149 + .start(); 150 + }); 151 + 152 + function _getColumnNode() { 153 + return JX.$('conpherence-durable-column'); 154 + } 155 + 156 + function _getColumnBodyNode() { 157 + var column = JX.$('conpherence-durable-column'); 158 + return JX.DOM.find( 159 + column, 160 + 'div', 161 + 'conpherence-durable-column-body'); 162 + } 163 + 164 + function _getColumnMessagesNode() { 165 + var column = JX.$('conpherence-durable-column'); 166 + return JX.DOM.find( 167 + column, 168 + 'div', 169 + 'conpherence-durable-column-transactions'); 170 + } 171 + 172 + function _getColumnFormNode() { 173 + var column = JX.$('conpherence-durable-column'); 174 + return JX.DOM.find( 175 + column, 176 + 'form', 177 + 'conpherence-message-form'); 178 + } 179 + 180 + function _getColumnTextareaNode() { 181 + var column = JX.$('conpherence-durable-column'); 182 + return JX.DOM.find( 183 + column, 184 + 'textarea', 185 + 'conpherence-durable-column-textarea'); 186 + } 187 + 188 + function _focusColumnTextareaNode() { 189 + var textarea = _getColumnTextareaNode(); 190 + setTimeout(function() { JX.DOM.focus(textarea); }, 1); 191 + } 192 + 193 + function _markLoading(loading) { 194 + var column = _getColumnNode(); 195 + JX.DOM.alterClass(column, 'loading', loading); 196 + } 197 + 198 + function loadThreadContent(thread_id) { 199 + // loaded this thread already 200 + if (loadedThreadID !== null && loadedThreadID == thread_id) { 201 + return; 202 + } 203 + _markLoading(true); 204 + 205 + var uri = '/conpherence/columnview/'; 206 + var params = null; 207 + // We can pick a thread from the server the first time 208 + if (shouldInit) { 209 + shouldInit = false; 210 + params = { shouldInit : true }; 211 + } else { 212 + params = { id : thread_id }; 213 + } 214 + var handler = function(r) { 215 + var column = _getColumnNode(); 216 + var new_column = JX.$H(r.content); 217 + loadedThreadID = r.threadID; 218 + loadedThreadPHID = r.threadPHID; 219 + loadThreadID = r.threadID; 220 + latestTransactionID = r.latestTransactionID; 221 + JX.DOM.replace(column, new_column); 222 + JX.DOM.show(_getColumnNode()); 223 + new JX.Scrollbar(JX.$('conpherence-durable-column-content')); 224 + _markLoading(false); 225 + }; 226 + 227 + new JX.Workflow(uri) 228 + .setData(params) 229 + .setHandler(handler) 230 + .start(); 231 + } 232 + 233 + function _sendMessage(e) { 234 + e.kill(); 235 + _markLoading(true); 236 + 237 + var form = _getColumnFormNode(); 238 + var params = { 239 + latest_transaction_id : latestTransactionID, 240 + minimal_display : true 241 + }; 242 + var workflow = JX.Workflow.newFromForm(form, params) 243 + .setHandler(function(r) { 244 + var messages = _getColumnMessagesNode(); 245 + JX.DOM.appendContent(messages, JX.$H(r.transactions)); 246 + messages.scrollTop = messages.scrollHeight; 247 + 248 + var textarea = _getColumnTextareaNode(); 249 + textarea.value = ''; 250 + 251 + latestTransactionID = r.latest_transaction_id; 252 + 253 + _markLoading(false); 254 + 255 + _focusColumnTextareaNode(); 256 + }); 257 + sync_workflow(workflow); 258 + } 259 + 260 + JX.Stratcom.listen( 261 + 'click', 262 + 'conpherence-send-message', 263 + _sendMessage); 264 + 265 + JX.Stratcom.listen( 266 + ['submit', 'didSyntheticSubmit'], 267 + 'conpherence-message-form', 268 + _sendMessage); 30 269 31 270 });