diff --git a/CHANGES.md b/CHANGES.md index 245974f54..2c7bb340e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,6 +10,7 @@ - #1823: New config options [muc_roomid_policy](https://conversejs.org/docs/html/configuration.html#muc-roomid-policy) and [muc_roomid_policy_hint](https://conversejs.org/docs/html/configuration.html#muc-roomid-policy-hint) - #1826: A user can now add himself as a contact +- #1839: Headline messages are shown in controlbox ## 6.0.0 (2020-01-09) diff --git a/sass/_controlbox.scss b/sass/_controlbox.scss index 9572b1575..67e8f1855 100644 --- a/sass/_controlbox.scss +++ b/sass/_controlbox.scss @@ -230,6 +230,10 @@ color: var(--chat-head-color-dark); } + .controlbox-heading--headline { + color: var(--headline-head-color); + } + .controlbox-heading__btn { cursor: pointer; font-size: 1em; diff --git a/spec/headline.js b/spec/headline.js index 7d93f49aa..78242449a 100644 --- a/spec/headline.js +++ b/spec/headline.js @@ -87,6 +87,79 @@ done(); })); + it("will show headline messages in the controlbox", mock.initConverse( + ['rosterGroupsFetched'], {}, async function (done, _converse) { + + /* + * SIEVE + * <juliet@example.com> You got mail. + * + * + * imap://romeo@example.com/INBOX;UIDVALIDITY=385759043/;UID=18 + * + * + * + */ + const stanza = $msg({ + 'type': 'headline', + 'from': 'notify.example.com', + 'to': 'romeo@montague.lit', + 'xml:lang': 'en' + }) + .c('subject').t('SIEVE').up() + .c('body').t('<juliet@example.com> You got mail.').up() + .c('x', {'xmlns': 'jabber:x:oob'}) + .c('url').t('imap://romeo@example.com/INBOX;UIDVALIDITY=385759043/;UID=18'); + + _converse.connection._dataRecv(test_utils.createRequest(stanza)); + const view = _converse.chatboxviews.get('controlbox'); + await u.waitUntil(() => view.el.querySelectorAll(".open-headline").length); + expect(view.el.querySelectorAll('.open-headline').length).toBe(1); + expect(view.el.querySelector('.open-headline').text).toBe('notify.example.com'); + done(); + })); + + it("will remove headline messages from the controlbox if closed", mock.initConverse( + ['rosterGroupsFetched'], {}, async function (done, _converse) { + + /* + * SIEVE + * <juliet@example.com> You got mail. + * + * + * imap://romeo@example.com/INBOX;UIDVALIDITY=385759043/;UID=18 + * + * + * + */ + const stanza = $msg({ + 'type': 'headline', + 'from': 'notify.example.com', + 'to': 'romeo@montague.lit', + 'xml:lang': 'en' + }) + .c('subject').t('SIEVE').up() + .c('body').t('<juliet@example.com> You got mail.').up() + .c('x', {'xmlns': 'jabber:x:oob'}) + .c('url').t('imap://romeo@example.com/INBOX;UIDVALIDITY=385759043/;UID=18'); + + _converse.connection._dataRecv(test_utils.createRequest(stanza)); + const cbview = _converse.chatboxviews.get('controlbox'); + await u.waitUntil(() => cbview.el.querySelectorAll(".open-headline").length); + const hlview = _converse.chatboxviews.get('notify.example.com'); + const close_el = hlview.el.querySelector('.close-chatbox-button'); + close_el.click(); + await u.waitUntil(() => cbview.el.querySelectorAll(".open-headline").length === 0); + expect(cbview.el.querySelectorAll('.open-headline').length).toBe(0); + done(); + })); + it("will not show a headline messages from a full JID if allow_non_roster_messaging is false", mock.initConverse( ['rosterGroupsFetched', 'chatBoxesFetched'], {}, function (done, _converse) { diff --git a/src/headless/converse-headlines.js b/src/headless/converse-headlines.js index 2ff059326..06aabf5a6 100644 --- a/src/headless/converse-headlines.js +++ b/src/headless/converse-headlines.js @@ -9,8 +9,10 @@ import "converse-chatview"; import converse from "@converse/headless/converse-core"; import { isString } from "lodash"; +import tpl_headline_list from "templates/headline_list.html"; +import tpl_headline_panel from "templates/headline_panel.html"; -const { utils } = converse.env; +const { Backbone, utils } = converse.env; converse.plugins.add('converse-headlines', { @@ -42,7 +44,13 @@ converse.plugins.add('converse-headlines', { return this.__super__.model.apply(this, arguments); } }, - } + }, + ControlBoxView: { + renderControlBoxPane () { + this.__super__.renderControlBoxPane.apply(this, arguments); + this.renderHeadlinePanel(); + }, + }, }, @@ -50,7 +58,33 @@ converse.plugins.add('converse-headlines', { /* The initialize function gets called as soon as the plugin is * loaded by converse.js's plugin machinery. */ - const { _converse } = this; + const { _converse } = this, + { __ } = _converse; + + const viewWithHeadlinePanel = { + renderHeadlinePanel () { + if (this.headlinepanel && utils.isInDOM(this.headlinepanel.el)) { + return this.headlinepanel; + } + this.headlinepanel = new _converse.HeadlinePanel(); + this.el.querySelector('.controlbox-pane').insertAdjacentElement( + 'beforeEnd', this.headlinepanel.render().el); + + return this.headlinepanel; + }, + + getHeadlinePanel () { + if (this.headlinepanel && utils.isInDOM(this.headlinepanel.el)) { + return this.headlinepanel; + } else { + return this.renderHeadlinePanel(); + } + } + } + + if (_converse.ControlBoxView) { + Object.assign(_converse.ControlBoxView.prototype, viewWithHeadlinePanel); + } /** * Shows headline messages @@ -83,6 +117,26 @@ converse.plugins.add('converse-headlines', { } }); + function getAllHeadlineBoxes (removedBox = null) { + const chatboxviews = _converse.chatboxviews.getAll(); + return Object.keys(chatboxviews) + .filter( + id => chatboxviews[id].el.classList.contains('headlines') && + id !== removedBox + ); + } + + function renderHeadlineList (removedBox = null) { + const controlboxview = _converse.chatboxviews.get('controlbox'); + if (controlboxview !== undefined) { + const el = controlboxview.el.querySelector('.list-container--headline'); + const headline_list = tpl_headline_list({ + 'headlineboxes': getAllHeadlineBoxes(removedBox), + 'open_title': __('Click to open this server message'), + }); + el && (el.outerHTML = headline_list); + } + } async function onHeadlineMessage (message) { // Handler method for all incoming messages of type "headline". @@ -106,9 +160,45 @@ converse.plugins.add('converse-headlines', { const attrs = await chatbox.getMessageAttributesFromStanza(message, message); await chatbox.messages.create(attrs); _converse.api.trigger('message', {'chatbox': chatbox, 'stanza': message}); + renderHeadlineList(); + _converse.chatboxviews.get(from_jid).el + .querySelector('.close-chatbox-button') + .addEventListener("click", + () => renderHeadlineList(from_jid) + ); } } + /** + * Backbone.NativeView which renders headlines section of the control box. + * @class + * @namespace _converse.HeadlinePanel + * @memberOf _converse + */ + _converse.HeadlinePanel = Backbone.NativeView.extend({ + tagName: 'div', + className: 'controlbox-section', + id: 'headline', + + events: { + 'click .open-headline': 'openHeadline' + }, + + openHeadline (ev) { + ev.preventDefault(); + const jid = ev.target.getAttribute('data-headline-jid'); + const chat = _converse.chatboxes.get(jid); + chat.maybeShow(true); + }, + + render () { + this.el.innerHTML = tpl_headline_panel({ + 'heading_headline': __('Server Messages') + }); + return this; + } + }); + /************************ BEGIN Event Handlers ************************/ function registerHeadlineHandler () { diff --git a/src/templates/headline_list.html b/src/templates/headline_list.html new file mode 100644 index 000000000..a7b81a5f4 --- /dev/null +++ b/src/templates/headline_list.html @@ -0,0 +1,12 @@ +
+
+ {[o.headlineboxes.forEach(function (headline) { ]} + + {[ }) ]} +
+
diff --git a/src/templates/headline_panel.html b/src/templates/headline_panel.html new file mode 100644 index 000000000..9dc7f4e3f --- /dev/null +++ b/src/templates/headline_panel.html @@ -0,0 +1,6 @@ + +
+ {{{o.heading_headline}}} +
+
+