`;
if (payload.previous_page_params) {
paginationHTML += `
`;
}
if (payload.next_page_params) {
paginationHTML += `
`;
}
paginationHTML += '
';
parent.querySelector("table").insertAdjacentHTML("afterend", paginationHTML);
}
addListener(parent.querySelectorAll(".js-show-history"), "click", showHistoryModal);
}
// Show a modal window overlayed on the page.
function showModal() {
const modal = document.querySelector("#super-settings-modal");
const content = document.querySelector(".super-settings-modal-content");
modal.style.display = "block";
modal.setAttribute("aria-hidden", "false");
modal.activator = document.activeElement;
focusableElements(document).forEach(function(element) {
if (!modal.contains(element)) {
element.dataset.saveTabIndex = element.getAttribute("tabindex");
element.setAttribute("tabindex", -1);
}
});
document.querySelector("body").style.overflow = "hidden";
}
// Hide the modal window overlayed on the page.
function hideModal() {
const modal = document.querySelector("#super-settings-modal");
const content = document.querySelector(".super-settings-modal-content");
modal.style.display = "none";
modal.setAttribute("aria-hidden", "true");
focusableElements(document).forEach(function(element) {
const tabIndex = element.dataset.saveTabIndex;
delete element.dataset.saveTabIndex;
if (tabIndex) {
element.setAttribute("tabindex", tabIndex);
}
});
if (modal.activator) {
modal.activator.focus();
delete modal.activator;
}
content.innerHTML = "";
document.querySelector("body").style.overflow = "visible";
}
// Returns a list of all focusable elements so that they can be set to not take the focus
// when a modal is opened.
function focusableElements(parent) {
return parent.querySelectorAll("a[href], area[href], button, input:not([type=hidden]), select, textarea, iframe, [tabindex], [contentEditable=true]")
}
// Find a setting by key.
function findSetting(id) {
let found = null;
id = "" + id;
activeSettings.forEach(function(setting) {
if ("" + setting.id === id) {
found = setting;
return;
}
});
return found;
}
/*** Event Listeners ***/
// Listener for showing the setting history modal.
function showHistoryModal(event) {
event.preventDefault();
if (!event.target.dataset) {
return;
}
const modal = document.querySelector("#super-settings-modal");
const content = document.querySelector(".super-settings-modal-content");
let key = event.target.dataset.key;
if (!key) {
const row = event.target.closest("tr");
if (row) {
const id = row.dataset.id;
const setting = findSetting(id);
if (setting) {
key = setting.key;
if (!key) {
return;
}
}
}
}
const params = {key: key, limit: 25};
if (event.target.dataset.limit) {
params["limit"] = event.target.dataset.limit;
}
if (event.target.dataset.offset) {
params["offset"] = event.target.dataset.offset;
}
SuperSettingsAPI.fetchHistory(params, function(settingHistory){
renderHistoryTable(content, settingHistory);
showModal();
});
}
// Listener for closing the modal window overlay.
function closeModal(event) {
if (event.target.classList.contains("js-close-modal")) {
event.preventDefault();
hideModal();
}
}
// Listener to just capture events.
function noOp(event) {
event.preventDefault();
}
// Listener for changing the setting value type select element. Different types will have
// different input elements for the setting value.
function changeSettingType(event) {
event.preventDefault();
const row = event.target.closest("tr");
const valueType = event.target.options[event.target.selectedIndex].value;
var setting = {
id: row.dataset.id,
key: row.querySelector(".super-settings-key input").value,
value: getSettingEditValue(row),
value_type: valueType,
description: row.querySelector(".super-settings-description textarea").value,
new_record: row.dataset.newrecord
}
const addedRow = addRowToTable(editSettingRow(setting));
if (addedRow.querySelector(".super-settings-value .js-date-input")) {
addedRow.querySelector(".super-settings-value .js-date-input").focus();
} else {
addedRow.querySelector(".super-settings-value .js-setting-value").focus();
}
}
// Listener for date and time input elements the combine the values into a hidden datetime field.
function changeDateTime(event) {
const parentNode = event.target.closest("span")
const dateValue = parentNode.querySelector(".js-date-input").value;
let timeValue = parentNode.querySelector(".js-time-input").value;
if (timeValue === "") {
timeValue = "00:00:00";
}
const date = new Date(Date.parse(dateValue + "T" + timeValue));
parentNode.querySelector(".js-setting-value").value = date.toISOString();
}
// Listener for the add setting button.
function addSetting(event) {
event.preventDefault();
const row = addRowToTable(newSettingRow());
row.querySelector(".super-settings-key input").focus();
}
// Listener for the edit setting button.
function editSetting(event) {
event.preventDefault();
const id = event.target.closest("tr").dataset.id;
const setting = findSetting(id);
const row = addRowToTable(editSettingRow(setting));
if (row.querySelector(".super-settings-value .js-date-input")) {
row.querySelector(".super-settings-value .js-date-input").focus();
} else {
row.querySelector(".super-settings-value .js-setting-value").focus();
}
}
// Listener for the restore setting button.
function restoreSetting(event) {
event.preventDefault();
const row = event.target.closest("tr");
const id = row.dataset.id;
const setting = findSetting(id);
if (setting) {
const newRow = settingRow(setting);
bindSettingControlEvents(newRow);
row.replaceWith(newRow);
} else {
row.remove();
}
enableSaveButton();
}
// Listener for the remove setting button.
function removeSetting(event) {
event.preventDefault();
const settingRow = event.target.closest("tr");
if (settingRow.dataset["id"]) {
settingRow.querySelector("input.js-setting-deleted").value = "1";
settingRow.dataset.edited = true;
settingRow.dataset.deleted = true;
settingRow.querySelector(".js-remove-setting").style.display = "none";
settingRow.querySelector(".js-restore-setting").style.display = "inline-block";
} else {
settingRow.remove();
}
enableSaveButton();
}
// Update the settings via the API.
function updateSettings(event) {
event.preventDefault();
event.target.disabled = true;
const settingsData = [];
document.querySelectorAll("#settings-table tbody tr[data-edited=true]").forEach(function(row) {
const data = {};
settingsData.push(data);
data.key = row.querySelector(".js-setting-key").value;
if (data.key != row.dataset.key) {
data.key_was = row.dataset.key;
}
const deleted = row.querySelector(".js-setting-deleted");
if (deleted && deleted.value === "1") {
data.deleted = true;
} else {
if (row.querySelector(".js-setting-value")) {
data.value = getSettingEditValue(row);
}
if (row.querySelector(".js-setting-value-type")) {
const valueTypeSelect = row.querySelector(".js-setting-value-type");
data.value_type = valueTypeSelect.options[valueTypeSelect.selectedIndex].value;
}
if (row.querySelector(".super-settings-description textarea")) {
data.description = row.querySelector(".super-settings-description textarea").value;
}
}
});
SuperSettingsAPI.updateSettings({settings: settingsData}, function(results) {
if (results.success) {
fetchActiveSettings();
showFlash("Settings saved", true)
} else {
event.target.disabled = false;
showFlash("Failed to save settings", false)
if (results.errors) {
showValidationErrors(results.errors)
}
}
});
}
// Listener for the filter input field.
function filterListener(event) {
const filter = event.target.value;
filterSettings(filter);
updateFilterURL(filter);
}
// Listener for refresh page button.
function refreshPage(event) {
event.preventDefault();
let url = window.location.href.replace(/\?.*/, "");
const filter = document.querySelector("#super-settings-filter").value;
if (filter !== "") {
url += "?filter=" + escape(filter);
}
window.location = url;
}
// Support integration into single page applications where OAuth2 access tokens are used.
// The access token can be passed either in the access_token query parameter per the
// OAuth2 standard, or in the URL hash. Passing it in the hash will prevent it from ever
// being sent to the backend and is a bit more secure since there's no chance a web server
// will accidentally log it with the request URL.
function storeAccessToken() {
let accessToken = null;
const params = new URLSearchParams(window.location.search);
if (params.get("access_token")) {
accessToken = params.get("access_token");
}
if (window.location.hash.startsWith("#access_token=")) {
accessToken = window.location.hash.replace("#access_token=", "");
}
if (accessToken) {
window.sessionStorage.setItem("super_settings_access_token", accessToken);
const params = new URLSearchParams(window.location.search);
params.delete("access_token");
window.location.hash = null;
window.history.replaceState("", document.title, window.location.pathname + "?" + params.toString());
}
}
// Attach event listener to one or more elements.
function addListener(elements, event, handler) {
if (elements.addEventListener) {
elements = [elements];
}
elements.forEach(function(element) {
if (element) {
element.addEventListener(event, handler);
}
});
}
// Bind event listeners for setting controls on a setting table row.
function bindSettingControlEvents(parent) {
addListener(parent.querySelectorAll(".js-remove-setting"), "click", removeSetting);
addListener(parent.querySelectorAll(".js-edit-setting"), "click", editSetting);
addListener(parent.querySelectorAll(".js-restore-setting"), "click", restoreSetting);
addListener(parent.querySelectorAll(".js-show-history"), "click", showHistoryModal);
addListener(parent.querySelectorAll(".js-no-op"), "click", noOp);
addListener(parent.querySelectorAll(".js-setting-value-type"), "change", changeSettingType);
addListener(parent.querySelectorAll(".js-date-input"), "change", changeDateTime);
addListener(parent.querySelectorAll(".js-time-input"), "change", changeDateTime);
}
// Initialize the table with all the settings plus any changes from a failed form submission.
function renderSettingsTable(settings) {
const tbody = document.querySelector("#settings-table tbody");
tbody.innerHTML = "";
let count = settings.length;
sortSettings(settings).forEach(function(setting) {
const randomId = "setting" + Math.floor((Math.random() * 0xFFFFFFFFFFFFF)).toString(16);
setting.id = (setting.id || randomId);
const row = settingRow(setting);
tbody.appendChild(row);
bindSettingControlEvents(row);
});
document.querySelector(".js-settings-count").textContent = `${count} ${count === 1 ? "Setting" : "Settings"}`;
const filter = document.querySelector("#super-settings-filter");
if (filter) {
filter.dispatchEvent(new Event("input"));
}
}
function sortOrder() {
const selectedSort = document.querySelector(".super-settings-sort-control[data-selected=true]");
const field = selectedSort.dataset.field;
const order = selectedSort.dataset.order;
return {field: field, order: order};
}
// Sort settings by the selected sort option.
function sortSettings(settings) {
const sort = sortOrder();
return settings.sort(function(a, b) {
let aValue = a[sort.field];
let bValue = b[sort.field];
if (sort.field == "updated_at") {
aValue = new Date(Date.parse(aValue));
bValue = new Date(Date.parse(bValue));
}
if (aValue === bValue) {
return 0;
} else if (sort.order === "asc") {
return (aValue < bValue) ? -1 : 1;
} else {
return (aValue > bValue) ? -1 : 1;
}
})
}
function setSortOrder(event) {
event.preventDefault();
const target = event.target.closest(".super-settings-sort-control");
let prevSelection = document.querySelector(".super-settings-sort-control[data-selected=true]");
if (prevSelection == target) {
selectSortElement(prevSelection, false);
target.querySelector(`[data-order=${target.dataset.order}]`).style.display = "none";
target.dataset.order = (target.dataset.order === "asc" ? "desc" : "asc");
target.querySelector(`[data-order=${target.dataset.order}]`).style.display = "inline-block";
} else {
selectSortElement(prevSelection, false);
}
selectSortElement(target, true);
renderSettingsTable(activeSettings);
}
function selectSortElement(element, selected) {
element.dataset.selected = selected;
const svg = element.querySelector(`[data-order=${element.dataset.order}]`).querySelector("svg");
if (selected) {
svg.style.backgroundColor = getComputedStyle(document.querySelector(".super-settings")).getPropertyValue("--primary-color");
svg.style.fill = "white";
} else {
svg.style.backgroundColor = null;
svg.style.fill = null;
}
}
function promptUnsavedChanges(event) {
if (changesCount() > 0) {
return "Are you sure you want to leave?";
} else {
return undefined;
}
}
// Run the supplied function when the document has been marked ready.
function docReady(fn) {
if (document.readyState === "complete" || document.readyState === "interactive") {
setTimeout(fn, 1);
} else {
document.addEventListener("DOMContentLoaded", fn);
}
}
function fetchActiveSettings() {
SuperSettingsAPI.fetchSettings(function(settings_hash) {
const settings = settings_hash["settings"];
activeSettings = settings;
renderSettingsTable(settings);
enableSaveButton();
});
}
let activeSettings = [];
docReady(function() {
storeAccessToken();
addListener(document.querySelector("#super-settings-filter"), "input", filterListener);
addListener(document.querySelector("#super-settings-add-setting"), "click", addSetting);
addListener(document.querySelector("#super-settings-discard-changes"), "click", refreshPage);
addListener(document.querySelector("#super-settings-save-settings"), "click", updateSettings);
addListener(document.querySelector("#super-settings-modal"), "click", closeModal);
addListener(document.querySelectorAll(".super-settings-sort-control"), "click", setSortOrder);
const queryParams = new URLSearchParams(window.location.search);
applyFilter(queryParams.get("filter"));
selectSortElement(document.querySelector(".super-settings-sort-control[data-selected=true]"), true);
fetchActiveSettings();
window.onbeforeunload = promptUnsavedChanges;
})
})();