diff --git a/app/main.py b/app/main.py
index 5d7a1e9..2ea2be1 100755
--- a/app/main.py
+++ b/app/main.py
@@ -38,6 +38,12 @@ async def semesterapparat_form(request: Request):
return templates.TemplateResponse("semesterapparat_form.html", {"request": request})
+@app.get("/elsa", response_class=HTMLResponse)
+async def elsa_form(request: Request):
+ """ELSA form page"""
+ return templates.TemplateResponse("elsa_mono_form.html", {"request": request})
+
+
@app.post("/submit")
async def handle_form(
request: Request,
@@ -62,7 +68,7 @@ async def handle_form(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Invalid email address format.",
)
-
+
# Build XML
root = Element("form_submission")
@@ -107,3 +113,236 @@ async def handle_form(
print("=" * 80)
return RedirectResponse("/?success=true", status_code=303)
+
+
+@app.post("/elsa/submit")
+async def handle_elsa_form(request: Request):
+ """Handle ELSA form submission with multiple media types"""
+ form_data = await request.form()
+
+ # Extract general information
+ name = str(form_data.get("name", ""))
+ lastname = str(form_data.get("lastname", ""))
+ title_field = str(form_data.get("title", ""))
+ mail = str(form_data.get("mail", ""))
+ subject = str(form_data.get("subject", ""))
+ classname = str(form_data.get("classname", ""))
+ usage_date_from = str(form_data.get("usage_date_from", ""))
+ usage_date_to = str(form_data.get("usage_date_to", ""))
+ availability_date = str(form_data.get("availability_date", ""))
+ message = str(form_data.get("message", ""))
+
+ # Basic email validation
+ if not EMAIL_REGEX.match(mail):
+ raise HTTPException(
+ status_code=status.HTTP_400_BAD_REQUEST,
+ detail="Invalid email address format.",
+ )
+
+ # Build XML structure
+ root = Element("elsa_submission")
+
+ # General information
+ general_info = SubElement(root, "general_info")
+ SubElement(general_info, "name").text = name
+ SubElement(general_info, "lastname").text = lastname
+ SubElement(general_info, "title").text = title_field
+ SubElement(general_info, "mail").text = mail
+ SubElement(general_info, "subject").text = subject
+ SubElement(general_info, "classname").text = classname
+ SubElement(general_info, "usage_date_from").text = usage_date_from
+ SubElement(general_info, "usage_date_to").text = usage_date_to
+ SubElement(general_info, "availability_date").text = availability_date
+ if message:
+ SubElement(general_info, "message").text = message
+
+ # Process media sections
+ media_root = SubElement(root, "media")
+
+ # Process Monografie entries
+ if "monografie_author[]" in form_data:
+ monografie_authors = [str(v) for v in form_data.getlist("monografie_author[]")]
+ monografie_years = [str(v) for v in form_data.getlist("monografie_year[]")]
+ monografie_editions = [
+ str(v) for v in form_data.getlist("monografie_edition[]")
+ ]
+ monografie_titles = [str(v) for v in form_data.getlist("monografie_title[]")]
+ monografie_signatures = [
+ str(v) for v in form_data.getlist("monografie_signature[]")
+ ]
+ monografie_pages_from = [
+ str(v) for v in form_data.getlist("monografie_pages_from[]")
+ ]
+ monografie_pages_to = [
+ str(v) for v in form_data.getlist("monografie_pages_to[]")
+ ]
+
+ # Get section IDs from the form (assuming they're in data-section attributes)
+ # Since we can't directly access data-section from form_data, we'll process sequentially
+ monografie_section = SubElement(media_root, "monografien")
+ for i in range(len(monografie_authors)):
+ entry = SubElement(monografie_section, "entry")
+ SubElement(entry, "author").text = (
+ monografie_authors[i] if i < len(monografie_authors) else ""
+ )
+ SubElement(entry, "year").text = (
+ monografie_years[i] if i < len(monografie_years) else ""
+ )
+ SubElement(entry, "edition").text = (
+ monografie_editions[i] if i < len(monografie_editions) else ""
+ )
+ SubElement(entry, "title").text = (
+ monografie_titles[i] if i < len(monografie_titles) else ""
+ )
+ SubElement(entry, "signature").text = (
+ monografie_signatures[i] if i < len(monografie_signatures) else ""
+ )
+ SubElement(entry, "pages_from").text = (
+ monografie_pages_from[i] if i < len(monografie_pages_from) else ""
+ )
+ SubElement(entry, "pages_to").text = (
+ monografie_pages_to[i] if i < len(monografie_pages_to) else ""
+ )
+
+ # Process Zeitschriftenartikel entries
+ if "zeitschrift_author[]" in form_data:
+ zeitschrift_authors = [
+ str(v) for v in form_data.getlist("zeitschrift_author[]")
+ ]
+ zeitschrift_years = [str(v) for v in form_data.getlist("zeitschrift_year[]")]
+ zeitschrift_volumes = [
+ str(v) for v in form_data.getlist("zeitschrift_volume[]")
+ ]
+ zeitschrift_article_titles = [
+ str(v) for v in form_data.getlist("zeitschrift_article_title[]")
+ ]
+ zeitschrift_journal_titles = [
+ str(v) for v in form_data.getlist("zeitschrift_journal_title[]")
+ ]
+ zeitschrift_signatures = [
+ str(v) for v in form_data.getlist("zeitschrift_signature[]")
+ ]
+ zeitschrift_pages_from = [
+ str(v) for v in form_data.getlist("zeitschrift_pages_from[]")
+ ]
+ zeitschrift_pages_to = [
+ str(v) for v in form_data.getlist("zeitschrift_pages_to[]")
+ ]
+
+ zeitschrift_section = SubElement(media_root, "zeitschriftenartikel")
+ for i in range(len(zeitschrift_authors)):
+ entry = SubElement(zeitschrift_section, "entry")
+ SubElement(entry, "author").text = (
+ zeitschrift_authors[i] if i < len(zeitschrift_authors) else ""
+ )
+ SubElement(entry, "year").text = (
+ zeitschrift_years[i] if i < len(zeitschrift_years) else ""
+ )
+ SubElement(entry, "volume").text = (
+ zeitschrift_volumes[i] if i < len(zeitschrift_volumes) else ""
+ )
+ SubElement(entry, "article_title").text = (
+ zeitschrift_article_titles[i]
+ if i < len(zeitschrift_article_titles)
+ else ""
+ )
+ SubElement(entry, "journal_title").text = (
+ zeitschrift_journal_titles[i]
+ if i < len(zeitschrift_journal_titles)
+ else ""
+ )
+ SubElement(entry, "signature").text = (
+ zeitschrift_signatures[i] if i < len(zeitschrift_signatures) else ""
+ )
+ SubElement(entry, "pages_from").text = (
+ zeitschrift_pages_from[i] if i < len(zeitschrift_pages_from) else ""
+ )
+ SubElement(entry, "pages_to").text = (
+ zeitschrift_pages_to[i] if i < len(zeitschrift_pages_to) else ""
+ )
+
+ # Process Herausgeberwerk entries
+ if "herausgeber_publisher[]" in form_data:
+ herausgeber_publishers = [
+ str(v) for v in form_data.getlist("herausgeber_publisher[]")
+ ]
+ herausgeber_work_titles = [
+ str(v) for v in form_data.getlist("herausgeber_work_title[]")
+ ]
+ herausgeber_years = [str(v) for v in form_data.getlist("herausgeber_year[]")]
+ herausgeber_editions = [
+ str(v) for v in form_data.getlist("herausgeber_edition[]")
+ ]
+ herausgeber_article_authors = [
+ str(v) for v in form_data.getlist("herausgeber_article_author[]")
+ ]
+ herausgeber_article_titles = [
+ str(v) for v in form_data.getlist("herausgeber_article_title[]")
+ ]
+ herausgeber_signatures = [
+ str(v) for v in form_data.getlist("herausgeber_signature[]")
+ ]
+ herausgeber_pages_from = [
+ str(v) for v in form_data.getlist("herausgeber_pages_from[]")
+ ]
+ herausgeber_pages_to = [
+ str(v) for v in form_data.getlist("herausgeber_pages_to[]")
+ ]
+
+ herausgeber_section = SubElement(media_root, "herausgeberwerke")
+ for i in range(len(herausgeber_publishers)):
+ entry = SubElement(herausgeber_section, "entry")
+ SubElement(entry, "publisher").text = (
+ herausgeber_publishers[i] if i < len(herausgeber_publishers) else ""
+ )
+ SubElement(entry, "work_title").text = (
+ herausgeber_work_titles[i] if i < len(herausgeber_work_titles) else ""
+ )
+ SubElement(entry, "year").text = (
+ herausgeber_years[i] if i < len(herausgeber_years) else ""
+ )
+ SubElement(entry, "edition").text = (
+ herausgeber_editions[i] if i < len(herausgeber_editions) else ""
+ )
+ SubElement(entry, "article_author").text = (
+ herausgeber_article_authors[i]
+ if i < len(herausgeber_article_authors)
+ else ""
+ )
+ SubElement(entry, "article_title").text = (
+ herausgeber_article_titles[i]
+ if i < len(herausgeber_article_titles)
+ else ""
+ )
+ SubElement(entry, "signature").text = (
+ herausgeber_signatures[i] if i < len(herausgeber_signatures) else ""
+ )
+ SubElement(entry, "pages_from").text = (
+ herausgeber_pages_from[i] if i < len(herausgeber_pages_from) else ""
+ )
+ SubElement(entry, "pages_to").text = (
+ herausgeber_pages_to[i] if i < len(herausgeber_pages_to) else ""
+ )
+
+ xml_data = tostring(root, encoding="unicode")
+
+ # Send or print email
+ msg = MIMEText(xml_data, "xml")
+ msg["Subject"] = "New ELSA Form Submission"
+ msg["From"] = MAIL_FROM
+ msg["To"] = MAIL_TO
+
+ if MAIL_ENABLED:
+ with smtplib.SMTP(SMTP_HOST, SMTP_PORT) as server:
+ server.send_message(msg)
+ else:
+ print("=" * 80)
+ print("MAIL SENDING DISABLED - Would have sent:")
+ print(f"From: {MAIL_FROM}")
+ print(f"To: {MAIL_TO}")
+ print(f"Subject: {msg['Subject']}")
+ print("-" * 80)
+ print(xml_data)
+ print("=" * 80)
+
+ return RedirectResponse("/?success=true", status_code=303)
diff --git a/app/static/styles.css b/app/static/styles.css
index 60e5080..33b94b8 100644
--- a/app/static/styles.css
+++ b/app/static/styles.css
@@ -598,3 +598,130 @@ input[type="radio"] { accent-color: var(--control-accent); }
transform: translateY(0);
}
}
+
+/* ELSA specific styles */
+.legal-notice {
+ background: #eff6ff;
+ border: 1px solid #93c5fd;
+ border-radius: 12px;
+ padding: 16px 20px;
+ margin-bottom: 24px;
+}
+
+[data-theme="dark"] .legal-notice {
+ background: #0f2942;
+ border-color: #1e3a52;
+}
+
+.legal-notice h3 {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin: 0 0 8px;
+ font-size: 1rem;
+ color: #1e40af;
+}
+
+[data-theme="dark"] .legal-notice h3 {
+ color: #93c5fd;
+}
+
+.legal-notice p {
+ margin: 0;
+ line-height: 1.6;
+ font-size: 0.95rem;
+ color: var(--text);
+}
+
+.media-controls {
+ display: flex;
+ gap: 12px;
+ flex-wrap: wrap;
+ margin-bottom: 20px;
+}
+
+.media-section {
+ background: var(--table-head-bg);
+ border: 1px solid var(--border);
+ border-radius: 12px;
+ padding: 20px;
+ margin-bottom: 20px;
+}
+
+.media-section-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 16px;
+}
+
+.media-section-header h3 {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin: 0;
+ font-size: 1.15rem;
+}
+
+.btn-icon {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 36px;
+ height: 36px;
+ padding: 0;
+ border-radius: 8px;
+ border: 1px solid var(--border);
+ background: var(--card-bg);
+ color: var(--text);
+ cursor: pointer;
+ transition: background-color 0.2s ease, color 0.2s ease;
+}
+
+.btn-icon:hover {
+ background: var(--table-head-bg);
+ color: #ef4444;
+}
+
+.btn-sm {
+ font-size: 0.9rem;
+ padding: 8px 14px;
+}
+
+.media-table {
+ margin-bottom: 12px;
+}
+
+.media-table th {
+ font-size: 0.85rem;
+ white-space: nowrap;
+}
+
+.media-table td {
+ padding: 8px 10px;
+}
+
+.media-table input[type="text"],
+.media-table input[type="number"] {
+ font-size: 0.9rem;
+ padding: 8px 10px;
+}
+
+.media-table input[type="number"] {
+ width: 80px;
+}
+
+@media (max-width: 1024px) {
+ .media-controls {
+ flex-direction: column;
+ }
+
+ .media-table {
+ font-size: 0.85rem;
+ }
+
+ .media-table th,
+ .media-table td {
+ padding: 6px 8px;
+ }
+}
diff --git a/app/templates/elsa_mono_form.html b/app/templates/elsa_mono_form.html
new file mode 100644
index 0000000..1f1a081
--- /dev/null
+++ b/app/templates/elsa_mono_form.html
@@ -0,0 +1,310 @@
+
+
+
+ ELSA - Elektronischer Semesterapparat
+
+
+
+
+
+
+
+
+
+ ELSA - Elektronischer Semesterapparat
+
+
+
Rechtlicher Hinweis
+
+ Das Urheberrecht gestattet gemäß § 60a UrhG zur Veranschaulichung des Unterrichts die Bereitstellung elektronischer Kopien bis max. 15% von veröffentlichten Werken, von einzelnen Aufsätzen aus Fachzeitschriften und von Werken geringen Umfangs (max. 25 S. Gesamtumfang) für einen genau abgegrenzten Benutzerkreis wie den Teilnehmenden einer Lehrveranstaltung, die sich auf ILIAS angemeldet haben.
+
+
+
+
+
+
+
+
+
+
diff --git a/app/templates/index.html b/app/templates/index.html
index aed8ab4..b3f7e70 100644
--- a/app/templates/index.html
+++ b/app/templates/index.html
@@ -40,16 +40,16 @@
-