diff --git a/approuter/xs-app.json b/approuter/xs-app.json index e8218a0e4..680ac35ad 100644 --- a/approuter/xs-app.json +++ b/approuter/xs-app.json @@ -690,6 +690,7 @@ { "source": "^/learn/?(\\?.*)?$", "target": "/content/pages/learn/$1", "destination": "srv-api", "authenticationType": "none" }, { "source": "^/model/?(\\?.*)?$", "target": "/content/pages/model/$1", "destination": "srv-api", "authenticationType": "none" }, { "source": "^/operate/?(\\?.*)?$", "target": "/content/pages/operate/$1", "destination": "srv-api", "authenticationType": "none" }, + { "source": "^/teched/?(\\?.*)?$", "target": "/content/pages/teched/$1", "destination": "srv-api", "authenticationType": "none" }, { "source": "^/sitemap\\.xml(\\?.*)?$", "target": "/content/pages/sitemap.xml$1", "destination": "srv-api", "authenticationType": "none" }, { "source": "^/index\\.xml(\\?.*)?$", "target": "/content/pages/index.xml$1", "destination": "srv-api", "authenticationType": "none" }, { "source": "^/llms\\.txt(\\?.*)?$", "target": "/content/pages/llms.txt$1", "destination": "srv-api", "authenticationType": "none" }, diff --git a/hugo/content/teched/_index.md b/hugo/content/teched/_index.md new file mode 100644 index 000000000..32c4a67fe --- /dev/null +++ b/hugo/content/teched/_index.md @@ -0,0 +1,6 @@ +--- +title: "SAP TechEd 2026 Sessions" +description: "Browse the SAP TechEd 2026 session catalog — in-person in Berlin and online via the Virtual experience." +type: teched +layout: list +--- diff --git a/hugo/data/.gitignore b/hugo/data/.gitignore index 72ab49342..9e184350e 100644 --- a/hugo/data/.gitignore +++ b/hugo/data/.gitignore @@ -1,3 +1,4 @@ browse.json author_index.json channels_stats.json +teched.json diff --git a/hugo/layouts/teched/list.html b/hugo/layouts/teched/list.html new file mode 100644 index 000000000..916887141 --- /dev/null +++ b/hugo/layouts/teched/list.html @@ -0,0 +1,57 @@ +{{ define "main" }} +{{- $sessions := (.Site.Data.teched.sessions) | default slice -}} +{{- $berlin := where $sessions "venue" "BERLIN" -}} +{{- $virtual := where $sessions "venue" "VIRTUAL" -}} +{{/* baseof.html already wraps this block in a single
landmark, so this + shell uses a plain
(the channels-page pattern) rather than nesting a + second
— a duplicate-main landmark trips axe landmark-no-duplicate-main. */}} +
+
+

{{ .Title }}

+ {{ with .Description }}

{{ . }}

{{ end }} +
+ + {{/* Full session payload for the island (Unit 8), which mounts into + #teched-sessions-grid-mount and renders the interactive Berlin/Virtual + grids. The static section headings + per-venue
+ +{{ end }} diff --git a/package.json b/package.json index 5a568ae27..506f8d428 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "fetch-tutorials": "tsx scripts/fetch-tutorials.ts --target hugo", "fetch-homepage-shelves": "tsx scripts/fetch-homepage-shelves.ts", "fetch-channels": "tsx scripts/fetch-channels.ts", + "fetch-teched": "tsx scripts/fetch-teched.ts", "fetch-channel-collections": "tsx scripts/fetch-channel-collections.ts", "fetch-channels-stats": "tsx scripts/fetch-channels-stats.ts", "fetch-verb-definitions": "tsx scripts/fetch-verb-definitions.ts", @@ -103,7 +104,7 @@ "build:display": "cd app/display-app && npm install && npm run build", "copy-joule-vendor": "node scripts/copy-joule-vendor.mjs", "check-deploy-cap-target": "node scripts/check-deploy-cap-target.cjs", - "build:all": "npm run prebuild && npm run fetch-tutorials -- --regenerate && npm run fetch-advocates && npm run fetch-homepage-shelves && npm run fetch-channels && npm run fetch-channel-atlas && npm run fetch-channel-collections && npm run fetch-channels-stats && npm run fetch-verb-definitions && npm run fetch-tags && npm run fetch-shelf-definitions && npm run fetch-featured-topics && npm run fetch-topic-clusters && npm run fetch-sitemap-catalog && npm run generate-ktt-lessons && npm run build:icon-subset && npm run build:css && npm run build:apps && npm run build:island-manifest && npm run check:ui5-single-copy && npx tsx scripts/check-ui5-entry-coverage.ts && npm run build:analytics-explorer && npm run copy-joule-vendor && npm run build:explore && npm run build:channel-atlas && npm run build:hugo && npm run build:page-fallback && npm run build:whats-new-snapshot && npm run retain:assets && npm run build:publish-island-manifest && npm run build:highlight && npm run build:display && npm run build:sdl", + "build:all": "npm run prebuild && npm run fetch-tutorials -- --regenerate && npm run fetch-advocates && npm run fetch-homepage-shelves && npm run fetch-channels && npm run fetch-channel-atlas && npm run fetch-channel-collections && npm run fetch-channels-stats && npm run fetch-verb-definitions && npm run fetch-tags && npm run fetch-shelf-definitions && npm run fetch-featured-topics && npm run fetch-topic-clusters && npm run fetch-sitemap-catalog && npm run fetch-teched && npm run generate-ktt-lessons && npm run build:icon-subset && npm run build:css && npm run build:apps && npm run build:island-manifest && npm run check:ui5-single-copy && npx tsx scripts/check-ui5-entry-coverage.ts && npm run build:analytics-explorer && npm run copy-joule-vendor && npm run build:explore && npm run build:channel-atlas && npm run build:hugo && npm run build:page-fallback && npm run build:whats-new-snapshot && npm run retain:assets && npm run build:publish-island-manifest && npm run build:highlight && npm run build:display && npm run build:sdl", "build:deploy": "npm run check-deploy-cap-target && npm run build:all", "deploy": "node scripts/deploy-mta.cjs", "build:admin": "npm --prefix app/admin-shell run build", diff --git a/scripts/fetch-teched.ts b/scripts/fetch-teched.ts new file mode 100644 index 000000000..31b6edf41 --- /dev/null +++ b/scripts/fetch-teched.ts @@ -0,0 +1,26 @@ +import { mkdirSync, writeFileSync } from 'node:fs'; +import { join } from 'node:path'; + +const CAP_BASE = process.env.CAP_BASE_URL || 'http://localhost:4004'; +const OUT_PATH = join('hugo', 'data', 'teched.json'); + +let payload: { + sessions: unknown[]; + speakers: unknown[]; + tracks: unknown[]; + buildAt: string; + error: string | null; +} = { + sessions: [], speakers: [], tracks: [], buildAt: new Date().toISOString(), error: null, +}; +try { + const res = await fetch(`${CAP_BASE}/build/teched`); + if (!res.ok) throw new Error(`status ${res.status}`); + payload = { ...payload, ...(await res.json()) }; +} catch (err) { + payload.error = err instanceof Error ? err.message : String(err); + console.warn(`[fetch-teched] warn: ${payload.error} — writing empty payload`); +} +mkdirSync(join('hugo', 'data'), { recursive: true }); +writeFileSync(OUT_PATH, JSON.stringify(payload, null, 2), 'utf-8'); +console.log(`[fetch-teched] wrote ${payload.sessions.length} sessions → ${OUT_PATH}`); diff --git a/srv/lib/page-key-map.js b/srv/lib/page-key-map.js index c3e822463..7bca13afa 100644 --- a/srv/lib/page-key-map.js +++ b/srv/lib/page-key-map.js @@ -25,6 +25,7 @@ export const IN_SCOPE_PAGES = [ { route: '/tutorial-navigator/', key: 'page-tutorial-navigator', file: 'tutorial-navigator/index.html', mimeType: 'text/html' }, { route: '/developer-advocates/', key: 'page-developer-advocates', file: 'developer-advocates/index.html', mimeType: 'text/html' }, { route: '/devtoberfest/', key: 'page-devtoberfest', file: 'devtoberfest/index.html', mimeType: 'text/html' }, + { route: '/teched/', key: 'page-teched', file: 'teched/index.html', mimeType: 'text/html' }, // Verb hub landing pages (#1659 Phase 2) — each is a single Hugo page // rendered by layouts/verb/list.html at //index.html. { route: '/ai/', key: 'page-ai', file: 'ai/index.html', mimeType: 'text/html' }, diff --git a/test/smoke/pages-routes.smoke.test.js b/test/smoke/pages-routes.smoke.test.js index f24e6d548..4a23298ac 100644 --- a/test/smoke/pages-routes.smoke.test.js +++ b/test/smoke/pages-routes.smoke.test.js @@ -34,6 +34,7 @@ const HTML_ROUTES = [ { path: '/tutorial-navigator/', marker: 'id="tutorial-navigator"' }, { path: '/developer-advocates/', marker: 'id="advocates-mount"' }, { path: '/devtoberfest/', marker: 'id="devtoberfest-mount"' }, + { path: '/teched/', marker: 'SAP TechEd' }, { path: '/ai/', marker: 'data-verb="ai"' }, { path: '/build/', marker: 'data-verb="build"' }, { path: '/connect/', marker: 'data-verb="connect"' },