diff --git a/frontend_tests/node_tests/hotkey.js b/frontend_tests/node_tests/hotkey.js index 593c45c46e..cce380cd8f 100644 --- a/frontend_tests/node_tests/hotkey.js +++ b/frontend_tests/node_tests/hotkey.js @@ -215,6 +215,7 @@ function stubbing(func_name_to_stub, test_function) { end: 35, home: 36, left_arrow: 37, + right_arrow: 39, page_up: 33, page_down: 34, spacebar: 32, @@ -273,6 +274,10 @@ function stubbing(func_name_to_stub, test_function) { assert_mapping('up_arrow', 'navigate.up'); assert_mapping('+', 'reactions.toggle_reaction', true, false); + hotkey.is_lightbox_open = return_true; + assert_mapping('left_arrow', 'lightbox.prev'); + assert_mapping('right_arrow', 'lightbox.next'); + hotkey.is_settings_page = return_true; assert_unmapped('end'); assert_unmapped('home'); diff --git a/static/js/click_handlers.js b/static/js/click_handlers.js index 5d3e4de4e6..7d6a04b147 100644 --- a/static/js/click_handlers.js +++ b/static/js/click_handlers.js @@ -526,6 +526,48 @@ $(function () { $("#lightbox_overlay .download").click(function () { this.blur(); }); + + $("#lightbox_overlay").on("click", ".image-list .image", function () { + var $image_list = $(this).parent(); + + var image = new Image(); + image.src = this.dataset.src; + image.title = this.title; + + lightbox.open({ + type: "photo", + user: message_store.get($(this).attr("data-zid")).sender_full_name, + image: image, + }); + + $(".image-list .image.selected").removeClass("selected"); + $(this).addClass("selected"); + + var parentOffset = this.parentNode.clientWidth + this.parentNode.scrollLeft; + // this is the left and right of the image compared to its parent. + var coords = { + left: this.offsetLeft, + right: this.offsetLeft + this.clientWidth, + }; + + if (coords.right > parentOffset) { + // add 2px margin + $image_list.animate({ + scrollLeft: coords.right - this.parentNode.clientWidth + 2, + }, 100); + } else if (coords.left < this.parentNode.scrollLeft) { + // subtract 2px margin + $image_list.animate({ scrollLeft: coords.left - 2 }, 100); + } + }); + + $("#lightbox_overlay").on("click", ".center .arrow", function () { + var direction = $(this).attr("data-direction"); + + if (/^(next|prev)$/.test(direction)) { + lightbox[direction](); + } + }); }()); // MAIN CLICK HANDLER diff --git a/static/js/hotkey.js b/static/js/hotkey.js index 57d0bf91e8..24af54bafc 100644 --- a/static/js/hotkey.js +++ b/static/js/hotkey.js @@ -29,6 +29,10 @@ exports.is_settings_page = function () { return (/^#*(settings|administration)/g).test(window.location.hash); }; +exports.is_lightbox_open = function () { + return lightbox.is_open; +}; + var actions_dropdown_hotkeys = [ 'down_arrow', 'up_arrow', @@ -57,7 +61,8 @@ var keydown_unshift_mappings = { 34: {name: 'page_down', message_view_only: true}, // page down 35: {name: 'end', message_view_only: true}, // end 36: {name: 'home', message_view_only: true}, // home - 37: {name: 'left_arrow', message_view_only: true}, // left arrow + 37: {name: 'left_arrow', message_view_only: false}, // left arrow + 39: {name: 'right_arrow', message_view_only: false}, // right arrow 38: {name: 'up_arrow', message_view_only: true}, // up arrow 40: {name: 'down_arrow', message_view_only: true}, // down arrow }; @@ -183,7 +188,7 @@ exports.process_escape_key = function (e) { return false; } - if ($("#lightbox_overlay").hasClass("show")) { + if (exports.is_lightbox_open()) { modals.close_modal("lightbox"); return true; } @@ -506,10 +511,22 @@ exports.process_hotkey = function (e, hotkey) { } if (event_name === 'left_arrow') { + if (exports.is_lightbox_open()) { + lightbox.prev(); + return true; + } + message_edit.edit_last_sent_message(); return true; } + if (event_name === 'right_arrow') { + if (exports.is_lightbox_open()) { + lightbox.next(); + return true; + } + } + // Shortcuts that don't require a message switch (event_name) { case 'compose': // 'c': compose diff --git a/static/js/lightbox.js b/static/js/lightbox.js index b473aa5220..acd06cb496 100644 --- a/static/js/lightbox.js +++ b/static/js/lightbox.js @@ -1,10 +1,40 @@ var lightbox = (function () { var exports = {}; +var images = []; +var is_open = false; + +var get_image_title = function (image) { + var image_title = $(image).attr("title"); + if (image_title) { + return image_title; + } + return $(image).parent().attr("title"); +}; + function display_image(image, user) { + if (!is_open) { + images = Array.prototype.slice.call($(".focused_table .messagebox-content img")); + var $image_list = $("#lightbox_overlay .image-list").html(""); + + images.forEach(function (img) { + var src = img.getAttribute("src"); + var className = $(image).attr("src").match(src) ? "image selected" : "image"; + + var node = $("
", { + class: className, + title: get_image_title(img), + "data-zid": $(img).closest(".message_row").attr("zid"), + "data-src": src, + }).css({ backgroundImage: "url(" + src + ")"}); + + $image_list.append(node); + }, ""); + } + // image should be an Image Object in JavaScript. var url = $(image).attr("src"); - var title = $(image).parent().attr("title"); + var title = get_image_title(image); $("#lightbox_overlay .player-container").hide(); $("#lightbox_overlay .image-actions, .image-description, .download").show(); @@ -38,9 +68,11 @@ exports.open = function (data) { switch (data.type) { case "photo": display_image(data.image, data.user); + is_open = true; break; case "youtube": display_youtube_video(data.id); + is_open = true; break; default: break; @@ -48,6 +80,7 @@ exports.open = function (data) { $("#lightbox_overlay").addClass("show"); popovers.hide_all(); + lightbox.is_open = true; }; exports.show_from_selected_message = function () { @@ -74,6 +107,25 @@ exports.show_from_inline_image = function ($img) { } }; +exports.prev = function () { + $(".image-list .image.selected").prev().click(); +}; + +exports.next = function () { + $(".image-list .image.selected").next().click(); +}; + +Object.defineProperty(exports, "is_open", { + get: function () { + return is_open; + }, + set: function (value) { + if (typeof value === "boolean") { + is_open = value; + } + }, +}); + return exports; }()); diff --git a/static/js/modals.js b/static/js/modals.js index 84feb1d57c..4cbcc9aee4 100644 --- a/static/js/modals.js +++ b/static/js/modals.js @@ -21,6 +21,7 @@ var modals = (function () { lightbox: function () { $(".player-container iframe").remove(); + lightbox.is_open = false; document.activeElement.blur(); }, diff --git a/static/styles/lightbox.css b/static/styles/lightbox.css index 56ec119e00..1f8c63d65b 100644 --- a/static/styles/lightbox.css +++ b/static/styles/lightbox.css @@ -11,7 +11,7 @@ justify-content: center; position: relative; width: 100%; - height: calc(100% - 65px - 30px); + height: calc(100% - 65px - 90px); margin: 0px; background-size: contain; @@ -157,6 +157,58 @@ white-space: pre; } +#lightbox_overlay .center .arrow { + display: inline-block; + vertical-align: top; + margin-top: 25px; + padding: 5px 10px; + + color: #fff; + font-size: 1.8em; + font-weight: 100; + + transform: scaleY(2); + cursor: pointer; + + opacity: 0.5; + transition: all 0.3s ease; +} + +#lightbox_overlay .center .arrow:hover { + opacity: 1; +} + +#lightbox_overlay .center .image-list { + position: relative; + display: inline-block; + padding: 15px 0px 12px 0px; + height: 50px; + font-size: 0px; + + max-width: 40vw; + overflow-x: auto; + white-space: nowrap; +} + +#lightbox_overlay .center .image-list .image { + display: inline-block; + vertical-align: top; + width: 50px; + height: 50px; + margin: 0px 2px; + + background-color: rgba(240, 240, 240, 0.2); + opacity: 0.5; + + background-size: cover; + background-position: center; + cursor: pointer; +} + +#lightbox_overlay .image-list .image.selected { + opacity: 1; +} + .image-actions .button { font-size: 0.8rem; min-width: inherit; @@ -199,4 +251,8 @@ #lightbox_overlay .image-description .title { max-width: calc(100% - 60px); } + + #lightbox_overlay .center .image-list { + max-width: 80vw; + } } diff --git a/static/styles/zulip.css b/static/styles/zulip.css index 9418c74c29..8d2ce9d2a1 100644 --- a/static/styles/zulip.css +++ b/static/styles/zulip.css @@ -41,6 +41,14 @@ a { cursor: pointer; } +.no-select { + -webkit-touch-callout: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + p.n-margin { margin: 10px 0px 0px 0px; } diff --git a/templates/zerver/lightbox_overlay.html b/templates/zerver/lightbox_overlay.html index aa9177183d..f548258386 100644 --- a/templates/zerver/lightbox_overlay.html +++ b/templates/zerver/lightbox_overlay.html @@ -18,4 +18,9 @@ +