(function () { "use strict"; var $ = window.MailSharp; var SYNC_MODES = ["NotSynchronized", "NewHeaders", "AllHeaders", "NewMessages", "AllMessages", "Direct"]; var state = { strings: {}, folders: [], foldersLoaded: false, dragFullName: null, foldersDirty: false }; // Sync mode/subscribed/display order are batched - unlike Add/Delete/Move (which hit the server // immediately), those only take effect once "Opslaan" is clicked. Leaving the page any other way // (closing the tab, navigating elsewhere) would otherwise silently drop them with no indication // anything was even pending. function markFoldersDirty() { state.foldersDirty = true; } window.addEventListener("beforeunload", function (e) { if (!state.foldersDirty) return; e.preventDefault(); e.returnValue = ""; }); function reportError(err) { if (err.cancelled) return; window.alert(err.message); } function syncModeLabel(mode) { var t = state.strings; return { NotSynchronized: t.sync_not_synchronized, NewHeaders: t.sync_new_headers, AllHeaders: t.sync_all_headers, NewMessages: t.sync_new_messages, AllMessages: t.sync_all_messages, Direct: t.sync_direct }[mode] || mode; } function formatSize(bytes, estimated) { if (!bytes) return "0 KB"; var kb = bytes / 1024; var text = kb >= 1024 ? (kb / 1024).toFixed(1) + " MB" : kb.toFixed(1) + " KB"; return estimated ? "~" + text : text; } function bootstrap() { $.initHeader().then(function (result) { state.strings = result.langState.strings; document.getElementById("settingsAccountInfo").textContent = result.authState.account.emailAddress; return $.apiFetch("api/settings"); }).then(renderSettingsForm).catch(reportError); } function renderSettingsForm(settingsState) { var s = settingsState.settings; document.getElementById("settingsFriendlyName").value = s.friendlyName; document.getElementById("settingsSignature").value = s.signature; document.getElementById("settingsMessagesPerPage").value = s.messagesPerPage || ""; document.getElementById("settingsContactsPerPage").value = s.contactsPerPage || ""; document.getElementById("settingsAutoCheckInterval").value = s.autoCheckIntervalMinutes; var tzSel = document.getElementById("settingsTimeZone"); tzSel.innerHTML = settingsState.timeZones.map(function (tz) { return '"; }).join(""); var langSel = document.getElementById("settingsLanguage"); langSel.innerHTML = settingsState.languages.map(function (l) { return '"; }).join(""); } document.getElementById("saveSettingsBtn").addEventListener("click", function () { var body = { friendlyName: document.getElementById("settingsFriendlyName").value.trim(), signature: document.getElementById("settingsSignature").value, messagesPerPage: parseInt(document.getElementById("settingsMessagesPerPage").value, 10) || 0, contactsPerPage: parseInt(document.getElementById("settingsContactsPerPage").value, 10) || 0, autoCheckIntervalMinutes: parseInt(document.getElementById("settingsAutoCheckInterval").value, 10) || 0, timeZoneId: document.getElementById("settingsTimeZone").value, language: document.getElementById("settingsLanguage").value }; $.apiFetch("api/settings", { method: "POST", body: body }).then(function () { window.alert(state.strings.settings_saved); window.location.reload(); }).catch(reportError); }); // ---------- tabs ---------- document.getElementById("settingsTabs").addEventListener("click", function (e) { var tab = e.target.closest(".subheader-tab"); if (!tab) return; e.preventDefault(); var name = tab.getAttribute("data-tab"); document.querySelectorAll(".subheader-tab").forEach(function (t) { t.classList.toggle("active", t === tab); }); document.querySelectorAll("[data-tab-panel]").forEach(function (p) { p.style.display = p.getAttribute("data-tab-panel") === name ? "" : "none"; }); if (name === "folders" && !state.foldersLoaded) loadFolders().catch(reportError); }); // ---------- folder management ---------- function loadFolders(opts) { var refresh = !!(opts && opts.refresh); return $.apiFetch("api/settings/folders?refresh=" + refresh).then(function (folders) { state.folders = folders; state.foldersLoaded = true; state.foldersDirty = false; renderFolderTable(); }); } function renderFolderTable() { var t = state.strings; var body = document.getElementById("folderManageBody"); body.innerHTML = state.folders.map(function (f, index) { var syncOptions = SYNC_MODES.map(function (m) { return '"; }).join(""); return ( '' + '' + '" + '" + "" + '" + '' + $.esc(f.displayName) + "" + '' + f.messageCount + "" + '' + formatSize(f.sizeBytes, f.isSizeEstimated) + "" + '" + '" + "" ); }).join(""); var totalMessages = state.folders.reduce(function (sum, f) { return sum + f.messageCount; }, 0); var totalSize = state.folders.reduce(function (sum, f) { return sum + f.sizeBytes; }, 0); var anyEstimated = state.folders.some(function (f) { return f.isSizeEstimated; }); document.getElementById("folderManageFoot").innerHTML = "" + $.esc(t.settings_total) + "" + totalMessages + "" + "" + formatSize(totalSize, anyEstimated) + ""; } // Reads the table's *current on-screen row order* back out (rather than trusting // state.folders, which is only touched on load/reorder) so save always persists exactly what // the user sees, regardless of which of the two reorder mechanisms (arrows or drag) got there. function currentRowOrder() { return [].slice.call(document.querySelectorAll("#folderManageBody tr[data-folder]")).map(function (row) { return row.getAttribute("data-folder"); }); } function reorderState(newFullNameOrder) { var byName = {}; state.folders.forEach(function (f) { byName[f.fullName] = f; }); state.folders = newFullNameOrder.map(function (name) { return byName[name]; }).filter(Boolean); renderFolderTable(); markFoldersDirty(); } document.getElementById("folderManageBody").addEventListener("change", function (e) { if (e.target.matches(".folder-sync-mode, .folder-subscribed")) markFoldersDirty(); }); document.getElementById("folderSelectAll").addEventListener("change", function (e) { document.querySelectorAll(".folder-row-select:not(:disabled)").forEach(function (cb) { cb.checked = e.target.checked; }); }); document.getElementById("folderManageBody").addEventListener("click", function (e) { var btn = e.target.closest(".move-handle button"); if (!btn || btn.disabled) return; var row = btn.closest("tr[data-folder]"); var order = currentRowOrder(); var index = order.indexOf(row.getAttribute("data-folder")); var target = index + parseInt(btn.getAttribute("data-dir"), 10); if (target < 0 || target >= order.length) return; var tmp = order[index]; order[index] = order[target]; order[target] = tmp; reorderState(order); }); document.getElementById("folderManageBody").addEventListener("dragstart", function (e) { var row = e.target.closest("tr[data-folder]"); if (!row) return; state.dragFullName = row.getAttribute("data-folder"); row.classList.add("dragging"); e.dataTransfer.effectAllowed = "move"; }); document.getElementById("folderManageBody").addEventListener("dragend", function (e) { var row = e.target.closest("tr[data-folder]"); if (row) row.classList.remove("dragging"); document.querySelectorAll("#folderManageBody tr.drag-over").forEach(function (r) { r.classList.remove("drag-over"); }); state.dragFullName = null; }); // The middle band of a row means "drop onto this folder" (nest as a subfolder - a real IMAP // RENAME, not just cosmetic); the top/bottom edges mean "drop between rows" (the existing // display-only reorder). Same drag, two outcomes depending on where within the row it lands. function isNestZone(row, clientY) { var rect = row.getBoundingClientRect(); var relative = (clientY - rect.top) / rect.height; return relative > 0.25 && relative < 0.75; } document.getElementById("folderManageBody").addEventListener("dragover", function (e) { var row = e.target.closest("tr[data-folder]"); if (!row || !state.dragFullName) return; e.preventDefault(); e.dataTransfer.dropEffect = "move"; document.querySelectorAll("#folderManageBody tr.drag-over, #folderManageBody tr.nest-target").forEach(function (r) { if (r !== row) { r.classList.remove("drag-over"); r.classList.remove("nest-target"); } }); if (row.getAttribute("data-folder") === state.dragFullName) { row.classList.remove("drag-over"); row.classList.remove("nest-target"); return; } if (isNestZone(row, e.clientY)) { row.classList.add("nest-target"); row.classList.remove("drag-over"); } else { row.classList.add("drag-over"); row.classList.remove("nest-target"); } }); document.getElementById("folderManageBody").addEventListener("drop", function (e) { var row = e.target.closest("tr[data-folder]"); if (!row || !state.dragFullName) return; e.preventDefault(); var wasNest = row.classList.contains("nest-target"); row.classList.remove("drag-over"); row.classList.remove("nest-target"); var draggedName = state.dragFullName; var targetName = row.getAttribute("data-folder"); if (draggedName === targetName) return; if (wasNest) { $.apiFetch("api/settings/folders/move", { method: "POST", body: { folder: draggedName, newParent: targetName } }) .then(loadFolders) .catch(reportError); return; } var order = currentRowOrder().filter(function (name) { return name !== draggedName; }); var targetIndex = order.indexOf(targetName); order.splice(targetIndex, 0, draggedName); reorderState(order); }); document.getElementById("saveFoldersBtn").addEventListener("click", function () { var folders = []; document.querySelectorAll("#folderManageBody tr[data-folder]").forEach(function (row) { folders.push({ fullName: row.getAttribute("data-folder"), syncMode: row.querySelector(".folder-sync-mode").value, subscribed: row.querySelector(".folder-subscribed").checked }); }); $.apiFetch("api/settings/folders", { method: "POST", body: { folders: folders } }).then(function () { state.foldersDirty = false; window.alert(state.strings.settings_saved); }).catch(reportError); }); document.getElementById("folderManageBody").addEventListener("dblclick", function (e) { var cell = e.target.closest(".folder-name-cell"); if (!cell) return; var row = cell.closest("tr[data-folder]"); if (!row || row.getAttribute("data-protected") === "true") return; var fullName = row.getAttribute("data-folder"); var currentName = state.folders.find(function (f) { return f.fullName === fullName; }).displayName; var newName = window.prompt(state.strings.settings_rename_prompt || "Nieuwe naam:", currentName); if (!newName || newName === currentName) return; $.apiFetch("api/settings/folders/rename", { method: "POST", body: { folder: fullName, newName: newName } }) .then(loadFolders) .catch(reportError); }); document.getElementById("addFolderBtn").addEventListener("click", function (e) { e.preventDefault(); var name = window.prompt(state.strings.settings_new_folder_prompt); if (!name) return; $.apiFetch("api/settings/folders/add", { method: "POST", body: { name: name } }) .then(loadFolders) .catch(reportError); }); document.getElementById("deleteFoldersBtn").addEventListener("click", function (e) { e.preventDefault(); var names = []; document.querySelectorAll("#folderManageBody tr[data-folder]").forEach(function (row) { if (row.querySelector(".folder-row-select").checked) names.push(row.getAttribute("data-folder")); }); if (names.length === 0) return; $.apiFetch("api/settings/folders/delete", { method: "POST", body: { folderNames: names } }) .then(function (result) { if (result.failed && result.failed.length > 0) { window.alert(state.strings.settings_delete_non_empty_warning + "\n" + result.failed.join(", ")); } return loadFolders(); }) .catch(reportError); }); document.getElementById("reloadFoldersBtn").addEventListener("click", function (e) { e.preventDefault(); loadFolders({ refresh: true }).catch(reportError); }); bootstrap(); })();