diff --git a/.gitignore b/.gitignore index 04e39e37c9..32e7c297db 100644 --- a/.gitignore +++ b/.gitignore @@ -131,3 +131,4 @@ src/*.boltdata/ src/umbraco.sln.ide/* build/UmbracoCms.*/ src/.vs/ +src/Umbraco.Web.UI/umbraco/js/install.loader.js diff --git a/build/NuSpecs/UmbracoCms.Core.nuspec b/build/NuSpecs/UmbracoCms.Core.nuspec index cba4de7cbe..6852b04510 100644 --- a/build/NuSpecs/UmbracoCms.Core.nuspec +++ b/build/NuSpecs/UmbracoCms.Core.nuspec @@ -26,7 +26,7 @@ - + diff --git a/build/NuSpecs/tools/Dashboard.config.install.xdt b/build/NuSpecs/tools/Dashboard.config.install.xdt index 605bbb825e..197f9c1b6f 100644 --- a/build/NuSpecs/tools/Dashboard.config.install.xdt +++ b/build/NuSpecs/tools/Dashboard.config.install.xdt @@ -62,13 +62,6 @@ views/dashboard/default/startupdashboardintro.html - - - - - views/dashboard/ChangePassword.html - - diff --git a/build/NuSpecs/tools/install.core.ps1 b/build/NuSpecs/tools/install.core.ps1 index 1fa9352be7..6213957221 100644 --- a/build/NuSpecs/tools/install.core.ps1 +++ b/build/NuSpecs/tools/install.core.ps1 @@ -63,6 +63,7 @@ if ($project) { if(Test-Path $umbracoBinFolder\MiniProfiler.dll) { Remove-Item $umbracoBinFolder\MiniProfiler.dll -Force -Confirm:$false } if(Test-Path $umbracoBinFolder\MySql.Data.dll) { Remove-Item $umbracoBinFolder\MySql.Data.dll -Force -Confirm:$false } if(Test-Path $umbracoBinFolder\Newtonsoft.Json.dll) { Remove-Item $umbracoBinFolder\Newtonsoft.Json.dll -Force -Confirm:$false } + if(Test-Path $umbracoBinFolder\System.Web.Helpers.dll) { Remove-Item $umbracoBinFolder\System.Web.Helpers.dll -Force -Confirm:$false } if(Test-Path $umbracoBinFolder\System.Net.Http.Formatting.dll) { Remove-Item $umbracoBinFolder\System.Net.Http.Formatting.dll -Force -Confirm:$false } if(Test-Path $umbracoBinFolder\System.Web.Http.dll) { Remove-Item $umbracoBinFolder\System.Web.Http.dll -Force -Confirm:$false } if(Test-Path $umbracoBinFolder\System.Web.Http.WebHost.dll) { Remove-Item $umbracoBinFolder\System.Web.Http.WebHost.dll -Force -Confirm:$false } diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index b10616ec36..7aba769888 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -11,5 +11,5 @@ using System.Resources; [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyFileVersion("7.3.1")] -[assembly: AssemblyInformationalVersion("7.3.1")] \ No newline at end of file +[assembly: AssemblyFileVersion("7.4.0")] +[assembly: AssemblyInformationalVersion("7.4.0")] \ No newline at end of file diff --git a/src/Umbraco.Core/ActionsResolver.cs b/src/Umbraco.Core/ActionsResolver.cs index ff34f62c60..2da95a3416 100644 --- a/src/Umbraco.Core/ActionsResolver.cs +++ b/src/Umbraco.Core/ActionsResolver.cs @@ -11,7 +11,7 @@ namespace Umbraco.Core /// /// A resolver to return all IAction objects /// - internal sealed class ActionsResolver : LazyManyObjectsResolverBase + public sealed class ActionsResolver : LazyManyObjectsResolverBase { /// /// Constructor diff --git a/src/Umbraco.Core/ApplicationContext.cs b/src/Umbraco.Core/ApplicationContext.cs index 4aef011d05..a05abe2a50 100644 --- a/src/Umbraco.Core/ApplicationContext.cs +++ b/src/Umbraco.Core/ApplicationContext.cs @@ -283,7 +283,12 @@ namespace Umbraco.Core { var configStatus = ConfigurationStatus; var currentVersion = UmbracoVersion.GetSemanticVersion(); - var ok = configStatus == currentVersion; + + var ok = + //we are not configured if this is null + string.IsNullOrWhiteSpace(configStatus) == false + //they must match + && configStatus == currentVersion; if (ok) { @@ -308,8 +313,9 @@ namespace Umbraco.Core return ok; } - catch + catch (Exception ex) { + LogHelper.Error("Error determining if application is configured, returning false", ex); return false; } diff --git a/src/Umbraco.Core/Constants-Applications.cs b/src/Umbraco.Core/Constants-Applications.cs index ee44e60dda..12f7076fc4 100644 --- a/src/Umbraco.Core/Constants-Applications.cs +++ b/src/Umbraco.Core/Constants-Applications.cs @@ -43,7 +43,7 @@ public const string Users = "users"; /// - /// Application alias for the users section. + /// Application alias for the forms section. /// public const string Forms = "forms"; } diff --git a/src/Umbraco.Core/Constants-PropertyEditors.cs b/src/Umbraco.Core/Constants-PropertyEditors.cs index 1a1eda2ae2..2f7d247b36 100644 --- a/src/Umbraco.Core/Constants-PropertyEditors.cs +++ b/src/Umbraco.Core/Constants-PropertyEditors.cs @@ -315,13 +315,13 @@ namespace Umbraco.Core public const string TextboxAlias = "Umbraco.Textbox"; /// - /// Guid for the Textbox multiple datatype. + /// Guid for the Textarea datatype. /// [Obsolete("GUIDs are no longer used to reference Property Editors, use the Alias constant instead. This will be removed in future versions")] public const string TextboxMultiple = "67DB8357-EF57-493E-91AC-936D305E0F2A"; /// - /// Alias for the Textbox multiple datatype. + /// Alias for the Textarea datatype. /// public const string TextboxMultipleAlias = "Umbraco.TextboxMultiple"; diff --git a/src/Umbraco.Core/Constants-PropertyTypeGroups.cs b/src/Umbraco.Core/Constants-PropertyTypeGroups.cs new file mode 100644 index 0000000000..5dabe90029 --- /dev/null +++ b/src/Umbraco.Core/Constants-PropertyTypeGroups.cs @@ -0,0 +1,31 @@ +namespace Umbraco.Core +{ + public static partial class Constants + { + /// + /// Defines the identifiers for property-type groups conventions that are used within the Umbraco core. + /// + public static class PropertyTypeGroups + { + /// + /// Guid for a Image PropertyTypeGroup object. + /// + public const string Image = "79ED4D07-254A-42CF-8FA9-EBE1C116A596"; + + /// + /// Guid for a File PropertyTypeGroup object. + /// + public const string File = "50899F9C-023A-4466-B623-ABA9049885FE"; + + /// + /// Guid for a Image PropertyTypeGroup object. + /// + public const string Contents = "79995FA2-63EE-453C-A29B-2E66F324CDBE"; + + /// + /// Guid for a Image PropertyTypeGroup object. + /// + public const string Membership = "0756729D-D665-46E3-B84A-37ACEAA614F8"; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Constants-Web.cs b/src/Umbraco.Core/Constants-Web.cs index 13dee96b97..60fba0ae40 100644 --- a/src/Umbraco.Core/Constants-Web.cs +++ b/src/Umbraco.Core/Constants-Web.cs @@ -1,4 +1,7 @@ -namespace Umbraco.Core +using System; +using System.ComponentModel; + +namespace Umbraco.Core { public static partial class Constants { @@ -15,6 +18,8 @@ /// /// The auth cookie name /// + [Obsolete("DO NOT USE THIS, USE ISecuritySection.AuthCookieName, this will be removed in future versions")] + [EditorBrowsable(EditorBrowsableState.Never)] public const string AuthCookieName = "UMB_UCONTEXT"; } diff --git a/src/Umbraco.Core/IO/SystemDirectories.cs b/src/Umbraco.Core/IO/SystemDirectories.cs index 433d01c206..98fdb01ff4 100644 --- a/src/Umbraco.Core/IO/SystemDirectories.cs +++ b/src/Umbraco.Core/IO/SystemDirectories.cs @@ -154,7 +154,7 @@ namespace Umbraco.Core.IO { get { - return IOHelper.ReturnPath("umbracoWebservicesPath", "~/umbraco/webservices"); + return IOHelper.ReturnPath("umbracoWebservicesPath", Umbraco.EnsureEndsWith("/") + "webservices"); } } diff --git a/src/Umbraco.Core/Models/ContentExtensions.cs b/src/Umbraco.Core/Models/ContentExtensions.cs index 88b6308c29..e91996e32a 100644 --- a/src/Umbraco.Core/Models/ContentExtensions.cs +++ b/src/Umbraco.Core/Models/ContentExtensions.cs @@ -560,8 +560,7 @@ namespace Umbraco.Core.Models //Additional thumbnails configured as prevalues on the DataType if (thumbnailSizes != null) { - var sep = (thumbnailSizes.Contains("") == false && thumbnailSizes.Contains(",")) ? ',' : ';'; - foreach (var thumb in thumbnailSizes.Split(sep)) + foreach (var thumb in thumbnailSizes.Split(new[] { ";", "," }, StringSplitOptions.RemoveEmptyEntries)) { int thumbSize; if (thumb != "" && int.TryParse(thumb, out thumbSize)) diff --git a/src/Umbraco.Core/Models/ContentTypeBase.cs b/src/Umbraco.Core/Models/ContentTypeBase.cs index 46127f8131..a83defb7b5 100644 --- a/src/Umbraco.Core/Models/ContentTypeBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeBase.cs @@ -538,6 +538,18 @@ namespace Umbraco.Core.Models /// Name of the to remove public void RemovePropertyGroup(string propertyGroupName) { + // if no group exists with that name, do nothing + var group = PropertyGroups[propertyGroupName]; + if (group == null) return; + + // re-assign the group's properties to no group + foreach (var property in group.PropertyTypes) + { + property.PropertyGroupId = new Lazy(() => 0); + _propertyTypes.Add(property); + } + + // actually remove the group PropertyGroups.RemoveItem(propertyGroupName); OnPropertyChanged(PropertyGroupCollectionSelector); } diff --git a/src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs b/src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs index f57d6683a2..0dc95a8987 100644 --- a/src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs +++ b/src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs @@ -4,6 +4,7 @@ using AutoMapper; using Umbraco.Core.Models.Mapping; using Umbraco.Core.Models.Membership; +using Umbraco.Core.Security; namespace Umbraco.Core.Models.Identity { @@ -24,6 +25,18 @@ namespace Umbraco.Core.Models.Identity .ForMember(user => user.UserTypeAlias, expression => expression.MapFrom(user => user.UserType.Alias)) .ForMember(user => user.AccessFailedCount, expression => expression.MapFrom(user => user.FailedPasswordAttempts)) .ForMember(user => user.AllowedSections, expression => expression.MapFrom(user => user.AllowedSections.ToArray())); + + config.CreateMap() + .ConstructUsing((BackOfficeIdentityUser user) => new UserData(Guid.NewGuid().ToString("N"))) //this is the 'session id' + .ForMember(detail => detail.Id, opt => opt.MapFrom(user => user.Id)) + .ForMember(detail => detail.AllowedApplications, opt => opt.MapFrom(user => user.AllowedSections)) + .ForMember(detail => detail.RealName, opt => opt.MapFrom(user => user.Name)) + .ForMember(detail => detail.Roles, opt => opt.MapFrom(user => new[] { user.UserTypeAlias })) + .ForMember(detail => detail.StartContentNode, opt => opt.MapFrom(user => user.StartContentId)) + .ForMember(detail => detail.StartMediaNode, opt => opt.MapFrom(user => user.StartMediaId)) + .ForMember(detail => detail.Username, opt => opt.MapFrom(user => user.UserName)) + .ForMember(detail => detail.Culture, opt => opt.MapFrom(user => user.Culture)) + .ForMember(detail => detail.SessionId, opt => opt.MapFrom(user => user.SecurityStamp.IsNullOrWhiteSpace() ? Guid.NewGuid().ToString("N") : user.SecurityStamp)); } private string GetPasswordHash(string storedPass) diff --git a/src/Umbraco.Core/Models/Language.cs b/src/Umbraco.Core/Models/Language.cs index ef8ebbf931..b23bbfb52a 100644 --- a/src/Umbraco.Core/Models/Language.cs +++ b/src/Umbraco.Core/Models/Language.cs @@ -64,7 +64,7 @@ namespace Umbraco.Core.Models [IgnoreDataMember] public CultureInfo CultureInfo { - get { return CultureInfo.CreateSpecificCulture(IsoCode); } + get { return CultureInfo.GetCultureInfo(IsoCode); } } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/MigrationDto.cs b/src/Umbraco.Core/Models/Rdbms/MigrationDto.cs index 5ab339fe01..1a76895e90 100644 --- a/src/Umbraco.Core/Models/Rdbms/MigrationDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/MigrationDto.cs @@ -11,7 +11,7 @@ namespace Umbraco.Core.Models.Rdbms internal class MigrationDto { [Column("id")] - [PrimaryKeyColumn(AutoIncrement = true)] + [PrimaryKeyColumn(AutoIncrement = true, IdentitySeed = 100)] public int Id { get; set; } [Column("name")] diff --git a/src/Umbraco.Core/Models/UserExtensions.cs b/src/Umbraco.Core/Models/UserExtensions.cs index bd670f3836..5b9f63cf48 100644 --- a/src/Umbraco.Core/Models/UserExtensions.cs +++ b/src/Umbraco.Core/Models/UserExtensions.cs @@ -2,6 +2,7 @@ using System.Globalization; using System.Linq; using System.Threading; +using Umbraco.Core.Models.Identity; using Umbraco.Core.Models.Membership; using Umbraco.Core.Services; diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs index b3242af4a8..761bda29c6 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs @@ -108,7 +108,7 @@ namespace Umbraco.Core.Persistence.Migrations.Initial _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -21, Trashed = false, ParentId = -1, UserId = 0, Level = 0, Path = "-1,-21", SortOrder = 0, UniqueId = new Guid("BF7C7CBC-952F-4518-97A2-69E9C7B33842"), Text = "Recycle Bin", NodeObjectType = new Guid(Constants.ObjectTypes.MediaRecycleBin), CreateDate = DateTime.Now }); _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -92, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-92", SortOrder = 35, UniqueId = new Guid("f0bc4bfb-b499-40d6-ba86-058885a5178c"), Text = "Label", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -90, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-90", SortOrder = 34, UniqueId = new Guid("84c6b441-31df-4ffe-b67e-67d5bc3ae65a"), Text = "Upload", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -89, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-89", SortOrder = 33, UniqueId = new Guid("c6bac0dd-4ab9-45b1-8e30-e4b619ee5da3"), Text = "Textbox multiple", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -89, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-89", SortOrder = 33, UniqueId = new Guid("c6bac0dd-4ab9-45b1-8e30-e4b619ee5da3"), Text = "Textarea", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -88, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-88", SortOrder = 32, UniqueId = new Guid("0cc0eba1-9960-42c9-bf9b-60e150b429ae"), Text = "Textstring", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -87, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-87", SortOrder = 4, UniqueId = new Guid("ca90c950-0aff-4e72-b976-a30b1ac57dad"), Text = "Richtext editor", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -51, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-51", SortOrder = 2, UniqueId = new Guid("2e6d3631-066e-44b8-aec4-96f09099b2b5"), Text = "Numeric", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); @@ -178,11 +178,11 @@ namespace Umbraco.Core.Persistence.Migrations.Initial private void CreateCmsPropertyTypeGroupData() { - _database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 3, ContentTypeNodeId = 1032, Text = "Image", SortOrder = 1, UniqueId = new Guid("79ED4D07-254A-42CF-8FA9-EBE1C116A596") }); - _database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 4, ContentTypeNodeId = 1033, Text = "File", SortOrder = 1, UniqueId = new Guid("50899F9C-023A-4466-B623-ABA9049885FE") }); - _database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 5, ContentTypeNodeId = 1031, Text = "Contents", SortOrder = 1, UniqueId = new Guid("79995FA2-63EE-453C-A29B-2E66F324CDBE") }); + _database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 3, ContentTypeNodeId = 1032, Text = "Image", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.Image) }); + _database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 4, ContentTypeNodeId = 1033, Text = "File", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.File) }); + _database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 5, ContentTypeNodeId = 1031, Text = "Contents", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.Contents) }); //membership property group - _database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 11, ContentTypeNodeId = 1044, Text = "Membership", SortOrder = 1, UniqueId = new Guid("0756729D-D665-46E3-B84A-37ACEAA614F8") }); + _database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 11, ContentTypeNodeId = 1044, Text = "Membership", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.Membership) }); } private void CreateCmsPropertyTypeData() diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/AddUniqueIdPropertyTypeGroupColumn.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/AddUniqueIdPropertyTypeGroupColumn.cs index bb09faaa7f..2a164b6e0d 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/AddUniqueIdPropertyTypeGroupColumn.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/AddUniqueIdPropertyTypeGroupColumn.cs @@ -46,19 +46,19 @@ ON cmsContentType.nodeId = umbracoNode.id")) // see BaseDataCreation... built-in groups have their own guids if (data.grId == 3) { - guid = new Guid("79ED4D07-254A-42CF-8FA9-EBE1C116A596"); + guid = new Guid(Constants.PropertyTypeGroups.Image); } else if (data.grId == 4) { - guid = new Guid("50899F9C-023A-4466-B623-ABA9049885FE"); + guid = new Guid(Constants.PropertyTypeGroups.File); } else if (data.grId == 5) { - guid = new Guid("79995FA2-63EE-453C-A29B-2E66F324CDBE"); + guid = new Guid(Constants.PropertyTypeGroups.Contents); } else if (data.grId == 11) { - guid = new Guid("0756729D-D665-46E3-B84A-37ACEAA614F8"); + guid = new Guid(Constants.PropertyTypeGroups.Membership); } else { @@ -69,7 +69,7 @@ ON cmsContentType.nodeId = umbracoNode.id")) } // set the Unique Id to the one we've generated - Update.Table("cmsPropertyTypeGroup").Set(new { uniqueID = guid }).Where(new { id = data.ptId }); + Update.Table("cmsPropertyTypeGroup").Set(new { uniqueID = guid }).Where(new { id = data.grId }); } } } diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeTwo/EnsureMigrationsTableIdentityIsCorrect.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeTwo/EnsureMigrationsTableIdentityIsCorrect.cs index ce70cdc407..91b4bd6438 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeTwo/EnsureMigrationsTableIdentityIsCorrect.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeTwo/EnsureMigrationsTableIdentityIsCorrect.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Models.Rdbms; @@ -18,13 +19,28 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeTw public override void Up() { - Delete.FromTable("umbracoMigration").AllRows(); - var migrations = Context.Database.Fetch(new Sql().Select("*").From(SqlSyntax)); - foreach (var migration in migrations) + // Due to the delayed execution of migrations, we have to wrap this code in Execute.Code to ensure the previous + // migration steps (esp. creating the migrations table) have completed before trying to fetch migrations from + // this table. + List migrations = null; + Execute.Code(db => { - Insert.IntoTable("umbracoMigration") - .Row(new {name = migration.Name, createDate = migration.CreateDate, version = migration.Version}); - } + migrations = Context.Database.Fetch(new Sql().Select("*").From(SqlSyntax)); + return string.Empty; + }); + Delete.FromTable("umbracoMigration").AllRows(); + Execute.Code(database => + { + if (migrations != null) + { + foreach (var migration in migrations) + { + database.Insert("umbracoMigration", "id", true, + new {name = migration.Name, createDate = migration.CreateDate, version = migration.Version}); + } + } + return string.Empty; + }); } public override void Down() diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixZeroOne/UpdatePropertyTypesAndGroups.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixZeroOne/UpdatePropertyTypesAndGroups.cs index eebad824ff..ff71aec142 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixZeroOne/UpdatePropertyTypesAndGroups.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixZeroOne/UpdatePropertyTypesAndGroups.cs @@ -31,31 +31,31 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSixZeroOne //Fetch all PropertyTypes that belongs to a PropertyTypeGroup //NOTE: We are writing the full query because we've added a column to the PropertyTypeDto in later versions so one of the columns // won't exist yet - var propertyTypes = database.Fetch("SELECT * FROM cmsPropertyType WHERE propertyTypeGroupId > 0"); + var propertyTypes = database.Fetch("SELECT * FROM cmsPropertyType WHERE propertyTypeGroupId > 0"); var propertyGroups = database.Fetch("WHERE id > 0"); foreach (var propertyType in propertyTypes) { // get the PropertyTypeGroup of the current PropertyType, skip if not found - var propertyTypeGroup = propertyGroups.FirstOrDefault(x => x.Id == propertyType.PropertyTypeGroupId); + var propertyTypeGroup = propertyGroups.FirstOrDefault(x => x.Id == propertyType.propertyTypeGroupId); if (propertyTypeGroup == null) continue; // if the PropretyTypeGroup belongs to the same content type as the PropertyType, then fine - if (propertyTypeGroup.ContentTypeNodeId == propertyType.ContentTypeId) continue; + if (propertyTypeGroup.ContentTypeNodeId == propertyType.contentTypeId) continue; // else we want to assign the PropertyType to a proper PropertyTypeGroup // ie one that does belong to the same content - look for it var okPropertyTypeGroup = propertyGroups.FirstOrDefault(x => x.Text == propertyTypeGroup.Text && // same name - x.ContentTypeNodeId == propertyType.ContentTypeId); // but for proper content type + x.ContentTypeNodeId == propertyType.contentTypeId); // but for proper content type if (okPropertyTypeGroup == null) { // does not exist, create a new PropertyTypeGroup, var propertyGroup = new PropertyTypeGroupDto { - ContentTypeNodeId = propertyType.ContentTypeId, + ContentTypeNodeId = propertyType.contentTypeId, Text = propertyTypeGroup.Text, SortOrder = propertyTypeGroup.SortOrder }; @@ -66,14 +66,14 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSixZeroOne propertyGroups.Add(propertyGroup); // update the PropertyType to use the new PropertyTypeGroup - propertyType.PropertyTypeGroupId = id; + propertyType.propertyTypeGroupId = id; } else { // exists, update PropertyType to use the PropertyTypeGroup - propertyType.PropertyTypeGroupId = okPropertyTypeGroup.Id; + propertyType.propertyTypeGroupId = okPropertyTypeGroup.Id; } - database.Update(propertyType); + database.Update("cmsPropertyType", "id", propertyType); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs index 6d8f73725c..a6d9b2b58b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs @@ -172,12 +172,12 @@ namespace Umbraco.Core.Persistence.Repositories ((ContentType)entity).AddingEntity(); PersistNewBaseContentType(entity); - PersisteTemplates(entity, false); + PersistTemplates(entity, false); entity.ResetDirtyProperties(); } - protected void PersisteTemplates(IContentType entity, bool clearAll) + protected void PersistTemplates(IContentType entity, bool clearAll) { // remove and insert, if required Database.Delete("WHERE contentTypeNodeId = @Id", new { Id = entity.Id }); @@ -225,7 +225,7 @@ namespace Umbraco.Core.Persistence.Repositories } PersistUpdatedBaseContentType(entity); - PersisteTemplates(entity, true); + PersistTemplates(entity, true); entity.ResetDirtyProperties(); } diff --git a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs index 7213dfc92a..a2f7c841fc 100644 --- a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs @@ -701,14 +701,16 @@ namespace Umbraco.Core.Persistence.Repositories public RenderingEngine DetermineTemplateRenderingEngine(ITemplate template) { var engine = _templateConfig.DefaultRenderingEngine; - - if (template.Content.IsNullOrWhiteSpace() == false && MasterPageHelper.IsMasterPageSyntax(template.Content)) + var viewHelper = new ViewHelper(_viewsFileSystem); + if (!viewHelper.ViewExists(template)) { - //there is a design but its definitely a webforms design - return RenderingEngine.WebForms; + if (template.Content.IsNullOrWhiteSpace() == false && MasterPageHelper.IsMasterPageSyntax(template.Content)) + { + //there is a design but its definitely a webforms design and we haven't got a MVC view already for it + return RenderingEngine.WebForms; + } } - var viewHelper = new ViewHelper(_viewsFileSystem); var masterPageHelper = new MasterPageHelper(_masterpagesFileSystem); switch (engine) diff --git a/src/Umbraco.Core/Persistence/UmbracoDatabase.cs b/src/Umbraco.Core/Persistence/UmbracoDatabase.cs index 1d60879f97..9541a1cac0 100644 --- a/src/Umbraco.Core/Persistence/UmbracoDatabase.cs +++ b/src/Umbraco.Core/Persistence/UmbracoDatabase.cs @@ -117,7 +117,7 @@ namespace Umbraco.Core.Persistence public override void OnException(Exception x) { - _logger.Info(x.StackTrace); + _logger.Error("Database exception occurred", x); base.OnException(x); } diff --git a/src/Umbraco.Core/Security/AuthenticationExtensions.cs b/src/Umbraco.Core/Security/AuthenticationExtensions.cs index e332cb6dca..1c7c544ed8 100644 --- a/src/Umbraco.Core/Security/AuthenticationExtensions.cs +++ b/src/Umbraco.Core/Security/AuthenticationExtensions.cs @@ -2,6 +2,7 @@ using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; +using System.ComponentModel; using System.Linq; using System.Net.Http; using System.Net.Http.Headers; @@ -15,7 +16,6 @@ using Microsoft.Owin; using Newtonsoft.Json; using Umbraco.Core.Configuration; using Umbraco.Core.Models.Membership; -using Microsoft.Owin; using Umbraco.Core.Logging; namespace Umbraco.Core.Security @@ -157,9 +157,6 @@ namespace Umbraco.Core.Security return new HttpContextWrapper(http).GetCurrentIdentity(authenticateRequestIfNotFound); } - /// - /// This clears the forms authentication cookie - /// public static void UmbracoLogout(this HttpContextBase http) { if (http == null) throw new ArgumentNullException("http"); @@ -170,6 +167,8 @@ namespace Umbraco.Core.Security /// This clears the forms authentication cookie for webapi since cookies are handled differently /// /// + [Obsolete("Use OWIN IAuthenticationManager.SignOut instead")] + [EditorBrowsable(EditorBrowsableState.Never)] public static void UmbracoLogoutWebApi(this HttpResponseMessage response) { if (response == null) throw new ArgumentNullException("response"); @@ -195,11 +194,8 @@ namespace Umbraco.Core.Security response.Headers.AddCookies(new[] { authCookie, prevCookie, extLoginCookie }); } - /// - /// This adds the forms authentication cookie for webapi since cookies are handled differently - /// - /// - /// + [Obsolete("Use WebSecurity.SetPrincipalForRequest")] + [EditorBrowsable(EditorBrowsableState.Never)] public static FormsAuthenticationTicket UmbracoLoginWebApi(this HttpResponseMessage response, IUser user) { if (response == null) throw new ArgumentNullException("response"); @@ -250,26 +246,29 @@ namespace Umbraco.Core.Security if (http == null) throw new ArgumentNullException("http"); new HttpContextWrapper(http).UmbracoLogout(); } - + /// - /// Renews the Umbraco authentication ticket + /// This will force ticket renewal in the OWIN pipeline /// /// /// public static bool RenewUmbracoAuthTicket(this HttpContextBase http) { if (http == null) throw new ArgumentNullException("http"); - return RenewAuthTicket(http, - UmbracoConfig.For.UmbracoSettings().Security.AuthCookieName, - UmbracoConfig.For.UmbracoSettings().Security.AuthCookieDomain, - //Umbraco has always persisted it's original cookie for 1 day so we'll keep it that way - 1440); + http.Items["umbraco-force-auth"] = true; + return true; } + /// + /// This will force ticket renewal in the OWIN pipeline + /// + /// + /// internal static bool RenewUmbracoAuthTicket(this HttpContext http) { if (http == null) throw new ArgumentNullException("http"); - return new HttpContextWrapper(http).RenewUmbracoAuthTicket(); + http.Items["umbraco-force-auth"] = true; + return true; } /// @@ -390,8 +389,7 @@ namespace Umbraco.Core.Security //ensure there's def an expired cookie http.Response.Cookies.Add(new HttpCookie(c) { Expires = DateTime.Now.AddYears(-1) }); } - } - + } } private static FormsAuthenticationTicket GetAuthTicket(this HttpContextBase http, string cookieName) @@ -432,51 +430,6 @@ namespace Umbraco.Core.Security return FormsAuthentication.Decrypt(formsCookie); } - /// - /// Renews the forms authentication ticket & cookie - /// - /// - /// - /// - /// - /// true if there was a ticket to renew otherwise false if there was no ticket - private static bool RenewAuthTicket(this HttpContextBase http, string cookieName, string cookieDomain, int minutesPersisted) - { - if (http == null) throw new ArgumentNullException("http"); - //get the ticket - var ticket = GetAuthTicket(http, cookieName); - //renew the ticket - var renewed = FormsAuthentication.RenewTicketIfOld(ticket); - if (renewed == null) - { - return false; - } - - //get the request cookie to get it's expiry date, - //NOTE: this will never be null becaues we already do this - // check in teh GetAuthTicket. - var formsCookie = http.Request.Cookies[cookieName]; - - //encrypt it - var hash = FormsAuthentication.Encrypt(renewed); - //write it to the response - var cookie = new HttpCookie(cookieName, hash) - { - Expires = DateTime.Now.AddMinutes(minutesPersisted), - Domain = cookieDomain - }; - - if (GlobalSettings.UseSSL) - cookie.Secure = true; - - //ensure http only, this should only be able to be accessed via the server - cookie.HttpOnly = true; - - //rewrite the cooke - http.Response.Cookies.Set(cookie); - return true; - } - /// /// Creates a custom FormsAuthentication ticket with the data specified /// diff --git a/src/Umbraco.Core/Security/BackOfficeCookieAuthenticationProvider.cs b/src/Umbraco.Core/Security/BackOfficeCookieAuthenticationProvider.cs index 5247ea0af4..086d1a77bf 100644 --- a/src/Umbraco.Core/Security/BackOfficeCookieAuthenticationProvider.cs +++ b/src/Umbraco.Core/Security/BackOfficeCookieAuthenticationProvider.cs @@ -1,12 +1,38 @@ +using System; using System.Globalization; +using System.Net.Http.Headers; using System.Threading; using System.Threading.Tasks; +using Microsoft.Owin; using Microsoft.Owin.Security.Cookies; +using Umbraco.Core.Configuration; namespace Umbraco.Core.Security { public class BackOfficeCookieAuthenticationProvider : CookieAuthenticationProvider { + public override void ResponseSignOut(CookieResponseSignOutContext context) + { + base.ResponseSignOut(context); + + //Make sure the definitely all of these cookies are cleared when signing out with cookies + context.Response.Cookies.Append(UmbracoConfig.For.UmbracoSettings().Security.AuthCookieName, "", new CookieOptions + { + Expires = DateTime.Now.AddYears(-1), + Path = "/" + }); + context.Response.Cookies.Append(Constants.Web.PreviewCookieName, "", new CookieOptions + { + Expires = DateTime.Now.AddYears(-1), + Path = "/" + }); + context.Response.Cookies.Append(Constants.Security.BackOfficeExternalCookieName, "", new CookieOptions + { + Expires = DateTime.Now.AddYears(-1), + Path = "/" + }); + } + /// /// Ensures that the culture is set correctly for the current back office user /// diff --git a/src/Umbraco.Core/Security/BackOfficeSignInManager.cs b/src/Umbraco.Core/Security/BackOfficeSignInManager.cs index 85d6a0c715..f1d18b9d0f 100644 --- a/src/Umbraco.Core/Security/BackOfficeSignInManager.cs +++ b/src/Umbraco.Core/Security/BackOfficeSignInManager.cs @@ -53,6 +53,11 @@ namespace Umbraco.Core.Security switch (result) { case SignInStatus.Success: + _logger.WriteCore(TraceEventType.Information, 0, + string.Format( + "User: {0} logged in from IP address {1}", + userName, + _request.RemoteIpAddress), null, null); break; case SignInStatus.LockedOut: _logger.WriteCore(TraceEventType.Information, 0, diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index ba8b51fa5b..7599001618 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -152,6 +152,9 @@ namespace Umbraco.Core.Services { var contentType = FindContentTypeByAlias(contentTypeAlias); var content = new Content(name, parentId, contentType); + var parent = GetById(content.ParentId); + content.Path = string.Concat(parent.IfNotNull(x => x.Path, content.ParentId.ToString()), ",", content.Id); + if (Creating.IsRaisedEventCancelled(new NewEventArgs(content, contentTypeAlias, parentId), this)) { @@ -190,8 +193,11 @@ namespace Umbraco.Core.Services /// public IContent CreateContent(string name, IContent parent, string contentTypeAlias, int userId = 0) { + if (parent == null) throw new ArgumentNullException("parent"); + var contentType = FindContentTypeByAlias(contentTypeAlias); var content = new Content(name, parent, contentType); + content.Path = string.Concat(parent.Path, ",", content.Id); if (Creating.IsRaisedEventCancelled(new NewEventArgs(content, contentTypeAlias, parent), this)) { @@ -225,7 +231,7 @@ namespace Umbraco.Core.Services public IContent CreateContentWithIdentity(string name, int parentId, string contentTypeAlias, int userId = 0) { var contentType = FindContentTypeByAlias(contentTypeAlias); - var content = new Content(name, parentId, contentType); + var content = new Content(name, parentId, contentType); //NOTE: I really hate the notion of these Creating/Created events - they are so inconsistent, I've only just found // out that in these 'WithIdentity' methods, the Saving/Saved events were not fired, wtf. Anyways, they're added now. @@ -276,6 +282,8 @@ namespace Umbraco.Core.Services /// public IContent CreateContentWithIdentity(string name, IContent parent, string contentTypeAlias, int userId = 0) { + if (parent == null) throw new ArgumentNullException("parent"); + var contentType = FindContentTypeByAlias(contentTypeAlias); var content = new Content(name, parent, contentType); diff --git a/src/Umbraco.Core/Services/EntityXmlSerializer.cs b/src/Umbraco.Core/Services/EntityXmlSerializer.cs index 772009e89a..712f98aeb2 100644 --- a/src/Umbraco.Core/Services/EntityXmlSerializer.cs +++ b/src/Umbraco.Core/Services/EntityXmlSerializer.cs @@ -100,6 +100,8 @@ namespace Umbraco.Core.Services xml.Add(new XAttribute("loginName", member.Username)); xml.Add(new XAttribute("email", member.Email)); + + xml.Add(new XAttribute("icon", member.ContentType.Icon)); return xml; } diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs index f7a88b7ac6..431eb52c8d 100644 --- a/src/Umbraco.Core/Services/MediaService.cs +++ b/src/Umbraco.Core/Services/MediaService.cs @@ -65,6 +65,8 @@ namespace Umbraco.Core.Services { var mediaType = FindMediaTypeByAlias(mediaTypeAlias); var media = new Models.Media(name, parentId, mediaType); + var parent = GetById(media.ParentId); + media.Path = string.Concat(parent.IfNotNull(x => x.Path, media.ParentId.ToString()), ",", media.Id); if (Creating.IsRaisedEventCancelled(new NewEventArgs(media, mediaTypeAlias, parentId), this)) { @@ -97,8 +99,12 @@ namespace Umbraco.Core.Services /// public IMedia CreateMedia(string name, IMedia parent, string mediaTypeAlias, int userId = 0) { + if (parent == null) throw new ArgumentNullException("parent"); + var mediaType = FindMediaTypeByAlias(mediaTypeAlias); var media = new Models.Media(name, parent, mediaType); + media.Path = string.Concat(parent.Path, ",", media.Id); + if (Creating.IsRaisedEventCancelled(new NewEventArgs(media, mediaTypeAlias, parent), this)) { media.WasCancelled = true; @@ -186,6 +192,8 @@ namespace Umbraco.Core.Services /// public IMedia CreateMediaWithIdentity(string name, IMedia parent, string mediaTypeAlias, int userId = 0) { + if (parent == null) throw new ArgumentNullException("parent"); + var mediaType = FindMediaTypeByAlias(mediaTypeAlias); var media = new Models.Media(name, parent, mediaType); diff --git a/src/Umbraco.Core/ThreadExtensions.cs b/src/Umbraco.Core/ThreadExtensions.cs new file mode 100644 index 0000000000..68f6959e91 --- /dev/null +++ b/src/Umbraco.Core/ThreadExtensions.cs @@ -0,0 +1,51 @@ +using System.Globalization; +using System.Threading; + +namespace Umbraco.Core +{ + static class ThreadExtensions + { + public static void SanitizeThreadCulture(this Thread thread) + { + // get the current culture + var currentCulture = CultureInfo.CurrentCulture; + + // at the top of any culture should be the invariant culture - find it + // doing an .Equals comparison ensure that we *will* find it and not loop + // endlessly + var invariantCulture = currentCulture; + while (invariantCulture.Equals(CultureInfo.InvariantCulture) == false) + invariantCulture = invariantCulture.Parent; + + // now that invariant culture should be the same object as CultureInfo.InvariantCulture + // yet for some reasons, sometimes it is not - and this breaks anything that loops on + // culture.Parent until a reference equality to CultureInfo.InvariantCulture. See, for + // example, the following code in PerformanceCounterLib.IsCustomCategory: + // + // CultureInfo culture = CultureInfo.CurrentCulture; + // while (culture != CultureInfo.InvariantCulture) + // { + // library = GetPerformanceCounterLib(machine, culture); + // if (library.IsCustomCategory(category)) + // return true; + // culture = culture.Parent; + // } + // + // The reference comparisons never succeeds, hence the loop never ends, and the + // application hangs. + // + // granted, that comparison should probably be a .Equals comparison, but who knows + // how many times the framework assumes that it can do a reference comparison? So, + // better fix the cultures. + + if (ReferenceEquals(invariantCulture, CultureInfo.InvariantCulture)) + return; + + // if we do not have equality, fix cultures by replacing them with a culture with + // the same name, but obtained here and now, with a proper invariant top culture + + thread.CurrentCulture = CultureInfo.GetCultureInfo(thread.CurrentCulture.Name); + thread.CurrentUICulture = CultureInfo.GetCultureInfo(thread.CurrentUICulture.Name); + } + } +} diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index ce7fe9f5a2..997f57b0ca 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -86,7 +86,7 @@ False - ..\packages\MySql.Data.6.9.7\lib\net45\MySql.Data.dll + ..\packages\MySql.Data.6.9.8\lib\net45\MySql.Data.dll ..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll @@ -1290,6 +1290,7 @@ + @@ -1332,6 +1333,9 @@ Constants.cs + + Constants.cs + Constants.cs diff --git a/src/Umbraco.Core/UmbracoApplicationBase.cs b/src/Umbraco.Core/UmbracoApplicationBase.cs index 6aadbd269a..225493b57b 100644 --- a/src/Umbraco.Core/UmbracoApplicationBase.cs +++ b/src/Umbraco.Core/UmbracoApplicationBase.cs @@ -2,6 +2,7 @@ using System.Diagnostics; using System.Linq; using System.Reflection; +using System.Threading; using System.Web; using System.Web.Hosting; using log4net; @@ -64,6 +65,7 @@ namespace Umbraco.Core /// protected void Application_Start(object sender, EventArgs e) { + Thread.CurrentThread.SanitizeThreadCulture(); StartApplication(sender, e); } diff --git a/src/Umbraco.Core/packages.config b/src/Umbraco.Core/packages.config index 56ad180bfd..4ab720b7be 100644 --- a/src/Umbraco.Core/packages.config +++ b/src/Umbraco.Core/packages.config @@ -14,7 +14,7 @@ - + diff --git a/src/Umbraco.Tests/App.config b/src/Umbraco.Tests/App.config index aa8ed6b0a7..33285877e9 100644 --- a/src/Umbraco.Tests/App.config +++ b/src/Umbraco.Tests/App.config @@ -56,7 +56,7 @@ - + @@ -203,4 +203,4 @@ - \ No newline at end of file + diff --git a/src/Umbraco.Tests/Configurations/DashboardSettings/Dashboard.config b/src/Umbraco.Tests/Configurations/DashboardSettings/Dashboard.config index a9ffdf78ee..9d393b0a87 100644 --- a/src/Umbraco.Tests/Configurations/DashboardSettings/Dashboard.config +++ b/src/Umbraco.Tests/Configurations/DashboardSettings/Dashboard.config @@ -28,7 +28,7 @@ - /umbraco/dashboard/ExamineManagement.ascx + dashboard/ExamineManagement.ascx @@ -85,7 +85,7 @@ - /umbraco/dashboard/latestEdits.ascx + dashboard/latestEdits.ascx @@ -104,11 +104,11 @@ views/dashboard/members/membersdashboardintro.html - /umbraco/members/membersearch.ascx + members/membersearch.ascx views/dashboard/members/membersdashboardvideos.html - \ No newline at end of file + diff --git a/src/Umbraco.Tests/Logging/ParallelForwarderTest.cs b/src/Umbraco.Tests/Logging/ParallelForwarderTest.cs index 03df34c420..d347395b1f 100644 --- a/src/Umbraco.Tests/Logging/ParallelForwarderTest.cs +++ b/src/Umbraco.Tests/Logging/ParallelForwarderTest.cs @@ -173,7 +173,8 @@ namespace Umbraco.Tests.Logging Console.WriteLine("Flushed {0} events during shutdown", numberLoggedAfterClose - numberLoggedBeforeClose); } - [Test, Explicit("Long-running")] + [NUnit.Framework.Ignore("Keeps failing very randomly")] + [Test] public void WillShutdownIfBufferCannotBeFlushedFastEnough() { const int testSize = 250; diff --git a/src/Umbraco.Tests/Migrations/SqlScripts/MySqlTotal-480.sql b/src/Umbraco.Tests/Migrations/SqlScripts/MySqlTotal-480.sql index 49af66b93b..96eff1eccb 100644 --- a/src/Umbraco.Tests/Migrations/SqlScripts/MySqlTotal-480.sql +++ b/src/Umbraco.Tests/Migrations/SqlScripts/MySqlTotal-480.sql @@ -482,7 +482,7 @@ ALTER TABLE umbracoUser2NodePermission ADD CONSTRAINT PK_umbracoUser2NodePermiss INSERT INTO umbracoNode (id, trashed, parentID, nodeUser, level, path, sortOrder, uniqueID, text, nodeObjectType, createDate) VALUES (-92, 0, -1, 0, 11, '-1,-92', 37, 'f0bc4bfb-b499-40d6-ba86-058885a5178c', 'Label', '30a2a501-1978-4ddb-a57b-f7efed43ba3c', '2004/09/30 14:01:49.920'), (-90, 0, -1, 0, 11, '-1,-90', 35, '84c6b441-31df-4ffe-b67e-67d5bc3ae65a', 'Upload', '30a2a501-1978-4ddb-a57b-f7efed43ba3c', '2004/09/30 14:01:49.920'), - (-89, 0, -1, 0, 11, '-1,-89', 34, 'c6bac0dd-4ab9-45b1-8e30-e4b619ee5da3', 'Textbox multiple', '30a2a501-1978-4ddb-a57b-f7efed43ba3c', '2004/09/30 14:01:49.920'), + (-89, 0, -1, 0, 11, '-1,-89', 34, 'c6bac0dd-4ab9-45b1-8e30-e4b619ee5da3', 'Textarea', '30a2a501-1978-4ddb-a57b-f7efed43ba3c', '2004/09/30 14:01:49.920'), (-88, 0, -1, 0, 11, '-1,-88', 33, '0cc0eba1-9960-42c9-bf9b-60e150b429ae', 'Textstring', '30a2a501-1978-4ddb-a57b-f7efed43ba3c', '2004/09/30 14:01:49.920'), (-87, 0, -1, 0, 11, '-1,-87', 32, 'ca90c950-0aff-4e72-b976-a30b1ac57dad', 'Richtext editor', '30a2a501-1978-4ddb-a57b-f7efed43ba3c', '2004/09/30 14:01:49.920'), (-51, 0, -1, 0, 11, '-1,-51', 4, '2e6d3631-066e-44b8-aec4-96f09099b2b5', 'Numeric', '30a2a501-1978-4ddb-a57b-f7efed43ba3c', '2004/09/30 14:01:49.920'), diff --git a/src/Umbraco.Tests/Migrations/SqlScripts/SqlCe-SchemaAndData-4110.sql b/src/Umbraco.Tests/Migrations/SqlScripts/SqlCe-SchemaAndData-4110.sql index e4c3d47ee2..ee157be2a7 100644 Binary files a/src/Umbraco.Tests/Migrations/SqlScripts/SqlCe-SchemaAndData-4110.sql and b/src/Umbraco.Tests/Migrations/SqlScripts/SqlCe-SchemaAndData-4110.sql differ diff --git a/src/Umbraco.Tests/Migrations/SqlScripts/SqlServerTotal-480.sql b/src/Umbraco.Tests/Migrations/SqlScripts/SqlServerTotal-480.sql index c729a7ffbc..d4923489b2 100644 --- a/src/Umbraco.Tests/Migrations/SqlScripts/SqlServerTotal-480.sql +++ b/src/Umbraco.Tests/Migrations/SqlScripts/SqlServerTotal-480.sql @@ -624,7 +624,7 @@ ALTER TABLE [umbracoNode] DROP CONSTRAINT [FK_umbracoNode_umbracoNode] SET IDENTITY_INSERT [umbracoNode] ON INSERT INTO [umbracoNode] ([id], [trashed], [parentID], [nodeUser], [level], [path], [sortOrder], [uniqueID], [text], [nodeObjectType], [createDate]) VALUES (-92, 0, -1, 0, 11, N'-1,-92', 37, 'f0bc4bfb-b499-40d6-ba86-058885a5178c', N'Label', '30a2a501-1978-4ddb-a57b-f7efed43ba3c', '20040930 14:01:49.920') INSERT INTO [umbracoNode] ([id], [trashed], [parentID], [nodeUser], [level], [path], [sortOrder], [uniqueID], [text], [nodeObjectType], [createDate]) VALUES (-90, 0, -1, 0, 11, N'-1,-90', 35, '84c6b441-31df-4ffe-b67e-67d5bc3ae65a', N'Upload', '30a2a501-1978-4ddb-a57b-f7efed43ba3c', '20040930 14:01:49.920') -INSERT INTO [umbracoNode] ([id], [trashed], [parentID], [nodeUser], [level], [path], [sortOrder], [uniqueID], [text], [nodeObjectType], [createDate]) VALUES (-89, 0, -1, 0, 11, N'-1,-89', 34, 'c6bac0dd-4ab9-45b1-8e30-e4b619ee5da3', N'Textbox multiple', '30a2a501-1978-4ddb-a57b-f7efed43ba3c', '20040930 14:01:49.920') +INSERT INTO [umbracoNode] ([id], [trashed], [parentID], [nodeUser], [level], [path], [sortOrder], [uniqueID], [text], [nodeObjectType], [createDate]) VALUES (-89, 0, -1, 0, 11, N'-1,-89', 34, 'c6bac0dd-4ab9-45b1-8e30-e4b619ee5da3', N'Textarea', '30a2a501-1978-4ddb-a57b-f7efed43ba3c', '20040930 14:01:49.920') INSERT INTO [umbracoNode] ([id], [trashed], [parentID], [nodeUser], [level], [path], [sortOrder], [uniqueID], [text], [nodeObjectType], [createDate]) VALUES (-88, 0, -1, 0, 11, N'-1,-88', 33, '0cc0eba1-9960-42c9-bf9b-60e150b429ae', N'Textstring', '30a2a501-1978-4ddb-a57b-f7efed43ba3c', '20040930 14:01:49.920') INSERT INTO [umbracoNode] ([id], [trashed], [parentID], [nodeUser], [level], [path], [sortOrder], [uniqueID], [text], [nodeObjectType], [createDate]) VALUES (-87, 0, -1, 0, 11, N'-1,-87', 32, 'ca90c950-0aff-4e72-b976-a30b1ac57dad', N'Richtext editor', '30a2a501-1978-4ddb-a57b-f7efed43ba3c', '20040930 14:01:49.920') INSERT INTO [umbracoNode] ([id], [trashed], [parentID], [nodeUser], [level], [path], [sortOrder], [uniqueID], [text], [nodeObjectType], [createDate]) VALUES (-51, 0, -1, 0, 11, N'-1,-51', 4, '2e6d3631-066e-44b8-aec4-96f09099b2b5', N'Numeric', '30a2a501-1978-4ddb-a57b-f7efed43ba3c', '20040930 14:01:49.920') diff --git a/src/Umbraco.Tests/Mvc/RenderIndexActionSelectorAttributeTests.cs b/src/Umbraco.Tests/Mvc/RenderIndexActionSelectorAttributeTests.cs new file mode 100644 index 0000000000..3f327d3155 --- /dev/null +++ b/src/Umbraco.Tests/Mvc/RenderIndexActionSelectorAttributeTests.cs @@ -0,0 +1,173 @@ +using System; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using System.Web; +using System.Web.Mvc; +using System.Web.Routing; +using Moq; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Logging; +using Umbraco.Core.Profiling; +using Umbraco.Web; +using Umbraco.Web.Models; +using Umbraco.Web.Mvc; +using Umbraco.Web.Routing; +using Umbraco.Web.Security; + +namespace Umbraco.Tests.Mvc +{ + [TestFixture] + public class RenderIndexActionSelectorAttributeTests + { + private MethodInfo GetRenderMvcControllerIndexMethodFromCurrentType(Type currType) + { + return currType.GetMethods().Single(x => + { + if (x.Name != "Index") return false; + if (x.ReturnParameter == null || x.ReturnParameter.ParameterType != typeof (ActionResult)) return false; + var p = x.GetParameters(); + if (p.Length != 1) return false; + if (p[0].ParameterType != typeof (RenderModel)) return false; + return true; + }); + } + + [Test] + public void Matches_Default_Index() + { + var attr = new RenderIndexActionSelectorAttribute(); + var req = new RequestContext(); + var appCtx = new ApplicationContext( + CacheHelper.CreateDisabledCacheHelper(), + new ProfilingLogger(Mock.Of(), Mock.Of())); + var umbCtx = UmbracoContext.EnsureContext( + Mock.Of(), + appCtx, + new Mock(null, null).Object, + Mock.Of(), + Enumerable.Empty(), + true); + var ctrl = new MatchesDefaultIndexController(umbCtx); + var controllerCtx = new ControllerContext(req, ctrl); + var result = attr.IsValidForRequest(controllerCtx, + GetRenderMvcControllerIndexMethodFromCurrentType(ctrl.GetType())); + + Assert.IsTrue(result); + } + + [Test] + public void Matches_Overriden_Index() + { + var attr = new RenderIndexActionSelectorAttribute(); + var req = new RequestContext(); + var appCtx = new ApplicationContext( + CacheHelper.CreateDisabledCacheHelper(), + new ProfilingLogger(Mock.Of(), Mock.Of())); + var umbCtx = UmbracoContext.EnsureContext( + Mock.Of(), + appCtx, + new Mock(null, null).Object, + Mock.Of(), + Enumerable.Empty(), + true); + var ctrl = new MatchesOverriddenIndexController(umbCtx); + var controllerCtx = new ControllerContext(req, ctrl); + var result = attr.IsValidForRequest(controllerCtx, + GetRenderMvcControllerIndexMethodFromCurrentType(ctrl.GetType())); + + Assert.IsTrue(result); + } + + [Test] + public void Matches_Custom_Index() + { + var attr = new RenderIndexActionSelectorAttribute(); + var req = new RequestContext(); + var appCtx = new ApplicationContext( + CacheHelper.CreateDisabledCacheHelper(), + new ProfilingLogger(Mock.Of(), Mock.Of())); + var umbCtx = UmbracoContext.EnsureContext( + Mock.Of(), + appCtx, + new Mock(null, null).Object, + Mock.Of(), + Enumerable.Empty(), + true); + var ctrl = new MatchesCustomIndexController(umbCtx); + var controllerCtx = new ControllerContext(req, ctrl); + var result = attr.IsValidForRequest(controllerCtx, + GetRenderMvcControllerIndexMethodFromCurrentType(ctrl.GetType())); + + Assert.IsFalse(result); + } + + [Test] + public void Matches_Async_Index_Same_Signature() + { + var attr = new RenderIndexActionSelectorAttribute(); + var req = new RequestContext(); + var appCtx = new ApplicationContext( + CacheHelper.CreateDisabledCacheHelper(), + new ProfilingLogger(Mock.Of(), Mock.Of())); + var umbCtx = UmbracoContext.EnsureContext( + Mock.Of(), + appCtx, + new Mock(null, null).Object, + Mock.Of(), + Enumerable.Empty(), + true); + var ctrl = new MatchesAsyncIndexController(umbCtx); + var controllerCtx = new ControllerContext(req, ctrl); + var result = attr.IsValidForRequest(controllerCtx, + GetRenderMvcControllerIndexMethodFromCurrentType(ctrl.GetType())); + + Assert.IsFalse(result); + } + + public class MatchesDefaultIndexController : RenderMvcController + { + public MatchesDefaultIndexController(UmbracoContext umbracoContext) : base(umbracoContext) + { + } + } + + public class MatchesOverriddenIndexController : RenderMvcController + { + public MatchesOverriddenIndexController(UmbracoContext umbracoContext) : base(umbracoContext) + { + } + + public override ActionResult Index(RenderModel model) + { + return base.Index(model); + } + } + + public class MatchesCustomIndexController : RenderMvcController + { + public MatchesCustomIndexController(UmbracoContext umbracoContext) : base(umbracoContext) + { + } + + public ActionResult Index(RenderModel model, int page) + { + return base.Index(model); + } + } + + public class MatchesAsyncIndexController : RenderMvcController + { + public MatchesAsyncIndexController(UmbracoContext umbracoContext) : base(umbracoContext) + { + } + + public new async Task Index(RenderModel model) + { + return await Task.FromResult(base.Index(model)); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Mvc/ViewDataDictionaryExtensionTests.cs b/src/Umbraco.Tests/Mvc/ViewDataDictionaryExtensionTests.cs index c13818a03c..4c06d30796 100644 --- a/src/Umbraco.Tests/Mvc/ViewDataDictionaryExtensionTests.cs +++ b/src/Umbraco.Tests/Mvc/ViewDataDictionaryExtensionTests.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using System.Collections.Generic; using System.Text; using System.Web.Mvc; using NUnit.Framework; diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index 41cb5d60d8..8d86e806a6 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -141,6 +141,20 @@ namespace Umbraco.Tests.Services Assert.AreEqual(20, contentService.CountDescendants(parent.Id)); } + [Test] + public void GetAncestors_Returns_Empty_List_When_Path_Is_Null() + { + // Arrange + var contentService = ServiceContext.ContentService; + + // Act + var current = new Mock(); + var res = contentService.GetAncestors(current.Object); + + // Assert + Assert.IsEmpty(res); + } + [Test] public void Tags_For_Entity_Are_Not_Exposed_Via_Tag_Api_When_Content_Is_Recycled() { diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs index 2c0f77a7aa..742205904f 100644 --- a/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs @@ -1173,6 +1173,8 @@ namespace Umbraco.Tests.Services { var service = ServiceContext.ContentTypeService; var basePage = (IContentType)MockedContentTypes.CreateBasicContentType(); + basePage.AddPropertyGroup("Content"); + basePage.AddPropertyGroup("Meta"); service.Save(basePage); var authorPropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext, "author") @@ -1184,6 +1186,16 @@ namespace Umbraco.Tests.Services DataTypeDefinitionId = -88 }; var authorAdded = basePage.AddPropertyType(authorPropertyType, "Content"); + var titlePropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext, "title") + { + Name = "Title", + Description = "", + Mandatory = false, + SortOrder = 1, + DataTypeDefinitionId = -88 + }; + var titleAdded = basePage.AddPropertyType(authorPropertyType, "Meta"); + service.Save(basePage); basePage = service.GetContentType(basePage.Id); diff --git a/src/Umbraco.Tests/Templates/TemplateRepositoryTests.cs b/src/Umbraco.Tests/Templates/TemplateRepositoryTests.cs new file mode 100644 index 0000000000..c385799411 --- /dev/null +++ b/src/Umbraco.Tests/Templates/TemplateRepositoryTests.cs @@ -0,0 +1,109 @@ +using Moq; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Umbraco.Core; +using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Repositories; +using Umbraco.Core.Persistence.SqlSyntax; +using Umbraco.Core.Persistence.UnitOfWork; + +namespace Umbraco.Tests.Templates +{ + [TestFixture] + public class TemplateRepositoryTests + { + private readonly Mock _unitOfWorkMock = new Mock(); + private readonly Mock _cacheMock = new Mock(); + private TemplateRepository _templateRepository; + private readonly Mock _viewFileSystemMock = new Mock(); + private readonly Mock _masterpageFileSystemMock = new Mock(); + private readonly Mock _templateConfigMock = new Mock(); + + [SetUp] + public void Setup() + { + var loggerMock = new Mock(); + var sqlSyntaxMock = new Mock(); + _templateRepository = new TemplateRepository(_unitOfWorkMock.Object, _cacheMock.Object, loggerMock.Object, sqlSyntaxMock.Object, _masterpageFileSystemMock.Object, _viewFileSystemMock.Object, _templateConfigMock.Object); + + } + + [Test] + public void DetermineTemplateRenderingEngine_Returns_MVC_When_ViewFile_Exists_And_Content_Has_Webform_Markup() + { + // Project in MVC mode + _templateConfigMock.Setup(x => x.DefaultRenderingEngine).Returns(RenderingEngine.Mvc); + + // Template has masterpage content + var templateMock = new Mock(); + templateMock.Setup(x => x.Alias).Returns("Something"); + templateMock.Setup(x => x.Content).Returns(""); + + // but MVC View already exists + _viewFileSystemMock.Setup(x => x.FileExists(It.IsAny())).Returns(true); + + var res = _templateRepository.DetermineTemplateRenderingEngine(templateMock.Object); + Assert.AreEqual(RenderingEngine.Mvc, res); + } + + [Test] + public void DetermineTemplateRenderingEngine_Returns_WebForms_When_ViewFile_Doesnt_Exist_And_Content_Has_Webform_Markup() + { + // Project in MVC mode + _templateConfigMock.Setup(x => x.DefaultRenderingEngine).Returns(RenderingEngine.Mvc); + + // Template has masterpage content + var templateMock = new Mock(); + templateMock.Setup(x => x.Alias).Returns("Something"); + templateMock.Setup(x => x.Content).Returns(""); + + // MVC View doesn't exist + _viewFileSystemMock.Setup(x => x.FileExists(It.IsAny())).Returns(false); + + var res = _templateRepository.DetermineTemplateRenderingEngine(templateMock.Object); + Assert.AreEqual(RenderingEngine.WebForms, res); + } + + [Test] + public void DetermineTemplateRenderingEngine_Returns_WebForms_When_MasterPage_Exists_And_In_Mvc_Mode() + { + // Project in MVC mode + _templateConfigMock.Setup(x => x.DefaultRenderingEngine).Returns(RenderingEngine.Mvc); + + var templateMock = new Mock(); + templateMock.Setup(x => x.Alias).Returns("Something"); + + // but masterpage already exists + _viewFileSystemMock.Setup(x => x.FileExists(It.IsAny())).Returns(false); + _masterpageFileSystemMock.Setup(x => x.FileExists(It.IsAny())).Returns(true); + + var res = _templateRepository.DetermineTemplateRenderingEngine(templateMock.Object); + Assert.AreEqual(RenderingEngine.WebForms, res); + } + + [Test] + public void DetermineTemplateRenderingEngine_Returns_Mvc_When_ViewPage_Exists_And_In_Webforms_Mode() + { + // Project in WebForms mode + _templateConfigMock.Setup(x => x.DefaultRenderingEngine).Returns(RenderingEngine.WebForms); + + var templateMock = new Mock(); + templateMock.Setup(x => x.Alias).Returns("Something"); + + // but MVC View already exists + _viewFileSystemMock.Setup(x => x.FileExists(It.IsAny())).Returns(true); + _masterpageFileSystemMock.Setup(x => x.FileExists(It.IsAny())).Returns(false); + + var res = _templateRepository.DetermineTemplateRenderingEngine(templateMock.Object); + Assert.AreEqual(RenderingEngine.Mvc, res); + } + + } +} diff --git a/src/Umbraco.Tests/TestHelpers/FakeHttpContextFactory.cs b/src/Umbraco.Tests/TestHelpers/FakeHttpContextFactory.cs index 31bdb616b0..7f38bf61a1 100644 --- a/src/Umbraco.Tests/TestHelpers/FakeHttpContextFactory.cs +++ b/src/Umbraco.Tests/TestHelpers/FakeHttpContextFactory.cs @@ -8,6 +8,7 @@ using System.Web; using System.Web.Routing; using Moq; using Umbraco.Core; +using Umbraco.Core.Configuration; namespace Umbraco.Tests.TestHelpers { @@ -60,7 +61,7 @@ namespace Umbraco.Tests.TestHelpers //Cookie collection var cookieCollection = new HttpCookieCollection(); - cookieCollection.Add(new HttpCookie(Constants.Web.AuthCookieName, "FBA996E7-D6BE-489B-B199-2B0F3D2DD826")); + cookieCollection.Add(new HttpCookie("UMB_UCONTEXT", "FBA996E7-D6BE-489B-B199-2B0F3D2DD826")); //Request var requestMock = new Mock(); diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 9674971d3d..f3ed7d225b 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -181,6 +181,7 @@ + @@ -362,6 +363,7 @@ + diff --git a/src/Umbraco.Web.UI.Client/bower.json b/src/Umbraco.Web.UI.Client/bower.json index cc67e06ae1..45abd9972a 100644 --- a/src/Umbraco.Web.UI.Client/bower.json +++ b/src/Umbraco.Web.UI.Client/bower.json @@ -22,7 +22,7 @@ "rgrove-lazyload": "*", "bootstrap-social": "~4.8.0", "jquery": "2.0.3", - "jquery-ui": "1.10.3", + "jquery-ui": "1.11.4", "angular-dynamic-locale": "~0.1.27", "ng-file-upload": "~7.3.8", "tinymce": "~4.1.10", diff --git a/src/Umbraco.Web.UI.Client/gruntFile.js b/src/Umbraco.Web.UI.Client/gruntFile.js index c830ee3433..2fb9b026a5 100644 --- a/src/Umbraco.Web.UI.Client/gruntFile.js +++ b/src/Umbraco.Web.UI.Client/gruntFile.js @@ -476,12 +476,11 @@ module.exports = function (grunt) { files: ['css/font-awesome.min.css', 'fonts/*'] }, "jquery": { - keepExpandedHierarchy: false, - files: ['dist/jquery.min.js', 'dist/jquery.min.map'] - }, + files: ['jquery.min.js', 'jquery.min.map'] + }, 'jquery-ui': { keepExpandedHierarchy: false, - files: ['ui/minified/jquery-ui.min.js'] + files: ['jquery-ui.min.js'] }, 'tinymce': { files: ['plugins/**', 'themes/**', 'tinymce.min.js'] diff --git a/src/Umbraco.Web.UI.Client/lib/jquery-ui-touch-punch/jquery.ui.touch-punch.js b/src/Umbraco.Web.UI.Client/lib/jquery-ui-touch-punch/jquery.ui.touch-punch.js new file mode 100644 index 0000000000..b395b9a372 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/jquery-ui-touch-punch/jquery.ui.touch-punch.js @@ -0,0 +1,180 @@ +/*! + * jQuery UI Touch Punch 0.2.3 + * + * Copyright 2011–2014, Dave Furfero + * Dual licensed under the MIT or GPL Version 2 licenses. + * + * Depends: + * jquery.ui.widget.js + * jquery.ui.mouse.js + */ +(function ($) { + + // Detect touch support + $.support.touch = 'ontouchend' in document; + + // Ignore browsers without touch support + if (!$.support.touch) { + return; + } + + var mouseProto = $.ui.mouse.prototype, + _mouseInit = mouseProto._mouseInit, + _mouseDestroy = mouseProto._mouseDestroy, + touchHandled; + + /** + * Simulate a mouse event based on a corresponding touch event + * @param {Object} event A touch event + * @param {String} simulatedType The corresponding mouse event + */ + function simulateMouseEvent (event, simulatedType) { + + // Ignore multi-touch events + if (event.originalEvent.touches.length > 1) { + return; + } + + event.preventDefault(); + + var touch = event.originalEvent.changedTouches[0], + simulatedEvent = document.createEvent('MouseEvents'); + + // Initialize the simulated mouse event using the touch event's coordinates + simulatedEvent.initMouseEvent( + simulatedType, // type + true, // bubbles + true, // cancelable + window, // view + 1, // detail + touch.screenX, // screenX + touch.screenY, // screenY + touch.clientX, // clientX + touch.clientY, // clientY + false, // ctrlKey + false, // altKey + false, // shiftKey + false, // metaKey + 0, // button + null // relatedTarget + ); + + // Dispatch the simulated event to the target element + event.target.dispatchEvent(simulatedEvent); + } + + /** + * Handle the jQuery UI widget's touchstart events + * @param {Object} event The widget element's touchstart event + */ + mouseProto._touchStart = function (event) { + + var self = this; + + // Ignore the event if another widget is already being handled + if (touchHandled || !self._mouseCapture(event.originalEvent.changedTouches[0])) { + return; + } + + // Set the flag to prevent other widgets from inheriting the touch event + touchHandled = true; + + // Track movement to determine if interaction was a click + self._touchMoved = false; + + // Simulate the mouseover event + simulateMouseEvent(event, 'mouseover'); + + // Simulate the mousemove event + simulateMouseEvent(event, 'mousemove'); + + // Simulate the mousedown event + simulateMouseEvent(event, 'mousedown'); + }; + + /** + * Handle the jQuery UI widget's touchmove events + * @param {Object} event The document's touchmove event + */ + mouseProto._touchMove = function (event) { + + // Ignore event if not handled + if (!touchHandled) { + return; + } + + // Interaction was not a click + this._touchMoved = true; + + // Simulate the mousemove event + simulateMouseEvent(event, 'mousemove'); + }; + + /** + * Handle the jQuery UI widget's touchend events + * @param {Object} event The document's touchend event + */ + mouseProto._touchEnd = function (event) { + + // Ignore event if not handled + if (!touchHandled) { + return; + } + + // Simulate the mouseup event + simulateMouseEvent(event, 'mouseup'); + + // Simulate the mouseout event + simulateMouseEvent(event, 'mouseout'); + + // If the touch interaction did not move, it should trigger a click + if (!this._touchMoved) { + + // Simulate the click event + simulateMouseEvent(event, 'click'); + } + + // Unset the flag to allow other widgets to inherit the touch event + touchHandled = false; + }; + + /** + * A duck punch of the $.ui.mouse _mouseInit method to support touch events. + * This method extends the widget with bound touch event handlers that + * translate touch events to mouse events and pass them to the widget's + * original mouse event handling methods. + */ + mouseProto._mouseInit = function () { + + var self = this; + + // Delegate the touch handlers to the widget's element + self.element.bind({ + touchstart: $.proxy(self, '_touchStart'), + touchmove: $.proxy(self, '_touchMove'), + touchend: $.proxy(self, '_touchEnd') + }); + + // Call the original $.ui.mouse init method + _mouseInit.call(self); + }; + + /** + * Remove the touch event handlers + */ + mouseProto._mouseDestroy = function () { + + var self = this; + + // Delegate the touch handlers to the widget's element + self.element.unbind({ + touchstart: $.proxy(self, '_touchStart'), + touchmove: $.proxy(self, '_touchMove'), + touchend: $.proxy(self, '_touchEnd') + }); + + // Call the original $.ui.mouse destroy method + _mouseDestroy.call(self); + }; + +})(jQuery); diff --git a/src/Umbraco.Web.UI.Client/src/assets/img/uploader/upload-illustration.png b/src/Umbraco.Web.UI.Client/src/assets/img/uploader/upload-illustration.png new file mode 100644 index 0000000000..0a6f8d17bc Binary files /dev/null and b/src/Umbraco.Web.UI.Client/src/assets/img/uploader/upload-illustration.png differ diff --git a/src/Umbraco.Web.UI.Client/src/assets/img/uploader/upload-illustration.svg b/src/Umbraco.Web.UI.Client/src/assets/img/uploader/upload-illustration.svg new file mode 100644 index 0000000000..30bce45e8b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/img/uploader/upload-illustration.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/Umbraco.Web.UI.Client/src/canvasdesigner/canvasdesigner.controller.js b/src/Umbraco.Web.UI.Client/src/canvasdesigner/canvasdesigner.controller.js index c0646fed0f..ceee2a2a9e 100644 --- a/src/Umbraco.Web.UI.Client/src/canvasdesigner/canvasdesigner.controller.js +++ b/src/Umbraco.Web.UI.Client/src/canvasdesigner/canvasdesigner.controller.js @@ -24,7 +24,7 @@ var app = angular.module("Umbraco.canvasdesigner", ['colorpicker', 'ui.slider', ]; $scope.previewDevice = $scope.devices[0]; - var apiController = "/Umbraco/Api/Canvasdesigner/"; + var apiController = "../Api/Canvasdesigner/"; /*****************************************************************************/ /* Preview devices */ @@ -40,7 +40,7 @@ var app = angular.module("Umbraco.canvasdesigner", ['colorpicker', 'ui.slider', /*****************************************************************************/ $scope.exitPreview = function () { - window.top.location.href = "/umbraco/endPreview.aspx?redir=%2f" + $scope.pageId; + window.top.location.href = "../endPreview.aspx?redir=%2f" + $scope.pageId; }; /*****************************************************************************/ diff --git a/src/Umbraco.Web.UI.Client/src/canvasdesigner/index.html b/src/Umbraco.Web.UI.Client/src/canvasdesigner/index.html index e078f0cc46..ab7a5b0fdb 100644 --- a/src/Umbraco.Web.UI.Client/src/canvasdesigner/index.html +++ b/src/Umbraco.Web.UI.Client/src/canvasdesigner/index.html @@ -2,9 +2,9 @@ Umbraco Canvas Designer - - - + + + @@ -20,7 +20,7 @@
- +
    @@ -125,7 +125,7 @@
    {{item.name}}
    -
    +
@@ -154,8 +154,8 @@

Styles saved and published

- - + + diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js index 02da7bba7f..066f91be53 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js @@ -146,6 +146,8 @@ angular.module('umbraco.directives') .directive('onOutsideClick', function ($timeout) { return function (scope, element, attrs) { + var eventBindings = []; + function oneTimeClick(event) { var el = event.target.nodeName; //ignore link and button clicks @@ -173,11 +175,33 @@ angular.module('umbraco.directives') scope.$apply(attrs.onOutsideClick); } + $timeout(function(){ - $(document).on("click", oneTimeClick); + + if ("bindClickOn" in attrs) { + + eventBindings.push(scope.$watch(function() { + return attrs.bindClickOn; + }, function(newValue) { + if (newValue === "true") { + $(document).on("click", oneTimeClick); + } else { + $(document).off("click", oneTimeClick); + } + })); + + } else { + $(document).on("click", oneTimeClick); + } scope.$on("$destroy", function() { $(document).off("click", oneTimeClick); + + // unbind watchers + for (var e in eventBindings) { + eventBindings[e](); + } + }); }); // Temp removal of 1 sec timeout to prevent bug where overlay does not open. We need to find a better solution. diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js index d125258157..0fb69a3e5e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js @@ -101,7 +101,8 @@ angular.module("umbraco.directives") relative_urls: false, toolbar: toolbar, content_css: stylesheets.join(','), - style_formats: styleFormats + style_formats: styleFormats, + autoresize_bottom_margin: 0 }; @@ -122,11 +123,6 @@ angular.module("umbraco.directives") editor.getBody().setAttribute('spellcheck', true); - //hide toolbar by default - $(editor.editorContainer) - .find(".mce-toolbar") - .css("visibility", "hidden"); - //force overflow to hidden to prevent no needed scroll editor.getBody().style.overflow = "hidden"; @@ -137,32 +133,6 @@ angular.module("umbraco.directives") }, 400); }); - - // pin toolbar to top of screen if we have focus and it scrolls off the screen - var pinToolbar = function () { - - var _toolbar = $(editor.editorContainer).find(".mce-toolbar"); - var toolbarHeight = _toolbar.height(); - - var _tinyMce = $(editor.editorContainer); - var tinyMceRect = _tinyMce[0].getBoundingClientRect(); - var tinyMceTop = tinyMceRect.top; - var tinyMceBottom = tinyMceRect.bottom; - - if (tinyMceTop < 100 && (tinyMceBottom > (100 + toolbarHeight))) { - _toolbar - .css("visibility", "visible") - .css("position", "fixed") - .css("top", "100px") - .css("margin-top", "0"); - } else { - _toolbar - .css("visibility", "visible") - .css("position", "absolute") - .css("top", "auto") - .css("margin-top", (-toolbarHeight - 2) + "px"); - } - }; //when we leave the editor (maybe) editor.on('blur', function (e) { @@ -177,8 +147,6 @@ angular.module("umbraco.directives") scope.onBlur(); } - _toolbar.css("visibility", "hidden"); - $('.umb-panel-body').off('scroll', pinToolbar); }); }); @@ -190,8 +158,6 @@ angular.module("umbraco.directives") scope.onFocus(); } - pinToolbar(); - $('.umb-panel-body').on('scroll', pinToolbar); }); }); @@ -203,8 +169,6 @@ angular.module("umbraco.directives") scope.onClick(); } - pinToolbar(); - $('.umb-panel-body').on('scroll', pinToolbar); }); }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/overlays/umboverlay.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/overlays/umboverlay.directive.js index 1985c9c699..b9fae638ef 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/overlays/umboverlay.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/overlays/umboverlay.directive.js @@ -144,8 +144,12 @@ function setTargetPosition() { - var viewportWidth = null; - var viewportHeight = null; + var container = $("#contentwrapper"); + var containerLeft = container[0].offsetLeft; + var containerRight = containerLeft + container[0].offsetWidth; + var containerTop = container[0].offsetTop; + var containerBottom = containerTop + container[0].offsetHeight; + var mousePositionClickX = null; var mousePositionClickY = null; var elementHeight = null; @@ -161,10 +165,6 @@ // if mouse click position is know place element with mouse in center if (scope.model.event && scope.model.event) { - // viewport size - viewportWidth = $(window).innerWidth(); - viewportHeight = $(window).innerHeight(); - // click position mousePositionClickX = scope.model.event.pageX; mousePositionClickY = scope.model.event.pageY; @@ -179,17 +179,29 @@ // check to see if element is outside screen // outside right - if (position.left + elementWidth > viewportWidth) { - position.right = 0; + if (position.left + elementWidth > containerRight) { + position.right = 10; position.left = "inherit"; } // outside bottom - if (position.top + elementHeight > viewportHeight) { - position.bottom = 0; + if (position.top + elementHeight > containerBottom) { + position.bottom = 10; position.top = "inherit"; } + // outside left + if (position.left < containerLeft) { + position.left = containerLeft + 10; + position.right = "inherit"; + } + + // outside top + if (position.top < containerTop) { + position.top = 10; + position.bottom = "inherit"; + } + el.css(position); } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreeitem.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreeitem.directive.js index f8a1284c69..20fdc6e332 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreeitem.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreeitem.directive.js @@ -38,8 +38,8 @@ angular.module("umbraco.directives") '
' + //NOTE: This ins element is used to display the search icon if the node is a container/listview and the tree is currently in dialog //'' + - '' + - '' + + ' ' + + '' + '' + //NOTE: These are the 'option' elipses '' + diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbconfirmaction.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbconfirmaction.directive.js new file mode 100644 index 0000000000..df3e431620 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbconfirmaction.directive.js @@ -0,0 +1,39 @@ +(function() { + 'use strict'; + + function ConfirmAction() { + + function link(scope, el, attr, ctrl) { + + scope.clickConfirm = function() { + if(scope.onConfirm) { + scope.onConfirm(); + } + }; + + scope.clickCancel = function() { + if(scope.onCancel) { + scope.onCancel(); + } + }; + + } + + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/umb-confirm-action.html', + scope: { + direction: "@", + onConfirm: "&", + onCancel: "&" + }, + link: link + }; + + return directive; + } + + angular.module('umbraco.directives').directive('umbConfirmAction', ConfirmAction); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbconfirmdelete.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbconfirmdelete.directive.js deleted file mode 100644 index 5cd244f108..0000000000 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbconfirmdelete.directive.js +++ /dev/null @@ -1,35 +0,0 @@ -(function() { - 'use strict'; - - function ConfirmDelete() { - - function link(scope, el, attr, ctrl) { - - scope.confirmOverlayOpen = false; - - scope.toggleOverlay = function() { - scope.confirmOverlayOpen = !scope.confirmOverlayOpen; - }; - - scope.closeOverlay = function() { - scope.confirmOverlayOpen = false; - }; - - } - - var directive = { - restrict: 'E', - replace: true, - templateUrl: 'views/components/umb-confirm-delete.html', - scope: { - confirmAction: "&" - }, - link: link - }; - - return directive; - } - - angular.module('umbraco.directives').directive('umbConfirmDelete', ConfirmDelete); - -})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js index c93df61e13..d18251da0b 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js @@ -157,6 +157,16 @@ } + /* ---------- DELETE PROMT ---------- */ + + scope.togglePrompt = function (object) { + object.deletePrompt = !object.deletePrompt; + }; + + scope.hidePrompt = function (object) { + object.deletePrompt = false; + }; + /* ---------- TOOLBAR ---------- */ scope.toggleSortingMode = function(tool) { @@ -404,13 +414,13 @@ } }; - scope.deleteProperty = function(tab, propertyIndex, group) { + scope.deleteProperty = function(tab, propertyIndex) { // remove property tab.properties.splice(propertyIndex, 1); // if the last property in group is an placeholder - remove add new tab placeholder - if(group.properties.length === 1 && group.properties[0].propertyState === "init") { + if(tab.properties.length === 1 && tab.properties[0].propertyState === "init") { angular.forEach(scope.model.groups, function(group, index, groups){ if(group.tabState === 'init') { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umblayoutselector.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblayoutselector.directive.js index ea3eedd968..48291bfa87 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umblayoutselector.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblayoutselector.directive.js @@ -46,32 +46,19 @@ function setActiveLayout(layouts) { for (var i = 0; layouts.length > i; i++) { - var layout = layouts[i]; - - if(layout.name === scope.activeLayout.name && layout.path === scope.activeLayout.path) { + if(layout.path === scope.activeLayout.path) { layout.active = true; } - } } scope.pickLayout = function(selectedLayout) { - - for (var i = 0; scope.layouts.length > i; i++) { - - var layout = scope.layouts[i]; - - layout.active = false; - } - - selectedLayout.active = true; - - scope.activeLayout = selectedLayout; - - scope.layoutDropDownIsOpen = false; - + if(scope.onLayoutSelect) { + scope.onLayoutSelect(selectedLayout); + scope.layoutDropDownIsOpen = false; + } }; scope.toggleLayoutDropdown = function() { @@ -92,7 +79,8 @@ templateUrl: 'views/components/umb-layout-selector.html', scope: { layouts: '=', - activeLayout: '=' + activeLayout: '=', + onLayoutSelect: "=" }, link: link }; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js index fa639b6225..f5108070b1 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js @@ -25,10 +25,7 @@ } function setItemData(item) { - item.isFolder = !mediaHelper.hasFilePropertyType(item); - item.hidden = item.isFolder; - if(!item.isFolder){ item.thumbnail = mediaHelper.resolveFile(item, true); item.image = mediaHelper.resolveFile(item, false); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfiledropzone.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfiledropzone.directive.js index 724940bd9d..d33a8edb48 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfiledropzone.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfiledropzone.directive.js @@ -144,33 +144,60 @@ angular.module("umbraco.directives") }).success(function (data, status, headers, config) { - // set done status on file - file.uploadStatus = "done"; + if(data.notifications && data.notifications.length > 0) { - // set date/time for when done - used for sorting - file.doneDate = new Date(); + // set error status on file + file.uploadStatus = "error"; + + // Throw message back to user with the cause of the error + file.serverErrorMessage = data.notifications[0].message; + + // Put the file in the rejected pool + scope.rejected.push(file); + + } else { + + // set done status on file + file.uploadStatus = "done"; + + // set date/time for when done - used for sorting + file.doneDate = new Date(); + + // Put the file in the done pool + scope.done.push(file); + + } - scope.done.push(file); scope.currentFile = undefined; //after processing, test if everthing is done _processQueueItem(); }).error( function (evt, status, headers, config) { + + // set status done file.uploadStatus = "error"; //if the service returns a detailed error - if(evt.InnerException){ - file.errorMessage = evt.InnerException.ExceptionMessage; + if (evt.InnerException) { + file.serverErrorMessage = evt.InnerException.ExceptionMessage; - //Check if its the common "too large file" exception - if(evt.InnerException.StackTrace && evt.InnerException.StackTrace.indexOf("ValidateRequestEntityLength") > 0){ - file.errorMessage = "File too large to upload"; - } + //Check if its the common "too large file" exception + if (evt.InnerException.StackTrace && evt.InnerException.StackTrace.indexOf("ValidateRequestEntityLength") > 0) { + file.serverErrorMessage = "File too large to upload"; + } + + } else if (evt.Message) { + file.serverErrorMessage = evt.Message; + } + + // If file not found, server will return a 404 and display this message + if(status === 404 ) { + file.serverErrorMessage = "File not found"; } //after processing, test if everthing is done - scope.done.push(file); + scope.rejected.push(file); scope.currentFile = undefined; _processQueueItem(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/util/konami.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/util/konami.directive.js new file mode 100644 index 0000000000..7c96e0e050 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/util/konami.directive.js @@ -0,0 +1,62 @@ +/** + * Konami Code directive for AngularJS + * @version v0.0.1 + * @license MIT License, http://www.opensource.org/licenses/MIT + */ + +angular.module('umbraco.directives') + .directive('konamiCode', ['$document', function ($document) { + var konamiKeysDefault = [38, 38, 40, 40, 37, 39, 37, 39, 66, 65]; + + return { + restrict: 'A', + link: function (scope, element, attr) { + + if (!attr.konamiCode) { + throw ('Konami directive must receive an expression as value.'); + } + + // Let user define a custom code. + var konamiKeys = attr.konamiKeys || konamiKeysDefault; + var keyIndex = 0; + + /** + * Fired when konami code is type. + */ + function activated() { + if ('konamiOnce' in attr) { + stopListening(); + } + // Execute expression. + scope.$eval(attr.konamiCode); + } + + /** + * Handle keydown events. + */ + function keydown(e) { + if (e.keyCode === konamiKeys[keyIndex++]) { + if (keyIndex === konamiKeys.length) { + keyIndex = 0; + activated(); + } + } else { + keyIndex = 0; + } + } + + /** + * Stop to listen typing. + */ + function stopListening() { + $document.off('keydown', keydown); + } + + // Start listening to key typing. + $document.on('keydown', keydown); + + // Stop listening when scope is destroyed. + scope.$on('$destroy', stopListening); + } + }; + }]); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/macro.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/macro.resource.js index 9ba64838c9..06b5f9496c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/macro.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/macro.resource.js @@ -36,7 +36,7 @@ function macroResource($q, $http, umbRequestHelper) { * @methodOf umbraco.resources.macroResource * * @description - * Gets the result of a macro as html to display in the rich text editor + * Gets the result of a macro as html to display in the rich text editor or in the Grid * * @param {int} macroId The macro id to get parameters for * @param {int} pageId The current page id @@ -45,39 +45,17 @@ function macroResource($q, $http, umbRequestHelper) { */ getMacroResultAsHtmlForEditor: function (macroAlias, pageId, macroParamDictionary) { - //need to format the query string for the custom dictionary - var query = "macroAlias=" + macroAlias + "&pageId=" + pageId; - if (macroParamDictionary) { - var counter = 0; - _.each(macroParamDictionary, function (val, key) { - //check for null - val = val ? val : ""; - //need to detect if the val is a string or an object - if (!angular.isString(val)) { - //if it's not a string we'll send it through the json serializer - var json = angular.toJson(val); - //then we need to url encode it so that it's safe - val = encodeURIComponent(json); - } - else { - //we still need to encode the string, it could contain line breaks, etc... - val = encodeURIComponent(val); - } - - query += "¯oParams[" + counter + "].key=" + key + "¯oParams[" + counter + "].value=" + val; - counter++; - }); - } - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "macroApiBaseUrl", - "GetMacroResultAsHtmlForEditor", - query)), - 'Failed to retrieve macro result for macro with alias ' + macroAlias); + $http.post( + umbRequestHelper.getApiUrl( + "macroApiBaseUrl", + "GetMacroResultAsHtmlForEditor"), { + macroAlias: macroAlias, + pageId: pageId, + macroParams: macroParamDictionary + }), + 'Failed to retrieve macro result for macro with alias ' + macroAlias); } - }; } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/listviewhelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/listviewhelper.service.js index ea85e709e9..e7bfe59d42 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/listviewhelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/listviewhelper.service.js @@ -1,10 +1,104 @@ (function() { 'use strict'; - function listViewHelper() { + function listViewHelper($cookieStore) { var firstSelectedIndex = 0; + function getLayout(nodeId, availableLayouts) { + + var storedLayouts = []; + + if ($cookieStore.get("umblistViewLayout")) { + storedLayouts = $cookieStore.get("umblistViewLayout"); + } + + if (storedLayouts && storedLayouts.length > 0) { + for (var i = 0; storedLayouts.length > i; i++) { + var layout = storedLayouts[i]; + if (layout.nodeId === nodeId) { + return setLayout(nodeId, layout, availableLayouts); + } + } + + } + + return getFirstAllowedLayout(availableLayouts); + + } + + function setLayout(nodeId, selectedLayout, availableLayouts) { + + var activeLayout = {}; + var layoutFound = false; + + for (var i = 0; availableLayouts.length > i; i++) { + var layout = availableLayouts[i]; + if (layout.path === selectedLayout.path) { + activeLayout = layout; + layout.active = true; + layoutFound = true; + } else { + layout.active = false; + } + } + + if(!layoutFound) { + activeLayout = getFirstAllowedLayout(availableLayouts); + } + + setLayoutCookie(nodeId, activeLayout); + + return activeLayout; + + } + + function setLayoutCookie(nodeId, selectedLayout) { + + var layoutFound = false; + var storedLayouts = []; + + if($cookieStore.get("umblistViewLayout")) { + storedLayouts = $cookieStore.get("umblistViewLayout"); + } + + if(storedLayouts.length > 0) { + for(var i = 0; storedLayouts.length > i; i++) { + var layout = storedLayouts[i]; + if(layout.nodeId === nodeId) { + layout.path = selectedLayout.path; + layoutFound = true; + } + } + } + + if(!layoutFound) { + var cookieObject = { + "nodeId": nodeId, + "path": selectedLayout.path + }; + storedLayouts.push(cookieObject); + } + + document.cookie="umblistViewLayout=" + JSON.stringify(storedLayouts); + + } + + function getFirstAllowedLayout(layouts) { + + var firstAllowedLayout = {}; + + for (var i = 0; layouts.length > i; i++) { + var layout = layouts[i]; + if (layout.selected === true) { + firstAllowedLayout = layout; + break; + } + } + + return firstAllowedLayout; + } + function selectHandler(selectedItem, selectedIndex, items, selection, $event) { var start = 0; @@ -57,7 +151,7 @@ isSelected = true; } } - if(!isSelected && !item.hidden) { + if(!isSelected) { selection.push({id: item.id}); item.selected = true; } @@ -169,6 +263,10 @@ var service = { + getLayout: getLayout, + getFirstAllowedLayout: getFirstAllowedLayout, + setLayout: setLayout, + setLayoutCookie: setLayoutCookie, selectHandler: selectHandler, selectItem: selectItem, deselectItem: deselectItem, diff --git a/src/Umbraco.Web.UI.Client/src/installer/installer.service.js b/src/Umbraco.Web.UI.Client/src/installer/installer.service.js index 970cf8da31..4364cf0f6b 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/installer.service.js +++ b/src/Umbraco.Web.UI.Client/src/installer/installer.service.js @@ -17,9 +17,9 @@ angular.module("umbraco.install").factory('installerService', function($rootScop //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', + 'Over 300 000 websites are currently powered by Umbraco', "At least 2 people have named their cat 'Umbraco'", - 'On an average day, more than 1.000 people download Umbraco', + 'On an average day, more than 1000 people download Umbraco', 'umbraco.tv is the premier source of Umbraco video tutorials to get you started', 'You can find the world\'s friendliest CMS community at our.umbraco.org', 'You can become a certified Umbraco developer by attending one of the official courses', @@ -31,7 +31,7 @@ angular.module("umbraco.install").factory('installerService', function($rootScop "At least 4 people have the Umbraco logo tattooed on them", "'Umbraco' is the danish name for an allen key", "Umbraco 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", + "More than 400 people from all over the world meet each year in Denmark in June for our annual conference CodeGarden", "While you are installing Umbraco someone else on the other side of the planet is probably doing it too", "You can extend Umbraco without modifying the source code and using either JavaScript or C#" ]; diff --git a/src/Umbraco.Web.UI.Client/src/less/belle.less b/src/Umbraco.Web.UI.Client/src/less/belle.less index cb62bfdb9a..191d75dd4f 100644 --- a/src/Umbraco.Web.UI.Client/src/less/belle.less +++ b/src/Umbraco.Web.UI.Client/src/less/belle.less @@ -91,7 +91,7 @@ @import "components/umb-group-builder.less"; @import "components/umb-list-view.less"; @import "components/umb-table.less"; -@import "components/umb-confirm-delete.less"; +@import "components/umb-confirm-action.less"; @import "components/umb-keyboard-shortcuts-overview.less"; @import "components/umb-checkbox-list.less"; @import "components/umb-locked-field.less"; @@ -105,6 +105,7 @@ @import "components/tooltip/umb-tooltip.less"; @import "components/tooltip/umb-tooltip-list.less"; @import "components/overlays/umb-overlay-backdrop.less"; +@import "components/umb-grid.less"; @import "components/buttons/umb-button.less"; @import "components/buttons/umb-button-group.less"; diff --git a/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less b/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less index b28522db1c..52c4052975 100644 --- a/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less +++ b/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less @@ -2,8 +2,8 @@ /******* font-face *******/ @font-face { - src: url('/Umbraco/assets/fonts/helveticons/helveticons.eot') !important; - src: url('/Umbraco/assets/fonts/helveticons/helveticons.eot?#iefix') format('embedded-opentype'), url('/Umbraco/assets/fonts/helveticons/helveticons.ttf') format('truetype'), url('/Umbraco/assets/fonts/helveticons/helveticons.svg#icomoon') format('svg') !important; + src: url('assets/fonts/helveticons/helveticons.eot') !important; + src: url('assets/fonts/helveticons/helveticons.eot?#iefix') format('embedded-opentype'), url('assets/fonts/helveticons/helveticons.ttf') format('truetype'), url('assets/fonts/helveticons/helveticons.svg#icomoon') format('svg') !important; } /****************************/ diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-confirm-action.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-confirm-action.less new file mode 100644 index 0000000000..1c31b97baa --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-confirm-action.less @@ -0,0 +1,130 @@ +// OVERLAY +.umb_confirm-action__overlay { + position: absolute; + z-index: 999999; + display: flex; +} + +// positions +.umb_confirm-action__overlay.-top { + top: -50px; + right: auto; + bottom: auto; + left: 0; + animation: fadeInUp 0.2s; + flex-direction: column; + + .umb_confirm-action__overlay-action { + margin-bottom: 5px; + } + + .umb_confirm-action__overlay-action.-confirm { + order: 1; + } + + .umb_confirm-action__overlay-action.-cancel { + order: 2; + } + +} + +.umb_confirm-action__overlay.-right { + top: 0; + right: -50px; + bottom: auto; + left: auto; + animation: fadeInLeft 0.2s; + flex-direction: row; + + .umb_confirm-action__overlay-action { + margin-left: 5px; + } + + .umb_confirm-action__overlay-action.-confirm { + order: 2; + } + + .umb_confirm-action__overlay-action.-cancel { + order: 1; + } +} + +.umb_confirm-action__overlay.-bottom { + top: auto; + right: auto; + bottom: -50px; + left: 0; + animation: fadeInDown 0.2s; + flex-direction: column; + + .umb_confirm-action__overlay-action { + margin-top: 5px; + } + + .umb_confirm-action__overlay-action.-confirm { + order: 2; + } + + .umb_confirm-action__overlay-action.-cancel { + order: 1; + } +} + +.umb_confirm-action__overlay.-left { + top: 0; + right: auto; + bottom: auto; + left: -50px; + animation: fadeInRight 0.2s; + flex-direction: row; + + .umb_confirm-action__overlay-action { + margin-right: 5px; + } + + .umb_confirm-action__overlay-action.-confirm { + order: 1; + } + + .umb_confirm-action__overlay-action.-cancel { + order: 2; + } +} + +// BUTTONS +.umb_confirm-action__overlay-action { + width: 20px; + height: 20px; + display: flex; + align-items: center; + justify-content: center; + color: #ffffff; + border-radius: 40px; + box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.26); + font-size: 18px; +} + +.umb_confirm-action__overlay-action:hover { + text-decoration: none; + color: #ffffff; +} + +// confirm button +.umb_confirm-action__overlay-action.-confirm { + background: #ffffff; + color: @green !important; +} + +.umb_confirm-action__overlay-action.-confirm:hover { + color: @green !important; +} + +// cancel button +.umb_confirm-action__overlay-action.-cancel { + background: #ffffff; + color: @red !important; +} + +.umb_confirm-action__overlay-action.-cancel:hover { + color: @red !important; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-confirm-delete.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-confirm-delete.less deleted file mode 100644 index 8f767c501c..0000000000 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-confirm-delete.less +++ /dev/null @@ -1,34 +0,0 @@ -.umb-confirm-delete { - position: relative; -} - -.umb_confirm-delete__overlay-action { - position: absolute; - z-index: 999999; - width: 20px; - height: 20px; - display: flex; - align-items: center; - justify-content: center; - color: #ffffff; - border-radius: 40px; - animation: fadeInRight 0.2s; - box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.26); -} - -.umb_confirm-delete__overlay-action:hover { - text-decoration: none; - color: #ffffff; -} - -.umb_confirm-delete__overlay-action.-confirm { - background: @green; - top: 0; - left: -50px; -} - -.umb_confirm-delete__overlay-action.-cancel { - background: @red; - top: 0; - left: -25px; -} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-file-dropzone.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-file-dropzone.less index 0aa6ae6a8e..d4e82a6419 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-file-dropzone.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-file-dropzone.less @@ -41,11 +41,15 @@ // file select link .file-select { - color: @gray; - font-size: 16px; + font-size: 15px; + color: @blue; cursor: pointer; + + margin-top: 10px; + &:hover { - text-decoration: underline; + color: @blueDark; + text-decoration: underline; } } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-grid.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-grid.less new file mode 100644 index 0000000000..7b11ca9338 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-grid.less @@ -0,0 +1,1072 @@ +// TODO General cleanup in !important (Round 2) + +// Gridview +// ------------------------- +.umb-grid IFRAME { + overflow: hidden; +} + + + +// Sortabel +// ------------------------- + +// sortable-helper +.umb-grid .ui-sortable-helper { + position: absolute !important; + background-color: @blue !important; + height: 42px !important; + width: 42px !important; + overflow: hidden; + padding: 5px; + border-radius: 2px; + text-align: center; + font-family: "icomoon"; + box-shadow: 3px 3px 12px 0 rgba(50, 50, 50, 0.45); + + &:after { + line-height: 42px; + font-size: 22px; + content: "\e126"; + color: #ffffff; + } + + * { + display: none; + } +} + +.umb-grid .ui-sortable-helper .umb-row-title-bar, +.umb-grid .ui-sortable-helper .cell-tools-add { + display: none !important; +} + +// sortable-placeholder +.umb-grid .ui-sortable-placeholder { + position: absolute; + left: 0; + right: 0; + background-color: @blue; + height: 2px; + margin-bottom: 20px; + + &:before, &:after { + position: absolute; + top: -9px; + font-family: "icomoon"; + font-size: 18px; + color: @blue; + } + + &:before { + left: -5px; + content: "\e0e9"; + } + + &:after { + right: -5px; + content: "\e0d7"; + } +} + +.umb-grid .umb-cell .ui-sortable-placeholder { + left: 10px; + right: 10px; +} + + + + +// utils +// ------------------------- +.umb-grid-width { + margin: 20px auto; + width: 100%; +} + +.umb-grid .right { + float: right; +} + + + +// general layout components +// ------------------------- +.umb-grid .tb { + width: 100%; +} + +.umb-grid .td { + width: 100%; + display: inline-block; + vertical-align: top; + box-sizing: border-box; +} + +.umb-grid .middle { + text-align: center; +} + +.umb-grid .mainTd { + position: relative; +} + + + +// COLUMN +// ------------------------- +.umb-grid .umb-column { + position: relative; +} + + + +// ROW +// ------------------------- +.umb-grid .umb-row { + position: relative; + margin-bottom: 40px; + padding-top: 10px; +} + +.umb-grid .row-tools a { + text-decoration: none; +} + + + +// CELL +// ------------------------- +.umb-grid .umb-cell { + position: relative; +} + +.umb-grid .umb-cell-content { + position: relative; + display: block; + box-sizing: border-box; + margin: 10px; + border: 1px solid transparent; +} + +.umb-grid .umb-row .umb-cell-placeholder { + min-height: 130px; + background-color: @grayLighter; + border-width: 2px; + border-style: dashed; + border-color: @grayLight; + transition: border-color 100ms linear; + + &:hover { + border-color: @blue; + cursor: pointer; + } +} + +.umb-grid .umb-cell-content.-has-editors { + padding-top: 38px; + background-color: #ffffff; + border-width: 1px; + border-style: solid; + border-color: @grayLight; + + &:hover { + cursor: auto; + } +} + + + +.umb-grid .cell-tools { + position: absolute; + top: 10px; + right: 10px; + color: @gray; + font-size: 16px; +} + + +.umb-grid .cell-tool { + cursor: pointer; + float: right; + + &:hover { + color: @blueDark; + } +} + + +.umb-grid .cell-tools-add { + color: @blue; + + &:focus, &:hover { + text-decoration: none; + } +} + +.umb-grid .cell-tools-add.-center { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + color: @blue; +} + +.umb-grid .cell-tools-add.-bar { + display: block; + background: @grayLighter; + text-align: center; + padding: 5px; + border: 1px dashed #ccc; + margin: 10px; + border-radius: 5px; +} + + +.umb-grid .cell-tools-remove { + display: inline-block; + position: relative; +} + +.umb-grid .cell-tools-remove .iconBox:hover, +.umb-grid .cell-tools-remove .iconBox:hover * { + background: @red !important; + border-color: @red !important; +} + +.umb-grid .cell-tools-move { + display: inline-block; +} + +.umb-grid .cell-tools-edit { + display: inline-block; + color: @white; +} + +.umb-grid .drop-overlay { + position: absolute; + z-index: 10; + top: 0; + left: 0; + background: #ffffff; + opacity: 0.9; + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + font-size: 14px; + box-sizing: border-box; + text-align: center; + line-height: 1.3em; + font-weight: bold; + flex-direction: column; +} + +.drop-overlay.-disable { + color: @red; +} + +.drop-overlay.-allow { + color: @green; +} + +.umb-grid .drop-overlay .drop-icon { + font-size: 40px; + margin-bottom: 20px; +} + + + +// CONTROL +// ------------------------- +.umb-grid .umb-control { + position: relative; + display: block; + overflow: hidden; + margin-left: 10px; + margin-right: 10px; + margin-bottom: 10px; +} + +.umb-grid .umb-control-click-overlay { + position: absolute; + width: 100%; + height: 100%; + z-index: 5; + top: 0; + left: 0; + opacity: 0; + cursor: pointer; + + &:hover { + opacity: 0.1; + background-color: @blue; + transition: opacity 0.1s; + } +} + + +.umb-grid .umb-row-title-bar { + display: flex; + padding-left: 10px; + padding-right: 10px; +} + +.umb-grid .umb-row-title { + display: inline-block; + cursor: pointer; + font-size: 15px; + font-weight: bold; + color: @black; + + margin-right: 6px; +} + +.umb-grid .row-tools { + display: inline-block; + margin-left: 10px; + font-size: 18px; + color: @gray; +} + +.umb-grid .row-tool { + cursor: pointer; +} + +.umb-grid .umb-add-row { + text-align: center; +} + + + +// CONTROL PLACEHOLDER +// ------------------------- +.umb-grid .umb-control-placeholder { + min-height: 20px; + position: relative; + text-align: center; + text-align: -moz-center; + cursor: text; +} + +.umb-grid .umb-control-placeholder .placeholder { + font-size: 14px; + opacity: 0.7; + text-align: left; + padding: 5px; + border: 1px solid rgba(182, 182, 182, 0.3); + height: 20px; +} + +.umb-grid .umb-control-placeholder:hover .placeholder { + border: 1px solid rgba(182, 182, 182, 0.8); +} + + + +// EDITOR PLACEHOLDER +// ------------------------- +.umb-grid .umb-editor-placeholder { + min-height: 65px; + padding: 20px; + padding-bottom: 30px; + position: relative; + background-color: @white; + border: 4px dashed @grayLight; + text-align: center; + text-align: -moz-center; + cursor: pointer; +} + +.umb-grid .umb-editor-placeholder i { + color: @grayLight; + font-size: 85px; + line-height: 85px; + display: block; + margin-bottom: 10px; +} + + + +// Active states +// ------------------------- + +// Row states +.umb-grid .umb-row.-active { + background-color: @blue; + + .row-tool, + .umb-row-title { + color: #fff; + } + + .umb-grid-has-config { + color: fade(@white, 66); + } + + .umb-cell { + .umb-grid-has-config { + color: fade(@black, 44); + } + } + + .umb-cell .umb-cell-content { + border-color: transparent; + } +} + + +.umb-grid .umb-row.-active-child { + background-color: @grayLighter; + + + .umb-row-title { + color: @gray; + } + + .row-tool { + color: fade(@black, 23); + } + + .umb-grid-has-config { + color: fade(@black, 44); + } + + .umb-cell-content.-placeholder { + border-color: @grayLight; + + &:hover { + border-color: fade(@gray, 44); + } + } +} + + +// Cell states + +.umb-grid .umb-row .umb-cell.-active { + border-color: @grayLight; + + .umb-cell-content.-has-editors { + box-shadow: 3px 3px 6px rgba(0, 0, 0, .07); + border-color: @blue; + } +} + +.umb-grid .umb-row .umb-cell.-active-child { + + .cell-tool { + color: fade(@black, 23); + } + + .umb-cell-content.-has-editors { + border-color: rgba(113, 136, 160, .44); + } +} + + + + + +// Title bar and tools +.umb-grid .umb-row-title-bar { + display: flex; +} + +.umb-grid .umb-grid-right { + display: flex; + flex-direction: row; + justify-content: center; +} + +.umb-grid .umb-tools { + margin-left: auto; +} + + +// Add more content button +.umb-grid-add-more-content { + text-align: center; +} + +.umb-grid .newbtn { + width: auto; + padding: 6px 15px; + border-style: solid; + + font-size: 12px; + font-weight: bold; + + display: inline-block; + + margin: 10px auto 20px; + + border-color: #E2E2E2; + + &:hover { + cursor: pointer; + opacity: .77; + } +} + + + + +// Form elements +// ------------------------- +.umb-grid textarea.textstring { + display: block; + overflow: hidden; + border: none; + background: #fff; + outline: none; + resize: none; + color: @gray; +} + +.umb-grid .umb-cell-rte textarea { + display: none !important; +} + +.umb-grid .umb-cell-media .caption { + display: block; + overflow: hidden; + border: none; + background: #fff; + outline: none; + width: 98%; + resize: none; + font-style: italic; +} + +.umb-grid .cellPanelRte { + min-height: 60px; +} + +.umb-grid .umb-cell-embed iframe { + width: 100%; +} + +.umb-grid .umb-cell-rte { + border-color: transparent; +} + + + +// ICONS +// ------------------------- +.umb-grid .iconBox { + padding: 4px 6px; + display: inline-block; + cursor: pointer; + border-radius: 200px; + background: rgba(255,255,255, 1); + border: 1px solid rgb(182, 182, 182); + margin: 2px; + + &:hover, &:hover * { + background: @blue !important; + color: white !important; + border-color: @blue !important; + text-decoration: none; + } +} + +.umb-grid .iconBox span.prompt { + display: block; + white-space: nowrap; + text-align: center; +} + +.umb-grid .iconBox span.prompt > a { + text-decoration: underline; +} + +.umb-grid .iconBox a:hover { + text-decoration: none; + color: white !important; +} + +.umb-grid .iconBox.selected { + -webkit-appearance: none; + background-image: linear-gradient(to bottom,#e6e6e6,#bfbfbf); + background-repeat: repeat-x; + filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe6e6e6',endColorstr='#ffbfbfbf',GradientType=0); + zoom: 1; + border-color: #bfbfbf #bfbfbf #999; + border-color: rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25); + box-shadow: inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05); + border-radius: 3px; + background: transparent; +} + +.umb-grid .iconBox i { + font-size: 16px !important; + color: #5F5F5F; + display: block; +} + +.umb-grid .help-text { + color: @black; + font-size: 14px; + font-weight: bold; + display: inline-block; + clear: both; +} + + + +// TINYMCE EDITOR +// ------------------------- + +.umb-grid .mce-panel { + background: transparent !important; + border: none !important; + clear: both; +} + +.umb-grid .mce-btn button { + padding: 8px 6px; + line-height: inherit; +} + +.umb-grid .mce-toolbar { + border-bottom: 1px solid rgba(207, 207, 207, 0.7); + background-color: rgba(250, 250, 250, 1); + display: none; +} + +.umb-grid .umb-control.-active .mce-toolbar { + display: block; +} + +.umb-grid .mce-flow-layout-item { + margin: 0; +} + +.umb-grid .mceContentBody { + overflow-y: hidden!important; +} + + +// MEDIA EDITOR +// ------------------------- +.umb-grid .fullSizeImage { + width: 100%; +} + + + +// Width +// ------------------------- +.umb-grid .boxWidth { + text-align: right; + margin-bottom: 10px; +} + +.umb-grid .boxWidth input { + text-align: center; + width: 40px; +} + +.umb-grid .boxWidth label { + font-size: 10px; + padding: 0; + margin: 5px 5px 0 0; + color: #808080; +} + + + +// Margin Control +// ------------------------- +.umb-grid .umb-control { + border-width: 1px; + border-style: solid; + border-color: transparent; +} + +.umb-grid .umb-control.-active { + border-color: @blue; +} + +.umb-grid .umb-templates-columns { + margin-top: 30px; +} + +.umb-grid .umb-control-inner { + position: relative; +} + +.umb-grid .umb-control-bar { + opacity: 0; + background: @blue; + padding: 2px 5px; + color: #ffffff; + font-size: 10px; + height: 0; + display: flex; + transition: height 80ms linear, opacity 80ms linear; + align-items: center; +} + +.umb-grid .umb-control-title { + display: flex; + align-items: center; +} + +.umb-grid .umb-control.-active .umb-control-bar { + opacity: 1; + height: 25px; +} + +.umb-grid .umb-control-tools { + display: inline-block; + margin-left: 10px; +} + +.umb-grid .umb-control-tool { + font-size: 16px; + margin-right: 5px; + position: relative; + cursor: pointer; +} + + + +// Template +// ------------------------- +.umb-grid .umb-templates { + text-align: center; + overflow: hidden; + width: 100%; +} + +.umb-grid .umb-templates-template { + display: inline-block; + width: 100px; + padding-right: 30px; + margin: 20px; +} + +.umb-grid .umb-templates-template a.tb:hover { + border: 5px solid @blue; +} + +.umb-grid .umb-templates-template .tb { + width: 100%; + height: 150px; + padding: 10px; + background-color: @grayLighter; + border: 5px solid @grayLight; + cursor: pointer; + position: relative; +} + +.umb-grid .umb-templates-template .tr { + height: 100%; + position: relative; +} + +.umb-grid .umb-templates-template .tb .umb-templates-column { + height: 100%; + border: 1px dashed @grayLight; + border-right: none; +} + +.umb-grid .umb-templates-template .tb .umb-templates-column.last { + border-right: 1px dashed @grayLight !important; +} + +.umb-grid a.umb-templates-column:hover, +.umb-grid a.umb-templates-column.selected { + background-color: @blue; +} + + + +// Template Column +// ------------------------- +/* New template preview */ +.umb-grid { + .templates-preview { + display: inline-block; + width: 100%; + text-align: center; + + small { + position: absolute; + width: 100%; + left: 0; + bottom: -25px; + padding-top: 15px; + } + + .help-text { + margin: 35px 35px 0 0; + } + } + + .preview-rows { + display: inline-block; + position: relative; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + width: 125px; + margin: 15px; + border: 3px solid @grayLight; + transition: border 100ms linear; + + &.prevalues-rows { + margin: 0 20px 20px 0; + width: 80px; + float: left; + } + + &.prevalues-templates { + margin: 0 20px 20px 0; + float: left; + } + + &:hover { + border-color: @blue; + cursor: pointer; + } + + .preview-row { + display: inline-block; + width: 100%; + vertical-align: bottom; + } + } + + .preview-rows.layout { + padding: 2px; + + .preview-row { + height: 100%; + } + + .preview-col { + height: 180px; + } + + .preview-cell { + background-color: @grayLighter; + } + + .preview-overlay { + display: none; + } + } + + .preview-rows.columns { + min-height: 16px; + line-height: 11px; + padding: 1px; + + &.prevalues-rows { + min-height: 30px; + } + } + + .preview-rows { + .preview-col { + display: block; + float: left; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + width: 33.3%; + + /* temp value */ + height: 10px; + margin: 0; + border: 1px solid @white; + + .preview-cell { + display: block; + width: 100%; + height: 100%; + background-color: @grayLight; + margin: 0 1px 1px 0; + } + } + + &.prevalues-templates { + .preview-col { + height: 80px; + } + } + } + + .preview-overlay { + display: block; + width: 100%; + position: absolute; + height: 100%; + top: 0; + box-sizing: border-box; + left: 0; + border: 3px solid white; + } +} + +// Has Config +// ------------------------- + +.umb-grid .umb-grid-has-config { + display: inline; + + font-size: 12px; + color: fade(@black, 44); +} + +.umb-grid .umb-cell { + .umb-grid-has-config { + position: absolute; + top: 10px; + left: 10px; + } +} + + +// Overlay +// ------------------------- +.umb-grid .cell-tools-menu { + position: absolute; + width: 360px; + height: 380px; + overflow: auto; + border: 1px solid #ccc; + margin-top: -270px; + margin-left: -150px; + background: @white; + padding: 7px; + top: 0; + left: 50%; + z-index: 6660; + box-shadow: 3px 3px 12px 0 rgba(50, 50, 50, 0.45); +} + +.umb-grid .cell-tools-menu h5 { + border-bottom: 1px solid #d9d9d9; + color: #999; + padding: 10px; + margin-top: 0; +} + +.umb-grid .elements { + display: block; + padding: 0; + margin: 0; +} + +.umb-grid .elements li { + display: inline-block; + width: 90px; + height: 80px; + margin: 5px; + padding: 5px; + overflow: hidden; + font-size: 11px; + + &:hover, &:hover * { + background: #2e8aea; + color: @white; + } +} + +.umb-grid .elements a { + color: #222; + text-decoration: none; +} + +.umb-grid .elements i { + font-size: 30px; + line-height: 50px; + color: #999; + display: block; +} + + + +// Configuration specific styles +// ------------------------- +.umb-grid-configuration .umb-templates { + text-align: left; +} + +.umb-grid-configuration ul { + display: block; +} + +.umb-grid-configuration ul li { + display: block; + width: auto; + text-align: left; +} + +.umb-grid-configuration .umb-templates .umb-templates-template .tb { + max-height: 50px; + border-width: 2px !important; + padding: 0; + border-spacing: 2px; + overflow: hidden; +} + +.umb-grid-configuration .umb-templates .umb-templates-template span { + background: @grayLight; + display: inline-block; +} + +.umb-grid-configuration .umb-templates .umb-templates-template .tb:hover { + border-width: 2px !important; +} + +.umb-grid-configuration .umb-templates-column { + display: block; + float: left; + margin-left: -1px; + border: 1px white solid !important; + background: @grayLight; +} + +.umb-grid-configuration .umb-templates-column.last { + margin-right: -1px; +} + +.umb-grid-configuration .umb-templates-column.add { + text-align: center; + font-size: 20px; + line-height: 70px; + color: #ccc; + text-decoration: none; + background: @white; +} + +.umb-grid-configuration .mainTdpt { + height: initial; + border: none; +} + +.umb-grid-configuration .umb-templates-rows .umb-templates-row { + margin: 0 50px 20px 0; + width: 60px; +} + +.umb-grid-configuration .umb-templates-rows .umb-templates-row .tb { + border-width: 2px !important; + padding: 0; + border-spacing: 2px; +} + +.umb-grid-configuration .umb-templates-rows .mainTdpt { + height: 10px !important; +} + +.umb-grid-configuration a.umb-templates-column { + height: 70px !important; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less index b8f0d457dd..2768c370c9 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less @@ -59,10 +59,12 @@ position: absolute; top: -30px; right: 20px; + font-size: 18px; } .umb-group-builder__group-remove:hover { cursor: pointer; + color: @blueDark; } .umb-group-builder__group-title-wrapper { @@ -312,6 +314,12 @@ input.umb-group-builder__group-sort-value { margin: 0 0 10px 0; display: block; font-size: 18px; + position: relative; + cursor: pointer; +} + +.umb-group-builder__property-action:hover { + color: @blueDark; } .umb-group-builder__property-inherited-label { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less index 98330a2dc8..aa86f3d0e7 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less @@ -51,14 +51,13 @@ input.umb-table__input { text-decoration: none; color: fade(@gray, 75%); } +} - - &.sortable { - &:hover { - text-decoration: none; - cursor: pointer; - color: @black; - } +.umb-table-head__link .sortable { + &:hover { + text-decoration: none; + cursor: pointer; + color: @black; } } @@ -119,11 +118,12 @@ input.umb-table__input { font-size: 16px; } -.umb-table-body__empty { // Styles of no items in the listview +// Styles of no items in the listview +.umb-table-body__empty { font-size: 16px; text-align: center; - color: fade(@grayLight, 85%); + color: @gray; padding: 20px 0; @@ -178,7 +178,7 @@ input.umb-table__input { .umb-table-cell > * { overflow: hidden; - white-space: nowrap; // Disable/Enable this to keep string on one line + white-space: nowrap; //NOTE Disable/Enable this to keep textstring on one line text-overflow: ellipsis; cursor: default; @@ -192,7 +192,7 @@ input.umb-table__input { } -//NOTE Gives the title more wieght in width compared to other cells +// Increases the space for the name cell .umb-table__name { flex: 1 1 25%; max-width: 25%; diff --git a/src/Umbraco.Web.UI.Client/src/less/main.less b/src/Umbraco.Web.UI.Client/src/less/main.less index 56e77342b0..f2a5082231 100644 --- a/src/Umbraco.Web.UI.Client/src/less/main.less +++ b/src/Umbraco.Web.UI.Client/src/less/main.less @@ -521,3 +521,58 @@ height:1px; .umb-loader-wrapper.-bottom { bottom: 0; } + +// Helpers + +.strong { + font-weight: bold; +} + +.inline { + display: inline; +} + + +// Input label styles +// @Simon: not sure where to put this part yet +// --- TODO Needs to be divided into the right .less directories + + +// Titles for input fields +.input-label--title { + font-weight: bold; + color: @black; + + margin-bottom: 3px; +} + + +// Used for input checkmark fields +.input-label--small { + display: inline; + + font-size: 12px; + font-weight: bold; + color: fade(@black, 70); + + &:hover { + color: @black; + } +} + +input[type=checkbox]:checked + .input-label--small { + color: @blue; +} + + +// Use this for headers in the panels +.panel-dialog--header { + border-bottom: 1px solid @gray; + + margin: 10px 0; + padding-bottom: 10px; + + font-size: @fontSizeLarge; + font-weight: bold; + line-height: 20px; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/panel.less b/src/Umbraco.Web.UI.Client/src/less/panel.less index ba25d0e85d..36c2812cc7 100644 --- a/src/Umbraco.Web.UI.Client/src/less/panel.less +++ b/src/Umbraco.Web.UI.Client/src/less/panel.less @@ -42,6 +42,23 @@ bottom: 90px; } +.umb-mediapicker-upload { + display: -ms-flexbox; + display: -webkit-box; + display: -webkit-flex; + display: flex; + + .form-search { + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; + } + + .upload-button { + margin-left: 16px; + } +} + .umb-panel.editor-breadcrumb .umb-panel-body, .umb-panel.editor-breadcrumb .umb-bottom-bar { bottom: 31px !important; } @@ -140,6 +157,7 @@ border-radius: @tabsBorderRadius; box-shadow: none; padding: 0; + z-index: 6020; } .umb-btn-toolbar .dropdown-menu small { diff --git a/src/Umbraco.Web.UI.Client/src/less/property-editors.less b/src/Umbraco.Web.UI.Client/src/less/property-editors.less index acb9644a97..082e8d079d 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -40,8 +40,16 @@ padding: 10px; } -.umb-contentpicker small a { +.umb-contentpicker small { + + &:not(:last-child) { + padding-right: 3px; + border-right: 1px solid @grayMed; + } + + a { color: @gray; + } } /* CODEMIRROR DATATYPE */ diff --git a/src/Umbraco.Web.UI.Client/src/less/variables.less b/src/Umbraco.Web.UI.Client/src/less/variables.less index ec73e62c0c..f002566eed 100644 --- a/src/Umbraco.Web.UI.Client/src/less/variables.less +++ b/src/Umbraco.Web.UI.Client/src/less/variables.less @@ -14,6 +14,7 @@ @grayDarker: #222; @grayDark: #343434; @gray: #555; +@grayMed: #999; @grayLight: #d9d9d9; @grayLighter: #f8f8f8; @white: #fff; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/help.html b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/help.html index f066da8e7c..b9428fb242 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/help.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/help.html @@ -37,7 +37,7 @@
  • diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js index e73c60685f..325af02bbc 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js @@ -1,5 +1,5 @@ angular.module("umbraco").controller("Umbraco.Dialogs.LoginController", - function ($scope, localizationService, userService, externalLoginInfo) { + function ($scope, $cookies, localizationService, userService, externalLoginInfo) { /** * @ngdoc function @@ -10,18 +10,39 @@ * @description * signs the user in */ - var d = new Date(); - //var weekday = new Array("Super Sunday", "Manic Monday", "Tremendous Tuesday", "Wonderful Wednesday", "Thunder Thursday", "Friendly Friday", "Shiny Saturday"); - localizationService.localize("login_greeting" + d.getDay()).then(function (label) { - $scope.greeting = label; - }); // weekday[d.getDay()]; + + + var d = new Date(); + var konamiGreetings = new Array("Suze Sunday", "Malibu Monday", "Tequila Tuesday", "Whiskey Wednesday", "Negroni Day", "Fernet Friday", "Sancerre Saturday"); + var konamiMode = $cookies.konamiLogin; + //var weekday = new Array("Super Sunday", "Manic Monday", "Tremendous Tuesday", "Wonderful Wednesday", "Thunder Thursday", "Friendly Friday", "Shiny Saturday"); + if (konamiMode == "1") { + $scope.greeting = "Happy " + konamiGreetings[d.getDay()]; + } else { + localizationService.localize("login_greeting" + d.getDay()).then(function (label) { + $scope.greeting = label; + }); // weekday[d.getDay()]; + } $scope.errorMsg = ""; $scope.externalLoginFormAction = Umbraco.Sys.ServerVariables.umbracoUrls.externalLoginsUrl; $scope.externalLoginProviders = externalLoginInfo.providers; $scope.externalLoginInfo = externalLoginInfo; + $scope.activateKonamiMode = function () { + if ($cookies.konamiLogin == "1") { + // somehow I can't update the cookie value using $cookies, so going native + document.cookie = "konamiLogin=; expires=Thu, 01 Jan 1970 00:00:01 GMT;"; + document.location.reload(); + } else { + document.cookie = "konamiLogin=1; expires=Tue, 01 Jan 2030 00:00:01 GMT;"; + $scope.$apply(function () { + $scope.greeting = "Happy " + konamiGreetings[d.getDay()]; + }); + } + } + $scope.loginSubmit = function (login, password) { //if the login and password are not empty we need to automatically diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html index 94826b07b4..3ca9bcda10 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html @@ -1,5 +1,5 @@ 
    -
    +

    {{greeting}}

    diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.html b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.html index cf9b5aba2d..540a19d64a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.html @@ -1,5 +1,7 @@ -

    +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/editorpicker/editorpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/editorpicker/editorpicker.controller.js index 98b637699c..90dc6a2e4e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/editorpicker/editorpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/editorpicker/editorpicker.controller.js @@ -95,7 +95,10 @@ if(editor.id === null) { - dataTypeResource.getScaffold().then(function(dataType) { + // add scaffold in data type root + var parentId = -1; + + dataTypeResource.getScaffold(parentId).then(function(dataType) { // set alias dataType.selectedEditor = editor.alias; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/itempicker/itempicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/itempicker/itempicker.html index 3d8184be7f..b13f908b75 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/itempicker/itempicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/itempicker/itempicker.html @@ -1,4 +1,4 @@ -