var ThreadSettingsForm = FormView.extend({ el: "#thread_settings", events: { // click: "hide", "click .inner": "stopPropagation", "click .thread_delete": "deleteThread", "click .file_delete": "deleteFile", "click .close_link": "hide", "change [name=color]": "changeColor", "change [name=privacy]": "toggleAllowed", "change [name=sort]": "changeSort", "click #allowed_names [type=checkbox]": "removeAllowed", "keydown [name=allowed_field]": "keydownAllowed", "blur [name=allowed_field]": "updateAllowed", "click [name=file_id]": "toggleFile", "click tr.file": "toggleFileRow", "click #move_files": "moveFiles", }, action: "", method: "put", initialize: function () { this.__super__.initialize.call(this); this.template = this.$(".template").html(); this.allowedTemplate = this.$(".allowedTemplate").html(); this.filesTemplate = this.$(".settingsFilesTemplate").html(); }, populate: function () { var data = this.options.parent.data; var keywords = data.keywords; var keyword = data.keyword; var thread = data.thread; var comments = data.comments; var files = data.files; var settings = thread.settings; var display = thread.display; this.thread = thread; this.files = data.files; this.action = "/api/thread/" + thread.id; this.allowed = (this.thread.allowed || "") .split(" ") .map((s) => s.trim()) .filter((s) => !!s); this.$(".close_link").attr("href", "/details/" + thread.id); this.$(".metadata").html(metadata(thread)); this.$("[name=title]").val(thread.title); this.$("[name=hootbox]").prop("checked", !!thread.settings.hootbox); this.$("[name=shorturls]").prop("checked", !!thread.settings.shorturls); this.$("[name=noupload]").prop("checked", !!thread.settings.noupload); this.$("[name=privacy]").prop("checked", !!thread.privacy); var $color = this.$("[name=color]"); Object.keys(COLORS).forEach((color) => { var option = document.createElement("option"); option.value = color; option.innerHTML = color; $color.append(option); }); $color.val( thread && thread.color ? thread.color : keyword ? keyword.color : "", ); var $sort = this.$("[name=sort]"); FILE_SORTS.forEach((sort) => { var option = document.createElement("option"); option.value = sort.key; option.innerHTML = sort.label; $sort.append(option); }); $sort.val(thread.settings.sort || "name_asc"); this.toggleAllowed(); this.fetchKeywords(); var $files = this.$(".files"); $files.empty(); files .sort((a, b) => cmp(a.filename, b.filename)) .forEach((file) => { var size = hush_size(file.size); var datetime = verbose_date(file.date, true); var date_class = carbon_date(file.date); var link = make_link(file); var t = this.filesTemplate .replace(/{{username}}/g, file.username) .replace(/{{link}}/g, link) .replace(/{{filename}}/g, file.filename) .replace(/{{date_class}}/g, date_class) .replace(/{{date}}/g, datetime[0]) .replace(/{{time}}/g, datetime[1]) .replace(/{{size_class}}/g, size[0]) .replace(/{{size}}/g, size[1]) .replace(/{{id}}/g, file.id); var $t = $(t); $files.append($t); }); $("body").removeClass("loading"); }, fetchKeywords: function (thread) { $.get( "/api/keywords", function (data) { var $keyword = this.$("[name=keyword]"); data.keywords .map((a) => a.keyword) .sort((a, b) => (a < b ? -1 : a === b ? 0 : 1)) .forEach((keyword) => { var option = document.createElement("option"); option.value = keyword; option.innerHTML = keyword; $keyword.append(option); }); $keyword.val(this.thread.keyword); }.bind(this), ); }, loadThreads: function (threads) { // update the dropdown list of threads var $thread_select = this.$("[name=thread]"); if (!threads || !threads.length) { $thread_select.parent().hide(); return; } $thread_select.parent().show(); threads .map((a) => [a.title.toLowerCase(), a]) .sort((a, b) => a[0].localeCompare(b[0])) .forEach((pair) => { const thread = pair[1]; var option = document.createElement("option"); option.value = thread.id; // console.log(thread, get_revision(thread)) option.innerHTML = "[" + thread.id + get_revision(thread) + "] " + sanitize(thread.title); $thread_select.append(option); }); // console.log(threads) }, toggleAllowed: function (e) { var checked = this.$("[name=privacy]").prop("checked"); this.$(".allowed_field_container").toggle(checked); this.$(".allowed_names").toggle(checked); if (!checked) return; this.$("[name=allowed_field]").focus(); this.displayAllowed(); }, displayAllowed: function (e) { var $allowedNames = this.$(".allowed_names").empty(); this.allowed.forEach((username) => { var t = this.allowedTemplate.replace(/{{username}}/g, username); $allowedNames.append(t); }); }, keydownAllowed: function (e) { if (e.keyCode === 13) { // enter e.preventDefault(); e.stopPropagation(); this.updateAllowed(); } }, updateAllowed: function () { var usernames = this.$("[name=allowed_field]") .val() .replace(/,/g, " ") .split(" ") .map((s) => s.trim()) .filter((s) => !!s); this.$("[name=allowed_field]").val(""); usernames = usernames .filter((name) => this.allowed.indexOf(name) === -1) .map((name) => sanitizeHTML(name)); $.ajax({ method: "PUT", url: "/api/checkUsernames", headers: { "csrf-token": $("[name=_csrf]").attr("value") }, data: JSON.stringify({ csrf: csrf(), usernames: usernames }), contentType: "application/json", dataType: "json", success: function (data) { if (!data.usernames || !data.usernames.length) return; this.allowed = this.allowed.concat(data.usernames); this.displayAllowed(); }.bind(this), }); }, removeAllowed: function () { this.allowed = this.$("#allowed_names input[type=checkbox]:checked").map( function () { return $(this).val(); }, ); this.displayAllowed(); }, validate: function () { var errors = []; var title = $("[name=title]").val(); if (!title || !title.length) { errors.push("Please enter a title."); } return errors.length ? errors : null; }, serialize: function () { var data = { title: $("[name=title]").val(), keyword: $("[name=keyword]").val(), color: $("[name=color]").val(), privacy: $("[name=privacy]:checked").val() ? 1 : 0, allowed: this.allowed.join(","), settings: { hootbox: $("[name=hootbox]:checked").val() ? true : false, shorturls: $("[name=shorturls]:checked").val() ? true : false, noupload: $("[name=noupload]:checked").val() ? true : false, sort: $("[name=sort]").val(), }, }; return JSON.stringify(data); }, success: function (data) { console.log(data); window.location.href = "/details/" + this.options.parent.data.thread.id; }, visible: false, show: function () { this.visible = true; app.typing = true; this.populate(); this.$el.addClass("visible"); app.router.pushState( "/details/" + this.options.parent.data.thread.id + "/settings", ); }, hide: function (e) { e && e.preventDefault(); this.visible = false; app.typing = false; this.$el.removeClass("visible"); app.router.pushState("/details/" + this.options.parent.data.thread.id); }, toggle: function () { if (this.visible) this.hide(); else this.show(); }, changeColor: function () { var color_name = this.$("[name=color]").val(); set_background_color(color_name); }, changeSort: function () { var sort_name = this.$("[name=sort]").val(); console.log(">", sort_name); app.view.files.resort(sort_name); }, toggleFile: function (e) { // e.preventDefault() e.stopPropagation(); const $input = $(e.currentTarget); const $tr = $input.closest("tr.file"); const value = e.currentTarget.checked; // $(e.currentTarget).prop('checked', value) // console.log('check', $input, value) this.toggleFileChecked($tr, null, value); }, toggleFileRow: function (e) { // e.preventDefault() e.stopPropagation(); const $tr = $(e.currentTarget); const $input = $tr.find('input[type="checkbox"]'); const value = !$input.prop("checked"); this.toggleFileChecked($tr, $input, value); }, toggleFileChecked: function ($tr, $input, value) { $tr.toggleClass("checked", value); if ($input) $input.prop("checked", value); }, moveFiles: function () { var thread_id = this.$("[name=thread]").val(); // if (!thread_id) return alert("Please choose a thread") var file_ids = toArray( this.el.querySelectorAll("[name=file_id]:checked"), ).map((input) => input.value); console.log("thread:", thread_id); console.log("files:", file_ids); var promises = file_ids.map((file_id) => { return new Promise((resolve, reject) => { $.ajax({ method: "GET", url: "/api/file/" + file_id + "/move/" + thread_id, headers: { "csrf-token": $("[name=_csrf]").attr("value") }, dataType: "json", success: function (data) { console.log("moved", file_id); resolve(data); }, error: function () { console.log("error moving", file_id); reject(); }, }); }); }); Promise.all(promises) .then(() => { window.location.href = "/details/" + thread_id; }) .catch(() => { console.error("whaaaaa"); alert("there was a problem moving the files..."); }); }, deleteThread: function (e) { var data = this.options.parent.data; var id = data.thread.id; var comment_count = (data.comments || []).length; var file_count = (data.files || []).length; var msg = "Are you sure you want to delete this thread?\n\n#" + id + ' "' + sanitizeHTML(data.thread.title) + '"'; msg += " + " + comment_count + " comment" + courtesy_s(comment_count); if (file_count) msg += " + " + file_count + " file" + courtesy_s(file_count); var should_remove = confirm(msg); if (should_remove) { $.ajax({ method: "DELETE", url: "/api/thread/" + id, headers: { "csrf-token": $("[name=_csrf]").attr("value") }, data: JSON.stringify({ csrf: csrf() }), dataType: "json", success: function () { window.location.href = "/"; }, }); } }, deleteFile: function (e) { e.preventDefault(); e.stopPropagation(); var $el = $(e.currentTarget); var $parent = $el.closest(".file"); var file_id = $el.data("id"); if (!file_id) return; var data = this.options.parent.data; var file = data.files.find((f) => f.id === file_id); if (!file) return; var msg = "Are you sure you want to delete this file?\n\n#" + file_id + ' "' + sanitizeHTML(file.filename) + '"'; var should_remove = confirm(msg); if (should_remove) { $.ajax({ method: "DELETE", url: "/api/file/" + file_id, headers: { "csrf-token": $("[name=_csrf]").attr("value") }, data: JSON.stringify({ csrf: csrf() }), dataType: "json", success: function (data) { console.log(data); $parent.remove(); }, }); } }, });