Umbraco 7 在自定义部分加载内容媒体选择器
Umbraco 7 load content media picker in custom section
我在 Umbraco 7 中创建了一个引用外部 urls 的自定义部分,但需要扩展它以使用与 'Content' rich 中的媒体选择器完全相同的功能文本编辑器。除了从图标和 select 内部或外部 url.
加载媒体选择器覆盖外,我不需要任何其他富文本功能
我已经尝试提取 umbraco 源代码,并尝试对在线教程进行各种改编,但到目前为止我无法加载媒体选择器。
我知道我基本上需要:
- 另一个 angular 控制器到 return 来自内容的数据
'getall' 方法
- 一个 html 包含媒体选择器覆盖的部分
- 在我的自定义部分的 edit.html 中引用以启动叠加层。
但是,到目前为止我还不能将它们连接在一起,所以非常感谢您的帮助。
好的,所以在找到一些优秀的帮助帖子并四处挖掘之后,我想出了解决方案,我已经在这里写了:
http://frazzledcircuits.blogspot.co.uk/2015/03/umbraco-7-automatic-keyphrase.html
所以,这就是我想出解决方案的方式......
第一个胜利是我发现了 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
创建一个模型 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;
}
}
创建一个 Umbraco 'application',它将通过实现 IApplication 接口来注册新的自定义部分。我已调用我的 'Utilities' 并将其关联到实用程序图标。
[Application("Utilities", "Utilities", "icon-utilities", 8)]
public class UtilitiesApplication : IApplication { }
装饰器允许我们提供新自定义部分的名称、别名、图标和 sort-order。
创建一个 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 更深的另一层节点)
为我们的关键字 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);
}
}
使用 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>
利用 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'
]
}
实施调整以允许解决方案的 CMS 部分正常工作。
在这一点上,我们几乎已经让我们的自定义部分开始唱歌了,但我们只需要再跳几个 Umbraco 箍,即
a) 添加一个关键字事件 class 来创建我们的关键字数据库 table 如果它不存在(见第 8 点)
b) 启动 Umbraco 并将新的自定义部分关联到目标用户(从用户菜单)
c) 通过在 umbraco-->config-->en.xml 中搜索它来更改自定义部分的占位符文本,并将占位符文本换成 'Utilities'
保存或发布内容时拦截目标数据类型的目标内容字段
我的要求是拦截新闻文章的 body 内容,因此您需要在 Umbraco 中创建一个文档类型,例如,具有 'Textstring' 类型的标题字段,以及body'Richtext editor'.
类型的内容字段
您还需要一个或多个关键短语作为目标,现在应该在新的 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'.
交换
创建一个关键短语解析器以交换 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中找到。
我在 Umbraco 7 中创建了一个引用外部 urls 的自定义部分,但需要扩展它以使用与 'Content' rich 中的媒体选择器完全相同的功能文本编辑器。除了从图标和 select 内部或外部 url.
加载媒体选择器覆盖外,我不需要任何其他富文本功能我已经尝试提取 umbraco 源代码,并尝试对在线教程进行各种改编,但到目前为止我无法加载媒体选择器。
我知道我基本上需要:
- 另一个 angular 控制器到 return 来自内容的数据 'getall' 方法
- 一个 html 包含媒体选择器覆盖的部分
- 在我的自定义部分的 edit.html 中引用以启动叠加层。
但是,到目前为止我还不能将它们连接在一起,所以非常感谢您的帮助。
好的,所以在找到一些优秀的帮助帖子并四处挖掘之后,我想出了解决方案,我已经在这里写了: http://frazzledcircuits.blogspot.co.uk/2015/03/umbraco-7-automatic-keyphrase.html
所以,这就是我想出解决方案的方式......
第一个胜利是我发现了 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
创建一个模型 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; } }
创建一个 Umbraco 'application',它将通过实现 IApplication 接口来注册新的自定义部分。我已调用我的 'Utilities' 并将其关联到实用程序图标。
[Application("Utilities", "Utilities", "icon-utilities", 8)] public class UtilitiesApplication : IApplication { }
装饰器允许我们提供新自定义部分的名称、别名、图标和 sort-order。
创建一个 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 更深的另一层节点)
为我们的关键字 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); } }
使用 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>
利用 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'
]
}
实施调整以允许解决方案的 CMS 部分正常工作。 在这一点上,我们几乎已经让我们的自定义部分开始唱歌了,但我们只需要再跳几个 Umbraco 箍,即 a) 添加一个关键字事件 class 来创建我们的关键字数据库 table 如果它不存在(见第 8 点) b) 启动 Umbraco 并将新的自定义部分关联到目标用户(从用户菜单) c) 通过在 umbraco-->config-->en.xml 中搜索它来更改自定义部分的占位符文本,并将占位符文本换成 'Utilities'
保存或发布内容时拦截目标数据类型的目标内容字段 我的要求是拦截新闻文章的 body 内容,因此您需要在 Umbraco 中创建一个文档类型,例如,具有 'Textstring' 类型的标题字段,以及body'Richtext editor'.
类型的内容字段
您还需要一个或多个关键短语作为目标,现在应该在新的 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'.
交换创建一个关键短语解析器以交换 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中找到。