OIDC Integration: Supports Authentik officially, other solutions currently untested. Social login not implemented
This commit is contained in:
1 parent
dfea7e869b
commit
68bbb53976
28 files changed
+2365
-751
No files matched your search
@@ -1 +1,11 @@
|
||||
(()=>{function le(o,t){var e=Object.keys(o);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(o);t&&(n=n.filter(function(i){return Object.getOwnPropertyDescriptor(o,i).enumerable})),e.push.apply(e,n)}return e}function K(o){for(var t=1;t<arguments.length;t++){var e=arguments[t]!=null?arguments[t]:{};t%2?le(Object(e),!0).forEach(function(n){Fe(o,n,e[n])}):Object.getOwnPropertyDescriptors?Object.defineProperties(o,Object.getOwnPropertyDescriptors(e)):le(Object(e)).forEach(function(n){Object.defineProperty(o,n,Object.getOwnPropertyDescriptor(e,n))})}return o}function Pt(o){return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?Pt=function(t){return typeof t}:Pt=function(t){return t&&typeof Symbol=="function"&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},Pt(o)}function Fe(o,t,e){return t in o?Object.defineProperty(o,t,{value:e,enumerable:!0,configurable:!0,writable:!0}):o[t]=e,o}function U(){return U=Object.assign||function(o){for(var t=1;t<arguments.length;t++){var e=arguments[t];for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(o[n]=e[n])}return o},U.apply(this,arguments)}function Re(o,t){if(o==null)return{};var e={},n=Object.keys(o),i,r;for(r=0;r<n.length;r++)i=n[r],!(t.indexOf(i)>=0)&&(e[i]=o[i]);return e}function ke(o,t){if(o==null)return{};var e=Re(o,t),n,i;if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(o);for(i=0;i<r.length;i++)n=r[i],!(t.indexOf(n)>=0)&&Object.prototype.propertyIsEnumerable.call(o,n)&&(e[n]=o[n])}return e}var He="1.15.2";function $(o){if(typeof window<"u"&&window.navigator)return!!navigator.userAgent.match(o)}var q=$(/(?:Trident.*rv[ :]?11\.|msie|iemobile|Windows Phone)/i),Ot=$(/Edge/i),se=$(/firefox/i),Et=$(/safari/i)&&!$(/chrome/i)&&!$(/android/i),me=$(/iP(ad|od|hone)/i),ve=$(/chrome/i)&&$(/android/i),be={capture:!1,passive:!1};function y(o,t,e){o.addEventListener(t,e,!q&&be)}function b(o,t,e){o.removeEventListener(t,e,!q&&be)}function Xt(o,t){if(t){if(t[0]===">"&&(t=t.substring(1)),o)try{if(o.matches)return o.matches(t);if(o.msMatchesSelector)return o.msMatchesSelector(t);if(o.webkitMatchesSelector)return o.webkitMatchesSelector(t)}catch{return!1}return!1}}function Xe(o){return o.host&&o!==document&&o.host.nodeType?o.host:o.parentNode}function G(o,t,e,n){if(o){e=e||document;do{if(t!=null&&(t[0]===">"?o.parentNode===e&&Xt(o,t):Xt(o,t))||n&&o===e)return o;if(o===e)break}while(o=Xe(o))}return null}var ue=/\s+/g;function F(o,t,e){if(o&&t)if(o.classList)o.classList[e?"add":"remove"](t);else{var n=(" "+o.className+" ").replace(ue," ").replace(" "+t+" "," ");o.className=(n+(e?" "+t:"")).replace(ue," ")}}function h(o,t,e){var n=o&&o.style;if(n){if(e===void 0)return document.defaultView&&document.defaultView.getComputedStyle?e=document.defaultView.getComputedStyle(o,""):o.currentStyle&&(e=o.currentStyle),t===void 0?e:e[t];!(t in n)&&t.indexOf("webkit")===-1&&(t="-webkit-"+t),n[t]=e+(typeof e=="string"?"":"px")}}function ct(o,t){var e="";if(typeof o=="string")e=o;else do{var n=h(o,"transform");n&&n!=="none"&&(e=n+" "+e)}while(!t&&(o=o.parentNode));var i=window.DOMMatrix||window.WebKitCSSMatrix||window.CSSMatrix||window.MSCSSMatrix;return i&&new i(e)}function ye(o,t,e){if(o){var n=o.getElementsByTagName(t),i=0,r=n.length;if(e)for(;i<r;i++)e(n[i],i);return n}return[]}function W(){var o=document.scrollingElement;return o||document.documentElement}function O(o,t,e,n,i){if(!(!o.getBoundingClientRect&&o!==window)){var r,a,l,s,u,d,c;if(o!==window&&o.parentNode&&o!==W()?(r=o.getBoundingClientRect(),a=r.top,l=r.left,s=r.bottom,u=r.right,d=r.height,c=r.width):(a=0,l=0,s=window.innerHeight,u=window.innerWidth,d=window.innerHeight,c=window.innerWidth),(t||e)&&o!==window&&(i=i||o.parentNode,!q))do if(i&&i.getBoundingClientRect&&(h(i,"transform")!=="none"||e&&h(i,"position")!=="static")){var m=i.getBoundingClientRect();a-=m.top+parseInt(h(i,"border-top-width")),l-=m.left+parseInt(h(i,"border-left-width")),s=a+r.height,u=l+r.width;break}while(i=i.parentNode);if(n&&o!==window){var w=ct(i||o),v=w&&w.a,E=w&&w.d;w&&(a/=E,l/=v,c/=v,d/=E,s=a+d,u=l+c)}return{top:a,left:l,bottom:s,right:u,width:c,height:d}}}function fe(o,t,e){for(var n=tt(o,!0),i=O(o)[t];n;){var r=O(n)[e],a=void 0;if(e==="top"||e==="left"?a=i>=r:a=i<=r,!a)return n;if(n===W())break;n=tt(n,!1)}return!1}function dt(o,t,e,n){for(var i=0,r=0,a=o.children;r<a.length;){if(a[r].style.display!=="none"&&a[r]!==p.ghost&&(n||a[r]!==p.dragged)&&G(a[r],e.draggable,o,!1)){if(i===t)return a[r];i++}r++}return null}function oe(o,t){for(var e=o.lastElementChild;e&&(e===p.ghost||h(e,"display")==="none"||t&&!Xt(e,t));)e=e.previousElementSibling;return e||null}function H(o,t){var e=0;if(!o||!o.parentNode)return-1;for(;o=o.previousElementSibling;)o.nodeName.toUpperCase()!=="TEMPLATE"&&o!==p.clone&&(!t||Xt(o,t))&&e++;return e}function ce(o){var t=0,e=0,n=W();if(o)do{var i=ct(o),r=i.a,a=i.d;t+=o.scrollLeft*r,e+=o.scrollTop*a}while(o!==n&&(o=o.parentNode));return[t,e]}function Ye(o,t){for(var e inLine truncated
|
||||
/*! Bundled license information:
|
||||
|
||||
sortablejs/modular/sortable.esm.js:
|
||||
(**!
|
||||
* Sortable 1.15.2
|
||||
* @author RubaXa <trash@rubaxa.org>
|
||||
* @author owenm <owen23355@gmail.com>
|
||||
* @license MIT
|
||||
*)
|
||||
*/
|
||||
+354
-23
@@ -9,11 +9,49 @@ window.addEventListener("load", (e) => {
|
||||
|
||||
warnWithoutSaving();
|
||||
|
||||
inlines();
|
||||
|
||||
tabNavigation();
|
||||
|
||||
scrollSidebarNav();
|
||||
});
|
||||
|
||||
/*************************************************************
|
||||
* Switch theme
|
||||
*************************************************************/
|
||||
function theme(defaultTheme = "auto") {
|
||||
return {
|
||||
adminTheme: Alpine.$persist(defaultTheme).as('adminTheme'),
|
||||
switchTheme(theme) {
|
||||
this.adminTheme = theme;
|
||||
},
|
||||
themeBindings: {
|
||||
['x-bind:class']() {
|
||||
if (this.adminTheme === 'dark' || (this.adminTheme === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
||||
return 'dark';
|
||||
}
|
||||
|
||||
if (this.adminTheme === 'light') {
|
||||
return 'light';
|
||||
}
|
||||
|
||||
return '';
|
||||
},
|
||||
['x-on:keydown.window'](event) {
|
||||
if ((event.metaKey || event.ctrlKey) && event.key === "e") {
|
||||
event.preventDefault();
|
||||
|
||||
if (this.adminTheme == "light") {
|
||||
this.adminTheme = "dark";
|
||||
} else {
|
||||
this.adminTheme = "light";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************
|
||||
* Scroll sidebar to active item
|
||||
*************************************************************/
|
||||
@@ -110,18 +148,36 @@ function tabNavigation() {
|
||||
/*************************************************************
|
||||
* Alpine.sort.js callback after sorting
|
||||
*************************************************************/
|
||||
const sortRecords = (e) => {
|
||||
const orderingField = e.from.dataset.orderingField;
|
||||
function sortRecords(e) {
|
||||
e.from
|
||||
.querySelectorAll(`.form-group.original`)
|
||||
.forEach(function (row, index) {
|
||||
input = row.querySelector(
|
||||
`input[name$=-${e.from.dataset.orderingField}]`
|
||||
);
|
||||
input.value = index;
|
||||
});
|
||||
|
||||
const weightInputs = Array.from(
|
||||
e.from.querySelectorAll(
|
||||
`.has_original input[name$=-${orderingField}], td.field-${orderingField} input[name$=-${orderingField}]`
|
||||
e.from
|
||||
.querySelectorAll(
|
||||
`td.field-${e.from.dataset.orderingField} input[name$=-${e.from.dataset.orderingField}]`
|
||||
)
|
||||
);
|
||||
.forEach(function (input, index) {
|
||||
input.value = index;
|
||||
});
|
||||
}
|
||||
|
||||
weightInputs.forEach((input, index) => {
|
||||
input.value = index;
|
||||
});
|
||||
/*************************************************************
|
||||
* Alpine.sort.js callback before moving records
|
||||
*************************************************************/
|
||||
const moveRecords = (e) => {
|
||||
if (
|
||||
(e.related.classList.contains("template") ||
|
||||
e.related.classList.contains("form-actions")) &&
|
||||
e.willInsertAfter
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/*************************************************************
|
||||
@@ -434,7 +490,7 @@ const watchClassChanges = (selector, callback) => {
|
||||
/*************************************************************
|
||||
* Calendar & clock
|
||||
*************************************************************/
|
||||
const dateTimeShortcutsOverlay = () => {
|
||||
function dateTimeShortcutsOverlay() {
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
for (const mutationRecord of mutations) {
|
||||
const display = mutationRecord.target.style.display;
|
||||
@@ -448,27 +504,32 @@ const dateTimeShortcutsOverlay = () => {
|
||||
}
|
||||
});
|
||||
|
||||
const targets = document.querySelectorAll(".calendarbox, .clockbox");
|
||||
const findCalendars = () => {
|
||||
for (const target of document.querySelectorAll(".calendarbox, .clockbox")) {
|
||||
observer.observe(target, {
|
||||
attributes: true,
|
||||
attributeFilter: ["style"],
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
for (const target of targets) {
|
||||
observer.observe(target, {
|
||||
attributes: true,
|
||||
attributeFilter: ["style"],
|
||||
});
|
||||
}
|
||||
new MutationObserver(findCalendars).observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
});
|
||||
};
|
||||
|
||||
/*************************************************************
|
||||
* File upload path
|
||||
*************************************************************/
|
||||
const fileInputUpdatePath = () => {
|
||||
const checkInputChanged = () => {
|
||||
function fileInputUpdatePath() {
|
||||
function checkInputChanged() {
|
||||
for (const input of document.querySelectorAll("input[type=file]")) {
|
||||
if (input.hasChangeListener) {
|
||||
continue;
|
||||
}
|
||||
|
||||
input.addEventListener("change", (e) => {
|
||||
input.addEventListener("change", function (e) {
|
||||
const parts = e.target.value.split("\\");
|
||||
const placeholder =
|
||||
input.parentNode.parentNode.parentNode.querySelector(
|
||||
@@ -479,9 +540,9 @@ const fileInputUpdatePath = () => {
|
||||
|
||||
input.hasChangeListener = true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
new MutationObserver(() => {
|
||||
new MutationObserver(function () {
|
||||
checkInputChanged();
|
||||
}).observe(document.body, {
|
||||
childList: true,
|
||||
@@ -489,7 +550,7 @@ const fileInputUpdatePath = () => {
|
||||
});
|
||||
|
||||
checkInputChanged();
|
||||
};
|
||||
}
|
||||
|
||||
/*************************************************************
|
||||
* Chart
|
||||
@@ -719,6 +780,276 @@ const renderCharts = () => {
|
||||
});
|
||||
};
|
||||
|
||||
/*************************************************************
|
||||
* Inlines
|
||||
*************************************************************/
|
||||
function inlines() {
|
||||
/**
|
||||
* Replacing: https://github.com/django/django/blob/main/django/contrib/admin/static/admin/js/inlines.js
|
||||
*
|
||||
* Expected HTML structure:
|
||||
*
|
||||
* <div class="formset-wrapper">
|
||||
* ... management form ...
|
||||
* <div class="formset">
|
||||
* <div class="form-group">
|
||||
* <div class="form-row">inputs</div>
|
||||
* <div class="form-nested">nested inlines if exists</div>
|
||||
* </div>
|
||||
*
|
||||
* ... another form group ...
|
||||
*
|
||||
* <div class="form-actions">
|
||||
* <div class="add-row">Add record button</div>
|
||||
* </div>
|
||||
* </div>
|
||||
* </div>
|
||||
*/
|
||||
document.querySelectorAll(".add-row").forEach(function (el) {
|
||||
el.addEventListener("click", addInlineTemplateHandler);
|
||||
});
|
||||
|
||||
document.querySelectorAll(".delete-template").forEach(function (el) {
|
||||
el.addEventListener("click", deleteInlineTemplateHandler);
|
||||
});
|
||||
}
|
||||
|
||||
function deleteInlineTemplateHandler(e) {
|
||||
const formset = e.target.closest(".formset");
|
||||
const options = JSON.parse(formset.dataset.inlineFormset).options;
|
||||
const formsetWrapper = e.target.closest(".formset-wrapper");
|
||||
const minNumForms = formsetWrapper.querySelector(
|
||||
"input[type=hidden][name$='MIN_NUM_FORMS']"
|
||||
);
|
||||
const totalForms = formsetWrapper.querySelector(
|
||||
"input[type=hidden][name$='TOTAL_FORMS']"
|
||||
);
|
||||
const maxNumForms = formsetWrapper.querySelector(
|
||||
"input[type=hidden][name$='MAX_NUM_FORMS']"
|
||||
);
|
||||
const rowToDelete = e.target.closest(".form-group");
|
||||
|
||||
if (parseInt(totalForms.value) == parseInt(minNumForms.value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let reindex = false;
|
||||
formset
|
||||
.querySelectorAll(":scope > .form-group:not(.empty-form)")
|
||||
.forEach((row, index) => {
|
||||
if (rowToDelete.isSameNode(row)) {
|
||||
reindex = true;
|
||||
}
|
||||
|
||||
if (!rowToDelete.isSameNode(row) && reindex) {
|
||||
const content = row.querySelector(":scope > .form-row");
|
||||
|
||||
content.innerHTML = content.innerHTML.replaceAll(
|
||||
`${options.prefix}-${index}`,
|
||||
`${options.prefix}-${index - 1}`
|
||||
);
|
||||
|
||||
// Reindex nested formset
|
||||
const nestedFormset = row.querySelector(":scope .form-nested");
|
||||
if (nestedFormset) {
|
||||
nestedFormset.innerHTML = nestedFormset.innerHTML.replaceAll(
|
||||
`${options.prefix}-${index}`,
|
||||
`${options.prefix}-${index - 1}`
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const rowToDeleteParent = rowToDelete.parentElement;
|
||||
rowToDelete.remove();
|
||||
updateStackedCounter(rowToDeleteParent);
|
||||
|
||||
// Update total forms count
|
||||
totalForms.value = parseInt(
|
||||
formset.querySelectorAll(":scope > .form-group:not(.empty-form)").length,
|
||||
10
|
||||
);
|
||||
|
||||
// Apply delete event
|
||||
formset.querySelectorAll(".delete-template").forEach(function (el) {
|
||||
el.removeEventListener("click", deleteInlineTemplateHandler);
|
||||
el.addEventListener("click", deleteInlineTemplateHandler);
|
||||
});
|
||||
|
||||
// Show add button
|
||||
if (parseInt(totalForms.value, 10) < parseInt(maxNumForms.value, 10)) {
|
||||
formset.querySelector(".add-row").classList.remove("hidden");
|
||||
}
|
||||
|
||||
applyLastClass(formset);
|
||||
checkFormsetEmpty(formset);
|
||||
|
||||
document.dispatchEvent(
|
||||
new CustomEvent("formset:removed", {
|
||||
detail: {
|
||||
formsetName: options.prefix,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function addInlineTemplateHandler(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const formset = e.target.closest(".formset");
|
||||
const formsetWrapper = e.target.closest(".formset-wrapper");
|
||||
const totalForms = formsetWrapper.querySelector(
|
||||
"input[type=hidden][name$='TOTAL_FORMS']"
|
||||
);
|
||||
const maxNumForms = formsetWrapper.querySelector(
|
||||
"input[type=hidden][name$='MAX_NUM_FORMS']"
|
||||
);
|
||||
const template = formset.querySelector(":scope >.empty-form");
|
||||
const index = formset.querySelectorAll(":scope > .form-group").length - 1;
|
||||
const options = JSON.parse(formset.dataset.inlineFormset).options;
|
||||
const row = template.cloneNode(true);
|
||||
|
||||
row.classList.remove("empty-form");
|
||||
|
||||
if (parseInt(totalForms.value, 10) >= parseInt(maxNumForms.value, 10)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Insert new row BEFORE template row which is always last in formset
|
||||
formset.insertBefore(row, template);
|
||||
applyLastClass(formset);
|
||||
checkFormsetEmpty(formset);
|
||||
|
||||
// Stacked inlines are having a counter in title
|
||||
updateStackedCounter(row.parentElement);
|
||||
|
||||
// Replace __prefix__ in RECENTLY ADDED row
|
||||
const formRow = row.querySelector(":scope > .form-row");
|
||||
formRow.innerHTML = formRow.innerHTML.replaceAll(/__prefix__/g, index);
|
||||
|
||||
// Update hidden TOTAL_FORMS input value
|
||||
totalForms.value = parseInt(totalForms.value, 10) + 1;
|
||||
|
||||
// Hide add row button if it is not possible to add more rows
|
||||
if (totalForms.value === maxNumForms.value) {
|
||||
e.target.parentElement.classList.add("hidden");
|
||||
}
|
||||
|
||||
// Call special `formsetGroup:added` event to reinitialize inlines
|
||||
row.dispatchEvent(
|
||||
new CustomEvent("formsetGroup:added", {
|
||||
bubbles: true,
|
||||
detail: {
|
||||
formsetName: options.prefix,
|
||||
row: row,
|
||||
index: index,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
document.addEventListener("formsetGroup:added", function (e) {
|
||||
if (!e.detail.row) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.detail.row.innerHTML = e.detail.row.innerHTML.replaceAll(
|
||||
`${e.detail.formsetName}-__prefix__`,
|
||||
`${e.detail.formsetName}-${e.detail.index}`
|
||||
);
|
||||
|
||||
if (typeof DateTimeShortcuts !== "undefined") {
|
||||
document.querySelectorAll(".datetimeshortcuts").forEach(function (el) {
|
||||
el.remove();
|
||||
});
|
||||
|
||||
DateTimeShortcuts.init();
|
||||
dateTimeShortcutsOverlay();
|
||||
}
|
||||
|
||||
e.detail.row.querySelector(":scope > .form-row").dispatchEvent(
|
||||
new CustomEvent("formset:added", {
|
||||
bubbles: true,
|
||||
detail: { formsetName: e.detail.formsetName },
|
||||
})
|
||||
);
|
||||
|
||||
e.detail.row
|
||||
.querySelectorAll(".nested-formset > .form-group:not(.empty-form)")
|
||||
.forEach(function (formGroup) {
|
||||
formGroup.querySelector(".form-row").dispatchEvent(
|
||||
new CustomEvent("formset:added", {
|
||||
bubbles: true,
|
||||
detail: { formsetName: e.detail.formsetName },
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
e.detail.row.querySelectorAll(".add-row").forEach(function (el) {
|
||||
el.addEventListener("click", addInlineTemplateHandler);
|
||||
});
|
||||
|
||||
e.detail.row.querySelectorAll(".delete-template").forEach(function (el) {
|
||||
el.addEventListener("click", deleteInlineTemplateHandler);
|
||||
});
|
||||
});
|
||||
|
||||
function checkFormsetEmpty(formset) {
|
||||
const formGroups = formset.querySelectorAll(
|
||||
":scope > .form-group:not(.empty-form)"
|
||||
);
|
||||
|
||||
if (formGroups.length === 0) {
|
||||
formset.classList.add("empty");
|
||||
} else {
|
||||
formset.classList.remove("empty");
|
||||
}
|
||||
}
|
||||
|
||||
function applyLastClass(formset) {
|
||||
formset.querySelectorAll(":scope > .form-group").forEach(function (row) {
|
||||
if (row.classList.contains("last")) {
|
||||
row.classList.remove("last");
|
||||
}
|
||||
|
||||
if (
|
||||
row.nextElementSibling.classList.contains("empty-form") ||
|
||||
row.nextElementSibling.classList.contains("form-actions")
|
||||
) {
|
||||
row.classList.add("last");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateStackedCounter(rowsParent) {
|
||||
rowsParent
|
||||
.querySelectorAll(":scope > .form-group:not(.empty-form) > .form-row")
|
||||
.forEach(function (row, index) {
|
||||
const counter = row.querySelector(".stacked-counter");
|
||||
|
||||
if (counter) {
|
||||
counter.textContent = index + 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/*************************************************************
|
||||
* Reinitialize inlines after pagination AJAX request
|
||||
*************************************************************/
|
||||
document.addEventListener("htmx:afterSettle", function (e) {
|
||||
e.target.querySelectorAll(".form-row").forEach(function (formRow) {
|
||||
formRow.dispatchEvent(
|
||||
new CustomEvent("formset:added", {
|
||||
bubbles: true,
|
||||
detail: {
|
||||
formsetName: JSON.parse(e.target.dataset.inlineFormset).options
|
||||
.prefix,
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
function getCurrentTab() {
|
||||
const fragment = window.location.hash?.replace('#', '');
|
||||
|
||||
|
||||
Reference in new issue
Block a user