mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-26 15:30:58 +01:00
Add a "feed" filter to the home page; align things; allow browsing older stories
Summary: Pretty straightforward; see title. Kind of gross but I have a bunch more iterations in mind here (like filtering). Paging this is a little tricky since we can't easily use AphrontPagerView, as it relies on OFFSET, and I think that's sort of sketchy to use here for UX reasons (query performance and view consistency as feed updates). Test Plan: Looked at feed, paged through feed. Reviewers: btrahan Reviewed By: btrahan CC: aran, epriestley Differential Revision: https://secure.phabricator.com/D1616
This commit is contained in:
parent
fce6a7089c
commit
29acc848c1
10 changed files with 153 additions and 121 deletions
|
@ -1406,7 +1406,7 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'phabricator-feed-css' =>
|
||||
array(
|
||||
'uri' => '/res/7d1d0015/rsrc/css/application/feed/feed.css',
|
||||
'uri' => '/res/e4bf27b5/rsrc/css/application/feed/feed.css',
|
||||
'type' => 'css',
|
||||
'requires' =>
|
||||
array(
|
||||
|
|
|
@ -512,7 +512,6 @@ phutil_register_library_map(array(
|
|||
'PhabricatorFeedStoryTypeConstants' => 'applications/feed/constants/story',
|
||||
'PhabricatorFeedStoryUnknown' => 'applications/feed/story/unknown',
|
||||
'PhabricatorFeedStoryView' => 'applications/feed/view/story',
|
||||
'PhabricatorFeedStreamController' => 'applications/feed/controller/stream',
|
||||
'PhabricatorFeedView' => 'applications/feed/view/base',
|
||||
'PhabricatorFile' => 'applications/files/storage/file',
|
||||
'PhabricatorFileController' => 'applications/files/controller/base',
|
||||
|
@ -1242,7 +1241,6 @@ phutil_register_library_map(array(
|
|||
'PhabricatorFeedStoryTypeConstants' => 'PhabricatorFeedConstants',
|
||||
'PhabricatorFeedStoryUnknown' => 'PhabricatorFeedStory',
|
||||
'PhabricatorFeedStoryView' => 'PhabricatorFeedView',
|
||||
'PhabricatorFeedStreamController' => 'PhabricatorFeedController',
|
||||
'PhabricatorFeedView' => 'AphrontView',
|
||||
'PhabricatorFile' => 'PhabricatorFileDAO',
|
||||
'PhabricatorFileController' => 'PhabricatorController',
|
||||
|
|
|
@ -32,9 +32,7 @@ class AphrontDefaultApplicationConfiguration
|
|||
|
||||
public function getURIMap() {
|
||||
return $this->getResourceURIMapRules() + array(
|
||||
'/' => array(
|
||||
'$' => 'PhabricatorDirectoryMainController',
|
||||
),
|
||||
'/(?:(?P<filter>feed)/)?$' => 'PhabricatorDirectoryMainController',
|
||||
'/directory/' => array(
|
||||
'(?P<id>\d+)/$'
|
||||
=> 'PhabricatorDirectoryCategoryViewController',
|
||||
|
@ -359,10 +357,7 @@ class AphrontDefaultApplicationConfiguration
|
|||
=> 'PhabricatorCountdownDeleteController'
|
||||
),
|
||||
|
||||
'/feed/' => array(
|
||||
'$' => 'PhabricatorFeedStreamController',
|
||||
'public/$' => 'PhabricatorFeedPublicStreamController',
|
||||
),
|
||||
'/feed/public/$' => 'PhabricatorFeedPublicStreamController',
|
||||
|
||||
'/V(?P<id>\d+)$' => 'PhabricatorSlowvotePollController',
|
||||
'/vote/' => array(
|
||||
|
|
|
@ -44,6 +44,7 @@ abstract class PhabricatorDirectoryController extends PhabricatorController {
|
|||
|
||||
$nav->addLabel('Phabricator');
|
||||
$nav->addFilter('home', 'Tactical Command', '/');
|
||||
$nav->addFilter('feed', 'Feed');
|
||||
$nav->addSpacer();
|
||||
$nav->addLabel('Applications');
|
||||
|
||||
|
|
|
@ -21,6 +21,10 @@ class PhabricatorDirectoryMainController
|
|||
|
||||
private $filter;
|
||||
|
||||
public function willProcessRequest(array $data) {
|
||||
$this->filter = idx($data, 'filter');
|
||||
}
|
||||
|
||||
public function shouldRequireAdmin() {
|
||||
// These controllers are admin-only by default, but this one is public,
|
||||
// so allow non-admin users to view it.
|
||||
|
@ -30,18 +34,29 @@ class PhabricatorDirectoryMainController
|
|||
public function processRequest() {
|
||||
$user = $this->getRequest()->getUser();
|
||||
|
||||
$nav = $this->buildNav();
|
||||
$this->filter = $nav->selectFilter($this->filter, 'home');
|
||||
|
||||
$project_query = new PhabricatorProjectQuery();
|
||||
$project_query->setMembers(array($user->getPHID()));
|
||||
$projects = $project_query->execute();
|
||||
|
||||
switch ($this->filter) {
|
||||
case 'feed':
|
||||
return $this->buildFeedResponse($nav, $projects);
|
||||
default:
|
||||
return $this->buildMainResponse($nav, $projects);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private function buildMainResponse($nav, $projects) {
|
||||
$unbreak_panel = $this->buildUnbreakNowPanel();
|
||||
$triage_panel = $this->buildNeedsTriagePanel($projects);
|
||||
$revision_panel = $this->buildRevisionPanel();
|
||||
$tasks_panel = $this->buildTasksPanel();
|
||||
$feed_view = $this->buildFeedView($projects);
|
||||
$feed_view = $this->buildFeedView($projects, $is_full = false);
|
||||
|
||||
$nav = $this->buildNav();
|
||||
$this->filter = $nav->selectFilter($this->filter, 'home');
|
||||
|
||||
$content = array(
|
||||
$unbreak_panel,
|
||||
|
@ -56,8 +71,17 @@ class PhabricatorDirectoryMainController
|
|||
return $this->buildStandardPageResponse(
|
||||
$nav,
|
||||
array(
|
||||
'title' => 'Directory',
|
||||
'tab' => 'directory',
|
||||
'title' => 'Phabricator',
|
||||
));
|
||||
}
|
||||
|
||||
private function buildFeedResponse($nav, $projects) {
|
||||
$view = $this->buildFeedView($projects, $is_full = true);
|
||||
$nav->appendChild($view);
|
||||
return $this->buildStandardPageResponse(
|
||||
$nav,
|
||||
array(
|
||||
'title' => 'Feed',
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -252,8 +276,9 @@ class PhabricatorDirectoryMainController
|
|||
return $view;
|
||||
}
|
||||
|
||||
private function buildFeedView(array $projects) {
|
||||
$user = $this->getRequest()->getUser();
|
||||
private function buildFeedView(array $projects, $is_full) {
|
||||
$request = $this->getRequest();
|
||||
$user = $request->getUser();
|
||||
$user_phid = $user->getPHID();
|
||||
|
||||
$feed_query = new PhabricatorFeedQuery();
|
||||
|
@ -261,19 +286,77 @@ class PhabricatorDirectoryMainController
|
|||
array_merge(
|
||||
array($user_phid),
|
||||
mpull($projects, 'getPHID')));
|
||||
|
||||
// TODO: All this limit stuff should probably be consolidated into the
|
||||
// feed query?
|
||||
|
||||
$old_link = null;
|
||||
$new_link = null;
|
||||
|
||||
if ($is_full) {
|
||||
$feed_query->setAfter($request->getStr('after'));
|
||||
$feed_query->setBefore($request->getStr('before'));
|
||||
$limit = 500;
|
||||
} else {
|
||||
$limit = 100;
|
||||
}
|
||||
|
||||
// Grab one more story than we intend to display so we can figure out
|
||||
// if we need to render an "Older Posts" link or not (with reasonable
|
||||
// accuracy, at least).
|
||||
$feed_query->setLimit($limit + 1);
|
||||
$feed = $feed_query->execute();
|
||||
$extra_row = (count($feed) == $limit + 1);
|
||||
|
||||
if ($is_full) {
|
||||
$have_new = ($request->getStr('before')) ||
|
||||
($request->getStr('after') && $extra_row);
|
||||
} else {
|
||||
$have_new = false;
|
||||
}
|
||||
|
||||
$have_old = ($request->getStr('after')) ||
|
||||
($request->getStr('before') && $extra_row) ||
|
||||
(!$request->getStr('before') &&
|
||||
!$request->getStr('after') &&
|
||||
$extra_row);
|
||||
$feed = array_slice($feed, 0, $limit, $preserve_keys = true);
|
||||
|
||||
if ($have_old) {
|
||||
$old_link = phutil_render_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => '/feed/?before='.end($feed)->getChronologicalKey(),
|
||||
'class' => 'phabricator-feed-older-link',
|
||||
),
|
||||
"Older Stories \xC2\xBB");
|
||||
}
|
||||
if ($have_new) {
|
||||
$new_link = phutil_render_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => '/feed/?after='.reset($feed)->getChronologicalKey(),
|
||||
'class' => 'phabricator-feed-newer-link',
|
||||
),
|
||||
"\xC2\xAB Newer Stories");
|
||||
}
|
||||
|
||||
$builder = new PhabricatorFeedBuilder($feed);
|
||||
$builder->setUser($user);
|
||||
$feed_view = $builder->buildView();
|
||||
|
||||
|
||||
return
|
||||
'<div style="padding: 1em 1em;">'.
|
||||
'<h1 style="font-size: 18px; '.
|
||||
'border-bottom: 1px solid #aaaaaa; '.
|
||||
'margin: 0 1em;">Feed</h1>'.
|
||||
'<div style="padding: 1em 3em;">'.
|
||||
'<div style="margin: 0 1em;">'.
|
||||
'<h1 style="font-size: 18px; '.
|
||||
'border-bottom: 1px solid #aaaaaa; '.
|
||||
'padding: 0;">Feed</h1>'.
|
||||
'</div>'.
|
||||
$feed_view->render().
|
||||
'<div class="phabricator-feed-frame">'.
|
||||
$new_link.
|
||||
$old_link.
|
||||
'</div>'.
|
||||
'</div>';
|
||||
}
|
||||
|
||||
|
|
|
@ -1,74 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2011 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
final class PhabricatorFeedStreamController extends PhabricatorFeedController {
|
||||
|
||||
public function processRequest() {
|
||||
|
||||
$request = $this->getRequest();
|
||||
$viewer = $request->getUser();
|
||||
|
||||
if ($request->isFormPost()) {
|
||||
$story = id(new PhabricatorFeedStoryPublisher())
|
||||
->setRelatedPHIDs(array($viewer->getPHID()))
|
||||
->setStoryType(PhabricatorFeedStoryTypeConstants::STORY_STATUS)
|
||||
->setStoryTime(time())
|
||||
->setStoryAuthorPHID($viewer->getPHID())
|
||||
->setStoryData(
|
||||
array(
|
||||
'content' => $request->getStr('status')
|
||||
))
|
||||
->publish();
|
||||
|
||||
return id(new AphrontRedirectResponse())->setURI(
|
||||
$request->getRequestURI());
|
||||
}
|
||||
|
||||
$query = new PhabricatorFeedQuery();
|
||||
$stories = $query->execute();
|
||||
|
||||
$builder = new PhabricatorFeedBuilder($stories);
|
||||
$builder->setUser($request->getUser());
|
||||
$view = $builder->buildView();
|
||||
|
||||
$post_form = id(new AphrontFormView())
|
||||
->setUser($viewer)
|
||||
->appendChild(
|
||||
id(new AphrontFormTextAreaControl())
|
||||
->setLabel('Pithy Wit')
|
||||
->setName('status'))
|
||||
->appendChild(
|
||||
id(new AphrontFormSubmitControl())
|
||||
->setValue('Publish'));
|
||||
|
||||
$post = new AphrontPanelView();
|
||||
$post->setWidth(AphrontPanelView::WIDTH_FORM);
|
||||
$post->setHeader('High Horse Soapbox');
|
||||
$post->appendChild($post_form);
|
||||
|
||||
$page = array();
|
||||
$page[] = $post;
|
||||
$page[] = $view;
|
||||
|
||||
return $this->buildStandardPageResponse(
|
||||
$page,
|
||||
array(
|
||||
'title' => 'Feed',
|
||||
));
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is automatically generated. Lint this module to rebuild it.
|
||||
* @generated
|
||||
*/
|
||||
|
||||
|
||||
|
||||
phutil_require_module('phabricator', 'aphront/response/redirect');
|
||||
phutil_require_module('phabricator', 'applications/feed/builder/feed');
|
||||
phutil_require_module('phabricator', 'applications/feed/constants/story');
|
||||
phutil_require_module('phabricator', 'applications/feed/controller/base');
|
||||
phutil_require_module('phabricator', 'applications/feed/publisher');
|
||||
phutil_require_module('phabricator', 'applications/feed/query');
|
||||
phutil_require_module('phabricator', 'view/form/base');
|
||||
phutil_require_module('phabricator', 'view/form/control/submit');
|
||||
phutil_require_module('phabricator', 'view/form/control/textarea');
|
||||
phutil_require_module('phabricator', 'view/layout/panel');
|
||||
|
||||
phutil_require_module('phutil', 'utils');
|
||||
|
||||
|
||||
phutil_require_source('PhabricatorFeedStreamController.php');
|
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2011 Facebook, Inc.
|
||||
* Copyright 2012 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -21,6 +21,7 @@ final class PhabricatorFeedQuery {
|
|||
private $filterPHIDs;
|
||||
private $limit = 100;
|
||||
private $after;
|
||||
private $before;
|
||||
|
||||
public function setFilterPHIDs(array $phids) {
|
||||
$this->filterPHIDs = $phids;
|
||||
|
@ -37,6 +38,11 @@ final class PhabricatorFeedQuery {
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function setBefore($before) {
|
||||
$this->before = $before;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function execute() {
|
||||
|
||||
$ref_table = new PhabricatorFeedStoryReference();
|
||||
|
@ -52,6 +58,29 @@ final class PhabricatorFeedQuery {
|
|||
$this->filterPHIDs);
|
||||
}
|
||||
|
||||
// For "before" queries, we can just add a constraint to the WHERE clause.
|
||||
// For "after" queries, we must also reverse the result ordering, since
|
||||
// otherwise we'll always grab the first page of results if there's a limit.
|
||||
// After MySQL applies the limit, we reverse the page in PHP (below) to
|
||||
// ensure consistent ordering.
|
||||
|
||||
$order = 'DESC';
|
||||
|
||||
if ($this->after) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'ref.chronologicalKey > %s',
|
||||
$this->after);
|
||||
$order = 'ASC';
|
||||
}
|
||||
|
||||
if ($this->before) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'ref.chronologicalKey < %s',
|
||||
$this->before);
|
||||
}
|
||||
|
||||
if ($where) {
|
||||
$where = 'WHERE ('.implode(') AND (', $where).')';
|
||||
} else {
|
||||
|
@ -64,12 +93,20 @@ final class PhabricatorFeedQuery {
|
|||
JOIN %T story ON ref.chronologicalKey = story.chronologicalKey
|
||||
%Q
|
||||
GROUP BY story.chronologicalKey
|
||||
ORDER BY story.chronologicalKey DESC
|
||||
ORDER BY story.chronologicalKey %Q
|
||||
LIMIT %d',
|
||||
$ref_table->getTableName(),
|
||||
$story_table->getTableName(),
|
||||
$where,
|
||||
$order,
|
||||
$this->limit);
|
||||
|
||||
if ($order != 'DESC') {
|
||||
// If we did order ASC to pull 'after' data, reverse the result set so
|
||||
// that stories are returned in a consistent (descending) order.
|
||||
$data = array_reverse($data);
|
||||
}
|
||||
|
||||
$data = $story_table->loadAllFromArray($data);
|
||||
|
||||
$stories = array();
|
||||
|
|
|
@ -83,6 +83,10 @@ abstract class PhabricatorFeedStory {
|
|||
return $this->getStoryData()->getEpoch();
|
||||
}
|
||||
|
||||
final public function getChronologicalKey() {
|
||||
return $this->getStoryData()->getChronologicalKey();
|
||||
}
|
||||
|
||||
final protected function renderHandleList(array $phids) {
|
||||
$list = array();
|
||||
foreach ($phids as $phid) {
|
||||
|
|
|
@ -43,3 +43,14 @@
|
|||
.phabricator-feed-story-date-separator {
|
||||
margin-top: 2em;
|
||||
}
|
||||
|
||||
.phabricator-feed-newer-link {
|
||||
float: left;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.phabricator-feed-older-link {
|
||||
float: right;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue