Umbraco 7 在自定义部分加载内容媒体选择器

Umbraco 7 load content media picker in custom section

我在 Umbraco 7 中创建了一个引用外部 urls 的自定义部分,但需要扩展它以使用与 'Content' rich 中的媒体选择器完全相同的功能文本编辑器。除了从图标和 select 内部或外部 url.

加载媒体选择器覆盖外,我不需要任何其他富文本功能

我已经尝试提取 umbraco 源代码,并尝试对在线教程进行各种改编,但到目前为止我无法加载媒体选择器。

我知道我基本上需要:

但是,到目前为止我还不能将它们连接在一起,所以非常感谢您的帮助。

好的,所以在找到一些优秀的帮助帖子并四处挖掘之后,我想出了解决方案,我已经在这里写了: http://frazzledcircuits.blogspot.co.uk/2015/03/umbraco-7-automatic-keyphrase.html

源代码在这里: https://github.com/AdTarling/UmbracoSandbox

所以,这就是我想出解决方案的方式......

第一个胜利是我发现了 2 个优秀的教程博客 posts,这个解决方案就站在它们的肩膀上,非常尊重以下代码猫:

Tim Geyssons - 蚕食 postings: http://www.nibble.be/?p=440

马库斯·约翰逊 - Enkelmedia http://www.enkelmedia.se/blogg/2013/11/22/creating-custom-sections-in-umbraco-7-part-1.aspx

  1. 创建一个模型 object 来表示一个关键短语,它将关联到一个新的、简单的 ORM table。 ToString() 方法允许在 front-end.

    上输出友好名称
     [TableName("Keyphrase")]
     public class Keyphrase
     {
        [PrimaryKeyColumn(AutoIncrement = true)]
        public int Id { get; set; }
        public string Name { get; set; }
        public string Phrase { get; set; }
        public string Link { get; set; }
    
        public override string ToString()
        {
            return Name;
        }
    }
    
  2. 创建一个 Umbraco 'application',它将通过实现 IApplication 接口来注册新的自定义部分。我已调用我的 'Utilities' 并将其关联到实用程序图标。

    [Application("Utilities", "Utilities", "icon-utilities", 8)]
    public class UtilitiesApplication : IApplication { }
    

装饰器允许我们提供新自定义部分的名称、别名、图标和 sort-order。

  1. 创建一个 Umbraco 树 Web 控制器,它允许我们为我们的关键词创建所需的菜单行为,并显示来自我们的数据库关键词 table 的关键词 collection。

    [PluginController("Utilities")]
    [Umbraco.Web.Trees.Tree("Utilities", "KeyphraseTree", "Keyphrase", iconClosed: "icon-doc", sortOrder: 1)]
    public class KeyphraseTreeController : TreeController
    {
        private KeyphraseApiController _keyphraseApiController;
    
        public KeyphraseTreeController()
        {
             _keyphraseApiController = new KeyphraseApiController();
        }
    
        protected override TreeNodeCollection GetTreeNodes(string id,                                                                FormDataCollection queryStrings)
        {
            var nodes = new TreeNodeCollection();
            var keyphrases = _keyphraseApiController.GetAll();
    
            if (id == Constants.System.Root.ToInvariantString())
            {
                foreach (var keyphrase in keyphrases)
                {
                    var node = CreateTreeNode(
                    keyphrase.Id.ToString(),
                    "-1",
                    queryStrings,
                    keyphrase.ToString(),
                    "icon-book-alt",
                    false);
    
                    nodes.Add(node);
                }
            }
    
            return nodes;
        }
    
        protected override MenuItemCollection GetMenuForNode(string id,                                                               FormDataCollection queryStrings)
        {
            var menu = new MenuItemCollection();
    
            if (id == Constants.System.Root.ToInvariantString())
            {
                // root actions
                 menu.Items.Add<CreateChildEntity, ActionNew>(ui.Text("actions", ActionNew.Instance.Alias));
                menu.Items.Add<RefreshNode, ActionRefresh>(ui.Text("actions", ActionRefresh.Instance.Alias), true);
                return menu;
            }
            else
            {
                menu.Items.Add<ActionDelete>(ui.Text("actions", ActionDelete.Instance.Alias));
            }
            return menu;
        }
    }
    

class 装饰器和 TreeController 扩展允许我们为我们的关键字树声明 Web 控制器,将其关联到我们的实用程序自定义部分,以及选择图标和排序顺序。

我们还声明了一个 api 控制器(我们会做到这一点!),这将允许我们访问我们的关键字数据 object。

GetTreeNodes 方法允许我们迭代关键字数据 collection 和 return 视图的结果节点。

GetMenuNode 方法允许我们创建自定义部分所需的菜单选项。 我们声明如果节点是根节点(实用程序),则允许我们添加 child 个节点并刷新节点 collection。 但是,如果我们在节点树(Keyphrase)中较低,那么我们只希望用户能够删除该节点(即不应允许用户创建比 Keyphrase 更深的另一层节点)

  1. 为我们的关键字 CRUD 请求创建一个 api 控制器

    public class KeyphraseApiController : UmbracoAuthorizedJsonController
    {
        public IEnumerable<Keyphrase> GetAll()
        {
            var query = new Sql().Select("*").From("keyphrase");
            return DatabaseContext.Database.Fetch<Keyphrase>(query);
        }
    
        public Keyphrase GetById(int id)
        {
            var query = new Sql().Select("*").From("keyphrase").Where<Keyphrase>(x => x.Id == id);
            return DatabaseContext.Database.Fetch<Keyphrase>(query).FirstOrDefault();
        }
    
        public Keyphrase PostSave(Keyphrase keyphrase)
        {
            if (keyphrase.Id > 0)
                DatabaseContext.Database.Update(keyphrase);
            else
                DatabaseContext.Database.Save(keyphrase);
    
            return keyphrase;
        }
    
        public int DeleteById(int id)
        {
            return DatabaseContext.Database.Delete<Keyphrase>(id);
        }
    }
    
  2. 使用 angular 控制器创建自定义剖面视图,这是 Umbraco 7 当前的架构风格。 需要注意的是,Umbraco期望你的自定义section组件被放入如下结构App_Plugins//BackOffice/

我们需要一个视图来显示和编辑我们的关键词名称、目标短语和 url

    <form name="keyphraseForm"
        ng-controller="Keyphrase.KeyphraseEditController"
        ng-show="loaded"
        ng-submit="save(keyphrase)"
        val-form-manager>
            <umb-panel>
                <umb-header>
                    <div class="span7">
                        <umb-content-name placeholder=""
                                          ng-model="keyphrase.Name" />
                    </div>

                    <div class="span5">
                        <div class="btn-toolbar pull-right umb-btn-toolbar">
                            <umb-options-menu ng-show="currentNode"
                                              current-node="currentNode"
                                              current-section="{{currentSection}}">
                            </umb-options-menu>
                        </div>
                    </div>
                </umb-header>

                <div class="umb-panel-body umb-scrollable row-fluid">
                    <div class="tab-content form-horizontal" style="padding-bottom: 90px">
                        <div class="umb-pane">
                            <umb-control-group label="Target keyphrase"                                                   description="Keyphrase to be linked'">
                                <input type="text" class="umb-editor umb-textstring" ng-model="keyphrase.Phrase" required />
                            </umb-control-group>

                            <umb-control-group label="Keyphrase link" description="Internal or external url">
                                <p>{{keyphrase.Link}}</p>
                                <umb-link-picker ng-model="keyphrase.Link" required/>
                            </umb-control-group>

                            <div class="umb-tab-buttons" detect-fold>
                                <div class="btn-group">
                                    <button type="submit" data-hotkey="ctrl+s" class="btn btn-success">
                                        <localize key="buttons_save">Save</localize>
                                    </button>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </umb-panel>
        </form>

这利用 umbraco 和 angular 标记来动态显示数据输入字段,并将我们的视图关联到与我们的数据层交互的 angular 控制器

    angular.module("umbraco").controller("Keyphrase.KeyphraseEditController",
    function ($scope, $routeParams, keyphraseResource, notificationsService, navigationService) {

        $scope.loaded = false;

        if ($routeParams.id == -1) {
            $scope.keyphrase = {};
            $scope.loaded = true;
        }
        else {
            //get a keyphrase id -> service
            keyphraseResource.getById($routeParams.id).then(function (response) {
            $scope.keyphrase = response.data;
            $scope.loaded = true;
        });
    }

    $scope.save = function (keyphrase) {
        keyphraseResource.save(keyphrase).then(function (response) {
            $scope.keyphrase = response.data;
            $scope.keyphraseForm.$dirty = false;
            navigationService.syncTree({ tree: 'KeyphraseTree', path: [-1, -1], forceReload: true });
            notificationsService.success("Success", keyphrase.Name + " has been saved");
        });
    };

});

然后我们需要 html 和相应的 angular 控制器用于关键字删除行为

    <div class="umb-pane" ng-controller="Keyphrase.KeyphraseDeleteController">
        <p>
            Are you sure you want to delete {{currentNode.name}} ?
        </p>
        <div>
            <div class="umb-pane btn-toolbar umb-btn-toolbar">
                <div class="control-group umb-control-group">
                    <a href="" class="btn btn-link" ng-click="cancelDelete()"
                        <localize key="general_cancel">Cancel</localize>
                    </a>
                    <a href="" class="btn btn-primary" ng-click="delete(currentNode.id)">
                        <localize key="general_ok">OK</localize>
                    </a>
                </div>
            </div>
        </div>
    </div>
  1. 利用 Umbraco 的 link 选择器允许用户 select 内部或外部 url。 我们需要 html 标记来启动 LinkPicker

    <div>
        <ul class="unstyled list-icons">
            <li>
                <i class="icon icon-add blue"></i>
                <a href ng-click="openLinkPicker()" prevent-default>Select</a>
            </li>
        </ul>
    </div>
    

以及启动 link 选择器并 post 将 select 编辑 url 返回到 html 视图的关联指令 js 文件

    angular.module("umbraco.directives")
        .directive('umbLinkPicker', function (dialogService, entityResource)         {
            return {
                restrict: 'E',
                replace: true,
                templateUrl: '/App_Plugins/Utilities/umb-link-picker.html',
                require: "ngModel",
                link: function (scope, element, attr, ctrl) {

                    ctrl.$render = function () {
                        var val = parseInt(ctrl.$viewValue);

                        if (!isNaN(val) && angular.isNumber(val) && val > 0) {

                            entityResource.getById(val, "Content").then(function (item) {
                                scope.node = item;
                            });
                        }
                };

                scope.openLinkPicker = function () {
                    dialogService.linkPicker({ callback: populateLink });
                }

                scope.removeLink = function () {
                    scope.node = undefined;
                    updateModel(0);
                }

                function populateLink(item) {
                    scope.node = item;
                    updateModel(item.url);

                }

                function updateModel(id) {
                    ctrl.$setViewValue(id);

                }
            }
        };
    });

最后一个 js 文件允许我们通过网络发送数据,其中包含每个人最喜欢的 http 动词 GET、POST(句柄也放在这里)和 DELETE

    angular.module("umbraco.resources")
        .factory("keyphraseResource", function ($http) {
            return {
                getById: function (id) {
                    return $http.get("BackOffice/Api/KeyphraseApi/GetById?id=" + id);
                },
                save: function (keyphrase) {
                    return $http.post("BackOffice/Api/KeyphraseApi/PostSave", angular.toJson(keyphrase));
                },
                deleteById: function (id) {
                    return $http.delete("BackOffice/Api/KeyphraseApi/DeleteById?id=" + id);
                }
            };
        });

此外,我们需要一个包清单来注册我们的 javascript 行为

    {
        javascript: [
        '~/App_Plugins/Utilities/BackOffice/KeyphraseTree/edit.controller.js',
            '~/App_Plugins/Utilities/BackOffice/KeyphraseTree/delete.controller.js',
        '~/App_Plugins/Utilities/keyphrase.resource.js',
        '~/App_Plugins/Utilities/umbLinkPicker.directive.js'
        ]
    }
  1. 实施调整以允许解决方案的 CMS 部分正常工作。 在这一点上,我们几乎已经让我们的自定义部分开始唱歌了,但我们只需要再跳几个 Umbraco 箍,即 a) 添加一个关键字事件 class 来创建我们的关键字数据库 table 如果它不存在(见第 8 点) b) 启动 Umbraco 并将新的自定义部分关联到目标用户(从用户菜单) c) 通过在 umbraco-->config-->en.xml 中搜索它来更改自定义部分的占位符文本,并将占位符文本换成 'Utilities'

  2. 保存或发布内容时拦截目标数据类型的目标内容字段 我的要求是拦截新闻文章的 body 内容,因此您需要在 Umbraco 中创建一个文档类型,例如,具有 'Textstring' 类型的标题字段,以及body'Richtext editor'.

  3. 类型的内容字段

您还需要一个或多个关键短语作为目标,现在应该在新的 Umbraco 自定义部分中,'Utilities'

在这里,我将关键词 'technology news' 到 link 定位到 bbc 技术新闻网站,这样每当我写短语 'technology news' 时,href link 就会被自动插入。 这显然是一个非常简单的例子,但如果用户需要 link 某些重复的法律文件,例如税收,属性,尽职调查,这将是非常强大的,可以托管在 CMS 本身的外部或内部。 href link 将在新选项卡中打开外部资源,一个同一 window 中的内部资源(我们将在第 9 点中讨论)

因此,我们要实现的原理是拦截文档的 Umbraco 保存事件并操纵我们的富文本以插入我们的 link。这是按如下方式完成的: a) 建立一个方法 (ContentServiceOnSaving),当用户单击 'save' 或 'publish and save' 时将触发该方法。 b) 定位我们想要的内容字段以找到我们的关键短语。 c) 根据我们的关键词 collection 解析目标内容 html 以创建我们的 internal/external links.

注意:如果您只想启动自定义部分 运行,您只需要 ApplicationStarted 方法来创建 KeyPhrase table.

    public class KeyphraseEvents : ApplicationEventHandler
    {
        private KeyphraseApiController _keyphraseApiController;

        protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, 
                                           ApplicationContext applicationContext)
        {
            _keyphraseApiController = new KeyphraseApiController();
            ContentService.Saving += ContentServiceOnSaving;
            var db = applicationContext.DatabaseContext.Database;

            if (!db.TableExist("keyphrase"))
            {
                db.CreateTable<Keyphrase>(false);
            }
        }

        private void ContentServiceOnSaving(IContentService sender, SaveEventArgs<IContent> saveEventArgs)
        {
            var keyphrases = _keyphraseApiController.GetAll();
            var keyphraseContentParser = new KeyphraseContentParser();

            foreach (IContent content in saveEventArgs.SavedEntities)
            {
                if (content.ContentType.Alias.Equals("NewsArticle"))
                {
                    var blogContent = content.GetValue<string>("bodyContent");
                    var parsedBodyText = keyphraseContentParser.ReplaceKeyphrasesWithLinks(blogContent, keyphrases);
                    content.SetValue("bodyContent", parsedBodyText);
                }
            }
        }
    }

ContentServiceOnSaving 方法允许我们拦截 Umbraco 中的任何保存事件。之后我们检查我们传入的内容,看看它是否是我们期望的类型 - 在这个例子中 'NewsArticle' - 如果是,然后定位 'bodyContent' 部分,用我们的 [=135= 解析它],并将当前 'bodyContent' 与解析后的 'bodyContent'.

交换
  1. 创建一个关键短语解析器以交换 internal/external links

    的关键短语
    public class KeyphraseContentParser
    {
        public string ReplaceKeyphrasesWithLinks(string htmlContent,                                                        IEnumerable<Keyphrase> keyphrases)
        {
            var parsedHtmlStringBuilder = new StringBuilder(htmlContent);
    
            foreach (var keyphrase in keyphrases)
            {
                if (htmlContent.CaseContains(keyphrase.Phrase, StringComparison.OrdinalIgnoreCase))
                {
                    var index = 0;
                    do
                    {
                        index = parsedHtmlStringBuilder.ToString()
                        .IndexOf(keyphrase.Phrase, index, StringComparison.OrdinalIgnoreCase);
    
                        if (index != -1)
                        {
                            var keyphraseSuffix = parsedHtmlStringBuilder.ToString(index, keyphrase.Phrase.Length + 4);
                            var keyPhraseFromContent = parsedHtmlStringBuilder.ToString(index, keyphrase.Phrase.Length);
    
                            var keyphraseTarget = "_blank";
    
                            if (keyphrase.Link.StartsWith("/"))
                            {
                                keyphraseTarget = "_self";
                            }
    
                            var keyphraseLinkReplacement = String.Format("<a href='{0}' target='{1}'>{2}</a>",
    keyphrase.Link, keyphraseTarget, keyPhraseFromContent);
    
                            if (!keyphraseSuffix.Equals(String.Format("{0}</a>", keyPhraseFromContent)))
                            {
                                parsedHtmlStringBuilder.Remove(index, keyPhraseFromContent.Length);
                                parsedHtmlStringBuilder.Insert(index, keyphraseLinkReplacement);
                                index += keyphraseLinkReplacement.Length;
                            }
                            else
                            {
                                var previousStartBracket = parsedHtmlStringBuilder.ToString().LastIndexOf("<a", index);
                                var nextEndBracket = parsedHtmlStringBuilder.ToString().IndexOf("a>", index);
    
                              parsedHtmlStringBuilder.Remove(previousStartBracket, (nextEndBracket - (previousStartBracket - 2)));
                                   parsedHtmlStringBuilder.Insert(previousStartBracket, keyphraseLinkReplacement);
                        index = previousStartBracket +         keyphraseLinkReplacement.Length;
                            }
                        }
                    } while (index != -1);
                }
            }
    
            return parsedHtmlStringBuilder.ToString();
        }
    }
    

单步执行上述代码可能是最简单的,但从根本上说,解析器必须:

a) 查找并包装所有关键短语,忽略大小写,使用 link 到内部 CMS 或外部网络资源。

b) 处理一个已经解析的 html 字符串以保留 links 而不创建嵌套的 links.

c) 允许在解析的 html 字符串中更新 CMS 关键词更改。

这个的博客,以及github的代码可以在前面post的link中找到。