Offer optional syncing of client settings
Write synced settings to localstorage. move settings and webpush init to init.js stub for server sending clientsettings get very basic setting sync working Also update client.config.clientSettings on settings:set Full setting sync with mandatory and excluded sync options Actually check client preferences. Further settings restructuring. Refactor options.js make storage act in a sane manner. Add new parameter to applySetting Do not sync if the setting is stored as a result of syncing General clean up, commenting and restructing. sync from server on checking "sync" offer initial sync Better deal with DOM being ready and instances of inital sync showing Don't try to disable autocompletion when not enabled. Restructure option.js to seperate functions from settings. More consistency in naming options vs settings Switch processSetting and applySetting names reflecting their functionality better. move options init back to configuration. simplify how settings are synced around. move options init after template building. Remove unneeded hasOwnProperty Use global for #theme and only apply theme in applySetting Return when no server side clientsettings excist. Autocompletion options to options.settings Make nocss param in url work again. Actually filter out empty highlight values. Clarify alwaysSync comment. Remove manual step for initial sync change attr to prop in options.js replace unbind with off in autocompletion.js Do not sync settings when the lounge is set to public. fix eslint error Fix merge error Do not show sync warning after page refresh when sync is enabled Move setting sync label in actual label. Improve server setting sync handling performance and failure potential. Don't give impression that the desktop notificiation is off when the browser permission is denied. Refine showing and hiding of notification warnings. rename all setting socket events to singular setting. add experimental note and icon to settingsync. fix css linting error
This commit is contained in:
parent
f9ca608ad5
commit
27393bb804
@ -223,6 +223,7 @@ kbd {
|
||||
#chat .title::before,
|
||||
#footer .icon,
|
||||
#chat .count::before,
|
||||
#settings .extra-experimental,
|
||||
#settings .extra-help,
|
||||
#settings #play::before,
|
||||
#form #submit::before,
|
||||
@ -401,6 +402,10 @@ kbd {
|
||||
line-height: 50px;
|
||||
}
|
||||
|
||||
#settings .extra-experimental::before {
|
||||
content: "\f0c3"; /* https://fontawesome.com/icons/flask?style=solid */
|
||||
}
|
||||
|
||||
#settings .extra-help::before {
|
||||
content: "\f059"; /* http://fontawesome.io/icon/question-circle/ */
|
||||
}
|
||||
@ -1565,6 +1570,10 @@ part/quit messages where we don't load previews (adds a blank line otherwise) */
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
#settings .sync-warning-base {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#settings .opt {
|
||||
display: block;
|
||||
padding: 5px 0 5px 1px;
|
||||
@ -1574,15 +1583,21 @@ part/quit messages where we don't load previews (adds a blank line otherwise) */
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
#settings .extra-experimental {
|
||||
color: #84ce88;
|
||||
}
|
||||
|
||||
#settings .extra-help,
|
||||
#settings #play {
|
||||
color: #7f8c8d;
|
||||
}
|
||||
|
||||
#settings .extra-experimental,
|
||||
#settings .extra-help {
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
#settings .extra-experimental,
|
||||
#settings h2 .extra-help {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
@ -10,16 +10,16 @@ const constants = require("./constants");
|
||||
|
||||
const input = $("#input");
|
||||
let textcomplete;
|
||||
let enabled = false;
|
||||
|
||||
module.exports = {
|
||||
enable: enableAutocomplete,
|
||||
disable: () => {
|
||||
input.off("input.tabcomplete");
|
||||
Mousetrap(input.get(0)).off("tab", "keydown");
|
||||
|
||||
if (textcomplete) {
|
||||
if (enabled) {
|
||||
input.off("input.tabcomplete");
|
||||
Mousetrap(input.get(0)).off("tab", "keydown");
|
||||
textcomplete.destroy();
|
||||
textcomplete = null;
|
||||
enabled = false;
|
||||
}
|
||||
},
|
||||
};
|
||||
@ -63,7 +63,7 @@ const nicksStrategy = {
|
||||
},
|
||||
replace([, original], position = 1) {
|
||||
// If no postfix specified, return autocompleted nick as-is
|
||||
if (!options.nickPostfix) {
|
||||
if (!options.settings.nickPostfix) {
|
||||
return original;
|
||||
}
|
||||
|
||||
@ -73,7 +73,7 @@ const nicksStrategy = {
|
||||
}
|
||||
|
||||
// If nick is first in the input, append specified postfix
|
||||
return original + options.nickPostfix;
|
||||
return original + options.settings.nickPostfix;
|
||||
},
|
||||
index: 1,
|
||||
};
|
||||
@ -169,6 +169,7 @@ const backgroundColorStrategy = {
|
||||
};
|
||||
|
||||
function enableAutocomplete() {
|
||||
enabled = true;
|
||||
let tabCount = 0;
|
||||
let lastMatch = "";
|
||||
let currentMatches = [];
|
||||
|
@ -5,6 +5,6 @@ const constants = require("../../constants");
|
||||
|
||||
module.exports = function(time) {
|
||||
const options = require("../../options");
|
||||
const format = options.showSeconds ? constants.timeFormats.msgWithSeconds : constants.timeFormats.msgDefault;
|
||||
const format = options.settings.showSeconds ? constants.timeFormats.msgWithSeconds : constants.timeFormats.msgDefault;
|
||||
return moment(time).format(format);
|
||||
};
|
||||
|
@ -2,15 +2,28 @@
|
||||
|
||||
const $ = require("jquery");
|
||||
const escapeRegExp = require("lodash/escapeRegExp");
|
||||
const userStyles = $("#user-specified-css");
|
||||
const storage = require("./localStorage");
|
||||
const tz = require("./libs/handlebars/tz");
|
||||
const socket = require("./socket");
|
||||
|
||||
const windows = $("#windows");
|
||||
const chat = $("#chat");
|
||||
const $windows = $("#windows");
|
||||
const $chat = $("#chat");
|
||||
const $settings = $("#settings");
|
||||
const $theme = $("#theme");
|
||||
const $userStyles = $("#user-specified-css");
|
||||
|
||||
// Default options
|
||||
const options = {
|
||||
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,
|
||||
autocomplete: true,
|
||||
nickPostfix: "",
|
||||
coloredNicks: true,
|
||||
@ -24,155 +37,267 @@ const options = {
|
||||
statusMessages: "condensed",
|
||||
theme: $("#theme").data("server-theme"),
|
||||
media: true,
|
||||
userStyles: userStyles.text(),
|
||||
userStyles: "",
|
||||
};
|
||||
let userOptions = JSON.parse(storage.get("settings")) || {};
|
||||
|
||||
for (const key in options) {
|
||||
if (userOptions[key] !== undefined) {
|
||||
options[key] = userOptions[key];
|
||||
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 on page load
|
||||
if (typeof userOptions.userStyles === "string" && !/[?&]nocss/.test(window.location.search)) {
|
||||
userStyles.html(userOptions.userStyles);
|
||||
// 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);
|
||||
}
|
||||
|
||||
userOptions = null;
|
||||
if (typeof userSettings.theme === "string") {
|
||||
$theme.prop("href", `themes/${userSettings.theme}.css`);
|
||||
}
|
||||
|
||||
module.exports = options;
|
||||
userSettings = null;
|
||||
|
||||
module.exports = {
|
||||
alwaysSync: alwaysSync,
|
||||
noSync: noSync,
|
||||
initialized: false,
|
||||
highlightsRE: null,
|
||||
settings: settings,
|
||||
shouldOpenMessagePreview,
|
||||
noServerSettings,
|
||||
processSetting,
|
||||
initialize,
|
||||
};
|
||||
|
||||
// Due to cyclical dependency, have to require it after exports
|
||||
const autocompletion = require("./autocompletion");
|
||||
|
||||
module.exports.shouldOpenMessagePreview = function(type) {
|
||||
return type === "link" ? options.links : options.media;
|
||||
};
|
||||
function shouldOpenMessagePreview(type) {
|
||||
return type === "link" ? settings.links : settings.media;
|
||||
}
|
||||
|
||||
module.exports.initialize = () => {
|
||||
module.exports.initialize = null;
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
||||
const settings = $("#settings");
|
||||
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") {
|
||||
$theme.prop("href", `themes/${value}.css`);
|
||||
} else if (name === "userStyles" && !noCSSparamReg.test(window.location.search)) {
|
||||
$userStyles.html(value);
|
||||
} else if (name === "highlights") {
|
||||
let highlights;
|
||||
|
||||
for (const i in options) {
|
||||
if (i === "userStyles") {
|
||||
settings.find("#user-specified-css-input").val(options[i]);
|
||||
} else if (i === "highlights") {
|
||||
settings.find("input[name=" + i + "]").val(options[i]);
|
||||
} else if (i === "nickPostfix") {
|
||||
settings.find("input[name=" + i + "]").val(options[i]);
|
||||
} else if (i === "statusMessages") {
|
||||
settings.find(`input[name=${i}][value=${options[i]}]`)
|
||||
.prop("checked", true);
|
||||
} else if (i === "theme") {
|
||||
$("#theme").prop("href", "themes/" + options[i] + ".css");
|
||||
settings.find("select[name=" + i + "]").val(options[i]);
|
||||
} else if (options[i]) {
|
||||
settings.find("input[name=" + i + "]").prop("checked", true);
|
||||
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 (value && Notification.permission !== "granted") {
|
||||
Notification.requestPermission(updateDesktopNotificationStatus);
|
||||
} else if (!value) {
|
||||
$warningBlocked.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const desktopNotificationsCheckbox = $("#desktopNotifications");
|
||||
const warningUnsupported = $("#warnUnsupportedDesktopNotifications");
|
||||
const warningBlocked = $("#warnBlockedDesktopNotifications").hide();
|
||||
function settingSetEmit(name, value) {
|
||||
socket.emit("setting:set", {
|
||||
name: name,
|
||||
value: value,
|
||||
});
|
||||
}
|
||||
|
||||
// Updates the checkbox and warning in settings when the Settings page is
|
||||
// opened or when the checkbox state is changed.
|
||||
// When notifications are not supported, this is never called (because
|
||||
// checkbox state can not be changed).
|
||||
const updateDesktopNotificationStatus = function() {
|
||||
if (Notification.permission === "denied") {
|
||||
desktopNotificationsCheckbox.prop("disabled", true);
|
||||
desktopNotificationsCheckbox.prop("checked", false);
|
||||
warningBlocked.show();
|
||||
} else {
|
||||
if (Notification.permission === "default" && desktopNotificationsCheckbox.prop("checked")) {
|
||||
desktopNotificationsCheckbox.prop("checked", false);
|
||||
}
|
||||
// When sync is `true` the setting will also be send to the backend for syncing.
|
||||
function updateSetting(name, value, sync) {
|
||||
let storeValue = value;
|
||||
|
||||
desktopNotificationsCheckbox.prop("disabled", false);
|
||||
warningBlocked.hide();
|
||||
// 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 browser does not support notifications, override existing settings and
|
||||
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);
|
||||
$warningUnsupported.hide();
|
||||
$windows.on("show", "#settings", updateDesktopNotificationStatus);
|
||||
} else {
|
||||
options.desktopNotifications = false;
|
||||
desktopNotificationsCheckbox.prop("disabled", true);
|
||||
desktopNotificationsCheckbox.prop("checked", false);
|
||||
$warningUnsupported.show();
|
||||
}
|
||||
|
||||
settings.on("change", "input, select, textarea", function() {
|
||||
const self = $(this);
|
||||
const type = self.prop("type");
|
||||
const name = self.prop("name");
|
||||
$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 === "password") {
|
||||
return;
|
||||
} else if (type === "radio") {
|
||||
if (self.prop("checked")) {
|
||||
options[name] = self.val();
|
||||
}
|
||||
} else if (type === "checkbox") {
|
||||
options[name] = self.prop("checked");
|
||||
} else {
|
||||
options[name] = self.val();
|
||||
}
|
||||
|
||||
storage.set("settings", JSON.stringify(options));
|
||||
|
||||
if (name === "motd") {
|
||||
chat.toggleClass("hide-" + name, !self.prop("checked"));
|
||||
} else if (name === "statusMessages") {
|
||||
chat.toggleClass("hide-status-messages", options[name] === "hidden");
|
||||
chat.toggleClass("condensed-status-messages", options[name] === "condensed");
|
||||
} else if (name === "coloredNicks") {
|
||||
chat.toggleClass("colored-nicks", self.prop("checked"));
|
||||
} else if (name === "theme") {
|
||||
$("#theme").prop("href", "themes/" + options[name] + ".css");
|
||||
} else if (name === "userStyles") {
|
||||
userStyles.html(options[name]);
|
||||
} else if (name === "highlights") {
|
||||
options.highlights = options[name].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 !== "";
|
||||
});
|
||||
// Construct regex with wordboundary for every highlight item
|
||||
const highlightsTokens = options.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 === "nickPostfix") {
|
||||
options.nickPostfix = options[name];
|
||||
} else if (name === "showSeconds") {
|
||||
chat.find(".msg > .time").each(function() {
|
||||
$(this).text(tz($(this).parent().data("time")));
|
||||
});
|
||||
chat.toggleClass("show-seconds", self.prop("checked"));
|
||||
} else if (name === "autocomplete") {
|
||||
if (self.prop("checked")) {
|
||||
autocompletion.enable();
|
||||
} else {
|
||||
autocompletion.disable();
|
||||
}
|
||||
} else if (name === "desktopNotifications") {
|
||||
if ($(this).prop("checked") && Notification.permission !== "granted") {
|
||||
Notification.requestPermission(updateDesktopNotificationStatus);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}).find("input")
|
||||
.trigger("change");
|
||||
};
|
||||
});
|
||||
|
||||
// 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");
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ const options = require("../options");
|
||||
const webpush = require("../webpush");
|
||||
|
||||
socket.on("configuration", function(data) {
|
||||
if (!options.initialize) {
|
||||
if (options.initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -20,3 +20,4 @@ require("./sign_out");
|
||||
require("./sessions_list");
|
||||
require("./configuration");
|
||||
require("./changelog");
|
||||
require("./setting");
|
||||
|
@ -124,9 +124,9 @@ function notifyMessage(targetId, channel, msg) {
|
||||
|
||||
const button = sidebar.find(".chan[data-id='" + targetId + "']");
|
||||
|
||||
if (msg.highlight || (options.notifyAllMessages && msg.type === "message")) {
|
||||
if (msg.highlight || (options.settings.notifyAllMessages && msg.type === "message")) {
|
||||
if (!document.hasFocus() || !channel.hasClass("active")) {
|
||||
if (options.notification) {
|
||||
if (options.settings.notification) {
|
||||
try {
|
||||
pop.play();
|
||||
} catch (exception) {
|
||||
@ -136,7 +136,7 @@ function notifyMessage(targetId, channel, msg) {
|
||||
|
||||
utils.toggleNotificationMarkers(true);
|
||||
|
||||
if (options.desktopNotifications && Notification.permission === "granted") {
|
||||
if (options.settings.desktopNotifications && ("Notification" in window) && Notification.permission === "granted") {
|
||||
let title;
|
||||
let body;
|
||||
|
||||
|
28
client/js/socket-events/setting.js
Normal file
28
client/js/socket-events/setting.js
Normal file
@ -0,0 +1,28 @@
|
||||
"use strict";
|
||||
|
||||
const socket = require("../socket");
|
||||
const options = require("../options");
|
||||
|
||||
function evaluateSetting(name, value) {
|
||||
if (options.settings.syncSettings && options.settings[name] !== value && !options.noSync.includes(name)) {
|
||||
options.processSetting(name, value, true);
|
||||
} else if (options.alwaysSync.includes(name)) {
|
||||
options.processSetting(name, value, true);
|
||||
}
|
||||
}
|
||||
|
||||
socket.on("setting:new", function(data) {
|
||||
const name = data.name;
|
||||
const value = data.value;
|
||||
evaluateSetting(name, value);
|
||||
});
|
||||
|
||||
socket.on("setting:all", function(settings) {
|
||||
if (Object.keys(settings).length === 0) {
|
||||
options.noServerSettings();
|
||||
} else {
|
||||
for (const name in settings) {
|
||||
evaluateSetting(name, settings[name]);
|
||||
}
|
||||
}
|
||||
});
|
@ -30,7 +30,7 @@ module.exports = function() {
|
||||
}
|
||||
);
|
||||
|
||||
options.ignoreSortSync = true;
|
||||
options.settings.ignoreSortSync = true;
|
||||
},
|
||||
});
|
||||
sidebar.find(".network").sortable({
|
||||
@ -58,7 +58,7 @@ module.exports = function() {
|
||||
}
|
||||
);
|
||||
|
||||
options.ignoreSortSync = true;
|
||||
options.settings.ignoreSortSync = true;
|
||||
},
|
||||
});
|
||||
};
|
||||
|
@ -5,6 +5,22 @@
|
||||
<h1 class="title">Settings</h1>
|
||||
|
||||
<div class="row">
|
||||
{{#unless public}}
|
||||
<div class="col-sm-12">
|
||||
<h2>
|
||||
Settings synchronisation
|
||||
<span class="tooltipped tooltipped-n tooltipped-no-delay" aria-label="Note: This is an experimental feature and may change in future releases.">
|
||||
<button class="extra-experimental" aria-label="Note: This is an experimental feature and may change in future releases."></button>
|
||||
</span>
|
||||
</h2>
|
||||
<label class="opt">
|
||||
<input type="checkbox" name="syncSettings">
|
||||
Synchronize settings with other clients.
|
||||
</label>
|
||||
<p class="sync-warning-override"><strong>Warning</strong> Checking this box will override the settings of this client with those stored on the server.</p>
|
||||
<p class="sync-warning-base"><strong>Warning</strong> No settings have been synced before. Enabling this will sync all settings of this client as the base for other clients.</p>
|
||||
</div>
|
||||
{{/unless}}
|
||||
<div class="col-sm-12">
|
||||
<h2>Messages</h2>
|
||||
</div>
|
||||
|
@ -116,6 +116,7 @@ ClientManager.prototype.addUser = function(name, password, enableLog) {
|
||||
awayMessage: "",
|
||||
networks: [],
|
||||
sessions: {},
|
||||
clientSettings: {},
|
||||
};
|
||||
|
||||
try {
|
||||
|
@ -441,6 +441,44 @@ function initializeClient(socket, client, token, lastMessage) {
|
||||
|
||||
socket.on("sessions:get", sendSessionList);
|
||||
|
||||
if (!Helper.config.public) {
|
||||
socket.on("setting:set", (newSetting) => {
|
||||
if (!newSetting || typeof newSetting !== "object") {
|
||||
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;
|
||||
|
||||
// Pass the setting to all clients.
|
||||
client.emit("setting:new", {
|
||||
name: newSetting.name,
|
||||
value: newSetting.value,
|
||||
});
|
||||
|
||||
client.manager.updateUser(client.name, {
|
||||
clientSettings: client.config.clientSettings,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("setting:get", () => {
|
||||
if (!client.config.hasOwnProperty("clientSettings")) {
|
||||
socket.emit("setting:all", {});
|
||||
return;
|
||||
}
|
||||
|
||||
const clientSettings = client.config.clientSettings;
|
||||
socket.emit("setting:all", clientSettings);
|
||||
});
|
||||
}
|
||||
|
||||
socket.on("sign-out", (tokenToSignOut) => {
|
||||
// If no token provided, sign same client out
|
||||
if (!tokenToSignOut) {
|
||||
|
Loading…
Reference in New Issue
Block a user