Merge branch 'dev-v7.7' into temp-U4-10275

This commit is contained in:
Shannon
2017-09-08 13:53:02 +10:00
93 changed files with 2653 additions and 1185 deletions

View File

@@ -39,7 +39,7 @@
"globals": {
"angular": false,
"_": false,
"$", false,
"$": false,
"tinymce": false,
"tinyMCE": false,
"FileReader": false,
@@ -47,7 +47,7 @@
"window": false,
"LazyLoad": false,
"ActiveXObject": false,
"Bloodhound", false
"Bloodhound": false
}
}

View File

@@ -1,97 +0,0 @@
#Belle
Umbraco 7 UI, codename "Belle" Built on AngularJS, bower, Lazyload.js and Twitter Bootstrap
##Introduction
Slides from the initial demonstration of Belle done at the Umbraco DK Fest can be found here:
http://rawgithub.com/umbraco/Umbraco.Web.Ui.Client/build/master/Presentation/index.html
##Running the site with mocked data
This won't require any database or setup, as everything is running through node. All you have to do is install
node and grunt on either windows or OSX and the entire setup is ready for you.
###Install node.js
We need node to run tests and automated less compiling and other automated tasks. go to http://nodejs.org. Node.js is a powerfull javascript engine, which allows us to run all our tests and tasks written in javascript locally.
*note:* On windows you might need to restart explorer.exe to register node.
###Install dependencies
Next we need to install all the required packages. This is done with the package tool, included with node.js, open /src/Umbraco.Web.UI.Client in cmd.exe or osx terminal and run the command:
npm install
this will fetch all needed packages to your local machine.
###Install grunt globally
Grunt is a task runner for node.js, and we use it for all automated tasks in the build process. For convenience we need to install it globally on your machine, so it can be used directly in cmd.exe or the terminal.
So run the command:
npm install grunt-cli -g
*note:* On windows you might need to restart explorer.exe to register the grunt cmd.
*note:* On OSX you might need to run:
sudo npm install grunt-cli -g
Now that you have node and grunt installed, you can open `/src/Umbraco.Web.UI.Client` in either `cmd.exe` or terminal and run:
grunt dev
This will build the site, merge less files, run tests and create the /Build folder, and finally open the site in your
browser.
##Limitations
The current prototype simply uses in-memory storage, so no database dependencies. It is aimed at showing UI, not a complete functional client-server setup.
##Project Structure
All project files are located in /src/Umbraco.Web.UI.Client which only contains client-side files, everything
related to asp.net are in /src/Umbraco.Web.UI
after building Belle files are located in /build/belle, with all files following AngularJs
conventions:
###Folders
- */Umbraco.Web.Ui.Client/build/lib:* Dependencies
- */Umbraco.Web.Ui.Client/build/js:* Application javascript files
- */Umbraco.Web.Ui.Client/build/views/common/:* Main application views
- */Umbraco.Web.Ui.Client/build/views/[sectioname]/pagename Editors html
- */Umbraco.Web.Ui.Client/build/views/propertyeditors:* Property Editors html
###Files
- */Umbraco.Web.Ui.Client/build/js/app.js:* Main umbraco application / modules
- */Umbraco.Web.Ui.Client/build/js/loader.js:* lazyload configuration for dependencies
- */Umbraco.Web.Ui.Client/build/js/routes.js:* Application routes
- */Umbraco.Web.Ui.Client/build/js/umbraco.controllers.js:* Application controllers
- */Umbraco.Web.Ui.Client/build/js/umbraco.services.js:* Application services
- */Umbraco.Web.Ui.Client/build/js/umbraco.filters.js:* Application filters
- */Umbraco.Web.Ui.Client/build/js/umbraco.directives.js:* Application directives
- */Umbraco.Web.Ui.Client/build/js/umbraco.resources.js:* Application resources, like content, media, users, members etc
- */Umbraco.Web.Ui.Client/build/js/umbraco.mocks.js:* Fake Application resources, for running the app without a server
##Getting started
The current app is built, following conventions from angularJs and bootstrap. To get started with the applicaton you will need to atleast know the basics of these frameworks
###AngularJS
- Excellent introduction videos on http://www.egghead.io/
- Official guide at: http://docs.angularjs.org/guide/
###Require.js
- Introduction: http://javascriptplayground.com/blog/2012/07/requirejs-amd-tutorial-introduction
- Require.js website: http://requirejs.org/

View File

@@ -30,6 +30,59 @@
"angular-local-storage": "~0.2.3",
"moment": "~2.10.3",
"ace-builds": "^1.2.3",
"font-awesome": "~4.2",
"clipboard": "1.7.1"
},
"install": {
"path": "lib-bower",
"ignore": [
"font-awesome",
"angular",
"bootstrap",
"codemirror"
],
"sources": {
"moment": "bower_components/moment/min/moment-with-locales.js",
"underscore": [
"bower_components/underscore/underscore-min.js",
"bower_components/underscore/underscore-min.map"
],
"jquery": [
"bower_components/jquery/dist/jquery.min.js",
"bower_components/jquery/dist/jquery.min.map"
],
"angular-dynamic-locale": [
"bower_components/angular-dynamic-locale/tmhDynamicLocale.min.js",
"bower_components/angular-dynamic-locale/tmhDynamicLocale.min.js.map"
],
"angular-local-storage": [
"bower_components/angular-local-storage/dist/angular-local-storage.min.js",
"bower_components/angular-local-storage/dist/angular-local-storage.min.js.map"
],
"tinymce": [
"bower_components/tinymce/tinymce.min.js"
],
"typeahead.js": "bower_components/typeahead.js/dist/typeahead.bundle.min.js",
"rgrove-lazyload":"bower_components/rgrove-lazyload/lazyload.js",
"ng-file-upload":"bower_components/ng-file-upload/ng-file-upload.min.js",
"jquery-ui":"bower_components/jquery-ui/jquery-ui.min.js",
"jquery-migrate":"bower_components/jquery-migrate/jquery-migrate.min.js",
"clipboard": "bower_components/clipboard/dist/clipboard.min.js"
}
}
}

View File

@@ -1,601 +0,0 @@
module.exports = function (grunt) {
// Default task.
grunt.registerTask('default', ['jshint:dev', 'build', 'karma:unit']);
grunt.registerTask('dev', ['jshint:dev', 'build-dev', 'webserver', 'open:dev', 'watch']);
grunt.registerTask('docserve', ['docs:api', 'connect:docserver', 'open:docs', 'watch:docs']);
grunt.registerTask('vs', ['jshint:dev', 'build-dev', 'watch']);
//TODO: Too much watching, this brings windows to it's knees when in dev mode
//run by the watch task
grunt.registerTask('watch-js', ['jshint:dev', 'concat', 'copy:app', 'copy:mocks', 'copy:canvasdesigner', 'copy:vs', 'karma:unit']);
grunt.registerTask('watch-less', ['recess:build', 'recess:installer', 'recess:nonodes', 'recess:canvasdesigner', 'postcss', 'copy:canvasdesigner', 'copy:assets', 'copy:vs']);
grunt.registerTask('watch-html', ['copy:views', 'copy:vs']);
grunt.registerTask('watch-installer', ['concat:install', 'concat:installJs', 'copy:installer', 'copy:vs']);
grunt.registerTask('watch-canvasdesigner', ['copy:canvasdesigner', 'concat:canvasdesignerJs', 'copy:vs']);
grunt.registerTask('watch-test', ['jshint:dev', 'karma:unit']);
//triggered from grunt
grunt.registerTask('build', ['concat', 'recess:build', 'recess:installer', 'recess:nonodes', 'recess:canvasdesigner', 'postcss', 'bower-install-simple', 'bower', 'copy', 'clean:post']);
//triggered from grunt dev vs or grunt vs
grunt.registerTask('build-dev', ['clean:pre', 'concat', 'recess:build', 'recess:installer', 'recess:nonodes', 'postcss', 'bower-install-simple', 'bower', 'copy']);
//utillity tasks
grunt.registerTask('docs', ['ngdocs']);
grunt.registerTask('webserver', ['connect:devserver']);
// Print a timestamp (useful for when watching)
grunt.registerTask('timestamp', function () {
grunt.log.subhead(Date());
});
// Project configuration.
grunt.initConfig({
buildVersion: grunt.option('buildversion') || '7',
connect: {
devserver: {
options: {
port: 9990,
hostname: '0.0.0.0',
base: './build',
middleware: function(connect, options) {
return [
//uncomment to enable CSP
// util.csp(),
//util.rewrite(),
connect.favicon('images/favicon.ico'),
connect.static(options.base),
connect.directory(options.base)
];
}
}
},
testserver: {},
docserver: {
options: {
port: 8880,
hostname: '0.0.0.0',
base: './docs/api',
middleware: function(connect, options) {
return [
//uncomment to enable CSP
// util.csp(),
//util.rewrite(),
connect.static(options.base),
connect.directory(options.base)
];
}
}
},
},
open: {
dev: {
path: 'http://localhost:9990/belle/'
},
docs: {
path: 'http://localhost:8880/index.html'
}
},
distdir: 'build/belle',
vsdir: '../Umbraco.Web.UI/umbraco',
pkg: grunt.file.readJSON('package.json'),
banner:
'/*! <%= pkg.title || pkg.name %>\n' +
'<%= pkg.homepage ? " * " + pkg.homepage + "\\n" : "" %>' +
' * Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author %>;\n' +
' * Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %>\n */\n',
src: {
js: ['src/**/*.js', 'src/*.js'],
common: ['src/common/**/*.js'],
controllers: ['src/**/*.controller.js'],
specs: ['test/**/*.spec.js'],
scenarios: ['test/**/*.scenario.js'],
samples: ['sample files/*.js'],
html: ['src/index.html', 'src/install.html'],
everything: ['src/**/*.*', 'test/**/*.*', 'docs/**/*.*'],
tpl: {
app: ['src/views/**/*.html'],
common: ['src/common/**/*.tpl.html']
},
less: ['src/less/belle.less'], // recess:build doesn't accept ** in its file patterns
prod: ['<%= distdir %>/js/*.js']
},
clean: {
pre: ['<%= distdir %>/*'],
post: ['<%= distdir %>/js/*.dev.js']
},
copy: {
assets: {
files: [{ dest: '<%= distdir %>/assets', src: '**', expand: true, cwd: 'src/assets/' }]
},
config: {
files: [{ dest: '<%= distdir %>/../config', src: '**', expand: true, cwd: 'src/config/' }]
},
installer: {
files: [{ dest: '<%= distdir %>/views/install', src: '**/*.html', expand: true, cwd: 'src/installer/steps' }]
},
canvasdesigner: {
files: [
{ dest: '<%= distdir %>/preview', src: '**/*.html', expand: true, cwd: 'src/canvasdesigner' },
{ dest: '<%= distdir %>/preview/editors', src: '**/*.html', expand: true, cwd: 'src/canvasdesigner/editors' },
{ dest: '<%= distdir %>/assets/less', src: '**/*.less', expand: true, cwd: 'src/canvasdesigner/editors' },
{ dest: '<%= distdir %>/js', src: 'canvasdesigner.config.js', expand: true, cwd: 'src/canvasdesigner/config' },
{ dest: '<%= distdir %>/js', src: 'canvasdesigner.palettes.js', expand: true, cwd: 'src/canvasdesigner/config' },
{ dest: '<%= distdir %>/js', src: 'canvasdesigner.front.js', expand: true, cwd: 'src/canvasdesigner' }
]
},
vendor: {
files: [{ dest: '<%= distdir %>/lib', src: '**', expand: true, cwd: 'lib/' }]
},
views: {
files: [{ dest: '<%= distdir %>/views', src: ['**/*.*', '!**/*.controller.js'], expand: true, cwd: 'src/views' }]
},
app: {
files: [
{ dest: '<%= distdir %>/js', src: '*.js', expand: true, cwd: 'src/' }
]
},
mocks: {
files: [{ dest: '<%= distdir %>/js', src: '*.js', expand: true, cwd: 'src/common/mocks/' }]
},
vs: {
files: [
//everything except the index.html root file!
//then we need to figure out how to not copy all the test stuff either!?
{ dest: '<%= vsdir %>/assets', src: '**', expand: true, cwd: '<%= distdir %>/assets' },
{ dest: '<%= vsdir %>/js', src: '**', expand: true, cwd: '<%= distdir %>/js' },
{ dest: '<%= vsdir %>/views', src: '**', expand: true, cwd: '<%= distdir %>/views' },
{ dest: '<%= vsdir %>/preview', src: '**', expand: true, cwd: '<%= distdir %>/preview' },
{ dest: '<%= vsdir %>/lib', src: '**', expand: true, cwd: '<%= distdir %>/lib' }
]
}
},
karma: {
unit: { configFile: 'test/config/karma.conf.js', keepalive: true },
e2e: { configFile: 'test/config/e2e.js', keepalive: true },
watch: { configFile: 'test/config/unit.js', singleRun: false, autoWatch: true, keepalive: true }
},
concat: {
index: {
src: ['src/index.html'],
dest: '<%= distdir %>/index.html',
options: {
process: true
}
},
install: {
src: ['src/installer/installer.html'],
dest: '<%= distdir %>/installer.html',
options: {
process: true
}
},
installJs: {
src: ['src/installer/**/*.js'],
dest: '<%= distdir %>/js/umbraco.installer.js',
options: {
banner: "<%= banner %>\n(function() { \n\n",
footer: "\n\n})();"
}
},
canvasdesignerJs: {
src: ['src/canvasdesigner/canvasdesigner.global.js', 'src/canvasdesigner/canvasdesigner.controller.js', 'src/canvasdesigner/editors/*.js', 'src/canvasdesigner/lib/*.js'],
dest: '<%= distdir %>/js/canvasdesigner.panel.js'
},
controllers: {
src: ['src/controllers/**/*.controller.js', 'src/views/**/*.controller.js'],
dest: '<%= distdir %>/js/umbraco.controllers.js',
options: {
banner: "<%= banner %>\n(function() { \n\n",
footer: "\n\n})();"
}
},
services: {
src: ['src/common/services/*.js'],
dest: '<%= distdir %>/js/umbraco.services.js',
options: {
banner: "<%= banner %>\n(function() { \n\n",
footer: "\n\n})();"
}
},
security: {
src: ['src/common/security/*.js'],
dest: '<%= distdir %>/js/umbraco.security.js',
options: {
banner: "<%= banner %>\n(function() { \n\n",
footer: "\n\n})();"
}
},
resources: {
src: ['src/common/resources/*.js'],
dest: '<%= distdir %>/js/umbraco.resources.js',
options: {
banner: "<%= banner %>\n(function() { \n\n",
footer: "\n\n})();"
}
},
testing: {
src: ['src/common/mocks/*/*.js'],
dest: '<%= distdir %>/js/umbraco.testing.js',
options: {
banner: "<%= banner %>\n(function() { \n\n",
footer: "\n\n})();"
}
},
directives: {
src: ['src/common/directives/**/*.js'],
dest: '<%= distdir %>/js/umbraco.directives.js',
options: {
banner: "<%= banner %>\n(function() { \n\n",
footer: "\n\n})();"
}
},
filters: {
src: ['src/common/filters/*.js'],
dest: '<%= distdir %>/js/umbraco.filters.js',
options: {
banner: "<%= banner %>\n(function() { \n\n",
footer: "\n\n})();"
}
}
},
uglify: {
options: {
mangle: true
},
combine: {
files: {
'<%= distdir %>/js/umbraco.min.js': ['<%= distdir %>/js/umbraco.*.js']
}
}
},
recess: {
build: {
files: {
'<%= distdir %>/assets/css/<%= pkg.name %>.css':
['<%= src.less %>']
},
options: {
compile: true,
compress: true
}
},
nonodes: {
files: {
'<%= distdir %>/assets/css/nonodes.style.min.css':
['src/less/pages/nonodes.less']
},
options: {
compile: true,
compress: true
}
},
installer: {
files: {
'<%= distdir %>/assets/css/installer.css':
['src/less/installer.less']
},
options: {
compile: true,
compress: true
}
},
canvasdesigner: {
files: {
'<%= distdir %>/assets/css/canvasdesigner.css':
['src/less/canvas-designer.less', 'src/less/helveticons.less']
},
options: {
compile: true,
compress: true
}
}
},
postcss: {
options: {
processors: [
// add vendor prefixes
require('autoprefixer-core')({
browsers: 'last 2 versions'
})
]
},
dist: {
src: '<%= distdir %>/assets/css/<%= pkg.name %>.css'
}
},
ngTemplateCache: {
views: {
files: {
'<%= distdir %>/js/umbraco.views.js': 'src/views/**/*.html'
},
options: {
trim: 'src/',
module: 'umbraco.views'
}
}
},
watch: {
docs: {
files: ['docs/src/**/*.md'],
tasks: ['watch-docs', 'timestamp']
},
css: {
files: 'src/**/*.less',
tasks: ['watch-less', 'timestamp'],
options: {
livereload: true,
},
},
js: {
files: ['src/**/*.js', 'src/*.js'],
tasks: ['watch-js', 'timestamp'],
},
test: {
files: ['test/**/*.js'],
tasks: ['watch-test', 'timestamp'],
},
installer: {
files: ['src/installer/**/*.*'],
tasks: ['watch-installer', 'timestamp'],
},
canvasdesigner: {
files: ['src/canvasdesigner/**/*.*'],
tasks: ['watch-canvasdesigner', 'timestamp'],
},
html: {
files: ['src/views/**/*.html', 'src/*.html'],
tasks: ['watch-html', 'timestamp']
},
options: {
interval: 500
}
},
ngdocs: {
options: {
dest: 'docs/api',
startPage: '/api',
title: "Umbraco Backoffice UI API Documentation",
html5Mode: false,
styles: [
'docs/umb-docs.css'
],
image: "https://our.umbraco.org/assets/images/logo.svg"
},
api: {
src: ['src/common/**/*.js', 'docs/src/api/**/*.ngdoc'],
title: 'API Documentation'
},
tutorials: {
src: [],
title: ''
}
},
eslint:{
src: ['<%= src.common %>','<%= src.controllers %>'],
options: {quiet: true}
},
jshint: {
dev: {
files: {
src: ['<%= src.common %>']
},
options: {
curly: true,
eqeqeq: true,
immed: true,
latedef: "nofunc",
newcap: true,
noarg: true,
sub: true,
boss: true,
//NOTE: This is required so it doesn't barf on reserved words like delete when doing $http.delete
es5: true,
eqnull: true,
//NOTE: we need to use eval sometimes so ignore it
evil: true,
//NOTE: we need to check for strings such as "javascript:" so don't throw errors regarding those
scripturl: true,
//NOTE: we ignore tabs vs spaces because enforcing that causes lots of errors depending on the text editor being used
smarttabs: true,
globals: {}
}
},
build: {
files: {
src: ['<%= src.prod %>']
},
options: {
curly: true,
eqeqeq: true,
immed: true,
latedef: "nofunc",
newcap: true,
noarg: true,
sub: true,
boss: true,
//NOTE: This is required so it doesn't barf on reserved words like delete when doing $http.delete
es5: true,
eqnull: true,
//NOTE: we need to use eval sometimes so ignore it
evil: true,
//NOTE: we need to check for strings such as "javascript:" so don't throw errors regarding those
scripturl: true,
//NOTE: we ignore tabs vs spaces because enforcing that causes lots of errors depending on the text editor being used
smarttabs: true,
globalstrict: true,
globals: { $: false, jQuery: false, define: false, require: false, window: false }
}
}
},
bower: {
dev: {
dest: '<%= distdir %>/lib',
options: {
expand: true,
ignorePackages: ['bootstrap'],
packageSpecific: {
'moment': {
keepExpandedHierarchy: false,
files: ['min/moment-with-locales.js']
},
'typeahead.js': {
keepExpandedHierarchy: false,
files: ['dist/typeahead.bundle.min.js']
},
'underscore': {
files: ['underscore-min.js', 'underscore-min.map']
},
'rgrove-lazyload': {
files: ['lazyload.js']
},
'bootstrap-social': {
files: ['bootstrap-social.css']
},
'font-awesome': {
files: ['css/font-awesome.min.css', 'fonts/*']
},
"jquery": {
keepExpandedHierarchy: false,
files: ['dist/jquery.min.js', 'dist/jquery.min.map']
},
'jquery-ui': {
keepExpandedHierarchy: false,
files: ['jquery-ui.min.js']
},
'jquery-migrate': {
keepExpandedHierarchy: false,
files: ['jquery-migrate.min.js']
},
'tinymce': {
files: ['plugins/**', 'themes/**', 'tinymce.min.js']
},
'angular-dynamic-locale': {
files: ['tmhDynamicLocale.min.js', 'tmhDynamicLocale.min.js.map']
},
'ng-file-upload': {
keepExpandedHierarchy: false,
files: ['ng-file-upload.min.js']
},
'angular-local-storage': {
keepExpandedHierarchy: false,
files: ['dist/angular-local-storage.min.js']
},
'codemirror': {
files: [
'lib/codemirror.js',
'lib/codemirror.css',
'mode/css/*',
'mode/javascript/*',
'mode/xml/*',
'mode/htmlmixed/*',
'addon/search/*',
'addon/edit/*',
'addon/selection/*',
'addon/dialog/*'
]
},
'ace-builds': {
files: [
'src-min-noconflict/ace.js',
'src-min-noconflict/ext-language_tools.js',
'src-min-noconflict/ext-searchbox.js',
'src-min-noconflict/ext-settings_menu.js',
'src-min-noconflict/snippets/text.js',
'src-min-noconflict/snippets/javascript.js',
'src-min-noconflict/theme-chrome.js',
'src-min-noconflict/mode-razor.js',
'src-min-noconflict/mode-javascript.js',
'src-min-noconflict/worker-javascript.js',
]
},
'clipboard': {
keepExpandedHierarchy: false,
files: ['dist/clipboard.min.js']
}
}
}
},
options: {
expand: true
}
},
"bower-install-simple": {
options: {
color: true
},
"dev": {}
}
});
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-recess');
grunt.loadNpmTasks('grunt-postcss');
grunt.loadNpmTasks('grunt-karma');
grunt.loadNpmTasks('grunt-open');
grunt.loadNpmTasks('grunt-contrib-connect');
grunt.loadNpmTasks("grunt-bower-install-simple");
grunt.loadNpmTasks('grunt-bower');
grunt.loadNpmTasks('grunt-ngdocs');
grunt.loadNpmTasks('grunt-eslint');
grunt.loadNpmTasks('grunt-hustler');
};

View File

@@ -0,0 +1,389 @@
var gulp = require('gulp');
var watch = require('gulp-watch');
var concat = require('gulp-concat');
var rename = require('gulp-rename');
var wrap = require("gulp-wrap-js");
var sort = require('gulp-sort');
var connect = require('gulp-connect');
var open = require('gulp-open');
var runSequence = require('run-sequence');
var _ = require('lodash');
var MergeStream = require('merge-stream');
//Less + css
var postcss = require('gulp-postcss');
var less = require('gulp-less');
var autoprefixer = require('autoprefixer');
var cssnano = require('cssnano');
// Documentation
var gulpDocs = require('gulp-ngdocs');
// Testing
var karmaServer = require('karma').Server;
/***************************************************************
Helper functions
***************************************************************/
function processJs(files, out) {
return gulp.src(files)
.pipe(sort())
.pipe(concat(out))
.pipe(wrap('(function(){\n%= body %\n})();'))
.pipe(gulp.dest(root + targets.js));
console.log(out + " compiled");
}
function processLess(files, out) {
var processors = [
autoprefixer,
cssnano
];
return gulp.src(files)
.pipe(less())
.pipe(postcss(processors))
.pipe(rename(out))
.pipe(gulp.dest(root + targets.css));
console.log(out + " compiled");
}
/***************************************************************
Paths and destinations
Each group is iterated automatically in the setup tasks below
***************************************************************/
var sources = {
//less files used by backoffice and preview
//processed in the less task
less: {
installer: { files: ["src/less/installer.less"], out: "installer.css" },
nonodes: { files: ["src/less/pages/nonodes.less"], out: "nonodes.style.min.css"},
preview: { files: ['src/less/canvas-designer.less', 'src/less/helveticons.less'], out: "canvasdesigner.css" },
umbraco: { files: ["src/less/belle.less"], out: "umbraco.css" }
},
//js files for backoffie
//processed in the js task
js: {
preview: { files: ["src/canvasdesigner/**/*.js"], out: "umbraco.canvasdesigner.js" },
installer: { files: ["src/installer/**/*.js"], out: "umbraco.installer.js" },
controllers: { files: ["src/{views,controllers}/**/*.controller.js"], out: "umbraco.controllers.js" },
directives: { files: ["src/common/directives/**/*.js"], out: "umbraco.directives.js" },
filters: { files: ["src/common/filters/**/*.js"], out: "umbraco.filters.js" },
resources: { files: ["src/common/resources/**/*.js"], out: "umbraco.resources.js" },
services: { files: ["src/common/services/**/*.js"], out: "umbraco.services.js" },
security: { files: ["src/common/security/**/*.js"], out: "umbraco.security.js" }
},
//selectors for copying all views into the build
//processed in the views task
views:{
umbraco: {files: ["src/views/**/*html"], folder: ""},
preview: { files: ["src/canvasdesigner/**/*.html"], folder: "../preview"},
installer: {files: ["src/installer/steps/*.html"], folder: "install"}
},
//globs for file-watching
globs:{
views: "./src/views/**/*.html",
less: "./src/less/**/*.less",
js: "./src/*.js",
lib: "./lib/**/*",
bower: "./lib-bower/**/*",
assets: "./src/assets/**"
}
};
var root = "../Umbraco.Web.UI/Umbraco/";
var targets = {
js: "js/",
lib: "lib/",
views: "views/",
css: "assets/css/",
assets: "assets/"
};
/**************************
* Main tasks for the project to prepare backoffice files
**************************/
// Build - build the files ready for production
gulp.task('build', function(cb) {
runSequence(["dependencies", "js", "less", "views"], "test:unit", cb);
});
// Dev - build the files ready for development and start watchers
gulp.task('dev', function(cb) {
runSequence(["dependencies", "js", "less", "views"], "watch", cb);
});
// Docserve - build and open the back office documentation
gulp.task('docserve', function(cb) {
runSequence('docs', 'connect:docs', 'open:docs', cb);
});
/**************************
* Task processes and copies all dependencies, either installed by bower, npm or stored locally in the project
**************************/
gulp.task('dependencies', function () {
//bower component specific copy rules
//this is to patch the sometimes wonky rules these libs are distrbuted under
//as we do multiple things in this task, we merge the multiple streams
var stream = new MergeStream();
//Tinymce
stream.add(
gulp.src(["./bower_components/tinymce/plugins/**",
"./bower_components/tinymce/themes/**"],
{ base: "./bower_components/tinymce/" })
.pipe(gulp.dest(root + targets.lib + "/tinymce"))
);
//font-awesome
stream.add(
gulp.src(["./bower_components/font-awesome/fonts/*",
"./bower_components/font-awesome/css/font-awesome.min.css"],
{ base: "./bower_components/font-awesome/" })
.pipe(gulp.dest(root + targets.lib + "/font-awesome"))
);
// ace Editor
stream.add(
gulp.src(["bower_components/ace-builds/src-min-noconflict/ace.js",
"bower_components/ace-builds/src-min-noconflict/ext-language_tools.js",
"bower_components/ace-builds/src-min-noconflict/ext-searchbox.js",
"bower_components/ace-builds/src-min-noconflict/ext-settings_menu.js",
"bower_components/ace-builds/src-min-noconflict/snippets/text.js",
"bower_components/ace-builds/src-min-noconflict/snippets/javascript.js",
"bower_components/ace-builds/src-min-noconflict/theme-chrome.js",
"bower_components/ace-builds/src-min-noconflict/mode-razor.js",
"bower_components/ace-builds/src-min-noconflict/mode-javascript.js",
"bower_components/ace-builds/src-min-noconflict/worker-javascript.js"],
{ base: "./bower_components/ace-builds/" })
.pipe(gulp.dest(root + targets.lib + "/ace-builds"))
);
// code mirror
stream.add(
gulp.src([
"bower_components/codemirror/lib/codemirror.js",
"bower_components/codemirror/lib/codemirror.css",
"bower_components/codemirror/mode/css/*",
"bower_components/codemirror/mode/javascript/*",
"bower_components/codemirror/mode/xml/*",
"bower_components/codemirror/mode/htmlmixed/*",
"bower_components/codemirror/addon/search/*",
"bower_components/codemirror/addon/edit/*",
"bower_components/codemirror/addon/selection/*",
"bower_components/codemirror/addon/dialog/*"],
{ base: "./bower_components/codemirror/" })
.pipe(gulp.dest(root + targets.lib + "/codemirror"))
);
//copy over libs which are not on bower (/lib) and
//libraries that have been managed by bower-installer (/lib-bower)
stream.add(
gulp.src(sources.globs.lib)
.pipe(gulp.dest(root + targets.lib))
);
stream.add(
gulp.src(sources.globs.bower)
.pipe(gulp.dest(root + targets.lib))
);
//Copies all static assets into /root / assets folder
//css, fonts and image files
stream.add(
gulp.src(sources.globs.assets)
.pipe(gulp.dest(root + targets.assets))
);
// Copies all the less files related to the preview into their folder
//these are not pre-processed as preview has its own less combiler client side
stream.add(
gulp.src("src/canvasdesigner/editors/*.less")
.pipe(gulp.dest(root + targets.assets + "/less"))
);
return stream;
});
/**************************
* Copies all angular JS files into their seperate umbraco.*.js file
**************************/
gulp.task('js', function () {
//we run multiple streams, so merge them all together
var stream = new MergeStream();
stream.add(
gulp.src(sources.globs.js)
.pipe(gulp.dest(root + targets.js))
);
_.forEach(sources.js, function (group) {
stream.add (processJs(group.files, group.out) );
});
return stream;
});
gulp.task('less', function () {
var stream = new MergeStream();
_.forEach(sources.less, function (group) {
stream.add( processLess(group.files, group.out) );
});
return stream;
});
gulp.task('views', function () {
var stream = new MergeStream();
_.forEach(sources.views, function (group) {
console.log("copying " + group.files + " to " + root + targets.views + group.folder)
stream.add (
gulp.src(group.files)
.pipe( gulp.dest(root + targets.views + group.folder) )
);
});
return stream;
});
gulp.task('watch', function () {
var stream = new MergeStream();
var watchInterval = 500;
//Setup a watcher for all groups of javascript files
_.forEach(sources.js, function (group) {
if(group.watch !== false){
stream.add(
watch(group.files, { ignoreInitial: true, interval: watchInterval }, function (file) {
console.info(file.path + " has changed, added to: " + group.out);
processJs(group.files, group.out);
})
);
}
});
stream.add(
//watch all less files and trigger the less task
watch(sources.globs.less, { ignoreInitial: true, interval: watchInterval }, function () {
gulp.run(['less']);
})
);
//watch all views - copy single file changes
stream.add(
watch(sources.globs.views, { interval: watchInterval })
.pipe(gulp.dest(root + targets.views))
);
//watch all app js files that will not be merged - copy single file changes
stream.add(
watch(sources.globs.js, { interval: watchInterval })
.pipe(gulp.dest(root + targets.js))
);
return stream;
});
/**************************
* Build Backoffice UI API documentation
**************************/
gulp.task('docs', [], function (cb) {
var options = {
html5Mode: false,
startPage: '/api',
title: "Umbraco Backoffice UI API Documentation",
dest: 'docs/api',
styles: ['docs/umb-docs.css'],
image: "https://our.umbraco.org/assets/images/logo.svg"
}
return gulpDocs.sections({
api: {
glob: ['src/common/**/*.js', 'docs/src/api/**/*.ngdoc'],
api: true,
title: 'API Documentation'
}
})
.pipe(gulpDocs.process(options))
.pipe(gulp.dest('docs/api'));
cb();
});
gulp.task('connect:docs', function (cb) {
connect.server({
root: 'docs/api',
livereload: true,
fallback: 'docs/api/index.html',
port: 8880
});
cb();
});
gulp.task('open:docs', function (cb) {
var options = {
uri: 'http://localhost:8880/index.html'
};
gulp.src(__filename)
.pipe(open(options));
cb();
});
/**************************
* Build tests
**************************/
// Karma test
gulp.task('test:unit', function() {
new karmaServer({
configFile: __dirname + "/test/config/karma.conf.js",
keepalive: true
})
.start();
});
gulp.task('test:e2e', function() {
new karmaServer({
configFile: __dirname + "/test/config/e2e.js",
keepalive: true
})
.start();
});

View File

@@ -193,10 +193,10 @@
// Reset container width
// Required here as we reset the width earlier on and the grid mixins don't override early enough
.navbar-static-top .container,
.navbar-static-top .container,
.navbar-fixed-top .container,
.navbar-fixed-bottom .container {
#grid > .core > .span(@gridColumns);
.navbar-fixed-bottom .container {
width: (@gridColumnWidth * @gridColumns) + (@gridGutterWidth * (@gridColumns - 1));
}
// Fixed to top

View File

@@ -6,7 +6,7 @@
"license": "MIT",
"repository": {
"type": "git",
"url": "git@github.com:umbraco/umbraco-cms.git"
"url": "https://github.com/umbraco/Umbraco-CMS.git"
},
"bugs": {
"url": "https://issues.umbraco.org"
@@ -14,38 +14,35 @@
"engines": {
"node": ">= 0.8.4"
},
"scripts": {
"install": "bower-installer",
"test": "karma start test/config/karma.conf.js --singlerun",
"build": "gulp"
},
"dependencies": {},
"devDependencies": {
"autoprefixer-core": "~5.2.1",
"bower": "^1.4.1",
"eslint": "^0.23.0",
"eslint-plugin-angular": "0.0.13",
"grunt": "~0.4.0",
"grunt-bower": "^0.19.0",
"grunt-bower-install-simple": "^1.1.3",
"grunt-contrib-clean": "~0.4.0",
"grunt-contrib-concat": "~0.1.3",
"grunt-contrib-connect": "~0.3.0",
"grunt-contrib-copy": "~0.7.0",
"grunt-contrib-jshint": "~0.2.0",
"grunt-contrib-uglify": "~0.1.1",
"grunt-contrib-watch": "~0.3.1",
"grunt-eslint": "^15.0.0",
"grunt-html2js": "~0.1.0",
"grunt-hustler": "^4.0.6",
"grunt-karma": "~0.5",
"grunt-ngdocs": "~0.1.2",
"grunt-open": "~0.2.0",
"grunt-postcss": "~0.6.0",
"grunt-recess": "~0.3",
"karma": "~0.9",
"karma-chrome-launcher": "0.0.2",
"karma-coffee-preprocessor": "0.0.1",
"karma-firefox-launcher": "0.0.2",
"karma-jasmine": "0.0.1",
"karma-phantomjs-launcher": "0.0.2",
"karma-requirejs": "0.0.1",
"karma-script-launcher": "0.0.1",
"phantomjs": "~1.9.1-0"
"autoprefixer": "^6.5.0",
"bower-installer": "^1.2.0",
"cssnano": "^3.7.6",
"gulp": "^3.9.1",
"gulp-concat": "^2.6.0",
"gulp-connect": "^5.0.0",
"gulp-less": "^3.1.0",
"gulp-ngdocs": "^0.3.0",
"gulp-open": "^2.0.0",
"gulp-postcss": "^6.2.0",
"gulp-rename": "^1.2.2",
"gulp-sort": "^2.0.0",
"gulp-watch": "^4.3.10",
"gulp-wrap": "^0.13.0",
"gulp-wrap-js": "^0.4.1",
"jasmine-core": "^2.5.2",
"karma": "^1.7.0",
"karma-jasmine": "^1.1.0",
"karma-phantomjs-launcher": "^1.0.4",
"less": "^2.6.1",
"lodash": "^4.16.3",
"merge-stream": "^1.0.1",
"run-sequence": "^2.1.0"
}
}

View File

@@ -11,8 +11,7 @@ LazyLoad.js([
'../js/umbraco.security.js',
'../ServerVariables',
'../lib/spectrum/spectrum.js',
'../js/canvasdesigner.panel.js',
'../js/umbraco.canvasdesigner.js',
], function () {
jQuery(document).ready(function () {
angular.bootstrap(document, ['Umbraco.canvasdesigner']);

View File

@@ -17,6 +17,8 @@
}
*/
$scope.showReset = false;
//set defaults if they are not available
if ($scope.config.disableToggle === undefined) {
$scope.config.disableToggle = false;
@@ -36,13 +38,13 @@
if ($scope.config.minPasswordLength === undefined) {
$scope.config.minPasswordLength = 0;
}
//set the model defaults
if (!angular.isObject($scope.passwordValues)) {
//if it's not an object then just create a new one
$scope.passwordValues = {
newPassword: null,
oldPassword: null,
oldPassword: null,
reset: null,
answer: null
};
@@ -61,11 +63,11 @@
//the value to compare to match passwords
if (!isNew) {
$scope.confirm = "";
$scope.passwordValues.confirm = "";
}
else if ($scope.passwordValues.newPassword && $scope.passwordValues.newPassword.length > 0) {
//if it is new and a new password has been set, then set the confirm password too
$scope.confirm = $scope.passwordValues.newPassword;
$scope.passwordValues.confirm = $scope.passwordValues.newPassword;
}
}
@@ -86,6 +88,7 @@
$scope.changing = true;
//if there was a previously generated password displaying, clear it
$scope.passwordValues.generatedPassword = null;
$scope.passwordValues.confirm = null;
};
$scope.cancelChange = function () {
@@ -120,25 +123,13 @@
unsubscribe[u]();
}
});
$scope.showReset = function () {
return $scope.config.enableReset;
};
$scope.showOldPass = function () {
return $scope.config.hasPassword &&
!$scope.config.allowManuallyChangingPassword &&
!$scope.config.enablePasswordRetrieval && !$scope.passwordValues.reset;
!$scope.config.enablePasswordRetrieval && !$scope.showReset;
};
$scope.showNewPass = function () {
return !$scope.passwordValues.reset;
};
$scope.showConfirmPass = function () {
return !$scope.passwordValues.reset;
};
//TODO: I don't think we need this or the cancel button, this can be up to the editor rendering this directive
$scope.showCancelBtn = function () {
return $scope.config.disableToggle !== true && $scope.config.hasPassword;

View File

@@ -5,48 +5,54 @@
*
*
**/
function currentUserResource($q, $http, umbRequestHelper) {
function currentUserResource($q, $http, umbRequestHelper, umbDataFormatter) {
//the factory object returned
return {
//the factory object returned
return {
performSetInvitedUserPassword: function (newPassword) {
performSetInvitedUserPassword: function (newPassword) {
if (!newPassword) {
return angularHelper.rejectedPromise({ errorMsg: 'newPassword cannot be empty' });
}
if (!newPassword) {
return angularHelper.rejectedPromise({ errorMsg: 'newPassword cannot be empty' });
}
return umbRequestHelper.resourcePromise(
$http.post(
umbRequestHelper.getApiUrl(
"currentUserApiBaseUrl",
"PostSetInvitedUserPassword"),
angular.toJson(newPassword)),
'Failed to change password');
},
return umbRequestHelper.resourcePromise(
$http.post(
umbRequestHelper.getApiUrl(
"currentUserApiBaseUrl",
"PostSetInvitedUserPassword"),
angular.toJson(newPassword)),
'Failed to change password');
},
/**
* @ngdoc method
* @name umbraco.resources.currentUserResource#changePassword
* @methodOf umbraco.resources.currentUserResource
*
* @description
* Changes the current users password
*
* @returns {Promise} resourcePromise object containing the user array.
*
*/
changePassword: function (changePasswordArgs) {
return umbRequestHelper.resourcePromise(
$http.post(
umbRequestHelper.getApiUrl(
"currentUserApiBaseUrl",
"PostChangePassword"),
changePasswordArgs),
'Failed to change password');
}
};
/**
* @ngdoc method
* @name umbraco.resources.currentUserResource#changePassword
* @methodOf umbraco.resources.currentUserResource
*
* @description
* Changes the current users password
*
* @returns {Promise} resourcePromise object containing the user array.
*
*/
changePassword: function (changePasswordArgs) {
changePasswordArgs = umbDataFormatter.formatChangePasswordModel(changePasswordArgs);
if (!changePasswordArgs) {
throw 'No password data to change';
}
return umbRequestHelper.resourcePromise(
$http.post(
umbRequestHelper.getApiUrl(
"currentUserApiBaseUrl",
"PostChangePassword"),
changePasswordArgs),
'Failed to change password');
}
};
}
angular.module('umbraco.resources').factory('currentUserResource', currentUserResource);

View File

@@ -87,6 +87,7 @@ function tinyMceService(dialogService, $log, imageHelper, $http, $timeout, macro
editor.addButton('umbmediapicker', {
icon: 'custom icon-picture',
tooltip: 'Media Picker',
stateSelector: 'img',
onclick: function () {
var selectedElm = editor.selection.getNode(),

View File

@@ -7,8 +7,30 @@
* @description A helper object used to format/transform JSON Umbraco data, mostly used for persisting data to the server
**/
function umbDataFormatter() {
return {
formatChangePasswordModel: function(model) {
if (!model) {
return null;
}
var trimmed = _.omit(model, ["confirm", "generatedPassword"])
//ensure that the pass value is null if all child properties are null
var allNull = true;
var vals = _.values(trimmed);
for (var k = 0; k < vals.length; k++) {
if (vals[k] !== null && vals[k] !== undefined) {
allNull = false;
}
}
if (allNull) {
return null;
}
return trimmed;
},
formatContentTypePostData: function (displayModel, action) {
//create the save model from the display model
@@ -82,6 +104,7 @@
//create the save model from the display model
var saveModel = _.pick(displayModel, 'id', 'parentId', 'name', 'username', 'culture', 'email', 'startContentIds', 'startMediaIds', 'userGroups', 'message', 'changePassword');
saveModel.changePassword = this.formatChangePasswordModel(saveModel.changePassword);
//make sure the userGroups are just a string array
var currGroups = saveModel.userGroups;
@@ -221,7 +244,8 @@
});
saveModel.email = propEmail.value;
saveModel.username = propLogin.value;
saveModel.password = propPass.value;
saveModel.password = this.formatChangePasswordModel(propPass.value);
var selectedGroups = [];
for (var n in propGroups.value) {

View File

@@ -1 +1 @@
angular.module("umbraco.install", ["umbraco.directives"]);
angular.module("umbraco.install", ["umbraco.directives"]);

View File

@@ -125,7 +125,7 @@ body {
@media (max-width: 500px) {
#search-form .form-search {
width: ~"(calc(~'100%' - ~'80px'))";
width: calc(100% - 80px);
}
}

View File

@@ -14,7 +14,7 @@ body {
overflow: hidden;
height: 100%;
width: 100%;
width: ~"(calc(~'100%' - ~'80px'))"; // 80px is the fixed left menu for toggling the different browser sizes
width: calc(100% - 80px); // 80px is the fixed left menu for toggling the different browser sizes
position: absolute;
padding: 0;
margin: 0;

View File

@@ -155,7 +155,7 @@
@media (max-width: 500px) {
.umb-overlay.umb-overlay-left {
margin-left: 41px;
width: ~"(calc(~'100%' - ~'41px'))";
width: calc(100% - 41px);
}
}

View File

@@ -329,7 +329,7 @@ a.umb-package-details__back-link {
.umb-package-details__main-content {
flex: 1 1 auto;
margin-right: 30px;
width: ~"(calc(~'100%' - ~'@{sidebarwidth}' - ~'30px'))"; // Make sure that the main content area doesn't gets affected by inline styling
width: calc(~'100%' - ~'@{sidebarwidth}' - ~'30px'); // Make sure that the main content area doesn't gets affected by inline styling
}
.umb-package-details__sidebar {

View File

@@ -356,7 +356,7 @@
.umb-panel-header-content-wrapper {
display: flex;
flex-direction: column;
height: 100px;
height: 99px;
padding: 0 20px;
}

View File

@@ -131,7 +131,7 @@ ul.sections li.help {
bottom: 0;
left: 0;
display: block;
width: ~"(calc(~'100%' - ~'5px'))"; //subtract 4px orange border + 1px border-right for sections
width: calc(100% - 5px); //subtract 4px orange border + 1px border-right for sections
}
ul.sections li.help a {

View File

@@ -13,7 +13,9 @@
<form name="inviteUserPasswordForm" novalidate="" ng-submit="inviteSavePassword()" val-form-manager>
<div class="form" ng-if="inviteStep === 1">
<h1 style="margin-bottom: 10px; text-align: left;">Hi, {{invitedUser.name}}</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>
<p style="line-height: 1.6; margin-bottom: 25px;">
<localize key="user_inviteWelcomeMessage">Welcome to Umbraco! Just need to get your password and avatar setup and then you're good to go</localize>
</p>
<div class="control-group" ng-class="{error: setPasswordForm.password.$invalid}">
<label>

View File

@@ -137,6 +137,9 @@ angular.module("umbraco")
currentUserResource.changePassword($scope.changePasswordModel.value).then(function(data) {
//reset old data
clearPasswordFields();
//if the password has been reset, then update our model
if (data.value) {
$scope.changePasswordModel.value.generatedPassword = data.value;
@@ -167,8 +170,9 @@ angular.module("umbraco")
}
function clearPasswordFields() {
$scope.changePasswordModel.value.oldPassword = "";
$scope.changePasswordModel.value.newPassword = "";
$scope.changePasswordModel.confirm = "";
$scope.changePasswordModel.value.confirm = "";
}
});

View File

@@ -12,12 +12,13 @@
</div>
<div ng-switch-when="true">
<ng-form name="passwordForm">
<umb-control-group alias="resetPassword" label="@user_resetPassword" ng-show="$parent.showReset()">
<umb-control-group alias="resetPassword" label="@user_resetPassword" ng-show="$parent.config.enableReset">
<input type="checkbox" ng-model="$parent.passwordValues.reset"
id="Checkbox1"
name="resetPassword"
val-server-field="resetPassword"
no-dirty-check />
no-dirty-check
ng-change="$parent.$parent.showReset = !$parent.$parent.showReset"/>
<span class="help-inline" val-msg-for="resetPassword" val-toggle-msg="valServerField"></span>
</umb-control-group>
@@ -33,7 +34,7 @@
<span class="help-inline" val-msg-for="oldPassword" val-toggle-msg="valServerField"></span>
</umb-control-group>
<umb-control-group alias="password" label="@user_newPassword" ng-if="$parent.showNewPass()" required="true">
<umb-control-group alias="password" label="@user_newPassword" ng-if="!$parent.showReset" required="true">
<input type="password" name="password" ng-model="$parent.passwordValues.newPassword"
class="input-block-level umb-textstring textstring"
required
@@ -46,8 +47,8 @@
<span class="help-inline" val-msg-for="password" val-toggle-msg="valServerField"></span>
</umb-control-group>
<umb-control-group alias="confirmpassword" label="@user_confirmNewPassword" ng-if="$parent.showConfirmPass()" required="true">
<input type="password" name="confirmpassword" ng-model="$parent.confirm"
<umb-control-group alias="confirmpassword" label="@user_confirmNewPassword" ng-if="!$parent.showReset" required="true">
<input type="password" name="confirmpassword" ng-model="$parent.passwordValues.confirm"
class="input-block-level umb-textstring textstring"
val-compare="password"
no-dirty-check
@@ -65,4 +66,5 @@
</ng-form>
</div>
</div>
</div>

View File

@@ -26,6 +26,7 @@ angular.module("umbraco")
$scope.control.value = {
focalPoint: selectedImage.focalPoint,
id: selectedImage.id,
udi: selectedImage.udi,
image: selectedImage.image,
altText: selectedImage.altText
};

View File

@@ -15,7 +15,7 @@
vm.labels = {};
vm.maxFileSize = Umbraco.Sys.ServerVariables.umbracoSettings.maxFileSize + "KB";
vm.acceptedFileTypes = mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes);
vm.emailIsUsername = true;
vm.usernameIsEmail = Umbraco.Sys.ServerVariables.umbracoSettings.usernameIsEmail;
//create the initial model for change password
vm.changePasswordModel = {
@@ -68,7 +68,7 @@
setUserDisplayState();
formatDatesToLocal(vm.user);
vm.emailIsUsername = user.email === user.username;
vm.usernameIsEmail = Umbraco.Sys.ServerVariables.umbracoSettings.usernameIsEmail && user.email === user.username;
//go get the config for the membership provider and add it to the model
authResource.getMembershipProviderConfig().then(function (data) {

View File

@@ -43,11 +43,11 @@
<span class="help-inline" val-msg-for="email" val-toggle-msg="valServerField"></span>
</umb-control-group>
<umb-control-group label="@general_username" ng-if="!vm.emailIsUsername" required="true">
<umb-control-group label="@general_username" ng-if="!vm.usernameIsEmail" required="true">
<input
type="text"
localize="placeholder"
placeholder="@placeholders_entername"
placeholder="@placeholders_enterusername"
class="input-block-level"
ng-model="vm.user.username"
umb-auto-focus name="username"

View File

@@ -59,10 +59,14 @@
function selectUserGroup(userGroup, selection, event) {
// only allow selection if user is member of the group or admin
// Only allow selection if user is member of the group or admin
if (currentUser.userGroups.indexOf(userGroup.group.alias) === -1 && currentUser.userGroups.indexOf("admin") === -1) {
return;
}
// Disallow selection of the admin group, the checkbox is not visible in the UI, but clicking(and thous selecting) is still possible.
// Currently selection can only be used for deleting, and the Controller will also disallow deleting the admin group.
if (userGroup.group.alias === "admin")
return;
if (userGroup.selected) {
var index = selection.indexOf(userGroup.group.id);

View File

@@ -84,6 +84,7 @@
<a class="umb-list-item" ng-click="vm.clickUserGroup(group)" ng-class="{'umb-list-item--selected': group.selected}" href="" ng-switch-when="true">
<div style="margin-right: 25px;">
<div class="umb-list-checkbox"
ng-hide="group.group.alias === 'admin'"
ng-class="{'umb-list-checkbox--visible': vm.selection.length > 0}"
ng-click="vm.selectUserGroup(group, vm.selection, $event)" >
<umb-checkmark checked="group.selected"></umb-checkmark>

View File

@@ -26,6 +26,8 @@
vm.selectedBulkUserGroups = [];
vm.usernameIsEmail = Umbraco.Sys.ServerVariables.umbracoSettings.usernameIsEmail;
vm.allowDisableUser = true;
vm.allowEnableUser = true;
vm.allowUnlockUser = true;
@@ -50,24 +52,31 @@
"selected": true
};
//don't set this if no email is configured
if (Umbraco.Sys.ServerVariables.umbracoSettings.emailServerConfigured) {
//don't show the invite button if no email is configured
if (Umbraco.Sys.ServerVariables.umbracoSettings.showUserInvite) {
vm.defaultButton = {
labelKey: "user_inviteUser",
handler: function () {
handler: function() {
vm.setUsersViewState('inviteUser');
}
};
vm.subButtons = [
{
labelKey: "user_createUser",
handler: function () {
vm.setUsersViewState('createUser');
}
}
];
}
vm.subButtons = [
{
else {
vm.defaultButton = {
labelKey: "user_createUser",
handler: function () {
vm.setUsersViewState('createUser');
}
}
];
};
}
vm.toggleFilter = toggleFilter;
vm.setUsersViewState = setUsersViewState;

View File

@@ -325,6 +325,13 @@
<span class="help-inline" val-msg-for="name" val-toggle-msg="valServerField"></span>
</umb-control-group>
<umb-control-group label="@general_username" label-for="username" required="true" ng-if="!vm.usernameIsEmail">
<input type="text" name="username" localize="placeholder" placeholder="@placeholders_enterusername" class="input-block-level"
ng-model="vm.newUser.username" val-server-field="Username" ng-required="vm.usernameIsEmail" />
<span class="help-inline" val-msg-for="username" val-toggle-msg="required"><localize key="general_required">Required</localize></span>
<span class="help-inline" val-msg-for="username" val-toggle-msg="valServerField"></span>
</umb-control-group>
<umb-control-group label="@general_email" label-for="email" required="true">
<input type="email" name="email" localize="placeholder" placeholder="@placeholders_enteremail" class="input-block-level"
ng-model="vm.newUser.email" required val-email val-server-field="Email" />

View File

@@ -1,5 +1,7 @@
module.exports = function(karma) {
karma.configure({
module.exports = function(config) {
config.set({
// base path, that will be used to resolve files and exclude
basePath: '../..',
@@ -7,31 +9,23 @@ module.exports = function(karma) {
// list of files / patterns to load in the browser
files: [
'lib/../build/belle/lib/jquery/jquery.min.js',
//libraries
'lib-bower/jquery/jquery.min.js',
'lib/angular/1.1.5/angular.js',
'lib/angular/1.1.5/angular-cookies.min.js',
'lib/angular/1.1.5/angular-mocks.js',
'lib/angular/angular-ui-sortable.js',
/*
For angular 1.2:
'lib/angular/1.2/angular.js',
'lib/angular/1.2/angular-route.min.js',
'lib/angular/1.2/angular-touch.min.js',
'lib/angular/1.2/angular-cookies.min.js',
'lib/angular/1.2/angular-animate.min.js',
'lib/angular/1.2/angular-mocks.js',*/
'lib/../build/belle/lib/underscore/underscore-min.js',
'lib/../build/belle/lib/moment/moment-with-locales.js',
'lib-bower/underscore/underscore-min.js',
'lib-bower/moment/moment-with-locales.js',
'lib/umbraco/Extensions.js',
'lib/../build/belle/lib/rgrove-lazyload/lazyload.js',
'lib/../build/belle/lib/angular-local-storage/angular-local-storage.min.js',
'lib-bower/rgrove-lazyload/lazyload.js',
'lib-bower//angular-local-storage/angular-local-storage.min.js',
//app bootstrap and loader
'test/config/app.unit.js',
'src/common/mocks/umbraco.servervariables.js',
//application files
'src/common/directives/**/*.js',
'src/common/filters/*.js',
'src/common/services/*.js',
@@ -39,8 +33,9 @@ module.exports = function(karma) {
'src/common/resources/*.js',
'src/common/mocks/**/*.js',
'src/views/**/*.controller.js',
'test/unit/**/*.spec.js',
{pattern: 'lib/**/*.js', watched: true, served: true, included: false}
//tests
'test/unit/**/*.spec.js'
],
// list of files to exclude
@@ -66,7 +61,7 @@ module.exports = function(karma) {
// level of logging
// possible values: karma.LOG_DISABLE || karma.LOG_ERROR || karma.LOG_WARN || karma.LOG_INFO || karma.LOG_DEBUG
// CLI --log-level debug
logLevel: karma.LOG_INFO,
logLevel: config.LOG_WARN,
// enable / disable watching file and executing tests whenever any file changes
// CLI --auto-watch --no-auto-watch