diff --git a/client/css/style.css b/client/css/style.css index 8dc4e095..96c4359f 100644 --- a/client/css/style.css +++ b/client/css/style.css @@ -274,6 +274,8 @@ kbd { .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-kick::before { content: "\f05e"; /* http://fontawesome.io/icon/ban/ */ } +.context-menu-action-op::before { content: "\f1fa"; /* http://fontawesome.io/icon/at/ */ } +.context-menu-action-voice::before { content: "\f067"; /* http://fontawesome.io/icon/plus/ */ } .context-menu-network::before { content: "\f233"; /* https://fontawesome.com/icons/server?style=solid */ } .context-menu-edit::before { content: "\f303"; /* https://fontawesome.com/icons/pencil-alt?style=solid */ } @@ -1888,7 +1890,7 @@ part/quit messages where we don't load previews (adds a blank line otherwise) */ list-style: none; margin: 0; padding: 0; - min-width: 160px; + min-width: 180px; font-size: 14px; background-color: #fff; box-shadow: 0 3px 12px rgba(0, 0, 0, 0.15); diff --git a/client/js/contextMenuFactory.js b/client/js/contextMenuFactory.js index 4a94a94f..ed0e3389 100644 --- a/client/js/contextMenuFactory.js +++ b/client/js/contextMenuFactory.js @@ -125,6 +125,82 @@ function addKickItem() { }); } +function addOpItem() { + function op(itemData) { + socket.emit("input", { + target: $("#chat").data("id"), + text: "/op " + itemData, + }); + } + + addContextMenuItem({ + check: (target) => + utils.hasRoleInChannel(target.closest(".chan"), ["op"]) && + !utils.hasRoleInChannel(target.closest(".chan"), ["op"], target.data("name")), + className: "action-op", + displayName: "Give operator (+o)", + data: (target) => target.data("name"), + callback: op, + }); +} + +function addDeopItem() { + function deop(itemData) { + socket.emit("input", { + target: $("#chat").data("id"), + text: "/deop " + itemData, + }); + } + + addContextMenuItem({ + check: (target) => + utils.hasRoleInChannel(target.closest(".chan"), ["op"]) && + utils.hasRoleInChannel(target.closest(".chan"), ["op"], target.data("name")), + className: "action-op", + displayName: "Revoke operator (-o)", + data: (target) => target.data("name"), + callback: deop, + }); +} + +function addVoiceItem() { + function voice(itemData) { + socket.emit("input", { + target: $("#chat").data("id"), + text: "/voice " + itemData, + }); + } + + addContextMenuItem({ + check: (target) => + utils.hasRoleInChannel(target.closest(".chan"), ["op"]) && + !utils.hasRoleInChannel(target.closest(".chan"), ["voice"], target.data("name")), + className: "action-voice", + displayName: "Give voice (+v)", + data: (target) => target.data("name"), + callback: voice, + }); +} + +function addDevoiceItem() { + function devoice(itemData) { + socket.emit("input", { + target: $("#chat").data("id"), + text: "/devoice " + itemData, + }); + } + + addContextMenuItem({ + check: (target) => + utils.hasRoleInChannel(target.closest(".chan"), ["op"]) && + utils.hasRoleInChannel(target.closest(".chan"), ["voice"], target.data("name")), + className: "action-voice", + displayName: "Revoke voice (-v)", + data: (target) => target.data("name"), + callback: devoice, + }); +} + function addFocusItem() { function focusChan(itemData) { $(`.networks .chan[data-target="${itemData}"]`).click(); @@ -221,6 +297,10 @@ function addDefaultItems() { addWhoisItem(); addQueryItem(); addKickItem(); + addOpItem(); + addDeopItem(); + addVoiceItem(); + addDevoiceItem(); addFocusItem(); addEditNetworkItem(); addChannelListItem(); diff --git a/client/js/utils.js b/client/js/utils.js index 3ddc1ccf..5c63891d 100644 --- a/client/js/utils.js +++ b/client/js/utils.js @@ -40,16 +40,16 @@ function resetHeight(element) { element.style.height = element.style.minHeight; } -// Given a channel element will determine if the lounge user is one of the supplied roles. -function hasRoleInChannel(channel, roles) { +// Given a channel element will determine if the lounge user or a given nick is one of the supplied roles. +function hasRoleInChannel(channel, roles, nick) { if (!channel || !roles) { return false; } const channelID = channel.data("id"); const network = $("#sidebar .network").has(`.chan[data-id="${channelID}"]`); - const ownNick = network.data("nick"); - const user = channel.find(`.names-original .user[data-name="${escape(ownNick)}"]`).first(); + const target = nick || network.data("nick"); + const user = channel.find(`.names-original .user[data-name="${escape(target)}"]`).first(); return user.parent().is("." + roles.join(", .")); }