Megamenu: rail display for page trees, cache tags, editorial promo boxes
- Page-tree entries get a "Page tree display" field (columns | rail |
rail-compact), emitted as display + cardSize: Markets and Solutions
show their main pages as a list and the sub-pages as image cards on
hover. Page-tree items carry image (page media) and teaser (abstract)
from stage 2. Additive; front ends ignoring display render columns.
- vitec:attach-page-media gains --depth=2 and falls back to the page's
hero background before the grey placeholder; the 39 sub-solution
pages got their hero image as page media.
- MegamenuJsonRenderer tags every page carrying the menu with the menu
tables and tt_content_<uid>, so saving menu records refreshes all
pages instead of waiting for a full flush.
- Products catalog: drop catalog.news; both promo boxes ("Product
highlight", "Product news") are the entry's editorial teaser cards.
ProvideInsightsSpotlight back to its previous state.
- Spec v1.29 and v1.30 (header version corrected), README.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
9fb7f0ca77
commit
9211c8a395
10 files changed
+171
-37
No files matched your search
@@ -136,6 +136,19 @@ final class MenuBuilder
|
||||
'columns' => $columns,
|
||||
];
|
||||
|
||||
// Page-tree display (2026-11-02): same columns, two presentations -
|
||||
// `columns` side by side, or `rail`: the columns as a list on the
|
||||
// left, the hovered column's items as image cards on the right
|
||||
// (`cardSize` regular|compact). Additive: a front end that does not
|
||||
// know `display` keeps rendering columns.
|
||||
if ($layout === 'pagetree') {
|
||||
$display = (string)($entry['tree_display'] ?? 'columns');
|
||||
$built['display'] = str_starts_with($display, 'rail') ? 'rail' : 'columns';
|
||||
if ($built['display'] === 'rail') {
|
||||
$built['cardSize'] = $display === 'rail-compact' ? 'compact' : 'regular';
|
||||
}
|
||||
}
|
||||
|
||||
// Catalog panels: the teaser cards serve as the featured tiles (left
|
||||
// column), the catalog data itself comes from a listener of
|
||||
// BuildCatalogEvent - this extension does not know the data source.
|
||||
@@ -229,10 +242,20 @@ final class MenuBuilder
|
||||
foreach ($this->pageRepository->getMenu($rootUid, '*', 'sorting', self::NAV_WHERE) as $child) {
|
||||
$items = [];
|
||||
foreach ($this->pageRepository->getMenu((int)$child['uid'], '*', 'sorting', self::NAV_WHERE) as $grandchild) {
|
||||
$items[] = [
|
||||
$item = [
|
||||
'title' => (string)(($grandchild['nav_title'] ?? '') !== '' ? $grandchild['nav_title'] : $grandchild['title']),
|
||||
'link' => $this->url('t3://page?uid=' . (int)$grandchild['uid'], $cObj),
|
||||
];
|
||||
// card data for the `rail` display (2026-11-02), same rules
|
||||
// as the spotlight nav: abstract as teaser, page media image
|
||||
if ($stage >= 2) {
|
||||
$teaser = trim((string)($grandchild['abstract'] ?? ''));
|
||||
if ($teaser !== '') {
|
||||
$item['teaser'] = $teaser;
|
||||
}
|
||||
$item['image'] = $this->image('pages', 'media', (int)$grandchild['uid']);
|
||||
}
|
||||
$items[] = $item;
|
||||
}
|
||||
$column = [
|
||||
'title' => (string)(($child['nav_title'] ?? '') !== '' ? $child['nav_title'] : $child['title']),
|
||||
|
||||
@@ -7,6 +7,8 @@ namespace Evomedien\EvoMegamenuJson\UserFunc;
|
||||
use Doctrine\DBAL\ParameterType;
|
||||
use Evomedien\EvoMegamenuJson\Service\MenuBuilder;
|
||||
use TYPO3\CMS\Core\Attribute\AsAllowedCallable;
|
||||
use TYPO3\CMS\Core\Cache\CacheDataCollectorInterface;
|
||||
use TYPO3\CMS\Core\Cache\CacheTag;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Database\Query\Restriction\FrontendRestrictionContainer;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
@@ -24,10 +26,24 @@ use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
|
||||
* TYPO3 v14 hands the ContentObjectRenderer in through the setter rather
|
||||
* than the constructor; without it $this->cObj stays null and the element
|
||||
* path cannot see which record it is rendering.
|
||||
*
|
||||
* Cache (2026-11-02): the menu is part of every cached page, but its records
|
||||
* live on a storage folder - saving them cleared only that folder, and every
|
||||
* page cached before kept the old menu until a full flush. The page is
|
||||
* therefore tagged with the menu tables; the DataHandler flushes the tags
|
||||
* `<table>` and `<table>_<uid>` of every record it writes, so any save in the
|
||||
* menu refreshes all pages carrying it. Data the panels read from the page
|
||||
* tree (pagetree, catalog, spotlight) is not covered and still needs a flush.
|
||||
*/
|
||||
final class MegamenuJsonRenderer
|
||||
{
|
||||
private const CTYPE = 'evomegamenujson_megamenu';
|
||||
private const RECORD_TABLES = [
|
||||
'tx_evomegamenujson_entry',
|
||||
'tx_evomegamenujson_column',
|
||||
'tx_evomegamenujson_item',
|
||||
'tx_evomegamenujson_teaser',
|
||||
];
|
||||
|
||||
protected ?ContentObjectRenderer $cObj = null;
|
||||
|
||||
@@ -63,6 +79,7 @@ final class MegamenuJsonRenderer
|
||||
}
|
||||
|
||||
$menu = GeneralUtility::makeInstance(MenuBuilder::class)->build($uid, $cObj, $row);
|
||||
$this->tagPageCache($cObj, $uid);
|
||||
|
||||
return (string)json_encode($menu, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
||||
} catch (\Throwable) {
|
||||
@@ -71,6 +88,21 @@ final class MegamenuJsonRenderer
|
||||
}
|
||||
}
|
||||
|
||||
/** Tags the page cache with the menu element and tables (see class comment). */
|
||||
private function tagPageCache(ContentObjectRenderer $cObj, int $contentUid): void
|
||||
{
|
||||
try {
|
||||
$collector = $cObj->getRequest()->getAttribute('frontend.cache.collector');
|
||||
if (!$collector instanceof CacheDataCollectorInterface) {
|
||||
return;
|
||||
}
|
||||
$tags = array_map(static fn(string $table): CacheTag => new CacheTag($table), self::RECORD_TABLES);
|
||||
$collector->addCacheTags(new CacheTag('tt_content_' . $contentUid), ...$tags);
|
||||
} catch (\Throwable) {
|
||||
// without tags the menu still renders, it just ages until a flush
|
||||
}
|
||||
}
|
||||
|
||||
/** uid of the first megamenu element on a storage folder, 0 when none. */
|
||||
private function firstElementOn(int $pid): int
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user