mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-27 17:22:42 +01:00
Process Remarkup in text and HTML email bodies appropriately
Summary: Ref T6343, adding HTMLMailMode to remarkup, and most objects should now be processed and appear pretty in emails. Test Plan: Add a comment to a Maniphest task containing a mention of an object like '{T1}' or 'T1'. Emails should show a styled version of the object similar to how the object looks in the context of the Maniphest task in the UI. Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: chad, Korvin, epriestley Maniphest Tasks: T6343, T2617 Differential Revision: https://secure.phabricator.com/D10859
This commit is contained in:
parent
81a13ed8bd
commit
1b438a8bd1
11 changed files with 181 additions and 32 deletions
|
@ -140,6 +140,10 @@ final class DivinerSymbolRemarkupRule extends PhutilRemarkupRule {
|
||||||
$link = $title;
|
$link = $title;
|
||||||
}
|
}
|
||||||
} else if ($href) {
|
} else if ($href) {
|
||||||
|
if ($this->getEngine()->isHTMLMailMode()) {
|
||||||
|
$href = PhabricatorEnv::getProductionURI($href);
|
||||||
|
}
|
||||||
|
|
||||||
$link = $this->newTag(
|
$link = $this->newTag(
|
||||||
'a',
|
'a',
|
||||||
array(
|
array(
|
||||||
|
|
|
@ -14,7 +14,11 @@ final class PhabricatorIconRemarkupRule extends PhutilRemarkupRule {
|
||||||
}
|
}
|
||||||
|
|
||||||
public function markupIcon($matches) {
|
public function markupIcon($matches) {
|
||||||
if (!$this->isFlatText($matches[0])) {
|
$engine = $this->getEngine();
|
||||||
|
$text_mode = $engine->isTextMode();
|
||||||
|
$mail_mode = $engine->isHTMLMailMode();
|
||||||
|
|
||||||
|
if (!$this->isFlatText($matches[0]) || $text_mode || $mail_mode) {
|
||||||
return $matches[0];
|
return $matches[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,6 +73,7 @@ final class PhabricatorIconRemarkupRule extends PhutilRemarkupRule {
|
||||||
$icon_view = id(new PHUIIconView())
|
$icon_view = id(new PHUIIconView())
|
||||||
->setIconFont('fa-'.$icon, $color);
|
->setIconFont('fa-'.$icon, $color);
|
||||||
|
|
||||||
|
|
||||||
return $this->getEngine()->storeText($icon_view);
|
return $this->getEngine()->storeText($icon_view);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -109,6 +109,8 @@ final class PhabricatorImageMacroRemarkupRule extends PhutilRemarkupRule {
|
||||||
$result = $spec['original'].' <'.$src_uri.'>';
|
$result = $spec['original'].' <'.$src_uri.'>';
|
||||||
$engine->overwriteStoredText($spec['token'], $result);
|
$engine->overwriteStoredText($spec['token'], $result);
|
||||||
continue;
|
continue;
|
||||||
|
} else if ($this->getEngine()->isHTMLMailMode()) {
|
||||||
|
$src_uri = PhabricatorEnv::getProductionURI($src_uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
$file_data = $file->getMetadata();
|
$file_data = $file->getMetadata();
|
||||||
|
|
|
@ -34,6 +34,10 @@ final class PhabricatorMemeRemarkupRule extends PhutilRemarkupRule {
|
||||||
->alter('uppertext', $options['above'])
|
->alter('uppertext', $options['above'])
|
||||||
->alter('lowertext', $options['below']);
|
->alter('lowertext', $options['below']);
|
||||||
|
|
||||||
|
if ($this->getEngine()->isHTMLMailMode()) {
|
||||||
|
$uri = PhabricatorEnv::getProductionURI($uri);
|
||||||
|
}
|
||||||
|
|
||||||
if ($this->getEngine()->isTextMode()) {
|
if ($this->getEngine()->isTextMode()) {
|
||||||
$img =
|
$img =
|
||||||
($options['above'] != '' ? "\"{$options['above']}\"\n" : '').
|
($options['above'] != '' ? "\"{$options['above']}\"\n" : '').
|
||||||
|
|
|
@ -12,6 +12,15 @@ final class PhabricatorMetaMTAMailBody {
|
||||||
private $htmlSections = array();
|
private $htmlSections = array();
|
||||||
private $attachments = array();
|
private $attachments = array();
|
||||||
|
|
||||||
|
private $viewer;
|
||||||
|
|
||||||
|
public function getViewer() {
|
||||||
|
return $this->viewer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setViewer($viewer) {
|
||||||
|
$this->viewer = $viewer;
|
||||||
|
}
|
||||||
|
|
||||||
/* -( Composition )-------------------------------------------------------- */
|
/* -( Composition )-------------------------------------------------------- */
|
||||||
|
|
||||||
|
@ -33,6 +42,39 @@ final class PhabricatorMetaMTAMailBody {
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function addRemarkupSection($text) {
|
||||||
|
try {
|
||||||
|
$engine = PhabricatorMarkupEngine::newMarkupEngine(array());
|
||||||
|
$engine->setConfig('viewer', $this->getViewer());
|
||||||
|
$engine->setMode(PhutilRemarkupEngine::MODE_TEXT);
|
||||||
|
$styled_text = $engine->markupText($text);
|
||||||
|
$this->sections[] = $styled_text;
|
||||||
|
} catch (Exception $ex) {
|
||||||
|
phlog($ex);
|
||||||
|
$this->sections[] = $text;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$mail_engine = PhabricatorMarkupEngine::newMarkupEngine(array());
|
||||||
|
$mail_engine->setConfig('viewer', $this->getViewer());
|
||||||
|
$mail_engine->setMode(PhutilRemarkupEngine::MODE_HTML_MAIL);
|
||||||
|
$mail_engine->setConfig(
|
||||||
|
'uri.base',
|
||||||
|
PhabricatorEnv::getProductionURI('/'));
|
||||||
|
$html = $mail_engine->markupText($text);
|
||||||
|
$this->htmlSections[] = $html;
|
||||||
|
} catch (Exception $ex) {
|
||||||
|
phlog($ex);
|
||||||
|
$this->htmlSections[] = phutil_escape_html_newlines(
|
||||||
|
phutil_tag(
|
||||||
|
'div',
|
||||||
|
array(),
|
||||||
|
$text));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
public function addRawPlaintextSection($text) {
|
public function addRawPlaintextSection($text) {
|
||||||
if (strlen($text)) {
|
if (strlen($text)) {
|
||||||
$text = rtrim($text);
|
$text = rtrim($text);
|
||||||
|
|
|
@ -100,22 +100,41 @@ final class PhabricatorMentionRemarkupRule extends PhutilRemarkupRule {
|
||||||
$user = $actual_users[$username];
|
$user = $actual_users[$username];
|
||||||
Javelin::initBehavior('phabricator-hovercards');
|
Javelin::initBehavior('phabricator-hovercards');
|
||||||
|
|
||||||
$tag = id(new PHUITagView())
|
$user_href = '/p/'.$user->getUserName().'/';
|
||||||
->setType(PHUITagView::TYPE_PERSON)
|
|
||||||
->setPHID($user->getPHID())
|
|
||||||
->setName('@'.$user->getUserName())
|
|
||||||
->setHref('/p/'.$user->getUserName().'/');
|
|
||||||
|
|
||||||
if (!$user->isUserActivated()) {
|
if ($engine->isHTMLMailMode()) {
|
||||||
$tag->setDotColor(PHUITagView::COLOR_GREY);
|
$user_href = PhabricatorEnv::getProductionURI($user_href);
|
||||||
|
$tag = phutil_tag(
|
||||||
|
'a',
|
||||||
|
array(
|
||||||
|
'href' => $user_href,
|
||||||
|
'style' => 'background-color: #f1f7ff;
|
||||||
|
border-color: #f1f7ff;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 3px;
|
||||||
|
color: #19558d;
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 0 4px;',
|
||||||
|
),
|
||||||
|
'@'.$user->getUserName());
|
||||||
} else {
|
} else {
|
||||||
$status = idx($user_statuses, $user->getPHID());
|
$tag = id(new PHUITagView())
|
||||||
if ($status) {
|
->setType(PHUITagView::TYPE_PERSON)
|
||||||
$status = $status->getStatus();
|
->setPHID($user->getPHID())
|
||||||
if ($status == PhabricatorCalendarEvent::STATUS_AWAY) {
|
->setName('@'.$user->getUserName())
|
||||||
$tag->setDotColor(PHUITagView::COLOR_RED);
|
->setHref($user_href);
|
||||||
} else if ($status == PhabricatorCalendarEvent::STATUS_AWAY) {
|
|
||||||
$tag->setDotColor(PHUITagView::COLOR_ORANGE);
|
if (!$user->isUserActivated()) {
|
||||||
|
$tag->setDotColor(PHUITagView::COLOR_GREY);
|
||||||
|
} else {
|
||||||
|
$status = idx($user_statuses, $user->getPHID());
|
||||||
|
if ($status) {
|
||||||
|
$status = $status->getStatus();
|
||||||
|
if ($status == PhabricatorCalendarEvent::STATUS_AWAY) {
|
||||||
|
$tag->setDotColor(PHUITagView::COLOR_RED);
|
||||||
|
} else if ($status == PhabricatorCalendarEvent::STATUS_AWAY) {
|
||||||
|
$tag->setDotColor(PHUITagView::COLOR_ORANGE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,9 +29,12 @@ final class PhrictionRemarkupRule extends PhutilRemarkupRule {
|
||||||
$slug = PhrictionDocument::getSlugURI($slug);
|
$slug = PhrictionDocument::getSlugURI($slug);
|
||||||
$href = (string)id(new PhutilURI($slug))->setFragment($fragment);
|
$href = (string)id(new PhutilURI($slug))->setFragment($fragment);
|
||||||
|
|
||||||
|
$text_mode = $this->getEngine()->isTextMode();
|
||||||
|
$mail_mode = $this->getEngine()->isHTMLMailMode();
|
||||||
|
|
||||||
if ($this->getEngine()->getState('toc')) {
|
if ($this->getEngine()->getState('toc')) {
|
||||||
$text = $name;
|
$text = $name;
|
||||||
} else if ($this->getEngine()->isTextMode()) {
|
} else if ($text_mode || $mail_mode) {
|
||||||
return PhabricatorEnv::getProductionURI($href);
|
return PhabricatorEnv::getProductionURI($href);
|
||||||
} else {
|
} else {
|
||||||
$text = $this->newTag(
|
$text = $this->newTag(
|
||||||
|
|
|
@ -2159,10 +2159,11 @@ abstract class PhabricatorApplicationTransactionEditor
|
||||||
}
|
}
|
||||||
|
|
||||||
$body = new PhabricatorMetaMTAMailBody();
|
$body = new PhabricatorMetaMTAMailBody();
|
||||||
|
$body->setViewer($this->requireActor());
|
||||||
$body->addRawSection(implode("\n", $headers));
|
$body->addRawSection(implode("\n", $headers));
|
||||||
|
|
||||||
foreach ($comments as $comment) {
|
foreach ($comments as $comment) {
|
||||||
$body->addRawSection($comment);
|
$body->addRemarkupSection($comment);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($object instanceof PhabricatorCustomFieldInterface) {
|
if ($object instanceof PhabricatorCustomFieldInterface) {
|
||||||
|
|
|
@ -80,20 +80,30 @@ final class PhabricatorNavigationRemarkupRule extends PhutilRemarkupRule {
|
||||||
$out[] = $tag;
|
$out[] = $tag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($this->getEngine()->isHTMLMailMode()) {
|
||||||
|
$arrow_attr = array(
|
||||||
|
'style' => 'color: #92969D;',
|
||||||
|
);
|
||||||
|
$nav_attr = array();
|
||||||
|
} else {
|
||||||
|
$arrow_attr = array(
|
||||||
|
'class' => 'remarkup-nav-sequence-arrow',
|
||||||
|
);
|
||||||
|
$nav_attr = array(
|
||||||
|
'class' => 'remarkup-nav-sequence',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
$joiner = phutil_tag(
|
$joiner = phutil_tag(
|
||||||
'span',
|
'span',
|
||||||
array(
|
$arrow_attr,
|
||||||
'class' => 'remarkup-nav-sequence-arrow',
|
|
||||||
),
|
|
||||||
" \xE2\x86\x92 ");
|
" \xE2\x86\x92 ");
|
||||||
|
|
||||||
$out = phutil_implode_html($joiner, $out);
|
$out = phutil_implode_html($joiner, $out);
|
||||||
|
|
||||||
$out = phutil_tag(
|
$out = phutil_tag(
|
||||||
'span',
|
'span',
|
||||||
array(
|
$nav_attr,
|
||||||
'class' => 'remarkup-nav-sequence',
|
|
||||||
),
|
|
||||||
$out);
|
$out);
|
||||||
|
|
||||||
return $this->getEngine()->storeText($out);
|
return $this->getEngine()->storeText($out);
|
||||||
|
|
|
@ -44,7 +44,11 @@ abstract class PhabricatorObjectRemarkupRule extends PhutilRemarkupRule {
|
||||||
return $handle->getURI();
|
return $handle->getURI();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function renderObjectRef($object, $handle, $anchor, $id) {
|
protected function renderObjectRefForAnyMedia (
|
||||||
|
$object,
|
||||||
|
$handle,
|
||||||
|
$anchor,
|
||||||
|
$id) {
|
||||||
$href = $this->getObjectHref($object, $handle, $id);
|
$href = $this->getObjectHref($object, $handle, $id);
|
||||||
$text = $this->getObjectNamePrefix().$id;
|
$text = $this->getObjectNamePrefix().$id;
|
||||||
|
|
||||||
|
@ -55,10 +59,25 @@ abstract class PhabricatorObjectRemarkupRule extends PhutilRemarkupRule {
|
||||||
|
|
||||||
if ($this->getEngine()->isTextMode()) {
|
if ($this->getEngine()->isTextMode()) {
|
||||||
return PhabricatorEnv::getProductionURI($href);
|
return PhabricatorEnv::getProductionURI($href);
|
||||||
|
} else if ($this->getEngine()->isHTMLMailMode()) {
|
||||||
|
$href = PhabricatorEnv::getProductionURI($href);
|
||||||
|
return $this->renderObjectTagForMail($text, $href, $handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $this->renderObjectRef($object, $handle, $anchor, $id);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function renderObjectRef($object, $handle, $anchor, $id) {
|
||||||
|
$href = $this->getObjectHref($object, $handle, $id);
|
||||||
|
$text = $this->getObjectNamePrefix().$id;
|
||||||
$status_closed = PhabricatorObjectHandleStatus::STATUS_CLOSED;
|
$status_closed = PhabricatorObjectHandleStatus::STATUS_CLOSED;
|
||||||
|
|
||||||
|
if ($anchor) {
|
||||||
|
$href = $href.'#'.$anchor;
|
||||||
|
$text = $text.'#'.$anchor;
|
||||||
|
}
|
||||||
|
|
||||||
$attr = array(
|
$attr = array(
|
||||||
'phid' => $handle->getPHID(),
|
'phid' => $handle->getPHID(),
|
||||||
'closed' => ($handle->getStatus() == $status_closed),
|
'closed' => ($handle->getStatus() == $status_closed),
|
||||||
|
@ -67,15 +86,24 @@ abstract class PhabricatorObjectRemarkupRule extends PhutilRemarkupRule {
|
||||||
return $this->renderHovertag($text, $href, $attr);
|
return $this->renderHovertag($text, $href, $attr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function renderObjectEmbedForAnyMedia($object, $handle, $options) {
|
||||||
|
$name = $handle->getFullName();
|
||||||
|
$href = $handle->getURI();
|
||||||
|
|
||||||
|
if ($this->getEngine()->isTextMode()) {
|
||||||
|
return $name.' <'.PhabricatorEnv::getProductionURI($href).'>';
|
||||||
|
} else if ($this->getEngine()->isHTMLMailMode()) {
|
||||||
|
$href = PhabricatorEnv::getProductionURI($href);
|
||||||
|
return $this->renderObjectTagForMail($name, $href, $handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->renderObjectEmbed($object, $handle, $options);
|
||||||
|
}
|
||||||
|
|
||||||
protected function renderObjectEmbed($object, $handle, $options) {
|
protected function renderObjectEmbed($object, $handle, $options) {
|
||||||
$name = $handle->getFullName();
|
$name = $handle->getFullName();
|
||||||
$href = $handle->getURI();
|
$href = $handle->getURI();
|
||||||
$status_closed = PhabricatorObjectHandleStatus::STATUS_CLOSED;
|
$status_closed = PhabricatorObjectHandleStatus::STATUS_CLOSED;
|
||||||
|
|
||||||
if ($this->getEngine()->isTextMode()) {
|
|
||||||
return $name.' <'.PhabricatorEnv::getProductionURI($href).'>';
|
|
||||||
}
|
|
||||||
|
|
||||||
$attr = array(
|
$attr = array(
|
||||||
'phid' => $handle->getPHID(),
|
'phid' => $handle->getPHID(),
|
||||||
'closed' => ($handle->getStatus() == $status_closed),
|
'closed' => ($handle->getStatus() == $status_closed),
|
||||||
|
@ -84,6 +112,31 @@ abstract class PhabricatorObjectRemarkupRule extends PhutilRemarkupRule {
|
||||||
return $this->renderHovertag($name, $href, $attr);
|
return $this->renderHovertag($name, $href, $attr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function renderObjectTagForMail(
|
||||||
|
$text,
|
||||||
|
$href,
|
||||||
|
$handle) {
|
||||||
|
|
||||||
|
$status_closed = PhabricatorObjectHandleStatus::STATUS_CLOSED;
|
||||||
|
$strikethrough = $handle->getStatus() == $status_closed ?
|
||||||
|
'text-decoration: line-through;' :
|
||||||
|
'text-decoration: none;';
|
||||||
|
|
||||||
|
return phutil_tag(
|
||||||
|
'a',
|
||||||
|
array(
|
||||||
|
'href' => $href,
|
||||||
|
'style' => 'background-color: #e7e7e7;
|
||||||
|
border-color: #e7e7e7;
|
||||||
|
border-radius: 3px;
|
||||||
|
padding: 0 4px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: black;'
|
||||||
|
.$strikethrough,
|
||||||
|
),
|
||||||
|
$text);
|
||||||
|
}
|
||||||
|
|
||||||
protected function renderHovertag($name, $href, array $attr = array()) {
|
protected function renderHovertag($name, $href, array $attr = array()) {
|
||||||
return id(new PHUITagView())
|
return id(new PHUITagView())
|
||||||
->setName($name)
|
->setName($name)
|
||||||
|
@ -282,7 +335,8 @@ abstract class PhabricatorObjectRemarkupRule extends PhutilRemarkupRule {
|
||||||
$object = $objects[$spec['id']];
|
$object = $objects[$spec['id']];
|
||||||
switch ($spec['type']) {
|
switch ($spec['type']) {
|
||||||
case 'ref':
|
case 'ref':
|
||||||
$view = $this->renderObjectRef(
|
|
||||||
|
$view = $this->renderObjectRefForAnyMedia(
|
||||||
$object,
|
$object,
|
||||||
$handle,
|
$handle,
|
||||||
$spec['anchor'],
|
$spec['anchor'],
|
||||||
|
@ -290,7 +344,10 @@ abstract class PhabricatorObjectRemarkupRule extends PhutilRemarkupRule {
|
||||||
break;
|
break;
|
||||||
case 'embed':
|
case 'embed':
|
||||||
$spec['options'] = $this->assertFlatText($spec['options']);
|
$spec['options'] = $this->assertFlatText($spec['options']);
|
||||||
$view = $this->renderObjectEmbed($object, $handle, $spec['options']);
|
$view = $this->renderObjectEmbedForAnyMedia(
|
||||||
|
$object,
|
||||||
|
$handle,
|
||||||
|
$spec['options']);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
$engine->overwriteStoredText($spec['token'], $view);
|
$engine->overwriteStoredText($spec['token'], $view);
|
||||||
|
|
|
@ -20,8 +20,10 @@ final class PhabricatorYoutubeRemarkupRule extends PhutilRemarkupRule {
|
||||||
|
|
||||||
public function markupYoutubeLink() {
|
public function markupYoutubeLink() {
|
||||||
$v = idx($this->uri->getQueryParams(), 'v');
|
$v = idx($this->uri->getQueryParams(), 'v');
|
||||||
|
$text_mode = $this->getEngine()->isTextMode();
|
||||||
|
$mail_mode = $this->getEngine()->isHTMLMailMode();
|
||||||
|
|
||||||
if ($this->getEngine()->isTextMode()) {
|
if ($text_mode || $mail_mode) {
|
||||||
return $this->getEngine()->storeText('http://youtu.be/'.$v);
|
return $this->getEngine()->storeText('http://youtu.be/'.$v);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue