diff --git a/client/js/keybinds.js b/client/js/keybinds.js new file mode 100644 index 00000000..961961c4 --- /dev/null +++ b/client/js/keybinds.js @@ -0,0 +1,112 @@ +"use strict"; + +const $ = require("jquery"); +const Mousetrap = require("mousetrap"); +const utils = require("./utils"); +const input = $("#input"); +const sidebar = $("#sidebar"); +const windows = $("#windows"); +const contextMenuContainer = $("#context-menu-container"); + +Mousetrap.bind([ + "pageup", + "pagedown" +], function(e, key) { + let container = windows.find(".window.active"); + + // Chat windows scroll message container + if (container.attr("id") === "chat-container") { + container = container.find(".chan.active .chat"); + } + + container.finish(); + + const offset = container.get(0).clientHeight * 0.9; + let scrollTop = container.scrollTop(); + + if (key === "pageup") { + scrollTop = Math.floor(scrollTop - offset); + } else { + scrollTop = Math.ceil(scrollTop + offset); + } + + container.animate({ + scrollTop: scrollTop + }, 200); + + return false; +}); + +Mousetrap.bind([ + "command+up", + "command+down", + "ctrl+up", + "ctrl+down" +], function(e, keys) { + const channels = sidebar.find(".chan"); + const index = channels.index(channels.filter(".active")); + const direction = keys.split("+").pop(); + let target; + + switch (direction) { + case "up": + target = (channels.length + (index - 1 + channels.length)) % channels.length; + break; + + case "down": + target = (channels.length + (index + 1 + channels.length)) % channels.length; + break; + } + + channels.eq(target).click(); +}); + +Mousetrap.bind([ + "command+shift+l", + "ctrl+shift+l" +], function(e) { + if (e.target === input[0]) { + utils.clear(); + e.preventDefault(); + } +}); + +Mousetrap.bind([ + "escape" +], function() { + contextMenuContainer.hide(); +}); + +const colorsHotkeys = { + k: "\x03", + b: "\x02", + u: "\x1F", + i: "\x1D", + o: "\x0F", +}; + +for (const hotkey in colorsHotkeys) { + Mousetrap.bind([ + "command+" + hotkey, + "ctrl+" + hotkey + ], function(e) { + e.preventDefault(); + + const cursorPosStart = input.prop("selectionStart"); + const cursorPosEnd = input.prop("selectionEnd"); + const value = input.val(); + let newValue = value.substring(0, cursorPosStart) + colorsHotkeys[e.key]; + + if (cursorPosStart === cursorPosEnd) { + // If no text is selected, insert at cursor + newValue += value.substring(cursorPosEnd, value.length); + } else { + // If text is selected, insert formatting character at start and the end + newValue += value.substring(cursorPosStart, cursorPosEnd) + colorsHotkeys[e.key] + value.substring(cursorPosEnd, value.length); + } + + input + .val(newValue) + .get(0).setSelectionRange(cursorPosStart + 1, cursorPosEnd + 1); + }); +} diff --git a/client/js/lounge.js b/client/js/lounge.js index 27bbc960..4fececa3 100644 --- a/client/js/lounge.js +++ b/client/js/lounge.js @@ -4,23 +4,22 @@ require("jquery-ui/ui/widgets/sortable"); const $ = require("jquery"); const moment = require("moment"); -const Mousetrap = require("mousetrap"); const URI = require("urijs"); const fuzzy = require("fuzzy"); // our libraries require("./libs/jquery/inputhistory"); require("./libs/jquery/stickyscroll"); -const helpers_roundBadgeNumber = require("./libs/handlebars/roundBadgeNumber"); const slideoutMenu = require("./libs/slideout"); const templates = require("../views"); const socket = require("./socket"); require("./socket-events"); const storage = require("./localStorage"); -const options = require("./options"); +require("./options"); const utils = require("./utils"); require("./autocompletion"); require("./webpush"); +require("./keybinds"); require("./clipboard"); $(function() { @@ -29,20 +28,6 @@ $(function() { $(document.body).data("app-name", document.title); - var pop; - try { - pop = new Audio(); - pop.src = "audio/pop.ogg"; - } catch (e) { - pop = { - play: $.noop - }; - } - - $("#play").on("click", function() { - pop.play(); - }); - var windows = $("#windows"); var viewport = $("#viewport"); var sidebarSlide = slideoutMenu(viewport[0], sidebar[0]); @@ -494,95 +479,6 @@ $(function() { container.html(templates.user_filtered({matches: result})).show(); }); - chat.on("msg", ".messages", function(e, target, msg) { - var unread = msg.unread; - msg = msg.msg; - - if (msg.self) { - return; - } - - var button = sidebar.find(".chan[data-target='" + target + "']"); - if (msg.highlight || (options.notifyAllMessages && msg.type === "message")) { - if (!document.hasFocus() || !$(target).hasClass("active")) { - if (options.notification) { - try { - pop.play(); - } catch (exception) { - // On mobile, sounds can not be played without user interaction. - } - } - utils.toggleNotificationMarkers(true); - - if (options.desktopNotifications && Notification.permission === "granted") { - var title; - var body; - - if (msg.type === "invite") { - title = "New channel invite:"; - body = msg.from + " invited you to " + msg.channel; - } else { - title = msg.from; - if (!button.hasClass("query")) { - title += " (" + button.data("title").trim() + ")"; - } - if (msg.type === "message") { - title += " says:"; - } - body = msg.text.replace(/\x03(?:[0-9]{1,2}(?:,[0-9]{1,2})?)?|[\x00-\x1F]|\x7F/g, "").trim(); - } - - try { - var notify = new Notification(title, { - body: body, - icon: "img/logo-64.png", - tag: target - }); - notify.addEventListener("click", function() { - window.focus(); - button.click(); - this.close(); - }); - } catch (exception) { - // `new Notification(...)` is not supported and should be silenced. - } - } - } - } - - if (button.hasClass("active")) { - return; - } - - if (!unread) { - return; - } - - var badge = button.find(".badge").html(helpers_roundBadgeNumber(unread)); - - if (msg.highlight) { - badge.addClass("highlight"); - } - }); - - 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 - .text("Loading older messages…") - .prop("disabled", true); - - socket.emit("more", { - target: self.data("id"), - lastId: lastMessageId - }); - }); - var forms = $("#sign-in, #connect, #change-password"); windows.on("show", "#sign-in", function() { @@ -668,111 +564,6 @@ $(function() { $(this).data("lastvalue", nick); }); - (function HotkeysScope() { - Mousetrap.bind([ - "pageup", - "pagedown" - ], function(e, key) { - let container = windows.find(".window.active"); - - // Chat windows scroll message container - if (container.attr("id") === "chat-container") { - container = container.find(".chan.active .chat"); - } - - container.finish(); - - const offset = container.get(0).clientHeight * 0.9; - let scrollTop = container.scrollTop(); - - if (key === "pageup") { - scrollTop = Math.floor(scrollTop - offset); - } else { - scrollTop = Math.ceil(scrollTop + offset); - } - - container.animate({ - scrollTop: scrollTop - }, 200); - - return false; - }); - - Mousetrap.bind([ - "command+up", - "command+down", - "ctrl+up", - "ctrl+down" - ], function(e, keys) { - var channels = sidebar.find(".chan"); - var index = channels.index(channels.filter(".active")); - var direction = keys.split("+").pop(); - switch (direction) { - case "up": - // Loop - var upTarget = (channels.length + (index - 1 + channels.length)) % channels.length; - channels.eq(upTarget).click(); - break; - - case "down": - // Loop - var downTarget = (channels.length + (index + 1 + channels.length)) % channels.length; - channels.eq(downTarget).click(); - break; - } - }); - - Mousetrap.bind([ - "command+shift+l", - "ctrl+shift+l" - ], function(e) { - if (e.target === input[0]) { - utils.clear(); - e.preventDefault(); - } - }); - - Mousetrap.bind([ - "escape" - ], function() { - contextMenuContainer.hide(); - }); - - var colorsHotkeys = { - k: "\x03", - b: "\x02", - u: "\x1F", - i: "\x1D", - o: "\x0F", - }; - - for (var hotkey in colorsHotkeys) { - Mousetrap.bind([ - "command+" + hotkey, - "ctrl+" + hotkey - ], function(e) { - e.preventDefault(); - - const cursorPosStart = input.prop("selectionStart"); - const cursorPosEnd = input.prop("selectionEnd"); - const value = input.val(); - let newValue = value.substring(0, cursorPosStart) + colorsHotkeys[e.key]; - - if (cursorPosStart === cursorPosEnd) { - // If no text is selected, insert at cursor - newValue += value.substring(cursorPosEnd, value.length); - } else { - // If text is selected, insert formatting character at start and the end - newValue += value.substring(cursorPosStart, cursorPosEnd) + colorsHotkeys[e.key] + value.substring(cursorPosEnd, value.length); - } - - input - .val(newValue) - .get(0).setSelectionRange(cursorPosStart + 1, cursorPosEnd + 1); - }); - } - }()); - $(document).on("visibilitychange focus click", () => { if (sidebar.find(".highlight").length === 0) { utils.toggleNotificationMarkers(false); diff --git a/client/js/socket-events/more.js b/client/js/socket-events/more.js index 20d5c12d..9183bfef 100644 --- a/client/js/socket-events/more.js +++ b/client/js/socket-events/more.js @@ -54,3 +54,21 @@ socket.on("more", function(data) { .text("Show older messages") .prop("disabled", false); }); + +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 + .text("Loading older messages…") + .prop("disabled", true); + + socket.emit("more", { + target: self.data("id"), + lastId: lastMessageId + }); +}); diff --git a/client/js/socket-events/msg.js b/client/js/socket-events/msg.js index 4ef503ae..bf5c0eb9 100644 --- a/client/js/socket-events/msg.js +++ b/client/js/socket-events/msg.js @@ -4,7 +4,22 @@ const $ = require("jquery"); const socket = require("../socket"); const render = require("../render"); const utils = require("../utils"); +const options = require("../options"); +const helpers_roundBadgeNumber = require("../libs/handlebars/roundBadgeNumber"); const chat = $("#chat"); +const sidebar = $("#sidebar"); + +let pop; +try { + pop = new Audio(); + pop.src = "audio/pop.ogg"; +} catch (e) { + pop = { + play: $.noop + }; +} + +$("#play").on("click", () => pop.play()); socket.on("msg", function(data) { // We set a maximum timeout of 2 seconds so that messages don't take too long to appear. @@ -27,14 +42,13 @@ function processReceivedMessage(data) { render.appendMessage( container, targetId, - $(target).attr("data-type"), + channel.attr("data-type"), data.msg ); - container.trigger("msg", [ - target, - data - ]).trigger("keepToBottom"); + container.trigger("keepToBottom"); + + notifyMessage(targetId, channel, data); var lastVisible = container.find("div:visible").last(); if (data.msg.self @@ -70,3 +84,71 @@ function processReceivedMessage(data) { } } } + +function notifyMessage(targetId, channel, msg) { + const unread = msg.unread; + msg = msg.msg; + + if (msg.self) { + return; + } + + const button = sidebar.find(".chan[data-id='" + targetId + "']"); + if (msg.highlight || (options.notifyAllMessages && msg.type === "message")) { + if (!document.hasFocus() || !channel.hasClass("active")) { + if (options.notification) { + try { + pop.play(); + } catch (exception) { + // On mobile, sounds can not be played without user interaction. + } + } + + utils.toggleNotificationMarkers(true); + + if (options.desktopNotifications && Notification.permission === "granted") { + let title; + let body; + + if (msg.type === "invite") { + title = "New channel invite:"; + body = msg.from + " invited you to " + msg.channel; + } else { + title = msg.from; + if (!button.hasClass("query")) { + title += " (" + button.data("title").trim() + ")"; + } + if (msg.type === "message") { + title += " says:"; + } + body = msg.text.replace(/\x03(?:[0-9]{1,2}(?:,[0-9]{1,2})?)?|[\x00-\x1F]|\x7F/g, "").trim(); + } + + try { + const notify = new Notification(title, { + body: body, + icon: "img/logo-64.png", + tag: `lounge-${targetId}` + }); + notify.addEventListener("click", function() { + window.focus(); + button.click(); + this.close(); + }); + } catch (exception) { + // `new Notification(...)` is not supported and should be silenced. + } + } + } + } + + if (!unread || button.hasClass("active")) { + return; + } + + const badge = button.find(".badge").html(helpers_roundBadgeNumber(unread)); + + if (msg.highlight) { + badge.addClass("highlight"); + } +}