diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2533,6 +2533,10 @@ 'PhrequentConduitAPIMethod' => 'applications/phrequent/conduit/PhrequentConduitAPIMethod.php', 'PhrequentController' => 'applications/phrequent/controller/PhrequentController.php', 'PhrequentDAO' => 'applications/phrequent/storage/PhrequentDAO.php', + 'PhrequentEditController' => 'applications/phrequent/controller/PhrequentEditController.php', + 'PhrequentExcelDefaultFormat' => 'applications/phrequent/export/PhrequentExcelDefaultFormat.php', + 'PhrequentExcelFormat' => 'applications/phrequent/export/PhrequentExcelFormat.php', + 'PhrequentExportController' => 'applications/phrequent/controller/PhrequentExportController.php', 'PhrequentListController' => 'applications/phrequent/controller/PhrequentListController.php', 'PhrequentPopConduitAPIMethod' => 'applications/phrequent/conduit/PhrequentPopConduitAPIMethod.php', 'PhrequentPushConduitAPIMethod' => 'applications/phrequent/conduit/PhrequentPushConduitAPIMethod.php', @@ -5448,6 +5452,9 @@ 'PhrequentConduitAPIMethod' => 'ConduitAPIMethod', 'PhrequentController' => 'PhabricatorController', 'PhrequentDAO' => 'PhabricatorLiskDAO', + 'PhrequentEditController' => 'PhrequentController', + 'PhrequentExcelDefaultFormat' => 'PhrequentExcelFormat', + 'PhrequentExportController' => 'PhrequentController', 'PhrequentListController' => 'PhrequentController', 'PhrequentPopConduitAPIMethod' => 'PhrequentConduitAPIMethod', 'PhrequentPushConduitAPIMethod' => 'PhrequentConduitAPIMethod', diff --git a/src/applications/phrequent/application/PhabricatorPhrequentApplication.php b/src/applications/phrequent/application/PhabricatorPhrequentApplication.php --- a/src/applications/phrequent/application/PhabricatorPhrequentApplication.php +++ b/src/applications/phrequent/application/PhabricatorPhrequentApplication.php @@ -41,7 +41,10 @@ '/phrequent/' => array( '(?:query/(?P[^/]+)/)?' => 'PhrequentListController', 'track/(?P[a-z]+)/(?P[^/]+)/' - => 'PhrequentTrackController' + => 'PhrequentTrackController', + 'create/' => 'PhrequentEditController', + 'edit/(?[0-9]+)/' => 'PhrequentEditController', + 'export/(?[^/]+)/' => 'PhrequentExportController' ), ); } diff --git a/src/applications/phrequent/controller/PhrequentController.php b/src/applications/phrequent/controller/PhrequentController.php --- a/src/applications/phrequent/controller/PhrequentController.php +++ b/src/applications/phrequent/controller/PhrequentController.php @@ -16,4 +16,16 @@ return $nav; } + + protected function buildApplicationCrumbs() { + $crumbs = parent::buildApplicationCrumbs(); + + $crumbs->addAction( + id(new PHUIListItemView()) + ->setName(pht('Create Tracked Time')) + ->setHref($this->getApplicationURI('create/')) + ->setIcon('create')); + + return $crumbs; + } } diff --git a/src/applications/phrequent/controller/PhrequentEditController.php b/src/applications/phrequent/controller/PhrequentEditController.php new file mode 100644 --- /dev/null +++ b/src/applications/phrequent/controller/PhrequentEditController.php @@ -0,0 +1,176 @@ +id = idx($data, 'id'); + } + + + public function processRequest() { + $request = $this->getRequest(); + $user = $request->getUser(); + $is_create = !$this->id; + + if ($is_create) { + $usertime = id(new PhrequentUserTime()) + ->setUserPHID($user->getPHID()); + } else { + $usertime = id(new PhrequentUserTimeQuery()) + ->setViewer($user) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->withIDs(array($this->id)) + ->executeOne(); + if (!$usertime) { + return new Aphront404Response(); + } + } + + $phids = array( + $usertime->getUserPHID(), + $usertime->getObjectPHID()); + + $handles = $this->loadViewerHandles($phids); + + $user_control = id(new AphrontFormTokenizerControl()) + ->setLabel(pht('User')) + ->setName('user') + ->setUser($user) + ->setDatasource('/typeahead/common/users/') + ->setLimit(1) + ->setDisableBehavior(true); + + $object_control = id(new AphrontFormTokenizerControl()) + ->setLabel(pht('Object')) + ->setName('object') + ->setUser($user) + ->setDatasource('/typeahead/common/mainsearch/') + ->setLimit(1); + + $start_control = id(new AphrontFormDateControl()) + ->setLabel(pht('Start')) + ->setName('start') + ->setUser($user) + ->setInitialTime(AphrontFormDateControl::TIME_START_OF_DAY); + + $end_control = id(new AphrontFormDateControl()) + ->setLabel(pht('End')) + ->setName('end') + ->setUser($user) + ->setInitialTime(AphrontFormDateControl::TIME_END_OF_DAY); + + $text = null; + $errors = array(); + $error_view = null; + + if ($is_create) { + $page_title = pht('New Tracked Time'); + } else { + $page_title = pht('Edit Tracked Time'); + } + + if ($request->isFormPost()) { + $object_value = $request->getArr('object'); + $user_value = $request->getArr('user'); + $object_phid = reset($object_value); + $user_phid = reset($user_value); + $start_value = $start_control->readValueFromRequest($request); + $end_value = $end_control->readValueFromRequest($request); + + if (!strlen($object_phid)) { + $object_control->setError(pht('Required')); + $errors[] = pht('Object is required.'); + } + + if ($start_value >= $end_value) { + $end_control->setError(pht('Undersized')); + $errors[] = pht('End must be greater than start.'); + } + + if (!$errors) { + $usertime + ->setUserPHID($user_phid) + ->setObjectPHID($object_phid) + ->setDateStarted($start_value) + ->setDateEnded($end_value) + ->save(); + + $uri = new PhutilURI($this->getApplicationURI()); + + if ($request->isAjax()) { + $response = id(new AphrontAjaxResponse()) + ->setContent(array('redirect_uri' => $uri)); + } else { + $response = id(new AphrontRedirectResponse()) + ->setURI($uri); + } + + return $response; + } + } + + $form = new AphrontFormView(); + + if ($error_view) { + $form->appendChild($error_view); + } + + $form + ->setUser($user); + + if (!$is_create) { + $user_control->setValue(array($handles[$usertime->getUserPHID()])); + $object_control->setValue(array($handles[$usertime->getObjectPHID()])); + $start_control->setValue($usertime->getDateStarted()); + $end_control->setValue($usertime->getDateEnded()); + } else { + $user_control->setValue(array($handles[$usertime->getUserPHID()])); + } + + $form + ->appendChild($user_control) + ->appendChild($object_control) + ->appendChild($start_control) + ->appendChild($end_control); + + $submit = new AphrontFormSubmitControl(); + $submit->addCancelButton($this->getApplicationURI()); + + if (!$is_create) { + $submit->setValue(pht('Save Tracked Time')); + $title = pht('Edit Tracked Time'); + $short = pht('Edit'); + } else { + $submit->setValue(pht('Create Tracked Time')); + $title = pht('Create New Tracked Time'); + $short = pht('Create'); + } + + $form->appendChild($submit); + + $form_box = id(new PHUIObjectBoxView()) + ->setHeaderText($title) + ->setFormErrors($errors) + ->setForm($form); + + $crumbs = $this->buildApplicationCrumbs($this->buildSideNavView()); + $crumbs->addTextCrumb($page_title); + + return $this->buildApplicationPage( + array( + $crumbs, + $form_box, + ), + array( + 'title' => $title, + 'device' => true, + )); + } + +} diff --git a/src/applications/phrequent/controller/PhrequentExportController.php b/src/applications/phrequent/controller/PhrequentExportController.php new file mode 100644 --- /dev/null +++ b/src/applications/phrequent/controller/PhrequentExportController.php @@ -0,0 +1,178 @@ +key = $data['key']; + return $this; + } + + /** + * @phutil-external-symbol class PHPExcel + * @phutil-external-symbol class PHPExcel_IOFactory + * @phutil-external-symbol class PHPExcel_Style_NumberFormat + * @phutil-external-symbol class PHPExcel_Cell_DataType + */ + public function processRequest() { + $request = $this->getRequest(); + $user = $request->getUser(); + + $ok = @include_once 'PHPExcel.php'; + if (!$ok) { + $dialog = new AphrontDialogView(); + $dialog->setUser($user); + + $inst1 = pht( + 'This system does not have PHPExcel installed. This software '. + 'component is required to export tasks to Excel. Have your system '. + 'administrator install it from:'); + + $inst2 = pht( + 'Your PHP "include_path" needs to be updated to include the '. + 'PHPExcel Classes directory.'); + + $dialog->setTitle(pht('Excel Export Not Configured')); + $dialog->appendChild(hsprintf( + '

%s

'. + '
'. + '

'. + 'http://www.phpexcel.net/'. + '

'. + '
'. + '

%s

', + $inst1, + $inst2)); + + $dialog->addCancelButton('/phrequent/'); + return id(new AphrontDialogResponse())->setDialog($dialog); + } + + // TODO: PHPExcel has a dependency on the PHP zip extension. We should test + // for that here, since it fatals if we don't have the ZipArchive class. + + $saved = id(new PhabricatorSavedQueryQuery()) + ->setViewer($user) + ->withQueryKeys(array($this->key)) + ->executeOne(); + if (!$saved) { + $engine = id(new PhrequentSearchEngine()) + ->setViewer($user); + if ($engine->isBuiltinQuery($this->key)) { + $saved = $engine->buildSavedQueryFromBuiltin($this->key); + } + if (!$saved) { + return new Aphront404Response(); + } + } + + $formats = PhrequentExcelFormat::loadAllFormats(); + $export_formats = array(); + foreach ($formats as $format_class => $format_object) { + $export_formats[$format_class] = $format_object->getName(); + } + + if (!$request->isDialogFormPost()) { + $dialog = new AphrontDialogView(); + $dialog->setUser($user); + + $dialog->setTitle(pht('Export User Time to Excel')); + $dialog->appendChild(phutil_tag('p', array(), pht( + 'Do you want to export the query results to Excel?'))); + + $form = id(new PHUIFormLayoutView()) + ->appendChild( + id(new AphrontFormSelectControl()) + ->setLabel(pht('Format:')) + ->setName('excel-format') + ->setOptions($export_formats)); + + $dialog->appendChild($form); + + $dialog->addCancelButton('/phrequent/'); + $dialog->addSubmitButton(pht('Export to Excel')); + return id(new AphrontDialogResponse())->setDialog($dialog); + } + + $format = idx($formats, $request->getStr('excel-format')); + if ($format === null) { + throw new Exception('Excel format object not found.'); + } + + $saved->makeEphemeral(); + $saved->setParameter('limit', PHP_INT_MAX); + + $engine = id(new PhrequentSearchEngine()) + ->setViewer($user); + + $query = $engine->buildQueryFromSavedQuery($saved); + $query->setViewer($user); + $events = $query->execute(); + + $before_date = $saved->getParameter('before'); + $after_date = $saved->getParameter('after'); + + if ($before_date || $after_date) { + foreach ($events as $event) { + if ($before_date && $event->getDateEnded() > $before_date) { + $event->setDateEnded($before_date); + } + if ($after_date && $event->getDateStarted() < $after_date) { + $event->setDateStarted($after_date); + } + } + } + /* + * array_merge( + array_mergev(mpull($all_tasks, 'getProjectPHIDs')), + mpull($all_revisions, 'getArcanistProjectPHID'));*/ + $all_users = mpull($events, 'getUserPHID'); + $all_objects = phid_group_by_type(mpull($events, 'getObjectPHID')); + + $all_tasks = id(new ManiphestTaskQuery()) + ->setViewer($user) + ->withPHIDS((array)array_unique($all_objects['TASK'])) + ->execute(); + $all_revisions = id(new DifferentialRevisionQuery()) + ->setViewer($user) + ->withPHIDS((array)array_unique($all_objects['DREV'])) + ->execute(); + $all_repositories = id(new PhabricatorRepositoryQuery()) + ->setViewer($user) + ->withPHIDs(array_unique(mpull($all_revisions, 'getRepositoryPHID'))) + ->needProjectPHIDs(true) + ->execute(); + $all_projects = array_merge( + array_mergev(mpull($all_tasks, 'getProjectPHIDs')), + array_mergev(mpull($all_repositories, 'getProjectPHIDs'))); + $all_users = id(new PhabricatorPeopleQuery()) + ->setViewer($user) + ->withPHIDS(array_unique($all_users)) + ->execute(); + $all_projects = id(new PhabricatorHandleQuery()) + ->setViewer($user) + ->withPHIDs(array_unique($all_projects)) + ->execute(); + + $handles = array_merge( + mpull($all_tasks, null, 'getPHID'), + mpull($all_revisions, null, 'getPHID'), + mpull($all_users, null, 'getPHID'), + mpull($all_projects, null, 'getPHID'), + mpull($all_repositories, null, 'getPHID')); + $workbook = new PHPExcel(); + $format->buildWorkbook($workbook, $events, $handles, $user); + $writer = PHPExcel_IOFactory::createWriter($workbook, 'Excel2007'); + + ob_start(); + $writer->save('php://output'); + $data = ob_get_clean(); + + $mime = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'; + + return id(new AphrontFileResponse()) + ->setMimeType($mime) + ->setDownload($format->getFileName().'.xlsx') + ->setContent($data); + } +} diff --git a/src/applications/phrequent/controller/PhrequentListController.php b/src/applications/phrequent/controller/PhrequentListController.php --- a/src/applications/phrequent/controller/PhrequentListController.php +++ b/src/applications/phrequent/controller/PhrequentListController.php @@ -21,5 +21,4 @@ return $this->delegateToController($controller); } - } diff --git a/src/applications/phrequent/export/PhrequentExcelDefaultFormat.php b/src/applications/phrequent/export/PhrequentExcelDefaultFormat.php new file mode 100644 --- /dev/null +++ b/src/applications/phrequent/export/PhrequentExcelDefaultFormat.php @@ -0,0 +1,142 @@ +setActiveSheetIndex(0); + $sheet->setTitle(pht('User Time')); + + $widths = array( + 20, + 30, + 40, + 15, + 15, + 10, + ); + + foreach ($widths as $col => $width) { + if ($width !== null) { + $sheet->getColumnDimension($this->col($col))->setWidth($width); + } + } + + $status_map = ManiphestTaskStatus::getTaskStatusMap(); + $pri_map = ManiphestTaskPriority::getTaskPriorityMap(); + + $date_format = null; + + $rows = array(); + $rows[] = array( + pht('User'), + pht('Object'), + pht('Projects'), + pht('Date Started'), + pht('Date Ended'), + pht('Hours') + ); + + $column_formats = array( + PHPExcel_Style_NumberFormat::FORMAT_TEXT, + PHPExcel_Style_NumberFormat::FORMAT_TEXT, + PHPExcel_Style_NumberFormat::FORMAT_TEXT, + PHPExcel_Style_NumberFormat::FORMAT_DATE_YYYYMMDD2, + PHPExcel_Style_NumberFormat::FORMAT_DATE_YYYYMMDD2, + '[h]:mm:ss', + ); + + $header_format = array( + 'font' => array( + 'bold' => true, + ), + ); + + foreach ($events as $event) { + $event_user = null; + if ($event->getUserPHID()) { + $event_user = $handles[$event->getUserPHID()]->getRealName(); + } + + $event_object = null; + $event_projects = array(); + if ($event->getObjectPHID()) { + switch (phid_get_type($event->getObjectPHID())) { + case 'TASK': + $event_task = $handles[$event->getObjectPHID()]; + foreach ($event_task->getProjectPHIDs() as $phid) { + $event_projects[] = $handles[$phid]->getName(); + } + $event_projects = implode(', ', $event_projects); + $event_object = $event_task->getTitle(); + break; + case 'DREV': + $event_revision = $handles[$event->getObjectPHID()]; + $event_repository = $handles[$event_revision->getRepositoryPHID()]; + foreach ($event_repository->getProjectPHIDs() as $phid) { + $event_projects[] = $handles[$phid]->getName(); + } + $event_projects = implode(', ', $event_projects); + $event_object = $event_revision->getTitle(); + break; + default: + $event_object = $handles[$event->getObjectPHID()]->getName(); + $event_projects = pht('Unknown'); + } + } + + $rows[] = array( + $event_user, + $event_object, + $event_projects, + $this->computeExcelDate($event->getDateStarted()), + $this->computeExcelDate($event->getDateEnded()), + ($event->getDateEnded() - $event->getDateStarted()) / 86400, + ); + } + + foreach ($rows as $row => $cols) { + foreach ($cols as $col => $spec) { + $cell_name = $this->col($col).($row + 1); + $cell = $sheet + ->setCellValue($cell_name, $spec, $return_cell = true); + + if ($row == 0) { + $sheet->getStyle($cell_name)->applyFromArray($header_format); + } + + $sheet + ->getStyle($cell_name) + ->getNumberFormat() + ->setFormatCode($column_formats[$col]); + } + } + } + + + private function col($n) { + return chr(ord('A') + $n); + } + +} diff --git a/src/applications/phrequent/export/PhrequentExcelFormat.php b/src/applications/phrequent/export/PhrequentExcelFormat.php new file mode 100644 --- /dev/null +++ b/src/applications/phrequent/export/PhrequentExcelFormat.php @@ -0,0 +1,47 @@ +setAncestorClass(__CLASS__) + ->setConcreteOnly(true) + ->selectAndLoadSymbols(); + + $objects = array(); + foreach ($classes as $class) { + $objects[$class['name']] = newv($class['name'], array()); + } + + $objects = msort($objects, 'getOrder'); + + return $objects; + } + + public abstract function getName(); + public abstract function getFileName(); + + public function getOrder() { + return 0; + } + + protected function computeExcelDate($epoch) { + $seconds_per_day = (60 * 60 * 24); + $offset = ($seconds_per_day * 25569); + + return ($epoch + $offset) / $seconds_per_day; + } + + /** + * @phutil-external-symbol class PHPExcel + */ + public abstract function buildWorkbook( + PHPExcel $workbook, + array $tasks, + array $handles, + PhabricatorUser $user); + +} diff --git a/src/applications/phrequent/query/PhrequentSearchEngine.php b/src/applications/phrequent/query/PhrequentSearchEngine.php --- a/src/applications/phrequent/query/PhrequentSearchEngine.php +++ b/src/applications/phrequent/query/PhrequentSearchEngine.php @@ -25,6 +25,22 @@ $saved->setParameter('order', $request->getStr('order')); + $saved->setParameter( + 'after', + id(new AphrontFormDateControl()) + ->setName('after') + ->setAllowNull(true) + ->setUser($request->getUser()) + ->readValueFromRequest($request)); + + $saved->setParameter( + 'before', + id(new AphrontFormDateControl()) + ->setName('before') + ->setAllowNull(true) + ->setUser($request->getUser()) + ->readValueFromRequest($request)); + return $saved; } @@ -46,6 +62,16 @@ $query->setOrder($order); } + $after = $saved->getParameter('after'); + if ($after != null) { + $query->afterDate($after); + } + + $before = $saved->getParameter('before'); + if ($before != null) { + $query->beforeDate($before); + } + return $query; } @@ -53,11 +79,15 @@ AphrontFormView $form, PhabricatorSavedQuery $saved_query) { + $user = $this->requireViewer(); + $user_phids = $saved_query->getParameter('userPHIDs', array()); $ended = $saved_query->getParameter( 'ended', PhrequentUserTimeQuery::ENDED_ALL); $order = $saved_query->getParameter( 'order', PhrequentUserTimeQuery::ORDER_ENDED_DESC); + $after = $saved_query->getParameter('after', null); + $before = $saved_query->getParameter('before', null); $phids = array_merge($user_phids); $handles = id(new PhabricatorHandleQuery()) @@ -83,7 +113,23 @@ ->setLabel(pht('Order')) ->setName('order') ->setValue($order) - ->setOptions(PhrequentUserTimeQuery::getOrderSearchOptions())); + ->setOptions(PhrequentUserTimeQuery::getOrderSearchOptions())) + ->appendChild( + id(new AphrontFormDateControl()) + ->setLabel(pht('After')) + ->setName('after') + ->setUser($user) + ->setValue($after) + ->setAllowNull(true) + ->setInitialTime(AphrontFormDateControl::TIME_START_OF_DAY)) + ->appendChild( + id(new AphrontFormDateControl()) + ->setLabel(pht('Before')) + ->setName('before') + ->setUser($user) + ->setValue($before) + ->setAllowNull(true) + ->setInitialTime(AphrontFormDateControl::TIME_END_OF_DAY)); } protected function getURI($path) { @@ -173,6 +219,17 @@ pht( 'Ended on %s', $time_ended)); + if ($usertime->getObjectPHID() !== null && + $usertime->getUserPHID() === $viewer->getPHID()) { + $item->addAction( + id(new PHUIListItemView()) + ->setIcon('fa-pencil') + ->setRenderNameAsTooltip(true) + ->setName(pht('Edit')) + ->setHref($this->getApplicationURI( + '/edit/'. + $usertime->getId().'/'))); + } } else { $item->addAttribute( pht( @@ -197,7 +254,37 @@ $view->addItem($item); } - return $view; + return phutil_tag( + 'div', + array( + 'class' => 'phrequent-list-container', + ), + array( + $view, + $this->renderListActions($query), + )); + } + + private function renderListActions(PhabricatorSavedQuery $saved_query) { + $user = $this->getRequest()->getUser(); + + $export = javelin_tag( + 'a', + array( + 'href' => '/phrequent/export/'.$saved_query->getQueryKey().'/', + 'class' => 'grey button', + ), + pht('Export to Excel')); + + require_celerity_resource('phrequent-css'); + + $actions = hsprintf( + '
    '. + '
  • %s
  • '. + '
', + $export); + + return $actions; } } diff --git a/src/applications/phrequent/query/PhrequentUserTimeQuery.php b/src/applications/phrequent/query/PhrequentUserTimeQuery.php --- a/src/applications/phrequent/query/PhrequentUserTimeQuery.php +++ b/src/applications/phrequent/query/PhrequentUserTimeQuery.php @@ -16,13 +16,21 @@ const ENDED_NO = 1; const ENDED_ALL = 2; + private $ids; private $userPHIDs; private $objectPHIDs; + private $afterDate; + private $beforeDate; private $order = self::ORDER_ID_ASC; private $ended = self::ENDED_ALL; private $needPreemptingEvents; + public function withIDs($ids) { + $this->ids = $ids; + return $this; + } + public function withUserPHIDs($user_phids) { $this->userPHIDs = $user_phids; return $this; @@ -33,6 +41,16 @@ return $this; } + public function afterDate($afterDate) { + $this->afterDate = $afterDate; + return $this; + } + + public function beforeDate($beforeDate) { + $this->beforeDate = $beforeDate; + return $this; + } + public function withEnded($ended) { $this->ended = $ended; return $this; @@ -51,6 +69,13 @@ private function buildWhereClause(AphrontDatabaseConnection $conn) { $where = array(); + if ($this->ids) { + $where[] = qsprintf( + $conn, + 'id IN (%Ld)', + $this->ids); + } + if ($this->userPHIDs) { $where[] = qsprintf( $conn, @@ -82,6 +107,20 @@ throw new Exception("Unknown ended '{$this->ended}'!"); } + if ($this->afterDate) { + $where[] = qsprintf( + $conn, + 'dateEnded > %d', + $this->afterDate); + } + + if ($this->beforeDate) { + $where[] = qsprintf( + $conn, + 'dateStarted < %d', + $this->beforeDate); + } + $where[] = $this->buildPagingClause($conn); return $this->formatWhereClause($where); diff --git a/src/applications/phrequent/storage/PhrequentUserTime.php b/src/applications/phrequent/storage/PhrequentUserTime.php --- a/src/applications/phrequent/storage/PhrequentUserTime.php +++ b/src/applications/phrequent/storage/PhrequentUserTime.php @@ -14,6 +14,7 @@ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, ); } diff --git a/webroot/rsrc/css/application/phrequent/phrequent.css b/webroot/rsrc/css/application/phrequent/phrequent.css --- a/webroot/rsrc/css/application/phrequent/phrequent.css +++ b/webroot/rsrc/css/application/phrequent/phrequent.css @@ -18,3 +18,11 @@ color: {$greytext}; background-image: url(/rsrc/image/phrequent_inactive.png); } + +.phrequent-list-actions { + border: 1px solid #e7e7e7; + border-bottom: 1px solid #A1A6B0; + background: #fff; + margin: 0 16px; + padding: 10px 8px; +}