mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-21 18:31:12 -05:00
Add email templates for user notifications and account management
- Created a base email template (base.html) for consistent styling across all emails. - Added moderation approval email template (moderation_approved.html) to notify users of approved submissions. - Added moderation rejection email template (moderation_rejected.html) to inform users of required changes for their submissions. - Created password reset email template (password_reset.html) for users requesting to reset their passwords. - Developed a welcome email template (welcome.html) to greet new users and provide account details and tips for using ThrillWiki.
This commit is contained in:
1
django/staticfiles/unfold/js/alpine.anchor.js
Normal file
1
django/staticfiles/unfold/js/alpine.anchor.js
Normal file
File diff suppressed because one or more lines are too long
5
django/staticfiles/unfold/js/alpine.js
Normal file
5
django/staticfiles/unfold/js/alpine.js
Normal file
File diff suppressed because one or more lines are too long
1
django/staticfiles/unfold/js/alpine.persist.js
Normal file
1
django/staticfiles/unfold/js/alpine.persist.js
Normal file
@@ -0,0 +1 @@
|
||||
(()=>{function d(t){let n=()=>{let r,a;try{a=localStorage}catch(i){console.error(i),console.warn("Alpine: $persist is using temporary storage since localStorage is unavailable.");let e=new Map;a={getItem:e.get.bind(e),setItem:e.set.bind(e)}}return t.interceptor((i,e,l,s,f)=>{let o=r||`_x_${s}`,u=g(o,a)?p(o,a):i;return l(u),t.effect(()=>{let c=e();m(o,c,a),l(c)}),u},i=>{i.as=e=>(r=e,i),i.using=e=>(a=e,i)})};Object.defineProperty(t,"$persist",{get:()=>n()}),t.magic("persist",n),t.persist=(r,{get:a,set:i},e=localStorage)=>{let l=g(r,e)?p(r,e):a();i(l),t.effect(()=>{let s=a();m(r,s,e),i(s)})}}function g(t,n){return n.getItem(t)!==null}function p(t,n){let r=n.getItem(t,n);if(r!==void 0)return JSON.parse(r)}function m(t,n,r){r.setItem(t,JSON.stringify(n))}document.addEventListener("alpine:init",()=>{window.Alpine.plugin(d)});})();
|
||||
306
django/staticfiles/unfold/js/app.js
Normal file
306
django/staticfiles/unfold/js/app.js
Normal file
@@ -0,0 +1,306 @@
|
||||
window.addEventListener("load", (e) => {
|
||||
submitSearch();
|
||||
|
||||
fileInputUpdatePath();
|
||||
|
||||
dateTimeShortcutsOverlay();
|
||||
|
||||
renderCharts();
|
||||
|
||||
filterForm();
|
||||
|
||||
warnWithoutSaving();
|
||||
});
|
||||
|
||||
/*************************************************************
|
||||
* Warn without saving
|
||||
*************************************************************/
|
||||
const warnWithoutSaving = () => {
|
||||
let formChanged = false;
|
||||
const form = document.querySelector("form.warn-unsaved-form");
|
||||
|
||||
const checkFormChanged = () => {
|
||||
const elements = document.querySelectorAll(
|
||||
"form.warn-unsaved-form input, form.warn-unsaved-form select, form.warn-unsaved-form textarea"
|
||||
);
|
||||
|
||||
Array.from(elements).forEach((field) => {
|
||||
field.addEventListener("input", (e) => (formChanged = true));
|
||||
});
|
||||
};
|
||||
|
||||
if (!form) {
|
||||
return;
|
||||
}
|
||||
|
||||
new MutationObserver((mutationsList, observer) => {
|
||||
checkFormChanged();
|
||||
}).observe(form, { attributes: true, childList: true, subtree: true });
|
||||
|
||||
checkFormChanged();
|
||||
|
||||
preventLeaving = (e) => {
|
||||
if (formChanged) {
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
form.addEventListener("submit", (e) => {
|
||||
window.removeEventListener("beforeunload", preventLeaving);
|
||||
});
|
||||
|
||||
window.addEventListener("beforeunload", preventLeaving);
|
||||
};
|
||||
|
||||
/*************************************************************
|
||||
* Filter form
|
||||
*************************************************************/
|
||||
const filterForm = () => {
|
||||
const filterForm = document.getElementById("filter-form");
|
||||
|
||||
if (!filterForm) {
|
||||
return;
|
||||
}
|
||||
|
||||
filterForm.addEventListener("formdata", (event) => {
|
||||
Array.from(event.formData.entries()).forEach(([key, value]) => {
|
||||
if (value === "") event.formData.delete(key);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/*************************************************************
|
||||
* Class watcher
|
||||
*************************************************************/
|
||||
const watchClassChanges = (selector, callback) => {
|
||||
const body = document.querySelector(selector);
|
||||
|
||||
const observer = new MutationObserver((mutationsList) => {
|
||||
for (const mutation of mutationsList) {
|
||||
if (
|
||||
mutation.type === "attributes" &&
|
||||
mutation.attributeName === "class"
|
||||
) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
observer.observe(body, { attributes: true, attributeFilter: ["class"] });
|
||||
};
|
||||
|
||||
/*************************************************************
|
||||
* Calendar & clock
|
||||
*************************************************************/
|
||||
const dateTimeShortcutsOverlay = () => {
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
mutations.forEach((mutationRecord) => {
|
||||
const display = mutationRecord.target.style.display;
|
||||
const overlay = document.getElementById("modal-overlay");
|
||||
|
||||
if (display === "block") {
|
||||
overlay.style.display = "block";
|
||||
} else {
|
||||
overlay.style.display = "none";
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const targets = document.querySelectorAll(".calendarbox, .clockbox");
|
||||
|
||||
Array.from(targets).forEach((target) => {
|
||||
observer.observe(target, {
|
||||
attributes: true,
|
||||
attributeFilter: ["style"],
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/*************************************************************
|
||||
* File upload path
|
||||
*************************************************************/
|
||||
const fileInputUpdatePath = () => {
|
||||
Array.from(document.querySelectorAll("input[type=file]")).forEach((input) => {
|
||||
input.addEventListener("change", (e) => {
|
||||
const parts = e.target.value.split("\\");
|
||||
const placeholder =
|
||||
input.parentNode.parentNode.parentNode.querySelector(
|
||||
"input[type=text]"
|
||||
);
|
||||
placeholder.setAttribute("value", parts[parts.length - 1]);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/*************************************************************
|
||||
* Search form on changelist view
|
||||
*************************************************************/
|
||||
const submitSearch = () => {
|
||||
const searchbar = document.getElementById("searchbar");
|
||||
const searchbarSubmit = document.getElementById("searchbar-submit");
|
||||
|
||||
const getQueryParams = (searchString) => {
|
||||
const queryParams = window.location.search
|
||||
.replace("?", "")
|
||||
.split("&")
|
||||
.map((param) => param.split("="))
|
||||
.reduce((values, [key, value]) => {
|
||||
if (key && key !== "q") {
|
||||
values[key] = value;
|
||||
}
|
||||
|
||||
return values;
|
||||
}, {});
|
||||
|
||||
if (searchString) {
|
||||
queryParams["q"] = searchString;
|
||||
}
|
||||
|
||||
const result = Object.entries(queryParams)
|
||||
.map(([key, value]) => `${key}=${value}`)
|
||||
.join("&");
|
||||
|
||||
return `?${result}`;
|
||||
};
|
||||
|
||||
if (searchbar !== null) {
|
||||
searchbar.addEventListener("keypress", (e) => {
|
||||
if (e.key === "Enter") {
|
||||
window.location = getQueryParams(e.target.value);
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (searchbarSubmit !== null && searchbar !== null) {
|
||||
searchbarSubmit.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
window.location = getQueryParams(searchbar.value);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/*************************************************************
|
||||
* Chart
|
||||
*************************************************************/
|
||||
const DEFAULT_CHART_OPTIONS = {
|
||||
animation: false,
|
||||
barPercentage: 1,
|
||||
base: 0,
|
||||
grouped: false,
|
||||
maxBarThickness: 4,
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
datasets: {
|
||||
bar: {
|
||||
borderRadius: 12,
|
||||
border: {
|
||||
width: 0,
|
||||
},
|
||||
borderSkipped: "middle",
|
||||
},
|
||||
line: {
|
||||
borderWidth: 2,
|
||||
pointBorderWidth: 0,
|
||||
pointStyle: false,
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
align: "end",
|
||||
display: false,
|
||||
position: "top",
|
||||
labels: {
|
||||
boxHeight: 5,
|
||||
boxWidth: 5,
|
||||
color: "#9ca3af",
|
||||
pointStyle: "circle",
|
||||
usePointStyle: true,
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
border: {
|
||||
dash: [5, 5],
|
||||
dashOffset: 2,
|
||||
width: 0,
|
||||
},
|
||||
ticks: {
|
||||
color: "#9ca3af",
|
||||
display: true,
|
||||
},
|
||||
grid: {
|
||||
display: true,
|
||||
tickWidth: 0,
|
||||
},
|
||||
},
|
||||
y: {
|
||||
border: {
|
||||
dash: [5, 5],
|
||||
dashOffset: 5,
|
||||
width: 0,
|
||||
},
|
||||
ticks: {
|
||||
display: false,
|
||||
font: {
|
||||
size: 13,
|
||||
},
|
||||
},
|
||||
grid: {
|
||||
lineWidth: function (context) {
|
||||
if (context.tick.value === 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
},
|
||||
tickWidth: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const renderCharts = () => {
|
||||
let charts = [];
|
||||
|
||||
const changeDarkModeSettings = () => {
|
||||
const hasDarkClass = document
|
||||
.querySelector("html")
|
||||
.classList.contains("dark");
|
||||
|
||||
charts.forEach((chart) => {
|
||||
chart.options.scales.x.grid.color = hasDarkClass ? "#374151" : "#d1d5db";
|
||||
chart.options.scales.y.grid.color = hasDarkClass ? "#374151" : "#d1d5db";
|
||||
chart.update();
|
||||
});
|
||||
};
|
||||
|
||||
Array.from(document.querySelectorAll(".chart")).forEach((chart) => {
|
||||
const ctx = chart.getContext("2d");
|
||||
const data = chart.dataset.value;
|
||||
const type = chart.dataset.type;
|
||||
const options = chart.dataset.options;
|
||||
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
|
||||
charts.push(
|
||||
new Chart(ctx, {
|
||||
type: type || "bar",
|
||||
data: JSON.parse(chart.dataset.value),
|
||||
options: options ? JSON.parse(options) : DEFAULT_CHART_OPTIONS,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
changeDarkModeSettings();
|
||||
|
||||
watchClassChanges("html", () => {
|
||||
changeDarkModeSettings();
|
||||
});
|
||||
};
|
||||
1
django/staticfiles/unfold/js/chart.js
Normal file
1
django/staticfiles/unfold/js/chart.js
Normal file
File diff suppressed because one or more lines are too long
1
django/staticfiles/unfold/js/htmx.js
Normal file
1
django/staticfiles/unfold/js/htmx.js
Normal file
File diff suppressed because one or more lines are too long
15
django/staticfiles/unfold/js/select2.init.js
Normal file
15
django/staticfiles/unfold/js/select2.init.js
Normal file
@@ -0,0 +1,15 @@
|
||||
"use strict";
|
||||
{
|
||||
const $ = django.jQuery;
|
||||
|
||||
$.fn.djangoCustomSelect2 = function () {
|
||||
$.each(this, function (i, element) {
|
||||
$(element).select2();
|
||||
});
|
||||
return this;
|
||||
};
|
||||
|
||||
$(function () {
|
||||
$(".admin-autocomplete").djangoCustomSelect2();
|
||||
});
|
||||
}
|
||||
10
django/staticfiles/unfold/js/simplebar.js
Normal file
10
django/staticfiles/unfold/js/simplebar.js
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user