Faktenkompass: UI-Rebrand, Admin-Assistent und Produktions-Setup.
Modernisiert die öffentliche Oberfläche mit Inter, Dark Mode und Live-Suche, ergänzt KI-gestützte Entwürfe im Admin mit Nachweis-Uploads und dokumentiert Daphne/Nginx für den Serverbetrieb. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
(function () {
|
||||
function showFilePreview(input) {
|
||||
var file = input.files && input.files[0];
|
||||
if (!file || !file.type.startsWith('image/')) {
|
||||
return;
|
||||
}
|
||||
|
||||
var row = input.closest('.inline-related') || input.closest('.form-row');
|
||||
if (!row) {
|
||||
return;
|
||||
}
|
||||
|
||||
var box = row.querySelector('.fk-evidence-preview-live');
|
||||
if (!box) {
|
||||
box = document.createElement('div');
|
||||
box.className = 'fk-evidence-preview-live';
|
||||
input.parentNode.appendChild(box);
|
||||
}
|
||||
|
||||
var reader = new FileReader();
|
||||
reader.onload = function (event) {
|
||||
box.innerHTML = '<img src="' + event.target.result + '" alt="Vorschau">';
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
|
||||
document.addEventListener('change', function (event) {
|
||||
if (event.target.type === 'file' && event.target.name && event.target.name.indexOf('evidence_files') !== -1) {
|
||||
showFilePreview(event.target);
|
||||
}
|
||||
});
|
||||
})();
|
||||
@@ -0,0 +1,44 @@
|
||||
(function () {
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var lightbox = document.getElementById('fk-lightbox');
|
||||
if (!lightbox) {
|
||||
return;
|
||||
}
|
||||
|
||||
var img = lightbox.querySelector('.fk-lightbox-img');
|
||||
var caption = lightbox.querySelector('.fk-lightbox-caption');
|
||||
var closeBtn = lightbox.querySelector('.fk-lightbox-close');
|
||||
var backdrop = lightbox.querySelector('.fk-lightbox-backdrop');
|
||||
|
||||
function openLightbox(src, alt) {
|
||||
img.src = src;
|
||||
img.alt = alt || '';
|
||||
caption.textContent = alt || '';
|
||||
caption.hidden = !alt;
|
||||
lightbox.hidden = false;
|
||||
document.body.classList.add('fk-lightbox-open');
|
||||
closeBtn.focus();
|
||||
}
|
||||
|
||||
function closeLightbox() {
|
||||
lightbox.hidden = true;
|
||||
img.src = '';
|
||||
document.body.classList.remove('fk-lightbox-open');
|
||||
}
|
||||
|
||||
document.querySelectorAll('.fk-img-open').forEach(function (button) {
|
||||
button.addEventListener('click', function () {
|
||||
openLightbox(button.dataset.src, button.dataset.caption);
|
||||
});
|
||||
});
|
||||
|
||||
closeBtn.addEventListener('click', closeLightbox);
|
||||
backdrop.addEventListener('click', closeLightbox);
|
||||
|
||||
document.addEventListener('keydown', function (event) {
|
||||
if (event.key === 'Escape' && !lightbox.hidden) {
|
||||
closeLightbox();
|
||||
}
|
||||
});
|
||||
});
|
||||
})();
|
||||
@@ -0,0 +1,63 @@
|
||||
(function () {
|
||||
const DEBOUNCE_MS = 300;
|
||||
|
||||
function debounce(fn, delay) {
|
||||
let timer;
|
||||
return function (...args) {
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(() => fn.apply(this, args), delay);
|
||||
};
|
||||
}
|
||||
|
||||
function buildUrl(form) {
|
||||
const params = new URLSearchParams();
|
||||
new FormData(form).forEach((value, key) => {
|
||||
if (value) {
|
||||
params.append(key, value);
|
||||
}
|
||||
});
|
||||
const action = form.getAttribute('action') || window.location.pathname;
|
||||
const query = params.toString();
|
||||
return query ? action + '?' + query : action;
|
||||
}
|
||||
|
||||
function runSearch(form) {
|
||||
const target = document.querySelector(form.dataset.liveTarget);
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
const url = buildUrl(form);
|
||||
fetch(url, {
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' },
|
||||
})
|
||||
.then((response) => {
|
||||
if (!response.ok) {
|
||||
throw new Error('search failed');
|
||||
}
|
||||
return response.text();
|
||||
})
|
||||
.then((html) => {
|
||||
target.innerHTML = html;
|
||||
history.replaceState(null, '', url);
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
document.querySelectorAll('form[data-live-search]').forEach(function (form) {
|
||||
const search = debounce(function () {
|
||||
runSearch(form);
|
||||
}, DEBOUNCE_MS);
|
||||
|
||||
form.querySelectorAll('input, select').forEach(function (field) {
|
||||
field.addEventListener('input', search);
|
||||
field.addEventListener('change', search);
|
||||
});
|
||||
|
||||
form.addEventListener('submit', function (event) {
|
||||
event.preventDefault();
|
||||
runSearch(form);
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
||||
@@ -0,0 +1,35 @@
|
||||
(function () {
|
||||
const storageKey = 'fk-theme';
|
||||
|
||||
function applyTheme(theme) {
|
||||
document.documentElement.setAttribute('data-theme', theme);
|
||||
const sun = document.querySelector('.fk-icon-sun');
|
||||
const moon = document.querySelector('.fk-icon-moon');
|
||||
if (sun && moon) {
|
||||
sun.hidden = theme === 'dark';
|
||||
moon.hidden = theme !== 'dark';
|
||||
}
|
||||
}
|
||||
|
||||
function preferredTheme() {
|
||||
const stored = localStorage.getItem(storageKey);
|
||||
if (stored === 'light' || stored === 'dark') {
|
||||
return stored;
|
||||
}
|
||||
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
||||
}
|
||||
|
||||
applyTheme(preferredTheme());
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const toggle = document.getElementById('fk-theme-toggle');
|
||||
if (!toggle) {
|
||||
return;
|
||||
}
|
||||
toggle.addEventListener('click', function () {
|
||||
const next = document.documentElement.getAttribute('data-theme') === 'dark' ? 'light' : 'dark';
|
||||
localStorage.setItem(storageKey, next);
|
||||
applyTheme(next);
|
||||
});
|
||||
});
|
||||
})();
|
||||
Reference in New Issue
Block a user