Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F15480119
D13219.id.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
29 KB
Referenced Files
None
Subscribers
None
D13219.id.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' => '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
Details
Attached
Mime Type
text/plain
Expires
Wed, Apr 9, 11:55 AM (2 w, 2 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7376181
Default Alt Text
D13219.id.diff (29 KB)
Attached To
Mode
D13219: Desktop Notification support
Attached
Detach File
Event Timeline
Log In to Comment