diff --git a/package.json b/package.json index 49d1d4b8..78a0a026 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "Calvin Montgomery", "name": "CyTube", "description": "Online media synchronizer and chat", - "version": "3.24.3", + "version": "3.25.0", "repository": { "url": "http://github.com/calzoneman/sync" }, diff --git a/src/channel/opts.js b/src/channel/opts.js index 175fd28b..c1b7545f 100644 --- a/src/channel/opts.js +++ b/src/channel/opts.js @@ -3,6 +3,10 @@ var Config = require("../config"); var Utilities = require("../utilities"); var url = require("url"); +function realTypeOf(thing) { + return thing === null ? 'null' : typeof thing; +} + function OptionsModule(channel) { ChannelModule.apply(this, arguments); this.opts = { @@ -100,8 +104,11 @@ OptionsModule.prototype.handleSetOptions = function (user, data) { return; } + var sendUpdate = false; + if ("allow_voteskip" in data) { this.opts.allow_voteskip = Boolean(data.allow_voteskip); + sendUpdate = true; } if ("voteskip_ratio" in data) { @@ -110,6 +117,7 @@ OptionsModule.prototype.handleSetOptions = function (user, data) { ratio = 0; } this.opts.voteskip_ratio = ratio; + sendUpdate = true; } if ("afk_timeout" in data) { @@ -125,12 +133,14 @@ OptionsModule.prototype.handleSetOptions = function (user, data) { u.autoAFK(); }); } + sendUpdate = true; } if ("pagetitle" in data && user.account.effectiveRank >= 3) { var title = (""+data.pagetitle).substring(0, 100); if (!title.trim().match(Config.get("reserved-names.pagetitles"))) { this.opts.pagetitle = (""+data.pagetitle).substring(0, 100); + sendUpdate = true; } else { user.socket.emit("errorMsg", { msg: "That pagetitle is reserved", @@ -151,31 +161,42 @@ OptionsModule.prototype.handleSetOptions = function (user, data) { ml = 0; } this.opts.maxlength = ml; + sendUpdate = true; } if ("externalcss" in data && user.account.effectiveRank >= 3) { - var link = (""+data.externalcss).substring(0, 255); + var prefix = "Invalid URL for external CSS: "; + if (typeof data.externalcss !== "string") { + user.socket.emit("validationError", { + target: "#cs-externalcss", + message: prefix + "URL must be a string, not " + + realTypeOf(data.externalcss) + }); + } + + var link = data.externalcss.substring(0, 255).trim(); if (!link) { this.opts.externalcss = ""; + sendUpdate = true; } else { - try { - var data = url.parse(link); - if (!data.protocol || !data.protocol.match(/^(https?|ftp):/)) { - throw "Unacceptable protocol " + data.protocol; - } else if (!data.host) { - throw "URL is missing host"; - } else { - link = data.href; - } - } catch (e) { - user.socket.emit("errorMsg", { - msg: "Invalid URL for external CSS: " + e, - alert: true + var data = url.parse(link); + if (!data.protocol || data.protocol !== 'https:') { + user.socket.emit("validationError", { + target: "#cs-externalcss", + message: prefix + " URL must begin with 'https://'" }); - return; + } else if (!data.host) { + user.socket.emit("validationError", { + target: "#cs-externalcss", + message: prefix + "missing hostname" + }); + } else { + user.socket.emit("validationPassed", { + target: "#cs-externalcss" + }); + this.opts.externalcss = data.href; + sendUpdate = true; } - - this.opts.externalcss = link; } } @@ -183,6 +204,7 @@ OptionsModule.prototype.handleSetOptions = function (user, data) { var link = (""+data.externaljs).substring(0, 255); if (!link) { this.opts.externaljs = ""; + sendUpdate = true; } else { try { @@ -203,11 +225,13 @@ OptionsModule.prototype.handleSetOptions = function (user, data) { } this.opts.externaljs = link; + sendUpdate = true; } } if ("chat_antiflood" in data) { this.opts.chat_antiflood = Boolean(data.chat_antiflood); + sendUpdate = true; } if ("chat_antiflood_params" in data) { @@ -238,38 +262,46 @@ OptionsModule.prototype.handleSetOptions = function (user, data) { sustained: s, cooldown: c }; + sendUpdate = true; } if ("show_public" in data && user.account.effectiveRank >= 3) { this.opts.show_public = Boolean(data.show_public); + sendUpdate = true; } if ("enable_link_regex" in data) { this.opts.enable_link_regex = Boolean(data.enable_link_regex); + sendUpdate = true; } if ("password" in data && user.account.effectiveRank >= 3) { var pw = data.password + ""; pw = pw === "" ? false : pw.substring(0, 100); this.opts.password = pw; + sendUpdate = true; } if ("allow_dupes" in data) { this.opts.allow_dupes = Boolean(data.allow_dupes); + sendUpdate = true; } if ("torbanned" in data && user.account.effectiveRank >= 3) { this.opts.torbanned = Boolean(data.torbanned); + sendUpdate = true; } if ("allow_ascii_control" in data && user.account.effectiveRank >= 3) { this.opts.allow_ascii_control = Boolean(data.allow_ascii_control); + sendUpdate = true; } if ("playlist_max_per_user" in data && user.account.effectiveRank >= 3) { var max = parseInt(data.playlist_max_per_user); if (!isNaN(max) && max >= 0) { this.opts.playlist_max_per_user = max; + sendUpdate = true; } } @@ -277,6 +309,7 @@ OptionsModule.prototype.handleSetOptions = function (user, data) { var delay = data.new_user_chat_delay; if (!isNaN(delay) && delay >= 0) { this.opts.new_user_chat_delay = delay; + sendUpdate = true; } } @@ -284,11 +317,14 @@ OptionsModule.prototype.handleSetOptions = function (user, data) { var delay = data.new_user_chat_link_delay; if (!isNaN(delay) && delay >= 0) { this.opts.new_user_chat_link_delay = delay; + sendUpdate = true; } } this.channel.logger.log("[mod] " + user.getName() + " updated channel options"); - this.sendOpts(this.channel.users); + if (sendUpdate) { + this.sendOpts(this.channel.users); + } }; module.exports = OptionsModule; diff --git a/www/js/callbacks.js b/www/js/callbacks.js index 6b89328e..d9b54c0b 100644 --- a/www/js/callbacks.js +++ b/www/js/callbacks.js @@ -49,7 +49,6 @@ Callbacks = { }, costanza: function (data) { - hidePlayer(); $("#costanza-modal").modal("hide"); var modal = makeModal(); modal.attr("id", "costanza-modal") @@ -61,7 +60,6 @@ Callbacks = { .appendTo(body); $("").text(data.msg).appendTo(body); - hidePlayer(); modal.modal(); }, @@ -1052,6 +1050,38 @@ Callbacks = { HAS_CONNECTED_BEFORE = false; ioServerConnect(socketConfig); setupCallbacks(); + }, + + validationError: function (error) { + var target = $(error.target); + target.parent().find(".text-danger").remove(); + + var formGroup = target.parent(); + while (!formGroup.hasClass("form-group") && formGroup.length > 0) { + formGroup = formGroup.parent(); + } + + if (formGroup.length > 0) { + formGroup.addClass("has-error"); + } + + $("

").addClass("text-danger") + .text(error.message) + .insertAfter(target); + }, + + validationPassed: function (data) { + var target = $(data.target); + target.parent().find(".text-danger").remove(); + + var formGroup = target.parent(); + while (!formGroup.hasClass("form-group") && formGroup.length > 0) { + formGroup = formGroup.parent(); + } + + if (formGroup.length > 0) { + formGroup.removeClass("has-error"); + } } } diff --git a/www/js/ui.js b/www/js/ui.js index 2e6b6869..83c66ae4 100644 --- a/www/js/ui.js +++ b/www/js/ui.js @@ -488,7 +488,6 @@ $("#voteskip").click(function() { $("#getplaylist").click(function() { var callback = function(data) { - hidePlayer(); var idx = socket.listeners("errorMsg").indexOf(errCallback); if (idx >= 0) { socket.listeners("errorMsg").splice(idx); @@ -523,7 +522,6 @@ $("#getplaylist").click(function() { $("

").addClass("modal-footer").appendTo(modal); outer.on("hidden.bs.modal", function() { outer.remove(); - unhidePlayer(); }); outer.modal(); }; @@ -862,7 +860,6 @@ applyOpts(); })(); var EMOTELISTMODAL = $("#emotelist"); -EMOTELISTMODAL.on("hidden.bs.modal", unhidePlayer); $("#emotelistbtn").click(function () { EMOTELISTMODAL.modal(); }); diff --git a/www/js/util.js b/www/js/util.js index a2f7760d..ef6a41cd 100644 --- a/www/js/util.js +++ b/www/js/util.js @@ -616,11 +616,6 @@ function rebuildPlaylist() { /* user settings menu */ function showUserOptions() { - hidePlayer(); - $("#useroptions").on("hidden.bs.modal", function () { - unhidePlayer(); - }); - if (CLIENT.rank < 2) { $("a[href='#us-mod']").parent().hide(); } else { @@ -2046,21 +2041,6 @@ function waitUntilDefined(obj, key, fn) { fn(); } -function hidePlayer() { - /* 2015-09-16 - * Originally used to hide the player while a modal was open because of - * certain flash videos that always rendered on top. Seems to no longer - * be an issue. Uncomment this if it is. - if (!PLAYER) return; - - $("#ytapiplayer").hide(); - */ -} - -function unhidePlayer() { - //$("#ytapiplayer").show(); -} - function chatDialog(div) { var parent = $("
").addClass("profile-box") .css({ @@ -2103,6 +2083,44 @@ function errDialog(err) { return div; } +/** + * 2016-12-08 + * I *promise* that one day I will actually split this file into submodules + * -cal + */ +function modalAlert(options) { + if (typeof options !== "object" || options === null) { + throw new Error("modalAlert() called without required parameter"); + } + + var modal = makeModal(); + modal.addClass("cytube-modal-alert"); + modal.removeClass("fade"); + modal.find(".modal-dialog").addClass("modal-dialog-nonfluid"); + + if (options.title) { + $("

").text(options.title).appendTo(modal.find(".modal-header")); + } + + var contentDiv = $("
").addClass("modal-body"); + if (options.htmlContent) { + contentDiv.html(options.htmlContent); + } else if (options.textContent) { + contentDiv.text(options.textContent); + } + + contentDiv.appendTo(modal.find(".modal-content")); + + var footer = $("
").addClass("modal-footer"); + var okButton = $("