mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-10 00:42:41 +01:00
Desktop Notification support
Summary: Fixes T4139. Adds a "Desktop Notifications" panel to settings. For now, we start with "Send Desktop Notifications Too" functionality. We can try to be fancy later and only send desktop notifications if the web app doesn't have focus, etc. Test Plan: Made some comments as a test user on a task and got purdy desktop notifications using Chrome. Then did it again with Firefox. Played around with permissions form with Chrome and got helpful information about what was up. Played around with Firefox and got similar results, except canceling the dialogue didn't invoke my handler code somehow. Oh Firefox! Reviewers: epriestley Reviewed By: epriestley Subscribers: rbalik, tycho.tatitscheff, joshuaspence, epriestley, Korvin Maniphest Tasks: T4139 Differential Revision: https://secure.phabricator.com/D13219
This commit is contained in:
parent
95fe4f9451
commit
1bb2978a89
15 changed files with 489 additions and 32 deletions
|
@ -8,7 +8,7 @@
|
|||
return array(
|
||||
'names' => array(
|
||||
'core.pkg.css' => 'eb51e6dc',
|
||||
'core.pkg.js' => 'e0117d99',
|
||||
'core.pkg.js' => '711e63c0',
|
||||
'darkconsole.pkg.js' => 'e7393ebb',
|
||||
'differential.pkg.css' => '02273347',
|
||||
'differential.pkg.js' => 'ebef29b1',
|
||||
|
@ -328,8 +328,9 @@ return array(
|
|||
'rsrc/image/texture/table_header_tall.png' => 'd56b434f',
|
||||
'rsrc/js/application/aphlict/Aphlict.js' => '5359e785',
|
||||
'rsrc/js/application/aphlict/behavior-aphlict-dropdown.js' => '995ad707',
|
||||
'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => 'b1a59974',
|
||||
'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => 'fb20ac8d',
|
||||
'rsrc/js/application/aphlict/behavior-aphlict-status.js' => 'ea681761',
|
||||
'rsrc/js/application/aphlict/behavior-desktop-notifications-control.js' => 'edd1ba66',
|
||||
'rsrc/js/application/auth/behavior-persona-login.js' => '9414ff18',
|
||||
'rsrc/js/application/calendar/behavior-day-view.js' => '5c46cff2',
|
||||
'rsrc/js/application/calendar/behavior-event-all-day.js' => '38dcf3c8',
|
||||
|
@ -429,7 +430,7 @@ return array(
|
|||
'rsrc/js/core/KeyboardShortcut.js' => '1ae869f2',
|
||||
'rsrc/js/core/KeyboardShortcutManager.js' => 'c1700f6f',
|
||||
'rsrc/js/core/MultirowRowManager.js' => 'b5d57730',
|
||||
'rsrc/js/core/Notification.js' => '0c6946e7',
|
||||
'rsrc/js/core/Notification.js' => 'ccf1cbf8',
|
||||
'rsrc/js/core/Prefab.js' => '6920d200',
|
||||
'rsrc/js/core/ShapedRequest.js' => '7cbe244b',
|
||||
'rsrc/js/core/TextAreaUtils.js' => '5c93c52c',
|
||||
|
@ -534,7 +535,7 @@ return array(
|
|||
'javelin-aphlict' => '5359e785',
|
||||
'javelin-behavior' => '61cbc29a',
|
||||
'javelin-behavior-aphlict-dropdown' => '995ad707',
|
||||
'javelin-behavior-aphlict-listen' => 'b1a59974',
|
||||
'javelin-behavior-aphlict-listen' => 'fb20ac8d',
|
||||
'javelin-behavior-aphlict-status' => 'ea681761',
|
||||
'javelin-behavior-aphront-basic-tokenizer' => 'b3a4b884',
|
||||
'javelin-behavior-aphront-crop' => 'fa0f4fc2',
|
||||
|
@ -556,6 +557,7 @@ return array(
|
|||
'javelin-behavior-dashboard-query-panel-select' => '453c5375',
|
||||
'javelin-behavior-dashboard-tab-panel' => 'd4eecc63',
|
||||
'javelin-behavior-day-view' => '5c46cff2',
|
||||
'javelin-behavior-desktop-notifications-control' => 'edd1ba66',
|
||||
'javelin-behavior-device' => 'a205cf28',
|
||||
'javelin-behavior-differential-add-reviewers-and-ccs' => 'e10f8e18',
|
||||
'javelin-behavior-differential-comment-jump' => '4fdb476d',
|
||||
|
@ -726,7 +728,7 @@ return array(
|
|||
'phabricator-keyboard-shortcut-manager' => 'c1700f6f',
|
||||
'phabricator-main-menu-view' => '663e3810',
|
||||
'phabricator-nav-view-css' => '7aeaf435',
|
||||
'phabricator-notification' => '0c6946e7',
|
||||
'phabricator-notification' => 'ccf1cbf8',
|
||||
'phabricator-notification-css' => '9c279160',
|
||||
'phabricator-notification-menu-css' => '3c9d8aa1',
|
||||
'phabricator-object-selector-css' => '029a133d',
|
||||
|
@ -892,13 +894,6 @@ return array(
|
|||
'javelin-dom',
|
||||
'javelin-router',
|
||||
),
|
||||
'0c6946e7' => array(
|
||||
'javelin-install',
|
||||
'javelin-dom',
|
||||
'javelin-stratcom',
|
||||
'javelin-util',
|
||||
'phabricator-notification-css',
|
||||
),
|
||||
'0f764c35' => array(
|
||||
'javelin-install',
|
||||
'javelin-util',
|
||||
|
@ -1644,20 +1639,6 @@ return array(
|
|||
'javelin-util',
|
||||
'phabricator-shaped-request',
|
||||
),
|
||||
'b1a59974' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-aphlict',
|
||||
'javelin-stratcom',
|
||||
'javelin-request',
|
||||
'javelin-uri',
|
||||
'javelin-dom',
|
||||
'javelin-json',
|
||||
'javelin-router',
|
||||
'javelin-util',
|
||||
'javelin-leader',
|
||||
'javelin-sound',
|
||||
'phabricator-notification',
|
||||
),
|
||||
'b1f0ccee' => array(
|
||||
'javelin-install',
|
||||
'javelin-dom',
|
||||
|
@ -1792,6 +1773,13 @@ return array(
|
|||
'javelin-stratcom',
|
||||
'phabricator-phtize',
|
||||
),
|
||||
'ccf1cbf8' => array(
|
||||
'javelin-install',
|
||||
'javelin-dom',
|
||||
'javelin-stratcom',
|
||||
'javelin-util',
|
||||
'phabricator-notification-css',
|
||||
),
|
||||
'cf86d16a' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-dom',
|
||||
|
@ -1939,6 +1927,13 @@ return array(
|
|||
'phabricator-phtize',
|
||||
'javelin-dom',
|
||||
),
|
||||
'edd1ba66' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-stratcom',
|
||||
'javelin-dom',
|
||||
'javelin-uri',
|
||||
'phabricator-notification',
|
||||
),
|
||||
'eeaa9e5a' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-stratcom',
|
||||
|
@ -2014,6 +2009,20 @@ return array(
|
|||
'javelin-vector',
|
||||
'javelin-magical-init',
|
||||
),
|
||||
'fb20ac8d' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-aphlict',
|
||||
'javelin-stratcom',
|
||||
'javelin-request',
|
||||
'javelin-uri',
|
||||
'javelin-dom',
|
||||
'javelin-json',
|
||||
'javelin-router',
|
||||
'javelin-util',
|
||||
'javelin-leader',
|
||||
'javelin-sound',
|
||||
'phabricator-notification',
|
||||
),
|
||||
'fbe497e7' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-util',
|
||||
|
|
|
@ -1789,6 +1789,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorDatabaseSetupCheck' => 'applications/config/check/PhabricatorDatabaseSetupCheck.php',
|
||||
'PhabricatorDateTimeSettingsPanel' => 'applications/settings/panel/PhabricatorDateTimeSettingsPanel.php',
|
||||
'PhabricatorDebugController' => 'applications/system/controller/PhabricatorDebugController.php',
|
||||
'PhabricatorDesktopNotificationsSettingsPanel' => 'applications/settings/panel/PhabricatorDesktopNotificationsSettingsPanel.php',
|
||||
'PhabricatorDestructibleInterface' => 'applications/system/interface/PhabricatorDestructibleInterface.php',
|
||||
'PhabricatorDestructionEngine' => 'applications/system/engine/PhabricatorDestructionEngine.php',
|
||||
'PhabricatorDeveloperConfigOptions' => 'applications/config/option/PhabricatorDeveloperConfigOptions.php',
|
||||
|
@ -2133,7 +2134,6 @@ phutil_register_library_map(array(
|
|||
'PhabricatorNamedQueryQuery' => 'applications/search/query/PhabricatorNamedQueryQuery.php',
|
||||
'PhabricatorNavigationRemarkupRule' => 'infrastructure/markup/rule/PhabricatorNavigationRemarkupRule.php',
|
||||
'PhabricatorNeverTriggerClock' => 'infrastructure/daemon/workers/clock/PhabricatorNeverTriggerClock.php',
|
||||
'PhabricatorNotificationAdHocFeedStory' => 'applications/notification/feed/PhabricatorNotificationAdHocFeedStory.php',
|
||||
'PhabricatorNotificationBuilder' => 'applications/notification/builder/PhabricatorNotificationBuilder.php',
|
||||
'PhabricatorNotificationClearController' => 'applications/notification/controller/PhabricatorNotificationClearController.php',
|
||||
'PhabricatorNotificationClient' => 'applications/notification/client/PhabricatorNotificationClient.php',
|
||||
|
@ -2147,6 +2147,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorNotificationStatusController' => 'applications/notification/controller/PhabricatorNotificationStatusController.php',
|
||||
'PhabricatorNotificationStatusView' => 'applications/notification/view/PhabricatorNotificationStatusView.php',
|
||||
'PhabricatorNotificationTestController' => 'applications/notification/controller/PhabricatorNotificationTestController.php',
|
||||
'PhabricatorNotificationTestFeedStory' => 'applications/notification/feed/PhabricatorNotificationTestFeedStory.php',
|
||||
'PhabricatorNotificationUIExample' => 'applications/uiexample/examples/PhabricatorNotificationUIExample.php',
|
||||
'PhabricatorNotificationsApplication' => 'applications/notification/application/PhabricatorNotificationsApplication.php',
|
||||
'PhabricatorNuanceApplication' => 'applications/nuance/application/PhabricatorNuanceApplication.php',
|
||||
|
@ -5385,6 +5386,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorDatabaseSetupCheck' => 'PhabricatorSetupCheck',
|
||||
'PhabricatorDateTimeSettingsPanel' => 'PhabricatorSettingsPanel',
|
||||
'PhabricatorDebugController' => 'PhabricatorController',
|
||||
'PhabricatorDesktopNotificationsSettingsPanel' => 'PhabricatorSettingsPanel',
|
||||
'PhabricatorDestructionEngine' => 'Phobject',
|
||||
'PhabricatorDeveloperConfigOptions' => 'PhabricatorApplicationConfigOptions',
|
||||
'PhabricatorDeveloperPreferencesSettingsPanel' => 'PhabricatorSettingsPanel',
|
||||
|
@ -5773,7 +5775,6 @@ phutil_register_library_map(array(
|
|||
'PhabricatorNamedQueryQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||
'PhabricatorNavigationRemarkupRule' => 'PhutilRemarkupRule',
|
||||
'PhabricatorNeverTriggerClock' => 'PhabricatorTriggerClock',
|
||||
'PhabricatorNotificationAdHocFeedStory' => 'PhabricatorFeedStory',
|
||||
'PhabricatorNotificationBuilder' => 'Phobject',
|
||||
'PhabricatorNotificationClearController' => 'PhabricatorNotificationController',
|
||||
'PhabricatorNotificationClient' => 'Phobject',
|
||||
|
@ -5787,6 +5788,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorNotificationStatusController' => 'PhabricatorNotificationController',
|
||||
'PhabricatorNotificationStatusView' => 'AphrontTagView',
|
||||
'PhabricatorNotificationTestController' => 'PhabricatorNotificationController',
|
||||
'PhabricatorNotificationTestFeedStory' => 'PhabricatorFeedStory',
|
||||
'PhabricatorNotificationUIExample' => 'PhabricatorUIExample',
|
||||
'PhabricatorNotificationsApplication' => 'PhabricatorApplication',
|
||||
'PhabricatorNuanceApplication' => 'PhabricatorApplication',
|
||||
|
|
|
@ -3,9 +3,11 @@
|
|||
final class PhabricatorNotificationBuilder extends Phobject {
|
||||
|
||||
private $stories;
|
||||
private $parsedStories;
|
||||
private $user = null;
|
||||
|
||||
public function __construct(array $stories) {
|
||||
assert_instances_of($stories, 'PhabricatorFeedStory');
|
||||
$this->stories = $stories;
|
||||
}
|
||||
|
||||
|
@ -14,7 +16,11 @@ final class PhabricatorNotificationBuilder extends Phobject {
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function buildView() {
|
||||
private function parseStories() {
|
||||
|
||||
if ($this->parsedStories) {
|
||||
return $this->parsedStories;
|
||||
}
|
||||
|
||||
$stories = $this->stories;
|
||||
$stories = mpull($stories, null, 'getChronologicalKey');
|
||||
|
@ -100,6 +106,12 @@ final class PhabricatorNotificationBuilder extends Phobject {
|
|||
$stories = mpull($stories, null, 'getChronologicalKey');
|
||||
krsort($stories);
|
||||
|
||||
$this->parsedStories = $stories;
|
||||
return $stories;
|
||||
}
|
||||
|
||||
public function buildView() {
|
||||
$stories = $this->parseStories();
|
||||
$null_view = new AphrontNullView();
|
||||
|
||||
foreach ($stories as $story) {
|
||||
|
@ -114,4 +126,39 @@ final class PhabricatorNotificationBuilder extends Phobject {
|
|||
|
||||
return $null_view;
|
||||
}
|
||||
|
||||
public function buildDict() {
|
||||
$stories = $this->parseStories();
|
||||
$dict = array();
|
||||
|
||||
foreach ($stories as $story) {
|
||||
if ($story instanceof PhabricatorApplicationTransactionFeedStory) {
|
||||
$dict[] = array(
|
||||
'desktopReady' => true,
|
||||
'title' => $story->renderText(),
|
||||
'body' => $story->renderTextBody(),
|
||||
'href' => $story->getURI(),
|
||||
'icon' => $story->getImageURI(),
|
||||
);
|
||||
} else if ($story instanceof PhabricatorNotificationTestFeedStory) {
|
||||
$dict[] = array(
|
||||
'desktopReady' => true,
|
||||
'title' => pht('Test Notification'),
|
||||
'body' => $story->renderText(),
|
||||
'href' => null,
|
||||
'icon' => PhabricatorUser::getDefaultProfileImageURI(),
|
||||
);
|
||||
} else {
|
||||
$dict[] = array(
|
||||
'desktopReady' => false,
|
||||
'title' => null,
|
||||
'body' => null,
|
||||
'href' => null,
|
||||
'icon' => null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $dict;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,10 +33,17 @@ final class PhabricatorNotificationIndividualController
|
|||
|
||||
$builder = new PhabricatorNotificationBuilder(array($story));
|
||||
$content = $builder->buildView()->render();
|
||||
$dict = $builder->buildDict();
|
||||
$data = $dict[0];
|
||||
|
||||
$response = array(
|
||||
'pertinent' => true,
|
||||
'primaryObjectPHID' => $story->getPrimaryObjectPHID(),
|
||||
'desktopReady' => $data['desktopReady'],
|
||||
'href' => $data['href'],
|
||||
'icon' => $data['icon'],
|
||||
'title' => $data['title'],
|
||||
'body' => $data['body'],
|
||||
'content' => hsprintf('%s', $content),
|
||||
);
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ final class PhabricatorNotificationTestController
|
|||
$request = $this->getRequest();
|
||||
$viewer = $request->getUser();
|
||||
|
||||
$story_type = 'PhabricatorNotificationAdHocFeedStory';
|
||||
$story_type = 'PhabricatorNotificationTestFeedStory';
|
||||
$story_data = array(
|
||||
'title' => pht(
|
||||
'This is a test notification, sent at %s.',
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorNotificationAdHocFeedStory extends PhabricatorFeedStory {
|
||||
final class PhabricatorNotificationTestFeedStory extends PhabricatorFeedStory {
|
||||
|
||||
public function getPrimaryObjectPHID() {
|
||||
return $this->getAuthorPHID();
|
|
@ -0,0 +1,159 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorDesktopNotificationsSettingsPanel
|
||||
extends PhabricatorSettingsPanel {
|
||||
|
||||
public function isEnabled() {
|
||||
return PhabricatorEnv::getEnvConfig('notification.enabled') &&
|
||||
PhabricatorApplication::isClassInstalled(
|
||||
'PhabricatorNotificationsApplication');
|
||||
}
|
||||
|
||||
public function getPanelKey() {
|
||||
return 'desktopnotifications';
|
||||
}
|
||||
|
||||
public function getPanelName() {
|
||||
return pht('Desktop Notifications');
|
||||
}
|
||||
|
||||
public function getPanelGroup() {
|
||||
return pht('Application Settings');
|
||||
}
|
||||
|
||||
public function processRequest(AphrontRequest $request) {
|
||||
$user = $request->getUser();
|
||||
$preferences = $user->loadPreferences();
|
||||
|
||||
$pref = PhabricatorUserPreferences::PREFERENCE_DESKTOP_NOTIFICATIONS;
|
||||
|
||||
if ($request->isFormPost()) {
|
||||
$notifications = $request->getInt($pref);
|
||||
$preferences->setPreference($pref, $notifications);
|
||||
$preferences->save();
|
||||
return id(new AphrontRedirectResponse())
|
||||
->setURI($this->getPanelURI('?saved=true'));
|
||||
}
|
||||
|
||||
$title = pht('Desktop Notifications');
|
||||
$control_id = celerity_generate_unique_node_id();
|
||||
$status_id = celerity_generate_unique_node_id();
|
||||
$browser_status_id = celerity_generate_unique_node_id();
|
||||
$cancel_ask = pht(
|
||||
'The dialog asking for permission to send desktop notifications was '.
|
||||
'closed without granting permission. Only application notifications '.
|
||||
'will be sent.');
|
||||
$accept_ask = pht(
|
||||
'Click "Save Preference" to persist these changes.');
|
||||
$reject_ask = pht(
|
||||
'Permission for desktop notifications was denied. Only application '.
|
||||
'notifications will be sent.');
|
||||
$no_support = pht(
|
||||
'This web browser does not support desktop notifications. Only '.
|
||||
'application notifications will be sent for this browser regardless of '.
|
||||
'this preference.');
|
||||
$default_status = phutil_tag(
|
||||
'span',
|
||||
array(),
|
||||
array(
|
||||
pht('This browser has not yet granted permission to send desktop '.
|
||||
'notifications for this Phabricator instance.'),
|
||||
phutil_tag('br'),
|
||||
phutil_tag('br'),
|
||||
javelin_tag(
|
||||
'button',
|
||||
array(
|
||||
'sigil' => 'desktop-notifications-permission-button',
|
||||
'class' => 'green',
|
||||
),
|
||||
pht('Grant Permission')),
|
||||
));
|
||||
$granted_status = phutil_tag(
|
||||
'span',
|
||||
array(),
|
||||
pht('This browser has been granted permission to send desktop '.
|
||||
'notifications for this Phabricator instance.'));
|
||||
$denied_status = phutil_tag(
|
||||
'span',
|
||||
array(),
|
||||
pht('This browser has denied permission to send desktop notifications '.
|
||||
'for this Phabricator instance. Consult your browser settings / '.
|
||||
'documentation to figure out how to clear this setting, do so, '.
|
||||
'and then re-visit this page to grant permission.'));
|
||||
$status_box = id(new PHUIInfoView())
|
||||
->setSeverity(PHUIInfoView::SEVERITY_NOTICE)
|
||||
->setID($status_id)
|
||||
->setIsHidden(true)
|
||||
->appendChild($accept_ask);
|
||||
|
||||
$control_config = array(
|
||||
'controlID' => $control_id,
|
||||
'statusID' => $status_id,
|
||||
'browserStatusID' => $browser_status_id,
|
||||
'defaultMode' => 0,
|
||||
'desktopMode' => 1,
|
||||
'cancelAsk' => $cancel_ask,
|
||||
'grantedAsk' => $accept_ask,
|
||||
'deniedAsk' => $reject_ask,
|
||||
'defaultStatus' => $default_status,
|
||||
'deniedStatus' => $denied_status,
|
||||
'grantedStatus' => $granted_status,
|
||||
'noSupport' => $no_support,
|
||||
);
|
||||
|
||||
$form = id(new AphrontFormView())
|
||||
->setUser($user)
|
||||
->appendChild(
|
||||
id(new AphrontFormSelectControl())
|
||||
->setLabel($title)
|
||||
->setControlID($control_id)
|
||||
->setName($pref)
|
||||
->setValue($preferences->getPreference($pref))
|
||||
->setOptions(
|
||||
array(
|
||||
1 => pht('Send Desktop Notifications Too'),
|
||||
0 => pht('Send Application Notifications Only'),
|
||||
))
|
||||
->setCaption(
|
||||
pht(
|
||||
'Should Phabricator send desktop notifications? These are sent '.
|
||||
'in addition to the notifications within the Phabricator '.
|
||||
'application.'))
|
||||
->initBehavior(
|
||||
'desktop-notifications-control',
|
||||
$control_config))
|
||||
->appendChild(
|
||||
id(new AphrontFormSubmitControl())
|
||||
->setValue(pht('Save Preference')));
|
||||
|
||||
$test_icon = id(new PHUIIconView())
|
||||
->setIconFont('fa-exclamation-triangle');
|
||||
$test_button = id(new PHUIButtonView())
|
||||
->setTag('a')
|
||||
->setWorkflow(true)
|
||||
->setText(pht('Send Test Notification'))
|
||||
->setHref('/notification/test/')
|
||||
->setIcon($test_icon);
|
||||
|
||||
$form_box = id(new PHUIObjectBoxView())
|
||||
->setHeader(
|
||||
id(new PHUIHeaderView())
|
||||
->setHeader(pht('Desktop Notifications'))
|
||||
->addActionLink($test_button))
|
||||
->setForm($form)
|
||||
->setInfoView($status_box)
|
||||
->setFormSaved($request->getBool('saved'));
|
||||
|
||||
$browser_status_box = id(new PHUIInfoView())
|
||||
->setID($browser_status_id)
|
||||
->setSeverity(PHUIInfoView::SEVERITY_NOTICE)
|
||||
->setIsHidden(true)
|
||||
->appendChild($default_status);
|
||||
|
||||
return array(
|
||||
$form_box,
|
||||
$browser_status_box,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -38,6 +38,8 @@ final class PhabricatorUserPreferences extends PhabricatorUserDAO {
|
|||
const PREFERENCE_CONPH_NOTIFICATIONS = 'conph-notifications';
|
||||
const PREFERENCE_CONPHERENCE_COLUMN = 'conpherence-column';
|
||||
|
||||
const PREFERENCE_DESKTOP_NOTIFICATIONS = 'desktop-notifications';
|
||||
|
||||
// These are in an unusual order for historic reasons.
|
||||
const MAILTAG_PREFERENCE_NOTIFY = 0;
|
||||
const MAILTAG_PREFERENCE_EMAIL = 1;
|
||||
|
|
|
@ -116,6 +116,35 @@ class PhabricatorApplicationTransactionFeedStory
|
|||
return $text;
|
||||
}
|
||||
|
||||
public function renderTextBody() {
|
||||
$all_bodies = '';
|
||||
$new_target = PhabricatorApplicationTransaction::TARGET_TEXT;
|
||||
$xaction_phids = $this->getValue('transactionPHIDs');
|
||||
foreach ($xaction_phids as $xaction_phid) {
|
||||
$secondary_xaction = $this->getObject($xaction_phid);
|
||||
$old_target = $secondary_xaction->getRenderingTarget();
|
||||
$secondary_xaction->setRenderingTarget($new_target);
|
||||
$secondary_xaction->setHandles($this->getHandles());
|
||||
|
||||
$body = $secondary_xaction->getBodyForMail();
|
||||
if (nonempty($body)) {
|
||||
$all_bodies .= $body."\n";
|
||||
}
|
||||
$secondary_xaction->setRenderingTarget($old_target);
|
||||
}
|
||||
return trim($all_bodies);
|
||||
}
|
||||
|
||||
public function getImageURI() {
|
||||
$author_phid = $this->getPrimaryTransaction()->getAuthorPHID();
|
||||
return $this->getHandle($author_phid)->getImageURI();
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
$handle = $this->getHandle($this->getPrimaryObjectPHID());
|
||||
return PhabricatorEnv::getProductionURI($handle->getURI());
|
||||
}
|
||||
|
||||
public function renderAsTextForDoorkeeper(
|
||||
DoorkeeperFeedStoryPublisher $publisher) {
|
||||
|
||||
|
|
|
@ -143,6 +143,7 @@ abstract class AphrontView extends Phobject
|
|||
$name,
|
||||
$config,
|
||||
$this->getDefaultResourceSource());
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ final class PHUIInfoView extends AphrontView {
|
|||
private $severity;
|
||||
private $id;
|
||||
private $buttons = array();
|
||||
private $isHidden;
|
||||
|
||||
public function setTitle($title) {
|
||||
$this->title = $title;
|
||||
|
@ -34,6 +35,11 @@ final class PHUIInfoView extends AphrontView {
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function setIsHidden($bool) {
|
||||
$this->isHidden = $bool;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addButton(PHUIButtonView $button) {
|
||||
|
||||
$this->buttons[] = $button;
|
||||
|
@ -112,6 +118,7 @@ final class PHUIInfoView extends AphrontView {
|
|||
array(
|
||||
'id' => $this->id,
|
||||
'class' => $classes,
|
||||
'style' => $this->isHidden ? 'display: none;' : null,
|
||||
),
|
||||
array(
|
||||
$buttons,
|
||||
|
|
|
@ -54,6 +54,10 @@ final class PHUIFeedStoryView extends AphrontView {
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function getImage() {
|
||||
return $this->image;
|
||||
}
|
||||
|
||||
public function setImageHref($image_href) {
|
||||
$this->imageHref = $image_href;
|
||||
return $this;
|
||||
|
|
|
@ -75,6 +75,12 @@ JX.behavior('aphlict-listen', function(config) {
|
|||
// Show the notification itself.
|
||||
new JX.Notification()
|
||||
.setContent(JX.$H(response.content))
|
||||
.setDesktopReady(response.desktopReady)
|
||||
.setKey(response.primaryObjectPHID)
|
||||
.setTitle(response.title)
|
||||
.setBody(response.body)
|
||||
.setHref(response.href)
|
||||
.setIcon(response.icon)
|
||||
.show();
|
||||
|
||||
// If the notification affected an object on this page, show a
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
/**
|
||||
* @provides javelin-behavior-desktop-notifications-control
|
||||
* @requires javelin-behavior
|
||||
* javelin-stratcom
|
||||
* javelin-dom
|
||||
* javelin-uri
|
||||
* phabricator-notification
|
||||
*/
|
||||
|
||||
JX.behavior('desktop-notifications-control', function(config, statics) {
|
||||
|
||||
function findEl(id) {
|
||||
var el = null;
|
||||
try {
|
||||
el = JX.$(id);
|
||||
} catch (e) {
|
||||
// not found
|
||||
}
|
||||
return el;
|
||||
}
|
||||
function updateFormStatus(permission) {
|
||||
var statusEl = findEl(config.statusID);
|
||||
if (!statusEl) {
|
||||
return;
|
||||
}
|
||||
switch (permission) {
|
||||
case 'default':
|
||||
JX.DOM.setContent(statusEl.firstChild, config.cancelAsk);
|
||||
break;
|
||||
case 'granted':
|
||||
JX.DOM.setContent(statusEl.firstChild, config.grantedAsk);
|
||||
break;
|
||||
case 'denied':
|
||||
JX.DOM.setContent(statusEl.firstChild, config.deniedAsk);
|
||||
break;
|
||||
}
|
||||
JX.DOM.show(statusEl);
|
||||
}
|
||||
|
||||
function updateBrowserStatus(permission) {
|
||||
var browserStatusEl = findEl(config.browserStatusID);
|
||||
if (!browserStatusEl) {
|
||||
return;
|
||||
}
|
||||
switch (permission) {
|
||||
case 'default':
|
||||
JX.DOM.alterClass(browserStatusEl, 'phui-info-severity-notice', true);
|
||||
JX.DOM.alterClass(browserStatusEl, 'phui-info-severity-success', false);
|
||||
JX.DOM.alterClass(browserStatusEl, 'phui-info-severity-error', false);
|
||||
JX.DOM.setContent(browserStatusEl, JX.$H(config.defaultStatus));
|
||||
break;
|
||||
case 'granted':
|
||||
JX.DOM.alterClass(browserStatusEl, 'phui-info-severity-success', true);
|
||||
JX.DOM.alterClass(browserStatusEl, 'phui-info-severity-notice', false);
|
||||
JX.DOM.alterClass(browserStatusEl, 'phui-info-severity-error', false);
|
||||
JX.DOM.setContent(browserStatusEl, JX.$H(config.grantedStatus));
|
||||
break;
|
||||
case 'denied':
|
||||
JX.DOM.alterClass(browserStatusEl, 'phui-info-severity-error', true);
|
||||
JX.DOM.alterClass(browserStatusEl, 'phui-info-severity-notice', false);
|
||||
JX.DOM.alterClass(browserStatusEl, 'phui-info-severity-success', false);
|
||||
JX.DOM.setContent(browserStatusEl, JX.$H(config.deniedStatus));
|
||||
break;
|
||||
}
|
||||
JX.DOM.show(browserStatusEl);
|
||||
}
|
||||
|
||||
function installSelectListener() {
|
||||
var controlEl = findEl(config.controlID);
|
||||
if (!controlEl) {
|
||||
return;
|
||||
}
|
||||
var select = JX.DOM.find(controlEl, 'select');
|
||||
JX.DOM.listen(
|
||||
select,
|
||||
'change',
|
||||
null,
|
||||
function (e) {
|
||||
if (!JX.Notification.supportsDesktopNotifications()) {
|
||||
return;
|
||||
}
|
||||
var value = e.getTarget().value;
|
||||
if (value == config.desktopMode) {
|
||||
window.Notification.requestPermission(
|
||||
function (permission) {
|
||||
updateFormStatus(permission);
|
||||
updateBrowserStatus(permission);
|
||||
});
|
||||
} else {
|
||||
var statusEl = JX.$(config.statusID);
|
||||
JX.DOM.hide(statusEl);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function install() {
|
||||
JX.Stratcom.listen(
|
||||
'click',
|
||||
'desktop-notifications-permission-button',
|
||||
function () {
|
||||
window.Notification.requestPermission(
|
||||
function (permission) {
|
||||
updateFormStatus(permission);
|
||||
updateBrowserStatus(permission);
|
||||
});
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
statics.installed = statics.installed || install();
|
||||
if (!JX.Notification.supportsDesktopNotifications()) {
|
||||
var statusEl = JX.$(config.statusID);
|
||||
JX.DOM.setContent(statusEl.firstChild, config.noSupport);
|
||||
JX.DOM.show(statusEl);
|
||||
} else {
|
||||
updateBrowserStatus(window.Notification.permission);
|
||||
}
|
||||
installSelectListener();
|
||||
});
|
|
@ -26,15 +26,43 @@ JX.install('Notification', {
|
|||
_visible : false,
|
||||
_hideTimer : null,
|
||||
_duration : 12000,
|
||||
_desktopReady : false,
|
||||
_key : null,
|
||||
_title : null,
|
||||
_body : null,
|
||||
_href : null,
|
||||
_icon : null,
|
||||
|
||||
show : function() {
|
||||
var self = JX.Notification;
|
||||
if (!this._visible) {
|
||||
this._visible = true;
|
||||
|
||||
var self = JX.Notification;
|
||||
self._show(this);
|
||||
this._updateTimer();
|
||||
}
|
||||
|
||||
if (self.supportsDesktopNotifications() &&
|
||||
self.desktopNotificationsEnabled() &&
|
||||
this._desktopReady) {
|
||||
// Note: specifying "tag" means that notifications with matching
|
||||
// keys will aggregate.
|
||||
var n = new window.Notification(this._title, {
|
||||
icon: this._icon,
|
||||
body: this._body,
|
||||
tag: this._key,
|
||||
});
|
||||
n.onclick = JX.bind(n, function (href) {
|
||||
this.close();
|
||||
window.focus();
|
||||
if (href) {
|
||||
JX.$U(href).go();
|
||||
}
|
||||
}, this._href);
|
||||
// Note: some OS / browsers do this automagically; make the behavior
|
||||
// happen everywhere.
|
||||
setTimeout(n.close.bind(n), this._duration);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
|
@ -59,6 +87,36 @@ JX.install('Notification', {
|
|||
return this;
|
||||
},
|
||||
|
||||
setDesktopReady : function(ready) {
|
||||
this._desktopReady = ready;
|
||||
return this;
|
||||
},
|
||||
|
||||
setTitle : function(title) {
|
||||
this._title = title;
|
||||
return this;
|
||||
},
|
||||
|
||||
setBody : function(body) {
|
||||
this._body = body;
|
||||
return this;
|
||||
},
|
||||
|
||||
setHref : function(href) {
|
||||
this._href = href;
|
||||
return this;
|
||||
},
|
||||
|
||||
setKey : function(key) {
|
||||
this._key = key;
|
||||
return this;
|
||||
},
|
||||
|
||||
setIcon : function(icon) {
|
||||
this._icon = icon;
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set duration before the notification fades away, in milliseconds. If set
|
||||
* to 0, the notification persists until dismissed.
|
||||
|
@ -97,6 +155,12 @@ JX.install('Notification', {
|
|||
},
|
||||
|
||||
statics : {
|
||||
supportsDesktopNotifications : function () {
|
||||
return 'Notification' in window;
|
||||
},
|
||||
desktopNotificationsEnabled : function () {
|
||||
return window.Notification.permission === 'granted';
|
||||
},
|
||||
_container : null,
|
||||
_listening : false,
|
||||
_active : [],
|
||||
|
|
Loading…
Reference in a new issue