const response = await fetch(endpoint, {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-sitecore-contextid": contextId,
// sc_editMode instructs Edge to return draft content and field metadata.
// sc_layoutKind selects between final and shared layout variants.
sc_editMode: editMode,
sc_layoutKind: layoutKind,
},
body: JSON.stringify({
query,
variables: { siteName: site, itemId, language, version },
}),
});
const result = await response.json();
if (result.errors) {
throw new Error(JSON.stringify(result.errors));
}
const item = result?.data?.item;
if (!item) return null; // item not found
// Parse the rendered layout data (same structure as the layout query).
const rendered = item.rendered;
const layoutData =
typeof rendered === "string" ? JSON.parse(rendered) : rendered;
// Collect the first page of dictionary entries.
// TODO: For sites with large dictionaries, implement pagination using
// result.data.site.siteInfo.dictionary.pageInfo.hasNext and endCursor.
const dictionary = {};
for (const { key, value } of result?.data?.site?.siteInfo?.dictionary
?.results ?? ) {
dictionarykey = value;
}
// editingData holds the layout data and site dictionary returned by the
// editing GraphQL query.
type editingData struct {
LayoutData mapstringany
Dictionary mapstringstring
}
// fetchEditingData retrieves editing layout data and the site dictionary for
// the given item using the Sitecore Edge GraphQL editing query.
//
// - itemId - the Sitecore item GUID received as sc_itemid
// - editMode - "true" to fetch draft content (edit canvas),
// "false" for preview
// - layoutKind - "final" or "shared"; defaults to "final"
func fetchEditingData(site, itemId, language, version, layoutKind, editMode string) (*editingData, error) {
endpoint := os.Getenv("SITECORE_EDGE_PLATFORM_URL")
contextID := os.Getenv("SITECORE_EDGE_CONTEXT_ID")
// sc_editMode instructs Edge to return draft content and field metadata.
// sc_layoutKind selects between final and shared layout variants.
req.Header.Set("sc_editMode", editMode) // "true" or "false"
req.Header.Set("sc_layoutKind", layoutKind) // "final" or "shared"
if err := json.NewDecoder(resp.Body).Decode(&gql); err != nil {
return nil, fmt.Errorf("decode editing response: %w", err)
}
if len(gql.Errors) > 0 {
return nil, fmt.Errorf("graphql editing: %s", gql.Errors0.Message)
}
if gql.Data.Item == nil {
return nil, nil // item not found
}
// Parse the rendered layout data (same structure as the layout query).
raw := gql.Data.Item.Rendered
var layoutData mapstringany
if json.Unmarshal(raw, &layoutData) != nil {
var s string
if json.Unmarshal(raw, &s) != nil {
return nil, fmt.Errorf("cannot parse rendered field")
}
if err := json.Unmarshal(byte(s), &layoutData); err != nil {
return nil, fmt.Errorf("cannot parse rendered string: %w", err)
}
}
// Collect the first page of dictionary entries.
// TODO: For sites with large dictionaries, implement pagination using
// gql.Data.Site.SiteInfo.Dictionary.PageInfo.HasNext and EndCursor.
dictionary := make(mapstringstring)
if gql.Data.Site != nil &&
gql.Data.Site.SiteInfo != nil &&
gql.Data.Site.SiteInfo.Dictionary != nil {
for _, entry := range gql.Data.Site.SiteInfo.Dictionary.Results {
dictionaryentry.Key = entry.Value
}
}
// metaField wraps rendered field HTML with field-chrome metadata code blocks.
// The Page builder reads these to identify which fields a content author can
// edit inline.
//
// fieldData must be the full field object (the object that contains both
// "value" and, in edit mode, "metadata"). If no "metadata" key is present,
// the rendered HTML is returned unchanged.
export function metaField(fieldData, rendered) {
const metadata = fieldData?.metadata;
if (!metadata) return rendered;
return (
`${JSON.stringify(metadata)}` +
rendered +
``
);
}
// General Link field, with jsonValue as a fallback for direct item queries.
const linkField = fields?.TargetUrl;
const link = linkField?.value ?? linkField?.jsonValue?.value;
const content = link?.href
? `<a href="${escHtml(link.href)}"${link.target ? ` target="${escHtml(link.target)}"` : ""}>${imgTag}`
: imgTag;
// Plain text caption field.
const caption = fields?.ImageCaption?.value;
const html = caption
? `
function renderPromoEditing(fields) {
for (const name of "PromoText", "PromoText2", "PromoText3") {
const f = fields?.name;
if (f?.value) return metaField(f, f.value);
}
return "";
}
function renderRichTextEditing(fields) {
const f = fields?.Text;
if (!f?.value) return metaField(f, "");
return metaField(f, `
${f.value}
`);
}
function renderTitleEditing(fields, params) {
const f = fields?.heading;
if (!f?.value) return metaField(f, "");
const classAttr = params?.cssClass
? ` class="${escHtml(params.cssClass)}"`
: "";
return metaField(f, `<h1${classAttr}>${escHtml(f.value)}`);
}
// editingPassthroughComponents is the set of structural wrapper components
// that have no fields of their own and only forward nested placeholders.
// Components registered here in componentMap use a passthrough renderer
// that calls Placeholder. In editing mode that would bypass
// renderPlaceholderEditing, stripping all rendering and field chromes from
// every component nested inside the wrapper.
//
// Add any new passthrough/container components here alongside adding them
// to componentMap.
export const editingPassthroughComponents = new Set(
"Container",
"ContainerFullBleed",
"PartialDesignDynamicPlaceholder",
);
// editingComponentMap maps Sitecore component names to editing-aware renderer
// functions. Each renderer wraps its field output with metaField so that the
// Page builder can activate inline field editing on click.
//
// ColumnSplitter is handled separately in renderComponentEditing because it
// renders nested placeholders and needs the component's own uid.
const editingComponentMap = {
Image: renderImageEditing,
Promo: renderPromoEditing,
RichText: renderRichTextEditing,
Title: renderTitleEditing,
};
// renderColumnSplitterEditing mirrors ColumnSplitter.astro but calls
// renderPlaceholderEditing so that nested column placeholders have metadata
// code blocks and accept component drops in the Page builder canvas.
function renderColumnSplitterEditing(fields, params, placeholders, uid) {
const enabled = params?.EnabledPlaceholders
? params.EnabledPlaceholders.split(",").map((s) => s.trim())
: null;
const cols = Object.entries(placeholders)
.map((key, items) => {
const m = key.match(/^column-(\d+)-/);
if (!m) return null;
const index = Number(m1);
if (enabled && !enabled.includes(String(index))) return null;
return { key, items, index };
})
.filter(Boolean)
.sort((a, b) => a.index - b.index);
let html = `<div data-component="ColumnSplitter" class="${escHtml(params?.GridParameters ?? "")}">`;
for (const col of cols) {
const colClass = params?.`ColumnWidth${col.index}` ?? "";
html += `
`;
html += renderPlaceholderEditing(col.key, col.items, uid);
html += "
";
}
html += "";
return html;
}
// renderPlaceholderEditing renders a placeholder wrapped in metadata code
// blocks. parentUID is the uid of the route or component that owns this
// placeholder.
export function renderPlaceholderEditing(
name,
items,
parentUID = DEFAULT_UID,
) {
const phId = `${name}_${parentUID}`;
return (
metaOpen("placeholder", phId) +
`
// renderComponentEditing renders a single component wrapped in metadata code
// blocks. It delegates field rendering to editingComponentMap (which adds
// field-chrome wrapping), with a fallback to renderMissingComponentEditing
// for any components not listed in editingComponentMap.
export function renderComponentEditing(comp) {
const componentName = comp?.componentName ?? "";
const uid = comp?.uid || DEFAULT_UID;
const fields = comp?.fields ?? {};
const params = comp?.params ?? {};
const rawPH = comp?.placeholders ?? {};
const placeholders = Object.fromEntries(
Object.entries(rawPH).map((k, v) => k, Array.isArray(v) ? v : ),
);
let inner;
if (componentName === "ColumnSplitter") {
// ColumnSplitter renders nested placeholders; it needs the component uid
// to build correct placeholder IDs for drag-and-drop in the canvas.
inner = renderColumnSplitterEditing(fields, params, placeholders, uid);
} else if (editingPassthroughComponents.has(componentName)) {
// Passthrough components have no fields of their own - they only forward
// nested placeholders. Delegating to the normal component map would call
// Placeholder instead of renderPlaceholderEditing, stripping all chromes
// from every component nested inside the wrapper.
inner = Object.entries(placeholders)
.map((key, items) => renderPlaceholderEditing(key, items, uid))
.join("");
} else if (editingComponentMapcomponentName) {
inner = editingComponentMapcomponentName(fields, params, placeholders);
} else {
inner = renderMissingComponentEditing(componentName, placeholders, uid);
}
// renderMissingComponentEditing renders unmapped components in editing mode,
// preserving their nested placeholders with metadata wrappers.
export function renderMissingComponentEditing(
componentName,
placeholders,
uid = DEFAULT_UID,
) {
const inner = Object.entries(placeholders)
.map((key, items) => renderPlaceholderEditing(key, items, uid))
.join("");
return `
${inner}
`;
}
// buildEditingHeadScripts extracts the three categories of editing scripts
// from sitecore.context and returns them as a combined block of
この記事を改善するための提案がある場合は、 お知らせください!
Documentation Assistant
This assistant uses AI to generate responses based on Sitecore documentation. While it has access to official sources, answers may be incomplete or inaccurate and should not be considered official advice or support.