Tabs and Cards to better organize parts and fields (#8310)

This commit is contained in:
Matteo Piovanelli
2020-02-06 21:16:16 +01:00
committed by GitHub
parent e4ac0c02bc
commit 937702479c
9 changed files with 284 additions and 92 deletions

View File

@@ -31,11 +31,11 @@ namespace Orchard.Core.Shapes {
private readonly IResourceFileHashProvider _resourceFileHashProvider;
public CoreShapes(
Work<WorkContext> workContext,
Work<WorkContext> workContext,
Work<IResourceManager> resourceManager,
Work<IHttpContextAccessor> httpContextAccessor,
Work<IShapeFactory> shapeFactory,
IResourceFileHashProvider resourceHashProvider
IResourceFileHashProvider resourceHashProvider
) {
_workContext = workContext;
_resourceManager = resourceManager;
@@ -47,7 +47,7 @@ namespace Orchard.Core.Shapes {
}
public Localizer T { get; set; }
public dynamic New { get { return _shapeFactory.Value; } }
public dynamic New { get { return _shapeFactory.Value; } }
public void Discover(ShapeTableBuilder builder) {
// the root page shape named 'Layout' is wrapped with 'Document'
@@ -57,7 +57,7 @@ namespace Orchard.Core.Shapes {
.OnCreating(creating => creating.Create = () => new ZoneHolding(() => creating.New.Zone()))
.OnCreated(created => {
var layout = created.Shape;
layout.Head = created.New.DocumentZone(ZoneName: "Head");
layout.Body = created.New.DocumentZone(ZoneName: "Body");
layout.Tail = created.New.DocumentZone(ZoneName: "Tail");
@@ -112,7 +112,7 @@ namespace Orchard.Core.Shapes {
string contentType = null;
int level = menuItem.Level;
if (menuItem.Content != null) {
contentType = ((IContent) menuItem.Content).ContentItem.ContentType;
contentType = ((IContent)menuItem.Content).ContentItem.ContentType;
}
menuItem.Metadata.Alternates.Add("MenuItemLink__level__" + level);
@@ -145,7 +145,7 @@ namespace Orchard.Core.Shapes {
menu.Classes.Add("localmenu");
menu.Metadata.Alternates.Add("LocalMenu__" + EncodeAlternateElement(menuName));
});
builder.Describe("LocalMenuItem")
.OnDisplaying(displaying => {
var menuItem = displaying.Shape;
@@ -258,6 +258,7 @@ namespace Orchard.Core.Shapes {
resource.Metadata.Alternates.Add("Resource__" + fileName);
}
});
}
@@ -286,24 +287,26 @@ namespace Orchard.Core.Shapes {
[Shape]
public void ContentZone(dynamic Display, dynamic Shape, TextWriter Output) {
var unordered = ((IEnumerable<dynamic>)Shape).ToArray();
var tabbed = unordered.GroupBy(x => (string)x.Metadata.Tab ?? "");
var ordered = Order(unordered);
var tabbed = ordered.GroupBy(x => (string)x.Metadata.Tab ?? "");
if (tabbed.Count() > 1) {
foreach (var tab in tabbed) {
var tabName = String.IsNullOrWhiteSpace(tab.Key) ? "Content" : tab.Key;
var tabBuilder = new TagBuilder("div");
tabBuilder.Attributes["id"] = "tab-" + tabName.HtmlClassify();
tabBuilder.Attributes["data-tab"] = tabName;
Output.Write(tabBuilder.ToString(TagRenderMode.StartTag));
foreach (var item in Order(tab))
Output.Write(Display(item));
Output.Write(tabBuilder.ToString(TagRenderMode.EndTag));
foreach (var tab in Order(tabbed)) {
Output.Write(Display(New.Tab(Tab:tab)));
}
}
else {
foreach (var item in Order(unordered))
Output.Write(Display(item));
var cards = ordered.GroupBy(x => (string)x.Metadata.Card ?? "");
if (cards.Count() > 1) {
foreach (var card in cards) {
Output.Write(Display(New.Card(Card: card, ContainerName: "Content")));
}
}
else {
foreach (var item in ordered)
Output.Write(Display(item));
}
}
}
@@ -361,7 +364,7 @@ namespace Orchard.Core.Shapes {
if (String.IsNullOrEmpty(tab))
continue;
if(!tabs.Contains(tab))
if (!tabs.Contains(tab))
tabs.Add(tab);
}
@@ -389,14 +392,14 @@ namespace Orchard.Core.Shapes {
[Shape]
public void Metas(TextWriter Output) {
foreach (var meta in _resourceManager.Value.GetRegisteredMetas() ) {
foreach (var meta in _resourceManager.Value.GetRegisteredMetas()) {
Output.WriteLine(meta.GetTag());
}
}
[Shape]
public void HeadLinks(TextWriter Output) {
foreach (var link in _resourceManager.Value.GetRegisteredLinks() ) {
foreach (var link in _resourceManager.Value.GetRegisteredLinks()) {
Output.WriteLine(link.GetTag());
}
}
@@ -470,7 +473,7 @@ namespace Orchard.Core.Shapes {
if (resourceType == "stylesheet") {
result = Display.Style(Url: url, Condition: condition, Resource: context.Resource, TagAttributes: attributes);
}
else if (resourceType == "script") {
else if (resourceType == "script") {
result = Display.Script(Url: url, Condition: condition, Resource: context.Resource, TagAttributes: attributes);
}
else {
@@ -502,11 +505,11 @@ namespace Orchard.Core.Shapes {
currentPage = 1;
var pageSize = PageSize;
var numberOfPagesToShow = Quantity ?? 0;
if (Quantity == null || Quantity < 0)
numberOfPagesToShow = 7;
var totalPageCount = pageSize > 0 ? (int)Math.Ceiling(TotalItemCount / pageSize) : 1;
var firstText = FirstText ?? T("&lt;&lt;");
@@ -531,7 +534,7 @@ namespace Orchard.Core.Shapes {
if (shapeRouteData == null) {
var route = shapeRoute as RouteData;
if (route != null) {
shapeRouteData = (route).Values;
shapeRouteData = (route).Values;
}
}
@@ -599,7 +602,7 @@ namespace Orchard.Core.Shapes {
if (lastPage < totalPageCount && numberOfPagesToShow > 0) {
Shape.Add(New.Pager_Gap(Value: gapText, Pager: Shape));
}
// next and last pages
if (Page < totalPageCount) {
// next
@@ -615,47 +618,47 @@ namespace Orchard.Core.Shapes {
[Shape]
public IHtmlString Pager(dynamic Shape, dynamic Display) {
Shape.Metadata.Alternates.Clear();
Shape.Metadata.Alternates.Clear();
Shape.Metadata.Type = "Pager_Links";
return Display(Shape);
}
[Shape]
public IHtmlString Pager_First(dynamic Shape, dynamic Display) {
Shape.Metadata.Alternates.Clear();
Shape.Metadata.Alternates.Clear();
Shape.Metadata.Type = "Pager_Link";
return Display(Shape);
}
[Shape]
public IHtmlString Pager_Previous(dynamic Shape, dynamic Display) {
Shape.Metadata.Alternates.Clear();
Shape.Metadata.Alternates.Clear();
Shape.Metadata.Type = "Pager_Link";
return Display(Shape);
}
[Shape]
public IHtmlString Pager_CurrentPage(HtmlHelper Html, dynamic Display, object Value) {
var tagBuilder = new TagBuilder("span");
tagBuilder.InnerHtml = EncodeOrDisplay(Value, Display, Html).ToString();
return MvcHtmlString.Create(tagBuilder.ToString());
}
[Shape]
public IHtmlString Pager_Next(dynamic Shape, dynamic Display) {
Shape.Metadata.Alternates.Clear();
Shape.Metadata.Alternates.Clear();
Shape.Metadata.Type = "Pager_Link";
return Display(Shape);
}
[Shape]
public IHtmlString Pager_Last(dynamic Shape, dynamic Display) {
Shape.Metadata.Alternates.Clear();
Shape.Metadata.Alternates.Clear();
Shape.Metadata.Type = "Pager_Link";
return Display(Shape);
}
[Shape]
public IHtmlString Pager_Link(HtmlHelper Html, dynamic Shape, dynamic Display, object Value) {
Shape.Metadata.Alternates.Clear();
@@ -709,7 +712,7 @@ namespace Orchard.Core.Shapes {
if (Items == null)
return;
// prevent multiple enumerations
var items = Items.ToList();
@@ -719,7 +722,7 @@ namespace Orchard.Core.Shapes {
return;
string listTagName = null;
if (Tag != "-") {
listTagName = string.IsNullOrEmpty(Tag) ? "ul" : Tag;
}
@@ -728,7 +731,7 @@ namespace Orchard.Core.Shapes {
string itemTagName = null;
if (ItemTag != "-") {
itemTagName = string.IsNullOrEmpty(ItemTag) ? "li" : ItemTag;
itemTagName = string.IsNullOrEmpty(ItemTag) ? "li" : ItemTag;
}
if (listTag != null) {
@@ -741,7 +744,7 @@ namespace Orchard.Core.Shapes {
// give the item shape the possibility to alter its container tag
var index = 0;
foreach (var item in items) {
var itemTag = String.IsNullOrEmpty(itemTagName) ? null : GetTagBuilder(itemTagName, null, ItemClasses, ItemAttributes);
if (item is IShape) {
@@ -762,7 +765,7 @@ namespace Orchard.Core.Shapes {
}
index = 0;
foreach(var itemOutput in itemOutputs) {
foreach (var itemOutput in itemOutputs) {
var itemTag = itemTags[index];
if (itemTag != null) {
@@ -811,6 +814,54 @@ namespace Orchard.Core.Shapes {
public void DefinitionTemplate(HtmlHelper Html, TextWriter Output, string TemplateName, object Model, string Prefix) {
RenderInternal(Html, Output, "DefinitionTemplates/" + TemplateName, Model, Prefix);
}
[Shape]
public void Tab(dynamic Display, IGrouping<string, dynamic> Tab, TextWriter Output) {
var tabName = String.IsNullOrWhiteSpace(Tab.Key) ? "Content" : Tab.Key;
var tabBuilder = new TagBuilder("div");
tabBuilder.Attributes["id"] = "tab-" + tabName.HtmlClassify();
tabBuilder.Attributes["data-tab"] = tabName;
Output.Write(tabBuilder.ToString(TagRenderMode.StartTag));
//starts processing cards
var cards = CoreShapes.Order(Tab).GroupBy(x => (string)x.Metadata.Card ?? "");
foreach (var card in cards) {
Output.Write(Display(New.Card(Card: card, ContainerName:tabName)));
}
Output.Write(tabBuilder.ToString(TagRenderMode.EndTag));
}
[Shape]
public void Card(dynamic Display, IGrouping<string, dynamic> Card, string ContainerName, TextWriter Output) {
if (String.IsNullOrWhiteSpace(Card.Key)) {
foreach (var item in CoreShapes.Order(Card)) {
Output.Write(Display(item));
}
}
else {
var cardName = String.IsNullOrWhiteSpace(Card.Key) ? "" : Card.Key;
var cardTag = new TagBuilder("div");
var cardHeaderTag = new TagBuilder("div");
var cardBodyTag = new TagBuilder("div");
cardTag.Attributes["id"] = "group-" + ContainerName.HtmlClassify() + "-" + cardName.HtmlClassify();
cardTag.Attributes["class"] = "row card";
cardBodyTag.Attributes["class"] = "card-body";
Output.Write(cardTag.ToString(TagRenderMode.StartTag));
if (!String.IsNullOrWhiteSpace(Card.Key)) {
cardHeaderTag.Attributes["class"] = "card-header";
cardHeaderTag.SetInnerText(Card.Key);
Output.Write(cardHeaderTag.ToString());
}
Output.Write(cardBodyTag.ToString(TagRenderMode.StartTag));
foreach (var item in CoreShapes.Order(Card)) {
Output.Write(Display(item));
}
Output.Write(cardBodyTag.ToString(TagRenderMode.EndTag));
Output.Write(cardTag.ToString(TagRenderMode.EndTag));
}
}
static void RenderInternal(HtmlHelper Html, TextWriter Output, string TemplateName, object Model, string Prefix) {
var adjustedViewData = new ViewDataDictionary(Html.ViewDataContainer.ViewData) {
@@ -861,7 +912,7 @@ namespace Orchard.Core.Shapes {
if (Value is IShape) {
return Display(Value).ToString();
}
return Html.Raw(Html.Encode(Value.ToString()));
}
}

View File

@@ -142,17 +142,67 @@ namespace Orchard.ContentTypes.Controllers {
if (contentTypeDefinition == null) return HttpNotFound();
var grouped = _placementService.GetEditorPlacement(id)
//Grouping Tabs > Cards > Shapes
var grouped = _placementService
// Get a collection of objects that describe the placement for all shapes
// in the editor view for a ContentItem of the given ContentType
.GetEditorPlacement(id)
// Order all those shapes based on their position
.OrderBy(x => x.PlacementInfo.GetPosition(), new FlatPositionComparer())
// Then alphabetically by their shape type
.ThenBy(x => x.PlacementSettings.ShapeType)
// only pick those shapes that live int the "Content" zone
.Where(e => e.PlacementSettings.Zone == "Content")
.GroupBy(x => x.PlacementInfo.GetTab())
.ToDictionary(x => x.Key, y => y.ToList());
var content = grouped.ContainsKey("") ? grouped[""] : new List<DriverResultPlacement>();
var listPlacements = grouped.Values.SelectMany(e => e).ToList();
grouped.Remove("");
// Form groups whose key is a string like {tabName}%{cardName}. Items
// in a group represent the shapes that will be in the card called {cardName}
// in the tab called {tabName}.
.GroupBy(g => g.PlacementInfo.GetTab() + "%" + g.PlacementInfo.GetCard())
// Transform each of those groups in an object representing the single cards.
// Each of these objects contains the name of the tab that contains it, as
// well as the list of shape placements in that card
.Select(x =>
new Card {
Name = x.Key.Split('%')[1],
TabName = x.Key.Split('%')[0],
Placements = x.ToList()
})
// Group cards by tab
.GroupBy(x => x.TabName)
// Since each of those groups "represents" a card, we actually make it into one.
.Select(x =>
new Tab {
Name = x.Key,
Cards = x.ToList()
})
// Make the collection into a List<Tab> because it's easy to interact with it
// (see later in the code)
.ToList();
var listPlacements = grouped
// By selecting all placements from the Tab objects we built earlier, we have
// them ordered nicely
.SelectMany(x => x.Cards.SelectMany(m => m.Placements))
.ToList();
// We want to have an un-named "default" Tab for shapes, in case none was defined
Tab content;
if (grouped.Any(x => string.IsNullOrWhiteSpace(x.Name))) {
// Because of the way the elements of the list have been ordered above,
// if there is a Tab with empty name, it is the first in the list.
content = grouped[0];
grouped.Remove(content);
} else {
content = new Tab {
Name = "",
Cards = new List<Card> { new Card { Name = "", TabName = "", Placements = new List<DriverResultPlacement>() } }
};
}
// In each Tab, we want to have a "default" un-named Card. This will simplfy
// UI interactions, because it ensures that each Tab has some place we can drop
// shapes in.
for (int i = 0; i < grouped.Count(); i++) {
if (!grouped[i].Cards.Any(x => string.IsNullOrEmpty(x.Name))) {
grouped[i].Cards.Insert(0, new Card { Name = "", TabName = grouped[i].Name, Placements = new List<DriverResultPlacement>() });
}
}
var placementModel = new EditPlacementViewModel {
Content = content,
AllPlacements = listPlacements,
@@ -177,7 +227,7 @@ namespace Orchard.ContentTypes.Controllers {
foreach (var placement in viewModel.AllPlacements) {
var placementSetting = placement.PlacementSettings;
contentTypeDefinition.Placement(
PlacementType.Editor,
placementSetting.ShapeType,
@@ -311,7 +361,7 @@ namespace Orchard.ContentTypes.Controllers {
var partsToAdd = viewModel.PartSelections.Where(ps => ps.IsSelected).Select(ps => ps.PartName);
foreach (var partToAdd in partsToAdd) {
_contentDefinitionService.AddPartToType(partToAdd, typeViewModel.Name);
Services.Notifier.Success(T("The \"{0}\" part has been added.", partToAdd));
}

View File

@@ -4,9 +4,11 @@
$('.type').each(function () {
var input = $(this);
var tab = input.closest(".zone-container").data("tab");
var tab = input.closest(".tab-container").data("tab");
var card = input.closest(".card-container").data("card");
//input = input.next();
var postab = tab !== "" ? position + "#" + tab : position + "";
postab += (card) && card !== "" ? "%" + card : "";
reAssignIdName(input, position); // type
input = input.next();
@@ -33,26 +35,32 @@
var startPos;
// Makes sortable Cards and Shapes
function initTab() {
$(".tabdrag").sortable({
placeholder: "placement-placeholder",
connectWith: ".tabdrag",
stop: function (event, ui) {
assignPositions();
$('#save-message').show();
}
});
$(".carddrag").sortable({
placeholder: "placement-placeholder",
connectWith: ".carddrag",
stop: function (event, ui) {
assignPositions();
$('#save-message').show();
}
});
}
// Makes sortable tabs
$('#sortableTabs').sortable({
placeholder: "tab-placeholder",
start: function (event, ui) {
var self = $(ui.item);
startPos = self.prevAll().size();
},
stop: function (event, ui) {
assignPositions();
$('#save-message').show();
}
});
@@ -75,9 +83,12 @@
$("#tabName").val("");
return;
}
//Insert the tab with an empty card
$("#sortableTabs").append('<div data-tab="' + tab + '" class="zone-container tab-container"><h2><a class="delete">Delete</a>'
+ tab + '</h2><ul class="tabdrag"></ul></div>'
+ tab + '</h2><ul class="tabdrag">'
+ '<li data-tab="' + tab + '" data-card="" class="zone-container card-container">'
+ '<ul class="carddrag"></ul></li>'
+ '</ul></div> '
);
// make it sortable
initTab();
@@ -86,20 +97,46 @@
$("#tabName").val("");
});
$("#newCard").click(function (e) {
e.preventDefault();
// get the new tab name, cancel if blank
var card = $("#tabName").val().replace(/\s/g, "");
if (!card.length) {
return;
}
// insert card in the Content Tab
$("#content-tab > ul").append('<li data-tab="" data-card="' + card + '" class="zone-container card-container"><a class="delete">Delete</a><div class="card-type"><h2>'
+ card + '</h2></div><ul class="carddrag"></ul></li>');
// make it sortable
initTab();
$("#sortableTabs").sortable("refresh");
// clear the textbox
$("#tabName").val("");
});
// remove tabs
// append items to content, create content if not there
$("#placement").on("click", ".delete", function (e) {
var me = $(this);
var parent = me.parent(".zone-container");
var list = parent.children(".tabdrag").html();
if (!parent.length) {
parent = me.parents(".zone-container");
}
var list, newList;
if (parent.hasClass("tab-container")) {
list = parent.children(".tabdrag").html();
newList = $("#placement .tabdrag").first();
} else if (parent.hasClass("card-container")) {
list = parent.children(".carddrag").html();
newList = $("#placement .tabdrag").first();
}
// get first tab
var newList = $("#placement .tabdrag").first();
if (newList.length) {
parent.remove();
newList.append(list);
}
assignPositions();
// make it sortable
initTab();
});
// toggle editor shapes

View File

@@ -176,7 +176,7 @@ fieldset.action {
/* PLACEMENT EDITOR */
.tabdrag {
.tabdrag, .carddrag {
min-height: 20px;
}
@@ -211,6 +211,15 @@ fieldset.action {
padding: 5px 0px 0px 30px;
}
.zone-container li .card-type {
cursor: move;
background-color: #ccc;
background: #ccc url(images/move.gif) no-repeat 10px 10px;
height: 30px;
padding: 5px 10px 5px 30px;
margin-bottom: 10px;
}
#placement #content-tab {
cursor: default;
}

View File

@@ -7,7 +7,19 @@ namespace Orchard.ContentTypes.ViewModels {
public class EditPlacementViewModel {
public ContentTypeDefinition ContentTypeDefinition { get; set; }
public List<DriverResultPlacement> AllPlacements { get; set; }
public Dictionary<string, List<DriverResultPlacement>> Tabs { get; set; }
public List<DriverResultPlacement> Content { get; set; }
public List<Tab> Tabs { get; set; }
public Tab Content { get; set; }
}
public class Tab {
public string Name { get; set; }
public List<Card> Cards { get; set; }
}
public class Card {
public string Name { get; set; }
public string TabName { get; set; }
public List<DriverResultPlacement> Placements { get; set; }
}
}

View File

@@ -1,4 +1,5 @@
@using Orchard.ContentTypes.Services
@using Orchard.ContentTypes.ViewModels
@model Orchard.ContentTypes.ViewModels.EditPlacementViewModel
@@ -7,23 +8,22 @@
Script.Require("PlacementEditor").AtFoot();
Layout.Title = T("Edit Placement - {0}", Html.Raw(Model.ContentTypeDefinition.DisplayName)).Text;
int i = 0;
}
@helper RenderPlacement(DriverResultPlacement p, int i)
@functions
{
private int i = 0;
}
@helper RenderPlacement(DriverResultPlacement p) {
var placement = p.PlacementSettings;
<li class="place" data-shape-type="@placement.ShapeType" data-shape-differentiator="@placement.Differentiator" data-shape-zone="Content" data-shape-position="@placement.Position">
<span class="toggle" data-text-show="@T("Show Editor Shape")" data-text-hide="@T("Hide Editor Shape")">@T("Show Editor Shape")</span>
<div class="shape-type"><h3>@placement.ShapeType @placement.Differentiator</h3></div>
<div class="shape-editor">
@try
{
@try {
@Display(p.Shape)
}
catch
{
catch {
}
</div>
@@ -32,36 +32,52 @@
@Html.HiddenFor(m => m.AllPlacements[i].PlacementSettings.Zone, new { @class = "zone" })
@Html.HiddenFor(m => m.AllPlacements[i].PlacementSettings.Position, new { @class = "position" })
</li>
{
i++;
}
}
@helper RenderCard(Card card) {
<li data-tab="@(card.TabName)" data-card="@(card.Name)" class="zone-container card-container">
@if (!string.IsNullOrWhiteSpace(card.Name)) {
<a class="delete">@T("Delete")</a>
<div class="card-type"><h2>@card.Name</h2></div>
}
<ul class="carddrag">
@foreach (var p in card.Placements) {
@RenderPlacement(p);
}
</ul>
</li>
}
<div id="save-message" class="message message-Warning">@T("You need to hit \"Save\" in order to save your changes.")</div>
@using (Html.BeginFormAntiForgeryPost())
{
@* Alert messages *@
<div style="display:none">
<div id="nested-cards" title="@T("Drag&Drop")">
<p>@T("Nested cards are not allowed.")</p>
</div>
</div>
@using (Html.BeginFormAntiForgeryPost()) {
@Html.ValidationSummary()
<div id="placement">
<div data-tab="" class="zone-container" id="content-tab">
<div data-tab="" class="zone-container tab-container" id="content-tab">
<h2>@T("Content")</h2>
<ul class="tabdrag">
@foreach (var p in Model.Content)
{
@RenderPlacement(p, i);
i++;
@foreach (var c in Model.Content.Cards) {
@RenderCard(c);
}
</ul>
</div>
<div id="sortableTabs">
@foreach (var tab in Model.Tabs)
{
<div data-tab="@tab.Key" class="zone-container tab-container">
@foreach (var tab in Model.Tabs) {
<div data-tab="@tab.Name" class="zone-container tab-container">
<a class="delete">@T("Delete")</a>
<h2>@tab.Key</h2>
<h2>@tab.Name</h2>
<ul class="tabdrag">
@foreach (var p in tab.Value)
{
@RenderPlacement(p, i);
i++;
@foreach (var c in tab.Cards) {
@RenderCard(c);
}
</ul>
</div>
@@ -72,6 +88,7 @@
<div>
<input type="text" id="tabName" />
<button class="primaryAction" id="newTab">@T("New Tab")</button>
<button class="primaryAction" id="newCard">@T("New Card")</button>
</div>
<fieldset class="action">

View File

@@ -67,6 +67,7 @@ namespace Orchard.ContentManagement.Drivers {
newShapeMetadata.DisplayType = displayType;
newShapeMetadata.PlacementSource = placement.Source;
newShapeMetadata.Tab = placement.GetTab();
newShapeMetadata.Card = placement.GetCard();
// If a specific shape is provided, remove all previous alternates and wrappers.
if (!String.IsNullOrEmpty(placement.ShapeType)) {

View File

@@ -4,7 +4,7 @@ using System.Linq;
namespace Orchard.DisplayManagement.Descriptors {
public class PlacementInfo {
private static readonly char[] Delimiters = {':', '#', '@'};
private static readonly char[] Delimiters = {':', '#', '@', '%'};
public PlacementInfo() {
Alternates = Enumerable.Empty<string>();
@@ -71,5 +71,19 @@ namespace Orchard.DisplayManagement.Descriptors {
return Location.Substring(groupDelimiter + 1, nextDelimiter - groupDelimiter - 1);
}
public string GetCard() {
var groupDelimiter = Location.IndexOf('%');
if (groupDelimiter == -1) {
return "";
}
var nextDelimiter = Location.IndexOfAny(Delimiters, groupDelimiter + 1);
if (nextDelimiter == -1) {
return Location.Substring(groupDelimiter + 1);
}
return Location.Substring(groupDelimiter + 1, nextDelimiter - groupDelimiter - 1);
}
}
}

View File

@@ -18,6 +18,7 @@ namespace Orchard.DisplayManagement.Shapes {
public string DisplayType { get; set; }
public string Position { get; set; }
public string Tab { get; set; }
public string Card { get; set; }
public string PlacementSource { get; set; }
public string Prefix { get; set; }
public IList<string> Wrappers { get; set; }