diff --git a/.github/CONTRIBUTING_DETAILED.md b/.github/CONTRIBUTING_DETAILED.md index 422dd1f664..1f7e827dc8 100644 --- a/.github/CONTRIBUTING_DETAILED.md +++ b/.github/CONTRIBUTING_DETAILED.md @@ -103,11 +103,9 @@ There's two big areas that you should know about: 1. The Umbraco backoffice is a extensible AngularJS app and requires you to run a `gulp dev` command while you're working with it, so changes are copied over to the appropriate directories and you can refresh your browser to view the results of your changes. You may need to run the following commands to set up gulp properly: ``` - npm cache clean - npm install -g gulp - npm install -g gulp-cli + npm cache clean --force npm install - gulp build + npm run build ``` 2. "The rest" is a C# based codebase, with some traces of our WebForms past but mostly ASP.NET MVC based these days. You can make changes, build them in Visual Studio, and hit `F5` to see the result. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 8cb9017518..55b12e2317 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -2,7 +2,7 @@ - [ ] I have added steps to test this contribution in the description below -If there's an existing issue for this PR then this fixes: +If there's an existing issue for this PR then this fixes ### Description diff --git a/build/NuSpecs/UmbracoCms.Core.nuspec b/build/NuSpecs/UmbracoCms.Core.nuspec index 88281cb84b..11b1870b90 100644 --- a/build/NuSpecs/UmbracoCms.Core.nuspec +++ b/build/NuSpecs/UmbracoCms.Core.nuspec @@ -1,6 +1,6 @@ - + UmbracoCms.Core 8.0.0 Umbraco Cms Core Binaries diff --git a/build/NuSpecs/UmbracoCms.Web.nuspec b/build/NuSpecs/UmbracoCms.Web.nuspec index 4ef825f931..e81c199288 100644 --- a/build/NuSpecs/UmbracoCms.Web.nuspec +++ b/build/NuSpecs/UmbracoCms.Web.nuspec @@ -1,6 +1,6 @@ - + UmbracoCms.Web 8.0.0 Umbraco Cms Core Binaries diff --git a/build/NuSpecs/UmbracoCms.nuspec b/build/NuSpecs/UmbracoCms.nuspec index 6f4bcd5526..c93a82c691 100644 --- a/build/NuSpecs/UmbracoCms.nuspec +++ b/build/NuSpecs/UmbracoCms.nuspec @@ -1,6 +1,6 @@ - + UmbracoCms 8.0.0 Umbraco Cms @@ -25,7 +25,7 @@ not want this to happen as the alpha of the next major is, really, the next major already. --> - + diff --git a/build/NuSpecs/tools/Web.config.install.xdt b/build/NuSpecs/tools/Web.config.install.xdt index 2da3206e7a..14778e0f10 100644 --- a/build/NuSpecs/tools/Web.config.install.xdt +++ b/build/NuSpecs/tools/Web.config.install.xdt @@ -126,45 +126,55 @@ + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/build/NuSpecs/tools/install.core.ps1 b/build/NuSpecs/tools/install.core.ps1 deleted file mode 100644 index 9b609a498e..0000000000 --- a/build/NuSpecs/tools/install.core.ps1 +++ /dev/null @@ -1,93 +0,0 @@ -param($installPath, $toolsPath, $package, $project) - -Write-Host "installPath:" "${installPath}" -Write-Host "toolsPath:" "${toolsPath}" - -Write-Host " " - -if ($project) { - $dateTime = Get-Date -Format yyyyMMdd-HHmmss - - # Create paths and list them - $projectPath = (Get-Item $project.Properties.Item("FullPath").Value).FullName - Write-Host "projectPath:" "${projectPath}" - $backupPath = Join-Path $projectPath "App_Data\NuGetBackup\$dateTime" - Write-Host "backupPath:" "${backupPath}" - $copyLogsPath = Join-Path $backupPath "CopyLogs" - Write-Host "copyLogsPath:" "${copyLogsPath}" - $umbracoBinFolder = Join-Path $projectPath "bin" - Write-Host "umbracoBinFolder:" "${umbracoBinFolder}" - - # Create backup folder and logs folder if it doesn't exist yet - New-Item -ItemType Directory -Force -Path $backupPath - New-Item -ItemType Directory -Force -Path $copyLogsPath - - # After backing up, remove all umbraco dlls from bin folder in case dll files are included in the VS project - # See: http://issues.umbraco.org/issue/U4-4930 - - if(Test-Path $umbracoBinFolder) { - $umbracoBinBackupPath = Join-Path $backupPath "bin" - - New-Item -ItemType Directory -Force -Path $umbracoBinBackupPath - - robocopy $umbracoBinFolder $umbracoBinBackupPath /e /LOG:$copyLogsPath\UmbracoBinBackup.log - - # Delete files Umbraco ships with - if(Test-Path $umbracoBinFolder\Microsoft.ApplicationBlocks.Data.dll) { Remove-Item $umbracoBinFolder\Microsoft.ApplicationBlocks.Data.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\System.Data.SqlServerCe.dll) { Remove-Item $umbracoBinFolder\System.Data.SqlServerCe.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\System.Data.SqlServerCe.Entity.dll) { Remove-Item $umbracoBinFolder\System.Data.SqlServerCe.Entity.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Umbraco.Web.dll) { Remove-Item $umbracoBinFolder\Umbraco.Web.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Umbraco.Core.dll) { Remove-Item $umbracoBinFolder\Umbraco.Core.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Umbraco.ModelsBuilder.dll) { Remove-Item $umbracoBinFolder\Umbraco.ModelsBuilder.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Umbraco.ModelsBuilder.AspNet.dll) { Remove-Item $umbracoBinFolder\Umbraco.ModelsBuilder.AspNet.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Umbraco.Web.UI.dll) { Remove-Item $umbracoBinFolder\Umbraco.Web.UI.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Umbraco.Examine.dll) { Remove-Item $umbracoBinFolder\Umbraco.Examine.dll -Force -Confirm:$false } - - # Delete files Umbraco depends upon - $amd64Folder = Join-Path $umbracoBinFolder "amd64" - if(Test-Path $amd64Folder) { Remove-Item $amd64Folder -Force -Recurse -Confirm:$false } - $x86Folder = Join-Path $umbracoBinFolder "x86" - if(Test-Path $x86Folder) { Remove-Item $x86Folder -Force -Recurse -Confirm:$false } - if(Test-Path $umbracoBinFolder\AutoMapper.dll) { Remove-Item $umbracoBinFolder\AutoMapper.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\AutoMapper.Net4.dll) { Remove-Item $umbracoBinFolder\AutoMapper.Net4.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\ClientDependency.Core.dll) { Remove-Item $umbracoBinFolder\ClientDependency.Core.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\ClientDependency.Core.Mvc.dll) { Remove-Item $umbracoBinFolder\ClientDependency.Core.Mvc.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\CookComputing.XmlRpcV2.dll) { Remove-Item $umbracoBinFolder\CookComputing.XmlRpcV2.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Examine.dll) { Remove-Item $umbracoBinFolder\Examine.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\HtmlAgilityPack.dll) { Remove-Item $umbracoBinFolder\HtmlAgilityPack.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\ICSharpCode.SharpZipLib.dll) { Remove-Item $umbracoBinFolder\ICSharpCode.SharpZipLib.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\ImageProcessor.dll) { Remove-Item $umbracoBinFolder\ImageProcessor.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\ImageProcessor.Web.dll) { Remove-Item $umbracoBinFolder\ImageProcessor.Web.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Lucene.Net.dll) { Remove-Item $umbracoBinFolder\Lucene.Net.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Microsoft.IO.RecyclableMemoryStream.dll) { Remove-Item $umbracoBinFolder\Microsoft.IO.RecyclableMemoryStream.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Microsoft.AspNet.Identity.Core.dll) { Remove-Item $umbracoBinFolder\Microsoft.AspNet.Identity.Core.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Microsoft.AspNet.Identity.Owin.dll) { Remove-Item $umbracoBinFolder\Microsoft.AspNet.Identity.Owin.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Microsoft.CodeAnalysis.CSharp.dll) { Remove-Item $umbracoBinFolder\Microsoft.CodeAnalysis.CSharp.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Microsoft.CodeAnalysis.dll) { Remove-Item $umbracoBinFolder\Microsoft.CodeAnalysis.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Microsoft.Owin.dll) { Remove-Item $umbracoBinFolder\Microsoft.Owin.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Microsoft.Owin.Host.SystemWeb.dll) { Remove-Item $umbracoBinFolder\Microsoft.Owin.Host.SystemWeb.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Microsoft.Owin.Security.dll) { Remove-Item $umbracoBinFolder\Microsoft.Owin.Security.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Microsoft.Owin.Security.Cookies.dll) { Remove-Item $umbracoBinFolder\Microsoft.Owin.Security.Cookies.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Microsoft.Owin.Security.OAuth.dll) { Remove-Item $umbracoBinFolder\Microsoft.Owin.Security.OAuth.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Microsoft.Web.Infrastructure.dll) { Remove-Item $umbracoBinFolder\Microsoft.Web.Infrastructure.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Microsoft.Web.Helpers.dll) { Remove-Item $umbracoBinFolder\Microsoft.Web.Helpers.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Microsoft.Web.Mvc.FixedDisplayModes.dll) { Remove-Item $umbracoBinFolder\Microsoft.Web.Mvc.FixedDisplayModes.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\MiniProfiler.dll) { Remove-Item $umbracoBinFolder\MiniProfiler.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Newtonsoft.Json.dll) { Remove-Item $umbracoBinFolder\Newtonsoft.Json.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Owin.dll) { Remove-Item $umbracoBinFolder\Owin.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\Semver.dll) { Remove-Item $umbracoBinFolder\Semver.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\System.Collections.Immutable.dll) { Remove-Item $umbracoBinFolder\System.Collections.Immutable.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\System.Reflection.Metadata.dll) { Remove-Item $umbracoBinFolder\System.Reflection.Metadata.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\System.Net.Http.Extensions.dll) { Remove-Item $umbracoBinFolder\System.Net.Http.Extensions.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.Net.Http.Primitives.dll) { Remove-Item $umbracoBinFolder\System.Net.Http.Primitives.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.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 } - if(Test-Path $umbracoBinFolder\System.Web.Mvc.dll) { Remove-Item $umbracoBinFolder\System.Web.Mvc.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\System.Web.Razor.dll) { Remove-Item $umbracoBinFolder\System.Web.Razor.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\System.Web.WebPages.dll) { Remove-Item $umbracoBinFolder\System.Web.WebPages.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\System.Web.WebPages.Deployment.dll) { Remove-Item $umbracoBinFolder\System.Web.WebPages.Deployment.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\System.Web.WebPages.Razor.dll) { Remove-Item $umbracoBinFolder\System.Web.WebPages.Razor.dll -Force -Confirm:$false } - } -} diff --git a/build/NuSpecs/tools/install.ps1 b/build/NuSpecs/tools/install.ps1 index 5ca42b54a1..1411cb5c97 100644 --- a/build/NuSpecs/tools/install.ps1 +++ b/build/NuSpecs/tools/install.ps1 @@ -11,37 +11,15 @@ if ($project) { # Create paths and list them $projectPath = (Get-Item $project.Properties.Item("FullPath").Value).FullName Write-Host "projectPath:" "${projectPath}" - $backupPath = Join-Path $projectPath "App_Data\NuGetBackup\$dateTime" - Write-Host "backupPath:" "${backupPath}" - $copyLogsPath = Join-Path $backupPath "CopyLogs" - Write-Host "copyLogsPath:" "${copyLogsPath}" $webConfigSource = Join-Path $projectPath "Web.config" Write-Host "webConfigSource:" "${webConfigSource}" $configFolder = Join-Path $projectPath "Config" Write-Host "configFolder:" "${configFolder}" - # Create backup folder and logs folder if it doesn't exist yet - New-Item -ItemType Directory -Force -Path $backupPath - New-Item -ItemType Directory -Force -Path $copyLogsPath - - # Create a backup of original web.config - Copy-Item $webConfigSource $backupPath -Force - - # Backup config files folder - if(Test-Path $configFolder) { - $umbracoBackupPath = Join-Path $backupPath "Config" - New-Item -ItemType Directory -Force -Path $umbracoBackupPath - - robocopy $configFolder $umbracoBackupPath /e /LOG:$copyLogsPath\ConfigBackup.log - } - # Copy umbraco and umbraco_files from package to project folder $umbracoFolder = Join-Path $projectPath "Umbraco" New-Item -ItemType Directory -Force -Path $umbracoFolder $umbracoFolderSource = Join-Path $installPath "UmbracoFiles\Umbraco" - $umbracoBackupPath = Join-Path $backupPath "Umbraco" - New-Item -ItemType Directory -Force -Path $umbracoBackupPath - robocopy $umbracoFolder $umbracoBackupPath /e /LOG:$copyLogsPath\UmbracoBackup.log robocopy $umbracoFolderSource $umbracoFolder /is /it /e /xf UI.xml /LOG:$copyLogsPath\UmbracoCopy.log $copyWebconfig = $true @@ -100,11 +78,6 @@ if ($project) { } } - $installFolder = Join-Path $projectPath "Install" - if(Test-Path $installFolder) { - Remove-Item $installFolder -Force -Recurse -Confirm:$false - } - # Open appropriate readme if($copyWebconfig -eq $true) { diff --git a/build/build.ps1 b/build/build.ps1 index d811c2cc21..c186d9adaa 100644 --- a/build/build.ps1 +++ b/build/build.ps1 @@ -15,7 +15,12 @@ [Parameter(Mandatory=$false)] [Alias("c")] [Alias("cont")] - [switch] $continue = $false + [switch] $continue = $false, + + # execute a command + [Parameter(Mandatory=$false, ValueFromRemainingArguments=$true)] + [String[]] + $command ) # ################################################################ @@ -475,7 +480,11 @@ # run if (-not $get) { - $ubuild.Build() + if ($command.Length -eq 0) + { + $command = @( "Build" ) + } + $ubuild.RunMethod($command); if ($ubuild.OnError()) { return } } if ($get) { return $ubuild } diff --git a/src/Umbraco.Core/Composing/Composition.cs b/src/Umbraco.Core/Composing/Composition.cs index d7686ccd07..34c5296dce 100644 --- a/src/Umbraco.Core/Composing/Composition.cs +++ b/src/Umbraco.Core/Composing/Composition.cs @@ -133,7 +133,11 @@ namespace Umbraco.Core.Composing Configs.RegisterWith(_register); - return _register.CreateFactory(); + IFactory factory = null; + // ReSharper disable once AccessToModifiedClosure -- on purpose + _register.Register(_ => factory, Lifetime.Singleton); + factory = _register.CreateFactory(); + return factory; } /// diff --git a/src/Umbraco.Core/Composing/IComposer.cs b/src/Umbraco.Core/Composing/IComposer.cs index a7a159d697..b73a745b61 100644 --- a/src/Umbraco.Core/Composing/IComposer.cs +++ b/src/Umbraco.Core/Composing/IComposer.cs @@ -8,7 +8,6 @@ /// /// Compose. /// - /// void Compose(Composition composition); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Composing/ICoreComposer.cs b/src/Umbraco.Core/Composing/ICoreComposer.cs index ff09f41f6b..1e9e5fced5 100644 --- a/src/Umbraco.Core/Composing/ICoreComposer.cs +++ b/src/Umbraco.Core/Composing/ICoreComposer.cs @@ -4,10 +4,8 @@ /// Represents a core . /// /// - /// All core composers are required by (compose before) all user composers, - /// and require (compose after) all runtime composers. + /// Core composers compose after the initial composer, and before user composers. /// - [ComposeAfter(typeof(IRuntimeComposer))] public interface ICoreComposer : IComposer { } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Composing/IRuntimeComposer.cs b/src/Umbraco.Core/Composing/IRuntimeComposer.cs deleted file mode 100644 index 2a352dc783..0000000000 --- a/src/Umbraco.Core/Composing/IRuntimeComposer.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Umbraco.Core.Composing -{ - /// - /// Represents a runtime . - /// - /// - /// All runtime composers are required by (compose before) all core composers - /// - public interface IRuntimeComposer : IComposer - { } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Composing/IUserComposer.cs b/src/Umbraco.Core/Composing/IUserComposer.cs index 136e69a469..96f6b38189 100644 --- a/src/Umbraco.Core/Composing/IUserComposer.cs +++ b/src/Umbraco.Core/Composing/IUserComposer.cs @@ -4,9 +4,9 @@ /// Represents a user . /// /// - /// All user composers require (compose after) all core composers. + /// User composers compose after core composers, and before the final composer. /// [ComposeAfter(typeof(ICoreComposer))] public interface IUserComposer : IComposer { } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Composing/TypeCollectionBuilderBase.cs b/src/Umbraco.Core/Composing/TypeCollectionBuilderBase.cs new file mode 100644 index 0000000000..35f85c9c2f --- /dev/null +++ b/src/Umbraco.Core/Composing/TypeCollectionBuilderBase.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; + +namespace Umbraco.Core.Composing +{ + /// + /// Provides a base class for collections of types. + /// + public abstract class TypeCollectionBuilderBase : ICollectionBuilder + where TCollection : class, IBuilderCollection + { + private readonly HashSet _types = new HashSet(); + + private Type Validate(Type type, string action) + { + if (!typeof(TConstraint).IsAssignableFrom(type)) + throw new InvalidOperationException($"Cannot {action} type {type.FullName} as it does not inherit from/implement {typeof(TConstraint).FullName}."); + return type; + } + + public void Add(Type type) => _types.Add(Validate(type, "add")); + + public void Add() => Add(typeof(T)); + + public void Add(IEnumerable types) + { + foreach (var type in types) Add(type); + } + + public void Remove(Type type) => _types.Remove(Validate(type, "remove")); + + public void Remove() => Remove(typeof(T)); + + public TCollection CreateCollection(IFactory factory) + { + return factory.CreateInstance(_types); + } + + public void RegisterWith(IRegister register) + { + register.Register(CreateCollection, Lifetime.Singleton); + } + } +} diff --git a/src/Umbraco.Core/Configuration/GlobalSettings.cs b/src/Umbraco.Core/Configuration/GlobalSettings.cs index 09962fab5a..94deb28d87 100644 --- a/src/Umbraco.Core/Configuration/GlobalSettings.cs +++ b/src/Umbraco.Core/Configuration/GlobalSettings.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Net.Configuration; using System.Web; using System.Web.Configuration; +using System.Web.Hosting; using System.Xml.Linq; using Umbraco.Core.IO; @@ -17,16 +18,15 @@ namespace Umbraco.Core.Configuration /// public class GlobalSettings : IGlobalSettings { + private string _localTempPath; - #region Private static fields - - + // TODO these should not be static private static string _reservedPaths; private static string _reservedUrls; + //ensure the built on (non-changeable) reserved paths are there at all times internal const string StaticReservedPaths = "~/app_plugins/,~/install/,~/mini-profiler-resources/,"; //must end with a comma! internal const string StaticReservedUrls = "~/config/splashes/noNodes.aspx,~/.well-known,"; //must end with a comma! - #endregion /// /// Used in unit testing to reset all config items that were set with property setters (i.e. did not come from config) @@ -131,7 +131,7 @@ namespace Umbraco.Core.Configuration : "~/App_Data/umbraco.config"; } } - + /// /// Gets the path to umbraco's root directory (/umbraco by default). /// @@ -163,7 +163,7 @@ namespace Umbraco.Core.Configuration SaveSetting(Constants.AppSettings.ConfigurationStatus, value); } } - + /// /// Saves a setting into the configuration file. /// @@ -206,7 +206,7 @@ namespace Umbraco.Core.Configuration ConfigurationManager.RefreshSection("appSettings"); } } - + /// /// Gets a value indicating whether umbraco is running in [debug mode]. /// @@ -250,7 +250,7 @@ namespace Umbraco.Core.Configuration } } } - + /// /// Returns the number of days that should take place between version checks. /// @@ -269,7 +269,7 @@ namespace Umbraco.Core.Configuration } } } - + /// public LocalTempStorage LocalTempStorageLocation { @@ -288,25 +288,43 @@ namespace Umbraco.Core.Configuration { get { + if (_localTempPath != null) + return _localTempPath; + switch (LocalTempStorageLocation) { case LocalTempStorage.AspNetTemp: - return System.IO.Path.Combine(HttpRuntime.CodegenDir, "UmbracoData"); + return _localTempPath = System.IO.Path.Combine(HttpRuntime.CodegenDir, "UmbracoData"); + case LocalTempStorage.EnvironmentTemp: - // include the appdomain hash is just a safety check, for example if a website is moved from worker A to worker B and then back - // to worker A again, in theory the %temp% folder should already be empty but we really want to make sure that its not - // utilizing an old path - assuming we cannot have SHA1 collisions on AppDomainAppId - var appDomainHash = HttpRuntime.AppDomainAppId.GenerateHash(); - return System.IO.Path.Combine(Environment.ExpandEnvironmentVariables("%temp%"), "UmbracoData", appDomainHash); + + // environment temp is unique, we need a folder per site + + // use a hash + // combine site name and application id + // site name is a Guid on Cloud + // application id is eg /LM/W3SVC/123456/ROOT + // the combination is unique on one server + // and, if a site moves from worker A to B and then back to A... + // hopefully it gets a new Guid or new application id? + + var siteName = HostingEnvironment.SiteName; + var applicationId = HostingEnvironment.ApplicationID; // ie HttpRuntime.AppDomainAppId + + var hashString = siteName + "::" + applicationId; + var hash = hashString.GenerateHash(); + var siteTemp = System.IO.Path.Combine(Environment.ExpandEnvironmentVariables("%temp%"), "UmbracoData", hash); + + return _localTempPath = System.IO.Path.Combine(siteTemp, "umbraco.config"); + //case LocalTempStorage.Default: //case LocalTempStorage.Unknown: default: - return IOHelper.MapPath("~/App_Data/TEMP"); + return _localTempPath = IOHelper.MapPath("~/App_Data/TEMP"); } } } - /// /// Gets the default UI language. /// diff --git a/src/Umbraco.Core/Logging/Viewer/JsonLogViewer.cs b/src/Umbraco.Core/Logging/Viewer/JsonLogViewer.cs index ea2c8aa49f..4c0e8ed4f8 100644 --- a/src/Umbraco.Core/Logging/Viewer/JsonLogViewer.cs +++ b/src/Umbraco.Core/Logging/Viewer/JsonLogViewer.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using Newtonsoft.Json; using Serilog.Events; using Serilog.Formatting.Compact.Reader; @@ -10,13 +11,15 @@ namespace Umbraco.Core.Logging.Viewer internal class JsonLogViewer : LogViewerSourceBase { private readonly string _logsPath; + private readonly ILogger _logger; - public JsonLogViewer(string logsPath = "", string searchPath = "") : base(searchPath) + public JsonLogViewer(ILogger logger, string logsPath = "", string searchPath = "") : base(searchPath) { if (string.IsNullOrEmpty(logsPath)) logsPath = $@"{AppDomain.CurrentDomain.BaseDirectory}\App_Data\Logs\"; _logsPath = logsPath; + _logger = logger; } private const int FileSizeCap = 100; @@ -77,8 +80,14 @@ namespace Umbraco.Core.Logging.Viewer using (var stream = new StreamReader(fs)) { var reader = new LogEventReader(stream); - while (reader.TryRead(out var evt)) + while (TryRead(reader, out var evt)) { + //We may get a null if log line is malformed + if (evt == null) + { + continue; + } + if (count > skip + take) { break; @@ -105,5 +114,21 @@ namespace Umbraco.Core.Logging.Viewer return logs; } + private bool TryRead(LogEventReader reader, out LogEvent evt) + { + try + { + return reader.TryRead(out evt); + } + catch (JsonReaderException ex) + { + // As we are reading/streaming one line at a time in the JSON file + // Thus we can not report the line number, as it will always be 1 + _logger.Error(ex, "Unable to parse a line in the JSON log file"); + + evt = null; + return true; + } + } } } diff --git a/src/Umbraco.Core/Logging/Viewer/LogViewerComposer.cs b/src/Umbraco.Core/Logging/Viewer/LogViewerComposer.cs index 651e3de06e..8eb835b4d9 100644 --- a/src/Umbraco.Core/Logging/Viewer/LogViewerComposer.cs +++ b/src/Umbraco.Core/Logging/Viewer/LogViewerComposer.cs @@ -9,7 +9,7 @@ namespace Umbraco.Core.Logging.Viewer { public void Compose(Composition composition) { - composition.SetLogViewer(_ => new JsonLogViewer()); + composition.SetLogViewer(_ => new JsonLogViewer(composition.Logger)); } } } diff --git a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs index ad307324ea..fa24cba21f 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs @@ -5,6 +5,7 @@ using Umbraco.Core.Configuration; using Umbraco.Core.Migrations.Upgrade.V_7_12_0; using Umbraco.Core.Migrations.Upgrade.V_7_14_0; using Umbraco.Core.Migrations.Upgrade.V_8_0_0; +using Umbraco.Core.Migrations.Upgrade.V_8_0_1; namespace Umbraco.Core.Migrations.Upgrade { @@ -137,6 +138,8 @@ namespace Umbraco.Core.Migrations.Upgrade To("{E0CBE54D-A84F-4A8F-9B13-900945FD7ED9}"); To("{78BAF571-90D0-4D28-8175-EF96316DA789}"); + To("{80C0A0CB-0DD5-4573-B000-C4B7C313C70D}"); + //FINAL @@ -167,7 +170,7 @@ namespace Umbraco.Core.Migrations.Upgrade From("{init-7.12.4}").To("{init-7.10.0}"); // same as 7.12.0 From("{init-7.13.0}").To("{init-7.10.0}"); // same as 7.12.0 From("{init-7.13.1}").To("{init-7.10.0}"); // same as 7.12.0 - + // 7.14.0 has migrations, handle it... // clone going from 7.10 to 1350617A (the last one before we started to merge 7.12 migrations), then // clone going from CF51B39B (after 7.12 migrations) to 0009109C (the last one before we started to merge 7.12 migrations), diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_1/ChangeNuCacheJsonFormat.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_1/ChangeNuCacheJsonFormat.cs new file mode 100644 index 0000000000..f6850eb254 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_1/ChangeNuCacheJsonFormat.cs @@ -0,0 +1,16 @@ +using Umbraco.Core.Migrations.PostMigrations; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_1 +{ + public class ChangeNuCacheJsonFormat : MigrationBase + { + public ChangeNuCacheJsonFormat(IMigrationContext context) : base(context) + { } + + public override void Migrate() + { + // nothing - just adding the post-migration + Context.AddPostMigration(); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_1_0/ChangeNuCacheJsonFormat.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_1_0/ChangeNuCacheJsonFormat.cs new file mode 100644 index 0000000000..0ceb366e1c --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_1_0/ChangeNuCacheJsonFormat.cs @@ -0,0 +1,16 @@ +using Umbraco.Core.Migrations.PostMigrations; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_1_0 +{ + public class ChangeNuCacheJsonFormat : MigrationBase + { + public ChangeNuCacheJsonFormat(IMigrationContext context) : base(context) + { } + + public override void Migrate() + { + // nothing - just adding the post-migration + Context.AddPostMigration(); + } + } +} diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedCultureInfos.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedCultureInfos.cs index a5669d1a9e..749b37a41a 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedCultureInfos.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedCultureInfos.cs @@ -11,14 +11,14 @@ namespace Umbraco.Core.Models.PublishedContent /// /// Initializes a new instance of the class. /// - public PublishedCultureInfo(string culture, string name, DateTime date) + public PublishedCultureInfo(string culture, string name, string urlSegment, DateTime date) { if (string.IsNullOrWhiteSpace(culture)) throw new ArgumentNullOrEmptyException(nameof(culture)); if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullOrEmptyException(nameof(name)); Culture = culture; Name = name; - UrlSegment = name.ToUrlSegment(culture); + UrlSegment = urlSegment; Date = date; } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs index 821f0941fc..6b8e6515f5 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs @@ -292,7 +292,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (psql.Arguments[i] is string s && s == "[[[ISOCODE]]]") { psql.Arguments[i] = ordering.Culture; - break; } } } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs index 24b50d294b..fcc8402265 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs @@ -982,8 +982,14 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // invariant: left join will yield NULL and we must use pcv to determine published // variant: left join may yield NULL or something, and that determines published + var joins = Sql() - .InnerJoin("ctype").On((content, contentType) => content.ContentTypeId == contentType.NodeId, aliasRight: "ctype"); + .InnerJoin("ctype").On((content, contentType) => content.ContentTypeId == contentType.NodeId, aliasRight: "ctype") + // left join on optional culture variation + //the magic "[[[ISOCODE]]]" parameter value will be replaced in ContentRepositoryBase.GetPage() by the actual ISO code + .LeftJoin(nested => + nested.InnerJoin("langp").On((ccv, lang) => ccv.LanguageId == lang.Id && lang.IsoCode == "[[[ISOCODE]]]", "ccvp", "langp"), "ccvp") + .On((version, ccv) => version.Id == ccv.VersionId, aliasLeft: "pcv", aliasRight: "ccvp"); sql = InsertJoins(sql, joins); @@ -993,7 +999,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // when invariant, ie 'variations' does not have the culture flag (value 1), use the global 'published' flag on pcv.id, // otherwise check if there's a version culture variation for the lang, via ccv.id - ", (CASE WHEN (ctype.variations & 1) = 0 THEN (CASE WHEN pcv.id IS NULL THEN 0 ELSE 1 END) ELSE (CASE WHEN ccv.id IS NULL THEN 0 ELSE 1 END) END) AS ordering "); // trailing space is important! + ", (CASE WHEN (ctype.variations & 1) = 0 THEN (CASE WHEN pcv.id IS NULL THEN 0 ELSE 1 END) ELSE (CASE WHEN ccvp.id IS NULL THEN 0 ELSE 1 END) END) AS ordering "); // trailing space is important! sql = Sql(sqlText, sql.Arguments); diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/YesNoValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/YesNoValueConverter.cs index 09170b746a..8ad09733f8 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/YesNoValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/YesNoValueConverter.cs @@ -36,6 +36,10 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters if (source is int) return (int)source == 1; + // this is required for correct true/false handling in nested content elements + if (source is long) + return (long)source == 1; + if (source is bool) return (bool)source; diff --git a/src/Umbraco.Core/Runtime/CoreRuntimeComponent.cs b/src/Umbraco.Core/Runtime/CoreInitialComponent.cs similarity index 90% rename from src/Umbraco.Core/Runtime/CoreRuntimeComponent.cs rename to src/Umbraco.Core/Runtime/CoreInitialComponent.cs index e9842a1ba1..20fee7a299 100644 --- a/src/Umbraco.Core/Runtime/CoreRuntimeComponent.cs +++ b/src/Umbraco.Core/Runtime/CoreInitialComponent.cs @@ -5,11 +5,11 @@ using Umbraco.Core.IO; namespace Umbraco.Core.Runtime { - public class CoreRuntimeComponent : IComponent + public class CoreInitialComponent : IComponent { private readonly IEnumerable _mapperProfiles; - public CoreRuntimeComponent(IEnumerable mapperProfiles) + public CoreInitialComponent(IEnumerable mapperProfiles) { _mapperProfiles = mapperProfiles; } diff --git a/src/Umbraco.Core/Runtime/CoreRuntimeComposer.cs b/src/Umbraco.Core/Runtime/CoreInitialComposer.cs similarity index 96% rename from src/Umbraco.Core/Runtime/CoreRuntimeComposer.cs rename to src/Umbraco.Core/Runtime/CoreInitialComposer.cs index 690ac4d56c..f32a46f3f8 100644 --- a/src/Umbraco.Core/Runtime/CoreRuntimeComposer.cs +++ b/src/Umbraco.Core/Runtime/CoreInitialComposer.cs @@ -24,7 +24,9 @@ using IntegerValidator = Umbraco.Core.PropertyEditors.Validators.IntegerValidato namespace Umbraco.Core.Runtime { - public class CoreRuntimeComposer : ComponentComposer, IRuntimeComposer + // core's initial composer composes before all core composers + [ComposeBefore(typeof(ICoreComposer))] + public class CoreInitialComposer : ComponentComposer { public override void Compose(Composition composition) { diff --git a/src/Umbraco.Core/Runtime/CoreRuntime.cs b/src/Umbraco.Core/Runtime/CoreRuntime.cs index 7462ecdf67..f00365496a 100644 --- a/src/Umbraco.Core/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Core/Runtime/CoreRuntime.cs @@ -168,7 +168,7 @@ namespace Umbraco.Core.Runtime _state.BootFailedException = bfe; } - timer.Fail(exception: bfe); // be sure to log the exception - even if we repeat ourselves + timer?.Fail(exception: bfe); // be sure to log the exception - even if we repeat ourselves // if something goes wrong above, we may end up with no factory // meaning nothing can get the runtime state, etc - so let's try @@ -242,7 +242,7 @@ namespace Umbraco.Core.Runtime } catch { - timer.Fail(); + timer?.Fail(); throw; } } diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index 3406eece3d..c060c84b0f 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -2862,7 +2862,7 @@ namespace Umbraco.Core.Services.Implement { if (blueprint == null) throw new ArgumentNullException(nameof(blueprint)); - var contentType = _contentTypeRepository.Get(blueprint.ContentType.Id); + var contentType = GetContentType(blueprint.ContentType.Alias); var content = new Content(name, -1, contentType); content.Path = string.Concat(content.ParentId.ToString(), ",", content.Id); diff --git a/src/Umbraco.Core/Strings/Utf8ToAsciiConverter.cs b/src/Umbraco.Core/Strings/Utf8ToAsciiConverter.cs index 91dd5a7597..54ca74d20f 100644 --- a/src/Umbraco.Core/Strings/Utf8ToAsciiConverter.cs +++ b/src/Umbraco.Core/Strings/Utf8ToAsciiConverter.cs @@ -17,7 +17,7 @@ namespace Umbraco.Core.Strings /// Converts an Utf8 string into an Ascii string. /// /// The text to convert. - /// The character to used to replace characters that cannot properly be converted. + /// The character to use to replace characters that cannot properly be converted. /// The converted text. public static string ToAsciiString(string text, char fail = '?') { @@ -39,7 +39,7 @@ namespace Umbraco.Core.Strings /// Converts an Utf8 string into an array of Ascii characters. /// /// The text to convert. - /// The character to used to replace characters that cannot properly be converted. + /// The character to use to replace characters that cannot properly be converted. /// The converted text. public static char[] ToAsciiCharArray(string text, char fail = '?') { @@ -66,7 +66,7 @@ namespace Umbraco.Core.Strings /// /// The input array. /// The output array. - /// The character to used to replace characters that cannot properly be converted. + /// The character to use to replace characters that cannot properly be converted. /// The number of characters in the output array. /// The caller must ensure that the output array is big enough. /// The output array is not big enough. @@ -112,7 +112,7 @@ namespace Umbraco.Core.Strings /// The input position. /// The output array. /// The output position. - /// The character to used to replace characters that cannot properly be converted. + /// The character to use to replace characters that cannot properly be converted. /// /// Adapted from various sources on the 'net including Lucene.Net.Analysis.ASCIIFoldingFilter. /// Input should contain Utf8 characters exclusively and NOT Unicode. diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index c0149ccadb..fa046acd63 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -168,7 +168,6 @@ - @@ -209,11 +208,13 @@ + + @@ -530,7 +531,7 @@ - + @@ -1311,7 +1312,7 @@ - + diff --git a/src/Umbraco.Examine/ExamineExtensions.cs b/src/Umbraco.Examine/ExamineExtensions.cs index 19171ac6b1..43a3ccc196 100644 --- a/src/Umbraco.Examine/ExamineExtensions.cs +++ b/src/Umbraco.Examine/ExamineExtensions.cs @@ -28,6 +28,8 @@ namespace Umbraco.Examine /// internal static readonly Regex CultureIsoCodeFieldNameMatchExpression = new Regex("^([_\\w]+)_([a-z]{2}-[a-z0-9]{2,4})$", RegexOptions.Compiled); + //TODO: We need a public method here to just match a field name against CultureIsoCodeFieldNameMatchExpression + /// /// Returns all index fields that are culture specific (suffixed) /// diff --git a/src/Umbraco.Tests/Cache/DistributedCacheBinderTests.cs b/src/Umbraco.Tests/Cache/DistributedCacheBinderTests.cs index 6e8835b9a5..c092306473 100644 --- a/src/Umbraco.Tests/Cache/DistributedCacheBinderTests.cs +++ b/src/Umbraco.Tests/Cache/DistributedCacheBinderTests.cs @@ -158,7 +158,7 @@ namespace Umbraco.Tests.Cache new TestDefaultCultureAccessor(), TestObjects.GetUmbracoSettings(), TestObjects.GetGlobalSettings(), - Enumerable.Empty(), + new UrlProviderCollection(Enumerable.Empty()), Mock.Of()); // just assert it does not throw diff --git a/src/Umbraco.Tests/Composing/CompositionTests.cs b/src/Umbraco.Tests/Composing/CompositionTests.cs new file mode 100644 index 0000000000..f4478e2add --- /dev/null +++ b/src/Umbraco.Tests/Composing/CompositionTests.cs @@ -0,0 +1,53 @@ +using System; +using Moq; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Composing; +using Umbraco.Core.Logging; + +namespace Umbraco.Tests.Composing +{ + [TestFixture] + public class CompositionTests + { + [Test] + public void FactoryIsResolvable() + { + Func factoryFactory = null; + + var mockedRegister = Mock.Of(); + var mockedFactory = Mock.Of(); + + // the mocked register creates the mocked factory + Mock.Get(mockedRegister) + .Setup(x => x.CreateFactory()) + .Returns(mockedFactory); + + // the mocked register can register a factory factory + Mock.Get(mockedRegister) + .Setup(x => x.Register(It.IsAny>(), Lifetime.Singleton)) + .Callback, Lifetime>((ff, lt) => factoryFactory = ff); + + // the mocked factory can invoke the factory factory + Mock.Get(mockedFactory) + .Setup(x => x.GetInstance(typeof(IFactory))) + .Returns(() => factoryFactory?.Invoke(mockedFactory)); + + var logger = new ProfilingLogger(Mock.Of(), Mock.Of()); + var typeLoader = new TypeLoader(Mock.Of(), "", logger); + var composition = new Composition(mockedRegister, typeLoader, logger, Mock.Of()); + + // create the factory, ensure it is the mocked factory + var factory = composition.CreateFactory(); + Assert.AreSame(mockedFactory, factory); + + // ensure we can get an IFactory instance, + // meaning that it has been properly registered + + var resolved = factory.GetInstance(); + Assert.IsNotNull(resolved); + Assert.AreSame(factory, resolved); + } + } +} diff --git a/src/Umbraco.Tests/Logging/LogviewerTests.cs b/src/Umbraco.Tests/Logging/LogviewerTests.cs index 75e2c66a61..35981f5368 100644 --- a/src/Umbraco.Tests/Logging/LogviewerTests.cs +++ b/src/Umbraco.Tests/Logging/LogviewerTests.cs @@ -1,4 +1,5 @@ -using NUnit.Framework; +using Moq; +using NUnit.Framework; using System; using System.IO; using System.Linq; @@ -47,7 +48,8 @@ namespace Umbraco.Tests.Logging File.Copy(exampleLogfilePath, _newLogfilePath, true); File.Copy(exampleSearchfilePath, _newSearchfilePath, true); - _logViewer = new JsonLogViewer(logsPath: _newLogfileDirPath, searchPath: _newSearchfilePath); + var logger = Mock.Of(); + _logViewer = new JsonLogViewer(logger, logsPath: _newLogfileDirPath, searchPath: _newSearchfilePath); } [OneTimeTearDown] diff --git a/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs b/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs index 3142e1038e..ad2b0220bb 100644 --- a/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs +++ b/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs @@ -15,6 +15,7 @@ using Umbraco.Core.PropertyEditors; using Umbraco.Core.Scoping; using Umbraco.Core.Services; using Umbraco.Core.Services.Changes; +using Umbraco.Core.Strings; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing.Objects; using Umbraco.Tests.Testing.Objects.Accessors; @@ -181,7 +182,8 @@ namespace Umbraco.Tests.PublishedContent globalSettings, new SiteDomainHelper(), Mock.Of(), - Mock.Of()); + Mock.Of(), + new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() })); // invariant is the current default _variationAccesor.VariationContext = new VariationContext(); diff --git a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs index 93c6100a51..f1f38504f1 100644 --- a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs +++ b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs @@ -40,7 +40,7 @@ namespace Umbraco.Tests.Routing { base.SetUp(); - WebRuntimeComponent.CreateRoutes( + WebFinalComponent.CreateRoutes( new TestUmbracoContextAccessor(), TestObjects.GetGlobalSettings(), new SurfaceControllerTypeCollection(Enumerable.Empty()), diff --git a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs index 915199654f..0f99b6b884 100644 --- a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs +++ b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs @@ -81,7 +81,7 @@ namespace Umbraco.Tests.Runtimes var composerTypes = typeLoader.GetTypes() // all of them .Where(x => !x.FullName.StartsWith("Umbraco.Tests.")) // exclude test components - .Where(x => x != typeof(WebRuntimeComposer)); // exclude web runtime + .Where(x => x != typeof(WebInitialComposer)); // exclude web runtime var composers = new Composers(composition, composerTypes, profilingLogger); composers.Compose(); diff --git a/src/Umbraco.Tests/Runtimes/WebRuntimeComponentTests.cs b/src/Umbraco.Tests/Runtimes/WebRuntimeComponentTests.cs index 27e5328f6f..b0d2788b58 100644 --- a/src/Umbraco.Tests/Runtimes/WebRuntimeComponentTests.cs +++ b/src/Umbraco.Tests/Runtimes/WebRuntimeComponentTests.cs @@ -18,7 +18,7 @@ namespace Umbraco.Tests.Runtimes new PluginViewEngine() }; - WebRuntimeComponent.WrapViewEngines(engines); + WebInitialComponent.WrapViewEngines(engines); Assert.That(engines.Count, Is.EqualTo(2)); Assert.That(engines[0], Is.InstanceOf()); @@ -34,7 +34,7 @@ namespace Umbraco.Tests.Runtimes new PluginViewEngine() }; - WebRuntimeComponent.WrapViewEngines(engines); + WebInitialComponent.WrapViewEngines(engines); Assert.That(engines.Count, Is.EqualTo(2)); Assert.That(((ProfilingViewEngine)engines[0]).Inner, Is.InstanceOf()); @@ -50,7 +50,7 @@ namespace Umbraco.Tests.Runtimes profiledEngine }; - WebRuntimeComponent.WrapViewEngines(engines); + WebInitialComponent.WrapViewEngines(engines); Assert.That(engines[0], Is.SameAs(profiledEngine)); } @@ -58,7 +58,7 @@ namespace Umbraco.Tests.Runtimes [Test] public void WrapViewEngines_CollectionIsNull_DoesNotThrow() { - Assert.DoesNotThrow(() => WebRuntimeComponent.WrapViewEngines(null)); + Assert.DoesNotThrow(() => WebInitialComponent.WrapViewEngines(null)); } } } diff --git a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs index 9b1300ee23..1dcc928141 100644 --- a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs @@ -17,6 +17,7 @@ using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; +using Umbraco.Core.Strings; using Umbraco.Core.Sync; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing; @@ -99,7 +100,8 @@ namespace Umbraco.Tests.Scoping new DatabaseDataSource(), Factory.GetInstance(), new SiteDomainHelper(), Factory.GetInstance(), - Mock.Of()); + Mock.Of(), + new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() })); } protected UmbracoContext GetUmbracoContextNu(string url, int templateId = 1234, RouteData routeData = null, bool setSingleton = false, IUmbracoSettingsSection umbracoSettings = null, IEnumerable urlProviders = null) diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs index b5bf9e61f8..c70b96a175 100644 --- a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs +++ b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs @@ -15,6 +15,7 @@ using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; +using Umbraco.Core.Strings; using Umbraco.Core.Sync; using Umbraco.Tests.Testing; using Umbraco.Web; @@ -72,7 +73,8 @@ namespace Umbraco.Tests.Services new DatabaseDataSource(), Factory.GetInstance(), new SiteDomainHelper(), Factory.GetInstance(), - Mock.Of()); + Mock.Of(), + new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() })); } public class LocalServerMessenger : ServerMessengerBase diff --git a/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs b/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs index 0f84319976..29fdc27840 100644 --- a/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs +++ b/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs @@ -116,7 +116,7 @@ namespace Umbraco.Tests.TestHelpers var umbracoSettings = GetUmbracoSettings(); var globalSettings = GetGlobalSettings(); - var urlProviders = Enumerable.Empty(); + var urlProviders = new UrlProviderCollection(Enumerable.Empty()); if (accessor == null) accessor = new TestUmbracoContextAccessor(); diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 20cea53b09..e0622f0c27 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -118,6 +118,7 @@ + diff --git a/src/Umbraco.Tests/Web/Mvc/RenderIndexActionSelectorAttributeTests.cs b/src/Umbraco.Tests/Web/Mvc/RenderIndexActionSelectorAttributeTests.cs index b41aca21c3..0b76de9879 100644 --- a/src/Umbraco.Tests/Web/Mvc/RenderIndexActionSelectorAttributeTests.cs +++ b/src/Umbraco.Tests/Web/Mvc/RenderIndexActionSelectorAttributeTests.cs @@ -71,7 +71,7 @@ namespace Umbraco.Tests.Web.Mvc new TestDefaultCultureAccessor(), TestObjects.GetUmbracoSettings(), globalSettings, - Enumerable.Empty(), + new UrlProviderCollection(Enumerable.Empty()), Mock.Of()); var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of()); @@ -100,7 +100,7 @@ namespace Umbraco.Tests.Web.Mvc new TestDefaultCultureAccessor(), TestObjects.GetUmbracoSettings(), globalSettings, - Enumerable.Empty(), + new UrlProviderCollection(Enumerable.Empty()), Mock.Of()); var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of()); @@ -129,7 +129,7 @@ namespace Umbraco.Tests.Web.Mvc new TestDefaultCultureAccessor(), TestObjects.GetUmbracoSettings(), globalSettings, - Enumerable.Empty(), + new UrlProviderCollection(Enumerable.Empty()), Mock.Of()); var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of()); @@ -158,7 +158,7 @@ namespace Umbraco.Tests.Web.Mvc new TestDefaultCultureAccessor(), TestObjects.GetUmbracoSettings(), globalSettings, - Enumerable.Empty(), + new UrlProviderCollection(Enumerable.Empty()), Mock.Of()); var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of()); diff --git a/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs b/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs index 846ee4b7d8..7de2dd1aad 100644 --- a/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs +++ b/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs @@ -46,7 +46,7 @@ namespace Umbraco.Tests.Web.Mvc new TestDefaultCultureAccessor(), TestObjects.GetUmbracoSettings(), globalSettings, - Enumerable.Empty(), + new UrlProviderCollection(Enumerable.Empty()), Mock.Of()); var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of()); @@ -73,7 +73,7 @@ namespace Umbraco.Tests.Web.Mvc new TestDefaultCultureAccessor(), TestObjects.GetUmbracoSettings(), globalSettings, - Enumerable.Empty(), + new UrlProviderCollection(Enumerable.Empty()), Mock.Of()); var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of()); @@ -103,7 +103,7 @@ namespace Umbraco.Tests.Web.Mvc new TestDefaultCultureAccessor(), Mock.Of(section => section.WebRouting == Mock.Of(routingSection => routingSection.UrlProviderMode == "Auto")), globalSettings, - Enumerable.Empty(), + new UrlProviderCollection(Enumerable.Empty()), Mock.Of()); var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of()); @@ -140,7 +140,7 @@ namespace Umbraco.Tests.Web.Mvc new TestDefaultCultureAccessor(), Mock.Of(section => section.WebRouting == webRoutingSettings), globalSettings, - Enumerable.Empty(), + new UrlProviderCollection(Enumerable.Empty()), Mock.Of()); var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of()); diff --git a/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs b/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs index 8a4e3e515b..5e9208faf9 100644 --- a/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs +++ b/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs @@ -104,7 +104,7 @@ namespace Umbraco.Tests.Web new TestDefaultCultureAccessor(), Mock.Of(section => section.WebRouting == Mock.Of(routingSection => routingSection.UrlProviderMode == "Auto")), globalSettings, - new[] { testUrlProvider.Object }, + new UrlProviderCollection(new[] { testUrlProvider.Object }), Mock.Of()); using (var reference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of())) diff --git a/src/Umbraco.Web.UI.Client/setupgulp.bat b/src/Umbraco.Web.UI.Client/setupgulp.bat deleted file mode 100644 index 6a53e11b71..0000000000 --- a/src/Umbraco.Web.UI.Client/setupgulp.bat +++ /dev/null @@ -1,22 +0,0 @@ -@ECHO OFF - -ECHO. -ECHO. -ECHO This will only work when you have NPM available on the command line -ECHO Works great with NodeJS Portable - https://gareth.flowers/nodejs-portable/ -ECHO. -ECHO. -set /P c=Are you sure you want to continue [Y/N]? -if /I "%c%" EQU "Y" goto :setupgulp -if /I "%c%" EQU "N" goto :eof - -:setupgulp -call npm install -call npm -g install gulp -call npm -g install gulp-cli - -ECHO. -ECHO. -ECHO You should now be able to run: gulp build or gulp dev -ECHO. -ECHO. \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js index e5b0dbf201..80b5f4ed39 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js @@ -331,8 +331,35 @@ // This is a helper method to reduce the amount of code repitition for actions: Save, Publish, SendToPublish function performSave(args) { - - + + // Check that all variants for publishing have a name. + if (args.action === "publish" || args.action === "sendToPublish") { + + if ($scope.content.variants) { + var iVariant; + for (var i = 0; i < $scope.content.variants.length; i++) { + iVariant = $scope.content.variants[i]; + + iVariant.notifications = [];// maybe not needed, need to investigate. + + if(iVariant.publish === true) { + if (iVariant.name == null) { + + var tokens = [iVariant.language.name]; + + localizationService.localize("publish_contentPublishedFailedByMissingName", tokens).then(function (value) { + iVariant.notifications.push({"message": value, "type": 1}); + }); + + return $q.reject(); + } + } + } + } + + } + + //Used to check validility of nested form - coming from Content Apps mostly //Set them all to be invalid var fieldsToRollback = checkValidility(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js index 0fdfe6a457..d4b617f94b 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js @@ -344,8 +344,8 @@ loadAuditTrail(true); loadRedirectUrls(); setNodePublishStatus(); - updateCurrentUrls(); } + updateCurrentUrls(); }); //ensure to unregister from all events! diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditornavigationitem.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditornavigationitem.directive.js index 3a0dbb06d8..ae725ef25e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditornavigationitem.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditornavigationitem.directive.js @@ -42,7 +42,7 @@ item: '=', onOpen: '&', onOpenAnchor: '&', - index: '@' + hotkey: '<' } }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbcheckbox.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbcheckbox.directive.js new file mode 100644 index 0000000000..47381a15c0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbcheckbox.directive.js @@ -0,0 +1,72 @@ +/** +@ngdoc directive +@name umbraco.directives.directive:umbCheckbox +@restrict E +@scope + +@description +Added in Umbraco version 7.14.0 Use this directive to render an umbraco checkbox. + +

Markup example

+
+    
+ + + + +
+
+ +@param {boolean} model Set to true or false to set the checkbox to checked or unchecked. +@param {string} input-id Set the id of the checkbox. +@param {string} value Set the value of the checkbox. +@param {string} name Set the name of the checkbox. +@param {string} text Set the text for the checkbox label. +@param {string} server-validation-field Set the val-server-field of the checkbox. +@param {boolean} disabled Set the checkbox to be disabled. +@param {boolean} required Set the checkbox to be required. +@param {string} on-change Callback when the value of the checkbox changed by interaction. + +**/ + +(function () { + 'use strict'; + + + function UmbCheckboxController($timeout) { + + var vm = this; + + vm.callOnChange = function() { + $timeout(function() { + vm.onChange({model:vm.model, value:vm.value}); + }, 0); + } + + } + + + var component = { + templateUrl: 'views/components/forms/umb-checkbox.html', + controller: UmbCheckboxController, + controllerAs: 'vm', + bindings: { + model: "=", + inputId: "@", + value: "@", + name: "@", + text: "@", + serverValidationField: "@", + disabled: "<", + required: "<", + onChange: "&" + } + }; + + angular.module('umbraco.directives').component('umbCheckbox', component); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbradiobutton.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbradiobutton.directive.js new file mode 100644 index 0000000000..351ba2fee2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbradiobutton.directive.js @@ -0,0 +1,57 @@ +/** +@ngdoc directive +@name umbraco.directives.directive:umbRadiobutton +@restrict E +@scope + +@description +Added in Umbraco version 7.14.0 Use this directive to render an umbraco radio button. + +

Markup example

+
+    
+ + + + +
+
+ +@param {boolean} model Set to true or false to set the radiobutton to checked or unchecked. +@param {string} value Set the value of the radiobutton. +@param {string} name Set the name of the radiobutton. +@param {string} text Set the text for the radiobutton label. +@param {boolean} disabled Set the radiobutton to be disabled. +@param {boolean} required Set the radiobutton to be required. + +**/ + +(function () { + 'use strict'; + + function RadiobuttonDirective() { + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/forms/umb-radiobutton.html', + scope: { + model: "=", + value: "@", + name: "@", + text: "@", + disabled: "=", + required: "=" + } + }; + + return directive; + + } + + angular.module('umbraco.directives').directive('umbRadiobutton', RadiobuttonDirective); + +})(); 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 6472dd3d38..e95a5992d1 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 @@ -19,9 +19,15 @@ angular.module("umbraco.directives") promises.push(assetsService.loadJs("lib/tinymce/tinymce.min.js", scope)); } - var toolbar = ["code", "styleselect", "bold", "italic", "alignleft", "aligncenter", "alignright", "bullist", "numlist", "link", "umbmediapicker", "umbembeddialog"]; - if (scope.configuration && scope.configuration.toolbar) { - toolbar = scope.configuration.toolbar; + var editorConfig = scope.configuration ? scope.configuration : null; + if (!editorConfig || angular.isString(editorConfig)) { + editorConfig = tinyMceService.defaultPrevalues(); + //for the grid by default, we don't want to include the macro toolbar + editorConfig.toolbar = _.without(editorConfig, "umbmacro"); + } + //make sure there's a max image size + if (!scope.configuration.maxImageSize && scope.configuration.maxImageSize !== 0) { + editorConfig.maxImageSize = tinyMceService.defaultPrevalues().maxImageSize; } //stores a reference to the editor @@ -29,9 +35,9 @@ angular.module("umbraco.directives") promises.push(tinyMceService.getTinyMceEditorConfig({ htmlId: scope.uniqueId, - stylesheets: scope.configuration ? scope.configuration.stylesheets : null, - toolbar: toolbar, - mode: scope.configuration.mode + stylesheets: editorConfig.stylesheets, + toolbar: editorConfig.toolbar, + mode: editorConfig.mode })); // pin toolbar to top of screen if we have focus and it scrolls off the screen @@ -46,9 +52,16 @@ angular.module("umbraco.directives") $q.all(promises).then(function (result) { - var tinyMceEditorConfig = result[promises.length - 1]; + var standardConfig = result[promises.length - 1]; - tinyMceEditorConfig.setup = function (editor) { + //create a baseline Config to extend upon + var baseLineConfigObj = { + maxImageSize: editorConfig.maxImageSize + }; + + angular.extend(baseLineConfigObj, standardConfig); + + baseLineConfigObj.setup = function (editor) { //set the reference tinyMceEditor = editor; @@ -111,7 +124,7 @@ angular.module("umbraco.directives") //the elements needed $timeout(function () { tinymce.DOM.events.domLoaded = true; - tinymce.init(tinyMceEditorConfig); + tinymce.init(baseLineConfigObj); }, 150, false); } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcontentgrid.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcontentgrid.directive.js index 3994770c8e..f226d924af 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcontentgrid.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcontentgrid.directive.js @@ -117,9 +117,11 @@ Use this directive to generate a list of content items presented as a flexbox gr }; scope.clickItemName = function(item, $event, $index) { - if(scope.onClickName) { + if(scope.onClickName && !($event.metaKey || $event.ctrlKey)) { scope.onClickName(item, $event, $index); + $event.preventDefault(); } + $event.stopPropagation(); }; } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbtable.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbtable.directive.js index ae41073c0d..3f1929e97d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbtable.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbtable.directive.js @@ -117,10 +117,11 @@ var vm = this; vm.clickItem = function (item, $event) { - if (vm.onClick) { + if (vm.onClick && !($event.metaKey || $event.ctrlKey)) { vm.onClick({ item: item}); - $event.stopPropagation(); + $event.preventDefault(); } + $event.stopPropagation(); }; vm.selectItem = function (item, $index, $event) { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfileupload.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfileupload.directive.js index f93e41d0c7..6a8ffa7969 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfileupload.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfileupload.directive.js @@ -15,10 +15,12 @@ function umbFileUpload() { el.on('change', function (event) { var files = event.target.files; //emit event upward - scope.$emit("filesSelected", { files: files }); + scope.$emit("filesSelected", { files: files }); + //clear the element value - this allows us to pick the same file again and again + el.val(''); }); } }; } -angular.module('umbraco.directives').directive("umbFileUpload", umbFileUpload); \ No newline at end of file +angular.module('umbraco.directives').directive("umbFileUpload", umbFileUpload); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js index b9bc4eb499..1efcf84dcb 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js @@ -301,6 +301,19 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica return allProps; }, + /** + * @ngdoc method + * @name umbraco.services.contentEditingHelper#buildCompositeVariantId + * @methodOf umbraco.services.contentEditingHelper + * @function + * + * @description + * Returns a id for the variant that is unique between all variants on the content + */ + buildCompositeVariantId: function (variant) { + return (variant.language ? variant.language.culture : "invariant") + "_" + (variant.segment ? variant.segment : ""); + }, + /** * @ngdoc method 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 6862b11565..6304f4b8a0 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 @@ -59,11 +59,11 @@ * Method for internal use, based on the collection of layouts passed, the method selects either * any previous layout from local storage, or picks the first allowed layout * - * @param {Number} nodeId The id of the current node displayed in the content editor + * @param {Any} id The identifier of the current node or application displayed in the content editor * @param {Array} availableLayouts Array of all allowed layouts, available from $scope.model.config.layouts */ - function getLayout(nodeId, availableLayouts) { + function getLayout(id, availableLayouts) { var storedLayouts = []; @@ -74,8 +74,8 @@ 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); + if (isMatchingLayout(id, layout)) { + return setLayout(id, layout, availableLayouts); } } @@ -93,12 +93,12 @@ * @description * Changes the current layout used by the listview to the layout passed in. Stores selection in localstorage * - * @param {Number} nodeID Id of the current node displayed in the content editor + * @param {Any} id The identifier of the current node or application displayed in the content editor * @param {Object} selectedLayout Layout selected as the layout to set as the current layout * @param {Array} availableLayouts Array of all allowed layouts, available from $scope.model.config.layouts */ - function setLayout(nodeId, selectedLayout, availableLayouts) { + function setLayout(id, selectedLayout, availableLayouts) { var activeLayout = {}; var layoutFound = false; @@ -118,7 +118,7 @@ activeLayout = getFirstAllowedLayout(availableLayouts); } - saveLayoutInLocalStorage(nodeId, activeLayout); + saveLayoutInLocalStorage(id, activeLayout); return activeLayout; @@ -132,11 +132,11 @@ * @description * Stores a given layout as the current default selection in local storage * - * @param {Number} nodeId Id of the current node displayed in the content editor + * @param {Any} id The identifier of the current node or application displayed in the content editor * @param {Object} selectedLayout Layout selected as the layout to set as the current layout */ - function saveLayoutInLocalStorage(nodeId, selectedLayout) { + function saveLayoutInLocalStorage(id, selectedLayout) { var layoutFound = false; var storedLayouts = []; @@ -147,7 +147,7 @@ if (storedLayouts.length > 0) { for (var i = 0; storedLayouts.length > i; i++) { var layout = storedLayouts[i]; - if (layout.nodeId === nodeId) { + if (isMatchingLayout(id, layout)) { layout.path = selectedLayout.path; layoutFound = true; } @@ -156,7 +156,7 @@ if (!layoutFound) { var storageObject = { - "nodeId": nodeId, + "id": id, "path": selectedLayout.path }; storedLayouts.push(storageObject); @@ -510,6 +510,12 @@ }; } + + function isMatchingLayout(id, layout) { + // legacy format uses "nodeId", be sure to look for both + return layout.id === id || layout.nodeId === id; + } + var service = { getLayout: getLayout, diff --git a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js index 76fcc2d1d8..ba8334d307 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js @@ -26,6 +26,8 @@ function navigationService($routeParams, $location, $q, $timeout, $injector, eve navReadyPromise.resolve(mainTreeApi); }); + + //A list of query strings defined that when changed will not cause a reload of the route var nonRoutingQueryStrings = ["mculture", "cculture", "lq"]; var retainedQueryStrings = ["mculture"]; diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js index 4fe7f8b7d7..ce4bf6077c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js @@ -427,8 +427,8 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s data["rel"] = img.id; data["data-id"] = img.id; } - - editor.insertContent(editor.dom.createHTML('img', data)); + + editor.selection.setContent(editor.dom.createHTML('img', data)); $timeout(function () { var imgElm = editor.dom.get('__mcenew'); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js index 4650458abc..6a15c0f553 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js @@ -350,6 +350,9 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS parent.children.splice(parent.children.indexOf(treeNode), 1); parent.hasChildren = parent.children.length !== 0; + + //Notify that the node has been removed + eventsService.emit("treeService.removeNode", { node: treeNode }); }, /** diff --git a/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js b/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js index 8e5c093462..c426d0d955 100644 --- a/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js +++ b/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js @@ -9,7 +9,7 @@ * * @param {navigationService} navigationService A reference to the navigationService */ -function NavigationController($scope, $rootScope, $location, $log, $q, $routeParams, $timeout, $cookies, treeService, appState, navigationService, keyboardService, historyService, eventsService, angularHelper, languageResource, contentResource) { +function NavigationController($scope, $rootScope, $location, $log, $q, $routeParams, $timeout, $cookies, treeService, appState, navigationService, keyboardService, historyService, eventsService, angularHelper, languageResource, contentResource, editorState) { //this is used to trigger the tree to start loading once everything is ready var treeInitPromise = $q.defer(); @@ -249,6 +249,20 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar $scope.infiniteMode = args && args.editors.length > 0 ? true : false; })); + evts.push(eventsService.on("treeService.removeNode", function (e, args) { + //check to see if the current page has been removed + + var currentEditorState = editorState.getCurrent() + if (currentEditorState && currentEditorState.id.toString() === args.node.id.toString()) { + //current page is loaded, so navigate to root + var section = appState.getSectionState("currentSection"); + $location.path("/" + section); + } + })); + + + + /** * Based on the current state of the application, this configures the scope variables that control the main tree and language drop down */ diff --git a/src/Umbraco.Web.UI.Client/src/less/belle.less b/src/Umbraco.Web.UI.Client/src/less/belle.less index 9458b11f46..8c0df988d1 100644 --- a/src/Umbraco.Web.UI.Client/src/less/belle.less +++ b/src/Umbraco.Web.UI.Client/src/less/belle.less @@ -117,6 +117,7 @@ @import "components/umb-confirm-action.less"; @import "components/umb-keyboard-shortcuts-overview.less"; @import "components/umb-checkbox-list.less"; +@import "components/umb-form-check.less"; @import "components/umb-locked-field.less"; @import "components/umb-tabs.less"; @import "components/umb-load-indicator.less"; @@ -166,6 +167,7 @@ @import "components/umb-property-file-upload.less"; @import "components/users/umb-user-cards.less"; +@import "components/users/umb-user-table.less"; @import "components/users/umb-user-details.less"; @import "components/users/umb-user-group-picker-list.less"; @import "components/users/umb-user-group-preview.less"; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/editor.less b/src/Umbraco.Web.UI.Client/src/less/components/editor.less index 0770a895c0..28d56058d2 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/editor.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/editor.less @@ -195,6 +195,7 @@ a.umb-variant-switcher__toggle { justify-content: space-between; align-items: center; border-bottom: 1px solid @gray-9; + position: relative; &:hover .umb-variant-switcher__name-wrapper { } @@ -246,10 +247,15 @@ a.umb-variant-switcher__toggle { } .umb-variant-switcher__split-view { - font-size: 13px; - display: none; - padding: 16px 20px; - + font-size: 13px; + display: none; + padding: 16px 20px; + position: absolute; + right: 0; + top: 0; + bottom: 0; + background-color: @white; + &:hover { background-color: @ui-option-hover; color: @ui-option-type-hover; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/overlays.less b/src/Umbraco.Web.UI.Client/src/less/components/overlays.less index 6e9c79a0c2..9205dc9c5f 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/overlays.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/overlays.less @@ -250,3 +250,11 @@ .form-horizontal .umb-overlay .controls { margin-left: 0 !important; } + +.umb-overlay .text-error { + color: @formErrorText; +} + +.umb-overlay .text-success { + color: @formSuccessText; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less index 8d740a866c..b66ab40335 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less @@ -166,7 +166,9 @@ body.touch .umb-tree { } .umb-tree .umb-tree-node-checked > .umb-tree-item__inner > i[class^="icon-"], -.umb-tree .umb-tree-node-checked > .umb-tree-item__inner > i[class*=" icon-"] { +.umb-tree .umb-tree-node-checked > .umb-tree-item__inner > i[class*=" icon-"], +.umb-tree .umb-tree-node-checked .umb-search-group-item-name > i[class^="icon-"], +.umb-tree .umb-tree-node-checked .umb-search-group-item-name > i[class*=" icon-"] { font-family: 'icomoon' !important; color: @green !important; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-checkbox-list.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-checkbox-list.less index 02f30f6f35..985e57bea5 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-checkbox-list.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-checkbox-list.less @@ -1,3 +1,6 @@ +@checkboxWidth: 15px; +@checkboxHeight: 15px; + .umb-checkbox-list { list-style: none; margin-left: 0; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-content-grid.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-content-grid.less index d2fa2be0c7..2a4a3be2e4 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-content-grid.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-content-grid.less @@ -13,7 +13,6 @@ user-select: none; box-shadow: 0 1px 1px 0 rgba(0,0,0,0.16); border-radius: 3px; - } .umb-content-grid__item.-selected { @@ -59,7 +58,8 @@ display: inline-flex; color: @ui-option-type; - &:hover { + &:hover, &:focus { + text-decoration: none; color:@ui-option-type-hover; } } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-navigation-item.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-navigation-item.less index 09c8fa9aa0..e6b3fdbfa9 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-navigation-item.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-navigation-item.less @@ -61,6 +61,9 @@ .show-validation .umb-sub-views-nav-item > a.-has-error { color: @red; + &::after { + background-color: @red; + } } .umb-sub-views-nav-item .icon { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-form-check.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-form-check.less new file mode 100644 index 0000000000..fbc9cd8f97 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-form-check.less @@ -0,0 +1,136 @@ +@checkboxWidth: 16px; +@checkboxHeight: 16px; + +.umb-form-check { + display: flex; + flex-wrap: wrap; + align-items: center; + position: relative; + padding: 0; + margin: 0; + min-height: 22px; + line-height: 22px; + cursor: pointer !important; + + &__text { + margin: 0 0 0 26px; + position: relative; + top: 0; + } + + &__input { + position: absolute; + top: 0; + left: 0; + opacity: 0; + + &:checked ~ .umb-form-check__state .umb-form-check__check { + border-color: @ui-option-type; + } + + &:focus:checked ~ .umb-form-check .umb-form-check__check, + &:focus ~ .umb-form-check__state .umb-form-check__check { + border-color: @inputBorderFocus; + } + + &:checked ~ .umb-form-check__state { + .umb-form-check__check { + // This only happens if the state has a radiobutton modifier + .umb-form-check--radiobutton & { + &:before { + opacity: 1; + transform: scale(1); + } + } + // This only happens if state has the checkbox modifier + .umb-form-check--checkbox & { + &:before { + width: @checkboxWidth; + height: @checkboxHeight; + } + } + } + // This only happens if state has the checkbox modifier + .umb-form-check--checkbox & { + .umb-form-check__icon { + opacity: 1; + } + } + } + } + + &__state { + display: flex; + height: 18px; + margin-top: 2px; + position: absolute; + top: 0; + left: 0; + } + + &__check { + display: flex; + position: relative; + background: @white; + border: 1px solid @inputBorder; + width: @checkboxWidth; + height: @checkboxHeight; + + &:before { + content: ""; + background: @ui-option-type; + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; + margin: auto; + } + // This only happens if state has the radiobutton modifier + .umb-form-check--radiobutton & { + border-radius: 100%; + + &:before { + width: 10px; + height: 10px; + border-radius: 100%; + opacity: 0; + transform: scale(0); + transition: .15s ease-out; + } + } + // This only happens if state has the checkbox modifier + .umb-form-check--checkbox & { + &:before { + width: 0; + height: 0; + transition: .05s ease-out; + } + } + } + + &__icon { + color: @white; + text-align: center; + font-size: 12px; + opacity: 0; + transition: .2s ease-out; + + &:before { + display: flex; + justify-content: center; + align-items: center; + position: absolute; + top: 0; + right: 0; + left: 0; + bottom: 0; + margin: auto; + } + } + + &.umb-form-check--disabled { + cursor: not-allowed !important; + opacity: 0.5; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-list.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-list.less index 846b7d7aee..bfe41ccaac 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-list.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-list.less @@ -22,6 +22,10 @@ a.umb-list-item:focus { opacity: 0.6; } +.umb-list-item--error { + color: @red; +} + .umb-list-item:hover .umb-list-checkbox, .umb-list-item--selected .umb-list-checkbox { opacity: 1; @@ -34,4 +38,4 @@ a.umb-list-item:focus { .umb-list-checkbox--visible { opacity: 1; -} \ No newline at end of file +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-packages.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-packages.less index 45170c9602..daee29351c 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-packages.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-packages.less @@ -261,6 +261,7 @@ .umb-package-details { display: flex; + flex-flow: row wrap; } a.umb-package-details__back-link { @@ -280,6 +281,7 @@ a.umb-package-details__back-link { flex: 1 1 auto; margin-right: 20px; width: calc(~'100%' - ~'@{sidebarwidth}' - ~'20px'); // Make sure that the main content area doesn't gets affected by inline styling + min-width: 500px; } .umb-package-details__sidebar { 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 291eef43e0..a0c3f02e40 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 @@ -151,14 +151,14 @@ input.umb-table__input { // Show checkmark when checked, hide file icon .umb-table-row--selected { - /* + .umb-table-body__fileicon { display: none; } .umb-table-body__checkicon { display: inline-block; } - */ + &::before { content: ""; position: absolute; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-cards.less b/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-cards.less index e24f68078b..190f977880 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-cards.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-cards.less @@ -17,6 +17,10 @@ outline: none; text-decoration: none !important; } + +.umb-user-card.-selectable { + cursor: pointer; +} .umb-user-card.-selected { &::before { content: ""; @@ -31,7 +35,6 @@ box-shadow: 0 0 4px 0 darken(@ui-selected-border, 20), inset 0 0 2px 0 darken(@ui-selected-border, 20); pointer-events: none; } - } .umb-user-card__content { @@ -44,14 +47,18 @@ box-sizing: border-box; display: flex; flex-direction: column; - cursor: pointer; max-width: 100%; } .umb-user-card__goToUser { - &:hover { + &:hover, &:focus { + text-decoration: none; .umb-user-card__name { - text-decoration: underline; + text-decoration: underline; + color: @ui-option-type-hover; + } + .umb-avatar { + border: 1px solid @ui-option-type-hover; } } } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-table.less b/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-table.less new file mode 100644 index 0000000000..d8813e3815 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-table.less @@ -0,0 +1,81 @@ +.umb-user-table { + + .umb-user-table-col-avatar { + flex: 0 0 32px; + padding: 15px 0; + } + + .umb-table-cell a { + &:hover, &:focus { + .umb-avatar { + border: 1px solid @ui-option-type-hover; + } + } + } + + .umb-table-body .umb-table-cell.umb-table__name { + margin: 0; + padding: 0; + a { + display: flex; + padding: 6px 2px; + height: 42px; + span { + margin: auto 14px; + } + } + } + .umb-table-cell.umb-table__name a { + &:hover, &:focus { + text-decoration: underline; + } + } + + .umb-user-table-row { + cursor: default; + .umb-checkmark { + visibility: hidden; + } + } + + .umb-user-table-row.-selectable { + cursor: pointer; + + + } + + &.-has-selection { + .umb-user-table-row.-selectable { + .umb-checkmark { + visibility: visible; + } + } + } + + .umb-user-table-row.-selectable:hover { + .umb-checkmark { + visibility: visible; + } + } + + .umb-user-table-row.-selected { + + .umb-checkmark { + visibility: visible; + } + + &::before { + content: ""; + position: absolute; + z-index:1; + top: 1px; + left: 1px; + right: 1px; + bottom: 1px; + border: 2px solid @ui-selected-border; + box-shadow: 0 0 2px 0 fade(@ui-selected-border, 80%); + pointer-events: none; + } + } + +} diff --git a/src/Umbraco.Web.UI.Client/src/less/listview.less b/src/Umbraco.Web.UI.Client/src/less/listview.less index 8b5a295752..c77e4fd1a4 100644 --- a/src/Umbraco.Web.UI.Client/src/less/listview.less +++ b/src/Umbraco.Web.UI.Client/src/less/listview.less @@ -30,7 +30,7 @@ position: absolute; padding: 5px 8px; pointer-events: none; - top: 0; + top: 2px; } input[type="text"] { diff --git a/src/Umbraco.Web.UI.Client/src/less/pages/login.less b/src/Umbraco.Web.UI.Client/src/less/pages/login.less index 4e7937830c..e36acdc273 100644 --- a/src/Umbraco.Web.UI.Client/src/less/pages/login.less +++ b/src/Umbraco.Web.UI.Client/src/less/pages/login.less @@ -13,23 +13,25 @@ border: none; border-radius: 0; overflow-y: auto; - background-color: @purple-d2; + background-color: @blueNight; } .login-overlay__background-image { background-position: center center; background-repeat: no-repeat; background-size: cover; + background-image: url('../img/login.jpg'); width: 100%; height: 100%; position: absolute; - opacity: 0.05; } .login-overlay__logo { position: absolute; top: 22px; left: 25px; + width: 30px; + height: 30px; z-index: 1; } 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 307f32788e..e021a577a5 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -18,7 +18,16 @@ &-push { float:right; - } + } + + &--list{ + float: left; + } + + &__item{ + line-height: 1; + margin: 0 0 5px; + } } .umb-property-editor-tiny { @@ -69,6 +78,11 @@ } } +.umb-property .alert { + border-radius: 3px; +} + + // // Content picker @@ -213,7 +227,7 @@ margin: 24px 0 0; display: flex; } - + &__input { width: 100%; &-wrap{ diff --git a/src/Umbraco.Web.UI.Client/src/less/sections.less b/src/Umbraco.Web.UI.Client/src/less/sections.less index cf362a67f0..6388369b51 100644 --- a/src/Umbraco.Web.UI.Client/src/less/sections.less +++ b/src/Umbraco.Web.UI.Client/src/less/sections.less @@ -37,7 +37,8 @@ ul.sections>li>a::after { height: 4px; width: 100%; background-color: @pinkLight; - position: absolute; + position: absolute; + left: 0; bottom: -4px; border-radius: 3px 3px 0 0; opacity: 0; diff --git a/src/Umbraco.Web.UI.Client/src/less/variables.less b/src/Umbraco.Web.UI.Client/src/less/variables.less index 2d32f0e088..19098b8a98 100644 --- a/src/Umbraco.Web.UI.Client/src/less/variables.less +++ b/src/Umbraco.Web.UI.Client/src/less/variables.less @@ -41,8 +41,8 @@ @purple-washed: #F6F3FD; // UI Colors -@red-d1: #F02E28;// currently used for validation, and is hard coded inline in various html places :/ -@red: #D42054;// updated 2019 - should be used as validation! and is already in some cases like the .umb-validation-label +@red-d1: #F02E28; +@red: #D42054;// updated 2019 @red-l1: #e22c60;// updated 2019 @red-l2: #FE8B88; @red-l3: #FFB2B0; @@ -508,7 +508,7 @@ @warningBorder: transparent; @errorText: @white; -@errorBackground: @red-d1; +@errorBackground: @red; @errorBorder: transparent; @successText: @white; diff --git a/src/Umbraco.Web.UI.Client/src/preview/preview.controller.js b/src/Umbraco.Web.UI.Client/src/preview/preview.controller.js index 7d6584d2f1..4733c58556 100644 --- a/src/Umbraco.Web.UI.Client/src/preview/preview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/preview/preview.controller.js @@ -113,7 +113,12 @@ var app = angular.module("umbraco.preview", ['umbraco.resources', 'umbraco.servi $scope.exitPreview = function () { var culture = $location.search().culture || getParameterByName("culture"); - var relativeUrl = "/" + $scope.pageId +'?culture='+ culture; + var relativeUrl = "/" + $scope.pageId; + + if(culture){ + relativeUrl +='?culture='+ culture; + } + window.top.location.href = "../preview/end?redir=" + encodeURIComponent(relativeUrl); }; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/copy/copy.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/copy/copy.controller.js index 060e17a55e..83b73b408d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/copy/copy.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/copy/copy.controller.js @@ -78,7 +78,7 @@ // method to select a search result function selectResult(evt, result) { result.selected = result.selected === true ? false : true; - nodeSelectHandler(evt, { event: evt, node: result }); + nodeSelectHandler({ event: evt, node: result }); } //callback when there are search results @@ -96,7 +96,7 @@ // Mini list view $scope.selectListViewNode = function (node) { node.selected = node.selected === true ? false : true; - nodeSelectHandler({}, { node: node }); + nodeSelectHandler({ node: node }); }; $scope.closeMiniListView = function () { diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypepicker/datatypepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypepicker/datatypepicker.controller.js index 9836b72468..c86f55b255 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypepicker/datatypepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypepicker/datatypepicker.controller.js @@ -10,7 +10,7 @@ (function() { "use strict"; - function DataTypePicker($scope, dataTypeResource, dataTypeHelper, contentTypeResource, localizationService, editorService) { + function DataTypePicker($scope, $filter, dataTypeResource, dataTypeHelper, contentTypeResource, localizationService, editorService) { var vm = this; @@ -119,13 +119,28 @@ $scope.model.itemDetails = null; if (vm.searchTerm) { - vm.showFilterResult = true; vm.showTabs = false; + + var regex = new RegExp(vm.searchTerm, "i"); + vm.filterResult = { + userConfigured: filterCollection(vm.userConfigured, regex), + typesAndEditors: filterCollection(vm.typesAndEditors, regex) + }; } else { - vm.showFilterResult = false; + vm.filterResult = null; vm.showTabs = true; } + } + function filterCollection(collection, regex) { + return _.map(_.keys(collection), function (key) { + return { + group: key, + dataTypes: $filter('filter')(collection[key], function (dataType) { + return regex.test(dataType.name) || regex.test(dataType.alias); + }) + } + }); } function showDetailsOverlay(property) { @@ -201,4 +216,4 @@ angular.module("umbraco").controller("Umbraco.Editors.DataTypePickerController", DataTypePicker); -})(); \ No newline at end of file +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypepicker/datatypepicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypepicker/datatypepicker.html index f3f991c63e..43933f8051 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypepicker/datatypepicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypepicker/datatypepicker.html @@ -79,13 +79,13 @@ -
+
-
-
-
{{key}}
+
+
+
{{result.group}}
    -
  • @@ -101,11 +101,11 @@
-
-
-
{{key}}
+
+
+
{{result.group}}
    -
  • diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/move/move.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/move/move.controller.js index c2a66dddf1..7e1e4b9047 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/move/move.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/move/move.controller.js @@ -79,7 +79,7 @@ // method to select a search result function selectResult(evt, result) { result.selected = result.selected === true ? false : true; - nodeSelectHandler(evt, { event: evt, node: result }); + nodeSelectHandler({ event: evt, node: result }); } //callback when there are search results @@ -96,7 +96,7 @@ // Mini list view $scope.selectListViewNode = function (node) { node.selected = node.selected === true ? false : true; - nodeSelectHandler({}, { node: node }); + nodeSelectHandler({ node: node }); }; $scope.closeMiniListView = function () { diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-login.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-login.html index d6ec18667c..d5dc203d67 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-login.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-login.html @@ -2,10 +2,10 @@
    - +
    - - -

    -
    - +
    -
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-content-header.html b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-content-header.html index 227a08c54f..d3bf14b58c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-content-header.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-content-header.html @@ -38,7 +38,7 @@ {{vm.currentVariant.language.name}} - + {{variant.language.name}} diff --git a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-navigation-item.html b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-navigation-item.html index 7d84cf830f..68a3da435b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-navigation-item.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-navigation-item.html @@ -1,13 +1,13 @@ + tabindex="-1" + ng-href="" + ng-click="vm.clicked()" + hotkey="{{::vm.hotkey}}" + hotkey-when-hidden="true" + ng-class="{'is-active': vm.item.active, '-has-error': vm.item.hasError}"> {{ vm.item.name }} -
{{vm.item.badge.count}}
+
{{vm.item.badge.count}}
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/forms/umb-checkbox.html b/src/Umbraco.Web.UI.Client/src/views/components/forms/umb-checkbox.html new file mode 100644 index 0000000000..d40263c6b6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/forms/umb-checkbox.html @@ -0,0 +1,19 @@ + diff --git a/src/Umbraco.Web.UI.Client/src/views/components/forms/umb-radiobutton.html b/src/Umbraco.Web.UI.Client/src/views/components/forms/umb-radiobutton.html new file mode 100644 index 0000000000..877e55a1c5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/forms/umb-radiobutton.html @@ -0,0 +1,13 @@ + diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-content-grid.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-content-grid.html index 487be4af87..ce85537d7c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-content-grid.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-content-grid.html @@ -10,10 +10,13 @@
- +
  • diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-table.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-table.html index efd212b517..65955f31eb 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-table.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-table.html @@ -40,7 +40,8 @@
- diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.assigndomain.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.assigndomain.controller.js index 0f27f3046c..53f97e9286 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.assigndomain.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.assigndomain.controller.js @@ -46,8 +46,7 @@ if (data.language !== "undefined") { var lang = vm.languages.filter(function (l) { - return matchLanguageById(l, data.language.Id); - + return matchLanguageById(l, data.language); }); if (lang.length > 0) { vm.language = lang[0]; @@ -116,6 +115,7 @@ if(response.valid) { vm.submitButtonState = "success"; + closeDialog(); // show validation messages for each domain } else { diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.copy.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.copy.controller.js index a1e74cc207..eac489ab08 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.copy.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.copy.controller.js @@ -93,7 +93,7 @@ angular.module("umbraco").controller("Umbraco.Editors.Content.CopyController", // method to select a search result $scope.selectResult = function (evt, result) { result.selected = result.selected === true ? false : true; - nodeSelectHandler(evt, { event: evt, node: result }); + nodeSelectHandler({ event: evt, node: result }); }; //callback when there are search results diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.move.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.move.controller.js index 5dceff2571..56a75b7173 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.move.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.move.controller.js @@ -66,7 +66,7 @@ angular.module("umbraco").controller("Umbraco.Editors.Content.MoveController", // method to select a search result $scope.selectResult = function (evt, result) { result.selected = result.selected === true ? false : true; - nodeSelectHandler(evt, { event: evt, node: result }); + nodeSelectHandler({ event: evt, node: result }); }; //callback when there are search results @@ -124,7 +124,7 @@ angular.module("umbraco").controller("Umbraco.Editors.Content.MoveController", // Mini list view $scope.selectListViewNode = function (node) { node.selected = node.selected === true ? false : true; - nodeSelectHandler({}, { node: node }); + nodeSelectHandler({ node: node }); }; $scope.closeMiniListView = function () { diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.restore.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.restore.controller.js index ddc23c7372..cfebdeac0e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.restore.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.restore.controller.js @@ -57,7 +57,7 @@ angular.module("umbraco").controller("Umbraco.Editors.Content.RestoreController" // method to select a search result $scope.selectResult = function (evt, result) { result.selected = result.selected === true ? false : true; - nodeSelectHandler(evt, { event: evt, node: result }); + nodeSelectHandler({ event: evt, node: result }); }; //callback when there are search results @@ -74,7 +74,7 @@ angular.module("umbraco").controller("Umbraco.Editors.Content.RestoreController" // Mini list view $scope.selectListViewNode = function (node) { node.selected = node.selected === true ? false : true; - nodeSelectHandler({}, { node: node }); + nodeSelectHandler({ node: node }); }; $scope.closeMiniListView = function () { diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/publish.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publish.controller.js index d6f6f67c55..d81b7e29e3 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/publish.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publish.controller.js @@ -1,7 +1,7 @@ (function () { "use strict"; - function PublishController($scope, localizationService) { + function PublishController($scope, localizationService, contentEditingHelper) { var vm = this; vm.loading = true; @@ -14,34 +14,51 @@ /** Returns true if publishing is possible based on if there are un-published mandatory languages */ function canPublish() { - var selected = []; + + var possible = false; for (var i = 0; i < vm.variants.length; i++) { var variant = vm.variants[i]; - - //if this variant will show up in the publish-able list - var publishable = dirtyVariantFilter(variant); - var published = !(variant.state === "NotCreated" || variant.state === "Draft"); - - if ((variant.language.isMandatory && !published) && (!publishable || !variant.publish)) { - //if a mandatory variant isn't published - //and it's not publishable or not selected to be published - //then we cannot continue - - // TODO: Show a message when this occurs + var state = canVariantPublish(variant); + if (state === true) { + possible = true; + } + if (state === false) { return false; } - - if (variant.publish) { - selected.push(variant.publish); - } } - return selected.length > 0; + return possible; + } + + /** Returns true if publishing is possible based on if the variant is a un-published mandatory language */ + function canVariantPublish(variant) { + + //if this variant will show up in the publish-able list + var publishable = dirtyVariantFilter(variant); + var published = !(variant.state === "NotCreated" || variant.state === "Draft"); + + // is this variant mandatory: + if (variant.language.isMandatory && !published && !variant.publish) { + //if a mandatory variant isn't published or set to be published + //then we cannot continue + + return false; + } + + // is this variant selected for publish: + if (variant.publish === true) { + return publishable; + } + + return null; } function changeSelection(variant) { + $scope.model.disableSubmitButton = !canPublish(); //need to set the Save state to true if publish is true variant.save = variant.publish; + + variant.willPublish = canVariantPublish(variant); } function dirtyVariantFilter(variant) { @@ -99,32 +116,49 @@ _.each(vm.variants, function (variant) { - if(variant.state !== "NotCreated"){ - vm.isNew = false; + if(variant.state === "NotCreated") { + vm.isNew = true; } - }); + } + ); _.each(vm.variants, function (variant) { - variant.compositeId = variant.language.culture + "_" + (variant.segment ? variant.segment : ""); + variant.compositeId = contentEditingHelper.buildCompositeVariantId(variant); variant.htmlId = "_content_variant_" + variant.compositeId; + + // reset to not be published + variant.publish = false; + variant.save = false; //check for pristine variants if (!vm.hasPristineVariants) { vm.hasPristineVariants = pristineVariantFilter(variant); } - - if(hasAnyData(variant)){ + + // If the variant havent been created jet. + if(variant.state === "NotCreated") { + // If the variant is mandatory, then set the variant to be published. + if (variant.language.isMandatory === true) { + variant.publish = true; + variant.save = true; + } + } + + variant.canPublish = dirtyVariantFilter(variant); + + // if we have data on this variant. + if(variant.canPublish && hasAnyData(variant)) { + // and if some varaints havent been saved before, or they dont have a publishing date set, then we set it for publishing. if(vm.isNew || variant.publishDate == null){ variant.publish = true; variant.save = true; } - }else{ - variant.publish = false; - variant.save = false; - variant.canSave = false; } - }); + + variant.willPublish = canVariantPublish(variant); + } + ); if (vm.variants.length !== 0) { //now sort it so that the current one is at the top @@ -153,7 +187,6 @@ localizationService.localize(labelKey).then(function (value) { vm.headline = value; - vm.loading = false; }); diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/publish.html b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publish.html index 8912f7606d..e92c16f0ef 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/publish.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publish.html @@ -10,7 +10,7 @@
-
+
- -
- - - -
- -
-
{{publishVariantSelectorForm.publishVariantSelector.errorMsg}}
-
- -
-
{{notification.message}}
-
-
@@ -64,7 +65,7 @@
-
{{notification.message}}
+
{{notification.message}}
diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.html b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.html index e7bfcaed12..1c7f212225 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.html @@ -22,11 +22,13 @@

-
-