mirror of
https://github.com/Theodor-Springmann-Stiftung/kgpz_web.git
synced 2025-10-29 17:15:31 +00:00
Page jumps
This commit is contained in:
BIN
Screenshot.png
BIN
Screenshot.png
Binary file not shown.
|
Before Width: | Height: | Size: 216 KiB After Width: | Height: | Size: 1.6 MiB |
@@ -97,57 +97,27 @@ func GetPageJumpForm(kgpz *xmlmodels.Library) fiber.Handler {
|
|||||||
year, err := strconv.Atoi(yearStr)
|
year, err := strconv.Atoi(yearStr)
|
||||||
if err != nil || year < MINYEAR || year > MAXYEAR {
|
if err != nil || year < MINYEAR || year > MAXYEAR {
|
||||||
logging.Debug("Invalid year in form: " + yearStr)
|
logging.Debug("Invalid year in form: " + yearStr)
|
||||||
// Get available years for dropdown (simplified for error case)
|
return c.Status(fiber.StatusBadRequest).Render("/errors/jump_error/", fiber.Map{
|
||||||
availableYears := []int{}
|
"Message": fmt.Sprintf("Ungültiges Jahr. Bitte wählen Sie ein Jahr zwischen %d und %d.", MINYEAR, MAXYEAR),
|
||||||
for y := MINYEAR; y <= MAXYEAR; y++ {
|
}, "clear")
|
||||||
availableYears = append(availableYears, y)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.Status(fiber.StatusBadRequest).SendString(renderPageJumpFormWithError(
|
|
||||||
fmt.Sprintf("Ungültiges Jahr. Bitte wählen Sie ein Jahr zwischen %d und %d.", MINYEAR, MAXYEAR),
|
|
||||||
"",
|
|
||||||
availableYears,
|
|
||||||
MINYEAR,
|
|
||||||
pageStr,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate page
|
// Validate page
|
||||||
page, err := strconv.Atoi(pageStr)
|
page, err := strconv.Atoi(pageStr)
|
||||||
if err != nil || page < 1 {
|
if err != nil || page < 1 {
|
||||||
logging.Debug("Invalid page in form: " + pageStr)
|
logging.Debug("Invalid page in form: " + pageStr)
|
||||||
// Get available years for dropdown
|
return c.Status(fiber.StatusBadRequest).Render("/errors/jump_error/", fiber.Map{
|
||||||
availableYears := []int{}
|
"Message": "Ungültige Seitenzahl. Bitte geben Sie eine positive Zahl ein.",
|
||||||
for y := MINYEAR; y <= MAXYEAR; y++ {
|
}, "clear")
|
||||||
availableYears = append(availableYears, y)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.Status(fiber.StatusBadRequest).SendString(renderPageJumpFormWithError(
|
|
||||||
"",
|
|
||||||
"Ungültige Seitenzahl. Bitte geben Sie eine positive Zahl ein.",
|
|
||||||
availableYears,
|
|
||||||
year,
|
|
||||||
"",
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the issue containing this page
|
// Find the issue containing this page
|
||||||
issue, err := FindIssueByYearAndPage(year, page, kgpz)
|
issue, err := FindIssueByYearAndPage(year, page, kgpz)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logging.Debug(fmt.Sprintf("Page %d not found in year %d: %v", page, year, err))
|
logging.Debug(fmt.Sprintf("Page %d not found in year %d: %v", page, year, err))
|
||||||
// Get available years for dropdown
|
return c.Status(fiber.StatusNotFound).Render("/errors/jump_error/", fiber.Map{
|
||||||
availableYears := []int{}
|
"Message": fmt.Sprintf("Seite %s wurde in Jahr %s nicht gefunden.", pageStr, yearStr),
|
||||||
for y := MINYEAR; y <= MAXYEAR; y++ {
|
}, "clear")
|
||||||
availableYears = append(availableYears, y)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.Status(fiber.StatusNotFound).SendString(renderPageJumpFormWithError(
|
|
||||||
"",
|
|
||||||
fmt.Sprintf("Seite %s wurde in Jahr %s nicht gefunden.", pageStr, yearStr),
|
|
||||||
availableYears,
|
|
||||||
year,
|
|
||||||
pageStr,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construct the redirect URL
|
// Construct the redirect URL
|
||||||
@@ -155,73 +125,10 @@ func GetPageJumpForm(kgpz *xmlmodels.Library) fiber.Handler {
|
|||||||
|
|
||||||
logging.Debug(fmt.Sprintf("Page jump form: year=%s, page=%s -> %s", yearStr, pageStr, redirectURL))
|
logging.Debug(fmt.Sprintf("Page jump form: year=%s, page=%s -> %s", yearStr, pageStr, redirectURL))
|
||||||
|
|
||||||
// Return HTMX redirect
|
// Clear any existing errors and redirect
|
||||||
c.Set("HX-Redirect", redirectURL)
|
c.Set("HX-Redirect", redirectURL)
|
||||||
return c.SendStatus(fiber.StatusOK)
|
c.Set("HX-Retarget", "#jump-errors")
|
||||||
|
c.Set("HX-Reswap", "innerHTML")
|
||||||
|
return c.SendString("")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// renderPageJumpFormWithError generates the page jump form HTML with error messages
|
|
||||||
func renderPageJumpFormWithError(yearError, pageError string, availableYears []int, currentYear int, pageValue string) string {
|
|
||||||
html := `<form hx-post="/jump" hx-swap="outerHTML" class="space-y-4">
|
|
||||||
<div class="grid grid-cols-2 gap-4">
|
|
||||||
<!-- Year Selection -->
|
|
||||||
<div>
|
|
||||||
<label for="jump-year" class="block text-sm font-medium text-slate-700 mb-1">Jahr</label>
|
|
||||||
<select id="jump-year" name="year" class="w-full px-3 py-2 border `
|
|
||||||
|
|
||||||
if yearError != "" {
|
|
||||||
html += `border-red-300`
|
|
||||||
} else {
|
|
||||||
html += `border-slate-300`
|
|
||||||
}
|
|
||||||
|
|
||||||
html += ` rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">`
|
|
||||||
|
|
||||||
for _, year := range availableYears {
|
|
||||||
html += fmt.Sprintf(`<option value="%d"`, year)
|
|
||||||
if year == currentYear {
|
|
||||||
html += ` selected`
|
|
||||||
}
|
|
||||||
html += fmt.Sprintf(`>%d</option>`, year)
|
|
||||||
}
|
|
||||||
|
|
||||||
html += `</select>`
|
|
||||||
|
|
||||||
if yearError != "" {
|
|
||||||
html += fmt.Sprintf(`<div class="text-red-600 text-sm mt-1">%s</div>`, yearError)
|
|
||||||
}
|
|
||||||
|
|
||||||
html += `</div>
|
|
||||||
|
|
||||||
<!-- Page Input -->
|
|
||||||
<div>
|
|
||||||
<label for="jump-page" class="block text-sm font-medium text-slate-700 mb-1">Seite</label>
|
|
||||||
<input type="number" id="jump-page" name="page" min="1" placeholder="z.B. 42" value="` + pageValue + `" class="w-full px-3 py-2 border `
|
|
||||||
|
|
||||||
if pageError != "" {
|
|
||||||
html += `border-red-300`
|
|
||||||
} else {
|
|
||||||
html += `border-slate-300`
|
|
||||||
}
|
|
||||||
|
|
||||||
html += ` rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">`
|
|
||||||
|
|
||||||
if pageError != "" {
|
|
||||||
html += fmt.Sprintf(`<div class="text-red-600 text-sm mt-1">%s</div>`, pageError)
|
|
||||||
}
|
|
||||||
|
|
||||||
html += `</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Submit Button -->
|
|
||||||
<div class="text-center">
|
|
||||||
<button type="submit" class="inline-flex items-center px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-md shadow-sm transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2">
|
|
||||||
<i class="ri-arrow-right-line mr-2"></i>
|
|
||||||
Zur Seite springen
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>`
|
|
||||||
|
|
||||||
return html
|
|
||||||
}
|
|
||||||
@@ -17,6 +17,13 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
ASSETS_URL_PREFIX = "/assets"
|
ASSETS_URL_PREFIX = "/assets"
|
||||||
|
CLEAR_LAYOUT = `
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
{{ block "head" . }}{{ end }}
|
||||||
|
</head>
|
||||||
|
{{ block "body" . }}{{ end }}
|
||||||
|
</html>`
|
||||||
)
|
)
|
||||||
|
|
||||||
type Engine struct {
|
type Engine struct {
|
||||||
@@ -227,12 +234,19 @@ func (e *Engine) Render(out io.Writer, path string, data interface{}, layout ...
|
|||||||
e.mu.Lock()
|
e.mu.Lock()
|
||||||
defer e.mu.Unlock()
|
defer e.mu.Unlock()
|
||||||
var l *template.Template
|
var l *template.Template
|
||||||
if layout == nil || len(layout) == 0 {
|
if len(layout) == 0 {
|
||||||
lay, err := e.LayoutRegistry.Default(&e.FuncMap)
|
lay, err := e.LayoutRegistry.Default(&e.FuncMap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
l = lay
|
l = lay
|
||||||
|
} else {
|
||||||
|
if layout[0] == "clear" {
|
||||||
|
lay, err := template.New("clear").Parse(CLEAR_LAYOUT)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
l = lay
|
||||||
} else {
|
} else {
|
||||||
lay, err := e.LayoutRegistry.Layout(layout[0], &e.FuncMap)
|
lay, err := e.LayoutRegistry.Layout(layout[0], &e.FuncMap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -240,6 +254,7 @@ func (e *Engine) Render(out io.Writer, path string, data interface{}, layout ...
|
|||||||
}
|
}
|
||||||
l = lay
|
l = lay
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
lay, err := l.Clone()
|
lay, err := l.Clone()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -16,46 +16,61 @@
|
|||||||
|
|
||||||
<!-- Page Jump Form -->
|
<!-- Page Jump Form -->
|
||||||
<div class="mt-8 w-full">
|
<div class="mt-8 w-full">
|
||||||
<div class="mx-auto max-w-md bg-slate-50 p-6 rounded-lg border border-slate-200">
|
<div class="mx-auto text-center">
|
||||||
<h3 class="text-lg font-semibold text-slate-800 mb-4 text-center">Direkt zu Seite springen</h3>
|
<h3 class="text-lg font-medium text-slate-700 mb-6">Direkt zu Seite springen</h3>
|
||||||
|
|
||||||
<form hx-post="/jump" hx-swap="outerHTML" class="space-y-4">
|
<form hx-post="/jump" hx-target="#jump-errors" hx-swap="innerHTML" hx-target-4*="#jump-errors" hx-target-5*="#jump-errors" hx-ext="response-targets" class="inline-flex items-center gap-3">
|
||||||
<div class="grid grid-cols-2 gap-4">
|
|
||||||
<!-- Year Selection -->
|
<!-- Year Selection -->
|
||||||
<div>
|
<div class="flex items-center gap-2">
|
||||||
<label for="jump-year" class="block text-sm font-medium text-slate-700 mb-1">Jahr</label>
|
<label for="jump-year" class="text-sm text-slate-600 whitespace-nowrap">Jahr</label>
|
||||||
<select id="jump-year" name="year" value="{{ $y }}" class="w-full px-3 py-2 border border-slate-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
<select id="jump-year" name="year" value="{{ $y }}" class="px-2 py-1 border border-slate-300 rounded text-sm focus:outline-none focus:ring-1 focus:ring-blue-400 focus:border-blue-400">
|
||||||
{{ range $year := .model.AvailableYears }}
|
{{ range $year := .model.AvailableYears }}
|
||||||
<option value="{{ $year }}" {{ if eq $year $y }}selected{{ end }}>{{ $year }}</option>
|
<option value="{{ $year }}" {{ if eq $year $y }}selected{{ end }}>{{ $year }}</option>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</select>
|
</select>
|
||||||
<div id="year-error"></div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Page Input -->
|
<!-- Page Input -->
|
||||||
<div>
|
<div class="flex items-center gap-2">
|
||||||
<label for="jump-page" class="block text-sm font-medium text-slate-700 mb-1">Seite</label>
|
<label for="jump-page" class="text-sm text-slate-600 whitespace-nowrap">Seite</label>
|
||||||
<input type="number" id="jump-page" name="page" min="1" placeholder="z.B. 42" class="w-full px-3 py-2 border border-slate-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
<input type="number" id="jump-page" name="page" min="1" placeholder="42" class="w-20 px-2 py-1 border border-slate-300 rounded text-sm focus:outline-none focus:ring-1 focus:ring-blue-400 focus:border-blue-400">
|
||||||
<div id="page-error"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Submit Button -->
|
<!-- Submit Button -->
|
||||||
<div class="text-center">
|
<button type="submit" class="inline-flex items-center px-3 py-1 bg-blue-600 hover:bg-blue-700 text-white text-sm rounded transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-1">
|
||||||
<button type="submit" class="inline-flex items-center px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-md shadow-sm transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2">
|
<i class="ri-arrow-right-line mr-1"></i>
|
||||||
<i class="ri-arrow-right-line mr-2"></i>
|
Springen
|
||||||
Zur Seite springen
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<!-- Error Display Area -->
|
||||||
|
<div id="jump-errors" class="mt-3 min-h-[1.5rem]"></div>
|
||||||
|
|
||||||
<!-- Instructions -->
|
<!-- Instructions -->
|
||||||
<div class="mt-4 text-sm text-slate-600 text-center">
|
<div class="mt-4 text-sm text-slate-500">
|
||||||
<p>Geben Sie Jahr und Seitenzahl ein, um direkt zur entsprechenden Ausgabe zu springen.</p>
|
<p>Geben Sie Jahr und Seitenzahl ein, um direkt zur entsprechenden Ausgabe zu springen.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Clear errors when user starts typing
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const errorContainer = document.getElementById('jump-errors');
|
||||||
|
const yearSelect = document.getElementById('jump-year');
|
||||||
|
const pageInput = document.getElementById('jump-page');
|
||||||
|
|
||||||
|
function clearErrors() {
|
||||||
|
if (errorContainer) {
|
||||||
|
errorContainer.innerHTML = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (yearSelect) yearSelect.addEventListener('change', clearErrors);
|
||||||
|
if (pageInput) pageInput.addEventListener('input', clearErrors);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
<div class="grid grid-cols-11 gap-x-2 gap-y-4 pt-8">
|
<div class="grid grid-cols-11 gap-x-2 gap-y-4 pt-8">
|
||||||
{{ range $index, $month := .model.Issues }}
|
{{ range $index, $month := .model.Issues }}
|
||||||
|
|
||||||
|
|||||||
1
views/routes/errors/jump_error/body.gohtml
Normal file
1
views/routes/errors/jump_error/body.gohtml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<div class="text-red-600 text-sm">{{ .Message }}</div>
|
||||||
Reference in New Issue
Block a user