mirror of
https://github.com/thelounge/thelounge
synced 2024-11-25 21:40:22 +00:00
Implement vue-router.
This commit is contained in:
parent
7355c91839
commit
737afc759b
19 changed files with 221 additions and 230 deletions
|
@ -3,12 +3,7 @@
|
|||
<Sidebar :networks="networks" :active-channel="activeChannel" :overlay="$refs.overlay" />
|
||||
<div id="sidebar-overlay" ref="overlay" @click="$root.setSidebar(false)" />
|
||||
<article id="windows">
|
||||
<Chat
|
||||
v-if="activeChannel"
|
||||
:network="activeChannel.network"
|
||||
:channel="activeChannel.channel"
|
||||
/>
|
||||
<component :is="$store.state.activeWindow" ref="window" />
|
||||
<router-view></router-view>
|
||||
</article>
|
||||
<ImageViewer ref="imageViewer" />
|
||||
</div>
|
||||
|
@ -18,29 +13,13 @@
|
|||
const throttle = require("lodash/throttle");
|
||||
|
||||
import Sidebar from "./Sidebar.vue";
|
||||
import NetworkList from "./NetworkList.vue";
|
||||
import Chat from "./Chat.vue";
|
||||
import ImageViewer from "./ImageViewer.vue";
|
||||
import SignIn from "./Windows/SignIn.vue";
|
||||
import Settings from "./Windows/Settings.vue";
|
||||
import NetworkEdit from "./Windows/NetworkEdit.vue";
|
||||
import Connect from "./Windows/Connect.vue";
|
||||
import Help from "./Windows/Help.vue";
|
||||
import Changelog from "./Windows/Changelog.vue";
|
||||
|
||||
export default {
|
||||
name: "App",
|
||||
components: {
|
||||
Sidebar,
|
||||
NetworkList,
|
||||
ImageViewer,
|
||||
Chat,
|
||||
SignIn,
|
||||
Settings,
|
||||
NetworkEdit,
|
||||
Connect,
|
||||
Help,
|
||||
Changelog,
|
||||
},
|
||||
props: {
|
||||
activeWindow: String,
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
:aria-selected="activeChannel && channel === activeChannel.channel"
|
||||
:style="closed ? {transition: 'none', opacity: 0.4} : null"
|
||||
role="tab"
|
||||
@click="click"
|
||||
>
|
||||
<slot :network="network" :channel="channel" :activeChannel="activeChannel" />
|
||||
</div>
|
||||
|
@ -80,6 +81,11 @@ export default {
|
|||
|
||||
return this.channel.name;
|
||||
},
|
||||
click() {
|
||||
// TODO: Find out why this sometimes throws `uncaught exception: Object`
|
||||
this.$router.push("chan-" + this.channel.id);
|
||||
this.$root.closeSidebarIfNeeded();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -133,7 +133,38 @@ export default {
|
|||
return undefined;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
channel(_, previousChannel) {
|
||||
this.channelChanged(previousChannel);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.channelChanged();
|
||||
},
|
||||
methods: {
|
||||
channelChanged(previousChannel) {
|
||||
// Triggered when active channel is set or changed
|
||||
|
||||
if (previousChannel) {
|
||||
this.$root.switchOutOfChannel(previousChannel);
|
||||
}
|
||||
|
||||
this.channel.highlight = 0;
|
||||
this.channel.unread = 0;
|
||||
|
||||
this.$store.commit("activeWindow", null);
|
||||
socket.emit("open", this.channel.id);
|
||||
|
||||
if (this.channel.usersOutdated) {
|
||||
this.channel.usersOutdated = false;
|
||||
|
||||
socket.emit("names", {
|
||||
target: this.channel.id,
|
||||
});
|
||||
}
|
||||
|
||||
this.$root.synchronizeNotifiedState();
|
||||
},
|
||||
hideUserVisibleError() {
|
||||
this.$root.currentUserVisibleError = null;
|
||||
},
|
||||
|
|
35
client/components/RoutedChat.vue
Normal file
35
client/components/RoutedChat.vue
Normal file
|
@ -0,0 +1,35 @@
|
|||
<template>
|
||||
<Chat v-if="activeChannel" :network="activeChannel.network" :channel="activeChannel.channel" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// Temporary component for routing channels and lobbies
|
||||
import Chat from "./Chat.vue";
|
||||
|
||||
export default {
|
||||
name: "RoutedChat",
|
||||
components: {
|
||||
Chat,
|
||||
},
|
||||
computed: {
|
||||
activeChannel() {
|
||||
const chan_id = parseInt(this.$route.params.pathMatch);
|
||||
const channel = this.$root.findChannel(chan_id);
|
||||
return channel;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
activeChannel() {
|
||||
this.setActiveChannel();
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.setActiveChannel();
|
||||
},
|
||||
methods: {
|
||||
setActiveChannel() {
|
||||
this.$root.activeChannel = this.activeChannel;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
|
@ -25,6 +25,7 @@
|
|||
role="tab"
|
||||
aria-controls="sign-in"
|
||||
:aria-selected="$store.state.activeWindow === 'SignIn'"
|
||||
@click="navigate('sign-in')"
|
||||
/></span>
|
||||
<span
|
||||
class="tooltipped tooltipped-n tooltipped-no-touch"
|
||||
|
@ -37,6 +38,7 @@
|
|||
role="tab"
|
||||
aria-controls="connect"
|
||||
:aria-selected="$store.state.activeWindow === 'Connect'"
|
||||
@click="navigate('connect')"
|
||||
/></span>
|
||||
<span class="tooltipped tooltipped-n tooltipped-no-touch" aria-label="Settings"
|
||||
><button
|
||||
|
@ -52,6 +54,7 @@
|
|||
role="tab"
|
||||
aria-controls="settings"
|
||||
:aria-selected="$store.state.activeWindow === 'Settings'"
|
||||
@click="navigate('settings')"
|
||||
/></span>
|
||||
<span class="tooltipped tooltipped-n tooltipped-no-touch" aria-label="Help"
|
||||
><button
|
||||
|
@ -62,6 +65,7 @@
|
|||
role="tab"
|
||||
aria-controls="help"
|
||||
:aria-selected="$store.state.activeWindow === 'Help'"
|
||||
@click="navigate('help')"
|
||||
/></span>
|
||||
</footer>
|
||||
</aside>
|
||||
|
@ -178,6 +182,16 @@ export default {
|
|||
this.$store.commit("sidebarOpen", state);
|
||||
};
|
||||
|
||||
this.navigate = (to) => {
|
||||
if (this.activeChannel && this.activeChannel.channel) {
|
||||
this.$root.switchOutOfChannel(this.activeChannel.channel);
|
||||
}
|
||||
|
||||
this.$root.activeChannel = null;
|
||||
this.$root.closeSidebarIfNeeded();
|
||||
this.$router.push(to);
|
||||
};
|
||||
|
||||
document.body.addEventListener("touchstart", this.onTouchStart, {passive: true});
|
||||
},
|
||||
methods: {
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<SidebarToggle />
|
||||
</div>
|
||||
<div class="container">
|
||||
<a id="back-to-help" href="#" data-target="#help" data-component="Help">« Help</a>
|
||||
<router-link id="back-to-help" to="help">« Help</router-link>
|
||||
|
||||
<template
|
||||
v-if="
|
||||
|
@ -65,6 +65,10 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
patchChangelog() {
|
||||
if (!this.$refs.changelog) {
|
||||
return;
|
||||
}
|
||||
|
||||
const links = this.$refs.changelog.querySelectorAll("a");
|
||||
|
||||
for (const link of links) {
|
||||
|
|
|
@ -8,12 +8,10 @@
|
|||
|
||||
<h2>
|
||||
<small class="pull-right">
|
||||
v{{ $root.serverConfiguration.version }} (<a
|
||||
v{{ $root.serverConfiguration.version }} (<router-link
|
||||
id="view-changelog"
|
||||
href="#"
|
||||
data-target="#changelog"
|
||||
data-component="Changelog"
|
||||
>release notes</a
|
||||
to="changelog"
|
||||
>release notes</router-link
|
||||
>)
|
||||
</small>
|
||||
About The Lounge
|
||||
|
|
|
@ -36,4 +36,6 @@ module.exports = {
|
|||
condensedTypesQuery,
|
||||
timeFormats,
|
||||
sizeUnits,
|
||||
// Same value as media query in CSS that forces sidebars to become overlays
|
||||
mobileViewportPixels: 768,
|
||||
};
|
||||
|
|
|
@ -6,135 +6,12 @@ const $ = require("jquery");
|
|||
// our libraries
|
||||
const socket = require("./socket");
|
||||
|
||||
const {vueApp, findChannel} = require("./vue");
|
||||
|
||||
window.vueMounted = () => {
|
||||
require("./socket-events");
|
||||
require("./contextMenuFactory");
|
||||
const utils = require("./utils");
|
||||
require("./webpush");
|
||||
require("./keybinds");
|
||||
|
||||
const sidebar = $("#sidebar, #footer");
|
||||
|
||||
const openWindow = function openWindow(e, {pushState, replaceHistory} = {}) {
|
||||
const self = $(this);
|
||||
const target = self.attr("data-target");
|
||||
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
|
||||
// This is a rather gross hack to account for sources that are in the
|
||||
// sidebar specifically. Needs to be done better when window management gets
|
||||
// refactored.
|
||||
const inSidebar = self.parents("#sidebar, #footer").length > 0;
|
||||
const channel = inSidebar ? findChannel(Number(self.attr("data-id"))) : null;
|
||||
|
||||
if (vueApp.activeChannel) {
|
||||
const {channel: lastChannel} = vueApp.activeChannel;
|
||||
|
||||
// If user clicks on the currently active channel, do nothing
|
||||
if (channel && lastChannel === channel.channel) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (lastChannel.messages.length > 0) {
|
||||
lastChannel.firstUnread = lastChannel.messages[lastChannel.messages.length - 1].id;
|
||||
}
|
||||
|
||||
if (lastChannel.messages.length > 100) {
|
||||
lastChannel.messages.splice(0, lastChannel.messages.length - 100);
|
||||
lastChannel.moreHistoryAvailable = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (channel) {
|
||||
vueApp.$store.commit("activeWindow", null);
|
||||
vueApp.activeChannel = channel;
|
||||
|
||||
if (channel) {
|
||||
channel.channel.highlight = 0;
|
||||
channel.channel.unread = 0;
|
||||
}
|
||||
|
||||
socket.emit("open", channel ? channel.channel.id : null);
|
||||
|
||||
if ($(window).outerWidth() <= utils.mobileViewportPixels) {
|
||||
vueApp.setSidebar(false);
|
||||
}
|
||||
} else {
|
||||
vueApp.activeChannel = null;
|
||||
const component = self.attr("data-component");
|
||||
vueApp.$store.commit("activeWindow", component);
|
||||
|
||||
if ($(window).outerWidth() <= utils.mobileViewportPixels) {
|
||||
vueApp.setSidebar(false);
|
||||
}
|
||||
}
|
||||
|
||||
utils.synchronizeNotifiedState();
|
||||
|
||||
if (self.hasClass("chan")) {
|
||||
vueApp.$nextTick(() => $("#chat-container").addClass("active"));
|
||||
}
|
||||
|
||||
/* TODO: move to ChatInput.vue
|
||||
const chanChat = chan.find(".chat");
|
||||
|
||||
if (chanChat.length > 0 && channel.type !== "special") {
|
||||
// On touch devices unfocus (blur) the input to correctly close the virtual keyboard
|
||||
// An explicit blur is required, as the keyboard may open back up if the focus remains
|
||||
// See https://github.com/thelounge/thelounge/issues/2257
|
||||
$("#input").trigger("ontouchstart" in window ? "blur" : "focus");
|
||||
}
|
||||
*/
|
||||
|
||||
if (channel && channel.channel.usersOutdated) {
|
||||
channel.channel.usersOutdated = false;
|
||||
|
||||
socket.emit("names", {
|
||||
target: channel.channel.id,
|
||||
});
|
||||
}
|
||||
|
||||
// Pushes states to history web API when clicking elements with a data-target attribute.
|
||||
// States are very trivial and only contain a single `clickTarget` property which
|
||||
// contains a CSS selector that targets elements which takes the user to a different view
|
||||
// when clicked. The `popstate` event listener will trigger synthetic click events using that
|
||||
// selector and thus take the user to a different view/state.
|
||||
if (pushState === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const state = {};
|
||||
|
||||
if (self.prop("id")) {
|
||||
state.clickTarget = `#${self.prop("id")}`;
|
||||
} else if (self.hasClass("chan")) {
|
||||
state.clickTarget = `#sidebar .chan[data-id="${self.attr("data-id")}"]`;
|
||||
} else {
|
||||
state.clickTarget = `#footer button[data-target="${target}"]`;
|
||||
}
|
||||
|
||||
if (history && history.pushState) {
|
||||
if (replaceHistory && history.replaceState) {
|
||||
history.replaceState(state, null, target);
|
||||
} else {
|
||||
history.pushState(state, null, target);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
sidebar.on("click", ".chan, button", openWindow);
|
||||
$("#windows").on("click", "#view-changelog, #back-to-help", openWindow);
|
||||
|
||||
$(document).on("visibilitychange focus click", () => {
|
||||
utils.synchronizeNotifiedState();
|
||||
});
|
||||
|
||||
window.addEventListener("popstate", (e) => {
|
||||
const {state} = e;
|
||||
|
||||
|
|
25
client/js/router.js
Normal file
25
client/js/router.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
"use strict";
|
||||
|
||||
const Vue = require("vue").default;
|
||||
const VueRouter = require("vue-router").default;
|
||||
Vue.use(VueRouter);
|
||||
|
||||
const SignIn = require("../components/Windows/SignIn.vue").default;
|
||||
const Connect = require("../components/Windows/Connect.vue").default;
|
||||
const Settings = require("../components/Windows/Settings.vue").default;
|
||||
const Help = require("../components/Windows/Help.vue").default;
|
||||
const Changelog = require("../components/Windows/Changelog.vue").default;
|
||||
const RoutedChat = require("../components/RoutedChat.vue").default;
|
||||
|
||||
const router = new VueRouter({
|
||||
routes: [
|
||||
{path: "/sign-in", component: SignIn},
|
||||
{path: "/connect", component: Connect},
|
||||
{path: "/settings", component: Settings},
|
||||
{path: "/help", component: Help},
|
||||
{path: "/changelog", component: Changelog},
|
||||
{path: "/chan-*", component: RoutedChat},
|
||||
],
|
||||
});
|
||||
|
||||
export default router;
|
|
@ -19,8 +19,6 @@ socket.on("auth", function(data) {
|
|||
|
||||
if (data.serverHash > -1) {
|
||||
utils.serverHash = data.serverHash;
|
||||
|
||||
vueApp.$store.commit("activeWindow", "SignIn");
|
||||
} else {
|
||||
getActiveWindowComponent().inFlight = false;
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ const webpush = require("../webpush");
|
|||
const sidebar = $("#sidebar");
|
||||
const storage = require("../localStorage");
|
||||
const utils = require("../utils");
|
||||
const constants = require("../constants");
|
||||
const {vueApp, initChannel} = require("../vue");
|
||||
|
||||
socket.on("init", function(data) {
|
||||
|
@ -31,7 +32,7 @@ socket.on("init", function(data) {
|
|||
const viewportWidth = window.outerWidth;
|
||||
let isUserlistOpen = storage.get("thelounge.state.userlist");
|
||||
|
||||
if (viewportWidth > utils.mobileViewportPixels) {
|
||||
if (viewportWidth > constants.mobileViewportPixels) {
|
||||
vueApp.setSidebar(storage.get("thelounge.state.sidebar") !== "false");
|
||||
}
|
||||
|
||||
|
@ -55,7 +56,7 @@ socket.on("init", function(data) {
|
|||
vueApp.$nextTick(() => openCorrectChannel(previousActive, data.active));
|
||||
|
||||
utils.confirmExit();
|
||||
utils.synchronizeNotifiedState();
|
||||
vueApp.synchronizeNotifiedState();
|
||||
});
|
||||
|
||||
function openCorrectChannel(clientActive, serverActive) {
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
const $ = require("jquery");
|
||||
const socket = require("../socket");
|
||||
const utils = require("../utils");
|
||||
const options = require("../options");
|
||||
const cleanIrcMessage = require("../libs/handlebars/ircmessageparser/cleanIrcMessage");
|
||||
const webpush = require("../webpush");
|
||||
|
@ -91,7 +90,7 @@ socket.on("msg", function(data) {
|
|||
}
|
||||
|
||||
if (data.msg.self || data.msg.highlight) {
|
||||
utils.synchronizeNotifiedState();
|
||||
vueApp.synchronizeNotifiedState();
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
"use strict";
|
||||
|
||||
const socket = require("../socket");
|
||||
const utils = require("../utils");
|
||||
const {vueApp, findChannel} = require("../vue");
|
||||
|
||||
// Sync unread badge and marker when other clients open a channel
|
||||
|
@ -28,5 +27,5 @@ socket.on("open", function(id) {
|
|||
}
|
||||
}
|
||||
|
||||
utils.synchronizeNotifiedState();
|
||||
vueApp.synchronizeNotifiedState();
|
||||
});
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
const $ = require("jquery");
|
||||
const socket = require("../socket");
|
||||
const utils = require("../utils");
|
||||
const {vueApp, findChannel} = require("../vue");
|
||||
|
||||
socket.on("part", function(data) {
|
||||
|
@ -23,5 +22,5 @@ socket.on("part", function(data) {
|
|||
);
|
||||
}
|
||||
|
||||
utils.synchronizeNotifiedState();
|
||||
vueApp.synchronizeNotifiedState();
|
||||
});
|
||||
|
|
|
@ -7,15 +7,12 @@ const {vueApp} = require("./vue");
|
|||
var serverHash = -1; // eslint-disable-line no-var
|
||||
|
||||
module.exports = {
|
||||
// Same value as media query in CSS that forces sidebars to become overlays
|
||||
mobileViewportPixels: 768,
|
||||
findCurrentNetworkChan,
|
||||
serverHash,
|
||||
confirmExit,
|
||||
scrollIntoViewNicely,
|
||||
hasRoleInChannel,
|
||||
move,
|
||||
synchronizeNotifiedState,
|
||||
requestIdleCallback,
|
||||
};
|
||||
|
||||
|
@ -45,60 +42,6 @@ function scrollIntoViewNicely(el) {
|
|||
el.scrollIntoView({block: "center", inline: "nearest"});
|
||||
}
|
||||
|
||||
const favicon = $("#favicon");
|
||||
|
||||
function synchronizeNotifiedState() {
|
||||
updateTitle();
|
||||
|
||||
let hasAnyHighlights = false;
|
||||
|
||||
for (const network of vueApp.networks) {
|
||||
for (const chan of network.channels) {
|
||||
if (chan.highlight > 0) {
|
||||
hasAnyHighlights = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
toggleNotificationMarkers(hasAnyHighlights);
|
||||
}
|
||||
|
||||
function toggleNotificationMarkers(newState) {
|
||||
if (vueApp.$store.state.isNotified !== newState) {
|
||||
// Toggles a dot on the menu icon when there are unread notifications
|
||||
vueApp.$store.commit("isNotified", newState);
|
||||
|
||||
// Toggles the favicon to red when there are unread notifications
|
||||
const old = favicon.prop("href");
|
||||
favicon.prop("href", favicon.data("other"));
|
||||
favicon.data("other", old);
|
||||
}
|
||||
}
|
||||
|
||||
function updateTitle() {
|
||||
let title = vueApp.appName;
|
||||
|
||||
if (vueApp.activeChannel) {
|
||||
title = `${vueApp.activeChannel.channel.name} — ${title}`;
|
||||
}
|
||||
|
||||
// add highlight count to title
|
||||
let alertEventCount = 0;
|
||||
|
||||
for (const network of vueApp.networks) {
|
||||
for (const channel of network.channels) {
|
||||
alertEventCount += channel.highlight;
|
||||
}
|
||||
}
|
||||
|
||||
if (alertEventCount > 0) {
|
||||
title = `(${alertEventCount}) ${title}`;
|
||||
}
|
||||
|
||||
document.title = title;
|
||||
}
|
||||
|
||||
function confirmExit() {
|
||||
if ($(document.body).hasClass("public")) {
|
||||
window.onbeforeunload = function() {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
"use strict";
|
||||
|
||||
const Vue = require("vue").default;
|
||||
|
||||
const store = require("./store").default;
|
||||
const App = require("../components/App.vue").default;
|
||||
const roundBadgeNumber = require("./libs/handlebars/roundBadgeNumber");
|
||||
|
@ -8,6 +9,8 @@ const localetime = require("./libs/handlebars/localetime");
|
|||
const friendlysize = require("./libs/handlebars/friendlysize");
|
||||
const colorClass = require("./libs/handlebars/colorClass");
|
||||
const storage = require("./localStorage");
|
||||
const router = require("./router").default;
|
||||
const constants = require("./constants");
|
||||
|
||||
Vue.filter("localetime", localetime);
|
||||
Vue.filter("friendlysize", friendlysize);
|
||||
|
@ -46,12 +49,17 @@ const vueApp = new Vue({
|
|||
userStyles: "",
|
||||
},
|
||||
},
|
||||
router,
|
||||
mounted() {
|
||||
Vue.nextTick(() => window.vueMounted());
|
||||
|
||||
if (navigator.platform.match(/(Mac|iPhone|iPod|iPad)/i)) {
|
||||
document.body.classList.add("is-apple");
|
||||
}
|
||||
|
||||
document.addEventListener("visibilitychange", this.synchronizeNotifiedState());
|
||||
document.addEventListener("focus", this.synchronizeNotifiedState());
|
||||
document.addEventListener("click", this.synchronizeNotifiedState());
|
||||
},
|
||||
methods: {
|
||||
onSocketInit() {
|
||||
|
@ -59,11 +67,9 @@ const vueApp = new Vue({
|
|||
this.$store.commit("isConnected", true);
|
||||
},
|
||||
setSidebar(state) {
|
||||
const utils = require("./utils");
|
||||
|
||||
this.$store.commit("sidebarOpen", state);
|
||||
|
||||
if (window.outerWidth > utils.mobileViewportPixels) {
|
||||
if (window.outerWidth > constants.mobileViewportPixels) {
|
||||
storage.set("thelounge.state.sidebar", state);
|
||||
}
|
||||
|
||||
|
@ -72,6 +78,11 @@ const vueApp = new Vue({
|
|||
toggleSidebar() {
|
||||
this.setSidebar(!this.$store.state.sidebarOpen);
|
||||
},
|
||||
closeSidebarIfNeeded() {
|
||||
if (window.innerWidth <= constants.mobileViewportPixels) {
|
||||
this.setSidebar(false);
|
||||
}
|
||||
},
|
||||
setUserlist(state) {
|
||||
storage.set("thelounge.state.userlist", state);
|
||||
this.$store.commit("userlistOpen", state);
|
||||
|
@ -80,6 +91,78 @@ const vueApp = new Vue({
|
|||
toggleUserlist() {
|
||||
this.setUserlist(!this.$store.state.userlistOpen);
|
||||
},
|
||||
findChannel(id) {
|
||||
for (const network of this.networks) {
|
||||
for (const channel of network.channels) {
|
||||
if (channel.id === id) {
|
||||
return {network, channel};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
switchOutOfChannel(channel) {
|
||||
// When switching out of a channel, mark everything as read
|
||||
if (channel.messages.length > 0) {
|
||||
channel.firstUnread = channel.messages[channel.messages.length - 1].id;
|
||||
}
|
||||
|
||||
if (channel.messages.length > 100) {
|
||||
channel.messages.splice(0, channel.messages.length - 100);
|
||||
channel.moreHistoryAvailable = true;
|
||||
}
|
||||
},
|
||||
synchronizeNotifiedState() {
|
||||
this.updateTitle();
|
||||
|
||||
let hasAnyHighlights = false;
|
||||
|
||||
for (const network of this.networks) {
|
||||
for (const chan of network.channels) {
|
||||
if (chan.highlight > 0) {
|
||||
hasAnyHighlights = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.toggleNotificationMarkers(hasAnyHighlights);
|
||||
},
|
||||
updateTitle() {
|
||||
let title = this.appName;
|
||||
|
||||
if (this.activeChannel) {
|
||||
title = `${this.activeChannel.channel.name} — ${title}`;
|
||||
}
|
||||
|
||||
// add highlight count to title
|
||||
let alertEventCount = 0;
|
||||
|
||||
for (const network of this.networks) {
|
||||
for (const channel of network.channels) {
|
||||
alertEventCount += channel.highlight;
|
||||
}
|
||||
}
|
||||
|
||||
if (alertEventCount > 0) {
|
||||
title = `(${alertEventCount}) ${title}`;
|
||||
}
|
||||
|
||||
document.title = title;
|
||||
},
|
||||
toggleNotificationMarkers(newState) {
|
||||
if (this.$store.state.isNotified !== newState) {
|
||||
// Toggles a dot on the menu icon when there are unread notifications
|
||||
this.$store.commit("isNotified", newState);
|
||||
|
||||
// Toggles the favicon to red when there are unread notifications
|
||||
const favicon = document.getElementById("favicon");
|
||||
const old = favicon.getAttribute("href");
|
||||
favicon.setAttribute("href", favicon.dataset.other);
|
||||
favicon.dataset.other = old;
|
||||
}
|
||||
},
|
||||
},
|
||||
render(createElement) {
|
||||
return createElement(App, {
|
||||
|
@ -96,15 +179,7 @@ Vue.config.errorHandler = function(e) {
|
|||
};
|
||||
|
||||
function findChannel(id) {
|
||||
for (const network of vueApp.networks) {
|
||||
for (const channel of network.channels) {
|
||||
if (channel.id === id) {
|
||||
return {network, channel};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
return vueApp.findChannel(id);
|
||||
}
|
||||
|
||||
function initChannel(channel) {
|
||||
|
|
|
@ -114,6 +114,7 @@
|
|||
"undate": "0.3.0",
|
||||
"vue": "2.6.10",
|
||||
"vue-loader": "15.7.2",
|
||||
"vue-router": "3.1.3",
|
||||
"vue-server-renderer": "2.6.10",
|
||||
"vue-template-compiler": "2.6.10",
|
||||
"vuedraggable": "2.23.2",
|
||||
|
|
|
@ -9078,6 +9078,11 @@ vue-loader@15.7.2:
|
|||
vue-hot-reload-api "^2.3.0"
|
||||
vue-style-loader "^4.1.0"
|
||||
|
||||
vue-router@3.1.3:
|
||||
version "3.1.3"
|
||||
resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.1.3.tgz#e6b14fabc0c0ee9fda0e2cbbda74b350e28e412b"
|
||||
integrity sha512-8iSa4mGNXBjyuSZFCCO4fiKfvzqk+mhL0lnKuGcQtO1eoj8nq3CmbEG8FwK5QqoqwDgsjsf1GDuisDX4cdb/aQ==
|
||||
|
||||
vue-server-renderer@2.6.10:
|
||||
version "2.6.10"
|
||||
resolved "https://registry.yarnpkg.com/vue-server-renderer/-/vue-server-renderer-2.6.10.tgz#cb2558842ead360ae2ec1f3719b75564a805b375"
|
||||
|
|
Loading…
Reference in a new issue