diff --git a/client/js/options.js b/client/js/options.js index acc841ed..5d104135 100644 --- a/client/js/options.js +++ b/client/js/options.js @@ -1,7 +1,6 @@ "use strict"; const $ = require("jquery"); -const escapeRegExp = require("lodash/escapeRegExp"); const storage = require("./localStorage"); const socket = require("./socket"); const {vueApp} = require("./vue"); @@ -27,9 +26,11 @@ const settings = vueApp.settings; const noSync = ["syncSettings"]; -// alwaysSync is reserved for things like "highlights". -// TODO: figure out how to deal with legacy clients that have different settings. -const alwaysSync = []; +// alwaysSync is reserved for settings that should be synced +// to the server regardless of the clients sync setting. +const alwaysSync = [ + "highlights", +]; // Process usersettings from localstorage. let userSettings = JSON.parse(storage.get("settings")) || false; @@ -39,7 +40,13 @@ if (!userSettings) { settings.syncSettings = true; } else { for (const key in settings) { - if (userSettings[key] !== undefined) { + // Older The Lounge versions converted highlights to an array, turn it back into a string + if (key === "highlights" && typeof userSettings[key] === "object") { + userSettings[key] = userSettings[key].join(", "); + } + + // Make sure the setting in local storage has the same type that the code expects + if (typeof userSettings[key] !== "undefined" && typeof settings[key] === typeof userSettings[key]) { settings[key] = userSettings[key]; } } @@ -62,7 +69,6 @@ module.exports = { alwaysSync, noSync, initialized: false, - highlightsRE: null, settings, syncAllSettings, processSetting, @@ -92,32 +98,6 @@ function applySetting(name, value) { } } else if (name === "userStyles" && !noCSSparamReg.test(window.location.search)) { $userStyles.html(value); - } else if (name === "highlights") { - let highlights; - - if (typeof value === "string") { - highlights = value.split(",").map(function(h) { - return h.trim(); - }); - } else { - highlights = value; - } - - highlights = highlights.filter(function(h) { - // Ensure we don't have empty string in the list of highlights - // otherwise, users get notifications for everything - return h !== ""; - }); - // Construct regex with wordboundary for every highlight item - const highlightsTokens = highlights.map(function(h) { - return escapeRegExp(h); - }); - - if (highlightsTokens && highlightsTokens.length) { - module.exports.highlightsRE = new RegExp(`(?:^| |\t)(?:${highlightsTokens.join("|")})(?:\t| |$)`, "i"); - } else { - module.exports.highlightsRE = null; - } } else if (name === "desktopNotifications") { if (("Notification" in window) && value && Notification.permission !== "granted") { Notification.requestPermission(updateDesktopNotificationStatus); @@ -135,25 +115,11 @@ function settingSetEmit(name, value) { // When sync is `true` the setting will also be send to the backend for syncing. function updateSetting(name, value, sync) { - let storeValue = value; - - // First convert highlights if input is a string. - // Otherwise we are comparing the wrong types. - if (name === "highlights" && typeof value === "string") { - storeValue = value.split(",").map(function(h) { - return h.trim(); - }).filter(function(h) { - // Ensure we don't have empty string in the list of highlights - // otherwise, users get notifications for everything - return h !== ""; - }); - } - const currentOption = settings[name]; // Only update and process when the setting is actually changed. - if (currentOption !== storeValue) { - settings[name] = storeValue; + if (currentOption !== value) { + settings[name] = value; storage.set("settings", JSON.stringify(settings)); applySetting(name, value); diff --git a/client/js/socket-events/msg.js b/client/js/socket-events/msg.js index b3acee1b..ab45dbe2 100644 --- a/client/js/socket-events/msg.js +++ b/client/js/socket-events/msg.js @@ -49,15 +49,6 @@ socket.on("msg", function(data) { } } - // See if any of the custom highlight regexes match - // TODO: This needs to be done on the server side with settings sync - if (!data.msg.highlight && !data.msg.self - && (data.msg.type === "message" || data.msg.type === "notice") - && options.highlightsRE - && options.highlightsRE.exec(data.msg.text)) { - data.msg.highlight = true; - } - channel.messages.push(data.msg); if (data.msg.self) { diff --git a/client/js/vue.js b/client/js/vue.js index 872d1c37..10c082ee 100644 --- a/client/js/vue.js +++ b/client/js/vue.js @@ -31,7 +31,7 @@ const vueApp = new Vue({ nickPostfix: "", coloredNicks: true, desktopNotifications: false, - highlights: [], + highlights: "", links: true, motd: true, notification: true, diff --git a/src/client.js b/src/client.js index a909e9e8..b80793c5 100644 --- a/src/client.js +++ b/src/client.js @@ -10,6 +10,7 @@ const Network = require("./models/network"); const Helper = require("./helper"); const UAParser = require("ua-parser-js"); const uuidv4 = require("uuid/v4"); +const escapeRegExp = require("lodash/escapeRegExp"); const MessageStorage = require("./plugins/messageStorage/sqlite"); const TextFileMessageStorage = require("./plugins/messageStorage/text"); @@ -81,6 +82,7 @@ function Client(manager, name, config = {}) { sockets: manager.sockets, manager: manager, messageStorage: [], + highlightRegex: null, }); const client = this; @@ -111,6 +113,12 @@ function Client(manager, name, config = {}) { client.config.sessions = {}; } + if (typeof client.config.clientSettings !== "object") { + client.config.clientSettings = {}; + } + + client.compileCustomHighlights(); + _.forOwn(client.config.sessions, (session) => { if (session.pushSubscription) { this.registerPushSubscription(session, session.pushSubscription, true); @@ -373,6 +381,29 @@ Client.prototype.inputLine = function(data) { } }; +Client.prototype.compileCustomHighlights = function() { + const client = this; + + if (typeof client.config.clientSettings.highlights !== "string") { + client.highlightRegex = null; + return; + } + + // Ensure we don't have empty string in the list of highlights + // otherwise, users get notifications for everything + const highlightsTokens = client.config.clientSettings.highlights + .split(",") + .map((highlight) => escapeRegExp(highlight.trim())) + .filter((highlight) => highlight.length > 0); + + if (highlightsTokens.length === 0) { + client.highlightRegex = null; + return; + } + + client.highlightRegex = new RegExp(`(?:^| |\t)(?:${highlightsTokens.join("|")})(?:\t| |$)`, "i"); +}; + Client.prototype.more = function(data) { const client = this; const target = client.find(data.target); diff --git a/src/plugins/irc-events/message.js b/src/plugins/irc-events/message.js index c047f763..f0633452 100644 --- a/src/plugins/irc-events/message.js +++ b/src/plugins/irc-events/message.js @@ -117,6 +117,11 @@ module.exports = function(irc, network) { // Non-self messages are highlighted as soon as the nick is detected if (!msg.highlight && !msg.self) { msg.highlight = network.highlightRegex.test(data.message); + + // If we still don't have a highlight, test against custom highlights if there's any + if (!msg.highlight && client.highlightRegex) { + msg.highlight = client.highlightRegex.test(data.message); + } } let match; diff --git a/src/server.js b/src/server.js index d05a0774..ac18037f 100644 --- a/src/server.js +++ b/src/server.js @@ -498,11 +498,6 @@ function initializeClient(socket, client, token, lastMessage) { return; } - // Older user configs will not have the clientSettings property. - if (!client.config.hasOwnProperty("clientSettings")) { - client.config.clientSettings = {}; - } - // We do not need to do write operations and emit events if nothing changed. if (client.config.clientSettings[newSetting.name] !== newSetting.value) { client.config.clientSettings[newSetting.name] = newSetting.value; @@ -516,6 +511,10 @@ function initializeClient(socket, client, token, lastMessage) { client.manager.updateUser(client.name, { clientSettings: client.config.clientSettings, }); + + if (newSetting.name === "highlights") { + client.compileCustomHighlights(); + } } });