diff --git a/client/css/style.css b/client/css/style.css index e517fe36..13b70968 100644 --- a/client/css/style.css +++ b/client/css/style.css @@ -194,6 +194,7 @@ kbd { #chat .title:before, #footer .icon, #chat .count:before, +#settings .extra-help, #settings #play:before, #form #submit:before, #chat .invite .from:before, @@ -317,6 +318,10 @@ kbd { line-height: 50px; } +#settings .extra-help:before { + content: "\f059"; /* http://fontawesome.io/icon/question-circle/ */ +} + #settings #play:before { content: "\f028"; /* http://fontawesome.io/icon/volume-up/ */ margin-right: 9px; @@ -801,6 +806,31 @@ kbd { display: block; } +#chat .condensed { + flex-wrap: wrap; +} + +#chat .condensed .content { + flex: 1; +} + +#chat .condensed-text { + cursor: pointer; + transition: opacity .2s; +} + +#chat .condensed-text:hover { + opacity: .6; +} + +#chat .condensed-text .toggle-button:hover { + opacity: 1; +} + +#chat .condensed.closed .msg { + display: none; +} + #windows .header .topic, .messages .msg, .sidebar { @@ -1092,15 +1122,17 @@ kbd { color: #555; } -#chat.hide-join .join, -#chat.hide-mode .mode, -#chat.hide-motd .motd, -#chat.hide-nick .nick, -#chat.hide-part .part, -#chat.hide-quit .quit { +#chat.hide-status-messages .join, +#chat.hide-status-messages .mode, +#chat.hide-status-messages .nick, +#chat.hide-status-messages .part, +#chat.hide-status-messages .quit, +#chat.hide-status-messages .condensed, +#chat.hide-motd .motd { display: none !important; } +#chat .condensed .content, #chat .join .content, #chat .kick .content, #chat .mode .content, @@ -1138,19 +1170,14 @@ kbd { #chat .toggle-button { display: inline-block; - color: #666; - transition: color .2s, transform .2s; + transition: opacity .2s, transform .2s; } -#chat .toggle-button.opened { +#chat .toggle-button.opened, /* Thumbnail toggle */ +#chat .msg.condensed:not(.closed) .toggle-button { /* Expanded status message toggle */ transform: rotate(90deg); } -#chat .toggle-button:hover { - /* transform and opacity together glitch, so need to use RGBA transition */ - color: rgba(102, 102, 102, .8); /* #666 x .8 opacity */ -} - #chat .toggle-content { background: #f5f5f5; border-radius: 2px; @@ -1361,18 +1388,26 @@ part/quit messages where we don't load previews (adds a blank line otherwise) */ #settings .opt { display: block; - padding: 5px 0 10px 1px; + padding: 5px 0 5px 1px; } #settings .opt input { - float: left; - margin: 4px 10px 0 0; + margin-right: 6px; } +#settings .extra-help, #settings #play { color: #7f8c8d; } +#settings .extra-help { + cursor: help; +} + +#settings h2 .extra-help { + font-size: .8em; +} + #settings #play { font-size: 14px; transition: opacity .2s; @@ -1694,6 +1729,16 @@ part/quit messages where we don't load previews (adds a blank line otherwise) */ animation-delay: .4s; } +.tooltipped-no-delay:hover:before, +.tooltipped-no-delay:hover:after, +.tooltipped-no-delay:active:before, +.tooltipped-no-delay:active:after, +.tooltipped-no-delay:focus:before, +.tooltipped-no-delay:focus:after { + -webkit-animation-delay: 0s; + animation-delay: 0s; +} + .tooltipped-s:after, .tooltipped-se:after, .tooltipped-sw:after { @@ -2130,3 +2175,7 @@ part/quit messages where we don't load previews (adds a blank line otherwise) */ #chat table.channel-list .topic { white-space: pre-wrap; } + +.hide-text { + color: transparent !important; +} diff --git a/client/index.html b/client/index.html index d8d39618..09baadb5 100644 --- a/client/index.html +++ b/client/index.html @@ -216,48 +216,40 @@

Messages

-
- -
-
- -
-
- -
-
- -
-
- -
+
+

+ Status messages + + + +

+
+
+ + + +

Visual Aids

diff --git a/client/js/condensed.js b/client/js/condensed.js new file mode 100644 index 00000000..a7119d1f --- /dev/null +++ b/client/js/condensed.js @@ -0,0 +1,55 @@ +"use strict"; + +const constants = require("./constants"); +const templates = require("../views"); + +module.exports = { + updateText +}; + +function updateText(condensed, addedTypes) { + const obj = {}; + + constants.condensedTypes.forEach((type) => { + obj[type] = condensed.data(type) || 0; + }); + + addedTypes.forEach((type) => { + obj[type]++; + condensed.data(type, obj[type]); + }); + + const strings = []; + constants.condensedTypes.forEach((type) => { + if (obj[type]) { + switch (type) { + case "join": + strings.push(obj[type] + (obj[type] > 1 ? " users have joined the channel" : " user has joined the channel")); + break; + case "part": + strings.push(obj[type] + (obj[type] > 1 ? " users have left the channel" : " user has left the channel")); + break; + case "quit": + strings.push(obj[type] + (obj[type] > 1 ? " users have quit" : " user has quit")); + break; + case "nick": + strings.push(obj[type] + (obj[type] > 1 ? " users have changed nick" : " user has changed nick")); + break; + case "kick": + strings.push(obj[type] + (obj[type] > 1 ? " users were kicked" : " user was kicked")); + break; + case "mode": + strings.push(obj[type] + (obj[type] > 1 ? " modes were set" : " mode was set")); + break; + } + } + }); + + let text = strings.pop(); + if (strings.length) { + text = strings.join(", ") + ", and " + text; + } + + condensed.find(".condensed-text") + .html(text + templates.msg_condensed_toggle()); +} diff --git a/client/js/constants.js b/client/js/constants.js index f95fbd00..878f0276 100644 --- a/client/js/constants.js +++ b/client/js/constants.js @@ -56,6 +56,32 @@ const commands = [ "/whois" ]; +const actionTypes = [ + "ban_list", + "invite", + "join", + "mode", + "kick", + "nick", + "part", + "quit", + "topic", + "topic_set_by", + "action", + "whois", + "ctcp", + "channel_list", +]; + +const condensedTypes = [ + "join", + "part", + "quit", + "nick", + "kick", + "mode", +]; + const timeFormats = { msgDefault: "HH:mm", msgWithSeconds: "HH:mm:ss" @@ -63,6 +89,8 @@ const timeFormats = { module.exports = { colorCodeMap: colorCodeMap, - timeFormats: timeFormats, - commands: commands + commands: commands, + condensedTypes: condensedTypes, + actionTypes: actionTypes, + timeFormats: timeFormats }; diff --git a/client/js/lounge.js b/client/js/lounge.js index 3322d10e..23e8dd5e 100644 --- a/client/js/lounge.js +++ b/client/js/lounge.js @@ -439,6 +439,10 @@ $(function() { } }); + chat.on("click", ".condensed-text", function() { + $(this).closest(".msg.condensed").toggleClass("closed"); + }); + chat.on("click", ".user", function() { var name = $(this).data("name"); var chan = findCurrentNetworkChan(name); @@ -707,6 +711,9 @@ $(function() { chat.on("click", ".show-more-button", function() { var self = $(this); var lastMessage = self.parent().next(".messages").children(".msg").first(); + if (lastMessage.is(".condensed")) { + lastMessage = lastMessage.children(".msg").first(); + } var lastMessageId = parseInt(lastMessage[0].id.replace("msg-", ""), 10); self diff --git a/client/js/options.js b/client/js/options.js index cd38b37f..4e0d83a6 100644 --- a/client/js/options.js +++ b/client/js/options.js @@ -9,25 +9,29 @@ const tz = require("./libs/handlebars/tz"); const windows = $("#windows"); const chat = $("#chat"); -const options = $.extend({ +// Default options +const options = { + autocomplete: true, coloredNicks: true, desktopNotifications: false, - join: true, + highlights: [], links: true, - mode: true, motd: true, - nick: true, notification: true, notifyAllMessages: false, - part: true, - quit: true, showSeconds: false, + statusMessages: "condensed", theme: $("#theme").attr("href").replace(/^themes\/(.*).css$/, "$1"), // Extracts default theme name, set on the server configuration thumbnails: true, userStyles: userStyles.text(), - highlights: [], - autocomplete: true -}, JSON.parse(storage.get("settings"))); +}; +const userOptions = JSON.parse(storage.get("settings")) || {}; + +for (const key in options) { + if (userOptions[key]) { + options[key] = userOptions[key]; + } +} module.exports = options; @@ -43,6 +47,9 @@ for (var i in options) { settings.find("#user-specified-css-input").val(options[i]); } else if (i === "highlights") { settings.find("input[name=" + i + "]").val(options[i]); + } else if (i === "statusMessages") { + settings.find(`input[name=${i}][value=${options[i]}]`) + .prop("checked", true); } else if (i === "theme") { $("#theme").attr("href", "themes/" + options[i] + ".css"); settings.find("select[name=" + i + "]").val(options[i]); @@ -58,6 +65,10 @@ settings.on("change", "input, select, textarea", function() { if (type === "password") { return; + } else if (type === "radio") { + if (self.prop("checked")) { + options[name] = self.val(); + } } else if (type === "checkbox") { options[name] = self.prop("checked"); } else { @@ -66,16 +77,10 @@ settings.on("change", "input, select, textarea", function() { storage.set("settings", JSON.stringify(options)); - if ([ - "join", - "mode", - "motd", - "nick", - "part", - "quit", - "notifyAllMessages", - ].indexOf(name) !== -1) { + if (name === "motd") { chat.toggleClass("hide-" + name, !self.prop("checked")); + } else if (name === "statusMessages") { + chat.toggleClass("hide-status-messages", options[name] === "hidden"); } else if (name === "coloredNicks") { chat.toggleClass("colored-nicks", self.prop("checked")); } else if (name === "theme") { diff --git a/client/js/render.js b/client/js/render.js index e3cca373..246e62b2 100644 --- a/client/js/render.js +++ b/client/js/render.js @@ -6,11 +6,14 @@ const options = require("./options"); const renderPreview = require("./renderPreview"); const utils = require("./utils"); const sorting = require("./sorting"); +const constants = require("./constants"); +const condense = require("./condensed"); const chat = $("#chat"); const sidebar = $("#sidebar"); module.exports = { + appendMessage, buildChannelMessages, buildChatMessage, renderChannel, @@ -19,16 +22,39 @@ module.exports = { renderNetworks, }; -function buildChannelMessages(channel, messages) { - return messages.reduce(function(docFragment, message) { - docFragment.append(buildChatMessage({ - chan: channel, +function buildChannelMessages(data) { + return data.messages.reduce(function(docFragment, message) { + appendMessage(docFragment, data.id, data.type, message.type, buildChatMessage({ + chan: data.id, msg: message })); return docFragment; }, $(document.createDocumentFragment())); } +function appendMessage(container, chan, chanType, messageType, msg) { + if (constants.condensedTypes.indexOf(messageType) !== -1 && chanType !== "lobby") { + var condensedTypesClasses = "." + constants.condensedTypes.join(", ."); + var lastChild = container.children("div.msg").last(); + var lastDate = (new Date(lastChild.attr("data-time"))).toDateString(); + var msgDate = (new Date(msg.attr("data-time"))).toDateString(); + if (lastChild && $(lastChild).hasClass("condensed") && !$(msg).hasClass("message") && lastDate === msgDate) { + lastChild.append(msg); + condense.updateText(lastChild, [messageType]); + } else if (lastChild && $(lastChild).is(condensedTypesClasses) && options.statusMessages === "condensed") { + var condensed = buildChatMessage({msg: {type: "condensed", time: msg.attr("data-time"), previews: []}, chan: chan}); + condensed.append(lastChild); + condensed.append(msg); + container.append(condensed); + condense.updateText(condensed, [messageType, lastChild.attr("data-type")]); + } else { + container.append(msg); + } + } else { + container.append(msg); + } +} + function buildChatMessage(data) { const type = data.msg.type; let target = "#chan-" + data.chan; @@ -45,25 +71,13 @@ function buildChatMessage(data) { data.msg.highlight = true; } - if ([ - "invite", - "join", - "mode", - "kick", - "nick", - "part", - "quit", - "topic", - "topic_set_by", - "action", - "whois", - "ctcp", - "channel_list", - "ban_list", - ].indexOf(type) !== -1) { + if (constants.actionTypes.indexOf(type) !== -1) { + data.msg.template = "actions/" + type; template = "msg_action"; } else if (type === "unhandled") { template = "msg_unhandled"; + } else if (type === "condensed") { + template = "msg_condensed"; } const msg = $(templates[template](data.msg)); @@ -100,7 +114,7 @@ function renderChannel(data) { } function renderChannelMessages(data) { - const documentFragment = buildChannelMessages(data.id, data.messages); + const documentFragment = buildChannelMessages(data); const channel = chat.find("#chan-" + data.id + " .messages").append(documentFragment); if (data.firstUnread > 0) { @@ -109,6 +123,8 @@ function renderChannelMessages(data) { // TODO: If the message is far off in the history, we still need to append the marker into DOM if (!first.length) { channel.prepend(templates.unread_marker()); + } else if (first.parent().hasClass("condensed")) { + first.parent().before(templates.unread_marker()); } else { first.before(templates.unread_marker()); } @@ -129,6 +145,10 @@ function renderChannelMessages(data) { } if (lastDate.toDateString() !== msgDate.toDateString()) { + var parent = msg.parent(); + if (parent.hasClass("condensed")) { + msg.insertAfter(parent); + } msg.before(templates.date_marker({msgDate: msgDate})); } diff --git a/client/js/renderPreview.js b/client/js/renderPreview.js index 3f09ff44..14edb400 100644 --- a/client/js/renderPreview.js +++ b/client/js/renderPreview.js @@ -47,7 +47,7 @@ function renderPreview(preview, msg) { container.trigger("keepToBottom"); } -$("#chat").on("click", ".toggle-button", function() { +$("#chat").on("click", ".text .toggle-button", function() { const self = $(this); const container = self.closest(".chat"); const content = self.closest(".content") diff --git a/client/js/socket-events/more.js b/client/js/socket-events/more.js index b8029934..8640a883 100644 --- a/client/js/socket-events/more.js +++ b/client/js/socket-events/more.js @@ -7,7 +7,7 @@ const chat = $("#chat"); const templates = require("../../views"); socket.on("more", function(data) { - const documentFragment = render.buildChannelMessages(data.chan, data.messages); + const documentFragment = render.buildChannelMessages(data); const chan = chat .find("#chan-" + data.chan) .find(".messages"); @@ -24,6 +24,8 @@ socket.on("more", function(data) { } else if (children.eq(1).hasClass("date-marker-container")) { // The unread-marker could be at index 0, which will cause the date-marker to become "stuck" children.eq(1).remove(); + } else if (children.eq(0).hasClass("condensed") && children.eq(0).children(".date-marker-container").eq(0).hasClass("date-marker-container")) { + children.eq(0).children(".date-marker-container").eq(0).remove(); } // Add the older messages @@ -52,6 +54,10 @@ socket.on("more", function(data) { } if (lastDate.toDateString() !== msgDate.toDateString()) { + var parent = msg.parent(); + if (parent.hasClass("condensed")) { + msg.insertAfter(parent); + } msg.before(templates.date_marker({msgDate: msgDate})); } diff --git a/client/js/socket-events/msg.js b/client/js/socket-events/msg.js index 15764848..44bf8081 100644 --- a/client/js/socket-events/msg.js +++ b/client/js/socket-events/msg.js @@ -8,8 +8,9 @@ const templates = require("../../views"); socket.on("msg", function(data) { const msg = render.buildChatMessage(data); - const target = data.chan; - const channel = chat.find("#chan-" + target); + const targetId = data.chan; + const target = "#chan-" + targetId; + const channel = chat.find(target); const container = channel.find(".messages"); const activeChannelId = chat.find(".chan.active").data("id"); @@ -19,7 +20,7 @@ socket.on("msg", function(data) { } // Check if date changed - const prevMsg = $(container.find(".msg")).last(); + let prevMsg = $(container.find(".msg")).last(); const prevMsgTime = new Date(prevMsg.attr("data-time")); const msgTime = new Date(msg.attr("data-time")); @@ -29,17 +30,26 @@ socket.on("msg", function(data) { } if (prevMsgTime.toDateString() !== msgTime.toDateString()) { + var parent = prevMsg.parent(); + if (parent.hasClass("condensed")) { + prevMsg = parent; + } prevMsg.after(templates.date_marker({msgDate: msgTime})); } // Add message to the container - container - .append(msg) - .trigger("msg", [ - "#chan-" + target, - data - ]) - .trigger("keepToBottom"); + render.appendMessage( + container, + data.chan, + $(target).attr("data-type"), + data.msg.type, + msg + ); + + container.trigger("msg", [ + target, + data + ]).trigger("keepToBottom"); var lastVisible = container.find("div:visible").last(); if (data.msg.self @@ -52,7 +62,7 @@ socket.on("msg", function(data) { } // Message arrived in a non active channel, trim it to 100 messages - if (activeChannelId !== target && container.find(".msg").slice(0, -100).remove().length) { + if (activeChannelId !== targetId && container.find(".msg").slice(0, -100).remove().length) { channel.find(".show-more").addClass("show"); // Remove date-seperators that would otherwise diff --git a/client/themes/morning.css b/client/themes/morning.css index 17e68bcd..3a5e7677 100644 --- a/client/themes/morning.css +++ b/client/themes/morning.css @@ -101,10 +101,6 @@ body { color: #428bca; } -#chat button:hover { - opacity: 1; -} - /* Increase contrast of some IRC colors */ .irc-fg2 { color: #0074d9; } .irc-fg5 { color: #e969a7; } @@ -208,17 +204,9 @@ body { } /* Embeds */ -#chat .toggle-content, -#chat .toggle-button { - color: #f3f3f3; -} - -#chat .toggle-button:hover { - color: rgba(243, 243, 243, .8); -} - #chat .toggle-content { background: #242a33; + color: #f3f3f3; } #chat .toggle-content .body { diff --git a/client/themes/zenburn.css b/client/themes/zenburn.css index 1465a3b3..5e73ed96 100644 --- a/client/themes/zenburn.css +++ b/client/themes/zenburn.css @@ -127,10 +127,6 @@ body { color: #8c8cbc; } -#chat button:hover { - opacity: 1; -} - /* Increase contrast of some IRC colors */ .irc-fg2 { color: #1b94ff; } .irc-fg5 { color: #e969a7; } @@ -235,17 +231,9 @@ body { /* Previews */ -#chat .toggle-content, -#chat .toggle-button { - color: #dcdccc; -} - -#chat .toggle-button:hover { - color: rgba(220, 220, 204, .8); -} - #chat .toggle-content { background: #333; + color: #dcdccc; } #chat .toggle-content .body { diff --git a/client/views/index.js b/client/views/index.js index 5c9da5dd..aae3c78b 100644 --- a/client/views/index.js +++ b/client/views/index.js @@ -25,6 +25,8 @@ module.exports = { date_marker: require("./date-marker.tpl"), msg: require("./msg.tpl"), msg_action: require("./msg_action.tpl"), + msg_condensed_toggle: require("./msg_condensed_toggle.tpl"), + msg_condensed: require("./msg_condensed.tpl"), msg_preview: require("./msg_preview.tpl"), msg_preview_toggle: require("./msg_preview_toggle.tpl"), msg_unhandled: require("./msg_unhandled.tpl"), diff --git a/client/views/msg_action.tpl b/client/views/msg_action.tpl index c3d5c772..42eb385f 100644 --- a/client/views/msg_action.tpl +++ b/client/views/msg_action.tpl @@ -1,4 +1,5 @@ -
+
{{tz time}} diff --git a/client/views/msg_condensed.tpl b/client/views/msg_condensed.tpl new file mode 100644 index 00000000..914ef27c --- /dev/null +++ b/client/views/msg_condensed.tpl @@ -0,0 +1,7 @@ +
+ {{tz time}} + + + + +
diff --git a/client/views/msg_condensed_toggle.tpl b/client/views/msg_condensed_toggle.tpl new file mode 100644 index 00000000..2c740773 --- /dev/null +++ b/client/views/msg_condensed_toggle.tpl @@ -0,0 +1 @@ +