mirror of
https://github.com/Theodor-Springmann-Stiftung/musenalm.git
synced 2026-02-04 02:25:30 +00:00
522 lines
19 KiB
Plaintext
522 lines
19 KiB
Plaintext
{{ $model := . }}
|
|
|
|
<edit-page>
|
|
<div class="flex container-normal bg-slate-100 mx-auto px-8">
|
|
<div class="flex flex-row w-full justify-between">
|
|
<div class="flex flex-col justify-end-safe flex-2/5">
|
|
<div class="mb-1">
|
|
<div class="header-tabs">
|
|
{{- if $model.result -}}
|
|
<a href="/almanach/{{- $model.result.Entry.MusenalmID -}}/edit" class="header-tab">
|
|
<i class="ri-book-line"></i> Almanach
|
|
</a>
|
|
{{- else -}}
|
|
<span class="header-tab header-tab-disabled">
|
|
<i class="ri-book-line"></i> Almanach
|
|
</span>
|
|
{{- end -}}
|
|
<a href="/almanach/{{- $model.result.Entry.MusenalmID -}}/contents/edit" class="header-tab">
|
|
<i class="ri-file-list-3-line"></i> Beiträge
|
|
</a>
|
|
<span class="header-tab-sep" aria-hidden="true">
|
|
<i class="ri-arrow-right-s-line"></i>
|
|
</span>
|
|
<span class="header-tab header-tab-active" aria-current="page">
|
|
<i class="ri-file-text-line"></i> Einzelbeitrag
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<h1 class="text-2xl w-full font-bold text-slate-900 mb-1">
|
|
{{- if $model.result -}}
|
|
{{- $model.result.Entry.PreferredTitle -}}<br>
|
|
{{- if $model.is_new -}}
|
|
<span class="text-base font-semibold text-slate-700">Neuer Beitrag</span>
|
|
{{- else -}}
|
|
<span class="mt-1 mb-1 block text-base font-semibold text-slate-900">Beitrag Nr. {{ $model.content.MusenalmID }}</span>
|
|
{{- end -}}
|
|
{{- else -}}
|
|
Beiträge bearbeiten
|
|
{{- end -}}
|
|
</h1>
|
|
{{- if $model.result -}}
|
|
<div class="flex flex-row gap-x-3">
|
|
{{- if not $model.is_new -}}
|
|
<div>
|
|
<a
|
|
href="/beitrag/{{ $model.content.MusenalmID }}"
|
|
class="text-gray-700 hover:text-slate-950 block no-underline">
|
|
<i class="ri-eye-line"></i> Anschauen
|
|
</a>
|
|
</div>
|
|
·
|
|
<div class="flex flex-row">
|
|
{{- if $model.prev_content -}}
|
|
<div>
|
|
<a href="/almanach/{{ $model.result.Entry.MusenalmID }}/contents/{{ $model.prev_content.MusenalmID }}/edit" class="text-gray-700 hover:text-slate-950 no-underline block">
|
|
<i class="ri-arrow-left-s-line"></i>
|
|
</a>
|
|
</div>
|
|
{{- end -}}
|
|
<div class="px-1.5 py-0.5 rounded-xs bg-gray-200 w-fit font-bold">
|
|
{{ $model.content.MusenalmID }}
|
|
</div>
|
|
{{- if $model.next_content -}}
|
|
<div>
|
|
<a href="/almanach/{{ $model.result.Entry.MusenalmID }}/contents/{{ $model.next_content.MusenalmID }}/edit" class="text-gray-700 hover:text-slate-950 no-underline block">
|
|
<i class="ri-arrow-right-s-line"></i>
|
|
</a>
|
|
</div>
|
|
{{- end -}}
|
|
</div>
|
|
{{- end -}}
|
|
</div>
|
|
{{- end -}}
|
|
</div>
|
|
{{- if $model.result -}}
|
|
<div class="flex flex-row" id="contents-header-data">
|
|
<div class="flex flex-col justify-end gap-y-6 pr-20">
|
|
<div class="">
|
|
<div class="font-bold text-sm">
|
|
<i class="ri-navigation-line"></i> Navigation
|
|
</div>
|
|
<div class="flex items-center gap-2">
|
|
{{- if not $model.is_new -}}
|
|
{{- if $model.prev_content -}}
|
|
<tool-tip position="top" class="!inline">
|
|
<div class="data-tip">
|
|
{{- if $model.prev_content.PreferredTitle -}}
|
|
{{- $model.prev_content.PreferredTitle -}}
|
|
{{- else if $model.prev_content.TitleStmt -}}
|
|
{{- $model.prev_content.TitleStmt -}}
|
|
{{- else -}}
|
|
Beitrag Nr. {{ $model.prev_content.MusenalmID }}
|
|
{{- end -}}
|
|
</div>
|
|
<a
|
|
href="/almanach/{{ $model.result.Entry.MusenalmID }}/contents/{{ $model.prev_content.MusenalmID }}/edit"
|
|
class="text-gray-700 hover:text-slate-950 no-underline">
|
|
<i class="ri-arrow-left-s-line"></i>
|
|
</a>
|
|
</tool-tip>
|
|
{{- else -}}
|
|
<span class="text-gray-700 opacity-0 pointer-events-none">
|
|
<i class="ri-arrow-left-s-line"></i>
|
|
</span>
|
|
{{- end -}}
|
|
<span class="text-gray-800 font-bold no-underline tabular-nums whitespace-nowrap">
|
|
<span class="inline-block w-[2.5ch] text-right">{{ $model.content_index }}</span> / <span class="inline-block w-[2.5ch] text-left">{{ $model.content_total }}</span>
|
|
</span>
|
|
{{- if $model.next_content -}}
|
|
<tool-tip position="top" class="!inline">
|
|
<div class="data-tip">
|
|
{{- if $model.next_content.PreferredTitle -}}
|
|
{{- $model.next_content.PreferredTitle -}}
|
|
{{- else if $model.next_content.TitleStmt -}}
|
|
{{- $model.next_content.TitleStmt -}}
|
|
{{- else -}}
|
|
Beitrag Nr. {{ $model.next_content.MusenalmID }}
|
|
{{- end -}}
|
|
</div>
|
|
<a
|
|
href="/almanach/{{ $model.result.Entry.MusenalmID }}/contents/{{ $model.next_content.MusenalmID }}/edit"
|
|
class="text-gray-700 hover:text-slate-950 no-underline">
|
|
<i class="ri-arrow-right-s-line"></i>
|
|
</a>
|
|
</tool-tip>
|
|
{{- else -}}
|
|
<span class="text-gray-700 opacity-0 pointer-events-none">
|
|
<i class="ri-arrow-right-s-line"></i>
|
|
</span>
|
|
{{- end -}}
|
|
{{- else -}}
|
|
<span class="text-gray-800 font-bold no-underline">Neu</span>
|
|
{{- end -}}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="flex flex-col justify-end gap-y-6 pr-10 w-40 shrink-0">
|
|
<div class="">
|
|
<div class="font-bold text-sm">
|
|
<i class="ri-database-2-line"></i> Datenbank-ID
|
|
</div>
|
|
<div class="whitespace-nowrap tabular-nums">{{ $model.content.Id }}</div>
|
|
</div>
|
|
</div>
|
|
<div class="flex flex-col justify-end gap-y-6 pr-4 w-48 shrink-0">
|
|
<div class="">
|
|
<div class="font-bold text-sm mb-1"><i class="ri-calendar-line"></i> Zuletzt bearbeitet</div>
|
|
<div>
|
|
<div class="px-1.5 py-0.5 rounded-xs bg-gray-200 w-fit whitespace-nowrap tabular-nums">
|
|
<span>{{ GermanDate $model.result.Entry.Updated }}</span>,
|
|
<span>{{ GermanTime $model.result.Entry.Updated }}</span>h
|
|
</div>
|
|
<div
|
|
class="px-1.5 py-0.5 rounded-xs mt-1.5 bg-gray-200 w-fit {{ if not $model.result.User }}hidden{{ end }}">
|
|
<i class="ri-user-line mr-1"></i>
|
|
<span>{{- if $model.result.User -}}{{ $model.result.User.Name }}{{- end -}}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{{- end -}}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="container-normal mx-auto mt-4 !px-0">
|
|
{{/* usermessage moved into action bar */}}
|
|
<form
|
|
autocomplete="off"
|
|
class="w-full dbform form-with-action-bar"
|
|
method="POST"
|
|
enctype="multipart/form-data"
|
|
hx-boost="false"
|
|
action="/almanach/{{ $model.result.Entry.MusenalmID }}/contents/edit">
|
|
<input type="hidden" name="csrf_token" value="{{ $model.csrf_token }}" />
|
|
<input type="hidden" name="content_id" value="{{ $model.content_id }}" />
|
|
|
|
<div class="flex gap-8">
|
|
<div class="flex-1 flex flex-col gap-4">
|
|
{{- template "_content_edit" (Dict
|
|
"content" $model.content
|
|
"content_id" $model.content_id
|
|
"entry" $model.result.Entry
|
|
"content_types" $model.content_types
|
|
"musenalm_types" $model.musenalm_types
|
|
"pagination_values" $model.pagination_values
|
|
"agents" $model.agents
|
|
"content_agents" $model.content_agents
|
|
"agent_relations" $model.agent_relations
|
|
) -}}
|
|
</div>
|
|
|
|
<div class="w-[28rem] shrink-0 flex flex-col gap-3">
|
|
{{- $prefix := printf "content_%s_" $model.content_id -}}
|
|
{{- $fieldId := printf "content-%s-edit-state" $model.content_id -}}
|
|
{{ template "_content_status_edit" (Arr $model.content $prefix $fieldId) }}
|
|
<div class="inputwrapper">
|
|
<div class="inputlabelrow">
|
|
<div class="flex items-center gap-1">
|
|
<label for="content-{{ $model.content_id }}-musenalm-type" class="inputlabel">Beitragstyp(en)</label>
|
|
<tool-tip position="top" class="!inline">
|
|
<div class="data-tip">{{ helpOr "contents" "musenalm_type" "Musenalm-Typen des Beitrags." }}</div>
|
|
<i class="ri-question-line"></i>
|
|
</tool-tip>
|
|
</div>
|
|
<button type="button" id="content-{{ $model.content_id }}-musenalm-type-toggle" class="text-sm font-bold text-gray-700 hover:text-slate-950 no-underline pr-3">
|
|
<i class="ri-link"></i> Typ verlinken
|
|
</button>
|
|
</div>
|
|
<div class="px-2 py-2">
|
|
<multi-select-simple
|
|
id="content-{{ $model.content_id }}-musenalm-type"
|
|
name="content_{{ $model.content_id }}_musenalm_type[]"
|
|
data-external-toggle-id="content-{{ $model.content_id }}-musenalm-type-toggle"
|
|
show-create-button="false"
|
|
placeholder="Musenalm-Typen suchen..."
|
|
data-toggle-label='<i class="ri-add-circle-line"></i>'
|
|
data-empty-text="Keine Typen verknüpft"
|
|
value='[{{- range $i, $t := $model.content.MusenalmType -}}{{- if $i }},{{ end -}}"{{ $t }}"{{- end -}}]'
|
|
data-initial-options='[{{- range $i, $t := $model.musenalm_types -}}{{- if $i }},{{ end -}}{{ printf "{\"id\":%q,\"name\":%q}" $t $t }}{{- end -}}]'
|
|
data-initial-values='[{{- range $i, $t := $model.content.MusenalmType -}}{{- if $i }},{{ end -}}{{ printf "%q" $t }}{{- end -}}]'>
|
|
</multi-select-simple>
|
|
</div>
|
|
</div>
|
|
<div class="inputwrapper">
|
|
<div class="inputlabelrow">
|
|
<label for="content-{{ $model.content_id }}-language" class="inputlabel">Sprache</label>
|
|
<button type="button" id="content-{{ $model.content_id }}-language-toggle" class="text-sm font-bold text-gray-700 hover:text-slate-950 no-underline pr-3">
|
|
<i class="ri-link"></i> Sprache verlinken
|
|
</button>
|
|
</div>
|
|
<div class="px-2 py-2">
|
|
<multi-select-simple
|
|
id="content-{{ $model.content_id }}-language"
|
|
name="content_{{ $model.content_id }}_language[]"
|
|
data-external-toggle-id="content-{{ $model.content_id }}-language-toggle"
|
|
show-create-button="false"
|
|
placeholder="Sprachen suchen..."
|
|
data-toggle-label='<i class="ri-add-circle-line"></i>'
|
|
data-empty-text="Keine Sprachen verknüpft"
|
|
value='[{{- range $i, $lang := $model.content.Language -}}{{- if $i }},{{ end -}}"{{ $lang }}"{{- end -}}]'
|
|
data-initial-values='[{{- range $i, $lang := $model.content.Language -}}{{- if $i }},{{ end -}}{{ printf "%q" $lang }}{{- end -}}]'>
|
|
</multi-select-simple>
|
|
</div>
|
|
</div>
|
|
{{- template "_content_images_panel" (Dict
|
|
"content" $model.content
|
|
"entry" $model.result.Entry
|
|
"csrf_token" $model.csrf_token
|
|
"is_new" false
|
|
) -}}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-action-bar">
|
|
<div class="form-action-bar-inner">
|
|
<p
|
|
id="user-message"
|
|
class="form-action-bar-message save-feedback {{ if $model.error }}save-feedback-error text-red-700{{ else if $model.success }}save-feedback-success text-green-700{{ else }}hidden{{ end }}"
|
|
{{ if $model.success }}data-autohide="true"{{ end }}
|
|
aria-live="polite">
|
|
{{- if $model.error -}}
|
|
{{ $model.error }}
|
|
{{- else if $model.success -}}
|
|
{{ $model.success }}
|
|
{{- end -}}
|
|
</p>
|
|
<div class="form-action-bar-actions">
|
|
<a href="/almanach/{{ $model.result.Entry.MusenalmID }}/contents/edit" class="resetbutton w-40 flex items-center gap-2 justify-center">
|
|
<i class="ri-arrow-left-line"></i>
|
|
<span>Liste</span>
|
|
</a>
|
|
<a href="/almanach/{{ $model.result.Entry.MusenalmID }}/contents/new" class="resetbutton w-40 flex items-center gap-2 justify-center">
|
|
<i class="ri-add-line"></i>
|
|
<span>Neuer Beitrag</span>
|
|
</a>
|
|
{{- if not $model.is_new -}}
|
|
<button type="button" class="resetbutton w-40 flex items-center gap-2 justify-center bg-red-50 text-red-800 hover:bg-red-100 hover:text-red-900" data-role="content-delete">
|
|
<i class="ri-delete-bin-line"></i>
|
|
<span>Eintrag loeschen</span>
|
|
</button>
|
|
{{- end -}}
|
|
{{- if $model.content.MusenalmID -}}
|
|
<button type="submit" name="save_action" value="stay" class="submitbutton flex items-center gap-2 justify-center">
|
|
<i class="ri-save-line"></i>
|
|
<span>Speichern</span>
|
|
</button>
|
|
<button
|
|
type="submit"
|
|
name="save_action"
|
|
value="view"
|
|
class="submitbutton flex items-center gap-2 justify-center"
|
|
>
|
|
<i class="ri-eye-line"></i>
|
|
<span>Speichern & Anzeigen</span>
|
|
</button>
|
|
{{- end -}}
|
|
{{- if not $model.content.MusenalmID -}}
|
|
<button type="submit" name="save_action" value="stay" class="submitbutton flex items-center gap-2 justify-center">
|
|
<i class="ri-save-line"></i>
|
|
<span>Speichern</span>
|
|
</button>
|
|
{{- end -}}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<dialog data-role="content-delete-dialog" class="dbform fixed inset-0 m-auto rounded-md border border-slate-200 p-0 shadow-xl backdrop:bg-black/40">
|
|
<div class="p-5 w-[22rem]">
|
|
<div class="text-base font-bold text-gray-900">Eintrag loeschen?</div>
|
|
{{- if $model.content.TitleStmt -}}
|
|
<div class="text-sm font-bold text-gray-900 mt-1">{{ $model.content.TitleStmt }}</div>
|
|
{{- end -}}
|
|
<p class="text-sm text-gray-700 mt-2">
|
|
Der Eintrag wird dauerhaft geloescht. Verknuepfungen, Exemplare und Beitraege werden entfernt.
|
|
</p>
|
|
<div class="flex items-center justify-end gap-3 mt-4">
|
|
<button type="button" class="resetbutton w-auto px-3 py-1 text-sm" data-role="content-delete-cancel">Abbrechen</button>
|
|
<button type="button" class="submitbutton w-auto bg-red-700 hover:bg-red-800 px-3 py-1 text-sm" data-role="content-delete-confirm">
|
|
Loeschen
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</dialog>
|
|
</form>
|
|
</div>
|
|
</edit-page>
|
|
|
|
<script>
|
|
(() => {
|
|
const applyMultiSelectInit = (el) => {
|
|
if (!el) return;
|
|
const optionsRaw = el.getAttribute("data-initial-options") || "[]";
|
|
const valuesRaw = el.getAttribute("data-initial-values") || "[]";
|
|
let options = [];
|
|
let values = [];
|
|
try {
|
|
options = JSON.parse(optionsRaw);
|
|
values = JSON.parse(valuesRaw);
|
|
} catch {
|
|
return;
|
|
}
|
|
if (options.length && typeof el.setOptions === "function") {
|
|
el.setOptions(options);
|
|
}
|
|
if (values.length) {
|
|
el.value = values;
|
|
if (typeof el.captureInitialSelection === "function") {
|
|
el.captureInitialSelection();
|
|
}
|
|
}
|
|
};
|
|
|
|
const deleteButton = document.querySelector("[data-role='content-delete']");
|
|
const deleteDialog = document.querySelector("[data-role='content-delete-dialog']");
|
|
const deleteConfirm = document.querySelector("[data-role='content-delete-confirm']");
|
|
const deleteCancel = document.querySelector("[data-role='content-delete-cancel']");
|
|
const csrfToken = "{{ $model.csrf_token }}";
|
|
const contentId = "{{ $model.content_id }}";
|
|
const deleteEndpoint = "/almanach/{{ $model.result.Entry.MusenalmID }}/contents/delete";
|
|
|
|
const initMultiSelects = () => {
|
|
document.querySelectorAll("multi-select-simple[data-initial-options], multi-select-simple[data-initial-values]").forEach((el) => {
|
|
applyMultiSelectInit(el);
|
|
});
|
|
};
|
|
if (window.customElements?.whenDefined) {
|
|
window.customElements.whenDefined("multi-select-simple").then(() => {
|
|
requestAnimationFrame(initMultiSelects);
|
|
});
|
|
} else {
|
|
initMultiSelects();
|
|
}
|
|
|
|
if (deleteButton && deleteDialog && deleteConfirm && deleteCancel) {
|
|
deleteButton.addEventListener("click", () => {
|
|
if (deleteDialog.showModal) {
|
|
deleteDialog.showModal();
|
|
} else {
|
|
deleteDialog.setAttribute("open", "true");
|
|
}
|
|
});
|
|
|
|
deleteCancel.addEventListener("click", () => {
|
|
deleteDialog.close();
|
|
});
|
|
|
|
deleteDialog.addEventListener("cancel", (event) => {
|
|
event.preventDefault();
|
|
deleteDialog.close();
|
|
});
|
|
|
|
deleteConfirm.addEventListener("click", () => {
|
|
const payload = new URLSearchParams();
|
|
payload.set("csrf_token", csrfToken);
|
|
payload.set("content_id", contentId);
|
|
fetch(deleteEndpoint, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
},
|
|
body: payload.toString(),
|
|
}).then((response) => {
|
|
if (response.ok) {
|
|
window.location.assign("/almanach/{{ $model.result.Entry.MusenalmID }}/contents/edit");
|
|
return;
|
|
}
|
|
deleteDialog.close();
|
|
}).catch(() => {
|
|
deleteDialog.close();
|
|
});
|
|
});
|
|
}
|
|
|
|
const showUserMessage = (message, type) => {
|
|
const userMessage = document.getElementById("user-message");
|
|
if (!userMessage) return;
|
|
userMessage.textContent = message;
|
|
userMessage.classList.remove("hidden", "save-feedback-error", "save-feedback-success", "text-red-700", "text-green-700");
|
|
if (type === "error") {
|
|
userMessage.classList.add("save-feedback-error", "text-red-700");
|
|
} else if (type === "success") {
|
|
userMessage.classList.add("save-feedback-success", "text-green-700");
|
|
}
|
|
};
|
|
|
|
const attachFormHandlers = () => {
|
|
const form = document.querySelector("form.dbform");
|
|
const uploadInput = document.querySelector("[data-role='content-images-upload-input']");
|
|
const userMessage = document.getElementById("user-message");
|
|
if (!form || !uploadInput || !userMessage) {
|
|
return;
|
|
}
|
|
form.addEventListener("submit", async (event) => {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
event.stopImmediatePropagation();
|
|
const submitter = event.submitter || document.activeElement;
|
|
const files = Array.from(uploadInput.files || []);
|
|
if (files.length > 0) {
|
|
const hasInvalid = files.some((file) => !file.type || !file.type.startsWith("image/"));
|
|
if (hasInvalid) {
|
|
showUserMessage("Bitte nur Bilddateien auswählen.", "error");
|
|
return;
|
|
}
|
|
}
|
|
|
|
const payload = new FormData(form);
|
|
if (submitter && submitter.name) {
|
|
payload.set(submitter.name, submitter.value || "");
|
|
}
|
|
if (payload.has("scans")) {
|
|
payload.delete("scans");
|
|
}
|
|
const imagesComponent = document.querySelector("content-images");
|
|
if (imagesComponent && typeof imagesComponent.getPendingFiles === "function") {
|
|
const pendingFiles = imagesComponent.getPendingFiles();
|
|
const pendingIds = typeof imagesComponent.getPendingIds === "function" ? imagesComponent.getPendingIds() : [];
|
|
const scanOrder = typeof imagesComponent.getScanOrder === "function" ? imagesComponent.getScanOrder() : [];
|
|
pendingFiles.forEach((file, index) => {
|
|
payload.append("scans", file);
|
|
if (pendingIds[index]) {
|
|
payload.append("scans_pending_ids[]", pendingIds[index]);
|
|
}
|
|
});
|
|
if (Array.isArray(scanOrder) && scanOrder.length > 0) {
|
|
scanOrder.forEach((token) => {
|
|
payload.append("scans_order[]", token);
|
|
});
|
|
}
|
|
if (typeof imagesComponent.getPendingDeletes === "function") {
|
|
imagesComponent.getPendingDeletes().forEach((fileName) => {
|
|
payload.append("scans_delete[]", fileName);
|
|
});
|
|
}
|
|
}
|
|
const response = await fetch(form.action, {
|
|
method: form.method || "POST",
|
|
body: payload,
|
|
credentials: "same-origin",
|
|
});
|
|
const html = await response.text();
|
|
if (response.redirected && response.url) {
|
|
if (response.url !== window.location.href) {
|
|
window.location.assign(response.url);
|
|
return;
|
|
}
|
|
}
|
|
if (!html) {
|
|
return;
|
|
}
|
|
const doc = new DOMParser().parseFromString(html, "text/html");
|
|
const nextMessage = doc.getElementById("user-message");
|
|
const liveMessage = document.getElementById("user-message");
|
|
const isErrorMessage = nextMessage && nextMessage.classList.contains("save-feedback-error");
|
|
if (nextMessage && liveMessage) {
|
|
liveMessage.className = nextMessage.className;
|
|
liveMessage.textContent = nextMessage.textContent || "";
|
|
if (!liveMessage.textContent.trim()) {
|
|
liveMessage.classList.add("hidden");
|
|
}
|
|
}
|
|
if (isErrorMessage || !response.ok) {
|
|
if (!nextMessage && !response.ok) {
|
|
showUserMessage("Speichern fehlgeschlagen.", "error");
|
|
}
|
|
return;
|
|
}
|
|
|
|
const nextForm = doc.querySelector("form.dbform");
|
|
if (nextForm) {
|
|
form.replaceWith(nextForm);
|
|
initMultiSelects();
|
|
attachFormHandlers();
|
|
return;
|
|
}
|
|
}, true);
|
|
};
|
|
|
|
attachFormHandlers();
|
|
})();
|
|
</script>
|