diff --git a/client/components/Chat.vue b/client/components/Chat.vue index 63049b2f..0796780a 100644 --- a/client/components/Chat.vue +++ b/client/components/Chat.vue @@ -174,7 +174,7 @@ export default { }, destroyed() { if (this.historyObserver) { - this.historyObserver.unobserve(this.$refs.loadMoreButton); + this.historyObserver.disconnect(); } }, methods: { diff --git a/client/components/ChatInput.vue b/client/components/ChatInput.vue index 0cc2337e..9060a8d2 100644 --- a/client/components/ChatInput.vue +++ b/client/components/ChatInput.vue @@ -30,6 +30,14 @@ export default { network: Object, channel: Object, }, + mounted() { + if (this.$root.settings.autocomplete) { + require("../js/autocompletion").enable(); + } + }, + destroyed() { + require("../js/autocompletion").disable(); + }, methods: { getInputPlaceholder(channel) { if (channel.type === "channel" || channel.type === "query") { diff --git a/client/js/autocompletion.js b/client/js/autocompletion.js index 3c287383..76a209a4 100644 --- a/client/js/autocompletion.js +++ b/client/js/autocompletion.js @@ -5,9 +5,10 @@ const fuzzy = require("fuzzy"); const Mousetrap = require("mousetrap"); const {Textcomplete, Textarea} = require("textcomplete"); const emojiMap = require("./libs/simplemap.json"); -const options = require("./options"); const constants = require("./constants"); +const {vueApp} = require("./vue"); +let input; let textcomplete; let enabled = false; @@ -15,7 +16,6 @@ module.exports = { enable: enableAutocomplete, disable() { if (enabled) { - const input = $("#input"); input.off("input.tabcomplete"); Mousetrap(input.get(0)).unbind("tab", "keydown"); textcomplete.destroy(); @@ -69,7 +69,7 @@ const nicksStrategy = { }, replace([, original], position = 1) { // If no postfix specified, return autocompleted nick as-is - if (!options.settings.nickPostfix) { + if (!vueApp.settings.nickPostfix) { return original; } @@ -79,7 +79,7 @@ const nicksStrategy = { } // If nick is first in the input, append specified postfix - return original + options.settings.nickPostfix; + return original + vueApp.settings.nickPostfix; }, index: 1, }; @@ -179,7 +179,7 @@ function enableAutocomplete() { let tabCount = 0; let lastMatch = ""; let currentMatches = []; - const input = $("#input"); + input = $("#input"); input.on("input.tabcomplete", () => { tabCount = 0; @@ -270,19 +270,17 @@ function fuzzyGrep(term, array) { } function rawNicks() { - const chan = chat.find(".active"); - const users = chan.find(".userlist"); + if (vueApp.activeChannel.channel.users.length > 0) { + const users = vueApp.activeChannel.channel.users.slice(); - // If this channel has a list of nicks, just return it - if (users.length > 0) { - return users.data("nicks"); + return users.sort((a, b) => b.lastMessage - a.lastMessage).map((u) => u.nick); } - const me = $("#nick").text(); - const otherUser = chan.attr("aria-label"); + const me = vueApp.activeChannel.network.nick; + const otherUser = vueApp.activeChannel.channel.name; // If this is a query, add their name to autocomplete - if (me !== otherUser && chan.data("type") === "query") { + if (me !== otherUser && vueApp.activeChannel.channel.type === "query") { return [otherUser, me]; } @@ -313,16 +311,11 @@ function completeCommands(word) { function completeChans(word) { const words = []; - sidebar.find(".chan.active") - .parent() - .find(".chan") - .each(function() { - const self = $(this); - - if (self.hasClass("channel")) { - words.push(self.attr("aria-label")); - } - }); + for (const channel of vueApp.activeChannel.network.channels) { + if (channel.type === "channel") { + words.push(channel.name); + } + } return fuzzyGrep(word, words); } diff --git a/client/js/lounge.js b/client/js/lounge.js index 4964336c..b3f8b6bc 100644 --- a/client/js/lounge.js +++ b/client/js/lounge.js @@ -5,21 +5,22 @@ const $ = require("jquery"); const moment = require("moment"); // our libraries -require("./libs/jquery/stickyscroll"); -const slideoutMenu = require("./slideout"); -const templates = require("../views"); const socket = require("./socket"); -require("./socket-events"); -const storage = require("./localStorage"); -const utils = require("./utils"); -require("./webpush"); -require("./keybinds"); -require("./clipboard"); -const contextMenuFactory = require("./contextMenuFactory"); const {vueApp, findChannel} = require("./vue"); -$(function() { +window.vueMounted = () => { + require("./socket-events"); + require("./libs/jquery/stickyscroll"); + const slideoutMenu = require("./slideout"); + const templates = require("../views"); + const contextMenuFactory = require("./contextMenuFactory"); + const storage = require("./localStorage"); + const utils = require("./utils"); + require("./webpush"); + require("./keybinds"); + require("./clipboard"); + const sidebar = $("#sidebar, #footer"); const chat = $("#chat"); @@ -353,4 +354,4 @@ $(function() { // Only start opening socket.io connection after all events have been registered socket.open(); -}); +}; diff --git a/client/js/options.js b/client/js/options.js index b9805610..65ef81b8 100644 --- a/client/js/options.js +++ b/client/js/options.js @@ -5,6 +5,8 @@ const escapeRegExp = require("lodash/escapeRegExp"); const storage = require("./localStorage"); const tz = require("./libs/handlebars/tz"); const socket = require("./socket"); +const {vueApp} = require("./vue"); +require("../js/autocompletion"); const $windows = $("#windows"); const $chat = $("#chat"); @@ -23,24 +25,7 @@ let $warningUnsupported; let $warningBlocked; // Default settings -const settings = { - syncSettings: false, - advanced: false, - autocomplete: true, - nickPostfix: "", - coloredNicks: true, - desktopNotifications: false, - highlights: [], - links: true, - motd: true, - notification: true, - notifyAllMessages: false, - showSeconds: false, - statusMessages: "condensed", - theme: $("#theme").attr("data-server-theme"), - media: true, - userStyles: "", -}; +const settings = vueApp.settings; const noSync = ["syncSettings"]; @@ -87,9 +72,6 @@ module.exports = { initialize, }; -// Due to cyclical dependency, have to require it after exports -const autocompletion = require("./autocompletion"); - function shouldOpenMessagePreview(type) { return type === "link" ? settings.links : settings.media; } @@ -155,12 +137,6 @@ function applySetting(name, value) { $(this).text(tz($(this).parent().data("time"))); }); $chat.toggleClass("show-seconds", value); - } else if (name === "autocomplete") { - if (value) { - autocompletion.enable(); - } else { - autocompletion.disable(); - } } else if (name === "desktopNotifications") { if (("Notification" in window) && value && Notification.permission !== "granted") { Notification.requestPermission(updateDesktopNotificationStatus); diff --git a/client/js/socket-events/msg.js b/client/js/socket-events/msg.js index 3fc1e9e2..83f3c702 100644 --- a/client/js/socket-events/msg.js +++ b/client/js/socket-events/msg.js @@ -107,16 +107,11 @@ function processReceivedMessage(data) { render.trimMessageInChannel(channelContainer, messageLimit); } - if ((data.msg.type === "message" || data.msg.type === "action") && channelContainer.hasClass("channel")) { - const nicks = channelContainer.find(".userlist").data("nicks"); + if ((data.msg.type === "message" || data.msg.type === "action") && channel.channel.type === "channel") { + const user = channel.channel.users.find((u) => u.nick === data.msg.from.nick); - if (nicks) { - const find = nicks.indexOf(data.msg.from.nick); - - if (find !== -1) { - nicks.splice(find, 1); - nicks.unshift(data.msg.from.nick); - } + if (user) { + user.lastMessage = (new Date(data.msg.time)).getTime() || Date.now(); } } } diff --git a/client/js/vue.js b/client/js/vue.js index 6ded26d2..bb518110 100644 --- a/client/js/vue.js +++ b/client/js/vue.js @@ -24,12 +24,33 @@ const vueApp = new Vue({ appName: document.title, activeChannel: null, networks: [], + settings: { + syncSettings: false, + advanced: false, + autocomplete: true, + nickPostfix: "", + coloredNicks: true, + desktopNotifications: false, + highlights: [], + links: true, + motd: true, + notification: true, + notifyAllMessages: false, + showSeconds: false, + statusMessages: "condensed", + theme: document.getElementById("theme").dataset.serverTheme, + media: true, + userStyles: "", + }, }, render(createElement) { return createElement(App, { props: this, }); }, + mounted() { + Vue.nextTick(() => window.vueMounted()); + } }); function findChannel(id) {