From 33d865501d004d2740077389edc9b458691edac2 Mon Sep 17 00:00:00 2001 From: Erik Vosseberg Date: Wed, 1 Nov 2017 10:16:19 +0100 Subject: [PATCH] Add whois and conversation as action to user contextmenu Add Op specific actions to contextMenu Show context menu when left clicking user Switch to using data attributes as selectors remove ban as possible action Move `isOpInChannel()` to utils.js Capitalize strings use CSS.escape for `ownNick` use string interpolation properly point to findCurrentNetworkChan Move context menu item actions to command pattern add icons for context menu actions Make list in context menu always list. remove empty lines in style.css use info circle instead of question circle change context menu labels. change contextMenuActions.execute to more explicit method. --- client/css/style.css | 3 + client/js/lounge.js | 116 ++++++++++++++++++++++-------- client/js/utils.js | 11 +++ client/views/contextmenu_item.tpl | 2 +- src/plugins/inputs/query.js | 1 + 5 files changed, 101 insertions(+), 32 deletions(-) diff --git a/client/css/style.css b/client/css/style.css index 1369957e..40eb7b53 100644 --- a/client/css/style.css +++ b/client/css/style.css @@ -224,6 +224,9 @@ kbd { .context-menu-chan::before { content: "\f292"; /* http://fontawesome.io/icon/hashtag/ */ } .context-menu-close::before { content: "\f00d"; /* http://fontawesome.io/icon/times/ */ } .context-menu-list::before { content: "\f03a"; /* http://fontawesome.io/icon/list/ */ } +.context-menu-action-whois::before { content: "\f05a"; /* http://fontawesome.io/icon/info-circle/ */ } +.context-menu-action-query::before { content: "\f0e6"; /* http://fontawesome.io/icon/comments-o/ */ } +.context-menu-action-kick::before { content: "\f05e"; /* http://fontawesome.io/icon/ban/ */ } .context-menu-network::before, #sidebar .chan.lobby::before, diff --git a/client/js/lounge.js b/client/js/lounge.js index 6e3757ce..dbfbe3fe 100644 --- a/client/js/lounge.js +++ b/client/js/lounge.js @@ -79,9 +79,34 @@ $(function() { if (target.hasClass("user")) { output = templates.contextmenu_item({ class: "user", + action: "whois", text: target.text(), data: target.data("name"), }); + output += templates.contextmenu_divider(); + output += templates.contextmenu_item({ + class: "action-whois", + action: "whois", + text: "User information", + data: target.data("name"), + }); + output += templates.contextmenu_item({ + class: "action-query", + action: "query", + text: "Direct messages", + data: target.data("name"), + }); + + const channel = target.closest(".chan"); + if (utils.isOpInChannel(channel) && channel.data("type") === "channel") { + output += templates.contextmenu_divider(); + output += templates.contextmenu_item({ + class: "action-kick", + action: "kick", + text: "Kick", + data: target.data("name"), + }); + } } else if (target.hasClass("chan")) { let itemClass; @@ -95,6 +120,7 @@ $(function() { output = templates.contextmenu_item({ class: itemClass, + action: "focusChan", text: target.data("title"), data: target.data("target"), }); @@ -102,12 +128,14 @@ $(function() { if (target.hasClass("lobby")) { output += templates.contextmenu_item({ class: "list", + action: "list", text: "List all channels", - data: target.data("target"), + data: target.data("id"), }); } output += templates.contextmenu_item({ class: "close", + action: "close", text: target.hasClass("lobby") ? "Disconnect" : target.hasClass("channel") ? "Leave" : "Close", data: target.data("target"), }); @@ -121,7 +149,11 @@ $(function() { return false; } - viewport.on("contextmenu", ".user, .network .chan", function(e) { + viewport.on("contextmenu", ".network .chan", function(e) { + return showContextMenu(this, e); + }); + + viewport.on("click contextmenu", ".user", function(e) { return showContextMenu(this, e); }); @@ -285,20 +317,6 @@ $(function() { $(this).closest(".msg.condensed").toggleClass("closed"); }); - chat.on("click", ".user", function() { - var name = $(this).data("name"); - var chan = utils.findCurrentNetworkChan(name); - - if (chan.length) { - chan.click(); - } - - socket.emit("input", { - target: chat.data("id"), - text: "/whois " + name, - }); - }); - sidebar.on("click", ".chan, button", function(e, data) { // Pushes states to history web API when clicking elements with a data-target attribute. // States are very trivial and only contain a single `clickTarget` property which @@ -447,24 +465,60 @@ $(function() { return false; }); - contextMenu.on("click", ".context-menu-item", function() { - switch ($(this).data("action")) { - case "close": - $(".networks .chan[data-target='" + $(this).data("data") + "'] .close").click(); - break; - case "chan": - $(".networks .chan[data-target='" + $(this).data("data") + "']").click(); - break; - case "user": - $(".channel.active .users .user[data-name='" + $(this).data("data") + "']").click(); - break; - case "list": + const contextMenuActions = { + close: function(itemData) { + $(`.networks .chan[data-target="${itemData}"] .close`).click(); + }, + focusChan: function(itemData) { + $(`.networks .chan[data-target="${itemData}"]`).click(); + }, + list: function(itemData) { socket.emit("input", { - target: chat.data("id"), + target: itemData, text: "/list", }); - break; - } + }, + whois: function(itemData) { + const chan = utils.findCurrentNetworkChan(itemData); + + if (chan.length) { + chan.click(); + } + + socket.emit("input", { + target: $("#chat").data("id"), + text: "/whois " + itemData, + }); + + $(`.channel.active .users .user[data-name="${itemData}"]`).click(); + }, + query: function(itemData) { + const chan = utils.findCurrentNetworkChan(itemData); + + if (chan.length) { + chan.click(); + } + + socket.emit("input", { + target: $("#chat").data("id"), + text: "/query " + itemData, + }); + }, + kick: function(itemData) { + socket.emit("input", { + target: $("#chat").data("id"), + text: "/kick " + itemData, + }); + }, + }; + + contextMenuActions.execute = (name, ...args) => contextMenuActions[name] && contextMenuActions[name](...args); + + contextMenu.on("click", ".context-menu-item", function() { + const $this = $(this); + const itemData = $this.data("data"); + const contextAction = $this.data("action"); + contextMenuActions.execute(contextAction, itemData); }); chat.on("input", ".search", function() { diff --git a/client/js/utils.js b/client/js/utils.js index 04974c13..9d3e7ba8 100644 --- a/client/js/utils.js +++ b/client/js/utils.js @@ -1,6 +1,7 @@ "use strict"; const $ = require("jquery"); +const escape = require("css.escape"); const input = $("#input"); var serverHash = -1; @@ -19,6 +20,7 @@ module.exports = { toggleNickEditor, toggleNotificationMarkers, requestIdleCallback, + isOpInChannel, }; function findCurrentNetworkChan(name) { @@ -37,6 +39,15 @@ function resetHeight(element) { element.style.height = element.style.minHeight; } +// Given a channel element will determine if the lounge user is Op in that channel +function isOpInChannel(channel) { + const channelID = channel.data("id"); + const network = $("#sidebar .network").has(`.chan[data-id="${channelID}"]`); + const ownNick = network.data("nick"); + const isOP = channel.find(`.users .user-mode.op .user[data-name="${escape(ownNick)}"]`).length; + return isOP; +} + // Triggering click event opens the virtual keyboard on mobile // This can only be called from another interactive event (e.g. button click) function forceFocus() { diff --git a/client/views/contextmenu_item.tpl b/client/views/contextmenu_item.tpl index e1cd4dc5..fe281d49 100644 --- a/client/views/contextmenu_item.tpl +++ b/client/views/contextmenu_item.tpl @@ -1,3 +1,3 @@ -
  • +
  • {{text}}
  • diff --git a/src/plugins/inputs/query.js b/src/plugins/inputs/query.js index 501b8bc1..24b3ccdd 100644 --- a/src/plugins/inputs/query.js +++ b/src/plugins/inputs/query.js @@ -48,5 +48,6 @@ exports.input = function(network, chan, cmd, args) { this.emit("join", { network: network.id, chan: newChan.getFilteredClone(true), + shouldOpen: true, }); };