mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2026-02-09 09:16:41 +08:00
Content item specific shapes
Content manager creating shape names based on display mode Template harvester converting known file patterns into appropriate shape names --HG-- branch : theming extra : rebase_source : a4d45b76ea0251bb3d6b7f24dba92563cc63addf
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using NUnit.Framework;
|
||||
using Orchard.DisplayManagement.Descriptors.ShapeTemplateStrategy;
|
||||
|
||||
namespace Orchard.Tests.DisplayManagement.Descriptors {
|
||||
[TestFixture]
|
||||
public class BasicShapeTemplateHarvesterTests {
|
||||
private static void VerifyShapeType(string givenSubPath, string givenFileName, string expectedShapeType) {
|
||||
var harvester = new BasicShapeTemplateHarvester(Enumerable.Empty<IShapeTemplateViewEngine>());
|
||||
var harvestShapeHits = harvester.HarvestShape(new HarvestShapeInfo { SubPath = givenSubPath, FileName = givenFileName });
|
||||
Assert.That(harvestShapeHits.Count(), Is.EqualTo(1));
|
||||
Assert.That(harvestShapeHits.Single().ShapeType, Is.EqualTo(expectedShapeType));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BasicFileNamesComeBackAsShapes() {
|
||||
VerifyShapeType("Views", "Hello", "Hello");
|
||||
VerifyShapeType("Views", "World", "World");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DashBecomesBreakingSeperator() {
|
||||
VerifyShapeType("Views", "Hello-World", "Hello__World");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DotBecomesNonBreakingSeperator() {
|
||||
VerifyShapeType("Views", "Hello.World", "Hello_World");
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void DefaultItemsContentTemplate() {
|
||||
VerifyShapeType("Views/Items", "Content", "Items_Content");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ImplicitSpecializationOfItemsContentTemplate() {
|
||||
VerifyShapeType("Views/Items", "MyType", "Items_Content__MyType");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ExplicitSpecializationOfItemsContentTemplate() {
|
||||
VerifyShapeType("Views/Items", "Content-MyType", "Items_Content__MyType");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ContentItemDisplayTypes() {
|
||||
VerifyShapeType("Views/Items", "Content", "Items_Content");
|
||||
VerifyShapeType("Views/Items", "Content.Summary", "Items_Content_Summary");
|
||||
VerifyShapeType("Views/Items", "Content.Edit", "Items_Content_Edit");
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void ExplicitSpecializationMixedWithDisplayTypes() {
|
||||
VerifyShapeType("Views/Items", "Content-MyType", "Items_Content__MyType");
|
||||
VerifyShapeType("Views/Items", "Content-MyType.Summary", "Items_Content_Summary__MyType");
|
||||
VerifyShapeType("Views/Items", "Content-MyType.Edit", "Items_Content_Edit__MyType");
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void ImplicitSpecializationMixedWithDisplayTypes() {
|
||||
VerifyShapeType("Views/Items", "MyType", "Items_Content__MyType");
|
||||
VerifyShapeType("Views/Items", "MyType.Summary", "Items_Content_Summary__MyType");
|
||||
VerifyShapeType("Views/Items", "MyType.Edit", "Items_Content_Edit__MyType");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -196,6 +196,7 @@
|
||||
<Compile Include="DisplayManagement\ArgsUtility.cs" />
|
||||
<Compile Include="DisplayManagement\DefaultDisplayManagerTests.cs" />
|
||||
<Compile Include="ContainerTestBase.cs" />
|
||||
<Compile Include="DisplayManagement\Descriptors\BasicShapeTemplateHarvesterTests.cs" />
|
||||
<Compile Include="DisplayManagement\Descriptors\DefaultShapeTableFactoryTests.cs" />
|
||||
<Compile Include="DisplayManagement\Descriptors\DefaultShapeTableManagerTests.cs" />
|
||||
<Compile Include="DisplayManagement\Descriptors\ShapeAttributeBindingStrategyTests.cs" />
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
content item -> @Model.ContentItem.ContentType
|
||||
|
||||
@Display(Model.primary)
|
||||
@@ -0,0 +1,3 @@
|
||||
content item -> @Model.ContentItem.ContentType
|
||||
|
||||
@Display(Model.primary)
|
||||
@@ -400,6 +400,8 @@
|
||||
<Content Include="ContentsLocation\Views\Web.config" />
|
||||
<Content Include="Messaging\Views\Web.config" />
|
||||
<None Include="Contents\Views\Items\Content.cshtml" />
|
||||
<None Include="Contents\Views\Items\Content.Edit.cshtml" />
|
||||
<None Include="Contents\Views\Items\Content.Summary.cshtml" />
|
||||
<None Include="Contents\Views\Item\Display.cshtml" />
|
||||
<None Include="HomePage\Views\HomePage.cshtml" />
|
||||
<None Include="Shapes\Views\Document.cshtml" />
|
||||
|
||||
66
src/Orchard.Web/Modules/Orchard.DevTools/DebugFilter.cs
Normal file
66
src/Orchard.Web/Modules/Orchard.DevTools/DebugFilter.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using System.Web.Mvc;
|
||||
using System.Xml;
|
||||
using Orchard.DisplayManagement.Shapes;
|
||||
using Orchard.Mvc.Filters;
|
||||
|
||||
namespace Orchard.DevTools {
|
||||
public class DebugFilter : FilterProvider, IActionFilter {
|
||||
public void OnActionExecuting(ActionExecutingContext filterContext) {
|
||||
}
|
||||
|
||||
public void OnActionExecuted(ActionExecutedContext filterContext) {
|
||||
var viewResultBase = filterContext.Result as ViewResultBase;
|
||||
var debugValueResult = filterContext.Controller.ValueProvider.GetValue("$debug");
|
||||
if (debugValueResult == null)
|
||||
return;
|
||||
|
||||
var debugValue = (string)debugValueResult.ConvertTo(typeof(string));
|
||||
if (debugValue == "model" && viewResultBase != null) {
|
||||
filterContext.Result = new DebugModelResult(viewResultBase);
|
||||
}
|
||||
}
|
||||
|
||||
public class DebugModelResult : ActionResult {
|
||||
private readonly ViewResultBase _viewResultBase;
|
||||
|
||||
public DebugModelResult(ViewResultBase viewResultBase) {
|
||||
_viewResultBase = viewResultBase;
|
||||
}
|
||||
|
||||
public override void ExecuteResult(ControllerContext context) {
|
||||
context.HttpContext.Response.ContentType = "application/xml";
|
||||
var output = context.HttpContext.Response.Output;
|
||||
using (var writer = XmlWriter.Create(output, new XmlWriterSettings { Indent = true, IndentChars = " " })) {
|
||||
try {
|
||||
Writer = writer;
|
||||
var model = _viewResultBase.ViewData.Model;
|
||||
Accept(model);
|
||||
}
|
||||
finally {
|
||||
Writer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected XmlWriter Writer { get; set; }
|
||||
|
||||
void Accept(dynamic model) {
|
||||
Shape shape = model;
|
||||
Visit(shape);
|
||||
}
|
||||
|
||||
void Visit(Shape shape) {
|
||||
Writer.WriteStartElement("Shape");
|
||||
Writer.WriteAttributeString("Type", shape.Metadata.Type);
|
||||
foreach (var item in shape.Items) {
|
||||
Accept(item);
|
||||
}
|
||||
Writer.WriteEndElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,6 +79,7 @@
|
||||
<Compile Include="Controllers\HomeController.cs" />
|
||||
<Compile Include="Controllers\InventoryController.cs" />
|
||||
<Compile Include="Controllers\MetadataController.cs" />
|
||||
<Compile Include="DebugFilter.cs" />
|
||||
<Compile Include="Handlers\DebugLinkHandler.cs" />
|
||||
<Compile Include="Models\ShowDebugLink.cs" />
|
||||
<Compile Include="Models\Simple.cs" />
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Autofac;
|
||||
using ClaySharp.Implementation;
|
||||
using Microsoft.CSharp.RuntimeBinder;
|
||||
using Orchard.ContentManagement.Handlers;
|
||||
using Orchard.ContentManagement.MetaData;
|
||||
using Orchard.ContentManagement.MetaData.Builders;
|
||||
@@ -368,17 +371,32 @@ namespace Orchard.ContentManagement {
|
||||
return context.Metadata;
|
||||
}
|
||||
|
||||
static readonly CallSiteCollection _shapeHelperCalls = new CallSiteCollection(shapeTypeName => Binder.InvokeMember(
|
||||
CSharpBinderFlags.None,
|
||||
shapeTypeName,
|
||||
Enumerable.Empty<Type>(),
|
||||
null,
|
||||
new[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) }));
|
||||
|
||||
|
||||
public dynamic BuildDisplayModel<TContent>(TContent content, string displayType) where TContent : IContent {
|
||||
var shapeHelper = _shapeHelperFactory.CreateHelper();
|
||||
var itemShape = shapeHelper.Items_Content(ContentItem:content.ContentItem);
|
||||
|
||||
var shapeTypeName = string.IsNullOrEmpty(displayType) ? "Items_Content" : ("Items_Content_" + displayType);
|
||||
_shapeHelperCalls.Invoke(shapeHelper, shapeTypeName);
|
||||
var itemShape = shapeHelper.Items_Content();
|
||||
|
||||
itemShape.ContentItem = content.ContentItem;
|
||||
|
||||
var context = new BuildDisplayModelContext(content, displayType, itemShape, _shapeHelperFactory);
|
||||
Handlers.Invoke(handler => handler.BuildDisplayShape(context), Logger);
|
||||
return context.Model;
|
||||
}
|
||||
|
||||
|
||||
public dynamic BuildEditorModel<TContent>(TContent content) where TContent : IContent {
|
||||
var shapeHelper = _shapeHelperFactory.CreateHelper();
|
||||
var itemShape = shapeHelper.Items_Content(ContentItem: content.ContentItem);
|
||||
var itemShape = shapeHelper.Items_Content_Edit(ContentItem: content.ContentItem);
|
||||
var context = new BuildEditorModelContext(content, itemShape, _shapeHelperFactory);
|
||||
Handlers.Invoke(handler => handler.BuildEditorShape(context), Logger);
|
||||
return context.Model;
|
||||
@@ -386,7 +404,7 @@ namespace Orchard.ContentManagement {
|
||||
|
||||
public dynamic UpdateEditorModel<TContent>(TContent content, IUpdateModel updater) where TContent : IContent {
|
||||
var shapeHelper = _shapeHelperFactory.CreateHelper();
|
||||
var itemShape = shapeHelper.Items_Content(ContentItem: content.ContentItem);
|
||||
var itemShape = shapeHelper.Items_Content_Edit(ContentItem: content.ContentItem);
|
||||
var context = new UpdateEditorModelContext(content, updater, itemShape, _shapeHelperFactory);
|
||||
Handlers.Invoke(handler => handler.UpdateEditorShape(context), Logger);
|
||||
return context.Model;
|
||||
@@ -426,4 +444,20 @@ namespace Orchard.ContentManagement {
|
||||
// : new NullSearchBuilder();
|
||||
//}
|
||||
}
|
||||
class CallSiteCollection : ConcurrentDictionary<string, CallSite<Func<CallSite, object, object>>> {
|
||||
readonly Func<string, CallSite<Func<CallSite, object, object>>> _valueFactory;
|
||||
|
||||
public CallSiteCollection(Func<string, CallSite<Func<CallSite, object, object>>> callSiteFactory) {
|
||||
_valueFactory = callSiteFactory;
|
||||
}
|
||||
|
||||
public CallSiteCollection(Func<string, CallSiteBinder> callSiteBinderFactory) {
|
||||
_valueFactory = key => CallSite<Func<CallSite, object, object>>.Create(callSiteBinderFactory(key));
|
||||
}
|
||||
|
||||
public object Invoke(object callee, string key) {
|
||||
var callSite = GetOrAdd(key, _valueFactory);
|
||||
return callSite.Target(callSite, callee);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,29 +29,41 @@ namespace Orchard.DisplayManagement.Descriptors.ShapeTemplateStrategy {
|
||||
var lastDot = info.FileName.IndexOf('.');
|
||||
if (lastDot <= 0) {
|
||||
yield return new HarvestShapeHit {
|
||||
ShapeType = Adjust(info.SubPath, info.FileName)
|
||||
ShapeType = Adjust(info.SubPath, info.FileName, null)
|
||||
};
|
||||
}
|
||||
else {
|
||||
var displayType = info.FileName.Substring(lastDot + 1);
|
||||
yield return new HarvestShapeHit {
|
||||
ShapeType = Adjust(info.SubPath, info.FileName.Substring(0, lastDot)),
|
||||
DisplayType = info.FileName.Substring(lastDot + 1)
|
||||
ShapeType = Adjust(info.SubPath, info.FileName.Substring(0, lastDot), displayType),
|
||||
DisplayType = displayType
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
static string Adjust(string subPath, string fileName) {
|
||||
var leader="";
|
||||
static string Adjust(string subPath, string fileName, string displayType) {
|
||||
var leader = "";
|
||||
if (subPath.StartsWith("Views/")) {
|
||||
leader = subPath.Substring("Views/".Length) + "_";
|
||||
}
|
||||
|
||||
if (leader == "Items_" && !fileName.StartsWith("Content")) {
|
||||
leader = "Items_Content__";
|
||||
}
|
||||
|
||||
// canonical shape type names must not have - or . to be compatible
|
||||
// with display and shape api calls)))
|
||||
return leader + fileName.Replace("--", "__").Replace("-", "__").Replace('.', '_');
|
||||
var shapeType = leader + fileName.Replace("--", "__").Replace("-", "__").Replace('.', '_');
|
||||
|
||||
if (string.IsNullOrEmpty(displayType)) {
|
||||
return shapeType;
|
||||
}
|
||||
var firstBreakingSeparator = shapeType.IndexOf("__");
|
||||
if (firstBreakingSeparator <= 0) {
|
||||
return shapeType + "_" + displayType;
|
||||
}
|
||||
|
||||
return shapeType.Substring(0, firstBreakingSeparator) + "_" + displayType + shapeType.Substring(firstBreakingSeparator);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -63,13 +63,27 @@ Parts/Content
|
||||
|
||||
Fields/Content
|
||||
|
||||
"Items_Content"
|
||||
"Items_Content_Summary"
|
||||
"Items_Content_Edit"
|
||||
|
||||
base + "__" + id
|
||||
base + "__" + contenttype
|
||||
|
||||
|
||||
==template discovery strategy==
|
||||
Items/Content.cshtml -> "Items_Content"
|
||||
Items/Content.Summary.cshtml -> "Items_Content"
|
||||
Items/Content.Edit.cshtml -> "Items_Content"
|
||||
Items/Content-Page.cshtml -> "Items_Content__Page"
|
||||
Items/Content-45.cshtml -> "Items_Content__45"
|
||||
Items/Content-45.Summary.cshtml -> "Items_Content_Summary__45"
|
||||
Items/Content.Summary-45.cshtml -> "Items_Content_Summary__Page"
|
||||
Items/Page.cshtml -> "Items_Content__Page"
|
||||
|
||||
Items/BlogPost.cshtml -> "Items_Content__BlogPost"
|
||||
Items/BlogPost.Summary.cshtml -> "Items_Content__BlogPost"
|
||||
Items/BlogPost.Edit.cshtml -> "Items_Content__BlogPost"
|
||||
|
||||
Widgets-TwitterThing.cshtml -> "Widget__TwitterThing"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user