Track mentions and add a window to view them

This commit is contained in:
Pavel Djundik 2019-11-04 11:21:05 +02:00
parent 8e00e26054
commit bc4f9b5f51
11 changed files with 239 additions and 0 deletions

View file

@ -3,6 +3,7 @@
<Sidebar v-if="$store.state.appLoaded" :overlay="$refs.overlay" />
<div id="sidebar-overlay" ref="overlay" @click="$store.commit('sidebarOpen', false)" />
<router-view ref="window"></router-view>
<Mentions />
<ImageViewer ref="imageViewer" />
<ContextMenu ref="contextMenu" />
<ConfirmDialog ref="confirmDialog" />
@ -21,6 +22,7 @@ import Sidebar from "./Sidebar.vue";
import ImageViewer from "./ImageViewer.vue";
import ContextMenu from "./ContextMenu.vue";
import ConfirmDialog from "./ConfirmDialog.vue";
import Mentions from "./Mentions.vue";
export default {
name: "App",
@ -29,6 +31,7 @@ export default {
ImageViewer,
ContextMenu,
ConfirmDialog,
Mentions,
},
computed: {
viewportClasses() {

View file

@ -38,6 +38,11 @@
:network="network"
:text="channel.topic"
/></span>
<button
class="mentions"
aria-label="Open your mentions"
@click="openMentions"
/>
<button
class="menu"
aria-label="Open the context menu"
@ -198,6 +203,11 @@ export default {
network: this.network,
});
},
openMentions() {
this.$root.$emit("mentions:toggle", {
event: event,
});
},
},
};
</script>

View file

@ -0,0 +1,173 @@
<template>
<div
v-if="isOpen"
id="mentions-popup-container"
@click="containerClick"
@contextmenu.prevent="containerClick"
>
<div class="mentions-popup">
<div class="mentions-popup-title">
Recent mentions
<span v-if="isLoading">- Loading</span>
</div>
<template v-if="resolvedMessages.length === 0">
<p>There are no recent mentions.</p>
</template>
<template v-for="message in resolvedMessages" v-else>
<div :key="message.id" :class="['msg', message.type]">
<span class="from">
<Username :user="message.from" />
<template v-if="message.channel">
in {{ message.channel.channel.name }} on
{{ message.channel.network.name }}
</template>
<template v-else>
in unknown channel
</template>
</span>
<span :title="message.time | localetime" class="time">
{{ messageTime(message.time) }}
</span>
<button
class="msg-hide"
aria-label="Hide this mention"
@click="hideMention(message)"
></button>
<div class="content" dir="auto">
<ParsedMessage :network="null" :message="message" />
</div>
</div>
</template>
</div>
</div>
</template>
<style>
.mentions-popup {
background-color: var(--window-bg-color);
position: absolute;
width: 400px;
right: 80px;
top: 55px;
max-height: 400px;
overflow-y: scroll;
z-index: 2;
padding: 10px;
}
.mentions-popup > .mentions-popup-title {
margin-bottom: 10px;
font-size: 20px;
}
.mentions-popup .msg {
margin-bottom: 15px;
user-select: text;
}
.mentions-popup .msg:last-child {
margin-bottom: 0;
}
.mentions-popup .msg .content {
background-color: var(--highlight-bg-color);
border-radius: 5px;
padding: 6px;
margin-top: 2px;
}
.mentions-popup .msg-hide::before {
font-size: 20px;
font-weight: normal;
display: inline-block;
line-height: 16px;
text-align: center;
content: "×";
}
@media (max-width: 768px) {
.mentions-popup {
border-radius: 0;
border: 0;
box-shadow: none;
width: 100%;
max-height: none;
right: 0;
left: 0;
bottom: 0;
top: 45px; /* header height */
}
}
</style>
<script>
import Username from "./Username.vue";
import ParsedMessage from "./ParsedMessage.vue";
import socket from "../js/socket";
import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime";
dayjs.extend(relativeTime);
export default {
name: "Mentions",
components: {
Username,
ParsedMessage,
},
data() {
return {
isOpen: false,
isLoading: false,
};
},
computed: {
resolvedMessages() {
const messages = this.$store.state.mentions.slice().reverse();
for (const message of messages) {
message.channel = this.$store.getters.findChannel(message.chanId);
}
return messages;
},
},
watch: {
"$store.state.mentions"() {
this.isLoading = false;
},
},
mounted() {
this.$root.$on("mentions:toggle", this.openPopup);
},
destroyed() {
this.$root.$off("mentions:toggle", this.openPopup);
},
methods: {
messageTime(time) {
return dayjs(time).fromNow();
},
hideMention(message) {
this.$store.state.mentions.splice(
this.$store.state.mentions.findIndex((m) => m.msgId === message.msgId),
1
);
socket.emit("mentions:hide", message.msgId);
},
containerClick(event) {
if (event.currentTarget === event.target) {
this.isOpen = false;
}
},
openPopup() {
this.isOpen = !this.isOpen;
if (this.isOpen) {
this.isLoading = true;
socket.emit("mentions:get");
}
},
},
};
</script>

View file

@ -283,6 +283,7 @@ p {
#viewport .lt::before,
#viewport .rt::before,
#chat button.mentions::before,
#chat button.menu::before,
.channel-list-item::before,
#footer .icon,
@ -336,6 +337,7 @@ p {
#viewport .lt::before { content: "\f0c9"; /* http://fontawesome.io/icon/bars/ */ }
#viewport .rt::before { content: "\f0c0"; /* https://fontawesome.com/icons/users?style=solid */ }
#chat button.menu::before { content: "\f142"; /* http://fontawesome.io/icon/ellipsis-v/ */ }
#chat button.mentions::before { content: "\f1fa"; /* https://fontawesome.com/icons/at?style=solid */ }
.context-menu-join::before { content: "\f067"; /* http://fontawesome.io/icon/plus/ */ }
.context-menu-user::before { content: "\f007"; /* http://fontawesome.io/icon/user/ */ }
@ -547,6 +549,7 @@ p {
#viewport .lt,
#viewport .rt,
#chat button.mentions,
#chat button.menu {
color: #607992;
display: flex;
@ -560,6 +563,7 @@ p {
#viewport .lt::before,
#viewport .rt::before,
#chat button.mentions::before,
#chat button.menu::before {
width: 36px;
line-height: 36px; /* Fix alignment in Microsoft Edge */
@ -2166,6 +2170,7 @@ part/quit messages where we don't load previews (adds a blank line otherwise) */
opacity: 0.5;
}
#mentions-popup-container,
#context-menu-container {
position: absolute;
top: 0;
@ -2176,6 +2181,7 @@ part/quit messages where we don't load previews (adds a blank line otherwise) */
background: transparent;
}
.mentions-popup,
#context-menu,
.textcomplete-menu {
position: absolute;

View file

@ -24,3 +24,4 @@ import "./configuration";
import "./changelog";
import "./setting";
import "./history_clear";
import "./mentions";

View file

@ -0,0 +1,8 @@
"use strict";
import socket from "../socket";
import store from "../store";
socket.on("mentions:list", function (data) {
store.commit("mentions", data);
});

View file

@ -26,6 +26,7 @@ const store = new Vuex.Store({
isAutoCompleting: false,
isConnected: false,
networks: [],
mentions: [],
hasServiceWorker: false,
pushNotificationState: "unsupported",
serverConfiguration: null,
@ -60,6 +61,9 @@ const store = new Vuex.Store({
networks(state, networks) {
state.networks = networks;
},
mentions(state, mentions) {
state.mentions = mentions;
},
removeNetwork(state, networkId) {
state.networks.splice(
store.state.networks.findIndex((n) => n.uuid === networkId),

View file

@ -112,6 +112,7 @@ body {
#viewport .lt,
#viewport .rt,
#chat button.mentions,
#chat button.menu,
#form #submit {
color: #b7c5d1;

View file

@ -56,6 +56,7 @@ function Client(manager, name, config = {}) {
idMsg: 1,
name: name,
networks: [],
mentions: [],
manager: manager,
messageStorage: [],
highlightRegex: null,

View file

@ -179,5 +179,21 @@ module.exports = function (irc, network) {
true
);
}
// Keep track of all mentions in channels for this client
if (msg.highlight && chan.type === Chan.Type.CHANNEL) {
client.mentions.push({
chanId: chan.id,
msgId: msg.id,
type: msg.type,
time: msg.time,
text: msg.text,
from: msg.from,
});
if (client.mentions.length > 100) {
client.mentions.splice(0, client.mentions.length - 100);
}
}
}
};

View file

@ -536,6 +536,22 @@ function initializeClient(socket, client, token, lastMessage, openChannel) {
}
});
socket.on("mentions:get", () => {
socket.emit("mentions:list", client.mentions);
});
socket.on("mentions:hide", (msgId) => {
if (typeof msgId !== "number") {
return;
}
client.mentions.splice(
client.mentions.findIndex((m) => m.msgId === msgId),
1
);
// TODO: emit to other clients?
});
if (!Helper.config.public) {
socket.on("push:register", (subscription) => {
if (!Object.prototype.hasOwnProperty.call(client.config.sessions, token)) {