1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-22 21:40:55 +01:00

Conpherence - paginate thread list

Summary: this is D5750 but just the conpherence part. fixes a few random conpherence bugs / quirks as well. Also messes with ApplicationTransactionEditor to expose the xactions so Conpherence doesn't over-update participation rows. Fixes T2429.

Test Plan: set LIMIT to 3. verified I could scroll down all conpherences. next, picked a conpherence "in the middle" to load. verified I could page up and down. next, picked a conpherence in the middle then had another user update that conpherence. verified as I paged up the conpherence re-loaded properly selected

Reviewers: epriestley

Reviewed By: epriestley

CC: chad, aran, Korvin, vrana

Maniphest Tasks: T2429

Differential Revision: https://secure.phabricator.com/D5783
This commit is contained in:
Bob Trahan 2013-04-26 10:30:41 -07:00
parent 664fe7ef73
commit 11cb2f4f6c
14 changed files with 574 additions and 197 deletions

View file

@ -0,0 +1,4 @@
ALTER TABLE {$NAMESPACE}_conpherence.conpherence_participant
DROP KEY participantPHID,
ADD KEY unreadCount (participantPHID, participationStatus),
ADD KEY participationIndex (participantPHID, dateTouched, id);

View file

@ -1289,7 +1289,7 @@ celerity_register_resource_map(array(
),
'javelin-behavior-conpherence-menu' =>
array(
'uri' => '/res/ce7bfa44/rsrc/js/application/conpherence/behavior-menu.js',
'uri' => '/res/06bfc1a3/rsrc/js/application/conpherence/behavior-menu.js',
'type' => 'js',
'requires' =>
array(
@ -1301,6 +1301,7 @@ celerity_register_resource_map(array(
5 => 'javelin-workflow',
6 => 'javelin-behavior-device',
7 => 'javelin-history',
8 => 'javelin-vector',
),
'disk' => '/rsrc/js/application/conpherence/behavior-menu.js',
),

View file

@ -238,6 +238,7 @@ phutil_register_library_map(array(
'ConpherenceMenuItemView' => 'applications/conpherence/view/ConpherenceMenuItemView.php',
'ConpherenceNewController' => 'applications/conpherence/controller/ConpherenceNewController.php',
'ConpherenceParticipant' => 'applications/conpherence/storage/ConpherenceParticipant.php',
'ConpherenceParticipantCountQuery' => 'applications/conpherence/query/ConpherenceParticipantCountQuery.php',
'ConpherenceParticipantQuery' => 'applications/conpherence/query/ConpherenceParticipantQuery.php',
'ConpherenceParticipationStatus' => 'applications/conpherence/constants/ConpherenceParticipationStatus.php',
'ConpherencePeopleMenuEventListener' => 'applications/conpherence/events/ConpherencePeopleMenuEventListener.php',
@ -2001,6 +2002,7 @@ phutil_register_library_map(array(
'ConpherenceMenuItemView' => 'AphrontTagView',
'ConpherenceNewController' => 'ConpherenceController',
'ConpherenceParticipant' => 'ConpherenceDAO',
'ConpherenceParticipantCountQuery' => 'PhabricatorOffsetPagedQuery',
'ConpherenceParticipantQuery' => 'PhabricatorOffsetPagedQuery',
'ConpherenceParticipationStatus' => 'ConpherenceConstants',
'ConpherencePeopleMenuEventListener' => 'PhutilEventListener',

View file

@ -6,62 +6,6 @@
abstract class ConpherenceController extends PhabricatorController {
private $conpherences;
/**
* Try for a full set of unread conpherences, and if we fail
* load read conpherences. Additional conpherences in either category
* are loaded asynchronously.
*/
public function loadStartingConpherences($current_selection_epoch = null) {
$user = $this->getRequest()->getUser();
$read_participant_query = id(new ConpherenceParticipantQuery())
->withParticipantPHIDs(array($user->getPHID()));
$read_status = ConpherenceParticipationStatus::UP_TO_DATE;
if ($current_selection_epoch) {
$read_one = $read_participant_query
->withParticipationStatus($read_status)
->withDateTouched($current_selection_epoch, '>')
->execute();
$read_two = $read_participant_query
->withDateTouched($current_selection_epoch, '<=')
->execute();
$read = array_merge($read_one, $read_two);
} else {
$read = $read_participant_query
->withParticipationStatus($read_status)
->execute();
}
$unread_status = ConpherenceParticipationStatus::BEHIND;
$unread = id(new ConpherenceParticipantQuery())
->withParticipantPHIDs(array($user->getPHID()))
->withParticipationStatus($unread_status)
->execute();
$all_participation = $unread + $read;
$all_conpherence_phids = array_keys($all_participation);
$all_conpherences = array();
if ($all_conpherence_phids) {
$all_conpherences = id(new ConpherenceThreadQuery())
->setViewer($user)
->withPHIDs($all_conpherence_phids)
->needParticipantCache(true)
->execute();
}
$unread_conpherences = array_select_keys(
$all_conpherences,
array_keys($unread));
$read_conpherences = array_select_keys(
$all_conpherences,
array_keys($read));
return array($unread_conpherences, $read_conpherences);
}
public function buildApplicationMenu() {
$nav = new PhabricatorMenuView();

View file

@ -6,6 +6,10 @@
final class ConpherenceListController
extends ConpherenceController {
const SELECTED_MODE = 'selected';
const UNSELECTED_MODE = 'unselected';
const PAGING_MODE = 'paging';
private $conpherenceID;
public function setConpherenceID($conpherence_id) {
@ -20,59 +24,227 @@ final class ConpherenceListController
$this->setConpherenceID(idx($data, 'id'));
}
/**
* Three main modes of operation...
*
* 1 - /conpherence/ - UNSELECTED_MODE
* 2 - /conpherence/<id>/ - SELECTED_MODE
* 3 - /conpherence/?direction='up'&... - PAGING_MODE
*
* UNSELECTED_MODE is not an Ajax request while the other two are Ajax
* requests.
*/
private function determineMode() {
$request = $this->getRequest();
$mode = self::UNSELECTED_MODE;
if ($request->isAjax()) {
if ($request->getStr('direction')) {
$mode = self::PAGING_MODE;
} else {
$mode = self::SELECTED_MODE;
}
}
return $mode;
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$title = pht('Conpherence');
$conpherence_id = $this->getConpherenceID();
$current_selection_epoch = null;
$conpherence = null;
if ($conpherence_id) {
$conpherence = id(new ConpherenceThreadQuery())
->setViewer($user)
->withIDs(array($conpherence_id))
->executeOne();
if (!$conpherence) {
return new Aphront404Response();
}
if ($conpherence->getTitle()) {
$title = $conpherence->getTitle();
}
$scroll_up_participant = $this->getEmptyParticipant();
$scroll_down_participant = $this->getEmptyParticipant();
$too_many = ConpherenceParticipantQuery::LIMIT + 1;
$all_participation = array();
$participant = $conpherence->getParticipant($user->getPHID());
$current_selection_epoch = $participant->getDateTouched();
$mode = $this->determineMode();
switch ($mode) {
case self::SELECTED_MODE:
$conpherence_id = $this->getConpherenceID();
$conpherence = id(new ConpherenceThreadQuery())
->setViewer($user)
->withIDs(array($conpherence_id))
->executeOne();
if (!$conpherence) {
return new Aphront404Response();
}
if ($conpherence->getTitle()) {
$title = $conpherence->getTitle();
}
$cursor = $conpherence->getParticipant($user->getPHID());
$data = $this->loadParticipationWithMidCursor($cursor);
$all_participation = $data['participation'];
$scroll_up_participant = $data['scroll_up_participant'];
$scroll_down_participant = $data['scroll_down_participant'];
break;
case self::PAGING_MODE:
$direction = $request->getStr('direction');
$id = $request->getInt('participant_id');
$date_touched = $request->getInt('date_touched');
$conpherence_phid = $request->getStr('conpherence_phid');
if ($direction == 'up') {
$order = ConpherenceParticipantQuery::ORDER_NEWER;
} else {
$order = ConpherenceParticipantQuery::ORDER_OLDER;
}
$scroller_participant = id(new ConpherenceParticipant())
->makeEphemeral()
->setID($id)
->setDateTouched($date_touched)
->setConpherencePHID($conpherence_phid);
$participation = id(new ConpherenceParticipantQuery())
->withParticipantPHIDs(array($user->getPHID()))
->withParticipantCursor($scroller_participant)
->setOrder($order)
->setLimit($too_many)
->execute();
if (count($participation) == $too_many) {
if ($direction == 'up') {
$node = $scroll_up_participant = reset($participation);
} else {
$node = $scroll_down_participant = end($participation);
}
unset($participation[$node->getConpherencePHID()]);
}
$all_participation = $participation;
break;
case self::UNSELECTED_MODE:
default:
$too_many = ConpherenceParticipantQuery::LIMIT + 1;
$all_participation = id(new ConpherenceParticipantQuery())
->withParticipantPHIDs(array($user->getPHID()))
->setLimit($too_many)
->execute();
if (count($all_participation) == $too_many) {
$node = end($participation);
unset($all_participation[$node->getConpherencePHID()]);
$scroll_down_participant = $node;
}
break;
}
list($unread, $read) = $this->loadStartingConpherences(
$current_selection_epoch);
$threads = $this->loadConpherenceThreadData(
$all_participation);
$thread_view = id(new ConpherenceThreadListView())
->setUser($user)
->setBaseURI($this->getApplicationURI())
->setUnreadThreads($unread)
->setReadThreads($read);
->setThreads($threads)
->setScrollUpParticipant($scroll_up_participant)
->setScrollDownParticipant($scroll_down_participant);
if ($request->isAjax()) {
return id(new AphrontAjaxResponse())->setContent($thread_view);
switch ($mode) {
case self::SELECTED_MODE:
$response = id(new AphrontAjaxResponse())->setContent($thread_view);
break;
case self::PAGING_MODE:
$thread_html = $thread_view->renderThreadsHTML();
$phids = array_keys($participation);
$content = array(
'html' => $thread_html,
'phids' => $phids);
$response = id(new AphrontAjaxResponse())->setContent($content);
break;
case self::UNSELECTED_MODE:
default:
$layout = id(new ConpherenceLayoutView())
->setBaseURI($this->getApplicationURI())
->setThreadView($thread_view)
->setRole('list');
if ($conpherence) {
$layout->setThread($conpherence);
}
$response = $this->buildApplicationPage(
$layout,
array(
'title' => $title,
'device' => true,
));
break;
}
$layout = id(new ConpherenceLayoutView())
->setBaseURI($this->getApplicationURI())
->setThreadView($thread_view)
->setRole('list');
return $response;
if ($conpherence) {
$layout->setThread($conpherence);
}
/**
* Handles the curious case when we are visiting a conpherence directly
* by issuing two separate queries. Otherwise, additional conpherences
* are fetched asynchronously. Note these can be earlier or later
* (up or down), depending on what conpherence was selected on initial
* load.
*/
private function loadParticipationWithMidCursor(
ConpherenceParticipant $cursor) {
$user = $this->getRequest()->getUser();
$scroll_up_participant = $this->getEmptyParticipant();
$scroll_down_participant = $this->getEmptyParticipant();
// Note this is a bit dodgy since there may be less than this
// amount in either the up or down direction, thus having us fail
// to fetch LIMIT in total. Whatevs for now and re-visit if we're
// fine-tuning this loading process.
$too_many = ceil(ConpherenceParticipantQuery::LIMIT / 2) + 1;
$participant_query = id(new ConpherenceParticipantQuery())
->withParticipantPHIDs(array($user->getPHID()))
->setLimit($too_many);
$current_selection_epoch = $cursor->getDateTouched();
$set_one = $participant_query
->withParticipantCursor($cursor)
->setOrder(ConpherenceParticipantQuery::ORDER_NEWER)
->execute();
if (count($set_one) == $too_many) {
$node = reset($set_one);
unset($set_one[$node->getConpherencePHID()]);
$scroll_up_participant = $node;
}
return $this->buildApplicationPage(
$layout,
array(
'title' => $title,
'device' => true,
));
$set_two = $participant_query
->withParticipantCursor($cursor)
->setOrder(ConpherenceParticipantQuery::ORDER_OLDER)
->execute();
if (count($set_two) == $too_many) {
$node = end($set_two);
unset($set_two[$node->getConpherencePHID()]);
$scroll_down_participant = $node;
}
$participation = array_merge(
$set_one,
$set_two);
return array(
'scroll_up_participant' => $scroll_up_participant,
'scroll_down_participant' => $scroll_down_participant,
'participation' => $participation);
}
private function loadConpherenceThreadData($participation) {
$user = $this->getRequest()->getUser();
$conpherence_phids = array_keys($participation);
if ($conpherence_phids) {
$conpherences = id(new ConpherenceThreadQuery())
->setViewer($user)
->withPHIDs($conpherence_phids)
->needParticipantCache(true)
->execute();
}
// this will re-sort by participation data
$conpherences = array_select_keys($conpherences, $conpherence_phids);
return $conpherences;
}
private function getEmptyParticipant() {
return id(new ConpherenceParticipant())
->makeEphemeral();
}
}

View file

@ -50,6 +50,9 @@ final class ConpherenceViewController extends
->setBeforeTransactionID($before_transaction_id);
}
$conpherence = $query->executeOne();
if (!$conpherence) {
return new Aphront404Response();
}
$this->setConpherence($conpherence);
$participant = $conpherence->getParticipant($user->getPHID());

View file

@ -140,9 +140,6 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor {
$object->setRecentParticipantPHIDs($participants);
}
/**
* For now this only supports adding more files and participants.
*/
protected function applyCustomExternalTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
@ -169,33 +166,8 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor {
$file_phid);
}
$editor->save();
// fallthrough
case PhabricatorTransactions::TYPE_COMMENT:
$xaction_phid = $xaction->getPHID();
$behind = ConpherenceParticipationStatus::BEHIND;
$up_to_date = ConpherenceParticipationStatus::UP_TO_DATE;
$participants = $object->getParticipants();
$user = $this->getActor();
$time = time();
foreach ($participants as $phid => $participant) {
if ($phid != $user->getPHID()) {
if ($participant->getParticipationStatus() != $behind) {
$participant->setBehindTransactionPHID($xaction_phid);
// decrement one as this is the message putting them behind!
$participant->setSeenMessageCount($object->getMessageCount() - 1);
}
$participant->setParticipationStatus($behind);
$participant->setDateTouched($time);
} else {
$participant->setSeenMessageCount($object->getMessageCount());
$participant->setParticipationStatus($up_to_date);
$participant->setDateTouched($time);
}
$participant->save();
}
break;
case ConpherenceTransactionType::TYPE_PARTICIPANTS:
$participants = $object->getParticipants();
$old_map = array_fuse($xaction->getOldValue());
@ -229,7 +201,37 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor {
}
$object->attachParticipants($participants);
break;
}
}
}
protected function applyFinalEffects(
PhabricatorLiskDAO $object,
array $xactions) {
// update everyone's participation status on the last xaction -only-
$xaction = end($xactions);
$xaction_phid = $xaction->getPHID();
$behind = ConpherenceParticipationStatus::BEHIND;
$up_to_date = ConpherenceParticipationStatus::UP_TO_DATE;
$participants = $object->getParticipants();
$user = $this->getActor();
$time = time();
foreach ($participants as $phid => $participant) {
if ($phid != $user->getPHID()) {
if ($participant->getParticipationStatus() != $behind) {
$participant->setBehindTransactionPHID($xaction_phid);
// decrement one as this is the message putting them behind!
$participant->setSeenMessageCount($object->getMessageCount() - 1);
}
$participant->setParticipationStatus($behind);
$participant->setDateTouched($time);
} else {
$participant->setSeenMessageCount($object->getMessageCount());
$participant->setParticipationStatus($up_to_date);
$participant->setDateTouched($time);
}
$participant->save();
}
}
protected function mergeTransactions(

View file

@ -0,0 +1,76 @@
<?php
/**
* Query class that answers the question:
*
* - Q: How many unread conpherences am I participating in?
* - A:
* id(new ConpherenceParticipantCountQuery())
* ->withParticipantPHIDs(array($my_phid))
* ->withParticipationStatus(ConpherenceParticipationStatus::BEHIND)
* ->execute();
*
* @group conpherence
*/
final class ConpherenceParticipantCountQuery
extends PhabricatorOffsetPagedQuery {
private $participantPHIDs;
private $participationStatus;
public function withParticipantPHIDs(array $phids) {
$this->participantPHIDs = $phids;
return $this;
}
public function withParticipationStatus($participation_status) {
$this->participationStatus = $participation_status;
return $this;
}
public function execute() {
$table = new ConpherenceParticipant();
$conn_r = $table->establishConnection('r');
$rows = queryfx_all(
$conn_r,
'SELECT COUNT(*) as count, participantPHID '.
'FROM %T participant %Q %Q %Q',
$table->getTableName(),
$this->buildWhereClause($conn_r),
$this->buildGroupByClause($conn_r),
$this->buildLimitClause($conn_r));
return ipull($rows, 'count', 'participantPHID');
}
private function buildWhereClause($conn_r) {
$where = array();
if ($this->participantPHIDs) {
$where[] = qsprintf(
$conn_r,
'participantPHID IN (%Ls)',
$this->participantPHIDs);
}
if ($this->participationStatus !== null) {
$where[] = qsprintf(
$conn_r,
'participationStatus = %d',
$this->participationStatus);
}
return $this->formatWhereClause($where);
}
private function buildGroupByClause(AphrontDatabaseConnection $conn_r) {
$group_by = qsprintf(
$conn_r,
'GROUP BY participantPHID');
return $group_by;
}
}

View file

@ -1,35 +1,61 @@
<?php
/**
* Query class that answers these questions:
*
* - Q: What are the conpherences to show when I land on /conpherence/ ?
* - A:
*
* id(new ConpherenceParticipantQuery())
* ->withParticipantPHIDs(array($my_phid))
* ->execute();
*
* - Q: What are the next set of conpherences as I scroll up (more recent) or
* down (less recent) this list of conpherences?
* - A:
*
* id(new ConpherenceParticipantQuery())
* ->withParticipantPHIDs(array($my_phid))
* ->withParticipantCursor($top_participant)
* ->setOrder(ConpherenceParticipantQuery::ORDER_NEWER)
* ->execute();
*
* -or-
*
* id(new ConpherenceParticipantQuery())
* ->withParticipantPHIDs(array($my_phid))
* ->withParticipantCursor($bottom_participant)
* ->setOrder(ConpherenceParticipantQuery::ORDER_OLDER)
* ->execute();
*
* For counts of read, un-read, or all conpherences by participant, see
* @{class:ConpherenceParticipantCountQuery}.
*
* @group conpherence
*/
final class ConpherenceParticipantQuery
extends PhabricatorOffsetPagedQuery {
private $conpherencePHIDs;
private $participantPHIDs;
private $dateTouched;
private $dateTouchedSort;
private $participationStatus;
const LIMIT = 100;
const ORDER_NEWER = 'newer';
const ORDER_OLDER = 'older';
public function withConpherencePHIDs(array $phids) {
$this->conpherencePHIDs = $phids;
return $this;
}
private $participantPHIDs;
private $participantCursor;
private $order = self::ORDER_OLDER;
public function withParticipantPHIDs(array $phids) {
$this->participantPHIDs = $phids;
return $this;
}
public function withDateTouched($date, $sort = null) {
$this->dateTouched = $date;
$this->dateTouchedSort = $sort ? $sort : '<';
public function withParticipantCursor(ConpherenceParticipant $participant) {
$this->participantCursor = $participant;
return $this;
}
public function withParticipationStatus($participation_status) {
$this->participationStatus = $participation_status;
public function setOrder($order) {
$this->order = $order;
return $this;
}
@ -49,19 +75,16 @@ final class ConpherenceParticipantQuery
$participants = mpull($participants, null, 'getConpherencePHID');
if ($this->order == self::ORDER_NEWER) {
$participants = array_reverse($participants);
}
return $participants;
}
private function buildWhereClause($conn_r) {
$where = array();
if ($this->conpherencePHIDs) {
$where[] = qsprintf(
$conn_r,
'conpherencePHID IN (%Ls)',
$this->conpherencePHIDs);
}
if ($this->participantPHIDs) {
$where[] = qsprintf(
$conn_r,
@ -69,28 +92,41 @@ final class ConpherenceParticipantQuery
$this->participantPHIDs);
}
if ($this->participationStatus !== null) {
if ($this->participantCursor) {
$date_touched = $this->participantCursor->getDateTouched();
$id = $this->participantCursor->getID();
if ($this->order == self::ORDER_OLDER) {
$compare_date = '<';
$compare_id = '<=';
} else {
$compare_date = '>';
$compare_id = '>=';
}
$where[] = qsprintf(
$conn_r,
'participationStatus = %d',
$this->participationStatus);
}
if ($this->dateTouched) {
if ($this->dateTouchedSort) {
$where[] = qsprintf(
$conn_r,
'dateTouched %Q %d',
$this->dateTouchedSort,
$this->dateTouched);
}
'(dateTouched %Q %d OR (dateTouched = %d AND id %Q %d))',
$compare_date,
$date_touched,
$date_touched,
$compare_id,
$id);
}
return $this->formatWhereClause($where);
}
private function buildOrderClause(AphrontDatabaseConnection $conn_r) {
return 'ORDER BY dateTouched DESC';
$order_word = ($this->order == self::ORDER_OLDER) ? 'DESC' : 'ASC';
// if these are different direction we won't get as efficient a query
// see http://dev.mysql.com/doc/refman/5.5/en/order-by-optimization.html
$order = qsprintf(
$conn_r,
'ORDER BY dateTouched %Q, id %Q',
$order_word,
$order_word);
return $order;
}
}

View file

@ -3,26 +3,33 @@
final class ConpherenceThreadListView extends AphrontView {
private $baseURI;
private $unreadThreads;
private $readThreads;
private $threads;
private $scrollUpParticipant;
private $scrollDownParticipant;
public function setThreads(array $threads) {
assert_instances_of($threads, 'ConpherenceThread');
$this->threads = $threads;
return $this;
}
public function setScrollUpParticipant(
ConpherenceParticipant $participant) {
$this->scrollUpParticipant = $participant;
return $this;
}
public function setScrollDownParticipant(
ConpherenceParticipant $participant) {
$this->scrollDownParticipant = $participant;
return $this;
}
public function setBaseURI($base_uri) {
$this->baseURI = $base_uri;
return $this;
}
public function setUnreadThreads(array $unread_threads) {
assert_instances_of($unread_threads, 'ConpherenceThread');
$this->unreadThreads = $unread_threads;
return $this;
}
public function setReadThreads(array $read_threads) {
assert_instances_of($read_threads, 'ConpherenceThread');
$this->readThreads = $read_threads;
return $this;
}
public function render() {
require_celerity_resource('conpherence-menu-css');
@ -39,10 +46,8 @@ final class ConpherenceThreadListView extends AphrontView {
->setHref($this->baseURI.'new/')
->setType(PhabricatorMenuItemView::TYPE_BUTTON));
$menu->newLabel(pht('Unread'));
$this->addThreadsToMenu($menu, $this->unreadThreads, $read = false);
$menu->newLabel(pht('Read'));
$this->addThreadsToMenu($menu, $this->readThreads, $read = true);
$menu->newLabel('');
$this->addThreadsToMenu($menu, $this->threads);
return $menu;
}
@ -51,6 +56,28 @@ final class ConpherenceThreadListView extends AphrontView {
return $this->renderThread($thread);
}
public function renderThreadsHTML() {
$thread_html = array();
if ($this->scrollUpParticipant->getID()) {
$thread_html[] = $this->getScrollMenuItem(
$this->scrollUpParticipant,
'up');
}
foreach ($this->threads as $thread) {
$thread_html[] = $this->renderSingleThread($thread);
}
if ($this->scrollDownParticipant->getID()) {
$thread_html[] = $this->getScrollMenuItem(
$this->scrollDownParticipant,
'down');
}
return phutil_implode_html('', $thread_html);
}
private function renderThreadItem(ConpherenceThread $thread) {
return id(new PhabricatorMenuItemView())
->setType(PhabricatorMenuItemView::TYPE_CUSTOM)
@ -87,28 +114,59 @@ final class ConpherenceThreadListView extends AphrontView {
private function addThreadsToMenu(
PhabricatorMenuView $menu,
array $conpherences,
$read = false) {
array $conpherences) {
if ($this->scrollUpParticipant->getID()) {
$item = $this->getScrollMenuItem($this->scrollUpParticipant, 'up');
$menu->addMenuItem($item);
}
foreach ($conpherences as $conpherence) {
$item = $this->renderThreadItem($conpherence);
$menu->addMenuItem($item);
}
if (empty($conpherences) || $read) {
$menu->addMenuItem($this->getNoConpherencesBlock());
if (empty($conpherences)) {
$menu->addMenuItem($this->getNoConpherencesMenuItem());
}
if ($this->scrollDownParticipant->getID()) {
$item = $this->getScrollMenuItem($this->scrollDownParticipant, 'down');
$menu->addMenuItem($item);
}
return $menu;
}
private function getNoConpherencesBlock() {
public function getScrollMenuItem(
ConpherenceParticipant $participant,
$direction) {
if ($direction == 'up') {
$name = pht('Load Newer Threads');
} else {
$name = pht('Load Older Threads');
}
$item = id(new PhabricatorMenuItemView())
->addSigil('conpherence-menu-scroller')
->setName($name)
->setHref($this->baseURI)
->setType(PhabricatorMenuItemView::TYPE_BUTTON)
->setMetadata(array(
'participant_id' => $participant->getID(),
'conpherence_phid' => $participant->getConpherencePHID(),
'date_touched' => $participant->getDateTouched(),
'direction' => $direction));
return $item;
}
private function getNoConpherencesMenuItem() {
$message = phutil_tag(
'div',
array(
'class' => 'no-conpherences-menu-item'
),
pht('No more conpherences.'));
pht('No conpherences.'));
return id(new PhabricatorMenuItemView())
->setType(PhabricatorMenuItemView::TYPE_CUSTOM)

View file

@ -257,6 +257,11 @@ abstract class PhabricatorApplicationTransactionEditor
throw new Exception("Capability not supported!");
}
protected function applyFinalEffects(
PhabricatorLiskDAO $object,
array $xactions) {
}
public function setContentSource(PhabricatorContentSource $content_source) {
$this->contentSource = $content_source;
return $this;
@ -386,6 +391,8 @@ abstract class PhabricatorApplicationTransactionEditor
$this->applyExternalEffects($object, $xaction);
}
$this->applyFinalEffects($object, $xactions);
if ($read_locking) {
$object->endReadLocking();
$read_locking = false;

View file

@ -1250,6 +1250,10 @@ final class PhabricatorBuiltinPatchList extends PhabricatorSQLPatchList {
'type' => 'sql',
'name' => $this->getPatchPath('20130423.phortunepaymentrevised.sql'),
),
'20130423.conpherenceindices.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130423.conpherenceindices.sql'),
),
);
}
}

View file

@ -267,11 +267,11 @@ final class PhabricatorMainMenuView extends AphrontView {
$message_count_id = celerity_generate_unique_node_id();
$unread_status = ConpherenceParticipationStatus::BEHIND;
$unread = id(new ConpherenceParticipantQuery())
$unread = id(new ConpherenceParticipantCountQuery())
->withParticipantPHIDs(array($user->getPHID()))
->withParticipationStatus($unread_status)
->execute();
$message_count_number = count($unread);
$message_count_number = $unread[$user->getPHID()];
if ($message_count_number > 999) {
$message_count_number = "\xE2\x88\x9E";
}

View file

@ -8,6 +8,7 @@
* javelin-workflow
* javelin-behavior-device
* javelin-history
* javelin-vector
*/
JX.behavior('conpherence-menu', function(config) {
@ -53,6 +54,15 @@ JX.behavior('conpherence-menu', function(config) {
redrawthread();
}
JX.Stratcom.listen(
'conpherence-selectthread',
null,
function (e) {
var node = JX.$(e.getData().id);
selectthread(node);
}
);
function updatepagedata(data) {
var uri_suffix = thread.selected + '/';
if (data.use_base_uri) {
@ -75,15 +85,6 @@ JX.behavior('conpherence-menu', function(config) {
}
);
JX.Stratcom.listen(
'conpherence-selectthread',
null,
function (e) {
var node = JX.$(e.getData().id);
selectthread(node);
}
);
function redrawthread() {
if (!thread.node) {
return;
@ -96,9 +97,9 @@ JX.behavior('conpherence-menu', function(config) {
var data = JX.Stratcom.getData(thread.node);
if (thread.visible !== null || !config.hasThread) {
var uri = config.base_uri + data.id + '/';
var uri = config.base_uri + data.id + '/';
new JX.Workflow(uri, {})
.setHandler(onresponse)
.setHandler(onloadthreadresponse)
.start();
} else {
didredrawthread();
@ -154,7 +155,7 @@ JX.behavior('conpherence-menu', function(config) {
}
}
function onresponse(response) {
function onloadthreadresponse(response) {
var header = JX.$H(response.header);
var messages = JX.$H(response.messages);
var form = JX.$H(response.form);
@ -252,11 +253,9 @@ JX.behavior('conpherence-menu', function(config) {
}).setData({ oldest_transaction_id : oldest_transaction_id }).send();
});
// On mobile, we just show a thread list, so we don't want to automatically
// select or load any threads. On Desktop, we automatically select the first
// thread.
var old_device = null;
function ondevicechange() {
var new_device = JX.Device.getDevice();
@ -284,16 +283,18 @@ JX.behavior('conpherence-menu', function(config) {
function loadthreads() {
var uri = config.base_uri + 'thread/' + config.selectedID + '/';
new JX.Workflow(uri)
.setHandler(onthreadresponse)
.setHandler(onloadthreadsresponse)
.start();
}
function onthreadresponse(r) {
function onloadthreadsresponse(r) {
var layout = JX.$(config.layoutID);
var menu = JX.DOM.find(layout, 'div', 'conpherence-menu-pane');
JX.DOM.setContent(menu, JX.$H(r));
config.selectedID && selectthreadid(config.selectedID);
thread.node.scrollIntoView();
}
function didloadthreads() {
@ -316,4 +317,71 @@ JX.behavior('conpherence-menu', function(config) {
redrawthread();
}
var handlethreadscrollers = function (e) {
e.kill();
var data = e.getNodeData('conpherence-menu-scroller');
var scroller = e.getNode('conpherence-menu-scroller');
new JX.Workflow(scroller.href, data)
.setHandler(
JX.bind(null, threadscrollerresponse, scroller, data.direction))
.start();
};
var threadscrollerresponse = function (scroller, direction, r) {
var html = JX.$H(r.html);
var threadPhids = r.phids;
var reselectId = null;
// remove any threads that are in the list that we just got back
// in the result set; things have changed and they'll be in the
// right place soon
for (var ii = 0; ii < threadPhids.length; ii++) {
try {
var nodeId = threadPhids[ii] + '-nav-item';
var node = JX.$(nodeId);
var nodeData = JX.Stratcom.getData(node);
if (nodeData.id == thread.selected) {
reselectId = nodeId;
}
JX.DOM.remove(node);
} catch (ex) {
// ignore , just haven't seen this thread yet
}
}
var root = JX.DOM.find(document, 'div', 'conpherence-layout');
var menuRoot = JX.DOM.find(root, 'div', 'conpherence-menu-pane');
var scrollY = 0;
// we have to do some hyjinx in the up case to make the menu scroll to
// where it should
if (direction == 'up') {
var style = {
position: 'absolute',
left: '-10000px'
};
var test_size = JX.$N('div', {style: style}, html);
document.body.appendChild(test_size);
var html_size = JX.Vector.getDim(test_size);
JX.DOM.remove(test_size);
scrollY = html_size.y;
}
JX.DOM.replace(scroller, html);
menuRoot.scrollTop += scrollY;
if (reselectId) {
JX.Stratcom.invoke(
'conpherence-selectthread',
null,
{ id : reselectId }
);
}
};
JX.Stratcom.listen(
['click'],
'conpherence-menu-scroller',
handlethreadscrollers
);
});