Page MenuHomePhabricator

D13219.diff
No OneTemporary

D13219.diff

diff --git a/resources/celerity/map.php b/resources/celerity/map.php
--- a/resources/celerity/map.php
+++ b/resources/celerity/map.php
@@ -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 @@
'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 @@
'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 @@
'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 @@
'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 @@
'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 @@
'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 @@
'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 @@
'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 @@
'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 @@
'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',
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -1789,6 +1789,7 @@
'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 @@
'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 @@
'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 @@
'PhabricatorDatabaseSetupCheck' => 'PhabricatorSetupCheck',
'PhabricatorDateTimeSettingsPanel' => 'PhabricatorSettingsPanel',
'PhabricatorDebugController' => 'PhabricatorController',
+ 'PhabricatorDesktopNotificationsSettingsPanel' => 'PhabricatorSettingsPanel',
'PhabricatorDestructionEngine' => 'Phobject',
'PhabricatorDeveloperConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorDeveloperPreferencesSettingsPanel' => 'PhabricatorSettingsPanel',
@@ -5773,7 +5775,6 @@
'PhabricatorNamedQueryQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorNavigationRemarkupRule' => 'PhutilRemarkupRule',
'PhabricatorNeverTriggerClock' => 'PhabricatorTriggerClock',
- 'PhabricatorNotificationAdHocFeedStory' => 'PhabricatorFeedStory',
'PhabricatorNotificationBuilder' => 'Phobject',
'PhabricatorNotificationClearController' => 'PhabricatorNotificationController',
'PhabricatorNotificationClient' => 'Phobject',
@@ -5787,6 +5788,7 @@
'PhabricatorNotificationStatusController' => 'PhabricatorNotificationController',
'PhabricatorNotificationStatusView' => 'AphrontTagView',
'PhabricatorNotificationTestController' => 'PhabricatorNotificationController',
+ 'PhabricatorNotificationTestFeedStory' => 'PhabricatorFeedStory',
'PhabricatorNotificationUIExample' => 'PhabricatorUIExample',
'PhabricatorNotificationsApplication' => 'PhabricatorApplication',
'PhabricatorNuanceApplication' => 'PhabricatorApplication',
diff --git a/src/applications/notification/builder/PhabricatorNotificationBuilder.php b/src/applications/notification/builder/PhabricatorNotificationBuilder.php
--- a/src/applications/notification/builder/PhabricatorNotificationBuilder.php
+++ b/src/applications/notification/builder/PhabricatorNotificationBuilder.php
@@ -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 @@
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 @@
$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 @@
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;
+ }
}
diff --git a/src/applications/notification/controller/PhabricatorNotificationIndividualController.php b/src/applications/notification/controller/PhabricatorNotificationIndividualController.php
--- a/src/applications/notification/controller/PhabricatorNotificationIndividualController.php
+++ b/src/applications/notification/controller/PhabricatorNotificationIndividualController.php
@@ -33,10 +33,17 @@
$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),
);
diff --git a/src/applications/notification/controller/PhabricatorNotificationTestController.php b/src/applications/notification/controller/PhabricatorNotificationTestController.php
--- a/src/applications/notification/controller/PhabricatorNotificationTestController.php
+++ b/src/applications/notification/controller/PhabricatorNotificationTestController.php
@@ -7,7 +7,7 @@
$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.',
diff --git a/src/applications/notification/feed/PhabricatorNotificationAdHocFeedStory.php b/src/applications/notification/feed/PhabricatorNotificationTestFeedStory.php
rename from src/applications/notification/feed/PhabricatorNotificationAdHocFeedStory.php
rename to src/applications/notification/feed/PhabricatorNotificationTestFeedStory.php
--- a/src/applications/notification/feed/PhabricatorNotificationAdHocFeedStory.php
+++ b/src/applications/notification/feed/PhabricatorNotificationTestFeedStory.php
@@ -1,6 +1,6 @@
<?php
-final class PhabricatorNotificationAdHocFeedStory extends PhabricatorFeedStory {
+final class PhabricatorNotificationTestFeedStory extends PhabricatorFeedStory {
public function getPrimaryObjectPHID() {
return $this->getAuthorPHID();
diff --git a/src/applications/settings/panel/PhabricatorDesktopNotificationsSettingsPanel.php b/src/applications/settings/panel/PhabricatorDesktopNotificationsSettingsPanel.php
new file mode 100644
--- /dev/null
+++ b/src/applications/settings/panel/PhabricatorDesktopNotificationsSettingsPanel.php
@@ -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,
+ );
+ }
+
+}
diff --git a/src/applications/settings/storage/PhabricatorUserPreferences.php b/src/applications/settings/storage/PhabricatorUserPreferences.php
--- a/src/applications/settings/storage/PhabricatorUserPreferences.php
+++ b/src/applications/settings/storage/PhabricatorUserPreferences.php
@@ -38,6 +38,8 @@
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;
diff --git a/src/applications/transactions/feed/PhabricatorApplicationTransactionFeedStory.php b/src/applications/transactions/feed/PhabricatorApplicationTransactionFeedStory.php
--- a/src/applications/transactions/feed/PhabricatorApplicationTransactionFeedStory.php
+++ b/src/applications/transactions/feed/PhabricatorApplicationTransactionFeedStory.php
@@ -116,6 +116,35 @@
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) {
diff --git a/src/view/AphrontView.php b/src/view/AphrontView.php
--- a/src/view/AphrontView.php
+++ b/src/view/AphrontView.php
@@ -143,6 +143,7 @@
$name,
$config,
$this->getDefaultResourceSource());
+ return $this;
}
diff --git a/src/view/form/PHUIInfoView.php b/src/view/form/PHUIInfoView.php
--- a/src/view/form/PHUIInfoView.php
+++ b/src/view/form/PHUIInfoView.php
@@ -13,6 +13,7 @@
private $severity;
private $id;
private $buttons = array();
+ private $isHidden;
public function setTitle($title) {
$this->title = $title;
@@ -34,6 +35,11 @@
return $this;
}
+ public function setIsHidden($bool) {
+ $this->isHidden = $bool;
+ return $this;
+ }
+
public function addButton(PHUIButtonView $button) {
$this->buttons[] = $button;
@@ -112,6 +118,7 @@
array(
'id' => $this->id,
'class' => $classes,
+ 'style' => $this->isHidden ? 'display: none;' : null,
),
array(
$buttons,
diff --git a/src/view/phui/PHUIFeedStoryView.php b/src/view/phui/PHUIFeedStoryView.php
--- a/src/view/phui/PHUIFeedStoryView.php
+++ b/src/view/phui/PHUIFeedStoryView.php
@@ -54,6 +54,10 @@
return $this;
}
+ public function getImage() {
+ return $this->image;
+ }
+
public function setImageHref($image_href) {
$this->imageHref = $image_href;
return $this;
diff --git a/webroot/rsrc/js/application/aphlict/behavior-aphlict-listen.js b/webroot/rsrc/js/application/aphlict/behavior-aphlict-listen.js
--- a/webroot/rsrc/js/application/aphlict/behavior-aphlict-listen.js
+++ b/webroot/rsrc/js/application/aphlict/behavior-aphlict-listen.js
@@ -75,6 +75,12 @@
// 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
diff --git a/webroot/rsrc/js/application/aphlict/behavior-desktop-notifications-control.js b/webroot/rsrc/js/application/aphlict/behavior-desktop-notifications-control.js
new file mode 100644
--- /dev/null
+++ b/webroot/rsrc/js/application/aphlict/behavior-desktop-notifications-control.js
@@ -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();
+});
diff --git a/webroot/rsrc/js/core/Notification.js b/webroot/rsrc/js/core/Notification.js
--- a/webroot/rsrc/js/core/Notification.js
+++ b/webroot/rsrc/js/core/Notification.js
@@ -26,15 +26,43 @@
_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 @@
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 @@
},
statics : {
+ supportsDesktopNotifications : function () {
+ return 'Notification' in window;
+ },
+ desktopNotificationsEnabled : function () {
+ return window.Notification.permission === 'granted';
+ },
_container : null,
_listening : false,
_active : [],

File Metadata

Mime Type
text/plain
Expires
Sun, Jun 9, 7:35 AM (2 w, 3 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6270301
Default Alt Text
D13219.diff (29 KB)

Event Timeline