Merge remote-tracking branch 'origin/temp-U4-9776' into temp-U4-8632

# Conflicts:
#	src/Umbraco.Web.UI/config/trees.config
This commit is contained in:
Shannon
2017-05-16 15:19:45 +10:00
44 changed files with 2068 additions and 28 deletions

View File

@@ -118,6 +118,8 @@
public const string Scripts = "scripts";
public const string Users = "users";
//TODO: Fill in the rest!
}
}

View File

@@ -52,15 +52,38 @@ Use this directive to render an avatar.
function AvatarDirective() {
function link(scope, element, attrs, ctrl) {
scope.initials = "";
function onInit() {
scope.initials = getNameInitials(scope.name);
}
function getNameInitials(name) {
if(name) {
var initials = name.match(/\b\w/g) || [];
initials = ((initials.shift() || '') + (initials.pop() || '')).toUpperCase();
return initials;
}
}
onInit();
}
var directive = {
restrict: 'E',
replace: true,
templateUrl: 'views/components/umb-avatar.html',
scope: {
size: "@",
name: "@",
color: "@",
imgSrc: "@",
imgSrcset: "@"
}
},
link: link
};
return directive;

View File

@@ -0,0 +1,23 @@
(function() {
'use strict';
function BadgeDirective() {
var directive = {
restrict: 'E',
replace: true,
transclude: true,
templateUrl: 'views/components/umb-badge.html',
scope: {
size: "@?",
color: "@?"
}
};
return directive;
}
angular.module('umbraco.directives').directive('umbBadge', BadgeDirective);
})();

View File

@@ -0,0 +1,23 @@
(function() {
'use strict';
function CheckmarkDirective() {
var directive = {
restrict: 'E',
replace: true,
transclude: true,
templateUrl: 'views/components/umb-checkmark.html',
scope: {
size: "@?",
checked: "="
}
};
return directive;
}
angular.module('umbraco.directives').directive('umbCheckmark', CheckmarkDirective);
})();

View File

@@ -0,0 +1,38 @@
/**
* @ngdoc filter
* @name umbraco.filters.filter:umbWordLimit
* @namespace umbWordLimitFilter
*
* @description
* Limits the number of words in a string to the passed in value
*/
(function () {
'use strict';
function umbWordLimitFilter() {
return function (collection, property) {
if (!angular.isString(collection)) {
return collection;
}
if (angular.isUndefined(property)) {
return collection;
}
var newString = "";
var array = [];
array = collection.split(" ", property);
array.length = property;
newString = array.join(" ");
return newString;
};
}
angular.module('umbraco.filters').filter('umbWordLimit', umbWordLimitFilter);
})();

View File

@@ -0,0 +1,288 @@
/**
* @ngdoc service
* @name umbraco.resources.usersResource
* @function
*
* @description
* Used by the users section to get users and send requests to create, invite, delete, etc. users.
*/
(function () {
'use strict';
function usersResource($http, umbRequestHelper, $q) {
function getUser() {
var deferred = $q.defer();
var user = {
"name": "Tammy Contreras",
"email": "tammy@contreras.com"
};
deferred.resolve(user);
return deferred.promise;
}
function getUsers() {
var deferred = $q.defer();
var users = [
{
"id": 1,
"name": "Tammy Contreras",
"userGroups": [
{
"name": "Admin"
}
],
"avatar": "https://s3.amazonaws.com/uifaces/faces/twitter/adellecharles/128.jpg",
"state": "active",
"lastLogin": "2014-04-25T01:32:21.196Z"
},
{
"id": 2,
"name": "Edward Flores",
"userGroups": [
{
"name": "Admin"
}
],
"avatar": "https://s3.amazonaws.com/uifaces/faces/twitter/marcosmoralez/128.jpg",
"state": "active",
"lastLogin": "2014-04-25T01:32:21.196Z"
},
{
"id": 3,
"name": "Benjamin Mills",
"userGroups": [
{
"name": "Writer"
}
],
"avatar": "https://s3.amazonaws.com/uifaces/faces/twitter/dancounsell/128.jpg",
"state": "disabled",
"lastLogin": "2014-04-25T01:32:21.196Z"
},
{
"id": 4,
"name": "Samantha Martinez",
"userGroups": [
{
"name": "Editor"
}
],
"avatar": "",
"state": "pending",
"lastLogin": ""
},
{
"id": 5,
"name": "Angela Stone",
"userGroups": [
{
"name": "Editor"
}
],
"avatar": "https://s3.amazonaws.com/uifaces/faces/twitter/jina/128.jpg",
"state": "active",
"lastLogin": "2014-04-25T01:32:21.196Z"
},
{
"id": 6,
"name": "Beverly Silva",
"userGroups": [
{
"name": "Editor"
}
],
"avatar": "",
"state": "active",
"lastLogin": "2014-04-25T01:32:21.196Z"
},
{
"id": 7,
"name": "Arthur Welch",
"userGroups": [
{
"name": "Editor"
}
],
"avatar": "https://s3.amazonaws.com/uifaces/faces/twitter/ashleyford/128.jpg",
"state": "active",
"lastLogin": "2014-04-25T01:32:21.196Z"
},
{
"id": 8,
"name": "Ruth Turner",
"userGroups": [
{
"name": "Translator"
},
{
"name": "Editor"
}
],
"avatar": "",
"state": "pending",
"lastLogin": ""
},
{
"id": 9,
"name": "Tammy Contreras",
"userGroups": [
{
"name": "Translator"
}
],
"avatar": "https://s3.amazonaws.com/uifaces/faces/twitter/adellecharles/128.jpg",
"state": "active",
"lastLogin": "2014-04-25T01:32:21.196Z"
},
{
"id": 10,
"name": "Edward Flores",
"userGroups": [
{
"name": "Admin"
}
],
"avatar": "https://s3.amazonaws.com/uifaces/faces/twitter/marcosmoralez/128.jpg",
"state": "active",
"lastLogin": "2014-04-25T01:32:21.196Z"
},
{
"id": 11,
"name": "Benjamin Mills",
"userGroups": [
{
"name": "Writer"
},
{
"name": "Translator"
}
],
"avatar": "https://s3.amazonaws.com/uifaces/faces/twitter/dancounsell/128.jpg",
"state": "disabled",
"lastLogin": "2014-04-25T01:32:21.196Z"
},
{
"id": 12,
"name": "Samantha Martinez",
"userGroupName": "Editor",
"userGroups": [
{
"name": "Editor"
}
],
"avatar": "",
"state": "pending",
"lastLogin": ""
},
{
"id": 13,
"name": "Angela Stone",
"userGroups": [
{
"name": "Editor"
}
],
"avatar": "https://s3.amazonaws.com/uifaces/faces/twitter/jina/128.jpg",
"state": "active",
"lastLogin": "2014-04-25T01:32:21.196Z"
},
{
"id": 14,
"name": "Beverly Silva",
"userGroups": [
{
"name": "Editor"
}
],
"avatar": "",
"state": "active",
"lastLogin": "2014-04-25T01:32:21.196Z"
},
{
"id": 15,
"name": "Arthur Welch",
"userGroups": [
{
"name": "Editor"
}
],
"avatar": "https://s3.amazonaws.com/uifaces/faces/twitter/ashleyford/128.jpg",
"state": "active",
"lastLogin": "2014-04-25T01:32:21.196Z"
},
{
"id": 16,
"name": "Ruth Turner",
"userGroups": [
{
"name": "Translator"
}
],
"avatar": "",
"state": "pending",
"lastLogin": ""
}
];
deferred.resolve(users);
return deferred.promise;
}
function getUserRole() {
var deferred = $q.defer();
var user = {
"name": "Admin",
"alias": "admin",
"id": 1,
"icon": "icon-medal"
};
deferred.resolve(user);
return deferred.promise;
}
function getUserGroups() {
var deferred = $q.defer();
var userGroups = [
{
"name": "Admin",
"alias": "admin",
"id": 1,
"icon": "icon-medal"
},
{
"name": "Writer",
"alias": "writer",
"id": 2,
"icon": "icon-edit"
},
{
"name": "Editor",
"alias": "editor",
"id": 3,
"icon": "icon-tools"
},
{
"name": "Translator",
"alias": "translator",
"id": 4,
"icon": "icon-globe"
}
];
deferred.resolve(userGroups);
return deferred.promise;
}
var resource = {
getUser: getUser,
getUsers: getUsers,
getUserRole: getUserRole,
getUserGroups: getUserGroups
};
return resource;
}
angular.module('umbraco.resources').factory('usersResource', usersResource);
})();

View File

@@ -120,6 +120,8 @@
@import "components/umb-querybuilder.less";
@import "components/umb-pagination.less";
@import "components/umb-mini-list-view.less";
@import "components/umb-badge.less";
@import "components/umb-checkmark.less";
@import "components/buttons/umb-button.less";
@import "components/buttons/umb-button-group.less";
@@ -130,6 +132,8 @@
@import "components/umb-node-preview.less";
@import "components/umb-mini-editor.less";
@import "components/users/umb-users.less";
// Utilities
@import "utilities/_flexbox.less";
@import "utilities/_spacing.less";

View File

@@ -18,10 +18,12 @@
transition: opacity 0.25s ease;
display: flex;
flex-wrap: wrap;
align-items: center;
}
.umb-button__icon {
margin-right: 5px;
line-height: 1em;
}
.umb-button__content.-hidden {

View File

@@ -2,29 +2,87 @@
border-radius: 50%;
width: 50px;
height: 50px;
background-color: transparent;
display: flex;
align-items: center;
justify-content: center;
color: @black;
font-weight: bold;
font-size: 16px;
box-sizing: border-box;
}
.umb-avatar.-xs {
/* Sizes */
.umb-avatar--xxs {
width: 26px;
height: 26px;
font-size: 12px;
}
.umb-avatar--xs {
width: 30px;
height: 30px;
font-size: 12px;
}
.umb-avatar.-s {
.umb-avatar--s {
width: 40px;
height: 40px;
font-size: 14px;
}
.umb-avatar.-m {
.umb-avatar--m {
width: 50px;
height: 50px;
font-size: 16px;
}
.umb-avatar.-l {
.umb-avatar--l {
width: 70px;
height: 70px;
font-size: 18px;
}
.umb-avatar.-xl {
.umb-avatar--xl {
width: 100px;
height: 100px;
font-size: 20px;
}
/* Colors */
.umb-avatar--white {
background-color: @white;
color: @black;
}
.umb-avatar--gray {
background-color: @gray-10;
color: @black;
}
.umb-avatar--primary {
background-color: @turquoise-l1;
color: @white;
}
.umb-avatar--secondary {
background-color: @purple-l1;
color: @white;
}
.umb-avatar--success {
background-color: @green-l1;
color: @white;
}
.umb-avatar--warning {
background-color: @yellow-l1;
color: @white;
}
.umb-avatar--danger {
background-color: @red-l1;
color: @white;
}

View File

@@ -0,0 +1,65 @@
.umb-badge {
padding: 6px 8px;
margin: 0 5px 0 0;
font-weight: 600;
color: @black;
background-color: @turquoise-washed;
border-width: 1px;
border-style: solid;
border-color: @turquoise;
display: inline-flex;
align-items: center;
justify-content: center;
}
// Colors
.umb-badge--primary {
background-color: @turquoise-washed;
border-color: @turquoise;
}
.umb-badge--seconday {
background-color: @purple-washed;
border-color: @purple;
}
.umb-badge--danger {
background-color: @red-washed;
border-color: @red;
}
.umb-badge--warning {
background-color: @yellow-washed;
border-color: @yellow;
}
.umb-badge--success {
background-color: @green-washed;
border-color: @green;
}
// Size
.umb-badge--xs {
font-size: 13px;
padding: 1px 6px;
}
.umb-badge--s {
font-size: 14px;
padding: 3px 6px;
}
.umb-badge--m {
font-size: 16px;
padding: 6px 8px;
}
.umb-badge--l {
font-size: 18px;
padding: 6px 8px;
}
.umb-badge--xl {
font-size: 20px;
padding: 6px 8px;
}

View File

@@ -0,0 +1,46 @@
.umb-checkmark {
border: 2px solid @white;
width: 25px;
height: 25px;
background: @gray-7;
border-radius: 50%;
box-sizing: border-box;
display: flex;
justify-content: center;
align-items: center;
color: @white;
cursor: pointer;
font-size: 15px;
}
.umb-checkmark--checked {
background: @green;
}
.umb-checkmark--xs {
width: 20px;
height: 20px;
font-size: 13px;
}
.umb-checkmark--s {
width: 25px;
height: 25px;
}
.umb-checkmark--m {
width: 30px;
height: 30px;
}
.umb-checkmark--l {
width: 40px;
height: 40px;
font-size: 18px;
}
.umb-checkmark--xl {
width: 50px;
height: 50px;
font-size: 20px;
}

View File

@@ -76,7 +76,7 @@
display: flex;
align-items: center;
justify-content: center;
border: 1px dashed @gray-8;
border: 1px dashed @gray-7;
color: @turquoise-d1;
font-weight: bold;
padding: 5px 15px;

View File

@@ -0,0 +1,75 @@
.umb-users {
display: flex;
flex-direction: row;
flex-wrap: wrap;
margin: -10px;
}
.umb-user {
flex: 1 1 200px;
background-color: @gray-10;
border: 1px solid @gray-9;
border-radius: 3px;
padding: 15px;
box-sizing: border-box;
display: flex;
flex-direction: column;
margin: 10px;
cursor: pointer;
position: relative;
align-items: center;
}
.umb-user--selected {
border-color: @turquoise;
}
.umb-user:hover {
border-color: @turquoise;
}
.umb-user__avatar {
margin-bottom: 10px;
margin-left: auto;
margin-right: auto;
}
.umb-user__badge {
position: absolute;
top: 10px;
left: 10px;
text-transform: capitalize;
}
.umb-user__name {
font-size: 15px;
font-weight: bold;
text-align: center;
margin-bottom: 2px;
}
.umb-user__checkmark {
position: absolute;
top: 10px;
right: 10px;
}
.umb-user__group {
font-size: 14px;
text-align: center;
}
.umb-user__divider {
display: block;
width: 20px;
border-bottom: 1px solid @gray-7;
margin-top: 15px;
margin-bottom: 10px;
margin-left: auto;
margin-right: auto;
}
.umb-user__last-login {
font-size: 13px;
text-align: center;
}

View File

@@ -113,6 +113,17 @@ h5.-black {
border: none;
}
/* BLOCK MODE */
.block-form .umb-control-group {
border-bottom: none;
margin-bottom: 10px !important;
padding-bottom: 0;
}
.block-form .umb-control-group label .help-block,
.block-form .umb-control-group label small {
font-size: 13px;
}
/*COMPACT MODE */
.compact .umb-pane{margin: 0px 0px 15px 0px;}

View File

@@ -13,6 +13,14 @@ table {
border-spacing: 0;
}
table thead {
background-color: @gray-10;
}
table tr:hover {
background-color: @gray-10;
}
// BASELINE STYLES
// ---------------
@@ -20,13 +28,13 @@ table {
.table {
width: 100%;
margin-bottom: @baseLineHeight;
border: 1px solid @gray-8;
// Cells
th,
td {
padding: 8px;
padding: 10px 8px;
line-height: @baseLineHeight;
text-align: left;
vertical-align: top;
border-top: 1px solid @tableBorder;
}
th {

View File

@@ -1,5 +1,20 @@
angular.module("umbraco").controller("Umbraco.Dialogs.LoginController",
function ($scope, $cookies, localizationService, userService, externalLoginInfo, resetPasswordCodeInfo, $timeout, authResource, dialogService) {
function ($scope, $cookies, $location, localizationService, userService, externalLoginInfo, resetPasswordCodeInfo, $timeout, authResource, dialogService) {
$scope.isInvite = false;
function init() {
// Check if it is a new user
if ($location.search().invite) {
$scope.isInvite = true;
$scope.inviteSetPassword = true;
}
}
$scope.inviteSavePassword = function() {
$scope.inviteSetPassword = false;
$scope.inviteSetAvatar = true;
};
var setFieldFocus = function(form, field) {
$timeout(function() {
@@ -234,4 +249,6 @@
$scope.showLogin();
}
init();
});

View File

@@ -1,5 +1,5 @@
<div ng-controller="Umbraco.Dialogs.LoginController">
<div id="login" class="umb-modalcolumn umb-dialog" ng-class="{'show-validation': loginForm.$invalid}" ng-cloak konami-code="activateKonamiMode()">
<div class="login-overlay__background-image" ng-if="backgroundImage" ng-style="{'background-image':'url(' + backgroundImage + ')'}"></div>
@@ -8,7 +8,61 @@
<img ng-src="assets/img/application/logo.png" ng-srcset="assets/img/application/logo@2x.png 2x, assets/img/application/logo@3x.png 3x">
</div>
<div class="umb-login-container">
<div ng-show="isInvite" class="umb-login-container">
<div class="form" ng-if="inviteSetPassword">
<h1 style="margin-bottom: 10px; text-align: left;">Hi, User</h1>
<p style="line-height: 1.6; margin-bottom: 25px;">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non libero vel turpis ultrices pharetra.</p>
<div class="control-group" ng-class="{error: setPasswordForm.password.$invalid}">
<label>
<localize key="user_newPassword">New password</localize>
<small style="font-size: 13px;">Some hint here about the new password</small>
</label>
<input type="password" ng-model="password" name="password" class="-full-width-input" umb-auto-focus />
</div>
<div class="control-group" ng-class="{error: setPasswordForm.confirmPassword.$invalid}">
<label><localize key="user_confirmNewPassword">Confirm new password</localize></label>
<input type="password" ng-model="confirmPassword" name="confirmPassword" class="-full-width-input" />
</div>
<div class="flex justify-between items-center">
<umb-button
type="button"
button-style="success"
action="inviteSavePassword()"
label="Save password">
</umb-button>
</div>
</div>
<div class="form" ng-if="inviteSetAvatar">
<div class="flex justify-center items-center">
<div style="border-radius: 50%; width: 100px; height: 100px; font-size: 50px; text-align: center; display: flex; align-items: center; justify-content: center; background-color: #F3F3F5; border: 2px dashed #A2A1A6; color: #A2A1A6;">+</div>
</div>
<h1 style="margin-bottom: 10px;">Upload a photo</h1>
<p style="text-align: center; margin-bottom: 25px; line-height: 1.6em;">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non libero vel turpis ultrices pharetra.</p>
<div class="flex justify-center items-center">
<umb-button
type="button"
button-style="success"
label="Get started">
</umb-button>
<umb-button
type="button"
button-style="link"
label="Skip">
</umb-button>
</div>
</div>
</div>
<div ng-show="!isInvite" class="umb-login-container">
<div class="form">
<h1>{{greeting}}</h1>

View File

@@ -3,11 +3,9 @@
<div class="umb-el-wrap">
<label ng-if="hideLabel!=='true'" class="control-label" for="{{alias}}">
{{labelstring}}
<small>{{descriptionstring}}</small>
<small ng-if="descriptionstring">{{descriptionstring}}</small>
</label>
<div class="controls controls-row" ng-transclude>
</div>
<div class="controls controls-row" ng-transclude></div>
</div>
</div>
</div>

View File

@@ -1 +1,7 @@
<img class="umb-avatar -{{size}}" ng-src="{{imgSrc}}" ng-srcset="{{imgSrcset}}" />
<div>
<img class="umb-avatar umb-avatar--{{size}}" ng-if="imgSrc || imgSrcset" ng-src="{{imgSrc}}" ng-srcset="{{imgSrcset}}" />
<div class="umb-avatar umb-avatar--{{size}} umb-avatar--{{color}}" ng-if="!imgSrc && !imgSrcset">
<span ng-if="name">{{ initials }}</span>
<span ng-if="!name">?</span>
</div>
</div>

View File

@@ -0,0 +1 @@
<span class="umb-badge umb-badge--{{color}} umb-badge--{{size}}" ng-transclude></span>

View File

@@ -0,0 +1 @@
<i class="icon-check umb-checkmark umb-checkmark--{{size}}" ng-class="{'umb-checkmark--checked': checked}"></i>

View File

@@ -0,0 +1,34 @@
(function () {
"use strict";
function UsersOverviewController($scope) {
var vm = this;
vm.page = {};
vm.page.name = "User Management";
vm.page.navigation = [
{
"name": "Users",
"icon": "icon-user",
"view": "views/users/views/users/users.html",
"active": true
},
{
"name": "Roles",
"icon": "icon-users",
"view": "views/users/views/roles/roles.html"
}
];
function init() {
}
init();
}
angular.module("umbraco").controller("Umbraco.Editors.Users.OverviewController", UsersOverviewController);
})();

View File

@@ -0,0 +1,28 @@
<div ng-controller="Umbraco.Editors.Users.OverviewController as vm" class="clearfix">
<form name="usersForm" novalidate val-form-manager>
<umb-editor-view footer="false">
<umb-editor-header
name="vm.page.name"
name-locked="true"
hide-icon="true"
hide-description="true"
navigation="vm.page.navigation"
hide-alias="true">
</umb-editor-header>
<umb-editor-container>
<umb-editor-sub-views
sub-views="vm.page.navigation">
</umb-editor-sub-views>
</umb-editor-container>
</umb-editor-view>
</form>
</div>

View File

@@ -0,0 +1,40 @@
(function () {
"use strict";
function UserRoleEditController($scope, $timeout, $location, usersResource) {
var vm = this;
vm.loading = false;
vm.page = {};
vm.userRole = {};
vm.goBack = goBack;
function init() {
vm.loading = true;
// get user
usersResource.getUserRole().then(function (userRole) {
vm.userRole = userRole;
});
// fake loading
$timeout(function () {
vm.loading = false;
}, 500);
}
function goBack() {
$location.path("users/users/overview");
}
init();
}
angular.module("umbraco").controller("Umbraco.Editors.Users.RoleController", UserRoleEditController);
})();

View File

@@ -0,0 +1,48 @@
<div ng-controller="Umbraco.Editors.Users.RoleController as vm" class="clearfix">
<umb-load-indicator ng-show="vm.loading"></umb-load-indicator>
<form name="editUserForm" novalidate val-form-manager>
<umb-editor-view>
<umb-editor-header
name="vm.userRole.name"
hide-icon="true"
hide-description="true"
hide-alias="true">
</umb-editor-header>
<umb-editor-container>
<umb-editor-sub-header>
<umb-editor-sub-header-content-left>
<a class="umb-package-details__back-link" href="" ng-click="vm.goBack()">&larr; Back to roles</a>
</umb-editor-sub-header-content-left>
</umb-editor-sub-header>
</umb-editor-container>
<umb-editor-footer>
<umb-editor-footer-content-right>
<umb-button
type="button"
action="vm.save()"
state="vm.page.saveButtonState"
button-style="success"
shortcut="ctrl+s"
label="Save"
label-key="buttons_save">
</umb-button>
</umb-editor-footer-content-right>
</umb-editor-footer>
</umb-editor-view>
</form>
</div>

View File

@@ -0,0 +1,40 @@
(function () {
"use strict";
function UserEditController($scope, $timeout, $location, usersResource) {
var vm = this;
vm.loading = false;
vm.page = {};
vm.user = {};
vm.goBack = goBack;
function init() {
vm.loading = true;
// get user
usersResource.getUser().then(function (user) {
vm.user = user;
});
// fake loading
$timeout(function () {
vm.loading = false;
}, 500);
}
function goBack() {
$location.path("users/users/overview");
}
init();
}
angular.module("umbraco").controller("Umbraco.Editors.Users.UserController", UserEditController);
})();

View File

@@ -0,0 +1,48 @@
<div ng-controller="Umbraco.Editors.Users.UserController as vm" class="clearfix">
<umb-load-indicator ng-show="vm.loading"></umb-load-indicator>
<form name="editUserForm" novalidate val-form-manager>
<umb-editor-view>
<umb-editor-header
name="vm.user.name"
hide-icon="true"
hide-description="true"
hide-alias="true">
</umb-editor-header>
<umb-editor-container>
<umb-editor-sub-header>
<umb-editor-sub-header-content-left>
<a class="umb-package-details__back-link" href="" ng-click="vm.goBack()">&larr; Back to users</a>
</umb-editor-sub-header-content-left>
</umb-editor-sub-header>
</umb-editor-container>
<umb-editor-footer>
<umb-editor-footer-content-right>
<umb-button
type="button"
action="vm.save()"
state="vm.page.saveButtonState"
button-style="success"
shortcut="ctrl+s"
label="Save"
label-key="buttons_save">
</umb-button>
</umb-editor-footer-content-right>
</umb-editor-footer>
</umb-editor-view>
</form>
</div>

View File

@@ -0,0 +1,94 @@
(function () {
"use strict";
function UserRolesController($scope, $timeout, $location, usersResource) {
var vm = this;
vm.userRoles = [];
vm.selection = [];
vm.viewState = 'overview';
vm.pagination = {
"pageNumber": 1,
"totalPages": 5
};
vm.setViewState = setViewState;
vm.goToUserRole = goToUserRole;
vm.clearSelection = clearSelection;
vm.selectUserRole = selectUserRole;
vm.selectAll = selectAll;
vm.areAllSelected = areAllSelected;
function init() {
vm.loading = true;
// Get users
usersResource.getUserGroups().then(function (userRoles) {
vm.userRoles = userRoles;
});
// fake loading
$timeout(function () {
vm.loading = false;
}, 500);
}
function setViewState(state) {
vm.viewState = state;
}
function selectUserRole(userRole, selection) {
if(userRole.selected) {
var index = selection.indexOf(userRole.id);
selection.splice(index, 1);
userRole.selected = false;
} else {
userRole.selected = true;
vm.selection.push(userRole.id);
}
}
function clearSelection() {
angular.forEach(vm.userRoles, function(userRole){
userRole.selected = false;
});
vm.selection = [];
}
function goToUserRole(userRole) {
$location.path('users/users/role/' + userRole.id);
}
function selectAll() {
if(areAllSelected()) {
vm.selection = [];
angular.forEach(vm.userRoles, function(userRole){
userRole.selected = false;
});
} else {
// clear selection so we don't add the same user twice
vm.selection = [];
// select all users
angular.forEach(vm.userRoles, function(userRole){
userRole.selected = true;
vm.selection.push(userRole.id);
});
}
}
function areAllSelected() {
if(vm.selection.length === vm.userRoles.length) {
return true;
}
}
init();
}
angular.module("umbraco").controller("Umbraco.Editors.Users.RolesController", UserRolesController);
})();

View File

@@ -0,0 +1,71 @@
<div ng-controller="Umbraco.Editors.Users.RolesController as vm" class="clearfix">
<umb-editor-sub-header>
<!-- No selection -->
<umb-editor-sub-header-content-left ng-if="vm.selection.length === 0">
<umb-button
type="button"
button-style="success"
action="vm.createUserRole()"
label="Create Role">
</umb-button>
</umb-editor-sub-header-content-left>
<umb-editor-sub-header-content-right ng-if="vm.selection.length === 0">
<umb-editor-sub-header-section>
<ng-form class="form-search -no-margin-bottom pull-right" novalidate>
<div class="inner-addon left-addon">
<i class="icon icon-search" ng-click="enterSearch($event)"></i>
<input
class="form-control search-input"
type="text"
localize="placeholder"
placeholder="@general_typeToSearch"
ng-model="options.filter"
ng-change="enterSearch()"
ng-keydown="forceSearch($event)"
prevent-enter-submit
no-dirty-check>
</div>
</ng-form>
</umb-editor-sub-header-section>
</umb-editor-sub-header-content-right>
<!-- With selection -->
<umb-editor-sub-header-content-left ng-if="vm.selection.length > 0">
<umb-editor-sub-header-section>
<umb-button
type="button"
label="Clear selection"
label-key="buttons_clearSelection"
action="vm.clearSelection()"
disabled="actionInProgress">
</umb-button>
</umb-editor-sub-header-section>
<umb-editor-sub-header-section>
<strong>{{ vm.selection.length }} <localize key="general_of">of</localize> {{ vm.userRoles.length }} <localize key="general_selected">selected</localize></strong>
</umb-editor-sub-header-section>
</umb-editor-sub-header-content-left>
<umb-editor-sub-header-content-right ng-if="vm.selection.length > 0">
<umb-button
type="button"
button-style="link"
label="Delete"
icon="icon-trash"
action="vm.deleteUserRole()">
</umb-button>
</umb-editor-sub-header-content-right>
</umb-editor-sub-header>
<div ng-repeat="userRole in vm.userRoles" class="flex items-center" ng-click="vm.selectUserRole(userRole, vm.selection)">
<i class="{{userRole.icon}}"></i>
<div class="bold" style="margin-right: 5px;">{{userRole.name}}</div>
<a href="" ng-click="vm.goToUserRole(userRole)">Edit</a>
</div>
</div>

View File

@@ -0,0 +1,268 @@
(function () {
"use strict";
function UsersController($scope, $timeout, $location, usersResource) {
var vm = this;
vm.users = [];
vm.userGroups = [];
vm.userStates = [];
vm.selection = [];
vm.newUser = {};
vm.newUser.userGroups = [];
vm.usersViewState = 'overview';
vm.allowDisableUser = true;
vm.allowEnableUser = true;
vm.allowSetUserRole = true;
vm.usersPagination = {
"pageNumber": 1,
"totalPages": 5
}
vm.layouts = [
{
"icon": "icon-thumbnails-small",
"path": "1",
"selected": true
},
{
"icon": "icon-list",
"path": "2",
"selected": true
}
];
vm.activeLayout = {
"icon": "icon-thumbnails-small",
"path": "1",
"selected": true
};
vm.defaultButton = {
labelKey:"users_inviteUser",
handler: function() {
vm.setUsersViewState('inviteUser');
}
};
vm.subButtons = [
{
labelKey: "users_createUser",
handler: function () {
vm.setUsersViewState('createUser');
}
}
];
vm.setUsersViewState = setUsersViewState;
vm.getUserStateType = getUserStateType;
vm.selectLayout = selectLayout;
vm.selectUser = selectUser;
vm.clearSelection = clearSelection;
vm.goToUser = goToUser;
vm.disableUser = disableUser;
vm.openUserGroupPicker = openUserGroupPicker;
vm.removeSelectedUserGroup = removeSelectedUserGroup;
vm.selectAll = selectAll;
vm.areAllSelected = areAllSelected;
function init() {
vm.loading = true;
// Get users
usersResource.getUsers().then(function (users) {
vm.users = users;
vm.userStates = getUserStates(vm.users);
formatDates(vm.users);
});
// Get user groups
usersResource.getUserGroups().then(function (userGroups) {
vm.userGroups = userGroups;
});
// fake loading
$timeout(function () {
vm.loading = false;
}, 500);
}
function setUsersViewState(state) {
vm.usersViewState = state;
}
function getUserStateType(state) {
switch (state) {
case "disabled" || "umbracoDisabled":
return "danger";
case "pending":
return "warning";
default:
return "success";
}
}
function selectLayout(selectedLayout) {
angular.forEach(vm.layouts, function(layout){
layout.active = false;
});
selectedLayout.active = true;
vm.activeLayout = selectedLayout;
}
function selectUser(user, selection) {
if(user.selected) {
var index = selection.indexOf(user.id);
selection.splice(index, 1);
user.selected = false;
} else {
user.selected = true;
vm.selection.push(user.id);
}
setBulkActions(vm.users);
}
function clearSelection() {
angular.forEach(vm.users, function(user){
user.selected = false;
});
vm.selection = [];
}
function goToUser(user, event) {
$location.path('users/users/user/' + user.id);
}
function disableUser() {
alert("disable users");
}
function openUserGroupPicker(event) {
vm.userGroupPicker = {
title: "Select user groups",
view: "itempicker",
event: event,
availableItems: vm.userGroups,
selectedItems: vm.newUser.userGroups,
show: true,
submit: function(model) {
if(model.selectedItem) {
vm.newUser.userGroups.push(model.selectedItem);
}
vm.userGroupPicker.show = false;
vm.userGroupPicker = null;
},
close: function(oldModel) {
vm.userGroupPicker.show = false;
vm.userGroupPicker = null;
}
};
}
function removeSelectedUserGroup(index, selection) {
selection.splice(index, 1);
}
function selectAll() {
if(areAllSelected()) {
vm.selection = [];
angular.forEach(vm.users, function(user){
user.selected = false;
});
} else {
// clear selection so we don't add the same user twice
vm.selection = [];
// select all users
angular.forEach(vm.users, function(user){
user.selected = true;
vm.selection.push(user.id);
});
}
}
function areAllSelected() {
if(vm.selection.length === vm.users.length) {
return true;
}
}
// helpers
function getUserStates(users) {
var userStates = [];
angular.forEach(users, function(user) {
var newUserState = {"name": user.state, "count": 1};
var userStateExists = false;
angular.forEach(userStates, function(userState){
if(newUserState.name === userState.name) {
userState.count = userState.count + 1;
userStateExists = true;
}
});
if(userStateExists === false) {
userStates.push(newUserState);
}
});
return userStates;
}
function formatDates(users) {
angular.forEach(users, function(user){
if(user.lastLogin) {
user.formattedLastLogin = moment(user.lastLogin).format("MMMM Do YYYY, HH:mm");
}
});
}
function setBulkActions(users) {
// reset all states
vm.allowDisableUser = true;
vm.allowEnableUser = true;
vm.allowSetUserGroup = true;
angular.forEach(users, function(user){
if(!user.selected) {
return;
}
if(user.state === "disabled") {
vm.allowDisableUser = false;
}
if(user.state === "active") {
vm.allowEnableUser = false;
}
if(user.state === "pending") {
vm.allowEnableUser = false;
}
});
}
init();
}
angular.module("umbraco").controller("Umbraco.Editors.Users.UsersController", UsersController);
})();

View File

@@ -0,0 +1,338 @@
<div ng-controller="Umbraco.Editors.Users.UsersController as vm" class="clearfix">
<umb-load-indicator ng-show="vm.loading"></umb-load-indicator>
<!-- Users Overview -->
<div ng-if="vm.usersViewState === 'overview'">
<umb-editor-sub-header>
<!-- No selection -->
<umb-editor-sub-header-content-left ng-if="vm.selection.length === 0">
<umb-button-group
ng-if="vm.defaultButton"
default-button="vm.defaultButton"
sub-buttons="vm.subButtons">
</umb-button-group>
</umb-editor-sub-header-content-left>
<umb-editor-sub-header-content-right ng-if="vm.selection.length === 0">
<umb-editor-sub-header-section>
<umb-layout-selector
ng-if="vm.layouts"
layouts="vm.layouts"
active-layout="vm.activeLayout"
on-layout-select="vm.selectLayout">
</umb-layout-selector>
</umb-editor-sub-header-section>
<umb-editor-sub-header-section>
<ng-form class="form-search -no-margin-bottom pull-right" novalidate>
<div class="inner-addon left-addon">
<i class="icon icon-search" ng-click="enterSearch($event)"></i>
<input
class="form-control search-input"
type="text"
localize="placeholder"
placeholder="@general_typeToSearch"
ng-model="options.filter"
ng-change="enterSearch()"
ng-keydown="forceSearch($event)"
prevent-enter-submit
no-dirty-check>
</div>
</ng-form>
</umb-editor-sub-header-section>
</umb-editor-sub-header-content-right>
<!-- With selection -->
<umb-editor-sub-header-content-left ng-if="vm.selection.length > 0">
<umb-editor-sub-header-section>
<umb-button
type="button"
label="Clear selection"
label-key="buttons_clearSelection"
action="vm.clearSelection()"
disabled="actionInProgress">
</umb-button>
</umb-editor-sub-header-section>
<umb-editor-sub-header-section>
<strong>{{ vm.selection.length }} <localize key="general_of">of</localize> {{ vm.users.length }} <localize key="general_selected">selected</localize></strong>
</umb-editor-sub-header-section>
</umb-editor-sub-header-content-left>
<umb-editor-sub-header-content-right ng-if="vm.selection.length > 0">
<umb-button
ng-if="vm.allowSetUserRole"
type="button"
button-style="link"
label="Set role"
icon="icon-users"
action="vm.setUserRoleUser()">
</umb-button>
<umb-button
ng-if="vm.allowEnableUser"
type="button"
button-style="link"
label="Enable"
icon="icon-check"
action="vm.enableUser()">
</umb-button>
<umb-button
ng-if="vm.allowDisableUser"
type="button"
button-style="link"
label="Disable"
icon="icon-block"
action="vm.disableUser()">
</umb-button>
</umb-editor-sub-header-content-right>
</umb-editor-sub-header>
<div style="margin-bottom: 20px;" class="flex items-center">
<div style="font-size: 16px;">
<span class="bold">Users</span> <span>({{vm.users.length}})</span>
</div>
<div class="flex" style="margin-left: auto;">
<div class="dropdown pull-right">
<a style="text-decoration: none;" class="btn btn-link dropdown-toggle" href="" ng-click="vm.showFilter = !vm.showFilter">
Show:
<span class="bold" style="text-decoration: underline; margin-left: 2px;">All</span>
<span class="caret"></span>
</a>
<ul ng-if="vm.showFilter" on-outside-click="vm.showFilter = false;" style="display: block;" class="dropdown-menu" role="menu" aria-labelledby="dropdownMenu">
<li style="padding: 5px 10px;">
<div class="flex items-center">
<input style="margin-right: 7px; margin-top: 2px;" type="checkbox" />All
</div>
</li>
<li class="divider"></li>
<li ng-repeat="userGroup in vm.userGroups" style="padding: 5px 10px;">
<div class="flex items-center">
<input style="margin-right: 7px; margin-top: 2px;" type="checkbox" />
{{ userGroup.name }}
</div>
</li>
<li class="divider"></li>
<li ng-repeat="userState in vm.userStates" style="padding: 5px 10px;">
<div class="flex items-center">
<input style="margin-right: 7px; margin-top: 2px;" type="checkbox" />
{{ userState.name }} ({{userState.count}})
</div>
</li>
</ul>
</div>
<div class="dropdown pull-right">
<a style="text-decoration: none;" class="btn btn-link dropdown-toggle" href="" data-toggle="dropdown">
Order by:
<span class="bold" style="text-decoration: underline; margin-left: 2px;">Last login</span>
<span class="caret"></span>
</a>
<ul class="dropdown-menu" role="menu" aria-labelledby="dropdownMenu">
<li><a tabindex="-1" href="#" prevent-default>Last login</a></li>
<li><a tabindex="-1" href="#" prevent-default>Name</a></li>
</ul>
</div>
</div>
</div>
<!-- Layout: Cards -->
<div class="umb-users" ng-if="vm.activeLayout.path === '1'">
<div class="umb-user" ng-repeat="user in vm.users" ng-class="{'umb-user--selected': user.selected}" ng-click="vm.selectUser(user, vm.selection)">
<umb-badge
class="umb-user__badge"
size="xs"
ng-if="user.state !== 'active'"
color="{{vm.getUserStateType(user.state)}}">
{{ user.state }}
</umb-badge>
<div class="umb-user__avatar">
<umb-avatar
size="l"
color="secondary"
name="{{user.name}}"
img-src="{{user.avatar}}">
</umb-avatar>
</div>
<div class="umb-user__checkmark" ng-if="user.selected"><umb-checkmark checked="user.selected" size="s"></umb-checkmark></div>
<a class="umb-user__name" href="" ng-click="vm.goToUser(user)">{{user.name}}</a>
<div class="umb-user__group">
<span ng-repeat="group in user.userGroups">{{ group.name }}<span ng-if="!$last">, </span></span>
</div>
<div class="umb-user__divider"></div>
<div class="umb-user__last-login">
<div ng-if="user.formattedLastLogin">
<div>Last login on</div>
{{ user.formattedLastLogin }}
</div>
<div ng-if="!user.formattedLastLogin">
<div>{{ user.name | umbWordLimit:1 }} has</div>
not logged in yet
</div>
</div>
</div>
</div>
<!-- Layout: Table -->
<div ng-if="vm.activeLayout.path === '2'">
<table class="table">
<thead>
<tr>
<th style="padding-left: 20px; width: 10px;">
<a href="" style="text-decoration: none;" ng-click="vm.selectAll()"><umb-checkmark checked="vm.areAllSelected()" size="xs"></umb-checkmark></a>
</th>
<th style="width: 70px;"></th>
<th>Name</th>
<th>User group</th>
<th>Last Login</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="user in vm.users" ng-click="vm.selectUser(user, vm.selection)" style="cursor: pointer;">
<td style="padding-left: 20px;">
<umb-checkmark
checked="user.selected"
size="xs">
</umb-checkmark>
</td>
<td scope="row" style="padding-left: 20px;">
<umb-avatar
size="xs"
color="secondary"
name="{{user.name}}"
img-src="{{user.avatar}}">
</umb-avatar>
</td>
<td class="bold"><a href="" ng-click="vm.goToUser(user)">{{user.name}}</a></td>
<td><span ng-repeat="group in user.userGroups">{{group.name}}<span ng-if="!$last">, </span></span></td>
<td>{{ user.formattedLastLogin }}</td>
<td style="text-transform: capitalize;">
<umb-badge
size="xs"
ng-if="user.state !== 'active'"
color="{{vm.getUserStateType(user.state)}}">
{{ user.state }}
</umb-badge>
</td>
</tr>
</tbody>
</table>
</div>
<!-- Pagination -->
<div class="flex justify-center">
<umb-pagination
ng-if="vm.usersPagination.totalPages"
page-number="vm.usersPagination.pageNumber"
total-pages="vm.usersPagination.totalPages">
</umb-pagination>
</div>
</div>
<!-- Invite user -->
<div ng-if="vm.usersViewState === 'inviteUser' || vm.usersViewState === 'createUser'">
<umb-editor-sub-header>
<umb-editor-sub-header-content-left>
<a class="umb-package-details__back-link" href="" ng-click="vm.setUsersViewState('overview');">&larr; Take me back</a>
</umb-editor-sub-header-content-left>
</umb-editor-sub-header>
<div class="flex justify-center">
<ng-form style="max-width: 500px;" class="block-form">
<div>
<div ng-if="vm.usersViewState === 'inviteUser'">
<h3 class="bold" style="margin-bottom: 0;">Invite user</h3>
<p style="line-height: 1.6em; margin-bottom: 15px;">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non libero vel turpis ultrices pharetra.</p>
</div>
<div ng-if="vm.usersViewState === 'createUser'">
<h3 class="bold" style="margin-bottom: 0;">Create user</h3>
<p style="line-height: 1.6em; margin-bottom: 15px;">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non libero vel turpis ultrices pharetra.</p>
</div>
</div>
<umb-control-group label="Name">
<input type="text"
localize="placeholder"
placeholder="@placeholders_entername"
class="input-block-level"
ng-model="vm.newUser.name"
umb-auto-focus />
</umb-control-group>
<umb-control-group label="Email">
<input type="email"
localize="placeholder"
placeholder="@placeholders_entername"
class="input-block-level"
ng-model="vm.newUser.email" />
</umb-control-group>
<umb-control-group label="User group">
<umb-node-preview
style="max-width: 100%;"
ng-repeat="group in vm.newUser.userGroups"
icon="group.icon"
name="group.name"
allow-remove="true"
on-remove="vm.removeSelectedUserGroup($index, vm.newUser.userGroups)">
</umb-node-preview>
<a href=""
style="max-width: 100%;"
class="umb-node-preview-add"
ng-click="vm.openUserGroupPicker($event)"
prevent-default>
<localize key="general_add">Add</localize>
</a>
</umb-control-group>
<umb-control-group label="Message" ng-if="vm.usersViewState === 'inviteUser'">
<textarea type="text"
class="input-block-level"
localize="placeholder"
placeholder="@placeholders_entername"
ng-model="vm.newUser.message"></textarea>
</umb-control-group>
<umb-button
ng-if="vm.usersViewState === 'inviteUser'"
button-style="success"
type="button"
action=""
label="Send invite">
</umb-button>
<umb-button
ng-if="vm.usersViewState === 'createUser'"
button-style="success"
type="button"
action=""
label="Create user">
</umb-button>
</ng-form>
</div>
</div>
<umb-overlay
ng-if="vm.userGroupPicker.show"
model="vm.userGroupPicker"
view="vm.userGroupPicker.view"
position="target">
</umb-overlay>
</div>

View File

@@ -24,9 +24,10 @@
<add application="developer" alias="xslt" title="XSLT Files" type="umbraco.loadXslt, umbraco" iconClosed="icon-folder" iconOpen="icon-folder" sortOrder="5" />
<add application="developer" alias="partialViewMacros" type="Umbraco.Web.Trees.PartialViewMacrosTreeController, umbraco" silent="false" initialize="true" sortOrder="6" title="Partial View Macro Files" iconClosed="icon-folder" iconOpen="icon-folder" />
<!--Users-->
<add application="users" alias="users" title="Users" type="umbraco.loadUsers, umbraco" iconClosed="icon-folder" iconOpen="icon-folder" sortOrder="0" />
<add application="users" alias="userGroups" title="User Groups" type="umbraco.cms.presentation.Trees.UserGroups, umbraco" iconClosed="icon-folder" iconOpen="icon-folder" sortOrder="1" />
<add application="users" alias="UserGroupPermissions" title="User Group Permissions" type="umbraco.cms.presentation.Trees.UserGroupPermissions, umbraco" iconClosed="icon-folder" iconOpen="icon-folder" sortOrder="2" />
<add initialize="true" sortOrder="3" alias="users" application="users" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Web.Trees.UserTreeController, umbraco" />
<add initialize="true" sortOrder="0" alias="users_old" application="users" title="Users (Legacy)" iconClosed=".sprTreeFolder" iconOpen=".sprTreeFolder_o" type="umbraco.loadUsers, umbraco" />
<!--Members-->
<add initialize="true" sortOrder="0" alias="member" application="member" title="Members" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Web.Trees.MemberTreeController, umbraco" />
<add initialize="true" sortOrder="1" alias="memberTypes" application="member" title="Member Types" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Web.Trees.MemberTypeTreeController, umbraco" />
@@ -40,6 +41,6 @@
<add initialize="true" sortOrder="2" alias="datasource" application="forms" title="Datasources" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Forms.Web.Trees.DataSourceTreeController, Umbraco.Forms.Web" />
<add initialize="true" sortOrder="0" alias="form" application="forms" title="Forms" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Forms.Web.Trees.FormTreeController, Umbraco.Forms.Web" />
<add initialize="true" sortOrder="3" alias="prevaluesource" application="forms" title="Prevalue sources" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Forms.Web.Trees.PreValueSourceTreeController, Umbraco.Forms.Web" />
<add initialize="true" sortOrder="3" alias="formsecurity" application="users" title="Forms Security" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Forms.Web.Trees.FormSecurityTreeController, Umbraco.Forms.Web" />
<add initialize="true" sortOrder="3" alias="formsecurity" application="users" title="Forms Security" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Forms.Web.Trees.FormSecurityTreeController, Umbraco.Forms.Web" />
<add initialize="true" sortOrder="2" alias="userPermissions" application="users" title="User Permissions" iconClosed=".sprTreeFolder" iconOpen=".sprTreeFolder_o" type="umbraco.cms.presentation.Trees.UserPermissions, umbraco" />
</trees>

View File

@@ -1371,6 +1371,7 @@ To manage your website, simply open the Umbraco back office and start adding con
<key alias="templates">Templates</key>
<key alias="xslt">XSLT Files</key>
<key alias="analytics">Analytics</key>
<key alias="users">Users</key>
</area>
<area alias="update">
<key alias="updateAvailable">New update ready</key>
@@ -1426,6 +1427,10 @@ To manage your website, simply open the Umbraco back office and start adding con
<key alias="yourHistory" version="7.0">Your recent history</key>
<key alias="sessionExpires" version="7.0">Session expires in</key>
</area>
<area alias="users">
<key alias="inviteUser">Invite user</key>
<key alias="createUser">Create user</key>
</area>
<area alias="validation">
<key alias="validation">Validation</key>
<key alias="validateAsEmail">Validate as email</key>

View File

@@ -1368,6 +1368,7 @@ To manage your website, simply open the Umbraco back office and start adding con
<key alias="templates">Templates</key>
<key alias="xslt">XSLT Files</key>
<key alias="analytics">Analytics</key>
<key alias="users">Users</key>
</area>
<area alias="update">
<key alias="updateAvailable">New update ready</key>
@@ -1423,6 +1424,10 @@ To manage your website, simply open the Umbraco back office and start adding con
<key alias="yourHistory" version="7.0">Your recent history</key>
<key alias="sessionExpires" version="7.0">Session expires in</key>
</area>
<area alias="users">
<key alias="inviteUser">Invite user</key>
<key alias="createUser">Create user</key>
</area>
<area alias="validation">
<key alias="validation">Validation</key>
<key alias="validateAsEmail">Validate as email</key>

View File

@@ -23,6 +23,7 @@ using System.Text;
namespace Umbraco.Web.Editors
{
/// <summary>
/// The API controller used for editing data types
/// </summary>

View File

@@ -1,5 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using AutoMapper;
using ClientDependency.Core;
using Umbraco.Core;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Web.Models.ContentEditing;
using Umbraco.Web.Mvc;
using Umbraco.Web.WebApi.Filters;
using Constants = Umbraco.Core.Constants;
@@ -15,7 +26,7 @@ namespace Umbraco.Web.Editors
/// </summary>
public UserController()
: this(UmbracoContext.Current)
{
{
}
/// <summary>
@@ -24,7 +35,87 @@ namespace Umbraco.Web.Editors
/// <param name="umbracoContext"></param>
public UserController(UmbracoContext umbracoContext)
: base(umbracoContext)
{
{
}
/// <summary>
/// Gets a user by Id
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public UserDisplay GetById(int id)
{
var user = Services.UserService.GetUserById(id);
if (user == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
return Mapper.Map<IUser, UserDisplay>(user);
}
//TODO: This will probably not be UserDisplay objects since there's probably too much data in the display object for a grid
public PagedResult<UserDisplay> GetPagedUsers(
int id,
int pageNumber = 1,
int pageSize = 0,
string orderBy = "SortOrder",
Direction orderDirection = Direction.Ascending,
string filter = "")
{
//TODO: Make this real, for now this is mock data
var startId = 100 + ((pageNumber -1) * pageSize);
var numUsers = pageSize;
var users = new List<UserDisplay>();
var userTypes = Services.UserService.GetAllUserTypes().ToDictionary(x => x.Alias, x => x.Name);
var cultures = Services.TextService.GetSupportedCultures().ToDictionary(x => x.Name, x => x.DisplayName);
for (int i = 0; i < numUsers; i++)
{
var display = new UserDisplay
{
Id = startId,
UserType = "writer",
AllowedSections = new[] {"content", "media"},
AvailableUserTypes = userTypes,
Email = "test" + startId + "@test.com",
Name = "User " + startId,
Culture = "en-US",
AvailableCultures = cultures,
Path = "-1," + startId,
ParentId = -1,
StartContentId = -1,
StartMediaId = -1
};
users.Add(display);
startId++;
}
return new PagedResult<UserDisplay>(100, pageNumber, pageSize)
{
Items = users
};
}
public UserDisplay PostSaveUser(UserSave userSave)
{
if (userSave == null) throw new ArgumentNullException("userSave");
if (ModelState.IsValid == false)
{
throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState));
}
var intId = userSave.Id.TryConvertTo<int>();
if (intId.Success == false)
throw new HttpResponseException(HttpStatusCode.NotFound);
var found = Services.UserService.GetUserById(intId.Result);
if (found == null)
throw new HttpResponseException(HttpStatusCode.NotFound);
//TODO: More validation, password changing logic, persisting
return Mapper.Map<IUser, UserDisplay>(found);
}
/// <summary>

View File

@@ -1,14 +1,15 @@
using System.ComponentModel.DataAnnotations;
using System;
using System.ComponentModel.DataAnnotations;
using System.Runtime.Serialization;
using Umbraco.Core.Models.Membership;
namespace Umbraco.Web.Models.ContentEditing
{
/// <summary>
/// A basic structure the represents a user
/// A bare minimum structure that represents a user, usually attached to other objects
/// </summary>
[DataContract(Name = "user", Namespace = "")]
public class UserBasic : System.IComparable
public class UserBasic : IComparable
{
[DataMember(Name = "id", IsRequired = true)]
[Required]
@@ -19,9 +20,9 @@ namespace Umbraco.Web.Models.ContentEditing
public string Name { get; set; }
int System.IComparable.CompareTo(object obj)
int IComparable.CompareTo(object obj)
{
return Name.CompareTo(((UserBasic)obj).Name);
return String.Compare(Name, ((UserBasic)obj).Name, StringComparison.Ordinal);
}
}
}

View File

@@ -3,7 +3,10 @@ using System.ComponentModel.DataAnnotations;
using System.Runtime.Serialization;
namespace Umbraco.Web.Models.ContentEditing
{
{
/// <summary>
/// Represents information for the current user
/// </summary>
[DataContract(Name = "user", Namespace = "")]
public class UserDetail : UserBasic
{

View File

@@ -0,0 +1,59 @@
using System.Collections.Generic;
using System.Globalization;
using System.Runtime.Serialization;
namespace Umbraco.Web.Models.ContentEditing
{
/// <summary>
/// Represents a user that is being edited
/// </summary>
[DataContract(Name = "user", Namespace = "")]
public class UserDisplay : EntityBasic, INotificationModel
{
public UserDisplay()
{
Notifications = new List<Notification>();
}
[DataMember(Name = "culture", IsRequired = true)]
public string Culture { get; set; }
[DataMember(Name = "email", IsRequired = true)]
public string Email { get; set; }
[DataMember(Name = "userType")]
public string UserType { get; set; }
/// <summary>
/// Gets the available user types (i.e. to populate a drop down)
/// The key is the Alias the value is the Name - the Alias is what is used in the UserType property and for persistence
/// </summary>
[DataMember(Name = "availableUserTypes")]
public IDictionary<string, string> AvailableUserTypes { get; set; }
/// <summary>
/// Gets the available cultures (i.e. to populate a drop down)
/// The key is the culture stored in the database, the value is the Name
/// </summary>
[DataMember(Name = "availableCultures")]
public IDictionary<string, string> AvailableCultures { get; set; }
[DataMember(Name = "startContentId")]
public int StartContentId { get; set; }
[DataMember(Name = "startMediaId")]
public int StartMediaId { get; set; }
/// <summary>
/// A list of sections the user is allowed to view.
/// </summary>
[DataMember(Name = "allowedSections")]
public IEnumerable<string> AllowedSections { get; set; }
/// <summary>
/// This is used to add custom localized messages/strings to the response for the app to use for localized UI purposes.
/// </summary>
[DataMember(Name = "notifications")]
public List<Notification> Notifications { get; private set; }
}
}

View File

@@ -0,0 +1,51 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Runtime.Serialization;
namespace Umbraco.Web.Models.ContentEditing
{
/// <summary>
/// Represents the data used to persist a user
/// </summary>
/// <remarks>
/// This will be different from the model used to display a user and we don't want to "Overpost" data back to the server,
/// and there will most likely be different bits of data required for updating passwords which will be different from the
/// data used to display vs save
/// </remarks>
[DataContract(Name = "user", Namespace = "")]
public class UserSave : EntityBasic, IValidatableObject
{
//TODO: There will be more information to save along with the structure for changing passwords
[DataMember(Name = "locale", IsRequired = true)]
[Required]
public string Culture { get; set; }
[DataMember(Name = "email", IsRequired = true)]
[Required]
[EmailAddress]
public string Email { get; set; }
[DataMember(Name = "userType")]
[Required]
public string UserType { get; set; }
[DataMember(Name = "startContentId")]
public int StartContentId { get; set; }
[DataMember(Name = "startMediaId")]
public int StartMediaId { get; set; }
[DataMember(Name = "allowedSections")]
public IEnumerable<string> AllowedSections { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
//TODO: Add other server side validation
//if (CultureInfo.GetCultureInfo(Culture))
// yield return new ValidationResult("The culture is invalid", new[] { "Culture" });
yield break;
}
}
}

View File

@@ -16,6 +16,16 @@ namespace Umbraco.Web.Models.Mapping
{
public override void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext)
{
config.CreateMap<IUser, UserDisplay>()
.ForMember(detail => detail.Id, opt => opt.MapFrom(user => user.Id))
.ForMember(detail => detail.UserType, opt => opt.MapFrom(user => user.UserType.Alias))
.ForMember(detail => detail.StartContentId, opt => opt.MapFrom(user => user.StartContentId))
.ForMember(detail => detail.StartMediaId, opt => opt.MapFrom(user => user.StartMediaId))
.ForMember(detail => detail.Culture, opt => opt.MapFrom(user => user.GetUserCulture(applicationContext.Services.TextService)))
.ForMember(
detail => detail.AvailableUserTypes,
opt => opt.MapFrom(user => applicationContext.Services.SectionService.GetSections().ToDictionary(x => x.Alias, x => x.Name)));
config.CreateMap<IUser, UserDetail>()
.ForMember(detail => detail.UserId, opt => opt.MapFrom(user => GetIntId(user.Id)))
.ForMember(detail => detail.StartContentId, opt => opt.MapFrom(user => user.StartContentId))

View File

@@ -0,0 +1,57 @@
using System.Net.Http.Formatting;
using Umbraco.Web.Models.Trees;
using Umbraco.Web.Mvc;
using Umbraco.Web.WebApi.Filters;
using Constants = Umbraco.Core.Constants;
namespace Umbraco.Web.Trees
{
[UmbracoTreeAuthorize(Constants.Trees.Users)]
[Tree(Constants.Applications.Users, Constants.Trees.Users, null, sortOrder: 3)]
[PluginController("UmbracoTrees")]
[CoreTree]
public class UserTreeController : TreeController
{
public UserTreeController()
{
}
public UserTreeController(UmbracoContext umbracoContext) : base(umbracoContext)
{
}
public UserTreeController(UmbracoContext umbracoContext, UmbracoHelper umbracoHelper) : base(umbracoContext, umbracoHelper)
{
}
/// <summary>
/// Helper method to create a root model for a tree
/// </summary>
/// <returns></returns>
protected override TreeNode CreateRootNode(FormDataCollection queryStrings)
{
var root = base.CreateRootNode(queryStrings);
//this will load in a custom UI instead of the dashboard for the root node
root.RoutePath = string.Format("{0}/{1}/{2}", Constants.Applications.Users, Constants.Trees.Users, "overview");
root.Icon = "icon-users";
root.HasChildren = false;
return root;
}
protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings)
{
var baseUrl = Constants.Applications.Users + "/users/";
var nodes = new TreeNodeCollection();
return nodes;
}
protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings)
{
var menu = new MenuItemCollection();
return menu;
}
}
}

View File

@@ -367,6 +367,8 @@
<Compile Include="Models\ContentEditing\SimpleNotificationModel.cs" />
<Compile Include="Models\ContentEditing\SnippetDisplay.cs" />
<Compile Include="Models\ContentEditing\TemplateDisplay.cs" />
<Compile Include="Models\ContentEditing\UserDisplay.cs" />
<Compile Include="Models\ContentEditing\UserSave.cs" />
<Compile Include="Models\LocalPackageInstallModel.cs" />
<Compile Include="Models\Mapping\CodeFileDisplayMapper.cs" />
<Compile Include="Models\Mapping\ContentTypeModelMapperExtensions.cs" />
@@ -434,6 +436,7 @@
<Compile Include="Models\Mapping\PropertyTypeGroupResolver.cs" />
<Compile Include="Security\Identity\PreviewAuthenticationMiddleware.cs" />
<Compile Include="SingletonHttpContextAccessor.cs" />
<Compile Include="Trees\UserTreeController.cs" />
<Compile Include="Trees\ContentTypeTreeController.cs" />
<Compile Include="Trees\PackagesTreeController.cs" />
<Compile Include="Trees\MediaTypeTreeController.cs" />

View File

@@ -34,7 +34,8 @@ namespace umbraco
/// <summary>
/// Handles loading of all umbraco users into the users application tree
/// </summary>
[Tree(Constants.Applications.Users, "users", "Users")]
[Tree(Constants.Applications.Users, "users_old", "Users (Legacy)")]
//TODO: Remove this tree when ready
public class loadUsers : BaseTree
{
public loadUsers(string application) : base(application) { }