umbraco-dynamic-root

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Umbraco Dynamic Root

Umbraco 动态根

What is it?

什么是动态根?

Dynamic Roots allow content pickers to have a starting point (origin) that is determined dynamically rather than being a fixed node. This includes two extension types:
  • Dynamic Root Origin: Defines where the picker starts (e.g., current page, site root, nearest ancestor of type)
  • Dynamic Root Query Step: Defines navigation steps from the origin (e.g., find nearest ancestor, get children of type)
These enable flexible content picker configurations that adapt based on context.
动态根允许内容选择器拥有一个动态确定的起始点(源),而非固定节点。它包含两种扩展类型:
  • 动态根源(Dynamic Root Origin):定义选择器的起始位置(例如当前页面、站点根目录、最近的指定类型祖先节点)
  • 动态根查询步骤(Dynamic Root Query Step):定义从源开始的导航步骤(例如查找最近的祖先、获取指定类型的子节点)
这些功能让内容选择器的配置能够根据上下文灵活调整。

Documentation

文档

Workflow

工作流程

  1. Fetch docs - Use WebFetch on the URLs above
  2. Ask questions - What starting point logic? What query steps needed?
  3. Generate files - Create manifest(s) based on latest docs
  4. Explain - Show what was created and how to test
  1. 获取文档 - 使用WebFetch获取上述URL的文档内容
  2. 明确需求 - 确定需要什么起始点逻辑?需要哪些查询步骤?
  3. 生成文件 - 根据最新文档创建清单文件(manifest)
  4. 解释说明 - 展示创建的内容以及测试方法

Minimal Examples

最小示例

Dynamic Root Origin Manifest

动态根源清单

typescript
import type { ManifestDynamicRootOrigin } from '@umbraco-cms/backoffice/extension-registry';

const manifest: ManifestDynamicRootOrigin = {
  type: 'dynamicRootOrigin',
  alias: 'My.DynamicRootOrigin.SiteRoot',
  name: 'Site Root Origin',
  meta: {
    originAlias: 'SiteRoot',
    label: 'Site Root',
    description: 'Start from the root of the current site',
    icon: 'icon-home',
  },
};

export const manifests = [manifest];
typescript
import type { ManifestDynamicRootOrigin } from '@umbraco-cms/backoffice/extension-registry';

const manifest: ManifestDynamicRootOrigin = {
  type: 'dynamicRootOrigin',
  alias: 'My.DynamicRootOrigin.SiteRoot',
  name: 'Site Root Origin',
  meta: {
    originAlias: 'SiteRoot',
    label: 'Site Root',
    description: 'Start from the root of the current site',
    icon: 'icon-home',
  },
};

export const manifests = [manifest];

Dynamic Root Query Step Manifest

动态根查询步骤清单

typescript
import type { ManifestDynamicRootQueryStep } from '@umbraco-cms/backoffice/extension-registry';

const manifest: ManifestDynamicRootQueryStep = {
  type: 'dynamicRootQueryStep',
  alias: 'My.DynamicRootQueryStep.NearestBlog',
  name: 'Nearest Blog Query Step',
  meta: {
    queryStepAlias: 'NearestBlog',
    label: 'Nearest Blog',
    description: 'Find the nearest blog ancestor',
    icon: 'icon-article',
  },
};

export const manifests = [manifest];
typescript
import type { ManifestDynamicRootQueryStep } from '@umbraco-cms/backoffice/extension-registry';

const manifest: ManifestDynamicRootQueryStep = {
  type: 'dynamicRootQueryStep',
  alias: 'My.DynamicRootQueryStep.NearestBlog',
  name: 'Nearest Blog Query Step',
  meta: {
    queryStepAlias: 'NearestBlog',
    label: 'Nearest Blog',
    description: 'Find the nearest blog ancestor',
    icon: 'icon-article',
  },
};

export const manifests = [manifest];

Multiple Origins and Steps

多源与多查询步骤

typescript
import type { ManifestDynamicRootOrigin, ManifestDynamicRootQueryStep } from '@umbraco-cms/backoffice/extension-registry';

const originManifests: ManifestDynamicRootOrigin[] = [
  {
    type: 'dynamicRootOrigin',
    alias: 'My.DynamicRootOrigin.CurrentPage',
    name: 'Current Page Origin',
    meta: {
      originAlias: 'CurrentPage',
      label: 'Current Page',
      description: 'Start from the page being edited',
      icon: 'icon-document',
    },
  },
  {
    type: 'dynamicRootOrigin',
    alias: 'My.DynamicRootOrigin.Parent',
    name: 'Parent Page Origin',
    meta: {
      originAlias: 'Parent',
      label: 'Parent Page',
      description: 'Start from the parent of current page',
      icon: 'icon-arrow-up',
    },
  },
];

const queryStepManifests: ManifestDynamicRootQueryStep[] = [
  {
    type: 'dynamicRootQueryStep',
    alias: 'My.DynamicRootQueryStep.Children',
    name: 'Children Query Step',
    meta: {
      queryStepAlias: 'Children',
      label: 'Children',
      description: 'Get all child pages',
      icon: 'icon-tree',
    },
  },
  {
    type: 'dynamicRootQueryStep',
    alias: 'My.DynamicRootQueryStep.Ancestors',
    name: 'Ancestors Query Step',
    meta: {
      queryStepAlias: 'Ancestors',
      label: 'Ancestors',
      description: 'Get ancestor pages',
      icon: 'icon-navigation-up',
    },
  },
];

export const manifests = [...originManifests, ...queryStepManifests];
typescript
import type { ManifestDynamicRootOrigin, ManifestDynamicRootQueryStep } from '@umbraco-cms/backoffice/extension-registry';

const originManifests: ManifestDynamicRootOrigin[] = [
  {
    type: 'dynamicRootOrigin',
    alias: 'My.DynamicRootOrigin.CurrentPage',
    name: 'Current Page Origin',
    meta: {
      originAlias: 'CurrentPage',
      label: 'Current Page',
      description: 'Start from the page being edited',
      icon: 'icon-document',
    },
  },
  {
    type: 'dynamicRootOrigin',
    alias: 'My.DynamicRootOrigin.Parent',
    name: 'Parent Page Origin',
    meta: {
      originAlias: 'Parent',
      label: 'Parent Page',
      description: 'Start from the parent of current page',
      icon: 'icon-arrow-up',
    },
  },
];

const queryStepManifests: ManifestDynamicRootQueryStep[] = [
  {
    type: 'dynamicRootQueryStep',
    alias: 'My.DynamicRootQueryStep.Children',
    name: 'Children Query Step',
    meta: {
      queryStepAlias: 'Children',
      label: 'Children',
      description: 'Get all child pages',
      icon: 'icon-tree',
    },
  },
  {
    type: 'dynamicRootQueryStep',
    alias: 'My.DynamicRootQueryStep.Ancestors',
    name: 'Ancestors Query Step',
    meta: {
      queryStepAlias: 'Ancestors',
      label: 'Ancestors',
      description: 'Get ancestor pages',
      icon: 'icon-navigation-up',
    },
  },
];

export const manifests = [...originManifests, ...queryStepManifests];

Backend Implementation Required

需要后端C#实现

Dynamic roots require backend C# implementation to handle the actual query logic:
csharp
// Example C# implementation for a custom origin
public class SiteRootDynamicRootOrigin : IDynamicRootOrigin
{
    public string OriginAlias => "SiteRoot";

    public Task<Guid?> GetOriginAsync(Guid contentKey, string? entityType)
    {
        // Return the site root GUID based on the content's position
        // Implementation depends on your site structure
        return Task.FromResult<Guid?>(GetSiteRootForContent(contentKey));
    }
}

// Example C# implementation for a custom query step
public class NearestBlogQueryStep : IDynamicRootQueryStep
{
    public string QueryStepAlias => "NearestBlog";

    public Task<IEnumerable<Guid>> ExecuteAsync(Guid originKey, string? entityType)
    {
        // Find nearest blog ancestor from the origin
        // Return matching content GUIDs
        return Task.FromResult(FindNearestBlogAncestors(originKey));
    }
}

// Register in Composer
public class DynamicRootComposer : IComposer
{
    public void Compose(IUmbracoBuilder builder)
    {
        builder.DynamicRootSteps()
            .AddOrigin<SiteRootDynamicRootOrigin>()
            .AddQueryStep<NearestBlogQueryStep>();
    }
}
动态根需要后端C#代码来处理实际的查询逻辑:
csharp
// Example C# implementation for a custom origin
public class SiteRootDynamicRootOrigin : IDynamicRootOrigin
{
    public string OriginAlias => "SiteRoot";

    public Task<Guid?> GetOriginAsync(Guid contentKey, string? entityType)
    {
        // Return the site root GUID based on the content's position
        // Implementation depends on your site structure
        return Task.FromResult<Guid?>(GetSiteRootForContent(contentKey));
    }
}

// Example C# implementation for a custom query step
public class NearestBlogQueryStep : IDynamicRootQueryStep
{
    public string QueryStepAlias => "NearestBlog";

    public Task<IEnumerable<Guid>> ExecuteAsync(Guid originKey, string? entityType)
    {
        // Find nearest blog ancestor from the origin
        // Return matching content GUIDs
        return Task.FromResult(FindNearestBlogAncestors(originKey));
    }
}

// Register in Composer
public class DynamicRootComposer : IComposer
{
    public void Compose(IUmbracoBuilder builder)
    {
        builder.DynamicRootSteps()
            .AddOrigin<SiteRootDynamicRootOrigin>()
            .AddQueryStep<NearestBlogQueryStep>();
    }
}

Origin Meta Properties

动态根源元属性

PropertyDescription
originAlias
Unique identifier matching backend implementation
label
Display name in picker configuration
description
Help text explaining the origin
icon
Icon shown in configuration UI
属性说明
originAlias
与后端实现匹配的唯一标识符
label
选择器配置界面中的显示名称
description
解释该源的帮助文本
icon
配置界面中显示的图标

Query Step Meta Properties

动态根查询步骤元属性

PropertyDescription
queryStepAlias
Unique identifier matching backend implementation
label
Display name in picker configuration
description
Help text explaining the query step
icon
Icon shown in configuration UI
That's it! Always fetch fresh docs, keep examples minimal, generate complete working code.
属性说明
queryStepAlias
与后端实现匹配的唯一标识符
label
选择器配置界面中的显示名称
description
解释该查询步骤的帮助文本
icon
配置界面中显示的图标
就是这样!请始终获取最新文档,保持示例简洁,生成完整可运行的代码。