diff --git a/src/Umbraco.Core/Manifest/ManifestContentAppFactory.cs b/src/Umbraco.Core/Manifest/ManifestContentAppFactory.cs index 1c50a4b895..788310d7a1 100644 --- a/src/Umbraco.Core/Manifest/ManifestContentAppFactory.cs +++ b/src/Umbraco.Core/Manifest/ManifestContentAppFactory.cs @@ -19,6 +19,8 @@ namespace Umbraco.Core.Manifest // '-content/foo', // hide for content type 'foo' // '+content/*', // show for all other content types // '+media/*', // show for all media types + // '-member/foo' // hide for member type 'foo' + // '+member/*' // show for all member types // '+role/admin' // show for admin users. Role based permissions will override others. // ] // }, @@ -56,6 +58,10 @@ namespace Umbraco.Core.Manifest partA = "media"; partB = media.ContentType.Alias; break; + case IMember member: + partA = "member"; + partB = member.ContentType.Alias; + break; default: return null; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/member/umbmembernodeinfo.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/member/umbmembernodeinfo.directive.js new file mode 100644 index 0000000000..3b6a2c069a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/member/umbmembernodeinfo.directive.js @@ -0,0 +1,73 @@ +(function () { + 'use strict'; + + function MemberNodeInfoDirective($timeout, $location, eventsService, userService, dateHelper, editorService) { + + function link(scope, element, attrs, ctrl) { + + var evts = []; + + //TODO: Infinite editing is not working yet. + scope.allowChangeMemberType = false; + + function onInit() { + // make sure dates are formatted to the user's locale + formatDatesToLocal(); + } + + function formatDatesToLocal() { + // get current backoffice user and format dates + userService.getCurrentUser().then(function (currentUser) { + scope.node.createDateFormatted = dateHelper.getLocalDate(scope.node.createDate, currentUser.locale, 'LLL'); + scope.node.updateDateFormatted = dateHelper.getLocalDate(scope.node.updateDate, currentUser.locale, 'LLL'); + }); + } + + scope.openMemberType = function (memberType) { + var editor = { + id: memberType.id, + submit: function (model) { + editorService.close(); + }, + close: function () { + editorService.close(); + } + }; + editorService.memberTypeEditor(editor); + }; + + // watch for content updates - reload content when node is saved, published etc. + scope.$watch('node.updateDate', function (newValue, oldValue) { + if (!newValue) { return; } + if (newValue === oldValue) { return; } + + // Update the create and update dates + formatDatesToLocal(); + }); + + //ensure to unregister from all events! + scope.$on('$destroy', function () { + for (var e in evts) { + eventsService.unsubscribe(evts[e]); + } + }); + + onInit(); + } + + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/member/umb-member-node-info.html', + scope: { + node: "=" + }, + link: link + }; + + return directive; + } + + angular.module('umbraco.directives').directive('umbMemberNodeInfo', MemberNodeInfoDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js index 3c64401933..272c2bae05 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js @@ -636,6 +636,23 @@ When building a custom infinite editor view you can use the same components as a open(editor); } + /** + * @ngdoc method + * @name umbraco.services.editorService#memberTypeEditor + * @methodOf umbraco.services.editorService + * + * @description + * Opens the member type editor in infinite editing, the submit callback returns the saved member type + * @param {Object} editor rendering options + * @param {Callback} editor.submit Submits the editor + * @param {Callback} editor.close Closes the editor + * @returns {Object} editor object + */ + function memberTypeEditor(editor) { + editor.view = "views/membertypes/edit.html"; + open(editor); + } + /** * @ngdoc method * @name umbraco.services.editorService#queryBuilder diff --git a/src/Umbraco.Web.UI.Client/src/views/components/member/umb-member-node-info.html b/src/Umbraco.Web.UI.Client/src/views/components/member/umb-member-node-info.html new file mode 100644 index 0000000000..62b2052771 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/member/umb-member-node-info.html @@ -0,0 +1,50 @@ +
+
+ + + +
+
{{ group.label }}
+
+
+ + + +
+
+ +
+ +
+ + + + + + {{node.createDateFormatted}} by {{ node.owner.name }} + + + + {{node.updateDateFormatted}} + + + + + + + + +
{{ node.id }}
+ {{ node.key }} +
+ +
+
+
+ +
diff --git a/src/Umbraco.Web.UI.Client/src/views/member/apps/content/content.controller.js b/src/Umbraco.Web.UI.Client/src/views/member/apps/content/content.controller.js new file mode 100644 index 0000000000..635c536816 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/member/apps/content/content.controller.js @@ -0,0 +1,11 @@ +(function () { + "use strict"; + + function MemberAppContentController($scope) { + + var vm = this; + + } + + angular.module("umbraco").controller("Umbraco.Editors.Member.Apps.ContentController", MemberAppContentController); +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/member/apps/content/content.html b/src/Umbraco.Web.UI.Client/src/views/member/apps/content/content.html new file mode 100644 index 0000000000..29df5ba638 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/member/apps/content/content.html @@ -0,0 +1,17 @@ +
+ +
+ +
+
{{ group.label }}
+
+ +
+ + + +
+ +
+ +
diff --git a/src/Umbraco.Web.UI.Client/src/views/member/apps/info/info.html b/src/Umbraco.Web.UI.Client/src/views/member/apps/info/info.html new file mode 100644 index 0000000000..bd9cb98b64 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/member/apps/info/info.html @@ -0,0 +1,4 @@ + + diff --git a/src/Umbraco.Web.UI.Client/src/views/member/edit.html b/src/Umbraco.Web.UI.Client/src/views/member/edit.html index 58bebe1e34..3fec222350 100644 --- a/src/Umbraco.Web.UI.Client/src/views/member/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/member/edit.html @@ -14,29 +14,22 @@ hide-icon="true" hide-description="true" hide-alias="true" + navigation="content.apps" + on-select-navigation-item="appChanged(item)" show-back-button="showBack()" on-back="onBack()" editorfor="header.editorfor" setpagetitle="header.setPageTitle"> - - -
- -
-
{{ group.label }}
+ + +
+
+
- -
- - - -
-
- -
+ diff --git a/src/Umbraco.Web.UI.Client/src/views/member/member.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/member/member.edit.controller.js index 23d35b6925..3ec76deb8e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/member/member.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/member/member.edit.controller.js @@ -45,9 +45,7 @@ function MemberEditController($scope, $routeParams, $location, appState, memberR $scope.content = data; - setHeaderNameState($scope.content); - - editorState.set($scope.content); + init(); $scope.page.loading = false; @@ -59,9 +57,7 @@ function MemberEditController($scope, $routeParams, $location, appState, memberR .then(function (data) { $scope.content = data; - setHeaderNameState($scope.content); - - editorState.set($scope.content); + init(); $scope.page.loading = false; @@ -90,9 +86,7 @@ function MemberEditController($scope, $routeParams, $location, appState, memberR $scope.content = data; - setHeaderNameState($scope.content); - - editorState.set($scope.content); + init(); if (!infiniteMode) { var path = buildTreePath(data); @@ -121,11 +115,48 @@ function MemberEditController($scope, $routeParams, $location, appState, memberR } - function setHeaderNameState(content) { + function init() { + + var content = $scope.content; + + // we need to check wether an app is present in the current data, if not we will present the default app. + var isAppPresent = false; + + // on first init, we dont have any apps. but if we are re-initializing, we do, but ... + if ($scope.app) { + + // lets check if it still exists as part of our apps array. (if not we have made a change to our docType, even just a re-save of the docType it will turn into new Apps.) + _.forEach(content.apps, function (app) { + if (app === $scope.app) { + isAppPresent = true; + } + }); + + // if we did reload our DocType, but still have the same app we will try to find it by the alias. + if (isAppPresent === false) { + _.forEach(content.apps, function (app) { + if (app.alias === $scope.app.alias) { + isAppPresent = true; + app.active = true; + $scope.appChanged(app); + } + }); + } + + } + + // if we still dont have a app, lets show the first one: + if (isAppPresent === false) { + content.apps[0].active = true; + $scope.appChanged(content.apps[0]); + } + + if (content.membershipScenario === 0) { + $scope.page.nameLocked = true; + } + + editorState.set($scope.content); - if(content.membershipScenario === 0) { - $scope.page.nameLocked = true; - } } /** Just shows a simple notification that there are client side validation issues to be fixed */ @@ -194,6 +225,15 @@ function MemberEditController($scope, $routeParams, $location, appState, memberR }; + $scope.appChanged = function (app) { + $scope.app = app; + + // setup infinite mode + if (infiniteMode) { + $scope.page.submitButtonLabelKey = "buttons_saveAndClose"; + } + } + $scope.showBack = function () { return !!listName; } diff --git a/src/Umbraco.Web/ContentApps/ContentEditorContentAppFactory.cs b/src/Umbraco.Web/ContentApps/ContentEditorContentAppFactory.cs index 8c251cacd2..add7e2f16a 100644 --- a/src/Umbraco.Web/ContentApps/ContentEditorContentAppFactory.cs +++ b/src/Umbraco.Web/ContentApps/ContentEditorContentAppFactory.cs @@ -14,6 +14,7 @@ namespace Umbraco.Web.ContentApps private ContentApp _contentApp; private ContentApp _mediaApp; + private ContentApp _memberApp; public ContentApp GetContentAppFor(object o, IEnumerable userGroups) { @@ -45,6 +46,16 @@ namespace Umbraco.Web.ContentApps case IMedia _: return null; + case IMember _: + return _memberApp ?? (_memberApp = new ContentApp + { + Alias = "umbContent", + Name = "Content", + Icon = Constants.Icons.Content, + View = "views/member/apps/content/content.html", + Weight = Weight + }); + default: throw new NotSupportedException($"Object type {o.GetType()} is not supported here."); } diff --git a/src/Umbraco.Web/ContentApps/ContentInfoContentAppFactory.cs b/src/Umbraco.Web/ContentApps/ContentInfoContentAppFactory.cs index 49be194349..fac03c43d0 100644 --- a/src/Umbraco.Web/ContentApps/ContentInfoContentAppFactory.cs +++ b/src/Umbraco.Web/ContentApps/ContentInfoContentAppFactory.cs @@ -13,6 +13,7 @@ namespace Umbraco.Web.ContentApps private ContentApp _contentApp; private ContentApp _mediaApp; + private ContentApp _memberApp; public ContentApp GetContentAppFor(object o, IEnumerable userGroups) { @@ -37,6 +38,15 @@ namespace Umbraco.Web.ContentApps View = "views/media/apps/info/info.html", Weight = Weight }); + case IMember _: + return _memberApp ?? (_memberApp = new ContentApp + { + Alias = "umbInfo", + Name = "Info", + Icon = "icon-info", + View = "views/member/apps/info/info.html", + Weight = Weight + }); default: throw new NotSupportedException($"Object type {o.GetType()} is not supported here."); diff --git a/src/Umbraco.Web/ContentApps/ListViewContentAppFactory.cs b/src/Umbraco.Web/ContentApps/ListViewContentAppFactory.cs index ae6d324e84..bf6184197f 100644 --- a/src/Umbraco.Web/ContentApps/ListViewContentAppFactory.cs +++ b/src/Umbraco.Web/ContentApps/ListViewContentAppFactory.cs @@ -45,6 +45,8 @@ namespace Umbraco.Web.ContentApps entityType = "media"; dtdId = Core.Constants.DataTypes.DefaultMediaListView; break; + case IMember member: + return null; default: throw new NotSupportedException($"Object type {o.GetType()} is not supported here."); } diff --git a/src/Umbraco.Web/Models/ContentEditing/MemberDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/MemberDisplay.cs index fd1c1ed5b8..3857731671 100644 --- a/src/Umbraco.Web/Models/ContentEditing/MemberDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/MemberDisplay.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Runtime.Serialization; using Umbraco.Core.Models; +using Umbraco.Core.Models.ContentEditing; using Umbraco.Core.Models.Membership; namespace Umbraco.Web.Models.ContentEditing @@ -15,6 +16,7 @@ namespace Umbraco.Web.Models.ContentEditing public MemberDisplay() { MemberProviderFieldMapping = new Dictionary(); + ContentApps = new List(); } [DataMember(Name = "username")] @@ -34,5 +36,7 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "fieldConfig")] public IDictionary MemberProviderFieldMapping { get; set; } + [DataMember(Name = "apps")] + public IEnumerable ContentApps { get; set; } } } diff --git a/src/Umbraco.Web/Models/Mapping/MemberMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/MemberMapDefinition.cs index 8671bfe538..851f452244 100644 --- a/src/Umbraco.Web/Models/Mapping/MemberMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/MemberMapDefinition.cs @@ -76,6 +76,7 @@ namespace Umbraco.Web.Models.Mapping // Umbraco.Code.MapAll -Trashed -IsContainer -VariesByCulture private void Map(IMember source, MemberDisplay target, MapperContext context) { + target.ContentApps = _commonMapper.GetContentApps(source); target.ContentTypeId = source.ContentType.Id; target.ContentTypeAlias = source.ContentType.Alias; target.ContentTypeName = source.ContentType.Name;