diff --git a/package.json b/package.json index 1afee257..091100fd 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "Calvin Montgomery", "name": "CyTube", "description": "Online media synchronizer and chat", - "version": "3.53.1", + "version": "3.52.4", "repository": { "url": "http://github.com/calzoneman/sync" }, diff --git a/src/channel/emotes.js b/src/channel/emotes.js index d3c6364b..5384c857 100644 --- a/src/channel/emotes.js +++ b/src/channel/emotes.js @@ -1,42 +1,96 @@ var ChannelModule = require("./module"); var XSS = require("../xss"); -class EmoteList { - constructor(list) { - if (!list) { - list = []; - } - - this.emotes = new Map(list.map(e => [e.name, e])); +function EmoteList(defaults) { + if (!defaults) { + defaults = []; } - toJSON() { - let list = []; - - for (let [key, value] of this.emotes.entries()) { - list.push(value); - } - - return list; - } - - hasEmote(name) { - return this.emotes.has(name); - } - - setEmote(name, emote) { - this.emotes.set(name, emote); - } - - deleteEmote(name) { - return this.emotes.delete(name); - } - - size() { - return this.emotes.size; - } + this.emotes = defaults.map(validateEmote).filter(function (f) { + return f !== false; + }); } +EmoteList.prototype = { + pack: function () { + return Array.prototype.slice.call(this.emotes); + }, + + importList: function (emotes) { + this.emotes = Array.prototype.slice.call(emotes); + }, + + emoteExists: function (emote){ + if(this.emotes.filter((item)=>{ return item.name === emote.name }).length){ + return true; + } + return false; + }, + + renameEmote: function (emote) { + var found = false; + for (var i = 0; i < this.emotes.length; i++) { + if (this.emotes[i].name === emote.old) { + found = true; + this.emotes[i] = emote; + delete this.emotes[i].old; + break; + } + } + + if(found){ + return true; + } + return false; + }, + + updateEmote: function (emote) { + var found = false; + for (var i = 0; i < this.emotes.length; i++) { + if (this.emotes[i].name === emote.name) { + found = true; + this.emotes[i] = emote; + break; + } + } + + /* If no emote was updated, add a new one */ + if (!found) { + this.emotes.push(emote); + } + }, + + removeEmote: function (emote) { + var found = false; + for (var i = 0; i < this.emotes.length; i++) { + if (this.emotes[i].name === emote.name) { + this.emotes.splice(i, 1); + break; + } + } + }, + + moveEmote: function (from, to) { + if (from < 0 || to < 0 || + from >= this.emotes.length || to >= this.emotes.length) { + return false; + } + + var f = this.emotes[from]; + /* Offset from/to indexes to account for the fact that removing + an element changes the position of one of them. + + I could have just done a swap, but it's already implemented this way + and it works. */ + to = to > from ? to + 1 : to; + from = to > from ? from : from + 1; + + this.emotes.splice(to, 0, f); + this.emotes.splice(from, 1); + return true; + }, +}; + function validateEmote(f) { if (typeof f.name !== "string" || typeof f.image !== "string") { return false; @@ -72,19 +126,21 @@ EmoteModule.prototype = Object.create(ChannelModule.prototype); EmoteModule.prototype.load = function (data) { if ("emotes" in data) { - this.emotes = new EmoteList(data.emotes); + for (var i = 0; i < data.emotes.length; i++) { + this.emotes.updateEmote(data.emotes[i]); + } } this.dirty = false; }; EmoteModule.prototype.save = function (data) { - data.emotes = this.emotes.toJSON(); + data.emotes = this.emotes.pack(); }; EmoteModule.prototype.packInfo = function (data, isAdmin) { if (isAdmin) { - data.emoteCount = this.emotes.size(); + data.emoteCount = this.emotes.emotes.length; } }; @@ -92,12 +148,13 @@ EmoteModule.prototype.onUserPostJoin = function (user) { user.socket.on("renameEmote", this.handleRenameEmote.bind(this, user)); user.socket.on("updateEmote", this.handleUpdateEmote.bind(this, user)); user.socket.on("importEmotes", this.handleImportEmotes.bind(this, user)); + user.socket.on("moveEmote", this.handleMoveEmote.bind(this, user)); user.socket.on("removeEmote", this.handleRemoveEmote.bind(this, user)); this.sendEmotes([user]); }; EmoteModule.prototype.sendEmotes = function (users) { - var f = this.emotes.toJSON(); + var f = this.emotes.pack(); var chan = this.channel; users.forEach(function (u) { u.socket.emit("emoteList", f); @@ -121,7 +178,7 @@ EmoteModule.prototype.handleRenameEmote = function (user, data) { return; } - var e = this.emotes.hasEmote(data.name); + var e = this.emotes.emoteExists(data); var f = validateEmote(data); if (!f || e) { var message = "Unable to rename emote '" + JSON.stringify(data) + "'. " + @@ -140,17 +197,9 @@ EmoteModule.prototype.handleRenameEmote = function (user, data) { return; } - var hadOld = this.emotes.deleteEmote(f.old); - - if (!hadOld) { - return; - } - - this.emotes.setEmote(f.name, { - name: f.name, - source: f.source, - image: f.image - }); + // See comment above + var success = this.emotes.renameEmote(Object.assign({}, f)); + if(!success){ return; } this.dirty = true; @@ -183,11 +232,7 @@ EmoteModule.prototype.handleUpdateEmote = function (user, data) { return; } - this.emotes.setEmote(f.name, { - name: f.name, - source: f.source, - image: f.image - }); + this.emotes.updateEmote(f); this.dirty = true; @@ -209,7 +254,9 @@ EmoteModule.prototype.handleImportEmotes = function (user, data) { return; } - this.emotes = new EmoteList(data.map(validateEmote).filter(f => f)); + this.emotes.importList(data.map(validateEmote).filter(function (f) { + return f !== false; + })); this.dirty = true; @@ -229,7 +276,7 @@ EmoteModule.prototype.handleRemoveEmote = function (user, data) { return; } - this.emotes.deleteEmote(data.name); + this.emotes.removeEmote(data); this.dirty = true; @@ -237,4 +284,22 @@ EmoteModule.prototype.handleRemoveEmote = function (user, data) { this.channel.broadcastAll("removeEmote", data); }; +EmoteModule.prototype.handleMoveEmote = function (user, data) { + if (typeof data !== "object") { + return; + } + + if (!this.channel.modules.permissions.canEditEmotes(user)) { + return; + } + + if (typeof data.to !== "number" || typeof data.from !== "number") { + return; + } + + this.emotes.moveEmote(data.from, data.to); + + this.dirty = true; +}; + module.exports = EmoteModule; diff --git a/templates/channel.pug b/templates/channel.pug index ace50c58..89e38daf 100644 --- a/templates/channel.pug +++ b/templates/channel.pug @@ -189,6 +189,11 @@ html(lang="en") .modal-body .pull-left input.emotelist-search.form-control(type="text", placeholder="Search") + .pull-right + .checkbox + label + input.emotelist-alphabetical(type="checkbox") + | Sort alphabetically .emotelist-paginator-container table.emotelist-table tbody diff --git a/templates/channeloptions.pug b/templates/channeloptions.pug index c350e80c..047f442d 100644 --- a/templates/channeloptions.pug +++ b/templates/channeloptions.pug @@ -197,6 +197,11 @@ mixin emotes form.form-inline .form-group input.emotelist-search.form-control(type="text", placeholder="Search") + .form-group + .checkbox + label + input.emotelist-alphabetical(type="checkbox") + | Sort alphabetically .emotelist-paginator-container table.emotelist-table.table.table-striped.table-condensed thead diff --git a/www/js/data.js b/www/js/data.js index 3a0524be..e71bfeb3 100644 --- a/www/js/data.js +++ b/www/js/data.js @@ -127,6 +127,7 @@ var USEROPTS = { default_quality : getOrDefault("default_quality", "auto"), boop : getOrDefault("boop", "never"), show_shadowchat : getOrDefault("show_shadowchat", false), + emotelist_sort : getOrDefault("emotelist_sort", true), no_emotes : getOrDefault("no_emotes", false), strip_image : getOrDefault("strip_image", false), chat_tab_method : getOrDefault("chat_tab_method", "Cycle options") diff --git a/www/js/ui.js b/www/js/ui.js index 27eb72c8..52ba5b91 100644 --- a/www/js/ui.js +++ b/www/js/ui.js @@ -846,6 +846,12 @@ $("#emotelistbtn").click(function () { EMOTELISTMODAL.modal(); }); +EMOTELISTMODAL.find(".emotelist-alphabetical").change(function () { + USEROPTS.emotelist_sort = this.checked; + setOpt("emotelist_sort", USEROPTS.emotelist_sort); +}); +EMOTELISTMODAL.find(".emotelist-alphabetical").prop("checked", USEROPTS.emotelist_sort); + $("#fullscreenbtn").click(function () { var elem = document.querySelector("#videowrap .embed-responsive"); // this shit is why frontend web development sucks diff --git a/www/js/util.js b/www/js/util.js index 51f05b44..ff47cf1b 100644 --- a/www/js/util.js +++ b/www/js/util.js @@ -2915,8 +2915,8 @@ function formatScriptAccessPrefs() { function EmoteList(selector, emoteClickCallback) { this.elem = $(selector); - this.sortAlphabetical = true; this.initSearch(); + this.initSortOption(); this.table = this.elem.find(".emotelist-table")[0]; this.paginatorContainer = this.elem.find(".emotelist-paginator-container"); this.cols = 5; @@ -2944,6 +2944,18 @@ EmoteList.prototype.initSearch = function () { }); }; +EmoteList.prototype.initSortOption = function () { + this.sortOption = this.elem.find(".emotelist-alphabetical"); + this.sortAlphabetical = false; + var self = this; + + this.sortOption.change(function () { + self.sortAlphabetical = this.checked; + self.handleChange(); + self.loadPage(0); + }); +}; + EmoteList.prototype.handleChange = function () { this.emotes = CHANNEL.emotes.slice(); if (this.sortAlphabetical) { @@ -3027,6 +3039,7 @@ function onEmoteClicked(emote) { } window.EMOTELIST = new EmoteList("#emotelist", onEmoteClicked); +window.EMOTELIST.sortAlphabetical = USEROPTS.emotelist_sort; function CSEmoteList(selector) { EmoteList.call(this, selector); @@ -3179,6 +3192,7 @@ CSEmoteList.prototype.loadPage = function (page) { }; window.CSEMOTELIST = new CSEmoteList("#cs-emotes"); +window.CSEMOTELIST.sortAlphabetical = USEROPTS.emotelist_sort; function showChannelSettings() { $("#channeloptions").modal();