Add survey question drag-sort and backend assets

- Frontend editor: drag-to-sort questions with dedicated sort assets/JS
- Backend list/search refinements for tl_survey and tl_survey_content
- Repository helpers for survey/question/editor queries
- Translations and AI handover notes update
This commit is contained in:
Jürgen Mummert
2026-06-14 09:13:07 +02:00
parent 79c4402aee
commit 1b16675ae9
23 changed files with 928 additions and 40 deletions
@@ -708,6 +708,11 @@
margin: 0;
}
.survey-accordion-header-row {
display: flex;
align-items: stretch;
}
.survey-accordion-toggle {
display: flex;
width: 100%;
@@ -720,6 +725,82 @@
text-align: left;
}
.survey-accordion-sort-handle {
position: relative;
display: inline-flex;
flex: 0 0 auto;
min-width: 3.5rem;
padding: 0 1rem;
border: 0;
border-left: 1px solid rgba(162, 168, 180, 0.18);
background: transparent;
cursor: grab;
align-items: center;
justify-content: center;
align-self: stretch;
transition: background 0.2s ease, color 0.2s ease;
}
.survey-accordion-sort-handle:active {
cursor: grabbing;
}
.survey-accordion-item .handorgel__header__button:hover + .survey-accordion-sort-handle,
.survey-accordion-item .handorgel__header__button:focus + .survey-accordion-sort-handle,
.survey-accordion-item .handorgel__header--opened .survey-accordion-sort-handle,
.survey-accordion-sort-handle:hover,
.survey-accordion-sort-handle:focus {
background: var(--survey-blue);
color: #fff;
}
.survey-sort-icon {
display: inline-flex;
width: 0.95rem;
height: 1.28rem;
align-items: center;
justify-content: center;
color: #58a6da;
line-height: 0;
transform: translateY(1px);
}
.survey-sort-icon svg {
display: block;
width: 100%;
height: 100%;
transform: translateY(4px);
}
.survey-question-order-save-shell {
position: fixed;
top: 50%;
right: 1rem;
z-index: 40;
transform: translate(120%, -50%);
opacity: 0;
pointer-events: none;
transition: transform 0.26s ease, opacity 0.26s ease;
}
.survey-question-order-save-shell.is-visible {
transform: translate(0, -50%);
opacity: 1;
pointer-events: auto;
}
.survey-question-order-save {
box-shadow: 0 18px 40px rgba(0, 59, 102, 0.24);
}
.survey-accordion-item.is-reordering {
box-shadow: 0 20px 44px rgba(0, 59, 102, 0.18);
}
.survey-accordion-item.sortable-ghost {
opacity: 0.55;
}
.survey-accordion-item .handorgel__header__button,
.survey-accordion-item .handorgel__header__button:hover,
.survey-accordion-item .handorgel__header__button:focus {
@@ -994,6 +1075,22 @@
padding: 0.95rem 1rem;
}
.survey-accordion-sort-handle {
min-width: 3.1rem;
padding: 0 0.8rem;
}
.survey-question-order-save-shell {
top: auto;
right: 0.85rem;
bottom: 0.85rem;
transform: translate(0, 130%);
}
.survey-question-order-save-shell.is-visible {
transform: translate(0, 0);
}
}
</style>
<script>
@@ -1152,6 +1249,133 @@
});
});
var initializeQuestionSorting = function () {
var styleId = 'survey-question-sort-icon-runtime';
if (!document.getElementById(styleId)) {
var runtimeStyle = document.createElement('style');
runtimeStyle.id = styleId;
runtimeStyle.textContent = '' +
'.survey-sort-icon{' +
'width:1.14rem !important;' +
'height:1.52rem !important;' +
'color:#58a6da !important;' +
'background:none !important;' +
'transform:translateY(1px) !important;' +
'}' +
'.survey-sort-icon::before,.survey-sort-icon::after{' +
'content:none !important;' +
'}' +
'.survey-sort-icon svg{' +
'display:block !important;' +
'width:100% !important;' +
'height:100% !important;' +
'transform:translateY(4px);' +
'}' +
'.survey-sort-icon svg path:first-child{' +
'transform:translateY(1.4px);' +
'transform-box:fill-box;' +
'transform-origin:center;' +
'}' +
'.survey-sort-icon svg path:nth-child(2){' +
'transform:translateY(-1.4px);' +
'transform-box:fill-box;' +
'transform-origin:center;' +
'}' +
'.survey-sort-icon svg path[stroke]{' +
'stroke-width:2px;' +
'}';
document.head.appendChild(runtimeStyle);
}
if (typeof Sortable !== 'function') {
return;
}
var sortableList = document.querySelector('[data-survey-question-sortable]');
var orderForm = document.querySelector('[data-survey-question-order-shell]');
var orderInput = document.querySelector('[data-survey-question-order-input]');
if (!sortableList || !orderForm || !orderInput) {
return;
}
if (sortableList._surveyQuestionSortable) {
return;
}
var getOrderedIds = function () {
return Array.from(sortableList.querySelectorAll('[data-survey-question-item]')).map(function (item) {
return item.getAttribute('data-question-id') || '';
}).filter(function (value) {
return value !== '';
});
};
var initialOrder = getOrderedIds().join(',');
var updateOrderField = function () {
orderInput.value = getOrderedIds().join(',');
};
var updateQuestionIndices = function () {
sortableList.querySelectorAll('[data-survey-question-item]').forEach(function (item, index) {
var indexNode = item.querySelector('.survey-accordion-index');
if (indexNode) {
indexNode.textContent = (index + 1) + '.';
}
});
};
var updateSaveVisibility = function () {
var currentOrder = getOrderedIds().join(',');
var isDirty = currentOrder !== initialOrder;
orderForm.hidden = !isDirty;
orderForm.classList.toggle('is-visible', isDirty);
updateOrderField();
};
sortableList.querySelectorAll('[data-survey-question-sort-handle]').forEach(function (handle) {
handle.addEventListener('click', function (event) {
event.preventDefault();
event.stopPropagation();
});
});
updateQuestionIndices();
updateOrderField();
updateSaveVisibility();
var sortableInstance = Sortable.create(sortableList, {
animation: 180,
draggable: '[data-survey-question-item]',
handle: '[data-survey-question-sort-handle]',
ghostClass: 'sortable-ghost',
forceFallback: true,
fallbackTolerance: 3,
onStart: function (event) {
if (event.item) {
event.item.classList.add('is-reordering');
}
},
onEnd: function (event) {
if (event.item) {
event.item.classList.remove('is-reordering');
}
updateQuestionIndices();
updateSaveVisibility();
}
});
sortableList._surveyQuestionSortable = sortableInstance;
};
window.initializeSurveyQuestionSorting = initializeQuestionSorting;
if (typeof handorgel === 'function') {
document.querySelectorAll('[data-survey-handorgel]').forEach(function (element) {
var accordion = new handorgel(element, {
@@ -1171,6 +1395,8 @@
});
}
initializeQuestionSorting();
resetEditorViewport();
});
</script>