From f960b252c079d34ecd0424922854d60fbc7ff3e1 Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Mon, 25 Jan 2016 00:40:32 +0200 Subject: [PATCH 1/5] Initial work on context menus --- client/css/style.css | 56 ++++++++++++++++++++++++++ client/index.html | 4 ++ client/js/shout.js | 67 +++++++++++++++++++++++++++++++ client/js/shout.templates.js | 15 +++++++ client/views/contextmenu_item.tpl | 3 ++ 5 files changed, 145 insertions(+) create mode 100644 client/views/contextmenu_item.tpl diff --git a/client/css/style.css b/client/css/style.css index 5a283571..18079452 100644 --- a/client/css/style.css +++ b/client/css/style.css @@ -982,6 +982,62 @@ button, .user { width: 58px; } +#context-menu-container { + display: none; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 1000; + background: transparent; +} +#context-menu { + position: absolute; + list-style: none; + margin: 0; + padding: 4px 0; + width: 240px; + background-color: #fff; + border: solid 1px #dfdfdf; + box-shadow: 0 0 20px 0 rgba(0,0,0,.2); +} +.context-menu-item { + display: block; + margin-bottom: 4px; +} +.context-menu-item:last-child { + margin-bottom: 0; +} +.context-menu-item:first-child { + padding-bottom: 4px; + border-bottom: 1px solid #dfdfdf; +} +.context-menu-item { + cursor: pointer; + display: block; + padding: 4px 12px; + color: #333; + text-decoration: none; +} +.context-menu-item:hover { + color: #fff; + background-color: #0066aa; +} +.context-menu-item:before { + font-family: FontAwesome; + width: 20px; + display: inline-block; +} +.context-menu-user:before { + content: "\f007"; +} +.context-menu-chan:before { + content: "\f0f6"; +} +.context-menu-close:before { + content: "\f057"; +} /** * IRC Message Styling diff --git a/client/index.html b/client/index.html index cbb823ce..0875cdbe 100644 --- a/client/index.html +++ b/client/index.html @@ -293,6 +293,10 @@

About Shout

+
+ +
+ diff --git a/client/js/shout.js b/client/js/shout.js index 99a89314..58626a71 100644 --- a/client/js/shout.js +++ b/client/js/shout.js @@ -463,6 +463,8 @@ $(function() { }); var viewport = $("#viewport"); + var contextMenuContainer = $("#context-menu-container"); + var contextMenu = $("#context-menu"); viewport.on("click", ".lt, .rt", function(e) { var self = $(this); @@ -475,6 +477,61 @@ $(function() { } }); + function positionContextMenu(e) { + var top, left; + var menuWidth = contextMenu.offsetWidth + 4; + var menuHeight = contextMenu.offsetHeight + 4; + + if ((window.innerWidth - e.pageX) < menuWidth) { + left = window.innerWidth - menuWidth; + } else { + left = e.pageX; + } + + if ((window.innerHeight - e.pageY) < menuHeight) { + top = window.innerHeight - menuHeight; + } else { + top = e.pageY; + } + + return {left: left, top: top}; + } + + viewport.on("contextmenu", ".user, .network .chan", function(e) { + var target = $(e.currentTarget); + var output = ""; + + if (target.hasClass("user")) { + output = render("contextmenu_item", { + class: "user", + text: target.text() + }); + } + else if (target.hasClass("chan")) { + output = render("contextmenu_item", { + class: "chan", + text: target.data("title") + }); + output += render("contextmenu_item", { + class: "close", + text: target.hasClass("lobby") ? "Leave network" : "Leave channel", + data: target.data("target") + }); + } + + contextMenu + .html(output) + .css(positionContextMenu(e)); + contextMenuContainer.show(); + + return false; + }); + + contextMenuContainer.on("click contextmenu", function() { + contextMenuContainer.hide(); + return false; + }); + var input = $("#input") .history() .tab(complete, {hint: false}); @@ -603,6 +660,10 @@ $(function() { return false; }); + contextMenu.on("click", ".context-menu-close", function() { + $(".networks .chan[data-target=" + $(this).data("data") + "] .close").click(); + }); + chat.on("input", ".search", function() { var value = $(this).val().toLowerCase(); var names = $(this).closest(".users").find(".names"); @@ -813,6 +874,12 @@ $(function() { } }); + Mousetrap.bind([ + "escape" + ], function() { + contextMenuContainer.hide(); + }); + setInterval(function() { chat.find(".chan:not(.active)").each(function() { var chan = $(this); diff --git a/client/js/shout.templates.js b/client/js/shout.templates.js index 47f8b66f..975841c1 100644 --- a/client/js/shout.templates.js +++ b/client/js/shout.templates.js @@ -72,6 +72,21 @@ templates['chat'] = template({"1":function(depth0,helpers,partials,data) { if (stack1 != null) { buffer += stack1; } return buffer; },"useData":true}); +templates['contextmenu_item'] = template({"1":function(depth0,helpers,partials,data) { + var helper, functionType="function", helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression; + return " data-data=\"" + + escapeExpression(((helper = (helper = helpers.data || (depth0 != null ? depth0.data : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"data","hash":{},"data":data}) : helper))) + + "\""; +},"compiler":[6,">= 2.0.0-beta.1"],"main":function(depth0,helpers,partials,data) { + var stack1, helper, functionType="function", helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression, buffer = "
  • \r\n " + + escapeExpression(((helper = (helper = helpers.text || (depth0 != null ? depth0.text : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"text","hash":{},"data":data}) : helper))) + + "\r\n
  • \r\n"; +},"useData":true}); templates['msg'] = template({"1":function(depth0,helpers,partials,data) { return "self"; },"3":function(depth0,helpers,partials,data) { diff --git a/client/views/contextmenu_item.tpl b/client/views/contextmenu_item.tpl new file mode 100644 index 00000000..cf8674ba --- /dev/null +++ b/client/views/contextmenu_item.tpl @@ -0,0 +1,3 @@ +
  • + {{text}} +
  • From a5760c040ce889d068bd2fe17c921b1b88b83fe9 Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Mon, 25 Jan 2016 15:33:44 +0200 Subject: [PATCH 2/5] Tweak context menu styles --- client/css/style.css | 26 +++++++++----------------- client/js/shout.templates.js | 4 ++-- 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/client/css/style.css b/client/css/style.css index 18079452..f00048bc 100644 --- a/client/css/style.css +++ b/client/css/style.css @@ -996,33 +996,25 @@ button, .user { position: absolute; list-style: none; margin: 0; - padding: 4px 0; - width: 240px; + padding: 0; + min-width: 160px; + font-size: 14px; background-color: #fff; - border: solid 1px #dfdfdf; - box-shadow: 0 0 20px 0 rgba(0,0,0,.2); -} -.context-menu-item { - display: block; - margin-bottom: 4px; -} -.context-menu-item:last-child { - margin-bottom: 0; + box-shadow: 0px 1px 2px rgba(0,0,0,0.1); + border: 1px solid rgba(61,70,77,0.1); } + .context-menu-item:first-child { - padding-bottom: 4px; - border-bottom: 1px solid #dfdfdf; + border-bottom: 1px solid rgba(61,70,77,0.1); } .context-menu-item { cursor: pointer; display: block; - padding: 4px 12px; + padding: 6px 8px; color: #333; - text-decoration: none; } .context-menu-item:hover { - color: #fff; - background-color: #0066aa; + background-color: #e3f2ff; } .context-menu-item:before { font-family: FontAwesome; diff --git a/client/js/shout.templates.js b/client/js/shout.templates.js index 975841c1..45685370 100644 --- a/client/js/shout.templates.js +++ b/client/js/shout.templates.js @@ -83,9 +83,9 @@ templates['contextmenu_item'] = template({"1":function(depth0,helpers,partials,d + "\""; stack1 = helpers['if'].call(depth0, (depth0 != null ? depth0.data : depth0), {"name":"if","hash":{},"fn":this.program(1, data),"inverse":this.noop,"data":data}); if (stack1 != null) { buffer += stack1; } - return buffer + ">\r\n " + return buffer + ">\n " + escapeExpression(((helper = (helper = helpers.text || (depth0 != null ? depth0.text : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"text","hash":{},"data":data}) : helper))) - + "\r\n\r\n"; + + "\n\n"; },"useData":true}); templates['msg'] = template({"1":function(depth0,helpers,partials,data) { return "self"; From 02620edac7a15b5dcb94ba9709a703de5aa81fca Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Mon, 25 Jan 2016 15:47:37 +0200 Subject: [PATCH 3/5] Support clicking on channel names/usernames --- client/css/style.css | 3 +++ client/js/shout.js | 32 ++++++++++++++++++++++++++----- client/js/shout.templates.js | 10 +++++++++- client/views/contextmenu_item.tpl | 2 +- client/views/msg.tpl | 2 +- client/views/msg_action.tpl | 2 +- client/views/user.tpl | 2 +- 7 files changed, 43 insertions(+), 10 deletions(-) diff --git a/client/css/style.css b/client/css/style.css index f00048bc..5c442e86 100644 --- a/client/css/style.css +++ b/client/css/style.css @@ -1030,6 +1030,9 @@ button, .user { .context-menu-close:before { content: "\f057"; } +.context-menu-settings:before { + content: "\f013"; +} /** * IRC Message Styling diff --git a/client/js/shout.js b/client/js/shout.js index 58626a71..92e30d94 100644 --- a/client/js/shout.js +++ b/client/js/shout.js @@ -504,17 +504,26 @@ $(function() { if (target.hasClass("user")) { output = render("contextmenu_item", { class: "user", - text: target.text() + text: target.text(), + data: target.data("name") }); } else if (target.hasClass("chan")) { output = render("contextmenu_item", { class: "chan", - text: target.data("title") + text: target.data("title"), + data: target.data("target") }); + if (target.hasClass("channel")) { + output += render("contextmenu_item", { + class: "settings", + text: "Settings", + data: target.data("target") + }); + } output += render("contextmenu_item", { class: "close", - text: target.hasClass("lobby") ? "Leave network" : "Leave channel", + text: target.hasClass("lobby") ? "Disconnect" : target.hasClass("query") ? "Close" : "Leave", data: target.data("target") }); } @@ -660,8 +669,21 @@ $(function() { return false; }); - contextMenu.on("click", ".context-menu-close", function() { - $(".networks .chan[data-target=" + $(this).data("data") + "] .close").click(); + 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 "settings": + alert("Not yet supported!"); + break; + } }); chat.on("input", ".search", function() { diff --git a/client/js/shout.templates.js b/client/js/shout.templates.js index 45685370..281f597b 100644 --- a/client/js/shout.templates.js +++ b/client/js/shout.templates.js @@ -79,6 +79,8 @@ templates['contextmenu_item'] = template({"1":function(depth0,helpers,partials,d + "\""; },"compiler":[6,">= 2.0.0-beta.1"],"main":function(depth0,helpers,partials,data) { var stack1, helper, functionType="function", helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression, buffer = "
  • " + escapeExpression(((helper = (helper = helpers.mode || (depth0 != null ? depth0.mode : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"mode","hash":{},"data":data}) : helper))) + escapeExpression(((helper = (helper = helpers.from || (depth0 != null ? depth0.from : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"from","hash":{},"data":data}) : helper))) @@ -140,7 +144,9 @@ templates['msg_action'] = template({"1":function(depth0,helpers,partials,data) { if (stack1 != null) { buffer += stack1; } buffer += "\">\n \n " + escapeExpression(((helpers.tz || (depth0 && depth0.tz) || helperMissing).call(depth0, (depth0 != null ? depth0.time : depth0), {"name":"tz","hash":{},"data":data}))) - + "\n \n \n \n " + + "\n \n \n \n " + escapeExpression(((helper = (helper = helpers.mode || (depth0 != null ? depth0.mode : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"mode","hash":{},"data":data}) : helper))) + escapeExpression(((helper = (helper = helpers.from || (depth0 != null ? depth0.from : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"from","hash":{},"data":data}) : helper))) + "\n " @@ -216,6 +222,8 @@ templates['user'] = template({"1":function(depth0,helpers,partials,data) { if (stack1 != null) { buffer += stack1; } return buffer + " + {{/each}} From 97b156af43a1e47928395a91c813b0d15ec382e1 Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Sat, 30 Jan 2016 23:09:25 +0200 Subject: [PATCH 4/5] Hide contextmenu on mousedown --- client/js/shout.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/js/shout.js b/client/js/shout.js index 92e30d94..c9127a47 100644 --- a/client/js/shout.js +++ b/client/js/shout.js @@ -536,7 +536,7 @@ $(function() { return false; }); - contextMenuContainer.on("click contextmenu", function() { + contextMenuContainer.on("mousedown", function() { contextMenuContainer.hide(); return false; }); From d31a040fbb7d375e44467c8cdaa5217a9bd635bc Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Sat, 30 Jan 2016 23:13:01 +0200 Subject: [PATCH 5/5] Add user-select: none on sidebar, this makes contextmenu work --- client/css/style.css | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/client/css/style.css b/client/css/style.css index 5c442e86..16ea6701 100644 --- a/client/css/style.css +++ b/client/css/style.css @@ -339,6 +339,12 @@ button, .user { top: 2px; right: 3px; } +#sidebar, #footer { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} #footer { background: rgba(0, 0, 0, .06); border-radius: 2px;