Backend of the sub-market template (spec agreed section by section with
the customer workflow in mind: the customer fills one XLSX per sub-market, an importer builds the page). - Frontend layout 21 'Sub-Market Page' in the existing BackendLayoutDataProvider: six zones, one per Figma section, emitted as colPos110..colPos160 - the JSON groups plus appearance.layout are the front-end contract (spec clause 7.17). A first attempt via page-TSconfig BackendLayouts was the wrong mechanism next to the provider and was removed again - New Content Blocks vitec_markethero, vitec_marketintro, vitec_marketinsights (field bags, serialized unprefixed under data); the workflow section reuses intro-paragraph + vitec_card flip cards - Stories gained quote/quote_person/quote_role (Testimonial tab), maintained once per story, emitted on every story card; usecaselist gained the layout value 'testimonials' - New plugin vitec_productfeature: one referenced product as a full product card, serialisation shared with the product list (cObj made protected for the subclass) - vitec_productlist gained hand-picked product selection: exactly the chosen products in the editor's order, category filter ignored, visibility filters kept - closes the long-standing single-select gap - New CLI vitec:generate-submarket-workbooks: one protected customer XLSX per sub-market (9 main markets, 25 sub-markets from the Market category tree) - dropdowns filled live from the database, named ranges as the importer contract, hidden Meta sheet pinning submarket/group/ record uid, soft character-limit warnings, asset URL fields Verified end to end on Broadcast & Media: layout-21 payload with colPos110 carrying the hero element and its subline. Spec raised to v1.20.
This commit is contained in:
1 parent
f0b0694842
commit
90ae6803a4
31 files changed
+493
-7
No files matched your search
@@ -51,6 +51,12 @@ final class UsecaseSerializer
|
||||
'teaser' => (string)($u['teaser'] ?? ''),
|
||||
'cardImage' => $this->image($uid, 'card_image', null, true),
|
||||
'customerLogo' => $this->image($uid, 'customer_logo'),
|
||||
// Testimonial data (2026-10-01): one quote per story, maintained
|
||||
// on the record, consumed by testimonial cards (sub-market
|
||||
// template). Empty strings when not yet maintained.
|
||||
'quote' => (string)($u['quote'] ?? ''),
|
||||
'quotePerson' => (string)($u['quote_person'] ?? ''),
|
||||
'quoteRole' => (string)($u['quote_role'] ?? ''),
|
||||
'markets' => $this->relationMulti('tx_vitec_usecase_market_mm', 'tx_vitec_domain_model_market', $uid),
|
||||
'categories' => $this->categories($uid),
|
||||
'featured' => (bool)($u['featured'] ?? false),
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Evomedien\Vitec\UserFunc;
|
||||
|
||||
use Doctrine\DBAL\ParameterType;
|
||||
use TYPO3\CMS\Core\Attribute\AsAllowedCallable;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Service\FlexFormService;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Featured product card - sub-market template section 5 (spec 2026-10-01).
|
||||
*
|
||||
* One referenced product, emitted as a single full product card under the
|
||||
* payload key `productfeature`. All copy comes from the product record
|
||||
* (decision Olli 2026-10-01: dropdown only, no free text) - the front end
|
||||
* renders image, title and teaser from the same card shape the product
|
||||
* list delivers, so no second contract exists.
|
||||
*
|
||||
* Extends ProductListJsonRenderer purely to reuse its serializeProduct()
|
||||
* and image handling - same filters, same shape, one source of truth.
|
||||
*/
|
||||
final class ProductFeatureJsonRenderer extends ProductListJsonRenderer
|
||||
{
|
||||
#[AsAllowedCallable]
|
||||
public function render(string $content = '', array $conf = []): string
|
||||
{
|
||||
try {
|
||||
$row = $this->cObj?->data ?? [];
|
||||
$flexFormData = GeneralUtility::makeInstance(FlexFormService::class)
|
||||
->convertFlexFormContentToArray((string)($row['pi_flexform'] ?? ''));
|
||||
$productUid = (int)($flexFormData['settings']['product'] ?? 0);
|
||||
if ($productUid <= 0) {
|
||||
return (string)json_encode(null);
|
||||
}
|
||||
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable('tx_vitec_domain_model_product');
|
||||
$product = $queryBuilder->select('*')->from('tx_vitec_domain_model_product')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($productUid, ParameterType::INTEGER)),
|
||||
$queryBuilder->expr()->eq('deleted', 0),
|
||||
$queryBuilder->expr()->eq('hidden', 0),
|
||||
$queryBuilder->expr()->eq('hideonwebsite', 0)
|
||||
)
|
||||
->executeQuery()->fetchAssociative();
|
||||
if (!$product) {
|
||||
return (string)json_encode(null);
|
||||
}
|
||||
|
||||
return (string)json_encode($this->serializeProduct($product), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
||||
} catch (\Throwable) {
|
||||
return (string)json_encode(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,9 @@ use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
|
||||
*/
|
||||
class ProductListJsonRenderer
|
||||
{
|
||||
private ?ContentObjectRenderer $cObj = null;
|
||||
// protected (not private) since 2026-10-01: ProductFeatureJsonRenderer
|
||||
// extends this class to reuse serializeProduct() and needs the cObj.
|
||||
protected ?ContentObjectRenderer $cObj = null;
|
||||
|
||||
/**
|
||||
* TYPO3 v14 hands the ContentObjectRenderer over through this setter only -
|
||||
@@ -119,6 +121,14 @@ class ProductListJsonRenderer
|
||||
}
|
||||
$debugMode = (bool)($settings['debug'] ?? false);
|
||||
$allProducts = (bool)($settings['allproducts'] ?? false);
|
||||
// Explicit picks (2026-10-01, built for the sub-market template's
|
||||
// Products section): when set, EXACTLY these products in the
|
||||
// editor's order - the category filter is ignored. The visibility
|
||||
// filters below stay in force on purpose, so a hidden/legacy
|
||||
// record never surfaces through a stale selection.
|
||||
$selectedUids = array_filter(
|
||||
array_map('intval', explode(',', (string)($settings['selectedproducts'] ?? '')))
|
||||
);
|
||||
|
||||
// Extbase repositories don't work in UserFunc context — direct query.
|
||||
$productQueryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class)
|
||||
@@ -137,7 +147,11 @@ class ProductListJsonRenderer
|
||||
$productQueryBuilder->expr()->eq('p.subproduct', 0)
|
||||
);
|
||||
|
||||
if (!empty($categoryUids) && !$allProducts) {
|
||||
if ($selectedUids !== [] && !$allProducts) {
|
||||
$productQuery->andWhere(
|
||||
$productQueryBuilder->expr()->in('p.uid', $productQueryBuilder->createNamedParameter($selectedUids, Connection::PARAM_INT_ARRAY))
|
||||
);
|
||||
} elseif (!empty($categoryUids) && !$allProducts) {
|
||||
$productQuery
|
||||
->join(
|
||||
'p',
|
||||
@@ -159,7 +173,12 @@ class ProductListJsonRenderer
|
||||
// sitemap. $categoryUids comes from expandWithDescendants in
|
||||
// depth-first tree order; within one category the uid order is
|
||||
// kept - the records were created in sitemap-V2 row order.
|
||||
if (!empty($categoryUids) && !$allProducts && $products !== []) {
|
||||
if ($selectedUids !== [] && !$allProducts && $products !== []) {
|
||||
// Editor's order, not storage order.
|
||||
$rankBySelection = array_flip($selectedUids);
|
||||
usort($products, static fn(array $a, array $b): int =>
|
||||
($rankBySelection[(int)$a['uid']] ?? PHP_INT_MAX) <=> ($rankBySelection[(int)$b['uid']] ?? PHP_INT_MAX));
|
||||
} elseif (!empty($categoryUids) && !$allProducts && $products !== []) {
|
||||
$rankByCategory = array_flip($categoryUids);
|
||||
$mmQueryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class)
|
||||
->getQueryBuilderForTable('sys_category_record_mm');
|
||||
|
||||
@@ -27,6 +27,15 @@ final class BackendLayoutDataProvider implements DataProviderInterface
|
||||
'hero' => 1,
|
||||
'sidebar' => 2,
|
||||
'preFooter' => 3,
|
||||
// Sub-market template sections (spec 2026-10-01): one slot per Figma
|
||||
// section. The headless JSON groups these as colPos110..colPos160 -
|
||||
// that grouping IS the front-end contract for layout 21.
|
||||
'smHero' => 110,
|
||||
'smIntro' => 120,
|
||||
'smWorkflow' => 130,
|
||||
'smTestimonials' => 140,
|
||||
'smProducts' => 150,
|
||||
'smInsights' => 160,
|
||||
];
|
||||
|
||||
/** Per-frontend-layout: title + ordered list of zones. */
|
||||
@@ -52,6 +61,7 @@ final class BackendLayoutDataProvider implements DataProviderInterface
|
||||
18 => ['title' => 'Content Page V3', 'zones' => ['hero', 'main', 'sidebar', 'preFooter']],
|
||||
19 => ['title' => 'Events Overview', 'zones' => ['hero', 'main']],
|
||||
20 => ['title' => 'Contact Page', 'zones' => ['hero', 'main', 'sidebar']],
|
||||
21 => ['title' => 'Sub-Market Page', 'zones' => ['smHero', 'smIntro', 'smWorkflow', 'smTestimonials', 'smProducts', 'smInsights']],
|
||||
];
|
||||
|
||||
/** Display name for each zone. Shown as column header in the BE page module. */
|
||||
@@ -60,6 +70,12 @@ final class BackendLayoutDataProvider implements DataProviderInterface
|
||||
'hero' => 'Hero',
|
||||
'sidebar' => 'Sidebar',
|
||||
'preFooter' => 'Pre-Footer',
|
||||
'smHero' => '1 · Hero',
|
||||
'smIntro' => '2 · Intro',
|
||||
'smWorkflow' => '3 · Workflow (header + flip cards)',
|
||||
'smTestimonials' => '4 · Testimonials',
|
||||
'smProducts' => '5 · Products (featured + list)',
|
||||
'smInsights' => '6 · Industry Insights',
|
||||
];
|
||||
|
||||
public function getIdentifier(): string
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Featured product card - sub-market template section 5.
|
||||
One product reference, all copy comes from the record.
|
||||
-->
|
||||
<T3DataStructure>
|
||||
<sheets>
|
||||
<sDEF>
|
||||
<ROOT>
|
||||
<sheetTitle>Featured Product</sheetTitle>
|
||||
<type>array</type>
|
||||
<el>
|
||||
<settings.product>
|
||||
<label>Product</label>
|
||||
<description>The product shown as the big featured card. Image, title and teaser come from the record.</description>
|
||||
<config>
|
||||
<type>group</type>
|
||||
<allowed>tx_vitec_domain_model_product</allowed>
|
||||
<size>1</size>
|
||||
<maxitems>1</maxitems>
|
||||
</config>
|
||||
</settings.product>
|
||||
</el>
|
||||
</ROOT>
|
||||
</sDEF>
|
||||
</sheets>
|
||||
</T3DataStructure>
|
||||
@@ -30,6 +30,16 @@
|
||||
</items>
|
||||
</config>
|
||||
</settings.debug>
|
||||
<settings.selectedproducts>
|
||||
<label>Hand-picked products</label>
|
||||
<description>When set, the list shows EXACTLY these products in this order - the category selection below is ignored. Hidden/legacy products stay suppressed. Used e.g. by the sub-market template's Products section.</description>
|
||||
<config>
|
||||
<type>group</type>
|
||||
<allowed>tx_vitec_domain_model_product</allowed>
|
||||
<size>6</size>
|
||||
<maxitems>50</maxitems>
|
||||
</config>
|
||||
</settings.selectedproducts>
|
||||
<settings.categories>
|
||||
<label>Select Category</label>
|
||||
<description>
|
||||
|
||||
@@ -29,6 +29,10 @@
|
||||
<label>50 / 50</label>
|
||||
<value>50-50</value>
|
||||
</numIndex>
|
||||
<numIndex index="4">
|
||||
<label>Testimonials (quote carousel, sub-market template)</label>
|
||||
<value>testimonials</value>
|
||||
</numIndex>
|
||||
</items>
|
||||
<default>grid</default>
|
||||
</config>
|
||||
|
||||
@@ -67,6 +67,21 @@ tt_content {
|
||||
}
|
||||
}
|
||||
|
||||
# Featured product card (sub-market template section 5): one referenced
|
||||
# product as a full product card under `productfeature` - same shape as
|
||||
# the list items, serialization shared with ProductListJsonRenderer.
|
||||
vitec_productfeature < lib.contentElementWithHeader
|
||||
vitec_productfeature {
|
||||
fields {
|
||||
content {
|
||||
fields {
|
||||
productfeature = USER
|
||||
productfeature.userFunc = Evomedien\Vitec\UserFunc\ProductFeatureJsonRenderer->render
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Product Finder - only the category hierarchy for the filter selects.
|
||||
# Placed once above the product lists; the front end filters the products
|
||||
# those lists already delivered, client side, via ?cat= and ?subcat=.
|
||||
|
||||
@@ -31,6 +31,7 @@ defined('TYPO3') or die();
|
||||
['label' => $l . 'layout.18', 'value' => '18'],
|
||||
['label' => $l . 'layout.19', 'value' => '19'],
|
||||
['label' => $l . 'layout.20', 'value' => '20'],
|
||||
['label' => $l . 'layout.21', 'value' => '21'],
|
||||
];
|
||||
$GLOBALS['TCA']['pages']['columns']['layout']['config']['default'] = '0';
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ use TYPO3\CMS\Extbase\Utility\ExtensionUtility;
|
||||
|
||||
$registerPluginWithFlexForm('Productlist', 'Show Products by Category', 'FILE:EXT:vitec/Configuration/FlexForms/Productlist.xml', 'vitec-plugin-productlist');
|
||||
$registerPluginWithFlexForm('Productfinder', 'Product Finder (filter data)', 'FILE:EXT:vitec/Configuration/FlexForms/Productfinder.xml', 'vitec-plugin-productlist');
|
||||
$registerPluginWithFlexForm('Productfeature', 'Featured Product Card', 'FILE:EXT:vitec/Configuration/FlexForms/Productfeature.xml', 'vitec-plugin-productlist');
|
||||
$registerPluginWithFlexForm('Simplecard', 'Simple Card', 'FILE:EXT:vitec/Configuration/FlexForms/Simplecard.xml', 'vitec-plugin-simplecard');
|
||||
$registerPluginWithFlexForm('Productshow', 'Show Single Product', 'FILE:EXT:vitec/Configuration/FlexForms/Productshow.xml', 'vitec-plugin-productshow');
|
||||
$registerPluginWithFlexForm('Usecaseshow', 'Single Success Story', 'FILE:EXT:vitec/Configuration/FlexForms/Usecase.xml', 'vitec-plugin-usecaseshow');
|
||||
|
||||
@@ -29,6 +29,8 @@ return [
|
||||
title, slug, detail_page, subtitle,
|
||||
--div--;List view,
|
||||
card_image, customer_logo, teaser,
|
||||
--div--;Testimonial,
|
||||
quote, quote_person, quote_role,
|
||||
--div--;Detail · Hero,
|
||||
hero_bgimage, hero_small_image, hero_video, hero_overlay_color, hero_overlay_opacity,
|
||||
--div--;Detail · Content,
|
||||
@@ -153,6 +155,26 @@ return [
|
||||
'config' => ['type' => 'text', 'cols' => 40, 'rows' => 4, 'eval' => 'trim'],
|
||||
],
|
||||
|
||||
// ------------------------------------------------------- Testimonial
|
||||
// One quote per story, maintained editorially and reused wherever the
|
||||
// story appears as a testimonial card (first consumer: the sub-market
|
||||
// template, decision Olli 2026-10-01 - the customer's XLS only picks
|
||||
// the story, never carries quote text).
|
||||
'quote' => [
|
||||
'label' => 'Quote',
|
||||
'description' => 'Shown on testimonial cards wherever this story is referenced. Plain text, no quotation marks - the front end adds them.',
|
||||
'config' => ['type' => 'text', 'cols' => 40, 'rows' => 4, 'eval' => 'trim'],
|
||||
],
|
||||
'quote_person' => [
|
||||
'label' => 'Quote: person',
|
||||
'config' => ['type' => 'input', 'size' => 30, 'max' => 255, 'eval' => 'trim'],
|
||||
],
|
||||
'quote_role' => [
|
||||
'label' => 'Quote: role / title',
|
||||
'description' => 'E.g. "Vice President of Technology, KCR" - shown below the name.',
|
||||
'config' => ['type' => 'input', 'size' => 30, 'max' => 255, 'eval' => 'trim'],
|
||||
],
|
||||
|
||||
// ------------------------------------------------------ Detail · Hero
|
||||
'hero_bgimage' => [
|
||||
'label' => 'Background Image',
|
||||
|
||||
@@ -16,6 +16,14 @@ mod {
|
||||
CType = vitec_productlist
|
||||
}
|
||||
}
|
||||
productfeature {
|
||||
iconIdentifier = vitec-plugin-productlist
|
||||
title = Featured Product Card
|
||||
description = One product as the big featured card (sub-market template). All copy comes from the record.
|
||||
tt_content_defValues {
|
||||
CType = vitec_productfeature
|
||||
}
|
||||
}
|
||||
productfinder {
|
||||
iconIdentifier = vitec-plugin-productlist
|
||||
title = Product Finder (filter data)
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16">
|
||||
<rect x="1.5" y="2.5" width="13" height="11" rx="1" fill="none" stroke="#000" stroke-width="1.2"/>
|
||||
<text x="3.4" y="6.6" font-family="serif" font-weight="bold" font-size="6" fill="#000">¶</text>
|
||||
<rect x="3" y="8" width="9" height="1" rx="0.2" fill="#000" opacity="0.4"/>
|
||||
<rect x="3" y="9.6" width="7.5" height="1" rx="0.2" fill="#000" opacity="0.35"/>
|
||||
<rect x="3" y="11.2" width="4" height="1.6" rx="0.3" fill="#000" opacity="0.55"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 536 B |
@@ -0,0 +1,45 @@
|
||||
# Sub-market template section 1 (Figma "Sub-Market Page", spec 2026-10-01).
|
||||
# The H1 is NOT a field - it is the page title (dev names are canonical).
|
||||
name: vitec/market-hero
|
||||
group: vitec_custom_components
|
||||
prefixFields: true
|
||||
prefixType: vendor
|
||||
|
||||
fields:
|
||||
- identifier: subline
|
||||
type: Text
|
||||
max: 120
|
||||
|
||||
- identifier: cta_section
|
||||
type: Palette
|
||||
label: CTA button
|
||||
fields:
|
||||
- identifier: cta_label
|
||||
type: Text
|
||||
max: 40
|
||||
- identifier: cta_link
|
||||
type: Link
|
||||
|
||||
- identifier: bgimage
|
||||
type: File
|
||||
minitems: 0
|
||||
maxitems: 1
|
||||
allowed: common-image-types
|
||||
|
||||
- identifier: video_section
|
||||
type: Palette
|
||||
label: Video teaser (optional - empty url hides the card)
|
||||
fields:
|
||||
- identifier: video_title
|
||||
type: Text
|
||||
max: 80
|
||||
- type: Linebreak
|
||||
- identifier: video_url
|
||||
type: Text
|
||||
max: 500
|
||||
- type: Linebreak
|
||||
- identifier: video_cover
|
||||
type: File
|
||||
minitems: 0
|
||||
maxitems: 1
|
||||
allowed: common-image-types
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xliff version="1.2">
|
||||
<file source-language="en" datatype="plaintext" original="messages">
|
||||
<body>
|
||||
<trans-unit id="title"><source>VITEC · Market Hero</source></trans-unit>
|
||||
<trans-unit id="description"><source>Sub-market template, section 1: subline, CTA, background image, optional video teaser. The H1 is the page title.</source></trans-unit>
|
||||
<trans-unit id="subline.label"><source>Subline</source></trans-unit>
|
||||
<trans-unit id="subline.description"><source>Max ~90 characters visible (two lines in the design).</source></trans-unit>
|
||||
<trans-unit id="cta_label.label"><source>CTA label</source></trans-unit>
|
||||
<trans-unit id="cta_label.description"><source>Empty = front-end default "Get in touch".</source></trans-unit>
|
||||
<trans-unit id="cta_link.label"><source>CTA target</source></trans-unit>
|
||||
<trans-unit id="cta_link.description"><source>Empty = /contact.</source></trans-unit>
|
||||
<trans-unit id="bgimage.label"><source>Background image</source></trans-unit>
|
||||
<trans-unit id="bgimage.description"><source>1920x600, dark enough for white text. Empty = dark gradient fallback.</source></trans-unit>
|
||||
<trans-unit id="video_title.label"><source>Video title</source></trans-unit>
|
||||
<trans-unit id="video_url.label"><source>Video URL</source></trans-unit>
|
||||
<trans-unit id="video_url.description"><source>YouTube/Vimeo or file URL. Empty = no video card, hero runs full width.</source></trans-unit>
|
||||
<trans-unit id="video_cover.label"><source>Video cover image</source></trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" data-namespace-typo3-fluid="true">
|
||||
<f:layout name="Preview"/>
|
||||
<f:section name="Content">
|
||||
<f:asset.css identifier="vitec-backend-preview" href="EXT:vitec/Resources/Public/Css/backend-preview.css"/>
|
||||
<div class="vitec-preview">
|
||||
<div class="vitec-preview__body">
|
||||
<strong>Market Hero</strong> — {data.vitec_subline}
|
||||
<f:if condition="{data.vitec_video_url}"><span> · 🎬 {data.vitec_video_title}</span></f:if>
|
||||
</div>
|
||||
</div>
|
||||
</f:section>
|
||||
</html>
|
||||
@@ -0,0 +1,3 @@
|
||||
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" data-namespace-typo3-fluid="true">
|
||||
<!-- Headless mode: JSON is built by nb-headless-content-blocks -->
|
||||
</html>
|
||||
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16">
|
||||
<rect x="1.5" y="2.5" width="13" height="11" rx="1" fill="none" stroke="#000" stroke-width="1.2"/>
|
||||
<text x="3.4" y="6.6" font-family="serif" font-weight="bold" font-size="6" fill="#000">¶</text>
|
||||
<rect x="3" y="8" width="9" height="1" rx="0.2" fill="#000" opacity="0.4"/>
|
||||
<rect x="3" y="9.6" width="7.5" height="1" rx="0.2" fill="#000" opacity="0.35"/>
|
||||
<rect x="3" y="11.2" width="4" height="1.6" rx="0.3" fill="#000" opacity="0.55"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 536 B |
@@ -0,0 +1,50 @@
|
||||
# Sub-market template section 6 (spec 2026-10-01).
|
||||
name: vitec/market-insights
|
||||
group: vitec_custom_components
|
||||
prefixFields: true
|
||||
prefixType: vendor
|
||||
|
||||
fields:
|
||||
- identifier: text1
|
||||
type: Textarea
|
||||
rows: 4
|
||||
|
||||
- identifier: text2
|
||||
type: Textarea
|
||||
rows: 4
|
||||
|
||||
- identifier: cta_section
|
||||
type: Palette
|
||||
label: CTA (optional)
|
||||
fields:
|
||||
- identifier: cta_label
|
||||
type: Text
|
||||
max: 40
|
||||
- identifier: cta_target
|
||||
type: Text
|
||||
max: 500
|
||||
|
||||
- identifier: video_section
|
||||
type: Palette
|
||||
label: Video card (optional - empty url hides the card)
|
||||
fields:
|
||||
- identifier: video_person
|
||||
type: Text
|
||||
max: 80
|
||||
- identifier: video_role
|
||||
type: Text
|
||||
max: 120
|
||||
- type: Linebreak
|
||||
- identifier: video_title
|
||||
type: Text
|
||||
max: 120
|
||||
- type: Linebreak
|
||||
- identifier: video_url
|
||||
type: Text
|
||||
max: 500
|
||||
- type: Linebreak
|
||||
- identifier: video_cover
|
||||
type: File
|
||||
minitems: 0
|
||||
maxitems: 1
|
||||
allowed: common-image-types
|
||||
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xliff version="1.2">
|
||||
<file source-language="en" datatype="plaintext" original="messages">
|
||||
<body>
|
||||
<trans-unit id="title"><source>VITEC · Market Insights</source></trans-unit>
|
||||
<trans-unit id="description"><source>Sub-market template, section 6: two paragraphs, optional CTA, optional video card with person.</source></trans-unit>
|
||||
<trans-unit id="text1.label"><source>Paragraph 1</source></trans-unit>
|
||||
<trans-unit id="text1.description"><source>Plain text, max ~250 characters.</source></trans-unit>
|
||||
<trans-unit id="text2.label"><source>Paragraph 2</source></trans-unit>
|
||||
<trans-unit id="text2.description"><source>Plain text, max ~250 characters.</source></trans-unit>
|
||||
<trans-unit id="cta_label.label"><source>CTA label</source></trans-unit>
|
||||
<trans-unit id="cta_label.description"><source>Empty = no button.</source></trans-unit>
|
||||
<trans-unit id="cta_target.label"><source>CTA target</source></trans-unit>
|
||||
<trans-unit id="cta_target.description"><source>Section anchor OR URL. Empty with a label set = /success-stories.</source></trans-unit>
|
||||
<trans-unit id="video_person.label"><source>Person</source></trans-unit>
|
||||
<trans-unit id="video_role.label"><source>Role / title</source></trans-unit>
|
||||
<trans-unit id="video_title.label"><source>Video title</source></trans-unit>
|
||||
<trans-unit id="video_url.label"><source>Video URL</source></trans-unit>
|
||||
<trans-unit id="video_url.description"><source>Empty = no video card, text runs wider.</source></trans-unit>
|
||||
<trans-unit id="video_cover.label"><source>Cover image</source></trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" data-namespace-typo3-fluid="true">
|
||||
<f:layout name="Preview"/>
|
||||
<f:section name="Content">
|
||||
<f:asset.css identifier="vitec-backend-preview" href="EXT:vitec/Resources/Public/Css/backend-preview.css"/>
|
||||
<div class="vitec-preview">
|
||||
<div class="vitec-preview__body">
|
||||
<strong>Market Insights</strong> — <f:format.crop maxCharacters="100">{data.vitec_text1}</f:format.crop>
|
||||
<f:if condition="{data.vitec_video_url}"><span> · 🎬 {data.vitec_video_person}</span></f:if>
|
||||
</div>
|
||||
</div>
|
||||
</f:section>
|
||||
</html>
|
||||
@@ -0,0 +1,3 @@
|
||||
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" data-namespace-typo3-fluid="true">
|
||||
<!-- Headless mode: JSON is built by nb-headless-content-blocks -->
|
||||
</html>
|
||||
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16">
|
||||
<rect x="1.5" y="2.5" width="13" height="11" rx="1" fill="none" stroke="#000" stroke-width="1.2"/>
|
||||
<text x="3.4" y="6.6" font-family="serif" font-weight="bold" font-size="6" fill="#000">¶</text>
|
||||
<rect x="3" y="8" width="9" height="1" rx="0.2" fill="#000" opacity="0.4"/>
|
||||
<rect x="3" y="9.6" width="7.5" height="1" rx="0.2" fill="#000" opacity="0.35"/>
|
||||
<rect x="3" y="11.2" width="4" height="1.6" rx="0.3" fill="#000" opacity="0.55"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 536 B |
@@ -0,0 +1,33 @@
|
||||
# Sub-market template section 2 (spec 2026-10-01).
|
||||
name: vitec/market-intro
|
||||
group: vitec_custom_components
|
||||
prefixFields: true
|
||||
prefixType: vendor
|
||||
|
||||
fields:
|
||||
- identifier: intro_text
|
||||
type: Textarea
|
||||
rows: 6
|
||||
|
||||
- identifier: image
|
||||
type: File
|
||||
minitems: 0
|
||||
maxitems: 1
|
||||
allowed: common-image-types
|
||||
|
||||
- identifier: learnmore_section
|
||||
type: Palette
|
||||
label: Learn-more button (optional)
|
||||
fields:
|
||||
- identifier: learnmore_label
|
||||
type: Text
|
||||
max: 40
|
||||
default: Learn more
|
||||
- identifier: learnmore_target
|
||||
type: Text
|
||||
max: 500
|
||||
|
||||
- identifier: show_portfolio
|
||||
type: Checkbox
|
||||
renderType: checkboxToggle
|
||||
default: 1
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xliff version="1.2">
|
||||
<file source-language="en" datatype="plaintext" original="messages">
|
||||
<body>
|
||||
<trans-unit id="title"><source>VITEC · Market Intro</source></trans-unit>
|
||||
<trans-unit id="description"><source>Sub-market template, section 2: intro text, image right, optional learn-more button, portfolio button toggle.</source></trans-unit>
|
||||
<trans-unit id="intro_text.label"><source>Intro text</source></trans-unit>
|
||||
<trans-unit id="intro_text.description"><source>Plain text, max ~350 characters. No HTML.</source></trans-unit>
|
||||
<trans-unit id="image.label"><source>Image (right)</source></trans-unit>
|
||||
<trans-unit id="image.description"><source>636x469 (about 4:3).</source></trans-unit>
|
||||
<trans-unit id="learnmore_label.label"><source>Label</source></trans-unit>
|
||||
<trans-unit id="learnmore_target.label"><source>Target</source></trans-unit>
|
||||
<trans-unit id="learnmore_target.description"><source>Section anchor (workflow, testimonials, products, insights) OR a URL. Empty = no button.</source></trans-unit>
|
||||
<trans-unit id="show_portfolio.label"><source>Show "Product Portfolio" button</source></trans-unit>
|
||||
<trans-unit id="show_portfolio.description"><source>Links to /products (fixed for now).</source></trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" data-namespace-typo3-fluid="true">
|
||||
<f:layout name="Preview"/>
|
||||
<f:section name="Content">
|
||||
<f:asset.css identifier="vitec-backend-preview" href="EXT:vitec/Resources/Public/Css/backend-preview.css"/>
|
||||
<div class="vitec-preview">
|
||||
<div class="vitec-preview__body">
|
||||
<strong>Market Intro</strong> — <f:format.crop maxCharacters="120">{data.vitec_intro_text}</f:format.crop>
|
||||
</div>
|
||||
</div>
|
||||
</f:section>
|
||||
</html>
|
||||
@@ -0,0 +1,3 @@
|
||||
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" data-namespace-typo3-fluid="true">
|
||||
<!-- Headless mode: JSON is built by nb-headless-content-blocks -->
|
||||
</html>
|
||||
@@ -3,9 +3,9 @@
|
||||
| | |
|
||||
|---|---|
|
||||
| **Document identifier** | EVO‑VITEC‑HL‑001 |
|
||||
| **Version** | 1.19 |
|
||||
| **Version** | 1.20 |
|
||||
| **Status** | Released |
|
||||
| **Date** | 2026‑09‑25 |
|
||||
| **Date** | 2026‑10‑01 |
|
||||
| **Applies to** | `evomedien/vitec` on TYPO3 v14.3 (headless) |
|
||||
| **Owner** | evomedien — VITEC relaunch |
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
| 1.17 | 2026‑09‑11 | **Interface change (additive).** The product list payload gained `successStories` (7.15.2.1): stories an editor attaches to a `sys_category` (tab *Options*, with its own layout), emitted in the list envelope beside `products`. Each card carries `forCategories` — the product categories it came from — so the front end can hide it with the same `?cat=`/`?subcat=` filter it applies to the products. Story links reuse the 7.5 rule, with the detail page taken from the element or the new site setting `vitec.storyDetailPid`. |
|
||||
| 1.18 | 2026‑09‑24 | **Interface change (additive).** Products gained `solutions` — an MM relation to solutions, emitted as solution cards in both product payloads — and a **Resources tab**: child table `tx_vitec_product_resource` with documentation / support cards (editable title, text and icon around one link, resolved per 9.12; cards without a resolvable link are not emitted) plus a `datasheets` MM selection of download records rendered into the same envelope as `type: datasheet` cards, delivered detail‑only as the `resources` list envelope whose `layout` switches to `carousel` above three cards in total (7.15). The `solutions` relation accepts group‑level records: the eight groups exist as `is_group = 1` solutions (created by `vitec:create-solution-groups`, excluded from lists and from `vitec:align-solutions`) because the agency workbooks reference the group level. Editorial: the footer had been stuck at v1.15 since 1.16/1.17 (2026‑09‑11). |
|
||||
| 1.19 | 2026‑09‑25 | **Interface change (additive).** The `vitec_card` Content Block gained the `layout_variant` value `flipcard` and a *Card Back* palette: `back_headline` (string, max 100) and `back_text` (richtext → HTML). The two fields are `displayCond`‑gated on the flipcard layout in the backend but serialised unconditionally (7.7) — the front end **shall** key the back face on `layout_variant`, never on field presence, because stored back values persist when an editor switches the layout away. Backend repair: `vitec_layout_variant` / `vitec_background_variant` are shared with `vitec_ctabanner`, and the index‑wise Content‑Blocks merge had dropped four card‑only select values (“Missing label”) — per‑type item lists now come from `columnsOverrides` (7.7); the Card‑Back palette lives in the Layout tab below the variant select. Editorial: the head‑matter version had been stuck at v1.17 since 1.18. |
|
||||
| 1.20 | 2026‑10‑01 | **Interface change (additive).** Sub-market page template: frontend layout 21 with one colPos group per design section (`colPos110`..`colPos160`, clause 7.17); new Content Blocks `vitec_markethero`, `vitec_marketintro`, `vitec_marketinsights`; new plugin `vitec_productfeature` (key `productfeature`, one product in the 7.15.2 item shape); story cards gained `quote`/`quotePerson`/`quoteRole`; `vitec_usecaselist` gained the layout value `testimonials`; `vitec_productlist` gained hand-picked product selection (editor order, category filter ignored). Field semantics: Template_Sub-Market_Spezifikation_2026-10-01.md. |
|
||||
|
||||
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
|
||||
@@ -1131,6 +1132,43 @@ content is extracted from the page’s `tt_content` rows via
|
||||
`index.queue.pages.fields.content` because the headless JSON output carries no
|
||||
`TYPO3SEARCH` markers.
|
||||
|
||||
---
|
||||
|
||||
### 7.17 Sub-market page template (frontend layout 21)
|
||||
|
||||
A sub-market page is identified by `appearance.layout == "layout-21"`. Its
|
||||
content is NOT read from element order: the backend layout assigns one column
|
||||
per design section, and the JSON groups them as
|
||||
|
||||
| Group | Section | Elements |
|
||||
|---|---|---|
|
||||
| `colPos110` | Hero | `vitec_markethero` |
|
||||
| `colPos120` | Intro | `vitec_marketintro` |
|
||||
| `colPos130` | Workflow | `vitec_introparagraph` + container with `vitec_card` flip cards |
|
||||
| `colPos140` | Testimonials | `vitec_usecaselist`, `layout: "testimonials"` |
|
||||
| `colPos150` | Products | `vitec_productfeature` + `vitec_productlist` (hand-picked) |
|
||||
| `colPos160` | Industry Insights | `vitec_marketinsights` |
|
||||
|
||||
The three `vitec_market*` elements are Content Blocks: their fields arrive
|
||||
unprefixed under the element's `data` (7.7). Field reference and empty-value
|
||||
semantics ("empty url = card hidden" etc.) are specified in
|
||||
`Template_Sub-Market_Spezifikation_2026-10-01.md`; the per-field limits live
|
||||
in the backend field descriptions.
|
||||
|
||||
Related interface additions (all additive):
|
||||
|
||||
- **Story cards** (7.5 shape, everywhere they appear) carry `quote`,
|
||||
`quotePerson`, `quoteRole` - maintained once per story, empty strings until
|
||||
maintained. The testimonial card renders them; the `vitec_usecaselist`
|
||||
FlexForm gained the `layout` value `testimonials` for this.
|
||||
- **`vitec_productfeature`** (payload key `productfeature`): exactly one
|
||||
referenced product as a full product card in the 7.15.2 item shape, `null`
|
||||
when unset or the product is not visible. Serialisation is shared with the
|
||||
product list (one source of truth).
|
||||
- **`vitec_productlist`** gained the FlexForm field *Hand-picked products*:
|
||||
when set, the list contains exactly those products in the editor's order;
|
||||
the category selection is ignored, the visibility filters stay in force.
|
||||
|
||||
## 8 Content‑type catalogue
|
||||
|
||||
| CType | TS pattern | Renderer / Processor | Payload key | Kind |
|
||||
|
||||
@@ -66,6 +66,9 @@
|
||||
<trans-unit id="layout.20" resname="layout.20">
|
||||
<source>Contact Page</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="layout.21" resname="layout.21">
|
||||
<source>Sub-Market Page</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
@@ -139,6 +139,9 @@ CREATE TABLE tx_vitec_domain_model_usecase (
|
||||
slug varchar(255) DEFAULT '' NOT NULL,
|
||||
detail_page int(11) DEFAULT '0' NOT NULL,
|
||||
subtitle varchar(255) DEFAULT '' NOT NULL,
|
||||
quote text,
|
||||
quote_person varchar(255) DEFAULT '' NOT NULL,
|
||||
quote_role varchar(255) DEFAULT '' NOT NULL,
|
||||
|
||||
card_image int(11) unsigned DEFAULT '0' NOT NULL,
|
||||
customer_logo int(11) unsigned DEFAULT '0' NOT NULL,
|
||||
|
||||
Reference in new issue
Block a user