diff --git a/CHANGES.md b/CHANGES.md index 7d7082d22..bc8f12845 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,9 +2,10 @@ ## 4.0.3 (Unreleased) -- Reduce join/leave clutter by removing subsequent ones (without text messages in between) - Bugfix. Handler not triggered when submitting MUC password form 2nd time - Bugfix. MUC features weren't being refreshed when saving the config form +- #537 Render `xmpp:` URI as link +- #1062 Collapse multiple join/leave messages into one - #1063 URLs in the topic / subject are not clickable - #1140 Add support for destroyed chatrooms - #1169 Non-joined participants display an unwanted status message diff --git a/css/converse.css b/css/converse.css index f71efa207..9b8e0c612 100644 --- a/css/converse.css +++ b/css/converse.css @@ -10770,7 +10770,9 @@ body.reset { font-style: italic; } #conversejs .message.chat-info.chat-event { clear: left; - font-style: italic; } + font-style: italic; + font-size: 90%; + padding: 0.17rem 1rem; } #conversejs .message.chat-info.chat-error { color: #D24E2B; font-weight: bold; } diff --git a/dist/converse.js b/dist/converse.js index 1d7070df0..2eff27b07 100644 --- a/dist/converse.js +++ b/dist/converse.js @@ -70015,6 +70015,31 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ } }, + getPreviousJoinOrLeaveNotification(el, nick) { + /* Working backwards, get the first join/leave notification + * from the same user, on the same day and BEFORE any chat + * messages were received. + */ + while (!_.isNil(el)) { + const data = _.get(el, 'dataset', {}); + + if (!_.includes(_.get(el, 'classList', []), 'chat-info')) { + return; + } + + if (!moment(el.getAttribute('data-isodate')).isSame(new Date(), "day")) { + el = el.previousElementSibling; + continue; + } + + if (data.join === nick || data.leave === nick || data.leavejoin === nick || data.joinleave === nick) { + return el; + } + + el = el.previousElementSibling; + } + }, + showJoinNotification(occupant) { if (this.model.get('connection_status') !== converse.ROOMSTATUS.ENTERED) { return; @@ -70022,21 +70047,30 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ const nick = occupant.get('nick'), stat = occupant.get('status'), - last_leave_el = this.getImmediateNotification(this.content.lastElementChild, nick, 'leave'); + prev_info_el = this.getPreviousJoinOrLeaveNotification(this.content.lastElementChild, nick), + data = _.get(prev_info_el, 'dataset', {}); - if (_.includes(_.get(last_leave_el, 'classList', []), 'chat-info') && _.get(last_leave_el, 'dataset', {}).leave === nick) { - let el = this.content.lastElementChild; - el.insertAdjacentElement('afterend', last_leave_el); - last_leave_el.outerHTML = tpl_info({ + if (data.leave === nick) { + let message; + + if (_.isNil(stat)) { + message = __('%1$s has left and re-entered the groupchat', nick); + } else { + message = __('%1$s has left and re-entered the groupchat. "%2$s"', nick, stat); + } + + const data = { 'data_name': 'leavejoin', 'data_value': nick, 'isodate': moment().format(), 'extra_classes': 'chat-event', - 'message': __('%1$s has left and re-entered the groupchat', nick) - }); - el = this.content.lastElementChild; + 'message': message + }; + this.content.removeChild(prev_info_el); + this.content.insertAdjacentHTML('beforeend', tpl_info(data)); + const el = this.content.lastElementChild; setTimeout(() => u.addClass('fade-out', el), 5000); - setTimeout(() => el.parentElement && el.parentElement.removeChild(el), 5250); + setTimeout(() => el.parentElement && el.parentElement.removeChild(el), 5500); } else { let message; @@ -70054,45 +70088,18 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ 'message': message }; - if (_.includes(_.get(last_leave_el, 'classList', []), 'chat-info') && _.get(last_leave_el, 'dataset', {}).joinleave === nick) { - last_leave_el.outerHTML = tpl_info(data); + if (prev_info_el) { + this.content.removeChild(prev_info_el); + this.content.insertAdjacentHTML('beforeend', tpl_info(data)); } else { - const el = u.stringToElement(tpl_info(data)); - this.content.insertAdjacentElement('beforeend', el); - this.insertDayIndicator(el); + this.content.insertAdjacentHTML('beforeend', tpl_info(data)); + this.insertDayIndicator(this.content.lastElementChild); } } this.scrollDown(); }, - getImmediateNotification(el, nick, type = 'join') { - while (!_.isNil(el)) { - const data = _.get(el, 'dataset', {}); - - if (!_.includes(_.get(el, 'classList', []), 'chat-info')) { - return; - } - - if (!moment(el.getAttribute('data-isodate')).isSame(new Date(), "day")) { - el = el.previousElementSibling; - continue; - } - - if (type === 'join') { - if (data.join === nick || data.leavejoin === nick) { - return el; - } - } else { - if (data.leave === nick || data.joinleave === nick) { - return el; - } - } - - el = el.previousElementSibling; - } - }, - showLeaveNotification(occupant) { if (_.includes(occupant.get('states'), '303') || _.includes(occupant.get('states'), '307')) { return; @@ -70100,46 +70107,30 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ const nick = occupant.get('nick'), stat = occupant.get('status'), - last_join_el = this.getImmediateNotification(this.content.lastElementChild, nick, 'join'), - data = _.get(last_join_el, 'dataset', {}); + prev_info_el = this.getPreviousJoinOrLeaveNotification(this.content.lastElementChild, nick), + dataset = _.get(prev_info_el, 'dataset', {}); - if (last_join_el) { + if (dataset.join === nick) { let message; - if (data.join === nick) { - if (_.isNil(stat)) { - message = __('%1$s has entered and left the groupchat', nick); - } else { - message = __('%1$s has entered and left the groupchat. "%2$s"', nick, stat); - } - - let el = this.content.lastElementChild; - el.insertAdjacentElement('afterend', last_join_el); - last_join_el.outerHTML = tpl_info({ - 'data_name': 'joinleave', - 'data_value': nick, - 'isodate': moment().format(), - 'extra_classes': 'chat-event', - 'message': message - }); - el = this.content.lastElementChild; - setTimeout(() => u.addClass('fade-out', el), 5000); - setTimeout(() => el.parentElement && el.parentElement.removeChild(el), 5250); - } else if (data.leavejoin === nick) { - if (_.isNil(stat)) { - message = __('%1$s has left the groupchat', nick); - } else { - message = __('%1$s has left the groupchat. "%2$s"', nick, stat); - } - - last_join_el.outerHTML = tpl_info({ - 'data_name': 'leave', - 'data_value': nick, - 'isodate': moment().format(), - 'extra_classes': 'chat-event', - 'message': message - }); + if (_.isNil(stat)) { + message = __('%1$s has entered and left the groupchat', nick); + } else { + message = __('%1$s has entered and left the groupchat. "%2$s"', nick, stat); } + + const data = { + 'data_name': 'joinleave', + 'data_value': nick, + 'isodate': moment().format(), + 'extra_classes': 'chat-event', + 'message': message + }; + this.content.removeChild(prev_info_el); + this.content.insertAdjacentHTML('beforeend', tpl_info(data)); + const el = this.content.lastElementChild; + setTimeout(() => u.addClass('fade-out', el), 5000); + setTimeout(() => el.parentElement && el.parentElement.removeChild(el), 5500); } else { let message; @@ -70156,9 +70147,14 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ 'data_name': 'leave', 'data_value': nick }; - const el = u.stringToElement(tpl_info(data)); - this.content.insertAdjacentElement('beforeend', el); - this.insertDayIndicator(el); + + if (prev_info_el) { + this.content.removeChild(prev_info_el); + this.content.insertAdjacentHTML('beforeend', tpl_info(data)); + } else { + this.content.insertAdjacentHTML('beforeend', tpl_info(data)); + this.insertDayIndicator(this.content.lastElementChild); + } } this.scrollDown(); diff --git a/sass/_messages.scss b/sass/_messages.scss index 619feddfb..60af68a56 100644 --- a/sass/_messages.scss +++ b/sass/_messages.scss @@ -48,6 +48,8 @@ &.chat-event { clear: left; font-style: italic; + font-size: 90%; + padding: 0.17rem 1rem } &.chat-error { color: $warning-color; diff --git a/src/converse-muc-views.js b/src/converse-muc-views.js index ba64df4cc..bd421c10a 100644 --- a/src/converse-muc-views.js +++ b/src/converse-muc-views.js @@ -1498,32 +1498,60 @@ } }, + getPreviousJoinOrLeaveNotification (el, nick) { + /* Working backwards, get the first join/leave notification + * from the same user, on the same day and BEFORE any chat + * messages were received. + */ + while (!_.isNil(el)) { + const data = _.get(el, 'dataset', {}); + if (!_.includes(_.get(el, 'classList', []), 'chat-info')) { + return; + } + if (!moment(el.getAttribute('data-isodate')).isSame(new Date(), "day")) { + el = el.previousElementSibling; + continue; + } + if (data.join === nick || + data.leave === nick || + data.leavejoin === nick || + data.joinleave === nick) { + return el; + } + el = el.previousElementSibling; + } + }, + showJoinNotification (occupant) { if (this.model.get('connection_status') !== converse.ROOMSTATUS.ENTERED) { return; } const nick = occupant.get('nick'), stat = occupant.get('status'), - last_leave_el = this.getImmediateNotification(this.content.lastElementChild, nick, 'leave'); + prev_info_el = this.getPreviousJoinOrLeaveNotification(this.content.lastElementChild, nick), + data = _.get(prev_info_el, 'dataset', {}); - if (_.includes(_.get(last_leave_el, 'classList', []), 'chat-info') && - _.get(last_leave_el, 'dataset', {}).leave === nick) { - - let el = this.content.lastElementChild; - el.insertAdjacentElement('afterend', last_leave_el); - last_leave_el.outerHTML = - tpl_info({ - 'data_name': 'leavejoin', - 'data_value': nick, - 'isodate': moment().format(), - 'extra_classes': 'chat-event', - 'message': __('%1$s has left and re-entered the groupchat', nick) - }); - el = this.content.lastElementChild; + if (data.leave === nick) { + let message; + if (_.isNil(stat)) { + message = __('%1$s has left and re-entered the groupchat', nick); + } else { + message = __('%1$s has left and re-entered the groupchat. "%2$s"', nick, stat); + } + const data = { + 'data_name': 'leavejoin', + 'data_value': nick, + 'isodate': moment().format(), + 'extra_classes': 'chat-event', + 'message': message + }; + this.content.removeChild(prev_info_el); + this.content.insertAdjacentHTML('beforeend', tpl_info(data)); + const el = this.content.lastElementChild; setTimeout(() => u.addClass('fade-out', el), 5000); - setTimeout(() => el.parentElement && el.parentElement.removeChild(el), 5250); + setTimeout(() => el.parentElement && el.parentElement.removeChild(el), 5500); } else { - let message; + let message; if (_.isNil(stat)) { message = __('%1$s has entered the groupchat', nick); } else { @@ -1536,87 +1564,45 @@ 'extra_classes': 'chat-event', 'message': message }; - if (_.includes(_.get(last_leave_el, 'classList', []), 'chat-info') && - _.get(last_leave_el, 'dataset', {}).joinleave === nick) { - - last_leave_el.outerHTML = tpl_info(data); + if (prev_info_el) { + this.content.removeChild(prev_info_el); + this.content.insertAdjacentHTML('beforeend', tpl_info(data)); } else { - const el = u.stringToElement(tpl_info(data)); - this.content.insertAdjacentElement('beforeend', el); - this.insertDayIndicator(el); + this.content.insertAdjacentHTML('beforeend', tpl_info(data)); + this.insertDayIndicator(this.content.lastElementChild); } } this.scrollDown(); }, - getImmediateNotification (el, nick, type='join') { - while (!_.isNil(el)) { - const data = _.get(el, 'dataset', {}); - if (!_.includes(_.get(el, 'classList', []), 'chat-info')) { - return; - } - if (!moment(el.getAttribute('data-isodate')).isSame(new Date(), "day")) { - el = el.previousElementSibling; - continue; - } - if (type === 'join') { - if (data.join === nick || data.leavejoin === nick) { - return el; - } - } else { - if (data.leave === nick || data.joinleave === nick) { - return el; - } - } - el = el.previousElementSibling; - } - }, - showLeaveNotification (occupant) { if (_.includes(occupant.get('states'), '303') || _.includes(occupant.get('states'), '307')) { return; } const nick = occupant.get('nick'), stat = occupant.get('status'), - last_join_el = this.getImmediateNotification(this.content.lastElementChild, nick, 'join'), - data = _.get(last_join_el, 'dataset', {}); + prev_info_el = this.getPreviousJoinOrLeaveNotification(this.content.lastElementChild, nick), + dataset = _.get(prev_info_el, 'dataset', {}); - if (last_join_el) { + if (dataset.join === nick) { let message; - if (data.join === nick) { - if (_.isNil(stat)) { - message = __('%1$s has entered and left the groupchat', nick); - } else { - message = __('%1$s has entered and left the groupchat. "%2$s"', nick, stat); - } - let el = this.content.lastElementChild; - el.insertAdjacentElement('afterend', last_join_el); - last_join_el.outerHTML = - tpl_info({ - 'data_name': 'joinleave', - 'data_value': nick, - 'isodate': moment().format(), - 'extra_classes': 'chat-event', - 'message': message - }); - el = this.content.lastElementChild; - setTimeout(() => u.addClass('fade-out', el), 5000); - setTimeout(() => el.parentElement && el.parentElement.removeChild(el), 5250); - } else if (data.leavejoin === nick) { - if (_.isNil(stat)) { - message = __('%1$s has left the groupchat', nick); - } else { - message = __('%1$s has left the groupchat. "%2$s"', nick, stat); - } - last_join_el.outerHTML = - tpl_info({ - 'data_name': 'leave', - 'data_value': nick, - 'isodate': moment().format(), - 'extra_classes': 'chat-event', - 'message': message - }); + if (_.isNil(stat)) { + message = __('%1$s has entered and left the groupchat', nick); + } else { + message = __('%1$s has entered and left the groupchat. "%2$s"', nick, stat); } + const data = { + 'data_name': 'joinleave', + 'data_value': nick, + 'isodate': moment().format(), + 'extra_classes': 'chat-event', + 'message': message + }; + this.content.removeChild(prev_info_el); + this.content.insertAdjacentHTML('beforeend', tpl_info(data)); + const el = this.content.lastElementChild; + setTimeout(() => u.addClass('fade-out', el), 5000); + setTimeout(() => el.parentElement && el.parentElement.removeChild(el), 5500); } else { let message; if (_.isNil(stat)) { @@ -1631,9 +1617,13 @@ 'data_name': 'leave', 'data_value': nick } - const el = u.stringToElement(tpl_info(data)); - this.content.insertAdjacentElement('beforeend', el); - this.insertDayIndicator(el); + if (prev_info_el) { + this.content.removeChild(prev_info_el); + this.content.insertAdjacentHTML('beforeend', tpl_info(data)); + } else { + this.content.insertAdjacentHTML('beforeend', tpl_info(data)); + this.insertDayIndicator(this.content.lastElementChild); + } } this.scrollDown(); },