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
@@ -79,9 +79,7 @@
|
||||
// ".view-related, .change-related, .delete-related"
|
||||
// );
|
||||
|
||||
const siblings = $this
|
||||
.closest(".related-widget-wrapper")
|
||||
.find(".view-related, .change-related, .delete-related");
|
||||
const siblings = $("body").find('#' + $this.closest(".related-widget-wrapper").data("id")).find(".view-related, .change-related, .delete-related");
|
||||
|
||||
if (!siblings.length) {
|
||||
return;
|
||||
|
||||
+320
-460
@@ -15,485 +15,345 @@
|
||||
* Licensed under the New BSD License
|
||||
* See: https://opensource.org/licenses/bsd-license.php
|
||||
*/
|
||||
"use strict";
|
||||
'use strict';
|
||||
{
|
||||
const $ = django.jQuery;
|
||||
$.fn.formset = function (opts) {
|
||||
const options = $.extend({}, $.fn.formset.defaults, opts);
|
||||
const $this = $(this);
|
||||
const $parent = $this.parent();
|
||||
const updateElementIndex = function (el, prefix, ndx) {
|
||||
const id_regex = new RegExp("(" + prefix + "-(\\d+|__prefix__))");
|
||||
const replacement = prefix + "-" + ndx;
|
||||
if ($(el).prop("for")) {
|
||||
$(el).prop("for", $(el).prop("for").replace(id_regex, replacement));
|
||||
}
|
||||
if (el.id) {
|
||||
el.id = el.id.replace(id_regex, replacement);
|
||||
}
|
||||
if (el.name) {
|
||||
// !CHANGED from original
|
||||
// el.name = el.name.replace(id_regex, replacement);
|
||||
el.setAttribute("name", el.name.replace(id_regex, replacement));
|
||||
}
|
||||
};
|
||||
const totalForms = $("#id_" + options.prefix + "-TOTAL_FORMS").prop(
|
||||
"autocomplete",
|
||||
"off"
|
||||
);
|
||||
let nextIndex = parseInt(totalForms.val(), 10);
|
||||
const maxForms = $("#id_" + options.prefix + "-MAX_NUM_FORMS").prop(
|
||||
"autocomplete",
|
||||
"off"
|
||||
);
|
||||
const minForms = $("#id_" + options.prefix + "-MIN_NUM_FORMS").prop(
|
||||
"autocomplete",
|
||||
"off"
|
||||
);
|
||||
let addButton;
|
||||
const $ = django.jQuery;
|
||||
$.fn.formset = function(opts) {
|
||||
const options = $.extend({}, $.fn.formset.defaults, opts);
|
||||
const $this = $(this);
|
||||
const $parent = $this.parent();
|
||||
const updateElementIndex = function(el, prefix, ndx) {
|
||||
const id_regex = new RegExp("(" + prefix + "-(\\d+|__prefix__))");
|
||||
const replacement = prefix + "-" + ndx;
|
||||
if ($(el).prop("for")) {
|
||||
$(el).prop("for", $(el).prop("for").replace(id_regex, replacement));
|
||||
}
|
||||
if (el.id) {
|
||||
el.id = el.id.replace(id_regex, replacement);
|
||||
}
|
||||
if (el.name) {
|
||||
el.name = el.name.replace(id_regex, replacement);
|
||||
}
|
||||
};
|
||||
const totalForms = $("#id_" + options.prefix + "-TOTAL_FORMS").prop("autocomplete", "off");
|
||||
let nextIndex = parseInt(totalForms.val(), 10);
|
||||
const maxForms = $("#id_" + options.prefix + "-MAX_NUM_FORMS").prop("autocomplete", "off");
|
||||
const minForms = $("#id_" + options.prefix + "-MIN_NUM_FORMS").prop("autocomplete", "off");
|
||||
let addButton;
|
||||
|
||||
/**
|
||||
* The "Add another MyModel" button below the inline forms.
|
||||
*/
|
||||
const addInlineAddButton = function () {
|
||||
if (addButton === null) {
|
||||
if ($this.prop("tagName") === "TR") {
|
||||
// If forms are laid out as table rows, insert the
|
||||
// "add" button in a new table row:
|
||||
const numCols = $this.eq(-1).children().length;
|
||||
$parent.append(
|
||||
'<tr class="' +
|
||||
options.addCssClass +
|
||||
'"><td colspan="' +
|
||||
numCols +
|
||||
'"><a href="#">' +
|
||||
options.addText +
|
||||
"</a></tr>"
|
||||
);
|
||||
addButton = $parent.find("tr:last a");
|
||||
/**
|
||||
* The "Add another MyModel" button below the inline forms.
|
||||
*/
|
||||
const addInlineAddButton = function() {
|
||||
if (addButton === null) {
|
||||
if ($this.prop("tagName") === "TR") {
|
||||
// If forms are laid out as table rows, insert the
|
||||
// "add" button in a new table row:
|
||||
const numCols = $this.eq(-1).children().length;
|
||||
$parent.append('<tr class="' + options.addCssClass + '"><td colspan="' + numCols + '"><a role="button" class="addlink" href="#">' + options.addText + "</a></tr>");
|
||||
addButton = $parent.find("tr:last a");
|
||||
} else {
|
||||
// Otherwise, insert it immediately after the last form:
|
||||
$this.filter(":last").after('<div class="' + options.addCssClass + '"><a role="button" class="addlink" href="#">' + options.addText + "</a></div>");
|
||||
addButton = $this.filter(":last").next().find("a");
|
||||
}
|
||||
}
|
||||
addButton.on('click', addInlineClickHandler);
|
||||
};
|
||||
|
||||
const addInlineClickHandler = function(e) {
|
||||
e.preventDefault();
|
||||
const template = $("#" + options.prefix + "-empty");
|
||||
const row = template.clone(true);
|
||||
row.removeClass(options.emptyCssClass)
|
||||
.addClass(options.formCssClass)
|
||||
.attr("id", options.prefix + "-" + nextIndex);
|
||||
addInlineDeleteButton(row);
|
||||
row.find("*").each(function() {
|
||||
updateElementIndex(this, options.prefix, totalForms.val());
|
||||
});
|
||||
// Insert the new form when it has been fully edited.
|
||||
row.insertBefore($(template));
|
||||
// Update number of total forms.
|
||||
$(totalForms).val(parseInt(totalForms.val(), 10) + 1);
|
||||
nextIndex += 1;
|
||||
// Hide the add button if there's a limit and it's been reached.
|
||||
if ((maxForms.val() !== '') && (maxForms.val() - totalForms.val()) <= 0) {
|
||||
addButton.parent().hide();
|
||||
}
|
||||
// Show the remove buttons if there are more than min_num.
|
||||
toggleDeleteButtonVisibility(row.closest('.inline-group'));
|
||||
|
||||
// Pass the new form to the post-add callback, if provided.
|
||||
if (options.added) {
|
||||
options.added(row);
|
||||
}
|
||||
row.get(0).dispatchEvent(new CustomEvent("formset:added", {
|
||||
bubbles: true,
|
||||
detail: {
|
||||
formsetName: options.prefix
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
/**
|
||||
* The "X" button that is part of every unsaved inline.
|
||||
* (When saved, it is replaced with a "Delete" checkbox.)
|
||||
*/
|
||||
const addInlineDeleteButton = function(row) {
|
||||
if (row.is("tr")) {
|
||||
// If the forms are laid out in table rows, insert
|
||||
// the remove button into the last table cell:
|
||||
row.children(":last").append('<div><a role="button" class="' + options.deleteCssClass + '" href="#">' + options.deleteText + "</a></div>");
|
||||
} else if (row.is("ul") || row.is("ol")) {
|
||||
// If they're laid out as an ordered/unordered list,
|
||||
// insert an <li> after the last list item:
|
||||
row.append('<li><a role="button" class="' + options.deleteCssClass + '" href="#">' + options.deleteText + "</a></li>");
|
||||
} else {
|
||||
// Otherwise, just insert the remove button as the
|
||||
// last child element of the form's container:
|
||||
row.children(":first").append('<span><a role="button" class="' + options.deleteCssClass + '" href="#">' + options.deleteText + "</a></span>");
|
||||
}
|
||||
// Add delete handler for each row.
|
||||
row.find("a." + options.deleteCssClass).on('click', inlineDeleteHandler.bind(this));
|
||||
};
|
||||
|
||||
const inlineDeleteHandler = function(e1) {
|
||||
e1.preventDefault();
|
||||
const deleteButton = $(e1.target);
|
||||
const row = deleteButton.closest('.' + options.formCssClass);
|
||||
const inlineGroup = row.closest('.inline-group');
|
||||
// Remove the parent form containing this button,
|
||||
// and also remove the relevant row with non-field errors:
|
||||
const prevRow = row.prev();
|
||||
if (prevRow.length && prevRow.hasClass('row-form-errors')) {
|
||||
prevRow.remove();
|
||||
}
|
||||
row.remove();
|
||||
nextIndex -= 1;
|
||||
// Pass the deleted form to the post-delete callback, if provided.
|
||||
if (options.removed) {
|
||||
options.removed(row);
|
||||
}
|
||||
document.dispatchEvent(new CustomEvent("formset:removed", {
|
||||
detail: {
|
||||
formsetName: options.prefix
|
||||
}
|
||||
}));
|
||||
// Update the TOTAL_FORMS form count.
|
||||
const forms = $("." + options.formCssClass);
|
||||
$("#id_" + options.prefix + "-TOTAL_FORMS").val(forms.length);
|
||||
// Show add button again once below maximum number.
|
||||
if ((maxForms.val() === '') || (maxForms.val() - forms.length) > 0) {
|
||||
addButton.parent().show();
|
||||
}
|
||||
// Hide the remove buttons if at min_num.
|
||||
toggleDeleteButtonVisibility(inlineGroup);
|
||||
// Also, update names and ids for all remaining form controls so
|
||||
// they remain in sequence:
|
||||
let i, formCount;
|
||||
const updateElementCallback = function() {
|
||||
updateElementIndex(this, options.prefix, i);
|
||||
};
|
||||
for (i = 0, formCount = forms.length; i < formCount; i++) {
|
||||
updateElementIndex($(forms).get(i), options.prefix, i);
|
||||
$(forms.get(i)).find("*").each(updateElementCallback);
|
||||
}
|
||||
};
|
||||
|
||||
const toggleDeleteButtonVisibility = function(inlineGroup) {
|
||||
if ((minForms.val() !== '') && (minForms.val() - totalForms.val()) >= 0) {
|
||||
inlineGroup.find('.inline-deletelink').hide();
|
||||
} else {
|
||||
inlineGroup.find('.inline-deletelink').show();
|
||||
}
|
||||
};
|
||||
|
||||
$this.each(function(i) {
|
||||
$(this).not("." + options.emptyCssClass).addClass(options.formCssClass);
|
||||
});
|
||||
|
||||
// Create the delete buttons for all unsaved inlines:
|
||||
$this.filter('.' + options.formCssClass + ':not(.has_original):not(.' + options.emptyCssClass + ')').each(function() {
|
||||
addInlineDeleteButton($(this));
|
||||
});
|
||||
toggleDeleteButtonVisibility($this);
|
||||
|
||||
// Create the add button, initially hidden.
|
||||
addButton = options.addButton;
|
||||
addInlineAddButton();
|
||||
|
||||
// Show the add button if allowed to add more items.
|
||||
// Note that max_num = None translates to a blank string.
|
||||
const showAddButton = maxForms.val() === '' || (maxForms.val() - totalForms.val()) > 0;
|
||||
if ($this.length && showAddButton) {
|
||||
addButton.parent().show();
|
||||
} else {
|
||||
// Otherwise, insert it immediately after the last form:
|
||||
$this
|
||||
.filter(":last")
|
||||
.after(
|
||||
'<div class="' +
|
||||
options.addCssClass +
|
||||
'"><a href="#">' +
|
||||
options.addText +
|
||||
"</a></div>"
|
||||
);
|
||||
addButton = $this.filter(":last").next().find("a");
|
||||
addButton.parent().hide();
|
||||
}
|
||||
}
|
||||
addButton.on("click", addInlineClickHandler);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
const addInlineClickHandler = function (e) {
|
||||
e.preventDefault();
|
||||
const template = $("#" + options.prefix + "-empty");
|
||||
const row = template.clone(true);
|
||||
row
|
||||
.removeClass(options.emptyCssClass)
|
||||
.addClass(options.formCssClass)
|
||||
.attr("id", options.prefix + "-" + nextIndex);
|
||||
|
||||
addInlineDeleteButton(row);
|
||||
row.find("*").each(function () {
|
||||
updateElementIndex(this, options.prefix, totalForms.val());
|
||||
});
|
||||
|
||||
// Insert the new form when it has been fully edited.
|
||||
// !CHANGED from original
|
||||
if ($(template).parent().is("tbody")) {
|
||||
row
|
||||
.wrap('<tbody class="template"></tbody>')
|
||||
.parent()
|
||||
.insertBefore($(template).parent());
|
||||
} else {
|
||||
row.insertBefore($(template));
|
||||
}
|
||||
|
||||
// Update number of total forms.
|
||||
$(totalForms).val(parseInt(totalForms.val(), 10) + 1);
|
||||
nextIndex += 1;
|
||||
// Hide the add button if there's a limit and it's been reached.
|
||||
if (maxForms.val() !== "" && maxForms.val() - totalForms.val() <= 0) {
|
||||
addButton.parent().hide();
|
||||
}
|
||||
// Show the remove buttons if there are more than min_num.
|
||||
toggleDeleteButtonVisibility(row.closest(".inline-group"));
|
||||
|
||||
// Pass the new form to the post-add callback, if provided.
|
||||
if (options.added) {
|
||||
options.added(row);
|
||||
}
|
||||
row.get(0).dispatchEvent(
|
||||
new CustomEvent("formset:added", {
|
||||
bubbles: true,
|
||||
detail: {
|
||||
formsetName: options.prefix,
|
||||
},
|
||||
})
|
||||
);
|
||||
/* Setup plugin defaults */
|
||||
$.fn.formset.defaults = {
|
||||
prefix: "form", // The form prefix for your django formset
|
||||
addText: "add another", // Text for the add link
|
||||
deleteText: "remove", // Text for the delete link
|
||||
addCssClass: "add-row", // CSS class applied to the add link
|
||||
deleteCssClass: "delete-row", // CSS class applied to the delete link
|
||||
emptyCssClass: "empty-row", // CSS class applied to the empty row
|
||||
formCssClass: "dynamic-form", // CSS class applied to each form in a formset
|
||||
added: null, // Function called each time a new form is added
|
||||
removed: null, // Function called each time a form is deleted
|
||||
addButton: null // Existing add button to use
|
||||
};
|
||||
|
||||
/**
|
||||
* The "X" button that is part of every unsaved inline.
|
||||
* (When saved, it is replaced with a "Delete" checkbox.)
|
||||
*/
|
||||
const addInlineDeleteButton = function (row) {
|
||||
if (row.is("tr")) {
|
||||
// If the forms are laid out in table rows, insert
|
||||
// the remove button into the last table cell:
|
||||
row
|
||||
.children(":last")
|
||||
.append(
|
||||
'<div><a class="' +
|
||||
options.deleteCssClass +
|
||||
'" href="#">' +
|
||||
options.deleteText +
|
||||
"</a></div>"
|
||||
);
|
||||
} else if (row.is("ul") || row.is("ol")) {
|
||||
// If they're laid out as an ordered/unordered list,
|
||||
// insert an <li> after the last list item:
|
||||
row.append(
|
||||
'<li><a class="' +
|
||||
options.deleteCssClass +
|
||||
'" href="#">' +
|
||||
options.deleteText +
|
||||
"</a></li>"
|
||||
);
|
||||
} else {
|
||||
// Otherwise, just insert the remove button as the
|
||||
// last child element of the form's container:
|
||||
row
|
||||
.children(":first")
|
||||
.append(
|
||||
'<span><a class="' +
|
||||
options.deleteCssClass +
|
||||
'" href="#">' +
|
||||
options.deleteText +
|
||||
"</a></span>"
|
||||
);
|
||||
}
|
||||
// Add delete handler for each row.
|
||||
row
|
||||
.find("a." + options.deleteCssClass)
|
||||
.on("click", inlineDeleteHandler.bind(this));
|
||||
};
|
||||
|
||||
const inlineDeleteHandler = function (e1) {
|
||||
e1.preventDefault();
|
||||
const deleteButton = $(e1.target);
|
||||
const row = deleteButton.closest("." + options.formCssClass);
|
||||
const inlineGroup = row.closest(".inline-group");
|
||||
// Remove the parent form containing this button,
|
||||
// and also remove the relevant row with non-field errors:
|
||||
const prevRow = row.prev();
|
||||
if (prevRow.length && prevRow.hasClass("row-form-errors")) {
|
||||
prevRow.remove();
|
||||
}
|
||||
// Tabular inlines ---------------------------------------------------------
|
||||
$.fn.tabularFormset = function(selector, options) {
|
||||
const $rows = $(this);
|
||||
|
||||
// !CHANGED from original
|
||||
if (deleteButton.parent().parent().parent().parent().is("tbody")) {
|
||||
row.parent().remove();
|
||||
} else {
|
||||
row.remove();
|
||||
}
|
||||
nextIndex -= 1;
|
||||
// Pass the deleted form to the post-delete callback, if provided.
|
||||
if (options.removed) {
|
||||
options.removed(row);
|
||||
}
|
||||
document.dispatchEvent(
|
||||
new CustomEvent("formset:removed", {
|
||||
detail: {
|
||||
formsetName: options.prefix,
|
||||
},
|
||||
})
|
||||
);
|
||||
// Update the TOTAL_FORMS form count.
|
||||
const forms = $("." + options.formCssClass);
|
||||
$("#id_" + options.prefix + "-TOTAL_FORMS").val(forms.length);
|
||||
// Show add button again once below maximum number.
|
||||
if (maxForms.val() === "" || maxForms.val() - forms.length > 0) {
|
||||
addButton.parent().show();
|
||||
}
|
||||
// Hide the remove buttons if at min_num.
|
||||
toggleDeleteButtonVisibility(inlineGroup);
|
||||
// Also, update names and ids for all remaining form controls so
|
||||
// they remain in sequence:
|
||||
let i, formCount;
|
||||
const updateElementCallback = function () {
|
||||
updateElementIndex(this, options.prefix, i);
|
||||
};
|
||||
for (i = 0, formCount = forms.length; i < formCount; i++) {
|
||||
updateElementIndex($(forms).get(i), options.prefix, i);
|
||||
$(forms.get(i)).find("*").each(updateElementCallback);
|
||||
}
|
||||
};
|
||||
const reinitDateTimeShortCuts = function() {
|
||||
// Reinitialize the calendar and clock widgets by force
|
||||
if (typeof DateTimeShortcuts !== "undefined") {
|
||||
$(".datetimeshortcuts").remove();
|
||||
DateTimeShortcuts.init();
|
||||
}
|
||||
};
|
||||
|
||||
const toggleDeleteButtonVisibility = function (inlineGroup) {
|
||||
if (minForms.val() !== "" && minForms.val() - totalForms.val() >= 0) {
|
||||
inlineGroup.find(".inline-deletelink").hide();
|
||||
} else {
|
||||
inlineGroup.find(".inline-deletelink").show();
|
||||
}
|
||||
};
|
||||
const updateSelectFilter = function() {
|
||||
// If any SelectFilter widgets are a part of the new form,
|
||||
// instantiate a new SelectFilter instance for it.
|
||||
if (typeof SelectFilter !== 'undefined') {
|
||||
$('.selectfilter').each(function(index, value) {
|
||||
SelectFilter.init(value.id, this.dataset.fieldName, false);
|
||||
});
|
||||
$('.selectfilterstacked').each(function(index, value) {
|
||||
SelectFilter.init(value.id, this.dataset.fieldName, true);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// !CHANGED from original. Business logic for tabular inlines is different.
|
||||
if ($this.parent().is("tbody")) {
|
||||
$this
|
||||
.parent()
|
||||
.parent()
|
||||
.find("tr.form-row")
|
||||
.each(function (i) {
|
||||
$(this)
|
||||
.not("." + options.emptyCssClass)
|
||||
.addClass(options.formCssClass);
|
||||
const initPrepopulatedFields = function(row) {
|
||||
row.find('.prepopulated_field').each(function() {
|
||||
const field = $(this),
|
||||
input = field.find('input, select, textarea'),
|
||||
dependency_list = input.data('dependency_list') || [],
|
||||
dependencies = [];
|
||||
$.each(dependency_list, function(i, field_name) {
|
||||
dependencies.push('#' + row.find('.field-' + field_name).find('input, select, textarea').attr('id'));
|
||||
});
|
||||
if (dependencies.length) {
|
||||
input.prepopulate(dependencies, input.attr('maxlength'));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$rows.formset({
|
||||
prefix: options.prefix,
|
||||
addText: options.addText,
|
||||
formCssClass: "dynamic-" + options.prefix,
|
||||
deleteCssClass: "inline-deletelink",
|
||||
deleteText: options.deleteText,
|
||||
emptyCssClass: "empty-form",
|
||||
added: function(row) {
|
||||
initPrepopulatedFields(row);
|
||||
reinitDateTimeShortCuts();
|
||||
updateSelectFilter();
|
||||
},
|
||||
addButton: options.addButton
|
||||
});
|
||||
} else {
|
||||
$this.each(function (i) {
|
||||
$(this)
|
||||
.not("." + options.emptyCssClass)
|
||||
.addClass(options.formCssClass);
|
||||
});
|
||||
}
|
||||
|
||||
// Create the delete buttons for all unsaved inlines:
|
||||
// !CHANGED from original, added parent() and used find() instead of filter()
|
||||
$this
|
||||
.parent()
|
||||
.parent()
|
||||
.find(
|
||||
"." +
|
||||
options.formCssClass +
|
||||
":not(.has_original):not(." +
|
||||
options.emptyCssClass +
|
||||
")"
|
||||
)
|
||||
.each(function () {
|
||||
addInlineDeleteButton($(this));
|
||||
});
|
||||
toggleDeleteButtonVisibility($this);
|
||||
|
||||
// Create the add button, initially hidden.
|
||||
addButton = options.addButton;
|
||||
addInlineAddButton();
|
||||
|
||||
// Show the add button if allowed to add more items.
|
||||
// Note that max_num = None translates to a blank string.
|
||||
const showAddButton =
|
||||
maxForms.val() === "" || maxForms.val() - totalForms.val() > 0;
|
||||
if ($this.length && showAddButton) {
|
||||
addButton.parent().show();
|
||||
} else {
|
||||
addButton.parent().hide();
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/* Setup plugin defaults */
|
||||
$.fn.formset.defaults = {
|
||||
prefix: "form", // The form prefix for your django formset
|
||||
addText: "add another", // Text for the add link
|
||||
deleteText: "remove", // Text for the delete link
|
||||
addCssClass: "add-row", // CSS class applied to the add link
|
||||
deleteCssClass: "delete-row", // CSS class applied to the delete link
|
||||
emptyCssClass: "empty-row", // CSS class applied to the empty row
|
||||
formCssClass: "dynamic-form", // CSS class applied to each form in a formset
|
||||
added: null, // Function called each time a new form is added
|
||||
removed: null, // Function called each time a form is deleted
|
||||
addButton: null, // Existing add button to use
|
||||
};
|
||||
|
||||
// Tabular inlines ---------------------------------------------------------
|
||||
$.fn.tabularFormset = function (selector, options, callback = null) {
|
||||
const $rows = $(this);
|
||||
|
||||
const reinitDateTimeShortCuts = function () {
|
||||
// Reinitialize the calendar and clock widgets by force
|
||||
if (typeof DateTimeShortcuts !== "undefined") {
|
||||
$(".datetimeshortcuts").remove();
|
||||
DateTimeShortcuts.init();
|
||||
}
|
||||
return $rows;
|
||||
};
|
||||
|
||||
const updateSelectFilter = function () {
|
||||
// If any SelectFilter widgets are a part of the new form,
|
||||
// instantiate a new SelectFilter instance for it.
|
||||
if (typeof SelectFilter !== "undefined") {
|
||||
$(".selectfilter").each(function (index, value) {
|
||||
SelectFilter.init(value.id, this.dataset.fieldName, false);
|
||||
// Stacked inlines ---------------------------------------------------------
|
||||
$.fn.stackedFormset = function(selector, options) {
|
||||
const $rows = $(this);
|
||||
const updateInlineLabel = function(row) {
|
||||
$(selector).find(".inline_label").each(function(i) {
|
||||
const count = i + 1;
|
||||
$(this).html($(this).html().replace(/(#\d+)/g, "#" + count));
|
||||
});
|
||||
};
|
||||
|
||||
const reinitDateTimeShortCuts = function() {
|
||||
// Reinitialize the calendar and clock widgets by force, yuck.
|
||||
if (typeof DateTimeShortcuts !== "undefined") {
|
||||
$(".datetimeshortcuts").remove();
|
||||
DateTimeShortcuts.init();
|
||||
}
|
||||
};
|
||||
|
||||
const updateSelectFilter = function() {
|
||||
// If any SelectFilter widgets were added, instantiate a new instance.
|
||||
if (typeof SelectFilter !== "undefined") {
|
||||
$(".selectfilter").each(function(index, value) {
|
||||
SelectFilter.init(value.id, this.dataset.fieldName, false);
|
||||
});
|
||||
$(".selectfilterstacked").each(function(index, value) {
|
||||
SelectFilter.init(value.id, this.dataset.fieldName, true);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const initPrepopulatedFields = function(row) {
|
||||
row.find('.prepopulated_field').each(function() {
|
||||
const field = $(this),
|
||||
input = field.find('input, select, textarea'),
|
||||
dependency_list = input.data('dependency_list') || [],
|
||||
dependencies = [];
|
||||
$.each(dependency_list, function(i, field_name) {
|
||||
// Dependency in a fieldset.
|
||||
let field_element = row.find('.form-row .field-' + field_name);
|
||||
// Dependency without a fieldset.
|
||||
if (!field_element.length) {
|
||||
field_element = row.find('.form-row.field-' + field_name);
|
||||
}
|
||||
dependencies.push('#' + field_element.find('input, select, textarea').attr('id'));
|
||||
});
|
||||
if (dependencies.length) {
|
||||
input.prepopulate(dependencies, input.attr('maxlength'));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$rows.formset({
|
||||
prefix: options.prefix,
|
||||
addText: options.addText,
|
||||
formCssClass: "dynamic-" + options.prefix,
|
||||
deleteCssClass: "inline-deletelink",
|
||||
deleteText: options.deleteText,
|
||||
emptyCssClass: "empty-form",
|
||||
removed: updateInlineLabel,
|
||||
added: function(row) {
|
||||
initPrepopulatedFields(row);
|
||||
reinitDateTimeShortCuts();
|
||||
updateSelectFilter();
|
||||
updateInlineLabel(row);
|
||||
},
|
||||
addButton: options.addButton
|
||||
});
|
||||
$(".selectfilterstacked").each(function (index, value) {
|
||||
SelectFilter.init(value.id, this.dataset.fieldName, true);
|
||||
});
|
||||
}
|
||||
|
||||
return $rows;
|
||||
};
|
||||
|
||||
const initPrepopulatedFields = function (row) {
|
||||
row.find(".prepopulated_field").each(function () {
|
||||
const field = $(this),
|
||||
input = field.find("input, select, textarea"),
|
||||
dependency_list = input.data("dependency_list") || [],
|
||||
dependencies = [];
|
||||
$.each(dependency_list, function (i, field_name) {
|
||||
dependencies.push(
|
||||
"#" +
|
||||
row
|
||||
.find(".field-" + field_name)
|
||||
.find("input, select, textarea")
|
||||
.attr("id")
|
||||
);
|
||||
$(document).ready(function() {
|
||||
$(".js-inline-admin-formset").each(function() {
|
||||
const data = $(this).data(),
|
||||
inlineOptions = data.inlineFormset;
|
||||
let selector;
|
||||
switch(data.inlineType) {
|
||||
case "stacked":
|
||||
selector = inlineOptions.name + "-group .inline-related";
|
||||
$(selector).stackedFormset(selector, inlineOptions.options);
|
||||
break;
|
||||
case "tabular":
|
||||
selector = inlineOptions.name + "-group .tabular.inline-related tbody:first > tr.form-row";
|
||||
$(selector).tabularFormset(selector, inlineOptions.options);
|
||||
break;
|
||||
}
|
||||
});
|
||||
if (dependencies.length) {
|
||||
input.prepopulate(dependencies, input.attr("maxlength"));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$rows.formset({
|
||||
prefix: options.prefix,
|
||||
addText: options.addText,
|
||||
formCssClass: "dynamic-" + options.prefix,
|
||||
deleteCssClass: "inline-deletelink",
|
||||
deleteText: options.deleteText,
|
||||
emptyCssClass: "empty-form",
|
||||
added: function (row) {
|
||||
initPrepopulatedFields(row);
|
||||
reinitDateTimeShortCuts();
|
||||
updateSelectFilter();
|
||||
},
|
||||
addButton: options.addButton,
|
||||
});
|
||||
|
||||
if (typeof callback === "function") {
|
||||
callback();
|
||||
}
|
||||
|
||||
return $rows;
|
||||
};
|
||||
|
||||
// Stacked inlines ---------------------------------------------------------
|
||||
$.fn.stackedFormset = function (selector, options, callback = null) {
|
||||
const $rows = $(this);
|
||||
const updateInlineLabel = function (row) {
|
||||
$(selector)
|
||||
.find(".inline_label")
|
||||
.each(function (i) {
|
||||
const count = i + 1;
|
||||
$(this).html(
|
||||
$(this)
|
||||
.html()
|
||||
.replace(/(#\d+)/g, "#" + count)
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const reinitDateTimeShortCuts = function () {
|
||||
// Reinitialize the calendar and clock widgets by force, yuck.
|
||||
if (typeof DateTimeShortcuts !== "undefined") {
|
||||
$(".datetimeshortcuts").remove();
|
||||
DateTimeShortcuts.init();
|
||||
}
|
||||
};
|
||||
|
||||
const updateSelectFilter = function () {
|
||||
// If any SelectFilter widgets were added, instantiate a new instance.
|
||||
if (typeof SelectFilter !== "undefined") {
|
||||
$(".selectfilter").each(function (index, value) {
|
||||
SelectFilter.init(value.id, this.dataset.fieldName, false);
|
||||
});
|
||||
$(".selectfilterstacked").each(function (index, value) {
|
||||
SelectFilter.init(value.id, this.dataset.fieldName, true);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const initPrepopulatedFields = function (row) {
|
||||
row.find(".prepopulated_field").each(function () {
|
||||
const field = $(this),
|
||||
input = field.find("input, select, textarea"),
|
||||
dependency_list = input.data("dependency_list") || [],
|
||||
dependencies = [];
|
||||
$.each(dependency_list, function (i, field_name) {
|
||||
// Dependency in a fieldset.
|
||||
let field_element = row.find(".form-row .field-" + field_name);
|
||||
// Dependency without a fieldset.
|
||||
if (!field_element.length) {
|
||||
field_element = row.find(".form-row.field-" + field_name);
|
||||
}
|
||||
dependencies.push(
|
||||
"#" + field_element.find("input, select, textarea").attr("id")
|
||||
);
|
||||
});
|
||||
if (dependencies.length) {
|
||||
input.prepopulate(dependencies, input.attr("maxlength"));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$rows.formset({
|
||||
prefix: options.prefix,
|
||||
addText: options.addText,
|
||||
formCssClass: "dynamic-" + options.prefix,
|
||||
deleteCssClass: "inline-deletelink",
|
||||
deleteText: options.deleteText,
|
||||
emptyCssClass: "empty-form",
|
||||
removed: updateInlineLabel,
|
||||
added: function (row) {
|
||||
initPrepopulatedFields(row);
|
||||
reinitDateTimeShortCuts();
|
||||
updateSelectFilter();
|
||||
updateInlineLabel(row);
|
||||
},
|
||||
addButton: options.addButton,
|
||||
});
|
||||
|
||||
if (typeof callback === "function") {
|
||||
callback();
|
||||
}
|
||||
|
||||
return $rows;
|
||||
};
|
||||
|
||||
$(window).on("htmx:afterSettle", function (event) {
|
||||
if (event.target.classList.contains("js-inline-admin-formset")) {
|
||||
initInlines($(event.target), function () {
|
||||
if (typeof DateTimeShortcuts !== "undefined") {
|
||||
$(".datetimeshortcuts").remove();
|
||||
DateTimeShortcuts.init();
|
||||
}
|
||||
|
||||
$(event.target).find(".admin-autocomplete").djangoAdminSelect2();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$(document).ready(function () {
|
||||
$(".js-inline-admin-formset").each(function () {
|
||||
initInlines(this);
|
||||
});
|
||||
});
|
||||
|
||||
function initInlines(el, callback = null) {
|
||||
const data = $(el).data(),
|
||||
inlineOptions = data.inlineFormset;
|
||||
let selector;
|
||||
switch (data.inlineType) {
|
||||
case "stacked":
|
||||
selector = inlineOptions.name + "-group .inline-related";
|
||||
$(selector).stackedFormset(selector, inlineOptions.options, callback);
|
||||
break;
|
||||
case "tabular":
|
||||
selector =
|
||||
inlineOptions.name +
|
||||
"-group .tabular.inline-related tbody:last > tr.form-row";
|
||||
$(selector).tabularFormset(selector, inlineOptions.options, callback);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1,2 @@
|
||||
/*! tailwindcss v4.1.16 | MIT License | https://tailwindcss.com */
|
||||
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-border-style:solid;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-ease:initial;--tw-text-shadow-color:initial;--tw-text-shadow-alpha:100%;--tw-content:""}}}@layer theme{:root,:host{--font-sans:"Inter",sans-serif;--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-red-50:oklch(97.1% .013 17.38);--color-red-100:oklch(93.6% .032 17.717);--color-red-200:oklch(88.5% .062 18.334);--color-red-300:oklch(80.8% .114 19.571);--color-red-400:oklch(70.4% .191 22.216);--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-orange-100:oklch(95.4% .038 75.164);--color-orange-400:oklch(75% .183 55.934);--color-orange-500:oklch(70.5% .213 47.604);--color-orange-600:oklch(64.6% .222 41.116);--color-orange-700:oklch(55.3% .195 38.402);--color-amber-100:oklch(96.2% .059 95.617);--color-amber-600:oklch(66.6% .179 58.318);--color-yellow-200:oklch(94.5% .129 101.54);--color-yellow-500:oklch(79.5% .184 86.047);--color-green-100:oklch(96.2% .044 156.743);--color-green-400:oklch(79.2% .209 151.711);--color-green-500:oklch(72.3% .219 149.579);--color-green-600:oklch(62.7% .194 149.214);--color-green-700:oklch(52.7% .154 150.069);--color-blue-50:oklch(97% .014 254.604);--color-blue-100:oklch(93.2% .032 255.585);--color-blue-400:oklch(70.7% .165 254.624);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-600:oklch(54.6% .245 262.881);--color-blue-700:oklch(48.8% .243 264.376);--color-white:#fff;--spacing:.25rem;--container-sm:24rem;--container-2xl:42rem;--container-3xl:48rem;--container-4xl:56rem;--container-5xl:64rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height:calc(1.5/1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25/1.875);--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5/2.25);--text-5xl:3rem;--text-5xl--line-height:1;--text-6xl:3.75rem;--text-6xl--line-height:1;--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-tight:-.025em;--leading-normal:1.5;--leading-relaxed:1.625;--radius-xs:.125rem;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--radius-2xl:1rem;--radius-3xl:1.5rem;--ease-in-out:cubic-bezier(.4,0,.2,1);--animate-spin:spin 1s linear infinite;--blur-xs:4px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-primary-50:var(--color-primary-50);--color-primary-100:var(--color-primary-100);--color-primary-200:var(--color-primary-200);--color-primary-300:var(--color-primary-300);--color-primary-400:var(--color-primary-400);--color-primary-500:var(--color-primary-500);--color-primary-600:var(--color-primary-600);--color-primary-700:var(--color-primary-700);--color-primary-800:var(--color-primary-800);--color-primary-900:var(--color-primary-900);--color-primary-950:var(--color-primary-950);--color-base-50:var(--color-base-50);--color-base-100:var(--color-base-10Line truncated
|
||||
/*! tailwindcss v4.2.0 | MIT License | https://tailwindcss.com */
|
||||
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-border-style:solid;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-text-shadow-color:initial;--tw-text-shadow-alpha:100%;--tw-content:""}}}@layer theme{:root,:host{--font-sans:"Inter", sans-serif;--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-red-50:oklch(97.1% .013 17.38);--color-red-100:oklch(93.6% .032 17.717);--color-red-200:oklch(88.5% .062 18.334);--color-red-300:oklch(80.8% .114 19.571);--color-red-400:oklch(70.4% .191 22.216);--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-red-800:oklch(44.4% .177 26.899);--color-red-900:oklch(39.6% .141 25.723);--color-red-950:oklch(25.8% .092 26.042);--color-orange-50:oklch(98% .016 73.684);--color-orange-100:oklch(95.4% .038 75.164);--color-orange-200:oklch(90.1% .076 70.697);--color-orange-300:oklch(83.7% .128 66.29);--color-orange-400:oklch(75% .183 55.934);--color-orange-500:oklch(70.5% .213 47.604);--color-orange-600:oklch(64.6% .222 41.116);--color-orange-700:oklch(55.3% .195 38.402);--color-orange-800:oklch(47% .157 37.304);--color-orange-900:oklch(40.8% .123 38.172);--color-orange-950:oklch(26.6% .079 36.259);--color-yellow-200:oklch(94.5% .129 101.54);--color-yellow-500:oklch(79.5% .184 86.047);--color-green-50:oklch(98.2% .018 155.826);--color-green-100:oklch(96.2% .044 156.743);--color-green-200:oklch(92.5% .084 155.995);--color-green-300:oklch(87.1% .15 154.449);--color-green-400:oklch(79.2% .209 151.711);--color-green-500:oklch(72.3% .219 149.579);--color-green-600:oklch(62.7% .194 149.214);--color-green-700:oklch(52.7% .154 150.069);--color-green-800:oklch(44.8% .119 151.328);--color-green-900:oklch(39.3% .095 152.535);--color-green-950:oklch(26.6% .065 152.934);--color-blue-50:oklch(97% .014 254.604);--color-blue-100:oklch(93.2% .032 255.585);--color-blue-200:oklch(88.2% .059 254.128);--color-blue-300:oklch(80.9% .105 251.813);--color-blue-400:oklch(70.7% .165 254.624);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-600:oklch(54.6% .245 262.881);--color-blue-700:oklch(48.8% .243 264.376);--color-blue-800:oklch(42.4% .199 265.638);--color-blue-900:oklch(37.9% .146 265.522);--color-blue-950:oklch(28.2% .091 267.935);--color-white:#fff;--spacing:.25rem;--container-sm:24rem;--container-xl:36rem;--container-2xl:42rem;--container-3xl:48rem;--container-4xl:56rem;--container-5xl:64rem;--container-6xl:72rem;--container-7xl:80rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25 / 1.875);--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5 / 2.25);--text-5xl:3rem;--text-5xl--line-height:1;--text-6xl:3.75rem;--text-6xl--line-height:1;--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-tight:-.025em;--leading-normal:1.5;--leading-relaxed:1.625;--radius-xs:.125rem;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--radius-2xl:1rem;--radius-3xl:1.5rem;--animate-spin:Line truncated
|
||||
@@ -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