# HG changeset patch # User Marcin Lulek # Date 2016-09-13 10:25:42 # Node ID 048ecbf3931d83807c61feddc3d920ab4b8ed721 # Parent 6387ba9865051aca35c5e04cc822673fa373cc66 frontend: introduce rhodecode-app for more complex cross element wiring diff --git a/grunt_config.json b/grunt_config.json --- a/grunt_config.json +++ b/grunt_config.json @@ -66,7 +66,6 @@ "<%= dirs.js.src %>/rhodecode/utils/topics.js", "<%= dirs.js.src %>/rhodecode/widgets/multiselect.js", "<%= dirs.js.src %>/rhodecode/init.js", - "<%= dirs.js.src %>/rhodecode/connection_controller.js", "<%= dirs.js.src %>/rhodecode/codemirror.js", "<%= dirs.js.src %>/rhodecode/comments.js", "<%= dirs.js.src %>/rhodecode/constants.js", @@ -81,7 +80,6 @@ "<%= dirs.js.src %>/rhodecode/select2_widgets.js", "<%= dirs.js.src %>/rhodecode/tooltips.js", "<%= dirs.js.src %>/rhodecode/users.js", - "<%= dirs.js.src %>/rhodecode/utils/notifications.js", "<%= dirs.js.src %>/rhodecode/appenlight.js", "<%= dirs.js.src %>/rhodecode.js" ], diff --git a/rhodecode/channelstream/__init__.py b/rhodecode/channelstream/__init__.py --- a/rhodecode/channelstream/__init__.py +++ b/rhodecode/channelstream/__init__.py @@ -29,7 +29,9 @@ from rhodecode.lib.ext_json import json def url_gen(request): urls = { 'connect': request.route_url('channelstream_connect'), - 'subscribe': request.route_url('channelstream_subscribe') + 'subscribe': request.route_url('channelstream_subscribe'), + 'longpoll': request.registry.settings.get('channelstream.longpoll_url', ''), + 'ws': request.registry.settings.get('channelstream.ws_url', '') } return json.dumps(urls) diff --git a/rhodecode/public/js/src/components/channelstream-connection/channelstream-connection.html b/rhodecode/public/js/src/components/channelstream-connection/channelstream-connection.html new file mode 100644 --- /dev/null +++ b/rhodecode/public/js/src/components/channelstream-connection/channelstream-connection.html @@ -0,0 +1,106 @@ + + + + + + + + diff --git a/rhodecode/public/js/src/components/channelstream-connection/channelstream-connection.js b/rhodecode/public/js/src/components/channelstream-connection/channelstream-connection.js new file mode 100644 --- /dev/null +++ b/rhodecode/public/js/src/components/channelstream-connection/channelstream-connection.js @@ -0,0 +1,502 @@ +Polymer({ + is: 'channelstream-connection', + + /** + * Fired when `channels` array changes. + * + * @event channelstream-channels-changed + */ + + /** + * Fired when `connect()` method succeeds. + * + * @event channelstream-connected + */ + + /** + * Fired when `connect` fails. + * + * @event channelstream-connect-error + */ + + /** + * Fired when `disconnect()` succeeds. + * + * @event channelstream-disconnected + */ + + /** + * Fired when `message()` succeeds. + * + * @event channelstream-message-sent + */ + + /** + * Fired when `message()` fails. + * + * @event channelstream-message-error + */ + + /** + * Fired when `subscribe()` succeeds. + * + * @event channelstream-subscribed + */ + + /** + * Fired when `subscribe()` fails. + * + * @event channelstream-subscribe-error + */ + + /** + * Fired when `unsubscribe()` succeeds. + * + * @event channelstream-unsubscribed + */ + + /** + * Fired when `unsubscribe()` fails. + * + * @event channelstream-unsubscribe-error + */ + + /** + * Fired when listening connection receives a message. + * + * @event channelstream-listen-message + */ + + /** + * Fired when listening connection is opened. + * + * @event channelstream-listen-opened + */ + + /** + * Fired when listening connection is closed. + * + * @event channelstream-listen-closed + */ + + /** + * Fired when listening connection suffers an error. + * + * @event channelstream-listen-error + */ + + properties: { + isReady: Boolean, + /** List of channels user should be subscribed to. */ + channels: { + type: Array, + value: function () { + return [] + }, + notify: true + }, + /** Username of connecting user. */ + username: { + type: String, + value: 'Anonymous', + reflectToAttribute: true + }, + /** Connection identifier. */ + connectionId: { + type: String, + reflectToAttribute: true + }, + /** Websocket instance. */ + websocket: { + type: Object, + value: null + }, + /** Websocket connection url. */ + websocketUrl: { + type: String, + value: '' + }, + /** URL used in `connect()`. */ + connectUrl: { + type: String, + value: '' + }, + /** URL used in `disconnect()`. */ + disconnectUrl: { + type: String, + value: '' + }, + /** URL used in `subscribe()`. */ + subscribeUrl: { + type: String, + value: '' + }, + /** URL used in `unsubscribe()`. */ + unsubscribeUrl: { + type: String, + value: '' + }, + /** URL used in `message()`. */ + messageUrl: { + type: String, + value: '' + }, + /** Long-polling connection url. */ + longPollUrl: { + type: String, + value: '' + }, + /** Long-polling connection url. */ + shouldReconnect: { + type: Boolean, + value: true + }, + /** Should send heartbeats. */ + heartbeats: { + type: Boolean, + value: true + }, + /** How much should every retry interval increase (in milliseconds) */ + increaseBounceIv: { + type: Number, + value: 2000 + }, + _currentBounceIv: { + type: Number, + reflectToAttribute: true, + value: 0 + }, + /** Should use websockets or long-polling by default */ + useWebsocket: { + type: Boolean, + reflectToAttribute: true, + value: true + }, + connected: { + type: Boolean, + reflectToAttribute: true, + value: false + } + }, + + observers: [ + '_handleChannelsChange(channels.splices)' + ], + + listeners: { + 'channelstream-connected': 'startListening', + 'channelstream-connect-error': 'retryConnection', + }, + + /** + * Mutators hold functions that you can set locally to change the data + * that the client is sending to all endpoints + * you can call it like `elem.mutators('connect', yourFunc())` + * mutators will be executed in order they were pushed onto arrays + * + */ + mutators: { + connect: function () { + return [] + }(), + message: function () { + return [] + }(), + subscribe: function () { + return [] + }(), + unsubscribe: function () { + return [] + }(), + disconnect: function () { + return [] + }() + }, + ready: function () { + this.isReady = true; + }, + + /** + * Connects user and fetches connection id from the server. + * + */ + connect: function () { + var request = this.$['ajaxConnect']; + request.url = this.connectUrl; + request.body = { + username: this.username, + channels: this.channels + }; + for (var i = 0; i < this.mutators.connect.length; i++) { + this.mutators.connect[i](request); + } + request.generateRequest() + }, + /** + * Overwrite with custom function that will + */ + addMutator: function (type, func) { + this.mutators[type].push(func); + }, + /** + * Subscribes user to channels. + * + */ + subscribe: function (channels) { + var request = this.$['ajaxSubscribe']; + request.url = this.subscribeUrl; + request.body = { + channels: channels, + conn_id: this.connectionId + }; + for (var i = 0; i < this.mutators.subscribe.length; i++) { + this.mutators.subscribe[i](request); + } + if (request.body.channels.length) { + request.generateRequest(); + } + }, + /** + * Unsubscribes user from channels. + * + */ + unsubscribe: function (unsubscribe) { + var request = this.$['ajaxUnsubscribe']; + + request.url = this.unsubscribeUrl; + request.body = { + channels: unsubscribe, + conn_id: this.connectionId + }; + for (var i = 0; i < this.mutators.unsubscribe.length; i++) { + this.mutators.unsubscribe[i](request); + } + request.generateRequest() + }, + + /** + * calculates list of channels we should add user to based on difference + * between channels property and passed channel list + */ + calculateSubscribe: function (channels) { + var currentlySubscribed = this.channels; + var toSubscribe = []; + for (var i = 0; i < channels.length; i++) { + if (currentlySubscribed.indexOf(channels[i]) === -1) { + toSubscribe.push(channels[i]); + } + } + return toSubscribe + }, + /** + * calculates list of channels we should remove user from based difference + * between channels property and passed channel list + */ + calculateUnsubscribe: function (channels) { + var currentlySubscribed = this.channels; + var toUnsubscribe = []; + for (var i = 0; i < channels.length; i++) { + if (currentlySubscribed.indexOf(channels[i]) !== -1) { + toUnsubscribe.push(channels[i]); + } + } + return toUnsubscribe + }, + /** + * Marks the connection as expired. + * + */ + disconnect: function () { + var request = this.$['ajaxDisconnect']; + request.url = this.disconnectUrl; + request.params = { + conn_id: this.connectionId + }; + for (var i = 0; i < this.mutators.disconnect.length; i++) { + this.mutators.disconnect[i](request); + } + // mark connection as expired + request.generateRequest(); + // disconnect existing connection + this.closeConnection(); + }, + + /** + * Sends a message to the server. + * + */ + message: function (message) { + var request = this.$['ajaxMessage']; + request.url = this.messageUrl; + request.body = message; + for (var i = 0; i < this.mutators.message.length; i++) { + this.mutators.message[i](request) + } + request.generateRequest(); + }, + /** + * Opens "long lived" (websocket/longpoll) connection to the channelstream server. + * + */ + startListening: function (event) { + this.fire('start-listening', {}); + if (this.useWebsocket) { + this.useWebsocket = window.WebSocket ? true : false; + } + if (this.useWebsocket) { + this.openWebsocket(); + } + else { + this.openLongPoll(); + } + }, + /** + * Opens websocket connection. + * + */ + openWebsocket: function () { + var url = this.websocketUrl + '?conn_id=' + this.connectionId; + this.websocket = new WebSocket(url); + this.websocket.onopen = this._handleListenOpen.bind(this); + this.websocket.onclose = this._handleListenCloseEvent.bind(this); + this.websocket.onerror = this._handleListenErrorEvent.bind(this); + this.websocket.onmessage = this._handleListenMessageEvent.bind(this); + }, + /** + * Opens long-poll connection. + * + */ + openLongPoll: function () { + var request = this.$['ajaxListen']; + request.url = this.longPollUrl + '?conn_id=' + this.connectionId; + request.generateRequest() + }, + /** + * Retries `connect()` call while incrementing interval between tries up to 1 minute. + * + */ + retryConnection: function () { + if (!this.shouldReconnect) { + return; + } + if (this._currentBounceIv < 60000) { + this._currentBounceIv = this._currentBounceIv + this.increaseBounceIv; + } + else { + this._currentBounceIv = 60000; + } + setTimeout(this.connect.bind(this), this._currentBounceIv); + }, + /** + * Closes listening connection. + * + */ + closeConnection: function () { + var request = this.$['ajaxListen']; + if (this.websocket && this.websocket.readyState === WebSocket.OPEN) { + this.websocket.onclose = null; + this.websocket.onerror = null; + this.websocket.close(); + } + if (request.loading) { + request.lastRequest.abort(); + } + this.connected = false; + }, + + _handleChannelsChange: function (event) { + // do not fire the event if set() didn't mutate anything + // is this a reliable way to do it? + if (!this.isReady || event === undefined) { + return + } + this.fire('channelstream-channels-changed', event) + }, + + _handleListenOpen: function (event) { + this.connected = true; + this.fire('channelstream-listen-opened', event); + this.createHeartBeats(); + }, + + createHeartBeats: function () { + if (typeof self._heartbeat === 'undefined' && this.websocket !== null + && this.heartbeats) { + self._heartbeat = setInterval(this._sendHeartBeat.bind(this), 10000); + } + }, + + _sendHeartBeat: function () { + if (this.websocket.readyState === WebSocket.OPEN && this.heartbeats) { + this.websocket.send(JSON.stringify({type: 'heartbeat'})); + } + }, + + _handleListenError: function (event) { + this.connected = false; + this.retryConnection(); + }, + _handleConnectError: function (event) { + this.connected = false; + this.fire('channelstream-connect-error', event.detail); + }, + + _handleListenMessageEvent: function (event) { + var data = null; + // comes from iron-ajax + if (event.detail) { + data = JSON.parse(event.detail.response) + // comes from websocket + setTimeout(this.openLongPoll.bind(this), 0); + } else { + data = JSON.parse(event.data) + } + this.fire('channelstream-listen-message', data); + + }, + + _handleListenCloseEvent: function (event) { + this.connected = false; + this.fire('channelstream-listen-closed', event.detail); + this.retryConnection(); + }, + + _handleListenErrorEvent: function (event) { + this.connected = false; + this.fire('channelstream-listen-error', {}) + }, + + _handleConnect: function (event) { + this.currentBounceIv = 0; + this.connectionId = event.detail.response.conn_id; + this.fire('channelstream-connected', event.detail.response); + }, + + _handleDisconnect: function (event) { + this.connected = false; + this.fire('channelstream-disconnected', {}); + }, + + _handleMessage: function (event) { + this.fire('channelstream-message-sent', event.detail.response); + }, + _handleMessageError: function (event) { + this.fire('channelstream-message-error', event.detail); + }, + + _handleSubscribe: function (event) { + this.fire('channelstream-subscribed', event.detail.response); + }, + + _handleSubscribeError: function (event) { + this.fire('channelstream-subscribe-error', event.detail); + }, + + _handleUnsubscribe: function (event) { + this.fire('channelstream-unsubscribed', event.detail.response); + }, + + _handleUnsubscribeError: function (event) { + this.fire('channelstream-unsubscribe-error', event.detail); + } +}); diff --git a/rhodecode/public/js/src/components/rhodecode-app/rhodecode-app.html b/rhodecode/public/js/src/components/rhodecode-app/rhodecode-app.html new file mode 100644 --- /dev/null +++ b/rhodecode/public/js/src/components/rhodecode-app/rhodecode-app.html @@ -0,0 +1,15 @@ + + + + + + + diff --git a/rhodecode/public/js/src/components/rhodecode-app/rhodecode-app.js b/rhodecode/public/js/src/components/rhodecode-app/rhodecode-app.js new file mode 100644 --- /dev/null +++ b/rhodecode/public/js/src/components/rhodecode-app/rhodecode-app.js @@ -0,0 +1,127 @@ +ccLog = Logger.get('RhodeCodeApp'); +ccLog.setLevel(Logger.OFF); + +var rhodeCodeApp = Polymer({ + is: 'rhodecode-app', + created: function () { + ccLog.debug('rhodeCodeApp created'); + $.Topic('/notifications').subscribe(this.handleNotifications.bind(this)); + + $.Topic('/plugins/__REGISTER__').subscribe( + this.kickoffChannelstreamPlugin.bind(this) + ); + + $.Topic('/connection_controller/subscribe').subscribe( + this.subscribeToChannelTopic.bind(this)); + }, + + /** proxy to channelstream connection */ + getChannelStreamConnection: function () { + return this.$['channelstream-connection']; + }, + + handleNotifications: function (data) { + this.$['notifications'].handleNotification(data); + }, + + /** opens connection to ws server */ + kickoffChannelstreamPlugin: function (data) { + ccLog.debug('kickoffChannelstreamPlugin'); + var channels = ['broadcast']; + var addChannels = this.checkViewChannels(); + for (var i = 0; i < addChannels.length; i++) { + channels.push(addChannels[i]); + } + var channelstreamConnection = this.$['channelstream-connection']; + channelstreamConnection.connectUrl = CHANNELSTREAM_URLS.connect; + channelstreamConnection.subscribeUrl = CHANNELSTREAM_URLS.subscribe; + channelstreamConnection.websocketUrl = CHANNELSTREAM_URLS.ws + '/ws'; + channelstreamConnection.longPollUrl = CHANNELSTREAM_URLS.longpoll + '/listen'; + // some channels might already be registered by topic + for (var i = 0; i < channels.length; i++) { + channelstreamConnection.push('channels', channels[i]); + } + // append any additional channels registered in other plugins + $.Topic('/connection_controller/subscribe').processPrepared(); + channelstreamConnection.connect(); + }, + + checkViewChannels: function () { + var channels = [] + // subscribe to PR repo channel for PR's' + if (templateContext.pull_request_data.pull_request_id) { + var channelName = '/repo$' + templateContext.repo_name + '$/pr/' + + String(templateContext.pull_request_data.pull_request_id); + channels.push(channelName); + } + return channels; + }, + + /** subscribes users from channels in channelstream */ + subscribeToChannelTopic: function (channels) { + var channelstreamConnection = this.$['channelstream-connection']; + var toSubscribe = channelstreamConnection.calculateSubscribe(channels); + ccLog.debug('subscribeToChannelTopic', toSubscribe); + if (toSubscribe.length > 0) { + // if we are connected then subscribe + if (channelstreamConnection.connected) { + channelstreamConnection.subscribe(toSubscribe); + } + // not connected? just push channels onto the stack + else { + for (var i = 0; i < toSubscribe.length; i++) { + channelstreamConnection.push('channels', toSubscribe[i]); + } + } + } + }, + + /** publish received messages into correct topic */ + receivedMessage: function (event) { + for (var i = 0; i < event.detail.length; i++) { + var message = event.detail[i]; + if (message.message.topic) { + ccLog.debug('publishing', message.message.topic); + $.Topic(message.message.topic).publish(message); + } + else if (message.type === 'presence'){ + $.Topic('/connection_controller/presence').publish(message); + } + else { + ccLog.warn('unhandled message', message); + } + } + }, + + handleConnected: function (event) { + var channelstreamConnection = this.$['channelstream-connection']; + channelstreamConnection.set('channelsState', + event.detail.channels_info); + channelstreamConnection.set('userState', event.detail.state); + channelstreamConnection.set('channels', event.detail.channels); + this.propagageChannelsState(); + }, + handleSubscribed: function (event) { + var channelstreamConnection = this.$['channelstream-connection']; + var channelInfo = event.detail.channels_info; + var channelKeys = Object.keys(event.detail.channels_info); + for (var i = 0; i < channelKeys.length; i++) { + var key = channelKeys[i]; + channelstreamConnection.set(['channelsState', key], channelInfo[key]); + } + channelstreamConnection.set('channels', event.detail.channels); + this.propagageChannelsState(); + }, + /** propagates channel states on topics */ + propagageChannelsState: function (event) { + var channelstreamConnection = this.$['channelstream-connection']; + var channel_data = channelstreamConnection.channelsState; + var channels = channelstreamConnection.channels; + for (var i = 0; i < channels.length; i++) { + var key = channels[i]; + $.Topic('/connection_controller/channel_update').publish( + {channel: key, state: channel_data[key]} + ); + } + } +}); diff --git a/rhodecode/public/js/src/components/rhodecode-toast/rhodecode-toast.js b/rhodecode/public/js/src/components/rhodecode-toast/rhodecode-toast.js --- a/rhodecode/public/js/src/components/rhodecode-toast/rhodecode-toast.js +++ b/rhodecode/public/js/src/components/rhodecode-toast/rhodecode-toast.js @@ -11,10 +11,6 @@ Polymer({ observers: [ '_changedToasts(toasts.splices)' ], - ready: function(){ - - }, - _changedToasts: function(newValue, oldValue){ this.$['p-toast'].notifyResize(); }, @@ -27,5 +23,16 @@ Polymer({ open: function(){ this.$['p-toast'].open(); }, + handleNotification: function(data){ + if (!templateContext.rhodecode_user.notification_status && !data.message.force) { + // do not act if notifications are disabled + return + } + this.push('toasts',{ + level: data.message.level, + message: data.message.message + }); + this.open(); + }, _gettext: _gettext }); diff --git a/rhodecode/public/js/src/components/shared-components.html b/rhodecode/public/js/src/components/shared-components.html --- a/rhodecode/public/js/src/components/shared-components.html +++ b/rhodecode/public/js/src/components/shared-components.html @@ -1,6 +1,8 @@ + + diff --git a/rhodecode/public/js/src/rhodecode/connection_controller.js b/rhodecode/public/js/src/rhodecode/connection_controller.js deleted file mode 100644 --- a/rhodecode/public/js/src/rhodecode/connection_controller.js +++ /dev/null @@ -1,208 +0,0 @@ -"use strict"; -/** leak object to top level scope **/ -var ccLog = undefined; -// global code-mirror logger;, to enable run -// Logger.get('ConnectionController').setLevel(Logger.DEBUG) -ccLog = Logger.get('ConnectionController'); -ccLog.setLevel(Logger.OFF); - -var ConnectionController; -var connCtrlr; -var registerViewChannels; - -(function () { - ConnectionController = function (webappUrl, serverUrl, urls) { - var self = this; - - var channels = ['broadcast']; - this.state = { - open: false, - webappUrl: webappUrl, - serverUrl: serverUrl, - connId: null, - socket: null, - channels: channels, - heartbeat: null, - channelsInfo: {}, - urls: urls - }; - - this.listen = function () { - if (window.WebSocket) { - ccLog.debug('attempting to create socket'); - var socket_url = self.state.serverUrl + "/ws?conn_id=" + self.state.connId; - var socket_conf = { - url: socket_url, - handleAs: 'json', - headers: { - "Accept": "application/json", - "Content-Type": "application/json" - } - }; - self.state.socket = new WebSocket(socket_conf.url); - - self.state.socket.onopen = function (event) { - ccLog.debug('open event', event); - if (self.state.heartbeat === null) { - self.state.heartbeat = setInterval(function () { - if (self.state.socket.readyState === WebSocket.OPEN) { - self.state.socket.send('heartbeat'); - } - }, 10000) - } - }; - self.state.socket.onmessage = function (event) { - var data = $.parseJSON(event.data); - for (var i = 0; i < data.length; i++) { - if (data[i].message.topic) { - ccLog.debug('publishing', - data[i].message.topic, data[i]); - $.Topic(data[i].message.topic).publish(data[i]) - } - else { - ccLog.warn('unhandled message', data); - } - } - }; - self.state.socket.onclose = function (event) { - ccLog.debug('closed event', event); - setTimeout(function () { - self.connect(true); - }, 5000); - }; - - self.state.socket.onerror = function (event) { - ccLog.debug('error event', event); - }; - } - else { - ccLog.debug('attempting to create long polling connection'); - var poolUrl = self.state.serverUrl + "/listen?conn_id=" + self.state.connId; - self.state.socket = $.ajax({ - url: poolUrl - }).done(function (data) { - ccLog.debug('data', data); - var data = $.parseJSON(data); - for (var i = 0; i < data.length; i++) { - if (data[i].message.topic) { - ccLog.info('publishing', - data[i].message.topic, data[i]); - $.Topic(data[i].message.topic).publish(data[i]) - } - else { - ccLog.warn('unhandled message', data); - } - } - self.listen(); - }).fail(function () { - ccLog.debug('longpoll error'); - setTimeout(function () { - self.connect(true); - }, 5000); - }); - } - - }; - - this.connect = function (create_new_socket) { - var connReq = {'channels': self.state.channels}; - ccLog.debug('try obtaining connection info', connReq); - $.ajax({ - url: self.state.urls.connect, - type: "POST", - contentType: "application/json", - data: JSON.stringify(connReq), - dataType: "json" - }).done(function (data) { - ccLog.debug('Got connection:', data.conn_id); - self.state.channels = data.channels; - self.state.channelsInfo = data.channels_info; - self.state.connId = data.conn_id; - if (create_new_socket) { - self.listen(); - } - self.update(); - }).fail(function () { - setTimeout(function () { - self.connect(create_new_socket); - }, 5000); - }); - self.update(); - }; - - this.subscribeToChannels = function (channels) { - var new_channels = []; - for (var i = 0; i < channels.length; i++) { - var channel = channels[i]; - if (self.state.channels.indexOf(channel)) { - self.state.channels.push(channel); - new_channels.push(channel) - } - } - /** - * only execute the request if socket is present because subscribe - * can actually add channels before initial app connection - **/ - if (new_channels && self.state.socket !== null) { - var connReq = { - 'channels': self.state.channels, - 'conn_id': self.state.connId - }; - $.ajax({ - url: self.state.urls.subscribe, - type: "POST", - contentType: "application/json", - data: JSON.stringify(connReq), - dataType: "json" - }).done(function (data) { - self.state.channels = data.channels; - self.state.channelsInfo = data.channels_info; - self.update(); - }); - } - self.update(); - }; - - this.update = function () { - for (var key in this.state.channelsInfo) { - if (this.state.channelsInfo.hasOwnProperty(key)) { - // update channels with latest info - $.Topic('/connection_controller/channel_update').publish( - {channel: key, state: this.state.channelsInfo[key]}); - } - } - }; - - this.run = function () { - this.connect(true); - }; - - $.Topic('/connection_controller/subscribe').subscribe( - self.subscribeToChannels); - }; - - $.Topic('/plugins/__REGISTER__').subscribe(function (data) { - if (window.CHANNELSTREAM_SETTINGS && window.CHANNELSTREAM_SETTINGS.enabled) { - connCtrlr = new ConnectionController( - CHANNELSTREAM_SETTINGS.webapp_location, - CHANNELSTREAM_SETTINGS.ws_location, - CHANNELSTREAM_URLS - ); - registerViewChannels(); - - $(document).ready(function () { - connCtrlr.run(); - }); - } - }); - -registerViewChannels = function (){ - // subscribe to PR repo channel for PR's' - if (templateContext.pull_request_data.pull_request_id) { - var channelName = '/repo$' + templateContext.repo_name + '$/pr/' + - String(templateContext.pull_request_data.pull_request_id); - connCtrlr.state.channels.push(channelName); - } -} - -})(); diff --git a/rhodecode/public/js/src/rhodecode/utils/notifications.js b/rhodecode/public/js/src/rhodecode/utils/notifications.js deleted file mode 100644 --- a/rhodecode/public/js/src/rhodecode/utils/notifications.js +++ /dev/null @@ -1,47 +0,0 @@ -"use strict"; - - -function notifySystem(data) { - var notification = new Notification(data.message.level + ': ' + data.message.message); -}; - -function notifyToaster(data){ - var notifications = document.getElementById('notifications'); - notifications.push('toasts', - { level: data.message.level, - message: data.message.message - }); - notifications.open(); -} - -function handleNotifications(data) { - if (!templateContext.rhodecode_user.notification_status && !data.message.force) { - // do not act if notifications are disabled - return - } - // use only js notifications for now - var onlyJS = true; - if (!("Notification" in window) || onlyJS) { - // use legacy notificartion - notifyToaster(data); - } - else { - // Let's check whether notification permissions have already been granted - if (Notification.permission === "granted") { - notifySystem(data); - } - // Otherwise, we need to ask the user for permission - else if (Notification.permission !== 'denied') { - Notification.requestPermission(function (permission) { - if (permission === "granted") { - notifySystem(data); - } - }); - } - else{ - notifyToaster(data); - } - } -}; - -$.Topic('/notifications').subscribe(handleNotifications); diff --git a/rhodecode/public/js/topics_list.txt b/rhodecode/public/js/topics_list.txt --- a/rhodecode/public/js/topics_list.txt +++ b/rhodecode/public/js/topics_list.txt @@ -1,4 +1,7 @@ /plugins/__REGISTER__ - launched after the onDomReady() code from rhodecode.js is executed /ui/plugins/code/anchor_focus - launched when rc starts to scroll on load to anchor on PR/Codeview /ui/plugins/code/comment_form_built - launched when injectInlineForm() is executed and the form object is created -/notifications - shows new event notifications \ No newline at end of file +/notifications - shows new event notifications +/connection_controller/subscribe - subscribes user to new channels +/connection_controller/presence - receives presence change messages +/connection_controller/channel_update - receives channel states diff --git a/rhodecode/templates/base/root.html b/rhodecode/templates/base/root.html --- a/rhodecode/templates/base/root.html +++ b/rhodecode/templates/base/root.html @@ -146,7 +146,9 @@ c.template_context['visual']['default_re <%def name="head_extra()"> ${self.head_extra()} ## extra stuff %if c.pre_code: @@ -174,6 +176,6 @@ c.template_context['visual']['default_re %if c.post_code: ${c.post_code|n} %endif - +