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

Merge branch 'master' into redesign-2015

This commit is contained in:
epriestley 2015-06-19 08:33:18 -07:00
commit 7d7e13d79b
50 changed files with 727 additions and 313 deletions

View file

@ -8,7 +8,7 @@
return array( return array(
'names' => array( 'names' => array(
'core.pkg.css' => '9cbee819', 'core.pkg.css' => '9cbee819',
'core.pkg.js' => '41f5edc5', 'core.pkg.js' => 'f1e8abd7',
'darkconsole.pkg.js' => 'e7393ebb', 'darkconsole.pkg.js' => 'e7393ebb',
'differential.pkg.css' => 'fe951924', 'differential.pkg.css' => 'fe951924',
'differential.pkg.js' => 'ebef29b1', 'differential.pkg.js' => 'ebef29b1',
@ -459,7 +459,7 @@ return array(
'rsrc/js/core/behavior-object-selector.js' => '49b73b36', 'rsrc/js/core/behavior-object-selector.js' => '49b73b36',
'rsrc/js/core/behavior-oncopy.js' => '2926fff2', 'rsrc/js/core/behavior-oncopy.js' => '2926fff2',
'rsrc/js/core/behavior-phabricator-nav.js' => '56a1ca03', 'rsrc/js/core/behavior-phabricator-nav.js' => '56a1ca03',
'rsrc/js/core/behavior-phabricator-remarkup-assist.js' => '095ed313', 'rsrc/js/core/behavior-phabricator-remarkup-assist.js' => 'eeaa9e5a',
'rsrc/js/core/behavior-refresh-csrf.js' => '7814b593', 'rsrc/js/core/behavior-refresh-csrf.js' => '7814b593',
'rsrc/js/core/behavior-remarkup-preview.js' => 'f7379f45', 'rsrc/js/core/behavior-remarkup-preview.js' => 'f7379f45',
'rsrc/js/core/behavior-reorder-applications.js' => '76b9fc3e', 'rsrc/js/core/behavior-reorder-applications.js' => '76b9fc3e',
@ -609,7 +609,7 @@ return array(
'javelin-behavior-phabricator-notification-example' => '8ce821c5', 'javelin-behavior-phabricator-notification-example' => '8ce821c5',
'javelin-behavior-phabricator-object-selector' => '49b73b36', 'javelin-behavior-phabricator-object-selector' => '49b73b36',
'javelin-behavior-phabricator-oncopy' => '2926fff2', 'javelin-behavior-phabricator-oncopy' => '2926fff2',
'javelin-behavior-phabricator-remarkup-assist' => '095ed313', 'javelin-behavior-phabricator-remarkup-assist' => 'eeaa9e5a',
'javelin-behavior-phabricator-reveal-content' => '60821bc7', 'javelin-behavior-phabricator-reveal-content' => '60821bc7',
'javelin-behavior-phabricator-search-typeahead' => '048330fa', 'javelin-behavior-phabricator-search-typeahead' => '048330fa',
'javelin-behavior-phabricator-show-older-transactions' => 'dbbf48b6', 'javelin-behavior-phabricator-show-older-transactions' => 'dbbf48b6',
@ -896,15 +896,6 @@ return array(
'javelin-stratcom', 'javelin-stratcom',
'javelin-vector', 'javelin-vector',
), ),
'095ed313' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'phabricator-phtize',
'phabricator-textareautils',
'javelin-workflow',
'javelin-vector',
),
'0a3f3021' => array( '0a3f3021' => array(
'javelin-behavior', 'javelin-behavior',
'javelin-stratcom', 'javelin-stratcom',
@ -1951,6 +1942,15 @@ return array(
'phabricator-phtize', 'phabricator-phtize',
'javelin-dom', 'javelin-dom',
), ),
'eeaa9e5a' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'phabricator-phtize',
'phabricator-textareautils',
'javelin-workflow',
'javelin-vector',
),
'efe49472' => array( 'efe49472' => array(
'javelin-install', 'javelin-install',
'javelin-util', 'javelin-util',

View file

@ -0,0 +1,5 @@
ALTER TABLE {$NAMESPACE}_diviner.diviner_livebook
ADD COLUMN repositoryPHID VARBINARY(64) AFTER name;
ALTER TABLE {$NAMESPACE}_diviner.diviner_livesymbol
ADD COLUMN repositoryPHID VARBINARY(64) AFTER bookPHID;

View file

@ -3288,7 +3288,6 @@ phutil_register_library_map(array(
'phabricator_format_local_time' => 'view/viewutils.php', 'phabricator_format_local_time' => 'view/viewutils.php',
'phabricator_relative_date' => 'view/viewutils.php', 'phabricator_relative_date' => 'view/viewutils.php',
'phabricator_time' => 'view/viewutils.php', 'phabricator_time' => 'view/viewutils.php',
'phabricator_time_format' => 'view/viewutils.php',
'phid_get_subtype' => 'applications/phid/utils.php', 'phid_get_subtype' => 'applications/phid/utils.php',
'phid_get_type' => 'applications/phid/utils.php', 'phid_get_type' => 'applications/phid/utils.php',
'phid_group_by_type' => 'applications/phid/utils.php', 'phid_group_by_type' => 'applications/phid/utils.php',

View file

@ -35,8 +35,10 @@ abstract class AphrontController extends Phobject {
throw new PhutilMethodNotImplementedException( throw new PhutilMethodNotImplementedException(
pht( pht(
'Controllers must implement either handleRequest() (recommended) '. 'Controllers must implement either %s (recommended) '.
'or processRequest() (deprecated).')); 'or %s (deprecated).',
'handleRequest()',
'processRequest()'));
} }
final public function setRequest(AphrontRequest $request) { final public function setRequest(AphrontRequest $request) {
@ -46,7 +48,7 @@ abstract class AphrontController extends Phobject {
final public function getRequest() { final public function getRequest() {
if (!$this->request) { if (!$this->request) {
throw new Exception(pht('Call setRequest() before getRequest()!')); throw new PhutilInvalidStateException('setRequest');
} }
return $this->request; return $this->request;
} }
@ -81,10 +83,12 @@ abstract class AphrontController extends Phobject {
} }
public function getDefaultResourceSource() { public function getDefaultResourceSource() {
throw new Exception( throw new PhutilMethodNotImplementedException(
pht( pht(
'A Controller must implement getDefaultResourceSource() before you '. 'A Controller must implement %s before you can invoke %s or %s.',
'can invoke requireResource() or initBehavior().')); 'getDefaultResourceSource()',
'requireResource()',
'initBehavior()'));
} }
public function requireResource($symbol) { public function requireResource($symbol) {

View file

@ -18,7 +18,7 @@ final class PhabricatorAuthInviteEngine extends Phobject {
public function getViewer() { public function getViewer() {
if (!$this->viewer) { if (!$this->viewer) {
throw new Exception(pht('Call setViewer() before getViewer()!')); throw new PhutilInvalidStateException('setViewer');
} }
return $this->viewer; return $this->viewer;
} }

View file

@ -437,10 +437,11 @@ final class PhabricatorCalendarEventEditController
->setValue($end_disabled); ->setValue($end_disabled);
} }
$description = id(new AphrontFormTextAreaControl()) $description = id(new PhabricatorRemarkupControl())
->setLabel(pht('Description')) ->setLabel(pht('Description'))
->setName('description') ->setName('description')
->setValue($description); ->setValue($description)
->setUser($viewer);
$view_policies = id(new AphrontFormPolicyControl()) $view_policies = id(new AphrontFormPolicyControl())
->setUser($viewer) ->setUser($viewer)

View file

@ -362,10 +362,19 @@ final class PhabricatorCalendarEventViewController
pht('Icon'), pht('Icon'),
$icon_display); $icon_display);
$properties->addSectionHeader( if (strlen($event->getDescription())) {
pht('Description'),
PHUIPropertyListView::ICON_SUMMARY); $description = PhabricatorMarkupEngine::renderOneObject(
$properties->addTextContent($event->getDescription()); id(new PhabricatorMarkupOneOff())->setContent($event->getDescription()),
'default',
$viewer);
$properties->addSectionHeader(
pht('Description'),
PHUIPropertyListView::ICON_SUMMARY);
$properties->addTextContent($description);
}
return $properties; return $properties;
} }

View file

@ -126,15 +126,15 @@ final class PhabricatorCalendarEventSearchEngine
$query->withDateRange($min_range, $max_range); $query->withDateRange($min_range, $max_range);
} }
$invited_phids = $saved->getParameter('invitedPHIDs'); $invited_phids = $saved->getParameter('invitedPHIDs', array());
$invited_phids = $user_datasource->evaluateTokens($invited_phids);
if ($invited_phids) { if ($invited_phids) {
$invited_phids = $user_datasource->evaluateTokens($invited_phids);
$query->withInvitedPHIDs($invited_phids); $query->withInvitedPHIDs($invited_phids);
} }
$creator_phids = $saved->getParameter('creatorPHIDs'); $creator_phids = $saved->getParameter('creatorPHIDs', array());
$creator_phids = $user_datasource->evaluateTokens($creator_phids);
if ($creator_phids) { if ($creator_phids) {
$creator_phids = $user_datasource->evaluateTokens($creator_phids);
$query->withCreatorPHIDs($creator_phids); $query->withCreatorPHIDs($creator_phids);
} }
@ -313,17 +313,31 @@ final class PhabricatorCalendarEventSearchEngine
$list = new PHUIObjectItemListView(); $list = new PHUIObjectItemListView();
foreach ($events as $event) { foreach ($events as $event) {
$from = phabricator_datetime($event->getDateFrom(), $viewer); $from = phabricator_datetime($event->getDateFrom(), $viewer);
$to = phabricator_datetime($event->getDateTo(), $viewer); $duration = '';
$creator_handle = $handles[$event->getUserPHID()]; $creator_handle = $handles[$event->getUserPHID()];
$attendees = array();
foreach ($event->getInvitees() as $invitee) {
$attendees[] = $invitee->getInviteePHID();
}
$attendees = pht(
'Attending: %s',
$viewer->renderHandleList($attendees)
->setAsInline(1)
->render());
if (strlen($event->getDuration()) > 0) {
$duration = pht(
'Duration: %s',
$event->getDuration());
}
$item = id(new PHUIObjectItemView()) $item = id(new PHUIObjectItemView())
->setHeader($event->getName()) ->setHeader($viewer->renderHandle($event->getPHID())->render())
->setHref($event->getURI()) ->addAttribute($attendees)
->addByline(pht('Creator: %s', $creator_handle->renderLink())) ->addIcon('none', $from)
->addAttribute(pht('From %s to %s', $from, $to)) ->addIcon('none', $duration);
->addAttribute(id(new PhutilUTF8StringTruncator())
->setMaximumGlyphs(64)
->truncateString($event->getDescription()));
$list->addItem($item); $list->addItem($item);
} }

View file

@ -373,6 +373,29 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
return false; return false;
} }
public function getDuration() {
$seconds = $this->dateTo - $this->dateFrom;
$minutes = round($seconds / 60, 1);
$hours = round($minutes / 60, 3);
$days = round($hours / 24, 2);
$duration = '';
if ($days >= 1) {
return pht(
'%s day(s)',
round($days, 1));
} else if ($hours >= 1) {
return pht(
'%s hour(s)',
round($hours, 1));
} else if ($minutes >= 1) {
return pht(
'%s minute(s)',
round($minutes, 0));
}
}
/* -( Markup Interface )--------------------------------------------------- */ /* -( Markup Interface )--------------------------------------------------- */

View file

@ -30,7 +30,7 @@ abstract class ConduitAPIMethod
$query = $this->newQueryObject(); $query = $this->newQueryObject();
if ($query) { if ($query) {
$types['order'] = 'order'; $types['order'] = 'optional order';
$types += $this->getPagerParamTypes(); $types += $this->getPagerParamTypes();
} }

View file

@ -20,7 +20,6 @@ final class PhabricatorSyntaxHighlightingConfigOptions
} }
public function getOptions() { public function getOptions() {
$caches_href = PhabricatorEnv::getDocLink('Managing Caches'); $caches_href = PhabricatorEnv::getDocLink('Managing Caches');
return array( return array(
@ -74,31 +73,43 @@ final class PhabricatorSyntaxHighlightingConfigOptions
'c' => 'C', 'c' => 'C',
'coffee-script' => 'CoffeeScript', 'coffee-script' => 'CoffeeScript',
'cpp' => 'C++', 'cpp' => 'C++',
'csharp' => 'C#',
'css' => 'CSS', 'css' => 'CSS',
'd' => 'D', 'd' => 'D',
'diff' => 'Diff', 'diff' => 'Diff',
'django' => 'Django Templating', 'django' => 'Django Templating',
'docker' => 'Docker',
'erb' => 'Embedded Ruby/ERB', 'erb' => 'Embedded Ruby/ERB',
'erlang' => 'Erlang', 'erlang' => 'Erlang',
'go' => 'Golang', 'go' => 'Golang',
'groovy' => 'Groovy', 'groovy' => 'Groovy',
'haskell' => 'Haskell', 'haskell' => 'Haskell',
'html' => 'HTML', 'html' => 'HTML',
'http' => 'HTTP',
'invisible' => 'Invisible', 'invisible' => 'Invisible',
'java' => 'Java', 'java' => 'Java',
'js' => 'Javascript', 'js' => 'Javascript',
'json' => 'JSON', 'json' => 'JSON',
'make' => 'Makefile',
'mysql' => 'MySQL', 'mysql' => 'MySQL',
'nginx' => 'Nginx Configuration',
'objc' => 'Objective-C', 'objc' => 'Objective-C',
'perl' => 'Perl', 'perl' => 'Perl',
'php' => 'PHP', 'php' => 'PHP',
'postgresql' => 'PostgreSQL',
'pot' => 'Gettext Catalog',
'puppet' => 'Puppet', 'puppet' => 'Puppet',
'rest' => 'reStructuredText',
'text' => 'Plain Text',
'python' => 'Python', 'python' => 'Python',
'rainbow' => 'Rainbow', 'rainbow' => 'Rainbow',
'remarkup' => 'Remarkup', 'remarkup' => 'Remarkup',
'rest' => 'reStructuredText',
'robotframework' => 'RobotFramework',
'rst' => 'reStructuredText',
'ruby' => 'Ruby', 'ruby' => 'Ruby',
'sql' => 'SQL',
'tex' => 'LaTeX',
'text' => 'Plain Text',
'twig' => 'Twig',
'xml' => 'XML', 'xml' => 'XML',
'yaml' => 'YAML', 'yaml' => 'YAML',
)) ))

View file

@ -64,7 +64,7 @@ final class ConpherenceTransactionView extends AphrontView {
public function render() { public function render() {
$viewer = $this->getUser(); $viewer = $this->getUser();
if (!$viewer) { if (!$viewer) {
throw new Exception(pht('Call setUser() before render()!')); throw new PhutilInvalidStateException('setUser');
} }
require_celerity_resource('conpherence-transaction-css'); require_celerity_resource('conpherence-transaction-css');

View file

@ -127,6 +127,13 @@ final class DarkConsoleCore extends Phobject {
} }
return $data; return $data;
} else { } else {
// Truncate huge strings. Since the data doesn't really matter much,
// just truncate bytes to avoid PhutilUTF8StringTruncator overhead.
$length = strlen($data);
$max = 4096;
if ($length > $max) {
$data = substr($data, 0, $max).'...<'.$length.' bytes>...';
}
return phutil_utf8ize($data); return phutil_utf8ize($data);
} }
} }

View file

@ -283,9 +283,7 @@ final class PhabricatorDashboardPanelRenderingEngine extends Phobject {
*/ */
private function detectRenderingCycle(PhabricatorDashboardPanel $panel) { private function detectRenderingCycle(PhabricatorDashboardPanel $panel) {
if ($this->parentPanelPHIDs === null) { if ($this->parentPanelPHIDs === null) {
throw new Exception( throw new PhutilInvalidStateException('setParentPanelPHIDs');
pht(
'You must call setParentPanelPHIDs() before rendering panels.'));
} }
$max_depth = 4; $max_depth = 4;

View file

@ -20,6 +20,8 @@ final class DiffusionFileContentQueryConduitAPIMethod
'path' => 'required string', 'path' => 'required string',
'commit' => 'required string', 'commit' => 'required string',
'needsBlame' => 'optional bool', 'needsBlame' => 'optional bool',
'timeout' => 'optional int',
'byteLimit' => 'optional int',
); );
} }
@ -31,16 +33,30 @@ final class DiffusionFileContentQueryConduitAPIMethod
$file_query $file_query
->setViewer($request->getUser()) ->setViewer($request->getUser())
->setNeedsBlame($needs_blame); ->setNeedsBlame($needs_blame);
$timeout = $request->getValue('timeout');
if ($timeout) {
$file_query->setTimeout($timeout);
}
$byte_limit = $request->getValue('byteLimit');
if ($byte_limit) {
$file_query->setByteLimit($byte_limit);
}
$file_content = $file_query->loadFileContent(); $file_content = $file_query->loadFileContent();
if ($needs_blame) { if ($needs_blame) {
list($text_list, $rev_list, $blame_dict) = $file_query->getBlameData(); list($text_list, $rev_list, $blame_dict) = $file_query->getBlameData();
} else { } else {
$text_list = $rev_list = $blame_dict = array(); $text_list = $rev_list = $blame_dict = array();
} }
$file_content $file_content
->setBlameDict($blame_dict) ->setBlameDict($blame_dict)
->setRevList($rev_list) ->setRevList($rev_list)
->setTextList($text_list); ->setTextList($text_list);
return $file_content->toDictionary(); return $file_content->toDictionary();
} }

View file

@ -54,14 +54,27 @@ final class DiffusionBrowseFileController extends DiffusionBrowseController {
$needs_blame = ($show_blame && !$show_color) || $needs_blame = ($show_blame && !$show_color) ||
($show_blame && $request->isAjax()); ($show_blame && $request->isAjax());
$params = array(
'commit' => $drequest->getCommit(),
'path' => $drequest->getPath(),
'needsBlame' => $needs_blame,
);
$byte_limit = null;
if ($view !== 'raw') {
$byte_limit = PhabricatorFileStorageEngine::getChunkThreshold();
$time_limit = 10;
$params += array(
'timeout' => $time_limit,
'byteLimit' => $byte_limit,
);
}
$file_content = DiffusionFileContent::newFromConduit( $file_content = DiffusionFileContent::newFromConduit(
$this->callConduitWithDiffusionRequest( $this->callConduitWithDiffusionRequest(
'diffusion.filecontentquery', 'diffusion.filecontentquery',
array( $params));
'commit' => $drequest->getCommit(),
'path' => $drequest->getPath(),
'needsBlame' => $needs_blame,
)));
$data = $file_content->getCorpus(); $data = $file_content->getCorpus();
if ($view === 'raw') { if ($view === 'raw') {
@ -71,8 +84,13 @@ final class DiffusionBrowseFileController extends DiffusionBrowseController {
$this->loadLintMessages(); $this->loadLintMessages();
$this->coverage = $drequest->loadCoverage(); $this->coverage = $drequest->loadCoverage();
$binary_uri = null; if ($byte_limit && (strlen($data) == $byte_limit)) {
if (ArcanistDiffUtils::isHeuristicBinaryFile($data)) { $corpus = $this->buildErrorCorpus(
pht(
'This file is larger than %s byte(s), and too large to display '.
'in the web UI.',
$byte_limit));
} else if (ArcanistDiffUtils::isHeuristicBinaryFile($data)) {
$file = $this->loadFileForData($path, $data); $file = $this->loadFileForData($path, $data);
$file_uri = $file->getBestURI(); $file_uri = $file->getBestURI();
@ -80,7 +98,6 @@ final class DiffusionBrowseFileController extends DiffusionBrowseController {
$corpus = $this->buildImageCorpus($file_uri); $corpus = $this->buildImageCorpus($file_uri);
} else { } else {
$corpus = $this->buildBinaryCorpus($file_uri, $data); $corpus = $this->buildBinaryCorpus($file_uri, $data);
$binary_uri = $file_uri;
} }
} else { } else {
// Build the content of the file. // Build the content of the file.
@ -940,6 +957,21 @@ final class DiffusionBrowseFileController extends DiffusionBrowseController {
return $box; return $box;
} }
private function buildErrorCorpus($message) {
$text = id(new PHUIBoxView())
->addPadding(PHUI::PADDING_LARGE)
->appendChild($message);
$header = id(new PHUIHeaderView())
->setHeader(pht('Details'));
$box = id(new PHUIObjectBoxView())
->setHeader($header)
->appendChild($text);
return $box;
}
private function buildBeforeResponse($before) { private function buildBeforeResponse($before) {
$request = $this->getRequest(); $request = $this->getRequest();
$drequest = $this->getDiffusionRequest(); $drequest = $this->getDiffusionRequest();

View file

@ -11,6 +11,26 @@ abstract class DiffusionFileContentQuery extends DiffusionQuery {
private $needsBlame; private $needsBlame;
private $fileContent; private $fileContent;
private $viewer; private $viewer;
private $timeout;
private $byteLimit;
public function setTimeout($timeout) {
$this->timeout = $timeout;
return $this;
}
public function getTimeout() {
return $this->timeout;
}
public function setByteLimit($byte_limit) {
$this->byteLimit = $byte_limit;
return $this;
}
public function getByteLimit() {
return $this->byteLimit;
}
final public static function newFromDiffusionRequest( final public static function newFromDiffusionRequest(
DiffusionRequest $request) { DiffusionRequest $request) {
@ -21,7 +41,31 @@ abstract class DiffusionFileContentQuery extends DiffusionQuery {
abstract protected function executeQueryFromFuture(Future $future); abstract protected function executeQueryFromFuture(Future $future);
final public function loadFileContentFromFuture(Future $future) { final public function loadFileContentFromFuture(Future $future) {
$this->fileContent = $this->executeQueryFromFuture($future);
if ($this->timeout) {
$future->setTimeout($this->timeout);
}
if ($this->getByteLimit()) {
$future->setStdoutSizeLimit($this->getByteLimit());
}
try {
$file_content = $this->executeQueryFromFuture($future);
} catch (CommandException $ex) {
if (!$future->getWasKilledByTimeout()) {
throw $ex;
}
$message = pht(
'<Attempt to load this file was terminated after %s second(s).>',
$this->timeout);
$file_content = new DiffusionFileContent();
$file_content->setCorpus($message);
}
$this->fileContent = $file_content;
$repository = $this->getRequest()->getRepository(); $repository = $this->getRequest()->getRepository();
$try_encoding = $repository->getDetail('encoding'); $try_encoding = $repository->getDetail('encoding');

View file

@ -84,6 +84,7 @@ final class DivinerAtomController extends DivinerController {
if ($atom) { if ($atom) {
$this->buildDefined($properties, $symbol); $this->buildDefined($properties, $symbol);
$this->buildExtendsAndImplements($properties, $symbol); $this->buildExtendsAndImplements($properties, $symbol);
$this->buildRepository($properties, $symbol);
$warnings = $atom->getWarnings(); $warnings = $atom->getWarnings();
if ($warnings) { if ($warnings) {
@ -294,6 +295,15 @@ final class DivinerAtomController extends DivinerController {
} }
} }
private function buildRepository(
PHUIPropertyListView $view,
DivinerLiveSymbol $symbol) {
$view->addProperty(
pht('Repository'),
$this->getViewer()->renderHandle($symbol->getRepositoryPHID()));
}
private function renderAtomTag(DivinerLiveSymbol $symbol) { private function renderAtomTag(DivinerLiveSymbol $symbol) {
return id(new PHUITagView()) return id(new PHUITagView())
->setType(PHUITagView::TYPE_OBJECT) ->setType(PHUITagView::TYPE_OBJECT)

View file

@ -14,6 +14,7 @@ final class DivinerBookController extends DivinerController {
$book = id(new DivinerBookQuery()) $book = id(new DivinerBookQuery())
->setViewer($viewer) ->setViewer($viewer)
->withNames(array($book_name)) ->withNames(array($book_name))
->needRepositories(true)
->executeOne(); ->executeOne();
if (!$book) { if (!$book) {
@ -43,6 +44,15 @@ final class DivinerBookController extends DivinerController {
->setEpoch($book->getDateModified()) ->setEpoch($book->getDateModified())
->addActionLink($action_button); ->addActionLink($action_button);
// TODO: This could probably look better.
if ($book->getRepositoryPHID()) {
$header->addTag(
id(new PHUITagView())
->setType(PHUITagView::TYPE_STATE)
->setBackgroundColor(PHUITagView::COLOR_BLUE)
->setName($book->getRepository()->getMonogram()));
}
$document = new PHUIDocumentView(); $document = new PHUIDocumentView();
$document->setHeader($header); $document->setHeader($header);
$document->addClass('diviner-view'); $document->addClass('diviner-view');

View file

@ -75,6 +75,16 @@ final class DivinerBookEditController extends DivinerController {
->setName('projectPHIDs') ->setName('projectPHIDs')
->setLabel(pht('Projects')) ->setLabel(pht('Projects'))
->setValue($book->getProjectPHIDs())) ->setValue($book->getProjectPHIDs()))
->appendControl(
id(new AphrontFormTokenizerControl())
->setDatasource(new DiffusionRepositoryDatasource())
->setName('repositoryPHIDs')
->setLabel(pht('Repository'))
->setDisableBehavior(true)
->setLimit(1)
->setValue($book->getRepositoryPHID()
? array($book->getRepositoryPHID())
: null))
->appendChild( ->appendChild(
id(new AphrontFormPolicyControl()) id(new AphrontFormPolicyControl())
->setName('viewPolicy') ->setName('viewPolicy')

View file

@ -4,7 +4,7 @@ final class DivinerLivePublisher extends DivinerPublisher {
private $book; private $book;
private function loadBook() { protected function getBook() {
if (!$this->book) { if (!$this->book) {
$book_name = $this->getConfig('name'); $book_name = $this->getConfig('name');
@ -20,7 +20,24 @@ final class DivinerLivePublisher extends DivinerPublisher {
->save(); ->save();
} }
$book->setConfigurationData($this->getConfigurationData())->save(); $conn_w = $book->establishConnection('w');
$conn_w->openTransaction();
$book
->setRepositoryPHID($this->getRepositoryPHID())
->setConfigurationData($this->getConfigurationData())
->save();
// TODO: This is gross. Without this, the repository won't be updated for
// atoms which have already been published.
queryfx(
$conn_w,
'UPDATE %T SET repositoryPHID = %s WHERE bookPHID = %s',
id(new DivinerLiveSymbol())->getTableName(),
$this->getRepositoryPHID(),
$book->getPHID());
$conn_w->saveTransaction();
$this->book = $book; $this->book = $book;
id(new PhabricatorSearchIndexer()) id(new PhabricatorSearchIndexer())
@ -33,7 +50,7 @@ final class DivinerLivePublisher extends DivinerPublisher {
private function loadSymbolForAtom(DivinerAtom $atom) { private function loadSymbolForAtom(DivinerAtom $atom) {
$symbol = id(new DivinerAtomQuery()) $symbol = id(new DivinerAtomQuery())
->setViewer(PhabricatorUser::getOmnipotentUser()) ->setViewer(PhabricatorUser::getOmnipotentUser())
->withBookPHIDs(array($this->loadBook()->getPHID())) ->withBookPHIDs(array($atom->getBook()))
->withTypes(array($atom->getType())) ->withTypes(array($atom->getType()))
->withNames(array($atom->getName())) ->withNames(array($atom->getName()))
->withContexts(array($atom->getContext())) ->withContexts(array($atom->getContext()))
@ -45,7 +62,7 @@ final class DivinerLivePublisher extends DivinerPublisher {
} }
return id(new DivinerLiveSymbol()) return id(new DivinerLiveSymbol())
->setBookPHID($this->loadBook()->getPHID()) ->setBookPHID($this->getBook()->getPHID())
->setType($atom->getType()) ->setType($atom->getType())
->setName($atom->getName()) ->setName($atom->getName())
->setContext($atom->getContext()) ->setContext($atom->getContext())
@ -68,7 +85,7 @@ final class DivinerLivePublisher extends DivinerPublisher {
protected function loadAllPublishedHashes() { protected function loadAllPublishedHashes() {
$symbols = id(new DivinerAtomQuery()) $symbols = id(new DivinerAtomQuery())
->setViewer(PhabricatorUser::getOmnipotentUser()) ->setViewer(PhabricatorUser::getOmnipotentUser())
->withBookPHIDs(array($this->loadBook()->getPHID())) ->withBookPHIDs(array($this->getBook()->getPHID()))
->withGhosts(false) ->withGhosts(false)
->execute(); ->execute();
@ -113,6 +130,7 @@ final class DivinerLivePublisher extends DivinerPublisher {
$is_documentable = $this->shouldGenerateDocumentForAtom($atom); $is_documentable = $this->shouldGenerateDocumentForAtom($atom);
$symbol $symbol
->setRepositoryPHID($this->getRepositoryPHID())
->setGraphHash($hash) ->setGraphHash($hash)
->setIsDocumentable((int)$is_documentable) ->setIsDocumentable((int)$is_documentable)
->setTitle($ref->getTitle()) ->setTitle($ref->getTitle())

View file

@ -9,6 +9,7 @@ abstract class DivinerPublisher extends Phobject {
private $config; private $config;
private $symbolReverseMap; private $symbolReverseMap;
private $dropCaches; private $dropCaches;
private $repositoryPHID;
final public function setDropCaches($drop_caches) { final public function setDropCaches($drop_caches) {
$this->dropCaches = $drop_caches; $this->dropCaches = $drop_caches;
@ -163,4 +164,13 @@ abstract class DivinerPublisher extends Phobject {
return true; return true;
} }
final public function getRepositoryPHID() {
return $this->repositoryPHID;
}
final public function setRepositoryPHID($repository_phid) {
$this->repositoryPHID = $repository_phid;
return $this;
}
} }

View file

@ -14,10 +14,12 @@ final class DivinerAtomQuery extends PhabricatorCursorPagedPolicyAwareQuery {
private $nodeHashes; private $nodeHashes;
private $titles; private $titles;
private $nameContains; private $nameContains;
private $repositoryPHIDs;
private $needAtoms; private $needAtoms;
private $needExtends; private $needExtends;
private $needChildren; private $needChildren;
private $needRepositories;
public function withIDs(array $ids) { public function withIDs(array $ids) {
$this->ids = $ids; $this->ids = $ids;
@ -109,6 +111,16 @@ final class DivinerAtomQuery extends PhabricatorCursorPagedPolicyAwareQuery {
return $this; return $this;
} }
public function withRepositoryPHIDs(array $repository_phids) {
$this->repositoryPHIDs = $repository_phids;
return $this;
}
public function needRepositories($need_repositories) {
$this->needRepositories = $need_repositories;
return $this;
}
protected function loadPage() { protected function loadPage() {
$table = new DivinerLiveSymbol(); $table = new DivinerLiveSymbol();
$conn_r = $table->establishConnection('r'); $conn_r = $table->establishConnection('r');
@ -125,6 +137,8 @@ final class DivinerAtomQuery extends PhabricatorCursorPagedPolicyAwareQuery {
} }
protected function willFilterPage(array $atoms) { protected function willFilterPage(array $atoms) {
assert_instances_of($atoms, 'DivinerLiveSymbol');
$books = array_unique(mpull($atoms, 'getBookPHID')); $books = array_unique(mpull($atoms, 'getBookPHID'));
$books = id(new DivinerBookQuery()) $books = id(new DivinerBookQuery())
@ -257,6 +271,31 @@ final class DivinerAtomQuery extends PhabricatorCursorPagedPolicyAwareQuery {
$this->attachAllChildren($atoms, $children, $this->needExtends); $this->attachAllChildren($atoms, $children, $this->needExtends);
} }
if ($this->needRepositories) {
$repositories = id(new PhabricatorRepositoryQuery())
->setViewer($this->getViewer())
->withPHIDs(mpull($atoms, 'getRepositoryPHID'))
->execute();
$repositories = mpull($repositories, null, 'getPHID');
foreach ($atoms as $key => $atom) {
if ($atom->getRepositoryPHID() === null) {
$atom->attachRepository(null);
continue;
}
$repository = idx($repositories, $atom->getRepositoryPHID());
if (!$repository) {
$this->didRejectResult($atom);
unset($atom[$key]);
continue;
}
$atom->attachRepository($repository);
}
}
return $atoms; return $atoms;
} }
@ -381,6 +420,13 @@ final class DivinerAtomQuery extends PhabricatorCursorPagedPolicyAwareQuery {
$this->nameContains); $this->nameContains);
} }
if ($this->repositoryPHIDs) {
$where[] = qsprintf(
$conn_r,
'repositoryPHID IN (%Ls)',
$this->repositoryPHIDs);
}
$where[] = $this->buildPagingClause($conn_r); $where[] = $this->buildPagingClause($conn_r);
return $this->formatWhereClause($where); return $this->formatWhereClause($where);

View file

@ -13,21 +13,23 @@ final class DivinerAtomSearchEngine extends PhabricatorApplicationSearchEngine {
public function buildSavedQueryFromRequest(AphrontRequest $request) { public function buildSavedQueryFromRequest(AphrontRequest $request) {
$saved = new PhabricatorSavedQuery(); $saved = new PhabricatorSavedQuery();
$saved->setParameter(
'repositoryPHIDs',
$this->readPHIDsFromRequest($request, 'repositoryPHIDs'));
$saved->setParameter('name', $request->getStr('name'));
$saved->setParameter( $saved->setParameter(
'types', 'types',
$this->readListFromRequest($request, 'types')); $this->readListFromRequest($request, 'types'));
$saved->setParameter('name', $request->getStr('name'));
return $saved; return $saved;
} }
public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) {
$query = id(new DivinerAtomQuery()); $query = id(new DivinerAtomQuery());
$types = $saved->getParameter('types'); $repository_phids = $saved->getParameter('repositoryPHIDs');
if ($types) { if ($repository_phids) {
$query->withTypes($types); $query->withRepositoryPHIDs($repository_phids);
} }
$name = $saved->getParameter('name'); $name = $saved->getParameter('name');
@ -35,6 +37,11 @@ final class DivinerAtomSearchEngine extends PhabricatorApplicationSearchEngine {
$query->withNameContains($name); $query->withNameContains($name);
} }
$types = $saved->getParameter('types');
if ($types) {
$query->withTypes($types);
}
return $query; return $query;
} }
@ -42,6 +49,12 @@ final class DivinerAtomSearchEngine extends PhabricatorApplicationSearchEngine {
AphrontFormView $form, AphrontFormView $form,
PhabricatorSavedQuery $saved) { PhabricatorSavedQuery $saved) {
$form->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Name Contains'))
->setName('name')
->setValue($saved->getParameter('name')));
$all_types = array(); $all_types = array();
foreach (DivinerAtom::getAllTypes() as $type) { foreach (DivinerAtom::getAllTypes() as $type) {
$all_types[$type] = DivinerAtom::getAtomTypeNameString($type); $all_types[$type] = DivinerAtom::getAtomTypeNameString($type);
@ -59,14 +72,14 @@ final class DivinerAtomSearchEngine extends PhabricatorApplicationSearchEngine {
$name, $name,
isset($types[$type])); isset($types[$type]));
} }
$form->appendChild($type_control);
$form $form->appendControl(
->appendChild( id(new AphrontFormTokenizerControl())
id(new AphrontFormTextControl()) ->setLabel(pht('Repositories'))
->setLabel(pht('Name Contains')) ->setName('repositoryPHIDs')
->setName('name') ->setDatasource(new DiffusionRepositoryDatasource())
->setValue($saved->getParameter('name'))) ->setValue($saved->getParameter('repositoryPHIDs')));
->appendChild($type_control);
} }
protected function getURI($path) { protected function getURI($path) {

View file

@ -5,8 +5,10 @@ final class DivinerBookQuery extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids; private $ids;
private $phids; private $phids;
private $names; private $names;
private $repositoryPHIDs;
private $needProjectPHIDs; private $needProjectPHIDs;
private $needRepositories;
public function withIDs(array $ids) { public function withIDs(array $ids) {
$this->ids = $ids; $this->ids = $ids;
@ -23,11 +25,21 @@ final class DivinerBookQuery extends PhabricatorCursorPagedPolicyAwareQuery {
return $this; return $this;
} }
public function withRepositoryPHIDs(array $repository_phids) {
$this->repositoryPHIDs = $repository_phids;
return $this;
}
public function needProjectPHIDs($need_phids) { public function needProjectPHIDs($need_phids) {
$this->needProjectPHIDs = $need_phids; $this->needProjectPHIDs = $need_phids;
return $this; return $this;
} }
public function needRepositories($need_repositories) {
$this->needRepositories = $need_repositories;
return $this;
}
protected function loadPage() { protected function loadPage() {
$table = new DivinerLiveBook(); $table = new DivinerLiveBook();
$conn_r = $table->establishConnection('r'); $conn_r = $table->establishConnection('r');
@ -46,6 +58,31 @@ final class DivinerBookQuery extends PhabricatorCursorPagedPolicyAwareQuery {
protected function didFilterPage(array $books) { protected function didFilterPage(array $books) {
assert_instances_of($books, 'DivinerLiveBook'); assert_instances_of($books, 'DivinerLiveBook');
if ($this->needRepositories) {
$repositories = id(new PhabricatorRepositoryQuery())
->setViewer($this->getViewer())
->withPHIDs(mpull($books, 'getRepositoryPHID'))
->execute();
$repositories = mpull($repositories, null, 'getPHID');
foreach ($books as $key => $book) {
if ($book->getRepositoryPHID() === null) {
$book->attachRepository(null);
continue;
}
$repository = idx($repositories, $book->getRepositoryPHID());
if (!$repository) {
$this->didRejectResult($book);
unset($books[$key]);
continue;
}
$book->attachRepository($repository);
}
}
if ($this->needProjectPHIDs) { if ($this->needProjectPHIDs) {
$edge_query = id(new PhabricatorEdgeQuery()) $edge_query = id(new PhabricatorEdgeQuery())
->withSourcePHIDs(mpull($books, 'getPHID')) ->withSourcePHIDs(mpull($books, 'getPHID'))
@ -91,6 +128,13 @@ final class DivinerBookQuery extends PhabricatorCursorPagedPolicyAwareQuery {
$this->names); $this->names);
} }
if ($this->repositoryPHIDs !== null) {
$where[] = qsprintf(
$conn_r,
'repositoryPHID IN (%Ls)',
$this->repositoryPHIDs);
}
$where[] = $this->buildPagingClause($conn_r); $where[] = $this->buildPagingClause($conn_r);
return $this->formatWhereClause($where); return $this->formatWhereClause($where);

View file

@ -29,6 +29,12 @@ final class DivinerAtomSearchIndexer extends PhabricatorSearchDocumentIndexer {
DivinerBookPHIDType::TYPECONST, DivinerBookPHIDType::TYPECONST,
PhabricatorTime::getNow()); PhabricatorTime::getNow());
$doc->addRelationship(
PhabricatorSearchRelationship::RELATIONSHIP_REPOSITORY,
$atom->getRepositoryPHID(),
PhabricatorRepositoryRepositoryPHIDType::TYPECONST,
PhabricatorTime::getNow());
$doc->addRelationship( $doc->addRelationship(
$atom->getGraphHash() $atom->getGraphHash()
? PhabricatorSearchRelationship::RELATIONSHIP_CLOSED ? PhabricatorSearchRelationship::RELATIONSHIP_CLOSED

View file

@ -18,6 +18,12 @@ final class DivinerBookSearchIndexer extends PhabricatorSearchDocumentIndexer {
PhabricatorSearchDocumentFieldType::FIELD_BODY, PhabricatorSearchDocumentFieldType::FIELD_BODY,
$book->getPreface()); $book->getPreface());
$doc->addRelationship(
PhabricatorSearchRelationship::RELATIONSHIP_REPOSITORY,
$book->getRepositoryPHID(),
PhabricatorRepositoryRepositoryPHIDType::TYPECONST,
$book->getDateCreated());
$this->indexTransactions( $this->indexTransactions(
$doc, $doc,
new DivinerLiveBookTransactionQuery(), new DivinerLiveBookTransactionQuery(),

View file

@ -8,11 +8,13 @@ final class DivinerLiveBook extends DivinerDAO
PhabricatorApplicationTransactionInterface { PhabricatorApplicationTransactionInterface {
protected $name; protected $name;
protected $repositoryPHID;
protected $viewPolicy; protected $viewPolicy;
protected $editPolicy; protected $editPolicy;
protected $configurationData = array(); protected $configurationData = array();
private $projectPHIDs = self::ATTACHABLE; private $projectPHIDs = self::ATTACHABLE;
private $repository = self::ATTACHABLE;
protected function getConfiguration() { protected function getConfiguration() {
return array( return array(
@ -22,6 +24,7 @@ final class DivinerLiveBook extends DivinerDAO
), ),
self::CONFIG_COLUMN_SCHEMA => array( self::CONFIG_COLUMN_SCHEMA => array(
'name' => 'text64', 'name' => 'text64',
'repositoryPHID' => 'phid?',
), ),
self::CONFIG_KEY_SCHEMA => array( self::CONFIG_KEY_SCHEMA => array(
'key_phid' => null, 'key_phid' => null,
@ -68,6 +71,15 @@ final class DivinerLiveBook extends DivinerDAO
return idx($spec, 'name', $group); return idx($spec, 'name', $group);
} }
public function attachRepository(PhabricatorRepository $repository = null) {
$this->repository = $repository;
return $this;
}
public function getRepository() {
return $this->assertAttached($this->repository);
}
public function attachProjectPHIDs(array $project_phids) { public function attachProjectPHIDs(array $project_phids) {
$this->projectPHIDs = $project_phids; $this->projectPHIDs = $project_phids;
return $this; return $this;
@ -98,7 +110,7 @@ final class DivinerLiveBook extends DivinerDAO
} }
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return false; return false;
} }
public function describeAutomaticCapability($capability) { public function describeAutomaticCapability($capability) {

View file

@ -7,6 +7,7 @@ final class DivinerLiveSymbol extends DivinerDAO
PhabricatorDestructibleInterface { PhabricatorDestructibleInterface {
protected $bookPHID; protected $bookPHID;
protected $repositoryPHID;
protected $context; protected $context;
protected $type; protected $type;
protected $name; protected $name;
@ -22,6 +23,7 @@ final class DivinerLiveSymbol extends DivinerDAO
protected $isDocumentable = 0; protected $isDocumentable = 0;
private $book = self::ATTACHABLE; private $book = self::ATTACHABLE;
private $repository = self::ATTACHABLE;
private $atom = self::ATTACHABLE; private $atom = self::ATTACHABLE;
private $extends = self::ATTACHABLE; private $extends = self::ATTACHABLE;
private $children = self::ATTACHABLE; private $children = self::ATTACHABLE;
@ -43,6 +45,7 @@ final class DivinerLiveSymbol extends DivinerDAO
'summary' => 'text?', 'summary' => 'text?',
'isDocumentable' => 'bool', 'isDocumentable' => 'bool',
'nodeHash' => 'text64?', 'nodeHash' => 'text64?',
'repositoryPHID' => 'phid?',
), ),
self::CONFIG_KEY_SCHEMA => array( self::CONFIG_KEY_SCHEMA => array(
'key_phid' => null, 'key_phid' => null,
@ -94,6 +97,15 @@ final class DivinerLiveSymbol extends DivinerDAO
return $this; return $this;
} }
public function getRepository() {
return $this->assertAttached($this->repository);
}
public function attachRepository(PhabricatorRepository $repository = null) {
$this->repository = $repository;
return $this;
}
public function getAtom() { public function getAtom() {
return $this->assertAttached($this->atom); return $this->assertAttached($this->atom);
} }

View file

@ -25,6 +25,11 @@ final class DivinerGenerateWorkflow extends DivinerWorkflow {
'help' => pht('Specify a subclass of %s.', 'DivinerPublisher'), 'help' => pht('Specify a subclass of %s.', 'DivinerPublisher'),
'default' => 'DivinerLivePublisher', 'default' => 'DivinerLivePublisher',
), ),
array(
'name' => 'repository',
'param' => 'callsign',
'help' => pht('Repository that the documentation belongs to.'),
),
)); ));
} }
@ -187,6 +192,24 @@ final class DivinerGenerateWorkflow extends DivinerWorkflow {
} }
$publisher = newv($publisher_class, array()); $publisher = newv($publisher_class, array());
$callsign = $args->getArg('repository');
$repository = null;
if ($callsign) {
$repository = id(new PhabricatorRepositoryQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withCallsigns(array($callsign))
->executeOne();
if (!$repository) {
throw new PhutilArgumentUsageException(
pht(
"Repository '%s' does not exist.",
$callsign));
}
$publisher->setRepositoryPHID($repository->getPHID());
}
$this->publishDocumentation($args->getArg('clean'), $publisher); $this->publishDocumentation($args->getArg('clean'), $publisher);
} }

View file

@ -77,20 +77,7 @@ final class DrydockPreallocatedHostBlueprintImplementation
$cmd = $lease->getInterface('command'); $cmd = $lease->getInterface('command');
if ($v_platform !== 'windows') { $cmd->execx('mkdir %s', $full_path);
$cmd->execx('mkdir %s', $full_path);
} else {
// Windows is terrible. The mkdir command doesn't even support putting
// the path in quotes. IN QUOTES. ARGUHRGHUGHHGG!! Do some terribly
// inaccurate sanity checking since we can't safely escape the path.
if (preg_match('/^[A-Z]\\:\\\\[a-zA-Z0-9\\\\\\ ]/', $full_path) === 0) {
throw new Exception(
pht(
'Unsafe path detected for Windows platform: "%s".',
$full_path));
}
$cmd->execx('mkdir %C', $full_path);
}
$lease->setAttribute('path', $full_path); $lease->setAttribute('path', $full_path);
} }

View file

@ -42,46 +42,8 @@ final class DrydockSSHCommandInterface extends DrydockCommandInterface {
$this->openCredentialsIfNotOpen(); $this->openCredentialsIfNotOpen();
$argv = func_get_args(); $argv = func_get_args();
$argv = $this->applyWorkingDirectoryToArgv($argv);
if ($this->getConfig('platform') === 'windows') { $full_command = call_user_func_array('csprintf', $argv);
// Handle Windows by executing the command under PowerShell.
$command = id(new PhutilCommandString($argv))
->setEscapingMode(PhutilCommandString::MODE_POWERSHELL);
$change_directory = '';
if ($this->getWorkingDirectory() !== null) {
$change_directory .= 'cd '.$this->getWorkingDirectory();
}
$script = <<<EOF
$change_directory
$command
if (\$LastExitCode -ne 0) {
exit \$LastExitCode
}
EOF;
// When Microsoft says "Unicode" they don't mean UTF-8.
$script = mb_convert_encoding($script, 'UTF-16LE');
$script = base64_encode($script);
$powershell =
'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe';
$powershell .=
' -ExecutionPolicy Bypass'.
' -NonInteractive'.
' -InputFormat Text'.
' -OutputFormat Text'.
' -EncodedCommand '.$script;
$full_command = $powershell;
} else {
// Handle UNIX by executing under the native shell.
$argv = $this->applyWorkingDirectoryToArgv($argv);
$full_command = call_user_func_array('csprintf', $argv);
}
$command_timeout = ''; $command_timeout = '';
if ($this->connectTimeout !== null) { if ($this->connectTimeout !== null) {

View file

@ -7,6 +7,7 @@ final class PhabricatorOwnersPackageQuery
private $phids; private $phids;
private $ownerPHIDs; private $ownerPHIDs;
private $repositoryPHIDs; private $repositoryPHIDs;
private $namePrefix;
/** /**
* Owners are direct owners, and members of owning projects. * Owners are direct owners, and members of owning projects.
@ -31,62 +32,59 @@ final class PhabricatorOwnersPackageQuery
return $this; return $this;
} }
protected function loadPage() { public function withNamePrefix($prefix) {
$table = new PhabricatorOwnersPackage(); $this->namePrefix = $prefix;
$conn_r = $table->establishConnection('r'); return $this;
$data = queryfx_all(
$conn_r,
'SELECT p.* FROM %T p %Q %Q %Q %Q',
$table->getTableName(),
$this->buildJoinClause($conn_r),
$this->buildWhereClause($conn_r),
$this->buildOrderClause($conn_r),
$this->buildLimitClause($conn_r));
return $table->loadAllFromArray($data);
} }
protected function buildJoinClause(AphrontDatabaseConnection $conn_r) { public function newResultObject() {
$joins = array(); return new PhabricatorOwnersPackage();
}
protected function loadPage() {
return $this->loadStandardPage(new PhabricatorOwnersPackage());
}
protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) {
$joins = parent::buildJoinClauseParts($conn);
if ($this->ownerPHIDs !== null) { if ($this->ownerPHIDs !== null) {
$joins[] = qsprintf( $joins[] = qsprintf(
$conn_r, $conn,
'JOIN %T o ON o.packageID = p.id', 'JOIN %T o ON o.packageID = p.id',
id(new PhabricatorOwnersOwner())->getTableName()); id(new PhabricatorOwnersOwner())->getTableName());
} }
if ($this->repositoryPHIDs !== null) { if ($this->repositoryPHIDs !== null) {
$joins[] = qsprintf( $joins[] = qsprintf(
$conn_r, $conn,
'JOIN %T rpath ON rpath.packageID = p.id', 'JOIN %T rpath ON rpath.packageID = p.id',
id(new PhabricatorOwnersPath())->getTableName()); id(new PhabricatorOwnersPath())->getTableName());
} }
return implode(' ', $joins); return $joins;
} }
protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = array(); $where = parent::buildWhereClauseParts($conn);
if ($this->phids !== null) { if ($this->phids !== null) {
$where[] = qsprintf( $where[] = qsprintf(
$conn_r, $conn,
'p.phid IN (%Ls)', 'p.phid IN (%Ls)',
$this->phids); $this->phids);
} }
if ($this->ids !== null) { if ($this->ids !== null) {
$where[] = qsprintf( $where[] = qsprintf(
$conn_r, $conn,
'p.id IN (%Ld)', 'p.id IN (%Ld)',
$this->ids); $this->ids);
} }
if ($this->repositoryPHIDs !== null) { if ($this->repositoryPHIDs !== null) {
$where[] = qsprintf( $where[] = qsprintf(
$conn_r, $conn,
'rpath.repositoryPHID IN (%Ls)', 'rpath.repositoryPHID IN (%Ls)',
$this->repositoryPHIDs); $this->repositoryPHIDs);
} }
@ -94,26 +92,79 @@ final class PhabricatorOwnersPackageQuery
if ($this->ownerPHIDs !== null) { if ($this->ownerPHIDs !== null) {
$base_phids = $this->ownerPHIDs; $base_phids = $this->ownerPHIDs;
$query = new PhabricatorProjectQuery(); $projects = id(new PhabricatorProjectQuery())
$query->setViewer($this->getViewer()); ->setViewer($this->getViewer())
$query->withMemberPHIDs($base_phids); ->withMemberPHIDs($base_phids)
$projects = $query->execute(); ->execute();
$project_phids = mpull($projects, 'getPHID'); $project_phids = mpull($projects, 'getPHID');
$all_phids = array_merge($base_phids, $project_phids); $all_phids = array_merge($base_phids, $project_phids);
$where[] = qsprintf( $where[] = qsprintf(
$conn_r, $conn,
'o.userPHID IN (%Ls)', 'o.userPHID IN (%Ls)',
$all_phids); $all_phids);
} }
$where[] = $this->buildPagingClause($conn_r); if (strlen($this->namePrefix)) {
return $this->formatWhereClause($where); // NOTE: This is a hacky mess, but this column is currently case
// sensitive and unique.
$where[] = qsprintf(
$conn,
'LOWER(p.name) LIKE %>',
phutil_utf8_strtolower($this->namePrefix));
}
return $where;
}
protected function shouldGroupQueryResultRows() {
if ($this->repositoryPHIDs) {
return true;
}
if ($this->ownerPHIDs) {
return true;
}
return parent::shouldGroupQueryResultRows();
}
public function getBuiltinOrders() {
return array(
'name' => array(
'vector' => array('name'),
'name' => pht('Name'),
),
) + parent::getBuiltinOrders();
}
public function getOrderableColumns() {
return parent::getOrderableColumns() + array(
'name' => array(
'table' => $this->getPrimaryTableAlias(),
'column' => 'name',
'type' => 'string',
'unique' => true,
'reverse' => true,
),
);
}
protected function getPagingValueMap($cursor, array $keys) {
$package = $this->loadCursorObject($cursor);
return array(
'id' => $package->getID(),
'name' => $package->getName(),
);
} }
public function getQueryApplicationClass() { public function getQueryApplicationClass() {
return 'PhabricatorOwnersApplication'; return 'PhabricatorOwnersApplication';
} }
protected function getPrimaryTableAlias() {
return 'p';
}
} }

View file

@ -11,68 +11,39 @@ final class PhabricatorOwnersPackageSearchEngine
return 'PhabricatorOwnersApplication'; return 'PhabricatorOwnersApplication';
} }
public function buildSavedQueryFromRequest(AphrontRequest $request) { public function newQuery() {
$saved = new PhabricatorSavedQuery(); return new PhabricatorOwnersPackageQuery();
$saved->setParameter(
'ownerPHIDs',
$this->readUsersFromRequest(
$request,
'owners',
array(
PhabricatorProjectProjectPHIDType::TYPECONST,
)));
$saved->setParameter(
'repositoryPHIDs',
$this->readPHIDsFromRequest(
$request,
'repositories',
array(
PhabricatorRepositoryRepositoryPHIDType::TYPECONST,
)));
return $saved;
} }
public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { protected function buildCustomSearchFields() {
$query = id(new PhabricatorOwnersPackageQuery()); return array(
id(new PhabricatorSearchDatasourceField())
->setLabel(pht('Owners'))
->setKey('ownerPHIDs')
->setAliases(array('owner', 'owners'))
->setDatasource(new PhabricatorProjectOrUserDatasource()),
id(new PhabricatorSearchDatasourceField())
->setLabel(pht('Repositories'))
->setKey('repositoryPHIDs')
->setAliases(array('repository', 'repositories'))
->setDatasource(new DiffusionRepositoryDatasource()),
);
}
$owner_phids = $saved->getParameter('ownerPHIDs', array()); protected function buildQueryFromParameters(array $map) {
if ($owner_phids) { $query = $this->newQuery();
$query->withOwnerPHIDs($owner_phids);
if ($map['ownerPHIDs']) {
$query->withOwnerPHIDs($map['ownerPHIDs']);
} }
$repository_phids = $saved->getParameter('repositoryPHIDs', array()); if ($map['repositoryPHIDs']) {
if ($repository_phids) { $query->withRepositoryPHIDs($map['repositoryPHIDs']);
$query->withRepositoryPHIDs($repository_phids);
} }
return $query; return $query;
} }
public function buildSearchForm(
AphrontFormView $form,
PhabricatorSavedQuery $saved) {
$owner_phids = $saved->getParameter('ownerPHIDs', array());
$repository_phids = $saved->getParameter('repositoryPHIDs', array());
$form
->appendControl(
id(new AphrontFormTokenizerControl())
->setDatasource(new PhabricatorProjectOrUserDatasource())
->setName('owners')
->setLabel(pht('Owners'))
->setValue($owner_phids))
->appendControl(
id(new AphrontFormTokenizerControl())
->setDatasource(new DiffusionRepositoryDatasource())
->setName('repositories')
->setLabel(pht('Repositories'))
->setValue($repository_phids));
}
protected function getURI($path) { protected function getURI($path) {
return '/owners/'.$path; return '/owners/'.$path;
} }

View file

@ -3,11 +3,6 @@
final class PhabricatorOwnersPackageDatasource final class PhabricatorOwnersPackageDatasource
extends PhabricatorTypeaheadDatasource { extends PhabricatorTypeaheadDatasource {
public function isBrowsable() {
// TODO: Make this browsable.
return false;
}
public function getBrowseTitle() { public function getBrowseTitle() {
return pht('Browse Packages'); return pht('Browse Packages');
} }
@ -26,10 +21,11 @@ final class PhabricatorOwnersPackageDatasource
$results = array(); $results = array();
$packages = id(new PhabricatorOwnersPackageQuery()) $query = id(new PhabricatorOwnersPackageQuery())
->setViewer($viewer) ->withNamePrefix($raw_query)
->execute(); ->setOrder('name');
$packages = $this->executeQuery($query);
foreach ($packages as $package) { foreach ($packages as $package) {
$results[] = id(new PhabricatorTypeaheadResult()) $results[] = id(new PhabricatorTypeaheadResult())
->setName($package->getName()) ->setName($package->getName())
@ -37,7 +33,7 @@ final class PhabricatorOwnersPackageDatasource
->setPHID($package->getPHID()); ->setPHID($package->getPHID());
} }
return $results; return $this->filterResultsAgainstTokens($results);
} }
} }

View file

@ -738,6 +738,41 @@ final class PhabricatorUser
return new DateTimeZone($this->getTimezoneIdentifier()); return new DateTimeZone($this->getTimezoneIdentifier());
} }
public function getPreference($key) {
$preferences = $this->loadPreferences();
// TODO: After T4103 and T7707 this should eventually be pushed down the
// stack into modular preference definitions and role profiles. This is
// just fixing T8601 and mildly anticipating those changes.
$value = $preferences->getPreference($key);
$allowed_values = null;
switch ($key) {
case PhabricatorUserPreferences::PREFERENCE_TIME_FORMAT:
$allowed_values = array(
'g:i A',
'H:i',
);
break;
case PhabricatorUserPreferences::PREFERENCE_DATE_FORMAT:
$allowed_values = array(
'Y-m-d',
'n/j/Y',
'd-m-Y',
);
break;
}
if ($allowed_values !== null) {
$allowed_values = array_fuse($allowed_values);
if (empty($allowed_values[$value])) {
$value = head($allowed_values);
}
}
return $value;
}
public function __toString() { public function __toString() {
return $this->getUsername(); return $this->getUsername();
} }

View file

@ -1913,7 +1913,25 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
PhabricatorDestructionEngine $engine) { PhabricatorDestructionEngine $engine) {
$this->openTransaction(); $this->openTransaction();
$this->delete();
$this->delete();
$books = id(new DivinerBookQuery())
->setViewer($engine->getViewer())
->withRepositoryPHIDs(array($this->getPHID()))
->execute();
foreach ($books as $book) {
$engine->destroyObject($book);
}
$atoms = id(new DivinerAtomQuery())
->setViewer($engine->getViewer())
->withRepositoryPHIDs(array($this->getPHID()))
->execute();
foreach ($atoms as $atom) {
$engine->destroyObject($atom);
}
$this->saveTransaction(); $this->saveTransaction();
} }

View file

@ -199,7 +199,8 @@ abstract class PhabricatorWorker extends Phobject {
} }
$tasks = id(new PhabricatorWorkerArchiveTaskQuery()) $tasks = id(new PhabricatorWorkerArchiveTaskQuery())
->withIDs($task_ids); ->withIDs($task_ids)
->execute();
foreach ($tasks as $task) { foreach ($tasks as $task) {
if ($task->getResult() != PhabricatorWorkerArchiveTask::RESULT_SUCCESS) { if ($task->getResult() != PhabricatorWorkerArchiveTask::RESULT_SUCCESS) {

View file

@ -198,10 +198,7 @@ final class PhabricatorMarkupEngine extends Phobject {
} }
if (!isset($this->objects[$key]['output'])) { if (!isset($this->objects[$key]['output'])) {
throw new Exception( throw new PhutilInvalidStateException('process');
pht(
'Call %s before using results.',
'process()'));
} }
} }

View file

@ -45,34 +45,57 @@ final class PhabricatorStorageManagementProbeWorkflow
} }
} }
$console->writeOut("%s\n", pht('APPROXIMATE TABLE SIZES'));
asort($totals); asort($totals);
$table = id(new PhutilConsoleTable())
->setShowHeader(false)
->setPadding(2)
->addColumn('name', array('title' => pht('Database / Table')))
->addColumn('size', array('title' => pht('Size')))
->addColumn('percentage', array('title' => pht('Percentage')));
foreach ($totals as $db => $size) { foreach ($totals as $db => $size) {
$database_size = $this->formatSize($totals[$db], $overall); list($database_size, $database_percentage) = $this->formatSize(
$console->writeOut( $totals[$db],
"**%s**\n", $overall);
sprintf('%-32.32s %18s', $db, $database_size));
$table->addRow(array(
'name' => phutil_console_format('**%s**', $db),
'size' => phutil_console_format('**%s**', $database_size),
'percentage' => phutil_console_format('**%s**', $database_percentage),
));
$data[$db] = isort($data[$db], '_totalSize'); $data[$db] = isort($data[$db], '_totalSize');
foreach ($data[$db] as $table => $info) { foreach ($data[$db] as $table_name => $info) {
$table_size = $this->formatSize($info['_totalSize'], $overall); list($table_size, $table_percentage) = $this->formatSize(
$console->writeOut( $info['_totalSize'],
"%s\n", $overall);
sprintf(' %-28.28s %18s', $table, $table_size));
$table->addRow(array(
'name' => ' '.$table_name,
'size' => $table_size,
'percentage' => $table_percentage,
));
} }
} }
$overall_size = $this->formatSize($overall, $overall);
$console->writeOut(
"**%s**\n",
sprintf('%-32.32s %18s', pht('TOTAL'), $overall_size));
list($overall_size, $overall_percentage) = $this->formatSize(
$overall,
$overall);
$table->addRow(array(
'name' => phutil_console_format('**%s**', pht('TOTAL')),
'size' => phutil_console_format('**%s**', $overall_size),
'percentage' => phutil_console_format('**%s**', $overall_percentage),
));
$table->draw();
return 0; return 0;
} }
private function formatSize($n, $o) { private function formatSize($n, $o) {
return sprintf( return array(
'%8.8s MB %5.5s%%', sprintf('%8.8s MB', number_format($n / (1024 * 1024), 1)),
number_format($n / (1024 * 1024), 1), sprintf('%3.1f%%', 100 * ($n / $o)),
sprintf('%3.1f', 100 * ($n / $o))); );
} }
} }

View file

@ -137,19 +137,13 @@ final class AphrontFormDateControl extends AphrontFormControl {
} }
private function getTimeFormat() { private function getTimeFormat() {
$viewer = $this->getUser(); return $this->getUser()
$preferences = $viewer->loadPreferences(); ->getPreference(PhabricatorUserPreferences::PREFERENCE_TIME_FORMAT);
$pref_time_format = PhabricatorUserPreferences::PREFERENCE_TIME_FORMAT;
return $preferences->getPreference($pref_time_format, 'g:i A');
} }
private function getDateFormat() { private function getDateFormat() {
$viewer = $this->getUser(); return $this->getUser()
$preferences = $viewer->loadPreferences(); ->getPreference(PhabricatorUserPreferences::PREFERENCE_DATE_FORMAT);
$pref_date_format = PhabricatorUserPreferences::PREFERENCE_DATE_FORMAT;
return $preferences->getPreference($pref_date_format, 'Y-m-d');
} }
private function getTimeInputValue() { private function getTimeInputValue() {

View file

@ -10,7 +10,6 @@ final class AphrontFormDateControlValue extends Phobject {
private $zone; private $zone;
private $optional; private $optional;
public function getValueDate() { public function getValueDate() {
return $this->valueDate; return $this->valueDate;
} }
@ -56,6 +55,10 @@ final class AphrontFormDateControlValue extends Phobject {
return $this->optional; return $this->optional;
} }
public function getViewer() {
return $this->viewer;
}
public static function newFromParts( public static function newFromParts(
PhabricatorUser $viewer, PhabricatorUser $viewer,
$year, $year,
@ -71,8 +74,7 @@ final class AphrontFormDateControlValue extends Phobject {
$year, $year,
$month, $month,
$day, $day,
coalesce($time, '12:00 AM'), coalesce($time, '12:00 AM'));
$value);
$value->valueEnabled = $enabled; $value->valueEnabled = $enabled;
return $value; return $value;
@ -85,8 +87,7 @@ final class AphrontFormDateControlValue extends Phobject {
list($value->valueDate, $value->valueTime) = list($value->valueDate, $value->valueTime) =
$value->getFormattedDateFromDate( $value->getFormattedDateFromDate(
$request->getStr($key.'_d'), $request->getStr($key.'_d'),
$request->getStr($key.'_t'), $request->getStr($key.'_t'));
$value);
$value->valueEnabled = $request->getStr($key.'_e'); $value->valueEnabled = $request->getStr($key.'_e');
return $value; return $value;
@ -108,8 +109,7 @@ final class AphrontFormDateControlValue extends Phobject {
$year, $year,
$month, $month,
$day, $day,
$time, $time);
$value);
return $value; return $value;
} }
@ -123,8 +123,7 @@ final class AphrontFormDateControlValue extends Phobject {
list($value->valueDate, $value->valueTime) = list($value->valueDate, $value->valueTime) =
$value->getFormattedDateFromDate( $value->getFormattedDateFromDate(
idx($dictionary, 'd'), idx($dictionary, 'd'),
idx($dictionary, 't'), idx($dictionary, 't'));
$value);
$value->valueEnabled = idx($dictionary, 'e'); $value->valueEnabled = idx($dictionary, 'e');
@ -205,29 +204,25 @@ final class AphrontFormDateControlValue extends Phobject {
} }
private function getTimeFormat() { private function getTimeFormat() {
$preferences = $this->viewer->loadPreferences(); return $this->getViewer()
$pref_time_format = PhabricatorUserPreferences::PREFERENCE_TIME_FORMAT; ->getPreference(PhabricatorUserPreferences::PREFERENCE_TIME_FORMAT);
return $preferences->getPreference($pref_time_format, 'g:i A');
} }
private function getDateFormat() { private function getDateFormat() {
$preferences = $this->viewer->loadPreferences(); return $this->getViewer()
$pref_date_format = PhabricatorUserPreferences::PREFERENCE_DATE_FORMAT; ->getPreference(PhabricatorUserPreferences::PREFERENCE_DATE_FORMAT);
return $preferences->getPreference($pref_date_format, 'Y-m-d');
} }
private function getFormattedDateFromDate($date, $time, $value) { private function getFormattedDateFromDate($date, $time) {
$original_input = $date; $original_input = $date;
$zone = $value->getTimezone(); $zone = $this->getTimezone();
$separator = $value->getFormatSeparator(); $separator = $this->getFormatSeparator();
$parts = preg_split('@[,./:-]@', $date); $parts = preg_split('@[,./:-]@', $date);
$date = implode($separator, $parts); $date = implode($separator, $parts);
$date = id(new DateTime($date, $zone)); $date = id(new DateTime($date, $zone));
if ($date) { if ($date) {
$date = $date->format($value->getDateFormat()); $date = $date->format($this->getDateFormat());
} else { } else {
$date = $original_input; $date = $original_input;
} }
@ -235,8 +230,8 @@ final class AphrontFormDateControlValue extends Phobject {
$date = id(new DateTime("{$date} {$time}", $zone)); $date = id(new DateTime("{$date} {$time}", $zone));
return array( return array(
$date->format($value->getDateFormat()), $date->format($this->getDateFormat()),
$date->format($value->getTimeFormat()), $date->format($this->getTimeFormat()),
); );
} }
@ -244,14 +239,14 @@ final class AphrontFormDateControlValue extends Phobject {
$year, $year,
$month, $month,
$day, $day,
$time, $time) {
$value) {
$zone = $value->getTimezone(); $zone = $this->getTimezone();
$date_time = id(new DateTime("{$year}-{$month}-{$day} {$time}", $zone)); $date_time = id(new DateTime("{$year}-{$month}-{$day} {$time}", $zone));
return array( return array(
$date_time->format($value->getDateFormat()), $date_time->format($this->getDateFormat()),
$date_time->format($value->getTimeFormat()), $date_time->format($this->getTimeFormat()),
); );
} }

View file

@ -162,10 +162,10 @@ final class AphrontFormPolicyControl extends AphrontFormControl {
protected function renderInput() { protected function renderInput() {
if (!$this->object) { if (!$this->object) {
throw new Exception(pht('Call setPolicyObject() before rendering!')); throw new PhutilInvalidStateException('setPolicyObject');
} }
if (!$this->capability) { if (!$this->capability) {
throw new Exception(pht('Call setCapability() before rendering!')); throw new PhutilInvalidStateException('setCapability');
} }
$policy = $this->object->getPolicy($this->capability); $policy = $this->object->getPolicy($this->capability);

View file

@ -109,8 +109,11 @@ final class AphrontFormTokenizerControl extends AphrontFormControl {
if (!$viewer) { if (!$viewer) {
throw new Exception( throw new Exception(
pht( pht(
'Call setUser() before rendering tokenizers. Use appendControl() '. 'Call %s before rendering tokenizers. '.
'on AphrontFormView to do this easily.')); 'Use %s on %s to do this easily.',
'setUser()',
'appendControl()',
'AphrontFormView'));
} }
$values = nonempty($this->getValue(), array()); $values = nonempty($this->getValue(), array());

View file

@ -187,10 +187,10 @@ final class AphrontSideNavFilterView extends AphrontView {
public function render() { public function render() {
if ($this->menu->getItems()) { if ($this->menu->getItems()) {
if (!$this->baseURI) { if (!$this->baseURI) {
throw new Exception(pht('Call setBaseURI() before render()!')); throw new PhutilInvalidStateException('setBaseURI');
} }
if ($this->selectedFilter === false) { if ($this->selectedFilter === false) {
throw new Exception(pht('Call selectFilter() before render()!')); throw new PhutilInvalidStateException('selectFilter');
} }
} }

View file

@ -29,7 +29,7 @@ final class PhabricatorActionListView extends AphrontView {
public function render() { public function render() {
if (!$this->user) { if (!$this->user) {
throw new Exception(pht('Call setUser() before render()!')); throw new PhutilInvalidStateException('setUser');
} }
$event = new PhabricatorEvent( $event = new PhabricatorEvent(

View file

@ -144,7 +144,7 @@ final class PHUITagView extends AphrontTagView {
protected function getTagContent() { protected function getTagContent() {
if (!$this->type) { if (!$this->type) {
throw new Exception(pht('You must call setType() before render()!')); throw new PhutilInvalidStateException('setType', 'render');
} }
$color = null; $color = null;

View file

@ -141,11 +141,8 @@ final class PHUICalendarListView extends AphrontTagView {
} }
private function getEventTooltip(AphrontCalendarEventView $event) { private function getEventTooltip(AphrontCalendarEventView $event) {
$viewer = $this->getUser(); $time_pref = $this->getUser()
$preferences = $viewer->loadPreferences(); ->getPreference(PhabricatorUserPreferences::PREFERENCE_TIME_FORMAT);
$time_pref = $preferences->getPreference(
PhabricatorUserPreferences::PREFERENCE_TIME_FORMAT,
'g:i A');
Javelin::initBehavior('phabricator-tooltips'); Javelin::initBehavior('phabricator-tooltips');

View file

@ -31,32 +31,21 @@ function phabricator_relative_date($epoch, $user, $on = false) {
} }
function phabricator_time($epoch, $user) { function phabricator_time($epoch, $user) {
$time_key = PhabricatorUserPreferences::PREFERENCE_TIME_FORMAT;
return phabricator_format_local_time( return phabricator_format_local_time(
$epoch, $epoch,
$user, $user,
phabricator_time_format($user)); $user->getPreference($time_key));
} }
function phabricator_datetime($epoch, $user) { function phabricator_datetime($epoch, $user) {
$time_key = PhabricatorUserPreferences::PREFERENCE_TIME_FORMAT;
return phabricator_format_local_time( return phabricator_format_local_time(
$epoch, $epoch,
$user, $user,
pht('%s, %s', pht('%s, %s',
phutil_date_format($epoch), phutil_date_format($epoch),
phabricator_time_format($user))); $user->getPreference($time_key)));
}
function phabricator_time_format($user) {
$prefs = $user->loadPreferences();
$pref = $prefs->getPreference(
PhabricatorUserPreferences::PREFERENCE_TIME_FORMAT);
if (strlen($pref)) {
return $pref;
}
return pht('g:i A');
} }
/** /**

View file

@ -99,8 +99,20 @@ JX.behavior('phabricator-remarkup-assist', function(config) {
} else { } else {
sel = [def]; sel = [def];
} }
sel = sel.join('\n' + ch);
return sel; if (ch === '>') {
for(var i=0; i < sel.length; i++) {
if (sel[i][0] === '>') {
ch = '>';
} else {
ch = '> ';
}
sel[i] = ch + sel[i];
}
return sel.join('\n');
}
return sel.join('\n' + ch);
} }
function assist(area, action, root) { function assist(area, action, root) {
@ -141,9 +153,9 @@ JX.behavior('phabricator-remarkup-assist', function(config) {
update(area, code_prefix + '```\n', sel, '\n```'); update(area, code_prefix + '```\n', sel, '\n```');
break; break;
case 'fa-quote-right': case 'fa-quote-right':
ch = '> '; ch = '>';
sel = prepend_char_to_lines(ch, sel, pht('Quoted Text')); sel = prepend_char_to_lines(ch, sel, pht('Quoted Text'));
update(area, ((r.start === 0) ? '' : '\n\n') + ch, sel, '\n\n'); update(area, ((r.start === 0) ? '' : '\n\n'), sel, '\n\n');
break; break;
case 'fa-table': case 'fa-table':
var table_prefix = (r.start === 0 ? '' : '\n\n'); var table_prefix = (r.start === 0 ? '' : '\n\n');