- section content type with structure service, reader context and grouped results/PDF/Excel - new questions append at the end (backend oncreate) and per-section add buttons - rich-text descriptions: Quill 2 (frontend), reduced TinyMCE (backend), server-side whitelist sanitizer - externalize inline assets, bundle Chart.js locally, harden debug access, validate range bounds Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
239 lines
7.7 KiB
JavaScript
239 lines
7.7 KiB
JavaScript
document.addEventListener('DOMContentLoaded', function () {
|
|
var surveyEditor = document.querySelector('[data-survey-handorgel]');
|
|
|
|
var resetEditorViewport = function () {
|
|
if (!surveyEditor) {
|
|
return;
|
|
}
|
|
|
|
window.scrollTo(0, 0);
|
|
};
|
|
|
|
if (surveyEditor && window.history && 'scrollRestoration' in window.history) {
|
|
window.history.scrollRestoration = 'manual';
|
|
}
|
|
|
|
resetEditorViewport();
|
|
window.addEventListener('load', function () {
|
|
window.setTimeout(resetEditorViewport, 0);
|
|
}, { once: true });
|
|
|
|
document.querySelectorAll('[data-survey-range]').forEach(function (input) {
|
|
var targetId = input.getAttribute('data-survey-range-target');
|
|
var output = targetId ? document.getElementById(targetId) : null;
|
|
|
|
if (!output) {
|
|
return;
|
|
}
|
|
|
|
var update = function () {
|
|
output.textContent = input.value;
|
|
};
|
|
|
|
input.addEventListener('input', update);
|
|
update();
|
|
});
|
|
|
|
document.querySelectorAll('[data-survey-question-type]').forEach(function (select) {
|
|
var wrapper = select.closest('[data-survey-question-editor]');
|
|
if (!wrapper) {
|
|
return;
|
|
}
|
|
|
|
var toggle = function () {
|
|
var showRange = select.value === 'range';
|
|
var showYesNoMaybe = select.value === 'yes_no_maybe';
|
|
var showChoice = select.value === 'choice';
|
|
var isSection = select.value === 'section';
|
|
|
|
wrapper.querySelectorAll('.question-only-field').forEach(function (element) {
|
|
element.style.display = isSection ? 'none' : '';
|
|
});
|
|
|
|
wrapper.querySelectorAll('.section-only-field').forEach(function (element) {
|
|
element.style.display = isSection ? '' : 'none';
|
|
});
|
|
|
|
wrapper.querySelectorAll('.range-config-field').forEach(function (element) {
|
|
element.style.display = showRange ? '' : 'none';
|
|
});
|
|
|
|
wrapper.querySelectorAll('.yes-no-config-field').forEach(function (element) {
|
|
element.style.display = showYesNoMaybe ? '' : 'none';
|
|
});
|
|
|
|
wrapper.querySelectorAll('.choice-config-field').forEach(function (element) {
|
|
element.style.display = showChoice ? '' : 'none';
|
|
});
|
|
};
|
|
|
|
select.addEventListener('change', toggle);
|
|
toggle();
|
|
});
|
|
|
|
document.querySelectorAll('[data-copy-text]').forEach(function (button) {
|
|
var originalLabel = button.getAttribute('data-copy-label') || button.textContent;
|
|
var successLabel = button.getAttribute('data-copy-success') || originalLabel;
|
|
|
|
var copyText = function (value) {
|
|
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
return navigator.clipboard.writeText(value);
|
|
}
|
|
|
|
return new Promise(function (resolve, reject) {
|
|
var helper = document.createElement('textarea');
|
|
helper.value = value;
|
|
helper.setAttribute('readonly', 'readonly');
|
|
helper.style.position = 'absolute';
|
|
helper.style.left = '-9999px';
|
|
document.body.appendChild(helper);
|
|
helper.select();
|
|
|
|
try {
|
|
document.execCommand('copy');
|
|
document.body.removeChild(helper);
|
|
resolve();
|
|
} catch (error) {
|
|
document.body.removeChild(helper);
|
|
reject(error);
|
|
}
|
|
});
|
|
};
|
|
|
|
button.addEventListener('click', function () {
|
|
var text = button.getAttribute('data-copy-text') || '';
|
|
|
|
if (!text) {
|
|
return;
|
|
}
|
|
|
|
copyText(text).then(function () {
|
|
button.textContent = successLabel;
|
|
window.setTimeout(function () {
|
|
button.textContent = originalLabel;
|
|
}, 1800);
|
|
}).catch(function () {
|
|
button.textContent = originalLabel;
|
|
});
|
|
});
|
|
});
|
|
|
|
var surveyDirtyChecks = [];
|
|
var surveyIsSubmitting = false;
|
|
|
|
document.querySelectorAll('[data-survey-dirty-form]').forEach(function (form) {
|
|
var formId = form.getAttribute('id') || '';
|
|
var hint = form.querySelector('[data-survey-dirty-hint]');
|
|
|
|
if (!hint && formId) {
|
|
hint = document.querySelector('[data-survey-dirty-hint-for="' + formId + '"]');
|
|
}
|
|
|
|
var serializeForm = function () {
|
|
var entries = [];
|
|
var formData = new FormData(form);
|
|
|
|
formData.forEach(function (value, key) {
|
|
entries.push(key + '=' + String(value));
|
|
});
|
|
|
|
return entries.join('&');
|
|
};
|
|
|
|
var initialState = serializeForm();
|
|
|
|
var isDirty = function () {
|
|
return serializeForm() !== initialState;
|
|
};
|
|
|
|
surveyDirtyChecks.push(isDirty);
|
|
|
|
var updateDirtyState = function () {
|
|
if (hint) {
|
|
hint.hidden = !isDirty();
|
|
}
|
|
};
|
|
|
|
if (hint) {
|
|
hint.hidden = true;
|
|
}
|
|
|
|
form.addEventListener('input', updateDirtyState);
|
|
form.addEventListener('change', updateDirtyState);
|
|
form.addEventListener('reset', function () {
|
|
window.setTimeout(function () {
|
|
if (hint) {
|
|
hint.hidden = true;
|
|
}
|
|
}, 0);
|
|
});
|
|
});
|
|
|
|
if (surveyDirtyChecks.length > 0) {
|
|
// Ein bewusstes Absenden (Speichern oder eine Aktion) darf nicht warnen.
|
|
document.addEventListener('submit', function () {
|
|
surveyIsSubmitting = true;
|
|
}, true);
|
|
|
|
window.addEventListener('beforeunload', function (event) {
|
|
if (surveyIsSubmitting) {
|
|
return undefined;
|
|
}
|
|
|
|
var hasUnsavedChanges = surveyDirtyChecks.some(function (isDirty) {
|
|
return isDirty();
|
|
});
|
|
|
|
if (!hasUnsavedChanges) {
|
|
return undefined;
|
|
}
|
|
|
|
// Moderne Browser zeigen einen generischen Hinweis; der Text wird
|
|
// ignoriert, muss aber gesetzt werden, damit der Dialog erscheint.
|
|
event.preventDefault();
|
|
event.returnValue = '';
|
|
|
|
return '';
|
|
});
|
|
}
|
|
|
|
if (typeof handorgel === 'function') {
|
|
document.querySelectorAll('[data-survey-handorgel]').forEach(function (element) {
|
|
var accordion = new handorgel(element, {
|
|
collapsible: true,
|
|
multiSelectable: false,
|
|
initialOpenTransition: true,
|
|
initialOpenTransitionDelay: 0
|
|
});
|
|
|
|
if (accordion && Array.isArray(accordion.folds)) {
|
|
accordion.folds.forEach(function (fold) {
|
|
if (!fold) {
|
|
return;
|
|
}
|
|
|
|
var article = fold.header ? fold.header.closest('article') : null;
|
|
var shouldOpen = !!(article && article.hasAttribute('data-survey-open'));
|
|
|
|
if (shouldOpen && !fold.expanded) {
|
|
fold.open(false);
|
|
} else if (!shouldOpen && fold.expanded) {
|
|
fold.close();
|
|
}
|
|
});
|
|
|
|
var openArticle = element.querySelector('article[data-survey-open]');
|
|
|
|
if (openArticle && typeof openArticle.scrollIntoView === 'function') {
|
|
window.setTimeout(function () {
|
|
openArticle.scrollIntoView({ block: 'start', behavior: 'smooth' });
|
|
}, 50);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
resetEditorViewport();
|
|
});
|