Files
contact.allucanget.biz/static/js/admin.js
zwitschi c1e3ce185f
All checks were successful
CI / test (3.11) (pull_request) Successful in 1m59s
CI / build-image (pull_request) Successful in 2m43s
Remove unused templates for newsletter creation, settings, and submissions; update unsubscribe confirmation link; add tests for email templates API.
2025-11-06 11:10:10 +01:00

987 lines
27 KiB
JavaScript

/**
* Admin JavaScript - Consolidated functionality for all admin pages
* Provides shared utilities, API interactions, and page-specific features
*/
// ==================== UTILITY FUNCTIONS ====================
/**
* Displays a message to the user with auto-hide functionality
* @param {string} text - The message text to display
* @param {string} type - Message type ('success', 'error', 'info', etc.)
*/
function showMessage(text, type) {
let messageDiv = document.getElementById("message");
if (!messageDiv) {
messageDiv = document.createElement("div");
messageDiv.id = "message";
document.body.insertBefore(messageDiv, document.body.firstChild);
}
messageDiv.className = `message ${type}`;
messageDiv.textContent = text;
messageDiv.style.display = "block";
setTimeout(() => (messageDiv.style.display = "none"), 5000);
}
/**
* Escapes HTML characters to prevent XSS
* @param {string} text - Text to escape
* @returns {string} Escaped HTML string
*/
const escapeHtml = (text) => {
const div = document.createElement("div");
div.textContent = text;
return div.innerHTML;
};
/**
* Copies text to clipboard with fallback for older browsers
* @param {string} text - Text to copy
* @returns {Promise<boolean>} Success status
*/
async function copyToClipboard(text) {
if (navigator.clipboard?.writeText) {
try {
await navigator.clipboard.writeText(text);
return true;
} catch (e) {
// Fall through to fallback
}
}
// Fallback method
const tmp = document.createElement("textarea");
tmp.value = text;
tmp.setAttribute("readonly", "");
tmp.style.position = "absolute";
tmp.style.left = "-9999px";
document.body.appendChild(tmp);
tmp.select();
try {
const ok = document.execCommand("copy");
document.body.removeChild(tmp);
return ok;
} catch (err) {
document.body.removeChild(tmp);
return false;
}
}
// ==================== SETTINGS MANAGEMENT ====================
/**
* Loads settings from API and displays them
*/
function loadSettingsForList() {
fetch("/admin/api/settings")
.then((response) => response.json())
.then((data) => {
if (data.status === "ok") {
appSettings = data.settings || {};
displaySettings();
} else {
showMessage(
"Error loading settings: " + (data.message || "Unknown error"),
"error"
);
}
})
.catch((error) => {
console.error("Error:", error);
showMessage("Error loading settings", "error");
});
}
/**
* Fetches embed settings from API
* @returns {Promise<Object>} Settings object or empty object on error
*/
async function fetchEmbedSettings() {
try {
const response = await fetch("/admin/api/settings");
const data = await response.json();
return data.status === "ok" && data.settings ? data.settings : {};
} catch (err) {
console.error("Failed to load embed settings", err);
return {};
}
}
/**
* Saves a setting via API
* @param {string} key - Setting key
* @param {string} inputId - Input element ID
*/
async function saveEmbedSetting(key, inputId) {
const input = document.getElementById(inputId);
if (!input) return showMessage("Input not found", "error");
const value = (input.value || "").toString().trim();
if (!value) return showMessage("Value cannot be empty", "error");
try {
const response = await fetch(
`/admin/api/settings/${encodeURIComponent(key)}`,
{
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ value }),
}
);
const data = await response.json();
if (data.status === "ok") {
showMessage("Setting saved", "success");
// Rebuild textarea values to reflect the new size
if (typeof loadEmbedSettingsAndInit === "function") {
loadEmbedSettingsAndInit();
}
} else {
showMessage("Failed to save setting: " + (data.message || ""), "error");
}
} catch (err) {
console.error("Failed to save setting", err);
showMessage("Failed to save setting", "error");
}
}
// ==================== EMBED MANAGEMENT ====================
/**
* Initializes embed settings and populates form fields
*/
async function loadEmbedSettingsAndInit() {
const origin =
window.location && window.location.origin
? window.location.origin
: "http://your-server-domain";
// Default dimensions
let contactWidth = "600",
contactHeight = "400";
let newsletterWidth = "600",
newsletterHeight = "300";
try {
const settings = await fetchEmbedSettings();
contactWidth = settings.embed_contact_width || contactWidth;
contactHeight = settings.embed_contact_height || contactHeight;
newsletterWidth = settings.embed_newsletter_width || newsletterWidth;
newsletterHeight = settings.embed_newsletter_height || newsletterHeight;
} catch (err) {
console.error("Error loading embed settings", err);
}
// Update input fields
const inputs = {
contactWidth,
contactHeight,
newsletterWidth,
newsletterHeight,
};
Object.entries(inputs).forEach(([id, value]) => {
const element = document.getElementById(id);
if (element) element.value = value;
});
// Update iframe code textareas
const contactTextarea = document.getElementById("iframeCode");
if (contactTextarea) {
contactTextarea.value = `<iframe src="${origin}/embed/contact" width="${contactWidth}" height="${contactHeight}" frameborder="0" allowfullscreen></iframe>`;
}
const newsletterTextarea = document.getElementById("iframeNewsletterCode");
if (newsletterTextarea) {
newsletterTextarea.value = `<iframe src="${origin}/embed/newsletter" width="${newsletterWidth}" height="${newsletterHeight}" frameborder="0" allowfullscreen></iframe>`;
}
const contactPreview = document.getElementById("contactFormPreview");
if (contactPreview) {
contactPreview.innerHTML = `<iframe src="${origin}/embed/contact" width="${contactWidth}" height="${contactHeight}" frameborder="0" allowfullscreen></iframe>`;
}
const newsletterPreview = document.getElementById("newsletterFormPreview");
if (newsletterPreview) {
newsletterPreview.innerHTML = `<iframe src="${origin}/embed/newsletter" width="${newsletterWidth}" height="${newsletterHeight}" frameborder="0" allowfullscreen></iframe>`;
}
}
/**
* Copies contact iframe code to clipboard
*/
function copyIframeCode() {
const textarea = document.getElementById("iframeCode");
if (!textarea) return showMessage("Contact iframe not found", "error");
copyToClipboard(textarea.value).then((ok) =>
showMessage(
ok
? "Contact iframe code copied to clipboard!"
: "Failed to copy contact iframe code",
ok ? "success" : "error"
)
);
}
/**
* Copies newsletter iframe code to clipboard
*/
function copyNewsletterIframeCode() {
const textarea = document.getElementById("iframeNewsletterCode");
if (!textarea) return showMessage("Newsletter iframe not found", "error");
copyToClipboard(textarea.value).then((ok) =>
showMessage(
ok
? "Newsletter iframe code copied to clipboard!"
: "Failed to copy newsletter iframe code",
ok ? "success" : "error"
)
);
}
// ==================== DASHBOARD ====================
/**
* Loads and displays dashboard statistics
*/
async function loadDashboardStats() {
try {
const [contactRes, newsletterRes, settingsRes] = await Promise.all([
fetch("/admin/api/contact?page=1&per_page=1"),
fetch("/admin/api/newsletter?page=1&per_page=1"),
fetch("/admin/api/settings"),
]);
if (contactRes.ok) {
const data = await contactRes.json();
document.getElementById("contact-count").textContent =
data.pagination.total;
}
if (newsletterRes.ok) {
const data = await newsletterRes.json();
document.getElementById("newsletter-count").textContent =
data.pagination.total;
}
if (settingsRes.ok) {
const data = await settingsRes.json();
document.getElementById("settings-count").textContent = Object.keys(
data.settings || {}
).length;
}
} catch (error) {
console.error("Failed to load dashboard stats:", error);
}
}
// ==================== EMAIL TEMPLATES ====================
/**
* Loads email template from settings
*/
function loadEmailTemplate() {
const textarea = document.getElementById("newsletterTemplate");
if (!textarea) return;
fetch("/admin/api/settings")
.then((r) => r.json())
.then((data) => {
if (
data.status === "ok" &&
data.settings &&
data.settings.newsletter_confirmation_template
) {
textarea.value = data.settings.newsletter_confirmation_template;
}
})
.catch((err) => console.error("Failed to load template", err));
}
/**
* Saves email template to settings
*/
function saveEmailTemplate() {
const textarea = document.getElementById("newsletterTemplate");
const message = document.getElementById("message");
if (!textarea || !message) return;
const value = textarea.value || "";
fetch("/admin/api/settings/newsletter_confirmation_template", {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ value }),
})
.then((r) => r.json())
.then((data) => {
const isSuccess = data.status === "ok";
message.className = `message ${isSuccess ? "success" : "error"}`;
message.textContent = isSuccess
? "Template saved"
: `Failed to save template: ${data.message || ""}`;
setTimeout(() => (message.textContent = ""), 4000);
})
.catch((err) => {
console.error("Failed to save template", err);
message.className = "message error";
message.textContent = "Failed to save template";
});
}
// ==================== NEWSLETTER CREATION ====================
let newsletterStats = {};
/**
* Loads newsletter statistics
*/
function loadNewsletterStats() {
fetch("/admin/api/newsletter?page=1&per_page=1")
.then((response) => response.json())
.then((data) => {
if (data.status === "ok") {
const total = data.pagination.total;
document.getElementById("totalSubscribers").textContent = total;
document.getElementById("activeSubscribers").textContent = total; // Assume all active
newsletterStats.totalSubscribers = total;
}
})
.catch((error) => console.error("Error loading subscriber stats:", error))
.finally(() => {
const lastSentEl = document.getElementById("lastSent");
if (lastSentEl) lastSentEl.textContent = "N/A";
});
}
/**
* Generates newsletter preview
*/
function previewNewsletter() {
const subject = document.getElementById("subject").value.trim();
const content = document.getElementById("content").value.trim();
const senderName = document.getElementById("senderName").value.trim();
if (!subject || !content) {
return showMessage(
"Subject and content are required for preview.",
"error"
);
}
const previewContent = document.getElementById("previewContent");
if (previewContent) {
previewContent.innerHTML = `
<h2>${escapeHtml(subject)}</h2>
${
senderName
? `<p><strong>From:</strong> ${escapeHtml(senderName)}</p>`
: ""
}
<div style="margin-top: 20px; line-height: 1.6;">
${content.replace(/\n/g, "<br>")}
</div>
`;
}
const previewSection = document.getElementById("previewSection");
if (previewSection) previewSection.classList.remove("hidden");
showMessage("Newsletter preview generated.", "info");
}
/**
* Saves newsletter as draft
*/
function saveDraft() {
const form = document.getElementById("newsletterForm");
if (!form) return;
const formData = new FormData(form);
const newsletterData = {
subject: formData.get("subject"),
content: formData.get("content"),
sender_name: formData.get("sender_name"),
send_date: formData.get("send_date"),
status: "draft",
};
if (!newsletterData.subject || !newsletterData.content) {
return showMessage("Subject and content are required.", "error");
}
fetch("/admin/api/newsletters", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(newsletterData),
})
.then((response) => response.json())
.then((data) => {
showMessage(
data.status === "ok"
? "Newsletter draft saved successfully!"
: data.message || "Failed to save draft.",
data.status === "ok" ? "success" : "error"
);
})
.catch((error) => {
console.error("Error saving draft:", error);
showMessage("Failed to save draft.", "error");
});
}
/**
* Sends newsletter to subscribers
*/
function sendNewsletter() {
const form = document.getElementById("newsletterForm");
if (!form) return;
const formData = new FormData(form);
const newsletterData = {
subject: formData.get("subject"),
content: formData.get("content"),
sender_name: formData.get("sender_name"),
send_date: formData.get("send_date"),
status: "sent",
};
if (!newsletterData.subject || !newsletterData.content) {
return showMessage("Subject and content are required.", "error");
}
if (
!confirm(
`Are you sure you want to send this newsletter to ${
newsletterStats.totalSubscribers || 0
} subscribers?`
)
) {
return;
}
// Save and send
fetch("/admin/api/newsletters", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(newsletterData),
})
.then((response) => response.json())
.then((data) => {
if (data.status === "ok") {
const newsletterId = data.newsletter_id;
return fetch(`/admin/api/newsletters/${newsletterId}/send`, {
method: "POST",
});
} else {
throw new Error(data.message || "Failed to save newsletter.");
}
})
.then((response) => response.json())
.then((data) => {
showMessage(
data.status === "ok"
? `Newsletter sent successfully to ${data.sent_count} subscribers!`
: data.message || "Failed to send newsletter.",
data.status === "ok" ? "success" : "error"
);
})
.catch((error) => {
console.error("Error sending newsletter:", error);
showMessage("Failed to send newsletter.", "error");
});
}
/**
* Clears newsletter form with confirmation
*/
function clearForm() {
if (
!confirm(
"Are you sure you want to clear the form? All unsaved changes will be lost."
)
) {
return;
}
const form = document.getElementById("newsletterForm");
if (form) form.reset();
const previewSection = document.getElementById("previewSection");
if (previewSection) previewSection.classList.add("hidden");
showMessage("Form cleared.", "info");
}
// ==================== NEWSLETTER SUBSCRIBERS ====================
let currentPage = 1;
let currentFilters = {
email: "",
sort_by: "subscribed_at",
sort_order: "desc",
};
/**
* Applies filters to subscriber list
*/
function applyFilters() {
const emailFilter = document.getElementById("emailFilter");
const sortBy = document.getElementById("sortBy");
const sortOrder = document.getElementById("sortOrder");
if (emailFilter) currentFilters.email = emailFilter.value.trim();
if (sortBy) currentFilters.sort_by = sortBy.value;
if (sortOrder) currentFilters.sort_order = sortOrder.value;
currentPage = 1;
loadSubscribers();
}
/**
* Clears all filters
*/
function clearFilters() {
const emailFilter = document.getElementById("emailFilter");
const sortBy = document.getElementById("sortBy");
const sortOrder = document.getElementById("sortOrder");
if (emailFilter) emailFilter.value = "";
if (sortBy) sortBy.value = "subscribed_at";
if (sortOrder) sortOrder.value = "desc";
currentFilters = { email: "", sort_by: "subscribed_at", sort_order: "desc" };
currentPage = 1;
loadSubscribers();
}
/**
* Loads subscribers with current filters and pagination
*/
function loadSubscribers() {
const loading = document.getElementById("loading");
const table = document.getElementById("subscribersTable");
const pagination = document.getElementById("pagination");
if (loading) loading.style.display = "block";
if (table) table.style.display = "none";
if (pagination) pagination.style.display = "none";
const params = new URLSearchParams({
page: currentPage,
per_page: 50,
sort_by: currentFilters.sort_by,
sort_order: currentFilters.sort_order,
});
if (currentFilters.email) params.append("email", currentFilters.email);
fetch(`/admin/api/newsletter?${params}`)
.then((response) => response.json())
.then((data) => {
if (data.status === "ok") {
displaySubscribers(data.subscribers);
updatePagination(data.pagination);
} else {
showMessage(
"Error loading subscribers: " + (data.message || "Unknown error"),
"error"
);
}
})
.catch((error) => {
console.error("Error:", error);
showMessage("Error loading subscribers", "error");
})
.finally(() => {
if (loading) loading.style.display = "none";
});
}
/**
* Displays subscribers in table
* @param {Array} subscribers - Array of subscriber objects
*/
function displaySubscribers(subscribers) {
const tbody = document.getElementById("subscribersBody");
if (!tbody) return;
tbody.innerHTML = "";
if (subscribers.length === 0) {
tbody.innerHTML =
'<tr><td colspan="3" style="text-align: center; padding: 40px; color: #666;">No subscribers found</td></tr>';
} else {
subscribers.forEach((subscriber) => {
const row = document.createElement("tr");
row.innerHTML = `
<td>${escapeHtml(subscriber.email)}</td>
<td>${new Date(subscriber.subscribed_at).toLocaleDateString()}</td>
<td class="actions">
<button class="btn btn-danger" onclick="unsubscribe('${escapeHtml(
subscriber.email
)}')">Unsubscribe</button>
</td>
`;
tbody.appendChild(row);
});
}
const table = document.getElementById("subscribersTable");
if (table) table.style.display = "table";
}
/**
* Updates pagination controls
* @param {Object} pagination - Pagination data
*/
function updatePagination(pagination) {
const pageInfo = document.getElementById("pageInfo");
const prevBtn = document.getElementById("prevBtn");
const nextBtn = document.getElementById("nextBtn");
if (pageInfo)
pageInfo.textContent = `Page ${pagination.page} of ${pagination.pages} (${pagination.total} total)`;
if (prevBtn) prevBtn.disabled = pagination.page <= 1;
if (nextBtn) nextBtn.disabled = pagination.page >= pagination.pages;
const paginationDiv = document.getElementById("pagination");
if (paginationDiv) paginationDiv.style.display = "flex";
}
/**
* Changes to a different page
* @param {number} page - Page number to navigate to
*/
function changePage(page) {
currentPage = page;
loadSubscribers();
}
/**
* Unsubscribes a user from newsletter
* @param {string} email - Email address to unsubscribe
*/
function unsubscribe(email) {
if (!confirm(`Are you sure you want to unsubscribe ${email}?`)) return;
fetch("/api/newsletter", {
method: "DELETE",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email }),
})
.then((response) => response.json())
.then((data) => {
if (data.status === "ok") {
showMessage("Subscriber unsubscribed successfully", "success");
loadSubscribers();
} else {
showMessage(
"Error unsubscribing: " + (data.message || "Unknown error"),
"error"
);
}
})
.catch((error) => {
console.error("Error:", error);
showMessage("Error unsubscribing subscriber", "error");
});
}
// ==================== CONTACT SUBMISSIONS ====================
let submissionsCurrentPage = 1;
let submissionsSortBy = "created_at";
let submissionsSortOrder = "desc";
/**
* Clears contact submission filters
*/
function clearSubmissionFilters() {
const email = document.getElementById("email");
const dateFrom = document.getElementById("date_from");
const dateTo = document.getElementById("date_to");
const perPage = document.getElementById("per_page");
if (email) email.value = "";
if (dateFrom) dateFrom.value = "";
if (dateTo) dateTo.value = "";
if (perPage) perPage.value = "50";
submissionsCurrentPage = 1;
submissionsSortBy = "created_at";
submissionsSortOrder = "desc";
loadSubmissions();
}
/**
* Loads contact submissions with filters
*/
function loadSubmissions() {
const loading = document.getElementById("loading");
const table = document.getElementById("submissionsTable");
if (loading) loading.style.display = "block";
if (table) table.style.opacity = "0.5";
const perPage = document.getElementById("per_page");
const email = document.getElementById("email");
const dateFrom = document.getElementById("date_from");
const dateTo = document.getElementById("date_to");
const params = new URLSearchParams({
page: submissionsCurrentPage,
per_page: perPage?.value || "50",
sort_by: submissionsSortBy,
sort_order: submissionsSortOrder,
email: email?.value || "",
date_from: dateFrom?.value || "",
date_to: dateTo?.value || "",
});
fetch(`/api/contact?${params}`)
.then((response) => response.json())
.then((data) => {
if (data.status === "ok") {
displaySubmissions(data.submissions);
displaySubmissionPagination(data.pagination);
} else {
showMessage(
"Error loading submissions: " + (data.message || "Unknown error"),
"error"
);
}
})
.catch((error) => {
console.error("Error:", error);
showMessage("Error loading submissions", "error");
})
.finally(() => {
if (loading) loading.style.display = "none";
if (table) table.style.opacity = "1";
});
}
/**
* Displays contact submissions in table
* @param {Array} submissions - Array of submission objects
*/
function displaySubmissions(submissions) {
const tbody = document.getElementById("submissionsBody");
if (!tbody) return;
if (submissions.length === 0) {
tbody.innerHTML =
'<tr><td colspan="7" class="no-data">No submissions found</td></tr>';
return;
}
tbody.innerHTML = submissions
.map(
(submission) => `
<tr>
<td>${submission.id}</td>
<td>${escapeHtml(submission.name)}</td>
<td>${escapeHtml(submission.email)}</td>
<td>${escapeHtml(submission.company || "")}</td>
<td class="submission-details">${escapeHtml(submission.message)}</td>
<td>${new Date(submission.created_at).toLocaleString()}</td>
<td><button class="delete-btn" onclick="deleteSubmission(${
submission.id
})">Delete</button></td>
</tr>
`
)
.join("");
}
/**
* Updates submission pagination controls
* @param {Object} pagination - Pagination data
*/
function displaySubmissionPagination(pagination) {
const paginationDiv = document.getElementById("pagination");
if (!paginationDiv) return;
if (pagination.pages <= 1) {
paginationDiv.innerHTML = "";
return;
}
let buttons = [];
// Previous button
buttons.push(
`<button ${
pagination.page <= 1 ? "disabled" : ""
} onclick="changeSubmissionPage(${pagination.page - 1})">Previous</button>`
);
// Page numbers
const startPage = Math.max(1, pagination.page - 2);
const endPage = Math.min(pagination.pages, pagination.page + 2);
for (let i = startPage; i <= endPage; i++) {
buttons.push(
`<button class="${
i === pagination.page ? "active" : ""
}" onclick="changeSubmissionPage(${i})">${i}</button>`
);
}
// Next button
buttons.push(
`<button ${
pagination.page >= pagination.pages ? "disabled" : ""
} onclick="changeSubmissionPage(${pagination.page + 1})">Next</button>`
);
paginationDiv.innerHTML = buttons.join("");
}
/**
* Changes to a different submission page
* @param {number} page - Page number to navigate to
*/
function changeSubmissionPage(page) {
submissionsCurrentPage = page;
loadSubmissions();
window.scrollTo(0, 0);
}
/**
* Deletes a contact submission
* @param {number} id - Submission ID to delete
*/
function deleteSubmission(id) {
if (!confirm("Are you sure you want to delete this submission?")) return;
fetch(`/api/contact/${id}`, { method: "DELETE" })
.then((response) => response.json())
.then((data) => {
if (data.status === "ok") {
showMessage("Submission deleted successfully", "success");
loadSubmissions();
} else {
showMessage(
"Error deleting submission: " + (data.message || "Unknown error"),
"error"
);
}
})
.catch((error) => {
console.error("Error:", error);
showMessage("Error deleting submission", "error");
});
}
// ==================== INITIALIZATION ====================
// Global admin object for external access
window.admin = {
showMessage,
escapeHtml,
fetchEmbedSettings,
saveEmbedSetting,
copyIframeCode,
copyNewsletterIframeCode,
loadEmbedSettingsAndInit,
loadDashboardStats,
loadEmailTemplate,
saveEmailTemplate,
loadNewsletterStats,
previewNewsletter,
saveDraft,
sendNewsletter,
clearForm,
applyFilters,
clearFilters,
loadSubscribers,
displaySubscribers,
updatePagination,
changePage,
unsubscribe,
clearSubmissionFilters,
loadSubmissions,
displaySubmissions,
displaySubmissionPagination,
changeSubmissionPage,
deleteSubmission,
};
// Auto-initialize based on page content
document.addEventListener("DOMContentLoaded", function () {
// Embed page
if (
document.getElementById("iframeCode") ||
document.getElementById("iframeNewsletterCode")
) {
loadEmbedSettingsAndInit();
}
// Dashboard
if (document.getElementById("contact-count")) {
loadDashboardStats();
}
// Email templates
if (document.getElementById("newsletterTemplate")) {
loadEmailTemplate();
const form = document.getElementById("templateForm");
if (form) {
form.addEventListener("submit", (e) => {
e.preventDefault();
saveEmailTemplate();
});
}
}
// Newsletter creation
if (document.getElementById("newsletterForm")) {
loadNewsletterStats();
}
// Newsletter subscribers
if (document.getElementById("subscribersTable")) {
loadSubscribers();
}
// Contact submissions
if (document.getElementById("submissionsTable")) {
loadSubmissions();
const filterForm = document.getElementById("filterForm");
if (filterForm) {
filterForm.addEventListener("submit", (e) => {
e.preventDefault();
submissionsCurrentPage = 1;
loadSubmissions();
});
}
// Table sorting
document.querySelectorAll("th[data-sort]").forEach((header) => {
header.addEventListener("click", function () {
const sortBy = this.dataset.sort;
if (submissionsSortBy === sortBy) {
submissionsSortOrder =
submissionsSortOrder === "asc" ? "desc" : "asc";
} else {
submissionsSortBy = sortBy;
submissionsSortOrder = "asc";
}
submissionsCurrentPage = 1;
loadSubmissions();
});
});
}
// Settings
if (document.getElementById("settingsList")) {
loadSettingsForList();
}
});