app/js/pages/raffles/new.ts
import "@assets/css/raffles/new.css";
import $ from "jquery";
import swal from "sweetalert";
import { escapeHtml } from "@js/util";
import { Endpoint } from "@js/api";
let ignoredUsersList = [];
function getDateFromUnixTime(timestamp) {
return new Date(timestamp * 1000).toDateString();
}
function initTableControl() {
const rowCount = $("#submissions > tbody > tr").length;
let visibleRowCount = 10;
if (rowCount > visibleRowCount) {
$("#table-control").show();
}
$(`#submissions > tbody > tr:lt(${visibleRowCount})`).show();
$("#show-more").click(() => {
visibleRowCount =
visibleRowCount + 10 <= rowCount ? visibleRowCount + 10 : rowCount;
$(`#submissions > tbody > tr:lt(${visibleRowCount})`).show();
if (visibleRowCount === rowCount) {
$("#show-more").hide();
}
});
}
function showSelectedSubmission($tr) {
const title = escapeHtml($tr.children("td:first").text().trim());
if ($("#submission-selection-error").length > 0) {
$("#submission-selection-error").remove();
}
if ($("#selected-submission").length > 0) {
$("#selected-submission").html(
`<p>Your selection: "${title}"</p>`
);
} else {
$("#submissions").before(
`<div id='selected-submission' class='content has-text-centered'><p>Your selection: "${title}"</p></div>`
);
}
}
/**
* Registers event handlers on the submission selection table
*/
function initTableRows() {
const $rows = $("#submissions > tbody > tr");
$rows.click(function () {
const $clickedRow = $(this);
// Highlights the selected row
$rows.removeClass("is-selected");
$clickedRow.addClass("is-selected");
$("#submission-selection").val(`https://redd.it/${$clickedRow.attr("id")}`); // Set submissionUrl in the form
showSelectedSubmission($clickedRow); // Display the selected submission to the user
/* eslint-disable no-use-before-define */
// Rebuild the ignored users list with the submission author
removeAllIgnoredUsers();
setDefaultIgnoredUsers();
addIgnoredUser($clickedRow.attr("data-submission-author"));
/* eslint-enable */
});
}
function buildSubmissionsTable(submissions) {
$("#loading-container").hide();
const $table = $("#submissions");
if (!submissions) {
const noSubmissionsHtml =
"<div id='no-submission-error' class='content has-text-centered has-text-danger'><p>Either you don't have any submissions yet or all your eligible submissions are already existing raffles.</p></div>";
$table.html(noSubmissionsHtml);
return;
}
// Add headers
const tableHeaders =
"<thead><th>Title</th><th>Subreddit</th><th>Created On</th></thead>";
$table.append(tableHeaders);
// Add row for each submission
submissions.forEach((submission) => {
const $tableBody = $("#submissions > tbody");
$tableBody.append(`
<tr id='${submission.id}' data-submission-author='${submission.author}'>
<td>${escapeHtml(
submission.title
)} <a href='${submission.url}' target='_blank'><i class='fas fa-external-link-alt fa-fw fa-xs'></i></a></td>
<td>${submission.subreddit}</td>
<td>${getDateFromUnixTime(submission.created_at_utc)}</td>
</tr>
`);
});
initTableControl(); // Add collapse/expand control
initTableRows(); // Add row click handlers
}
function showSubmissionDetails(submission) {
const $inputField = $("#submission-url");
const $msg = $("#submission-url-msg");
// Clear any previous messages and styling
$msg.empty().attr("class", "help");
$inputField.attr("class", "input is-success");
const authorHtml = submission.author
? `<a href='https://reddit.com/u/${submission.author}'>/u/${submission.author}</a>`
: "an unknown user";
$msg.html(`
<a href='${submission.url}'>'${escapeHtml(submission.title)}'</a> in /r/${
submission.subreddit
} by ${authorHtml} on ${getDateFromUnixTime(submission.created_at_utc)}
`);
}
function showSubmissionError(url?: string) {
const $inputField = $("#submission-url");
const $msg = $("#submission-url-msg");
// Clear any previous messages and styling
$msg.empty().attr("class", "help");
$inputField.attr("class", "input is-danger");
$msg.addClass("is-danger");
if (url) {
$msg.html(
`There is already an <a href='${url}'>existing raffle</a> for this submission.`
);
} else {
$msg.text("This is not a valid submission URL.");
}
}
function showValidationResults(jqXHR) {
switch (jqXHR.status) {
case 200:
showSubmissionDetails(jqXHR.responseJSON);
break;
case 303:
showSubmissionError(jqXHR.responseJSON.url);
break;
default:
showSubmissionError();
}
}
function validateUrl() {
const $msg = $("#submission-url-msg");
let url = $(this).val() as string;
const URL_REGEX = /[\w-]+(\.[\w-]+)+([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?/;
if (!url || !URL_REGEX.test(url)) {
// @ts-ignore
window._prevUrl = url;
showSubmissionError();
return;
}
const PROTOCOL_REGEX = /^((http|https):\/\/)/;
if (!PROTOCOL_REGEX.test(url)) {
url = `https://${url}`; // Add https if protocol not present
} else {
url = url.replace(/^http:\/\//i, "https://"); // Replace http with https
}
// Skip validation if input value hasn't changed
// @ts-ignore
if (url !== window._prevUrl) {
// @ts-ignore
window._prevUrl = url;
$msg.html(
"<div class='la-ball-clip-rotate la-sm la-reddit'><div></div></div>"
);
$.ajax({
dataType: "json",
data: { url },
url: Endpoint.getSubmission,
complete: showValidationResults,
});
}
}
function getFormDataForSubmit() {
const $form = $("#raffle-form");
const useCombinedKarma = $("#combined-karma-checkbox").prop("checked");
const data = $form.serializeArray();
data.push({ name: "ignoredUsers", value: JSON.stringify(ignoredUsersList) });
return data.filter((obj) => {
const { name } = obj;
if (useCombinedKarma) {
return !["minComment", "minLink"].includes(name);
}
return !["minCombined"].includes(name);
});
}
function submitForm() {
const $submitBtn = $("#submit-btn");
const $submitBtnMsg = $("#submit-btn-msg");
const formData = getFormDataForSubmit();
$submitBtn.addClass("is-loading");
$submitBtn.prop("disabled", true);
$.ajax({
dataType: "json",
type: "POST",
data: formData,
url: Endpoint.postFormSubmit,
complete: (jqXHR) => {
switch (jqXHR.status) {
case 202: // Redirect to status page
case 303: // Redirect to existing raffle
window.location.href = jqXHR.responseJSON.url;
break;
case 422: // Form validation failed
$submitBtnMsg.text(jqXHR.responseJSON.message);
$submitBtn.removeClass("is-loading");
$submitBtn.prop("disabled", false);
break;
default:
$submitBtnMsg.text("Something went wrong behind the scenes.");
$submitBtn.removeClass("is-loading");
$submitBtn.prop("disabled", false);
}
},
});
}
function confirmForm() {
swal({
icon: "warning",
buttons: {
cancel: {
text: "Go Back",
value: false,
visible: true,
},
confirm: {
text: "Submit",
value: true,
visible: true,
},
},
text:
"You won't be able to edit or remove this raffle once it's created. Please make sure you've entered the options correctly!",
}).then((confirmed) => {
if (confirmed) {
submitForm();
}
});
}
function validateAndSubmitForm(event) {
event.preventDefault();
// Logged in user did not select a submission
if (
$("#submission-selection").length > 0 &&
!$("#submission-selection").val()
) {
if (
$("#submission-selection-error").length === 0 &&
$("#no-submission-error").length === 0
) {
$("#submissions").before(
"<div id='submission-selection-error' class='content has-text-centered'><p class='has-text-danger'>Please select a submission.</p></div>"
);
}
$(document).scrollTop($("#submission-selection").offset().top);
return;
}
// Guest user did not enter a valid submission URL
if (
$("#submission-url").length > 0 &&
!$("#submission-url").hasClass("is-success")
) {
$(document).scrollTop($("#submission-url").offset().top);
return;
}
// Validation passed, show confirmation message for form submit
confirmForm();
}
function addIgnoredUser(username) {
// Add user to internal list and add tag
ignoredUsersList.push(username);
$("#ignored-users").append(`
<span class='tag is-medium is-reddit is-rounded'><span name='username'>${username}</span><a class='delete is-small'></a></span>
`);
}
function removeAllIgnoredUsers() {
ignoredUsersList = [];
const ignoredUsersContainer = document.getElementById("ignored-users");
ignoredUsersContainer.innerHTML = null;
}
function removeIgnoredUser() {
// Remove user from internal list and remove tag
const $tag = $(this).parent("span");
const $username = $(this).siblings("span[name='username']");
ignoredUsersList = ignoredUsersList.filter((elem) => elem.toLowerCase() !== $username.text().toLowerCase());
$tag.remove();
}
function setDefaultIgnoredUsers() {
const DEFAULT_USERS = ["AutoModerator"];
DEFAULT_USERS.forEach((user) => {
addIgnoredUser(user);
});
}
function isValidUsername(username) {
const USERNAME_REGEX = /^[\w-]+$/;
return (
username.length >= 3 &&
username.length <= 20 &&
USERNAME_REGEX.test(username) &&
ignoredUsersList.toString().toLowerCase().indexOf(username.toLowerCase()) <
0
);
}
function validateAndAddIgnoredUser() {
const MAX_IGNORED_USERS_COUNT = 100;
const $input = $("#ignored-user-input");
const $msg = $("#ignored-user-msg");
const username = $input.val();
// Clear any previous messages
$msg.empty().attr("class", "help is-danger");
$input.removeClass("is-danger");
if (ignoredUsersList.length >= MAX_IGNORED_USERS_COUNT) {
$input.addClass("is-danger");
$msg.text(
"Too many ignored usernames. Remove some of them before trying to add to the list again."
);
return;
}
if (isValidUsername(username)) {
// Add to ignored users list and reset input
addIgnoredUser(username);
$input.val("");
} else {
// Add helper message if failed validation
$input.addClass("is-danger");
$msg.text(
"This is not a valid Reddit username, or it is already in the list."
);
}
}
// Validate and add ignored user when enter is pressed
function validateOnEnter(event) {
if (event.keyCode === 13) {
event.preventDefault();
validateAndAddIgnoredUser();
}
}
function handleCombinedKarmaCheckEvent() {
const $splitKarmaInputGroup = $("#split-karma-input");
const $combinedKarmaInputGroup = $("#combined-karma-input");
let groupToHide;
let groupToShow;
if (this.checked) {
groupToHide = $splitKarmaInputGroup;
groupToShow = $combinedKarmaInputGroup;
} else {
groupToHide = $combinedKarmaInputGroup;
groupToShow = $splitKarmaInputGroup;
}
groupToHide.hide();
groupToHide.find("input").each(function () {
$(this).val(this.defaultValue);
});
groupToShow.show();
}
$(() => {
if ($("#submissions").length > 0) {
$.ajax({
dataType: "json",
url: Endpoint.getSubmissionsForCurrentUser,
success: buildSubmissionsTable,
});
}
if ($("#submission-url").length > 0) {
$("#submission-url").focusout(validateUrl);
}
$("#combined-karma-checkbox").change(handleCombinedKarmaCheckEvent);
$("#raffle-form").submit(validateAndSubmitForm);
// Ignored User section
setDefaultIgnoredUsers();
$("#ignored-user-btn").click(validateAndAddIgnoredUser);
$("#ignored-users").on("click", "a.delete", removeIgnoredUser);
$("#ignored-user-input").keydown(validateOnEnter);
});