Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F14690973
D13219.id31956.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
21 KB
Referenced Files
None
Subscribers
None
D13219.id31956.diff
View Options
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' => 'd7ecac6d',
- 'core.pkg.js' => '288f6571',
+ 'core.pkg.js' => '0bf99194',
'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' => 'cc7af32b',
'rsrc/js/application/aphlict/behavior-aphlict-status.js' => 'ea681761',
+ 'rsrc/js/application/aphlict/behavior-desktop-notifications-control.js' => 'ae4f02d9',
'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' => '902410d2',
'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' => 'cc7af32b',
'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' => 'ae4f02d9',
'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' => '902410d2',
'phabricator-notification-css' => '9c279160',
'phabricator-notification-menu-css' => '3c9d8aa1',
'phabricator-object-selector-css' => '029a133d',
@@ -901,13 +903,6 @@
'javelin-dom',
'javelin-router',
),
- '0c6946e7' => array(
- 'javelin-install',
- 'javelin-dom',
- 'javelin-stratcom',
- 'javelin-util',
- 'phabricator-notification-css',
- ),
'0f764c35' => array(
'javelin-install',
'javelin-util',
@@ -1536,6 +1531,13 @@
'javelin-dom',
'javelin-stratcom',
),
+ '902410d2' => array(
+ 'javelin-install',
+ 'javelin-dom',
+ 'javelin-stratcom',
+ 'javelin-util',
+ 'phabricator-notification-css',
+ ),
93568464 => array(
'javelin-behavior',
'javelin-dom',
@@ -1675,27 +1677,20 @@
'javelin-stratcom',
'javelin-install',
),
- 'b064af76' => array(
+ 'ae4f02d9' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
- 'javelin-request',
- 'javelin-util',
- 'phabricator-shaped-request',
+ 'javelin-uri',
+ 'phabricator-notification',
),
- 'b1a59974' => array(
+ 'b064af76' => array(
'javelin-behavior',
- 'javelin-aphlict',
'javelin-stratcom',
- 'javelin-request',
- 'javelin-uri',
'javelin-dom',
- 'javelin-json',
- 'javelin-router',
+ 'javelin-request',
'javelin-util',
- 'javelin-leader',
- 'javelin-sound',
- 'phabricator-notification',
+ 'phabricator-shaped-request',
),
'b1f0ccee' => array(
'javelin-install',
@@ -1822,6 +1817,20 @@
'javelin-stratcom',
'phabricator-phtize',
),
+ 'cc7af32b' => 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',
+ ),
'cf86d16a' => array(
'javelin-behavior',
'javelin-dom',
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
@@ -1754,6 +1754,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',
@@ -5182,6 +5183,7 @@
'PhabricatorDatabaseSetupCheck' => 'PhabricatorSetupCheck',
'PhabricatorDateTimeSettingsPanel' => 'PhabricatorSettingsPanel',
'PhabricatorDebugController' => 'PhabricatorController',
+ 'PhabricatorDesktopNotificationsSettingsPanel' => 'PhabricatorSettingsPanel',
'PhabricatorDestructionEngine' => 'Phobject',
'PhabricatorDeveloperConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorDeveloperPreferencesSettingsPanel' => 'PhabricatorSettingsPanel',
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 {
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,20 @@
return $null_view;
}
+
+ public function buildDict() {
+ $stories = $this->parseStories();
+ $dict = array();
+
+ foreach ($stories as $story) {
+ $dict[] = array(
+ 'title' => $story->renderText(),
+ 'body' => $story->renderTextBody(),
+ 'href' => $story->getURI(),
+ 'icon' => $story->getImageURI(),
+ );
+ }
+
+ 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,16 @@
$builder = new PhabricatorNotificationBuilder(array($story));
$content = $builder->buildView()->render();
+ $dict = $builder->buildDict();
+ $data = $dict[0];
$response = array(
'pertinent' => true,
'primaryObjectPHID' => $story->getPrimaryObjectPHID(),
+ 'icon' => $data['icon'],
+ 'title' => $data['title'],
+ 'body' => $data['body'],
+ 'href' => $data['href'],
'content' => hsprintf('%s', $content),
);
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,119 @@
+<?php
+
+final class PhabricatorDesktopNotificationsSettingsPanel
+ extends PhabricatorSettingsPanel {
+
+ public function isEnabled() {
+ return 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();
+ $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.');
+ $conflict_settings = pht(
+ 'Permission for desktop notifications was denied, but the preference '.
+ 'was saved to enable desktop notifications too. 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.');
+ $status_box = id(new PHUIInfoView())
+ ->setSeverity(PHUIInfoView::SEVERITY_NOTICE)
+ ->setID($status_id)
+ ->setIsHidden(true)
+ ->appendChild($accept_ask);
+
+ $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',
+ array('controlID' => $control_id,
+ 'statusID' => $status_id,
+ 'defaultMode' => 0,
+ 'desktopMode' => 1,
+ 'cancelAsk' => $cancel_ask,
+ 'grantedAsk' => $accept_ask,
+ 'deniedAsk' => $reject_ask,
+ 'noSupport' => $no_support,
+ )))
+ ->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'));
+
+ return array(
+ $form_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
@@ -37,6 +37,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() {
+ return PhabricatorEnv::getProductionURI(
+ $this->getHandle($this->getPrimaryObjectPHID())->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,11 @@
// Show the notification itself.
new JX.Notification()
.setContent(JX.$H(response.content))
+ .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,51 @@
+/**
+ * @provides javelin-behavior-desktop-notifications-control
+ * @requires javelin-behavior
+ * javelin-stratcom
+ * javelin-dom
+ * javelin-uri
+ * phabricator-notification
+ */
+
+JX.behavior('desktop-notifications-control', function(config) {
+
+ var controlEl = JX.$(config.controlID);
+ var select = JX.DOM.find(controlEl, 'select');
+ var statusEl = JX.$(config.statusID);
+
+ JX.DOM.listen(
+ select,
+ 'change',
+ null,
+ function (e) {
+ if (!JX.Notification.supportsDesktopNotifications()) {
+ return;
+ }
+ var value = e.getTarget().value;
+ if (value == config.desktopMode) {
+ Notification.requestPermission(
+ function (permission) {
+ 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);
+ });
+ } else {
+ JX.DOM.hide(statusEl);
+ }
+ });
+
+ if (!JX.Notification.supportsDesktopNotifications()) {
+ JX.DOM.setContent(statusEl.firstChild, config.noSupport);
+ JX.DOM.show(statusEl);
+ }
+
+});
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,35 @@
_visible : false,
_hideTimer : null,
_duration : 12000,
+ _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()) {
+ var n = new Notification(this._title, {
+ icon: this._icon,
+ body: this._body,
+ tag: this._key,
+ });
+ n.onclick = JX.bind(n, function () {
+ n.close();
+ JX.$U(this._href).go();
+ });
+ // Note: some OS / browsers do this automagically.
+ setTimeout(n.close.bind(n), this._duration);
+ }
return this;
},
@@ -59,6 +79,31 @@
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 +142,12 @@
},
statics : {
+ supportsDesktopNotifications : function () {
+ return 'Notification' in window;
+ },
+ desktopNotificationsEnabled : function () {
+ return window.Notification.permission === 'granted';
+ },
_container : null,
_listening : false,
_active : [],
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Wed, Jan 15, 2:28 AM (3 h, 59 m)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6993449
Default Alt Text
D13219.id31956.diff (21 KB)
Attached To
Mode
D13219: Desktop Notification support
Attached
Detach File
Event Timeline
Log In to Comment