refactor: split serve.py and index.html into single-responsibility modules
Backend:
- path_validator.py: PathValidator-Klasse für Pfad-Validierung
- file_ops.py: read_file, write_file, directory_exists, file_exists
- content_types.py: get_content_type mit EXTENSION_MAP
- handler.py: Handler-Klasse mit do_GET/do_PUT, nutzt above modules
- serve.py: Entry-Point (main, find_free_port), setzt Handler.validator/directory
Frontend:
- css/variables.css: CSS-Variablen (--bg-*, --text-*, --accent, etc.)
- css/styles.css: Alle CSS-Regeln (modal, card, template-grid, etc.)
- js/utils.js: esc, showToast, copyContentToClipboard
- js/modal.js: showModal, closeModal, closeEditModal, wasViewModalOpen
- js/editor.js: editModalContent, createJsonEditUI, extractJsonFromForm
- js/api.js: viewTemplate, copyContent, loadTemplates, saveEditedContent
- js/templates.js: renderTemplates, applyFilters, parseTypeFromHash
- js/main.js: Event-Listener, Hash-Filter, Initialisierung
- index.html: Inline-CSS/JS entfernt, <link>/<script src>-Tags hinzugefügt
Smoke test: SO_REUSEADDR für schnelle Port-Wiederverwendung
2026-05-03 14:40:44 +02:00
|
|
|
|
/**
|
|
|
|
|
|
* Template-Rendering und Filterung.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Rendert die Template-Karte und wendet Filter (Typ, Suche) an.
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
let currentEditTemplate = null;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Render Template-Karten in den Container
|
|
|
|
|
|
* @param {Array} templates - Zu rendernde Templates
|
|
|
|
|
|
*/
|
|
|
|
|
|
function renderTemplates(templates) {
|
|
|
|
|
|
const container = document.getElementById('templates');
|
|
|
|
|
|
const count = document.getElementById('count');
|
|
|
|
|
|
|
|
|
|
|
|
count.textContent = `${templates.length} Template(s)`;
|
|
|
|
|
|
|
|
|
|
|
|
if (templates.length === 0) {
|
|
|
|
|
|
container.innerHTML = '<div class="empty-state"><h3>Keine Templates gefunden</h3><p>Füge Templates in den templates/ Ordnern hinzu.</p></div>';
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
container.innerHTML = templates.map(t => `
|
|
|
|
|
|
<div class="template-item">
|
|
|
|
|
|
<h3>${esc(t.name)}</h3>
|
|
|
|
|
|
<div class="meta">
|
|
|
|
|
|
<span>🏷️ ${esc(t.type)}</span>
|
|
|
|
|
|
<span>📄 ${esc(t.format)}</span>
|
|
|
|
|
|
<span>📌 v${esc(t.version)}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<p>${esc(t.description) || 'Keine Beschreibung'}</p>
|
|
|
|
|
|
<div class="tags">
|
|
|
|
|
|
${t.tags.map(tag => `<span class="tag">${esc(tag)}</span>`).join('')}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="actions">
|
2026-05-03 20:09:36 +02:00
|
|
|
|
<button class="btn btn-icon btn-view" data-path="${t.path}" aria-label="Anzeigen: ${esc(t.name)}">Anzeigen</button>
|
|
|
|
|
|
<button class="btn btn-icon btn-edit" data-path="${t.path}" aria-label="Bearbeiten: ${esc(t.name)}">📝 Bearbeiten</button>
|
|
|
|
|
|
<button class="btn btn-icon btn-copy" data-path="${t.path}" aria-label="Inhalt kopieren: ${esc(t.name)}">Inhalt kopieren</button>
|
|
|
|
|
|
<button class="btn btn-icon btn-tune" data-path="${t.path}" aria-label="Tunen: ${esc(t.name)}">🎯 Tunen</button>
|
refactor: split serve.py and index.html into single-responsibility modules
Backend:
- path_validator.py: PathValidator-Klasse für Pfad-Validierung
- file_ops.py: read_file, write_file, directory_exists, file_exists
- content_types.py: get_content_type mit EXTENSION_MAP
- handler.py: Handler-Klasse mit do_GET/do_PUT, nutzt above modules
- serve.py: Entry-Point (main, find_free_port), setzt Handler.validator/directory
Frontend:
- css/variables.css: CSS-Variablen (--bg-*, --text-*, --accent, etc.)
- css/styles.css: Alle CSS-Regeln (modal, card, template-grid, etc.)
- js/utils.js: esc, showToast, copyContentToClipboard
- js/modal.js: showModal, closeModal, closeEditModal, wasViewModalOpen
- js/editor.js: editModalContent, createJsonEditUI, extractJsonFromForm
- js/api.js: viewTemplate, copyContent, loadTemplates, saveEditedContent
- js/templates.js: renderTemplates, applyFilters, parseTypeFromHash
- js/main.js: Event-Listener, Hash-Filter, Initialisierung
- index.html: Inline-CSS/JS entfernt, <link>/<script src>-Tags hinzugefügt
Smoke test: SO_REUSEADDR für schnelle Port-Wiederverwendung
2026-05-03 14:40:44 +02:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
`).join('');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Filter-State: aktueller Typ-Filter
|
|
|
|
|
|
*/
|
|
|
|
|
|
let currentType = null;
|
|
|
|
|
|
let currentQuery = '';
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Extrahiere Typ aus URL-Hash
|
|
|
|
|
|
* @returns {string|null} Typ-Filter oder null
|
|
|
|
|
|
*/
|
|
|
|
|
|
function parseTypeFromHash() {
|
|
|
|
|
|
const match = window.location.hash.match(/[?&]type=([^&]+)/);
|
|
|
|
|
|
return match ? decodeURIComponent(match[1]) : null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Setze Nav-Active-State basierend auf Typ
|
|
|
|
|
|
* @param {string|null} type - Typ-Filter
|
|
|
|
|
|
*/
|
|
|
|
|
|
function setNavActive(type) {
|
|
|
|
|
|
document.querySelectorAll('.nav a').forEach(a => {
|
|
|
|
|
|
const m = (a.getAttribute('href') || '').match(/type=([^&]+)/);
|
|
|
|
|
|
const aType = m ? m[1] : null;
|
|
|
|
|
|
a.classList.toggle('active', aType === type);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Wende Filter an und render Templates
|
|
|
|
|
|
*/
|
|
|
|
|
|
function applyFilters() {
|
|
|
|
|
|
let list = window.allTemplates || [];
|
|
|
|
|
|
if (currentType) {
|
|
|
|
|
|
list = list.filter(t => t.type === currentType);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (currentQuery) {
|
|
|
|
|
|
const q = currentQuery;
|
|
|
|
|
|
list = list.filter(t =>
|
|
|
|
|
|
(t.name || '').toLowerCase().includes(q) ||
|
|
|
|
|
|
(t.description || '').toLowerCase().includes(q) ||
|
|
|
|
|
|
(t.tags || []).some(tag => tag.toLowerCase().includes(q))
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
setNavActive(currentType);
|
|
|
|
|
|
renderTemplates(list);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-03 19:12:28 +02:00
|
|
|
|
// Event delegation: click on template cards routes to action functions
|
|
|
|
|
|
document.addEventListener('click', (e) => {
|
|
|
|
|
|
const btn = e.target.closest('.btn[data-path]');
|
|
|
|
|
|
if (!btn) return;
|
|
|
|
|
|
const card = btn.closest('.template-item');
|
|
|
|
|
|
if (!card) return;
|
|
|
|
|
|
const path = btn.dataset.path;
|
|
|
|
|
|
const action = btn.classList.contains('btn-view') ? 'viewTemplate'
|
|
|
|
|
|
: btn.classList.contains('btn-edit') ? 'editModalContent'
|
|
|
|
|
|
: btn.classList.contains('btn-copy') ? 'copyContent'
|
|
|
|
|
|
: btn.classList.contains('btn-tune') ? 'tuneModalContent' : null;
|
|
|
|
|
|
if (action) window[action](path);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
refactor: split serve.py and index.html into single-responsibility modules
Backend:
- path_validator.py: PathValidator-Klasse für Pfad-Validierung
- file_ops.py: read_file, write_file, directory_exists, file_exists
- content_types.py: get_content_type mit EXTENSION_MAP
- handler.py: Handler-Klasse mit do_GET/do_PUT, nutzt above modules
- serve.py: Entry-Point (main, find_free_port), setzt Handler.validator/directory
Frontend:
- css/variables.css: CSS-Variablen (--bg-*, --text-*, --accent, etc.)
- css/styles.css: Alle CSS-Regeln (modal, card, template-grid, etc.)
- js/utils.js: esc, showToast, copyContentToClipboard
- js/modal.js: showModal, closeModal, closeEditModal, wasViewModalOpen
- js/editor.js: editModalContent, createJsonEditUI, extractJsonFromForm
- js/api.js: viewTemplate, copyContent, loadTemplates, saveEditedContent
- js/templates.js: renderTemplates, applyFilters, parseTypeFromHash
- js/main.js: Event-Listener, Hash-Filter, Initialisierung
- index.html: Inline-CSS/JS entfernt, <link>/<script src>-Tags hinzugefügt
Smoke test: SO_REUSEADDR für schnelle Port-Wiederverwendung
2026-05-03 14:40:44 +02:00
|
|
|
|
// Export for main.js (global scope, loaded before main.js)
|
|
|
|
|
|
// renderTemplates, applyFilters, parseTypeFromHash, setNavActive
|
|
|
|
|
|
// currentType, currentQuery sind als globale Variablen verfügbar
|