我在Umbraco 7中创建了一个引用外部网址的自定义部分,但是需要扩展它以使用与“内容”富文本编辑器中的媒体选择器完全相同的功能。我不需要任何其他富文本功能,只需从图标加载媒体选择器覆盖,然后选择内部或外部URL。
我试图提取umbraco源代码,并尝试对在线教程进行各种调整,但到目前为止我无法加载媒体选择器。
我知道从根本上说我需要:
然而,到目前为止,我还没能将它们连接在一起,所以任何帮助都非常感激。
答案 0 :(得分:3)
所以,这就是我提出解决方案的方法.....
第一个胜利是我发现了2个优秀的教程博客文章,在这个解决方案的基础上,非常尊重以下代码猫:
Tim Geyssons - Nibble的帖子: http://www.nibble.be/?p=440
Markus Johansson - Enkelmedia http://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应用程序'这将通过实现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控制器,将其与我们的Utilities自定义部分相关联,以及选择图标和排序顺序。
我们还声明了一个api控制器(我们将会这样做!),这将允许我们访问我们的Keyphrase数据对象。
GetTreeNodes方法允许我们迭代关键短语数据集合并将结果节点返回到视图。
GetMenuNode方法允许我们为自定义部分创建所需的菜单选项。 我们声明如果节点是根(Utilities),那么允许我们添加子节点并刷新节点集合。 但是,如果我们在节点树(Keyphrase)中较低,那么我们只希望用户能够删除该节点(即,不允许用户创建比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);
}
}
使用角度控制器创建自定义剖面视图,这是Umbraco 7中当前的架构风格。 应该注意的是,Umbraco希望您的自定义部分组件放入以下结构App_Plugins // BackOffice /
我们需要一个视图来显示和编辑我们的关键短语名称,目标短语和网址
<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.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的linkpicker允许用户选择内部或外部网址。 我们需要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事件类,如果它不存在,则创建我们的关键短语db表(参见第8点) b)启动Umbraco并将新的自定义部分与目标用户关联(从“用户”菜单) c)通过在umbraco中搜索它来更改自定义部分的占位符文本 - > gt> config - &gt; en.xml并交换&#39;实用程序&#39;
在保存或发布内容时拦截目标数据类型的目标内容字段 我给出的要求是拦截新闻文章的正文内容,因此您需要在Umbraco中创建一个文档类型,例如,标题字段类型为&#39; Textstring&#39;,和&#39; Richtext编辑器&#39;。
您还需要一个或多个关键短语定位,现在应该在新的Umbraco自定义栏目中,&#39; Utilities&#39;
在这里,我定位了关键短语&#39;技术新闻&#39;链接到英国广播公司技术新闻网站,以便我随时写下短语“科技新闻”。 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中的任何保存事件。然后我们检查我们的传入内容,看看它是否符合我们预期的类型 - 在本例中&#39; NewsArticle&#39; - 如果是,则定位&#39; bodyContent&#39;部分,使用我们的&#39; KeyphraseContentParser&#39;解析它,并交换当前的&#39; bodyContent&#39;使用已解析的&bodyContent&#39;。
创建一个Keyphrase解析器来交换内部/外部链接的关键短语
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或外部网络资源的链接。
b)处理已解析的html字符串,以保留链接到位,而不是创建嵌套链接。
c)允许在解析的html字符串中更新CMS关键短语更改。
此博客以及github代码可以从上一篇文章的链接中找到。
答案 1 :(得分:0)
好的,所以在找到一些优秀的帮助帖并挖掘后,我想出了解决方案,我在这里写过: http://frazzledcircuits.blogspot.co.uk/2015/03/umbraco-7-automatic-keyphrase.html