Add favoriting/pinning channels

This commit is contained in:
Max Leiter 2022-05-01 00:37:21 -07:00
parent f2a8d5aacc
commit a8935376a1
No known key found for this signature in database
GPG key ID: A3512F2F2F17EBDA
11 changed files with 86 additions and 63 deletions

View file

@ -15,13 +15,27 @@
> >
<span class="parted-channel-icon" /> <span class="parted-channel-icon" />
</span> </span>
<span class="close-tooltip tooltipped tooltipped-w" aria-label="Leave"> <span
<button class="close" aria-label="Leave" @click.stop="close" /> class="close-tooltip tooltipped tooltipped-w"
:aria-label="channel.favorite ? 'Unfavorite' : 'Leave'"
>
<button
class="close"
:aria-label="channel.favorite ? 'Unfavorite' : 'Leave'"
@click.stop="close"
/>
</span> </span>
</template> </template>
<template v-else> <template v-else>
<span class="close-tooltip tooltipped tooltipped-w" aria-label="Close"> <span
<button class="close" aria-label="Close" @click.stop="close" /> class="close-tooltip tooltipped tooltipped-w"
:aria-label="channel.favorite ? 'Unfavorite' : 'Close'"
>
<button
class="close"
:aria-label="channel.favorite ? 'Unfavorite' : 'Close'"
@click.stop="close"
/>
</span> </span>
</template> </template>
</ChannelWrapper> </ChannelWrapper>

View file

@ -57,6 +57,10 @@ export default {
const extra = []; const extra = [];
const type = this.channel.type; const type = this.channel.type;
if (this.channel.favorite) {
`favorited on ${this.network.name}`;
}
if (this.channel.unread > 0) { if (this.channel.unread > 0) {
if (this.channel.unread > 1) { if (this.channel.unread > 1) {
extra.push(`${this.channel.unread} unread messages`); extra.push(`${this.channel.unread} unread messages`);

View file

@ -13,13 +13,13 @@
:id="'chan-' + channel.id" :id="'chan-' + channel.id"
class="chat-view" class="chat-view"
:data-type="channel.type" :data-type="channel.type"
:aria-label="channel.name" :aria-label="channel.displayName ? channel.displayName : channel.name"
role="tabpanel" role="tabpanel"
> >
<div class="header"> <div class="header">
<SidebarToggle /> <SidebarToggle />
<span class="title" :aria-label="'Currently open ' + channel.type">{{ <span class="title" :aria-label="'Currently open ' + channel.type">{{
channel.name channel.displayName ? channel.displayName : channel.name
}}</span> }}</span>
<div v-if="channel.editTopic === true" class="topic-container"> <div v-if="channel.editTopic === true" class="topic-container">
<input <input

View file

@ -4,31 +4,39 @@
<div class="lobby-wrap"> <div class="lobby-wrap">
<CollapseFavoritesButton :on-collapse-click="onCollapseClick" /> <CollapseFavoritesButton :on-collapse-click="onCollapseClick" />
<span title="Favorites" class="name">Favorites</span> <span title="Favorites" class="name">Favorites</span>
<span v-if="unreadCount > 0" class="badge">{{ unreadCount }}</span>
</div> </div>
</div> </div>
<div v-for="channel in $store.state.favoriteChannels" :key="channel.id"> <Draggable
<Channel draggable=".channel-list-item"
:channel="channel" ghost-class="ui-sortable-ghost"
:network="network" drag-class="ui-sortable-dragging"
:is-filtering="false" :group="network.uuid"
:active=" :list="channels"
$store.state.activeChannel && channel === $store.state.activeChannel.channel :delay="longTouchDuration"
" :delay-on-touch-only="true"
/> :touch-start-threshold="10"
</div> class="channels"
@choose="onDraggableChoose"
@unchoose="onDraggableUnchoose"
>
<template v-for="channel in channels">
<Channel
:key="channel.id"
:channel="channel"
:network="network"
:is-filtering="false"
:active="
$store.state.activeChannel && channel === $store.state.activeChannel.channel
"
/>
</template>
</Draggable>
</div> </div>
</template> </template>
<style scoped>
.lobby-wrap {
display: flex;
/* margin-left: 40px; */
}
</style>
<script> <script>
import Draggable from "vuedraggable";
import eventbus from "../js/eventbus"; import eventbus from "../js/eventbus";
import roundBadgeNumber from "../js/helpers/roundBadgeNumber";
import Channel from "./Channel.vue"; import Channel from "./Channel.vue";
import CollapseFavoritesButton from "./CollapseFavoritesButton.vue"; import CollapseFavoritesButton from "./CollapseFavoritesButton.vue";
@ -37,9 +45,13 @@ export default {
components: { components: {
Channel, Channel,
CollapseFavoritesButton, CollapseFavoritesButton,
Draggable,
}, },
props: { props: {
channels: Array, channels: Array,
onDraggableUnchoose: Function,
onDraggableChoose: Function,
longTouchDuration: Number,
}, },
computed: { computed: {
network() { network() {
@ -52,7 +64,6 @@ export default {
}; };
}, },
}, },
methods: { methods: {
onCollapseClick() { onCollapseClick() {
this.$store.commit("toggleFavorites"); this.$store.commit("toggleFavorites");
@ -63,13 +74,6 @@ export default {
channel: this.channel, channel: this.channel,
}); });
}, },
unreadCount() {
const unread = this.channels.reduce((acc, channel) => {
return acc + channel.unread || 0;
}, 0);
return roundBadgeNumber(unread);
},
}, },
}; };
</script> </script>

View file

@ -80,7 +80,12 @@
role="region" role="region"
aria-live="polite" aria-live="polite"
> >
<Favorites :channels="$store.state.favoriteChannels" /> <Favorites
:channels="$store.state.favoriteChannels"
:long-touch-duration="LONG_TOUCH_DURATION"
:on-draggable-unchoose="onDraggableUnchoose"
:on-draggable-choose="onDraggableChoose"
/>
</div> </div>
<div <div
v-for="network in $store.state.networks" v-for="network in $store.state.networks"
@ -277,8 +282,6 @@ export default {
Mousetrap.bind("alt+shift+right", this.expandNetwork); Mousetrap.bind("alt+shift+right", this.expandNetwork);
Mousetrap.bind("alt+shift+left", this.collapseNetwork); Mousetrap.bind("alt+shift+left", this.collapseNetwork);
Mousetrap.bind("alt+j", this.toggleSearch); Mousetrap.bind("alt+j", this.toggleSearch);
console.log(this.$store.state.favoriteChannels[0]);
}, },
beforeDestroy() { beforeDestroy() {
Mousetrap.unbind("alt+shift+right", this.expandNetwork); Mousetrap.unbind("alt+shift+right", this.expandNetwork);

View file

@ -372,6 +372,8 @@ p {
.context-menu-clear-favorites::before, .context-menu-clear-favorites::before,
.context-menu-clear-history::before { content: "\f1f8"; /* https://fontawesome.com/icons/trash?style=solid */ } .context-menu-clear-history::before { content: "\f1f8"; /* https://fontawesome.com/icons/trash?style=solid */ }
.context-menu-mute::before { content: "\f6a9"; /* https://fontawesome.com/v5.15/icons/volume-mute?style=solid */ } .context-menu-mute::before { content: "\f6a9"; /* https://fontawesome.com/v5.15/icons/volume-mute?style=solid */ }
.context-menu-favorite::before { content: "\f005"; /* http://fontawesome.io/icon/star/ */ }
.channel-list-item .not-secure-icon::before { .channel-list-item .not-secure-icon::before {
content: "\f071"; /* https://fontawesome.com/icons/exclamation-triangle?style=solid */ content: "\f071"; /* https://fontawesome.com/icons/exclamation-triangle?style=solid */
@ -510,6 +512,7 @@ p {
color: #2ecc40; color: #2ecc40;
} }
#chat .msg[data-type="action"] .from::before { #chat .msg[data-type="action"] .from::before {
content: "\f005"; /* http://fontawesome.io/icon/star/ */ content: "\f005"; /* http://fontawesome.io/icon/star/ */
} }

View file

@ -97,9 +97,9 @@ export function generateChannelContextMenu($root, channel, network) {
class: "favorite", class: "favorite",
action() { action() {
if (channel.favorite) { if (channel.favorite) {
socket.emit("favorites:remove", channel.id); socket.emit("favorites:remove", Number(channel.id));
} else { } else {
socket.emit("favorites:add", channel.id); socket.emit("favorites:add", Number(channel.id));
} }
}, },
}); });
@ -210,7 +210,6 @@ export function generateChannelContextMenu($root, channel, network) {
}); });
} }
// Add close menu item
items.push({ items.push({
label: closeMap[channel.type], label: closeMap[channel.type],
type: "item", type: "item",

View file

@ -4,7 +4,5 @@ import socket from "../socket";
import store from "../store"; import store from "../store";
socket.on("favorites", function (data) { socket.on("favorites", function (data) {
console.log("favorites", data);
store.commit("favoriteChannels", data.favoriteChannels); store.commit("favoriteChannels", data.favoriteChannels);
}); });

View file

@ -132,7 +132,6 @@ const store = new Vuex.Store({
state.messageSearchResults = value; state.messageSearchResults = value;
}, },
favoriteChannels(state, payload) { favoriteChannels(state, payload) {
console.log("payload", payload);
state.favoriteChannels.forEach((channel) => { state.favoriteChannels.forEach((channel) => {
channel.favorite = false; channel.favorite = false;
channel.displayName = ""; channel.displayName = "";
@ -159,7 +158,7 @@ const store = new Vuex.Store({
); );
netChan.channel.displayName = netChan.channel.displayName =
netChan.channel.name + `(${netChan.network.name})`; netChan.channel.name + ` (${netChan.network.name})`;
otherNetChan.channel.displayName = otherNetChan.channel.displayName =
otherNetChan.channel.name + ` (${otherNetChan.network.name})`; otherNetChan.channel.name + ` (${otherNetChan.network.name})`;

View file

@ -50,16 +50,16 @@ new Vue({
}); });
} }
); );
} else if (channel.favorite) {
socket.emit("favorites:remove", Number(channel.id));
} else {
channel.closed = true;
return; socket.emit("input", {
target: Number(channel.id),
text: "/close",
});
} }
channel.closed = true;
socket.emit("input", {
target: Number(channel.id),
text: "/close",
});
}, },
}, },
render(createElement) { render(createElement) {

View file

@ -120,18 +120,7 @@ function Client(manager, name, config = {}) {
} }
}); });
(client.config.networks || []).forEach((network) => { (client.config.networks || []).forEach((network) => client.connect(network, true));
client.connect(network, true);
for (const chan of network.channels) {
if (chan.favorite) {
// third argument is whether to save or not;
// we don't need to here as the config is loaded from the filesystem
console.log(network.uuid, chan.id);
client.addToFavorites(network.uuid, chan.id);
}
}
});
// Networks are stored directly in the client object // Networks are stored directly in the client object
// We don't need to keep it in the config object // We don't need to keep it in the config object
@ -215,6 +204,7 @@ Client.prototype.connect = function (args, isStartup = false) {
key: chan.key || "", key: chan.key || "",
type: chan.type, type: chan.type,
muted: chan.muted, muted: chan.muted,
favorite: chan.favorite,
}) })
); );
}); });
@ -308,6 +298,15 @@ Client.prototype.connect = function (args, isStartup = false) {
client.save(); client.save();
channels.forEach((channel) => channel.loadMessages(client, network)); channels.forEach((channel) => channel.loadMessages(client, network));
} }
channels.forEach((chan) => {
if (chan.favorite) {
// The third argument for addToFavorites is whether to save,
// we will only be adding in this case if the favorite is loaded from disk,
// so we can safely set it to false.
this.addToFavorites(network.uuid, chan.id, false);
}
});
}; };
Client.prototype.generateToken = function (callback) { Client.prototype.generateToken = function (callback) {
@ -666,7 +665,7 @@ Client.prototype.part = function (network, chan) {
const client = this; const client = this;
network.channels = _.without(network.channels, chan); network.channels = _.without(network.channels, chan);
client.mentions = client.mentions.filter((msg) => !(msg.chanId === chan.id)); client.mentions = client.mentions.filter((msg) => !(msg.chanId === chan.id));
client.favoriteChannels = client.favoriteChannels.filter((fav) => fav.id !== chan.id); client.favoriteChannels = client.favoriteChannels.filter((fav) => fav.channelId !== chan.id);
chan.destroy(); chan.destroy();
client.save(); client.save();
client.emit("part", { client.emit("part", {