diff --git a/src/SQLCE4Umbraco/SqlCEHelper.cs b/src/SQLCE4Umbraco/SqlCEHelper.cs index a7f1abd88c..1787c05912 100644 --- a/src/SQLCE4Umbraco/SqlCEHelper.cs +++ b/src/SQLCE4Umbraco/SqlCEHelper.cs @@ -42,6 +42,13 @@ namespace SqlCE4Umbraco { var sqlCeEngine = new SqlCeEngine(ConnectionString); sqlCeEngine.CreateDatabase(); + + // SD: Pretty sure this should be in a using clause but i don't want to cause unknown side-effects here + // since it's been like this for quite some time + //using (var sqlCeEngine = new SqlCeEngine(ConnectionString)) + //{ + // sqlCeEngine.CreateDatabase(); + //} } } diff --git a/src/Umbraco.Core/ApplicationContext.cs b/src/Umbraco.Core/ApplicationContext.cs index 79e4a72756..f1bb929a94 100644 --- a/src/Umbraco.Core/ApplicationContext.cs +++ b/src/Umbraco.Core/ApplicationContext.cs @@ -165,6 +165,9 @@ namespace Umbraco.Core /// internal string OriginalRequestUrl { get; set; } + /// + /// Checks if the version configured matches the assembly version + /// private bool Configured { get diff --git a/src/Umbraco.Core/Configuration/GlobalSettings.cs b/src/Umbraco.Core/Configuration/GlobalSettings.cs index 3c16377a8f..dc2675bd58 100644 --- a/src/Umbraco.Core/Configuration/GlobalSettings.cs +++ b/src/Umbraco.Core/Configuration/GlobalSettings.cs @@ -41,7 +41,7 @@ namespace Umbraco.Core.Configuration private static string _reservedUrls; //ensure the built on (non-changeable) reserved paths are there at all times private const string StaticReservedPaths = "~/app_plugins/,~/install/,"; - private const string StaticReservedUrls = "~/config/splashes/booting.aspx,~/install/default.aspx,~/config/splashes/noNodes.aspx,~/VSEnterpriseHelper.axd,"; + private const string StaticReservedUrls = "~/config/splashes/booting.aspx,~/config/splashes/noNodes.aspx,~/VSEnterpriseHelper.axd,"; #endregion diff --git a/src/Umbraco.Core/DatabaseContext.cs b/src/Umbraco.Core/DatabaseContext.cs index 849599e0c6..435470a493 100644 --- a/src/Umbraco.Core/DatabaseContext.cs +++ b/src/Umbraco.Core/DatabaseContext.cs @@ -113,8 +113,8 @@ namespace Umbraco.Core public void ConfigureEmbeddedDatabaseConnection() { const string providerName = "System.Data.SqlServerCe.4.0"; - const string connectionString = @"Data Source=|DataDirectory|\Umbraco.sdf;Flush Interval=1;"; + var connectionString = GetEmbeddedDatabaseConnectionString(); SaveConnectionString(connectionString, providerName); var path = Path.Combine(GlobalSettings.FullpathToRoot, "App_Data", "Umbraco.sdf"); @@ -122,11 +122,23 @@ namespace Umbraco.Core { var engine = new SqlCeEngine(connectionString); engine.CreateDatabase(); + + // SD: Pretty sure this should be in a using clause but i don't want to cause unknown side-effects here + // since it's been like this for quite some time + //using (var engine = new SqlCeEngine(connectionString)) + //{ + // engine.CreateDatabase(); + //} } Initialize(providerName); } + public string GetEmbeddedDatabaseConnectionString() + { + return @"Data Source=|DataDirectory|\Umbraco.sdf;Flush Interval=1;"; + } + /// /// Configure a ConnectionString that has been entered manually. /// @@ -149,27 +161,29 @@ namespace Umbraco.Core /// Database Password /// Type of the provider to be used (Sql, Sql Azure, Sql Ce, MySql) public void ConfigureDatabaseConnection(string server, string databaseName, string user, string password, string databaseProvider) - { - string connectionString; - string providerName = "System.Data.SqlClient"; - if (databaseProvider.ToLower().Contains("mysql")) - { - providerName = "MySql.Data.MySqlClient"; - connectionString = string.Format("Server={0}; Database={1};Uid={2};Pwd={3}", server, databaseName, user, password); - } - else if (databaseProvider.ToLower().Contains("azure")) - { - connectionString = BuildAzureConnectionString(server, databaseName, user, password); - } - else - { - connectionString = string.Format("server={0};database={1};user id={2};password={3}", server, databaseName, user, password); - } + { + string providerName; + var connectionString = GetDatabaseConnectionString(server, databaseName, user, password, databaseProvider, out providerName); SaveConnectionString(connectionString, providerName); Initialize(providerName); } + public string GetDatabaseConnectionString(string server, string databaseName, string user, string password, string databaseProvider, out string providerName) + { + providerName = "System.Data.SqlClient"; + if (databaseProvider.ToLower().Contains("mysql")) + { + providerName = "MySql.Data.MySqlClient"; + return string.Format("Server={0}; Database={1};Uid={2};Pwd={3}", server, databaseName, user, password); + } + if (databaseProvider.ToLower().Contains("azure")) + { + return BuildAzureConnectionString(server, databaseName, user, password); + } + return string.Format("server={0};database={1};user id={2};password={3}", server, databaseName, user, password); + } + /// /// Configures a ConnectionString for the Umbraco database that uses Microsoft SQL Server integrated security. /// @@ -178,12 +192,16 @@ namespace Umbraco.Core public void ConfigureIntegratedSecurityDatabaseConnection(string server, string databaseName) { const string providerName = "System.Data.SqlClient"; - string connectionString = String.Format("Server={0};Database={1};Integrated Security=true", server, databaseName); - + var connectionString = GetIntegratedSecurityDatabaseConnectionString(server, databaseName); SaveConnectionString(connectionString, providerName); Initialize(providerName); } + public string GetIntegratedSecurityDatabaseConnectionString(string server, string databaseName) + { + return String.Format("Server={0};Database={1};Integrated Security=true", server, databaseName); + } + internal string BuildAzureConnectionString(string server, string databaseName, string user, string password) { if (server.Contains(".") && ServerStartsWithTcp(server) == false) @@ -597,5 +615,34 @@ namespace Umbraco.Core public bool Success { get; set; } public string Percentage { get; set; } } + + internal bool IsConnectionStringConfigured(ConnectionStringSettings databaseSettings) + { + var dbIsSqlCe = false; + if (databaseSettings != null && databaseSettings.ProviderName != null) + dbIsSqlCe = databaseSettings.ProviderName == "System.Data.SqlServerCe.4.0"; + var sqlCeDatabaseExists = false; + if (dbIsSqlCe) + { + var parts = databaseSettings.ConnectionString.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries); + var dataSourcePart = parts.FirstOrDefault(x => x.InvariantStartsWith("Data Source=")); + if (dataSourcePart != null) + { + var datasource = dataSourcePart.Replace("|DataDirectory|", AppDomain.CurrentDomain.GetData("DataDirectory").ToString()); + var filePath = datasource.Replace("Data Source=", string.Empty); + sqlCeDatabaseExists = File.Exists(filePath); + } + } + + // Either the connection details are not fully specified or it's a SQL CE database that doesn't exist yet + if (databaseSettings == null + || string.IsNullOrWhiteSpace(databaseSettings.ConnectionString) || string.IsNullOrWhiteSpace(databaseSettings.ProviderName) + || (dbIsSqlCe && sqlCeDatabaseExists == false)) + { + return false; + } + + return true; + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/DictionaryExtensions.cs b/src/Umbraco.Core/DictionaryExtensions.cs index fd6476fe21..ddfe963f48 100644 --- a/src/Umbraco.Core/DictionaryExtensions.cs +++ b/src/Umbraco.Core/DictionaryExtensions.cs @@ -15,6 +15,25 @@ namespace Umbraco.Core /// internal static class DictionaryExtensions { + + /// + /// Method to Get a value by the key. If the key doesn't exist it will create a new TVal object for the key and return it. + /// + /// + /// + /// + /// + /// + public static TVal GetOrCreate(this IDictionary dict, TKey key) + where TVal : class, new() + { + if (dict.ContainsKey(key) == false) + { + dict.Add(key, new TVal()); + } + return dict[key]; + } + /// /// Updates an item with the specified key with the specified value /// diff --git a/src/Umbraco.Core/Persistence/SqlExtensions.cs b/src/Umbraco.Core/Persistence/SqlExtensions.cs new file mode 100644 index 0000000000..7fbd254709 --- /dev/null +++ b/src/Umbraco.Core/Persistence/SqlExtensions.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Data.SqlClient; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Umbraco.Core.Persistence +{ + //TODO: check if any of this works and for what databse types it works for: + // ref: http://stackoverflow.com/questions/16171144/how-to-check-for-database-availability + + internal static class SqlExtensions + { + public static bool IsConnectionAvailable(string connString) + { + using (var connection = new SqlConnection(connString)) + { + return connection.IsAvailable(); + } + } + + public static bool IsAvailable(this SqlConnection connection) + { + try + { + connection.Open(); + connection.Close(); + } + catch (SqlException) + { + return false; + } + + return true; + } + } +} diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 0d467eaab4..9e08a02dbc 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -66,7 +66,6 @@ False ..\packages\Newtonsoft.Json.4.5.11\lib\net40\Newtonsoft.Json.dll - @@ -356,6 +355,7 @@ + diff --git a/src/Umbraco.Tests/MockTests.cs b/src/Umbraco.Tests/MockTests.cs index 45b07d4c71..c028992772 100644 --- a/src/Umbraco.Tests/MockTests.cs +++ b/src/Umbraco.Tests/MockTests.cs @@ -116,7 +116,6 @@ namespace Umbraco.Tests Assert.AreNotEqual(appCtx, result); } - [NUnit.Framework.Ignore("Need to fix more stuff up, this is ignore because an exception occurs because it wants to ensure we have a resolver initialized - need to make that process better for testability")] [Test] public void Can_Get_Umbraco_Context() { diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 833d891a0b..b638b4060e 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -1,4 +1,4 @@ - + Debug @@ -651,7 +651,9 @@ - + + + xcopy "$(ProjectDir)"..\packages\SqlServerCE.4.0.0.0\amd64\*.* "$(TargetDir)amd64\" /Y /F /E /D diff --git a/src/Umbraco.Web.UI.Client/gruntFile.js b/src/Umbraco.Web.UI.Client/gruntFile.js index 15d5c6e9d4..7ae0a96841 100644 --- a/src/Umbraco.Web.UI.Client/gruntFile.js +++ b/src/Umbraco.Web.UI.Client/gruntFile.js @@ -3,17 +3,18 @@ module.exports = function (grunt) { // Default task. grunt.registerTask('default', ['jshint:dev','build','karma:unit']); grunt.registerTask('dev', ['jshint:dev', 'build', 'webserver', 'open:dev', 'watch']); - + //run by the watch task grunt.registerTask('watch-js', ['jshint:dev','concat','copy:app','copy:mocks','copy:packages','copy:vs','karma:unit']); - grunt.registerTask('watch-less', ['recess:build','copy:assets','copy:vs']); + grunt.registerTask('watch-less', ['recess:build','recess:installer','copy:assets','copy:vs']); grunt.registerTask('watch-html', ['copy:views', 'copy:vs']); grunt.registerTask('watch-packages', ['copy:packages']); + grunt.registerTask('watch-installer', ['concat:install','concat:installJs','copy:installer', 'copy:vs']); grunt.registerTask('watch-test', ['jshint:dev', 'karma:unit']); //triggered from grunt dev or grunt - grunt.registerTask('build', ['clean','concat','recess:min','copy']); - + grunt.registerTask('build', ['clean','concat','recess:min','recess:installer','copy']); + //utillity tasks grunt.registerTask('docs', ['ngdocs']); grunt.registerTask('webserver', ['connect:devserver']); @@ -68,7 +69,7 @@ module.exports = function (grunt) { specs: ['test/**/*.spec.js'], scenarios: ['test/**/*.scenario.js'], samples: ['sample files/*.js'], - html: ['src/index.html'], + html: ['src/index.html','src/install.html'], everything:['src/**/*.*', 'test/**/*.*', 'docs/**/*.*'], @@ -86,6 +87,11 @@ module.exports = function (grunt) { assets: { files: [{ dest: '<%= distdir %>/assets', src : '**', expand: true, cwd: 'src/assets/' }] }, + + installer: { + files: [{ dest: '<%= distdir %>/views/install', src : '**/*.html', expand: true, cwd: 'src/installer/steps' }] + }, + vendor: { files: [{ dest: '<%= distdir %>/lib', src : '**', expand: true, cwd: 'lib/' }] }, @@ -130,8 +136,24 @@ module.exports = function (grunt) { 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 angular.module('umbraco.install', []); \n", + footer: "\n\n})();" + } + }, controllers: { - src:['src/views/**/*.controller.js'], + src:['src/controllers/**/*.controller.js','src/views/**/*.controller.js'], dest: '<%= distdir %>/js/umbraco.controllers.js', options: { banner: "<%= banner %>\n(function() { \n\n", @@ -198,7 +220,7 @@ module.exports = function (grunt) { } } }, - + recess: { build: { files: { @@ -208,6 +230,14 @@ module.exports = function (grunt) { compile: true } }, + installer: { + files: { + '<%= distdir %>/assets/css/installer.css': + ['src/less/installer.less'] }, + options: { + compile: true + } + }, min: { files: { '<%= distdir %>/assets/css/<%= pkg.name %>.css': ['<%= src.less %>'] @@ -220,7 +250,6 @@ module.exports = function (grunt) { }, - watch:{ css: { files: '**/*.less', @@ -237,6 +266,10 @@ module.exports = function (grunt) { files: ['test/**/*.js'], tasks: ['watch-test', 'timestamp'], }, + installer: { + files: ['src/installer/**/*.*'], + tasks: ['watch-installer', 'timestamp'], + }, html: { files: ['src/views/**/*.html', 'src/*.html'], tasks:['watch-html','timestamp'] @@ -288,7 +321,7 @@ module.exports = function (grunt) { //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.prod %>'], @@ -312,13 +345,13 @@ module.exports = function (grunt) { smarttabs: true, globalstrict:true, globals:{$:false, jQuery:false,define:false,require:false,window:false} - } + } } } }); - - + + grunt.loadNpmTasks('grunt-contrib-concat'); grunt.loadNpmTasks('grunt-contrib-jshint'); grunt.loadNpmTasks('grunt-contrib-clean'); @@ -328,11 +361,10 @@ module.exports = function (grunt) { grunt.loadNpmTasks('grunt-recess'); grunt.loadNpmTasks('grunt-karma'); - + grunt.loadNpmTasks('grunt-open'); grunt.loadNpmTasks('grunt-contrib-connect'); grunt.loadNpmTasks('grunt-ngdocs'); - grunt.loadNpmTasks('grunt-ngmin'); }; diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index f5f6ae1bb2..ac3f424f50 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -42,7 +42,6 @@ "karma-coffee-preprocessor": "0.0.1", "karma": "~0.9", "karma-phantomjs-launcher": "0.0.2", - "grunt-ngdocs": "~0.1.2", - "grunt-ngmin": "0.0.3" + "grunt-ngdocs": "~0.1.2" } } diff --git a/src/Umbraco.Web.UI.Client/src/assets/img/application/logo_black.png b/src/Umbraco.Web.UI.Client/src/assets/img/application/logo_black.png new file mode 100644 index 0000000000..d3c6dc56c2 Binary files /dev/null and b/src/Umbraco.Web.UI.Client/src/assets/img/application/logo_black.png differ diff --git a/src/Umbraco.Web.UI.Client/src/assets/img/application/logo_white.png b/src/Umbraco.Web.UI.Client/src/assets/img/application/logo_white.png new file mode 100644 index 0000000000..72b2fe470a Binary files /dev/null and b/src/Umbraco.Web.UI.Client/src/assets/img/application/logo_white.png differ diff --git a/src/Umbraco.Web.UI.Client/src/assets/img/installer.jpg b/src/Umbraco.Web.UI.Client/src/assets/img/installer.jpg new file mode 100644 index 0000000000..7985a510b7 Binary files /dev/null and b/src/Umbraco.Web.UI.Client/src/assets/img/installer.jpg differ diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/editors/ace.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/editors/ace.directive.js deleted file mode 100644 index d8ef422165..0000000000 --- a/src/Umbraco.Web.UI.Client/src/common/directives/editors/ace.directive.js +++ /dev/null @@ -1,59 +0,0 @@ -angular.module('umbraco.directives.editors').directive('ace', function(assetsService) { - var ACE_EDITOR_CLASS = 'ace-editor'; - - function loadAceEditor(element, mode) { - assetsService.loadJs("lib/ace/noconflict/ace.js").then(function(){ - var editor = ace.edit($(element).find('.' + ACE_EDITOR_CLASS)[0]); - editor.session.setMode("ace/mode/" + mode); - editor.renderer.setShowPrintMargin(false); - return editor; - }); - } - - function valid(editor) { - return (Object.keys(editor.getSession().getAnnotations()).length === 0); - } - - return { - restrict: 'A', - require: '?ngModel', - transclude: true, - template: '
', - - link: function(scope, element, attrs, ngModel) { - function read() { - ngModel.$setViewValue(editor.getValue()); - textarea.val(editor.getValue()); - } - - var textarea = $(element).find('textarea'); - textarea.hide(); - - var mode = attrs.ace; - var editor = loadAceEditor(element, mode); - scope.ace = editor; - - if (!ngModel) - { - return; // do nothing if no ngModel - } - - ngModel.$render = function() { - var value = ngModel.$viewValue || ''; - editor.getSession().setValue(value); - textarea.val(value); - }; - - editor.getSession().on('changeAnnotation', function() { - if (valid(editor)) { - scope.$apply(read); - } - }); - - editor.getSession().setValue(textarea.val()); - - read(); - - } - }; -}); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/umbsinglefileupload.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/editors/umbsinglefileupload.directive.js similarity index 96% rename from src/Umbraco.Web.UI.Client/src/common/directives/umbsinglefileupload.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/editors/umbsinglefileupload.directive.js index e1f6dd380f..b8123b9202 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/umbsinglefileupload.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/editors/umbsinglefileupload.directive.js @@ -1,33 +1,33 @@ -/** -* @ngdoc directive -* @name umbraco.directives.directive:umbFileUpload -* @function -* @restrict A -* @scope -* @description -* A single file upload field that will reset itself based on the object passed in for the rebuild parameter. This -* is required because the only way to reset an upload control is to replace it's html. -**/ -function umbSingleFileUpload($compile) { - return { - restrict: "E", - scope: { - rebuild: "=" - }, - replace: true, - template: "
", - link: function (scope, el, attrs) { - - scope.$watch("rebuild", function (newVal, oldVal) { - if (newVal && newVal !== oldVal) { - //recompile it! - el.html(""); - $compile(el.contents())(scope); - } - }); - - } - }; -} - +/** +* @ngdoc directive +* @name umbraco.directives.directive:umbFileUpload +* @function +* @restrict A +* @scope +* @description +* A single file upload field that will reset itself based on the object passed in for the rebuild parameter. This +* is required because the only way to reset an upload control is to replace it's html. +**/ +function umbSingleFileUpload($compile) { + return { + restrict: "E", + scope: { + rebuild: "=" + }, + replace: true, + template: "
", + link: function (scope, el, attrs) { + + scope.$watch("rebuild", function (newVal, oldVal) { + if (newVal && newVal !== oldVal) { + //recompile it! + el.html(""); + $compile(el.contents())(scope); + } + }); + + } + }; +} + angular.module('umbraco.directives').directive("umbSingleFileUpload", umbSingleFileUpload); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/sectionicon.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/html/sectionicon.directive.js similarity index 97% rename from src/Umbraco.Web.UI.Client/src/common/directives/sectionicon.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/html/sectionicon.directive.js index 7f8ea83215..d6a4537262 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/sectionicon.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/html/sectionicon.directive.js @@ -1,30 +1,30 @@ -angular.module("umbraco.directives") -.directive('sectionIcon', function ($compile, iconHelper) { - return { - restrict: 'E', - replace: true, - - link: function (scope, element, attrs) { - - var icon = attrs.icon; - - if (iconHelper.isLegacyIcon(icon)) { - //its a known legacy icon, convert to a new one - element.html(""); - } - else if (iconHelper.isFileBasedIcon(icon)) { - var convert = iconHelper.convertFromLegacyImage(icon); - if(convert){ - element.html(""); - }else{ - element.html(""); - } - //it's a file, normally legacy so look in the icon tray images - } - else { - //it's normal - element.html(""); - } - } - }; +angular.module("umbraco.directives") +.directive('sectionIcon', function ($compile, iconHelper) { + return { + restrict: 'E', + replace: true, + + link: function (scope, element, attrs) { + + var icon = attrs.icon; + + if (iconHelper.isLegacyIcon(icon)) { + //its a known legacy icon, convert to a new one + element.html(""); + } + else if (iconHelper.isFileBasedIcon(icon)) { + var convert = iconHelper.convertFromLegacyImage(icon); + if(convert){ + element.html(""); + }else{ + element.html(""); + } + //it's a file, normally legacy so look in the icon tray images + } + else { + //it's normal + element.html(""); + } + } + }; }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/umbavatar.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/html/umbavatar.directive.js similarity index 96% rename from src/Umbraco.Web.UI.Client/src/common/directives/umbavatar.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/html/umbavatar.directive.js index 6779b6e1d8..0702d7207c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/umbavatar.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/html/umbavatar.directive.js @@ -1,27 +1,27 @@ -/** -* @ngdoc directive -* @name umbraco.directives.directive:umbAvatar -* @restrict E -**/ -function avatarDirective() { - return { - restrict: "E", // restrict to an element - replace: true, // replace the html element with the template - templateUrl: 'views/directives/umb-avatar.html', - scope: { - name: '@', - email: '@', - hash: '@' - }, - link: function(scope, element, attr, ctrl) { - - scope.$watch("hash", function (val) { - //set the gravatar url - scope.gravatar = "http://www.gravatar.com/avatar/" + val + "?s=40"; - }); - - } - }; -} - -angular.module('umbraco.directives').directive("umbAvatar", avatarDirective); +/** +* @ngdoc directive +* @name umbraco.directives.directive:umbAvatar +* @restrict E +**/ +function avatarDirective() { + return { + restrict: "E", // restrict to an element + replace: true, // replace the html element with the template + templateUrl: 'views/directives/umb-avatar.html', + scope: { + name: '@', + email: '@', + hash: '@' + }, + link: function(scope, element, attr, ctrl) { + + scope.$watch("hash", function (val) { + //set the gravatar url + scope.gravatar = "http://www.gravatar.com/avatar/" + val + "?s=40"; + }); + + } + }; +} + +angular.module('umbraco.directives').directive("umbAvatar", avatarDirective); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/umbheader.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/html/umbheader.directive.js similarity index 97% rename from src/Umbraco.Web.UI.Client/src/common/directives/umbheader.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/html/umbheader.directive.js index cba6a05e60..b1cca01d44 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/umbheader.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/html/umbheader.directive.js @@ -1,79 +1,79 @@ -angular.module("umbraco.directives") -.directive('umbHeader', function($parse, $timeout){ - return { - restrict: 'E', - replace: true, - transclude: 'true', - templateUrl: 'views/directives/umb-header.html', - //create a new isolated scope assigning a tabs property from the attribute 'tabs' - //which is bound to the parent scope property passed in - scope: { - tabs: "=" - }, - link: function (scope, iElement, iAttrs) { - - var maxTabs = 4; - - function collectFromDom(activeTab){ - var $panes = $('div.tab-content'); - - angular.forEach($panes.find('.tab-pane'), function (pane, index) { - var $this = angular.element(pane); - - var id = $this.attr("rel"); - var label = $this.attr("label"); - var tab = {id: id, label: label, active: false}; - if(!activeTab){ - tab.active = true; - activeTab = tab; - } - - if ($this.attr("rel") === String(activeTab.id)) { - $this.addClass('active'); - } - else { - $this.removeClass('active'); - } - - if(label){ - scope.visibleTabs.push(tab); - } - - }); - } - - scope.showTabs = iAttrs.tabs ? true : false; - scope.visibleTabs = []; - scope.overflownTabs = []; - - $timeout(function () { - collectFromDom(undefined); - }, 500); - - //when the tabs change, we need to hack the planet a bit and force the first tab content to be active, - //unfortunately twitter bootstrap tabs is not playing perfectly with angular. - scope.$watch("tabs", function (newValue, oldValue) { - - angular.forEach(newValue, function(val, index){ - var tab = {id: val.id, label: val.label}; - scope.visibleTabs.push(tab); - }); - - //don't process if we cannot or have already done so - if (!newValue) {return;} - if (!newValue.length || newValue.length === 0){return;} - - var activeTab = _.find(newValue, function (item) { - return item.active; - }); - - //we need to do a timeout here so that the current sync operation can complete - // and update the UI, then this will fire and the UI elements will be available. - $timeout(function () { - collectFromDom(activeTab); - }, 500); - - }); - } - }; +angular.module("umbraco.directives") +.directive('umbHeader', function($parse, $timeout){ + return { + restrict: 'E', + replace: true, + transclude: 'true', + templateUrl: 'views/directives/umb-header.html', + //create a new isolated scope assigning a tabs property from the attribute 'tabs' + //which is bound to the parent scope property passed in + scope: { + tabs: "=" + }, + link: function (scope, iElement, iAttrs) { + + var maxTabs = 4; + + function collectFromDom(activeTab){ + var $panes = $('div.tab-content'); + + angular.forEach($panes.find('.tab-pane'), function (pane, index) { + var $this = angular.element(pane); + + var id = $this.attr("rel"); + var label = $this.attr("label"); + var tab = {id: id, label: label, active: false}; + if(!activeTab){ + tab.active = true; + activeTab = tab; + } + + if ($this.attr("rel") === String(activeTab.id)) { + $this.addClass('active'); + } + else { + $this.removeClass('active'); + } + + if(label){ + scope.visibleTabs.push(tab); + } + + }); + } + + scope.showTabs = iAttrs.tabs ? true : false; + scope.visibleTabs = []; + scope.overflownTabs = []; + + $timeout(function () { + collectFromDom(undefined); + }, 500); + + //when the tabs change, we need to hack the planet a bit and force the first tab content to be active, + //unfortunately twitter bootstrap tabs is not playing perfectly with angular. + scope.$watch("tabs", function (newValue, oldValue) { + + angular.forEach(newValue, function(val, index){ + var tab = {id: val.id, label: val.label}; + scope.visibleTabs.push(tab); + }); + + //don't process if we cannot or have already done so + if (!newValue) {return;} + if (!newValue.length || newValue.length === 0){return;} + + var activeTab = _.find(newValue, function (item) { + return item.active; + }); + + //we need to do a timeout here so that the current sync operation can complete + // and update the UI, then this will fire and the UI elements will be available. + $timeout(function () { + collectFromDom(activeTab); + }, 500); + + }); + } + }; }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/html/umbpanel.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/html/umbpanel.directive.js index 79524e06e6..5b32941fff 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/html/umbpanel.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/html/umbpanel.directive.js @@ -9,66 +9,6 @@ angular.module("umbraco.directives.html") restrict: 'E', replace: true, transclude: 'true', - templateUrl: 'views/directives/html/umb-panel.html', - link: function (scope, el, attrs) { - - - function _setClass(bar, resize){ - - bar = $(bar); - - //no need to process - if(resize){ - bar.removeClass("umb-bottom-bar"); - } - - //already positioned - if(bar.hasClass("umb-bottom-bar")){ - return; - } - - var offset = bar.offset(); - if(offset){ - var bottom = bar.offset().top + bar.height(); - if(bottom > $(window).height()){ - bar.addClass("umb-bottom-bar"); - $(".tab-content .active").addClass("with-buttons"); - }else{ - bar.removeClass("umb-bottom-bar"); - $(".tab-content .active").removeClass("with-buttons"); - } - } - } - - - //initial loading - $timeout(function(){ - var bar = $(".tab-content .active .umb-tab-buttons")[0] || $(".tab-content .umb-tab-buttons")[0]; - var winHeight = $(window).height(); - - scope.$watch(function () { - if(!bar){ - bar = $(".tab-content .active .umb-tab-buttons")[0] || $(".tab-content .umb-tab-buttons")[0]; - } - - var bottom = bar.offsetTop + bar.offsetHeight; - return bottom > winHeight; - }, function(val) { - _setClass(bar); - }); - - - $(window).bind("resize", function () { - _setClass(bar, true); - winHeight = $(window).height(); - }); - - $('a[data-toggle="tab"]').on('shown', function (e) { - bar = $(".tab-content .active .umb-tab-buttons")[0]; - _setClass(bar); - }); - - }, 1000, false); - } + templateUrl: 'views/directives/html/umb-panel.html' }; }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/umbtab.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/html/umbtab.directive.js similarity index 95% rename from src/Umbraco.Web.UI.Client/src/common/directives/umbtab.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/html/umbtab.directive.js index be26042678..3758c64179 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/umbtab.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/html/umbtab.directive.js @@ -1,14 +1,14 @@ -/** -* @ngdoc directive -* @name umbraco.directives.directive:umbTab -* @restrict E -**/ -angular.module("umbraco.directives") -.directive('umbTab', function(){ - return { - restrict: 'E', - replace: true, - transclude: 'true', - templateUrl: 'views/directives/umb-tab.html' - }; +/** +* @ngdoc directive +* @name umbraco.directives.directive:umbTab +* @restrict E +**/ +angular.module("umbraco.directives") +.directive('umbTab', function(){ + return { + restrict: 'E', + replace: true, + transclude: 'true', + templateUrl: 'views/directives/umb-tab.html' + }; }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/umbtabview.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/html/umbtabview.directive.js similarity index 95% rename from src/Umbraco.Web.UI.Client/src/common/directives/umbtabview.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/html/umbtabview.directive.js index 505acb3328..7bb4f1cb18 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/umbtabview.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/html/umbtabview.directive.js @@ -1,14 +1,14 @@ -/** -* @ngdoc directive -* @name umbraco.directives.directive:umbTabView -* @restrict E -**/ -angular.module("umbraco.directives") -.directive('umbTabView', function($timeout, $log){ - return { - restrict: 'E', - replace: true, - transclude: 'true', - templateUrl: 'views/directives/umb-tab-view.html' - }; +/** +* @ngdoc directive +* @name umbraco.directives.directive:umbTabView +* @restrict E +**/ +angular.module("umbraco.directives") +.directive('umbTabView', function($timeout, $log){ + return { + restrict: 'E', + replace: true, + transclude: 'true', + templateUrl: 'views/directives/umb-tab-view.html' + }; }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/autoscale.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/utill/autoscale.directive.js similarity index 96% rename from src/Umbraco.Web.UI.Client/src/common/directives/autoscale.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/utill/autoscale.directive.js index daa432d5ba..11fff9639f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/autoscale.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/utill/autoscale.directive.js @@ -1,38 +1,38 @@ -/** - * @ngdoc directive - * @name umbraco.directives.directive:autoScale - * @element div - * @function - * - * @description - * Resize div's automatically to fit to the bottom of the screen, as an optional parameter an y-axis offset can be set - * So if you only want to scale the div to 70 pixels from the bottom you pass "70" - * - * @example - - -
-
-
- */ -angular.module("umbraco.directives") - .directive('autoScale', function ($window) { - return function (scope, el, attrs) { - - var totalOffset = 0; - var offsety = parseInt(attrs.autoScale, 10); - var window = angular.element($window); - if (offsety !== undefined){ - totalOffset += offsety; - } - - setTimeout(function () { - el.height(window.height() - (el.offset().top + totalOffset)); - }, 500); - - window.bind("resize", function () { - el.height(window.height() - (el.offset().top + totalOffset)); - }); - - }; +/** + * @ngdoc directive + * @name umbraco.directives.directive:autoScale + * @element div + * @function + * + * @description + * Resize div's automatically to fit to the bottom of the screen, as an optional parameter an y-axis offset can be set + * So if you only want to scale the div to 70 pixels from the bottom you pass "70" + * + * @example + + +
+
+
+ */ +angular.module("umbraco.directives") + .directive('autoScale', function ($window) { + return function (scope, el, attrs) { + + var totalOffset = 0; + var offsety = parseInt(attrs.autoScale, 10); + var window = angular.element($window); + if (offsety !== undefined){ + totalOffset += offsety; + } + + setTimeout(function () { + el.height(window.height() - (el.offset().top + totalOffset)); + }, 500); + + window.bind("resize", function () { + el.height(window.height() - (el.offset().top + totalOffset)); + }); + + }; }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/utill/detectfold.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/utill/detectfold.directive.js new file mode 100644 index 0000000000..a0d1a8293b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/utill/detectfold.directive.js @@ -0,0 +1,49 @@ +/** +* @ngdoc directive +* @name umbraco.directives.directive:umbPanel +* @restrict E +**/ +angular.module("umbraco.directives.html") + .directive('detectFold', function($timeout, $log){ + return { + restrict: 'A', + link: function (scope, el, attrs) { + + var state = false, + parent = $(".umb-panel-body"), + winHeight = $(window).height(), + calculate = _.throttle(function(){ + if(el && el.is(":visible") && !el.hasClass("umb-bottom-bar")){ + //var parent = el.parent(); + var hasOverflow = parent.innerHeight() < parent[0].scrollHeight; + //var belowFold = (el.offset().top + el.height()) > winHeight; + if(hasOverflow){ + el.addClass("umb-bottom-bar"); + } + } + return state; + }, 1000); + + scope.$watch(calculate, function(newVal, oldVal) { + if(newVal !== oldVal){ + if(newVal){ + el.addClass("umb-bottom-bar"); + }else{ + el.removeClass("umb-bottom-bar"); + } + } + }); + + $(window).bind("resize", function () { + winHeight = $(window).height(); + el.removeClass("umb-bottom-bar"); + state = false; + calculate(); + }); + + $('a[data-toggle="tab"]').on('shown', function (e) { + calculate(); + }); + } + }; + }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/hotkey.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/utill/hotkey.directive.js similarity index 96% rename from src/Umbraco.Web.UI.Client/src/common/directives/hotkey.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/utill/hotkey.directive.js index f5277e98e0..39a565b7ab 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/hotkey.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/utill/hotkey.directive.js @@ -1,27 +1,27 @@ -/** -* @ngdoc directive -* @name umbraco.directives.directive:headline -**/ -angular.module("umbraco.directives") - .directive('hotkey', function ($window, keyboardService, $log) { - - return function (scope, el, attrs) { - - //support data binding - - var keyCombo = scope.$eval(attrs["hotkey"]); - if (!keyCombo) { - keyCombo = attrs["hotkey"]; - } - - keyboardService.bind(keyCombo, function() { - var element = $(el); - if(element.is("a,button,input[type='button'],input[type='submit']")){ - element.click(); - }else{ - element.focus(); - } - }); - - }; +/** +* @ngdoc directive +* @name umbraco.directives.directive:headline +**/ +angular.module("umbraco.directives") + .directive('hotkey', function ($window, keyboardService, $log) { + + return function (scope, el, attrs) { + + //support data binding + + var keyCombo = scope.$eval(attrs["hotkey"]); + if (!keyCombo) { + keyCombo = attrs["hotkey"]; + } + + keyboardService.bind(keyCombo, function() { + var element = $(el); + if(element.is("a,button,input[type='button'],input[type='submit']")){ + element.click(); + }else{ + element.focus(); + } + }); + + }; }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/localize.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/utill/localize.directive.js similarity index 100% rename from src/Umbraco.Web.UI.Client/src/common/directives/localize.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/utill/localize.directive.js diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/preventdefault.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/utill/preventdefault.directive.js similarity index 97% rename from src/Umbraco.Web.UI.Client/src/common/directives/preventdefault.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/utill/preventdefault.directive.js index e0747600ac..d1e279a9ac 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/preventdefault.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/utill/preventdefault.directive.js @@ -1,29 +1,29 @@ -/** -* @ngdoc directive -* @name umbraco.directives.directive:preventDefault -**/ -angular.module("umbraco.directives") - .directive('preventDefault', function() { - return function(scope, element, attrs) { - - var enabled = true; - //check if there's a value for the attribute, if there is and it's false then we conditionally don't - //prevent default. - if (attrs.preventDefault) { - attrs.$observe("preventDefault", function (newVal) { - enabled = (newVal === "false" || newVal === 0 || newVal === false) ? false : true; - }); - } - - $(element).click(function (event) { - if (event.metaKey || event.ctrlKey) { - return; - } - else { - if (enabled === true) { - event.preventDefault(); - } - } - }); - }; +/** +* @ngdoc directive +* @name umbraco.directives.directive:preventDefault +**/ +angular.module("umbraco.directives") + .directive('preventDefault', function() { + return function(scope, element, attrs) { + + var enabled = true; + //check if there's a value for the attribute, if there is and it's false then we conditionally don't + //prevent default. + if (attrs.preventDefault) { + attrs.$observe("preventDefault", function (newVal) { + enabled = (newVal === "false" || newVal === 0 || newVal === false) ? false : true; + }); + } + + $(element).click(function (event) { + if (event.metaKey || event.ctrlKey) { + return; + } + else { + if (enabled === true) { + event.preventDefault(); + } + } + }); + }; }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/resizeToContent.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/utill/resizeToContent.directive.js similarity index 100% rename from src/Umbraco.Web.UI.Client/src/common/directives/resizeToContent.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/utill/resizeToContent.directive.js diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/selectOnFocus.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/utill/selectOnFocus.directive.js similarity index 100% rename from src/Umbraco.Web.UI.Client/src/common/directives/selectOnFocus.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/utill/selectOnFocus.directive.js diff --git a/src/Umbraco.Web.UI.Client/src/common/services/mediahelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/mediahelper.service.js index 1b3902122b..0ce434d7c9 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/mediahelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/mediahelper.service.js @@ -125,6 +125,39 @@ function mediaHelper(umbRequestHelper) { return ""; }, + registerFileResolver: function(propertyEditorAlias, func){ + _mediaFileResolvers[propertyEditorAlias] = func; + }, + + resolveFile : function(mediaItem){ + var _props = []; + + //we either have properties raw on the object, or spread out on tabs + if(mediaItem.properties){ + _props = mediaItem.properties; + }else if(mediaItem.tabs){ + _.each(mediaItem.tabs, function(tab){ + if(tab.properties){ + _props.concat(tab.propeties); + } + }); + } + + //we go through our file resolvers to see if any of them matches the editors + var result = ""; + _.each(_mediaFileResolvers, function(resolver, key){ + var property = _.find(_props, function(property){ return property.editor === key; }); + + if(property){ + var file = resolver(property); + if(file){ + result = file; + } + } + }); + + return result; + }, /** * @ngdoc function * @name umbraco.services.mediaHelper#scaleToMaxSize @@ -208,41 +241,8 @@ function mediaHelper(umbRequestHelper) { var lowered = imagePath.toLowerCase(); var ext = lowered.substr(lowered.lastIndexOf(".") + 1); return ("," + Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes + ",").indexOf("," + ext + ",") !== -1; - }, - - registerFileResolver: function(propertyEditorAlias, func){ - _mediaFileResolvers[propertyEditorAlias] = func; - }, - - resolveFile : function(mediaItem){ - var _props = []; - - //we either have properties raw on the object, or spread out on tabs - if(mediaItem.properties){ - _props = mediaItem.properties; - }else if(mediaItem.tabs){ - _.each(mediaItem.tabs, function(tab){ - if(tab.properties){ - _props.concat(tab.propeties); - } - }); - } - - //we go through our file resolvers to see if any of them matches the editors - var result = ""; - _.each(_mediaFileResolvers, function(resolver, key){ - var property = _.find(_props, function(property){ return property.editor === key; }); - - if(property){ - var file = resolver(property); - if(file){ - result = file; - } - } - }); - - return result; } + }; } angular.module('umbraco.services').factory('mediaHelper', mediaHelper); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/common/main.controller.js b/src/Umbraco.Web.UI.Client/src/controllers/main.controller.js similarity index 100% rename from src/Umbraco.Web.UI.Client/src/views/common/main.controller.js rename to src/Umbraco.Web.UI.Client/src/controllers/main.controller.js diff --git a/src/Umbraco.Web.UI.Client/src/views/common/navigation.controller.js b/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js similarity index 100% rename from src/Umbraco.Web.UI.Client/src/views/common/navigation.controller.js rename to src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js diff --git a/src/Umbraco.Web.UI.Client/src/views/common/search.controller.js b/src/Umbraco.Web.UI.Client/src/controllers/search.controller.js similarity index 100% rename from src/Umbraco.Web.UI.Client/src/views/common/search.controller.js rename to src/Umbraco.Web.UI.Client/src/controllers/search.controller.js diff --git a/src/Umbraco.Web.UI.Client/src/install.loader.js b/src/Umbraco.Web.UI.Client/src/install.loader.js new file mode 100644 index 0000000000..bf4a8e787d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/install.loader.js @@ -0,0 +1,21 @@ +yepnope({ + + load: [ + 'lib/jquery/jquery-2.0.3.min.js', + + /* 1.1.5 */ + 'lib/angular/1.1.5/angular.min.js', + 'lib/angular/1.1.5/angular-cookies.min.js', + 'lib/angular/1.1.5/angular-mobile.min.js', + 'lib/angular/1.1.5/angular-mocks.js', + 'lib/angular/1.1.5/angular-sanitize.min.js', + 'lib/underscore/underscore.js', + 'js/umbraco.installer.js' + ], + + complete: function () { + jQuery(document).ready(function () { + angular.bootstrap(document, ['umbraco.install']); + }); + } +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/installer/installer.controller.js b/src/Umbraco.Web.UI.Client/src/installer/installer.controller.js new file mode 100644 index 0000000000..30bf96a329 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/installer/installer.controller.js @@ -0,0 +1,32 @@ +angular.module("umbraco.install").controller("Umbraco.InstallerController", + function($scope, installerService){ + + $scope.stepIndex = 0; + //comment this out if you just want to see tips + installerService.init(); + + //uncomment this to see tips + //installerService.switchToFeedback(); + + $scope.installer = installerService.status; + + $scope.forward = function(){ + installerService.forward(); + }; + + $scope.backward = function(){ + installerService.backward(); + }; + + $scope.install = function(){ + installerService.install(); + }; + + $scope.gotoStep = function(step){ + installerService.gotoNamedStep(step); + }; + + $scope.restart = function () { + installerService.gotoStep(0); + }; +}); diff --git a/src/Umbraco.Web.UI.Client/src/installer/installer.html b/src/Umbraco.Web.UI.Client/src/installer/installer.html new file mode 100644 index 0000000000..39a822a896 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/installer/installer.html @@ -0,0 +1,24 @@ + + + + + + + + Install Umbraco + + + + + + + +
+
+
+ +
+ + + + diff --git a/src/Umbraco.Web.UI.Client/src/installer/installer.service.js b/src/Umbraco.Web.UI.Client/src/installer/installer.service.js new file mode 100644 index 0000000000..e74e90eddf --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/installer/installer.service.js @@ -0,0 +1,320 @@ +angular.module("umbraco.install").factory('installerService', function($rootScope, $q, $timeout, $http, $location, $log){ + + var _status = { + index: 0, + current: undefined, + steps: undefined, + loading: true, + progress: "100%" + }; + + var factTimer = undefined; + var _installerModel = { + installId: undefined, + instructions: { + } + }; + + //add to umbraco installer facts here + var facts = ['Umbraco helped millions of people watch a man jump from the edge of space', + 'Over 250.000 websites are currently powered by Umbraco', + 'On an average day, more then 1000 people download Umbraco', + 'umbraco.tv is the premier source of Umbraco video tutorials to get you started', + 'You can find the worlds friendliest CMS community at our.umbraco.org', + 'You can become a certified Umbraco developer by attending one of the official courses', + 'Umbraco works really well on tablets', + 'You have 100% control over your markup and design when crafting a website in Umbraco', + 'Umbraco is the best of both worlds: 100% free and open source, and backed by a professional and profitable company', + "There's a pretty big chance, you've visited a website powered by Umbraco today", + "'Umbraco-spotting' is the game of spotting big brands running Umbraco", + "Atleast 2 people have the Umbraco logo tattooed on them", + "'Umbraco' is the danish name for an allen key", + "Has been around since 2005, that's a looong time in IT", + "More than 400 people from all over the world meet each year in Denmark in June for our annual conference CodeGarden", + "While you install Umbraco someone else on the other side of the planet probably does it too", + "You can extend Umbraco without modifying the source code and using either javascript or c#" + ]; + + /** + Returns the description for the step at a given index based on the order of the serverOrder of steps + Since they don't execute on the server in the order that they are displayed in the UI. + */ + function getDescriptionForStepAtIndex(steps, index) { + var sorted = _.sortBy(steps, "serverOrder"); + if (sorted[index]) { + return sorted[index].description; + } + return null; + } + /* Returns the description for the given step name */ + function getDescriptionForStepName(steps, name) { + var found = _.find(steps, function(i) { + return i.name == name; + }); + return (found) ? found.description : null; + } + + //calculates the offset of the progressbar on the installaer + function calculateProgress(steps, next) { + var pct = "100%"; + var f = _.find(steps, function(item, index) { + if(item.name == next){ + pct = Math.floor((index+1 / steps.length * 100)) + "%"; + return true; + }else{ + return false; + } + }); + return pct; + } + + //helpful defaults for the view loading + function resolveView(view){ + + if(view.indexOf(".html") < 0){ + view = view + ".html"; + } + if(view.indexOf("/") < 0){ + view = "views/install/" + view; + } + + return view; + } + + /** Have put this here because we are not referencing our other modules */ + function safeApply (scope, fn) { + if (scope.$$phase || scope.$root.$$phase) { + if (angular.isFunction(fn)) { + fn(); + } + } + else { + if (angular.isFunction(fn)) { + scope.$apply(fn); + } + else { + scope.$apply(); + } + } + } + + var service = { + + status : _status, + //loads the needed steps and sets the intial state + init : function(){ + service.status.loading = true; + if(!_status.all){ + service.getSteps().then(function(response){ + service.status.steps = response.data.steps; + service.status.index = 0; + _installerModel.installId = response.data.installId; + service.findNextStep(); + + $timeout(function(){ + service.status.loading = false; + service.status.configuring = true; + }, 2000); + }); + } + }, + + //loads available packages from our.umbraco.org + getPackages : function(){ + return $http.get(Umbraco.Sys.ServerVariables.installApiBaseUrl + "GetPackages"); + }, + + getSteps : function(){ + return $http.get(Umbraco.Sys.ServerVariables.installApiBaseUrl + "GetSetup"); + }, + + gotoStep : function(index){ + var step = service.status.steps[index]; + step.view = resolveView(step.view); + + if(!step.model){ + step.model = {}; + } + + service.status.index = index; + service.status.current = step; + service.retrieveCurrentStep(); + }, + + gotoNamedStep : function(stepName){ + var step = _.find(service.status.steps, function(s, index){ + if (s.view && s.name === stepName) { + service.status.index = index; + return true; + } + return false; + }); + + step.view = resolveView(step.view); + if(!step.model){ + step.model = {}; + } + service.retrieveCurrentStep(); + service.status.current = step; + }, + + /** + Finds the next step containing a view. If one is found it stores it as the current step + and retreives the step information and returns it, otherwise returns null . + */ + findNextStep : function(){ + var step = _.find(service.status.steps, function(s, index){ + if(s.view && index >= service.status.index){ + service.status.index = index; + return true; + } + return false; + }); + + if (step) { + if (step.view.indexOf(".html") < 0) { + step.view = step.view + ".html"; + } + + if (step.view.indexOf("/") < 0) { + step.view = "views/install/" + step.view; + } + + if (!step.model) { + step.model = {}; + } + + service.status.current = step; + service.retrieveCurrentStep(); + + //returns the next found step + return step; + } + else { + //there are no more steps found containing a view so return null + return null; + } + }, + + storeCurrentStep : function(){ + _installerModel.instructions[service.status.current.name] = service.status.current.model; + }, + + retrieveCurrentStep : function(){ + if(_installerModel.instructions[service.status.current.name]){ + service.status.current.model = _installerModel.instructions[service.status.current.name]; + } + }, + + /** Moves the installer forward to the next view, if there are not more views than the installation will commence */ + forward : function(){ + service.storeCurrentStep(); + service.status.index++; + var found = service.findNextStep(); + if (!found) { + //no more steps were found so start the installation process + service.install(); + } + }, + + backwards : function(){ + service.storeCurrentStep(); + service.gotoStep(service.status.index--); + }, + + install : function(){ + service.storeCurrentStep(); + service.switchToFeedback(); + + service.status.feedback = getDescriptionForStepAtIndex(service.status.steps, 0); + service.status.progress = 0; + + function processInstallStep(){ + $http.post(Umbraco.Sys.ServerVariables.installApiBaseUrl + "PostPerformInstall", + _installerModel).then(function(response){ + if(!response.data.complete){ + + //progress feedback + service.status.progress = calculateProgress(service.status.steps, response.data.nextStep); + + if(response.data.view){ + //set the current view and model to whatever the process returns, the view is responsible for retriggering install(); + var v = resolveView(response.data.view); + service.status.current = {view: v, model: response.data.model}; + + //turn off loading bar and feedback + service.switchToConfiguration(); + } + else { + var desc = getDescriptionForStepName(service.status.steps, response.data.nextStep); + if (desc) { + service.status.feedback = desc; + } + processInstallStep(); + } + } + else { + service.complete(); + } + }, function(err){ + //this is where we handle installer error + var v = err.data.view ? resolveView(err.data.view) : resolveView("error"); + var model = err.data.model ? err.data.model : err.data; + + service.status.current = {view: v, model: model}; + service.switchToConfiguration(); + }); + } + processInstallStep(); + }, + + randomFact: function () { + safeApply($rootScope, function() { + service.status.fact = facts[_.random(facts.length - 1)]; + }); + }, + + switchToFeedback : function(){ + service.status.current = undefined; + service.status.loading = true; + service.status.configuring = false; + + //initial fact + service.randomFact(); + + //timed facts + factTimer = window.setInterval(function(){ + service.randomFact(); + },6000); + }, + + switchToConfiguration : function(){ + service.status.loading = false; + service.status.configuring = true; + service.status.feedback = undefined; + service.status.fact = undefined; + + if(factTimer){ + clearInterval(factTimer); + } + }, + + complete : function(){ + + service.status.progress = "100%"; + service.status.done = true; + service.status.feedback = "Redirecting you to Umbraco, please wait"; + service.status.loading = false; + + if(factTimer){ + clearInterval(factTimer); + } + + $timeout(function(){ + window.location.href = Umbraco.Sys.ServerVariables.umbracoBaseUrl; + }, 1500); + } + }; + + return service; +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/continueinstall.html b/src/Umbraco.Web.UI.Client/src/installer/steps/continueinstall.html new file mode 100644 index 0000000000..28fa1d3fff --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/continueinstall.html @@ -0,0 +1,13 @@ +
+

Continue Umbraco Installation

+

+ You see this screen because your Umbraco installation did not complete correctly. +

+

+ Simply click continue below to be guided through the rest of the installation process. +

+ +

+ +

+
diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/database.controller.js b/src/Umbraco.Web.UI.Client/src/installer/steps/database.controller.js new file mode 100644 index 0000000000..81d35145e2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/database.controller.js @@ -0,0 +1,25 @@ +angular.module("umbraco.install").controller("Umbraco.Installer.DataBaseController", function($scope, $http, installerService){ + + $scope.checking = false; + $scope.validateAndForward = function(){ + if(!$scope.checking && this.myForm.$valid){ + $scope.checking = true; + var model = installerService.status.current.model; + + $http.post(Umbraco.Sys.ServerVariables.installApiBaseUrl + "PostValidateDatabaseConnection", + model).then(function(response){ + + if(response.data === "true"){ + installerService.forward(); + }else{ + $scope.invalidDbDns = true; + } + + $scope.checking = false; + }, function(){ + $scope.invalidDbDns = true; + $scope.checking = false; + }); + } + }; +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/database.html b/src/Umbraco.Web.UI.Client/src/installer/steps/database.html new file mode 100644 index 0000000000..4f123d96f8 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/database.html @@ -0,0 +1,132 @@ +
+ +

Configure your database

+

+ Enter connection and authentication details for the database you want to install Umbraco on +

+ +
+ + +
+ What type of database do you use? + +
+ +
+
+ + +
+

Great!, no need to configure anything then, you simply click the continue button below to continue to the next step

+
+ + + +
+ + What is the exact connectionstring we should use? +
+ +
+ + + Enter a valid database connection string. +
+
+
+ +
+
+ Where do we find your database? +
+
+ +
+ + Enter server domain or IP +
+
+
+ +
+
+ +
+ + Enter the name of the database +
+
+
+
+ + +
+ What credentials are used to access the database? +
+
+ +
+ + Enter the database user name +
+
+
+ +
+
+ +
+ + Enter the database password +
+
+
+ +
+
+ +
+
+
+
+ +
+
+
+ + + + Validating your database connection... + + + + Could not connect to database + +
+
+
+ + +
+
diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/error.html b/src/Umbraco.Web.UI.Client/src/installer/steps/error.html new file mode 100644 index 0000000000..234f86e1a4 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/error.html @@ -0,0 +1,9 @@ +
+

Error during installation

+ +

{{installer.current.model.message}}

+ +

See log for full details

+ + +
diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/permissionsreport.html b/src/Umbraco.Web.UI.Client/src/installer/steps/permissionsreport.html new file mode 100644 index 0000000000..3ced6ed678 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/permissionsreport.html @@ -0,0 +1,26 @@ +
+

Your permission settings are not ready for umbraco

+

+ In order to run umbraco, you'll need to update your permission settings. + Detailed information about the correct file & folder permissions for Umbraco can be found + here. +

+

+ The following report list the permissions that are currently failing. Once the permissions are fixed press the 'Go back' button to restart the installation. +

+ +
    +
  • +

    {{category}}

    +
      +
    • + {{item}} +
    • +
    +
  • +
+ +

+ +

+
diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/starterkit.controller.js b/src/Umbraco.Web.UI.Client/src/installer/steps/starterkit.controller.js new file mode 100644 index 0000000000..81958ddd55 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/starterkit.controller.js @@ -0,0 +1,12 @@ +angular.module("umbraco.install").controller("Umbraco.Installer.PackagesController", function ($scope, installerService) { + + installerService.getPackages().then(function (response) { + $scope.packages = response.data; + }); + + $scope.setPackageAndContinue = function (pckId) { + installerService.status.current.model = pckId; + installerService.forward(); + }; + +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/starterkit.html b/src/Umbraco.Web.UI.Client/src/installer/steps/starterkit.html new file mode 100644 index 0000000000..7347023c9d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/starterkit.html @@ -0,0 +1,20 @@ +
+

Install a starter website

+ +

+ Installing a starter website helps you learn how Umbraco works, and gives you a solid + and simple foundation to build on top of. +

+ + + + + No thanks, I do not want to install a starter website + +
diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/upgrade.html b/src/Umbraco.Web.UI.Client/src/installer/steps/upgrade.html new file mode 100644 index 0000000000..3638f7f37a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/upgrade.html @@ -0,0 +1,14 @@ +
+

Upgrading Umbraco

+

+ Welcome to the Umbraco installer. You see this screen because your Umbraco installation needs a quick upgrade of its database and files, + it is completely harmless and will simply ensure your website is kept as fast, secure and uptodate as possible. +

+

+ Simply click continue below to be guided through the rest of the upgrade +

+ +

+ +

+
diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/user.controller.js b/src/Umbraco.Web.UI.Client/src/installer/steps/user.controller.js new file mode 100644 index 0000000000..6c689a3166 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/user.controller.js @@ -0,0 +1,24 @@ +angular.module("umbraco.install").controller("Umbraco.Install.UserController", function($scope, installerService) { + + $scope.passwordPattern = /.*/; + if ($scope.installer.current.model.minNonAlphaNumericLength > 0) { + var exp = ""; + for (var i = 0; i < $scope.installer.current.model.minNonAlphaNumericLength; i++) { + exp += ".*[\\W].*"; + } + //replace duplicates + exp = exp.replace(".*.*", ".*"); + $scope.passwordPattern = new RegExp(exp); + } + + $scope.validateAndInstall = function(){ + installerService.install(); + }; + + $scope.validateAndForward = function(){ + if(this.myForm.$valid){ + installerService.forward(); + } + }; + +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/user.html b/src/Umbraco.Web.UI.Client/src/installer/steps/user.html new file mode 100644 index 0000000000..0b8b44124a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/user.html @@ -0,0 +1,56 @@ +
+

Install Umbraco 7

+ +

Enter your name, email and password to install Umbraco 7 with its default settings, alternatively you can customize your installation

+ +
+ +
+
+
+
+ +
+ +
+
+ +
+ +
+ + Your email will be used as your login +
+
+ +
+ +
+ + At least {{installer.current.model.minCharLength}} characters long + + At least {{installer.current.model.minNonAlphaNumericLength}} symbol{{installer.current.model.minNonAlphaNumericLength > 1 ? 's' : ''}} + +
+
+ +
+
+ + + + Customize +
+
+ +
+
+
+
+ +
\ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/version7upgradereport.html b/src/Umbraco.Web.UI.Client/src/installer/steps/version7upgradereport.html new file mode 100644 index 0000000000..df1e58d737 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/version7upgradereport.html @@ -0,0 +1,22 @@ +
+

Major version upgrade from {{installer.current.model.currentVersion}} to {{installer.current.model.newVersion}}

+

There were {{installer.current.model.errors.length}} issues detected

+

+ The following compatibility issues were found. If you continue all non-compatible property editors will be converted to a Readonly/Label. + You will be able to change the property editor to a compatible type manually by editing the data type after installation. +

+

+ Otherwise if you choose not to proceed you will need to fix the errors listed below. + Refer to v{{installer.current.model.newVersion}} upgrade instructions for full details. +

+ +
    +
  • + {{item}} +
  • +
+ +

+ +

+
\ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/less/installer.less b/src/Umbraco.Web.UI.Client/src/less/installer.less new file mode 100644 index 0000000000..1e306419c4 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/installer.less @@ -0,0 +1,280 @@ +// Core variables and mixins +@import "fonts.less"; // Loading fonts +@import "variables.less"; // Modify this for custom colors, font-sizes, etc +@import "mixins.less"; +@import "buttons.less"; +@import "forms.less"; + +// Grid system and page structure +@import "../../lib/bootstrap/less/scaffolding.less"; +@import "../../lib/bootstrap/less/grid.less"; +@import "../../lib/bootstrap/less/layouts.less"; + +@import "../../lib/bootstrap/less/thumbnails.less"; +@import "../../lib/bootstrap/less/media.less"; + + +[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak { + display: none !important; +} + + +html { + background: url('../img/installer.jpg') no-repeat center center fixed; + -webkit-background-size: cover; + -moz-background-size: cover; + -o-background-size: cover; + background-size: cover; +} + +body { + margin: 0; + padding: 0; + height: 100%; + width: 100%; + + font-family: @baseFontFamily; + font-size: @baseFontSize; + line-height: @baseLineHeight; + color: @textColor; + + vertical-align: center; + text-align: center; +} + +#logo{ + position: absolute; + top: 20px; + left: 20px; + opacity: 0.8; + z-index: 777; +} + +#installer{ + margin: auto; + background: white; + width: 750px; + height: 600px; + text-align: left; + padding: 30px; + overflow:hidden; + z-index: 667; +} + +#overlay{ + position: absolute; + top: 0; + right: 0; + left: 0; + bottom: 0; + background: @blackLight; + z-index: 666; +} + +.loading #overlay{ + opacity: 0.5; + display: block !important; +} + + +#fact{ + color: #fff; + text-shadow: 0px 0px 4px black; + font-size: 25px; + text-align: left; + line-height: 35px; + z-index: 667; + height: 600px; + width: 750px; +} + +#fact h2{ + font-size: 35px; + border-bottom: 1px solid white; + padding-bottom: 10px; + margin-bottom: 20px; + color: white; +} + +#fact a{color: white;} + +#feedback{ + color: #fff; + text-shadow: 0px 0px 4px black; + font-size: 14px; + text-align: center; + line-height: 20px; + z-index: 667; + bottom: 20px; + right: 0; + left: 0; + height: 25px; + position: absolute; +} + + +h1{ + border-bottom: 1px solid @grayLighter; + padding-bottom: 10px; + color: @gray; +} + +.error h1, .error .message, span.error{ color: @red;} + +legend{font-size: 14px; font-weight: bold} + +input.ng-dirty.ng-invalid{border-color: #b94a48; color: #b94a48;} +.disabled{ + opacity: 0.6; +} + +.controls{ + text-align: left +} + +.controls small{display: block; color: @gray;} + +.absolute-center { + margin: auto; + position: absolute; + top: 0; left: 0; bottom: 0; right: 0; +} + +.fade-hide, .fade-show { + -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; + -moz-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; + -o-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; + transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; +} +.fade-hide { + opacity:1; +} +.fade-hide.fade-hide-active { + opacity:0; +} +.fade-show { + opacity:0; +} +.fade-show.fade-show-active { + opacity:1; +} + + +.umb-loader{ +background-color: white; +margin-top:0; +margin-left:-100%; +-moz-animation-name:bounce_loadingProgressG; +-moz-animation-duration:1s; +-moz-animation-iteration-count:infinite; +-moz-animation-timing-function:linear; +-webkit-animation-name:bounce_loadingProgressG; +-webkit-animation-duration:1s; +-webkit-animation-iteration-count:infinite; +-webkit-animation-timing-function:linear; +-ms-animation-name:bounce_loadingProgressG; +-ms-animation-duration:1s; +-ms-animation-iteration-count:infinite; +-ms-animation-timing-function:linear; +-o-animation-name:bounce_loadingProgressG; +-o-animation-duration:1s; +-o-animation-iteration-count:infinite; +-o-animationtiming-function:linear; +animation-name:bounce_loadingProgressG; +animation-duration:1s; +animation-iteration-count:infinite; +animation-timing-function:linear; +width:100%; +height: 5px; +} + + + @-moz-keyframes bounce_loadingProgressG{ + 0%{ + margin-left:-100%; + } + + 100%{ + margin-left:100%; + } + + } + + @-webkit-keyframes bounce_loadingProgressG{ + 0%{ + margin-left:-100%; + } + + 100%{ + margin-left:100%; + } + + } + + @-ms-keyframes bounce_loadingProgressG{ + 0%{ + margin-left:-100%; + } + + 100%{ + margin-left:100%; + } + + } + + @-o-keyframes bounce_loadingProgressG{ + 0%{ + margin-left:-100%; + } + + 100%{ + margin-left:100%; + } + + } + + @keyframes bounce_loadingProgressG{ + 0%{ + margin-left:-100%; + } + 100%{ + margin-left:100%; + } +} + +//loader defaults +.umb-loader-container, .umb-loader-done{ + height: 3px; + position: absolute; + bottom: 0; + left: 0; + overflow: hidden; + width: 0%; + z-index: 777; +} + +.umb-loader-done{ + right: 0%; + background: white; +} + + +.permissions-report { + overflow:auto; + height:320px; + margin:0; + display:block; + padding:0; +} +.permissions-report > li { + list-style:none; +} +.permissions-report h4 { + margin:7px; +} + +.upgrade-report { + overflow:auto; + height:280px; + display:block; +} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/less/panel.less b/src/Umbraco.Web.UI.Client/src/less/panel.less index 64e5cf7bc5..18eefc402a 100644 --- a/src/Umbraco.Web.UI.Client/src/less/panel.less +++ b/src/Umbraco.Web.UI.Client/src/less/panel.less @@ -166,7 +166,7 @@ } .umb-tab-buttons{padding-left: 240px;} -.umb-tab-pane.with-buttons{padding-bottom: 90px} +.umb-tab-pane{padding-bottom: 90px} .tab-content{overflow: visible; } diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/treepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/treepicker.controller.js index da55d80529..e655664cfb 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/treepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/treepicker.controller.js @@ -127,13 +127,6 @@ angular.module("umbraco").controller("Umbraco.Dialogs.TreePickerController", $scope.multiSubmit = function (result) { - - if(dialogOptions.minNumber || dialogOptions.maxNumber){ - if(dialogOptions.minNumber > result.length || dialogOptions.maxNumber < result.length){ - return; - } - } - entityResource.getByIds(result, $scope.entityType).then(function (ents) { $scope.submit(ents); }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/treepicker.html b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/treepicker.html index 13da681dd9..af4832c105 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/treepicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/treepicker.html @@ -49,8 +49,7 @@