308 lines
9.0 KiB
JavaScript
308 lines
9.0 KiB
JavaScript
"use strict";
|
|
|
|
const $ = require("jquery");
|
|
const escapeRegExp = require("lodash/escapeRegExp");
|
|
const storage = require("./localStorage");
|
|
const tz = require("./libs/handlebars/tz");
|
|
const socket = require("./socket");
|
|
|
|
const $windows = $("#windows");
|
|
const $chat = $("#chat");
|
|
const $settings = $("#settings");
|
|
const $theme = $("#theme");
|
|
const $userStyles = $("#user-specified-css");
|
|
|
|
const noCSSparamReg = /[?&]nocss/;
|
|
|
|
// Not yet available at this point but used in various functionaly.
|
|
// Will be assigned when `initialize` is called.
|
|
let $syncWarningOverride;
|
|
let $syncWarningBase;
|
|
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").data("server-theme"),
|
|
media: true,
|
|
userStyles: "",
|
|
};
|
|
|
|
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 = [];
|
|
|
|
// Process usersettings from localstorage.
|
|
let userSettings = JSON.parse(storage.get("settings")) || {};
|
|
|
|
for (const key in settings) {
|
|
if (userSettings[key] !== undefined) {
|
|
settings[key] = userSettings[key];
|
|
}
|
|
}
|
|
|
|
// Apply custom CSS and themes on page load
|
|
// Done here and not on init because on slower devices and connections
|
|
// it can take up to several seconds before init is called.
|
|
if (typeof userSettings.userStyles === "string" && !noCSSparamReg.test(window.location.search)) {
|
|
$userStyles.html(userSettings.userStyles);
|
|
}
|
|
|
|
if (typeof userSettings.theme === "string" && $theme.attr("href") !== `themes/${userSettings.theme}.css`) {
|
|
$theme.attr("href", `themes/${userSettings.theme}.css`);
|
|
}
|
|
|
|
userSettings = null;
|
|
|
|
module.exports = {
|
|
alwaysSync,
|
|
noSync,
|
|
initialized: false,
|
|
highlightsRE: null,
|
|
settings,
|
|
shouldOpenMessagePreview,
|
|
noServerSettings,
|
|
processSetting,
|
|
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;
|
|
}
|
|
|
|
// Updates the checkbox and warning in settings.
|
|
// When notifications are not supported, this is never called (because
|
|
// checkbox state can not be changed).
|
|
function updateDesktopNotificationStatus() {
|
|
if (Notification.permission === "denied") {
|
|
$warningBlocked.show();
|
|
} else {
|
|
$warningBlocked.hide();
|
|
}
|
|
}
|
|
|
|
function applySetting(name, value) {
|
|
if (name === "syncSettings" && value) {
|
|
$syncWarningOverride.hide();
|
|
} else if (name === "motd") {
|
|
$chat.toggleClass("hide-" + name, !value);
|
|
} else if (name === "statusMessages") {
|
|
$chat.toggleClass("hide-status-messages", value === "hidden");
|
|
$chat.toggleClass("condensed-status-messages", value === "condensed");
|
|
} else if (name === "coloredNicks") {
|
|
$chat.toggleClass("colored-nicks", value);
|
|
} else if (name === "theme") {
|
|
value = `themes/${value}.css`;
|
|
|
|
if ($theme.attr("href") !== value) {
|
|
$theme.attr("href", 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("\\b(?:" + highlightsTokens.join("|") + ")\\b", "i");
|
|
} else {
|
|
module.exports.highlightsRE = null;
|
|
}
|
|
} else if (name === "showSeconds") {
|
|
$chat.find(".msg > .time").each(function() {
|
|
$(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);
|
|
} else if (!value) {
|
|
$warningBlocked.hide();
|
|
}
|
|
} else if (name === "advanced") {
|
|
$("#settings [data-advanced]").toggle(settings[name]);
|
|
}
|
|
}
|
|
|
|
function settingSetEmit(name, value) {
|
|
socket.emit("setting:set", {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;
|
|
storage.set("settings", JSON.stringify(settings));
|
|
applySetting(name, value);
|
|
|
|
// Sync is checked, request settings from server.
|
|
if (name === "syncSettings" && value) {
|
|
socket.emit("setting:get");
|
|
$syncWarningOverride.hide();
|
|
$syncWarningBase.hide();
|
|
} else if (name === "syncSettings") {
|
|
$syncWarningOverride.show();
|
|
}
|
|
|
|
if (settings.syncSettings && !noSync.includes(name) && sync) {
|
|
settingSetEmit(name, value);
|
|
} else if (alwaysSync.includes(name) && sync) {
|
|
settingSetEmit(name, value);
|
|
}
|
|
}
|
|
}
|
|
|
|
function noServerSettings() {
|
|
// Sync is enabled but the server has no settings so we sync all settings from this client.
|
|
if (settings.syncSettings) {
|
|
for (const name in settings) {
|
|
if (!noSync.includes(name)) {
|
|
settingSetEmit(name, settings[name]);
|
|
} else if (alwaysSync.includes(name)) {
|
|
settingSetEmit(name, settings[name]);
|
|
}
|
|
}
|
|
|
|
$syncWarningOverride.hide();
|
|
$syncWarningBase.hide();
|
|
} else {
|
|
$syncWarningOverride.hide();
|
|
$syncWarningBase.show();
|
|
}
|
|
}
|
|
|
|
// If `save` is set to true it will pass the setting to `updateSetting()` processSetting
|
|
function processSetting(name, value, save) {
|
|
if (name === "userStyles") {
|
|
$settings.find("#user-specified-css-input").val(value);
|
|
} else if (name === "highlights") {
|
|
$settings.find(`input[name=${name}]`).val(value);
|
|
} else if (name === "nickPostfix") {
|
|
$settings.find(`input[name=${name}]`).val(value);
|
|
} else if (name === "statusMessages") {
|
|
$settings.find(`input[name=${name}][value=${value}]`)
|
|
.prop("checked", true);
|
|
} else if (name === "theme") {
|
|
$settings.find("#theme-select").val(value);
|
|
} else if (typeof value === "boolean") {
|
|
$settings.find(`input[name=${name}]`).prop("checked", value);
|
|
}
|
|
|
|
// No need to also call processSetting when `save` is true.
|
|
// updateSetting does take care of that.
|
|
if (save) {
|
|
// Sync is false as applySetting is never called as the result of a user changing the setting.
|
|
updateSetting(name, value, false);
|
|
} else {
|
|
applySetting(name, value);
|
|
}
|
|
}
|
|
|
|
function initialize() {
|
|
$warningBlocked = $settings.find("#warnBlockedDesktopNotifications");
|
|
$warningUnsupported = $settings.find("#warnUnsupportedDesktopNotifications");
|
|
|
|
$syncWarningOverride = $settings.find(".sync-warning-override");
|
|
$syncWarningBase = $settings.find(".sync-warning-base");
|
|
|
|
$warningBlocked.hide();
|
|
module.exports.initialized = true;
|
|
|
|
// Settings have now entirely updated, apply settings to the client.
|
|
for (const name in settings) {
|
|
processSetting(name, settings[name], false);
|
|
}
|
|
|
|
// If browser does not support notifications
|
|
// display proper message in settings.
|
|
if (("Notification" in window)) {
|
|
$warningUnsupported.hide();
|
|
$windows.on("show", "#settings", updateDesktopNotificationStatus);
|
|
} else {
|
|
$warningUnsupported.show();
|
|
}
|
|
|
|
$settings.on("change", "input, select, textarea", function(e) {
|
|
// We only want to trigger on human triggerd changes.
|
|
if (e.originalEvent) {
|
|
const $self = $(this);
|
|
const type = $self.prop("type");
|
|
const name = $self.prop("name");
|
|
|
|
if (type === "radio") {
|
|
if ($self.prop("checked")) {
|
|
updateSetting(name, $self.val(), true);
|
|
}
|
|
} else if (type === "checkbox") {
|
|
updateSetting(name, $self.prop("checked"), true);
|
|
settings[name] = $self.prop("checked");
|
|
} else if (type !== "password") {
|
|
updateSetting(name, $self.val(), true);
|
|
}
|
|
}
|
|
});
|
|
|
|
// Local init is done, let's sync
|
|
// We always ask for synced settings even if it is disabled.
|
|
// Settings can be mandatory to sync and it is used to determine sync base state.
|
|
socket.emit("settings:get");
|
|
}
|