diff --git a/src/Orchard.Web/Modules/Orchard.ContentTypes/Controllers/AdminController.cs b/src/Orchard.Web/Modules/Orchard.ContentTypes/Controllers/AdminController.cs index dbb560b02..8b976b17f 100644 --- a/src/Orchard.Web/Modules/Orchard.ContentTypes/Controllers/AdminController.cs +++ b/src/Orchard.Web/Modules/Orchard.ContentTypes/Controllers/AdminController.cs @@ -28,8 +28,8 @@ namespace Orchard.ContentTypes.Controllers { private readonly ShellSettings _settings; public AdminController( - IOrchardServices orchardServices, - IContentDefinitionService contentDefinitionService, + IOrchardServices orchardServices, + IContentDefinitionService contentDefinitionService, IContentDefinitionManager contentDefinitionManager, IPlacementService placementService, Lazy> settingsManagerEventHandlers, @@ -101,7 +101,7 @@ namespace Orchard.ContentTypes.Controllers { } var contentTypeDefinition = _contentDefinitionService.AddType(viewModel.Name, viewModel.DisplayName); - + // adds CommonPart by default _contentDefinitionService.AddPartToType("CommonPart", viewModel.Name); @@ -148,9 +148,21 @@ namespace Orchard.ContentTypes.Controllers { if (contentTypeDefinition == null) return HttpNotFound(); + var grouped = _placementService.GetEditorPlacement(id) + .OrderBy(x => x.PlacementInfo.GetPosition(), new FlatPositionComparer()) + .ThenBy(x => x.PlacementSettings.ShapeType) + .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(); + var listPlacements = grouped.Values.SelectMany(e => e).ToList(); + + grouped.Remove(""); var placementModel = new EditPlacementViewModel { - PlacementSettings = contentTypeDefinition.GetPlacement(PlacementType.Editor), - AllPlacements = _placementService.GetEditorPlacement(id).OrderBy(x => x.PlacementSettings.Position, new FlatPositionComparer()).ThenBy(x => x.PlacementSettings.ShapeType).ToList(), + Content = content, + AllPlacements = listPlacements, + Tabs = grouped, ContentTypeDefinition = contentTypeDefinition, }; @@ -168,20 +180,10 @@ namespace Orchard.ContentTypes.Controllers { if (contentTypeDefinition == null) return HttpNotFound(); - var allPlacements = _placementService.GetEditorPlacement(id).ToList(); - var result = new List(contentTypeDefinition.GetPlacement(PlacementType.Editor)); - contentTypeDefinition.ResetPlacement(PlacementType.Editor); - foreach(var driverPlacement in viewModel.AllPlacements) { - // if the placement has changed, persist it - if (!allPlacements.Any(x => x.PlacementSettings.Equals(driverPlacement.PlacementSettings))) { - result = result.Where(x => !x.IsSameAs(driverPlacement.PlacementSettings)).ToList(); - result.Add(driverPlacement.PlacementSettings); - } - } - - foreach(var placementSetting in result) { + foreach (var placement in viewModel.AllPlacements) { + var placementSetting = placement.PlacementSettings; contentTypeDefinition.Placement(PlacementType.Editor, placementSetting.ShapeType, placementSetting.Differentiator, @@ -195,7 +197,7 @@ namespace Orchard.ContentTypes.Controllers { _settingsManagerEventHandlers.Value.Invoke(x => x.Saved(_settings), Logger); - return RedirectToAction("EditPlacement", new {id}); + return RedirectToAction("EditPlacement", new { id }); } [HttpPost, ActionName("EditPlacement")] @@ -234,11 +236,11 @@ namespace Orchard.ContentTypes.Controllers { TryUpdateModel(edited); typeViewModel.DisplayName = edited.DisplayName ?? string.Empty; - if ( String.IsNullOrWhiteSpace(typeViewModel.DisplayName) ) { + if (String.IsNullOrWhiteSpace(typeViewModel.DisplayName)) { ModelState.AddModelError("DisplayName", T("The Content Type name can't be empty.").ToString()); } - if ( _contentDefinitionService.GetTypes().Any(t => String.Equals(t.DisplayName.Trim(), typeViewModel.DisplayName.Trim(), StringComparison.OrdinalIgnoreCase) && !String.Equals(t.Name, id)) ) { + if (_contentDefinitionService.GetTypes().Any(t => String.Equals(t.DisplayName.Trim(), typeViewModel.DisplayName.Trim(), StringComparison.OrdinalIgnoreCase) && !String.Equals(t.Name, id))) { ModelState.AddModelError("DisplayName", T("A type with the same name already exists.").ToString()); } @@ -271,7 +273,7 @@ namespace Orchard.ContentTypes.Controllers { _contentDefinitionService.RemoveType(id, true); Services.Notifier.Success(T("\"{0}\" has been removed.", typeViewModel.DisplayName)); - + return RedirectToAction("List"); } @@ -322,7 +324,7 @@ namespace Orchard.ContentTypes.Controllers { return AddPartsTo(id); } - return RedirectToAction("Edit", new {id}); + return RedirectToAction("Edit", new { id }); } public ActionResult RemovePartFrom(string id) { @@ -364,7 +366,7 @@ namespace Orchard.ContentTypes.Controllers { Services.Notifier.Success(T("The \"{0}\" part has been removed.", viewModel.Name)); - return RedirectToAction("Edit", new {id}); + return RedirectToAction("Edit", new { id }); } #endregion @@ -447,8 +449,7 @@ namespace Orchard.ContentTypes.Controllers { [HttpPost, ActionName("EditPart")] [FormValueRequired("submit.Delete")] - public ActionResult DeletePart(string id) - { + public ActionResult DeletePart(string id) { if (!Services.Authorizer.Authorize(Permissions.EditContentTypes, T("Not allowed to delete a content part."))) return new HttpUnauthorizedResult(); @@ -497,8 +498,8 @@ namespace Orchard.ContentTypes.Controllers { if (partViewModel == null) { // id passed in might be that of a type w/ no implicit field if (typeViewModel != null) { - partViewModel = new EditPartViewModel {Name = typeViewModel.Name}; - _contentDefinitionService.AddPart(new CreatePartViewModel {Name = partViewModel.Name}); + partViewModel = new EditPartViewModel { Name = typeViewModel.Name }; + _contentDefinitionService.AddPart(new CreatePartViewModel { Name = partViewModel.Name }); _contentDefinitionService.AddPartToType(partViewModel.Name, typeViewModel.Name); } else { @@ -555,7 +556,7 @@ namespace Orchard.ContentTypes.Controllers { Services.Notifier.Success(T("The \"{0}\" field has been added.", viewModel.DisplayName)); if (typeViewModel != null) { - return RedirectToAction("Edit", new {id}); + return RedirectToAction("Edit", new { id }); } return RedirectToAction("EditPart", new { id }); @@ -573,7 +574,7 @@ namespace Orchard.ContentTypes.Controllers { var fieldViewModel = partViewModel.Fields.FirstOrDefault(x => x.Name == name); - if(fieldViewModel == null) { + if (fieldViewModel == null) { return HttpNotFound(); } @@ -602,14 +603,14 @@ namespace Orchard.ContentTypes.Controllers { // prevent null reference exception in validation viewModel.DisplayName = viewModel.DisplayName ?? String.Empty; - + // remove extra spaces viewModel.DisplayName = viewModel.DisplayName.Trim(); if (String.IsNullOrWhiteSpace(viewModel.DisplayName)) { ModelState.AddModelError("DisplayName", T("The Display Name name can't be empty.").ToString()); } - + if (_contentDefinitionService.GetPart(partViewModel.Name).Fields.Any(t => t.Name != viewModel.Name && String.Equals(t.DisplayName.Trim(), viewModel.DisplayName.Trim(), StringComparison.OrdinalIgnoreCase))) { ModelState.AddModelError("DisplayName", T("A field with the same Display Name already exists.").ToString()); } @@ -620,7 +621,7 @@ namespace Orchard.ContentTypes.Controllers { var field = _contentDefinitionManager.GetPartDefinition(id).Fields.FirstOrDefault(x => x.Name == viewModel.Name); - if(field == null) { + if (field == null) { return HttpNotFound(); } diff --git a/src/Orchard.Web/Modules/Orchard.ContentTypes/Orchard.ContentTypes.csproj b/src/Orchard.Web/Modules/Orchard.ContentTypes/Orchard.ContentTypes.csproj index e63898923..54187253b 100644 --- a/src/Orchard.Web/Modules/Orchard.ContentTypes/Orchard.ContentTypes.csproj +++ b/src/Orchard.Web/Modules/Orchard.ContentTypes/Orchard.ContentTypes.csproj @@ -147,6 +147,7 @@ + diff --git a/src/Orchard.Web/Modules/Orchard.ContentTypes/ResourceManifest.cs b/src/Orchard.Web/Modules/Orchard.ContentTypes/ResourceManifest.cs index 8d15d2c0a..57795d0ad 100644 --- a/src/Orchard.Web/Modules/Orchard.ContentTypes/ResourceManifest.cs +++ b/src/Orchard.Web/Modules/Orchard.ContentTypes/ResourceManifest.cs @@ -4,6 +4,10 @@ namespace Orchard.ContentTypes { public class ResourceManifest : IResourceManifestProvider { public void BuildManifests(ResourceManifestBuilder builder) { builder.Add().DefineStyle("ContentTypesAdmin").SetUrl("orchard-contenttypes-admin.css"); + + builder.Add().DefineScript("PlacementEditor") + .SetUrl("admin-placementeditor.js") + .SetDependencies("jQueryUI_Sortable"); } } } diff --git a/src/Orchard.Web/Modules/Orchard.ContentTypes/Scripts/admin-placementeditor.js b/src/Orchard.Web/Modules/Orchard.ContentTypes/Scripts/admin-placementeditor.js new file mode 100644 index 000000000..fcb5a3454 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.ContentTypes/Scripts/admin-placementeditor.js @@ -0,0 +1,130 @@ +(function ($) { + var assignPositions = function () { + var position = 0; + $('.type').each(function () { + var input = $(this); + var tab = input.closest(".zone-container").data("tab"); + //input = input.next(); + var postab = tab != "" ? position + "#" + tab : position + ""; + reAssignIdName(input, position); // type + + input = input.next(); + reAssignIdName(input, position); // differentiator + + input = input.next(); + reAssignIdName(input, position); // zone + + input = input.next(); + reAssignIdName(input, position); // position + + input.val(postab); + position++; + }); + }; + + var reAssignIdName = function (input, pos) { + var name = input.attr('name'); + input.attr('name', name.replace(new RegExp("\\[.*\\]", 'gi'), '[' + pos + ']')); + + var id = input.attr('id'); + input.attr('id', id.replace(new RegExp('_.*__', 'i'), '_' + pos + '__')); + }; + + var startPos; + + function initTab() { + $(".tabdrag").sortable({ + placeholder: "placement-placeholder", + connectWith: ".tabdrag", + stop: function (event, ui) { + assignPositions(); + } + }); + } + + $('#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(); + + } + }); + + $("#newTab").click(function (e) { + e.preventDefault(); + // get the new tab name, cancel if blank + var tab = $("#tabName").val().replace(/\s/g, ""); + if (!tab.length) { + return; + } + + if (tab.toLowerCase() === "content") { + $("#tabName").val(""); + return; + } + + // in tabs already + var tabs = getTabs(true); + if ($.inArray(tab, tabs) >= 0) { + $("#tabName").val(""); + return; + } + + $("#sortableTabs").append('

Delete' + + tab + '

    ' + ); + // 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(); + // get first tab + var newList = $("#placement .tabdrag").first(); + if (newList.length) { + parent.remove(); + newList.append(list); + } + + assignPositions(); + }); + + // toggle editor shapes + $("#placement").on("click", ".toggle", function (e) { + var $this = $(this); + var shape = $(this).next().next(); + shape.toggle(); + if ($this.text() == 'Show Editor Shape') { + $this.text('Hide Editor Shape'); + } else { + $this.text('Show Editor Shape'); + } + }) + + // returns all the tabs + function getTabs(header) { + var tabs = []; + $(".zone-container").each(function (index, e) { + tabs.push($(e).data("tab")); + }); + + return tabs; + } + + initTab(); + assignPositions(); + $('.shape-editor *').attr('disabled', 'disabled'); + $("#placement").disableSelection(); +})(jQuery); \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.ContentTypes/Services/PlacementService.cs b/src/Orchard.Web/Modules/Orchard.ContentTypes/Services/PlacementService.cs index 78b60cb28..cd500bfa8 100644 --- a/src/Orchard.Web/Modules/Orchard.ContentTypes/Services/PlacementService.cs +++ b/src/Orchard.Web/Modules/Orchard.ContentTypes/Services/PlacementService.cs @@ -18,6 +18,7 @@ using Orchard.UI.Zones; namespace Orchard.ContentTypes.Services { public class DriverResultPlacement { + public PlacementInfo PlacementInfo { get; set; } public PlacementSettings PlacementSettings { get; set; } public DriverResult ShapeResult { get; set; } public dynamic Shape { get; set; } @@ -201,6 +202,7 @@ namespace Orchard.ContentTypes.Services { yield return new DriverResultPlacement { + PlacementInfo = placement, Shape = itemShape.Content, ShapeResult = contentShapeResult, PlacementSettings = new PlacementSettings { diff --git a/src/Orchard.Web/Modules/Orchard.ContentTypes/Styles/orchard-contenttypes-admin.css b/src/Orchard.Web/Modules/Orchard.ContentTypes/Styles/orchard-contenttypes-admin.css index e85997927..4adb3b26b 100644 --- a/src/Orchard.Web/Modules/Orchard.ContentTypes/Styles/orchard-contenttypes-admin.css +++ b/src/Orchard.Web/Modules/Orchard.ContentTypes/Styles/orchard-contenttypes-admin.css @@ -21,38 +21,38 @@ .manage-part, .manage-field { margin-bottom: 0; - padding:0; + padding: 0; border-bottom: 1px solid #EAEAEA; } -.manage-part h3, -.manage-field h3 { - display: relative; - line-height: 1.4em; - padding-bottom: 0; - padding-top: 0; -} + .manage-part h3, + .manage-field h3 { + display: relative; + line-height: 1.4em; + padding-bottom: 0; + padding-top: 0; + } -.manage-part h3, -.manage-field h3, -.manage-part h4, -.manage-type .manage-field .details, -.manage-type .manage-part .manage-field, -.manage-type .manage-part .settings { - padding-left: 30px; - padding-right: 30px; -} + .manage-part h3, + .manage-field h3, + .manage-part h4, + .manage-type .manage-field .details, + .manage-type .manage-part .manage-field, + .manage-type .manage-part .settings { + padding-left: 30px; + padding-right: 30px; + } -.manage-part h3, .manage-field h3 { - margin: 1em 0 !important; - padding-left: 30px; - width: 90%; -} + .manage-part h3, .manage-field h3 { + margin: 1em 0 !important; + padding-left: 30px; + width: 90%; + } -.manage-part .expando-glyph, .manage-field .expando-glyph { - width:16px; - height:16px; -} + .manage-part .expando-glyph, .manage-field .expando-glyph { + width: 16px; + height: 16px; + } .manage-type .manage-field .settings { padding-bottom: 10px; @@ -176,38 +176,81 @@ fieldset.action { /* PLACEMENT EDITOR */ - -#placement li { - margin-bottom: 10px; - padding: 0px; - border: 1px solid #eee; +.tabdrag { + min-height: 20px; } - #placement li .shape-type { - cursor: move; - background-color: #eee; - background: #EEE url(images/move.gif) no-repeat 10px 15px; - height: 30px; - padding: 10px 0px 0px 30px; +.zone-container { + border: 1px dotted #6F6F6F; + padding: 10px; + margin-bottom: 10px; +} + + .zone-container li { + margin-bottom: 10px; + padding: 0px; + border: 1px solid #eee; } - #placement li .shape-editor { - padding: 10px; - background: white; - } +#placement .zone-container h2 { + cursor: move; + margin-top: 0px; + padding-left: 20px; + padding-bottom: 15px; +} + +#placement .tab-container { + background: white url(images/move.gif) no-repeat 10px 15px; +} + +.zone-container li .shape-type { + cursor: move; + background-color: #eee; + background: #EEE url(images/move.gif) no-repeat 10px 10px; + height: 30px; + padding: 5px 0px 0px 30px; +} + +#placement #content-tab { + cursor: default; +} + +.zone-container li .toggle { + float: right; + padding-top: 5px; + padding-right: 5px; + cursor: pointer; +} + +.zone-container li .shape-editor { + padding: 10px; + background: white; + display: none; +} #save-message { display: none; margin-bottom: 10px; } -#placement fieldset { +.zone-container fieldset { float: inherit; /* prevent bad layout if float is defined to left in specific parts, e.g. datetimepicker */ height: auto; } +.tab-placeholder { + background: #FDF5BC; + border: 1px solid #FDF5BC; + height: 100px; +} + .placement-placeholder { background: #FDF5BC; border: 1px solid #FDF5BC; - height: 100px; + height: 40px; +} + +.zone-container .delete { + float: right; + cursor: pointer; } diff --git a/src/Orchard.Web/Modules/Orchard.ContentTypes/ViewModels/EditPlacementViewModel.cs b/src/Orchard.Web/Modules/Orchard.ContentTypes/ViewModels/EditPlacementViewModel.cs index ebc3df44e..86b6fd953 100644 --- a/src/Orchard.Web/Modules/Orchard.ContentTypes/ViewModels/EditPlacementViewModel.cs +++ b/src/Orchard.Web/Modules/Orchard.ContentTypes/ViewModels/EditPlacementViewModel.cs @@ -6,7 +6,8 @@ using Orchard.ContentTypes.Settings; namespace Orchard.ContentTypes.ViewModels { public class EditPlacementViewModel { public ContentTypeDefinition ContentTypeDefinition { get; set; } - public PlacementSettings[] PlacementSettings { get; set; } public List AllPlacements { get; set; } + public Dictionary> Tabs { get; set; } + public List Content { get; set; } } } \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.ContentTypes/Views/Admin/EditPlacement.cshtml b/src/Orchard.Web/Modules/Orchard.ContentTypes/Views/Admin/EditPlacement.cshtml index 93c054a02..b8b6c4934 100644 --- a/src/Orchard.Web/Modules/Orchard.ContentTypes/Views/Admin/EditPlacement.cshtml +++ b/src/Orchard.Web/Modules/Orchard.ContentTypes/Views/Admin/EditPlacement.cshtml @@ -1,107 +1,71 @@ +@using Orchard.ContentTypes.Services @model Orchard.ContentTypes.ViewModels.EditPlacementViewModel @{ Style.Require("ContentTypesAdmin"); - Script.Require("jQueryUI_Sortable"); + Script.Require("PlacementEditor").AtFoot(); Layout.Title = T("Edit Placement - {0}", Model.ContentTypeDefinition.DisplayName).ToString(); - var hiddenShapes = Model.AllPlacements.Where(x => String.IsNullOrEmpty(x.PlacementSettings.Zone) && (String.IsNullOrWhiteSpace(x.PlacementSettings.Position) || x.PlacementSettings.Position == "-")); + int i = 0; +} + +@helper RenderPlacement(DriverResultPlacement p, int i) { +var placement = p.PlacementSettings; +
  • + Show Editor Shape +

    @placement.ShapeType @placement.Differentiator

    +
    + @try { + @Display(p.Shape) + } + catch { + } +
    + @Html.HiddenFor(m => m.AllPlacements[i].PlacementSettings.ShapeType, new { @class = "type" }) + @Html.HiddenFor(m => m.AllPlacements[i].PlacementSettings.Differentiator, new { @class = "differentiator" }) + @Html.HiddenFor(m => m.AllPlacements[i].PlacementSettings.Zone, new { @class = "zone" }) + @Html.HiddenFor(m => m.AllPlacements[i].PlacementSettings.Position, new { @class = "position" }) +
  • }
    @T("You need to hit \"Save\" in order to save your changes.")
    @using (Html.BeginFormAntiForgeryPost()) { @Html.ValidationSummary() +
    +
    +

    Content

    +
      + @foreach (var p in Model.Content) { + @RenderPlacement(p, i); + i++; + } +
    +
    - -
      - @for (int i = 0; i < Model.AllPlacements.Count; i++ ) { - - var placement = Model.AllPlacements[i].PlacementSettings; - - if(placement.Zone != "Content") { - continue; +
      + @foreach (var tab in Model.Tabs) { +
      + Delete +

      @tab.Key

      +
        + @foreach (var p in tab.Value) { + @RenderPlacement(p, i); + i++; + } +
      +
      } - -
    • -

      @placement.ShapeType @placement.Differentiator

      -
      - @try { - @Display(Model.AllPlacements[i].Shape) - } - catch { - } -
      - - @* @shape.Position @(Model.PlacementSettings.Any(x => x.Equals(shape)))*@ - @Html.HiddenFor(m => m.AllPlacements[i].PlacementSettings.ShapeType, new { @class = "type" }) - @Html.HiddenFor(m => m.AllPlacements[i].PlacementSettings.Differentiator, new { @class = "differentiator" }) - @Html.HiddenFor(m => m.AllPlacements[i].PlacementSettings.Zone, new { @class = "zone" }) - @Html.HiddenFor(m => m.AllPlacements[i].PlacementSettings.Position, new { @class = "position" }) -
    • - } -
    - +
    + + +
    + + +
    +
    -} - -@using (Script.Foot()) { - } \ No newline at end of file