Got macro content loading in editor async updated more of the macro plugin to ensure the macro contents cannot be edited.
This commit is contained in:
@@ -10,7 +10,7 @@ namespace Umbraco.Core.Macros
|
||||
/// </summary>
|
||||
internal class MacroTagParser
|
||||
{
|
||||
private static readonly Regex MacroRteContent = new Regex(@"(<div class=[""']umb-macro-holder[""'].*?>.*?<!--\s*?)(<\?UMBRACO_MACRO.*?/>)(.*?</div>)", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline);
|
||||
private static readonly Regex MacroRteContent = new Regex(@"(<div class=[""']umb-macro-holder.+?[""'].*?>.*?<!--\s*?)(<\?UMBRACO_MACRO.*?/>)(.*?</div>)", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline);
|
||||
private static readonly Regex MacroPersistedFormat = new Regex(@"<\?UMBRACO_MACRO macroAlias=[""'](\w+?)[""'].+?/>", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline);
|
||||
|
||||
/// <summary>
|
||||
@@ -35,8 +35,11 @@ namespace Umbraco.Core.Macros
|
||||
{
|
||||
if (match.Groups.Count >= 2)
|
||||
{
|
||||
//<div class="umb-macro-holder" data-load-content="false">
|
||||
var sb = new StringBuilder("<div class=\"umb-macro-holder\"");
|
||||
//<div class="umb-macro-holder myMacro mceNonEditable">
|
||||
var alias = match.Groups[1].Value;
|
||||
var sb = new StringBuilder("<div class=\"umb-macro-holder ");
|
||||
sb.Append(alias);
|
||||
sb.Append(" mceNonEditable\"");
|
||||
foreach (var htmlAttribute in htmlAttributes)
|
||||
{
|
||||
sb.Append(" ");
|
||||
@@ -51,7 +54,7 @@ namespace Umbraco.Core.Macros
|
||||
sb.AppendLine(" -->");
|
||||
sb.Append("Macro alias: ");
|
||||
sb.Append("<strong>");
|
||||
sb.Append(match.Groups[1].Value);
|
||||
sb.Append(alias);
|
||||
sb.Append("</strong></div>");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace Umbraco.Tests.Macros
|
||||
|
||||
Assert.AreEqual(@"<p>asdfasdf</p>
|
||||
<p>asdfsadf</p>
|
||||
<div class=""umb-macro-holder"" test1=""value1"" test2=""value2"">
|
||||
<div class=""umb-macro-holder Map mceNonEditable"" test1=""value1"" test2=""value2"">
|
||||
<!-- <?UMBRACO_MACRO macroAlias=""Map"" /> -->
|
||||
Macro alias: <strong>Map</strong></div>
|
||||
<p>asdfasdf</p>", result);
|
||||
@@ -30,7 +30,7 @@ Macro alias: <strong>Map</strong></div>
|
||||
var content = @"<html>
|
||||
<body>
|
||||
<h1>asdfasdf</h1>
|
||||
<div class='umb-macro-holder' att1='asdf' att2='asdfasdfasdf' att3=""sdfsdfd"">
|
||||
<div class='umb-macro-holder Map mceNonEditable' att1='asdf' att2='asdfasdfasdf' att3=""sdfsdfd"">
|
||||
<!-- <?UMBRACO_MACRO macroAlias=""myMacro"" param1=""test1"" param2=""test2"" /> -->
|
||||
asdfasdf
|
||||
asdfas
|
||||
|
||||
@@ -83,3 +83,13 @@ td.mce-item-selected, th.mce-item-selected {
|
||||
display:block;
|
||||
margin:3px;
|
||||
}
|
||||
|
||||
/* loader for macro loading in tinymce*/
|
||||
.mce-content-body .umb-macro-holder.loading {
|
||||
background: url(img/loader.gif) right no-repeat;
|
||||
-moz-background-size: 18px;
|
||||
-o-background-size: 18px;
|
||||
-webkit-background-size: 18px;
|
||||
background-size: 18px;
|
||||
background-position-x: 99%;
|
||||
}
|
||||
@@ -11,7 +11,7 @@ function macroResource($q, $http, umbRequestHelper) {
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name umbraco.resources.entityResource#getMacroParameters
|
||||
* @name umbraco.resources.macroResource#getMacroParameters
|
||||
* @methodOf umbraco.resources.macroResource
|
||||
*
|
||||
* @description
|
||||
@@ -28,6 +28,28 @@ function macroResource($q, $http, umbRequestHelper) {
|
||||
"GetMacroParameters",
|
||||
[{ macroId: macroId }])),
|
||||
'Failed to retreive macro parameters for macro with id ' + macroId);
|
||||
},
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name umbraco.resources.macroResource#getMacroResult
|
||||
* @methodOf umbraco.resources.macroResource
|
||||
*
|
||||
* @description
|
||||
* Gets the result of a macro as html to display in the rich text editor
|
||||
*
|
||||
* @param {int} macroId The macro id to get parameters for
|
||||
* @param {int} pageId The current page id
|
||||
*
|
||||
*/
|
||||
getMacroResultAsHtmlForEditor: function (macroAlias, pageId) {
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"macroApiBaseUrl",
|
||||
"GetMacroResultAsHtmlForEditor",
|
||||
[{ macroAlias: macroAlias }, { pageId: pageId }])),
|
||||
'Failed to retreive macro result for macro with alias ' + macroAlias);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* @description
|
||||
* A service containing all logic for all of the Umbraco TinyMCE plugins
|
||||
*/
|
||||
function tinyMceService(dialogService, $log, imageHelper, assetsService, $timeout) {
|
||||
function tinyMceService(dialogService, $log, imageHelper, assetsService, $timeout, macroResource) {
|
||||
return {
|
||||
|
||||
/**
|
||||
@@ -126,8 +126,7 @@ function tinyMceService(dialogService, $log, imageHelper, assetsService, $timeou
|
||||
* @param {Object} $scope the current controller scope
|
||||
*/
|
||||
createInsertMacro: function (editor, $scope) {
|
||||
|
||||
|
||||
|
||||
/** Adds custom rules for the macro plugin and custom serialization */
|
||||
editor.on('preInit', function (args) {
|
||||
//this is requires so that we tell the serializer that a 'div' is actually allowed in the root, otherwise the cleanup will strip it out
|
||||
@@ -141,13 +140,9 @@ function tinyMceService(dialogService, $log, imageHelper, assetsService, $timeou
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
///** Listens for the editor saving */
|
||||
//$scope.on("saving", function() {
|
||||
|
||||
//});
|
||||
|
||||
/** Adds the button instance */
|
||||
editor.addButton('umbmacro', {
|
||||
icon: 'custom icon-settings-alt',
|
||||
@@ -194,7 +189,7 @@ function tinyMceService(dialogService, $log, imageHelper, assetsService, $timeou
|
||||
|
||||
//remove the event listener before re-selecting
|
||||
editor.off('NodeChange', onNodeChanged);
|
||||
|
||||
|
||||
// move selection to top element to ensure we can't edit this
|
||||
editor.selection.select(macroElement);
|
||||
|
||||
@@ -221,6 +216,31 @@ function tinyMceService(dialogService, $log, imageHelper, assetsService, $timeou
|
||||
|
||||
}
|
||||
|
||||
/** This prevents any other commands from executing when the current element is the macro so the content cannot be edited */
|
||||
editor.on('BeforeExecCommand', function (o) {
|
||||
if (isOnMacroElement) {
|
||||
if (o.preventDefault) {
|
||||
o.preventDefault();
|
||||
}
|
||||
if (o.stopImmediatePropagation) {
|
||||
o.stopImmediatePropagation();
|
||||
}
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
/** This double checks and ensures you can't paste content into the rendered macro */
|
||||
editor.on("Paste", function (o) {
|
||||
if (isOnMacroElement) {
|
||||
if (o.preventDefault) {
|
||||
o.preventDefault();
|
||||
}
|
||||
if (o.stopImmediatePropagation) {
|
||||
o.stopImmediatePropagation();
|
||||
}
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
//set onNodeChanged event listener
|
||||
editor.on('NodeChange', onNodeChanged);
|
||||
@@ -264,7 +284,8 @@ function tinyMceService(dialogService, $log, imageHelper, assetsService, $timeou
|
||||
};
|
||||
|
||||
//supported keys to move to the next or prev element (13-enter, 27-esc, 38-up, 40-down, 39-right, 37-left)
|
||||
//supported keys to remove the macro (8-backspace, 46-delete)
|
||||
//supported keys to remove the macro (8-backspace, 46-delete)
|
||||
//TODO: Should we make the enter key insert a line break before or leave it as moving to the next element?
|
||||
if ($.inArray(e.keyCode, [13, 40, 39]) !== -1) {
|
||||
//move to next element
|
||||
moveSibling(macroElement, true);
|
||||
@@ -280,13 +301,13 @@ function tinyMceService(dialogService, $log, imageHelper, assetsService, $timeou
|
||||
moveSibling(macroElement, false);
|
||||
editor.dom.remove(macroElement);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
return ;
|
||||
}
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
/** The insert macro button click event handler */
|
||||
onclick: function () {
|
||||
|
||||
dialogService.open({
|
||||
@@ -298,16 +319,31 @@ function tinyMceService(dialogService, $log, imageHelper, assetsService, $timeou
|
||||
//put the macro syntax in comments, we will parse this out on the server side to be used
|
||||
//for persisting.
|
||||
var macroSyntaxComment = "<!-- " + data.syntax + " -->";
|
||||
//create an id class for this element so we can re-select it after inserting
|
||||
var uniqueId = "umb-macro-" + editor.dom.uniqueId();
|
||||
var macroDiv = editor.dom.create('div',
|
||||
{
|
||||
'class': 'umb-macro-holder ' + data.macroAlias + ' mceNonEditable ' + uniqueId
|
||||
},
|
||||
macroSyntaxComment + '<ins>Macro alias: <strong>' + data.macroAlias + '</strong></ins>');
|
||||
|
||||
editor.insertContent(
|
||||
editor.dom.createHTML('div',
|
||||
{
|
||||
'class': 'umb-macro-holder',
|
||||
// indicates whether or not this should kick off the ajax request to load in the macro contents.
|
||||
'data-load-content': false
|
||||
},
|
||||
macroSyntaxComment + 'Macro alias: <strong>' + data.macroAlias + '</strong>'));
|
||||
editor.selection.setNode(macroDiv);
|
||||
|
||||
var $macroDiv = $(editor.dom.select("div.umb-macro-holder." + uniqueId));
|
||||
var $ins = $macroDiv.find("ins");
|
||||
|
||||
//show the throbber
|
||||
$macroDiv.addClass("loading");
|
||||
|
||||
macroResource.getMacroResultAsHtmlForEditor(data.macroAlias, 1234)
|
||||
.then(function (htmlResult) {
|
||||
|
||||
$macroDiv.removeClass("loading");
|
||||
htmlResult = htmlResult.trim();
|
||||
if (htmlResult !== "") {
|
||||
$ins.html(htmlResult);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -91,7 +91,6 @@ iframe, .content-column-body {
|
||||
.mce-panel{background: @grayLighter !important; border-color: @grayLight !important;}
|
||||
.mce-btn-group, .mce-btn{border: none !important; background: none !important;}
|
||||
.mce-ico{font-size: 12px !important; color: @blackLight !important;}
|
||||
|
||||
/* Special case to support helviticons for the tiny mce button controls */
|
||||
.mce-ico.mce-i-custom[class^="icon-"],
|
||||
.mce-ico.mce-i-custom[class*=" icon-"] {
|
||||
|
||||
@@ -1,9 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Web;
|
||||
using System.Web.Http;
|
||||
using AutoMapper;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Web.Models;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Web.Mvc;
|
||||
using Umbraco.Web.Routing;
|
||||
using umbraco;
|
||||
|
||||
namespace Umbraco.Web.Editors
|
||||
{
|
||||
@@ -31,5 +41,62 @@ namespace Umbraco.Web.Editors
|
||||
|
||||
return Mapper.Map<IEnumerable<MacroParameter>>(macro);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a rendered macro as html for rendering in the rich text editor
|
||||
/// </summary>
|
||||
/// <param name="macroAlias"></param>
|
||||
/// <param name="pageId"></param>
|
||||
/// <returns></returns>
|
||||
public HttpResponseMessage GetMacroResultAsHtmlForEditor(string macroAlias, int pageId)
|
||||
{
|
||||
var doc = Services.ContentService.GetById(pageId);
|
||||
if (doc == null)
|
||||
{
|
||||
throw new HttpResponseException(HttpStatusCode.NotFound);
|
||||
}
|
||||
|
||||
//need to get a legacy macro object - eventually we'll have a new format but nto yet
|
||||
var macro = new macro(macroAlias);
|
||||
if (macro == null)
|
||||
{
|
||||
throw new HttpResponseException(HttpStatusCode.NotFound);
|
||||
}
|
||||
|
||||
//if it isn't supposed to be rendered in the editor then return an empty string
|
||||
if (macro.DontRenderInEditor)
|
||||
{
|
||||
return new HttpResponseMessage()
|
||||
{
|
||||
//need to create a specific content result formatted as html since this controller has been configured
|
||||
//with only json formatters.
|
||||
Content = new StringContent(string.Empty, Encoding.UTF8, "text/html")
|
||||
};
|
||||
}
|
||||
|
||||
//because macro's are filled with insane legacy bits and pieces we need all sorts of wierdness to make them render.
|
||||
//the 'easiest' way might be to create an IPublishedContent manually and populate the legacy 'page' object with that
|
||||
//and then set the legacy parameters.
|
||||
|
||||
var xml = doc.ToXml();
|
||||
var publishedContent = new XmlPublishedContent(xml.ToXmlElement());
|
||||
|
||||
var legacyPage = new global::umbraco.page(publishedContent);
|
||||
UmbracoContext.HttpContext.Items["pageID"] = doc.Id;
|
||||
UmbracoContext.HttpContext.Items["pageElements"] = legacyPage.Elements;
|
||||
UmbracoContext.HttpContext.Items[global::Umbraco.Core.Constants.Conventions.Url.AltTemplate] = null;
|
||||
|
||||
return new HttpResponseMessage()
|
||||
{
|
||||
//need to create a specific content result formatted as html since this controller has been configured
|
||||
//with only json formatters.
|
||||
Content = new StringContent(
|
||||
Umbraco.RenderMacro(macro, new Dictionary<string, object>(), legacyPage).ToString(),
|
||||
Encoding.UTF8,
|
||||
"text/html"
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -376,6 +376,8 @@ namespace Umbraco.Web
|
||||
// TODO - this is dirty old legacy tricks, we should clean it up at some point
|
||||
// also, what is a "custom page" and when should this be either null, or different
|
||||
// from PublishedContentRequest.PublishedContent.Id ??
|
||||
// SD: Have found out it can be different when rendering macro contents in the back office, but really youshould just be able
|
||||
// to pass a page id to the macro renderer instead but due to all the legacy bits that's real difficult.
|
||||
get
|
||||
{
|
||||
try
|
||||
|
||||
@@ -138,71 +138,107 @@ namespace Umbraco.Web
|
||||
/// <returns></returns>
|
||||
public IHtmlString RenderMacro(string alias, IDictionary<string, object> parameters)
|
||||
{
|
||||
if (alias == null) throw new ArgumentNullException("alias");
|
||||
|
||||
var m = macro.GetMacro(alias);
|
||||
if (_umbracoContext.PageId == null)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot render a macro when UmbracoContext.PageId is null.");
|
||||
}
|
||||
|
||||
if (_umbracoContext.PublishedContentRequest == null)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot render a macro when there is no current PublishedContentRequest.");
|
||||
}
|
||||
var macroProps = new Hashtable();
|
||||
foreach (var i in parameters)
|
||||
{
|
||||
//TODO: We are doing at ToLower here because for some insane reason the UpdateMacroModel method of macro.cs
|
||||
// looks for a lower case match. WTF. the whole macro concept needs to be rewritten.
|
||||
macroProps.Add(i.Key.ToLower(), i.Value);
|
||||
}
|
||||
var macroControl = m.renderMacro(macroProps,
|
||||
UmbracoContext.Current.PublishedContentRequest.UmbracoPage.Elements,
|
||||
_umbracoContext.PageId.Value);
|
||||
|
||||
string html;
|
||||
if (macroControl is LiteralControl)
|
||||
{
|
||||
// no need to execute, we already have text
|
||||
html = (macroControl as LiteralControl).Text;
|
||||
}
|
||||
else
|
||||
{
|
||||
var containerPage = new FormlessPage();
|
||||
containerPage.Controls.Add(macroControl);
|
||||
return RenderMacro(alias, parameters, _umbracoContext.PublishedContentRequest.UmbracoPage);
|
||||
}
|
||||
|
||||
using (var output = new StringWriter())
|
||||
{
|
||||
// .Execute() does a PushTraceContext/PopTraceContext and writes trace output straight into 'output'
|
||||
// and I do not see how we could wire the trace context to the current context... so it creates dirty
|
||||
// trace output right in the middle of the page.
|
||||
//
|
||||
// The only thing we can do is fully disable trace output while .Execute() runs and restore afterwards
|
||||
// which means trace output is lost if the macro is a control (.ascx or user control) that is invoked
|
||||
// from within Razor -- which makes sense anyway because the control can _not_ run correctly from
|
||||
// within Razor since it will never be inserted into the page pipeline (which may even not exist at all
|
||||
// if we're running MVC).
|
||||
//
|
||||
/// <summary>
|
||||
/// Renders the macro with the specified alias, passing in the specified parameters.
|
||||
/// </summary>
|
||||
/// <param name="alias">The alias.</param>
|
||||
/// <param name="parameters">The parameters.</param>
|
||||
/// <param name="umbracoPage">The legacy umbraco page object that is required for some macros</param>
|
||||
/// <returns></returns>
|
||||
internal IHtmlString RenderMacro(string alias, IDictionary<string, object> parameters, page umbracoPage)
|
||||
{
|
||||
if (alias == null) throw new ArgumentNullException("alias");
|
||||
if (umbracoPage == null) throw new ArgumentNullException("umbracoPage");
|
||||
|
||||
var m = macro.GetMacro(alias);
|
||||
if (m == null)
|
||||
{
|
||||
throw new KeyNotFoundException("Could not find macro with alias " + alias);
|
||||
}
|
||||
|
||||
return RenderMacro(m, parameters, umbracoPage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Renders the macro with the specified alias, passing in the specified parameters.
|
||||
/// </summary>
|
||||
/// <param name="m">The macro.</param>
|
||||
/// <param name="parameters">The parameters.</param>
|
||||
/// <param name="umbracoPage">The legacy umbraco page object that is required for some macros</param>
|
||||
/// <returns></returns>
|
||||
internal IHtmlString RenderMacro(macro m, IDictionary<string, object> parameters, page umbracoPage)
|
||||
{
|
||||
if (umbracoPage == null) throw new ArgumentNullException("umbracoPage");
|
||||
if (m == null) throw new ArgumentNullException("m");
|
||||
|
||||
if (_umbracoContext.PageId == null)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot render a macro when UmbracoContext.PageId is null.");
|
||||
}
|
||||
|
||||
var macroProps = new Hashtable();
|
||||
foreach (var i in parameters)
|
||||
{
|
||||
//TODO: We are doing at ToLower here because for some insane reason the UpdateMacroModel method of macro.cs
|
||||
// looks for a lower case match. WTF. the whole macro concept needs to be rewritten.
|
||||
macroProps.Add(i.Key.ToLower(), i.Value);
|
||||
}
|
||||
var macroControl = m.renderMacro(macroProps,
|
||||
umbracoPage.Elements,
|
||||
_umbracoContext.PageId.Value);
|
||||
|
||||
string html;
|
||||
if (macroControl is LiteralControl)
|
||||
{
|
||||
// no need to execute, we already have text
|
||||
html = (macroControl as LiteralControl).Text;
|
||||
}
|
||||
else
|
||||
{
|
||||
var containerPage = new FormlessPage();
|
||||
containerPage.Controls.Add(macroControl);
|
||||
|
||||
using (var output = new StringWriter())
|
||||
{
|
||||
// .Execute() does a PushTraceContext/PopTraceContext and writes trace output straight into 'output'
|
||||
// and I do not see how we could wire the trace context to the current context... so it creates dirty
|
||||
// trace output right in the middle of the page.
|
||||
//
|
||||
// The only thing we can do is fully disable trace output while .Execute() runs and restore afterwards
|
||||
// which means trace output is lost if the macro is a control (.ascx or user control) that is invoked
|
||||
// from within Razor -- which makes sense anyway because the control can _not_ run correctly from
|
||||
// within Razor since it will never be inserted into the page pipeline (which may even not exist at all
|
||||
// if we're running MVC).
|
||||
//
|
||||
// I'm sure there's more things that will get lost with this context changing but I guess we'll figure
|
||||
// those out as we go along. One thing we lose is the content type response output.
|
||||
// http://issues.umbraco.org/issue/U4-1599 if it is setup during the macro execution. So
|
||||
// here we'll save the content type response and reset it after execute is called.
|
||||
|
||||
var contentType = _umbracoContext.HttpContext.Response.ContentType;
|
||||
var traceIsEnabled = containerPage.Trace.IsEnabled;
|
||||
containerPage.Trace.IsEnabled = false;
|
||||
_umbracoContext.HttpContext.Server.Execute(containerPage, output, false);
|
||||
containerPage.Trace.IsEnabled = traceIsEnabled;
|
||||
var contentType = _umbracoContext.HttpContext.Response.ContentType;
|
||||
var traceIsEnabled = containerPage.Trace.IsEnabled;
|
||||
containerPage.Trace.IsEnabled = false;
|
||||
_umbracoContext.HttpContext.Server.Execute(containerPage, output, false);
|
||||
containerPage.Trace.IsEnabled = traceIsEnabled;
|
||||
//reset the content type
|
||||
_umbracoContext.HttpContext.Response.ContentType = contentType;
|
||||
_umbracoContext.HttpContext.Response.ContentType = contentType;
|
||||
|
||||
//Now, we need to ensure that local links are parsed
|
||||
html = TemplateUtilities.ParseInternalLinks(output.ToString());
|
||||
}
|
||||
}
|
||||
//Now, we need to ensure that local links are parsed
|
||||
html = TemplateUtilities.ParseInternalLinks(output.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
return new HtmlString(html);
|
||||
}
|
||||
return new HtmlString(html);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace umbraco.presentation
|
||||
/// <summary>
|
||||
/// Summary description for macroResultWrapper.
|
||||
/// </summary>
|
||||
[Obsolete("This is no longer used and will be removed from the codebase")]
|
||||
public partial class macroResultWrapper : BasePages.UmbracoEnsuredPage
|
||||
{
|
||||
|
||||
|
||||
Reference in New Issue
Block a user