(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();
})();