feat(site-builder): reusable global sections with per-page overrides
Make a top-level section global (context menu), place instances from the
Section library's new Global tab, edit the master once in its own zone
("Editing global section" banner, Done - update N instances) and every
instance on every page/header/footer is rebuilt from it. Instances are real
inlined subtrees (custom.global on the root, custom.gkey on every node), so
export and published output are unchanged static HTML.
- utils/global-sections.ts: pure model (createMaster, instantiate,
syncInstances, collectOverridesFromInstance, applyOverrides, detach,
findInstanceRoot, master zone round trip). Unchanged input returns the
identical string; sync keeps the instance root id and the instance's own
inner ids per gkey.
- PageContext: __global__:<id> zone (editGlobal / finishGlobal); leaving it
commits the master (rev + 1 only when it changed), syncs every stored
slot and applies the sync to the returned-to canvas as ONE undo step.
- GlobalSectionsContext: makeGlobal / insertInstance / detach / setOverride
/ resetOverride / remove (refused while used), one undo step each.
- Inline text edits inside an instance record an override (text + html)
via useGlobalOverrideCommit; GuidedStyles shows a Global section panel
(Edit master, Detach, content override fields) instead of style panels.
- RenderNode: Global badge, dashed outline, lock icon on inner nodes.
- buildSavePayload: a master zone on the canvas is never saved into a page.
Refs #57
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
48061d37ae
commit
dd8b99be06
15 files changed
+1898
-29
No files matched your search
@@ -3279,3 +3279,48 @@ body {
|
||||
border-radius: 6px; padding: 7px 14px; font-size: 13px; font-weight: 500; cursor: pointer;
|
||||
}
|
||||
.sitesmith-regenerate-btn:disabled { opacity: 0.6; cursor: not-allowed; }
|
||||
|
||||
/* ---- #57 Global sections ---- */
|
||||
/* Instance root: dashed accent outline on the canvas (editor-only attribute). */
|
||||
.canvas-device-frame [data-wsb-global] { outline: 2px dashed #8b5cf6; outline-offset: -2px; }
|
||||
.canvas-device-frame [data-wsb-global-inner][data-craft-hovered] { outline-color: #8b5cf6; cursor: text; }
|
||||
.canvas-device-frame.guides-off [data-wsb-global] { outline: none; }
|
||||
.component-indicator-global {
|
||||
display: inline-flex; align-items: center; gap: 3px;
|
||||
background: #7c3aed; color: #fff; border-radius: 3px; padding: 0 5px; margin-left: -4px;
|
||||
}
|
||||
.component-indicator-lock { opacity: 0.85; }
|
||||
.global-master-banner {
|
||||
display: flex; align-items: center; gap: 8px; padding: 6px 12px; font-size: 11px; font-weight: 600;
|
||||
color: #a78bfa; background: rgba(139, 92, 246, 0.1); border-bottom: 1px solid rgba(139, 92, 246, 0.35);
|
||||
}
|
||||
.global-master-banner-text { flex: 1; min-width: 0; }
|
||||
.global-master-banner-done {
|
||||
flex-shrink: 0; border: none; border-radius: var(--radius-sm); padding: 4px 10px; font-size: 11px; font-weight: 600;
|
||||
color: #fff; background: #7c3aed; cursor: pointer;
|
||||
}
|
||||
.global-master-banner-done:hover { background: #6d28d9; }
|
||||
.global-instance-panel { display: flex; flex-direction: column; gap: 8px; }
|
||||
.global-instance-head { display: flex; align-items: center; gap: 6px; font-size: 12px; color: #a78bfa; }
|
||||
.global-instance-head strong { color: var(--color-text); }
|
||||
.global-instance-actions { display: flex; gap: 6px; flex-wrap: wrap; }
|
||||
.global-instance-btn {
|
||||
display: inline-flex; align-items: center; gap: 5px; padding: 5px 10px; font-size: 12px; cursor: pointer;
|
||||
color: var(--color-text); background: var(--color-bg-base); border: 1px solid var(--color-border); border-radius: var(--radius-sm);
|
||||
}
|
||||
.global-instance-btn:hover { border-color: #8b5cf6; }
|
||||
.global-instance-hint { margin: 0; font-size: 11px; color: var(--color-text-muted); }
|
||||
.global-override-fields { display: flex; flex-direction: column; gap: 6px; }
|
||||
.global-override-title { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.4px; color: var(--color-text-muted); }
|
||||
.global-override-badge {
|
||||
margin-left: 6px; text-transform: none; letter-spacing: 0; font-weight: 500;
|
||||
color: #a78bfa; background: rgba(139, 92, 246, 0.15); border-radius: 999px; padding: 1px 6px;
|
||||
}
|
||||
.global-override-field { display: flex; flex-direction: column; gap: 3px; }
|
||||
.global-override-label { font-size: 11px; color: var(--color-text-muted); }
|
||||
.global-override-input {
|
||||
width: 100%; box-sizing: border-box; padding: 6px 8px; font-size: 12px;
|
||||
color: var(--color-text); background: var(--color-bg-base); border: 1px solid var(--color-border); border-radius: var(--radius-sm);
|
||||
}
|
||||
.section-lib-chip-global i { margin-right: 3px; }
|
||||
.section-lib-usage { display: block; font-size: 11px; color: var(--color-text-muted); }
|
||||
Reference in new issue
Block a user