Skip to content

Commit bc4cf73

Browse files
Merge pull request #244 from adobecom/homepage-configurator-q
Add DA folder picker and per-item image upload to Homepage configurator
2 parents bea3d45 + 84140f2 commit bc4cf73

54 files changed

Lines changed: 3205 additions & 930 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
name: List changed code files
2+
3+
on:
4+
pull_request_target:
5+
types: [opened, synchronize, reopened]
6+
branches: [main]
7+
8+
permissions:
9+
pull-requests: write
10+
11+
jobs:
12+
list-changed-files:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- uses: actions/github-script@v6
16+
with:
17+
script: |
18+
const MARKER = '<!-- pr-changed-files-comment -->';
19+
20+
const DOC_EXTENSIONS = new Set([
21+
'.md', '.mdx', '.markdown', '.txt', '.rst', '.adoc',
22+
'.doc', '.docx', '.pdf', '.rtf',
23+
]);
24+
const DOC_BASENAMES = new Set([
25+
'license', 'changelog', 'contributing', 'notice', 'authors', 'readme',
26+
]);
27+
28+
function isDocFile(path) {
29+
const base = path.split('/').pop();
30+
const dotIndex = base.lastIndexOf('.');
31+
if (dotIndex === -1) {
32+
return DOC_BASENAMES.has(base.toLowerCase());
33+
}
34+
const ext = base.slice(dotIndex).toLowerCase();
35+
return DOC_EXTENSIONS.has(ext);
36+
}
37+
38+
const files = await github.paginate(github.rest.pulls.listFiles, {
39+
owner: context.repo.owner,
40+
repo: context.repo.repo,
41+
pull_number: context.payload.pull_request.number,
42+
});
43+
44+
const codeFiles = files
45+
.filter((file) => !isDocFile(file.filename))
46+
.map((file) => file.filename)
47+
.sort();
48+
49+
let body;
50+
if (codeFiles.length === 0) {
51+
body = `${MARKER}\n### Changed files for cache purging\n\nNo purge-relevant files changed in this PR.`;
52+
} else {
53+
const list = codeFiles.map((path) => `<domain>/${path}`).join('\n');
54+
body = `${MARKER}\n### Changed files for cache purging\n\n`
55+
+ 'Replace `<domain>` with the target host (e.g. `main--event-libs--adobecom.aem.live`) '
56+
+ "before purging — files must be purged individually, wildcards aren't supported.\n\n"
57+
+ `\`\`\`\n${list}\n\`\`\``;
58+
}
59+
60+
const comments = await github.paginate(github.rest.issues.listComments, {
61+
owner: context.repo.owner,
62+
repo: context.repo.repo,
63+
issue_number: context.payload.pull_request.number,
64+
});
65+
const existing = comments.find((comment) => comment.body.includes(MARKER));
66+
67+
if (existing) {
68+
await github.rest.issues.updateComment({
69+
owner: context.repo.owner,
70+
repo: context.repo.repo,
71+
comment_id: existing.id,
72+
body,
73+
});
74+
} else {
75+
await github.rest.issues.createComment({
76+
owner: context.repo.owner,
77+
repo: context.repo.repo,
78+
issue_number: context.payload.pull_request.number,
79+
body,
80+
});
81+
}

docs/section-columns-layout.md

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
# Section Columns Layout
2+
3+
## Overview
4+
5+
`section-layout` is a page-wide metadata key that lets authors selectively group
6+
top-level sections into shared rows, instead of the usual all-sections-stack
7+
vertically behavior. A section is full-width by default; adding a `column-span-*`
8+
value via that section's own **Section Metadata** block opts it into sharing a row
9+
with its *contiguous* neighbors that carry the same kind of tag, proportioned by
10+
the span ratio.
11+
12+
This is a page-level layout concern, not a block — no new block, no new
13+
`EVENT_BLOCKS` entry. It's implemented entirely as an opt-in class toggle
14+
(`applySectionColumnsLayout()` in `event-libs/v1/utils/decorate.js`) plus CSS
15+
(`event-libs/v1/libs-styles.css`).
16+
17+
## Authoring
18+
19+
**1. Turn on the layout** — add a **Metadata** block at the bottom of the page:
20+
21+
| Metadata | |
22+
|---|---|
23+
| Section Layout | columns |
24+
25+
This applies to the whole page's `<main>`, not any one section.
26+
27+
**2. Split the page into sections** — insert a section break (`---`) between each
28+
block of content you want as a potential column, same as any other EDS page.
29+
30+
**3. Group sections into a row** — add a **Section Metadata** block inside each
31+
section you want side-by-side, with a `style` row set to `column-span-1`,
32+
`column-span-2`, or `column-span-3`:
33+
34+
| Section Metadata | |
35+
|---|---|
36+
| style | column-span-2 |
37+
38+
- `column-span-1` — equal share of the row (default weight for a tagged section)
39+
- `column-span-2` — twice the width of a `column-span-1` neighbor
40+
- `column-span-3` — three times the width of a `column-span-1` neighbor
41+
42+
`column-span-*` is a **weight**, not a slot number — a section's actual share of
43+
the row is *its own weight ÷ the sum of every weight in that row*. So:
44+
45+
- `column-span-1` + `column-span-1` → 50% / 50%
46+
- `column-span-1` + `column-span-2` → 33% / 67%
47+
- `column-span-3` + `column-span-2` → 60% / 40%
48+
- `column-span-1` + `column-span-1` + `column-span-1` → even thirds
49+
50+
You can combine this with any other `style` values you already use (background,
51+
spacing, theme, etc.) by comma-separating them in the same row, exactly as today.
52+
53+
**4. Sections without a `column-span-*` tag stay full-width**, stacked normally.
54+
This is the default — you only tag the sections you actually want grouped.
55+
56+
**5. Any number of contiguous tagged sections can share one row** — two, three,
57+
or more. There's no separate "how many columns" setting; a row is just however
58+
many contiguous `column-span-*` sections you author next to each other.
59+
60+
**6. Grouping only works between *contiguous* sections.** An untagged section
61+
between two tagged ones breaks them into two separate single-item rows rather
62+
than merging them — there's nothing to configure here, it falls directly out of
63+
how the CSS works (see Technical Notes), and it's visible immediately in preview.
64+
65+
**7. Two separate, back-to-back row-groups need a divider between them.** A page
66+
can have as many independent rows as you want, but the *only* thing that ends a
67+
row is an untagged (full-width) section. If two different groups sit directly
68+
next to each other with nothing untagged between them — e.g. you want sections
69+
2+3 in one row and sections 4+5 in a separate row, immediately after — they'll
70+
merge into a single four-column row instead, since there's nothing to force a
71+
break between section 3 and section 4. Insert a plain, untagged section between
72+
the two groups to force the break (it can be empty/purely structural if you
73+
don't want it to show as its own visible row).
74+
75+
**8. Blocks inside each section** stack top-to-bottom within their column exactly
76+
as they do in any normal section — nothing changes there.
77+
78+
**9. Below 900px viewport width**, every section collapses back to plain vertical
79+
stacking regardless of tagging — there's no authoring for this, it's automatic.
80+
81+
### Example
82+
83+
```html
84+
<!-- Metadata block at the end of the page -->
85+
<div>
86+
<div>Section Layout</div>
87+
<div>columns</div>
88+
</div>
89+
```
90+
91+
A page with four sections, where the middle two sit side-by-side (1:2 ratio) and
92+
the outer two stay full-width:
93+
94+
```
95+
Section 1 (no column-span tag — full width)
96+
---
97+
Section 2 (Section Metadata: style = column-span-1)
98+
---
99+
Section 3 (Section Metadata: style = column-span-2)
100+
---
101+
Section 4 (no column-span tag — full width)
102+
```
103+
104+
Renders (above 900px) as: Section 1 full-width, Sections 2+3 side-by-side split
105+
1:2, Section 4 full-width — each on its own row.
106+
107+
### Example: multiple independent rows on one page
108+
109+
Two separate 2-up rows, a solo full-width section, and a 3-up row — s1+s2 and
110+
s3+s4 are *different* groups, so a divider (s2.5) is needed between them:
111+
112+
```
113+
Section 1 (Section Metadata: style = column-span-1)
114+
---
115+
Section 2 (Section Metadata: style = column-span-1)
116+
---
117+
Section 2.5 — untagged divider, needed only because s1+s2 and s3+s4
118+
are separate groups sitting back-to-back with nothing
119+
else to force the break between them
120+
---
121+
Section 3 (Section Metadata: style = column-span-1)
122+
---
123+
Section 4 (Section Metadata: style = column-span-1)
124+
---
125+
Section 5 (no column-span tag — full width, "alone in its own row")
126+
---
127+
Section 6 (Section Metadata: style = column-span-1)
128+
---
129+
Section 7 (Section Metadata: style = column-span-1)
130+
---
131+
Section 8 (Section Metadata: style = column-span-1)
132+
```
133+
134+
Renders (above 900px) as five rows: [1, 2] split 50/50 → [2.5, full-width]
135+
[3, 4] split 50/50 → [5, full-width][6, 7, 8] split into even thirds. Section
136+
5 needed no divider on either side — an untagged section always forces a break
137+
before *and* after itself, which is exactly what makes it render alone.
138+
139+
## Technical Notes
140+
141+
- The metadata key is `section-layout`, value `columns` (exact string match).
142+
Read via the existing `getMetadata()` utility — no new metadata-reading code.
143+
- `applySectionColumnsLayout()` (`event-libs/v1/utils/decorate.js`) is **not**
144+
called from `decorateEvent()``decorateEvent` only runs on pages with an
145+
`event-id`, but this layout is meant for static/non-event pages too. A
146+
consuming site's own `decorateArea` must call it directly and unconditionally
147+
(see `da-events/events/scripts/scripts.js`'s `decorateArea()` for the reference
148+
integration). It always resolves the real page `<main>` directly and re-reads
149+
metadata on every call — safe to call repeatedly, since `decorateArea` can
150+
re-enter multiple times per page load (once per fragment/personalization pass).
151+
- **No DOM reparenting.** Sections are never moved — every `.section` stays
152+
exactly where Milo's `loadArea()` puts it, as a direct child of `<main>`. This
153+
was a deliberate choice: an earlier design considered wrapping selected
154+
sections in a new element to act as their own flex container, but that breaks
155+
several load-bearing Milo behaviors that depend on `.section` being `<main>`'s
156+
direct child — `position: relative` (needed by section-metadata's
157+
background-image feature), `main > .section > .content` max-width rules used
158+
by several blocks, `sticky-section.js`'s forced `main.prepend`/`append`, and
159+
personalization's post-LCP `main > div` containment check. None of that is a
160+
concern here since the DOM structure never changes.
161+
- **Grouping mechanism is pure CSS**, via `flex-wrap`, not CSS Grid. Every
162+
`.section` defaults to `flex: 1 1 100%` (forces it alone onto its own line —
163+
visually identical to normal stacking). A `column-span-*` class overrides that
164+
to `flex: N 1 0; min-width: 0`, letting the section shrink from 100% and pack
165+
onto a shared line with adjacent similarly-tagged siblings. `flex-basis: 0`
166+
means the entire row width counts as free space, distributed by `flex-grow`
167+
ratio — this is the whole authoring model in the previous section: a row's
168+
split is always "my weight ÷ the row's total weight," for any number of
169+
sections in the row, not just two. Because there's no fixed size involved
170+
(`min-width: 0` removes the min-content floor too), items also never wrap
171+
based on width alone — the only thing that forces a line break is an
172+
untagged `flex: 1 1 100%` sibling, which is why grouping/row-separation is
173+
entirely driven by that mechanism rather than any explicit "row" concept.
174+
Flexbox was chosen over CSS Grid specifically because it recalculates
175+
fill-percentage per line independently — a group's flex-grow ratios always
176+
sum to fill their shared row completely, regardless of how many sections are
177+
in the group or what span numbers they use. A fixed-track CSS Grid would
178+
leave a visible gap whenever a
179+
group's spans didn't sum to the grid's total column count.
180+
- The 900px breakpoint matches this repo's own existing stack→side-by-side
181+
precedent (`event-agenda.css`, `event-partners.css`, `bento-cards.css`), rather
182+
than Milo's own `section-metadata` intra-section grid breakpoint (1200px),
183+
since a full-page column is much wider than a sub-section grid column at the
184+
same viewport.
185+
- `main.section-columns` sets `align-items: stretch` explicitly (the flex/grid
186+
default) so sections sharing a row equalize in height — useful for backgrounds
187+
or borders to line up cleanly across the row. This only stretches each
188+
section's own box, not its content: `.section` lays out its children as normal
189+
block flow, not flex/grid, so a shorter section's actual content stays at its
190+
natural height inside the taller box rather than being force-stretched too.

event-libs/tier-1-event-configurator/MOBILE-RIDER-STREAM-ID-GAP.md

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,27 +24,29 @@ Homepage configurator either. Both surfaces are blocked on the same backend gap.
2424

2525
## Current state in the Homepage configurator
2626

27-
`buildSessionAuthorEntry()` (`tier-1-event-configurator/utils.js`) builds the
28-
JSON an author copies into `upcoming-sessions`/Featured Sessions' section-metadata.
29-
It does **not** include `mrStreamId` (or `watchUrl`, which isn't read by
30-
`upcoming-sessions.js` at all yet). For any session that genuinely is
31-
MR-streamed, an author currently has to hand-edit `mrStreamId` into the copied
32-
JSON after the fact — same workaround the older `build-author-data.mjs` CLI
33-
script already required, for the same reason (ESP's session catalog carries no
34-
such field either).
35-
36-
## Options once this needs solving for real
27+
**Resolved — Option 1 below was implemented.** `FeaturedSessionsEditor.js` has an
28+
optional per-session "Mobile Rider stream ID" (and "Watch URL") field next to each
29+
picked session; `buildSessionAuthorEntry()` (`tier-1-event-configurator/utils.js`)
30+
folds that author-entered `meta` into each session entry. Those entries feed the
31+
"Copy Link" payload (`ConfigEditor.js`'s `handleCopyHomepageLink`) — not, as this doc
32+
originally said, a JSON blob pasted into section-metadata; that pre-link authoring
33+
path no longer exists. `decorate.js`'s `tec-homepage` auto-block builder decodes the
34+
link and passes `mrStreamId`/`watchUrl` straight through to `upcoming-sessions.js`/
35+
`featured-sessions.js`, so an author only needs to fill this field in for the few
36+
sessions that are actually MR-streamed — no hand-editing of copied output required.
37+
38+
## Options considered
3739

3840
1. **Manual input field per session** in the Upcoming/Featured Sessions picker —
3941
an optional "Mobile Rider stream ID" text field next to each picked session,
4042
author fills it in only for the few sessions that are actually MR-streamed.
4143
Doesn't require any backend change; purely a configurator UX addition.
42-
2. **Leave it out of the UI** — keep relying on hand-editing the copied JSON,
43-
i.e. today's status quo, no configurator change.
44+
**— chosen, see above.**
45+
2. **Leave it out of the UI** — keep relying on hand-editing the copied JSON.
4446
3. **Wait on backend** — don't build anything now; once ESP/RF actually exposes
4547
a real stream-id field, both Session Guide and the Homepage configurator
4648
should consume it the same way, so building a workaround now risks being
4749
thrown away.
4850

49-
No decision has been made yet — revisit this doc once there's a concrete need
50-
or the backend gap closes.
51+
Revisit if/when ESP/RF exposes a real `mrStreamId` field — the manual field could
52+
then be dropped in favor of reading it straight from the session catalog.

event-libs/tier-1-event-configurator/TierOneEventConfigurator.js

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,19 @@ import { useNavigation } from './context/NavigationContext.js';
55
import { useConfigs } from './context/ConfigsContext.js';
66
import { useDA } from './context/DAContext.js';
77
import { useEventEnv } from './context/EventEnvContext.js';
8-
import { PAGES, EVENT_SERVICE_ENV_OPTIONS } from './constants.js';
8+
import { decodeHomepageConfigParam } from './utils.js';
9+
import { PAGES, EVENT_SERVICE_ENV_OPTIONS, HOMEPAGE_LINK_HASH_KEY } from './constants.js';
910

1011
const TOAST_TIMEOUT_MS = 6000;
12+
const HOMEPAGE_LINK_HASH_RE = new RegExp(`[#&]${HOMEPAGE_LINK_HASH_KEY}=([A-Za-z0-9+/=%-]{20,})`);
1113

1214
export default function TierOneEventConfigurator() {
1315
const { isLoading: isDaLoading, error: daError } = useDA();
14-
const { activePage } = useNavigation();
16+
const { activePage, goToEditor } = useNavigation();
1517
const { envName } = useEventEnv();
1618
const {
1719
toastError, clearToastError, toastSuccess, clearToastSuccess, isInitialLoading, error,
20+
findConfigByEventId, startEditConfig, setToastError,
1821
} = useConfigs();
1922

2023
const envLabel = EVENT_SERVICE_ENV_OPTIONS.find((opt) => opt.value === envName)?.label || envName;
@@ -32,6 +35,26 @@ export default function TierOneEventConfigurator() {
3235
return () => clearTimeout(timer);
3336
}, [toastSuccess, clearToastSuccess]);
3437

38+
// Deep-link back into a Homepage config from a "Copy Link" URL (see ConfigEditor.js's
39+
// handleCopyHomepageLink) — only once the config library has actually loaded, so
40+
// findConfigByEventId isn't run against an empty, not-yet-fetched configs array. Reads only
41+
// window.location.hash, never .search — DA's iframe only forwards the hash through to this
42+
// app (same constraint Schedule Maker documents for its own `schedule=` links).
43+
useEffect(() => {
44+
if (isInitialLoading || error) return;
45+
const match = window.location.hash.match(HOMEPAGE_LINK_HASH_RE);
46+
if (!match) return;
47+
const decoded = decodeHomepageConfigParam(match[1]);
48+
if (!decoded) return;
49+
const row = findConfigByEventId(decoded.eventId, decoded.configType);
50+
if (!row) {
51+
setToastError('Config not found for this link — it may have been deleted.');
52+
return;
53+
}
54+
startEditConfig(row);
55+
goToEditor();
56+
}, [isInitialLoading, error]);
57+
3558
if (isDaLoading) {
3659
return html`
3760
<div class="tec-app">

0 commit comments

Comments
 (0)