我在Umbraco 7中创建了一个引用外部URL的自定义部分,但需要将其扩展为使用与"Content"富文本编辑器中的媒体选择器完全相同的功能。除了从图标加载媒体选择器覆盖并选择内部或外部url之外,我不需要任何其他富文本功能。
我尝试过对umbraco源代码进行提炼,也尝试过对在线教程的各种改编,但到目前为止,我还无法加载媒体选择器。
我知道从根本上我需要:
- 另一个角度控制器从内容返回数据"getall"方法
- 包含媒体选取器覆盖的html部分
- 在我的自定义部分的edit.html中有一个引用,用于启动覆盖
然而,到目前为止,我还没能把它连接在一起,所以非常感谢任何帮助。
所以,这就是我提出解决方案的方式。。。。。
第一个胜利是,我发现了两篇优秀的教程博客文章,这个解决方案就站在上面,非常尊重以下代码猫:
Tim Geyssons-Nibble帖子:http://www.nibble.be/?p=440
Markus Johansson-Enkelmediahttp://www.enkelmedia.se/blogg/2013/11/22/creating-custom-sections-in-umbraco-7-part-1.aspx
-
创建一个模型对象来表示一个关键字,该关键字将与一个新的、简单的ORM表相关联。ToString()方法允许在前端输出一个友好的名称。
[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接口来注册新的自定义节。我把我的名字叫做"实用程序",并把它和实用程序图标关联起来。
[Application("Utilities", "Utilities", "icon-utilities", 8)] public class UtilitiesApplication : IApplication { }
装饰器允许我们提供新自定义部分的名称、别名、图标和排序顺序。
-
创建一个Umbraco树web控制器,该控制器将允许我们为关键字创建所需的菜单行为,并显示数据库关键字表中的关键字集合。
[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; } }
类装饰器和TreeController扩展允许我们为关键字树声明web控制器,将其与实用程序自定义部分关联,以及选择图标和排序顺序。
我们还声明了一个api控制器(我们将讨论这个!),它将允许我们访问Keyphrase数据对象。
GetTreeNodes方法允许我们迭代关键短语数据集合,并将结果节点返回到视图。
GetMenuNode方法允许我们为自定义部分创建所需的菜单选项。我们声明,如果节点是根(实用程序),则允许我们添加子节点并刷新节点集合。然而,如果我们在节点树(Keyphrase)中排名较低,那么我们只希望用户能够删除节点(即不应允许用户创建比Keyphrasse更深的另一级别的节点)
-
为我们的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); } }
-
使用角度控制器创建自定义剖面视图,这是Umbraco7中当前的架构样式。需要注意的是,Umbraco希望您的自定义部分组件被放入以下结构中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>
这利用本影和角度标记来动态显示数据输入字段,并将我们的视图与与与我们的数据层交互的角度控制器相关联
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和相应的角度控制器来实现关键字删除行为
<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的链接选择器允许用户选择内部或外部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>
以及一个相关的指令js文件,该文件启动链接选择器并将选定的url发布回html视图
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) 添加一个keyphrase事件类,如果它不存在,它将创建我们的keyphrase-db表(参见第8点)b) 启动Umbraco并将新的自定义部分与目标用户关联(从"用户"菜单)c) 更改自定义部分的占位符文本,方法是在umbraco-->config->en.xml中搜索它,并将占位符文本替换为"Utilities">
保存或发布内容时拦截目标数据类型的目标内容字段给我的要求是截取新闻文章的正文内容,所以你需要在Umbraco中创建一个文档类型,例如,有一个类型为"Textstring"的标题字段和类型为"Richtext editor"的bodyContent字段。
您还需要一个或多个关键短语作为目标,现在应该在新的Umbraco自定义部分"实用程序"中
在这里,我将关键短语"技术新闻"链接到bbc技术新闻网站,这样每当我写下短语"技术消息"时,href链接都会自动插入。这显然是一个非常简单的例子,但如果用户需要链接到某些重复的法律文件,例如税务、财产、尽职调查,则会非常强大,这些文件可以在外部或CMS内部托管。href链接将在一个新的选项卡中打开一个外部资源,并在同一窗口中打开内部资源(我们将在第9点中了解)
因此,我们试图实现的原则是拦截文档的Umbraco保存事件,并操纵富文本来插入链接。具体操作如下:a) 建立一个方法(ContentServiceOnSaving),该方法将在用户单击"保存"或"发布并保存"时激发。b) 瞄准我们想要的内容领域,找到我们的关键词。c) 根据我们的关键词集合分析目标内容html,以创建我们的内部/外部链接。
注意:如果你只想启动并运行自定义部分,你只需要ApplicationStarted方法来创建KeyPhrase表。
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"部分为目标,用"KeyphraseContentParser"解析它,并将当前的"bodyCntent"与解析的"bodiContent"交换。
创建一个Keyphrase解析器以交换内部/外部链接的keyphrases
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) 使用指向内部CMS或外部web资源的链接查找并包装所有关键短语(忽略大小写)。
b) 处理一个已经解析的html字符串,既保留链接,又不创建嵌套链接。
c) 允许在解析的html字符串中更新CMS密钥短语更改。
这个博客以及github代码可以从上一篇文章的链接中找到。
好的,所以在找到一些优秀的助手帖子并四处挖掘后,我提出了解决方案,我在这里写了这些解决方案:http://frazzledcircuits.blogspot.co.uk/2015/03/umbraco-7-automatic-keyphrase.html
源代码在这里:https://github.com/AdTarling/UmbracoSandbox