188 lines
5.9 KiB
JavaScript
188 lines
5.9 KiB
JavaScript
/**
|
|
* VITEC — collapse/expand toggle for container content elements in the
|
|
* TYPO3 v14 Page module.
|
|
*
|
|
* Adds a chevron button into the `.btn-toolbar` of every VITEC container
|
|
* (vitec_container, vitec_cols_*, vitec_cards_carousel — anything that
|
|
* renders a nested b13 grid). Clicking it hides/shows the container body
|
|
* (the nested child columns). State is persisted per element uid in
|
|
* localStorage, so it survives reloads.
|
|
*
|
|
* Pure DOM enhancement — no core or b13 template overrides.
|
|
*/
|
|
class VitecContainerCollapse {
|
|
constructor() {
|
|
this.storagePrefix = 'vitec-ce-collapsed-';
|
|
this.ready();
|
|
}
|
|
|
|
ready() {
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', () => this.boot());
|
|
} else {
|
|
this.boot();
|
|
}
|
|
}
|
|
|
|
boot() {
|
|
this.processAll();
|
|
// Page module can re-render columns via AJAX; watch for new elements.
|
|
const root = document.querySelector('.t3js-page-columns-wrapper, .module, body');
|
|
if (root) {
|
|
const observer = new MutationObserver(() => {
|
|
window.clearTimeout(this._debounce);
|
|
this._debounce = window.setTimeout(() => this.processAll(), 150);
|
|
});
|
|
observer.observe(root, { childList: true, subtree: true });
|
|
}
|
|
}
|
|
|
|
processAll() {
|
|
document.querySelectorAll('.t3-page-ce[data-uid]').forEach((ce) => this.enhance(ce));
|
|
}
|
|
|
|
isCollapsed(uid) {
|
|
try {
|
|
return window.localStorage.getItem(this.storagePrefix + uid) === '1';
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
setCollapsed(uid, collapsed) {
|
|
try {
|
|
if (collapsed) {
|
|
window.localStorage.setItem(this.storagePrefix + uid, '1');
|
|
} else {
|
|
window.localStorage.removeItem(this.storagePrefix + uid);
|
|
}
|
|
} catch (e) {
|
|
/* storage unavailable — ignore */
|
|
}
|
|
}
|
|
|
|
enhance(ce) {
|
|
if (ce.dataset.vitecCollapseReady === '1') {
|
|
return;
|
|
}
|
|
|
|
// The direct body of THIS element (not a nested child's body).
|
|
const element = ce.querySelector(':scope > .t3-page-ce-element');
|
|
if (!element) {
|
|
return;
|
|
}
|
|
const body = element.querySelector(':scope > .t3-page-ce-body');
|
|
if (!body) {
|
|
return;
|
|
}
|
|
|
|
// A container is recognised by a nested b13 grid inside its body.
|
|
const hasNestedGrid = body.querySelector('.t3-grid-container') !== null;
|
|
if (!hasNestedGrid) {
|
|
return;
|
|
}
|
|
|
|
// Best-effort VITEC filter via the header icon identifier
|
|
// (b13 sets the icon identifier to the CType, e.g. "vitec_container").
|
|
const iconEl = ce.querySelector(
|
|
'.t3-page-ce-header-left typo3-backend-icon[identifier], .t3-page-ce-header-left [data-identifier]'
|
|
);
|
|
if (iconEl) {
|
|
const identifier =
|
|
iconEl.getAttribute('identifier') || iconEl.getAttribute('data-identifier') || '';
|
|
if (identifier && identifier.indexOf('vitec') === -1) {
|
|
return; // a non-VITEC container — leave it alone
|
|
}
|
|
}
|
|
|
|
const toolbar = element.querySelector(
|
|
':scope > .t3-page-ce-header .btn-toolbar .btn-group'
|
|
);
|
|
if (!toolbar) {
|
|
return;
|
|
}
|
|
|
|
ce.dataset.vitecCollapseReady = '1';
|
|
const uid = ce.dataset.uid;
|
|
|
|
const button = document.createElement('button');
|
|
button.type = 'button';
|
|
button.className = 'btn btn-sm btn-default btn-borderless vitec-collapse-toggle';
|
|
button.title = 'Collapse / expand container';
|
|
button.setAttribute('aria-label', 'Collapse or expand container');
|
|
button.innerHTML =
|
|
'<typo3-backend-icon identifier="actions-chevron-up" size="small"></typo3-backend-icon>';
|
|
|
|
toolbar.insertBefore(button, toolbar.firstChild);
|
|
|
|
if (this.isCollapsed(uid)) {
|
|
ce.classList.add('vitec-ce-collapsed');
|
|
}
|
|
this.syncPreviewHeader(ce);
|
|
|
|
button.addEventListener('click', (event) => {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
const collapsed = ce.classList.toggle('vitec-ce-collapsed');
|
|
this.setCollapsed(uid, collapsed);
|
|
this.syncPreviewHeader(ce);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Keep the element's own `.element-preview-header` visible while collapsed:
|
|
* it is moved into the CE header row (after `.t3-page-ce-header-right`) and
|
|
* moved back to its original spot (placeholder marker) on expand.
|
|
*/
|
|
syncPreviewHeader(ce) {
|
|
const element = ce.querySelector(':scope > .t3-page-ce-element');
|
|
if (!element) {
|
|
return;
|
|
}
|
|
const header = element.querySelector(':scope > .t3-page-ce-header');
|
|
const body = element.querySelector(':scope > .t3-page-ce-body');
|
|
if (!header || !body) {
|
|
return;
|
|
}
|
|
|
|
const collapsed = ce.classList.contains('vitec-ce-collapsed');
|
|
|
|
if (collapsed) {
|
|
// Find THIS element's preview header (not one of a nested child CE).
|
|
let preview = null;
|
|
for (const candidate of body.querySelectorAll('.element-preview-header')) {
|
|
if (candidate.closest('.t3-page-ce') === ce) {
|
|
preview = candidate;
|
|
break;
|
|
}
|
|
}
|
|
if (!preview || header.contains(preview)) {
|
|
return;
|
|
}
|
|
const placeholder = document.createElement('span');
|
|
placeholder.className = 'vitec-preview-header-placeholder';
|
|
placeholder.hidden = true;
|
|
preview.before(placeholder);
|
|
preview.classList.add('vitec-preview-header--in-header');
|
|
const headerRight = header.querySelector('.t3-page-ce-header-right');
|
|
if (headerRight) {
|
|
headerRight.insertAdjacentElement('afterend', preview);
|
|
} else {
|
|
header.appendChild(preview);
|
|
}
|
|
} else {
|
|
const preview = header.querySelector('.element-preview-header.vitec-preview-header--in-header');
|
|
const placeholder = body.querySelector('.vitec-preview-header-placeholder');
|
|
if (preview && placeholder) {
|
|
preview.classList.remove('vitec-preview-header--in-header');
|
|
placeholder.replaceWith(preview);
|
|
} else if (preview) {
|
|
preview.classList.remove('vitec-preview-header--in-header');
|
|
body.prepend(preview);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
export default new VitecContainerCollapse();
|