mirror of
https://github.com/Theodor-Springmann-Stiftung/musenalm.git
synced 2026-02-04 02:25:30 +00:00
small frointend annoyances
This commit is contained in:
@@ -59,6 +59,7 @@ type SeitenEditorSection struct {
|
|||||||
type SeitenEditorSelected struct {
|
type SeitenEditorSelected struct {
|
||||||
Key string
|
Key string
|
||||||
Title string
|
Title string
|
||||||
|
URL string
|
||||||
Description string
|
Description string
|
||||||
Keywords string
|
Keywords string
|
||||||
Sections []SeitenEditorSection
|
Sections []SeitenEditorSection
|
||||||
@@ -286,9 +287,32 @@ func seitenEditorSelected(app core.App, key string) (*SeitenEditorSelected, erro
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pageURL := page.URL()
|
||||||
|
if pageURL == "" {
|
||||||
|
switch key {
|
||||||
|
case pagemodels.P_INDEX_NAME:
|
||||||
|
pageURL = "/"
|
||||||
|
case pagemodels.P_REIHEN_NAME:
|
||||||
|
pageURL = "/reihen/"
|
||||||
|
case pagemodels.P_DANK_NAME:
|
||||||
|
pageURL = "/redaktion/danksagungen/"
|
||||||
|
case pagemodels.P_EINFUEHRUNG_NAME:
|
||||||
|
pageURL = "/redaktion/einleitung/"
|
||||||
|
case pagemodels.P_KONTAKT_NAME:
|
||||||
|
pageURL = "/redaktion/kontakt/"
|
||||||
|
case pagemodels.P_LIT_NAME:
|
||||||
|
pageURL = "/redaktion/literatur/"
|
||||||
|
case pagemodels.P_DOK_NAME:
|
||||||
|
pageURL = "/redaktion/benutzerhinweise/"
|
||||||
|
case pagemodels.P_KABINETT_NAME:
|
||||||
|
pageURL = "/redaktion/lesekabinett/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return &SeitenEditorSelected{
|
return &SeitenEditorSelected{
|
||||||
Key: key,
|
Key: key,
|
||||||
Title: page.Title(),
|
Title: page.Title(),
|
||||||
|
URL: pageURL,
|
||||||
Description: description,
|
Description: description,
|
||||||
Keywords: keywords,
|
Keywords: keywords,
|
||||||
Sections: sections,
|
Sections: sections,
|
||||||
|
|||||||
@@ -677,41 +677,49 @@ const (
|
|||||||
var pageMetaSeed = map[string]PageMeta{
|
var pageMetaSeed = map[string]PageMeta{
|
||||||
pagemodels.P_INDEX_NAME: {
|
pagemodels.P_INDEX_NAME: {
|
||||||
Title: INDEX_TITLE,
|
Title: INDEX_TITLE,
|
||||||
|
URL: "/",
|
||||||
Description: INDEX_DESCRIPTION,
|
Description: INDEX_DESCRIPTION,
|
||||||
Keywords: "",
|
Keywords: "",
|
||||||
},
|
},
|
||||||
pagemodels.P_REIHEN_NAME: {
|
pagemodels.P_REIHEN_NAME: {
|
||||||
Title: REIHEN_TITLE,
|
Title: REIHEN_TITLE,
|
||||||
|
URL: "/reihen/",
|
||||||
Description: REIHEN_DESCRIPTION,
|
Description: REIHEN_DESCRIPTION,
|
||||||
Keywords: "",
|
Keywords: "",
|
||||||
},
|
},
|
||||||
pagemodels.P_DANK_NAME: {
|
pagemodels.P_DANK_NAME: {
|
||||||
Title: "Danksagungen",
|
Title: "Danksagungen",
|
||||||
|
URL: "/redaktion/danksagungen/",
|
||||||
Description: DANKSAGUNGEN_DESCRIPTION,
|
Description: DANKSAGUNGEN_DESCRIPTION,
|
||||||
Keywords: "",
|
Keywords: "",
|
||||||
},
|
},
|
||||||
pagemodels.P_EINFUEHRUNG_NAME: {
|
pagemodels.P_EINFUEHRUNG_NAME: {
|
||||||
Title: EINLEITUNG_TITLE,
|
Title: EINLEITUNG_TITLE,
|
||||||
|
URL: "/redaktion/einleitung/",
|
||||||
Description: EINLEITUNG_DESCRIPTION,
|
Description: EINLEITUNG_DESCRIPTION,
|
||||||
Keywords: "",
|
Keywords: "",
|
||||||
},
|
},
|
||||||
pagemodels.P_KONTAKT_NAME: {
|
pagemodels.P_KONTAKT_NAME: {
|
||||||
Title: KONTAKT_TITLE,
|
Title: KONTAKT_TITLE,
|
||||||
|
URL: "/redaktion/kontakt/",
|
||||||
Description: KONTAKT_DESCRIPTION,
|
Description: KONTAKT_DESCRIPTION,
|
||||||
Keywords: "",
|
Keywords: "",
|
||||||
},
|
},
|
||||||
pagemodels.P_LIT_NAME: {
|
pagemodels.P_LIT_NAME: {
|
||||||
Title: LITERATUR_TITLE,
|
Title: LITERATUR_TITLE,
|
||||||
|
URL: "/redaktion/literatur/",
|
||||||
Description: LITERATUR_DESCRIPTION,
|
Description: LITERATUR_DESCRIPTION,
|
||||||
Keywords: "",
|
Keywords: "",
|
||||||
},
|
},
|
||||||
pagemodels.P_DOK_NAME: {
|
pagemodels.P_DOK_NAME: {
|
||||||
Title: DOKUMENTATION_TITLE,
|
Title: DOKUMENTATION_TITLE,
|
||||||
|
URL: "/redaktion/benutzerhinweise/",
|
||||||
Description: DOKUMENTATION_DESCRIPTION,
|
Description: DOKUMENTATION_DESCRIPTION,
|
||||||
Keywords: "",
|
Keywords: "",
|
||||||
},
|
},
|
||||||
pagemodels.P_KABINETT_NAME: {
|
pagemodels.P_KABINETT_NAME: {
|
||||||
Title: KABINETT_TITLE,
|
Title: KABINETT_TITLE,
|
||||||
|
URL: "/redaktion/lesekabinett/",
|
||||||
Description: KABINETT_DESCRIPTION,
|
Description: KABINETT_DESCRIPTION,
|
||||||
Keywords: "",
|
Keywords: "",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
|
|
||||||
type PageMeta struct {
|
type PageMeta struct {
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
|
URL string `json:"url"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
Keywords string `json:"keywords"`
|
Keywords string `json:"keywords"`
|
||||||
}
|
}
|
||||||
@@ -30,6 +31,9 @@ func upsertPageMeta(app core.App, key string, meta PageMeta) error {
|
|||||||
record.Set(dbmodels.KEY_FIELD, key)
|
record.Set(dbmodels.KEY_FIELD, key)
|
||||||
}
|
}
|
||||||
record.Set(dbmodels.TITLE_FIELD, meta.Title)
|
record.Set(dbmodels.TITLE_FIELD, meta.Title)
|
||||||
|
if meta.URL != "" {
|
||||||
|
record.Set(dbmodels.URL_FIELD, meta.URL)
|
||||||
|
}
|
||||||
|
|
||||||
data := map[string]any{}
|
data := map[string]any{}
|
||||||
if existing := record.Get(dbmodels.DATA_FIELD); existing != nil {
|
if existing := record.Get(dbmodels.DATA_FIELD); existing != nil {
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -11,6 +11,12 @@
|
|||||||
Abkürzungen
|
Abkürzungen
|
||||||
</h1>
|
</h1>
|
||||||
<div class="flex flex-row gap-x-3">
|
<div class="flex flex-row gap-x-3">
|
||||||
|
<div>
|
||||||
|
<a href="/abkuerzungen/" class="text-gray-700 hover:text-slate-950 block no-underline">
|
||||||
|
<i class="ri-eye-line"></i> Anschauen
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
·
|
||||||
<div>
|
<div>
|
||||||
<a href="/abkuerzungen/" class="text-gray-700 hover:text-slate-950 block no-underline">
|
<a href="/abkuerzungen/" class="text-gray-700 hover:text-slate-950 block no-underline">
|
||||||
<i class="ri-loop-left-line"></i> Reset
|
<i class="ri-loop-left-line"></i> Reset
|
||||||
@@ -42,18 +48,23 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="overflow-x-auto bg-white border border-gray-200 rounded">
|
<div class="overflow-x-auto bg-white border border-gray-200 rounded">
|
||||||
<table class="w-full">
|
<table class="w-full table-fixed" style="table-layout: fixed;">
|
||||||
|
<colgroup>
|
||||||
|
<col style="width: 12rem;" />
|
||||||
|
<col />
|
||||||
|
<col style="width: 8rem;" />
|
||||||
|
</colgroup>
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="border-b-2 border-gray-300 bg-gray-50">
|
<tr class="border-b-2 border-gray-300 bg-gray-50">
|
||||||
<th class="text-left px-4 py-3 font-bold text-sm text-gray-700 w-48">Kürzel</th>
|
<th class="text-left px-4 py-3 font-bold text-sm text-gray-700">Kürzel</th>
|
||||||
<th class="text-left px-4 py-3 font-bold text-sm text-gray-700">Bedeutung</th>
|
<th class="text-left px-4 py-3 font-bold text-sm text-gray-700">Bedeutung</th>
|
||||||
<th class="w-16 px-4 py-3"></th>
|
<th class="px-4 py-3"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="abk-tbody">
|
<tbody id="abk-tbody">
|
||||||
{{- range $index, $entry := $model.result.Entries -}}
|
{{- range $index, $entry := $model.result.Entries -}}
|
||||||
<tr class="border-b border-gray-200 hover:bg-gray-50 odd:bg-stone-100" data-row="{{ $index }}">
|
<tr class="border-b border-gray-200 hover:bg-gray-50 odd:bg-stone-100" data-rel-row data-row="{{ $index }}">
|
||||||
<td class="px-4 py-2.5">
|
<td class="px-4 py-2.5" style="width: 12rem;">
|
||||||
<input type="text"
|
<input type="text"
|
||||||
name="abkuerzungen[{{ $index }}][key]"
|
name="abkuerzungen[{{ $index }}][key]"
|
||||||
value="{{ $entry.Key }}"
|
value="{{ $entry.Key }}"
|
||||||
@@ -69,10 +80,11 @@
|
|||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
required>
|
required>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-2.5 text-center">
|
<td class="px-2 py-2.5 pr-4 text-center" style="width: 8rem;">
|
||||||
<input type="hidden" name="abkuerzungen[{{ $index }}][_delete]" value="false" class="delete-flag">
|
<input type="hidden" name="abkuerzungen[{{ $index }}][_delete]" id="abkuerzungen_delete_{{ $index }}" value="false" class="delete-flag">
|
||||||
<button type="button" class="delete-btn text-red-600 hover:text-red-800" title="Löschen">
|
<button type="button" class="delete-btn inline-flex items-center gap-1 whitespace-nowrap text-sm px-1.5" data-delete-toggle="abkuerzungen_delete_{{ $index }}">
|
||||||
<i class="ri-delete-bin-line text-lg"></i>
|
<i class="ri-delete-bin-line"></i>
|
||||||
|
<span class="no-underline" data-delete-label data-delete-default="Entfernen" data-delete-active="Wird entfernt" data-delete-hover="Rückgängig">Entfernen</span>
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -106,13 +118,108 @@
|
|||||||
const addBtn = document.getElementById('add-row-btn');
|
const addBtn = document.getElementById('add-row-btn');
|
||||||
let rowIndex = {{ len $model.result.Entries }};
|
let rowIndex = {{ len $model.result.Entries }};
|
||||||
|
|
||||||
|
const updateDeleteLabel = (button, deleted, hovered) => {
|
||||||
|
const label = button.querySelector('[data-delete-label]');
|
||||||
|
if (label) {
|
||||||
|
let nextLabel;
|
||||||
|
if (deleted && hovered) {
|
||||||
|
nextLabel = label.getAttribute('data-delete-hover') || 'Rückgängig';
|
||||||
|
} else if (deleted) {
|
||||||
|
nextLabel = label.getAttribute('data-delete-active') || 'Wird entfernt';
|
||||||
|
} else {
|
||||||
|
nextLabel = label.getAttribute('data-delete-default') || 'Entfernen';
|
||||||
|
}
|
||||||
|
label.textContent = nextLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
const icon = button.querySelector('i');
|
||||||
|
if (!icon) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (deleted) {
|
||||||
|
if (hovered) {
|
||||||
|
icon.classList.remove('hidden');
|
||||||
|
icon.classList.add('ri-arrow-go-back-line');
|
||||||
|
icon.classList.remove('ri-delete-bin-line');
|
||||||
|
} else {
|
||||||
|
icon.classList.add('hidden');
|
||||||
|
icon.classList.remove('ri-delete-bin-line', 'ri-arrow-go-back-line');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
icon.classList.remove('hidden');
|
||||||
|
icon.classList.add('ri-delete-bin-line');
|
||||||
|
icon.classList.remove('ri-arrow-go-back-line');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const setRowDeleted = (row, button, deleted) => {
|
||||||
|
const deleteFlag = row.querySelector('.delete-flag');
|
||||||
|
if (deleteFlag) {
|
||||||
|
deleteFlag.value = deleted ? 'true' : 'false';
|
||||||
|
}
|
||||||
|
row.classList.toggle('bg-red-50', deleted);
|
||||||
|
row.querySelectorAll('input, select, textarea').forEach((control) => {
|
||||||
|
if (control === deleteFlag || control.type === 'hidden') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ('readOnly' in control) {
|
||||||
|
control.readOnly = deleted;
|
||||||
|
}
|
||||||
|
control.classList.toggle('opacity-50', deleted);
|
||||||
|
control.classList.toggle('cursor-not-allowed', deleted);
|
||||||
|
});
|
||||||
|
updateDeleteLabel(button, deleted, button.matches(':hover'));
|
||||||
|
};
|
||||||
|
|
||||||
|
const wireDeleteButton = (button) => {
|
||||||
|
if (!button || button.dataset.abkBound === 'true') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
button.dataset.abkBound = 'true';
|
||||||
|
|
||||||
|
button.addEventListener('click', (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
const row = button.closest('tr');
|
||||||
|
if (!row) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const deleteFlag = row.querySelector('.delete-flag');
|
||||||
|
const isDeleted = deleteFlag && deleteFlag.value === 'true';
|
||||||
|
setRowDeleted(row, button, !isDeleted);
|
||||||
|
});
|
||||||
|
|
||||||
|
button.addEventListener('mouseenter', () => {
|
||||||
|
const row = button.closest('tr');
|
||||||
|
const deleteFlag = row ? row.querySelector('.delete-flag') : null;
|
||||||
|
if (!deleteFlag || deleteFlag.value !== 'true') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
updateDeleteLabel(button, true, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
button.addEventListener('mouseleave', () => {
|
||||||
|
const row = button.closest('tr');
|
||||||
|
const deleteFlag = row ? row.querySelector('.delete-flag') : null;
|
||||||
|
updateDeleteLabel(button, deleteFlag && deleteFlag.value === 'true', false);
|
||||||
|
});
|
||||||
|
|
||||||
|
const row = button.closest('tr');
|
||||||
|
const deleteFlag = row ? row.querySelector('.delete-flag') : null;
|
||||||
|
if (row && deleteFlag && deleteFlag.value === 'true') {
|
||||||
|
setRowDeleted(row, button, true);
|
||||||
|
} else {
|
||||||
|
updateDeleteLabel(button, false, false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Add new row
|
// Add new row
|
||||||
addBtn.addEventListener('click', function() {
|
addBtn.addEventListener('click', function() {
|
||||||
const tr = document.createElement('tr');
|
const tr = document.createElement('tr');
|
||||||
tr.className = 'border-b border-gray-200 hover:bg-gray-50 odd:bg-stone-100';
|
tr.className = 'border-b border-gray-200 hover:bg-gray-50 odd:bg-stone-100';
|
||||||
tr.dataset.row = rowIndex;
|
tr.dataset.row = rowIndex;
|
||||||
|
tr.setAttribute('data-rel-row', '');
|
||||||
tr.innerHTML = `
|
tr.innerHTML = `
|
||||||
<td class="px-4 py-2.5">
|
<td class="px-4 py-2.5" style="width: 12rem;">
|
||||||
<input type="text"
|
<input type="text"
|
||||||
name="abkuerzungen[${rowIndex}][key]"
|
name="abkuerzungen[${rowIndex}][key]"
|
||||||
value=""
|
value=""
|
||||||
@@ -128,14 +235,16 @@
|
|||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
required>
|
required>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-2.5 text-center">
|
<td class="px-2 py-2.5 pr-4 text-center" style="width: 8rem;">
|
||||||
<input type="hidden" name="abkuerzungen[${rowIndex}][_delete]" value="false" class="delete-flag">
|
<input type="hidden" name="abkuerzungen[${rowIndex}][_delete]" id="abkuerzungen_delete_${rowIndex}" value="false" class="delete-flag">
|
||||||
<button type="button" class="delete-btn text-red-600 hover:text-red-800" title="Löschen">
|
<button type="button" class="delete-btn inline-flex items-center gap-1 whitespace-nowrap text-sm px-1.5" data-delete-toggle="abkuerzungen_delete_${rowIndex}">
|
||||||
<i class="ri-delete-bin-line text-lg"></i>
|
<i class="ri-delete-bin-line"></i>
|
||||||
|
<span class="no-underline" data-delete-label data-delete-default="Entfernen" data-delete-active="Wird entfernt" data-delete-hover="Rückgängig">Entfernen</span>
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
`;
|
`;
|
||||||
tbody.appendChild(tr);
|
tbody.appendChild(tr);
|
||||||
|
wireDeleteButton(tr.querySelector('.delete-btn'));
|
||||||
rowIndex++;
|
rowIndex++;
|
||||||
|
|
||||||
// Focus the new key input
|
// Focus the new key input
|
||||||
@@ -145,15 +254,8 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Delete row handler (using event delegation)
|
const deleteButtons = tbody.querySelectorAll('.delete-btn');
|
||||||
tbody.addEventListener('click', function(e) {
|
deleteButtons.forEach((button) => wireDeleteButton(button));
|
||||||
if (e.target.classList.contains('delete-btn')) {
|
|
||||||
const tr = e.target.closest('tr');
|
|
||||||
const deleteFlag = tr.querySelector('.delete-flag');
|
|
||||||
deleteFlag.value = 'true';
|
|
||||||
tr.style.display = 'none';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
2
views/routes/abkuerzungen/head.gohtml
Normal file
2
views/routes/abkuerzungen/head.gohtml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
<title>{{ .site.title }} – Abkürzungen</title>
|
||||||
|
<meta name="description" content="Musenalm: Abkürzungsverzeichnis." />
|
||||||
@@ -9,6 +9,15 @@
|
|||||||
<h1 class="text-2xl w-full font-bold text-slate-900 mb-1">
|
<h1 class="text-2xl w-full font-bold text-slate-900 mb-1">
|
||||||
Seiteneditor
|
Seiteneditor
|
||||||
</h1>
|
</h1>
|
||||||
|
{{- if and $model.selected $model.selected.URL -}}
|
||||||
|
<div class="flex flex-row gap-x-3">
|
||||||
|
<div>
|
||||||
|
<a href="{{ $model.selected.URL }}" class="text-gray-700 hover:text-slate-950 block no-underline">
|
||||||
|
<i class="ri-eye-line"></i> Anschauen
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{- end -}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col justify-end gap-y-4 pr-4">
|
<div class="flex flex-col justify-end gap-y-4 pr-4">
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
<title>Seiteneditor</title>
|
<title>{{ .site.title }} – Seiteneditor</title>
|
||||||
<script src="/assets/vendor/tinymce/tinymce.min.js" defer></script>
|
<script src="/assets/vendor/tinymce/tinymce.min.js" defer></script>
|
||||||
|
|||||||
@@ -204,7 +204,13 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-rel-row].bg-red-50::before,
|
[data-rel-row].bg-red-50 > td,
|
||||||
|
.items-row.bg-red-50,
|
||||||
|
.mss-selected-item-pill.bg-red-100 {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-rel-row].bg-red-50 > td::before,
|
||||||
.items-row.bg-red-50::before,
|
.items-row.bg-red-50::before,
|
||||||
.mss-selected-item-pill.bg-red-100::before {
|
.mss-selected-item-pill.bg-red-100::before {
|
||||||
content: "";
|
content: "";
|
||||||
|
|||||||
Reference in New Issue
Block a user