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
+169
-35
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
|
||||
{
|
||||
|
||||
@@ -43,7 +43,7 @@ return [
|
||||
'1' => [
|
||||
'showitem' => '
|
||||
--div--;' . $ll . 'tab.general,
|
||||
hidden, title, link, layout,
|
||||
hidden, title, link, layout, tree_display,
|
||||
--div--;' . $ll . 'tab.columns,
|
||||
menu_columns,
|
||||
--div--;' . $ll . 'tab.teasers,
|
||||
@@ -115,6 +115,22 @@ return [
|
||||
],
|
||||
],
|
||||
],
|
||||
'tree_display' => [
|
||||
// page-tree panels only: same data, two presentations
|
||||
'displayCond' => 'FIELD:layout:=:pagetree',
|
||||
'label' => $ll . 'tree_display',
|
||||
'description' => $ll . 'tree_display.description',
|
||||
'config' => [
|
||||
'type' => 'select',
|
||||
'renderType' => 'selectSingle',
|
||||
'default' => 'columns',
|
||||
'items' => [
|
||||
['label' => $ll . 'tree_display.columns', 'value' => 'columns'],
|
||||
['label' => $ll . 'tree_display.rail', 'value' => 'rail'],
|
||||
['label' => $ll . 'tree_display.rail-compact', 'value' => 'rail-compact'],
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
'menu_columns' => [
|
||||
'label' => $ll . 'menu_columns',
|
||||
|
||||
@@ -125,7 +125,15 @@ page, its children as the items. Hidden or nav-hidden pages leave the menu
|
||||
with the next cache flush. Curated columns are ignored; teasers and the
|
||||
featured slot stay editorial. From stage 2 a child page with media gets its
|
||||
first image as `image` on the column - attach images in the page
|
||||
properties and the panel turns visual by itself.
|
||||
properties and the panel turns visual by itself. From stage 2 the items
|
||||
carry `image` (first page media or `null`) and, when set, the page
|
||||
`abstract` as `teaser`.
|
||||
|
||||
The entry field *Page tree display* picks the presentation and travels as
|
||||
`display`: `columns` (default, 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) plus `cardSize` (`regular` | `compact`) for `rail`. A front end that
|
||||
ignores `display` keeps rendering columns.
|
||||
|
||||
### Layout `spotlight`
|
||||
|
||||
@@ -163,5 +171,9 @@ The VITEC site package ships `ProvideInsightsSpotlight`, which supplies
|
||||
entry.
|
||||
* The renderer never throws: a broken menu returns an empty value rather than
|
||||
taking the page payload down.
|
||||
* Caching: every page carrying the menu is tagged with the menu tables and
|
||||
`tt_content_<uid>` of the menu element, so saving any menu record refreshes
|
||||
all of them. Panels built from the page tree (`pagetree`, `catalog`,
|
||||
`spotlight`) pick up page changes with the next cache flush.
|
||||
* Without `friendsoftypo3/headless` the element has no JSON envelope to render
|
||||
into — the extension is built for headless installations.
|
||||
@@ -27,6 +27,11 @@
|
||||
<trans-unit id="tx_evomegamenujson_entry.layout.catalog"><source>Catalog (data-driven: categories and items from the site package, teaser cards as featured tiles)</source></trans-unit>
|
||||
<trans-unit id="tx_evomegamenujson_entry.layout.pagetree"><source>Page tree (data-driven: one column per child of the linked page, its children as items; curated columns are ignored)</source></trans-unit>
|
||||
<trans-unit id="tx_evomegamenujson_entry.layout.spotlight"><source>Spotlight (data-driven: left-rail nav from the linked page's children plus live visual cards - latest news, next events - supplied by the site package; curated columns are ignored)</source></trans-unit>
|
||||
<trans-unit id="tx_evomegamenujson_entry.tree_display"><source>Page tree display</source></trans-unit>
|
||||
<trans-unit id="tx_evomegamenujson_entry.tree_display.description"><source>Columns: one column per child page with its children as links. List + cards: the child pages as a list on the left, the children of the hovered one as image cards on the right (images from the page media).</source></trans-unit>
|
||||
<trans-unit id="tx_evomegamenujson_entry.tree_display.columns"><source>Columns side by side</source></trans-unit>
|
||||
<trans-unit id="tx_evomegamenujson_entry.tree_display.rail"><source>List on the left, cards on hover</source></trans-unit>
|
||||
<trans-unit id="tx_evomegamenujson_entry.tree_display.rail-compact"><source>List on the left, small cards on hover (for many sub-pages)</source></trans-unit>
|
||||
<trans-unit id="tx_evomegamenujson_entry.menu_columns"><source>Columns</source></trans-unit>
|
||||
<trans-unit id="tx_evomegamenujson_entry.menu_columns.description"><source>One column per group. In the teaser layout only the first column is rendered, next to the cards.</source></trans-unit>
|
||||
<trans-unit id="tx_evomegamenujson_entry.menu_teasers"><source>Teaser cards</source></trans-unit>
|
||||
|
||||
@@ -8,6 +8,7 @@ CREATE TABLE tx_evomegamenujson_entry (
|
||||
title varchar(255) DEFAULT '' NOT NULL,
|
||||
link varchar(1024) DEFAULT '' NOT NULL,
|
||||
layout varchar(30) DEFAULT 'columns' NOT NULL,
|
||||
tree_display varchar(30) DEFAULT 'columns' NOT NULL,
|
||||
menu_columns int(11) DEFAULT '0' NOT NULL,
|
||||
menu_teasers int(11) DEFAULT '0' NOT NULL,
|
||||
featured_enable tinyint(1) unsigned DEFAULT '0' NOT NULL,
|
||||
|
||||
@@ -24,12 +24,17 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
* Image per page: the numbered placeholder
|
||||
* `fileadmin/placeholders/pages/<pid>-08.png` when it exists (slot 08 =
|
||||
* page media / menu tile; the number in the picture doubles as the image
|
||||
* wish list towards the agency), otherwise the generic wide grey
|
||||
* placeholder. A page that already has a media reference is NEVER touched -
|
||||
* editors swap the placeholder for the real image in the page properties.
|
||||
* wish list towards the agency), else the page's own hero background
|
||||
* (`vitec_bgimage`, same file referenced - 2026-11-02, for the sub-market /
|
||||
* sub-solution cards of the megamenu `rail` display), otherwise the generic
|
||||
* wide grey placeholder. A page that already has a media reference is NEVER
|
||||
* touched - editors swap the image in the page properties.
|
||||
*
|
||||
* --depth=2 targets the grandchildren (e.g. the sub-markets below
|
||||
* /markets/<market>) instead of the children.
|
||||
*
|
||||
* vendor/bin/typo3 vitec:attach-page-media --root=/support
|
||||
* vendor/bin/typo3 vitec:attach-page-media --root=/support --apply
|
||||
* vendor/bin/typo3 vitec:attach-page-media --root=/markets --depth=2 --apply
|
||||
*/
|
||||
#[AsCommand(
|
||||
name: 'vitec:attach-page-media',
|
||||
@@ -40,6 +45,7 @@ final class AttachPageMediaCommand extends Command
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->addOption('root', null, InputOption::VALUE_REQUIRED, 'Slug of the root page, e.g. /support');
|
||||
$this->addOption('depth', null, InputOption::VALUE_REQUIRED, '1 = children of the root, 2 = their children', '1');
|
||||
$this->addOption('apply', null, InputOption::VALUE_NONE, 'Write changes (default: dry run)');
|
||||
}
|
||||
|
||||
@@ -56,11 +62,15 @@ final class AttachPageMediaCommand extends Command
|
||||
$output->writeln('<error>Root page ' . $rootSlug . ' not found.</error>');
|
||||
return Command::FAILURE;
|
||||
}
|
||||
$depth = max(1, min(2, (int)$input->getOption('depth')));
|
||||
$parents = [(int)$root['uid']];
|
||||
for ($level = 1; $level <= $depth; $level++) {
|
||||
$children = $pages->fetchAllAssociative(
|
||||
'SELECT uid, title FROM pages WHERE deleted = 0 AND hidden = 0 AND doktype = 1 AND pid = ? AND sys_language_uid IN (0,-1) ORDER BY sorting',
|
||||
[(int)$root['uid']]
|
||||
'SELECT uid, pid, title FROM pages WHERE deleted = 0 AND hidden = 0 AND doktype = 1 AND pid IN (' . implode(',', $parents ?: [0]) . ') AND sys_language_uid IN (0,-1) ORDER BY pid, sorting'
|
||||
);
|
||||
$output->writeln(($apply ? 'APPLY' : 'DRY-RUN') . sprintf(' · %s #%d · %d child page(s)', $rootSlug, (int)$root['uid'], count($children)));
|
||||
$parents = array_map('intval', array_column($children, 'uid'));
|
||||
}
|
||||
$output->writeln(($apply ? 'APPLY' : 'DRY-RUN') . sprintf(' · %s #%d · depth %d · %d page(s)', $rootSlug, (int)$root['uid'], $depth, count($children)));
|
||||
|
||||
$storage = GeneralUtility::makeInstance(StorageRepository::class)->getDefaultStorage();
|
||||
$refs = $pool->getConnectionForTable('sys_file_reference');
|
||||
@@ -76,10 +86,19 @@ final class AttachPageMediaCommand extends Command
|
||||
$output->writeln(sprintf(' = #%d "%s": has media (%d) - untouched', $uid, (string)$child['title'], $existing));
|
||||
continue;
|
||||
}
|
||||
$fileUid = 0;
|
||||
$identifier = 'placeholders/pages/' . $uid . '-08.png';
|
||||
if (!$storage->hasFile($identifier)) {
|
||||
$identifier = 'placeholders/placeholder-grey-wide.png';
|
||||
if (!$storage->hasFile($identifier)) {
|
||||
// the page's own hero background, first hero on the page
|
||||
$fileUid = (int)$refs->fetchOne(
|
||||
"SELECT r.uid_local FROM sys_file_reference r JOIN tt_content c ON c.uid = r.uid_foreign
|
||||
WHERE r.deleted = 0 AND r.tablenames = 'tt_content' AND r.fieldname = 'vitec_bgimage'
|
||||
AND c.deleted = 0 AND c.hidden = 0 AND c.pid = ?
|
||||
ORDER BY c.colPos, c.sorting, r.sorting_foreign LIMIT 1",
|
||||
[$uid]
|
||||
);
|
||||
$identifier = $fileUid > 0 ? 'hero image (sys_file #' . $fileUid . ')' : 'placeholders/placeholder-grey-wide.png';
|
||||
if ($fileUid === 0 && !$storage->hasFile($identifier)) {
|
||||
$output->writeln(sprintf(' <comment>! #%d "%s": no placeholder file - skipped</comment>', $uid, (string)$child['title']));
|
||||
continue;
|
||||
}
|
||||
@@ -88,11 +107,13 @@ final class AttachPageMediaCommand extends Command
|
||||
if (!$apply) {
|
||||
continue;
|
||||
}
|
||||
$file = $storage->getFile($identifier);
|
||||
if ($fileUid === 0) {
|
||||
$fileUid = $storage->getFile($identifier)->getUid();
|
||||
}
|
||||
$datamap['sys_file_reference']['NEWPM' . (++$newIndex)] = [
|
||||
'pid' => $uid,
|
||||
'table_local' => 'sys_file',
|
||||
'uid_local' => $file->getUid(),
|
||||
'uid_local' => $fileUid,
|
||||
'tablenames' => 'pages',
|
||||
'uid_foreign' => $uid,
|
||||
'fieldname' => 'media',
|
||||
|
||||
@@ -50,13 +50,8 @@ final class ProvideInsightsSpotlight
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Public since 2026-11-02: the products catalog panel shows the same
|
||||
* news cards ("Product news" slot) - one query, one card shape.
|
||||
*
|
||||
* @return array<int,array<string,mixed>>
|
||||
*/
|
||||
public function latestNews(int $stage, int $count = self::NEWS_COUNT): array
|
||||
/** @return array<int,array<string,mixed>> */
|
||||
private function latestNews(int $stage): array
|
||||
{
|
||||
$queryBuilder = $this->connectionPool->getQueryBuilderForTable(self::NEWS_TABLE);
|
||||
$queryBuilder->setRestrictions(GeneralUtility::makeInstance(FrontendRestrictionContainer::class));
|
||||
@@ -68,7 +63,7 @@ final class ProvideInsightsSpotlight
|
||||
$queryBuilder->expr()->eq('type', $queryBuilder->createNamedParameter('0'))
|
||||
)
|
||||
->orderBy('datetime', 'DESC')
|
||||
->setMaxResults($count)
|
||||
->setMaxResults(self::NEWS_COUNT)
|
||||
->executeQuery()->fetchAllAssociative();
|
||||
} catch (\Throwable) {
|
||||
return [];
|
||||
|
||||
@@ -29,9 +29,10 @@ use TYPO3\CMS\Extbase\Service\ImageService;
|
||||
* pages the visitor reaches from the overview page; the menu does not show
|
||||
* them, the count and the list serve the "N sub-products" line and search.
|
||||
*
|
||||
* Next to the tree: viewAll (/products), news (the latest news cards,
|
||||
* shared with the Insights spotlight) and the type-ahead endpoint. The
|
||||
* entry's teaser cards stay the editorial "Product highlight".
|
||||
* Next to the tree: viewAll (/products) and the type-ahead endpoint. The
|
||||
* boxes of the left column ("Product highlight", "Product news") are the
|
||||
* entry's editorial teaser cards, not data from here (decision Olli
|
||||
* 2026-11-02: no automatic news).
|
||||
*
|
||||
* Version 1 (2026-10-09) listed sys_category groups with their products;
|
||||
* the nine old category pages are plain pages below /products and are not
|
||||
@@ -45,12 +46,10 @@ final class ProvideProductCatalog
|
||||
private const DOKTYPE_PAGE = 1;
|
||||
private const DOKTYPE_SHORTCUT = 4;
|
||||
private const DOKTYPE_FOLDER = 254;
|
||||
private const NEWS_COUNT = 2;
|
||||
|
||||
public function __construct(
|
||||
private readonly ConnectionPool $connectionPool,
|
||||
private readonly FileRepository $fileRepository,
|
||||
private readonly ProvideInsightsSpotlight $spotlight,
|
||||
) {}
|
||||
|
||||
public function __invoke(BuildCatalogEvent $event): void
|
||||
@@ -66,7 +65,6 @@ final class ProvideProductCatalog
|
||||
'placeholder' => 'Search products',
|
||||
],
|
||||
'viewAll' => ['title' => 'View all products', 'link' => self::PRODUCTS_SLUG],
|
||||
'news' => $this->spotlight->latestNews($stage, self::NEWS_COUNT),
|
||||
'categories' => $this->categories($stage),
|
||||
]);
|
||||
} catch (\Throwable) {
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
| | |
|
||||
|---|---|
|
||||
| **Document identifier** | EVO‑VITEC‑HL‑001 |
|
||||
| **Version** | 1.26 |
|
||||
| **Version** | 1.30 |
|
||||
| **Status** | Released |
|
||||
| **Date** | 2026‑10‑26 |
|
||||
| **Date** | 2026‑11‑02 |
|
||||
| **Applies to** | `evomedien/vitec` on TYPO3 v14.3 (headless) |
|
||||
| **Owner** | evomedien — VITEC relaunch |
|
||||
|
||||
@@ -41,6 +41,8 @@
|
||||
| 1.26 | 2026‑10‑26 | **Interface change (additive).** Product detail resources band simplified (customer CR): the `resources` envelope gains **`sectionLayout`** — `50-50` (default) \| `33-33-33` — from the new per‑product select *Resources Section Layout* (Layout 1: Technical Highlights left, resource cards right, 50/50; Layout 2: highlights over two columns, cards in the third). Capabilities leave the band editorially; the TCA fields `capabilities`, `datasheets` and `downloads` are parked in the Misc tab — all three stay in the payload unchanged. The automatic newest‑datasheet/documentation cards remain deferred; meanwhile the manual card `restype` vocabulary gains `datasheet` and `brochure` — a pinned datasheet card links the download record and stays current because the download route always serves the newest file. |
|
||||
| 1.27 | 2026‑11‑02 | **Interface change (additive).** Product *Resources Section Layout* gained the value `hardware` (“Layout 3 - Hardware option”): `resources.sectionLayout = "hardware"` — Technical Highlights over two columns, no resource cards (`resources.items` is emitted empty even when cards are maintained, so they reappear on a switch back). |
|
||||
| 1.28 | 2026‑11‑02 | **Interface change (breaking for the products menu, additive elsewhere).** Product section rebuilt after the agency sitemap/menu prototype of 2026-11-02: 20 product overview pages `/products/<slug>` grouped by sysfolders (3 categories → 9 groups) below /products. The megamenu `catalog` payload is now **version 2**: `categories[].groups[].items[]` of type `page` (overview page with `productCount`/`products`/`image`) or `shortcut` (`target` indices), plus `viewAll` and `news`; the menu no longer lists products. Transitional v1 fields (`groups[].products` etc.) stay until the front end has switched. The product type-ahead (`?type=7385`) gained `overviews` and a per-product `overview` with menu path. |
|
||||
| 1.29 | 2026‑11‑02 | **Interface change (removal within the unreleased catalog v2).** `catalog.news` dropped (decision: no automatic news in the products menu). Both promo boxes of the products panel, "Product highlight" and "Product news", are the entry's editorial `teasers` (`kicker` = box label). Megamenu caching: pages carrying the menu are now tagged with the menu tables, so saving menu records refreshes every page; data read from the page tree still needs a cache flush. |
|
||||
| 1.30 | 2026‑11‑02 | **Interface change (additive).** Megamenu `pagetree` entries carry `display` (`columns` \| `rail`) and, for `rail`, `cardSize` (`regular` \| `compact`): Markets and Solutions switch to a left list with the sub-pages as image cards on hover. Page-tree items gained `image` (page media) and `teaser` (abstract) from stage 2; the sub-solution pages received their hero background as page media (sub-markets had page media already). |
|
||||
|
||||
This document is drafted in the style of, and adopts the terminology conventions of,
|
||||
ISO/IEC/IEEE 42010 (architecture description), ISO/IEC/IEEE 26514 (information for
|
||||
@@ -1220,7 +1222,6 @@ pages are reached from there.
|
||||
"version": 2,
|
||||
"search": { "endpoint": "/_json/?type=7385", "param": "q", "minChars": 2, "placeholder": "Search products" },
|
||||
"viewAll": { "title": "View all products", "link": "/products" },
|
||||
"news": [ { "title": "…", "date": "2026-10-28", "link": "/news/article/…", "teaser": "…", "image": {} } ],
|
||||
"categories": [ { "title": "IP Video Streaming, Distribution & KVM",
|
||||
"groups": [ { "title": "Encoders & Decoders",
|
||||
"items": [
|
||||
@@ -1241,9 +1242,11 @@ pages are reached from there.
|
||||
their number - a card without a resolvable product is not counted.
|
||||
- `image` (from content stage 2) is the overview page's first page media as a
|
||||
640 px WebP, `null` when none is set.
|
||||
- `news` are the latest news cards (same shape and query as the Insights
|
||||
spotlight, here two); the entry's `teasers` remain the editorial product
|
||||
highlight.
|
||||
- The promo boxes of the left column ("Product highlight", "Product news")
|
||||
are the entry's editorial `teasers` next to `catalog` (`kicker` = box
|
||||
label, plus `title`, `text`, `link`, `image`; designed for two, an empty
|
||||
array hides the column). There are no automatic news (`catalog.news` was
|
||||
dropped in v1.29).
|
||||
- Hidden pages and pages with *Hide in menu* drop out; order = page tree
|
||||
order. Version 1 (2026-10-09, sys_category groups with products) is no
|
||||
longer the source.
|
||||
@@ -1257,6 +1260,34 @@ pages are reached from there.
|
||||
by link); and a `teaser` (card text, plain) on every product. Do not build
|
||||
on these fields.
|
||||
|
||||
**Markets and Solutions menus (megamenu `layout: "pagetree"`, `display`
|
||||
since v1.30).** Page-tree entries build their `columns` live from the
|
||||
linked page (one column per child page, its children as `items`). The
|
||||
entry field *Page tree display* adds the presentation:
|
||||
|
||||
```json
|
||||
{ "title": "Markets", "link": "/markets", "layout": "pagetree",
|
||||
"display": "rail", "cardSize": "regular",
|
||||
"columns": [ { "title": "Transport", "link": "/markets/transport", "image": { "url": "…" },
|
||||
"items": [ { "title": "Airports", "link": "/markets/transport/airports",
|
||||
"image": { "url": "/fileadmin/_processed_/…webp", "width": 640, "height": 360, "alternative": "", "title": "" },
|
||||
"teaser": "…" } ] } ] }
|
||||
```
|
||||
|
||||
- `display: "columns"` (default; Support and every entry not switched):
|
||||
columns side by side as before. `display: "rail"`: the columns as a list
|
||||
on the left; hovering (or focusing) one shows its `items` as image cards
|
||||
on the right, the first column is preselected. `cardSize` (`regular` |
|
||||
`compact`, only with `rail`) sizes the cards - Solutions uses `compact`
|
||||
so five sub-solutions fit next to the list.
|
||||
- From content stage 2 every item carries `image` (first page media as a
|
||||
640 px WebP, `null` when none is set) and, when the page has an abstract,
|
||||
`teaser`. The sub-market pages already had page media; the 39 sub-solution
|
||||
pages got their hero background as page media
|
||||
(`vitec:attach-page-media --depth=2`); editors replace it in
|
||||
the page properties.
|
||||
- Additive: a front end that ignores `display` keeps rendering columns.
|
||||
|
||||
**Operations.** Indexing runs automatically: record and page saves enter the
|
||||
index queue, a scheduler task (Index Queue Worker, every 5 minutes) pushes them
|
||||
to Solr; the CLI command `vitec:solr-index` (with `--initialize` and `--debug`)
|
||||
@@ -1674,4 +1705,4 @@ remediation.
|
||||
- Header convention — the uniform header section across CEs, plugins and containers.
|
||||
- `Configuration/Sets/Vitecset/setup.typoscript` — the single TypoScript entry point.
|
||||
|
||||
*End of document EVO‑VITEC‑HL‑001 v1.28.*
|
||||
*End of document EVO‑VITEC‑HL‑001 v1.30.*
|
||||
Reference in new issue
Block a user