mirror of
https://we.phorge.it/source/phorge.git
synced 2025-01-20 19:51:08 +01:00
Prepare for hovercards
Summary: - Unify all the reference/embed Remarkup rules for Differential, Maniphest, Paste and Ponder. - Add rules for Pholio. - Does not yet unify Diffusion or Files (both are a bit more involved). - Prepare for hovercards. Test Plan: {F33894} Reviewers: chad, vrana Reviewed By: vrana CC: aran Differential Revision: https://secure.phabricator.com/D5120
This commit is contained in:
parent
7c8b32c6a8
commit
5e11eb7f72
15 changed files with 274 additions and 126 deletions
|
@ -1181,13 +1181,11 @@ phutil_register_library_map(array(
|
|||
'PhabricatorRedirectController' => 'applications/base/controller/PhabricatorRedirectController.php',
|
||||
'PhabricatorRefreshCSRFController' => 'applications/auth/controller/PhabricatorRefreshCSRFController.php',
|
||||
'PhabricatorRemarkupControl' => 'view/form/control/PhabricatorRemarkupControl.php',
|
||||
'PhabricatorRemarkupRuleDifferentialHandle' => 'applications/differential/remarkup/PhabricatorRemarkupRuleDifferentialHandle.php',
|
||||
'PhabricatorRemarkupRuleEmbedFile' => 'applications/files/remarkup/PhabricatorRemarkupRuleEmbedFile.php',
|
||||
'PhabricatorRemarkupRuleImageMacro' => 'applications/macro/remarkup/PhabricatorRemarkupRuleImageMacro.php',
|
||||
'PhabricatorRemarkupRuleManiphestHandle' => 'applications/maniphest/remarkup/PhabricatorRemarkupRuleManiphestHandle.php',
|
||||
'PhabricatorRemarkupRuleMeme' => 'applications/macro/remarkup/PhabricatorRemarkupRuleMeme.php',
|
||||
'PhabricatorRemarkupRuleMention' => 'applications/people/remarkup/PhabricatorRemarkupRuleMention.php',
|
||||
'PhabricatorRemarkupRuleObjectHandle' => 'infrastructure/markup/rule/PhabricatorRemarkupRuleObjectHandle.php',
|
||||
'PhabricatorRemarkupRuleObject' => 'infrastructure/markup/rule/PhabricatorRemarkupRuleObject.php',
|
||||
'PhabricatorRemarkupRuleObjectName' => 'infrastructure/markup/rule/PhabricatorRemarkupRuleObjectName.php',
|
||||
'PhabricatorRemarkupRuleYoutube' => 'infrastructure/markup/rule/PhabricatorRemarkupRuleYoutube.php',
|
||||
'PhabricatorRepository' => 'applications/repository/storage/PhabricatorRepository.php',
|
||||
|
@ -1462,6 +1460,7 @@ phutil_register_library_map(array(
|
|||
'PholioMockListController' => 'applications/pholio/controller/PholioMockListController.php',
|
||||
'PholioMockQuery' => 'applications/pholio/query/PholioMockQuery.php',
|
||||
'PholioMockViewController' => 'applications/pholio/controller/PholioMockViewController.php',
|
||||
'PholioRemarkupRule' => 'applications/pholio/remarkup/PholioRemarkupRule.php',
|
||||
'PholioReplyHandler' => 'applications/pholio/mail/PholioReplyHandler.php',
|
||||
'PholioSearchIndexer' => 'applications/pholio/search/PholioSearchIndexer.php',
|
||||
'PholioTransaction' => 'applications/pholio/storage/PholioTransaction.php',
|
||||
|
@ -2657,13 +2656,11 @@ phutil_register_library_map(array(
|
|||
'PhabricatorRedirectController' => 'PhabricatorController',
|
||||
'PhabricatorRefreshCSRFController' => 'PhabricatorAuthController',
|
||||
'PhabricatorRemarkupControl' => 'AphrontFormTextAreaControl',
|
||||
'PhabricatorRemarkupRuleDifferentialHandle' => 'PhabricatorRemarkupRuleObjectHandle',
|
||||
'PhabricatorRemarkupRuleEmbedFile' => 'PhutilRemarkupRule',
|
||||
'PhabricatorRemarkupRuleImageMacro' => 'PhutilRemarkupRule',
|
||||
'PhabricatorRemarkupRuleManiphestHandle' => 'PhabricatorRemarkupRuleObjectHandle',
|
||||
'PhabricatorRemarkupRuleMeme' => 'PhutilRemarkupRule',
|
||||
'PhabricatorRemarkupRuleMention' => 'PhutilRemarkupRule',
|
||||
'PhabricatorRemarkupRuleObjectHandle' => 'PhutilRemarkupRule',
|
||||
'PhabricatorRemarkupRuleObject' => 'PhutilRemarkupRule',
|
||||
'PhabricatorRemarkupRuleObjectName' => 'PhutilRemarkupRule',
|
||||
'PhabricatorRemarkupRuleYoutube' => 'PhutilRemarkupRule',
|
||||
'PhabricatorRepository' =>
|
||||
|
@ -2948,6 +2945,7 @@ phutil_register_library_map(array(
|
|||
'PholioMockListController' => 'PholioController',
|
||||
'PholioMockQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||
'PholioMockViewController' => 'PholioController',
|
||||
'PholioRemarkupRule' => 'PhabricatorRemarkupRuleObject',
|
||||
'PholioReplyHandler' => 'PhabricatorMailReplyHandler',
|
||||
'PholioSearchIndexer' => 'PhabricatorSearchDocumentIndexer',
|
||||
'PholioTransaction' => 'PhabricatorApplicationTransaction',
|
||||
|
|
|
@ -152,6 +152,10 @@ abstract class PhabricatorApplication {
|
|||
}
|
||||
}
|
||||
|
||||
public function getRemarkupRules() {
|
||||
return array();
|
||||
}
|
||||
|
||||
|
||||
/* -( URI Routing )-------------------------------------------------------- */
|
||||
|
||||
|
|
|
@ -73,6 +73,12 @@ final class PhabricatorApplicationDifferential extends PhabricatorApplication {
|
|||
return 0.100;
|
||||
}
|
||||
|
||||
public function getRemarkupRules() {
|
||||
return array(
|
||||
new DifferentialRemarkupRule(),
|
||||
);
|
||||
}
|
||||
|
||||
public function loadStatus(PhabricatorUser $user) {
|
||||
$revisions = id(new DifferentialRevisionQuery())
|
||||
->withResponsibleUsers(array($user->getPHID()))
|
||||
|
|
|
@ -60,6 +60,16 @@ final class DifferentialRevisionQuery {
|
|||
private $needDiffIDs = false;
|
||||
private $needCommitPHIDs = false;
|
||||
private $needHashes = false;
|
||||
private $viewer;
|
||||
|
||||
public function setViewer(PhabricatorUser $viewer) {
|
||||
$this->viewer = $viewer;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getViewer() {
|
||||
return $this->viewer;
|
||||
}
|
||||
|
||||
|
||||
/* -( Query Configuration )------------------------------------------------ */
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @group markup
|
||||
*/
|
||||
final class PhabricatorRemarkupRuleDifferentialHandle
|
||||
extends PhabricatorRemarkupRuleObjectHandle {
|
||||
|
||||
protected function getObjectNamePrefix() {
|
||||
return 'D';
|
||||
}
|
||||
|
||||
protected function loadObjectPHID($id) {
|
||||
$revision = id(new DifferentialRevision())->load($id);
|
||||
if ($revision) {
|
||||
return $revision->getPHID();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -58,7 +58,16 @@ final class ManiphestTaskQuery extends PhabricatorQuery {
|
|||
private $rowCount = null;
|
||||
|
||||
private $groupByProjectResults = null; // See comment at bottom for details
|
||||
private $viewer;
|
||||
|
||||
public function setViewer(PhabricatorUser $viewer) {
|
||||
$this->viewer = $viewer;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getViewer() {
|
||||
return $this->viewer;
|
||||
}
|
||||
|
||||
public function withAuthors(array $authors) {
|
||||
$this->authorPHIDs = $authors;
|
||||
|
|
|
@ -38,7 +38,13 @@ final class PhabricatorApplicationManiphest extends PhabricatorApplication {
|
|||
|
||||
public function getEventListeners() {
|
||||
return array(
|
||||
new ManiphestPeopleMenuEventListener()
|
||||
new ManiphestPeopleMenuEventListener(),
|
||||
);
|
||||
}
|
||||
|
||||
public function getRemarkupRules() {
|
||||
return array(
|
||||
new ManiphestRemarkupRule(),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @group markup
|
||||
*/
|
||||
final class PhabricatorRemarkupRuleManiphestHandle
|
||||
extends PhabricatorRemarkupRuleObjectHandle {
|
||||
|
||||
protected function getObjectNamePrefix() {
|
||||
return 'T';
|
||||
}
|
||||
|
||||
protected function loadObjectPHID($id) {
|
||||
$task = id(new ManiphestTask())->load($id);
|
||||
if ($task) {
|
||||
return $task->getPHID();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -22,6 +22,12 @@ final class PhabricatorApplicationPaste extends PhabricatorApplication {
|
|||
return $this->getBaseURI().'create/';
|
||||
}
|
||||
|
||||
public function getRemarkupRules() {
|
||||
return array(
|
||||
new PhabricatorPasteRemarkupRule(),
|
||||
);
|
||||
}
|
||||
|
||||
public function getRoutes() {
|
||||
return array(
|
||||
'/P(?P<id>[1-9]\d*)' => 'PhabricatorPasteViewController',
|
||||
|
|
|
@ -34,6 +34,12 @@ final class PhabricatorApplicationPholio extends PhabricatorApplication {
|
|||
return true;
|
||||
}
|
||||
|
||||
public function getRemarkupRules() {
|
||||
return array(
|
||||
new PholioRemarkupRule(),
|
||||
);
|
||||
}
|
||||
|
||||
public function getRoutes() {
|
||||
return array(
|
||||
'/M(?P<id>[1-9]\d*)' => 'PholioMockViewController',
|
||||
|
|
23
src/applications/pholio/remarkup/PholioRemarkupRule.php
Normal file
23
src/applications/pholio/remarkup/PholioRemarkupRule.php
Normal file
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
final class PholioRemarkupRule
|
||||
extends PhabricatorRemarkupRuleObject {
|
||||
|
||||
protected function getObjectNamePrefix() {
|
||||
return 'M';
|
||||
}
|
||||
|
||||
protected function loadObjects(array $ids) {
|
||||
$viewer = $this->getEngine()->getConfig('viewer');
|
||||
|
||||
if (!$viewer) {
|
||||
return array();
|
||||
}
|
||||
|
||||
return id(new PholioMockQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs($ids)
|
||||
->execute();
|
||||
}
|
||||
|
||||
}
|
|
@ -31,6 +31,12 @@ final class PhabricatorApplicationPonder extends PhabricatorApplication {
|
|||
return $status;
|
||||
}
|
||||
|
||||
public function getRemarkupRules() {
|
||||
return array(
|
||||
new PonderRemarkupRule(),
|
||||
);
|
||||
}
|
||||
|
||||
public function getApplicationGroup() {
|
||||
return self::GROUP_COMMUNICATION;
|
||||
}
|
||||
|
|
|
@ -400,23 +400,18 @@ final class PhabricatorMarkupEngine {
|
|||
$rules[] = new PhutilRemarkupRuleHyperlink();
|
||||
$rules[] = new PhrictionRemarkupRule();
|
||||
|
||||
$rules[] = new PhabricatorRemarkupRuleDifferentialHandle();
|
||||
if (PhabricatorEnv::getEnvConfig('maniphest.enabled')) {
|
||||
$rules[] = new PhabricatorRemarkupRuleManiphestHandle();
|
||||
}
|
||||
|
||||
$rules[] = new PhabricatorRemarkupRuleEmbedFile();
|
||||
|
||||
$rules[] = new DifferentialRemarkupRule();
|
||||
$rules[] = new DiffusionRemarkupRule();
|
||||
if (PhabricatorEnv::getEnvConfig('maniphest.enabled')) {
|
||||
$rules[] = new ManiphestRemarkupRule();
|
||||
}
|
||||
$rules[] = new PhabricatorPasteRemarkupRule();
|
||||
|
||||
$rules[] = new PhabricatorCountdownRemarkupRule();
|
||||
|
||||
$rules[] = new PonderRemarkupRule();
|
||||
$applications = PhabricatorApplication::getAllInstalledApplications();
|
||||
foreach ($applications as $application) {
|
||||
foreach ($application->getRemarkupRules() as $rule) {
|
||||
$rules[] = $rule;
|
||||
}
|
||||
}
|
||||
|
||||
if ($options['macros']) {
|
||||
$rules[] = new PhabricatorRemarkupRuleImageMacro();
|
||||
|
|
187
src/infrastructure/markup/rule/PhabricatorRemarkupRuleObject.php
Normal file
187
src/infrastructure/markup/rule/PhabricatorRemarkupRuleObject.php
Normal file
|
@ -0,0 +1,187 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @group markup
|
||||
*/
|
||||
abstract class PhabricatorRemarkupRuleObject
|
||||
extends PhutilRemarkupRule {
|
||||
|
||||
const KEY_RULE_OBJECT = 'rule.object';
|
||||
|
||||
abstract protected function getObjectNamePrefix();
|
||||
abstract protected function loadObjects(array $ids);
|
||||
|
||||
protected function getObjectIDPattern() {
|
||||
return '[1-9]\d*';
|
||||
}
|
||||
|
||||
protected function shouldMarkupObject(array $params) {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function loadHandles(array $objects) {
|
||||
$phids = mpull($objects, 'getPHID');
|
||||
$query = new PhabricatorObjectHandleData($phids);
|
||||
|
||||
$viewer = $this->getEngine()->getConfig('viewer');
|
||||
if ($viewer) {
|
||||
$query->setViewer($viewer);
|
||||
}
|
||||
$handles = $query->loadHandles();
|
||||
|
||||
$result = array();
|
||||
foreach ($objects as $id => $object) {
|
||||
$result[$id] = $handles[$object->getPHID()];
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected function renderObjectRef($object, $handle, $anchor) {
|
||||
$href = $handle->getURI();
|
||||
$text = $this->getObjectNamePrefix().$object->getID();
|
||||
if ($anchor) {
|
||||
$matches = null;
|
||||
if (preg_match('@^#(?:comment-)?(\d{1,7})$@', $anchor, $matches)) {
|
||||
// Maximum length is 7 because 12345678 could be a file hash in
|
||||
// Differential.
|
||||
$href = $href."#comment-".$matches[1];
|
||||
$text = $text."#".$matches[1];
|
||||
} else {
|
||||
$href = $href.$anchor;
|
||||
$text = $text.$anchor;
|
||||
}
|
||||
}
|
||||
|
||||
$status_closed = PhabricatorObjectHandleStatus::STATUS_CLOSED;
|
||||
|
||||
$attr = array(
|
||||
'phid' => $handle->getPHID(),
|
||||
'closed' => ($handle->getStatus() == $status_closed),
|
||||
);
|
||||
|
||||
return $this->renderHovertag($text, $href, $attr);
|
||||
}
|
||||
|
||||
protected function renderObjectEmbed($object, $handle, $options) {
|
||||
$name = $handle->getFullName();
|
||||
$href = $handle->getURI();
|
||||
$attr = array(
|
||||
'phid' => $handle->getPHID(),
|
||||
);
|
||||
|
||||
return $this->renderHovertag($name, $href, $attr);
|
||||
}
|
||||
|
||||
protected function renderHovertag($name, $href, array $attr = array()) {
|
||||
return id(new PhabricatorTagView())
|
||||
->setName($name)
|
||||
->setHref($href)
|
||||
->setType(PhabricatorTagView::TYPE_OBJECT)
|
||||
->setPHID(idx($attr, 'phid'))
|
||||
->setClosed(idx($attr, 'closed'))
|
||||
->render();
|
||||
}
|
||||
|
||||
public function apply($text) {
|
||||
$prefix = $this->getObjectNamePrefix();
|
||||
$prefix = preg_quote($prefix, '@');
|
||||
$id = $this->getObjectIDPattern();
|
||||
|
||||
$text = preg_replace_callback(
|
||||
'@\B{'.$prefix.'('.$id.')((?:[^}\\\\]|\\\\.)*)}\B@',
|
||||
array($this, 'markupObjectEmbed'),
|
||||
$text);
|
||||
|
||||
$text = preg_replace_callback(
|
||||
'@\b'.$prefix.'('.$id.')(?:#([-\w\d]+))?\b@',
|
||||
array($this, 'markupObjectReference'),
|
||||
$text);
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
public function markupObjectEmbed($matches) {
|
||||
return $this->markupObject(array(
|
||||
'type' => 'embed',
|
||||
'id' => $matches[1],
|
||||
'options' => idx($matches, 2),
|
||||
'original' => $matches[0],
|
||||
));
|
||||
}
|
||||
|
||||
public function markupObjectReference($matches) {
|
||||
return $this->markupObject(array(
|
||||
'type' => 'ref',
|
||||
'id' => $matches[1],
|
||||
'anchor' => idx($matches, 2),
|
||||
'original' => $matches[0],
|
||||
));
|
||||
}
|
||||
|
||||
private function markupObject(array $params) {
|
||||
if (!$this->shouldMarkupObject($params)) {
|
||||
return $params['original'];
|
||||
}
|
||||
|
||||
$engine = $this->getEngine();
|
||||
$token = $engine->storeText('x');
|
||||
|
||||
$metadata_key = self::KEY_RULE_OBJECT.'.'.$this->getObjectNamePrefix();
|
||||
$metadata = $engine->getTextMetadata($metadata_key, array());
|
||||
|
||||
$metadata[] = array(
|
||||
'token' => $token,
|
||||
) + $params;
|
||||
|
||||
$engine->setTextMetadata($metadata_key, $metadata);
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
public function didMarkupText() {
|
||||
$engine = $this->getEngine();
|
||||
$metadata_key = self::KEY_RULE_OBJECT.'.'.$this->getObjectNamePrefix();
|
||||
$metadata = $engine->getTextMetadata($metadata_key, array());
|
||||
|
||||
if (!$metadata) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
$ids = ipull($metadata, 'id');
|
||||
$objects = $this->loadObjects($ids);
|
||||
|
||||
// For objects that are invalid or which the user can't see, just render
|
||||
// the original text.
|
||||
|
||||
// TODO: We should probably distinguish between these cases and render a
|
||||
// "you can't see this" state for nonvisible objects.
|
||||
|
||||
foreach ($metadata as $key => $spec) {
|
||||
if (empty($objects[$spec['id']])) {
|
||||
$engine->overwriteStoredText(
|
||||
$spec['token'],
|
||||
$spec['original']);
|
||||
unset($metadata[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
$handles = $this->loadHandles($objects);
|
||||
foreach ($metadata as $key => $spec) {
|
||||
$handle = $handles[$spec['id']];
|
||||
$object = $objects[$spec['id']];
|
||||
switch ($spec['type']) {
|
||||
case 'ref':
|
||||
$view = $this->renderObjectRef($object, $handle, $spec['anchor']);
|
||||
break;
|
||||
case 'embed':
|
||||
$view = $this->renderObjectEmbed($object, $handle, $spec['options']);
|
||||
break;
|
||||
}
|
||||
$engine->overwriteStoredText($spec['token'], $view);
|
||||
}
|
||||
|
||||
$engine->setTextMetadata($metadata_key, array());
|
||||
}
|
||||
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @group markup
|
||||
*/
|
||||
abstract class PhabricatorRemarkupRuleObjectHandle
|
||||
extends PhutilRemarkupRule {
|
||||
|
||||
const KEY_RULE_HANDLE = 'rule.handle';
|
||||
|
||||
abstract protected function getObjectNamePrefix();
|
||||
abstract protected function loadObjectPHID($id);
|
||||
|
||||
public function apply($text) {
|
||||
$prefix = $this->getObjectNamePrefix();
|
||||
return preg_replace_callback(
|
||||
"@\B{{$prefix}(\d+)}\B@",
|
||||
array($this, 'markupObjectHandle'),
|
||||
$text);
|
||||
}
|
||||
|
||||
protected function markupObjectHandle($matches) {
|
||||
// TODO: These are single gets but should be okay for now, they're behind
|
||||
// the cache.
|
||||
$phid = $this->loadObjectPHID($matches[1]);
|
||||
if (!$phid) {
|
||||
return $matches[0];
|
||||
}
|
||||
|
||||
$engine = $this->getEngine();
|
||||
$token = $engine->storeText('');
|
||||
|
||||
$metadata_key = self::KEY_RULE_HANDLE;
|
||||
$metadata = $engine->getTextMetadata($metadata_key, array());
|
||||
if (empty($metadata[$phid])) {
|
||||
$metadata[$phid] = array();
|
||||
}
|
||||
$metadata[$phid][] = $token;
|
||||
$engine->setTextMetadata($metadata_key, $metadata);
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
public function didMarkupText() {
|
||||
$engine = $this->getEngine();
|
||||
|
||||
$metadata_key = self::KEY_RULE_HANDLE;
|
||||
$metadata = $engine->getTextMetadata($metadata_key, array());
|
||||
if (empty($metadata)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$handles = id(new PhabricatorObjectHandleData(array_keys($metadata)))
|
||||
->loadHandles();
|
||||
|
||||
foreach ($metadata as $phid => $tokens) {
|
||||
$link = $handles[$phid]->renderLink();
|
||||
foreach ($tokens as $token) {
|
||||
$engine->overwriteStoredText($token, $link);
|
||||
}
|
||||
}
|
||||
|
||||
$engine->setTextMetadata($metadata_key, array());
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue