Cleanup chat/userlist to use flexbox, fix a couple of bugs

This commit is contained in:
Pavel Djundik 2018-03-04 22:03:11 +02:00
parent cbf82a1bc7
commit e719e4ff81
11 changed files with 76 additions and 95 deletions

View file

@ -498,8 +498,8 @@ kbd {
float: right; float: right;
} }
#viewport.rt #chat .sidebar { #viewport.rt #chat .userlist {
right: -180px; display: none;
} }
#sidebar { #sidebar {
@ -954,7 +954,8 @@ button.collapse-network:first-child:nth-last-child(3) {
} }
#chat .chan.active { #chat .chan.active {
display: block; display: flex;
flex-direction: column;
} }
#chat .condensed { #chat .condensed {
@ -1006,48 +1007,40 @@ button.collapse-network:first-child:nth-last-child(3) {
#windows #form .input, #windows #form .input,
.messages .msg, .messages .msg,
.sidebar { .userlist {
font-size: 14px; font-size: 14px;
line-height: 1.4; line-height: 1.4;
} }
#windows #chat .header { #windows #chat .header {
display: flex; display: flex;
flex-shrink: 0;
} }
#chat .chat, #chat .chat-content {
#chat .sidebar { display: flex;
top: 48px; flex-grow: 1;
} }
#chat .chat { #chat .chat {
bottom: 0;
left: 0;
right: 0;
overflow: auto; overflow: auto;
will-change: transform, scroll-position;
-webkit-overflow-scrolling: touch;
position: absolute;
display: flex; display: flex;
flex-grow: 1;
flex-direction: column; flex-direction: column;
} -webkit-overflow-scrolling: touch;
#chat .channel .chat {
right: 180px;
} }
#viewport.rt .chat { #viewport.rt .chat {
right: 0; right: 0;
} }
#chat .sidebar { #chat .userlist {
background: #fff; background: #fff;
border-left: 1px solid #e7e7e7; border-left: 1px solid #e7e7e7;
bottom: 0;
position: absolute;
right: 0;
width: 180px; width: 180px;
transition: right 0.4s; display: flex;
flex-direction: column;
flex-shrink: 0;
touch-action: pan-y; touch-action: pan-y;
} }
@ -1456,16 +1449,13 @@ part/quit messages where we don't load previews (adds a blank line otherwise) */
display: none; display: none;
} }
#chat .count { #chat .userlist .count {
background: #fafafa; background: #fafafa;
height: 48px; height: 48px;
left: 0; flex-shrink: 0;
position: absolute;
right: 0;
top: 0;
} }
#chat .search { #chat .userlist .search {
color: #222; color: #222;
border: 0; border: 0;
background: none; background: none;
@ -1476,17 +1466,14 @@ part/quit messages where we don't load previews (adds a blank line otherwise) */
width: 100%; width: 100%;
} }
#chat .names { #chat .userlist .names {
bottom: 0; flex-grow: 1;
overflow: auto; overflow: auto;
overflow-x: hidden; overflow-x: hidden;
will-change: transform, scroll-position;
-webkit-overflow-scrolling: touch;
padding-bottom: 10px; padding-bottom: 10px;
position: absolute;
top: 48px;
width: 100%; width: 100%;
touch-action: pan-y; touch-action: pan-y;
-webkit-overflow-scrolling: touch;
} }
#chat .names-filtered { #chat .names-filtered {
@ -1973,7 +1960,7 @@ part/quit messages where we don't load previews (adds a blank line otherwise) */
.context-menu-item:hover, .context-menu-item:hover,
.textcomplete-item:hover, .textcomplete-item:hover,
.textcomplete-menu .active, .textcomplete-menu .active,
#chat .users .user.active { #chat .userlist .user.active {
background-color: #f6f6f6; background-color: #f6f6f6;
transition: none; transition: none;
} }
@ -2320,10 +2307,6 @@ part/quit messages where we don't load previews (adds a blank line otherwise) */
left: 0; left: 0;
} }
#chat .chat {
right: 0;
}
#viewport .lt, #viewport .lt,
#viewport .channel .rt { #viewport .channel .rt {
display: flex; display: flex;
@ -2333,16 +2316,17 @@ part/quit messages where we don't load previews (adds a blank line otherwise) */
display: block; display: block;
} }
#chat .channel .chat { #chat .userlist {
height: 100%;
position: absolute;
right: 0; right: 0;
transform: translateX(180px);
transition: transform 0.2s;
} }
#chat .sidebar { #viewport.rt #chat .userlist {
right: -180px; display: flex;
} transform: translateX(0);
#viewport.rt #chat .sidebar {
right: 0;
} }
#chat .header .title { #chat .header .title {

View file

@ -263,7 +263,7 @@ function fuzzyGrep(term, array) {
function rawNicks() { function rawNicks() {
const chan = chat.find(".active"); const chan = chat.find(".active");
const users = chan.find(".users"); const users = chan.find(".userlist");
// If this channel has a list of nicks, just return it // If this channel has a list of nicks, just return it
if (users.length > 0) { if (users.length > 0) {

View file

@ -41,11 +41,12 @@ $(function() {
} }
}); });
viewport.on("click", ".rt", function(e) { viewport.on("click", ".rt", function() {
const self = $(this); const self = $(this);
viewport.toggleClass(self.prop("class")); viewport.toggleClass(self.prop("class"));
e.stopPropagation(); chat.find(".chan.active .chat").trigger("keepToBottom");
chat.find(".chan.active .chat").trigger("msg.sticky");
return false;
}); });
function positionContextMenu(that, e) { function positionContextMenu(that, e) {
@ -212,7 +213,7 @@ $(function() {
+ Math.round(parseFloat(style.borderBottomWidth) || 0) + Math.round(parseFloat(style.borderBottomWidth) || 0)
) + "px"; ) + "px";
chat.find(".chan.active .chat").trigger("msg.sticky"); // fix growing chat.find(".chan.active .chat").trigger("keepToBottom"); // fix growing
}); });
let focus = $.noop; let focus = $.noop;
@ -553,7 +554,7 @@ $(function() {
text: "/whois " + itemData, text: "/whois " + itemData,
}); });
$(`.channel.active .users .user[data-name="${itemData}"]`).trigger("click"); $(`.channel.active .userlist .user[data-name="${itemData}"]`).trigger("click");
}, },
query: function(itemData) { query: function(itemData) {
const chan = utils.findCurrentNetworkChan(itemData); const chan = utils.findCurrentNetworkChan(itemData);

View file

@ -158,7 +158,7 @@ function renderUnreadMarker(template, firstUnread, channel) {
} }
function renderChannelUsers(data) { function renderChannelUsers(data) {
const users = chat.find("#chan-" + data.id).find(".users"); const users = chat.find("#chan-" + data.id).find(".userlist");
const nicks = data.users const nicks = data.users
.concat() // Make a copy of the user list, sort is applied in-place .concat() // Make a copy of the user list, sort is applied in-place
.sort((a, b) => b.lastMessage - a.lastMessage) .sort((a, b) => b.lastMessage - a.lastMessage)
@ -167,7 +167,7 @@ function renderChannelUsers(data) {
// Before re-rendering the list of names, there might have been an entry // Before re-rendering the list of names, there might have been an entry
// marked as active (i.e. that was highlighted by keyboard navigation). // marked as active (i.e. that was highlighted by keyboard navigation).
// It is `undefined` if there was none. // It is `undefined` if there was none.
const previouslyActive = users.find(".active").data("name"); const previouslyActive = users.find(".active");
const search = users const search = users
.find(".search") .find(".search")
@ -185,11 +185,11 @@ function renderChannelUsers(data) {
// If a nick was highlighted before re-rendering the lists, re-highlight it in // If a nick was highlighted before re-rendering the lists, re-highlight it in
// the newly-rendered list. // the newly-rendered list.
if (previouslyActive) { if (previouslyActive.length > 0) {
// We need to un-highlight everything first because triggering `input` with // We need to un-highlight everything first because triggering `input` with
// a value highlights the first entry. // a value highlights the first entry.
users.find(".user").removeClass("active"); users.find(".user").removeClass("active");
users.find(`.user[data-name="${previouslyActive}"]`).addClass("active"); users.find(`.user[data-name="${previouslyActive.data("name")}"]`).addClass("active");
} }
return users; return users;

View file

@ -102,7 +102,7 @@ function processReceivedMessage(data) {
} }
if ((data.msg.type === "message" || data.msg.type === "action") && channel.hasClass("channel")) { if ((data.msg.type === "message" || data.msg.type === "action") && channel.hasClass("channel")) {
const nicks = channel.find(".users").data("nicks"); const nicks = channel.find(".userlist").data("nicks");
if (nicks) { if (nicks) {
const find = nicks.indexOf(data.msg.from.nick); const find = nicks.indexOf(data.msg.from.nick);

View file

@ -8,9 +8,9 @@ const templates = require("../views");
const chat = $("#chat"); const chat = $("#chat");
chat.on("input", ".users .search", function() { chat.on("input", ".userlist .search", function() {
const value = $(this).val(); const value = $(this).val();
const parent = $(this).closest(".users"); const parent = $(this).closest(".userlist");
const names = parent.find(".names-original"); const names = parent.find(".names-original");
const container = parent.find(".names-filtered"); const container = parent.find(".names-filtered");
@ -42,22 +42,24 @@ chat.on("input", ".users .search", function() {
container.find(".user").first().addClass("active"); container.find(".user").first().addClass("active");
}); });
chat.on("mouseenter", ".users .user", function() { chat.on("mouseenter", ".userlist .user", function() {
// Reset any potential selection, this is required in cas there is already a // Reset any potential selection, this is required in cas there is already a
// nick previously selected by keyboard // nick previously selected by keyboard
$(".users .user").removeClass("active"); $(this).parent().find(".user.active").removeClass("active");
$(this).addClass("active"); $(this).addClass("active");
}); });
chat.on("mouseleave", ".users .user", function() { chat.on("mouseleave", ".userlist .user", function() {
// Reset any potential selection // Reset any potential selection
$(".users .user").removeClass("active"); $(this).parent().find(".user.active").removeClass("active");
}); });
exports.handleKeybinds = function(input) { exports.handleKeybinds = function(input) {
Mousetrap(input.get(0)).bind(["up", "down"], (_e, key) => { Mousetrap(input.get(0)).bind(["up", "down"], (e, key) => {
const userlists = input.closest(".users"); e.preventDefault();
const userlists = input.closest(".userlist");
let userlist; let userlist;
// If input field has content, use the filtered list instead // If input field has content, use the filtered list instead
@ -69,13 +71,17 @@ exports.handleKeybinds = function(input) {
const users = userlist.find(".user"); const users = userlist.find(".user");
if (users.length === 0) {
return;
}
// Find which item in the array of users is currently selected, if any. // Find which item in the array of users is currently selected, if any.
// Returns -1 if none. // Returns -1 if none.
const activeIndex = users.toArray() const activeIndex = users.toArray()
.findIndex((user) => user.classList.contains("active")); .findIndex((user) => user.classList.contains("active"));
// Now that we know which user is active, reset any selection // Now that we know which user is active, reset any selection
userlists.find(".user").removeClass("active"); userlist.find(".user.active").removeClass("active");
// Mark next/previous user as active. // Mark next/previous user as active.
if (key === "down") { if (key === "down") {
@ -87,23 +93,13 @@ exports.handleKeybinds = function(input) {
} }
// Adjust scroll when active item is outside of the visible area // Adjust scroll when active item is outside of the visible area
const userlistHeight = userlist.height(); userlist.find(".user.active")[0].scrollIntoView(false);
const userlistScroll = userlist.scrollTop();
const active = $(".user.active");
const activeTop = active.position().top;
const activeHeight = active.height();
if (activeTop > userlistHeight - activeHeight) {
userlist.scrollTop(userlistScroll + activeTop - userlistHeight + activeHeight);
} else if (activeTop < 0) {
userlist.scrollTop(userlistScroll + activeTop - activeHeight);
}
}); });
// When pressing Enter, open the context menu (emit a click) on the active // When pressing Enter, open the context menu (emit a click) on the active
// user // user
Mousetrap(input.get(0)).bind("enter", () => { Mousetrap(input.get(0)).bind("enter", () => {
const user = input.closest(".users").find(".user.active"); const user = input.closest(".userlist").find(".user.active");
if (user.length) { if (user.length) {
const clickEvent = new $.Event("click"); const clickEvent = new $.Event("click");

View file

@ -48,7 +48,7 @@ function hasRoleInChannel(channel, roles) {
const channelID = channel.data("id"); const channelID = channel.data("id");
const network = $("#sidebar .network").has(`.chan[data-id="${channelID}"]`); const network = $("#sidebar .network").has(`.chan[data-id="${channelID}"]`);
const ownNick = network.data("nick"); const ownNick = network.data("nick");
const user = channel.find(`.users .user[data-name="${escape(ownNick)}"]`).first(); const user = channel.find(`.names-original .user[data-name="${escape(ownNick)}"]`).first();
return user.parent().is("." + roles.join(", .")); return user.parent().is("." + roles.join(", ."));
} }

View file

@ -91,7 +91,7 @@ a:hover,
#windows .header .topic, #windows .header .topic,
.messages .msg, .messages .msg,
.sidebar { .userlist {
line-height: 1.8; line-height: 1.8;
} }

View file

@ -31,7 +31,7 @@ body {
} }
#main, #main,
#chat .sidebar, #chat .userlist,
#windows .chan, #windows .chan,
#windows .window { #windows .window {
background: #333c4a; background: #333c4a;
@ -50,7 +50,7 @@ body {
#chat .content, #chat .content,
#windows .header, #windows .header,
#chat .user-mode::before, #chat .user-mode::before,
#chat .sidebar { #chat .userlist {
border-color: #2a323d; border-color: #2a323d;
} }

View file

@ -32,7 +32,7 @@ body {
} }
#main, #main,
#chat .sidebar, #chat .userlist,
#windows .chan, #windows .chan,
#windows .window { #windows .window {
background: #3f3f3f; background: #3f3f3f;
@ -76,7 +76,7 @@ body {
#chat .content, #chat .content,
#windows .header, #windows .header,
#chat .user-mode::before, #chat .user-mode::before,
#chat .sidebar { #chat .userlist {
border-color: #333; border-color: #333;
} }

View file

@ -18,22 +18,22 @@
</span> </span>
{{/equal}} {{/equal}}
</div> </div>
<div class="chat"> <div class="chat-content">
<div class="show-more{{#if messages.length}} show{{/if}}"> <div class="chat">
<button class="show-more-button" data-id="{{id}}">Show older messages</button> <div class="show-more{{#if messages.length}} show{{/if}}">
<button class="show-more-button" data-id="{{id}}">Show older messages</button>
</div>
<div class="messages" role="log" aria-live="polite" aria-relevant="additions"></div>
</div> </div>
<div class="messages" role="log" aria-live="polite" aria-relevant="additions"></div> {{#equal type "channel"}}
</div> <aside class="userlist">
{{#equal type "channel"}}
<aside class="sidebar">
<div class="users">
<div class="count"> <div class="count">
<input type="search" class="search" aria-label="Search among the user list" tabindex="-1"> <input type="search" class="search" aria-label="Search among the user list" tabindex="-1">
</div> </div>
<div class="names names-filtered"></div> <div class="names names-filtered"></div>
<div class="names names-original"></div> <div class="names names-original"></div>
</div> </aside>
</aside> {{/equal}}
{{/equal}} </div>
</div> </div>
{{/each}} {{/each}}