Spit and polish
* Add a distinctly colored title and background for DM threads apart from the public channels * On the Who List, the profile picture is clickable to open profile links * Fix auto-scrolling issues: it won't autoscroll if the new message was in a different channel, and when toggling between channels always scroll back to the bottom of that channel
This commit is contained in:
parent
da29117741
commit
a797bc45da
|
@ -38,6 +38,10 @@
|
||||||
color: #56cf98 !important;
|
color: #56cf98 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.has-background-dm {
|
||||||
|
background-color: #100010 !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* end nonshy custom overrides */
|
/* end nonshy custom overrides */
|
||||||
|
|
||||||
html {
|
html {
|
||||||
|
|
|
@ -19,6 +19,14 @@ body {
|
||||||
overflow: hidden !important;
|
overflow: hidden !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* DM title and bg color */
|
||||||
|
.has-background-private {
|
||||||
|
background-color: #b748c7;
|
||||||
|
}
|
||||||
|
.has-background-dm {
|
||||||
|
background-color: #ffefff;
|
||||||
|
}
|
||||||
|
|
||||||
/************************
|
/************************
|
||||||
* Main CSS Grid Layout *
|
* Main CSS Grid Layout *
|
||||||
************************/
|
************************/
|
||||||
|
@ -149,16 +157,16 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* User configurable font size increases */
|
/* User configurable font size increases */
|
||||||
#chatHistory.x1 {
|
#chatHistory .x1 {
|
||||||
font-size: large;
|
font-size: large;
|
||||||
}
|
}
|
||||||
#chatHistory.x2 {
|
#chatHistory .x2 {
|
||||||
font-size: x-large;
|
font-size: x-large;
|
||||||
}
|
}
|
||||||
#chatHistory.x3 {
|
#chatHistory .x3 {
|
||||||
font-size: xx-large;
|
font-size: xx-large;
|
||||||
}
|
}
|
||||||
#chatHistory.x4 {
|
#chatHistory .x4 {
|
||||||
font-size: 64pt;
|
font-size: 64pt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -852,7 +852,7 @@ const app = Vue.createApp({
|
||||||
// Set active chat room.
|
// Set active chat room.
|
||||||
setChannel(channel) {
|
setChannel(channel) {
|
||||||
this.channel = typeof(channel) === "string" ? channel : channel.ID;
|
this.channel = typeof(channel) === "string" ? channel : channel.ID;
|
||||||
this.scrollHistory();
|
this.scrollHistory(this.channel, true);
|
||||||
this.channels[this.channel].unread = 0;
|
this.channels[this.channel].unread = 0;
|
||||||
|
|
||||||
// Responsive CSS: switch back to chat panel upon selecting a channel.
|
// Responsive CSS: switch back to chat panel upon selecting a channel.
|
||||||
|
@ -1376,7 +1376,7 @@ const app = Vue.createApp({
|
||||||
isChatServer,
|
isChatServer,
|
||||||
isChatClient,
|
isChatClient,
|
||||||
});
|
});
|
||||||
this.scrollHistory();
|
this.scrollHistory(channel);
|
||||||
|
|
||||||
// Mark unread notifiers if this is not our channel.
|
// Mark unread notifiers if this is not our channel.
|
||||||
if (this.channel !== channel) {
|
if (this.channel !== channel) {
|
||||||
|
@ -1390,10 +1390,13 @@ const app = Vue.createApp({
|
||||||
this.makeLinksExternal();
|
this.makeLinksExternal();
|
||||||
},
|
},
|
||||||
|
|
||||||
scrollHistory() {
|
scrollHistory(channel, force) {
|
||||||
if (!this.autoscroll) return;
|
if (!this.autoscroll && !force) return;
|
||||||
|
|
||||||
window.requestAnimationFrame(() => {
|
window.requestAnimationFrame(() => {
|
||||||
|
// Only scroll if it's the current channel.
|
||||||
|
if (channel !== this.channel) return;
|
||||||
|
|
||||||
this.historyScrollbox.scroll({
|
this.historyScrollbox.scroll({
|
||||||
top: this.historyScrollbox.scrollHeight,
|
top: this.historyScrollbox.scrollHeight,
|
||||||
behavior: 'smooth',
|
behavior: 'smooth',
|
||||||
|
|
|
@ -563,7 +563,8 @@
|
||||||
<div class="chat-column">
|
<div class="chat-column">
|
||||||
|
|
||||||
<div class="card grid-card">
|
<div class="card grid-card">
|
||||||
<header class="card-header has-background-link">
|
<header class="card-header"
|
||||||
|
:class="{'has-background-private': isDM, 'has-background-link': !isDM}">
|
||||||
<div class="columns is-mobile card-header-title has-text-light">
|
<div class="columns is-mobile card-header-title has-text-light">
|
||||||
<div class="column is-narrow mobile-only pr-0">
|
<div class="column is-narrow mobile-only pr-0">
|
||||||
<!-- Responsive mobile button to pan to Left Column -->
|
<!-- Responsive mobile button to pan to Left Column -->
|
||||||
|
@ -712,7 +713,7 @@
|
||||||
-->
|
-->
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="card-content" id="chatHistory" :class="fontSizeClass">
|
<div class="card-content" id="chatHistory" :class="{'has-background-dm': isDM}">
|
||||||
|
|
||||||
<div class="autoscroll-field tag">
|
<div class="autoscroll-field tag">
|
||||||
<label class="checkbox is-size-6" title="Automatically scroll when new chat messages come in.">
|
<label class="checkbox is-size-6" title="Automatically scroll when new chat messages come in.">
|
||||||
|
@ -723,130 +724,134 @@
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- No history? -->
|
<div :class="fontSizeClass">
|
||||||
<div v-if="chatHistory.length === 0">
|
|
||||||
<em v-if="isDM">
|
|
||||||
Starting a direct message chat with [[channel]]. Type a message and say hello!
|
|
||||||
</em>
|
|
||||||
<em v-else>
|
|
||||||
There are no messages in this channel yet.
|
|
||||||
</em>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-for="(msg, i) in chatHistory" v-bind:key="i">
|
|
||||||
<!-- Enter chat presence messages draw as a short banner -->
|
|
||||||
<div v-if="msg.action === 'presence'" class="notification is-success is-light py-1 px-3 mb-2">
|
|
||||||
|
|
||||||
<!-- Tiny avatar next to name and action buttons -->
|
|
||||||
<div class="columns is-mobile">
|
|
||||||
<div class="column is-narrow pr-0 pt-4">
|
|
||||||
<figure class="image is-16x16">
|
|
||||||
<img v-if="avatarForUsername(msg.username)"
|
|
||||||
:src="avatarForUsername(msg.username)">
|
|
||||||
<img v-else src="/static/img/shy.png">
|
|
||||||
</figure>
|
|
||||||
</div>
|
|
||||||
<div class="column">
|
|
||||||
<strong>[[ nicknameForUsername(msg.username) ]]</strong>
|
|
||||||
<small v-if="isUsernameOnline(msg.username)" class="ml-1">(@[[msg.username]])</small>
|
|
||||||
[[msg.message]]
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<!-- No history? -->
|
||||||
|
<div v-if="chatHistory.length === 0">
|
||||||
|
<em v-if="isDM">
|
||||||
|
Starting a direct message chat with [[channel]]. Type a message and say hello!
|
||||||
|
</em>
|
||||||
|
<em v-else>
|
||||||
|
There are no messages in this channel yet.
|
||||||
|
</em>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Normal chat message: full size card w/ avatar -->
|
<div v-for="(msg, i) in chatHistory" v-bind:key="i">
|
||||||
<div v-else class="box mb-2 px-4 pt-3 pb-1">
|
<!-- Enter chat presence messages draw as a short banner -->
|
||||||
<div class="media mb-0">
|
<div v-if="msg.action === 'presence'" class="notification is-success is-light py-1 px-3 mb-2">
|
||||||
<div class="media-left">
|
|
||||||
<a :href="profileURLForUsername(msg.username)" @click.prevent="openProfile({username: msg.username})"
|
<!-- Tiny avatar next to name and action buttons -->
|
||||||
:class="{'cursor-default': !profileURLForUsername(msg.username)}">
|
<div class="columns is-mobile">
|
||||||
<figure class="image is-48x48">
|
<div class="column is-narrow pr-0 pt-4">
|
||||||
<img v-if="msg.isChatServer"
|
<figure class="image is-16x16">
|
||||||
src="/static/img/server.png">
|
<img v-if="avatarForUsername(msg.username)"
|
||||||
<img v-else-if="msg.isChatClient"
|
|
||||||
src="/static/img/client.png">
|
|
||||||
<img v-else-if="avatarForUsername(msg.username)"
|
|
||||||
:src="avatarForUsername(msg.username)">
|
:src="avatarForUsername(msg.username)">
|
||||||
<img v-else src="/static/img/shy.png">
|
<img v-else src="/static/img/shy.png">
|
||||||
</figure>
|
</figure>
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div class="media-content">
|
|
||||||
<div class="columns is-mobile pb-0">
|
|
||||||
<div class="column is-narrow pb-0">
|
|
||||||
<label class="label"
|
|
||||||
:class="{'has-text-success is-dark': msg.isChatServer,
|
|
||||||
'has-text-warning is-dark': msg.isAdmin,
|
|
||||||
'has-text-danger': msg.isChatClient}">
|
|
||||||
|
|
||||||
<!-- User nickname/display name -->
|
|
||||||
[[nicknameForUsername(msg.username)]]
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="column has-text-right pb-0">
|
|
||||||
<small class="has-text-grey is-size-7" :title="msg.at">[[prettyDate(msg.at)]]</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="column">
|
||||||
<!-- User @username below it which may link to a profile URL if JWT -->
|
<strong>[[ nicknameForUsername(msg.username) ]]</strong>
|
||||||
<div class="columns is-mobile pt-0" v-if="(msg.isChatClient || msg.isChatServer)">
|
<small v-if="isUsernameOnline(msg.username)" class="ml-1">(@[[msg.username]])</small>
|
||||||
<div class="column is-narrow pt-0">
|
[[msg.message]]
|
||||||
<small v-if="!(msg.isChatClient || msg.isChatServer)">
|
|
||||||
<a v-if="profileURLForUsername(msg.username)"
|
|
||||||
:href="profileURLForUsername(msg.username)"
|
|
||||||
target="_blank"
|
|
||||||
class="has-text-grey">
|
|
||||||
@[[msg.username]]
|
|
||||||
</a>
|
|
||||||
<span v-else class="has-text-grey">@[[msg.username]]</span>
|
|
||||||
</small>
|
|
||||||
<small v-else class="has-text-grey">internal</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-else class="columns is-mobile pt-0">
|
|
||||||
<div class="column is-narrow pt-0">
|
|
||||||
<small v-if="!(msg.isChatClient || msg.isChatServer)">
|
|
||||||
<a v-if="profileURLForUsername(msg.username)"
|
|
||||||
:href="profileURLForUsername(msg.username)"
|
|
||||||
target="_blank"
|
|
||||||
class="has-text-grey">
|
|
||||||
@[[msg.username]]
|
|
||||||
</a>
|
|
||||||
<span v-else class="has-text-grey">@[[msg.username]]</span>
|
|
||||||
</small>
|
|
||||||
<small v-else class="has-text-grey">internal</small>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="column is-narrow px-1 pt-0"
|
|
||||||
v-if="!(msg.username === username || isDM)">
|
|
||||||
<!-- DMs button -->
|
|
||||||
<button type="button"
|
|
||||||
class="button is-grey is-outlined is-small px-2"
|
|
||||||
@click="openDMs({username: msg.username})">
|
|
||||||
<i class="fa fa-message"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="column is-narrow pl-1 pt-0"
|
|
||||||
v-if="!(msg.username === username)">
|
|
||||||
<!-- Mute button -->
|
|
||||||
<button type="button"
|
|
||||||
class="button is-grey is-outlined is-small px-2"
|
|
||||||
@click="muteUser(msg.username)"
|
|
||||||
title="Mute user">
|
|
||||||
<i class="fa fa-comment-slash"
|
|
||||||
:class="{'has-text-success': isMutedUser(msg.username),
|
|
||||||
'has-text-danger': !isMutedUser(msg.username)}"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Message box -->
|
<!-- Normal chat message: full size card w/ avatar -->
|
||||||
<div class="content pl-5 py-3 mb-0">
|
<div v-else class="box mb-2 px-4 pt-3 pb-1">
|
||||||
<em v-if="msg.action === 'presence'">[[msg.message]]</em>
|
<div class="media mb-0">
|
||||||
<div v-else v-html="msg.message"></div>
|
<div class="media-left">
|
||||||
|
<a :href="profileURLForUsername(msg.username)" @click.prevent="openProfile({username: msg.username})"
|
||||||
|
:class="{'cursor-default': !profileURLForUsername(msg.username)}">
|
||||||
|
<figure class="image is-48x48">
|
||||||
|
<img v-if="msg.isChatServer"
|
||||||
|
src="/static/img/server.png">
|
||||||
|
<img v-else-if="msg.isChatClient"
|
||||||
|
src="/static/img/client.png">
|
||||||
|
<img v-else-if="avatarForUsername(msg.username)"
|
||||||
|
:src="avatarForUsername(msg.username)">
|
||||||
|
<img v-else src="/static/img/shy.png">
|
||||||
|
</figure>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="media-content">
|
||||||
|
<div class="columns is-mobile pb-0">
|
||||||
|
<div class="column is-narrow pb-0">
|
||||||
|
<strong
|
||||||
|
:class="{'has-text-success is-dark': msg.isChatServer,
|
||||||
|
'has-text-warning is-dark': msg.isAdmin,
|
||||||
|
'has-text-danger': msg.isChatClient}">
|
||||||
|
|
||||||
|
<!-- User nickname/display name -->
|
||||||
|
[[nicknameForUsername(msg.username)]]
|
||||||
|
</strong>
|
||||||
|
</div>
|
||||||
|
<div class="column has-text-right pb-0">
|
||||||
|
<small class="has-text-grey is-size-7" :title="msg.at">[[prettyDate(msg.at)]]</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- User @username below it which may link to a profile URL if JWT -->
|
||||||
|
<div class="columns is-mobile pt-0" v-if="(msg.isChatClient || msg.isChatServer)">
|
||||||
|
<div class="column is-narrow pt-0">
|
||||||
|
<small v-if="!(msg.isChatClient || msg.isChatServer)">
|
||||||
|
<a v-if="profileURLForUsername(msg.username)"
|
||||||
|
:href="profileURLForUsername(msg.username)"
|
||||||
|
target="_blank"
|
||||||
|
class="has-text-grey">
|
||||||
|
@[[msg.username]]
|
||||||
|
</a>
|
||||||
|
<span v-else class="has-text-grey">@[[msg.username]]</span>
|
||||||
|
</small>
|
||||||
|
<small v-else class="has-text-grey">internal</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="columns is-mobile pt-0">
|
||||||
|
<div class="column is-narrow pt-0">
|
||||||
|
<small v-if="!(msg.isChatClient || msg.isChatServer)">
|
||||||
|
<a v-if="profileURLForUsername(msg.username)"
|
||||||
|
:href="profileURLForUsername(msg.username)"
|
||||||
|
target="_blank"
|
||||||
|
class="has-text-grey">
|
||||||
|
@[[msg.username]]
|
||||||
|
</a>
|
||||||
|
<span v-else class="has-text-grey">@[[msg.username]]</span>
|
||||||
|
</small>
|
||||||
|
<small v-else class="has-text-grey">internal</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="column is-narrow px-1 pt-0"
|
||||||
|
v-if="!(msg.username === username || isDM)">
|
||||||
|
<!-- DMs button -->
|
||||||
|
<button type="button"
|
||||||
|
class="button is-grey is-outlined is-small px-2"
|
||||||
|
@click="openDMs({username: msg.username})">
|
||||||
|
<i class="fa fa-message"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="column is-narrow pl-1 pt-0"
|
||||||
|
v-if="!(msg.username === username)">
|
||||||
|
<!-- Mute button -->
|
||||||
|
<button type="button"
|
||||||
|
class="button is-grey is-outlined is-small px-2"
|
||||||
|
@click="muteUser(msg.username)"
|
||||||
|
title="Mute user">
|
||||||
|
<i class="fa fa-comment-slash"
|
||||||
|
:class="{'has-text-success': isMutedUser(msg.username),
|
||||||
|
'has-text-danger': !isMutedUser(msg.username)}"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Message box -->
|
||||||
|
<div class="content pl-5 py-3 mb-0">
|
||||||
|
<em v-if="msg.action === 'presence'">[[msg.message]]</em>
|
||||||
|
<div v-else v-html="msg.message"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -948,16 +953,20 @@
|
||||||
<div class="columns is-mobile">
|
<div class="columns is-mobile">
|
||||||
<!-- Avatar URL if available -->
|
<!-- Avatar URL if available -->
|
||||||
<div class="column is-narrow pr-0" style="position: relative">
|
<div class="column is-narrow pr-0" style="position: relative">
|
||||||
<img v-if="u.avatar" :src="avatarURL(u)"
|
<a :href="profileURLForUsername(u.username)" @click.prevent="openProfile({username: u.username})"
|
||||||
width="24" height="24"
|
:class="{'cursor-default': !profileURLForUsername(u.username)}"
|
||||||
:alt="'Avatar image for ' + u.username">
|
class="p-0">
|
||||||
<img v-else src="/static/img/shy.png"
|
<img v-if="u.avatar" :src="avatarURL(u)"
|
||||||
width="24" height="24">
|
width="24" height="24"
|
||||||
|
:alt="'Avatar image for ' + u.username">
|
||||||
|
<img v-else src="/static/img/shy.png"
|
||||||
|
width="24" height="24">
|
||||||
|
|
||||||
<!-- Away symbol -->
|
<!-- Away symbol -->
|
||||||
<div v-if="u.status !== 'online'" class="status-away-icon" :title="'Status: '+u.status">
|
<div v-if="u.status !== 'online'" class="status-away-icon" :title="'Status: '+u.status">
|
||||||
<i class="fa fa-clock has-text-light"></i>
|
<i class="fa fa-clock has-text-light"></i>
|
||||||
</div>
|
</div>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="column pr-0"
|
<div class="column pr-0"
|
||||||
:class="{'pl-1': u.avatar}">
|
:class="{'pl-1': u.avatar}">
|
||||||
|
|
Loading…
Reference in New Issue
Block a user