db7094fd1e
CSRF / actions: - List action forms now send both REQUEST_TOKEN (Contao gate) and _token (Symfony, validated via isCsrfTokenValid) — mirrors the editor; fixes the 400/500 "Ungültiger CSRF-Token" on publish/toggle/duplicate/delete Public reader / URLs: - Reader & results read auto_item via Input::get (marks the route param used) so clean path URLs /<page>/<alias> no longer 404 - List controller generates path URLs via ['parameters' => '/'.$alias] - Allow re-participation in the same session after a 60s cooldown Review fixes: - Results: merge answer snapshots by questionId+type (no more split cards on label edits); seed "Vielleicht" when allowMaybe so screen matches Excel - Reader: replace generic answer hint with per-option "weiter zu Frage X" only when a jump is configured; strip HTML tags from survey description - PDF: render free-text frequency summary; drop duplicate "Minimum" label - Drag-sort: bundle SortableJS locally, drop the duplicated inline sort CSS/JS from _survey_assets, throttle the backend MutationObserver - Backend: move hardcoded tl_survey operation labels to TL_LANG (de+en) - Remove dead code: SurveyEditorVoter, SurveyPermissionService, categoryFilter/categorySummary + helpers, orphaned publishSurvey lang key Reader layout: - max-width 1000px (other views keep 1400px); back/next buttons fill the width as equal, card-sized halves; sort handle icon offset tweak Docs: update AI_HANDOVER (dual-token CSRF, clean URLs) and README Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
136 lines
3.9 KiB
JavaScript
136 lines
3.9 KiB
JavaScript
(function () {
|
|
function isSurveyListPage() {
|
|
var params = new URLSearchParams(window.location.search);
|
|
|
|
return params.get('do') === 'survey_bundle_surveys';
|
|
}
|
|
|
|
function isSurveyQuestionListPage() {
|
|
var params = new URLSearchParams(window.location.search);
|
|
|
|
return params.get('do') === 'survey_bundle_surveys' && params.get('table') === 'tl_survey_content' && !params.get('act');
|
|
}
|
|
|
|
function toggleQuestionListClass() {
|
|
document.body.classList.toggle('survey-question-list-page', isSurveyQuestionListPage());
|
|
document.body.classList.toggle('survey-question-list-clipboard-active', isSurveyQuestionListPage() && hasQuestionListClipboardState());
|
|
}
|
|
|
|
function hasQuestionListClipboardState() {
|
|
if (!isSurveyQuestionListPage()) {
|
|
return false;
|
|
}
|
|
|
|
if (document.querySelector('#paste_hint')) {
|
|
return true;
|
|
}
|
|
|
|
return Array.from(document.querySelectorAll('button')).some(function (button) {
|
|
return (button.textContent || '').trim() === 'Ablage leeren';
|
|
});
|
|
}
|
|
|
|
function getSearchSelect() {
|
|
return document.querySelector('select[name="tl_search"]');
|
|
}
|
|
|
|
function getSearchForm(select) {
|
|
return select ? select.closest('form') : null;
|
|
}
|
|
|
|
function removeIdOption(select) {
|
|
if (!select) {
|
|
return;
|
|
}
|
|
|
|
var idOption = select.querySelector('option[value="id"]');
|
|
|
|
if (!idOption) {
|
|
return;
|
|
}
|
|
|
|
var wasSelected = idOption.selected;
|
|
idOption.remove();
|
|
|
|
if (wasSelected) {
|
|
var fallback = select.querySelector('option[value="title"]') || select.options[0];
|
|
|
|
if (fallback) {
|
|
fallback.selected = true;
|
|
select.value = fallback.value;
|
|
select.dispatchEvent(new Event('change', { bubbles: true }));
|
|
}
|
|
}
|
|
}
|
|
|
|
function removeIdChoicesUi() {
|
|
document.querySelectorAll('.choices__item[data-value="id"]').forEach(function (node) {
|
|
node.remove();
|
|
});
|
|
|
|
document.querySelectorAll('.choices__item--choice[data-value="id"]').forEach(function (node) {
|
|
node.remove();
|
|
});
|
|
}
|
|
|
|
function normalizeSearchField() {
|
|
toggleQuestionListClass();
|
|
|
|
if (!isSurveyListPage()) {
|
|
return;
|
|
}
|
|
|
|
var select = getSearchSelect();
|
|
|
|
if (!select) {
|
|
return;
|
|
}
|
|
|
|
removeIdOption(select);
|
|
removeIdChoicesUi();
|
|
|
|
var form = getSearchForm(select);
|
|
|
|
if (form && !form.dataset.surveySearchNormalized) {
|
|
form.dataset.surveySearchNormalized = '1';
|
|
form.addEventListener('submit', function () {
|
|
if (select.value === 'id') {
|
|
var fallback = select.querySelector('option[value="title"]') || select.options[0];
|
|
|
|
if (fallback) {
|
|
select.value = fallback.value;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', normalizeSearchField);
|
|
document.addEventListener('turbo:load', normalizeSearchField);
|
|
|
|
// Mutationen werden gebündelt: viele DOM-Änderungen lösen nur einen
|
|
// normalizeSearchField-Lauf pro Frame aus statt einen pro Mutation.
|
|
var scheduled = false;
|
|
var scheduleNormalize = function () {
|
|
if (scheduled) {
|
|
return;
|
|
}
|
|
|
|
scheduled = true;
|
|
|
|
var run = function () {
|
|
scheduled = false;
|
|
normalizeSearchField();
|
|
};
|
|
|
|
if (typeof window.requestAnimationFrame === 'function') {
|
|
window.requestAnimationFrame(run);
|
|
} else {
|
|
window.setTimeout(run, 100);
|
|
}
|
|
};
|
|
|
|
var observer = new MutationObserver(scheduleNormalize);
|
|
|
|
observer.observe(document.documentElement, { childList: true, subtree: true });
|
|
})(); |