Merge pull request #1851 from creesch/settingSync
Optional syncing of client settings.
This commit is contained in:
commit
b2eb11b5ef
@ -223,6 +223,7 @@ kbd {
|
|||||||
#chat .title::before,
|
#chat .title::before,
|
||||||
#footer .icon,
|
#footer .icon,
|
||||||
#chat .count::before,
|
#chat .count::before,
|
||||||
|
#settings .extra-experimental,
|
||||||
#settings .extra-help,
|
#settings .extra-help,
|
||||||
#settings #play::before,
|
#settings #play::before,
|
||||||
#form #submit::before,
|
#form #submit::before,
|
||||||
@ -413,6 +414,10 @@ kbd {
|
|||||||
line-height: 50px;
|
line-height: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#settings .extra-experimental::before {
|
||||||
|
content: "\f0c3"; /* https://fontawesome.com/icons/flask?style=solid */
|
||||||
|
}
|
||||||
|
|
||||||
#settings .extra-help::before {
|
#settings .extra-help::before {
|
||||||
content: "\f059"; /* http://fontawesome.io/icon/question-circle/ */
|
content: "\f059"; /* http://fontawesome.io/icon/question-circle/ */
|
||||||
}
|
}
|
||||||
@ -1626,6 +1631,10 @@ part/quit messages where we don't load previews (adds a blank line otherwise) */
|
|||||||
margin-top: 30px;
|
margin-top: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#settings .sync-warning-base {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
#settings .opt {
|
#settings .opt {
|
||||||
display: block;
|
display: block;
|
||||||
padding: 5px 0 5px 1px;
|
padding: 5px 0 5px 1px;
|
||||||
@ -1635,15 +1644,21 @@ part/quit messages where we don't load previews (adds a blank line otherwise) */
|
|||||||
margin-right: 6px;
|
margin-right: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#settings .extra-experimental {
|
||||||
|
color: #84ce88;
|
||||||
|
}
|
||||||
|
|
||||||
#settings .extra-help,
|
#settings .extra-help,
|
||||||
#settings #play {
|
#settings #play {
|
||||||
color: #7f8c8d;
|
color: #7f8c8d;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#settings .extra-experimental,
|
||||||
#settings .extra-help {
|
#settings .extra-help {
|
||||||
cursor: help;
|
cursor: help;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#settings .extra-experimental,
|
||||||
#settings h2 .extra-help {
|
#settings h2 .extra-help {
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
}
|
}
|
||||||
|
@ -10,16 +10,16 @@ const constants = require("./constants");
|
|||||||
|
|
||||||
const input = $("#input");
|
const input = $("#input");
|
||||||
let textcomplete;
|
let textcomplete;
|
||||||
|
let enabled = false;
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
enable: enableAutocomplete,
|
enable: enableAutocomplete,
|
||||||
disable: () => {
|
disable: () => {
|
||||||
input.off("input.tabcomplete");
|
if (enabled) {
|
||||||
Mousetrap(input.get(0)).off("tab", "keydown");
|
input.off("input.tabcomplete");
|
||||||
|
Mousetrap(input.get(0)).off("tab", "keydown");
|
||||||
if (textcomplete) {
|
|
||||||
textcomplete.destroy();
|
textcomplete.destroy();
|
||||||
textcomplete = null;
|
enabled = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -63,7 +63,7 @@ const nicksStrategy = {
|
|||||||
},
|
},
|
||||||
replace([, original], position = 1) {
|
replace([, original], position = 1) {
|
||||||
// If no postfix specified, return autocompleted nick as-is
|
// If no postfix specified, return autocompleted nick as-is
|
||||||
if (!options.nickPostfix) {
|
if (!options.settings.nickPostfix) {
|
||||||
return original;
|
return original;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,7 +73,7 @@ const nicksStrategy = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If nick is first in the input, append specified postfix
|
// If nick is first in the input, append specified postfix
|
||||||
return original + options.nickPostfix;
|
return original + options.settings.nickPostfix;
|
||||||
},
|
},
|
||||||
index: 1,
|
index: 1,
|
||||||
};
|
};
|
||||||
@ -169,6 +169,7 @@ const backgroundColorStrategy = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function enableAutocomplete() {
|
function enableAutocomplete() {
|
||||||
|
enabled = true;
|
||||||
let tabCount = 0;
|
let tabCount = 0;
|
||||||
let lastMatch = "";
|
let lastMatch = "";
|
||||||
let currentMatches = [];
|
let currentMatches = [];
|
||||||
|
@ -5,6 +5,6 @@ const constants = require("../../constants");
|
|||||||
|
|
||||||
module.exports = function(time) {
|
module.exports = function(time) {
|
||||||
const options = require("../../options");
|
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);
|
return moment(time).format(format);
|
||||||
};
|
};
|
||||||
|
@ -2,15 +2,28 @@
|
|||||||
|
|
||||||
const $ = require("jquery");
|
const $ = require("jquery");
|
||||||
const escapeRegExp = require("lodash/escapeRegExp");
|
const escapeRegExp = require("lodash/escapeRegExp");
|
||||||
const userStyles = $("#user-specified-css");
|
|
||||||
const storage = require("./localStorage");
|
const storage = require("./localStorage");
|
||||||
const tz = require("./libs/handlebars/tz");
|
const tz = require("./libs/handlebars/tz");
|
||||||
|
const socket = require("./socket");
|
||||||
|
|
||||||
const windows = $("#windows");
|
const $windows = $("#windows");
|
||||||
const chat = $("#chat");
|
const $chat = $("#chat");
|
||||||
|
const $settings = $("#settings");
|
||||||
|
const $theme = $("#theme");
|
||||||
|
const $userStyles = $("#user-specified-css");
|
||||||
|
|
||||||
// Default options
|
const noCSSparamReg = /[?&]nocss/;
|
||||||
const options = {
|
|
||||||
|
// 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,
|
autocomplete: true,
|
||||||
nickPostfix: "",
|
nickPostfix: "",
|
||||||
coloredNicks: true,
|
coloredNicks: true,
|
||||||
@ -24,155 +37,267 @@ const options = {
|
|||||||
statusMessages: "condensed",
|
statusMessages: "condensed",
|
||||||
theme: $("#theme").data("server-theme"),
|
theme: $("#theme").data("server-theme"),
|
||||||
media: true,
|
media: true,
|
||||||
userStyles: userStyles.text(),
|
userStyles: "",
|
||||||
};
|
};
|
||||||
let userOptions = JSON.parse(storage.get("settings")) || {};
|
|
||||||
|
|
||||||
for (const key in options) {
|
const noSync = ["syncSettings"];
|
||||||
if (userOptions[key] !== undefined) {
|
|
||||||
options[key] = userOptions[key];
|
// 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
|
// Apply custom CSS and themes on page load
|
||||||
if (typeof userOptions.userStyles === "string" && !/[?&]nocss/.test(window.location.search)) {
|
// Done here and not on init because on slower devices and connections
|
||||||
userStyles.html(userOptions.userStyles);
|
// 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
|
// Due to cyclical dependency, have to require it after exports
|
||||||
const autocompletion = require("./autocompletion");
|
const autocompletion = require("./autocompletion");
|
||||||
|
|
||||||
module.exports.shouldOpenMessagePreview = function(type) {
|
function shouldOpenMessagePreview(type) {
|
||||||
return type === "link" ? options.links : options.media;
|
return type === "link" ? settings.links : settings.media;
|
||||||
};
|
}
|
||||||
|
|
||||||
module.exports.initialize = () => {
|
// Updates the checkbox and warning in settings.
|
||||||
module.exports.initialize = null;
|
// 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 (typeof value === "string") {
|
||||||
if (i === "userStyles") {
|
highlights = value.split(",").map(function(h) {
|
||||||
settings.find("#user-specified-css-input").val(options[i]);
|
return h.trim();
|
||||||
} else if (i === "highlights") {
|
});
|
||||||
settings.find("input[name=" + i + "]").val(options[i]);
|
} else {
|
||||||
} else if (i === "nickPostfix") {
|
highlights = value;
|
||||||
settings.find("input[name=" + i + "]").val(options[i]);
|
}
|
||||||
} else if (i === "statusMessages") {
|
|
||||||
settings.find(`input[name=${i}][value=${options[i]}]`)
|
highlights = highlights.filter(function(h) {
|
||||||
.prop("checked", true);
|
// Ensure we don't have empty string in the list of highlights
|
||||||
} else if (i === "theme") {
|
// otherwise, users get notifications for everything
|
||||||
$("#theme").prop("href", "themes/" + options[i] + ".css");
|
return h !== "";
|
||||||
settings.find("select[name=" + i + "]").val(options[i]);
|
});
|
||||||
} else if (options[i]) {
|
// Construct regex with wordboundary for every highlight item
|
||||||
settings.find("input[name=" + i + "]").prop("checked", true);
|
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");
|
function settingSetEmit(name, value) {
|
||||||
const warningUnsupported = $("#warnUnsupportedDesktopNotifications");
|
socket.emit("setting:set", {
|
||||||
const warningBlocked = $("#warnBlockedDesktopNotifications").hide();
|
name: name,
|
||||||
|
value: value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Updates the checkbox and warning in settings when the Settings page is
|
// When sync is `true` the setting will also be send to the backend for syncing.
|
||||||
// opened or when the checkbox state is changed.
|
function updateSetting(name, value, sync) {
|
||||||
// When notifications are not supported, this is never called (because
|
let storeValue = value;
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
desktopNotificationsCheckbox.prop("disabled", false);
|
// First convert highlights if input is a string.
|
||||||
warningBlocked.hide();
|
// 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.
|
// display proper message in settings.
|
||||||
if (("Notification" in window)) {
|
if (("Notification" in window)) {
|
||||||
warningUnsupported.hide();
|
$warningUnsupported.hide();
|
||||||
windows.on("show", "#settings", updateDesktopNotificationStatus);
|
$windows.on("show", "#settings", updateDesktopNotificationStatus);
|
||||||
} else {
|
} else {
|
||||||
options.desktopNotifications = false;
|
$warningUnsupported.show();
|
||||||
desktopNotificationsCheckbox.prop("disabled", true);
|
|
||||||
desktopNotificationsCheckbox.prop("checked", false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
settings.on("change", "input, select, textarea", function() {
|
$settings.on("change", "input, select, textarea", function(e) {
|
||||||
const self = $(this);
|
// We only want to trigger on human triggerd changes.
|
||||||
const type = self.prop("type");
|
if (e.originalEvent) {
|
||||||
const name = self.prop("name");
|
const $self = $(this);
|
||||||
|
const type = $self.prop("type");
|
||||||
|
const name = $self.prop("name");
|
||||||
|
|
||||||
if (type === "password") {
|
if (type === "radio") {
|
||||||
return;
|
if ($self.prop("checked")) {
|
||||||
} else if (type === "radio") {
|
updateSetting(name, $self.val(), true);
|
||||||
if (self.prop("checked")) {
|
}
|
||||||
options[name] = self.val();
|
} else if (type === "checkbox") {
|
||||||
}
|
updateSetting(name, $self.prop("checked"), true);
|
||||||
} else if (type === "checkbox") {
|
settings[name] = $self.prop("checked");
|
||||||
options[name] = self.prop("checked");
|
} else if (type !== "password") {
|
||||||
} else {
|
updateSetting(name, $self.val(), true);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).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");
|
const webpush = require("../webpush");
|
||||||
|
|
||||||
socket.on("configuration", function(data) {
|
socket.on("configuration", function(data) {
|
||||||
if (!options.initialize) {
|
if (options.initialized) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,3 +20,4 @@ require("./sign_out");
|
|||||||
require("./sessions_list");
|
require("./sessions_list");
|
||||||
require("./configuration");
|
require("./configuration");
|
||||||
require("./changelog");
|
require("./changelog");
|
||||||
|
require("./setting");
|
||||||
|
@ -125,9 +125,9 @@ function notifyMessage(targetId, channel, msg) {
|
|||||||
|
|
||||||
const button = sidebar.find(".chan[data-id='" + targetId + "']");
|
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 (!document.hasFocus() || !channel.hasClass("active")) {
|
||||||
if (options.notification) {
|
if (options.settings.notification) {
|
||||||
try {
|
try {
|
||||||
pop.play();
|
pop.play();
|
||||||
} catch (exception) {
|
} catch (exception) {
|
||||||
@ -137,7 +137,7 @@ function notifyMessage(targetId, channel, msg) {
|
|||||||
|
|
||||||
utils.toggleNotificationMarkers(true);
|
utils.toggleNotificationMarkers(true);
|
||||||
|
|
||||||
if (options.desktopNotifications && Notification.permission === "granted") {
|
if (options.settings.desktopNotifications && ("Notification" in window) && Notification.permission === "granted") {
|
||||||
let title;
|
let title;
|
||||||
let body;
|
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({
|
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>
|
<h1 class="title">Settings</h1>
|
||||||
|
|
||||||
<div class="row">
|
<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">
|
<div class="col-sm-12">
|
||||||
<h2>Messages</h2>
|
<h2>Messages</h2>
|
||||||
</div>
|
</div>
|
||||||
|
@ -116,6 +116,7 @@ ClientManager.prototype.addUser = function(name, password, enableLog) {
|
|||||||
awayMessage: "",
|
awayMessage: "",
|
||||||
networks: [],
|
networks: [],
|
||||||
sessions: {},
|
sessions: {},
|
||||||
|
clientSettings: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -445,6 +445,44 @@ function initializeClient(socket, client, token, lastMessage) {
|
|||||||
|
|
||||||
socket.on("sessions:get", sendSessionList);
|
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) => {
|
socket.on("sign-out", (tokenToSignOut) => {
|
||||||
// If no token provided, sign same client out
|
// If no token provided, sign same client out
|
||||||
if (!tokenToSignOut) {
|
if (!tokenToSignOut) {
|
||||||
|
Loading…
Reference in New Issue
Block a user