@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Improve the implementation of Notifications

Summary:
Currently, you can't change a notification that's already shown. There's no reason for this.

(I'm planning to put file upload progress/errors in notifications.)

- Make `setContent()` and `setDuration()` immediately affect the notification.
- When there are more than 5 notifications, queue them up instead of dropping them.
- Allow arbitrarily many classes to be added/removed.
- Make the examples in the UIExamples tests more rich.

Test Plan:
- Verified normal notifications continue to function as expected.
- Played with the UIExamples notifications:
- Verified the "update every second" notification udpated every second.
- Verified the permanent alert notification was yellow and requires a click to dismiss.
- Verified the interactive notification responds correctly to "OK" / "Cancel".
- Verified the "click every 2 seconds" notification doesn't vanish until not clicked for 2 seconds.

Reviewers: btrahan, vrana

Reviewed By: btrahan

CC: aran

Differential Revision: https://secure.phabricator.com/D3653

+123 -72
+3 -3
src/__celerity_resource_map__.php
··· 888 888 ), 889 889 'javelin-behavior-aphlict-listen' => 890 890 array( 891 - 'uri' => '/res/0743d3f3/rsrc/js/application/aphlict/behavior-aphlict-listen.js', 891 + 'uri' => '/res/6dde3f43/rsrc/js/application/aphlict/behavior-aphlict-listen.js', 892 892 'type' => 'js', 893 893 'requires' => 894 894 array( ··· 1522 1522 ), 1523 1523 'javelin-behavior-phabricator-notification-example' => 1524 1524 array( 1525 - 'uri' => '/res/df97e4b3/rsrc/js/application/uiexample/notification-example.js', 1525 + 'uri' => '/res/a6d51998/rsrc/js/application/uiexample/notification-example.js', 1526 1526 'type' => 'js', 1527 1527 'requires' => 1528 1528 array( ··· 2499 2499 ), 2500 2500 'phabricator-notification' => 2501 2501 array( 2502 - 'uri' => '/res/cacd79f1/rsrc/js/application/core/Notification.js', 2502 + 'uri' => '/res/c604fbbe/rsrc/js/application/core/Notification.js', 2503 2503 'type' => 'js', 2504 2504 'requires' => 2505 2505 array(
+2 -2
webroot/rsrc/js/application/aphlict/behavior-aphlict-listen.js
··· 35 35 36 36 new JX.Notification() 37 37 .setContent('(Aphlict) [' + type + '] ' + details) 38 - .setClassName('jx-notification-debug') 38 + .alterClassName('jx-notification-debug', true) 39 39 .setDuration(0) 40 40 .show(); 41 41 } ··· 63 63 !showing_reload) { 64 64 var reload = new JX.Notification() 65 65 .setContent('Page updated, click to reload.') 66 - .setClassName('jx-notification-alert') 66 + .alterClassName('jx-notification-alert', true) 67 67 .setDuration(0); 68 68 reload.listen('activate', function(e) { JX.$U().go(); }) 69 69 reload.show();
+73 -47
webroot/rsrc/js/application/core/Notification.js
··· 8 8 */ 9 9 10 10 /** 11 - * Show a notification. Usage: 11 + * Show a notification popup on screen. Usage: 12 12 * 13 13 * var n = new JX.Notification() 14 14 * .setContent('click me!'); ··· 21 21 events : ['activate', 'close'], 22 22 23 23 members : { 24 + _container : null, 25 + _visible : false, 26 + _hideTimer : null, 27 + _duration : 12000, 28 + 24 29 show : function() { 25 - var self = JX.Notification; 26 - self._show(this); 30 + if (!this._visible) { 31 + this._visible = true; 27 32 28 - if (this.getDuration()) { 29 - setTimeout(JX.bind(self, self._hide, this), this.getDuration()); 33 + var self = JX.Notification; 34 + self._show(this); 35 + this._updateTimer(); 30 36 } 37 + return this; 31 38 }, 32 - _render : function() { 33 - return JX.$N( 34 - 'div', 35 - { 36 - className: 'jx-notification ' + this.getClassName(), 37 - sigil: 'jx-notification' 38 - }, 39 - this.getContent()); 40 - } 41 - }, 42 39 43 - properties : { 40 + hide : function() { 41 + if (this._visible) { 42 + this._visible = false; 44 43 45 - /** 46 - * Optional class name(s) to add to the rendered notification. 47 - * 48 - * @param string Class name(s). 49 - */ 50 - className : null, 44 + var self = JX.Notification; 45 + self._hide(this); 46 + this._updateTimer(); 47 + } 48 + return this; 49 + }, 51 50 52 - /** 53 - * Notification content. 54 - * 55 - * @param mixed Content. 56 - */ 57 - content : null, 51 + alterClassName : function(name, enable) { 52 + JX.DOM.alterClass(this._getContainer(), name, enable); 53 + return this; 54 + }, 55 + 56 + setContent : function(content) { 57 + JX.DOM.setContent(this._getContainer(), content); 58 + return this; 59 + }, 58 60 59 61 /** 60 - * Duration before the notification fades away, in milliseconds. If set to 61 - * 0, the notification persists until dismissed. 62 + * Set duration before the notification fades away, in milliseconds. If set 63 + * to 0, the notification persists until dismissed. 62 64 * 63 65 * @param int Notification duration, in milliseconds. 66 + * @return this 64 67 */ 65 - duration : 12000 68 + setDuration : function(milliseconds) { 69 + this._duration = milliseconds; 70 + this._updateTimer(false); 71 + return this; 72 + }, 73 + 74 + _updateTimer : function() { 75 + if (this._hideTimer) { 76 + clearTimeout(this._hideTimer); 77 + this._hideTimer = null; 78 + } 66 79 80 + if (this._visible && this._duration) { 81 + this._hideTimer = setTimeout(JX.bind(this, this.hide), this._duration); 82 + } 83 + }, 84 + 85 + _getContainer : function() { 86 + if (!this._container) { 87 + this._container = JX.$N( 88 + 'div', 89 + { 90 + className: 'jx-notification', 91 + sigil: 'jx-notification' 92 + }); 93 + } 94 + return this._container; 95 + } 67 96 }, 68 97 69 98 statics : { ··· 74 103 var self = JX.Notification; 75 104 76 105 self._installListener(); 77 - self._active.push({ 78 - object: notification, 79 - render: notification._render() 80 - }); 81 - 82 - // Don't show more than a few notifications at once because it's silly. 83 - while (self._active.length > 5) { 84 - self._hide(self._active[0].object); 85 - } 86 - 106 + self._active.push(notification); 87 107 self._redraw(); 88 108 }, 89 109 _hide : function(notification) { 90 110 var self = JX.Notification; 91 111 92 112 for (var ii = 0; ii < self._active.length; ii++) { 93 - if (self._active[ii].object === notification) { 113 + if (self._active[ii] === notification) { 94 114 notification.invoke('close'); 95 115 self._active.splice(ii, 1); 96 116 break; ··· 113 133 'jx-notification', 114 134 function(e) { 115 135 // NOTE: Don't kill the event since the user might have clicked a 116 - // link, and we want to follow the link if they did. Istead, invoke 136 + // link, and we want to follow the link if they did. Instead, invoke 117 137 // the activate event for the active notification and dismiss it if it 118 138 // isn't handled. 119 139 120 140 var target = e.getNode('jx-notification'); 121 141 for (var ii = 0; ii < self._active.length; ii++) { 122 142 var n = self._active[ii]; 123 - if (n.render === target) { 124 - var activation = n.object.invoke('activate'); 143 + if (n._getContainer() === target) { 144 + var activation = n.invoke('activate'); 125 145 if (!activation.getPrevented()) { 126 - self._hide(n.object); 146 + n.hide(); 127 147 } 128 148 return; 129 149 } ··· 151 171 document.body.appendChild(self._container); 152 172 } 153 173 174 + // Show only a limited number of notifications at once. 175 + var limit = 5; 176 + 154 177 var notifications = []; 155 178 for (var ii = 0; ii < self._active.length; ii++) { 156 - notifications.push(self._active[ii].render); 179 + notifications.push(self._active[ii]._getContainer()); 180 + if (!(--limit)) { 181 + break; 182 + } 157 183 } 158 184 159 185 JX.DOM.setContent(self._container, notifications);
+45 -20
webroot/rsrc/js/application/uiexample/notification-example.js
··· 7 7 */ 8 8 9 9 JX.behavior('phabricator-notification-example', function(config) { 10 + 11 + var sequence = 0; 12 + 10 13 JX.Stratcom.listen( 11 14 'click', 12 15 'notification-example', ··· 14 17 e.kill(); 15 18 16 19 var notification = new JX.Notification(); 17 - if (Math.random() > 0.1) { 18 - notification.setContent('It is ' + new Date().toString()); 20 + switch (sequence % 4) { 21 + case 0: 22 + var update = function() { 23 + notification.setContent('It is ' + new Date().toString()); 24 + }; 25 + 26 + update(); 27 + setInterval(update, 1000); 28 + 29 + break; 30 + case 1: 31 + notification 32 + .setContent('Permanent alert notification (until clicked).') 33 + .setDuration(0) 34 + .alterClassName('jx-notification-alert', true); 35 + break; 36 + case 2: 37 + notification 38 + .setContent('This notification reacts when you click it.'); 19 39 20 - notification.listen( 21 - 'activate', 22 - function(e) { 23 - if (!confirm("Close notification?")) { 24 - e.kill(); 25 - } 26 - }); 27 - } else { 28 - notification 29 - .setContent('Alert! Click to reload!') 30 - .setDuration(0) 31 - .setClassName('jx-notification-alert'); 40 + notification.listen( 41 + 'activate', 42 + function() { 43 + if (!confirm("Close notification?")) { 44 + JX.Stratcom.context().kill(); 45 + } 46 + }); 47 + break; 48 + case 3: 49 + notification 50 + .setDuration(2000) 51 + .setContent('This notification will close after 2 seconds ' + 52 + 'unless you keep clicking it!'); 32 53 33 - notification.listen( 34 - 'activate', 35 - function(e) { 36 - new JX.$U().go(); 37 - }); 54 + notification.listen( 55 + 'activate', 56 + function() { 57 + notification.setDuration(2000); 58 + JX.Stratcom.context().kill(); 59 + }); 60 + break; 38 61 } 39 - notification.show() 62 + 63 + notification.show(); 64 + sequence++; 40 65 }); 41 66 42 67 });