diff --git a/appveyor.yml b/appveyor.yml index 922fa68..7e3c389 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,7 +1,7 @@ image: Visual Studio 2017 # version format -version: 1.1.2.{build} +version: 2.0.0.{build} # UMBRACO_PACKAGE_PRERELEASE_SUFFIX if a rtm release build this should be blank, otherwise if empty will default to alpha # example UMBRACO_PACKAGE_PRERELEASE_SUFFIX=beta diff --git a/src/Our.Umbraco.InnerContent/Helpers/InnerContentHelper.cs b/src/Our.Umbraco.InnerContent/Helpers/InnerContentHelper.cs index 5c34c02..d4732da 100644 --- a/src/Our.Umbraco.InnerContent/Helpers/InnerContentHelper.cs +++ b/src/Our.Umbraco.InnerContent/Helpers/InnerContentHelper.cs @@ -155,5 +155,42 @@ internal static PublishedContentType GetPublishedContentTypeFromItem(JObject ite return PublishedContentType.Get(PublishedItemType.Content, contentTypeAlias); } + + internal static IContent ConvertInnerContentToBlueprint(JObject item, int userId = 0) + { + var contentType = GetContentTypeFromItem(item); + + // creates a fast lookup of the property types + var propertyTypes = contentType.PropertyTypes.ToDictionary(x => x.Alias, x => x, StringComparer.InvariantCultureIgnoreCase); + + var propValues = item.ToObject>(); + var properties = new List(); + + foreach (var jProp in propValues) + { + if (propertyTypes.ContainsKey(jProp.Key) == false) + continue; + + var propType = propertyTypes[jProp.Key]; + if (propType != null) + { + // TODO: Check if we need to call `ConvertEditorToDb`? + properties.Add(new Property(propType, jProp.Value)); + } + } + + // Manually parse out the special properties + propValues.TryGetValue("name", out object name); + propValues.TryGetValue("key", out object key); + + return new Content(name?.ToString(), -1, contentType, new PropertyCollection(properties)) + { + Key = key == null ? Guid.Empty : Guid.Parse(key.ToString()), + ParentId = -1, + Path = "-1", + CreatorId = userId, + WriterId = userId + }; + } } } \ No newline at end of file diff --git a/src/Our.Umbraco.InnerContent/Our.Umbraco.InnerContent.csproj b/src/Our.Umbraco.InnerContent/Our.Umbraco.InnerContent.csproj index f8be1d0..87700b3 100644 --- a/src/Our.Umbraco.InnerContent/Our.Umbraco.InnerContent.csproj +++ b/src/Our.Umbraco.InnerContent/Our.Umbraco.InnerContent.csproj @@ -30,24 +30,12 @@ 4 - - ..\packages\ClientDependency.1.8.4\lib\net45\ClientDependency.Core.dll + + ..\packages\UmbracoCms.Core.7.7.0\lib\net45\interfaces.dll False - - ..\packages\UmbracoCms.Core.7.4.0\lib\interfaces.dll - False - - - ..\packages\UmbracoCms.Core.7.4.0\lib\log4net.dll - False - - - ..\packages\MiniProfiler.2.1.0\lib\net40\MiniProfiler.dll - False - - - ..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll + + ..\packages\Newtonsoft.Json.10.0.2\lib\net45\Newtonsoft.Json.dll False @@ -68,18 +56,18 @@ - - ..\packages\UmbracoCms.Core.7.4.0\lib\umbraco.dll + + ..\packages\UmbracoCms.Core.7.7.0\lib\net45\umbraco.dll False - - ..\packages\UmbracoCms.Core.7.4.0\lib\Umbraco.Core.dll + + ..\packages\UmbracoCms.Core.7.7.0\lib\net45\Umbraco.Core.dll False - + @@ -88,7 +76,7 @@ - + @@ -102,12 +90,11 @@ - - + + - diff --git a/src/Our.Umbraco.InnerContent/PropertyEditors/InnerContentPropertyValueEditorWrapper.cs b/src/Our.Umbraco.InnerContent/PropertyEditors/InnerContentPropertyValueEditor.cs similarity index 90% rename from src/Our.Umbraco.InnerContent/PropertyEditors/InnerContentPropertyValueEditorWrapper.cs rename to src/Our.Umbraco.InnerContent/PropertyEditors/InnerContentPropertyValueEditor.cs index 97374d0..5e99441 100644 --- a/src/Our.Umbraco.InnerContent/PropertyEditors/InnerContentPropertyValueEditorWrapper.cs +++ b/src/Our.Umbraco.InnerContent/PropertyEditors/InnerContentPropertyValueEditor.cs @@ -12,9 +12,9 @@ namespace Our.Umbraco.InnerContent.PropertyEditors { - public abstract class InnerContentPropertyValueEditorWrapper : PropertyValueEditorWrapper + public abstract class InnerContentPropertyValueEditor : PropertyValueEditorWrapper { - protected InnerContentPropertyValueEditorWrapper(PropertyValueEditor wrapped) + protected InnerContentPropertyValueEditor(PropertyValueEditor wrapped) : base(wrapped) { } @@ -22,14 +22,9 @@ public override void ConfigureForDisplay(PreValueCollection preValues) { base.ConfigureForDisplay(preValues); - var asDictionary = preValues.PreValuesAsDictionary; - if (asDictionary.ContainsKey("hideLabel")) + if (preValues.PreValuesAsDictionary.ContainsKey("hideLabel")) { - var boolAttempt = asDictionary["hideLabel"].Value.TryConvertTo(); - if (boolAttempt.Success) - { - HideLabel = boolAttempt.Result; - } + HideLabel = preValues.PreValuesAsDictionary["hideLabel"].Value == "1"; } } @@ -76,7 +71,7 @@ protected void ConvertInnerContentDbToString(JObject item, IDataTypeService data var propEditor = PropertyEditorResolver.Current.GetByAlias(propType.PropertyEditorAlias); // Get the editor to do it's conversion, and store it back - item[propKey] = propEditor.ValueEditor.ConvertDbToString(prop, propType, dataTypeService); + item[propKey] = propEditor?.ValueEditor?.ConvertDbToString(prop, propType, dataTypeService); } catch (InvalidOperationException) { @@ -122,7 +117,7 @@ protected void ConvertInnerContentDbToEditor(JObject item, IDataTypeService data foreach (var propKey in propValueKeys) { - var propType = contentType.CompositionPropertyTypes.FirstOrDefault(x => x.Alias == propKey); + var propType = contentType.CompositionPropertyTypes.FirstOrDefault(x => x.Alias.InvariantEquals(propKey)); if (propType == null) { if (IsSystemPropertyKey(propKey) == false) @@ -142,7 +137,7 @@ protected void ConvertInnerContentDbToEditor(JObject item, IDataTypeService data var propEditor = PropertyEditorResolver.Current.GetByAlias(propType.PropertyEditorAlias); // Get the editor to do it's conversion - var newValue = propEditor.ValueEditor.ConvertDbToEditor(prop, propType, dataTypeService); + var newValue = propEditor?.ValueEditor?.ConvertDbToEditor(prop, propType, dataTypeService); // Store the value back item[propKey] = (newValue == null) ? null : JToken.FromObject(newValue); @@ -150,7 +145,7 @@ protected void ConvertInnerContentDbToEditor(JObject item, IDataTypeService data catch (InvalidOperationException) { // https://github.com/umco/umbraco-nested-content/issues/111 - // Catch any invalid cast operations as likely means courier failed due to missing + // Catch any invalid cast operations as likely means Courier failed due to missing // or trashed item so couldn't convert a guid back to an int item[propKey] = null; @@ -159,7 +154,7 @@ protected void ConvertInnerContentDbToEditor(JObject item, IDataTypeService data } // Process children - var childrenProp = item.Properties().FirstOrDefault(x => x.Name == "children"); + var childrenProp = item.Properties().FirstOrDefault(x => x.Name.InvariantEquals("children")); if (childrenProp != null) { ConvertInnerContentDbToEditor(childrenProp.Value.Value(), dataTypeService); diff --git a/src/Our.Umbraco.InnerContent/PropertyEditors/SimpleInnerContentPropertyValueEditor.cs b/src/Our.Umbraco.InnerContent/PropertyEditors/SimpleInnerContentPropertyValueEditor.cs index 5414779..833073e 100644 --- a/src/Our.Umbraco.InnerContent/PropertyEditors/SimpleInnerContentPropertyValueEditor.cs +++ b/src/Our.Umbraco.InnerContent/PropertyEditors/SimpleInnerContentPropertyValueEditor.cs @@ -8,7 +8,7 @@ namespace Our.Umbraco.InnerContent.PropertyEditors { - public class SimpleInnerContentPropertyValueEditor : InnerContentPropertyValueEditorWrapper + public class SimpleInnerContentPropertyValueEditor : InnerContentPropertyValueEditor { public SimpleInnerContentPropertyValueEditor(PropertyValueEditor wrapped) : base(wrapped) diff --git a/src/Our.Umbraco.InnerContent/Converters/InnerContentValueConverter.cs b/src/Our.Umbraco.InnerContent/ValueConverters/InnerContentValueConverter.cs similarity index 95% rename from src/Our.Umbraco.InnerContent/Converters/InnerContentValueConverter.cs rename to src/Our.Umbraco.InnerContent/ValueConverters/InnerContentValueConverter.cs index 10a74d8..54afb1c 100644 --- a/src/Our.Umbraco.InnerContent/Converters/InnerContentValueConverter.cs +++ b/src/Our.Umbraco.InnerContent/ValueConverters/InnerContentValueConverter.cs @@ -4,7 +4,7 @@ using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; -namespace Our.Umbraco.InnerContent.Converters +namespace Our.Umbraco.InnerContent.ValueConverters { public abstract class InnerContentValueConverter : PropertyValueConverterBase { diff --git a/src/Our.Umbraco.InnerContent/Web/Controllers/InnerContentApiController.cs b/src/Our.Umbraco.InnerContent/Web/Controllers/InnerContentApiController.cs index 4d7db26..af6ff1d 100644 --- a/src/Our.Umbraco.InnerContent/Web/Controllers/InnerContentApiController.cs +++ b/src/Our.Umbraco.InnerContent/Web/Controllers/InnerContentApiController.cs @@ -3,7 +3,10 @@ using System.Linq; using System.Web.Http; using System.Web.Http.ModelBinding; +using Newtonsoft.Json.Linq; +using Our.Umbraco.InnerContent.Helpers; using Our.Umbraco.InnerContent.Web.WebApi.Filters; +using Umbraco.Core.Services; using Umbraco.Web.Editors; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Mvc; @@ -32,18 +35,23 @@ public IEnumerable GetAllContentTypes() [HttpGet] public IEnumerable GetContentTypesByGuid([ModelBinder] Guid[] guids) { - return Services.ContentTypeService.GetAllContentTypes() - .Where(x => guids == null || guids.Contains(x.Key)) - .OrderBy(x => x.SortOrder) - .Select(x => new - { - id = x.Id, - guid = x.Key, - name = x.Name, - alias = x.Alias, - icon = string.IsNullOrWhiteSpace(x.Icon) || x.Icon == ".sprTreeFolder" ? "icon-folder" : x.Icon, - tabs = x.CompositionPropertyGroups.Select(y => y.Name).Distinct() - }); + var contentTypes = Services.ContentTypeService.GetAllContentTypes(guids).OrderBy(x => x.SortOrder).ToList(); + var blueprints = Services.ContentService.GetBlueprintsForContentTypes(contentTypes.Select(x => x.Id).ToArray()).ToArray(); + + // NOTE: Using an anonymous class, as the `ContentTypeBasic` type is heavier than what we need (for our requirements) + return contentTypes.Select(ct => new + { + // TODO: localize the name and description (in case of dictionary items) + // Umbraco core uses `localizedTextService.UmbracoDictionaryTranslate`, but this is currently marked as internal. + // https://github.com/umbraco/Umbraco-CMS/blob/release-7.7.0/src/Umbraco.Core/Services/LocalizedTextServiceExtensions.cs#L76 + + name = ct.Name, + description = ct.Description, + guid = ct.Key, + key = ct.Key, + icon = string.IsNullOrWhiteSpace(ct.Icon) || ct.Icon == ".sprTreeFolder" ? "icon-document" : ct.Icon, + blueprints = blueprints.Where(bp => bp.ContentTypeId == ct.Id).ToDictionary(bp => bp.Id, bp => bp.Name) + }); } [HttpGet] @@ -80,5 +88,25 @@ public ContentItemDisplay GetContentTypeScaffoldByGuid(Guid guid) var contentType = Services.ContentTypeService.GetContentType(guid); return new ContentController().GetEmpty(contentType.Alias, -20); } + + [HttpGet] + [UseInternalActionFilter("Umbraco.Web.WebApi.Filters.OutgoingEditorModelEventAttribute", onActionExecuted: true)] + public ContentItemDisplay GetContentTypeScaffoldByBlueprintId(int blueprintId) + { + return new ContentController().GetEmpty(blueprintId, -20); + } + + [HttpPost] + public SimpleNotificationModel CreateBlueprintFromContent([FromBody] JObject item, int userId = 0) + { + var blueprint = InnerContentHelper.ConvertInnerContentToBlueprint(item, userId); + + Services.ContentService.SaveBlueprint(blueprint, userId); + + return new SimpleNotificationModel(new Notification( + Services.TextService.Localize("blueprints/createdBlueprintHeading"), + Services.TextService.Localize("blueprints/createdBlueprintMessage", new[] { blueprint.Name }), + global::Umbraco.Web.UI.SpeechBubbleIcon.Success)); + } } } \ No newline at end of file diff --git a/src/Our.Umbraco.InnerContent/Web/UI/App_Plugins/InnerContent/css/innercontent.css b/src/Our.Umbraco.InnerContent/Web/UI/App_Plugins/InnerContent/css/innercontent.css index 3c0fdf5..5fe1ff7 100644 --- a/src/Our.Umbraco.InnerContent/Web/UI/App_Plugins/InnerContent/css/innercontent.css +++ b/src/Our.Umbraco.InnerContent/Web/UI/App_Plugins/InnerContent/css/innercontent.css @@ -1,28 +1,29 @@ -/* - Inner Content - Doc Type Picker -*/ -.inner-content__doctypepicker table input, .inner-content__doctypepicker table select { +/* Inner Content - Doc Type Picker */ + +.inner-content__doctypepicker table input, +.inner-content__doctypepicker table select { width: 100%; padding-right: 0; } -.inner-content__doctypepicker table td.icon-navigation, .inner-content__doctypepicker i.inner-content__help-icon { +.inner-content__doctypepicker table td.icon-navigation, +.inner-content__doctypepicker i.inner-content__help-icon { vertical-align: middle; color: #CCC; } -.inner-content__doctypepicker table td.icon-navigation:hover, .inner-content__doctypepicker i.inner-content__help-icon:hover { +.inner-content__doctypepicker table td.icon-navigation:hover, +.inner-content__doctypepicker i.inner-content__help-icon:hover { color: #343434; } -.inner-content__doctypepicker i.inner-content__help-icon { - margin-left: 10px; -} - .inner-content__doctypepicker table .td-min { width: 1px; } + +/* Inner Content - Content Overlay Panel */ + .inner-content-overlay > .umb-overlay-right > .umb-overlay__form > .umb-overlay-container { padding: 0; position: static; @@ -31,3 +32,7 @@ .inner-content-dialog > .nav-tabs { margin: -31px 0 0 20px; } + +.inner-content-pane { + margin: 30px 20px; +} diff --git a/src/Our.Umbraco.InnerContent/Web/UI/App_Plugins/InnerContent/js/innercontent.controllers.js b/src/Our.Umbraco.InnerContent/Web/UI/App_Plugins/InnerContent/js/innercontent.js similarity index 61% rename from src/Our.Umbraco.InnerContent/Web/UI/App_Plugins/InnerContent/js/innercontent.controllers.js rename to src/Our.Umbraco.InnerContent/Web/UI/App_Plugins/InnerContent/js/innercontent.js index e8acaa1..ce0e38b 100644 --- a/src/Our.Umbraco.InnerContent/Web/UI/App_Plugins/InnerContent/js/innercontent.controllers.js +++ b/src/Our.Umbraco.InnerContent/Web/UI/App_Plugins/InnerContent/js/innercontent.js @@ -1,100 +1,154 @@ // Prevalue Editors -angular.module("umbraco").controller("Our.Umbraco.InnerContent.Controllers.DocTypeTabPickerController", [ +angular.module("umbraco").controller("Our.Umbraco.InnerContent.Controllers.DocTypePickerController", [ "$scope", "innerContentService", function ($scope, innerContentService) { - $scope.add = function () { - $scope.model.value.push({ - icContentTypeGuid: "", - icTabAlias: "", - nameTemplate: "" - }); - } + var vm = this; + vm.add = add; + vm.remove = remove; + vm.tooltipMouseOver = tooltipMouseOver; + vm.tooltipMouseLeave = tooltipMouseLeave; - $scope.selectedDocTypeTabs = function (cfg) { - var dt = _.find($scope.model.docTypes, function (itm) { - return itm.guid.toLowerCase() === cfg.icContentTypeGuid.toLowerCase(); - }); - var tabs = dt ? dt.tabs : []; - if (!_.contains(tabs, cfg.icTabAlias)) { - cfg.icTabAlias = tabs[0]; - } - return tabs; - } - - $scope.remove = function (index) { - $scope.model.value.splice(index, 1); - } - - $scope.sortableOptions = { + vm.sortableOptions = { axis: "y", + containment: "parent", cursor: "move", - handle: ".icon-navigation" + handle: ".icon-navigation", + opacity: 0.7, + scroll: true, + tolerance: "pointer", + stop: function (e, ui) { + setDirty(); + } + }; + + vm.tooltip = { + show: false, + event: null, + content: null }; innerContentService.getAllContentTypes().then(function (docTypes) { - $scope.model.docTypes = docTypes; + vm.docTypes = docTypes; }); if (!$scope.model.value) { $scope.model.value = []; - $scope.add(); + add(); } - } -]); - -angular.module("umbraco").controller("Our.Umbraco.InnerContent.Controllers.DocTypePickerController", [ - "$scope", - "innerContentService", - - function ($scope, innerContentService) { - - $scope.add = function () { + function add() { $scope.model.value.push({ icContentTypeGuid: "", nameTemplate: "" }); - } + setDirty(); + }; - $scope.remove = function (index) { + function remove(index) { $scope.model.value.splice(index, 1); - } + setDirty(); + }; - $scope.sortableOptions = { - axis: "y", - cursor: "move", - handle: ".icon-navigation" + function tooltipMouseOver($event) { + vm.tooltip = { + show: true, + event: $event, + content: $event.currentTarget.dataset.tooltip + }; }; - innerContentService.getAllContentTypes().then(function (docTypes) { - $scope.model.docTypes = docTypes; - }); + function tooltipMouseLeave() { + vm.tooltip = { + show: false, + event: null, + content: null + }; + }; - if (!$scope.model.value) { - $scope.model.value = []; - $scope.add(); - } + function setDirty() { + if ($scope.propertyForm) { + $scope.propertyForm.$setDirty(); + } + }; } ]); // Property Editors +angular.module("umbraco").controller("Our.Umbraco.InnerContent.Controllers.InnerContentCreateController", + [ + "$scope", + "blueprintConfig", + + function ($scope, blueprintConfig) { + + function initialize() { + + $scope.allowedTypes = $scope.model.availableItems; + $scope.allowBlank = blueprintConfig.allowBlank; + + if ($scope.allowedTypes.length === 1) { + $scope.selectedDocType = $scope.allowedTypes[0]; + $scope.selectContentType = false; + $scope.selectBlueprint = true; + } else { + $scope.selectContentType = true; + $scope.selectBlueprint = false; + } + }; + + function createBlank(docTypeKey) { + $scope.model.selectedItem = { "key": docTypeKey, "blueprint": null }; + $scope.model.submit($scope.model); + }; + + function createOrSelectBlueprintIfAny(docType) { + var blueprintIds = _.keys(docType.blueprints || {}); + $scope.selectedDocType = docType; + if (blueprintIds.length) { + if (blueprintConfig.skipSelect) { + createFromBlueprint(docType.key, blueprintIds[0]); + } else { + $scope.selectContentType = false; + $scope.selectBlueprint = true; + } + } else { + createBlank(docType.key); + } + }; + + function createFromBlueprint(docTypeKey, blueprintId) { + $scope.model.selectedItem = { "key": docTypeKey, "blueprint": blueprintId }; + $scope.model.submit($scope.model); + }; + + $scope.createBlank = createBlank; + $scope.createOrSelectBlueprintIfAny = createOrSelectBlueprintIfAny; + $scope.createFromBlueprint = createFromBlueprint; + + initialize(); + } + ]); + angular.module("umbraco").controller("Our.Umbraco.InnerContent.Controllers.InnerContentDialogController", [ "$scope", - "$rootScope", - "$interpolate", + "overlayHelper", - function ($scope, $rootScope) { + function ($scope, overlayHelper) { $scope.item = $scope.model.dialogData.item; // Set a nodeContext property as nested property editors // can use this to know what doc type this node is etc // NC + DTGE do the same $scope.nodeContext = $scope.item; + + // When using doctype compositions, the tab Id may conflict with any nested inner-content items. + // This attempts to make the tab ID to be unique. + $scope.tabIdSuffix = "_" + $scope.item.contentTypeAlias + "_" + overlayHelper.getNumberOfOverlays(); } ]); @@ -115,41 +169,43 @@ angular.module("umbraco.directives").directive("innerContentOverlay", [ return _.find(scope.config.contentTypes, function (ct) { return ct.icContentTypeGuid.toLowerCase() === guid.toLowerCase(); }); - } + }; // Helper function to createEditorModel but at the same time // cache the scaffold so that if we create another item of the same // content type, we don't need to fetch the scaffold again - var createEditorModel = function (contentType, dbModel) { + var createEditorModel = function (contentType, dbModel, blueprintId) { var process = function (editorModel, dbModel2) { var n = angular.copy(editorModel); n.key = innerContentService.generateUid(); // Create new ID for item return innerContentService.extendEditorModel(n, dbModel2); - } + }; - if (scope.config.editorModels.hasOwnProperty(contentType.icContentTypeGuid)) { - var res = process(scope.config.editorModels[contentType.icContentTypeGuid], dbModel); + var cacheKey = contentType.icContentTypeGuid + ":" + blueprintId; + if (scope.config.editorModels.hasOwnProperty(cacheKey)) { + var res = process(scope.config.editorModels[cacheKey], dbModel); return $q.when(res); } else { - return innerContentService.createEditorModel(contentType).then(function (em) { - scope.config.editorModels[contentType.icContentTypeGuid] = em; - var res = process(scope.config.editorModels[contentType.icContentTypeGuid], dbModel); + return innerContentService.createEditorModel(contentType, dbModel, blueprintId).then(function (em) { + scope.config.editorModels[cacheKey] = em; + var res = process(scope.config.editorModels[cacheKey], dbModel); return res; }); } - } + }; scope.contentTypePickerOverlay = { - view: "itempicker", - filter: false, + view: Umbraco.Sys.ServerVariables.umbracoSettings.appPluginsPath + "/innercontent/views/innercontent.create.html", title: "Insert Content", show: false, + hideSubmitButton: true, closeButtonLabelKey: "general_cancel", submit: function (model) { - var ct = getContentType(model.selectedItem.guid); - createEditorModel(ct).then(function (em) { + var ct = getContentType(model.selectedItem.key); + var bp = model.selectedItem.blueprint; + createEditorModel(ct, null, bp).then(function (em) { scope.currentItem = em; scope.closeContentTypePickerOverlay(); scope.openContentEditorOverlay(); @@ -187,8 +243,8 @@ angular.module("umbraco.directives").directive("innerContentOverlay", [ return; } - if (scope.contentTypePickerOverlay.availableItems.length === 1) { - var ct = getContentType(scope.contentTypePickerOverlay.availableItems[0].guid); + if (scope.contentTypePickerOverlay.availableItems.length === 1 && _.isEmpty(scope.contentTypePickerOverlay.availableItems[0].blueprints)) { + var ct = getContentType(scope.contentTypePickerOverlay.availableItems[0].key); createEditorModel(ct).then(function (em) { scope.currentItem = em; scope.openContentEditorOverlay(); @@ -236,7 +292,7 @@ angular.module("umbraco.directives").directive("innerContentOverlay", [ }); } - } + }; // Initialize if (scope.config) { @@ -247,11 +303,10 @@ angular.module("umbraco.directives").directive("innerContentOverlay", [ var guids = scope.config.contentTypes.map(function (itm) { return itm.icContentTypeGuid; }); - - innerContentService.getContentTypesByGuid(guids).then(function (docTypes) { + innerContentService.getContentTypesByGuid(guids).then(function (contentTypes) { // Cache items in the PE's config so we only request these once per PE instance - scope.config.contentTypePickerItems = docTypes; + scope.config.contentTypePickerItems = contentTypes; initOpen(); @@ -308,23 +363,22 @@ angular.module("umbraco.directives").directive("innerContentUnsavedChanges", [ if (scope.canConfirmClose) { overlayScope.oldCloseOverLay = overlayScope.closeOverLay; overlayScope.closeOverLay = function () { - // TODO: Find out why this throws an error with nested IC editors. (`$dirty` is undefined) [LK:2018-02-28] - if (overlayScope.overlayForm.$dirty) { + if (overlayScope.overlayForm && overlayScope.overlayForm.$dirty) { scope.showConfirmClose = true; } else { overlayScope.oldCloseOverLay.apply(overlayScope); } - } + }; } scope.confirmClose = function () { scope.showConfirmClose = false; overlayScope.oldCloseOverLay.apply(overlayScope); - } + }; scope.cancelClose = function () { scope.showConfirmClose = false; - } + }; } @@ -343,18 +397,17 @@ angular.module("umbraco.directives").directive("innerContentUnsavedChanges", [ // Services angular.module("umbraco").factory("innerContentService", [ - "$q", "$interpolate", - "contentResource", - + "localStorageService", "Our.Umbraco.InnerContent.Resources.InnerContentResources", - function ($q, $interpolate, contentResource, icResources) { + function ($interpolate, localStorageService, icResources) { var self = {}; - var getScaffold = function (contentType) { - return icResources.getContentTypeScaffoldByGuid(contentType.icContentTypeGuid).then(function (scaffold) { + var getScaffold = function (contentType, blueprintId) { + + var process = function (scaffold) { // remove all tabs except the specified tab if (contentType.hasOwnProperty("icTabAlias")) { @@ -378,8 +431,14 @@ angular.module("umbraco").factory("innerContentService", [ return scaffold; - }); - } + }; + + if (blueprintId > 0) { + return icResources.getContentTypeScaffoldByBlueprintId(blueprintId).then(process); + } else { + return icResources.getContentTypeScaffoldByGuid(contentType.icContentTypeGuid).then(process); + } + }; self.populateName = function (itm, idx, contentTypes) { @@ -405,23 +464,23 @@ angular.module("umbraco").factory("innerContentService", [ delete itm.$index; } - } + }; self.getAllContentTypes = function () { return icResources.getAllContentTypes(); - } + }; self.getContentTypesByGuid = function (guids) { return icResources.getContentTypesByGuid(guids); - } + }; self.getContentTypeIconsByGuid = function (guids) { return icResources.getContentTypeIconsByGuid(guids); - } + }; - self.createEditorModel = function (contentType, dbModel) { + self.createEditorModel = function (contentType, dbModel, blueprintId) { - return getScaffold(contentType).then(function (scaffold) { + return getScaffold(contentType, blueprintId).then(function (scaffold) { scaffold.key = self.generateUid(); scaffold.icContentTypeGuid = contentType.icContentTypeGuid; @@ -431,7 +490,7 @@ angular.module("umbraco").factory("innerContentService", [ }); - } + }; self.extendEditorModel = function (editorModel, dbModel) { @@ -456,7 +515,7 @@ angular.module("umbraco").factory("innerContentService", [ return editorModel; - } + }; self.createDbModel = function (model) { @@ -478,17 +537,17 @@ angular.module("umbraco").factory("innerContentService", [ } return dbModel; - } + }; self.createDefaultDbModel = function (contentType) { return self.createEditorModel(contentType).then(function (editorModel) { return self.createDbModel(editorModel); }); - } + }; self.compareCurrentUmbracoVersion = function compareCurrentUmbracoVersion(v, options) { return this.compareVersions(Umbraco.Sys.ServerVariables.application.version, v, options); - } + }; self.compareVersions = function compareVersions(v1, v2, options) { @@ -539,10 +598,38 @@ angular.module("umbraco").factory("innerContentService", [ return 0; - } + }; + + self.canCopyContent = function () { + return localStorageService.isSupported; + }; + + self.canPasteContent = function () { + return localStorageService.isSupported; + }; + + self.setCopiedContent = function (itm) { + if (itm && itm.icContentTypeGuid) { + localStorageService.set("icContentTypeGuid", itm.icContentTypeGuid); + itm.key = undefined; + localStorageService.set("icContentJson", itm); + return true; + } + return false; + }; + + self.getCopiedContent = function () { + var itm = localStorageService.get("icContentJson"); + itm.key = self.generateUid(); + return itm; + }; + + self.getCopiedContentTypeGuid = function () { + return localStorageService.get("icContentTypeGuid"); + }; // Helpful methods - var lut = []; for (var i = 0; i < 256; i++) { lut[i] = (i < 16 ? "0" : "") + (i).toString(16); } + var lut = []; for (var i = 0; i < 256; i++) { lut[i] = (i < 16 ? "0" : "") + i.toString(16); } self.generateUid = function () { var d0 = Math.random() * 0xffffffff | 0; var d1 = Math.random() * 0xffffffff | 0; @@ -552,9 +639,91 @@ angular.module("umbraco").factory("innerContentService", [ lut[d1 & 0xff] + lut[d1 >> 8 & 0xff] + "-" + lut[d1 >> 16 & 0x0f | 0x40] + lut[d1 >> 24 & 0xff] + "-" + lut[d2 & 0x3f | 0x80] + lut[d2 >> 8 & 0xff] + "-" + lut[d2 >> 16 & 0xff] + lut[d2 >> 24 & 0xff] + lut[d3 & 0xff] + lut[d3 >> 8 & 0xff] + lut[d3 >> 16 & 0xff] + lut[d3 >> 24 & 0xff]; - } + }; return self; } -]); \ No newline at end of file +]); + +// Resources +angular.module("umbraco.resources").factory("Our.Umbraco.InnerContent.Resources.InnerContentResources", [ + + "$http", + "umbRequestHelper", + + function ($http, umbRequestHelper) { + return { + getAllContentTypes: function () { + return umbRequestHelper.resourcePromise( + $http({ + url: umbRequestHelper.convertVirtualToAbsolutePath("~/umbraco/backoffice/InnerContent/InnerContentApi/GetAllContentTypes"), + method: "GET" + }), + "Failed to retrieve content types" + ); + }, + getContentTypesByGuid: function (guids) { + return umbRequestHelper.resourcePromise( + $http({ + url: umbRequestHelper.convertVirtualToAbsolutePath("~/umbraco/backoffice/InnerContent/InnerContentApi/GetContentTypesByGuid"), + method: "GET", + params: { guids: guids } + }), + "Failed to retrieve content types" + ); + }, + getContentTypesByAlias: function (aliases) { + return umbRequestHelper.resourcePromise( + $http({ + url: umbRequestHelper.convertVirtualToAbsolutePath("~/umbraco/backoffice/InnerContent/InnerContentApi/GetContentTypesByAlias"), + method: "GET", + params: { aliases: aliases } + }), + "Failed to retrieve content types" + ); + }, + getContentTypeIconsByGuid: function (guids) { + return umbRequestHelper.resourcePromise( + $http({ + url: umbRequestHelper.convertVirtualToAbsolutePath("~/umbraco/backoffice/InnerContent/InnerContentApi/GetContentTypeIconsByGuid"), + method: "GET", + params: { guids: guids } + }), + "Failed to retrieve content type icons" + ); + }, + getContentTypeScaffoldByGuid: function (guid) { + return umbRequestHelper.resourcePromise( + $http({ + url: umbRequestHelper.convertVirtualToAbsolutePath("~/umbraco/backoffice/InnerContent/InnerContentApi/GetContentTypeScaffoldByGuid"), + method: "GET", + params: { guid: guid } + }), + "Failed to retrieve content type scaffold by Guid" + ); + }, + getContentTypeScaffoldByBlueprintId: function (blueprintId) { + return umbRequestHelper.resourcePromise( + $http({ + url: umbRequestHelper.convertVirtualToAbsolutePath("~/umbraco/backoffice/InnerContent/InnerContentApi/GetContentTypeScaffoldByBlueprintId"), + method: "GET", + params: { blueprintId: blueprintId } + }), + "Failed to retrieve content type scaffold by blueprint Id" + ); + }, + createBlueprintFromContent: function (data, userId) { + return umbRequestHelper.resourcePromise( + $http({ + url: umbRequestHelper.convertVirtualToAbsolutePath("~/umbraco/backoffice/InnerContent/InnerContentApi/CreateBlueprintFromContent"), + method: "POST", + params: { userId: userId }, + data: data + }), + "Failed to create blueprint from content" + ); + } + }; + } +]); diff --git a/src/Our.Umbraco.InnerContent/Web/UI/App_Plugins/InnerContent/js/innercontent.resources.js b/src/Our.Umbraco.InnerContent/Web/UI/App_Plugins/InnerContent/js/innercontent.resources.js deleted file mode 100644 index 66b2147..0000000 --- a/src/Our.Umbraco.InnerContent/Web/UI/App_Plugins/InnerContent/js/innercontent.resources.js +++ /dev/null @@ -1,54 +0,0 @@ -angular.module("umbraco.resources").factory("Our.Umbraco.InnerContent.Resources.InnerContentResources", - function ($q, $http, umbRequestHelper) { - return { - getAllContentTypes: function () { - return umbRequestHelper.resourcePromise( - $http({ - url: "/umbraco/backoffice/InnerContent/InnerContentApi/GetAllContentTypes", - method: "GET" - }), - "Failed to retrieve content types" - ); - }, - getContentTypesByGuid: function (guids) { - return umbRequestHelper.resourcePromise( - $http({ - url: "/umbraco/backoffice/InnerContent/InnerContentApi/GetContentTypesByGuid", - method: "GET", - params: { guids: guids } - }), - "Failed to retrieve content types" - ); - }, - getContentTypesByAlias: function (aliases) { - return umbRequestHelper.resourcePromise( - $http({ - url: "/umbraco/backoffice/InnerContent/InnerContentApi/GetContentTypesByAlias", - method: "GET", - params: { aliases: aliases } - }), - "Failed to retrieve content types" - ); - }, - getContentTypeIconsByGuid: function (guids) { - return umbRequestHelper.resourcePromise( - $http({ - url: "/umbraco/backoffice/InnerContent/InnerContentApi/GetContentTypeIconsByGuid", - method: "GET", - params: { guids: guids } - }), - "Failed to retrieve content type icons" - ); - }, - getContentTypeScaffoldByGuid: function (guid) { - return umbRequestHelper.resourcePromise( - $http({ - url: "/umbraco/backoffice/InnerContent/InnerContentApi/GetContentTypeScaffoldByGuid", - method: "GET", - params: { guid: guid } - }), - "Failed to retrieve content type scaffold by Guid" - ); - } - }; - }); \ No newline at end of file diff --git a/src/Our.Umbraco.InnerContent/Web/UI/App_Plugins/InnerContent/package.manifest b/src/Our.Umbraco.InnerContent/Web/UI/App_Plugins/InnerContent/package.manifest index 85d0c3f..f7e52f9 100644 --- a/src/Our.Umbraco.InnerContent/Web/UI/App_Plugins/InnerContent/package.manifest +++ b/src/Our.Umbraco.InnerContent/Web/UI/App_Plugins/InnerContent/package.manifest @@ -1,9 +1,8 @@ { - "javascript" : [ - "~/App_Plugins/InnerContent/js/innercontent.resources.js", - "~/App_Plugins/InnerContent/js/innercontent.controllers.js" - ], - "css" : [ - "~/App_Plugins/InnerContent/css/innercontent.css" - ] + "javascript": [ + "~/App_Plugins/InnerContent/js/innercontent.js" + ], + "css": [ + "~/App_Plugins/InnerContent/css/innercontent.css" + ] } \ No newline at end of file diff --git a/src/Our.Umbraco.InnerContent/Web/UI/App_Plugins/InnerContent/views/innercontent.create.html b/src/Our.Umbraco.InnerContent/Web/UI/App_Plugins/InnerContent/views/innercontent.create.html new file mode 100644 index 0000000..b8d815e --- /dev/null +++ b/src/Our.Umbraco.InnerContent/Web/UI/App_Plugins/InnerContent/views/innercontent.create.html @@ -0,0 +1,48 @@ +
+ +
Select a Content Type
+
Select a blueprint
+ +

+ +

+ + + + + +
\ No newline at end of file diff --git a/src/Our.Umbraco.InnerContent/Web/UI/App_Plugins/InnerContent/views/innercontent.dialog.html b/src/Our.Umbraco.InnerContent/Web/UI/App_Plugins/InnerContent/views/innercontent.dialog.html index 2915b26..9636e05 100644 --- a/src/Our.Umbraco.InnerContent/Web/UI/App_Plugins/InnerContent/views/innercontent.dialog.html +++ b/src/Our.Umbraco.InnerContent/Web/UI/App_Plugins/InnerContent/views/innercontent.dialog.html @@ -1,10 +1,10 @@ 
- +
- + diff --git a/src/Our.Umbraco.InnerContent/Web/UI/App_Plugins/InnerContent/views/innercontent.doctypepicker.html b/src/Our.Umbraco.InnerContent/Web/UI/App_Plugins/InnerContent/views/innercontent.doctypepicker.html index 434774b..1ac3718 100644 --- a/src/Our.Umbraco.InnerContent/Web/UI/App_Plugins/InnerContent/views/innercontent.doctypepicker.html +++ b/src/Our.Umbraco.InnerContent/Web/UI/App_Plugins/InnerContent/views/innercontent.doctypepicker.html @@ -1,4 +1,4 @@ -
+
@@ -9,36 +9,35 @@ - +
Name Template +
- - Remove + Remove
- Add - + Add
-
-
-

- Name template:
- Enter an angular expression to evaluate against each item for its name. -

-
+ + {{vm.tooltip.content}} +
\ No newline at end of file diff --git a/src/Our.Umbraco.InnerContent/Web/UI/App_Plugins/InnerContent/views/innercontent.doctypetabpicker.html b/src/Our.Umbraco.InnerContent/Web/UI/App_Plugins/InnerContent/views/innercontent.doctypetabpicker.html deleted file mode 100644 index 7023b45..0000000 --- a/src/Our.Umbraco.InnerContent/Web/UI/App_Plugins/InnerContent/views/innercontent.doctypetabpicker.html +++ /dev/null @@ -1,54 +0,0 @@ -
-
- - - - - - - - - - - - - - - - -
- - Document Type - - Tab - - Name Template - -
- - - - - - - Remove -
-
- Add - -
-
-
-
-

- Tab:
- Select the tab who's properties should be displayed. If left blank, the first tab on the doc type will be used. -

-

- Name template:
- Enter an angular expression to evaluate against each item for its name. -

-
-
\ No newline at end of file diff --git a/src/Our.Umbraco.InnerContent/Web/UI/App_Plugins/InnerContent/views/innercontent.overlay.html b/src/Our.Umbraco.InnerContent/Web/UI/App_Plugins/InnerContent/views/innercontent.overlay.html index 4902b96..663d047 100644 --- a/src/Our.Umbraco.InnerContent/Web/UI/App_Plugins/InnerContent/views/innercontent.overlay.html +++ b/src/Our.Umbraco.InnerContent/Web/UI/App_Plugins/InnerContent/views/innercontent.overlay.html @@ -3,7 +3,7 @@ + position="right"> - + @@ -26,7 +26,7 @@ - + @@ -36,6 +36,14 @@ + + + + + + + + \ No newline at end of file diff --git a/src/Our.Umbraco.InnerContent/packages.config b/src/Our.Umbraco.InnerContent/packages.config index a56b824..f186dc0 100644 --- a/src/Our.Umbraco.InnerContent/packages.config +++ b/src/Our.Umbraco.InnerContent/packages.config @@ -1,37 +1,42 @@  - - + + - - - - + + + + + + - + + - - - - - + + + + + + - + - + + \ No newline at end of file