diff --git a/.gitignore b/.gitignore index 0073675d82..32e7c297db 100644 --- a/.gitignore +++ b/.gitignore @@ -76,6 +76,7 @@ src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/umbraco.* src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/routes.js src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/app.dev.js src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/loader.js +src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/loader.dev.js src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/main.js src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/app.js @@ -129,3 +130,5 @@ src/*.boltdata/ /src/Umbraco.Web.UI/Umbraco/Js/canvasdesigner.front.js src/umbraco.sln.ide/* build/UmbracoCms.*/ +src/.vs/ +src/Umbraco.Web.UI/umbraco/js/install.loader.js diff --git a/README.md b/README.md index d4a024ddbc..5988cc2a19 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,9 @@ The easiest way to get started is to run `build/build.bat` which will build both If you're interested in making changes to Belle make sure to read the [Belle ReadMe file](src/Umbraco.Web.UI.Client/README.md). Note that you can always [download a nightly build](http://nightly.umbraco.org/umbraco%207.0.0/) so you don't have to build the code yourself. -## Watch a five minute introduction video ## +## Watch a introduction video ## -[![ScreenShot](http://umbraco.com/images/whatisumbraco.png)](http://umbraco.org/help-and-support/video-tutorials/getting-started/what-is-umbraco) +[![ScreenShot](http://umbraco.com/images/whatisumbraco.png)](https://umbraco.tv/videos/umbraco-v7/content-editor/basics/introduction/cms-explanation/) ## Umbraco - the simple, flexible and friendly ASP.NET CMS ## diff --git a/build/Build.bat b/build/Build.bat index f29260b274..bd18f719ad 100644 --- a/build/Build.bat +++ b/build/Build.bat @@ -25,9 +25,9 @@ ECHO Installing the Microsoft.Bcl.Build package before anything else, otherwise SET nuGetFolder=%CD%\..\src\packages\ ..\src\.nuget\NuGet.exe sources Remove -Name MyGetUmbracoCore >NUL ..\src\.nuget\NuGet.exe sources Add -Name MyGetUmbracoCore -Source https://www.myget.org/F/umbracocore/api/v2/ >NUL -..\src\.nuget\NuGet.exe install ..\src\Umbraco.Web.UI\packages.config -OutputDirectory %nuGetFolder% -..\src\.nuget\NuGet.exe install ..\src\umbraco.businesslogic\packages.config -OutputDirectory %nuGetFolder% -..\src\.nuget\NuGet.exe install ..\src\Umbraco.Core\packages.config -OutputDirectory %nuGetFolder% +..\src\.nuget\NuGet.exe install ..\src\Umbraco.Web.UI\packages.config -OutputDirectory %nuGetFolder% -Verbosity quiet +..\src\.nuget\NuGet.exe install ..\src\umbraco.businesslogic\packages.config -OutputDirectory %nuGetFolder% -Verbosity quiet +..\src\.nuget\NuGet.exe install ..\src\Umbraco.Core\packages.config -OutputDirectory %nuGetFolder% -Verbosity quiet ECHO Removing the belle build folder and bower_components folder to make sure everything is clean as a whistle RD ..\src\Umbraco.Web.UI.Client\build /Q /S @@ -43,7 +43,7 @@ DEL /F /Q webpihash.txt ECHO Making sure Git is in the path so that the build can succeed CALL InstallGit.cmd ECHO Performing MSBuild and producing Umbraco binaries zip files -%windir%\Microsoft.NET\Framework\v4.0.30319\msbuild.exe "Build.proj" /p:BUILD_RELEASE=%release% /p:BUILD_COMMENT=%comment% +%windir%\Microsoft.NET\Framework\v4.0.30319\msbuild.exe "Build.proj" /p:BUILD_RELEASE=%release% /p:BUILD_COMMENT=%comment% /verbosity:minimal ECHO Setting node_modules folder to hidden to prevent VS13 from crashing on it while loading the websites project attrib +h ..\src\Umbraco.Web.UI.Client\node_modules @@ -53,11 +53,13 @@ REN .\_BuildOutput\WebApp\Views\Web.config Web.config.transform REN .\_BuildOutput\WebApp\Xslt\Web.config Web.config.transform ECHO Packing the NuGet release files -..\src\.nuget\NuGet.exe Pack NuSpecs\UmbracoCms.Core.nuspec -Version %version% -Symbols -..\src\.nuget\NuGet.exe Pack NuSpecs\UmbracoCms.nuspec -Version %version% +..\src\.nuget\NuGet.exe Pack NuSpecs\UmbracoCms.Core.nuspec -Version %version% -Symbols -Verbosity quiet +..\src\.nuget\NuGet.exe Pack NuSpecs\UmbracoCms.nuspec -Version %version% -Verbosity quiet IF ERRORLEVEL 1 GOTO :showerror +ECHO No errors were detected but you still may see some in the output, then it's time to investigate. +ECHO You might see some warnings but that is completely normal. GOTO :EOF :showerror diff --git a/build/Build.proj b/build/Build.proj index 09303788d9..e937df15bd 100644 --- a/build/Build.proj +++ b/build/Build.proj @@ -68,6 +68,12 @@ .$(BUILD_RELEASE)-$(BUILD_COMMENT) + + .$(BUILD_RELEASE)-$(BUILD_NIGHTLY) + + + .$(BUILD_RELEASE)-$(BUILD_COMMENT)-$(BUILD_NIGHTLY) + Release @@ -91,15 +97,8 @@ - - - - - - - @@ -145,14 +144,14 @@ DestinationFiles="@(WebPiFiles->'$(WebPiFolder)%(RecursiveDir)%(Filename)%(Extension)')" /> - + - - + + @@ -219,6 +218,7 @@ + @@ -227,6 +227,11 @@ OverwriteReadOnlyFiles="true" SkipUnchangedFiles="false" /> + + - + @@ -271,9 +276,11 @@ $(BUILD_RELEASE) $(BUILD_RELEASE)-$(BUILD_COMMENT) + $(BUILD_RELEASE)-$(BUILD_NIGHTLY) + $(BUILD_RELEASE)-$(BUILD_COMMENT)-$(BUILD_NIGHTLY) - + + + + + + + + diff --git a/build/BuildBelle.bat b/build/BuildBelle.bat index e323abbb0d..0d58204f65 100644 --- a/build/BuildBelle.bat +++ b/build/BuildBelle.bat @@ -6,7 +6,7 @@ SET nuGetFolder=%CD%\..\src\packages\ ECHO Configured packages folder: %nuGetFolder% ECHO Current folder: %CD% -%CD%\..\src\.nuget\NuGet.exe install Npm.js -OutputDirectory %nuGetFolder% +%CD%\..\src\.nuget\NuGet.exe install Npm.js -OutputDirectory %nuGetFolder% -Verbosity quiet for /f "delims=" %%A in ('dir %nuGetFolder%node.js.* /b') do set "nodePath=%nuGetFolder%%%A\" for /f "delims=" %%A in ('dir %nuGetFolder%npm.js.* /b') do set "npmPath=%nuGetFolder%%%A\tools\" @@ -24,9 +24,9 @@ ECHO Change directory to %CD%\..\src\Umbraco.Web.UI.Client\ CD %CD%\..\src\Umbraco.Web.UI.Client\ ECHO Do npm install and the grunt build of Belle -call npm install -call npm install -g grunt-cli -call npm install -g bower +call npm install --quiet +call npm install -g grunt-cli --quiet +call npm install -g bower --quiet call grunt build --buildversion=%release% ECHO Reset path to what it was before diff --git a/build/InstallGit.cmd b/build/InstallGit.cmd index f10fe6c100..2bd6d7cc35 100644 --- a/build/InstallGit.cmd +++ b/build/InstallGit.cmd @@ -6,14 +6,14 @@ if %ERRORLEVEL%==9009 GOTO :trydefaultpath GOTO :EOF :trydefaultpath -path=C:\Program Files (x86)\Git\cmd;%PATH% +path=C:\Program Files (x86)\Git\cmd;C:\Program Files\Git\cmd;%PATH% git.exe 2> NUL if %ERRORLEVEL%==9009 GOTO :showerror GOTO :EOF :showerror path=%oldPath% -ECHO Git is not in your path and could not be found in C:\Program Files (x86)\Git\bin +ECHO Git is not in your path and could not be found in C:\Program Files (x86)\Git\cmd set /p install=" Do you want to install Git through Chocolatey [y/n]? " %=% if %install%==y ( GOTO :installgit diff --git a/build/NuSpecs/UmbracoCms.Core.nuspec b/build/NuSpecs/UmbracoCms.Core.nuspec index 1bb1a78d03..6852b04510 100644 --- a/build/NuSpecs/UmbracoCms.Core.nuspec +++ b/build/NuSpecs/UmbracoCms.Core.nuspec @@ -15,27 +15,29 @@ en-US umbraco - + - - - + + - + - + - - - - - - - + + + + + + + + + + diff --git a/build/NuSpecs/UmbracoCms.nuspec b/build/NuSpecs/UmbracoCms.nuspec index 76d72935e9..61a8188804 100644 --- a/build/NuSpecs/UmbracoCms.nuspec +++ b/build/NuSpecs/UmbracoCms.nuspec @@ -26,7 +26,6 @@ - @@ -41,6 +40,7 @@ + \ No newline at end of file diff --git a/build/NuSpecs/build/UmbracoCms.targets b/build/NuSpecs/build/UmbracoCms.targets index 024d8af7ad..fde5b4ea81 100644 --- a/build/NuSpecs/build/UmbracoCms.targets +++ b/build/NuSpecs/build/UmbracoCms.targets @@ -1,6 +1,17 @@  + + + $(MSBuildThisFileDirectory)..\UmbracoFiles\ + + + + + + + + $(MSBuildThisFileDirectory)..\UmbracoFiles\ @@ -47,4 +58,4 @@ - \ No newline at end of file + diff --git a/build/NuSpecs/tools/Dashboard.config.install.xdt b/build/NuSpecs/tools/Dashboard.config.install.xdt index 1f5bbb0164..197f9c1b6f 100644 --- a/build/NuSpecs/tools/Dashboard.config.install.xdt +++ b/build/NuSpecs/tools/Dashboard.config.install.xdt @@ -1,6 +1,6 @@ -
+
@@ -20,7 +20,7 @@
-
+
views/dashboard/developer/developerdashboardvideos.html @@ -29,15 +29,20 @@
-
+
views/dashboard/developer/examinemanagement.html + + + views/dashboard/developer/xmldataintegrityreport.html + +
-
+
@@ -47,7 +52,7 @@
-
+
@@ -57,17 +62,10 @@ views/dashboard/default/startupdashboardintro.html - - - - - views/dashboard/ChangePassword.html - -
-
+
diff --git a/build/NuSpecs/tools/ReadmeUpgrade.txt b/build/NuSpecs/tools/ReadmeUpgrade.txt index 29aa41b55a..894f5e5f93 100644 --- a/build/NuSpecs/tools/ReadmeUpgrade.txt +++ b/build/NuSpecs/tools/ReadmeUpgrade.txt @@ -10,9 +10,6 @@ Don't forget to build! -When upgrading your website using NuGet you should answer "No" to the questions to overwrite the Web.config -file (and config files in the config folder). - We've done our best to transform your configuration files but in case something is not quite right: remember we backed up your files in App_Data\NuGetBackup so you can find the original files before they were transformed. diff --git a/build/NuSpecs/tools/Views.Web.config.install.xdt b/build/NuSpecs/tools/Views.Web.config.install.xdt new file mode 100644 index 0000000000..c34963f2b0 --- /dev/null +++ b/build/NuSpecs/tools/Views.Web.config.install.xdt @@ -0,0 +1,37 @@ + + + + +
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build/NuSpecs/tools/Web.config.install.xdt b/build/NuSpecs/tools/Web.config.install.xdt index e6dc46aab7..cff81ba2d8 100644 --- a/build/NuSpecs/tools/Web.config.install.xdt +++ b/build/NuSpecs/tools/Web.config.install.xdt @@ -20,9 +20,9 @@ - + - + @@ -48,6 +48,13 @@ + + + + + + + > @@ -74,6 +81,10 @@ + + + + @@ -143,6 +154,12 @@ + + + + + + @@ -158,7 +175,31 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/build/NuSpecs/tools/install.core.ps1 b/build/NuSpecs/tools/install.core.ps1 index 9c631001d2..c81fa419c9 100644 --- a/build/NuSpecs/tools/install.core.ps1 +++ b/build/NuSpecs/tools/install.core.ps1 @@ -56,12 +56,25 @@ if ($project) { 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.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.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\MySql.Data.dll) { Remove-Item $umbracoBinFolder\MySql.Data.dll -Force -Confirm:$false } if(Test-Path $umbracoBinFolder\Newtonsoft.Json.dll) { Remove-Item $umbracoBinFolder\Newtonsoft.Json.dll -Force -Confirm:$false } + if(Test-Path $umbracoBinFolder\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.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 } diff --git a/build/NuSpecs/tools/log4net.config.install.xdt b/build/NuSpecs/tools/log4net.config.install.xdt new file mode 100644 index 0000000000..f4d3caf7fc --- /dev/null +++ b/build/NuSpecs/tools/log4net.config.install.xdt @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/build/NuSpecs/tools/trees.config.install.xdt b/build/NuSpecs/tools/trees.config.install.xdt index 6acc793e05..f5a807b4bf 100644 --- a/build/NuSpecs/tools/trees.config.install.xdt +++ b/build/NuSpecs/tools/trees.config.install.xdt @@ -1,36 +1,127 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/build/RevertToCleanInstall.bat b/build/RevertToCleanInstall.bat index 6c391386cd..b21b33d8ff 100644 --- a/build/RevertToCleanInstall.bat +++ b/build/RevertToCleanInstall.bat @@ -45,9 +45,6 @@ del ..\src\Umbraco.Web.UI\UserControls\*.* echo Removing masterpage files del ..\src\Umbraco.Web.UI\masterpages\*.* -echo Removing view files -del ..\src\Umbraco.Web.UI\Views\*.* - echo Removing razor files del ..\src\Umbraco.Web.UI\macroScripts\*.* @@ -104,7 +101,9 @@ echo Removing user control files FOR %%A IN (..\src\Umbraco.Web.UI\usercontrols\*.*) DO DEL %%A echo Removing view files -FOR %%A IN (..\src\Umbraco.Web.UI\Views\*.*) DO DEL %%A +ATTRIB +H ..\src\Umbraco.Web.UI\Views\Partials\Grid\*.cshtml /S +FOR %%A IN (..\src\Umbraco.Web.UI\Views\) DO DEL /Q /S *.cshtml -H +ATTRIB -H ..\src\Umbraco.Web.UI\Views\Partials\Grid\*.cshtml /S echo Removing razor files FOR %%A IN (..\src\Umbraco.Web.UI\macroScripts\*.*) DO DEL %%A diff --git a/build/RevertToEmptyInstall.bat b/build/RevertToEmptyInstall.bat index d59e2cdc88..b8abe4e64e 100644 --- a/build/RevertToEmptyInstall.bat +++ b/build/RevertToEmptyInstall.bat @@ -54,9 +54,6 @@ del ..\src\Umbraco.Web.UI\UserControls\*.* echo Removing masterpage files del ..\src\Umbraco.Web.UI\masterpages\*.* -echo Removing view files -del ..\src\Umbraco.Web.UI\Views\*.* - echo Removing razor files del ..\src\Umbraco.Web.UI\macroScripts\*.* @@ -122,7 +119,9 @@ echo Removing user control files FOR %%A IN (..\src\Umbraco.Web.UI\usercontrols\*.*) DO DEL %%A echo Removing view files -FOR %%A IN (..\src\Umbraco.Web.UI\Views\*.*) DO DEL %%A +ATTRIB +H ..\src\Umbraco.Web.UI\Views\Partials\Grid\*.cshtml /S +FOR %%A IN (..\src\Umbraco.Web.UI\Views\) DO DEL /Q /S *.cshtml -H +ATTRIB -H ..\src\Umbraco.Web.UI\Views\Partials\Grid\*.cshtml /S echo Removing razor files FOR %%A IN (..\src\Umbraco.Web.UI\macroScripts\*.*) DO DEL %%A @@ -151,6 +150,9 @@ rmdir "..\src\Umbraco.Web.UI\usercontrols\umbracoContour\" /S /Q echo Start with a clean web.config copy ..\src\Umbraco.Web.UI\web.Template.config ..\src\Umbraco.Web.UI\web.config /Y +echo Start with a clean web.config +copy ..\src\Umbraco.Web.UI\web.Template.config ..\src\Umbraco.Web.UI\web.config /Y + echo "Umbraco install reverted to clean install" pause exit diff --git a/build/UmbracoVersion.txt b/build/UmbracoVersion.txt index bba582b4dc..803b95f9af 100644 --- a/build/UmbracoVersion.txt +++ b/build/UmbracoVersion.txt @@ -1,2 +1,3 @@ # Usage: on line 2 put the release version, on line 3 put the version comment (example: beta) -8.0.0 \ No newline at end of file +8.0.0 +beta \ No newline at end of file diff --git a/src/NuGet.Config b/src/NuGet.Config new file mode 100644 index 0000000000..dcccfdd5c1 --- /dev/null +++ b/src/NuGet.Config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/SQLCE4Umbraco/SqlCE4Umbraco.csproj.DotSettings b/src/SQLCE4Umbraco/SqlCE4Umbraco.csproj.DotSettings new file mode 100644 index 0000000000..662f95686e --- /dev/null +++ b/src/SQLCE4Umbraco/SqlCE4Umbraco.csproj.DotSettings @@ -0,0 +1,2 @@ + + CSharp50 \ No newline at end of file diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index 6c766f8e97..28b314750a 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -11,5 +11,5 @@ using System.Resources; [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyFileVersion("7.3.0")] -[assembly: AssemblyInformationalVersion("7.3.0")] \ No newline at end of file +[assembly: AssemblyFileVersion("7.4.0")] +[assembly: AssemblyInformationalVersion("7.4.0-beta")] \ No newline at end of file diff --git a/src/Umbraco.Core/ActionsResolver.cs b/src/Umbraco.Core/ActionsResolver.cs index ff34f62c60..2da95a3416 100644 --- a/src/Umbraco.Core/ActionsResolver.cs +++ b/src/Umbraco.Core/ActionsResolver.cs @@ -11,7 +11,7 @@ namespace Umbraco.Core /// /// A resolver to return all IAction objects /// - internal sealed class ActionsResolver : LazyManyObjectsResolverBase + public sealed class ActionsResolver : LazyManyObjectsResolverBase { /// /// Constructor diff --git a/src/Umbraco.Core/ApplicationContext.cs b/src/Umbraco.Core/ApplicationContext.cs index b303c087f7..503b7e7dd0 100644 --- a/src/Umbraco.Core/ApplicationContext.cs +++ b/src/Umbraco.Core/ApplicationContext.cs @@ -1,6 +1,7 @@ using System; using System.Configuration; using System.Threading; +using System.Threading.Tasks; using System.Web; using System.Web.Caching; using Umbraco.Core.Cache; @@ -10,7 +11,7 @@ using Umbraco.Core.Logging; using Umbraco.Core.ObjectResolution; using Umbraco.Core.Profiling; using Umbraco.Core.Services; - +using Umbraco.Core.Sync; namespace Umbraco.Core { @@ -34,10 +35,13 @@ namespace Umbraco.Core if (dbContext == null) throw new ArgumentNullException("dbContext"); if (serviceContext == null) throw new ArgumentNullException("serviceContext"); if (cache == null) throw new ArgumentNullException("cache"); + if (logger == null) throw new ArgumentNullException("logger"); _databaseContext = dbContext; _services = serviceContext; ApplicationCache = cache; ProfilingLogger = logger; + + Init(); } /// @@ -46,7 +50,7 @@ namespace Umbraco.Core /// /// /// - [Obsolete("Use the other constructor specifying an ILogger instead")] + [Obsolete("Use the other constructor specifying a ProfilingLogger instead")] public ApplicationContext(DatabaseContext dbContext, ServiceContext serviceContext, CacheHelper cache) : this(dbContext, serviceContext, cache, new ProfilingLogger(LoggerResolver.Current.Logger, ProfilerResolver.Current.Profiler)) @@ -57,9 +61,27 @@ namespace Umbraco.Core /// Creates a basic app context /// /// + [Obsolete("Use the other constructor specifying a ProfilingLogger instead")] public ApplicationContext(CacheHelper cache) { + if (cache == null) throw new ArgumentNullException("cache"); ApplicationCache = cache; + ProfilingLogger = new ProfilingLogger(LoggerResolver.Current.Logger, ProfilerResolver.Current.Profiler); + Init(); + } + + /// + /// Creates a basic app context + /// + /// + /// + public ApplicationContext(CacheHelper cache, ProfilingLogger logger) + { + if (cache == null) throw new ArgumentNullException("cache"); + if (logger == null) throw new ArgumentNullException("logger"); + ApplicationCache = cache; + ProfilingLogger = logger; + Init(); } /// @@ -75,13 +97,13 @@ namespace Umbraco.Core /// public static ApplicationContext EnsureContext(ApplicationContext appContext, bool replaceContext) { - if (ApplicationContext.Current != null) + if (Current != null) { if (!replaceContext) - return ApplicationContext.Current; + return Current; } - ApplicationContext.Current = appContext; - return ApplicationContext.Current; + Current = appContext; + return Current; } /// @@ -101,14 +123,14 @@ namespace Umbraco.Core [Obsolete("Use the other method specifying an ProfilingLogger instead")] public static ApplicationContext EnsureContext(DatabaseContext dbContext, ServiceContext serviceContext, CacheHelper cache, bool replaceContext) { - if (ApplicationContext.Current != null) + if (Current != null) { if (!replaceContext) - return ApplicationContext.Current; + return Current; } var ctx = new ApplicationContext(dbContext, serviceContext, cache); - ApplicationContext.Current = ctx; - return ApplicationContext.Current; + Current = ctx; + return Current; } /// @@ -128,14 +150,14 @@ namespace Umbraco.Core /// public static ApplicationContext EnsureContext(DatabaseContext dbContext, ServiceContext serviceContext, CacheHelper cache, ProfilingLogger logger, bool replaceContext) { - if (ApplicationContext.Current != null) + if (Current != null) { if (!replaceContext) - return ApplicationContext.Current; + return Current; } var ctx = new ApplicationContext(dbContext, serviceContext, cache, logger); - ApplicationContext.Current = ctx; - return ApplicationContext.Current; + Current = ctx; + return Current; } /// @@ -190,64 +212,117 @@ namespace Umbraco.Core // GlobalSettings.CurrentVersion returns the hard-coded "current version" // the system is configured if they match // if they don't, install runs, updates web.config (presumably) and updates GlobalSettings.ConfiguredStatus - // - // then there is Application["umbracoNeedConfiguration"] which makes no sense... getting rid of it... SD: I have actually remove that now! - // + public bool IsConfigured { - // todo - we should not do this - ok for now - get - { - return Configured; - } + get { return _configured.Value; } } /// - /// If the db is configured and there is a database context, but we are not 'configured' , then it means we are upgrading + /// If the db is configured, there is a database context and there is an umbraco schema, but we are not 'configured' , then it means we are upgrading /// public bool IsUpgrading { - get { return IsConfigured == false && DatabaseContext != null && DatabaseContext.IsDatabaseConfigured; } + get + { + if (IsConfigured == false + && DatabaseContext != null + && DatabaseContext.IsDatabaseConfigured) + { + var schemaresult = DatabaseContext.ValidateDatabaseSchema(); + if (schemaresult.ValidTables.Count > 0) return true; + } + + return false; + } } /// - /// The original/first url that the web application executes + /// The application url. /// /// - /// we need to set the initial url in our ApplicationContext, this is so our keep alive service works and this must - /// exist on a global context because the keep alive service doesn't run in a web context. - /// we are NOT going to put a lock on this because locking will slow down the application and we don't really care - /// if two threads write to this at the exact same time during first page hit. - /// see: http://issues.umbraco.org/issue/U4-2059 + /// The application url is the url that should be used by services to talk to the application, + /// eg keep alive or scheduled publishing services. It must exist on a global context because + /// some of these services may not run within a web context. + /// The format of the application url is: + /// - has a scheme (http or https) + /// - has the SystemDirectories.Umbraco path + /// - does not end with a slash + /// It is initialized on the first request made to the server, by UmbracoModule.EnsureApplicationUrl: + /// - if umbracoSettings:settings/web.routing/@umbracoApplicationUrl is set, use the value (new setting) + /// - if umbracoSettings:settings/scheduledTasks/@baseUrl is set, use the value (backward compatibility) + /// - otherwise, use the url of the (first) request. + /// Not locking, does not matter if several threads write to this. + /// See also issues: + /// - http://issues.umbraco.org/issue/U4-2059 + /// - http://issues.umbraco.org/issue/U4-6788 + /// - http://issues.umbraco.org/issue/U4-5728 + /// - http://issues.umbraco.org/issue/U4-5391 /// - internal string OriginalRequestUrl { get; set; } + internal string UmbracoApplicationUrl + { + get + { + ApplicationUrlHelper.EnsureApplicationUrl(this); + return _umbracoApplicationUrl; + } + } - /// - /// Checks if the version configured matches the assembly version - /// - private bool Configured + // ReSharper disable once InconsistentNaming + internal string _umbracoApplicationUrl; + + private Lazy _configured; + internal MainDom MainDom { get; private set; } + + private void Init() { - get - { - try - { - string configStatus = ConfigurationStatus; - string currentVersion = UmbracoVersion.Current.ToString(3); + MainDom = new MainDom(ProfilingLogger.Logger); + MainDom.Acquire(); + + //Create the lazy value to resolve whether or not the application is 'configured' + _configured = new Lazy(() => + { + try + { + var configStatus = ConfigurationStatus; + var currentVersion = UmbracoVersion.GetSemanticVersion(); + var ok = + //we are not configured if this is null + string.IsNullOrWhiteSpace(configStatus) == false + //they must match + && configStatus == currentVersion; - if (currentVersion != configStatus) - { - ProfilingLogger.Logger.Debug("CurrentVersion different from configStatus: '" + currentVersion + "','" + configStatus + "'"); - } - + if (ok) + { + //The versions are the same in config, but are they the same in the database. We can only check this + // if we have a db context available, if we don't then we are not installed anyways + if (DatabaseContext.IsDatabaseConfigured && DatabaseContext.CanConnect) + { + var found = Services.MigrationEntryService.FindEntry(GlobalSettings.UmbracoMigrationName, UmbracoVersion.GetSemanticVersion()); + if (found == null) + { + //we haven't executed this migration in this environment, so even though the config versions match, + // this db has not been updated. + ProfilingLogger.Logger.Debug(string.Format("The migration for version: '{0} has not been executed, there is no record in the database", currentVersion.ToSemanticString())); + ok = false; + } + } + } + else + { + ProfilingLogger.Logger.Debug(string.Format("CurrentVersion different from configStatus: '{0}','{1}'", currentVersion.ToSemanticString(), configStatus)); + } - return (configStatus == currentVersion); - } - catch - { - return false; - } - } + return ok; + } + catch (Exception ex) + { + LogHelper.Error("Error determining if application is configured, returning false", ex); + return false; + } + + }); } private string ConfigurationStatus @@ -305,6 +380,11 @@ namespace Umbraco.Core internal set { _services = value; } } + internal ServerRole GetCurrentServerRole() + { + var registrar = ServerRegistrarResolver.Current.Registrar as IServerRegistrar2; + return registrar == null ? ServerRole.Unknown : registrar.GetCurrentServerRole(); + } private volatile bool _disposed; private readonly ReaderWriterLockSlim _disposalLocker = new ReaderWriterLockSlim(); diff --git a/src/Umbraco.Core/ApplicationEventHandler.cs b/src/Umbraco.Core/ApplicationEventHandler.cs index a725a08a8e..8d97baac95 100644 --- a/src/Umbraco.Core/ApplicationEventHandler.cs +++ b/src/Umbraco.Core/ApplicationEventHandler.cs @@ -74,7 +74,7 @@ namespace Umbraco.Core /// private bool ShouldExecute(ApplicationContext applicationContext) { - if (applicationContext.IsConfigured && applicationContext.DatabaseContext.CanConnect) + if (applicationContext.IsConfigured && applicationContext.DatabaseContext.IsDatabaseConfigured) { return true; } @@ -84,7 +84,7 @@ namespace Umbraco.Core return true; } - if (!applicationContext.DatabaseContext.CanConnect && ExecuteWhenDatabaseNotConfigured) + if (!applicationContext.DatabaseContext.IsDatabaseConfigured && ExecuteWhenDatabaseNotConfigured) { return true; } diff --git a/src/Umbraco.Core/AsyncLock.cs b/src/Umbraco.Core/AsyncLock.cs index 426984ab9f..b6a3f8534e 100644 --- a/src/Umbraco.Core/AsyncLock.cs +++ b/src/Umbraco.Core/AsyncLock.cs @@ -1,5 +1,6 @@ using System; using System.Runtime.ConstrainedExecution; +using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; @@ -11,6 +12,15 @@ namespace Umbraco.Core // - this is NOT a reader/writer lock // - this is NOT a recursive lock // + // using a named Semaphore here and not a Mutex because mutexes have thread + // affinity which does not work with async situations + // + // it is important that managed code properly release the Semaphore before + // going down else it will maintain the lock - however note that when the + // whole process (w3wp.exe) goes down and all handles to the Semaphore have + // been closed, the Semaphore system object is destroyed - so in any case + // an iisreset should clean up everything + // internal class AsyncLock { private readonly SemaphoreSlim _semaphore; @@ -54,14 +64,14 @@ namespace Umbraco.Core // for anonymous semaphore, use the unique releaser, else create a new one return _semaphore != null ? _releaser // (IDisposable)new SemaphoreSlimReleaser(_semaphore) - : (IDisposable)new NamedSemaphoreReleaser(_semaphore2); + : new NamedSemaphoreReleaser(_semaphore2); } public Task LockAsync() { var wait = _semaphore != null - ? _semaphore.WaitAsync() - : WaitOneAsync(_semaphore2); + ? _semaphore.WaitAsync() + : _semaphore2.WaitOneAsync(); return wait.IsCompleted ? _releaserTask ?? Task.FromResult(CreateReleaser()) // anonymous vs named @@ -70,6 +80,19 @@ namespace Umbraco.Core TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); } + public Task LockAsync(int millisecondsTimeout) + { + var wait = _semaphore != null + ? _semaphore.WaitAsync(millisecondsTimeout) + : _semaphore2.WaitOneAsync(millisecondsTimeout); + + return wait.IsCompleted + ? _releaserTask ?? Task.FromResult(CreateReleaser()) // anonymous vs named + : wait.ContinueWith((_, state) => (((AsyncLock)state).CreateReleaser()), + this, CancellationToken.None, + TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); + } + public IDisposable Lock() { if (_semaphore != null) @@ -95,38 +118,55 @@ namespace Umbraco.Core private class NamedSemaphoreReleaser : CriticalFinalizerObject, IDisposable { private readonly Semaphore _semaphore; + private GCHandle _handle; internal NamedSemaphoreReleaser(Semaphore semaphore) { _semaphore = semaphore; + _handle = GCHandle.Alloc(_semaphore); } public void Dispose() { Dispose(true); - GC.SuppressFinalize(this); + GC.SuppressFinalize(this); // finalize will not run } private void Dispose(bool disposing) { // critical + _handle.Free(); _semaphore.Release(); + _semaphore.Dispose(); } - // we WANT to release the semaphore because it's a system object - // ie a critical non-managed resource - so we inherit from CriticalFinalizerObject - // which means that the finalizer "should" run in all situations + // we WANT to release the semaphore because it's a system object, ie a critical + // non-managed resource - and if it is not released then noone else can acquire + // the lock - so we inherit from CriticalFinalizerObject which means that the + // finalizer "should" run in all situations - there is always a chance that it + // does not run and the semaphore remains "acquired" but then chances are the + // whole process (w3wp.exe...) is going down, at which point the semaphore will + // be destroyed by Windows. - // however... that can fail with System.ObjectDisposedException because the - // underlying handle was closed... because we cannot guarantee that the semaphore - // is not gone already... unless we get a GCHandle = GCHandle.Alloc(_semaphore); - // which should keep it around and then we free the handle? + // however, the semaphore is a managed object, and so when the finalizer runs it + // might have been finalized already, and then we get a, ObjectDisposedException + // in the finalizer - which is bad. - // so... I'm not sure this is safe really... + // in order to prevent this we do two things + // - use a GCHandler to ensure the semaphore is still there when the finalizer + // runs, so we can actually release it + // - wrap the finalizer code in a try...catch to make sure it never throws ~NamedSemaphoreReleaser() { - Dispose(false); + try + { + Dispose(false); + } + catch + { + // we do NOT want the finalizer to throw - never ever + } } } @@ -159,40 +199,5 @@ namespace Umbraco.Core Dispose(false); } } - - // http://stackoverflow.com/questions/25382583/waiting-on-a-named-semaphore-with-waitone100-vs-waitone0-task-delay100 - // http://blog.nerdbank.net/2011/07/c-await-for-waithandle.html - // F# has a AwaitWaitHandle method that accepts a time out... and seems pretty complex... - // version below should be OK - - private static Task WaitOneAsync(WaitHandle handle) - { - var tcs = new TaskCompletionSource(); - var callbackHandleInitLock = new object(); - lock (callbackHandleInitLock) - { - RegisteredWaitHandle callbackHandle = null; - // ReSharper disable once RedundantAssignment - callbackHandle = ThreadPool.RegisterWaitForSingleObject( - handle, - (state, timedOut) => - { - tcs.SetResult(null); - - // we take a lock here to make sure the outer method has completed setting the local variable callbackHandle. - lock (callbackHandleInitLock) - { - // ReSharper disable once PossibleNullReferenceException - // ReSharper disable once AccessToModifiedClosure - callbackHandle.Unregister(null); - } - }, - /*state:*/ null, - /*millisecondsTimeOutInterval:*/ Timeout.Infinite, - /*executeOnlyOnce:*/ true); - } - - return tcs.Task; - } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Cache/DictionaryCacheProviderBase.cs b/src/Umbraco.Core/Cache/DictionaryCacheProviderBase.cs index 5c729dba87..ae840d7b0e 100644 --- a/src/Umbraco.Core/Cache/DictionaryCacheProviderBase.cs +++ b/src/Umbraco.Core/Cache/DictionaryCacheProviderBase.cs @@ -12,7 +12,7 @@ namespace Umbraco.Core.Cache protected const string CacheItemPrefix = "umbrtmche"; // an object that represent a value that has not been created yet - protected readonly object ValueNotCreated = new object(); + protected internal static readonly object ValueNotCreated = new object(); // manupulate the underlying cache entries // these *must* be called from within the appropriate locks @@ -30,21 +30,58 @@ namespace Umbraco.Core.Cache return string.Format("{0}-{1}", CacheItemPrefix, key); } - protected object GetSafeLazyValue(Lazy lazy, bool onlyIfValueIsCreated = false) + protected internal static Lazy GetSafeLazy(Func getCacheItem) { + // try to generate the value and if it fails, + // wrap in an ExceptionHolder - would be much simpler + // to just use lazy.IsValueFaulted alas that field is + // internal + return new Lazy(() => + { + try + { + return getCacheItem(); + } + catch (Exception e) + { + return new ExceptionHolder(e); + } + }); + } + + protected internal static object GetSafeLazyValue(Lazy lazy, bool onlyIfValueIsCreated = false) + { + // if onlyIfValueIsCreated, do not trigger value creation + // must return something, though, to differenciate from null values + if (onlyIfValueIsCreated && lazy.IsValueCreated == false) return ValueNotCreated; + + // if execution has thrown then lazy.IsValueCreated is false + // and lazy.IsValueFaulted is true (but internal) so we use our + // own exception holder (see Lazy source code) to return null + if (lazy.Value is ExceptionHolder) return null; + + // we have a value and execution has not thrown so returning + // here does not throw - unless we're re-entering, take care of it try { - // if onlyIfValueIsCreated, do not trigger value creation - // must return something, though, to differenciate from null values - if (onlyIfValueIsCreated && lazy.IsValueCreated == false) return ValueNotCreated; return lazy.Value; } - catch + catch (InvalidOperationException e) { - return null; + throw new InvalidOperationException("The method that computes a value for the cache has tried to read that value from the cache.", e); } } + internal class ExceptionHolder + { + public ExceptionHolder(Exception e) + { + Exception = e; + } + + public Exception Exception { get; private set; } + } + #region Clear public virtual void ClearAllCache() diff --git a/src/Umbraco.Core/Cache/HttpRequestCacheProvider.cs b/src/Umbraco.Core/Cache/HttpRequestCacheProvider.cs index 0a95ff6fd2..ca1f4e85a0 100644 --- a/src/Umbraco.Core/Cache/HttpRequestCacheProvider.cs +++ b/src/Umbraco.Core/Cache/HttpRequestCacheProvider.cs @@ -102,20 +102,27 @@ namespace Umbraco.Core.Cache // cannot create value within the lock, so if result.IsValueCreated is false, just // do nothing here - means that if creation throws, a race condition could cause // more than one thread to reach the return statement below and throw - accepted. - + if (result == null || GetSafeLazyValue(result, true) == null) // get non-created as NonCreatedValue & exceptions as null { - result = new Lazy(getCacheItem); + result = GetSafeLazy(getCacheItem); ContextItems[cacheKey] = result; } } - // this may throw if getCacheItem throws, but this is the only place where - // it would throw as everywhere else we use GetLazySaveValue() to hide exceptions - // and pretend exceptions were never inserted into cache to begin with. - return result.Value; + // using GetSafeLazy and GetSafeLazyValue ensures that we don't cache + // exceptions (but try again and again) and silently eat them - however at + // some point we have to report them - so need to re-throw here + + // this does not throw anymore + //return result.Value; + + var value = result.Value; // will not throw (safe lazy) + var eh = value as ExceptionHolder; + if (eh != null) throw eh.Exception; // throw once! + return value; } - + #endregion #region Insert diff --git a/src/Umbraco.Core/Cache/HttpRuntimeCacheProvider.cs b/src/Umbraco.Core/Cache/HttpRuntimeCacheProvider.cs index a8033ed091..e7f5d17b83 100644 --- a/src/Umbraco.Core/Cache/HttpRuntimeCacheProvider.cs +++ b/src/Umbraco.Core/Cache/HttpRuntimeCacheProvider.cs @@ -35,7 +35,7 @@ namespace Umbraco.Core.Cache { const string prefix = CacheItemPrefix + "-"; return _cache.Cast() - .Where(x => x.Key is string && ((string) x.Key).StartsWith(prefix)); + .Where(x => x.Key is string && ((string)x.Key).StartsWith(prefix)); } protected override void RemoveEntry(string key) @@ -135,19 +135,27 @@ namespace Umbraco.Core.Cache if (result == null || GetSafeLazyValue(result, true) == null) // get non-created as NonCreatedValue & exceptions as null { - result = new Lazy(getCacheItem); + result = GetSafeLazy(getCacheItem); var absolute = isSliding ? System.Web.Caching.Cache.NoAbsoluteExpiration : (timeout == null ? System.Web.Caching.Cache.NoAbsoluteExpiration : DateTime.Now.Add(timeout.Value)); var sliding = isSliding == false ? System.Web.Caching.Cache.NoSlidingExpiration : (timeout ?? System.Web.Caching.Cache.NoSlidingExpiration); lck.UpgradeToWriteLock(); + //NOTE: 'Insert' on System.Web.Caching.Cache actually does an add or update! _cache.Insert(cacheKey, result, dependency, absolute, sliding, priority, removedCallback); } } - // this may throw if getCacheItem throws, but this is the only place where - // it would throw as everywhere else we use GetLazySaveValue() to hide exceptions - // and pretend exceptions were never inserted into cache to begin with. - return result.Value; + // using GetSafeLazy and GetSafeLazyValue ensures that we don't cache + // exceptions (but try again and again) and silently eat them - however at + // some point we have to report them - so need to re-throw here + + // this does not throw anymore + //return result.Value; + + value = result.Value; // will not throw (safe lazy) + var eh = value as ExceptionHolder; + if (eh != null) throw eh.Exception; // throw once! + return value; } public object GetCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) @@ -179,17 +187,18 @@ namespace Umbraco.Core.Cache // NOTE - here also we must insert a Lazy but we can evaluate it right now // and make sure we don't store a null value. - var result = new Lazy(getCacheItem); + var result = GetSafeLazy(getCacheItem); var value = result.Value; // force evaluation now - this may throw if cacheItem throws, and then nothing goes into cache if (value == null) return; // do not store null values (backward compat) - cacheKey = GetCacheKey(cacheKey); + cacheKey = GetCacheKey(cacheKey); var absolute = isSliding ? System.Web.Caching.Cache.NoAbsoluteExpiration : (timeout == null ? System.Web.Caching.Cache.NoAbsoluteExpiration : DateTime.Now.Add(timeout.Value)); var sliding = isSliding == false ? System.Web.Caching.Cache.NoSlidingExpiration : (timeout ?? System.Web.Caching.Cache.NoSlidingExpiration); using (new WriteLock(_locker)) { + //NOTE: 'Insert' on System.Web.Caching.Cache actually does an add or update! _cache.Insert(cacheKey, result, dependency, absolute, sliding, priority, removedCallback); } } diff --git a/src/Umbraco.Core/Cache/IPayloadCacheRefresher.cs b/src/Umbraco.Core/Cache/IPayloadCacheRefresher.cs new file mode 100644 index 0000000000..416cb223d7 --- /dev/null +++ b/src/Umbraco.Core/Cache/IPayloadCacheRefresher.cs @@ -0,0 +1,16 @@ +using umbraco.interfaces; + +namespace Umbraco.Core.Cache +{ + /// + /// A cache refresher that supports refreshing cache based on a custom payload + /// + interface IPayloadCacheRefresher : IJsonCacheRefresher + { + /// + /// Refreshes, clears, etc... any cache based on the information provided in the payload + /// + /// + void Refresh(object payload); + } +} diff --git a/src/Umbraco.Core/Cache/JsonCacheRefresherBase.cs b/src/Umbraco.Core/Cache/JsonCacheRefresherBase.cs index 708e2e1605..48dd008a3d 100644 --- a/src/Umbraco.Core/Cache/JsonCacheRefresherBase.cs +++ b/src/Umbraco.Core/Cache/JsonCacheRefresherBase.cs @@ -4,17 +4,16 @@ using umbraco.interfaces; namespace Umbraco.Core.Cache { /// - /// A base class for json cache refreshers that ensures the correct events are raised when - /// cache refreshing occurs. + /// Provides a base class for "json" cache refreshers. /// - /// The real cache refresher type, this is used for raising strongly typed events - public abstract class JsonCacheRefresherBase : CacheRefresherBase, IJsonCacheRefresher - where TInstanceType : ICacheRefresher + /// The actual cache refresher type. + /// Ensures that the correct events are raised when cache refreshing occurs. + public abstract class JsonCacheRefresherBase : CacheRefresherBase, IJsonCacheRefresher + where TInstance : ICacheRefresher { - - public virtual void Refresh(string jsonPayload) + public virtual void Refresh(string json) { - OnCacheUpdated(Instance, new CacheRefresherEventArgs(jsonPayload, MessageType.RefreshByJson)); + OnCacheUpdated(Instance, new CacheRefresherEventArgs(json, MessageType.RefreshByJson)); } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs b/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs index 509943fe3e..8afa5639f1 100644 --- a/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs +++ b/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs @@ -21,9 +21,6 @@ namespace Umbraco.Core.Cache private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); internal ObjectCache MemoryCache; - // an object that represent a value that has not been created yet - protected readonly object ValueNotCreated = new object(); - /// /// Used for debugging /// @@ -35,21 +32,6 @@ namespace Umbraco.Core.Cache InstanceId = Guid.NewGuid(); } - protected object GetSafeLazyValue(Lazy lazy, bool onlyIfValueIsCreated = false) - { - try - { - // if onlyIfValueIsCreated, do not trigger value creation - // must return something, though, to differenciate from null values - if (onlyIfValueIsCreated && lazy.IsValueCreated == false) return ValueNotCreated; - return lazy.Value; - } - catch - { - return null; - } - } - #region Clear public virtual void ClearAllCache() @@ -67,7 +49,7 @@ namespace Umbraco.Core.Cache { if (MemoryCache[key] == null) return; MemoryCache.Remove(key); - } + } } public virtual void ClearCacheObjectTypes(string typeName) @@ -83,7 +65,7 @@ namespace Umbraco.Core.Cache // x.Value is Lazy and not null, its value may be null // remove null values as well, does not hurt // get non-created as NonCreatedValue & exceptions as null - var value = GetSafeLazyValue((Lazy)x.Value, true); + var value = DictionaryCacheProviderBase.GetSafeLazyValue((Lazy)x.Value, true); // if T is an interface remove anything that implements that interface // otherwise remove exact types (not inherited types) @@ -107,7 +89,7 @@ namespace Umbraco.Core.Cache // x.Value is Lazy and not null, its value may be null // remove null values as well, does not hurt // get non-created as NonCreatedValue & exceptions as null - var value = GetSafeLazyValue((Lazy)x.Value, true); + var value = DictionaryCacheProviderBase.GetSafeLazyValue((Lazy)x.Value, true); // if T is an interface remove anything that implements that interface // otherwise remove exact types (not inherited types) @@ -132,7 +114,7 @@ namespace Umbraco.Core.Cache // x.Value is Lazy and not null, its value may be null // remove null values as well, does not hurt // get non-created as NonCreatedValue & exceptions as null - var value = GetSafeLazyValue((Lazy)x.Value, true); + var value = DictionaryCacheProviderBase.GetSafeLazyValue((Lazy)x.Value, true); if (value == null) return true; // if T is an interface remove anything that implements that interface @@ -155,7 +137,7 @@ namespace Umbraco.Core.Cache .Select(x => x.Key) .ToArray()) // ToArray required to remove MemoryCache.Remove(key); - } + } } public virtual void ClearCacheByKeyExpression(string regexString) @@ -167,7 +149,7 @@ namespace Umbraco.Core.Cache .Select(x => x.Key) .ToArray()) // ToArray required to remove MemoryCache.Remove(key); - } + } } #endregion @@ -184,7 +166,7 @@ namespace Umbraco.Core.Cache .ToArray(); // evaluate while locked } return entries - .Select(x => GetSafeLazyValue((Lazy)x.Value)) // return exceptions as null + .Select(x => DictionaryCacheProviderBase.GetSafeLazyValue((Lazy)x.Value)) // return exceptions as null .Where(x => x != null) // backward compat, don't store null values in the cache .ToList(); } @@ -199,7 +181,7 @@ namespace Umbraco.Core.Cache .ToArray(); // evaluate while locked } return entries - .Select(x => GetSafeLazyValue((Lazy)x.Value)) // return exceptions as null + .Select(x => DictionaryCacheProviderBase.GetSafeLazyValue((Lazy)x.Value)) // return exceptions as null .Where(x => x != null) // backward compat, don't store null values in the cache .ToList(); } @@ -211,7 +193,7 @@ namespace Umbraco.Core.Cache { result = MemoryCache.Get(cacheKey) as Lazy; // null if key not found } - return result == null ? null : GetSafeLazyValue(result); // return exceptions as null + return result == null ? null : DictionaryCacheProviderBase.GetSafeLazyValue(result); // return exceptions as null } public object GetCacheItem(string cacheKey, Func getCacheItem) @@ -219,7 +201,7 @@ namespace Umbraco.Core.Cache return GetCacheItem(cacheKey, getCacheItem, null); } - public object GetCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal,CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) + public object GetCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) { // see notes in HttpRuntimeCacheProvider @@ -228,17 +210,23 @@ namespace Umbraco.Core.Cache using (var lck = new UpgradeableReadLock(_locker)) { result = MemoryCache.Get(cacheKey) as Lazy; - if (result == null || GetSafeLazyValue(result, true) == null) // get non-created as NonCreatedValue & exceptions as null + if (result == null || DictionaryCacheProviderBase.GetSafeLazyValue(result, true) == null) // get non-created as NonCreatedValue & exceptions as null { - result = new Lazy(getCacheItem); + result = DictionaryCacheProviderBase.GetSafeLazy(getCacheItem); var policy = GetPolicy(timeout, isSliding, removedCallback, dependentFiles); lck.UpgradeToWriteLock(); + //NOTE: This does an add or update MemoryCache.Set(cacheKey, result, policy); } } - return result.Value; + //return result.Value; + + var value = result.Value; // will not throw (safe lazy) + var eh = value as DictionaryCacheProviderBase.ExceptionHolder; + if (eh != null) throw eh.Exception; // throw once! + return value; } #endregion @@ -250,11 +238,12 @@ namespace Umbraco.Core.Cache // NOTE - here also we must insert a Lazy but we can evaluate it right now // and make sure we don't store a null value. - var result = new Lazy(getCacheItem); + var result = DictionaryCacheProviderBase.GetSafeLazy(getCacheItem); var value = result.Value; // force evaluation now if (value == null) return; // do not store null values (backward compat) var policy = GetPolicy(timeout, isSliding, removedCallback, dependentFiles); + //NOTE: This does an add or update MemoryCache.Set(cacheKey, result, policy); } @@ -275,7 +264,7 @@ namespace Umbraco.Core.Cache { policy.ChangeMonitors.Add(new HostFileChangeMonitor(dependentFiles.ToList())); } - + if (removedCallback != null) { policy.RemovedCallback = arguments => @@ -306,6 +295,5 @@ namespace Umbraco.Core.Cache } return policy; } - } } \ No newline at end of file diff --git a/src/Umbraco.Core/Cache/PayloadCacheRefresherBase.cs b/src/Umbraco.Core/Cache/PayloadCacheRefresherBase.cs new file mode 100644 index 0000000000..b3ea2ff7b1 --- /dev/null +++ b/src/Umbraco.Core/Cache/PayloadCacheRefresherBase.cs @@ -0,0 +1,27 @@ +using Umbraco.Core.Sync; +using umbraco.interfaces; + +namespace Umbraco.Core.Cache +{ + /// + /// Provides a base class for "payload" cache refreshers. + /// + /// The actual cache refresher type. + /// Ensures that the correct events are raised when cache refreshing occurs. + public abstract class PayloadCacheRefresherBase : JsonCacheRefresherBase, IPayloadCacheRefresher + where TInstance : ICacheRefresher + { + protected abstract object Deserialize(string json); + + public override void Refresh(string json) + { + var payload = Deserialize(json); + Refresh(payload); + } + + public virtual void Refresh(object payload) + { + OnCacheUpdated(Instance, new CacheRefresherEventArgs(payload, MessageType.RefreshByPayload)); + } + } +} diff --git a/src/Umbraco.Core/Configuration/GlobalSettings.cs b/src/Umbraco.Core/Configuration/GlobalSettings.cs index a32b38e9da..525bff2999 100644 --- a/src/Umbraco.Core/Configuration/GlobalSettings.cs +++ b/src/Umbraco.Core/Configuration/GlobalSettings.cs @@ -36,7 +36,7 @@ namespace Umbraco.Core.Configuration //make this volatile so that we can ensure thread safety with a double check lock private static volatile string _reservedUrlsCache; private static string _reservedPathsCache; - private static StartsWithContainer _reservedList = new StartsWithContainer(); + private static HashSet _reservedList = new HashSet(); private static string _reservedPaths; private static string _reservedUrls; //ensure the built on (non-changeable) reserved paths are there at all times @@ -242,6 +242,7 @@ namespace Umbraco.Core.Configuration } } + //TODO: Move these to constants! public const string UmbracoConnectionName = "umbracoDbDSN"; public const string UmbracoMigrationName = "Umbraco"; @@ -420,14 +421,15 @@ namespace Umbraco.Core.Configuration /// Gets a value indicating whether the current version of umbraco is configured. /// /// true if configured; otherwise, false. - public static bool Configured + [Obsolete("Do not use this, it is no longer in use and will be removed from the codebase in future versions")] + internal static bool Configured { get { try { string configStatus = ConfigurationStatus; - string currentVersion = UmbracoVersion.Current.ToString(3); + string currentVersion = UmbracoVersion.GetSemanticVersion().ToSemanticString(); if (currentVersion != configStatus) @@ -594,10 +596,7 @@ namespace Umbraco.Core.Configuration [Obsolete("Use Umbraco.Core.Configuration.UmbracoVersion.Current instead", false)] public static string CurrentVersion { - get - { - return UmbracoVersion.Current.ToString(3); - } + get { return UmbracoVersion.GetSemanticVersion().ToSemanticString(); } } /// @@ -768,38 +767,31 @@ namespace Umbraco.Core.Configuration // store references to strings to determine changes _reservedPathsCache = GlobalSettings.ReservedPaths; _reservedUrlsCache = GlobalSettings.ReservedUrls; - - string _root = SystemDirectories.Root.Trim().ToLower(); - + // add URLs and paths to a new list - StartsWithContainer _newReservedList = new StartsWithContainer(); - foreach (string reservedUrl in _reservedUrlsCache.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries)) + var newReservedList = new HashSet(); + foreach (var reservedUrlTrimmed in _reservedUrlsCache + .Split(new[] {","}, StringSplitOptions.RemoveEmptyEntries) + .Select(x => x.Trim().ToLowerInvariant()) + .Where(x => x.IsNullOrWhiteSpace() == false) + .Select(reservedUrl => IOHelper.ResolveUrl(reservedUrl).Trim().EnsureStartsWith("/")) + .Where(reservedUrlTrimmed => reservedUrlTrimmed.IsNullOrWhiteSpace() == false)) { - if (string.IsNullOrWhiteSpace(reservedUrl)) - continue; - - - //resolves the url to support tilde chars - string reservedUrlTrimmed = IOHelper.ResolveUrl(reservedUrl.Trim()).Trim().ToLower(); - if (reservedUrlTrimmed.Length > 0) - _newReservedList.Add(reservedUrlTrimmed); + newReservedList.Add(reservedUrlTrimmed); } - foreach (string reservedPath in _reservedPathsCache.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries)) + foreach (var reservedPathTrimmed in _reservedPathsCache + .Split(new[] {","}, StringSplitOptions.RemoveEmptyEntries) + .Select(x => x.Trim().ToLowerInvariant()) + .Where(x => x.IsNullOrWhiteSpace() == false) + .Select(reservedPath => IOHelper.ResolveUrl(reservedPath).Trim().EnsureStartsWith("/").EnsureEndsWith("/")) + .Where(reservedPathTrimmed => reservedPathTrimmed.IsNullOrWhiteSpace() == false)) { - bool trimEnd = !reservedPath.EndsWith("/"); - if (string.IsNullOrWhiteSpace(reservedPath)) - continue; - - //resolves the url to support tilde chars - string reservedPathTrimmed = IOHelper.ResolveUrl(reservedPath.Trim()).Trim().ToLower(); - - if (reservedPathTrimmed.Length > 0) - _newReservedList.Add(reservedPathTrimmed + (reservedPathTrimmed.EndsWith("/") ? "" : "/")); + newReservedList.Add(reservedPathTrimmed); } // use the new list from now on - _reservedList = _newReservedList; + _reservedList = newReservedList; } } } @@ -807,107 +799,17 @@ namespace Umbraco.Core.Configuration //The url should be cleaned up before checking: // * If it doesn't contain an '.' in the path then we assume it is a path based URL, if that is the case we should add an trailing '/' because all of our reservedPaths use a trailing '/' // * We shouldn't be comparing the query at all - var pathPart = url.Split('?')[0]; - if (!pathPart.Contains(".") && !pathPart.EndsWith("/")) + var pathPart = url.Split(new[] {'?'}, StringSplitOptions.RemoveEmptyEntries)[0].ToLowerInvariant(); + if (pathPart.Contains(".") == false) { - pathPart += "/"; + pathPart = pathPart.EnsureEndsWith('/'); } // return true if url starts with an element of the reserved list - return _reservedList.StartsWith(pathPart.ToLowerInvariant()); + return _reservedList.Any(x => pathPart.InvariantStartsWith(x)); } - /// - /// Structure that checks in logarithmic time - /// if a given string starts with one of the added keys. - /// - private class StartsWithContainer - { - /// Internal sorted list of keys. - public SortedList _list - = new SortedList(StartsWithComparator.Instance); - - /// - /// Adds the specified new key. - /// - /// The new key. - public void Add(string newKey) - { - // if the list already contains an element that begins with newKey, return - if (String.IsNullOrEmpty(newKey) || StartsWith(newKey)) - return; - - // create a new collection, so the old one can still be accessed - SortedList newList - = new SortedList(_list.Count + 1, StartsWithComparator.Instance); - - // add only keys that don't already start with newKey, others are unnecessary - foreach (string key in _list.Keys) - if (!key.StartsWith(newKey)) - newList.Add(key, null); - // add the new key - newList.Add(newKey, null); - - // update the list (thread safe, _list was never in incomplete state) - _list = newList; - } - - /// - /// Checks if the given string starts with any of the added keys. - /// - /// The target. - /// true if a key is found that matches the start of target - /// - /// Runs in O(s*log(n)), with n the number of keys and s the length of target. - /// - public bool StartsWith(string target) - { - return _list.ContainsKey(target); - } - - /// Comparator that tests if a string starts with another. - /// Not a real comparator, since it is not reflexive. (x==y does not imply y==x) - private sealed class StartsWithComparator : IComparer - { - /// Default string comparer. - private readonly static Comparer _stringComparer = Comparer.Default; - - /// Gets an instance of the StartsWithComparator. - public static readonly StartsWithComparator Instance = new StartsWithComparator(); - - /// - /// Tests if whole begins with all characters of part. - /// - /// The part. - /// The whole. - /// - /// Returns 0 if whole starts with part, otherwise performs standard string comparison. - /// - public int Compare(string part, string whole) - { - // let the default string comparer deal with null or when part is not smaller then whole - if (part == null || whole == null || part.Length >= whole.Length) - return _stringComparer.Compare(part, whole); - - ////ensure both have a / on the end - //part = part.EndsWith("/") ? part : part + "/"; - //whole = whole.EndsWith("/") ? whole : whole + "/"; - //if (part.Length >= whole.Length) - // return _stringComparer.Compare(part, whole); - - // loop through all characters that part and whole have in common - int pos = 0; - bool match; - do - { - match = (part[pos] == whole[pos]); - } while (match && ++pos < part.Length); - - // return result of last comparison - return match ? 0 : (part[pos] < whole[pos] ? -1 : 1); - } - } - } + } diff --git a/src/Umbraco.Core/Configuration/Grid/GridConfig.cs b/src/Umbraco.Core/Configuration/Grid/GridConfig.cs new file mode 100644 index 0000000000..334124a4e5 --- /dev/null +++ b/src/Umbraco.Core/Configuration/Grid/GridConfig.cs @@ -0,0 +1,16 @@ +using System.IO; +using Umbraco.Core.Cache; +using Umbraco.Core.Logging; + +namespace Umbraco.Core.Configuration.Grid +{ + class GridConfig : IGridConfig + { + public GridConfig(ILogger logger, IRuntimeCacheProvider runtimeCache, DirectoryInfo appPlugins, DirectoryInfo configFolder, bool isDebug) + { + EditorsConfig = new GridEditorsConfig(logger, runtimeCache, appPlugins, configFolder, isDebug); + } + + public IGridEditorsConfig EditorsConfig { get; private set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs b/src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs new file mode 100644 index 0000000000..389c620637 --- /dev/null +++ b/src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Newtonsoft.Json.Linq; +using Umbraco.Core.Cache; +using Umbraco.Core.Logging; +using Umbraco.Core.Manifest; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Core.Configuration.Grid +{ + class GridEditorsConfig : IGridEditorsConfig + { + private readonly ILogger _logger; + private readonly IRuntimeCacheProvider _runtimeCache; + private readonly DirectoryInfo _appPlugins; + private readonly DirectoryInfo _configFolder; + private readonly bool _isDebug; + + public GridEditorsConfig(ILogger logger, IRuntimeCacheProvider runtimeCache, DirectoryInfo appPlugins, DirectoryInfo configFolder, bool isDebug) + { + _logger = logger; + _runtimeCache = runtimeCache; + _appPlugins = appPlugins; + _configFolder = configFolder; + _isDebug = isDebug; + } + + public IEnumerable Editors + { + get + { + Func> getResult = () => + { + var editors = new List(); + var gridConfig = Path.Combine(_configFolder.FullName, "grid.editors.config.js"); + if (File.Exists(gridConfig)) + { + try + { + var arr = JArray.Parse(File.ReadAllText(gridConfig)); + //ensure the contents parse correctly to objects + var parsed = ManifestParser.GetGridEditors(arr); + editors.AddRange(parsed); + } + catch (Exception ex) + { + _logger.Error("Could not parse the contents of grid.editors.config.js into a JSON array", ex); + } + } + + var parser = new ManifestParser(_appPlugins, _runtimeCache); + var builder = new ManifestBuilder(_runtimeCache, parser); + foreach (var gridEditor in builder.GridEditors) + { + //no duplicates! (based on alias) + if (editors.Contains(gridEditor) == false) + { + editors.Add(gridEditor); + } + } + return editors; + }; + + //cache the result if debugging is disabled + var result = _isDebug + ? getResult() + : _runtimeCache.GetCacheItem>( + typeof(GridEditorsConfig) + "Editors", + () => getResult(), + TimeSpan.FromMinutes(10)); + + return result; + } + + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/Grid/IGridConfig.cs b/src/Umbraco.Core/Configuration/Grid/IGridConfig.cs new file mode 100644 index 0000000000..a1170c136e --- /dev/null +++ b/src/Umbraco.Core/Configuration/Grid/IGridConfig.cs @@ -0,0 +1,14 @@ +using System; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Umbraco.Core.Configuration.Grid +{ + public interface IGridConfig + { + + IGridEditorsConfig EditorsConfig { get; } + + } +} diff --git a/src/Umbraco.Core/Configuration/Grid/IGridEditorConfig.cs b/src/Umbraco.Core/Configuration/Grid/IGridEditorConfig.cs new file mode 100644 index 0000000000..0eedd5e035 --- /dev/null +++ b/src/Umbraco.Core/Configuration/Grid/IGridEditorConfig.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using Newtonsoft.Json.Linq; + +namespace Umbraco.Core.Configuration.Grid +{ + public interface IGridEditorConfig + { + string Name { get; } + string Alias { get; } + string View { get; } + string Render { get; } + string Icon { get; } + IDictionary Config { get; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/Grid/IGridEditorsConfig.cs b/src/Umbraco.Core/Configuration/Grid/IGridEditorsConfig.cs new file mode 100644 index 0000000000..64fb1a831f --- /dev/null +++ b/src/Umbraco.Core/Configuration/Grid/IGridEditorsConfig.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace Umbraco.Core.Configuration.Grid +{ + public interface IGridEditorsConfig + { + IEnumerable Editors { get; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoConfig.cs b/src/Umbraco.Core/Configuration/UmbracoConfig.cs index 1be7b5aadf..3cd9bab776 100644 --- a/src/Umbraco.Core/Configuration/UmbracoConfig.cs +++ b/src/Umbraco.Core/Configuration/UmbracoConfig.cs @@ -1,6 +1,9 @@ using System; using System.Configuration; +using System.IO; +using Umbraco.Core.Cache; using Umbraco.Core.Configuration.Dashboard; +using Umbraco.Core.Configuration.Grid; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; @@ -53,6 +56,7 @@ namespace Umbraco.Core.Configuration private IDashboardSection _dashboardSection; private IUmbracoSettingsSection _umbracoSettings; + private IGridConfig _gridConfig; /// /// Gets the IDashboardSection @@ -103,6 +107,28 @@ namespace Umbraco.Core.Configuration } + } + + /// + /// Only for testing + /// + /// + public void SetGridConfig(IGridConfig value) + { + _gridConfig = value; + } + + /// + /// Gets the IGridConfig + /// + public IGridConfig GridConfig(ILogger logger, IRuntimeCacheProvider runtimeCache, DirectoryInfo appPlugins, DirectoryInfo configFolder, bool isDebug) + { + if (_gridConfig == null) + { + _gridConfig = new GridConfig(logger, runtimeCache, appPlugins, configFolder, isDebug); + } + + return _gridConfig; //TODO: Add other configurations here ! } diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IWebRoutingSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IWebRoutingSection.cs index f3d42b6904..2998fc2f78 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IWebRoutingSection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IWebRoutingSection.cs @@ -11,6 +11,8 @@ bool DisableFindContentByIdPath { get; } string UrlProviderMode { get; } + + string UmbracoApplicationUrl { get; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/WebRoutingElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/WebRoutingElement.cs index f5b71eb2c7..1ed9bc034c 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/WebRoutingElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/WebRoutingElement.cs @@ -30,8 +30,13 @@ namespace Umbraco.Core.Configuration.UmbracoSettings [ConfigurationProperty("urlProviderMode", DefaultValue = "AutoLegacy")] public string UrlProviderMode { - get { return (string)base["urlProviderMode"]; } + get { return (string) base["urlProviderMode"]; } } + [ConfigurationProperty("umbracoApplicationUrl", DefaultValue = null)] + public string UmbracoApplicationUrl + { + get { return (string)base["umbracoApplicationUrl"]; } + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoVersion.cs b/src/Umbraco.Core/Configuration/UmbracoVersion.cs index d79b2d6123..ef33e7a966 100644 --- a/src/Umbraco.Core/Configuration/UmbracoVersion.cs +++ b/src/Umbraco.Core/Configuration/UmbracoVersion.cs @@ -1,5 +1,6 @@ using System; using System.Reflection; +using Semver; namespace Umbraco.Core.Configuration { @@ -23,10 +24,20 @@ namespace Umbraco.Core.Configuration /// Gets the version comment (like beta or RC). /// /// The version comment. - public static string CurrentComment { get { return ""; } } + public static string CurrentComment { get { return "beta"; } } // Get the version of the umbraco.dll by looking at a class in that dll // Had to do it like this due to medium trust issues, see: http://haacked.com/archive/2010/11/04/assembly-location-and-medium-trust.aspx public static string AssemblyVersion { get { return new AssemblyName(typeof(ActionsResolver).Assembly.FullName).Version.ToString(); } } + + public static SemVersion GetSemanticVersion() + { + return new SemVersion( + Current.Major, + Current.Minor, + Current.Build, + CurrentComment.IsNullOrWhiteSpace() ? null : CurrentComment, + Current.Revision > 0 ? Current.Revision.ToInvariantString() : null); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Constants-Applications.cs b/src/Umbraco.Core/Constants-Applications.cs index f03108f4ac..12f7076fc4 100644 --- a/src/Umbraco.Core/Constants-Applications.cs +++ b/src/Umbraco.Core/Constants-Applications.cs @@ -43,7 +43,7 @@ public const string Users = "users"; /// - /// Application alias for the users section. + /// Application alias for the forms section. /// public const string Forms = "forms"; } @@ -59,7 +59,7 @@ public const string Content = "content"; /// - /// alias for the media tree. + /// alias for the member tree. /// public const string Members = "member"; @@ -71,7 +71,7 @@ /// /// alias for the datatype tree. /// - public const string DataTypes = "datatype"; + public const string DataTypes = "dataTypes"; /// /// alias for the dictionary tree. @@ -80,6 +80,22 @@ public const string Stylesheets = "stylesheets"; + /// + /// alias for the document type tree. + /// + public const string DocumentTypes = "documentTypes"; + + /// + /// alias for the media type tree. + /// + public const string MediaTypes = "mediaTypes"; + + + /// + /// alias for the member type tree. + /// + public const string MemberTypes = "memberTypes"; + /// /// alias for the template tree. /// diff --git a/src/Umbraco.Core/Constants-Conventions.cs b/src/Umbraco.Core/Constants-Conventions.cs index c1d5bd35e3..7e2bb88964 100644 --- a/src/Umbraco.Core/Constants-Conventions.cs +++ b/src/Umbraco.Core/Constants-Conventions.cs @@ -27,6 +27,7 @@ namespace Umbraco.Core /// /// The root id for all top level dictionary items /// + [Obsolete("There is no dictionary root item id anymore, it is simply null")] public const string DictionaryItemRootId = "41c7638d-f529-4bff-853e-59a0c2fb1bde"; } @@ -205,7 +206,7 @@ namespace Umbraco.Core /// internal const string StandardPropertiesGroupName = "Membership"; - internal static Dictionary GetStandardPropertyTypeStubs() + public static Dictionary GetStandardPropertyTypeStubs() { return new Dictionary { diff --git a/src/Umbraco.Core/Constants-Icons.cs b/src/Umbraco.Core/Constants-Icons.cs new file mode 100644 index 0000000000..a0928919f0 --- /dev/null +++ b/src/Umbraco.Core/Constants-Icons.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Umbraco.Core +{ + public static partial class Constants + { + public static class Icons + { + + + /// + /// System contenttype icon + /// + public const string ContentType = "icon-arrangement"; + + /// + /// System datatype icon + /// + public const string DataType = "icon-autofill"; + + /// + /// System property editor icon + /// + public const string PropertyEditor = "icon-autofill"; + + /// + /// System macro icon + /// + public const string Macro = "icon-settings-alt"; + + /// + /// System member icon + /// + public const string Member = "icon-user"; + + /// + /// System member icon + /// + public const string MemberType = "icon-users"; + + + /// + /// System member icon + /// + public const string Template = "icon-layout"; + + } + } +} diff --git a/src/Umbraco.Core/Constants-ObjectTypes.cs b/src/Umbraco.Core/Constants-ObjectTypes.cs index 81bd66928d..560cd4b306 100644 --- a/src/Umbraco.Core/Constants-ObjectTypes.cs +++ b/src/Umbraco.Core/Constants-ObjectTypes.cs @@ -2,91 +2,148 @@ namespace Umbraco.Core { - public static partial class Constants - { - /// - /// Defines the identifiers for Umbraco object types as constants for easy centralized access/management. - /// - public static class ObjectTypes - { - /// - /// Guid for a Content Item object. - /// - public const string ContentItem = "10E2B09F-C28B-476D-B77A-AA686435E44A"; + public static partial class Constants + { + /// + /// Defines the identifiers for Umbraco object types as constants for easy centralized access/management. + /// + public static class ObjectTypes + { + /// + /// Guid for a data type container + /// + public const string DataTypeContainer = "521231E3-8B37-469C-9F9D-51AFC91FEB7B"; - /// - /// Guid for a Content Item Type object. - /// - public const string ContentItemType = "7A333C54-6F43-40A4-86A2-18688DC7E532"; + /// + /// Guid for a data type container + /// + public static readonly Guid DataTypeContainerGuid = new Guid(DataTypeContainer); - /// - /// Guid for the Content Recycle Bin. - /// - public const string ContentRecycleBin = "01BB7FF2-24DC-4C0C-95A2-C24EF72BBAC8"; + /// + /// Guid for a doc type container + /// + public const string DocumentTypeContainer = "2F7A2769-6B0B-4468-90DD-AF42D64F7F16"; - /// - /// Guid for a DataType object. - /// - public const string DataType = "30A2A501-1978-4DDB-A57B-F7EFED43BA3C"; + /// + /// Guid for a doc type container + /// + public static readonly Guid DocumentTypeContainerGuid = new Guid(DocumentTypeContainer); - /// - /// Guid for a Document object. - /// - public const string Document = "C66BA18E-EAF3-4CFF-8A22-41B16D66A972"; + /// + /// Guid for a doc type container + /// + public const string MediaTypeContainer = "42AEF799-B288-4744-9B10-BE144B73CDC4"; - /// - /// Guid for a Document Type object. - /// - public const string DocumentType = "A2CB7800-F571-4787-9638-BC48539A0EFB"; + /// + /// Guid for a doc type container + /// + public static readonly Guid MediaTypeContainerGuid = new Guid(MediaTypeContainer); - /// - /// Guid for a Media object. - /// - public const string Media = "B796F64C-1F99-4FFB-B886-4BF4BC011A9C"; + /// + /// Guid for a Content Item object. + /// + public const string ContentItem = "10E2B09F-C28B-476D-B77A-AA686435E44A"; - /// - /// Guid for the Media Recycle Bin. - /// - public const string MediaRecycleBin = "CF3D8E34-1C1C-41e9-AE56-878B57B32113"; + /// + /// Guid for a Content Item Type object. + /// + public const string ContentItemType = "7A333C54-6F43-40A4-86A2-18688DC7E532"; - /// - /// Guid for a Media Type object. - /// - public const string MediaType = "4EA4382B-2F5A-4C2B-9587-AE9B3CF3602E"; + /// + /// Guid for the Content Recycle Bin. + /// + public const string ContentRecycleBin = "01BB7FF2-24DC-4C0C-95A2-C24EF72BBAC8"; - /// - /// Guid for a Member object. - /// - public const string Member = "39EB0F98-B348-42A1-8662-E7EB18487560"; + /// + /// Guid for a DataType object. + /// + public const string DataType = "30A2A501-1978-4DDB-A57B-F7EFED43BA3C"; - /// - /// Guid for a Member Group object. - /// - public const string MemberGroup = "366E63B9-880F-4E13-A61C-98069B029728"; + /// + /// Guid for a DataType object. + /// + public static readonly Guid DataTypeGuid = new Guid(DataType); - /// - /// Guid for a Member Type object. - /// - public const string MemberType = "9B5416FB-E72F-45A9-A07B-5A9A2709CE43"; + /// + /// Guid for a Document object. + /// + public const string Document = "C66BA18E-EAF3-4CFF-8A22-41B16D66A972"; - /// - /// Guid for a Stylesheet object. - /// + /// + /// Guid for a Document Type object. + /// + public const string DocumentType = "A2CB7800-F571-4787-9638-BC48539A0EFB"; + + /// + /// Guid for a Document Type object. + /// + public static readonly Guid DocumentTypeGuid = new Guid(DocumentType); + + /// + /// Guid for a Media object. + /// + public const string Media = "B796F64C-1F99-4FFB-B886-4BF4BC011A9C"; + + /// + /// Guid for the Media Recycle Bin. + /// + public const string MediaRecycleBin = "CF3D8E34-1C1C-41e9-AE56-878B57B32113"; + + /// + /// Guid for a Media Type object. + /// + public const string MediaType = "4EA4382B-2F5A-4C2B-9587-AE9B3CF3602E"; + + /// + /// Guid for a Media Type object. + /// + public static readonly Guid MediaTypeGuid = new Guid(MediaType); + + /// + /// Guid for a Member object. + /// + public const string Member = "39EB0F98-B348-42A1-8662-E7EB18487560"; + + /// + /// Guid for a Member Group object. + /// + public const string MemberGroup = "366E63B9-880F-4E13-A61C-98069B029728"; + + /// + /// Guid for a Member Type object. + /// + public const string MemberType = "9B5416FB-E72F-45A9-A07B-5A9A2709CE43"; + + /// + /// Guid for a Member Type object. + /// + public static readonly Guid MemberTypeGuid = new Guid(MemberType); + + /// + /// Guid for a Stylesheet object. + /// [Obsolete("This no longer exists in the database")] public const string Stylesheet = "9F68DA4F-A3A8-44C2-8226-DCBD125E4840"; [Obsolete("This no longer exists in the database")] internal const string StylesheetProperty = "5555da4f-a123-42b2-4488-dcdfb25e4111"; - /// - /// Guid for the System Root. - /// - public const string SystemRoot = "EA7D8624-4CFE-4578-A871-24AA946BF34D"; + /// + /// Guid for the System Root. + /// + public const string SystemRoot = "EA7D8624-4CFE-4578-A871-24AA946BF34D"; - /// - /// Guid for a Template object. - /// - public const string Template = "6FBDE604-4178-42CE-A10B-8A2600A2F07D"; - } - } + /// + /// Guid for a Template object. + /// + public const string Template = "6FBDE604-4178-42CE-A10B-8A2600A2F07D"; + + /// + /// Guid for a Lock object. + /// + public const string LockObject = "87A9F1FF-B1E4-4A25-BABB-465A4A47EC41"; + + + } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Constants-PropertyEditors.cs b/src/Umbraco.Core/Constants-PropertyEditors.cs index a719e845b1..2f7d247b36 100644 --- a/src/Umbraco.Core/Constants-PropertyEditors.cs +++ b/src/Umbraco.Core/Constants-PropertyEditors.cs @@ -158,6 +158,11 @@ namespace Umbraco.Core /// public const string IntegerAlias = "Umbraco.Integer"; + /// + /// Alias for the Decimal datatype. + /// + public const string DecimalAlias = "Umbraco.Decimal"; + /// /// Alias for the listview datatype. /// @@ -310,13 +315,13 @@ namespace Umbraco.Core public const string TextboxAlias = "Umbraco.Textbox"; /// - /// Guid for the Textbox multiple datatype. + /// Guid for the Textarea datatype. /// [Obsolete("GUIDs are no longer used to reference Property Editors, use the Alias constant instead. This will be removed in future versions")] public const string TextboxMultiple = "67DB8357-EF57-493E-91AC-936D305E0F2A"; /// - /// Alias for the Textbox multiple datatype. + /// Alias for the Textarea datatype. /// public const string TextboxMultipleAlias = "Umbraco.TextboxMultiple"; diff --git a/src/Umbraco.Core/Constants-PropertyTypeGroups.cs b/src/Umbraco.Core/Constants-PropertyTypeGroups.cs new file mode 100644 index 0000000000..5dabe90029 --- /dev/null +++ b/src/Umbraco.Core/Constants-PropertyTypeGroups.cs @@ -0,0 +1,31 @@ +namespace Umbraco.Core +{ + public static partial class Constants + { + /// + /// Defines the identifiers for property-type groups conventions that are used within the Umbraco core. + /// + public static class PropertyTypeGroups + { + /// + /// Guid for a Image PropertyTypeGroup object. + /// + public const string Image = "79ED4D07-254A-42CF-8FA9-EBE1C116A596"; + + /// + /// Guid for a File PropertyTypeGroup object. + /// + public const string File = "50899F9C-023A-4466-B623-ABA9049885FE"; + + /// + /// Guid for a Image PropertyTypeGroup object. + /// + public const string Contents = "79995FA2-63EE-453C-A29B-2E66F324CDBE"; + + /// + /// Guid for a Image PropertyTypeGroup object. + /// + public const string Membership = "0756729D-D665-46E3-B84A-37ACEAA614F8"; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Constants-System.cs b/src/Umbraco.Core/Constants-System.cs index b72afa265f..82e3a1ff3f 100644 --- a/src/Umbraco.Core/Constants-System.cs +++ b/src/Umbraco.Core/Constants-System.cs @@ -26,6 +26,8 @@ public const int DefaultMediaListViewDataTypeId = -96; public const int DefaultMembersListViewDataTypeId = -97; + // identifiers for lock objects + public const int ServersLock = -331; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Constants-Web.cs b/src/Umbraco.Core/Constants-Web.cs index 0d7c2f41da..60fba0ae40 100644 --- a/src/Umbraco.Core/Constants-Web.cs +++ b/src/Umbraco.Core/Constants-Web.cs @@ -1,4 +1,7 @@ -namespace Umbraco.Core +using System; +using System.ComponentModel; + +namespace Umbraco.Core { public static partial class Constants { @@ -15,6 +18,8 @@ /// /// The auth cookie name /// + [Obsolete("DO NOT USE THIS, USE ISecuritySection.AuthCookieName, this will be removed in future versions")] + [EditorBrowsable(EditorBrowsableState.Never)] public const string AuthCookieName = "UMB_UCONTEXT"; } @@ -26,6 +31,7 @@ public const string BackOfficeExternalAuthenticationType = "UmbracoExternalCookie"; public const string BackOfficeExternalCookieName = "UMB_EXTLOGIN"; public const string BackOfficeTokenAuthenticationType = "UmbracoBackOfficeToken"; + public const string BackOfficeTwoFactorAuthenticationType = "UmbracoTwoFactorCookie"; /// /// The prefix used for external identity providers for their authentication type diff --git a/src/Umbraco.Core/CoreBootManager.cs b/src/Umbraco.Core/CoreBootManager.cs index 990e8d471e..2c8ffe4f7f 100644 --- a/src/Umbraco.Core/CoreBootManager.cs +++ b/src/Umbraco.Core/CoreBootManager.cs @@ -3,10 +3,13 @@ using System.IO; using System.IO; using System.Linq; using System.Threading.Tasks; +using System.Threading; using AutoMapper; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Events; +using Umbraco.Core.Exceptions; using Umbraco.Core.IO; using Umbraco.Core.LightInject; using Umbraco.Core.Logging; @@ -51,8 +54,8 @@ namespace Umbraco.Core private bool _isStarted = false; private bool _isComplete = false; private readonly UmbracoApplicationBase _umbracoApplication; - protected ApplicationContext ApplicationContext { get; private set; } + protected CacheHelper ApplicationCache { get; private set; } protected UmbracoApplicationBase UmbracoApplication { @@ -72,27 +75,32 @@ namespace Umbraco.Core _umbracoApplication = umbracoApplication; } + internal CoreBootManager(UmbracoApplicationBase umbracoApplication, ProfilingLogger logger) + { + if (umbracoApplication == null) throw new ArgumentNullException("umbracoApplication"); + if (logger == null) throw new ArgumentNullException("logger"); + _umbracoApplication = umbracoApplication; + ProfilingLogger = logger; + } + public virtual IBootManager Initialize() { if (_isInitialized) throw new InvalidOperationException("The boot manager has already been initialized"); - //Create logger/profiler, and their resolvers, these are special resolvers that can be resolved before frozen so we can start logging - LoggerResolver.Current = new LoggerResolver(_umbracoApplication.Logger) { CanResolveBeforeFrozen = true }; - var profiler = CreateProfiler(); - ProfilerResolver.Current = new ProfilerResolver(profiler) {CanResolveBeforeFrozen = true}; - ProfilingLogger = new ProfilingLogger(_umbracoApplication.Logger, profiler); + InitializeLoggerResolver(); + InitializeProfilerResolver(); - _timer = ProfilingLogger.DebugDuration("Umbraco application starting", "Umbraco application startup complete"); + ProfilingLogger = ProfilingLogger?? new ProfilingLogger(LoggerResolver.Current.Logger, ProfilerResolver.Current.Profiler); - //create the plugin manager - //TODO: this is currently a singleton but it would be better if it weren't. Unfortunately the only way to get - // rid of this singleton would be to put it into IoC and then use the ServiceLocator pattern. + _timer = ProfilingLogger.TraceDuration( + string.Format("Umbraco {0} application starting on {1}", UmbracoVersion.GetSemanticVersion().ToSemanticString(), NetworkHelper.MachineName), + "Umbraco application startup complete"); _cacheHelper = CreateApplicationCache(); ServiceProvider = new ActivatorServiceProvider(); PluginManager.Current = PluginManager = new PluginManager(ServiceProvider, _cacheHelper.RuntimeCache, ProfilingLogger, true); - //build up core IoC servoces + ApplicationCache = CreateApplicationCache(); ConfigureCoreServices(Container); //set the singleton resolved from the core container @@ -118,6 +126,7 @@ namespace Umbraco.Core InitializeModelMappers(); //now we need to call the initialize methods + //TODO: Make sure to try/catch the OnApplicationInitialized!! Parallel.ForEach(_appStartupEvtContainer.GetAllInstances(), x => x.OnApplicationInitialized(UmbracoApplication, ApplicationContext)); _isInitialized = true; @@ -258,6 +267,7 @@ namespace Umbraco.Core if (_isStarted) throw new InvalidOperationException("The boot manager has already been initialized"); + //TODO: Make sure to try/catch the OnApplicationInitialized!! //call OnApplicationStarting of each application events handler Parallel.ForEach(_appStartupEvtContainer.GetAllInstances(), x => x.OnApplicationStarting(UmbracoApplication, ApplicationContext)); @@ -280,12 +290,24 @@ namespace Umbraco.Core { if (_isComplete) throw new InvalidOperationException("The boot manager has already been completed"); - + FreezeResolution(); + //Here we need to make sure the db can be connected to + EnsureDatabaseConnection(); + + + //This is a special case for the user service, we need to tell it if it's an upgrade, if so we need to ensure that + // exceptions are bubbled up if a user is attempted to be persisted during an upgrade (i.e. when they auth to login) + ((UserService) ApplicationContext.Services.UserService).IsUpgrading = true; + + + //call OnApplicationStarting of each application events handler + //TODO: Make sure to try/catch the OnApplicationInitialized!! Parallel.ForEach(_appStartupEvtContainer.GetAllInstances(), x => x.OnApplicationStarted(UmbracoApplication, ApplicationContext)); + //end the current scope which was created to intantiate all of the startup handlers _appStartupEvtContainer.EndCurrentScope(); @@ -302,6 +324,36 @@ namespace Umbraco.Core //stop the timer and log the output _timer.Dispose(); return this; + } + + /// + /// We cannot continue if the db cannot be connected to + /// + private void EnsureDatabaseConnection() + { + if (ApplicationContext.IsConfigured == false) return; + if (ApplicationContext.DatabaseContext.IsDatabaseConfigured == false) return; + + //try now + if (ApplicationContext.DatabaseContext.CanConnect) + return; + + var currentTry = 0; + while (currentTry < 5) + { + //first wait, then retry + Thread.Sleep(1000); + + if (ApplicationContext.DatabaseContext.CanConnect) + break; + + currentTry++; + } + + if (currentTry == 5) + { + throw new UmbracoStartupFailedException("Umbraco cannot start. A connection string is configured but the Umbraco cannot connect to the database."); + } } /// @@ -336,17 +388,34 @@ namespace Umbraco.Core new Lazy(() => typeof (DelimitedManifestValueValidator)), new Lazy(() => typeof (EmailValidator)), new Lazy(() => typeof (IntegerValidator)), + new Lazy(() => typeof (DecimalValidator)), }); - //by default we'll use the standard configuration based sync - ServerRegistrarResolver.Current = new ServerRegistrarResolver(Container, typeof(ConfigServerRegistrar)); + //by default we'll use the db server registrar unless the developer has the legacy + // dist calls enabled, in which case we'll use the config server registrar + if (UmbracoConfig.For.UmbracoSettings().DistributedCall.Enabled) + { + ServerRegistrarResolver.Current = new ServerRegistrarResolver(new ConfigServerRegistrar()); + } + else + { + ServerRegistrarResolver.Current = new ServerRegistrarResolver( + new DatabaseServerRegistrar( + new Lazy(() => ApplicationContext.Services.ServerRegistrationService), + new DatabaseServerRegistrarOptions())); + } + - //by default (outside of the web) we'll use the default server messenger without - //supplying a username/password, this will automatically disable distributed calls - // .. we'll override this in the WebBootManager - ServerMessengerResolver.Current = new ServerMessengerResolver(Container, typeof (WebServiceServerMessenger)); + //by default we'll use the database server messenger with default options (no callbacks), + // this will be overridden in the web startup + ServerMessengerResolver.Current = new ServerMessengerResolver( + new DatabaseServerMessenger(ApplicationContext, true, new DatabaseServerMessengerOptions())); + MappingResolver.Current = new MappingResolver( + ServiceProvider, ProfilingLogger.Logger, + () => PluginManager.ResolveAssignedMapperTypes()); + //RepositoryResolver.Current = new RepositoryResolver( // new RepositoryFactory(ApplicationCache)); diff --git a/src/Umbraco.Core/DatabaseContext.cs b/src/Umbraco.Core/DatabaseContext.cs index 741c4f9746..17b274d4ba 100644 --- a/src/Umbraco.Core/DatabaseContext.cs +++ b/src/Umbraco.Core/DatabaseContext.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Web; using System.Web.Configuration; using System.Xml.Linq; +using Semver; using Umbraco.Core.Configuration; using Umbraco.Core.IO; using Umbraco.Core.Logging; @@ -13,6 +14,7 @@ using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Migrations; using Umbraco.Core.Persistence.Migrations.Initial; using Umbraco.Core.Persistence.SqlSyntax; +using Umbraco.Core.Services; namespace Umbraco.Core { @@ -28,8 +30,6 @@ namespace Umbraco.Core private readonly ILogger _logger; private readonly SqlSyntaxProviders _syntaxProviders; private bool _configured; - private bool _canConnect; - private volatile bool _connectCheck = false; private readonly object _locker = new object(); private string _connectionString; private string _providerName; @@ -78,7 +78,7 @@ namespace Umbraco.Core /// This should not be used for CRUD operations or queries against the /// standard Umbraco tables! Use the Public services for that. /// - public UmbracoDatabase Database + public virtual UmbracoDatabase Database { get { return _factory.CreateDatabase(); } } @@ -86,7 +86,7 @@ namespace Umbraco.Core /// /// Boolean indicating whether the database has been configured /// - public bool IsDatabaseConfigured + public virtual bool IsDatabaseConfigured { get { return _configured; } } @@ -94,33 +94,21 @@ namespace Umbraco.Core /// /// Determines if the db can be connected to /// - public bool CanConnect + public virtual bool CanConnect { get { if (IsDatabaseConfigured == false) return false; - - //double check lock so that it is only checked once and is fast - if (_connectCheck == false) - { - lock (_locker) - { - if (_canConnect == false) - { - _canConnect = DbConnectionExtensions.IsConnectionAvailable(ConnectionString, DatabaseProvider); - _connectCheck = true; - } - } - } - - return _canConnect; + var canConnect = DbConnectionExtensions.IsConnectionAvailable(ConnectionString, DatabaseProvider); + LogHelper.Info("CanConnect = " + canConnect); + return canConnect; } } /// /// Gets the configured umbraco db connection string. /// - public string ConnectionString + public virtual string ConnectionString { get { return _connectionString; } } @@ -152,7 +140,7 @@ namespace Umbraco.Core /// /// Returns the Type of DatabaseProvider used /// - public DatabaseProviders DatabaseProvider + public virtual DatabaseProviders DatabaseProvider { get { @@ -527,7 +515,7 @@ namespace Umbraco.Core return _result; } - internal Result CreateDatabaseSchemaAndData() + internal Result CreateDatabaseSchemaAndData(ApplicationContext applicationContext) { try { @@ -564,13 +552,15 @@ namespace Umbraco.Core message = GetResultMessageForMySql(); var schemaResult = ValidateDatabaseSchema(); - var installedVersion = schemaResult.DetermineInstalledVersion(); + + var installedSchemaVersion = schemaResult.DetermineInstalledVersion(); //If Configuration Status is empty and the determined version is "empty" its a new install - otherwise upgrade the existing - if (string.IsNullOrEmpty(GlobalSettings.ConfigurationStatus) && installedVersion.Equals(new Version(0, 0, 0))) + if (string.IsNullOrEmpty(GlobalSettings.ConfigurationStatus) && installedSchemaVersion.Equals(new Version(0, 0, 0))) { - var schemaHelper = new DatabaseSchemaHelper(database, _logger, SqlSyntax); - schemaHelper.CreateDatabaseSchema(); + var helper = new DatabaseSchemaHelper(database, _logger, SqlSyntax); + helper.CreateDatabaseSchema(true, applicationContext); + message = message + "

Installation completed!

"; //now that everything is done, we need to determine the version of SQL server that is executing @@ -599,7 +589,7 @@ namespace Umbraco.Core /// This assumes all of the previous checks are done! ///
/// - internal Result UpgradeSchemaAndData(IMigrationResolver migrationResolver) + internal Result UpgradeSchemaAndData(IMigrationEntryService migrationEntryService) { try { @@ -618,16 +608,55 @@ namespace Umbraco.Core var message = GetResultMessageForMySql(); var schemaResult = ValidateDatabaseSchema(); - var installedVersion = schemaResult.DetermineInstalledVersion(); + + var installedSchemaVersion = new SemVersion(schemaResult.DetermineInstalledVersion()); + + var installedMigrationVersion = new SemVersion(0); + //we cannot check the migrations table if it doesn't exist, this will occur when upgrading to 7.3 + if (schemaResult.ValidTables.Any(x => x.InvariantEquals("umbracoMigration"))) + { + installedMigrationVersion = schemaResult.DetermineInstalledVersionByMigrations(migrationEntryService); + } + + var targetVersion = UmbracoVersion.Current; + //In some cases - like upgrading from 7.2.6 -> 7.3, there will be no migration information in the database and therefore it will + // return a version of 0.0.0 and we don't necessarily want to run all migrations from 0 -> 7.3, so we'll just ensure that the + // migrations are run for the target version + if (installedMigrationVersion == new SemVersion(new Version(0, 0, 0)) && installedSchemaVersion > new SemVersion(new Version(0, 0, 0))) + { + //set the installedMigrationVersion to be one less than the target so the latest migrations are guaranteed to execute + installedMigrationVersion = new SemVersion(targetVersion.SubtractRevision()); + } + + //Figure out what our current installed version is. If the web.config doesn't have a version listed, then we'll use the minimum + // version detected between the schema installed and the migrations listed in the migration table. + // If there is a version in the web.config, we'll take the minimum between the listed migration in the db and what + // is declared in the web.config. + + var currentInstalledVersion = string.IsNullOrEmpty(GlobalSettings.ConfigurationStatus) + //Take the minimum version between the detected schema version and the installed migration version + ? new[] {installedSchemaVersion, installedMigrationVersion}.Min() + //Take the minimum version between the installed migration version and the version specified in the config + : new[] { SemVersion.Parse(GlobalSettings.ConfigurationStatus), installedMigrationVersion }.Min(); + + //Ok, another edge case here. If the current version is a pre-release, + // then we want to ensure all migrations for the current release are executed. + if (currentInstalledVersion.Prerelease.IsNullOrWhiteSpace() == false) + { + currentInstalledVersion = new SemVersion(currentInstalledVersion.GetVersion().SubtractRevision()); + } + //DO the upgrade! - var currentVersion = string.IsNullOrEmpty(GlobalSettings.ConfigurationStatus) - ? installedVersion - : new Version(GlobalSettings.ConfigurationStatus); - var targetVersion = UmbracoVersion.Current; - var runner = new MigrationRunner(migrationResolver, _logger, currentVersion, targetVersion, GlobalSettings.UmbracoMigrationName); - var upgraded = runner.Execute(database, DatabaseProvider, SqlSyntax, true); + var runner = new MigrationRunner(migrationEntryService, _logger, currentInstalledVersion, UmbracoVersion.GetSemanticVersion(), GlobalSettings.UmbracoMigrationName); + + + if (upgraded == false) + { + throw new ApplicationException("Upgrading failed, either an error occurred during the upgrade process or an event canceled the upgrade process, see log for full details"); + } + message = message + "

Upgrade completed!

"; //now that everything is done, we need to determine the version of SQL server that is executing diff --git a/src/Umbraco.Core/DelegateExtensions.cs b/src/Umbraco.Core/DelegateExtensions.cs new file mode 100644 index 0000000000..78450448e2 --- /dev/null +++ b/src/Umbraco.Core/DelegateExtensions.cs @@ -0,0 +1,44 @@ +using System; +using System.Diagnostics; +using System.Threading; + +namespace Umbraco.Core +{ + public static class DelegateExtensions + { + public static Attempt RetryUntilSuccessOrTimeout(this Func> task, TimeSpan timeout, TimeSpan pause) + { + if (pause.TotalMilliseconds < 0) + { + throw new ArgumentException("pause must be >= 0 milliseconds"); + } + var stopwatch = Stopwatch.StartNew(); + do + { + var result = task(); + if (result) { return result; } + Thread.Sleep((int)pause.TotalMilliseconds); + } + while (stopwatch.Elapsed < timeout); + return Attempt.Fail(); + } + + public static Attempt RetryUntilSuccessOrMaxAttempts(this Func> task, int totalAttempts, TimeSpan pause) + { + if (pause.TotalMilliseconds < 0) + { + throw new ArgumentException("pause must be >= 0 milliseconds"); + } + int attempts = 0; + do + { + attempts++; + var result = task(attempts); + if (result) { return result; } + Thread.Sleep((int)pause.TotalMilliseconds); + } + while (attempts < totalAttempts); + return Attempt.Fail(); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Dictionary/CultureDictionaryFactoryResolver.cs b/src/Umbraco.Core/Dictionary/CultureDictionaryFactoryResolver.cs index 8d744c90a6..cf0227d2a3 100644 --- a/src/Umbraco.Core/Dictionary/CultureDictionaryFactoryResolver.cs +++ b/src/Umbraco.Core/Dictionary/CultureDictionaryFactoryResolver.cs @@ -1,5 +1,6 @@ using System; using System.Linq.Expressions; +using System.ComponentModel; using Umbraco.Core.LightInject; using Umbraco.Core.ObjectResolution; @@ -36,13 +37,13 @@ namespace Umbraco.Core.Dictionary } /// - /// Can be used by developers at runtime to set their ICultureDictionaryFactory at app startup - /// - /// - public void SetContentStore(ICultureDictionaryFactory factory) - { - Value = factory; - } + /// Can be used by developers at runtime to set their ICultureDictionaryFactory at app startup + ///
+ /// + public void SetDictionaryFactory(ICultureDictionaryFactory factory) + { + Value = factory; + } /// /// Returns the ICultureDictionaryFactory diff --git a/src/Umbraco.Core/Dictionary/ICultureDictionary.cs b/src/Umbraco.Core/Dictionary/ICultureDictionary.cs index 57a739dbc6..8617d7440e 100644 --- a/src/Umbraco.Core/Dictionary/ICultureDictionary.cs +++ b/src/Umbraco.Core/Dictionary/ICultureDictionary.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Globalization; namespace Umbraco.Core.Dictionary @@ -19,5 +20,12 @@ namespace Umbraco.Core.Dictionary /// Returns the current culture /// CultureInfo Culture { get; } + + /// + /// Returns the child dictionary entries for a given key + /// + /// + /// + IDictionary GetChildren(string key); } } \ No newline at end of file diff --git a/src/Umbraco.Core/DisposableObject.cs b/src/Umbraco.Core/DisposableObject.cs index f054b53c98..516a9712e5 100644 --- a/src/Umbraco.Core/DisposableObject.cs +++ b/src/Umbraco.Core/DisposableObject.cs @@ -4,69 +4,58 @@ using System.Threading; namespace Umbraco.Core { /// - /// Abstract implementation of logic commonly required to safely handle disposable unmanaged resources. + /// Abstract implementation of IDisposable. /// + /// + /// Can also be used as a pattern for when inheriting is not possible. + /// + /// See also: https://msdn.microsoft.com/en-us/library/b1yfkh5e%28v=vs.110%29.aspx + /// See also: https://lostechies.com/chrispatterson/2012/11/29/idisposable-done-right/ + /// + /// Note: if an object's ctor throws, it will never be disposed, and so if that ctor + /// has allocated disposable objects, it should take care of disposing them. + /// public abstract class DisposableObject : IDisposable { private bool _disposed; - private readonly ReaderWriterLockSlim _disposalLocker = new ReaderWriterLockSlim(); + private readonly object _locko = new object(); - /// - /// Gets a value indicating whether this instance is disposed. - /// - /// - /// true if this instance is disposed; otherwise, false. - /// - public bool IsDisposed - { - get { return _disposed; } - } + // gets a value indicating whether this instance is disposed. + // for internal tests only (not thread safe) + //TODO make this internal + rename "Disposed" when we can break compatibility + public bool IsDisposed { get { return _disposed; } } - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - /// 2 + // implements IDisposable public void Dispose() { Dispose(true); - - // Use SupressFinalize in case a subclass of this type implements a finalizer. GC.SuppressFinalize(this); } + // finalizer ~DisposableObject() { - // Run dispose but let the class know it was due to the finalizer running. Dispose(false); } - protected virtual void Dispose(bool disposing) + //TODO make this private, non-virtual when we can break compatibility + protected virtual void Dispose(bool disposing) { - // Only operate if we haven't already disposed - if (IsDisposed || !disposing) return; + lock (_locko) + { + if (_disposed) return; + _disposed = true; + } - using (new WriteLock(_disposalLocker)) - { - // Check again now we're inside the lock - if (IsDisposed) return; + DisposeUnmanagedResources(); - // Call to actually release resources. This method is only - // kept separate so that the entire disposal logic can be used as a VS snippet - DisposeResources(); - - // Indicate that the instance has been disposed. - _disposed = true; - } + if (disposing) + DisposeResources(); } - /// - /// Handles the disposal of resources. Derived from abstract class which handles common required locking logic. - /// protected abstract void DisposeResources(); protected virtual void DisposeUnmanagedResources() - { - - } + { } } } \ No newline at end of file diff --git a/src/Umbraco.Core/EnumerableExtensions.cs b/src/Umbraco.Core/EnumerableExtensions.cs index 79b48af3ab..58d4d453b7 100644 --- a/src/Umbraco.Core/EnumerableExtensions.cs +++ b/src/Umbraco.Core/EnumerableExtensions.cs @@ -101,6 +101,7 @@ namespace Umbraco.Core /// The select child. /// Item type /// list of TItem + [Obsolete("Do not use, use SelectRecursive instead which has far less potential of re-iterating an iterator which may cause significantly more SQL queries")] public static IEnumerable FlattenList(this IEnumerable e, Func> f) { return e.SelectMany(c => f(c).FlattenList(f)).Concat(e); diff --git a/src/Umbraco.Core/Events/CancellableEventArgs.cs b/src/Umbraco.Core/Events/CancellableEventArgs.cs index 80e69ae353..506ba1c22e 100644 --- a/src/Umbraco.Core/Events/CancellableEventArgs.cs +++ b/src/Umbraco.Core/Events/CancellableEventArgs.cs @@ -3,7 +3,7 @@ using System.Security.Permissions; namespace Umbraco.Core.Events { - /// + /// /// Event args for that can support cancellation /// [HostProtection(SecurityAction.LinkDemand, SharedState = true)] @@ -11,15 +11,30 @@ namespace Umbraco.Core.Events { private bool _cancel; - public CancellableEventArgs(bool canCancel) + public CancellableEventArgs(bool canCancel, EventMessages eventMessages) + { + if (eventMessages == null) throw new ArgumentNullException("eventMessages"); + CanCancel = canCancel; + Messages = eventMessages; + } + + public CancellableEventArgs(bool canCancel) { CanCancel = canCancel; - } + //create a standalone messages + Messages = new EventMessages(); + } - public CancellableEventArgs() + public CancellableEventArgs(EventMessages eventMessages) + : this(true, eventMessages) + { + } + + public CancellableEventArgs() : this(true) { } + /// /// Flag to determine if this instance will support being cancellable /// @@ -32,7 +47,7 @@ namespace Umbraco.Core.Events { get { - if (!CanCancel) + if (CanCancel == false) { throw new InvalidOperationException("This event argument class does not support cancelling."); } @@ -40,12 +55,28 @@ namespace Umbraco.Core.Events } set { - if (!CanCancel) + if (CanCancel == false) { throw new InvalidOperationException("This event argument class does not support cancelling."); } _cancel = value; } } - } + + /// + /// if this instance supports cancellation, this will set Cancel to true with an affiliated cancellation message + /// + /// + public void CancelOperation(EventMessage cancelationMessage) + { + Cancel = true; + cancelationMessage.IsDefaultEventMessage = true; + Messages.Add(cancelationMessage); + } + + /// + /// Returns the EventMessages object which is used to add messages to the message collection for this event + /// + public EventMessages Messages { get; private set; } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Events/CancellableObjectEventArgs.cs b/src/Umbraco.Core/Events/CancellableObjectEventArgs.cs index a51da5652e..726d32d7b0 100644 --- a/src/Umbraco.Core/Events/CancellableObjectEventArgs.cs +++ b/src/Umbraco.Core/Events/CancellableObjectEventArgs.cs @@ -12,7 +12,18 @@ namespace Umbraco.Core.Events public class CancellableObjectEventArgs : CancellableEventArgs { - public CancellableObjectEventArgs(T eventObject, bool canCancel) + public CancellableObjectEventArgs(T eventObject, bool canCancel, EventMessages eventMessages) + : base(canCancel, eventMessages) + { + EventObject = eventObject; + } + + public CancellableObjectEventArgs(T eventObject, EventMessages eventMessages) + : this(eventObject, true, eventMessages) + { + } + + public CancellableObjectEventArgs(T eventObject, bool canCancel) : base(canCancel) { EventObject = eventObject; diff --git a/src/Umbraco.Core/Events/DeleteEventArgs.cs b/src/Umbraco.Core/Events/DeleteEventArgs.cs index 4edee846cf..1025066bcc 100644 --- a/src/Umbraco.Core/Events/DeleteEventArgs.cs +++ b/src/Umbraco.Core/Events/DeleteEventArgs.cs @@ -4,12 +4,56 @@ namespace Umbraco.Core.Events { public class DeleteEventArgs : CancellableObjectEventArgs> { - /// - /// Constructor accepting multiple entities that are used in the delete operation - /// - /// - /// - public DeleteEventArgs(IEnumerable eventObject, bool canCancel) : base(eventObject, canCancel) + /// + /// Constructor accepting multiple entities that are used in the delete operation + /// + /// + /// + /// + public DeleteEventArgs(IEnumerable eventObject, bool canCancel, EventMessages eventMessages) : base(eventObject, canCancel, eventMessages) + { + MediaFilesToDelete = new List(); + } + + /// + /// Constructor accepting multiple entities that are used in the delete operation + /// + /// + /// + public DeleteEventArgs(IEnumerable eventObject, EventMessages eventMessages) : base(eventObject, eventMessages) + { + MediaFilesToDelete = new List(); + } + + /// + /// Constructor accepting a single entity instance + /// + /// + /// + public DeleteEventArgs(TEntity eventObject, EventMessages eventMessages) + : base(new List { eventObject }, eventMessages) + { + MediaFilesToDelete = new List(); + } + + /// + /// Constructor accepting a single entity instance + /// + /// + /// + /// + public DeleteEventArgs(TEntity eventObject, bool canCancel, EventMessages eventMessages) + : base(new List { eventObject }, canCancel, eventMessages) + { + MediaFilesToDelete = new List(); + } + + /// + /// Constructor accepting multiple entities that are used in the delete operation + /// + /// + /// + public DeleteEventArgs(IEnumerable eventObject, bool canCancel) : base(eventObject, canCancel) { MediaFilesToDelete = new List(); } @@ -60,7 +104,13 @@ namespace Umbraco.Core.Events public class DeleteEventArgs : CancellableEventArgs { - public DeleteEventArgs(int id, bool canCancel) + public DeleteEventArgs(int id, bool canCancel, EventMessages eventMessages) + : base(canCancel, eventMessages) + { + Id = id; + } + + public DeleteEventArgs(int id, bool canCancel) : base(canCancel) { Id = id; diff --git a/src/Umbraco.Core/Events/EventExtensions.cs b/src/Umbraco.Core/Events/EventExtensions.cs index 99cf145fe0..8c645aead6 100644 --- a/src/Umbraco.Core/Events/EventExtensions.cs +++ b/src/Umbraco.Core/Events/EventExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; namespace Umbraco.Core.Events { @@ -7,16 +8,16 @@ namespace Umbraco.Core.Events /// public static class EventExtensions { - /// - /// Raises the event and returns a boolean value indicating if the event was cancelled - /// - /// - /// - /// - /// - /// - /// - public static bool IsRaisedEventCancelled( + /// + /// Raises the event and returns a boolean value indicating if the event was cancelled + /// + /// + /// + /// + /// + /// + /// + public static bool IsRaisedEventCancelled( this TypedEventHandler eventHandler, TArgs args, TSender sender) @@ -27,16 +28,16 @@ namespace Umbraco.Core.Events return args.Cancel; } - - /// - /// Raises the event - /// - /// - /// - /// - /// - /// - public static void RaiseEvent( + + /// + /// Raises the event + /// + /// + /// + /// + /// + /// + public static void RaiseEvent( this TypedEventHandler eventHandler, TArgs args, TSender sender) diff --git a/src/Umbraco.Core/Events/EventMessage.cs b/src/Umbraco.Core/Events/EventMessage.cs new file mode 100644 index 0000000000..473f955d45 --- /dev/null +++ b/src/Umbraco.Core/Events/EventMessage.cs @@ -0,0 +1,27 @@ +namespace Umbraco.Core.Events +{ + /// + /// An event message + /// + public sealed class EventMessage + { + /// + /// Initializes a new instance of the class. + /// + public EventMessage(string category, string message, EventMessageType messageType = EventMessageType.Default) + { + Category = category; + Message = message; + MessageType = messageType; + } + + public string Category { get; private set; } + public string Message { get; private set; } + public EventMessageType MessageType { get; private set; } + + /// + /// This is used to track if this message should be used as a default message so that Umbraco doesn't also append it's own default messages + /// + internal bool IsDefaultEventMessage { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Events/EventMessages.cs b/src/Umbraco.Core/Events/EventMessages.cs new file mode 100644 index 0000000000..2900f3d471 --- /dev/null +++ b/src/Umbraco.Core/Events/EventMessages.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; + +namespace Umbraco.Core.Events +{ + /// + /// Event messages collection + /// + public sealed class EventMessages : DisposableObject + { + private readonly List _msgs = new List(); + + public void Add(EventMessage msg) + { + _msgs.Add(msg); + } + + public int Count + { + get { return _msgs.Count; } + } + + public IEnumerable GetAll() + { + return _msgs; + } + + protected override void DisposeResources() + { + _msgs.Clear(); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Events/IEventMessagesFactory.cs b/src/Umbraco.Core/Events/IEventMessagesFactory.cs new file mode 100644 index 0000000000..cb2391186d --- /dev/null +++ b/src/Umbraco.Core/Events/IEventMessagesFactory.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace Umbraco.Core.Events +{ + /// + /// Event messages factory + /// + public interface IEventMessagesFactory + { + EventMessages Get(); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Events/MessageType.cs b/src/Umbraco.Core/Events/MessageType.cs new file mode 100644 index 0000000000..6299a0e7d7 --- /dev/null +++ b/src/Umbraco.Core/Events/MessageType.cs @@ -0,0 +1,14 @@ +namespace Umbraco.Core.Events +{ + /// + /// The type of event message + /// + public enum EventMessageType + { + Default = 0, + Info = 1, + Error = 2, + Success = 3, + Warning = 4 + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Events/MigrationEventArgs.cs b/src/Umbraco.Core/Events/MigrationEventArgs.cs index c6da480999..7eed61484b 100644 --- a/src/Umbraco.Core/Events/MigrationEventArgs.cs +++ b/src/Umbraco.Core/Events/MigrationEventArgs.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.ComponentModel; +using Semver; using Umbraco.Core.Persistence.Migrations; namespace Umbraco.Core.Events @@ -13,11 +15,20 @@ namespace Umbraco.Core.Events /// /// /// + public MigrationEventArgs(IList eventObject, SemVersion configuredVersion, SemVersion targetVersion, bool canCancel) + : base(eventObject, canCancel) + { + ConfiguredSemVersion = configuredVersion; + TargetSemVersion = targetVersion; + } + + [Obsolete("Use constructor accepting UmbracoVersion instances instead")] + [EditorBrowsable(EditorBrowsableState.Never)] public MigrationEventArgs(IList eventObject, Version configuredVersion, Version targetVersion, bool canCancel) : base(eventObject, canCancel) { - ConfiguredVersion = configuredVersion; - TargetVersion = targetVersion; + ConfiguredSemVersion = new SemVersion(configuredVersion); + TargetSemVersion = new SemVersion(targetVersion); } /// @@ -28,12 +39,12 @@ namespace Umbraco.Core.Events /// /// /// - internal MigrationEventArgs(IList eventObject, MigrationContext migrationContext, Version configuredVersion, Version targetVersion, bool canCancel) + internal MigrationEventArgs(IList eventObject, MigrationContext migrationContext, SemVersion configuredVersion, SemVersion targetVersion, bool canCancel) : base(eventObject, canCancel) { MigrationContext = migrationContext; - ConfiguredVersion = configuredVersion; - TargetVersion = targetVersion; + ConfiguredSemVersion = configuredVersion; + TargetSemVersion = targetVersion; } /// @@ -42,11 +53,20 @@ namespace Umbraco.Core.Events /// /// /// + public MigrationEventArgs(IList eventObject, SemVersion configuredVersion, SemVersion targetVersion) + : base(eventObject) + { + ConfiguredSemVersion = configuredVersion; + TargetSemVersion = targetVersion; + } + + [Obsolete("Use constructor accepting UmbracoVersion instances instead")] + [EditorBrowsable(EditorBrowsableState.Never)] public MigrationEventArgs(IList eventObject, Version configuredVersion, Version targetVersion) : base(eventObject) { - ConfiguredVersion = configuredVersion; - TargetVersion = targetVersion; + ConfiguredSemVersion = new SemVersion(configuredVersion); + TargetSemVersion = new SemVersion(targetVersion); } /// @@ -57,9 +77,23 @@ namespace Umbraco.Core.Events get { return EventObject; } } - public Version ConfiguredVersion { get; private set; } + [Obsolete("Use ConfiguredSemVersion instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + public Version ConfiguredVersion + { + get { return ConfiguredSemVersion.GetVersion(); } + } - public Version TargetVersion { get; private set; } + [Obsolete("Use TargetUmbracoVersion instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + public Version TargetVersion + { + get { return TargetSemVersion.GetVersion(); } + } + + public SemVersion ConfiguredSemVersion { get; private set; } + + public SemVersion TargetSemVersion { get; private set; } internal MigrationContext MigrationContext { get; private set; } } diff --git a/src/Umbraco.Core/Events/MoveEventArgs.cs b/src/Umbraco.Core/Events/MoveEventArgs.cs index 107629ff19..0f0a5183a9 100644 --- a/src/Umbraco.Core/Events/MoveEventArgs.cs +++ b/src/Umbraco.Core/Events/MoveEventArgs.cs @@ -4,22 +4,51 @@ using System.Linq; namespace Umbraco.Core.Events { - public class MoveEventInfo - { - public MoveEventInfo(TEntity entity, string originalPath, int newParentId) - { - Entity = entity; - OriginalPath = originalPath; - NewParentId = newParentId; - } - - public TEntity Entity { get; set; } - public string OriginalPath { get; set; } - public int NewParentId { get; set; } - } - public class MoveEventArgs : CancellableObjectEventArgs { + /// + /// Constructor accepting a collection of MoveEventInfo objects + /// + /// + /// + /// + /// A colleciton of MoveEventInfo objects that exposes all entities that have been moved during a single move operation + /// + public MoveEventArgs(bool canCancel, EventMessages eventMessages, params MoveEventInfo[] moveInfo) + : base(default(TEntity), canCancel, eventMessages) + { + if (moveInfo.FirstOrDefault() == null) + { + throw new ArgumentException("moveInfo argument must contain at least one item"); + } + + MoveInfoCollection = moveInfo; + //assign the legacy props + EventObject = moveInfo.First().Entity; + ParentId = moveInfo.First().NewParentId; + } + + /// + /// Constructor accepting a collection of MoveEventInfo objects + /// + /// + /// + /// A colleciton of MoveEventInfo objects that exposes all entities that have been moved during a single move operation + /// + public MoveEventArgs(EventMessages eventMessages, params MoveEventInfo[] moveInfo) + : base(default(TEntity), eventMessages) + { + if (moveInfo.FirstOrDefault() == null) + { + throw new ArgumentException("moveInfo argument must contain at least one item"); + } + + MoveInfoCollection = moveInfo; + //assign the legacy props + EventObject = moveInfo.First().Entity; + ParentId = moveInfo.First().NewParentId; + } + /// /// Constructor accepting a collection of MoveEventInfo objects /// diff --git a/src/Umbraco.Core/Events/MoveEventInfo.cs b/src/Umbraco.Core/Events/MoveEventInfo.cs new file mode 100644 index 0000000000..a74db7f36e --- /dev/null +++ b/src/Umbraco.Core/Events/MoveEventInfo.cs @@ -0,0 +1,16 @@ +namespace Umbraco.Core.Events +{ + public class MoveEventInfo + { + public MoveEventInfo(TEntity entity, string originalPath, int newParentId) + { + Entity = entity; + OriginalPath = originalPath; + NewParentId = newParentId; + } + + public TEntity Entity { get; set; } + public string OriginalPath { get; set; } + public int NewParentId { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Events/NewEventArgs.cs b/src/Umbraco.Core/Events/NewEventArgs.cs index 8e6cd64db1..acfd64e60d 100644 --- a/src/Umbraco.Core/Events/NewEventArgs.cs +++ b/src/Umbraco.Core/Events/NewEventArgs.cs @@ -4,7 +4,39 @@ namespace Umbraco.Core.Events { public class NewEventArgs : CancellableObjectEventArgs { - public NewEventArgs(TEntity eventObject, bool canCancel, string @alias, int parentId) : base(eventObject, canCancel) + + + public NewEventArgs(TEntity eventObject, bool canCancel, string @alias, int parentId, EventMessages eventMessages) + : base(eventObject, canCancel, eventMessages) + { + Alias = alias; + ParentId = parentId; + } + + public NewEventArgs(TEntity eventObject, bool canCancel, string @alias, TEntity parent, EventMessages eventMessages) + : base(eventObject, canCancel, eventMessages) + { + Alias = alias; + Parent = parent; + } + + public NewEventArgs(TEntity eventObject, string @alias, int parentId, EventMessages eventMessages) + : base(eventObject, eventMessages) + { + Alias = alias; + ParentId = parentId; + } + + public NewEventArgs(TEntity eventObject, string @alias, TEntity parent, EventMessages eventMessages) + : base(eventObject, eventMessages) + { + Alias = alias; + Parent = parent; + } + + + + public NewEventArgs(TEntity eventObject, bool canCancel, string @alias, int parentId) : base(eventObject, canCancel) { Alias = alias; ParentId = parentId; diff --git a/src/Umbraco.Core/Events/PublishEventArgs.cs b/src/Umbraco.Core/Events/PublishEventArgs.cs index 2355e250ef..a791781617 100644 --- a/src/Umbraco.Core/Events/PublishEventArgs.cs +++ b/src/Umbraco.Core/Events/PublishEventArgs.cs @@ -4,6 +4,52 @@ namespace Umbraco.Core.Events { public class PublishEventArgs : CancellableObjectEventArgs> { + /// + /// Constructor accepting multiple entities that are used in the publish operation + /// + /// + /// + /// + /// + public PublishEventArgs(IEnumerable eventObject, bool canCancel, bool isAllPublished, EventMessages eventMessages) + : base(eventObject, canCancel, eventMessages) + { + IsAllRepublished = isAllPublished; + } + + /// + /// Constructor accepting multiple entities that are used in the publish operation + /// + /// + /// + public PublishEventArgs(IEnumerable eventObject, EventMessages eventMessages) + : base(eventObject, eventMessages) + { + } + + /// + /// Constructor accepting a single entity instance + /// + /// + /// + public PublishEventArgs(TEntity eventObject, EventMessages eventMessages) + : base(new List { eventObject }, eventMessages) + { + } + + /// + /// Constructor accepting a single entity instance + /// + /// + /// + /// + /// + public PublishEventArgs(TEntity eventObject, bool canCancel, bool isAllPublished, EventMessages eventMessages) + : base(new List { eventObject }, canCancel, eventMessages) + { + IsAllRepublished = isAllPublished; + } + /// /// Constructor accepting multiple entities that are used in the publish operation /// diff --git a/src/Umbraco.Core/Events/SaveEventArgs.cs b/src/Umbraco.Core/Events/SaveEventArgs.cs index d3ac8b22e5..b84e28285e 100644 --- a/src/Umbraco.Core/Events/SaveEventArgs.cs +++ b/src/Umbraco.Core/Events/SaveEventArgs.cs @@ -4,12 +4,55 @@ namespace Umbraco.Core.Events { public class SaveEventArgs : CancellableObjectEventArgs> { - /// - /// Constructor accepting multiple entities that are used in the saving operation - /// - /// - /// - public SaveEventArgs(IEnumerable eventObject, bool canCancel) + /// + /// Constructor accepting multiple entities that are used in the saving operation + /// + /// + /// + /// + public SaveEventArgs(IEnumerable eventObject, bool canCancel, EventMessages eventMessages) + : base(eventObject, canCancel, eventMessages) + { + } + + /// + /// Constructor accepting multiple entities that are used in the saving operation + /// + /// + /// + public SaveEventArgs(IEnumerable eventObject, EventMessages eventMessages) + : base(eventObject, eventMessages) + { + } + + /// + /// Constructor accepting a single entity instance + /// + /// + /// + public SaveEventArgs(TEntity eventObject, EventMessages eventMessages) + : base(new List { eventObject }, eventMessages) + { + } + + /// + /// Constructor accepting a single entity instance + /// + /// + /// + /// + public SaveEventArgs(TEntity eventObject, bool canCancel, EventMessages eventMessages) + : base(new List { eventObject }, canCancel, eventMessages) + { + } + + + /// + /// Constructor accepting multiple entities that are used in the saving operation + /// + /// + /// + public SaveEventArgs(IEnumerable eventObject, bool canCancel) : base(eventObject, canCancel) { } diff --git a/src/Umbraco.Core/Events/TransientMessagesFactory.cs b/src/Umbraco.Core/Events/TransientMessagesFactory.cs new file mode 100644 index 0000000000..5cd291a37f --- /dev/null +++ b/src/Umbraco.Core/Events/TransientMessagesFactory.cs @@ -0,0 +1,13 @@ +namespace Umbraco.Core.Events +{ + /// + /// A simple/default transient messages factory + /// + internal class TransientMessagesFactory : IEventMessagesFactory + { + public EventMessages Get() + { + return new EventMessages(); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Exceptions/DataOperationException.cs b/src/Umbraco.Core/Exceptions/DataOperationException.cs new file mode 100644 index 0000000000..9a66e6a5be --- /dev/null +++ b/src/Umbraco.Core/Exceptions/DataOperationException.cs @@ -0,0 +1,21 @@ +using System; + +namespace Umbraco.Core.Exceptions +{ + internal class DataOperationException : Exception + { + public T Operation { get; private set; } + + public DataOperationException(T operation, string message) + :base(message) + { + Operation = operation; + } + + public DataOperationException(T operation) + : base("Data operation exception: " + operation) + { + Operation = operation; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Exceptions/InvalidCompositionException.cs b/src/Umbraco.Core/Exceptions/InvalidCompositionException.cs index fcf7a44677..bb9becf058 100644 --- a/src/Umbraco.Core/Exceptions/InvalidCompositionException.cs +++ b/src/Umbraco.Core/Exceptions/InvalidCompositionException.cs @@ -3,22 +3,41 @@ namespace Umbraco.Core.Exceptions { public class InvalidCompositionException : Exception - { - public string ContentTypeAlias { get; set; } + { + public InvalidCompositionException(string contentTypeAlias, string addedCompositionAlias, string[] propertyTypeAliass) + { + ContentTypeAlias = contentTypeAlias; + AddedCompositionAlias = addedCompositionAlias; + PropertyTypeAliases = propertyTypeAliass; + } - public string AddedCompositionAlias { get; set; } + public InvalidCompositionException(string contentTypeAlias, string[] propertyTypeAliass) + { + ContentTypeAlias = contentTypeAlias; + PropertyTypeAliases = propertyTypeAliass; + } - public string PropertyTypeAlias { get; set; } + public string ContentTypeAlias { get; private set; } + + public string AddedCompositionAlias { get; private set; } + + public string[] PropertyTypeAliases { get; private set; } public override string Message { get { - return string.Format( - "InvalidCompositionException - ContentType with alias '{0}' was added as a Compsition to ContentType with alias '{1}', " + - "but there was a conflict on the PropertyType alias '{2}'. " + + return AddedCompositionAlias.IsNullOrWhiteSpace() + ? string.Format( + "ContentType with alias '{0}' has an invalid composition " + + "and there was a conflict on the following PropertyTypes: '{1}'. " + "PropertyTypes must have a unique alias across all Compositions in order to compose a valid ContentType Composition.", - AddedCompositionAlias, ContentTypeAlias, PropertyTypeAlias); + ContentTypeAlias, string.Join(", ", PropertyTypeAliases)) + : string.Format( + "ContentType with alias '{0}' was added as a Composition to ContentType with alias '{1}', " + + "but there was a conflict on the following PropertyTypes: '{2}'. " + + "PropertyTypes must have a unique alias across all Compositions in order to compose a valid ContentType Composition.", + AddedCompositionAlias, ContentTypeAlias, string.Join(", ", PropertyTypeAliases)); } } } diff --git a/src/Umbraco.Core/Exceptions/UmbracoStartupFailedException.cs b/src/Umbraco.Core/Exceptions/UmbracoStartupFailedException.cs new file mode 100644 index 0000000000..d27d38de9a --- /dev/null +++ b/src/Umbraco.Core/Exceptions/UmbracoStartupFailedException.cs @@ -0,0 +1,18 @@ +using System; + +namespace Umbraco.Core.Exceptions +{ + /// + /// An exception that is thrown if the umbraco application cannnot boot + /// + public class UmbracoStartupFailedException : Exception + { + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// The message that describes the error. + public UmbracoStartupFailedException(string message) : base(message) + { + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web.UI/xslt/Web.config b/src/Umbraco.Core/FileResources/BlockingWeb.config similarity index 96% rename from src/Umbraco.Web.UI/xslt/Web.config rename to src/Umbraco.Core/FileResources/BlockingWeb.config index fd6e3a816a..80182af9a1 100644 --- a/src/Umbraco.Web.UI/xslt/Web.config +++ b/src/Umbraco.Core/FileResources/BlockingWeb.config @@ -1,18 +1,18 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + diff --git a/src/Umbraco.Core/FileResources/Files.Designer.cs b/src/Umbraco.Core/FileResources/Files.Designer.cs new file mode 100644 index 0000000000..456dae221f --- /dev/null +++ b/src/Umbraco.Core/FileResources/Files.Designer.cs @@ -0,0 +1,86 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Umbraco.Core.FileResources { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Files { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Files() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Umbraco.Core.FileResources.Files", typeof(Files).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to <?xml version="1.0"?> + /// + ///<!-- Blocks public downloading of anything in this folder and sub folders --> + /// + ///<configuration> + /// <system.web> + /// <httpHandlers> + /// <add path="*" verb="*" type="System.Web.HttpNotFoundHandler"/> + /// </httpHandlers> + /// </system.web> + /// <system.webServer> + /// <validation validateIntegratedModeConfiguration="false" /> + /// <handlers> + /// <remove name="BlockViewHandler"/> + /// <add name="BlockViewHandler" path="*" verb="*" preCondition="integratedMode" type="System.Web.H [rest of string was truncated]";. + /// + internal static string BlockingWebConfig { + get { + return ResourceManager.GetString("BlockingWebConfig", resourceCulture); + } + } + } +} diff --git a/src/Umbraco.Core/FileResources/Files.resx b/src/Umbraco.Core/FileResources/Files.resx new file mode 100644 index 0000000000..823aacefb7 --- /dev/null +++ b/src/Umbraco.Core/FileResources/Files.resx @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + blockingweb.config;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;Windows-1252 + + \ No newline at end of file diff --git a/src/Umbraco.Core/IO/IOHelper.cs b/src/Umbraco.Core/IO/IOHelper.cs index dc5e278970..286acf0285 100644 --- a/src/Umbraco.Core/IO/IOHelper.cs +++ b/src/Umbraco.Core/IO/IOHelper.cs @@ -6,6 +6,7 @@ using System.IO; using System.Configuration; using System.Web; using System.Text.RegularExpressions; +using System.Web.Hosting; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; @@ -51,7 +52,23 @@ namespace Umbraco.Core.IO return VirtualPathUtility.ToAbsolute(virtualPath, SystemDirectories.Root); } - [Obsolete("Use Umbraco.Web.Templates.TemplateUtilities.ResolveUrlsFromTextString instead, this method on this class will be removed in future versions")] + public static Attempt TryResolveUrl(string virtualPath) + { + try + { + if (virtualPath.StartsWith("~")) + return Attempt.Succeed(virtualPath.Replace("~", SystemDirectories.Root).Replace("//", "/")); + if (Uri.IsWellFormedUriString(virtualPath, UriKind.Absolute)) + return Attempt.Succeed(virtualPath); + return Attempt.Succeed(VirtualPathUtility.ToAbsolute(virtualPath, SystemDirectories.Root)); + } + catch (Exception ex) + { + return Attempt.Fail(virtualPath, ex); + } + } + + [Obsolete("Use Umbraco.Web.Templates.TemplateUtilities.ResolveUrlsFromTextString instead, this method on this class will be removed in future versions")] internal static string ResolveUrlsFromTextString(string text) { if (UmbracoConfig.For.UmbracoSettings().Content.ResolveUrlsFromTextString) @@ -67,7 +84,7 @@ namespace Umbraco.Core.IO if (tag.Groups[1].Success) url = tag.Groups[1].Value; - if (string.IsNullOrEmpty(url) == false) + if (String.IsNullOrEmpty(url) == false) { string resolvedUrl = (url.Substring(0, 1) == "/") ? ResolveUrl(url.Substring(1)) : ResolveUrl(url); text = text.Replace(url, resolvedUrl); @@ -92,10 +109,10 @@ namespace Umbraco.Core.IO if (useHttpContext && HttpContext.Current != null) { //string retval; - if (string.IsNullOrEmpty(path) == false && (path.StartsWith("~") || path.StartsWith(SystemDirectories.Root))) - return System.Web.Hosting.HostingEnvironment.MapPath(path); + if (String.IsNullOrEmpty(path) == false && (path.StartsWith("~") || path.StartsWith(SystemDirectories.Root))) + return HostingEnvironment.MapPath(path); else - return System.Web.Hosting.HostingEnvironment.MapPath("~/" + path.TrimStart('/')); + return HostingEnvironment.MapPath("~/" + path.TrimStart('/')); } var root = GetRootDirectorySafe(); @@ -115,7 +132,7 @@ namespace Umbraco.Core.IO { string retval = ConfigurationManager.AppSettings[settingsKey]; - if (string.IsNullOrEmpty(retval)) + if (String.IsNullOrEmpty(retval)) retval = standardPath; return retval.TrimEnd('/'); @@ -135,12 +152,7 @@ namespace Umbraco.Core.IO /// A value indicating whether the filepath is valid. internal static bool VerifyEditPath(string filePath, string validDir) { - if (filePath.StartsWith(MapPath(SystemDirectories.Root)) == false) - filePath = MapPath(filePath); - if (validDir.StartsWith(MapPath(SystemDirectories.Root)) == false) - validDir = MapPath(validDir); - - return filePath.StartsWith(validDir); + return VerifyEditPath(filePath, new[] { validDir }); } /// @@ -165,15 +177,31 @@ namespace Umbraco.Core.IO /// A value indicating whether the filepath is valid. internal static bool VerifyEditPath(string filePath, IEnumerable validDirs) { + // this is called from ScriptRepository, PartialViewRepository, etc. + // filePath is the fullPath (rooted, filesystem path, can be trusted) + // validDirs are virtual paths (eg ~/Views) + // + // except that for templates, filePath actually is a virtual path + + //TODO + // what's below is dirty, there are too many ways to get the root dir, etc. + // not going to fix everything today + + var mappedRoot = MapPath(SystemDirectories.Root); + if (filePath.StartsWith(mappedRoot) == false) + filePath = MapPath(filePath); + + // yes we can (see above) + //// don't trust what we get, it may contain relative segments + //filePath = Path.GetFullPath(filePath); + foreach (var dir in validDirs) { var validDir = dir; - if (filePath.StartsWith(MapPath(SystemDirectories.Root)) == false) - filePath = MapPath(filePath); - if (validDir.StartsWith(MapPath(SystemDirectories.Root)) == false) + if (validDir.StartsWith(mappedRoot) == false) validDir = MapPath(validDir); - if (filePath.StartsWith(validDir)) + if (PathStartsWith(filePath, validDir, Path.DirectorySeparatorChar)) return true; } @@ -202,11 +230,8 @@ namespace Umbraco.Core.IO /// A value indicating whether the filepath is valid. internal static bool VerifyFileExtension(string filePath, List validFileExtensions) { - if (filePath.StartsWith(MapPath(SystemDirectories.Root)) == false) - filePath = MapPath(filePath); - var f = new FileInfo(filePath); - - return validFileExtensions.Contains(f.Extension.Substring(1)); + var ext = Path.GetExtension(filePath); + return ext != null && validFileExtensions.Contains(ext.TrimStart('.')); } /// @@ -223,6 +248,16 @@ namespace Umbraco.Core.IO return true; } + public static bool PathStartsWith(string path, string root, char separator) + { + // either it is identical to root, + // or it is root + separator + anything + + if (path.StartsWith(root, StringComparison.OrdinalIgnoreCase) == false) return false; + if (path.Length == root.Length) return true; + if (path.Length < root.Length) return false; + return path[root.Length] == separator; + } /// /// Returns the path to the root of the application, by getting the path to where the assembly where this @@ -232,7 +267,7 @@ namespace Umbraco.Core.IO /// internal static string GetRootDirectorySafe() { - if (string.IsNullOrEmpty(_rootDir) == false) + if (String.IsNullOrEmpty(_rootDir) == false) { return _rootDir; } @@ -241,7 +276,7 @@ namespace Umbraco.Core.IO var uri = new Uri(codeBase); var path = uri.LocalPath; var baseDirectory = Path.GetDirectoryName(path); - if (string.IsNullOrEmpty(baseDirectory)) + if (String.IsNullOrEmpty(baseDirectory)) throw new Exception("No root directory could be resolved. Please ensure that your Umbraco solution is correctly configured."); _rootDir = baseDirectory.Contains("bin") @@ -253,8 +288,8 @@ namespace Umbraco.Core.IO internal static string GetRootDirectoryBinFolder() { - string binFolder = string.Empty; - if (string.IsNullOrEmpty(_rootDir)) + string binFolder = String.Empty; + if (String.IsNullOrEmpty(_rootDir)) { binFolder = Assembly.GetExecutingAssembly().GetAssemblyFile().Directory.FullName; return binFolder; @@ -298,5 +333,25 @@ namespace Umbraco.Core.IO // use string extensions return filePath.ToSafeFileName(); } + + public static void EnsurePathExists(string path) + { + var absolutePath = IOHelper.MapPath(path); + if (Directory.Exists(absolutePath) == false) + Directory.CreateDirectory(absolutePath); + } + + public static void EnsureFileExists(string path, string contents) + { + var absolutePath = IOHelper.MapPath(path); + if (File.Exists(absolutePath) == false) + { + using (var writer = File.CreateText(absolutePath)) + { + writer.Write(contents); + } + } + + } } } diff --git a/src/Umbraco.Core/IO/PhysicalFileSystem.cs b/src/Umbraco.Core/IO/PhysicalFileSystem.cs index 13df315960..47daff932d 100644 --- a/src/Umbraco.Core/IO/PhysicalFileSystem.cs +++ b/src/Umbraco.Core/IO/PhysicalFileSystem.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; -using System.Globalization; -using System.IO; +using System.IO; using System.Linq; using Umbraco.Core.Logging; @@ -9,7 +8,12 @@ namespace Umbraco.Core.IO { public class PhysicalFileSystem : IFileSystem { - internal string RootPath { get; private set; } + // the rooted, filesystem path, using directory separator chars, NOT ending with a separator + // eg "c:" or "c:\path\to\site" or "\\server\path" + private readonly string _rootPath; + + // the ??? url, using url separator chars, NOT ending with a separator + // eg "" (?) or "/Scripts" or ??? private readonly string _rootUrl; public PhysicalFileSystem(string virtualRoot) @@ -18,8 +22,13 @@ namespace Umbraco.Core.IO if (virtualRoot.StartsWith("~/") == false) throw new ArgumentException("The virtualRoot argument must be a virtual path and start with '~/'"); - RootPath = IOHelper.MapPath(virtualRoot); + _rootPath = IOHelper.MapPath(virtualRoot); + _rootPath = EnsureDirectorySeparatorChar(_rootPath); + _rootPath = _rootPath.TrimEnd(Path.DirectorySeparatorChar); + _rootUrl = IOHelper.ResolveUrl(virtualRoot); + _rootUrl = EnsureUrlSeparatorChar(_rootUrl); + _rootUrl = _rootUrl.TrimEnd('/'); } public PhysicalFileSystem(string rootPath, string rootUrl) @@ -33,18 +42,31 @@ namespace Umbraco.Core.IO if (rootPath.StartsWith("~/")) throw new ArgumentException("The rootPath argument cannot be a virtual path and cannot start with '~/'"); - RootPath = rootPath; - _rootUrl = rootUrl; + // rootPath should be... rooted, as in, it's a root path! + // but the test suite App.config cannot really "root" anything so we'll have to do it here + + //var localRoot = AppDomain.CurrentDomain.BaseDirectory; + var localRoot = IOHelper.GetRootDirectorySafe(); + if (Path.IsPathRooted(rootPath) == false) + { + rootPath = Path.Combine(localRoot, rootPath); + } + + rootPath = EnsureDirectorySeparatorChar(rootPath); + rootUrl = EnsureUrlSeparatorChar(rootUrl); + + _rootPath = rootPath.TrimEnd(Path.DirectorySeparatorChar); + _rootUrl = rootUrl.TrimEnd('/'); } public IEnumerable GetDirectories(string path) { - path = EnsureTrailingSeparator(GetFullPath(path)); + var fullPath = GetFullPath(path); try { - if (Directory.Exists(path)) - return Directory.EnumerateDirectories(path).Select(GetRelativePath); + if (Directory.Exists(fullPath)) + return Directory.EnumerateDirectories(fullPath).Select(GetRelativePath); } catch (UnauthorizedAccessException ex) { @@ -65,12 +87,13 @@ namespace Umbraco.Core.IO public void DeleteDirectory(string path, bool recursive) { - if (DirectoryExists(path) == false) + var fullPath = GetFullPath(path); + if (Directory.Exists(fullPath) == false) return; try { - Directory.Delete(GetFullPath(path), recursive); + Directory.Delete(fullPath, recursive); } catch (DirectoryNotFoundException ex) { @@ -80,7 +103,8 @@ namespace Umbraco.Core.IO public bool DirectoryExists(string path) { - return Directory.Exists(GetFullPath(path)); + var fullPath = GetFullPath(path); + return Directory.Exists(fullPath); } public void AddFile(string path, Stream stream) @@ -90,17 +114,17 @@ namespace Umbraco.Core.IO public void AddFile(string path, Stream stream, bool overrideIfExists) { - var fsRelativePath = GetRelativePath(path); + var fullPath = GetFullPath(path); + var exists = File.Exists(fullPath); + if (exists && overrideIfExists == false) + throw new InvalidOperationException(string.Format("A file at path '{0}' already exists", path)); - var exists = FileExists(fsRelativePath); - if (exists && overrideIfExists == false) throw new InvalidOperationException(string.Format("A file at path '{0}' already exists", path)); - - EnsureDirectory(Path.GetDirectoryName(fsRelativePath)); + Directory.CreateDirectory(Path.GetDirectoryName(fullPath)); // ensure it exists if (stream.CanSeek) stream.Seek(0, 0); - using (var destination = (Stream)File.Create(GetFullPath(fsRelativePath))) + using (var destination = (Stream)File.Create(fullPath)) stream.CopyTo(destination); } @@ -111,9 +135,7 @@ namespace Umbraco.Core.IO public IEnumerable GetFiles(string path, string filter) { - var fsRelativePath = GetRelativePath(path); - - var fullPath = EnsureTrailingSeparator(GetFullPath(fsRelativePath)); + var fullPath = GetFullPath(path); try { @@ -140,12 +162,13 @@ namespace Umbraco.Core.IO public void DeleteFile(string path) { - if (!FileExists(path)) + var fullPath = GetFullPath(path); + if (File.Exists(fullPath) == false) return; try { - File.Delete(GetFullPath(path)); + File.Delete(fullPath); } catch (FileNotFoundException ex) { @@ -155,39 +178,86 @@ namespace Umbraco.Core.IO public bool FileExists(string path) { - return File.Exists(GetFullPath(path)); + var fullpath = GetFullPath(path); + return File.Exists(fullpath); } + // beware, many things depend on how the GetRelative/AbsolutePath methods work! + + /// + /// Gets the relative path. + /// + /// The full path or url. + /// The path, relative to this filesystem's root. + /// + /// The relative path is relative to this filesystem's root, not starting with any + /// directory separator. If input was recognized as a url (path), then output uses url (path) separator + /// chars. + /// public string GetRelativePath(string fullPathOrUrl) { - var relativePath = fullPathOrUrl - .TrimStart(_rootUrl) - .Replace('/', Path.DirectorySeparatorChar) - .TrimStart(RootPath) - .TrimStart(Path.DirectorySeparatorChar); + // test url + var path = fullPathOrUrl.Replace('\\', '/'); // ensure url separator char - return relativePath; + if (IOHelper.PathStartsWith(path, _rootUrl, '/')) // if it starts with the root url... + return path.Substring(_rootUrl.Length) // strip it + .TrimStart('/'); // it's relative + + // test path + path = EnsureDirectorySeparatorChar(fullPathOrUrl); + + if (IOHelper.PathStartsWith(path, _rootPath, Path.DirectorySeparatorChar)) // if it starts with the root path + return path.Substring(_rootPath.Length) // strip it + .TrimStart(Path.DirectorySeparatorChar); // it's relative + + // unchanged - including separators + return fullPathOrUrl; } + /// + /// Gets the full path. + /// + /// The full or relative path. + /// The full path. + /// + /// On the physical filesystem, the full path is the rooted (ie non-relative), safe (ie within this + /// filesystem's root) path. All separators are converted to Path.DirectorySeparatorChar. + /// public string GetFullPath(string path) { - //if the path starts with a '/' then it's most likely not a FS relative path which is required so convert it - if (path.StartsWith("/")) - { - path = GetRelativePath(path); - } + // normalize + var opath = path; + path = EnsureDirectorySeparatorChar(path); - return !path.StartsWith(RootPath) - ? Path.Combine(RootPath, path) - : path; + // not sure what we are doing here - so if input starts with a (back) slash, + // we assume it's not a FS relative path and we try to convert it... but it + // really makes little sense? + if (path.StartsWith(Path.DirectorySeparatorChar.ToString())) + path = GetRelativePath(path); + + // if already a full path, return + if (IOHelper.PathStartsWith(path, _rootPath, Path.DirectorySeparatorChar)) + return path; + + // else combine and sanitize, ie GetFullPath will take care of any relative + // segments in path, eg '../../foo.tmp' - it may throw a SecurityException + // if the combined path reaches illegal parts of the filesystem + var fpath = Path.Combine(_rootPath, path); + fpath = Path.GetFullPath(fpath); + + // at that point, path is within legal parts of the filesystem, ie we have + // permissions to reach that path, but it may nevertheless be outside of + // our root path, due to relative segments, so better check + if (IOHelper.PathStartsWith(fpath, _rootPath, Path.DirectorySeparatorChar)) + return fpath; + + throw new FileSecurityException("File '" + opath + "' is outside this filesystem's root."); } public string GetUrl(string path) { - return _rootUrl.TrimEnd("/") + "/" + path - .TrimStart(Path.DirectorySeparatorChar) - .Replace(Path.DirectorySeparatorChar, '/') - .TrimEnd("/"); + path = EnsureUrlSeparatorChar(path).Trim('/'); + return _rootUrl + "/" + path; } public DateTimeOffset GetLastModified(string path) @@ -214,9 +284,19 @@ namespace Umbraco.Core.IO protected string EnsureTrailingSeparator(string path) { - if (!path.EndsWith(Path.DirectorySeparatorChar.ToString(CultureInfo.InvariantCulture), StringComparison.Ordinal)) - path = path + Path.DirectorySeparatorChar; + return path.EnsureEndsWith(Path.DirectorySeparatorChar); + } + protected string EnsureDirectorySeparatorChar(string path) + { + path = path.Replace('/', Path.DirectorySeparatorChar); + path = path.Replace('\\', Path.DirectorySeparatorChar); + return path; + } + + protected string EnsureUrlSeparatorChar(string path) + { + path = path.Replace('\\', '/'); return path; } diff --git a/src/Umbraco.Core/IO/SystemDirectories.cs b/src/Umbraco.Core/IO/SystemDirectories.cs index 14997b7de7..38bc358e94 100644 --- a/src/Umbraco.Core/IO/SystemDirectories.cs +++ b/src/Umbraco.Core/IO/SystemDirectories.cs @@ -132,7 +132,7 @@ namespace Umbraco.Core.IO { get { - return IOHelper.ReturnPath("umbracoWebservicesPath", "~/umbraco/webservices"); + return IOHelper.ReturnPath("umbracoWebservicesPath", Umbraco.EnsureEndsWith("/") + "webservices"); } } diff --git a/src/Umbraco.Core/IO/UmbracoMediaFile.cs b/src/Umbraco.Core/IO/UmbracoMediaFile.cs index b8fe310f54..d708fcb804 100644 --- a/src/Umbraco.Core/IO/UmbracoMediaFile.cs +++ b/src/Umbraco.Core/IO/UmbracoMediaFile.cs @@ -199,8 +199,8 @@ namespace Umbraco.Core.IO using (var image = Image.FromStream(fs)) { var fileNameThumb = string.IsNullOrWhiteSpace(fileNameAddition) - ? string.Format("{0}_UMBRACOSYSTHUMBNAIL.jpg", Path.Substring(0, Path.LastIndexOf(".", StringComparison.Ordinal))) - : string.Format("{0}_{1}.jpg", Path.Substring(0, Path.LastIndexOf(".", StringComparison.Ordinal)), fileNameAddition); + ? string.Format("{0}_UMBRACOSYSTHUMBNAIL." + Extension, Path.Substring(0, Path.LastIndexOf(".", StringComparison.Ordinal))) + : string.Format("{0}_{1}." + Extension, Path.Substring(0, Path.LastIndexOf(".", StringComparison.Ordinal)), fileNameAddition); var thumbnail = maxWidthHeight == -1 ? ImageHelper.GenerateThumbnail(image, width, height, fileNameThumb, Extension, _fs) diff --git a/src/Umbraco.Core/Logging/AppDomainTokenFormatter.cs b/src/Umbraco.Core/Logging/AppDomainTokenConverter.cs similarity index 100% rename from src/Umbraco.Core/Logging/AppDomainTokenFormatter.cs rename to src/Umbraco.Core/Logging/AppDomainTokenConverter.cs diff --git a/src/Umbraco.Core/Logging/AsyncForwardingAppenderBase.cs b/src/Umbraco.Core/Logging/AsyncForwardingAppenderBase.cs new file mode 100644 index 0000000000..74a1de81f4 --- /dev/null +++ b/src/Umbraco.Core/Logging/AsyncForwardingAppenderBase.cs @@ -0,0 +1,105 @@ +using System; +using log4net.Appender; +using log4net.Core; +using log4net.Util; + +namespace Umbraco.Core.Logging +{ + /// + /// Based on https://github.com/cjbhaines/Log4Net.Async + /// + public abstract class AsyncForwardingAppenderBase : ForwardingAppender + { + #region Private Members + + private const FixFlags DefaultFixFlags = FixFlags.Partial; + private FixFlags _fixFlags = DefaultFixFlags; + private LoggingEventHelper _loggingEventHelper; + + #endregion Private Members + + #region Properties + + public FixFlags Fix + { + get { return _fixFlags; } + set { SetFixFlags(value); } + } + + /// + /// The logger name that will be used for logging internal errors. + /// + protected abstract string InternalLoggerName { get; } + + public abstract int? BufferSize { get; set; } + + #endregion Properties + + public override void ActivateOptions() + { + base.ActivateOptions(); + _loggingEventHelper = new LoggingEventHelper(InternalLoggerName, DefaultFixFlags); + InitializeAppenders(); + } + + #region Appender Management + + public override void AddAppender(IAppender newAppender) + { + base.AddAppender(newAppender); + SetAppenderFixFlags(newAppender); + } + + private void SetFixFlags(FixFlags newFixFlags) + { + if (newFixFlags != _fixFlags) + { + _loggingEventHelper.Fix = newFixFlags; + _fixFlags = newFixFlags; + InitializeAppenders(); + } + } + + private void InitializeAppenders() + { + foreach (var appender in Appenders) + { + SetAppenderFixFlags(appender); + } + } + + private void SetAppenderFixFlags(IAppender appender) + { + var bufferingAppender = appender as BufferingAppenderSkeleton; + if (bufferingAppender != null) + { + bufferingAppender.Fix = Fix; + } + } + + #endregion Appender Management + + #region Forwarding + + protected void ForwardInternalError(string message, Exception exception, Type thisType) + { + LogLog.Error(thisType, message, exception); + var loggingEvent = _loggingEventHelper.CreateLoggingEvent(Level.Error, message, exception); + ForwardLoggingEvent(loggingEvent, thisType); + } + + protected void ForwardLoggingEvent(LoggingEvent loggingEvent, Type thisType) + { + try + { + base.Append(loggingEvent); + } + catch (Exception exception) + { + LogLog.Error(thisType, "Unable to forward logging event", exception); + } + } + + #endregion Forwarding + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Logging/AsynchronousRollingFileAppender.cs b/src/Umbraco.Core/Logging/AsynchronousRollingFileAppender.cs index 56d04c8426..cb58ebbfaa 100644 --- a/src/Umbraco.Core/Logging/AsynchronousRollingFileAppender.cs +++ b/src/Umbraco.Core/Logging/AsynchronousRollingFileAppender.cs @@ -1,276 +1,276 @@ +using log4net.Core; +using log4net.Util; using System; +using System.Runtime.Remoting.Messaging; using System.Security.Principal; using System.Threading; using System.Threading.Tasks; using log4net.Appender; -using log4net.Core; -using log4net.Util; namespace Umbraco.Core.Logging { - /// - /// Based on code by Chris Haines http://cjbhaines.wordpress.com/2012/02/13/asynchronous-log4net-appenders/ + /// + /// Based on https://github.com/cjbhaines/Log4Net.Async + /// which is based on code by Chris Haines http://cjbhaines.wordpress.com/2012/02/13/asynchronous-log4net-appenders/ /// public class AsynchronousRollingFileAppender : RollingFileAppender { - private readonly ManualResetEvent _manualResetEvent; - private int _bufferOverflowCounter; - private bool _forceStop; - private bool _hasFinished; - private DateTime _lastLoggedBufferOverflow; - private bool _logBufferOverflow; - private RingBuffer _pendingAppends; - private int _queueSizeLimit = 1000; - private bool _shuttingDown; + private RingBuffer pendingAppends; + private readonly ManualResetEvent manualResetEvent; + private bool shuttingDown; + private bool hasFinished; + private bool forceStop; + private bool logBufferOverflow; + private int bufferOverflowCounter; + private DateTime lastLoggedBufferOverflow; + private int queueSizeLimit = 1000; + public int QueueSizeLimit + { + get + { + return queueSizeLimit; + } + set + { + queueSizeLimit = value; + } + } - public AsynchronousRollingFileAppender() - { - _manualResetEvent = new ManualResetEvent(false); - } + public AsynchronousRollingFileAppender() + { + manualResetEvent = new ManualResetEvent(false); + } - public int QueueSizeLimit - { - get { return _queueSizeLimit; } - set { _queueSizeLimit = value; } - } + public override void ActivateOptions() + { + base.ActivateOptions(); + pendingAppends = new RingBuffer(QueueSizeLimit); + pendingAppends.BufferOverflow += OnBufferOverflow; + StartAppendTask(); + } - public override void ActivateOptions() - { - base.ActivateOptions(); - _pendingAppends = new RingBuffer(QueueSizeLimit); - _pendingAppends.BufferOverflow += OnBufferOverflow; - StartAppendTask(); - } + protected override void Append(LoggingEvent[] loggingEvents) + { + Array.ForEach(loggingEvents, Append); + } - protected override void Append(LoggingEvent[] loggingEvents) - { - Array.ForEach(loggingEvents, Append); - } + protected override void Append(LoggingEvent loggingEvent) + { + if (FilterEvent(loggingEvent)) + { + pendingAppends.Enqueue(loggingEvent); + } + } - protected override void Append(LoggingEvent loggingEvent) - { - if (FilterEvent(loggingEvent)) - { - _pendingAppends.Enqueue(loggingEvent); - } - } + protected override void OnClose() + { + shuttingDown = true; + manualResetEvent.WaitOne(TimeSpan.FromSeconds(5)); - protected override void OnClose() - { - _shuttingDown = true; - _manualResetEvent.WaitOne(TimeSpan.FromSeconds(5)); + if (!hasFinished) + { + forceStop = true; + base.Append(new LoggingEvent(new LoggingEventData + { + Level = Level.Error, + Message = "Unable to clear out the AsyncRollingFileAppender buffer in the allotted time, forcing a shutdown", + TimeStamp = DateTime.UtcNow, + Identity = "", + ExceptionString = "", + UserName = WindowsIdentity.GetCurrent() != null ? WindowsIdentity.GetCurrent().Name : "", + Domain = AppDomain.CurrentDomain.FriendlyName, + ThreadName = Thread.CurrentThread.ManagedThreadId.ToString(), + LocationInfo = new LocationInfo(this.GetType().Name, "OnClose", "AsyncRollingFileAppender.cs", "75"), + LoggerName = this.GetType().FullName, + Properties = new PropertiesDictionary(), + }) + ); + } - if (!_hasFinished) - { - _forceStop = true; - var windowsIdentity = WindowsIdentity.GetCurrent(); + base.OnClose(); + } - var logEvent = new LoggingEvent(new LoggingEventData - { - Level = global::log4net.Core.Level.Error, - Message = - "Unable to clear out the AsynchronousRollingFileAppender buffer in the allotted time, forcing a shutdown", - TimeStamp = DateTime.UtcNow, - Identity = "", - ExceptionString = "", - UserName = windowsIdentity != null ? windowsIdentity.Name : "", - Domain = AppDomain.CurrentDomain.FriendlyName, - ThreadName = Thread.CurrentThread.ManagedThreadId.ToString(), - LocationInfo = - new LocationInfo(this.GetType().Name, "OnClose", "AsynchronousRollingFileAppender.cs", "59"), - LoggerName = this.GetType().FullName, - Properties = new PropertiesDictionary(), - }); + private void StartAppendTask() + { + if (!shuttingDown) + { + Task appendTask = new Task(AppendLoggingEvents, TaskCreationOptions.LongRunning); + appendTask.LogErrors(LogAppenderError).ContinueWith(x => StartAppendTask()).LogErrors(LogAppenderError); + appendTask.Start(); + } + } - if (this.DateTimeStrategy != null) - { - base.Append(logEvent); - } - } + private void LogAppenderError(string logMessage, Exception exception) + { + base.Append(new LoggingEvent(new LoggingEventData + { + Level = Level.Error, + Message = "Appender exception: " + logMessage, + TimeStamp = DateTime.UtcNow, + Identity = "", + ExceptionString = exception.ToString(), + UserName = WindowsIdentity.GetCurrent() != null ? WindowsIdentity.GetCurrent().Name : "", + Domain = AppDomain.CurrentDomain.FriendlyName, + ThreadName = Thread.CurrentThread.ManagedThreadId.ToString(), + LocationInfo = new LocationInfo(this.GetType().Name, "LogAppenderError", "AsyncRollingFileAppender.cs", "152"), + LoggerName = this.GetType().FullName, + Properties = new PropertiesDictionary(), + })); + } - base.OnClose(); - } + private void AppendLoggingEvents() + { + LoggingEvent loggingEventToAppend; + while (!shuttingDown) + { + if (logBufferOverflow) + { + LogBufferOverflowError(); + logBufferOverflow = false; + bufferOverflowCounter = 0; + lastLoggedBufferOverflow = DateTime.UtcNow; + } - private void StartAppendTask() - { - if (!_shuttingDown) - { - Task appendTask = new Task(AppendLoggingEvents, TaskCreationOptions.LongRunning); - appendTask.LogErrors(LogAppenderError).ContinueWith(x => StartAppendTask()).LogErrors(LogAppenderError); - appendTask.Start(); - } - } + while (!pendingAppends.TryDequeue(out loggingEventToAppend)) + { + Thread.Sleep(10); + if (shuttingDown) + { + break; + } + } + if (loggingEventToAppend == null) + { + continue; + } - private void LogAppenderError(string logMessage, Exception exception) - { - var windowsIdentity = WindowsIdentity.GetCurrent(); - base.Append(new LoggingEvent(new LoggingEventData - { - Level = Level.Error, - Message = "Appender exception: " + logMessage, - TimeStamp = DateTime.UtcNow, - Identity = "", - ExceptionString = exception.ToString(), - UserName = windowsIdentity != null ? windowsIdentity.Name : "", - Domain = AppDomain.CurrentDomain.FriendlyName, - ThreadName = Thread.CurrentThread.ManagedThreadId.ToString(), - LocationInfo = - new LocationInfo(this.GetType().Name, - "LogAppenderError", - "AsynchronousRollingFileAppender.cs", - "100"), - LoggerName = this.GetType().FullName, - Properties = new PropertiesDictionary(), - })); - } + try + { + base.Append(loggingEventToAppend); + } + catch + { + } + } - private void AppendLoggingEvents() - { - LoggingEvent loggingEventToAppend; - while (!_shuttingDown) - { - if (_logBufferOverflow) - { - LogBufferOverflowError(); - _logBufferOverflow = false; - _bufferOverflowCounter = 0; - _lastLoggedBufferOverflow = DateTime.UtcNow; - } + while (pendingAppends.TryDequeue(out loggingEventToAppend) && !forceStop) + { + try + { + base.Append(loggingEventToAppend); + } + catch + { + } + } + hasFinished = true; + manualResetEvent.Set(); + } - while (!_pendingAppends.TryDequeue(out loggingEventToAppend)) - { - Thread.Sleep(10); - if (_shuttingDown) - { - break; - } - } - if (loggingEventToAppend == null) - { - continue; - } + private void LogBufferOverflowError() + { + base.Append(new LoggingEvent(new LoggingEventData + { + Level = Level.Error, + Message = string.Format("Buffer overflow. {0} logging events have been lost in the last 30 seconds. [QueueSizeLimit: {1}]", bufferOverflowCounter, QueueSizeLimit), + TimeStamp = DateTime.UtcNow, + Identity = "", + ExceptionString = "", + UserName = WindowsIdentity.GetCurrent() != null ? WindowsIdentity.GetCurrent().Name : "", + Domain = AppDomain.CurrentDomain.FriendlyName, + ThreadName = Thread.CurrentThread.ManagedThreadId.ToString(), + LocationInfo = new LocationInfo(this.GetType().Name, "LogBufferOverflowError", "AsyncRollingFileAppender.cs", "152"), + LoggerName = this.GetType().FullName, + Properties = new PropertiesDictionary(), + })); + } - try - { - base.Append(loggingEventToAppend); - } - catch - { - } - } + private void OnBufferOverflow(object sender, EventArgs eventArgs) + { + bufferOverflowCounter++; + if (logBufferOverflow == false) + { + if (lastLoggedBufferOverflow < DateTime.UtcNow.AddSeconds(-30)) + { + logBufferOverflow = true; + } + } + } + } - while (_pendingAppends.TryDequeue(out loggingEventToAppend) && !_forceStop) - { - try - { - base.Append(loggingEventToAppend); - } - catch - { - } - } - _hasFinished = true; - _manualResetEvent.Set(); - } + internal interface IQueue + { + void Enqueue(T item); + bool TryDequeue(out T ret); + } - private void LogBufferOverflowError() - { - var windowsIdentity = WindowsIdentity.GetCurrent(); - base.Append(new LoggingEvent(new LoggingEventData - { - Level = Level.Error, - Message = - string.Format( - "Buffer overflow. {0} logging events have been lost in the last 30 seconds. [QueueSizeLimit: {1}]", - _bufferOverflowCounter, - QueueSizeLimit), - TimeStamp = DateTime.UtcNow, - Identity = "", - ExceptionString = "", - UserName = windowsIdentity != null ? windowsIdentity.Name : "", - Domain = AppDomain.CurrentDomain.FriendlyName, - ThreadName = Thread.CurrentThread.ManagedThreadId.ToString(), - LocationInfo = - new LocationInfo(this.GetType().Name, - "LogBufferOverflowError", - "AsynchronousRollingFileAppender.cs", - "172"), - LoggerName = this.GetType().FullName, - Properties = new PropertiesDictionary(), - })); - } + internal class RingBuffer : IQueue + { + private readonly object lockObject = new object(); + private readonly T[] buffer; + private readonly int size; + private int readIndex = 0; + private int writeIndex = 0; + private bool bufferFull = false; - private void OnBufferOverflow(object sender, EventArgs eventArgs) - { - _bufferOverflowCounter++; - if (_logBufferOverflow == false) - { - if (_lastLoggedBufferOverflow < DateTime.UtcNow.AddSeconds(-30)) - { - _logBufferOverflow = true; - } - } - } + public int Size { get { return size; } } - private class RingBuffer - { - private readonly object _lockObject = new object(); - private readonly T[] _buffer; - private readonly int _size; - private int _readIndex = 0; - private int _writeIndex = 0; - private bool _bufferFull = false; + public event Action BufferOverflow; - public event Action BufferOverflow; + public RingBuffer(int size) + { + this.size = size; + buffer = new T[size]; + } - public RingBuffer(int size) - { - this._size = size; - _buffer = new T[size]; - } + public void Enqueue(T item) + { + var bufferWasFull = false; + lock (lockObject) + { + buffer[writeIndex] = item; + writeIndex = (++writeIndex) % size; + if (bufferFull) + { + bufferWasFull = true; + readIndex = writeIndex; + } + else if (writeIndex == readIndex) + { + bufferFull = true; + } + } - public void Enqueue(T item) - { - lock (_lockObject) - { - _buffer[_writeIndex] = item; - _writeIndex = (++_writeIndex) % _size; - if (_bufferFull) - { - if (BufferOverflow != null) - { - BufferOverflow(this, EventArgs.Empty); - } - _readIndex = _writeIndex; - } - else if (_writeIndex == _readIndex) - { - _bufferFull = true; - } - } - } + if (bufferWasFull) + { + if (BufferOverflow != null) + { + BufferOverflow(this, EventArgs.Empty); + } + } + } - public bool TryDequeue(out T ret) - { - if (_readIndex == _writeIndex && !_bufferFull) - { - ret = default(T); - return false; - } - lock (_lockObject) - { - if (_readIndex == _writeIndex && !_bufferFull) - { - ret = default(T); - return false; - } + public bool TryDequeue(out T ret) + { + if (readIndex == writeIndex && !bufferFull) + { + ret = default(T); + return false; + } + lock (lockObject) + { + if (readIndex == writeIndex && !bufferFull) + { + ret = default(T); + return false; + } - ret = _buffer[_readIndex]; - _readIndex = (++_readIndex) % _size; - _bufferFull = false; - return true; - } - } - } - } + ret = buffer[readIndex]; + buffer[readIndex] = default(T); + readIndex = (++readIndex) % size; + bufferFull = false; + return true; + } + } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Logging/Logger.cs b/src/Umbraco.Core/Logging/Logger.cs index 58f696d82b..ae8bb60fcd 100644 --- a/src/Umbraco.Core/Logging/Logger.cs +++ b/src/Umbraco.Core/Logging/Logger.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.IO; using System.Linq; using System.Threading; @@ -12,15 +13,20 @@ namespace Umbraco.Core.Logging /// Used for logging /// public class Logger : ILogger - { + { + public Logger(FileInfo log4NetConfigFile) + :this() { XmlConfigurator.Configure(log4NetConfigFile); } private Logger() { - + //Add custom global properties to the log4net context that we can use in our logging output + + log4net.GlobalContext.Properties["processId"] = Process.GetCurrentProcess().Id; + log4net.GlobalContext.Properties["appDomainId"] = AppDomain.CurrentDomain.Id; } /// @@ -53,31 +59,19 @@ namespace Umbraco.Core.Logging return LogManager.GetLogger(getTypeFromInstance.GetType()); } - - /// - /// Useful if the logger itself is running on another thread - /// - /// - /// - private string PrefixThreadId(string generateMessageFormat) - { - return "[Thread " + Thread.CurrentThread.ManagedThreadId + "] " + generateMessageFormat; - } - + public void Error(Type callingType, string message, Exception exception) { var logger = LogManager.GetLogger(callingType); if (logger != null) - logger.Error(PrefixThreadId(message), exception); + logger.Error((message), exception); } - - public void Warn(Type callingType, string message, params Func[] formatItems) { var logger = LogManager.GetLogger(callingType); if (logger == null || logger.IsWarnEnabled == false) return; - logger.WarnFormat(PrefixThreadId(message), formatItems.Select(x => x.Invoke()).ToArray()); + logger.WarnFormat((message), formatItems.Select(x => x.Invoke()).ToArray()); } public void Warn(Type callingType, string message, bool showHttpTrace, params Func[] formatItems) @@ -92,7 +86,7 @@ namespace Umbraco.Core.Logging var logger = LogManager.GetLogger(callingType); if (logger == null || logger.IsWarnEnabled == false) return; - logger.WarnFormat(PrefixThreadId(message), formatItems.Select(x => x.Invoke()).ToArray()); + logger.WarnFormat((message), formatItems.Select(x => x.Invoke()).ToArray()); } @@ -105,7 +99,7 @@ namespace Umbraco.Core.Logging var logger = LogManager.GetLogger(callingType); if (logger == null || logger.IsWarnEnabled == false) return; var executedParams = formatItems.Select(x => x.Invoke()).ToArray(); - logger.WarnFormat(PrefixThreadId(message) + ". Exception: " + e, executedParams); + logger.WarnFormat((message) + ". Exception: " + e, executedParams); } /// @@ -117,7 +111,7 @@ namespace Umbraco.Core.Logging { var logger = LogManager.GetLogger(callingType); if (logger == null || logger.IsInfoEnabled == false) return; - logger.Info(PrefixThreadId(generateMessage.Invoke())); + logger.Info((generateMessage.Invoke())); } /// @@ -131,7 +125,7 @@ namespace Umbraco.Core.Logging var logger = LogManager.GetLogger(type); if (logger == null || logger.IsInfoEnabled == false) return; var executedParams = formatItems.Select(x => x.Invoke()).ToArray(); - logger.InfoFormat(PrefixThreadId(generateMessageFormat), executedParams); + logger.InfoFormat((generateMessageFormat), executedParams); } @@ -144,7 +138,7 @@ namespace Umbraco.Core.Logging { var logger = LogManager.GetLogger(callingType); if (logger == null || logger.IsDebugEnabled == false) return; - logger.Debug(PrefixThreadId(generateMessage.Invoke())); + logger.Debug((generateMessage.Invoke())); } /// @@ -158,7 +152,7 @@ namespace Umbraco.Core.Logging var logger = LogManager.GetLogger(type); if (logger == null || logger.IsDebugEnabled == false) return; var executedParams = formatItems.Select(x => x.Invoke()).ToArray(); - logger.DebugFormat(PrefixThreadId(generateMessageFormat), executedParams); + logger.DebugFormat((generateMessageFormat), executedParams); } diff --git a/src/Umbraco.Core/Logging/LoggingEventContext.cs b/src/Umbraco.Core/Logging/LoggingEventContext.cs new file mode 100644 index 0000000000..159af4266b --- /dev/null +++ b/src/Umbraco.Core/Logging/LoggingEventContext.cs @@ -0,0 +1,17 @@ +using log4net.Core; + +namespace Umbraco.Core.Logging +{ + /// + /// Based on https://github.com/cjbhaines/Log4Net.Async + /// + internal class LoggingEventContext + { + public LoggingEventContext(LoggingEvent loggingEvent) + { + LoggingEvent = loggingEvent; + } + + public LoggingEvent LoggingEvent { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Logging/LoggingEventHelper.cs b/src/Umbraco.Core/Logging/LoggingEventHelper.cs new file mode 100644 index 0000000000..c788e115f2 --- /dev/null +++ b/src/Umbraco.Core/Logging/LoggingEventHelper.cs @@ -0,0 +1,31 @@ +using System; +using log4net.Core; + +namespace Umbraco.Core.Logging +{ + /// + /// Based on https://github.com/cjbhaines/Log4Net.Async + /// + internal class LoggingEventHelper + { + // needs to be a seperate class so that location is determined correctly by log4net when required + + private static readonly Type HelperType = typeof(LoggingEventHelper); + private readonly string loggerName; + + public FixFlags Fix { get; set; } + + public LoggingEventHelper(string loggerName, FixFlags fix) + { + this.loggerName = loggerName; + Fix = fix; + } + + public LoggingEvent CreateLoggingEvent(Level level, string message, Exception exception) + { + var loggingEvent = new LoggingEvent(HelperType, null, loggerName, level, message, exception); + loggingEvent.Fix = Fix; + return loggingEvent; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Logging/ParallelForwardingAppender.cs b/src/Umbraco.Core/Logging/ParallelForwardingAppender.cs new file mode 100644 index 0000000000..cf7efdb4c4 --- /dev/null +++ b/src/Umbraco.Core/Logging/ParallelForwardingAppender.cs @@ -0,0 +1,307 @@ +using System; +using System.Collections.Concurrent; +using System.Threading; +using System.Threading.Tasks; +using log4net.Core; +using log4net.Util; + +namespace Umbraco.Core.Logging +{ + /// + /// An asynchronous appender based on + /// + /// + /// Based on https://github.com/cjbhaines/Log4Net.Async + /// + public class ParallelForwardingAppender : AsyncForwardingAppenderBase, IDisposable + { + #region Private Members + + private const int DefaultBufferSize = 1000; + private BlockingCollection _loggingEvents; + private CancellationTokenSource _loggingCancelationTokenSource; + private CancellationToken _loggingCancelationToken; + private Task _loggingTask; + private Double _shutdownFlushTimeout = 1; + private TimeSpan _shutdownFlushTimespan = TimeSpan.FromSeconds(1); + private static readonly Type ThisType = typeof(ParallelForwardingAppender); + private volatile bool _shutDownRequested; + private int? _bufferSize = DefaultBufferSize; + + #endregion Private Members + + #region Properties + + /// + /// Gets or sets the number of LoggingEvents that will be buffered. Set to null for unlimited. + /// + public override int? BufferSize + { + get { return _bufferSize; } + set { _bufferSize = value; } + } + + public int BufferEntryCount + { + get + { + if (_loggingEvents == null) return 0; + return _loggingEvents.Count; + } + } + + /// + /// Gets or sets the time period in which the system will wait for appenders to flush before canceling the background task. + /// + public Double ShutdownFlushTimeout + { + get + { + return _shutdownFlushTimeout; + } + set + { + _shutdownFlushTimeout = value; + } + } + + protected override string InternalLoggerName + { + get { return "ParallelForwardingAppender"; } + } + + #endregion Properties + + #region Startup + + public override void ActivateOptions() + { + base.ActivateOptions(); + _shutdownFlushTimespan = TimeSpan.FromSeconds(_shutdownFlushTimeout); + StartForwarding(); + } + + private void StartForwarding() + { + if (_shutDownRequested) + { + return; + } + //Create a collection which will block the thread and wait for new entries + //if the collection is empty + if (BufferSize.HasValue && BufferSize > 0) + { + _loggingEvents = new BlockingCollection(BufferSize.Value); + } + else + { + //No limit on the number of events. + _loggingEvents = new BlockingCollection(); + } + //The cancellation token is used to cancel a running task gracefully. + _loggingCancelationTokenSource = new CancellationTokenSource(); + _loggingCancelationToken = _loggingCancelationTokenSource.Token; + _loggingTask = new Task(SubscriberLoop, _loggingCancelationToken); + _loggingTask.Start(); + } + + #endregion Startup + + #region Shutdown + + private void CompleteSubscriberTask() + { + _shutDownRequested = true; + if (_loggingEvents == null || _loggingEvents.IsAddingCompleted) + { + return; + } + //Don't allow more entries to be added. + _loggingEvents.CompleteAdding(); + //Allow some time to flush + Thread.Sleep(_shutdownFlushTimespan); + if (!_loggingTask.IsCompleted && !_loggingCancelationToken.IsCancellationRequested) + { + _loggingCancelationTokenSource.Cancel(); + //Wait here so that the error logging messages do not get into a random order. + //Don't pass the cancellation token because we are not interested + //in catching the OperationCanceledException that results. + _loggingTask.Wait(); + } + if (!_loggingEvents.IsCompleted) + { + ForwardInternalError("The buffer was not able to be flushed before timeout occurred.", null, ThisType); + } + } + + protected override void OnClose() + { + CompleteSubscriberTask(); + base.OnClose(); + } + + #endregion Shutdown + + #region Appending + + protected override void Append(LoggingEvent loggingEvent) + { + if (_loggingEvents == null || _loggingEvents.IsAddingCompleted || loggingEvent == null) + { + return; + } + + loggingEvent.Fix = Fix; + //In the case where blocking on a full collection, and the task is subsequently completed, the cancellation token + //will prevent the entry from attempting to add to the completed collection which would result in an exception. + _loggingEvents.Add(new LoggingEventContext(loggingEvent), _loggingCancelationToken); + } + + protected override void Append(LoggingEvent[] loggingEvents) + { + if (_loggingEvents == null || _loggingEvents.IsAddingCompleted || loggingEvents == null) + { + return; + } + + foreach (var loggingEvent in loggingEvents) + { + Append(loggingEvent); + } + } + + #endregion Appending + + #region Forwarding + + /// + /// Iterates over a BlockingCollection containing LoggingEvents. + /// + private void SubscriberLoop() + { + Thread.CurrentThread.Name = String.Format("{0} ParallelForwardingAppender Subscriber Task", Name); + //The task will continue in a blocking loop until + //the queue is marked as adding completed, or the task is canceled. + try + { + //This call blocks until an item is available or until adding is completed + foreach (var entry in _loggingEvents.GetConsumingEnumerable(_loggingCancelationToken)) + { + ForwardLoggingEvent(entry.LoggingEvent, ThisType); + } + } + catch (OperationCanceledException ex) + { + //The thread was canceled before all entries could be forwarded and the collection completed. + ForwardInternalError("Subscriber task was canceled before completion.", ex, ThisType); + //Cancellation is called in the CompleteSubscriberTask so don't call that again. + } + catch (ThreadAbortException ex) + { + //Thread abort may occur on domain unload. + ForwardInternalError("Subscriber task was aborted.", ex, ThisType); + //Cannot recover from a thread abort so complete the task. + CompleteSubscriberTask(); + //The exception is swallowed because we don't want the client application + //to halt due to a logging issue. + } + catch (Exception ex) + { + //On exception, try to log the exception + ForwardInternalError("Subscriber task error in forwarding loop.", ex, ThisType); + //Any error in the loop is going to be some sort of extenuating circumstance from which we + //probably cannot recover anyway. Complete subscribing. + CompleteSubscriberTask(); + } + } + + #endregion Forwarding + + #region IDisposable Implementation + + private bool _disposed = false; + + //Implement IDisposable. + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + if (disposing) + { + if (_loggingTask != null) + { + if (!(_loggingTask.IsCanceled || _loggingTask.IsCompleted || _loggingTask.IsFaulted)) + { + try + { + CompleteSubscriberTask(); + } + catch (Exception ex) + { + LogLog.Error(ThisType, "Exception Completing Subscriber Task in Dispose Method", ex); + } + } + try + { + _loggingTask.Dispose(); + } + catch (Exception ex) + { + LogLog.Error(ThisType, "Exception Disposing Logging Task", ex); + } + finally + { + _loggingTask = null; + } + } + if (_loggingEvents != null) + { + try + { + _loggingEvents.Dispose(); + } + catch (Exception ex) + { + LogLog.Error(ThisType, "Exception Disposing BlockingCollection", ex); + } + finally + { + _loggingEvents = null; + } + } + if (_loggingCancelationTokenSource != null) + { + try + { + _loggingCancelationTokenSource.Dispose(); + } + catch (Exception ex) + { + LogLog.Error(ThisType, "Exception Disposing CancellationTokenSource", ex); + } + finally + { + _loggingCancelationTokenSource = null; + } + } + } + _disposed = true; + } + } + + // Use C# destructor syntax for finalization code. + ~ParallelForwardingAppender() + { + // Simply call Dispose(false). + Dispose(false); + } + + #endregion IDisposable Implementation + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Macros/MacroTagParser.cs b/src/Umbraco.Core/Macros/MacroTagParser.cs index f731eda56e..66f8a8a568 100644 --- a/src/Umbraco.Core/Macros/MacroTagParser.cs +++ b/src/Umbraco.Core/Macros/MacroTagParser.cs @@ -11,8 +11,12 @@ namespace Umbraco.Core.Macros /// internal class MacroTagParser { - private static readonly Regex MacroRteContent = new Regex(@"()", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline); - private static readonly Regex MacroPersistedFormat = new Regex(@"(<\?UMBRACO_MACRO macroAlias=[""']([^""\'\n\r]+?)[""'].+?)(?:/>|>.*?)", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline); + private static readonly Regex MacroRteContent = new Regex(@"()", + RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Singleline); + + private static readonly Regex MacroPersistedFormat = + new Regex(@"(<\?UMBRACO_MACRO (?:.+?)??macroAlias=[""']([^""\'\n\r]+?)[""'].+?)(?:/>|>.*?)", + RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Singleline); /// /// This formats the persisted string to something useful for the rte so that the macro renders properly since we diff --git a/src/Umbraco.Core/MainDom.cs b/src/Umbraco.Core/MainDom.cs new file mode 100644 index 0000000000..6f4a539194 --- /dev/null +++ b/src/Umbraco.Core/MainDom.cs @@ -0,0 +1,189 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO.MemoryMappedFiles; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Web.Hosting; +using Umbraco.Core.Logging; +using Umbraco.Core.ObjectResolution; + +namespace Umbraco.Core +{ + // represents the main domain + class MainDom : IRegisteredObject + { + #region Vars + + private readonly ILogger _logger; + + // our own lock for local consistency + private readonly object _locko = new object(); + + // async lock representing the main domain lock + private readonly AsyncLock _asyncLock; + private IDisposable _asyncLocker; + + // event wait handle used to notify current main domain that it should + // release the lock because a new domain wants to be the main domain + private readonly EventWaitHandle _signal; + + // indicates whether... + private volatile bool _isMainDom; // we are the main domain + private volatile bool _signaled; // we have been signaled + + // actions to run before releasing the main domain + private readonly SortedList _callbacks = new SortedList(); + + private const int LockTimeoutMilliseconds = 90000; // (1.5 * 60 * 1000) == 1 min 30 seconds + + #endregion + + #region Ctor + + // initializes a new instance of MainDom + public MainDom(ILogger logger) + { + _logger = logger; + + var appId = string.Empty; + // HostingEnvironment.ApplicationID is null in unit tests, making ReplaceNonAlphanumericChars fail + if (HostingEnvironment.ApplicationID != null) + appId = HostingEnvironment.ApplicationID.ReplaceNonAlphanumericChars(string.Empty); + + var lockName = "UMBRACO-" + appId + "-MAINDOM-LCK"; + _asyncLock = new AsyncLock(lockName); + + var eventName = "UMBRACO-" + appId + "-MAINDOM-EVT"; + _signal = new EventWaitHandle(false, EventResetMode.AutoReset, eventName); + } + + #endregion + + // register a main domain consumer + public bool Register(Action release, int weight = 100) + { + return Register(null, release, weight); + } + + // register a main domain consumer + public bool Register(Action install, Action release, int weight = 100) + { + lock (_locko) + { + if (_signaled) return false; + if (install != null) + install(); + if (release != null) + _callbacks.Add(weight, release); + return true; + } + } + + // handles the signal requesting that the main domain is released + private void OnSignal(string source) + { + // once signaled, we stop waiting, but then there is the hosting environment + // so we have to make sure that we only enter that method once + + lock (_locko) + { + _logger.Debug("Signaled" + (_signaled ? " (again)" : "") + " (" + source + ")."); + if (_signaled) return; + if (_isMainDom == false) return; // probably not needed + _signaled = true; + } + + try + { + _logger.Debug("Stopping..."); + foreach (var callback in _callbacks.Values) + { + try + { + callback(); // no timeout on callbacks + } + catch (Exception e) + { + _logger.Error("Error while running callback, remaining callbacks will not run.", e); + throw; + } + + } + _logger.Debug("Stopped."); + } + finally + { + // in any case... + _isMainDom = false; + _asyncLocker.Dispose(); + _logger.Debug("Released MainDom."); + } + } + + // acquires the main domain + public bool Acquire() + { + lock (_locko) // we don't want the hosting environment to interfere by signaling + { + // if signaled, too late to acquire, give up + // the handler is not installed so that would be the hosting environment + if (_signaled) + { + _logger.Debug("Cannot acquire MainDom (signaled)."); + return false; + } + + _logger.Debug("Acquiring MainDom..."); + + // signal other instances that we want the lock, then wait one the lock, + // which may timeout, and this is accepted - see comments below + + // signal, then wait for the lock, then make sure the event is + // resetted (maybe there was noone listening..) + _signal.Set(); + + // if more than 1 instance reach that point, one will get the lock + // and the other one will timeout, which is accepted + + _asyncLocker = _asyncLock.Lock(LockTimeoutMilliseconds); + _isMainDom = true; + + // we need to reset the event, because otherwise we would end up + // signaling ourselves and commiting suicide immediately. + // only 1 instance can reach that point, but other instances may + // have started and be trying to get the lock - they will timeout, + // which is accepted + + _signal.Reset(); + _signal.WaitOneAsync() + .ContinueWith(_ => OnSignal("signal")); + + HostingEnvironment.RegisterObject(this); + + _logger.Debug("Acquired MainDom."); + return true; + } + } + + // gets a value indicating whether we are the main domain + public bool IsMainDom + { + get { return _isMainDom; } + } + + // IRegisteredObject + public void Stop(bool immediate) + { + try + { + OnSignal("environment"); // will run once + } + finally + { + HostingEnvironment.UnregisterObject(this); + } + } + } +} diff --git a/src/Umbraco.Core/Manifest/ManifestParser.cs b/src/Umbraco.Core/Manifest/ManifestParser.cs index c8c421aea8..7e7d9fe2b6 100644 --- a/src/Umbraco.Core/Manifest/ManifestParser.cs +++ b/src/Umbraco.Core/Manifest/ManifestParser.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text; using System.Text.RegularExpressions; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -100,11 +101,15 @@ namespace Umbraco.Core.Manifest if (depth < 1) { - var dirs = currDir.GetDirectories(); var result = new List(); - foreach (var d in dirs) + if (currDir.Exists) { - result.AddRange(GetAllManifestFileContents(d)); + var dirs = currDir.GetDirectories(); + + foreach (var d in dirs) + { + result.AddRange(GetAllManifestFileContents(d)); + } } return result; } @@ -140,11 +145,22 @@ namespace Umbraco.Core.Manifest { var result = new List(); foreach (var m in manifestFileContents) - { - if (m.IsNullOrWhiteSpace()) continue; + { + var manifestContent = m; + + if (manifestContent.IsNullOrWhiteSpace()) continue; + + // Strip byte object marker, JSON.NET does not like it + var preamble = Encoding.UTF8.GetString(Encoding.UTF8.GetPreamble()); + + // Strangely StartsWith(preamble) would always return true + if (manifestContent.Substring(0, 1) == preamble) + manifestContent = manifestContent.Remove(0, preamble.Length); + + if (manifestContent.IsNullOrWhiteSpace()) continue; //remove any comments first - var replaced = CommentsSurround.Replace(m, match => " "); + var replaced = CommentsSurround.Replace(manifestContent, match => " "); replaced = CommentsLine.Replace(replaced, match => ""); JObject deserialized; diff --git a/src/Umbraco.Core/Media/Exif/ExifFileTypeDescriptor.cs b/src/Umbraco.Core/Media/Exif/ExifFileTypeDescriptor.cs index 04c9c0ded7..00fd9a11c0 100644 --- a/src/Umbraco.Core/Media/Exif/ExifFileTypeDescriptor.cs +++ b/src/Umbraco.Core/Media/Exif/ExifFileTypeDescriptor.cs @@ -34,7 +34,7 @@ namespace Umbraco.Core.Media.Exif } /// - /// Expands ExifProperty objects contained in an ExifFile as seperate properties. + /// Expands ExifProperty objects contained in an ExifFile as separate properties. /// internal sealed class ExifFileTypeDescriptor : CustomTypeDescriptor { diff --git a/src/Umbraco.Core/Media/ImageHelper.cs b/src/Umbraco.Core/Media/ImageHelper.cs index 5842bb67bb..99fc278e61 100644 --- a/src/Umbraco.Core/Media/ImageHelper.cs +++ b/src/Umbraco.Core/Media/ImageHelper.cs @@ -59,7 +59,7 @@ namespace Umbraco.Core.Media var fileHeight = image.Height; return new Size(fileWidth, fileHeight); } - + } public static string GetMimeType(this Image image) @@ -79,22 +79,22 @@ namespace Umbraco.Core.Media /// /// internal static IEnumerable GenerateMediaThumbnails( - IFileSystem fs, - string fileName, - string extension, + IFileSystem fs, + string fileName, + string extension, Image originalImage, IEnumerable additionalThumbSizes) { var result = new List(); - var allSizesDictionary = new Dictionary {{100,"thumb"}, {500,"big-thumb"}}; - + var allSizesDictionary = new Dictionary { { 100, "thumb" }, { 500, "big-thumb" } }; + //combine the static dictionary with the additional sizes with only unique values var allSizes = allSizesDictionary.Select(kv => kv.Key) .Union(additionalThumbSizes.Where(x => x > 0).Distinct()); - var sizesDictionary = allSizes.ToDictionary(s => s, s => allSizesDictionary.ContainsKey(s) ? allSizesDictionary[s]: ""); + var sizesDictionary = allSizes.ToDictionary(s => s, s => allSizesDictionary.ContainsKey(s) ? allSizesDictionary[s] : ""); foreach (var s in sizesDictionary) { @@ -121,9 +121,9 @@ namespace Umbraco.Core.Media /// private static ResizedImage Resize(IFileSystem fileSystem, string path, string extension, int maxWidthHeight, string fileNameAddition, Image originalImage) { - var fileNameThumb = String.IsNullOrEmpty(fileNameAddition) - ? string.Format("{0}_UMBRACOSYSTHUMBNAIL.jpg", path.Substring(0, path.LastIndexOf("."))) - : string.Format("{0}_{1}.jpg", path.Substring(0, path.LastIndexOf(".")), fileNameAddition); + var fileNameThumb = string.IsNullOrWhiteSpace(fileNameAddition) + ? string.Format("{0}_UMBRACOSYSTHUMBNAIL." + extension, path.Substring(0, path.LastIndexOf(".", StringComparison.Ordinal))) + : string.Format("{0}_{1}." + extension, path.Substring(0, path.LastIndexOf(".", StringComparison.Ordinal)), fileNameAddition); var thumb = GenerateThumbnail( originalImage, @@ -190,9 +190,9 @@ namespace Umbraco.Core.Media //use best quality g.InterpolationMode = InterpolationMode.HighQualityBicubic; } - - g.SmoothingMode = SmoothingMode.HighQuality; + + g.SmoothingMode = SmoothingMode.HighQuality; g.PixelOffsetMode = PixelOffsetMode.HighQuality; g.CompositingQuality = CompositingQuality.HighQuality; @@ -202,10 +202,29 @@ namespace Umbraco.Core.Media // Copy metadata var imageEncoders = ImageCodecInfo.GetImageEncoders(); - - var codec = extension.ToLower() == "png" || extension.ToLower() == "gif" - ? imageEncoders.Single(t => t.MimeType.Equals("image/png")) - : imageEncoders.Single(t => t.MimeType.Equals("image/jpeg")); + ImageCodecInfo codec; + switch (extension.ToLower()) + { + case "png": + codec = imageEncoders.Single(t => t.MimeType.Equals("image/png")); + break; + case "gif": + codec = imageEncoders.Single(t => t.MimeType.Equals("image/gif")); + break; + case "tif": + case "tiff": + codec = imageEncoders.Single(t => t.MimeType.Equals("image/tiff")); + break; + case "bmp": + codec = imageEncoders.Single(t => t.MimeType.Equals("image/bmp")); + break; + // TODO: this is dirty, defaulting to jpg but the return value of this thing is used all over the + // place so left it here, but it needs to not set a codec if it doesn't know which one to pick + // Note: when fixing this: both .jpg and .jpeg should be handled as extensions + default: + codec = imageEncoders.Single(t => t.MimeType.Equals("image/jpeg")); + break; + } // Set compresion ratio to 90% var ep = new EncoderParameters(); @@ -213,12 +232,14 @@ namespace Umbraco.Core.Media // Save the new image using the dimensions of the image var predictableThumbnailName = thumbnailFileName.Replace("UMBRACOSYSTHUMBNAIL", maxWidthHeight.ToString(CultureInfo.InvariantCulture)); + var predictableThumbnailNameJpg = predictableThumbnailName.Substring(0, predictableThumbnailName.LastIndexOf(".", StringComparison.Ordinal)) + ".jpg"; using (var ms = new MemoryStream()) { bp.Save(ms, codec, ep); ms.Seek(0, 0); fs.AddFile(predictableThumbnailName, ms); + fs.AddFile(predictableThumbnailNameJpg, ms); } // TODO: Remove this, this is ONLY here for backwards compatibility but it is essentially completely unusable see U4-5385 diff --git a/src/Umbraco.Core/Models/Content.cs b/src/Umbraco.Core/Models/Content.cs index 9b95fe5475..2238260611 100644 --- a/src/Umbraco.Core/Models/Content.cs +++ b/src/Umbraco.Core/Models/Content.cs @@ -95,13 +95,7 @@ namespace Umbraco.Core.Models [DataMember] public virtual ITemplate Template { - get - { - if (_template == null) - return _contentType.DefaultTemplate; - - return _template; - } + get { return _template; } set { SetPropertyValueAndDetectChanges(o => diff --git a/src/Umbraco.Core/Models/ContentExtensions.cs b/src/Umbraco.Core/Models/ContentExtensions.cs index 88b6308c29..e91996e32a 100644 --- a/src/Umbraco.Core/Models/ContentExtensions.cs +++ b/src/Umbraco.Core/Models/ContentExtensions.cs @@ -560,8 +560,7 @@ namespace Umbraco.Core.Models //Additional thumbnails configured as prevalues on the DataType if (thumbnailSizes != null) { - var sep = (thumbnailSizes.Contains("") == false && thumbnailSizes.Contains(",")) ? ',' : ';'; - foreach (var thumb in thumbnailSizes.Split(sep)) + foreach (var thumb in thumbnailSizes.Split(new[] { ";", "," }, StringSplitOptions.RemoveEmptyEntries)) { int thumbSize; if (thumb != "" && int.TryParse(thumb, out thumbSize)) diff --git a/src/Umbraco.Core/Models/ContentTypeBase.cs b/src/Umbraco.Core/Models/ContentTypeBase.cs index bee9ed9b6e..a83defb7b5 100644 --- a/src/Umbraco.Core/Models/ContentTypeBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeBase.cs @@ -26,7 +26,7 @@ namespace Umbraco.Core.Models private string _alias; private string _description; private int _sortOrder; - private string _icon = "folder.png"; + private string _icon = "icon-folder"; private string _thumbnail = "folder.png"; private int _creatorId; private bool _allowedAsRoot; @@ -373,43 +373,35 @@ namespace Umbraco.Core.Models { _propertyGroups = value; _propertyGroups.CollectionChanged += PropertyGroupsChanged; + PropertyGroupsChanged(_propertyGroups, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } } /// - /// List of PropertyTypes available on this ContentType. - /// This list aggregates PropertyTypes across the PropertyGroups. + /// Gets all property types, across all property groups. /// - /// - /// - /// The setter is used purely to set the property types that DO NOT belong to a group! - /// - /// Marked as DoNotClone because the result of this property is not the natural result of the data, it is - /// a union of data so when auto-cloning if the setter is used it will be setting the unnatural result of the - /// data. We manually clone this instead. - /// [IgnoreDataMember] [DoNotClone] public virtual IEnumerable PropertyTypes { get { - var types = _propertyTypes.Union(PropertyGroups.SelectMany(x => x.PropertyTypes)); - return types; - } - internal set - { - _propertyTypes = new PropertyTypeCollection(value); - _propertyTypes.CollectionChanged += PropertyTypesChanged; + return _propertyTypes.Union(PropertyGroups.SelectMany(x => x.PropertyTypes)); } } /// - /// Returns the property type collection containing types that are non-groups - used for tests + /// Gets or sets the property types that are not in a group. /// - internal IEnumerable NonGroupedPropertyTypes + public IEnumerable NoGroupPropertyTypes { get { return _propertyTypes; } + set + { + _propertyTypes = new PropertyTypeCollection(value); + _propertyTypes.CollectionChanged += PropertyTypesChanged; + PropertyTypesChanged(_propertyTypes, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } } /// @@ -460,6 +452,11 @@ namespace Umbraco.Core.Models /// Returns True if PropertyType was added, otherwise False public bool AddPropertyType(PropertyType propertyType) { + if (propertyType.HasIdentity == false) + { + propertyType.Key = Guid.NewGuid(); + } + if (PropertyTypeExists(propertyType.Alias) == false) { _propertyTypes.Add(propertyType); @@ -475,24 +472,37 @@ namespace Umbraco.Core.Models /// Alias of the PropertyType to move /// Name of the PropertyGroup to move the PropertyType to /// + /// If is null then the property is moved back to + /// "generic properties" ie does not have a tab anymore. public bool MovePropertyType(string propertyTypeAlias, string propertyGroupName) { - if (PropertyTypes.Any(x => x.Alias == propertyTypeAlias) == false || PropertyGroups.Any(x => x.Name == propertyGroupName) == false) - return false; + // note: not dealing with alias casing at all here? - var propertyType = PropertyTypes.First(x => x.Alias == propertyTypeAlias); - //The PropertyType already belongs to a PropertyGroup, so we have to remove the PropertyType from that group - if (PropertyGroups.Any(x => x.PropertyTypes.Any(y => y.Alias == propertyTypeAlias))) - { - var oldPropertyGroup = PropertyGroups.First(x => x.PropertyTypes.Any(y => y.Alias == propertyTypeAlias)); - oldPropertyGroup.PropertyTypes.RemoveItem(propertyTypeAlias); - } + // get property, ensure it exists + var propertyType = PropertyTypes.FirstOrDefault(x => x.Alias == propertyTypeAlias); + if (propertyType == null) return false; + // get new group, if required, and ensure it exists + var newPropertyGroup = propertyGroupName == null + ? null + : PropertyGroups.FirstOrDefault(x => x.Name == propertyGroupName); + if (propertyGroupName != null && newPropertyGroup == null) return false; + + // get old group + var oldPropertyGroup = PropertyGroups.FirstOrDefault(x => + x.PropertyTypes.Any(y => y.Alias == propertyTypeAlias)); + + // reset PropertyGroupId, which will be re-evaluated when the content type + // is saved - what is important is group.PropertyTypes - see code in + // ContentTypeBaseRepository.PersistUpdatedBaseContentType propertyType.PropertyGroupId = new Lazy(() => default(int)); - propertyType.ResetDirtyProperties(); + propertyType.ResetDirtyProperties(); // PropertyGroupId must not be dirty - var propertyGroup = PropertyGroups.First(x => x.Name == propertyGroupName); - propertyGroup.PropertyTypes.Add(propertyType); + // remove from old group, if any - add to new group, if any + if (oldPropertyGroup != null) + oldPropertyGroup.PropertyTypes.RemoveItem(propertyTypeAlias); + if (newPropertyGroup != null) + newPropertyGroup.PropertyTypes.Add(propertyType); return true; } @@ -528,6 +538,18 @@ namespace Umbraco.Core.Models /// Name of the to remove public void RemovePropertyGroup(string propertyGroupName) { + // if no group exists with that name, do nothing + var group = PropertyGroups[propertyGroupName]; + if (group == null) return; + + // re-assign the group's properties to no group + foreach (var property in group.PropertyTypes) + { + property.PropertyGroupId = new Lazy(() => 0); + _propertyTypes.Add(property); + } + + // actually remove the group PropertyGroups.RemoveItem(propertyGroupName); OnPropertyChanged(PropertyGroupCollectionSelector); } diff --git a/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs b/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs index 1fd1496742..5ac21885d7 100644 --- a/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs @@ -37,16 +37,21 @@ namespace Umbraco.Core.Models x => x.ContentTypeComposition); /// - /// List of ContentTypes that make up a composition of PropertyGroups and PropertyTypes for the current ContentType + /// Gets or sets the content types that compose this content type. /// [DataMember] public IEnumerable ContentTypeComposition { get { return _contentTypeComposition; } + set + { + _contentTypeComposition = value.ToList(); + OnPropertyChanged(ContentTypeCompositionSelector); + } } /// - /// Returns a list of objects from the composition + /// Gets the property groups for the entire composition. /// [IgnoreDataMember] public IEnumerable CompositionPropertyGroups @@ -59,7 +64,7 @@ namespace Umbraco.Core.Models } /// - /// Returns a list of objects from the composition + /// Gets the property types for the entire composition. /// [IgnoreDataMember] public IEnumerable CompositionPropertyTypes @@ -72,10 +77,10 @@ namespace Umbraco.Core.Models } /// - /// Adds a new ContentType to the list of composite ContentTypes + /// Adds a content type to the composition. /// - /// to add - /// True if ContentType was added, otherwise returns False + /// The content type to add. + /// True if the content type was added, otherwise false. public bool AddContentType(IContentTypeComposition contentType) { if (contentType.ContentTypeComposition.Any(x => x.CompositionAliases().Any(ContentTypeCompositionExists))) @@ -94,13 +99,7 @@ namespace Umbraco.Core.Models .Select(p => p.Alias)).ToList(); if (conflictingPropertyTypeAliases.Any()) - throw new InvalidCompositionException - { - AddedCompositionAlias = contentType.Alias, - ContentTypeAlias = Alias, - PropertyTypeAlias = - string.Join(", ", conflictingPropertyTypeAliases) - }; + throw new InvalidCompositionException(Alias, contentType.Alias, conflictingPropertyTypeAliases.ToArray()); _contentTypeComposition.Add(contentType); OnPropertyChanged(ContentTypeCompositionSelector); @@ -110,10 +109,10 @@ namespace Umbraco.Core.Models } /// - /// Removes a ContentType with the supplied alias from the the list of composite ContentTypes + /// Removes a content type with a specified alias from the composition. /// - /// Alias of a - /// True if ContentType was removed, otherwise returns False + /// The alias of the content type to remove. + /// True if the content type was removed, otherwise false. public bool RemoveContentType(string alias) { if (ContentTypeCompositionExists(alias)) @@ -163,31 +162,44 @@ namespace Umbraco.Core.Models /// /// Adds a PropertyGroup. - /// This method will also check if a group already exists with the same name and link it to the parent. /// /// Name of the PropertyGroup to add /// Returns True if a PropertyGroup with the passed in name was added, otherwise False public override bool AddPropertyGroup(string groupName) { - if (PropertyGroups.Any(x => x.Name == groupName)) - return false; + return AddAndReturnPropertyGroup(groupName) != null; + } - var propertyGroup = new PropertyGroup {Name = groupName, SortOrder = 0}; + private PropertyGroup AddAndReturnPropertyGroup(string name) + { + // ensure we don't have it already + if (PropertyGroups.Any(x => x.Name == name)) + return null; - if (CompositionPropertyGroups.Any(x => x.Name == groupName)) + // create the new group + var group = new PropertyGroup { Name = name, SortOrder = 0 }; + + // check if it is inherited - there might be more than 1 but we want the 1st, to + // reuse its sort order - if there are more than 1 and they have different sort + // orders... there isn't much we can do anyways + var inheritGroup = CompositionPropertyGroups.FirstOrDefault(x => x.Name == name); + if (inheritGroup == null) { - var firstGroup = CompositionPropertyGroups.First(x => x.Name == groupName && x.ParentId.HasValue == false); - propertyGroup.SetLazyParentId(new Lazy(() => firstGroup.Id)); + // no, just local, set sort order + var lastGroup = PropertyGroups.LastOrDefault(); + if (lastGroup != null) + group.SortOrder = lastGroup.SortOrder + 1; + } + else + { + // yes, inherited, re-use sort order + group.SortOrder = inheritGroup.SortOrder; } - if (PropertyGroups.Any()) - { - var last = PropertyGroups.Last(); - propertyGroup.SortOrder = last.SortOrder + 1; - } + // add + PropertyGroups.Add(group); - PropertyGroups.Add(propertyGroup); - return true; + return group; } /// @@ -198,34 +210,25 @@ namespace Umbraco.Core.Models /// Returns True if PropertyType was added, otherwise False public override bool AddPropertyType(PropertyType propertyType, string propertyGroupName) { - if (PropertyTypeExists(propertyType.Alias) == false) - { - if (PropertyGroups.Contains(propertyGroupName)) - { - propertyType.PropertyGroupId = new Lazy(() => PropertyGroups[propertyGroupName].Id); - PropertyGroups[propertyGroupName].PropertyTypes.Add(propertyType); - } - else - { - //If the PropertyGroup doesn't already exist we create a new one - var propertyTypes = new List { propertyType }; - var propertyGroup = new PropertyGroup(new PropertyTypeCollection(propertyTypes)) { Name = propertyGroupName, SortOrder = 1 }; - //and check if its an inherited PropertyGroup, which exists in the composition - if (CompositionPropertyGroups.Any(x => x.Name == propertyGroupName)) - { - var parentPropertyGroup = CompositionPropertyGroups.First(x => x.Name == propertyGroupName && x.ParentId.HasValue == false); - propertyGroup.SortOrder = parentPropertyGroup.SortOrder; - //propertyGroup.ParentId = parentPropertyGroup.Id; - propertyGroup.SetLazyParentId(new Lazy(() => parentPropertyGroup.Id)); - } + if (propertyType.HasIdentity == false) + propertyType.Key = Guid.NewGuid(); - PropertyGroups.Add(propertyGroup); - } + // ensure no duplicate alias - over all composition properties + if (PropertyTypeExists(propertyType.Alias)) + return false; - return true; - } + // get and ensure a group local to this content type + var group = PropertyGroups.Contains(propertyGroupName) + ? PropertyGroups[propertyGroupName] + : AddAndReturnPropertyGroup(propertyGroupName); + if (group == null) + return false; - return false; + // add property to group + propertyType.PropertyGroupId = new Lazy(() => group.Id); + group.PropertyTypes.Add(propertyType); + + return true; } /// diff --git a/src/Umbraco.Core/Models/ContentTypeExtensions.cs b/src/Umbraco.Core/Models/ContentTypeExtensions.cs index df8c09b3d1..a47b430979 100644 --- a/src/Umbraco.Core/Models/ContentTypeExtensions.cs +++ b/src/Umbraco.Core/Models/ContentTypeExtensions.cs @@ -15,7 +15,7 @@ namespace Umbraco.Core.Models { var contentTypeService = ApplicationContext.Current.Services.ContentTypeService; var descendants = contentTypeService.GetContentTypeChildren(contentType.Id) - .FlattenList(type => contentTypeService.GetContentTypeChildren(type.Id)); + .SelectRecursive(type => contentTypeService.GetContentTypeChildren(type.Id)); return descendants; } diff --git a/src/Umbraco.Core/Models/DataTypeDatabaseType.cs b/src/Umbraco.Core/Models/DataTypeDatabaseType.cs index e45cdd1a73..1db8ac65cb 100644 --- a/src/Umbraco.Core/Models/DataTypeDatabaseType.cs +++ b/src/Umbraco.Core/Models/DataTypeDatabaseType.cs @@ -21,6 +21,8 @@ namespace Umbraco.Core.Models [EnumMember] Integer, [EnumMember] - Date + Date, + [EnumMember] + Decimal } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/DataTypeDefinition.cs b/src/Umbraco.Core/Models/DataTypeDefinition.cs index 2fddbad6cb..5cf3976613 100644 --- a/src/Umbraco.Core/Models/DataTypeDefinition.cs +++ b/src/Umbraco.Core/Models/DataTypeDefinition.cs @@ -32,6 +32,7 @@ namespace Umbraco.Core.Models private string _propertyEditorAlias; private DataTypeDatabaseType _databaseType; + public DataTypeDefinition(int parentId, string propertyEditorAlias) { _parentId = parentId; @@ -40,6 +41,14 @@ namespace Umbraco.Core.Models _additionalData = new Dictionary(); } + public DataTypeDefinition(string propertyEditorAlias) + { + _parentId = -1; + _propertyEditorAlias = propertyEditorAlias; + + _additionalData = new Dictionary(); + } + private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); private static readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentId); private static readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); @@ -220,11 +229,6 @@ namespace Umbraco.Core.Models get { return _additionalData; } } - /// - /// Some entities may expose additional data that other's might not, this custom data will be available in this collection - /// - public IDictionary AdditionalData { get; private set; } - internal override void AddingEntity() { base.AddingEntity(); @@ -233,4 +237,4 @@ namespace Umbraco.Core.Models Key = Guid.NewGuid(); } } -} +} diff --git a/src/Umbraco.Core/Models/DeepCloneHelper.cs b/src/Umbraco.Core/Models/DeepCloneHelper.cs index 0e0e260c06..7523555c24 100644 --- a/src/Umbraco.Core/Models/DeepCloneHelper.cs +++ b/src/Umbraco.Core/Models/DeepCloneHelper.cs @@ -78,6 +78,12 @@ namespace Umbraco.Core.Models } else { + //skip instead of trying to create instance of abstract or interface + if (propertyInfo.PropertyType.IsAbstract || propertyInfo.PropertyType.IsInterface) + { + continue; + } + //its a custom IEnumerable, we'll try to create it try { diff --git a/src/Umbraco.Core/Models/DictionaryItem.cs b/src/Umbraco.Core/Models/DictionaryItem.cs index e09c370053..7e59726c80 100644 --- a/src/Umbraco.Core/Models/DictionaryItem.cs +++ b/src/Umbraco.Core/Models/DictionaryItem.cs @@ -14,22 +14,22 @@ namespace Umbraco.Core.Models [DataContract(IsReference = true)] public class DictionaryItem : Entity, IDictionaryItem { - private Guid _parentId; + private Guid? _parentId; private string _itemKey; private IEnumerable _translations; public DictionaryItem(string itemKey) - : this(new Guid(Constants.Conventions.Localization.DictionaryItemRootId), itemKey) + : this(null, itemKey) {} - public DictionaryItem(Guid parentId, string itemKey) + public DictionaryItem(Guid? parentId, string itemKey) { _parentId = parentId; _itemKey = itemKey; _translations = new List(); } - private static readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentId); + private static readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentId); private static readonly PropertyInfo ItemKeySelector = ExpressionHelper.GetPropertyInfo(x => x.ItemKey); private static readonly PropertyInfo TranslationsSelector = ExpressionHelper.GetPropertyInfo>(x => x.Translations); @@ -37,7 +37,7 @@ namespace Umbraco.Core.Models /// Gets or Sets the Parent Id of the Dictionary Item /// [DataMember] - public Guid ParentId + public Guid? ParentId { get { return _parentId; } set @@ -95,11 +95,7 @@ namespace Umbraco.Core.Models { base.AddingEntity(); - Key = Guid.NewGuid(); - - //If ParentId is not set we should default to the root parent id - if(ParentId == Guid.Empty) - _parentId = new Guid(Constants.Conventions.Localization.DictionaryItemRootId); + Key = Guid.NewGuid(); } } diff --git a/src/Umbraco.Core/Models/EntityBase/Entity.cs b/src/Umbraco.Core/Models/EntityBase/Entity.cs index 660d6674ca..eeacb771a9 100644 --- a/src/Umbraco.Core/Models/EntityBase/Entity.cs +++ b/src/Umbraco.Core/Models/EntityBase/Entity.cs @@ -101,6 +101,7 @@ namespace Umbraco.Core.Models.EntityBase /// the new api, which also needs to take effect in the legacy api. /// [IgnoreDataMember] + [Obsolete("Anytime there's a cancellable method it needs to return an Attempt so we know the outcome instead of this hack, not all services have been updated to use this though yet.")] internal bool WasCancelled { get { return _wasCancelled; } diff --git a/src/Umbraco.Core/Models/EntityContainer.cs b/src/Umbraco.Core/Models/EntityContainer.cs new file mode 100644 index 0000000000..92965bf238 --- /dev/null +++ b/src/Umbraco.Core/Models/EntityContainer.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Umbraco.Core.Models +{ + /// + /// Represents a folder for organizing entities such as content types and data types. + /// + public sealed class EntityContainer : UmbracoEntity + { + private readonly Guid _containedObjectType; + + private static readonly Dictionary ObjectTypeMap = new Dictionary + { + { Constants.ObjectTypes.DataTypeGuid, Constants.ObjectTypes.DataTypeContainerGuid }, + { Constants.ObjectTypes.DocumentTypeGuid, Constants.ObjectTypes.DocumentTypeContainerGuid }, + { Constants.ObjectTypes.MediaTypeGuid, Constants.ObjectTypes.MediaTypeContainerGuid } + }; + + /// + /// Initializes a new instance of an class. + /// + public EntityContainer(Guid containedObjectType) + { + if (ObjectTypeMap.ContainsKey(containedObjectType) == false) + throw new ArgumentException("Not a contained object type.", "containedObjectType"); + _containedObjectType = containedObjectType; + + ParentId = -1; + Path = "-1"; + Level = 0; + SortOrder = 0; + } + + /// + /// Initializes a new instance of an class. + /// + internal EntityContainer(int id, Guid uniqueId, int parentId, string path, int level, int sortOrder, Guid containedObjectType, string name, int userId) + : this(containedObjectType) + { + Id = id; + Key = uniqueId; + ParentId = parentId; + Name = name; + Path = path; + Level = level; + SortOrder = sortOrder; + CreatorId = userId; + } + + /// + /// Gets or sets the node object type of the contained objects. + /// + public Guid ContainedObjectType + { + get { return _containedObjectType; } + } + + /// + /// Gets the node object type of the container objects. + /// + public Guid ContainerObjectType + { + get { return ObjectTypeMap[_containedObjectType]; } + } + + /// + /// Gets the container object type corresponding to a contained object type. + /// + /// The contained object type. + /// The object type of containers containing objects of the contained object type. + public static Guid GetContainerObjectType(Guid containedObjectType) + { + if (ObjectTypeMap.ContainsKey(containedObjectType) == false) + throw new ArgumentException("Not a contained object type.", "containedObjectType"); + return ObjectTypeMap[containedObjectType]; + } + + /// + /// Gets the contained object type corresponding to a container object type. + /// + /// The container object type. + /// The object type of objects that containers of the container object type can contain. + public static Guid GetContainedObjectType(Guid containerObjectType) + { + var contained = ObjectTypeMap.FirstOrDefault(x => x.Value == containerObjectType).Key; + if (contained == null) + throw new ArgumentException("Not a container object type.", "containerObjectType"); + return contained; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/File.cs b/src/Umbraco.Core/Models/File.cs index ae4aac6a1c..8ead6da5f8 100644 --- a/src/Umbraco.Core/Models/File.cs +++ b/src/Umbraco.Core/Models/File.cs @@ -2,6 +2,8 @@ using System.IO; using System.Reflection; using System.Runtime.Serialization; +using System.Text; +using Umbraco.Core.IO; using Umbraco.Core.Models.EntityBase; namespace Umbraco.Core.Models @@ -15,12 +17,20 @@ namespace Umbraco.Core.Models { private string _path; private string _originalPath; - private string _content = string.Empty; //initialize to empty string, not null - protected File(string path) + // initialize to string.Empty so that it is possible to save a new file, + // should use the lazyContent ctor to set it to null when loading existing. + // cannot simply use HasIdentity as some classes (eg Script) override it + // in a weird way. + private string _content; + internal Func GetFileContent { get; set; } + + protected File(string path, Func getFileContent = null) { - _path = path; + _path = SanitizePath(path); _originalPath = _path; + GetFileContent = getFileContent; + _content = getFileContent != null ? null : string.Empty; } private static readonly PropertyInfo ContentSelector = ExpressionHelper.GetPropertyInfo(x => x.Content); @@ -28,6 +38,14 @@ namespace Umbraco.Core.Models private string _alias; private string _name; + private static string SanitizePath(string path) + { + return path + .Replace('\\', System.IO.Path.DirectorySeparatorChar) + .Replace('/', System.IO.Path.DirectorySeparatorChar); + //.TrimStart(System.IO.Path.DirectorySeparatorChar); + } + /// /// Gets or sets the Name of the File including extension /// @@ -71,7 +89,7 @@ namespace Umbraco.Core.Models SetPropertyValueAndDetectChanges(o => { - _path = value; + _path = SanitizePath(value); return _path; }, _path, PathSelector); } @@ -96,15 +114,26 @@ namespace Umbraco.Core.Models /// /// Gets or sets the Content of a File /// + /// Marked as DoNotClone, because it should be lazy-reloaded from disk. [DataMember] + [DoNotClone] public virtual string Content { - get { return _content; } + get + { + if (_content != null) + return _content; + + // else, must lazy-load, and ensure it's not null + if (GetFileContent != null) + _content = GetFileContent(this); + return _content ?? (_content = string.Empty); + } set { SetPropertyValueAndDetectChanges(o => { - _content = value; + _content = value ?? string.Empty; // cannot set to null return _content; }, _content, ContentSelector); } @@ -121,17 +150,32 @@ namespace Umbraco.Core.Models return true; } + // this exists so that class that manage name and alias differently, eg Template, + // can implement their own cloning - (though really, not sure it's even needed) + protected virtual void DeepCloneNameAndAlias(File clone) + { + // set fields that have a lazy value, by forcing evaluation of the lazy + clone._name = Name; + clone._alias = Alias; + } + public override object DeepClone() { - var clone = (File)base.DeepClone(); - //turn off change tracking + var clone = (File) base.DeepClone(); + + // clear fields that were memberwise-cloned and that we don't want to clone + clone._content = null; + + // turn off change tracking clone.DisableChangeTracking(); - //need to manually assign since they are readonly properties - clone._alias = Alias; - clone._name = Name; - //this shouldn't really be needed since we're not tracking + + // ... + DeepCloneNameAndAlias(clone); + + // this shouldn't really be needed since we're not tracking clone.ResetDirtyProperties(false); - //re-enable tracking + + // re-enable tracking clone.EnableChangeTracking(); return clone; diff --git a/src/Umbraco.Core/Models/IContentTypeBase.cs b/src/Umbraco.Core/Models/IContentTypeBase.cs index 89fca20b8e..80e62f50cf 100644 --- a/src/Umbraco.Core/Models/IContentTypeBase.cs +++ b/src/Umbraco.Core/Models/IContentTypeBase.cs @@ -54,10 +54,15 @@ namespace Umbraco.Core.Models PropertyGroupCollection PropertyGroups { get; set; } /// - /// Gets an enumerable list of Property Types aggregated for all groups + /// Gets all property types, across all property groups. /// IEnumerable PropertyTypes { get; } + /// + /// Gets or sets the property types that are not in a group. + /// + IEnumerable NoGroupPropertyTypes { get; set; } + /// /// Removes a PropertyType from the current ContentType /// diff --git a/src/Umbraco.Core/Models/IContentTypeComposition.cs b/src/Umbraco.Core/Models/IContentTypeComposition.cs index 8b5049f236..ab7e068fdd 100644 --- a/src/Umbraco.Core/Models/IContentTypeComposition.cs +++ b/src/Umbraco.Core/Models/IContentTypeComposition.cs @@ -8,17 +8,17 @@ namespace Umbraco.Core.Models public interface IContentTypeComposition : IContentTypeBase { /// - /// Gets a list of ContentTypes that make up a composition of PropertyGroups and PropertyTypes for the current ContentType + /// Gets or sets the content types that compose this content type. /// - IEnumerable ContentTypeComposition { get; } + IEnumerable ContentTypeComposition { get; set; } /// - /// Gets a list of objects from the composition + /// Gets the property groups for the entire composition. /// IEnumerable CompositionPropertyGroups { get; } /// - /// Gets a list of objects from the composition + /// Gets the property types for the entire composition. /// IEnumerable CompositionPropertyTypes { get; } diff --git a/src/Umbraco.Core/Models/IDictionaryItem.cs b/src/Umbraco.Core/Models/IDictionaryItem.cs index e10a80e831..ed88acfbec 100644 --- a/src/Umbraco.Core/Models/IDictionaryItem.cs +++ b/src/Umbraco.Core/Models/IDictionaryItem.cs @@ -12,7 +12,7 @@ namespace Umbraco.Core.Models /// Gets or Sets the Parent Id of the Dictionary Item /// [DataMember] - Guid ParentId { get; set; } + Guid? ParentId { get; set; } /// /// Gets or sets the Key for the Dictionary Item diff --git a/src/Umbraco.Core/Models/IDomain.cs b/src/Umbraco.Core/Models/IDomain.cs index 9fb3e8019d..b9f6bc65ec 100644 --- a/src/Umbraco.Core/Models/IDomain.cs +++ b/src/Umbraco.Core/Models/IDomain.cs @@ -4,9 +4,14 @@ namespace Umbraco.Core.Models { public interface IDomain : IAggregateRoot, IRememberBeingDirty, ICanBeDirty { - ILanguage Language { get; set; } + int? LanguageId { get; set; } string DomainName { get; set; } - IContent RootContent { get; set; } + int? RootContentId { get; set; } bool IsWildcard { get; } + + /// + /// Readonly value of the language ISO code for the domain + /// + string LanguageIsoCode { get; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/IMigrationEntry.cs b/src/Umbraco.Core/Models/IMigrationEntry.cs new file mode 100644 index 0000000000..65bb7bc1f3 --- /dev/null +++ b/src/Umbraco.Core/Models/IMigrationEntry.cs @@ -0,0 +1,12 @@ +using System; +using Semver; +using Umbraco.Core.Models.EntityBase; + +namespace Umbraco.Core.Models +{ + public interface IMigrationEntry : IAggregateRoot, IRememberBeingDirty + { + string MigrationName { get; set; } + SemVersion Version { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/IPublishedContentWithKey.cs b/src/Umbraco.Core/Models/IPublishedContentWithKey.cs new file mode 100644 index 0000000000..b0e71221b2 --- /dev/null +++ b/src/Umbraco.Core/Models/IPublishedContentWithKey.cs @@ -0,0 +1,14 @@ +using System; + +namespace Umbraco.Core.Models +{ + /// + /// Represents a cached content with a GUID key. + /// + /// This is temporary, because we cannot add the Key property to IPublishedContent without + /// breaking backward compatibility. With v8, it will be merged into IPublishedContent. + public interface IPublishedContentWithKey : IPublishedContent + { + Guid Key { get; } + } +} diff --git a/src/Umbraco.Core/Models/IServerRegistration.cs b/src/Umbraco.Core/Models/IServerRegistration.cs new file mode 100644 index 0000000000..8053ea0a6e --- /dev/null +++ b/src/Umbraco.Core/Models/IServerRegistration.cs @@ -0,0 +1,36 @@ +using System; +using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Sync; + +namespace Umbraco.Core.Models +{ + public interface IServerRegistration : IServerAddress, IAggregateRoot, IRememberBeingDirty + { + /// + /// Gets or sets the server unique identity. + /// + string ServerIdentity { get; set; } + + new string ServerAddress { get; set; } + + /// + /// Gets or sets a value indicating whether the server is active. + /// + bool IsActive { get; set; } + + /// + /// Gets or sets a value indicating whether the server is master. + /// + bool IsMaster { get; set; } + + /// + /// Gets the date and time the registration was created. + /// + DateTime Registered { get; set; } + + /// + /// Gets the date and time the registration was last accessed. + /// + DateTime Accessed { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs b/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs index 1523cf9040..0d803c26e5 100644 --- a/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs +++ b/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs @@ -12,6 +12,13 @@ namespace Umbraco.Core.Models.Identity public class BackOfficeIdentityUser : IdentityUser, IdentityUserClaim> { + public BackOfficeIdentityUser() + { + StartMediaId = -1; + StartContentId = -1; + Culture = Configuration.GlobalSettings.DefaultUILanguage; + } + public virtual async Task GenerateUserIdentityAsync(BackOfficeUserManager manager) { // NOTE the authenticationType must match the umbraco one @@ -31,6 +38,30 @@ namespace Umbraco.Core.Models.Identity public string UserTypeAlias { get; set; } + /// + /// Lockout is always enabled + /// + public override bool LockoutEnabled + { + get { return true; } + set + { + //do nothing + } + } + + /// + /// Based on the user's lockout end date, this will determine if they are locked out + /// + internal bool IsLockedOut + { + get + { + var isLocked = (LockoutEndDateUtc.HasValue && LockoutEndDateUtc.Value.ToLocalTime() >= DateTime.Now); + return isLocked; + } + } + /// /// Overridden to make the retrieval lazy /// diff --git a/src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs b/src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs index def71a8982..0dc95a8987 100644 --- a/src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs +++ b/src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs @@ -4,6 +4,7 @@ using AutoMapper; using Umbraco.Core.Models.Mapping; using Umbraco.Core.Models.Membership; +using Umbraco.Core.Security; namespace Umbraco.Core.Models.Identity { @@ -14,8 +15,7 @@ namespace Umbraco.Core.Models.Identity config.CreateMap() .ForMember(user => user.Email, expression => expression.MapFrom(user => user.Email)) .ForMember(user => user.Id, expression => expression.MapFrom(user => user.Id)) - .ForMember(user => user.LockoutEnabled, expression => expression.MapFrom(user => user.IsLockedOut)) - .ForMember(user => user.LockoutEndDateUtc, expression => expression.UseValue(DateTime.MaxValue.ToUniversalTime())) + .ForMember(user => user.LockoutEndDateUtc, expression => expression.MapFrom(user => user.IsLockedOut ? DateTime.MaxValue.ToUniversalTime() : (DateTime?)null)) .ForMember(user => user.UserName, expression => expression.MapFrom(user => user.Username)) .ForMember(user => user.PasswordHash, expression => expression.MapFrom(user => GetPasswordHash(user.RawPasswordValue))) .ForMember(user => user.Culture, expression => expression.MapFrom(user => user.GetUserCulture(applicationContext.Services.TextService))) @@ -23,7 +23,20 @@ namespace Umbraco.Core.Models.Identity .ForMember(user => user.StartMediaId, expression => expression.MapFrom(user => user.StartMediaId)) .ForMember(user => user.StartContentId, expression => expression.MapFrom(user => user.StartContentId)) .ForMember(user => user.UserTypeAlias, expression => expression.MapFrom(user => user.UserType.Alias)) + .ForMember(user => user.AccessFailedCount, expression => expression.MapFrom(user => user.FailedPasswordAttempts)) .ForMember(user => user.AllowedSections, expression => expression.MapFrom(user => user.AllowedSections.ToArray())); + + config.CreateMap() + .ConstructUsing((BackOfficeIdentityUser user) => new UserData(Guid.NewGuid().ToString("N"))) //this is the 'session id' + .ForMember(detail => detail.Id, opt => opt.MapFrom(user => user.Id)) + .ForMember(detail => detail.AllowedApplications, opt => opt.MapFrom(user => user.AllowedSections)) + .ForMember(detail => detail.RealName, opt => opt.MapFrom(user => user.Name)) + .ForMember(detail => detail.Roles, opt => opt.MapFrom(user => new[] { user.UserTypeAlias })) + .ForMember(detail => detail.StartContentNode, opt => opt.MapFrom(user => user.StartContentId)) + .ForMember(detail => detail.StartMediaNode, opt => opt.MapFrom(user => user.StartMediaId)) + .ForMember(detail => detail.Username, opt => opt.MapFrom(user => user.UserName)) + .ForMember(detail => detail.Culture, opt => opt.MapFrom(user => user.Culture)) + .ForMember(detail => detail.SessionId, opt => opt.MapFrom(user => user.SecurityStamp.IsNullOrWhiteSpace() ? Guid.NewGuid().ToString("N") : user.SecurityStamp)); } private string GetPasswordHash(string storedPass) diff --git a/src/Umbraco.Core/Models/Language.cs b/src/Umbraco.Core/Models/Language.cs index ef8ebbf931..b23bbfb52a 100644 --- a/src/Umbraco.Core/Models/Language.cs +++ b/src/Umbraco.Core/Models/Language.cs @@ -64,7 +64,7 @@ namespace Umbraco.Core.Models [IgnoreDataMember] public CultureInfo CultureInfo { - get { return CultureInfo.CreateSpecificCulture(IsoCode); } + get { return CultureInfo.GetCultureInfo(IsoCode); } } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Macro.cs b/src/Umbraco.Core/Models/Macro.cs index fcc23ed858..a15639f886 100644 --- a/src/Umbraco.Core/Models/Macro.cs +++ b/src/Umbraco.Core/Models/Macro.cs @@ -18,7 +18,7 @@ namespace Umbraco.Core.Models /// [Serializable] [DataContract(IsReference = true)] - internal class Macro : Entity, IMacro + public class Macro : Entity, IMacro { public Macro() { diff --git a/src/Umbraco.Core/Models/Mapping/MappingExpressionExtensions.cs b/src/Umbraco.Core/Models/Mapping/MappingExpressionExtensions.cs new file mode 100644 index 0000000000..570e51dbc3 --- /dev/null +++ b/src/Umbraco.Core/Models/Mapping/MappingExpressionExtensions.cs @@ -0,0 +1,20 @@ +using AutoMapper; + +namespace Umbraco.Core.Models.Mapping +{ + internal static class MappingExpressionExtensions + { + /// + /// Ignores all unmapped members by default - Use with caution! + /// + /// + /// + /// + /// + public static IMappingExpression IgnoreAllUnmapped(this IMappingExpression expression) + { + expression.ForAllMembers(opt => opt.Ignore()); + return expression; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Member.cs b/src/Umbraco.Core/Models/Member.cs index d416c846ef..7788fadf75 100644 --- a/src/Umbraco.Core/Models/Member.cs +++ b/src/Umbraco.Core/Models/Member.cs @@ -280,7 +280,7 @@ namespace Umbraco.Core.Models //This is the default value if the prop is not found true); if (a.Success == false) return a.Result; - + if (Properties[Constants.Conventions.Member.IsApproved].Value == null) return true; var tryConvert = Properties[Constants.Conventions.Member.IsApproved].Value.TryConvertTo(); if (tryConvert.Success) { @@ -313,7 +313,7 @@ namespace Umbraco.Core.Models { var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.IsLockedOut, "IsLockedOut", false); if (a.Success == false) return a.Result; - + if (Properties[Constants.Conventions.Member.IsLockedOut].Value == null) return false; var tryConvert = Properties[Constants.Conventions.Member.IsLockedOut].Value.TryConvertTo(); if (tryConvert.Success) { @@ -346,7 +346,7 @@ namespace Umbraco.Core.Models { var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.LastLoginDate, "LastLoginDate", default(DateTime)); if (a.Success == false) return a.Result; - + if (Properties[Constants.Conventions.Member.LastLoginDate].Value == null) return default(DateTime); var tryConvert = Properties[Constants.Conventions.Member.LastLoginDate].Value.TryConvertTo(); if (tryConvert.Success) { @@ -379,7 +379,7 @@ namespace Umbraco.Core.Models { var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.LastPasswordChangeDate, "LastPasswordChangeDate", default(DateTime)); if (a.Success == false) return a.Result; - + if (Properties[Constants.Conventions.Member.LastPasswordChangeDate].Value == null) return default(DateTime); var tryConvert = Properties[Constants.Conventions.Member.LastPasswordChangeDate].Value.TryConvertTo(); if (tryConvert.Success) { @@ -412,7 +412,7 @@ namespace Umbraco.Core.Models { var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.LastLockoutDate, "LastLockoutDate", default(DateTime)); if (a.Success == false) return a.Result; - + if (Properties[Constants.Conventions.Member.LastLockoutDate].Value == null) return default(DateTime); var tryConvert = Properties[Constants.Conventions.Member.LastLockoutDate].Value.TryConvertTo(); if (tryConvert.Success) { @@ -446,7 +446,7 @@ namespace Umbraco.Core.Models { var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.FailedPasswordAttempts, "FailedPasswordAttempts", 0); if (a.Success == false) return a.Result; - + if (Properties[Constants.Conventions.Member.FailedPasswordAttempts].Value == null) return default(int); var tryConvert = Properties[Constants.Conventions.Member.FailedPasswordAttempts].Value.TryConvertTo(); if (tryConvert.Success) { diff --git a/src/Umbraco.Core/Models/MigrationEntry.cs b/src/Umbraco.Core/Models/MigrationEntry.cs new file mode 100644 index 0000000000..e756e92629 --- /dev/null +++ b/src/Umbraco.Core/Models/MigrationEntry.cs @@ -0,0 +1,53 @@ +using System; +using System.Reflection; +using Semver; +using Umbraco.Core.Models.EntityBase; + +namespace Umbraco.Core.Models +{ + public class MigrationEntry : Entity, IMigrationEntry + { + public MigrationEntry() + { + } + + public MigrationEntry(int id, DateTime createDate, string migrationName, SemVersion version) + { + Id = id; + CreateDate = createDate; + _migrationName = migrationName; + _version = version; + } + + private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.MigrationName); + private static readonly PropertyInfo VersionSelector = ExpressionHelper.GetPropertyInfo(x => x.Version); + private string _migrationName; + private SemVersion _version; + + public string MigrationName + { + get { return _migrationName; } + set + { + SetPropertyValueAndDetectChanges(o => + { + _migrationName = value; + return _migrationName; + }, _migrationName, NameSelector); + } + } + + public SemVersion Version + { + get { return _version; } + set + { + SetPropertyValueAndDetectChanges(o => + { + _version = value; + return _version; + }, _version, VersionSelector); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/PartialView.cs b/src/Umbraco.Core/Models/PartialView.cs index d483de4176..75914820f0 100644 --- a/src/Umbraco.Core/Models/PartialView.cs +++ b/src/Umbraco.Core/Models/PartialView.cs @@ -1,9 +1,6 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Runtime.Serialization; -using System.Text.RegularExpressions; -using Umbraco.Core.IO; +using Umbraco.Core.Services; namespace Umbraco.Core.Models { @@ -15,12 +12,14 @@ namespace Umbraco.Core.Models [DataContract(IsReference = true)] public class PartialView : File, IPartialView { - public PartialView(string path) - : base(path) - { - base.Path = path; - } + : this(path, null) + { } + internal PartialView(string path, Func getFileContent) + : base(path, getFileContent) + { } + + internal PartialViewType ViewType { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/PartialViewType.cs b/src/Umbraco.Core/Models/PartialViewType.cs new file mode 100644 index 0000000000..2b45448271 --- /dev/null +++ b/src/Umbraco.Core/Models/PartialViewType.cs @@ -0,0 +1,9 @@ +namespace Umbraco.Core.Models +{ + internal enum PartialViewType : byte + { + Unknown = 0, // default + PartialView = 1, + PartialViewMacro = 2 + } +} diff --git a/src/Umbraco.Core/Models/Property.cs b/src/Umbraco.Core/Models/Property.cs index b618f84341..d7c2eb92a8 100644 --- a/src/Umbraco.Core/Models/Property.cs +++ b/src/Umbraco.Core/Models/Property.cs @@ -137,9 +137,23 @@ namespace Umbraco.Core.Models new DelegateEqualityComparer( (o, o1) => { - //Custom comparer for enumerable if it is enumerable if (o == null && o1 == null) return true; + + //custom comparer for strings. + if (o is string || o1 is string) + { + //if one is null and another is empty then they are the same + if ((o as string).IsNullOrWhiteSpace() && (o1 as string).IsNullOrWhiteSpace()) + { + return true; + } + if (o == null || o1 == null) return false; + return o.Equals(o1); + } + if (o == null || o1 == null) return false; + + //Custom comparer for enumerable if it is enumerable var enum1 = o as IEnumerable; var enum2 = o1 as IEnumerable; if (enum1 != null && enum2 != null) diff --git a/src/Umbraco.Core/Models/PropertyGroup.cs b/src/Umbraco.Core/Models/PropertyGroup.cs index 94e46e1ed1..de88012c0e 100644 --- a/src/Umbraco.Core/Models/PropertyGroup.cs +++ b/src/Umbraco.Core/Models/PropertyGroup.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Specialized; using System.Diagnostics; +using System.Linq; using System.Reflection; using System.Runtime.Serialization; using Umbraco.Core.Models.EntityBase; @@ -16,7 +17,6 @@ namespace Umbraco.Core.Models public class PropertyGroup : Entity, IEquatable { private string _name; - private Lazy _parentId; private int _sortOrder; private PropertyTypeCollection _propertyTypes; @@ -30,7 +30,6 @@ namespace Umbraco.Core.Models } private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); - private static readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentId); private static readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); private readonly static PropertyInfo PropertyTypeCollectionSelector = ExpressionHelper.GetPropertyInfo(x => x.PropertyTypes); void PropertyTypesChanged(object sender, NotifyCollectionChangedEventArgs e) @@ -55,29 +54,6 @@ namespace Umbraco.Core.Models } } - /// - /// Gets or sets the Id of the Parent PropertyGroup. - /// - /// - /// A Parent PropertyGroup corresponds to an inherited PropertyGroup from a composition. - /// If a PropertyType is inserted into an inherited group then a new group will be created with an Id reference to the parent. - /// - [DataMember] - public int? ParentId - { - get - { - if (_parentId == null) - return default(int?); - return _parentId.Value; - } - set - { - _parentId = new Lazy(() => value); - OnPropertyChanged(ParentIdSelector); - } - } - /// /// Gets or sets the Sort Order of the Group /// @@ -105,19 +81,17 @@ namespace Umbraco.Core.Models set { _propertyTypes = value; + + //since we're adding this collection to this group, we need to ensure that all the lazy values are set. + foreach (var propertyType in _propertyTypes) + { + propertyType.PropertyGroupId = new Lazy(() => this.Id); + } + _propertyTypes.CollectionChanged += PropertyTypesChanged; } } - /// - /// Sets the ParentId from the lazy integer id - /// - /// Id of the Parent - internal void SetLazyParentId(Lazy id) - { - _parentId = id; - } - public bool Equals(PropertyGroup other) { if (base.Equals(other)) return true; diff --git a/src/Umbraco.Core/Models/PropertyType.cs b/src/Umbraco.Core/Models/PropertyType.cs index e9d4451314..7f22b65c8c 100644 --- a/src/Umbraco.Core/Models/PropertyType.cs +++ b/src/Umbraco.Core/Models/PropertyType.cs @@ -9,6 +9,7 @@ using Umbraco.Core.Strings; namespace Umbraco.Core.Models { + /// /// Defines the type of a object /// @@ -32,6 +33,8 @@ namespace Umbraco.Core.Models public PropertyType(IDataTypeDefinition dataTypeDefinition) { + if (dataTypeDefinition == null) throw new ArgumentNullException("dataTypeDefinition"); + if(dataTypeDefinition.HasIdentity) _dataTypeDefinitionId = dataTypeDefinition.Id; @@ -401,6 +404,9 @@ namespace Umbraco.Core.Models if (DataTypeDatabaseType == DataTypeDatabaseType.Integer && type == typeof(int)) return true; + if (DataTypeDatabaseType == DataTypeDatabaseType.Decimal && type == typeof(decimal)) + return true; + if (DataTypeDatabaseType == DataTypeDatabaseType.Date && type == typeof(DateTime)) return true; diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentExtended.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentExtended.cs index 8c0eeef86f..fef066e0b1 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentExtended.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentExtended.cs @@ -62,9 +62,20 @@ namespace Umbraco.Core.Models.PublishedContent // model and therefore returned the original content unchanged. var model = content.CreateModel(); - var extended = model == content // == means the factory did not create a model - ? new PublishedContentExtended(content) // so we have to extend - : model; // else we can use what the factory returned + IPublishedContent extended; + if (model == content) // == means the factory did not create a model + { + // so we have to extend + var contentWithKey = content as IPublishedContentWithKey; + extended = contentWithKey == null + ? new PublishedContentExtended(content) + : new PublishedContentWithKeyExtended(contentWithKey); + } + else + { + // else we can use what the factory returned + extended = model; + } // so extended should always implement IPublishedContentExtended, however if // by mistake the factory returned a different object that does not implement diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWithKeyExtended.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWithKeyExtended.cs new file mode 100644 index 0000000000..492fd79796 --- /dev/null +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWithKeyExtended.cs @@ -0,0 +1,14 @@ +using System; + +namespace Umbraco.Core.Models.PublishedContent +{ + public class PublishedContentWithKeyExtended : PublishedContentExtended, IPublishedContentWithKey + { + // protected for models, internal for PublishedContentExtended static Extend method + protected internal PublishedContentWithKeyExtended(IPublishedContentWithKey content) + : base(content) + { } + + public Guid Key { get { return ((IPublishedContentWithKey) Content).Key; } } + } +} diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWithKeyModel.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWithKeyModel.cs new file mode 100644 index 0000000000..4761a52617 --- /dev/null +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWithKeyModel.cs @@ -0,0 +1,13 @@ +using System; + +namespace Umbraco.Core.Models.PublishedContent +{ + public abstract class PublishedContentWithKeyModel : PublishedContentModel, IPublishedContentWithKey + { + protected PublishedContentWithKeyModel(IPublishedContentWithKey content) + : base (content) + { } + + public Guid Key { get { return ((IPublishedContentWithKey) Content).Key; } } + } +} diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWithKeyWrapped.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWithKeyWrapped.cs new file mode 100644 index 0000000000..35d7dd6f1f --- /dev/null +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWithKeyWrapped.cs @@ -0,0 +1,17 @@ +using System; + +namespace Umbraco.Core.Models.PublishedContent +{ + /// + /// Provides an abstract base class for IPublishedContentWithKey implementations that + /// wrap and extend another IPublishedContentWithKey. + /// + public class PublishedContentWithKeyWrapped : PublishedContentWrapped, IPublishedContentWithKey + { + protected PublishedContentWithKeyWrapped(IPublishedContentWithKey content) + : base(content) + { } + + public virtual Guid Key { get { return ((IPublishedContentWithKey) Content).Key; } } + } +} diff --git a/src/Umbraco.Core/Models/Rdbms/AccessDto.cs b/src/Umbraco.Core/Models/Rdbms/AccessDto.cs index ba2cb6d767..37b1dbddd8 100644 --- a/src/Umbraco.Core/Models/Rdbms/AccessDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/AccessDto.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Security.AccessControl; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; namespace Umbraco.Core.Models.Rdbms { @@ -30,11 +31,11 @@ namespace Umbraco.Core.Models.Rdbms public int NoAccessNodeId { get; set; } [Column("createDate")] - [Constraint(Default = "getdate()")] + [Constraint(Default = SystemMethods.CurrentDateTime)] public DateTime CreateDate { get; set; } [Column("updateDate")] - [Constraint(Default = "getdate()")] + [Constraint(Default = SystemMethods.CurrentDateTime)] public DateTime UpdateDate { get; set; } [ResultColumn] diff --git a/src/Umbraco.Core/Models/Rdbms/AccessRuleDto.cs b/src/Umbraco.Core/Models/Rdbms/AccessRuleDto.cs index a98dfb9450..78e3444e56 100644 --- a/src/Umbraco.Core/Models/Rdbms/AccessRuleDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/AccessRuleDto.cs @@ -1,6 +1,7 @@ using System; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; namespace Umbraco.Core.Models.Rdbms { @@ -25,11 +26,11 @@ namespace Umbraco.Core.Models.Rdbms public string RuleType { get; set; } [Column("createDate")] - [Constraint(Default = "getdate()")] + [Constraint(Default = SystemMethods.CurrentDateTime)] public DateTime CreateDate { get; set; } [Column("updateDate")] - [Constraint(Default = "getdate()")] + [Constraint(Default = SystemMethods.CurrentDateTime)] public DateTime UpdateDate { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/DocumentTypeDto.cs b/src/Umbraco.Core/Models/Rdbms/ContentTypeTemplateDto.cs similarity index 93% rename from src/Umbraco.Core/Models/Rdbms/DocumentTypeDto.cs rename to src/Umbraco.Core/Models/Rdbms/ContentTypeTemplateDto.cs index e3fdfb9810..88ef02ea90 100644 --- a/src/Umbraco.Core/Models/Rdbms/DocumentTypeDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/ContentTypeTemplateDto.cs @@ -1,28 +1,28 @@ -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseAnnotations; - -namespace Umbraco.Core.Models.Rdbms -{ - [TableName("cmsDocumentType")] - [PrimaryKey("contentTypeNodeId", autoIncrement = false)] - [ExplicitColumns] - internal class DocumentTypeDto - { - [Column("contentTypeNodeId")] - [PrimaryKeyColumn(AutoIncrement = false, Name = "PK_cmsDocumentType", OnColumns = "contentTypeNodeId, templateNodeId")] - [ForeignKey(typeof(ContentTypeDto), Column = "nodeId")] - [ForeignKey(typeof(NodeDto))] - public int ContentTypeNodeId { get; set; } - - [Column("templateNodeId")] - [ForeignKey(typeof(TemplateDto), Column = "nodeId")] - public int TemplateNodeId { get; set; } - - [Column("IsDefault")] - [Constraint(Default = "0")] - public bool IsDefault { get; set; } - - [ResultColumn] - public ContentTypeDto ContentTypeDto { get; set; } - } +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.DatabaseAnnotations; + +namespace Umbraco.Core.Models.Rdbms +{ + [TableName("cmsDocumentType")] + [PrimaryKey("contentTypeNodeId", autoIncrement = false)] + [ExplicitColumns] + internal class ContentTypeTemplateDto + { + [Column("contentTypeNodeId")] + [PrimaryKeyColumn(AutoIncrement = false, Name = "PK_cmsDocumentType", OnColumns = "contentTypeNodeId, templateNodeId")] + [ForeignKey(typeof(ContentTypeDto), Column = "nodeId")] + [ForeignKey(typeof(NodeDto))] + public int ContentTypeNodeId { get; set; } + + [Column("templateNodeId")] + [ForeignKey(typeof(TemplateDto), Column = "nodeId")] + public int TemplateNodeId { get; set; } + + [Column("IsDefault")] + [Constraint(Default = "0")] + public bool IsDefault { get; set; } + + [ResultColumn] + public ContentTypeDto ContentTypeDto { get; set; } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/ContentVersionDto.cs b/src/Umbraco.Core/Models/Rdbms/ContentVersionDto.cs index 9b1fa02dae..3bd85cccf6 100644 --- a/src/Umbraco.Core/Models/Rdbms/ContentVersionDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/ContentVersionDto.cs @@ -1,6 +1,7 @@ using System; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; namespace Umbraco.Core.Models.Rdbms { @@ -23,7 +24,7 @@ namespace Umbraco.Core.Models.Rdbms public Guid VersionId { get; set; } [Column("VersionDate")] - [Constraint(Default = "getdate()")] + [Constraint(Default = SystemMethods.CurrentDateTime)] public DateTime VersionDate { get; set; } [ResultColumn] diff --git a/src/Umbraco.Core/Models/Rdbms/DictionaryDto.cs b/src/Umbraco.Core/Models/Rdbms/DictionaryDto.cs index 18fd16b658..712d1937a9 100644 --- a/src/Umbraco.Core/Models/Rdbms/DictionaryDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/DictionaryDto.cs @@ -19,7 +19,9 @@ namespace Umbraco.Core.Models.Rdbms public Guid UniqueId { get; set; } [Column("parent")] - public Guid Parent { get; set; } + [NullSetting(NullSetting = NullSettings.Null)] + [ForeignKey(typeof(DictionaryDto), Column = "id")] + public Guid? Parent { get; set; } [Column("key")] [Length(1000)] diff --git a/src/Umbraco.Core/Models/Rdbms/DocumentDto.cs b/src/Umbraco.Core/Models/Rdbms/DocumentDto.cs index de087667b2..88e04087c9 100644 --- a/src/Umbraco.Core/Models/Rdbms/DocumentDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/DocumentDto.cs @@ -1,6 +1,7 @@ using System; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; namespace Umbraco.Core.Models.Rdbms { @@ -38,7 +39,7 @@ namespace Umbraco.Core.Models.Rdbms public DateTime? ExpiresDate { get; set; } [Column("updateDate")] - [Constraint(Default = "getdate()")] + [Constraint(Default = SystemMethods.CurrentDateTime)] public DateTime UpdateDate { get; set; } [Column("templateId")] diff --git a/src/Umbraco.Core/Models/Rdbms/DomainDto.cs b/src/Umbraco.Core/Models/Rdbms/DomainDto.cs index bd0bbc859c..e43c1bdeae 100644 --- a/src/Umbraco.Core/Models/Rdbms/DomainDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/DomainDto.cs @@ -23,5 +23,11 @@ namespace Umbraco.Core.Models.Rdbms [Column("domainName")] public string DomainName { get; set; } + + /// + /// Used for a result on the query to get the associated language for a domain if there is one + /// + [ResultColumn("languageISOCode")] + public string IsoCode { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/ExternalLoginDto.cs b/src/Umbraco.Core/Models/Rdbms/ExternalLoginDto.cs index 652c9df714..803c25fdfc 100644 --- a/src/Umbraco.Core/Models/Rdbms/ExternalLoginDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/ExternalLoginDto.cs @@ -1,6 +1,7 @@ using System; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; namespace Umbraco.Core.Models.Rdbms { @@ -27,7 +28,7 @@ namespace Umbraco.Core.Models.Rdbms public string ProviderKey { get; set; } [Column("createDate")] - [Constraint(Default = "getdate()")] + [Constraint(Default = SystemMethods.CurrentDateTime)] public DateTime CreateDate { get; set; } diff --git a/src/Umbraco.Core/Models/Rdbms/LanguageTextDto.cs b/src/Umbraco.Core/Models/Rdbms/LanguageTextDto.cs index d062df4eae..87329fbd4c 100644 --- a/src/Umbraco.Core/Models/Rdbms/LanguageTextDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/LanguageTextDto.cs @@ -14,6 +14,7 @@ namespace Umbraco.Core.Models.Rdbms public int PrimaryKey { get; set; } [Column("languageId")] + [ForeignKey(typeof(LanguageDto), Column = "id")] public int LanguageId { get; set; } [Column("UniqueId")] diff --git a/src/Umbraco.Core/Models/Rdbms/LogDto.cs b/src/Umbraco.Core/Models/Rdbms/LogDto.cs index 3c2a6a61a5..be67b5873a 100644 --- a/src/Umbraco.Core/Models/Rdbms/LogDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/LogDto.cs @@ -1,6 +1,7 @@ using System; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; namespace Umbraco.Core.Models.Rdbms { @@ -21,7 +22,7 @@ namespace Umbraco.Core.Models.Rdbms public int NodeId { get; set; } [Column("Datestamp")] - [Constraint(Default = "getdate()")] + [Constraint(Default = SystemMethods.CurrentDateTime)] public DateTime Datestamp { get; set; } [Column("logHeader")] diff --git a/src/Umbraco.Core/Models/Rdbms/MigrationDto.cs b/src/Umbraco.Core/Models/Rdbms/MigrationDto.cs new file mode 100644 index 0000000000..1a76895e90 --- /dev/null +++ b/src/Umbraco.Core/Models/Rdbms/MigrationDto.cs @@ -0,0 +1,30 @@ +using System; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.DatabaseAnnotations; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; + +namespace Umbraco.Core.Models.Rdbms +{ + [TableName("umbracoMigration")] + [PrimaryKey("id")] + [ExplicitColumns] + internal class MigrationDto + { + [Column("id")] + [PrimaryKeyColumn(AutoIncrement = true, IdentitySeed = 100)] + public int Id { get; set; } + + [Column("name")] + [Length(255)] + [Index(IndexTypes.UniqueNonClustered, ForColumns = "name,version", Name = "IX_umbracoMigration")] + public string Name { get; set; } + + [Column("createDate")] + [Constraint(Default = SystemMethods.CurrentDateTime)] + public DateTime CreateDate { get; set; } + + [Column("version")] + [Length(50)] + public string Version { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/NodeDto.cs b/src/Umbraco.Core/Models/Rdbms/NodeDto.cs index 33c1a73059..7003c58e77 100644 --- a/src/Umbraco.Core/Models/Rdbms/NodeDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/NodeDto.cs @@ -1,6 +1,7 @@ using System; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; namespace Umbraco.Core.Models.Rdbms { @@ -9,6 +10,12 @@ namespace Umbraco.Core.Models.Rdbms [ExplicitColumns] internal class NodeDto { + public NodeDto() + { + //By default, always generate a new guid + UniqueId = Guid.NewGuid(); + } + public const int NodeIdSeed = 1050; [Column("id")] @@ -42,6 +49,7 @@ namespace Umbraco.Core.Models.Rdbms [Column("uniqueID")] [NullSetting(NullSetting = NullSettings.NotNull)] [Index(IndexTypes.UniqueNonClustered, Name = "IX_umbracoNodeUniqueID")] + [Constraint(Default = SystemMethods.NewGuid)] public Guid UniqueId { get; set; } [Column("text")] @@ -54,7 +62,7 @@ namespace Umbraco.Core.Models.Rdbms public Guid? NodeObjectType { get; set; } [Column("createDate")] - [Constraint(Default = "getdate()")] + [Constraint(Default = SystemMethods.CurrentDateTime)] public DateTime CreateDate { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/PropertyDataDto.cs b/src/Umbraco.Core/Models/Rdbms/PropertyDataDto.cs index 4e59f0275b..63e3104d12 100644 --- a/src/Umbraco.Core/Models/Rdbms/PropertyDataDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/PropertyDataDto.cs @@ -33,6 +33,10 @@ namespace Umbraco.Core.Models.Rdbms [NullSetting(NullSetting = NullSettings.Null)] public int? Integer { get; set; } + [Column("dataDecimal")] + [NullSetting(NullSetting = NullSettings.Null)] + public decimal? Decimal { get; set; } + [Column("dataDate")] [NullSetting(NullSetting = NullSettings.Null)] public DateTime? Date { get; set; } @@ -55,22 +59,27 @@ namespace Umbraco.Core.Models.Rdbms { get { - if(Integer.HasValue) + if (Integer.HasValue) { return Integer.Value; } + + if (Decimal.HasValue) + { + return Decimal.Value; + } - if(Date.HasValue) + if (Date.HasValue) { return Date.Value; } - if(string.IsNullOrEmpty(VarChar) == false) + if (string.IsNullOrEmpty(VarChar) == false) { return VarChar; } - if(string.IsNullOrEmpty(Text) == false) + if (string.IsNullOrEmpty(Text) == false) { return Text; } diff --git a/src/Umbraco.Core/Models/Rdbms/PropertyTypeDto.cs b/src/Umbraco.Core/Models/Rdbms/PropertyTypeDto.cs index f2ca705e8b..74a6d34289 100644 --- a/src/Umbraco.Core/Models/Rdbms/PropertyTypeDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/PropertyTypeDto.cs @@ -1,5 +1,7 @@ -using Umbraco.Core.Persistence; +using System; +using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; namespace Umbraco.Core.Models.Rdbms { @@ -8,6 +10,12 @@ namespace Umbraco.Core.Models.Rdbms [ExplicitColumns] internal class PropertyTypeDto { + public PropertyTypeDto() + { + //by default always create a new guid + UniqueId = Guid.NewGuid(); + } + [Column("id")] [PrimaryKeyColumn(IdentitySeed = 50)] public int Id { get; set; } @@ -51,5 +59,11 @@ namespace Umbraco.Core.Models.Rdbms [ResultColumn] public DataTypeDto DataTypeDto { get; set; } + + [Column("UniqueID")] + [NullSetting(NullSetting = NullSettings.NotNull)] + [Constraint(Default = SystemMethods.NewGuid)] + [Index(IndexTypes.UniqueNonClustered, Name = "IX_cmsPropertyTypeUniqueID")] + public Guid UniqueId { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/PropertyTypeGroupDto.cs b/src/Umbraco.Core/Models/Rdbms/PropertyTypeGroupDto.cs index 51cb9872fe..42abd9ed49 100644 --- a/src/Umbraco.Core/Models/Rdbms/PropertyTypeGroupDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/PropertyTypeGroupDto.cs @@ -1,6 +1,8 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; namespace Umbraco.Core.Models.Rdbms { @@ -9,16 +11,16 @@ namespace Umbraco.Core.Models.Rdbms [ExplicitColumns] internal class PropertyTypeGroupDto { + public PropertyTypeGroupDto() + { + //by default always create a new guid + UniqueId = Guid.NewGuid(); + } + [Column("id")] [PrimaryKeyColumn(IdentitySeed = 12)] public int Id { get; set; } - [Column("parentGroupId")] - [NullSetting(NullSetting = NullSettings.Null)] - //[Constraint(Default = "NULL")] - [ForeignKey(typeof(PropertyTypeGroupDto))] - public int? ParentGroupId { get; set; } - [Column("contenttypeNodeId")] [ForeignKey(typeof(ContentTypeDto), Column = "nodeId")] public int ContentTypeNodeId { get; set; } @@ -31,5 +33,11 @@ namespace Umbraco.Core.Models.Rdbms [ResultColumn] public List PropertyTypeDtos { get; set; } + + [Column("uniqueID")] + [NullSetting(NullSetting = NullSettings.NotNull)] + [Constraint(Default = SystemMethods.NewGuid)] + [Index(IndexTypes.UniqueNonClustered, Name = "IX_cmsPropertyTypeGroupUniqueID")] + public Guid UniqueId { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/PropertyTypeGroupReadOnlyDto.cs b/src/Umbraco.Core/Models/Rdbms/PropertyTypeGroupReadOnlyDto.cs index 8dcc4af29c..beebef9eeb 100644 --- a/src/Umbraco.Core/Models/Rdbms/PropertyTypeGroupReadOnlyDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/PropertyTypeGroupReadOnlyDto.cs @@ -10,9 +10,6 @@ namespace Umbraco.Core.Models.Rdbms [Column("PropertyTypeGroupId")] public int? Id { get; set; } - [Column("parentGroupId")] - public int? ParentGroupId { get; set; } - [Column("PropertyGroupName")] public string Text { get; set; } diff --git a/src/Umbraco.Core/Models/Rdbms/RelationDto.cs b/src/Umbraco.Core/Models/Rdbms/RelationDto.cs index e204e42040..368904a5cb 100644 --- a/src/Umbraco.Core/Models/Rdbms/RelationDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/RelationDto.cs @@ -1,6 +1,7 @@ using System; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; namespace Umbraco.Core.Models.Rdbms { @@ -26,7 +27,7 @@ namespace Umbraco.Core.Models.Rdbms public int RelationType { get; set; } [Column("datetime")] - [Constraint(Default = "getdate()")] + [Constraint(Default = SystemMethods.CurrentDateTime)] public DateTime Datetime { get; set; } [Column("comment")] diff --git a/src/Umbraco.Core/Models/Rdbms/ServerRegistrationDto.cs b/src/Umbraco.Core/Models/Rdbms/ServerRegistrationDto.cs index b7bdf265ce..2a3751c083 100644 --- a/src/Umbraco.Core/Models/Rdbms/ServerRegistrationDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/ServerRegistrationDto.cs @@ -1,6 +1,7 @@ using System; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; namespace Umbraco.Core.Models.Rdbms { @@ -23,7 +24,7 @@ namespace Umbraco.Core.Models.Rdbms public string ServerIdentity { get; set; } [Column("registeredDate")] - [Constraint(Default = "getdate()")] + [Constraint(Default = SystemMethods.CurrentDateTime)] public DateTime DateRegistered { get; set; } [Column("lastNotifiedDate")] @@ -33,6 +34,7 @@ namespace Umbraco.Core.Models.Rdbms [Index(IndexTypes.NonClustered)] public bool IsActive { get; set; } - + [Column("isMaster")] + public bool IsMaster { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/TaskDto.cs b/src/Umbraco.Core/Models/Rdbms/TaskDto.cs index 75e4d25cb7..e27f7c0a93 100644 --- a/src/Umbraco.Core/Models/Rdbms/TaskDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/TaskDto.cs @@ -1,6 +1,7 @@ using System; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; namespace Umbraco.Core.Models.Rdbms { @@ -34,7 +35,7 @@ namespace Umbraco.Core.Models.Rdbms public int UserId { get; set; } [Column("DateTime")] - [Constraint(Default = "getdate()")] + [Constraint(Default = SystemMethods.CurrentDateTime)] public DateTime DateTime { get; set; } [Column("Comment")] diff --git a/src/Umbraco.Core/Models/Rdbms/UmbracoDeployChecksumDto.cs b/src/Umbraco.Core/Models/Rdbms/UmbracoDeployChecksumDto.cs new file mode 100644 index 0000000000..06d904ee88 --- /dev/null +++ b/src/Umbraco.Core/Models/Rdbms/UmbracoDeployChecksumDto.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.DatabaseAnnotations; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; + +namespace Umbraco.Core.Models.Rdbms +{ + [TableName("umbracoDeployChecksum")] + [PrimaryKey("id")] + [ExplicitColumns] + internal class UmbracoDeployChecksumDto + { + [Column("id")] + [PrimaryKeyColumn(Name = "PK_umbracoDeployChecksum")] + public int Id { get; set; } + + [Column("entityType")] + [Length(32)] + [NullSetting(NullSetting = NullSettings.NotNull)] + [Index(IndexTypes.UniqueNonClustered, Name = "IX_umbracoDeployChecksum", ForColumns = "entityType,entityGuid,entityPath")] + public string EntityType { get; set; } + + [Column("entityGuid")] + [NullSetting(NullSetting = NullSettings.Null)] + public Guid EntityGuid { get; set; } + + [Column("entityPath")] + [Length(256)] + [NullSetting(NullSetting = NullSettings.Null)] + public string EntityPath { get; set; } + + [Column("localChecksum")] + [NullSetting(NullSetting = NullSettings.NotNull)] + [Length(32)] + public string LocalChecksum { get; set; } + + [Column("compositeChecksum")] + [NullSetting(NullSetting = NullSettings.Null)] + [Length(32)] + public string CompositeChecksum { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/UmbracoDeployDependencyDto.cs b/src/Umbraco.Core/Models/Rdbms/UmbracoDeployDependencyDto.cs new file mode 100644 index 0000000000..765a32c929 --- /dev/null +++ b/src/Umbraco.Core/Models/Rdbms/UmbracoDeployDependencyDto.cs @@ -0,0 +1,26 @@ +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.DatabaseAnnotations; + +namespace Umbraco.Core.Models.Rdbms +{ + [TableName("umbracoDeployDependency")] + [ExplicitColumns] + internal class UmbracoDeployDependencyDto + { + [Column("sourceId")] + [PrimaryKeyColumn(AutoIncrement = false, Clustered = true, Name = "PK_umbracoDeployDependency", OnColumns = "sourceId, targetId")] + [ForeignKey(typeof(UmbracoDeployChecksumDto), Name = "FK_umbracoDeployDependency_umbracoDeployChecksum_id1")] + [NullSetting(NullSetting = NullSettings.NotNull)] + public int SourceId { get; set; } + + [Column("targetId")] + [ForeignKey(typeof(UmbracoDeployChecksumDto), Name = "FK_umbracoDeployDependency_umbracoDeployChecksum_id2")] + [NullSetting(NullSetting = NullSettings.NotNull)] + public int TargetId { get; set; } + + [Column("mode")] + [NullSetting(NullSetting = NullSettings.NotNull)] + public int Mode { get; set; } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/UserLoginDto.cs b/src/Umbraco.Core/Models/Rdbms/UserLoginDto.cs deleted file mode 100644 index 6826377856..0000000000 --- a/src/Umbraco.Core/Models/Rdbms/UserLoginDto.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseAnnotations; - -namespace Umbraco.Core.Models.Rdbms -{ - [TableName("umbracoUserLogins")] - [ExplicitColumns] - internal class UserLoginDto - { - [Column("contextID")] - [Index(IndexTypes.Clustered, Name = "IX_umbracoUserLogins_Index")] - public Guid ContextId { get; set; } - - [Column("userID")] - public int UserId { get; set; } - - [Column("timeout")] - public long Timeout { get; set; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Script.cs b/src/Umbraco.Core/Models/Script.cs index 71f0684447..325885d9ba 100644 --- a/src/Umbraco.Core/Models/Script.cs +++ b/src/Umbraco.Core/Models/Script.cs @@ -1,7 +1,5 @@ using System; -using System.Linq; using System.Runtime.Serialization; -using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; @@ -15,16 +13,17 @@ namespace Umbraco.Core.Models public class Script : File { public Script(string path) - : base(path) - { - - } + : this(path, (Func) null) + { } + + internal Script(string path, Func getFileContent) + : base(path, getFileContent) + { } [Obsolete("This is no longer used and will be removed from the codebase in future versions")] public Script(string path, IContentSection contentConfig) - : this(path) - { - } + : base(path) + { } /// /// Indicates whether the current entity has an identity, which in this case is a path/name. diff --git a/src/Umbraco.Core/Models/ServerRegistration.cs b/src/Umbraco.Core/Models/ServerRegistration.cs index 900d6deb94..cee70893d0 100644 --- a/src/Umbraco.Core/Models/ServerRegistration.cs +++ b/src/Umbraco.Core/Models/ServerRegistration.cs @@ -2,22 +2,23 @@ using System.Globalization; using System.Reflection; using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Sync; namespace Umbraco.Core.Models { /// /// Represents a registered server in a multiple-servers environment. /// - public class ServerRegistration : Entity, IServerAddress, IAggregateRoot + public class ServerRegistration : Entity, IServerRegistration { private string _serverAddress; private string _serverIdentity; private bool _isActive; + private bool _isMaster; private static readonly PropertyInfo ServerAddressSelector = ExpressionHelper.GetPropertyInfo(x => x.ServerAddress); private static readonly PropertyInfo ServerIdentitySelector = ExpressionHelper.GetPropertyInfo(x => x.ServerIdentity); private static readonly PropertyInfo IsActiveSelector = ExpressionHelper.GetPropertyInfo(x => x.IsActive); + private static readonly PropertyInfo IsMasterSelector = ExpressionHelper.GetPropertyInfo(x => x.IsMaster); /// /// Initialiazes a new instance of the class. @@ -34,7 +35,8 @@ namespace Umbraco.Core.Models /// The date and time the registration was created. /// The date and time the registration was last accessed. /// A value indicating whether the registration is active. - public ServerRegistration(int id, string serverAddress, string serverIdentity, DateTime registered, DateTime accessed, bool isActive) + /// A value indicating whether the registration is master. + public ServerRegistration(int id, string serverAddress, string serverIdentity, DateTime registered, DateTime accessed, bool isActive, bool isMaster) { UpdateDate = accessed; CreateDate = registered; @@ -43,6 +45,7 @@ namespace Umbraco.Core.Models ServerAddress = serverAddress; ServerIdentity = serverIdentity; IsActive = isActive; + IsMaster = isMaster; } /// @@ -108,6 +111,22 @@ namespace Umbraco.Core.Models } } + /// + /// Gets or sets a value indicating whether the server is master. + /// + public bool IsMaster + { + get { return _isMaster; } + set + { + SetPropertyValueAndDetectChanges(o => + { + _isMaster = value; + return _isMaster; + }, _isMaster, IsMasterSelector); + } + } + /// /// Gets the date and time the registration was created. /// @@ -124,7 +143,7 @@ namespace Umbraco.Core.Models /// public override string ToString() { - return string.Format("{{\"{0}\", \"{1}\", {2}active}}", ServerAddress, ServerIdentity, IsActive ? "" : "!"); + return string.Format("{{\"{0}\", \"{1}\", {2}active, {3}master}}", ServerAddress, ServerIdentity, IsActive ? "" : "!", IsMaster ? "" : "!"); } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Stylesheet.cs b/src/Umbraco.Core/Models/Stylesheet.cs index 7381da0930..060246df54 100644 --- a/src/Umbraco.Core/Models/Stylesheet.cs +++ b/src/Umbraco.Core/Models/Stylesheet.cs @@ -1,9 +1,9 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Data; using System.Linq; using System.Runtime.Serialization; -using System.Text; using Umbraco.Core.IO; using Umbraco.Core.Strings.Css; @@ -16,12 +16,16 @@ namespace Umbraco.Core.Models [DataContract(IsReference = true)] public class Stylesheet : File { - public Stylesheet(string path) - : base(path) - { + public Stylesheet(string path) + : this(path, null) + { } + + internal Stylesheet(string path, Func getFileContent) + : base(path.EnsureEndsWith(".css"), getFileContent) + { InitializeProperties(); } - + private Lazy> _properties; private void InitializeProperties() @@ -33,11 +37,11 @@ namespace Umbraco.Core.Models //re-parse it so we can check what properties are different and adjust the event handlers var parsed = StylesheetHelper.ParseRules(Content).ToArray(); var names = parsed.Select(x => x.Name).ToArray(); - var existing = _properties.Value.Where(x => names.Contains(x.Name)).ToArray(); + var existing = _properties.Value.Where(x => names.InvariantContains(x.Name)).ToArray(); //update existing foreach (var stylesheetProperty in existing) { - var updateFrom = parsed.Single(x => x.Name == stylesheetProperty.Name); + var updateFrom = parsed.Single(x => x.Name.InvariantEquals(stylesheetProperty.Name)); //remove current event handler while we update, we'll reset it after stylesheetProperty.PropertyChanged -= Property_PropertyChanged; stylesheetProperty.Alias = updateFrom.Selector; @@ -46,14 +50,14 @@ namespace Umbraco.Core.Models stylesheetProperty.PropertyChanged += Property_PropertyChanged; } //remove no longer existing - var nonExisting = _properties.Value.Where(x => names.Contains(x.Name) == false).ToArray(); + var nonExisting = _properties.Value.Where(x => names.InvariantContains(x.Name) == false).ToArray(); foreach (var stylesheetProperty in nonExisting) { stylesheetProperty.PropertyChanged -= Property_PropertyChanged; _properties.Value.Remove(stylesheetProperty); } //add new ones - var newItems = parsed.Where(x => _properties.Value.Select(p => p.Name).Contains(x.Name) == false); + var newItems = parsed.Where(x => _properties.Value.Select(p => p.Name).InvariantContains(x.Name) == false); foreach (var stylesheetRule in newItems) { var prop = new StylesheetProperty(stylesheetRule.Name, stylesheetRule.Selector, stylesheetRule.Styles); @@ -81,7 +85,7 @@ namespace Umbraco.Core.Models /// /// /// - void Property_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) + void Property_PropertyChanged(object sender, PropertyChangedEventArgs e) { var prop = (StylesheetProperty) sender; @@ -128,7 +132,7 @@ namespace Umbraco.Core.Models /// public void AddProperty(StylesheetProperty property) { - if (Properties.Any(x => x.Name == property.Name)) + if (Properties.Any(x => x.Name.InvariantEquals(property.Name))) { throw new DuplicateNameException("The property with the name " + property.Name + " already exists in the collection"); } @@ -151,7 +155,7 @@ namespace Umbraco.Core.Models /// public void RemoveProperty(string name) { - if (Properties.Any(x => x.Name == name)) + if (Properties.Any(x => x.Name.InvariantEquals(name))) { Content = StylesheetHelper.ReplaceRule(Content, name, null); } diff --git a/src/Umbraco.Core/Models/Template.cs b/src/Umbraco.Core/Models/Template.cs index 3a8cbf8794..4aca88f286 100644 --- a/src/Umbraco.Core/Models/Template.cs +++ b/src/Umbraco.Core/Models/Template.cs @@ -1,7 +1,10 @@ using System; using System.Collections.Generic; +using System.Drawing; +using System.IO; using System.Reflection; using System.Runtime.Serialization; +using System.Text; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; @@ -29,12 +32,16 @@ namespace Umbraco.Core.Models private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); public Template(string name, string alias) - : base(string.Empty) + : this(name, alias, (Func) null) + { } + + internal Template(string name, string alias, Func getFileContent) + : base(string.Empty, getFileContent) { _name = name; _alias = alias.ToCleanString(CleanStringType.UnderscoreAlias); _masterTemplateId = new Lazy(() => -1); - } + } [Obsolete("This constructor should not be used, file path is determined by alias, setting the path here will have no affect")] public Template(string path, string name, string alias) @@ -123,7 +130,6 @@ namespace Umbraco.Core.Models Key = Guid.NewGuid(); } - public void SetMasterTemplate(ITemplate masterTemplate) { if (masterTemplate == null) @@ -139,27 +145,9 @@ namespace Umbraco.Core.Models } - public override object DeepClone() + protected override void DeepCloneNameAndAlias(File clone) { - - //We cannot call in to the base classes to clone because the base File class treats Alias, Name.. differently so we need to manually do the clone - - //Memberwise clone on Entity will work since it doesn't have any deep elements - // for any sub class this will work for standard properties as well that aren't complex object's themselves. - var clone = (Template)MemberwiseClone(); - //turn off change tracking - clone.DisableChangeTracking(); - //Automatically deep clone ref properties that are IDeepCloneable - DeepCloneHelper.DeepCloneRefProperties(this, clone); - - //this shouldn't really be needed since we're not tracking - clone.ResetDirtyProperties(false); - //re-enable tracking - clone.EnableChangeTracking(); - - return clone; + // do nothing - prevents File from doing its stuff } - - } } diff --git a/src/Umbraco.Core/Models/UmbracoDomain.cs b/src/Umbraco.Core/Models/UmbracoDomain.cs index 963535729c..943f96c9f9 100644 --- a/src/Umbraco.Core/Models/UmbracoDomain.cs +++ b/src/Umbraco.Core/Models/UmbracoDomain.cs @@ -5,8 +5,6 @@ using Umbraco.Core.Models.EntityBase; namespace Umbraco.Core.Models { - //TODO: Need to custom serialize this - [Serializable] [DataContract(IsReference = true)] public class UmbracoDomain : Entity, IDomain @@ -16,25 +14,32 @@ namespace Umbraco.Core.Models _domainName = domainName; } - private IContent _content; - private ILanguage _language; + public UmbracoDomain(string domainName, string languageIsoCode) + : this(domainName) + { + LanguageIsoCode = languageIsoCode; + } + + private int? _contentId; + private int? _languageId; private string _domainName; - private static readonly PropertyInfo DefaultLanguageSelector = ExpressionHelper.GetPropertyInfo(x => x.Language); + private static readonly PropertyInfo ContentSelector = ExpressionHelper.GetPropertyInfo(x => x.RootContentId); + private static readonly PropertyInfo DefaultLanguageSelector = ExpressionHelper.GetPropertyInfo(x => x.LanguageId); private static readonly PropertyInfo DomainNameSelector = ExpressionHelper.GetPropertyInfo(x => x.DomainName); - private static readonly PropertyInfo ContentSelector = ExpressionHelper.GetPropertyInfo(x => x.RootContent); + [DataMember] - public ILanguage Language + public int? LanguageId { - get { return _language; } + get { return _languageId; } set { SetPropertyValueAndDetectChanges(o => { - _language = value; - return _language; - }, _language, DefaultLanguageSelector); + _languageId = value; + return _languageId; + }, _languageId, DefaultLanguageSelector); } } @@ -53,16 +58,16 @@ namespace Umbraco.Core.Models } [DataMember] - public IContent RootContent + public int? RootContentId { - get { return _content; } + get { return _contentId; } set { SetPropertyValueAndDetectChanges(o => { - _content = value; - return _content; - }, _content, ContentSelector); + _contentId = value; + return _contentId; + }, _contentId, ContentSelector); } } @@ -70,5 +75,10 @@ namespace Umbraco.Core.Models { get { return string.IsNullOrWhiteSpace(DomainName) || DomainName.StartsWith("*"); } } + + /// + /// Readonly value of the language ISO code for the domain + /// + public string LanguageIsoCode { get; internal set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/UmbracoEntity.cs b/src/Umbraco.Core/Models/UmbracoEntity.cs index 4975ad3bd2..70ef056068 100644 --- a/src/Umbraco.Core/Models/UmbracoEntity.cs +++ b/src/Umbraco.Core/Models/UmbracoEntity.cs @@ -11,7 +11,7 @@ namespace Umbraco.Core.Models /// /// Implementation of the for internal use. /// - internal class UmbracoEntity : Entity, IUmbracoEntity + public class UmbracoEntity : Entity, IUmbracoEntity { private int _creatorId; private int _level; diff --git a/src/Umbraco.Core/Models/UmbracoEntityExtensions.cs b/src/Umbraco.Core/Models/UmbracoEntityExtensions.cs index edcc25ade8..987be2a276 100644 --- a/src/Umbraco.Core/Models/UmbracoEntityExtensions.cs +++ b/src/Umbraco.Core/Models/UmbracoEntityExtensions.cs @@ -11,6 +11,20 @@ namespace Umbraco.Core.Models internal static class UmbracoEntityExtensions { + public static bool HasChildren(this IUmbracoEntity entity) + { + if (entity.AdditionalData.ContainsKey("HasChildren")) + { + var convert = entity.AdditionalData["HasChildren"].TryConvertTo(); + if (convert) + { + return convert.Result; + } + } + return false; + } + + public static object GetAdditionalDataValueIgnoreCase(this IUmbracoEntity entity, string key, object defaultVal) { if (entity.AdditionalData.ContainsKeyIgnoreCase(key) == false) return defaultVal; diff --git a/src/Umbraco.Core/Models/UmbracoObjectTypes.cs b/src/Umbraco.Core/Models/UmbracoObjectTypes.cs index 37ad181682..2be2010301 100644 --- a/src/Umbraco.Core/Models/UmbracoObjectTypes.cs +++ b/src/Umbraco.Core/Models/UmbracoObjectTypes.cs @@ -15,49 +15,49 @@ namespace Umbraco.Core.Models /// /// Content Item Type /// - [UmbracoObjectTypeAttribute(Constants.ObjectTypes.ContentItemType)] + [UmbracoObjectType(Constants.ObjectTypes.ContentItemType)] [FriendlyName("Content Item Type")] ContentItemType, /// /// Root /// - [UmbracoObjectTypeAttribute(Constants.ObjectTypes.SystemRoot)] + [UmbracoObjectType(Constants.ObjectTypes.SystemRoot)] [FriendlyName("Root")] ROOT, /// /// Document /// - [UmbracoObjectTypeAttribute(Constants.ObjectTypes.Document, typeof(IContent))] + [UmbracoObjectType(Constants.ObjectTypes.Document, typeof(IContent))] [FriendlyName("Document")] Document, /// /// Media /// - [UmbracoObjectTypeAttribute(Constants.ObjectTypes.Media, typeof(IMedia))] + [UmbracoObjectType(Constants.ObjectTypes.Media, typeof(IMedia))] [FriendlyName("Media")] Media, /// /// Member Type /// - [UmbracoObjectTypeAttribute(Constants.ObjectTypes.MemberType, typeof(IMemberType))] + [UmbracoObjectType(Constants.ObjectTypes.MemberType, typeof(IMemberType))] [FriendlyName("Member Type")] MemberType, /// /// Template /// - [UmbracoObjectTypeAttribute(Constants.ObjectTypes.Template, typeof(ITemplate))] + [UmbracoObjectType(Constants.ObjectTypes.Template, typeof(ITemplate))] [FriendlyName("Template")] Template, /// /// Member Group /// - [UmbracoObjectTypeAttribute(Constants.ObjectTypes.MemberGroup)] + [UmbracoObjectType(Constants.ObjectTypes.MemberGroup)] [FriendlyName("Member Group")] MemberGroup, @@ -65,50 +65,73 @@ namespace Umbraco.Core.Models /// /// Content Item /// - [UmbracoObjectTypeAttribute(Constants.ObjectTypes.ContentItem)] + [UmbracoObjectType(Constants.ObjectTypes.ContentItem)] [FriendlyName("Content Item")] ContentItem, /// /// "Media Type /// - [UmbracoObjectTypeAttribute(Constants.ObjectTypes.MediaType, typeof(IMediaType))] + [UmbracoObjectType(Constants.ObjectTypes.MediaType, typeof(IMediaType))] [FriendlyName("Media Type")] MediaType, /// /// Document Type /// - [UmbracoObjectTypeAttribute(Constants.ObjectTypes.DocumentType, typeof(IContentType))] + [UmbracoObjectType(Constants.ObjectTypes.DocumentType, typeof(IContentType))] [FriendlyName("Document Type")] DocumentType, /// /// Recycle Bin /// - [UmbracoObjectTypeAttribute(Constants.ObjectTypes.ContentRecycleBin)] + [UmbracoObjectType(Constants.ObjectTypes.ContentRecycleBin)] [FriendlyName("Recycle Bin")] RecycleBin, /// /// Stylesheet /// - [UmbracoObjectTypeAttribute(Constants.ObjectTypes.Stylesheet)] + [UmbracoObjectType(Constants.ObjectTypes.Stylesheet)] [FriendlyName("Stylesheet")] Stylesheet, /// /// Member /// - [UmbracoObjectTypeAttribute(Constants.ObjectTypes.Member, typeof(IMember))] + [UmbracoObjectType(Constants.ObjectTypes.Member, typeof(IMember))] [FriendlyName("Member")] Member, /// /// Data Type /// - [UmbracoObjectTypeAttribute(Constants.ObjectTypes.DataType, typeof(IDataTypeDefinition))] + [UmbracoObjectType(Constants.ObjectTypes.DataType, typeof(IDataTypeDefinition))] [FriendlyName("Data Type")] - DataType + DataType, + + /// + /// Document type container + /// + [UmbracoObjectType(Constants.ObjectTypes.DocumentTypeContainer)] + [FriendlyName("Document Type Container")] + DocumentTypeContainer, + + /// + /// Media type container + /// + [UmbracoObjectType(Constants.ObjectTypes.MediaTypeContainer)] + [FriendlyName("Media Type Container")] + MediaTypeContainer, + + /// + /// Media type container + /// + [UmbracoObjectType(Constants.ObjectTypes.DataTypeContainer)] + [FriendlyName("Data Type Container")] + DataTypeContainer + + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/UserExtensions.cs b/src/Umbraco.Core/Models/UserExtensions.cs index bd670f3836..5b9f63cf48 100644 --- a/src/Umbraco.Core/Models/UserExtensions.cs +++ b/src/Umbraco.Core/Models/UserExtensions.cs @@ -2,6 +2,7 @@ using System.Globalization; using System.Linq; using System.Threading; +using Umbraco.Core.Models.Identity; using Umbraco.Core.Models.Membership; using Umbraco.Core.Services; diff --git a/src/Umbraco.Core/ObjectResolution/Resolution.cs b/src/Umbraco.Core/ObjectResolution/Resolution.cs index 87eb06e295..0b478d54cf 100644 --- a/src/Umbraco.Core/ObjectResolution/Resolution.cs +++ b/src/Umbraco.Core/ObjectResolution/Resolution.cs @@ -112,7 +112,7 @@ namespace Umbraco.Core.ObjectResolution /// resolution is already frozen. public static void Freeze() { - LogHelper.Debug(typeof(Resolution), "Freezing resolution"); + LogHelper.Debug(typeof (Resolution), "Freezing resolution"); using (new WriteLock(ConfigurationLock)) { @@ -121,9 +121,20 @@ namespace Umbraco.Core.ObjectResolution _isFrozen = true; } - - if (Frozen != null) - Frozen(null, null); + + LogHelper.Debug(typeof(Resolution), "Resolution is frozen"); + + if (Frozen == null) return; + + try + { + Frozen(null, null); + } + catch (Exception e) + { + LogHelper.Error(typeof (Resolution), "Exception in Frozen event handler.", e); + throw; + } } /// diff --git a/src/Umbraco.Core/Persistence/DatabaseAnnotations/ConstraintAttribute.cs b/src/Umbraco.Core/Persistence/DatabaseAnnotations/ConstraintAttribute.cs index 599f599f85..33c7616687 100644 --- a/src/Umbraco.Core/Persistence/DatabaseAnnotations/ConstraintAttribute.cs +++ b/src/Umbraco.Core/Persistence/DatabaseAnnotations/ConstraintAttribute.cs @@ -20,6 +20,6 @@ namespace Umbraco.Core.Persistence.DatabaseAnnotations /// /// Gets or sets the Default value /// - public string Default { get; set; } + public object Default { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/SystemMethods.cs b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/SystemMethods.cs index b7cacf0899..24c2294b59 100644 --- a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/SystemMethods.cs +++ b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/SystemMethods.cs @@ -3,8 +3,8 @@ namespace Umbraco.Core.Persistence.DatabaseModelDefinitions public enum SystemMethods { NewGuid, - NewSequentialId, CurrentDateTime, - CurrentUTCDateTime + //NewSequentialId, + //CurrentUTCDateTime } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/DatabaseSchemaHelper.cs b/src/Umbraco.Core/Persistence/DatabaseSchemaHelper.cs index 12fd437b96..418e5b953d 100644 --- a/src/Umbraco.Core/Persistence/DatabaseSchemaHelper.cs +++ b/src/Umbraco.Core/Persistence/DatabaseSchemaHelper.cs @@ -1,10 +1,12 @@ using System; using System.Linq; +using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Migrations.Initial; using Umbraco.Core.Persistence.SqlSyntax; +using Umbraco.Core.Services; namespace Umbraco.Core.Persistence { @@ -37,13 +39,41 @@ namespace Umbraco.Core.Persistence /// /// Creates the Umbraco db schema in the Database of the current Database. + /// Safe method that is only able to create the schema in non-configured + /// umbraco instances. /// - public void CreateDatabaseSchema() + public void CreateDatabaseSchema(ApplicationContext applicationContext) { - CreateDatabaseSchemaDo(); + if (applicationContext == null) throw new ArgumentNullException("applicationContext"); + CreateDatabaseSchema(true, applicationContext); } - private void CreateDatabaseSchemaDo() + /// + /// Creates the Umbraco db schema in the Database of the current Database + /// with the option to guard the db from having the schema created + /// multiple times. + /// + /// + /// + public void CreateDatabaseSchema(bool guardConfiguration, ApplicationContext applicationContext) + { + if (applicationContext == null) throw new ArgumentNullException("applicationContext"); + + if (guardConfiguration && applicationContext.IsConfigured) + throw new Exception("Umbraco is already configured!"); + + CreateDatabaseSchemaDo(applicationContext.Services.MigrationEntryService); + } + + internal void CreateDatabaseSchemaDo(bool guardConfiguration, ApplicationContext applicationContext) + { + if (guardConfiguration && applicationContext.IsConfigured) + throw new Exception("Umbraco is already configured!"); + + CreateDatabaseSchemaDo(applicationContext.Services.MigrationEntryService); + } + + internal void CreateDatabaseSchemaDo(IMigrationEntryService migrationEntryService) { _logger.Info("Initializing database schema creation"); @@ -118,6 +148,13 @@ namespace Umbraco.Core.Persistence _db.Update("SET id = @IdAfter WHERE id = @IdBefore AND userLogin = @Login", new { IdAfter = 0, IdBefore = 1, Login = "admin" }); } + //Loop through index statements and execute sql + foreach (var sql in indexSql) + { + int createdIndex = _db.Execute(new Sql(sql)); + _logger.Info(string.Format("Create Index sql {0}:\n {1}", createdIndex, sql)); + } + //Loop through foreignkey statements and execute sql foreach (var sql in foreignSql) { @@ -125,12 +162,7 @@ namespace Umbraco.Core.Persistence _logger.Info(string.Format("Create Foreign Key sql {0}:\n {1}", createdFk, sql)); } - //Loop through index statements and execute sql - foreach (var sql in indexSql) - { - int createdIndex = _db.Execute(new Sql(sql)); - _logger.Info(string.Format("Create Index sql {0}:\n {1}", createdIndex, sql)); - } + transaction.Complete(); } diff --git a/src/Umbraco.Core/Persistence/DatabasenodeLockExtensions.cs b/src/Umbraco.Core/Persistence/DatabasenodeLockExtensions.cs new file mode 100644 index 0000000000..3e6d245416 --- /dev/null +++ b/src/Umbraco.Core/Persistence/DatabasenodeLockExtensions.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Runtime.CompilerServices; + +namespace Umbraco.Core.Persistence +{ + internal static class DatabaseNodeLockExtensions + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void ValidateDatabase(UmbracoDatabase database) + { + if (database == null) + throw new ArgumentNullException("database"); + if (database.CurrentTransactionIsolationLevel < IsolationLevel.RepeatableRead) + throw new InvalidOperationException("A transaction with minimum RepeatableRead isolation level is required."); + } + + // updating a record within a repeatable-read transaction gets an exclusive lock on + // that record which will be kept until the transaction is ended, effectively locking + // out all other accesses to that record - thus obtaining an exclusive lock over the + // protected resources. + public static void AcquireLockNodeWriteLock(this UmbracoDatabase database, int nodeId) + { + ValidateDatabase(database); + + database.Execute("UPDATE umbracoNode SET sortOrder = (CASE WHEN (sortOrder=1) THEN -1 ELSE 1 END) WHERE id=@id", + new { @id = nodeId }); + } + + // reading a record within a repeatable-read transaction gets a shared lock on + // that record which will be kept until the transaction is ended, effectively preventing + // other write accesses to that record - thus obtaining a shared lock over the protected + // resources. + public static void AcquireLockNodeReadLock(this UmbracoDatabase database, int nodeId) + { + ValidateDatabase(database); + + database.ExecuteScalar("SELECT sortOrder FROM umbracoNode WHERE id=@id", + new { @id = nodeId }); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/DbConnectionExtensions.cs b/src/Umbraco.Core/Persistence/DbConnectionExtensions.cs index c4ca875eb2..5145ec95c5 100644 --- a/src/Umbraco.Core/Persistence/DbConnectionExtensions.cs +++ b/src/Umbraco.Core/Persistence/DbConnectionExtensions.cs @@ -7,6 +7,7 @@ using System.Data.SqlClient; using System.Linq; using System.Text; using System.Threading.Tasks; +using Umbraco.Core.Logging; namespace Umbraco.Core.Persistence { @@ -75,8 +76,10 @@ namespace Umbraco.Core.Persistence connection.Open(); connection.Close(); } - catch (DbException) + catch (DbException exc) { + // Don't swallow this error, the exception is super handy for knowing "why" its not available + LogHelper.WarnWithException("Configured database is reporting as not being available!", exc); return false; } diff --git a/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs b/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs index fdd2759d76..54c7d8d2c9 100644 --- a/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs @@ -1,98 +1,144 @@ using System; +using System.Collections.Generic; using System.Globalization; +using System.Linq; using Umbraco.Core.Models; +using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models.Membership; using Umbraco.Core.Models.Rdbms; namespace Umbraco.Core.Persistence.Factories { + // factory for + // IContentType (document types) + // IMediaType (media types) + // IMemberType (member types) + // internal class ContentTypeFactory { - private readonly Guid _nodeObjectType; + #region IContentType - public ContentTypeFactory(Guid nodeObjectType) + public IContentType BuildContentTypeEntity(ContentTypeDto dto) { - _nodeObjectType = nodeObjectType; - } + var contentType = new ContentType(dto.NodeDto.ParentId); + BuildCommonEntity(contentType, dto); - #region Implementation of IEntityFactory - - public IContentType BuildEntity(DocumentTypeDto dto) - { - var contentType = new ContentType(dto.ContentTypeDto.NodeDto.ParentId) - { - Id = dto.ContentTypeDto.NodeDto.NodeId, - Key = dto.ContentTypeDto.NodeDto.UniqueId, - Alias = dto.ContentTypeDto.Alias, - Name = dto.ContentTypeDto.NodeDto.Text, - Icon = dto.ContentTypeDto.Icon, - Thumbnail = dto.ContentTypeDto.Thumbnail, - SortOrder = dto.ContentTypeDto.NodeDto.SortOrder, - Description = dto.ContentTypeDto.Description, - CreateDate = dto.ContentTypeDto.NodeDto.CreateDate, - Path = dto.ContentTypeDto.NodeDto.Path, - Level = dto.ContentTypeDto.NodeDto.Level, - CreatorId = dto.ContentTypeDto.NodeDto.UserId.Value, - AllowedAsRoot = dto.ContentTypeDto.AllowAtRoot, - IsContainer = dto.ContentTypeDto.IsContainer, - Trashed = dto.ContentTypeDto.NodeDto.Trashed, - DefaultTemplateId = dto.TemplateNodeId - }; //on initial construction we don't want to have dirty properties tracked // http://issues.umbraco.org/issue/U4-1946 contentType.ResetDirtyProperties(false); - return contentType; - } - public DocumentTypeDto BuildDto(IContentType entity) - { - var documentTypeDto = new DocumentTypeDto - {ContentTypeDto = BuildContentTypeDto(entity), ContentTypeNodeId = entity.Id}; - - var contentType = entity as ContentType; - if(contentType != null) - { - documentTypeDto.TemplateNodeId = contentType.DefaultTemplateId; - documentTypeDto.IsDefault = true; - } - return documentTypeDto; + return contentType; } #endregion - private ContentTypeDto BuildContentTypeDto(IContentType entity) + #region IMediaType + + public IMediaType BuildMediaTypeEntity(ContentTypeDto dto) { + var contentType = new MediaType(dto.NodeDto.ParentId); + BuildCommonEntity(contentType, dto); + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + contentType.ResetDirtyProperties(false); + + return contentType; + } + + #endregion + + #region IMemberType + + public IMemberType BuildMemberTypeEntity(ContentTypeDto dto) + { + throw new NotImplementedException(); + } + + public IEnumerable BuildMemberTypeDtos(IMemberType entity) + { + var memberType = entity as MemberType; + if (memberType == null || memberType.PropertyTypes.Any() == false) + return Enumerable.Empty(); + + var dtos = memberType.PropertyTypes.Select(x => new MemberTypeDto + { + NodeId = entity.Id, + PropertyTypeId = x.Id, + CanEdit = memberType.MemberCanEditProperty(x.Alias), + ViewOnProfile = memberType.MemberCanViewProperty(x.Alias) + }).ToList(); + return dtos; + } + + #endregion + + #region Common + + private static void BuildCommonEntity(ContentTypeBase entity, ContentTypeDto dto) + { + entity.Id = dto.NodeDto.NodeId; + entity.Key = dto.NodeDto.UniqueId; + entity.Alias = dto.Alias; + entity.Name = dto.NodeDto.Text; + entity.Icon = dto.Icon; + entity.Thumbnail = dto.Thumbnail; + entity.SortOrder = dto.NodeDto.SortOrder; + entity.Description = dto.Description; + entity.CreateDate = dto.NodeDto.CreateDate; + entity.Path = dto.NodeDto.Path; + entity.Level = dto.NodeDto.Level; + entity.CreatorId = dto.NodeDto.UserId.Value; + entity.AllowedAsRoot = dto.AllowAtRoot; + entity.IsContainer = dto.IsContainer; + entity.Trashed = dto.NodeDto.Trashed; + } + + public ContentTypeDto BuildContentTypeDto(IContentTypeBase entity) + { + Guid nodeObjectType; + if (entity is IContentType) + nodeObjectType = Constants.ObjectTypes.DocumentTypeGuid; + else if (entity is IMediaType) + nodeObjectType = Constants.ObjectTypes.MediaTypeGuid; + else if (entity is IMemberType) + nodeObjectType = Constants.ObjectTypes.MemberTypeGuid; + else + throw new Exception("oops: invalid entity."); + var contentTypeDto = new ContentTypeDto - { - Alias = entity.Alias, - Description = entity.Description, - Icon = entity.Icon, - Thumbnail = entity.Thumbnail, - NodeId = entity.Id, - AllowAtRoot = entity.AllowedAsRoot, - IsContainer = entity.IsContainer, - NodeDto = BuildNodeDto(entity) - }; + { + Alias = entity.Alias, + Description = entity.Description, + Icon = entity.Icon, + Thumbnail = entity.Thumbnail, + NodeId = entity.Id, + AllowAtRoot = entity.AllowedAsRoot, + IsContainer = entity.IsContainer, + NodeDto = BuildNodeDto(entity, nodeObjectType) + }; return contentTypeDto; } - private NodeDto BuildNodeDto(IContentType entity) + private static NodeDto BuildNodeDto(IUmbracoEntity entity, Guid nodeObjectType) { var nodeDto = new NodeDto - { - CreateDate = entity.CreateDate, - NodeId = entity.Id, - Level = short.Parse(entity.Level.ToString(CultureInfo.InvariantCulture)), - NodeObjectType = _nodeObjectType, - ParentId = entity.ParentId, - Path = entity.Path, - SortOrder = entity.SortOrder, - Text = entity.Name, - Trashed = false, - UniqueId = entity.Key, - UserId = entity.CreatorId - }; + { + CreateDate = entity.CreateDate, + NodeId = entity.Id, + Level = short.Parse(entity.Level.ToString(CultureInfo.InvariantCulture)), + NodeObjectType = nodeObjectType, + ParentId = entity.ParentId, + Path = entity.Path, + SortOrder = entity.SortOrder, + Text = entity.Name, + Trashed = false, + UniqueId = entity.Key, + UserId = entity.CreatorId + }; return nodeDto; } + + #endregion } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Factories/DataTypeDefinitionFactory.cs b/src/Umbraco.Core/Persistence/Factories/DataTypeDefinitionFactory.cs index 6fd6500552..377d70e3cb 100644 --- a/src/Umbraco.Core/Persistence/Factories/DataTypeDefinitionFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/DataTypeDefinitionFactory.cs @@ -19,7 +19,7 @@ namespace Umbraco.Core.Persistence.Factories public IDataTypeDefinition BuildEntity(DataTypeDto dto) { - var dataTypeDefinition = new DataTypeDefinition(dto.NodeDto.ParentId, dto.PropertyEditorAlias) + var dataTypeDefinition = new DataTypeDefinition(dto.PropertyEditorAlias) { CreateDate = dto.NodeDto.CreateDate, DatabaseType = dto.DbType.EnumParse(true), diff --git a/src/Umbraco.Core/Persistence/Factories/MediaTypeFactory.cs b/src/Umbraco.Core/Persistence/Factories/MediaTypeFactory.cs deleted file mode 100644 index 98048cd3a7..0000000000 --- a/src/Umbraco.Core/Persistence/Factories/MediaTypeFactory.cs +++ /dev/null @@ -1,82 +0,0 @@ -using System; -using System.Globalization; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; - -namespace Umbraco.Core.Persistence.Factories -{ - internal class MediaTypeFactory - { - private readonly Guid _nodeObjectType; - - public MediaTypeFactory(Guid nodeObjectType) - { - _nodeObjectType = nodeObjectType; - } - - #region Implementation of IEntityFactory - - public IMediaType BuildEntity(ContentTypeDto dto) - { - var contentType = new MediaType(dto.NodeDto.ParentId) - { - Id = dto.NodeDto.NodeId, - Key = dto.NodeDto.UniqueId, - Alias = dto.Alias, - Name = dto.NodeDto.Text, - Icon = dto.Icon, - Thumbnail = dto.Thumbnail, - SortOrder = dto.NodeDto.SortOrder, - Description = dto.Description, - CreateDate = dto.NodeDto.CreateDate, - Path = dto.NodeDto.Path, - Level = dto.NodeDto.Level, - CreatorId = dto.NodeDto.UserId.Value, - AllowedAsRoot = dto.AllowAtRoot, - IsContainer = dto.IsContainer, - Trashed = dto.NodeDto.Trashed - }; - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - contentType.ResetDirtyProperties(false); - return contentType; - } - - public ContentTypeDto BuildDto(IMediaType entity) - { - var contentTypeDto = new ContentTypeDto - { - Alias = entity.Alias, - Description = entity.Description, - Icon = entity.Icon, - Thumbnail = entity.Thumbnail, - NodeId = entity.Id, - AllowAtRoot = entity.AllowedAsRoot, - IsContainer = entity.IsContainer, - NodeDto = BuildNodeDto(entity) - }; - return contentTypeDto; - } - - #endregion - - private NodeDto BuildNodeDto(IMediaType entity) - { - var nodeDto = new NodeDto - { - CreateDate = entity.CreateDate, - NodeId = entity.Id, - Level = short.Parse(entity.Level.ToString(CultureInfo.InvariantCulture)), - NodeObjectType = _nodeObjectType, - ParentId = entity.ParentId, - Path = entity.Path, - SortOrder = entity.SortOrder, - Text = entity.Name, - Trashed = false, - UniqueId = entity.Key, - UserId = entity.CreatorId - }; - return nodeDto; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Factories/MemberTypeFactory.cs b/src/Umbraco.Core/Persistence/Factories/MemberTypeFactory.cs deleted file mode 100644 index 345981342b..0000000000 --- a/src/Umbraco.Core/Persistence/Factories/MemberTypeFactory.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; - -namespace Umbraco.Core.Persistence.Factories -{ - internal class MemberTypeFactory - { - private readonly Guid _nodeObjectType; - - public MemberTypeFactory(Guid nodeObjectType) - { - _nodeObjectType = nodeObjectType; - } - - public IMemberType BuildEntity(ContentTypeDto dto) - { - throw new System.NotImplementedException(); - } - - public ContentTypeDto BuildDto(IMemberType entity) - { - var contentTypeDto = new ContentTypeDto - { - Alias = entity.Alias, - Description = entity.Description, - Icon = entity.Icon, - Thumbnail = entity.Thumbnail, - NodeId = entity.Id, - AllowAtRoot = entity.AllowedAsRoot, - IsContainer = entity.IsContainer, - NodeDto = BuildNodeDto(entity) - }; - return contentTypeDto; - } - - public IEnumerable BuildMemberTypeDtos(IMemberType entity) - { - var memberType = entity as MemberType; - if (memberType == null || memberType.PropertyTypes.Any() == false) - return Enumerable.Empty(); - - var memberTypes = new List(); - foreach (var propertyType in memberType.PropertyTypes) - { - memberTypes.Add(new MemberTypeDto - { - NodeId = entity.Id, - PropertyTypeId = propertyType.Id, - CanEdit = memberType.MemberCanEditProperty(propertyType.Alias), - ViewOnProfile = memberType.MemberCanViewProperty(propertyType.Alias) - }); - } - - return memberTypes; - } - - private NodeDto BuildNodeDto(IMemberType entity) - { - var nodeDto = new NodeDto - { - CreateDate = entity.CreateDate, - NodeId = entity.Id, - Level = short.Parse(entity.Level.ToString(CultureInfo.InvariantCulture)), - NodeObjectType = _nodeObjectType, - ParentId = entity.ParentId, - Path = entity.Path, - SortOrder = entity.SortOrder, - Text = entity.Name, - Trashed = false, - UniqueId = entity.Key, - UserId = entity.CreatorId - }; - return nodeDto; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs b/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs index 38ef4542f4..93ad99a5c1 100644 --- a/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs @@ -51,31 +51,28 @@ namespace Umbraco.Core.Persistence.Factories memberType.MemberTypePropertyTypes.Add(standardPropertyType.Key, new MemberTypePropertyProfileAccess(false, false)); } - memberType.PropertyTypes = propertyTypes; + memberType.NoGroupPropertyTypes = propertyTypes; return memberType; } private PropertyGroupCollection GetPropertyTypeGroupCollection(MemberTypeReadOnlyDto dto, MemberType memberType, Dictionary standardProps) { - var propertyGroups = new PropertyGroupCollection(); - + // see PropertyGroupFactory, repeating code here... + + var propertyGroups = new PropertyGroupCollection(); foreach (var groupDto in dto.PropertyTypeGroups.Where(x => x.Id.HasValue)) { var group = new PropertyGroup(); - - //Only assign an Id if the PropertyGroup belongs to this ContentType + + // if the group is defined on the current member type, + // assign its identifier, else it will be zero if (groupDto.ContentTypeNodeId == memberType.Id) { + // note: no idea why Id is nullable here, but better check + if (groupDto.Id.HasValue == false) + throw new Exception("oops: groupDto.Id has no value."); group.Id = groupDto.Id.Value; - - if (groupDto.ParentGroupId.HasValue) - group.ParentId = groupDto.ParentGroupId.Value; - } - else - { - //If the PropertyGroup is inherited, we add a reference to the group as a Parent. - group.ParentId = groupDto.Id; } group.Name = groupDto.Text; diff --git a/src/Umbraco.Core/Persistence/Factories/MigrationEntryFactory.cs b/src/Umbraco.Core/Persistence/Factories/MigrationEntryFactory.cs new file mode 100644 index 0000000000..1cb7000293 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Factories/MigrationEntryFactory.cs @@ -0,0 +1,40 @@ +using System; +using Semver; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Rdbms; + +namespace Umbraco.Core.Persistence.Factories +{ + internal class MigrationEntryFactory + { + public MigrationEntry BuildEntity(MigrationDto dto) + { + SemVersion parsed; + if (SemVersion.TryParse(dto.Version, out parsed) == false) + { + throw new FormatException("Cannot parse the version string in the database to a SemVersion object: " + dto.Version); + } + + var model = new MigrationEntry(dto.Id, dto.CreateDate, dto.Name, parsed); + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + model.ResetDirtyProperties(false); + return model; + } + + public MigrationDto BuildDto(IMigrationEntry entity) + { + var dto = new MigrationDto + { + CreateDate = entity.CreateDate, + Name = entity.MigrationName, + Version = entity.Version.ToString() + }; + + if (entity.HasIdentity) + dto.Id = entity.Id; + + return dto; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs b/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs index 4e3653bf9e..8d51b627ea 100644 --- a/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs @@ -63,7 +63,9 @@ namespace Umbraco.Core.Persistence.Factories //Check if property has an Id and set it, so that it can be updated if it already exists if (property.HasIdentity) + { dto.Id = property.Id; + } if (property.DataTypeDatabaseType == DataTypeDatabaseType.Integer) { @@ -82,11 +84,21 @@ namespace Umbraco.Core.Persistence.Factories } } } + else if (property.DataTypeDatabaseType == DataTypeDatabaseType.Decimal && property.Value != null) + { + decimal val; + if (decimal.TryParse(property.Value.ToString(), out val)) + { + dto.Decimal = val; + } + } else if (property.DataTypeDatabaseType == DataTypeDatabaseType.Date && property.Value != null && string.IsNullOrWhiteSpace(property.Value.ToString()) == false) { DateTime date; - if(DateTime.TryParse(property.Value.ToString(), out date)) + if (DateTime.TryParse(property.Value.ToString(), out date)) + { dto.Date = date; + } } else if (property.DataTypeDatabaseType == DataTypeDatabaseType.Ntext && property.Value != null) { diff --git a/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs b/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs index c7cd2094a1..5b2cad3415 100644 --- a/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs @@ -8,21 +8,21 @@ namespace Umbraco.Core.Persistence.Factories { internal class PropertyGroupFactory { - private readonly int _id; + private readonly int _contentTypeId; private readonly DateTime _createDate; private readonly DateTime _updateDate; //a callback to create a property type which can be injected via a contructor private readonly Func _propertyTypeCtor; - public PropertyGroupFactory(int id) + public PropertyGroupFactory(int contentTypeId) { - _id = id; + _contentTypeId = contentTypeId; _propertyTypeCtor = (propertyEditorAlias, dbType, alias) => new PropertyType(propertyEditorAlias, dbType); } - public PropertyGroupFactory(int id, DateTime createDate, DateTime updateDate, Func propertyTypeCtor) + public PropertyGroupFactory(int contentTypeId, DateTime createDate, DateTime updateDate, Func propertyTypeCtor) { - _id = id; + _contentTypeId = contentTypeId; _createDate = createDate; _updateDate = updateDate; _propertyTypeCtor = propertyTypeCtor; @@ -30,29 +30,24 @@ namespace Umbraco.Core.Persistence.Factories #region Implementation of IEntityFactory,IEnumerable> - public IEnumerable BuildEntity(IEnumerable dto) + public IEnumerable BuildEntity(IEnumerable groupDtos) { + // groupDtos contains all the groups, those that are defined on the current + // content type, and those that are inherited from composition content types var propertyGroups = new PropertyGroupCollection(); - foreach (var groupDto in dto) + foreach (var groupDto in groupDtos) { var group = new PropertyGroup(); - //Only assign an Id if the PropertyGroup belongs to this ContentType - if (groupDto.ContentTypeNodeId == _id) - { - group.Id = groupDto.Id; - if (groupDto.ParentGroupId.HasValue) - group.ParentId = groupDto.ParentGroupId.Value; - } - else - { - //If the PropertyGroup is inherited, we add a reference to the group as a Parent. - group.ParentId = groupDto.Id; - } + // if the group is defined on the current content type, + // assign its identifier, else it will be zero + if (groupDto.ContentTypeNodeId == _contentTypeId) + group.Id = groupDto.Id; group.Name = groupDto.Text; group.SortOrder = groupDto.SortOrder; group.PropertyTypes = new PropertyTypeCollection(); + group.Key = groupDto.UniqueId; //Because we are likely to have a group with no PropertyTypes we need to ensure that these are excluded var typeDtos = groupDto.PropertyTypeDtos.Where(x => x.Id > 0); @@ -67,6 +62,7 @@ namespace Umbraco.Core.Persistence.Factories propertyType.DataTypeDefinitionId = typeDto.DataTypeId; propertyType.Description = typeDto.Description; propertyType.Id = typeDto.Id; + propertyType.Key = typeDto.UniqueId; propertyType.Name = typeDto.Name; propertyType.Mandatory = typeDto.Mandatory; propertyType.SortOrder = typeDto.SortOrder; @@ -100,14 +96,14 @@ namespace Umbraco.Core.Persistence.Factories { var dto = new PropertyTypeGroupDto { - ContentTypeNodeId = _id, + ContentTypeNodeId = _contentTypeId, SortOrder = propertyGroup.SortOrder, - Text = propertyGroup.Name + Text = propertyGroup.Name, + UniqueId = propertyGroup.Key == Guid.Empty + ? Guid.NewGuid() + : propertyGroup.Key }; - if (propertyGroup.ParentId.HasValue) - dto.ParentGroupId = propertyGroup.ParentId.Value; - if (propertyGroup.HasIdentity) dto.Id = propertyGroup.Id; @@ -119,16 +115,19 @@ namespace Umbraco.Core.Persistence.Factories internal PropertyTypeDto BuildPropertyTypeDto(int tabId, PropertyType propertyType) { var propertyTypeDto = new PropertyTypeDto - { - Alias = propertyType.Alias, - ContentTypeId = _id, - DataTypeId = propertyType.DataTypeDefinitionId, - Description = propertyType.Description, - Mandatory = propertyType.Mandatory, - Name = propertyType.Name, - SortOrder = propertyType.SortOrder, - ValidationRegExp = propertyType.ValidationRegExp - }; + { + Alias = propertyType.Alias, + ContentTypeId = _contentTypeId, + DataTypeId = propertyType.DataTypeDefinitionId, + Description = propertyType.Description, + Mandatory = propertyType.Mandatory, + Name = propertyType.Name, + SortOrder = propertyType.SortOrder, + ValidationRegExp = propertyType.ValidationRegExp, + UniqueId = propertyType.Key == Guid.Empty + ? Guid.NewGuid() + : propertyType.Key + }; if (tabId != default(int)) { diff --git a/src/Umbraco.Core/Persistence/Factories/ServerRegistrationFactory.cs b/src/Umbraco.Core/Persistence/Factories/ServerRegistrationFactory.cs index aa0ed25ccd..9c315aef46 100644 --- a/src/Umbraco.Core/Persistence/Factories/ServerRegistrationFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/ServerRegistrationFactory.cs @@ -5,34 +5,31 @@ namespace Umbraco.Core.Persistence.Factories { internal class ServerRegistrationFactory { - #region Implementation of IEntityFactory - public ServerRegistration BuildEntity(ServerRegistrationDto dto) { - var model = new ServerRegistration(dto.Id, dto.ServerAddress, dto.ServerIdentity, dto.DateRegistered, dto.DateAccessed, dto.IsActive); + var model = new ServerRegistration(dto.Id, dto.ServerAddress, dto.ServerIdentity, dto.DateRegistered, dto.DateAccessed, dto.IsActive, dto.IsMaster); //on initial construction we don't want to have dirty properties tracked // http://issues.umbraco.org/issue/U4-1946 model.ResetDirtyProperties(false); return model; } - public ServerRegistrationDto BuildDto(ServerRegistration entity) + public ServerRegistrationDto BuildDto(IServerRegistration entity) { var dto = new ServerRegistrationDto - { - ServerAddress = entity.ServerAddress, - DateRegistered = entity.CreateDate, - IsActive = entity.IsActive, - DateAccessed = entity.UpdateDate, - ServerIdentity = entity.ServerIdentity - }; + { + ServerAddress = entity.ServerAddress, + DateRegistered = entity.CreateDate, + IsActive = entity.IsActive, + IsMaster = ((ServerRegistration) entity).IsMaster, + DateAccessed = entity.UpdateDate, + ServerIdentity = entity.ServerIdentity + }; if (entity.HasIdentity) dto.Id = entity.Id; return dto; } - - #endregion } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Factories/TemplateFactory.cs b/src/Umbraco.Core/Persistence/Factories/TemplateFactory.cs index 1e59c8e920..60cde916b6 100644 --- a/src/Umbraco.Core/Persistence/Factories/TemplateFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/TemplateFactory.cs @@ -33,9 +33,9 @@ namespace Umbraco.Core.Persistence.Factories #region Implementation of IEntityFactory - public Template BuildEntity(TemplateDto dto, IEnumerable childDefinitions) + public Template BuildEntity(TemplateDto dto, IEnumerable childDefinitions, Func getFileContent) { - var template = new Template(dto.NodeDto.Text, dto.Alias) + var template = new Template(dto.NodeDto.Text, dto.Alias, getFileContent) { CreateDate = dto.NodeDto.CreateDate, Id = dto.NodeId, @@ -43,12 +43,8 @@ namespace Umbraco.Core.Persistence.Factories Path = dto.NodeDto.Path }; - if (childDefinitions.Any(x => x.ParentId == dto.NodeId)) - { - template.IsMasterTemplate = true; - } + template.IsMasterTemplate = childDefinitions.Any(x => x.ParentId == dto.NodeId); - //TODO: Change this to ParentId: http://issues.umbraco.org/issue/U4-5846 if(dto.NodeDto.ParentId > 0) template.MasterTemplateId = new Lazy(() => dto.NodeDto.ParentId); diff --git a/src/Umbraco.Core/Persistence/LockedRepository.cs b/src/Umbraco.Core/Persistence/LockedRepository.cs new file mode 100644 index 0000000000..b5d2d672f2 --- /dev/null +++ b/src/Umbraco.Core/Persistence/LockedRepository.cs @@ -0,0 +1,27 @@ +using System; +using Umbraco.Core.Persistence.Repositories; +using Umbraco.Core.Persistence.UnitOfWork; + +namespace Umbraco.Core.Persistence +{ + internal class LockedRepository + where TRepository : IDisposable, IRepository + { + public LockedRepository(Transaction transaction, IDatabaseUnitOfWork unitOfWork, TRepository repository) + { + Transaction = transaction; + UnitOfWork = unitOfWork; + Repository = repository; + } + + public Transaction Transaction { get; private set; } + public IDatabaseUnitOfWork UnitOfWork { get; private set; } + public TRepository Repository { get; private set; } + + public void Commit() + { + UnitOfWork.Commit(); + Transaction.Complete(); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/LockingRepository.cs b/src/Umbraco.Core/Persistence/LockingRepository.cs new file mode 100644 index 0000000000..f513073e71 --- /dev/null +++ b/src/Umbraco.Core/Persistence/LockingRepository.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using Umbraco.Core.Persistence.Repositories; +using Umbraco.Core.Persistence.UnitOfWork; + +namespace Umbraco.Core.Persistence +{ + internal class LockingRepository + where TRepository : IDisposable, IRepository + { + private readonly IDatabaseUnitOfWorkProvider _uowProvider; + private readonly Func _repositoryFactory; + private readonly int[] _readLockIds, _writeLockIds; + + public LockingRepository(IDatabaseUnitOfWorkProvider uowProvider, Func repositoryFactory, + IEnumerable readLockIds, IEnumerable writeLockIds) + { + Mandate.ParameterNotNull(uowProvider, "uowProvider"); + Mandate.ParameterNotNull(repositoryFactory, "repositoryFactory"); + + _uowProvider = uowProvider; + _repositoryFactory = repositoryFactory; + _readLockIds = readLockIds == null ? new int[0] : readLockIds.ToArray(); + _writeLockIds = writeLockIds == null ? new int[0] : writeLockIds.ToArray(); + } + + public void WithReadLocked(Action> action, bool autoCommit = true) + { + var uow = _uowProvider.GetUnitOfWork(); + using (var transaction = uow.Database.GetTransaction(IsolationLevel.RepeatableRead)) + { + foreach (var lockId in _readLockIds) + uow.Database.AcquireLockNodeReadLock(lockId); + + using (var repository = _repositoryFactory(uow)) + { + action(new LockedRepository(transaction, uow, repository)); + if (autoCommit == false) return; + uow.Commit(); + transaction.Complete(); + } + } + } + + public TResult WithReadLocked(Func, TResult> func, bool autoCommit = true) + { + var uow = _uowProvider.GetUnitOfWork(); + using (var transaction = uow.Database.GetTransaction(IsolationLevel.RepeatableRead)) + { + foreach (var lockId in _readLockIds) + uow.Database.AcquireLockNodeReadLock(lockId); + + using (var repository = _repositoryFactory(uow)) + { + var ret = func(new LockedRepository(transaction, uow, repository)); + if (autoCommit == false) return ret; + uow.Commit(); + transaction.Complete(); + return ret; + } + } + } + + public void WithWriteLocked(Action> action, bool autoCommit = true) + { + var uow = _uowProvider.GetUnitOfWork(); + using (var transaction = uow.Database.GetTransaction(IsolationLevel.RepeatableRead)) + { + foreach (var lockId in _writeLockIds) + uow.Database.AcquireLockNodeWriteLock(lockId); + + using (var repository = _repositoryFactory(uow)) + { + action(new LockedRepository(transaction, uow, repository)); + if (autoCommit == false) return; + uow.Commit(); + transaction.Complete(); + } + } + } + + public TResult WithWriteLocked(Func, TResult> func, bool autoCommit = true) + { + var uow = _uowProvider.GetUnitOfWork(); + using (var transaction = uow.Database.GetTransaction(IsolationLevel.RepeatableRead)) + { + foreach (var lockId in _writeLockIds) + uow.Database.AcquireLockNodeReadLock(lockId); + + using (var repository = _repositoryFactory(uow)) + { + var ret = func(new LockedRepository(transaction, uow, repository)); + if (autoCommit == false) return ret; + uow.Commit(); + transaction.Complete(); + return ret; + } + } + } + } +} diff --git a/src/Umbraco.Core/Persistence/Mappers/DomainMapper.cs b/src/Umbraco.Core/Persistence/Mappers/DomainMapper.cs index 46c67e762d..5ea4426a21 100644 --- a/src/Umbraco.Core/Persistence/Mappers/DomainMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/DomainMapper.cs @@ -6,7 +6,8 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Mappers { - [MapperFor(typeof(DomainRepository.CacheableDomain))] + [MapperFor(typeof(IDomain))] + [MapperFor(typeof(UmbracoDomain))] public sealed class DomainMapper : BaseMapper { private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); @@ -23,10 +24,10 @@ namespace Umbraco.Core.Persistence.Mappers protected override void BuildMap() { - CacheMap(src => src.Id, dto => dto.Id); - CacheMap(src => src.RootContentId, dto => dto.RootStructureId); - CacheMap(src => src.DefaultLanguageId, dto => dto.DefaultLanguage); - CacheMap(src => src.DomainName, dto => dto.DomainName); + CacheMap(src => src.Id, dto => dto.Id); + CacheMap(src => src.RootContentId, dto => dto.RootStructureId); + CacheMap(src => src.LanguageId, dto => dto.DefaultLanguage); + CacheMap(src => src.DomainName, dto => dto.DomainName); } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Mappers/MigrationEntryMapper.cs b/src/Umbraco.Core/Persistence/Mappers/MigrationEntryMapper.cs new file mode 100644 index 0000000000..5c0838ede9 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Mappers/MigrationEntryMapper.cs @@ -0,0 +1,38 @@ +using System.Collections.Concurrent; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Rdbms; + +namespace Umbraco.Core.Persistence.Mappers +{ + [MapperFor(typeof(MigrationEntry))] + [MapperFor(typeof(IMigrationEntry))] + internal sealed class MigrationEntryMapper : BaseMapper + { + private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + + //NOTE: its an internal class but the ctor must be public since we're using Activator.CreateInstance to create it + // otherwise that would fail because there is no public constructor. + public MigrationEntryMapper() + { + BuildMap(); + } + + #region Overrides of BaseMapper + + internal override ConcurrentDictionary PropertyInfoCache + { + get { return PropertyInfoCacheInstance; } + } + + internal override void BuildMap() + { + CacheMap(src => src.Id, dto => dto.Id); + CacheMap(src => src.CreateDate, dto => dto.CreateDate); + CacheMap(src => src.UpdateDate, dto => dto.CreateDate); + CacheMap(src => src.Version, dto => dto.Version); + CacheMap(src => src.MigrationName, dto => dto.Name); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Mappers/PropertyGroupMapper.cs b/src/Umbraco.Core/Persistence/Mappers/PropertyGroupMapper.cs index 0b44b24702..384f3e3866 100644 --- a/src/Umbraco.Core/Persistence/Mappers/PropertyGroupMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/PropertyGroupMapper.cs @@ -31,7 +31,7 @@ namespace Umbraco.Core.Persistence.Mappers protected override void BuildMap() { CacheMap(src => src.Id, dto => dto.Id); - CacheMap(src => src.ParentId, dto => dto.ParentGroupId); + CacheMap(src => src.Key, dto => dto.UniqueId); CacheMap(src => src.SortOrder, dto => dto.SortOrder); CacheMap(src => src.Name, dto => dto.Text); } diff --git a/src/Umbraco.Core/Persistence/Mappers/PropertyTypeMapper.cs b/src/Umbraco.Core/Persistence/Mappers/PropertyTypeMapper.cs index 216d68b7cc..f770874c02 100644 --- a/src/Umbraco.Core/Persistence/Mappers/PropertyTypeMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/PropertyTypeMapper.cs @@ -31,6 +31,7 @@ namespace Umbraco.Core.Persistence.Mappers { if(PropertyInfoCache.IsEmpty) { + CacheMap(src => src.Key, dto => dto.UniqueId); CacheMap(src => src.Id, dto => dto.Id); CacheMap(src => src.Alias, dto => dto.Alias); CacheMap(src => src.DataTypeDefinitionId, dto => dto.DataTypeId); diff --git a/src/Umbraco.Core/Persistence/Mappers/ServerRegistrationMapper.cs b/src/Umbraco.Core/Persistence/Mappers/ServerRegistrationMapper.cs index ea35da3e58..f57a258d7a 100644 --- a/src/Umbraco.Core/Persistence/Mappers/ServerRegistrationMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/ServerRegistrationMapper.cs @@ -8,6 +8,7 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Mappers { [MapperFor(typeof(ServerRegistration))] + [MapperFor(typeof(IServerRegistration))] internal sealed class ServerRegistrationMapper : BaseMapper { private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); @@ -28,6 +29,7 @@ namespace Umbraco.Core.Persistence.Mappers { CacheMap(src => src.Id, dto => dto.Id); CacheMap(src => src.IsActive, dto => dto.IsActive); + CacheMap(src => src.IsMaster, dto => dto.IsMaster); CacheMap(src => src.ServerAddress, dto => dto.ServerAddress); CacheMap(src => src.CreateDate, dto => dto.DateRegistered); CacheMap(src => src.UpdateDate, dto => dto.DateAccessed); diff --git a/src/Umbraco.Core/Persistence/Mappers/TaskTypeMapper.cs b/src/Umbraco.Core/Persistence/Mappers/TaskTypeMapper.cs new file mode 100644 index 0000000000..4399fd323b --- /dev/null +++ b/src/Umbraco.Core/Persistence/Mappers/TaskTypeMapper.cs @@ -0,0 +1,38 @@ +using System.Collections.Concurrent; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Rdbms; + +namespace Umbraco.Core.Persistence.Mappers +{ + /// + /// Represents a to DTO mapper used to translate the properties of the public api + /// implementation to that of the database's DTO as sql: [tableName].[columnName]. + /// + [MapperFor(typeof(TaskType))] + public sealed class TaskTypeMapper : BaseMapper + { + private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + + //NOTE: its an internal class but the ctor must be public since we're using Activator.CreateInstance to create it + // otherwise that would fail because there is no public constructor. + public TaskTypeMapper() + { + BuildMap(); + } + + #region Overrides of BaseMapper + + internal override ConcurrentDictionary PropertyInfoCache + { + get { return PropertyInfoCacheInstance; } + } + + internal override void BuildMap() + { + CacheMap(src => src.Id, dto => dto.Id); + CacheMap(src => src.Alias, dto => dto.Alias); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Mappers/TemplateMapper.cs b/src/Umbraco.Core/Persistence/Mappers/TemplateMapper.cs index 6e1ad761ac..b4613ed9e4 100644 --- a/src/Umbraco.Core/Persistence/Mappers/TemplateMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/TemplateMapper.cs @@ -38,6 +38,7 @@ namespace Umbraco.Core.Persistence.Mappers { CacheMap(src => src.Id, dto => dto.NodeId); CacheMap(src => src.MasterTemplateId, dto => dto.ParentId); + CacheMap(src => src.Key, dto => dto.UniqueId); CacheMap(src => src.Alias, dto => dto.Alias); CacheMap(src => src.Content, dto => dto.Design); } diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs index 27f5ec434d..5e07a617d4 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs @@ -1,4 +1,5 @@ using System; +using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Models.Rdbms; @@ -86,11 +87,17 @@ namespace Umbraco.Core.Persistence.Migrations.Initial { CreateUmbracoRelationTypeData(); } + if (tableName.Equals("cmsTaskType")) { CreateCmsTaskTypeData(); } + if (tableName.Equals("umbracoMigration")) + { + CreateUmbracoMigrationData(); + } + _logger.Info(string.Format("Done creating data in table {0}", tableName)); } @@ -101,7 +108,7 @@ namespace Umbraco.Core.Persistence.Migrations.Initial _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -21, Trashed = false, ParentId = -1, UserId = 0, Level = 0, Path = "-1,-21", SortOrder = 0, UniqueId = new Guid("BF7C7CBC-952F-4518-97A2-69E9C7B33842"), Text = "Recycle Bin", NodeObjectType = new Guid(Constants.ObjectTypes.MediaRecycleBin), CreateDate = DateTime.Now }); _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -92, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-92", SortOrder = 35, UniqueId = new Guid("f0bc4bfb-b499-40d6-ba86-058885a5178c"), Text = "Label", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -90, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-90", SortOrder = 34, UniqueId = new Guid("84c6b441-31df-4ffe-b67e-67d5bc3ae65a"), Text = "Upload", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -89, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-89", SortOrder = 33, UniqueId = new Guid("c6bac0dd-4ab9-45b1-8e30-e4b619ee5da3"), Text = "Textbox multiple", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -89, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-89", SortOrder = 33, UniqueId = new Guid("c6bac0dd-4ab9-45b1-8e30-e4b619ee5da3"), Text = "Textarea", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -88, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-88", SortOrder = 32, UniqueId = new Guid("0cc0eba1-9960-42c9-bf9b-60e150b429ae"), Text = "Textstring", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -87, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-87", SortOrder = 4, UniqueId = new Guid("ca90c950-0aff-4e72-b976-a30b1ac57dad"), Text = "Richtext editor", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -51, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-51", SortOrder = 2, UniqueId = new Guid("2e6d3631-066e-44b8-aec4-96f09099b2b5"), Text = "Numeric", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); @@ -110,8 +117,7 @@ namespace Umbraco.Core.Persistence.Migrations.Initial _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -42, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-42", SortOrder = 2, UniqueId = new Guid("0b6a45e7-44ba-430d-9da5-4e46060b9e03"), Text = "Dropdown", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -41, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-41", SortOrder = 2, UniqueId = new Guid("5046194e-4237-453c-a547-15db3a07c4e1"), Text = "Date Picker", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -40, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-40", SortOrder = 2, UniqueId = new Guid("bb5f57c9-ce2b-4bb9-b697-4caca783a805"), Text = "Radiobox", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -39, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-39", SortOrder = 2, UniqueId = new Guid("f38f0ac7-1d27-439c-9f3f-089cd8825a53"), Text = "Dropdown multiple", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -38, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-38", SortOrder = 2, UniqueId = new Guid("fd9f1447-6c61-4a7c-9595-5aa39147d318"), Text = "Folder Browser", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -39, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-39", SortOrder = 2, UniqueId = new Guid("f38f0ac7-1d27-439c-9f3f-089cd8825a53"), Text = "Dropdown multiple", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -37, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-37", SortOrder = 2, UniqueId = new Guid("0225af17-b302-49cb-9176-b9f35cab9c17"), Text = "Approved Color", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -36, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-36", SortOrder = 2, UniqueId = new Guid("e4d66c0f-b935-4200-81f0-025f7256b89a"), Text = "Date Picker with time", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = Constants.System.DefaultContentListViewDataTypeId, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-95", SortOrder = 2, UniqueId = new Guid("C0808DD3-8133-4E4B-8CE8-E2BEA84A96A4"), Text = Constants.Conventions.DataTypes.ListViewPrefix + "Content", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); @@ -172,37 +178,33 @@ namespace Umbraco.Core.Persistence.Migrations.Initial private void CreateCmsPropertyTypeGroupData() { - _database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 3, ContentTypeNodeId = 1032, Text = "Image", SortOrder = 1 }); - _database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 4, ContentTypeNodeId = 1033, Text = "File", SortOrder = 1 }); - _database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 5, ContentTypeNodeId = 1031, Text = "Contents", SortOrder = 1 }); + _database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 3, ContentTypeNodeId = 1032, Text = "Image", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.Image) }); + _database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 4, ContentTypeNodeId = 1033, Text = "File", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.File) }); + _database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 5, ContentTypeNodeId = 1031, Text = "Contents", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.Contents) }); //membership property group - _database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 11, ContentTypeNodeId = 1044, Text = "Membership", SortOrder = 1 }); + _database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 11, ContentTypeNodeId = 1044, Text = "Membership", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.Membership) }); } private void CreateCmsPropertyTypeData() { - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 6, DataTypeId = -90, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.File, Name = "Upload image", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 7, DataTypeId = -92, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Width, Name = "Width", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 8, DataTypeId = -92, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Height, Name = "Height", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 9, DataTypeId = -92, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Bytes, Name = "Size", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 10, DataTypeId = -92, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Extension, Name = "Type", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 24, DataTypeId = -90, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.File, Name = "Upload file", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 25, DataTypeId = -92, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.Extension, Name = "Type", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 26, DataTypeId = -92, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.Bytes, Name = "Size", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 27, DataTypeId = -38, ContentTypeId = 1031, PropertyTypeGroupId = 5, Alias = "contents", Name = "Contents:", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 6, UniqueId = 6.ToGuid(), DataTypeId = 1043, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.File, Name = "Upload image", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 7, UniqueId = 7.ToGuid(), DataTypeId = -92, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Width, Name = "Width", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 8, UniqueId = 8.ToGuid(), DataTypeId = -92, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Height, Name = "Height", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 9, UniqueId = 9.ToGuid(), DataTypeId = -92, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Bytes, Name = "Size", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 10, UniqueId = 10.ToGuid(), DataTypeId = -92, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Extension, Name = "Type", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 24, UniqueId = 24.ToGuid(), DataTypeId = -90, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.File, Name = "Upload file", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 25, UniqueId = 25.ToGuid(), DataTypeId = -92, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.Extension, Name = "Type", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 26, UniqueId = 26.ToGuid(), DataTypeId = -92, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.Bytes, Name = "Size", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 27, UniqueId = 27.ToGuid(), DataTypeId = Constants.System.DefaultMediaListViewDataTypeId, ContentTypeId = 1031, PropertyTypeGroupId = 5, Alias = "contents", Name = "Contents:", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); //membership property types - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 28, DataTypeId = -89, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.Comments, Name = Constants.Conventions.Member.CommentsLabel, SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 29, DataTypeId = -92, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.FailedPasswordAttempts, Name = Constants.Conventions.Member.FailedPasswordAttemptsLabel, SortOrder = 1, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 30, DataTypeId = -49, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.IsApproved, Name = Constants.Conventions.Member.IsApprovedLabel, SortOrder = 2, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 31, DataTypeId = -49, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.IsLockedOut, Name = Constants.Conventions.Member.IsLockedOutLabel, SortOrder = 3, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 32, DataTypeId = -92, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastLockoutDate, Name = Constants.Conventions.Member.LastLockoutDateLabel, SortOrder = 4, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 33, DataTypeId = -92, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastLoginDate, Name = Constants.Conventions.Member.LastLoginDateLabel, SortOrder = 5, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 34, DataTypeId = -92, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastPasswordChangeDate, Name = Constants.Conventions.Member.LastPasswordChangeDateLabel, SortOrder = 6, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 28, UniqueId = 28.ToGuid(), DataTypeId = -89, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.Comments, Name = Constants.Conventions.Member.CommentsLabel, SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 29, UniqueId = 29.ToGuid(), DataTypeId = -92, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.FailedPasswordAttempts, Name = Constants.Conventions.Member.FailedPasswordAttemptsLabel, SortOrder = 1, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 30, UniqueId = 30.ToGuid(), DataTypeId = -49, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.IsApproved, Name = Constants.Conventions.Member.IsApprovedLabel, SortOrder = 2, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 31, UniqueId = 31.ToGuid(), DataTypeId = -49, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.IsLockedOut, Name = Constants.Conventions.Member.IsLockedOutLabel, SortOrder = 3, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 32, UniqueId = 32.ToGuid(), DataTypeId = -92, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastLockoutDate, Name = Constants.Conventions.Member.LastLockoutDateLabel, SortOrder = 4, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 33, UniqueId = 33.ToGuid(), DataTypeId = -92, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastLoginDate, Name = Constants.Conventions.Member.LastLoginDateLabel, SortOrder = 5, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 34, UniqueId = 34.ToGuid(), DataTypeId = -92, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastPasswordChangeDate, Name = Constants.Conventions.Member.LastPasswordChangeDateLabel, SortOrder = 6, Mandatory = false, ValidationRegExp = null, Description = null }); - //TODO: The member editor doesn't currently support providers that have question/answer so we'll leave these out for now. - // Also, it's worth noting that the built in ASP.Net providers encrypt the answer so that admins cannot see it for added security which is something we should actually do! - //_database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 35, DataTypeId = -92, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.PasswordAnswer, Name = Constants.Conventions.Member.PasswordAnswerLabel, HelpText = null, SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - //_database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 36, DataTypeId = -92, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.PasswordQuestion, Name = Constants.Conventions.Member.PasswordQuestionLabel, HelpText = null, SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); } private void CreateUmbracoLanguageData() @@ -228,8 +230,7 @@ namespace Umbraco.Core.Persistence.Migrations.Initial _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 6, DataTypeId = -90, PropertyEditorAlias = Constants.PropertyEditors.UploadFieldAlias, DbType = "Nvarchar" }); _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 7, DataTypeId = -92, PropertyEditorAlias = Constants.PropertyEditors.NoEditAlias, DbType = "Nvarchar" }); _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 8, DataTypeId = -36, PropertyEditorAlias = Constants.PropertyEditors.DateTimeAlias, DbType = "Date" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 9, DataTypeId = -37, PropertyEditorAlias = Constants.PropertyEditors.ColorPickerAlias, DbType = "Nvarchar" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 10, DataTypeId = -38, PropertyEditorAlias = Constants.PropertyEditors.FolderBrowserAlias, DbType = "Nvarchar" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 9, DataTypeId = -37, PropertyEditorAlias = Constants.PropertyEditors.ColorPickerAlias, DbType = "Nvarchar" }); _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 11, DataTypeId = -39, PropertyEditorAlias = Constants.PropertyEditors.DropDownListMultipleAlias, DbType = "Nvarchar" }); _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 12, DataTypeId = -40, PropertyEditorAlias = Constants.PropertyEditors.RadioButtonListAlias, DbType = "Nvarchar" }); _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 13, DataTypeId = -41, PropertyEditorAlias = Constants.PropertyEditors.DateAlias, DbType = "Date" }); @@ -250,7 +251,6 @@ namespace Umbraco.Core.Persistence.Migrations.Initial //_database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 19, DataTypeId = 1038, PropertyEditorAlias = Constants.PropertyEditors.MarkdownEditorAlias, DbType = "Ntext" }); //_database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 20, DataTypeId = 1039, PropertyEditorAlias = Constants.PropertyEditors.UltimatePickerAlias, DbType = "Ntext" }); //_database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 23, DataTypeId = 1042, PropertyEditorAlias = Constants.PropertyEditors.MacroContainerAlias, DbType = "Ntext" }); - } private void CreateCmsDataTypePreValuesData() @@ -266,6 +266,17 @@ namespace Umbraco.Core.Persistence.Migrations.Initial _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -2, Alias = "orderBy", SortOrder = 2, DataTypeNodeId = Constants.System.DefaultMembersListViewDataTypeId, Value = "Name" }); _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -3, Alias = "orderDirection", SortOrder = 3, DataTypeNodeId = Constants.System.DefaultMembersListViewDataTypeId, Value = "asc" }); _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -4, Alias = "includeProperties", SortOrder = 4, DataTypeNodeId = Constants.System.DefaultMembersListViewDataTypeId, Value = "[{\"alias\":\"email\",\"isSystem\":1},{\"alias\":\"username\",\"isSystem\":1},{\"alias\":\"updateDate\",\"header\":\"Last edited\",\"isSystem\":1}]" }); + + //layouts for the list view + var cardLayout = "{\"name\": \"Grid\",\"path\": \"views/propertyeditors/listview/layouts/grid/grid.html\", \"icon\": \"icon-thumbnails-small\", \"isSystem\": 1, \"selected\": true}"; + var listLayout = "{\"name\": \"List\",\"path\": \"views/propertyeditors/listview/layouts/list/list.html\",\"icon\": \"icon-list\", \"isSystem\": 1,\"selected\": true}"; + + //defaults for the media list + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -5, Alias = "pageSize", SortOrder = 1, DataTypeNodeId = Constants.System.DefaultMediaListViewDataTypeId, Value = "100" }); + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -6, Alias = "orderBy", SortOrder = 2, DataTypeNodeId = Constants.System.DefaultMediaListViewDataTypeId, Value = "VersionDate" }); + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -7, Alias = "orderDirection", SortOrder = 3, DataTypeNodeId = Constants.System.DefaultMediaListViewDataTypeId, Value = "desc" }); + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -8, Alias = "layouts", SortOrder = 4, DataTypeNodeId = Constants.System.DefaultMediaListViewDataTypeId, Value = "[" + cardLayout + "," + listLayout + "]" }); + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -9, Alias = "includeProperties", SortOrder = 5, DataTypeNodeId = Constants.System.DefaultMediaListViewDataTypeId, Value = "[{\"alias\":\"sortOrder\",\"isSystem\":1, \"header\": \"Sort order\"},{\"alias\":\"updateDate\",\"header\":\"Last edited\",\"isSystem\":1},{\"alias\":\"owner\",\"header\":\"Updated by\",\"isSystem\":1}]" }); } private void CreateUmbracoRelationTypeData() @@ -278,5 +289,18 @@ namespace Umbraco.Core.Persistence.Migrations.Initial { _database.Insert("cmsTaskType", "id", false, new TaskTypeDto { Id = 1, Alias = "toTranslate" }); } + + private void CreateUmbracoMigrationData() + { + var dto = new MigrationDto + { + Id = 1, + Name = GlobalSettings.UmbracoMigrationName, + Version = UmbracoVersion.GetSemanticVersion().ToString(), + CreateDate = DateTime.Now + }; + + _database.Insert("umbracoMigration", "pk", false, dto); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs index 1888a9f0aa..f789c18aa1 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs @@ -44,12 +44,12 @@ namespace Umbraco.Core.Persistence.Migrations.Initial {4, typeof (ContentVersionDto)}, {5, typeof (DocumentDto)}, - {6, typeof (DocumentTypeDto)}, + {6, typeof (ContentTypeTemplateDto)}, {7, typeof (DataTypeDto)}, {8, typeof (DataTypePreValueDto)}, {9, typeof (DictionaryDto)}, - {10, typeof (LanguageTextDto)}, - {11, typeof (LanguageDto)}, + {10, typeof (LanguageDto)}, + {11, typeof (LanguageTextDto)}, {12, typeof (DomainDto)}, {13, typeof (LogDto)}, {14, typeof (MacroDto)}, @@ -68,7 +68,6 @@ namespace Umbraco.Core.Persistence.Migrations.Initial {27, typeof (StylesheetPropertyDto)}, {28, typeof (TagDto)}, {29, typeof (TagRelationshipDto)}, - {30, typeof (UserLoginDto)}, {31, typeof (UserTypeDto)}, {32, typeof (UserDto)}, {33, typeof (TaskTypeDto)}, @@ -82,7 +81,10 @@ namespace Umbraco.Core.Persistence.Migrations.Initial {41, typeof (AccessDto)}, {42, typeof (AccessRuleDto)}, {43, typeof(CacheInstructionDto)}, - {44, typeof (ExternalLoginDto)} + {44, typeof (ExternalLoginDto)}, + {45, typeof (MigrationDto)}, + {46, typeof (UmbracoDeployChecksumDto)}, + {47, typeof (UmbracoDeployDependencyDto)} }; #endregion diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaResult.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaResult.cs index 5c5678a90a..70ab65b422 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaResult.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaResult.cs @@ -2,9 +2,11 @@ using System; using System.Collections.Generic; using System.Linq; using System.Text; +using Semver; using Umbraco.Core.Configuration; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.SqlSyntax; +using Umbraco.Core.Services; namespace Umbraco.Core.Persistence.Migrations.Initial { @@ -38,7 +40,20 @@ namespace Umbraco.Core.Persistence.Migrations.Initial internal IEnumerable DbIndexDefinitions { get; set; } /// - /// Determines the version of the currently installed database. + /// Checks in the db which version is installed based on the migrations that have been run + /// + /// + /// + public SemVersion DetermineInstalledVersionByMigrations(IMigrationEntryService migrationEntryService) + { + var allMigrations = migrationEntryService.GetAll(GlobalSettings.UmbracoMigrationName); + var mostrecent = allMigrations.OrderByDescending(x => x.Version).Select(x => x.Version).FirstOrDefault(); + + return mostrecent ?? new SemVersion(new Version(0, 0, 0)); + } + + /// + /// Determines the version of the currently installed database by detecting the current database structure /// /// /// A with Major and Minor values for @@ -107,6 +122,12 @@ namespace Umbraco.Core.Persistence.Migrations.Initial return new Version(7, 2, 5); } + //if the error is for umbracoDeployChecksum it must be the previous version to 7.4 since that is when it is added + if (Errors.Any(x => x.Item1.Equals("Table") && (x.Item2.InvariantEquals("umbracoDeployChecksum")))) + { + return new Version(7, 3, 4); + } + return UmbracoVersion.Current; } diff --git a/src/Umbraco.Core/Persistence/Migrations/MigrationBase.cs b/src/Umbraco.Core/Persistence/Migrations/MigrationBase.cs index 64ad867008..70821d301f 100644 --- a/src/Umbraco.Core/Persistence/Migrations/MigrationBase.cs +++ b/src/Umbraco.Core/Persistence/Migrations/MigrationBase.cs @@ -42,7 +42,7 @@ namespace Umbraco.Core.Persistence.Migrations public IAlterSyntaxBuilder Alter { - get { return new AlterSyntaxBuilder(Context); } + get { return new AlterSyntaxBuilder(Context, SqlSyntax); } } public ICreateBuilder Create @@ -57,27 +57,27 @@ namespace Umbraco.Core.Persistence.Migrations public IExecuteBuilder Execute { - get { return new ExecuteBuilder(Context); } + get { return new ExecuteBuilder(Context, SqlSyntax); } } public IInsertBuilder Insert { - get { return new InsertBuilder(Context); } + get { return new InsertBuilder(Context, SqlSyntax); } } public IRenameBuilder Rename { - get { return new RenameBuilder(Context); } + get { return new RenameBuilder(Context, SqlSyntax); } } public IUpdateBuilder Update { - get { return new UpdateBuilder(Context); } + get { return new UpdateBuilder(Context, SqlSyntax); } } public IIfDatabaseBuilder IfDatabase(params DatabaseProviders[] databaseProviders) { - return new IfDatabaseBuilder(Context, databaseProviders); + return new IfDatabaseBuilder(Context, SqlSyntax, databaseProviders); } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/MigrationExpressionBase.cs b/src/Umbraco.Core/Persistence/Migrations/MigrationExpressionBase.cs index 5349658c8e..6c3ef66735 100644 --- a/src/Umbraco.Core/Persistence/Migrations/MigrationExpressionBase.cs +++ b/src/Umbraco.Core/Persistence/Migrations/MigrationExpressionBase.cs @@ -42,5 +42,34 @@ namespace Umbraco.Core.Persistence.Migrations /// to ensure they are not executed twice. /// internal string Name { get; set; } + + protected string GetQuotedValue(object val) + { + if (val == null) return "NULL"; + + var type = val.GetType(); + + switch (Type.GetTypeCode(type)) + { + case TypeCode.Boolean: + return ((bool)val) ? "1" : "0"; + case TypeCode.Single: + case TypeCode.Double: + case TypeCode.Decimal: + case TypeCode.SByte: + case TypeCode.Int16: + case TypeCode.Int32: + case TypeCode.Int64: + case TypeCode.Byte: + case TypeCode.UInt16: + case TypeCode.UInt32: + case TypeCode.UInt64: + return val.ToString(); + case TypeCode.DateTime: + return SqlSyntax.GetQuotedValue(SqlSyntax.FormatDateTime((DateTime) val)); + default: + return SqlSyntax.GetQuotedValue(val.ToString()); + } + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/MigrationResolver.cs b/src/Umbraco.Core/Persistence/Migrations/MigrationResolver.cs index 54ee5f507b..2b81424a21 100644 --- a/src/Umbraco.Core/Persistence/Migrations/MigrationResolver.cs +++ b/src/Umbraco.Core/Persistence/Migrations/MigrationResolver.cs @@ -27,5 +27,9 @@ namespace Umbraco.Core.Persistence.Migrations get { return Values; } } + { + ApplicationContext.Current.DatabaseContext.SqlSyntax, + ApplicationContext.Current.ProfilingLogger.Logger + }); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs b/src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs index 90b8933b17..0d4cc66ad5 100644 --- a/src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs +++ b/src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs @@ -1,10 +1,16 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; +using System.Text; +using log4net; +using Semver; +using Umbraco.Core.Configuration; using Umbraco.Core.Events; using Umbraco.Core.Logging; using Umbraco.Core.Persistence.Migrations.Syntax.IfDatabase; using Umbraco.Core.Persistence.SqlSyntax; +using Umbraco.Core.Services; namespace Umbraco.Core.Persistence.Migrations { @@ -14,42 +20,54 @@ namespace Umbraco.Core.Persistence.Migrations /// public class MigrationRunner { - private readonly IMigrationResolver _resolver; + private readonly IMigrationEntryService _migrationEntryService; private readonly ILogger _logger; - private readonly Version _currentVersion; - private readonly Version _targetVersion; + private readonly SemVersion _currentVersion; + private readonly SemVersion _targetVersion; private readonly string _productName; private readonly IMigration[] _migrations; - public MigrationRunner(IMigrationResolver resolver, ILogger logger, Version currentVersion, Version targetVersion, string productName) + [Obsolete("Use the ctor that specifies all dependencies instead")] + public MigrationRunner(ILogger logger, Version currentVersion, Version targetVersion, string productName) + : this(logger, currentVersion, targetVersion, productName, null) { - if (logger == null) throw new ArgumentNullException("logger"); - if (currentVersion == null) throw new ArgumentNullException("currentVersion"); - if (targetVersion == null) throw new ArgumentNullException("targetVersion"); - Mandate.ParameterNotNullOrEmpty(productName, "productName"); - - _resolver = resolver; - _logger = logger; - _currentVersion = currentVersion; - _targetVersion = targetVersion; - _productName = productName; } - + + [Obsolete("Use the ctor that specifies all dependencies instead")] public MigrationRunner(ILogger logger, Version currentVersion, Version targetVersion, string productName, params IMigration[] migrations) + : this(ApplicationContext.Current.Services.MigrationEntryService, logger, new SemVersion(currentVersion), new SemVersion(targetVersion), productName, migrations) { + + } + + public MigrationRunner(IMigrationEntryService migrationEntryService, ILogger logger, SemVersion currentVersion, SemVersion targetVersion, string productName, params IMigration[] migrations) + { + if (migrationEntryService == null) throw new ArgumentNullException("migrationEntryService"); if (logger == null) throw new ArgumentNullException("logger"); if (currentVersion == null) throw new ArgumentNullException("currentVersion"); if (targetVersion == null) throw new ArgumentNullException("targetVersion"); Mandate.ParameterNotNullOrEmpty(productName, "productName"); + _migrationEntryService = migrationEntryService; _logger = logger; _currentVersion = currentVersion; _targetVersion = targetVersion; _productName = productName; - _migrations = migrations; + //ensure this is null if there aren't any + _migrations = migrations.Length == 0 ? null : migrations; } + /// + /// Executes the migrations against the database. + /// + /// The PetaPoco Database, which the migrations will be run against + /// Boolean indicating whether this is an upgrade or downgrade + /// True if migrations were applied, otherwise False + public virtual bool Execute(Database database, bool isUpgrade = true) + { + return Execute(database, database.GetDatabaseProvider(), isUpgrade); + } /// /// Executes the migrations against the database. @@ -70,10 +88,12 @@ namespace Umbraco.Core.Persistence.Migrations ? OrderedUpgradeMigrations(foundMigrations).ToList() : OrderedDowngradeMigrations(foundMigrations).ToList(); - //SD: Why do we want this? - //MCH: Because extensibility ... Mostly relevant to package developers who needs to utilize this type of event to add or remove migrations from the list + if (Migrating.IsRaisedEventCancelled(new MigrationEventArgs(migrations, _currentVersion, _targetVersion, true), this)) + { + _logger.Warn("Migration was cancelled by an event"); return false; + } //Loop through migrations to generate sql var migrationContext = InitializeMigrations(migrations, database, databaseProvider, sqlSyntaxProvider, isUpgrade); @@ -110,17 +130,21 @@ namespace Umbraco.Core.Persistence.Migrations /// public IEnumerable OrderedUpgradeMigrations(IEnumerable foundMigrations) { + //get the version instance to compare with the migrations, this will be a normal c# Version object with only 3 parts + var targetVersionToCompare = _targetVersion.GetVersion(3); + var currentVersionToCompare = _currentVersion.GetVersion(3); + var migrations = (from migration in foundMigrations - let migrationAttributes = migration.GetType().GetCustomAttributes(false) - from migrationAttribute in migrationAttributes - where migrationAttribute != null - where migrationAttribute.TargetVersion > _currentVersion && - migrationAttribute.TargetVersion <= _targetVersion && - migrationAttribute.ProductName == _productName && - //filter if the migration specifies a minimum current version for which to execute - (migrationAttribute.MinimumCurrentVersion == null || _currentVersion >= migrationAttribute.MinimumCurrentVersion) - orderby migrationAttribute.TargetVersion, migrationAttribute.SortOrder ascending - select migration).Distinct(); + let migrationAttributes = migration.GetType().GetCustomAttributes(false) + from migrationAttribute in migrationAttributes + where migrationAttribute != null + where migrationAttribute.TargetVersion > currentVersionToCompare && + migrationAttribute.TargetVersion <= targetVersionToCompare && + migrationAttribute.ProductName == _productName && + //filter if the migration specifies a minimum current version for which to execute + (migrationAttribute.MinimumCurrentVersion == null || currentVersionToCompare >= migrationAttribute.MinimumCurrentVersion) + orderby migrationAttribute.TargetVersion, migrationAttribute.SortOrder ascending + select migration).Distinct(); return migrations; } @@ -131,18 +155,22 @@ namespace Umbraco.Core.Persistence.Migrations /// public IEnumerable OrderedDowngradeMigrations(IEnumerable foundMigrations) { + //get the version instance to compare with the migrations, this will be a normal c# Version object with only 3 parts + var targetVersionToCompare = _targetVersion.GetVersion(3); + var currentVersionToCompare = _currentVersion.GetVersion(3); + var migrations = (from migration in foundMigrations - let migrationAttributes = migration.GetType().GetCustomAttributes(false) - from migrationAttribute in migrationAttributes - where migrationAttribute != null - where - migrationAttribute.TargetVersion > _currentVersion && - migrationAttribute.TargetVersion <= _targetVersion && + let migrationAttributes = migration.GetType().GetCustomAttributes(false) + from migrationAttribute in migrationAttributes + where migrationAttribute != null + where + migrationAttribute.TargetVersion > currentVersionToCompare && + migrationAttribute.TargetVersion <= targetVersionToCompare && migrationAttribute.ProductName == _productName && //filter if the migration specifies a minimum current version for which to execute - (migrationAttribute.MinimumCurrentVersion == null || _currentVersion >= migrationAttribute.MinimumCurrentVersion) - orderby migrationAttribute.TargetVersion, migrationAttribute.SortOrder descending - select migration).Distinct(); + (migrationAttribute.MinimumCurrentVersion == null || currentVersionToCompare >= migrationAttribute.MinimumCurrentVersion) + orderby migrationAttribute.TargetVersion, migrationAttribute.SortOrder descending + select migration).Distinct(); return migrations; } @@ -216,12 +244,59 @@ namespace Umbraco.Core.Persistence.Migrations continue; } - _logger.Info("Executing sql statement " + i + ": " + sql); - database.Execute(sql); + //TODO: We should output all of these SQL calls to files in a migration folder in App_Data/TEMP + // so if people want to executed them manually on another environment, they can. + + //The following ensures the multiple statement sare executed one at a time, this is a requirement + // of SQLCE, it's unfortunate but necessary. + // http://stackoverflow.com/questions/13665491/sql-ce-inconsistent-with-multiple-statements + var sb = new StringBuilder(); + using (var reader = new StringReader(sql)) + { + string line; + while ((line = reader.ReadLine()) != null) + { + line = line.Trim(); + if (line.Equals("GO", StringComparison.OrdinalIgnoreCase)) + { + //Execute the SQL up to the point of a GO statement + var exeSql = sb.ToString(); + _logger.Info("Executing sql statement " + i + ": " + exeSql); + database.Execute(exeSql); + + //restart the string builder + sb.Remove(0, sb.Length); + } + else + { + sb.AppendLine(line); + } + } + //execute anything remaining + if (sb.Length > 0) + { + var exeSql = sb.ToString(); + _logger.Info("Executing sql statement " + i + ": " + exeSql); + database.Execute(exeSql); + } + } + i++; } transaction.Complete(); + + //Now that this is all complete, we need to add an entry to the migrations table flagging that migrations + // for this version have executed. + //NOTE: We CANNOT do this as part of the transaction!!! This is because when upgrading to 7.3, we cannot + // create the migrations table and then add data to it in the same transaction without issuing things like GO + // commands and since we need to support all Dbs, we need to just do this after the fact. + var exists = _migrationEntryService.FindEntry(GlobalSettings.UmbracoMigrationName, _targetVersion); + if (exists == null) + { + _migrationEntryService.CreateEntry(_productName, _targetVersion); + } + } } diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/AlterSyntaxBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/AlterSyntaxBuilder.cs index 93499141ca..e253f8e607 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/AlterSyntaxBuilder.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/AlterSyntaxBuilder.cs @@ -1,30 +1,35 @@ using Umbraco.Core.Persistence.Migrations.Syntax.Alter.Column; using Umbraco.Core.Persistence.Migrations.Syntax.Alter.Expressions; using Umbraco.Core.Persistence.Migrations.Syntax.Alter.Table; +using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Syntax.Alter { public class AlterSyntaxBuilder : IAlterSyntaxBuilder { private readonly IMigrationContext _context; + private readonly ISqlSyntaxProvider _sqlSyntax; + private readonly DatabaseProviders[] _databaseProviders; - public AlterSyntaxBuilder(IMigrationContext context) + public AlterSyntaxBuilder(IMigrationContext context, ISqlSyntaxProvider sqlSyntax, params DatabaseProviders[] databaseProviders) { _context = context; + _sqlSyntax = sqlSyntax; + _databaseProviders = databaseProviders; } public IAlterTableSyntax Table(string tableName) { - var expression = new AlterTableExpression(_context.SqlSyntax, _context.CurrentDatabaseProvider) { TableName = tableName }; + var expression = new AlterTableExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax) { TableName = tableName }; //_context.Expressions.Add(expression); - return new AlterTableBuilder(expression, _context); + return new AlterTableBuilder(_context, _databaseProviders, expression); } public IAlterColumnSyntax Column(string columnName) { - var expression = new AlterColumnExpression(_context.SqlSyntax, _context.CurrentDatabaseProvider) { Column = { Name = columnName } }; + var expression = new AlterColumnExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax) {Column = {Name = columnName}}; //_context.Expressions.Add(expression); - return new AlterColumnBuilder(expression, _context); + return new AlterColumnBuilder(_context, _databaseProviders, expression); } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Column/AlterColumnBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Column/AlterColumnBuilder.cs index 3eaecb00bd..a7077f2d35 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Column/AlterColumnBuilder.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Column/AlterColumnBuilder.cs @@ -2,6 +2,7 @@ using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Migrations.Syntax.Alter.Expressions; using Umbraco.Core.Persistence.Migrations.Syntax.Expressions; +using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Syntax.Alter.Column { @@ -11,11 +12,13 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Alter.Column IAlterColumnOptionForeignKeyCascadeSyntax { private readonly IMigrationContext _context; + private readonly DatabaseProviders[] _databaseProviders; - public AlterColumnBuilder(AlterColumnExpression expression, IMigrationContext context) + public AlterColumnBuilder(IMigrationContext context, DatabaseProviders[] databaseProviders, AlterColumnExpression expression) : base(expression) { _context = context; + _databaseProviders = databaseProviders; } public ForeignKeyDefinition CurrentForeignKey { get; set; } @@ -33,7 +36,7 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Alter.Column public IAlterColumnOptionSyntax WithDefault(SystemMethods method) { - var dc = new AlterDefaultConstraintExpression(_context.SqlSyntax, _context.CurrentDatabaseProvider) + var dc = new AlterDefaultConstraintExpression(_context.CurrentDatabaseProvider, _databaseProviders, Expression.SqlSyntax) { TableName = Expression.TableName, SchemaName = Expression.SchemaName, @@ -51,7 +54,7 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Alter.Column public IAlterColumnOptionSyntax WithDefaultValue(object value) { - var dc = new AlterDefaultConstraintExpression(_context.SqlSyntax, _context.CurrentDatabaseProvider) + var dc = new AlterDefaultConstraintExpression(_context.CurrentDatabaseProvider, _databaseProviders, Expression.SqlSyntax) { TableName = Expression.TableName, SchemaName = Expression.SchemaName, @@ -81,15 +84,12 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Alter.Column { Expression.Column.IsIndexed = true; - var index = new CreateIndexExpression(_context.SqlSyntax, _context.CurrentDatabaseProvider) - { - Index = new IndexDefinition - { - Name = indexName, - SchemaName = Expression.SchemaName, - TableName = Expression.TableName - } - }; + var index = new CreateIndexExpression(_context.CurrentDatabaseProvider, _databaseProviders, Expression.SqlSyntax, new IndexDefinition + { + Name = indexName, + SchemaName = Expression.SchemaName, + TableName = Expression.TableName + }); index.Index.Columns.Add(new IndexColumnDefinition { @@ -135,16 +135,13 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Alter.Column { Expression.Column.IsUnique = true; - var index = new CreateIndexExpression(_context.SqlSyntax, _context.CurrentDatabaseProvider) - { - Index = new IndexDefinition - { - Name = indexName, - SchemaName = Expression.SchemaName, - TableName = Expression.TableName, - IsUnique = true - } - }; + var index = new CreateIndexExpression(_context.CurrentDatabaseProvider, _databaseProviders, Expression.SqlSyntax, new IndexDefinition + { + Name = indexName, + SchemaName = Expression.SchemaName, + TableName = Expression.TableName, + IsUnique = true + }); index.Index.Columns.Add(new IndexColumnDefinition { @@ -172,17 +169,14 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Alter.Column { Expression.Column.IsForeignKey = true; - var fk = new CreateForeignKeyExpression(_context.SqlSyntax, _context.CurrentDatabaseProvider) - { - ForeignKey = new ForeignKeyDefinition - { - Name = foreignKeyName, - PrimaryTable = primaryTableName, - PrimaryTableSchema = primaryTableSchema, - ForeignTable = Expression.TableName, - ForeignTableSchema = Expression.SchemaName - } - }; + var fk = new CreateForeignKeyExpression(_context.CurrentDatabaseProvider, _databaseProviders, Expression.SqlSyntax, new ForeignKeyDefinition + { + Name = foreignKeyName, + PrimaryTable = primaryTableName, + PrimaryTableSchema = primaryTableSchema, + ForeignTable = Expression.TableName, + ForeignTableSchema = Expression.SchemaName + }); fk.ForeignKey.PrimaryColumns.Add(primaryColumnName); fk.ForeignKey.ForeignColumns.Add(Expression.Column.Name); @@ -212,17 +206,14 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Alter.Column public IAlterColumnOptionForeignKeyCascadeSyntax ReferencedBy(string foreignKeyName, string foreignTableSchema, string foreignTableName, string foreignColumnName) { - var fk = new CreateForeignKeyExpression(_context.SqlSyntax, _context.CurrentDatabaseProvider) - { - ForeignKey = new ForeignKeyDefinition - { - Name = foreignKeyName, - PrimaryTable = Expression.TableName, - PrimaryTableSchema = Expression.SchemaName, - ForeignTable = foreignTableName, - ForeignTableSchema = foreignTableSchema - } - }; + var fk = new CreateForeignKeyExpression(_context.CurrentDatabaseProvider, _databaseProviders, Expression.SqlSyntax, new ForeignKeyDefinition + { + Name = foreignKeyName, + PrimaryTable = Expression.TableName, + PrimaryTableSchema = Expression.SchemaName, + ForeignTable = foreignTableName, + ForeignTableSchema = foreignTableSchema + }); fk.ForeignKey.PrimaryColumns.Add(Expression.Column.Name); fk.ForeignKey.ForeignColumns.Add(foreignColumnName); diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Expressions/AlterColumnExpression.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Expressions/AlterColumnExpression.cs index 578210f018..77234d1cfb 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Expressions/AlterColumnExpression.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Expressions/AlterColumnExpression.cs @@ -6,30 +6,27 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Alter.Expressions { public class AlterColumnExpression : MigrationExpressionBase { - public AlterColumnExpression(ISqlSyntaxProvider sqlSyntax, DatabaseProviders currentDatabaseProvider, DatabaseProviders[] supportedDatabaseProviders = null) - : base(sqlSyntax, currentDatabaseProvider, supportedDatabaseProviders) + + public AlterColumnExpression(DatabaseProviders current, DatabaseProviders[] databaseProviders, ISqlSyntaxProvider sqlSyntax) + : base(current, databaseProviders, sqlSyntax) { Column = new ColumnDefinition() { ModificationType = ModificationType.Alter }; } + + + public virtual string SchemaName { get; set; } public virtual string TableName { get; set; } public virtual ColumnDefinition Column { get; set; } public override string ToString() { - //string columnNameFormat = string.Format("{0} {1}", - // SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName(Column.Name), - // SqlSyntaxContext.SqlSyntaxProvider.Format(Column)); - return string.Format( - SqlSyntax.AlterColumn, - SqlSyntax.GetQuotedTableName(TableName), - SqlSyntax.Format(Column)); + return string.Format(SqlSyntax.AlterColumn, + SqlSyntax.GetQuotedTableName(TableName), + SqlSyntax.Format(Column)); - //return string.Format(SqlSyntaxContext.SqlSyntaxProvider.AlterColumn, - // SqlSyntaxContext.SqlSyntaxProvider.GetQuotedTableName(TableName), - // SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName(Column.Name)); } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Expressions/AlterDefaultConstraintExpression.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Expressions/AlterDefaultConstraintExpression.cs index 9998db7fc4..cd83f55a35 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Expressions/AlterDefaultConstraintExpression.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Expressions/AlterDefaultConstraintExpression.cs @@ -4,8 +4,8 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Alter.Expressions { public class AlterDefaultConstraintExpression : MigrationExpressionBase { - public AlterDefaultConstraintExpression(ISqlSyntaxProvider sqlSyntax, DatabaseProviders currentDatabaseProvider, DatabaseProviders[] supportedDatabaseProviders = null) - : base(sqlSyntax, currentDatabaseProvider, supportedDatabaseProviders) + public AlterDefaultConstraintExpression(DatabaseProviders current, DatabaseProviders[] databaseProviders, ISqlSyntaxProvider sqlSyntax) + : base(current, databaseProviders, sqlSyntax) { } diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Expressions/AlterTableExpression.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Expressions/AlterTableExpression.cs index 9e09b39e70..b5906ed324 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Expressions/AlterTableExpression.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Expressions/AlterTableExpression.cs @@ -4,8 +4,9 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Alter.Expressions { public class AlterTableExpression : MigrationExpressionBase { - public AlterTableExpression(ISqlSyntaxProvider sqlSyntax, DatabaseProviders currentDatabaseProvider, DatabaseProviders[] supportedDatabaseProviders = null) - : base(sqlSyntax, currentDatabaseProvider, supportedDatabaseProviders) + + public AlterTableExpression(DatabaseProviders current, DatabaseProviders[] databaseProviders, ISqlSyntaxProvider sqlSyntax) + : base(current, databaseProviders, sqlSyntax) { } diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Table/AlterTableBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Table/AlterTableBuilder.cs index 150d662b7b..7a6ab8d47a 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Table/AlterTableBuilder.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Table/AlterTableBuilder.cs @@ -2,6 +2,7 @@ using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Migrations.Syntax.Alter.Expressions; using Umbraco.Core.Persistence.Migrations.Syntax.Expressions; +using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Syntax.Alter.Table { @@ -10,11 +11,13 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Alter.Table IAlterTableColumnOptionForeignKeyCascadeSyntax { private readonly IMigrationContext _context; + private readonly DatabaseProviders[] _databaseProviders; - public AlterTableBuilder(AlterTableExpression expression, IMigrationContext context) + public AlterTableBuilder(IMigrationContext context, DatabaseProviders[] databaseProviders, AlterTableExpression expression) : base(expression) { _context = context; + _databaseProviders = databaseProviders; } public ColumnDefinition CurrentColumn { get; set; } @@ -36,7 +39,7 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Alter.Table { if (CurrentColumn.ModificationType == ModificationType.Alter) { - var dc = new AlterDefaultConstraintExpression(_context.SqlSyntax, _context.CurrentDatabaseProvider) + var dc = new AlterDefaultConstraintExpression(_context.CurrentDatabaseProvider, _databaseProviders, Expression.SqlSyntax) { TableName = Expression.TableName, SchemaName = Expression.SchemaName, @@ -66,15 +69,12 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Alter.Table { CurrentColumn.IsIndexed = true; - var index = new CreateIndexExpression(_context.SqlSyntax, _context.CurrentDatabaseProvider) - { - Index = new IndexDefinition - { - Name = indexName, - SchemaName = Expression.SchemaName, - TableName = Expression.TableName - } - }; + var index = new CreateIndexExpression(_context.CurrentDatabaseProvider, _databaseProviders, Expression.SqlSyntax, new IndexDefinition + { + Name = indexName, + SchemaName = Expression.SchemaName, + TableName = Expression.TableName + }); index.Index.Columns.Add(new IndexColumnDefinition { @@ -120,16 +120,13 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Alter.Table { CurrentColumn.IsUnique = true; - var index = new CreateIndexExpression(_context.SqlSyntax, _context.CurrentDatabaseProvider) - { - Index = new IndexDefinition - { - Name = indexName, - SchemaName = Expression.SchemaName, - TableName = Expression.TableName, - IsUnique = true - } - }; + var index = new CreateIndexExpression(_context.CurrentDatabaseProvider, _databaseProviders, Expression.SqlSyntax, new IndexDefinition + { + Name = indexName, + SchemaName = Expression.SchemaName, + TableName = Expression.TableName, + IsUnique = true + }); index.Index.Columns.Add(new IndexColumnDefinition { @@ -157,17 +154,14 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Alter.Table { CurrentColumn.IsForeignKey = true; - var fk = new CreateForeignKeyExpression(_context.SqlSyntax, _context.CurrentDatabaseProvider) - { - ForeignKey = new ForeignKeyDefinition - { - Name = foreignKeyName, - PrimaryTable = primaryTableName, - PrimaryTableSchema = primaryTableSchema, - ForeignTable = Expression.TableName, - ForeignTableSchema = Expression.SchemaName - } - }; + var fk = new CreateForeignKeyExpression(_context.CurrentDatabaseProvider, _databaseProviders, Expression.SqlSyntax, new ForeignKeyDefinition + { + Name = foreignKeyName, + PrimaryTable = primaryTableName, + PrimaryTableSchema = primaryTableSchema, + ForeignTable = Expression.TableName, + ForeignTableSchema = Expression.SchemaName + }); fk.ForeignKey.PrimaryColumns.Add(primaryColumnName); fk.ForeignKey.ForeignColumns.Add(CurrentColumn.Name); @@ -197,17 +191,14 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Alter.Table public IAlterTableColumnOptionForeignKeyCascadeSyntax ReferencedBy(string foreignKeyName, string foreignTableSchema, string foreignTableName, string foreignColumnName) { - var fk = new CreateForeignKeyExpression(_context.SqlSyntax, _context.CurrentDatabaseProvider) - { - ForeignKey = new ForeignKeyDefinition - { - Name = foreignKeyName, - PrimaryTable = Expression.TableName, - PrimaryTableSchema = Expression.SchemaName, - ForeignTable = foreignTableName, - ForeignTableSchema = foreignTableSchema - } - }; + var fk = new CreateForeignKeyExpression(_context.CurrentDatabaseProvider, _databaseProviders, Expression.SqlSyntax, new ForeignKeyDefinition + { + Name = foreignKeyName, + PrimaryTable = Expression.TableName, + PrimaryTableSchema = Expression.SchemaName, + ForeignTable = foreignTableName, + ForeignTableSchema = foreignTableSchema + }); fk.ForeignKey.PrimaryColumns.Add(CurrentColumn.Name); fk.ForeignKey.ForeignColumns.Add(foreignColumnName); @@ -220,7 +211,7 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Alter.Table public IAlterTableColumnTypeSyntax AddColumn(string name) { var column = new ColumnDefinition { Name = name, ModificationType = ModificationType.Create }; - var createColumn = new CreateColumnExpression(_context.SqlSyntax, _context.CurrentDatabaseProvider) + var createColumn = new CreateColumnExpression(_context.CurrentDatabaseProvider, _databaseProviders, Expression.SqlSyntax) { Column = column, SchemaName = Expression.SchemaName, @@ -236,12 +227,12 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Alter.Table public IAlterTableColumnTypeSyntax AlterColumn(string name) { var column = new ColumnDefinition { Name = name, ModificationType = ModificationType.Alter }; - var alterColumn = new AlterColumnExpression(_context.SqlSyntax, _context.CurrentDatabaseProvider) - { - Column = column, - SchemaName = Expression.SchemaName, - TableName = Expression.TableName - }; + var alterColumn = new AlterColumnExpression(_context.CurrentDatabaseProvider, _databaseProviders, Expression.SqlSyntax) + { + Column = column, + SchemaName = Expression.SchemaName, + TableName = Expression.TableName + }; CurrentColumn = column; diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Column/CreateColumnBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Column/CreateColumnBuilder.cs index b494d858dc..d004cd1176 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Column/CreateColumnBuilder.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Column/CreateColumnBuilder.cs @@ -1,6 +1,7 @@ using System.Data; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Migrations.Syntax.Expressions; +using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Syntax.Create.Column { @@ -10,11 +11,13 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Create.Column ICreateColumnOptionForeignKeyCascadeSyntax { private readonly IMigrationContext _context; + private readonly DatabaseProviders[] _databaseProviders; - public CreateColumnBuilder(CreateColumnExpression expression, IMigrationContext context) + public CreateColumnBuilder(IMigrationContext context, DatabaseProviders[] databaseProviders, CreateColumnExpression expression) : base(expression) { _context = context; + _databaseProviders = databaseProviders; } public ForeignKeyDefinition CurrentForeignKey { get; set; } @@ -56,15 +59,12 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Create.Column { Expression.Column.IsIndexed = true; - var index = new CreateIndexExpression(_context.SqlSyntax, _context.CurrentDatabaseProvider) - { - Index = new IndexDefinition - { - Name = indexName, - SchemaName = Expression.SchemaName, - TableName = Expression.TableName - } - }; + var index = new CreateIndexExpression(_context.CurrentDatabaseProvider, _databaseProviders, Expression.SqlSyntax, new IndexDefinition + { + Name = indexName, + SchemaName = Expression.SchemaName, + TableName = Expression.TableName + }); index.Index.Columns.Add(new IndexColumnDefinition { @@ -110,16 +110,13 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Create.Column { Expression.Column.IsUnique = true; - var index = new CreateIndexExpression(_context.SqlSyntax, _context.CurrentDatabaseProvider) - { - Index = new IndexDefinition - { - Name = indexName, - SchemaName = Expression.SchemaName, - TableName = Expression.TableName, - IsUnique = true - } - }; + var index = new CreateIndexExpression(_context.CurrentDatabaseProvider, _databaseProviders, Expression.SqlSyntax, new IndexDefinition + { + Name = indexName, + SchemaName = Expression.SchemaName, + TableName = Expression.TableName, + IsUnique = true + }); index.Index.Columns.Add(new IndexColumnDefinition { @@ -147,17 +144,14 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Create.Column { Expression.Column.IsForeignKey = true; - var fk = new CreateForeignKeyExpression(_context.SqlSyntax, _context.CurrentDatabaseProvider) - { - ForeignKey = new ForeignKeyDefinition - { - Name = foreignKeyName, - PrimaryTable = primaryTableName, - PrimaryTableSchema = primaryTableSchema, - ForeignTable = Expression.TableName, - ForeignTableSchema = Expression.SchemaName - } - }; + var fk = new CreateForeignKeyExpression(_context.CurrentDatabaseProvider, _databaseProviders, Expression.SqlSyntax, new ForeignKeyDefinition + { + Name = foreignKeyName, + PrimaryTable = primaryTableName, + PrimaryTableSchema = primaryTableSchema, + ForeignTable = Expression.TableName, + ForeignTableSchema = Expression.SchemaName + }); fk.ForeignKey.PrimaryColumns.Add(primaryColumnName); fk.ForeignKey.ForeignColumns.Add(Expression.Column.Name); @@ -187,17 +181,14 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Create.Column public ICreateColumnOptionForeignKeyCascadeSyntax ReferencedBy(string foreignKeyName, string foreignTableSchema, string foreignTableName, string foreignColumnName) { - var fk = new CreateForeignKeyExpression(_context.SqlSyntax, _context.CurrentDatabaseProvider) - { - ForeignKey = new ForeignKeyDefinition - { - Name = foreignKeyName, - PrimaryTable = Expression.TableName, - PrimaryTableSchema = Expression.SchemaName, - ForeignTable = foreignTableName, - ForeignTableSchema = foreignTableSchema - } - }; + var fk = new CreateForeignKeyExpression(_context.CurrentDatabaseProvider, _databaseProviders, Expression.SqlSyntax, new ForeignKeyDefinition + { + Name = foreignKeyName, + PrimaryTable = Expression.TableName, + PrimaryTableSchema = Expression.SchemaName, + ForeignTable = foreignTableName, + ForeignTableSchema = foreignTableSchema + }); fk.ForeignKey.PrimaryColumns.Add(Expression.Column.Name); fk.ForeignKey.ForeignColumns.Add(foreignColumnName); diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/CreateBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/CreateBuilder.cs index 56e0d5632d..2bf6da8ec0 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/CreateBuilder.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/CreateBuilder.cs @@ -18,74 +18,65 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Create public CreateBuilder(IMigrationContext context, params DatabaseProviders[] databaseProviders) { + if (context == null) throw new ArgumentNullException("context"); + if (sqlSyntax == null) throw new ArgumentNullException("sqlSyntax"); + _context = context; _databaseProviders = databaseProviders; } public ICreateTableWithColumnSyntax Table(string tableName) { - var expression = new CreateTableExpression(_context.SqlSyntax, _context.CurrentDatabaseProvider, _databaseProviders) - { - TableName = tableName - }; + var expression = new CreateTableExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax) { TableName = tableName }; _context.Expressions.Add(expression); - return new CreateTableBuilder(expression, _context); + return new CreateTableBuilder(_context, _databaseProviders, expression); } public ICreateColumnOnTableSyntax Column(string columnName) { - var expression = new CreateColumnExpression(_context.SqlSyntax, _context.CurrentDatabaseProvider, _databaseProviders) - { - Column = { Name = columnName } - }; + var expression = new CreateColumnExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax) { Column = { Name = columnName } }; _context.Expressions.Add(expression); - return new CreateColumnBuilder(expression, _context); + return new CreateColumnBuilder(_context, _databaseProviders, expression); } public ICreateForeignKeyFromTableSyntax ForeignKey() { - var expression = new CreateForeignKeyExpression(_context.SqlSyntax, _context.CurrentDatabaseProvider, _databaseProviders); + var expression = new CreateForeignKeyExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax); _context.Expressions.Add(expression); return new CreateForeignKeyBuilder(expression); } public ICreateForeignKeyFromTableSyntax ForeignKey(string foreignKeyName) { - var expression = new CreateForeignKeyExpression(_context.SqlSyntax, _context.CurrentDatabaseProvider, _databaseProviders) - { - ForeignKey = { Name = foreignKeyName } - }; + var expression = new CreateForeignKeyExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax) { ForeignKey = { Name = foreignKeyName } }; _context.Expressions.Add(expression); return new CreateForeignKeyBuilder(expression); } public ICreateIndexForTableSyntax Index() { - var expression = new CreateIndexExpression(_context.SqlSyntax, _context.CurrentDatabaseProvider, _databaseProviders); + var expression = new CreateIndexExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax); _context.Expressions.Add(expression); return new CreateIndexBuilder(expression); } public ICreateIndexForTableSyntax Index(string indexName) { - var expression = new CreateIndexExpression(_context.SqlSyntax, _context.CurrentDatabaseProvider, _databaseProviders) - { - Index = { Name = indexName } - }; + var expression = new CreateIndexExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax) { Index = { Name = indexName } }; _context.Expressions.Add(expression); return new CreateIndexBuilder(expression); } public ICreateConstraintOnTableSyntax PrimaryKey() { - var expression = new CreateConstraintExpression(_context.SqlSyntax, _context.CurrentDatabaseProvider, ConstraintType.PrimaryKey, _databaseProviders); + var expression = new CreateConstraintExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax, ConstraintType.PrimaryKey); _context.Expressions.Add(expression); return new CreateConstraintBuilder(expression); } public ICreateConstraintOnTableSyntax PrimaryKey(string primaryKeyName) { - var expression = new CreateConstraintExpression(_context.SqlSyntax, _context.CurrentDatabaseProvider, ConstraintType.PrimaryKey, _databaseProviders); + var expression = new CreateConstraintExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax, ConstraintType.PrimaryKey); expression.Constraint.ConstraintName = primaryKeyName; _context.Expressions.Add(expression); return new CreateConstraintBuilder(expression); @@ -93,14 +84,14 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Create public ICreateConstraintOnTableSyntax UniqueConstraint() { - var expression = new CreateConstraintExpression(_context.SqlSyntax, _context.CurrentDatabaseProvider, ConstraintType.Unique, _databaseProviders); + var expression = new CreateConstraintExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax, ConstraintType.Unique); _context.Expressions.Add(expression); return new CreateConstraintBuilder(expression); } public ICreateConstraintOnTableSyntax UniqueConstraint(string constraintName) { - var expression = new CreateConstraintExpression(_context.SqlSyntax, _context.CurrentDatabaseProvider, ConstraintType.Unique, _databaseProviders); + var expression = new CreateConstraintExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax, ConstraintType.Unique); expression.Constraint.ConstraintName = constraintName; _context.Expressions.Add(expression); return new CreateConstraintBuilder(expression); @@ -108,7 +99,7 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Create public ICreateConstraintOnTableSyntax Constraint(string constraintName) { - var expression = new CreateConstraintExpression(_context.SqlSyntax, _context.CurrentDatabaseProvider, ConstraintType.NonUnique, _databaseProviders); + var expression = new CreateConstraintExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax, ConstraintType.NonUnique); expression.Constraint.ConstraintName = constraintName; _context.Expressions.Add(expression); return new CreateConstraintBuilder(expression); diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Expressions/CreateConstraintExpression.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Expressions/CreateConstraintExpression.cs index 1184114bd4..a06775929b 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Expressions/CreateConstraintExpression.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Expressions/CreateConstraintExpression.cs @@ -6,11 +6,12 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Create.Expressions { public class CreateConstraintExpression : MigrationExpressionBase { - public CreateConstraintExpression(ISqlSyntaxProvider sqlSyntax, DatabaseProviders currentDatabaseProvider, ConstraintType constraintType, DatabaseProviders[] supportedDatabaseProviders = null) - : base(sqlSyntax, currentDatabaseProvider, supportedDatabaseProviders) + public CreateConstraintExpression(DatabaseProviders current, DatabaseProviders[] databaseProviders, ISqlSyntaxProvider sqlSyntax, ConstraintType constraint) + : base(current, databaseProviders, sqlSyntax) { - Constraint = new ConstraintDefinition(constraintType); + Constraint = new ConstraintDefinition(constraint); } + public ConstraintDefinition Constraint { get; private set; } diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Expressions/CreateTableExpression.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Expressions/CreateTableExpression.cs index 8755306b13..57d08b43ef 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Expressions/CreateTableExpression.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Expressions/CreateTableExpression.cs @@ -7,8 +7,10 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Create.Expressions { public class CreateTableExpression : MigrationExpressionBase { - public CreateTableExpression(ISqlSyntaxProvider sqlSyntax, DatabaseProviders currentDatabaseProvider, DatabaseProviders[] supportedDatabaseProviders = null) - : base(sqlSyntax, currentDatabaseProvider, supportedDatabaseProviders) + + + public CreateTableExpression(DatabaseProviders current, DatabaseProviders[] databaseProviders, ISqlSyntaxProvider sqlSyntax) + : base(current, databaseProviders, sqlSyntax) { Columns = new List(); } diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Table/CreateTableBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Table/CreateTableBuilder.cs index 636d35084f..86760b71bc 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Table/CreateTableBuilder.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Table/CreateTableBuilder.cs @@ -2,6 +2,7 @@ using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Migrations.Syntax.Create.Expressions; using Umbraco.Core.Persistence.Migrations.Syntax.Expressions; +using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Syntax.Create.Table { @@ -11,11 +12,13 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Create.Table ICreateTableColumnOptionForeignKeyCascadeSyntax { private readonly IMigrationContext _context; + private readonly DatabaseProviders[] _databaseProviders; - public CreateTableBuilder(CreateTableExpression expression, IMigrationContext context) + public CreateTableBuilder(IMigrationContext context, DatabaseProviders[] databaseProviders, CreateTableExpression expression) : base(expression) { _context = context; + _databaseProviders = databaseProviders; } public ColumnDefinition CurrentColumn { get; set; } @@ -62,15 +65,12 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Create.Table { CurrentColumn.IsIndexed = true; - var index = new CreateIndexExpression(_context.SqlSyntax, _context.CurrentDatabaseProvider) - { - Index = new IndexDefinition - { - Name = indexName, - SchemaName = Expression.SchemaName, - TableName = Expression.TableName - } - }; + var index = new CreateIndexExpression(_context.CurrentDatabaseProvider, _databaseProviders, Expression.SqlSyntax, new IndexDefinition + { + Name = indexName, + SchemaName = Expression.SchemaName, + TableName = Expression.TableName + }); index.Index.Columns.Add(new IndexColumnDefinition { @@ -86,15 +86,25 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Create.Table { CurrentColumn.IsPrimaryKey = true; - var expression = new CreateConstraintExpression(_context.SqlSyntax, _context.CurrentDatabaseProvider, ConstraintType.PrimaryKey) + //For MySQL, the PK will be created WITH the create table expression, however for + // SQL Server, the PK get's created in a different Alter table expression afterwords. + // MySQL will choke if the same constraint is again added afterword + // TODO: This is a super hack, I'd rather not add another property like 'CreatesPkInCreateTableDefinition' to check + // for this, but I don't see another way around. MySQL doesn't support checking for a constraint before creating + // it... except in a very strange way but it doesn't actually provider error feedback if it doesn't work so we cannot use + // it. For now, this is what I'm doing + if (Expression.CurrentDatabaseProvider != DatabaseProviders.MySql) { - Constraint = + var expression = new CreateConstraintExpression(_context.CurrentDatabaseProvider, _databaseProviders, Expression.SqlSyntax, ConstraintType.PrimaryKey) + { + Constraint = { TableName = CurrentColumn.TableName, Columns = new[] { CurrentColumn.Name } } - }; - _context.Expressions.Add(expression); + }; + _context.Expressions.Add(expression); + } return this; } @@ -104,16 +114,27 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Create.Table CurrentColumn.IsPrimaryKey = true; CurrentColumn.PrimaryKeyName = primaryKeyName; - var expression = new CreateConstraintExpression(_context.SqlSyntax, _context.CurrentDatabaseProvider, ConstraintType.PrimaryKey) + //For MySQL, the PK will be created WITH the create table expression, however for + // SQL Server, the PK get's created in a different Alter table expression afterwords. + // MySQL will choke if the same constraint is again added afterword + // TODO: This is a super hack, I'd rather not add another property like 'CreatesPkInCreateTableDefinition' to check + // for this, but I don't see another way around. MySQL doesn't support checking for a constraint before creating + // it... except in a very strange way but it doesn't actually provider error feedback if it doesn't work so we cannot use + // it. For now, this is what I'm doing + + if (Expression.CurrentDatabaseProvider != DatabaseProviders.MySql) { - Constraint = + var expression = new CreateConstraintExpression(_context.CurrentDatabaseProvider, _databaseProviders, Expression.SqlSyntax, ConstraintType.PrimaryKey) + { + Constraint = { ConstraintName = primaryKeyName, TableName = CurrentColumn.TableName, Columns = new[] { CurrentColumn.Name } } - }; - _context.Expressions.Add(expression); + }; + _context.Expressions.Add(expression); + } return this; } @@ -139,16 +160,13 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Create.Table { CurrentColumn.IsUnique = true; - var index = new CreateIndexExpression(_context.SqlSyntax, _context.CurrentDatabaseProvider) - { - Index = new IndexDefinition - { - Name = indexName, - SchemaName = Expression.SchemaName, - TableName = Expression.TableName, - IsUnique = true - } - }; + var index = new CreateIndexExpression(_context.CurrentDatabaseProvider, _databaseProviders, Expression.SqlSyntax, new IndexDefinition + { + Name = indexName, + SchemaName = Expression.SchemaName, + TableName = Expression.TableName, + IsUnique = true + }); index.Index.Columns.Add(new IndexColumnDefinition { @@ -176,17 +194,14 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Create.Table { CurrentColumn.IsForeignKey = true; - var fk = new CreateForeignKeyExpression(_context.SqlSyntax, _context.CurrentDatabaseProvider) - { - ForeignKey = new ForeignKeyDefinition - { - Name = foreignKeyName, - PrimaryTable = primaryTableName, - PrimaryTableSchema = primaryTableSchema, - ForeignTable = Expression.TableName, - ForeignTableSchema = Expression.SchemaName - } - }; + var fk = new CreateForeignKeyExpression(_context.CurrentDatabaseProvider, _databaseProviders, Expression.SqlSyntax, new ForeignKeyDefinition + { + Name = foreignKeyName, + PrimaryTable = primaryTableName, + PrimaryTableSchema = primaryTableSchema, + ForeignTable = Expression.TableName, + ForeignTableSchema = Expression.SchemaName + }); fk.ForeignKey.PrimaryColumns.Add(primaryColumnName); fk.ForeignKey.ForeignColumns.Add(CurrentColumn.Name); @@ -216,17 +231,14 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Create.Table public ICreateTableColumnOptionForeignKeyCascadeSyntax ReferencedBy(string foreignKeyName, string foreignTableSchema, string foreignTableName, string foreignColumnName) { - var fk = new CreateForeignKeyExpression(_context.SqlSyntax, _context.CurrentDatabaseProvider) - { - ForeignKey = new ForeignKeyDefinition - { - Name = foreignKeyName, - PrimaryTable = Expression.TableName, - PrimaryTableSchema = Expression.SchemaName, - ForeignTable = foreignTableName, - ForeignTableSchema = foreignTableSchema - } - }; + var fk = new CreateForeignKeyExpression(_context.CurrentDatabaseProvider, _databaseProviders, Expression.SqlSyntax, new ForeignKeyDefinition + { + Name = foreignKeyName, + PrimaryTable = Expression.TableName, + PrimaryTableSchema = Expression.SchemaName, + ForeignTable = foreignTableName, + ForeignTableSchema = foreignTableSchema + }); fk.ForeignKey.PrimaryColumns.Add(CurrentColumn.Name); fk.ForeignKey.ForeignColumns.Add(foreignColumnName); diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/DeleteBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/DeleteBuilder.cs index 878c2b8108..947005c856 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/DeleteBuilder.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/DeleteBuilder.cs @@ -23,70 +23,55 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete public void Table(string tableName) { - var expression = new DeleteTableExpression(_context.SqlSyntax, _context.CurrentDatabaseProvider, _databaseProviders) - { - TableName = tableName - }; + var expression = new DeleteTableExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax) { TableName = tableName }; _context.Expressions.Add(expression); } public IDeleteColumnFromTableSyntax Column(string columnName) { - var expression = new DeleteColumnExpression(_context.SqlSyntax, _context.CurrentDatabaseProvider, _databaseProviders) - { - ColumnNames = { columnName } - }; + var expression = new DeleteColumnExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax) {ColumnNames = {columnName}}; _context.Expressions.Add(expression); return new DeleteColumnBuilder(expression); } public IDeleteForeignKeyFromTableSyntax ForeignKey() { - var expression = new DeleteForeignKeyExpression(_context.SqlSyntax, _context.CurrentDatabaseProvider, _databaseProviders); + var expression = new DeleteForeignKeyExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax); _context.Expressions.Add(expression); return new DeleteForeignKeyBuilder(expression); } public IDeleteForeignKeyOnTableSyntax ForeignKey(string foreignKeyName) { - var expression = new DeleteForeignKeyExpression(_context.SqlSyntax, _context.CurrentDatabaseProvider, _databaseProviders) - { - ForeignKey = { Name = foreignKeyName } - }; + var expression = new DeleteForeignKeyExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax) {ForeignKey = {Name = foreignKeyName}}; _context.Expressions.Add(expression); return new DeleteForeignKeyBuilder(expression); } public IDeleteDataSyntax FromTable(string tableName) { - var expression = new DeleteDataExpression(_context.SqlSyntax, _context.CurrentDatabaseProvider, _databaseProviders) - { - TableName = tableName - }; + var expression = new DeleteDataExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax) { TableName = tableName }; _context.Expressions.Add(expression); return new DeleteDataBuilder(expression); } public IDeleteIndexForTableSyntax Index() { - var expression = new DeleteIndexExpression(_context.SqlSyntax, _context.CurrentDatabaseProvider, _databaseProviders); + var expression = new DeleteIndexExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax); _context.Expressions.Add(expression); return new DeleteIndexBuilder(expression); } public IDeleteIndexForTableSyntax Index(string indexName) { - var expression = new DeleteIndexExpression(_context.SqlSyntax, _context.CurrentDatabaseProvider, _databaseProviders) - { - Index = { Name = indexName } - }; + var expression = new DeleteIndexExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax) { Index = { Name = indexName } }; _context.Expressions.Add(expression); return new DeleteIndexBuilder(expression); } public IDeleteConstraintOnTableSyntax PrimaryKey(string primaryKeyName) { - var expression = new DeleteConstraintExpression(_context.SqlSyntax, _context.CurrentDatabaseProvider, ConstraintType.PrimaryKey, _databaseProviders) + var expression = new DeleteConstraintExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax, ConstraintType.PrimaryKey) { Constraint = { ConstraintName = primaryKeyName } }; @@ -96,7 +81,7 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete public IDeleteConstraintOnTableSyntax UniqueConstraint(string constraintName) { - var expression = new DeleteConstraintExpression(_context.SqlSyntax, _context.CurrentDatabaseProvider, ConstraintType.Unique, _databaseProviders) + var expression = new DeleteConstraintExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax, ConstraintType.Unique) { Constraint = { ConstraintName = constraintName } }; @@ -106,7 +91,7 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete public IDeleteDefaultConstraintOnTableSyntax DefaultConstraint() { - var expression = new DeleteDefaultConstraintExpression(_context.SqlSyntax, _context.CurrentDatabaseProvider, _databaseProviders); + var expression = new DeleteDefaultConstraintExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax); _context.Expressions.Add(expression); return new DeleteDefaultConstraintBuilder(expression); } diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteColumnExpression.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteColumnExpression.cs index 4792da1d41..381e5c8271 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteColumnExpression.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteColumnExpression.cs @@ -7,8 +7,9 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete.Expressions { public class DeleteColumnExpression : MigrationExpressionBase { - public DeleteColumnExpression(ISqlSyntaxProvider sqlSyntax, DatabaseProviders currentDatabaseProvider, DatabaseProviders[] supportedDatabaseProviders = null) - : base(sqlSyntax, currentDatabaseProvider, supportedDatabaseProviders) + + public DeleteColumnExpression(DatabaseProviders current, DatabaseProviders[] databaseProviders, ISqlSyntaxProvider sqlSyntax) + : base(current, databaseProviders, sqlSyntax) { ColumnNames = new List(); } diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteConstraintExpression.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteConstraintExpression.cs index 3f089aee34..21ef6bb517 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteConstraintExpression.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteConstraintExpression.cs @@ -5,8 +5,9 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete.Expressions { public class DeleteConstraintExpression : MigrationExpressionBase { - public DeleteConstraintExpression(ISqlSyntaxProvider sqlSyntax, DatabaseProviders current, ConstraintType type, DatabaseProviders[] databaseProviders = null) - : base(sqlSyntax, current, databaseProviders) + + public DeleteConstraintExpression(DatabaseProviders current, DatabaseProviders[] databaseProviders, ISqlSyntaxProvider sqlSyntax, ConstraintType type) + : base(current, databaseProviders, sqlSyntax) { Constraint = new ConstraintDefinition(type); } @@ -20,27 +21,24 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete.Expressions { if (Constraint.IsPrimaryKeyConstraint) { - return string.Format( - SqlSyntax.DeleteConstraint, - SqlSyntax.GetQuotedTableName(Constraint.TableName), - "PRIMARY KEY", - ""); + return string.Format(SqlSyntax.DeleteConstraint, + SqlSyntax.GetQuotedTableName(Constraint.TableName), + "PRIMARY KEY", + ""); } else { - return string.Format( - SqlSyntax.DeleteConstraint, - SqlSyntax.GetQuotedTableName(Constraint.TableName), - "FOREIGN KEY", - ""); + return string.Format(SqlSyntax.DeleteConstraint, + SqlSyntax.GetQuotedTableName(Constraint.TableName), + "FOREIGN KEY", + ""); } } else { - return string.Format( - SqlSyntax.DeleteConstraint, - SqlSyntax.GetQuotedTableName(Constraint.TableName), - SqlSyntax.GetQuotedName(Constraint.ConstraintName)); + return string.Format(SqlSyntax.DeleteConstraint, + SqlSyntax.GetQuotedTableName(Constraint.TableName), + SqlSyntax.GetQuotedName(Constraint.ConstraintName)); } } } diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteDataExpression.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteDataExpression.cs index f62c97da07..97c601a2a1 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteDataExpression.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteDataExpression.cs @@ -8,10 +8,9 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete.Expressions public class DeleteDataExpression : MigrationExpressionBase { private readonly List _rows = new List(); - - - public DeleteDataExpression(ISqlSyntaxProvider sqlSyntax, DatabaseProviders currentDatabaseProvider, DatabaseProviders[] supportedDatabaseProviders = null) - : base(sqlSyntax, currentDatabaseProvider, supportedDatabaseProviders) + + public DeleteDataExpression(DatabaseProviders current, DatabaseProviders[] databaseProviders, ISqlSyntaxProvider sqlSyntax) + : base(current, databaseProviders, sqlSyntax) { } @@ -30,9 +29,7 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete.Expressions if (IsAllRows) { - deleteItems.Add(string.Format( - SqlSyntax.DeleteData, - SqlSyntax.GetQuotedTableName(TableName), "1 = 1")); + deleteItems.Add(string.Format(SqlSyntax.DeleteData, SqlSyntax.GetQuotedTableName(TableName), "1 = 1")); } else { @@ -44,7 +41,7 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete.Expressions whereClauses.Add(string.Format("{0} {1} {2}", SqlSyntax.GetQuotedColumnName(item.Key), item.Value == null ? "IS" : "=", - SqlSyntax.GetQuotedValue(item.Value.ToString()))); + GetQuotedValue(item.Value))); } deleteItems.Add(string.Format(SqlSyntax.DeleteData, diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteDefaultConstraintExpression.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteDefaultConstraintExpression.cs index c610d8bdc1..70a74da28e 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteDefaultConstraintExpression.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteDefaultConstraintExpression.cs @@ -4,8 +4,8 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete.Expressions { public class DeleteDefaultConstraintExpression : MigrationExpressionBase { - public DeleteDefaultConstraintExpression(ISqlSyntaxProvider sqlSyntax, DatabaseProviders currentDatabaseProvider, DatabaseProviders[] supportedDatabaseProviders = null) - : base(sqlSyntax, currentDatabaseProvider, supportedDatabaseProviders) + public DeleteDefaultConstraintExpression(DatabaseProviders current, DatabaseProviders[] databaseProviders, ISqlSyntaxProvider sqlSyntax) + : base(current, databaseProviders, sqlSyntax) { } diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteForeignKeyExpression.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteForeignKeyExpression.cs index 7173beea99..97342e851c 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteForeignKeyExpression.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteForeignKeyExpression.cs @@ -8,13 +8,15 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete.Expressions { public class DeleteForeignKeyExpression : MigrationExpressionBase { - public DeleteForeignKeyExpression(ISqlSyntaxProvider sqlSyntax, DatabaseProviders currentDatabaseProvider, DatabaseProviders[] supportedDatabaseProviders = null) - : base(sqlSyntax, currentDatabaseProvider, supportedDatabaseProviders) - { + + + public DeleteForeignKeyExpression(DatabaseProviders current, DatabaseProviders[] databaseProviders, ISqlSyntaxProvider sqlSyntax) + : base(current, databaseProviders, sqlSyntax) + { ForeignKey = new ForeignKeyDefinition(); } - public virtual ForeignKeyDefinition ForeignKey { get; set; } + public ForeignKeyDefinition ForeignKey { get; set; } public override string ToString() { @@ -26,7 +28,7 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete.Expressions if (CurrentDatabaseProvider == DatabaseProviders.MySql) { - //MySql naming "convention" for foreignkeys, which aren't explicitly named + //MySql naming "convention" for foreignkeys, which aren't explicitly named if (string.IsNullOrEmpty(ForeignKey.Name)) ForeignKey.Name = string.Format("{0}_ibfk_1", ForeignKey.ForeignTable.ToLower()); diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteIndexExpression.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteIndexExpression.cs index 203ffa09d1..e504223669 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteIndexExpression.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteIndexExpression.cs @@ -5,13 +5,20 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete.Expressions { public class DeleteIndexExpression : MigrationExpressionBase { - public DeleteIndexExpression(ISqlSyntaxProvider sqlSyntax, DatabaseProviders currentDatabaseProvider, DatabaseProviders[] supportedDatabaseProviders = null) - : base(sqlSyntax, currentDatabaseProvider, supportedDatabaseProviders) + + public DeleteIndexExpression(DatabaseProviders current, DatabaseProviders[] databaseProviders, ISqlSyntaxProvider sqlSyntax) + : base(current, databaseProviders, sqlSyntax) { Index = new IndexDefinition(); } - public virtual IndexDefinition Index { get; set; } + public DeleteIndexExpression(DatabaseProviders current, DatabaseProviders[] databaseProviders, ISqlSyntaxProvider sqlSyntax, IndexDefinition index) + : base(current, databaseProviders, sqlSyntax) + { + Index = index; + } + + public IndexDefinition Index { get; private set; } public override string ToString() { diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteTableExpression.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteTableExpression.cs index 133cc4b7d8..0456091a4b 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteTableExpression.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteTableExpression.cs @@ -4,8 +4,9 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete.Expressions { public class DeleteTableExpression : MigrationExpressionBase { - public DeleteTableExpression(ISqlSyntaxProvider sqlSyntax, DatabaseProviders currentDatabaseProvider, DatabaseProviders[] supportedDatabaseProviders = null) - : base(sqlSyntax, currentDatabaseProvider, supportedDatabaseProviders) + + public DeleteTableExpression(DatabaseProviders current, DatabaseProviders[] databaseProviders, ISqlSyntaxProvider sqlSyntax) + : base(current, databaseProviders, sqlSyntax) { } diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/IDeleteBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/IDeleteBuilder.cs index 6521d043a7..4a31c3bf37 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/IDeleteBuilder.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/IDeleteBuilder.cs @@ -12,7 +12,6 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete void Table(string tableName); IDeleteColumnFromTableSyntax Column(string columnName); IDeleteForeignKeyFromTableSyntax ForeignKey(); - [Obsolete("Do not use this construct as it does not work with MySql, use the syntax: Delete.ForeignKey().FromTable(\"umbracoUser2app\").ForeignColumn(... instead")] IDeleteForeignKeyOnTableSyntax ForeignKey(string foreignKeyName); IDeleteDataSyntax FromTable(string tableName); IDeleteIndexForTableSyntax Index(); diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/ExecuteBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/ExecuteBuilder.cs index 3ecffa46ba..549a4a0c66 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/ExecuteBuilder.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/ExecuteBuilder.cs @@ -1,34 +1,31 @@ using System; using Umbraco.Core.Persistence.Migrations.Syntax.Execute.Expressions; +using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Syntax.Execute { public class ExecuteBuilder : IExecuteBuilder { private readonly IMigrationContext _context; + private readonly ISqlSyntaxProvider _sqlSyntax; private readonly DatabaseProviders[] _databaseProviders; - public ExecuteBuilder(IMigrationContext context, params DatabaseProviders[] databaseProviders) + public ExecuteBuilder(IMigrationContext context, ISqlSyntaxProvider sqlSyntax, params DatabaseProviders[] databaseProviders) { _context = context; + _sqlSyntax = sqlSyntax; _databaseProviders = databaseProviders; } public void Sql(string sqlStatement) { - var expression = new ExecuteSqlStatementExpression( - _context.SqlSyntax, - _context.CurrentDatabaseProvider, - _databaseProviders) {SqlStatement = sqlStatement}; + var expression = new ExecuteSqlStatementExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax) {SqlStatement = sqlStatement}; _context.Expressions.Add(expression); } public void Code(Func codeStatement) { - var expression = new ExecuteCodeStatementExpression( - _context.SqlSyntax, - _context.CurrentDatabaseProvider, - _databaseProviders) {CodeStatement = codeStatement}; + var expression = new ExecuteCodeStatementExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax) { CodeStatement = codeStatement }; _context.Expressions.Add(expression); } } diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/Expressions/ExecuteCodeStatementExpression.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/Expressions/ExecuteCodeStatementExpression.cs index dc3ace9760..e5f487d15d 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/Expressions/ExecuteCodeStatementExpression.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/Expressions/ExecuteCodeStatementExpression.cs @@ -5,8 +5,8 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Execute.Expressions { public class ExecuteCodeStatementExpression : MigrationExpressionBase { - public ExecuteCodeStatementExpression(ISqlSyntaxProvider sqlSyntax, DatabaseProviders currentDatabaseProvider, DatabaseProviders[] supportedDatabaseProviders = null) - : base(sqlSyntax, currentDatabaseProvider, supportedDatabaseProviders) + public ExecuteCodeStatementExpression(DatabaseProviders current, DatabaseProviders[] databaseProviders, ISqlSyntaxProvider sqlSyntax) + : base(current, databaseProviders, sqlSyntax) { } @@ -14,7 +14,7 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Execute.Expressions public override string Process(Database database) { - if (CodeStatement != null) + if(CodeStatement != null) return CodeStatement(database); return base.Process(database); diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/Expressions/ExecuteSqlStatementExpression.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/Expressions/ExecuteSqlStatementExpression.cs index 152fa749ef..1164ba7caa 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/Expressions/ExecuteSqlStatementExpression.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/Expressions/ExecuteSqlStatementExpression.cs @@ -4,8 +4,9 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Execute.Expressions { public class ExecuteSqlStatementExpression : MigrationExpressionBase { - public ExecuteSqlStatementExpression(ISqlSyntaxProvider sqlSyntax, DatabaseProviders currentDatabaseProvider, DatabaseProviders[] supportedDatabaseProviders = null) - : base(sqlSyntax, currentDatabaseProvider, supportedDatabaseProviders) + + public ExecuteSqlStatementExpression(DatabaseProviders current, DatabaseProviders[] databaseProviders, ISqlSyntaxProvider sqlSyntax) + : base(current, databaseProviders, sqlSyntax) { } diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Expressions/CreateColumnExpression.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Expressions/CreateColumnExpression.cs index 14eb5eeb4d..ad16da5286 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Expressions/CreateColumnExpression.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Expressions/CreateColumnExpression.cs @@ -5,15 +5,16 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Expressions { public class CreateColumnExpression : MigrationExpressionBase { - public CreateColumnExpression(ISqlSyntaxProvider sqlSyntax, DatabaseProviders currentDatabaseProvider, DatabaseProviders[] supportedDatabaseProviders = null) - : base(sqlSyntax, currentDatabaseProvider, supportedDatabaseProviders) + + public CreateColumnExpression(DatabaseProviders current, DatabaseProviders[] databaseProviders, ISqlSyntaxProvider sqlSyntax) + : base(current, databaseProviders, sqlSyntax) { Column = new ColumnDefinition { ModificationType = ModificationType.Create }; } - public virtual string SchemaName { get; set; } - public virtual string TableName { get; set; } - public virtual ColumnDefinition Column { get; set; } + public string SchemaName { get; set; } + public string TableName { get; set; } + public ColumnDefinition Column { get; set; } public override string ToString() { diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Expressions/CreateForeignKeyExpression.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Expressions/CreateForeignKeyExpression.cs index 57836281d3..45e40e961f 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Expressions/CreateForeignKeyExpression.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Expressions/CreateForeignKeyExpression.cs @@ -5,13 +5,19 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Expressions { public class CreateForeignKeyExpression : MigrationExpressionBase { - public CreateForeignKeyExpression(ISqlSyntaxProvider sqlSyntax, DatabaseProviders currentDatabaseProvider, DatabaseProviders[] supportedDatabaseProviders = null) - : base(sqlSyntax, currentDatabaseProvider, supportedDatabaseProviders) + public CreateForeignKeyExpression(DatabaseProviders current, DatabaseProviders[] databaseProviders, ISqlSyntaxProvider sqlSyntax, ForeignKeyDefinition fkDef) + : base(current, databaseProviders, sqlSyntax) + { + ForeignKey = fkDef; + } + + public CreateForeignKeyExpression(DatabaseProviders current, DatabaseProviders[] databaseProviders, ISqlSyntaxProvider sqlSyntax) + : base(current, databaseProviders, sqlSyntax) { ForeignKey = new ForeignKeyDefinition(); } - public virtual ForeignKeyDefinition ForeignKey { get; set; } + public ForeignKeyDefinition ForeignKey { get; set; } public override string ToString() { diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Expressions/CreateIndexExpression.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Expressions/CreateIndexExpression.cs index b4ad3dcade..7fa38cd3ae 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Expressions/CreateIndexExpression.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Expressions/CreateIndexExpression.cs @@ -6,13 +6,20 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Expressions { public class CreateIndexExpression : MigrationExpressionBase { - public CreateIndexExpression(ISqlSyntaxProvider sqlSyntax, DatabaseProviders currentDatabaseProvider, DatabaseProviders[] supportedDatabaseProviders = null) - : base(sqlSyntax, currentDatabaseProvider, supportedDatabaseProviders) + + public CreateIndexExpression(DatabaseProviders current, DatabaseProviders[] databaseProviders, ISqlSyntaxProvider sqlSyntax, IndexDefinition index) + : base(current, databaseProviders, sqlSyntax) + { + Index = index; + } + + public CreateIndexExpression(DatabaseProviders current, DatabaseProviders[] databaseProviders, ISqlSyntaxProvider sqlSyntax) + : base(current, databaseProviders, sqlSyntax) { Index = new IndexDefinition(); } - - public virtual IndexDefinition Index { get; set; } + + public IndexDefinition Index { get; set; } public override string ToString() { diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/IfDatabase/IfDatabaseBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/IfDatabase/IfDatabaseBuilder.cs index b7105170f1..e0d20c4875 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/IfDatabase/IfDatabaseBuilder.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/IfDatabase/IfDatabaseBuilder.cs @@ -3,43 +3,46 @@ using Umbraco.Core.Persistence.Migrations.Syntax.Delete; using Umbraco.Core.Persistence.Migrations.Syntax.Execute; using Umbraco.Core.Persistence.Migrations.Syntax.Rename; using Umbraco.Core.Persistence.Migrations.Syntax.Update; +using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Syntax.IfDatabase { public class IfDatabaseBuilder : IIfDatabaseBuilder { private readonly IMigrationContext _context; + private readonly ISqlSyntaxProvider _sqlSyntax; private readonly DatabaseProviders[] _databaseProviders; - public IfDatabaseBuilder(IMigrationContext context, params DatabaseProviders[] databaseProviders) + public IfDatabaseBuilder(IMigrationContext context, ISqlSyntaxProvider sqlSyntax, params DatabaseProviders[] databaseProviders) { _context = context; + _sqlSyntax = sqlSyntax; _databaseProviders = databaseProviders; } public ICreateBuilder Create { - get { return new CreateBuilder(_context, _databaseProviders); } + get { return new CreateBuilder(_context, _sqlSyntax, _databaseProviders); } } public IExecuteBuilder Execute { - get { return new ExecuteBuilder(_context, _databaseProviders); } + get { return new ExecuteBuilder(_context, _sqlSyntax, _databaseProviders); } } public IDeleteBuilder Delete { - get { return new DeleteBuilder(_context, _databaseProviders); } + get { return new DeleteBuilder(_context, _sqlSyntax, _databaseProviders); } } public IRenameBuilder Rename { - get { return new RenameBuilder(_context, _databaseProviders); } + get { return new RenameBuilder(_context, _sqlSyntax, _databaseProviders); } } public IUpdateBuilder Update { - get { return new UpdateBuilder(_context, _databaseProviders); } + get { return new UpdateBuilder(_context, _sqlSyntax, _databaseProviders); } } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Insert/Expressions/InsertDataExpression.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Insert/Expressions/InsertDataExpression.cs index f1d739a503..28b8c71d39 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Insert/Expressions/InsertDataExpression.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Insert/Expressions/InsertDataExpression.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Text; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.SqlSyntax; @@ -8,79 +9,78 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Insert.Expressions public class InsertDataExpression : MigrationExpressionBase { private readonly List _rows = new List(); - - public InsertDataExpression(ISqlSyntaxProvider sqlSyntax, DatabaseProviders currentDatabaseProvider, DatabaseProviders[] supportedDatabaseProviders = null) - : base(sqlSyntax, currentDatabaseProvider, supportedDatabaseProviders) + + public InsertDataExpression(DatabaseProviders current, DatabaseProviders[] databaseProviders, ISqlSyntaxProvider sqlSyntax) + : base(current, databaseProviders, sqlSyntax) { } public string SchemaName { get; set; } public string TableName { get; set; } - + public bool EnabledIdentityInsert { get; set; } public List Rows { get { return _rows; } } - - - + public override string ToString() { - //TODO: This works for single insertion entries, not sure if it is valid for bulk insert operations!!! - if (IsExpressionSupported() == false) return string.Empty; + + var sb = new StringBuilder(); - var insertItems = new List(); - - foreach (var item in Rows) + if (EnabledIdentityInsert && SqlSyntax.SupportsIdentityInsert()) { - var cols = ""; - var vals = ""; - foreach (var keyVal in item) + sb.AppendLine(string.Format("SET IDENTITY_INSERT {0} ON;", SqlSyntax.GetQuotedTableName(TableName))); + if (CurrentDatabaseProvider == DatabaseProviders.SqlServer || CurrentDatabaseProvider == DatabaseProviders.SqlServerCE) { - cols += keyVal.Key + ","; - vals += GetQuotedValue(keyVal.Value) + ","; + sb.AppendLine("GO"); } - cols = cols.TrimEnd(','); - vals = vals.TrimEnd(','); - - - var sql = string.Format( - SqlSyntax.InsertData, - SqlSyntax.GetQuotedTableName(TableName), - cols, vals); - - insertItems.Add(sql); } - return string.Join(",", insertItems); - } - - private string GetQuotedValue(object val) - { - var type = val.GetType(); - - switch (Type.GetTypeCode(type)) + try { - case TypeCode.Boolean: - return ((bool)val) ? "1" : "0"; - case TypeCode.Single: - case TypeCode.Double: - case TypeCode.Decimal: - case TypeCode.SByte: - case TypeCode.Int16: - case TypeCode.Int32: - case TypeCode.Int64: - case TypeCode.Byte: - case TypeCode.UInt16: - case TypeCode.UInt32: - case TypeCode.UInt64: - return val.ToString(); - default: - return SqlSyntax.GetQuotedValue(val.ToString()); + foreach (var item in Rows) + { + var cols = ""; + var vals = ""; + foreach (var keyVal in item) + { + cols += SqlSyntax.GetQuotedColumnName(keyVal.Key) + ","; + vals += GetQuotedValue(keyVal.Value) + ","; + } + cols = cols.TrimEnd(','); + vals = vals.TrimEnd(','); + + + var sql = string.Format(SqlSyntax.InsertData, + SqlSyntax.GetQuotedTableName(TableName), + cols, vals); + + sb.AppendLine(string.Format("{0};", sql)); + if (CurrentDatabaseProvider == DatabaseProviders.SqlServer || CurrentDatabaseProvider == DatabaseProviders.SqlServerCE) + { + sb.AppendLine("GO"); + } + } } + finally + { + if (EnabledIdentityInsert && SqlSyntax.SupportsIdentityInsert()) + { + sb.AppendLine(string.Format("SET IDENTITY_INSERT {0} OFF;", SqlSyntax.GetQuotedTableName(TableName))); + if (CurrentDatabaseProvider == DatabaseProviders.SqlServer || CurrentDatabaseProvider == DatabaseProviders.SqlServerCE) + { + sb.AppendLine("GO"); + } + } + } + + return sb.ToString(); } + + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Insert/IInsertDataSyntax.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Insert/IInsertDataSyntax.cs index fb0585f0e6..5791f0cb83 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Insert/IInsertDataSyntax.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Insert/IInsertDataSyntax.cs @@ -2,6 +2,7 @@ { public interface IInsertDataSyntax : IFluentSyntax { + IInsertDataSyntax EnableIdentityInsert(); IInsertDataSyntax Row(object dataAsAnonymousType); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Insert/InsertBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Insert/InsertBuilder.cs index d80be1cb39..2d3ce9c64d 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Insert/InsertBuilder.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Insert/InsertBuilder.cs @@ -1,19 +1,24 @@ using Umbraco.Core.Persistence.Migrations.Syntax.Insert.Expressions; +using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Syntax.Insert { public class InsertBuilder : IInsertBuilder { private readonly IMigrationContext _context; + private readonly ISqlSyntaxProvider _sqlSyntax; + private readonly DatabaseProviders[] _databaseProviders; - public InsertBuilder(IMigrationContext context) + public InsertBuilder(IMigrationContext context, ISqlSyntaxProvider sqlSyntax, params DatabaseProviders[] databaseProviders) { _context = context; + _sqlSyntax = sqlSyntax; + _databaseProviders = databaseProviders; } public IInsertDataSyntax IntoTable(string tableName) { - var expression = new InsertDataExpression(_context.SqlSyntax, _context.CurrentDatabaseProvider) { TableName = tableName }; + var expression = new InsertDataExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax) { TableName = tableName }; _context.Expressions.Add(expression); return new InsertDataBuilder(expression); } diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Insert/InsertDataBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Insert/InsertDataBuilder.cs index 9b419f8daf..4f8bf2dfcc 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Insert/InsertDataBuilder.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Insert/InsertDataBuilder.cs @@ -14,6 +14,12 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Insert _expression = expression; } + public IInsertDataSyntax EnableIdentityInsert() + { + _expression.EnabledIdentityInsert = true; + return this; + } + public IInsertDataSyntax Row(object dataAsAnonymousType) { _expression.Rows.Add(GetData(dataAsAnonymousType)); diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Rename/Expressions/RenameColumnExpression.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Rename/Expressions/RenameColumnExpression.cs index 445eed65f2..1f84401923 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Rename/Expressions/RenameColumnExpression.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Rename/Expressions/RenameColumnExpression.cs @@ -3,9 +3,9 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Rename.Expressions { public class RenameColumnExpression : MigrationExpressionBase - { - public RenameColumnExpression(ISqlSyntaxProvider sqlSyntax, DatabaseProviders currentDatabaseProvider, DatabaseProviders[] supportedDatabaseProviders = null) - : base(sqlSyntax, currentDatabaseProvider, supportedDatabaseProviders) + { + public RenameColumnExpression(DatabaseProviders current, DatabaseProviders[] databaseProviders, ISqlSyntaxProvider sqlSyntax) + : base(current, databaseProviders, sqlSyntax) { } diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Rename/Expressions/RenameTableExpression.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Rename/Expressions/RenameTableExpression.cs index 930d6eca8d..ec60117d7e 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Rename/Expressions/RenameTableExpression.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Rename/Expressions/RenameTableExpression.cs @@ -4,8 +4,8 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Rename.Expressions { public class RenameTableExpression : MigrationExpressionBase { - public RenameTableExpression(ISqlSyntaxProvider sqlSyntax, DatabaseProviders currentDatabaseProvider, DatabaseProviders[] supportedDatabaseProviders = null) - : base(sqlSyntax, currentDatabaseProvider, supportedDatabaseProviders) + public RenameTableExpression(DatabaseProviders current, DatabaseProviders[] databaseProviders, ISqlSyntaxProvider sqlSyntax) + : base(current, databaseProviders, sqlSyntax) { } @@ -17,7 +17,7 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Rename.Expressions { if (IsExpressionSupported() == false) return string.Empty; - + return SqlSyntax.FormatTableRename(OldName, NewName); } } diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Rename/RenameBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Rename/RenameBuilder.cs index 2e5d7cb55b..ebfb08b862 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Rename/RenameBuilder.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Rename/RenameBuilder.cs @@ -1,34 +1,33 @@ using Umbraco.Core.Persistence.Migrations.Syntax.Rename.Column; using Umbraco.Core.Persistence.Migrations.Syntax.Rename.Expressions; using Umbraco.Core.Persistence.Migrations.Syntax.Rename.Table; +using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Syntax.Rename { public class RenameBuilder : IRenameBuilder { private readonly IMigrationContext _context; + private readonly ISqlSyntaxProvider _sqlSyntax; private readonly DatabaseProviders[] _databaseProviders; - public RenameBuilder(IMigrationContext context, params DatabaseProviders[] databaseProviders) + public RenameBuilder(IMigrationContext context, ISqlSyntaxProvider sqlSyntax, params DatabaseProviders[] databaseProviders) { _context = context; + _sqlSyntax = sqlSyntax; _databaseProviders = databaseProviders; } public IRenameTableSyntax Table(string oldName) { - var expression = new RenameTableExpression( - _context.SqlSyntax, - _context.CurrentDatabaseProvider, _databaseProviders) {OldName = oldName}; + var expression = new RenameTableExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax) { OldName = oldName }; _context.Expressions.Add(expression); return new RenameTableBuilder(expression); } public IRenameColumnTableSyntax Column(string oldName) { - var expression = new RenameColumnExpression( - _context.SqlSyntax, - _context.CurrentDatabaseProvider, _databaseProviders) {OldName = oldName}; + var expression = new RenameColumnExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax) { OldName = oldName }; _context.Expressions.Add(expression); return new RenameColumnBuilder(expression); } diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Update/Expressions/UpdateDataExpression.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Update/Expressions/UpdateDataExpression.cs index 37516f8b5b..18bec8b938 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Update/Expressions/UpdateDataExpression.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Update/Expressions/UpdateDataExpression.cs @@ -5,8 +5,8 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Update.Expressions { public class UpdateDataExpression : MigrationExpressionBase { - public UpdateDataExpression(ISqlSyntaxProvider sqlSyntax, DatabaseProviders currentDatabaseProvider, DatabaseProviders[] supportedDatabaseProviders = null) - : base(sqlSyntax, currentDatabaseProvider, supportedDatabaseProviders) + public UpdateDataExpression(DatabaseProviders current, DatabaseProviders[] databaseProviders, ISqlSyntaxProvider sqlSyntax) + : base(current, databaseProviders, sqlSyntax) { } @@ -29,7 +29,7 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Update.Expressions { updateItems.Add(string.Format("{0} = {1}", SqlSyntax.GetQuotedColumnName(item.Key), - SqlSyntax.GetQuotedValue(item.Value.ToString()))); + GetQuotedValue(item.Value))); } if (IsAllRows) @@ -43,12 +43,12 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Update.Expressions whereClauses.Add(string.Format("{0} {1} {2}", SqlSyntax.GetQuotedColumnName(item.Key), item.Value == null ? "IS" : "=", - SqlSyntax.GetQuotedValue(item.Value.ToString()))); + GetQuotedValue(item.Value))); } } return string.Format(SqlSyntax.UpdateData, SqlSyntax.GetQuotedTableName(TableName), - string.Join(", ", updateItems.ToArray()), + string.Join(", ", updateItems.ToArray()), string.Join(" AND ", whereClauses.ToArray())); } } diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Update/UpdateBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Update/UpdateBuilder.cs index 325a231211..3816e65132 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Update/UpdateBuilder.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Update/UpdateBuilder.cs @@ -1,25 +1,24 @@ using Umbraco.Core.Persistence.Migrations.Syntax.Update.Expressions; +using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Syntax.Update { public class UpdateBuilder : IUpdateBuilder { private readonly IMigrationContext _context; + private readonly ISqlSyntaxProvider _sqlSyntax; private readonly DatabaseProviders[] _databaseProviders; - public UpdateBuilder(IMigrationContext context, params DatabaseProviders[] databaseProviders) + public UpdateBuilder(IMigrationContext context, ISqlSyntaxProvider sqlSyntax, params DatabaseProviders[] databaseProviders) { _context = context; + _sqlSyntax = sqlSyntax; _databaseProviders = databaseProviders; } public IUpdateSetSyntax Table(string tableName) { - var expression = new UpdateDataExpression( - _context.SqlSyntax, - _context.CurrentDatabaseProvider, - _databaseProviders) {TableName = tableName}; - + var expression = new UpdateDataExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax) { TableName = tableName }; _context.Expressions.Add(expression); return new UpdateDataBuilder(expression, _context); } diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AddIndexToCmsMacroTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AddIndexToCmsMacroTable.cs index 0c9bbcc68a..d18cb430c0 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AddIndexToCmsMacroTable.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AddIndexToCmsMacroTable.cs @@ -3,6 +3,7 @@ using System.CodeDom; using System.Linq; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; +using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.SqlSyntax; @@ -14,12 +15,12 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSeven [Migration("7.0.0", 4, GlobalSettings.UmbracoMigrationName)] public class AddIndexToCmsMacroTable : MigrationBase { - private readonly bool _skipIndexCheck; + private readonly bool _forTesting; - internal AddIndexToCmsMacroTable(bool skipIndexCheck, ISqlSyntaxProvider sqlSyntax, ILogger logger) + internal AddIndexToCmsMacroTable(bool forTesting, ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) { - _skipIndexCheck = skipIndexCheck; + _forTesting = forTesting; } public AddIndexToCmsMacroTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) @@ -28,7 +29,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSeven public override void Up() { - var dbIndexes = _skipIndexCheck ? new DbIndexDefinition[] { } : SqlSyntax.GetDefinedIndexes(Context.Database) + var dbIndexes = _forTesting ? new DbIndexDefinition[] { } : SqlSyntax.GetDefinedIndexes(Context.Database) .Select(x => new DbIndexDefinition() { TableName = x.Item1, @@ -40,9 +41,33 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSeven //make sure it doesn't already exist if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_cmsMacro_Alias")) == false) { - Create.Index("IX_cmsMacro_Alias").OnTable("cmsMacro").OnColumn("macroAlias").Unique(); - } + //in order to create this index, we need to ensure that there are no duplicates. This could have happened with very old/corrupt umbraco versions. + // So we'll remove any duplicates based on alias and only keep the one with the smallest id since I'm pretty sure we'd always choose the 'first' one + // when running a query. + if (_forTesting == false) + { + //NOTE: Using full SQL statement here in case the DTO has changed between versions + var macros = Context.Database.Fetch("SELECT * FROM cmsMacro") + .GroupBy(x => x.Alias) + .Where(x => x.Count() > 1); + + foreach (var m in macros) + { + //get the min id (to keep) + var minId = m.Min(x => x.Id); + //delete all the others + foreach (var macroDto in m.Where(x => x.Id != minId)) + { + Delete.FromTable("cmsMacro").Row(new { id = macroDto.Id }); + } + } + } + + + Create.Index("IX_cmsMacro_Alias").OnTable("cmsMacro").OnColumn("macroAlias").Unique(); + } + } public override void Down() diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterTagRelationsTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterTagRelationsTable.cs index 3370eb763e..922581ee8f 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterTagRelationsTable.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterTagRelationsTable.cs @@ -39,15 +39,20 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSeven if (Context.CurrentDatabaseProvider == DatabaseProviders.MySql) { Delete.ForeignKey().FromTable("cmsTagRelationship").ForeignColumn("nodeId").ToTable("umbracoNode").PrimaryColumn("id"); + //check for another strange really old one that might have existed + if (constraints.Any(x => x.Item1 == "cmsTagRelationship" && x.Item2 == "tagId")) + { + Delete.ForeignKey().FromTable("cmsTagRelationship").ForeignColumn("tagId").ToTable("cmsTags").PrimaryColumn("id"); + } } else { //Before we try to delete this constraint, we'll see if it exists first, some older schemas never had it and some older schema's had this named // differently than the default. - var constraint = constraints - .SingleOrDefault(x => x.Item1 == "cmsTagRelationship" && x.Item2 == "nodeId" && x.Item3.InvariantStartsWith("PK_") == false); - if (constraint != null) + var constraintMatches = constraints.Where(x => x.Item1 == "cmsTagRelationship" && x.Item2 == "nodeId" && x.Item3.InvariantStartsWith("PK_") == false); + + foreach (var constraint in constraintMatches) { Delete.ForeignKey(constraint.Item3).OnTable("cmsTagRelationship"); } diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterTagsTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterTagsTable.cs index 8d18172341..d832cb936d 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterTagsTable.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterTagsTable.cs @@ -28,6 +28,11 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSeven }).ToArray(); //add a foreign key to the parent id column too! + + //In some cases in very old corrupted db's this will fail, so it means we need to clean the data first + //set the parentID to NULL where it doesn't actually exist in the normal ids + Execute.Sql(@"UPDATE cmsTags SET parentId = NULL WHERE parentId IS NOT NULL AND parentId NOT IN (SELECT id FROM cmsTags)"); + Create.ForeignKey("FK_cmsTags_cmsTags") .FromTable("cmsTags") .ForeignColumn("ParentId") diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/UpdateRelatedLinksData.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/UpdateRelatedLinksData.cs index 488faa13d5..5ed60a8de0 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/UpdateRelatedLinksData.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/UpdateRelatedLinksData.cs @@ -57,9 +57,11 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSeven } var propertyTypeIds = propertyData.Select(x => x.PropertyTypeId).Distinct(); - var propertyTypes = database.Fetch( - "WHERE id in (@propertyTypeIds)", new { propertyTypeIds = propertyTypeIds }); + //NOTE: We are writing the full query because we've added a column to the PropertyTypeDto in later versions so one of the columns + // won't exist yet + var propertyTypes = database.Fetch("SELECT * FROM cmsPropertyType WHERE id in (@propertyTypeIds)", new { propertyTypeIds = propertyTypeIds }); + foreach (var data in propertyData) { if (string.IsNullOrEmpty(data.Text) == false) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/AddDataDecimalColumn.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/AddDataDecimalColumn.cs new file mode 100644 index 0000000000..442b92d2b5 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/AddDataDecimalColumn.cs @@ -0,0 +1,29 @@ +using System.Linq; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFourZero +{ + [Migration("7.4.0", 1, GlobalSettings.UmbracoMigrationName)] + public class AddDataDecimalColumn : MigrationBase + { + public AddDataDecimalColumn(ISqlSyntaxProvider sqlSyntax, ILogger logger) + : base(sqlSyntax, logger) + { + } + + public override void Up() + { + //Don't exeucte if the column is already there + var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray(); + + if (columns.Any(x => x.TableName.InvariantEquals("cmsPropertyData") && x.ColumnName.InvariantEquals("dataDecimal")) == false) + Create.Column("dataDecimal").OnTable("cmsPropertyData").AsDecimal().Nullable(); + } + + public override void Down() + { + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/AddUmbracoDeployTables.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/AddUmbracoDeployTables.cs new file mode 100644 index 0000000000..a39cea2ee0 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/AddUmbracoDeployTables.cs @@ -0,0 +1,48 @@ +using System.Linq; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFourZero +{ + [Migration("7.4.0", 5, GlobalSettings.UmbracoMigrationName)] + public class AddUmbracoDeployTables : MigrationBase + { + public AddUmbracoDeployTables(ISqlSyntaxProvider sqlSyntax, ILogger logger) + : base(sqlSyntax, logger) + { } + + public override void Up() + { + //Don't exeucte if the table is already there + var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); + if (tables.InvariantContains("umbracoDeployChecksum")) return; + + Create.Table("umbracoDeployChecksum") + .WithColumn("id").AsInt32().Identity().PrimaryKey("PK_umbracoDeployChecksum") + .WithColumn("entityType").AsString(32).NotNullable() + .WithColumn("entityGuid").AsGuid().Nullable() + .WithColumn("entityPath").AsString(256).Nullable() + .WithColumn("localChecksum").AsString(32).NotNullable() + .WithColumn("compositeChecksum").AsString(32).Nullable(); + + Create.Table("umbracoDeployDependency") + .WithColumn("sourceId").AsInt32().NotNullable().ForeignKey("FK_umbracoDeployDependency_umbracoDeployChecksum_id1", "umbracoDeployChecksum", "id") + .WithColumn("targetId").AsInt32().NotNullable().ForeignKey("FK_umbracoDeployDependency_umbracoDeployChecksum_id2", "umbracoDeployChecksum", "id") + .WithColumn("mode").AsInt32().NotNullable(); + + Create.PrimaryKey("PK_umbracoDeployDependency").OnTable("umbracoDeployDependency").Columns(new[] {"sourceId", "targetId"}); + + Create.Index("IX_umbracoDeployChecksum").OnTable("umbracoDeployChecksum") + .OnColumn("entityType") + .Ascending() + .OnColumn("entityGuid") + .Ascending() + .OnColumn("entityPath") + .Unique(); + } + + public override void Down() + { } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/AddUniqueIdPropertyTypeGroupColumn.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/AddUniqueIdPropertyTypeGroupColumn.cs new file mode 100644 index 0000000000..2a164b6e0d --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/AddUniqueIdPropertyTypeGroupColumn.cs @@ -0,0 +1,80 @@ +using System; +using System.Linq; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFourZero +{ + [Migration("7.4.0", 2, GlobalSettings.UmbracoMigrationName)] + public class AddUniqueIdPropertyTypeGroupColumn : MigrationBase + { + public AddUniqueIdPropertyTypeGroupColumn(ISqlSyntaxProvider sqlSyntax, ILogger logger) + : base(sqlSyntax, logger) + { } + + public override void Up() + { + // don't execute if the column is already there + var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray(); + + if (columns.Any(x => x.TableName.InvariantEquals("cmsPropertyTypeGroup") && x.ColumnName.InvariantEquals("uniqueID")) == false) + { + Create.Column("uniqueID").OnTable("cmsPropertyTypeGroup").AsGuid().NotNullable().WithDefault(SystemMethods.NewGuid); + + // unique constraint on name + version + Create.Index("IX_cmsPropertyTypeGroupUniqueID").OnTable("cmsPropertyTypeGroup") + .OnColumn("uniqueID").Ascending() + .WithOptions() + .NonClustered() + .WithOptions() + .Unique(); + + // fill in the data in a way that is consistent over all environments + // (ie cannot use random guids, http://issues.umbraco.org/issue/U4-6942) + + foreach (var data in Context.Database.Query(@" +SELECT cmsPropertyTypeGroup.id grId, cmsPropertyTypeGroup.text grName, cmsContentType.alias ctAlias, umbracoNode.nodeObjectType nObjType +FROM cmsPropertyTypeGroup +INNER JOIN cmsContentType +ON cmsPropertyTypeGroup.contentTypeNodeId = cmsContentType.nodeId +INNER JOIN umbracoNode +ON cmsContentType.nodeId = umbracoNode.id")) + { + Guid guid; + // see BaseDataCreation... built-in groups have their own guids + if (data.grId == 3) + { + guid = new Guid(Constants.PropertyTypeGroups.Image); + } + else if (data.grId == 4) + { + guid = new Guid(Constants.PropertyTypeGroups.File); + } + else if (data.grId == 5) + { + guid = new Guid(Constants.PropertyTypeGroups.Contents); + } + else if (data.grId == 11) + { + guid = new Guid(Constants.PropertyTypeGroups.Membership); + } + else + { + // create a consistent guid from + // group name + content type alias + object type + string guidSource = data.grName + data.ctAlias + data.nObjType; + guid = guidSource.ToGuid(); + } + + // set the Unique Id to the one we've generated + Update.Table("cmsPropertyTypeGroup").Set(new { uniqueID = guid }).Where(new { id = data.grId }); + } + } + } + + public override void Down() + { } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/EnsureContentTypeUniqueIdsAreConsistent.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/EnsureContentTypeUniqueIdsAreConsistent.cs new file mode 100644 index 0000000000..20200a3230 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/EnsureContentTypeUniqueIdsAreConsistent.cs @@ -0,0 +1,55 @@ +using System; +using System.Linq; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFourZero +{ + /// + /// Courier on v. 7.4+ will handle ContentTypes using GUIDs instead of + /// alias, so we need to ensure that these are initially consistent on + /// all environments (based on the alias). + /// + [Migration("7.4.0", 3, GlobalSettings.UmbracoMigrationName)] + public class EnsureContentTypeUniqueIdsAreConsistent : MigrationBase + { + public EnsureContentTypeUniqueIdsAreConsistent(ISqlSyntaxProvider sqlSyntax, ILogger logger) + : base(sqlSyntax, logger) + { } + + public override void Up() + { + var objectTypes = new[] + { + Constants.ObjectTypes.DocumentTypeGuid, + Constants.ObjectTypes.MediaTypeGuid, + Constants.ObjectTypes.MemberTypeGuid, + }; + + var sql = new Sql() + .Select("umbracoNode.id,cmsContentType.alias,umbracoNode.nodeObjectType") + .From(SqlSyntax) + .InnerJoin(SqlSyntax) + .On(SqlSyntax, dto => dto.NodeId, dto => dto.NodeId) + .WhereIn(x => x.NodeObjectType, objectTypes); + + var rows = Context.Database.Fetch(sql); + + foreach (var row in rows) + { + // create a consistent guid from + // alias + object type + var guidSource = ((string) row.alias) + ((Guid) row.nodeObjectType); + var guid = guidSource.ToGuid(); + + // set the Unique Id to the one we've generated + Update.Table("umbracoNode").Set(new { uniqueID = guid }).Where(new { id = row.id }); + } + } + + public override void Down() + { } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/FixListViewMediaSortOrder.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/FixListViewMediaSortOrder.cs new file mode 100644 index 0000000000..e1fa9e9257 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/FixListViewMediaSortOrder.cs @@ -0,0 +1,34 @@ +using System.Linq; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFourZero +{ + [Migration("7.4.0", 4, GlobalSettings.UmbracoMigrationName)] + public class FixListViewMediaSortOrder : MigrationBase + { + public FixListViewMediaSortOrder(ISqlSyntaxProvider sqlSyntax, ILogger logger) + : base(sqlSyntax, logger) + { + } + + public override void Up() + { + var mediaListviewIncludeProperties = Context.Database.Fetch(new Sql().Select("*").From(SqlSyntax).Where(x => x.Id == -9)).FirstOrDefault(); + if (mediaListviewIncludeProperties != null) + { + if (mediaListviewIncludeProperties.Value.Contains("\"alias\":\"sort\"")) + { + mediaListviewIncludeProperties.Value = mediaListviewIncludeProperties.Value.Replace("\"alias\":\"sort\"", "\"alias\":\"sortOrder\""); + Context.Database.InsertOrUpdate(mediaListviewIncludeProperties); + } + } + } + + public override void Down() + { + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/RemoveParentIdPropertyTypeGroupColumn.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/RemoveParentIdPropertyTypeGroupColumn.cs new file mode 100644 index 0000000000..5d5900308a --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/RemoveParentIdPropertyTypeGroupColumn.cs @@ -0,0 +1,30 @@ +using System.Linq; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFourZero +{ + [Migration("7.4.0", 4, GlobalSettings.UmbracoMigrationName)] + public class RemoveParentIdPropertyTypeGroupColumn : MigrationBase + { + public RemoveParentIdPropertyTypeGroupColumn(ISqlSyntaxProvider sqlSyntax, ILogger logger) + : base(sqlSyntax, logger) + { } + + public override void Up() + { + // don't execute if the column is not there anymore + var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray(); + + if (columns.Any(x => x.TableName.InvariantEquals("cmsPropertyTypeGroup") && x.ColumnName.InvariantEquals("parentGroupId")) == false) + return; + + Delete.ForeignKey("FK_cmsPropertyTypeGroup_cmsPropertyTypeGroup_id").OnTable("cmsPropertyTypeGroup"); + Delete.Column("parentGroupId").FromTable("cmsPropertyTypeGroup"); + } + + public override void Down() + { } + } +} diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeOne/UpdateUserLanguagesToIsoCode.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeOne/UpdateUserLanguagesToIsoCode.cs new file mode 100644 index 0000000000..50f78ca66d --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeOne/UpdateUserLanguagesToIsoCode.cs @@ -0,0 +1,38 @@ +using System.Linq; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeOne +{ + /// + /// This fixes the storage of user languages from the old format like en_us to en-US + /// + [Migration("7.3.1", 0, GlobalSettings.UmbracoMigrationName)] + public class UpdateUserLanguagesToIsoCode : MigrationBase + { + public UpdateUserLanguagesToIsoCode(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) + { + } + + public override void Up() + { + var userData = Context.Database.Fetch(new Sql().Select("*").From(SqlSyntax)); + foreach (var user in userData.Where(x => x.UserLanguage.Contains("_"))) + { + var languageParts = user.UserLanguage.Split('_'); + if (languageParts.Length == 2) + { + Update.Table("umbracoUser") + .Set(new {userLanguage = languageParts[0] + "-" + languageParts[1].ToUpperInvariant()}) + .Where(new {id = user.Id}); + } + } + } + + public override void Down() + { + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeTwo/EnsureMigrationsTableIdentityIsCorrect.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeTwo/EnsureMigrationsTableIdentityIsCorrect.cs new file mode 100644 index 0000000000..91b4bd6438 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeTwo/EnsureMigrationsTableIdentityIsCorrect.cs @@ -0,0 +1,50 @@ +using System.Collections.Generic; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeTwo +{ + /// + /// This reinserts all migrations in the migrations table to account for initial rows inserted + /// on creation without identity enabled. + /// + [Migration("7.3.2", 0, GlobalSettings.UmbracoMigrationName)] + public class EnsureMigrationsTableIdentityIsCorrect : MigrationBase + { + public EnsureMigrationsTableIdentityIsCorrect(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) + { + } + + public override void Up() + { + // Due to the delayed execution of migrations, we have to wrap this code in Execute.Code to ensure the previous + // migration steps (esp. creating the migrations table) have completed before trying to fetch migrations from + // this table. + List migrations = null; + Execute.Code(db => + { + migrations = Context.Database.Fetch(new Sql().Select("*").From(SqlSyntax)); + return string.Empty; + }); + Delete.FromTable("umbracoMigration").AllRows(); + Execute.Code(database => + { + if (migrations != null) + { + foreach (var migration in migrations) + { + database.Insert("umbracoMigration", "id", true, + new {name = migration.Name, createDate = migration.CreateDate, version = migration.Version}); + } + } + return string.Empty; + }); + } + + public override void Down() + { + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddForeignKeysForLanguageAndDictionaryTables.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddForeignKeysForLanguageAndDictionaryTables.cs new file mode 100644 index 0000000000..eea56031d4 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddForeignKeysForLanguageAndDictionaryTables.cs @@ -0,0 +1,107 @@ +using System; +using System.Data; +using System.Linq; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZero +{ + [Migration("7.3.0", 14, GlobalSettings.UmbracoMigrationName)] + public class AddForeignKeysForLanguageAndDictionaryTables : MigrationBase + { + public AddForeignKeysForLanguageAndDictionaryTables(ISqlSyntaxProvider sqlSyntax, ILogger logger) + : base(sqlSyntax, logger) + { + } + + public override void Up() + { + var constraints = SqlSyntax.GetConstraintsPerColumn(Context.Database).Distinct().ToArray(); + + //if the FK doesn't exist + if (constraints.Any(x => x.Item1.InvariantEquals("cmsLanguageText") && x.Item2.InvariantEquals("languageId") && x.Item3.InvariantEquals("FK_cmsLanguageText_umbracoLanguage_id")) == false) + { + //Somehow, a language text item might end up with a language Id of zero or one that no longer exists + //before we add the foreign key + foreach (var pk in Context.Database.Query( + "SELECT cmsLanguageText.pk FROM cmsLanguageText WHERE cmsLanguageText.languageId NOT IN (SELECT umbracoLanguage.id FROM umbracoLanguage)")) + { + Delete.FromTable("cmsLanguageText").Row(new { pk = pk }); + } + + var columns = SqlSyntax.GetColumnsInSchema(Context.Database).Distinct().ToArray(); + + if (columns.Any(x => x.ColumnName.InvariantEquals("id") + && x.TableName.InvariantEquals("umbracoLanguage") + && x.DataType.InvariantEquals("smallint"))) + { + //Ensure that the umbracoLanguage PK is INT and not SmallInt (which it might be in older db versions) + // In order to 'change' this to an INT, we have to run a full migration script which is super annoying + Create.Table("umbracoLanguage_TEMP") + .WithColumn("id").AsInt32().NotNullable().Identity() + .WithColumn("languageISOCode").AsString(10).Nullable() + .WithColumn("languageCultureName").AsString(50).Nullable(); + + var currentData = this.Context.Database.Fetch(new Sql().Select("*").From(SqlSyntax)); + foreach (var languageDto in currentData) + { + Insert.IntoTable("umbracoLanguage_TEMP") + .EnableIdentityInsert() + .Row(new {id = languageDto.Id, languageISOCode = languageDto.IsoCode, languageCultureName = languageDto.CultureName}); + } + + //ok, all data has been copied over, drop the old table, rename the temp table and re-add constraints. + Delete.Table("umbracoLanguage"); + Rename.Table("umbracoLanguage_TEMP").To("umbracoLanguage"); + + //add the pk + Create.PrimaryKey("PK_language").OnTable("umbracoLanguage").Column("id"); + } + + var dbIndexes = SqlSyntax.GetDefinedIndexes(Context.Database) + .Select(x => new DbIndexDefinition + { + TableName = x.Item1, + IndexName = x.Item2, + ColumnName = x.Item3, + IsUnique = x.Item4 + }).ToArray(); + + //make sure it doesn't already exist + if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_cmsDictionary_id")) == false) + { + Create.Index("IX_cmsDictionary_id").OnTable("cmsDictionary") + .OnColumn("id").Ascending() + .WithOptions().NonClustered() + .WithOptions().Unique(); + } + + //now we need to create a foreign key + Create.ForeignKey("FK_cmsLanguageText_umbracoLanguage_id").FromTable("cmsLanguageText").ForeignColumn("languageId") + .ToTable("umbracoLanguage").PrimaryColumn("id").OnDeleteOrUpdate(Rule.None); + + Alter.Table("cmsDictionary").AlterColumn("parent").AsGuid().Nullable(); + + //set the parent to null if it equals the default dictionary item root id + foreach (var pk in Context.Database.Query("SELECT pk FROM cmsDictionary WHERE parent NOT IN (SELECT id FROM cmsDictionary)")) + { + Update.Table("cmsDictionary").Set(new { parent = (Guid?)null }).Where(new { pk = pk }); + } + + Create.ForeignKey("FK_cmsDictionary_cmsDictionary_id").FromTable("cmsDictionary").ForeignColumn("parent") + .ToTable("cmsDictionary").PrimaryColumn("id").OnDeleteOrUpdate(Rule.None); + } + + + + } + + public override void Down() + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddMigrationTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddMigrationTable.cs new file mode 100644 index 0000000000..079dbe1465 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddMigrationTable.cs @@ -0,0 +1,43 @@ +using System.Linq; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZero +{ + [Migration("7.3.0", 11, GlobalSettings.UmbracoMigrationName)] + public class AddMigrationTable : MigrationBase + { + public AddMigrationTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) + : base(sqlSyntax, logger) + { + } + + public override void Up() + { + //Don't exeucte if the table is already there + var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); + if (tables.InvariantContains("umbracoMigration")) return; + + Create.Table("umbracoMigration") + .WithColumn("id").AsInt32().NotNullable().PrimaryKey("PK_umbracoMigrations").Identity() + .WithColumn("name").AsString(255).NotNullable() + .WithColumn("version").AsString(50).NotNullable() + .WithColumn("createDate").AsDateTime().NotNullable().WithDefault(SystemMethods.CurrentDateTime); + + //unique constraint on name + version + Create.Index("IX_umbracoMigration").OnTable("umbracoMigration") + .OnColumn("name").Ascending() + .OnColumn("version").Ascending() + .WithOptions() + .NonClustered() + .WithOptions() + .Unique(); + } + + public override void Down() + { + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddServerRegistrationColumnsAndLock.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddServerRegistrationColumnsAndLock.cs new file mode 100644 index 0000000000..118dc1fc06 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddServerRegistrationColumnsAndLock.cs @@ -0,0 +1,58 @@ +using System; +using System.Linq; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZero +{ + [Migration("7.3.0", 17, GlobalSettings.UmbracoMigrationName)] + public class AddServerRegistrationColumnsAndLock : MigrationBase + { + public AddServerRegistrationColumnsAndLock(ISqlSyntaxProvider sqlSyntax, ILogger logger) + : base(sqlSyntax, logger) + { } + + public override void Up() + { + // don't execute if the column is already there + var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray(); + if (columns.Any(x => x.TableName.InvariantEquals("umbracoServer") && x.ColumnName.InvariantEquals("isMaster")) == false) + { + Create.Column("isMaster").OnTable("umbracoServer").AsBoolean().NotNullable().WithDefaultValue(0); + } + + EnsureLockObject(Constants.System.ServersLock, "0AF5E610-A310-4B6F-925F-E928D5416AF7", "LOCK: Servers"); + } + + public override void Down() + { + // not implemented + } + + private void EnsureLockObject(int id, string uniqueId, string text) + { + var exists = Context.Database.Exists(id); + if (exists) return; + + Insert + .IntoTable("umbracoNode") + .EnableIdentityInsert() + .Row(new + { + id = id, // NodeId + trashed = false, + parentId = -1, + nodeUser = 0, + level = 1, + path = "-1," + id, + sortOrder = 0, + uniqueId = new Guid(uniqueId), + text = text, + nodeObjectType = new Guid(Constants.ObjectTypes.LockObject), + createDate = DateTime.Now + }); + } + } +} diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddUniqueIdPropertyTypeColumn.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddUniqueIdPropertyTypeColumn.cs new file mode 100644 index 0000000000..7589c7000d --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddUniqueIdPropertyTypeColumn.cs @@ -0,0 +1,58 @@ +using System.Linq; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZero +{ + [Migration("7.3.0", 13, GlobalSettings.UmbracoMigrationName)] + public class AddUniqueIdPropertyTypeColumn : MigrationBase + { + public AddUniqueIdPropertyTypeColumn(ISqlSyntaxProvider sqlSyntax, ILogger logger) + : base(sqlSyntax, logger) + { } + + public override void Up() + { + //Don't exeucte if the column is already there + var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray(); + + if (columns.Any(x => x.TableName.InvariantEquals("cmsPropertyType") && x.ColumnName.InvariantEquals("uniqueID")) == false) + { + Create.Column("uniqueID").OnTable("cmsPropertyType").AsGuid().NotNullable().WithDefault(SystemMethods.NewGuid); + + // unique constraint on name + version + Create.Index("IX_cmsPropertyTypeUniqueID").OnTable("cmsPropertyType") + .OnColumn("uniqueID").Ascending() + .WithOptions() + .NonClustered() + .WithOptions() + .Unique(); + + // fill in the data in a way that is consistent over all environments + // (ie cannot use random guids, http://issues.umbraco.org/issue/U4-6942) + + foreach (var data in Context.Database.Query(@" +SELECT cmsPropertyType.id ptId, cmsPropertyType.Alias ptAlias, cmsContentType.alias ctAlias, umbracoNode.nodeObjectType nObjType +FROM cmsPropertyType +INNER JOIN cmsContentType +ON cmsPropertyType.contentTypeId = cmsContentType.nodeId +INNER JOIN umbracoNode +ON cmsContentType.nodeId = umbracoNode.id")) + { + // create a consistent guid from + // property alias + content type alias + object type + string guidSource = data.ptAlias + data.ctAlias + data.nObjType; + var guid = guidSource.ToGuid(); + + // set the Unique Id to the one we've generated + Update.Table("cmsPropertyType").Set(new { uniqueID = guid }).Where(new { id = data.ptId }); + } + } + } + + public override void Down() + { } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddUserColumns.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddUserColumns.cs index 9e0bb26ab9..46fde95005 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddUserColumns.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddUserColumns.cs @@ -30,7 +30,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZe if (columns.Any(x => x.TableName.InvariantEquals("umbracoUser") && x.ColumnName.InvariantEquals("lastPasswordChangeDate")) == false) Create.Column("lastPasswordChangeDate").OnTable("umbracoUser").AsDateTime().Nullable(); - if (columns.Any(x => x.TableName.InvariantEquals("umbracoUser") && x.ColumnName.InvariantEquals("lastLoginDate")) == false); + if (columns.Any(x => x.TableName.InvariantEquals("umbracoUser") && x.ColumnName.InvariantEquals("lastLoginDate")) == false) Create.Column("lastLoginDate").OnTable("umbracoUser").AsDateTime().Nullable(); } diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/CleanUpCorruptedPublishedFlags.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/CleanUpCorruptedPublishedFlags.cs new file mode 100644 index 0000000000..d62ad7645d --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/CleanUpCorruptedPublishedFlags.cs @@ -0,0 +1,49 @@ +using System.Linq; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZero +{ + /// + /// Older corrupted dbs might have multiple published flags for a content item, this shouldn't be possible + /// so we need to clear the content flag on the older version + /// + [Migration("7.3.0", 18, GlobalSettings.UmbracoMigrationName)] + public class CleanUpCorruptedPublishedFlags : MigrationBase + { + public CleanUpCorruptedPublishedFlags(ISqlSyntaxProvider sqlSyntax, ILogger logger) + : base(sqlSyntax, logger) + { + } + + public override void Up() + { + //Get all cmsDocument items that have more than one published date set + var sql = @"SELECT * FROM cmsDocument WHERE nodeId IN +(SELECT nodeId +FROM cmsDocument +GROUP BY nodeId +HAVING SUM(CASE WHEN published = 0 THEN 0 ELSE 1 END) > 1) +AND published = 1 +ORDER BY nodeId, updateDate"; + + var docs = Context.Database.Fetch(sql).GroupBy(x => x.NodeId); + + foreach (var doc in docs) + { + var latest = doc.OrderByDescending(x => x.UpdateDate).First(); + foreach (var old in doc.Where(x => x.VersionId != latest.VersionId)) + { + Update.Table("cmsDocument").Set(new {published = 0}).Where(new {nodeId = old.NodeId}); + } + } + } + + public override void Down() + { + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/CreateCacheInstructionTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/CreateCacheInstructionTable.cs index 44757aed9a..afa5a1e675 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/CreateCacheInstructionTable.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/CreateCacheInstructionTable.cs @@ -28,13 +28,11 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZe var textType = SqlSyntax.GetSpecialDbType(SpecialDbTypes.NTEXT); Create.Table("umbracoCacheInstruction") - .WithColumn("id").AsInt32().Identity().NotNullable() + .WithColumn("id").AsInt32().NotNullable().PrimaryKey("PK_umbracoCacheInstruction").Identity() .WithColumn("utcStamp").AsDateTime().NotNullable() - .WithColumn("jsonInstruction").AsCustom(textType).NotNullable(); - - Create.PrimaryKey("PK_umbracoCacheInstruction") - .OnTable("umbracoCacheInstruction") - .Column("id"); + .WithColumn("jsonInstruction").AsCustom(textType).NotNullable() + .WithColumn("originated").AsString(500).NotNullable(); + } public override void Down() diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/MigrateAndRemoveTemplateMasterColumn.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/MigrateAndRemoveTemplateMasterColumn.cs index dc241f9659..01db36abd9 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/MigrateAndRemoveTemplateMasterColumn.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/MigrateAndRemoveTemplateMasterColumn.cs @@ -3,6 +3,7 @@ using System.Linq; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZero @@ -23,46 +24,120 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZe { //Don't execute anything if there is no 'master' column - this might occur if the db is already upgraded - var cols = SqlSyntax.GetColumnsInSchema(Context.Database); + var cols = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray(); if (cols.Any(x => x.ColumnName.InvariantEquals("master") && x.TableName.InvariantEquals("cmsTemplate")) == false) { return; } + + var constraints = SqlSyntax.GetConstraintsPerColumn(Context.Database).Distinct().ToArray(); //update the parentId column for all templates to be correct so it matches the current 'master' template - //NOTE: we are using dynamic because we need to get the data in a column that no longer exists in the schema - var templates = Context.Database.Fetch(new Sql().Select("*").From(SqlSyntax)); - foreach (var template in templates) + + //In some old corrupted databases, the information will not be correct in the master column so we need to fix that + //first by nulling out the master column where the id doesn't actually exist + Execute.Sql(@"UPDATE cmsTemplate SET master = NULL WHERE " + + SqlSyntax.GetQuotedColumnName("master") + @" IS NOT NULL AND " + + SqlSyntax.GetQuotedColumnName("master") + @" NOT IN (" + + //Stupid MySQL... needs this stupid syntax because it can do an update with a sub query of itself, + // yet it can do one with a sub sub query + // ... this will work in all dbs too + @"SELECT nodeId FROM (SELECT * FROM cmsTemplate a) b)"); + + //Now we can bulk update the parentId column + + //NOTE: This single statement should be used but stupid SQLCE doesn't support Update with a FROM !! + // so now we have to do this by individual rows :( + //Execute.Sql(@"UPDATE umbracoNode + //SET parentID = COALESCE(t2." + SqlSyntax.GetQuotedColumnName("master") + @", -1) + //FROM umbracoNode t1 + //INNER JOIN cmsTemplate t2 + //ON t1.id = t2.nodeId"); + Execute.Code(database => { - Update.Table("umbracoNode").Set(new { parentID = template.master ?? -1 }).Where(new { id = template.nodeId }); + var templateData = database.Fetch("SELECT * FROM cmsTemplate"); - //now build the correct path for the template - Update.Table("umbracoNode").Set(new { path = BuildPath(template, templates) }).Where(new { id = template.nodeId }); + foreach (var template in templateData) + { + var sql = "SET parentID=@parentId WHERE id=@nodeId"; - } + LogHelper.Info("Executing sql statement: UPDATE umbracoNode " + sql); + + database.Update(sql, + new {parentId = template.master ?? -1, nodeId = template.nodeId}); + } + + return string.Empty; + }); + + //Now we can update the path, but this needs to be done in a delegate callback so that the query runs after the updates just completed + Execute.Code(database => + { + //NOTE: we are using dynamic because we need to get the data in a column that no longer exists in the schema + var templates = database.Fetch(new Sql().Select("*").From()); + foreach (var template in templates) + { + var sql = string.Format(SqlSyntax.UpdateData, + SqlSyntax.GetQuotedTableName("umbracoNode"), + "path=@buildPath", + "id=@nodeId"); + + LogHelper.Info("Executing sql statement: " + sql); + + //now build the correct path for the template + database.Execute(sql, new + { + buildPath = BuildPath(template, templates), + nodeId = template.nodeId + }); + } + + return string.Empty; + }); + + //now remove the master column and key if (this.Context.CurrentDatabaseProvider == DatabaseProviders.MySql) { - Delete.ForeignKey().FromTable("cmsTemplate").ForeignColumn("master").ToTable("umbracoUser").PrimaryColumn("id"); + //Because MySQL doesn't name keys with what you want, we need to query for the one that is associated + // this is required for this specific case because there are 2 foreign keys on the cmsTemplate table + var fkName = constraints.FirstOrDefault(x => x.Item1.InvariantEquals("cmsTemplate") && x.Item2.InvariantEquals("master")); + if (fkName != null) + { + Delete.ForeignKey(fkName.Item3).OnTable("cmsTemplate"); + } } else { - //These are the old aliases, before removing them, check they exist - var constraints = SqlSyntax.GetConstraintsPerColumn(Context.Database).Distinct().ToArray(); - if (constraints.Any(x => x.Item1.InvariantEquals("cmsTemplate") && x.Item3.InvariantEquals("FK_cmsTemplate_cmsTemplate"))) { - Delete.ForeignKey("FK_cmsTemplate_cmsTemplate").OnTable("cmsTemplate"); + Delete.ForeignKey("FK_cmsTemplate_cmsTemplate").OnTable("cmsTemplate"); } //TODO: Hopefully it's not named something else silly in some crazy old versions } - var columns = SqlSyntax.GetColumnsInSchema(Context.Database).Distinct().ToArray(); - if (columns.Any(x => x.ColumnName.InvariantEquals("master") && x.TableName.InvariantEquals("cmsTemplate"))) + + var dbIndexes = SqlSyntax.GetDefinedIndexes(Context.Database) + .Select(x => new DbIndexDefinition() + { + TableName = x.Item1, + IndexName = x.Item2, + ColumnName = x.Item3, + IsUnique = x.Item4 + }).ToArray(); + + //in some databases there's an index (IX_Master) on the master column which needs to be dropped first + var foundIndex = dbIndexes.FirstOrDefault(x => x.TableName.InvariantEquals("cmsTemplate") && x.ColumnName.InvariantEquals("master")); + if (foundIndex != null) { - Delete.Column("master").FromTable("cmsTemplate"); + Delete.Index(foundIndex.IndexName).OnTable("cmsTemplate"); + } + + if (cols.Any(x => x.ColumnName.InvariantEquals("master") && x.TableName.InvariantEquals("cmsTemplate"))) + { + Delete.Column("master").FromTable("cmsTemplate"); } } diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/MovePublicAccessXmlDataToDb.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/MovePublicAccessXmlDataToDb.cs index 7dd5f81b3a..d60385926b 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/MovePublicAccessXmlDataToDb.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/MovePublicAccessXmlDataToDb.cs @@ -30,6 +30,9 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZe } var xmlFile = IOHelper.MapPath(SystemFiles.AccessXml); + + if (File.Exists(xmlFile) == false) return; + using (var fileStream = File.OpenRead(xmlFile)) { var xml = XDocument.Load(fileStream); diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/RemoveUmbracoLoginsTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/RemoveUmbracoLoginsTable.cs new file mode 100644 index 0000000000..b842ec041a --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/RemoveUmbracoLoginsTable.cs @@ -0,0 +1,29 @@ +using System.Linq; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZero +{ + [Migration("7.3.0", 15, GlobalSettings.UmbracoMigrationName)] + public class RemoveUmbracoLoginsTable : MigrationBase + { + public RemoveUmbracoLoginsTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) + : base(sqlSyntax, logger) + { + } + + public override void Up() + { + if (SqlSyntax.GetColumnsInSchema(Context.Database).Any(x => x.TableName.InvariantEquals("umbracoUserLogins"))) + { + Delete.Table("umbracoUserLogins"); + } + } + + public override void Down() + { + throw new System.NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/AdditionalIndexesAndKeys.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/AdditionalIndexesAndKeys.cs index c629f31b17..e0c462b966 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/AdditionalIndexesAndKeys.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/AdditionalIndexesAndKeys.cs @@ -48,11 +48,33 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSixTwoZero Create.Index("IX_cmsDocument_newest").OnTable("cmsDocument").OnColumn("newest").Ascending().WithOptions().NonClustered(); } - //TODO: We need to fix this for SQL Azure since it does not let you drop any clustered indexes - // Issue: http://issues.umbraco.org/issue/U4-5673 - // Some work around notes: - // http://stackoverflow.com/questions/15872347/alter-clustered-index-column - // https://social.msdn.microsoft.com/Forums/azure/en-US/5cc4b302-fa42-4c62-956a-bbf79dbbd040/changing-clustered-index-in-azure?forum=ssdsgetstarted + //We need to do this for SQL Azure V2 since it does not let you drop any clustered indexes + // Issue: http://issues.umbraco.org/issue/U4-5673 + if (Context.CurrentDatabaseProvider == DatabaseProviders.SqlServer || Context.CurrentDatabaseProvider == DatabaseProviders.SqlAzure) + { + var version = Context.Database.ExecuteScalar("SELECT @@@@VERSION"); + if (version.Contains("Microsoft SQL Azure")) + { + var parts = version.Split(new[] { '-' }, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()).ToArray(); + if (parts.Length > 1) + { + if (parts[1].StartsWith("11.")) + { + + //we want to drop the umbracoUserLogins_Index index since it is named incorrectly and then re-create it so + // it follows the standard naming convention + if (dbIndexes.Any(x => x.IndexName.InvariantEquals("umbracoUserLogins_Index"))) + { + //It's the old version that doesn't support dropping a clustered index on a table, so we need to do some manual work. + ExecuteSqlAzureSqlForChangingIndex(); + } + + return; + } + } + } + } + //we want to drop the umbracoUserLogins_Index index since it is named incorrectly and then re-create it so // it follows the standard naming convention @@ -73,5 +95,28 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSixTwoZero Delete.Index("IX_cmsDocument_published").OnTable("cmsDocument"); Delete.Index("IX_cmsDocument_newest").OnTable("cmsDocument"); } + + private void ExecuteSqlAzureSqlForChangingIndex() + { + Context.Database.Execute(@"CREATE TABLE ""umbracoUserLogins_temp"" +( + contextID uniqueidentifier NOT NULL, + userID int NOT NULL, + [timeout] bigint NOT NULL +); +CREATE CLUSTERED INDEX ""IX_umbracoUserLogins_Index"" ON ""umbracoUserLogins_temp"" (""contextID""); +INSERT INTO ""umbracoUserLogins_temp"" SELECT * FROM ""umbracoUserLogins"" +DROP TABLE ""umbracoUserLogins"" +CREATE TABLE ""umbracoUserLogins"" +( + contextID uniqueidentifier NOT NULL, + userID int NOT NULL, + [timeout] bigint NOT NULL +); +CREATE CLUSTERED INDEX ""IX_umbracoUserLogins_Index"" ON ""umbracoUserLogins"" (""contextID""); +INSERT INTO ""umbracoUserLogins"" SELECT * FROM ""umbracoUserLogins_temp"" +DROP TABLE ""umbracoUserLogins_temp"""); + + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixZeroOne/UpdatePropertyTypesAndGroups.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixZeroOne/UpdatePropertyTypesAndGroups.cs index b4cf0d93fa..28ede71ad1 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixZeroOne/UpdatePropertyTypesAndGroups.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixZeroOne/UpdatePropertyTypesAndGroups.cs @@ -29,54 +29,52 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSixZeroOne { if (database != null) { - //Fetch all PropertyTypes that belongs to a PropertyTypeGroup - var propertyTypes = database.Fetch("WHERE propertyTypeGroupId > 0"); + //Fetch all PropertyTypes that belongs to a PropertyTypeGroup + //NOTE: We are writing the full query because we've added a column to the PropertyTypeDto in later versions so one of the columns + // won't exist yet + var propertyTypes = database.Fetch("SELECT * FROM cmsPropertyType WHERE propertyTypeGroupId > 0"); + var propertyGroups = database.Fetch("WHERE id > 0"); foreach (var propertyType in propertyTypes) { - //Get the PropertyTypeGroup that the current PropertyType references - var parentPropertyTypeGroup = propertyGroups.FirstOrDefault(x => x.Id == propertyType.PropertyTypeGroupId); - if (parentPropertyTypeGroup != null) + // get the PropertyTypeGroup of the current PropertyType, skip if not found + var propertyTypeGroup = propertyGroups.FirstOrDefault(x => x.Id == propertyType.propertyTypeGroupId); + if (propertyTypeGroup == null) continue; + + // if the PropretyTypeGroup belongs to the same content type as the PropertyType, then fine + if (propertyTypeGroup.ContentTypeNodeId == propertyType.contentTypeId) continue; + + // else we want to assign the PropertyType to a proper PropertyTypeGroup + // ie one that does belong to the same content - look for it + var okPropertyTypeGroup = propertyGroups.FirstOrDefault(x => + x.Text == propertyTypeGroup.Text && // same name + x.ContentTypeNodeId == propertyType.contentTypeId); // but for proper content type + + if (okPropertyTypeGroup == null) { - //If the ContentType is the same on the PropertyType and the PropertyTypeGroup the group is valid and we skip to the next - if (parentPropertyTypeGroup.ContentTypeNodeId == propertyType.ContentTypeId) continue; - - //Check if the 'new' PropertyTypeGroup has already been created - var existingPropertyTypeGroup = - propertyGroups.FirstOrDefault( - x => - x.ParentGroupId == parentPropertyTypeGroup.Id && x.Text == parentPropertyTypeGroup.Text && - x.ContentTypeNodeId == propertyType.ContentTypeId); - - //This should ensure that we don't create duplicate groups for a single ContentType - if (existingPropertyTypeGroup == null) + // does not exist, create a new PropertyTypeGroup, + var propertyGroup = new PropertyTypeGroupDto { + ContentTypeNodeId = propertyType.contentTypeId, + Text = propertyTypeGroup.Text, + SortOrder = propertyTypeGroup.SortOrder + }; - //Create a new PropertyTypeGroup that references the parent group that the PropertyType was referencing pre-6.0.1 - var propertyGroup = new PropertyTypeGroupDto - { - ContentTypeNodeId = propertyType.ContentTypeId, - ParentGroupId = parentPropertyTypeGroup.Id, - Text = parentPropertyTypeGroup.Text, - SortOrder = parentPropertyTypeGroup.SortOrder - }; + // save + add to list of groups + int id = Convert.ToInt16(database.Insert(propertyGroup)); + propertyGroup.Id = id; + propertyGroups.Add(propertyGroup); - //Save the PropertyTypeGroup in the database and update the list of groups with this new group - int id = Convert.ToInt16(database.Insert(propertyGroup)); - propertyGroup.Id = id; - propertyGroups.Add(propertyGroup); - //Update the reference to the new PropertyTypeGroup on the current PropertyType - propertyType.PropertyTypeGroupId = id; - database.Update(propertyType); - } - else - { - //Update the reference to the existing PropertyTypeGroup on the current PropertyType - propertyType.PropertyTypeGroupId = existingPropertyTypeGroup.Id; - database.Update(propertyType); - } + // update the PropertyType to use the new PropertyTypeGroup + propertyType.propertyTypeGroupId = id; } + else + { + // exists, update PropertyType to use the PropertyTypeGroup + propertyType.propertyTypeGroupId = okPropertyTypeGroup.Id; + } + database.Update("cmsPropertyType", "id", propertyType); } } diff --git a/src/Umbraco.Core/Persistence/PetaPoco.cs b/src/Umbraco.Core/Persistence/PetaPoco.cs index c54e9abc82..234b602a94 100644 --- a/src/Umbraco.Core/Persistence/PetaPoco.cs +++ b/src/Umbraco.Core/Persistence/PetaPoco.cs @@ -215,6 +215,10 @@ namespace Umbraco.Core.Persistence _paramPrefix = "?"; if (_dbType == DBType.Oracle) _paramPrefix = ":"; + + // by default use MSSQL default ReadCommitted level + //TODO change to RepeatableRead - but that is breaking + _isolationLevel = IsolationLevel.ReadCommitted; } // Automatically close one open shared connection @@ -237,7 +241,27 @@ namespace Umbraco.Core.Persistence _sharedConnection.ConnectionString = _connectionString; _sharedConnection.OpenWithRetry();//Changed .Open() => .OpenWithRetry() extension method - _sharedConnection = OnConnectionOpened(_sharedConnection); + // ensure we have the proper isolation level, as levels can leak in pools + // read http://stackoverflow.com/questions/9851415/sql-server-isolation-level-leaks-across-pooled-connections + // and http://stackoverflow.com/questions/641120/what-exec-sp-reset-connection-shown-in-sql-profiler-means + // + // NPoco has that code in OpenSharedConnectionImp (commented out?) + //using (var cmd = _sharedConnection.CreateCommand()) + //{ + // cmd.CommandText = GetSqlForTransactionLevel(_isolationLevel); + // cmd.CommandTimeout = CommandTimeout; + // cmd.ExecuteNonQuery(); + //} + // + // although MSDN documentation for SQL CE clearly states that the above method + // should work, it fails & reports an error parsing the query on 'TRANSACTION', + // and Google is no help (others have faced the same issue... no solution). So, + // switching to another method that does work on all databases. + var tr = _sharedConnection.BeginTransaction(_isolationLevel); + tr.Commit(); + tr.Dispose(); + + _sharedConnection = OnConnectionOpened(_sharedConnection); if (KeepConnectionAlive) _sharedConnectionDepth++; // Make sure you call Dispose @@ -269,10 +293,20 @@ namespace Umbraco.Core.Persistence // Helper to create a transaction scope public Transaction GetTransaction() { - return new Transaction(this); - } + return GetTransaction(_isolationLevel); + } - // Use by derived repo generated by T4 templates + public Transaction GetTransaction(IsolationLevel isolationLevel) + { + return new Transaction(this, isolationLevel); + } + + public IsolationLevel CurrentTransactionIsolationLevel + { + get { return _transaction == null ? IsolationLevel.Unspecified : _transaction.IsolationLevel; } + } + + // Use by derived repo generated by T4 templates public virtual void OnBeginTransaction() { } public virtual void OnEndTransaction() { } @@ -281,17 +315,23 @@ namespace Umbraco.Core.Persistence // Use `using (var scope=db.Transaction) { scope.Complete(); }` to ensure correct semantics public void BeginTransaction() { - _transactionDepth++; + BeginTransaction(_isolationLevel); + } + + public void BeginTransaction(IsolationLevel isolationLevel) + { + _transactionDepth++; if (_transactionDepth == 1) { OpenSharedConnection(); - _transaction = _sharedConnection.BeginTransaction(); + _transaction = _sharedConnection.BeginTransaction(isolationLevel); _transactionCancelled = false; OnBeginTransaction(); } - - } + else if (isolationLevel > _transaction.IsolationLevel) + throw new Exception("Already in a transaction with a lower isolation level."); + } // Internal helper to cleanup transaction stuff void CleanupTransaction() @@ -313,6 +353,7 @@ namespace Umbraco.Core.Persistence public void AbortTransaction() { _transactionCancelled = true; + //TODO what shall we do if transactionDepth is already zero? if ((--_transactionDepth) == 0) CleanupTransaction(); } @@ -320,11 +361,32 @@ namespace Umbraco.Core.Persistence // Complete the transaction public void CompleteTransaction() { - if ((--_transactionDepth) == 0) + //TODO what shall we do if transactionDepth is already zero? + if ((--_transactionDepth) == 0) CleanupTransaction(); } - // Helper to handle named parameters from object properties + // in NPoco this is in DatabaseType + private static string GetSqlForTransactionLevel(IsolationLevel isolationLevel) + { + switch (isolationLevel) + { + case IsolationLevel.ReadCommitted: + return "SET TRANSACTION ISOLATION LEVEL READ COMMITTED"; + case IsolationLevel.ReadUncommitted: + return "SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED"; + case IsolationLevel.RepeatableRead: + return "SET TRANSACTION ISOLATION LEVEL REPEATABLE READ"; + case IsolationLevel.Serializable: + return "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE"; + case IsolationLevel.Snapshot: + return "SET TRANSACTION ISOLATION LEVEL SNAPSHOT"; + default: + return "SET TRANSACTION ISOLATION LEVEL READ COMMITTED"; + } + } + + // Helper to handle named parameters from object properties static Regex rxParams = new Regex(@"(? args_dest) { @@ -2264,16 +2326,17 @@ namespace Umbraco.Core.Persistence string _lastSql; object[] _lastArgs; string _paramPrefix = "@"; + IsolationLevel _isolationLevel; } // Transaction object helps maintain transaction depth counts public class Transaction : IDisposable { - public Transaction(Database db) - { - _db = db; - _db.BeginTransaction(); - } + public Transaction(Database db, IsolationLevel isolationLevel) + { + _db = db; + _db.BeginTransaction(isolationLevel); + } public virtual void Complete() { @@ -2283,6 +2346,7 @@ namespace Umbraco.Core.Persistence public void Dispose() { + //TODO prevent multiple calls to Dispose if (_db != null) _db.AbortTransaction(); } diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentPreviewRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentPreviewRepository.cs index 6b77c5972f..e40460ee7d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentPreviewRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentPreviewRepository.cs @@ -83,7 +83,7 @@ namespace Umbraco.Core.Persistence.Repositories NodeId = entity.Id, Timestamp = DateTime.Now, VersionId = entity.Version, - Xml = entity.Xml.ToString(SaveOptions.None) + Xml = entity.Xml.ToDataString() }; //We need to do a special InsertOrUpdate here because we know that the PreviewXmlDto table has a composite key and thus diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index 25c2bb63e9..2b150529b3 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -38,8 +38,8 @@ namespace Umbraco.Core.Persistence.Repositories private readonly ContentPreviewRepository _contentPreviewRepository; private readonly ContentXmlRepository _contentXmlRepository; - public ContentRepository(IDatabaseUnitOfWork work, CacheHelper cacheHelper, ILogger logger, ISqlSyntaxProvider syntaxProvider, IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository, IMappingResolver mappingResolver) - : base(work, cacheHelper, logger, syntaxProvider, mappingResolver) + public ContentRepository(IDatabaseUnitOfWork work, CacheHelper cacheHelper, ILogger logger, ISqlSyntaxProvider syntaxProvider, IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository, IContentSection contentSection) + : base(work, cacheHelper, logger, syntaxProvider, contentSection) { if (contentTypeRepository == null) throw new ArgumentNullException("contentTypeRepository"); if (templateRepository == null) throw new ArgumentNullException("templateRepository"); @@ -238,7 +238,7 @@ namespace Umbraco.Core.Persistence.Repositories var xmlItems = (from descendant in descendants let xml = serializer(descendant) - select new ContentXmlDto { NodeId = descendant.Id, Xml = xml.ToString(SaveOptions.None) }).ToArray(); + select new ContentXmlDto { NodeId = descendant.Id, Xml = xml.ToDataString() }).ToArray(); //bulk insert it into the database Database.BulkInsertRecords(SqlSyntax, xmlItems, tr); @@ -340,6 +340,12 @@ namespace Umbraco.Core.Persistence.Repositories { ((Content)entity).AddingEntity(); + //ensure the default template is assigned + if (entity.Template == null) + { + entity.Template = entity.ContentType.DefaultTemplate; + } + //Ensure unique name on the same level entity.Name = EnsureUniqueNodeName(entity.ParentId, entity.Name); @@ -352,10 +358,11 @@ namespace Umbraco.Core.Persistence.Repositories //NOTE Should the logic below have some kind of fallback for empty parent ids ? //Logic for setting Path, Level and SortOrder var parent = Database.First("WHERE id = @ParentId", new { ParentId = entity.ParentId }); - int level = parent.Level + 1; - int sortOrder = - Database.ExecuteScalar("SELECT COUNT(*) FROM umbracoNode WHERE parentID = @ParentId AND nodeObjectType = @NodeObjectType", - new { ParentId = entity.ParentId, NodeObjectType = NodeObjectTypeId }); + var level = parent.Level + 1; + var maxSortOrder = Database.ExecuteScalar( + "SELECT coalesce(max(sortOrder),-1) FROM umbracoNode WHERE parentid = @ParentId AND nodeObjectType = @NodeObjectType", + new { /*ParentId =*/ entity.ParentId, NodeObjectType = NodeObjectTypeId }); + var sortOrder = maxSortOrder + 1; //Create the (base) node data - umbracoNode var nodeDto = dto.ContentVersionDto.ContentDto.NodeDto; @@ -647,8 +654,8 @@ namespace Umbraco.Core.Persistence.Repositories var translator = new SqlTranslator(sqlClause, query); var sql = translator.Translate() .Where(SqlSyntax, x => x.Published) - .OrderBy(SqlSyntax, x => x.Level) - .OrderBy(SqlSyntax, x => x.SortOrder); + .OrderBy(x => x.Level, SqlSyntax) + .OrderBy(x => x.SortOrder, SqlSyntax); //NOTE: This doesn't allow properties to be part of the query var dtos = Database.Fetch(sql); @@ -659,7 +666,7 @@ namespace Umbraco.Core.Persistence.Repositories // then we can use that entity. Otherwise if it is not published (which can be the case // because we only store the 'latest' entries in the cache which might not be the published // version) - var fromCache = RepositoryCache.RuntimeCache.GetCacheItem(GetCacheIdKey(dto.NodeId)); + var fromCache = RuntimeCache.GetCacheItem(GetCacheIdKey(dto.NodeId)); //var fromCache = TryGetFromCache(dto.NodeId); if (fromCache != null && fromCache.Published) { @@ -819,12 +826,13 @@ namespace Umbraco.Core.Persistence.Repositories var contentTypes = _contentTypeRepository.GetAll(dtos.Select(x => x.ContentVersionDto.ContentDto.ContentTypeId).ToArray()) .ToArray(); + + var ids = dtos + .Where(dto => dto.TemplateId.HasValue && dto.TemplateId.Value > 0) + .Select(x => x.TemplateId.Value).ToArray(); + //NOTE: This should be ok for an SQL 'IN' statement, there shouldn't be an insane amount of content types - var templates = _templateRepository.GetAll( - dtos - .Where(dto => dto.TemplateId.HasValue && dto.TemplateId.Value > 0) - .Select(x => x.TemplateId.Value).ToArray()) - .ToArray(); + var templates = ids.Length == 0 ? Enumerable.Empty() : _templateRepository.GetAll(ids).ToArray(); var dtosWithContentTypes = dtos //This select into and null check are required because we don't have a foreign damn key on the contentType column @@ -853,7 +861,7 @@ namespace Umbraco.Core.Persistence.Repositories /// /// Private method to create a content object from a DocumentDto, which is used by Get and GetByVersion. /// - /// + /// /// /// /// @@ -871,6 +879,11 @@ namespace Umbraco.Core.Persistence.Repositories { content.Template = template ?? _templateRepository.Get(dto.TemplateId.Value); } + else + { + //ensure there isn't one set. + content.Template = null; + } content.Properties = propCollection; @@ -883,7 +896,7 @@ namespace Umbraco.Core.Persistence.Repositories /// /// Private method to create a content object from a DocumentDto, which is used by Get and GetByVersion. /// - /// + /// /// /// /// diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs index 3aadcfa5d2..2d4e2cb638 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs @@ -6,6 +6,8 @@ using System.Globalization; using System.Linq; using System.Text; using System.Threading.Tasks; +using Umbraco.Core.Events; +using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; @@ -17,6 +19,7 @@ using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.Relators; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; +using Umbraco.Core.Services; namespace Umbraco.Core.Persistence.Repositories { @@ -25,13 +28,55 @@ namespace Umbraco.Core.Persistence.Repositories /// /// Exposes shared functionality /// - internal abstract class ContentTypeBaseRepository : PetaPocoRepositoryBase + internal abstract class ContentTypeBaseRepository : PetaPocoRepositoryBase, IReadRepository where TEntity : class, IContentTypeComposition { - protected ContentTypeBaseRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IMappingResolver mappingResolver) : base(work, cache, logger, sqlSyntax, mappingResolver) { + _guidRepo = new GuidReadOnlyContentTypeBaseRepository(this, work, cache, logger, sqlSyntax); + } + + private readonly GuidReadOnlyContentTypeBaseRepository _guidRepo; + + public IEnumerable> Move(TEntity toMove, EntityContainer container) + { + var parentId = -1; + if (container != null) + { + // Check on paths + if ((string.Format(",{0},", container.Path)).IndexOf(string.Format(",{0},", toMove.Id), StringComparison.Ordinal) > -1) + { + throw new DataOperationException(MoveOperationStatusType.FailedNotAllowedByPath); + } + parentId = container.Id; + } + + //used to track all the moved entities to be given to the event + var moveInfo = new List> + { + new MoveEventInfo(toMove, toMove.Path, parentId) + }; + + //do the move to a new parent + toMove.ParentId = parentId; + //schedule it for updating in the transaction + AddOrUpdate(toMove); + + //update all descendants + var descendants = this.GetByQuery( + new Query().Where(type => type.Path.StartsWith(toMove.Path + ","))); + foreach (var descendant in descendants) + { + moveInfo.Add(new MoveEventInfo(descendant, descendant.Path, descendant.ParentId)); + + //all we're doing here is setting the parent Id to be dirty so that it resets the path/level/etc... + descendant.ParentId = descendant.ParentId; + //schedule it for updating in the transaction + AddOrUpdate(descendant); + } + + return moveInfo; } /// @@ -51,7 +96,7 @@ namespace Umbraco.Core.Persistence.Repositories var translator = new SqlTranslator(sqlClause, query); var sql = translator.Translate() - .OrderBy(SqlSyntax, x => x.PropertyTypeGroupId); + .OrderBy(x => x.PropertyTypeGroupId, SqlSyntax); var dtos = Database.Fetch(new GroupPropertyTypeRelator().Map, sql); @@ -60,14 +105,17 @@ namespace Umbraco.Core.Persistence.Repositories yield return dto.ContentTypeNodeId; } } - + protected virtual PropertyType CreatePropertyType(string propertyEditorAlias, DataTypeDatabaseType dbType, string propertyTypeAlias) { return new PropertyType(propertyEditorAlias, dbType, propertyTypeAlias); } - protected void PersistNewBaseContentType(ContentTypeDto dto, IContentTypeComposition entity) + protected void PersistNewBaseContentType(IContentTypeComposition entity) { + var factory = new ContentTypeFactory(); + var dto = factory.BuildContentTypeDto(entity); + //Cannot add a duplicate content type type var exists = Database.ExecuteScalar(@"SELECT COUNT(*) FROM cmsContentType INNER JOIN umbracoNode ON cmsContentType.nodeId = umbracoNode.id @@ -119,7 +167,7 @@ AND umbracoNode.nodeObjectType = @objectType", else { //Fallback for ContentTypes with no identity - var contentTypeDto = Database.FirstOrDefault("WHERE alias = @Alias", new {Alias = composition.Alias}); + var contentTypeDto = Database.FirstOrDefault("WHERE alias = @Alias", new { Alias = composition.Alias }); if (contentTypeDto != null) { Database.Insert(new ContentType2ContentTypeDto { ParentId = contentTypeDto.NodeId, ChildId = entity.Id }); @@ -139,7 +187,7 @@ AND umbracoNode.nodeObjectType = @objectType", } var propertyFactory = new PropertyGroupFactory(nodeDto.NodeId); - + //Insert Tabs foreach (var propertyGroup in entity.PropertyGroups) { @@ -179,38 +227,35 @@ AND umbracoNode.nodeObjectType = @objectType", } } - protected void PersistUpdatedBaseContentType(ContentTypeDto dto, IContentTypeComposition entity) + protected void PersistUpdatedBaseContentType(IContentTypeComposition entity) { + var factory = new ContentTypeFactory(); + var dto = factory.BuildContentTypeDto(entity); - //Cannot update to a duplicate alias + // ensure the alias is not used already var exists = Database.ExecuteScalar(@"SELECT COUNT(*) FROM cmsContentType INNER JOIN umbracoNode ON cmsContentType.nodeId = umbracoNode.id WHERE cmsContentType." + SqlSyntax.GetQuotedColumnName("alias") + @"= @alias AND umbracoNode.nodeObjectType = @objectType AND umbracoNode.id <> @id", - new { id = dto.NodeId, alias = entity.Alias, objectType = NodeObjectTypeId }); + new { id = dto.NodeId, alias = dto.Alias, objectType = NodeObjectTypeId }); if (exists > 0) - { - throw new DuplicateNameException("An item with the alias " + entity.Alias + " already exists"); - } - - var propertyGroupFactory = new PropertyGroupFactory(entity.Id); + throw new DuplicateNameException("An item with the alias " + dto.Alias + " already exists"); + // handle (update) the node var nodeDto = dto.NodeDto; - var o = Database.Update(nodeDto); + Database.Update(nodeDto); + // fixme - why? we are UPDATING so we should ALREADY have a PK! //Look up ContentType entry to get PrimaryKey for updating the DTO - var dtoPk = Database.First("WHERE nodeId = @Id", new {Id = entity.Id}); + var dtoPk = Database.First("WHERE nodeId = @Id", new { Id = entity.Id }); dto.PrimaryKey = dtoPk.PrimaryKey; Database.Update(dto); - //Delete the ContentType composition entries before adding the updated collection - Database.Delete("WHERE childContentTypeId = @Id", new {Id = entity.Id}); - //Update ContentType composition in new table + // handle (delete then recreate) compositions + Database.Delete("WHERE childContentTypeId = @Id", new { Id = entity.Id }); foreach (var composition in entity.ContentTypeComposition) - { - Database.Insert(new ContentType2ContentTypeDto {ParentId = composition.Id, ChildId = entity.Id}); - } + Database.Insert(new ContentType2ContentTypeDto { ParentId = composition.Id, ChildId = entity.Id }); //Removing a ContentType from a composition (U4-1690) //1. Find content based on the current ContentType: entity.Id @@ -234,7 +279,7 @@ AND umbracoNode.id <> @id", foreach (var key in compositionBase.RemovedContentTypeKeyTracker) { //Find PropertyTypes for the removed ContentType - var propertyTypes = Database.Fetch("WHERE contentTypeId = @Id", new {Id = key}); + var propertyTypes = Database.Fetch("WHERE contentTypeId = @Id", new { Id = key }); //Loop through the Content that is based on the current ContentType in order to remove the Properties that are //based on the PropertyTypes that belong to the removed ContentType. foreach (var contentDto in contentDtos) @@ -259,7 +304,7 @@ AND umbracoNode.id <> @id", } //Delete the allowed content type entries before adding the updated collection - Database.Delete("WHERE Id = @Id", new {Id = entity.Id}); + Database.Delete("WHERE Id = @Id", new { Id = entity.Id }); //Insert collection of allowed content types foreach (var allowedContentType in entity.AllowedContentTypes) { @@ -272,10 +317,10 @@ AND umbracoNode.id <> @id", } - if (((ICanBeDirty) entity).IsPropertyDirty("PropertyTypes") || entity.PropertyTypes.Any(x => x.IsDirty())) + if (((ICanBeDirty)entity).IsPropertyDirty("PropertyTypes") || entity.PropertyTypes.Any(x => x.IsDirty())) { //Delete PropertyTypes by excepting entries from db with entries from collections - var dbPropertyTypes = Database.Fetch("WHERE contentTypeId = @Id", new {Id = entity.Id}); + var dbPropertyTypes = Database.Fetch("WHERE contentTypeId = @Id", new { Id = entity.Id }); var dbPropertyTypeAlias = dbPropertyTypes.Select(x => x.Id); var entityPropertyTypes = entity.PropertyTypes.Where(x => x.HasIdentity).Select(x => x.Id); var items = dbPropertyTypeAlias.Except(entityPropertyTypes); @@ -283,60 +328,49 @@ AND umbracoNode.id <> @id", { //Before a PropertyType can be deleted, all Properties based on that PropertyType should be deleted. Database.Delete("WHERE propertyTypeId = @Id", new { Id = item }); - Database.Delete("WHERE propertytypeid = @Id", new {Id = item}); + Database.Delete("WHERE propertytypeid = @Id", new { Id = item }); Database.Delete("WHERE contentTypeId = @Id AND id = @PropertyTypeId", - new {Id = entity.Id, PropertyTypeId = item}); + new { Id = entity.Id, PropertyTypeId = item }); } } - if (entity.IsPropertyDirty("PropertyGroups") || - entity.PropertyGroups.Any(x => x.IsDirty())) + if (entity.IsPropertyDirty("PropertyGroups") || entity.PropertyGroups.Any(x => x.IsDirty())) { - //Delete Tabs/Groups by excepting entries from db with entries from collections - var dbPropertyGroups = - Database.Fetch("WHERE contenttypeNodeId = @Id", new {Id = entity.Id}) - .Select(x => new Tuple(x.Id, x.Text)) - .ToList(); - var entityPropertyGroups = entity.PropertyGroups.Select(x => new Tuple(x.Id, x.Name)).ToList(); - var tabsToDelete = dbPropertyGroups.Select(x => x.Item1).Except(entityPropertyGroups.Select(x => x.Item1)); - var tabs = dbPropertyGroups.Where(x => tabsToDelete.Any(y => y == x.Item1)); - //Update Tab name downstream to ensure renaming is done properly - foreach (var propertyGroup in entityPropertyGroups) + // todo + // we used to try to propagate tabs renaming downstream, relying on ParentId, but + // 1) ParentId makes no sense (if a tab can be inherited from multiple composition + // types) so we would need to figure things out differently, visiting downstream + // content types and looking for tabs with the same name... + // 2) It was not deployable as changing a content type changes other content types + // that was not deterministic, because it would depend on the order of the changes. + // That last point could be fixed if (1) is fixed, but then it still is an issue with + // deploy because changing a content type changes other content types that are not + // dependencies but dependents, and then what? + // + // So... for the time being, all renaming propagation is disabled. We just don't do it. + + // (all gone) + + // delete tabs that do not exist anymore + // get the tabs that are currently existing (in the db) + // get the tabs that we want, now + // and derive the tabs that we want to delete + var existingPropertyGroups = Database.Fetch("WHERE contentTypeNodeId = @id", new { id = entity.Id }) + .Select(x => x.Id) + .ToList(); + var newPropertyGroups = entity.PropertyGroups.Select(x => x.Id).ToList(); + var tabsToDelete = existingPropertyGroups + .Except(newPropertyGroups) + .ToArray(); + + // move properties to generic properties, and delete the tabs + if (tabsToDelete.Length > 0) { - Database.Update("SET Text = @TabName WHERE parentGroupId = @TabId", - new { TabName = propertyGroup.Item2, TabId = propertyGroup.Item1 }); - - var childGroups = Database.Fetch("WHERE parentGroupId = @TabId", new { TabId = propertyGroup.Item1 }); - foreach (var childGroup in childGroups) - { - var sibling = Database.Fetch("WHERE contenttypeNodeId = @Id AND text = @Name", - new { Id = childGroup.ContentTypeNodeId, Name = propertyGroup.Item2 }) - .FirstOrDefault(x => x.ParentGroupId.HasValue == false || x.ParentGroupId.Value.Equals(propertyGroup.Item1) == false); - //If the child group doesn't have a sibling there is no chance of duplicates and we continue - if (sibling == null || (sibling.ParentGroupId.HasValue && sibling.ParentGroupId.Value.Equals(propertyGroup.Item1))) continue; - - //Since the child group has a sibling with the same name we need to point any PropertyTypes to the sibling - //as this child group is about to leave the party. - Database.Update( - "SET propertyTypeGroupId = @PropertyTypeGroupId WHERE propertyTypeGroupId = @PropertyGroupId AND ContentTypeId = @ContentTypeId", - new { PropertyTypeGroupId = sibling.Id, PropertyGroupId = childGroup.Id, ContentTypeId = childGroup.ContentTypeNodeId }); - - //Since the parent group has been renamed and we have duplicates we remove this group - //and leave our sibling in charge of the part. - Database.Delete(childGroup); - } - } - //Do Tab updates - foreach (var tab in tabs) - { - Database.Update("SET propertyTypeGroupId = NULL WHERE propertyTypeGroupId = @PropertyGroupId", - new {PropertyGroupId = tab.Item1}); - Database.Update("SET parentGroupId = NULL WHERE parentGroupId = @TabId", - new {TabId = tab.Item1}); - Database.Delete("WHERE contenttypeNodeId = @Id AND text = @Name", - new {Id = entity.Id, Name = tab.Item2}); + Database.Update("SET propertyTypeGroupId=NULL WHERE propertyTypeGroupId IN (@ids)", new { ids = tabsToDelete }); + Database.Delete("WHERE id IN (@ids)", new { ids = tabsToDelete }); } } + var propertyGroupFactory = new PropertyGroupFactory(entity.Id); //Run through all groups to insert or update entries foreach (var propertyGroup in entity.PropertyGroups) @@ -380,25 +414,6 @@ AND umbracoNode.id <> @id", if (propertyType.HasIdentity == false) propertyType.Id = typePrimaryKey; //Set Id on new PropertyType } - - //If a Composition is removed we need to update/reset references to the PropertyGroups on that ContentType - if (entity.IsPropertyDirty("ContentTypeComposition") && - compositionBase != null && - compositionBase.RemovedContentTypeKeyTracker != null && - compositionBase.RemovedContentTypeKeyTracker.Any()) - { - foreach (var compositionId in compositionBase.RemovedContentTypeKeyTracker) - { - var dbPropertyGroups = - Database.Fetch("WHERE contenttypeNodeId = @Id", new { Id = compositionId }) - .Select(x => x.Id); - foreach (var propertyGroup in dbPropertyGroups) - { - Database.Update("SET parentGroupId = NULL WHERE parentGroupId = @TabId AND contenttypeNodeId = @ContentTypeNodeId", - new { TabId = propertyGroup, ContentTypeNodeId = entity.Id }); - } - } - } } protected IEnumerable GetAllowedContentTypeIds(int id) @@ -448,7 +463,7 @@ AND umbracoNode.id <> @id", var list = new List(); foreach (var dto in dtos.Where(x => (x.PropertyTypeGroupId > 0) == false)) { - var propType = CreatePropertyType(dto.DataTypeDto.PropertyEditorAlias, dto.DataTypeDto.DbType.EnumParse(true), dto.Alias); + var propType = CreatePropertyType(dto.DataTypeDto.PropertyEditorAlias, dto.DataTypeDto.DbType.EnumParse(true), dto.Alias); propType.DataTypeDefinitionId = dto.DataTypeId; propType.Description = dto.Description; propType.Id = dto.Id; @@ -499,7 +514,7 @@ AND umbracoNode.id <> @id", throw exception; }); } - + /// /// Try to set the data type id based on its ControlId /// @@ -562,28 +577,30 @@ AND umbracoNode.id <> @id", } } - public static IEnumerable GetMediaTypes( - int[] mediaTypeIds, Database db, ISqlSyntaxProvider sqlSyntax, + public static IEnumerable GetMediaTypes( + TId[] mediaTypeIds, Database db, ISqlSyntaxProvider sqlSyntax, TRepo contentTypeRepository) - where TRepo : IRepositoryQueryable + where TRepo : IReadRepository + where TId: struct { - IDictionary> allParentMediaTypeIds; + IDictionary> allParentMediaTypeIds; var mediaTypes = MapMediaTypes(mediaTypeIds, db, sqlSyntax, out allParentMediaTypeIds) .ToArray(); MapContentTypeChildren(mediaTypes, db, sqlSyntax, contentTypeRepository, allParentMediaTypeIds); - + return mediaTypes; } - public static IEnumerable GetContentTypes( - int[] contentTypeIds, Database db, ISqlSyntaxProvider sqlSyntax, + public static IEnumerable GetContentTypes( + TId[] contentTypeIds, Database db, ISqlSyntaxProvider sqlSyntax, TRepo contentTypeRepository, ITemplateRepository templateRepository) - where TRepo : IRepositoryQueryable + where TRepo : IReadRepository + where TId : struct { - IDictionary> allAssociatedTemplates; - IDictionary> allParentContentTypeIds; + IDictionary> allAssociatedTemplates; + IDictionary> allParentContentTypeIds; var contentTypes = MapContentTypes(contentTypeIds, db, sqlSyntax, out allAssociatedTemplates, out allParentContentTypeIds) .ToArray(); @@ -593,17 +610,18 @@ AND umbracoNode.id <> @id", contentTypes, db, contentTypeRepository, templateRepository, allAssociatedTemplates); MapContentTypeChildren( - contentTypes, db, sqlSyntax, contentTypeRepository, allParentContentTypeIds); + contentTypes, db, sqlSyntax, contentTypeRepository, allParentContentTypeIds); } return contentTypes; } - internal static void MapContentTypeChildren(IContentTypeComposition[] contentTypes, + internal static void MapContentTypeChildren(IContentTypeComposition[] contentTypes, Database db, ISqlSyntaxProvider sqlSyntax, TRepo contentTypeRepository, - IDictionary> allParentContentTypeIds) - where TRepo : IRepositoryQueryable + IDictionary> allParentContentTypeIds) + where TRepo : IReadRepository + where TId : struct { //NOTE: SQL call #2 @@ -615,9 +633,9 @@ AND umbracoNode.id <> @id", foreach (var contentType in contentTypes) { contentType.PropertyGroups = allPropGroups[contentType.Id]; - ((ContentTypeBase) contentType).PropertyTypes = allPropTypes[contentType.Id]; + contentType.NoGroupPropertyTypes = allPropTypes[contentType.Id]; } - + //NOTE: SQL call #3++ if (allParentContentTypeIds != null) @@ -628,7 +646,18 @@ AND umbracoNode.id <> @id", var allParentContentTypes = contentTypeRepository.GetAll(allParentIdsAsArray).ToArray(); foreach (var contentType in contentTypes) { - var parentContentTypes = allParentContentTypes.Where(x => allParentContentTypeIds[contentType.Id].Contains(x.Id)); + //TODO: this is pretty hacky right now but i don't have time to refactor/fix running queries based on ints and Guids + // (i.e. for v8) but we need queries by GUIDs now so this is how it's gonna have to be + var entityId = typeof(TId) == typeof(int) ? contentType.Id : (object)contentType.Key; + + var parentContentTypes = allParentContentTypes.Where(x => + { + //TODO: this is pretty hacky right now but i don't have time to refactor/fix running queries based on ints and Guids + // (i.e. for v8) but we need queries by GUIDs now so this is how it's gonna have to be + var parentEntityId = typeof(TId) == typeof(int) ? x.Id : (object)x.Key; + + return allParentContentTypeIds[(TId)entityId].Contains((TId)parentEntityId); + }); foreach (var parentContentType in parentContentTypes) { var result = contentType.AddContentType(parentContentType); @@ -642,15 +671,16 @@ AND umbracoNode.id <> @id", } } - + } - internal static void MapContentTypeTemplates(IContentType[] contentTypes, + internal static void MapContentTypeTemplates(IContentType[] contentTypes, Database db, TRepo contentTypeRepository, ITemplateRepository templateRepository, - IDictionary> associatedTemplates) - where TRepo : IRepositoryQueryable + IDictionary> associatedTemplates) + where TRepo : IReadRepository + where TId: struct { if (associatedTemplates == null || associatedTemplates.Any() == false) return; @@ -667,7 +697,11 @@ AND umbracoNode.id <> @id", foreach (var contentType in contentTypes) { - var associatedTemplateIds = associatedTemplates[contentType.Id].Select(x => x.TemplateId) + //TODO: this is pretty hacky right now but i don't have time to refactor/fix running queries based on ints and Guids + // (i.e. for v8) but we need queries by GUIDs now so this is how it's gonna have to be + var entityId = typeof(TId) == typeof(int) ? contentType.Id : (object)contentType.Key; + + var associatedTemplateIds = associatedTemplates[(TId)entityId].Select(x => x.TemplateId) .Distinct() .ToArray(); @@ -676,11 +710,12 @@ AND umbracoNode.id <> @id", : Enumerable.Empty()).ToArray(); } - + } - internal static IEnumerable MapMediaTypes(int[] mediaTypeIds, Database db, ISqlSyntaxProvider sqlSyntax, - out IDictionary> parentMediaTypeIds) + internal static IEnumerable MapMediaTypes(TId[] mediaTypeIds, Database db, ISqlSyntaxProvider sqlSyntax, + out IDictionary> parentMediaTypeIds) + where TId : struct { Mandate.That(mediaTypeIds.Any(), () => new InvalidOperationException("must be at least one content type id specified")); Mandate.ParameterNotNull(db, "db"); @@ -691,7 +726,7 @@ AND umbracoNode.id <> @id", var sql = @"SELECT cmsContentType.pk as ctPk, cmsContentType.alias as ctAlias, cmsContentType.allowAtRoot as ctAllowAtRoot, cmsContentType.description as ctDesc, cmsContentType.icon as ctIcon, cmsContentType.isContainer as ctIsContainer, cmsContentType.nodeId as ctId, cmsContentType.thumbnail as ctThumb, AllowedTypes.allowedId as ctaAllowedId, AllowedTypes.SortOrder as ctaSortOrder, AllowedTypes.alias as ctaAlias, - ParentTypes.parentContentTypeId as chtParentId, + ParentTypes.parentContentTypeId as chtParentId, ParentTypes.parentContentTypeKey as chtParentKey, umbracoNode.createDate as nCreateDate, umbracoNode." + sqlSyntax.GetQuotedColumnName("level") + @" as nLevel, umbracoNode.nodeObjectType as nObjectType, umbracoNode.nodeUser as nUser, umbracoNode.parentID as nParentId, umbracoNode." + sqlSyntax.GetQuotedColumnName("path") + @" as nPath, umbracoNode.sortOrder as nSortOrder, umbracoNode." + sqlSyntax.GetQuotedColumnName("text") + @" as nName, umbracoNode.trashed as nTrashed, umbracoNode.uniqueID as nUniqueId @@ -705,10 +740,28 @@ AND umbracoNode.id <> @id", ON cmsContentTypeAllowedContentType.AllowedId = cmsContentType.nodeId ) AllowedTypes ON AllowedTypes.Id = cmsContentType.nodeId - LEFT JOIN cmsContentType2ContentType as ParentTypes + LEFT JOIN ( + SELECT cmsContentType2ContentType.parentContentTypeId, umbracoNode.uniqueID AS parentContentTypeKey, cmsContentType2ContentType.childContentTypeId + FROM cmsContentType2ContentType + INNER JOIN umbracoNode + ON cmsContentType2ContentType.parentContentTypeId = umbracoNode." + sqlSyntax.GetQuotedColumnName("id") + @" + ) ParentTypes ON ParentTypes.childContentTypeId = cmsContentType.nodeId - WHERE (umbracoNode.nodeObjectType = @nodeObjectType) - AND (umbracoNode.id IN (@contentTypeIds))"; + WHERE (umbracoNode.nodeObjectType = @nodeObjectType)"; + + if (mediaTypeIds.Any()) + { + //TODO: This is all sorts of hacky but i don't have time to refactor a lot to get both ints and guids working nicely... this will + // work for the time being. + if (typeof(TId) == typeof(int)) + { + sql = sql + " AND (umbracoNode.id IN (@contentTypeIds))"; + } + else if (typeof(TId) == typeof(Guid)) + { + sql = sql + " AND (umbracoNode.uniqueID IN (@contentTypeIds))"; + } + } //NOTE: we are going to assume there's not going to be more than 2100 content type ids since that is the max SQL param count! if ((mediaTypeIds.Length - 1) > 2000) @@ -722,7 +775,7 @@ AND umbracoNode.id <> @id", return Enumerable.Empty(); } - parentMediaTypeIds = new Dictionary>(); + parentMediaTypeIds = new Dictionary>(); var mappedMediaTypes = new List(); foreach (var contentTypeId in mediaTypeIds) @@ -734,7 +787,14 @@ AND umbracoNode.id <> @id", //first we want to get the main content type data this is 1 : 1 with umbraco node data var ct = result - .Where(x => x.ctId == currentCtId) + .Where(x => + { + //TODO: This is a bit hacky right now but don't have time to do a nice refactor to support both GUID and Int queries, so this is + // how it is for now. + return (typeof (TId) == typeof (int)) + ? x.ctId == currentCtId + : x.nUniqueId == currentCtId; + }) .Select(x => new { x.ctPk, x.ctId, x.ctAlias, x.ctAllowAtRoot, x.ctDesc, x.ctIcon, x.ctIsContainer, x.ctThumb, x.nName, x.nCreateDate, x.nLevel, x.nObjectType, x.nUser, x.nParentId, x.nPath, x.nSortOrder, x.nTrashed, x.nUniqueId }) .DistinctBy(x => (int)x.ctId) .FirstOrDefault(); @@ -758,7 +818,7 @@ AND umbracoNode.id <> @id", NodeDto = new NodeDto { CreateDate = ct.nCreateDate, - Level = (short) ct.nLevel, + Level = (short)ct.nLevel, NodeId = ct.ctId, NodeObjectType = ct.nObjectType, ParentId = ct.nParentId, @@ -770,11 +830,11 @@ AND umbracoNode.id <> @id", UserId = ct.nUser } }; - + //now create the media type object - var factory = new MediaTypeFactory(new Guid(Constants.ObjectTypes.MediaType)); - var mediaType = factory.BuildEntity(contentTypeDto); + var factory = new ContentTypeFactory(); + var mediaType = factory.BuildMediaTypeEntity(contentTypeDto); //map the allowed content types //map the child content type ids @@ -786,9 +846,10 @@ AND umbracoNode.id <> @id", return mappedMediaTypes; } - internal static IEnumerable MapContentTypes(int[] contentTypeIds, Database db, ISqlSyntaxProvider sqlSyntax, - out IDictionary> associatedTemplates, - out IDictionary> parentContentTypeIds) + internal static IEnumerable MapContentTypes(TId[] contentTypeIds, Database db, ISqlSyntaxProvider sqlSyntax, + out IDictionary> associatedTemplates, + out IDictionary> parentContentTypeIds) + where TId : struct { Mandate.ParameterNotNull(db, "db"); @@ -799,7 +860,7 @@ AND umbracoNode.id <> @id", cmsContentType.pk as ctPk, cmsContentType.alias as ctAlias, cmsContentType.allowAtRoot as ctAllowAtRoot, cmsContentType.description as ctDesc, cmsContentType.icon as ctIcon, cmsContentType.isContainer as ctIsContainer, cmsContentType.nodeId as ctId, cmsContentType.thumbnail as ctThumb, AllowedTypes.allowedId as ctaAllowedId, AllowedTypes.SortOrder as ctaSortOrder, AllowedTypes.alias as ctaAlias, - ParentTypes.parentContentTypeId as chtParentId, + ParentTypes.parentContentTypeId as chtParentId,ParentTypes.parentContentTypeKey as chtParentKey, umbracoNode.createDate as nCreateDate, umbracoNode." + sqlSyntax.GetQuotedColumnName("level") + @" as nLevel, umbracoNode.nodeObjectType as nObjectType, umbracoNode.nodeUser as nUser, umbracoNode.parentID as nParentId, umbracoNode." + sqlSyntax.GetQuotedColumnName("path") + @" as nPath, umbracoNode.sortOrder as nSortOrder, umbracoNode." + sqlSyntax.GetQuotedColumnName("text") + @" as nName, umbracoNode.trashed as nTrashed, umbracoNode.uniqueID as nUniqueId, @@ -822,11 +883,29 @@ AND umbracoNode.id <> @id", ON cmsTemplate.nodeId = umbracoNode.id ) as Template ON Template.nodeId = cmsDocumentType.templateNodeId - LEFT JOIN cmsContentType2ContentType as ParentTypes + LEFT JOIN ( + SELECT cmsContentType2ContentType.parentContentTypeId, umbracoNode.uniqueID AS parentContentTypeKey, cmsContentType2ContentType.childContentTypeId + FROM cmsContentType2ContentType + INNER JOIN umbracoNode + ON cmsContentType2ContentType.parentContentTypeId = umbracoNode." + sqlSyntax.GetQuotedColumnName("id") + @" + ) ParentTypes ON ParentTypes.childContentTypeId = cmsContentType.nodeId WHERE (umbracoNode.nodeObjectType = @nodeObjectType)"; - if(contentTypeIds.Any()) - sql = sql + " AND (umbracoNode.id IN (@contentTypeIds))"; + + if (contentTypeIds.Any()) + { + //TODO: This is all sorts of hacky but i don't have time to refactor a lot to get both ints and guids working nicely... this will + // work for the time being. + if (typeof(TId) == typeof(int)) + { + sql = sql + " AND (umbracoNode.id IN (@contentTypeIds))"; + } + else if (typeof(TId) == typeof(Guid)) + { + sql = sql + " AND (umbracoNode.uniqueID IN (@contentTypeIds))"; + } + } + //NOTE: we are going to assume there's not going to be more than 2100 content type ids since that is the max SQL param count! if ((contentTypeIds.Length - 1) > 2000) @@ -841,8 +920,8 @@ AND umbracoNode.id <> @id", return Enumerable.Empty(); } - parentContentTypeIds = new Dictionary>(); - associatedTemplates = new Dictionary>(); + parentContentTypeIds = new Dictionary>(); + associatedTemplates = new Dictionary>(); var mappedContentTypes = new List(); foreach (var contentTypeId in contentTypeIds) @@ -854,7 +933,14 @@ AND umbracoNode.id <> @id", //first we want to get the main content type data this is 1 : 1 with umbraco node data var ct = result - .Where(x => x.ctId == currentCtId) + .Where(x => + { + //TODO: This is a bit hacky right now but don't have time to do a nice refactor to support both GUID and Int queries, so this is + // how it is for now. + return (typeof(TId) == typeof(int)) + ? x.ctId == currentCtId + : x.nUniqueId == currentCtId; + }) .Select(x => new { x.ctPk, x.ctId, x.ctAlias, x.ctAllowAtRoot, x.ctDesc, x.ctIcon, x.ctIsContainer, x.ctThumb, x.nName, x.nCreateDate, x.nLevel, x.nObjectType, x.nUser, x.nParentId, x.nPath, x.nSortOrder, x.nTrashed, x.nUniqueId }) .DistinctBy(x => (int)x.ctId) .FirstOrDefault(); @@ -866,7 +952,14 @@ AND umbracoNode.id <> @id", //get the unique list of associated templates var defaultTemplates = result - .Where(x => x.ctId == currentCtId) + .Where(x => + { + //TODO: This is a bit hacky right now but don't have time to do a nice refactor to support both GUID and Int queries, so this is + // how it is for now. + return (typeof(TId) == typeof(int)) + ? x.ctId == currentCtId + : x.nUniqueId == currentCtId; + }) //use a tuple so that distinct checks both values (in some rare cases the dtIsDefault will not compute as bool?, so we force it with Convert.ToBoolean) .Select(x => new Tuple(Convert.ToBoolean(x.dtIsDefault), x.dtTemplateId)) .Where(x => x.Item1.HasValue && x.Item2.HasValue) @@ -877,7 +970,7 @@ AND umbracoNode.id <> @id", var defaultTemplate = defaultTemplates.FirstOrDefault(x => x.Item1.Value) ?? defaultTemplates.FirstOrDefault(); - var dtDto = new DocumentTypeDto + var dtDto = new ContentTypeTemplateDto { //create the content type dto ContentTypeDto = new ContentTypeDto @@ -914,7 +1007,14 @@ AND umbracoNode.id <> @id", // We will map a subset of the associated template - alias, id, name associatedTemplates.Add(currentCtId, result - .Where(x => x.ctId == currentCtId) + .Where(x => + { + //TODO: This is a bit hacky right now but don't have time to do a nice refactor to support both GUID and Int queries, so this is + // how it is for now. + return (typeof(TId) == typeof(int)) + ? x.ctId == currentCtId + : x.nUniqueId == currentCtId; + }) .Where(x => x.tId != null) .Select(x => new AssociatedTemplate(x.tId, x.tAlias, x.tText)) .Distinct() @@ -922,25 +1022,40 @@ AND umbracoNode.id <> @id", //now create the content type object - var factory = new ContentTypeFactory(new Guid(Constants.ObjectTypes.DocumentType)); - var contentType = factory.BuildEntity(dtDto); + var factory = new ContentTypeFactory(); + var contentType = factory.BuildContentTypeEntity(dtDto.ContentTypeDto); + + // NOTE + // that was done by the factory but makes little sense, moved here, so + // now we have to reset dirty props again (as the factory does it) and yet, + // we are not managing allowed templates... the whole thing is weird. + ((ContentType) contentType).DefaultTemplateId = dtDto.TemplateNodeId; + contentType.ResetDirtyProperties(false); //map the allowed content types //map the child content type ids MapCommonContentTypeObjects(contentType, currentCtId, result, parentContentTypeIds); - + mappedContentTypes.Add(contentType); } return mappedContentTypes; } - private static void MapCommonContentTypeObjects(T contentType, int currentCtId, List result, IDictionary> parentContentTypeIds) - where T: IContentTypeBase + private static void MapCommonContentTypeObjects(T contentType, TId currentCtId, List result, IDictionary> parentContentTypeIds) + where T : IContentTypeBase + where TId : struct { //map the allowed content types contentType.AllowedContentTypes = result - .Where(x => x.ctId == currentCtId) + .Where(x => + { + //TODO: This is a bit hacky right now but don't have time to do a nice refactor to support both GUID and Int queries, so this is + // how it is for now. + return (typeof(TId) == typeof(int)) + ? x.ctId == currentCtId + : x.nUniqueId == currentCtId; + }) //use tuple so we can use distinct on all vals .Select(x => new Tuple(x.ctaAllowedId, x.ctaSortOrder, x.ctaAlias)) .Where(x => x.Item1.HasValue && x.Item2.HasValue && x.Item3 != null) @@ -950,8 +1065,22 @@ AND umbracoNode.id <> @id", //map the child content type ids parentContentTypeIds.Add(currentCtId, result - .Where(x => x.ctId == currentCtId) - .Select(x => (int?)x.chtParentId) + .Where(x => + { + //TODO: This is a bit hacky right now but don't have time to do a nice refactor to support both GUID and Int queries, so this is + // how it is for now. + return (typeof(TId) == typeof(int)) + ? x.ctId == currentCtId + : x.nUniqueId == currentCtId; + }) + .Select(x => + { + //TODO: This is a bit hacky right now but don't have time to do a nice refactor to support both GUID and Int queries, so this is + // how it is for now. + return (typeof(TId) == typeof(int)) + ? (TId?)x.chtParentId + : (TId?)x.chtParentKey; + }) .Where(x => x.HasValue) .Distinct() .Select(x => x.Value).ToList()); @@ -960,21 +1089,26 @@ AND umbracoNode.id <> @id", internal static void MapGroupsAndProperties(int[] contentTypeIds, Database db, ISqlSyntaxProvider sqlSyntax, out IDictionary allPropertyTypeCollection, out IDictionary allPropertyGroupCollection) - { + { + allPropertyGroupCollection = new Dictionary(); + allPropertyTypeCollection = new Dictionary(); + + // query below is not safe + pointless if array is empty + if (contentTypeIds.Length == 0) return; // first part Gets all property groups including property type data even when no property type exists on the group // second part Gets all property types including ones that are not on a group // therefore the union of the two contains all of the property type and property group information we need // NOTE: MySQL requires a SELECT * FROM the inner union in order to be able to sort . lame. - var sqlBuilder = new StringBuilder(@"SELECT PG.contenttypeNodeId as contentTypeId, - PT.ptId, PT.ptAlias, PT.ptDesc,PT.ptMandatory,PT.ptName,PT.ptSortOrder,PT.ptRegExp, + var sqlBuilder = new StringBuilder(@"SELECT PG.contenttypeNodeId as contentTypeId, + PT.ptUniqueId as ptUniqueID, PT.ptId, PT.ptAlias, PT.ptDesc,PT.ptMandatory,PT.ptName,PT.ptSortOrder,PT.ptRegExp, PT.dtId,PT.dtDbType,PT.dtPropEdAlias, - PG.id as pgId, PG.parentGroupId as pgParentGroupId, PG.sortorder as pgSortOrder, PG." + sqlSyntax.GetQuotedColumnName("text") + @" as pgText + PG.id as pgId, PG.uniqueID as pgKey, PG.sortorder as pgSortOrder, PG." + sqlSyntax.GetQuotedColumnName("text") + @" as pgText FROM cmsPropertyTypeGroup as PG LEFT JOIN ( - SELECT PT.id as ptId, PT.Alias as ptAlias, PT." + sqlSyntax.GetQuotedColumnName("Description") + @" as ptDesc, + SELECT PT.uniqueID as ptUniqueId, PT.id as ptId, PT.Alias as ptAlias, PT." + sqlSyntax.GetQuotedColumnName("Description") + @" as ptDesc, PT.mandatory as ptMandatory, PT.Name as ptName, PT.sortOrder as ptSortOrder, PT.validationRegExp as ptRegExp, PT.propertyTypeGroupId as ptGroupId, DT.dbType as dtDbType, DT.nodeId as dtId, DT.propertyEditorAlias as dtPropEdAlias @@ -988,18 +1122,16 @@ AND umbracoNode.id <> @id", UNION SELECT PT.contentTypeId as contentTypeId, - PT.id as ptId, PT.Alias as ptAlias, PT." + sqlSyntax.GetQuotedColumnName("Description") + @" as ptDesc, + PT.uniqueID as ptUniqueID, PT.id as ptId, PT.Alias as ptAlias, PT." + sqlSyntax.GetQuotedColumnName("Description") + @" as ptDesc, PT.mandatory as ptMandatory, PT.Name as ptName, PT.sortOrder as ptSortOrder, PT.validationRegExp as ptRegExp, DT.nodeId as dtId, DT.dbType as dtDbType, DT.propertyEditorAlias as dtPropEdAlias, - PG.id as pgId, PG.parentGroupId as pgParentGroupId, PG.sortorder as pgSortOrder, PG." + sqlSyntax.GetQuotedColumnName("text") + @" as pgText + PG.id as pgId, PG.uniqueID as pgKey, PG.sortorder as pgSortOrder, PG." + sqlSyntax.GetQuotedColumnName("text") + @" as pgText FROM cmsPropertyType as PT INNER JOIN cmsDataType as DT ON PT.dataTypeId = DT.nodeId LEFT JOIN cmsPropertyTypeGroup as PG - ON PG.id = PT.propertyTypeGroupId"); - - if(contentTypeIds.Any()) - sqlBuilder.AppendLine(" WHERE (PT.contentTypeId in (@contentTypeIds))"); + ON PG.id = PT.propertyTypeGroupId + WHERE (PT.contentTypeId in (@contentTypeIds))"); sqlBuilder.AppendLine(" ORDER BY (pgId)"); @@ -1010,9 +1142,6 @@ AND umbracoNode.id <> @id", var result = db.Fetch(sqlBuilder.ToString(), new { contentTypeIds = contentTypeIds }); - allPropertyGroupCollection = new Dictionary(); - allPropertyTypeCollection = new Dictionary(); - foreach (var contentTypeId in contentTypeIds) { //from this we need to make : @@ -1023,13 +1152,13 @@ AND umbracoNode.id <> @id", int currId = contentTypeId; - var propertyGroupCollection = new PropertyGroupCollection(result + var propertyGroupCollection = new PropertyGroupCollection(result //get all rows that have a group id .Where(x => x.pgId != null) //filter based on the current content type .Where(x => x.contentTypeId == currId) //turn that into a custom object containing only the group info - .Select(x => new { GroupId = x.pgId, ParentGroupId = x.pgParentGroupId, SortOrder = x.pgSortOrder, Text = x.pgText }) + .Select(x => new { GroupId = x.pgId, SortOrder = x.pgSortOrder, Text = x.pgText, Key = x.pgKey }) //get distinct data by id .DistinctBy(x => (int)x.GroupId) //for each of these groups, create a group object with it's associated properties @@ -1042,6 +1171,7 @@ AND umbracoNode.id <> @id", Description = row.ptDesc, DataTypeDefinitionId = row.dtId, Id = row.ptId, + Key = row.ptUniqueID, Mandatory = Convert.ToBoolean(row.ptMandatory), Name = row.ptName, PropertyGroupId = new Lazy(() => group.GroupId, false), @@ -1052,15 +1182,15 @@ AND umbracoNode.id <> @id", //fill in the rest of the group properties Id = group.GroupId, Name = group.Text, - ParentId = group.ParentGroupId, - SortOrder = group.SortOrder + SortOrder = group.SortOrder, + Key = group.Key }).ToArray()); allPropertyGroupCollection[currId] = propertyGroupCollection; //Create the property type collection now (that don't have groups) - var propertyTypeCollection = new PropertyTypeCollection(result + var propertyTypeCollection = new PropertyTypeCollection(result .Where(x => x.pgId == null) //filter based on the current content type .Where(x => x.contentTypeId == currId) @@ -1070,6 +1200,7 @@ AND umbracoNode.id <> @id", Description = row.ptDesc, DataTypeDefinitionId = row.dtId, Id = row.ptId, + Key = row.ptUniqueID, Mandatory = Convert.ToBoolean(row.ptMandatory), Name = row.ptName, PropertyGroupId = null, @@ -1080,9 +1211,105 @@ AND umbracoNode.id <> @id", allPropertyTypeCollection[currId] = propertyTypeCollection; } - + } } + + /// + /// Inner repository to support the GUID lookups and keep the caching consistent + /// + internal class GuidReadOnlyContentTypeBaseRepository : PetaPocoRepositoryBase + { + private readonly ContentTypeBaseRepository _parentRepo; + + public GuidReadOnlyContentTypeBaseRepository( + ContentTypeBaseRepository parentRepo, + IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) + : base(work, cache, logger, sqlSyntax) + { + _parentRepo = parentRepo; + } + + protected override TEntity PerformGet(Guid id) + { + return _parentRepo.PerformGet(id); + } + + protected override IEnumerable PerformGetAll(params Guid[] ids) + { + return _parentRepo.PerformGetAll(ids); + } + + protected override Sql GetBaseQuery(bool isCount) + { + return _parentRepo.GetBaseQuery(isCount); + } + + protected override string GetBaseWhereClause() + { + return "umbracoNode.uniqueID = @Id"; + } + + #region No implementation required + protected override IEnumerable PerformGetByQuery(IQuery query) + { + throw new NotImplementedException(); + } + + protected override IEnumerable GetDeleteClauses() + { + throw new NotImplementedException(); + } + + protected override Guid NodeObjectTypeId + { + get { throw new NotImplementedException(); } + } + + protected override void PersistNewItem(TEntity entity) + { + throw new NotImplementedException(); + } + + protected override void PersistUpdatedItem(TEntity entity) + { + throw new NotImplementedException(); + } + #endregion + } + + protected abstract TEntity PerformGet(Guid id); + protected abstract IEnumerable PerformGetAll(params Guid[] ids); + + /// + /// Gets an Entity by Id + /// + /// + /// + public TEntity Get(Guid id) + { + return _guidRepo.Get(id); + } + + /// + /// Gets all entities of the spefified type + /// + /// + /// + public IEnumerable GetAll(params Guid[] ids) + { + return _guidRepo.GetAll(ids); + } + + /// + /// Boolean indicating whether an Entity with the specified Id exists + /// + /// + /// + public bool Exists(Guid id) + { + return _guidRepo.Exists(id); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs index 48e1687371..18c02f9767 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; +using Umbraco.Core.Events; +using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; @@ -12,6 +14,7 @@ using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.Relators; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; +using Umbraco.Core.Services; namespace Umbraco.Core.Persistence.Repositories { @@ -26,10 +29,8 @@ namespace Umbraco.Core.Persistence.Repositories : base(work, cache, logger, sqlSyntax, mappingResolver) { _templateRepository = templateRepository; - } + } - #region Overrides of RepositoryBase - protected override IContentType PerformGet(int id) { var contentTypes = ContentTypeQueryMapper.GetContentTypes( @@ -47,8 +48,7 @@ namespace Umbraco.Core.Persistence.Repositories } else { - var sql = new Sql().Select("id").From(SqlSyntax) - .Where(SqlSyntax, dto => dto.NodeObjectType == NodeObjectTypeId); + var sql = new Sql().Select("id").From(SqlSyntax).Where(dto => dto.NodeObjectType == NodeObjectTypeId); var allIds = Database.Fetch(sql).ToArray(); return ContentTypeQueryMapper.GetContentTypes(allIds, Database, SqlSyntax, this, _templateRepository); } @@ -59,16 +59,14 @@ namespace Umbraco.Core.Persistence.Repositories var sqlClause = GetBaseQuery(false); var translator = new SqlTranslator(sqlClause, query); var sql = translator.Translate() - .OrderBy(SqlSyntax, x => x.Text); + .OrderBy(x => x.Text, SqlSyntax); - var dtos = Database.Fetch(sql); + var dtos = Database.Fetch(sql); return dtos.Any() ? GetAll(dtos.DistinctBy(x => x.ContentTypeDto.NodeId).Select(x => x.ContentTypeDto.NodeId).ToArray()) : Enumerable.Empty(); } - - #endregion - + /// /// Gets all entities of the specified query /// @@ -90,9 +88,7 @@ namespace Umbraco.Core.Persistence.Repositories { return Database.Fetch("SELECT DISTINCT Alias FROM cmsPropertyType ORDER BY Alias"); } - - #region Overrides of PetaPocoRepositoryBase - + protected override Sql GetBaseQuery(bool isCount) { var sql = new Sql(); @@ -101,8 +97,8 @@ namespace Umbraco.Core.Persistence.Repositories .From(SqlSyntax) .InnerJoin(SqlSyntax) .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .LeftJoin(SqlSyntax) - .On(SqlSyntax, left => left.ContentTypeNodeId, right => right.NodeId) + .LeftJoin(SqlSyntax) + .On(SqlSyntax, left => left.ContentTypeNodeId, right => right.NodeId) .Where(SqlSyntax, x => x.NodeObjectType == NodeObjectTypeId); return sql; @@ -137,11 +133,7 @@ namespace Umbraco.Core.Persistence.Repositories { get { return new Guid(Constants.ObjectTypes.DocumentType); } } - - #endregion - - #region Unit of Work Implementation - + /// /// Deletes a content type /// @@ -180,26 +172,39 @@ namespace Umbraco.Core.Persistence.Repositories ((ContentType)entity).AddingEntity(); - var factory = new ContentTypeFactory(NodeObjectTypeId); - var dto = factory.BuildDto(entity); - - PersistNewBaseContentType(dto.ContentTypeDto, entity); - //Inserts data into the cmsDocumentType table if a template exists - if (dto.TemplateNodeId > 0) - { - dto.ContentTypeNodeId = entity.Id; - Database.Insert(dto); - } - - //Insert allowed Templates not including the default one, as that has already been inserted - foreach (var template in entity.AllowedTemplates.Where(x => x != null && x.Id != dto.TemplateNodeId)) - { - Database.Insert(new DocumentTypeDto { ContentTypeNodeId = entity.Id, TemplateNodeId = template.Id, IsDefault = false }); - } + PersistNewBaseContentType(entity); + PersistTemplates(entity, false); entity.ResetDirtyProperties(); } + protected void PersistTemplates(IContentType entity, bool clearAll) + { + // remove and insert, if required + Database.Delete("WHERE contentTypeNodeId = @Id", new { Id = entity.Id }); + + // we could do it all in foreach if we assume that the default template is an allowed template?? + var defaultTemplateId = ((ContentType) entity).DefaultTemplateId; + if (defaultTemplateId > 0) + { + Database.Insert(new ContentTypeTemplateDto + { + ContentTypeNodeId = entity.Id, + TemplateNodeId = defaultTemplateId, + IsDefault = true + }); + } + foreach (var template in entity.AllowedTemplates.Where(x => x != null && x.Id != defaultTemplateId)) + { + Database.Insert(new ContentTypeTemplateDto + { + ContentTypeNodeId = entity.Id, + TemplateNodeId = template.Id, + IsDefault = false + }); + } + } + protected override void PersistUpdatedItem(IContentType entity) { ValidateAlias(entity); @@ -220,30 +225,33 @@ namespace Umbraco.Core.Persistence.Repositories entity.SortOrder = maxSortOrder + 1; } - var factory = new ContentTypeFactory(NodeObjectTypeId); - var dto = factory.BuildDto(entity); - - PersistUpdatedBaseContentType(dto.ContentTypeDto, entity); - - //Look up DocumentType entries for updating - this could possibly be a "remove all, insert all"-approach - Database.Delete("WHERE contentTypeNodeId = @Id", new { Id = entity.Id }); - //Insert the updated DocumentTypeDto if a template exists - if (dto.TemplateNodeId > 0) - { - Database.Insert(dto); - } - - //Insert allowed Templates not including the default one, as that has already been inserted - foreach (var template in entity.AllowedTemplates.Where(x => x != null && x.Id != dto.TemplateNodeId)) - { - Database.Insert(new DocumentTypeDto { ContentTypeNodeId = entity.Id, TemplateNodeId = template.Id, IsDefault = false }); - } + PersistUpdatedBaseContentType(entity); + PersistTemplates(entity, true); entity.ResetDirtyProperties(); } - - #endregion - + protected override IContentType PerformGet(Guid id) + { + var contentTypes = ContentTypeQueryMapper.GetContentTypes( + new[] { id }, Database, SqlSyntax, this, _templateRepository); + + var contentType = contentTypes.SingleOrDefault(); + return contentType; + } + + protected override IEnumerable PerformGetAll(params Guid[] ids) + { + if (ids.Any()) + { + return ContentTypeQueryMapper.GetContentTypes(ids, Database, SqlSyntax, this, _templateRepository); + } + else + { + var sql = new Sql().Select("id").From(SqlSyntax).Where(dto => dto.NodeObjectType == NodeObjectTypeId); + var allIds = Database.Fetch(sql).ToArray(); + return ContentTypeQueryMapper.GetContentTypes(allIds, Database, SqlSyntax, this, _templateRepository); + } + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentXmlRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentXmlRepository.cs index e405091bd7..4b7c8a273a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentXmlRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentXmlRepository.cs @@ -81,8 +81,8 @@ namespace Umbraco.Core.Persistence.Repositories var poco = new ContentXmlDto { - NodeId = entity.Id, - Xml = entity.Xml.ToString(SaveOptions.None) + NodeId = entity.Id, + Xml = entity.Xml.ToDataString() }; //We need to do a special InsertOrUpdate here because we know that the ContentXmlDto table has a 1:1 relation diff --git a/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs index 6541a1300f..041e308cc9 100644 --- a/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs @@ -3,8 +3,11 @@ using System.Collections.Generic; using System.Data; using System.Globalization; using System.Linq; +using System.Text.RegularExpressions; using System.Threading; using Umbraco.Core.Cache; +using Umbraco.Core.Events; +using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; @@ -146,6 +149,10 @@ namespace Umbraco.Core.Persistence.Repositories { ((DataTypeDefinition)entity).AddingEntity(); + //ensure a datatype has a unique name before creating it + entity.Name = EnsureUniqueNodeName(entity.Name); + + //TODO: should the below be removed? //Cannot add a duplicate data type var exists = Database.ExecuteScalar(@"SELECT COUNT(*) FROM cmsDataType INNER JOIN umbracoNode ON cmsDataType.nodeId = umbracoNode.id @@ -191,6 +198,8 @@ WHERE umbracoNode." + SqlSyntax.GetQuotedColumnName("text") + "= @name", new { n protected override void PersistUpdatedItem(IDataTypeDefinition entity) { + entity.Name = EnsureUniqueNodeName(entity.Name, entity.Id); + //Cannot change to a duplicate alias var exists = Database.ExecuteScalar(@"SELECT COUNT(*) FROM cmsDataType INNER JOIN umbracoNode ON cmsDataType.nodeId = umbracoNode.id @@ -280,19 +289,22 @@ AND umbracoNode.id <> @id", return GetAndCachePreValueCollection(dataTypeId); } + internal static string GetCacheKeyRegex(int preValueId) + { + return CacheKeys.DataTypePreValuesCacheKey + @"[-\d]+-([\d]*,)*" + preValueId + @"(?!\d)[,\d$]*"; + } + public string GetPreValueAsString(int preValueId) { //We need to see if we can find the cached PreValueCollection based on the cache key above - var regex = CacheKeys.DataTypePreValuesCacheKey + @"[\d]+-[,\d]*" + preValueId + @"[,\d$]*"; - - var cached = _cacheHelper.RuntimeCache.GetCacheItemsByKeyExpression(regex); + var cached = _cacheHelper.RuntimeCache.GetCacheItemsByKeyExpression(GetCacheKeyRegex(preValueId)); if (cached != null && cached.Any()) { //return from the cache var collection = cached.First(); - var preVal = collection.FormatAsDictionary().Single(x => x.Value.Id == preValueId); - return preVal.Value.Value; + var preVal = collection.FormatAsDictionary().Single(x => x.Value.Id == preValueId); + return preVal.Value.Value; } //go and find the data type id for the pre val id passed in @@ -320,6 +332,47 @@ AND umbracoNode.id <> @id", AddOrUpdatePreValues(dtd, values); } + public IEnumerable> Move(IDataTypeDefinition toMove, EntityContainer container) + { + var parentId = -1; + if (container != null) + { + // Check on paths + if ((string.Format(",{0},", container.Path)).IndexOf(string.Format(",{0},", toMove.Id), StringComparison.Ordinal) > -1) + { + throw new DataOperationException(MoveOperationStatusType.FailedNotAllowedByPath); + } + parentId = container.Id; + } + + //used to track all the moved entities to be given to the event + var moveInfo = new List> + { + new MoveEventInfo(toMove, toMove.Path, parentId) + }; + + //do the move to a new parent + toMove.ParentId = parentId; + //schedule it for updating in the transaction + AddOrUpdate(toMove); + + //update all descendants + var descendants = this.GetByQuery( + new Query().Where(type => type.Path.StartsWith(toMove.Path + ","))); + foreach (var descendant in descendants) + { + moveInfo.Add(new MoveEventInfo(descendant, descendant.Path, descendant.ParentId)); + + //all we're doing here is setting the parent Id to be dirty so that it resets the path/level/etc... + descendant.ParentId = descendant.ParentId + 1; + descendant.ParentId = descendant.ParentId - 1; + //schedule it for updating in the transaction + AddOrUpdate(descendant); + } + + return moveInfo; + } + public void AddOrUpdatePreValues(IDataTypeDefinition dataType, IDictionary values) { var currentVals = new DataTypePreValueDto[] { }; @@ -329,7 +382,7 @@ AND umbracoNode.id <> @id", var sql = new Sql().Select("*") .From(SqlSyntax) .Where(SqlSyntax, dto => dto.DataTypeNodeId == dataType.Id) - .OrderBy(SqlSyntax, dto => dto.SortOrder); + .OrderBy(dto => dto.SortOrder, SqlSyntax); currentVals = Database.Fetch(sql).ToArray(); } @@ -414,6 +467,37 @@ AND umbracoNode.id <> @id", return collection; } + private string EnsureUniqueNodeName(string nodeName, int id = 0) + { + + + var sql = new Sql(); + sql.Select("*") + .From(SqlSyntax) + .Where(x => x.NodeObjectType == NodeObjectTypeId && x.Text.StartsWith(nodeName)); + + int uniqueNumber = 1; + var currentName = nodeName; + + var dtos = Database.Fetch(sql); + if (dtos.Any()) + { + var results = dtos.OrderBy(x => x.Text, new SimilarNodeNameComparer()); + foreach (var dto in results) + { + if (id != 0 && id == dto.NodeId) continue; + + if (dto.Text.ToLowerInvariant().Equals(currentName.ToLowerInvariant())) + { + currentName = nodeName + string.Format(" ({0})", uniqueNumber); + uniqueNumber++; + } + } + } + + return currentName; + } + /// /// Private class to handle pre-value crud based on units of work with transactions /// @@ -486,15 +570,10 @@ AND umbracoNode.id <> @id", throw new InvalidOperationException("Cannot insert a pre value for a data type that has no identity"); } - //Cannot add a duplicate alias - var exists = Database.ExecuteScalar(@"SELECT COUNT(*) FROM cmsDataTypePreValues -WHERE alias = @alias -AND datatypeNodeId = @dtdid", - new { alias = entity.Alias, dtdid = entity.DataType.Id }); - if (exists > 0) - { - throw new DuplicateNameException("A pre value with the alias " + entity.Alias + " already exists for this data type"); - } + //NOTE: We used to check that the Alias was unique for the given DataTypeNodeId prevalues list, BUT + // in reality there is no need to check the uniqueness of this alias because the only way that this code executes is + // based on an IDictionary dictionary being passed to this repository and a dictionary + // must have unique aliases by definition, so there is no need for this additional check var dto = new DataTypePreValueDto { @@ -512,17 +591,12 @@ AND datatypeNodeId = @dtdid", { throw new InvalidOperationException("Cannot update a pre value for a data type that has no identity"); } - - //Cannot change to a duplicate alias - var exists = Database.ExecuteScalar(@"SELECT COUNT(*) FROM cmsDataTypePreValues -WHERE alias = @alias -AND datatypeNodeId = @dtdid -AND id <> @id", - new { id = entity.Id, alias = entity.Alias, dtdid = entity.DataType.Id }); - if (exists > 0) - { - throw new DuplicateNameException("A pre value with the alias " + entity.Alias + " already exists for this data type"); - } + + //NOTE: We used to check that the Alias was unique for the given DataTypeNodeId prevalues list, BUT + // this causes issues when sorting the pre-values (http://issues.umbraco.org/issue/U4-5670) but in reality + // there is no need to check the uniqueness of this alias because the only way that this code executes is + // based on an IDictionary dictionary being passed to this repository and a dictionary + // must have unique aliases by definition, so there is no need for this additional check var dto = new DataTypePreValueDto { @@ -534,6 +608,8 @@ AND id <> @id", }; Database.Update(dto); } + + } internal static class PreValueConverter diff --git a/src/Umbraco.Core/Persistence/Repositories/DeepCloneRuntimeCacheProvider.cs b/src/Umbraco.Core/Persistence/Repositories/DeepCloneRuntimeCacheProvider.cs new file mode 100644 index 0000000000..0b5b42660d --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/DeepCloneRuntimeCacheProvider.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web.Caching; +using Umbraco.Core.Cache; +using Umbraco.Core.Models; +using Umbraco.Core.Models.EntityBase; + +namespace Umbraco.Core.Persistence.Repositories +{ + /// + /// A wrapper for any IRuntimeCacheProvider that ensures that all inserts and returns + /// are a deep cloned copy of the item when the item is IDeepCloneable and that tracks changes are + /// reset if the object is TracksChangesEntityBase + /// + internal class DeepCloneRuntimeCacheProvider : IRuntimeCacheProvider + { + internal IRuntimeCacheProvider InnerProvider { get; private set; } + + public DeepCloneRuntimeCacheProvider(IRuntimeCacheProvider innerProvider) + { + InnerProvider = innerProvider; + } + + #region Clear - doesn't require any changes + public void ClearAllCache() + { + InnerProvider.ClearAllCache(); + } + + public void ClearCacheItem(string key) + { + InnerProvider.ClearCacheItem(key); + } + + public void ClearCacheObjectTypes(string typeName) + { + InnerProvider.ClearCacheObjectTypes(typeName); + } + + public void ClearCacheObjectTypes() + { + InnerProvider.ClearCacheObjectTypes(); + } + + public void ClearCacheObjectTypes(Func predicate) + { + InnerProvider.ClearCacheObjectTypes(predicate); + } + + public void ClearCacheByKeySearch(string keyStartsWith) + { + InnerProvider.ClearCacheByKeySearch(keyStartsWith); + } + + public void ClearCacheByKeyExpression(string regexString) + { + InnerProvider.ClearCacheByKeyExpression(regexString); + } + #endregion + + public IEnumerable GetCacheItemsByKeySearch(string keyStartsWith) + { + return InnerProvider.GetCacheItemsByKeySearch(keyStartsWith) + .Select(CheckCloneableAndTracksChanges); + } + + public IEnumerable GetCacheItemsByKeyExpression(string regexString) + { + return InnerProvider.GetCacheItemsByKeyExpression(regexString) + .Select(CheckCloneableAndTracksChanges); + } + + public object GetCacheItem(string cacheKey) + { + var item = InnerProvider.GetCacheItem(cacheKey); + return CheckCloneableAndTracksChanges(item); + } + + public object GetCacheItem(string cacheKey, Func getCacheItem) + { + var cached = InnerProvider.GetCacheItem(cacheKey, () => + { + var result = DictionaryCacheProviderBase.GetSafeLazy(getCacheItem); + var value = result.Value; // force evaluation now - this may throw if cacheItem throws, and then nothing goes into cache + if (value == null) return null; // do not store null values (backward compat) + + return CheckCloneableAndTracksChanges(value); + }); + return CheckCloneableAndTracksChanges(cached); + } + + public object GetCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) + { + var cached = InnerProvider.GetCacheItem(cacheKey, () => + { + var result = DictionaryCacheProviderBase.GetSafeLazy(getCacheItem); + var value = result.Value; // force evaluation now - this may throw if cacheItem throws, and then nothing goes into cache + if (value == null) return null; // do not store null values (backward compat) + + return CheckCloneableAndTracksChanges(value); + }, timeout, isSliding, priority, removedCallback, dependentFiles); + + return CheckCloneableAndTracksChanges(cached); + } + + public void InsertCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) + { + InnerProvider.InsertCacheItem(cacheKey, () => + { + var result = DictionaryCacheProviderBase.GetSafeLazy(getCacheItem); + var value = result.Value; // force evaluation now - this may throw if cacheItem throws, and then nothing goes into cache + if (value == null) return null; // do not store null values (backward compat) + + return CheckCloneableAndTracksChanges(value); + }, timeout, isSliding, priority, removedCallback, dependentFiles); + } + + private static object CheckCloneableAndTracksChanges(object input) + { + var cloneable = input as IDeepCloneable; + if (cloneable != null) + { + input = cloneable.DeepClone(); + } + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + var tracksChanges = input as IRememberBeingDirty; + if (tracksChanges != null) + { + tracksChanges.ResetDirtyProperties(false); + input = tracksChanges; + } + + return input; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs b/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs index bf1d107e3c..82af6f4aaf 100644 --- a/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs @@ -36,13 +36,16 @@ namespace Umbraco.Core.Persistence.Repositories { var sql = GetBaseQuery(false) .Where(GetBaseWhereClause(), new {Id = id}) - .OrderBy(SqlSyntax, x => x.UniqueId); + .OrderBy(x => x.UniqueId, SqlSyntax); var dto = Database.Fetch(new DictionaryLanguageTextRelator().Map, sql).FirstOrDefault(); if (dto == null) return null; - var entity = ConvertFromDto(dto); + //This will be cached + var allLanguages = _languageRepository.GetAll().ToArray(); + + var entity = ConvertFromDto(dto, allLanguages); //on initial construction we don't want to have dirty properties tracked // http://issues.umbraco.org/issue/U4-1946 @@ -59,8 +62,11 @@ namespace Umbraco.Core.Persistence.Repositories sql.Where("cmsDictionary.pk in (@ids)", new { ids = ids }); } + //This will be cached + var allLanguages = _languageRepository.GetAll().ToArray(); + return Database.Fetch(new DictionaryLanguageTextRelator().Map, sql) - .Select(ConvertFromDto); + .Select(dto => ConvertFromDto(dto, allLanguages)); } protected override IEnumerable PerformGetByQuery(IQuery query) @@ -68,10 +74,13 @@ namespace Umbraco.Core.Persistence.Repositories var sqlClause = GetBaseQuery(false); var translator = new SqlTranslator(sqlClause, query); var sql = translator.Translate(); - sql.OrderBy(SqlSyntax, x => x.UniqueId); + sql.OrderBy(x => x.UniqueId, SqlSyntax); + + //This will be cached + var allLanguages = _languageRepository.GetAll().ToArray(); return Database.Fetch(new DictionaryLanguageTextRelator().Map, sql) - .Select(ConvertFromDto); + .Select(x => ConvertFromDto(x, allLanguages)); } #endregion @@ -106,12 +115,9 @@ namespace Umbraco.Core.Persistence.Repositories return new List(); } - /// - /// Returns the Top Level Parent Guid Id - /// protected override Guid NodeObjectTypeId { - get { return new Guid(Constants.Conventions.Localization.DictionaryItemRootId); } + get { throw new NotImplementedException(); } } #endregion @@ -172,8 +178,8 @@ namespace Umbraco.Core.Persistence.Repositories entity.ResetDirtyProperties(); //Clear the cache entries that exist by uniqueid/item key - RepositoryCache.RuntimeCache.ClearCacheItem(GetCacheIdKey(entity.ItemKey)); - RepositoryCache.RuntimeCache.ClearCacheItem(GetCacheIdKey(entity.Key)); + RuntimeCache.ClearCacheItem(GetCacheIdKey(entity.ItemKey)); + RuntimeCache.ClearCacheItem(GetCacheIdKey(entity.Key)); } protected override void PersistDeletedItem(IDictionaryItem entity) @@ -184,8 +190,8 @@ namespace Umbraco.Core.Persistence.Repositories Database.Delete("WHERE id = @Id", new { Id = entity.Key }); //Clear the cache entries that exist by uniqueid/item key - RepositoryCache.RuntimeCache.ClearCacheItem(GetCacheIdKey(entity.ItemKey)); - RepositoryCache.RuntimeCache.ClearCacheItem(GetCacheIdKey(entity.Key)); + RuntimeCache.ClearCacheItem(GetCacheIdKey(entity.ItemKey)); + RuntimeCache.ClearCacheItem(GetCacheIdKey(entity.Key)); } private void RecursiveDelete(Guid parentId) @@ -199,14 +205,14 @@ namespace Umbraco.Core.Persistence.Repositories Database.Delete("WHERE id = @Id", new { Id = dto.UniqueId }); //Clear the cache entries that exist by uniqueid/item key - RepositoryCache.RuntimeCache.ClearCacheItem(GetCacheIdKey(dto.Key)); - RepositoryCache.RuntimeCache.ClearCacheItem(GetCacheIdKey(dto.UniqueId)); + RuntimeCache.ClearCacheItem(GetCacheIdKey(dto.Key)); + RuntimeCache.ClearCacheItem(GetCacheIdKey(dto.UniqueId)); } } #endregion - protected IDictionaryItem ConvertFromDto(DictionaryDto dto) + protected IDictionaryItem ConvertFromDto(DictionaryDto dto, ILanguage[] allLanguages) { var factory = new DictionaryItemFactory(); var entity = factory.BuildEntity(dto); @@ -214,9 +220,11 @@ namespace Umbraco.Core.Persistence.Repositories var list = new List(); foreach (var textDto in dto.LanguageTextDtos) { - var language = _languageRepository.Get(textDto.LanguageId); + //Assuming this is cached! + var language = allLanguages.FirstOrDefault(x => x.Id == textDto.LanguageId); if (language == null) continue; + var translationFactory = new DictionaryTranslationFactory(dto.UniqueId, language); list.Add(translationFactory.BuildEntity(textDto)); } @@ -241,6 +249,47 @@ namespace Umbraco.Core.Persistence.Repositories } } + private IEnumerable GetRootDictionaryItems() + { + var query = Query.Builder.Where(x => x.ParentId == null); + return GetByQuery(query); + } + + public IEnumerable GetDictionaryItemDescendants(Guid? parentId) + { + //This will be cached + var allLanguages = _languageRepository.GetAll().ToArray(); + + //This methods will look up children at each level, since we do not store a path for dictionary (ATM), we need to do a recursive + // lookup to get descendants. Currently this is the most efficient way to do it + + Func>> getItemsFromParents = guids => + { + //needs to be in groups of 2000 because we are doing an IN clause and there's a max parameter count that can be used. + return guids.InGroupsOf(2000) + .Select(@group => + { + var sqlClause = GetBaseQuery(false) + .Where(x => x.Parent != null) + .Where(string.Format("{0} IN (@parentIds)", SqlSyntax.GetQuotedColumnName("parent")), new { parentIds = @group }); + + var translator = new SqlTranslator(sqlClause, Query.Builder); + var sql = translator.Translate(); + sql.OrderBy(x => x.UniqueId, SqlSyntax); + + return Database.Fetch(new DictionaryLanguageTextRelator().Map, sql) + .Select(x => ConvertFromDto(x, allLanguages)); + }); + }; + + var childItems = parentId.HasValue == false + ? new[] { GetRootDictionaryItems() } + : getItemsFromParents(new[] { parentId.Value }); + + return childItems.SelectRecursive(items => getItemsFromParents(items.Select(x => x.Key).ToArray())).SelectMany(items => items); + + } + private class DictionaryByUniqueIdRepository : SimpleGetRepository { private readonly DictionaryRepository _dictionaryRepository; @@ -268,7 +317,9 @@ namespace Umbraco.Core.Persistence.Repositories protected override IDictionaryItem ConvertToEntity(DictionaryDto dto) { - return _dictionaryRepository.ConvertFromDto(dto); + //This will be cached + var allLanguages = _dictionaryRepository._languageRepository.GetAll().ToArray(); + return _dictionaryRepository.ConvertFromDto(dto, allLanguages); } protected override object GetBaseWhereClauseArguments(Guid id) @@ -309,7 +360,9 @@ namespace Umbraco.Core.Persistence.Repositories protected override IDictionaryItem ConvertToEntity(DictionaryDto dto) { - return _dictionaryRepository.ConvertFromDto(dto); + //This will be cached + var allLanguages = _dictionaryRepository._languageRepository.GetAll().ToArray(); + return _dictionaryRepository.ConvertFromDto(dto, allLanguages); } protected override object GetBaseWhereClauseArguments(string id) diff --git a/src/Umbraco.Core/Persistence/Repositories/DomainRepository.cs b/src/Umbraco.Core/Persistence/Repositories/DomainRepository.cs index 9112d2549f..21ba8b4baf 100644 --- a/src/Umbraco.Core/Persistence/Repositories/DomainRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/DomainRepository.cs @@ -7,56 +7,53 @@ using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence.Mappers; +using Umbraco.Core.Persistence.FaultHandling; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; namespace Umbraco.Core.Persistence.Repositories { + //TODO: We need to get a readonly ISO code for the domain assigned + internal class DomainRepository : PetaPocoRepositoryBase, IDomainRepository { - private readonly IContentRepository _contentRepository; - private readonly ILanguageRepository _languageRepository; - private readonly IMappingResolver _mappingResolver; + private readonly RepositoryCacheOptions _cacheOptions; - public DomainRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IContentRepository contentRepository, ILanguageRepository languageRepository, IMappingResolver mappingResolver) - : base(work, cache, logger, sqlSyntax, mappingResolver) + public DomainRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) + : base(work, cache, logger, sqlSyntax) { - _contentRepository = contentRepository; - _languageRepository = languageRepository; - _mappingResolver = mappingResolver; + //Custom cache options for better performance + _cacheOptions = new RepositoryCacheOptions + { + GetAllCacheAllowZeroCount = true, + GetAllCacheValidateCount = false + }; } /// - /// Override the cache, this repo will not perform any cache, the caching is taken care of in the inner repository + /// Returns the repository cache options /// - /// - /// This is required because IDomain is a deep object and we dont' want to cache it since it contains an ILanguage and an IContent, when these - /// are deep cloned the object graph that will be cached will be huge. Instead we'll have an internal repository that caches the simple - /// Domain structure and we'll use the other repositories to resolve the entities to attach - /// - protected override IRuntimeCacheProvider RuntimeCache + protected override RepositoryCacheOptions RepositoryCacheOptions { - get { return new NullCacheProvider(); } + get { return _cacheOptions; } } protected override IDomain PerformGet(int id) { - using (var repo = new CachedDomainRepository(this, UnitOfWork, RepositoryCache, Logger, SqlSyntax, _mappingResolver)) - { - var factory = new DomainModelFactory(); - return factory.BuildDomainEntity(repo.Get(id), _contentRepository, _languageRepository); - } + //use the underlying GetAll which will force cache all domains + return GetAll().FirstOrDefault(x => x.Id == id); } protected override IEnumerable PerformGetAll(params int[] ids) { - using (var repo = new CachedDomainRepository(this, UnitOfWork, RepositoryCache, Logger, SqlSyntax, _mappingResolver)) + var sql = GetBaseQuery(false).Where("umbracoDomains.id > 0"); + if (ids.Any()) { - var factory = new DomainModelFactory(); - return factory.BuildDomainEntities(repo.GetAll(ids).ToArray(), _contentRepository, _languageRepository); + sql.Where("umbracoDomains.id in (@ids)", new { ids = ids }); } + + return Database.Fetch(sql).Select(ConvertFromDto); } protected override IEnumerable PerformGetByQuery(IQuery query) @@ -67,7 +64,18 @@ namespace Umbraco.Core.Persistence.Repositories protected override Sql GetBaseQuery(bool isCount) { var sql = new Sql(); - sql.Select(isCount ? "COUNT(*)" : "*").From(SqlSyntax); + if (isCount) + { + sql.Select("COUNT(*)").From(SqlSyntax); + } + else + { + sql.Select("umbracoDomains.*, umbracoLanguage.languageISOCode") + .From(SqlSyntax) + .LeftJoin(SqlSyntax) + .On(SqlSyntax, dto => dto.DefaultLanguage, dto => dto.Id); + } + return sql; } @@ -76,18 +84,13 @@ namespace Umbraco.Core.Persistence.Repositories return "umbracoDomains.id = @Id"; } - protected override void PersistDeletedItem(IDomain entity) - { - using (var repo = new CachedDomainRepository(this, UnitOfWork, RepositoryCache, Logger, SqlSyntax, _mappingResolver)) - { - var factory = new DomainModelFactory(); - repo.PersistDeletedItem(factory.BuildEntity(entity)); - } - } - protected override IEnumerable GetDeleteClauses() { - throw new NotImplementedException(); + var list = new List + { + "DELETE FROM umbracoDomains WHERE id = @Id" + }; + return list; } protected override Guid NodeObjectTypeId @@ -97,245 +100,112 @@ namespace Umbraco.Core.Persistence.Repositories protected override void PersistNewItem(IDomain entity) { - using (var repo = new CachedDomainRepository(this, UnitOfWork, RepositoryCache, Logger, SqlSyntax, _mappingResolver)) + var exists = Database.ExecuteScalar("SELECT COUNT(*) FROM umbracoDomains WHERE domainName = @domainName", new { domainName = entity.DomainName }); + if (exists > 0) throw new DuplicateNameException(string.Format("The domain name {0} is already assigned", entity.DomainName)); + + if (entity.RootContentId.HasValue) { - var factory = new DomainModelFactory(); - var cacheableEntity = factory.BuildEntity(entity); - repo.PersistNewItem(cacheableEntity); - //re-map the id - entity.Id = cacheableEntity.Id; - entity.ResetDirtyProperties(); + var contentExists = Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContent WHERE nodeId = @id", new { id = entity.RootContentId.Value }); + if (contentExists == 0) throw new NullReferenceException("No content exists with id " + entity.RootContentId.Value); } + + if (entity.LanguageId.HasValue) + { + var languageExists = Database.ExecuteScalar("SELECT COUNT(*) FROM umbracoLanguage WHERE id = @id", new { id = entity.LanguageId.Value }); + if (languageExists == 0) throw new NullReferenceException("No language exists with id " + entity.LanguageId.Value); + } + + ((UmbracoDomain)entity).AddingEntity(); + + var factory = new DomainModelFactory(); + var dto = factory.BuildDto(entity); + + var id = Convert.ToInt32(Database.Insert(dto)); + entity.Id = id; + + //if the language changed, we need to resolve the ISO code! + if (entity.LanguageId.HasValue) + { + ((UmbracoDomain)entity).LanguageIsoCode = Database.ExecuteScalar("SELECT languageISOCode FROM umbracoLanguage WHERE id=@langId", new { langId = entity.LanguageId }); + } + + entity.ResetDirtyProperties(); } protected override void PersistUpdatedItem(IDomain entity) { - using (var repo = new CachedDomainRepository(this, UnitOfWork, RepositoryCache, Logger, SqlSyntax, _mappingResolver)) + ((UmbracoDomain)entity).UpdatingEntity(); + + var exists = Database.ExecuteScalar("SELECT COUNT(*) FROM umbracoDomains WHERE domainName = @domainName AND umbracoDomains.id <> @id", + new { domainName = entity.DomainName, id = entity.Id }); + //ensure there is no other domain with the same name on another entity + if (exists > 0) throw new DuplicateNameException(string.Format("The domain name {0} is already assigned", entity.DomainName)); + + if (entity.RootContentId.HasValue) { - var factory = new DomainModelFactory(); - repo.PersistUpdatedItem(factory.BuildEntity(entity)); - entity.ResetDirtyProperties(); + var contentExists = Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContent WHERE nodeId = @id", new { id = entity.RootContentId.Value }); + if (contentExists == 0) throw new NullReferenceException("No content exists with id " + entity.RootContentId.Value); } + + if (entity.LanguageId.HasValue) + { + var languageExists = Database.ExecuteScalar("SELECT COUNT(*) FROM umbracoLanguage WHERE id = @id", new { id = entity.LanguageId.Value }); + if (languageExists == 0) throw new NullReferenceException("No language exists with id " + entity.LanguageId.Value); + } + + var factory = new DomainModelFactory(); + var dto = factory.BuildDto(entity); + + Database.Update(dto); + + //if the language changed, we need to resolve the ISO code! + if (entity.WasPropertyDirty("LanguageId")) + { + ((UmbracoDomain)entity).LanguageIsoCode = Database.ExecuteScalar("SELECT languageISOCode FROM umbracoLanguage WHERE id=@langId", new {langId = entity.LanguageId}); + } + + entity.ResetDirtyProperties(); } public IDomain GetByName(string domainName) { - using (var repo = new CachedDomainRepository(this, UnitOfWork, RepositoryCache, Logger, SqlSyntax, _mappingResolver)) - { - var factory = new DomainModelFactory(); - return factory.BuildDomainEntity( - repo.GetByQuery(repo.Query.Where(x => x.DomainName.InvariantEquals(domainName))).FirstOrDefault(), - _contentRepository, _languageRepository); - } + return GetAll().FirstOrDefault(x => x.DomainName.InvariantEquals(domainName)); } public bool Exists(string domainName) { - using (var repo = new CachedDomainRepository(this, UnitOfWork, RepositoryCache, Logger, SqlSyntax, _mappingResolver)) - { - var query = repo.Query.Where(x => x.DomainName.InvariantEquals(domainName)); - return repo.GetByQuery(query).Any(); - } + return GetAll().Any(x => x.DomainName.InvariantEquals(domainName)); } public IEnumerable GetAll(bool includeWildcards) { - using (var repo = new CachedDomainRepository(this, UnitOfWork, RepositoryCache, Logger, SqlSyntax, _mappingResolver)) - { - var factory = new DomainModelFactory(); - return factory.BuildDomainEntities(repo.GetAll().ToArray(), _contentRepository, _languageRepository) - .Where(x => includeWildcards || x.IsWildcard == false); - } + return GetAll().Where(x => includeWildcards || x.IsWildcard == false); } public IEnumerable GetAssignedDomains(int contentId, bool includeWildcards) { - using (var repo = new CachedDomainRepository(this, UnitOfWork, RepositoryCache, Logger, SqlSyntax, _mappingResolver)) - { - var factory = new DomainModelFactory(); - - var query = repo.Query.Where(x => x.RootContentId == contentId); - - return factory.BuildDomainEntities(repo.GetByQuery(query).ToArray(), _contentRepository, _languageRepository) - .Where(x => includeWildcards || x.IsWildcard == false); - } + return GetAll() + .Where(x => x.RootContentId == contentId) + .Where(x => includeWildcards || x.IsWildcard == false); } - /// - /// Dispose disposable properties - /// - /// - /// Ensure the unit of work is disposed - /// - protected override void DisposeResources() + private IDomain ConvertFromDto(DomainDto dto) { - _contentRepository.Dispose(); - _languageRepository.Dispose(); - } - - /// - /// A simple domain model that is cacheable without a large object graph - /// - internal class CacheableDomain : Entity, IAggregateRoot - { - public int? DefaultLanguageId { get; set; } - public string DomainName { get; set; } - public int? RootContentId { get; set; } - } - - /// - /// Inner repository responsible for CRUD for domains that allows caching simple data - /// - private class CachedDomainRepository : PetaPocoRepositoryBase - { - private readonly DomainRepository _domainRepo; - - public CachedDomainRepository(DomainRepository domainRepo, IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IMappingResolver mappingResolver) - : base(work, cache, logger, sqlSyntax, mappingResolver) - { - _domainRepo = domainRepo; - } - - protected override CacheableDomain PerformGet(int id) - { - var sql = GetBaseQuery(false); - sql.Where(GetBaseWhereClause(), new { Id = id }); - - var dto = Database.FirstOrDefault(sql); - if (dto == null) - return null; - - var entity = ConvertFromDto(dto); - - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - ((Entity)entity).ResetDirtyProperties(false); - - return entity; - } - - protected override IEnumerable PerformGetAll(params int[] ids) - { - var sql = GetBaseQuery(false).Where("umbracoDomains.id > 0"); - if (ids.Any()) - { - sql.Where("umbracoDomains.id in (@ids)", new { ids = ids }); - } - - return Database.Fetch(sql).Select(ConvertFromDto); - } - - protected override IEnumerable PerformGetByQuery(IQuery query) - { - var sqlClause = GetBaseQuery(false); - var translator = new SqlTranslator(sqlClause, query); - var sql = translator.Translate(); - return Database.Fetch(sql).Select(ConvertFromDto); - } - - protected override Sql GetBaseQuery(bool isCount) - { - return _domainRepo.GetBaseQuery(isCount); - } - - protected override string GetBaseWhereClause() - { - return _domainRepo.GetBaseWhereClause(); - } - - protected override IEnumerable GetDeleteClauses() - { - var list = new List - { - "DELETE FROM umbracoDomains WHERE id = @Id" - }; - return list; - } - - protected override Guid NodeObjectTypeId - { - get { throw new NotImplementedException(); } - } - - protected override void PersistNewItem(CacheableDomain entity) - { - var exists = Database.ExecuteScalar("SELECT COUNT(*) FROM umbracoDomains WHERE domainName = @domainName", new { domainName = entity.DomainName }); - if (exists > 0) throw new DuplicateNameException(string.Format("The domain name {0} is already assigned", entity.DomainName)); - - entity.AddingEntity(); - - var factory = new DomainModelFactory(); - var dto = factory.BuildDto(entity); - - var id = Convert.ToInt32(Database.Insert(dto)); - entity.Id = id; - - entity.ResetDirtyProperties(); - } - - protected override void PersistUpdatedItem(CacheableDomain entity) - { - entity.UpdatingEntity(); - - var factory = new DomainModelFactory(); - var dto = factory.BuildDto(entity); - - Database.Update(dto); - - entity.ResetDirtyProperties(); - } - - private CacheableDomain ConvertFromDto(DomainDto dto) - { - var factory = new DomainModelFactory(); - var entity = factory.BuildEntity(dto); - return entity; - } - } + var factory = new DomainModelFactory(); + var entity = factory.BuildEntity(dto); + return entity; + } internal class DomainModelFactory { - public IEnumerable BuildDomainEntities(CacheableDomain[] cacheableDomains, IContentRepository contentRepository, ILanguageRepository languageRepository) + + public IDomain BuildEntity(DomainDto dto) { - var contentIds = cacheableDomains.Select(x => x.RootContentId).Where(x => x.HasValue).Select(x => x.Value).Distinct().ToArray(); - var langIds = cacheableDomains.Select(x => x.DefaultLanguageId).Where(x => x.HasValue).Select(x => x.Value).Distinct().ToArray(); - var contentItems = contentRepository.GetAll(contentIds); - var langItems = languageRepository.GetAll(langIds); - - return cacheableDomains - .WhereNotNull() - .Select(cacheableDomain => new UmbracoDomain(cacheableDomain.DomainName) - { - Id = cacheableDomain.Id, - //lookup from repo - this will be cached - Language = cacheableDomain.DefaultLanguageId.HasValue ? langItems.FirstOrDefault(l => l.Id == cacheableDomain.DefaultLanguageId.Value) : null, - //lookup from repo - this will be cached - RootContent = cacheableDomain.RootContentId.HasValue ? contentItems.FirstOrDefault(l => l.Id == cacheableDomain.RootContentId.Value) : null, - }); - } - - public IDomain BuildDomainEntity(CacheableDomain cacheableDomain, IContentRepository contentRepository, ILanguageRepository languageRepository) - { - if (cacheableDomain == null) return null; - - return new UmbracoDomain(cacheableDomain.DomainName) + var domain = new UmbracoDomain(dto.DomainName, dto.IsoCode) { - Id = cacheableDomain.Id, - //lookup from repo - this will be cached - Language = cacheableDomain.DefaultLanguageId.HasValue ? languageRepository.Get(cacheableDomain.DefaultLanguageId.Value) : null, - //lookup from repo - this will be cached - RootContent = cacheableDomain.RootContentId.HasValue ? contentRepository.Get(cacheableDomain.RootContentId.Value) : null - }; - } - - public CacheableDomain BuildEntity(IDomain entity) - { - var domain = new CacheableDomain - { - Id = entity.Id, - DefaultLanguageId = entity.Language == null ? null : (int?)entity.Language.Id, - DomainName = entity.DomainName, - RootContentId = entity.RootContent == null ? null : (int?)entity.RootContent.Id + Id = dto.Id, + LanguageId = dto.DefaultLanguage, + RootContentId = dto.RootStructureId }; //on initial construction we don't want to have dirty properties tracked // http://issues.umbraco.org/issue/U4-1946 @@ -343,18 +213,9 @@ namespace Umbraco.Core.Persistence.Repositories return domain; } - public CacheableDomain BuildEntity(DomainDto dto) + public DomainDto BuildDto(IDomain entity) { - var domain = new CacheableDomain { Id = dto.Id, DefaultLanguageId = dto.DefaultLanguage, DomainName = dto.DomainName, RootContentId = dto.RootStructureId }; - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - domain.ResetDirtyProperties(false); - return domain; - } - - public DomainDto BuildDto(CacheableDomain entity) - { - var dto = new DomainDto { DefaultLanguage = entity.DefaultLanguageId, DomainName = entity.DomainName, Id = entity.Id, RootStructureId = entity.RootContentId }; + var dto = new DomainDto { DefaultLanguage = entity.LanguageId, DomainName = entity.DomainName, Id = entity.Id, RootStructureId = entity.RootContentId }; return dto; } } diff --git a/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs b/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs new file mode 100644 index 0000000000..6fd5045e04 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs @@ -0,0 +1,252 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Cache; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.Factories; +using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Persistence.SqlSyntax; +using Umbraco.Core.Persistence.UnitOfWork; + +namespace Umbraco.Core.Persistence.Repositories +{ + /// + /// An internal repository for managing entity containers such as doc type, media type, data type containers. + /// + internal class EntityContainerRepository : PetaPocoRepositoryBase + { + public EntityContainerRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) + : base(work, cache, logger, sqlSyntax) + { } + + /// + /// Do not cache anything + /// + protected override IRuntimeCacheProvider RuntimeCache + { + get { return new NullCacheProvider(); } + } + + protected override EntityContainer PerformGet(int id) + { + var sql = GetBaseQuery(false).Where(GetBaseWhereClause(), new { id }); + + var nodeDto = Database.Fetch(sql).FirstOrDefault(); + return nodeDto == null ? null : CreateEntity(nodeDto); + } + + // temp - so we don't have to implement GetByQuery + public EntityContainer Get(Guid id) + { + var sql = GetBaseQuery(false).Where("UniqueId=@uniqueId", new { uniqueId = id }); + + var nodeDto = Database.Fetch(sql).FirstOrDefault(); + return nodeDto == null ? null : CreateEntity(nodeDto); + } + + protected override IEnumerable PerformGetAll(params int[] ids) + { + throw new NotImplementedException(); + } + + protected override IEnumerable PerformGetByQuery(IQuery query) + { + throw new NotImplementedException(); + } + + private static EntityContainer CreateEntity(NodeDto nodeDto) + { + if (nodeDto.NodeObjectType.HasValue == false) + throw new InvalidOperationException("Node with id " + nodeDto.NodeId + " has no object type."); + + // throws if node is not a container + var containedObjectType = EntityContainer.GetContainedObjectType(nodeDto.NodeObjectType.Value); + + var entity = new EntityContainer(nodeDto.NodeId, nodeDto.UniqueId, + nodeDto.ParentId, nodeDto.Path, nodeDto.Level, nodeDto.SortOrder, + containedObjectType, + nodeDto.Text, nodeDto.UserId ?? 0); + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + entity.ResetDirtyProperties(false); + + return entity; + } + + protected override Sql GetBaseQuery(bool isCount) + { + var sql = new Sql(); + if (isCount) + { + sql.Select("COUNT(*)").From(SqlSyntax); + } + else + { + sql.Select("*").From(SqlSyntax); + } + return sql; + } + + protected override string GetBaseWhereClause() + { + return "umbracoNode.id = @id"; //" and nodeObjectType = @NodeObjectType"; + } + + protected override IEnumerable GetDeleteClauses() + { + throw new NotImplementedException(); + } + + protected override Guid NodeObjectTypeId + { + get { throw new NotImplementedException(); } + } + + protected override void PersistDeletedItem(EntityContainer entity) + { + var nodeDto = Database.FirstOrDefault(new Sql().Select("*") + .From(SqlSyntax) + .Where(dto => dto.NodeId == entity.Id && dto.NodeObjectType == entity.ContainerObjectType)); + + if (nodeDto == null) return; + + // move children to the parent so they are not orphans + var childDtos = Database.Fetch(new Sql().Select("*") + .From(SqlSyntax) + .Where("parentID=@parentID AND (nodeObjectType=@containedObjectType OR nodeObjectType=@containerObjectType)", + new + { + parentID = entity.Id, + containedObjectType = entity.ContainedObjectType, + containerObjectType = entity.ContainerObjectType + })); + + foreach (var childDto in childDtos) + { + childDto.ParentId = nodeDto.ParentId; + Database.Update(childDto); + } + + // delete + Database.Delete(nodeDto); + } + + protected override void PersistNewItem(EntityContainer entity) + { + entity.Name = entity.Name.Trim(); + Mandate.ParameterNotNullOrEmpty(entity.Name, "entity.Name"); + + // guard against duplicates + var nodeDto = Database.FirstOrDefault(new Sql().Select("*") + .From(SqlSyntax) + .Where(dto => dto.ParentId == entity.ParentId && dto.Text == entity.Name && dto.NodeObjectType == entity.ContainerObjectType)); + if (nodeDto != null) + throw new InvalidOperationException("A container with the same name already exists."); + + // create + var level = 0; + var path = "-1"; + if (entity.ParentId > -1) + { + var parentDto = Database.FirstOrDefault(new Sql().Select("*") + .From(SqlSyntax) + .Where(dto => dto.NodeId == entity.ParentId && dto.NodeObjectType == entity.ContainerObjectType)); + + if (parentDto == null) + throw new NullReferenceException("Could not find parent container with id " + entity.ParentId); + + level = parentDto.Level; + path = parentDto.Path; + } + + // note: sortOrder is NOT managed and always zero for containers + + nodeDto = new NodeDto + { + CreateDate = DateTime.Now, + Level = Convert.ToInt16(level + 1), + NodeObjectType = entity.ContainerObjectType, + ParentId = entity.ParentId, + Path = path, + SortOrder = 0, + Text = entity.Name, + UserId = entity.CreatorId + }; + + if (entity.Key != default(Guid)) + nodeDto.UniqueId = entity.Key; + + // insert, get the id, update the path with the id + var id = Convert.ToInt32(Database.Insert(nodeDto)); + nodeDto.Path = nodeDto.Path + "," + nodeDto.NodeId; + Database.Save(nodeDto); + + // refresh the entity + entity.Id = id; + entity.Key = nodeDto.UniqueId; + entity.Path = nodeDto.Path; + entity.Level = nodeDto.Level; + entity.SortOrder = 0; + entity.CreateDate = nodeDto.CreateDate; + entity.ResetDirtyProperties(); + } + + // beware! does NOT manage descendants in case of a new parent + // + protected override void PersistUpdatedItem(EntityContainer entity) + { + entity.Name = entity.Name.Trim(); + Mandate.ParameterNotNullOrEmpty(entity.Name, "entity.Name"); + + // find container to update + var nodeDto = Database.FirstOrDefault(new Sql().Select("*") + .From(SqlSyntax) + .Where(dto => dto.NodeId == entity.Id && dto.NodeObjectType == entity.ContainerObjectType)); + if (nodeDto == null) + throw new InvalidOperationException("Could not find container with id " + entity.Id); + + // guard against duplicates + var dupNodeDto = Database.FirstOrDefault(new Sql().Select("*") + .From(SqlSyntax) + .Where(dto => dto.ParentId == entity.ParentId && dto.Text == entity.Name && dto.NodeObjectType == entity.ContainerObjectType)); + if (dupNodeDto != null && dupNodeDto.NodeId != nodeDto.NodeId) + throw new InvalidOperationException("A container with the same name already exists."); + + // update + nodeDto.Text = entity.Name; + if (nodeDto.ParentId != entity.ParentId) + { + nodeDto.Level = 0; + nodeDto.Path = "-1"; + if (entity.ParentId > -1) + { + var parent = Database.FirstOrDefault(new Sql().Select("*") + .From(SqlSyntax) + .Where(dto => dto.NodeId == entity.ParentId && dto.NodeObjectType == entity.ContainerObjectType)); + + if (parent == null) + throw new NullReferenceException("Could not find parent container with id " + entity.ParentId); + + nodeDto.Level = Convert.ToInt16(parent.Level + 1); + nodeDto.Path = parent.Path + "," + nodeDto.NodeId; + } + nodeDto.ParentId = entity.ParentId; + } + + // note: sortOrder is NOT managed and always zero for containers + + // update + Database.Update(nodeDto); + + // refresh the entity + entity.Path = nodeDto.Path; + entity.Level = nodeDto.Level; + entity.SortOrder = 0; + entity.ResetDirtyProperties(); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs index eba77afe86..190c34fa57 100644 --- a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs @@ -334,7 +334,7 @@ namespace Umbraco.Core.Persistence.Repositories .Append(new Sql(") tmpTbl LEFT JOIN (")) .Append(joinSql) .Append(new Sql(") as property ON id = property.contentNodeId")) - .OrderBy("sortOrder"); + .OrderBy("sortOrder, id"); return wrappedSql; } diff --git a/src/Umbraco.Core/Persistence/Repositories/ExternalLoginRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ExternalLoginRepository.cs index 315f61e2c0..249ecef23d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ExternalLoginRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ExternalLoginRepository.cs @@ -68,7 +68,7 @@ namespace Umbraco.Core.Persistence.Repositories //on initial construction we don't want to have dirty properties tracked // http://issues.umbraco.org/issue/U4-1946 - ((TracksChangesEntityBase)entity).ResetDirtyProperties(false); + entity.ResetDirtyProperties(false); return entity; } diff --git a/src/Umbraco.Core/Persistence/Repositories/FileRepository.cs b/src/Umbraco.Core/Persistence/Repositories/FileRepository.cs index 391a777e5e..e1e4c3105a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/FileRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/FileRepository.cs @@ -147,10 +147,6 @@ namespace Umbraco.Core.Persistence.Repositories protected virtual void PersistUpdatedItem(TEntity entity) { - //TODO: A big problem here is if the entities 'Path' changes, if that is the case then - // we'd need to rename the underlying file, BUT how would we do this since we aren't storing an - // original path property. - using (var stream = GetContentStream(entity.Content)) { FileSystem.AddFile(entity.Path, stream, true); @@ -219,6 +215,15 @@ namespace Umbraco.Core.Persistence.Repositories return list; } + protected string GetFileContent(string filename) + { + using (var stream = FileSystem.OpenFile(filename)) + using (var reader = new StreamReader(stream, Encoding.UTF8, true)) + { + return reader.ReadToEnd(); + } + } + /// /// Dispose any disposable properties /// diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs index c27dd96d46..9d3fcbb40b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs @@ -9,7 +9,7 @@ using Umbraco.Core.Persistence.Querying; namespace Umbraco.Core.Persistence.Repositories { - public interface IContentRepository : IRepositoryVersionable, IRecycleBinRepository + public interface IContentRepository : IRepositoryVersionable, IRecycleBinRepository, IDeleteMediaFilesRepository { /// /// Get the count of published items diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeRepository.cs index e3ce084cf7..625023fd9e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeRepository.cs @@ -1,10 +1,12 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using Umbraco.Core.Events; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Querying; namespace Umbraco.Core.Persistence.Repositories { - public interface IContentTypeRepository : IRepositoryQueryable + public interface IContentTypeRepository : IRepositoryQueryable, IReadRepository { /// /// Gets all entities of the specified query @@ -18,5 +20,7 @@ namespace Umbraco.Core.Persistence.Repositories /// /// IEnumerable GetAllPropertyTypeAliases(); + + IEnumerable> Move(IContentType toMove, EntityContainer container); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDataTypeDefinitionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDataTypeDefinitionRepository.cs index f04b90c2e0..6d7265f717 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDataTypeDefinitionRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDataTypeDefinitionRepository.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Umbraco.Core.Events; using Umbraco.Core.Models; using Umbraco.Core.Persistence.UnitOfWork; @@ -11,5 +12,6 @@ namespace Umbraco.Core.Persistence.Repositories void AddOrUpdatePreValues(IDataTypeDefinition dataType, IDictionary values); void AddOrUpdatePreValues(int dataTypeId, IDictionary values); + IEnumerable> Move(IDataTypeDefinition toMove, EntityContainer container); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDeleteMediaFilesRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDeleteMediaFilesRepository.cs new file mode 100644 index 0000000000..005c1d62ba --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDeleteMediaFilesRepository.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + +namespace Umbraco.Core.Persistence.Repositories +{ + public interface IDeleteMediaFilesRepository + { + /// + /// Called to remove all files associated with entities when an entity is permanently deleted + /// + /// + /// + bool DeleteMediaFiles(IEnumerable files); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDictionaryRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDictionaryRepository.cs index 3a0328294a..d030bcda2a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDictionaryRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDictionaryRepository.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Umbraco.Core.Models; namespace Umbraco.Core.Persistence.Repositories @@ -7,5 +8,6 @@ namespace Umbraco.Core.Persistence.Repositories { IDictionaryItem Get(Guid uniqueId); IDictionaryItem Get(string key); + IEnumerable GetDictionaryItemDescendants(Guid? parentId); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs index 1b6e1a83bd..b712622e63 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs @@ -7,7 +7,7 @@ using Umbraco.Core.Persistence.Querying; namespace Umbraco.Core.Persistence.Repositories { - public interface IMediaRepository : IRepositoryVersionable, IRecycleBinRepository + public interface IMediaRepository : IRepositoryVersionable, IRecycleBinRepository, IDeleteMediaFilesRepository { IMedia GetMediaByPath(string mediaPath); diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaTypeRepository.cs index 54220e0b59..6d9331a22a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaTypeRepository.cs @@ -1,10 +1,12 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using Umbraco.Core.Events; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Querying; namespace Umbraco.Core.Persistence.Repositories { - public interface IMediaTypeRepository : IRepositoryQueryable + public interface IMediaTypeRepository : IRepositoryQueryable, IReadRepository { /// /// Gets all entities of the specified query @@ -12,5 +14,7 @@ namespace Umbraco.Core.Persistence.Repositories /// /// An enumerable list of objects IEnumerable GetByQuery(IQuery query); + + IEnumerable> Move(IMediaType toMove, EntityContainer container); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs index 2436ffbfb6..9cb74d1806 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs @@ -9,7 +9,7 @@ using Umbraco.Core.Persistence.Querying; namespace Umbraco.Core.Persistence.Repositories { - public interface IMemberRepository : IRepositoryVersionable + public interface IMemberRepository : IRepositoryVersionable, IDeleteMediaFilesRepository { /// /// Finds members in a given role diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberTypeRepository.cs index 9e91b1c87c..ae7739b28b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberTypeRepository.cs @@ -1,8 +1,9 @@ -using Umbraco.Core.Models; +using System; +using Umbraco.Core.Models; namespace Umbraco.Core.Persistence.Repositories { - public interface IMemberTypeRepository : IRepositoryQueryable + public interface IMemberTypeRepository : IRepositoryQueryable, IReadRepository { } diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMigrationEntryRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMigrationEntryRepository.cs new file mode 100644 index 0000000000..0189923e21 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMigrationEntryRepository.cs @@ -0,0 +1,11 @@ +using System; +using Semver; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Persistence.Repositories +{ + public interface IMigrationEntryRepository : IRepositoryQueryable + { + IMigrationEntry FindEntry(string migrationName, SemVersion version); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IPartialViewRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IPartialViewRepository.cs index 8ba28eeaee..3fbcdd3283 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IPartialViewRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IPartialViewRepository.cs @@ -6,5 +6,6 @@ namespace Umbraco.Core.Persistence.Repositories { void AddFolder(string folderPath); void DeleteFolder(string folderPath); + bool ValidatePartialView(IPartialView partialView); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRecycleBinRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRecycleBinRepository.cs index e1a464af66..a6ed95711e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRecycleBinRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRecycleBinRepository.cs @@ -18,12 +18,6 @@ namespace Umbraco.Core.Persistence.Repositories /// /// bool EmptyRecycleBin(); - - /// - /// Called to remove all files associated with entities when recycle bin is emptied - /// - /// - /// - bool DeleteFiles(IEnumerable files); + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRepository.cs index 9cb4d938c7..ecee9af138 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRepository.cs @@ -15,23 +15,8 @@ namespace Umbraco.Core.Persistence.Repositories } - /// - /// Defines the implementation of a Repository - /// - public interface IRepository : IRepository - { - /// - /// Adds or Updates an Entity - /// - /// - void AddOrUpdate(TEntity entity); - - /// - /// Deletes an Entity - /// - /// - void Delete(TEntity entity); - + public interface IReadRepository : IRepository + { /// /// Gets an Entity by Id /// @@ -52,5 +37,25 @@ namespace Umbraco.Core.Persistence.Repositories /// /// bool Exists(TId id); - } + } + + //TODO: This should be decoupled! Shouldn't inherit from the IReadRepository and should be named IWriteRepository + + /// + /// Defines the implementation of a Repository + /// + public interface IRepository : IReadRepository + { + /// + /// Adds or Updates an Entity + /// + /// + void AddOrUpdate(TEntity entity); + + /// + /// Deletes an Entity + /// + /// + void Delete(TEntity entity); + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRepositoryQueryable.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRepositoryQueryable.cs index e6419fc370..4eab7d58ef 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRepositoryQueryable.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRepositoryQueryable.cs @@ -4,6 +4,8 @@ using Umbraco.Core.Persistence.Querying; namespace Umbraco.Core.Persistence.Repositories { + //TODO: This should be decoupled! Shouldn't inherit from the IRepository + /// /// Defines the implementation of a Repository, which allows queries against the /// diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IServerRegistrationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IServerRegistrationRepository.cs new file mode 100644 index 0000000000..5db9c6087e --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IServerRegistrationRepository.cs @@ -0,0 +1,10 @@ +using System; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Persistence.Repositories +{ + public interface IServerRegistrationRepository : IRepositoryQueryable + { + void DeactiveStaleServers(TimeSpan staleTimeout); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/ITagRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/ITagRepository.cs index 993f350ce1..afd03f4273 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/ITagRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/ITagRepository.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using Umbraco.Core.Models; @@ -5,6 +6,10 @@ namespace Umbraco.Core.Persistence.Repositories { public interface ITagRepository : IRepositoryQueryable { + + TaggedEntity GetTaggedEntityByKey(Guid key); + TaggedEntity GetTaggedEntityById(int id); + IEnumerable GetTaggedEntitiesByTagGroup(TaggableObjectTypes objectType, string tagGroup); IEnumerable GetTaggedEntitiesByTag(TaggableObjectTypes objectType, string tag, string tagGroup = null); @@ -25,6 +30,14 @@ namespace Umbraco.Core.Persistence.Repositories /// IEnumerable GetTagsForEntity(int contentId, string group = null); + /// + /// Returns all tags that exist on the content item - Content/Media/Member + /// + /// The content item id to get tags for + /// Optional group + /// + IEnumerable GetTagsForEntity(Guid contentId, string group = null); + /// /// Returns all tags that exist on the content item for the property specified - Content/Media/Member /// @@ -34,6 +47,15 @@ namespace Umbraco.Core.Persistence.Repositories /// IEnumerable GetTagsForProperty(int contentId, string propertyTypeAlias, string group = null); + /// + /// Returns all tags that exist on the content item for the property specified - Content/Media/Member + /// + /// The content item id to get tags for + /// The property alias to get tags for + /// Optional group + /// + IEnumerable GetTagsForProperty(Guid contentId, string propertyTypeAlias, string group = null); + /// /// Assigns the given tags to a content item's property /// diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/ITemplateRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/ITemplateRepository.cs index 4366aaa4a6..14957ac49f 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/ITemplateRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/ITemplateRepository.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Umbraco.Core.Models; namespace Umbraco.Core.Persistence.Repositories @@ -10,12 +11,17 @@ namespace Umbraco.Core.Persistence.Repositories IEnumerable GetAll(params string[] aliases); IEnumerable GetChildren(int masterTemplateId); + IEnumerable GetChildren(string alias); + + IEnumerable GetDescendants(int masterTemplateId); + IEnumerable GetDescendants(string alias); /// /// Returns a template as a template node which can be traversed (parent, children) /// /// /// + [Obsolete("Use GetDescendants instead")] TemplateNode GetTemplateNode(string alias); /// @@ -24,6 +30,7 @@ namespace Umbraco.Core.Persistence.Repositories /// /// /// + [Obsolete("Use GetDescendants instead")] TemplateNode FindTemplateInTree(TemplateNode anyNode, string alias); /// diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserRepository.cs index e40d996637..542cceac40 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserRepository.cs @@ -55,5 +55,13 @@ namespace Umbraco.Core.Persistence.Repositories /// /// void ReplaceUserPermissions(int userId, IEnumerable permissions, params int[] entityIds); + + /// + /// Assigns the same permission set for a single user to any number of entities + /// + /// + /// + /// + void AssignUserPermission(int userId, char permission, params int[] entityIds); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs b/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs index 8ef313a143..2f379b4587 100644 --- a/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs @@ -7,7 +7,6 @@ using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence.Factories; -using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; @@ -19,32 +18,33 @@ namespace Umbraco.Core.Persistence.Repositories /// internal class LanguageRepository : PetaPocoRepositoryBase, ILanguageRepository { - private readonly IMappingResolver _mappingResolver; - - public LanguageRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IMappingResolver mappingResolver) - : base(work, cache, logger, sqlSyntax, mappingResolver) + public LanguageRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) + : base(work, cache, logger, sqlSyntax) { - _mappingResolver = mappingResolver; + //Custom cache options for better performance + _cacheOptions = new RepositoryCacheOptions + { + GetAllCacheAllowZeroCount = true, + GetAllCacheValidateCount = false + }; + } + + private readonly RepositoryCacheOptions _cacheOptions; + + /// + /// Returns the repository cache options + /// + protected override RepositoryCacheOptions RepositoryCacheOptions + { + get { return _cacheOptions; } } #region Overrides of RepositoryBase protected override ILanguage PerformGet(int id) { - var sql = GetBaseQuery(false); - sql.Where(GetBaseWhereClause(), new { Id = id }); - - var languageDto = Database.FirstOrDefault(sql); - if (languageDto == null) - return null; - - var entity = ConvertFromDto(languageDto); - - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - ((Entity)entity).ResetDirtyProperties(false); - - return entity; + //use the underlying GetAll which will force cache all domains + return GetAll().FirstOrDefault(x => x.Id == id); } protected override IEnumerable PerformGetAll(params int[] ids) @@ -57,7 +57,7 @@ namespace Umbraco.Core.Persistence.Repositories //this needs to be sorted since that is the way legacy worked - default language is the first one!! //even though legacy didn't sort, it should be by id - sql.OrderBy(SqlSyntax, dto => dto.Id); + sql.OrderBy(dto => dto.Id, SqlSyntax); return Database.Fetch(sql).Select(ConvertFromDto); @@ -135,8 +135,8 @@ namespace Umbraco.Core.Persistence.Repositories entity.ResetDirtyProperties(); //Clear the cache entries that exist by key/iso - RepositoryCache.RuntimeCache.ClearCacheItem(GetCacheIdKey(entity.IsoCode)); - RepositoryCache.RuntimeCache.ClearCacheItem(GetCacheIdKey(entity.CultureName)); + RuntimeCache.ClearCacheItem(GetCacheIdKey(entity.IsoCode)); + RuntimeCache.ClearCacheItem(GetCacheIdKey(entity.CultureName)); } protected override void PersistDeletedItem(ILanguage entity) @@ -144,8 +144,8 @@ namespace Umbraco.Core.Persistence.Repositories base.PersistDeletedItem(entity); //Clear the cache entries that exist by key/iso - RepositoryCache.RuntimeCache.ClearCacheItem(GetCacheIdKey(entity.IsoCode)); - RepositoryCache.RuntimeCache.ClearCacheItem(GetCacheIdKey(entity.CultureName)); + RuntimeCache.ClearCacheItem(GetCacheIdKey(entity.IsoCode)); + RuntimeCache.ClearCacheItem(GetCacheIdKey(entity.CultureName)); } #endregion @@ -159,94 +159,16 @@ namespace Umbraco.Core.Persistence.Repositories public ILanguage GetByCultureName(string cultureName) { - var cultureNameRepo = new LanguageByCultureNameRepository(this, UnitOfWork, RepositoryCache, Logger, SqlSyntax, _mappingResolver); - return cultureNameRepo.Get(cultureName); + //use the underlying GetAll which will force cache all domains + return GetAll().FirstOrDefault(x => x.CultureName.InvariantEquals(cultureName)); } public ILanguage GetByIsoCode(string isoCode) { - var isoRepo = new LanguageByIsoCodeRepository(this, UnitOfWork, RepositoryCache, Logger, SqlSyntax, _mappingResolver); - return isoRepo.Get(isoCode); + //use the underlying GetAll which will force cache all domains + return GetAll().FirstOrDefault(x => x.IsoCode.InvariantEquals(isoCode)); } - /// - /// Inner repository for looking up languages by ISO code, this deals with caching by a string key - /// - private class LanguageByIsoCodeRepository : SimpleGetRepository - { - private readonly LanguageRepository _languageRepository; - - public LanguageByIsoCodeRepository(LanguageRepository languageRepository, IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IMappingResolver mappingResolver) - : base(work, cache, logger, sqlSyntax, mappingResolver) - { - _languageRepository = languageRepository; - } - - protected override Sql GetBaseQuery(bool isCount) - { - return _languageRepository.GetBaseQuery(isCount); - } - - protected override string GetBaseWhereClause() - { - return "umbracoLanguage.languageISOCode = @Id"; - } - - protected override ILanguage ConvertToEntity(LanguageDto dto) - { - var factory = new LanguageFactory(); - return factory.BuildEntity(dto); - } - - protected override object GetBaseWhereClauseArguments(string id) - { - return new {Id = id}; - } - - protected override string GetWhereInClauseForGetAll() - { - return "umbracoLanguage.languageISOCode in (@ids)"; - } - } - - /// - /// Inner repository for looking up languages by culture name, this deals with caching by a string key - /// - private class LanguageByCultureNameRepository : SimpleGetRepository - { - private readonly LanguageRepository _languageRepository; - - public LanguageByCultureNameRepository(LanguageRepository languageRepository, IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IMappingResolver mappingResolver) - : base(work, cache, logger, sqlSyntax, mappingResolver) - { - _languageRepository = languageRepository; - } - - protected override Sql GetBaseQuery(bool isCount) - { - return _languageRepository.GetBaseQuery(isCount); - } - - protected override string GetBaseWhereClause() - { - return "umbracoLanguage.languageCultureName = @Id"; - } - - protected override ILanguage ConvertToEntity(LanguageDto dto) - { - var factory = new LanguageFactory(); - return factory.BuildEntity(dto); - } - - protected override object GetBaseWhereClauseArguments(string id) - { - return new {Id = id}; - } - - protected override string GetWhereInClauseForGetAll() - { - return "umbracoLanguage.languageCultureName in (@ids)"; - } - } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs index 363d912d67..162ad88ca0 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs @@ -3,9 +3,9 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text; -using System.Text.RegularExpressions; using System.Xml.Linq; using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Dynamics; using Umbraco.Core.IO; using Umbraco.Core.Logging; @@ -15,7 +15,6 @@ using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Factories; -using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; @@ -33,19 +32,19 @@ namespace Umbraco.Core.Persistence.Repositories private readonly ContentXmlRepository _contentXmlRepository; private readonly ContentPreviewRepository _contentPreviewRepository; - public MediaRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IMediaTypeRepository mediaTypeRepository, ITagRepository tagRepository, IMappingResolver mappingResolver) - : base(work, cache, logger, sqlSyntax, mappingResolver) + public MediaRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IMediaTypeRepository mediaTypeRepository, ITagRepository tagRepository, IContentSection contentSection) + : base(work, cache, logger, sqlSyntax, contentSection) { if (mediaTypeRepository == null) throw new ArgumentNullException("mediaTypeRepository"); if (tagRepository == null) throw new ArgumentNullException("tagRepository"); _mediaTypeRepository = mediaTypeRepository; _tagRepository = tagRepository; - _contentXmlRepository = new ContentXmlRepository(work, CacheHelper.CreateDisabledCacheHelper(), logger, sqlSyntax, mappingResolver); - _contentPreviewRepository = new ContentPreviewRepository(work, CacheHelper.CreateDisabledCacheHelper(), logger, sqlSyntax, mappingResolver); - EnsureUniqueNaming = true; + _contentXmlRepository = new ContentXmlRepository(work, CacheHelper.CreateDisabledCacheHelper(), logger, sqlSyntax); + _contentPreviewRepository = new ContentPreviewRepository(work, CacheHelper.CreateDisabledCacheHelper(), logger, sqlSyntax); + EnsureUniqueNaming = contentSection.EnsureUniqueNaming; } - public bool EnsureUniqueNaming { get; set; } + public bool EnsureUniqueNaming { get; private set; } #region Overrides of RepositoryBase @@ -53,7 +52,7 @@ namespace Umbraco.Core.Persistence.Repositories { var sql = GetBaseQuery(false); sql.Where(GetBaseWhereClause(), new { Id = id }); - sql.OrderByDescending(SqlSyntax, x => x.VersionDate); + sql.OrderByDescending(x => x.VersionDate); var dto = Database.Fetch(sql).FirstOrDefault(); @@ -81,7 +80,7 @@ namespace Umbraco.Core.Persistence.Repositories var sqlClause = GetBaseQuery(false); var translator = new SqlTranslator(sqlClause, query); var sql = translator.Translate() - .OrderBy(SqlSyntax, x => x.SortOrder); + .OrderBy(x => x.SortOrder); return ProcessQuery(sql); } @@ -94,12 +93,12 @@ namespace Umbraco.Core.Persistence.Repositories { var sql = new Sql(); sql.Select(isCount ? "COUNT(*)" : "*") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .Where(SqlSyntax, x => x.NodeObjectType == NodeObjectTypeId); + .From() + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .Where(x => x.NodeObjectType == NodeObjectTypeId); return sql; } @@ -142,7 +141,7 @@ namespace Umbraco.Core.Persistence.Repositories { var sql = GetBaseQuery(false); sql.Where("cmsContentVersion.VersionId = @VersionId", new { VersionId = versionId }); - sql.OrderByDescending(SqlSyntax, x => x.VersionDate); + sql.OrderByDescending(x => x.VersionDate); var dto = Database.Fetch(sql).FirstOrDefault(); @@ -176,10 +175,10 @@ namespace Umbraco.Core.Persistence.Repositories var mediaObjectType = Guid.Parse(Constants.ObjectTypes.Media); var subQuery = new Sql() .Select("DISTINCT cmsContentXml.nodeId") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .Where(SqlSyntax, dto => dto.NodeObjectType == mediaObjectType); + .From() + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .Where(dto => dto.NodeObjectType == mediaObjectType); var deleteSql = SqlSyntax.GetDeleteSubquery("cmsContentXml", "nodeId", subQuery); Database.Execute(deleteSql); @@ -192,13 +191,13 @@ namespace Umbraco.Core.Persistence.Repositories var mediaObjectType = Guid.Parse(Constants.ObjectTypes.Media); var subQuery = new Sql() .Select("DISTINCT cmsContentXml.nodeId") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .Where(SqlSyntax, dto => dto.NodeObjectType == mediaObjectType) - .Where(SqlSyntax, dto => dto.ContentTypeId == id1); + .From() + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .Where(dto => dto.NodeObjectType == mediaObjectType) + .Where(dto => dto.ContentTypeId == id1); var deleteSql = SqlSyntax.GetDeleteSubquery("cmsContentXml", "nodeId", subQuery); Database.Execute(deleteSql); @@ -208,7 +207,8 @@ namespace Umbraco.Core.Persistence.Repositories //now insert the data, again if something fails here, the whole transaction is reversed if (contentTypeIds == null) { - RebuildXmlStructuresProcessQuery(serializer, Query, tr, groupSize); + var query = Query.Builder; + RebuildXmlStructuresProcessQuery(serializer, query, tr, groupSize); } else { @@ -216,7 +216,7 @@ namespace Umbraco.Core.Persistence.Repositories { //copy local var id = contentTypeId; - var query = Query.Where(x => x.ContentTypeId == id && x.Trashed == false); + var query = Query.Builder.Where(x => x.ContentTypeId == id && x.Trashed == false); RebuildXmlStructuresProcessQuery(serializer, query, tr, groupSize); } } @@ -236,10 +236,10 @@ namespace Umbraco.Core.Persistence.Repositories var xmlItems = (from descendant in descendants let xml = serializer(descendant) - select new ContentXmlDto { NodeId = descendant.Id, Xml = xml.ToString(SaveOptions.None) }).ToArray(); + select new ContentXmlDto { NodeId = descendant.Id, Xml = xml.ToDataString() }).ToArray(); //bulk insert it into the database - Database.BulkInsertRecords(SqlSyntax, xmlItems, tr); + Database.BulkInsertRecords(xmlItems, tr); processed += xmlItems.Length; @@ -247,42 +247,6 @@ namespace Umbraco.Core.Persistence.Repositories } while (processed < total); } - public IMedia GetMediaByPath(string mediaPath) - { - var umbracoFileValue = mediaPath; - const string pattern = ".*[_][0-9]+[x][0-9]+[.].*"; - var isResized = Regex.IsMatch(mediaPath, pattern); - - // If the image has been resized we strip the "_403x328" of the original "/media/1024/koala_403x328.jpg" url. - if (isResized) - { - var underscoreIndex = mediaPath.LastIndexOf('_'); - var dotIndex = mediaPath.LastIndexOf('.'); - umbracoFileValue = string.Concat(mediaPath.Substring(0, underscoreIndex), mediaPath.Substring(dotIndex)); - } - - Func createSql = url => new Sql().Select("*") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.PropertyTypeId, right => right.Id) - .Where(SqlSyntax, x => x.Alias == "umbracoFile") - .Where(SqlSyntax, x => x.VarChar == url); - - var sql = createSql(umbracoFileValue); - - var propertyDataDto = UnitOfWork.Database.Fetch(sql).FirstOrDefault(); - - // If the stripped-down url returns null, we try again with the original url. - // Previously, the function would fail on e.g. "my_x_image.jpg" - if (propertyDataDto == null) - { - sql = createSql(mediaPath); - propertyDataDto = UnitOfWork.Database.Fetch(sql).FirstOrDefault(); - } - - return propertyDataDto == null ? null : Get(propertyDataDto.NodeId); - } - public void AddOrUpdateContentXml(IMedia content, Func xml) { _contentXmlRepository.AddOrUpdate(new ContentXmlEntity(content, xml)); @@ -320,10 +284,11 @@ namespace Umbraco.Core.Persistence.Repositories //NOTE Should the logic below have some kind of fallback for empty parent ids ? //Logic for setting Path, Level and SortOrder var parent = Database.First("WHERE id = @ParentId", new { ParentId = entity.ParentId }); - int level = parent.Level + 1; - int sortOrder = - Database.ExecuteScalar("SELECT COUNT(*) FROM umbracoNode WHERE parentID = @ParentId AND nodeObjectType = @NodeObjectType", - new { ParentId = entity.ParentId, NodeObjectType = NodeObjectTypeId }); + var level = parent.Level + 1; + var maxSortOrder = Database.ExecuteScalar( + "SELECT coalesce(max(sortOrder),-1) FROM umbracoNode WHERE parentid = @ParentId AND nodeObjectType = @NodeObjectType", + new { /*ParentId =*/ entity.ParentId, NodeObjectType = NodeObjectTypeId }); + var sortOrder = maxSortOrder + 1; //Create the (base) node data - umbracoNode var nodeDto = dto.ContentDto.NodeDto; @@ -503,10 +468,11 @@ namespace Umbraco.Core.Persistence.Repositories { //NOTE: This doesn't allow properties to be part of the query var dtos = Database.Fetch(sql); + + var ids = dtos.Select(x => x.ContentDto.ContentTypeId).ToArray(); //content types - var contentTypes = _mediaTypeRepository.GetAll(dtos.Select(x => x.ContentDto.ContentTypeId).ToArray()) - .ToArray(); + var contentTypes = ids.Length == 0 ? Enumerable.Empty() : _mediaTypeRepository.GetAll(ids).ToArray(); var dtosWithContentTypes = dtos //This select into and null check are required because we don't have a foreign damn key on the contentType column @@ -587,8 +553,8 @@ namespace Umbraco.Core.Persistence.Repositories var sql = new Sql(); sql.Select("*") - .From(SqlSyntax) - .Where(SqlSyntax, x => x.NodeObjectType == NodeObjectTypeId && x.ParentId == parentId && x.Text.StartsWith(nodeName)); + .From() + .Where(x => x.NodeObjectType == NodeObjectTypeId && x.ParentId == parentId && x.Text.StartsWith(nodeName)); int uniqueNumber = 1; var currentName = nodeName; diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs index 731caff1c0..3616a145ce 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs @@ -1,16 +1,18 @@ using System; using System.Collections.Generic; using System.Linq; +using Umbraco.Core.Events; +using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence.Factories; -using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; +using Umbraco.Core.Services; namespace Umbraco.Core.Persistence.Repositories { @@ -20,40 +22,17 @@ namespace Umbraco.Core.Persistence.Repositories internal class MediaTypeRepository : ContentTypeBaseRepository, IMediaTypeRepository { - public MediaTypeRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IMappingResolver mappingResolver) - : base(work, cache, logger, sqlSyntax, mappingResolver) + public MediaTypeRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) + : base(work, cache, logger, sqlSyntax) { } - - #region Overrides of RepositoryBase - + protected override IMediaType PerformGet(int id) { - var contentTypeSql = GetBaseQuery(false); - contentTypeSql.Where(GetBaseWhereClause(), new { Id = id}); + var contentTypes = ContentTypeQueryMapper.GetMediaTypes( + new[] { id }, Database, SqlSyntax, this); - var dto = Database.Fetch(contentTypeSql).FirstOrDefault(); - - if (dto == null) - return null; - - var factory = new MediaTypeFactory(NodeObjectTypeId); - var contentType = factory.BuildEntity(dto); - - contentType.AllowedContentTypes = GetAllowedContentTypeIds(id); - contentType.PropertyGroups = GetPropertyGroupCollection(id, contentType.CreateDate, contentType.UpdateDate); - ((MediaType)contentType).PropertyTypes = GetPropertyTypeCollection(id, contentType.CreateDate, contentType.UpdateDate); - - var list = Database.Fetch("WHERE childContentTypeId = @Id", new{ Id = id}); - foreach (var contentTypeDto in list) - { - bool result = contentType.AddContentType(Get(contentTypeDto.ParentId)); - //Do something if adding fails? (Should hopefully not be possible unless someone create a circular reference) - } - - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - ((Entity)contentType).ResetDirtyProperties(false); + var contentType = contentTypes.SingleOrDefault(); return contentType; } @@ -65,7 +44,7 @@ namespace Umbraco.Core.Persistence.Repositories } else { - var sql = new Sql().Select("id").From(SqlSyntax).Where(SqlSyntax, dto => dto.NodeObjectType == NodeObjectTypeId); + var sql = new Sql().Select("id").From(SqlSyntax).Where(dto => dto.NodeObjectType == NodeObjectTypeId); var allIds = Database.Fetch(sql).ToArray(); return ContentTypeQueryMapper.GetMediaTypes(allIds, Database, SqlSyntax, this); } @@ -76,27 +55,27 @@ namespace Umbraco.Core.Persistence.Repositories var sqlClause = GetBaseQuery(false); var translator = new SqlTranslator(sqlClause, query); var sql = translator.Translate() - .OrderBy(SqlSyntax, x => x.Text); + .OrderBy(x => x.Text, SqlSyntax); var dtos = Database.Fetch(sql); return dtos.Any() ? GetAll(dtos.DistinctBy(x => x.NodeId).Select(x => x.NodeId).ToArray()) : Enumerable.Empty(); } - - #endregion - + + /// + /// Gets all entities of the specified query + /// + /// + /// An enumerable list of objects public IEnumerable GetByQuery(IQuery query) { - var ints = PerformGetByQuery(query); - foreach (var i in ints) - { - yield return Get(i); - } - } - - #region Overrides of PetaPocoRepositoryBase - + var ints = PerformGetByQuery(query).ToArray(); + return ints.Any() + ? GetAll(ints) + : Enumerable.Empty(); + } + protected override Sql GetBaseQuery(bool isCount) { var sql = new Sql(); @@ -104,7 +83,7 @@ namespace Umbraco.Core.Persistence.Repositories .From(SqlSyntax) .InnerJoin(SqlSyntax) .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .Where(SqlSyntax, x => x.NodeObjectType == NodeObjectTypeId); + .Where(x => x.NodeObjectType == NodeObjectTypeId); return sql; } @@ -136,19 +115,12 @@ namespace Umbraco.Core.Persistence.Repositories { get { return new Guid(Constants.ObjectTypes.MediaType); } } - - #endregion - - #region Unit of Work Implementation - + protected override void PersistNewItem(IMediaType entity) { ((MediaType)entity).AddingEntity(); - var factory = new MediaTypeFactory(NodeObjectTypeId); - var dto = factory.BuildDto(entity); - - PersistNewBaseContentType(dto, entity); + PersistNewBaseContentType(entity); entity.ResetDirtyProperties(); } @@ -173,14 +145,32 @@ namespace Umbraco.Core.Persistence.Repositories entity.SortOrder = maxSortOrder + 1; } - var factory = new MediaTypeFactory(NodeObjectTypeId); - var dto = factory.BuildDto(entity); - - PersistUpdatedBaseContentType(dto, entity); + PersistUpdatedBaseContentType(entity); entity.ResetDirtyProperties(); } + + protected override IMediaType PerformGet(Guid id) + { + var contentTypes = ContentTypeQueryMapper.GetMediaTypes( + new[] { id }, Database, SqlSyntax, this); - #endregion + var contentType = contentTypes.SingleOrDefault(); + return contentType; + } + + protected override IEnumerable PerformGetAll(params Guid[] ids) + { + if (ids.Any()) + { + return ContentTypeQueryMapper.GetMediaTypes(ids, Database, SqlSyntax, this); + } + else + { + var sql = new Sql().Select("id").From(SqlSyntax).Where(dto => dto.NodeObjectType == NodeObjectTypeId); + var allIds = Database.Fetch(sql).ToArray(); + return ContentTypeQueryMapper.GetMediaTypes(allIds, Database, SqlSyntax, this); + } + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs index 710047f98c..ac5529b523 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs @@ -6,6 +6,7 @@ using System.Linq.Expressions; using System.Text; using System.Xml.Linq; using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models.EntityBase; @@ -19,7 +20,6 @@ using Umbraco.Core.Persistence.Relators; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; using Umbraco.Core.Dynamics; -using Umbraco.Core.Persistence.Mappers; namespace Umbraco.Core.Persistence.Repositories { @@ -34,16 +34,16 @@ namespace Umbraco.Core.Persistence.Repositories private readonly ContentXmlRepository _contentXmlRepository; private readonly ContentPreviewRepository _contentPreviewRepository; - public MemberRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IMemberTypeRepository memberTypeRepository, IMemberGroupRepository memberGroupRepository, ITagRepository tagRepository, IMappingResolver mappingResolver) - : base(work, cache, logger, sqlSyntax, mappingResolver) + public MemberRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IMemberTypeRepository memberTypeRepository, IMemberGroupRepository memberGroupRepository, ITagRepository tagRepository, IContentSection contentSection) + : base(work, cache, logger, sqlSyntax, contentSection) { if (memberTypeRepository == null) throw new ArgumentNullException("memberTypeRepository"); if (tagRepository == null) throw new ArgumentNullException("tagRepository"); _memberTypeRepository = memberTypeRepository; _tagRepository = tagRepository; _memberGroupRepository = memberGroupRepository; - _contentXmlRepository = new ContentXmlRepository(work, CacheHelper.CreateDisabledCacheHelper(), logger, sqlSyntax, mappingResolver); - _contentPreviewRepository = new ContentPreviewRepository(work, CacheHelper.CreateDisabledCacheHelper(), logger, sqlSyntax, mappingResolver); + _contentXmlRepository = new ContentXmlRepository(work, CacheHelper.CreateDisabledCacheHelper(), logger, sqlSyntax); + _contentPreviewRepository = new ContentPreviewRepository(work, CacheHelper.CreateDisabledCacheHelper(), logger, sqlSyntax); } #region Overrides of RepositoryBase @@ -52,7 +52,7 @@ namespace Umbraco.Core.Persistence.Repositories { var sql = GetBaseQuery(false); sql.Where(GetBaseWhereClause(), new { Id = id }); - sql.OrderByDescending(SqlSyntax, x => x.VersionDate); + sql.OrderByDescending(x => x.VersionDate); var dto = Database.Fetch(sql).FirstOrDefault(); @@ -93,7 +93,7 @@ namespace Umbraco.Core.Persistence.Repositories var sql = translator.Translate(); baseQuery.Append(new Sql("WHERE umbracoNode.id IN (" + sql.SQL + ")", sql.Arguments)) - .OrderBy(SqlSyntax, x => x.SortOrder); + .OrderBy(x => x.SortOrder); return ProcessQuery(baseQuery); } @@ -101,7 +101,7 @@ namespace Umbraco.Core.Persistence.Repositories { var translator = new SqlTranslator(baseQuery, query); var sql = translator.Translate() - .OrderBy(SqlSyntax, x => x.SortOrder); + .OrderBy(x => x.SortOrder); return ProcessQuery(sql); } @@ -116,19 +116,18 @@ namespace Umbraco.Core.Persistence.Repositories { var sql = new Sql(); sql.Select(isCount ? "COUNT(*)" : "*") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) + .From() + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) //We're joining the type so we can do a query against the member type - not sure if this adds much overhead or not? // the execution plan says it doesn't so we'll go with that and in that case, it might be worth joining the content // types by default on the document and media repo's so we can query by content type there too. - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.ContentTypeId) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .Where(SqlSyntax, x => x.NodeObjectType == NodeObjectTypeId); + .InnerJoin().On(left => left.NodeId, right => right.ContentTypeId) + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .Where(x => x.NodeObjectType == NodeObjectTypeId); return sql; } @@ -142,16 +141,16 @@ namespace Umbraco.Core.Persistence.Repositories { var sql = new Sql(); sql.Select("DISTINCT(umbracoNode.id)") - .From(SqlSyntax) - .InnerJoin(SqlSyntax).On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .InnerJoin(SqlSyntax).On(SqlSyntax, left => left.NodeId, right => right.ContentTypeId) - .InnerJoin(SqlSyntax).On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .InnerJoin(SqlSyntax).On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .LeftJoin(SqlSyntax).On(SqlSyntax, left => left.ContentTypeId, right => right.ContentTypeId) - .LeftJoin(SqlSyntax).On(SqlSyntax, left => left.DataTypeId, right => right.DataTypeId) - .LeftJoin(SqlSyntax).On(SqlSyntax, left => left.PropertyTypeId, right => right.Id) + .From() + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .InnerJoin().On(left => left.NodeId, right => right.ContentTypeId) + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .LeftJoin().On(left => left.ContentTypeId, right => right.ContentTypeId) + .LeftJoin().On(left => left.DataTypeId, right => right.DataTypeId) + .LeftJoin().On(left => left.PropertyTypeId, right => right.Id) .Append("AND cmsPropertyData.versionId = cmsContentVersion.VersionId") - .Where(SqlSyntax, x => x.NodeObjectType == NodeObjectTypeId); + .Where(x => x.NodeObjectType == NodeObjectTypeId); return sql; } @@ -375,36 +374,6 @@ namespace Umbraco.Core.Persistence.Repositories dirtyEntity.ResetDirtyProperties(); } - protected override void PersistDeletedItem(IMember entity) - { - var fs = FileSystemProviderManager.Current.GetFileSystemProvider(); - var uploadFieldAlias = Constants.PropertyEditors.UploadFieldAlias; - //Loop through properties to check if the media item contains images/file that should be deleted - foreach (var property in ((Member)entity).Properties) - { - if (property.PropertyType.PropertyEditorAlias == uploadFieldAlias && - string.IsNullOrEmpty(property.Value.ToString()) == false - && fs.FileExists(fs.GetRelativePath(property.Value.ToString()))) - { - var relativeFilePath = fs.GetRelativePath(property.Value.ToString()); - var parentDirectory = System.IO.Path.GetDirectoryName(relativeFilePath); - - // don't want to delete the media folder if not using directories. - if (UmbracoConfig.For.UmbracoSettings().Content.UploadAllowDirectories && parentDirectory != fs.GetRelativePath("/")) - { - //issue U4-771: if there is a parent directory the recursive parameter should be true - fs.DeleteDirectory(parentDirectory, String.IsNullOrEmpty(parentDirectory) == false); - } - else - { - fs.DeleteFile(relativeFilePath, true); - } - } - } - - base.PersistDeletedItem(entity); - } - #endregion #region Overrides of VersionableRepositoryBase @@ -421,10 +390,10 @@ namespace Umbraco.Core.Persistence.Repositories var memberObjectType = Guid.Parse(Constants.ObjectTypes.Member); var subQuery = new Sql() .Select("DISTINCT cmsContentXml.nodeId") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .Where(SqlSyntax, dto => dto.NodeObjectType == memberObjectType); + .From() + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .Where(dto => dto.NodeObjectType == memberObjectType); var deleteSql = SqlSyntax.GetDeleteSubquery("cmsContentXml", "nodeId", subQuery); Database.Execute(deleteSql); @@ -437,13 +406,13 @@ namespace Umbraco.Core.Persistence.Repositories var memberObjectType = Guid.Parse(Constants.ObjectTypes.Member); var subQuery = new Sql() .Select("DISTINCT cmsContentXml.nodeId") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .Where(SqlSyntax, dto => dto.NodeObjectType == memberObjectType) - .Where(SqlSyntax, dto => dto.ContentTypeId == id1); + .From() + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .Where(dto => dto.NodeObjectType == memberObjectType) + .Where(dto => dto.ContentTypeId == id1); var deleteSql = SqlSyntax.GetDeleteSubquery("cmsContentXml", "nodeId", subQuery); Database.Execute(deleteSql); @@ -453,7 +422,7 @@ namespace Umbraco.Core.Persistence.Repositories //now insert the data, again if something fails here, the whole transaction is reversed if (contentTypeIds == null) { - var query = Query; + var query = Query.Builder; RebuildXmlStructuresProcessQuery(serializer, query, tr, groupSize); } else @@ -462,7 +431,7 @@ namespace Umbraco.Core.Persistence.Repositories { //copy local var id = contentTypeId; - var query = Query.Where(x => x.ContentTypeId == id && x.Trashed == false); + var query = Query.Builder.Where(x => x.ContentTypeId == id && x.Trashed == false); RebuildXmlStructuresProcessQuery(serializer, query, tr, groupSize); } } @@ -482,10 +451,10 @@ namespace Umbraco.Core.Persistence.Repositories var xmlItems = (from descendant in descendants let xml = serializer(descendant) - select new ContentXmlDto { NodeId = descendant.Id, Xml = xml.ToString(SaveOptions.None) }).ToArray(); + select new ContentXmlDto { NodeId = descendant.Id, Xml = xml.ToDataString() }).ToArray(); //bulk insert it into the database - Database.BulkInsertRecords(SqlSyntax, xmlItems, tr); + Database.BulkInsertRecords(xmlItems, tr); processed += xmlItems.Length; @@ -497,7 +466,7 @@ namespace Umbraco.Core.Persistence.Repositories { var sql = GetBaseQuery(false); sql.Where("cmsContentVersion.VersionId = @VersionId", new { VersionId = versionId }); - sql.OrderByDescending(SqlSyntax, x => x.VersionDate); + sql.OrderByDescending(x => x.VersionDate); var dto = Database.Fetch(sql).FirstOrDefault(); @@ -532,12 +501,12 @@ namespace Umbraco.Core.Persistence.Repositories public IEnumerable FindMembersInRole(string roleName, string usernameToMatch, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) { //get the group id - var grpQry = QueryFactory.Create().Where(group => group.Name.Equals(roleName)); + var grpQry = new Query().Where(group => group.Name.Equals(roleName)); var memberGroup = _memberGroupRepository.GetByQuery(grpQry).FirstOrDefault(); if (memberGroup == null) return Enumerable.Empty(); // get the members by username - var query = QueryFactory.Create(); + var query = new Query(); switch (matchType) { case StringPropertyMatchType.Exact: @@ -567,8 +536,8 @@ namespace Umbraco.Core.Persistence.Repositories foreach (var batch in inGroups) { var memberIdBatch = batch.Select(x => x.Id); - var sql = new Sql().Select("*").From(SqlSyntax) - .Where(SqlSyntax, dto => dto.MemberGroup == memberGroup.Id) + var sql = new Sql().Select("*").From() + .Where(dto => dto.MemberGroup == memberGroup.Id) .Where("Member IN (@memberIds)", new { memberIds = memberIdBatch }); var memberIdsInGroup = Database.Fetch(sql) .Select(x => x.Member).ToArray(); @@ -587,19 +556,18 @@ namespace Umbraco.Core.Persistence.Repositories /// public IEnumerable GetByMemberGroup(string groupName) { - var grpQry = QueryFactory.Create().Where(group => group.Name.Equals(groupName)); + var grpQry = new Query().Where(group => group.Name.Equals(groupName)); var memberGroup = _memberGroupRepository.GetByQuery(grpQry).FirstOrDefault(); if (memberGroup == null) return Enumerable.Empty(); - var subQuery = new Sql().Select("Member").From(SqlSyntax) - .Where(SqlSyntax, dto => dto.MemberGroup == memberGroup.Id); + var subQuery = new Sql().Select("Member").From().Where(dto => dto.MemberGroup == memberGroup.Id); var sql = GetBaseQuery(false) //TODO: An inner join would be better, though I've read that the query optimizer will always turn a // subquery with an IN clause into an inner join anyways. .Append(new Sql("WHERE umbracoNode.id IN (" + subQuery.SQL + ")", subQuery.Arguments)) - .OrderByDescending(SqlSyntax, x => x.VersionDate) - .OrderBy(SqlSyntax, x => x.SortOrder); + .OrderByDescending(x => x.VersionDate) + .OrderBy(x => x.SortOrder); return ProcessQuery(sql); @@ -610,8 +578,8 @@ namespace Umbraco.Core.Persistence.Repositories var sql = new Sql(); sql.Select("COUNT(*)") - .From(SqlSyntax) - .Where(SqlSyntax, x => x.LoginName == username); + .From() + .Where(x => x.LoginName == username); return Database.ExecuteScalar(sql) > 0; } @@ -706,8 +674,10 @@ namespace Umbraco.Core.Persistence.Repositories //NOTE: This doesn't allow properties to be part of the query var dtos = Database.Fetch(sql); + var ids = dtos.Select(x => x.ContentVersionDto.ContentDto.ContentTypeId).ToArray(); + //content types - var contentTypes = _memberTypeRepository.GetAll(dtos.Select(x => x.ContentVersionDto.ContentDto.ContentTypeId).ToArray()).ToArray(); + var contentTypes = ids.Length == 0 ? Enumerable.Empty() : _memberTypeRepository.GetAll(ids).ToArray(); var dtosWithContentTypes = dtos //This select into and null check are required because we don't have a foreign damn key on the contentType column @@ -794,5 +764,6 @@ namespace Umbraco.Core.Persistence.Repositories _contentXmlRepository.Dispose(); _contentPreviewRepository.Dispose(); } + } } diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs index 13babe3b05..91b6e67cf9 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs @@ -8,7 +8,6 @@ using Umbraco.Core.Models; using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence.Factories; -using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.Relators; using Umbraco.Core.Persistence.SqlSyntax; @@ -22,8 +21,8 @@ namespace Umbraco.Core.Persistence.Repositories internal class MemberTypeRepository : ContentTypeBaseRepository, IMemberTypeRepository { - public MemberTypeRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IMappingResolver mappingResolver) - : base(work, cache, logger, sqlSyntax, mappingResolver) + public MemberTypeRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) + : base(work, cache, logger, sqlSyntax) { } @@ -33,7 +32,7 @@ namespace Umbraco.Core.Persistence.Repositories { var sql = GetBaseQuery(false); sql.Where(GetBaseWhereClause(), new { Id = id }); - sql.OrderByDescending(SqlSyntax, x => x.NodeId); + sql.OrderByDescending(x => x.NodeId, SqlSyntax); var dtos = Database.Fetch( @@ -56,7 +55,7 @@ namespace Umbraco.Core.Persistence.Repositories var statement = string.Join(" OR ", ids.Select(x => string.Format("umbracoNode.id='{0}'", x))); sql.Where(statement); } - sql.OrderByDescending(SqlSyntax, x => x.NodeId); + sql.OrderByDescending(x => x.NodeId); var dtos = Database.Fetch( @@ -72,7 +71,7 @@ namespace Umbraco.Core.Persistence.Repositories var subquery = translator.Translate(); var sql = GetBaseQuery(false) .Append(new Sql("WHERE umbracoNode.id IN (" + subquery.SQL + ")", subquery.Arguments)) - .OrderBy(SqlSyntax, x => x.SortOrder); + .OrderBy(x => x.SortOrder); var dtos = Database.Fetch( @@ -92,10 +91,9 @@ namespace Umbraco.Core.Persistence.Repositories if (isCount) { sql.Select("COUNT(*)") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .Where(SqlSyntax, x => x.NodeObjectType == NodeObjectTypeId); + .From() + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .Where(x => x.NodeObjectType == NodeObjectTypeId); return sql; } @@ -104,15 +102,15 @@ namespace Umbraco.Core.Persistence.Repositories "cmsPropertyType.validationRegExp", "cmsPropertyType.dataTypeId", "cmsPropertyType.sortOrder AS PropertyTypeSortOrder", "cmsPropertyType.propertyTypeGroupId AS PropertyTypesGroupId", "cmsMemberType.memberCanEdit", "cmsMemberType.viewOnProfile", "cmsDataType.propertyEditorAlias", "cmsDataType.dbType", "cmsPropertyTypeGroup.id AS PropertyTypeGroupId", - "cmsPropertyTypeGroup.text AS PropertyGroupName", "cmsPropertyTypeGroup.parentGroupId", + "cmsPropertyTypeGroup.text AS PropertyGroupName", "cmsPropertyTypeGroup.sortorder AS PropertyGroupSortOrder", "cmsPropertyTypeGroup.contenttypeNodeId") - .From(SqlSyntax) - .InnerJoin(SqlSyntax).On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .LeftJoin(SqlSyntax).On(SqlSyntax, left => left.ContentTypeId, right => right.NodeId) - .LeftJoin(SqlSyntax).On(SqlSyntax, left => left.PropertyTypeId, right => right.Id) - .LeftJoin(SqlSyntax).On(SqlSyntax, left => left.DataTypeId, right => right.DataTypeId) - .LeftJoin(SqlSyntax).On(SqlSyntax, left => left.ContentTypeNodeId, right => right.NodeId) - .Where(SqlSyntax, x => x.NodeObjectType == NodeObjectTypeId); + .From() + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .LeftJoin().On(left => left.ContentTypeId, right => right.NodeId) + .LeftJoin().On(left => left.PropertyTypeId, right => right.Id) + .LeftJoin().On(left => left.DataTypeId, right => right.DataTypeId) + .LeftJoin().On(left => left.ContentTypeNodeId, right => right.NodeId) + .Where(x => x.NodeObjectType == NodeObjectTypeId); return sql; } @@ -120,13 +118,13 @@ namespace Umbraco.Core.Persistence.Repositories { var sql = new Sql() .Select("DISTINCT(umbracoNode.id)") - .From(SqlSyntax) - .InnerJoin(SqlSyntax).On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .LeftJoin(SqlSyntax).On(SqlSyntax, left => left.ContentTypeId, right => right.NodeId) - .LeftJoin(SqlSyntax).On(SqlSyntax, left => left.PropertyTypeId, right => right.Id) - .LeftJoin(SqlSyntax).On(SqlSyntax, left => left.DataTypeId, right => right.DataTypeId) - .LeftJoin(SqlSyntax).On(SqlSyntax, left => left.ContentTypeNodeId, right => right.NodeId) - .Where(SqlSyntax, x => x.NodeObjectType == NodeObjectTypeId); + .From() + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .LeftJoin().On(left => left.ContentTypeId, right => right.NodeId) + .LeftJoin().On(left => left.PropertyTypeId, right => right.Id) + .LeftJoin().On(left => left.DataTypeId, right => right.DataTypeId) + .LeftJoin().On(left => left.ContentTypeNodeId, right => right.NodeId) + .Where(x => x.NodeObjectType == NodeObjectTypeId); return sql; } @@ -184,12 +182,10 @@ namespace Umbraco.Core.Persistence.Repositories entity.AddPropertyType(standardPropertyType.Value, Constants.Conventions.Member.StandardPropertiesGroupName); } - var factory = new MemberTypeFactory(NodeObjectTypeId); - var dto = factory.BuildDto(entity); + var factory = new ContentTypeFactory(); EnsureExplicitDataTypeForBuiltInProperties(entity); - - PersistNewBaseContentType(dto, entity); + PersistNewBaseContentType(entity); //Handles the MemberTypeDto (cmsMemberType table) var memberTypeDtos = factory.BuildMemberTypeDtos(entity); @@ -221,17 +217,13 @@ namespace Umbraco.Core.Persistence.Repositories entity.SortOrder = maxSortOrder + 1; } - var factory = new MemberTypeFactory(NodeObjectTypeId); - var dto = factory.BuildDto(entity); + var factory = new ContentTypeFactory(); EnsureExplicitDataTypeForBuiltInProperties(entity); + PersistUpdatedBaseContentType(entity); - PersistUpdatedBaseContentType(dto, entity); - - //Remove existing entries before inserting new ones + // remove and insert - handle cmsMemberType table Database.Delete("WHERE NodeId = @Id", new { Id = entity.Id }); - - //Handles the MemberTypeDto (cmsMemberType table) var memberTypeDtos = factory.BuildMemberTypeDtos(entity); foreach (var memberTypeDto in memberTypeDtos) { @@ -242,7 +234,7 @@ namespace Umbraco.Core.Persistence.Repositories } #endregion - + /// /// Override so we can specify explicit db type's on any property types that are built-in. /// @@ -262,6 +254,42 @@ namespace Umbraco.Core.Persistence.Repositories propertyTypeAlias); } + protected override IMemberType PerformGet(Guid id) + { + var sql = GetBaseQuery(false); + sql.Where("umbracoNode.uniqueID = @Id", new { Id = id }); + sql.OrderByDescending(x => x.NodeId); + + var dtos = + Database.Fetch( + new PropertyTypePropertyGroupRelator().Map, sql); + + if (dtos == null || dtos.Any() == false) + return null; + + var factory = new MemberTypeReadOnlyFactory(); + var member = factory.BuildEntity(dtos.First()); + + return member; + } + + protected override IEnumerable PerformGetAll(params Guid[] ids) + { + var sql = GetBaseQuery(false); + if (ids.Any()) + { + var statement = string.Join(" OR ", ids.Select(x => string.Format("umbracoNode.uniqueID='{0}'", x))); + sql.Where(statement); + } + sql.OrderByDescending(x => x.NodeId, SqlSyntax); + + var dtos = + Database.Fetch( + new PropertyTypePropertyGroupRelator().Map, sql); + + return BuildFromDtos(dtos); + } + /// /// Ensure that all the built-in membership provider properties have their correct data type /// and property editors assigned. This occurs prior to saving so that the correct values are persisted. diff --git a/src/Umbraco.Core/Persistence/Repositories/MigrationEntryRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MigrationEntryRepository.cs new file mode 100644 index 0000000000..19df777666 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/MigrationEntryRepository.cs @@ -0,0 +1,132 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Semver; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.Factories; +using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Persistence.SqlSyntax; +using Umbraco.Core.Persistence.UnitOfWork; + +namespace Umbraco.Core.Persistence.Repositories +{ + internal class MigrationEntryRepository : PetaPocoRepositoryBase, IMigrationEntryRepository + { + public MigrationEntryRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) + : base(work, cache, logger, sqlSyntax) + { + } + + protected override IMigrationEntry PerformGet(int id) + { + var sql = GetBaseQuery(false); + sql.Where(GetBaseWhereClause(), new { Id = id }); + + var dto = Database.First(sql); + if (dto == null) + return null; + + var factory = new MigrationEntryFactory(); + var entity = factory.BuildEntity(dto); + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + entity.ResetDirtyProperties(false); + + return entity; + } + + protected override IEnumerable PerformGetAll(params int[] ids) + { + var factory = new MigrationEntryFactory(); + + if (ids.Any()) + { + return Database.Fetch("WHERE id in (@ids)", new { ids = ids }) + .Select(x => factory.BuildEntity(x)); + } + + return Database.Fetch("WHERE id > 0") + .Select(x => factory.BuildEntity(x)); + } + + protected override IEnumerable PerformGetByQuery(IQuery query) + { + var factory = new MigrationEntryFactory(); + var sqlClause = GetBaseQuery(false); + var translator = new SqlTranslator(sqlClause, query); + var sql = translator.Translate(); + + return Database.Fetch(sql).Select(x => factory.BuildEntity(x)); + } + + protected override Sql GetBaseQuery(bool isCount) + { + var sql = new Sql(); + sql.Select(isCount ? "COUNT(*)" : "*") + .From(SqlSyntax); + return sql; + } + + protected override string GetBaseWhereClause() + { + return "id = @Id"; + } + + protected override IEnumerable GetDeleteClauses() + { + var list = new List + { + "DELETE FROM umbracoMigration WHERE id = @Id" + }; + return list; + } + + protected override Guid NodeObjectTypeId + { + get { throw new NotImplementedException(); } + } + + protected override void PersistNewItem(IMigrationEntry entity) + { + ((MigrationEntry)entity).AddingEntity(); + + var factory = new MigrationEntryFactory(); + var dto = factory.BuildDto(entity); + + var id = Convert.ToInt32(Database.Insert(dto)); + entity.Id = id; + + entity.ResetDirtyProperties(); + } + + protected override void PersistUpdatedItem(IMigrationEntry entity) + { + ((MigrationEntry)entity).UpdatingEntity(); + + var factory = new MigrationEntryFactory(); + var dto = factory.BuildDto(entity); + + Database.Update(dto); + + entity.ResetDirtyProperties(); + } + + public IMigrationEntry FindEntry(string migrationName, SemVersion version) + { + var versionString = version.ToString(); + + var sql = new Sql().Select("*") + .From(SqlSyntax) + .Where(x => x.Name.InvariantEquals(migrationName) && x.Version == versionString); + + var result = Database.FirstOrDefault(sql); + + var factory = new MigrationEntryFactory(); + + return result == null ? null : factory.BuildEntity(result); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/PartialViewMacroRepository.cs b/src/Umbraco.Core/Persistence/Repositories/PartialViewMacroRepository.cs index f4d6906cd1..e055c3dd93 100644 --- a/src/Umbraco.Core/Persistence/Repositories/PartialViewMacroRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/PartialViewMacroRepository.cs @@ -7,7 +7,6 @@ namespace Umbraco.Core.Persistence.Repositories { internal class PartialViewMacroRepository : PartialViewRepository { - public PartialViewMacroRepository(IUnitOfWork work) : this(work, new PhysicalFileSystem(SystemDirectories.MvcViews + "/MacroPartials/")) { @@ -18,5 +17,6 @@ namespace Umbraco.Core.Persistence.Repositories { } + protected override PartialViewType ViewType { get { return PartialViewType.PartialViewMacro; } } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/PartialViewRepository.cs b/src/Umbraco.Core/Persistence/Repositories/PartialViewRepository.cs index 2fd0240701..e9352a945f 100644 --- a/src/Umbraco.Core/Persistence/Repositories/PartialViewRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/PartialViewRepository.cs @@ -20,43 +20,52 @@ namespace Umbraco.Core.Persistence.Repositories { } + protected virtual PartialViewType ViewType { get { return PartialViewType.PartialView; } } + public override IPartialView Get(string id) { - if (FileSystem.FileExists(id) == false) - { - return null; - } - - string content; - using (var stream = FileSystem.OpenFile(id)) - { - var bytes = new byte[stream.Length]; - stream.Position = 0; - stream.Read(bytes, 0, (int)stream.Length); - content = Encoding.UTF8.GetString(bytes); - } - + // get the relative path within the filesystem + // (though... id should be relative already) var path = FileSystem.GetRelativePath(id); + + if (FileSystem.FileExists(path) == false) + return null; + + // content will be lazy-loaded when required var created = FileSystem.GetCreated(path).UtcDateTime; var updated = FileSystem.GetLastModified(path).UtcDateTime; + //var content = GetFileContent(path); - - var script = new PartialView(path) + var view = new PartialView(path, file => GetFileContent(file.OriginalPath)) { //id can be the hash Id = path.GetHashCode(), - Content = content, Key = path.EncodeAsGuid(), + //Content = content, CreateDate = created, UpdateDate = updated, - VirtualPath = FileSystem.GetUrl(id) + VirtualPath = FileSystem.GetUrl(id), + ViewType = ViewType }; //on initial construction we don't want to have dirty properties tracked // http://issues.umbraco.org/issue/U4-1946 - script.ResetDirtyProperties(false); + view.ResetDirtyProperties(false); - return script; + return view; + } + + public override void AddOrUpdate(IPartialView entity) + { + var partialView = entity as PartialView; + if (partialView != null) + partialView.ViewType = ViewType; + + base.AddOrUpdate(entity); + + // ensure that from now on, content is lazy-loaded + if (partialView != null && partialView.GetFileContent == null) + partialView.GetFileContent = file => GetFileContent(file.OriginalPath); } public override IEnumerable GetAll(params string[] ids) @@ -81,6 +90,29 @@ namespace Umbraco.Core.Persistence.Repositories } } + private static readonly List ValidExtensions = new List { "cshtml" }; + + public virtual bool ValidatePartialView(IPartialView partialView) + { + // get full path + string fullPath; + try + { + // may throw for security reasons + fullPath = FileSystem.GetFullPath(partialView.Path); + } + catch + { + return false; + } + + // validate path & extension + var validDir = SystemDirectories.MvcViews; + var isValidPath = IOHelper.VerifyEditPath(fullPath, validDir); + var isValidExtension = IOHelper.VerifyFileExtension(fullPath, ValidExtensions); + return isValidPath && isValidExtension; + } + /// /// Gets a stream that is used to write to the file /// diff --git a/src/Umbraco.Core/Persistence/Repositories/PermissionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/PermissionRepository.cs index a404a2bac0..752cab4143 100644 --- a/src/Umbraco.Core/Persistence/Repositories/PermissionRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/PermissionRepository.cs @@ -127,8 +127,12 @@ namespace Umbraco.Core.Persistence.Repositories var db = _unitOfWork.Database; using (var trans = db.GetTransaction()) { - db.Execute("DELETE FROM umbracoUser2NodePermission WHERE userId=@userId AND nodeId in (@nodeIds)", - new {userId = userId, nodeIds = entityIds}); + //we need to batch these in groups of 2000 so we don't exceed the max 2100 limit + foreach (var idGroup in entityIds.InGroupsOf(2000)) + { + db.Execute("DELETE FROM umbracoUser2NodePermission WHERE userId=@userId AND nodeId in (@nodeIds)", + new { userId = userId, nodeIds = idGroup }); + } var toInsert = new List(); foreach (var p in permissions) @@ -152,6 +156,42 @@ namespace Umbraco.Core.Persistence.Repositories AssignedPermissions.RaiseEvent( new SaveEventArgs(ConvertToPermissionList(toInsert), false), this); } + } + + /// + /// Assigns one permission for a user to many entities + /// + /// + /// + /// + public void AssignUserPermission(int userId, char permission, params int[] entityIds) + { + var db = _unitOfWork.Database; + using (var trans = db.GetTransaction()) + { + db.Execute("DELETE FROM umbracoUser2NodePermission WHERE userId=@userId AND permission=@permission AND nodeId in (@entityIds)", + new + { + userId = userId, + permission = permission.ToString(CultureInfo.InvariantCulture), + entityIds = entityIds + }); + + var actions = entityIds.Select(id => new User2NodePermissionDto + { + NodeId = id, + Permission = permission.ToString(CultureInfo.InvariantCulture), + UserId = userId + }).ToArray(); + + _unitOfWork.Database.BulkInsertRecords(actions, trans); + + trans.Complete(); + + //Raise the event + AssignedPermissions.RaiseEvent( + new SaveEventArgs(ConvertToPermissionList(actions), false), this); + } } /// diff --git a/src/Umbraco.Core/Persistence/Repositories/RecycleBinRepository.cs b/src/Umbraco.Core/Persistence/Repositories/RecycleBinRepository.cs index 470d555da6..be1f03a73f 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RecycleBinRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RecycleBinRepository.cs @@ -3,11 +3,12 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence.Mappers; + using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; @@ -17,8 +18,8 @@ namespace Umbraco.Core.Persistence.Repositories internal abstract class RecycleBinRepository : VersionableRepositoryBase, IRecycleBinRepository where TEntity : class, IUmbracoEntity { - protected RecycleBinRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IMappingResolver mappingResolver) - : base(work, cache, logger, sqlSyntax, mappingResolver) + protected RecycleBinRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IContentSection contentSection) + : base(work, cache, logger, sqlSyntax, contentSection) { } @@ -26,7 +27,7 @@ namespace Umbraco.Core.Persistence.Repositories public virtual IEnumerable GetEntitiesInRecycleBin() { - return GetByQuery(Query.Where(entity => entity.Trashed)); + return GetByQuery(new Query().Where(entity => entity.Trashed)); } /// @@ -43,6 +44,11 @@ namespace Umbraco.Core.Persistence.Repositories { FormatDeleteStatement("umbracoUser2NodeNotify", "nodeId"), FormatDeleteStatement("umbracoUser2NodePermission", "nodeId"), + @"DELETE FROM umbracoAccessRule WHERE umbracoAccessRule.accessId IN ( + SELECT TB1.id FROM umbracoAccess as TB1 + INNER JOIN umbracoNode as TB2 ON TB1.nodeId = TB2.id + WHERE TB2.trashed = '1' AND TB2.nodeObjectType = @NodeObjectType)", + FormatDeleteStatement("umbracoAccess", "nodeId"), FormatDeleteStatement("umbracoRelation", "parentId"), FormatDeleteStatement("umbracoRelation", "childId"), FormatDeleteStatement("cmsTagRelationship", "nodeId"), @@ -74,58 +80,13 @@ namespace Umbraco.Core.Persistence.Repositories } catch (Exception ex) { - trans.Dispose(); + // transaction will rollback Logger.Error>("An error occurred while emptying the Recycle Bin: " + ex.Message, ex); - return false; + throw; } } } - /// - /// Deletes all files passed in. - /// - /// - /// - public virtual bool DeleteFiles(IEnumerable files) - { - //ensure duplicates are removed - files = files.Distinct(); - - var allsuccess = true; - - var fs = FileSystemProviderManager.Current.GetFileSystemProvider(); - Parallel.ForEach(files, file => - { - try - { - if (file.IsNullOrWhiteSpace()) return; - - var relativeFilePath = fs.GetRelativePath(file); - if (fs.FileExists(relativeFilePath) == false) return; - - var parentDirectory = System.IO.Path.GetDirectoryName(relativeFilePath); - - // don't want to delete the media folder if not using directories. - if (UmbracoConfig.For.UmbracoSettings().Content.UploadAllowDirectories && parentDirectory != fs.GetRelativePath("/")) - { - //issue U4-771: if there is a parent directory the recursive parameter should be true - fs.DeleteDirectory(parentDirectory, String.IsNullOrEmpty(parentDirectory) == false); - } - else - { - fs.DeleteFile(file, true); - } - } - catch (Exception e) - { - Logger.Error>("An error occurred while deleting file attached to nodes: " + file, e); - allsuccess = false; - } - }); - - return allsuccess; - } - private string FormatDeleteStatement(string tableName, string keyName) { //This query works with sql ce and sql server: @@ -155,10 +116,10 @@ namespace Umbraco.Core.Persistence.Repositories //Alias: Constants.Conventions.Media.File and PropertyEditorAlias: Constants.PropertyEditors.UploadField var sql = new Sql(); sql.Select("DISTINCT(dataNvarchar)") - .From(SqlSyntax) - .InnerJoin(SqlSyntax).On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .InnerJoin(SqlSyntax).On(SqlSyntax, left => left.PropertyTypeId, right => right.Id) - .InnerJoin(SqlSyntax).On(SqlSyntax, left => left.DataTypeId, right => right.DataTypeId) + .From() + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .InnerJoin().On(left => left.PropertyTypeId, right => right.Id) + .InnerJoin().On(left => left.DataTypeId, right => right.DataTypeId) .Where("umbracoNode.trashed = '1' AND umbracoNode.nodeObjectType = @NodeObjectType AND dataNvarchar IS NOT NULL AND (cmsPropertyType.Alias = @FileAlias OR cmsDataType.propertyEditorAlias = @PropertyEditorAlias)", new { FileAlias = Constants.Conventions.Media.File, NodeObjectType = NodeObjectTypeId, PropertyEditorAlias = Constants.PropertyEditors.UploadFieldAlias }); diff --git a/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs index 037bab7a3b..f5aceef299 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs @@ -22,7 +22,11 @@ namespace Umbraco.Core.Persistence.Repositories if (logger == null) throw new ArgumentNullException("logger"); Logger = logger; _work = work; - _cache = cache; + + //IMPORTANT: We will force the DeepCloneRuntimeCacheProvider to be used here which is a wrapper for the underlying + // runtime cache to ensure that anything that can be deep cloned in/out is done so, this also ensures that our tracks + // changes entities are reset. + _cache = new CacheHelper(new DeepCloneRuntimeCacheProvider(cache.RuntimeCache), cache.StaticCache, cache.RequestCache); } /// @@ -127,20 +131,17 @@ namespace Umbraco.Core.Persistence.Repositories /// public TEntity Get(TId id) { - return RuntimeCache.GetCacheItem( - GetCacheIdKey(id), () => - { - var entity = PerformGet(id); - if (entity == null) return null; - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - var asEntity = entity as TracksChangesEntityBase; - if (asEntity != null) - { - asEntity.ResetDirtyProperties(false); - } - return entity; - }); + var cacheKey = GetCacheIdKey(id); + var fromCache = RuntimeCache.GetCacheItem(cacheKey); + + if (fromCache != null) return fromCache; + + var entity = PerformGet(id); + if (entity == null) return null; + + RuntimeCache.InsertCacheItem(cacheKey, () => entity); + + return entity; } protected abstract IEnumerable PerformGetAll(params TId[] ids); @@ -302,7 +303,7 @@ namespace Umbraco.Core.Persistence.Repositories //If there's a GetAll zero count cache, ensure it is cleared RuntimeCache.ClearCacheItem(GetCacheTypeKey()); } - catch (Exception) + catch (Exception ex) { //if an exception is thrown we need to remove the entry from cache, this is ONLY a work around because of the way // that we cache entities: http://issues.umbraco.org/issue/U4-4259 diff --git a/src/Umbraco.Core/Persistence/Repositories/ScriptRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ScriptRepository.cs index b48e096e3f..13b2877c50 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ScriptRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ScriptRepository.cs @@ -1,8 +1,6 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; -using System.Text; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Models; @@ -28,33 +26,27 @@ namespace Umbraco.Core.Persistence.Repositories public override Script Get(string id) { - if(FileSystem.FileExists(id) == false) - { - return null; - } - - string content; - using (var stream = FileSystem.OpenFile(id)) - { - var bytes = new byte[stream.Length]; - stream.Position = 0; - stream.Read(bytes, 0, (int)stream.Length); - content = Encoding.UTF8.GetString(bytes); - } - + // get the relative path within the filesystem + // (though... id should be relative already) var path = FileSystem.GetRelativePath(id); + + if (FileSystem.FileExists(path) == false) + return null; + + // content will be lazy-loaded when required var created = FileSystem.GetCreated(path).UtcDateTime; var updated = FileSystem.GetLastModified(path).UtcDateTime; + //var content = GetFileContent(path); - var script = new Script(path) + var script = new Script(path, file => GetFileContent(file.OriginalPath)) { //id can be the hash Id = path.GetHashCode(), - Content = content, Key = path.EncodeAsGuid(), + //Content = content, CreateDate = created, UpdateDate = updated, - VirtualPath = FileSystem.GetUrl(id) + VirtualPath = FileSystem.GetUrl(path) }; //on initial construction we don't want to have dirty properties tracked @@ -64,6 +56,15 @@ namespace Umbraco.Core.Persistence.Repositories return script; } + public override void AddOrUpdate(Script entity) + { + base.AddOrUpdate(entity); + + // ensure that from now on, content is lazy-loaded + if (entity.GetFileContent == null) + entity.GetFileContent = file => GetFileContent(file.OriginalPath); + } + public override IEnumerable - - - - - -

CodeMirror: Razor mode

-
- - -

MIME types defined: text/x-coffeescript.

- -

The CoffeeScript mode was written by Jeff Pickhardt (license).

- - - + + + + CodeMirror: Razor mode + + + + + + + +

CodeMirror: Razor mode

+
+ + +

MIME types defined: text/x-coffeescript.

+ +

The CoffeeScript mode was written by Jeff Pickhardt (license).

+ + + diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/razor/razor.js b/src/Umbraco.Web.UI.Client/lib/codemirror/mode/razor/razor.js similarity index 97% rename from src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/razor/razor.js rename to src/Umbraco.Web.UI.Client/lib/codemirror/mode/razor/razor.js index 1e5bff1907..3907b97dd9 100644 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/razor/razor.js +++ b/src/Umbraco.Web.UI.Client/lib/codemirror/mode/razor/razor.js @@ -1,254 +1,254 @@ -CodeMirror.defineMode("razor", function(config, parserConfig) { - var indentUnit = config.indentUnit, - keywords = words("abstract as base break case catch checked class const continue" + - " default delegate do else enum event explicit extern finally fixed for" + - " foreach goto if implicit in interface internal is lock namespace new" + - " operator out override params private protected public readonly ref return sealed" + - " sizeof stackalloc static struct switch this throw try typeof unchecked" + - " unsafe using virtual void volatile while add alias ascending descending dynamic from get" + - " global group into join let orderby partial remove select set value var yield"), - - builtin = words("Boolean Byte Char DateTime DateTimeOffset Decimal Double" + - " Guid Int16 Int32 Int64 Library Model Object SByte Single String TimeSpan UInt16 UInt32" + - " UInt64 bool byte char decimal double short int long object" + - " sbyte float string ushort uint ulong"), - blockKeywords = words("catch class do else finally for foreach if struct switch try while"), - atoms = words("true false null"), - hooks = parserConfig.hooks || {}, - multiLineStrings = parserConfig.multiLineStrings; - var isOperatorChar = /[+\-*&%=<>!?|\/]/; - - var curPunc; - - function tokenBase(stream, state) { - var ch = stream.next(); - if (hooks[ch]) { - var result = hooks[ch](stream, state); - if (result !== false) return result; - } - if (ch == '"' || ch == "'") { - state.tokenize = tokenString(ch); - return state.tokenize(stream, state); - } - if (/[\[\]{}\(\),;\:\.]/.test(ch)) { - curPunc = ch; - return null; - } - if (/\d/.test(ch)) { - stream.eatWhile(/[\w\.]/); - return "number"; - } - if (ch == "@") { - if (stream.eat("*")) { - state.tokenize = tokenComment; - return tokenComment(stream, state); - } - if (stream.eat("/")) { - stream.skipToEnd(); - return "comment"; - } - - return "at"; - } - - if (isOperatorChar.test(ch)) { - stream.eatWhile(isOperatorChar); - return "operator"; - } - stream.eatWhile(/[\w\$_]/); - var cur = stream.current(); - if (keywords.propertyIsEnumerable(cur)) { - if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement"; - return "keyword"; - } - if (builtin.propertyIsEnumerable(cur)) { - if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement"; - return "builtin"; - } - if (atoms.propertyIsEnumerable(cur)) return "atom"; - return "variable"; - } - - function tokenString(quote) { - return function(stream, state) { - var escaped = false, next, end = false; - while ((next = stream.next()) != null) { - if (next == quote && !escaped) {end = true; break;} - escaped = !escaped && next == "\\"; - } - if (end || !(escaped || multiLineStrings)) - state.tokenize = null; - return "string"; - }; - } - - function tokenComment(stream, state) { - var maybeEnd = false, ch; - while (ch = stream.next()) { - if (ch == "@" && maybeEnd) { - state.tokenize = null; - break; - } - maybeEnd = (ch == "*"); - } - return "comment"; - } - - function Context(indented, column, type, align, prev) { - this.indented = indented; - this.column = column; - this.type = type; - this.align = align; - this.prev = prev; - } - function pushContext(state, col, type) { - return state.context = new Context(state.indented, col, type, null, state.context); - } - function popContext(state) { - var t = state.context.type; - if (t == ")" || t == "]" || t == "}") - state.indented = state.context.indented; - return state.context = state.context.prev; - } - - function words(str) { - var obj = {}, words = str.split(" "); - for (var i = 0; i < words.length; ++i) obj[words[i]] = true; - return obj; - } - - // Interface - - return { - startState: function(basecolumn) { - return { - tokenize: null, - context: new Context((basecolumn || 0) - indentUnit, 0, "top", false), - indented: 0, - startOfLine: true - }; - }, - - token: function(stream, state) { - var ctx = state.context; - if (stream.sol()) { - if (ctx.align == null) ctx.align = false; - state.indented = stream.indentation(); - state.startOfLine = true; - } - if (stream.eatSpace()) return null; - curPunc = null; - var style = (state.tokenize || tokenBase)(stream, state); - if (style == "comment" || style == "meta") return style; - if (ctx.align == null) ctx.align = true; - - if ((curPunc == ";" || curPunc == ":") && ctx.type == "statement") popContext(state); - else if (curPunc == "{") pushContext(state, stream.column(), "}"); - else if (curPunc == "[") pushContext(state, stream.column(), "]"); - else if (curPunc == "(") pushContext(state, stream.column(), ")"); - else if (curPunc == "}") { - while (ctx.type == "statement") ctx = popContext(state); - if (ctx.type == "}") ctx = popContext(state); - while (ctx.type == "statement") ctx = popContext(state); - } - else if (curPunc == ctx.type) popContext(state); - else if (ctx.type == "}" || ctx.type == "top" || (ctx.type == "statement" && curPunc == "newstatement")) - pushContext(state, stream.column(), "statement"); - state.startOfLine = false; - return style; - }, - - indent: function(state, textAfter) { - if (state.tokenize != tokenBase && state.tokenize != null) return 0; - var ctx = state.context, firstChar = textAfter && textAfter.charAt(0); - if (ctx.type == "statement" && firstChar == "}") ctx = ctx.prev; - var closing = firstChar == ctx.type; - if (ctx.type == "statement") return ctx.indented + (firstChar == "{" ? 0 : indentUnit); - else if (ctx.align) return ctx.column + (closing ? 0 : 1); - else return ctx.indented + (closing ? 0 : indentUnit); - }, - - electricChars: "{}" - }; -}); - -(function() { - function words(str) { - var obj = {}, words = str.split(" "); - for (var i = 0; i < words.length; ++i) obj[words[i]] = true; - return obj; - } - var cKeywords = "auto if break int case long char register continue return default short do sizeof " + - "double static else struct entry switch extern typedef float union for unsigned " + - "goto while enum void const signed volatile"; - - function cppHook(stream, state) { - if (!state.startOfLine) return false; - stream.skipToEnd(); - return "meta"; - } - - // C#-style strings where "" escapes a quote. - function tokenAtString(stream, state) { - var next; - while ((next = stream.next()) != null) { - if (next == '"' && !stream.eat('"')) { - state.tokenize = null; - break; - } - } - return "string"; - } - - function mimes(ms, mode) { - for (var i = 0; i < ms.length; ++i) CodeMirror.defineMIME(ms[i], mode); - } - -/* - mimes(["text/x-csrc", "text/x-c", "text/x-chdr"], { - name: "clike", - keywords: words(cKeywords), - blockKeywords: words("case do else for if switch while struct"), - atoms: words("null"), - hooks: {"#": cppHook} - }); - - mimes(["text/x-c++src", "text/x-c++hdr"], { - name: "clike", - keywords: words(cKeywords + " asm dynamic_cast namespace reinterpret_cast try bool explicit new " + - "static_cast typeid catch operator template typename class friend private " + - "this using const_cast inline public throw virtual delete mutable protected " + - "wchar_t"), - blockKeywords: words("catch class do else finally for if struct switch try while"), - atoms: words("true false null"), - hooks: {"#": cppHook} - });*/ - - CodeMirror.defineMIME("text/x-razor", { - name: "razor", - keywords: words("abstract as base break case catch checked class const continue" + - " default delegate do else enum event explicit extern finally fixed for" + - " foreach goto if implicit in interface internal is lock namespace new" + - " operator out override params private protected public readonly ref return sealed" + - " sizeof stackalloc static struct switch this throw try typeof unchecked" + - " unsafe using virtual void volatile while add alias ascending descending dynamic from get" + - " global group into join let orderby partial remove select set value var yield"), - blockKeywords: words("catch class do else finally for foreach if struct switch try while"), - builtin: words("Boolean Byte Char DateTime DateTimeOffset Decimal Double" + - " Guid Int16 Int32 Int64 Object SByte Single String TimeSpan UInt16 UInt32" + - " UInt64 bool byte char decimal double short int long object" + - " sbyte float string ushort uint ulong"), - atoms: words("true false null"), - hooks: { - "@": function(stream, state) { - if (stream.eat('"')) { - state.tokenize = tokenAtString; - return tokenAtString(stream, state); - } - stream.eatWhile(/[\w\$_]/); - return "meta"; - } - } - }); - +CodeMirror.defineMode("razor", function(config, parserConfig) { + var indentUnit = config.indentUnit, + keywords = words("abstract as base break case catch checked class const continue" + + " default delegate do else enum event explicit extern finally fixed for" + + " foreach goto if implicit in interface internal is lock namespace new" + + " operator out override params private protected public readonly ref return sealed" + + " sizeof stackalloc static struct switch this throw try typeof unchecked" + + " unsafe using virtual void volatile while add alias ascending descending dynamic from get" + + " global group into join let orderby partial remove select set value var yield"), + + builtin = words("Boolean Byte Char DateTime DateTimeOffset Decimal Double" + + " Guid Int16 Int32 Int64 Library Model Object SByte Single String TimeSpan UInt16 UInt32" + + " UInt64 bool byte char decimal double short int long object" + + " sbyte float string ushort uint ulong"), + blockKeywords = words("catch class do else finally for foreach if struct switch try while"), + atoms = words("true false null"), + hooks = parserConfig.hooks || {}, + multiLineStrings = parserConfig.multiLineStrings; + var isOperatorChar = /[+\-*&%=<>!?|\/]/; + + var curPunc; + + function tokenBase(stream, state) { + var ch = stream.next(); + if (hooks[ch]) { + var result = hooks[ch](stream, state); + if (result !== false) return result; + } + if (ch == '"' || ch == "'") { + state.tokenize = tokenString(ch); + return state.tokenize(stream, state); + } + if (/[\[\]{}\(\),;\:\.]/.test(ch)) { + curPunc = ch; + return null; + } + if (/\d/.test(ch)) { + stream.eatWhile(/[\w\.]/); + return "number"; + } + if (ch == "@") { + if (stream.eat("*")) { + state.tokenize = tokenComment; + return tokenComment(stream, state); + } + if (stream.eat("/")) { + stream.skipToEnd(); + return "comment"; + } + + return "at"; + } + + if (isOperatorChar.test(ch)) { + stream.eatWhile(isOperatorChar); + return "operator"; + } + stream.eatWhile(/[\w\$_]/); + var cur = stream.current(); + if (keywords.propertyIsEnumerable(cur)) { + if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement"; + return "keyword"; + } + if (builtin.propertyIsEnumerable(cur)) { + if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement"; + return "builtin"; + } + if (atoms.propertyIsEnumerable(cur)) return "atom"; + return "variable"; + } + + function tokenString(quote) { + return function(stream, state) { + var escaped = false, next, end = false; + while ((next = stream.next()) != null) { + if (next == quote && !escaped) {end = true; break;} + escaped = !escaped && next == "\\"; + } + if (end || !(escaped || multiLineStrings)) + state.tokenize = null; + return "string"; + }; + } + + function tokenComment(stream, state) { + var maybeEnd = false, ch; + while (ch = stream.next()) { + if (ch == "@" && maybeEnd) { + state.tokenize = null; + break; + } + maybeEnd = (ch == "*"); + } + return "comment"; + } + + function Context(indented, column, type, align, prev) { + this.indented = indented; + this.column = column; + this.type = type; + this.align = align; + this.prev = prev; + } + function pushContext(state, col, type) { + return state.context = new Context(state.indented, col, type, null, state.context); + } + function popContext(state) { + var t = state.context.type; + if (t == ")" || t == "]" || t == "}") + state.indented = state.context.indented; + return state.context = state.context.prev; + } + + function words(str) { + var obj = {}, words = str.split(" "); + for (var i = 0; i < words.length; ++i) obj[words[i]] = true; + return obj; + } + + // Interface + + return { + startState: function(basecolumn) { + return { + tokenize: null, + context: new Context((basecolumn || 0) - indentUnit, 0, "top", false), + indented: 0, + startOfLine: true + }; + }, + + token: function(stream, state) { + var ctx = state.context; + if (stream.sol()) { + if (ctx.align == null) ctx.align = false; + state.indented = stream.indentation(); + state.startOfLine = true; + } + if (stream.eatSpace()) return null; + curPunc = null; + var style = (state.tokenize || tokenBase)(stream, state); + if (style == "comment" || style == "meta") return style; + if (ctx.align == null) ctx.align = true; + + if ((curPunc == ";" || curPunc == ":") && ctx.type == "statement") popContext(state); + else if (curPunc == "{") pushContext(state, stream.column(), "}"); + else if (curPunc == "[") pushContext(state, stream.column(), "]"); + else if (curPunc == "(") pushContext(state, stream.column(), ")"); + else if (curPunc == "}") { + while (ctx.type == "statement") ctx = popContext(state); + if (ctx.type == "}") ctx = popContext(state); + while (ctx.type == "statement") ctx = popContext(state); + } + else if (curPunc == ctx.type) popContext(state); + else if (ctx.type == "}" || ctx.type == "top" || (ctx.type == "statement" && curPunc == "newstatement")) + pushContext(state, stream.column(), "statement"); + state.startOfLine = false; + return style; + }, + + indent: function(state, textAfter) { + if (state.tokenize != tokenBase && state.tokenize != null) return 0; + var ctx = state.context, firstChar = textAfter && textAfter.charAt(0); + if (ctx.type == "statement" && firstChar == "}") ctx = ctx.prev; + var closing = firstChar == ctx.type; + if (ctx.type == "statement") return ctx.indented + (firstChar == "{" ? 0 : indentUnit); + else if (ctx.align) return ctx.column + (closing ? 0 : 1); + else return ctx.indented + (closing ? 0 : indentUnit); + }, + + electricChars: "{}" + }; +}); + +(function() { + function words(str) { + var obj = {}, words = str.split(" "); + for (var i = 0; i < words.length; ++i) obj[words[i]] = true; + return obj; + } + var cKeywords = "auto if break int case long char register continue return default short do sizeof " + + "double static else struct entry switch extern typedef float union for unsigned " + + "goto while enum void const signed volatile"; + + function cppHook(stream, state) { + if (!state.startOfLine) return false; + stream.skipToEnd(); + return "meta"; + } + + // C#-style strings where "" escapes a quote. + function tokenAtString(stream, state) { + var next; + while ((next = stream.next()) != null) { + if (next == '"' && !stream.eat('"')) { + state.tokenize = null; + break; + } + } + return "string"; + } + + function mimes(ms, mode) { + for (var i = 0; i < ms.length; ++i) CodeMirror.defineMIME(ms[i], mode); + } + +/* + mimes(["text/x-csrc", "text/x-c", "text/x-chdr"], { + name: "clike", + keywords: words(cKeywords), + blockKeywords: words("case do else for if switch while struct"), + atoms: words("null"), + hooks: {"#": cppHook} + }); + + mimes(["text/x-c++src", "text/x-c++hdr"], { + name: "clike", + keywords: words(cKeywords + " asm dynamic_cast namespace reinterpret_cast try bool explicit new " + + "static_cast typeid catch operator template typename class friend private " + + "this using const_cast inline public throw virtual delete mutable protected " + + "wchar_t"), + blockKeywords: words("catch class do else finally for if struct switch try while"), + atoms: words("true false null"), + hooks: {"#": cppHook} + });*/ + + CodeMirror.defineMIME("text/x-razor", { + name: "razor", + keywords: words("abstract as base break case catch checked class const continue" + + " default delegate do else enum event explicit extern finally fixed for" + + " foreach goto if implicit in interface internal is lock namespace new" + + " operator out override params private protected public readonly ref return sealed" + + " sizeof stackalloc static struct switch this throw try typeof unchecked" + + " unsafe using virtual void volatile while add alias ascending descending dynamic from get" + + " global group into join let orderby partial remove select set value var yield"), + blockKeywords: words("catch class do else finally for foreach if struct switch try while"), + builtin: words("Boolean Byte Char DateTime DateTimeOffset Decimal Double" + + " Guid Int16 Int32 Int64 Object SByte Single String TimeSpan UInt16 UInt32" + + " UInt64 bool byte char decimal double short int long object" + + " sbyte float string ushort uint ulong"), + atoms: words("true false null"), + hooks: { + "@": function(stream, state) { + if (stream.eat('"')) { + state.tokenize = tokenAtString; + return tokenAtString(stream, state); + } + stream.eatWhile(/[\w\$_]/); + return "meta"; + } + } + }); + }()); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/jquery-ui-touch-punch/jquery.ui.touch-punch.js b/src/Umbraco.Web.UI.Client/lib/jquery-ui-touch-punch/jquery.ui.touch-punch.js new file mode 100644 index 0000000000..b395b9a372 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/jquery-ui-touch-punch/jquery.ui.touch-punch.js @@ -0,0 +1,180 @@ +/*! + * jQuery UI Touch Punch 0.2.3 + * + * Copyright 2011–2014, Dave Furfero + * Dual licensed under the MIT or GPL Version 2 licenses. + * + * Depends: + * jquery.ui.widget.js + * jquery.ui.mouse.js + */ +(function ($) { + + // Detect touch support + $.support.touch = 'ontouchend' in document; + + // Ignore browsers without touch support + if (!$.support.touch) { + return; + } + + var mouseProto = $.ui.mouse.prototype, + _mouseInit = mouseProto._mouseInit, + _mouseDestroy = mouseProto._mouseDestroy, + touchHandled; + + /** + * Simulate a mouse event based on a corresponding touch event + * @param {Object} event A touch event + * @param {String} simulatedType The corresponding mouse event + */ + function simulateMouseEvent (event, simulatedType) { + + // Ignore multi-touch events + if (event.originalEvent.touches.length > 1) { + return; + } + + event.preventDefault(); + + var touch = event.originalEvent.changedTouches[0], + simulatedEvent = document.createEvent('MouseEvents'); + + // Initialize the simulated mouse event using the touch event's coordinates + simulatedEvent.initMouseEvent( + simulatedType, // type + true, // bubbles + true, // cancelable + window, // view + 1, // detail + touch.screenX, // screenX + touch.screenY, // screenY + touch.clientX, // clientX + touch.clientY, // clientY + false, // ctrlKey + false, // altKey + false, // shiftKey + false, // metaKey + 0, // button + null // relatedTarget + ); + + // Dispatch the simulated event to the target element + event.target.dispatchEvent(simulatedEvent); + } + + /** + * Handle the jQuery UI widget's touchstart events + * @param {Object} event The widget element's touchstart event + */ + mouseProto._touchStart = function (event) { + + var self = this; + + // Ignore the event if another widget is already being handled + if (touchHandled || !self._mouseCapture(event.originalEvent.changedTouches[0])) { + return; + } + + // Set the flag to prevent other widgets from inheriting the touch event + touchHandled = true; + + // Track movement to determine if interaction was a click + self._touchMoved = false; + + // Simulate the mouseover event + simulateMouseEvent(event, 'mouseover'); + + // Simulate the mousemove event + simulateMouseEvent(event, 'mousemove'); + + // Simulate the mousedown event + simulateMouseEvent(event, 'mousedown'); + }; + + /** + * Handle the jQuery UI widget's touchmove events + * @param {Object} event The document's touchmove event + */ + mouseProto._touchMove = function (event) { + + // Ignore event if not handled + if (!touchHandled) { + return; + } + + // Interaction was not a click + this._touchMoved = true; + + // Simulate the mousemove event + simulateMouseEvent(event, 'mousemove'); + }; + + /** + * Handle the jQuery UI widget's touchend events + * @param {Object} event The document's touchend event + */ + mouseProto._touchEnd = function (event) { + + // Ignore event if not handled + if (!touchHandled) { + return; + } + + // Simulate the mouseup event + simulateMouseEvent(event, 'mouseup'); + + // Simulate the mouseout event + simulateMouseEvent(event, 'mouseout'); + + // If the touch interaction did not move, it should trigger a click + if (!this._touchMoved) { + + // Simulate the click event + simulateMouseEvent(event, 'click'); + } + + // Unset the flag to allow other widgets to inherit the touch event + touchHandled = false; + }; + + /** + * A duck punch of the $.ui.mouse _mouseInit method to support touch events. + * This method extends the widget with bound touch event handlers that + * translate touch events to mouse events and pass them to the widget's + * original mouse event handling methods. + */ + mouseProto._mouseInit = function () { + + var self = this; + + // Delegate the touch handlers to the widget's element + self.element.bind({ + touchstart: $.proxy(self, '_touchStart'), + touchmove: $.proxy(self, '_touchMove'), + touchend: $.proxy(self, '_touchEnd') + }); + + // Call the original $.ui.mouse init method + _mouseInit.call(self); + }; + + /** + * Remove the touch event handlers + */ + mouseProto._mouseDestroy = function () { + + var self = this; + + // Delegate the touch handlers to the widget's element + self.element.unbind({ + touchstart: $.proxy(self, '_touchStart'), + touchmove: $.proxy(self, '_touchMove'), + touchend: $.proxy(self, '_touchEnd') + }); + + // Call the original $.ui.mouse destroy method + _mouseDestroy.call(self); + }; + +})(jQuery); diff --git a/src/Umbraco.Web.UI.Client/lib/slider/bootstrap-slider-custom.css b/src/Umbraco.Web.UI.Client/lib/slider/bootstrap-slider-custom.css new file mode 100644 index 0000000000..e7a395d458 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/slider/bootstrap-slider-custom.css @@ -0,0 +1,79 @@ +/* set default selection color */ +.slider-track .slider-selection { + background: #89cdef; +} + +/* custom styling for triangle */ +.slider.slider-horizontal .slider-tick.triangle, +.slider.slider-horizontal .slider-handle.triangle { + box-shadow: none; + border-width: 0 12px 15px 12px !important; + margin-left: -12px; + margin-top: -5px !important; +} + +.slider.slider-vertical .slider-tick.triangle, +.slider.slider-vertical .slider-handle.triangle { + box-shadow: none; + border-width: 12px 0 12px 15px !important; + margin-top: -12px; +} + +/* for custom handle remove box-shadow */ +.slider-track .slider-handle.custom { + box-shadow: none; + margin-left: -8px; +} + +/* for custom handle add text-shadow */ +.slider-track .slider-handle.custom::before { + text-shadow: 0 1px 2px rgba(0,0,0,.05); + color: #337ab7; + font-size: 26px; +} + +/* we make each tick a bit more clear */ +.slider .slider-tick { + background-image: -webkit-linear-gradient(top, #e6e6e6 0, #dedede 100%); + background-image: -o-linear-gradient(top, #e6e6e6 0, #dedede 100%); + background-image: linear-gradient(to bottom, #e6e6e6 0, #dedede 100%); +} + +.slider .slider-tick.in-selection { + background-image: -webkit-linear-gradient(top, #89cdef 0, #81bfde 100%); + background-image: -o-linear-gradient(top, #89cdef 0, #81bfde 100%); + background-image: linear-gradient(to bottom, #89cdef 0, #81bfde 100%); +} + +/* horizontal - triangle border-bottom color */ +.slider.slider-horizontal .slider-tick.triangle { + border-bottom-color: #dedede; +} +.slider.slider-horizontal .slider-handle.triangle { + border-bottom-color: #0480be; +} +.slider.slider-horizontal .slider-tick.triangle.in-selection { + border-bottom-color: #81bfde; +} + +/* vertical - triangle border-left color */ +.slider.slider-vertical .slider-tick.triangle { + border-left-color: #dedede; +} +.slider.slider-vertical .slider-handle.triangle { + border-left-color: #0480be; +} +.slider.slider-vertical .slider-tick.triangle.in-selection { + border-left-color: #81bfde; +} + +.slider.slider-horizontal { + width: 400px; + max-width: 100%; +} + +@media only screen and (max-width: 767px) { + .slider.slider-horizontal { + width: 100%; + } +} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/slider/bootstrap-slider.css b/src/Umbraco.Web.UI.Client/lib/slider/bootstrap-slider.css new file mode 100644 index 0000000000..aa763b6bb6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/slider/bootstrap-slider.css @@ -0,0 +1,255 @@ +/*! ======================================================= + VERSION 5.2.6 +========================================================= */ +/*! ========================================================= + * bootstrap-slider.js + * + * Maintainers: + * Kyle Kemp + * - Twitter: @seiyria + * - Github: seiyria + * Rohit Kalkur + * - Twitter: @Rovolutionary + * - Github: rovolution + * + * ========================================================= + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ========================================================= */ +.slider { + display: inline-block; + vertical-align: middle; + position: relative; +} +.slider.slider-horizontal { + width: 210px; + height: 20px; +} +.slider.slider-horizontal .slider-track { + height: 10px; + width: 100%; + margin-top: -5px; + top: 50%; + left: 0; +} +.slider.slider-horizontal .slider-selection, +.slider.slider-horizontal .slider-track-low, +.slider.slider-horizontal .slider-track-high { + height: 100%; + top: 0; + bottom: 0; +} +.slider.slider-horizontal .slider-tick, +.slider.slider-horizontal .slider-handle { + margin-left: -10px; + margin-top: -5px; +} +.slider.slider-horizontal .slider-tick.triangle, +.slider.slider-horizontal .slider-handle.triangle { + border-width: 0 10px 10px 10px; + width: 0; + height: 0; + border-bottom-color: #0480be; + margin-top: 0; +} +.slider.slider-horizontal .slider-tick-label-container { + white-space: nowrap; + margin-top: 20px; +} +.slider.slider-horizontal .slider-tick-label-container .slider-tick-label { + padding-top: 4px; + display: inline-block; + text-align: center; +} +.slider.slider-vertical { + height: 210px; + width: 20px; +} +.slider.slider-vertical .slider-track { + width: 10px; + height: 100%; + margin-left: -5px; + left: 50%; + top: 0; +} +.slider.slider-vertical .slider-selection { + width: 100%; + left: 0; + top: 0; + bottom: 0; +} +.slider.slider-vertical .slider-track-low, +.slider.slider-vertical .slider-track-high { + width: 100%; + left: 0; + right: 0; +} +.slider.slider-vertical .slider-tick, +.slider.slider-vertical .slider-handle { + margin-left: -5px; + margin-top: -10px; +} +.slider.slider-vertical .slider-tick.triangle, +.slider.slider-vertical .slider-handle.triangle { + border-width: 10px 0 10px 10px; + width: 1px; + height: 1px; + border-left-color: #0480be; + margin-left: 0; +} +.slider.slider-vertical .slider-tick-label-container { + white-space: nowrap; +} +.slider.slider-vertical .slider-tick-label-container .slider-tick-label { + padding-left: 4px; +} +.slider.slider-disabled .slider-handle { + background-image: -webkit-linear-gradient(top, #dfdfdf 0%, #bebebe 100%); + background-image: -o-linear-gradient(top, #dfdfdf 0%, #bebebe 100%); + background-image: linear-gradient(to bottom, #dfdfdf 0%, #bebebe 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdfdfdf', endColorstr='#ffbebebe', GradientType=0); +} +.slider.slider-disabled .slider-track { + background-image: -webkit-linear-gradient(top, #e5e5e5 0%, #e9e9e9 100%); + background-image: -o-linear-gradient(top, #e5e5e5 0%, #e9e9e9 100%); + background-image: linear-gradient(to bottom, #e5e5e5 0%, #e9e9e9 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe5e5e5', endColorstr='#ffe9e9e9', GradientType=0); + cursor: not-allowed; +} +.slider input { + display: none; +} +.slider .tooltip.top { + margin-top: -36px; +} +.slider .tooltip-inner { + white-space: nowrap; +} +.slider .hide { + display: none; +} +.slider-track { + position: absolute; + cursor: pointer; + background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #f9f9f9 100%); + background-image: -o-linear-gradient(top, #f5f5f5 0%, #f9f9f9 100%); + background-image: linear-gradient(to bottom, #f5f5f5 0%, #f9f9f9 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#fff9f9f9', GradientType=0); + -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); + border-radius: 4px; +} +.slider-selection { + position: absolute; + background-image: -webkit-linear-gradient(top, #f9f9f9 0%, #f5f5f5 100%); + background-image: -o-linear-gradient(top, #f9f9f9 0%, #f5f5f5 100%); + background-image: linear-gradient(to bottom, #f9f9f9 0%, #f5f5f5 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff9f9f9', endColorstr='#fff5f5f5', GradientType=0); + -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); + box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + border-radius: 4px; +} +.slider-selection.tick-slider-selection { + background-image: -webkit-linear-gradient(top, #89cdef 0%, #81bfde 100%); + background-image: -o-linear-gradient(top, #89cdef 0%, #81bfde 100%); + background-image: linear-gradient(to bottom, #89cdef 0%, #81bfde 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff89cdef', endColorstr='#ff81bfde', GradientType=0); +} +.slider-track-low, +.slider-track-high { + position: absolute; + background: transparent; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + border-radius: 4px; +} +.slider-handle { + position: absolute; + width: 20px; + height: 20px; + background-color: #337ab7; + background-image: -webkit-linear-gradient(top, #149bdf 0%, #0480be 100%); + background-image: -o-linear-gradient(top, #149bdf 0%, #0480be 100%); + background-image: linear-gradient(to bottom, #149bdf 0%, #0480be 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf', endColorstr='#ff0480be', GradientType=0); + filter: none; + -webkit-box-shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05); + box-shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05); + border: 0px solid transparent; +} +.slider-handle.round { + border-radius: 50%; +} +.slider-handle.triangle { + background: transparent none; +} +.slider-handle.custom { + background: transparent none; +} +.slider-handle.custom::before { + line-height: 20px; + font-size: 20px; + content: '\2605'; + color: #726204; +} +.slider-tick { + position: absolute; + width: 20px; + height: 20px; + background-image: -webkit-linear-gradient(top, #f9f9f9 0%, #f5f5f5 100%); + background-image: -o-linear-gradient(top, #f9f9f9 0%, #f5f5f5 100%); + background-image: linear-gradient(to bottom, #f9f9f9 0%, #f5f5f5 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff9f9f9', endColorstr='#fff5f5f5', GradientType=0); + -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); + box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + filter: none; + opacity: 0.8; + border: 0px solid transparent; +} +.slider-tick.round { + border-radius: 50%; +} +.slider-tick.triangle { + background: transparent none; +} +.slider-tick.custom { + background: transparent none; +} +.slider-tick.custom::before { + line-height: 20px; + font-size: 20px; + content: '\2605'; + color: #726204; +} +.slider-tick.in-selection { + background-image: -webkit-linear-gradient(top, #89cdef 0%, #81bfde 100%); + background-image: -o-linear-gradient(top, #89cdef 0%, #81bfde 100%); + background-image: linear-gradient(to bottom, #89cdef 0%, #81bfde 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff89cdef', endColorstr='#ff81bfde', GradientType=0); + opacity: 1; +} diff --git a/src/Umbraco.Web.UI.Client/lib/slider/js/bootstrap-slider.js b/src/Umbraco.Web.UI.Client/lib/slider/js/bootstrap-slider.js index fe752f5295..3ae97f4fd6 100644 --- a/src/Umbraco.Web.UI.Client/lib/slider/js/bootstrap-slider.js +++ b/src/Umbraco.Web.UI.Client/lib/slider/js/bootstrap-slider.js @@ -1,388 +1,1553 @@ -/* ========================================================= - * bootstrap-slider.js v2.0.0 - * http://www.eyecon.ro/bootstrap-slider - * ========================================================= - * Copyright 2012 Stefan Petre - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ========================================================= */ - -!function( $ ) { - - var Slider = function(element, options) { - this.element = $(element); - this.picker = $('
'+ - '
'+ - '
'+ - '
'+ - '
'+ - '
'+ - '
'+ - '
') - .insertBefore(this.element) - .append(this.element); - this.id = this.element.data('slider-id')||options.id; - if (this.id) { - this.picker[0].id = this.id; - } - - if (typeof Modernizr !== 'undefined' && Modernizr.touch) { - this.touchCapable = true; - } - - var tooltip = this.element.data('slider-tooltip')||options.tooltip; - - this.tooltip = this.picker.find('.tooltip'); - this.tooltipInner = this.tooltip.find('div.tooltip-inner'); - - this.orientation = this.element.data('slider-orientation')||options.orientation; - switch(this.orientation) { - case 'vertical': - this.picker.addClass('slider-vertical'); - this.stylePos = 'top'; - this.mousePos = 'pageY'; - this.sizePos = 'offsetHeight'; - this.tooltip.addClass('right')[0].style.left = '100%'; - break; - default: - this.picker - .addClass('slider-horizontal') - .css('width', this.element.outerWidth()); - this.orientation = 'horizontal'; - this.stylePos = 'left'; - this.mousePos = 'pageX'; - this.sizePos = 'offsetWidth'; - this.tooltip.addClass('top')[0].style.top = -this.tooltip.outerHeight() - 14 + 'px'; - break; - } - - this.min = this.element.data('slider-min')||options.min; - this.max = this.element.data('slider-max')||options.max; - this.step = this.element.data('slider-step')||options.step; - this.value = this.element.data('slider-value')||options.value; - if (this.value[1]) { - this.range = true; - } - - this.selection = this.element.data('slider-selection')||options.selection; - this.selectionEl = this.picker.find('.slider-selection'); - if (this.selection === 'none') { - this.selectionEl.addClass('hide'); - } - this.selectionElStyle = this.selectionEl[0].style; - - - this.handle1 = this.picker.find('.slider-handle:first'); - this.handle1Stype = this.handle1[0].style; - this.handle2 = this.picker.find('.slider-handle:last'); - this.handle2Stype = this.handle2[0].style; - - var handle = this.element.data('slider-handle')||options.handle; - switch(handle) { - case 'round': - this.handle1.addClass('round'); - this.handle2.addClass('round'); - break - case 'triangle': - this.handle1.addClass('triangle'); - this.handle2.addClass('triangle'); - break - } - - if (this.range) { - this.value[0] = Math.max(this.min, Math.min(this.max, this.value[0])); - this.value[1] = Math.max(this.min, Math.min(this.max, this.value[1])); - } else { - this.value = [ Math.max(this.min, Math.min(this.max, this.value))]; - this.handle2.addClass('hide'); - if (this.selection == 'after') { - this.value[1] = this.max; - } else { - this.value[1] = this.min; - } - } - this.diff = this.max - this.min; - this.percentage = [ - (this.value[0]-this.min)*100/this.diff, - (this.value[1]-this.min)*100/this.diff, - this.step*100/this.diff - ]; - - this.offset = this.picker.offset(); - this.size = this.picker[0][this.sizePos]; - - this.formater = options.formater; - - this.layout(); - - if (this.touchCapable) { - // Touch: Bind touch events: - this.picker.on({ - touchstart: $.proxy(this.mousedown, this) - }); - } else { - this.picker.on({ - mousedown: $.proxy(this.mousedown, this) - }); - } - - if (tooltip === 'show') { - this.picker.on({ - mouseenter: $.proxy(this.showTooltip, this), - mouseleave: $.proxy(this.hideTooltip, this) - }); - } else { - this.tooltip.addClass('hide'); - } - }; - - Slider.prototype = { - constructor: Slider, - - over: false, - inDrag: false, - - showTooltip: function(){ - this.tooltip.addClass('in'); - //var left = Math.round(this.percent*this.width); - //this.tooltip.css('left', left - this.tooltip.outerWidth()/2); - this.over = true; - }, - - hideTooltip: function(){ - if (this.inDrag === false) { - this.tooltip.removeClass('in'); - } - this.over = false; - }, - - layout: function(){ - this.handle1Stype[this.stylePos] = this.percentage[0]+'%'; - this.handle2Stype[this.stylePos] = this.percentage[1]+'%'; - if (this.orientation == 'vertical') { - this.selectionElStyle.top = Math.min(this.percentage[0], this.percentage[1]) +'%'; - this.selectionElStyle.height = Math.abs(this.percentage[0] - this.percentage[1]) +'%'; - } else { - this.selectionElStyle.left = Math.min(this.percentage[0], this.percentage[1]) +'%'; - this.selectionElStyle.width = Math.abs(this.percentage[0] - this.percentage[1]) +'%'; - } - if (this.range) { - this.tooltipInner.text( - this.formater(this.value[0]) + - ' : ' + - this.formater(this.value[1]) - ); - this.tooltip[0].style[this.stylePos] = this.size * (this.percentage[0] + (this.percentage[1] - this.percentage[0])/2)/100 - (this.orientation === 'vertical' ? this.tooltip.outerHeight()/2 : this.tooltip.outerWidth()/2) +'px'; - } else { - this.tooltipInner.text( - this.formater(this.value[0]) - ); - this.tooltip[0].style[this.stylePos] = this.size * this.percentage[0]/100 - (this.orientation === 'vertical' ? this.tooltip.outerHeight()/2 : this.tooltip.outerWidth()/2) +'px'; - } - }, - - mousedown: function(ev) { - - // Touch: Get the original event: - if (this.touchCapable && ev.type === 'touchstart') { - ev = ev.originalEvent; - } - - this.offset = this.picker.offset(); - this.size = this.picker[0][this.sizePos]; - - var percentage = this.getPercentage(ev); - - if (this.range) { - var diff1 = Math.abs(this.percentage[0] - percentage); - var diff2 = Math.abs(this.percentage[1] - percentage); - this.dragged = (diff1 < diff2) ? 0 : 1; - } else { - this.dragged = 0; - } - - this.percentage[this.dragged] = percentage; - this.layout(); - - if (this.touchCapable) { - // Touch: Bind touch events: - $(document).on({ - touchmove: $.proxy(this.mousemove, this), - touchend: $.proxy(this.mouseup, this) - }); - } else { - $(document).on({ - mousemove: $.proxy(this.mousemove, this), - mouseup: $.proxy(this.mouseup, this) - }); - } - - this.inDrag = true; - var val = this.calculateValue(); - this.element.trigger({ - type: 'slideStart', - value: val - }).trigger({ - type: 'slide', - value: val - }); - return false; - }, - - mousemove: function(ev) { - - // Touch: Get the original event: - if (this.touchCapable && ev.type === 'touchmove') { - ev = ev.originalEvent; - } - - var percentage = this.getPercentage(ev); - if (this.range) { - if (this.dragged === 0 && this.percentage[1] < percentage) { - this.percentage[0] = this.percentage[1]; - this.dragged = 1; - } else if (this.dragged === 1 && this.percentage[0] > percentage) { - this.percentage[1] = this.percentage[0]; - this.dragged = 0; - } - } - this.percentage[this.dragged] = percentage; - this.layout(); - var val = this.calculateValue(); - this.element - .trigger({ - type: 'slide', - value: val - }) - .data('value', val) - .prop('value', val); - return false; - }, - - mouseup: function(ev) { - if (this.touchCapable) { - // Touch: Bind touch events: - $(document).off({ - touchmove: this.mousemove, - touchend: this.mouseup - }); - } else { - $(document).off({ - mousemove: this.mousemove, - mouseup: this.mouseup - }); - } - - this.inDrag = false; - if (this.over == false) { - this.hideTooltip(); - } - this.element; - var val = this.calculateValue(); - this.element - .trigger({ - type: 'slideStop', - value: val - }) - .data('value', val) - .prop('value', val); - return false; - }, - - calculateValue: function() { - var val; - if (this.range) { - val = [ - (this.min + Math.round((this.diff * this.percentage[0]/100)/this.step)*this.step), - (this.min + Math.round((this.diff * this.percentage[1]/100)/this.step)*this.step) - ]; - this.value = val; - } else { - val = (this.min + Math.round((this.diff * this.percentage[0]/100)/this.step)*this.step); - this.value = [val, this.value[1]]; - } - return val; - }, - - getPercentage: function(ev) { - if (this.touchCapable) { - ev = ev.touches[0]; - } - var percentage = (ev[this.mousePos] - this.offset[this.stylePos])*100/this.size; - percentage = Math.round(percentage/this.percentage[2])*this.percentage[2]; - return Math.max(0, Math.min(100, percentage)); - }, - - getValue: function() { - if (this.range) { - return this.value; - } - return this.value[0]; - }, - - setValue: function(val) { - this.value = val; - - if (this.range) { - this.value[0] = Math.max(this.min, Math.min(this.max, this.value[0])); - this.value[1] = Math.max(this.min, Math.min(this.max, this.value[1])); - } else { - this.value = [ Math.max(this.min, Math.min(this.max, this.value))]; - this.handle2.addClass('hide'); - if (this.selection == 'after') { - this.value[1] = this.max; - } else { - this.value[1] = this.min; - } - } - this.diff = this.max - this.min; - this.percentage = [ - (this.value[0]-this.min)*100/this.diff, - (this.value[1]-this.min)*100/this.diff, - this.step*100/this.diff - ]; - this.layout(); - } - }; - - $.fn.slider = function ( option, val ) { - return this.each(function () { - var $this = $(this), - data = $this.data('slider'), - options = typeof option === 'object' && option; - if (!data) { - $this.data('slider', (data = new Slider(this, $.extend({}, $.fn.slider.defaults,options)))); - } - if (typeof option == 'string') { - data[option](val); - } - }) - }; - - $.fn.slider.defaults = { - min: 0, - max: 10, - step: 1, - orientation: 'horizontal', - value: 5, - selection: 'before', - tooltip: 'show', - handle: 'round', - formater: function(value) { - return value; - } - }; - - $.fn.slider.Constructor = Slider; - -}( window.jQuery ); \ No newline at end of file +/*! ======================================================= + VERSION 5.2.6 +========================================================= */ +/*! ========================================================= + * bootstrap-slider.js + * + * Maintainers: + * Kyle Kemp + * - Twitter: @seiyria + * - Github: seiyria + * Rohit Kalkur + * - Twitter: @Rovolutionary + * - Github: rovolution + * + * ========================================================= + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ========================================================= */ + + +/** + * Bridget makes jQuery widgets + * v1.0.1 + * MIT license + */ + +(function(root, factory) { + if(typeof define === "function" && define.amd) { + define(["jquery"], factory); + } + else if(typeof module === "object" && module.exports) { + var jQuery; + try { + jQuery = require("jquery"); + } + catch (err) { + jQuery = null; + } + module.exports = factory(jQuery); + } + else { + root.Slider = factory(root.jQuery); + } +}(this, function($) { + // Reference to Slider constructor + var Slider; + + + (function( $ ) { + + 'use strict'; + + // -------------------------- utils -------------------------- // + + var slice = Array.prototype.slice; + + function noop() {} + + // -------------------------- definition -------------------------- // + + function defineBridget( $ ) { + + // bail if no jQuery + if ( !$ ) { + return; + } + + // -------------------------- addOptionMethod -------------------------- // + + /** + * adds option method -> $().plugin('option', {...}) + * @param {Function} PluginClass - constructor class + */ + function addOptionMethod( PluginClass ) { + // don't overwrite original option method + if ( PluginClass.prototype.option ) { + return; + } + + // option setter + PluginClass.prototype.option = function( opts ) { + // bail out if not an object + if ( !$.isPlainObject( opts ) ){ + return; + } + this.options = $.extend( true, this.options, opts ); + }; + } + + + // -------------------------- plugin bridge -------------------------- // + + // helper function for logging errors + // $.error breaks jQuery chaining + var logError = typeof console === 'undefined' ? noop : + function( message ) { + console.error( message ); + }; + + /** + * jQuery plugin bridge, access methods like $elem.plugin('method') + * @param {String} namespace - plugin name + * @param {Function} PluginClass - constructor class + */ + function bridge( namespace, PluginClass ) { + // add to jQuery fn namespace + $.fn[ namespace ] = function( options ) { + if ( typeof options === 'string' ) { + // call plugin method when first argument is a string + // get arguments for method + var args = slice.call( arguments, 1 ); + + for ( var i=0, len = this.length; i < len; i++ ) { + var elem = this[i]; + var instance = $.data( elem, namespace ); + if ( !instance ) { + logError( "cannot call methods on " + namespace + " prior to initialization; " + + "attempted to call '" + options + "'" ); + continue; + } + if ( !$.isFunction( instance[options] ) || options.charAt(0) === '_' ) { + logError( "no such method '" + options + "' for " + namespace + " instance" ); + continue; + } + + // trigger method with arguments + var returnValue = instance[ options ].apply( instance, args); + + // break look and return first value if provided + if ( returnValue !== undefined && returnValue !== instance) { + return returnValue; + } + } + // return this if no return value + return this; + } else { + var objects = this.map( function() { + var instance = $.data( this, namespace ); + if ( instance ) { + // apply options & init + instance.option( options ); + instance._init(); + } else { + // initialize new instance + instance = new PluginClass( this, options ); + $.data( this, namespace, instance ); + } + return $(this); + }); + + if(!objects || objects.length > 1) { + return objects; + } else { + return objects[0]; + } + } + }; + + } + + // -------------------------- bridget -------------------------- // + + /** + * converts a Prototypical class into a proper jQuery plugin + * the class must have a ._init method + * @param {String} namespace - plugin name, used in $().pluginName + * @param {Function} PluginClass - constructor class + */ + $.bridget = function( namespace, PluginClass ) { + addOptionMethod( PluginClass ); + bridge( namespace, PluginClass ); + }; + + return $.bridget; + + } + + // get jquery from browser global + defineBridget( $ ); + + })( $ ); + + + /************************************************* + + BOOTSTRAP-SLIDER SOURCE CODE + + **************************************************/ + + (function($) { + + var ErrorMsgs = { + formatInvalidInputErrorMsg : function(input) { + return "Invalid input value '" + input + "' passed in"; + }, + callingContextNotSliderInstance : "Calling context element does not have instance of Slider bound to it. Check your code to make sure the JQuery object returned from the call to the slider() initializer is calling the method" + }; + + var SliderScale = { + linear: { + toValue: function(percentage) { + var rawValue = percentage/100 * (this.options.max - this.options.min); + if (this.options.ticks_positions.length > 0) { + var minv, maxv, minp, maxp = 0; + for (var i = 0; i < this.options.ticks_positions.length; i++) { + if (percentage <= this.options.ticks_positions[i]) { + minv = (i > 0) ? this.options.ticks[i-1] : 0; + minp = (i > 0) ? this.options.ticks_positions[i-1] : 0; + maxv = this.options.ticks[i]; + maxp = this.options.ticks_positions[i]; + + break; + } + } + if (i > 0) { + var partialPercentage = (percentage - minp) / (maxp - minp); + rawValue = minv + partialPercentage * (maxv - minv); + } + } + + var value = this.options.min + Math.round(rawValue / this.options.step) * this.options.step; + if (value < this.options.min) { + return this.options.min; + } else if (value > this.options.max) { + return this.options.max; + } else { + return value; + } + }, + toPercentage: function(value) { + if (this.options.max === this.options.min) { + return 0; + } + + if (this.options.ticks_positions.length > 0) { + var minv, maxv, minp, maxp = 0; + for (var i = 0; i < this.options.ticks.length; i++) { + if (value <= this.options.ticks[i]) { + minv = (i > 0) ? this.options.ticks[i-1] : 0; + minp = (i > 0) ? this.options.ticks_positions[i-1] : 0; + maxv = this.options.ticks[i]; + maxp = this.options.ticks_positions[i]; + + break; + } + } + if (i > 0) { + var partialPercentage = (value - minv) / (maxv - minv); + return minp + partialPercentage * (maxp - minp); + } + } + + return 100 * (value - this.options.min) / (this.options.max - this.options.min); + } + }, + + logarithmic: { + /* Based on http://stackoverflow.com/questions/846221/logarithmic-slider */ + toValue: function(percentage) { + var min = (this.options.min === 0) ? 0 : Math.log(this.options.min); + var max = Math.log(this.options.max); + var value = Math.exp(min + (max - min) * percentage / 100); + value = this.options.min + Math.round((value - this.options.min) / this.options.step) * this.options.step; + /* Rounding to the nearest step could exceed the min or + * max, so clip to those values. */ + if (value < this.options.min) { + return this.options.min; + } else if (value > this.options.max) { + return this.options.max; + } else { + return value; + } + }, + toPercentage: function(value) { + if (this.options.max === this.options.min) { + return 0; + } else { + var max = Math.log(this.options.max); + var min = this.options.min === 0 ? 0 : Math.log(this.options.min); + var v = value === 0 ? 0 : Math.log(value); + return 100 * (v - min) / (max - min); + } + } + } + }; + + + /************************************************* + + CONSTRUCTOR + + **************************************************/ + Slider = function(element, options) { + createNewSlider.call(this, element, options); + return this; + }; + + function createNewSlider(element, options) { + + /* + The internal state object is used to store data about the current 'state' of slider. + + This includes values such as the `value`, `enabled`, etc... + */ + this._state = { + value: null, + enabled: null, + offset: null, + size: null, + percentage: null, + inDrag: false, + over: false + }; + + + if(typeof element === "string") { + this.element = document.querySelector(element); + } else if(element instanceof HTMLElement) { + this.element = element; + } + + /************************************************* + + Process Options + + **************************************************/ + options = options ? options : {}; + var optionTypes = Object.keys(this.defaultOptions); + + for(var i = 0; i < optionTypes.length; i++) { + var optName = optionTypes[i]; + + // First check if an option was passed in via the constructor + var val = options[optName]; + // If no data attrib, then check data atrributes + val = (typeof val !== 'undefined') ? val : getDataAttrib(this.element, optName); + // Finally, if nothing was specified, use the defaults + val = (val !== null) ? val : this.defaultOptions[optName]; + + // Set all options on the instance of the Slider + if(!this.options) { + this.options = {}; + } + this.options[optName] = val; + } + + /* + Validate `tooltip_position` against 'orientation` + - if `tooltip_position` is incompatible with orientation, swith it to a default compatible with specified `orientation` + -- default for "vertical" -> "right" + -- default for "horizontal" -> "left" + */ + if(this.options.orientation === "vertical" && (this.options.tooltip_position === "top" || this.options.tooltip_position === "bottom")) { + + this.options.tooltip_position = "right"; + + } + else if(this.options.orientation === "horizontal" && (this.options.tooltip_position === "left" || this.options.tooltip_position === "right")) { + + this.options.tooltip_position = "top"; + + } + + function getDataAttrib(element, optName) { + var dataName = "data-slider-" + optName.replace(/_/g, '-'); + var dataValString = element.getAttribute(dataName); + + try { + return JSON.parse(dataValString); + } + catch(err) { + return dataValString; + } + } + + /************************************************* + + Create Markup + + **************************************************/ + + var origWidth = this.element.style.width; + var updateSlider = false; + var parent = this.element.parentNode; + var sliderTrackSelection; + var sliderTrackLow, sliderTrackHigh; + var sliderMinHandle; + var sliderMaxHandle; + + if (this.sliderElem) { + updateSlider = true; + } else { + /* Create elements needed for slider */ + this.sliderElem = document.createElement("div"); + this.sliderElem.className = "slider"; + + /* Create slider track elements */ + var sliderTrack = document.createElement("div"); + sliderTrack.className = "slider-track"; + + sliderTrackLow = document.createElement("div"); + sliderTrackLow.className = "slider-track-low"; + + sliderTrackSelection = document.createElement("div"); + sliderTrackSelection.className = "slider-selection"; + + sliderTrackHigh = document.createElement("div"); + sliderTrackHigh.className = "slider-track-high"; + + sliderMinHandle = document.createElement("div"); + sliderMinHandle.className = "slider-handle min-slider-handle"; + + sliderMaxHandle = document.createElement("div"); + sliderMaxHandle.className = "slider-handle max-slider-handle"; + + sliderTrack.appendChild(sliderTrackLow); + sliderTrack.appendChild(sliderTrackSelection); + sliderTrack.appendChild(sliderTrackHigh); + + /* Create ticks */ + this.ticks = []; + if (Array.isArray(this.options.ticks) && this.options.ticks.length > 0) { + for (i = 0; i < this.options.ticks.length; i++) { + var tick = document.createElement('div'); + tick.className = 'slider-tick'; + + this.ticks.push(tick); + sliderTrack.appendChild(tick); + } + + sliderTrackSelection.className += " tick-slider-selection"; + } + + sliderTrack.appendChild(sliderMinHandle); + sliderTrack.appendChild(sliderMaxHandle); + + this.tickLabels = []; + if (Array.isArray(this.options.ticks_labels) && this.options.ticks_labels.length > 0) { + this.tickLabelContainer = document.createElement('div'); + this.tickLabelContainer.className = 'slider-tick-label-container'; + + for (i = 0; i < this.options.ticks_labels.length; i++) { + var label = document.createElement('div'); + var noTickPositionsSpecified = this.options.ticks_positions.length === 0; + var tickLabelsIndex = (this.options.reversed && noTickPositionsSpecified) ? (this.options.ticks_labels.length - (i + 1)) : i; + label.className = 'slider-tick-label'; + label.innerHTML = this.options.ticks_labels[tickLabelsIndex]; + + this.tickLabels.push(label); + this.tickLabelContainer.appendChild(label); + } + } + + + var createAndAppendTooltipSubElements = function(tooltipElem) { + var arrow = document.createElement("div"); + arrow.className = "tooltip-arrow"; + + var inner = document.createElement("div"); + inner.className = "tooltip-inner"; + + tooltipElem.appendChild(arrow); + tooltipElem.appendChild(inner); + + }; + + /* Create tooltip elements */ + var sliderTooltip = document.createElement("div"); + sliderTooltip.className = "tooltip tooltip-main"; + createAndAppendTooltipSubElements(sliderTooltip); + + var sliderTooltipMin = document.createElement("div"); + sliderTooltipMin.className = "tooltip tooltip-min"; + createAndAppendTooltipSubElements(sliderTooltipMin); + + var sliderTooltipMax = document.createElement("div"); + sliderTooltipMax.className = "tooltip tooltip-max"; + createAndAppendTooltipSubElements(sliderTooltipMax); + + + /* Append components to sliderElem */ + this.sliderElem.appendChild(sliderTrack); + this.sliderElem.appendChild(sliderTooltip); + this.sliderElem.appendChild(sliderTooltipMin); + this.sliderElem.appendChild(sliderTooltipMax); + + if (this.tickLabelContainer) { + this.sliderElem.appendChild(this.tickLabelContainer); + } + + /* Append slider element to parent container, right before the original element */ + parent.insertBefore(this.sliderElem, this.element); + + /* Hide original element */ + this.element.style.display = "none"; + } + /* If JQuery exists, cache JQ references */ + if($) { + this.$element = $(this.element); + this.$sliderElem = $(this.sliderElem); + } + + /************************************************* + + Setup + + **************************************************/ + this.eventToCallbackMap = {}; + this.sliderElem.id = this.options.id; + + this.touchCapable = 'ontouchstart' in window || (window.DocumentTouch && document instanceof window.DocumentTouch); + + this.tooltip = this.sliderElem.querySelector('.tooltip-main'); + this.tooltipInner = this.tooltip.querySelector('.tooltip-inner'); + + this.tooltip_min = this.sliderElem.querySelector('.tooltip-min'); + this.tooltipInner_min = this.tooltip_min.querySelector('.tooltip-inner'); + + this.tooltip_max = this.sliderElem.querySelector('.tooltip-max'); + this.tooltipInner_max= this.tooltip_max.querySelector('.tooltip-inner'); + + if (SliderScale[this.options.scale]) { + this.options.scale = SliderScale[this.options.scale]; + } + + if (updateSlider === true) { + // Reset classes + this._removeClass(this.sliderElem, 'slider-horizontal'); + this._removeClass(this.sliderElem, 'slider-vertical'); + this._removeClass(this.tooltip, 'hide'); + this._removeClass(this.tooltip_min, 'hide'); + this._removeClass(this.tooltip_max, 'hide'); + + // Undo existing inline styles for track + ["left", "top", "width", "height"].forEach(function(prop) { + this._removeProperty(this.trackLow, prop); + this._removeProperty(this.trackSelection, prop); + this._removeProperty(this.trackHigh, prop); + }, this); + + // Undo inline styles on handles + [this.handle1, this.handle2].forEach(function(handle) { + this._removeProperty(handle, 'left'); + this._removeProperty(handle, 'top'); + }, this); + + // Undo inline styles and classes on tooltips + [this.tooltip, this.tooltip_min, this.tooltip_max].forEach(function(tooltip) { + this._removeProperty(tooltip, 'left'); + this._removeProperty(tooltip, 'top'); + this._removeProperty(tooltip, 'margin-left'); + this._removeProperty(tooltip, 'margin-top'); + + this._removeClass(tooltip, 'right'); + this._removeClass(tooltip, 'top'); + }, this); + } + + if(this.options.orientation === 'vertical') { + this._addClass(this.sliderElem,'slider-vertical'); + this.stylePos = 'top'; + this.mousePos = 'pageY'; + this.sizePos = 'offsetHeight'; + } else { + this._addClass(this.sliderElem, 'slider-horizontal'); + this.sliderElem.style.width = origWidth; + this.options.orientation = 'horizontal'; + this.stylePos = 'left'; + this.mousePos = 'pageX'; + this.sizePos = 'offsetWidth'; + + } + this._setTooltipPosition(); + /* In case ticks are specified, overwrite the min and max bounds */ + if (Array.isArray(this.options.ticks) && this.options.ticks.length > 0) { + this.options.max = Math.max.apply(Math, this.options.ticks); + this.options.min = Math.min.apply(Math, this.options.ticks); + } + + if (Array.isArray(this.options.value)) { + this.options.range = true; + this._state.value = this.options.value; + } + else if (this.options.range) { + // User wants a range, but value is not an array + this._state.value = [this.options.value, this.options.max]; + } + else { + this._state.value = this.options.value; + } + + this.trackLow = sliderTrackLow || this.trackLow; + this.trackSelection = sliderTrackSelection || this.trackSelection; + this.trackHigh = sliderTrackHigh || this.trackHigh; + + if (this.options.selection === 'none') { + this._addClass(this.trackLow, 'hide'); + this._addClass(this.trackSelection, 'hide'); + this._addClass(this.trackHigh, 'hide'); + } + + this.handle1 = sliderMinHandle || this.handle1; + this.handle2 = sliderMaxHandle || this.handle2; + + if (updateSlider === true) { + // Reset classes + this._removeClass(this.handle1, 'round triangle'); + this._removeClass(this.handle2, 'round triangle hide'); + + for (i = 0; i < this.ticks.length; i++) { + this._removeClass(this.ticks[i], 'round triangle hide'); + } + } + + var availableHandleModifiers = ['round', 'triangle', 'custom']; + var isValidHandleType = availableHandleModifiers.indexOf(this.options.handle) !== -1; + if (isValidHandleType) { + this._addClass(this.handle1, this.options.handle); + this._addClass(this.handle2, this.options.handle); + + for (i = 0; i < this.ticks.length; i++) { + this._addClass(this.ticks[i], this.options.handle); + } + } + + this._state.offset = this._offset(this.sliderElem); + this._state.size = this.sliderElem[this.sizePos]; + this.setValue(this._state.value); + + /****************************************** + + Bind Event Listeners + + ******************************************/ + + // Bind keyboard handlers + this.handle1Keydown = this._keydown.bind(this, 0); + this.handle1.addEventListener("keydown", this.handle1Keydown, false); + + this.handle2Keydown = this._keydown.bind(this, 1); + this.handle2.addEventListener("keydown", this.handle2Keydown, false); + + this.mousedown = this._mousedown.bind(this); + if (this.touchCapable) { + // Bind touch handlers + this.sliderElem.addEventListener("touchstart", this.mousedown, false); + } + this.sliderElem.addEventListener("mousedown", this.mousedown, false); + + + // Bind tooltip-related handlers + if(this.options.tooltip === 'hide') { + this._addClass(this.tooltip, 'hide'); + this._addClass(this.tooltip_min, 'hide'); + this._addClass(this.tooltip_max, 'hide'); + } + else if(this.options.tooltip === 'always') { + this._showTooltip(); + this._alwaysShowTooltip = true; + } + else { + this.showTooltip = this._showTooltip.bind(this); + this.hideTooltip = this._hideTooltip.bind(this); + + this.sliderElem.addEventListener("mouseenter", this.showTooltip, false); + this.sliderElem.addEventListener("mouseleave", this.hideTooltip, false); + + this.handle1.addEventListener("focus", this.showTooltip, false); + this.handle1.addEventListener("blur", this.hideTooltip, false); + + this.handle2.addEventListener("focus", this.showTooltip, false); + this.handle2.addEventListener("blur", this.hideTooltip, false); + } + + if(this.options.enabled) { + this.enable(); + } else { + this.disable(); + } + } + + + + /************************************************* + + INSTANCE PROPERTIES/METHODS + + - Any methods bound to the prototype are considered + part of the plugin's `public` interface + + **************************************************/ + Slider.prototype = { + _init: function() {}, // NOTE: Must exist to support bridget + + constructor: Slider, + + defaultOptions: { + id: "", + min: 0, + max: 10, + step: 1, + precision: 0, + orientation: 'horizontal', + value: 5, + range: false, + selection: 'before', + tooltip: 'show', + tooltip_split: false, + handle: 'round', + reversed: false, + enabled: true, + formatter: function(val) { + if (Array.isArray(val)) { + return val[0] + " : " + val[1]; + } else { + return val; + } + }, + natural_arrow_keys: false, + ticks: [], + ticks_positions: [], + ticks_labels: [], + ticks_snap_bounds: 0, + scale: 'linear', + focus: false, + tooltip_position: null + }, + + getElement: function() { + return this.sliderElem; + }, + + getValue: function() { + if (this.options.range) { + return this._state.value; + } + else { + return this._state.value[0]; + } + }, + + setValue: function(val, triggerSlideEvent, triggerChangeEvent) { + if (!val) { + val = 0; + } + var oldValue = this.getValue(); + this._state.value = this._validateInputValue(val); + var applyPrecision = this._applyPrecision.bind(this); + + if (this.options.range) { + this._state.value[0] = applyPrecision(this._state.value[0]); + this._state.value[1] = applyPrecision(this._state.value[1]); + + this._state.value[0] = Math.max(this.options.min, Math.min(this.options.max, this._state.value[0])); + this._state.value[1] = Math.max(this.options.min, Math.min(this.options.max, this._state.value[1])); + } + else { + this._state.value = applyPrecision(this._state.value); + this._state.value = [ Math.max(this.options.min, Math.min(this.options.max, this._state.value))]; + this._addClass(this.handle2, 'hide'); + if (this.options.selection === 'after') { + this._state.value[1] = this.options.max; + } else { + this._state.value[1] = this.options.min; + } + } + + if (this.options.max > this.options.min) { + this._state.percentage = [ + this._toPercentage(this._state.value[0]), + this._toPercentage(this._state.value[1]), + this.options.step * 100 / (this.options.max - this.options.min) + ]; + } else { + this._state.percentage = [0, 0, 100]; + } + + this._layout(); + var newValue = this.options.range ? this._state.value : this._state.value[0]; + + if(triggerSlideEvent === true) { + this._trigger('slide', newValue); + } + if( (oldValue !== newValue) && (triggerChangeEvent === true) ) { + this._trigger('change', { + oldValue: oldValue, + newValue: newValue + }); + } + this._setDataVal(newValue); + + return this; + }, + + destroy: function(){ + // Remove event handlers on slider elements + this._removeSliderEventHandlers(); + + // Remove the slider from the DOM + this.sliderElem.parentNode.removeChild(this.sliderElem); + /* Show original element */ + this.element.style.display = ""; + + // Clear out custom event bindings + this._cleanUpEventCallbacksMap(); + + // Remove data values + this.element.removeAttribute("data"); + + // Remove JQuery handlers/data + if($) { + this._unbindJQueryEventHandlers(); + this.$element.removeData('slider'); + } + }, + + disable: function() { + this._state.enabled = false; + this.handle1.removeAttribute("tabindex"); + this.handle2.removeAttribute("tabindex"); + this._addClass(this.sliderElem, 'slider-disabled'); + this._trigger('slideDisabled'); + + return this; + }, + + enable: function() { + this._state.enabled = true; + this.handle1.setAttribute("tabindex", 0); + this.handle2.setAttribute("tabindex", 0); + this._removeClass(this.sliderElem, 'slider-disabled'); + this._trigger('slideEnabled'); + + return this; + }, + + toggle: function() { + if(this._state.enabled) { + this.disable(); + } else { + this.enable(); + } + return this; + }, + + isEnabled: function() { + return this._state.enabled; + }, + + on: function(evt, callback) { + this._bindNonQueryEventHandler(evt, callback); + return this; + }, + + off: function(evt, callback) { + if($) { + this.$element.off(evt, callback); + this.$sliderElem.off(evt, callback); + } else { + this._unbindNonQueryEventHandler(evt, callback); + } + }, + + getAttribute: function(attribute) { + if(attribute) { + return this.options[attribute]; + } else { + return this.options; + } + }, + + setAttribute: function(attribute, value) { + this.options[attribute] = value; + return this; + }, + + refresh: function() { + this._removeSliderEventHandlers(); + createNewSlider.call(this, this.element, this.options); + if($) { + // Bind new instance of slider to the element + $.data(this.element, 'slider', this); + } + return this; + }, + + relayout: function() { + this._layout(); + return this; + }, + + /******************************+ + + HELPERS + + - Any method that is not part of the public interface. + - Place it underneath this comment block and write its signature like so: + + _fnName : function() {...} + + ********************************/ + _removeSliderEventHandlers: function() { + // Remove keydown event listeners + this.handle1.removeEventListener("keydown", this.handle1Keydown, false); + this.handle2.removeEventListener("keydown", this.handle2Keydown, false); + + if (this.showTooltip) { + this.handle1.removeEventListener("focus", this.showTooltip, false); + this.handle2.removeEventListener("focus", this.showTooltip, false); + } + if (this.hideTooltip) { + this.handle1.removeEventListener("blur", this.hideTooltip, false); + this.handle2.removeEventListener("blur", this.hideTooltip, false); + } + + // Remove event listeners from sliderElem + if (this.showTooltip) { + this.sliderElem.removeEventListener("mouseenter", this.showTooltip, false); + } + if (this.hideTooltip) { + this.sliderElem.removeEventListener("mouseleave", this.hideTooltip, false); + } + this.sliderElem.removeEventListener("touchstart", this.mousedown, false); + this.sliderElem.removeEventListener("mousedown", this.mousedown, false); + }, + _bindNonQueryEventHandler: function(evt, callback) { + if(this.eventToCallbackMap[evt] === undefined) { + this.eventToCallbackMap[evt] = []; + } + this.eventToCallbackMap[evt].push(callback); + }, + _unbindNonQueryEventHandler: function(evt, callback) { + var callbacks = this.eventToCallbackMap[evt]; + if(callbacks !== undefined) { + for (var i = 0; i < callbacks.length; i++) { + if (callbacks[i] === callback) { + callbacks.splice(i, 1); + break; + } + } + } + }, + _cleanUpEventCallbacksMap: function() { + var eventNames = Object.keys(this.eventToCallbackMap); + for(var i = 0; i < eventNames.length; i++) { + var eventName = eventNames[i]; + this.eventToCallbackMap[eventName] = null; + } + }, + _showTooltip: function() { + if (this.options.tooltip_split === false ){ + this._addClass(this.tooltip, 'in'); + this.tooltip_min.style.display = 'none'; + this.tooltip_max.style.display = 'none'; + } else { + this._addClass(this.tooltip_min, 'in'); + this._addClass(this.tooltip_max, 'in'); + this.tooltip.style.display = 'none'; + } + this._state.over = true; + }, + _hideTooltip: function() { + if (this._state.inDrag === false && this.alwaysShowTooltip !== true) { + this._removeClass(this.tooltip, 'in'); + this._removeClass(this.tooltip_min, 'in'); + this._removeClass(this.tooltip_max, 'in'); + } + this._state.over = false; + }, + _layout: function() { + var positionPercentages; + + if(this.options.reversed) { + positionPercentages = [ 100 - this._state.percentage[0], this.options.range ? 100 - this._state.percentage[1] : this._state.percentage[1]]; + } + else { + positionPercentages = [ this._state.percentage[0], this._state.percentage[1] ]; + } + + this.handle1.style[this.stylePos] = positionPercentages[0]+'%'; + this.handle2.style[this.stylePos] = positionPercentages[1]+'%'; + + /* Position ticks and labels */ + if (Array.isArray(this.options.ticks) && this.options.ticks.length > 0) { + + var styleSize = this.options.orientation === 'vertical' ? 'height' : 'width'; + var styleMargin = this.options.orientation === 'vertical' ? 'marginTop' : 'marginLeft'; + var labelSize = this._state.size / (this.options.ticks.length - 1); + + if (this.tickLabelContainer) { + var extraMargin = 0; + if (this.options.ticks_positions.length === 0) { + if (this.options.orientation !== 'vertical') { + this.tickLabelContainer.style[styleMargin] = -labelSize/2 + 'px'; + } + + extraMargin = this.tickLabelContainer.offsetHeight; + } else { + /* Chidren are position absolute, calculate height by finding the max offsetHeight of a child */ + for (i = 0 ; i < this.tickLabelContainer.childNodes.length; i++) { + if (this.tickLabelContainer.childNodes[i].offsetHeight > extraMargin) { + extraMargin = this.tickLabelContainer.childNodes[i].offsetHeight; + } + } + } + if (this.options.orientation === 'horizontal') { + this.sliderElem.style.marginBottom = extraMargin + 'px'; + } + } + for (var i = 0; i < this.options.ticks.length; i++) { + + var percentage = this.options.ticks_positions[i] || this._toPercentage(this.options.ticks[i]); + + if (this.options.reversed) { + percentage = 100 - percentage; + } + + this.ticks[i].style[this.stylePos] = percentage + '%'; + + /* Set class labels to denote whether ticks are in the selection */ + this._removeClass(this.ticks[i], 'in-selection'); + if (!this.options.range) { + if (this.options.selection === 'after' && percentage >= positionPercentages[0]){ + this._addClass(this.ticks[i], 'in-selection'); + } else if (this.options.selection === 'before' && percentage <= positionPercentages[0]) { + this._addClass(this.ticks[i], 'in-selection'); + } + } else if (percentage >= positionPercentages[0] && percentage <= positionPercentages[1]) { + this._addClass(this.ticks[i], 'in-selection'); + } + + if (this.tickLabels[i]) { + this.tickLabels[i].style[styleSize] = labelSize + 'px'; + + if (this.options.orientation !== 'vertical' && this.options.ticks_positions[i] !== undefined) { + this.tickLabels[i].style.position = 'absolute'; + this.tickLabels[i].style[this.stylePos] = percentage + '%'; + this.tickLabels[i].style[styleMargin] = -labelSize/2 + 'px'; + } else if (this.options.orientation === 'vertical') { + this.tickLabels[i].style['marginLeft'] = this.sliderElem.offsetWidth + 'px'; + this.tickLabelContainer.style['marginTop'] = this.sliderElem.offsetWidth / 2 * -1 + 'px'; + } + } + } + } + + var formattedTooltipVal; + + if (this.options.range) { + formattedTooltipVal = this.options.formatter(this._state.value); + this._setText(this.tooltipInner, formattedTooltipVal); + this.tooltip.style[this.stylePos] = (positionPercentages[1] + positionPercentages[0])/2 + '%'; + + if (this.options.orientation === 'vertical') { + this._css(this.tooltip, 'margin-top', -this.tooltip.offsetHeight / 2 + 'px'); + } else { + this._css(this.tooltip, 'margin-left', -this.tooltip.offsetWidth / 2 + 'px'); + } + + if (this.options.orientation === 'vertical') { + this._css(this.tooltip, 'margin-top', -this.tooltip.offsetHeight / 2 + 'px'); + } else { + this._css(this.tooltip, 'margin-left', -this.tooltip.offsetWidth / 2 + 'px'); + } + + var innerTooltipMinText = this.options.formatter(this._state.value[0]); + this._setText(this.tooltipInner_min, innerTooltipMinText); + + var innerTooltipMaxText = this.options.formatter(this._state.value[1]); + this._setText(this.tooltipInner_max, innerTooltipMaxText); + + this.tooltip_min.style[this.stylePos] = positionPercentages[0] + '%'; + + if (this.options.orientation === 'vertical') { + this._css(this.tooltip_min, 'margin-top', -this.tooltip_min.offsetHeight / 2 + 'px'); + } else { + this._css(this.tooltip_min, 'margin-left', -this.tooltip_min.offsetWidth / 2 + 'px'); + } + + this.tooltip_max.style[this.stylePos] = positionPercentages[1] + '%'; + + if (this.options.orientation === 'vertical') { + this._css(this.tooltip_max, 'margin-top', -this.tooltip_max.offsetHeight / 2 + 'px'); + } else { + this._css(this.tooltip_max, 'margin-left', -this.tooltip_max.offsetWidth / 2 + 'px'); + } + } else { + formattedTooltipVal = this.options.formatter(this._state.value[0]); + this._setText(this.tooltipInner, formattedTooltipVal); + + this.tooltip.style[this.stylePos] = positionPercentages[0] + '%'; + if (this.options.orientation === 'vertical') { + this._css(this.tooltip, 'margin-top', -this.tooltip.offsetHeight / 2 + 'px'); + } else { + this._css(this.tooltip, 'margin-left', -this.tooltip.offsetWidth / 2 + 'px'); + } + } + + if (this.options.orientation === 'vertical') { + this.trackLow.style.top = '0'; + this.trackLow.style.height = Math.min(positionPercentages[0], positionPercentages[1]) +'%'; + + this.trackSelection.style.top = Math.min(positionPercentages[0], positionPercentages[1]) +'%'; + this.trackSelection.style.height = Math.abs(positionPercentages[0] - positionPercentages[1]) +'%'; + + this.trackHigh.style.bottom = '0'; + this.trackHigh.style.height = (100 - Math.min(positionPercentages[0], positionPercentages[1]) - Math.abs(positionPercentages[0] - positionPercentages[1])) +'%'; + } + else { + this.trackLow.style.left = '0'; + this.trackLow.style.width = Math.min(positionPercentages[0], positionPercentages[1]) +'%'; + + this.trackSelection.style.left = Math.min(positionPercentages[0], positionPercentages[1]) +'%'; + this.trackSelection.style.width = Math.abs(positionPercentages[0] - positionPercentages[1]) +'%'; + + this.trackHigh.style.right = '0'; + this.trackHigh.style.width = (100 - Math.min(positionPercentages[0], positionPercentages[1]) - Math.abs(positionPercentages[0] - positionPercentages[1])) +'%'; + + var offset_min = this.tooltip_min.getBoundingClientRect(); + var offset_max = this.tooltip_max.getBoundingClientRect(); + + if (offset_min.right > offset_max.left) { + this._removeClass(this.tooltip_max, 'top'); + this._addClass(this.tooltip_max, 'bottom'); + this.tooltip_max.style.top = 18 + 'px'; + } else { + this._removeClass(this.tooltip_max, 'bottom'); + this._addClass(this.tooltip_max, 'top'); + this.tooltip_max.style.top = this.tooltip_min.style.top; + } + } + }, + _removeProperty: function(element, prop) { + if (element.style.removeProperty) { + element.style.removeProperty(prop); + } else { + element.style.removeAttribute(prop); + } + }, + _mousedown: function(ev) { + if(!this._state.enabled) { + return false; + } + + this._state.offset = this._offset(this.sliderElem); + this._state.size = this.sliderElem[this.sizePos]; + + var percentage = this._getPercentage(ev); + + if (this.options.range) { + var diff1 = Math.abs(this._state.percentage[0] - percentage); + var diff2 = Math.abs(this._state.percentage[1] - percentage); + this._state.dragged = (diff1 < diff2) ? 0 : 1; + } else { + this._state.dragged = 0; + } + + this._state.percentage[this._state.dragged] = percentage; + this._layout(); + + if (this.touchCapable) { + document.removeEventListener("touchmove", this.mousemove, false); + document.removeEventListener("touchend", this.mouseup, false); + } + + if(this.mousemove){ + document.removeEventListener("mousemove", this.mousemove, false); + } + if(this.mouseup){ + document.removeEventListener("mouseup", this.mouseup, false); + } + + this.mousemove = this._mousemove.bind(this); + this.mouseup = this._mouseup.bind(this); + + if (this.touchCapable) { + // Touch: Bind touch events: + document.addEventListener("touchmove", this.mousemove, false); + document.addEventListener("touchend", this.mouseup, false); + } + // Bind mouse events: + document.addEventListener("mousemove", this.mousemove, false); + document.addEventListener("mouseup", this.mouseup, false); + + this._state.inDrag = true; + var newValue = this._calculateValue(); + + this._trigger('slideStart', newValue); + + this._setDataVal(newValue); + this.setValue(newValue, false, true); + + this._pauseEvent(ev); + + if (this.options.focus) { + this._triggerFocusOnHandle(this._state.dragged); + } + + return true; + }, + _triggerFocusOnHandle: function(handleIdx) { + if(handleIdx === 0) { + this.handle1.focus(); + } + if(handleIdx === 1) { + this.handle2.focus(); + } + }, + _keydown: function(handleIdx, ev) { + if(!this._state.enabled) { + return false; + } + + var dir; + switch (ev.keyCode) { + case 37: // left + case 40: // down + dir = -1; + break; + case 39: // right + case 38: // up + dir = 1; + break; + } + if (!dir) { + return; + } + + // use natural arrow keys instead of from min to max + if (this.options.natural_arrow_keys) { + var ifVerticalAndNotReversed = (this.options.orientation === 'vertical' && !this.options.reversed); + var ifHorizontalAndReversed = (this.options.orientation === 'horizontal' && this.options.reversed); + + if (ifVerticalAndNotReversed || ifHorizontalAndReversed) { + dir = -dir; + } + } + + var val = this._state.value[handleIdx] + dir * this.options.step; + if (this.options.range) { + val = [ (!handleIdx) ? val : this._state.value[0], + ( handleIdx) ? val : this._state.value[1]]; + } + + this._trigger('slideStart', val); + this._setDataVal(val); + this.setValue(val, true, true); + + this._setDataVal(val); + this._trigger('slideStop', val); + this._layout(); + + this._pauseEvent(ev); + + return false; + }, + _pauseEvent: function(ev) { + if(ev.stopPropagation) { + ev.stopPropagation(); + } + if(ev.preventDefault) { + ev.preventDefault(); + } + ev.cancelBubble=true; + ev.returnValue=false; + }, + _mousemove: function(ev) { + if(!this._state.enabled) { + return false; + } + + var percentage = this._getPercentage(ev); + this._adjustPercentageForRangeSliders(percentage); + this._state.percentage[this._state.dragged] = percentage; + this._layout(); + + var val = this._calculateValue(true); + this.setValue(val, true, true); + + return false; + }, + _adjustPercentageForRangeSliders: function(percentage) { + if (this.options.range) { + var precision = this._getNumDigitsAfterDecimalPlace(percentage); + precision = precision ? precision - 1 : 0; + var percentageWithAdjustedPrecision = this._applyToFixedAndParseFloat(percentage, precision); + if (this._state.dragged === 0 && this._applyToFixedAndParseFloat(this._state.percentage[1], precision) < percentageWithAdjustedPrecision) { + this._state.percentage[0] = this._state.percentage[1]; + this._state.dragged = 1; + } else if (this._state.dragged === 1 && this._applyToFixedAndParseFloat(this._state.percentage[0], precision) > percentageWithAdjustedPrecision) { + this._state.percentage[1] = this._state.percentage[0]; + this._state.dragged = 0; + } + } + }, + _mouseup: function() { + if(!this._state.enabled) { + return false; + } + if (this.touchCapable) { + // Touch: Unbind touch event handlers: + document.removeEventListener("touchmove", this.mousemove, false); + document.removeEventListener("touchend", this.mouseup, false); + } + // Unbind mouse event handlers: + document.removeEventListener("mousemove", this.mousemove, false); + document.removeEventListener("mouseup", this.mouseup, false); + + this._state.inDrag = false; + if (this._state.over === false) { + this._hideTooltip(); + } + var val = this._calculateValue(true); + + this._layout(); + this._setDataVal(val); + this._trigger('slideStop', val); + + return false; + }, + _calculateValue: function(snapToClosestTick) { + var val; + if (this.options.range) { + val = [this.options.min,this.options.max]; + if (this._state.percentage[0] !== 0){ + val[0] = this._toValue(this._state.percentage[0]); + val[0] = this._applyPrecision(val[0]); + } + if (this._state.percentage[1] !== 100){ + val[1] = this._toValue(this._state.percentage[1]); + val[1] = this._applyPrecision(val[1]); + } + } else { + val = this._toValue(this._state.percentage[0]); + val = parseFloat(val); + val = this._applyPrecision(val); + } + + if (snapToClosestTick) { + var min = [val, Infinity]; + for (var i = 0; i < this.options.ticks.length; i++) { + var diff = Math.abs(this.options.ticks[i] - val); + if (diff <= min[1]) { + min = [this.options.ticks[i], diff]; + } + } + if (min[1] <= this.options.ticks_snap_bounds) { + return min[0]; + } + } + + return val; + }, + _applyPrecision: function(val) { + var precision = this.options.precision || this._getNumDigitsAfterDecimalPlace(this.options.step); + return this._applyToFixedAndParseFloat(val, precision); + }, + _getNumDigitsAfterDecimalPlace: function(num) { + var match = (''+num).match(/(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/); + if (!match) { return 0; } + return Math.max(0, (match[1] ? match[1].length : 0) - (match[2] ? +match[2] : 0)); + }, + _applyToFixedAndParseFloat: function(num, toFixedInput) { + var truncatedNum = num.toFixed(toFixedInput); + return parseFloat(truncatedNum); + }, + /* + Credits to Mike Samuel for the following method! + Source: http://stackoverflow.com/questions/10454518/javascript-how-to-retrieve-the-number-of-decimals-of-a-string-number + */ + _getPercentage: function(ev) { + if (this.touchCapable && (ev.type === 'touchstart' || ev.type === 'touchmove')) { + ev = ev.touches[0]; + } + + var eventPosition = ev[this.mousePos]; + var sliderOffset = this._state.offset[this.stylePos]; + var distanceToSlide = eventPosition - sliderOffset; + // Calculate what percent of the length the slider handle has slid + var percentage = (distanceToSlide / this._state.size) * 100; + percentage = Math.round(percentage / this._state.percentage[2]) * this._state.percentage[2]; + if (this.options.reversed) { + percentage = 100 - percentage; + } + + // Make sure the percent is within the bounds of the slider. + // 0% corresponds to the 'min' value of the slide + // 100% corresponds to the 'max' value of the slide + return Math.max(0, Math.min(100, percentage)); + }, + _validateInputValue: function(val) { + if (typeof val === 'number') { + return val; + } else if (Array.isArray(val)) { + this._validateArray(val); + return val; + } else { + throw new Error( ErrorMsgs.formatInvalidInputErrorMsg(val) ); + } + }, + _validateArray: function(val) { + for(var i = 0; i < val.length; i++) { + var input = val[i]; + if (typeof input !== 'number') { throw new Error( ErrorMsgs.formatInvalidInputErrorMsg(input) ); } + } + }, + _setDataVal: function(val) { + this.element.setAttribute('data-value', val); + this.element.setAttribute('value', val); + this.element.value = val; + }, + _trigger: function(evt, val) { + val = (val || val === 0) ? val : undefined; + + var callbackFnArray = this.eventToCallbackMap[evt]; + if(callbackFnArray && callbackFnArray.length) { + for(var i = 0; i < callbackFnArray.length; i++) { + var callbackFn = callbackFnArray[i]; + callbackFn(val); + } + } + + /* If JQuery exists, trigger JQuery events */ + if($) { + this._triggerJQueryEvent(evt, val); + } + }, + _triggerJQueryEvent: function(evt, val) { + var eventData = { + type: evt, + value: val + }; + this.$element.trigger(eventData); + this.$sliderElem.trigger(eventData); + }, + _unbindJQueryEventHandlers: function() { + this.$element.off(); + this.$sliderElem.off(); + }, + _setText: function(element, text) { + if(typeof element.innerText !== "undefined") { + element.innerText = text; + } else if(typeof element.textContent !== "undefined") { + element.textContent = text; + } + }, + _removeClass: function(element, classString) { + var classes = classString.split(" "); + var newClasses = element.className; + + for(var i = 0; i < classes.length; i++) { + var classTag = classes[i]; + var regex = new RegExp("(?:\\s|^)" + classTag + "(?:\\s|$)"); + newClasses = newClasses.replace(regex, " "); + } + + element.className = newClasses.trim(); + }, + _addClass: function(element, classString) { + var classes = classString.split(" "); + var newClasses = element.className; + + for(var i = 0; i < classes.length; i++) { + var classTag = classes[i]; + var regex = new RegExp("(?:\\s|^)" + classTag + "(?:\\s|$)"); + var ifClassExists = regex.test(newClasses); + + if(!ifClassExists) { + newClasses += " " + classTag; + } + } + + element.className = newClasses.trim(); + }, + _offsetLeft: function(obj){ + return obj.getBoundingClientRect().left; + }, + _offsetTop: function(obj){ + var offsetTop = obj.offsetTop; + while((obj = obj.offsetParent) && !isNaN(obj.offsetTop)){ + offsetTop += obj.offsetTop; + } + return offsetTop; + }, + _offset: function (obj) { + return { + left: this._offsetLeft(obj), + top: this._offsetTop(obj) + }; + }, + _css: function(elementRef, styleName, value) { + if ($) { + $.style(elementRef, styleName, value); + } else { + var style = styleName.replace(/^-ms-/, "ms-").replace(/-([\da-z])/gi, function (all, letter) { + return letter.toUpperCase(); + }); + elementRef.style[style] = value; + } + }, + _toValue: function(percentage) { + return this.options.scale.toValue.apply(this, [percentage]); + }, + _toPercentage: function(value) { + return this.options.scale.toPercentage.apply(this, [value]); + }, + _setTooltipPosition: function(){ + var tooltips = [this.tooltip, this.tooltip_min, this.tooltip_max]; + if (this.options.orientation === 'vertical'){ + var tooltipPos = this.options.tooltip_position || 'right'; + var oppositeSide = (tooltipPos === 'left') ? 'right' : 'left'; + tooltips.forEach(function(tooltip){ + this._addClass(tooltip, tooltipPos); + tooltip.style[oppositeSide] = '100%'; + }.bind(this)); + } else if(this.options.tooltip_position === 'bottom') { + tooltips.forEach(function(tooltip){ + this._addClass(tooltip, 'bottom'); + tooltip.style.top = 22 + 'px'; + }.bind(this)); + } else { + tooltips.forEach(function(tooltip){ + this._addClass(tooltip, 'top'); + tooltip.style.top = -this.tooltip.outerHeight - 14 + 'px'; + }.bind(this)); + } + } + }; + + /********************************* + + Attach to global namespace + + *********************************/ + if($) { + var namespace = $.fn.slider ? 'bootstrapSlider' : 'slider'; + $.bridget(namespace, Slider); + } + + })( $ ); + + return Slider; +})); diff --git a/src/Umbraco.Web.UI.Client/lib/slider/slider.css b/src/Umbraco.Web.UI.Client/lib/slider/slider.css deleted file mode 100644 index 8cbc16cf85..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/slider/slider.css +++ /dev/null @@ -1,138 +0,0 @@ -/*! - * Slider for Bootstrap - * - * Copyright 2012 Stefan Petre - * Licensed under the Apache License v2.0 - * http://www.apache.org/licenses/LICENSE-2.0 - * - */ -.slider { - display: inline-block; - vertical-align: middle; - position: relative; -} -.slider.slider-horizontal { - width: 210px; - height: 20px; -} -.slider.slider-horizontal .slider-track { - height: 10px; - width: 100%; - margin-top: -5px; - top: 50%; - left: 0; -} -.slider.slider-horizontal .slider-selection { - height: 100%; - top: 0; - bottom: 0; -} -.slider.slider-horizontal .slider-handle { - margin-left: -10px; - margin-top: -5px; -} -.slider.slider-horizontal .slider-handle.triangle { - border-width: 0 10px 10px 10px; - width: 0; - height: 0; - border-bottom-color: #0480be; - margin-top: 0; -} -.slider.slider-vertical { - height: 210px; - width: 20px; -} -.slider.slider-vertical .slider-track { - width: 10px; - height: 100%; - margin-left: -5px; - left: 50%; - top: 0; -} -.slider.slider-vertical .slider-selection { - width: 100%; - left: 0; - top: 0; - bottom: 0; -} -.slider.slider-vertical .slider-handle { - margin-left: -5px; - margin-top: -10px; -} -.slider.slider-vertical .slider-handle.triangle { - border-width: 10px 0 10px 10px; - width: 1px; - height: 1px; - border-left-color: #0480be; - margin-left: 0; -} -.slider input { - display: none; -} -.slider .tooltip-inner { - white-space: nowrap; -} -.slider-track { - position: absolute; - cursor: pointer; - background-color: #f7f7f7; - background-image: -moz-linear-gradient(top, #f5f5f5, #f9f9f9); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f5f5f5), to(#f9f9f9)); - background-image: -webkit-linear-gradient(top, #f5f5f5, #f9f9f9); - background-image: -o-linear-gradient(top, #f5f5f5, #f9f9f9); - background-image: linear-gradient(to bottom, #f5f5f5, #f9f9f9); - background-repeat: repeat-x; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#fff9f9f9', GradientType=0); - -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); - -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); - box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; -} -.slider-selection { - position: absolute; - background-color: #f7f7f7; - background-image: -moz-linear-gradient(top, #f9f9f9, #f5f5f5); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f9f9f9), to(#f5f5f5)); - background-image: -webkit-linear-gradient(top, #f9f9f9, #f5f5f5); - background-image: -o-linear-gradient(top, #f9f9f9, #f5f5f5); - background-image: linear-gradient(to bottom, #f9f9f9, #f5f5f5); - background-repeat: repeat-x; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff9f9f9', endColorstr='#fff5f5f5', GradientType=0); - -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); - -moz-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); - box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; -} -.slider-handle { - position: absolute; - width: 20px; - height: 20px; - background-color: #0e90d2; - background-image: -moz-linear-gradient(top, #149bdf, #0480be); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#149bdf), to(#0480be)); - background-image: -webkit-linear-gradient(top, #149bdf, #0480be); - background-image: -o-linear-gradient(top, #149bdf, #0480be); - background-image: linear-gradient(to bottom, #149bdf, #0480be); - background-repeat: repeat-x; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf', endColorstr='#ff0480be', GradientType=0); - -webkit-box-shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05); - -moz-box-shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05); - box-shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05); - opacity: 0.8; - border: 0px solid transparent; -} -.slider-handle.round { - -webkit-border-radius: 20px; - -moz-border-radius: 20px; - border-radius: 20px; -} -.slider-handle.triangle { - background: transparent none; -} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/readme.md b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/readme.md deleted file mode 100755 index a52bf03f9a..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/readme.md +++ /dev/null @@ -1,3 +0,0 @@ -This is where language files should be placed. - -Please DO NOT translate these directly use this service: https://www.transifex.com/projects/p/tinymce/ diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/license.txt b/src/Umbraco.Web.UI.Client/lib/tinymce/license.txt deleted file mode 100755 index 1837b0acbe..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/license.txt +++ /dev/null @@ -1,504 +0,0 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 2.1, February 1999 - - Copyright (C) 1991, 1999 Free Software Foundation, Inc. - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - -[This is the first released version of the Lesser GPL. It also counts - as the successor of the GNU Library Public License, version 2, hence - the version number 2.1.] - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -Licenses are intended to guarantee your freedom to share and change -free software--to make sure the software is free for all its users. - - This license, the Lesser General Public License, applies to some -specially designated software packages--typically libraries--of the -Free Software Foundation and other authors who decide to use it. You -can use it too, but we suggest you first think carefully about whether -this license or the ordinary General Public License is the better -strategy to use in any particular case, based on the explanations below. - - When we speak of free software, we are referring to freedom of use, -not price. Our General Public Licenses are designed to make sure that -you have the freedom to distribute copies of free software (and charge -for this service if you wish); that you receive source code or can get -it if you want it; that you can change the software and use pieces of -it in new free programs; and that you are informed that you can do -these things. - - To protect your rights, we need to make restrictions that forbid -distributors to deny you these rights or to ask you to surrender these -rights. These restrictions translate to certain responsibilities for -you if you distribute copies of the library or if you modify it. - - For example, if you distribute copies of the library, whether gratis -or for a fee, you must give the recipients all the rights that we gave -you. You must make sure that they, too, receive or can get the source -code. If you link other code with the library, you must provide -complete object files to the recipients, so that they can relink them -with the library after making changes to the library and recompiling -it. And you must show them these terms so they know their rights. - - We protect your rights with a two-step method: (1) we copyright the -library, and (2) we offer you this license, which gives you legal -permission to copy, distribute and/or modify the library. - - To protect each distributor, we want to make it very clear that -there is no warranty for the free library. Also, if the library is -modified by someone else and passed on, the recipients should know -that what they have is not the original version, so that the original -author's reputation will not be affected by problems that might be -introduced by others. - - Finally, software patents pose a constant threat to the existence of -any free program. We wish to make sure that a company cannot -effectively restrict the users of a free program by obtaining a -restrictive license from a patent holder. Therefore, we insist that -any patent license obtained for a version of the library must be -consistent with the full freedom of use specified in this license. - - Most GNU software, including some libraries, is covered by the -ordinary GNU General Public License. This license, the GNU Lesser -General Public License, applies to certain designated libraries, and -is quite different from the ordinary General Public License. We use -this license for certain libraries in order to permit linking those -libraries into non-free programs. - - When a program is linked with a library, whether statically or using -a shared library, the combination of the two is legally speaking a -combined work, a derivative of the original library. The ordinary -General Public License therefore permits such linking only if the -entire combination fits its criteria of freedom. The Lesser General -Public License permits more lax criteria for linking other code with -the library. - - We call this license the "Lesser" General Public License because it -does Less to protect the user's freedom than the ordinary General -Public License. It also provides other free software developers Less -of an advantage over competing non-free programs. These disadvantages -are the reason we use the ordinary General Public License for many -libraries. However, the Lesser license provides advantages in certain -special circumstances. - - For example, on rare occasions, there may be a special need to -encourage the widest possible use of a certain library, so that it becomes -a de-facto standard. To achieve this, non-free programs must be -allowed to use the library. A more frequent case is that a free -library does the same job as widely used non-free libraries. In this -case, there is little to gain by limiting the free library to free -software only, so we use the Lesser General Public License. - - In other cases, permission to use a particular library in non-free -programs enables a greater number of people to use a large body of -free software. For example, permission to use the GNU C Library in -non-free programs enables many more people to use the whole GNU -operating system, as well as its variant, the GNU/Linux operating -system. - - Although the Lesser General Public License is Less protective of the -users' freedom, it does ensure that the user of a program that is -linked with the Library has the freedom and the wherewithal to run -that program using a modified version of the Library. - - The precise terms and conditions for copying, distribution and -modification follow. Pay close attention to the difference between a -"work based on the library" and a "work that uses the library". The -former contains code derived from the library, whereas the latter must -be combined with the library in order to run. - - GNU LESSER GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License Agreement applies to any software library or other -program which contains a notice placed by the copyright holder or -other authorized party saying it may be distributed under the terms of -this Lesser General Public License (also called "this License"). -Each licensee is addressed as "you". - - A "library" means a collection of software functions and/or data -prepared so as to be conveniently linked with application programs -(which use some of those functions and data) to form executables. - - The "Library", below, refers to any such software library or work -which has been distributed under these terms. A "work based on the -Library" means either the Library or any derivative work under -copyright law: that is to say, a work containing the Library or a -portion of it, either verbatim or with modifications and/or translated -straightforwardly into another language. (Hereinafter, translation is -included without limitation in the term "modification".) - - "Source code" for a work means the preferred form of the work for -making modifications to it. For a library, complete source code means -all the source code for all modules it contains, plus any associated -interface definition files, plus the scripts used to control compilation -and installation of the library. - - Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running a program using the Library is not restricted, and output from -such a program is covered only if its contents constitute a work based -on the Library (independent of the use of the Library in a tool for -writing it). Whether that is true depends on what the Library does -and what the program that uses the Library does. - - 1. You may copy and distribute verbatim copies of the Library's -complete source code as you receive it, in any medium, provided that -you conspicuously and appropriately publish on each copy an -appropriate copyright notice and disclaimer of warranty; keep intact -all the notices that refer to this License and to the absence of any -warranty; and distribute a copy of this License along with the -Library. - - You may charge a fee for the physical act of transferring a copy, -and you may at your option offer warranty protection in exchange for a -fee. - - 2. You may modify your copy or copies of the Library or any portion -of it, thus forming a work based on the Library, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) The modified work must itself be a software library. - - b) You must cause the files modified to carry prominent notices - stating that you changed the files and the date of any change. - - c) You must cause the whole of the work to be licensed at no - charge to all third parties under the terms of this License. - - d) If a facility in the modified Library refers to a function or a - table of data to be supplied by an application program that uses - the facility, other than as an argument passed when the facility - is invoked, then you must make a good faith effort to ensure that, - in the event an application does not supply such function or - table, the facility still operates, and performs whatever part of - its purpose remains meaningful. - - (For example, a function in a library to compute square roots has - a purpose that is entirely well-defined independent of the - application. Therefore, Subsection 2d requires that any - application-supplied function or table used by this function must - be optional: if the application does not supply it, the square - root function must still compute square roots.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Library, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Library, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote -it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Library. - -In addition, mere aggregation of another work not based on the Library -with the Library (or with a work based on the Library) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may opt to apply the terms of the ordinary GNU General Public -License instead of this License to a given copy of the Library. To do -this, you must alter all the notices that refer to this License, so -that they refer to the ordinary GNU General Public License, version 2, -instead of to this License. (If a newer version than version 2 of the -ordinary GNU General Public License has appeared, then you can specify -that version instead if you wish.) Do not make any other change in -these notices. - - Once this change is made in a given copy, it is irreversible for -that copy, so the ordinary GNU General Public License applies to all -subsequent copies and derivative works made from that copy. - - This option is useful when you wish to copy part of the code of -the Library into a program that is not a library. - - 4. You may copy and distribute the Library (or a portion or -derivative of it, under Section 2) in object code or executable form -under the terms of Sections 1 and 2 above provided that you accompany -it with the complete corresponding machine-readable source code, which -must be distributed under the terms of Sections 1 and 2 above on a -medium customarily used for software interchange. - - If distribution of object code is made by offering access to copy -from a designated place, then offering equivalent access to copy the -source code from the same place satisfies the requirement to -distribute the source code, even though third parties are not -compelled to copy the source along with the object code. - - 5. A program that contains no derivative of any portion of the -Library, but is designed to work with the Library by being compiled or -linked with it, is called a "work that uses the Library". Such a -work, in isolation, is not a derivative work of the Library, and -therefore falls outside the scope of this License. - - However, linking a "work that uses the Library" with the Library -creates an executable that is a derivative of the Library (because it -contains portions of the Library), rather than a "work that uses the -library". The executable is therefore covered by this License. -Section 6 states terms for distribution of such executables. - - When a "work that uses the Library" uses material from a header file -that is part of the Library, the object code for the work may be a -derivative work of the Library even though the source code is not. -Whether this is true is especially significant if the work can be -linked without the Library, or if the work is itself a library. The -threshold for this to be true is not precisely defined by law. - - If such an object file uses only numerical parameters, data -structure layouts and accessors, and small macros and small inline -functions (ten lines or less in length), then the use of the object -file is unrestricted, regardless of whether it is legally a derivative -work. (Executables containing this object code plus portions of the -Library will still fall under Section 6.) - - Otherwise, if the work is a derivative of the Library, you may -distribute the object code for the work under the terms of Section 6. -Any executables containing that work also fall under Section 6, -whether or not they are linked directly with the Library itself. - - 6. As an exception to the Sections above, you may also combine or -link a "work that uses the Library" with the Library to produce a -work containing portions of the Library, and distribute that work -under terms of your choice, provided that the terms permit -modification of the work for the customer's own use and reverse -engineering for debugging such modifications. - - You must give prominent notice with each copy of the work that the -Library is used in it and that the Library and its use are covered by -this License. You must supply a copy of this License. If the work -during execution displays copyright notices, you must include the -copyright notice for the Library among them, as well as a reference -directing the user to the copy of this License. Also, you must do one -of these things: - - a) Accompany the work with the complete corresponding - machine-readable source code for the Library including whatever - changes were used in the work (which must be distributed under - Sections 1 and 2 above); and, if the work is an executable linked - with the Library, with the complete machine-readable "work that - uses the Library", as object code and/or source code, so that the - user can modify the Library and then relink to produce a modified - executable containing the modified Library. (It is understood - that the user who changes the contents of definitions files in the - Library will not necessarily be able to recompile the application - to use the modified definitions.) - - b) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (1) uses at run time a - copy of the library already present on the user's computer system, - rather than copying library functions into the executable, and (2) - will operate properly with a modified version of the library, if - the user installs one, as long as the modified version is - interface-compatible with the version that the work was made with. - - c) Accompany the work with a written offer, valid for at - least three years, to give the same user the materials - specified in Subsection 6a, above, for a charge no more - than the cost of performing this distribution. - - d) If distribution of the work is made by offering access to copy - from a designated place, offer equivalent access to copy the above - specified materials from the same place. - - e) Verify that the user has already received a copy of these - materials or that you have already sent this user a copy. - - For an executable, the required form of the "work that uses the -Library" must include any data and utility programs needed for -reproducing the executable from it. However, as a special exception, -the materials to be distributed need not include anything that is -normally distributed (in either source or binary form) with the major -components (compiler, kernel, and so on) of the operating system on -which the executable runs, unless that component itself accompanies -the executable. - - It may happen that this requirement contradicts the license -restrictions of other proprietary libraries that do not normally -accompany the operating system. Such a contradiction means you cannot -use both them and the Library together in an executable that you -distribute. - - 7. You may place library facilities that are a work based on the -Library side-by-side in a single library together with other library -facilities not covered by this License, and distribute such a combined -library, provided that the separate distribution of the work based on -the Library and of the other library facilities is otherwise -permitted, and provided that you do these two things: - - a) Accompany the combined library with a copy of the same work - based on the Library, uncombined with any other library - facilities. This must be distributed under the terms of the - Sections above. - - b) Give prominent notice with the combined library of the fact - that part of it is a work based on the Library, and explaining - where to find the accompanying uncombined form of the same work. - - 8. You may not copy, modify, sublicense, link with, or distribute -the Library except as expressly provided under this License. Any -attempt otherwise to copy, modify, sublicense, link with, or -distribute the Library is void, and will automatically terminate your -rights under this License. However, parties who have received copies, -or rights, from you under this License will not have their licenses -terminated so long as such parties remain in full compliance. - - 9. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Library or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Library (or any work based on the -Library), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Library or works based on it. - - 10. Each time you redistribute the Library (or any work based on the -Library), the recipient automatically receives a license from the -original licensor to copy, distribute, link with or modify the Library -subject to these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties with -this License. - - 11. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Library at all. For example, if a patent -license would not permit royalty-free redistribution of the Library by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Library. - -If any portion of this section is held invalid or unenforceable under any -particular circumstance, the balance of the section is intended to apply, -and the section as a whole is intended to apply in other circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 12. If the distribution and/or use of the Library is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Library under this License may add -an explicit geographical distribution limitation excluding those countries, -so that distribution is permitted only in or among countries not thus -excluded. In such case, this License incorporates the limitation as if -written in the body of this License. - - 13. The Free Software Foundation may publish revised and/or new -versions of the Lesser General Public License from time to time. -Such new versions will be similar in spirit to the present version, -but may differ in detail to address new problems or concerns. - -Each version is given a distinguishing version number. If the Library -specifies a version number of this License which applies to it and -"any later version", you have the option of following the terms and -conditions either of that version or of any later version published by -the Free Software Foundation. If the Library does not specify a -license version number, you may choose any version ever published by -the Free Software Foundation. - - 14. If you wish to incorporate parts of the Library into other free -programs whose distribution conditions are incompatible with these, -write to the author to ask for permission. For software which is -copyrighted by the Free Software Foundation, write to the Free -Software Foundation; we sometimes make exceptions for this. Our -decision will be guided by the two goals of preserving the free status -of all derivatives of our free software and of promoting the sharing -and reuse of software generally. - - NO WARRANTY - - 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO -WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. -EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR -OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY -KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE -LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME -THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN -WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY -AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU -FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR -CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE -LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING -RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A -FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF -SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH -DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Libraries - - If you develop a new library, and you want it to be of the greatest -possible use to the public, we recommend making it free software that -everyone can redistribute and change. You can do so by permitting -redistribution under these terms (or, alternatively, under the terms of the -ordinary General Public License). - - To apply these terms, attach the following notices to the library. It is -safest to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least the -"copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - -Also add information on how to contact you by electronic and paper mail. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the library, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the - library `Frob' (a library for tweaking knobs) written by James Random Hacker. - - , 1 April 1990 - Ty Coon, President of Vice - -That's all there is to it! - - diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/advlist/plugin.min.js b/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/advlist/plugin.min.js deleted file mode 100755 index da1cdb2ba4..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/advlist/plugin.min.js +++ /dev/null @@ -1 +0,0 @@ -tinymce.PluginManager.add("advlist",function(t){function e(t,e){var n=[];return tinymce.each(e.split(/[ ,]/),function(t){n.push({text:t.replace(/\-/g," ").replace(/\b\w/g,function(t){return t.toUpperCase()}),data:"default"==t?"":t})}),n}function n(e,n){var i,r=t.dom,a=t.selection;i=r.getParent(a.getNode(),"ol,ul"),i&&i.nodeName==e&&n!==!1||t.execCommand("UL"==e?"InsertUnorderedList":"InsertOrderedList"),n=n===!1?o[e]:n,o[e]=n,i=r.getParent(a.getNode(),"ol,ul"),i&&(r.setStyle(i,"listStyleType",n),i.removeAttribute("data-mce-style")),t.focus()}function i(e){var n=t.dom.getStyle(t.dom.getParent(t.selection.getNode(),"ol,ul"),"listStyleType")||"";e.control.items().each(function(t){t.active(t.settings.data===n)})}var r,a,o={};r=e("OL",t.getParam("advlist_number_styles","default,lower-alpha,lower-greek,lower-roman,upper-alpha,upper-roman")),a=e("UL",t.getParam("advlist_bullet_styles","default,circle,disc,square")),t.addButton("numlist",{type:"splitbutton",tooltip:"Numbered list",menu:r,onshow:i,onselect:function(t){n("OL",t.control.settings.data)},onclick:function(){n("OL",!1)}}),t.addButton("bullist",{type:"splitbutton",tooltip:"Bullet list",menu:a,onshow:i,onselect:function(t){n("UL",t.control.settings.data)},onclick:function(){n("UL",!1)}})}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/anchor/plugin.min.js b/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/anchor/plugin.min.js deleted file mode 100755 index 6a3fd792d8..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/anchor/plugin.min.js +++ /dev/null @@ -1 +0,0 @@ -tinymce.PluginManager.add("anchor",function(e){function t(){var t=e.selection.getNode();e.windowManager.open({title:"Anchor",body:{type:"textbox",name:"name",size:40,label:"Name",value:t.name||t.id},onsubmit:function(t){e.execCommand("mceInsertContent",!1,e.dom.createHTML("a",{id:t.data.name}))}})}e.addButton("anchor",{icon:"anchor",tooltip:"Anchor",onclick:t,stateSelector:"a:not([href])"}),e.addMenuItem("anchor",{icon:"anchor",text:"Anchor",context:"insert",onclick:t})}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/autolink/plugin.min.js b/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/autolink/plugin.min.js deleted file mode 100755 index 3d2f58ee66..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/autolink/plugin.min.js +++ /dev/null @@ -1 +0,0 @@ -tinymce.PluginManager.add("autolink",function(t){function e(t){o(t,-1,"(",!0)}function n(t){o(t,0,"",!0)}function i(t){o(t,-1,"",!1)}function o(t,e,n){var i,o,r,a,s,l,c,u,d;if(i=t.selection.getRng(!0).cloneRange(),i.startOffset<5){if(u=i.endContainer.previousSibling,!u){if(!i.endContainer.firstChild||!i.endContainer.firstChild.nextSibling)return;u=i.endContainer.firstChild.nextSibling}if(d=u.length,i.setStart(u,d),i.setEnd(u,d),i.endOffset<5)return;o=i.endOffset,a=u}else{if(a=i.endContainer,3!=a.nodeType&&a.firstChild){for(;3!=a.nodeType&&a.firstChild;)a=a.firstChild;3==a.nodeType&&(i.setStart(a,0),i.setEnd(a,a.nodeValue.length))}o=1==i.endOffset?2:i.endOffset-1-e}r=o;do i.setStart(a,o>=2?o-2:0),i.setEnd(a,o>=1?o-1:0),o-=1;while(" "!=i.toString()&&""!==i.toString()&&160!=i.toString().charCodeAt(0)&&o-2>=0&&i.toString()!=n);if(i.toString()==n||160==i.toString().charCodeAt(0)?(i.setStart(a,o),i.setEnd(a,r),o+=1):0===i.startOffset?(i.setStart(a,0),i.setEnd(a,r)):(i.setStart(a,o),i.setEnd(a,r)),l=i.toString(),"."==l.charAt(l.length-1)&&i.setEnd(a,r-1),l=i.toString(),c=l.match(/^(https?:\/\/|ssh:\/\/|ftp:\/\/|file:\/|www\.|(?:mailto:)?[A-Z0-9._%+\-]+@)(.+)$/i),c&&("www."==c[1]?c[1]="http://www.":/@$/.test(c[1])&&!/^mailto:/.test(c[1])&&(c[1]="mailto:"+c[1]),s=t.selection.getBookmark(),t.selection.setRng(i),t.execCommand("createlink",!1,c[1]+c[2]),t.selection.moveToBookmark(s),t.nodeChanged(),tinymce.Env.webkit)){t.selection.collapse(!1);var m=Math.min(a.length,r+1);i.setStart(a,m),i.setEnd(a,m),t.selection.setRng(i)}}t.on("keydown",function(e){return 13==e.keyCode?i(t):void 0}),tinymce.Env.ie||(t.on("keypress",function(n){return 41==n.which?e(t):void 0}),t.on("keyup",function(e){return 32==e.keyCode?n(t):void 0}))}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/autoresize/plugin.min.js b/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/autoresize/plugin.min.js deleted file mode 100755 index 9a46de8378..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/autoresize/plugin.min.js +++ /dev/null @@ -1 +0,0 @@ -tinymce.PluginManager.add("autoresize",function(e){function t(i){var a,s,r=e.getDoc(),g=r.body,u=r.documentElement,m=tinymce.DOM,l=n.autoresize_min_height;"setcontent"==i.type&&i.initial||e.plugins.fullscreen&&e.plugins.fullscreen.isFullscreen()||(s=tinymce.Env.ie?g.scrollHeight:tinymce.Env.webkit&&0===g.clientHeight?0:g.offsetHeight,s>n.autoresize_min_height&&(l=s),n.autoresize_max_height&&s>n.autoresize_max_height?(l=n.autoresize_max_height,g.style.overflowY="auto",u.style.overflowY="auto"):(g.style.overflowY="hidden",u.style.overflowY="hidden",g.scrollTop=0),l!==o&&(a=l-o,m.setStyle(m.get(e.id+"_ifr"),"height",l+"px"),o=l,tinymce.isWebKit&&0>a&&t(i)))}function i(e,n,o){setTimeout(function(){t({}),e--?i(e,n,o):o&&o()},n)}var n=e.settings,o=0;e.settings.inline||(n.autoresize_min_height=parseInt(e.getParam("autoresize_min_height",e.getElement().offsetHeight),10),n.autoresize_max_height=parseInt(e.getParam("autoresize_max_height",0),10),e.on("init",function(){e.dom.setStyle(e.getBody(),"paddingBottom",e.getParam("autoresize_bottom_margin",50)+"px")}),e.on("change setcontent paste keyup",t),e.getParam("autoresize_on_init",!0)&&e.on("init",function(){i(20,100,function(){i(5,1e3)})}),e.addCommand("mceAutoResize",t))}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/autosave/plugin.min.js b/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/autosave/plugin.min.js deleted file mode 100755 index 2f1637bd4b..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/autosave/plugin.min.js +++ /dev/null @@ -1 +0,0 @@ -tinymce.PluginManager.add("autosave",function(e){function t(e,t){var n={s:1e3,m:6e4};return e=/^(\d+)([ms]?)$/.exec(""+(e||t)),(e[2]?n[e[2]]:1)*parseInt(e,10)}function n(){var e=parseInt(l.getItem(d+"time"),10)||0;return(new Date).getTime()-e>v.autosave_retention?(a(!1),!1):!0}function a(t){l.removeItem(d+"draft"),l.removeItem(d+"time"),t!==!1&&e.fire("RemoveDraft")}function r(){c()||(l.setItem(d+"draft",e.getContent({format:"raw",no_events:!0})),l.setItem(d+"time",(new Date).getTime()),e.fire("StoreDraft"))}function o(){n()&&(e.setContent(l.getItem(d+"draft"),{format:"raw"}),e.fire("RestoreDraft"))}function i(){m||(setInterval(function(){e.removed||r()},v.autosave_interval),m=!0)}function s(){var t=this;t.disabled(!n()),e.on("StoreDraft RestoreDraft RemoveDraft",function(){t.disabled(!n())}),i()}function u(){e.undoManager.beforeChange(),o(),a(),e.undoManager.add()}function f(){var e;return tinymce.each(tinymce.editors,function(t){t.plugins.autosave&&t.plugins.autosave.storeDraft(),!e&&t.isDirty()&&t.getParam("autosave_ask_before_unload",!0)&&(e=t.translate("You have unsaved changes are you sure you want to navigate away?"))}),e}function c(t){var n=e.settings.forced_root_block;return t=tinymce.trim("undefined"==typeof t?e.getBody().innerHTML:t),""===t||new RegExp("^<"+n+"[^>]*>(( | |[ ]|]*>)+?|)|
$","i").test(t)}var d,m,v=e.settings,l=tinymce.util.LocalStorage;d=v.autosave_prefix||"tinymce-autosave-{path}{query}-{id}-",d=d.replace(/\{path\}/g,document.location.pathname),d=d.replace(/\{query\}/g,document.location.search),d=d.replace(/\{id\}/g,e.id),v.autosave_interval=t(v.autosave_interval,"30s"),v.autosave_retention=t(v.autosave_retention,"20m"),e.addButton("restoredraft",{title:"Restore last draft",onclick:u,onPostRender:s}),e.addMenuItem("restoredraft",{text:"Restore last draft",onclick:u,onPostRender:s,context:"file"}),e.settings.autosave_restore_when_empty!==!1&&(e.on("init",function(){n()&&c()&&o()}),e.on("saveContent",function(){a()})),window.onbeforeunload=f,this.hasDraft=n,this.storeDraft=r,this.restoreDraft=o,this.removeDraft=a,this.isEmpty=c}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/bbcode/plugin.min.js b/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/bbcode/plugin.min.js deleted file mode 100755 index 70a88a7d69..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/bbcode/plugin.min.js +++ /dev/null @@ -1 +0,0 @@ -!function(){tinymce.create("tinymce.plugins.BBCodePlugin",{init:function(e){var t=this,n=e.getParam("bbcode_dialect","punbb").toLowerCase();e.on("beforeSetContent",function(e){e.content=t["_"+n+"_bbcode2html"](e.content)}),e.on("postProcess",function(e){e.set&&(e.content=t["_"+n+"_bbcode2html"](e.content)),e.get&&(e.content=t["_"+n+"_html2bbcode"](e.content))})},getInfo:function(){return{longname:"BBCode Plugin",author:"Moxiecode Systems AB",authorurl:"http://www.tinymce.com",infourl:"http://www.tinymce.com/wiki.php/Plugin:bbcode"}},_punbb_html2bbcode:function(e){function t(t,n){e=e.replace(t,n)}return e=tinymce.trim(e),t(/(.*?)<\/a>/gi,"[url=$1]$2[/url]"),t(/(.*?)<\/font>/gi,"[code][color=$1]$2[/color][/code]"),t(/(.*?)<\/font>/gi,"[quote][color=$1]$2[/color][/quote]"),t(/(.*?)<\/font>/gi,"[code][color=$1]$2[/color][/code]"),t(/(.*?)<\/font>/gi,"[quote][color=$1]$2[/color][/quote]"),t(/(.*?)<\/span>/gi,"[color=$1]$2[/color]"),t(/(.*?)<\/font>/gi,"[color=$1]$2[/color]"),t(/(.*?)<\/span>/gi,"[size=$1]$2[/size]"),t(/(.*?)<\/font>/gi,"$1"),t(//gi,"[img]$1[/img]"),t(/(.*?)<\/span>/gi,"[code]$1[/code]"),t(/(.*?)<\/span>/gi,"[quote]$1[/quote]"),t(/(.*?)<\/strong>/gi,"[code][b]$1[/b][/code]"),t(/(.*?)<\/strong>/gi,"[quote][b]$1[/b][/quote]"),t(/(.*?)<\/em>/gi,"[code][i]$1[/i][/code]"),t(/(.*?)<\/em>/gi,"[quote][i]$1[/i][/quote]"),t(/(.*?)<\/u>/gi,"[code][u]$1[/u][/code]"),t(/(.*?)<\/u>/gi,"[quote][u]$1[/u][/quote]"),t(/<\/(strong|b)>/gi,"[/b]"),t(/<(strong|b)>/gi,"[b]"),t(/<\/(em|i)>/gi,"[/i]"),t(/<(em|i)>/gi,"[i]"),t(/<\/u>/gi,"[/u]"),t(/(.*?)<\/span>/gi,"[u]$1[/u]"),t(//gi,"[u]"),t(/]*>/gi,"[quote]"),t(/<\/blockquote>/gi,"[/quote]"),t(/
/gi,"\n"),t(//gi,"\n"),t(/
/gi,"\n"),t(/

/gi,""),t(/<\/p>/gi,"\n"),t(/ |\u00a0/gi," "),t(/"/gi,'"'),t(/</gi,"<"),t(/>/gi,">"),t(/&/gi,"&"),e},_punbb_bbcode2html:function(e){function t(t,n){e=e.replace(t,n)}return e=tinymce.trim(e),t(/\n/gi,"
"),t(/\[b\]/gi,""),t(/\[\/b\]/gi,""),t(/\[i\]/gi,""),t(/\[\/i\]/gi,""),t(/\[u\]/gi,""),t(/\[\/u\]/gi,""),t(/\[url=([^\]]+)\](.*?)\[\/url\]/gi,'$2'),t(/\[url\](.*?)\[\/url\]/gi,'$1'),t(/\[img\](.*?)\[\/img\]/gi,''),t(/\[color=(.*?)\](.*?)\[\/color\]/gi,'$2'),t(/\[code\](.*?)\[\/code\]/gi,'$1 '),t(/\[quote.*?\](.*?)\[\/quote\]/gi,'$1 '),e}}),tinymce.PluginManager.add("bbcode",tinymce.plugins.BBCodePlugin)}(); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/charmap/plugin.min.js b/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/charmap/plugin.min.js deleted file mode 100755 index dff18e6e55..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/charmap/plugin.min.js +++ /dev/null @@ -1 +0,0 @@ -tinymce.PluginManager.add("charmap",function(e){function t(){function t(e){for(;e;){if("TD"==e.nodeName)return e;e=e.parentNode}}var i,a,r,o;i='';var s=25;for(r=0;10>r;r++){for(i+="",a=0;s>a;a++){var l=n[r*s+a],c="g"+(r*s+a);i+='"}i+=""}i+="";var u={type:"container",html:i,onclick:function(t){var n=t.target;"DIV"==n.nodeName&&e.execCommand("mceInsertContent",!1,n.firstChild.nodeValue)},onmouseover:function(e){var n=t(e.target);n&&o.find("#preview").text(n.firstChild.firstChild.data)}};o=e.windowManager.open({title:"Special character",spacing:10,padding:10,items:[u,{type:"label",name:"preview",text:" ",style:"font-size: 40px; text-align: center",border:1,minWidth:100,minHeight:80}],buttons:[{text:"Close",onclick:function(){o.close()}}]})}var n=[["160","no-break space"],["38","ampersand"],["34","quotation mark"],["162","cent sign"],["8364","euro sign"],["163","pound sign"],["165","yen sign"],["169","copyright sign"],["174","registered sign"],["8482","trade mark sign"],["8240","per mille sign"],["181","micro sign"],["183","middle dot"],["8226","bullet"],["8230","three dot leader"],["8242","minutes / feet"],["8243","seconds / inches"],["167","section sign"],["182","paragraph sign"],["223","sharp s / ess-zed"],["8249","single left-pointing angle quotation mark"],["8250","single right-pointing angle quotation mark"],["171","left pointing guillemet"],["187","right pointing guillemet"],["8216","left single quotation mark"],["8217","right single quotation mark"],["8220","left double quotation mark"],["8221","right double quotation mark"],["8218","single low-9 quotation mark"],["8222","double low-9 quotation mark"],["60","less-than sign"],["62","greater-than sign"],["8804","less-than or equal to"],["8805","greater-than or equal to"],["8211","en dash"],["8212","em dash"],["175","macron"],["8254","overline"],["164","currency sign"],["166","broken bar"],["168","diaeresis"],["161","inverted exclamation mark"],["191","turned question mark"],["710","circumflex accent"],["732","small tilde"],["176","degree sign"],["8722","minus sign"],["177","plus-minus sign"],["247","division sign"],["8260","fraction slash"],["215","multiplication sign"],["185","superscript one"],["178","superscript two"],["179","superscript three"],["188","fraction one quarter"],["189","fraction one half"],["190","fraction three quarters"],["402","function / florin"],["8747","integral"],["8721","n-ary sumation"],["8734","infinity"],["8730","square root"],["8764","similar to"],["8773","approximately equal to"],["8776","almost equal to"],["8800","not equal to"],["8801","identical to"],["8712","element of"],["8713","not an element of"],["8715","contains as member"],["8719","n-ary product"],["8743","logical and"],["8744","logical or"],["172","not sign"],["8745","intersection"],["8746","union"],["8706","partial differential"],["8704","for all"],["8707","there exists"],["8709","diameter"],["8711","backward difference"],["8727","asterisk operator"],["8733","proportional to"],["8736","angle"],["180","acute accent"],["184","cedilla"],["170","feminine ordinal indicator"],["186","masculine ordinal indicator"],["8224","dagger"],["8225","double dagger"],["192","A - grave"],["193","A - acute"],["194","A - circumflex"],["195","A - tilde"],["196","A - diaeresis"],["197","A - ring above"],["198","ligature AE"],["199","C - cedilla"],["200","E - grave"],["201","E - acute"],["202","E - circumflex"],["203","E - diaeresis"],["204","I - grave"],["205","I - acute"],["206","I - circumflex"],["207","I - diaeresis"],["208","ETH"],["209","N - tilde"],["210","O - grave"],["211","O - acute"],["212","O - circumflex"],["213","O - tilde"],["214","O - diaeresis"],["216","O - slash"],["338","ligature OE"],["352","S - caron"],["217","U - grave"],["218","U - acute"],["219","U - circumflex"],["220","U - diaeresis"],["221","Y - acute"],["376","Y - diaeresis"],["222","THORN"],["224","a - grave"],["225","a - acute"],["226","a - circumflex"],["227","a - tilde"],["228","a - diaeresis"],["229","a - ring above"],["230","ligature ae"],["231","c - cedilla"],["232","e - grave"],["233","e - acute"],["234","e - circumflex"],["235","e - diaeresis"],["236","i - grave"],["237","i - acute"],["238","i - circumflex"],["239","i - diaeresis"],["240","eth"],["241","n - tilde"],["242","o - grave"],["243","o - acute"],["244","o - circumflex"],["245","o - tilde"],["246","o - diaeresis"],["248","o slash"],["339","ligature oe"],["353","s - caron"],["249","u - grave"],["250","u - acute"],["251","u - circumflex"],["252","u - diaeresis"],["253","y - acute"],["254","thorn"],["255","y - diaeresis"],["913","Alpha"],["914","Beta"],["915","Gamma"],["916","Delta"],["917","Epsilon"],["918","Zeta"],["919","Eta"],["920","Theta"],["921","Iota"],["922","Kappa"],["923","Lambda"],["924","Mu"],["925","Nu"],["926","Xi"],["927","Omicron"],["928","Pi"],["929","Rho"],["931","Sigma"],["932","Tau"],["933","Upsilon"],["934","Phi"],["935","Chi"],["936","Psi"],["937","Omega"],["945","alpha"],["946","beta"],["947","gamma"],["948","delta"],["949","epsilon"],["950","zeta"],["951","eta"],["952","theta"],["953","iota"],["954","kappa"],["955","lambda"],["956","mu"],["957","nu"],["958","xi"],["959","omicron"],["960","pi"],["961","rho"],["962","final sigma"],["963","sigma"],["964","tau"],["965","upsilon"],["966","phi"],["967","chi"],["968","psi"],["969","omega"],["8501","alef symbol"],["982","pi symbol"],["8476","real part symbol"],["978","upsilon - hook symbol"],["8472","Weierstrass p"],["8465","imaginary part"],["8592","leftwards arrow"],["8593","upwards arrow"],["8594","rightwards arrow"],["8595","downwards arrow"],["8596","left right arrow"],["8629","carriage return"],["8656","leftwards double arrow"],["8657","upwards double arrow"],["8658","rightwards double arrow"],["8659","downwards double arrow"],["8660","left right double arrow"],["8756","therefore"],["8834","subset of"],["8835","superset of"],["8836","not a subset of"],["8838","subset of or equal to"],["8839","superset of or equal to"],["8853","circled plus"],["8855","circled times"],["8869","perpendicular"],["8901","dot operator"],["8968","left ceiling"],["8969","right ceiling"],["8970","left floor"],["8971","right floor"],["9001","left-pointing angle bracket"],["9002","right-pointing angle bracket"],["9674","lozenge"],["9824","black spade suit"],["9827","black club suit"],["9829","black heart suit"],["9830","black diamond suit"],["8194","en space"],["8195","em space"],["8201","thin space"],["8204","zero width non-joiner"],["8205","zero width joiner"],["8206","left-to-right mark"],["8207","right-to-left mark"],["173","soft hyphen"]];e.addButton("charmap",{icon:"charmap",tooltip:"Special character",onclick:t}),e.addMenuItem("charmap",{icon:"charmap",text:"Special character",onclick:t,context:"insert"})}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/code/plugin.min.js b/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/code/plugin.min.js deleted file mode 100755 index cfcef52bab..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/code/plugin.min.js +++ /dev/null @@ -1,7 +0,0 @@ -tinymce.PluginManager.add("code", function(e) { - function o() { - e.windowManager.open({ title: "Source code", body: { type: "textbox", name: "code", multiline: !0, minWidth: e.getParam("code_dialog_width", 600), minHeight: e.getParam("code_dialog_height", Math.min(tinymce.DOM.getViewPort().h - 200, 500)), value: e.getContent({ source_view: !0 }), spellcheck: !1, style: "direction: ltr; text-align: left" }, onSubmit: function(o) { e.focus(), e.undoManager.transact(function() { e.setContent(o.data.code) }), e.selection.setCursorLocation(), e.nodeChanged() } }) - } - - e.addCommand("mceCodeEditor", o), e.addButton("code", { icon: "code", tooltip: "Source code", onclick: o }), e.addMenuItem("code", { icon: "code", text: "Source code", context: "tools", onclick: o }) -}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/codemirror/source.html b/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/codemirror/source.html index 359066d688..807abbedf2 100755 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/codemirror/source.html +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/codemirror/source.html @@ -52,19 +52,20 @@ function inArray(key, arr) }, // CodeMirror v4 commented out below. jsFiles: [// Default JS files 'lib/codemirror.js', - 'lib/util/match-highlighter.js', //'addon/edit/matchbrackets.js', + 'addon/edit/matchbrackets.js', + 'addon/search/match-highlighter.js', 'mode/xml/xml.js', 'mode/javascript/javascript.js', 'mode/css/css.js', 'mode/htmlmixed/htmlmixed.js', - 'lib/util/dialog.js', //'addon/dialog/dialog.js', - 'lib/util/searchcursor.js', //'addon/search/searchcursor.js', - 'lib/util/search.js' //'addon/search/search.js', - //'addon/selection/active-line.js' + 'addon/dialog/dialog.js', + 'addon/search/searchcursor.js', + 'addon/search/search.js', + 'addon/selection/active-line.js' ], cssFiles: [// Default CSS files 'lib/codemirror.css', - 'lib/util/dialog.css'//'addon/dialog/dialog.css' + 'addon/dialog/dialog.css' ] }; diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/compat3x/editable_selects.js b/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/compat3x/editable_selects.js deleted file mode 100755 index c8027461e6..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/compat3x/editable_selects.js +++ /dev/null @@ -1,70 +0,0 @@ -/** - * editable_selects.js - * - * Copyright, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://www.tinymce.com/license - * Contributing: http://www.tinymce.com/contributing - */ - -var TinyMCE_EditableSelects = { - editSelectElm : null, - - init : function() { - var nl = document.getElementsByTagName("select"), i, d = document, o; - - for (i=0; i'; - h += ' '; - - return h; -} - -function updateColor(img_id, form_element_id) { - document.getElementById(img_id).style.backgroundColor = document.forms[0].elements[form_element_id].value; -} - -function setBrowserDisabled(id, state) { - var img = document.getElementById(id); - var lnk = document.getElementById(id + "_link"); - - if (lnk) { - if (state) { - lnk.setAttribute("realhref", lnk.getAttribute("href")); - lnk.removeAttribute("href"); - tinyMCEPopup.dom.addClass(img, 'disabled'); - } else { - if (lnk.getAttribute("realhref")) - lnk.setAttribute("href", lnk.getAttribute("realhref")); - - tinyMCEPopup.dom.removeClass(img, 'disabled'); - } - } -} - -function getBrowserHTML(id, target_form_element, type, prefix) { - var option = prefix + "_" + type + "_browser_callback", cb, html; - - cb = tinyMCEPopup.getParam(option, tinyMCEPopup.getParam("file_browser_callback")); - - if (!cb) - return ""; - - html = ""; - html += ''; - html += ' '; - - return html; -} - -function openBrowser(img_id, target_form_element, type, option) { - var img = document.getElementById(img_id); - - if (img.className != "mceButtonDisabled") - tinyMCEPopup.openBrowser(target_form_element, type, option); -} - -function selectByValue(form_obj, field_name, value, add_custom, ignore_case) { - if (!form_obj || !form_obj.elements[field_name]) - return; - - if (!value) - value = ""; - - var sel = form_obj.elements[field_name]; - - var found = false; - for (var i=0; i/langs/_dlg.js lang pack file. - * - * @method requireLangPack - */ - requireLangPack : function() { - var t = this, u = t.getWindowArg('plugin_url') || t.getWindowArg('theme_url'); - - if (u && t.editor.settings.language && t.features.translate_i18n !== false && t.editor.settings.language_load !== false) { - u += '/langs/' + t.editor.settings.language + '_dlg.js'; - - if (!tinymce.ScriptLoader.isDone(u)) { - document.write(''); - tinymce.ScriptLoader.markDone(u); - } - } - }, - - /** - * Executes a color picker on the specified element id. When the user - * then selects a color it will be set as the value of the specified element. - * - * @method pickColor - * @param {DOMEvent} e DOM event object. - * @param {string} element_id Element id to be filled with the color value from the picker. - */ - pickColor : function(e, element_id) { - this.execCommand('mceColorPicker', true, { - color : document.getElementById(element_id).value, - func : function(c) { - document.getElementById(element_id).value = c; - - try { - document.getElementById(element_id).onchange(); - } catch (ex) { - // Try fire event, ignore errors - } - } - }); - }, - - /** - * Opens a filebrowser/imagebrowser this will set the output value from - * the browser as a value on the specified element. - * - * @method openBrowser - * @param {string} element_id Id of the element to set value in. - * @param {string} type Type of browser to open image/file/flash. - * @param {string} option Option name to get the file_broswer_callback function name from. - */ - openBrowser : function(element_id, type, option) { - tinyMCEPopup.restoreSelection(); - this.editor.execCallback('file_browser_callback', element_id, document.getElementById(element_id).value, type, window); - }, - - /** - * Creates a confirm dialog. Please don't use the blocking behavior of this - * native version use the callback method instead then it can be extended. - * - * @method confirm - * @param {String} t Title for the new confirm dialog. - * @param {function} cb Callback function to be executed after the user has selected ok or cancel. - * @param {Object} s Optional scope to execute the callback in. - */ - confirm : function(t, cb, s) { - this.editor.windowManager.confirm(t, cb, s, window); - }, - - /** - * Creates a alert dialog. Please don't use the blocking behavior of this - * native version use the callback method instead then it can be extended. - * - * @method alert - * @param {String} t Title for the new alert dialog. - * @param {function} cb Callback function to be executed after the user has selected ok. - * @param {Object} s Optional scope to execute the callback in. - */ - alert : function(tx, cb, s) { - this.editor.windowManager.alert(tx, cb, s, window); - }, - - /** - * Closes the current window. - * - * @method close - */ - close : function() { - var t = this; - - // To avoid domain relaxing issue in Opera - function close() { - t.editor.windowManager.close(window); - tinymce = tinyMCE = t.editor = t.params = t.dom = t.dom.doc = null; // Cleanup - }; - - if (tinymce.isOpera) - t.getWin().setTimeout(close, 0); - else - close(); - }, - - // Internal functions - - _restoreSelection : function() { - var e = window.event.srcElement; - - if (e.nodeName == 'INPUT' && (e.type == 'submit' || e.type == 'button')) - tinyMCEPopup.restoreSelection(); - }, - -/* _restoreSelection : function() { - var e = window.event.srcElement; - - // If user focus a non text input or textarea - if ((e.nodeName != 'INPUT' && e.nodeName != 'TEXTAREA') || e.type != 'text') - tinyMCEPopup.restoreSelection(); - },*/ - - _onDOMLoaded : function() { - var t = tinyMCEPopup, ti = document.title, bm, h, nv; - - // Translate page - if (t.features.translate_i18n !== false) { - h = document.body.innerHTML; - - // Replace a=x with a="x" in IE - if (tinymce.isIE) - h = h.replace(/ (value|title|alt)=([^"][^\s>]+)/gi, ' $1="$2"') - - document.dir = t.editor.getParam('directionality',''); - - if ((nv = t.editor.translate(h)) && nv != h) - document.body.innerHTML = nv; - - if ((nv = t.editor.translate(ti)) && nv != ti) - document.title = ti = nv; - } - - if (!t.editor.getParam('browser_preferred_colors', false) || !t.isWindow) - t.dom.addClass(document.body, 'forceColors'); - - document.body.style.display = ''; - - // Restore selection in IE when focus is placed on a non textarea or input element of the type text - if (tinymce.isIE) { - document.attachEvent('onmouseup', tinyMCEPopup._restoreSelection); - - // Add base target element for it since it would fail with modal dialogs - t.dom.add(t.dom.select('head')[0], 'base', {target : '_self'}); - } - - t.restoreSelection(); - t.resizeToInnerSize(); - - // Set inline title - if (!t.isWindow) - t.editor.windowManager.setTitle(window, ti); - else - window.focus(); - - if (!tinymce.isIE && !t.isWindow) { - t.dom.bind(document, 'focus', function() { - t.editor.windowManager.focus(t.id); - }); - } - - // Patch for accessibility - tinymce.each(t.dom.select('select'), function(e) { - e.onkeydown = tinyMCEPopup._accessHandler; - }); - - // Call onInit - // Init must be called before focus so the selection won't get lost by the focus call - tinymce.each(t.listeners, function(o) { - o.func.call(o.scope, t.editor); - }); - - // Move focus to window - if (t.getWindowArg('mce_auto_focus', true)) { - window.focus(); - - // Focus element with mceFocus class - tinymce.each(document.forms, function(f) { - tinymce.each(f.elements, function(e) { - if (t.dom.hasClass(e, 'mceFocus') && !e.disabled) { - e.focus(); - return false; // Break loop - } - }); - }); - } - - document.onkeyup = tinyMCEPopup._closeWinKeyHandler; - }, - - _accessHandler : function(e) { - e = e || window.event; - - if (e.keyCode == 13 || e.keyCode == 32) { - var elm = e.target || e.srcElement; - - if (elm.onchange) - elm.onchange(); - - return tinymce.dom.Event.cancel(e); - } - }, - - _closeWinKeyHandler : function(e) { - e = e || window.event; - - if (e.keyCode == 27) - tinyMCEPopup.close(); - }, - - _eventProxy: function(id) { - return function(evt) { - tinyMCEPopup.dom.events.callNativeHandler(id, evt); - }; - } -}; - -tinyMCEPopup.init(); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/compat3x/validate.js b/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/compat3x/validate.js deleted file mode 100755 index 1538fd08de..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/compat3x/validate.js +++ /dev/null @@ -1,252 +0,0 @@ -/** - * validate.js - * - * Copyright, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://www.tinymce.com/license - * Contributing: http://www.tinymce.com/contributing - */ - -/** - // String validation: - - if (!Validator.isEmail('myemail')) - alert('Invalid email.'); - - // Form validation: - - var f = document.forms['myform']; - - if (!Validator.isEmail(f.myemail)) - alert('Invalid email.'); -*/ - -var Validator = { - isEmail : function(s) { - return this.test(s, '^[-!#$%&\'*+\\./0-9=?A-Z^_`a-z{|}~]+@[-!#$%&\'*+\\/0-9=?A-Z^_`a-z{|}~]+\.[-!#$%&\'*+\\./0-9=?A-Z^_`a-z{|}~]+$'); - }, - - isAbsUrl : function(s) { - return this.test(s, '^(news|telnet|nttp|file|http|ftp|https)://[-A-Za-z0-9\\.]+\\/?.*$'); - }, - - isSize : function(s) { - return this.test(s, '^[0-9.]+(%|in|cm|mm|em|ex|pt|pc|px)?$'); - }, - - isId : function(s) { - return this.test(s, '^[A-Za-z_]([A-Za-z0-9_])*$'); - }, - - isEmpty : function(s) { - var nl, i; - - if (s.nodeName == 'SELECT' && s.selectedIndex < 1) - return true; - - if (s.type == 'checkbox' && !s.checked) - return true; - - if (s.type == 'radio') { - for (i=0, nl = s.form.elements; i parseInt(v)) - st = this.mark(f, n); - } - } - - return st; - }, - - hasClass : function(n, c, d) { - return new RegExp('\\b' + c + (d ? '[0-9]+' : '') + '\\b', 'g').test(n.className); - }, - - getNum : function(n, c) { - c = n.className.match(new RegExp('\\b' + c + '([0-9]+)\\b', 'g'))[0]; - c = c.replace(/[^0-9]/g, ''); - - return c; - }, - - addClass : function(n, c, b) { - var o = this.removeClass(n, c); - n.className = b ? c + (o != '' ? (' ' + o) : '') : (o != '' ? (o + ' ') : '') + c; - }, - - removeClass : function(n, c) { - c = n.className.replace(new RegExp("(^|\\s+)" + c + "(\\s+|$)"), ' '); - return n.className = c != ' ' ? c : ''; - }, - - tags : function(f, s) { - return f.getElementsByTagName(s); - }, - - mark : function(f, n) { - var s = this.settings; - - this.addClass(n, s.invalid_cls); - n.setAttribute('aria-invalid', 'true'); - this.markLabels(f, n, s.invalid_cls); - - return false; - }, - - markLabels : function(f, n, ic) { - var nl, i; - - nl = this.tags(f, "label"); - for (i=0; i'}),t+=""}),t+=""}var i=[["cool","cry","embarassed","foot-in-mouth"],["frown","innocent","kiss","laughing"],["money-mouth","sealed","smile","surprised"],["tongue-out","undecided","wink","yell"]];t.addButton("emoticons",{type:"panelbutton",panel:{autohide:!0,html:n,onclick:function(e){var n=t.dom.getParent(e.target,"a");n&&(t.insertContent(''),this.hide())}},tooltip:"Emoticons"})}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/example/plugin.min.js b/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/example/plugin.min.js deleted file mode 100755 index 1ff20b46b9..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/example/plugin.min.js +++ /dev/null @@ -1 +0,0 @@ -tinymce.PluginManager.add("example",function(t){t.addButton("example",{text:"My button",icon:!1,onclick:function(){t.windowManager.open({title:"Example plugin",body:[{type:"textbox",name:"title",label:"Title"}],onsubmit:function(e){t.insertContent("Title: "+e.data.title)}})}}),t.addMenuItem("example",{text:"Example plugin",context:"tools",onclick:function(){t.windowManager.open({title:"TinyMCE site",url:"http://www.tinymce.com",width:800,height:600,buttons:[{text:"Close",onclick:"close"}]})}})}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/example_dependency/plugin.min.js b/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/example_dependency/plugin.min.js deleted file mode 100755 index e61bf473ad..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/example_dependency/plugin.min.js +++ /dev/null @@ -1 +0,0 @@ -tinymce.PluginManager.add("example_dependency",function(){},["example"]); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/fullpage/plugin.min.js b/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/fullpage/plugin.min.js deleted file mode 100755 index 7cdbf79275..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/fullpage/plugin.min.js +++ /dev/null @@ -1 +0,0 @@ -tinymce.PluginManager.add("fullpage",function(e){function t(){var t=n();e.windowManager.open({title:"Document properties",data:t,defaults:{type:"textbox",size:40},body:[{name:"title",label:"Title"},{name:"keywords",label:"Keywords"},{name:"description",label:"Description"},{name:"robots",label:"Robots"},{name:"author",label:"Author"},{name:"docencoding",label:"Encoding"}],onSubmit:function(e){l(tinymce.extend(t,e.data))}})}function n(){function t(e,t){var n=e.attr(t);return n||""}var n,l,a=i(),r={};return r.fontface=e.getParam("fullpage_default_fontface",""),r.fontsize=e.getParam("fullpage_default_fontsize",""),n=a.firstChild,7==n.type&&(r.xml_pi=!0,l=/encoding="([^"]+)"/.exec(n.value),l&&(r.docencoding=l[1])),n=a.getAll("#doctype")[0],n&&(r.doctype=""),n=a.getAll("title")[0],n&&n.firstChild&&(r.title=n.firstChild.value),s(a.getAll("meta"),function(e){var t,n=e.attr("name"),l=e.attr("http-equiv");n?r[n.toLowerCase()]=e.attr("content"):"Content-Type"==l&&(t=/charset\s*=\s*(.*)\s*/gi.exec(e.attr("content")),t&&(r.docencoding=t[1]))}),n=a.getAll("html")[0],n&&(r.langcode=t(n,"lang")||t(n,"xml:lang")),r.stylesheets=[],tinymce.each(a.getAll("link"),function(e){"stylesheet"==e.attr("rel")&&r.stylesheets.push(e.attr("href"))}),n=a.getAll("body")[0],n&&(r.langdir=t(n,"dir"),r.style=t(n,"style"),r.visited_color=t(n,"vlink"),r.link_color=t(n,"link"),r.active_color=t(n,"alink")),r}function l(t){function n(e,t,n){e.attr(t,n?n:void 0)}function l(e){r.firstChild?r.insert(e,r.firstChild):r.append(e)}var a,r,o,c,u,f=e.dom;a=i(),r=a.getAll("head")[0],r||(c=a.getAll("html")[0],r=new m("head",1),c.firstChild?c.insert(r,c.firstChild,!0):c.append(r)),c=a.firstChild,t.xml_pi?(u='version="1.0"',t.docencoding&&(u+=' encoding="'+t.docencoding+'"'),7!=c.type&&(c=new m("xml",7),a.insert(c,a.firstChild,!0)),c.value=u):c&&7==c.type&&c.remove(),c=a.getAll("#doctype")[0],t.doctype?(c||(c=new m("#doctype",10),t.xml_pi?a.insert(c,a.firstChild):l(c)),c.value=t.doctype.substring(9,t.doctype.length-1)):c&&c.remove(),t.docencoding&&(c=null,s(a.getAll("meta"),function(e){"Content-Type"==e.attr("http-equiv")&&(c=e)}),c||(c=new m("meta",1),c.attr("http-equiv","Content-Type"),c.shortEnded=!0,l(c)),c.attr("content","text/html; charset="+t.docencoding)),c=a.getAll("title")[0],t.title?c||(c=new m("title",1),c.append(new m("#text",3)).value=t.title,l(c)):c&&c.remove(),s("keywords,description,author,copyright,robots".split(","),function(e){var n,i,r=a.getAll("meta"),o=t[e];for(n=0;n"))}function i(){return new tinymce.html.DomParser({validate:!1,root_name:"#document"}).parse(d)}function a(t){function n(e){return e.replace(/<\/?[A-Z]+/g,function(e){return e.toLowerCase()})}var l,a,o,m,u=t.content,f="",g=e.dom;if(!t.selection&&!("raw"==t.format&&d||t.source_view&&e.getParam("fullpage_hide_in_source_view"))){u=u.replace(/<(\/?)BODY/gi,"<$1body"),l=u.indexOf("",l),d=n(u.substring(0,l+1)),a=u.indexOf("\n"),o=i(),s(o.getAll("style"),function(e){e.firstChild&&(f+=e.firstChild.value)}),m=o.getAll("body")[0],m&&g.setAttribs(e.getBody(),{style:m.attr("style")||"",dir:m.attr("dir")||"",vLink:m.attr("vlink")||"",link:m.attr("link")||"",aLink:m.attr("alink")||""}),g.remove("fullpage_styles");var y=e.getDoc().getElementsByTagName("head")[0];f&&(g.add(y,"style",{id:"fullpage_styles"},f),m=g.get("fullpage_styles"),m.styleSheet&&(m.styleSheet.cssText=f));var h={};tinymce.each(y.getElementsByTagName("link"),function(e){"stylesheet"==e.rel&&e.getAttribute("data-mce-fullpage")&&(h[e.href]=e)}),tinymce.each(o.getAll("link"),function(e){var t=e.attr("href");h[t]||"stylesheet"!=e.attr("rel")||g.add(y,"link",{rel:"stylesheet",text:"text/css",href:t,"data-mce-fullpage":"1"}),delete h[t]}),tinymce.each(h,function(e){e.parentNode.removeChild(e)})}}function r(){var t,n="",l="";return e.getParam("fullpage_default_xml_pi")&&(n+='\n'),n+=e.getParam("fullpage_default_doctype",""),n+="\n\n\n",(t=e.getParam("fullpage_default_title"))&&(n+=""+t+"\n"),(t=e.getParam("fullpage_default_encoding"))&&(n+='\n'),(t=e.getParam("fullpage_default_font_family"))&&(l+="font-family: "+t+";"),(t=e.getParam("fullpage_default_font_size"))&&(l+="font-size: "+t+";"),(t=e.getParam("fullpage_default_text_color"))&&(l+="color: "+t+";"),n+="\n\n"}function o(t){t.selection||t.source_view&&e.getParam("fullpage_hide_in_source_view")||(t.content=tinymce.trim(d)+"\n"+tinymce.trim(t.content)+"\n"+tinymce.trim(c))}var d,c,s=tinymce.each,m=tinymce.html.Node;e.addCommand("mceFullPageProperties",t),e.addButton("fullpage",{title:"Document properties",cmd:"mceFullPageProperties"}),e.addMenuItem("fullpage",{text:"Document properties",cmd:"mceFullPageProperties",context:"file"}),e.on("BeforeSetContent",a),e.on("GetContent",o)}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/fullscreen/plugin.min.js b/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/fullscreen/plugin.min.js deleted file mode 100755 index 1bb1940dd9..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/fullscreen/plugin.min.js +++ /dev/null @@ -1 +0,0 @@ -tinymce.PluginManager.add("fullscreen",function(e){function t(){var e,t,n=window,i=document,l=i.body;return l.offsetWidth&&(e=l.offsetWidth,t=l.offsetHeight),n.innerWidth&&n.innerHeight&&(e=n.innerWidth,t=n.innerHeight),{w:e,h:t}}function n(){function n(){d.setStyle(a,"height",t().h-(h.clientHeight-a.clientHeight))}var u,h,a,f,m=document.body,g=document.documentElement;s=!s,h=e.getContainer(),u=h.style,a=e.getContentAreaContainer().firstChild,f=a.style,s?(i=f.width,l=f.height,f.width=f.height="100%",c=u.width,o=u.height,u.width=u.height="",d.addClass(m,"mce-fullscreen"),d.addClass(g,"mce-fullscreen"),d.addClass(h,"mce-fullscreen"),d.bind(window,"resize",n),n(),r=n):(f.width=i,f.height=l,c&&(u.width=c),o&&(u.height=o),d.removeClass(m,"mce-fullscreen"),d.removeClass(g,"mce-fullscreen"),d.removeClass(h,"mce-fullscreen"),d.unbind(window,"resize",r)),e.fire("FullscreenStateChanged",{state:s})}var i,l,r,c,o,s=!1,d=tinymce.DOM;return e.settings.inline?void 0:(e.on("init",function(){e.addShortcut("Ctrl+Alt+F","",n)}),e.on("remove",function(){r&&d.unbind(window,"resize",r)}),e.addCommand("mceFullScreen",n),e.addMenuItem("fullscreen",{text:"Fullscreen",shortcut:"Ctrl+Alt+F",selectable:!0,onClick:n,onPostRender:function(){var t=this;e.on("FullscreenStateChanged",function(e){t.active(e.state)})},context:"view"}),e.addButton("fullscreen",{tooltip:"Fullscreen",shortcut:"Ctrl+Alt+F",onClick:n,onPostRender:function(){var t=this;e.on("FullscreenStateChanged",function(e){t.active(e.state)})}}),{isFullscreen:function(){return s}})}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/hr/plugin.min.js b/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/hr/plugin.min.js deleted file mode 100755 index ca36c92751..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/hr/plugin.min.js +++ /dev/null @@ -1 +0,0 @@ -tinymce.PluginManager.add("hr",function(e){e.addCommand("InsertHorizontalRule",function(){e.execCommand("mceInsertContent",!1,"


")}),e.addButton("hr",{icon:"hr",tooltip:"Horizontal line",cmd:"InsertHorizontalRule"}),e.addMenuItem("hr",{icon:"hr",text:"Horizontal line",cmd:"InsertHorizontalRule",context:"insert"})}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/image/plugin.min.js b/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/image/plugin.min.js deleted file mode 100755 index 8d881be554..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/image/plugin.min.js +++ /dev/null @@ -1 +0,0 @@ -tinymce.PluginManager.add("image",function(t){function e(t,e){function n(t,n){i.parentNode.removeChild(i),e({width:t,height:n})}var i=document.createElement("img");i.onload=function(){n(i.clientWidth,i.clientHeight)},i.onerror=function(){n()},i.src=t;var a=i.style;a.visibility="hidden",a.position="fixed",a.bottom=a.left=0,a.width=a.height="auto",document.body.appendChild(i)}function n(e){return function(){var n=t.settings.image_list;"string"==typeof n?tinymce.util.XHR.send({url:n,success:function(t){e(tinymce.util.JSON.parse(t))}}):e(n)}}function i(n){function i(){var e=[{text:"None",value:""}];return tinymce.each(n,function(n){e.push({text:n.text||n.title,value:t.convertURL(n.value||n.url,"src"),menu:n.menu})}),e}function a(t){var e,n,i,a;e=s.find("#width")[0],n=s.find("#height")[0],i=e.value(),a=n.value(),s.find("#constrain")[0].checked()&&d&&u&&i&&a&&(t.control==e?(a=Math.round(i/d*a),n.value(a)):(i=Math.round(a/u*i),e.value(i))),d=i,u=a}function o(){function e(e){function i(){e.onload=e.onerror=null,t.selection.select(e),t.nodeChanged()}e.onload=function(){n.width||n.height||m.setAttribs(e,{width:e.clientWidth,height:e.clientHeight}),i()},e.onerror=i}var n=s.toJSON();""===n.width&&(n.width=null),""===n.height&&(n.height=null),""===n.style&&(n.style=null),n={src:n.src,alt:n.alt,width:n.width,height:n.height,style:n.style},t.undoManager.transact(function(){return n.src?(p?m.setAttribs(p,n):(n.id="__mcenew",t.selection.setContent(m.createHTML("img",n)),p=m.get("__mcenew"),m.setAttrib(p,"id",null)),e(p),void 0):(p&&(m.remove(p),t.nodeChanged()),void 0)})}function l(t){return t&&(t=t.replace(/px$/,"")),t}function r(){h&&h.value(t.convertURL(this.value(),"src")),e(this.value(),function(t){t.width&&t.height&&(d=t.width,u=t.height,s.find("#width").value(d),s.find("#height").value(u))})}function c(){function t(t){return t.length>0&&/^[0-9]+$/.test(t)&&(t+="px"),t}var e=s.toJSON(),n=m.parseStyle(e.style);delete n.margin,n["margin-top"]=n["margin-bottom"]=t(e.vspace),n["margin-left"]=n["margin-right"]=t(e.hspace),n["border-width"]=t(e.border),s.find("#style").value(m.serializeStyle(m.parseStyle(m.serializeStyle(n))))}var s,d,u,h,g={},m=t.dom,p=t.selection.getNode();d=m.getAttrib(p,"width"),u=m.getAttrib(p,"height"),"IMG"!=p.nodeName||p.getAttribute("data-mce-object")?p=null:g={src:m.getAttrib(p,"src"),alt:m.getAttrib(p,"alt"),width:d,height:u},n&&(h={type:"listbox",label:"Image list",values:i(),value:g.src&&t.convertURL(g.src,"src"),onselect:function(t){var e=s.find("#alt");(!e.value()||t.lastControl&&e.value()==t.lastControl.text())&&e.value(t.control.text()),s.find("#src").value(t.control.value())},onPostRender:function(){h=this}});var y=[{name:"src",type:"filepicker",filetype:"image",label:"Source",autofocus:!0,onchange:r},h,{name:"alt",type:"textbox",label:"Image description"},{type:"container",label:"Dimensions",layout:"flex",direction:"row",align:"center",spacing:5,items:[{name:"width",type:"textbox",maxLength:3,size:3,onchange:a},{type:"label",text:"x"},{name:"height",type:"textbox",maxLength:3,size:3,onchange:a},{name:"constrain",type:"checkbox",checked:!0,text:"Constrain proportions"}]}];t.settings.image_advtab?(p&&(g.hspace=l(p.style.marginLeft||p.style.marginRight),g.vspace=l(p.style.marginTop||p.style.marginBottom),g.border=l(p.style.borderWidth),g.style=t.dom.serializeStyle(t.dom.parseStyle(t.dom.getAttrib(p,"style")))),s=t.windowManager.open({title:"Insert/edit image",data:g,bodyType:"tabpanel",body:[{title:"General",type:"form",items:y},{title:"Advanced",type:"form",pack:"start",items:[{label:"Style",name:"style",type:"textbox"},{type:"form",layout:"grid",packV:"start",columns:2,padding:0,alignH:["left","right"],defaults:{type:"textbox",maxWidth:50,onchange:c},items:[{label:"Vertical space",name:"vspace"},{label:"Horizontal space",name:"hspace"},{label:"Border",name:"border"}]}]}],onSubmit:o})):s=t.windowManager.open({title:"Insert/edit image",data:g,body:y,onSubmit:o})}t.addButton("image",{icon:"image",tooltip:"Insert/edit image",onclick:n(i),stateSelector:"img:not([data-mce-object])"}),t.addMenuItem("image",{icon:"image",text:"Insert image",onclick:n(i),context:"insert",prependToContext:!0})}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/importcss/plugin.min.js b/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/importcss/plugin.min.js deleted file mode 100644 index 61622762d0..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/importcss/plugin.min.js +++ /dev/null @@ -1 +0,0 @@ -tinymce.PluginManager.add("importcss",function(t){function e(t){return"string"==typeof t?function(e){return-1!==e.indexOf(t)}:t instanceof RegExp?function(e){return t.test(e)}:t}function n(e,n){function r(t,e){var o,l=t.href;if((e||c[l])&&(!n||n(l))){s(t.imports,function(t){r(t,!0)});try{o=t.cssRules||t.rules}catch(a){}s(o,function(t){t.styleSheet?r(t.styleSheet,!0):t.selectorText&&s(t.selectorText.split(","),function(t){i.push(tinymce.trim(t))})})}}var i=[],c={};s(t.contentCSS,function(t){c[t]=!0});try{s(e.styleSheets,function(t){r(t)})}catch(o){}return i}function r(e){var n,r=/^(?:([a-z0-9\-_]+))?(\.[a-z0-9_\-\.]+)$/i.exec(e);if(r){var i=r[1],s=r[2].substr(1).split(".").join(" ");return r[1]?(n={title:e},t.schema.getTextBlockElements()[i]?n.block=i:t.schema.getBlockElements()[i]?n.selector=i:n.inline=i):r[2]&&(n={inline:"span",title:e.substr(1),classes:s}),t.settings.importcss_merge_classes!==!1?n.classes=s:n.attributes={"class":s},n}}var i=this,s=tinymce.each;t.settings.style_formats||t.on("renderFormatsMenu",function(c){var o=t.settings,l={},a=o.importcss_selector_converter||r,f=e(o.importcss_selector_filter);t.settings.importcss_append||c.control.items().remove();var m=o.importcss_groups;if(m)for(var u=0;u'+i+"";var r=e.dom.getParent(e.selection.getStart(),"time");if(r)return e.dom.setOuterHTML(r,i),void 0}e.insertContent(i)}var i,a="Sun Mon Tue Wed Thu Fri Sat Sun".split(" "),r="Sunday Monday Tuesday Wednesday Thursday Friday Saturday Sunday".split(" "),o="Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(" "),s="January February March April May June July August September October November December".split(" "),l=[];e.addCommand("mceInsertDate",function(){n(e.getParam("insertdatetime_dateformat",e.translate("%Y-%m-%d")))}),e.addCommand("mceInsertTime",function(){n(e.getParam("insertdatetime_timeformat",e.translate("%H:%M:%S")))}),e.addButton("inserttime",{type:"splitbutton",title:"Insert time",onclick:function(){n(i||"%H:%M:%S")},menu:l}),tinymce.each(e.settings.insertdatetime_formats||["%H:%M:%S","%Y-%m-%d","%I:%M:%S %p","%D"],function(e){l.push({text:t(e),onclick:function(){i=e,n(e)}})}),e.addMenuItem("insertdatetime",{icon:"date",text:"Insert date/time",menu:l,context:"insert"})}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/layer/plugin.min.js b/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/layer/plugin.min.js deleted file mode 100755 index eb1ad4b68d..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/layer/plugin.min.js +++ /dev/null @@ -1 +0,0 @@ -tinymce.PluginManager.add("layer",function(e){function t(e){do if(e.className&&-1!=e.className.indexOf("mceItemLayer"))return e;while(e=e.parentNode)}function n(t){var n=e.dom;tinymce.each(n.select("div,p",t),function(e){/^(absolute|relative|fixed)$/i.test(e.style.position)&&(e.hasVisual?n.addClass(e,"mceItemVisualAid"):n.removeClass(e,"mceItemVisualAid"),n.addClass(e,"mceItemLayer"))})}function i(n){var i,o,a=[],r=t(e.selection.getNode()),l=-1,s=-1;for(o=[],tinymce.walk(e.getBody(),function(e){1==e.nodeType&&/^(absolute|relative|static)$/i.test(e.style.position)&&o.push(e)},"childNodes"),i=0;il&&o[i]==r&&(l=i);if(0>n){for(i=0;i-1?(o[l].style.zIndex=a[s],o[s].style.zIndex=a[l]):a[l]>0&&(o[l].style.zIndex=a[l]-1)}else{for(i=0;ia[l]){s=i;break}s>-1?(o[l].style.zIndex=a[s],o[s].style.zIndex=a[l]):o[l].style.zIndex=a[l]+1}e.execCommand("mceRepaint")}function o(){var t=e.dom,n=t.getPos(t.getParent(e.selection.getNode(),"*")),i=e.getBody();e.dom.add(i,"div",{style:{position:"absolute",left:n.x,top:n.y>20?n.y:20,width:100,height:100},"class":"mceItemVisualAid mceItemLayer"},e.selection.getContent()||e.getLang("layer.content")),tinymce.Env.ie&&t.setHTML(i,i.innerHTML)}function a(){var n=t(e.selection.getNode());n||(n=e.dom.getParent(e.selection.getNode(),"DIV,P,IMG")),n&&("absolute"==n.style.position.toLowerCase()?(e.dom.setStyles(n,{position:"",left:"",top:"",width:"",height:""}),e.dom.removeClass(n,"mceItemVisualAid"),e.dom.removeClass(n,"mceItemLayer")):(n.style.left||(n.style.left="20px"),n.style.top||(n.style.top="20px"),n.style.width||(n.style.width=n.width?n.width+"px":"100px"),n.style.height||(n.style.height=n.height?n.height+"px":"100px"),n.style.position="absolute",e.dom.setAttrib(n,"data-mce-style",""),e.addVisual(e.getBody())),e.execCommand("mceRepaint"),e.nodeChanged())}e.addCommand("mceInsertLayer",o),e.addCommand("mceMoveForward",function(){i(1)}),e.addCommand("mceMoveBackward",function(){i(-1)}),e.addCommand("mceMakeAbsolute",function(){a()}),e.addButton("moveforward",{title:"layer.forward_desc",cmd:"mceMoveForward"}),e.addButton("movebackward",{title:"layer.backward_desc",cmd:"mceMoveBackward"}),e.addButton("absolute",{title:"layer.absolute_desc",cmd:"mceMakeAbsolute"}),e.addButton("insertlayer",{title:"layer.insertlayer_desc",cmd:"mceInsertLayer"}),e.on("init",function(){tinymce.Env.ie&&e.getDoc().execCommand("2D-Position",!1,!0)}),e.on("mouseup",function(n){var i=t(n.target);i&&e.dom.setAttrib(i,"data-mce-style","")}),e.on("mousedown",function(n){var i,o=n.target,a=e.getDoc();tinymce.Env.gecko&&(t(o)?"on"!==a.designMode&&(a.designMode="on",o=a.body,i=o.parentNode,i.removeChild(o),i.appendChild(o)):"on"==a.designMode&&(a.designMode="off"))}),e.on("NodeChange",n)}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/legacyoutput/plugin.min.js b/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/legacyoutput/plugin.min.js deleted file mode 100755 index 4f6f7c1aa3..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/legacyoutput/plugin.min.js +++ /dev/null @@ -1 +0,0 @@ -!function(e){e.on("AddEditor",function(e){e.editor.settings.inline_styles=!1}),e.PluginManager.add("legacyoutput",function(t){t.on("init",function(){var n="p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img",i=e.explode(t.settings.font_size_style_values),o=t.schema;t.formatter.register({alignleft:{selector:n,attributes:{align:"left"}},aligncenter:{selector:n,attributes:{align:"center"}},alignright:{selector:n,attributes:{align:"right"}},alignjustify:{selector:n,attributes:{align:"justify"}},bold:[{inline:"b",remove:"all"},{inline:"strong",remove:"all"},{inline:"span",styles:{fontWeight:"bold"}}],italic:[{inline:"i",remove:"all"},{inline:"em",remove:"all"},{inline:"span",styles:{fontStyle:"italic"}}],underline:[{inline:"u",remove:"all"},{inline:"span",styles:{textDecoration:"underline"},exact:!0}],strikethrough:[{inline:"strike",remove:"all"},{inline:"span",styles:{textDecoration:"line-through"},exact:!0}],fontname:{inline:"font",attributes:{face:"%value"}},fontsize:{inline:"font",attributes:{size:function(t){return e.inArray(i,t.value)+1}}},forecolor:{inline:"font",attributes:{color:"%value"}},hilitecolor:{inline:"font",styles:{backgroundColor:"%value"}}}),e.each("b,i,u,strike".split(","),function(e){o.addValidElements(e+"[*]")}),o.getElementRule("font")||o.addValidElements("font[face|size|color|style]"),e.each(n.split(","),function(e){var t=o.getElementRule(e);t&&(t.attributes.align||(t.attributes.align={},t.attributesOrder.push("align")))})})})}(tinymce); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/link/plugin.min.js b/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/link/plugin.min.js deleted file mode 100755 index 05b7ead8ac..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/link/plugin.min.js +++ /dev/null @@ -1 +0,0 @@ -tinymce.PluginManager.add("link",function(e){function t(t){return function(){var n=e.settings.link_list;"string"==typeof n?tinymce.util.XHR.send({url:n,success:function(e){t(tinymce.util.JSON.parse(e))}}):t(n)}}function n(t){function n(e){var t=f.find("#text");(!t.value()||e.lastControl&&t.value()==e.lastControl.text())&&t.value(e.control.text()),f.find("#href").value(e.control.value())}function l(){var n=[{text:"None",value:""}];return tinymce.each(t,function(t){n.push({text:t.text||t.title,value:e.convertURL(t.value||t.url,"href"),menu:t.menu})}),n}function i(t){var n=[{text:"None",value:""}];return tinymce.each(e.settings.rel_list,function(e){n.push({text:e.text||e.title,value:e.value,selected:t===e.value})}),n}function r(t){var n=[{text:"None",value:""}];return e.settings.target_list||n.push({text:"New window",value:"_blank"}),tinymce.each(e.settings.target_list,function(e){n.push({text:e.text||e.title,value:e.value,selected:t===e.value})}),n}function a(t){var l=[];return tinymce.each(e.dom.select("a:not([href])"),function(e){var n=e.name||e.id;n&&l.push({text:n,value:"#"+n,selected:-1!=t.indexOf("#"+n)})}),l.length?(l.unshift({text:"None",value:""}),{name:"anchor",type:"listbox",label:"Anchors",values:l,onselect:n}):void 0}function o(){d&&d.value(e.convertURL(this.value(),"href")),c||0!==x.text.length||this.parent().parent().find("#text")[0].value(this.value())}var u,s,c,f,d,h,v,x={},g=e.selection,m=e.dom;u=g.getNode(),s=m.getParent(u,"a[href]"),x.text=c=s?s.innerText||s.textContent:g.getContent({format:"text"}),x.href=s?m.getAttrib(s,"href"):"",x.target=s?m.getAttrib(s,"target"):"",x.rel=s?m.getAttrib(s,"rel"):"","IMG"==u.nodeName&&(x.text=c=" "),t&&(d={type:"listbox",label:"Link list",values:l(),onselect:n,value:e.convertURL(x.href,"href"),onPostRender:function(){d=this}}),e.settings.target_list!==!1&&(v={name:"target",type:"listbox",label:"Target",values:r(x.target)}),e.settings.rel_list&&(h={name:"rel",type:"listbox",label:"Rel",values:i(x.rel)}),f=e.windowManager.open({title:"Insert link",data:x,body:[{name:"href",type:"filepicker",filetype:"file",size:40,autofocus:!0,label:"Url",onchange:o,onkeyup:o},{name:"text",type:"textbox",size:40,label:"Text to display",onchange:function(){x.text=this.value()}},a(x.href),d,h,v],onSubmit:function(t){function n(t,n){window.setTimeout(function(){e.windowManager.confirm(t,n)},0)}function l(){i.text!=c?s?(e.focus(),s.innerHTML=i.text,m.setAttribs(s,{href:r,target:i.target?i.target:null,rel:i.rel?i.rel:null}),g.select(s)):e.insertContent(m.createHTML("a",{href:r,target:i.target?i.target:null,rel:i.rel?i.rel:null},i.text)):e.execCommand("mceInsertLink",!1,{href:r,target:i.target,rel:i.rel?i.rel:null})}var i=t.data,r=i.href;return r?r.indexOf("@")>0&&-1==r.indexOf("//")&&-1==r.indexOf("mailto:")?(n("The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?",function(e){e&&(r="mailto:"+r),l()}),void 0):/^\s*www\./i.test(r)?(n("The URL you entered seems to be an external link. Do you want to add the required http:// prefix?",function(e){e&&(r="http://"+r),l()}),void 0):(l(),void 0):(e.execCommand("unlink"),void 0)}})}e.addButton("link",{icon:"link",tooltip:"Insert/edit link",shortcut:"Ctrl+K",onclick:t(n),stateSelector:"a[href]"}),e.addButton("unlink",{icon:"unlink",tooltip:"Remove link",cmd:"unlink",stateSelector:"a[href]"}),e.addShortcut("Ctrl+K","",t(n)),this.showDialog=n,e.addMenuItem("link",{icon:"link",text:"Insert link",shortcut:"Ctrl+K",onclick:t(n),stateSelector:"a[href]",context:"insert",prependToContext:!0})}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/lists/plugin.min.js b/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/lists/plugin.min.js deleted file mode 100755 index 3f1de3972e..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/lists/plugin.min.js +++ /dev/null @@ -1 +0,0 @@ -tinymce.PluginManager.add("lists",function(e){function t(e){return e&&/^(OL|UL)$/.test(e.nodeName)}function n(e){return e.parentNode.firstChild==e}function r(e){return e.parentNode.lastChild==e}function o(t){return t&&!!e.schema.getTextBlockElements()[t.nodeName]}function i(e){return e&&"SPAN"===e.nodeName&&"bookmark"===e.getAttribute("data-mce-type")}var a=this;e.on("init",function(){function d(e){function t(t){var r,o,i;o=e[t?"startContainer":"endContainer"],i=e[t?"startOffset":"endOffset"],1==o.nodeType&&(r=b.create("span",{"data-mce-type":"bookmark"}),o.hasChildNodes()?(i=Math.min(i,o.childNodes.length-1),t?o.insertBefore(r,o.childNodes[i]):b.insertAfter(r,o.childNodes[i])):o.appendChild(r),o=r,i=0),n[t?"startContainer":"endContainer"]=o,n[t?"startOffset":"endOffset"]=i}var n={};return t(!0),e.collapsed||t(),n}function s(e){function t(t){function n(e){for(var t=e.parentNode.firstChild,n=0;t;){if(t==e)return n;(1!=t.nodeType||"bookmark"!=t.getAttribute("data-mce-type"))&&n++,t=t.nextSibling}return-1}var r,o,i;r=i=e[t?"startContainer":"endContainer"],o=e[t?"startOffset":"endOffset"],r&&(1==r.nodeType&&(o=n(r),r=r.parentNode,b.remove(i)),e[t?"startContainer":"endContainer"]=r,e[t?"startOffset":"endOffset"]=o)}t(!0),t();var n=b.createRng();n.setStart(e.startContainer,e.startOffset),e.endContainer&&n.setEnd(e.endContainer,e.endOffset),L.setRng(n)}function f(t,n){var r,o,i,a=b.createFragment(),d=e.schema.getBlockElements();if(e.settings.forced_root_block&&(n=n||e.settings.forced_root_block),n&&(o=b.create(n),o.tagName===e.settings.forced_root_block&&b.setAttribs(o,e.settings.forced_root_block_attrs),a.appendChild(o)),t)for(;r=t.firstChild;){var s=r.nodeName;i||"SPAN"==s&&"bookmark"==r.getAttribute("data-mce-type")||(i=!0),d[s]?(a.appendChild(r),o=null):n?(o||(o=b.create(n),a.appendChild(o)),o.appendChild(r)):a.appendChild(r)}return e.settings.forced_root_block?i||tinymce.Env.ie&&!(tinymce.Env.ie>10)||o.appendChild(b.create("br",{"data-mce-bogus":"1"})):a.appendChild(b.create("br")),a}function l(){return tinymce.grep(L.getSelectedBlocks(),function(e){return"LI"==e.nodeName})}function c(e,t,n){var r,o,i=b.select('span[data-mce-type="bookmark"]',e);n=n||f(t),r=b.createRng(),r.setStartAfter(t),r.setEndAfter(e),o=r.extractContents(),b.isEmpty(o)||b.insertAfter(o,e),b.insertAfter(n,e),b.isEmpty(t.parentNode)&&(tinymce.each(i,function(e){t.parentNode.parentNode.insertBefore(e,t.parentNode)}),b.remove(t.parentNode)),b.remove(t)}function p(e){var n,r;if(n=e.nextSibling,n&&t(n)&&n.nodeName==e.nodeName){for(;r=n.firstChild;)e.appendChild(r);b.remove(n)}if(n=e.previousSibling,n&&t(n)&&n.nodeName==e.nodeName){for(;r=n.firstChild;)e.insertBefore(r,e.firstChild);b.remove(n)}}function u(e){tinymce.each(tinymce.grep(b.select("ol,ul",e)),function(e){var n,r=e.parentNode;"LI"==r.nodeName&&r.firstChild==e&&(n=r.previousSibling,n&&"LI"==n.nodeName&&(n.appendChild(e),b.isEmpty(r)&&b.remove(r))),t(r)&&(n=r.previousSibling,n&&"LI"==n.nodeName&&n.appendChild(e))})}function m(e){function o(e){b.isEmpty(e)&&b.remove(e)}var i,a=e.parentNode,d=a.parentNode;return n(e)&&r(e)?("LI"==d.nodeName?(b.insertAfter(e,d),o(d),b.remove(a)):t(d)?b.remove(a,!0):(d.insertBefore(f(e),a),b.remove(a)),!0):n(e)?("LI"==d.nodeName?(b.insertAfter(e,d),e.appendChild(a),o(d)):t(d)?d.insertBefore(e,a):(d.insertBefore(f(e),a),b.remove(e)),!0):r(e)?("LI"==d.nodeName?b.insertAfter(e,d):t(d)?b.insertAfter(e,a):(b.insertAfter(f(e),a),b.remove(e)),!0):("LI"==d.nodeName?(a=d,i=f(e,"LI")):i=t(d)?f(e,"LI"):f(e),c(a,e,i),u(a.parentNode),!0)}function h(e){function n(n,r){var o;if(t(n)){for(;o=e.lastChild.firstChild;)r.appendChild(o);b.remove(n)}}var r,o;return r=e.previousSibling,r&&t(r)?(r.appendChild(e),!0):r&&"LI"==r.nodeName&&t(r.lastChild)?(r.lastChild.appendChild(e),n(e.lastChild,r.lastChild),!0):(r=e.nextSibling,r&&t(r)?(r.insertBefore(e,r.firstChild),!0):r&&"LI"==r.nodeName&&t(e.lastChild)?!1:(r=e.previousSibling,r&&"LI"==r.nodeName?(o=b.create(e.parentNode.nodeName),r.appendChild(o),o.appendChild(e),n(e.lastChild,o),!0):!1))}function v(){var t=l();if(t.length){for(var n=d(L.getRng(!0)),r=0;r0))return n;for(var o=new tinymce.dom.TreeWalker(e.startContainer);n=o[t?"next":"prev"]();)if(3==n.nodeType&&n.data.length>0)return n}function r(e,n){var r,o,i=e.parentNode;for(t(n.lastChild)&&(o=n.lastChild),r=n.lastChild,r&&"BR"==r.nodeName&&e.hasChildNodes()&&b.remove(r);r=e.firstChild;)n.appendChild(r);o&&n.appendChild(o),b.remove(e),b.isEmpty(i)&&b.remove(i)}if(L.isCollapsed()){var o=b.getParent(L.getStart(),"LI");if(o){var i=L.getRng(!0),a=b.getParent(n(i,e),"LI");if(a&&a!=o){var f=d(i);return e?r(a,o):r(o,a),s(f),!0}if(!a&&!e&&N(o.parentNode.nodeName))return!0}}},e.addCommand("Indent",function(){return v()?void 0:!0}),e.addCommand("Outdent",function(){return C()?void 0:!0}),e.addCommand("InsertUnorderedList",function(){y("UL")}),e.addCommand("InsertOrderedList",function(){y("OL")}),e.on("keydown",function(t){9==t.keyCode&&e.dom.getParent(e.selection.getStart(),"LI")&&(t.preventDefault(),t.shiftKey?C():v())})}),e.addButton("indent",{icon:"indent",title:"Increase indent",cmd:"Indent",onPostRender:function(){var t=this;e.on("nodechange",function(){var r=e.dom.getParent(e.selection.getNode(),"LI,UL,OL");t.disabled(r&&("LI"!=r.nodeName||n(r)))})}}),e.on("keydown",function(e){e.keyCode==tinymce.util.VK.BACKSPACE?a.backspaceDelete()&&e.preventDefault():e.keyCode==tinymce.util.VK.DELETE&&a.backspaceDelete(!0)&&e.preventDefault()})}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/media/moxieplayer.swf b/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/media/moxieplayer.swf deleted file mode 100755 index 19c771bea5..0000000000 Binary files a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/media/moxieplayer.swf and /dev/null differ diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/media/plugin.min.js b/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/media/plugin.min.js deleted file mode 100755 index eb524e50ef..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/media/plugin.min.js +++ /dev/null @@ -1 +0,0 @@ -tinymce.PluginManager.add("media",function(e,t){function i(e){return-1!=e.indexOf(".mp3")?"audio/mpeg":-1!=e.indexOf(".wav")?"audio/wav":-1!=e.indexOf(".mp4")?"video/mp4":-1!=e.indexOf(".webm")?"video/webm":-1!=e.indexOf(".ogg")?"video/ogg":-1!=e.indexOf(".swf")?"application/x-shockwave-flash":""}function o(){function t(e){var t,a,r,c;t=i.find("#width")[0],a=i.find("#height")[0],r=t.value(),c=a.value(),i.find("#constrain")[0].checked()&&o&&s&&r&&c&&(e.control==t?(c=Math.round(r/o*c),a.value(c)):(r=Math.round(c/s*r),t.value(r))),o=r,s=c}var i,o,s,m;m=n(e.selection.getNode()),o=m.width,s=m.height,i=e.windowManager.open({title:"Insert/edit video",data:m,bodyType:"tabpanel",body:[{title:"General",type:"form",onShowTab:function(){this.fromJSON(c(this.next().find("#embed").value()))},items:[{name:"source1",type:"filepicker",filetype:"media",size:40,autofocus:!0,label:"Source"},{name:"source2",type:"filepicker",filetype:"media",size:40,label:"Alternative source"},{name:"poster",type:"filepicker",filetype:"image",size:40,label:"Poster"},{type:"container",label:"Dimensions",layout:"flex",align:"center",spacing:5,items:[{name:"width",type:"textbox",maxLength:3,size:3,onchange:t},{type:"label",text:"x"},{name:"height",type:"textbox",maxLength:3,size:3,onchange:t},{name:"constrain",type:"checkbox",checked:!0,text:"Constrain proportions"}]}]},{title:"Embed",type:"panel",layout:"flex",direction:"column",align:"stretch",padding:10,spacing:10,onShowTab:function(){this.find("#embed").value(r(this.parent().toJSON()))},items:[{type:"label",text:"Paste your embed code below:"},{type:"textbox",flex:1,name:"embed",value:a(),multiline:!0,label:"Source"}]}],onSubmit:function(){e.insertContent(r(this.toJSON()))}})}function a(){var t=e.selection.getNode();return t.getAttribute("data-mce-object")?e.selection.getContent():void 0}function r(o){var a="";return o.source1||(tinymce.extend(o,c(o.embed)),o.source1)?(o.source1=e.convertURL(o.source1,"source"),o.source2=e.convertURL(o.source2,"source"),o.source1mime=i(o.source1),o.source2mime=i(o.source2),o.poster=e.convertURL(o.poster,"poster"),o.flashPlayerUrl=e.convertURL(t+"/moxieplayer.swf","movie"),o.embed?a=s(o.embed,o,!0):(tinymce.each(m,function(e){var t,i,a;if(t=e.regex.exec(o.source1)){for(a=e.url,i=0;t[i];i++)a=a.replace("$"+i,function(){return t[i]});o.source1=a,o.type=e.type,o.width=e.w,o.height=e.h}}),o.width=o.width||300,o.height=o.height||150,tinymce.each(o,function(t,i){o[i]=e.dom.encode(t)}),"iframe"==o.type?a+='':"application/x-shockwave-flash"==o.source1mime?(a+='',o.poster&&(a+=''),a+=""):-1!=o.source1mime.indexOf("audio")?e.settings.audio_template_callback?a=e.settings.audio_template_callback(o):a+='":a=e.settings.video_template_callback?e.settings.video_template_callback(o):'"),a):""}function c(e){var t={};return new tinymce.html.SaxParser({validate:!1,allow_conditional_comments:!0,special:"script,noscript",start:function(e,i){t.source1||"param"!=e||(t.source1=i.map.movie),("iframe"==e||"object"==e||"embed"==e||"video"==e||"audio"==e)&&(t=tinymce.extend(i.map,t)),"source"==e&&(t.source1?t.source2||(t.source2=i.map.src):t.source1=i.map.src),"img"!=e||t.poster||(t.poster=i.map.src)}}).parse(e),t.source1=t.source1||t.src||t.data,t.source2=t.source2||"",t.poster=t.poster||"",t}function n(t){return t.getAttribute("data-mce-object")?c(e.serializer.serialize(t,{selection:!0})):{}}function s(e,t,i){function o(e,t){var i,o,a,r;for(i in t)if(a=""+t[i],e.map[i])for(o=e.length;o--;)r=e[o],r.name==i&&(a?(e.map[i]=a,r.value=a):(delete e.map[i],e.splice(o,1)));else a&&(e.push({name:i,value:a}),e.map[i]=a)}var a,r=new tinymce.html.Writer,c=0;return new tinymce.html.SaxParser({validate:!1,allow_conditional_comments:!0,special:"script,noscript",comment:function(e){r.comment(e)},cdata:function(e){r.cdata(e)},text:function(e,t){r.text(e,t)},start:function(e,n,s){switch(e){case"video":case"object":case"embed":case"img":case"iframe":o(n,{width:t.width,height:t.height})}if(i)switch(e){case"video":o(n,{poster:t.poster,src:""}),t.source2&&o(n,{src:""});break;case"iframe":o(n,{src:t.source1});break;case"source":if(c++,2>=c&&(o(n,{src:t["source"+c],type:t["source"+c+"mime"]}),!t["source"+c]))return;break;case"img":if(!t.poster)return;a=!0}r.start(e,n,s)},end:function(e){if("video"==e&&i)for(var n=1;2>=n;n++)if(t["source"+n]){var s=[];s.map={},n>c&&(o(s,{src:t["source"+n],type:t["source"+n+"mime"]}),r.start("source",s,!0))}if(t.poster&&"object"==e&&i&&!a){var m=[];m.map={},o(m,{src:t.poster,width:t.width,height:t.height}),r.start("img",m,!0)}r.end(e)}},new tinymce.html.Schema({})).parse(e),r.getContent()}var m=[{regex:/youtu\.be\/([a-z1-9.-_]+)/,type:"iframe",w:425,h:350,url:"http://www.youtube.com/embed/$1"},{regex:/youtube\.com(.+)v=([^&]+)/,type:"iframe",w:425,h:350,url:"http://www.youtube.com/embed/$2"},{regex:/vimeo\.com\/([0-9]+)/,type:"iframe",w:425,h:350,url:"http://player.vimeo.com/video/$1?title=0&byline=0&portrait=0&color=8dc7dc"},{regex:/maps\.google\.([a-z]{2,3})\/maps\/(.+)msid=(.+)/,type:"iframe",w:425,h:350,url:'http://maps.google.com/maps/ms?msid=$2&output=embed"'}];e.on("ResolveName",function(e){var t;1==e.target.nodeType&&(t=e.target.getAttribute("data-mce-object"))&&(e.name=t)}),e.on("preInit",function(){var t=e.schema.getSpecialElements();tinymce.each("video audio iframe object".split(" "),function(e){t[e]=new RegExp("]*>","gi")}),e.schema.addValidElements("object[id|style|width|height|classid|codebase|*],embed[id|style|width|height|type|src|*],video[*],audio[*]");var i=e.schema.getBoolAttrs();tinymce.each("webkitallowfullscreen mozallowfullscreen allowfullscreen".split(" "),function(e){i[e]={}}),e.parser.addNodeFilter("iframe,video,audio,object,embed",function(t,i){for(var o,a,r,c,n,s,m,d=t.length;d--;){for(a=t[d],r=new tinymce.html.Node("img",1),r.shortEnded=!0,s=a.attributes,o=s.length;o--;)c=s[o].name,n=s[o].value,"width"!==c&&"height"!==c&&"style"!==c&&(("data"==c||"src"==c)&&(n=e.convertURL(n,c)),r.attr("data-mce-p-"+c,n));m=a.firstChild&&a.firstChild.value,m&&(r.attr("data-mce-html",escape(m)),r.firstChild=null),r.attr({width:a.attr("width")||"300",height:a.attr("height")||("audio"==i?"30":"150"),style:a.attr("style"),src:tinymce.Env.transparentSrc,"data-mce-object":i,"class":"mce-object mce-object-"+i}),a.replace(r)}}),e.serializer.addAttributeFilter("data-mce-object",function(e,t){for(var i,o,a,r,c,n,s=e.length;s--;){for(i=e[s],o=new tinymce.html.Node(i.attr(t),1),"audio"!=i.attr(t)&&o.attr({width:i.attr("width"),height:i.attr("height")}),o.attr({style:i.attr("style")}),r=i.attributes,a=r.length;a--;){var m=r[a].name;0===m.indexOf("data-mce-p-")&&o.attr(m.substr(11),r[a].value)}c=i.attr("data-mce-html"),c&&(n=new tinymce.html.Node("#text",3),n.raw=!0,n.value=unescape(c),o.append(n)),i.replace(o)}})}),e.on("ObjectSelected",function(e){"audio"==e.target.getAttribute("data-mce-object")&&e.preventDefault()}),e.on("objectResized",function(e){var t,i=e.target;i.getAttribute("data-mce-object")&&(t=i.getAttribute("data-mce-html"),t&&(t=unescape(t),i.setAttribute("data-mce-html",escape(s(t,{width:e.width,height:e.height})))))}),e.addButton("media",{tooltip:"Insert/edit video",onclick:o,stateSelector:"img[data-mce-object=video]"}),e.addMenuItem("media",{icon:"media",text:"Insert video",onclick:o,context:"insert",prependToContext:!0})}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/nonbreaking/plugin.min.js b/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/nonbreaking/plugin.min.js deleted file mode 100755 index 866339c7dc..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/nonbreaking/plugin.min.js +++ /dev/null @@ -1 +0,0 @@ -tinymce.PluginManager.add("nonbreaking",function(e){var t=e.getParam("nonbreaking_force_tab");if(e.addCommand("mceNonBreaking",function(){e.insertContent(e.plugins.visualchars&&e.plugins.visualchars.state?' ':" ")}),e.addButton("nonbreaking",{title:"Insert nonbreaking space",cmd:"mceNonBreaking"}),e.addMenuItem("nonbreaking",{text:"Nonbreaking space",cmd:"mceNonBreaking",context:"insert"}),t){var n=+t>1?+t:3;e.on("keydown",function(t){if(9==t.keyCode){if(t.shiftKey)return;t.preventDefault();for(var i=0;n>i;i++)e.execCommand("mceNonBreaking")}})}}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/noneditable/plugin.min.js b/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/noneditable/plugin.min.js deleted file mode 100755 index dd15d59ee0..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/noneditable/plugin.min.js +++ /dev/null @@ -1 +0,0 @@ -tinymce.PluginManager.add("noneditable",function(e){function t(){function t(e){var t;if(1===e.nodeType){if(t=e.getAttribute(s),t&&"inherit"!==t)return t;if(t=e.contentEditable,"inherit"!==t)return t}return null}function n(e){for(var n;e;){if(n=t(e))return"false"===n?e:null;e=e.parentNode}}function i(e){for(;e;){if(e.id===g)return e;e=e.parentNode}}function o(e){var t;if(e)for(t=new r(e,e),e=t.current();e;e=t.next())if(3===e.nodeType)return e}function a(n,i){var o,a;return"false"===t(n)&&m.isBlock(n)?(f.select(n),void 0):(a=m.createRng(),"true"===t(n)&&(n.firstChild||n.appendChild(e.getDoc().createTextNode(" ")),n=n.firstChild,i=!0),o=m.create("span",{id:g,"data-mce-bogus":!0},p),i?n.parentNode.insertBefore(o,n):m.insertAfter(o,n),a.setStart(o.firstChild,1),a.collapse(!0),f.setRng(a),o)}function l(e){var t,n,a,r;if(e)t=f.getRng(!0),t.setStartBefore(e),t.setEndBefore(e),n=o(e),n&&n.nodeValue.charAt(0)==p&&(n=n.deleteData(0,1)),m.remove(e,!0),f.setRng(t);else for(a=i(f.getStart());(e=m.get(g))&&e!==r;)a!==e&&(n=o(e),n&&n.nodeValue.charAt(0)==p&&(n=n.deleteData(0,1)),m.remove(e,!0)),r=e}function d(){function e(e,n){var i,o,a,l,s;if(i=c.startContainer,o=c.startOffset,3==i.nodeType){if(s=i.nodeValue.length,o>0&&s>o||(n?o==s:0===o))return}else{if(!(o0?o-1:o;i=i.childNodes[d],i.hasChildNodes()&&(i=i.firstChild)}for(a=new r(i,e);l=a[n?"prev":"next"]();){if(3===l.nodeType&&l.nodeValue.length>0)return;if("true"===t(l))return l}return e}var i,o,s,c,d;l(),s=f.isCollapsed(),i=n(f.getStart()),o=n(f.getEnd()),(i||o)&&(c=f.getRng(!0),s?(i=i||o,(d=e(i,!0))?a(d,!0):(d=e(i,!1))?a(d,!1):f.select(i)):(c=f.getRng(!0),i&&c.setStartBefore(i),o&&c.setEndAfter(o),f.setRng(c)))}function u(o){function a(e,t){for(;e=e[t?"previousSibling":"nextSibling"];)if(3!==e.nodeType||e.nodeValue.length>0)return e}function s(e,t){f.select(e),f.collapse(t)}function u(o){function a(e){for(var t=s;t;){if(t===e)return;t=t.parentNode}m.remove(e),d()}function r(){var i,r,l=e.schema.getNonEmptyElements();for(r=new tinymce.dom.TreeWalker(s,e.getBody());(i=o?r.prev():r.next())&&!l[i.nodeName.toLowerCase()]&&!(3===i.nodeType&&tinymce.trim(i.nodeValue).length>0);)if("false"===t(i))return a(i),!0;return n(i)?!0:!1}var l,s,c,u;if(f.isCollapsed()){if(l=f.getRng(!0),s=l.startContainer,c=l.startOffset,s=i(s)||s,u=n(s))return a(u),!1;if(3==s.nodeType&&(o?c>0:cv||v>124)&&v!=c.DELETE&&v!=c.BACKSPACE){if((tinymce.isMac?o.metaKey:o.ctrlKey)&&(67==v||88==v||86==v))return;if(o.preventDefault(),v==c.LEFT||v==c.RIGHT){var b=v==c.LEFT;if(e.dom.isBlock(g)){var x=b?g.previousSibling:g.nextSibling,w=new r(x,x),C=b?w.prev():w.next();s(C,!b)}else s(g,b)}}else if(v==c.LEFT||v==c.RIGHT||v==c.BACKSPACE||v==c.DELETE){if(p=i(h)){if(v==c.LEFT||v==c.BACKSPACE)if(g=a(p,!0),g&&"false"===t(g)){if(o.preventDefault(),v!=c.LEFT)return m.remove(g),void 0;s(g,!0)}else l(p);if(v==c.RIGHT||v==c.DELETE)if(g=a(p),g&&"false"===t(g)){if(o.preventDefault(),v!=c.RIGHT)return m.remove(g),void 0;s(g,!1)}else l(p)}if((v==c.BACKSPACE||v==c.DELETE)&&!u(v==c.BACKSPACE))return o.preventDefault(),!1}}var m=e.dom,f=e.selection,g="mce_noneditablecaret",p="";e.on("mousedown",function(n){var i=e.selection.getNode();"false"===t(i)&&i==n.target&&d()}),e.on("mouseup keyup",d),e.on("keydown",u)}function n(t){var n=a.length,i=t.content,r=tinymce.trim(o);if("raw"!=t.format){for(;n--;)i=i.replace(a[n],function(t){var n=arguments,o=n[n.length-2];return o>0&&'"'==i.charAt(o-1)?t:''+e.dom.encode("string"==typeof n[1]?n[1]:n[0])+""});t.content=i}}var i,o,a,r=tinymce.dom.TreeWalker,l="contenteditable",s="data-mce-"+l,c=tinymce.util.VK;i=" "+tinymce.trim(e.getParam("noneditable_editable_class","mceEditable"))+" ",o=" "+tinymce.trim(e.getParam("noneditable_noneditable_class","mceNonEditable"))+" ",a=e.getParam("noneditable_regexp"),a&&!a.length&&(a=[a]),e.on("PreInit",function(){t(),a&&e.on("BeforeSetContent",n),e.parser.addAttributeFilter("class",function(e){for(var t,n,a=e.length;a--;)n=e[a],t=" "+n.attr("class")+" ",-1!==t.indexOf(i)?n.attr(s,"true"):-1!==t.indexOf(o)&&n.attr(s,"false")}),e.serializer.addAttributeFilter(s,function(e){for(var t,n=e.length;n--;)t=e[n],a&&t.attr("data-mce-content")?(t.name="#text",t.type=3,t.raw=!0,t.value=t.attr("data-mce-content")):(t.attr(l,null),t.attr(s,null))}),e.parser.addAttributeFilter(l,function(e){for(var t,n=e.length;n--;)t=e[n],t.attr(s,t.attr(l)),t.attr(l,null)})})}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/pagebreak/plugin.min.js b/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/pagebreak/plugin.min.js deleted file mode 100755 index e224cb4414..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/pagebreak/plugin.min.js +++ /dev/null @@ -1 +0,0 @@ -tinymce.PluginManager.add("pagebreak",function(e){var a="mce-pagebreak",t=e.getParam("pagebreak_separator",""),n=new RegExp(t.replace(/[\?\.\*\[\]\(\)\{\}\+\^\$\:]/g,function(e){return"\\"+e}),"gi"),r='';e.addCommand("mcePageBreak",function(){e.settings.pagebreak_split_block?e.insertContent("

"+r+"

"):e.insertContent(r)}),e.addButton("pagebreak",{title:"Page break",cmd:"mcePageBreak"}),e.addMenuItem("pagebreak",{text:"Page break",icon:"pagebreak",cmd:"mcePageBreak",context:"insert"}),e.on("ResolveName",function(t){"IMG"==t.target.nodeName&&e.dom.hasClass(t.target,a)&&(t.name="pagebreak")}),e.on("click",function(t){t=t.target,"IMG"===t.nodeName&&e.dom.hasClass(t,a)&&e.selection.select(t)}),e.on("BeforeSetContent",function(e){e.content=e.content.replace(n,r)}),e.on("PreInit",function(){e.serializer.addNodeFilter("img",function(a){for(var n,r,c=a.length;c--;)if(n=a[c],r=n.attr("class"),r&&-1!==r.indexOf("mce-pagebreak")){var o=n.parent;if(e.schema.getBlockElements()[o.name]&&e.settings.pagebreak_split_block){o.type=3,o.value=t,o.raw=!0,n.remove();continue}n.type=3,n.value=t,n.raw=!0}})})}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/paste/plugin.min.js b/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/paste/plugin.min.js deleted file mode 100755 index dd3f20c644..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/paste/plugin.min.js +++ /dev/null @@ -1 +0,0 @@ -!function(e,t){"use strict";function n(e,t){for(var n,i=[],r=0;r"),t&&/^(PRE|DIV)$/.test(t.nodeName)||!a?e=n.filter(e,[[/\n/g,"
"]]):(e=n.filter(e,[[/\n\n/g,"

"+o],[/^(.*<\/p>)(

)$/,o+"$1"],[/\n/g,"
"]]),-1!=e.indexOf("

")&&(e=o+e)),r(e)}function o(){var e=i.dom,t=i.getBody(),n=i.dom.getViewPort(i.getWin()),r=i.inline?t.clientHeight:n.h;s(),d=e.add(i.getBody(),"div",{id:"mcepastebin",contentEditable:!0,"data-mce-bogus":"1",style:"position: fixed; top: 20px;width: 10px; height: "+(r-40)+"px; overflow: hidden; opacity: 0"},m),e.setStyle(d,"left","rtl"==e.getStyle(t,"direction",!0)?65535:-65535),e.bind(d,"beforedeactivate focusin focusout",function(e){e.stopPropagation()}),u=i.selection.getRng(),d.focus(),i.selection.select(d,!0)}function s(){d&&(i.dom.unbind(d),i.dom.remove(d),u&&i.selection.setRng(u)),g=!1,d=u=null}function l(){return d?d.innerHTML:m}function c(e){var t={},n=e.clipboardData||i.getDoc().dataTransfer;if(n&&n.types){t["text/plain"]=n.getData("Text");for(var r=0;rl?n&&(n=n.parent.parent):(i=n,n=null)),n&&n.name==o?n.append(e):(i=i||n,n=new r(o,1),s>1&&n.attr("start",""+s),e.wrap(n)),e.name="li",t.value="";var c=t.next;c&&3==c.type&&(c.value=c.value.replace(/^\u00a0+/,"")),l>a&&i&&i.lastChild.append(n),a=l}for(var n,i,a=1,o=e.getAll("p"),s=0;s/gi,/<(!|script[^>]*>.*?<\/script(?=[>\s])|\/?(\?xml(:\w+)?|img|meta|link|style|\w:\w+)(?=[\s\/>]))[^>]*>/gi,[/<(\/?)s>/gi,"<$1strike>"],[/ /gi,"\xa0"],[/([\s\u00a0]*)<\/span>/gi,function(e,t){return t.length>0?t.replace(/./," ").slice(Math.floor(t.length/2)).split("").join("\xa0"):""}]]);var g=l.paste_word_valid_elements;g||(g="@[style],-strong/b,-em/i,-span,-p,-ol,-ul,-li,-h1,-h2,-h3,-h4,-h5,-h6,-table,-tr,-td[colspan|rowspan],-th,-thead,-tfoot,-tbody,-a[href|name],sub,sup,strike,br");var v=new n({valid_elements:g}),h=new t({},v);h.addAttributeFilter("style",function(e){for(var t=e.length,n;t--;)n=e[t],n.attr("style",d(n,n.attr("style"))),"span"!=n.name||n.attributes.length||n.unwrap()}),h.addNodeFilter("a",function(e){for(var t=e.length,n,i,r;t--;)n=e[t],i=n.attr("href"),r=n.attr("name"),i&&0===i.indexOf("file://")&&(i=i.split("#")[1],i&&(i="#"+i)),i||r?n.attr({href:i,name:r}):n.unwrap()});var b=h.parse(u);p(b),c.content=new i({},v).serialize(b)}})}return s.isWordContent=o,s}),i(b,[f,c,g,l],function(e,t,n,i){return function(r){function a(e){r.on("BeforePastePreProcess",function(t){t.content=e(t.content)})}function o(e){return e=i.filter(e,[/^[\s\S]*|[\s\S]*$/g,[/\u00a0<\/span>/g,"\xa0"],/
$/])}function s(e){if(!n.isWordContent(e))return e;var a=[];t.each(r.schema.getBlockElements(),function(e,t){a.push(t)});var o=new RegExp("(?:
 [\\s\\r\\n]+|
)*(<\\/?("+a.join("|")+")[^>]*>)(?:
 [\\s\\r\\n]+|
)*","g");return e=i.filter(e,[[o,"$1"]]),e=i.filter(e,[[/

/g,"

"],[/
/g," "],[/

/g,"
"]])}function l(e){return(r.settings.paste_remove_styles||r.settings.paste_remove_styles_if_webkit!==!1)&&(e=e.replace(/ style=\"[^\"]+\"/g,"")),e}e.webkit&&(a(l),a(o)),e.ie&&a(s)}}),i(y,[P,u,g,b],function(e,t,n,i){var r;e.add("paste",function(e){function a(){"text"==s.pasteFormat?(this.active(!1),s.pasteFormat="html"):(s.pasteFormat="text",this.active(!0),r||(e.windowManager.alert("Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off."),r=!0))}var o=this,s,l=e.settings;o.clipboard=s=new t(e),o.quirks=new i(e),o.wordFilter=new n(e),e.settings.paste_as_text&&(o.clipboard.pasteFormat="text"),l.paste_preprocess&&e.on("PastePreProcess",function(e){l.paste_preprocess.call(o,o,e)}),l.paste_postprocess&&e.on("PastePostProcess",function(e){l.paste_postprocess.call(o,o,e)}),e.addCommand("mceInsertClipboardContent",function(e,t){t.content&&o.clipboard.pasteHtml(t.content),t.text&&o.clipboard.pasteText(t.text)}),e.paste_block_drop&&e.on("dragend dragover draggesture dragdrop drop drag",function(e){e.preventDefault(),e.stopPropagation()}),e.settings.paste_data_images||e.on("drop",function(e){var t=e.dataTransfer;t&&t.files&&t.files.length>0&&e.preventDefault()}),e.addButton("pastetext",{icon:"pastetext",tooltip:"Paste as text",onclick:a,active:"text"==o.clipboard.pasteFormat}),e.addMenuItem("pastetext",{text:"Paste as text",selectable:!0,active:s.pasteFormat,onclick:a})})}),o([l,u,g,b,y])}(this); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/preview/plugin.min.js b/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/preview/plugin.min.js deleted file mode 100755 index 8a24058f74..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/preview/plugin.min.js +++ /dev/null @@ -1 +0,0 @@ -tinymce.PluginManager.add("preview",function(e){var t=e.settings;e.addCommand("mcePreview",function(){e.windowManager.open({title:"Preview",width:parseInt(e.getParam("plugin_preview_width","650"),10),height:parseInt(e.getParam("plugin_preview_height","500"),10),html:'',buttons:{text:"Close",onclick:function(){this.parent().parent().close()}},onPostRender:function(){var i,n=this.getEl("body").firstChild.contentWindow.document,a="";tinymce.each(e.contentCSS,function(t){a+=''});var d=t.body_id||"tinymce";-1!=d.indexOf("=")&&(d=e.getParam("body_id","","hash"),d=d[e.id]||d);var r=t.body_class||"";-1!=r.indexOf("=")&&(r=e.getParam("body_class","","hash"),r=r[e.id]||""),i=""+a+""+''+e.getContent()+""+"",n.open(),n.write(i),n.close()}})}),e.addButton("preview",{title:"Preview",cmd:"mcePreview"}),e.addMenuItem("preview",{text:"Preview",cmd:"mcePreview",context:"view"})}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/print/plugin.min.js b/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/print/plugin.min.js deleted file mode 100755 index abc37b5fd4..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/print/plugin.min.js +++ /dev/null @@ -1 +0,0 @@ -tinymce.PluginManager.add("print",function(t){t.addCommand("mcePrint",function(){t.getWin().print()}),t.addButton("print",{title:"Print",cmd:"mcePrint"}),t.addShortcut("Ctrl+P","","mcePrint"),t.addMenuItem("print",{text:"Print",cmd:"mcePrint",icon:"print",shortcut:"Ctrl+P",context:"file"})}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/save/plugin.min.js b/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/save/plugin.min.js deleted file mode 100755 index bd50cec41e..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/save/plugin.min.js +++ /dev/null @@ -1 +0,0 @@ -tinymce.PluginManager.add("save",function(e){function t(){var t,n;return t=tinymce.DOM.getParent(e.id,"form"),!e.getParam("save_enablewhendirty",!0)||e.isDirty()?(tinymce.triggerSave(),(n=e.getParam("save_onsavecallback"))?(e.execCallback("save_onsavecallback",e)&&(e.startContent=tinymce.trim(e.getContent({format:"raw"})),e.nodeChanged()),void 0):(t?(e.isNotDirty=!0,(!t.onsubmit||t.onsubmit())&&("function"==typeof t.submit?t.submit():e.windowManager.alert("Error: Form submit field collision.")),e.nodeChanged()):e.windowManager.alert("Error: No form element found."),void 0)):void 0}function n(){var t,n=tinymce.trim(e.startContent);return(t=e.getParam("save_oncancelcallback"))?(e.execCallback("save_oncancelcallback",e),void 0):(e.setContent(n),e.undoManager.clear(),e.nodeChanged(),void 0)}function i(){var t=this;e.on("nodeChange",function(){t.disabled(e.getParam("save_enablewhendirty",!0)&&!e.isDirty())})}e.addCommand("mceSave",t),e.addCommand("mceCancel",n),e.addButton("save",{icon:"save",text:"Save",cmd:"mceSave",disabled:!0,onPostRender:i}),e.addButton("cancel",{text:"Cancel",icon:!1,cmd:"mceCancel",disabled:!0,onPostRender:i}),e.addShortcut("ctrl+s","","mceSave")}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/searchreplace/plugin.min.js b/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/searchreplace/plugin.min.js deleted file mode 100755 index 7ffaf4610f..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/searchreplace/plugin.min.js +++ /dev/null @@ -1 +0,0 @@ -!function(){function e(e,t,n,a,r){function i(e,t){if(t=t||0,!e[0])throw"findAndReplaceDOMText cannot handle zero-length matches";var n=e.index;if(t>0){var a=e[t];if(!a)throw"Invalid capture group";n+=e[0].indexOf(a),e[0]=a}return[n,n+e[0].length,[e[0]]]}function d(e){var t;if(3===e.nodeType)return e.data;if(h[e.nodeName]&&!u[e.nodeName])return"";if(t="",(u[e.nodeName]||m[e.nodeName])&&(t+="\n"),e=e.firstChild)do t+=d(e);while(e=e.nextSibling);return t}function o(e,t,n){var a,r,i,d,o=[],l=0,c=e,s=t.shift(),f=0;e:for(;;){if((u[c.nodeName]||m[c.nodeName])&&l++,3===c.nodeType&&(!r&&c.length+l>=s[1]?(r=c,d=s[1]-l):a&&o.push(c),!a&&c.length+l>s[0]&&(a=c,i=s[0]-l),l+=c.length),a&&r){if(c=n({startNode:a,startNodeIndex:i,endNode:r,endNodeIndex:d,innerNodes:o,match:s[2],matchIndex:f}),l-=r.length-d,a=null,r=null,o=[],s=t.shift(),f++,!s)break}else{if((!h[c.nodeName]||u[c.nodeName])&&c.firstChild){c=c.firstChild;continue}if(c.nextSibling){c=c.nextSibling;continue}}for(;;){if(c.nextSibling){c=c.nextSibling;break}if(c.parentNode===e)break e;c=c.parentNode}}}function l(e){var t;if("function"!=typeof e){var n=e.nodeType?e:f.createElement(e);t=function(e,t){var a=n.cloneNode(!1);return a.setAttribute("data-mce-index",t),e&&a.appendChild(f.createTextNode(e)),a}}else t=e;return function(e){var n,a,r,i=e.startNode,d=e.endNode,o=e.matchIndex;if(i===d){var l=i;r=l.parentNode,e.startNodeIndex>0&&(n=f.createTextNode(l.data.substring(0,e.startNodeIndex)),r.insertBefore(n,l));var c=t(e.match[0],o);return r.insertBefore(c,l),e.endNodeIndexh;++h){var g=e.innerNodes[h],p=t(g.data,o);g.parentNode.replaceChild(p,g),u.push(p)}var x=t(d.data.substring(0,e.endNodeIndex),o);return r=i.parentNode,r.insertBefore(n,i),r.insertBefore(s,i),r.removeChild(i),r=d.parentNode,r.insertBefore(x,d),r.insertBefore(a,d),r.removeChild(d),x}}var c,s,f,u,h,m,g=[],p=0;if(f=t.ownerDocument,u=r.getBlockElements(),h=r.getWhiteSpaceElements(),m=r.getShortEndedElements(),s=d(t)){if(e.global)for(;c=e.exec(s);)g.push(i(c,a));else c=s.match(e),g.push(i(c,a));return g.length&&(p=g.length,o(t,g,l(n))),p}}function t(t){function n(){function e(){r.statusbar.find("#next").disabled(!d(s+1).length),r.statusbar.find("#prev").disabled(!d(s-1).length)}function n(){tinymce.ui.MessageBox.alert("Could not find the specified string.",function(){r.find("#find")[0].focus()})}var a={},r=tinymce.ui.Factory.create({type:"window",layout:"flex",pack:"center",align:"center",onClose:function(){t.focus(),c.done()},onSubmit:function(t){var i,o,l,f;return t.preventDefault(),o=r.find("#case").checked(),f=r.find("#words").checked(),l=r.find("#find").value(),l.length?a.text==l&&a.caseState==o&&a.wholeWord==f?0===d(s+1).length?(n(),void 0):(c.next(),e(),void 0):(i=c.find(l,o,f),i||n(),r.statusbar.items().slice(1).disabled(0===i),e(),a={text:l,caseState:o,wholeWord:f},void 0):(c.done(!1),r.statusbar.items().slice(1).disabled(!0),void 0)},buttons:[{text:"Find",onclick:function(){r.submit()}},{text:"Replace",disabled:!0,onclick:function(){c.replace(r.find("#replace").value())||(r.statusbar.items().slice(1).disabled(!0),s=-1,a={})}},{text:"Replace all",disabled:!0,onclick:function(){c.replace(r.find("#replace").value(),!0,!0),r.statusbar.items().slice(1).disabled(!0),a={}}},{type:"spacer",flex:1},{text:"Prev",name:"prev",disabled:!0,onclick:function(){c.prev(),e()}},{text:"Next",name:"next",disabled:!0,onclick:function(){c.next(),e()}}],title:"Find and replace",items:{type:"form",padding:20,labelGap:30,spacing:10,items:[{type:"textbox",name:"find",size:40,label:"Find",value:t.selection.getNode().src},{type:"textbox",name:"replace",size:40,label:"Replace with"},{type:"checkbox",name:"case",text:"Match case",label:" "},{type:"checkbox",name:"words",text:"Whole words",label:" "}]}}).renderTo().reflow()}function a(e){var t=e.getAttribute("data-mce-index");return"number"==typeof t?""+t:t}function r(n){var a,r;return r=t.dom.create("span",{"data-mce-bogus":1}),r.className="mce-match-marker",a=t.getBody(),c.done(!1),e(n,a,r,!1,t.schema)}function i(e){var t=e.parentNode;t.insertBefore(e.firstChild,e),e.parentNode.removeChild(e)}function d(e){var n,r=[];if(n=tinymce.toArray(t.getBody().getElementsByTagName("span")),n.length)for(var i=0;is&&f[o].setAttribute("data-mce-index",m-1)}return t.undoManager.add(),s=p,n?(g=d(p+1).length>0,c.next()):(g=d(p-1).length>0,c.prev()),!r&&g},c.done=function(e){var n,r,d,o;for(r=tinymce.toArray(t.getBody().getElementsByTagName("span")),n=0;n=s[1]?(o=d,a=s[1]-l):r&&c.push(d),!r&&d.length+l>s[0]&&(r=d,i=s[0]-l),l+=d.length),r&&o){if(d=n({startNode:r,startNodeIndex:i,endNode:o,endNodeIndex:a,innerNodes:c,match:s[2],matchIndex:u}),l-=o.length-a,r=null,o=null,c=[],s=t.shift(),u++,!s)break}else{if((!m[d.nodeName]||p[d.nodeName])&&d.firstChild){d=d.firstChild;continue}if(d.nextSibling){d=d.nextSibling;continue}}for(;;){if(d.nextSibling){d=d.nextSibling;break}if(d.parentNode===e)break e;d=d.parentNode}}}function a(e){var t;if("function"!=typeof e){var n=e.nodeType?e:g.createElement(e);t=function(e,t){var r=n.cloneNode(!1);return r.setAttribute("data-mce-index",t),e&&r.appendChild(g.createTextNode(e)),r}}else t=e;return function r(e){var n,r,o,i=e.startNode,a=e.endNode,c=e.matchIndex;if(i===a){var l=i;o=l.parentNode,e.startNodeIndex>0&&(n=g.createTextNode(l.data.substring(0,e.startNodeIndex)),o.insertBefore(n,l));var d=t(e.match[0],c);return o.insertBefore(d,l),e.endNodeIndexf;++f){var p=e.innerNodes[f],m=t(p.data,c);p.parentNode.replaceChild(m,p),u.push(m)}var v=t(a.data.substring(0,e.endNodeIndex),c);return o=i.parentNode,o.insertBefore(n,i),o.insertBefore(s,i),o.removeChild(i),o=a.parentNode,o.insertBefore(v,a),o.insertBefore(r,a),o.removeChild(a),v}}function c(e){var t=[];return l(function(n,r){e(n,r)&&t.push(n)}),u=t,this}function l(e){for(var t=0,n=u.length;n>t&&e(u[t],t)!==!1;t++);return this}function d(e){return u.length&&(h=u.length,i(t,u,a(e))),this}var s,u=[],f,h=0,g,p,m,v;if(g=t.ownerDocument,p=n.getBlockElements(),m=n.getWhiteSpaceElements(),v=n.getShortEndedElements(),f=o(t),f&&e.global)for(;s=e.exec(f);)u.push(r(s));return{text:f,count:h,matches:u,each:l,filter:c,mark:d}}}),r(d,[l,s,u,f,h,g,p],function(e,t,n,r,o,i,a){t.add("spellchecker",function(t,c){function l(e){for(var t in e)return!1;return!0}function d(e,i){var a=[],c=m[i];n.each(c,function(e){a.push({text:e,onclick:function(){t.insertContent(e),u()}})}),a.push.apply(a,[{text:"-"},{text:"Ignore",onclick:function(){h(e,i)}},{text:"Ignore all",onclick:function(){h(e,i,!0)}},{text:"Finish",onclick:g}]),N=new r({items:a,context:"contextmenu",onautohide:function(e){-1!=e.target.className.indexOf("spellchecker")&&e.preventDefault()},onhide:function(){N.remove(),N=null}}),N.renderTo(document.body);var l=o.DOM.getPos(t.getContentAreaContainer()),d=t.dom.getPos(e);l.x+=d.x,l.y+=d.y,N.moveTo(l.x,l.y+e.offsetHeight)}function s(){function n(e){return t.setProgressState(!1),l(e)?(t.windowManager.alert("No misspellings found"),v=!1,void 0):(m=e,o.filter(function(t){return!!e[t[2][0]]}).mark(t.dom.create("span",{"class":"mce-spellchecker-word","data-mce-bogus":1})),o=null,t.fire("SpellcheckStart"),void 0)}function r(e,n,r){i.sendRPC({url:new a(c).toAbsolute(x.spellchecker_rpc_url),method:e,params:{lang:x.spellchecker_language||"en",words:n},success:function(e){r(e)},error:function(e,n){e="JSON Parse error."==e?"Non JSON response:"+n.responseText:"Error: "+e,t.windowManager.alert(e),t.setProgressState(!1),o=null,v=!1}})}var o,d=[],s={};if(v)return g(),void 0;v=!0;var u=t.getParam("spellchecker_wordchar_pattern")||new RegExp('[^\\s!"#$%&()*+,-./:;<=>?@[\\]^_{|}`\xa7\xa9\xab\xae\xb1\xb6\xb7\xb8\xbb\xbc\xbd\xbe\xbf\xd7\xf7\xa4\u201d\u201c\u201e]+',"g");o=new e(u,t.getBody(),t.schema).each(function(e){var t=e[2][0];if(!s[t]){if(/^\d+$/.test(t)||1==t.length)return;d.push(t),s[t]=!0}}),t.setProgressState(!0);var f=x.spellchecker_callback||r;f("spellcheck",d,n)}function u(){t.dom.select("span.mce-spellchecker-word").length||g()}function f(e){var t=e.parentNode;t.insertBefore(e.firstChild,e),e.parentNode.removeChild(e)}function h(e,r,o){o?n.each(t.dom.select("span.mce-spellchecker-word"),function(e){var t=e.innerText||e.textContent;t==r&&f(e)}):f(e),u()}function g(){var e,n,r;for(v=!1,r=t.getBody(),n=r.getElementsByTagName("span"),e=n.length;e--;)r=n[e],r.getAttribute("data-mce-index")&&f(r);t.fire("SpellcheckEnd")}function p(e){var n,r,o,i=-1,a,c;for(e=""+e,n=t.getBody().getElementsByTagName("span"),r=0;r0){for(c=u+1;c=0;c--)if(a(d[c]))return d[c];return null}var u,d,a,c;if(9===n.keyCode&&(a=r(e.getParam("tab_focus",e.getParam("tabfocus_elements",":prev,:next"))),1==a.length&&(a[1]=a[0],a[0]=":prev"),d=n.shiftKey?":prev"==a[0]?t(-1):i.get(a[0]):":next"==a[1]?t(1):i.get(a[1]))){var f=tinymce.get(d.id||d.name);d.id&&f?f.focus():window.setTimeout(function(){tinymce.Env.webkit||window.focus(),d.focus()},10),n.preventDefault()}}var i=tinymce.DOM,o=tinymce.each,r=tinymce.explode;e.on("init",function(){e.inline&&tinymce.DOM.setAttrib(e.getBody(),"tabIndex",null)}),e.on("keyup",n),tinymce.Env.gecko?e.on("keypress keydown",t):e.on("keydown",t)}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/table/plugin.min.js b/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/table/plugin.min.js deleted file mode 100755 index 54ab6c0e18..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/table/plugin.min.js +++ /dev/null @@ -1 +0,0 @@ -!function(e,t){"use strict";function n(e,t){for(var n,r=[],i=0;i "+t+" tr",o);r(i,function(i,o){o+=e,r(M.select("> td, > th",i),function(e,r){var i,a,s,l;if(R[o])for(;R[o][r];)r++;for(s=n(e,"rowspan"),l=n(e,"colspan"),a=o;o+s>a;a++)for(R[a]||(R[a]=[]),i=r;r+l>i;i++)R[a][i]={part:t,real:a==o&&i==r,elm:e,rowspan:s,colspan:l}})}),e+=i.length})}function s(e,t){return e=e.cloneNode(t),e.removeAttribute("id"),e}function l(e,t){var n;return n=R[t],n?n[e]:void 0}function c(e,t,n){e&&(n=parseInt(n,10),1===n?e.removeAttribute(t,1):e.setAttribute(t,n,1))}function d(e){return e&&(M.hasClass(e.elm,"mce-item-selected")||e==L)}function u(){var e=[];return r(o.rows,function(t){r(t.cells,function(n){return M.hasClass(n,"mce-item-selected")||n==L.elm?(e.push(t),!1):void 0})}),e}function f(){var e=M.createRng();e.setStartAfter(o),e.setEndAfter(o),H.setRng(e),M.remove(o)}function p(n){var o,a={};return i.settings.table_clone_elements!==!1&&(a=e.makeMap((i.settings.table_clone_elements||"strong em b i span font h1 h2 h3 h4 h5 h6 p div").toUpperCase(),/[ ,]/)),e.walk(n,function(e){var i;return 3==e.nodeType?(r(M.getParents(e.parentNode,null,n).reverse(),function(e){a[e.nodeName]&&(e=s(e,!1),o?i&&i.appendChild(e):o=i=e,i=e)}),i&&(i.innerHTML=t.ie?" ":'
'),!1):void 0},"childNodes"),n=s(n,!1),c(n,"rowSpan",1),c(n,"colSpan",1),o?n.appendChild(o):t.ie||(n.innerHTML='
'),n}function m(){var e=M.createRng(),t;return r(M.select("tr",o),function(e){0===e.cells.length&&M.remove(e)}),0===M.select("tr",o).length?(e.setStartBefore(o),e.setEndBefore(o),H.setRng(e),M.remove(o),void 0):(r(M.select("thead,tbody,tfoot",o),function(e){0===e.rows.length&&M.remove(e)}),a(),t=R[Math.min(R.length-1,A.y)],t&&(H.select(t[Math.min(t.length-1,A.x)].elm,!0),H.collapse(!0)),void 0)}function h(e,t,n,r){var i,o,a,s,l;for(i=R[t][e].elm.parentNode,a=1;n>=a;a++)if(i=M.getNext(i,"tr")){for(o=e;o>=0;o--)if(l=R[t+a][o].elm,l.parentNode==i){for(s=1;r>=s;s++)M.insertAfter(p(l),l);break}if(-1==o)for(s=1;r>=s;s++)i.insertBefore(p(i.cells[0]),i.cells[0])}}function g(){r(R,function(e,t){r(e,function(e,r){var i,o,a;if(d(e)&&(e=e.elm,i=n(e,"colspan"),o=n(e,"rowspan"),i>1||o>1)){for(c(e,"rowSpan",1),c(e,"colSpan",1),a=0;i-1>a;a++)M.insertAfter(p(e),e);h(r,t,o-1,i)}})})}function v(t,n,i){var o,s,u,f,p,h,v,y,b,C,x;if(t?(o=E(t),s=o.x,u=o.y,f=s+(n-1),p=u+(i-1)):(A=B=null,r(R,function(e,t){r(e,function(e,n){d(e)&&(A||(A={x:n,y:t}),B={x:n,y:t})})}),s=A.x,u=A.y,f=B.x,p=B.y),y=l(s,u),b=l(f,p),y&&b&&y.part==b.part){for(g(),a(),y=l(s,u).elm,c(y,"colSpan",f-s+1),c(y,"rowSpan",p-u+1),v=u;p>=v;v++)for(h=s;f>=h;h++)R[v]&&R[v][h]&&(t=R[v][h].elm,t!=y&&(C=e.grep(t.childNodes),r(C,function(e){y.appendChild(e)}),C.length&&(C=e.grep(y.childNodes),x=0,r(C,function(e){"BR"==e.nodeName&&M.getAttrib(e,"data-mce-bogus")&&x++0&&R[t-1][a]&&(m=R[t-1][a].elm,h=n(m,"rowSpan"),h>1)){c(m,"rowSpan",h+1);continue}}else if(h=n(i,"rowspan"),h>1){c(i,"rowSpan",h+1);continue}f=p(i),c(f,"colSpan",i.colSpan),u.appendChild(f),o=i}u.hasChildNodes()&&(e?l.parentNode.insertBefore(u,l):M.insertAfter(u,l))}function b(e){var t,i;r(R,function(n){return r(n,function(n,r){return d(n)&&(t=r,e)?!1:void 0}),e?!t:void 0}),r(R,function(r,o){var a,s,l;r[t]&&(a=r[t].elm,a!=i&&(l=n(a,"colspan"),s=n(a,"rowspan"),1==l?e?(a.parentNode.insertBefore(p(a),a),h(t,o,s-1,l)):(M.insertAfter(p(a),a),h(t,o,s-1,l)):c(a,"colSpan",a.colSpan+1),i=a))})}function C(){var t=[];r(R,function(i){r(i,function(i,o){d(i)&&-1===e.inArray(t,o)&&(r(R,function(e){var t=e[o].elm,r;r=n(t,"colSpan"),r>1?c(t,"colSpan",r-1):M.remove(t)}),t.push(o))})}),m()}function x(){function e(e){var t,i,o;t=M.getNext(e,"tr"),r(e.cells,function(e){var t=n(e,"rowSpan");t>1&&(c(e,"rowSpan",t-1),i=E(e),h(i.x,i.y,1,1))}),i=E(e.cells[0]),r(R[i.y],function(e){var t;e=e.elm,e!=o&&(t=n(e,"rowSpan"),1>=t?M.remove(e):c(e,"rowSpan",t-1),o=e)})}var t;t=u(),r(t.reverse(),function(t){e(t)}),m()}function w(){var e=u();return M.remove(e),m(),e}function _(){var e=u();return r(e,function(t,n){e[n]=s(t,!0)}),e}function N(e,t){var n=u(),i=n[t?0:n.length-1],o=i.cells.length;e&&(r(R,function(e){var t;return o=0,r(e,function(e){e.real&&(o+=e.colspan),e.elm.parentNode==i&&(t=1)}),t?!1:void 0}),t||e.reverse(),r(e,function(e){var n,r=e.cells.length,a;for(n=0;r>n;n++)a=e.cells[n],c(a,"colSpan",1),c(a,"rowSpan",1);for(n=r;o>n;n++)e.appendChild(p(e.cells[r-1]));for(n=o;r>n;n++)M.remove(e.cells[n]);t?i.parentNode.insertBefore(e,i):M.insertAfter(e,i)}),M.removeClass(M.select("td.mce-item-selected,th.mce-item-selected"),"mce-item-selected"))}function E(e){var t;return r(R,function(n,i){return r(n,function(n,r){return n.elm==e?(t={x:r,y:i},!1):void 0}),!t}),t}function k(e){A=E(e)}function S(){var e,t;return e=t=0,r(R,function(n,i){r(n,function(n,r){var o,a;d(n)&&(n=R[i][r],r>e&&(e=r),i>t&&(t=i),n.real&&(o=n.colspan-1,a=n.rowspan-1,o&&r+o>e&&(e=r+o),a&&i+a>t&&(t=i+a)))})}),{x:e,y:t}}function T(e){var t,n,r,i,o,a,s,l,c,d;if(B=E(e),A&&B){for(t=Math.min(A.x,B.x),n=Math.min(A.y,B.y),r=Math.max(A.x,B.x),i=Math.max(A.y,B.y),o=r,a=i,d=n;a>=d;d++)e=R[d][t],e.real||t-(e.colspan-1)=c;c++)e=R[n][c],e.real||n-(e.rowspan-1)=d;d++)for(c=t;r>=c;c++)e=R[d][c],e.real&&(s=e.colspan-1,l=e.rowspan-1,s&&c+s>o&&(o=c+s),l&&d+l>a&&(a=d+l));for(M.removeClass(M.select("td.mce-item-selected,th.mce-item-selected"),"mce-item-selected"),d=n;a>=d;d++)for(c=t;o>=c;c++)R[d][c]&&M.addClass(R[d][c].elm,"mce-item-selected")}}var R,A,B,L,H=i.selection,M=H.dom;o=o||M.getParent(H.getStart(),"table"),a(),L=M.getParent(H.getStart(),"th,td"),L&&(A=E(L),B=S(),L=l(A.x,A.y)),e.extend(this,{deleteTable:f,split:g,merge:v,insertRow:y,insertCol:b,deleteCols:C,deleteRows:x,cutRows:w,copyRows:_,pasteRows:N,getPos:E,setStartCell:k,setEndCell:T})}}),r(u,[f,d,c],function(e,t,n){function r(e,t){return parseInt(e.getAttribute(t)||1,10)}var i=n.each;return function(n){function o(){function t(t){function o(e,r){var i=e?"previousSibling":"nextSibling",o=n.dom.getParent(r,"tr"),s=o[i];if(s)return g(n,r,s,e),t.preventDefault(),!0;var d=n.dom.getParent(o,"table"),u=o.parentNode,f=u.nodeName.toLowerCase();if("tbody"===f||f===(e?"tfoot":"thead")){var p=a(e,d,u,"tbody");if(null!==p)return l(e,p,r)}return c(e,o,i,d)}function a(e,t,r,i){var o=n.dom.select(">"+i,t),a=o.indexOf(r);if(e&&0===a||!e&&a===o.length-1)return s(e,t);if(-1===a){var l="thead"===r.tagName.toLowerCase()?0:o.length-1;return o[l]}return o[a+(e?-1:1)]}function s(e,t){var r=e?"thead":"tfoot",i=n.dom.select(">"+r,t);return 0!==i.length?i[0]:null}function l(e,r,i){var o=d(r,e);return o&&g(n,i,o,e),t.preventDefault(),!0}function c(e,r,i,a){var s=a[i];if(s)return u(s),!0;var l=n.dom.getParent(a,"td,th");if(l)return o(e,l,t);var c=d(r,!e);return u(c),t.preventDefault(),!1}function d(e,t){var r=e&&e[t?"lastChild":"firstChild"];return r&&"BR"===r.nodeName?n.dom.getParent(r,"td,th"):r}function u(e){n.selection.setCursorLocation(e,0)}function f(){return b==e.UP||b==e.DOWN}function p(e){var t=e.selection.getNode(),n=e.dom.getParent(t,"tr");return null!==n}function m(e){for(var t=0,n=e;n.previousSibling;)n=n.previousSibling,t+=r(n,"colspan");return t}function h(e,t){var n=0,o=0;return i(e.children,function(e,i){return n+=r(e,"colspan"),o=i,n>t?!1:void 0}),o}function g(e,t,r,i){var o=m(n.dom.getParent(t,"td,th")),a=h(r,o),s=r.childNodes[a],l=d(s,i);u(l||s)}function v(e){var t=n.selection.getNode(),r=n.dom.getParent(t,"td,th"),i=n.dom.getParent(e,"td,th");return r&&r!==i&&y(r,i)}function y(e,t){return n.dom.getParent(e,"TABLE")===n.dom.getParent(t,"TABLE")}var b=t.keyCode;if(f()&&p(n)){var C=n.selection.getNode();setTimeout(function(){v(C)&&o(!t.shiftKey&&b===e.UP,C,t)},0)}}n.on("KeyDown",function(e){t(e)})}function a(){function e(e,t){var n=t.ownerDocument,r=n.createRange(),i;return r.setStartBefore(t),r.setEnd(e.endContainer,e.endOffset),i=n.createElement("body"),i.appendChild(r.cloneContents()),0===i.innerHTML.replace(/<(br|img|object|embed|input|textarea)[^>]*>/gi,"-").replace(/<[^>]+>/g,"").length}n.on("KeyDown",function(t){var r,i,o=n.dom;(37==t.keyCode||38==t.keyCode)&&(r=n.selection.getRng(),i=o.getParent(r.startContainer,"table"),i&&n.getBody().firstChild==i&&e(r,i)&&(r=o.createRng(),r.setStartBefore(i),r.setEndBefore(i),n.selection.setRng(r),t.preventDefault()))})}function s(){n.on("KeyDown SetContent VisualAid",function(){var e;for(e=n.getBody().lastChild;e;e=e.previousSibling)if(3==e.nodeType){if(e.nodeValue.length>0)break}else if(1==e.nodeType&&!e.getAttribute("data-mce-bogus"))break;e&&"TABLE"==e.nodeName&&(n.settings.forced_root_block?n.dom.add(n.getBody(),n.settings.forced_root_block,n.settings.forced_root_block_attrs,t.ie&&t.ie<11?" ":'
'):n.dom.add(n.getBody(),"br",{"data-mce-bogus":"1"}))}),n.on("PreProcess",function(e){var t=e.node.lastChild;t&&("BR"==t.nodeName||1==t.childNodes.length&&("BR"==t.firstChild.nodeName||"\xa0"==t.firstChild.nodeValue))&&t.previousSibling&&"TABLE"==t.previousSibling.nodeName&&n.dom.remove(t)})}function l(){function e(e,t,n,r){var i=3,o=e.dom.getParent(t.startContainer,"TABLE"),a,s,l;return o&&(a=o.parentNode),s=t.startContainer.nodeType==i&&0===t.startOffset&&0===t.endOffset&&r&&("TR"==n.nodeName||n==a),l=("TD"==n.nodeName||"TH"==n.nodeName)&&!r,s||l}function t(){var t=n.selection.getRng(),r=n.selection.getNode(),i=n.dom.getParent(t.startContainer,"TD,TH");if(e(n,t,r,i)){i||(i=r);for(var o=i.lastChild;o.lastChild;)o=o.lastChild;t.setEnd(o,o.nodeValue.length),n.selection.setRng(t)}}n.on("KeyDown",function(){t()}),n.on("MouseDown",function(e){2!=e.button&&t()})}t.webkit&&(o(),l()),t.gecko&&(a(),s()),t.ie>10&&(a(),s())}}),r(p,[l,m,c],function(e,t,n){return function(r){function i(){r.getBody().style.webkitUserSelect="",d&&(r.dom.removeClass(r.dom.select("td.mce-item-selected,th.mce-item-selected"),"mce-item-selected"),d=!1)}function o(t){var n,i,o=t.target;if(l&&(s||o!=l)&&("TD"==o.nodeName||"TH"==o.nodeName)){i=a.getParent(o,"table"),i==c&&(s||(s=new e(r,i),s.setStartCell(l),r.getBody().style.webkitUserSelect="none"),s.setEndCell(o),d=!0),n=r.selection.getSel();try{n.removeAllRanges?n.removeAllRanges():n.empty()}catch(u){}t.preventDefault()}}var a=r.dom,s,l,c,d=!0;return r.on("MouseDown",function(e){2!=e.button&&(i(),l=a.getParent(e.target,"td,th"),c=a.getParent(l,"table"))}),a.bind(r.getDoc(),"mouseover",o),r.on("remove",function(){a.unbind(r.getDoc(),"mouseover",o)}),r.on("MouseUp",function(){function e(e,r){var o=new t(e,e);do{if(3==e.nodeType&&0!==n.trim(e.nodeValue).length)return r?i.setStart(e,0):i.setEnd(e,e.nodeValue.length),void 0;if("BR"==e.nodeName)return r?i.setStartBefore(e):i.setEndBefore(e),void 0}while(e=r?o.next():o.prev())}var i,o=r.selection,d,u,f,p,m;if(l){if(s&&(r.getBody().style.webkitUserSelect=""),d=a.select("td.mce-item-selected,th.mce-item-selected"),d.length>0){i=a.createRng(),f=d[0],m=d[d.length-1],i.setStartBefore(f),i.setEndAfter(f),e(f,1),u=new t(f,a.getParent(d[0],"table"));do if("TD"==f.nodeName||"TH"==f.nodeName){if(!a.hasClass(f,"mce-item-selected"))break;p=f}while(f=u.next());e(p),o.setRng(i)}r.nodeChanged(),l=s=c=null}}),r.on("KeyUp",function(){i()}),{clear:i}}}),r(h,[l,u,p,c,m,d,g],function(e,t,n,r,i,o,a){function s(r){function i(e){return e?e.replace(/px$/,""):""}function a(e){return/^[0-9]+$/.test(e)&&(e+="px"),e}function s(e){l("left center right".split(" "),function(t){r.formatter.remove("align"+t,{},e)})}function c(){var e=r.dom,t,n,c;t=r.dom.getParent(r.selection.getStart(),"table"),c=!1,n={width:i(e.getStyle(t,"width")||e.getAttrib(t,"width")),height:i(e.getStyle(t,"height")||e.getAttrib(t,"height")),cellspacing:e.getAttrib(t,"cellspacing"),cellpadding:e.getAttrib(t,"cellpadding"),border:e.getAttrib(t,"border"),caption:!!e.select("caption",t)[0]},l("left center right".split(" "),function(e){r.formatter.matchNode(t,"align"+e)&&(n.align=e)}),r.windowManager.open({title:"Table properties",items:{type:"form",layout:"grid",columns:2,data:n,defaults:{type:"textbox",maxWidth:50},items:[c?{label:"Cols",name:"cols",disabled:!0}:null,c?{label:"Rows",name:"rows",disabled:!0}:null,{label:"Width",name:"width"},{label:"Height",name:"height"},{label:"Cell spacing",name:"cellspacing"},{label:"Cell padding",name:"cellpadding"},{label:"Border",name:"border"},{label:"Caption",name:"caption",type:"checkbox"},{label:"Alignment",minWidth:90,name:"align",type:"listbox",text:"None",maxWidth:null,values:[{text:"None",value:""},{text:"Left",value:"left"},{text:"Center",value:"center"},{text:"Right",value:"right"}]}]},onsubmit:function(){var n=this.toJSON(),i;r.undoManager.transact(function(){r.dom.setAttribs(t,{cellspacing:n.cellspacing,cellpadding:n.cellpadding,border:n.border}),r.dom.setStyles(t,{width:a(n.width),height:a(n.height)}),i=e.select("caption",t)[0],i&&!n.caption&&e.remove(i),!i&&n.caption&&(i=e.create("caption"),i.innerHTML=o.ie?"\xa0":'
',t.insertBefore(i,t.firstChild)),s(t),n.align&&r.formatter.apply("align"+n.align,{},t),r.focus(),r.addVisual()})}})}function d(e,t){r.windowManager.open({title:"Merge cells",body:[{label:"Cols",name:"cols",type:"textbox",size:10},{label:"Rows",name:"rows",type:"textbox",size:10}],onsubmit:function(){var n=this.toJSON();r.undoManager.transact(function(){e.merge(t,n.cols,n.rows)})}})}function u(){var e=r.dom,t,n,o=[];o=r.dom.select("td.mce-item-selected,th.mce-item-selected"),t=r.dom.getParent(r.selection.getStart(),"td,th"),!o.length&&t&&o.push(t),t=t||o[0],n={width:i(e.getStyle(t,"width")||e.getAttrib(t,"width")),height:i(e.getStyle(t,"height")||e.getAttrib(t,"height")),scope:e.getAttrib(t,"scope")},n.type=t.nodeName.toLowerCase(),l("left center right".split(" "),function(e){r.formatter.matchNode(t,"align"+e)&&(n.align=e)}),r.windowManager.open({title:"Cell properties",items:{type:"form",data:n,layout:"grid",columns:2,defaults:{type:"textbox",maxWidth:50},items:[{label:"Width",name:"width"},{label:"Height",name:"height"},{label:"Cell type",name:"type",type:"listbox",text:"None",minWidth:90,maxWidth:null,menu:[{text:"Cell",value:"td"},{text:"Header cell",value:"th"}]},{label:"Scope",name:"scope",type:"listbox",text:"None",minWidth:90,maxWidth:null,menu:[{text:"None",value:""},{text:"Row",value:"row"},{text:"Column",value:"col"},{text:"Row group",value:"rowgroup"},{text:"Column group",value:"colgroup"}]},{label:"Alignment",name:"align",type:"listbox",text:"None",minWidth:90,maxWidth:null,values:[{text:"None",value:""},{text:"Left",value:"left"},{text:"Center",value:"center"},{text:"Right",value:"right"}]}]},onsubmit:function(){var t=this.toJSON();r.undoManager.transact(function(){l(o,function(n){r.dom.setAttrib(n,"scope",t.scope),r.dom.setStyles(n,{width:a(t.width),height:a(t.height)}),t.type&&n.nodeName.toLowerCase()!=t.type&&(n=e.rename(n,t.type)),s(n),t.align&&r.formatter.apply("align"+t.align,{},n)}),r.focus()})}})}function f(){var e=r.dom,t,n,o,c,d=[];t=r.dom.getParent(r.selection.getStart(),"table"),n=r.dom.getParent(r.selection.getStart(),"td,th"),l(t.rows,function(t){l(t.cells,function(r){return e.hasClass(r,"mce-item-selected")||r==n?(d.push(t),!1):void 0})}),o=d[0],c={height:i(e.getStyle(o,"height")||e.getAttrib(o,"height")),scope:e.getAttrib(o,"scope")},c.type=o.parentNode.nodeName.toLowerCase(),l("left center right".split(" "),function(e){r.formatter.matchNode(o,"align"+e)&&(c.align=e)}),r.windowManager.open({title:"Row properties",items:{type:"form",data:c,columns:2,defaults:{type:"textbox"},items:[{type:"listbox",name:"type",label:"Row type",text:"None",maxWidth:null,menu:[{text:"Header",value:"thead"},{text:"Body",value:"tbody"},{text:"Footer",value:"tfoot"}]},{type:"listbox",name:"align",label:"Alignment",text:"None",maxWidth:null,menu:[{text:"None",value:""},{text:"Left",value:"left"},{text:"Center",value:"center"},{text:"Right",value:"right"}]},{label:"Height",name:"height"}]},onsubmit:function(){var t=this.toJSON(),n,i,o;r.undoManager.transact(function(){var c=t.type;l(d,function(l){r.dom.setAttrib(l,"scope",t.scope),r.dom.setStyles(l,{height:a(t.height)}),c!=l.parentNode.nodeName.toLowerCase()&&(n=e.getParent(l,"table"),i=l.parentNode,o=e.select(c,n)[0],o||(o=e.create(c),n.firstChild?n.insertBefore(o,n.firstChild):n.appendChild(o)),o.appendChild(l),i.hasChildNodes()||e.remove(i)),s(l),t.align&&r.formatter.apply("align"+t.align,{},l)}),r.focus()})}})}function p(e){return function(){r.execCommand(e)}}function m(e,t){var n,i,a;for(a="",n=0;t>n;n++){for(a+="",i=0;e>i;i++)a+="";a+=""}a+="
"+(o.ie?" ":"
")+"
",r.insertContent(a)}function h(e,t){function n(){e.disabled(!r.dom.getParent(r.selection.getStart(),t)),r.selection.selectorChanged(t,function(t){e.disabled(!t)})}r.initialized?n():r.on("init",n)}function g(){h(this,"table")}function v(){h(this,"td,th")}function y(){var e="";e='';for(var t=0;10>t;t++){e+="";for(var n=0;10>n;n++)e+='';e+=""}return e+="",e+='

0 x 0
'}var b,C,x=this;r.addMenuItem("inserttable",{text:"Insert table",icon:"table",context:"table",onhide:function(){r.dom.removeClass(this.menu.items()[0].getEl().getElementsByTagName("a"),"mce-active")},menu:[{type:"container",html:y(),onmousemove:function(e){var t,n,i=e.target;if("A"==i.nodeName){var o=r.dom.getParent(i,"table"),a=i.getAttribute("data-mce-index"),s=e.control.parent().rel;if(a!=this.lastPos){if(a=a.split(","),a[0]=parseInt(a[0],10),a[1]=parseInt(a[1],10),e.control.isRtl()||"tl-tr"==s){for(n=9;n>=0;n--)for(t=0;10>t;t++)r.dom.toggleClass(o.rows[n].childNodes[t].firstChild,"mce-active",t>=a[0]&&n<=a[1]);a[0]=9-a[0],o.nextSibling.innerHTML=a[0]+" x "+(a[1]+1)}else{for(n=0;10>n;n++)for(t=0;10>t;t++)r.dom.toggleClass(o.rows[n].childNodes[t].firstChild,"mce-active",t<=a[0]&&n<=a[1]);o.nextSibling.innerHTML=a[0]+1+" x "+(a[1]+1)}this.lastPos=a}}},onclick:function(e){"A"==e.target.nodeName&&this.lastPos&&(e.preventDefault(),m(this.lastPos[0]+1,this.lastPos[1]+1),this.parent().cancel())}}]}),r.addMenuItem("tableprops",{text:"Table properties",context:"table",onPostRender:g,onclick:c}),r.addMenuItem("deletetable",{text:"Delete table",context:"table",onPostRender:g,cmd:"mceTableDelete"}),r.addMenuItem("cell",{separator:"before",text:"Cell",context:"table",menu:[{text:"Cell properties",onclick:p("mceTableCellProps"),onPostRender:v},{text:"Merge cells",onclick:p("mceTableMergeCells"),onPostRender:v},{text:"Split cell",onclick:p("mceTableSplitCells"),onPostRender:v}]}),r.addMenuItem("row",{text:"Row",context:"table",menu:[{text:"Insert row before",onclick:p("mceTableInsertRowBefore"),onPostRender:v},{text:"Insert row after",onclick:p("mceTableInsertRowAfter"),onPostRender:v},{text:"Delete row",onclick:p("mceTableDeleteRow"),onPostRender:v},{text:"Row properties",onclick:p("mceTableRowProps"),onPostRender:v},{text:"-"},{text:"Cut row",onclick:p("mceTableCutRow"),onPostRender:v},{text:"Copy row",onclick:p("mceTableCopyRow"),onPostRender:v},{text:"Paste row before",onclick:p("mceTablePasteRowBefore"),onPostRender:v},{text:"Paste row after",onclick:p("mceTablePasteRowAfter"),onPostRender:v}]}),r.addMenuItem("column",{text:"Column",context:"table",menu:[{text:"Insert column before",onclick:p("mceTableInsertColBefore"),onPostRender:v},{text:"Insert column after",onclick:p("mceTableInsertColAfter"),onPostRender:v},{text:"Delete column",onclick:p("mceTableDeleteCol"),onPostRender:v}]});var w=[];l("inserttable tableprops deletetable | cell row column".split(" "),function(e){"|"==e?w.push({text:"-"}):w.push(r.menuItems[e])}),r.addButton("table",{type:"menubutton",title:"Table",menu:w}),o.isIE||r.on("click",function(e){e=e.target,"TABLE"===e.nodeName&&(r.selection.select(e),r.nodeChanged())}),x.quirks=new t(r),r.on("Init",function(){b=r.windowManager,x.cellSelection=new n(r)}),l({mceTableSplitCells:function(e){e.split()},mceTableMergeCells:function(e){var t,n,i;i=r.dom.getParent(r.selection.getStart(),"th,td"),i&&(t=i.rowSpan,n=i.colSpan),r.dom.select("td.mce-item-selected,th.mce-item-selected").length?e.merge():d(e,i)},mceTableInsertRowBefore:function(e){e.insertRow(!0)},mceTableInsertRowAfter:function(e){e.insertRow()},mceTableInsertColBefore:function(e){e.insertCol(!0)},mceTableInsertColAfter:function(e){e.insertCol()},mceTableDeleteCol:function(e){e.deleteCols()},mceTableDeleteRow:function(e){e.deleteRows()},mceTableCutRow:function(e){C=e.cutRows()},mceTableCopyRow:function(e){C=e.copyRows()},mceTablePasteRowBefore:function(e){e.pasteRows(C,!0)},mceTablePasteRowAfter:function(e){e.pasteRows(C)},mceTableDelete:function(e){e.deleteTable()}},function(t,n){r.addCommand(n,function(){var n=new e(r);n&&(t(n),r.execCommand("mceRepaint"),x.cellSelection.clear())})}),l({mceInsertTable:function(){c()},mceTableRowProps:f,mceTableCellProps:u},function(e,t){r.addCommand(t,function(t,n){e(n)})})}var l=r.each;a.add("table",s)}),a([l,u,p,h])}(this); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/template/plugin.min.js b/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/template/plugin.min.js deleted file mode 100755 index 91a8b5fa2d..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/template/plugin.min.js +++ /dev/null @@ -1 +0,0 @@ -tinymce.PluginManager.add("template",function(e){function t(t){return function(){var a=e.settings.templates;"string"==typeof a?tinymce.util.XHR.send({url:a,success:function(e){t(tinymce.util.JSON.parse(e))}}):t(a)}}function a(t){function a(t){function a(t){if(-1==t.indexOf("")){var a="";tinymce.each(e.contentCSS,function(t){a+=''}),t=""+a+""+""+t+""+""}t=r(t,"template_preview_replace_values");var l=n.find("iframe")[0].getEl().contentWindow.document;l.open(),l.write(t),l.close()}var c=t.control.value();c.url?tinymce.util.XHR.send({url:c.url,success:function(e){l=e,a(l)}}):(l=c.content,a(l)),n.find("#description")[0].text(t.control.value().description)}var n,l,i=[];return t&&0!==t.length?(tinymce.each(t,function(e){i.push({selected:!i.length,text:e.title,value:{url:e.url,content:e.content,description:e.description}})}),n=e.windowManager.open({title:"Insert template",layout:"flex",direction:"column",align:"stretch",padding:15,spacing:10,items:[{type:"form",flex:0,padding:0,items:[{type:"container",label:"Templates",items:{type:"listbox",label:"Templates",name:"template",values:i,onselect:a}}]},{type:"label",name:"description",label:"Description",text:" "},{type:"iframe",flex:1,border:1}],onsubmit:function(){c(!1,l)},width:e.getParam("template_popup_width",600),height:e.getParam("template_popup_height",500)}),n.find("listbox")[0].fire("select"),void 0):(e.windowManager.alert("No templates defined"),void 0)}function n(t,a){function n(e,t){if(e=""+e,e.length0&&(o=p.create("div",null),o.appendChild(s[0].cloneNode(!0))),i(p.select("*",o),function(t){c(t,e.getParam("template_cdate_classes","cdate").replace(/\s+/g,"|"))&&(t.innerHTML=n(e.getParam("template_cdate_format",e.getLang("template.cdate_format")))),c(t,e.getParam("template_mdate_classes","mdate").replace(/\s+/g,"|"))&&(t.innerHTML=n(e.getParam("template_mdate_format",e.getLang("template.mdate_format")))),c(t,e.getParam("template_selected_content_classes","selcontent").replace(/\s+/g,"|"))&&(t.innerHTML=m)}),l(o),e.execCommand("mceInsertContent",!1,o.innerHTML),e.addVisual()}var i=tinymce.each;e.addCommand("mceInsertTemplate",c),e.addButton("template",{title:"Insert template",onclick:t(a)}),e.addMenuItem("template",{text:"Insert template",onclick:t(a),context:"insert"}),e.on("PreProcess",function(t){var a=e.dom;i(a.select("div",t.node),function(t){a.hasClass(t,"mceTmpl")&&(i(a.select("*",t),function(t){a.hasClass(t,e.getParam("template_mdate_classes","mdate").replace(/\s+/g,"|"))&&(t.innerHTML=n(e.getParam("template_mdate_format",e.getLang("template.mdate_format"))))}),l(t))})})}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/textcolor/plugin.min.js b/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/textcolor/plugin.min.js deleted file mode 100755 index ee943e497f..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/textcolor/plugin.min.js +++ /dev/null @@ -1 +0,0 @@ -tinymce.PluginManager.add("textcolor",function(e){function o(){var o,t,r=[];for(t=e.settings.textcolor_map||["000000","Black","993300","Burnt orange","333300","Dark olive","003300","Dark green","003366","Dark azure","000080","Navy Blue","333399","Indigo","333333","Very dark gray","800000","Maroon","FF6600","Orange","808000","Olive","008000","Green","008080","Teal","0000FF","Blue","666699","Grayish blue","808080","Gray","FF0000","Red","FF9900","Amber","99CC00","Yellow green","339966","Sea green","33CCCC","Turquoise","3366FF","Royal blue","800080","Purple","999999","Medium gray","FF00FF","Magenta","FFCC00","Gold","FFFF00","Yellow","00FF00","Lime","00FFFF","Aqua","00CCFF","Sky blue","993366","Brown","C0C0C0","Silver","FF99CC","Pink","FFCC99","Peach","FFFF99","Light yellow","CCFFCC","Pale green","CCFFFF","Pale cyan","99CCFF","Light sky blue","CC99FF","Plum","FFFFFF","White"],o=0;o',c=t.length-1,n=e.settings.textcolor_rows||5,a=e.settings.textcolor_cols||8,i=0;n>i;i++){for(l+="",F=0;a>F;F++)d=i*a+F,d>c?l+="":(r=t[d],l+='
'+"
"+"");l+=""}return l+=""}function r(o){var t,r=this.parent();(t=o.target.getAttribute("data-mce-color"))&&(r.hidePanel(),t="#"+t,r.color(t),e.execCommand(r.settings.selectcmd,!1,t))}function l(){var o=this;o._color&&e.execCommand(o.settings.selectcmd,!1,o._color)}e.addButton("forecolor",{type:"colorbutton",tooltip:"Text color",selectcmd:"ForeColor",panel:{html:t,onclick:r},onclick:l}),e.addButton("backcolor",{type:"colorbutton",tooltip:"Background color",selectcmd:"HiliteColor",panel:{html:t,onclick:r},onclick:l})}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/umbracolink/plugin.min.js b/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/umbracolink/plugin.min.js index e50c347b5a..deb0a3cf3e 100644 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/umbracolink/plugin.min.js +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/umbracolink/plugin.min.js @@ -189,7 +189,8 @@ tinymce.PluginManager.add('umbracolink', function(editor) { href: href, title: data.name, target: data.target ? data.target : null, - rel: data.rel ? data.rel : null + rel: data.rel ? data.rel : null, + 'data-id': data.id ? data.id : null }); selection.select(anchorElm); @@ -199,7 +200,8 @@ tinymce.PluginManager.add('umbracolink', function(editor) { href: href, title: data.name, target: data.target ? data.target : null, - rel: data.rel ? data.rel : null + rel: data.rel ? data.rel : null, + 'data-id': data.id ? data.id : null }); } } diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/visualblocks/css/visualblocks.css b/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/visualblocks/css/visualblocks.css deleted file mode 100755 index fe6fa930a4..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/visualblocks/css/visualblocks.css +++ /dev/null @@ -1,128 +0,0 @@ -.mce-visualblocks p { - padding-top: 10px; - border: 1px dashed #BBB; - margin-left: 3px; - background: transparent no-repeat url(data:image/gif;base64,R0lGODlhCQAJAJEAAAAAAP///7u7u////yH5BAEAAAMALAAAAAAJAAkAAAIQnG+CqCN/mlyvsRUpThG6AgA7); -} - -.mce-visualblocks h1 { - padding-top: 10px; - border: 1px dashed #BBB; - margin-left: 3px; - background: transparent no-repeat url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGu1JuxHoAfRNRW3TWXyF2YiRUAOw==); -} - -.mce-visualblocks h2 { - padding-top: 10px; - border: 1px dashed #BBB; - margin-left: 3px; - background: transparent no-repeat url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8Hybbx4oOuqgTynJd6bGlWg3DkJzoaUAAAOw==); -} - -.mce-visualblocks h3 { - padding-top: 10px; - border: 1px dashed #BBB; - margin-left: 3px; - background: transparent no-repeat url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIZjI8Hybbx4oOuqgTynJf2Ln2NOHpQpmhAAQA7); -} - -.mce-visualblocks h4 { - padding-top: 10px; - border: 1px dashed #BBB; - margin-left: 3px; - background: transparent no-repeat url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxInR0zqeAdhtJlXwV1oCll2HaWgAAOw==); -} - -.mce-visualblocks h5 { - padding-top: 10px; - border: 1px dashed #BBB; - margin-left: 3px; - background: transparent no-repeat url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjane4iq5GlW05GgIkIZUAAAOw==); -} - -.mce-visualblocks h6 { - padding-top: 10px; - border: 1px dashed #BBB; - margin-left: 3px; - background: transparent no-repeat url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjan04jep1iZ1XRlAo5bVgAAOw==); -} - -.mce-visualblocks div { - padding-top: 10px; - border: 1px dashed #BBB; - margin-left: 3px; - background: transparent no-repeat url(data:image/gif;base64,R0lGODlhEgAKAIABALu7u////yH5BAEAAAEALAAAAAASAAoAAAIfjI9poI0cgDywrhuxfbrzDEbQM2Ei5aRjmoySW4pAAQA7); -} - -.mce-visualblocks section { - padding-top: 10px; - border: 1px dashed #BBB; - margin: 0 0 1em 3px; - background: transparent no-repeat url(data:image/gif;base64,R0lGODlhKAAKAIABALu7u////yH5BAEAAAEALAAAAAAoAAoAAAI5jI+pywcNY3sBWHdNrplytD2ellDeSVbp+GmWqaDqDMepc8t17Y4vBsK5hDyJMcI6KkuYU+jpjLoKADs=); -} - -.mce-visualblocks article { - padding-top: 10px; - border: 1px dashed #BBB; - margin: 0 0 1em 3px; - background: transparent no-repeat url(data:image/gif;base64,R0lGODlhKgAKAIABALu7u////yH5BAEAAAEALAAAAAAqAAoAAAI6jI+pywkNY3wG0GBvrsd2tXGYSGnfiF7ikpXemTpOiJScasYoDJJrjsG9gkCJ0ag6KhmaIe3pjDYBBQA7); -} - -.mce-visualblocks blockquote { - padding-top: 10px; - border: 1px dashed #BBB; - background: transparent no-repeat url(data:image/gif;base64,R0lGODlhPgAKAIABALu7u////yH5BAEAAAEALAAAAAA+AAoAAAJPjI+py+0Knpz0xQDyuUhvfoGgIX5iSKZYgq5uNL5q69asZ8s5rrf0yZmpNkJZzFesBTu8TOlDVAabUyatguVhWduud3EyiUk45xhTTgMBBQA7); -} - -.mce-visualblocks address { - padding-top: 10px; - border: 1px dashed #BBB; - margin: 0 0 1em 3px; - background: transparent no-repeat url(data:image/gif;base64,R0lGODlhLQAKAIABALu7u////yH5BAEAAAEALAAAAAAtAAoAAAI/jI+pywwNozSP1gDyyZcjb3UaRpXkWaXmZW4OqKLhBmLs+K263DkJK7OJeifh7FicKD9A1/IpGdKkyFpNmCkAADs=); -} - -.mce-visualblocks pre { - padding-top: 10px; - border: 1px dashed #BBB; - margin-left: 3px; - background: transparent no-repeat url(data:image/gif;base64,R0lGODlhFQAKAIABALu7uwAAACH5BAEAAAEALAAAAAAVAAoAAAIjjI+ZoN0cgDwSmnpz1NCueYERhnibZVKLNnbOq8IvKpJtVQAAOw==); -} - -.mce-visualblocks figure { - padding-top: 10px; - border: 1px dashed #BBB; - margin: 0 0 1em 3px; - background: transparent no-repeat url(data:image/gif;base64,R0lGODlhJAAKAIAAALu7u////yH5BAEAAAEALAAAAAAkAAoAAAI0jI+py+2fwAHUSFvD3RlvG4HIp4nX5JFSpnZUJ6LlrM52OE7uSWosBHScgkSZj7dDKnWAAgA7); -} - -.mce-visualblocks hgroup { - padding-top: 10px; - border: 1px dashed #BBB; - margin: 0 0 1em 3px; - background: transparent no-repeat url(data:image/gif;base64,R0lGODlhJwAKAIABALu7uwAAACH5BAEAAAEALAAAAAAnAAoAAAI3jI+pywYNI3uB0gpsRtt5fFnfNZaVSYJil4Wo03Hv6Z62uOCgiXH1kZIIJ8NiIxRrAZNMZAtQAAA7); -} - -.mce-visualblocks aside { - padding-top: 10px; - border: 1px dashed #BBB; - margin: 0 0 1em 3px; - background: transparent no-repeat url(data:image/gif;base64,R0lGODlhHgAKAIABAKqqqv///yH5BAEAAAEALAAAAAAeAAoAAAItjI+pG8APjZOTzgtqy7I3f1yehmQcFY4WKZbqByutmW4aHUd6vfcVbgudgpYCADs=); -} - -.mce-visualblocks figcaption { - border: 1px dashed #BBB; -} - -.mce-visualblocks ul { - padding-top: 10px; - border: 1px dashed #BBB; - margin: 0 0 1em 3px; - background: transparent no-repeat url(data:image/gif;base64,R0lGODlhDQAKAIAAALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGuYnqUVSjvw26DzzXiqIDlVwAAOw==) -} - -.mce-visualblocks ol { - padding-top: 10px; - border: 1px dashed #BBB; - margin: 0 0 1em 3px; - background: transparent no-repeat url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybH6HHt0qourxC6CvzXieHyeWQAAOw==); -} diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/visualblocks/plugin.min.js b/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/visualblocks/plugin.min.js deleted file mode 100755 index cafa418736..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/visualblocks/plugin.min.js +++ /dev/null @@ -1 +0,0 @@ -tinymce.PluginManager.add("visualblocks",function(e,t){function n(){var t=this;t.active(r),e.on("VisualBlocks",function(){t.active(e.dom.hasClass(e.getBody(),"mce-visualblocks"))})}var i,a,r;window.NodeList&&(e.addCommand("mceVisualBlocks",function(){var n,o=e.dom;i||(i=o.uniqueId(),n=o.create("link",{id:i,rel:"stylesheet",href:t+"/css/visualblocks.css"}),e.getDoc().getElementsByTagName("head")[0].appendChild(n)),e.on("PreviewFormats AfterPreviewFormats",function(t){r&&o.toggleClass(e.getBody(),"mce-visualblocks","afterpreviewformats"==t.type)}),o.toggleClass(e.getBody(),"mce-visualblocks"),r=e.dom.hasClass(e.getBody(),"mce-visualblocks"),a&&a.active(o.hasClass(e.getBody(),"mce-visualblocks")),e.fire("VisualBlocks")}),e.addButton("visualblocks",{title:"Show blocks",cmd:"mceVisualBlocks",onPostRender:n}),e.addMenuItem("visualblocks",{text:"Show blocks",cmd:"mceVisualBlocks",onPostRender:n,selectable:!0,context:"view",prependToContext:!0}),e.on("init",function(){e.settings.visualblocks_default_state&&e.execCommand("mceVisualBlocks",!1,null,{skip_focus:!0})}),e.on("remove",function(){e.dom.removeClass(e.getBody(),"mce-visualblocks")}))}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/visualchars/plugin.min.js b/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/visualchars/plugin.min.js deleted file mode 100755 index 447423884e..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/visualchars/plugin.min.js +++ /dev/null @@ -1 +0,0 @@ -tinymce.PluginManager.add("visualchars",function(e){function t(t){var n,a,r,o,l,s,c=e.getBody(),d=e.selection;if(i=!i,e.fire("VisualChars",{state:i}),t&&(s=d.getBookmark()),i)for(a=[],tinymce.walk(c,function(e){3==e.nodeType&&e.nodeValue&&-1!=e.nodeValue.indexOf(" ")&&a.push(e)},"childNodes"),r=0;r$1
'),l=e.dom.create("div",null,o);n=l.lastChild;)e.dom.insertAfter(n,a[r]);e.dom.remove(a[r])}else for(a=e.dom.select("span.mce-nbsp",c),r=a.length-1;r>=0;r--)e.dom.remove(a[r],1);d.moveToBookmark(s)}function n(){var t=this;e.on("VisualChars",function(e){t.active(e.state)})}var i;e.addCommand("mceVisualChars",t),e.addButton("visualchars",{title:"Show invisible characters",cmd:"mceVisualChars",onPostRender:n}),e.addMenuItem("visualchars",{text:"Show invisible characters",cmd:"mceVisualChars",onPostRender:n,selectable:!0,context:"view",prependToContext:!0}),e.on("beforegetcontent",function(e){i&&"raw"!=e.format&&!e.draft&&(i=!0,t(!1))})}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/wordcount/plugin.min.js b/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/wordcount/plugin.min.js deleted file mode 100755 index acda5d044c..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/wordcount/plugin.min.js +++ /dev/null @@ -1 +0,0 @@ -tinymce.PluginManager.add("wordcount",function(e){function t(){e.theme.panel.find("#wordcount").text(["Words: {0}",a.getCount()])}var n,o,a=this;n=e.getParam("wordcount_countregex",/[\w\u2019\x27\-\u0600-\u06FF]+/g),o=e.getParam("wordcount_cleanregex",/[0-9.(),;:!?%#$?\x27\x22_+=\\\/\-]*/g),e.on("init",function(){var n=e.theme.panel&&e.theme.panel.find("#statusbar")[0];n&&window.setTimeout(function(){n.insert({type:"label",name:"wordcount",text:["Words: {0}",a.getCount()],classes:"wordcount",disabled:e.settings.readonly},0),e.on("setcontent beforeaddundo",t),e.on("keyup",function(e){32==e.keyCode&&t()})},0)}),a.getCount=function(){var t=e.getContent({format:"raw"}),a=0;if(t){t=t.replace(/\.\.\./g," "),t=t.replace(/<.[^<>]*?>/g," ").replace(/ | /gi," "),t=t.replace(/(\w+)(&.+?;)+(\w+)/,"$1$3").replace(/&.+?;/g," "),t=t.replace(o,"");var r=t.match(n);r&&(a=r.length)}return a}}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/content.inline.min.css b/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/content.inline.min.css deleted file mode 100644 index 2f0db0e954..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/content.inline.min.css +++ /dev/null @@ -1 +0,0 @@ -.mce-object{border:1px dotted #3A3A3A;background:#d5d5d5 url(img/object.gif) no-repeat center}.mce-pagebreak{cursor:default;display:block;border:0;width:100%;height:5px;border:1px dashed #666;margin-top:15px}.mce-item-anchor{cursor:default;display:inline-block;-webkit-user-select:all;-webkit-user-modify:read-only;-moz-user-select:all;-moz-user-modify:read-only;user-select:all;user-modify:read-only;width:9px !important;height:9px !important;border:1px dotted #3A3A3A;background:#d5d5d5 url(img/anchor.gif) no-repeat center}.mce-nbsp{background:#AAA}hr{cursor:default}.mce-match-marker{background:#AAA;color:#fff}.mce-match-marker-selected{background:#3399ff;color:#fff}.mce-spellchecker-word{background:url(img/wline.gif) repeat-x bottom left;cursor:default}.mce-item-table,.mce-item-table td,.mce-item-table th,.mce-item-table caption{border:1px dashed #BBB}td.mce-item-selected,th.mce-item-selected{background-color:#3399ff !important}.mce-edit-focus{outline:1px dotted #333} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/content.min.css b/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/content.min.css deleted file mode 100755 index 340a53778f..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/content.min.css +++ /dev/null @@ -1 +0,0 @@ -body{background-color:#FFFFFF;color:#000000;font-family:Verdana,Arial,Helvetica,sans-serif;font-size:11px;scrollbar-3dlight-color:#F0F0EE;scrollbar-arrow-color:#676662;scrollbar-base-color:#F0F0EE;scrollbar-darkshadow-color:#DDDDDD;scrollbar-face-color:#E0E0DD;scrollbar-highlight-color:#F0F0EE;scrollbar-shadow-color:#F0F0EE;scrollbar-track-color:#F5F5F5}td,th{font-family:Verdana,Arial,Helvetica,sans-serif;font-size:11px}.mce-object{border:1px dotted #3A3A3A;background:#d5d5d5 url(img/object.gif) no-repeat center}.mce-pagebreak{cursor:default;display:block;border:0;width:100%;height:5px;border:1px dashed #666;margin-top:15px}.mce-item-anchor{cursor:default;display:inline-block;-webkit-user-select:all;-webkit-user-modify:read-only;-moz-user-select:all;-moz-user-modify:read-only;user-select:all;user-modify:read-only;width:9px !important;height:9px !important;border:1px dotted #3A3A3A;background:#d5d5d5 url(img/anchor.gif) no-repeat center}.mce-nbsp{background:#AAA}hr{cursor:default}.mce-match-marker{background:#AAA;color:#fff}.mce-match-marker-selected{background:#3399ff;color:#fff}.mce-spellchecker-word{background:url(img/wline.gif) repeat-x bottom left;cursor:default}.mce-item-table,.mce-item-table td,.mce-item-table th,.mce-item-table caption{border:1px dashed #BBB}td.mce-item-selected,th.mce-item-selected{background-color:#3399ff !important}.mce-edit-focus{outline:1px dotted #333} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/fonts/icomoon-small.eot b/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/fonts/icomoon-small.eot deleted file mode 100644 index 43a30f9925..0000000000 Binary files a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/fonts/icomoon-small.eot and /dev/null differ diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/fonts/icomoon-small.svg b/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/fonts/icomoon-small.svg deleted file mode 100644 index d338114f07..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/fonts/icomoon-small.svg +++ /dev/null @@ -1,175 +0,0 @@ - - - - -This is a custom SVG font generated by IcoMoon. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/fonts/icomoon-small.ttf b/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/fonts/icomoon-small.ttf deleted file mode 100644 index 841c79c182..0000000000 Binary files a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/fonts/icomoon-small.ttf and /dev/null differ diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/fonts/icomoon-small.woff b/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/fonts/icomoon-small.woff deleted file mode 100644 index ad14a2406e..0000000000 Binary files a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/fonts/icomoon-small.woff and /dev/null differ diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/fonts/icomoon.eot b/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/fonts/icomoon.eot deleted file mode 100755 index eed4f8149a..0000000000 Binary files a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/fonts/icomoon.eot and /dev/null differ diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/fonts/icomoon.svg b/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/fonts/icomoon.svg deleted file mode 100755 index 727f61af13..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/fonts/icomoon.svg +++ /dev/null @@ -1,153 +0,0 @@ - - - - -This is a custom SVG font generated by IcoMoon. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/fonts/icomoon.ttf b/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/fonts/icomoon.ttf deleted file mode 100755 index dea6e458f9..0000000000 Binary files a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/fonts/icomoon.ttf and /dev/null differ diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/fonts/icomoon.woff b/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/fonts/icomoon.woff deleted file mode 100755 index f17657986c..0000000000 Binary files a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/fonts/icomoon.woff and /dev/null differ diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/fonts/readme.md b/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/fonts/readme.md deleted file mode 100755 index fa5d63946c..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/fonts/readme.md +++ /dev/null @@ -1 +0,0 @@ -Icons are generated and provided by the http://icomoon.io service. diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/fonts/tinymce-small.eot b/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/fonts/tinymce-small.eot deleted file mode 100644 index 128a98f3d8..0000000000 Binary files a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/fonts/tinymce-small.eot and /dev/null differ diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/fonts/tinymce-small.svg b/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/fonts/tinymce-small.svg deleted file mode 100644 index fd5e3ff4d2..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/fonts/tinymce-small.svg +++ /dev/null @@ -1,175 +0,0 @@ - - - - -This is a custom SVG font generated by IcoMoon. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/fonts/tinymce-small.ttf b/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/fonts/tinymce-small.ttf deleted file mode 100644 index 7b50bfdae9..0000000000 Binary files a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/fonts/tinymce-small.ttf and /dev/null differ diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/fonts/tinymce-small.woff b/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/fonts/tinymce-small.woff deleted file mode 100644 index 725aaf19e2..0000000000 Binary files a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/fonts/tinymce-small.woff and /dev/null differ diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/fonts/tinymce.eot b/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/fonts/tinymce.eot deleted file mode 100644 index b769c2c661..0000000000 Binary files a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/fonts/tinymce.eot and /dev/null differ diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/fonts/tinymce.svg b/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/fonts/tinymce.svg deleted file mode 100644 index fe4e31bf03..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/fonts/tinymce.svg +++ /dev/null @@ -1,153 +0,0 @@ - - - - -This is a custom SVG font generated by IcoMoon. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/fonts/tinymce.ttf b/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/fonts/tinymce.ttf deleted file mode 100644 index a31b584c2b..0000000000 Binary files a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/fonts/tinymce.ttf and /dev/null differ diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/fonts/tinymce.woff b/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/fonts/tinymce.woff deleted file mode 100644 index f0e8a34c6f..0000000000 Binary files a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/fonts/tinymce.woff and /dev/null differ diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/img/anchor.gif b/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/img/anchor.gif deleted file mode 100755 index 606348c7f5..0000000000 Binary files a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/img/anchor.gif and /dev/null differ diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/img/loader.gif b/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/img/loader.gif deleted file mode 100755 index c69e937232..0000000000 Binary files a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/img/loader.gif and /dev/null differ diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/img/object.gif b/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/img/object.gif deleted file mode 100755 index cccd7f023f..0000000000 Binary files a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/img/object.gif and /dev/null differ diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/img/trans.gif b/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/img/trans.gif deleted file mode 100755 index 388486517f..0000000000 Binary files a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/img/trans.gif and /dev/null differ diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/img/wline.gif b/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/img/wline.gif deleted file mode 100755 index 7d0a4dbca0..0000000000 Binary files a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/img/wline.gif and /dev/null differ diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/skin.classic.min.css b/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/skin.classic.min.css deleted file mode 100755 index 1c7e683e61..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/skin.classic.min.css +++ /dev/null @@ -1 +0,0 @@ -.mce-container,.mce-container *,.mce-widget,.mce-widget *{margin:0;padding:0;border:0;outline:0;vertical-align:top;background:transparent;text-decoration:none;color:#000;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;text-shadow:none;float:none;position:static;width:auto;height:auto;white-space:nowrap;cursor:inherit;-webkit-tap-highlight-color:transparent;line-height:normal}.mce-container *[unselectable]{-moz-user-select:none;-webkit-user-select:none;-o-user-select:none;user-select:none}.mce-container ::-webkit-scrollbar{width:8px;height:8px;-webkit-border-radius:4px}.mce-container ::-webkit-scrollbar-track,.mce-container ::-webkit-scrollbar-track-piece{background-color:transparent}.mce-container ::-webkit-scrollbar-thumb{background-color:rgba(53,57,71,0.3);width:6px;height:6px;-webkit-border-radius:4px}.mce-fade{opacity:0;-webkit-transition:opacity .15s linear;transition:opacity .15s linear}.mce-fade.mce-in{opacity:1}.mce-tinymce{visibility:visible!important;position:relative}.mce-fullscreen{border:0;padding:0;margin:0;overflow:hidden;background:#FFF;height:100%;z-index:100}div.mce-fullscreen{position:fixed;top:0;left:0;width:100%;height:auto}.mce-tinymce{display:block;border-radius:2px}.mce-wordcount{float:right;padding:8px}.mce-edit-area{background:#FFF;filter:none}.mce-statusbar{position:relative}.mce-statusbar .mce-container-body{position:relative}.mce-fullscreen .mce-resizehandle{display:none}.mce-charmap{border-collapse:collapse}.mce-charmap td{cursor:default;border:1px solid #c5c5c5;width:20px;height:20px;line-height:20px;text-align:center;vertical-align:center;padding:2px}.mce-charmap td:hover{background:#d9d9d9}.mce-grid{border-spacing:2px;border-collapse:separate}.mce-grid a{display:block;border:1px solid transparent}.mce-grid a:hover{border-color:#c5c5c5}.mce-grid-border{margin:0 4px 0 4px}.mce-grid-border a{border-color:#e8e8e8;width:13px;height:13px}.mce-grid-border a:hover,.mce-grid-border a.mce-active{border-color:#c4daff;background:#deeafa}.mce-text-center{text-align:center}div.mce-tinymce-inline{width:100%;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.mce-container,.mce-container-body{display:block}.mce-autoscroll{overflow:hidden}.mce-scrollbar{position:absolute;width:7px;height:100%;top:2px;right:2px;opacity:.4;filter:alpha(opacity=40);zoom:1}.mce-scrollbar-h{top:auto;right:auto;left:2px;bottom:2px;width:100%;height:7px}.mce-scrollbar-thumb{position:absolute;background-color:#000;border:1px solid #888;border-color:rgba(85,85,85,0.6);width:5px;height:100%;-webkit-border-radius:7px;-moz-border-radius:7px;border-radius:7px}.mce-scrollbar-h .mce-scrollbar-thumb{width:100%;height:5px}.mce-scrollbar:hover,.mce-scrollbar.mce-active{background-color:#AAA;opacity:.6;filter:alpha(opacity=60);zoom:1;-webkit-border-radius:7px;-moz-border-radius:7px;border-radius:7px}.mce-scroll{position:relative}.mce-panel{border:0 solid #9e9e9e;background-color:#f0f0f0;background-image:-moz-linear-gradient(top,#fdfdfd,#ddd);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fdfdfd),to(#ddd));background-image:-webkit-linear-gradient(top,#fdfdfd,#ddd);background-image:-o-linear-gradient(top,#fdfdfd,#ddd);background-image:linear-gradient(to bottom,#fdfdfd,#ddd);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffdfdfd',endColorstr='#ffdddddd',GradientType=0);zoom:1}.mce-floatpanel{position:absolute;-webkit-box-shadow:#ccc 5px 5px 5px;-moz-box-shadow:#ccc 5px 5px 5px;box-shadow:#ccc 5px 5px 5px}.mce-floatpanel .mce-arrow,.mce-floatpanel .mce-arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.mce-floatpanel .mce-arrow{border-width:11px}.mce-floatpanel .mce-arrow:after{border-width:10px;content:""}.mce-floatpanel.mce-popover{position:absolute;top:0;left:0;background:#fff;-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2)}.mce-floatpanel.mce-popover.mce-bottom{margin-top:10px}.mce-floatpanel.mce-popover.mce-bottom>.mce-arrow{left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,0.25);top:-11px}.mce-floatpanel.mce-popover.mce-bottom>.mce-arrow:after{top:1px;margin-left:-10px;border-top-width:0;border-bottom-color:#fff}.mce-floatpanel.mce-popover.mce-bottom.mce-start{margin-left:-22px}.mce-floatpanel.mce-popover.mce-bottom.mce-start>.mce-arrow{left:20px}.mce-floatpanel.mce-popover.mce-bottom.mce-end{margin-left:22px}.mce-floatpanel.mce-popover.mce-bottom.mce-end>.mce-arrow{right:10px;left:auto}.mce-fullscreen{border:0;padding:0;margin:0;overflow:hidden;background:#FFF;height:100%}div.mce-fullscreen{position:fixed;top:0;left:0}#mce-modal-block{opacity:0;filter:alpha(opacity=0);zoom:1;position:fixed;left:0;top:0;width:100%;height:100%;background:#000}#mce-modal-block.mce-in{opacity:.3;filter:alpha(opacity=30);zoom:1}.mce-window-move{cursor:move}.mce-window{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0,0,0,0.3);-moz-box-shadow:0 3px 7px rgba(0,0,0,0.3);box-shadow:0 3px 7px rgba(0,0,0,0.3);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background:transparent;background:#FFF;position:fixed;top:0;left:0;opacity:0;-webkit-transition:opacity 150ms ease-in;transition:opacity 150ms ease-in}.mce-window.mce-in{opacity:1}.mce-window-head{padding:9px 15px;border-bottom:1px solid #EEE;position:relative}.mce-window-head .mce-close{position:absolute;right:15px;top:9px;font-size:20px;font-weight:bold;line-height:20px;color:#CCC;text-shadow:0 1px 0 white;cursor:pointer;height:20px;overflow:hidden}.mce-close:hover{color:#AAA}.mce-window-head .mce-title{display:inline-block;*display:inline;*zoom:1;line-height:20px;font-size:20px;font-weight:bold;text-rendering:optimizelegibility;padding-right:10px}.mce-window .mce-container-body{display:block}.mce-foot{display:block;background-color:whiteSmoke;border-top:1px solid #DDD;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;-webkit-box-shadow:inset 0 1px 0 #fff;-moz-box-shadow:inset 0 1px 0 #fff;box-shadow:inset 0 1px 0 #fff}.mce-window-head .mce-dragh{position:absolute;top:0;left:0;cursor:move;width:90%;height:100%}.mce-window iframe{width:100%;height:100%}.mce-window.mce-fullscreen,.mce-window.mce-fullscreen .mce-foot{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.mce-abs-layout{position:relative}body .mce-abs-layout-item,.mce-abs-end{position:absolute}.mce-abs-end{width:1px;height:1px}.mce-container-body.mce-abs-layout{overflow:hidden}.mce-tooltip{position:absolute;padding:5px;opacity:.8;filter:alpha(opacity=80);zoom:1}.mce-tooltip-inner{font-size:11px;background-color:#000;color:#fff;max-width:200px;padding:5px 8px 4px 8px;text-align:center;white-space:normal}.mce-tooltip-inner{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.mce-tooltip-inner{-webkit-box-shadow:0 0 5px #000;-moz-box-shadow:0 0 5px #000;box-shadow:0 0 5px #000}.mce-tooltip-arrow{position:absolute;width:0;height:0;line-height:0;border:5px dashed #000}.mce-tooltip-arrow-n{border-bottom-color:#000}.mce-tooltip-arrow-s{border-top-color:#000}.mce-tooltip-arrow-e{border-left-color:#000}.mce-tooltip-arrow-w{border-right-color:#000}.mce-tooltip-n .mce-tooltip-arrow{top:0;left:50%;margin-left:-5px;border-bottom-style:solid;border-top:0;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-nw .mce-tooltip-arrow{top:0;left:10px;border-bottom-style:solid;border-top:0;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-ne .mce-tooltip-arrow{top:0;right:10px;border-bottom-style:solid;border-top:0;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-s .mce-tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-top-style:solid;border-bottom:0;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-sw .mce-tooltip-arrow{bottom:0;left:10px;border-top-style:solid;border-bottom:0;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-se .mce-tooltip-arrow{bottom:0;right:10px;border-top-style:solid;border-bottom:0;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-e .mce-tooltip-arrow{right:0;top:50%;margin-top:-5px;border-left-style:solid;border-right:0;border-top-color:transparent;border-bottom-color:transparent}.mce-tooltip-w .mce-tooltip-arrow{left:0;top:50%;margin-top:-5px;border-right-style:solid;border-left:none;border-top-color:transparent;border-bottom-color:transparent}.mce-btn{border:1px solid #c5c5c5;position:relative;color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#f0f0f0;background-image:-moz-linear-gradient(top,#fff,#d9d9d9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#d9d9d9));background-image:-webkit-linear-gradient(top,#fff,#d9d9d9);background-image:-o-linear-gradient(top,#fff,#d9d9d9);background-image:linear-gradient(to bottom,#fff,#d9d9d9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffd9d9d9',GradientType=0);zoom:1;border-color:#d9d9d9 #d9d9d9 #b3b3b3;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);display:inline-block;*display:inline;*zoom:1;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.mce-btn:hover,.mce-btn:focus{text-decoration:none;color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#e3e3e3;background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f2f2f2),to(#ccc));background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(to bottom,#f2f2f2,#ccc);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2f2f2',endColorstr='#ffcccccc',GradientType=0);zoom:1;border-color:#ccc #ccc #a6a6a6;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25)}.mce-btn.mce-disabled,.mce-btn.mce-disabled:hover{cursor:default;background-image:none;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;opacity:.65;filter:alpha(opacity=65);zoom:1}.mce-btn.mce-active,.mce-btn.mce-active:hover{color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#d6d6d6;background-image:-moz-linear-gradient(top,#e6e6e6,#bfbfbf);background-image:-webkit-gradient(linear,0 0,0 100%,from(#e6e6e6),to(#bfbfbf));background-image:-webkit-linear-gradient(top,#e6e6e6,#bfbfbf);background-image:-o-linear-gradient(top,#e6e6e6,#bfbfbf);background-image:linear-gradient(to bottom,#e6e6e6,#bfbfbf);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe6e6e6',endColorstr='#ffbfbfbf',GradientType=0);zoom:1;border-color:#bfbfbf #bfbfbf #999;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.mce-btn button{padding:4px 10px;font-size:14px;line-height:20px;*line-height:16px;cursor:pointer;color:#333;overflow:visible;-webkit-appearance:none}.mce-btn button::-moz-focus-inner{border:0;padding:0}.mce-btn i{text-shadow:1px 1px #fff}.mce-primary{color:#fff;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#006dcc;background-image:-moz-linear-gradient(top,#08c,#04c);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#04c));background-image:-webkit-linear-gradient(top,#08c,#04c);background-image:-o-linear-gradient(top,#08c,#04c);background-image:linear-gradient(to bottom,#08c,#04c);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0044cc',GradientType=0);zoom:1;border-color:#04c #04c #002b80;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25)}.mce-primary:hover,.mce-primary:focus{color:#fff;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#005fb3;background-image:-moz-linear-gradient(top,#0077b3,#003cb3);background-image:-webkit-gradient(linear,0 0,0 100%,from(#0077b3),to(#003cb3));background-image:-webkit-linear-gradient(top,#0077b3,#003cb3);background-image:-o-linear-gradient(top,#0077b3,#003cb3);background-image:linear-gradient(to bottom,#0077b3,#003cb3);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0077b3',endColorstr='#ff003cb3',GradientType=0);zoom:1;border-color:#003cb3 #003cb3 #026;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25)}.mce-primary button{color:#fff}.mce-btn-large button{padding:9px 14px;font-size:16px;line-height:normal;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.mce-btn-large i{margin-top:2px}.mce-btn-small button{padding:3px 5px;font-size:12px;line-height:15px}.mce-btn-small i{margin-top:0}.mce-btn .mce-caret{margin-top:8px;*margin-top:6px;margin-left:0}.mce-btn-small .mce-caret{margin-top:6px;*margin-top:4px;margin-left:0}.mce-caret{display:inline-block;*display:inline;*zoom:1;width:0;height:0;vertical-align:top;border-top:4px solid #444;border-right:4px solid transparent;border-left:4px solid transparent;content:""}.mce-disabled .mce-caret{border-top-color:#999}.mce-caret.mce-up{border-bottom:4px solid #444;border-top:0}.mce-btn-group .mce-btn{border-width:1px 0 1px 0;margin:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.mce-btn-group .mce-btn:hover,.mce-btn-group .mce-btn:focus{color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#e3e3e3;background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f2f2f2),to(#ccc));background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(to bottom,#f2f2f2,#ccc);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2f2f2',endColorstr='#ffcccccc',GradientType=0);zoom:1;border-color:#ccc #ccc #a6a6a6;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25)}.mce-btn-group .mce-btn.mce-disabled,.mce-btn-group .mce-btn.mce-disabled:hover{-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#f0f0f0;background-image:-moz-linear-gradient(top,#fff,#d9d9d9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#d9d9d9));background-image:-webkit-linear-gradient(top,#fff,#d9d9d9);background-image:-o-linear-gradient(top,#fff,#d9d9d9);background-image:linear-gradient(to bottom,#fff,#d9d9d9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffd9d9d9',GradientType=0);zoom:1;border-color:#d9d9d9 #d9d9d9 #b3b3b3;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25)}.mce-btn-group .mce-btn.mce-active,.mce-btn-group .mce-btn.mce-active:hover,.mce-btn-group .mce-btn:active{color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#d6d6d6;background-image:-moz-linear-gradient(top,#e6e6e6,#bfbfbf);background-image:-webkit-gradient(linear,0 0,0 100%,from(#e6e6e6),to(#bfbfbf));background-image:-webkit-linear-gradient(top,#e6e6e6,#bfbfbf);background-image:-o-linear-gradient(top,#e6e6e6,#bfbfbf);background-image:linear-gradient(to bottom,#e6e6e6,#bfbfbf);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe6e6e6',endColorstr='#ffbfbfbf',GradientType=0);zoom:1;border-color:#bfbfbf #bfbfbf #999;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.mce-btn-group .mce-btn.mce-disabled button{opacity:.65;filter:alpha(opacity=65);zoom:1}.mce-btn-group .mce-first{border-left:1px solid #c5c5c5;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px}.mce-btn-group .mce-last{border-right:1px solid #c5c5c5;-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0}.mce-btn-group .mce-first.mce-last{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.mce-btn-group .mce-btn.mce-flow-layout-item{margin:0}.mce-checkbox{cursor:pointer}i.mce-i-checkbox{margin:0 3px 0 0;border:1px solid #c5c5c5;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);background-color:#f0f0f0;background-image:-moz-linear-gradient(top,#fdfdfd,#ddd);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fdfdfd),to(#ddd));background-image:-webkit-linear-gradient(top,#fdfdfd,#ddd);background-image:-o-linear-gradient(top,#fdfdfd,#ddd);background-image:linear-gradient(to bottom,#fdfdfd,#ddd);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffdfdfd',endColorstr='#ffdddddd',GradientType=0);zoom:1;text-indent:-10em;*font-size:0;*line-height:0;*text-indent:0}.mce-checked i.mce-i-checkbox{color:#000;font-size:16px;line-height:16px;text-indent:0}.mce-checkbox:focus i.mce-i-checkbox{border:1px solid #59a5e1;border:1px solid rgba(82,168,236,0.8);-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6)}.mce-colorbutton .mce-ico{position:relative}.mce-colorpicker{background:#FFF}.mce-colorbutton-grid{margin:4px}.mce-grid td div{border:1px solid #808080;width:12px;height:12px;margin:2px;cursor:pointer}.mce-grid td div:hover{border-color:black}.mce-grid td div:focus{border-color:#59a5e1;outline:1px solid rgba(82,168,236,0.8);border-color:rgba(82,168,236,0.8)}.mce-colorbutton{position:relative}.mce-colorbutton .mce-preview{display:block;position:absolute;left:50%;top:50%;margin-left:-8px;margin-top:7px;background:gray;width:16px;height:2px;overflow:hidden}.mce-combobox{display:inline-block;*display:inline;*zoom:1;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;width:100px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.mce-combobox input{border-color:1px solid #c5c5c5;border-right-color:rgba(0,0,0,0.15);height:28px}.mce-combobox.mce-has-open input{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.mce-combobox .mce-btn{border-left:0;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.mce-combobox button{padding-right:8px;padding-left:8px}.mce-combobox *:focus{border-color:#59a5e1;border-color:rgba(82,168,236,0.8);-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6)}.mce-path{display:inline-block;*display:inline;*zoom:1;padding:8px;white-space:normal}.mce-path .mce-txt{display:inline-block;padding-right:3px}.mce-path .mce-path-body{display:inline-block}.mce-path-item{display:inline-block;*display:inline;*zoom:1;cursor:pointer;color:#000}.mce-path-item:hover{text-decoration:underline}.mce-path-item:focus{background:gray;color:white}.mce-path .mce-divider{display:inline}.mce-fieldset{border:0 solid #9e9e9e;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.mce-fieldset>.mce-container-body{margin-top:-15px}.mce-fieldset-title{margin-left:5px;padding:0 5px 0 5px}.mce-fit-layout{display:inline-block;*display:inline;*zoom:1}.mce-fit-layout-item{position:absolute}.mce-flow-layout-item{display:inline-block;*display:inline;*zoom:1}.mce-flow-layout-item{margin:2px 0 2px 2px}.mce-flow-layout-item.mce-last{margin-right:2px}.mce-flow-layout{white-space:normal}.mce-iframe{border:0 solid #c5c5c5;width:100%;height:100%}.mce-label{display:inline-block;*display:inline;*zoom:1;text-shadow:0 1px 1px rgba(255,255,255,0.75);border:0 solid #c5c5c5;overflow:hidden}.mce-label.mce-autoscroll{overflow:auto}.mce-label-disabled .mce-text{color:#999}.mce-label.mce-multiline{white-space:pre-wrap}.mce-menubar .mce-menubtn{border-color:transparent;background:transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;filter:none}.mce-menubar{border:1px solid #ddd}.mce-menubar .mce-menubtn button{color:#000}.mce-menubar .mce-menubtn:hover,.mce-menubar .mce-menubtn.mce-active,.mce-menubtn:focus{border-color:transparent;background:#ddd;filter:none}.mce-menubtn.mce-disabled span{color:#999}.mce-listbox button{text-align:left;padding-right:20px;position:relative}.mce-listbox .mce-caret{position:absolute;margin-top:-2px;right:8px;top:50%}.mce-listbox span{width:100%;display:block;overflow:hidden}.mce-menu-item{display:block;padding:6px 20px;clear:both;font-weight:normal;line-height:20px;color:#333;white-space:nowrap;cursor:pointer;line-height:normal}.mce-menu-item.mce-disabled .mce-text{color:#999}.mce-menu-item:hover,.mce-menu-item.mce-selected,.mce-menu-item:focus{text-decoration:none;color:#fff;background-color:#0081c2;background-image:-moz-linear-gradient(top,#08c,#0077b3);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#0077b3));background-image:-webkit-linear-gradient(top,#08c,#0077b3);background-image:-o-linear-gradient(top,#08c,#0077b3);background-image:linear-gradient(to bottom,#08c,#0077b3);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0077b3',GradientType=0);zoom:1}.mce-menu-item:hover .mce-text,.mce-menu-item.mce-selected .mce-text{color:#fff}.mce-menu-item:hover .mce-ico,.mce-menu-item.mce-selected .mce-ico,.mce-menu-item:focus .mce-ico{color:white}.mce-menu-shortcut{display:inline-block;color:#999}.mce-menu-shortcut{display:inline-block;*display:inline;*zoom:1;padding:0 20px 0 20px}.mce-menu-item .mce-caret{margin-top:6px;*margin-top:3px;margin-right:6px;border-top:4px solid transparent;border-bottom:4px solid transparent;border-left:4px solid #666}.mce-menu-item.mce-selected .mce-caret,.mce-menu-item:focus .mce-caret{border-left-color:#FFF}.mce-menu-align .mce-menu-shortcut{*margin-top:-2px}.mce-menu-align .mce-menu-shortcut,.mce-menu-align .mce-caret{position:absolute;right:0}.mce-menu-item-sep,.mce-menu-item-sep:hover{padding:0;height:1px;margin:9px 1px;overflow:hidden;background:#e5e5e5;border-bottom:1px solid white;cursor:default;filter:none}.mce-menu-item.mce-active i{visibility:visible}.mce-menu-item.mce-active{background-color:#c8def4;outline:1px solid #c5c5c5}.mce-menu-item-checkbox.mce-active{background-color:#FFF;outline:0}.mce-menu{filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background:transparent;z-index:1000;padding:5px 0 5px 0;margin:2px 0 0;min-width:160px;background:#FFF;border:1px solid #CCC;border:1px solid rgba(0,0,0,0.2);z-index:1002;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2)}.mce-menu i{display:none}.mce-menu-has-icons i{display:inline-block;*display:inline;*zoom:1}.mce-menu-sub{margin-top:-6px;margin-left:-1px;-webkit-border-radius:0 6px 6px 6px;-moz-border-radius:0 6px 6px 6px;border-radius:0 6px 6px 6px}i.mce-radio{padding:1px;margin:0 3px 0 0;background-color:#fafafa;border:1px solid #cacece;-webkit-border-radius:8px;-moz-border-radius:8px;border-radius:8px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);background-color:#f0f0f0;background-image:-moz-linear-gradient(top,#fdfdfd,#ddd);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fdfdfd),to(#ddd));background-image:-webkit-linear-gradient(top,#fdfdfd,#ddd);background-image:-o-linear-gradient(top,#fdfdfd,#ddd);background-image:linear-gradient(to bottom,#fdfdfd,#ddd);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffdfdfd',endColorstr='#ffdddddd',GradientType=0);zoom:1}i.mce-radio:after{font-family:Arial;font-size:12px;color:#000;content:'\25cf'}.mce-container-body .mce-resizehandle{position:absolute;right:0;bottom:0;width:16px;height:16px;visibility:visible;cursor:s-resize;margin:0}.mce-resizehandle-both{cursor:se-resize}i.mce-i-resize{color:#000}.mce-spacer{visibility:hidden}.mce-splitbtn .mce-open{border-left:1px solid transparent;border-right:1px solid transparent}.mce-splitbtn:hover .mce-open{border-left-color:#c5c5c5;border-right-color:#c5c5c5}.mce-splitbtn button{padding-right:4px}.mce-splitbtn .mce-open{padding-left:4px}.mce-splitbtn .mce-open.mce-active{-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.mce-stack-layout-item{display:block}.mce-tabs{display:block;border-bottom:1px solid #ccc}.mce-tab{display:inline-block;*display:inline;*zoom:1;border:1px solid #ccc;border-width:1px 1px 0 0;background:#e3e3e3;padding:8px;text-shadow:0 1px 1px rgba(255,255,255,0.75);height:13px;cursor:pointer}.mce-tab:hover{background:#fdfdfd}.mce-tab.mce-active{background:#fdfdfd;border-bottom-color:transparent;margin-bottom:-1px;height:14px}.mce-textbox{background:#FFF;border:1px solid #c5c5c5;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);display:inline-block;-webkit-transition:border linear .2s,box-shadow linear .2s;transition:border linear .2s,box-shadow linear .2s;height:28px;resize:none;padding:0 4px 0 4px;white-space:normal;color:#000}.mce-textbox:focus{border-color:rgba(82,168,236,0.8);-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6)}.mce-placeholder .mce-textbox{color:#aaa}.mce-textbox.mce-multiline{padding:4px}.mce-throbber{position:absolute;top:0;left:0;width:100%;height:100%;opacity:.6;filter:alpha(opacity=60);zoom:1;background:#fff url('img/loader.gif') no-repeat center center}@font-face{font-family:'tinymce';src:url('fonts/icomoon.eot');src:url('fonts/icomoon.eot?#iefix') format('embedded-opentype'),url('fonts/icomoon.svg#icomoon') format('svg'),url('fonts/icomoon.woff') format('woff'),url('fonts/icomoon.ttf') format('truetype');font-weight:normal;font-style:normal}.mce-ico{font-family:'tinymce',Arial;font-style:normal;font-weight:normal;font-size:16px;line-height:16px;vertical-align:text-top;-webkit-font-smoothing:antialiased;display:inline-block;background:transparent center center;width:16px;height:16px;color:#333}.mce-i-save:before{content:"\e000"}.mce-i-newdocument:before{content:"\e001"}.mce-i-fullpage:before{content:"\e002"}.mce-i-alignleft:before{content:"\e003"}.mce-i-aligncenter:before{content:"\e004"}.mce-i-alignright:before{content:"\e005"}.mce-i-alignjustify:before{content:"\e006"}.mce-i-cut:before{content:"\e007"}.mce-i-paste:before{content:"\e008"}.mce-i-searchreplace:before{content:"\e009"}.mce-i-bullist:before{content:"\e00a"}.mce-i-numlist:before{content:"\e00b"}.mce-i-indent:before{content:"\e00c"}.mce-i-outdent:before{content:"\e00d"}.mce-i-blockquote:before{content:"\e00e"}.mce-i-undo:before{content:"\e00f"}.mce-i-redo:before{content:"\e010"}.mce-i-link:before{content:"\e011"}.mce-i-unlink:before{content:"\e012"}.mce-i-anchor:before{content:"\e013"}.mce-i-image:before{content:"\e014"}.mce-i-media:before{content:"\e015"}.mce-i-help:before{content:"\e016"}.mce-i-code:before{content:"\e017"}.mce-i-inserttime:before{content:"\e018"}.mce-i-preview:before{content:"\e019"}.mce-i-forecolor:before{content:"\e01a"}.mce-i-backcolor:before{content:"\e01a"}.mce-i-table:before{content:"\e01b"}.mce-i-hr:before{content:"\e01c"}.mce-i-removeformat:before{content:"\e01d"}.mce-i-subscript:before{content:"\e01e"}.mce-i-superscript:before{content:"\e01f"}.mce-i-charmap:before{content:"\e020"}.mce-i-emoticons:before{content:"\e021"}.mce-i-print:before{content:"\e022"}.mce-i-fullscreen:before{content:"\e023"}.mce-i-spellchecker:before{content:"\e024"}.mce-i-nonbreaking:before{content:"\e025"}.mce-i-template:before{content:"\e026"}.mce-i-pagebreak:before{content:"\e027"}.mce-i-restoredraft:before{content:"\e028"}.mce-i-untitled:before{content:"\e029"}.mce-i-bold:before{content:"\e02a"}.mce-i-italic:before{content:"\e02b"}.mce-i-underline:before{content:"\e02c"}.mce-i-strikethrough:before{content:"\e02d"}.mce-i-visualchars:before{content:"\e02e"}.mce-i-ltr:before{content:"\e02f"}.mce-i-rtl:before{content:"\e030"}.mce-i-copy:before{content:"\e031"}.mce-i-resize:before{content:"\e032"}.mce-i-browse:before{content:"\e034"}.mce-i-checkbox:before,.mce-i-selected:before{content:"\e033"}.mce-i-selected{visibility:hidden}i.mce-i-backcolor{text-shadow:none;background:#BBB} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/skin.ie7.min.css b/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/skin.ie7.min.css deleted file mode 100755 index 509ce7f078..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/skin.ie7.min.css +++ /dev/null @@ -1 +0,0 @@ -.mce-container,.mce-container *,.mce-widget,.mce-widget *{margin:0;padding:0;border:0;outline:0;vertical-align:top;background:transparent;text-decoration:none;color:#333;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;text-shadow:none;float:none;position:static;width:auto;height:auto;white-space:nowrap;cursor:inherit;-webkit-tap-highlight-color:transparent;line-height:normal;font-weight:normal;text-align:left;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box;direction:ltr}.mce-widget button{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.mce-container *[unselectable]{-moz-user-select:none;-webkit-user-select:none;-o-user-select:none;user-select:none}.mce-container ::-webkit-scrollbar{width:8px;height:8px;-webkit-border-radius:4px}.mce-container ::-webkit-scrollbar-track,.mce-container ::-webkit-scrollbar-track-piece{background-color:transparent}.mce-container ::-webkit-scrollbar-thumb{background-color:rgba(53,57,71,0.3);width:6px;height:6px;-webkit-border-radius:4px}.mce-fade{opacity:0;-webkit-transition:opacity .15s linear;transition:opacity .15s linear}.mce-fade.mce-in{opacity:1}.mce-tinymce{visibility:visible !important;position:relative}.mce-fullscreen{border:0;padding:0;margin:0;overflow:hidden;height:100%;z-index:100}div.mce-fullscreen{position:fixed;top:0;left:0;width:100%;height:auto}.mce-tinymce{display:block;-webkit-border-radius:2px;-moz-border-radius:2px;border-radius:2px}.mce-wordcount{position:absolute;top:0;right:0;padding:8px}div.mce-edit-area{background:#FFF;filter:none}.mce-statusbar{position:relative}.mce-statusbar .mce-container-body{position:relative}.mce-fullscreen .mce-resizehandle{display:none}.mce-charmap{border-collapse:collapse}.mce-charmap td{cursor:default;border:1px solid #9e9e9e;width:20px;height:20px;line-height:20px;text-align:center;vertical-align:middle;padding:2px}.mce-charmap td div{text-align:center}.mce-charmap td:hover{background:#d9d9d9}.mce-grid td div{border:1px solid #d6d6d6;width:12px;height:12px;margin:2px;cursor:pointer}.mce-grid{border-spacing:2px;border-collapse:separate}.mce-grid a{display:block;border:1px solid transparent}.mce-grid a:hover{border-color:#a1a1a1}.mce-grid-border{margin:0 4px 0 4px}.mce-grid-border a{border-color:#d6d6d6;width:13px;height:13px}.mce-grid-border a:hover,.mce-grid-border a.mce-active{border-color:#a1a1a1;background:#c8def4}.mce-text-center{text-align:center}div.mce-tinymce-inline{width:100%;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.mce-toolbar-grp{padding-bottom:2px}.mce-toolbar-grp .mce-flow-layout-item{margin-bottom:0}.mce-rtl .mce-wordcount{left:0;right:auto}.mce-container,.mce-container-body{display:block}.mce-autoscroll{overflow:hidden}.mce-scrollbar{position:absolute;width:7px;height:100%;top:2px;right:2px;opacity:.4;filter:alpha(opacity=40);zoom:1}.mce-scrollbar-h{top:auto;right:auto;left:2px;bottom:2px;width:100%;height:7px}.mce-scrollbar-thumb{position:absolute;background-color:#000;border:1px solid #888;border-color:rgba(85,85,85,0.6);width:5px;height:100%;-webkit-border-radius:7px;-moz-border-radius:7px;border-radius:7px}.mce-scrollbar-h .mce-scrollbar-thumb{width:100%;height:5px}.mce-scrollbar:hover,.mce-scrollbar.mce-active{background-color:#AAA;opacity:.6;filter:alpha(opacity=60);zoom:1;-webkit-border-radius:7px;-moz-border-radius:7px;border-radius:7px}.mce-scroll{position:relative}.mce-panel{border:0 solid #9e9e9e;background-color:#f0f0f0;background-image:-moz-linear-gradient(top, #fdfdfd, #ddd);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#fdfdfd), to(#ddd));background-image:-webkit-linear-gradient(top, #fdfdfd, #ddd);background-image:-o-linear-gradient(top, #fdfdfd, #ddd);background-image:linear-gradient(to bottom, #fdfdfd, #ddd);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffdfdfd', endColorstr='#ffdddddd', GradientType=0);zoom:1}.mce-floatpanel{position:absolute;-webkit-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);-moz-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);box-shadow:0 5px 10px rgba(0, 0, 0, 0.2)}.mce-floatpanel.mce-fixed{position:fixed}.mce-floatpanel .mce-arrow,.mce-floatpanel .mce-arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.mce-floatpanel .mce-arrow{border-width:11px}.mce-floatpanel .mce-arrow:after{border-width:10px;content:""}.mce-floatpanel.mce-popover{filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background:transparent;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);-moz-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);top:0;left:0;background:#fff;border:1px solid #9e9e9e;border:1px solid rgba(0,0,0,0.25)}.mce-floatpanel.mce-popover.mce-bottom{margin-top:10px;*margin-top:0}.mce-floatpanel.mce-popover.mce-bottom>.mce-arrow{left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#9e9e9e;border-bottom-color:rgba(0,0,0,0.25);top:-11px}.mce-floatpanel.mce-popover.mce-bottom>.mce-arrow:after{top:1px;margin-left:-10px;border-top-width:0;border-bottom-color:#fff}.mce-floatpanel.mce-popover.mce-bottom.mce-start{margin-left:-22px}.mce-floatpanel.mce-popover.mce-bottom.mce-start>.mce-arrow{left:20px}.mce-floatpanel.mce-popover.mce-bottom.mce-end{margin-left:22px}.mce-floatpanel.mce-popover.mce-bottom.mce-end>.mce-arrow{right:10px;left:auto}.mce-fullscreen{border:0;padding:0;margin:0;overflow:hidden;background:#fff;height:100%}div.mce-fullscreen{position:fixed;top:0;left:0}#mce-modal-block{opacity:0;filter:alpha(opacity=0);zoom:1;position:fixed;left:0;top:0;width:100%;height:100%;background:#000}#mce-modal-block.mce-in{opacity:.3;filter:alpha(opacity=30);zoom:1}.mce-window-move{cursor:move}.mce-window{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-moz-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background:transparent;background:#fff;position:fixed;top:0;left:0;opacity:0;-webkit-transition:opacity 150ms ease-in;transition:opacity 150ms ease-in}.mce-window.mce-in{opacity:1}.mce-window-head{padding:9px 15px;border-bottom:1px solid #c5c5c5;position:relative}.mce-window-head .mce-close{position:absolute;right:15px;top:9px;font-size:20px;font-weight:bold;line-height:20px;color:#858585;cursor:pointer;height:20px;overflow:hidden}.mce-close:hover{color:#adadad}.mce-window-head .mce-title{line-height:20px;font-size:20px;font-weight:bold;text-rendering:optimizelegibility;padding-right:10px}.mce-window .mce-container-body{display:block}.mce-foot{display:block;background-color:#fff;border-top:1px solid #c5c5c5;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px}.mce-window-head .mce-dragh{position:absolute;top:0;left:0;cursor:move;width:90%;height:100%}.mce-window iframe{width:100%;height:100%}.mce-window.mce-fullscreen,.mce-window.mce-fullscreen .mce-foot{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.mce-rtl .mce-window-head .mce-close{position:absolute;right:auto;left:15px}.mce-rtl .mce-window-head .mce-dragh{left:auto;right:0}.mce-rtl .mce-window-head .mce-title{direction:rtl;text-align:right}.mce-abs-layout{position:relative}body .mce-abs-layout-item,.mce-abs-end{position:absolute}.mce-abs-end{width:1px;height:1px}.mce-container-body.mce-abs-layout{overflow:hidden}.mce-tooltip{position:absolute;padding:5px;opacity:.8;filter:alpha(opacity=80);zoom:1}.mce-tooltip-inner{font-size:11px;background-color:#000;color:#fff;max-width:200px;padding:5px 8px 4px 8px;text-align:center;white-space:normal}.mce-tooltip-inner{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.mce-tooltip-inner{-webkit-box-shadow:0 0 5px #000000;-moz-box-shadow:0 0 5px #000000;box-shadow:0 0 5px #000000}.mce-tooltip-arrow{position:absolute;width:0;height:0;line-height:0;border:5px dashed #000}.mce-tooltip-arrow-n{border-bottom-color:#000}.mce-tooltip-arrow-s{border-top-color:#000}.mce-tooltip-arrow-e{border-left-color:#000}.mce-tooltip-arrow-w{border-right-color:#000}.mce-tooltip-nw,.mce-tooltip-sw{margin-left:-14px}.mce-tooltip-n .mce-tooltip-arrow{top:0px;left:50%;margin-left:-5px;border-bottom-style:solid;border-top:none;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-nw .mce-tooltip-arrow{top:0;left:10px;border-bottom-style:solid;border-top:none;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-ne .mce-tooltip-arrow{top:0;right:10px;border-bottom-style:solid;border-top:none;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-s .mce-tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-top-style:solid;border-bottom:none;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-sw .mce-tooltip-arrow{bottom:0;left:10px;border-top-style:solid;border-bottom:none;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-se .mce-tooltip-arrow{bottom:0;right:10px;border-top-style:solid;border-bottom:none;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-e .mce-tooltip-arrow{right:0;top:50%;margin-top:-5px;border-left-style:solid;border-right:none;border-top-color:transparent;border-bottom-color:transparent}.mce-tooltip-w .mce-tooltip-arrow{left:0;top:50%;margin-top:-5px;border-right-style:solid;border-left:none;border-top-color:transparent;border-bottom-color:transparent}.mce-btn{border:1px solid #b1b1b1;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25) rgba(0,0,0,0.25);position:relative;text-shadow:0 1px 1px rgba(255,255,255,0.75);display:inline-block;*display:inline;*zoom:1;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);background-color:#f0f0f0;background-image:-moz-linear-gradient(top, #fff, #d9d9d9);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#fff), to(#d9d9d9));background-image:-webkit-linear-gradient(top, #fff, #d9d9d9);background-image:-o-linear-gradient(top, #fff, #d9d9d9);background-image:linear-gradient(to bottom, #fff, #d9d9d9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffd9d9d9', GradientType=0);zoom:1}.mce-btn:hover,.mce-btn:focus{color:#333;background-color:#e3e3e3;background-image:-moz-linear-gradient(top, #f2f2f2, #ccc);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#f2f2f2), to(#ccc));background-image:-webkit-linear-gradient(top, #f2f2f2, #ccc);background-image:-o-linear-gradient(top, #f2f2f2, #ccc);background-image:linear-gradient(to bottom, #f2f2f2, #ccc);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2f2f2', endColorstr='#ffcccccc', GradientType=0);zoom:1}.mce-btn.mce-disabled button,.mce-btn.mce-disabled:hover button{cursor:default;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;opacity:.4;filter:alpha(opacity=40);zoom:1}.mce-btn.mce-active,.mce-btn.mce-active:hover{background-color:#d6d6d6;background-image:-moz-linear-gradient(top, #e6e6e6, #c0c0c0);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#e6e6e6), to(#c0c0c0));background-image:-webkit-linear-gradient(top, #e6e6e6, #c0c0c0);background-image:-o-linear-gradient(top, #e6e6e6, #c0c0c0);background-image:linear-gradient(to bottom, #e6e6e6, #c0c0c0);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe6e6e6', endColorstr='#ffc0c0c0', GradientType=0);zoom:1;-webkit-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05)}.mce-btn:not(.mce-disabled):active{background-color:#d6d6d6;background-image:-moz-linear-gradient(top, #e6e6e6, #c0c0c0);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#e6e6e6), to(#c0c0c0));background-image:-webkit-linear-gradient(top, #e6e6e6, #c0c0c0);background-image:-o-linear-gradient(top, #e6e6e6, #c0c0c0);background-image:linear-gradient(to bottom, #e6e6e6, #c0c0c0);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe6e6e6', endColorstr='#ffc0c0c0', GradientType=0);zoom:1;-webkit-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05)}.mce-btn button{padding:4px 10px;font-size:14px;line-height:20px;*line-height:16px;cursor:pointer;color:#333;text-align:center;overflow:visible;-webkit-appearance:none}.mce-btn button::-moz-focus-inner{border:0;padding:0}.mce-btn i{text-shadow:1px 1px #fff}.mce-primary{min-width:50px;color:#fff;border:1px solid #b1b1b1;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25) rgba(0,0,0,0.25);background-color:#006dcc;background-image:-moz-linear-gradient(top, #08c, #04c);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#08c), to(#04c));background-image:-webkit-linear-gradient(top, #08c, #04c);background-image:-o-linear-gradient(top, #08c, #04c);background-image:linear-gradient(to bottom, #08c, #04c);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0044cc', GradientType=0);zoom:1}.mce-primary:hover,.mce-primary:focus{background-color:#005fb3;background-image:-moz-linear-gradient(top, #0077b3, #003cb3);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#0077b3), to(#003cb3));background-image:-webkit-linear-gradient(top, #0077b3, #003cb3);background-image:-o-linear-gradient(top, #0077b3, #003cb3);background-image:linear-gradient(to bottom, #0077b3, #003cb3);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0077b3', endColorstr='#ff003cb3', GradientType=0);zoom:1}.mce-primary.mce-disabled button,.mce-primary.mce-disabled:hover button{cursor:default;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;opacity:.4;filter:alpha(opacity=40);zoom:1}.mce-primary.mce-active,.mce-primary.mce-active:hover,.mce-primary:not(.mce-disabled):active{background-color:#005299;background-image:-moz-linear-gradient(top, #069, #039);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#069), to(#039));background-image:-webkit-linear-gradient(top, #069, #039);background-image:-o-linear-gradient(top, #069, #039);background-image:linear-gradient(to bottom, #069, #039);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff006699', endColorstr='#ff003399', GradientType=0);zoom:1;-webkit-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05)}.mce-primary button,.mce-primary button i{color:#fff;text-shadow:1px 1px #333}.mce-btn-large button{padding:9px 14px;font-size:16px;line-height:normal;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.mce-btn-large i{margin-top:2px}.mce-btn-small button{padding:1px 5px;font-size:12px;*padding-bottom:2px}.mce-btn-small i{line-height:20px;vertical-align:top;*line-height:18px}.mce-btn .mce-caret{margin-top:8px;margin-left:0}.mce-btn-small .mce-caret{margin-top:8px;margin-left:0}.mce-caret{display:inline-block;*display:inline;*zoom:1;width:0;height:0;vertical-align:top;border-top:4px solid #333;border-right:4px solid transparent;border-left:4px solid transparent;content:""}.mce-disabled .mce-caret{border-top-color:#aaa}.mce-caret.mce-up{border-bottom:4px solid #333;border-top:0}.mce-rtl .mce-btn button{direction:rtl}.mce-btn-group .mce-btn{border-width:1px 0 1px 0;margin:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.mce-btn-group .mce-first{border-left:1px solid #b1b1b1;border-left:1px solid rgba(0,0,0,0.25);-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px}.mce-btn-group .mce-last{border-right:1px solid #b1b1b1;border-right:1px solid rgba(0,0,0,0.1);-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0}.mce-btn-group .mce-first.mce-last{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.mce-btn-group .mce-btn.mce-flow-layout-item{margin:0}.mce-checkbox{cursor:pointer}i.mce-i-checkbox{margin:0 3px 0 0;border:1px solid #c5c5c5;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);background-color:#f0f0f0;background-image:-moz-linear-gradient(top, #fff, #d9d9d9);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#fff), to(#d9d9d9));background-image:-webkit-linear-gradient(top, #fff, #d9d9d9);background-image:-o-linear-gradient(top, #fff, #d9d9d9);background-image:linear-gradient(to bottom, #fff, #d9d9d9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffd9d9d9', GradientType=0);zoom:1;text-indent:-10em;*font-size:0;*line-height:0;*text-indent:0;overflow:hidden}.mce-checked i.mce-i-checkbox{color:#333;font-size:16px;line-height:16px;text-indent:0}.mce-checkbox:focus i.mce-i-checkbox,.mce-checkbox.mce-focus i.mce-i-checkbox{border:1px solid rgba(82,168,236,0.8);-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.65);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.65);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.65)}.mce-checkbox.mce-disabled .mce-label,.mce-checkbox.mce-disabled i.mce-i-checkbox{color:#acacac}.mce-rtl .mce-checkbox{direction:rtl;text-align:right}.mce-rtl i.mce-i-checkbox{margin:0 0 0 3px}.mce-colorbutton .mce-ico{position:relative}.mce-colorbutton-grid{margin:4px}.mce-colorbutton button{padding-right:4px}.mce-colorbutton .mce-preview{padding-right:3px;display:block;position:absolute;left:50%;top:50%;margin-left:-14px;margin-top:7px;background:gray;width:13px;height:2px;overflow:hidden}.mce-colorbutton.mce-btn-small .mce-preview{margin-left:-16px;padding-right:0;width:16px}.mce-colorbutton .mce-open{padding-left:4px;border-left:1px solid transparent;border-right:1px solid transparent}.mce-colorbutton:hover .mce-open{border-left-color:#bdbdbd;border-right-color:#bdbdbd}.mce-colorbutton.mce-btn-small .mce-open{padding:0 3px 0 3px}.mce-rtl .mce-colorbutton{direction:rtl}.mce-rtl .mce-colorbutton .mce-preview{margin-left:0;padding-right:0;padding-left:4px;margin-right:-14px}.mce-rtl .mce-colorbutton.mce-btn-small .mce-preview{margin-left:0;padding-right:0;margin-right:-17px;padding-left:0}.mce-rtl .mce-colorbutton button{padding-right:10px;padding-left:10px}.mce-rtl .mce-colorbutton .mce-open{padding-left:4px;padding-right:4px}.mce-combobox{display:inline-block;*display:inline;*zoom:1;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);*height:32px}.mce-combobox input{border:1px solid #c5c5c5;border-right-color:#c5c5c5;height:28px}.mce-combobox.mce-disabled input{color:#adadad}.mce-combobox.mce-has-open input{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.mce-combobox .mce-btn{border-left:0;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.mce-combobox button{padding-right:8px;padding-left:8px}.mce-combobox.mce-disabled .mce-btn button{cursor:default;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;opacity:.4;filter:alpha(opacity=40);zoom:1}.mce-path{display:inline-block;*display:inline;*zoom:1;padding:8px;white-space:normal}.mce-path .mce-txt{display:inline-block;padding-right:3px}.mce-path .mce-path-body{display:inline-block}.mce-path-item{display:inline-block;*display:inline;*zoom:1;cursor:pointer;color:#333}.mce-path-item:hover{text-decoration:underline}.mce-path-item:focus{background:#666;color:#fff}.mce-path .mce-divider{display:inline}.mce-disabled .mce-path-item{color:#aaa}.mce-rtl .mce-path{direction:rtl}.mce-fieldset{border:0 solid #9E9E9E;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.mce-fieldset>.mce-container-body{margin-top:-15px}.mce-fieldset-title{margin-left:5px;padding:0 5px 0 5px}.mce-fit-layout{display:inline-block;*display:inline;*zoom:1}.mce-fit-layout-item{position:absolute}.mce-flow-layout-item{display:inline-block;*display:inline;*zoom:1}.mce-flow-layout-item{margin:2px 0 2px 2px}.mce-flow-layout-item.mce-last{margin-right:2px}.mce-flow-layout{white-space:normal}.mce-tinymce-inline .mce-flow-layout{white-space:nowrap}.mce-rtl .mce-flow-layout{text-align:right;direction:rtl}.mce-rtl .mce-flow-layout-item{margin:2px 2px 2px 0}.mce-rtl .mce-flow-layout-item.mce-last{margin-left:2px}.mce-iframe{border:0 solid #9e9e9e;width:100%;height:100%}.mce-label{display:inline-block;*display:inline;*zoom:1;text-shadow:0 1px 1px rgba(255,255,255,0.75);border:0;overflow:hidden}.mce-label.mce-autoscroll{overflow:auto}.mce-label.mce-disabled{color:#aaa}.mce-label.mce-multiline{white-space:pre-wrap}.mce-rtl .mce-label{text-align:right;direction:rtl}.mce-menubar .mce-menubtn{border-color:transparent;background:transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;filter:none}.mce-menubar{border:1px solid #c4c4c4}.mce-menubar .mce-menubtn button span{color:#333}.mce-menubar .mce-caret{border-top-color:#333}.mce-menubar .mce-menubtn:hover,.mce-menubar .mce-menubtn.mce-active,.mce-menubar .mce-menubtn:focus{border-color:transparent;background:#e6e6e6;filter:none;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.mce-menubtn.mce-disabled span{color:#aaa}.mce-menubtn span{color:#333;margin-right:2px;line-height:20px;*line-height:16px}.mce-menubtn.mce-btn-small span{font-size:12px}.mce-menubtn.mce-fixed-width span{display:inline-block;overflow-x:hidden;text-overflow:ellipsis;width:90px}.mce-menubtn.mce-fixed-width.mce-btn-small span{width:70px}.mce-menubtn .mce-caret{*margin-top:6px}.mce-rtl .mce-menubtn button{direction:rtl;text-align:right}.mce-listbox button{text-align:left;padding-right:20px;position:relative}.mce-listbox .mce-caret{position:absolute;margin-top:-2px;right:8px;top:50%}.mce-rtl .mce-listbox .mce-caret{right:auto;left:8px}.mce-rtl .mce-listbox button{padding-right:10px;padding-left:20px}.mce-menu-item{display:block;padding:6px 15px 6px 12px;clear:both;font-weight:normal;line-height:20px;color:#333;white-space:nowrap;cursor:pointer;line-height:normal;border-left:4px solid transparent;margin-bottom:1px}.mce-menu-item .mce-ico,.mce-menu-item .mce-text{color:#333}.mce-menu-item.mce-disabled .mce-text,.mce-menu-item.mce-disabled .mce-ico{color:#adadad}.mce-menu-item:hover .mce-text,.mce-menu-item.mce-selected .mce-text{color:#fff}.mce-menu-item:hover .mce-ico,.mce-menu-item.mce-selected .mce-ico,.mce-menu-item:focus .mce-ico{color:#fff}.mce-menu-item.mce-disabled:hover{background:#ccc}.mce-menu-shortcut{display:inline-block;color:#adadad}.mce-menu-shortcut{display:inline-block;*display:inline;*zoom:1;padding:0 15px 0 20px}.mce-menu-item:hover .mce-menu-shortcut,.mce-menu-item.mce-selected .mce-menu-shortcut,.mce-menu-item:focus .mce-menu-shortcut{color:#fff}.mce-menu-item .mce-caret{margin-top:4px;*margin-top:3px;margin-right:6px;border-top:4px solid transparent;border-bottom:4px solid transparent;border-left:4px solid #333}.mce-menu-item.mce-selected .mce-caret,.mce-menu-item:focus .mce-caret,.mce-menu-item:hover .mce-caret{border-left-color:#fff}.mce-menu-align .mce-menu-shortcut{*margin-top:-2px}.mce-menu-align .mce-menu-shortcut,.mce-menu-align .mce-caret{position:absolute;right:0}.mce-menu-item.mce-active i{visibility:visible}.mce-menu-item-normal.mce-active{background-color:#c8def4}.mce-menu-item-preview.mce-active{border-left:5px solid #aaa}.mce-menu-item-normal.mce-active .mce-text{color:#333}.mce-menu-item-normal.mce-active:hover .mce-text,.mce-menu-item-normal.mce-active:hover .mce-ico{color:#fff}.mce-menu-item:hover,.mce-menu-item.mce-selected,.mce-menu-item:focus{text-decoration:none;color:#fff;background-color:#0081c2;background-image:-moz-linear-gradient(top, #08c, #0077b3);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#08c), to(#0077b3));background-image:-webkit-linear-gradient(top, #08c, #0077b3);background-image:-o-linear-gradient(top, #08c, #0077b3);background-image:linear-gradient(to bottom, #08c, #0077b3);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0);zoom:1}div.mce-menu .mce-menu-item-sep,.mce-menu-item-sep:hover{border:0;padding:0;height:1px;margin:9px 1px;overflow:hidden;background:#cbcbcb;border-bottom:1px solid #fff;cursor:default;filter:none}.mce-menu.mce-rtl{direction:rtl}.mce-rtl .mce-menu-item{text-align:right;direction:rtl;padding:6px 12px 6px 15px}.mce-menu-align.mce-rtl .mce-menu-shortcut,.mce-menu-align.mce-rtl .mce-caret{right:auto;left:0}.mce-rtl .mce-menu-item .mce-caret{margin-left:6px;margin-right:0;border-right:4px solid #333;border-left:0}.mce-rtl .mce-menu-item.mce-selected .mce-caret,.mce-rtl .mce-menu-item:focus .mce-caret,.mce-rtl .mce-menu-item:hover .mce-caret{border-left-color:transparent;border-right-color:#fff}.mce-menu{position:absolute;left:0;top:0;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background:transparent;z-index:1000;padding:5px 0 5px 0;margin:2px 0 0;min-width:160px;background:#fff;border:1px solid #989898;border:1px solid rgba(0,0,0,0.2);z-index:1002;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);-moz-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);max-height:400px;overflow:auto;overflow-x:hidden}.mce-menu i{display:none}.mce-menu-has-icons i{display:inline-block;*display:inline}.mce-menu-sub-tr-tl{margin:-6px 0 0 -1px}.mce-menu-sub-br-bl{margin:6px 0 0 -1px}.mce-menu-sub-tl-tr{margin:-6px 0 0 1px}.mce-menu-sub-bl-br{margin:6px 0 0 1px}.mce-container-body .mce-resizehandle{position:absolute;right:0;bottom:0;width:16px;height:16px;visibility:visible;cursor:s-resize;margin:0}.mce-container-body .mce-resizehandle-both{cursor:se-resize}i.mce-i-resize{color:#333}.mce-spacer{visibility:hidden}.mce-splitbtn .mce-open{border-left:1px solid transparent;border-right:1px solid transparent}.mce-splitbtn:hover .mce-open{border-left-color:#bdbdbd;border-right-color:#bdbdbd}.mce-splitbtn button{padding-right:4px}.mce-splitbtn .mce-open{padding-left:4px}.mce-splitbtn .mce-open.mce-active{-webkit-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05)}.mce-splitbtn.mce-btn-small .mce-open{padding:0 3px 0 3px}.mce-rtl .mce-splitbtn{direction:rtl;text-align:right}.mce-rtl .mce-splitbtn button{padding-right:10px;padding-left:10px}.mce-rtl .mce-splitbtn .mce-open{padding-left:4px;padding-right:4px}.mce-stack-layout-item{display:block}.mce-tabs{display:block;border-bottom:1px solid #c5c5c5}.mce-tab{display:inline-block;*display:inline;*zoom:1;border:1px solid #c5c5c5;border-width:0 1px 0 0;background:#e3e3e3;padding:8px;text-shadow:0 1px 1px rgba(255,255,255,0.75);height:13px;cursor:pointer}.mce-tab:hover{background:#fdfdfd}.mce-tab.mce-active{background:#fdfdfd;border-bottom-color:transparent;margin-bottom:-1px;height:14px}.mce-rtl .mce-tabs{text-align:right;direction:rtl}.mce-rtl .mce-tab{border-width:0 0 0 1px}.mce-textbox{background:#fff;border:1px solid #c5c5c5;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);display:inline-block;-webkit-transition:border linear .2s, box-shadow linear .2s;transition:border linear .2s, box-shadow linear .2s;height:28px;resize:none;padding:0 4px 0 4px;white-space:pre-wrap;*white-space:pre;color:#333}.mce-textbox:focus,.mce-textbox.mce-focus{border-color:rgba(82,168,236,0.8);-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.65);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.65);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.65)}.mce-placeholder .mce-textbox{color:#aaa}.mce-textbox.mce-multiline{padding:4px}.mce-textbox.mce-disabled{color:#adadad}.mce-rtl .mce-textbox{text-align:right;direction:rtl}.mce-throbber{position:absolute;top:0;left:0;width:100%;height:100%;opacity:.6;filter:alpha(opacity=60);zoom:1;background:#fff url('img/loader.gif') no-repeat center center}@font-face{font-family:'tinymce';src:url('fonts/tinymce.eot');src:url('fonts/tinymce.eot?#iefix') format('embedded-opentype'),url('fonts/tinymce.woff') format('woff'),url('fonts/tinymce.ttf') format('truetype'),url('fonts/tinymce.svg#tinymce') format('svg');font-weight:normal;font-style:normal}@font-face{font-family:'tinymce-small';src:url('fonts/tinymce-small.eot');src:url('fonts/tinymce-small.eot?#iefix') format('embedded-opentype'),url('fonts/tinymce-small.woff') format('woff'),url('fonts/tinymce-small.ttf') format('truetype'),url('fonts/tinymce-small.svg#tinymce') format('svg');font-weight:normal;font-style:normal}.mce-ico{font-family:'tinymce';font-style:normal;font-weight:normal;font-size:16px;line-height:16px;vertical-align:text-top;-webkit-font-smoothing:antialiased;display:inline-block;background:transparent center center;width:16px;height:16px;color:#333;-ie7-icon:' '}.mce-btn-small .mce-ico{font-family:'tinymce-small'}.mce-ico,i.mce-i-checkbox{zoom:expression(this.runtimeStyle['zoom'] = '1', this.innerHTML = this.currentStyle['-ie7-icon'].substr(1, 1) + ' ')}.mce-i-save{-ie7-icon:"\e000"}.mce-i-newdocument{-ie7-icon:"\e001"}.mce-i-fullpage{-ie7-icon:"\e002"}.mce-i-alignleft{-ie7-icon:"\e003"}.mce-i-aligncenter{-ie7-icon:"\e004"}.mce-i-alignright{-ie7-icon:"\e005"}.mce-i-alignjustify{-ie7-icon:"\e006"}.mce-i-cut{-ie7-icon:"\e007"}.mce-i-paste{-ie7-icon:"\e008"}.mce-i-searchreplace{-ie7-icon:"\e009"}.mce-i-bullist{-ie7-icon:"\e00a"}.mce-i-numlist{-ie7-icon:"\e00b"}.mce-i-indent{-ie7-icon:"\e00c"}.mce-i-outdent{-ie7-icon:"\e00d"}.mce-i-blockquote{-ie7-icon:"\e00e"}.mce-i-undo{-ie7-icon:"\e00f"}.mce-i-redo{-ie7-icon:"\e010"}.mce-i-link{-ie7-icon:"\e011"}.mce-i-unlink{-ie7-icon:"\e012"}.mce-i-anchor{-ie7-icon:"\e013"}.mce-i-image{-ie7-icon:"\e014"}.mce-i-media{-ie7-icon:"\e015"}.mce-i-help{-ie7-icon:"\e016"}.mce-i-code{-ie7-icon:"\e017"}.mce-i-inserttime{-ie7-icon:"\e018"}.mce-i-preview{-ie7-icon:"\e019"}.mce-i-forecolor{-ie7-icon:"\e01a"}.mce-i-backcolor{-ie7-icon:"\e01a"}.mce-i-table{-ie7-icon:"\e01b"}.mce-i-hr{-ie7-icon:"\e01c"}.mce-i-removeformat{-ie7-icon:"\e01d"}.mce-i-subscript{-ie7-icon:"\e01e"}.mce-i-superscript{-ie7-icon:"\e01f"}.mce-i-charmap{-ie7-icon:"\e020"}.mce-i-emoticons{-ie7-icon:"\e021"}.mce-i-print{-ie7-icon:"\e022"}.mce-i-fullscreen{-ie7-icon:"\e023"}.mce-i-spellchecker{-ie7-icon:"\e024"}.mce-i-nonbreaking{-ie7-icon:"\e025"}.mce-i-template{-ie7-icon:"\e026"}.mce-i-pagebreak{-ie7-icon:"\e027"}.mce-i-restoredraft{-ie7-icon:"\e028"}.mce-i-untitled{-ie7-icon:"\e029"}.mce-i-bold{-ie7-icon:"\e02a"}.mce-i-italic{-ie7-icon:"\e02b"}.mce-i-underline{-ie7-icon:"\e02c"}.mce-i-strikethrough{-ie7-icon:"\e02d"}.mce-i-visualchars{-ie7-icon:"\e02e"}.mce-i-ltr{-ie7-icon:"\e02f"}.mce-i-rtl{-ie7-icon:"\e030"}.mce-i-copy{-ie7-icon:"\e031"}.mce-i-resize{-ie7-icon:"\e032"}.mce-i-browse{-ie7-icon:"\e034"}.mce-i-pastetext{-ie7-icon:"\e035"}.mce-i-checkbox,.mce-i-selected{-ie7-icon:"\e033"}.mce-i-selected{visibility:hidden}.mce-i-backcolor{background:#BBB} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/skin.min.css b/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/skin.min.css deleted file mode 100755 index bcf7b87ddf..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/lightgray/skin.min.css +++ /dev/null @@ -1 +0,0 @@ -.mce-container,.mce-container *,.mce-widget,.mce-widget *{margin:0;padding:0;border:0;outline:0;vertical-align:top;background:transparent;text-decoration:none;color:#333;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;text-shadow:none;float:none;position:static;width:auto;height:auto;white-space:nowrap;cursor:inherit;-webkit-tap-highlight-color:transparent;line-height:normal;font-weight:normal;text-align:left;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box;direction:ltr}.mce-widget button{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.mce-container *[unselectable]{-moz-user-select:none;-webkit-user-select:none;-o-user-select:none;user-select:none}.mce-container ::-webkit-scrollbar{width:8px;height:8px;-webkit-border-radius:4px}.mce-container ::-webkit-scrollbar-track,.mce-container ::-webkit-scrollbar-track-piece{background-color:transparent}.mce-container ::-webkit-scrollbar-thumb{background-color:rgba(53,57,71,0.3);width:6px;height:6px;-webkit-border-radius:4px}.mce-fade{opacity:0;-webkit-transition:opacity .15s linear;transition:opacity .15s linear}.mce-fade.mce-in{opacity:1}.mce-tinymce{visibility:visible !important;position:relative}.mce-fullscreen{border:0;padding:0;margin:0;overflow:hidden;height:100%;z-index:100}div.mce-fullscreen{position:fixed;top:0;left:0;width:100%;height:auto}.mce-tinymce{display:block;-webkit-border-radius:2px;-moz-border-radius:2px;border-radius:2px}.mce-wordcount{position:absolute;top:0;right:0;padding:8px}div.mce-edit-area{background:#FFF;filter:none}.mce-statusbar{position:relative}.mce-statusbar .mce-container-body{position:relative}.mce-fullscreen .mce-resizehandle{display:none}.mce-charmap{border-collapse:collapse}.mce-charmap td{cursor:default;border:1px solid #9e9e9e;width:20px;height:20px;line-height:20px;text-align:center;vertical-align:middle;padding:2px}.mce-charmap td div{text-align:center}.mce-charmap td:hover{background:#d9d9d9}.mce-grid td div{border:1px solid #d6d6d6;width:12px;height:12px;margin:2px;cursor:pointer}.mce-grid{border-spacing:2px;border-collapse:separate}.mce-grid a{display:block;border:1px solid transparent}.mce-grid a:hover{border-color:#a1a1a1}.mce-grid-border{margin:0 4px 0 4px}.mce-grid-border a{border-color:#d6d6d6;width:13px;height:13px}.mce-grid-border a:hover,.mce-grid-border a.mce-active{border-color:#a1a1a1;background:#c8def4}.mce-text-center{text-align:center}div.mce-tinymce-inline{width:100%;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.mce-toolbar-grp{padding-bottom:2px}.mce-toolbar-grp .mce-flow-layout-item{margin-bottom:0}.mce-rtl .mce-wordcount{left:0;right:auto}.mce-container,.mce-container-body{display:block}.mce-autoscroll{overflow:hidden}.mce-scrollbar{position:absolute;width:7px;height:100%;top:2px;right:2px;opacity:.4;filter:alpha(opacity=40);zoom:1}.mce-scrollbar-h{top:auto;right:auto;left:2px;bottom:2px;width:100%;height:7px}.mce-scrollbar-thumb{position:absolute;background-color:#000;border:1px solid #888;border-color:rgba(85,85,85,0.6);width:5px;height:100%;-webkit-border-radius:7px;-moz-border-radius:7px;border-radius:7px}.mce-scrollbar-h .mce-scrollbar-thumb{width:100%;height:5px}.mce-scrollbar:hover,.mce-scrollbar.mce-active{background-color:#AAA;opacity:.6;filter:alpha(opacity=60);zoom:1;-webkit-border-radius:7px;-moz-border-radius:7px;border-radius:7px}.mce-scroll{position:relative}.mce-panel{border:0 solid #9e9e9e;background-color:#f0f0f0;background-image:-moz-linear-gradient(top, #fdfdfd, #ddd);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#fdfdfd), to(#ddd));background-image:-webkit-linear-gradient(top, #fdfdfd, #ddd);background-image:-o-linear-gradient(top, #fdfdfd, #ddd);background-image:linear-gradient(to bottom, #fdfdfd, #ddd);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffdfdfd', endColorstr='#ffdddddd', GradientType=0);zoom:1}.mce-floatpanel{position:absolute;-webkit-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);-moz-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);box-shadow:0 5px 10px rgba(0, 0, 0, 0.2)}.mce-floatpanel.mce-fixed{position:fixed}.mce-floatpanel .mce-arrow,.mce-floatpanel .mce-arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.mce-floatpanel .mce-arrow{border-width:11px}.mce-floatpanel .mce-arrow:after{border-width:10px;content:""}.mce-floatpanel.mce-popover{filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background:transparent;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);-moz-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);top:0;left:0;background:#fff;border:1px solid #9e9e9e;border:1px solid rgba(0,0,0,0.25)}.mce-floatpanel.mce-popover.mce-bottom{margin-top:10px;*margin-top:0}.mce-floatpanel.mce-popover.mce-bottom>.mce-arrow{left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#9e9e9e;border-bottom-color:rgba(0,0,0,0.25);top:-11px}.mce-floatpanel.mce-popover.mce-bottom>.mce-arrow:after{top:1px;margin-left:-10px;border-top-width:0;border-bottom-color:#fff}.mce-floatpanel.mce-popover.mce-bottom.mce-start{margin-left:-22px}.mce-floatpanel.mce-popover.mce-bottom.mce-start>.mce-arrow{left:20px}.mce-floatpanel.mce-popover.mce-bottom.mce-end{margin-left:22px}.mce-floatpanel.mce-popover.mce-bottom.mce-end>.mce-arrow{right:10px;left:auto}.mce-fullscreen{border:0;padding:0;margin:0;overflow:hidden;background:#fff;height:100%}div.mce-fullscreen{position:fixed;top:0;left:0}#mce-modal-block{opacity:0;filter:alpha(opacity=0);zoom:1;position:fixed;left:0;top:0;width:100%;height:100%;background:#000}#mce-modal-block.mce-in{opacity:.3;filter:alpha(opacity=30);zoom:1}.mce-window-move{cursor:move}.mce-window{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-moz-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background:transparent;background:#fff;position:fixed;top:0;left:0;opacity:0;-webkit-transition:opacity 150ms ease-in;transition:opacity 150ms ease-in}.mce-window.mce-in{opacity:1}.mce-window-head{padding:9px 15px;border-bottom:1px solid #c5c5c5;position:relative}.mce-window-head .mce-close{position:absolute;right:15px;top:9px;font-size:20px;font-weight:bold;line-height:20px;color:#858585;cursor:pointer;height:20px;overflow:hidden}.mce-close:hover{color:#adadad}.mce-window-head .mce-title{line-height:20px;font-size:20px;font-weight:bold;text-rendering:optimizelegibility;padding-right:10px}.mce-window .mce-container-body{display:block}.mce-foot{display:block;background-color:#fff;border-top:1px solid #c5c5c5;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px}.mce-window-head .mce-dragh{position:absolute;top:0;left:0;cursor:move;width:90%;height:100%}.mce-window iframe{width:100%;height:100%}.mce-window.mce-fullscreen,.mce-window.mce-fullscreen .mce-foot{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.mce-rtl .mce-window-head .mce-close{position:absolute;right:auto;left:15px}.mce-rtl .mce-window-head .mce-dragh{left:auto;right:0}.mce-rtl .mce-window-head .mce-title{direction:rtl;text-align:right}.mce-abs-layout{position:relative}body .mce-abs-layout-item,.mce-abs-end{position:absolute}.mce-abs-end{width:1px;height:1px}.mce-container-body.mce-abs-layout{overflow:hidden}.mce-tooltip{position:absolute;padding:5px;opacity:.8;filter:alpha(opacity=80);zoom:1}.mce-tooltip-inner{font-size:11px;background-color:#000;color:#fff;max-width:200px;padding:5px 8px 4px 8px;text-align:center;white-space:normal}.mce-tooltip-inner{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.mce-tooltip-inner{-webkit-box-shadow:0 0 5px #000000;-moz-box-shadow:0 0 5px #000000;box-shadow:0 0 5px #000000}.mce-tooltip-arrow{position:absolute;width:0;height:0;line-height:0;border:5px dashed #000}.mce-tooltip-arrow-n{border-bottom-color:#000}.mce-tooltip-arrow-s{border-top-color:#000}.mce-tooltip-arrow-e{border-left-color:#000}.mce-tooltip-arrow-w{border-right-color:#000}.mce-tooltip-nw,.mce-tooltip-sw{margin-left:-14px}.mce-tooltip-n .mce-tooltip-arrow{top:0px;left:50%;margin-left:-5px;border-bottom-style:solid;border-top:none;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-nw .mce-tooltip-arrow{top:0;left:10px;border-bottom-style:solid;border-top:none;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-ne .mce-tooltip-arrow{top:0;right:10px;border-bottom-style:solid;border-top:none;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-s .mce-tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-top-style:solid;border-bottom:none;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-sw .mce-tooltip-arrow{bottom:0;left:10px;border-top-style:solid;border-bottom:none;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-se .mce-tooltip-arrow{bottom:0;right:10px;border-top-style:solid;border-bottom:none;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-e .mce-tooltip-arrow{right:0;top:50%;margin-top:-5px;border-left-style:solid;border-right:none;border-top-color:transparent;border-bottom-color:transparent}.mce-tooltip-w .mce-tooltip-arrow{left:0;top:50%;margin-top:-5px;border-right-style:solid;border-left:none;border-top-color:transparent;border-bottom-color:transparent}.mce-btn{border:1px solid #b1b1b1;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25) rgba(0,0,0,0.25);position:relative;text-shadow:0 1px 1px rgba(255,255,255,0.75);display:inline-block;*display:inline;*zoom:1;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);background-color:#f0f0f0;background-image:-moz-linear-gradient(top, #fff, #d9d9d9);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#fff), to(#d9d9d9));background-image:-webkit-linear-gradient(top, #fff, #d9d9d9);background-image:-o-linear-gradient(top, #fff, #d9d9d9);background-image:linear-gradient(to bottom, #fff, #d9d9d9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffd9d9d9', GradientType=0);zoom:1}.mce-btn:hover,.mce-btn:focus{color:#333;background-color:#e3e3e3;background-image:-moz-linear-gradient(top, #f2f2f2, #ccc);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#f2f2f2), to(#ccc));background-image:-webkit-linear-gradient(top, #f2f2f2, #ccc);background-image:-o-linear-gradient(top, #f2f2f2, #ccc);background-image:linear-gradient(to bottom, #f2f2f2, #ccc);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2f2f2', endColorstr='#ffcccccc', GradientType=0);zoom:1}.mce-btn.mce-disabled button,.mce-btn.mce-disabled:hover button{cursor:default;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;opacity:.4;filter:alpha(opacity=40);zoom:1}.mce-btn.mce-active,.mce-btn.mce-active:hover{background-color:#d6d6d6;background-image:-moz-linear-gradient(top, #e6e6e6, #c0c0c0);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#e6e6e6), to(#c0c0c0));background-image:-webkit-linear-gradient(top, #e6e6e6, #c0c0c0);background-image:-o-linear-gradient(top, #e6e6e6, #c0c0c0);background-image:linear-gradient(to bottom, #e6e6e6, #c0c0c0);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe6e6e6', endColorstr='#ffc0c0c0', GradientType=0);zoom:1;-webkit-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05)}.mce-btn:not(.mce-disabled):active{background-color:#d6d6d6;background-image:-moz-linear-gradient(top, #e6e6e6, #c0c0c0);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#e6e6e6), to(#c0c0c0));background-image:-webkit-linear-gradient(top, #e6e6e6, #c0c0c0);background-image:-o-linear-gradient(top, #e6e6e6, #c0c0c0);background-image:linear-gradient(to bottom, #e6e6e6, #c0c0c0);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe6e6e6', endColorstr='#ffc0c0c0', GradientType=0);zoom:1;-webkit-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05)}.mce-btn button{padding:4px 10px;font-size:14px;line-height:20px;*line-height:16px;cursor:pointer;color:#333;text-align:center;overflow:visible;-webkit-appearance:none}.mce-btn button::-moz-focus-inner{border:0;padding:0}.mce-btn i{text-shadow:1px 1px #fff}.mce-primary{min-width:50px;color:#fff;border:1px solid #b1b1b1;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25) rgba(0,0,0,0.25);background-color:#006dcc;background-image:-moz-linear-gradient(top, #08c, #04c);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#08c), to(#04c));background-image:-webkit-linear-gradient(top, #08c, #04c);background-image:-o-linear-gradient(top, #08c, #04c);background-image:linear-gradient(to bottom, #08c, #04c);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0044cc', GradientType=0);zoom:1}.mce-primary:hover,.mce-primary:focus{background-color:#005fb3;background-image:-moz-linear-gradient(top, #0077b3, #003cb3);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#0077b3), to(#003cb3));background-image:-webkit-linear-gradient(top, #0077b3, #003cb3);background-image:-o-linear-gradient(top, #0077b3, #003cb3);background-image:linear-gradient(to bottom, #0077b3, #003cb3);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0077b3', endColorstr='#ff003cb3', GradientType=0);zoom:1}.mce-primary.mce-disabled button,.mce-primary.mce-disabled:hover button{cursor:default;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;opacity:.4;filter:alpha(opacity=40);zoom:1}.mce-primary.mce-active,.mce-primary.mce-active:hover,.mce-primary:not(.mce-disabled):active{background-color:#005299;background-image:-moz-linear-gradient(top, #069, #039);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#069), to(#039));background-image:-webkit-linear-gradient(top, #069, #039);background-image:-o-linear-gradient(top, #069, #039);background-image:linear-gradient(to bottom, #069, #039);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff006699', endColorstr='#ff003399', GradientType=0);zoom:1;-webkit-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05)}.mce-primary button,.mce-primary button i{color:#fff;text-shadow:1px 1px #333}.mce-btn-large button{padding:9px 14px;font-size:16px;line-height:normal;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.mce-btn-large i{margin-top:2px}.mce-btn-small button{padding:1px 5px;font-size:12px;*padding-bottom:2px}.mce-btn-small i{line-height:20px;vertical-align:top;*line-height:18px}.mce-btn .mce-caret{margin-top:8px;margin-left:0}.mce-btn-small .mce-caret{margin-top:8px;margin-left:0}.mce-caret{display:inline-block;*display:inline;*zoom:1;width:0;height:0;vertical-align:top;border-top:4px solid #333;border-right:4px solid transparent;border-left:4px solid transparent;content:""}.mce-disabled .mce-caret{border-top-color:#aaa}.mce-caret.mce-up{border-bottom:4px solid #333;border-top:0}.mce-rtl .mce-btn button{direction:rtl}.mce-btn-group .mce-btn{border-width:1px 0 1px 0;margin:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.mce-btn-group .mce-first{border-left:1px solid #b1b1b1;border-left:1px solid rgba(0,0,0,0.25);-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px}.mce-btn-group .mce-last{border-right:1px solid #b1b1b1;border-right:1px solid rgba(0,0,0,0.1);-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0}.mce-btn-group .mce-first.mce-last{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.mce-btn-group .mce-btn.mce-flow-layout-item{margin:0}.mce-checkbox{cursor:pointer}i.mce-i-checkbox{margin:0 3px 0 0;border:1px solid #c5c5c5;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);background-color:#f0f0f0;background-image:-moz-linear-gradient(top, #fff, #d9d9d9);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#fff), to(#d9d9d9));background-image:-webkit-linear-gradient(top, #fff, #d9d9d9);background-image:-o-linear-gradient(top, #fff, #d9d9d9);background-image:linear-gradient(to bottom, #fff, #d9d9d9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffd9d9d9', GradientType=0);zoom:1;text-indent:-10em;*font-size:0;*line-height:0;*text-indent:0;overflow:hidden}.mce-checked i.mce-i-checkbox{color:#333;font-size:16px;line-height:16px;text-indent:0}.mce-checkbox:focus i.mce-i-checkbox,.mce-checkbox.mce-focus i.mce-i-checkbox{border:1px solid rgba(82,168,236,0.8);-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.65);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.65);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.65)}.mce-checkbox.mce-disabled .mce-label,.mce-checkbox.mce-disabled i.mce-i-checkbox{color:#acacac}.mce-rtl .mce-checkbox{direction:rtl;text-align:right}.mce-rtl i.mce-i-checkbox{margin:0 0 0 3px}.mce-colorbutton .mce-ico{position:relative}.mce-colorbutton-grid{margin:4px}.mce-colorbutton button{padding-right:4px}.mce-colorbutton .mce-preview{padding-right:3px;display:block;position:absolute;left:50%;top:50%;margin-left:-14px;margin-top:7px;background:gray;width:13px;height:2px;overflow:hidden}.mce-colorbutton.mce-btn-small .mce-preview{margin-left:-16px;padding-right:0;width:16px}.mce-colorbutton .mce-open{padding-left:4px;border-left:1px solid transparent;border-right:1px solid transparent}.mce-colorbutton:hover .mce-open{border-left-color:#bdbdbd;border-right-color:#bdbdbd}.mce-colorbutton.mce-btn-small .mce-open{padding:0 3px 0 3px}.mce-rtl .mce-colorbutton{direction:rtl}.mce-rtl .mce-colorbutton .mce-preview{margin-left:0;padding-right:0;padding-left:4px;margin-right:-14px}.mce-rtl .mce-colorbutton.mce-btn-small .mce-preview{margin-left:0;padding-right:0;margin-right:-17px;padding-left:0}.mce-rtl .mce-colorbutton button{padding-right:10px;padding-left:10px}.mce-rtl .mce-colorbutton .mce-open{padding-left:4px;padding-right:4px}.mce-combobox{display:inline-block;*display:inline;*zoom:1;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);*height:32px}.mce-combobox input{border:1px solid #c5c5c5;border-right-color:#c5c5c5;height:28px}.mce-combobox.mce-disabled input{color:#adadad}.mce-combobox.mce-has-open input{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.mce-combobox .mce-btn{border-left:0;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.mce-combobox button{padding-right:8px;padding-left:8px}.mce-combobox.mce-disabled .mce-btn button{cursor:default;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;opacity:.4;filter:alpha(opacity=40);zoom:1}.mce-path{display:inline-block;*display:inline;*zoom:1;padding:8px;white-space:normal}.mce-path .mce-txt{display:inline-block;padding-right:3px}.mce-path .mce-path-body{display:inline-block}.mce-path-item{display:inline-block;*display:inline;*zoom:1;cursor:pointer;color:#333}.mce-path-item:hover{text-decoration:underline}.mce-path-item:focus{background:#666;color:#fff}.mce-path .mce-divider{display:inline}.mce-disabled .mce-path-item{color:#aaa}.mce-rtl .mce-path{direction:rtl}.mce-fieldset{border:0 solid #9E9E9E;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.mce-fieldset>.mce-container-body{margin-top:-15px}.mce-fieldset-title{margin-left:5px;padding:0 5px 0 5px}.mce-fit-layout{display:inline-block;*display:inline;*zoom:1}.mce-fit-layout-item{position:absolute}.mce-flow-layout-item{display:inline-block;*display:inline;*zoom:1}.mce-flow-layout-item{margin:2px 0 2px 2px}.mce-flow-layout-item.mce-last{margin-right:2px}.mce-flow-layout{white-space:normal}.mce-tinymce-inline .mce-flow-layout{white-space:nowrap}.mce-rtl .mce-flow-layout{text-align:right;direction:rtl}.mce-rtl .mce-flow-layout-item{margin:2px 2px 2px 0}.mce-rtl .mce-flow-layout-item.mce-last{margin-left:2px}.mce-iframe{border:0 solid #9e9e9e;width:100%;height:100%}.mce-label{display:inline-block;*display:inline;*zoom:1;text-shadow:0 1px 1px rgba(255,255,255,0.75);border:0;overflow:hidden}.mce-label.mce-autoscroll{overflow:auto}.mce-label.mce-disabled{color:#aaa}.mce-label.mce-multiline{white-space:pre-wrap}.mce-rtl .mce-label{text-align:right;direction:rtl}.mce-menubar .mce-menubtn{border-color:transparent;background:transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;filter:none}.mce-menubar{border:1px solid #c4c4c4}.mce-menubar .mce-menubtn button span{color:#333}.mce-menubar .mce-caret{border-top-color:#333}.mce-menubar .mce-menubtn:hover,.mce-menubar .mce-menubtn.mce-active,.mce-menubar .mce-menubtn:focus{border-color:transparent;background:#e6e6e6;filter:none;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.mce-menubtn.mce-disabled span{color:#aaa}.mce-menubtn span{color:#333;margin-right:2px;line-height:20px;*line-height:16px}.mce-menubtn.mce-btn-small span{font-size:12px}.mce-menubtn.mce-fixed-width span{display:inline-block;overflow-x:hidden;text-overflow:ellipsis;width:90px}.mce-menubtn.mce-fixed-width.mce-btn-small span{width:70px}.mce-menubtn .mce-caret{*margin-top:6px}.mce-rtl .mce-menubtn button{direction:rtl;text-align:right}.mce-listbox button{text-align:left;padding-right:20px;position:relative}.mce-listbox .mce-caret{position:absolute;margin-top:-2px;right:8px;top:50%}.mce-rtl .mce-listbox .mce-caret{right:auto;left:8px}.mce-rtl .mce-listbox button{padding-right:10px;padding-left:20px}.mce-menu-item{display:block;padding:6px 15px 6px 12px;clear:both;font-weight:normal;line-height:20px;color:#333;white-space:nowrap;cursor:pointer;line-height:normal;border-left:4px solid transparent;margin-bottom:1px}.mce-menu-item .mce-ico,.mce-menu-item .mce-text{color:#333}.mce-menu-item.mce-disabled .mce-text,.mce-menu-item.mce-disabled .mce-ico{color:#adadad}.mce-menu-item:hover .mce-text,.mce-menu-item.mce-selected .mce-text{color:#fff}.mce-menu-item:hover .mce-ico,.mce-menu-item.mce-selected .mce-ico,.mce-menu-item:focus .mce-ico{color:#fff}.mce-menu-item.mce-disabled:hover{background:#ccc}.mce-menu-shortcut{display:inline-block;color:#adadad}.mce-menu-shortcut{display:inline-block;*display:inline;*zoom:1;padding:0 15px 0 20px}.mce-menu-item:hover .mce-menu-shortcut,.mce-menu-item.mce-selected .mce-menu-shortcut,.mce-menu-item:focus .mce-menu-shortcut{color:#fff}.mce-menu-item .mce-caret{margin-top:4px;*margin-top:3px;margin-right:6px;border-top:4px solid transparent;border-bottom:4px solid transparent;border-left:4px solid #333}.mce-menu-item.mce-selected .mce-caret,.mce-menu-item:focus .mce-caret,.mce-menu-item:hover .mce-caret{border-left-color:#fff}.mce-menu-align .mce-menu-shortcut{*margin-top:-2px}.mce-menu-align .mce-menu-shortcut,.mce-menu-align .mce-caret{position:absolute;right:0}.mce-menu-item.mce-active i{visibility:visible}.mce-menu-item-normal.mce-active{background-color:#c8def4}.mce-menu-item-preview.mce-active{border-left:5px solid #aaa}.mce-menu-item-normal.mce-active .mce-text{color:#333}.mce-menu-item-normal.mce-active:hover .mce-text,.mce-menu-item-normal.mce-active:hover .mce-ico{color:#fff}.mce-menu-item:hover,.mce-menu-item.mce-selected,.mce-menu-item:focus{text-decoration:none;color:#fff;background-color:#0081c2;background-image:-moz-linear-gradient(top, #08c, #0077b3);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#08c), to(#0077b3));background-image:-webkit-linear-gradient(top, #08c, #0077b3);background-image:-o-linear-gradient(top, #08c, #0077b3);background-image:linear-gradient(to bottom, #08c, #0077b3);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0);zoom:1}div.mce-menu .mce-menu-item-sep,.mce-menu-item-sep:hover{border:0;padding:0;height:1px;margin:9px 1px;overflow:hidden;background:#cbcbcb;border-bottom:1px solid #fff;cursor:default;filter:none}.mce-menu.mce-rtl{direction:rtl}.mce-rtl .mce-menu-item{text-align:right;direction:rtl;padding:6px 12px 6px 15px}.mce-menu-align.mce-rtl .mce-menu-shortcut,.mce-menu-align.mce-rtl .mce-caret{right:auto;left:0}.mce-rtl .mce-menu-item .mce-caret{margin-left:6px;margin-right:0;border-right:4px solid #333;border-left:0}.mce-rtl .mce-menu-item.mce-selected .mce-caret,.mce-rtl .mce-menu-item:focus .mce-caret,.mce-rtl .mce-menu-item:hover .mce-caret{border-left-color:transparent;border-right-color:#fff}.mce-menu{position:absolute;left:0;top:0;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background:transparent;z-index:1000;padding:5px 0 5px 0;margin:2px 0 0;min-width:160px;background:#fff;border:1px solid #989898;border:1px solid rgba(0,0,0,0.2);z-index:1002;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);-moz-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);max-height:400px;overflow:auto;overflow-x:hidden}.mce-menu i{display:none}.mce-menu-has-icons i{display:inline-block;*display:inline}.mce-menu-sub-tr-tl{margin:-6px 0 0 -1px}.mce-menu-sub-br-bl{margin:6px 0 0 -1px}.mce-menu-sub-tl-tr{margin:-6px 0 0 1px}.mce-menu-sub-bl-br{margin:6px 0 0 1px}.mce-container-body .mce-resizehandle{position:absolute;right:0;bottom:0;width:16px;height:16px;visibility:visible;cursor:s-resize;margin:0}.mce-container-body .mce-resizehandle-both{cursor:se-resize}i.mce-i-resize{color:#333}.mce-spacer{visibility:hidden}.mce-splitbtn .mce-open{border-left:1px solid transparent;border-right:1px solid transparent}.mce-splitbtn:hover .mce-open{border-left-color:#bdbdbd;border-right-color:#bdbdbd}.mce-splitbtn button{padding-right:4px}.mce-splitbtn .mce-open{padding-left:4px}.mce-splitbtn .mce-open.mce-active{-webkit-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05)}.mce-splitbtn.mce-btn-small .mce-open{padding:0 3px 0 3px}.mce-rtl .mce-splitbtn{direction:rtl;text-align:right}.mce-rtl .mce-splitbtn button{padding-right:10px;padding-left:10px}.mce-rtl .mce-splitbtn .mce-open{padding-left:4px;padding-right:4px}.mce-stack-layout-item{display:block}.mce-tabs{display:block;border-bottom:1px solid #c5c5c5}.mce-tab{display:inline-block;*display:inline;*zoom:1;border:1px solid #c5c5c5;border-width:0 1px 0 0;background:#e3e3e3;padding:8px;text-shadow:0 1px 1px rgba(255,255,255,0.75);height:13px;cursor:pointer}.mce-tab:hover{background:#fdfdfd}.mce-tab.mce-active{background:#fdfdfd;border-bottom-color:transparent;margin-bottom:-1px;height:14px}.mce-rtl .mce-tabs{text-align:right;direction:rtl}.mce-rtl .mce-tab{border-width:0 0 0 1px}.mce-textbox{background:#fff;border:1px solid #c5c5c5;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);display:inline-block;-webkit-transition:border linear .2s, box-shadow linear .2s;transition:border linear .2s, box-shadow linear .2s;height:28px;resize:none;padding:0 4px 0 4px;white-space:pre-wrap;*white-space:pre;color:#333}.mce-textbox:focus,.mce-textbox.mce-focus{border-color:rgba(82,168,236,0.8);-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.65);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.65);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.65)}.mce-placeholder .mce-textbox{color:#aaa}.mce-textbox.mce-multiline{padding:4px}.mce-textbox.mce-disabled{color:#adadad}.mce-rtl .mce-textbox{text-align:right;direction:rtl}.mce-throbber{position:absolute;top:0;left:0;width:100%;height:100%;opacity:.6;filter:alpha(opacity=60);zoom:1;background:#fff url('img/loader.gif') no-repeat center center}@font-face{font-family:'tinymce';src:url('fonts/tinymce.eot');src:url('fonts/tinymce.eot?#iefix') format('embedded-opentype'),url('fonts/tinymce.woff') format('woff'),url('fonts/tinymce.ttf') format('truetype'),url('fonts/tinymce.svg#tinymce') format('svg');font-weight:normal;font-style:normal}@font-face{font-family:'tinymce-small';src:url('fonts/tinymce-small.eot');src:url('fonts/tinymce-small.eot?#iefix') format('embedded-opentype'),url('fonts/tinymce-small.woff') format('woff'),url('fonts/tinymce-small.ttf') format('truetype'),url('fonts/tinymce-small.svg#tinymce') format('svg');font-weight:normal;font-style:normal}.mce-ico{font-family:'tinymce',Arial;font-style:normal;font-weight:normal;font-size:16px;line-height:16px;vertical-align:text-top;-webkit-font-smoothing:antialiased;display:inline-block;background:transparent center center;width:16px;height:16px;color:#333}.mce-btn-small .mce-ico{font-family:'tinymce-small',Arial}.mce-i-save:before{content:"\e000"}.mce-i-newdocument:before{content:"\e001"}.mce-i-fullpage:before{content:"\e002"}.mce-i-alignleft:before{content:"\e003"}.mce-i-aligncenter:before{content:"\e004"}.mce-i-alignright:before{content:"\e005"}.mce-i-alignjustify:before{content:"\e006"}.mce-i-cut:before{content:"\e007"}.mce-i-paste:before{content:"\e008"}.mce-i-searchreplace:before{content:"\e009"}.mce-i-bullist:before{content:"\e00a"}.mce-i-numlist:before{content:"\e00b"}.mce-i-indent:before{content:"\e00c"}.mce-i-outdent:before{content:"\e00d"}.mce-i-blockquote:before{content:"\e00e"}.mce-i-undo:before{content:"\e00f"}.mce-i-redo:before{content:"\e010"}.mce-i-link:before{content:"\e011"}.mce-i-unlink:before{content:"\e012"}.mce-i-anchor:before{content:"\e013"}.mce-i-image:before{content:"\e014"}.mce-i-media:before{content:"\e015"}.mce-i-help:before{content:"\e016"}.mce-i-code:before{content:"\e017"}.mce-i-inserttime:before{content:"\e018"}.mce-i-preview:before{content:"\e019"}.mce-i-forecolor:before{content:"\e01a"}.mce-i-backcolor:before{content:"\e01a"}.mce-i-table:before{content:"\e01b"}.mce-i-hr:before{content:"\e01c"}.mce-i-removeformat:before{content:"\e01d"}.mce-i-subscript:before{content:"\e01e"}.mce-i-superscript:before{content:"\e01f"}.mce-i-charmap:before{content:"\e020"}.mce-i-emoticons:before{content:"\e021"}.mce-i-print:before{content:"\e022"}.mce-i-fullscreen:before{content:"\e023"}.mce-i-spellchecker:before{content:"\e024"}.mce-i-nonbreaking:before{content:"\e025"}.mce-i-template:before{content:"\e026"}.mce-i-pagebreak:before{content:"\e027"}.mce-i-restoredraft:before{content:"\e028"}.mce-i-untitled:before{content:"\e029"}.mce-i-bold:before{content:"\e02a"}.mce-i-italic:before{content:"\e02b"}.mce-i-underline:before{content:"\e02c"}.mce-i-strikethrough:before{content:"\e02d"}.mce-i-visualchars:before{content:"\e02e"}.mce-i-visualblocks:before{content:"\e02e"}.mce-i-ltr:before{content:"\e02f"}.mce-i-rtl:before{content:"\e030"}.mce-i-copy:before{content:"\e031"}.mce-i-resize:before{content:"\e032"}.mce-i-browse:before{content:"\e034"}.mce-i-pastetext:before{content:"\e035"}.mce-i-checkbox:before,.mce-i-selected:before{content:"\e033"}.mce-i-selected{visibility:hidden}i.mce-i-backcolor{text-shadow:none;background:#bbb} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/themes/modern/theme.min.js b/src/Umbraco.Web.UI.Client/lib/tinymce/themes/modern/theme.min.js deleted file mode 100755 index 6869cf7615..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/themes/modern/theme.min.js +++ /dev/null @@ -1 +0,0 @@ -tinymce.ThemeManager.add("modern",function(e){function t(){function t(t){var i,o=[];if(t)return d(t.split(/[ ,]/),function(t){function n(){var n=e.selection;"bullist"==r&&n.selectorChanged("ul > li",function(e,n){for(var i,o=n.parents.length;o--&&(i=n.parents[o].nodeName,"OL"!=i&&"UL"!=i););t.active(e&&"UL"==i)}),"numlist"==r&&n.selectorChanged("ol > li",function(e,n){for(var i,o=n.parents.length;o--&&(i=n.parents[o].nodeName,"OL"!=i&&"UL"!=i););t.active(e&&"OL"==i)}),t.settings.stateSelector&&n.selectorChanged(t.settings.stateSelector,function(e){t.active(e)},!0),t.settings.disabledStateSelector&&n.selectorChanged(t.settings.disabledStateSelector,function(e){t.disabled(e)})}var r;"|"==t?i=null:c.has(t)?(t={type:t},u.toolbar_items_size&&(t.size=u.toolbar_items_size),o.push(t),i=null):(i||(i={type:"buttongroup",items:[]},o.push(i)),e.buttons[t]&&(r=t,t=e.buttons[r],"function"==typeof t&&(t=t()),t.type=t.type||"button",u.toolbar_items_size&&(t.size=u.toolbar_items_size),t=c.create(t),i.items.push(t),e.initialized?n():e.on("init",n)))}),n.push({type:"toolbar",layout:"flow",items:o}),!0}for(var n=[],i=1;10>i&&t(u["toolbar"+i]);i++);return n.length||t(u.toolbar||f),n}function n(){function t(t){var n;return"|"==t?{text:"|"}:n=e.menuItems[t]}function n(n){var i,o,r,a,s;if(s=tinymce.makeMap((u.removed_menuitems||"").split(/[ ,]/)),u.menu?(o=u.menu[n],a=!0):o=h[n],o){i={text:o.title},r=[],d((o.items||"").split(/[ ,]/),function(e){var n=t(e);n&&!s[e]&&r.push(t(e))}),a||d(e.menuItems,function(e){e.context==n&&("before"==e.separator&&r.push({text:"|"}),e.prependToContext?r.unshift(e):r.push(e),"after"==e.separator&&r.push({text:"|"}))});for(var l=0;lr;r++)if(o=n[r],o&&o.func.call(o.scope,e)===!1&&e.preventDefault(),e.isImmediatePropagationStopped())return}var a=this,s={},l,c,u,d,f;c=o+(+new Date).toString(32),d="onmouseenter"in document.documentElement,u="onfocusin"in document.documentElement,f={mouseenter:"mouseover",mouseleave:"mouseout"},l=1,a.domLoaded=!1,a.events=s,a.bind=function(t,o,p,h){function m(e){i(n(e||_.event),g)}var g,v,y,b,C,x,w,_=window;if(t&&3!==t.nodeType&&8!==t.nodeType){for(t[c]?g=t[c]:(g=l++,t[c]=g,s[g]={}),h=h||t,o=o.split(" "),y=o.length;y--;)b=o[y],x=m,C=w=!1,"DOMContentLoaded"===b&&(b="ready"),a.domLoaded&&"ready"===b&&"complete"==t.readyState?p.call(h,n({type:b})):(d||(C=f[b],C&&(x=function(e){var t,r;if(t=e.currentTarget,r=e.relatedTarget,r&&t.contains)r=t.contains(r);else for(;r&&r!==t;)r=r.parentNode;r||(e=n(e||_.event),e.type="mouseout"===e.type?"mouseleave":"mouseenter",e.target=t,i(e,g))})),u||"focusin"!==b&&"focusout"!==b||(w=!0,C="focusin"===b?"focus":"blur",x=function(e){e=n(e||_.event),e.type="focus"===e.type?"focusin":"focusout",i(e,g)}),v=s[g][b],v?"ready"===b&&a.domLoaded?p({type:b}):v.push({func:p,scope:h}):(s[g][b]=v=[{func:p,scope:h}],v.fakeName=C,v.capture=w,v.nativeHandler=x,"ready"===b?r(t,x,a):e(t,C||b,x,w)));return t=v=0,p}},a.unbind=function(e,n,r){var i,o,l,u,d,f;if(!e||3===e.nodeType||8===e.nodeType)return a;if(i=e[c]){if(f=s[i],n){for(n=n.split(" "),l=n.length;l--;)if(d=n[l],o=f[d]){if(r)for(u=o.length;u--;)if(o[u].func===r){var p=o.nativeHandler;o=o.slice(0,u).concat(o.slice(u+1)),o.nativeHandler=p,f[d]=o}r&&0!==o.length||(delete f[d],t(e,o.fakeName||d,o.nativeHandler,o.capture))}}else{for(d in f)o=f[d],t(e,o.fakeName||d,o.nativeHandler,o.capture);f={}}for(d in f)return a;delete s[i];try{delete e[c]}catch(h){e[c]=null}}return a},a.fire=function(e,t,r){var o;if(!e||3===e.nodeType||8===e.nodeType)return a;r=n(null,r),r.type=t,r.target=e;do o=e[c],o&&i(r,o),e=e.parentNode||e.ownerDocument||e.defaultView||e.parentWindow;while(e&&!r.isPropagationStopped());return a},a.clean=function(e){var t,n,r=a.unbind;if(!e||3===e.nodeType||8===e.nodeType)return a;if(e[c]&&r(e),e.getElementsByTagName||(e=e.document),e&&e.getElementsByTagName)for(r(e),n=e.getElementsByTagName("*"),t=n.length;t--;)e=n[t],e[c]&&r(e);return a},a.destroy=function(){s={}},a.cancel=function(e){return e&&(e.preventDefault(),e.stopImmediatePropagation()),!1}}var o="mce-data-",a=/^(?:mouse|contextmenu)|click/,s={keyLocation:1,layerX:1,layerY:1,returnValue:1};return i.Event=new i,i.Event.bind(window,"ready",function(){}),i}),r(c,[],function(){function e(e){return mt.test(e+"")}function n(){var e,t=[];return e=function(n,r){return t.push(n+=" ")>_.cacheLength&&delete e[t.shift()],e[n]=r,r}}function r(e){return e[I]=!0,e}function i(e){var t=B.createElement("div");try{return!!e(t)}catch(n){return!1}finally{t=null}}function o(e,t,n,r){var i,o,a,s,l,c,f,p,h,m;if((t?t.ownerDocument||t:F)!==B&&A(t),t=t||B,n=n||[],!e||"string"!=typeof e)return n;if(1!==(s=t.nodeType)&&9!==s)return[];if(H&&!r){if(i=gt.exec(e))if(a=i[1]){if(9===s){if(o=t.getElementById(a),!o||!o.parentNode)return n;if(o.id===a)return n.push(o),n}else if(t.ownerDocument&&(o=t.ownerDocument.getElementById(a))&&O(t,o)&&o.id===a)return n.push(o),n}else{if(i[2])return Z.apply(n,t.getElementsByTagName(e)),n;if((a=i[3])&&z.getElementsByClassName&&t.getElementsByClassName)return Z.apply(n,t.getElementsByClassName(a)),n}if(z.qsa&&!D.test(e)){if(f=!0,p=I,h=t,m=9===s&&e,1===s&&"object"!==t.nodeName.toLowerCase()){for(c=u(e),(f=t.getAttribute("id"))?p=f.replace(bt,"\\$&"):t.setAttribute("id",p),p="[id='"+p+"'] ",l=c.length;l--;)c[l]=p+d(c[l]);h=ht.test(e)&&t.parentNode||t,m=c.join(",")}if(m)try{return Z.apply(n,h.querySelectorAll(m)),n}catch(g){}finally{f||t.removeAttribute("id")}}}return b(e.replace(lt,"$1"),t,n,r)}function a(e,t){var n=t&&e,r=n&&(~t.sourceIndex||Y)-(~e.sourceIndex||Y);if(r)return r;if(n)for(;n=n.nextSibling;)if(n===t)return-1;return e?1:-1}function s(e){return function(t){var n=t.nodeName.toLowerCase();return"input"===n&&t.type===e}}function l(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function c(e){return r(function(t){return t=+t,r(function(n,r){for(var i,o=e([],n.length,t),a=o.length;a--;)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}function u(e,t){var n,r,i,a,s,l,c,u=q[e+" "];if(u)return t?0:u.slice(0);for(s=e,l=[],c=_.preFilter;s;){(!n||(r=ct.exec(s)))&&(r&&(s=s.slice(r[0].length)||s),l.push(i=[])),n=!1,(r=ut.exec(s))&&(n=r.shift(),i.push({value:n,type:r[0].replace(lt," ")}),s=s.slice(n.length));for(a in _.filter)!(r=pt[a].exec(s))||c[a]&&!(r=c[a](r))||(n=r.shift(),i.push({value:n,type:a,matches:r}),s=s.slice(n.length));if(!n)break}return t?s.length:s?o.error(e):q(e,l).slice(0)}function d(e){for(var t=0,n=e.length,r="";n>t;t++)r+=e[t].value;return r}function f(e,t,n){var r=t.dir,i=n&&"parentNode"===r,o=V++;return t.first?function(t,n,o){for(;t=t[r];)if(1===t.nodeType||i)return e(t,n,o)}:function(t,n,a){var s,l,c,u=W+" "+o;if(a){for(;t=t[r];)if((1===t.nodeType||i)&&e(t,n,a))return!0}else for(;t=t[r];)if(1===t.nodeType||i)if(c=t[I]||(t[I]={}),(l=c[r])&&l[0]===u){if((s=l[1])===!0||s===w)return s===!0}else if(l=c[r]=[u],l[1]=e(t,n,a)||w,l[1]===!0)return!0}}function p(e){return e.length>1?function(t,n,r){for(var i=e.length;i--;)if(!e[i](t,n,r))return!1;return!0}:e[0]}function h(e,t,n,r,i){for(var o,a=[],s=0,l=e.length,c=null!=t;l>s;s++)(o=e[s])&&(!n||n(o,r,i))&&(a.push(o),c&&t.push(s));return a}function m(e,t,n,i,o,a){return i&&!i[I]&&(i=m(i)),o&&!o[I]&&(o=m(o,a)),r(function(r,a,s,l){var c,u,d,f=[],p=[],m=a.length,g=r||y(t||"*",s.nodeType?[s]:s,[]),v=!e||!r&&t?g:h(g,f,e,s,l),b=n?o||(r?e:m||i)?[]:a:v;if(n&&n(v,b,s,l),i)for(c=h(b,p),i(c,[],s,l),u=c.length;u--;)(d=c[u])&&(b[p[u]]=!(v[p[u]]=d));if(r){if(o||e){if(o){for(c=[],u=b.length;u--;)(d=b[u])&&c.push(v[u]=d);o(null,b=[],c,l)}for(u=b.length;u--;)(d=b[u])&&(c=o?tt.call(r,d):f[u])>-1&&(r[c]=!(a[c]=d))}}else b=h(b===a?b.splice(m,b.length):b),o?o(null,a,b,l):Z.apply(a,b)})}function g(e){for(var t,n,r,i=e.length,o=_.relative[e[0].type],a=o||_.relative[" "],s=o?1:0,l=f(function(e){return e===t},a,!0),c=f(function(e){return tt.call(t,e)>-1},a,!0),u=[function(e,n,r){return!o&&(r||n!==S)||((t=n).nodeType?l(e,n,r):c(e,n,r))}];i>s;s++)if(n=_.relative[e[s].type])u=[f(p(u),n)];else{if(n=_.filter[e[s].type].apply(null,e[s].matches),n[I]){for(r=++s;i>r&&!_.relative[e[r].type];r++);return m(s>1&&p(u),s>1&&d(e.slice(0,s-1)).replace(lt,"$1"),n,r>s&&g(e.slice(s,r)),i>r&&g(e=e.slice(r)),i>r&&d(e))}u.push(n)}return p(u)}function v(e,t){var n=0,i=t.length>0,a=e.length>0,s=function(r,s,l,c,u){var d,f,p,m=[],g=0,v="0",y=r&&[],b=null!=u,C=S,x=r||a&&_.find.TAG("*",u&&s.parentNode||s),N=W+=null==C?1:Math.random()||.1;for(b&&(S=s!==B&&s,w=n);null!=(d=x[v]);v++){if(a&&d){for(f=0;p=e[f++];)if(p(d,s,l)){c.push(d);break}b&&(W=N,w=++n)}i&&((d=!p&&d)&&g--,r&&y.push(d))}if(g+=v,i&&v!==g){for(f=0;p=t[f++];)p(y,m,s,l);if(r){if(g>0)for(;v--;)y[v]||m[v]||(m[v]=J.call(c));m=h(m)}Z.apply(c,m),b&&!r&&m.length>0&&g+t.length>1&&o.uniqueSort(c)}return b&&(W=N,S=C),y};return i?r(s):s}function y(e,t,n){for(var r=0,i=t.length;i>r;r++)o(e,t[r],n);return n}function b(e,t,n,r){var i,o,a,s,l,c=u(e);if(!r&&1===c.length){if(o=c[0]=c[0].slice(0),o.length>2&&"ID"===(a=o[0]).type&&9===t.nodeType&&H&&_.relative[o[1].type]){if(t=(_.find.ID(a.matches[0].replace(xt,wt),t)||[])[0],!t)return n;e=e.slice(o.shift().value.length)}for(i=pt.needsContext.test(e)?0:o.length;i--&&(a=o[i],!_.relative[s=a.type]);)if((l=_.find[s])&&(r=l(a.matches[0].replace(xt,wt),ht.test(o[0].type)&&t.parentNode||t))){if(o.splice(i,1),e=r.length&&d(o),!e)return Z.apply(n,r),n;break}}return k(e,c)(r,t,!H,n,ht.test(e)),n}function C(){}var x,w,_,N,E,k,S,T,R,A,B,L,H,D,M,P,O,I="sizzle"+-new Date,F=window.document,z={},W=0,V=0,U=n(),q=n(),$=n(),j=!1,K=function(){return 0},G=typeof t,Y=1<<31,X=[],J=X.pop,Q=X.push,Z=X.push,et=X.slice,tt=X.indexOf||function(e){for(var t=0,n=this.length;n>t;t++)if(this[t]===e)return t;return-1},nt="[\\x20\\t\\r\\n\\f]",rt="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",it=rt.replace("w","w#"),ot="([*^$|!~]?=)",at="\\["+nt+"*("+rt+")"+nt+"*(?:"+ot+nt+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+it+")|)|)"+nt+"*\\]",st=":("+rt+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+at.replace(3,8)+")*)|.*)\\)|)",lt=new RegExp("^"+nt+"+|((?:^|[^\\\\])(?:\\\\.)*)"+nt+"+$","g"),ct=new RegExp("^"+nt+"*,"+nt+"*"),ut=new RegExp("^"+nt+"*([\\x20\\t\\r\\n\\f>+~])"+nt+"*"),dt=new RegExp(st),ft=new RegExp("^"+it+"$"),pt={ID:new RegExp("^#("+rt+")"),CLASS:new RegExp("^\\.("+rt+")"),NAME:new RegExp("^\\[name=['\"]?("+rt+")['\"]?\\]"),TAG:new RegExp("^("+rt.replace("w","w*")+")"),ATTR:new RegExp("^"+at),PSEUDO:new RegExp("^"+st),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+nt+"*(even|odd|(([+-]|)(\\d*)n|)"+nt+"*(?:([+-]|)"+nt+"*(\\d+)|))"+nt+"*\\)|)","i"),needsContext:new RegExp("^"+nt+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+nt+"*((?:-\\d)?\\d*)"+nt+"*\\)|)(?=[^-]|$)","i")},ht=/[\x20\t\r\n\f]*[+~]/,mt=/^[^{]+\{\s*\[native code/,gt=/^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/,vt=/^(?:input|select|textarea|button)$/i,yt=/^h\d$/i,bt=/'|\\/g,Ct=/\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g,xt=/\\([\da-fA-F]{1,6}[\x20\t\r\n\f]?|.)/g,wt=function(e,t){var n="0x"+t-65536;return n!==n?t:0>n?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320)};try{Z.apply(X=et.call(F.childNodes),F.childNodes),X[F.childNodes.length].nodeType}catch(_t){Z={apply:X.length?function(e,t){Q.apply(e,et.call(t))}:function(e,t){for(var n=e.length,r=0;e[n++]=t[r++];);e.length=n-1}}}E=o.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?"HTML"!==t.nodeName:!1},A=o.setDocument=function(n){var r=n?n.ownerDocument||n:F;return r!==B&&9===r.nodeType&&r.documentElement?(B=r,L=r.documentElement,H=!E(r),z.getElementsByTagName=i(function(e){return e.appendChild(r.createComment("")),!e.getElementsByTagName("*").length}),z.attributes=i(function(e){e.innerHTML="";var t=typeof e.lastChild.getAttribute("multiple");return"boolean"!==t&&"string"!==t}),z.getElementsByClassName=i(function(e){return e.innerHTML="",e.getElementsByClassName&&e.getElementsByClassName("e").length?(e.lastChild.className="e",2===e.getElementsByClassName("e").length):!1}),z.getByName=i(function(e){e.id=I+0,e.appendChild(B.createElement("a")).setAttribute("name",I),e.appendChild(B.createElement("i")).setAttribute("name",I),L.appendChild(e);var t=r.getElementsByName&&r.getElementsByName(I).length===2+r.getElementsByName(I+0).length;return L.removeChild(e),t}),z.sortDetached=i(function(e){return e.compareDocumentPosition&&1&e.compareDocumentPosition(B.createElement("div"))}),_.attrHandle=i(function(e){return e.innerHTML="",e.firstChild&&typeof e.firstChild.getAttribute!==G&&"#"===e.firstChild.getAttribute("href")})?{}:{href:function(e){return e.getAttribute("href",2)},type:function(e){return e.getAttribute("type")}},z.getByName?(_.find.ID=function(e,t){if(typeof t.getElementById!==G&&H){var n=t.getElementById(e);return n&&n.parentNode?[n]:[]}},_.filter.ID=function(e){var t=e.replace(xt,wt);return function(e){return e.getAttribute("id")===t}}):(_.find.ID=function(e,n){if(typeof n.getElementById!==G&&H){var r=n.getElementById(e);return r?r.id===e||typeof r.getAttributeNode!==G&&r.getAttributeNode("id").value===e?[r]:t:[]}},_.filter.ID=function(e){var t=e.replace(xt,wt);return function(e){var n=typeof e.getAttributeNode!==G&&e.getAttributeNode("id");return n&&n.value===t}}),_.find.TAG=z.getElementsByTagName?function(e,t){return typeof t.getElementsByTagName!==G?t.getElementsByTagName(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){for(;n=o[i++];)1===n.nodeType&&r.push(n);return r}return o},_.find.NAME=z.getByName&&function(e,t){return typeof t.getElementsByName!==G?t.getElementsByName(name):void 0},_.find.CLASS=z.getElementsByClassName&&function(e,t){return typeof t.getElementsByClassName!==G&&H?t.getElementsByClassName(e):void 0},M=[],D=[":focus"],(z.qsa=e(r.querySelectorAll))&&(i(function(e){e.innerHTML="",e.querySelectorAll("[selected]").length||D.push("\\["+nt+"*(?:checked|disabled|ismap|multiple|readonly|selected|value)"),e.querySelectorAll(":checked").length||D.push(":checked")}),i(function(e){e.innerHTML="",e.querySelectorAll("[i^='']").length&&D.push("[*^$]="+nt+"*(?:\"\"|'')"),e.querySelectorAll(":enabled").length||D.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),D.push(",.*:")})),(z.matchesSelector=e(P=L.matchesSelector||L.mozMatchesSelector||L.webkitMatchesSelector||L.oMatchesSelector||L.msMatchesSelector))&&i(function(e){z.disconnectedMatch=P.call(e,"div"),P.call(e,"[s!='']:x"),M.push("!=",st)}),D=new RegExp(D.join("|")),M=M.length&&new RegExp(M.join("|")),O=e(L.contains)||L.compareDocumentPosition?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)for(;t=t.parentNode;)if(t===e)return!0;return!1},K=L.compareDocumentPosition?function(e,t){if(e===t)return j=!0,0;var n=t.compareDocumentPosition&&e.compareDocumentPosition&&e.compareDocumentPosition(t);return n?1&n||T&&t.compareDocumentPosition(e)===n?e===r||O(F,e)?-1:t===r||O(F,t)?1:R?tt.call(R,e)-tt.call(R,t):0:4&n?-1:1:e.compareDocumentPosition?-1:1}:function(e,t){var n,i=0,o=e.parentNode,s=t.parentNode,l=[e],c=[t];if(e===t)return j=!0,0;if(!o||!s)return e===r?-1:t===r?1:o?-1:s?1:0;if(o===s)return a(e,t);for(n=e;n=n.parentNode;)l.unshift(n);for(n=t;n=n.parentNode;)c.unshift(n);for(;l[i]===c[i];)i++;return i?a(l[i],c[i]):l[i]===F?-1:c[i]===F?1:0},B):B},o.matches=function(e,t){return o(e,null,null,t)},o.matchesSelector=function(e,t){if((e.ownerDocument||e)!==B&&A(e),t=t.replace(Ct,"='$1']"),z.matchesSelector&&H&&(!M||!M.test(t))&&!D.test(t))try{var n=P.call(e,t);if(n||z.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(r){}return o(t,B,null,[e]).length>0},o.contains=function(e,t){return(e.ownerDocument||e)!==B&&A(e),O(e,t)},o.attr=function(e,t){var n;return(e.ownerDocument||e)!==B&&A(e),H&&(t=t.toLowerCase()),(n=_.attrHandle[t])?n(e):!H||z.attributes?e.getAttribute(t):((n=e.getAttributeNode(t))||e.getAttribute(t))&&e[t]===!0?t:n&&n.specified?n.value:null},o.error=function(e){throw new Error("Syntax error, unrecognized expression: "+e)},o.uniqueSort=function(e){var t,n=[],r=0,i=0;if(j=!z.detectDuplicates,T=!z.sortDetached,R=!z.sortStable&&e.slice(0),e.sort(K),j){for(;t=e[i++];)t===e[i]&&(r=n.push(i));for(;r--;)e.splice(n[r],1)}return e},N=o.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=N(e)}else if(3===i||4===i)return e.nodeValue}else for(;t=e[r];r++)n+=N(t);return n},_=o.selectors={cacheLength:50,createPseudo:r,match:pt,find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(xt,wt),e[3]=(e[4]||e[5]||"").replace(xt,wt),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||o.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&o.error(e[0]),e},PSEUDO:function(e){var t,n=!e[5]&&e[2];return pt.CHILD.test(e[0])?null:(e[4]?e[2]=e[4]:n&&dt.test(n)&&(t=u(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){return"*"===e?function(){return!0}:(e=e.replace(xt,wt).toLowerCase(),function(t){return t.nodeName&&t.nodeName.toLowerCase()===e})},CLASS:function(e){var t=U[e+" "];return t||(t=new RegExp("(^|"+nt+")"+e+"("+nt+"|$)"))&&U(e,function(e){return t.test(e.className||typeof e.getAttribute!==G&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=o.attr(r,e);return null==i?"!="===t:t?(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i+" ").indexOf(n)>-1:"|="===t?i===n||i.slice(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,l){var c,u,d,f,p,h,m=o!==a?"nextSibling":"previousSibling",g=t.parentNode,v=s&&t.nodeName.toLowerCase(),y=!l&&!s;if(g){if(o){for(;m;){for(d=t;d=d[m];)if(s?d.nodeName.toLowerCase()===v:1===d.nodeType)return!1;h=m="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?g.firstChild:g.lastChild],a&&y){for(u=g[I]||(g[I]={}),c=u[e]||[],p=c[0]===W&&c[1],f=c[0]===W&&c[2],d=p&&g.childNodes[p];d=++p&&d&&d[m]||(f=p=0)||h.pop();)if(1===d.nodeType&&++f&&d===t){u[e]=[W,p,f];break}}else if(y&&(c=(t[I]||(t[I]={}))[e])&&c[0]===W)f=c[1];else for(;(d=++p&&d&&d[m]||(f=p=0)||h.pop())&&((s?d.nodeName.toLowerCase()!==v:1!==d.nodeType)||!++f||(y&&((d[I]||(d[I]={}))[e]=[W,f]),d!==t)););return f-=i,f===r||f%r===0&&f/r>=0}}},PSEUDO:function(e,t){var n,i=_.pseudos[e]||_.setFilters[e.toLowerCase()]||o.error("unsupported pseudo: "+e);return i[I]?i(t):i.length>1?(n=[e,e,"",t],_.setFilters.hasOwnProperty(e.toLowerCase())?r(function(e,n){for(var r,o=i(e,t),a=o.length;a--;)r=tt.call(e,o[a]),e[r]=!(n[r]=o[a])}):function(e){return i(e,0,n)}):i}},pseudos:{not:r(function(e){var t=[],n=[],i=k(e.replace(lt,"$1"));return i[I]?r(function(e,t,n,r){for(var o,a=i(e,null,r,[]),s=e.length;s--;)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,r,o){return t[0]=e,i(t,null,o,n),!n.pop()}}),has:r(function(e){return function(t){return o(e,t).length>0}}),contains:r(function(e){return function(t){return(t.textContent||t.innerText||N(t)).indexOf(e)>-1}}),lang:r(function(e){return ft.test(e||"")||o.error("unsupported lang: "+e),e=e.replace(xt,wt).toLowerCase(),function(t){var n;do if(n=H?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return n=n.toLowerCase(),n===e||0===n.indexOf(e+"-");while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(e){var t=window.location&&window.location.hash;return t&&t.slice(1)===e.id},root:function(e){return e===L},focus:function(e){return e===B.activeElement&&(!B.hasFocus||B.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeName>"@"||3===e.nodeType||4===e.nodeType)return!1;return!0},parent:function(e){return!_.pseudos.empty(e)},header:function(e){return yt.test(e.nodeName)},input:function(e){return vt.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||t.toLowerCase()===e.type)},first:c(function(){return[0]}),last:c(function(e,t){return[t-1]}),eq:c(function(e,t,n){return[0>n?n+t:n]}),even:c(function(e,t){for(var n=0;t>n;n+=2)e.push(n);return e}),odd:c(function(e,t){for(var n=1;t>n;n+=2)e.push(n);return e}),lt:c(function(e,t,n){for(var r=0>n?n+t:n;--r>=0;)e.push(r);return e}),gt:c(function(e,t,n){for(var r=0>n?n+t:n;++rn;n++)t[n]=e[n];return t}function f(e,t){var n;if(t.indexOf)return t.indexOf(e);for(n=t.length;n--;)if(t[n]===e)return n;return-1}function p(e,t){var n,r,i,o,a;if(e)if(n=e.length,n===o){for(r in e)if(e.hasOwnProperty(r)&&(a=e[r],t.call(a,a,r)===!1))break}else for(i=0;n>i&&(a=e[i],t.call(a,a,r)!==!1);i++);return e}function h(e,n,r){for(var i=[],o=e[n];o&&9!==o.nodeType&&(r===t||1!==o.nodeType||!c(o).is(r));)1===o.nodeType&&i.push(o),o=o[n];return i}function m(e,t,n,r){for(var i=[];e;e=e[n])r&&e.nodeType!==r||e===t||i.push(e);return i}var g=document,v=Array.prototype.push,y=Array.prototype.slice,b=/^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,C=e.Event,x=l("fillOpacity fontWeight lineHeight opacity orphans widows zIndex zoom"),w=Array.isArray||function(e){return"[object Array]"===Object.prototype.toString.call(e)},_=/^\s*|\s*$/g,N=function(e){return null===e||e===t?"":(""+e).replace(_,"")};return c.fn=c.prototype={constructor:c,selector:"",length:0,init:function(e,t){var n=this,r,a;if(!e)return n;if(e.nodeType)return n.context=n[0]=e,n.length=1,n;if(i(e)){if(r="<"===e.charAt(0)&&">"===e.charAt(e.length-1)&&e.length>=3?[null,e,null]:b.exec(e),!r)return c(t||document).find(e);if(r[1])for(a=o(e).firstChild;a;)this.add(a),a=a.nextSibling;else{if(a=g.getElementById(r[2]),a.id!==r[2])return n.find(e);n.length=1,n[0]=a}}else this.add(e);return n},toArray:function(){return d(this)},add:function(e){var t=this;return w(e)?v.apply(t,e):e instanceof c?t.add(e.toArray()):v.call(t,e),t},attr:function(e,n){var i=this;if("object"==typeof e)p(e,function(e,t){i.attr(t,e)});else{if(!r(n))return i[0]&&1===i[0].nodeType?i[0].getAttribute(e):t;this.each(function(){1===this.nodeType&&this.setAttribute(e,n)})}return i},css:function(e,n){var i=this;if("object"==typeof e)p(e,function(e,t){i.css(t,e)});else{if(e=e.replace(/-(\D)/g,function(e,t){return t.toUpperCase()}),!r(n))return i[0]?i[0].style[e]:t;"number"!=typeof n||x[e]||(n+="px"),i.each(function(){var t=this.style;"opacity"===e&&this.runtimeStyle&&"undefined"==typeof this.runtimeStyle.opacity&&(t.filter=""===n?"":"alpha(opacity="+100*n+")");try{t[e]=n}catch(r){}})}return i},remove:function(){for(var e=this,t,n=this.length;n--;)t=e[n],C.clean(t),t.parentNode&&t.parentNode.removeChild(t);return this},empty:function(){for(var e=this,t,n=this.length;n--;)for(t=e[n];t.firstChild;)t.removeChild(t.firstChild);return this},html:function(e){var t=this,n;if(r(e)){for(n=t.length;n--;)t[n].innerHTML=e;return t}return t[0]?t[0].innerHTML:""},text:function(e){var t=this,n;if(r(e)){for(n=t.length;n--;)t[n].innerText=t[0].textContent=e;return t}return t[0]?t[0].innerText||t[0].textContent:""},append:function(){return a(this,arguments,function(e){1===this.nodeType&&this.appendChild(e)})},prepend:function(){return a(this,arguments,function(e){1===this.nodeType&&this.insertBefore(e,this.firstChild)})},before:function(){var e=this;return e[0]&&e[0].parentNode?a(e,arguments,function(e){this.parentNode.insertBefore(e,this.nextSibling)}):e},after:function(){var e=this;return e[0]&&e[0].parentNode?a(e,arguments,function(e){this.parentNode.insertBefore(e,this)}):e},appendTo:function(e){return c(e).append(this),this},addClass:function(e){return this.toggleClass(e,!0)},removeClass:function(e){return this.toggleClass(e,!1)},toggleClass:function(e,t){var n=this;return-1!==e.indexOf(" ")?p(e.split(" "),function(){n.toggleClass(this,t)}):n.each(function(){var n=this,r;s(n,e)!==t&&(r=n.className,t?n.className+=r?" "+e:e:n.className=N((" "+r+" ").replace(" "+e+" "," ")))}),n},hasClass:function(e){return s(this[0],e)},each:function(e){return p(this,e)},on:function(e,t){return this.each(function(){C.bind(this,e,t)})},off:function(e,t){return this.each(function(){C.unbind(this,e,t)})},show:function(){return this.css("display","")},hide:function(){return this.css("display","none")},slice:function(){return new c(y.apply(this,arguments))},eq:function(e){return-1===e?this.slice(e):this.slice(e,+e+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},replaceWith:function(e){var t=this;return t[0]&&t[0].parentNode.replaceChild(c(e)[0],t[0]),t},wrap:function(e){return e=c(e)[0],this.each(function(){var t=this,n=e.cloneNode(!1);t.parentNode.insertBefore(n,t),n.appendChild(t)})},unwrap:function(){return this.each(function(){for(var e=this,t=e.firstChild,n;t;)n=t,t=t.nextSibling,e.parentNode.insertBefore(n,e)})},clone:function(){var e=[];return this.each(function(){e.push(this.cloneNode(!0))}),c(e)},find:function(e){var t,n,r=[];for(t=0,n=this.length;n>t;t++)c.find(e,this[t],r);return c(r)},push:v,sort:[].sort,splice:[].splice},u(c,{extend:u,toArray:d,inArray:f,isArray:w,each:p,trim:N,makeMap:l,find:n,expr:n.selectors,unique:n.uniqueSort,text:n.getText,isXMLDoc:n.isXML,contains:n.contains,filter:function(e,t,n){return n&&(e=":not("+e+")"),1===t.length?c.find.matchesSelector(t[0],e)?[t[0]]:[]:c.find.matches(e,t)}}),p({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return h(e,"parentNode")},parentsUntil:function(e,t){return h(e,"parentNode",t)},next:function(e){return m(e,"nextSibling",1)},prev:function(e){return m(e,"previousSibling",1)},nextNodes:function(e){return m(e,"nextSibling")},prevNodes:function(e){return m(e,"previousSibling")},children:function(e){return m(e.firstChild,"nextSibling",1)},contents:function(e){return d(("iframe"===e.nodeName?e.contentDocument||e.contentWindow.document:e).childNodes)}},function(e,t){c.fn[e]=function(n){var r=this,i;if(r.length>1)throw new Error("DomQuery only supports traverse functions on a single node.");return r[0]&&(i=t(r[0],n)),i=c(i),n&&"parentsUntil"!==e?i.filter(n):i}}),c.fn.filter=function(e){return c.filter(e)},c.fn.is=function(e){return!!e&&this.filter(e).length>0},c.fn.init.prototype=c.fn,c}),r(d,[],function(){return function(e,t){function n(e,t,n,r){function i(e){return e=parseInt(e,10).toString(16),e.length>1?e:"0"+e}return"#"+i(t)+i(n)+i(r)}var r=/rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi,i=/(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi,o=/\s*([^:]+):\s*([^;]+);?/g,a=/\s+$/,s,l,c={},u,d="\ufeff"; -for(e=e||{},u=("\\\" \\' \\; \\: ; : "+d).split(" "),l=0;l-1&&n||(m[e+t]=-1==l?s[0]:s.join(" "),delete m[e+"-top"+t],delete m[e+"-right"+t],delete m[e+"-bottom"+t],delete m[e+"-left"+t])}}function u(e){var t=m[e],n;if(t){for(t=t.split(" "),n=t.length;n--;)if(t[n]!==t[0])return!1;return m[e]=t[0],!0}}function d(e,t,n,r){u(t)&&u(n)&&u(r)&&(m[e]=m[t]+" "+m[n]+" "+m[r],delete m[t],delete m[n],delete m[r])}function f(e){return b=!0,c[e]}function p(e,t){return b&&(e=e.replace(/\uFEFF[0-9]/g,function(e){return c[e]})),t||(e=e.replace(/\\([\'\";:])/g,"$1")),e}function h(t,n,r,i,o,a){return(o=o||a)?(o=p(o),"'"+o.replace(/\'/g,"\\'")+"'"):(n=p(n||r||i),!e.allow_script_urls&&/(java|vb)script:/i.test(n.replace(/[\s\r\n]+/,""))?"":(C&&(n=C.call(x,n,"style")),"url('"+n.replace(/\'/g,"\\'")+"')"))}var m={},g,v,y,b,C=e.url_converter,x=e.url_converter_scope||this;if(t){for(t=t.replace(/[\u0000-\u001F]/g,""),t=t.replace(/\\[\"\';:\uFEFF]/g,f).replace(/\"[^\"]+\"|\'[^\']+\'/g,function(e){return e.replace(/[;:]/g,f)});g=o.exec(t);){if(v=g[1].replace(a,"").toLowerCase(),y=g[2].replace(a,""),v&&y.length>0){if(!e.allow_script_urls&&("behavior"==v||/expression\s*\(/.test(y)))continue;"font-weight"===v&&"700"===y?y="bold":("color"===v||"background-color"===v)&&(y=y.toLowerCase()),y=y.replace(r,n),y=y.replace(i,h),m[v]=b?p(y,!0):y}o.lastIndex=g.index+g[0].length}s("border","",!0),s("border","-width"),s("border","-color"),s("border","-style"),s("padding",""),s("margin",""),d("border","border-width","border-style","border-color"),"medium none"===m.border&&delete m.border,"none"===m["border-image"]&&delete m["border-image"]}return m},serialize:function(e,n){function r(n){var r,o,a,l;if(r=t.styles[n])for(o=0,a=r.length;a>o;o++)n=r[o],l=e[n],l!==s&&l.length>0&&(i+=(i.length>0?" ":"")+n+": "+l+";")}var i="",o,a;if(n&&t&&t.styles)r("*"),r(n);else for(o in e)a=e[o],a!==s&&a.length>0&&(i+=(i.length>0?" ":"")+o+": "+a+";");return i}}}}),r(f,[],function(){return function(e,t){function n(e,n,r,i){var o,a;if(e){if(!i&&e[n])return e[n];if(e!=t){if(o=e[r])return o;for(a=e.parentNode;a&&a!=t;a=a.parentNode)if(o=a[r])return o}}}var r=e;this.current=function(){return r},this.next=function(e){return r=n(r,"firstChild","nextSibling",e)},this.prev=function(e){return r=n(r,"lastChild","previousSibling",e)}}}),r(p,[],function(){function e(e,n){return n?"array"==n&&g(e)?!0:typeof e==n:e!==t}function n(e){var t=[],n,r;for(n=0,r=e.length;r>n;n++)t[n]=e[n];return t}function r(e,t,n){var r;for(e=e||[],t=t||",","string"==typeof e&&(e=e.split(t)),n=n||{},r=e.length;r--;)n[e[r]]={};return n}function i(e,n,r){var i,o;if(!e)return 0;if(r=r||e,e.length!==t){for(i=0,o=e.length;o>i;i++)if(n.call(r,e[i],i,e)===!1)return 0}else for(i in e)if(e.hasOwnProperty(i)&&n.call(r,e[i],i,e)===!1)return 0;return 1}function o(e,t){var n=[];return i(e,function(e){n.push(t(e))}),n}function a(e,t){var n=[];return i(e,function(e){(!t||t(e))&&n.push(e)}),n}function s(e,t,n){var r=this,i,o,a,s,l,c=0;if(e=/^((static) )?([\w.]+)(:([\w.]+))?/.exec(e),a=e[3].match(/(^|\.)(\w+)$/i)[2],o=r.createNS(e[3].replace(/\.\w+$/,""),n),!o[a]){if("static"==e[2])return o[a]=t,this.onCreate&&this.onCreate(e[2],e[3],o[a]),void 0;t[a]||(t[a]=function(){},c=1),o[a]=t[a],r.extend(o[a].prototype,t),e[5]&&(i=r.resolve(e[5]).prototype,s=e[5].match(/\.(\w+)$/i)[1],l=o[a],o[a]=c?function(){return i[s].apply(this,arguments)}:function(){return this.parent=i[s],l.apply(this,arguments)},o[a].prototype[a]=o[a],r.each(i,function(e,t){o[a].prototype[t]=i[t]}),r.each(t,function(e,t){i[t]?o[a].prototype[t]=function(){return this.parent=i[t],e.apply(this,arguments)}:t!=a&&(o[a].prototype[t]=e)})),r.each(t["static"],function(e,t){o[a][t]=e})}}function l(e,t){var n,r;if(e)for(n=0,r=e.length;r>n;n++)if(e[n]===t)return n;return-1}function c(e,n){var r,i,o,a=arguments,s;for(r=1,i=a.length;i>r;r++){n=a[r];for(o in n)n.hasOwnProperty(o)&&(s=n[o],s!==t&&(e[o]=s))}return e}function u(e,t,n,r){r=r||this,e&&(n&&(e=e[n]),i(e,function(e,i){return t.call(r,e,i,n)===!1?!1:(u(e,t,n,r),void 0)}))}function d(e,t){var n,r;for(t=t||window,e=e.split("."),n=0;nn&&(t=t[e[n]],t);n++);return t}function p(t,n){return!t||e(t,"array")?t:o(t.split(n||","),m)}var h=/^\s*|\s*$/g,m=function(e){return null===e||e===t?"":(""+e).replace(h,"")},g=Array.isArray||function(e){return"[object Array]"===Object.prototype.toString.call(e)};return{trim:m,isArray:g,is:e,toArray:n,makeMap:r,each:i,map:o,grep:a,inArray:l,extend:c,create:s,walk:u,createNS:d,resolve:f,explode:p}}),r(h,[p],function(e){function t(n){function r(){return M.createDocumentFragment()}function i(e,t){_(F,e,t)}function o(e,t){_(z,e,t)}function a(e){i(e.parentNode,j(e))}function s(e){i(e.parentNode,j(e)+1)}function l(e){o(e.parentNode,j(e))}function c(e){o(e.parentNode,j(e)+1)}function u(e){e?(D[U]=D[V],D[q]=D[W]):(D[V]=D[U],D[W]=D[q]),D.collapsed=F}function d(e){a(e),c(e)}function f(e){i(e,0),o(e,1===e.nodeType?e.childNodes.length:e.nodeValue.length)}function p(e,t){var n=D[V],r=D[W],i=D[U],o=D[q],a=t.startContainer,s=t.startOffset,l=t.endContainer,c=t.endOffset;return 0===e?w(n,r,a,s):1===e?w(i,o,a,s):2===e?w(i,o,l,c):3===e?w(n,r,l,c):void 0}function h(){N(I)}function m(){return N(P)}function g(){return N(O)}function v(e){var t=this[V],r=this[W],i,o;3!==t.nodeType&&4!==t.nodeType||!t.nodeValue?(t.childNodes.length>0&&(o=t.childNodes[r]),o?t.insertBefore(e,o):3==t.nodeType?n.insertAfter(e,t):t.appendChild(e)):r?r>=t.nodeValue.length?n.insertAfter(e,t):(i=t.splitText(r),t.parentNode.insertBefore(e,i)):t.parentNode.insertBefore(e,t)}function y(e){var t=D.extractContents();D.insertNode(e),e.appendChild(t),D.selectNode(e)}function b(){return $(new t(n),{startContainer:D[V],startOffset:D[W],endContainer:D[U],endOffset:D[q],collapsed:D.collapsed,commonAncestorContainer:D.commonAncestorContainer})}function C(e,t){var n;if(3==e.nodeType)return e;if(0>t)return e;for(n=e.firstChild;n&&t>0;)--t,n=n.nextSibling;return n?n:e}function x(){return D[V]==D[U]&&D[W]==D[q]}function w(e,t,r,i){var o,a,s,l,c,u;if(e==r)return t==i?0:i>t?-1:1;for(o=r;o&&o.parentNode!=e;)o=o.parentNode;if(o){for(a=0,s=e.firstChild;s!=o&&t>a;)a++,s=s.nextSibling;return a>=t?-1:1}for(o=e;o&&o.parentNode!=r;)o=o.parentNode;if(o){for(a=0,s=r.firstChild;s!=o&&i>a;)a++,s=s.nextSibling;return i>a?-1:1}for(l=n.findCommonAncestor(e,r),c=e;c&&c.parentNode!=l;)c=c.parentNode;for(c||(c=l),u=r;u&&u.parentNode!=l;)u=u.parentNode;if(u||(u=l),c==u)return 0;for(s=l.firstChild;s;){if(s==c)return-1;if(s==u)return 1;s=s.nextSibling}}function _(e,t,r){var i,o;for(e?(D[V]=t,D[W]=r):(D[U]=t,D[q]=r),i=D[U];i.parentNode;)i=i.parentNode;for(o=D[V];o.parentNode;)o=o.parentNode;o==i?w(D[V],D[W],D[U],D[q])>0&&D.collapse(e):D.collapse(e),D.collapsed=x(),D.commonAncestorContainer=n.findCommonAncestor(D[V],D[U])}function N(e){var t,n=0,r=0,i,o,a,s,l,c;if(D[V]==D[U])return E(e);for(t=D[U],i=t.parentNode;i;t=i,i=i.parentNode){if(i==D[V])return k(t,e);++n}for(t=D[V],i=t.parentNode;i;t=i,i=i.parentNode){if(i==D[U])return S(t,e);++r}for(o=r-n,a=D[V];o>0;)a=a.parentNode,o--;for(s=D[U];0>o;)s=s.parentNode,o++;for(l=a.parentNode,c=s.parentNode;l!=c;l=l.parentNode,c=c.parentNode)a=l,s=c;return T(a,s,e)}function E(e){var t,n,i,o,a,s,l,c,u;if(e!=I&&(t=r()),D[W]==D[q])return t;if(3==D[V].nodeType){if(n=D[V].nodeValue,i=n.substring(D[W],D[q]),e!=O&&(o=D[V],c=D[W],u=D[q]-D[W],0===c&&u>=o.nodeValue.length-1?o.parentNode.removeChild(o):o.deleteData(c,u),D.collapse(F)),e==I)return;return i.length>0&&t.appendChild(M.createTextNode(i)),t}for(o=C(D[V],D[W]),a=D[q]-D[W];o&&a>0;)s=o.nextSibling,l=L(o,e),t&&t.appendChild(l),--a,o=s;return e!=O&&D.collapse(F),t}function k(e,t){var n,i,o,a,s,l;if(t!=I&&(n=r()),i=R(e,t),n&&n.appendChild(i),o=j(e),a=o-D[W],0>=a)return t!=O&&(D.setEndBefore(e),D.collapse(z)),n;for(i=e.previousSibling;a>0;)s=i.previousSibling,l=L(i,t),n&&n.insertBefore(l,n.firstChild),--a,i=s;return t!=O&&(D.setEndBefore(e),D.collapse(z)),n}function S(e,t){var n,i,o,a,s,l;for(t!=I&&(n=r()),o=A(e,t),n&&n.appendChild(o),i=j(e),++i,a=D[q]-i,o=e.nextSibling;o&&a>0;)s=o.nextSibling,l=L(o,t),n&&n.appendChild(l),--a,o=s;return t!=O&&(D.setStartAfter(e),D.collapse(F)),n}function T(e,t,n){var i,o,a,s,l,c,u,d;for(n!=I&&(o=r()),i=A(e,n),o&&o.appendChild(i),a=e.parentNode,s=j(e),l=j(t),++s,c=l-s,u=e.nextSibling;c>0;)d=u.nextSibling,i=L(u,n),o&&o.appendChild(i),u=d,--c;return i=R(t,n),o&&o.appendChild(i),n!=O&&(D.setStartAfter(e),D.collapse(F)),o}function R(e,t){var n=C(D[U],D[q]-1),r,i,o,a,s,l=n!=D[U];if(n==e)return B(n,l,z,t);for(r=n.parentNode,i=B(r,z,z,t);r;){for(;n;)o=n.previousSibling,a=B(n,l,z,t),t!=I&&i.insertBefore(a,i.firstChild),l=F,n=o;if(r==e)return i;n=r.previousSibling,r=r.parentNode,s=B(r,z,z,t),t!=I&&s.appendChild(i),i=s}}function A(e,t){var n=C(D[V],D[W]),r=n!=D[V],i,o,a,s,l;if(n==e)return B(n,r,F,t);for(i=n.parentNode,o=B(i,z,F,t);i;){for(;n;)a=n.nextSibling,s=B(n,r,F,t),t!=I&&o.appendChild(s),r=F,n=a;if(i==e)return o;n=i.nextSibling,i=i.parentNode,l=B(i,z,F,t),t!=I&&l.appendChild(o),o=l}}function B(e,t,r,i){var o,a,s,l,c;if(t)return L(e,i);if(3==e.nodeType){if(o=e.nodeValue,r?(l=D[W],a=o.substring(l),s=o.substring(0,l)):(l=D[q],a=o.substring(0,l),s=o.substring(l)),i!=O&&(e.nodeValue=s),i==I)return;return c=n.clone(e,z),c.nodeValue=a,c}if(i!=I)return n.clone(e,z)}function L(e,t){return t!=I?t==O?n.clone(e,F):e:(e.parentNode.removeChild(e),void 0)}function H(){return n.create("body",null,g()).outerText}var D=this,M=n.doc,P=0,O=1,I=2,F=!0,z=!1,W="startOffset",V="startContainer",U="endContainer",q="endOffset",$=e.extend,j=n.nodeIndex;return $(D,{startContainer:M,startOffset:0,endContainer:M,endOffset:0,collapsed:F,commonAncestorContainer:M,START_TO_START:0,START_TO_END:1,END_TO_END:2,END_TO_START:3,setStart:i,setEnd:o,setStartBefore:a,setStartAfter:s,setEndBefore:l,setEndAfter:c,collapse:u,selectNode:d,selectNodeContents:f,compareBoundaryPoints:p,deleteContents:h,extractContents:m,cloneContents:g,insertNode:v,surroundContents:y,cloneRange:b,toStringIE:H}),D}return t.prototype.toString=function(){return this.toStringIE()},t}),r(m,[p],function(e){function t(e){var t;return t=document.createElement("div"),t.innerHTML=e,t.textContent||t.innerText||e}function n(e,t){var n,r,i,a={};if(e){for(e=e.split(","),t=t||10,n=0;n\"\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,l=/[<>&\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,c=/[<>&\"\']/g,u=/&(#x|#)?([\w]+);/g,d={128:"\u20ac",130:"\u201a",131:"\u0192",132:"\u201e",133:"\u2026",134:"\u2020",135:"\u2021",136:"\u02c6",137:"\u2030",138:"\u0160",139:"\u2039",140:"\u0152",142:"\u017d",145:"\u2018",146:"\u2019",147:"\u201c",148:"\u201d",149:"\u2022",150:"\u2013",151:"\u2014",152:"\u02dc",153:"\u2122",154:"\u0161",155:"\u203a",156:"\u0153",158:"\u017e",159:"\u0178"};o={'"':""","'":"'","<":"<",">":">","&":"&"},a={"<":"<",">":">","&":"&",""":'"',"'":"'"},i=n("50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,t9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro",32);var f={encodeRaw:function(e,t){return e.replace(t?s:l,function(e){return o[e]||e})},encodeAllRaw:function(e){return(""+e).replace(c,function(e){return o[e]||e})},encodeNumeric:function(e,t){return e.replace(t?s:l,function(e){return e.length>1?"&#"+(1024*(e.charCodeAt(0)-55296)+(e.charCodeAt(1)-56320)+65536)+";":o[e]||"&#"+e.charCodeAt(0)+";"})},encodeNamed:function(e,t,n){return n=n||i,e.replace(t?s:l,function(e){return o[e]||n[e]||e})},getEncodeFunc:function(e,t){function a(e,n){return e.replace(n?s:l,function(e){return o[e]||t[e]||"&#"+e.charCodeAt(0)+";"||e})}function c(e,n){return f.encodeNamed(e,n,t)}return t=n(t)||i,e=r(e.replace(/\+/g,",")),e.named&&e.numeric?a:e.named?t?c:f.encodeNamed:e.numeric?f.encodeNumeric:f.encodeRaw},decode:function(e){return e.replace(u,function(e,n,r){return n?(r=parseInt(r,2===n.length?16:10),r>65535?(r-=65536,String.fromCharCode(55296+(r>>10),56320+(1023&r))):d[r]||String.fromCharCode(r)):a[e]||i[e]||t(e)})}};return f}),r(g,[],function(){var e=navigator,t=e.userAgent,n,r,i,o,a,s,l;n=window.opera&&window.opera.buildNumber,r=/WebKit/.test(t),i=!r&&!n&&/MSIE/gi.test(t)&&/Explorer/gi.test(e.appName),i=i&&/MSIE (\w+)\./.exec(t)[1],o=-1==t.indexOf("Trident/")||-1==t.indexOf("rv:")&&-1==e.appName.indexOf("Netscape")?!1:11,i=i||o,a=!r&&!o&&/Gecko/.test(t),s=-1!=t.indexOf("Mac"),l=/(iPad|iPhone)/.test(t);var c=!l||t.match(/AppleWebKit\/(\d*)/)[1]>=534;return{opera:n,webkit:r,ie:i,gecko:a,mac:s,iOS:l,contentEditable:c,transparentSrc:"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7",caretAfter:8!=i,range:window.getSelection&&"Range"in window,documentMode:i?document.documentMode||7:10}}),r(v,[c,d,l,f,h,m,g,p],function(e,n,r,i,o,a,s,l){function c(e,t){var i=this,o;i.doc=e,i.win=window,i.files={},i.counter=0,i.stdMode=!g||e.documentMode>=8,i.boxModel=!g||"CSS1Compat"==e.compatMode||i.stdMode,i.hasOuterHTML="outerHTML"in e.createElement("a"),this.boundEvents=[],i.settings=t=h({keep_values:!1,hex_colors:1},t),i.schema=t.schema,i.styles=new n({url_converter:t.url_converter,url_converter_scope:t.url_converter_scope},t.schema),i.fixDoc(e),i.events=t.ownEvents?new r(t.proxy):r.Event,o=t.schema?t.schema.getBlockElements():{},i.isBlock=function(e){if(!e)return!1;var t=e.nodeType;return t?!(1!==t||!o[e.nodeName]):!!o[e]}}var u=l.each,d=l.is,f=l.grep,p=l.trim,h=l.extend,m=s.webkit,g=s.ie,v=/^([a-z0-9],?)+$/i,y=/^[ \t\r\n]*$/,b=l.makeMap("fillOpacity fontWeight lineHeight opacity orphans widows zIndex zoom"," ");return c.prototype={root:null,props:{"for":"htmlFor","class":"className",className:"className",checked:"checked",disabled:"disabled",maxlength:"maxLength",readonly:"readOnly",selected:"selected",value:"value",id:"id",name:"name",type:"type"},fixDoc:function(e){var t=this.settings,n;if(g&&t.schema){"abbr article aside audio canvas details figcaption figure footer header hgroup mark menu meter nav output progress section summary time video".replace(/\w+/g,function(t){e.createElement(t)});for(n in t.schema.getCustomElements())e.createElement(n)}},clone:function(e,t){var n=this,r,i;return!g||1!==e.nodeType||t?e.cloneNode(t):(i=n.doc,t?r.firstChild:(r=i.createElement(e.nodeName),u(n.getAttribs(e),function(t){n.setAttrib(r,t.nodeName,n.getAttrib(e,t.nodeName))}),r))},getRoot:function(){var e=this;return e.get(e.settings.root_element)||e.doc.body},getViewPort:function(e){var t,n;return e=e?e:this.win,t=e.document,n=this.boxModel?t.documentElement:t.body,{x:e.pageXOffset||n.scrollLeft,y:e.pageYOffset||n.scrollTop,w:e.innerWidth||n.clientWidth,h:e.innerHeight||n.clientHeight}},getRect:function(e){var t=this,n,r;return e=t.get(e),n=t.getPos(e),r=t.getSize(e),{x:n.x,y:n.y,w:r.w,h:r.h}},getSize:function(e){var t=this,n,r;return e=t.get(e),n=t.getStyle(e,"width"),r=t.getStyle(e,"height"),-1===n.indexOf("px")&&(n=0),-1===r.indexOf("px")&&(r=0),{w:parseInt(n,10)||e.offsetWidth||e.clientWidth,h:parseInt(r,10)||e.offsetHeight||e.clientHeight}},getParent:function(e,t,n){return this.getParents(e,t,n,!1)},getParents:function(e,n,r,i){var o=this,a,s=[];for(e=o.get(e),i=i===t,r=r||("BODY"!=o.getRoot().nodeName?o.getRoot().parentNode:null),d(n,"string")&&(a=n,n="*"===n?function(e){return 1==e.nodeType}:function(e){return o.is(e,a)});e&&e!=r&&e.nodeType&&9!==e.nodeType;){if(!n||n(e)){if(!i)return e;s.push(e)}e=e.parentNode}return i?s:null},get:function(e){var t;return e&&this.doc&&"string"==typeof e&&(t=e,e=this.doc.getElementById(e),e&&e.id!==t)?this.doc.getElementsByName(t)[1]:e},getNext:function(e,t){return this._findSib(e,t,"nextSibling")},getPrev:function(e,t){return this._findSib(e,t,"previousSibling")},select:function(t,n){var r=this;return e(t,r.get(n)||r.get(r.settings.root_element)||r.doc,[])},is:function(n,r){var i;if(n.length===t){if("*"===r)return 1==n.nodeType;if(v.test(r)){for(r=r.toLowerCase().split(/,/),n=n.nodeName.toLowerCase(),i=r.length-1;i>=0;i--)if(r[i]==n)return!0;return!1}}return n.nodeType&&1!=n.nodeType?!1:e.matches(r,n.nodeType?[n]:n).length>0},add:function(e,t,n,r,i){var o=this;return this.run(e,function(e){var a;return a=d(t,"string")?o.doc.createElement(t):t,o.setAttribs(a,n),r&&(r.nodeType?a.appendChild(r):o.setHTML(a,r)),i?a:e.appendChild(a)})},create:function(e,t,n){return this.add(this.doc.createElement(e),e,t,n,1)},createHTML:function(e,t,n){var r="",i;r+="<"+e;for(i in t)t.hasOwnProperty(i)&&null!==t[i]&&(r+=" "+i+'="'+this.encode(t[i])+'"');return"undefined"!=typeof n?r+">"+n+"":r+" />"},createFragment:function(e){var t,n,r=this.doc,i;for(i=r.createElement("div"),t=r.createDocumentFragment(),e&&(i.innerHTML=e);n=i.firstChild;)t.appendChild(n);return t},remove:function(e,t){return this.run(e,function(e){var n,r=e.parentNode;if(!r)return null;if(t)for(;n=e.firstChild;)!g||3!==n.nodeType||n.nodeValue?r.insertBefore(n,e):e.removeChild(n);return r.removeChild(e)})},setStyle:function(e,t,n){return this.run(e,function(e){var r=this,i,o;if(t)if("string"==typeof t){i=e.style,t=t.replace(/-(\D)/g,function(e,t){return t.toUpperCase()}),"number"!=typeof n||b[t]||(n+="px"),"opacity"===t&&e.runtimeStyle&&"undefined"==typeof e.runtimeStyle.opacity&&(i.filter=""===n?"":"alpha(opacity="+100*n+")"),"float"==t&&(t="cssFloat"in e.style?"cssFloat":"styleFloat");try{i[t]=n}catch(a){}r.settings.update_styles&&e.removeAttribute("data-mce-style")}else for(o in t)r.setStyle(e,o,t[o])})},getStyle:function(e,n,r){if(e=this.get(e)){if(this.doc.defaultView&&r){n=n.replace(/[A-Z]/g,function(e){return"-"+e});try{return this.doc.defaultView.getComputedStyle(e,null).getPropertyValue(n)}catch(i){return null}}return n=n.replace(/-(\D)/g,function(e,t){return t.toUpperCase()}),"float"==n&&(n=g?"styleFloat":"cssFloat"),e.currentStyle&&r?e.currentStyle[n]:e.style?e.style[n]:t}},setStyles:function(e,t){this.setStyle(e,t)},css:function(e,t,n){this.setStyle(e,t,n)},removeAllAttribs:function(e){return this.run(e,function(e){var t,n=e.attributes;for(t=n.length-1;t>=0;t--)e.removeAttributeNode(n.item(t))})},setAttrib:function(e,t,n){var r=this;if(e&&t)return this.run(e,function(e){var i=r.settings,o=e.getAttribute(t);if(null!==n)switch(t){case"style":if(!d(n,"string"))return u(n,function(t,n){r.setStyle(e,n,t)}),void 0;i.keep_values&&(n?e.setAttribute("data-mce-style",n,2):e.removeAttribute("data-mce-style",2)),e.style.cssText=n;break;case"class":e.className=n||"";break;case"src":case"href":i.keep_values&&(i.url_converter&&(n=i.url_converter.call(i.url_converter_scope||r,n,t,e)),r.setAttrib(e,"data-mce-"+t,n,2));break;case"shape":e.setAttribute("data-mce-style",n)}d(n)&&null!==n&&0!==n.length?e.setAttribute(t,""+n,2):e.removeAttribute(t,2),o!=n&&i.onSetAttrib&&i.onSetAttrib({attrElm:e,attrName:t,attrValue:n})})},setAttribs:function(e,t){var n=this;return this.run(e,function(e){u(t,function(t,r){n.setAttrib(e,r,t)})})},getAttrib:function(e,t,n){var r,i=this,o;if(e=i.get(e),!e||1!==e.nodeType)return n===o?!1:n;if(d(n)||(n=""),/^(src|href|style|coords|shape)$/.test(t)&&(r=e.getAttribute("data-mce-"+t)))return r;if(g&&i.props[t]&&(r=e[i.props[t]],r=r&&r.nodeValue?r.nodeValue:r),r||(r=e.getAttribute(t,2)),/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(t))return e[i.props[t]]===!0&&""===r?t:r?t:"";if("FORM"===e.nodeName&&e.getAttributeNode(t))return e.getAttributeNode(t).nodeValue;if("style"===t&&(r=r||e.style.cssText,r&&(r=i.serializeStyle(i.parseStyle(r),e.nodeName),i.settings.keep_values&&e.setAttribute("data-mce-style",r))),m&&"class"===t&&r&&(r=r.replace(/(apple|webkit)\-[a-z\-]+/gi,"")),g)switch(t){case"rowspan":case"colspan":1===r&&(r="");break;case"size":("+0"===r||20===r||0===r)&&(r="");break;case"width":case"height":case"vspace":case"checked":case"disabled":case"readonly":0===r&&(r="");break;case"hspace":-1===r&&(r="");break;case"maxlength":case"tabindex":(32768===r||2147483647===r||"32768"===r)&&(r="");break;case"multiple":case"compact":case"noshade":case"nowrap":return 65535===r?t:n;case"shape":r=r.toLowerCase();break;default:0===t.indexOf("on")&&r&&(r=(""+r).replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/,"$1"))}return r!==o&&null!==r&&""!==r?""+r:n},getPos:function(e,t){var n=this,r=0,i=0,o,a=n.doc,s;if(e=n.get(e),t=t||a.body,e){if(t===a.body&&e.getBoundingClientRect)return s=e.getBoundingClientRect(),t=n.boxModel?a.documentElement:a.body,r=s.left+(a.documentElement.scrollLeft||a.body.scrollLeft)-t.clientTop,i=s.top+(a.documentElement.scrollTop||a.body.scrollTop)-t.clientLeft,{x:r,y:i};for(o=e;o&&o!=t&&o.nodeType;)r+=o.offsetLeft||0,i+=o.offsetTop||0,o=o.offsetParent;for(o=e.parentNode;o&&o!=t&&o.nodeType;)r-=o.scrollLeft||0,i-=o.scrollTop||0,o=o.parentNode}return{x:r,y:i}},parseStyle:function(e){return this.styles.parse(e)},serializeStyle:function(e,t){return this.styles.serialize(e,t)},addStyle:function(e){var t=this,n=t.doc,r,i;if(t!==c.DOM&&n===document){var o=c.DOM.addedStyles;if(o=o||[],o[e])return;o[e]=!0,c.DOM.addedStyles=o}i=n.getElementById("mceDefaultStyles"),i||(i=n.createElement("style"),i.id="mceDefaultStyles",i.type="text/css",r=n.getElementsByTagName("head")[0],r.firstChild?r.insertBefore(i,r.firstChild):r.appendChild(i)),i.styleSheet?i.styleSheet.cssText+=e:i.appendChild(n.createTextNode(e))},loadCSS:function(e){var t=this,n=t.doc,r;return t!==c.DOM&&n===document?(c.DOM.loadCSS(e),void 0):(e||(e=""),r=n.getElementsByTagName("head")[0],u(e.split(","),function(e){var i;t.files[e]||(t.files[e]=!0,i=t.create("link",{rel:"stylesheet",href:e}),g&&n.documentMode&&n.recalc&&(i.onload=function(){n.recalc&&n.recalc(),i.onload=null}),r.appendChild(i))}),void 0)},addClass:function(e,t){return this.run(e,function(e){var n;return t?this.hasClass(e,t)?e.className:(n=this.removeClass(e,t),e.className=n=(""!==n?n+" ":"")+t,n):0})},removeClass:function(e,t){var n=this,r;return n.run(e,function(e){var i;return n.hasClass(e,t)?(r||(r=new RegExp("(^|\\s+)"+t+"(\\s+|$)","g")),i=e.className.replace(r," "),i=p(" "!=i?i:""),e.className=i,i||(e.removeAttribute("class"),e.removeAttribute("className")),i):e.className})},hasClass:function(e,t){return e=this.get(e),e&&t?-1!==(" "+e.className+" ").indexOf(" "+t+" "):!1},toggleClass:function(e,n,r){r=r===t?!this.hasClass(e,n):r,this.hasClass(e,n)!==r&&(r?this.addClass(e,n):this.removeClass(e,n))},show:function(e){return this.setStyle(e,"display","block")},hide:function(e){return this.setStyle(e,"display","none")},isHidden:function(e){return e=this.get(e),!e||"none"==e.style.display||"none"==this.getStyle(e,"display")},uniqueId:function(e){return(e?e:"mce_")+this.counter++},setHTML:function(e,t){var n=this;return n.run(e,function(e){if(g){for(;e.firstChild;)e.removeChild(e.firstChild);try{e.innerHTML="
"+t,e.removeChild(e.firstChild)}catch(r){var i=n.create("div");i.innerHTML="
"+t,u(f(i.childNodes),function(t,n){n&&e.canHaveHTML&&e.appendChild(t)})}}else e.innerHTML=t;return t})},getOuterHTML:function(e){var t,n=this;return(e=n.get(e))?1===e.nodeType&&n.hasOuterHTML?e.outerHTML:(t=(e.ownerDocument||n.doc).createElement("body"),t.appendChild(e.cloneNode(!0)),t.innerHTML):null},setOuterHTML:function(e,t,n){var r=this;return r.run(e,function(e){function i(){var i,o;for(o=n.createElement("body"),o.innerHTML=t,i=o.lastChild;i;)r.insertAfter(i.cloneNode(!0),e),i=i.previousSibling;r.remove(e)}if(1==e.nodeType)if(n=n||e.ownerDocument||r.doc,g)try{1==e.nodeType&&r.hasOuterHTML?e.outerHTML=t:i()}catch(o){i()}else i()})},decode:a.decode,encode:a.encodeAllRaw,insertAfter:function(e,t){return t=this.get(t),this.run(e,function(e){var n,r;return n=t.parentNode,r=t.nextSibling,r?n.insertBefore(e,r):n.appendChild(e),e})},replace:function(e,t,n){var r=this;return r.run(t,function(t){return d(t,"array")&&(e=e.cloneNode(!0)),n&&u(f(t.childNodes),function(t){e.appendChild(t)}),t.parentNode.replaceChild(e,t)})},rename:function(e,t){var n=this,r;return e.nodeName!=t.toUpperCase()&&(r=n.create(t),u(n.getAttribs(e),function(t){n.setAttrib(r,t.nodeName,n.getAttrib(e,t.nodeName))}),n.replace(r,e,1)),r||e},findCommonAncestor:function(e,t){for(var n=e,r;n;){for(r=t;r&&n!=r;)r=r.parentNode;if(n==r)break;n=n.parentNode}return!n&&e.ownerDocument?e.ownerDocument.documentElement:n},toHex:function(e){return this.styles.toHex(l.trim(e))},run:function(e,t,n){var r=this,i;return"string"==typeof e&&(e=r.get(e)),e?(n=n||this,e.nodeType||!e.length&&0!==e.length?t.call(n,e):(i=[],u(e,function(e,o){e&&("string"==typeof e&&(e=r.get(e)),i.push(t.call(n,e,o)))}),i)):!1},getAttribs:function(e){var t;if(e=this.get(e),!e)return[];if(g){if(t=[],"OBJECT"==e.nodeName)return e.attributes;"OPTION"===e.nodeName&&this.getAttrib(e,"selected")&&t.push({specified:1,nodeName:"selected"});var n=/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi;return e.cloneNode(!1).outerHTML.replace(n,"").replace(/[\w:\-]+/gi,function(e){t.push({specified:1,nodeName:e})}),t}return e.attributes},isEmpty:function(e,t){var n=this,r,o,a,s,l,c=0;if(e=e.firstChild){s=new i(e,e.parentNode),t=t||n.schema?n.schema.getNonEmptyElements():null;do{if(a=e.nodeType,1===a){if(e.getAttribute("data-mce-bogus"))continue;if(l=e.nodeName.toLowerCase(),t&&t[l]){if("br"===l){c++;continue}return!1}for(o=n.getAttribs(e),r=e.attributes.length;r--;)if(l=e.attributes[r].nodeName,"name"===l||"data-mce-bookmark"===l)return!1}if(8==a)return!1;if(3===a&&!y.test(e.nodeValue))return!1}while(e=s.next())}return 1>=c},createRng:function(){var e=this.doc;return e.createRange?e.createRange():new o(this)},nodeIndex:function(e,t){var n=0,r,i,o;if(e)for(r=e.nodeType,e=e.previousSibling,i=e;e;e=e.previousSibling)o=e.nodeType,(!t||3!=o||o!=r&&e.nodeValue.length)&&(n++,r=o);return n},split:function(e,t,n){function r(e){function t(e){var t=e.previousSibling&&"SPAN"==e.previousSibling.nodeName,n=e.nextSibling&&"SPAN"==e.nextSibling.nodeName;return t&&n}var n,o=e.childNodes,a=e.nodeType;if(1!=a||"bookmark"!=e.getAttribute("data-mce-type")){for(n=o.length-1;n>=0;n--)r(o[n]);if(9!=a){if(3==a&&e.nodeValue.length>0){var s=p(e.nodeValue).length;if(!i.isBlock(e.parentNode)||s>0||0===s&&t(e))return}else if(1==a&&(o=e.childNodes,1==o.length&&o[0]&&1==o[0].nodeType&&"bookmark"==o[0].getAttribute("data-mce-type")&&e.parentNode.insertBefore(o[0],e),o.length||/^(br|hr|input|img)$/i.test(e.nodeName)))return;i.remove(e)}return e}}var i=this,o=i.createRng(),a,s,l;return e&&t?(o.setStart(e.parentNode,i.nodeIndex(e)),o.setEnd(t.parentNode,i.nodeIndex(t)),a=o.extractContents(),o=i.createRng(),o.setStart(t.parentNode,i.nodeIndex(t)+1),o.setEnd(e.parentNode,i.nodeIndex(e)+1),s=o.extractContents(),l=e.parentNode,l.insertBefore(r(a),e),n?l.replaceChild(n,t):l.insertBefore(t,e),l.insertBefore(r(s),e),i.remove(e),n||t):void 0},bind:function(e,t,n,r){var i=this;if(l.isArray(e)){for(var o=e.length;o--;)e[o]=i.bind(e[o],t,n,r);return e}return!i.settings.collect||e!==i.doc&&e!==i.win||i.boundEvents.push([e,t,n,r]),i.events.bind(e,t,n,r||i)},unbind:function(e,t,n){var r=this,i;if(l.isArray(e)){for(i=e.length;i--;)e[i]=r.unbind(e[i],t,n);return e}if(r.boundEvents&&(e===r.doc||e===r.win))for(i=r.boundEvents.length;i--;){var o=r.boundEvents[i];e!=o[0]||t&&t!=o[1]||n&&n!=o[2]||this.events.unbind(o[0],o[1],o[2])}return this.events.unbind(e,t,n)},fire:function(e,t,n){return this.events.fire(e,t,n)},getContentEditable:function(e){var t;return 1!=e.nodeType?null:(t=e.getAttribute("data-mce-contenteditable"),t&&"inherit"!==t?t:"inherit"!==e.contentEditable?e.contentEditable:null)},destroy:function(){var t=this;if(t.boundEvents){for(var n=t.boundEvents.length;n--;){var r=t.boundEvents[n];this.events.unbind(r[0],r[1],r[2])}t.boundEvents=null}e.setDocument&&e.setDocument(),t.win=t.doc=t.root=t.events=t.frag=null},dumpRng:function(e){return"startContainer: "+e.startContainer.nodeName+", startOffset: "+e.startOffset+", endContainer: "+e.endContainer.nodeName+", endOffset: "+e.endOffset},_findSib:function(e,t,n){var r=this,i=t;if(e)for("string"==typeof i&&(i=function(e){return r.is(e,t)}),e=e[n];e;e=e[n])if(i(e))return e;return null}},c.DOM=new c(document),c}),r(y,[v,p],function(e,t){function n(){function e(e,t){function n(){o.remove(s),a&&(a.onreadystatechange=a.onload=a=null),t()}function i(){"undefined"!=typeof console&&console.log&&console.log("Failed to load: "+e)}var o=r,a,s;s=o.uniqueId(),a=document.createElement("script"),a.id=s,a.type="text/javascript",a.src=e,"onreadystatechange"in a?a.onreadystatechange=function(){/loaded|complete/.test(a.readyState)&&n()}:a.onload=n,a.onerror=i,(document.getElementsByTagName("head")[0]||document.body).appendChild(a)}var t=0,n=1,a=2,s={},l=[],c={},u=[],d=0,f;this.isDone=function(e){return s[e]==a},this.markDone=function(e){s[e]=a},this.add=this.load=function(e,n,r){var i=s[e];i==f&&(l.push(e),s[e]=t),n&&(c[e]||(c[e]=[]),c[e].push({func:n,scope:r||this}))},this.loadQueue=function(e,t){this.loadScripts(l,e,t)},this.loadScripts=function(t,r,l){function p(e){i(c[e],function(e){e.func.call(e.scope)}),c[e]=f}var h;u.push({func:r,scope:l||this}),h=function(){var r=o(t);t.length=0,i(r,function(t){return s[t]==a?(p(t),void 0):(s[t]!=n&&(s[t]=n,d++,e(t,function(){s[t]=a,d--,p(t),h()})),void 0)}),d||(i(u,function(e){e.func.call(e.scope)}),u.length=0)},h()}}var r=e.DOM,i=t.each,o=t.grep;return n.ScriptLoader=new n,n}),r(b,[y,p],function(e,n){function r(){var e=this;e.items=[],e.urls={},e.lookup={}}var i=n.each;return r.prototype={get:function(e){return this.lookup[e]?this.lookup[e].instance:t -},dependencies:function(e){var t;return this.lookup[e]&&(t=this.lookup[e].dependencies),t||[]},requireLangPack:function(t,n){if(r.language&&r.languageLoad!==!1){if(n&&new RegExp("([, ]|\\b)"+r.language+"([, ]|\\b)").test(n)===!1)return;e.ScriptLoader.add(this.urls[t]+"/langs/"+r.language+".js")}},add:function(e,t,n){return this.items.push(t),this.lookup[e]={instance:t,dependencies:n},t},createUrl:function(e,t){return"object"==typeof t?t:{prefix:e.prefix,resource:t,suffix:e.suffix}},addComponents:function(t,n){var r=this.urls[t];i(n,function(t){e.ScriptLoader.add(r+"/"+t)})},load:function(n,o,a,s){function l(){var r=c.dependencies(n);i(r,function(e){var n=c.createUrl(o,e);c.load(n.resource,n,t,t)}),a&&(s?a.call(s):a.call(e))}var c=this,u=o;c.urls[n]||("object"==typeof o&&(u=o.prefix+o.resource+o.suffix),0!==u.indexOf("/")&&-1==u.indexOf("://")&&(u=r.baseURL+"/"+u),c.urls[n]=u.substring(0,u.lastIndexOf("/")),c.lookup[n]?l():e.ScriptLoader.add(u,l,s))}},r.PluginManager=new r,r.ThemeManager=new r,r}),r(C,[],function(){function e(e,t,n){var r,i,o=n?"lastChild":"firstChild",a=n?"prev":"next";if(e[o])return e[o];if(e!==t){if(r=e[a])return r;for(i=e.parent;i&&i!==t;i=i.parent)if(r=i[a])return r}}function t(e,t){this.name=e,this.type=t,1===t&&(this.attributes=[],this.attributes.map={})}var n=/^[ \t\r\n]*$/,r={"#text":3,"#comment":8,"#cdata":4,"#pi":7,"#doctype":10,"#document-fragment":11};return t.prototype={replace:function(e){var t=this;return e.parent&&e.remove(),t.insert(e,t),t.remove(),t},attr:function(e,t){var n=this,r,i,o;if("string"!=typeof e){for(i in e)n.attr(i,e[i]);return n}if(r=n.attributes){if(t!==o){if(null===t){if(e in r.map)for(delete r.map[e],i=r.length;i--;)if(r[i].name===e)return r=r.splice(i,1),n;return n}if(e in r.map){for(i=r.length;i--;)if(r[i].name===e){r[i].value=t;break}}else r.push({name:e,value:t});return r.map[e]=t,n}return r.map[e]}},clone:function(){var e=this,n=new t(e.name,e.type),r,i,o,a,s;if(o=e.attributes){for(s=[],s.map={},r=0,i=o.length;i>r;r++)a=o[r],"id"!==a.name&&(s[s.length]={name:a.name,value:a.value},s.map[a.name]=a.value);n.attributes=s}return n.value=e.value,n.shortEnded=e.shortEnded,n},wrap:function(e){var t=this;return t.parent.insert(e,t),e.append(t),t},unwrap:function(){var e=this,t,n;for(t=e.firstChild;t;)n=t.next,e.insert(t,e,!0),t=n;e.remove()},remove:function(){var e=this,t=e.parent,n=e.next,r=e.prev;return t&&(t.firstChild===e?(t.firstChild=n,n&&(n.prev=null)):r.next=n,t.lastChild===e?(t.lastChild=r,r&&(r.next=null)):n.prev=r,e.parent=e.next=e.prev=null),e},append:function(e){var t=this,n;return e.parent&&e.remove(),n=t.lastChild,n?(n.next=e,e.prev=n,t.lastChild=e):t.lastChild=t.firstChild=e,e.parent=t,e},insert:function(e,t,n){var r;return e.parent&&e.remove(),r=t.parent||this,n?(t===r.firstChild?r.firstChild=e:t.prev.next=e,e.prev=t.prev,e.next=t,t.prev=e):(t===r.lastChild?r.lastChild=e:t.next.prev=e,e.next=t.next,e.prev=t,t.next=e),e.parent=r,e},getAll:function(t){var n=this,r,i=[];for(r=n.firstChild;r;r=e(r,n))r.name===t&&i.push(r);return i},empty:function(){var t=this,n,r,i;if(t.firstChild){for(n=[],i=t.firstChild;i;i=e(i,t))n.push(i);for(r=n.length;r--;)i=n[r],i.parent=i.firstChild=i.lastChild=i.next=i.prev=null}return t.firstChild=t.lastChild=null,t},isEmpty:function(t){var r=this,i=r.firstChild,o,a;if(i)do{if(1===i.type){if(i.attributes.map["data-mce-bogus"])continue;if(t[i.name])return!1;for(o=i.attributes.length;o--;)if(a=i.attributes[o].name,"name"===a||0===a.indexOf("data-mce-"))return!1}if(8===i.type)return!1;if(3===i.type&&!n.test(i.value))return!1}while(i=e(i,r));return!0},walk:function(t){return e(this,null,t)}},t.create=function(e,n){var i,o;if(i=new t(e,r[e]||1),n)for(o in n)i.attr(o,n[o]);return i},t}),r(x,[p],function(e){function t(e,t){return e?e.split(t||" "):[]}function n(e){function n(e,n,r){function i(e){var t={},n,r;for(n=0,r=e.length;r>n;n++)t[e[n]]={};return t}var o,l,c,u=arguments;for(r=r||[],n=n||"","string"==typeof r&&(r=t(r)),l=3;lo;o++)i.attributes[n[o]]={},i.attributesOrder.push(n[o])}var a={},s,l,c,u,d,f,p;return r[e]?r[e]:(s=t("id accesskey class dir lang style tabindex title"),l=t("onabort onblur oncancel oncanplay oncanplaythrough onchange onclick onclose oncontextmenu oncuechange ondblclick ondrag ondragend ondragenter ondragleave ondragover ondragstart ondrop ondurationchange onemptied onended onerror onfocus oninput oninvalid onkeydown onkeypress onkeyup onload onloadeddata onloadedmetadata onloadstart onmousedown onmousemove onmouseout onmouseover onmouseup onmousewheel onpause onplay onplaying onprogress onratechange onreset onscroll onseeked onseeking onseeking onselect onshow onstalled onsubmit onsuspend ontimeupdate onvolumechange onwaiting"),c=t("address blockquote div dl fieldset form h1 h2 h3 h4 h5 h6 hr menu ol p pre table ul"),u=t("a abbr b bdo br button cite code del dfn em embed i iframe img input ins kbd label map noscript object q s samp script select small span strong sub sup textarea u var #text #comment"),"html4"!=e&&(s.push.apply(s,t("contenteditable contextmenu draggable dropzone hidden spellcheck translate")),c.push.apply(c,t("article aside details dialog figure header footer hgroup section nav")),u.push.apply(u,t("audio canvas command datalist mark meter output progress time wbr video ruby bdi keygen"))),"html5-strict"!=e&&(s.push("xml:lang"),p=t("acronym applet basefont big font strike tt"),u.push.apply(u,p),o(p,function(e){n(e,"",u)}),f=t("center dir isindex noframes"),c.push.apply(c,f),d=[].concat(c,u),o(f,function(e){n(e,"",d)})),d=d||[].concat(c,u),n("html","manifest","head body"),n("head","","base command link meta noscript script style title"),n("title hr noscript br"),n("base","href target"),n("link","href rel media hreflang type sizes hreflang"),n("meta","name http-equiv content charset"),n("style","media type scoped"),n("script","src async defer type charset"),n("body","onafterprint onbeforeprint onbeforeunload onblur onerror onfocus onhashchange onload onmessage onoffline ononline onpagehide onpageshow onpopstate onresize onscroll onstorage onunload",d),n("address dt dd div caption","",d),n("h1 h2 h3 h4 h5 h6 pre p abbr code var samp kbd sub sup i b u bdo span legend em strong small s cite dfn","",u),n("blockquote","cite",d),n("ol","reversed start type","li"),n("ul","","li"),n("li","value",d),n("dl","","dt dd"),n("a","href target rel media hreflang type",u),n("q","cite",u),n("ins del","cite datetime",d),n("img","src alt usemap ismap width height"),n("iframe","src name width height",d),n("embed","src type width height"),n("object","data type typemustmatch name usemap form width height",d,"param"),n("param","name value"),n("map","name",d,"area"),n("area","alt coords shape href target rel media hreflang type"),n("table","border","caption colgroup thead tfoot tbody tr"+("html4"==e?" col":"")),n("colgroup","span","col"),n("col","span"),n("tbody thead tfoot","","tr"),n("tr","","td th"),n("td","colspan rowspan headers",d),n("th","colspan rowspan headers scope abbr",d),n("form","accept-charset action autocomplete enctype method name novalidate target",d),n("fieldset","disabled form name",d,"legend"),n("label","form for",u),n("input","accept alt autocomplete checked dirname disabled form formaction formenctype formmethod formnovalidate formtarget height list max maxlength min multiple name pattern readonly required size src step type value width"),n("button","disabled form formaction formenctype formmethod formnovalidate formtarget name type value","html4"==e?d:u),n("select","disabled form multiple name required size","option optgroup"),n("optgroup","disabled label","option"),n("option","disabled label selected value"),n("textarea","cols dirname disabled form maxlength name readonly required rows wrap"),n("menu","type label",d,"li"),n("noscript","",d),"html4"!=e&&(n("wbr"),n("ruby","",u,"rt rp"),n("figcaption","",d),n("mark rt rp summary bdi","",u),n("canvas","width height",d),n("video","src crossorigin poster preload autoplay mediagroup loop muted controls width height",d,"track source"),n("audio","src crossorigin preload autoplay mediagroup loop muted controls",d,"track source"),n("source","src type media"),n("track","kind src srclang label default"),n("datalist","",u,"option"),n("article section nav aside header footer","",d),n("hgroup","","h1 h2 h3 h4 h5 h6"),n("figure","",d,"figcaption"),n("time","datetime",u),n("dialog","open",d),n("command","type label icon disabled checked radiogroup command"),n("output","for form name",u),n("progress","value max",u),n("meter","value min max low high optimum",u),n("details","open",d,"summary"),n("keygen","autofocus challenge disabled form keytype name")),"html5-strict"!=e&&(i("script","language xml:space"),i("style","xml:space"),i("object","declare classid codebase codetype archive standby align border hspace vspace"),i("param","valuetype type"),i("a","charset name rev shape coords"),i("br","clear"),i("applet","codebase archive code object alt name width height align hspace vspace"),i("img","name longdesc align border hspace vspace"),i("iframe","longdesc frameborder marginwidth marginheight scrolling align"),i("font basefont","size color face"),i("input","usemap align"),i("select","onchange"),i("textarea"),i("h1 h2 h3 h4 h5 h6 div p legend caption","align"),i("ul","type compact"),i("li","type"),i("ol dl menu dir","compact"),i("pre","width xml:space"),i("hr","align noshade size width"),i("isindex","prompt"),i("table","summary width frame rules cellspacing cellpadding align bgcolor"),i("col","width align char charoff valign"),i("colgroup","width align char charoff valign"),i("thead","align char charoff valign"),i("tr","align char charoff valign bgcolor"),i("th","axis align char charoff valign nowrap bgcolor width height"),i("form","accept"),i("td","abbr axis scope align char charoff valign nowrap bgcolor width height"),i("tfoot","align char charoff valign"),i("tbody","align char charoff valign"),i("area","nohref"),i("body","background bgcolor text link vlink alink")),"html4"!=e&&(i("input button select textarea","autofocus"),i("input textarea","placeholder"),i("a","download"),i("link script img","crossorigin"),i("iframe","srcdoc sandbox seamless allowfullscreen")),o(t("a form meter progress dfn"),function(e){a[e]&&delete a[e].children[e]}),delete a.caption.children.table,r[e]=a,a)}var r={},i=e.makeMap,o=e.each,a=e.extend,s=e.explode,l=e.inArray;return function(e){function c(t,n,o){var s=e[t];return s?s=i(s,",",i(s.toUpperCase()," ")):(s=r[t],s||(s=i(n," ",i(n.toUpperCase()," ")),s=a(s,o),r[t]=s)),s}function u(e){return new RegExp("^"+e.replace(/([?+*])/g,".$1")+"$")}function d(e){var n,r,o,a,s,c,d,f,p,h,m,g,y,C,x,w,_,N,E,k=/^([#+\-])?([^\[!\/]+)(?:\/([^\[!]+))?(?:(!?)\[([^\]]+)\])?$/,S=/^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/,T=/[*?+]/;if(e)for(e=t(e,","),v["@"]&&(w=v["@"].attributes,_=v["@"].attributesOrder),n=0,r=e.length;r>n;n++)if(s=k.exec(e[n])){if(C=s[1],p=s[2],x=s[3],f=s[5],g={},y=[],c={attributes:g,attributesOrder:y},"#"===C&&(c.paddEmpty=!0),"-"===C&&(c.removeEmpty=!0),"!"===s[4]&&(c.removeEmptyAttrs=!0),w){for(N in w)g[N]=w[N];y.push.apply(y,_)}if(f)for(f=t(f,"|"),o=0,a=f.length;a>o;o++)if(s=S.exec(f[o])){if(d={},m=s[1],h=s[2].replace(/::/g,":"),C=s[3],E=s[4],"!"===m&&(c.attributesRequired=c.attributesRequired||[],c.attributesRequired.push(h),d.required=!0),"-"===m){delete g[h],y.splice(l(y,h),1);continue}C&&("="===C&&(c.attributesDefault=c.attributesDefault||[],c.attributesDefault.push({name:h,value:E}),d.defaultValue=E),":"===C&&(c.attributesForced=c.attributesForced||[],c.attributesForced.push({name:h,value:E}),d.forcedValue=E),"<"===C&&(d.validValues=i(E,"?"))),T.test(h)?(c.attributePatterns=c.attributePatterns||[],d.pattern=u(h),c.attributePatterns.push(d)):(g[h]||y.push(h),g[h]=d)}w||"@"!=p||(w=g,_=y),x&&(c.outputName=p,v[x]=c),T.test(p)?(c.pattern=u(p),b.push(c)):v[p]=c}}function f(e){v={},b=[],d(e),o(x,function(e,t){y[t]=e.children})}function p(e){var n=/^(~)?(.+)$/;e&&o(t(e,","),function(e){var t=n.exec(e),r="~"===t[1],i=r?"span":"div",s=t[2];if(y[s]=y[i],R[s]=i,r||(k[s.toUpperCase()]={},k[s]={}),!v[s]){var l=v[i];l=a({},l),delete l.removeEmptyAttrs,delete l.removeEmpty,v[s]=l}o(y,function(e){e[i]&&(e[s]=e[i])})})}function h(e){var n=/^([+\-]?)(\w+)\[([^\]]+)\]$/;e&&o(t(e,","),function(e){var r=n.exec(e),i,a;r&&(a=r[1],i=a?y[r[2]]:y[r[2]]={"#comment":{}},i=y[r[2]],o(t(r[3],"|"),function(e){"-"===a?delete i[e]:i[e]={}}))})}function m(e){var t=v[e],n;if(t)return t;for(n=b.length;n--;)if(t=b[n],t.pattern.test(e))return t}var g=this,v={},y={},b=[],C,x,w,_,N,E,k,S,T,R={},A={};e=e||{},x=n(e.schema),e.verify_html===!1&&(e.valid_elements="*[*]"),e.valid_styles&&(C={},o(e.valid_styles,function(e,t){C[t]=s(e)})),w=c("whitespace_elements","pre script noscript style textarea video audio iframe object"),_=c("self_closing_elements","colgroup dd dt li option p td tfoot th thead tr"),N=c("short_ended_elements","area base basefont br col frame hr img input isindex link meta param embed source wbr track"),E=c("boolean_attributes","checked compact declare defer disabled ismap multiple nohref noresize noshade nowrap readonly selected autoplay loop controls"),S=c("non_empty_elements","td th iframe video audio object",N),T=c("text_block_elements","h1 h2 h3 h4 h5 h6 p div address pre form blockquote center dir fieldset header footer article section hgroup aside nav figure"),k=c("block_elements","hr table tbody thead tfoot th tr td li ol ul caption dl dt dd noscript menu isindex samp option datalist select optgroup",T),o((e.special||"script noscript style textarea").split(" "),function(e){A[e]=new RegExp("]*>","gi")}),e.valid_elements?f(e.valid_elements):(o(x,function(e,t){v[t]={attributes:e.attributes,attributesOrder:e.attributesOrder},y[t]=e.children}),"html5"!=e.schema&&o(t("strong/b em/i"),function(e){e=t(e,"/"),v[e[1]].outputName=e[0]}),v.img.attributesDefault=[{name:"alt",value:""}],o(t("ol ul sub sup blockquote span font a table tbody tr strong em b i"),function(e){v[e]&&(v[e].removeEmpty=!0)}),o(t("p h1 h2 h3 h4 h5 h6 th td pre div address caption"),function(e){v[e].paddEmpty=!0}),o(t("span"),function(e){v[e].removeEmptyAttrs=!0})),p(e.custom_elements),h(e.valid_children),d(e.extended_valid_elements),h("+ol[ul|ol],+ul[ul|ol]"),e.invalid_elements&&o(s(e.invalid_elements),function(e){v[e]&&delete v[e]}),m("span")||d("span[!data-mce-type|*]"),g.children=y,g.styles=C,g.getBoolAttrs=function(){return E},g.getBlockElements=function(){return k},g.getTextBlockElements=function(){return T},g.getShortEndedElements=function(){return N},g.getSelfClosingElements=function(){return _},g.getNonEmptyElements=function(){return S},g.getWhiteSpaceElements=function(){return w},g.getSpecialElements=function(){return A},g.isValidChild=function(e,t){var n=y[e];return!(!n||!n[t])},g.isValid=function(e,t){var n,r,i=m(e);if(i){if(!t)return!0;if(i.attributes[t])return!0;if(n=i.attributePatterns)for(r=n.length;r--;)if(n[r].pattern.test(e))return!0}return!1},g.getElementRule=m,g.getCustomElements=function(){return R},g.addValidElements=d,g.setValidElements=f,g.addCustomElements=p,g.addValidChildren=h,g.elements=v}}),r(w,[x,m,p],function(e,t,n){var r=n.each;return function(i,o){var a=this,s=function(){};i=i||{},a.schema=o=o||new e,i.fix_self_closing!==!1&&(i.fix_self_closing=!0),r("comment cdata text start end pi doctype".split(" "),function(e){e&&(a[e]=i[e]||s)}),a.parse=function(e){function r(e){var t,n;for(t=f.length;t--&&f[t].name!==e;);if(t>=0){for(n=f.length-1;n>=t;n--)e=f[n],e.valid&&s.end(e.name);f.length=t}}function a(e,t,n,r,o){var a,s,l=/[\s\u0000-\u001F]+/g;if(t=t.toLowerCase(),n=t in C?t:F(n||r||o||""),w&&!v&&0!==t.indexOf("data-")){if(a=S[t],!a&&T){for(s=T.length;s--&&(a=T[s],!a.pattern.test(t)););-1===s&&(a=null)}if(!a)return;if(a.validValues&&!(n in a.validValues))return}if(W[t]&&!i.allow_script_urls){var c=n.replace(l,"");try{if(c=decodeURIComponent(c),V.test(c))return}catch(u){if(c=unescape(c),V.test(c))return}}p.map[t]=n,p.push({name:t,value:n})}var s=this,l,c=0,u,d,f=[],p,h,m,g,v,y,b,C,x,w,_,N,E,k,S,T,R,A,B,L,H,D,M,P,O,I=0,F=t.decode,z,W=n.makeMap("src,href"),V=/(java|vb)script:/i;for(D=new RegExp("<(?:(?:!--([\\w\\W]*?)-->)|(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|(?:!DOCTYPE([\\w\\W]*?)>)|(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|(?:\\/([^>]+)>)|(?:([A-Za-z0-9\\-\\:\\.]+)((?:\\s+[^\"'>]+(?:(?:\"[^\"]*\")|(?:'[^']*')|[^>]*))*|\\/|\\s+)>))","g"),M=/([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:[^\"])*)\")|(?:\'((?:[^\'])*)\')|([^>\s]+)))?/g,b=o.getShortEndedElements(),H=i.self_closing_elements||o.getSelfClosingElements(),C=o.getBoolAttrs(),w=i.validate,y=i.remove_internals,z=i.fix_self_closing,P=o.getSpecialElements();l=D.exec(e);){if(c0&&f[f.length-1].name===u&&r(u),!w||(_=o.getElementRule(u))){if(N=!0,w&&(S=_.attributes,T=_.attributePatterns),(k=l[8])?(v=-1!==k.indexOf("data-mce-type"),v&&y&&(N=!1),p=[],p.map={},k.replace(M,a)):(p=[],p.map={}),w&&!v){if(R=_.attributesRequired,A=_.attributesDefault,B=_.attributesForced,L=_.removeEmptyAttrs,L&&!p.length&&(N=!1),B)for(h=B.length;h--;)E=B[h],g=E.name,O=E.value,"{$uid}"===O&&(O="mce_"+I++),p.map[g]=O,p.push({name:g,value:O});if(A)for(h=A.length;h--;)E=A[h],g=E.name,g in p.map||(O=E.value,"{$uid}"===O&&(O="mce_"+I++),p.map[g]=O,p.push({name:g,value:O}));if(R){for(h=R.length;h--&&!(R[h]in p.map););-1===h&&(N=!1)}p.map["data-mce-bogus"]&&(N=!1)}N&&s.start(u,p,x)}else N=!1;if(d=P[u]){d.lastIndex=c=l.index+l[0].length,(l=d.exec(e))?(N&&(m=e.substr(c,l.index-c)),c=l.index+l[0].length):(m=e.substr(c),c=e.length),N&&(m.length>0&&s.text(m,!0),s.end(u)),D.lastIndex=c;continue}x||(k&&k.indexOf("/")==k.length-1?N&&s.end(u):f.push({name:u,valid:N}))}else(u=l[1])?(">"===u.charAt(0)&&(u=" "+u),i.allow_conditional_comments||"[if"!==u.substr(0,3)||(u=" "+u),s.comment(u)):(u=l[2])?s.cdata(u):(u=l[3])?s.doctype(u):(u=l[4])&&s.pi(u,l[5]);c=l.index+l[0].length}for(c=0;h--)u=f[h],u.valid&&s.end(u.name)}}}),r(_,[C,x,w,p],function(e,t,n,r){var i=r.makeMap,o=r.each,a=r.explode,s=r.extend;return function(r,l){function c(t){var n,r,o,a,s,c,d,f,p,h,m,g,v,y;for(m=i("tr,td,th,tbody,thead,tfoot,table"),h=l.getNonEmptyElements(),g=l.getTextBlockElements(),n=0;n1){for(a.reverse(),s=c=u.filterNode(a[0].clone()),p=0;p0?(t.value=n,t=t.prev):(r=t.prev,t.remove(),t=r)}function g(e){var t,n={};for(t in e)"li"!==t&&"p"!=t&&(n[t]=e[t]);return n}var v,y,b,C,x,w,_,N,E,k,S,T,R,A=[],B,L,H,D,M,P,O,I;if(o=o||{},p={},h={},T=s(i("script,style,head,html,body,title,meta,param"),l.getBlockElements()),O=l.getNonEmptyElements(),P=l.children,S=r.validate,I="forced_root_block"in o?o.forced_root_block:r.forced_root_block,M=l.getWhiteSpaceElements(),R=/^[ \t\r\n]+/,L=/[ \t\r\n]+$/,H=/[ \t\r\n]+/g,D=/^[ \t\r\n]+$/,v=new n({validate:S,allow_script_urls:r.allow_script_urls,allow_conditional_comments:r.allow_conditional_comments,self_closing_elements:g(l.getSelfClosingElements()),cdata:function(e){b.append(u("#cdata",4)).value=e},text:function(e,t){var n;B||(e=e.replace(H," "),b.lastChild&&T[b.lastChild.name]&&(e=e.replace(R,""))),0!==e.length&&(n=u("#text",3),n.raw=!!t,b.append(n).value=e)},comment:function(e){b.append(u("#comment",8)).value=e},pi:function(e,t){b.append(u(e,7)).value=t,m(b)},doctype:function(e){var t;t=b.append(u("#doctype",10)),t.value=e,m(b)},start:function(e,t,n){var r,i,o,a,s;if(o=S?l.getElementRule(e):{}){for(r=u(o.outputName||e,1),r.attributes=t,r.shortEnded=n,b.append(r),s=P[b.name],s&&P[r.name]&&!s[r.name]&&A.push(r),i=f.length;i--;)a=f[i].name,a in t.map&&(E=h[a],E?E.push(r):h[a]=[r]);T[e]&&m(r),n||(b=r),!B&&M[e]&&(B=!0)}},end:function(t){var n,r,i,o,a;if(r=S?l.getElementRule(t):{}){if(T[t]&&!B){if(n=b.firstChild,n&&3===n.type)if(i=n.value.replace(R,""),i.length>0)n.value=i,n=n.next;else for(o=n.next,n.remove(),n=o;n&&3===n.type;)i=n.value,o=n.next,(0===i.length||D.test(i))&&(n.remove(),n=o),n=o;if(n=b.lastChild,n&&3===n.type)if(i=n.value.replace(L,""),i.length>0)n.value=i,n=n.prev;else for(o=n.prev,n.remove(),n=o;n&&3===n.type;)i=n.value,o=n.prev,(0===i.length||D.test(i))&&(n.remove(),n=o),n=o}if(B&&M[t]&&(B=!1),(r.removeEmpty||r.paddEmpty)&&b.isEmpty(O))if(r.paddEmpty)b.empty().append(new e("#text","3")).value="\xa0";else if(!b.attributes.map.name&&!b.attributes.map.id)return a=b.parent,b.empty().remove(),b=a,void 0;b=b.parent}}},l),y=b=new e(o.context||r.root_name,11),v.parse(t),S&&A.length&&(o.context?o.invalid=!0:c(A)),I&&("body"==y.name||o.isRootContent)&&a(),!o.invalid){for(k in p){for(E=d[k],C=p[k],_=C.length;_--;)C[_].parent||C.splice(_,1);for(x=0,w=E.length;w>x;x++)E[x](C,k,o)}for(x=0,w=f.length;w>x;x++)if(E=f[x],E.name in h){for(C=h[E.name],_=C.length;_--;)C[_].parent||C.splice(_,1);for(_=0,N=E.callbacks.length;N>_;_++)E.callbacks[_](C,E.name,o)}}return y},r.remove_trailing_brs&&u.addNodeFilter("br",function(t){var n,r=t.length,i,o=s({},l.getBlockElements()),a=l.getNonEmptyElements(),c,u,d,f,p,h;for(o.body=1,n=0;r>n;n++)if(i=t[n],c=i.parent,o[i.parent.name]&&i===c.lastChild){for(d=i.prev;d;){if(f=d.name,"span"!==f||"bookmark"!==d.attr("data-mce-type")){if("br"!==f)break;if("br"===f){i=null;break}}d=d.prev}i&&(i.remove(),c.isEmpty(a)&&(p=l.getElementRule(c.name),p&&(p.removeEmpty?c.remove():p.paddEmpty&&(c.empty().append(new e("#text",3)).value="\xa0"))))}else{for(u=i;c&&c.firstChild===u&&c.lastChild===u&&(u=c,!o[c.name]);)c=c.parent;u===c&&(h=new e("#text",3),h.value="\xa0",i.replace(h))}}),r.allow_html_in_named_anchor||u.addAttributeFilter("id,name",function(e){for(var t=e.length,n,r,i,o;t--;)if(o=e[t],"a"===o.name&&o.firstChild&&!o.attr("href")){i=o.parent,n=o.lastChild;do r=n.prev,i.insert(n,o),n=r;while(n)}})}}),r(N,[m,p],function(e,t){var n=t.makeMap;return function(t){var r=[],i,o,a,s,l;return t=t||{},i=t.indent,o=n(t.indent_before||""),a=n(t.indent_after||""),s=e.getEncodeFunc(t.entity_encoding||"raw",t.entities),l="html"==t.element_format,{start:function(e,t,n){var c,u,d,f;if(i&&o[e]&&r.length>0&&(f=r[r.length-1],f.length>0&&"\n"!==f&&r.push("\n")),r.push("<",e),t)for(c=0,u=t.length;u>c;c++)d=t[c],r.push(" ",d.name,'="',s(d.value,!0),'"');r[r.length]=!n||l?">":" />",n&&i&&a[e]&&r.length>0&&(f=r[r.length-1],f.length>0&&"\n"!==f&&r.push("\n"))},end:function(e){var t;r.push(""),i&&a[e]&&r.length>0&&(t=r[r.length-1],t.length>0&&"\n"!==t&&r.push("\n"))},text:function(e,t){e.length>0&&(r[r.length]=t?e:s(e))},cdata:function(e){r.push("")},comment:function(e){r.push("")},pi:function(e,t){t?r.push(""):r.push(""),i&&r.push("\n")},doctype:function(e){r.push("",i?"\n":"")},reset:function(){r.length=0},getContent:function(){return r.join("").replace(/\n$/,"")}}}}),r(E,[N,x],function(e,t){return function(n,r){var i=this,o=new e(n);n=n||{},n.validate="validate"in n?n.validate:!0,i.schema=r=r||new t,i.writer=o,i.serialize=function(e){function t(e){var n=i[e.type],s,l,c,u,d,f,p,h,m;if(n)n(e);else{if(s=e.name,l=e.shortEnded,c=e.attributes,a&&c&&c.length>1){for(f=[],f.map={},m=r.getElementRule(e.name),p=0,h=m.attributesOrder.length;h>p;p++)u=m.attributesOrder[p],u in c.map&&(d=c.map[u],f.map[u]=d,f.push({name:u,value:d}));for(p=0,h=c.length;h>p;p++)u=c[p].name,u in f.map||(d=c.map[u],f.map[u]=d,f.push({name:u,value:d}));c=f}if(o.start(e.name,c,l),!l){if(e=e.firstChild)do t(e);while(e=e.next);o.end(s)}}}var i,a;return a=n.validate,i={3:function(e){o.text(e.value,e.raw)},8:function(e){o.comment(e.value)},7:function(e){o.pi(e.name,e.value)},10:function(e){o.doctype(e.value)},4:function(e){o.cdata(e.value)},11:function(e){if(e=e.firstChild)do t(e);while(e=e.next)}},o.reset(),1!=e.type||n.inner?i[11](e):t(e),o.getContent()}}}),r(k,[v,_,m,E,C,x,g,p],function(e,t,n,r,i,o,a,s){var l=s.each,c=s.trim,u=e.DOM;return function(e,i){var s,d,f;return i&&(s=i.dom,d=i.schema),s=s||u,d=d||new o(e),e.entity_encoding=e.entity_encoding||"named",e.remove_trailing_brs="remove_trailing_brs"in e?e.remove_trailing_brs:!0,f=new t(e,d),f.addAttributeFilter("src,href,style",function(t,n){for(var r=t.length,i,o,a="data-mce-"+n,l=e.url_converter,c=e.url_converter_scope,u;r--;)i=t[r],o=i.attributes.map[a],o!==u?(i.attr(n,o.length>0?o:null),i.attr(a,null)):(o=i.attributes.map[n],"style"===n?o=s.serializeStyle(s.parseStyle(o),i.name):l&&(o=l.call(c,o,n,i.name)),i.attr(n,o.length>0?o:null))}),f.addAttributeFilter("class",function(e){for(var t=e.length,n,r;t--;)n=e[t],r=n.attr("class").replace(/(?:^|\s)mce-item-\w+(?!\S)/g,""),n.attr("class",r.length>0?r:null)}),f.addAttributeFilter("data-mce-type",function(e,t,n){for(var r=e.length,i;r--;)i=e[r],"bookmark"!==i.attributes.map["data-mce-type"]||n.cleanup||i.remove()}),f.addAttributeFilter("data-mce-expando",function(e,t){for(var n=e.length;n--;)e[n].attr(t,null)}),f.addNodeFilter("noscript",function(e){for(var t=e.length,r;t--;)r=e[t].firstChild,r&&(r.value=n.decode(r.value))}),f.addNodeFilter("script,style",function(e,t){function n(e){return e.replace(/()/g,"\n").replace(/^[\r\n]*|[\r\n]*$/g,"").replace(/^\s*(()?|\s*\/\/\s*\]\]>(-->)?|\/\/\s*(-->)?|\]\]>|\/\*\s*-->\s*\*\/|\s*-->\s*)\s*$/g,"")}for(var r=e.length,i,o;r--;)if(i=e[r],o=i.firstChild?i.firstChild.value:"","script"===t){var a=(i.attr("type")||"text/javascript").replace(/^mce\-/,"");i.attr("type","text/javascript"===a?null:a),o.length>0&&(i.firstChild.value="// ")}else o.length>0&&(i.firstChild.value="")}),f.addNodeFilter("#comment",function(e){for(var t=e.length,n;t--;)n=e[t],0===n.value.indexOf("[CDATA[")?(n.name="#cdata",n.type=4,n.value=n.value.replace(/^\[CDATA\[|\]\]$/g,"")):0===n.value.indexOf("mce:protected ")&&(n.name="#text",n.type=3,n.raw=!0,n.value=unescape(n.value).substr(14))}),f.addNodeFilter("xml:namespace,input",function(e,t){for(var n=e.length,r;n--;)r=e[n],7===r.type?r.remove():1===r.type&&("input"!==t||"type"in r.attributes.map||r.attr("type","text"))}),e.fix_list_elements&&f.addNodeFilter("ul,ol",function(e){for(var t=e.length,n,r;t--;)n=e[t],r=n.parent,("ul"===r.name||"ol"===r.name)&&n.prev&&"li"===n.prev.name&&n.prev.append(n)}),f.addAttributeFilter("data-mce-src,data-mce-href,data-mce-style,data-mce-selected",function(e,t){for(var n=e.length;n--;)e[n].attr(t,null)}),{schema:d,addNodeFilter:f.addNodeFilter,addAttributeFilter:f.addAttributeFilter,serialize:function(t,n){var i=this,o,u,p,h,m;return a.ie&&s.select("script,style,select,map").length>0?(m=t.innerHTML,t=t.cloneNode(!1),s.setHTML(t,m)):t=t.cloneNode(!0),o=t.ownerDocument.implementation,o.createHTMLDocument&&(u=o.createHTMLDocument(""),l("BODY"==t.nodeName?t.childNodes:[t],function(e){u.body.appendChild(u.importNode(e,!0))}),t="BODY"!=t.nodeName?u.body.firstChild:u.body,p=s.doc,s.doc=u),n=n||{},n.format=n.format||"html",n.selection&&(n.forced_root_block=""),n.no_events||(n.node=t,i.onPreProcess(n)),h=new r(e,d),n.content=h.serialize(f.parse(c(n.getInner?t.innerHTML:s.getOuterHTML(t)),n)),n.cleanup||(n.content=n.content.replace(/\uFEFF/g,"")),n.no_events||i.onPostProcess(n),p&&(s.doc=p),n.node=null,n.content},addRules:function(e){d.addValidElements(e)},setRules:function(e){d.setValidElements(e)},onPreProcess:function(e){i&&i.fire("PreProcess",e)},onPostProcess:function(e){i&&i.fire("PostProcess",e)}}}}),r(S,[],function(){function e(e){function t(t,n){var r,i=0,o,a,s,l,c,u,d=-1,f;if(r=t.duplicate(),r.collapse(n),f=r.parentElement(),f.ownerDocument===e.dom.doc){for(;"false"===f.contentEditable;)f=f.parentNode;if(!f.hasChildNodes())return{node:f,inside:1};for(s=f.children,o=s.length-1;o>=i;)if(u=Math.floor((i+o)/2),l=s[u],r.moveToElementText(l),d=r.compareEndPoints(n?"StartToStart":"EndToEnd",t),d>0)o=u-1;else{if(!(0>d))return{node:l};i=u+1}if(0>d)for(l?r.collapse(!1):(r.moveToElementText(f),r.collapse(!0),l=f,a=!0),c=0;0!==r.compareEndPoints(n?"StartToStart":"StartToEnd",t)&&0!==r.move("character",1)&&f==r.parentElement();)c++;else for(r.collapse(!0),c=0;0!==r.compareEndPoints(n?"StartToStart":"StartToEnd",t)&&0!==r.move("character",-1)&&f==r.parentElement();)c++;return{node:l,position:d,offset:c,inside:a}}}function n(){function n(e){var n=t(o,e),r,i,s=0,l,c,u;if(r=n.node,i=n.offset,n.inside&&!r.hasChildNodes())return a[e?"setStart":"setEnd"](r,0),void 0;if(i===c)return a[e?"setStartBefore":"setEndAfter"](r),void 0;if(n.position<0){if(l=n.inside?r.firstChild:r.nextSibling,!l)return a[e?"setStartAfter":"setEndAfter"](r),void 0;if(!i)return 3==l.nodeType?a[e?"setStart":"setEnd"](l,0):a[e?"setStartBefore":"setEndBefore"](l),void 0;for(;l;){if(u=l.nodeValue,s+=u.length,s>=i){r=l,s-=i,s=u.length-s;break}l=l.nextSibling}}else{if(l=r.previousSibling,!l)return a[e?"setStartBefore":"setEndBefore"](r);if(!i)return 3==r.nodeType?a[e?"setStart":"setEnd"](l,r.nodeValue.length):a[e?"setStartAfter":"setEndAfter"](l),void 0;for(;l;){if(s+=l.nodeValue.length,s>=i){r=l,s-=i;break}l=l.previousSibling}}a[e?"setStart":"setEnd"](r,s)}var o=e.getRng(),a=i.createRng(),s,l,c,u,d;if(s=o.item?o.item(0):o.parentElement(),s.ownerDocument!=i.doc)return a;if(l=e.isCollapsed(),o.item)return a.setStart(s.parentNode,i.nodeIndex(s)),a.setEnd(a.startContainer,a.startOffset+1),a;try{n(!0),l||n()}catch(f){if(-2147024809!=f.number)throw f;d=r.getBookmark(2),c=o.duplicate(),c.collapse(!0),s=c.parentElement(),l||(c=o.duplicate(),c.collapse(!1),u=c.parentElement(),u.innerHTML=u.innerHTML),s.innerHTML=s.innerHTML,r.moveToBookmark(d),o=e.getRng(),n(!0),l||n() -}return a}var r=this,i=e.dom,o=!1;this.getBookmark=function(n){function r(e){var t,n,r,o,a=[];for(t=e.parentNode,n=i.getRoot().parentNode;t!=n&&9!==t.nodeType;){for(r=t.children,o=r.length;o--;)if(e===r[o]){a.push(o);break}e=t,t=t.parentNode}return a}function o(e){var n;return n=t(a,e),n?{position:n.position,offset:n.offset,indexes:r(n.node),inside:n.inside}:void 0}var a=e.getRng(),s={};return 2===n&&(a.item?s.start={ctrl:!0,indexes:r(a.item(0))}:(s.start=o(!0),e.isCollapsed()||(s.end=o()))),s},this.moveToBookmark=function(e){function t(e){var t,n,r,o;for(t=i.getRoot(),n=e.length-1;n>=0;n--)o=t.children,r=e[n],r<=o.length-1&&(t=o[r]);return t}function n(n){var i=e[n?"start":"end"],a,s,l,c;i&&(a=i.position>0,s=o.createTextRange(),s.moveToElementText(t(i.indexes)),c=i.offset,c!==l?(s.collapse(i.inside||a),s.moveStart("character",a?-c:c)):s.collapse(n),r.setEndPoint(n?"StartToStart":"EndToStart",s),n&&r.collapse(!0))}var r,o=i.doc.body;e.start&&(e.start.ctrl?(r=o.createControlRange(),r.addElement(t(e.start.indexes)),r.select()):(r=o.createTextRange(),n(!0),n(),r.select()))},this.addRange=function(t){function n(e){var t,n,a,d,h;a=i.create("a"),t=e?s:c,n=e?l:u,d=r.duplicate(),(t==f||t==f.documentElement)&&(t=p,n=0),3==t.nodeType?(t.parentNode.insertBefore(a,t),d.moveToElementText(a),d.moveStart("character",n),i.remove(a),r.setEndPoint(e?"StartToStart":"EndToEnd",d)):(h=t.childNodes,h.length?(n>=h.length?i.insertAfter(a,h[h.length-1]):t.insertBefore(a,h[n]),d.moveToElementText(a)):t.canHaveHTML&&(t.innerHTML="",a=t.firstChild,d.moveToElementText(a),d.collapse(o)),r.setEndPoint(e?"StartToStart":"EndToEnd",d),i.remove(a))}var r,a,s,l,c,u,d,f=e.dom.doc,p=f.body,h,m;if(s=t.startContainer,l=t.startOffset,c=t.endContainer,u=t.endOffset,r=p.createTextRange(),s==c&&1==s.nodeType){if(l==u&&!s.hasChildNodes()){if(s.canHaveHTML)return d=s.previousSibling,d&&!d.hasChildNodes()&&i.isBlock(d)?d.innerHTML="":d=null,s.innerHTML="",r.moveToElementText(s.lastChild),r.select(),i.doc.selection.clear(),s.innerHTML="",d&&(d.innerHTML=""),void 0;l=i.nodeIndex(s),s=s.parentNode}if(l==u-1)try{if(m=s.childNodes[l],a=p.createControlRange(),a.addElement(m),a.select(),h=e.getRng(),h.item&&m===h.item(0))return}catch(g){}}n(!0),n(),r.select()},this.getRangeAt=n}return e}),r(T,[g],function(e){return{BACKSPACE:8,DELETE:46,DOWN:40,ENTER:13,LEFT:37,RIGHT:39,SPACEBAR:32,TAB:9,UP:38,modifierPressed:function(e){return e.shiftKey||e.ctrlKey||e.altKey},metaKeyPressed:function(t){return(e.mac?t.metaKey:t.ctrlKey)&&!t.altKey}}}),r(R,[T,p,g],function(e,t,n){return function(r,i){function o(e){return i.settings.object_resizing===!1?!1:/TABLE|IMG|DIV/.test(e.nodeName)?"false"===e.getAttribute("data-mce-resize")?!1:!0:!1}function a(t){var n,r;n=t.screenX-k,r=t.screenY-S,D=n*N[2]+A,M=r*N[3]+B,D=5>D?5:D,M=5>M?5:M,(e.modifierPressed(t)||"IMG"==x.nodeName&&N[2]*N[3]!==0)&&(D=Math.round(M/L),M=Math.round(D*L)),b.setStyles(w,{width:D,height:M}),N[2]<0&&w.clientWidth<=D&&b.setStyle(w,"left",T+(A-D)),N[3]<0&&w.clientHeight<=M&&b.setStyle(w,"top",R+(B-M)),H||(i.fire("ObjectResizeStart",{target:x,width:A,height:B}),H=!0)}function s(){function e(e,t){t&&(x.style[e]||!i.schema.isValid(x.nodeName.toLowerCase(),e)?b.setStyle(x,e,t):b.setAttrib(x,e,t))}H=!1,e("width",D),e("height",M),b.unbind(P,"mousemove",a),b.unbind(P,"mouseup",s),O!=P&&(b.unbind(O,"mousemove",a),b.unbind(O,"mouseup",s)),b.remove(w),I&&"TABLE"!=x.nodeName||l(x),i.fire("ObjectResized",{target:x,width:D,height:M}),i.nodeChanged()}function l(e,t,n){var r,l,u,d,f,p=i.getBody();r=b.getPos(e,p),T=r.x,R=r.y,f=e.getBoundingClientRect(),l=f.width||f.right-f.left,u=f.height||f.bottom-f.top,x!=e&&(m(),x=e,D=M=0),d=i.fire("ObjectSelected",{target:e}),o(e)&&!d.isDefaultPrevented()?C(_,function(e,r){function o(t){H=!0,k=t.screenX,S=t.screenY,A=x.clientWidth,B=x.clientHeight,L=B/A,N=e,w=x.cloneNode(!0),b.addClass(w,"mce-clonedresizable"),w.contentEditable=!1,w.unSelectabe=!0,b.setStyles(w,{left:T,top:R,margin:0}),w.removeAttribute("data-mce-selected"),i.getBody().appendChild(w),b.bind(P,"mousemove",a),b.bind(P,"mouseup",s),O!=P&&(b.bind(O,"mousemove",a),b.bind(O,"mouseup",s))}var c,d;return t?(r==t&&o(n),void 0):(c=b.get("mceResizeHandle"+r),c?b.show(c):(d=i.getBody(),c=b.add(d,"div",{id:"mceResizeHandle"+r,"data-mce-bogus":!0,"class":"mce-resizehandle",contentEditable:!1,unSelectabe:!0,style:"cursor:"+r+"-resize; margin:0; padding:0"}),b.bind(c,"mousedown",function(e){e.preventDefault(),o(e)})),b.setStyles(c,{left:l*e[0]+T-c.offsetWidth/2,top:u*e[1]+R-c.offsetHeight/2}),void 0)}):c(),x.setAttribute("data-mce-selected","1")}function c(){var e,t;x&&x.removeAttribute("data-mce-selected");for(e in _)t=b.get("mceResizeHandle"+e),t&&(b.unbind(t),b.remove(t))}function u(e){function t(e,t){do if(e===t)return!0;while(e=e.parentNode)}var n;return C(b.select("img[data-mce-selected],hr[data-mce-selected]"),function(e){e.removeAttribute("data-mce-selected")}),n="mousedown"==e.type?e.target:r.getNode(),n=b.getParent(n,I?"table":"table,img,hr"),n&&(g(),t(r.getStart(),n)&&t(r.getEnd(),n)&&(!I||n!=r.getStart()&&"IMG"!==r.getStart().nodeName))?(l(n),void 0):(c(),void 0)}function d(e,t,n){e&&e.attachEvent&&e.attachEvent("on"+t,n)}function f(e,t,n){e&&e.detachEvent&&e.detachEvent("on"+t,n)}function p(e){var t=e.srcElement,n,r,o,a,s,c,u;n=t.getBoundingClientRect(),c=E.clientX-n.left,u=E.clientY-n.top;for(r in _)if(o=_[r],a=t.offsetWidth*o[0],s=t.offsetHeight*o[1],Math.abs(a-c)<8&&Math.abs(s-u)<8){N=o;break}H=!0,i.getDoc().selection.empty(),l(t,r,E)}function h(e){var t=e.srcElement;if(t!=x){if(m(),0===t.id.indexOf("mceResizeHandle"))return e.returnValue=!1,void 0;("IMG"==t.nodeName||"TABLE"==t.nodeName)&&(c(),x=t,d(t,"resizestart",p))}}function m(){f(x,"resizestart",p)}function g(){try{i.getDoc().execCommand("enableObjectResizing",!1,!1)}catch(e){}}function v(e){var t;if(I){t=P.body.createControlRange();try{return t.addElement(e),t.select(),!0}catch(n){}}}function y(){x=w=null,I&&(m(),f(i.getBody(),"controlselect",h))}var b=i.dom,C=t.each,x,w,_,N,E,k,S,T,R,A,B,L,H,D,M,P=i.getDoc(),O=document,I=n.ie&&n.ie<11;_={n:[.5,0,0,-1],e:[1,.5,1,0],s:[.5,1,0,1],w:[0,.5,-1,0],nw:[0,0,-1,-1],ne:[1,0,1,-1],se:[1,1,1,1],sw:[0,1,-1,1]};var F=".mce-content-body";return i.contentStyles.push(F+" div.mce-resizehandle {position: absolute;border: 1px solid black;background: #FFF;width: 5px;height: 5px;z-index: 10000}"+F+" .mce-resizehandle:hover {background: #000}"+F+" img[data-mce-selected], hr[data-mce-selected] {outline: 1px solid black;resize: none}"+F+" .mce-clonedresizable {position: absolute;"+(n.gecko?"":"outline: 1px dashed black;")+"opacity: .5;filter: alpha(opacity=50);z-index: 10000}"),i.on("init",function(){I?(i.on("ObjectResized",function(e){"TABLE"!=e.target.nodeName&&(c(),v(e.target))}),d(i.getBody(),"controlselect",h),i.on("mousedown",function(e){E=e})):(g(),n.ie>=11&&(i.on("mouseup",function(e){var t=e.target.nodeName;/^(TABLE|IMG|HR)$/.test(t)&&(i.selection.select(e.target,"TABLE"==t),i.nodeChanged())}),i.dom.bind(i.getBody(),"mscontrolselect",function(e){/^(TABLE|IMG|HR)$/.test(e.target.nodeName)&&e.preventDefault()}))),i.on("nodechange mousedown mouseup ResizeEditor",u),i.on("keydown keyup",function(e){x&&"TABLE"==x.nodeName&&u(e)})}),{controlSelect:v,destroy:y}}}),r(A,[f,S,R,g,p],function(e,n,r,i,o){function a(e,t,i,o){var a=this;a.dom=e,a.win=t,a.serializer=i,a.editor=o,a.controlSelection=new r(a,o),a.win.getSelection||(a.tridentSel=new n(a))}var s=o.each,l=o.grep,c=o.trim,u=i.ie,d=i.opera;return a.prototype={setCursorLocation:function(e,t){var n=this,r=n.dom.createRng();e?(r.setStart(e,t),r.setEnd(e,t),n.setRng(r),n.collapse(!1)):(n._moveEndPoint(r,n.editor.getBody(),!0),n.setRng(r))},getContent:function(e){var n=this,r=n.getRng(),i=n.dom.create("body"),o=n.getSel(),a,s,l;return e=e||{},a=s="",e.get=!0,e.format=e.format||"html",e.selection=!0,n.editor.fire("BeforeGetContent",e),"text"==e.format?n.isCollapsed()?"":r.text||(o.toString?o.toString():""):(r.cloneContents?(l=r.cloneContents(),l&&i.appendChild(l)):r.item!==t||r.htmlText!==t?(i.innerHTML="
"+(r.item?r.item(0).outerHTML:r.htmlText),i.removeChild(i.firstChild)):i.innerHTML=r.toString(),/^\s/.test(i.innerHTML)&&(a=" "),/\s+$/.test(i.innerHTML)&&(s=" "),e.getInner=!0,e.content=n.isCollapsed()?"":a+n.serializer.serialize(i,e)+s,n.editor.fire("GetContent",e),e.content)},setContent:function(e,t){var n=this,r=n.getRng(),i,o=n.win.document,a,s;if(t=t||{format:"html"},t.set=!0,t.selection=!0,e=t.content=e,t.no_events||n.editor.fire("BeforeSetContent",t),e=t.content,r.insertNode){e+='_',r.startContainer==o&&r.endContainer==o?o.body.innerHTML=e:(r.deleteContents(),0===o.body.childNodes.length?o.body.innerHTML=e:r.createContextualFragment?r.insertNode(r.createContextualFragment(e)):(a=o.createDocumentFragment(),s=o.createElement("div"),a.appendChild(s),s.outerHTML=e,r.insertNode(a))),i=n.dom.get("__caret"),r=o.createRange(),r.setStartBefore(i),r.setEndBefore(i),n.setRng(r),n.dom.remove("__caret");try{n.setRng(r)}catch(l){}}else r.item&&(o.execCommand("Delete",!1,null),r=n.getRng()),/^\s+/.test(e)?(r.pasteHTML('_'+e),n.dom.remove("__mce_tmp")):r.pasteHTML(e);t.no_events||n.editor.fire("SetContent",t)},getStart:function(){var e=this,t=e.getRng(),n,r,i,o;if(t.duplicate||t.item){if(t.item)return t.item(0);for(i=t.duplicate(),i.collapse(1),n=i.parentElement(),n.ownerDocument!==e.dom.doc&&(n=e.dom.getRoot()),r=o=t.parentElement();o=o.parentNode;)if(o==n){n=r;break}return n}return n=t.startContainer,1==n.nodeType&&n.hasChildNodes()&&(n=n.childNodes[Math.min(n.childNodes.length-1,t.startOffset)]),n&&3==n.nodeType?n.parentNode:n},getEnd:function(){var e=this,t=e.getRng(),n,r;return t.duplicate||t.item?t.item?t.item(0):(t=t.duplicate(),t.collapse(0),n=t.parentElement(),n.ownerDocument!==e.dom.doc&&(n=e.dom.getRoot()),n&&"BODY"==n.nodeName?n.lastChild||n:n):(n=t.endContainer,r=t.endOffset,1==n.nodeType&&n.hasChildNodes()&&(n=n.childNodes[r>0?r-1:r]),n&&3==n.nodeType?n.parentNode:n)},getBookmark:function(e,t){function n(e,t){var n=0;return s(a.select(e),function(e,r){e==t&&(n=r)}),n}function r(e){function t(t){var n,r,i,o=t?"start":"end";n=e[o+"Container"],r=e[o+"Offset"],1==n.nodeType&&"TR"==n.nodeName&&(i=n.childNodes,n=i[Math.min(t?r:r-1,i.length-1)],n&&(r=t?0:n.childNodes.length,e["set"+(t?"Start":"End")](n,r)))}return t(!0),t(),e}function i(){function e(e,n){var i=e[n?"startContainer":"endContainer"],a=e[n?"startOffset":"endOffset"],s=[],l,c,u=0;if(3==i.nodeType){if(t)for(l=i.previousSibling;l&&3==l.nodeType;l=l.previousSibling)a+=l.nodeValue.length;s.push(a)}else c=i.childNodes,a>=c.length&&c.length&&(u=1,a=Math.max(0,c.length-1)),s.push(o.dom.nodeIndex(c[a],t)+u);for(;i&&i!=r;i=i.parentNode)s.push(o.dom.nodeIndex(i,t));return s}var n=o.getRng(!0),r=a.getRoot(),i={};return i.start=e(n,!0),o.isCollapsed()||(i.end=e(n)),i}var o=this,a=o.dom,l,c,u,d,f,p,h="",m;if(2==e)return p=o.getNode(),f=p.nodeName,"IMG"==f?{name:f,index:n(f,p)}:o.tridentSel?o.tridentSel.getBookmark(e):i();if(e)return{rng:o.getRng()};if(l=o.getRng(),u=a.uniqueId(),d=o.isCollapsed(),m="overflow:hidden;line-height:0px",l.duplicate||l.item){if(l.item)return p=l.item(0),f=p.nodeName,{name:f,index:n(f,p)};c=l.duplicate();try{l.collapse(),l.pasteHTML(''+h+""),d||(c.collapse(!1),l.moveToElementText(c.parentElement()),0===l.compareEndPoints("StartToEnd",c)&&c.move("character",-1),c.pasteHTML(''+h+""))}catch(g){return null}}else{if(p=o.getNode(),f=p.nodeName,"IMG"==f)return{name:f,index:n(f,p)};c=r(l.cloneRange()),d||(c.collapse(!1),c.insertNode(a.create("span",{"data-mce-type":"bookmark",id:u+"_end",style:m},h))),l=r(l),l.collapse(!0),l.insertNode(a.create("span",{"data-mce-type":"bookmark",id:u+"_start",style:m},h))}return o.moveToBookmark({id:u,keep:1}),{id:u}},moveToBookmark:function(e){function t(t){var n=e[t?"start":"end"],r,i,o,s;if(n){for(o=n[0],i=c,r=n.length-1;r>=1;r--){if(s=i.childNodes,n[r]>s.length-1)return;i=s[n[r]]}3===i.nodeType&&(o=Math.min(n[0],i.nodeValue.length)),1===i.nodeType&&(o=Math.min(n[0],i.childNodes.length)),t?a.setStart(i,o):a.setEnd(i,o)}return!0}function n(t){var n=o.get(e.id+"_"+t),r,i,a,c,u=e.keep;if(n&&(r=n.parentNode,"start"==t?(u?(r=n.firstChild,i=1):i=o.nodeIndex(n),f=p=r,h=m=i):(u?(r=n.firstChild,i=1):i=o.nodeIndex(n),p=r,m=i),!u)){for(c=n.previousSibling,a=n.nextSibling,s(l(n.childNodes),function(e){3==e.nodeType&&(e.nodeValue=e.nodeValue.replace(/\uFEFF/g,""))});n=o.get(e.id+"_"+t);)o.remove(n,1);c&&a&&c.nodeType==a.nodeType&&3==c.nodeType&&!d&&(i=c.nodeValue.length,c.appendData(a.nodeValue),o.remove(a),"start"==t?(f=p=c,h=m=i):(p=c,m=i))}}function r(e){return!o.isBlock(e)||e.innerHTML||u||(e.innerHTML='
'),e}var i=this,o=i.dom,a,c,f,p,h,m;if(e)if(e.start){if(a=o.createRng(),c=o.getRoot(),i.tridentSel)return i.tridentSel.moveToBookmark(e);t(!0)&&t()&&i.setRng(a)}else e.id?(n("start"),n("end"),f&&(a=o.createRng(),a.setStart(r(f),h),a.setEnd(r(p),m),i.setRng(a))):e.name?i.select(o.select(e.name)[e.index]):e.rng&&i.setRng(e.rng)},select:function(e,t){var n=this,r=n.dom,i=r.createRng(),o;if(n.lastFocusBookmark=null,e){if(!t&&n.controlSelection.controlSelect(e))return;o=r.nodeIndex(e),i.setStart(e.parentNode,o),i.setEnd(e.parentNode,o+1),t&&(n._moveEndPoint(i,e,!0),n._moveEndPoint(i,e)),n.setRng(i)}return e},isCollapsed:function(){var e=this,t=e.getRng(),n=e.getSel();return!t||t.item?!1:t.compareEndPoints?0===t.compareEndPoints("StartToEnd",t):!n||t.collapsed},collapse:function(e){var t=this,n=t.getRng(),r;n.item&&(r=n.item(0),n=t.win.document.body.createTextRange(),n.moveToElementText(r)),n.collapse(!!e),t.setRng(n)},getSel:function(){var e=this.win;return e.getSelection?e.getSelection():e.document.selection},getRng:function(e){var t=this,n,r,i,o=t.win.document,a;if(!e&&t.lastFocusBookmark){var s=t.lastFocusBookmark;return s.startContainer?(r=o.createRange(),r.setStart(s.startContainer,s.startOffset),r.setEnd(s.endContainer,s.endOffset)):r=s,r}if(e&&t.tridentSel)return t.tridentSel.getRangeAt(0);try{(n=t.getSel())&&(r=n.rangeCount>0?n.getRangeAt(0):n.createRange?n.createRange():o.createRange())}catch(l){}if(u&&r&&r.setStart){try{a=o.selection.createRange()}catch(l){}a&&a.item&&(i=a.item(0),r=o.createRange(),r.setStartBefore(i),r.setEndAfter(i))}return r||(r=o.createRange?o.createRange():o.body.createTextRange()),r.setStart&&9===r.startContainer.nodeType&&r.collapsed&&(i=t.dom.getRoot(),r.setStart(i,0),r.setEnd(i,0)),t.selectedRange&&t.explicitRange&&(0===r.compareBoundaryPoints(r.START_TO_START,t.selectedRange)&&0===r.compareBoundaryPoints(r.END_TO_END,t.selectedRange)?r=t.explicitRange:(t.selectedRange=null,t.explicitRange=null)),r},setRng:function(e,t){var n=this,r;if(e.select)try{e.select()}catch(i){}else if(n.tridentSel){if(e.cloneRange)try{return n.tridentSel.addRange(e),void 0}catch(i){}}else if(r=n.getSel()){n.explicitRange=e;try{r.removeAllRanges(),r.addRange(e)}catch(i){}t===!1&&r.extend&&(r.collapse(e.endContainer,e.endOffset),r.extend(e.startContainer,e.startOffset)),n.selectedRange=r.rangeCount>0?r.getRangeAt(0):null}},setNode:function(e){var t=this;return t.setContent(t.dom.getOuterHTML(e)),e},getNode:function(){function e(e,t){for(var n=e;e&&3===e.nodeType&&0===e.length;)e=t?e.nextSibling:e.previousSibling;return e||n}var t=this,n=t.getRng(),r,i=n.startContainer,o=n.endContainer,a=n.startOffset,s=n.endOffset,l=t.dom.getRoot();return n?n.setStart?(r=n.commonAncestorContainer,!n.collapsed&&(i==o&&2>s-a&&i.hasChildNodes()&&(r=i.childNodes[a]),3===i.nodeType&&3===o.nodeType&&(i=i.length===a?e(i.nextSibling,!0):i.parentNode,o=0===s?e(o.previousSibling,!1):o.parentNode,i&&i===o))?i:r&&3==r.nodeType?r.parentNode:r):(r=n.item?n.item(0):n.parentElement(),r.ownerDocument!==t.win.document&&(r=l),r):l},getSelectedBlocks:function(t,n){var r=this,i=r.dom,o,a,s=[];if(a=i.getRoot(),t=i.getParent(t||r.getStart(),i.isBlock),n=i.getParent(n||r.getEnd(),i.isBlock),t&&t!=a&&s.push(t),t&&n&&t!=n){o=t;for(var l=new e(t,a);(o=l.next())&&o!=n;)i.isBlock(o)&&s.push(o)}return n&&t!=n&&n!=a&&s.push(n),s},isForward:function(){var e=this.dom,t=this.getSel(),n,r;return t&&t.anchorNode&&t.focusNode?(n=e.createRng(),n.setStart(t.anchorNode,t.anchorOffset),n.collapse(!0),r=e.createRng(),r.setStart(t.focusNode,t.focusOffset),r.collapse(!0),n.compareBoundaryPoints(n.START_TO_START,r)<=0):!0},normalize:function(){function t(t){function a(t,n){for(var r=new e(t,f.getParent(t.parentNode,f.isBlock)||p);t=r[n?"prev":"next"]();)if("BR"===t.nodeName)return!0}function s(e,t){return e.previousSibling&&e.previousSibling.nodeName==t}function l(t,n){var r,a;for(n=n||c,r=new e(n,f.getParent(n.parentNode,f.isBlock)||p);h=r[t?"prev":"next"]();){if(3===h.nodeType&&h.nodeValue.length>0)return c=h,u=t?h.nodeValue.length:0,i=!0,void 0;if(f.isBlock(h)||m[h.nodeName.toLowerCase()])return;a=h}o&&a&&(c=a,i=!0,u=0)}var c,u,d,f=n.dom,p=f.getRoot(),h,m,g;if(c=r[(t?"start":"end")+"Container"],u=r[(t?"start":"end")+"Offset"],m=f.schema.getNonEmptyElements(),9===c.nodeType&&(c=f.getRoot(),u=0),c===p){if(t&&(h=c.childNodes[u>0?u-1:0],h&&(g=h.nodeName.toLowerCase(),m[h.nodeName]||"TABLE"==h.nodeName)))return;if(c.hasChildNodes()&&(u=Math.min(!t&&u>0?u-1:u,c.childNodes.length-1),c=c.childNodes[u],u=0,c.hasChildNodes()&&!/TABLE/.test(c.nodeName))){h=c,d=new e(c,p);do{if(3===h.nodeType&&h.nodeValue.length>0){u=t?0:h.nodeValue.length,c=h,i=!0;break}if(m[h.nodeName.toLowerCase()]){u=f.nodeIndex(h),c=h.parentNode,"IMG"!=h.nodeName||t||u++,i=!0;break}}while(h=t?d.next():d.prev())}}o&&(3===c.nodeType&&0===u&&l(!0),1===c.nodeType&&(h=c.childNodes[u],!h||"BR"!==h.nodeName||s(h,"A")||a(h)||a(h,!0)||l(!0,c.childNodes[u]))),t&&!o&&3===c.nodeType&&u===c.nodeValue.length&&l(!1),i&&r["set"+(t?"Start":"End")](c,u)}var n=this,r,i,o;u||(r=n.getRng(),o=r.collapsed,t(!0),o||t(),i&&(o&&r.collapse(!0),n.setRng(r,n.isForward())))},selectorChanged:function(e,t){var n=this,r;return n.selectorChangedData||(n.selectorChangedData={},r={},n.editor.on("NodeChange",function(e){var t=e.element,i=n.dom,o=i.getParents(t,null,i.getRoot()),a={};s(n.selectorChangedData,function(e,t){s(o,function(n){return i.is(n,t)?(r[t]||(s(e,function(e){e(!0,{node:n,selector:t,parents:o})}),r[t]=e),a[t]=e,!1):void 0})}),s(r,function(e,n){a[n]||(delete r[n],s(e,function(e){e(!1,{node:t,selector:n,parents:o})}))})})),n.selectorChangedData[e]||(n.selectorChangedData[e]=[]),n.selectorChangedData[e].push(t),n},getScrollContainer:function(){for(var e,t=this.dom.getRoot();t&&"BODY"!=t.nodeName;){if(t.scrollHeight>t.clientHeight){e=t;break}t=t.parentNode}return e},scrollIntoView:function(e){function t(e){for(var t=0,n=0,r=e;r&&r.nodeType;)t+=r.offsetLeft||0,n+=r.offsetTop||0,r=r.offsetParent;return{x:t,y:n}}var n,r,i=this,o=i.dom,a=o.getRoot(),s,l;if("BODY"!=a.nodeName){var c=i.getScrollContainer();if(c)return n=t(e).y-t(c).y,l=c.clientHeight,s=c.scrollTop,(s>n||n+25>s+l)&&(c.scrollTop=s>n?n:n-l+25),void 0}r=o.getViewPort(i.editor.getWin()),n=o.getPos(e).y,s=r.y,l=r.h,(ns+l)&&i.editor.getWin().scrollTo(0,s>n?n:n-l+25)},_moveEndPoint:function(t,n,r){var o=n,a=new e(n,o),s=this.dom.schema.getNonEmptyElements();do{if(3==n.nodeType&&0!==c(n.nodeValue).length)return r?t.setStart(n,0):t.setEnd(n,n.nodeValue.length),void 0;if(s[n.nodeName])return r?t.setStartBefore(n):"BR"==n.nodeName?t.setEndBefore(n):t.setEndAfter(n),void 0;if(i.ie&&i.ie<11&&this.dom.isBlock(n)&&this.dom.isEmpty(n))return r?t.setStart(n,0):t.setEnd(n,0),void 0}while(n=r?a.next():a.prev());"BODY"==o.nodeName&&(r?t.setStart(o,0):t.setEnd(o,o.childNodes.length))},destroy:function(){this.win=null,this.controlSelection.destroy()}},a}),r(B,[p],function(e){function t(e){this.walk=function(t,r){function i(e){var t;return t=e[0],3===t.nodeType&&t===l&&c>=t.nodeValue.length&&e.splice(0,1),t=e[e.length-1],0===d&&e.length>0&&t===u&&3===t.nodeType&&e.splice(e.length-1,1),e}function o(e,t,n){for(var r=[];e&&e!=n;e=e[t])r.push(e);return r}function a(e,t){do{if(e.parentNode==t)return e;e=e.parentNode}while(e)}function s(e,t,n){var a=n?"nextSibling":"previousSibling";for(m=e,g=m.parentNode;m&&m!=t;m=g)g=m.parentNode,v=o(m==e?m:m[a],a),v.length&&(n||v.reverse(),r(i(v)))}var l=t.startContainer,c=t.startOffset,u=t.endContainer,d=t.endOffset,f,p,h,m,g,v,y;if(y=e.select("td.mce-item-selected,th.mce-item-selected"),y.length>0)return n(y,function(e){r([e])}),void 0;if(1==l.nodeType&&l.hasChildNodes()&&(l=l.childNodes[c]),1==u.nodeType&&u.hasChildNodes()&&(u=u.childNodes[Math.min(d-1,u.childNodes.length-1)]),l==u)return r(i([l]));for(f=e.findCommonAncestor(l,u),m=l;m;m=m.parentNode){if(m===u)return s(l,f,!0);if(m===f)break}for(m=u;m;m=m.parentNode){if(m===l)return s(u,f);if(m===f)break}p=a(l,f)||l,h=a(u,f)||u,s(l,p,!0),v=o(p==l?p:p.nextSibling,"nextSibling",h==u?h.nextSibling:h),v.length&&r(i(v)),s(u,h)},this.split=function(e){function t(e,t){return e.splitText(t)}var n=e.startContainer,r=e.startOffset,i=e.endContainer,o=e.endOffset;return n==i&&3==n.nodeType?r>0&&rr?(o-=r,n=i=t(i,o).previousSibling,o=i.nodeValue.length,r=0):o=0):(3==n.nodeType&&r>0&&r0&&o=e;e++)r.addShortcut("ctrl+"+e,"",["FormatBlock",!1,"h"+e]);r.addShortcut("ctrl+7","",["FormatBlock",!1,"p"]),r.addShortcut("ctrl+8","",["FormatBlock",!1,"div"]),r.addShortcut("ctrl+9","",["FormatBlock",!1,"address"])}function c(e){return e?O[e]:O}function u(e,t){e&&("string"!=typeof e?et(e,function(e,t){u(t,e)}):(t=t.length?t:[t],et(t,function(e){e.deep===X&&(e.deep=!e.selector),e.split===X&&(e.split=!e.selector||e.inline),e.remove===X&&e.selector&&!e.inline&&(e.remove="none"),e.selector&&e.inline&&(e.mixed=!0,e.block_expand=!0),"string"==typeof e.classes&&(e.classes=e.classes.split(/\s+/))}),O[e]=t))}function d(e){var t;return r.dom.getParent(e,function(e){return t=r.dom.getStyle(e,"text-decoration"),t&&"none"!==t}),t}function f(e){var t;1===e.nodeType&&e.parentNode&&1===e.parentNode.nodeType&&(t=d(e.parentNode),r.dom.getStyle(e,"color")&&t?r.dom.setStyle(e,"text-decoration",t):r.dom.getStyle(e,"textdecoration")===t&&r.dom.setStyle(e,"text-decoration",null))}function p(t,n,o){function s(e,t){t=t||m,e&&(t.onformat&&t.onformat(e,t,n,o),et(t.styles,function(t,r){I.setStyle(e,r,E(t,n))}),et(t.attributes,function(t,r){I.setAttrib(e,r,E(t,n))}),et(t.classes,function(t){t=E(t,n),I.hasClass(e,t)||I.addClass(e,t)}))}function l(){function t(t,n){var r=new e(n);for(o=r.current();o;o=r.prev())if(o.childNodes.length>1||o==t||"BR"==o.tagName)return o}var n=r.selection.getRng(),i=n.startContainer,a=n.endContainer;if(i!=a&&0===n.endOffset){var s=t(i,a),l=3==s.nodeType?s.length:s.childNodes.length;n.setEnd(s,l)}return n}function u(e,t,n,r,i){var o=[],a=-1,s,l=-1,c=-1,u;return et(e.childNodes,function(e,t){return"UL"===e.nodeName||"OL"===e.nodeName?(a=t,s=e,!1):void 0}),et(e.childNodes,function(e,n){"SPAN"===e.nodeName&&"bookmark"==I.getAttrib(e,"data-mce-type")&&(e.id==t.id+"_start"?l=n:e.id==t.id+"_end"&&(c=n))}),0>=a||a>l&&c>a?(et(tt(e.childNodes),i),0):(u=I.clone(n,K),et(tt(e.childNodes),function(e,t){(a>l&&a>t||l>a&&t>a)&&(o.push(e),e.parentNode.removeChild(e))}),a>l?e.insertBefore(u,s):l>a&&e.insertBefore(u,s.nextSibling),r.push(u),et(o,function(e){u.appendChild(e)}),u)}function d(e,r,o){var l=[],c,d,f=!0;c=m.inline||m.block,d=I.create(c),s(d),z.walk(e,function(e){function p(e){var y,C,x,_,N;return N=f,y=e.nodeName.toLowerCase(),C=e.parentNode.nodeName.toLowerCase(),1===e.nodeType&&J(e)&&(N=f,f="true"===J(e),_=!0),w(y,"br")?(v=0,m.block&&I.remove(e),void 0):m.wrapper&&g(e,t,n)?(v=0,void 0):f&&!_&&m.block&&!m.wrapper&&i(y)&&W(C,c)?(e=I.rename(e,c),s(e),l.push(e),v=0,void 0):m.selector&&(et(h,function(t){"collapsed"in t&&t.collapsed!==b||I.is(e,t.selector)&&!a(e)&&(s(e,t),x=!0)}),!m.inline||x)?(v=0,void 0):(!f||_||!W(c,y)||!W(C,c)||!o&&3===e.nodeType&&1===e.nodeValue.length&&65279===e.nodeValue.charCodeAt(0)||a(e)||m.inline&&V(e)?"li"==y&&r?v=u(e,r,d,l,p):(v=0,et(tt(e.childNodes),p),_&&(f=N),v=0):(v||(v=I.clone(d,K),e.parentNode.insertBefore(v,e),l.push(v)),v.appendChild(e)),void 0)}var v;et(e,p)}),m.wrap_links===!1&&et(l,function(e){function t(e){var n,r,i;if("A"===e.nodeName){for(r=I.clone(d,K),l.push(r),i=tt(e.childNodes),n=0;n1||!V(e))&&0===o)return I.remove(e,1),void 0;if(m.inline||m.wrapper){if(m.exact||1!==o||(e=i(e)),et(h,function(t){et(I.select(t.inline,e),function(e){var r;if(t.wrap_links===!1){r=e.parentNode;do if("A"===r.nodeName)return;while(r=r.parentNode)}R(t,n,e,t.exact?e:null)})}),g(e.parentNode,t,n))return I.remove(e,1),e=0,G;m.merge_with_parents&&I.getParent(e.parentNode,function(r){return g(r,t,n)?(I.remove(e,1),e=0,G):void 0}),e&&m.merge_siblings!==!1&&(e=H(B(e),e),e=H(e,B(e,G)))}})}var h=c(t),m=h[0],v,y,b=!o&&F.isCollapsed();if(m)if(o)o.nodeType?(y=I.createRng(),y.setStartBefore(o),y.setEndAfter(o),d(T(y,h),null,!0)):d(o,null,!0);else if(b&&m.inline&&!I.select("td.mce-item-selected,th.mce-item-selected").length)M("apply",t,n);else{var C=r.selection.getNode();U||!h[0].defaultBlock||I.getParent(C,I.isBlock)||p(h[0].defaultBlock),r.selection.setRng(l()),v=F.getBookmark(),d(T(F.getRng(G),h),v),m.styles&&(m.styles.color||m.styles.textDecoration)&&(nt(C,f,"childNodes"),f(C)),F.moveToBookmark(v),P(F.getRng(G)),r.nodeChanged()}}function h(e,t,n){function i(e){var n,r,o,a,s;if(1===e.nodeType&&J(e)&&(a=b,b="true"===J(e),s=!0),n=tt(e.childNodes),b&&!s)for(r=0,o=p.length;o>r&&!R(p[r],t,e,e);r++);if(h.deep&&n.length){for(r=0,o=n.length;o>r;r++)i(n[r]);s&&(b=a)}}function a(n){var r;return et(o(n.parentNode).reverse(),function(n){var i;r||"_start"==n.id||"_end"==n.id||(i=g(n,e,t),i&&i.split!==!1&&(r=n))}),r}function s(e,n,r,i){var o,a,s,l,c,u;if(e){for(u=e.parentNode,o=n.parentNode;o&&o!=u;o=o.parentNode){for(a=I.clone(o,K),c=0;c=0;a--){if(s=t[a].selector,!s||t[a].defaultBlock)return G; -for(i=r.length-1;i>=0;i--)if(I.is(r[i],s))return G}return K}function C(e,t,n){var i;return Y||(Y={},i={},r.on("NodeChange",function(e){var t=o(e.element),n={};et(Y,function(e,r){et(t,function(o){return g(o,r,{},e.similar)?(i[r]||(et(e,function(e){e(!0,{node:o,format:r,parents:t})}),i[r]=e),n[r]=e,!1):void 0})}),et(i,function(r,o){n[o]||(delete i[o],et(r,function(n){n(!1,{node:e.element,format:o,parents:t})}))})})),et(e.split(","),function(e){Y[e]||(Y[e]=[],Y[e].similar=n),Y[e].push(t)}),this}function x(e,t){return w(e,t.inline)?G:w(e,t.block)?G:t.selector?1==e.nodeType&&I.is(e,t.selector):void 0}function w(e,t){return e=e||"",t=t||"",e=""+(e.nodeName||e),t=""+(t.nodeName||t),e.toLowerCase()==t.toLowerCase()}function _(e,t){return N(I.getStyle(e,t),t)}function N(e,t){return("color"==t||"backgroundColor"==t)&&(e=I.toHex(e)),"fontWeight"==t&&700==e&&(e="bold"),"fontFamily"==t&&(e=e.replace(/[\'\"]/g,"").replace(/,\s+/g,",")),""+e}function E(e,t){return"string"!=typeof e?e=e(t):t&&(e=e.replace(/%(\w+)/g,function(e,n){return t[n]||e})),e}function k(e){return e&&3===e.nodeType&&/^([\t \r\n]+|)$/.test(e.nodeValue)}function S(e,t,n){var r=I.create(t,n);return e.parentNode.insertBefore(r,e),r.appendChild(e),r}function T(t,n,a){function s(e){function t(e){return"BR"==e.nodeName&&e.getAttribute("data-mce-bogus")&&!e.nextSibling}var r,i,o,a,s;if(r=i=e?g:y,a=e?"previousSibling":"nextSibling",s=I.getRoot(),3==r.nodeType&&!k(r)&&(e?v>0:br?n:r,-1===n||a||n++):(n=o.indexOf(" ",t),r=o.indexOf("\xa0",t),n=-1!==n&&(-1===r||r>n)?n:r),n}var s,l,c,u;if(3===t.nodeType){if(c=o(t,n),-1!==c)return{container:t,offset:c};u=t}for(s=new e(t,I.getParent(t,V)||r.getBody());l=s[i?"prev":"next"]();)if(3===l.nodeType){if(u=l,c=o(l),-1!==c)return{container:l,offset:c}}else if(V(l))break;return u?(n=i?0:u.length,{container:u,offset:n}):void 0}function d(e,r){var i,a,s,l;for(3==e.nodeType&&0===e.nodeValue.length&&e[r]&&(e=e[r]),i=o(e),a=0;ap?p:v],3==g.nodeType&&(v=0)),1==y.nodeType&&y.hasChildNodes()&&(p=y.childNodes.length-1,y=y.childNodes[b>p?p:b-1],3==y.nodeType&&(b=y.nodeValue.length)),g=c(g),y=c(y),(L(g.parentNode)||L(g))&&(g=L(g)?g:g.parentNode,g=g.nextSibling||g,3==g.nodeType&&(v=0)),(L(y.parentNode)||L(y))&&(y=L(y)?y:y.parentNode,y=y.previousSibling||y,3==y.nodeType&&(b=y.length)),n[0].inline&&(t.collapsed&&(m=u(g,v,!0),m&&(g=m.container,v=m.offset),m=u(y,b),m&&(y=m.container,b=m.offset)),h=l(y,b),h.node)){for(;h.node&&0===h.offset&&h.node.previousSibling;)h=l(h.node.previousSibling);h.node&&h.offset>0&&3===h.node.nodeType&&" "===h.node.nodeValue.charAt(h.offset-1)&&h.offset>1&&(y=h.node,y.splitText(h.offset-1))}return(n[0].inline||n[0].block_expand)&&(n[0].inline&&3==g.nodeType&&0!==v||(g=s(!0)),n[0].inline&&3==y.nodeType&&b!==y.nodeValue.length||(y=s())),n[0].selector&&n[0].expand!==K&&!n[0].inline&&(g=d(g,"previousSibling"),y=d(y,"nextSibling")),(n[0].block||n[0].selector)&&(g=f(g,"previousSibling"),y=f(y,"nextSibling"),n[0].block&&(V(g)||(g=s(!0)),V(y)||(y=s()))),1==g.nodeType&&(v=q(g),g=g.parentNode),1==y.nodeType&&(b=q(y)+1,y=y.parentNode),{startContainer:g,startOffset:v,endContainer:y,endOffset:b}}function R(e,t,n,r){var i,o,a;if(!x(n,e))return K;if("all"!=e.remove)for(et(e.styles,function(e,i){e=N(E(e,t),i),"number"==typeof i&&(i=e,r=0),(!r||w(_(r,i),e))&&I.setStyle(n,i,""),a=1}),a&&""===I.getAttrib(n,"style")&&(n.removeAttribute("style"),n.removeAttribute("data-mce-style")),et(e.attributes,function(e,i){var o;if(e=E(e,t),"number"==typeof i&&(i=e,r=0),!r||w(I.getAttrib(r,i),e)){if("class"==i&&(e=I.getAttrib(n,i),e&&(o="",et(e.split(/\s+/),function(e){/mce\w+/.test(e)&&(o+=(o?" ":"")+e)}),o)))return I.setAttrib(n,i,o),void 0;"class"==i&&n.removeAttribute("className"),j.test(i)&&n.removeAttribute("data-mce-"+i),n.removeAttribute(i)}}),et(e.classes,function(e){e=E(e,t),(!r||I.hasClass(r,e))&&I.removeClass(n,e)}),o=I.getAttribs(n),i=0;ia?a:o]),3===i.nodeType&&n&&o>=i.nodeValue.length&&(i=new e(i,r.getBody()).next()||i),3!==i.nodeType||n||0!==o||(i=new e(i,r.getBody()).prev()||i),i}function M(t,n,o){function a(e){var t=I.create("span",{id:y,"data-mce-bogus":!0,style:b?"color:red":""});return e&&t.appendChild(r.getDoc().createTextNode($)),t}function s(e,t){for(;e;){if(3===e.nodeType&&e.nodeValue!==$||e.childNodes.length>1)return!1;t&&1===e.nodeType&&t.push(e),e=e.firstChild}return!0}function l(e){for(;e;){if(e.id===y)return e;e=e.parentNode}}function u(t){var n;if(t)for(n=new e(t,t),t=n.current();t;t=n.next())if(3===t.nodeType)return t}function d(e,t){var n,r;if(e)r=F.getRng(!0),s(e)?(t!==!1&&(r.setStartBefore(e),r.setEndBefore(e)),I.remove(e)):(n=u(e),n.nodeValue.charAt(0)===$&&(n=n.deleteData(0,1)),I.remove(e,1)),F.setRng(r);else if(e=l(F.getStart()),!e)for(;e=I.get(y);)d(e,!1)}function f(){var e,t,r,i,s,d,f;e=F.getRng(!0),i=e.startOffset,d=e.startContainer,f=d.nodeValue,t=l(F.getStart()),t&&(r=u(t)),f&&i>0&&i=0;p--)u.appendChild(I.clone(f[p],!1)),u=u.firstChild;u.appendChild(I.doc.createTextNode($)),u=u.firstChild;var v=I.getParent(d,i);v&&I.isEmpty(v)?d.parentNode.replaceChild(m,d):I.insertAfter(m,d),F.setCursorLocation(u,1),I.isEmpty(d)&&I.remove(d)}}function v(){var e;e=l(F.getStart()),e&&!I.isEmpty(e)&&nt(e,function(e){1!=e.nodeType||e.id===y||I.isEmpty(e)||I.setAttrib(e,"data-mce-bogus",null)},"childNodes")}var y="_mce_caret",b=r.settings.caret_debug;r._hasCaretEvents||(Z=function(){var e=[],t;if(s(l(F.getStart()),e))for(t=e.length;t--;)I.setAttrib(e[t],"data-mce-bogus","1")},Q=function(e){var t=e.keyCode;d(),(8==t||37==t||39==t)&&d(l(F.getStart())),v()},r.on("SetContent",function(e){e.selection&&v()}),r._hasCaretEvents=!0),"apply"==t?f():m()}function P(t){var n=t.startContainer,r=t.startOffset,i,o,a,s,l;if(3==n.nodeType&&r>=n.nodeValue.length&&(r=q(n),n=n.parentNode,i=!0),1==n.nodeType)for(s=n.childNodes,n=s[Math.min(r,s.length-1)],o=new e(n,I.getParent(n,I.isBlock)),(r>s.length-1||i)&&o.next(),a=o.current();a;a=o.next())if(3==a.nodeType&&!k(a))return l=I.create("a",null,$),a.parentNode.insertBefore(l,a),t.setStart(a,0),F.setRng(t),I.remove(l),void 0}var O={},I=r.dom,F=r.selection,z=new t(I),W=r.schema.isValidChild,V=I.isBlock,U=r.settings.forced_root_block,q=I.nodeIndex,$="\ufeff",j=/^(src|href|style)$/,K=!1,G=!0,Y,X,J=I.getContentEditable,Q,Z,et=n.each,tt=n.grep,nt=n.walk,rt=n.extend;rt(this,{get:c,register:u,apply:p,remove:h,toggle:m,match:v,matchAll:y,matchNode:g,canApply:b,formatChanged:C}),s(),l(),r.on("BeforeGetContent",function(){Z&&Z()}),r.on("mouseup keydown",function(e){Q&&Q(e)})}}),r(H,[g,p],function(e,t){var n=t.trim,r;return r=new RegExp(["]+data-mce-bogus[^>]+>[\u200b\ufeff]+<\\/span>","]+data-mce-bogus[^>]+><\\/div>",'\\s?data-mce-selected="[^"]+"'].join("|"),"gi"),function(t){function i(){return n(t.getContent({format:"raw",no_events:1}).replace(r,""))}function o(){a.typing=!1,a.add()}var a,s=0,l=[],c,u,d;return t.on("init",function(){a.add()}),t.on("BeforeExecCommand",function(e){var t=e.command;"Undo"!=t&&"Redo"!=t&&"mceRepaint"!=t&&a.beforeChange()}),t.on("ExecCommand",function(e){var t=e.command;"Undo"!=t&&"Redo"!=t&&"mceRepaint"!=t&&a.add()}),t.on("ObjectResizeStart",function(){a.beforeChange()}),t.on("SaveContent ObjectResized",o),t.dom.bind(t.dom.getRoot(),"dragend",o),t.dom.bind(t.getBody(),"focusout",function(){!t.removed&&a.typing&&o()}),t.on("KeyUp",function(n){var r=n.keyCode;(r>=33&&36>=r||r>=37&&40>=r||45==r||13==r||n.ctrlKey)&&(o(),t.nodeChanged()),(46==r||8==r||e.mac&&(91==r||93==r))&&t.nodeChanged(),u&&a.typing&&(t.isDirty()||(t.isNotDirty=!l[0]||i()==l[0].content,t.isNotDirty||t.fire("change",{level:l[0],lastLevel:null})),t.fire("TypingUndo"),u=!1,t.nodeChanged())}),t.on("KeyDown",function(e){var t=e.keyCode;return t>=33&&36>=t||t>=37&&40>=t||45==t?(a.typing&&o(),void 0):((16>t||t>20)&&224!=t&&91!=t&&!a.typing&&(a.beforeChange(),a.typing=!0,a.add(),u=!0),void 0)}),t.on("MouseDown",function(){a.typing&&o()}),t.addShortcut("ctrl+z","","Undo"),t.addShortcut("ctrl+y,ctrl+shift+z","","Redo"),t.on("AddUndo Undo Redo ClearUndos MouseUp",function(e){e.isDefaultPrevented()||t.nodeChanged()}),a={data:l,typing:!1,beforeChange:function(){d||(c=t.selection.getBookmark(2,!0))},add:function(e){var n,r=t.settings,o;if(e=e||{},e.content=i(),d||t.fire("BeforeAddUndo",{level:e}).isDefaultPrevented())return null;if(o=l[s],o&&o.content==e.content)return null;if(l[s]&&(l[s].beforeBookmark=c),r.custom_undo_redo_levels&&l.length>r.custom_undo_redo_levels){for(n=0;n0&&(t.fire("change",a),t.isNotDirty=!1),e},undo:function(){var e;return a.typing&&(a.add(),a.typing=!1),s>0&&(e=l[--s],0===s&&(t.isNotDirty=!0),t.setContent(e.content,{format:"raw"}),t.selection.moveToBookmark(e.beforeBookmark),t.fire("undo",{level:e})),e},redo:function(){var e;return s0||a.typing&&l[0]&&i()!=l[0].content},hasRedo:function(){return sB)&&(d=o.create("br"),n.parentNode.insertBefore(d,n)),l.setStartBefore(n),l.setEndBefore(n)):(l.setStartAfter(n),l.setEndAfter(n)):(l.setStart(n,0),l.setEnd(n,0));a.setRng(l),o.remove(d),a.scrollIntoView(n)}function m(e){var t=s.forced_root_block;t&&t.toLowerCase()===e.tagName.toLowerCase()&&o.setAttribs(e,s.forced_root_block_attrs)}function g(e){var t=T,r,i,a;if(e||"TABLE"==P?(r=o.create(e||I),m(r)):r=A.cloneNode(!1),a=r,s.keep_styles!==!1)do if(/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(t.nodeName)){if("_mce_caret"==t.id)continue;i=t.cloneNode(!1),o.setAttrib(i,"id",""),r.hasChildNodes()?(i.appendChild(r.firstChild),r.appendChild(i)):(a=i,r.appendChild(i))}while(t=t.parentNode);return n||(a.innerHTML='
'),r}function v(t){var n,r,i;if(3==T.nodeType&&(t?R>0:R0)return!0}function x(){var e,t,r;T&&3==T.nodeType&&R>=T.nodeValue.length&&(n||C()||(e=o.create("br"),E.insertNode(e),E.setStartAfter(e),E.setEndAfter(e),t=!0)),e=o.create("br"),E.insertNode(e),n&&"PRE"==P&&(!B||8>B)&&e.parentNode.insertBefore(o.doc.createTextNode("\r"),e),r=o.create("span",{}," "),e.parentNode.insertBefore(r,e),a.scrollIntoView(r),o.remove(r),t?(E.setStartBefore(e),E.setEndBefore(e)):(E.setStartAfter(e),E.setEndAfter(e)),a.setRng(E),l.add()}function w(e){do 3===e.nodeType&&(e.nodeValue=e.nodeValue.replace(/^[\r\n]+/,"")),e=e.firstChild;while(e)}function _(e){var t=o.getRoot(),n,r;for(n=e;n!==t&&"false"!==o.getContentEditable(n);)"true"===o.getContentEditable(n)&&(r=n),n=n.parentNode;return n!==t?r:t}function N(e){var t;n||(e.normalize(),t=e.lastChild,(!t||/^(left|right)$/gi.test(o.getStyle(t,"float",!0)))&&o.add(e,"br"))}var E=a.getRng(!0),k,S,T,R,A,B,L,H,D,M,P,O,I,F;if(!E.collapsed)return r.execCommand("Delete"),void 0;if(!i.isDefaultPrevented()&&(T=E.startContainer,R=E.startOffset,I=(s.force_p_newlines?"p":"")||s.forced_root_block,I=I?I.toUpperCase():"",B=o.doc.documentMode,L=i.shiftKey,1==T.nodeType&&T.hasChildNodes()&&(F=R>T.childNodes.length-1,T=T.childNodes[Math.min(R,T.childNodes.length-1)]||T,R=F&&3==T.nodeType?T.nodeValue.length:0),S=_(T))){if(l.beforeChange(),!o.isBlock(S)&&S!=o.getRoot())return(!I||L)&&x(),void 0;if((I&&!L||!I&&L)&&(T=y(T,R)),A=o.getParent(T,o.isBlock),M=A?o.getParent(A.parentNode,o.isBlock):null,P=A?A.nodeName.toUpperCase():"",O=M?M.nodeName.toUpperCase():"","LI"!=O||i.ctrlKey||(A=M,P=O),"LI"==P){if(!I&&L)return x(),void 0;if(o.isEmpty(A))return b(),void 0}if("PRE"==P&&s.br_in_pre!==!1){if(!L)return x(),void 0}else if(!I&&!L&&"LI"!=P||I&&L)return x(),void 0;I&&A===r.getBody()||(I=I||"P",v()?(H=/^(H[1-6]|PRE|FIGURE)$/.test(P)&&"HGROUP"!=O?g(I):g(),s.end_container_on_empty_block&&d(M)&&o.isEmpty(A)?H=o.split(M,A):o.insertAfter(H,A),h(H)):v(!0)?(H=A.parentNode.insertBefore(g(),A),f(H),h(A)):(k=E.cloneRange(),k.setEndAfter(A),D=k.extractContents(),w(D),H=D.firstChild,o.insertAfter(D,A),p(H),N(A),h(H)),o.setAttrib(H,"id",""),r.fire("NewBlock",{newBlock:H}),l.add())}}var o=r.dom,a=r.selection,s=r.settings,l=r.undoManager,c=r.schema,u=c.getNonEmptyElements();r.on("keydown",function(e){13==e.keyCode&&i(e)!==!1&&e.preventDefault()})}}),r(M,[],function(){return function(e){function t(){var t=i.getStart(),s=e.getBody(),l,c,u,d,f,p,h,m=-16777215,g,v,y,b,C;if(C=n.forced_root_block,t&&1===t.nodeType&&C){for(;t&&t!=s;){if(a[t.nodeName])return;t=t.parentNode}if(l=i.getRng(),l.setStart){c=l.startContainer,u=l.startOffset,d=l.endContainer,f=l.endOffset;try{v=e.getDoc().activeElement===s}catch(x){}}else l.item&&(t=l.item(0),l=e.getDoc().body.createTextRange(),l.moveToElementText(t)),v=l.parentElement().ownerDocument===e.getDoc(),y=l.duplicate(),y.collapse(!0),u=-1*y.move("character",m),y.collapsed||(y=l.duplicate(),y.collapse(!1),f=-1*y.move("character",m)-u);for(t=s.firstChild,b=s.nodeName.toLowerCase();t;)if((3===t.nodeType||1==t.nodeType&&!a[t.nodeName])&&o.isValidChild(b,C.toLowerCase())){if(3===t.nodeType&&0===t.nodeValue.length){h=t,t=t.nextSibling,r.remove(h);continue}p||(p=r.create(C,e.settings.forced_root_block_attrs),t.parentNode.insertBefore(p,t),g=!0),h=t,t=t.nextSibling,p.appendChild(h)}else p=null,t=t.nextSibling;if(g&&v){if(l.setStart)l.setStart(c,u),l.setEnd(d,f),i.setRng(l);else try{l=e.getDoc().body.createTextRange(),l.moveToElementText(s),l.collapse(!0),l.moveStart("character",u),f>0&&l.moveEnd("character",f),l.select()}catch(x){}e.nodeChanged()}}}var n=e.settings,r=e.dom,i=e.selection,o=e.schema,a=o.getBlockElements();n.forced_root_block&&e.on("NodeChange",t)}}),r(P,[E,g,p],function(e,n,r){var i=r.each,o=r.extend,a=r.map,s=r.inArray,l=r.explode,c=n.gecko,u=n.ie,d=!0,f=!1;return function(r){function p(e,t,n){var r;return e=e.toLowerCase(),(r=N.exec[e])?(r(e,t,n),d):f}function h(e){var t;return e=e.toLowerCase(),(t=N.state[e])?t(e):-1}function m(e){var t;return e=e.toLowerCase(),(t=N.value[e])?t(e):f}function g(e,t){t=t||"exec",i(e,function(e,n){i(n.toLowerCase().split(","),function(n){N[t][n]=e})})}function v(e,n,i){return n===t&&(n=f),i===t&&(i=null),r.getDoc().execCommand(e,n,i)}function y(e){return k.match(e)}function b(e,n){k.toggle(e,n?{value:n}:t),r.nodeChanged()}function C(e){S=_.getBookmark(e)}function x(){_.moveToBookmark(S)}var w=r.dom,_=r.selection,N={state:{},exec:{},value:{}},E=r.settings,k=r.formatter,S;o(this,{execCommand:p,queryCommandState:h,queryCommandValue:m,addCommands:g}),g({"mceResetDesignMode,mceBeginUndoLevel":function(){},"mceEndUndoLevel,mceAddUndoLevel":function(){r.undoManager.add()},"Cut,Copy,Paste":function(e){var t=r.getDoc(),i;try{v(e)}catch(o){i=d}if(i||!t.queryCommandSupported(e)){var a=r.translate("Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X/C/V keyboard shortcuts instead.");n.mac&&(a=a.replace(/Ctrl\+/g,"\u2318+")),r.windowManager.alert(a)}},unlink:function(e){if(_.isCollapsed()){var t=_.getNode();return"A"==t.tagName&&r.dom.remove(t,!0),void 0}v(e),_.collapse(f)},"JustifyLeft,JustifyCenter,JustifyRight,JustifyFull":function(e){var t=e.substring(7);"full"==t&&(t="justify"),i("left,center,right,justify".split(","),function(e){t!=e&&k.remove("align"+e)}),b("align"+t),p("mceRepaint")},"InsertUnorderedList,InsertOrderedList":function(e){var t,n;v(e),t=w.getParent(_.getNode(),"ol,ul"),t&&(n=t.parentNode,/^(H[1-6]|P|ADDRESS|PRE)$/.test(n.nodeName)&&(C(),w.split(n,t),x()))},"Bold,Italic,Underline,Strikethrough,Superscript,Subscript":function(e){b(e)},"ForeColor,HiliteColor,FontName":function(e,t,n){b(e,n)},FontSize:function(e,t,n){var r,i;n>=1&&7>=n&&(i=l(E.font_size_style_values),r=l(E.font_size_classes),n=r?r[n-1]||n:i[n-1]||n),b(e,n)},RemoveFormat:function(e){k.remove(e)},mceBlockQuote:function(){b("blockquote")},FormatBlock:function(e,t,n){return b(n||"p")},mceCleanup:function(){var e=_.getBookmark();r.setContent(r.getContent({cleanup:d}),{cleanup:d}),_.moveToBookmark(e)},mceRemoveNode:function(e,t,n){var i=n||_.getNode();i!=r.getBody()&&(C(),r.dom.remove(i,d),x())},mceSelectNodeDepth:function(e,t,n){var i=0;w.getParent(_.getNode(),function(e){return 1==e.nodeType&&i++==n?(_.select(e),f):void 0},r.getBody())},mceSelectNode:function(e,t,n){_.select(n)},mceInsertContent:function(t,n,i){function o(e){function t(e){return r[e]&&3==r[e].nodeType}var n,r,i;return n=_.getRng(!0),r=n.startContainer,i=n.startOffset,3==r.nodeType&&(i>0?e=e.replace(/^ /," "):t("previousSibling")||(e=e.replace(/^ /," ")),i|)$/," "):t("nextSibling")||(e=e.replace(/( | )(
|)$/," "))),e}var a,s,l,c,d,f,p,h,m,g,v;/^ | $/.test(i)&&(i=o(i)),a=r.parser,s=new e({},r.schema),v='ÈB;',f={content:i,format:"html",selection:!0},r.fire("BeforeSetContent",f),i=f.content,-1==i.indexOf("{$caret}")&&(i+="{$caret}"),i=i.replace(/\{\$caret\}/,v),h=_.getRng();var y=h.startContainer||(h.parentElement?h.parentElement():null),b=r.getBody();y===b&&_.isCollapsed()&&w.isBlock(b.firstChild)&&w.isEmpty(b.firstChild)&&(h=w.createRng(),h.setStart(b.firstChild,0),h.setEnd(b.firstChild,0),_.setRng(h)),_.isCollapsed()||r.getDoc().execCommand("Delete",!1,null),l=_.getNode();var C={context:l.nodeName.toLowerCase()};if(d=a.parse(i,C),m=d.lastChild,"mce_marker"==m.attr("id"))for(p=m,m=m.prev;m;m=m.walk(!0))if(3==m.type||!w.isBlock(m.name)){m.parent.insert(p,m,"br"===m.name);break}if(C.invalid){for(_.setContent(v),l=_.getNode(),c=r.getBody(),9==l.nodeType?l=m=c:m=l;m!==c;)l=m,m=m.parentNode;i=l==c?c.innerHTML:w.getOuterHTML(l),i=s.serialize(a.parse(i.replace(//i,function(){return s.serialize(d)}))),l==c?w.setHTML(c,i):w.setOuterHTML(l,i)}else i=s.serialize(d),m=l.firstChild,g=l.lastChild,!m||m===g&&"BR"===m.nodeName?w.setHTML(l,i):_.setContent(i);p=w.get("mce_marker"),_.scrollIntoView(p),h=w.createRng(),m=p.previousSibling,m&&3==m.nodeType?(h.setStart(m,m.nodeValue.length),u||(g=p.nextSibling,g&&3==g.nodeType&&(m.appendData(g.data),g.parentNode.removeChild(g)))):(h.setStartBefore(p),h.setEndBefore(p)),w.remove(p),_.setRng(h),r.fire("SetContent",f),r.addVisual()},mceInsertRawHTML:function(e,t,n){_.setContent("tiny_mce_marker"),r.setContent(r.getContent().replace(/tiny_mce_marker/g,function(){return n}))},mceToggleFormat:function(e,t,n){b(n)},mceSetContent:function(e,t,n){r.setContent(n)},"Indent,Outdent":function(e){var t,n,r;t=E.indentation,n=/[a-z%]+$/i.exec(t),t=parseInt(t,10),h("InsertUnorderedList")||h("InsertOrderedList")?v(e):(E.forced_root_block||w.getParent(_.getNode(),w.isBlock)||k.apply("div"),i(_.getSelectedBlocks(),function(i){var o;"LI"!=i.nodeName&&(o="rtl"==w.getStyle(i,"direction",!0)?"paddingRight":"paddingLeft","outdent"==e?(r=Math.max(0,parseInt(i.style[o]||0,10)-t),w.setStyle(i,o,r?r+n:"")):(r=parseInt(i.style[o]||0,10)+t+n,w.setStyle(i,o,r)))}))},mceRepaint:function(){if(c)try{C(d),_.getSel()&&_.getSel().selectAllChildren(r.getBody()),_.collapse(d),x()}catch(e){}},InsertHorizontalRule:function(){r.execCommand("mceInsertContent",!1,"
")},mceToggleVisualAid:function(){r.hasVisual=!r.hasVisual,r.addVisual()},mceReplaceContent:function(e,t,n){r.execCommand("mceInsertContent",!1,n.replace(/\{\$selection\}/g,_.getContent({format:"text"})))},mceInsertLink:function(e,t,n){var r;"string"==typeof n&&(n={href:n}),r=w.getParent(_.getNode(),"a"),n.href=n.href.replace(" ","%20"),r&&n.href||k.remove("link"),n.href&&k.apply("link",n,r)},selectAll:function(){var e=w.getRoot(),t;_.getRng().setStart?(t=w.createRng(),t.setStart(e,0),t.setEnd(e,e.childNodes.length),_.setRng(t)):(t=_.getRng(),t.item||(t.moveToElementText(e),t.select()))},"delete":function(){v("Delete");var e=r.getBody();w.isEmpty(e)&&(r.setContent(""),e.firstChild&&w.isBlock(e.firstChild)?r.selection.setCursorLocation(e.firstChild,0):r.selection.setCursorLocation(e,0))},mceNewDocument:function(){r.setContent("")}}),g({"JustifyLeft,JustifyCenter,JustifyRight,JustifyFull":function(e){var t="align"+e.substring(7),n=_.isCollapsed()?[w.getParent(_.getNode(),w.isBlock)]:_.getSelectedBlocks(),r=a(n,function(e){return!!k.matchNode(e,t)});return-1!==s(r,d)},"Bold,Italic,Underline,Strikethrough,Superscript,Subscript":function(e){return y(e)},mceBlockQuote:function(){return y("blockquote")},Outdent:function(){var e;if(E.inline_styles){if((e=w.getParent(_.getStart(),w.isBlock))&&parseInt(e.style.paddingLeft,10)>0)return d;if((e=w.getParent(_.getEnd(),w.isBlock))&&parseInt(e.style.paddingLeft,10)>0)return d}return h("InsertUnorderedList")||h("InsertOrderedList")||!E.inline_styles&&!!w.getParent(_.getNode(),"BLOCKQUOTE")},"InsertUnorderedList,InsertOrderedList":function(e){var t=w.getParent(_.getNode(),"ul,ol");return t&&("insertunorderedlist"===e&&"UL"===t.tagName||"insertorderedlist"===e&&"OL"===t.tagName)}},"state"),g({"FontSize,FontName":function(e){var t=0,n;return(n=w.getParent(_.getNode(),"span"))&&(t="fontsize"==e?n.style.fontSize:n.style.fontFamily.replace(/, /g,",").replace(/[\'\"]/g,"").toLowerCase()),t}},"value"),g({Undo:function(){r.undoManager.undo()},Redo:function(){r.undoManager.redo()}})}}),r(O,[p],function(e){function t(e,i){var o=this,a,s;if(e=r(e),i=o.settings=i||{},/^([\w\-]+):([^\/]{2})/i.test(e)||/^\s*#/.test(e))return o.source=e,void 0;var l=0===e.indexOf("//");0!==e.indexOf("/")||l||(e=(i.base_uri?i.base_uri.protocol||"http":"http")+"://mce_host"+e),/^[\w\-]*:?\/\//.test(e)||(s=i.base_uri?i.base_uri.path:new t(location.href).directory,e=""===i.base_uri.protocol?"//mce_host"+o.toAbsPath(s,e):(i.base_uri&&i.base_uri.protocol||"http")+"://mce_host"+o.toAbsPath(s,e)),e=e.replace(/@@/g,"(mce_at)"),e=/^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(e),n(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"],function(t,n){var r=e[n];r&&(r=r.replace(/\(mce_at\)/g,"@@")),o[t]=r}),a=i.base_uri,a&&(o.protocol||(o.protocol=a.protocol),o.userInfo||(o.userInfo=a.userInfo),o.port||"mce_host"!==o.host||(o.port=a.port),o.host&&"mce_host"!==o.host||(o.host=a.host),o.source=""),l&&(o.protocol="")}var n=e.each,r=e.trim;return t.prototype={setPath:function(e){var t=this;e=/^(.*?)\/?(\w+)?$/.exec(e),t.path=e[0],t.directory=e[1],t.file=e[2],t.source="",t.getURI()},toRelative:function(e){var n=this,r;if("./"===e)return e;if(e=new t(e,{base_uri:n}),"mce_host"!=e.host&&n.host!=e.host&&e.host||n.port!=e.port||n.protocol!=e.protocol&&""!==e.protocol)return e.getURI();var i=n.getURI(),o=e.getURI();return i==o||"/"==i.charAt(i.length-1)&&i.substr(0,i.length-1)==o?i:(r=n.toRelPath(n.path,e.path),e.query&&(r+="?"+e.query),e.anchor&&(r+="#"+e.anchor),r)},toAbsolute:function(e,n){return e=new t(e,{base_uri:this}),e.getURI(this.host==e.host&&this.protocol==e.protocol?n:0)},toRelPath:function(e,t){var n,r=0,i="",o,a;if(e=e.substring(0,e.lastIndexOf("/")),e=e.split("/"),n=t.split("/"),e.length>=n.length)for(o=0,a=e.length;a>o;o++)if(o>=n.length||e[o]!=n[o]){r=o+1;break}if(e.lengtho;o++)if(o>=e.length||e[o]!=n[o]){r=o+1;break}if(1===r)return t;for(o=0,a=e.length-(r-1);a>o;o++)i+="../";for(o=r-1,a=n.length;a>o;o++)i+=o!=r-1?"/"+n[o]:n[o];return i},toAbsPath:function(e,t){var r,i=0,o=[],a,s;for(a=/\/$/.test(t)?"/":"",e=e.split("/"),t=t.split("/"),n(e,function(e){e&&o.push(e)}),e=o,r=t.length-1,o=[];r>=0;r--)0!==t[r].length&&"."!==t[r]&&(".."!==t[r]?i>0?i--:o.push(t[r]):i++);return r=e.length-i,s=0>=r?o.reverse().join("/"):e.slice(0,r).join("/")+"/"+o.reverse().join("/"),0!==s.indexOf("/")&&(s="/"+s),a&&s.lastIndexOf("/")!==s.length-1&&(s+=a),s},getURI:function(e){var t,n=this;return(!n.source||e)&&(t="",e||(t+=n.protocol?n.protocol+"://":"//",n.userInfo&&(t+=n.userInfo+"@"),n.host&&(t+=n.host),n.port&&(t+=":"+n.port)),n.path&&(t+=n.path),n.query&&(t+="?"+n.query),n.anchor&&(t+="#"+n.anchor),n.source=t),n.source}},t}),r(I,[p],function(e){function t(){}var n=e.each,r=e.extend,i,o;return t.extend=i=function(e){function t(){var e,t,n,r;if(!o&&(r=this,r.init&&r.init.apply(r,arguments),t=r.Mixins))for(e=t.length;e--;)n=t[e],n.init&&n.init.apply(r,arguments)}function a(){return this}function s(e,t){return function(){var n=this,r=n._super,i;return n._super=c[e],i=t.apply(n,arguments),n._super=r,i}}var l=this,c=l.prototype,u,d,f;o=!0,u=new l,o=!1,e.Mixins&&(n(e.Mixins,function(t){t=t;for(var n in t)"init"!==n&&(e[n]=t[n])}),c.Mixins&&(e.Mixins=c.Mixins.concat(e.Mixins))),e.Methods&&n(e.Methods.split(","),function(t){e[t]=a}),e.Properties&&n(e.Properties.split(","),function(t){var n="_"+t;e[t]=function(e){var t=this,r;return e!==r?(t[n]=e,t):t[n]}}),e.Statics&&n(e.Statics,function(e,n){t[n]=e}),e.Defaults&&c.Defaults&&(e.Defaults=r({},c.Defaults,e.Defaults));for(d in e)f=e[d],u[d]="function"==typeof f&&c[d]?s(d,f):f;return t.prototype=u,t.constructor=t,t.extend=i,t -},t}),r(F,[I],function(e){function t(e){for(var t=[],n=e.length,r;n--;)r=e[n],r.__checked||(t.push(r),r.__checked=1);for(n=t.length;n--;)delete t[n].__checked;return t}var n=/^([\w\\*]+)?(?:#([\w\\]+))?(?:\.([\w\\\.]+))?(?:\[\@?([\w\\]+)([\^\$\*!~]?=)([\w\\]+)\])?(?:\:(.+))?/i,r=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,i=/^\s*|\s*$/g,o,a=e.extend({init:function(e){function t(e){return e?(e=e.toLowerCase(),function(t){return"*"===e||t.type===e}):void 0}function o(e){return e?function(t){return t._name===e}:void 0}function a(e){return e?(e=e.split("."),function(t){for(var n=e.length;n--;)if(!t.hasClass(e[n]))return!1;return!0}):void 0}function s(e,t,n){return e?function(r){var i=r[e]?r[e]():"";return t?"="===t?i===n:"*="===t?i.indexOf(n)>=0:"~="===t?(" "+i+" ").indexOf(" "+n+" ")>=0:"!="===t?i!=n:"^="===t?0===i.indexOf(n):"$="===t?i.substr(i.length-n.length)===n:!1:!!n}:void 0}function l(e){var t;return e?(e=/(?:not\((.+)\))|(.+)/i.exec(e),e[1]?(t=u(e[1],[]),function(e){return!d(e,t)}):(e=e[2],function(t,n,r){return"first"===e?0===n:"last"===e?n===r-1:"even"===e?n%2===0:"odd"===e?n%2===1:t[e]?t[e]():!1})):void 0}function c(e,r,c){function u(e){e&&r.push(e)}var d;return d=n.exec(e.replace(i,"")),u(t(d[1])),u(o(d[2])),u(a(d[3])),u(s(d[4],d[5],d[6])),u(l(d[7])),r.psuedo=!!d[7],r.direct=c,r}function u(e,t){var n=[],i,o,a;do if(r.exec(""),o=r.exec(e),o&&(e=o[3],n.push(o[1]),o[2])){i=o[3];break}while(o);for(i&&u(i,t),e=[],a=0;a"!=n[a]&&e.push(c(n[a],[],">"===n[a-1]));return t.push(e),t}var d=this.match;this._selectors=u(e,[])},match:function(e,t){var n,r,i,o,a,s,l,c,u,d,f,p,h;for(t=t||this._selectors,n=0,r=t.length;r>n;n++){for(a=t[n],o=a.length,h=e,p=0,i=o-1;i>=0;i--)for(c=a[i];h;){if(c.psuedo)for(f=h.parent().items(),u=d=f.length;u--&&f[u]!==h;);for(s=0,l=c.length;l>s;s++)if(!c[s](h,u,d)){s=l+1;break}if(s===l){p++;break}if(i===o-1)break;h=h.parent()}if(p===o)return!0}return!1},find:function(e){function n(e,t,i){var o,a,s,l,c,u=t[i];for(o=0,a=e.length;a>o;o++){for(c=e[o],s=0,l=u.length;l>s;s++)if(!u[s](c,o,a)){s=l+1;break}if(s===l)i==t.length-1?r.push(c):c.items&&n(c.items(),t,i+1);else if(u.direct)return;c.items&&n(c.items(),t,i)}}var r=[],i,s,l=this._selectors;if(e.items){for(i=0,s=l.length;s>i;i++)n(e.items(),l[i],0);s>1&&(r=t(r))}return o||(o=a.Collection),new o(r)}});return a}),r(z,[p,F,I],function(e,t,n){var r,i,o=Array.prototype.push,a=Array.prototype.slice;return i={length:0,init:function(e){e&&this.add(e)},add:function(t){var n=this;return e.isArray(t)?o.apply(n,t):t instanceof r?n.add(t.toArray()):o.call(n,t),n},set:function(e){var t=this,n=t.length,r;for(t.length=0,t.add(e),r=t.length;n>r;r++)delete t[r];return t},filter:function(e){var n=this,i,o,a=[],s,l;for("string"==typeof e?(e=new t(e),l=function(t){return e.match(t)}):l=e,i=0,o=n.length;o>i;i++)s=n[i],l(s)&&a.push(s);return new r(a)},slice:function(){return new r(a.apply(this,arguments))},eq:function(e){return-1===e?this.slice(e):this.slice(e,+e+1)},each:function(t){return e.each(this,t),this},toArray:function(){return e.toArray(this)},indexOf:function(e){for(var t=this,n=t.length;n--&&t[n]!==e;);return n},reverse:function(){return new r(e.toArray(this).reverse())},hasClass:function(e){return this[0]?this[0].hasClass(e):!1},prop:function(e,t){var n=this,r,i;return t!==r?(n.each(function(n){n[e]&&n[e](t)}),n):(i=n[0],i&&i[e]?i[e]():void 0)},exec:function(t){var n=this,r=e.toArray(arguments).slice(1);return n.each(function(e){e[t]&&e[t].apply(e,r)}),n},remove:function(){for(var e=this.length;e--;)this[e].remove();return this}},e.each("fire on off show hide addClass removeClass append prepend before after reflow".split(" "),function(t){i[t]=function(){var n=e.toArray(arguments);return this.each(function(e){t in e&&e[t].apply(e,n)}),this}}),e.each("text name disabled active selected checked visible parent value data".split(" "),function(e){i[e]=function(t){return this.prop(e,t)}}),r=n.extend(i),t.Collection=r,r}),r(W,[p,v],function(e,t){return{id:function(){return t.DOM.uniqueId()},createFragment:function(e){return t.DOM.createFragment(e)},getWindowSize:function(){return t.DOM.getViewPort()},getSize:function(e){var t,n;if(e.getBoundingClientRect){var r=e.getBoundingClientRect();t=Math.max(r.width||r.right-r.left,e.offsetWidth),n=Math.max(r.height||r.bottom-r.bottom,e.offsetHeight)}else t=e.offsetWidth,n=e.offsetHeight;return{width:t,height:n}},getPos:function(e,n){return t.DOM.getPos(e,n)},getViewPort:function(e){return t.DOM.getViewPort(e)},get:function(e){return document.getElementById(e)},addClass:function(e,n){return t.DOM.addClass(e,n)},removeClass:function(e,n){return t.DOM.removeClass(e,n)},hasClass:function(e,n){return t.DOM.hasClass(e,n)},toggleClass:function(e,n,r){return t.DOM.toggleClass(e,n,r)},css:function(e,n,r){return t.DOM.setStyle(e,n,r)},on:function(e,n,r,i){return t.DOM.bind(e,n,r,i)},off:function(e,n,r){return t.DOM.unbind(e,n,r)},fire:function(e,n,r){return t.DOM.fire(e,n,r)},innerHtml:function(e,n){t.DOM.setHTML(e,n)}}}),r(V,[I,p,z,W],function(e,t,n,r){var i=t.makeMap("focusin focusout scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave wheel keydown keypress keyup contextmenu"," "),o={},a="onmousewheel"in document,s=!1,l=e.extend({Statics:{controlIdLookup:{},elementIdCache:o},isRtl:function(){return l.rtl},classPrefix:"mce-",init:function(e){var n=this,i,o;if(n.settings=e=t.extend({},n.Defaults,e),n._id=r.id(),n._text=n._name="",n._width=n._height=0,n._aria={role:e.role},i=e.classes)for(i=i.split(" "),i.map={},o=i.length;o--;)i.map[i[o]]=!0;n._classes=i||[],n.visible(!0),t.each("title text width height name classes visible disabled active value".split(" "),function(t){var r=e[t],i;r!==i?n[t](r):n["_"+t]===i&&(n["_"+t]=!1)}),n.on("click",function(){return n.disabled()?!1:void 0}),e.classes&&t.each(e.classes.split(" "),function(e){n.addClass(e)}),n.settings=e,n._borderBox=n.parseBox(e.border),n._paddingBox=n.parseBox(e.padding),n._marginBox=n.parseBox(e.margin),e.hidden&&n.hide()},Properties:"parent,title,text,width,height,disabled,active,name,value",Methods:"renderHtml",getContainerElm:function(){return document.body},getParentCtrl:function(e){for(var t;e&&!(t=l.controlIdLookup[e.id]);)e=e.parentNode;return t},parseBox:function(e){var t,n=10;if(e)return"number"==typeof e?(e=e||0,{top:e,left:e,bottom:e,right:e}):(e=e.split(" "),t=e.length,1===t?e[1]=e[2]=e[3]=e[0]:2===t?(e[2]=e[0],e[3]=e[1]):3===t&&(e[3]=e[1]),{top:parseInt(e[0],n)||0,right:parseInt(e[1],n)||0,bottom:parseInt(e[2],n)||0,left:parseInt(e[3],n)||0})},borderBox:function(){return this._borderBox},paddingBox:function(){return this._paddingBox},marginBox:function(){return this._marginBox},measureBox:function(e,t){function n(t){var n=document.defaultView;return n?(t=t.replace(/[A-Z]/g,function(e){return"-"+e}),n.getComputedStyle(e,null).getPropertyValue(t)):e.currentStyle[t]}function r(e){var t=parseFloat(n(e),10);return isNaN(t)?0:t}return{top:r(t+"TopWidth"),right:r(t+"RightWidth"),bottom:r(t+"BottomWidth"),left:r(t+"LeftWidth")}},initLayoutRect:function(){var e=this,t=e.settings,n,i,o=e.getEl(),a,s,l,c,u,d,f,p;n=e._borderBox=e._borderBox||e.measureBox(o,"border"),e._paddingBox=e._paddingBox||e.measureBox(o,"padding"),e._marginBox=e._marginBox||e.measureBox(o,"margin"),p=r.getSize(o),d=t.minWidth,f=t.minHeight,l=d||p.width,c=f||p.height,a=t.width,s=t.height,u=t.autoResize,u="undefined"!=typeof u?u:!a&&!s,a=a||l,s=s||c;var h=n.left+n.right,m=n.top+n.bottom,g=t.maxWidth||65535,v=t.maxHeight||65535;return e._layoutRect=i={x:t.x||0,y:t.y||0,w:a,h:s,deltaW:h,deltaH:m,contentW:a-h,contentH:s-m,innerW:a-h,innerH:s-m,startMinWidth:d||0,startMinHeight:f||0,minW:Math.min(l,g),minH:Math.min(c,v),maxW:g,maxH:v,autoResize:u,scrollW:0},e._lastLayoutRect={},i},layoutRect:function(e){var t=this,n=t._layoutRect,r,i,o,a,s,c;return n||(n=t.initLayoutRect()),e?(o=n.deltaW,a=n.deltaH,e.x!==s&&(n.x=e.x),e.y!==s&&(n.y=e.y),e.minW!==s&&(n.minW=e.minW),e.minH!==s&&(n.minH=e.minH),i=e.w,i!==s&&(i=in.maxW?n.maxW:i,n.w=i,n.innerW=i-o),i=e.h,i!==s&&(i=in.maxH?n.maxH:i,n.h=i,n.innerH=i-a),i=e.innerW,i!==s&&(i=in.maxW-o?n.maxW-o:i,n.innerW=i,n.w=i+o),i=e.innerH,i!==s&&(i=in.maxH-a?n.maxH-a:i,n.innerH=i,n.h=i+a),e.contentW!==s&&(n.contentW=e.contentW),e.contentH!==s&&(n.contentH=e.contentH),r=t._lastLayoutRect,(r.x!==n.x||r.y!==n.y||r.w!==n.w||r.h!==n.h)&&(c=l.repaintControls,c&&c.map&&!c.map[t._id]&&(c.push(t),c.map[t._id]=!0),r.x=n.x,r.y=n.y,r.w=n.w,r.h=n.h),t):n},repaint:function(){var e=this,t,n,r,i,o=0,a=0,s,l;l=document.createRange?function(e){return e}:Math.round,t=e.getEl().style,r=e._layoutRect,s=e._lastRepaintRect||{},i=e._borderBox,o=i.left+i.right,a=i.top+i.bottom,r.x!==s.x&&(t.left=l(r.x)+"px",s.x=r.x),r.y!==s.y&&(t.top=l(r.y)+"px",s.y=r.y),r.w!==s.w&&(t.width=l(r.w-o)+"px",s.w=r.w),r.h!==s.h&&(t.height=l(r.h-a)+"px",s.h=r.h),e._hasBody&&r.innerW!==s.innerW&&(n=e.getEl("body").style,n.width=l(r.innerW)+"px",s.innerW=r.innerW),e._hasBody&&r.innerH!==s.innerH&&(n=n||e.getEl("body").style,n.height=l(r.innerH)+"px",s.innerH=r.innerH),e._lastRepaintRect=s,e.fire("repaint",{},!1)},on:function(e,t){function n(e){var t,n;return function(i){return t||r.parents().each(function(r){var i=r.settings.callbacks;return i&&(t=i[e])?(n=r,!1):void 0}),t.call(n,i)}}var r=this,o,a,s,l;if(t)for("string"==typeof t&&(t=n(t)),s=e.toLowerCase().split(" "),l=s.length;l--;)e=s[l],o=r._bindings,o||(o=r._bindings={}),a=o[e],a||(a=o[e]=[]),a.push(t),i[e]&&(r._nativeEvents?r._nativeEvents[e]=!0:r._nativeEvents={name:!0},r._rendered&&r.bindPendingEvents());return r},off:function(e,t){var n=this,r,i=n._bindings,o,a,s,l;if(i)if(e)for(s=e.toLowerCase().split(" "),r=s.length;r--;){if(e=s[r],o=i[e],!e){for(a in i)i[a].length=0;return n}if(o)if(t)for(l=o.length;l--;)o[l]===t&&o.splice(l,1);else o.length=0}else n._bindings=[];return n},fire:function(e,t,n){function r(){return!1}function i(){return!0}var o=this,a,s,l,c;if(e=e.toLowerCase(),t=t||{},t.type||(t.type=e),t.control||(t.control=o),t.preventDefault||(t.preventDefault=function(){t.isDefaultPrevented=i},t.stopPropagation=function(){t.isPropagationStopped=i},t.stopImmediatePropagation=function(){t.isImmediatePropagationStopped=i},t.isDefaultPrevented=r,t.isPropagationStopped=r,t.isImmediatePropagationStopped=r),o._bindings&&(l=o._bindings[e]))for(a=0,s=l.length;s>a&&(t.isImmediatePropagationStopped()||l[a].call(o,t)!==!1);a++);if(n!==!1)for(c=o.parent();c&&!t.isPropagationStopped();)c.fire(e,t,!1),c=c.parent();return t},hasEventListeners:function(e){return e in this._bindings},parents:function(e){var t=this,r=new n;for(t=t.parent();t;t=t.parent())r.add(t);return e&&(r=r.filter(e)),r},next:function(){var e=this.parent().items();return e[e.indexOf(this)+1]},prev:function(){var e=this.parent().items();return e[e.indexOf(this)-1]},findCommonAncestor:function(e,t){for(var n;e;){for(n=t;n&&e!=n;)n=n.parent();if(e==n)break;e=e.parent()}return e},hasClass:function(e,t){var n=this._classes[t||"control"];return e=this.classPrefix+e,n&&!!n.map[e]},addClass:function(e,t){var n=this,r,i;return e=this.classPrefix+e,r=n._classes[t||"control"],r||(r=[],r.map={},n._classes[t||"control"]=r),r.map[e]||(r.map[e]=e,r.push(e),n._rendered&&(i=n.getEl(t),i&&(i.className=r.join(" ")))),n},removeClass:function(e,t){var n=this,r,i,o;if(e=this.classPrefix+e,r=n._classes[t||"control"],r&&r.map[e])for(delete r.map[e],i=r.length;i--;)r[i]===e&&r.splice(i,1);return n._rendered&&(o=n.getEl(t),o&&(o.className=r.join(" "))),n},toggleClass:function(e,t,n){var r=this;return t?r.addClass(e,n):r.removeClass(e,n),r},classes:function(e){var t=this._classes[e||"control"];return t?t.join(" "):""},innerHtml:function(e){return r.innerHtml(this.getEl(),e),this},getEl:function(e,t){var n,i=e?this._id+"-"+e:this._id;return n=o[i]=(t===!0?null:o[i])||r.get(i)},visible:function(e){var t=this,n;return"undefined"!=typeof e?(t._visible!==e&&(t._rendered&&(t.getEl().style.display=e?"":"none"),t._visible=e,n=t.parent(),n&&(n._lastRect=null),t.fire(e?"show":"hide")),t):t._visible},show:function(){return this.visible(!0)},hide:function(){return this.visible(!1)},focus:function(){try{this.getEl().focus()}catch(e){}return this},blur:function(){return this.getEl().blur(),this},aria:function(e,t){var n=this,r=n.getEl();return"undefined"==typeof t?n._aria[e]:(n._aria[e]=t,n._rendered&&("label"==e&&r.setAttribute("aria-labeledby",n._id),r.setAttribute("role"==e?e:"aria-"+e,t)),n)},encode:function(e,t){return t!==!1&&l.translate&&(e=l.translate(e)),(e||"").replace(/[&<>"]/g,function(e){return"&#"+e.charCodeAt(0)+";"})},before:function(e){var t=this,n=t.parent();return n&&n.insert(e,n.items().indexOf(t),!0),t},after:function(e){var t=this,n=t.parent();return n&&n.insert(e,n.items().indexOf(t)),t},remove:function(){var e=this,t=e.getEl(),n=e.parent(),i,a;if(e.items){var s=e.items().toArray();for(a=s.length;a--;)s[a].remove()}if(n&&n.items&&(i=[],n.items().each(function(t){t!==e&&i.push(t)}),n.items().set(i),n._lastRect=null),e._eventsRoot&&e._eventsRoot==e&&r.off(t),delete l.controlIdLookup[e._id],delete o[e._id],t&&t.parentNode){var c=t.getElementsByTagName("*");for(a=c.length;a--;)delete o[c[a].id];t.parentNode.removeChild(t)}return e},renderBefore:function(e){var t=this;return e.parentNode.insertBefore(r.createFragment(t.renderHtml()),e),t.postRender(),t},renderTo:function(e){var t=this;return e=e||t.getContainerElm(),e.appendChild(r.createFragment(t.renderHtml())),t.postRender(),t},postRender:function(){var e=this,t=e.settings,n,i,o,a,s;for(a in t)0===a.indexOf("on")&&e.on(a.substr(2),t[a]);if(e._eventsRoot){for(o=e.parent();!s&&o;o=o.parent())s=o._eventsRoot;if(s)for(a in s._nativeEvents)e._nativeEvents[a]=!0}e.bindPendingEvents(),t.style&&(n=e.getEl(),n&&(n.setAttribute("style",t.style),n.style.cssText=t.style)),e._visible||r.css(e.getEl(),"display","none"),e.settings.border&&(i=e.borderBox(),r.css(e.getEl(),{"border-top-width":i.top,"border-right-width":i.right,"border-bottom-width":i.bottom,"border-left-width":i.left})),l.controlIdLookup[e._id]=e;for(var c in e._aria)e.aria(c,e._aria[c]);e.fire("postrender",{},!1)},scrollIntoView:function(e){function t(e,t){var n,r,i=e;for(n=r=0;i&&i!=t&&i.nodeType;)n+=i.offsetLeft||0,r+=i.offsetTop||0,i=i.offsetParent;return{x:n,y:r}}var n=this.getEl(),r=n.parentNode,i,o,a,s,l,c,u=t(n,r);return i=u.x,o=u.y,a=n.offsetWidth,s=n.offsetHeight,l=r.clientWidth,c=r.clientHeight,"end"==e?(i-=l-a,o-=c-s):"center"==e&&(i-=l/2-a/2,o-=c/2-s/2),r.scrollLeft=i,r.scrollTop=o,this},bindPendingEvents:function(){function e(e){var t=o.getParentCtrl(e.target);t&&t.fire(e.type,e)}function t(){var e=d._lastHoverCtrl;e&&(e.fire("mouseleave",{target:e.getEl()}),e.parents().each(function(e){e.fire("mouseleave",{target:e.getEl()})}),d._lastHoverCtrl=null)}function n(e){var t=o.getParentCtrl(e.target),n=d._lastHoverCtrl,r=0,i,a,s;if(t!==n){if(d._lastHoverCtrl=t,a=t.parents().toArray().reverse(),a.push(t),n){for(s=n.parents().toArray().reverse(),s.push(n),r=0;r=r;i--)n=s[i],n.fire("mouseleave",{target:n.getEl()})}for(i=r;il;l++)d=u[l]._eventsRoot;for(d||(d=u[u.length-1]||o),o._eventsRoot=d,c=l,l=0;c>l;l++)u[l]._eventsRoot=d;for(p in f){if(!f)return!1;"wheel"!==p||s?("mouseenter"===p||"mouseleave"===p?d._hasMouseEnter||(r.on(d.getEl(),"mouseleave",t),r.on(d.getEl(),"mouseover",n),d._hasMouseEnter=1):d[p]||(r.on(d.getEl(),p,e),d[p]=!0),f[p]=!1):a?r.on(o.getEl(),"mousewheel",i):r.on(o.getEl(),"DOMMouseScroll",i)}}},reflow:function(){return this.repaint(),this}});return l}),r(U,[],function(){var e={},t;return{add:function(t,n){e[t.toLowerCase()]=n},has:function(t){return!!e[t.toLowerCase()]},create:function(n,r){var i,o,a;if(!t){a=tinymce.ui;for(o in a)e[o.toLowerCase()]=a[o];t=!0}if("string"==typeof n?(r=r||{},r.type=n):(r=n,n=r.type),n=n.toLowerCase(),i=e[n],!i)throw new Error("Could not find control by type: "+n);return i=new i(r),i.type=n,i}}}),r(q,[V,z,F,U,p,W],function(e,t,n,r,i,o){var a={};return e.extend({layout:"",innerClass:"container-inner",init:function(e){var n=this;n._super(e),e=n.settings,n._fixed=e.fixed,n._items=new t,n.isRtl()&&n.addClass("rtl"),n.addClass("container"),n.addClass("container-body","body"),e.containerCls&&n.addClass(e.containerCls),n._layout=r.create((e.layout||n.layout)+"layout"),n.settings.items&&n.add(n.settings.items),n._hasBody=!0},items:function(){return this._items},find:function(e){return e=a[e]=a[e]||new n(e),e.find(this)},add:function(e){var t=this;return t.items().add(t.create(e)).parent(t),t},focus:function(){var e=this;return e.keyNav?e.keyNav.focusFirst():e._super(),e},replace:function(e,t){for(var n,r=this.items(),i=r.length;i--;)if(r[i]===e){r[i]=t;break}i>=0&&(n=t.getEl(),n&&n.parentNode.removeChild(n),n=e.getEl(),n&&n.parentNode.removeChild(n)),t.parent(this)},create:function(t){var n=this,o,a=[];return i.isArray(t)||(t=[t]),i.each(t,function(t){t&&(t instanceof e||("string"==typeof t&&(t={type:t}),o=i.extend({},n.settings.defaults,t),t.type=o.type=o.type||t.type||n.settings.defaultType||(o.defaults?o.defaults.type:null),t=r.create(o)),a.push(t))}),a},renderNew:function(){var e=this;return e.items().each(function(t,n){var r,i;t.parent(e),t._rendered||(r=e.getEl("body"),i=o.createFragment(t.renderHtml()),r.hasChildNodes()&&n<=r.childNodes.length-1?r.insertBefore(i,r.childNodes[n]):r.appendChild(i),t.postRender())}),e._layout.applyClasses(e),e._lastRect=null,e},append:function(e){return this.add(e).renderNew()},prepend:function(e){var t=this;return t.items().set(t.create(e).concat(t.items().toArray())),t.renderNew()},insert:function(e,t,n){var r=this,i,o,a;return e=r.create(e),i=r.items(),!n&&t=0&&t
'+(e.settings.html||"")+t.renderHtml(e)+"
"},postRender:function(){var e=this,t;return e.items().exec("postRender"),e._super(),e._layout.postRender(e),e._rendered=!0,e.settings.style&&o.css(e.getEl(),e.settings.style),e.settings.border&&(t=e.borderBox(),o.css(e.getEl(),{"border-top-width":t.top,"border-right-width":t.right,"border-bottom-width":t.bottom,"border-left-width":t.left})),e},initLayoutRect:function(){var e=this,t=e._super();return e._layout.recalc(e),t},recalc:function(){var e=this,t=e._layoutRect,n=e._lastRect;return n&&n.w==t.w&&n.h==t.h?void 0:(e._layout.recalc(e),t=e.layoutRect(),e._lastRect={x:t.x,y:t.y,w:t.w,h:t.h},!0)},reflow:function(){var t,n;if(this.visible()){for(e.repaintControls=[],e.repaintControls.map={},n=this.recalc(),t=e.repaintControls.length;t--;)e.repaintControls[t].repaint();"flow"!==this.settings.layout&&"stack"!==this.settings.layout&&this.repaint(),e.repaintControls=[]}return this}})}),r($,[W],function(e){function t(){var e=document,t,n,r,i,o,a,s,l,c=Math.max;return t=e.documentElement,n=e.body,r=c(t.scrollWidth,n.scrollWidth),i=c(t.clientWidth,n.clientWidth),o=c(t.offsetWidth,n.offsetWidth),a=c(t.scrollHeight,n.scrollHeight),s=c(t.clientHeight,n.clientHeight),l=c(t.offsetHeight,n.offsetHeight),{width:o>r?i:r,height:l>a?s:a}}return function(n,r){function i(){return a.getElementById(r.handle||n)}var o,a=document,s,l,c,u,d,f;r=r||{},l=function(n){var l=t(),p,h;n.preventDefault(),s=n.button,p=i(),d=n.screenX,f=n.screenY,h=window.getComputedStyle?window.getComputedStyle(p,null).getPropertyValue("cursor"):p.runtimeStyle.cursor,o=a.createElement("div"),e.css(o,{position:"absolute",top:0,left:0,width:l.width,height:l.height,zIndex:2147483647,opacity:1e-4,background:"red",cursor:h}),a.body.appendChild(o),e.on(a,"mousemove",u),e.on(a,"mouseup",c),r.start(n)},u=function(e){return e.button!==s?c(e):(e.deltaX=e.screenX-d,e.deltaY=e.screenY-f,e.preventDefault(),r.drag(e),void 0)},c=function(t){e.off(a,"mousemove",u),e.off(a,"mouseup",c),o.parentNode.removeChild(o),r.stop&&r.stop(t)},this.destroy=function(){e.off(i())},e.on(i(),"mousedown",l)}}),r(j,[W,$],function(e,t){return{init:function(){var e=this;e.on("repaint",e.renderScroll)},renderScroll:function(){function n(){function t(t,a,s,l,c,u){var d,f,p,h,m,g,v,y,b;if(f=i.getEl("scroll"+t)){if(y=a.toLowerCase(),b=s.toLowerCase(),i.getEl("absend")&&e.css(i.getEl("absend"),y,i.layoutRect()[l]-1),!c)return e.css(f,"display","none"),void 0;e.css(f,"display","block"),d=i.getEl("body"),p=i.getEl("scroll"+t+"t"),h=d["client"+s]-2*o,h-=n&&r?f["client"+u]:0,m=d["scroll"+s],g=h/m,v={},v[y]=d["offset"+a]+o,v[b]=h,e.css(f,v),v={},v[y]=d["scroll"+a]*g,v[b]=h*g,e.css(p,v)}}var n,r,a;a=i.getEl("body"),n=a.scrollWidth>a.clientWidth,r=a.scrollHeight>a.clientHeight,t("h","Left","Width","contentW",n,"Height"),t("v","Top","Height","contentH",r,"Width")}function r(){function n(n,r,a,s,l){var c,u=i._id+"-scroll"+n,d=i.classPrefix;i.getEl().appendChild(e.createFragment('
')),i.draghelper=new t(u+"t",{start:function(){c=i.getEl("body")["scroll"+r],e.addClass(e.get(u),d+"active")},drag:function(e){var t,u,d,f,p=i.layoutRect();u=p.contentW>p.innerW,d=p.contentH>p.innerH,f=i.getEl("body")["client"+a]-2*o,f-=u&&d?i.getEl("scroll"+n)["client"+l]:0,t=f/i.getEl("body")["scroll"+a],i.getEl("body")["scroll"+r]=c+e["delta"+s]/t},stop:function(){e.removeClass(e.get(u),d+"active")}})}i.addClass("scroll"),n("v","Top","Height","Y","Width"),n("h","Left","Width","X","Height")}var i=this,o=2;i.settings.autoScroll&&(i._hasScroll||(i._hasScroll=!0,r(),i.on("wheel",function(e){var t=i.getEl("body");t.scrollLeft+=10*(e.deltaX||0),t.scrollTop+=10*e.deltaY,n()}),e.on(i.getEl("body"),"scroll",n)),n())}}}),r(K,[q,j],function(e,t){return e.extend({Defaults:{layout:"fit",containerCls:"panel"},Mixins:[t],renderHtml:function(){var e=this,t=e._layout,n=e.settings.html;return e.preRender(),t.preRender(e),"undefined"==typeof n?n='
'+t.renderHtml(e)+"
":("function"==typeof n&&(n=n.call(e)),e._hasBody=!1),'
'+(e._preBodyHtml||"")+n+"
"}})}),r(G,[W],function(e){function t(t,n,r){var i,o,a,s,l,c,u,d,f,p;return f=e.getViewPort(),o=e.getPos(n),a=o.x,s=o.y,t._fixed&&(a-=f.x,s-=f.y),i=t.getEl(),p=e.getSize(i),l=p.width,c=p.height,p=e.getSize(n),u=p.width,d=p.height,r=(r||"").split(""),"b"===r[0]&&(s+=d),"r"===r[1]&&(a+=u),"c"===r[0]&&(s+=Math.round(d/2)),"c"===r[1]&&(a+=Math.round(u/2)),"b"===r[3]&&(s-=c),"r"===r[4]&&(a-=l),"c"===r[3]&&(s-=Math.round(c/2)),"c"===r[4]&&(a-=Math.round(l/2)),{x:a,y:s,w:l,h:c}}return{testMoveRel:function(n,r){for(var i=e.getViewPort(),o=0;o0&&a.x+a.w0&&a.y+a.hi.x&&a.x+a.wi.y&&a.y+a.he?0:e+n>t?(e=t-n,0>e?0:e):e}var i=this;if(i.settings.constrainToViewport){var o=e.getViewPort(window),a=i.layoutRect();t=r(t,o.w+o.x,a.w),n=r(n,o.h+o.y,a.h)}return i._rendered?i.layoutRect({x:t,y:n}).repaint():(i.settings.x=t,i.settings.y=n),i.fire("move",{x:t,y:n}),i}}}),r(Y,[W],function(e){return{resizeToContent:function(){this._layoutRect.autoResize=!0,this._lastRect=null,this.reflow()},resizeTo:function(t,n){if(1>=t||1>=n){var r=e.getWindowSize();t=1>=t?t*r.w:t,n=1>=n?n*r.h:n}return this._layoutRect.autoResize=!1,this.layoutRect({minW:t,minH:n,w:t,h:n}).reflow()},resizeBy:function(e,t){var n=this,r=n.layoutRect();return n.resizeTo(r.w+e,r.h+t)}}}),r(X,[K,G,Y,W],function(e,t,n,r){function i(e){var t;for(t=s.length;t--;)s[t]===e&&s.splice(t,1);for(t=l.length;t--;)l[t]===e&&l.splice(t,1)}var o,a,s=[],l=[],c,u=e.extend({Mixins:[t,n],init:function(e){function t(){var e,t=u.zIndex||65535,n;if(l.length)for(e=0;en&&(e.fixed(!1).layoutRect({y:e._autoFixY}).repaint(),t(!1,e._autoFixY-n)):(e._autoFixY=e.layoutRect().y,e._autoFixY'),n=n.firstChild,d.getContainerElm().appendChild(n),setTimeout(function(){r.addClass(n,i+"in"),r.addClass(d.getEl(),i+"in")},0),c=!0),l.push(d),t()}}),d.on("close hide",function(e){if(e.control==d){for(var n=l.length;n--;)l[n]===d&&l.splice(n,1);t()}}),d.on("show",function(){d.parents().each(function(e){return e._fixed?(d.fixed(!0),!1):void 0})}),e.popover&&(d._preBodyHtml='
',d.addClass("popover").addClass("bottom").addClass(d.isRtl()?"end":"start"))},fixed:function(e){var t=this;if(t._fixed!=e){if(t._rendered){var n=r.getViewPort();e?t.layoutRect().y-=n.y:t.layoutRect().y+=n.y}t.toggleClass("fixed",e),t._fixed=e}return t},show:function(){var e=this,t,n=e._super();for(t=s.length;t--&&s[t]!==e;);return-1===t&&s.push(e),n},hide:function(){return i(this),this._super()},hideAll:function(){u.hideAll()},close:function(){var e=this;return e.fire("close"),e.remove()},remove:function(){i(this),this._super()}});return u.hideAll=function(){for(var e=s.length;e--;){var t=s[e];t.settings.autohide&&(t.fire("cancel",{},!1),t.hide(),s.splice(e,1))}},u}),r(J,[W],function(e){return function(t){function n(){if(!h)if(h=[],d.find)d.find("*").each(function(e){e.canFocus&&h.push(e.getEl())});else for(var e=d.getEl().getElementsByTagName("*"),t=0;ti?i=l.length-1:i>=l.length&&(i=0),o=l[i],o.focus(),m=o.id,t.actOnFocus&&s()}function u(){var e,r;for(r=i(t.root.getEl()),n(),e=h.length;e--;)if("toolbar"==r&&h[e].id===m)return h[e].focus(),void 0;h[0].focus()}var d=t.root,f=t.enableUpDown!==!1,p=t.enableLeftRight!==!1,h=t.items,m;return d.on("keydown",function(e){var n=37,r=39,u=38,d=40,h=27,m=14,g=13,v=32,y=9,b;switch(e.keyCode){case n:p&&(t.leftAction?t.leftAction():c(-1),b=!0);break;case r:p&&("menuitem"==i()&&"menu"==o()?a("haspopup")&&s():c(1),b=!0);break;case u:f&&(c(-1),b=!0);break;case d:f&&("menuitem"==i()&&"menubar"==o()?s():"button"==i()&&a("haspopup")?s():c(1),b=!0);break;case y:b=!0,e.shiftKey?c(-1):c(1);break;case h:b=!0,l();break;case m:case g:case v:b=s()}b&&(e.stopPropagation(),e.preventDefault())}),d.on("focusin",function(e){n(),m=e.target.id}),{moveFocus:c,focusFirst:u,cancel:l}}}),r(Q,[X,K,W,J,$],function(e,t,n,r,i){var o=e.extend({modal:!0,Defaults:{border:1,layout:"flex",containerCls:"panel",role:"dialog",callbacks:{submit:function(){this.fire("submit",{data:this.toJSON()})},close:function(){this.close()}}},init:function(e){var n=this;n._super(e),n.isRtl()&&n.addClass("rtl"),n.addClass("window"),n._fixed=!0,e.buttons&&(n.statusbar=new t({layout:"flex",border:"1 0 0 0",spacing:3,padding:10,align:"center",pack:n.isRtl()?"start":"end",defaults:{type:"button"},items:e.buttons}),n.statusbar.addClass("foot"),n.statusbar.parent(n)),n.on("click",function(e){-1!=e.target.className.indexOf(n.classPrefix+"close")&&n.close()}),n.aria("label",e.title),n._fullscreen=!1},recalc:function(){var e=this,t=e.statusbar,r,i,o,a;e._fullscreen&&(e.layoutRect(n.getWindowSize()),e.layoutRect().contentH=e.layoutRect().innerH),e._super(),r=e.layoutRect(),e.settings.title&&!e._fullscreen&&(i=r.headerW,i>r.w&&(o=r.x-Math.max(0,i/2),e.layoutRect({w:i,x:o}),a=!0)),t&&(t.layoutRect({w:e.layoutRect().innerW}).recalc(),i=t.layoutRect().minW+r.deltaW,i>r.w&&(o=r.x-Math.max(0,i-r.w),e.layoutRect({w:i,x:o}),a=!0)),a&&e.recalc()},initLayoutRect:function(){var e=this,t=e._super(),r=0,i;if(e.settings.title&&!e._fullscreen){i=e.getEl("head");var o=n.getSize(i);t.headerW=o.width,t.headerH=o.height,r+=t.headerH}e.statusbar&&(r+=e.statusbar.layoutRect().h),t.deltaH+=r,t.minH+=r,t.h+=r;var a=n.getWindowSize();return t.x=Math.max(0,a.w/2-t.w/2),t.y=Math.max(0,a.h/2-t.h/2),t},renderHtml:function(){var e=this,t=e._layout,n=e._id,r=e.classPrefix,i=e.settings,o="",a="",s=i.html;return e.preRender(),t.preRender(e),i.title&&(o='
'+e.encode(i.title)+'
'),i.url&&(s=''),"undefined"==typeof s&&(s=t.renderHtml(e)),e.statusbar&&(a=e.statusbar.renderHtml()),'
'+o+'
'+s+"
"+a+"
"},fullscreen:function(e){var t=this,r=document.documentElement,i,o=t.classPrefix,a;if(e!=t._fullscreen)if(n.on(window,"resize",function(){var e;if(t._fullscreen)if(i)t._timer||(t._timer=setTimeout(function(){var e=n.getWindowSize();t.moveTo(0,0).resizeTo(e.w,e.h),t._timer=0},50));else{e=(new Date).getTime();var r=n.getWindowSize();t.moveTo(0,0).resizeTo(r.w,r.h),(new Date).getTime()-e>50&&(i=!0)}}),a=t.layoutRect(),t._fullscreen=e,e){t._initial={x:a.x,y:a.y,w:a.w,h:a.h},t._borderBox=t.parseBox("0"),t.getEl("head").style.display="none",a.deltaH-=a.headerH+2,n.addClass(r,o+"fullscreen"),n.addClass(document.body,o+"fullscreen"),t.addClass("fullscreen"); -var s=n.getWindowSize();t.moveTo(0,0).resizeTo(s.w,s.h)}else t._borderBox=t.parseBox(t.settings.border),t.getEl("head").style.display="",a.deltaH+=a.headerH,n.removeClass(r,o+"fullscreen"),n.removeClass(document.body,o+"fullscreen"),t.removeClass("fullscreen"),t.moveTo(t._initial.x,t._initial.y).resizeTo(t._initial.w,t._initial.h);return t.reflow()},postRender:function(){var e=this,t=[],n,o,a;setTimeout(function(){e.addClass("in")},0),e.keyboardNavigation=new r({root:e,enableLeftRight:!1,enableUpDown:!1,items:t,onCancel:function(){e.close()}}),e.find("*").each(function(e){e.canFocus&&(o=o||e.settings.autofocus,n=n||e,"filepicker"==e.type?(t.push(e.getEl("inp")),e.getEl("open")&&t.push(e.getEl("open"))):t.push(e.getEl()))}),e.statusbar&&e.statusbar.find("*").each(function(e){e.canFocus&&(o=o||e.settings.autofocus,n=n||e,t.push(e.getEl()))}),e._super(),e.statusbar&&e.statusbar.postRender(),!o&&n&&n.focus(),this.dragHelper=new i(e._id+"-dragh",{start:function(){a={x:e.layoutRect().x,y:e.layoutRect().y}},drag:function(t){e.moveTo(a.x+t.deltaX,a.y+t.deltaY)}}),e.on("submit",function(t){t.isDefaultPrevented()||e.close()})},submit:function(){return this.fire("submit",{data:this.toJSON()})},remove:function(){var e=this,t=e.classPrefix;e.dragHelper.destroy(),e._super(),e.statusbar&&this.statusbar.remove(),e._fullscreen&&(n.removeClass(document.documentElement,t+"fullscreen"),n.removeClass(document.body,t+"fullscreen"))}});return o}),r(Z,[Q],function(e){var t=e.extend({init:function(e){e={border:1,padding:20,layout:"flex",pack:"center",align:"center",containerCls:"panel",autoScroll:!0,buttons:{type:"button",text:"Ok",action:"ok"},items:{type:"label",multiline:!0,maxWidth:500,maxHeight:200}},this._super(e)},Statics:{OK:1,OK_CANCEL:2,YES_NO:3,YES_NO_CANCEL:4,msgBox:function(n){var r,i=n.callback||function(){};switch(n.buttons){case t.OK_CANCEL:r=[{type:"button",text:"Ok",subtype:"primary",onClick:function(e){e.control.parents()[1].close(),i(!0)}},{type:"button",text:"Cancel",onClick:function(e){e.control.parents()[1].close(),i(!1)}}];break;case t.YES_NO:r=[{type:"button",text:"Ok",subtype:"primary",onClick:function(e){e.control.parents()[1].close(),i(!0)}}];break;case t.YES_NO_CANCEL:r=[{type:"button",text:"Ok",subtype:"primary",onClick:function(e){e.control.parents()[1].close()}}];break;default:r=[{type:"button",text:"Ok",subtype:"primary",onClick:function(e){e.control.parents()[1].close(),i(!0)}}]}return new e({padding:20,x:n.x,y:n.y,minWidth:300,minHeight:100,layout:"flex",pack:"center",align:"center",buttons:r,title:n.title,items:{type:"label",multiline:!0,maxWidth:500,maxHeight:200,text:n.text},onClose:n.onClose}).renderTo(document.body).reflow()},alert:function(e,n){return"string"==typeof e&&(e={text:e}),e.callback=n,t.msgBox(e)},confirm:function(e,n){return"string"==typeof e&&(e={text:e}),e.callback=n,e.buttons=t.OK_CANCEL,t.msgBox(e)}}});return t}),r(et,[Q,Z],function(e,t){return function(n){function r(){return o.length?o[o.length-1]:void 0}var i=this,o=[];i.windows=o,i.open=function(t,r){var i;return n.editorManager.activeEditor=n,t.title=t.title||" ",t.url=t.url||t.file,t.url&&(t.width=parseInt(t.width||320,10),t.height=parseInt(t.height||240,10)),t.body&&(t.items={defaults:t.defaults,type:t.bodyType||"form",items:t.body}),t.url||t.buttons||(t.buttons=[{text:"Ok",subtype:"primary",onclick:function(){i.find("form")[0].submit(),i.close()}},{text:"Cancel",onclick:function(){i.close()}}]),i=new e(t),o.push(i),i.on("close",function(){for(var e=o.length;e--;)o[e]===i&&o.splice(e,1);n.focus()}),t.data&&i.on("postRender",function(){this.find("*").each(function(e){var n=e.name();n in t.data&&e.value(t.data[n])})}),i.features=t||{},i.params=r||{},n.nodeChanged(),i.renderTo(document.body).reflow()},i.alert=function(e,n,r){t.alert(e,function(){n&&n.call(r||this)})},i.confirm=function(e,n,r){t.confirm(e,function(e){n.call(r||this,e)})},i.close=function(){r()&&r().close()},i.getParams=function(){return r()?r().params:null},i.setParams=function(e){r()&&(r().params=e)}}}),r(tt,[T,B,C,m,g,p],function(e,t,n,r,i,o){return function(a){function s(e,t){try{a.getDoc().execCommand(e,!1,t)}catch(n){}}function l(){var e=a.getDoc().documentMode;return e?e:6}function c(e){return e.isDefaultPrevented()}function u(){function t(e){function t(){if(3==l.nodeType){if(e&&c==l.length)return!0;if(!e&&0===c)return!0}}var n,r,i,s,l,c,u;n=V.getRng();var d=[n.startContainer,n.startOffset,n.endContainer,n.endOffset];if(n.collapsed||(e=!0),l=n[(e?"start":"end")+"Container"],c=n[(e?"start":"end")+"Offset"],3==l.nodeType&&(r=W.getParent(n.startContainer,W.isBlock),e&&(r=W.getNext(r,W.isBlock)),!r||!t()&&n.collapsed||(i=W.create("em",{id:"__mceDel"}),I(o.grep(r.childNodes),function(e){i.appendChild(e)}),r.appendChild(i))),n=W.createRng(),n.setStart(d[0],d[1]),n.setEnd(d[2],d[3]),V.setRng(n),a.getDoc().execCommand(e?"ForwardDelete":"Delete",!1,null),i){for(s=V.getBookmark();u=W.get("__mceDel");)W.remove(u,!0);V.moveToBookmark(s)}}a.on("keydown",function(n){var r;r=n.keyCode==z,c(n)||!r&&n.keyCode!=F||e.modifierPressed(n)||(n.preventDefault(),t(r))}),a.addCommand("Delete",function(){t()})}function d(){function e(e){var t=W.create("body"),n=e.cloneContents();return t.appendChild(n),V.serializer.serialize(t,{format:"html"})}function n(n){if(!n.setStart){if(n.item)return!1;var r=n.duplicate();return r.moveToElementText(a.getBody()),t.compareRanges(n,r)}var i=e(n),o=W.createRng();o.selectNode(a.getBody());var s=e(o);return i===s}a.on("keydown",function(e){var t=e.keyCode,r,i;if(!c(e)&&(t==z||t==F)){if(r=a.selection.isCollapsed(),i=a.getBody(),r&&!W.isEmpty(i))return;if(!r&&!n(a.selection.getRng()))return;e.preventDefault(),a.setContent(""),i.firstChild&&W.isBlock(i.firstChild)?a.selection.setCursorLocation(i.firstChild,0):a.selection.setCursorLocation(i,0),a.nodeChanged()}})}function f(){a.on("keydown",function(t){!c(t)&&65==t.keyCode&&e.metaKeyPressed(t)&&(t.preventDefault(),a.execCommand("SelectAll"))})}function p(){a.settings.content_editable||(W.bind(a.getDoc(),"focusin",function(){V.setRng(V.getRng())}),W.bind(a.getDoc(),"mousedown",function(e){e.target==a.getDoc().documentElement&&(a.getWin().focus(),V.setRng(V.getRng()))}))}function h(){a.on("keydown",function(e){if(!c(e)&&e.keyCode===F&&V.isCollapsed()&&0===V.getRng(!0).startOffset){var t=V.getNode(),n=t.previousSibling;if("HR"==t.nodeName)return W.remove(t),e.preventDefault(),void 0;n&&n.nodeName&&"hr"===n.nodeName.toLowerCase()&&(W.remove(n),e.preventDefault())}})}function m(){window.Range.prototype.getClientRects||a.on("mousedown",function(e){if(!c(e)&&"HTML"===e.target.nodeName){var t=a.getBody();t.blur(),setTimeout(function(){t.focus()},0)}})}function g(){a.on("click",function(e){e=e.target,/^(IMG|HR)$/.test(e.nodeName)&&V.getSel().setBaseAndExtent(e,0,e,1),"A"==e.nodeName&&W.hasClass(e,"mce-item-anchor")&&V.select(e),a.nodeChanged()})}function v(){function e(){var e=W.getAttribs(V.getStart().cloneNode(!1));return function(){var t=V.getStart();t!==a.getBody()&&(W.setAttrib(t,"style",null),I(e,function(e){t.setAttributeNode(e.cloneNode(!0))}))}}function t(){return!V.isCollapsed()&&W.getParent(V.getStart(),W.isBlock)!=W.getParent(V.getEnd(),W.isBlock)}a.on("keypress",function(n){var r;return c(n)||8!=n.keyCode&&46!=n.keyCode||!t()?void 0:(r=e(),a.getDoc().execCommand("delete",!1,null),r(),n.preventDefault(),!1)}),W.bind(a.getDoc(),"cut",function(n){var r;!c(n)&&t()&&(r=e(),setTimeout(function(){r()},0))})}function y(){var e,n;a.on("selectionchange",function(){n&&(clearTimeout(n),n=0),n=window.setTimeout(function(){var n=V.getRng();e&&t.compareRanges(n,e)||(a.nodeChanged(),e=n)},50)})}function b(){document.body.setAttribute("role","application")}function C(){a.on("keydown",function(e){if(!c(e)&&e.keyCode===F&&V.isCollapsed()&&0===V.getRng(!0).startOffset){var t=V.getNode().previousSibling;if(t&&t.nodeName&&"table"===t.nodeName.toLowerCase())return e.preventDefault(),!1}})}function x(){l()>7||(s("RespectVisibilityInDesign",!0),a.contentStyles.push(".mceHideBrInPre pre br {display: none}"),W.addClass(a.getBody(),"mceHideBrInPre"),q.addNodeFilter("pre",function(e){for(var t=e.length,r,i,o,a;t--;)for(r=e[t].getAll("br"),i=r.length;i--;)o=r[i],a=o.prev,a&&3===a.type&&"\n"!=a.value.charAt(a.value-1)?a.value+="\n":o.parent.insert(new n("#text",3),o,!0).value="\n"}),$.addNodeFilter("pre",function(e){for(var t=e.length,n,r,i,o;t--;)for(n=e[t].getAll("br"),r=n.length;r--;)i=n[r],o=i.prev,o&&3==o.type&&(o.value=o.value.replace(/\r?\n$/,""))}))}function w(){W.bind(a.getBody(),"mouseup",function(){var e,t=V.getNode();"IMG"==t.nodeName&&((e=W.getStyle(t,"width"))&&(W.setAttrib(t,"width",e.replace(/[^0-9%]+/g,"")),W.setStyle(t,"width","")),(e=W.getStyle(t,"height"))&&(W.setAttrib(t,"height",e.replace(/[^0-9%]+/g,"")),W.setStyle(t,"height","")))})}function _(){a.on("keydown",function(t){var n,r,i,o,s,l,u,d;if(n=t.keyCode==z,!c(t)&&(n||t.keyCode==F)&&!e.modifierPressed(t)&&(r=V.getRng(),i=r.startContainer,o=r.startOffset,u=r.collapsed,3==i.nodeType&&i.nodeValue.length>0&&(0===o&&!u||u&&o===(n?0:1)))){if(l=i.previousSibling,l&&"IMG"==l.nodeName)return;d=a.schema.getNonEmptyElements(),t.preventDefault(),s=W.create("br",{id:"__tmp"}),i.parentNode.insertBefore(s,i),a.getDoc().execCommand(n?"ForwardDelete":"Delete",!1,null),i=V.getRng().startContainer,l=i.previousSibling,l&&1==l.nodeType&&!W.isBlock(l)&&W.isEmpty(l)&&!d[l.nodeName.toLowerCase()]&&W.remove(l),W.remove("__tmp")}})}function N(){a.on("keydown",function(t){var n,r,i,o,s;if(!c(t)&&t.keyCode==e.BACKSPACE&&(n=V.getRng(),r=n.startContainer,i=n.startOffset,o=W.getRoot(),s=r,n.collapsed&&0===i)){for(;s&&s.parentNode&&s.parentNode.firstChild==s&&s.parentNode!=o;)s=s.parentNode;"BLOCKQUOTE"===s.tagName&&(a.formatter.toggle("blockquote",null,s),n=W.createRng(),n.setStart(r,0),n.setEnd(r,0),V.setRng(n))}})}function E(){function e(){a._refreshContentEditable(),s("StyleWithCSS",!1),s("enableInlineTableEditing",!1),U.object_resizing||s("enableObjectResizing",!1)}U.readonly||a.on("BeforeExecCommand MouseDown",e)}function k(){function e(){I(W.select("a"),function(e){var t=e.parentNode,n=W.getRoot();if(t.lastChild===e){for(;t&&!W.isBlock(t);){if(t.parentNode.lastChild!==t||t===n)return;t=t.parentNode}W.add(t,"br",{"data-mce-bogus":1})}})}a.on("SetContent ExecCommand",function(t){("setcontent"==t.type||"mceInsertLink"===t.command)&&e()})}function S(){U.forced_root_block&&a.on("init",function(){s("DefaultParagraphSeparator",U.forced_root_block)})}function T(){a.on("Undo Redo SetContent",function(e){e.initial||a.execCommand("mceRepaint")})}function R(){a.on("keydown",function(e){var t;c(e)||e.keyCode!=F||(t=a.getDoc().selection.createRange(),t&&t.item&&(e.preventDefault(),a.undoManager.beforeChange(),W.remove(t.item(0)),a.undoManager.add()))})}function A(){var e;l()>=10&&(e="",I("p div h1 h2 h3 h4 h5 h6".split(" "),function(t,n){e+=(n>0?",":"")+t+":empty"}),a.contentStyles.push(e+"{padding-right: 1px !important}"))}function B(){l()<9&&(q.addNodeFilter("noscript",function(e){for(var t=e.length,n,r;t--;)n=e[t],r=n.firstChild,r&&n.attr("data-mce-innertext",r.value)}),$.addNodeFilter("noscript",function(e){for(var t=e.length,i,o,a;t--;)i=e[t],o=e[t].firstChild,o?o.value=r.decode(o.value):(a=i.attributes.map["data-mce-innertext"],a&&(i.attr("data-mce-innertext",null),o=new n("#text",3),o.value=a,o.raw=!0,i.append(o)))}))}function L(){function e(e,t){var n=i.createTextRange();try{n.moveToPoint(e,t)}catch(r){n=null}return n}function t(t){var r;t.button?(r=e(t.x,t.y),r&&(r.compareEndPoints("StartToStart",a)>0?r.setEndPoint("StartToStart",a):r.setEndPoint("EndToEnd",a),r.select())):n()}function n(){var e=r.selection.createRange();a&&!e.item&&0===e.compareEndPoints("StartToEnd",e)&&a.select(),W.unbind(r,"mouseup",n),W.unbind(r,"mousemove",t),a=o=0}var r=W.doc,i=r.body,o,a,s;r.documentElement.unselectable=!0,W.bind(r,"mousedown contextmenu",function(i){if("HTML"===i.target.nodeName){if(o&&n(),s=r.documentElement,s.scrollHeight>s.clientHeight)return;o=1,a=e(i.x,i.y),a&&(W.bind(r,"mouseup",n),W.bind(r,"mousemove",t),W.win.focus(),a.select())}})}function H(){a.on("keyup focusin",function(t){65==t.keyCode&&e.metaKeyPressed(t)||V.normalize()})}function D(){a.contentStyles.push("img:-moz-broken {-moz-force-broken-image-icon:1;min-width:24px;min-height:24px}")}function M(){a.inline||a.on("keydown",function(){document.activeElement==document.body&&a.getWin().focus()})}function P(){a.inline||(a.contentStyles.push("body {min-height: 150px}"),a.on("click",function(e){"HTML"==e.target.nodeName&&(a.execCommand("SelectAll"),a.selection.collapse(!0),a.nodeChanged())}))}function O(){i.mac&&a.on("keydown",function(t){!e.metaKeyPressed(t)||37!=t.keyCode&&39!=t.keyCode||(t.preventDefault(),a.selection.getSel().modify("move",37==t.keyCode?"backward":"forward","word"))})}var I=o.each,F=e.BACKSPACE,z=e.DELETE,W=a.dom,V=a.selection,U=a.settings,q=a.parser,$=a.serializer,j=i.gecko,K=i.ie,G=i.webkit;C(),N(),d(),H(),G&&(_(),u(),p(),g(),S(),i.iOS?(y(),M(),P()):f()),K&&i.ie<11&&(h(),b(),x(),w(),R(),A(),B(),L()),i.ie>=11&&P(),i.ie&&f(),j&&(h(),m(),v(),E(),k(),T(),D(),O())}}),r(nt,[p],function(e){function t(){return!1}function n(){return!0}var r="__bindings",i=e.makeMap("focusin focusout click dblclick mousedown mouseup mousemove mouseover beforepaste paste cut copy selectionchange mouseout mouseenter mouseleave keydown keypress keyup contextmenu dragend dragover draggesture dragdrop drop drag"," ");return{fire:function(e,i,o){var a=this,s,l,c,u,d;if(e=e.toLowerCase(),i=i||{},i.type=e,i.target||(i.target=a),i.preventDefault||(i.preventDefault=function(){i.isDefaultPrevented=n},i.stopPropagation=function(){i.isPropagationStopped=n},i.stopImmediatePropagation=function(){i.isImmediatePropagationStopped=n},i.isDefaultPrevented=t,i.isPropagationStopped=t,i.isImmediatePropagationStopped=t),a[r]&&(s=a[r][e]))for(l=0,c=s.length;c>l&&(s[l]=u=s[l],!i.isImmediatePropagationStopped());l++)if(u.call(a,i)===!1)return i.preventDefault(),i;if(o!==!1&&a.parent)for(d=a.parent();d&&!i.isPropagationStopped();)d.fire(e,i,!1),d=d.parent();return i},on:function(e,t){var n=this,o,a,s,l;if(t===!1&&(t=function(){return!1}),t)for(s=e.toLowerCase().split(" "),l=s.length;l--;)e=s[l],o=n[r],o||(o=n[r]={}),a=o[e],a||(a=o[e]=[],n.bindNative&&i[e]&&n.bindNative(e)),a.push(t);return n},off:function(e,t){var n=this,o,a=n[r],s,l,c,u;if(a)if(e)for(c=e.toLowerCase().split(" "),o=c.length;o--;){if(e=c[o],s=a[e],!e){for(l in a)a[e].length=0;return n}if(s){if(t)for(u=s.length;u--;)s[u]===t&&s.splice(u,1);else s.length=0;!s.length&&n.unbindNative&&i[e]&&(n.unbindNative(e),delete a[e])}}else{if(n.unbindNative)for(e in a)n.unbindNative(e);n[r]=[]}return n},hasEventListeners:function(e){var t=this[r];return e=e.toLowerCase(),!(!t||!t[e]||0===t[e].length)}}}),r(rt,[p,g],function(e,t){var n=e.each,r=e.explode,i={f9:120,f10:121,f11:122};return function(o){var a=this,s={};o.on("keyup keypress keydown",function(e){(e.altKey||e.ctrlKey||e.metaKey)&&n(s,function(n){var r=t.mac?e.metaKey:e.ctrlKey;if(n.ctrl==r&&n.alt==e.altKey&&n.shift==e.shiftKey)return e.keyCode==n.keyCode||e.charCode&&e.charCode==n.charCode?(e.preventDefault(),"keydown"==e.type&&n.func.call(n.scope),!0):void 0})}),a.add=function(t,a,l,c){var u;return u=l,"string"==typeof l?l=function(){o.execCommand(u,!1,null)}:e.isArray(u)&&(l=function(){o.execCommand(u[0],u[1],u[2])}),n(r(t.toLowerCase()),function(e){var t={func:l,scope:c||o,desc:o.translate(a),alt:!1,ctrl:!1,shift:!1};n(r(e,"+"),function(e){switch(e){case"alt":case"ctrl":case"shift":t[e]=!0;break;default:t.charCode=e.charCodeAt(0),t.keyCode=i[e]||e.toUpperCase().charCodeAt(0)}}),s[(t.ctrl?"ctrl":"")+","+(t.alt?"alt":"")+","+(t.shift?"shift":"")+","+t.keyCode]=t}),!0}}}),r(it,[v,b,C,k,E,A,L,H,D,M,P,O,y,l,et,x,_,tt,g,p,nt,rt],function(e,n,r,i,o,a,s,l,c,u,d,f,p,h,m,g,v,y,b,C,x,w){function _(e,t){return"selectionchange"==t||"drop"==t?e.getDoc():!e.inline&&/^mouse|click|contextmenu/.test(t)?e.getDoc():e.getBody()}function N(e,t,r){var i=this,o,a;o=i.documentBaseUrl=r.documentBaseURL,a=r.baseURI,i.settings=t=T({id:e,theme:"modern",delta_width:0,delta_height:0,popup_css:"",plugins:"",document_base_url:o,add_form_submit_trigger:!0,submit_patch:!0,add_unload_trigger:!0,convert_urls:!0,relative_urls:!0,remove_script_host:!0,object_resizing:!0,doctype:"",visual:!0,font_size_style_values:"xx-small,x-small,small,medium,large,x-large,xx-large",font_size_legacy_values:"xx-small,small,medium,large,x-large,xx-large,300%",forced_root_block:"p",hidden_input:!0,padd_empty_editor:!0,render_ui:!0,indentation:"30px",inline_styles:!0,convert_fonts_to_spans:!0,indent:"simple",indent_before:"p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist",indent_after:"p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist",validate:!0,entity_encoding:"named",url_converter:i.convertURL,url_converter_scope:i,ie7_compat:!0},t),n.language=t.language||"en",n.languageLoad=t.language_load,n.baseURL=r.baseURL,i.id=t.id=e,i.isNotDirty=!0,i.plugins={},i.documentBaseURI=new f(t.document_base_url||o,{base_uri:a}),i.baseURI=a,i.contentCSS=[],i.contentStyles=[],i.shortcuts=new w(i),i.execCommands={},i.queryStateCommands={},i.queryValueCommands={},i.loadedCSS={},i.suffix=r.suffix,i.editorManager=r,i.inline=t.inline,i.execCallback("setup",i),r.fire("SetupEditor",i)}var E=e.DOM,k=n.ThemeManager,S=n.PluginManager,T=C.extend,R=C.each,A=C.explode,B=C.inArray,L=C.trim,H=C.resolve,D=h.Event,M=b.gecko,P=b.ie;return N.prototype={render:function(){function e(){E.unbind(window,"ready",e),n.render()}function t(){var e=p.ScriptLoader;if(r.language&&"en"!=r.language&&(r.language_url=n.editorManager.baseURL+"/langs/"+r.language+".js"),r.language_url&&e.add(r.language_url),r.theme&&"function"!=typeof r.theme&&"-"!=r.theme.charAt(0)&&!k.urls[r.theme]){var t=r.theme_url;t=t?n.documentBaseURI.toAbsolute(t):"themes/"+r.theme+"/theme"+o+".js",k.load(r.theme,t)}C.isArray(r.plugins)&&(r.plugins=r.plugins.join(" ")),R(r.external_plugins,function(e,t){S.load(t,e),r.plugins+=" "+t}),R(r.plugins.split(/[ ,]/),function(e){if(e=L(e),e&&!S.urls[e])if("-"==e.charAt(0)){e=e.substr(1,e.length);var t=S.dependencies(e);R(t,function(e){var t={prefix:"plugins/",resource:e,suffix:"/plugin"+o+".js"};e=S.createUrl(t,e),S.load(e.resource,e)})}else S.load(e,{prefix:"plugins/",resource:e,suffix:"/plugin"+o+".js"})}),e.loadQueue(function(){n.removed||n.init()})}var n=this,r=n.settings,i=n.id,o=n.suffix;if(!D.domLoaded)return E.bind(window,"ready",e),void 0;if(n.getElement()&&b.contentEditable){r.inline?n.inline=!0:(n.orgVisibility=n.getElement().style.visibility,n.getElement().style.visibility="hidden");var a=n.getElement().form||E.getParent(i,"form");a&&(n.formElement=a,r.hidden_input&&!/TEXTAREA|INPUT/i.test(n.getElement().nodeName)&&(E.insertAfter(E.create("input",{type:"hidden",name:i}),i),n.hasHiddenInput=!0),n.formEventDelegate=function(e){n.fire(e.type,e)},E.bind(a,"submit reset",n.formEventDelegate),n.on("reset",function(){n.setContent(n.startContent,{format:"raw"})}),!r.submit_patch||a.submit.nodeType||a.submit.length||a._mceOldSubmit||(a._mceOldSubmit=a.submit,a.submit=function(){return n.editorManager.triggerSave(),n.isNotDirty=!0,a._mceOldSubmit(a)})),n.windowManager=new m(n),"xml"==r.encoding&&n.on("GetContent",function(e){e.save&&(e.content=E.encode(e.content))}),r.add_form_submit_trigger&&n.on("submit",function(){n.initialized&&n.save()}),r.add_unload_trigger&&(n._beforeUnload=function(){!n.initialized||n.destroyed||n.isHidden()||n.save({format:"raw",no_events:!0,set_dirty:!1})},n.editorManager.on("BeforeUnload",n._beforeUnload)),t()}},init:function(){function e(n){var r=S.get(n),i,o;i=S.urls[n]||t.documentBaseUrl.replace(/\/$/,""),n=L(n),r&&-1===B(h,n)&&(R(S.dependencies(n),function(t){e(t)}),o=new r(t,i),t.plugins[n]=o,o.init&&(o.init(t,i),h.push(n)))}var t=this,n=t.settings,r=t.getElement(),i,o,a,s,l,c,u,d,f,p,h=[];if(t.rtl=this.editorManager.i18n.rtl,t.editorManager.add(t),n.aria_label=n.aria_label||E.getAttrib(r,"aria-label",t.getLang("aria.rich_text_area")),n.theme&&("function"!=typeof n.theme?(n.theme=n.theme.replace(/-/,""),l=k.get(n.theme),t.theme=new l(t,k.urls[n.theme]),t.theme.init&&t.theme.init(t,k.urls[n.theme]||t.documentBaseUrl.replace(/\/$/,""))):t.theme=n.theme),R(n.plugins.replace(/\-/g,"").split(/[ ,]/),e),n.render_ui&&t.theme&&(t.orgDisplay=r.style.display,"function"!=typeof n.theme?(i=n.width||r.style.width||r.offsetWidth,o=n.height||r.style.height||r.offsetHeight,a=n.min_height||100,f=/^[0-9\.]+(|px)$/i,f.test(""+i)&&(i=Math.max(parseInt(i,10)+(l.deltaWidth||0),100)),f.test(""+o)&&(o=Math.max(parseInt(o,10)+(l.deltaHeight||0),a)),l=t.theme.renderUI({targetNode:r,width:i,height:o,deltaWidth:n.delta_width,deltaHeight:n.delta_height}),n.content_editable||(E.setStyles(l.sizeContainer||l.editorContainer,{wi2dth:i,h2eight:o}),o=(l.iframeHeight||o)+("number"==typeof o?l.deltaHeight||0:""),a>o&&(o=a))):(l=n.theme(t,r),l.editorContainer.nodeType&&(l.editorContainer=l.editorContainer.id=l.editorContainer.id||t.id+"_parent"),l.iframeContainer.nodeType&&(l.iframeContainer=l.iframeContainer.id=l.iframeContainer.id||t.id+"_iframecontainer"),o=l.iframeHeight||r.offsetHeight),t.editorContainer=l.editorContainer),n.content_css&&R(A(n.content_css),function(e){t.contentCSS.push(t.documentBaseURI.toAbsolute(e))}),n.content_style&&t.contentStyles.push(n.content_style),n.content_editable)return r=s=l=null,t.initContentBody();for(t.iframeHTML=n.doctype+"",n.document_base_url!=t.documentBaseUrl&&(t.iframeHTML+=''),!b.caretAfter&&n.ie7_compat&&(t.iframeHTML+=''),t.iframeHTML+='',p=0;p',t.loadedCSS[m]=!0}u=n.body_id||"tinymce",-1!=u.indexOf("=")&&(u=t.getParam("body_id","","hash"),u=u[t.id]||u),d=n.body_class||"",-1!=d.indexOf("=")&&(d=t.getParam("body_class","","hash"),d=d[t.id]||""),t.iframeHTML+='
";var g='javascript:(function(){document.open();document.domain="'+document.domain+'";var ed = window.parent.tinymce.get("'+t.id+'");document.write(ed.iframeHTML);document.close();ed.initContentBody(true);})()';if(document.domain!=location.hostname&&(c=g),s=E.add(l.iframeContainer,"iframe",{id:t.id+"_ifr",src:c||'javascript:""',frameBorder:"0",allowTransparency:"true",title:t.editorManager.translate("Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help"),style:{width:"100%",height:o,display:"block"}}),P)try{t.getDoc()}catch(v){s.src=c=g}t.contentAreaContainer=l.iframeContainer,l.editorContainer&&(E.get(l.editorContainer).style.display=t.orgDisplay),E.get(t.id).style.display="none",E.setAttrib(t.id,"aria-hidden",!0),c||t.initContentBody(),r=s=l=null},initContentBody:function(t){var n=this,o=n.settings,f=E.get(n.id),p=n.getDoc(),h,m;o.inline||(n.getElement().style.visibility=n.orgVisibility),t||o.content_editable||(p.open(),p.write(n.iframeHTML),p.close()),o.content_editable&&(n.on("remove",function(){var e=this.getBody();E.removeClass(e,"mce-content-body"),E.removeClass(e,"mce-edit-focus"),E.setAttrib(e,"tabIndex",null),E.setAttrib(e,"contentEditable",null)}),E.addClass(f,"mce-content-body"),f.tabIndex=-1,n.contentDocument=p=o.content_document||document,n.contentWindow=o.content_window||window,n.bodyElement=f,o.content_document=o.content_window=null,o.root_name=f.nodeName.toLowerCase()),h=n.getBody(),h.disabled=!0,o.readonly||(n.inline&&"static"==E.getStyle(h,"position",!0)&&(h.style.position="relative"),h.contentEditable=n.getParam("content_editable_state",!0)),h.disabled=!1,n.schema=new g(o),n.dom=new e(p,{keep_values:!0,url_converter:n.convertURL,url_converter_scope:n,hex_colors:o.force_hex_style_colors,class_filter:o.class_filter,update_styles:!0,root_element:o.content_editable?n.id:null,collect:o.content_editable,schema:n.schema,onSetAttrib:function(e){n.fire("SetAttrib",e)}}),n.parser=new v(o,n.schema),n.parser.addAttributeFilter("src,href,style",function(e,t){for(var r=e.length,i,o=n.dom,a,s;r--;)i=e[r],a=i.attr(t),s="data-mce-"+t,i.attributes.map[s]||("style"===t?i.attr(s,o.serializeStyle(o.parseStyle(a),i.name)):i.attr(s,n.convertURL(a,t,i.name)))}),n.parser.addNodeFilter("script",function(e){for(var t=e.length,n;t--;)n=e[t],n.attr("type","mce-"+(n.attr("type")||"text/javascript"))}),n.parser.addNodeFilter("#cdata",function(e){for(var t=e.length,n;t--;)n=e[t],n.type=8,n.name="#comment",n.value="[CDATA["+n.value+"]]"}),n.parser.addNodeFilter("p,h1,h2,h3,h4,h5,h6,div",function(e){for(var t=e.length,i,o=n.schema.getNonEmptyElements();t--;)i=e[t],i.isEmpty(o)&&(i.empty().append(new r("br",1)).shortEnded=!0)}),n.serializer=new i(o,n),n.selection=new a(n.dom,n.getWin(),n.serializer,n),n.formatter=new s(n),n.undoManager=new l(n),n.forceBlocks=new u(n),n.enterKey=new c(n),n.editorCommands=new d(n),n.fire("PreInit"),o.browser_spellcheck||o.gecko_spellcheck||(p.body.spellcheck=!1,E.setAttrib(h,"spellcheck","false")),n.fire("PostRender"),n.quirks=y(n),o.directionality&&(h.dir=o.directionality),o.nowrap&&(h.style.whiteSpace="nowrap"),o.protect&&n.on("BeforeSetContent",function(e){R(o.protect,function(t){e.content=e.content.replace(t,function(e){return""})})}),n.on("SetContent",function(){n.addVisual(n.getBody())}),o.padd_empty_editor&&n.on("PostProcess",function(e){e.content=e.content.replace(/^(]*>( | |\s|\u00a0|)<\/p>[\r\n]*|
[\r\n]*)$/,"")}),n.load({initial:!0,format:"html"}),n.startContent=n.getContent({format:"raw"}),n.initialized=!0,R(n._pendingNativeEvents,function(e){n.dom.bind(_(n,e),e,function(e){n.fire(e.type,e)})}),n.fire("init"),n.focus(!0),n.nodeChanged({initial:!0}),n.execCallback("init_instance_callback",n),n.contentStyles.length>0&&(m="",R(n.contentStyles,function(e){m+=e+"\r\n"}),n.dom.addStyle(m)),R(n.contentCSS,function(e){n.loadedCSS[e]||(n.dom.loadCSS(e),n.loadedCSS[e]=!0)}),o.auto_focus&&setTimeout(function(){var e=n.editorManager.get(o.auto_focus);e.selection.select(e.getBody(),1),e.selection.collapse(1),e.getBody().focus(),e.getWin().focus()},100),f=p=h=null},focus:function(e){var t,n=this,r=n.selection,i=n.settings.content_editable,o,a,s=n.getDoc(),l;e||(o=r.getRng(),o.item&&(a=o.item(0)),n._refreshContentEditable(),i||(b.opera||n.getBody().focus(),n.getWin().focus()),(M||i)&&(l=n.getBody(),l.setActive&&b.ie<11?l.setActive():l.focus(),i&&r.normalize()),a&&a.ownerDocument==s&&(o=s.body.createControlRange(),o.addElement(a),o.select())),n.editorManager.activeEditor!=n&&((t=n.editorManager.activeEditor)&&t.fire("deactivate",{relatedTarget:n}),n.fire("activate",{relatedTarget:t})),n.editorManager.activeEditor=n},execCallback:function(e){var t=this,n=t.settings[e],r;if(n)return t.callbackLookup&&(r=t.callbackLookup[e])&&(n=r.func,r=r.scope),"string"==typeof n&&(r=n.replace(/\.\w+$/,""),r=r?H(r):0,n=H(n),t.callbackLookup=t.callbackLookup||{},t.callbackLookup[e]={func:n,scope:r}),n.apply(r||t,Array.prototype.slice.call(arguments,1))},translate:function(e){var t=this.settings.language||"en",n=this.editorManager.i18n;return e?n.data[t+"."+e]||e.replace(/\{\#([^\}]+)\}/g,function(e,r){return n.data[t+"."+r]||"{#"+r+"}"}):""},getLang:function(e,n){return this.editorManager.i18n.data[(this.settings.language||"en")+"."+e]||(n!==t?n:"{#"+e+"}")},getParam:function(e,t,n){var r=e in this.settings?this.settings[e]:t,i;return"hash"===n?(i={},"string"==typeof r?R(r.indexOf("=")>0?r.split(/[;,](?![^=;,]*(?:[;,]|$))/):r.split(","),function(e){e=e.split("="),i[L(e[0])]=e.length>1?L(e[1]):L(e)}):i=r,i):r},nodeChanged:function(){var e=this,t=e.selection,n,r,i;e.initialized&&!e.settings.disable_nodechange&&(i=e.getBody(),n=t.getStart()||i,n=P&&n.ownerDocument!=e.getDoc()?e.getBody():n,"IMG"==n.nodeName&&t.isCollapsed()&&(n=n.parentNode),r=[],e.dom.getParent(n,function(e){return e===i?!0:(r.push(e),void 0)}),e.fire("NodeChange",{element:n,parents:r}))},addButton:function(e,t){var n=this;t.cmd&&(t.onclick=function(){n.execCommand(t.cmd)}),t.text||t.icon||(t.icon=e),n.buttons=n.buttons||{},t.tooltip=t.tooltip||t.title,n.buttons[e]=t},addMenuItem:function(e,t){var n=this;t.cmd&&(t.onclick=function(){n.execCommand(t.cmd)}),n.menuItems=n.menuItems||{},n.menuItems[e]=t},addCommand:function(e,t,n){this.execCommands[e]={func:t,scope:n||this}},addQueryStateHandler:function(e,t,n){this.queryStateCommands[e]={func:t,scope:n||this}},addQueryValueHandler:function(e,t,n){this.queryValueCommands[e]={func:t,scope:n||this}},addShortcut:function(e,t,n,r){this.shortcuts.add(e,t,n,r)},execCommand:function(e,t,n,r){var i=this,o=0,a;return/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint)$/.test(e)||r&&r.skip_focus||i.focus(),r=T({},r),r=i.fire("BeforeExecCommand",{command:e,ui:t,value:n}),r.isDefaultPrevented()?!1:(a=i.execCommands[e])&&a.func.call(a.scope,t,n)!==!0?(i.fire("ExecCommand",{command:e,ui:t,value:n}),!0):(R(i.plugins,function(r){return r.execCommand&&r.execCommand(e,t,n)?(i.fire("ExecCommand",{command:e,ui:t,value:n}),o=!0,!1):void 0}),o?o:i.theme&&i.theme.execCommand&&i.theme.execCommand(e,t,n)?(i.fire("ExecCommand",{command:e,ui:t,value:n}),!0):i.editorCommands.execCommand(e,t,n)?(i.fire("ExecCommand",{command:e,ui:t,value:n}),!0):(i.getDoc().execCommand(e,t,n),i.fire("ExecCommand",{command:e,ui:t,value:n}),void 0))},queryCommandState:function(e){var t=this,n,r;if(!t._isHidden()){if((n=t.queryStateCommands[e])&&(r=n.func.call(n.scope),r!==!0))return r;if(r=t.editorCommands.queryCommandState(e),-1!==r)return r;try{return t.getDoc().queryCommandState(e)}catch(i){}}},queryCommandValue:function(e){var n=this,r,i;if(!n._isHidden()){if((r=n.queryValueCommands[e])&&(i=r.func.call(r.scope),i!==!0))return i;if(i=n.editorCommands.queryCommandValue(e),i!==t)return i;try{return n.getDoc().queryCommandValue(e)}catch(o){}}},show:function(){var e=this;E.show(e.getContainer()),E.hide(e.id),e.load(),e.fire("show")},hide:function(){var e=this,t=e.getDoc();P&&t&&!e.inline&&t.execCommand("SelectAll"),e.save(),E.hide(e.getContainer()),E.setStyle(e.id,"display",e.orgDisplay),e.fire("hide")},isHidden:function(){return!E.isHidden(this.id)},setProgressState:function(e,t){this.fire("ProgressState",{state:e,time:t})},load:function(e){var n=this,r=n.getElement(),i;return r?(e=e||{},e.load=!0,i=n.setContent(r.value!==t?r.value:r.innerHTML,e),e.element=r,e.no_events||n.fire("LoadContent",e),e.element=r=null,i):void 0},save:function(e){var t=this,n=t.getElement(),r,i;if(n&&t.initialized)return e=e||{},e.save=!0,e.element=n,r=e.content=t.getContent(e),e.no_events||t.fire("SaveContent",e),r=e.content,/TEXTAREA|INPUT/i.test(n.nodeName)?n.value=r:(n.innerHTML=r,(i=E.getParent(t.id,"form"))&&R(i.elements,function(e){return e.name==t.id?(e.value=r,!1):void 0})),e.element=n=null,e.set_dirty!==!1&&(t.isNotDirty=!0),r},setContent:function(e,t){var n=this,r=n.getBody(),i;return t=t||{},t.format=t.format||"html",t.set=!0,t.content=e,t.no_events||n.fire("BeforeSetContent",t),e=t.content,0===e.length||/^\s+$/.test(e)?(i=n.settings.forced_root_block,i&&n.schema.isValidChild(r.nodeName.toLowerCase(),i.toLowerCase())?(e=P&&11>P?"":'
',e=n.dom.createHTML(i,n.settings.forced_root_block_attrs,e)):(!P||11>P)&&(e='
'),r.innerHTML=e,n.fire("SetContent",t)):("raw"!==t.format&&(e=new o({},n.schema).serialize(n.parser.parse(e,{isRootContent:!0}))),t.content=L(e),n.dom.setHTML(r,t.content),t.no_events||n.fire("SetContent",t)),t.content},getContent:function(e){var t=this,n,r=t.getBody(); -return e=e||{},e.format=e.format||"html",e.get=!0,e.getInner=!0,e.no_events||t.fire("BeforeGetContent",e),n="raw"==e.format?r.innerHTML:"text"==e.format?r.innerText||r.textContent:t.serializer.serialize(r,e),e.content="text"!=e.format?L(n):n,e.no_events||t.fire("GetContent",e),e.content},insertContent:function(e){this.execCommand("mceInsertContent",!1,e)},isDirty:function(){return!this.isNotDirty},getContainer:function(){var e=this;return e.container||(e.container=E.get(e.editorContainer||e.id+"_parent")),e.container},getContentAreaContainer:function(){return this.contentAreaContainer},getElement:function(){return E.get(this.settings.content_element||this.id)},getWin:function(){var e=this,t;return e.contentWindow||(t=E.get(e.id+"_ifr"),t&&(e.contentWindow=t.contentWindow)),e.contentWindow},getDoc:function(){var e=this,t;return e.contentDocument||(t=e.getWin(),t&&(e.contentDocument=t.document)),e.contentDocument},getBody:function(){return this.bodyElement||this.getDoc().body},convertURL:function(e,t,n){var r=this,i=r.settings;return i.urlconverter_callback?r.execCallback("urlconverter_callback",e,n,!0,t):!i.convert_urls||n&&"LINK"==n.nodeName||0===e.indexOf("file:")||0===e.length?e:i.relative_urls?r.documentBaseURI.toRelative(e):e=r.documentBaseURI.toAbsolute(e,i.remove_script_host)},addVisual:function(e){var n=this,r=n.settings,i=n.dom,o;e=e||n.getBody(),n.hasVisual===t&&(n.hasVisual=r.visual),R(i.select("table,a",e),function(e){var t;switch(e.nodeName){case"TABLE":return o=r.visual_table_class||"mce-item-table",t=i.getAttrib(e,"border"),t&&"0"!=t||(n.hasVisual?i.addClass(e,o):i.removeClass(e,o)),void 0;case"A":return i.getAttrib(e,"href",!1)||(t=i.getAttrib(e,"name")||e.id,o="mce-item-anchor",t&&(n.hasVisual?i.addClass(e,o):i.removeClass(e,o))),void 0}}),n.fire("VisualAid",{element:e,hasVisual:n.hasVisual})},remove:function(){var e=this;if(!e.removed){e.removed=1,e.hasHiddenInput&&E.remove(e.getElement().nextSibling);var t=e.getDoc();P&&t&&!e.inline&&t.execCommand("SelectAll"),e.save(),E.setStyle(e.id,"display",e.orgDisplay),e.settings.content_editable||(D.unbind(e.getWin()),D.unbind(e.getDoc()));var n=e.getContainer();D.unbind(e.getBody()),D.unbind(n),e.fire("remove"),e.editorManager.remove(e),E.remove(n),e.destroy()}},bindNative:function(e){var t=this;t.settings.readonly||(t.initialized?t.dom.bind(_(t,e),e,function(n){t.fire(e,n)}):t._pendingNativeEvents?t._pendingNativeEvents.push(e):t._pendingNativeEvents=[e])},unbindNative:function(e){var t=this;t.initialized&&t.dom.unbind(e)},destroy:function(e){var t=this,n;if(!t.destroyed){if(!e&&!t.removed)return t.remove(),void 0;e&&M&&(D.unbind(t.getDoc()),D.unbind(t.getWin()),D.unbind(t.getBody())),e||(t.editorManager.off("beforeunload",t._beforeUnload),t.theme&&t.theme.destroy&&t.theme.destroy(),t.selection.destroy(),t.dom.destroy()),n=t.formElement,n&&(n._mceOldSubmit&&(n.submit=n._mceOldSubmit,n._mceOldSubmit=null),E.unbind(n,"submit reset",t.formEventDelegate)),t.contentAreaContainer=t.formElement=t.container=null,t.settings.content_element=t.bodyElement=t.contentDocument=t.contentWindow=null,t.selection&&(t.selection=t.selection.win=t.selection.dom=t.selection.dom.doc=null),t.destroyed=1}},_refreshContentEditable:function(){var e=this,t,n;e._isHidden()&&(t=e.getBody(),n=t.parentNode,n.removeChild(t),n.appendChild(t),t.focus())},_isHidden:function(){var e;return M?(e=this.selection.getSel(),!e||!e.rangeCount||0===e.rangeCount):0}},T(N.prototype,x),N}),r(ot,[],function(){var e={};return{rtl:!1,add:function(t,n){for(var r in n)e[r]=n[r];this.rtl=this.rtl||"rtl"===e._dir},translate:function(t){if("undefined"==typeof t)return t;if("string"!=typeof t&&t.raw)return t.raw;if(t.push){var n=t.slice(1);t=(e[t[0]]||t[0]).replace(/\{([^\}]+)\}/g,function(e,t){return n[t]})}return e[t]||t},data:e}}),r(at,[v,g],function(e,t){function n(r){function i(){try{return document.activeElement}catch(e){return document.body}}function o(e){return e&&e.startContainer?{startContainer:e.startContainer,startOffset:e.startOffset,endContainer:e.endContainer,endOffset:e.endOffset}:e}function a(e,t){var n;return t.startContainer?(n=e.getDoc().createRange(),n.setStart(t.startContainer,t.startOffset),n.setEnd(t.endContainer,t.endOffset)):n=t,n}function s(s){function l(t){return!!e.DOM.getParent(t,n.isEditorUIElement)}function c(e){for(var t=u.getBody();e;){if(e==t)return!0;e=e.parentNode}}var u=s.editor,d,f;u.on("init",function(){"onbeforedeactivate"in document&&t.ie<11?u.dom.bind(u.getBody(),"beforedeactivate",function(){var e=u.getDoc().selection;try{d=e&&e.createRange?e.createRange():u.selection.getRng()}catch(t){}}):(u.inline||t.ie>10)&&(u.on("nodechange keyup",function(){var e=document.activeElement;e&&e.id==u.id+"_ifr"&&(e=u.getBody()),c(e)&&(d=u.selection.getRng())}),t.webkit&&(f=function(){var e=u.selection.getRng();e.collapsed||(d=e)},e.DOM.bind(document,"selectionchange",f),u.on("remove",function(){e.DOM.unbind(document,"selectionchange",f)})))}),u.on("setcontent",function(){d=null}),u.on("mousedown",function(){u.selection.lastFocusBookmark=null}),u.on("focusin",function(){var e=r.focusedEditor;u.selection.lastFocusBookmark&&(u.selection.setRng(a(u,u.selection.lastFocusBookmark)),u.selection.lastFocusBookmark=null),e!=u&&(e&&e.fire("blur",{focusedEditor:u}),r.activeEditor=u,u.fire("focus",{blurredEditor:e}),u.focus(!1),r.focusedEditor=u),d=null}),u.on("focusout",function(e){e.target!==u.getBody()&&c(e.target)||(u.selection.lastFocusBookmark=o(d),window.setTimeout(function(){var e=r.focusedEditor;e!=u&&(u.selection.lastFocusBookmark=null),l(i())||e!=u||(u.fire("blur",{focusedEditor:null}),r.focusedEditor=null,u.selection.lastFocusBookmark=null)},0))})}r.on("AddEditor",s)}return n.isEditorUIElement=function(e){return-1!==e.className.indexOf("mce-")},n}),r(st,[it,v,O,g,p,nt,ot,at],function(e,n,r,i,o,a,s,l){var c=n.DOM,u=o.explode,d=o.each,f=o.extend,p=0,h,m={majorVersion:"4",minorVersion:"0.11",releaseDate:"2013-11-20",editors:[],i18n:s,activeEditor:null,setup:function(){var e=this,t,n,i="",o;if(n=document.location.href.replace(/[\?#].*$/,"").replace(/[\/\\][^\/]+$/,""),/[\/\\]$/.test(n)||(n+="/"),o=window.tinymce||window.tinyMCEPreInit)t=o.base||o.baseURL,i=o.suffix;else for(var a=document.getElementsByTagName("script"),s=0;s0&&d(u(h),function(n){c.get(n)?(l=new e(n,t,a),s.push(l),l.render(!0)):d(document.forms,function(r){d(r.elements,function(r){r.name===n&&(n="mce_editor_"+p++,c.setAttrib(r,"id",n),l=new e(n,t,a),s.push(l),l.render(1))})})});break;case"textareas":case"specific_textareas":d(c.select("textarea"),function(r){t.editor_deselector&&i(r,t.editor_deselector)||(!t.editor_selector||i(r,t.editor_selector))&&(l=new e(n(r),t,a),s.push(l),l.render(!0))})}t.oninit&&(h=m=0,d(s,function(e){m++,e.initialized?h++:e.on("init",function(){h++,h==m&&r(t,"oninit")}),h==m&&r(t,"oninit")}))}var a=this,s=[],l;a.settings=t,c.bind(window,"ready",o)},get:function(e){return e===t?this.editors:this.editors[e]},add:function(e){var t=this,n=t.editors;return n[e.id]=e,n.push(e),t.activeEditor=e,t.fire("AddEditor",{editor:e}),h||(h=function(){t.fire("BeforeUnload")},c.bind(window,"beforeunload",h)),e},createEditor:function(t,n){return this.add(new e(t,n,this))},remove:function(e){var t=this,n,r=t.editors,i,o;{if(e){if("string"==typeof e)return e=e.selector||e,d(c.select(e),function(e){t.remove(r[e.id])}),void 0;if(i=e,!r[i.id])return null;for(delete r[i.id],n=0;n=0;n--)t.remove(r[n])}},execCommand:function(t,n,r){var i=this,o=i.get(r);switch(t){case"mceAddEditor":return i.get(r)||new e(r,i.settings,i).render(),!0;case"mceRemoveEditor":return o&&o.remove(),!0;case"mceToggleEditor":return o?(o.isHidden()?o.show():o.hide(),!0):(i.execCommand("mceAddEditor",0,r),!0)}return i.activeEditor?i.activeEditor.execCommand(t,n,r):!1},triggerSave:function(){d(this.editors,function(e){e.save()})},addI18n:function(e,t){s.add(e,t)},translate:function(e){return s.translate(e)}};return f(m,a),m.setup(),window.tinymce=window.tinyMCE=m,m}),r(lt,[st,p],function(e,t){var n=t.each,r=t.explode;e.on("AddEditor",function(e){var t=e.editor;t.on("preInit",function(){function e(e,t){n(t,function(t,n){t&&s.setStyle(e,n,t)}),s.rename(e,"span")}function i(e){s=t.dom,l.convert_fonts_to_spans&&n(s.select("font,u,strike",e.node),function(e){o[e.nodeName.toLowerCase()](s,e)})}var o,a,s,l=t.settings;l.inline_styles&&(a=r(l.font_size_legacy_values),o={font:function(t,n){e(n,{backgroundColor:n.style.backgroundColor,color:n.color,fontFamily:n.face,fontSize:a[parseInt(n.size,10)-1]})},u:function(t,n){e(n,{textDecoration:"underline"})},strike:function(t,n){e(n,{textDecoration:"line-through"})}},t.on("PreProcess SetContent",i))})})}),r(ct,[],function(){return{send:function(e){function t(){!e.async||4==n.readyState||r++>1e4?(e.success&&1e4>r&&200==n.status?e.success.call(e.success_scope,""+n.responseText,n,e):e.error&&e.error.call(e.error_scope,r>1e4?"TIMED_OUT":"GENERAL",n,e),n=null):setTimeout(t,10)}var n,r=0;if(e.scope=e.scope||this,e.success_scope=e.success_scope||e.scope,e.error_scope=e.error_scope||e.scope,e.async=e.async===!1?!1:!0,e.data=e.data||"",n=new XMLHttpRequest){if(n.overrideMimeType&&n.overrideMimeType(e.content_type),n.open(e.type||(e.data?"POST":"GET"),e.url,e.async),e.content_type&&n.setRequestHeader("Content-Type",e.content_type),n.setRequestHeader("X-Requested-With","XMLHttpRequest"),n.send(e.data),!e.async)return t();setTimeout(t,10)}}}}),r(ut,[],function(){function e(t,n){var r,i,o,a;if(n=n||'"',null===t)return"null";if(o=typeof t,"string"==o)return i="\bb t\nn\ff\rr\"\"''\\\\",n+t.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g,function(e,t){return'"'===n&&"'"===e?e:(r=i.indexOf(t),r+1?"\\"+i.charAt(r+1):(e=t.charCodeAt().toString(16),"\\u"+"0000".substring(e.length)+e))})+n;if("object"==o){if(t.hasOwnProperty&&"[object Array]"===Object.prototype.toString.call(t)){for(r=0,i="[";r0?",":"")+e(t[r],n);return i+"]"}i="{";for(a in t)t.hasOwnProperty(a)&&(i+="function"!=typeof t[a]?(i.length>1?","+n:n)+a+n+":"+e(t[a],n):"");return i+"}"}return""+t}return{serialize:e,parse:function(e){try{return window[String.fromCharCode(101)+"val"]("("+e+")")}catch(t){}}}}),r(dt,[ut,ct,p],function(e,t,n){function r(e){this.settings=i({},e),this.count=0}var i=n.extend;return r.sendRPC=function(e){return(new r).send(e)},r.prototype={send:function(n){var r=n.error,o=n.success;n=i(this.settings,n),n.success=function(t,i){t=e.parse(t),"undefined"==typeof t&&(t={error:"JSON Parse error."}),t.error?r.call(n.error_scope||n.scope,t.error,i):o.call(n.success_scope||n.scope,t.result)},n.error=function(e,t){r&&r.call(n.error_scope||n.scope,e,t)},n.data=e.serialize({id:n.id||"c"+this.count++,method:n.method,params:n.params}),n.content_type="application/json",t.send(n)}},r}),r(ft,[v],function(e){return{callbacks:{},count:0,send:function(n){var r=this,i=e.DOM,o=n.count!==t?n.count:r.count,a="tinymce_jsonp_"+o;r.callbacks[o]=function(e){i.remove(a),delete r.callbacks[o],n.callback(e)},i.add(i.doc.body,"script",{id:a,src:n.url,type:"text/javascript"}),r.count++}}}),r(pt,[],function(){function e(){s=[];for(var e in a)s.push(e);i.length=s.length}function n(){function n(e){var n,r;return r=e!==t?u+e:i.indexOf(",",u),-1===r||r>i.length?null:(n=i.substring(u,r),u=r+1,n)}var r,i,s,u=0;if(a={},c){o.load(l),i=o.getAttribute(l)||"";do{var d=n();if(null===d)break;if(r=n(parseInt(d,32)||0),null!==r){if(d=n(),null===d)break;s=n(parseInt(d,32)||0),r&&(a[r]=s)}}while(null!==r);e()}}function r(){var t,n="";if(c){for(var r in a)t=a[r],n+=(n?",":"")+r.length.toString(32)+","+r+","+t.length.toString(32)+","+t;o.setAttribute(l,n);try{o.save(l)}catch(i){}e()}}var i,o,a,s,l,c;try{if(window.localStorage)return localStorage}catch(u){}return l="tinymce",o=document.documentElement,c=!!o.addBehavior,c&&o.addBehavior("#default#userData"),i={key:function(e){return s[e]},getItem:function(e){return e in a?a[e]:null},setItem:function(e,t){a[e]=""+t,r()},removeItem:function(e){delete a[e],r()},clear:function(){a={},r()}},n(),i}),r(ht,[v,l,y,b,p,g],function(e,t,n,r,i,o){var a=window.tinymce;return a.DOM=e.DOM,a.ScriptLoader=n.ScriptLoader,a.PluginManager=r.PluginManager,a.ThemeManager=r.ThemeManager,a.dom=a.dom||{},a.dom.Event=t.Event,i.each(i,function(e,t){a[t]=e}),i.each("isOpera isWebKit isIE isGecko isMac".split(" "),function(e){a[e]=o[e.substr(2).toLowerCase()]}),{}}),r(mt,[I,p],function(e,t){return e.extend({Defaults:{firstControlClass:"first",lastControlClass:"last"},init:function(e){this.settings=t.extend({},this.Defaults,e)},preRender:function(e){e.addClass(this.settings.containerClass,"body")},applyClasses:function(e){var t=this,n=t.settings,r,i,o;r=e.items().filter(":visible"),i=n.firstControlClass,o=n.lastControlClass,r.each(function(e){e.removeClass(i).removeClass(o),n.controlClass&&e.addClass(n.controlClass)}),r.eq(0).addClass(i),r.eq(-1).addClass(o)},renderHtml:function(e){var t=this,n=t.settings,r,i="";return r=e.items(),r.eq(0).addClass(n.firstControlClass),r.eq(-1).addClass(n.lastControlClass),r.each(function(e){n.controlClass&&e.addClass(n.controlClass),i+=e.renderHtml()}),i},recalc:function(){},postRender:function(){}})}),r(gt,[mt],function(e){return e.extend({Defaults:{containerClass:"abs-layout",controlClass:"abs-layout-item"},recalc:function(e){e.items().filter(":visible").each(function(e){var t=e.settings;e.layoutRect({x:t.x,y:t.y,w:t.w,h:t.h}),e.recalc&&e.recalc()})},renderHtml:function(e){return'
'+this._super(e)}})}),r(vt,[V,G],function(e,t){return e.extend({Mixins:[t],Defaults:{classes:"widget tooltip tooltip-n"},text:function(e){var t=this;return"undefined"!=typeof e?(t._value=e,t._rendered&&(t.getEl().lastChild.innerHTML=t.encode(e)),t):t._value},renderHtml:function(){var e=this,t=e.classPrefix;return'"},repaint:function(){var e=this,t,n;t=e.getEl().style,n=e._layoutRect,t.left=n.x+"px",t.top=n.y+"px",t.zIndex=131070}})}),r(yt,[V,vt],function(e,t){var n,r=e.extend({init:function(e){var t=this;t._super(e),t.canFocus=!0,e.tooltip&&r.tooltips!==!1&&t.on("mouseenter mouseleave",function(n){var r=t.tooltip().moveTo(-65535);if(n.control==t&&"mouseenter"==n.type){var i=r.text(e.tooltip).show().testMoveRel(t.getEl(),["bc-tc","bc-tl","bc-tr"]);r.toggleClass("tooltip-n","bc-tc"==i),r.toggleClass("tooltip-nw","bc-tl"==i),r.toggleClass("tooltip-ne","bc-tr"==i),r.moveRel(t.getEl(),i)}else r.hide()}),t.aria("label",e.tooltip)},tooltip:function(){var e=this;return n||(n=new t({type:"tooltip"}),n.renderTo(e.getContainerElm())),n},active:function(e){var t=this,n;return e!==n&&(t.aria("pressed",e),t.toggleClass("active",e)),t._super(e)},disabled:function(e){var t=this,n;return e!==n&&(t.aria("disabled",e),t.toggleClass("disabled",e)),t._super(e)},postRender:function(){var e=this,t=e.settings;e._rendered=!0,e._super(),e.parent()||!t.width&&!t.height||(e.initLayoutRect(),e.repaint()),t.autofocus&&setTimeout(function(){e.focus()},0)},remove:function(){this._super(),n&&(n.remove(),n=null)}});return r}),r(bt,[yt],function(e){return e.extend({Defaults:{classes:"widget btn",role:"button"},init:function(e){var t=this,n;t.on("click mousedown",function(e){e.preventDefault()}),t._super(e),n=e.size,e.subtype&&t.addClass(e.subtype),n&&t.addClass("btn-"+n)},icon:function(e){var t=this,n=t.classPrefix;if("undefined"==typeof e)return t.settings.icon;if(t.settings.icon=e,e=e?n+"ico "+n+"i-"+t.settings.icon:"",t._rendered){var r=t.getEl().firstChild,i=r.getElementsByTagName("i")[0];e?(i&&i==r.firstChild||(i=document.createElement("i"),r.insertBefore(i,r.firstChild)),i.className=e):i&&r.removeChild(i),t.text(t._text)}return t},repaint:function(){var e=this.getEl().firstChild.style;e.width=e.height="100%",this._super()},renderHtml:function(){var e=this,t=e._id,n=e.classPrefix,r=e.settings.icon,i="";return e.settings.image&&(r="none",i=" style=\"background-image: url('"+e.settings.image+"')\""),r=e.settings.icon?n+"ico "+n+"i-"+r:"",'
"}})}),r(Ct,[q],function(e){return e.extend({Defaults:{defaultType:"button",role:"toolbar"},renderHtml:function(){var e=this,t=e._layout;return e.addClass("btn-group"),e.preRender(),t.preRender(e),'
'+(e.settings.html||"")+t.renderHtml(e)+"
"}})}),r(xt,[yt],function(e){return e.extend({Defaults:{classes:"checkbox",role:"checkbox",checked:!1},init:function(e){var t=this;t._super(e),t.on("click mousedown",function(e){e.preventDefault()}),t.on("click",function(e){e.preventDefault(),t.disabled()||t.checked(!t.checked())}),t.checked(t.settings.checked)},checked:function(e){var t=this;return"undefined"!=typeof e?(e?t.addClass("checked"):t.removeClass("checked"),t._checked=e,t.aria("checked",e),t):t._checked},value:function(e){return this.checked(e)},renderHtml:function(){var e=this,t=e._id,n=e.classPrefix;return'
'+e.encode(e._text)+"
"}})}),r(wt,[bt,X],function(e,t){return e.extend({showPanel:function(){var e=this,n=e.settings;if(e.active(!0),e.panel)e.panel.show();else{var r=n.panel;r.type&&(r={layout:"grid",items:r}),r.popover=!0,r.autohide=!0,e.panel=new t(r).on("hide",function(){e.active(!1)}).parent(e).renderTo(e.getContainerElm()),e.panel.fire("show"),e.panel.reflow()}e.panel.moveRel(e.getEl(),n.popoverAlign||(e.isRtl()?["bc-tr","bc-tc"]:["bc-tl","bc-tc"]))},hidePanel:function(){var e=this;e.panel&&e.panel.hide()},postRender:function(){var e=this;return e.on("click",function(t){t.control===e&&(e.panel&&e.panel.visible()?e.hidePanel():e.showPanel())}),e._super()}})}),r(_t,[wt,v],function(e,t){var n=t.DOM;return e.extend({init:function(e){this._super(e),this.addClass("colorbutton")},color:function(e){return e?(this._color=e,this.getEl("preview").style.backgroundColor=e,this):this._color},renderHtml:function(){var e=this,t=e._id,n=e.classPrefix,r=e.settings.icon?n+"ico "+n+"i-"+e.settings.icon:"",i=e.settings.image?" style=\"background-image: url('"+e.settings.image+"')\"":"";return'
'},postRender:function(){var e=this,t=e.settings.onclick;return e.on("click",function(r){r.control!=e||n.getParent(r.target,"."+e.classPrefix+"open")||(r.stopImmediatePropagation(),t.call(e,r))}),delete e.settings.onclick,e._super()}})}),r(Nt,[yt,W],function(e,t){return e.extend({init:function(e){var n=this;n._super(e),n.addClass("combobox"),n.on("click",function(e){for(var t=e.target;t;)t.id&&-1!=t.id.indexOf("-open")&&n.fire("action"),t=t.parentNode}),n.on("keydown",function(e){"INPUT"==e.target.nodeName&&13==e.keyCode&&n.parents().reverse().each(function(t){return e.preventDefault(),n.fire("change"),t.hasEventListeners("submit")&&t.toJSON?(t.fire("submit",{data:t.toJSON()}),!1):void 0})}),e.placeholder&&(n.addClass("placeholder"),n.on("focusin",function(){n._hasOnChange||(t.on(n.getEl("inp"),"change",function(){n.fire("change")}),n._hasOnChange=!0),n.hasClass("placeholder")&&(n.getEl("inp").value="",n.removeClass("placeholder"))}),n.on("focusout",function(){0===n.value().length&&(n.getEl("inp").value=e.placeholder,n.addClass("placeholder"))}))},value:function(e){var t=this;return"undefined"!=typeof e?(t._value=e,t.removeClass("placeholder"),t._rendered&&(t.getEl("inp").value=e),t):t._rendered?(e=t.getEl("inp").value,e!=t.settings.placeholder?e:""):t._value},disabled:function(e){var t=this;return t._rendered&&"undefined"!=typeof e&&(t.getEl("inp").disabled=e),t._super(e)},focus:function(){this.getEl("inp").focus()},repaint:function(){var e=this,n=e.getEl(),r=e.getEl("open"),i=e.layoutRect(),o,a;o=r?i.w-t.getSize(r).width-10:i.w-10;var s=document;return s.all&&(!s.documentMode||s.documentMode<=8)&&(a=e.layoutRect().h-2+"px"),t.css(n.firstChild,{width:o,lineHeight:a}),e._super(),e},postRender:function(){var e=this;return t.on(this.getEl("inp"),"change",function(){e.fire("change")}),e._super()},remove:function(){t.off(this.getEl("inp")),this._super()},renderHtml:function(){var e=this,t=e._id,n=e.settings,r=e.classPrefix,i=n.value||n.placeholder||"",o,a,s="";return o=n.icon?r+"ico "+r+"i-"+n.icon:"",a=e._text,(o||a)&&(s='
",e.addClass("has-open")),'
"+s+"
"}})}),r(Et,[yt,J],function(e,t){return e.extend({init:function(e){var t=this;e.delimiter||(e.delimiter="\xbb"),t._super(e),t.addClass("path"),t.canFocus=!0,t.on("click",function(e){var n,r=e.target;(n=r.getAttribute("data-index"))&&t.fire("select",{value:t.data()[n],index:n})})},focus:function(){var e=this;return e.keyNav=new t({root:e,enableLeftRight:!0}),e.keyNav.focusFirst(),e},data:function(e){var t=this;return"undefined"!=typeof e?(t._data=e,t.update(),t):t._data},update:function(){this.innerHtml(this._getPathHtml())},postRender:function(){var e=this;e._super(),e.data(e.settings.data)},renderHtml:function(){var e=this;return'
'+e._getPathHtml()+"
"},_getPathHtml:function(){var e=this,t=e._data||[],n,r,i="",o=e.classPrefix;for(n=0,r=t.length;r>n;n++)i+=(n>0?'":"")+'
'+t[n].name+"
";return i||(i='
 
'),i}})}),r(kt,[Et,st],function(e,t){return e.extend({postRender:function(){function e(e){if(1===e.nodeType){if("BR"==e.nodeName||e.getAttribute("data-mce-bogus"))return!0;if("bookmark"===e.getAttribute("data-mce-type"))return!0}return!1}var n=this,r=t.activeEditor;return n.on("select",function(t){var n=[],i,o=r.getBody();for(r.focus(),i=r.selection.getStart();i&&i!=o;)e(i)||n.push(i),i=i.parentNode;r.selection.select(n[n.length-1-t.index]),r.nodeChanged()}),r.on("nodeChange",function(t){for(var i=[],o=t.parents,a=o.length;a--;)if(1==o[a].nodeType&&!e(o[a])){var s=r.fire("ResolveName",{name:o[a].nodeName.toLowerCase(),target:o[a]});i.push({name:s.name})}n.data(i)}),n._super()}})}),r(St,[q],function(e){return e.extend({Defaults:{layout:"flex",align:"center",defaults:{flex:1}},renderHtml:function(){var e=this,t=e._layout,n=e.classPrefix;return e.addClass("formitem"),t.preRender(e),'
'+(e.settings.title?'
'+e.settings.title+"
":"")+'
'+(e.settings.html||"")+t.renderHtml(e)+"
"}})}),r(Tt,[q,St],function(e,t){return e.extend({Defaults:{containerCls:"form",layout:"flex",direction:"column",align:"stretch",flex:1,padding:20,labelGap:30,spacing:10,callbacks:{submit:function(){this.submit()}}},preRender:function(){var e=this,n=e.items();n.each(function(n){var r,i=n.settings.label;i&&(r=new t({layout:"flex",autoResize:"overflow",defaults:{flex:1},items:[{type:"label",text:i,flex:0,forId:n._id}]}),r.type="formitem","undefined"==typeof n.settings.flex&&(n.settings.flex=1),e.replace(n,r),r.add(n))})},recalcLabels:function(){var e=this,t=0,n=[],r,i;if(e.settings.labelGapCalc!==!1)for(e.items().filter("formitem").each(function(e){var r=e.items()[0],i=r.getEl().clientWidth;t=i>t?i:t,n.push(r)}),i=e.settings.labelGap||0,r=n.length;r--;)n[r].settings.minWidth=t+i},visible:function(e){var t=this._super(e);return e===!0&&this._rendered&&this.recalcLabels(),t},submit:function(){return this.fire("submit",{data:this.toJSON()})},postRender:function(){var e=this;e._super(),e.recalcLabels(),e.fromJSON(e.settings.data)}})}),r(Rt,[Tt],function(e){return e.extend({Defaults:{containerCls:"fieldset",layout:"flex",direction:"column",align:"stretch",flex:1,padding:"25 15 5 15",labelGap:30,spacing:10,border:1},renderHtml:function(){var e=this,t=e._layout,n=e.classPrefix;return e.preRender(),t.preRender(e),'
'+(e.settings.title?''+e.settings.title+"":"")+'
'+(e.settings.html||"")+t.renderHtml(e)+"
"}})}),r(At,[Nt],function(e){return e.extend({init:function(e){var t=this,n=tinymce.activeEditor,r;e.spellcheck=!1,r=n.settings.file_browser_callback,r&&(e.icon="browse",e.onaction=function(){r(t.getEl("inp").id,t.getEl("inp").value,e.filetype,window)}),t._super(e)}})}),r(Bt,[gt],function(e){return e.extend({recalc:function(e){var t=e.layoutRect(),n=e.paddingBox();e.items().filter(":visible").each(function(e){e.layoutRect({x:n.left,y:n.top,w:t.innerW-n.right-n.left,h:t.innerH-n.top-n.bottom}),e.recalc&&e.recalc()})}})}),r(Lt,[gt],function(e){return e.extend({recalc:function(e){var t,n,r,i,o,a,s,l,c,u,d,f,p,h,m,g,v=[],y,b,C,x,w,_,N,E,k,S,T,R,A,B,L,H,D,M,P,O,I,F,z,W,V=Math.max,U=Math.min;for(r=e.items().filter(":visible"),i=e.layoutRect(),o=e._paddingBox,a=e.settings,f=e.isRtl()?a.direction||"row-reversed":a.direction,s=a.align,l=e.isRtl()?a.pack||"end":a.pack,c=a.spacing||0,("row-reversed"==f||"column-reverse"==f)&&(r=r.set(r.toArray().reverse()),f=f.split("-")[0]),"column"==f?(k="y",N="h",E="minH",S="maxH",R="innerH",T="top",A="bottom",B="deltaH",L="contentH",I="left",M="w",H="x",D="innerW",P="minW",O="maxW",F="right",z="deltaW",W="contentW"):(k="x",N="w",E="minW",S="maxW",R="innerW",T="left",A="right",B="deltaW",L="contentW",I="top",M="h",H="y",D="innerH",P="minH",O="maxH",F="bottom",z="deltaH",W="contentH"),d=i[R]-o[T]-o[T],_=u=0,t=0,n=r.length;n>t;t++)p=r[t],h=p.layoutRect(),m=p.settings,g=m.flex,d-=n-1>t?c:0,g>0&&(u+=g,h[S]&&v.push(p),h.flex=g),d-=h[E],y=o[I]+h[P]+o[F],y>_&&(_=y);if(x={},x[E]=0>d?i[E]-d+i[B]:i[R]-d+i[B],x[P]=_+i[z],x[L]=i[R]-d,x[W]=_,x.minW=U(x.minW,i.maxW),x.minH=U(x.minH,i.maxH),x.minW=V(x.minW,i.startMinWidth),x.minH=V(x.minH,i.startMinHeight),!i.autoResize||x.minW==i.minW&&x.minH==i.minH){for(C=d/u,t=0,n=v.length;n>t;t++)p=v[t],h=p.layoutRect(),b=h[S],y=h[E]+h.flex*C,y>b?(d-=h[S]-h[E],u-=h.flex,h.flex=0,h.maxFlexSize=b):h.maxFlexSize=0;for(C=d/u,w=o[T],x={},0===u&&("end"==l?w=d+o[T]:"center"==l?(w=Math.round(i[R]/2-(i[R]-d)/2)+o[T],0>w&&(w=o[T])):"justify"==l&&(w=o[T],c=Math.floor(d/(r.length-1)))),x[H]=o[I],t=0,n=r.length;n>t;t++)p=r[t],h=p.layoutRect(),y=h.maxFlexSize||h[E],"center"===s?x[H]=Math.round(i[D]/2-h[M]/2):"stretch"===s?(x[M]=V(h[P]||0,i[D]-o[I]-o[F]),x[H]=o[I]):"end"===s&&(x[H]=i[D]-h[M]-o.top),h.flex>0&&(y+=h.flex*C),x[N]=y,x[k]=w,p.layoutRect(x),p.recalc&&p.recalc(),w+=y+c}else if(x.w=x.minW,x.h=x.minH,e.layoutRect(x),this.recalc(e),null===e._lastRect){var q=e.parent();q&&(q._lastRect=null,q.recalc())}}})}),r(Ht,[mt],function(e){return e.extend({Defaults:{containerClass:"flow-layout",controlClass:"flow-layout-item",endClass:"break"},recalc:function(e){e.items().filter(":visible").each(function(e){e.recalc&&e.recalc()})}})}),r(Dt,[V,yt,X,p,st,g],function(e,t,n,r,i,o){function a(e){function t(t){function n(e){return e.replace(/%(\w+)/g,"")}var r,i,o=e.dom,a="",l,c;return c=e.settings.preview_styles,c===!1?"":(c||(c="font-family font-size font-weight font-style text-decoration text-transform color background-color border border-radius"),(t=e.formatter.get(t))?(t=t[0],r=t.block||t.inline||"span",i=o.create(r),s(t.styles,function(e,t){e=n(e),e&&o.setStyle(i,t,e)}),s(t.attributes,function(e,t){e=n(e),e&&o.setAttrib(i,t,e)}),s(t.classes,function(e){e=n(e),o.hasClass(i,e)||o.addClass(i,e)}),e.fire("PreviewFormats"),o.setStyles(i,{position:"absolute",left:-65535}),e.getBody().appendChild(i),l=o.getStyle(e.getBody(),"fontSize",!0),l=/px$/.test(l)?parseInt(l,10):0,s(c.split(" "),function(t){var n=o.getStyle(i,t,!0);if(!("background-color"==t&&/transparent|rgba\s*\([^)]+,\s*0\)/.test(n)&&(n=o.getStyle(e.getBody(),t,!0),"#ffffff"==o.toHex(n).toLowerCase())||"color"==t&&"#000000"==o.toHex(n).toLowerCase())){if("font-size"==t&&/em|%$/.test(n)){if(0===l)return;n=parseFloat(n,10)/(/%$/.test(n)?100:1),n=n*l+"px"}"border"==t&&n&&(a+="padding:0 2px;"),a+=t+":"+n+";"}}),e.fire("AfterPreviewFormats"),o.remove(i),a):void 0)}function r(t,n){return function(){var r=this;e.on("nodeChange",function(i){var o=e.formatter,a=null;s(i.parents,function(e){return s(t,function(t){return n?o.matchNode(e,n,{value:t.value})&&(a=t.value):o.matchNode(e,t.value)&&(a=t.value),a?!1:void 0}),a?!1:void 0}),r.value(a)})}}function i(e){e=e.split(";");for(var t=e.length;t--;)e[t]=e[t].split("=");return e}function o(){function n(e){var t=[];if(e)return s(e,function(e){var o={text:e.title,icon:e.icon};if(e.items)o.menu=n(e.items);else{var a=e.format||"custom"+r++;e.format||(e.name=a,i.push(e)),o.format=a}t.push(o)}),t}var r=0,i=[],o=[{title:"Headers",items:[{title:"Header 1",format:"h1"},{title:"Header 2",format:"h2"},{title:"Header 3",format:"h3"},{title:"Header 4",format:"h4"},{title:"Header 5",format:"h5"},{title:"Header 6",format:"h6"}]},{title:"Inline",items:[{title:"Bold",icon:"bold",format:"bold"},{title:"Italic",icon:"italic",format:"italic"},{title:"Underline",icon:"underline",format:"underline"},{title:"Strikethrough",icon:"strikethrough",format:"strikethrough"},{title:"Superscript",icon:"superscript",format:"superscript"},{title:"Subscript",icon:"subscript",format:"subscript"},{title:"Code",icon:"code",format:"code"}]},{title:"Blocks",items:[{title:"Paragraph",format:"p"},{title:"Blockquote",format:"blockquote"},{title:"Div",format:"div"},{title:"Pre",format:"pre"}]},{title:"Alignment",items:[{title:"Left",icon:"alignleft",format:"alignleft"},{title:"Center",icon:"aligncenter",format:"aligncenter"},{title:"Right",icon:"alignright",format:"alignright"},{title:"Justify",icon:"alignjustify",format:"alignjustify"}]}];e.on("init",function(){s(i,function(t){e.formatter.register(t.name,t)})});var a=n(e.settings.style_formats||o); -return a={type:"menu",items:a,onPostRender:function(t){e.fire("renderFormatsMenu",{control:t.control})},itemDefaults:{preview:!0,textStyle:function(){return this.settings.format?t(this.settings.format):void 0},onPostRender:function(){var t=this,n=this.settings.format;n&&t.parent().on("show",function(){t.disabled(!e.formatter.canApply(n)),t.active(e.formatter.match(n))})},onclick:function(){this.settings.format&&f(this.settings.format)}}}}function a(){return e.undoManager?e.undoManager.hasUndo():!1}function l(){return e.undoManager?e.undoManager.hasRedo():!1}function c(){var t=this;t.disabled(!a()),e.on("Undo Redo AddUndo TypingUndo",function(){t.disabled(!a())})}function u(){var t=this;t.disabled(!l()),e.on("Undo Redo AddUndo TypingUndo",function(){t.disabled(!l())})}function d(){var t=this;e.on("VisualAid",function(e){t.active(e.hasVisual)}),t.active(e.hasVisual)}function f(t){t.control&&(t=t.control.value()),t&&e.execCommand("mceToggleFormat",!1,t)}var p;p=o(),s({bold:"Bold",italic:"Italic",underline:"Underline",strikethrough:"Strikethrough",subscript:"Subscript",superscript:"Superscript"},function(t,n){e.addButton(n,{tooltip:t,onPostRender:function(){var t=this;e.formatter?e.formatter.formatChanged(n,function(e){t.active(e)}):e.on("init",function(){e.formatter.formatChanged(n,function(e){t.active(e)})})},onclick:function(){f(n)}})}),s({outdent:["Decrease indent","Outdent"],indent:["Increase indent","Indent"],cut:["Cut","Cut"],copy:["Copy","Copy"],paste:["Paste","Paste"],help:["Help","mceHelp"],selectall:["Select all","SelectAll"],hr:["Insert horizontal rule","InsertHorizontalRule"],removeformat:["Clear formatting","RemoveFormat"],visualaid:["Visual aids","mceToggleVisualAid"],newdocument:["New document","mceNewDocument"]},function(t,n){e.addButton(n,{tooltip:t[0],cmd:t[1]})}),s({blockquote:["Toggle blockquote","mceBlockQuote"],numlist:["Numbered list","InsertOrderedList"],bullist:["Bullet list","InsertUnorderedList"],subscript:["Subscript","Subscript"],superscript:["Superscript","Superscript"],alignleft:["Align left","JustifyLeft"],aligncenter:["Align center","JustifyCenter"],alignright:["Align right","JustifyRight"],alignjustify:["Justify","JustifyFull"]},function(t,n){e.addButton(n,{tooltip:t[0],cmd:t[1],onPostRender:function(){var t=this;e.formatter?e.formatter.formatChanged(n,function(e){t.active(e)}):e.on("init",function(){e.formatter.formatChanged(n,function(e){t.active(e)})})}})}),e.addButton("undo",{tooltip:"Undo",onPostRender:c,cmd:"undo"}),e.addButton("redo",{tooltip:"Redo",onPostRender:u,cmd:"redo"}),e.addMenuItem("newdocument",{text:"New document",shortcut:"Ctrl+N",icon:"newdocument",cmd:"mceNewDocument"}),e.addMenuItem("undo",{text:"Undo",icon:"undo",shortcut:"Ctrl+Z",onPostRender:c,cmd:"undo"}),e.addMenuItem("redo",{text:"Redo",icon:"redo",shortcut:"Ctrl+Y",onPostRender:u,cmd:"redo"}),e.addMenuItem("visualaid",{text:"Visual aids",selectable:!0,onPostRender:d,cmd:"mceToggleVisualAid"}),s({cut:["Cut","Cut","Ctrl+X"],copy:["Copy","Copy","Ctrl+C"],paste:["Paste","Paste","Ctrl+V"],selectall:["Select all","SelectAll","Ctrl+A"],bold:["Bold","Bold","Ctrl+B"],italic:["Italic","Italic","Ctrl+I"],underline:["Underline","Underline"],strikethrough:["Strikethrough","Strikethrough"],subscript:["Subscript","Subscript"],superscript:["Superscript","Superscript"],removeformat:["Clear formatting","RemoveFormat"]},function(t,n){e.addMenuItem(n,{text:t[0],icon:n,shortcut:t[2],cmd:t[1]})}),e.on("mousedown",function(){n.hideAll()}),e.addButton("styleselect",{type:"menubutton",text:"Formats",menu:p}),e.addButton("formatselect",function(){var n=[],o=i(e.settings.block_formats||"Paragraph=p;Address=address;Pre=pre;Header 1=h1;Header 2=h2;Header 3=h3;Header 4=h4;Header 5=h5;Header 6=h6");return s(o,function(e){n.push({text:e[0],value:e[1],textStyle:function(){return t(e[1])}})}),{type:"listbox",text:{raw:o[0][0]},values:n,fixedWidth:!0,onselect:f,onPostRender:r(n)}}),e.addButton("fontselect",function(){var t="Andale Mono=andale mono,times;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book antiqua,palatino;Comic Sans MS=comic sans ms,sans-serif;Courier New=courier new,courier;Georgia=georgia,palatino;Helvetica=helvetica;Impact=impact,chicago;Symbol=symbol;Tahoma=tahoma,arial,helvetica,sans-serif;Terminal=terminal,monaco;Times New Roman=times new roman,times;Trebuchet MS=trebuchet ms,geneva;Verdana=verdana,geneva;Webdings=webdings;Wingdings=wingdings,zapf dingbats",n=[],o=i(e.settings.font_formats||t);return s(o,function(e){n.push({text:{raw:e[0]},value:e[1],textStyle:-1==e[1].indexOf("dings")?"font-family:"+e[1]:""})}),{type:"listbox",text:"Font Family",tooltip:"Font Family",values:n,fixedWidth:!0,onPostRender:r(n,"fontname"),onselect:function(t){t.control.settings.value&&e.execCommand("FontName",!1,t.control.settings.value)}}}),e.addButton("fontsizeselect",function(){var t=[],n="8pt 10pt 12pt 14pt 18pt 24pt 36pt",i=e.settings.fontsize_formats||n;return s(i.split(" "),function(e){t.push({text:e,value:e})}),{type:"listbox",text:"Font Sizes",tooltip:"Font Sizes",values:t,fixedWidth:!0,onPostRender:r(t,"fontsize"),onclick:function(t){t.control.settings.value&&e.execCommand("FontSize",!1,t.control.settings.value)}}}),e.addMenuItem("formats",{text:"Formats",menu:p})}var s=r.each;i.on("AddEditor",function(t){t.editor.rtl&&(e.rtl=!0),a(t.editor)}),e.translate=function(e){return i.translate(e)},t.tooltips=!o.iOS}),r(Mt,[gt],function(e){return e.extend({recalc:function(e){var t=e.settings,n,r,i,o,a,s,l,c,u,d,f,p,h,m,g,v,y,b,C,x,w,_,N=[],E=[],k,S,T,R,A,B;for(t=e.settings,i=e.items().filter(":visible"),o=e.layoutRect(),r=t.columns||Math.ceil(Math.sqrt(i.length)),n=Math.ceil(i.length/r),y=t.spacingH||t.spacing||0,b=t.spacingV||t.spacing||0,C=t.alignH||t.align,x=t.alignV||t.align,g=e._paddingBox,C&&"string"==typeof C&&(C=[C]),x&&"string"==typeof x&&(x=[x]),d=0;r>d;d++)N.push(0);for(f=0;n>f;f++)E.push(0);for(f=0;n>f;f++)for(d=0;r>d&&(u=i[f*r+d],u);d++)c=u.layoutRect(),k=c.minW,S=c.minH,N[d]=k>N[d]?k:N[d],E[f]=S>E[f]?S:E[f];for(A=o.innerW-g.left-g.right,w=0,d=0;r>d;d++)w+=N[d]+(d>0?y:0),A-=(d>0?y:0)+N[d];for(B=o.innerH-g.top-g.bottom,_=0,f=0;n>f;f++)_+=E[f]+(f>0?b:0),B-=(f>0?b:0)+E[f];if(w+=g.left+g.right,_+=g.top+g.bottom,l={},l.minW=w+(o.w-o.innerW),l.minH=_+(o.h-o.innerH),l.contentW=l.minW-o.deltaW,l.contentH=l.minH-o.deltaH,l.minW=Math.min(l.minW,o.maxW),l.minH=Math.min(l.minH,o.maxH),l.minW=Math.max(l.minW,o.startMinWidth),l.minH=Math.max(l.minH,o.startMinHeight),!o.autoResize||l.minW==o.minW&&l.minH==o.minH){o.autoResize&&(l=e.layoutRect(l),l.contentW=l.minW-o.deltaW,l.contentH=l.minH-o.deltaH);var L;L="start"==t.packV?0:B>0?Math.floor(B/n):0;var H=0,D=t.flexWidths;if(D)for(d=0;dd;d++)N[d]+=D?D[d]*M:M;for(h=g.top,f=0;n>f;f++){for(p=g.left,s=E[f]+L,d=0;r>d&&(u=i[f*r+d],u);d++)m=u.settings,c=u.layoutRect(),a=Math.max(N[d],c.startMinWidth),T=R=0,c.x=p,c.y=h,v=m.alignH||(C?C[d]||C[0]:null),"center"==v?c.x=p+a/2-c.w/2:"right"==v?c.x=p+a-c.w:"stretch"==v&&(c.w=a),v=m.alignV||(x?x[d]||x[0]:null),"center"==v?c.y=h+s/2-c.h/2:"bottom"==v?c.y=h+s-c.h:"stretch"==v&&(c.h=s),u.layoutRect(c),p+=a+y,u.recalc&&u.recalc();h+=s+b}}else if(l.w=l.minW,l.h=l.minH,e.layoutRect(l),this.recalc(e),null===e._lastRect){var P=e.parent();P&&(P._lastRect=null,P.recalc())}}})}),r(Pt,[yt],function(e){return e.extend({renderHtml:function(){var e=this;return e.addClass("iframe"),e.canFocus=!1,''},src:function(e){this.getEl().src=e},html:function(e,t){var n=this,r=this.getEl().contentWindow.document.body;return r?(r.innerHTML=e,t&&t()):setTimeout(function(){n.html(e)},0),this}})}),r(Ot,[yt,W],function(e,t){return e.extend({init:function(e){var t=this;t._super(e),t.addClass("widget"),t.addClass("label"),t.canFocus=!1,e.multiline&&t.addClass("autoscroll"),e.strong&&t.addClass("strong")},initLayoutRect:function(){var e=this,n=e._super();if(e.settings.multiline){var r=t.getSize(e.getEl());r.width>n.maxW&&(n.minW=n.maxW,e.addClass("multiline")),e.getEl().style.width=n.minW+"px",n.startMinH=n.h=n.minH=Math.min(n.maxH,t.getSize(e.getEl()).height)}return n},repaint:function(){var e=this;return e.settings.multiline||(e.getEl().style.lineHeight=e.layoutRect().h+"px"),e._super()},text:function(e){var t=this;return t._rendered&&e&&this.innerHtml(t.encode(e)),t._super(e)},renderHtml:function(){var e=this,t=e.settings.forId;return'"}})}),r(It,[q,J],function(e,t){return e.extend({Defaults:{role:"toolbar",layout:"flow"},init:function(e){var t=this;t._super(e),t.addClass("toolbar")},postRender:function(){var e=this;return e.items().addClass("toolbar-item"),e.keyNav=new t({root:e,enableLeftRight:!0}),e._super()}})}),r(Ft,[It],function(e){return e.extend({Defaults:{role:"menubar",containerCls:"menubar",defaults:{type:"menubutton"}}})}),r(zt,[bt,U,Ft],function(e,t,n){function r(e,t){for(;e;){if(t===e)return!0;e=e.parentNode}return!1}var i=e.extend({init:function(e){var t=this;t._renderOpen=!0,t._super(e),t.addClass("menubtn"),e.fixedWidth&&t.addClass("fixed-width"),t.aria("haspopup",!0),t.hasPopup=!0},showMenu:function(){var e=this,n=e.settings,r;return e.menu&&e.menu.visible()?e.hideMenu():(e.menu||(r=n.menu||[],r.length?r={type:"menu",items:r}:r.type=r.type||"menu",e.menu=t.create(r).parent(e).renderTo(e.getContainerElm()),e.fire("createmenu"),e.menu.reflow(),e.menu.on("cancel",function(t){t.control===e.menu&&e.focus()}),e.menu.on("show hide",function(t){t.control==e.menu&&e.activeMenu("show"==t.type)}).fire("show"),e.aria("expanded",!0)),e.menu.show(),e.menu.layoutRect({w:e.layoutRect().w}),e.menu.moveRel(e.getEl(),e.isRtl()?["br-tr","tr-br"]:["bl-tl","tl-bl"]),void 0)},hideMenu:function(){var e=this;e.menu&&(e.menu.items().each(function(e){e.hideMenu&&e.hideMenu()}),e.menu.hide(),e.aria("expanded",!1))},activeMenu:function(e){this.toggleClass("active",e)},renderHtml:function(){var e=this,t=e._id,r=e.classPrefix,i=e.settings.icon?r+"ico "+r+"i-"+e.settings.icon:"";return e.aria("role",e.parent()instanceof n?"menuitem":"button"),'
'},postRender:function(){var e=this;return e.on("click",function(t){t.control===e&&r(t.target,e.getEl())&&(e.showMenu(),t.keyboard&&e.menu.items()[0].focus())}),e.on("mouseenter",function(t){var n=t.control,r=e.parent(),o;n&&r&&n instanceof i&&n.parent()==r&&(r.items().filter("MenuButton").each(function(e){e.hideMenu&&e!=n&&(e.menu&&e.menu.visible()&&(o=!0),e.hideMenu())}),o&&(n.focus(),n.showMenu()))}),e._super()},text:function(e){var t=this,n,r;if(t._rendered)for(r=t.getEl("open").getElementsByTagName("span"),n=0;n'+("-"!==o?' ":"")+("-"!==o?''+o+"":"")+(l?'
'+l+"
":"")+(r.menu?'
':"")+""},postRender:function(){var e=this,t=e.settings,n=t.textStyle;if("function"==typeof n&&(n=n.call(this)),n){var r=e.getEl("text");r&&r.setAttribute("style",n)}return e._super()},remove:function(){this._super(),this.menu&&this.menu.remove()}})}),r(Ut,[X,J,Vt,p],function(e,t,n,r){var i=e.extend({Defaults:{defaultType:"menuitem",border:1,layout:"stack",role:"menu"},init:function(e){var i=this;if(e.autohide=!0,e.constrainToViewport=!0,e.itemDefaults)for(var o=e.items,a=o.length;a--;)o[a]=r.extend({},e.itemDefaults,o[a]);i._super(e),i.addClass("menu"),i.keyNav=new t({root:i,enableUpDown:!0,enableLeftRight:!0,leftAction:function(){i.parent()instanceof n&&i.keyNav.cancel()},onCancel:function(){i.fire("cancel",{},!1),i.hide()}})},repaint:function(){return this.toggleClass("menu-align",!0),this._super(),this.getEl().style.height="",this.getEl("body").style.height="",this},cancel:function(){var e=this;e.hideAll(),e.fire("cancel"),e.fire("select")},hideAll:function(){var e=this;return this.find("menuitem").exec("hideMenu"),e._super()},preRender:function(){var e=this;return e.items().each(function(t){var n=t.settings;return n.icon||n.selectable?(e._hasIcons=!0,!1):void 0}),e._super()}});return i}),r(qt,[xt],function(e){return e.extend({Defaults:{classes:"radio",role:"radio"}})}),r($t,[yt,$],function(e,t){return e.extend({renderHtml:function(){var e=this,t=e.classPrefix;return e.addClass("resizehandle"),"both"==e.settings.direction&&e.addClass("resizehandle-both"),e.canFocus=!1,'
'},postRender:function(){var e=this;e._super(),e.resizeDragHelper=new t(this._id,{start:function(){e.fire("ResizeStart")},drag:function(t){"both"!=e.settings.direction&&(t.deltaX=0),e.fire("Resize",t)},end:function(){e.fire("ResizeEnd")}})},remove:function(){return this.resizeDragHelper&&this.resizeDragHelper.destroy(),this._super()}})}),r(jt,[yt],function(e){return e.extend({renderHtml:function(){var e=this;return e.addClass("spacer"),e.canFocus=!1,'
'}})}),r(Kt,[zt,W],function(e,t){return e.extend({Defaults:{classes:"widget btn splitbtn",role:"splitbutton"},repaint:function(){var e=this,n=e.getEl(),r=e.layoutRect(),i,o;return e._super(),i=n.firstChild,o=n.lastChild,t.css(i,{width:r.w-t.getSize(o).width,height:r.h-2}),t.css(o,{height:r.h-2}),e},activeMenu:function(e){var n=this;t.toggleClass(n.getEl().lastChild,n.classPrefix+"active",e)},renderHtml:function(){var e=this,t=e._id,n=e.classPrefix,r=e.settings.icon?n+"ico "+n+"i-"+e.settings.icon:"";return'
'},postRender:function(){var e=this,t=e.settings.onclick;return e.on("click",function(e){var n=e.target;if(e.control==this)for(;n;){if("BUTTON"==n.nodeName&&-1==n.className.indexOf("open"))return e.stopImmediatePropagation(),t.call(this,e),void 0;n=n.parentNode}}),delete e.settings.onclick,e._super()}})}),r(Gt,[Ht],function(e){return e.extend({Defaults:{containerClass:"stack-layout",controlClass:"stack-layout-item",endClass:"break"}})}),r(Yt,[K,W],function(e,t){return e.extend({lastIdx:0,Defaults:{layout:"absolute",defaults:{type:"panel"}},activateTab:function(e){this.activeTabId&&t.removeClass(this.getEl(this.activeTabId),this.classPrefix+"active"),this.activeTabId="t"+e,t.addClass(this.getEl("t"+e),this.classPrefix+"active"),e!=this.lastIdx&&(this.items()[this.lastIdx].hide(),this.lastIdx=e),this.items()[e].show().fire("showtab"),this.reflow()},renderHtml:function(){var e=this,t=e._layout,n="",r=e.classPrefix;return e.preRender(),t.preRender(e),e.items().each(function(t,i){n+='
'+e.encode(t.settings.title)+"
"}),'
'+n+'
'+t.renderHtml(e)+"
"},postRender:function(){var e=this;e._super(),e.settings.activeTab=e.settings.activeTab||0,e.activateTab(e.settings.activeTab),this.on("click",function(t){var n=t.target.parentNode;if(t.target.parentNode.id==e._id+"-head")for(var r=n.childNodes.length;r--;)n.childNodes[r]==t.target&&e.activateTab(r)})},initLayoutRect:function(){var e=this,n,r,i;r=t.getSize(e.getEl("head")).width,r=0>r?0:r,i=0,e.items().each(function(t,n){r=Math.max(r,t.layoutRect().minW),i=Math.max(i,t.layoutRect().minH),e.settings.activeTab!=n&&t.hide()}),e.items().each(function(e){e.settings.x=0,e.settings.y=0,e.settings.w=r,e.settings.h=i,e.layoutRect({x:0,y:0,w:r,h:i})});var o=t.getSize(e.getEl("head")).height;return e.settings.minWidth=r,e.settings.minHeight=i+o,n=e._super(),n.deltaH+=o,n.innerH=n.h-n.deltaH,n}})}),r(Xt,[yt,W],function(e,t){return e.extend({init:function(e){var t=this;t._super(e),t._value=e.value||"",t.addClass("textbox"),e.multiline?t.addClass("multiline"):t.on("keydown",function(e){13==e.keyCode&&t.parents().reverse().each(function(t){return e.preventDefault(),t.hasEventListeners("submit")&&t.toJSON?(t.fire("submit",{data:t.toJSON()}),!1):void 0})})},disabled:function(e){var t=this;return t._rendered&&"undefined"!=typeof e&&(t.getEl().disabled=e),t._super(e)},value:function(e){var t=this;return"undefined"!=typeof e?(t._value=e,t._rendered&&(t.getEl().value=e),t):t._rendered?t.getEl().value:t._value},repaint:function(){var e=this,t,n,r,i=0,o=0,a;t=e.getEl().style,n=e._layoutRect,a=e._lastRepaintRect||{};var s=document;return!e.settings.multiline&&s.all&&(!s.documentMode||s.documentMode<=8)&&(t.lineHeight=n.h-o+"px"),r=e._borderBox,i=r.left+r.right+8,o=r.top+r.bottom+(e.settings.multiline?8:0),n.x!==a.x&&(t.left=n.x+"px",a.x=n.x),n.y!==a.y&&(t.top=n.y+"px",a.y=n.y),n.w!==a.w&&(t.width=n.w-i+"px",a.w=n.w),n.h!==a.h&&(t.height=n.h-o+"px",a.h=n.h),e._lastRepaintRect=a,e.fire("repaint",{},!1),e},renderHtml:function(){var e=this,t=e._id,n=e.settings,r=e.encode(e._value,!1),i="";return"spellcheck"in n&&(i+=' spellcheck="'+n.spellcheck+'"'),n.maxLength&&(i+=' maxlength="'+n.maxLength+'"'),n.size&&(i+=' size="'+n.size+'"'),n.subtype&&(i+=' type="'+n.subtype+'"'),e.disabled()&&(i+=' disabled="disabled"'),n.multiline?'":'"},postRender:function(){var e=this;return t.on(e.getEl(),"change",function(t){e.fire("change",t)}),e._super()},remove:function(){t.off(this.getEl()),this._super()}})}),r(Jt,[W],function(e){return function(t){var n=this,r;n.show=function(i){return n.hide(),r=!0,window.setTimeout(function(){r&&t.appendChild(e.createFragment('
'))},i||0),n},n.hide=function(){var e=t.lastChild;return e&&-1!=e.className.indexOf("throbber")&&e.parentNode.removeChild(e),r=!1,n}}}),a([l,c,u,d,f,p,h,m,g,v,y,b,C,x,w,_,N,E,k,S,T,R,A,B,L,H,D,M,P,O,I,F,z,W,V,U,q,$,j,K,G,Y,X,J,Q,Z,et,tt,nt,rt,it,ot,at,st,lt,ct,ut,dt,ft,pt,ht,mt,gt,vt,yt,bt,Ct,xt,wt,_t,Nt,Et,kt,St,Tt,Rt,At,Bt,Lt,Ht,Dt,Mt,Pt,Ot,It,Ft,zt,Wt,Vt,Ut,qt,$t,jt,Kt,Gt,Yt,Xt,Jt])}(this); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/umbraco/LegacyUmbClientMgr.js b/src/Umbraco.Web.UI.Client/lib/umbraco/LegacyUmbClientMgr.js index 98a9987019..75f0850ee2 100644 --- a/src/Umbraco.Web.UI.Client/lib/umbraco/LegacyUmbClientMgr.js +++ b/src/Umbraco.Web.UI.Client/lib/umbraco/LegacyUmbClientMgr.js @@ -384,6 +384,11 @@ Umbraco.Sys.registerNamespace("Umbraco.Application"); //just call the native dialog close() method to remove the dialog lastModal.close(); + + //if it's the last one close them all + if (this._modal.length == 0) { + getRootScope().$emit("app.closeDialogs", undefined); + } } else { //instead of calling just the dialog service we funnel it through the global diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index b8ac55d60c..3fb598edf2 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -21,9 +21,13 @@ }, "dependencies": {}, "devDependencies": { - "bower": "~1.3.3", + "autoprefixer-core": "~5.2.1", + "bower": "^1.4.1", + "eslint": "^0.23.0", + "eslint-plugin-angular": "0.0.13", "grunt": "~0.4.0", - "grunt-bower-task": "^0.4.0", + "grunt-bower": "^0.19.0", + "grunt-bower-install-simple": "^1.1.3", "grunt-contrib-clean": "~0.4.0", "grunt-contrib-concat": "~0.1.3", "grunt-contrib-connect": "~0.3.0", @@ -31,10 +35,13 @@ "grunt-contrib-jshint": "~0.2.0", "grunt-contrib-uglify": "~0.1.1", "grunt-contrib-watch": "~0.3.1", + "grunt-eslint": "^15.0.0", "grunt-html2js": "~0.1.0", + "grunt-hustler": "^4.0.6", "grunt-karma": "~0.5", "grunt-ngdocs": "~0.1.2", "grunt-open": "~0.2.0", + "grunt-postcss": "~0.6.0", "grunt-recess": "~0.3", "karma": "~0.9", "karma-chrome-launcher": "0.0.2", diff --git a/src/Umbraco.Web.UI.Client/src/app.dev.js b/src/Umbraco.Web.UI.Client/src/app.dev.js index 774186609f..6abddd4e2f 100644 --- a/src/Umbraco.Web.UI.Client/src/app.dev.js +++ b/src/Umbraco.Web.UI.Client/src/app.dev.js @@ -7,12 +7,10 @@ var app = angular.module('umbraco', [ 'ngCookies', 'ngMobile', 'ngSanitize', - /*'ui.sortable',*/ - 'blueimp.fileupload', 'tmh.dynamicLocale' ]); -/* For Angular 1.2: we need to load in Route, animate and touch seperately +/* For Angular 1.4: we need to load in Route, animate and touch seperately 'ngRoute', 'ngAnimate', 'ngTouch' diff --git a/src/Umbraco.Web.UI.Client/src/app.js b/src/Umbraco.Web.UI.Client/src/app.js index c541e0776c..60583852b3 100644 --- a/src/Umbraco.Web.UI.Client/src/app.js +++ b/src/Umbraco.Web.UI.Client/src/app.js @@ -4,18 +4,37 @@ var app = angular.module('umbraco', [ 'umbraco.resources', 'umbraco.services', 'umbraco.packages', + 'umbraco.views', + 'ngCookies', 'ngSanitize', 'ngMobile', - 'blueimp.fileupload', - 'tmh.dynamicLocale' + 'tmh.dynamicLocale', + 'ngFileUpload' ]); + var packages = angular.module("umbraco.packages", []); -//Call a document callback if defined, this is sort of a dodgy hack to +//this ensures we can inject our own views into templateCache and clear +//the entire cache before the app runs, due to the module +//order, clearing will always happen before umbraco.views and umbraco +//module is initilized. +angular.module("umbraco.views", ["umbraco.viewcache"]); +angular.module("umbraco.viewcache", []) + .run(function($rootScope, $templateCache){ + /** For debug mode, always clear template cache to cut down on + dev frustration and chrome cache on templates */ + if(Umbraco.Sys.ServerVariables.isDebuggingEnabled){ + //$rootScope.$on('$viewContentLoaded', function() { + $templateCache.removeAll(); + //}); + } + }) + +//Call a document callback if defined, this is sort of a dodgy hack to // be able to configure angular values in the Default.cshtml // view which is much easier to do that configuring values by injecting them in the back office controller // to follow through to the js initialization stuff if (angular.isFunction(document.angularReady)) { document.angularReady.apply(this, [app]); -} \ No newline at end of file +} diff --git a/src/Umbraco.Web.UI.Client/src/assets/css/nonodes.style.min.css b/src/Umbraco.Web.UI.Client/src/assets/css/nonodes.style.min.css index 68cc761e96..5299a59c7e 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/css/nonodes.style.min.css +++ b/src/Umbraco.Web.UI.Client/src/assets/css/nonodes.style.min.css @@ -1 +1,17 @@ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 400; + src: url('../fonts/opensans/OpenSans-Regular-webfont.eot'); + src: local('Open Sans'), local('OpenSans'), url('../fonts/opensans/OpenSans-Regular-webfont.eot?#iefix') format('embedded-opentype'), url('../fonts/opensans/OpenSans-Regular-webfont.ttf') format('truetype'), url('../fonts/opensans/OpenSans-Regular-webfont.svg#open_sansregular') format('svg'); +} + +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 600; + src: url('../fonts/opensans/OpenSans-Semibold-webfont.eot'); + src: local('Open Sans Semibold'), local('OpenSans-Semibold'), url('../fonts/opensans/OpenSans-Semibold-webfont.eot?#iefix') format('embedded-opentype'), url('../fonts/opensans/OpenSans-Semibold-webfont.ttf') format('truetype'), url('../fonts/opensans/OpenSans-Semibold-webfont.svg#open_sanssemibold') format('svg'); +} + abbr,address,article,aside,audio,b,blockquote,body,canvas,caption,cite,code,dd,del,details,dfn,div,dl,dt,em,fieldset,figcaption,figure,footer,form,h1,h2,h3,h4,h5,h6,header,hgroup,html,i,iframe,img,ins,kbd,label,legend,li,mark,menu,nav,object,ol,p,pre,q,samp,section,small,span,strong,sub,summary,sup,table,tbody,td,tfoot,th,thead,time,tr,ul,var,video{margin:0;padding:0;outline:0;border:0;background:0 0;vertical-align:baseline;font-size:100%}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}nav ul{list-style:none}blockquote,q{quotes:none}blockquote:after,blockquote:before,q:after,q:before{content:'';content:none}a{margin:0;padding:0;background:0 0;vertical-align:baseline;font-size:100%}ins{background-color:#ff9;color:#000;text-decoration:none}mark{background-color:#ff9;color:#000;font-weight:700;font-style:italic}del{text-decoration:line-through}abbr[title],dfn[title]{border-bottom:1px dotted;cursor:help}table{border-spacing:0;border-collapse:collapse}hr{display:block;margin:1em 0;padding:0;height:1px;border:0;border-top:1px solid #ccc}input,select{vertical-align:middle}html{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}*,:after,:before{box-sizing:border-box}body,html{height:100%;width:100%;color:#fff;font-family:'Open Sans',sans-serif;font-weight:400;font-size:.9375em;line-height:1.5}h1{font-size:1.7em;margin:40px auto 10px;font-weight:700}h2{font-size:1.35em;margin:0 auto .4em;font-weight:700}h3{font-size:1em;font-weight:400;font-style:italic}p{font-size:1em;line-height:1.6}p+a{margin-top:1rem;display:inline-block}a,a:active,a:visited{text-decoration:none}.cta{margin:4.5em auto 1.5em;padding-bottom:4.5em}.button,.button:visited{padding:.9375em 1.875em;border-radius:.1em;font-weight:600;font-size:1.2em;background:#2e99f4;color:#fff;display:inline-block;border:none;transition:all 200ms ease-in-out}.button:hover,.button:visited:hover{border-bottom:none;background:#0c80e3}section{background:url(../img/nonodesbg.jpg) center center/cover;height:100%;width:100%;display:table;padding:3em 1.75em}section a,section a:focus,section a:visited{color:#46a5f5;font-size:1.1625em;border-bottom:1px solid transparent;transition:border-bottom 100ms ease-in-out}section a:focus:hover,section a:hover,section a:visited:hover{border-bottom:1px solid}section:after{content:"";position:absolute;top:0;right:0;bottom:0;left:0;background:rgba(0,0,0,.17);background:linear-gradient(45deg,rgba(85,98,112,.1) 10%,rgba(255,107,107,.1) 95%);z-index:0}section article{display:table-cell;vertical-align:middle;margin:0 auto;text-align:center;position:relative;z-index:100}section article>div{max-width:60em;margin:0 auto}section .logo{background:url(../img/logo.png) no-repeat;width:91px;height:91px;margin:0 auto}section .row{overflow:hidden}section .row .col{text-align:left;width:100%}section .row .col:nth-child(2){margin-top:3em}@media screen and (min-width:48em){body,html{font-size:1em}h1{font-size:2.5em;margin:70px auto 0;letter-spacing:.5px}h2{font-size:1.4375em;margin:0 auto 1em}h3{font-size:1.2em}a{font-size:1.1rem;font-weight:600}p+a{margin-top:3rem}.cta{margin:7.5em auto 2.5em;border-bottom:1px solid rgba(255,255,255,.5);padding-bottom:7.5em}section{padding:0 15px}section .row .col{float:left;width:50%;padding-right:5%;display:inline-block}section .row .col:nth-child(2){padding-right:0;padding-left:5%;margin-top:0}.button{font-size:1.1625em}} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/img/application/logo.png b/src/Umbraco.Web.UI.Client/src/assets/img/application/logo.png index 2b2ff80a1d..388354e8a5 100644 Binary files a/src/Umbraco.Web.UI.Client/src/assets/img/application/logo.png and b/src/Umbraco.Web.UI.Client/src/assets/img/application/logo.png differ diff --git a/src/Umbraco.Web.UI.Client/src/assets/img/application/logo@2x.png b/src/Umbraco.Web.UI.Client/src/assets/img/application/logo@2x.png new file mode 100644 index 0000000000..3e7257a41a Binary files /dev/null and b/src/Umbraco.Web.UI.Client/src/assets/img/application/logo@2x.png differ diff --git a/src/Umbraco.Web.UI.Client/src/assets/img/application/logo@3x.png b/src/Umbraco.Web.UI.Client/src/assets/img/application/logo@3x.png new file mode 100644 index 0000000000..d4bc08f1bc Binary files /dev/null and b/src/Umbraco.Web.UI.Client/src/assets/img/application/logo@3x.png differ diff --git a/src/Umbraco.Web.UI.Client/src/assets/img/installer.jpg b/src/Umbraco.Web.UI.Client/src/assets/img/installer.jpg index d460bf4cd3..e8bc1f8300 100644 Binary files a/src/Umbraco.Web.UI.Client/src/assets/img/installer.jpg and b/src/Umbraco.Web.UI.Client/src/assets/img/installer.jpg differ diff --git a/src/Umbraco.Web.UI.Client/src/assets/img/transparent.png b/src/Umbraco.Web.UI.Client/src/assets/img/transparent.png new file mode 100644 index 0000000000..ec8b019cdb Binary files /dev/null and b/src/Umbraco.Web.UI.Client/src/assets/img/transparent.png differ diff --git a/src/Umbraco.Web.UI.Client/src/assets/img/uploader/upload-illustration.png b/src/Umbraco.Web.UI.Client/src/assets/img/uploader/upload-illustration.png new file mode 100644 index 0000000000..0a6f8d17bc Binary files /dev/null and b/src/Umbraco.Web.UI.Client/src/assets/img/uploader/upload-illustration.png differ diff --git a/src/Umbraco.Web.UI.Client/src/assets/img/uploader/upload-illustration.svg b/src/Umbraco.Web.UI.Client/src/assets/img/uploader/upload-illustration.svg new file mode 100644 index 0000000000..30bce45e8b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/img/uploader/upload-illustration.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/Umbraco.Web.UI.Client/src/canvasdesigner.loader.js b/src/Umbraco.Web.UI.Client/src/canvasdesigner.loader.js index 3e4dfe7f54..c7cc3ff6ed 100644 --- a/src/Umbraco.Web.UI.Client/src/canvasdesigner.loader.js +++ b/src/Umbraco.Web.UI.Client/src/canvasdesigner.loader.js @@ -1,19 +1,22 @@ + LazyLoad.js([ - '/Umbraco/lib/jquery/jquery.min.js', - '/Umbraco/lib/jquery-ui/jquery-ui.min.js', - '/Umbraco/lib/angular/1.1.5/angular.min.js', - '/Umbraco/lib/underscore/underscore-min.js', - '/Umbraco/js/app.js', - '/Umbraco/js/umbraco.resources.js', - '/Umbraco/js/umbraco.services.js', - '/Umbraco/js/umbraco.security.js', - '/Umbraco/ServerVariables', - '/Umbraco/lib/spectrum/spectrum.js', - 'http://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js', - '/umbraco/js/canvasdesigner.panel.js', + '../lib/jquery/jquery.min.js', + '../lib/jquery-ui/jquery-ui.min.js', + '../lib/angular/1.1.5/angular.min.js', + '../lib/underscore/underscore-min.js', + '../lib/umbraco/Extensions.js', + '../js/app.js', + '../js/umbraco.resources.js', + '../js/umbraco.services.js', + '../js/umbraco.security.js', + '../ServerVariables', + '../lib/spectrum/spectrum.js', + + '../js/canvasdesigner.panel.js', ], function () { jQuery(document).ready(function () { angular.bootstrap(document, ['Umbraco.canvasdesigner']); }); -} -); +}); + +LazyLoad.js(['https://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js']); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/canvasdesigner/canvasdesigner.controller.js b/src/Umbraco.Web.UI.Client/src/canvasdesigner/canvasdesigner.controller.js index 127d5882dc..ceee2a2a9e 100644 --- a/src/Umbraco.Web.UI.Client/src/canvasdesigner/canvasdesigner.controller.js +++ b/src/Umbraco.Web.UI.Client/src/canvasdesigner/canvasdesigner.controller.js @@ -24,7 +24,7 @@ var app = angular.module("Umbraco.canvasdesigner", ['colorpicker', 'ui.slider', ]; $scope.previewDevice = $scope.devices[0]; - var apiController = "/Umbraco/Api/Canvasdesigner/"; + var apiController = "../Api/Canvasdesigner/"; /*****************************************************************************/ /* Preview devices */ @@ -35,6 +35,14 @@ var app = angular.module("Umbraco.canvasdesigner", ['colorpicker', 'ui.slider', $scope.previewDevice = device; }; + /*****************************************************************************/ + /* Exit Preview */ + /*****************************************************************************/ + + $scope.exitPreview = function () { + window.top.location.href = "../endPreview.aspx?redir=%2f" + $scope.pageId; + }; + /*****************************************************************************/ /* UI designer managment */ /*****************************************************************************/ @@ -180,7 +188,7 @@ var app = angular.module("Umbraco.canvasdesigner", ['colorpicker', 'ui.slider', // TODO: special init for font family picker if (item.type == "googlefontpicker" && item.values.fontFamily) { var variant = item.values.fontWeight != "" || item.values.fontStyle != "" ? ":" + item.values.fontWeight + item.values.fontStyle : ""; - var gimport = "@import url('http://fonts.googleapis.com/css?family=" + item.values.fontFamily + variant + "');"; + var gimport = "@import url('https://fonts.googleapis.com/css?family=" + item.values.fontFamily + variant + "');"; if ($.inArray(gimport, parameters) < 0) { parameters.splice(0, 0, gimport); } @@ -412,7 +420,7 @@ var app = angular.module("Umbraco.canvasdesigner", ['colorpicker', 'ui.slider', var webFontScriptLoaded = false; var loadGoogleFont = function (font) { if (!webFontScriptLoaded) { - $.getScript('http://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js') + $.getScript('https://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js') .done(function () { webFontScriptLoaded = true; // Recursively call once webfont script is available. diff --git a/src/Umbraco.Web.UI.Client/src/canvasdesigner/canvasdesigner.front.js b/src/Umbraco.Web.UI.Client/src/canvasdesigner/canvasdesigner.front.js index 38a0878ca8..042d3ed89a 100644 --- a/src/Umbraco.Web.UI.Client/src/canvasdesigner/canvasdesigner.front.js +++ b/src/Umbraco.Web.UI.Client/src/canvasdesigner/canvasdesigner.front.js @@ -19,7 +19,7 @@ var refreshLayout = function (parameters) { var webFontScriptLoaded = false; var getFont = function (font) { if (!webFontScriptLoaded) { - $.getScript('http://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js') + $.getScript('https://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js') .done(function () { webFontScriptLoaded = true; // Recursively call once webfont script is available. diff --git a/src/Umbraco.Web.UI.Client/src/canvasdesigner/config/canvasdesigner.config.js b/src/Umbraco.Web.UI.Client/src/canvasdesigner/config/canvasdesigner.config.js index 699cd353a7..54a27cc415 100644 --- a/src/Umbraco.Web.UI.Client/src/canvasdesigner/config/canvasdesigner.config.js +++ b/src/Umbraco.Web.UI.Client/src/canvasdesigner/config/canvasdesigner.config.js @@ -9,11 +9,6 @@ var canvasdesignerConfig = { schema: "body", selector: "body", editors: [ - { - type: "wide", - category: "Dimension", - name: "Layout" - }, { type: "background", category: "Color", diff --git a/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/googlefontpicker.js b/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/googlefontpicker.js index b32311ee7c..ce6bf2c9f7 100644 --- a/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/googlefontpicker.js +++ b/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/googlefontpicker.js @@ -104,6 +104,30 @@ angular.module("Umbraco.canvasdesigner") } }; + function loadFont(font, variant) { + WebFont.load({ + google: { + families: [font.fontFamily + ":" + variant] + }, + loading: function () { + console.log('loading'); + }, + active: function () { + $scope.selectedFont = font; + $scope.selectedFont.fontWeight = googleGetWeight(variant); + $scope.selectedFont.fontStyle = googleGetStyle(variant); + // If $apply isn't called, the new font family isn't applied until the next user click. + $scope.change({ + fontFamily: $scope.selectedFont.fontFamily, + fontType: $scope.selectedFont.fontType, + fontWeight: $scope.selectedFont.fontWeight, + fontStyle: $scope.selectedFont.fontStyle, + }); + } + }); + } + + var webFontScriptLoaded = false; $scope.showFontPreview = function (font, variant) { if (!variant) @@ -114,27 +138,19 @@ angular.module("Umbraco.canvasdesigner") // Font needs to be independently loaded in the iframe for live preview to work. document.getElementById("resultFrame").contentWindow.getFont(font.fontFamily + ":" + variant); - WebFont.load({ - google: { - families: [font.fontFamily + ":" + variant] - }, - loading: function () { - console.log('loading'); - }, - active: function () { - $scope.selectedFont = font; - $scope.selectedFont.fontWeight = googleGetWeight(variant); - $scope.selectedFont.fontStyle = googleGetStyle(variant); - // If $apply isn't called, the new font family isn't applied until the next user click. - $scope.change({ - fontFamily: $scope.selectedFont.fontFamily, - fontType: $scope.selectedFont.fontType, - fontWeight: $scope.selectedFont.fontWeight, - fontStyle: $scope.selectedFont.fontStyle, + if (!webFontScriptLoaded) { + $.getScript('https://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js') + .done(function() { + webFontScriptLoaded = true; + loadFont(font, variant); + }) + .fail(function() { + console.log('error loading webfont'); }); - } - }); - + } + else { + loadFont(font, variant); + } } else { diff --git a/src/Umbraco.Web.UI.Client/src/canvasdesigner/index.html b/src/Umbraco.Web.UI.Client/src/canvasdesigner/index.html index 3db48d1940..ab7a5b0fdb 100644 --- a/src/Umbraco.Web.UI.Client/src/canvasdesigner/index.html +++ b/src/Umbraco.Web.UI.Client/src/canvasdesigner/index.html @@ -2,9 +2,9 @@ Umbraco Canvas Designer - - - + + + @@ -20,7 +20,7 @@
- +
    @@ -34,6 +34,9 @@ +
  • + +
    @@ -122,7 +125,7 @@
    {{item.name}}
    -
    +
@@ -151,8 +154,8 @@

Styles saved and published

- - + + diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/_obsolete/_readme.md b/src/Umbraco.Web.UI.Client/src/common/directives/_obsolete/_readme.md new file mode 100644 index 0000000000..8feb0377db --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/_obsolete/_readme.md @@ -0,0 +1,3 @@ +#Obsolete directives + +Folder contains directives we plan to remove in the next major version of umbraco (8.0) these are not recommended to use. diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/util/autoscale.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/_obsolete/autoscale.directive.js similarity index 100% rename from src/Umbraco.Web.UI.Client/src/common/directives/util/autoscale.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/_obsolete/autoscale.directive.js diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/_obsolete/detectfold.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/_obsolete/detectfold.directive.js new file mode 100644 index 0000000000..f890ee4989 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/_obsolete/detectfold.directive.js @@ -0,0 +1,80 @@ +/** +* @ngdoc directive +* @name umbraco.directives.directive:umbPanel +* @description This is used for the editor buttons to ensure they are displayed correctly if the horizontal overflow of the editor + * exceeds the height of the window +**/ +angular.module("umbraco.directives.html") + .directive('detectFold', function ($timeout, $log, windowResizeListener) { + return { + require: "^?umbTabs", + restrict: 'A', + link: function (scope, el, attrs, tabsCtrl) { + + var firstRun = false; + var parent = $(".umb-panel-body"); + var winHeight = $(window).height(); + var calculate = function () { + if (el && el.is(":visible") && !el.hasClass("umb-bottom-bar")) { + + //now that the element is visible, set the flag in a couple of seconds, + // this will ensure that loading time of a current tab get's completed and that + // we eventually stop watching to save on CPU time + $timeout(function() { + firstRun = true; + }, 4000); + + //var parent = el.parent(); + var hasOverflow = parent.innerHeight() < parent[0].scrollHeight; + //var belowFold = (el.offset().top + el.height()) > winHeight; + if (hasOverflow) { + el.addClass("umb-bottom-bar"); + + //I wish we didn't have to put this logic here but unfortunately we + // do. This needs to calculate the left offest to place the bottom bar + // depending on if the left column splitter has been moved by the user + // (based on the nav-resize directive) + var wrapper = $("#mainwrapper"); + var contentPanel = $("#leftcolumn").next(); + var contentPanelLeftPx = contentPanel.css("left"); + + el.css({ left: contentPanelLeftPx }); + } + } + return firstRun; + }; + + var resizeCallback = function(size) { + winHeight = size.height; + el.removeClass("umb-bottom-bar"); + calculate(); + }; + + windowResizeListener.register(resizeCallback); + + //Only execute the watcher if this tab is the active (first) tab on load, otherwise there's no reason to execute + // the watcher since it will be recalculated when the tab changes! + if (el.closest(".umb-tab-pane").index() === 0) { + //run a watcher to ensure that the calculation occurs until it's firstRun but ensure + // the calculations are throttled to save a bit of CPU + var listener = scope.$watch(_.throttle(calculate, 1000), function (newVal, oldVal) { + if (newVal !== oldVal) { + listener(); + } + }); + } + + //listen for tab changes + if (tabsCtrl != null) { + tabsCtrl.onTabShown(function (args) { + calculate(); + }); + } + + //ensure to unregister + scope.$on('$destroy', function() { + windowResizeListener.unregister(resizeCallback); + }); + } + }; + }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/umbItemSorter.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/_obsolete/umbItemSorter.directive.js similarity index 94% rename from src/Umbraco.Web.UI.Client/src/common/directives/umbItemSorter.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/_obsolete/umbItemSorter.directive.js index 8c6d47628c..2be9e08376 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/umbItemSorter.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/_obsolete/umbItemSorter.directive.js @@ -13,7 +13,7 @@ function umbItemSorter(angularHelper) { }, restrict: "E", // restrict to an element replace: true, // replace the html element with the template - templateUrl: 'views/directives/umb-item-sorter.html', + templateUrl: 'views/directives/_obsolete/umb-item-sorter.html', link: function(scope, element, attrs, ctrl) { var defaultModel = { okButton: "Ok", @@ -38,7 +38,7 @@ function umbItemSorter(angularHelper) { scope.handleOk = function() { scope.$emit("umbItemSorter.ok"); }; - + //defines the options for the jquery sortable scope.sortableOptions = { axis: 'y', diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/editors/umbcontentname.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/_obsolete/umbcontentname.directive.js similarity index 86% rename from src/Umbraco.Web.UI.Client/src/common/directives/editors/umbcontentname.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/_obsolete/umbcontentname.directive.js index 16b0ab9d23..a99e3bd378 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/editors/umbcontentname.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/_obsolete/umbcontentname.directive.js @@ -1,9 +1,9 @@ /** * @ngdoc directive -* @name umbraco.directives.directive:umbContentName +* @name umbraco.directives.directive:umbContentName * @restrict E * @function -* @description +* @description * Used by editors that require naming an entity. Shows a textbox/headline with a required validator within it's own form. **/ angular.module("umbraco.directives") @@ -12,7 +12,8 @@ angular.module("umbraco.directives") require: "ngModel", restrict: 'E', replace: true, - templateUrl: 'views/directives/umb-content-name.html', + templateUrl: 'views/directives/_obsolete/umb-content-name.html', + scope: { placeholder: '@placeholder', model: '=ngModel', @@ -24,10 +25,10 @@ angular.module("umbraco.directives") if(scope.placeholder && scope.placeholder[0] === "@"){ localizationService.localize(scope.placeholder.substring(1)) .then(function(value){ - scope.placeholder = value; + scope.placeholder = value; }); } - + var mX, mY, distance; function calculateDistance(elem, mouseX, mouseY) { @@ -64,18 +65,18 @@ angular.module("umbraco.directives") scope.goEdit(); } }, 100, false); - + scope.goEdit = function(){ scope.editMode = true; - $timeout(function () { - inputElement.focus(); + $timeout(function () { + inputElement.focus(); }, 100, false); }; scope.exitEdit = function(){ if(scope.model && scope.model !== ""){ - scope.editMode = false; + scope.editMode = false; } }; @@ -85,4 +86,4 @@ angular.module("umbraco.directives") }); } }; - }); \ No newline at end of file + }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/_obsolete/umbheader.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/_obsolete/umbheader.directive.js new file mode 100644 index 0000000000..5056d298dd --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/_obsolete/umbheader.directive.js @@ -0,0 +1,61 @@ +/** +* @ngdoc directive +* @name umbraco.directives.directive:umbHeader +* @restrict E +* @function +* @description +* The header on an editor that contains tabs using bootstrap tabs - THIS IS OBSOLETE, use umbTabHeader instead +**/ +angular.module("umbraco.directives") +.directive('umbHeader', function ($parse, $timeout) { + return { + restrict: 'E', + replace: true, + transclude: 'true', + templateUrl: 'views/directives/_obsolete/umb-header.html', + //create a new isolated scope assigning a tabs property from the attribute 'tabs' + //which is bound to the parent scope property passed in + scope: { + tabs: "=" + }, + link: function (scope, iElement, iAttrs) { + + scope.showTabs = iAttrs.tabs ? true : false; + scope.visibleTabs = []; + + //since tabs are loaded async, we need to put a watch on them to determine + // when they are loaded, then we can close the watch + var tabWatch = scope.$watch("tabs", function (newValue, oldValue) { + + angular.forEach(newValue, function(val, index){ + var tab = {id: val.id, label: val.label}; + scope.visibleTabs.push(tab); + }); + + //don't process if we cannot or have already done so + if (!newValue) {return;} + if (!newValue.length || newValue.length === 0){return;} + + //we need to do a timeout here so that the current sync operation can complete + // and update the UI, then this will fire and the UI elements will be available. + $timeout(function () { + + //use bootstrap tabs API to show the first one + iElement.find(".nav-tabs a:first").tab('show'); + + //enable the tab drop + iElement.find('.nav-pills, .nav-tabs').tabdrop(); + + //ensure to destroy tabdrop (unbinds window resize listeners) + scope.$on('$destroy', function () { + iElement.find('.nav-pills, .nav-tabs').tabdrop("destroy"); + }); + + //stop watching now + tabWatch(); + }, 200); + + }); + } + }; +}); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/umblogin.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/_obsolete/umblogin.directive.js similarity index 81% rename from src/Umbraco.Web.UI.Client/src/common/directives/umblogin.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/_obsolete/umblogin.directive.js index 176ccd6c04..fbea4cd60d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/umblogin.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/_obsolete/umblogin.directive.js @@ -9,7 +9,7 @@ function loginDirective() { return { restrict: "E", // restrict to an element replace: true, // replace the html element with the template - templateUrl: 'views/directives/umb-login.html' + templateUrl: 'views/directives/_obsolete/umb-login.html' }; } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/umboptionsmenu.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/_obsolete/umboptionsmenu.directive.js similarity index 94% rename from src/Umbraco.Web.UI.Client/src/common/directives/umboptionsmenu.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/_obsolete/umboptionsmenu.directive.js index 618f19b50e..4f5f241537 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/umboptionsmenu.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/_obsolete/umboptionsmenu.directive.js @@ -7,7 +7,7 @@ angular.module("umbraco.directives") }, restrict: 'E', replace: true, - templateUrl: 'views/directives/umb-optionsmenu.html', + templateUrl: 'views/directives/_obsolete/umb-optionsmenu.html', link: function (scope, element, attrs, ctrl) { //adds a handler to the context menu item click, we need to handle this differently @@ -22,7 +22,7 @@ angular.module("umbraco.directives") if (!scope.currentNode) { return; } - + //when the options item is selected, we need to set the current menu item in appState (since this is synonymous with a menu) appState.setMenuState("currentNode", scope.currentNode); @@ -36,4 +36,4 @@ angular.module("umbraco.directives") } }; -}); \ No newline at end of file +}); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/html/umbphotofolder.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/_obsolete/umbphotofolder.directive.js similarity index 93% rename from src/Umbraco.Web.UI.Client/src/common/directives/html/umbphotofolder.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/_obsolete/umbphotofolder.directive.js index 84f4f2b974..c60e682ee1 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/html/umbphotofolder.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/_obsolete/umbphotofolder.directive.js @@ -5,13 +5,13 @@ **/ angular.module("umbraco.directives.html") .directive('umbPhotoFolder', function($compile, $log, $timeout, $filter, umbPhotoFolderHelper) { - + return { restrict: 'E', replace: true, require: '?ngModel', terminate: true, - templateUrl: 'views/directives/html/umb-photo-folder.html', + templateUrl: 'views/directives/_obsolete/umb-photo-folder.html', link: function(scope, element, attrs, ngModel) { var lastWatch = null; @@ -24,10 +24,10 @@ angular.module("umbraco.directives.html") scope.clickHandler = scope.$eval(element.attr('on-click')); - + var imagesOnly = element.attr('images-only') === "true"; - - + + var margin = element.attr('border') ? parseInt(element.attr('border'), 10) : 5; var startingIndex = element.attr('baseline') ? parseInt(element.attr('baseline'), 10) : 0; var minWidth = element.attr('min-width') ? parseInt(element.attr('min-width'), 10) : 420; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/editors/umbsort.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/_obsolete/umbsort.directive.js similarity index 100% rename from src/Umbraco.Web.UI.Client/src/common/directives/editors/umbsort.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/_obsolete/umbsort.directive.js diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/html/umbtabview.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/_obsolete/umbtabview.directive.js similarity index 63% rename from src/Umbraco.Web.UI.Client/src/common/directives/html/umbtabview.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/_obsolete/umbtabview.directive.js index 7bb4f1cb18..75d1c91681 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/html/umbtabview.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/_obsolete/umbtabview.directive.js @@ -1,6 +1,6 @@ /** * @ngdoc directive -* @name umbraco.directives.directive:umbTabView +* @name umbraco.directives.directive:umbTabView * @restrict E **/ angular.module("umbraco.directives") @@ -9,6 +9,6 @@ angular.module("umbraco.directives") restrict: 'E', replace: true, transclude: 'true', - templateUrl: 'views/directives/umb-tab-view.html' + templateUrl: 'views/directives/_obsolete/umb-tab-view.html' }; -}); \ No newline at end of file +}); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/html/umbuploaddropzone.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/_obsolete/umbuploaddropzone.directive.js similarity index 75% rename from src/Umbraco.Web.UI.Client/src/common/directives/html/umbuploaddropzone.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/_obsolete/umbuploaddropzone.directive.js index 04e305a7a7..4c53732805 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/html/umbuploaddropzone.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/_obsolete/umbuploaddropzone.directive.js @@ -8,6 +8,6 @@ angular.module("umbraco.directives.html") return { restrict: 'E', replace: true, - templateUrl: 'views/directives/html/umb-upload-dropzone.html' + templateUrl: 'views/directives/_obsolete/umb-upload-dropzone.html' }; - }); \ No newline at end of file + }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/buttongroup.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/buttongroup.directive.js deleted file mode 100644 index 903970a6ff..0000000000 --- a/src/Umbraco.Web.UI.Client/src/common/directives/buttongroup.directive.js +++ /dev/null @@ -1,49 +0,0 @@ -/** -* @ngdoc directive -* @name umbraco.directives.directive:umbProperty -* @restrict E -**/ -angular.module("umbraco.directives") - .directive('buttonGroup', function (contentEditingHelper) { - return { - scope: { - actions: "=", - handler: "=" - }, - transclude: true, - restrict: 'E', - replace: true, - templateUrl: 'views/directives/button-group.html', - link: function (scope, element, attrs, ctrl) { - - scope.buttons = []; - - scope.handle = function(action){ - if(scope.handler){ - - } - }; - function processActions() { - var buttons = []; - - angular.forEach(scope.actions, function(action){ - if(angular.isObject(action)){ - buttons.push(action); - }else{ - var btn = contentEditingHelper.getButtonFromAction(action); - if(btn){ - buttons.push(btn); - } - } - }); - - scope.defaultButton = buttons.pop(0); - scope.buttons = buttons; - } - - scope.$watchCollection(scope.actions, function(){ - processActions(); - }); - } - }; - }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/_readme.md b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/_readme.md new file mode 100644 index 0000000000..7ceb11d9a2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/_readme.md @@ -0,0 +1,3 @@ +#Application + +Directives used for the main application window. Navigation, sections etc. diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/navresize.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/navresize.directive.js new file mode 100644 index 0000000000..8addd6faf6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/navresize.directive.js @@ -0,0 +1,111 @@ +/** +* @ngdoc directive +* @name umbraco.directives.directive:navResize +* @restrict A + * + * @description + * Handles how the navigation responds to window resizing and controls how the draggable resize panel works +**/ +angular.module("umbraco.directives") + .directive('navResize', function (appState, eventsService, windowResizeListener) { + return { + restrict: 'A', + link: function (scope, element, attrs, ctrl) { + + var minScreenSize = 1100; + var resizeEnabled = false; + + function setTreeMode() { + appState.setGlobalState("showNavigation", appState.getGlobalState("isTablet") === false); + } + + function enableResize() { + //only enable when the size is correct and it's not already enabled + if (!resizeEnabled && appState.getGlobalState("isTablet") === false) { + element.resizable( + { + containment: $("#mainwrapper"), + autoHide: true, + handles: "e", + alsoResize: ".navigation-inner-container", + resize: function(e, ui) { + var wrapper = $("#mainwrapper"); + var contentPanel = $("#contentwrapper"); + var umbNotification = $("#umb-notifications-wrapper"); + var apps = $("#applications"); + var bottomBar = contentPanel.find(".umb-bottom-bar"); + var navOffeset = $("#navOffset"); + + var leftPanelWidth = ui.element.width() + apps.width(); + + contentPanel.css({ left: leftPanelWidth }); + bottomBar.css({ left: leftPanelWidth }); + umbNotification.css({ left: leftPanelWidth }); + + navOffeset.css({ "margin-left": ui.element.outerWidth() }); + }, + stop: function (e, ui) { + + } + }); + + resizeEnabled = true; + } + } + + function resetResize() { + if (resizeEnabled) { + //kill the resize + element.resizable("destroy"); + element.css("width", ""); + + var navInnerContainer = element.find(".navigation-inner-container"); + + navInnerContainer.css("width", ""); + $("#contentwrapper").css("left", ""); + $("#umb-notifications-wrapper").css("left", ""); + $("#navOffset").css("margin-left", ""); + + resizeEnabled = false; + } + } + + var evts = []; + + //Listen for global state changes + evts.push(eventsService.on("appState.globalState.changed", function (e, args) { + if (args.key === "showNavigation") { + if (args.value === false) { + resetResize(); + } + else { + enableResize(); + } + } + })); + + var resizeCallback = function(size) { + //set the global app state + appState.setGlobalState("isTablet", (size.width <= minScreenSize)); + setTreeMode(); + }; + + windowResizeListener.register(resizeCallback); + + //ensure to unregister from all events and kill jquery plugins + scope.$on('$destroy', function () { + windowResizeListener.unregister(resizeCallback); + for (var e in evts) { + eventsService.unsubscribe(evts[e]); + } + var navInnerContainer = element.find(".navigation-inner-container"); + navInnerContainer.resizable("destroy"); + }); + + //init + //set the global app state + appState.setGlobalState("isTablet", ($(window).width() <= minScreenSize)); + setTreeMode(); + } + }; + }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/html/sectionicon.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/sectionicon.directive.js similarity index 100% rename from src/Umbraco.Web.UI.Client/src/common/directives/html/sectionicon.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/components/application/sectionicon.directive.js diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/umbcontextmenu.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbcontextmenu.directive.js similarity index 87% rename from src/Umbraco.Web.UI.Client/src/common/directives/umbcontextmenu.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbcontextmenu.directive.js index d0ca9201dd..a21c9e4cf1 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/umbcontextmenu.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbcontextmenu.directive.js @@ -9,9 +9,9 @@ angular.module("umbraco.directives") }, restrict: 'E', replace: true, - templateUrl: 'views/directives/umb-contextmenu.html', + templateUrl: 'views/components/application/umb-contextmenu.html', link: function (scope, element, attrs, ctrl) { - + //adds a handler to the context menu item click, we need to handle this differently //depending on what the menu item is supposed to do. scope.executeMenuItem = function (action) { @@ -19,4 +19,4 @@ angular.module("umbraco.directives") }; } }; -}); \ No newline at end of file +}); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/umbnavigation.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbnavigation.directive.js similarity index 75% rename from src/Umbraco.Web.UI.Client/src/common/directives/umbnavigation.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbnavigation.directive.js index dfee6c1daf..baf0f59643 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/umbnavigation.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbnavigation.directive.js @@ -7,8 +7,8 @@ function umbNavigationDirective() { return { restrict: "E", // restrict to an element replace: true, // replace the html element with the template - templateUrl: 'views/directives/umb-navigation.html' + templateUrl: 'views/components/application/umb-navigation.html' }; } -angular.module('umbraco.directives').directive("umbNavigation", umbNavigationDirective); \ No newline at end of file +angular.module('umbraco.directives').directive("umbNavigation", umbNavigationDirective); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/umbsections.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsections.directive.js similarity index 70% rename from src/Umbraco.Web.UI.Client/src/common/directives/umbsections.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsections.directive.js index dffa973f77..31b7cb9506 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/umbsections.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsections.directive.js @@ -7,9 +7,9 @@ function sectionsDirective($timeout, $window, navigationService, treeService, se return { restrict: "E", // restrict to an element replace: true, // replace the html element with the template - templateUrl: 'views/directives/umb-sections.html', + templateUrl: 'views/components/application/umb-sections.html', link: function (scope, element, attr, ctrl) { - + //setup scope vars scope.maxSections = 7; scope.overflowingSections = 0; @@ -18,7 +18,7 @@ function sectionsDirective($timeout, $window, navigationService, treeService, se scope.showTray = false; //appState.getGlobalState("showTray"); scope.stickyNavigation = appState.getGlobalState("stickyNavigation"); scope.needTray = false; - scope.trayAnimation = function() { + scope.trayAnimation = function() { if (scope.showTray) { return 'slide'; } @@ -85,23 +85,65 @@ function sectionsDirective($timeout, $window, navigationService, treeService, se //on page resize window.onresize = calculateHeight; - + scope.avatarClick = function(){ - navigationService.showUserDialog(); + + if(!scope.userDialog) { + + scope.userDialog = {}; + scope.userDialog.view = "user"; + scope.userDialog.show = true; + + scope.userDialog.close = function(oldModel) { + scope.userDialog.show = false; + scope.userDialog = null; + }; + + } else { + scope.userDialog.show = false; + scope.userDialog = null; + } + }; scope.helpClick = function(){ - navigationService.showHelpDialog(); + + if(!scope.helpDialog) { + + scope.helpDialog = {}; + scope.helpDialog.show = true; + scope.helpDialog.view = "help"; + + scope.helpDialog.close = function(oldModel) { + scope.helpDialog.show = false; + scope.helpDialog = null; + }; + + } else { + scope.helpDialog.show = false; + scope.helpDialog = null; + } + }; - scope.sectionClick = function (section) { + scope.sectionClick = function (event, section) { + + if (event.ctrlKey || + event.shiftKey || + event.metaKey || // apple + (event.button && event.button === 1) // middle click, >IE9 + everyone else + ) { + return; + } + + if (navigationService.userDialog) { navigationService.userDialog.close(); } if (navigationService.helpDialog) { navigationService.helpDialog.close(); } - + navigationService.hideSearch(); navigationService.showTree(section.alias); $location.path("/" + section.alias); @@ -126,7 +168,7 @@ function sectionsDirective($timeout, $window, navigationService, treeService, se navigationService.showTray(); } }; - + loadSections(); } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbbutton.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbbutton.directive.js new file mode 100644 index 0000000000..1bb70e2b6c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbbutton.directive.js @@ -0,0 +1,66 @@ +(function() { + 'use strict'; + + function ButtonDirective($timeout) { + + function link(scope, el, attr, ctrl) { + + scope.style = null; + + function activate() { + + if (!scope.state) { + scope.state = "init"; + } + + if (scope.buttonStyle) { + scope.style = "btn-" + scope.buttonStyle; + } + + } + + activate(); + + var unbindStateWatcher = scope.$watch('state', function(newValue, oldValue) { + + if (newValue === 'success' || newValue === 'error') { + $timeout(function() { + scope.state = 'init'; + }, 2000); + } + + }); + + scope.$on('$destroy', function() { + unbindStateWatcher(); + }); + + } + + var directive = { + transclude: true, + restrict: 'E', + replace: true, + templateUrl: 'views/components/buttons/umb-button.html', + link: link, + scope: { + action: "&?", + href: "@?", + type: "@", + buttonStyle: "@?", + state: "=?", + shortcut: "@?", + label: "@?", + labelKey: "@?", + icon: "@?", + disabled: "=" + } + }; + + return directive; + + } + + angular.module('umbraco.directives').directive('umbButton', ButtonDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbbuttongroup.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbbuttongroup.directive.js new file mode 100644 index 0000000000..4360cc6995 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbbuttongroup.directive.js @@ -0,0 +1,24 @@ +(function() { + 'use strict'; + + function ButtonGroupDirective() { + + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/buttons/umb-button-group.html', + scope: { + defaultButton: "=", + subButtons: "=", + state: "=?", + direction: "@?", + float: "@?" + } + }; + + return directive; + } + + angular.module('umbraco.directives').directive('umbButtonGroup', ButtonGroupDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/_readme.md b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/_readme.md new file mode 100644 index 0000000000..0955f9ca71 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/_readme.md @@ -0,0 +1,3 @@ +#Editor + +Directives only used to construct the main editor window. Includes header, footer, menu and navigation. diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/subheader/umbeditorsubheader.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/subheader/umbeditorsubheader.directive.js new file mode 100644 index 0000000000..b03d289a44 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/subheader/umbeditorsubheader.directive.js @@ -0,0 +1,18 @@ +(function() { + 'use strict'; + + function EditorSubHeaderDirective() { + + var directive = { + transclude: true, + restrict: 'E', + replace: true, + templateUrl: 'views/components/editor/subheader/umb-editor-sub-header.html' + }; + + return directive; + } + + angular.module('umbraco.directives').directive('umbEditorSubHeader', EditorSubHeaderDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/subheader/umbeditorsubheadercontentleft.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/subheader/umbeditorsubheadercontentleft.directive.js new file mode 100644 index 0000000000..d1c5bca923 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/subheader/umbeditorsubheadercontentleft.directive.js @@ -0,0 +1,18 @@ +(function() { + 'use strict'; + + function EditorSubHeaderContentLeftDirective() { + + var directive = { + transclude: true, + restrict: 'E', + replace: true, + templateUrl: 'views/components/editor/subheader/umb-editor-sub-header-content-left.html' + }; + + return directive; + } + + angular.module('umbraco.directives').directive('umbEditorSubHeaderContentLeft', EditorSubHeaderContentLeftDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/subheader/umbeditorsubheadercontentright.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/subheader/umbeditorsubheadercontentright.directive.js new file mode 100644 index 0000000000..819d7e30a6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/subheader/umbeditorsubheadercontentright.directive.js @@ -0,0 +1,18 @@ +(function() { + 'use strict'; + + function EditorSubHeaderContentRightDirective() { + + var directive = { + transclude: true, + restrict: 'E', + replace: true, + templateUrl: 'views/components/editor/subheader/umb-editor-sub-header-content-right.html' + }; + + return directive; + } + + angular.module('umbraco.directives').directive('umbEditorSubHeaderContentRight', EditorSubHeaderContentRightDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/subheader/umbeditorsubheadersection.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/subheader/umbeditorsubheadersection.directive.js new file mode 100644 index 0000000000..4ea2de83f1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/subheader/umbeditorsubheadersection.directive.js @@ -0,0 +1,18 @@ +(function() { + 'use strict'; + + function EditorSubHeaderSectionDirective() { + + var directive = { + transclude: true, + restrict: 'E', + replace: true, + templateUrl: 'views/components/editor/subheader/umb-editor-sub-header-section.html' + }; + + return directive; + } + + angular.module('umbraco.directives').directive('umbEditorSubHeaderSection', EditorSubHeaderSectionDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbbreadcrumbs.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbbreadcrumbs.directive.js new file mode 100644 index 0000000000..8ce56b84e2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbbreadcrumbs.directive.js @@ -0,0 +1,22 @@ +(function() { + 'use strict'; + + function BreadcrumbsDirective() { + + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/editor/umb-breadcrumbs.html', + scope: { + ancestors: "=", + entityType: "@" + } + }; + + return directive; + + } + + angular.module('umbraco.directives').directive('umbBreadcrumbs', BreadcrumbsDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorcontainer.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorcontainer.directive.js new file mode 100644 index 0000000000..33f7473680 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorcontainer.directive.js @@ -0,0 +1,31 @@ +(function() { + 'use strict'; + + function EditorContainerDirective(overlayHelper) { + + function link(scope, el, attr, ctrl) { + + scope.numberOfOverlays = 0; + + scope.$watch(function(){ + return overlayHelper.getNumberOfOverlays(); + }, function (newValue) { + scope.numberOfOverlays = newValue; + }); + + } + + var directive = { + transclude: true, + restrict: 'E', + replace: true, + templateUrl: 'views/components/editor/umb-editor-container.html', + link: link + }; + + return directive; + } + + angular.module('umbraco.directives').directive('umbEditorContainer', EditorContainerDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorfooter.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorfooter.directive.js new file mode 100644 index 0000000000..ae5cf211de --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorfooter.directive.js @@ -0,0 +1,18 @@ +(function() { + 'use strict'; + + function EditorFooterDirective() { + + var directive = { + transclude: true, + restrict: 'E', + replace: true, + templateUrl: 'views/components/editor/umb-editor-footer.html' + }; + + return directive; + } + + angular.module('umbraco.directives').directive('umbEditorFooter', EditorFooterDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorfootercontentleft.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorfootercontentleft.directive.js new file mode 100644 index 0000000000..74a8cdf0f9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorfootercontentleft.directive.js @@ -0,0 +1,18 @@ +(function() { + 'use strict'; + + function EditorFooterContentLeftDirective() { + + var directive = { + transclude: true, + restrict: 'E', + replace: true, + templateUrl: 'views/components/editor/umb-editor-footer-content-left.html' + }; + + return directive; + } + + angular.module('umbraco.directives').directive('umbEditorFooterContentLeft', EditorFooterContentLeftDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorfootercontentright.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorfootercontentright.directive.js new file mode 100644 index 0000000000..42f7ac3cd3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorfootercontentright.directive.js @@ -0,0 +1,18 @@ +(function() { + 'use strict'; + + function EditorFooterContentRightDirective() { + + var directive = { + transclude: true, + restrict: 'E', + replace: true, + templateUrl: 'views/components/editor/umb-editor-footer-content-right.html' + }; + + return directive; + } + + angular.module('umbraco.directives').directive('umbEditorFooterContentRight', EditorFooterContentRightDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorheader.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorheader.directive.js new file mode 100644 index 0000000000..bf87d764a6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorheader.directive.js @@ -0,0 +1,52 @@ +(function() { + 'use strict'; + + function EditorHeaderDirective(iconHelper) { + + function link(scope, el, attr, ctrl) { + + scope.openIconPicker = function() { + scope.dialogModel = { + view: "iconpicker", + show: true, + submit: function(model) { + if (model.color) { + scope.icon = model.icon + " " + model.color; + } else { + scope.icon = model.icon; + } + scope.dialogModel.show = false; + scope.dialogModel = null; + } + }; + }; + } + + var directive = { + transclude: true, + restrict: 'E', + replace: true, + templateUrl: 'views/components/editor/umb-editor-header.html', + scope: { + tabs: "=", + actions: "=", + name: "=", + nameLocked: "=", + menu: "=", + icon: "=", + hideIcon: "@", + alias: "=", + hideAlias: "@", + description: "=", + hideDescription: "@", + navigation: "=" + }, + link: link + }; + + return directive; + } + + angular.module('umbraco.directives').directive('umbEditorHeader', EditorHeaderDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditormenu.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditormenu.directive.js new file mode 100644 index 0000000000..b6af7a4ca6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditormenu.directive.js @@ -0,0 +1,50 @@ +(function() { + 'use strict'; + + function EditorMenuDirective($injector, treeService, navigationService, umbModelMapper, appState) { + + function link(scope, el, attr, ctrl) { + + //adds a handler to the context menu item click, we need to handle this differently + //depending on what the menu item is supposed to do. + scope.executeMenuItem = function (action) { + navigationService.executeMenuAction(action, scope.currentNode, scope.currentSection); + }; + + //callback method to go and get the options async + scope.getOptions = function () { + + if (!scope.currentNode) { + return; + } + + //when the options item is selected, we need to set the current menu item in appState (since this is synonymous with a menu) + appState.setMenuState("currentNode", scope.currentNode); + + if (!scope.actions) { + treeService.getMenu({ treeNode: scope.currentNode }) + .then(function (data) { + scope.actions = data.menuItems; + }); + } + }; + + } + + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/editor/umb-editor-menu.html', + link: link, + scope: { + currentNode: "=", + currentSection: "@" + } + }; + + return directive; + } + + angular.module('umbraco.directives').directive('umbEditorMenu', EditorMenuDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditornavigation.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditornavigation.directive.js new file mode 100644 index 0000000000..cceb3ac86f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditornavigation.directive.js @@ -0,0 +1,64 @@ +(function() { + 'use strict'; + + function EditorNavigationDirective() { + + function link(scope, el, attr, ctrl) { + + scope.showNavigation = true; + + scope.clickNavigationItem = function(selectedItem) { + setItemToActive(selectedItem); + runItemAction(selectedItem); + }; + + function runItemAction(selectedItem) { + if (selectedItem.action) { + selectedItem.action(selectedItem); + } + } + + function setItemToActive(selectedItem) { + // set all other views to inactive + if (selectedItem.view) { + + for (var index = 0; index < scope.navigation.length; index++) { + var item = scope.navigation[index]; + item.active = false; + } + + // set view to active + selectedItem.active = true; + + } + } + + function activate() { + + // hide navigation if there is only 1 item + if (scope.navigation.length <= 1) { + scope.showNavigation = false; + } + + } + + activate(); + + } + + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/editor/umb-editor-navigation.html', + scope: { + navigation: "=" + }, + link: link + }; + + return directive; + } + + angular.module('umbraco.directives.html').directive('umbEditorNavigation', EditorNavigationDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorsubviews.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorsubviews.directive.js new file mode 100644 index 0000000000..db0dad8de7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorsubviews.directive.js @@ -0,0 +1,48 @@ +(function() { + 'use strict'; + + function EditorSubViewsDirective() { + + function link(scope, el, attr, ctrl) { + + scope.activeView = {}; + + // set toolbar from selected navigation item + function setActiveView(items) { + + for (var index = 0; index < items.length; index++) { + + var item = items[index]; + + if (item.active && item.view) { + scope.activeView = item; + } + } + } + + // watch for navigation changes + scope.$watch('subViews', function(newValue, oldValue) { + if (newValue) { + setActiveView(newValue); + } + }, true); + + } + + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/editor/umb-editor-sub-views.html', + scope: { + subViews: "=", + model: "=" + }, + link: link + }; + + return directive; + } + + angular.module('umbraco.directives').directive('umbEditorSubViews', EditorSubViewsDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditortoolbar.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditortoolbar.directive.js new file mode 100644 index 0000000000..82ea69acae --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditortoolbar.directive.js @@ -0,0 +1,20 @@ +(function() { + 'use strict'; + + function EditorToolbarDirective() { + + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/editor/umb-editor-toolbar.html', + scope: { + tools: "=" + } + }; + + return directive; + } + + angular.module('umbraco.directives').directive('umbEditorToolbar', EditorToolbarDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorview.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorview.directive.js new file mode 100644 index 0000000000..aad87178cc --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorview.directive.js @@ -0,0 +1,18 @@ +(function() { + 'use strict'; + + function EditorViewDirective() { + + var directive = { + transclude: true, + restrict: 'E', + replace: true, + templateUrl: 'views/components/editor/umb-editor-view.html' + }; + + return directive; + } + + angular.module('umbraco.directives').directive('umbEditorView', EditorViewDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js new file mode 100644 index 0000000000..066f91be53 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js @@ -0,0 +1,268 @@ +/** +* @description Utillity directives for key and field events +**/ +angular.module('umbraco.directives') + +.directive('onKeyup', function () { + return { + link: function (scope, elm, attrs) { + var f = function () { + scope.$apply(attrs.onKeyup); + }; + elm.on("keyup", f); + scope.$on("$destroy", function(){ elm.off("keyup", f);} ); + } + }; +}) + +.directive('onKeydown', function () { + return { + link: function (scope, elm, attrs) { + var f = function () { + scope.$apply(attrs.onKeydown); + }; + elm.on("keydown", f); + scope.$on("$destroy", function(){ elm.off("keydown", f);} ); + } + }; +}) + +.directive('onBlur', function () { + return { + link: function (scope, elm, attrs) { + var f = function () { + scope.$apply(attrs.onBlur); + }; + elm.on("blur", f); + scope.$on("$destroy", function(){ elm.off("blur", f);} ); + } + }; +}) + +.directive('onFocus', function () { + return { + link: function (scope, elm, attrs) { + var f = function () { + scope.$apply(attrs.onFocus); + }; + elm.on("focus", f); + scope.$on("$destroy", function(){ elm.off("focus", f);} ); + } + }; +}) + +.directive('onDragEnter', function () { + return { + link: function (scope, elm, attrs) { + var f = function () { + scope.$apply(attrs.onDragEnter); + }; + elm.on("dragenter", f); + scope.$on("$destroy", function(){ elm.off("dragenter", f);} ); + } + }; +}) + +.directive('onDragLeave', function () { + return function (scope, elm, attrs) { + var f = function (event) { + var rect = this.getBoundingClientRect(); + var getXY = function getCursorPosition(event) { + var x, y; + + if (typeof event.clientX === 'undefined') { + // try touch screen + x = event.pageX + document.documentElement.scrollLeft; + y = event.pageY + document.documentElement.scrollTop; + } else { + x = event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft; + y = event.clientY + document.body.scrollTop + document.documentElement.scrollTop; + } + + return { x: x, y : y }; + }; + + var e = getXY(event.originalEvent); + + // Check the mouseEvent coordinates are outside of the rectangle + if (e.x > rect.left + rect.width - 1 || e.x < rect.left || e.y > rect.top + rect.height - 1 || e.y < rect.top) { + scope.$apply(attrs.onDragLeave); + } + }; + + elm.on("dragleave", f); + scope.$on("$destroy", function(){ elm.off("dragleave", f);} ); + }; +}) + +.directive('onDragOver', function () { + return { + link: function (scope, elm, attrs) { + var f = function () { + scope.$apply(attrs.onDragOver); + }; + elm.on("dragover", f); + scope.$on("$destroy", function(){ elm.off("dragover", f);} ); + } + }; +}) + +.directive('onDragStart', function () { + return { + link: function (scope, elm, attrs) { + var f = function () { + scope.$apply(attrs.onDragStart); + }; + elm.on("dragstart", f); + scope.$on("$destroy", function(){ elm.off("dragstart", f);} ); + } + }; +}) + +.directive('onDragEnd', function () { + return { + link: function (scope, elm, attrs) { + var f = function () { + scope.$apply(attrs.onDragEnd); + }; + elm.on("dragend", f); + scope.$on("$destroy", function(){ elm.off("dragend", f);} ); + } + }; +}) + +.directive('onDrop', function () { + return { + link: function (scope, elm, attrs) { + var f = function () { + scope.$apply(attrs.onDrop); + }; + elm.on("drop", f); + scope.$on("$destroy", function(){ elm.off("drop", f);} ); + } + }; +}) + +.directive('onOutsideClick', function ($timeout) { + return function (scope, element, attrs) { + + var eventBindings = []; + + function oneTimeClick(event) { + var el = event.target.nodeName; + //ignore link and button clicks + var els = ["INPUT","A","BUTTON"]; + if(els.indexOf(el) >= 0){return;} + + // ignore children of links and buttons + // ignore clicks on new overlay + var parents = $(event.target).parents("a,button,.umb-overlay"); + if(parents.length > 0){ + return; + } + + // ignore clicks on dialog from old dialog service + var oldDialog = $(el).parents("#old-dialog-service"); + if (oldDialog.length === 1) { + return; + } + + //ignore clicks inside this element + if( $(element).has( $(event.target) ).length > 0 ){ + return; + } + + scope.$apply(attrs.onOutsideClick); + } + + + $timeout(function(){ + + if ("bindClickOn" in attrs) { + + eventBindings.push(scope.$watch(function() { + return attrs.bindClickOn; + }, function(newValue) { + if (newValue === "true") { + $(document).on("click", oneTimeClick); + } else { + $(document).off("click", oneTimeClick); + } + })); + + } else { + $(document).on("click", oneTimeClick); + } + + scope.$on("$destroy", function() { + $(document).off("click", oneTimeClick); + + // unbind watchers + for (var e in eventBindings) { + eventBindings[e](); + } + + }); + }); // Temp removal of 1 sec timeout to prevent bug where overlay does not open. We need to find a better solution. + + }; +}) + +.directive('onRightClick',function(){ + + document.oncontextmenu = function (e) { + if(e.target.hasAttribute('on-right-click')) { + e.preventDefault(); + e.stopPropagation(); + return false; + } + }; + + return function(scope,el,attrs){ + el.on('contextmenu',function(e){ + e.preventDefault(); + e.stopPropagation(); + scope.$apply(attrs.onRightClick); + return false; + }); + }; +}) + +.directive('onDelayedMouseleave', function ($timeout, $parse) { + return { + + restrict: 'A', + + link: function (scope, element, attrs, ctrl) { + var active = false; + var fn = $parse(attrs.onDelayedMouseleave); + + var leave_f = function(event) { + var callback = function() { + fn(scope, {$event:event}); + }; + + active = false; + $timeout(function(){ + if(active === false){ + scope.$apply(callback); + } + }, 650); + }; + + var enter_f = function(event, args){ + active = true; + }; + + + element.on("mouseleave", leave_f); + element.on("mouseenter", enter_f); + + //unsub events + scope.$on("$destroy", function(){ + element.off("mouseleave", leave_f); + element.off("mouseenter", enter_f); + }); + } + }; + }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/_readme.md b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/_readme.md new file mode 100644 index 0000000000..e82cf0c131 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/_readme.md @@ -0,0 +1,3 @@ +#Forms + +Directives used to enhance form elements. diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/util/checklistmodel.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/checklistmodel.directive.js similarity index 100% rename from src/Umbraco.Web.UI.Client/src/common/directives/util/checklistmodel.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/components/forms/checklistmodel.directive.js diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/contenteditable.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/contenteditable.directive.js new file mode 100644 index 0000000000..08eae2f378 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/contenteditable.directive.js @@ -0,0 +1,35 @@ +angular.module("umbraco.directives") +.directive("contenteditable", function() { + + return { + require: "ngModel", + link: function(scope, element, attrs, ngModel) { + + function read() { + ngModel.$setViewValue(element.html()); + } + + ngModel.$render = function() { + element.html(ngModel.$viewValue || ""); + }; + + + element.bind("focus", function(){ + + var range = document.createRange(); + range.selectNodeContents(element[0]); + + var sel = window.getSelection(); + sel.removeAllRanges(); + sel.addRange(range); + + }); + + element.bind("blur keyup change", function() { + scope.$apply(read); + }); + } + + }; + +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/util/fixnumber.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/fixnumber.directive.js similarity index 100% rename from src/Umbraco.Web.UI.Client/src/common/directives/util/fixnumber.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/components/forms/fixnumber.directive.js diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/util/focuswhen.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/focuswhen.directive.js similarity index 76% rename from src/Umbraco.Web.UI.Client/src/common/directives/util/focuswhen.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/components/forms/focuswhen.directive.js index 7422dcfc35..5d57b29175 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/util/focuswhen.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/focuswhen.directive.js @@ -4,7 +4,9 @@ angular.module("umbraco.directives").directive('focusWhen', function ($timeout) link: function (scope, elm, attrs, ctrl) { attrs.$observe("focusWhen", function (newValue) { if (newValue === "true") { - elm.focus(); + $timeout(function () { + elm.focus(); + }); } }); } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/hexbackgroundcolor.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/hexbackgroundcolor.directive.js similarity index 100% rename from src/Umbraco.Web.UI.Client/src/common/directives/hexbackgroundcolor.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/components/forms/hexbackgroundcolor.directive.js diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/hotkey.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/hotkey.directive.js new file mode 100644 index 0000000000..398918eea2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/hotkey.directive.js @@ -0,0 +1,55 @@ +/** +* @ngdoc directive +* @name umbraco.directives.directive:headline +**/ +angular.module("umbraco.directives") + .directive('hotkey', function ($window, keyboardService, $log) { + + return function (scope, el, attrs) { + + var options = {}; + var keyCombo = attrs.hotkey; + + if (!keyCombo) { + //support data binding + keyCombo = scope.$eval(attrs["hotkey"]); + } + + // disable shortcuts in input fields if keycombo is 1 character + if(keyCombo) { + + if(keyCombo.length === 1) { + options = { + inputDisabled: true + }; + } + + keyboardService.bind(keyCombo, function(){ + + var element = $(el); + var activeElementType = document.activeElement.tagName; + var clickableElements = ["A", "BUTTON"]; + + if(element.is("a,div,button,input[type='button'],input[type='submit'],input[type='checkbox']") && !element.is(':disabled') ){ + + // when keycombo is enter and a link or button has focus - click the link or button instead of using the hotkey + if(keyCombo === "enter" && clickableElements.indexOf(activeElementType) === 0) { + document.activeElement.click(); + } else { + element.click(); + } + + }else{ + element.focus(); + } + + }, options); + + el.on('$destroy', function(){ + keyboardService.unbind(keyCombo); + }); + + } + + }; + }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/util/preventdefault.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/preventdefault.directive.js similarity index 100% rename from src/Umbraco.Web.UI.Client/src/common/directives/util/preventdefault.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/components/forms/preventdefault.directive.js diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/util/prevententersubmit.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/prevententersubmit.directive.js similarity index 100% rename from src/Umbraco.Web.UI.Client/src/common/directives/util/prevententersubmit.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/components/forms/prevententersubmit.directive.js diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/util/resizetocontent.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/resizetocontent.directive.js similarity index 100% rename from src/Umbraco.Web.UI.Client/src/common/directives/util/resizetocontent.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/components/forms/resizetocontent.directive.js diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/util/selectonfocus.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/selectonfocus.directive.js similarity index 100% rename from src/Umbraco.Web.UI.Client/src/common/directives/util/selectonfocus.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/components/forms/selectonfocus.directive.js diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/editors/umbAutoFocus.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbautofocus.directive.js similarity index 85% rename from src/Umbraco.Web.UI.Client/src/common/directives/editors/umbAutoFocus.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbautofocus.directive.js index bf415ff25d..bb65e94593 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/editors/umbAutoFocus.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbautofocus.directive.js @@ -4,7 +4,7 @@ angular.module("umbraco.directives") return function(scope, element, attr){ var update = function() { //if it uses its default naming - if(element.val() === ""){ + if(element.val() === "" || attr.focusOnFilled){ element.focus(); } }; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbautoresize.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbautoresize.directive.js new file mode 100644 index 0000000000..8a456670c3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbautoresize.directive.js @@ -0,0 +1,157 @@ +angular.module("umbraco.directives") + .directive('umbAutoResize', function($timeout) { + return { + require: ["^?umbTabs", "ngModel"], + link: function(scope, element, attr, controllersArr) { + + var domEl = element[0]; + var domElType = domEl.type; + var umbTabsController = controllersArr[0]; + var ngModelController = controllersArr[1]; + + // IE elements + var isIEFlag = false; + var wrapper = angular.element('#umb-ie-resize-input-wrapper'); + var mirror = angular.element(''); + + function isIE() { + + var ua = window.navigator.userAgent; + var msie = ua.indexOf("MSIE "); + + if (msie > 0 || !!navigator.userAgent.match(/Trident.*rv\:11\./) || navigator.userAgent.match(/Edge\/\d+/)) { + return true; + } else { + return false; + } + + } + + function activate() { + + // check if browser is Internet Explorere + isIEFlag = isIE(); + + // scrollWidth on element does not work in IE on inputs + // we have to do some dirty dom element copying. + if (isIEFlag === true && domElType === "text") { + setupInternetExplorerElements(); + } + + } + + function setupInternetExplorerElements() { + + if (!wrapper.length) { + wrapper = angular.element('
'); + angular.element('body').append(wrapper); + } + + angular.forEach(['fontFamily', 'fontSize', 'fontWeight', 'fontStyle', + 'letterSpacing', 'textTransform', 'wordSpacing', 'textIndent', + 'boxSizing', 'borderRightWidth', 'borderLeftWidth', 'borderLeftStyle', 'borderRightStyle', + 'paddingLeft', 'paddingRight', 'marginLeft', 'marginRight' + ], function(value) { + mirror.css(value, element.css(value)); + }); + + wrapper.append(mirror); + + } + + function resizeInternetExplorerInput() { + + mirror.text(element.val() || attr.placeholder); + element.css('width', mirror.outerWidth() + 1); + + } + + function resizeInput() { + + if (domEl.scrollWidth !== domEl.clientWidth) { + if (ngModelController.$modelValue) { + element.width(domEl.scrollWidth); + } + } + + if(!ngModelController.$modelValue && attr.placeholder) { + attr.$set('size', attr.placeholder.length); + element.width('auto'); + } + + } + + function resizeTextarea() { + + if(domEl.scrollHeight !== domEl.clientHeight) { + + element.height(domEl.scrollHeight); + + } + + } + + var update = function(force) { + + + if (force === true) { + + if (domElType === "textarea") { + element.height(0); + } else if (domElType === "text") { + element.width(0); + } + + } + + + if (isIEFlag === true && domElType === "text") { + + resizeInternetExplorerInput(); + + } else { + + if (domElType === "textarea") { + + resizeTextarea(); + + } else if (domElType === "text") { + + resizeInput(); + + } + + } + + }; + + activate(); + + //listen for tab changes + if (umbTabsController != null) { + umbTabsController.onTabShown(function(args) { + update(); + }); + } + + // listen for ng-model changes + var unbindModelWatcher = scope.$watch(function() { + return ngModelController.$modelValue; + }, function(newValue) { + update(true); + }); + + scope.$on('$destroy', function() { + element.unbind('keyup keydown keypress change', update); + element.unbind('blur', update(true)); + unbindModelWatcher(); + + // clean up IE dom element + if (isIEFlag === true && domElType === "text") { + mirror.remove(); + } + + }); + } + }; + }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/editors/umbRawModel.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbrawmodel.directive.js similarity index 100% rename from src/Umbraco.Web.UI.Client/src/common/directives/editors/umbRawModel.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbrawmodel.directive.js diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/grid/grid.rte.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js similarity index 83% rename from src/Umbraco.Web.UI.Client/src/common/directives/grid/grid.rte.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js index e7a9712418..0fccd4eabb 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/grid/grid.rte.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js @@ -7,7 +7,11 @@ angular.module("umbraco.directives") onClick: '&', onFocus: '&', onBlur: '&', - configuration:"=" + configuration:"=", + onMediaPickerClick: "=", + onEmbedClick: "=", + onMacroPickerClick: "=", + onLinkPickerClick: "=" }, template: "", replace: true, @@ -56,7 +60,7 @@ angular.module("umbraco.directives") if(scope.configuration && scope.configuration.stylesheets){ angular.forEach(scope.configuration.stylesheets, function(stylesheet, key){ - stylesheets.push("/css/" + stylesheet + ".css"); + stylesheets.push(Umbraco.Sys.ServerVariables.umbracoSettings.cssPath + "/" + stylesheet + ".css"); await.push(stylesheetResource.getRulesByName(stylesheet).then(function (rules) { angular.forEach(rules, function (rule) { var r = {}; @@ -101,7 +105,8 @@ angular.module("umbraco.directives") relative_urls: false, toolbar: toolbar, content_css: stylesheets.join(','), - style_formats: styleFormats + style_formats: styleFormats, + autoresize_bottom_margin: 0 }; @@ -122,11 +127,6 @@ angular.module("umbraco.directives") editor.getBody().setAttribute('spellcheck', true); - //hide toolbar by default - $(editor.editorContainer) - .find(".mce-toolbar") - .css("visibility", "hidden"); - //force overflow to hidden to prevent no needed scroll editor.getBody().style.overflow = "hidden"; @@ -137,32 +137,6 @@ angular.module("umbraco.directives") }, 400); }); - - // pin toolbar to top of screen if we have focus and it scrolls off the screen - var pinToolbar = function () { - - var _toolbar = $(editor.editorContainer).find(".mce-toolbar"); - var toolbarHeight = _toolbar.height(); - - var _tinyMce = $(editor.editorContainer); - var tinyMceRect = _tinyMce[0].getBoundingClientRect(); - var tinyMceTop = tinyMceRect.top; - var tinyMceBottom = tinyMceRect.bottom; - - if (tinyMceTop < 100 && (tinyMceBottom > (100 + toolbarHeight))) { - _toolbar - .css("visibility", "visible") - .css("position", "fixed") - .css("top", "100px") - .css("margin-top", "0"); - } else { - _toolbar - .css("visibility", "visible") - .css("position", "absolute") - .css("top", "auto") - .css("margin-top", (-toolbarHeight - 2) + "px"); - } - }; //when we leave the editor (maybe) editor.on('blur', function (e) { @@ -177,8 +151,6 @@ angular.module("umbraco.directives") scope.onBlur(); } - _toolbar.css("visibility", "hidden"); - $('.umb-panel-body').off('scroll', pinToolbar); }); }); @@ -190,8 +162,6 @@ angular.module("umbraco.directives") scope.onFocus(); } - pinToolbar(); - $('.umb-panel-body').on('scroll', pinToolbar); }); }); @@ -203,8 +173,6 @@ angular.module("umbraco.directives") scope.onClick(); } - pinToolbar(); - $('.umb-panel-body').on('scroll', pinToolbar); }); }); @@ -235,24 +203,39 @@ angular.module("umbraco.directives") }); editor.on('ObjectResized', function (e) { - var qs = "?width=" + e.width + "px&height=" + e.height + "px"; + var qs = "?width=" + e.width + "&height=" + e.height; var srcAttr = $(e.target).attr("src"); var path = srcAttr.split("?")[0]; $(e.target).attr("data-mce-src", path + qs); }); + //Create the insert link plugin + tinyMceService.createLinkPicker(editor, scope, function(currentTarget, anchorElement){ + if(scope.onLinkPickerClick) { + scope.onLinkPickerClick(editor, currentTarget, anchorElement); + } + }); //Create the insert media plugin - tinyMceService.createMediaPicker(editor, scope); + tinyMceService.createMediaPicker(editor, scope, function(currentTarget, userData){ + if(scope.onMediaPickerClick) { + scope.onMediaPickerClick(editor, currentTarget, userData); + } + }); //Create the embedded plugin - tinyMceService.createInsertEmbeddedMedia(editor, scope); - - //Create the insert link plugin - //tinyMceService.createLinkPicker(editor, scope); + tinyMceService.createInsertEmbeddedMedia(editor, scope, function(){ + if(scope.onEmbedClick) { + scope.onEmbedClick(editor); + } + }); //Create the insert macro plugin - tinyMceService.createInsertMacro(editor, scope); + tinyMceService.createInsertMacro(editor, scope, function(dialogData){ + if(scope.onMacroPickerClick) { + scope.onMacroPickerClick(editor, dialogData); + } + }); }; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/html/readme.md b/src/Umbraco.Web.UI.Client/src/common/directives/components/html/readme.md similarity index 100% rename from src/Umbraco.Web.UI.Client/src/common/directives/html/readme.md rename to src/Umbraco.Web.UI.Client/src/common/directives/components/html/readme.md diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/html/umbcontrolgroup.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/html/umbcontrolgroup.directive.js similarity index 90% rename from src/Umbraco.Web.UI.Client/src/common/directives/html/umbcontrolgroup.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/components/html/umbcontrolgroup.directive.js index f371dba9b5..5da3b1c2a9 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/html/umbcontrolgroup.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/html/umbcontrolgroup.directive.js @@ -15,8 +15,8 @@ angular.module("umbraco.directives.html") require: '?^form', transclude: true, restrict: 'E', - replace: true, - templateUrl: 'views/directives/html/umb-control-group.html', + replace: true, + templateUrl: 'views/components/html/umb-control-group.html', link: function (scope, element, attr, formCtrl) { scope.formValid = function() { @@ -43,4 +43,4 @@ angular.module("umbraco.directives.html") } }; - }); \ No newline at end of file + }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/html/umbpane.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/html/umbpane.directive.js similarity index 70% rename from src/Umbraco.Web.UI.Client/src/common/directives/html/umbpane.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/components/html/umbpane.directive.js index 93ce6f13a4..865d5f9c0c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/html/umbpane.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/html/umbpane.directive.js @@ -8,7 +8,7 @@ angular.module("umbraco.directives.html") return { transclude: true, restrict: 'E', - replace: true, - templateUrl: 'views/directives/html/umb-pane.html' + replace: true, + templateUrl: 'views/components/html/umb-pane.html' }; - }); \ No newline at end of file + }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/html/umbpanel.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/html/umbpanel.directive.js similarity index 77% rename from src/Umbraco.Web.UI.Client/src/common/directives/html/umbpanel.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/components/html/umbpanel.directive.js index 5b32941fff..7c69437f4a 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/html/umbpanel.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/html/umbpanel.directive.js @@ -9,6 +9,6 @@ angular.module("umbraco.directives.html") restrict: 'E', replace: true, transclude: 'true', - templateUrl: 'views/directives/html/umb-panel.html' + templateUrl: 'views/components/html/umb-panel.html' }; - }); \ No newline at end of file + }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/imaging/umbimagecrop.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/imaging/umbimagecrop.directive.js similarity index 88% rename from src/Umbraco.Web.UI.Client/src/common/directives/imaging/umbimagecrop.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/components/imaging/umbimagecrop.directive.js index 74c205417a..135d806aa4 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/imaging/umbimagecrop.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/imaging/umbimagecrop.directive.js @@ -5,12 +5,12 @@ * @function **/ angular.module("umbraco.directives") - .directive('umbImageCrop', + .directive('umbImageCrop', function ($timeout, localizationService, cropperHelper, $log) { return { restrict: 'E', replace: true, - templateUrl: 'views/directives/imaging/umb-image-crop.html', + templateUrl: 'views/components/imaging/umb-image-crop.html', scope: { src: '=', width: '@', @@ -39,15 +39,15 @@ angular.module("umbraco.directives") //live rendering of viewport and image styles scope.style = function () { - return { + return { 'height': (parseInt(scope.dimensions.viewport.height, 10)) + 'px', - 'width': (parseInt(scope.dimensions.viewport.width, 10)) + 'px' + 'width': (parseInt(scope.dimensions.viewport.width, 10)) + 'px' }; }; - + //elements - var $viewport = element.find(".viewport"); + var $viewport = element.find(".viewport"); var $image = element.find("img"); var $overlay = element.find(".overlay"); var $container = element.find(".crop-container"); @@ -64,7 +64,7 @@ angular.module("umbraco.directives") }; - var setDimensions = function(originalImage){ + var setDimensions = function(originalImage){ originalImage.width("auto"); originalImage.height("auto"); @@ -86,15 +86,15 @@ angular.module("umbraco.directives") var _viewPortH = parseInt(scope.height, 10); //if we set a constraint we will scale it down if needed - if(scope.maxSize){ + if(scope.maxSize){ var ratioCalculation = cropperHelper.scaleToMaxSize( - _viewPortW, + _viewPortW, _viewPortH, scope.maxSize); //so if we have a max size, override the thumb sizes _viewPortW = ratioCalculation.width; - _viewPortH = ratioCalculation.height; + _viewPortH = ratioCalculation.height; } scope.dimensions.viewport.width = _viewPortW + 2 * scope.dimensions.margin; @@ -106,12 +106,12 @@ angular.module("umbraco.directives") //when loading an image without any crop info, we center and fit it var resizeImageToEditor = function(){ - //returns size fitting the cropper + //returns size fitting the cropper var size = cropperHelper.calculateAspectRatioFit( - scope.dimensions.image.width, - scope.dimensions.image.height, - scope.dimensions.cropper.width, - scope.dimensions.cropper.height, + scope.dimensions.image.width, + scope.dimensions.image.height, + scope.dimensions.cropper.width, + scope.dimensions.cropper.height, true); //sets the image size and updates the scope @@ -145,16 +145,16 @@ angular.module("umbraco.directives") //resize the image to a predefined crop coordinate var resizeImageToCrop = function(){ scope.dimensions.image = cropperHelper.convertToStyle( - scope.crop, + scope.crop, {width: scope.dimensions.image.originalWidth, height: scope.dimensions.image.originalHeight}, scope.dimensions.cropper, scope.dimensions.margin); var ratioCalculation = cropperHelper.calculateAspectRatioFit( - scope.dimensions.image.originalWidth, - scope.dimensions.image.originalHeight, - scope.dimensions.cropper.width, - scope.dimensions.cropper.height, + scope.dimensions.image.originalWidth, + scope.dimensions.image.originalHeight, + scope.dimensions.cropper.width, + scope.dimensions.cropper.height, true); scope.dimensions.scale.current = scope.dimensions.image.ratio; @@ -169,7 +169,7 @@ angular.module("umbraco.directives") var validatePosition = function(left, top){ if(left > constraints.left.max) { - left = constraints.left.max; + left = constraints.left.max; } if(left <= constraints.left.min){ @@ -178,7 +178,7 @@ angular.module("umbraco.directives") if(top > constraints.top.max) { - top = constraints.top.max; + top = constraints.top.max; } if(top <= constraints.top.min){ top = constraints.top.min; @@ -186,17 +186,17 @@ angular.module("umbraco.directives") if(scope.dimensions.image.left !== left){ scope.dimensions.image.left = left; - } - + } + if(scope.dimensions.image.top !== top){ scope.dimensions.image.top = top; } - }; + }; - //sets scope.crop to the recalculated % based crop + //sets scope.crop to the recalculated % based crop var calculateCropBox = function(){ - scope.crop = cropperHelper.pixelsToCoordinates(scope.dimensions.image, scope.dimensions.cropper.width, scope.dimensions.cropper.height, scope.dimensions.margin); + scope.crop = cropperHelper.pixelsToCoordinates(scope.dimensions.image, scope.dimensions.cropper.width, scope.dimensions.cropper.height, scope.dimensions.margin); }; @@ -218,7 +218,7 @@ angular.module("umbraco.directives") }); } }); - + var init = function(image){ @@ -252,10 +252,10 @@ angular.module("umbraco.directives") var throttledResizing = _.throttle(function(){ resizeImageToScale(scope.dimensions.scale.current); - calculateCropBox(); + calculateCropBox(); }, 100); - + //happens when we change the scale scope.$watch("dimensions.scale.current", function(){ if(scope.loaded){ @@ -270,9 +270,9 @@ angular.module("umbraco.directives") scope.$apply(function(){ scope.dimensions.scale.current = ranger.val(); }); - }); + }); } - + //// INIT ///// $image.load(function(){ $timeout(function(){ @@ -281,4 +281,4 @@ angular.module("umbraco.directives") }); } }; - }); \ No newline at end of file + }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/imaging/umbimagegravity.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/imaging/umbimagegravity.directive.js similarity index 91% rename from src/Umbraco.Web.UI.Client/src/common/directives/imaging/umbimagegravity.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/components/imaging/umbimagegravity.directive.js index 3de777cee9..9e5944a460 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/imaging/umbimagegravity.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/imaging/umbimagegravity.directive.js @@ -3,7 +3,7 @@ * @name umbraco.directives.directive:umbCropsy * @restrict E * @function -* @description +* @description * Used by editors that require naming an entity. Shows a textbox/headline with a required validator within it's own form. **/ angular.module("umbraco.directives") @@ -11,13 +11,13 @@ angular.module("umbraco.directives") return { restrict: 'E', replace: true, - templateUrl: 'views/directives/imaging/umb-image-gravity.html', + templateUrl: 'views/components/imaging/umb-image-gravity.html', scope: { src: '=', center: "=" }, link: function(scope, element, attrs) { - + //Internal values for keeping track of the dot and the size of the editor scope.dimensions = { width: 0, @@ -27,10 +27,10 @@ angular.module("umbraco.directives") }; //elements - var $viewport = element.find(".viewport"); + var $viewport = element.find(".viewport"); var $image = element.find("img"); var $overlay = element.find(".overlay"); - + scope.style = function () { if(scope.dimensions.width <= 0){ setDimensions(); @@ -38,7 +38,7 @@ angular.module("umbraco.directives") return { 'top': scope.dimensions.top + 'px', - 'left': scope.dimensions.left + 'px' + 'left': scope.dimensions.left + 'px' }; }; @@ -52,7 +52,7 @@ angular.module("umbraco.directives") }else{ scope.center = { left: 0.5, top: 0.5 }; } - }; + }; var calculateGravity = function(){ scope.dimensions.left = $overlay[0].offsetLeft; @@ -61,13 +61,13 @@ angular.module("umbraco.directives") scope.center.left = (scope.dimensions.left+10) / scope.dimensions.width; scope.center.top = (scope.dimensions.top+10) / scope.dimensions.height; }; - + var lazyEndEvent = _.debounce(function(){ scope.$apply(function(){ scope.$emit("imageFocalPointStop"); }); }, 2000); - + //Drag and drop positioning, using jquery ui draggable //TODO ensure that the point doesnt go outside the box @@ -95,4 +95,4 @@ angular.module("umbraco.directives") }); } }; - }); \ No newline at end of file + }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/imaging/umbimagethumbnail.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/imaging/umbimagethumbnail.directive.js similarity index 84% rename from src/Umbraco.Web.UI.Client/src/common/directives/imaging/umbimagethumbnail.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/components/imaging/umbimagethumbnail.directive.js index 803ab278d0..c93747616f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/imaging/umbimagethumbnail.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/imaging/umbimagethumbnail.directive.js @@ -3,17 +3,17 @@ * @name umbraco.directives.directive:umbCropsy * @restrict E * @function -* @description +* @description * Used by editors that require naming an entity. Shows a textbox/headline with a required validator within it's own form. **/ angular.module("umbraco.directives") - .directive('umbImageThumbnail', + .directive('umbImageThumbnail', function ($timeout, localizationService, cropperHelper, $log) { return { restrict: 'E', replace: true, - templateUrl: 'views/directives/imaging/umb-image-thumbnail.html', - + templateUrl: 'views/components/imaging/umb-image-thumbnail.html', + scope: { src: '=', width: '@', @@ -22,7 +22,7 @@ angular.module("umbraco.directives") crop: "=", maxSize: '@' }, - + link: function(scope, element, attrs) { //// INIT ///// var $image = element.find("img"); @@ -40,10 +40,10 @@ angular.module("umbraco.directives") //we do not compare to the image dimensions, but the thumbs if(scope.maxSize){ var ratioCalculation = cropperHelper.calculateAspectRatioFit( - scope.width, + scope.width, scope.height, - scope.maxSize, - scope.maxSize, + scope.maxSize, + scope.maxSize, false); //so if we have a max size, override the thumb sizes @@ -51,7 +51,7 @@ angular.module("umbraco.directives") scope.height = ratioCalculation.height; } - setPreviewStyle(); + setPreviewStyle(); }); }); @@ -65,22 +65,22 @@ angular.module("umbraco.directives") scope.$watch("center", function(){ setPreviewStyle(); }, true); - + function setPreviewStyle(){ if(scope.crop && scope.image){ scope.preview = cropperHelper.convertToStyle( - scope.crop, + scope.crop, scope.image, {width: scope.width, height: scope.height}, 0); }else if(scope.image){ - //returns size fitting the cropper + //returns size fitting the cropper var p = cropperHelper.calculateAspectRatioFit( - scope.image.width, - scope.image.height, - scope.width, - scope.height, + scope.image.width, + scope.image.height, + scope.width, + scope.height, true); @@ -98,4 +98,4 @@ angular.module("umbraco.directives") } } }; - }); \ No newline at end of file + }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/localization/localize.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/localization/localize.directive.js new file mode 100644 index 0000000000..52271961a7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/localization/localize.directive.js @@ -0,0 +1,43 @@ +angular.module("umbraco.directives") + + .directive('localize', function ($log, localizationService) { + return { + restrict: 'E', + scope:{ + key: '@' + }, + replace: true, + + link: function (scope, element, attrs) { + var key = scope.key; + localizationService.localize(key).then(function(value){ + element.html(value); + }); + } + }; + }) + + .directive('localize', function ($log, localizationService) { + return { + restrict: 'A', + link: function (scope, element, attrs) { + var keys = attrs.localize.split(','); + + angular.forEach(keys, function(value, key){ + var attr = element.attr(value); + + if(attr){ + if(attr[0] === '@'){ + + var t = localizationService.tokenize(attr.substring(1), scope); + localizationService.localize(t.key, t.tokens).then(function(val){ + element.attr(value, val); + }); + + } + } + }); + } + }; + + }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/notifications/umbnotifications.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/notifications/umbnotifications.directive.js new file mode 100644 index 0000000000..d9f8f76576 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/notifications/umbnotifications.directive.js @@ -0,0 +1,36 @@ +/** + * @ngdoc directive + * @name umbraco.directives.directive:umbNotifications + */ + +(function() { + 'use strict'; + + function NotificationDirective(notificationsService) { + + function link(scope, el, attr, ctrl) { + + //subscribes to notifications in the notification service + scope.notifications = notificationsService.current; + scope.$watch('notificationsService.current', function (newVal, oldVal, scope) { + if (newVal) { + scope.notifications = newVal; + } + }); + + } + + var directive = { + restrict: "E", + replace: true, + templateUrl: 'views/components/notifications/umb-notifications.html', + link: link + }; + + return directive; + + } + + angular.module('umbraco.directives').directive('umbNotifications', NotificationDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/overlays/umboverlay.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/overlays/umboverlay.directive.js new file mode 100644 index 0000000000..294e2767ac --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/overlays/umboverlay.directive.js @@ -0,0 +1,295 @@ +/** + * @ngdoc directive + * @name umbraco.directives.directive:umbProperty + * @restrict E + **/ + +(function() { + 'use strict'; + + function OverlayDirective($timeout, formHelper, overlayHelper, localizationService) { + + function link(scope, el, attr, ctrl) { + + var overlayNumber = 0; + var numberOfOverlays = 0; + var isRegistered = false; + + var modelCopy = {}; + + function activate() { + + setView(); + + setButtonText(); + + registerOverlay(); + + modelCopy = makeModelCopy(scope.model); + + $timeout(function() { + + if (scope.position === "target") { + setTargetPosition(); + } + + setOverlayIndent(); + + }); + + } + + function setView() { + + if (scope.view) { + + if (scope.view.indexOf(".html") === -1) { + var viewAlias = scope.view.toLowerCase(); + scope.view = "views/common/overlays/" + viewAlias + "/" + viewAlias + ".html"; + } + + } + + } + + function setButtonText() { + if (!scope.model.closeButtonLabelKey && !scope.model.closeButtonLabel) { + scope.model.closeButtonLabel = localizationService.localize("general_close").then(function (value) {return value;}); + } + if (!scope.model.submitButtonLabelKey && !scope.model.submitButtonLabel) { + scope.model.submitButtonLabel = localizationService.localize("general_submit").then(function (value) {return value;}); + } + } + + function registerOverlay() { + + overlayNumber = overlayHelper.registerOverlay(); + + $(document).bind("keydown.overlay-" + overlayNumber, function(event) { + + if (event.which === 27) { + + numberOfOverlays = overlayHelper.getNumberOfOverlays(); + + if(numberOfOverlays === overlayNumber) { + scope.closeOverLay(); + } + + event.preventDefault(); + } + + if (event.which === 13) { + + numberOfOverlays = overlayHelper.getNumberOfOverlays(); + + if(numberOfOverlays === overlayNumber) { + + var activeElementType = document.activeElement.tagName; + var clickableElements = ["A", "BUTTON"]; + var submitOnEnter = document.activeElement.hasAttribute("overlay-submit-on-enter"); + + if(clickableElements.indexOf(activeElementType) === 0) { + document.activeElement.click(); + event.preventDefault(); + } else if(activeElementType === "TEXTAREA" && !submitOnEnter) { + + + } else { + scope.$apply(function () { + scope.submitForm(scope.model); + }); + event.preventDefault(); + } + + } + + } + + }); + + isRegistered = true; + + } + + function unregisterOverlay() { + + if(isRegistered) { + + overlayHelper.unregisterOverlay(); + + $(document).unbind("keydown.overlay-" + overlayNumber); + + isRegistered = false; + } + + } + + function makeModelCopy(object) { + + var newObject = {}; + + for (var key in object) { + if (key !== "event") { + newObject[key] = angular.copy(object[key]); + } + } + + return newObject; + + } + + function setOverlayIndent() { + + var overlayIndex = overlayNumber - 1; + var indentSize = overlayIndex * 20; + var overlayWidth = el.context.clientWidth; + + el.css('width', overlayWidth - indentSize); + + if(scope.position === "center" || scope.position === "target") { + var overlayTopPosition = el.context.offsetTop; + el.css('top', overlayTopPosition + indentSize); + } + + } + + function setTargetPosition() { + + var container = $("#contentwrapper"); + var containerLeft = container[0].offsetLeft; + var containerRight = containerLeft + container[0].offsetWidth; + var containerTop = container[0].offsetTop; + var containerBottom = containerTop + container[0].offsetHeight; + + var mousePositionClickX = null; + var mousePositionClickY = null; + var elementHeight = null; + var elementWidth = null; + + var position = { + right: "inherit", + left: "inherit", + top: "inherit", + bottom: "inherit" + }; + + // if mouse click position is know place element with mouse in center + if (scope.model.event && scope.model.event) { + + // click position + mousePositionClickX = scope.model.event.pageX; + mousePositionClickY = scope.model.event.pageY; + + // element size + elementHeight = el.context.clientHeight; + elementWidth = el.context.clientWidth; + + // move element to this position + position.left = mousePositionClickX - (elementWidth / 2); + position.top = mousePositionClickY - (elementHeight / 2); + + // check to see if element is outside screen + // outside right + if (position.left + elementWidth > containerRight) { + position.right = 10; + position.left = "inherit"; + } + + // outside bottom + if (position.top + elementHeight > containerBottom) { + position.bottom = 10; + position.top = "inherit"; + } + + // outside left + if (position.left < containerLeft) { + position.left = containerLeft + 10; + position.right = "inherit"; + } + + // outside top + if (position.top < containerTop) { + position.top = 10; + position.bottom = "inherit"; + } + + el.css(position); + + } + + } + + scope.submitForm = function(model) { + + if(scope.model.submit) { + + if (formHelper.submitForm({scope: scope})) { + + formHelper.resetForm({ scope: scope }); + + unregisterOverlay(); + + scope.model.submit(model); + + } + + } + + }; + + scope.closeOverLay = function() { + + unregisterOverlay(); + + if (scope.model.close) { + scope.model = modelCopy; + scope.model.close(scope.model); + } else { + scope.model.show = false; + scope.model = null; + } + + }; + + // angular does not support ng-show on custom directives + // width isolated scopes. So we have to make our own. + if (attr.hasOwnProperty("ngShow")) { + scope.$watch("ngShow", function(value) { + if (value) { + el.show(); + activate(); + } else { + unregisterOverlay(); + el.hide(); + } + }); + } else { + activate(); + } + + scope.$on('$destroy', function(){ + unregisterOverlay(); + }); + + } + + var directive = { + transclude: true, + restrict: 'E', + replace: true, + templateUrl: 'views/components/overlays/umb-overlay.html', + scope: { + ngShow: "=", + model: "=", + view: "=", + position: "@" + }, + link: link + }; + + return directive; + } + + angular.module('umbraco.directives').directive('umbOverlay', OverlayDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/overlays/umboverlaybackdrop.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/overlays/umboverlaybackdrop.directive.js new file mode 100644 index 0000000000..cfcd7e064b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/overlays/umboverlaybackdrop.directive.js @@ -0,0 +1,31 @@ +(function() { + 'use strict'; + + function OverlayBackdropDirective(overlayHelper) { + + function link(scope, el, attr, ctrl) { + + scope.numberOfOverlays = 0; + + scope.$watch(function(){ + return overlayHelper.getNumberOfOverlays(); + }, function (newValue) { + scope.numberOfOverlays = newValue; + }); + + } + + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/overlays/umb-overlay-backdrop.html', + link: link + }; + + return directive; + + } + + angular.module('umbraco.directives').directive('umbOverlayBackdrop', OverlayBackdropDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/umbproperty.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js similarity index 75% rename from src/Umbraco.Web.UI.Client/src/common/directives/umbproperty.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js index d84bb8e24d..adbc3a96a7 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/umbproperty.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js @@ -1,30 +1,32 @@ -/** -* @ngdoc directive -* @name umbraco.directives.directive:umbProperty -* @restrict E -**/ -angular.module("umbraco.directives") - .directive('umbProperty', function (umbPropEditorHelper) { - return { - scope: { - property: "=" - }, - transclude: true, - restrict: 'E', - replace: true, - templateUrl: 'views/directives/umb-property.html', - - //Define a controller for this directive to expose APIs to other directives - controller: function ($scope, $timeout) { - - var self = this; - - //set the API properties/methods - - self.property = $scope.property; - self.setPropertyError = function(errorMsg) { - $scope.property.propertyErrorMessage = errorMsg; - }; - } - }; +/** +* @ngdoc directive +* @name umbraco.directives.directive:umbProperty +* @restrict E +**/ +angular.module("umbraco.directives") + .directive('umbProperty', function (umbPropEditorHelper) { + return { + scope: { + property: "=" + }, + transclude: true, + restrict: 'E', + replace: true, + templateUrl: 'views/components/property/umb-property.html', + link: function(scope) { + scope.propertyAlias = Umbraco.Sys.ServerVariables.isDebuggingEnabled === true ? scope.property.alias : null; + }, + //Define a controller for this directive to expose APIs to other directives + controller: function ($scope, $timeout) { + + var self = this; + + //set the API properties/methods + + self.property = $scope.property; + self.setPropertyError = function(errorMsg) { + $scope.property.propertyErrorMessage = errorMsg; + }; + } + }; }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/umbeditor.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbpropertyeditor.directive.js similarity index 51% rename from src/Umbraco.Web.UI.Client/src/common/directives/umbeditor.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbpropertyeditor.directive.js index 7446ef8f16..9a032d569f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/umbeditor.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbpropertyeditor.directive.js @@ -1,33 +1,41 @@ -/** -* @ngdoc directive -* @function -* @name umbraco.directives.directive:umbEditor -* @requires formController -* @restrict E -**/ -angular.module("umbraco.directives") - .directive('umbEditor', function (umbPropEditorHelper) { - return { - scope: { - model: "=", - isPreValue: "@" - }, - require: "^form", - restrict: 'E', - replace: true, - templateUrl: 'views/directives/umb-editor.html', - link: function (scope, element, attrs, ctrl) { - - //we need to copy the form controller val to our isolated scope so that - //it get's carried down to the child scopes of this! - //we'll also maintain the current form name. - scope[ctrl.$name] = ctrl; - - if(!scope.model.alias){ - scope.model.alias = Math.random().toString(36).slice(2); - } - - scope.propertyEditorView = umbPropEditorHelper.getViewPath(scope.model.view, scope.isPreValue); - } - }; - }); \ No newline at end of file +/** +* @ngdoc directive +* @function +* @name umbraco.directives.directive:umbPropertyEditor +* @requires formController +* @restrict E +**/ + +//share property editor directive function +var _umbPropertyEditor = function (umbPropEditorHelper) { + return { + scope: { + model: "=", + isPreValue: "@" + }, + + require: "^form", + restrict: 'E', + replace: true, + templateUrl: 'views/components/property/umb-property-editor.html', + link: function (scope, element, attrs, ctrl) { + + //we need to copy the form controller val to our isolated scope so that + //it get's carried down to the child scopes of this! + //we'll also maintain the current form name. + scope[ctrl.$name] = ctrl; + + if(!scope.model.alias){ + scope.model.alias = Math.random().toString(36).slice(2); + } + + scope.$watch("model.view", function(val){ + scope.propertyEditorView = umbPropEditorHelper.getViewPath(scope.model.view, scope.isPreValue); + }); + } + }; + }; + +//Preffered is the umb-property-editor as its more explicit - but we keep umb-editor for backwards compat +angular.module("umbraco.directives").directive('umbPropertyEditor', _umbPropertyEditor); +angular.module("umbraco.directives").directive('umbEditor', _umbPropertyEditor); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbpropertygroup.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbpropertygroup.directive.js new file mode 100644 index 0000000000..fd2be97b42 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbpropertygroup.directive.js @@ -0,0 +1,9 @@ +angular.module("umbraco.directives.html") + .directive('umbPropertyGroup', function () { + return { + transclude: true, + restrict: 'E', + replace: true, + templateUrl: 'views/components/property/umb-property-group.html' + }; + }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tabs/umbtab.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tabs/umbtab.directive.js new file mode 100644 index 0000000000..f25909affe --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tabs/umbtab.directive.js @@ -0,0 +1,14 @@ +/** +* @ngdoc directive +* @name umbraco.directives.directive:umbTab +* @restrict E +**/ +angular.module("umbraco.directives") +.directive('umbTab', function ($parse, $timeout) { + return { + restrict: 'E', + replace: true, + transclude: 'true', + templateUrl: 'views/components/tabs/umb-tab.html' + }; +}); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tabs/umbtabs.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tabs/umbtabs.directive.js new file mode 100644 index 0000000000..882372aba9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tabs/umbtabs.directive.js @@ -0,0 +1,45 @@ +/** +* @ngdoc directive +* @name umbraco.directives.directive:umbTabs +* @restrict A +* @description Used to bind to bootstrap tab events so that sub directives can use this API to listen to tab changes +**/ +angular.module("umbraco.directives") +.directive('umbTabs', function () { + return { + restrict: 'A', + controller: function ($scope, $element, $attrs) { + + var callbacks = []; + this.onTabShown = function(cb) { + callbacks.push(cb); + }; + + function tabShown(event) { + + var curr = $(event.target); // active tab + var prev = $(event.relatedTarget); // previous tab + + for (var c in callbacks) { + callbacks[c].apply(this, [{current: curr, previous: prev}]); + } + } + + //NOTE: it MUST be done this way - binding to an ancestor element that exists + // in the DOM to bind to the dynamic elements that will be created. + // It would be nicer to create this event handler as a directive for which child + // directives can attach to. + $element.on('shown', '.nav-tabs a', tabShown); + + //ensure to unregister + $scope.$on('$destroy', function () { + $element.off('shown', '.nav-tabs a', tabShown); + + for (var c in callbacks) { + delete callbacks[c]; + } + callbacks = null; + }); + } + }; +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tabs/umbtabscontent.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tabs/umbtabscontent.directive.js new file mode 100644 index 0000000000..f567e21b6a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tabs/umbtabscontent.directive.js @@ -0,0 +1,25 @@ +(function() { + 'use strict'; + + function UmbTabsContentDirective() { + + function link(scope, el, attr, ctrl) { + + scope.view = attr.view; + + } + + var directive = { + restrict: "E", + replace: true, + transclude: 'true', + templateUrl: "views/components/tabs/umb-tabs-content.html", + link: link + }; + + return directive; + } + + angular.module('umbraco.directives').directive('umbTabsContent', UmbTabsContentDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tabs/umbtabsnav.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tabs/umbtabsnav.directive.js new file mode 100644 index 0000000000..890acf3d6f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tabs/umbtabsnav.directive.js @@ -0,0 +1,56 @@ +(function() { + 'use strict'; + + function UmbTabsNavDirective($timeout) { + + function link(scope, el, attr) { + + function activate() { + + $timeout(function () { + + //use bootstrap tabs API to show the first one + el.find("a:first").tab('show'); + + //enable the tab drop + el.tabdrop(); + + }); + + } + + var unbindModelWatch = scope.$watch('model', function(newValue, oldValue){ + + activate(); + + }); + + + scope.$on('$destroy', function () { + + //ensure to destroy tabdrop (unbinds window resize listeners) + el.tabdrop("destroy"); + + unbindModelWatch(); + + }); + + } + + var directive = { + restrict: "E", + replace: true, + templateUrl: "views/components/tabs/umb-tabs-nav.html", + scope: { + model: "=", + tabdrop: "=" + }, + link: link + }; + + return directive; + } + + angular.module('umbraco.directives').directive('umbTabsNav', UmbTabsNavDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/umbtree.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js similarity index 96% rename from src/Umbraco.Web.UI.Client/src/common/directives/umbtree.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js index 2aaf66c81a..a7d271c892 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/umbtree.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js @@ -200,13 +200,17 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat //NOTE: loadChildren is ONLY used for legacy purposes, do not use this when syncing the tree as it will cause problems // since there will be double request and event handling operations. function loadActiveTree(treeAlias, loadChildren) { + if (!treeAlias) { + return; + } + scope.activeTree = undefined; function doLoad(tree) { var childrenAndSelf = [tree].concat(tree.children); scope.activeTree = _.find(childrenAndSelf, function (node) { - if(node && node.metaData){ - return node.metaData.treeAlias === treeAlias; + if(node && node.metaData && node.metaData.treeAlias) { + return node.metaData.treeAlias.toUpperCase() === treeAlias.toUpperCase(); } return false; }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/umbtreeitem.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreeitem.directive.js similarity index 91% rename from src/Umbraco.Web.UI.Client/src/common/directives/umbtreeitem.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreeitem.directive.js index 4f4971f312..20fdc6e332 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/umbtreeitem.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreeitem.directive.js @@ -37,11 +37,10 @@ angular.module("umbraco.directives") template: '
  • ' + '
    ' + //NOTE: This ins element is used to display the search icon if the node is a container/listview and the tree is currently in dialog - //'' + - '' + - '' + - '' + - '' + + //'' + + ' ' + + '' + + '' + //NOTE: These are the 'option' elipses '' + '
    ' + @@ -87,7 +86,7 @@ angular.module("umbraco.directives") icon.addClass(node.cssClass); icon.attr("title", node.routePath); - element.find("a:first").html(node.name); + element.find("a:first").text(node.name); if (!node.menuUrl) { element.find("a.umb-options").remove(); @@ -149,8 +148,17 @@ angular.module("umbraco.directives") and emits it as a treeNodeSelect element if there is a callback object defined on the tree */ - scope.select = function(n, ev) { + scope.select = function (n, ev) { + if (ev.ctrlKey || + ev.shiftKey || + ev.metaKey || // apple + (ev.button && ev.button === 1) // middle click, >IE9 + everyone else + ) { + return; + } + emitEvent("treeNodeSelect", { element: element, tree: scope.tree, node: n, event: ev }); + ev.preventDefault(); }; /** diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/umbtreesearchbox.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js similarity index 94% rename from src/Umbraco.Web.UI.Client/src/common/directives/umbtreesearchbox.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js index c9dd185495..a3b2fab00f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/umbtreesearchbox.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js @@ -17,7 +17,7 @@ function treeSearchBox(localizationService, searchService, $q) { }, restrict: "E", // restrict to an element replace: true, // replace the html element with the template - templateUrl: 'views/directives/umb-tree-search-box.html', + templateUrl: 'views/components/tree/umb-tree-search-box.html', link: function (scope, element, attrs, ctrl) { scope.term = ""; @@ -40,7 +40,7 @@ function treeSearchBox(localizationService, searchService, $q) { function performSearch() { if (scope.term) { scope.results = []; - + //a canceler exists, so perform the cancelation operation and reset if (canceler) { console.log("CANCELED!"); @@ -80,7 +80,7 @@ function treeSearchBox(localizationService, searchService, $q) { var searcher = searchService.searchContent; //search if (scope.section === "member") { - searcher = searchService.searchMembers; + searcher = searchService.searchMembers; } else if (scope.section === "media") { searcher = searchService.searchMedia; @@ -88,4 +88,4 @@ function treeSearchBox(localizationService, searchService, $q) { } }; } -angular.module('umbraco.directives').directive("umbTreeSearchBox", treeSearchBox); \ No newline at end of file +angular.module('umbraco.directives').directive("umbTreeSearchBox", treeSearchBox); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/umbtreesearchresults.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchresults.directive.js similarity index 82% rename from src/Umbraco.Web.UI.Client/src/common/directives/umbtreesearchresults.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchresults.directive.js index 9542deb5eb..bc74cbb13b 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/umbtreesearchresults.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchresults.directive.js @@ -13,10 +13,10 @@ function treeSearchResults() { }, restrict: "E", // restrict to an element replace: true, // replace the html element with the template - templateUrl: 'views/directives/umb-tree-search-results.html', + templateUrl: 'views/components/tree/umb-tree-search-results.html', link: function (scope, element, attrs, ctrl) { - + } }; } -angular.module('umbraco.directives').directive("umbTreeSearchResults", treeSearchResults); \ No newline at end of file +angular.module('umbraco.directives').directive("umbTreeSearchResults", treeSearchResults); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbGenerateAlias.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbGenerateAlias.directive.js new file mode 100644 index 0000000000..26164baccb --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbGenerateAlias.directive.js @@ -0,0 +1,80 @@ +angular.module("umbraco.directives") + .directive('umbGenerateAlias', function ($timeout, entityResource) { + return { + restrict: 'E', + templateUrl: 'views/components/umb-generate-alias.html', + replace: true, + scope: { + alias: '=', + aliasFrom: '=', + enableLock: '=?' + }, + link: function (scope, element, attrs, ctrl) { + + var eventBindings = []; + var bindWatcher = true; + var generateAliasTimeout = ""; + var updateAlias = false; + + scope.locked = true; + scope.placeholderText = "Enter alias..."; + + function generateAlias(value) { + + if (generateAliasTimeout) { + $timeout.cancel(generateAliasTimeout); + } + + if( value !== undefined && value !== "" && value !== null) { + + scope.alias = "Generating Alias..."; + + generateAliasTimeout = $timeout(function () { + updateAlias = true; + entityResource.getSafeAlias(value, true).then(function (safeAlias) { + if(updateAlias) { + scope.alias = safeAlias.alias; + } + }); + }, 500); + + } else { + updateAlias = true; + scope.alias = ""; + scope.placeholderText = "Enter alias..."; + } + + } + + // if alias gets unlocked - stop watching alias + eventBindings.push(scope.$watch('locked', function(newValue, oldValue){ + if(newValue === false) { + bindWatcher = false; + } + })); + + // validate custom entered alias + eventBindings.push(scope.$watch('alias', function(newValue, oldValue){ + + if(scope.alias === "" && bindWatcher === true || scope.alias === null && bindWatcher === true) { + // add watcher + eventBindings.push(scope.$watch('aliasFrom', function(newValue, oldValue) { + if(bindWatcher) { + generateAlias(newValue); + } + })); + } + + })); + + // clean up + scope.$on('$destroy', function(){ + // unbind watchers + for(var e in eventBindings) { + eventBindings[e](); + } + }); + + } + }; + }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbchildselector.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbchildselector.directive.js new file mode 100644 index 0000000000..fb60faaf54 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbchildselector.directive.js @@ -0,0 +1,108 @@ +(function() { + 'use strict'; + + function ChildSelectorDirective() { + + function link(scope, el, attr, ctrl) { + + var eventBindings = []; + scope.dialogModel = {}; + scope.showDialog = false; + + scope.removeChild = function(selectedChild, $index) { + if(scope.onRemove) { + scope.onRemove(selectedChild, $index); + } + }; + + scope.addChild = function($event) { + if(scope.onAdd) { + scope.onAdd($event); + } + }; + + function syncParentName() { + + // update name on available item + angular.forEach(scope.availableChildren, function(availableChild){ + if(availableChild.id === scope.parentId) { + availableChild.name = scope.parentName; + } + }); + + // update name on selected child + angular.forEach(scope.selectedChildren, function(selectedChild){ + if(selectedChild.id === scope.parentId) { + selectedChild.name = scope.parentName; + } + }); + + } + + function syncParentIcon() { + + // update icon on available item + angular.forEach(scope.availableChildren, function(availableChild){ + if(availableChild.id === scope.parentId) { + availableChild.icon = scope.parentIcon; + } + }); + + // update icon on selected child + angular.forEach(scope.selectedChildren, function(selectedChild){ + if(selectedChild.id === scope.parentId) { + selectedChild.icon = scope.parentIcon; + } + }); + + } + + eventBindings.push(scope.$watch('parentName', function(newValue, oldValue){ + + if (newValue === oldValue) { return; } + if ( oldValue === undefined || newValue === undefined) { return; } + + syncParentName(); + + })); + + eventBindings.push(scope.$watch('parentIcon', function(newValue, oldValue){ + + if (newValue === oldValue) { return; } + if ( oldValue === undefined || newValue === undefined) { return; } + + syncParentIcon(); + })); + + // clean up + scope.$on('$destroy', function(){ + // unbind watchers + for(var e in eventBindings) { + eventBindings[e](); + } + }); + + } + + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/umb-child-selector.html', + scope: { + selectedChildren: '=', + availableChildren: "=", + parentName: "=", + parentIcon: "=", + parentId: "=", + onRemove: "=", + onAdd: "=" + }, + link: link + }; + + return directive; + } + + angular.module('umbraco.directives').directive('umbChildSelector', ChildSelectorDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/umbconfirm.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbconfirm.directive.js similarity index 83% rename from src/Umbraco.Web.UI.Client/src/common/directives/umbconfirm.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/components/umbconfirm.directive.js index d1c9015ee3..0c618d9de3 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/umbconfirm.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbconfirm.directive.js @@ -4,22 +4,22 @@ * @function * @description * A confirmation dialog - * + * * @restrict E */ function confirmDirective() { return { restrict: "E", // restrict to an element replace: true, // replace the html element with the template - templateUrl: 'views/directives/umb-confirm.html', + templateUrl: 'views/components/umb-confirm.html', scope: { onConfirm: '=', onCancel: '=', caption: '@' }, link: function (scope, element, attr, ctrl) { - + } }; } -angular.module('umbraco.directives').directive("umbConfirm", confirmDirective); \ No newline at end of file +angular.module('umbraco.directives').directive("umbConfirm", confirmDirective); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbconfirmaction.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbconfirmaction.directive.js new file mode 100644 index 0000000000..df3e431620 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbconfirmaction.directive.js @@ -0,0 +1,39 @@ +(function() { + 'use strict'; + + function ConfirmAction() { + + function link(scope, el, attr, ctrl) { + + scope.clickConfirm = function() { + if(scope.onConfirm) { + scope.onConfirm(); + } + }; + + scope.clickCancel = function() { + if(scope.onCancel) { + scope.onCancel(); + } + }; + + } + + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/umb-confirm-action.html', + scope: { + direction: "@", + onConfirm: "&", + onCancel: "&" + }, + link: link + }; + + return directive; + } + + angular.module('umbraco.directives').directive('umbConfirmAction', ConfirmAction); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcontentgrid.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcontentgrid.directive.js new file mode 100644 index 0000000000..0d8ddc2170 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcontentgrid.directive.js @@ -0,0 +1,41 @@ +(function() { + 'use strict'; + + function ContentGridDirective() { + + function link(scope, el, attr, ctrl) { + + scope.clickItem = function(item) { + if(scope.onClick) { + scope.onClick(item); + } + }; + + scope.selectItem = function(item, $event, $index) { + if(scope.onSelect) { + scope.onSelect(item, $event, $index); + $event.stopPropagation(); + } + }; + + } + + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/umb-content-grid.html', + scope: { + content: '=', + contentProperties: "=", + onSelect: '=', + onClick: "=" + }, + link: link + }; + + return directive; + } + + angular.module('umbraco.directives').directive('umbContentGrid', ContentGridDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbfoldergrid.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbfoldergrid.directive.js new file mode 100644 index 0000000000..06475e9c45 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbfoldergrid.directive.js @@ -0,0 +1,40 @@ +(function() { + 'use strict'; + + function FolderGridDirective() { + + function link(scope, el, attr, ctrl) { + + scope.clickFolder = function(folder) { + if(scope.onClick) { + scope.onClick(folder); + } + }; + + scope.selectFolder = function(folder, $event, $index) { + if(scope.onSelect) { + scope.onSelect(folder, $event, $index); + } + $event.stopPropagation(); + }; + + } + + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/umb-folder-grid.html', + scope: { + folders: '=', + onSelect: '=', + onClick: "=" + }, + link: link + }; + + return directive; + } + + angular.module('umbraco.directives').directive('umbFolderGrid', FolderGridDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgridselector.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgridselector.directive.js new file mode 100644 index 0000000000..f8bdb50e32 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgridselector.directive.js @@ -0,0 +1,157 @@ +(function() { + 'use strict'; + + function GridSelector() { + + function link(scope, el, attr, ctrl) { + + var eventBindings = []; + scope.dialogModel = {}; + scope.showDialog = false; + scope.itemLabel = ""; + + // set default item name + if(!scope.itemName){ + scope.itemLabel = "item"; + } else { + scope.itemLabel = scope.itemName; + } + + scope.removeItem = function(selectedItem) { + var selectedItemIndex = scope.selectedItems.indexOf(selectedItem); + scope.selectedItems.splice(selectedItemIndex, 1); + }; + + scope.removeDefaultItem = function() { + + // it will be the last item so we can clear the array + scope.selectedItems = []; + + // remove as default item + scope.defaultItem = null; + + }; + + scope.openItemPicker = function($event){ + scope.dialogModel = { + view: "itempicker", + title: "Choose " + scope.itemLabel, + availableItems: scope.availableItems, + selectedItems: scope.selectedItems, + event: $event, + show: true, + submit: function(model) { + scope.selectedItems.push(model.selectedItem); + + // if no default item - set item as default + if(scope.defaultItem === null) { + scope.setAsDefaultItem(model.selectedItem); + } + + scope.dialogModel.show = false; + scope.dialogModel = null; + } + }; + }; + + scope.setAsDefaultItem = function(selectedItem) { + + // clear default item + scope.defaultItem = {}; + + // set as default item + scope.defaultItem = selectedItem; + }; + + function updatePlaceholders() { + + // update default item + if(scope.defaultItem !== null && scope.defaultItem.placeholder) { + + scope.defaultItem.name = scope.name; + + if(scope.alias !== null && scope.alias !== undefined) { + scope.defaultItem.alias = scope.alias; + } + + } + + // update selected items + angular.forEach(scope.selectedItems, function(selectedItem) { + if(selectedItem.placeholder) { + + selectedItem.name = scope.name; + + if(scope.alias !== null && scope.alias !== undefined) { + selectedItem.alias = scope.alias; + } + + } + }); + + // update availableItems + angular.forEach(scope.availableItems, function(availableItem) { + if(availableItem.placeholder) { + + availableItem.name = scope.name; + + if(scope.alias !== null && scope.alias !== undefined) { + availableItem.alias = scope.alias; + } + + } + }); + + } + + function activate() { + + // add watchers for updating placeholde name and alias + if(scope.updatePlaceholder) { + eventBindings.push(scope.$watch('name', function(newValue, oldValue){ + updatePlaceholders(); + })); + + eventBindings.push(scope.$watch('alias', function(newValue, oldValue){ + updatePlaceholders(); + })); + } + + } + + activate(); + + // clean up + scope.$on('$destroy', function(){ + + // clear watchers + for(var e in eventBindings) { + eventBindings[e](); + } + + }); + + } + + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/umb-grid-selector.html', + scope: { + name: "=", + alias: "=", + selectedItems: '=', + availableItems: "=", + defaultItem: "=", + itemName: "@", + updatePlaceholder: "=" + }, + link: link + }; + + return directive; + } + + angular.module('umbraco.directives').directive('umbGridSelector', GridSelector); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js new file mode 100644 index 0000000000..d18251da0b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js @@ -0,0 +1,516 @@ +(function() { + 'use strict'; + + function GroupsBuilderDirective(contentTypeHelper, contentTypeResource, mediaTypeResource, dataTypeHelper, dataTypeResource, $filter) { + + function link(scope, el, attr, ctrl) { + + scope.sortingMode = false; + scope.toolbar = []; + scope.sortableOptionsGroup = {}; + scope.sortableOptionsProperty = {}; + + function activate() { + + setToolbar(); + + setSortingOptions(); + + // set placeholder property on each group + if (scope.model.groups.length !== 0) { + angular.forEach(scope.model.groups, function(group) { + addInitProperty(group); + }); + } + + // add init tab + addInitGroup(scope.model.groups); + + } + + function setToolbar() { + + scope.toolbar = []; + + var compositionTool = { + "name": "Compositions", + "icon": "icon-merge", + "action": function(tool) { + scope.openCompositionsDialog(tool); + } + }; + + var sortingTool = { + "name": "Reorder", + "icon": "icon-navigation", + "action": function(tool) { + scope.toggleSortingMode(tool); + } + }; + + if(scope.compositions || scope.compositions === undefined) { + scope.toolbar.push(compositionTool); + } + + if(scope.sorting || scope.sorting === undefined) { + scope.toolbar.push(sortingTool); + } + + } + + function setSortingOptions() { + + scope.sortableOptionsGroup = { + distance: 10, + tolerance: "pointer", + opacity: 0.7, + scroll: true, + cursor: "move", + placeholder: "umb-group-builder__group-sortable-placeholder", + zIndex: 6000, + handle: ".umb-group-builder__group-handle", + items: ".umb-group-builder__group-sortable", + start: function(e, ui) { + ui.placeholder.height(ui.item.height()); + }, + stop: function(e, ui) { + updateTabsSortOrder(); + }, + }; + + scope.sortableOptionsProperty = { + distance: 10, + tolerance: "pointer", + connectWith: ".umb-group-builder__properties", + opacity: 0.7, + scroll: true, + cursor: "move", + placeholder: "umb-group-builder__property_sortable-placeholder", + zIndex: 6000, + handle: ".umb-group-builder__property-handle", + items: ".umb-group-builder__property-sortable", + start: function(e, ui) { + ui.placeholder.height(ui.item.height()); + }, + stop: function(e, ui) { + updatePropertiesSortOrder(); + } + }; + + } + + function updateTabsSortOrder() { + + var first = true; + var prevSortOrder = 0; + + scope.model.groups.map(function(group){ + + var index = scope.model.groups.indexOf(group); + + if(group.tabState !== "init") { + + // set the first not inherited tab to sort order 0 + if(!group.inherited && first) { + + // set the first tab sort order to 0 if prev is 0 + if( prevSortOrder === 0 ) { + group.sortOrder = 0; + // when the first tab is inherited and sort order is not 0 + } else { + group.sortOrder = prevSortOrder + 1; + } + + first = false; + + } else if(!group.inherited && !first) { + + // find next group + var nextGroup = scope.model.groups[index + 1]; + + // if a groups is dropped in the middle of to groups with + // same sort order. Give it the dropped group same sort order + if( prevSortOrder === nextGroup.sortOrder ) { + group.sortOrder = prevSortOrder; + } else { + group.sortOrder = prevSortOrder + 1; + } + + } + + // store this tabs sort order as reference for the next + prevSortOrder = group.sortOrder; + + } + + }); + + } + + function updatePropertiesSortOrder() { + + angular.forEach(scope.model.groups, function(group){ + if( group.tabState !== "init" ) { + group.properties = contentTypeHelper.updatePropertiesSortOrder(group.properties); + } + }); + + } + + /* ---------- DELETE PROMT ---------- */ + + scope.togglePrompt = function (object) { + object.deletePrompt = !object.deletePrompt; + }; + + scope.hidePrompt = function (object) { + object.deletePrompt = false; + }; + + /* ---------- TOOLBAR ---------- */ + + scope.toggleSortingMode = function(tool) { + + scope.sortingMode = !scope.sortingMode; + + if(scope.sortingMode === true) { + tool.name = "I'm done reordering"; + } else { + tool.name = "Reorder"; + } + + }; + + scope.openCompositionsDialog = function() { + scope.compositionsDialogModel = {}; + scope.compositionsDialogModel.title = "Compositions"; + scope.compositionsDialogModel.contentType = scope.model; + scope.compositionsDialogModel.availableCompositeContentTypes = scope.model.availableCompositeContentTypes; + scope.compositionsDialogModel.compositeContentTypes = scope.model.compositeContentTypes; + scope.compositionsDialogModel.view = "views/common/overlays/contenttypeeditor/compositions/compositions.html"; + scope.compositionsDialogModel.show = true; + + scope.compositionsDialogModel.submit = function(model) { + + // make sure that all tabs has an init property + if (scope.model.groups.length !== 0) { + angular.forEach(scope.model.groups, function(group) { + addInitProperty(group); + }); + } + + // remove overlay + scope.compositionsDialogModel.show = false; + scope.compositionsDialogModel = null; + }; + + scope.compositionsDialogModel.close = function(oldModel) { + // reset composition changes + scope.model.groups = oldModel.contentType.groups; + scope.model.compositeContentTypes = oldModel.contentType.compositeContentTypes; + + // remove overlay + scope.compositionsDialogModel.show = false; + scope.compositionsDialogModel = null; + }; + + scope.compositionsDialogModel.selectCompositeContentType = function(compositeContentType) { + + if (scope.model.compositeContentTypes.indexOf(compositeContentType.alias) === -1) { + //merge composition with content type + + if(scope.contentType === "documentType") { + + contentTypeResource.getById(compositeContentType.id).then(function(composition){ + contentTypeHelper.mergeCompositeContentType(scope.model, composition); + }); + + + } else if(scope.contentType === "mediaType") { + + mediaTypeResource.getById(compositeContentType.id).then(function(composition){ + contentTypeHelper.mergeCompositeContentType(scope.model, composition); + }); + + } + + + } else { + // split composition from content type + contentTypeHelper.splitCompositeContentType(scope.model, compositeContentType); + } + + }; + + }; + + + /* ---------- GROUPS ---------- */ + + scope.addGroup = function(group) { + + // set group sort order + var index = scope.model.groups.indexOf(group); + var prevGroup = scope.model.groups[index - 1]; + + if( index > 0) { + // set index to 1 higher than the previous groups sort order + group.sortOrder = prevGroup.sortOrder + 1; + + } else { + // first group - sort order will be 0 + group.sortOrder = 0; + } + + // activate group + scope.activateGroup(group); + + }; + + scope.activateGroup = function(selectedGroup) { + + // set all other groups that are inactive to active + angular.forEach(scope.model.groups, function(group) { + // skip init tab + if (group.tabState !== "init") { + group.tabState = "inActive"; + } + }); + + selectedGroup.tabState = "active"; + + }; + + scope.removeGroup = function(groupIndex) { + scope.model.groups.splice(groupIndex, 1); + }; + + scope.updateGroupTitle = function(group) { + if (group.properties.length === 0) { + addInitProperty(group); + } + }; + + scope.changeSortOrderValue = function() { + scope.model.groups = $filter('orderBy')(scope.model.groups, 'sortOrder'); + }; + + function addInitGroup(groups) { + + // check i init tab already exists + var addGroup = true; + + angular.forEach(groups, function(group) { + if (group.tabState === "init") { + addGroup = false; + } + }); + + if (addGroup) { + groups.push({ + properties: [], + parentTabContentTypes: [], + parentTabContentTypeNames: [], + name: "", + tabState: "init" + }); + } + + return groups; + } + + /* ---------- PROPERTIES ---------- */ + + scope.addProperty = function(property, group) { + + // set property sort order + var index = group.properties.indexOf(property); + var prevProperty = group.properties[index - 1]; + + if( index > 0) { + // set index to 1 higher than the previous property sort order + property.sortOrder = prevProperty.sortOrder + 1; + + } else { + // first property - sort order will be 0 + property.sortOrder = 0; + } + + // open property settings dialog + scope.editPropertyTypeSettings(property, group); + + }; + + scope.editPropertyTypeSettings = function(property, group) { + + if (!property.inherited) { + + scope.propertySettingsDialogModel = {}; + scope.propertySettingsDialogModel.title = "Property settings"; + scope.propertySettingsDialogModel.property = property; + scope.propertySettingsDialogModel.contentTypeName = scope.model.name; + scope.propertySettingsDialogModel.view = "views/common/overlays/contenttypeeditor/propertysettings/propertysettings.html"; + scope.propertySettingsDialogModel.show = true; + + // set state to active to access the preview + property.propertyState = "active"; + + // set property states + property.dialogIsOpen = true; + + scope.propertySettingsDialogModel.submit = function(model) { + + property.inherited = false; + property.dialogIsOpen = false; + + // update existing data types + if(model.updateSameDataTypes) { + updateSameDataTypes(property); + } + + // remove dialog + scope.propertySettingsDialogModel.show = false; + scope.propertySettingsDialogModel = null; + + // push new init property to group + addInitProperty(group); + + // set focus on init property + var numberOfProperties = group.properties.length; + group.properties[numberOfProperties - 1].focus = true; + + // push new init tab to the scope + addInitGroup(scope.model.groups); + + }; + + scope.propertySettingsDialogModel.close = function(oldModel) { + + // reset all property changes + property.label = oldModel.property.label; + property.alias = oldModel.property.alias; + property.description = oldModel.property.description; + property.config = oldModel.property.config; + property.editor = oldModel.property.editor; + property.view = oldModel.property.view; + property.dataTypeId = oldModel.property.dataTypeId; + property.dataTypeIcon = oldModel.property.dataTypeIcon; + property.dataTypeName = oldModel.property.dataTypeName; + + // because we set state to active, to show a preview, we have to check if has been filled out + // label is required so if it is not filled we know it is a placeholder + if(oldModel.property.editor === undefined || oldModel.property.editor === null || oldModel.property.editor === "") { + property.propertyState = "init"; + } else { + property.propertyState = oldModel.property.propertyState; + } + + // remove dialog + scope.propertySettingsDialogModel.show = false; + scope.propertySettingsDialogModel = null; + + }; + + } + }; + + scope.deleteProperty = function(tab, propertyIndex) { + + // remove property + tab.properties.splice(propertyIndex, 1); + + // if the last property in group is an placeholder - remove add new tab placeholder + if(tab.properties.length === 1 && tab.properties[0].propertyState === "init") { + + angular.forEach(scope.model.groups, function(group, index, groups){ + if(group.tabState === 'init') { + groups.splice(index, 1); + } + }); + + } + + }; + + function addInitProperty(group) { + + var addInitPropertyBool = true; + var initProperty = { + label: null, + alias: null, + propertyState: "init", + validation: { + mandatory: false, + pattern: null + } + }; + + // check if there already is an init property + angular.forEach(group.properties, function(property) { + if (property.propertyState === "init") { + addInitPropertyBool = false; + } + }); + + if (addInitPropertyBool) { + group.properties.push(initProperty); + } + + return group; + } + + function updateSameDataTypes(newProperty) { + + // find each property + angular.forEach(scope.model.groups, function(group){ + angular.forEach(group.properties, function(property){ + + if(property.dataTypeId === newProperty.dataTypeId) { + + // update property data + property.config = newProperty.config; + property.editor = newProperty.editor; + property.view = newProperty.view; + property.dataTypeId = newProperty.dataTypeId; + property.dataTypeIcon = newProperty.dataTypeIcon; + property.dataTypeName = newProperty.dataTypeName; + + } + + }); + }); + } + + + var unbindModelWatcher = scope.$watch('model', function(newValue, oldValue) { + if (newValue !== undefined && newValue.groups !== undefined) { + activate(); + } + }); + + // clean up + scope.$on('$destroy', function(){ + unbindModelWatcher(); + }); + + } + + var directive = { + restrict: "E", + replace: true, + templateUrl: "views/components/umb-groups-builder.html", + scope: { + model: "=", + compositions: "=", + sorting: "=", + contentType: "@" + }, + link: link + }; + + return directive; + } + + angular.module('umbraco.directives').directive('umbGroupsBuilder', GroupsBuilderDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbkeyboardshortcutsoverview.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbkeyboardshortcutsoverview.directive.js new file mode 100644 index 0000000000..127bf38f49 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbkeyboardshortcutsoverview.directive.js @@ -0,0 +1,31 @@ +(function() { + 'use strict'; + + function KeyboardShortcutsOverviewDirective() { + + function link(scope, el, attr, ctrl) { + + scope.shortcutOverlay = false; + + scope.toggleShortcutsOverlay = function() { + scope.shortcutOverlay = !scope.shortcutOverlay; + }; + + } + + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/umb-keyboard-shortcuts-overview.html', + link: link, + scope: { + model: "=" + } + }; + + return directive; + } + + angular.module('umbraco.directives').directive('umbKeyboardShortcutsOverview', KeyboardShortcutsOverviewDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/editors/umblaunchminieditor.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblaunchminieditor.directive.js similarity index 100% rename from src/Umbraco.Web.UI.Client/src/common/directives/editors/umblaunchminieditor.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/components/umblaunchminieditor.directive.js diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umblayoutselector.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblayoutselector.directive.js new file mode 100644 index 0000000000..48291bfa87 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblayoutselector.directive.js @@ -0,0 +1,93 @@ +(function() { + 'use strict'; + + function LayoutSelectorDirective() { + + function link(scope, el, attr, ctrl) { + + scope.layoutDropDownIsOpen = false; + scope.showLayoutSelector = true; + + function activate() { + + setVisibility(); + + setActiveLayout(scope.layouts); + + } + + function setVisibility() { + + var numberOfAllowedLayouts = getNumberOfAllowedLayouts(scope.layouts); + + if(numberOfAllowedLayouts === 1) { + scope.showLayoutSelector = false; + } + + } + + function getNumberOfAllowedLayouts(layouts) { + + var allowedLayouts = 0; + + for (var i = 0; layouts.length > i; i++) { + + var layout = layouts[i]; + + if(layout.selected === true) { + allowedLayouts++; + } + + } + + return allowedLayouts; + } + + function setActiveLayout(layouts) { + + for (var i = 0; layouts.length > i; i++) { + var layout = layouts[i]; + if(layout.path === scope.activeLayout.path) { + layout.active = true; + } + } + + } + + scope.pickLayout = function(selectedLayout) { + if(scope.onLayoutSelect) { + scope.onLayoutSelect(selectedLayout); + scope.layoutDropDownIsOpen = false; + } + }; + + scope.toggleLayoutDropdown = function() { + scope.layoutDropDownIsOpen = !scope.layoutDropDownIsOpen; + }; + + scope.closeLayoutDropdown = function() { + scope.layoutDropDownIsOpen = false; + }; + + activate(); + + } + + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/umb-layout-selector.html', + scope: { + layouts: '=', + activeLayout: '=', + onLayoutSelect: "=" + }, + link: link + }; + + return directive; + } + + angular.module('umbraco.directives').directive('umbLayoutSelector', LayoutSelectorDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umblistviewlayout.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblistviewlayout.directive.js new file mode 100644 index 0000000000..d026a3f4e5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblistviewlayout.directive.js @@ -0,0 +1,37 @@ +(function() { + 'use strict'; + + function ListViewLayoutDirective() { + + function link(scope, el, attr, ctrl) { + + scope.getContent = function(contentId) { + if(scope.onGetContent) { + scope.onGetContent(contentId); + } + }; + + } + + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/umb-list-view-layout.html', + scope: { + contentId: '=', + folders: '=', + items: '=', + selection: '=', + options: '=', + entityType: '@', + onGetContent: '=' + }, + link: link + }; + + return directive; + } + + angular.module('umbraco.directives').directive('umbListViewLayout', ListViewLayoutDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umblistviewsettings.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblistviewsettings.directive.js new file mode 100644 index 0000000000..ae9da02237 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblistviewsettings.directive.js @@ -0,0 +1,153 @@ +(function() { + 'use strict'; + + function ListViewSettingsDirective(contentTypeResource, dataTypeResource, dataTypeHelper) { + + function link(scope, el, attr, ctrl) { + + scope.dataType = {}; + scope.editDataTypeSettings = false; + scope.customListViewCreated = false; + + /* ---------- INIT ---------- */ + + function activate() { + + if(scope.enableListView) { + + dataTypeResource.getByName(scope.listViewName) + .then(function(dataType) { + + scope.dataType = dataType; + + scope.customListViewCreated = checkForCustomListView(); + + }); + + } else { + + scope.dataType = {}; + + } + + } + + /* ----------- LIST VIEW SETTINGS --------- */ + + scope.toggleEditListViewDataTypeSettings = function() { + scope.editDataTypeSettings = !scope.editDataTypeSettings; + }; + + scope.saveListViewDataType = function() { + + var preValues = dataTypeHelper.createPreValueProps(scope.dataType.preValues); + + dataTypeResource.save(scope.dataType, preValues, false).then(function(dataType) { + + // store data type + scope.dataType = dataType; + + // hide settings panel + scope.editDataTypeSettings = false; + + }); + + }; + + + /* ---------- CUSTOM LIST VIEW ---------- */ + + scope.createCustomListViewDataType = function() { + + dataTypeResource.createCustomListView(scope.modelAlias).then(function(dataType) { + + // store data type + scope.dataType = dataType; + + // set list view name on scope + scope.listViewName = dataType.name; + + // change state to custom list view + scope.customListViewCreated = true; + + // show settings panel + scope.editDataTypeSettings = true; + + }); + + }; + + scope.removeCustomListDataType = function() { + + scope.editDataTypeSettings = false; + + // delete custom list view data type + dataTypeResource.deleteById(scope.dataType.id).then(function(dataType) { + + // set list view name on scope + if(scope.contentType === "documentType") { + + scope.listViewName = "List View - Content"; + + } else if(scope.contentType === "mediaType") { + + scope.listViewName = "List View - Media"; + + } + + // get default data type + dataTypeResource.getByName(scope.listViewName) + .then(function(dataType) { + + // store data type + scope.dataType = dataType; + + // change state to default list view + scope.customListViewCreated = false; + + }); + }); + + }; + + /* ----------- SCOPE WATCHERS ----------- */ + var unbindEnableListViewWatcher = scope.$watch('enableListView', function(newValue, oldValue){ + + if(newValue !== undefined) { + activate(); + } + + }); + + // clean up + scope.$on('$destroy', function(){ + unbindEnableListViewWatcher(); + }); + + /* ----------- METHODS ---------- */ + + function checkForCustomListView() { + return scope.dataType.name === "List View - " + scope.modelAlias; + } + + } + + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/umb-list-view-settings.html', + scope: { + enableListView: "=", + listViewName: "=", + modelAlias: "=", + contentType: "@" + }, + link: link + }; + + return directive; + } + + angular.module('umbraco.directives').directive('umbListViewSettings', ListViewSettingsDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbloadindicator.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbloadindicator.directive.js new file mode 100644 index 0000000000..efa95f1fbd --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbloadindicator.directive.js @@ -0,0 +1,17 @@ +(function() { + 'use strict'; + + function UmbLoadIndicatorDirective() { + + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/umb-load-indicator.html' + }; + + return directive; + } + + angular.module('umbraco.directives').directive('umbLoadIndicator', UmbLoadIndicatorDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umblockedfield.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblockedfield.directive.js new file mode 100644 index 0000000000..04fde87de5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblockedfield.directive.js @@ -0,0 +1,97 @@ +/** +* @ngdoc directive +* @name umbraco.directives.directive:umbContentName +* @restrict E +* @function +* @description +* Used by editors that require naming an entity. Shows a textbox/headline with a required validator within it's own form. +**/ +(function() { + 'use strict'; + + function LockedFieldDirective($timeout, localizationService) { + + function link(scope, el, attr, ngModel) { + + var input = el.find('.umb-locked-field__input'); + + function activate() { + + // if locked state is not defined as an attr set default state + if (scope.locked === undefined || scope.locked === null) { + scope.locked = true; + } + + // if regex validation is not defined as an attr set default state + // if this is set to an empty string then regex validation can be ignored. + if (scope.regexValidation === undefined || scope.regexValidation === null) { + scope.regexValidation = "^[a-zA-Z]\\w.*$"; + } + + if (scope.serverValidationField === undefined || scope.serverValidationField === null) { + scope.serverValidationField = ""; + } + + // if locked state is not defined as an attr set default state + if (scope.placeholderText === undefined || scope.placeholderText === null) { + scope.placeholderText = "Enter value..."; + } + + } + + scope.lock = function() { + scope.locked = true; + input.unbind("blur"); + }; + + scope.unlock = function() { + scope.locked = false; + autoFocusField(); + }; + + function autoFocusField() { + + var onBlurHandler = function() { + scope.$apply(function(){ + scope.lock(); + }); + }; + + $timeout(function() { + input.focus(); + input.select(); + input.on("blur", onBlurHandler); + }); + + } + + activate(); + + scope.$on('$destroy', function() { + input.unbind('blur'); + }); + + } + + var directive = { + require: "ngModel", + restrict: 'E', + replace: true, + templateUrl: 'views/components/umb-locked-field.html', + scope: { + model: '=ngModel', + locked: "=?", + placeholderText: "=?", + regexValidation: "=?", + serverValidationField: "@" + }, + link: link + }; + + return directive; + + } + + angular.module('umbraco.directives').directive('umbLockedField', LockedFieldDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js new file mode 100644 index 0000000000..2626fc7f33 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js @@ -0,0 +1,162 @@ +(function() { + 'use strict'; + + function MediaGridDirective($filter, mediaHelper) { + + function link(scope, el, attr, ctrl) { + + var itemDefaultHeight = 200; + var itemDefaultWidth = 200; + var itemMaxWidth = 300; + var itemMaxHeight = 300; + + function activate() { + + for (var i = 0; scope.items.length > i; i++) { + var item = scope.items[i]; + setItemData(item); + setOriginalSize(item, itemMaxHeight); + } + + if(scope.items.length > 0) { + setFlexValues(scope.items); + } + + } + + function setItemData(item) { + item.isFolder = !mediaHelper.hasFilePropertyType(item); + if(!item.isFolder){ + item.thumbnail = mediaHelper.resolveFile(item, true); + item.image = mediaHelper.resolveFile(item, false); + } + } + + function setOriginalSize(item, maxHeight) { + + //set to a square by default + item.width = itemDefaultWidth; + item.height = itemDefaultHeight; + item.aspectRatio = 1; + + var widthProp = _.find(item.properties, function(v) { return (v.alias === "umbracoWidth"); }); + + if (widthProp && widthProp.value) { + item.width = parseInt(widthProp.value, 10); + if (isNaN(item.width)) { + item.width = itemDefaultWidth; + } + } + + var heightProp = _.find(item.properties, function(v) { return (v.alias === "umbracoHeight"); }); + + if (heightProp && heightProp.value) { + item.height = parseInt(heightProp.value, 10); + if (isNaN(item.height)) { + item.height = itemDefaultWidth; + } + } + + item.aspectRatio = item.width / item.height; + + // set max width and height + if(item.width > itemMaxWidth) { + item.width = itemMaxWidth; + item.height = itemMaxWidth / item.aspectRatio; + } + + if(item.height > itemMaxHeight) { + item.height = itemMaxHeight; + item.width = itemMaxHeight * item.aspectRatio; + } + + } + + function setFlexValues(mediaItems) { + + var flexSortArray = mediaItems; + var smallestImageWidth = null; + var widestImageAspectRatio = null; + + // sort array after image width with the widest image first + flexSortArray = $filter('orderBy')(flexSortArray, 'width', true); + + // find widest image aspect ratio + widestImageAspectRatio = flexSortArray[0].aspectRatio; + + // find smallest image width + smallestImageWidth = flexSortArray[flexSortArray.length - 1].width; + + for (var i = 0; flexSortArray.length > i; i++) { + + var mediaItem = flexSortArray[i]; + var flex = 1 / (widestImageAspectRatio / mediaItem.aspectRatio); + + if (flex === 0) { + flex = 1; + } + + var imageMinWidth = smallestImageWidth * flex; + + var flexStyle = { + "flex": flex + " 1 " + imageMinWidth + "px", + "max-width": mediaItem.width + "px" + }; + + mediaItem.flexStyle = flexStyle; + + } + + } + + scope.selectItem = function(item, $event, $index) { + if(scope.onSelect) { + scope.onSelect(item, $event, $index); + $event.stopPropagation(); + } + }; + + scope.clickItem = function(item) { + if(scope.onClick) { + scope.onClick(item); + } + }; + + scope.hoverItemDetails = function(item, $event, hover) { + if(scope.onDetailsHover) { + scope.onDetailsHover(item, $event, hover); + } + }; + + var unbindItemsWatcher = scope.$watch('items', function(newValue, oldValue){ + if(angular.isArray(newValue)) { + activate(); + } + }); + + scope.$on('$destroy', function(){ + unbindItemsWatcher(); + }); + + } + + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/umb-media-grid.html', + scope: { + items: '=', + onDetailsHover: "=", + onSelect: '=', + onClick: '=', + filterBy: "=" + }, + link: link + }; + + return directive; + } + + angular.module('umbraco.directives').directive('umbMediaGrid', MediaGridDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbpagination.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbpagination.directive.js new file mode 100644 index 0000000000..18dd616f75 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbpagination.directive.js @@ -0,0 +1,105 @@ +(function() { + 'use strict'; + + function PaginationDirective() { + + function link(scope, el, attr, ctrl) { + + function activate() { + + scope.pagination = []; + + var i = 0; + + if (scope.totalPages <= 10) { + for (i = 0; i < scope.totalPages; i++) { + scope.pagination.push({ + val: (i + 1), + isActive: scope.pageNumber === (i + 1) + }); + } + } + else { + //if there is more than 10 pages, we need to do some fancy bits + + //get the max index to start + var maxIndex = scope.totalPages - 10; + //set the start, but it can't be below zero + var start = Math.max(scope.pageNumber - 5, 0); + //ensure that it's not too far either + start = Math.min(maxIndex, start); + + for (i = start; i < (10 + start) ; i++) { + scope.pagination.push({ + val: (i + 1), + isActive: scope.pageNumber === (i + 1) + }); + } + + //now, if the start is greater than 0 then '1' will not be displayed, so do the elipses thing + if (start > 0) { + scope.pagination.unshift({ name: "First", val: 1, isActive: false }, {val: "...",isActive: false}); + } + + //same for the end + if (start < maxIndex) { + scope.pagination.push({ val: "...", isActive: false }, { name: "Last", val: scope.totalPages, isActive: false }); + } + } + + } + + scope.next = function() { + if (scope.onNext && scope.pageNumber < scope.totalPages) { + scope.pageNumber++; + scope.onNext(scope.pageNumber); + } + }; + + scope.prev = function(pageNumber) { + if (scope.onPrev && scope.pageNumber > 1) { + scope.pageNumber--; + scope.onPrev(scope.pageNumber); + } + }; + + scope.goToPage = function(pageNumber) { + if(scope.onGoToPage) { + scope.pageNumber = pageNumber + 1; + scope.onGoToPage(scope.pageNumber); + } + }; + + var unbindPageNumberWatcher = scope.$watch('pageNumber', function(newValue, oldValue){ + activate(); + }); + + scope.$on('$destroy', function(){ + unbindPageNumberWatcher(); + }); + + activate(); + + } + + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/umb-pagination.html', + scope: { + pageNumber: "=", + totalPages: "=", + onNext: "=", + onPrev: "=", + onGoToPage: "=" + }, + link: link + }; + + return directive; + + } + + angular.module('umbraco.directives').directive('umbPagination', PaginationDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbstickybar.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbstickybar.directive.js new file mode 100644 index 0000000000..727dc5ec94 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbstickybar.directive.js @@ -0,0 +1,116 @@ +(function() { + 'use strict'; + + function StickyBarDirective($rootScope) { + + function link(scope, el, attr, ctrl) { + + var bar = $(el); + var scrollableContainer = null; + var clonedBar = null; + var cloneIsMade = false; + var barTop = bar.context.offsetTop; + + function activate() { + + if (attr.scrollableContainer) { + scrollableContainer = $(attr.scrollableContainer); + } else { + scrollableContainer = $(window); + } + + scrollableContainer.on('scroll.umbStickyBar', determineVisibility).trigger("scroll"); + $(window).on('resize.umbStickyBar', determineVisibility); + + scope.$on('$destroy', function() { + scrollableContainer.off('.umbStickyBar'); + $(window).off('.umbStickyBar'); + }); + + } + + function determineVisibility() { + + var scrollTop = scrollableContainer.scrollTop(); + + if (scrollTop > barTop) { + + if (!cloneIsMade) { + + createClone(); + + clonedBar.css({ + 'visibility': 'visible' + }); + + } else { + + calculateSize(); + + } + + } else { + + if (cloneIsMade) { + + //remove cloned element (switched places with original on creation) + bar.remove(); + bar = clonedBar; + clonedBar = null; + + bar.removeClass('-umb-sticky-bar'); + bar.css({ + position: 'relative', + 'width': 'auto', + 'height': 'auto', + 'z-index': 'auto', + 'visibility': 'visible' + }); + + cloneIsMade = false; + + } + + } + + } + + function calculateSize() { + clonedBar.css({ + width: bar.outerWidth(), + height: bar.height() + }); + } + + function createClone() { + //switch place with cloned element, to keep binding intact + clonedBar = bar; + bar = clonedBar.clone(); + clonedBar.after(bar); + clonedBar.addClass('-umb-sticky-bar'); + clonedBar.css({ + 'position': 'fixed', + 'z-index': 500, + 'visibility': 'hidden' + }); + + cloneIsMade = true; + calculateSize(); + + } + + activate(); + + } + + var directive = { + restrict: 'A', + link: link + }; + + return directive; + } + + angular.module('umbraco.directives').directive('umbStickyBar', StickyBarDirective); + +})(); 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 new file mode 100644 index 0000000000..fa57647a78 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbtable.directive.js @@ -0,0 +1,70 @@ +(function() { + 'use strict'; + + function TableDirective() { + + function link(scope, el, attr, ctrl) { + + scope.clickItem = function(item, $event) { + if(scope.onClick) { + scope.onClick(item); + $event.stopPropagation(); + } + }; + + scope.selectItem = function(item, $index, $event) { + if(scope.onSelect) { + scope.onSelect(item, $index, $event); + $event.stopPropagation(); + } + }; + + scope.selectAll = function($event) { + if(scope.onSelectAll) { + scope.onSelectAll($event); + } + }; + + scope.isSelectedAll = function() { + if(scope.onSelectedAll && scope.items && scope.items.length > 0) { + return scope.onSelectedAll(); + } + }; + + scope.isSortDirection = function (col, direction) { + if (scope.onSortingDirection) { + return scope.onSortingDirection(col, direction); + } + }; + + scope.sort = function(field, allow) { + if(scope.onSort) { + scope.onSort(field, allow); + } + }; + + } + + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/umb-table.html', + scope: { + items: '=', + itemProperties: "=", + onSelect: '=', + onClick: "=", + onSelectAll: "=", + onSelectedAll: "=", + onSortingDirection: "=", + onSort:"=" + }, + link: link + }; + + return directive; + } + + angular.module('umbraco.directives').directive('umbTable', TableDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbtooltip.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbtooltip.directive.js new file mode 100644 index 0000000000..4dd539d147 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbtooltip.directive.js @@ -0,0 +1,82 @@ +(function() { + 'use strict'; + + function TooltipDirective($timeout) { + + function link(scope, el, attr, ctrl) { + + scope.tooltipStyles = {}; + scope.tooltipStyles.left = 0; + scope.tooltipStyles.top = 0; + + function activate() { + + $timeout(function() { + setTooltipPosition(scope.event); + }); + + } + + function setTooltipPosition(event) { + + var viewportWidth = null; + var viewportHeight = null; + var elementHeight = null; + var elementWidth = null; + + var position = { + right: "inherit", + left: "inherit", + top: "inherit", + bottom: "inherit" + }; + + // viewport size + viewportWidth = $(window).innerWidth(); + viewportHeight = $(window).innerHeight(); + + // element size + elementHeight = el.context.clientHeight; + elementWidth = el.context.clientWidth; + + position.left = event.pageX - (elementWidth / 2); + position.top = event.pageY; + + // check to see if element is outside screen + // outside right + if (position.left + elementWidth > viewportWidth) { + position.right = 0; + position.left = "inherit"; + } + + // outside bottom + if (position.top + elementHeight > viewportHeight) { + position.bottom = 0; + position.top = "inherit"; + } + + scope.tooltipStyles = position; + + } + + activate(); + + } + + var directive = { + restrict: 'E', + transclude: true, + replace: true, + templateUrl: 'views/components/umb-tooltip.html', + scope: { + event: "=" + }, + link: link + }; + + return directive; + } + + angular.module('umbraco.directives').directive('umbTooltip', TooltipDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfiledropzone.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfiledropzone.directive.js new file mode 100644 index 0000000000..d33a8edb48 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfiledropzone.directive.js @@ -0,0 +1,221 @@ +/** +* @ngdoc directive +* @name umbraco.directives.directive:umbContentName +* @restrict E +* @function +* @description +* Used by editors that require naming an entity. Shows a textbox/headline with a required validator within it's own form. +**/ + +/* +TODO +.directive("umbFileDrop", function ($timeout, $upload, localizationService, umbRequestHelper){ + + return{ + restrict: "A", + link: function(scope, element, attrs){ + + //load in the options model + + + } + } +}) +*/ + +angular.module("umbraco.directives") + +.directive('umbFileDropzone', function ($timeout, Upload, localizationService, umbRequestHelper) { + return { + + restrict: 'E', + replace: true, + + templateUrl: 'views/components/upload/umb-file-dropzone.html', + + scope: { + parentId: '@', + contentTypeAlias: '@', + propertyAlias: '@', + accept: '@', + maxFileSize: '@', + + compact: '@', + hideDropzone: '@', + + filesQueued: '=', + handleFile: '=', + filesUploaded: '=' + }, + + link: function(scope, element, attrs) { + + scope.queue = []; + scope.done = []; + scope.rejected = []; + scope.currentFile = undefined; + + function _filterFile(file) { + + var ignoreFileNames = ['Thumbs.db']; + var ignoreFileTypes = ['directory']; + + // ignore files with names from the list + // ignore files with types from the list + // ignore files which starts with "." + if(ignoreFileNames.indexOf(file.name) === -1 && + ignoreFileTypes.indexOf(file.type) === -1 && + file.name.indexOf(".") !== 0) { + return true; + } else { + return false; + } + + } + + function _filesQueued(files, event){ + + //Push into the queue + angular.forEach(files, function(file){ + + if(_filterFile(file) === true) { + + if(file.$error) { + scope.rejected.push(file); + } else { + scope.queue.push(file); + } + + } + + }); + + //when queue is done, kick the uploader + if(!scope.working){ + _processQueueItem(); + } + } + + + function _processQueueItem(){ + + if(scope.queue.length > 0){ + scope.currentFile = scope.queue.shift(); + _upload(scope.currentFile); + }else if(scope.done.length > 0){ + + if(scope.filesUploaded){ + //queue is empty, trigger the done action + scope.filesUploaded(scope.done); + } + + //auto-clear the done queue after 3 secs + var currentLength = scope.done.length; + $timeout(function(){ + scope.done.splice(0, currentLength); + }, 3000); + } + } + + function _upload(file) { + + scope.propertyAlias = scope.propertyAlias ? scope.propertyAlias : "umbracoFile"; + scope.contentTypeAlias = scope.contentTypeAlias ? scope.contentTypeAlias : "Image"; + + Upload.upload({ + url: umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostAddFile"), + fields: { + 'currentFolder': scope.parentId, + 'contentTypeAlias': scope.contentTypeAlias, + 'propertyAlias': scope.propertyAlias, + 'path': file.path + }, + file: file + }).progress(function (evt) { + + // calculate progress in percentage + var progressPercentage = parseInt(100.0 * evt.loaded / evt.total, 10); + + // set percentage property on file + file.uploadProgress = progressPercentage; + + // set uploading status on file + file.uploadStatus = "uploading"; + + }).success(function (data, status, headers, config) { + + if(data.notifications && data.notifications.length > 0) { + + // set error status on file + file.uploadStatus = "error"; + + // Throw message back to user with the cause of the error + file.serverErrorMessage = data.notifications[0].message; + + // Put the file in the rejected pool + scope.rejected.push(file); + + } else { + + // set done status on file + file.uploadStatus = "done"; + + // set date/time for when done - used for sorting + file.doneDate = new Date(); + + // Put the file in the done pool + scope.done.push(file); + + } + + scope.currentFile = undefined; + + //after processing, test if everthing is done + _processQueueItem(); + + }).error( function (evt, status, headers, config) { + + // set status done + file.uploadStatus = "error"; + + //if the service returns a detailed error + if (evt.InnerException) { + file.serverErrorMessage = evt.InnerException.ExceptionMessage; + + //Check if its the common "too large file" exception + if (evt.InnerException.StackTrace && evt.InnerException.StackTrace.indexOf("ValidateRequestEntityLength") > 0) { + file.serverErrorMessage = "File too large to upload"; + } + + } else if (evt.Message) { + file.serverErrorMessage = evt.Message; + } + + // If file not found, server will return a 404 and display this message + if(status === 404 ) { + file.serverErrorMessage = "File not found"; + } + + //after processing, test if everthing is done + scope.rejected.push(file); + scope.currentFile = undefined; + + _processQueueItem(); + }); + } + + + scope.handleFiles = function(files, event){ + if(scope.filesQueued){ + scope.filesQueued(files, event); + } + + _filesQueued(files, event); + + }; + + } + + + }; + }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/umbfileupload.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfileupload.directive.js similarity index 100% rename from src/Umbraco.Web.UI.Client/src/common/directives/umbfileupload.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfileupload.directive.js diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/editors/umbsinglefileupload.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbsinglefileupload.directive.js similarity index 100% rename from src/Umbraco.Web.UI.Client/src/common/directives/editors/umbsinglefileupload.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbsinglefileupload.directive.js diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/nodirtycheck.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/nodirtycheck.directive.js similarity index 100% rename from src/Umbraco.Web.UI.Client/src/common/directives/validation/nodirtycheck.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/components/validation/nodirtycheck.directive.js diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valCustom.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valCustom.directive.js similarity index 99% rename from src/Umbraco.Web.UI.Client/src/common/directives/validation/valCustom.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valCustom.directive.js index a402065708..dac010a97f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valCustom.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valCustom.directive.js @@ -57,7 +57,7 @@ angular.module('umbraco.directives.validation') } }; validators[key] = validateFn; - ctrl.$formatters.push(validateFn); + ctrl.$parsers.push(validateFn); }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valHighlight.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valHighlight.directive.js similarity index 84% rename from src/Umbraco.Web.UI.Client/src/common/directives/validation/valHighlight.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valHighlight.directive.js index fdcf768947..599cda766c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valHighlight.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valHighlight.directive.js @@ -9,9 +9,7 @@ function valHighlight($timeout) { restrict: "A", link: function (scope, element, attrs, ctrl) { - scope.$watch(function() { - return scope.$eval(attrs.valHighlight); - }, function(newVal, oldVal) { + attrs.$observe("valHighlight", function (newVal) { if (newVal === true) { element.addClass("highlight-error"); $timeout(function () { @@ -23,7 +21,7 @@ function valHighlight($timeout) { element.removeClass("highlight-error"); } }); - + } }; } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valcompare.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valcompare.directive.js similarity index 100% rename from src/Umbraco.Web.UI.Client/src/common/directives/validation/valcompare.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valcompare.directive.js diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valemail.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valemail.directive.js similarity index 96% rename from src/Umbraco.Web.UI.Client/src/common/directives/validation/valemail.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valemail.directive.js index 88ffd6f0fa..1e81d8edec 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valemail.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valemail.directive.js @@ -29,7 +29,6 @@ function valEmail(valEmailExpression) { } }; - ctrl.$formatters.push(patternValidator); ctrl.$parsers.push(patternValidator); } }; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valformmanager.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valformmanager.directive.js similarity index 81% rename from src/Umbraco.Web.UI.Client/src/common/directives/validation/valformmanager.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valformmanager.directive.js index 506bba8990..37c0313c45 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valformmanager.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valformmanager.directive.js @@ -16,6 +16,27 @@ function valFormManager(serverValidationManager, $rootScope, $log, $timeout, not return { require: "form", restrict: "A", + controller: function($scope) { + //This exposes an API for direct use with this directive + + var unsubscribe = []; + var self = this; + + //This is basically the same as a directive subscribing to an event but maybe a little + // nicer since the other directive can use this directive's API instead of a magical event + this.onValidationStatusChanged = function (cb) { + unsubscribe.push($scope.$on("valStatusChanged", function(evt, args) { + cb.apply(self, [evt, args]); + })); + }; + + //Ensure to remove the event handlers when this instance is destroyted + $scope.$on('$destroy', function () { + for (var u in unsubscribe) { + unsubscribe[u](); + } + }); + }, link: function (scope, element, attr, formCtrl) { scope.$watch(function () { @@ -70,7 +91,7 @@ function valFormManager(serverValidationManager, $rootScope, $log, $timeout, not //This handles the 'unsaved changes' dialog which is triggered when a route is attempting to be changed but // the form has pending changes - unsubscribe.push($rootScope.$on('$locationChangeStart', function(event, nextLocation, currentLocation) { + var locationEvent = $rootScope.$on('$locationChangeStart', function(event, nextLocation, currentLocation) { if (!formCtrl.$dirty || isSavingNewItem) { return; } @@ -93,7 +114,9 @@ function valFormManager(serverValidationManager, $rootScope, $log, $timeout, not eventsService.emit("valFormManager.pendingChanges", true); } - })); + }); + unsubscribe.push(locationEvent); + //Ensure to remove the event handler when this instance is destroyted scope.$on('$destroy', function() { for (var u in unsubscribe) { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertymsg.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valpropertymsg.directive.js similarity index 100% rename from src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertymsg.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valpropertymsg.directive.js diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertyvalidator.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valpropertyvalidator.directive.js similarity index 96% rename from src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertyvalidator.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valpropertyvalidator.directive.js index 77652d7f69..53a1ea67b2 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertyvalidator.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valpropertyvalidator.directive.js @@ -59,9 +59,6 @@ function valPropertyValidator(serverValidationManager) { } }; - // Formatters are invoked when the model is modified in the code. - modelCtrl.$formatters.push(validate); - // Parsers are called as soon as the value in the form input is modified modelCtrl.$parsers.push(validate); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valregex.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valregex.directive.js new file mode 100644 index 0000000000..bc69d1cd02 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valregex.directive.js @@ -0,0 +1,65 @@ +/** + * @ngdoc directive + * @name umbraco.directives.directive:valRegex + * @restrict A + * @description A custom directive to allow for matching a value against a regex string. + * NOTE: there's already an ng-pattern but this requires that a regex expression is set, not a regex string + **/ +function valRegex() { + + return { + require: 'ngModel', + restrict: "A", + link: function (scope, elm, attrs, ctrl) { + + var flags = ""; + var regex; + + attrs.$observe("valRegexFlags", function (newVal) { + if (newVal) { + flags = newVal; + } + }); + + attrs.$observe("valRegex", function (newVal) { + if (newVal) { + try { + var resolved = newVal; + if (resolved) { + regex = new RegExp(resolved, flags); + } + else { + regex = new RegExp(attrs.valRegex, flags); + } + } + catch (e) { + regex = new RegExp(attrs.valRegex, flags); + } + } + }); + + var patternValidator = function (viewValue) { + if (regex) { + //NOTE: we don't validate on empty values, use required validator for that + if (!viewValue || regex.test(viewValue.toString())) { + // it is valid + ctrl.$setValidity('valRegex', true); + //assign a message to the validator + ctrl.errorMsg = ""; + return viewValue; + } + else { + // it is invalid, return undefined (no model update) + ctrl.$setValidity('valRegex', false); + //assign a message to the validator + ctrl.errorMsg = "Value is invalid, it does not match the correct pattern"; + return undefined; + } + } + }; + + ctrl.$parsers.push(patternValidator); + } + }; +} +angular.module('umbraco.directives.validation').directive("valRegex", valRegex); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valserver.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valserver.directive.js similarity index 100% rename from src/Umbraco.Web.UI.Client/src/common/directives/validation/valserver.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valserver.directive.js diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valserverfield.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valserverfield.directive.js new file mode 100644 index 0000000000..6fe2dfdf08 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valserverfield.directive.js @@ -0,0 +1,57 @@ +/** + * @ngdoc directive + * @name umbraco.directives.directive:valServerField + * @restrict A + * @description This directive is used to associate a content field (not user defined) with a server-side validation response + * so that the validators in angular are updated based on server-side feedback. + **/ +function valServerField(serverValidationManager) { + return { + require: 'ngModel', + restrict: "A", + link: function (scope, element, attr, ctrl) { + + var fieldName = null; + + attr.$observe("valServerField", function (newVal) { + if (newVal && fieldName === null) { + fieldName = newVal; + + //subscribe to the changed event of the view model. This is required because when we + // have a server error we actually invalidate the form which means it cannot be + // resubmitted. So once a field is changed that has a server error assigned to it + // we need to re-validate it for the server side validator so the user can resubmit + // the form. Of course normal client-side validators will continue to execute. + ctrl.$viewChangeListeners.push(function () { + if (ctrl.$invalid) { + ctrl.$setValidity('valServerField', true); + } + }); + + //subscribe to the server validation changes + serverValidationManager.subscribe(null, fieldName, function (isValid, fieldErrors, allErrors) { + if (!isValid) { + ctrl.$setValidity('valServerField', false); + //assign an error msg property to the current validator + ctrl.errorMsg = fieldErrors[0].errorMsg; + } + else { + ctrl.$setValidity('valServerField', true); + //reset the error message + ctrl.errorMsg = ""; + } + }); + + //when the element is disposed we need to unsubscribe! + // NOTE: this is very important otherwise when this controller re-binds the previous subscriptsion will remain + // but they are a different callback instance than the above. + element.bind('$destroy', function () { + serverValidationManager.unsubscribe(null, fieldName); + }); + + } + }); + } + }; +} +angular.module('umbraco.directives.validation').directive("valServerField", valServerField); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valtab.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valtab.directive.js similarity index 77% rename from src/Umbraco.Web.UI.Client/src/common/directives/validation/valtab.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valtab.directive.js index cd6dc51eca..fbca0cd233 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valtab.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valtab.directive.js @@ -8,16 +8,16 @@ **/ function valTab() { return { - require: "^form", + require: ['^form', '^valFormManager'], restrict: "A", - link: function (scope, element, attr, formCtrl) { - - var tabId = "tab" + scope.tab.id; - + link: function (scope, element, attr, ctrs) { + + var valFormManager = ctrs[1]; + var tabId = "tab" + scope.tab.id; scope.tabHasError = false; //listen for form validation changes - scope.$on("valStatusChanged", function(evt, args) { + valFormManager.onValidationStatusChanged(function (evt, args) { if (!args.form.$valid) { var tabContent = element.closest(".umb-panel").find("#" + tabId); //check if the validation messages are contained inside of this tabs diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valtogglemsg.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valtogglemsg.directive.js similarity index 100% rename from src/Umbraco.Web.UI.Client/src/common/directives/validation/valtogglemsg.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valtogglemsg.directive.js diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valtriggerchange.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valtriggerchange.directive.js similarity index 100% rename from src/Umbraco.Web.UI.Client/src/common/directives/validation/valtriggerchange.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valtriggerchange.directive.js diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/editors/umbAutoResize.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/editors/umbAutoResize.directive.js deleted file mode 100644 index 7e7d5a7c5a..0000000000 --- a/src/Umbraco.Web.UI.Client/src/common/directives/editors/umbAutoResize.directive.js +++ /dev/null @@ -1,32 +0,0 @@ -angular.module("umbraco.directives") - .directive('umbAutoResize', function($timeout) { - - return function(scope, element, attr){ - var domEl = element[0]; - var update = function(force) { - - if(force === true){ - element.height(0); - } - - if(domEl.scrollHeight !== domEl.clientHeight){ - element.height(domEl.scrollHeight); - } - }; - - element.bind('keyup keydown keypress change', update); - element.bind('blur', function(){ update(true); }); - - $timeout(function() { - update(true); - }, 200); - - - //I hate bootstrap tabs - $('a[data-toggle="tab"]').on('shown', update); - - scope.$on('$destroy', function() { - $('a[data-toggle="tab"]').unbind("shown", update); - }); - }; -}); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/html/umbavatar.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/html/umbavatar.directive.js deleted file mode 100644 index 35a94008d4..0000000000 --- a/src/Umbraco.Web.UI.Client/src/common/directives/html/umbavatar.directive.js +++ /dev/null @@ -1,27 +0,0 @@ -/** -* @ngdoc directive -* @name umbraco.directives.directive:umbAvatar -* @restrict E -**/ -function avatarDirective() { - return { - restrict: "E", // restrict to an element - replace: true, // replace the html element with the template - templateUrl: 'views/directives/umb-avatar.html', - scope: { - name: '@', - email: '@', - hash: '@' - }, - link: function(scope, element, attr, ctrl) { - - scope.$watch("hash", function (val) { - //set the gravatar url - scope.gravatar = "//www.gravatar.com/avatar/" + val + "?s=40"; - }); - - } - }; -} - -angular.module('umbraco.directives').directive("umbAvatar", avatarDirective); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/html/umbheader.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/html/umbheader.directive.js deleted file mode 100644 index b1cca01d44..0000000000 --- a/src/Umbraco.Web.UI.Client/src/common/directives/html/umbheader.directive.js +++ /dev/null @@ -1,79 +0,0 @@ -angular.module("umbraco.directives") -.directive('umbHeader', function($parse, $timeout){ - return { - restrict: 'E', - replace: true, - transclude: 'true', - templateUrl: 'views/directives/umb-header.html', - //create a new isolated scope assigning a tabs property from the attribute 'tabs' - //which is bound to the parent scope property passed in - scope: { - tabs: "=" - }, - link: function (scope, iElement, iAttrs) { - - var maxTabs = 4; - - function collectFromDom(activeTab){ - var $panes = $('div.tab-content'); - - angular.forEach($panes.find('.tab-pane'), function (pane, index) { - var $this = angular.element(pane); - - var id = $this.attr("rel"); - var label = $this.attr("label"); - var tab = {id: id, label: label, active: false}; - if(!activeTab){ - tab.active = true; - activeTab = tab; - } - - if ($this.attr("rel") === String(activeTab.id)) { - $this.addClass('active'); - } - else { - $this.removeClass('active'); - } - - if(label){ - scope.visibleTabs.push(tab); - } - - }); - } - - scope.showTabs = iAttrs.tabs ? true : false; - scope.visibleTabs = []; - scope.overflownTabs = []; - - $timeout(function () { - collectFromDom(undefined); - }, 500); - - //when the tabs change, we need to hack the planet a bit and force the first tab content to be active, - //unfortunately twitter bootstrap tabs is not playing perfectly with angular. - scope.$watch("tabs", function (newValue, oldValue) { - - angular.forEach(newValue, function(val, index){ - var tab = {id: val.id, label: val.label}; - scope.visibleTabs.push(tab); - }); - - //don't process if we cannot or have already done so - if (!newValue) {return;} - if (!newValue.length || newValue.length === 0){return;} - - var activeTab = _.find(newValue, function (item) { - return item.active; - }); - - //we need to do a timeout here so that the current sync operation can complete - // and update the UI, then this will fire and the UI elements will be available. - $timeout(function () { - collectFromDom(activeTab); - }, 500); - - }); - } - }; -}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/html/umbtab.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/html/umbtab.directive.js deleted file mode 100644 index 3758c64179..0000000000 --- a/src/Umbraco.Web.UI.Client/src/common/directives/html/umbtab.directive.js +++ /dev/null @@ -1,14 +0,0 @@ -/** -* @ngdoc directive -* @name umbraco.directives.directive:umbTab -* @restrict E -**/ -angular.module("umbraco.directives") -.directive('umbTab', function(){ - return { - restrict: 'E', - replace: true, - transclude: 'true', - templateUrl: 'views/directives/umb-tab.html' - }; -}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/imaging/umbimagefolder.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/imaging/umbimagefolder.directive.js deleted file mode 100644 index 5b1ad4f0dd..0000000000 --- a/src/Umbraco.Web.UI.Client/src/common/directives/imaging/umbimagefolder.directive.js +++ /dev/null @@ -1,164 +0,0 @@ -/** -* @ngdoc directive -* @name umbraco.directives.directive:umbImageFolder -* @restrict E -* @function -**/ -function umbImageFolder($rootScope, assetsService, $timeout, $log, umbRequestHelper, mediaResource, imageHelper, notificationsService) { - return { - restrict: 'E', - replace: true, - templateUrl: 'views/directives/imaging/umb-image-folder.html', - scope: { - options: '=', - nodeId: '@', - onUploadComplete: '=' - }, - link: function (scope, element, attrs) { - //NOTE: Blueimp handlers are documented here: https://github.com/blueimp/jQuery-File-Upload/wiki/Options - //NOTE: We are using a Blueimp version specifically ~9.4.0 because any higher than that and we get crazy errors with jquery, also note - // that the jquery UI version 1.10.3 is required for this blueimp version! if we go higher to 1.10.4 it breaks! seriously! - // It's do to with the widget framework in jquery ui changes which must have broken a whole lot of stuff. So don't change it for now. - - if (scope.onUploadComplete && !angular.isFunction(scope.onUploadComplete)) { - throw "onUploadComplete must be a function callback"; - } - - scope.uploading = false; - scope.files = []; - scope.progress = 0; - - var defaultOptions = { - url: umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostAddFile") + "?origin=blueimp", - //we'll start it manually to make sure the UI is all in order before processing - autoUpload: true, - disableImageResize: /Android(?!.*Chrome)|Opera/ - .test(window.navigator.userAgent), - previewMaxWidth: 150, - previewMaxHeight: 150, - previewCrop: true, - dropZone: element.find(".drop-zone"), - fileInput: element.find("input.uploader"), - formData: { - currentFolder: scope.nodeId - } - }; - - //merge options - scope.blueimpOptions = angular.extend(defaultOptions, scope.options); - - function loadChildren(id) { - mediaResource.getChildren(id) - .then(function(data) { - scope.images = data.items; - }); - } - - function checkComplete(e, data) { - scope.$apply(function () { - //remove the amount of files complete - //NOTE: function is here instead of in the loop otherwise jshint blows up - function findFile(file) { return file === data.files[i]; } - for (var i = 0; i < data.files.length; i++) { - var found = _.find(scope.files, findFile); - found.completed = true; - } - - //when none are left resync everything - var remaining = _.filter(scope.files, function (file) { return file.completed !== true; }); - if (remaining.length === 0) { - - scope.progress = 100; - - //just the ui transition isn't too abrupt, just wait a little here - $timeout(function () { - scope.progress = 0; - scope.files = []; - scope.uploading = false; - - loadChildren(scope.nodeId); - - //call the callback - scope.onUploadComplete.apply(this, [data]); - - - }, 200); - - - } - }); - } - - //when one is finished - scope.$on('fileuploaddone', function(e, data) { - checkComplete(e, data); - }); - - //This handler gives us access to the file 'preview', this is the only handler that makes this available for whatever reason - // so we'll use this to also perform the adding of files to our collection - scope.$on('fileuploadprocessalways', function(e, data) { - scope.$apply(function() { - scope.uploading = true; - scope.files.push(data.files[data.index]); - }); - }); - - //This is a bit of a hack to check for server errors, currently if there's a non - //known server error we will tell them to check the logs, otherwise we'll specifically - //check for the file size error which can only be done with dodgy string checking - scope.$on('fileuploadfail', function (e, data) { - if (data.jqXHR.status === 500 && data.jqXHR.responseText.indexOf("Maximum request length exceeded") >= 0) { - notificationsService.error(data.errorThrown, "The uploaded file was too large, check with your site administrator to adjust the maximum size allowed"); - - } - else { - notificationsService.error(data.errorThrown, data.jqXHR.statusText); - } - - checkComplete(e, data); - }); - - //This executes prior to the whole processing which we can use to get the UI going faster, - //this also gives us the start callback to invoke to kick of the whole thing - scope.$on('fileuploadadd', function(e, data) { - scope.$apply(function() { - scope.uploading = true; - }); - }); - - // All these sit-ups are to add dropzone area and make sure it gets removed if dragging is aborted! - scope.$on('fileuploaddragover', function(e, data) { - if (!scope.dragClearTimeout) { - scope.$apply(function() { - scope.dropping = true; - }); - } - else { - $timeout.cancel(scope.dragClearTimeout); - } - scope.dragClearTimeout = $timeout(function() { - scope.dropping = null; - scope.dragClearTimeout = null; - }, 300); - }); - - //init load - loadChildren(scope.nodeId); - - } - }; -} - -angular.module("umbraco.directives") - .directive("umbUploadPreview", function($parse) { - return { - link: function(scope, element, attr, ctrl) { - var fn = $parse(attr.umbUploadPreview), - file = fn(scope); - if (file.preview) { - element.append(file.preview); - } - } - }; - }) - .directive('umbImageFolder', umbImageFolder); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/imaging/umbimageupload.js b/src/Umbraco.Web.UI.Client/src/common/directives/imaging/umbimageupload.js deleted file mode 100644 index be7336cbc0..0000000000 --- a/src/Umbraco.Web.UI.Client/src/common/directives/imaging/umbimageupload.js +++ /dev/null @@ -1,39 +0,0 @@ -/** -* @ngdoc directive -* @name umbraco.directives.directive:umbImageFileUpload -* @restrict E -* @function -* @description -* This is a wrapper around the blueimp angular file-upload directive so that we can expose a proper API to other directives, the blueimp -* directive isn't actually made very well and only exposes an API/events on the $scope so we can't do things like require: "^fileUpload" and use -* it's instance. -**/ -function umbImageUpload($compile) { - return { - restrict: 'A', - scope: true, - link: function (scope, element, attrs) { - //set inner scope variable to assign to file-upload directive in the template - scope.innerOptions = scope.$eval(attrs.umbImageUpload); - - //compile an inner blueimp file-upload with our scope - - var x = angular.element('
    '); - element.append(x); - $compile(x)(scope); - }, - - //Define a controller for this directive to expose APIs to other directives - controller: function ($scope, $element, $attrs) { - - - //create a method to allow binding to a blueimp event (which is based on it's directives scope) - this.bindEvent = function (e, callback) { - $scope.$on(e, callback); - }; - - } - }; -} - -angular.module("umbraco.directives").directive('umbImageUpload', umbImageUpload); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/imaging/umbimageuploadprogress.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/imaging/umbimageuploadprogress.directive.js deleted file mode 100644 index 6101ee4a01..0000000000 --- a/src/Umbraco.Web.UI.Client/src/common/directives/imaging/umbimageuploadprogress.directive.js +++ /dev/null @@ -1,23 +0,0 @@ -/** -* @ngdoc directive -* @name umbraco.directives.directive:umbImageUploadProgress -* @restrict E -* @function -**/ -function umbImageUploadProgress($rootScope, assetsService, $timeout, $log, umbRequestHelper, mediaResource, imageHelper) { - return { - require: '^umbImageUpload', - restrict: 'E', - replace: true, - template: '
    ', - - link: function (scope, element, attrs, umbImgUploadCtrl) { - - umbImgUploadCtrl.bindEvent('fileuploadprogressall', function (e, data) { - scope.progress = parseInt(data.loaded / data.total * 100, 10); - }); - } - }; -} - -angular.module("umbraco.directives").directive('umbImageUploadProgress', umbImageUploadProgress); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/umbnotifications.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/umbnotifications.directive.js deleted file mode 100644 index 365c212f42..0000000000 --- a/src/Umbraco.Web.UI.Client/src/common/directives/umbnotifications.directive.js +++ /dev/null @@ -1,24 +0,0 @@ -/** - * @ngdoc directive - * @name umbraco.directives.directive:umbNotifications - */ -function notificationDirective(notificationsService) { - return { - restrict: "E", // restrict to an element - replace: true, // replace the html element with the template - templateUrl: 'views/directives/umb-notifications.html', - link: function (scope, element, attr, ctrl) { - - //subscribes to notifications in the notification service - scope.notifications = notificationsService.current; - scope.$watch('notificationsService.current', function (newVal, oldVal, scope) { - if (newVal) { - scope.notifications = newVal; - } - }); - - } - }; -} - -angular.module('umbraco.directives').directive("umbNotifications", notificationDirective); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/util.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/util.directive.js deleted file mode 100644 index ebc4bcea7e..0000000000 --- a/src/Umbraco.Web.UI.Client/src/common/directives/util.directive.js +++ /dev/null @@ -1,56 +0,0 @@ -/** -* @description Utillity directives for key and field events -**/ -angular.module('umbraco.directives') - -.directive('onKeyup', function () { - return function (scope, elm, attrs) { - elm.bind("keyup", function () { - scope.$apply(attrs.onKeyup); - }); - }; -}) - -.directive('onKeydown', function () { - return { - link: function (scope, elm, attrs) { - scope.$apply(attrs.onKeydown); - } - }; -}) - -.directive('onBlur', function () { - return function (scope, elm, attrs) { - elm.bind("blur", function () { - scope.$apply(attrs.onBlur); - }); - }; -}) - -.directive('onFocus', function () { - return function (scope, elm, attrs) { - elm.bind("focus", function () { - scope.$apply(attrs.onFocus); - }); - }; -}) - -.directive('onRightClick',function(){ - - document.oncontextmenu = function (e) { - if(e.target.hasAttribute('on-right-click')) { - e.preventDefault(); - e.stopPropagation(); - return false; - } - }; - - return function(scope,el,attrs){ - el.bind('contextmenu',function(e){ - e.preventDefault(); - e.stopPropagation(); - scope.$apply(attrs.onRightClick); - return false; - }); - }; -}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/util/delayedMouseLeave.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/util/delayedMouseLeave.directive.js deleted file mode 100644 index 94d5925f2c..0000000000 --- a/src/Umbraco.Web.UI.Client/src/common/directives/util/delayedMouseLeave.directive.js +++ /dev/null @@ -1,26 +0,0 @@ -angular.module("umbraco.directives") - .directive('delayedMouseleave', function ($timeout, $parse) { - return { - restrict: 'A', - link: function (scope, element, attrs, ctrl) { - var active = false; - var fn = $parse(attrs.delayedMouseleave); - element.on("mouseleave", function(event) { - var callback = function() { - fn(scope, {$event:event}); - }; - - active = false; - $timeout(function(){ - if(active === false){ - scope.$apply(callback); - } - }, 650); - }); - - element.on("mouseenter", function(event, args){ - active = true; - }); - } - }; - }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/util/detectfold.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/util/detectfold.directive.js deleted file mode 100644 index a0d1a8293b..0000000000 --- a/src/Umbraco.Web.UI.Client/src/common/directives/util/detectfold.directive.js +++ /dev/null @@ -1,49 +0,0 @@ -/** -* @ngdoc directive -* @name umbraco.directives.directive:umbPanel -* @restrict E -**/ -angular.module("umbraco.directives.html") - .directive('detectFold', function($timeout, $log){ - return { - restrict: 'A', - link: function (scope, el, attrs) { - - var state = false, - parent = $(".umb-panel-body"), - winHeight = $(window).height(), - calculate = _.throttle(function(){ - if(el && el.is(":visible") && !el.hasClass("umb-bottom-bar")){ - //var parent = el.parent(); - var hasOverflow = parent.innerHeight() < parent[0].scrollHeight; - //var belowFold = (el.offset().top + el.height()) > winHeight; - if(hasOverflow){ - el.addClass("umb-bottom-bar"); - } - } - return state; - }, 1000); - - scope.$watch(calculate, function(newVal, oldVal) { - if(newVal !== oldVal){ - if(newVal){ - el.addClass("umb-bottom-bar"); - }else{ - el.removeClass("umb-bottom-bar"); - } - } - }); - - $(window).bind("resize", function () { - winHeight = $(window).height(); - el.removeClass("umb-bottom-bar"); - state = false; - calculate(); - }); - - $('a[data-toggle="tab"]').on('shown', function (e) { - calculate(); - }); - } - }; - }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/util/hotkey.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/util/hotkey.directive.js deleted file mode 100644 index 2ed1c87220..0000000000 --- a/src/Umbraco.Web.UI.Client/src/common/directives/util/hotkey.directive.js +++ /dev/null @@ -1,28 +0,0 @@ -/** -* @ngdoc directive -* @name umbraco.directives.directive:headline -**/ -angular.module("umbraco.directives") - .directive('hotkey', function ($window, keyboardService, $log) { - - return function (scope, el, attrs) { - - //support data binding - - var keyCombo = scope.$eval(attrs["hotkey"]); - if (!keyCombo) { - keyCombo = attrs["hotkey"]; - } - - keyboardService.bind(keyCombo, function() { - var element = $(el); - - if(element.is("a,button,input[type='button'],input[type='submit']") && !element.is(':disabled') ){ - element.click(); - }else{ - element.focus(); - } - }); - - }; - }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/util/konami.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/util/konami.directive.js new file mode 100644 index 0000000000..7c96e0e050 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/util/konami.directive.js @@ -0,0 +1,62 @@ +/** + * Konami Code directive for AngularJS + * @version v0.0.1 + * @license MIT License, http://www.opensource.org/licenses/MIT + */ + +angular.module('umbraco.directives') + .directive('konamiCode', ['$document', function ($document) { + var konamiKeysDefault = [38, 38, 40, 40, 37, 39, 37, 39, 66, 65]; + + return { + restrict: 'A', + link: function (scope, element, attr) { + + if (!attr.konamiCode) { + throw ('Konami directive must receive an expression as value.'); + } + + // Let user define a custom code. + var konamiKeys = attr.konamiKeys || konamiKeysDefault; + var keyIndex = 0; + + /** + * Fired when konami code is type. + */ + function activated() { + if ('konamiOnce' in attr) { + stopListening(); + } + // Execute expression. + scope.$eval(attr.konamiCode); + } + + /** + * Handle keydown events. + */ + function keydown(e) { + if (e.keyCode === konamiKeys[keyIndex++]) { + if (keyIndex === konamiKeys.length) { + keyIndex = 0; + activated(); + } + } else { + keyIndex = 0; + } + } + + /** + * Stop to listen typing. + */ + function stopListening() { + $document.off('keydown', keydown); + } + + // Start listening to key typing. + $document.on('keydown', keydown); + + // Stop listening when scope is destroyed. + scope.$on('$destroy', stopListening); + } + }; + }]); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/util/localize.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/util/localize.directive.js deleted file mode 100644 index 113f24ad5f..0000000000 --- a/src/Umbraco.Web.UI.Client/src/common/directives/util/localize.directive.js +++ /dev/null @@ -1,37 +0,0 @@ -angular.module("umbraco.directives") -.directive('localize', function ($log, localizationService) { - return { - restrict: 'E', - scope:{ - key: '@' - }, - replace: true, - link: function (scope, element, attrs) { - var key = scope.key; - localizationService.localize(key).then(function(value){ - element.html(value); - }); - } - }; -}) -.directive('localize', function ($log, localizationService) { - return { - restrict: 'A', - link: function (scope, element, attrs) { - var keys = attrs.localize.split(','); - - angular.forEach(keys, function(value, key){ - var attr = element.attr(value); - if(attr){ - if(attr[0] === '@'){ - var t = localizationService.tokenize(attr.substring(1), scope); - localizationService.localize(t.key, t.tokens).then(function(val){ - element.attr(value, val); - }); - } - } - }); - - } - }; -}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valregex.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valregex.directive.js deleted file mode 100644 index 651c0a54c7..0000000000 --- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valregex.directive.js +++ /dev/null @@ -1,64 +0,0 @@ -/** - * @ngdoc directive - * @name umbraco.directives.directive:valRegex - * @restrict A - * @description A custom directive to allow for matching a value against a regex string. - * NOTE: there's already an ng-pattern but this requires that a regex expression is set, not a regex string - **/ -function valRegex() { - - return { - require: 'ngModel', - restrict: "A", - link: function (scope, elm, attrs, ctrl) { - - var flags = ""; - if (attrs.valRegexFlags) { - try { - flags = scope.$eval(attrs.valRegexFlags); - if (!flags) { - flags = attrs.valRegexFlags; - } - } - catch (e) { - flags = attrs.valRegexFlags; - } - } - var regex; - try { - var resolved = scope.$eval(attrs.valRegex); - if (resolved) { - regex = new RegExp(resolved, flags); - } - else { - regex = new RegExp(attrs.valRegex, flags); - } - } - catch(e) { - regex = new RegExp(attrs.valRegex, flags); - } - - var patternValidator = function (viewValue) { - //NOTE: we don't validate on empty values, use required validator for that - if (!viewValue || regex.test(viewValue)) { - // it is valid - ctrl.$setValidity('valRegex', true); - //assign a message to the validator - ctrl.errorMsg = ""; - return viewValue; - } - else { - // it is invalid, return undefined (no model update) - ctrl.$setValidity('valRegex', false); - //assign a message to the validator - ctrl.errorMsg = "Value is invalid, it does not match the correct pattern"; - return undefined; - } - }; - - ctrl.$formatters.push(patternValidator); - ctrl.$parsers.push(patternValidator); - } - }; -} -angular.module('umbraco.directives.validation').directive("valRegex", valRegex); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valrequirecomponent.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valrequirecomponent.directive.js new file mode 100644 index 0000000000..565136608f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valrequirecomponent.directive.js @@ -0,0 +1,38 @@ +(function() { + 'use strict'; + + function ValRequireComponentDirective() { + + function link(scope, el, attr, ngModel) { + + var unbindModelWatcher = scope.$watch(function () { + return ngModel.$modelValue; + }, function(newValue) { + + if(newValue === undefined || newValue === null || newValue === "") { + ngModel.$setValidity("valRequiredComponent", false); + } else { + ngModel.$setValidity("valRequiredComponent", true); + } + + }); + + // clean up + scope.$on('$destroy', function(){ + unbindModelWatcher(); + }); + + } + + var directive = { + require: 'ngModel', + restrict: "A", + link: link + }; + + return directive; + } + + angular.module('umbraco.directives').directive('valRequireComponent', ValRequireComponentDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valserverfield.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valserverfield.directive.js deleted file mode 100644 index 9a077615df..0000000000 --- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valserverfield.directive.js +++ /dev/null @@ -1,54 +0,0 @@ -/** - * @ngdoc directive - * @name umbraco.directives.directive:valServerField - * @restrict A - * @description This directive is used to associate a content field (not user defined) with a server-side validation response - * so that the validators in angular are updated based on server-side feedback. - **/ -function valServerField(serverValidationManager) { - return { - require: 'ngModel', - restrict: "A", - link: function (scope, element, attr, ctrl) { - - if (!attr.valServerField) { - throw "valServerField must have a field name for referencing server errors"; - } - - var fieldName = attr.valServerField; - - //subscribe to the changed event of the view model. This is required because when we - // have a server error we actually invalidate the form which means it cannot be - // resubmitted. So once a field is changed that has a server error assigned to it - // we need to re-validate it for the server side validator so the user can resubmit - // the form. Of course normal client-side validators will continue to execute. - ctrl.$viewChangeListeners.push(function () { - if (ctrl.$invalid) { - ctrl.$setValidity('valServerField', true); - } - }); - - //subscribe to the server validation changes - serverValidationManager.subscribe(null, fieldName, function (isValid, fieldErrors, allErrors) { - if (!isValid) { - ctrl.$setValidity('valServerField', false); - //assign an error msg property to the current validator - ctrl.errorMsg = fieldErrors[0].errorMsg; - } - else { - ctrl.$setValidity('valServerField', true); - //reset the error message - ctrl.errorMsg = ""; - } - }); - - //when the element is disposed we need to unsubscribe! - // NOTE: this is very important otherwise when this controller re-binds the previous subscriptsion will remain - // but they are a different callback instance than the above. - element.bind('$destroy', function () { - serverValidationManager.unsubscribe(null, fieldName); - }); - } - }; -} -angular.module('umbraco.directives.validation').directive("valServerField", valServerField); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valsubview.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valsubview.directive.js new file mode 100644 index 0000000000..916b933063 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valsubview.directive.js @@ -0,0 +1,49 @@ +/** +* @ngdoc directive +* @name umbraco.directives.directive:valSubView +* @restrict A +* @description Used to show validation warnings for a editor sub view to indicate that the section content has validation errors in its data. +* In order for this directive to work, the valFormManager directive must be placed on the containing form. +**/ +(function() { + 'use strict'; + + function valSubViewDirective() { + + function link(scope, el, attr, ctrl) { + + var valFormManager = ctrl[1]; + scope.subView.hasError = false; + + //listen for form validation changes + valFormManager.onValidationStatusChanged(function (evt, args) { + if (!args.form.$valid) { + + var subViewContent = el.find(".ng-invalid"); + + if (subViewContent.length > 0) { + scope.subView.hasError = true; + } else { + scope.subView.hasError = false; + } + + } + else { + scope.subView.hasError = false; + } + }); + + } + + var directive = { + require: ['^form', '^valFormManager'], + restrict: "A", + link: link + }; + + return directive; + } + + angular.module('umbraco.directives').directive('valSubView', valSubViewDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/filters/compareArrays.filter.js b/src/Umbraco.Web.UI.Client/src/common/filters/compareArrays.filter.js new file mode 100644 index 0000000000..13f603260d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/filters/compareArrays.filter.js @@ -0,0 +1,26 @@ +angular.module("umbraco.filters") + .filter('compareArrays', function() { + return function inArray(array, compareArray, compareProperty) { + + var result = []; + + angular.forEach(array, function(arrayItem){ + + var exists = false; + + angular.forEach(compareArray, function(compareItem){ + if( arrayItem[compareProperty] === compareItem[compareProperty]) { + exists = true; + } + }); + + if(!exists) { + result.push(arrayItem); + } + + }); + + return result; + + }; +}); diff --git a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/tree.mocks.js b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/tree.mocks.js index 1ecc0b5cf8..9ad97899e5 100644 --- a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/tree.mocks.js +++ b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/tree.mocks.js @@ -157,7 +157,7 @@ angular.module('umbraco.mocks'). name: "developer", id: -1, children: [ - { name: "Data types", childNodesUrl: dataTypeChildrenUrl, id: -1, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: dataTypeMenuUrl, metaData: { treeAlias: "datatype" } }, + { name: "Data types", childNodesUrl: dataTypeChildrenUrl, id: -1, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: dataTypeMenuUrl, metaData: { treeAlias: "dataTypes" } }, { name: "Macros", childNodesUrl: url, id: -1, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl, metaData: { treeAlias: "macros" } }, { name: "Packages", childNodesUrl: url, id: -1, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl, metaData: { treeAlias: "packager" } }, { name: "XSLT Files", childNodesUrl: url, id: -1, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl, metaData: { treeAlias: "xslt" } }, @@ -179,7 +179,7 @@ angular.module('umbraco.mocks'). { name: "Templates", childNodesUrl: url, id: -1, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl, metaData: { treeAlias: "templates" } }, { name: "Dictionary", childNodesUrl: url, id: -1, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl, metaData: { treeAlias: "dictionary" } }, { name: "Media types", childNodesUrl: url, id: -1, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl, metaData: { treeAlias: "mediaTypes" } }, - { name: "Document types", childNodesUrl: url, id: -1, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl, metaData: { treeAlias: "nodeTypes" } } + { name: "Document types", childNodesUrl: url, id: -1, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl, metaData: { treeAlias: "documentTypes" } } ], expanded: true, hasChildren: true, diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js index 4de66fa240..86584d41f7 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js @@ -495,7 +495,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { $http.get( umbRequestHelper.getApiUrl( "contentApiBaseUrl", - "GetHasPermission", + "HasPermission", [{ permissionToCheck: permission },{ nodeId: id }])), 'Failed to check permission for item ' + id); }, @@ -507,7 +507,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * * @description * Saves changes made to a content item to its current version, if the content item is new, the isNew paramater must be passed to force creation - * if the content item needs to have files attached, they must be provided as the files param and passed seperately + * if the content item needs to have files attached, they must be provided as the files param and passed separately * * * ##usage @@ -540,7 +540,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * * @description * Saves and publishes changes made to a content item to a new version, if the content item is new, the isNew paramater must be passed to force creation - * if the content item needs to have files attached, they must be provided as the files param and passed seperately + * if the content item needs to have files attached, they must be provided as the files param and passed separately * * * ##usage diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js index deaa857e94..4b1ed8f9c1 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js @@ -3,22 +3,21 @@ * @name umbraco.resources.contentTypeResource * @description Loads in data for content types **/ -function contentTypeResource($q, $http, umbRequestHelper) { +function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter) { return { - getAssignedListViewDataType: function (contentTypeId) { - + getAvailableCompositeContentTypes: function (contentTypeId) { return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( "contentTypeApiBaseUrl", - "GetAssignedListViewDataType", + "GetAvailableCompositeContentTypes", [{ contentTypeId: contentTypeId }])), - 'Failed to retrieve data for content id ' + contentTypeId); + 'Failed to retrieve data for content type id ' + contentTypeId); }, - + /** * @ngdoc method * @name umbraco.resources.contentTypeResource#getAllowedTypes @@ -33,22 +32,23 @@ function contentTypeResource($q, $http, umbRequestHelper) { * .then(function(array) { * $scope.type = type; * }); - * - * @param {Int} contentId id of the content item to retrive allowed child types for + * + * @param {Int} contentTypeId id of the content item to retrive allowed child types for * @returns {Promise} resourcePromise object. * */ - getAllowedTypes: function (contentId) { - + getAllowedTypes: function (contentTypeId) { + return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( "contentTypeApiBaseUrl", "GetAllowedChildren", - [{ contentId: contentId }])), - 'Failed to retrieve data for content id ' + contentId); + [{ contentId: contentTypeId }])), + 'Failed to retrieve data for content id ' + contentTypeId); }, + /** * @ngdoc method * @name umbraco.resources.contentTypeResource#getAllPropertyTypeAliases @@ -56,7 +56,7 @@ function contentTypeResource($q, $http, umbRequestHelper) { * * @description * Returns a list of defined property type aliases - * + * * @returns {Promise} resourcePromise object. * */ @@ -68,8 +68,154 @@ function contentTypeResource($q, $http, umbRequestHelper) { "contentTypeApiBaseUrl", "GetAllPropertyTypeAliases")), 'Failed to retrieve property type aliases'); - } + }, + getPropertyTypeScaffold : function (id) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentTypeApiBaseUrl", + "GetPropertyTypeScaffold", + [{ id: id }])), + 'Failed to retrieve property type scaffold'); + }, + + getById: function (id) { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentTypeApiBaseUrl", + "GetById", + [{ id: id }])), + 'Failed to retrieve content type'); + }, + + deleteById: function (id) { + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "contentTypeApiBaseUrl", + "DeleteById", + [{ id: id }])), + 'Failed to delete content type'); + }, + + deleteContainerById: function (id) { + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "contentTypeApiBaseUrl", + "DeleteContainer", + [{ id: id }])), + 'Failed to delete content type contaier'); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentTypeResource#getAll + * @methodOf umbraco.resources.contentTypeResource + * + * @description + * Returns a list of all content types + * + * @returns {Promise} resourcePromise object. + * + */ + getAll: function () { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentTypeApiBaseUrl", + "GetAll")), + 'Failed to retrieve all content types'); + }, + + getScaffold: function (parentId) { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentTypeApiBaseUrl", + "GetEmpty", { parentId: parentId })), + 'Failed to retrieve content type scaffold'); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentTypeResource#save + * @methodOf umbraco.resources.contentTypeResource + * + * @description + * Saves or update a content type + * + * @param {Object} content data type object to create/update + * @returns {Promise} resourcePromise object. + * + */ + save: function (contentType) { + + var saveModel = umbDataFormatter.formatContentTypePostData(contentType); + + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("contentTypeApiBaseUrl", "PostSave"), saveModel), + 'Failed to save data for content type id ' + contentType.id); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentTypeResource#move + * @methodOf umbraco.resources.contentTypeResource + * + * @description + * Moves a node underneath a new parentId + * + * ##usage + *
    +         * contentTypeResource.move({ parentId: 1244, id: 123 })
    +         *    .then(function() {
    +         *        alert("node was moved");
    +         *    }, function(err){
    +         *      alert("node didnt move:" + err.data.Message); 
    +         *    });
    +         * 
    + * @param {Object} args arguments object + * @param {Int} args.idd the ID of the node to move + * @param {Int} args.parentId the ID of the parent node to move to + * @returns {Promise} resourcePromise object. + * + */ + move: function (args) { + if (!args) { + throw "args cannot be null"; + } + if (!args.parentId) { + throw "args.parentId cannot be null"; + } + if (!args.id) { + throw "args.id cannot be null"; + } + + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("contentTypeApiBaseUrl", "PostMove"), + { + parentId: args.parentId, + id: args.id + }), + 'Failed to move content'); + }, + + createContainer: function(parentId, name) { + + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("contentTypeApiBaseUrl", "PostCreateContainer", { parentId: parentId, name: name })), + 'Failed to create a folder under parent id ' + parentId); + + } + }; } angular.module('umbraco.resources').factory('contentTypeResource', contentTypeResource); diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/datatype.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/datatype.resource.js index 3bd8548990..7bc65c89a6 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/datatype.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/datatype.resource.js @@ -4,9 +4,9 @@ * @description Loads in data for data types **/ function dataTypeResource($q, $http, umbDataFormatter, umbRequestHelper) { - + return { - + /** * @ngdoc method * @name umbraco.resources.dataTypeResource#getPreValues @@ -17,17 +17,17 @@ function dataTypeResource($q, $http, umbDataFormatter, umbRequestHelper) { * * ##usage *
    -         * dataTypeResource.getPrevalyes("Umbraco.MediaPicker", 1234)
    +         * dataTypeResource.getPreValues("Umbraco.MediaPicker", 1234)
              *    .then(function(prevalues) {
              *        alert('its gone!');
              *    });
    -         * 
    - * + * + * * @param {String} editorAlias string alias of editor type to retrive prevalues configuration for - * @param {Int} id id of datatype to retrieve prevalues for + * @param {Int} id id of datatype to retrieve prevalues for * @returns {Promise} resourcePromise object. * - */ + */ getPreValues: function (editorAlias, dataTypeId) { if (!dataTypeId) { @@ -40,7 +40,7 @@ function dataTypeResource($q, $http, umbDataFormatter, umbRequestHelper) { "dataTypeApiBaseUrl", "GetPreValues", [{ editorAlias: editorAlias }, { dataTypeId: dataTypeId }])), - 'Failed to retrieve pre values for editor alias ' + editorAlias); + "Failed to retrieve pre values for editor alias " + editorAlias); }, /** @@ -54,34 +54,93 @@ function dataTypeResource($q, $http, umbDataFormatter, umbRequestHelper) { * ##usage *
              * dataTypeResource.getById(1234)
    -         *    .then(function() {
    -         *        alert('its gone!');
    +         *    .then(function(datatype) {
    +         *        alert('its here!');
              *    });
    -         * 
    - * - * @param {Int} id id of data type to retrieve + * + * + * @param {Int} id id of data type to retrieve * @returns {Promise} resourcePromise object. * */ getById: function (id) { - + return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( "dataTypeApiBaseUrl", "GetById", [{ id: id }])), - 'Failed to retrieve data for data type id ' + id); + "Failed to retrieve data for data type id " + id); + }, + + /** + * @ngdoc method + * @name umbraco.resources.dataTypeResource#getByName + * @methodOf umbraco.resources.dataTypeResource + * + * @description + * Gets a data type item with a given name + * + * ##usage + *
    +         * dataTypeResource.getByName("upload")
    +         *    .then(function(datatype) {
    +         *        alert('its here!');
    +         *    });
    +         * 
    + * + * @param {String} name Name of data type to retrieve + * @returns {Promise} resourcePromise object. + * + */ + getByName: function (name) { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "dataTypeApiBaseUrl", + "GetByName", + [{ name: name }])), + "Failed to retrieve data for data type with name: " + name); }, getAll: function () { - + return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( "dataTypeApiBaseUrl", "GetAll")), - 'Failed to retrieve data'); + "Failed to retrieve data"); + }, + + getGroupedDataTypes: function () { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "dataTypeApiBaseUrl", + "GetGroupedDataTypes")), + "Failed to retrieve data"); + }, + + getGroupedPropertyEditors : function(){ + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "dataTypeApiBaseUrl", + "GetGroupedPropertyEditors")), + "Failed to retrieve data"); + }, + + getAllPropertyEditors: function () { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "dataTypeApiBaseUrl", + "GetAllPropertyEditors")), + "Failed to retrieve data"); }, /** @@ -91,34 +150,34 @@ function dataTypeResource($q, $http, umbDataFormatter, umbRequestHelper) { * * @description * Returns a scaffold of an empty data type item - * + * * The scaffold is used to build editors for data types that has not yet been populated with data. - * + * * ##usage *
              * dataTypeResource.getScaffold()
              *    .then(function(scaffold) {
              *        var myType = scaffold;
    -         *        myType.name = "My new data type"; 
    +         *        myType.name = "My new data type";
              *
              *        dataTypeResource.save(myType, myType.preValues, true)
              *            .then(function(type){
              *                alert("Retrieved, updated and saved again");
              *            });
              *    });
    -         * 
    - * + * + * * @returns {Promise} resourcePromise object containing the data type scaffold. * */ - getScaffold: function () { - + getScaffold: function (parentId) { + return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( "dataTypeApiBaseUrl", - "GetEmpty")), - 'Failed to retrieve data for empty datatype'); + "GetEmpty", { parentId: parentId })), + "Failed to retrieve data for empty datatype"); }, /** * @ngdoc method @@ -134,9 +193,9 @@ function dataTypeResource($q, $http, umbDataFormatter, umbRequestHelper) { * .then(function() { * alert('its gone!'); * }); - * - * - * @param {Int} id id of content item to delete + * + * + * @param {Int} id id of content item to delete * @returns {Promise} resourcePromise object. * */ @@ -147,32 +206,159 @@ function dataTypeResource($q, $http, umbDataFormatter, umbRequestHelper) { "dataTypeApiBaseUrl", "DeleteById", [{ id: id }])), - 'Failed to delete item ' + id); + "Failed to delete item " + id); }, - + + deleteContainerById: function (id) { + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "dataTypeApiBaseUrl", + "DeleteContainer", + [{ id: id }])), + 'Failed to delete content type contaier'); + }, + + + + /** + * @ngdoc method + * @name umbraco.resources.dataTypeResource#getCustomListView + * @methodOf umbraco.resources.dataTypeResource + * + * @description + * Returns a custom listview, given a content types alias + * + * + * ##usage + *
    +         * dataTypeResource.getCustomListView("home")
    +         *    .then(function(listview) {
    +         *    });
    +         * 
    + * + * @returns {Promise} resourcePromise object containing the listview datatype. + * + */ + + getCustomListView: function (contentTypeAlias) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "dataTypeApiBaseUrl", + "GetCustomListView", + { contentTypeAlias: contentTypeAlias } + )), + "Failed to retrieve data for custom listview datatype"); + }, + + /** + * @ngdoc method + * @name umbraco.resources.dataTypeResource#createCustomListView + * @methodOf umbraco.resources.dataTypeResource + * + * @description + * Creates and returns a custom listview, given a content types alias + * + * ##usage + *
    +        * dataTypeResource.createCustomListView("home")
    +        *    .then(function(listview) {
    +        *    });
    +        * 
    + * + * @returns {Promise} resourcePromise object containing the listview datatype. + * + */ + createCustomListView: function (contentTypeAlias) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "dataTypeApiBaseUrl", + "PostCreateCustomListView", + { contentTypeAlias: contentTypeAlias } + )), + "Failed to create a custom listview datatype"); + }, + /** * @ngdoc method * @name umbraco.resources.dataTypeResource#save * @methodOf umbraco.resources.dataTypeResource * * @description - * Saves or update a data type - * + * Saves or update a data type + * * @param {Object} dataType data type object to create/update * @param {Array} preValues collection of prevalues on the datatype - * @param {Bool} isNew set to true if type should be create instead of updated + * @param {Bool} isNew set to true if type should be create instead of updated * @returns {Promise} resourcePromise object. * */ save: function (dataType, preValues, isNew) { - + var saveModel = umbDataFormatter.formatDataTypePostData(dataType, preValues, "save" + (isNew ? "New" : "")); return umbRequestHelper.resourcePromise( $http.post(umbRequestHelper.getApiUrl("dataTypeApiBaseUrl", "PostSave"), saveModel), - 'Failed to save data for data type id ' + dataType.id); + "Failed to save data for data type id " + dataType.id); + }, + + /** + * @ngdoc method + * @name umbraco.resources.dataTypeResource#move + * @methodOf umbraco.resources.dataTypeResource + * + * @description + * Moves a node underneath a new parentId + * + * ##usage + *
    +         * dataTypeResource.move({ parentId: 1244, id: 123 })
    +         *    .then(function() {
    +         *        alert("node was moved");
    +         *    }, function(err){
    +         *      alert("node didnt move:" + err.data.Message); 
    +         *    });
    +         * 
    + * @param {Object} args arguments object + * @param {Int} args.idd the ID of the node to move + * @param {Int} args.parentId the ID of the parent node to move to + * @returns {Promise} resourcePromise object. + * + */ + move: function (args) { + if (!args) { + throw "args cannot be null"; + } + if (!args.parentId) { + throw "args.parentId cannot be null"; + } + if (!args.id) { + throw "args.id cannot be null"; + } + + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("dataTypeApiBaseUrl", "PostMove"), + { + parentId: args.parentId, + id: args.id + }), + 'Failed to move content'); + }, + + createContainer: function (parentId, name) { + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "dataTypeApiBaseUrl", + "PostCreateContainer", + { parentId: parentId, name: name })), + 'Failed to create a folder under parent id ' + parentId); } }; } -angular.module('umbraco.resources').factory('dataTypeResource', dataTypeResource); +angular.module("umbraco.resources").factory("dataTypeResource", dataTypeResource); diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js index c136f88bc7..a9296acc37 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js @@ -36,6 +36,16 @@ function entityResource($q, $http, umbRequestHelper) { //the factory object returned return { + getSafeAlias: function (value, camelCase) { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "GetSafeAlias", { value: value, camelCase: camelCase })), + 'Failed to retrieve content type scaffold'); + }, + /** * @ngdoc method * @name umbraco.resources.entityResource#getPath @@ -139,6 +149,12 @@ function entityResource($q, $http, umbRequestHelper) { _.each(ids, function(item) { query += "ids=" + item + "&"; }); + + // if ids array is empty we need a empty variable in the querystring otherwise the service returns a error + if (ids.length === 0) { + query += "ids=&"; + } + query += "type=" + type; return umbRequestHelper.resourcePromise( diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/macro.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/macro.resource.js index 9ba64838c9..06b5f9496c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/macro.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/macro.resource.js @@ -36,7 +36,7 @@ function macroResource($q, $http, umbRequestHelper) { * @methodOf umbraco.resources.macroResource * * @description - * Gets the result of a macro as html to display in the rich text editor + * Gets the result of a macro as html to display in the rich text editor or in the Grid * * @param {int} macroId The macro id to get parameters for * @param {int} pageId The current page id @@ -45,39 +45,17 @@ function macroResource($q, $http, umbRequestHelper) { */ getMacroResultAsHtmlForEditor: function (macroAlias, pageId, macroParamDictionary) { - //need to format the query string for the custom dictionary - var query = "macroAlias=" + macroAlias + "&pageId=" + pageId; - if (macroParamDictionary) { - var counter = 0; - _.each(macroParamDictionary, function (val, key) { - //check for null - val = val ? val : ""; - //need to detect if the val is a string or an object - if (!angular.isString(val)) { - //if it's not a string we'll send it through the json serializer - var json = angular.toJson(val); - //then we need to url encode it so that it's safe - val = encodeURIComponent(json); - } - else { - //we still need to encode the string, it could contain line breaks, etc... - val = encodeURIComponent(val); - } - - query += "¯oParams[" + counter + "].key=" + key + "¯oParams[" + counter + "].value=" + val; - counter++; - }); - } - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "macroApiBaseUrl", - "GetMacroResultAsHtmlForEditor", - query)), - 'Failed to retrieve macro result for macro with alias ' + macroAlias); + $http.post( + umbRequestHelper.getApiUrl( + "macroApiBaseUrl", + "GetMacroResultAsHtmlForEditor"), { + macroAlias: macroAlias, + pageId: pageId, + macroParams: macroParamDictionary + }), + 'Failed to retrieve macro result for macro with alias ' + macroAlias); } - }; } diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js index 12963a1ea7..f6443735cf 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js @@ -336,7 +336,7 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { * * @description * Saves changes made to a media item, if the media item is new, the isNew paramater must be passed to force creation - * if the media item needs to have files attached, they must be provided as the files param and passed seperately + * if the media item needs to have files attached, they must be provided as the files param and passed separately * * * ##usage @@ -392,7 +392,45 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { }), 'Failed to add folder'); }, - + + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#getChildFolders + * @methodOf umbraco.resources.mediaResource + * + * @description + * Retrieves all media children with types used as folders. + * Uses the convention of looking for media items with mediaTypes ending in + * *Folder so will match "Folder", "bannerFolder", "secureFolder" etc, + * + * ##usage + *
    +         * mediaResource.getChildFolders(1234)
    +         *    .then(function(data) {
    +         *        alert('folders');
    +         *    });
    +         * 
    + * + * @param {int} parentId Id of the media item to query for child folders + * @returns {Promise} resourcePromise object. + * + */ + getChildFolders: function(parentId){ + if(!parentId){ + parentId = -1; + } + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetChildFolders", + [ + { id: parentId } + ])), + 'Failed to retrieve child folders for media item ' + parentId); + }, + /** * @ngdoc method * @name umbraco.resources.mediaResource#emptyRecycleBin diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/mediatype.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/mediatype.resource.js index 94699d4195..555889e5a3 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/mediatype.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/mediatype.resource.js @@ -3,10 +3,20 @@ * @name umbraco.resources.mediaTypeResource * @description Loads in data for media types **/ -function mediaTypeResource($q, $http, umbRequestHelper) { +function mediaTypeResource($q, $http, umbRequestHelper, umbDataFormatter) { return { + getAvailableCompositeContentTypes: function (contentTypeId) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaTypeApiBaseUrl", + "GetAvailableCompositeMediaTypes", + [{ contentTypeId: contentTypeId }])), + 'Failed to retrieve data for content type id ' + contentTypeId); + }, + /** * @ngdoc method * @name umbraco.resources.mediaTypeResource#getAllowedTypes @@ -35,6 +45,122 @@ function mediaTypeResource($q, $http, umbRequestHelper) { "GetAllowedChildren", [{ contentId: mediaId }])), 'Failed to retrieve allowed types for media id ' + mediaId); + }, + + getById: function (id) { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaTypeApiBaseUrl", + "GetById", + [{ id: id }])), + 'Failed to retrieve content type'); + }, + + getAll: function () { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaTypeApiBaseUrl", + "GetAll")), + 'Failed to retrieve all content types'); + }, + + getScaffold: function (parentId) { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaTypeApiBaseUrl", + "GetEmpty", { parentId: parentId })), + 'Failed to retrieve content type scaffold'); + }, + + deleteById: function (id) { + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "mediaTypeApiBaseUrl", + "DeleteById", + [{ id: id }])), + 'Failed to retrieve content type'); + }, + + deleteContainerById: function (id) { + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "mediaTypeApiBaseUrl", + "DeleteContainer", + [{ id: id }])), + 'Failed to delete content type contaier'); + }, + + save: function (contentType) { + + var saveModel = umbDataFormatter.formatContentTypePostData(contentType); + + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("mediaTypeApiBaseUrl", "PostSave"), saveModel), + 'Failed to save data for content type id ' + contentType.id); + }, + + /** + * @ngdoc method + * @name umbraco.resources.mediaTypeResource#move + * @methodOf umbraco.resources.mediaTypeResource + * + * @description + * Moves a node underneath a new parentId + * + * ##usage + *
    +         * mediaTypeResource.move({ parentId: 1244, id: 123 })
    +         *    .then(function() {
    +         *        alert("node was moved");
    +         *    }, function(err){
    +         *      alert("node didnt move:" + err.data.Message); 
    +         *    });
    +         * 
    + * @param {Object} args arguments object + * @param {Int} args.idd the ID of the node to move + * @param {Int} args.parentId the ID of the parent node to move to + * @returns {Promise} resourcePromise object. + * + */ + move: function (args) { + if (!args) { + throw "args cannot be null"; + } + if (!args.parentId) { + throw "args.parentId cannot be null"; + } + if (!args.id) { + throw "args.id cannot be null"; + } + + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("mediaTypeApiBaseUrl", "PostMove"), + { + parentId: args.parentId, + id: args.id + }), + 'Failed to move content'); + }, + + createContainer: function(parentId, name) { + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "mediaTypeApiBaseUrl", + "PostCreateContainer", + { parentId: parentId, name: name })), + 'Failed to create a folder under parent id ' + parentId); } }; diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/member.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/member.resource.js index 381019fb5b..42db4f6366 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/member.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/member.resource.js @@ -203,7 +203,7 @@ function memberResource($q, $http, umbDataFormatter, umbRequestHelper) { * * @description * Saves changes made to a member, if the member is new, the isNew paramater must be passed to force creation - * if the member needs to have files attached, they must be provided as the files param and passed seperately + * if the member needs to have files attached, they must be provided as the files param and passed separately * * * ##usage diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/membertype.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/membertype.resource.js index 587ee4c58b..d949844a6c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/membertype.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/membertype.resource.js @@ -3,10 +3,20 @@ * @name umbraco.resources.memberTypeResource * @description Loads in data for member types **/ -function memberTypeResource($q, $http, umbRequestHelper) { +function memberTypeResource($q, $http, umbRequestHelper, umbDataFormatter) { return { + getAvailableCompositeContentTypes: function (contentTypeId) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "memberTypeApiBaseUrl", + "GetAvailableCompositeMemberTypes", + [{ contentTypeId: contentTypeId }])), + 'Failed to retrieve data for content type id ' + contentTypeId); + }, + //return all member types getTypes: function () { @@ -16,6 +26,59 @@ function memberTypeResource($q, $http, umbRequestHelper) { "memberTypeApiBaseUrl", "GetAllTypes")), 'Failed to retrieve data for member types id'); + }, + + getById: function (id) { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "memberTypeApiBaseUrl", + "GetById", + [{ id: id }])), + 'Failed to retrieve content type'); + }, + + deleteById: function (id) { + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "memberTypeApiBaseUrl", + "DeleteById", + [{ id: id }])), + 'Failed to delete member type'); + }, + + getScaffold: function () { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "memberTypeApiBaseUrl", + "GetEmpty")), + 'Failed to retrieve content type scaffold'); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentTypeResource#save + * @methodOf umbraco.resources.contentTypeResource + * + * @description + * Saves or update a member type + * + * @param {Object} content data type object to create/update + * @returns {Promise} resourcePromise object. + * + */ + save: function (contentType) { + + var saveModel = umbDataFormatter.formatContentTypePostData(contentType); + + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("memberTypeApiBaseUrl", "PostSave"), saveModel), + 'Failed to save data for member type id ' + contentType.id); } }; diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/models.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/models.resource.js new file mode 100644 index 0000000000..fb3cb0f68c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/resources/models.resource.js @@ -0,0 +1,25 @@ +function modelsResource($q, $http, umbRequestHelper) { + + // fixme - should use BackOfficeController to register urls? How can we extend it? + + return { + getModelsOutOfDateStatus: function() { + return umbRequestHelper.resourcePromise( + $http.get( + /*umbRequestHelper.getApiUrl( + "modelsApiBaseUrl", + "GetModelsOutOfDateStatus")*/ "/Umbraco/BackOffice/Zbu/ModelsBuilderApi/GetModelsOutOfDateStatus"), + "Failed to get models out-of-date status"); + }, + + buildModels: function() { + return umbRequestHelper.resourcePromise( + $http.get( + /*umbRequestHelper.getApiUrl( + "modelsApiBaseUrl", + "BuildModels")*/ "/Umbraco/BackOffice/Zbu/ModelsBuilderApi/BuildModels"), + "Failed to build models"); + } + }; +} +angular.module("umbraco.resources").factory("modelsResource", modelsResource); diff --git a/src/Umbraco.Web.UI.Client/src/common/security/interceptor.js b/src/Umbraco.Web.UI.Client/src/common/security/interceptor.js index 56e0eb3041..2b757707ba 100644 --- a/src/Umbraco.Web.UI.Client/src/common/security/interceptor.js +++ b/src/Umbraco.Web.UI.Client/src/common/security/interceptor.js @@ -1,6 +1,6 @@ angular.module('umbraco.security.interceptor', ['umbraco.security.retryQueue']) // This http interceptor listens for authentication successes and failures - .factory('securityInterceptor', ['$injector', 'securityRetryQueue', 'notificationsService', function ($injector, queue, notifications) { + .factory('securityInterceptor', ['$injector', 'securityRetryQueue', 'notificationsService', 'requestInterceptorFilter', function ($injector, queue, notifications, requestInterceptorFilter) { return function(promise) { return promise.then( @@ -19,6 +19,19 @@ angular.module('umbraco.security.interceptor', ['umbraco.security.retryQueue']) return promise; }, function(originalResponse) { // Intercept failed requests + + //Here we'll check if we should ignore the error, this will be based on an original header set + var headers = originalResponse.config ? originalResponse.config.headers : {}; + if (headers["x-umb-ignore-error"] === "ignore") { + //exit/ignore + return promise; + } + var filtered = _.find(requestInterceptorFilter(), function(val) { + return originalResponse.config.url.indexOf(val) > 0; + }); + if (filtered) { + return promise; + } //A 401 means that the user is not logged in if (originalResponse.status === 401) { @@ -72,6 +85,10 @@ angular.module('umbraco.security.interceptor', ['umbraco.security.retryQueue']) }; }]) + .value('requestInterceptorFilter', function() { + return ["www.gravatar.com"]; + }) + // We have to add the interceptor to the queue as a string because the interceptor depends upon service instances that are not available in the config block. .config(['$httpProvider', function ($httpProvider) { $httpProvider.responseInterceptors.push('securityInterceptor'); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/appstate.service.js b/src/Umbraco.Web.UI.Client/src/common/services/appstate.service.js index 1177da323d..e51310f584 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/appstate.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/appstate.service.js @@ -43,7 +43,8 @@ function appState(eventsService) { showTray: null, stickyNavigation: null, navMode: null, - isReady: null + isReady: null, + isTablet: null }; var sectionState = { diff --git a/src/Umbraco.Web.UI.Client/src/common/services/assets.service.js b/src/Umbraco.Web.UI.Client/src/common/services/assets.service.js index 28ac072ea6..ff54c5c222 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/assets.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/assets.service.js @@ -242,18 +242,19 @@ angular.module('umbraco.services') _.each(assets, function (asset) { LazyLoad.js(appendRnd(asset.path), function () { + asset.state = "loaded"; if (!scope) { - asset.state = "loaded"; asset.deferred.resolve(true); - } else { - asset.state = "loaded"; + } + else { angularHelper.safeApply(scope, function () { asset.deferred.resolve(true); }); } }); }); - } else { + } + else { //return and resolve var deferred = $q.defer(); promise = deferred.promise; 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 0c39bdfa38..d21f297a91 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 @@ -2,13 +2,32 @@ /** * @ngdoc service * @name umbraco.services.contentEditingHelper -* @description A helper service for most editors, some methods are specific to content/media/member model types but most are used by +* @description A helper service for most editors, some methods are specific to content/media/member model types but most are used by * all editors to share logic and reduce the amount of replicated code among editors. **/ function contentEditingHelper(fileManager, $q, $location, $routeParams, notificationsService, serverValidationManager, dialogService, formHelper, appState, keyboardService) { + function isValidIdentifier(id){ + //empty id <= 0 + if(angular.isNumber(id) && id > 0){ + return true; + } + + //empty guid + if(id === "00000000-0000-0000-0000-000000000000"){ + return false; + } + + //empty string / alias + if(id === ""){ + return false; + } + + return true; + } + return { - + /** Used by the content editor and mini content editor to perform saving operations */ contentEditorPerformSave: function (args) { if (!angular.isObject(args)) { @@ -29,8 +48,11 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica var self = this; + //we will use the default one for content if not specified + var rebindCallback = args.rebindCallback === undefined ? self.reBindChangedProperties : args.rebindCallback; + var deferred = $q.defer(); - + if (!args.scope.busy && formHelper.submitForm({ scope: args.scope, statusMessage: args.statusMessage })) { args.scope.busy = true; @@ -43,7 +65,9 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica self.handleSuccessfulSave({ scope: args.scope, savedContent: data, - rebindCallback: self.reBindChangedProperties(args.content, data) + rebindCallback: function() { + rebindCallback.apply(self, [args.content, data]); + } }); args.scope.busy = false; @@ -53,8 +77,16 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica self.handleSaveError({ redirectOnFailure: true, err: err, - rebindCallback: self.reBindChangedProperties(args.content, err.data) + rebindCallback: function() { + rebindCallback.apply(self, [args.content, err.data]); + } }); + //show any notifications + if (angular.isArray(err.data.notifications)) { + for (var i = 0; i < err.data.notifications.length; i++) { + notificationsService.showNotification(err.data.notifications[i]); + } + } args.scope.busy = false; deferred.reject(err); }); @@ -65,9 +97,10 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica return deferred.promise; }, - + + /** Returns the action button definitions based on what permissions the user has. - The content.allowedActions parameter contains a list of chars, each represents a button by permission so + The content.allowedActions parameter contains a list of chars, each represents a button by permission so here we'll build the buttons according to the chars of the user. */ configureContentEditorButtons: function (args) { @@ -130,7 +163,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica handler: args.methods.unPublish }; default: - return null; + return null; } } @@ -154,8 +187,8 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica //Now we need to make the drop down button list, this is also slightly tricky because: //We cannot have any buttons if there's no default button above. - //We cannot have the unpublish button (Z) when there's no publish permission. - //We cannot have the unpublish button (Z) when the item is not published. + //We cannot have the unpublish button (Z) when there's no publish permission. + //We cannot have the unpublish button (Z) when the item is not published. if (buttons.defaultButton) { //get the last index of the button order @@ -168,7 +201,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica } - //if we are not creating, then we should add unpublish too, + //if we are not creating, then we should add unpublish too, // so long as it's already published and if the user has access to publish if (!args.create) { if (args.content.publishDate && _.contains(args.content.allowedActions, "U")) { @@ -229,13 +262,13 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica } } } - + actions.push(defaultAction); //Now we need to make the drop down button list, this is also slightly tricky because: //We cannot have any buttons if there's no default button above. - //We cannot have the unpublish button (Z) when there's no publish permission. - //We cannot have the unpublish button (Z) when the item is not published. + //We cannot have the unpublish button (Z) when there's no publish permission. + //We cannot have the unpublish button (Z) when the item is not published. if (defaultAction) { //get the last index of the button order var lastIndex = _.indexOf(actionOrder, defaultAction); @@ -247,7 +280,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica } } - //if we are not creating, then we should add unpublish too, + //if we are not creating, then we should add unpublish too, // so long as it's already published and if the user has access to publish if (!creating) { if (content.publishDate && _.contains(content.allowedActions,"U")) { @@ -323,7 +356,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica var allOrigProps = this.getAllProps(origContent); var allNewProps = this.getAllProps(savedContent); - function getNewProp(alias) { + function getNewProp(alias) { return _.find(allNewProps, function (item) { return item.alias === alias; }); @@ -337,12 +370,12 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica }; //check for changed built-in properties of the content for (var o in origContent) { - + //ignore the ones listed in the array if (shouldIgnore(o)) { continue; } - + if (!_.isEqual(origContent[o], savedContent[o])) { origContent[o] = savedContent[o]; } @@ -356,8 +389,8 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica //they have changed so set the origContent prop to the new one var origVal = allOrigProps[p].value; allOrigProps[p].value = newProp.value; - - //instead of having a property editor $watch their expression to check if it has + + //instead of having a property editor $watch their expression to check if it has // been updated, instead we'll check for the existence of a special method on their model // and just call it. if (angular.isFunction(allOrigProps[p].onValueChanged)) { @@ -382,7 +415,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica * A function to handle what happens when we have validation issues from the server side */ handleSaveError: function (args) { - + if (!args.err) { throw "args.err cannot be null"; } @@ -396,7 +429,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica if (args.err.status === 400) { //now we need to look through all the validation errors if (args.err.data && (args.err.data.ModelState)) { - + //wire up the server validation errs formHelper.handleServerValidation(args.err.data.ModelState); @@ -408,7 +441,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica if (args.rebindCallback && angular.isFunction(args.rebindCallback)) { args.rebindCallback(); } - + serverValidationManager.executeAndClearAllSubscriptions(); } @@ -447,7 +480,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica } if (!this.redirectToCreatedContent(args.redirectId ? args.redirectId : args.savedContent.id)) { - + //we are not redirecting because this is not new content, it is existing content. In this case // we need to detect what properties have changed and re-bind them with the server data. //call the callback @@ -465,14 +498,14 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica * * @description * Changes the location to be editing the newly created content after create was successful. - * We need to decide if we need to redirect to edito mode or if we will remain in create mode. - * We will only need to maintain create mode if we have not fulfilled the basic requirements for creating an entity which is at least having a name. + * We need to decide if we need to redirect to edito mode or if we will remain in create mode. + * We will only need to maintain create mode if we have not fulfilled the basic requirements for creating an entity which is at least having a name and ID */ redirectToCreatedContent: function (id, modelState) { //only continue if we are currently in create mode and if there is no 'Name' modelstate errors // since we need at least a name to create content. - if ($routeParams.create && (!modelState || !modelState["Name"])) { + if ($routeParams.create && (isValidIdentifier(id) && (!modelState || !modelState["Name"]))) { //need to change the location to not be in 'create' mode. Currently the route will be something like: // /belle/#/content/edit/1234?doctype=newsArticle&create=true @@ -481,7 +514,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica //clear the query strings $location.search(""); - + //change to new path $location.path("/" + $routeParams.section + "/" + $routeParams.tree + "/" + $routeParams.method + "/" + id); //don't add a browser history for this @@ -492,4 +525,4 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica } }; } -angular.module('umbraco.services').factory('contentEditingHelper', contentEditingHelper); \ No newline at end of file +angular.module('umbraco.services').factory('contentEditingHelper', contentEditingHelper); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/contenttypehelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/contenttypehelper.service.js new file mode 100644 index 0000000000..f9ffcd4a23 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/services/contenttypehelper.service.js @@ -0,0 +1,279 @@ +/** + * @ngdoc service + * @name umbraco.services.contentTypeHelper + * @description A helper service for the content type editor + **/ +function contentTypeHelper(contentTypeResource, dataTypeResource, $filter) { + + var contentTypeHelperService = { + + createIdArray: function(array) { + + var newArray = []; + + angular.forEach(array, function(arrayItem){ + + if(angular.isObject(arrayItem)) { + newArray.push(arrayItem.id); + } else { + newArray.push(arrayItem); + } + + }); + + return newArray; + + }, + + makeObjectArrayFromId: function (idArray, objectArray) { + var newArray = []; + + for (var idIndex = 0; idArray.length > idIndex; idIndex++) { + var id = idArray[idIndex]; + + for (var objectIndex = 0; objectArray.length > objectIndex; objectIndex++) { + var object = objectArray[objectIndex]; + if (id === object.id) { + newArray.push(object); + } + } + + } + + return newArray; + }, + + mergeCompositeContentType: function(contentType, compositeContentType) { + + angular.forEach(compositeContentType.groups, function(compositionGroup) { + + // order composition groups based on sort order + compositionGroup.properties = $filter('orderBy')(compositionGroup.properties, 'sortOrder'); + + // get data type details + angular.forEach(compositionGroup.properties, function(property) { + dataTypeResource.getById(property.dataTypeId) + .then(function(dataType) { + property.dataTypeIcon = dataType.icon; + property.dataTypeName = dataType.name; + }); + }); + + // set inherited state on tab + compositionGroup.inherited = true; + + // set inherited state on properties + angular.forEach(compositionGroup.properties, function(compositionProperty) { + compositionProperty.inherited = true; + }); + + // set tab state + compositionGroup.tabState = "inActive"; + + // if groups are named the same - merge the groups + angular.forEach(contentType.groups, function(contentTypeGroup) { + + if (contentTypeGroup.name === compositionGroup.name) { + + // set flag to show if properties has been merged into a tab + compositionGroup.groupIsMerged = true; + + // make group inherited + contentTypeGroup.inherited = true; + + // add properties to the top of the array + contentTypeGroup.properties = compositionGroup.properties.concat(contentTypeGroup.properties); + + // update sort order on all properties in merged group + contentTypeGroup.properties = contentTypeHelperService.updatePropertiesSortOrder(contentTypeGroup.properties); + + // make parentTabContentTypeNames to an array so we can push values + if (contentTypeGroup.parentTabContentTypeNames === null || contentTypeGroup.parentTabContentTypeNames === undefined) { + contentTypeGroup.parentTabContentTypeNames = []; + } + + // push name to array of merged composite content types + contentTypeGroup.parentTabContentTypeNames.push(compositeContentType.name); + + // make parentTabContentTypes to an array so we can push values + if (contentTypeGroup.parentTabContentTypes === null || contentTypeGroup.parentTabContentTypes === undefined) { + contentTypeGroup.parentTabContentTypes = []; + } + + // push id to array of merged composite content types + contentTypeGroup.parentTabContentTypes.push(compositeContentType.id); + + // get sort order from composition + contentTypeGroup.sortOrder = compositionGroup.sortOrder; + + // splice group to the top of the array + var contentTypeGroupCopy = angular.copy(contentTypeGroup); + var index = contentType.groups.indexOf(contentTypeGroup); + contentType.groups.splice(index, 1); + contentType.groups.unshift(contentTypeGroupCopy); + + } + + }); + + // if group is not merged - push it to the end of the array - before init tab + if (compositionGroup.groupIsMerged === false || compositionGroup.groupIsMerged === undefined) { + + // make parentTabContentTypeNames to an array so we can push values + if (compositionGroup.parentTabContentTypeNames === null || compositionGroup.parentTabContentTypeNames === undefined) { + compositionGroup.parentTabContentTypeNames = []; + } + + // push name to array of merged composite content types + compositionGroup.parentTabContentTypeNames.push(compositeContentType.name); + + // make parentTabContentTypes to an array so we can push values + if (compositionGroup.parentTabContentTypes === null || compositionGroup.parentTabContentTypes === undefined) { + compositionGroup.parentTabContentTypes = []; + } + + // push id to array of merged composite content types + compositionGroup.parentTabContentTypes.push(compositeContentType.id); + + // push group before placeholder tab + contentType.groups.unshift(compositionGroup); + + } + + }); + + // sort all groups by sortOrder property + contentType.groups = $filter('orderBy')(contentType.groups, 'sortOrder'); + + return contentType; + + }, + + splitCompositeContentType: function (contentType, compositeContentType) { + + var groups = []; + + angular.forEach(contentType.groups, function(contentTypeGroup){ + + if( contentTypeGroup.tabState !== "init" ) { + + var idIndex = contentTypeGroup.parentTabContentTypes.indexOf(compositeContentType.id); + var nameIndex = contentTypeGroup.parentTabContentTypeNames.indexOf(compositeContentType.name); + var groupIndex = contentType.groups.indexOf(contentTypeGroup); + + + if( idIndex !== -1 ) { + + var properties = []; + + // remove all properties from composite content type + angular.forEach(contentTypeGroup.properties, function(property){ + if(property.contentTypeId !== compositeContentType.id) { + properties.push(property); + } + }); + + // set new properties array to properties + contentTypeGroup.properties = properties; + + // remove composite content type name and id from inherited arrays + contentTypeGroup.parentTabContentTypes.splice(idIndex, 1); + contentTypeGroup.parentTabContentTypeNames.splice(nameIndex, 1); + + // remove inherited state if there are no inherited properties + if(contentTypeGroup.parentTabContentTypes.length === 0) { + contentTypeGroup.inherited = false; + } + + // remove group if there are no properties left + if(contentTypeGroup.properties.length > 1) { + //contentType.groups.splice(groupIndex, 1); + groups.push(contentTypeGroup); + } + + } else { + groups.push(contentTypeGroup); + } + + } else { + groups.push(contentTypeGroup); + } + + // update sort order on properties + contentTypeGroup.properties = contentTypeHelperService.updatePropertiesSortOrder(contentTypeGroup.properties); + + }); + + contentType.groups = groups; + + }, + + updatePropertiesSortOrder: function (properties) { + + var sortOrder = 0; + + angular.forEach(properties, function(property) { + if( !property.inherited && property.propertyState !== "init") { + property.sortOrder = sortOrder; + } + sortOrder++; + }); + + return properties; + + }, + + getTemplatePlaceholder: function() { + + var templatePlaceholder = { + "name": "", + "icon": "icon-layout", + "alias": "templatePlaceholder", + "placeholder": true + }; + + return templatePlaceholder; + + }, + + insertDefaultTemplatePlaceholder: function(defaultTemplate) { + + // get template placeholder + var templatePlaceholder = contentTypeHelperService.getTemplatePlaceholder(); + + // add as default template + defaultTemplate = templatePlaceholder; + + return defaultTemplate; + + }, + + insertTemplatePlaceholder: function(array) { + + // get template placeholder + var templatePlaceholder = contentTypeHelperService.getTemplatePlaceholder(); + + // add as selected item + array.push(templatePlaceholder); + + return array; + + }, + + insertChildNodePlaceholder: function (array, name, icon, id) { + + var placeholder = { + "name": name, + "icon": icon, + "id": id + }; + + array.push(placeholder); + + } + + }; + + return contentTypeHelperService; +} +angular.module('umbraco.services').factory('contentTypeHelper', contentTypeHelper); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/datatypehelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/datatypehelper.service.js new file mode 100644 index 0000000000..3cde632d4b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/services/datatypehelper.service.js @@ -0,0 +1,55 @@ +/** + * @ngdoc service + * @name umbraco.services.dataTypeHelper + * @description A helper service for data types + **/ +function dataTypeHelper() { + + var dataTypeHelperService = { + + createPreValueProps: function(preVals) { + + var preValues = []; + + for (var i = 0; i < preVals.length; i++) { + preValues.push({ + hideLabel: preVals[i].hideLabel, + alias: preVals[i].key, + description: preVals[i].description, + label: preVals[i].label, + view: preVals[i].view, + value: preVals[i].value + }); + } + + return preValues; + + }, + + rebindChangedProperties: function (origContent, savedContent) { + + //a method to ignore built-in prop changes + var shouldIgnore = function (propName) { + return _.some(["notifications", "ModelState"], function (i) { + return i === propName; + }); + }; + //check for changed built-in properties of the content + for (var o in origContent) { + + //ignore the ones listed in the array + if (shouldIgnore(o)) { + continue; + } + + if (!_.isEqual(origContent[o], savedContent[o])) { + origContent[o] = savedContent[o]; + } + } + } + + }; + + return dataTypeHelperService; +} +angular.module('umbraco.services').factory('dataTypeHelper', dataTypeHelper); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/services/dialog.service.js b/src/Umbraco.Web.UI.Client/src/common/services/dialog.service.js index 00af2c57cb..263397787d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/dialog.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/dialog.service.js @@ -2,13 +2,13 @@ * @ngdoc service * @name umbraco.services.dialogService * - * @requires $rootScope + * @requires $rootScope * @requires $compile * @requires $http * @requires $log * @requires $q * @requires $templateCache - * + * * @description * Application-wide service for handling modals, overlays and dialogs * By default it injects the passed template url into a div to body of the document @@ -22,10 +22,10 @@ *
      *    var dialog = dialogService.open({template: 'path/to/page.html', show: true, callback: done});
      *    functon done(data){
    - *      //The dialog has been submitted 
    + *      //The dialog has been submitted
      *      //data contains whatever the dialog has selected / attached
    - *    }     
    - * 
    + * } + * */ angular.module('umbraco.services') @@ -38,12 +38,12 @@ angular.module('umbraco.services') for (var i = 0; i < dialogs.length; i++) { var dialog = dialogs[i]; - //very special flag which means that global events cannot close this dialog - currently only used on the login + //very special flag which means that global events cannot close this dialog - currently only used on the login // dialog since it's special and cannot be closed without logging in. if (!dialog.manualClose) { dialog.close(args); } - + } } @@ -56,28 +56,18 @@ angular.module('umbraco.services') //this is not entirely enough since the damn webforms scriploader still complains if (dialog.iframe) { dialog.element.find("iframe").attr("src", "about:blank"); - $timeout(function () { - //we need to do more than just remove the element, this will not destroy the - // scope in angular 1.1x, in angular 1.2x this is taken care of but if we dont - // take care of this ourselves we have memory leaks. - dialog.element.remove(); - //SD: No idea why this is required but was there before - pretty sure it's not required - $("#" + dialog.element.attr("id")).remove(); - dialog.scope.$destroy(); - }, 1000); - } else { - //we need to do more than just remove the element, this will not destroy the - // scope in angular 1.1x, in angular 1.2x this is taken care of but if we dont - // take care of this ourselves we have memory leaks. - dialog.element.remove(); - //SD: No idea why this is required but was there before - pretty sure it's not required - $("#" + dialog.element.attr("id")).remove(); - dialog.scope.$destroy(); } - } - //remove 'this' dialog from the dialogs array - dialogs = _.reject(dialogs, function (i) { return i === dialog; }); + dialog.scope.$destroy(); + + //we need to do more than just remove the element, this will not destroy the + // scope in angular 1.1x, in angular 1.2x this is taken care of but if we dont + // take care of this ourselves we have memory leaks. + dialog.element.remove(); + + //remove 'this' dialog from the dialogs array + dialogs = _.reject(dialogs, function (i) { return i === dialog; }); + } } /** Internal method that handles opening all dialogs */ @@ -93,24 +83,24 @@ angular.module('umbraco.services') template: "views/common/notfound.html", callback: undefined, closeCallback: undefined, - element: undefined, + element: undefined, // It will set this value as a property on the dialog controller's scope as dialogData, - // used to pass in custom data to the dialog controller's $scope. Though this is near identical to - // the dialogOptions property that is also set the the dialog controller's $scope object. + // used to pass in custom data to the dialog controller's $scope. Though this is near identical to + // the dialogOptions property that is also set the the dialog controller's $scope object. // So there's basically 2 ways of doing the same thing which we're now stuck with and in fact // dialogData has another specially attached property called .selection which gets used. dialogData: undefined }; var dialog = angular.extend(defaults, options); - + //NOTE: People should NOT pass in a scope object that is legacy functoinality and causes problems. We will ALWAYS // destroy the scope when the dialog is closed regardless if it is in use elsewhere which is why it shouldn't be done. var scope = options.scope || $rootScope.$new(); - //Modal dom obj and unique id + //Modal dom obj and set id to old-dialog-service - used until we get all dialogs moved the the new overlay directive dialog.element = $('
    '); - var id = dialog.template.replace('.html', '').replace('.aspx', '').replace(/[\/|\.|:\&\?\=]/g, "-") + '-' + scope.$id; + var id = "old-dialog-service"; if (options.inline) { dialog.animation = ""; @@ -156,7 +146,7 @@ angular.module('umbraco.services') dialog.element.css("width", dialog.width); - //Autoshow + //Autoshow if (dialog.show) { dialog.element.modal('show'); } @@ -167,7 +157,7 @@ angular.module('umbraco.services') else { //We need to load the template with an httpget and once it's loaded we'll compile and assign the result to the container - // object. However since the result could be a promise or just data we need to use a $q.when. We still need to return the + // object. However since the result could be a promise or just data we need to use a $q.when. We still need to return the // $modal object so we'll actually return the modal object synchronously without waiting for the promise. Otherwise this openDialog // method will always need to return a promise which gets nasty because of promises in promises plus the result just needs a reference // to the $modal object which will not change (only it's contents will change). @@ -177,7 +167,7 @@ angular.module('umbraco.services') // Build modal object dialog.element.html(template); - //append to body or other container element + //append to body or other container element dialog.container.append(dialog.element); // Compile modal content @@ -224,8 +214,8 @@ angular.module('umbraco.services') scope.close = function (data) { dialog.close(data); }; - - //NOTE: This can ONLY ever be used to show the dialog if dialog.show is false (autoshow). + + //NOTE: This can ONLY ever be used to show the dialog if dialog.show is false (autoshow). // You CANNOT call show() after you call hide(). hide = close, they are the same thing and once // a dialog is closed it's resources are disposed of. scope.show = function () { @@ -237,7 +227,7 @@ angular.module('umbraco.services') //just show normally dialog.element.modal('show'); } - + }; scope.select = function (item) { @@ -266,11 +256,11 @@ angular.module('umbraco.services') dialog.scope = scope; - //Autoshow + //Autoshow if (dialog.show) { scope.show(); } - + }); //Return the modal object outside of the promise! @@ -368,7 +358,7 @@ angular.module('umbraco.services') * @param {Function} options.callback callback function * @returns {Object} modal object */ - contentPicker: function (options) { + contentPicker: function (options) { options.treeAlias = "content"; options.section = "content"; @@ -424,7 +414,7 @@ angular.module('umbraco.services') * @returns {Object} modal object */ memberPicker: function (options) { - + options.treeAlias = "member"; options.section = "member"; @@ -511,7 +501,7 @@ angular.module('umbraco.services') * @name umbraco.services.dialogService#embedDialog * @methodOf umbraco.services.dialogService * @description - * Opens a dialog to an embed dialog + * Opens a dialog to an embed dialog */ embedDialog: function (options) { options.template = 'views/common/dialogs/rteembed.html'; @@ -546,4 +536,4 @@ angular.module('umbraco.services') return openDialog(options); } }; -}); \ No newline at end of file +}); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js index 057e0b8cff..4b5521b8db 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js @@ -7,7 +7,7 @@ * A utility class used to streamline how forms are developed, to ensure that validation is check and displayed consistently and to ensure that the correct events * fire when they need to. */ -function formHelper(angularHelper, serverValidationManager, $timeout, notificationsService, dialogService) { +function formHelper(angularHelper, serverValidationManager, $timeout, notificationsService, dialogService, localizationService) { return { /** @@ -157,9 +157,19 @@ function formHelper(angularHelper, serverValidationManager, $timeout, notificati * * @param {object} err The error object returned from the http promise */ - handleServerValidation: function(modelState) { + handleServerValidation: function (modelState) { for (var e in modelState) { + //This is where things get interesting.... + // We need to support validation for all editor types such as both the content and content type editors. + // The Content editor ModelState is quite specific with the way that Properties are validated especially considering + // that each property is a User Developer property editor. + // The way that Content Type Editor ModelState is created is simply based on the ASP.Net validation data-annotations + // system. + // So, to do this (since we need to support backwards compat), we need to hack a little bit. For Content Properties, + // which are user defined, we know that they will exist with a prefixed ModelState of "_Properties.", so if we detect + // this, then we know it's a Property. + //the alias in model state can be in dot notation which indicates // * the first part is the content property alias // * the second part is the field to which the valiation msg is associated with @@ -167,7 +177,11 @@ function formHelper(angularHelper, serverValidationManager, $timeout, notificati //If it is not prefixed with "Properties" that means the error is for a field of the object directly. var parts = e.split("."); - if (parts.length > 1) { + + //Check if this is for content properties - specific to content/media/member editors because those are special + // user defined properties with custom controls. + if (parts.length > 1 && parts[0] === "_Properties") { + var propertyAlias = parts[1]; //if it contains 2 '.' then we will wire it up to a property's field @@ -182,12 +196,15 @@ function formHelper(angularHelper, serverValidationManager, $timeout, notificati } else { - //the parts are only 1, this means its not a property but a native content property - serverValidationManager.addFieldError(parts[0], modelState[e][0]); + + //Everthing else is just a 'Field'... the field name could contain any level of 'parts' though, for example: + // Groups[0].Properties[2].Alias + serverValidationManager.addFieldError(e, modelState[e][0]); } //add to notifications notificationsService.error("Validation", modelState[e][0]); + } } }; 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 new file mode 100644 index 0000000000..e7bfe59d42 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/services/listviewhelper.service.js @@ -0,0 +1,288 @@ +(function() { + 'use strict'; + + function listViewHelper($cookieStore) { + + var firstSelectedIndex = 0; + + function getLayout(nodeId, availableLayouts) { + + var storedLayouts = []; + + if ($cookieStore.get("umblistViewLayout")) { + storedLayouts = $cookieStore.get("umblistViewLayout"); + } + + if (storedLayouts && storedLayouts.length > 0) { + for (var i = 0; storedLayouts.length > i; i++) { + var layout = storedLayouts[i]; + if (layout.nodeId === nodeId) { + return setLayout(nodeId, layout, availableLayouts); + } + } + + } + + return getFirstAllowedLayout(availableLayouts); + + } + + function setLayout(nodeId, selectedLayout, availableLayouts) { + + var activeLayout = {}; + var layoutFound = false; + + for (var i = 0; availableLayouts.length > i; i++) { + var layout = availableLayouts[i]; + if (layout.path === selectedLayout.path) { + activeLayout = layout; + layout.active = true; + layoutFound = true; + } else { + layout.active = false; + } + } + + if(!layoutFound) { + activeLayout = getFirstAllowedLayout(availableLayouts); + } + + setLayoutCookie(nodeId, activeLayout); + + return activeLayout; + + } + + function setLayoutCookie(nodeId, selectedLayout) { + + var layoutFound = false; + var storedLayouts = []; + + if($cookieStore.get("umblistViewLayout")) { + storedLayouts = $cookieStore.get("umblistViewLayout"); + } + + if(storedLayouts.length > 0) { + for(var i = 0; storedLayouts.length > i; i++) { + var layout = storedLayouts[i]; + if(layout.nodeId === nodeId) { + layout.path = selectedLayout.path; + layoutFound = true; + } + } + } + + if(!layoutFound) { + var cookieObject = { + "nodeId": nodeId, + "path": selectedLayout.path + }; + storedLayouts.push(cookieObject); + } + + document.cookie="umblistViewLayout=" + JSON.stringify(storedLayouts); + + } + + function getFirstAllowedLayout(layouts) { + + var firstAllowedLayout = {}; + + for (var i = 0; layouts.length > i; i++) { + var layout = layouts[i]; + if (layout.selected === true) { + firstAllowedLayout = layout; + break; + } + } + + return firstAllowedLayout; + } + + function selectHandler(selectedItem, selectedIndex, items, selection, $event) { + + var start = 0; + var end = 0; + var item = null; + + if ($event.shiftKey === true) { + + if(selectedIndex > firstSelectedIndex) { + + start = firstSelectedIndex; + end = selectedIndex; + + for (; end >= start; start++) { + item = items[start]; + selectItem(item, selection); + } + + } else { + + start = firstSelectedIndex; + end = selectedIndex; + + for (; end <= start; start--) { + item = items[start]; + selectItem(item, selection); + } + + } + + } else { + + if(selectedItem.selected) { + deselectItem(selectedItem, selection); + } else { + selectItem(selectedItem, selection); + } + + firstSelectedIndex = selectedIndex; + + } + + } + + function selectItem(item, selection) { + var isSelected = false; + for (var i = 0; selection.length > i; i++) { + var selectedItem = selection[i]; + if (item.id === selectedItem.id) { + isSelected = true; + } + } + if(!isSelected) { + selection.push({id: item.id}); + item.selected = true; + } + } + + function deselectItem(item, selection) { + for (var i = 0; selection.length > i; i++) { + var selectedItem = selection[i]; + if (item.id === selectedItem.id) { + selection.splice(i, 1); + item.selected = false; + } + } + } + + function clearSelection(items, folders, selection) { + + var i = 0; + + selection.length = 0; + + if(angular.isArray(items)) { + for(i = 0; items.length > i; i++) { + var item = items[i]; + item.selected = false; + } + } + + if(angular.isArray(items)) { + for(i = 0; folders.length > i; i++) { + var folder = folders[i]; + folder.selected = false; + } + } + } + + function selectAllItems(items, selection, $event) { + + var checkbox = $event.target; + var clearSelection = false; + + if (!angular.isArray(items)) { + return; + } + + selection.length = 0; + + for (var i = 0; i < items.length; i++) { + + var item = items[i]; + + if (checkbox.checked) { + selection.push({id: item.id}); + } else { + clearSelection = true; + } + + item.selected = checkbox.checked; + + } + + if (clearSelection) { + selection.length = 0; + } + + } + + function isSelectedAll(items, selection) { + + var numberOfSelectedItem = 0; + + for(var itemIndex = 0; items.length > itemIndex; itemIndex++) { + var item = items[itemIndex]; + + for(var selectedIndex = 0; selection.length > selectedIndex; selectedIndex++) { + var selectedItem = selection[selectedIndex]; + + if(item.id === selectedItem.id) { + numberOfSelectedItem++; + } + } + + } + + if(numberOfSelectedItem === items.length) { + return true; + } + + } + + + function setSortingDirection(col, direction, options) { + return options.orderBy.toUpperCase() === col.toUpperCase() && options.orderDirection === direction; + } + + + function setSorting(field, allow, options) { + if (allow) { + options.orderBy = field; + + if (options.orderDirection === "desc") { + options.orderDirection = "asc"; + } else { + options.orderDirection = "desc"; + } + } + } + + + + var service = { + getLayout: getLayout, + getFirstAllowedLayout: getFirstAllowedLayout, + setLayout: setLayout, + setLayoutCookie: setLayoutCookie, + selectHandler: selectHandler, + selectItem: selectItem, + deselectItem: deselectItem, + clearSelection: clearSelection, + selectAllItems: selectAllItems, + isSelectedAll: isSelectedAll, + setSortingDirection: setSortingDirection, + setSorting: setSorting + }; + + return service; + + } + + + angular.module('umbraco.services').factory('listViewHelper', listViewHelper); + + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/macro.service.js b/src/Umbraco.Web.UI.Client/src/common/services/macro.service.js index e60ee01ff0..6e78d71e85 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/macro.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/macro.service.js @@ -15,7 +15,7 @@ function macroService() { //This regex will match an alias of anything except characters that are quotes or new lines (for legacy reasons, when new macros are created // their aliases are cleaned an invalid chars are stripped) - var expression = /(<\?UMBRACO_MACRO macroAlias=["']([^\"\'\n\r]+?)["'][\s\S]+?)(\/>|>.*?<\/\?UMBRACO_MACRO>)/i; + var expression = /(<\?UMBRACO_MACRO (?:.+?)?macroAlias=["']([^\"\'\n\r]+?)["'][\s\S]+?)(\/>|>.*?<\/\?UMBRACO_MACRO>)/i; var match = expression.exec(syntax); if (!match || match.length < 3) { return null; @@ -153,6 +153,51 @@ function macroService() { macroString += ")"; return macroString; + }, + + collectValueData: function(macro, macroParams, renderingEngine) { + + var paramDictionary = {}; + var macroAlias = macro.alias; + var syntax; + + _.each(macroParams, function (item) { + + var val = item.value; + + if (item.value !== null && item.value !== undefined && !_.isString(item.value)) { + try { + val = angular.toJson(val); + } + catch (e) { + // not json + } + } + + //each value needs to be xml escaped!! since the value get's stored as an xml attribute + paramDictionary[item.alias] = _.escape(val); + + }); + + //get the syntax based on the rendering engine + if (renderingEngine && renderingEngine === "WebForms") { + syntax = this.generateWebFormsSyntax({ macroAlias: macroAlias, macroParamsDictionary: paramDictionary }); + } + else if (renderingEngine && renderingEngine === "Mvc") { + syntax = this.generateMvcSyntax({ macroAlias: macroAlias, macroParamsDictionary: paramDictionary }); + } + else { + syntax = this.generateMacroSyntax({ macroAlias: macroAlias, macroParamsDictionary: paramDictionary }); + } + + var macroObject = { + "macroParamsDictionary": paramDictionary, + "macroAlias": macroAlias, + "syntax": syntax + }; + + return macroObject; + } }; diff --git a/src/Umbraco.Web.UI.Client/src/common/services/mediahelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/mediahelper.service.js index 68ca32efbc..114e1a0962 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/mediahelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/mediahelper.service.js @@ -335,8 +335,37 @@ function mediaHelper(umbRequestHelper) { var lowered = imagePath.toLowerCase(); var ext = lowered.substr(lowered.lastIndexOf(".") + 1); return ("," + Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes + ",").indexOf("," + ext + ",") !== -1; + }, + + /** + * @ngdoc function + * @name umbraco.services.mediaHelper#formatFileTypes + * @methodOf umbraco.services.mediaHelper + * @function + * + * @description + * Returns a string with correctly formated file types for ng-file-upload + * + * @param {string} file types, ex: jpg,png,tiff + */ + formatFileTypes: function(fileTypes) { + + var fileTypesArray = fileTypes.split(','); + var newFileTypesArray = []; + + for (var i = 0; i < fileTypesArray.length; i++) { + var fileType = fileTypesArray[i]; + + if (fileType.indexOf(".") !== 0) { + fileType = ".".concat(fileType); + } + + newFileTypesArray.push(fileType); + } + + return newFileTypesArray.join(","); + } }; -} -angular.module('umbraco.services').factory('mediaHelper', mediaHelper); \ No newline at end of file +}angular.module('umbraco.services').factory('mediaHelper', mediaHelper); 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 ba4a4b1488..7bc069ef44 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 @@ -2,14 +2,14 @@ * @ngdoc service * @name umbraco.services.navigationService * - * @requires $rootScope + * @requires $rootScope * @requires $routeParams * @requires $log * @requires $location * @requires dialogService * @requires treeService * @requires sectionResource - * + * * @description * Service to handle the main application navigation. Responsible for invoking the tree * Section navigation and search, and maintain their state for the entire application lifetime @@ -17,21 +17,15 @@ */ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeout, $injector, dialogService, umbModelMapper, treeService, notificationsService, historyService, appState, angularHelper) { - var minScreenSize = 1100; + //used to track the current dialog object var currentDialog = null; - //tracks the screen size as a tablet - var isTablet = false; + //the main tree event handler, which gets assigned via the setupTreeEvents method var mainTreeEventHandler = null; //tracks the user profile dialog var userDialog = null; - function setTreeMode() { - isTablet = ($(window).width() <= minScreenSize); - appState.setGlobalState("showNavigation", !isTablet); - } - function setMode(mode) { switch (mode) { case 'tree': @@ -41,8 +35,8 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo appState.setMenuState("showMenuDialog", false); appState.setGlobalState("stickyNavigation", false); appState.setGlobalState("showTray", false); - - //$("#search-form input").focus(); + + //$("#search-form input").focus(); break; case 'menu': appState.setGlobalState("navMode", "menu"); @@ -80,7 +74,7 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo appState.setGlobalState("stickyNavigation", false); appState.setGlobalState("showTray", false); - if (isTablet) { + if (appState.getGlobalState("isTablet") === true) { appState.setGlobalState("showNavigation", false); } @@ -93,9 +87,7 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo /** initializes the navigation service */ init: function() { - setTreeMode(); - - //keep track of the current section - initially this will always be undefined so + //keep track of the current section - initially this will always be undefined so // no point in setting it now until it changes. $rootScope.$watch(function () { return $routeParams.section; @@ -103,10 +95,7 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo appState.setSectionState("currentSection", newVal); }); - //TODO: This does not belong here - would be much better off in a directive - $(window).bind("resize", function() { - setTreeMode(); - }); + }, /** @@ -118,8 +107,8 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo * Shows the legacy iframe and loads in the content based on the source url * @param {String} source The URL to load into the iframe */ - loadLegacyIFrame: function (source) { - $location.path("/" + appState.getSectionState("currentSection") + "/framed/" + encodeURIComponent(source)); + loadLegacyIFrame: function (source) { + $location.path("/" + appState.getSectionState("currentSection").toLowerCase() + "/framed/" + encodeURIComponent(source)); }, /** @@ -143,7 +132,7 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo appState.setSectionState("currentSection", sectionAlias); this.showTree(sectionAlias); - $location.path(sectionAlias); + $location.path(sectionAlias.toLowerCase()); }, /** @@ -160,7 +149,7 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo showTree: function (sectionAlias, syncArgs) { if (sectionAlias !== appState.getSectionState("currentSection")) { appState.setSectionState("currentSection", sectionAlias); - + if (syncArgs) { this.syncTree(syncArgs); } @@ -176,7 +165,7 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo appState.setGlobalState("showTray", false); }, - /** + /** Called to assign the main tree event handler - this is called by the navigation controller. TODO: Potentially another dev could call this which would kind of mung the whole app so potentially there's a better way. */ @@ -190,7 +179,7 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo //when a tree node is synced this event will fire, this allows us to set the currentNode mainTreeEventHandler.bind("treeSynced", function (ev, args) { - + if (args.activate === undefined || args.activate === true) { //set the current selected node appState.setTreeState("selectedNode", args.node); @@ -207,7 +196,7 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo //Set the current action node (this is not the same as the current selected node!) appState.setMenuState("currentNode", args.node); - + if (args.event && args.event.altKey) { args.skipDefault = true; } @@ -231,7 +220,7 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo ev.preventDefault(); if (n.metaData && n.metaData["jsClickCallback"] && angular.isString(n.metaData["jsClickCallback"]) && n.metaData["jsClickCallback"] !== "") { - //this is a legacy tree node! + //this is a legacy tree node! var jsPrefix = "javascript:"; var js; if (n.metaData["jsClickCallback"].startsWith(jsPrefix)) { @@ -254,17 +243,17 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo else if (n.routePath) { //add action to the history service historyService.add({ name: n.name, link: n.routePath, icon: n.icon }); - + //put this node into the tree state appState.setTreeState("selectedNode", args.node); //when a node is clicked we also need to set the active menu node to this node appState.setMenuState("currentNode", args.node); //not legacy, lets just set the route value and clear the query string if there is one. - $location.path(n.routePath).search(""); + $location.path(n.routePath.toLowerCase()).search(""); } else if (args.element.section) { - $location.path(args.element.section).search(""); + $location.path(args.element.section.toLowerCase()).search(""); } service.hideNavigation(); @@ -280,7 +269,7 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo * The path format is: ["itemId","itemId"], and so on * so to sync to a specific document type node do: *
    -         * navigationService.syncTree({tree: 'content', path: ["-1","123d"], forceReload: true});  
    +         * navigationService.syncTree({tree: 'content', path: ["-1","123d"], forceReload: true});
              * 
    * @param {Object} args arguments passed to the function * @param {String} args.tree the tree alias to sync to @@ -298,7 +287,7 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo if (!args.tree) { throw "args.tree cannot be null"; } - + if (mainTreeEventHandler) { //returns a promise return mainTreeEventHandler.syncTree(args); @@ -308,8 +297,8 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo return angularHelper.rejectedPromise(); }, - /** - Internal method that should ONLY be used by the legacy API wrapper, the legacy API used to + /** + Internal method that should ONLY be used by the legacy API wrapper, the legacy API used to have to set an active tree and then sync, the new API does this in one method by using syncTree */ _syncPath: function(path, forceReload) { @@ -333,8 +322,8 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo } }, - /** - Internal method that should ONLY be used by the legacy API wrapper, the legacy API used to + /** + Internal method that should ONLY be used by the legacy API wrapper, the legacy API used to have to set an active tree and then sync, the new API does this in one method by using syncTreePath */ _setActiveTreeType: function (treeAlias, loadChildren) { @@ -342,7 +331,7 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo mainTreeEventHandler._setActiveTreeType(treeAlias, loadChildren); } }, - + /** * @ngdoc method * @name umbraco.services.navigationService#hideTree @@ -353,7 +342,7 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo */ hideTree: function() { - if (isTablet && !appState.getGlobalState("stickyNavigation")) { + if (appState.getGlobalState("isTablet") === true && !appState.getGlobalState("stickyNavigation")) { //reset it to whatever is in the url appState.setSectionState("currentSection", $routeParams.section); setMode("default-hidesectiontree"); @@ -367,7 +356,7 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo * @methodOf umbraco.services.navigationService * * @description - * Hides the tree by hiding the containing dom element. + * Hides the tree by hiding the containing dom element. * This always returns a promise! * * @param {Event} event the click event triggering the method, passed from the DOM element @@ -393,7 +382,7 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo //NOTE: This is assigning the current action node - this is not the same as the currently selected node! appState.setMenuState("currentNode", args.node); - + //ensure the current dialog is cleared before creating another! if (currentDialog) { dialogService.close(currentDialog); @@ -411,13 +400,13 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo } } - //there is no default or we couldn't find one so just continue showing the menu + //there is no default or we couldn't find one so just continue showing the menu setMode("menu"); appState.setMenuState("currentNode", args.node); appState.setMenuState("menuActions", data.menuItems); - appState.setMenuState("dialogTitle", args.node.name); + appState.setMenuState("dialogTitle", args.node.name); //we're not opening a dialog, return null. deferred.resolve(null); @@ -448,7 +437,7 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo throw "action cannot be null"; } if (!node) { - throw "node cannot be null"; + throw "node cannot be null"; } if (!section) { throw "section cannot be null"; @@ -457,7 +446,7 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo if (action.metaData && action.metaData["actionRoute"] && angular.isString(action.metaData["actionRoute"])) { //first check if the menu item simply navigates to a route var parts = action.metaData["actionRoute"].split("?"); - $location.path(parts[0]).search(parts.length > 1 ? parts[1] : ""); + $location.path(parts[0].toLowerCase()).search(parts.length > 1 ? parts[1] : ""); this.hideNavigation(); return; } @@ -467,9 +456,9 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo var menuAction = action.metaData["jsAction"].split('.'); if (menuAction.length !== 2) { - //if it is not two parts long then this most likely means that it's a legacy action + //if it is not two parts long then this most likely means that it's a legacy action var js = action.metaData["jsAction"].replace("javascript:", ""); - //there's not really a different way to acheive this except for eval + //there's not really a different way to acheive this except for eval eval(js); } else { @@ -562,7 +551,7 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo modalClass: "umb-modal-left", show: true }); - + return service.helpDialog; }, @@ -575,13 +564,13 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo * Opens a dialog, for a given action on a given tree node * uses the dialogService to inject the selected action dialog * into #dialog div.umb-panel-body - * the path to the dialog view is determined by: + * the path to the dialog view is determined by: * "views/" + current tree + "/" + action alias + ".html" * The dialog controller will get passed a scope object that is created here with the properties: * scope.currentNode = the selected tree node * scope.currentAction = the selected menu item * so that the dialog controllers can use these properties - * + * * @param {Object} args arguments passed to the function * @param {Scope} args.scope current scope passed to the dialog * @param {Object} args.action the clicked action containing `name` and `alias` @@ -601,6 +590,7 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo //ensure the current dialog is cleared before creating another! if (currentDialog) { dialogService.close(currentDialog); + currentDialog = null; } setMode("dialog"); @@ -660,14 +650,14 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo } //TODO: some action's want to launch a new window like live editing, we support this in the menu item's metadata with - // a key called: "actionUrlMethod" which can be set to either: Dialog, BlankWindow. Normally this is always set to Dialog - // if a URL is specified in the "actionUrl" metadata. For now I'm not going to implement launching in a blank window, + // a key called: "actionUrlMethod" which can be set to either: Dialog, BlankWindow. Normally this is always set to Dialog + // if a URL is specified in the "actionUrl" metadata. For now I'm not going to implement launching in a blank window, // though would be v-easy, just not sure we want to ever support that? var dialog = dialogService.open( { container: $("#dialog div.umb-modalcolumn-body"), - //The ONLY reason we're passing in scope to the dialogService (which is legacy functionality) is + //The ONLY reason we're passing in scope to the dialogService (which is legacy functionality) is // for backwards compatibility since many dialogs require $scope.currentNode or $scope.currentAction // to exist scope: dialogScope, @@ -696,9 +686,9 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo * hides the currently open dialog */ hideDialog: function (showMenu) { - + setMode("default"); - + if(showMenu){ this.showMenu(undefined, { skipDefault: true, node: appState.getMenuState("currentNode") }); } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/overlayhelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/overlayhelper.service.js new file mode 100644 index 0000000000..6dd3de6cb8 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/services/overlayhelper.service.js @@ -0,0 +1,37 @@ +(function() { + 'use strict'; + + function overlayHelper() { + + var numberOfOverlays = 0; + + function registerOverlay() { + numberOfOverlays++; + return numberOfOverlays; + } + + function unregisterOverlay() { + numberOfOverlays--; + return numberOfOverlays; + } + + function getNumberOfOverlays() { + return numberOfOverlays; + } + + var service = { + numberOfOverlays: numberOfOverlays, + registerOverlay: registerOverlay, + unregisterOverlay: unregisterOverlay, + getNumberOfOverlays: getNumberOfOverlays + }; + + return service; + + } + + + angular.module('umbraco.services').factory('overlayHelper', overlayHelper); + + +})(); 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 fb93b77dd4..cdc7d77ca4 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 @@ -56,20 +56,22 @@ function tinyMceService(dialogService, $log, imageHelper, $http, $timeout, macro * @param {Object} editor the TinyMCE editor instance * @param {Object} $scope the current controller scope */ - createInsertEmbeddedMedia: function (editor, $scope) { + createInsertEmbeddedMedia: function (editor, scope, callback) { editor.addButton('umbembeddialog', { icon: 'custom icon-tv', tooltip: 'Embed', onclick: function () { - dialogService.embedDialog({ - callback: function (data) { - editor.insertContent(data); - } - }); + if (callback) { + callback(); + } } }); }, + insertEmbeddedMediaInEditor: function(editor, preview) { + editor.insertContent(preview); + }, + /** * @ngdoc method * @name umbraco.services.tinyMceService#createMediaPicker @@ -81,7 +83,7 @@ function tinyMceService(dialogService, $log, imageHelper, $http, $timeout, macro * @param {Object} editor the TinyMCE editor instance * @param {Object} $scope the current controller scope */ - createMediaPicker: function (editor) { + createMediaPicker: function (editor, scope, callback) { editor.addButton('umbmediapicker', { icon: 'custom icon-picture', tooltip: 'Media Picker', @@ -101,51 +103,48 @@ function tinyMceService(dialogService, $log, imageHelper, $http, $timeout, macro } userService.getCurrentUser().then(function(userData) { - dialogService.mediaPicker({ - currentTarget: currentTarget, - onlyImages: true, - showDetails: true, - startNodeId: userData.startMediaId, - callback: function (img) { - - if (img) { - - var data = { - alt: img.altText || "", - src: (img.url) ? img.url : "nothing.jpg", - rel: img.id, - id: '__mcenew' - }; - - editor.insertContent(editor.dom.createHTML('img', data)); - - $timeout(function () { - var imgElm = editor.dom.get('__mcenew'); - var size = editor.dom.getSize(imgElm); - - if (editor.settings.maxImageSize && editor.settings.maxImageSize !== 0) { - var newSize = imageHelper.scaleToMaxSize(editor.settings.maxImageSize, size.w, size.h); - - var s = "width: " + newSize.width + "px; height:" + newSize.height + "px;"; - editor.dom.setAttrib(imgElm, 'style', s); - editor.dom.setAttrib(imgElm, 'id', null); - - if (img.url) { - var src = img.url + "?width=" + newSize.width + "&height=" + newSize.height; - editor.dom.setAttrib(imgElm, 'data-mce-src', src); - } - } - }, 500); - } - } - }); + if(callback) { + callback(currentTarget, userData); + } }); - } }); }, + insertMediaInEditor: function(editor, img) { + if(img) { + + var data = { + alt: img.altText || "", + src: (img.url) ? img.url : "nothing.jpg", + rel: img.id, + 'data-id': img.id, + id: '__mcenew' + }; + + editor.insertContent(editor.dom.createHTML('img', data)); + + $timeout(function () { + var imgElm = editor.dom.get('__mcenew'); + var size = editor.dom.getSize(imgElm); + + if (editor.settings.maxImageSize && editor.settings.maxImageSize !== 0) { + var newSize = imageHelper.scaleToMaxSize(editor.settings.maxImageSize, size.w, size.h); + + var s = "width: " + newSize.width + "px; height:" + newSize.height + "px;"; + editor.dom.setAttrib(imgElm, 'style', s); + editor.dom.setAttrib(imgElm, 'id', null); + + if (img.url) { + var src = img.url + "?width=" + newSize.width + "&height=" + newSize.height; + editor.dom.setAttrib(imgElm, 'data-mce-src', src); + } + } + }, 500); + } + }, + /** * @ngdoc method * @name umbraco.services.tinyMceService#createUmbracoMacro @@ -157,8 +156,10 @@ function tinyMceService(dialogService, $log, imageHelper, $http, $timeout, macro * @param {Object} editor the TinyMCE editor instance * @param {Object} $scope the current controller scope */ - createInsertMacro: function (editor, $scope) { - + createInsertMacro: function (editor, $scope, callback) { + + var createInsertMacroScope = this; + /** Adds custom rules for the macro plugin and custom serialization */ editor.on('preInit', function (args) { //this is requires so that we tell the serializer that a 'div' is actually allowed in the root, otherwise the cleanup will strip it out @@ -194,45 +195,6 @@ function tinyMceService(dialogService, $log, imageHelper, $http, $timeout, macro return null; } - /** loads in the macro content async from the server */ - function loadMacroContent($macroDiv, macroData) { - - //if we don't have the macroData, then we'll need to parse it from the macro div - if (!macroData) { - var contents = $macroDiv.contents(); - var comment = _.find(contents, function (item) { - return item.nodeType === 8; - }); - if (!comment) { - throw "Cannot parse the current macro, the syntax in the editor is invalid"; - } - var syntax = comment.textContent.trim(); - var parsed = macroService.parseMacroSyntax(syntax); - macroData = parsed; - } - - var $ins = $macroDiv.find("ins"); - - //show the throbber - $macroDiv.addClass("loading"); - - var contentId = $routeParams.id; - - //need to wrap in safe apply since this might be occuring outside of angular - angularHelper.safeApply($scope, function() { - macroResource.getMacroResultAsHtmlForEditor(macroData.macroAlias, contentId, macroData.macroParamsDictionary) - .then(function (htmlResult) { - - $macroDiv.removeClass("loading"); - htmlResult = htmlResult.trim(); - if (htmlResult !== "") { - $ins.html(htmlResult); - } - }); - }); - - } - /** Adds the button instance */ editor.addButton('umbmacro', { icon: 'custom icon-settings-alt', @@ -335,8 +297,8 @@ function tinyMceService(dialogService, $log, imageHelper, $http, $timeout, macro //get all macro divs and load their content $(editor.dom.select(".umb-macro-holder.mceNonEditable")).each(function() { - loadMacroContent($(this)); - }); + createInsertMacroScope.loadMacroContent($(this), null, $scope); + }); }); @@ -459,33 +421,355 @@ function tinyMceService(dialogService, $log, imageHelper, $http, $timeout, macro }; } - dialogService.macroPicker({ - dialogData : dialogData, - callback: function(data) { - - //put the macro syntax in comments, we will parse this out on the server side to be used - //for persisting. - var macroSyntaxComment = ""; - //create an id class for this element so we can re-select it after inserting - var uniqueId = "umb-macro-" + editor.dom.uniqueId(); - var macroDiv = editor.dom.create('div', - { - 'class': 'umb-macro-holder ' + data.macroAlias + ' mceNonEditable ' + uniqueId - }, - macroSyntaxComment + 'Macro alias: ' + data.macroAlias + ''); - - editor.selection.setNode(macroDiv); - - var $macroDiv = $(editor.dom.select("div.umb-macro-holder." + uniqueId)); - - //async load the macro content - loadMacroContent($macroDiv, data); - } - }); + if(callback) { + callback(dialogData); + } } }); + }, + + insertMacroInEditor: function(editor, macroObject, $scope) { + + //put the macro syntax in comments, we will parse this out on the server side to be used + //for persisting. + var macroSyntaxComment = ""; + //create an id class for this element so we can re-select it after inserting + var uniqueId = "umb-macro-" + editor.dom.uniqueId(); + var macroDiv = editor.dom.create('div', + { + 'class': 'umb-macro-holder ' + macroObject.macroAlias + ' mceNonEditable ' + uniqueId + }, + macroSyntaxComment + 'Macro alias: ' + macroObject.macroAlias + ''); + + editor.selection.setNode(macroDiv); + + var $macroDiv = $(editor.dom.select("div.umb-macro-holder." + uniqueId)); + + //async load the macro content + this.loadMacroContent($macroDiv, macroObject, $scope); + + }, + + /** loads in the macro content async from the server */ + loadMacroContent: function($macroDiv, macroData, $scope) { + + //if we don't have the macroData, then we'll need to parse it from the macro div + if (!macroData) { + var contents = $macroDiv.contents(); + var comment = _.find(contents, function (item) { + return item.nodeType === 8; + }); + if (!comment) { + throw "Cannot parse the current macro, the syntax in the editor is invalid"; + } + var syntax = comment.textContent.trim(); + var parsed = macroService.parseMacroSyntax(syntax); + macroData = parsed; + } + + var $ins = $macroDiv.find("ins"); + + //show the throbber + $macroDiv.addClass("loading"); + + var contentId = $routeParams.id; + + //need to wrap in safe apply since this might be occuring outside of angular + angularHelper.safeApply($scope, function() { + macroResource.getMacroResultAsHtmlForEditor(macroData.macroAlias, contentId, macroData.macroParamsDictionary) + .then(function (htmlResult) { + + $macroDiv.removeClass("loading"); + htmlResult = htmlResult.trim(); + if (htmlResult !== "") { + $ins.html(htmlResult); + } + }); + }); + + }, + + createLinkPicker: function(editor, $scope, onClick) { + + function createLinkList(callback) { + return function() { + var linkList = editor.settings.link_list; + + if (typeof(linkList) === "string") { + tinymce.util.XHR.send({ + url: linkList, + success: function(text) { + callback(tinymce.util.JSON.parse(text)); + } + }); + } else { + callback(linkList); + } + }; + } + + function showDialog(linkList) { + var data = {}, selection = editor.selection, dom = editor.dom, selectedElm, anchorElm, initialText; + var win, linkListCtrl, relListCtrl, targetListCtrl; + + function linkListChangeHandler(e) { + var textCtrl = win.find('#text'); + + if (!textCtrl.value() || (e.lastControl && textCtrl.value() === e.lastControl.text())) { + textCtrl.value(e.control.text()); + } + + win.find('#href').value(e.control.value()); + } + + function buildLinkList() { + var linkListItems = [{ + text: 'None', + value: '' + }]; + + tinymce.each(linkList, function(link) { + linkListItems.push({ + text: link.text || link.title, + value: link.value || link.url, + menu: link.menu + }); + }); + + return linkListItems; + } + + function buildRelList(relValue) { + var relListItems = [{ + text: 'None', + value: '' + }]; + + tinymce.each(editor.settings.rel_list, function(rel) { + relListItems.push({ + text: rel.text || rel.title, + value: rel.value, + selected: relValue === rel.value + }); + }); + + return relListItems; + } + + function buildTargetList(targetValue) { + var targetListItems = [{ + text: 'None', + value: '' + }]; + + if (!editor.settings.target_list) { + targetListItems.push({ + text: 'New window', + value: '_blank' + }); + } + + tinymce.each(editor.settings.target_list, function(target) { + targetListItems.push({ + text: target.text || target.title, + value: target.value, + selected: targetValue === target.value + }); + }); + + return targetListItems; + } + + function buildAnchorListControl(url) { + var anchorList = []; + + tinymce.each(editor.dom.select('a:not([href])'), function(anchor) { + var id = anchor.name || anchor.id; + + if (id) { + anchorList.push({ + text: id, + value: '#' + id, + selected: url.indexOf('#' + id) !== -1 + }); + } + }); + + if (anchorList.length) { + anchorList.unshift({ + text: 'None', + value: '' + }); + + return { + name: 'anchor', + type: 'listbox', + label: 'Anchors', + values: anchorList, + onselect: linkListChangeHandler + }; + } + } + + function updateText() { + if (!initialText && data.text.length === 0) { + this.parent().parent().find('#text')[0].value(this.value()); + } + } + + selectedElm = selection.getNode(); + anchorElm = dom.getParent(selectedElm, 'a[href]'); + + data.text = initialText = anchorElm ? (anchorElm.innerText || anchorElm.textContent) : selection.getContent({format: 'text'}); + data.href = anchorElm ? dom.getAttrib(anchorElm, 'href') : ''; + data.target = anchorElm ? dom.getAttrib(anchorElm, 'target') : ''; + data.rel = anchorElm ? dom.getAttrib(anchorElm, 'rel') : ''; + + if (selectedElm.nodeName === "IMG") { + data.text = initialText = " "; + } + + if (linkList) { + linkListCtrl = { + type: 'listbox', + label: 'Link list', + values: buildLinkList(), + onselect: linkListChangeHandler + }; + } + + if (editor.settings.target_list !== false) { + targetListCtrl = { + name: 'target', + type: 'listbox', + label: 'Target', + values: buildTargetList(data.target) + }; + } + + if (editor.settings.rel_list) { + relListCtrl = { + name: 'rel', + type: 'listbox', + label: 'Rel', + values: buildRelList(data.rel) + }; + } + + var injector = angular.element(document.getElementById("umbracoMainPageBody")).injector(); + var dialogService = injector.get("dialogService"); + var currentTarget = null; + + //if we already have a link selected, we want to pass that data over to the dialog + if(anchorElm){ + var anchor = $(anchorElm); + currentTarget = { + name: anchor.attr("title"), + url: anchor.attr("href"), + target: anchor.attr("target") + }; + + //locallink detection, we do this here, to avoid poluting the dialogservice + //so the dialog service can just expect to get a node-like structure + if(currentTarget.url.indexOf("localLink:") > 0){ + currentTarget.id = currentTarget.url.substring(currentTarget.url.indexOf(":")+1,currentTarget.url.length-1); + } + } + + if(onClick) { + onClick(currentTarget, anchorElm); + } + + } + + editor.addButton('link', { + icon: 'link', + tooltip: 'Insert/edit link', + shortcut: 'Ctrl+K', + onclick: createLinkList(showDialog), + stateSelector: 'a[href]' + }); + + editor.addButton('unlink', { + icon: 'unlink', + tooltip: 'Remove link', + cmd: 'unlink', + stateSelector: 'a[href]' + }); + + editor.addShortcut('Ctrl+K', '', createLinkList(showDialog)); + this.showDialog = showDialog; + + editor.addMenuItem('link', { + icon: 'link', + text: 'Insert link', + shortcut: 'Ctrl+K', + onclick: createLinkList(showDialog), + stateSelector: 'a[href]', + context: 'insert', + prependToContext: true + }); + + }, + + insertLinkInEditor: function(editor, target, anchorElm) { + + var href = target.url; + + function insertLink() { + if (anchorElm) { + editor.dom.setAttribs(anchorElm, { + href: href, + title: target.name, + target: target.target ? target.target : null, + rel: target.rel ? target.rel : null, + 'data-id': target.id ? target.id : null + }); + + editor.selection.select(anchorElm); + editor.execCommand('mceEndTyping'); + } else { + editor.execCommand('mceInsertLink', false, { + href: href, + title: target.name, + target: target.target ? target.target : null, + rel: target.rel ? target.rel : null, + 'data-id': target.id ? target.id : null + }); + } + } + + if (!href) { + editor.execCommand('unlink'); + return; + } + + //if we have an id, it must be a locallink:id, aslong as the isMedia flag is not set + if(target.id && (angular.isUndefined(target.isMedia) || !target.isMedia)){ + href = "/{localLink:" + target.id + "}"; + insertLink(); + return; + } + + // Is email and not //user@domain.com + if (href.indexOf('@') > 0 && href.indexOf('//') === -1 && href.indexOf('mailto:') === -1) { + href = 'mailto:' + href; + insertLink(); + return; + } + + // Is www. prefixed + if (/^\s*www\./i.test(href)) { + href = 'http://' + href; + insertLink(); + return; + } + + insertLink(); + } + }; } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js index 963e2af06e..ccdd283ea6 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js @@ -3,7 +3,7 @@ * @name umbraco.services.umbRequestHelper * @description A helper object used for sending requests to the server **/ -function umbRequestHelper($http, $q, umbDataFormatter, angularHelper, dialogService, notificationsService) { +function umbRequestHelper($http, $q, umbDataFormatter, angularHelper, dialogService, notificationsService, eventsService) { return { /** @@ -158,9 +158,10 @@ function umbRequestHelper($http, $q, umbDataFormatter, angularHelper, dialogServ //show a ysod dialog if (Umbraco.Sys.ServerVariables["isDebuggingEnabled"] === true) { - dialogService.ysodDialog({ - errorMsg: result.errorMsg, - data: result.data + eventsService.emit('app.ysod', + { + errorMsg: 'An error occured', + data: data }); } else { @@ -248,13 +249,14 @@ function umbRequestHelper($http, $q, umbDataFormatter, angularHelper, dialogServ //This is a bit of a hack to check if the error is due to a file being uploaded that is too large, // we have to just check for the existence of a string value but currently that is the best way to // do this since it's very hacky/difficult to catch this on the server - if (data.indexOf("Maximum request length exceeded") >= 0) { + if (typeof data !== "undefined" && typeof data.indexOf === "function" && data.indexOf("Maximum request length exceeded") >= 0) { notificationsService.error("Server error", "The uploaded file was too large, check with your site administrator to adjust the maximum size allowed"); } else if (Umbraco.Sys.ServerVariables["isDebuggingEnabled"] === true) { //show a ysod dialog - dialogService.ysodDialog({ - errorMsg: 'An error occurred', + eventsService.emit('app.ysod', + { + errorMsg: 'An error occured', data: data }); } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js index 8d99086ba5..3fb291619d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js @@ -1,5 +1,5 @@ angular.module('umbraco.services') - .factory('userService', function ($rootScope, eventsService, $q, $location, $log, securityRetryQueue, authResource, dialogService, $timeout, angularHelper) { + .factory('userService', function ($rootScope, eventsService, $q, $location, $log, securityRetryQueue, authResource, dialogService, $timeout, angularHelper, $http) { var currentUser = null; var lastUserId = null; @@ -102,7 +102,14 @@ angular.module('umbraco.services') if (Umbraco.Sys.ServerVariables.umbracoSettings.keepUserLoggedIn !== true) { //NOTE: the safeApply because our timeout is set to not run digests (performance reasons) angularHelper.safeApply($rootScope, function () { - userAuthExpired(); + try { + //NOTE: We are calling this again so that the server can create a log that the timeout has expired, we + // don't actually care about this result. + authResource.getRemainingTimeoutSeconds(); + } + finally { + userAuthExpired(); + } }); } else { @@ -243,7 +250,7 @@ angular.module('umbraco.services') } setCurrentUser(data); - currentUser.avatar = '//www.gravatar.com/avatar/' + data.emailHash + '?s=40&d=404'; + deferred.resolve(currentUser); }); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/util.service.js b/src/Umbraco.Web.UI.Client/src/common/services/util.service.js index d83a0f3eaa..4371c0d7a7 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/util.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/util.service.js @@ -348,7 +348,7 @@ function umbModelMapper() { * @param {String} source.name The node name * @param {String} source.icon The models icon as a css class (.icon-doc) * @param {Number} source.parentId The parentID, if no parent, set to -1 - * @param {path} source.path comma-seperated string of ancestor IDs (-1,1234,1782,1234) + * @param {path} source.path comma-separated string of ancestor IDs (-1,1234,1782,1234) */ /** This converts the source model to a basic entity model, it will throw an exception if there isn't enough data to create the model */ @@ -487,10 +487,57 @@ angular.module('umbraco.services').factory('umbPropEditorHelper', umbPropEditorH function umbDataFormatter() { return { + formatContentTypePostData: function (displayModel, action) { + + //create the save model from the display model + var saveModel = _.pick(displayModel, + 'compositeContentTypes', 'isContainer', 'allowAsRoot', 'allowedTemplates', 'allowedContentTypes', + 'alias', 'description', 'thumbnail', 'name', 'id', 'icon', 'trashed', + 'key', 'parentId', 'alias', 'path'); + + //TODO: Map these + saveModel.allowedTemplates = _.map(displayModel.allowedTemplates, function (t) { return t.alias; }); + saveModel.defaultTemplate = displayModel.defaultTemplate ? displayModel.defaultTemplate.alias : null; + var realGroups = _.reject(displayModel.groups, function(g) { + //do not include these tabs + return g.tabState === "init"; + }); + saveModel.groups = _.map(realGroups, function (g) { + + var saveGroup = _.pick(g, 'inherited', 'id', 'sortOrder', 'name'); + + var realProperties = _.reject(g.properties, function (p) { + //do not include these properties + return p.propertyState === "init" || p.inherited === true; + }); + + var saveProperties = _.map(realProperties, function (p) { + var saveProperty = _.pick(p, 'id', 'alias', 'description', 'validation', 'label', 'sortOrder', 'dataTypeId', 'groupId'); + return saveProperty; + }); + + saveGroup.properties = saveProperties; + + //if this is an inherited group and there are not non-inherited properties on it, then don't send up the data + if (saveGroup.inherited === true && saveProperties.length === 0) { + return null; + } + + return saveGroup; + }); + + //we don't want any null groups + saveModel.groups = _.reject(saveModel.groups, function(g) { + return !g; + }); + + return saveModel; + }, + /** formats the display model used to display the data type to the model used to save the data type */ formatDataTypePostData: function(displayModel, preValues, action) { var saveModel = { - parentId: -1, + parentId: displayModel.parentId, id: displayModel.id, name: displayModel.name, selectedEditor: displayModel.selectedEditor, diff --git a/src/Umbraco.Web.UI.Client/src/common/services/windowresizelistener.service.js b/src/Umbraco.Web.UI.Client/src/common/services/windowresizelistener.service.js new file mode 100644 index 0000000000..3b86510173 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/services/windowresizelistener.service.js @@ -0,0 +1,67 @@ +/** + * @ngdoc service + * @name umbraco.services.windowResizeListener + * @function + * + * @description + * A single window resize listener... we don't want to have more than one in theory to ensure that + * there aren't too many events raised. This will debounce the event with 100 ms intervals and force + * a $rootScope.$apply when changed and notify all listeners + * + */ +function windowResizeListener($rootScope) { + + var WinReszier = (function () { + var registered = []; + var inited = false; + var resize = _.debounce(function(ev) { + notify(); + }, 100); + var notify = function () { + var h = $(window).height(); + var w = $(window).width(); + //execute all registrations inside of a digest + $rootScope.$apply(function() { + for (var i = 0, cnt = registered.length; i < cnt; i++) { + registered[i].apply($(window), [{ width: w, height: h }]); + } + }); + }; + return { + register: function (fn) { + registered.push(fn); + if (inited === false) { + $(window).bind('resize', resize); + inited = true; + } + }, + unregister: function (fn) { + var index = registered.indexOf(fn); + if (index > -1) { + registered.splice(index, 1); + } + } + }; + }()); + + return { + + /** + * Register a callback for resizing + * @param {Function} cb + */ + register: function (cb) { + WinReszier.register(cb); + }, + + /** + * Removes a registered callback + * @param {Function} cb + */ + unregister: function(cb) { + WinReszier.unregister(cb); + } + + }; +} +angular.module('umbraco.services').factory('windowResizeListener', windowResizeListener); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/services/xmlhelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/xmlhelper.service.js index 03f1f6cbbe..a8562c833e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/xmlhelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/xmlhelper.service.js @@ -372,9 +372,6 @@ function xmlhelper($http) { fromJson: function (json) { var xml = x2js.json2xml_str(json); return xml; - }, - parseFeed: function (url) { - return $http.jsonp('//ajax.googleapis.com/ajax/services/feed/load?v=1.0&num=50&callback=JSON_CALLBACK&q=' + encodeURIComponent(url)); } }; } diff --git a/src/Umbraco.Web.UI.Client/src/controllers/main.controller.js b/src/Umbraco.Web.UI.Client/src/controllers/main.controller.js index e524f8e49e..42d50b1ce7 100644 --- a/src/Umbraco.Web.UI.Client/src/controllers/main.controller.js +++ b/src/Umbraco.Web.UI.Client/src/controllers/main.controller.js @@ -13,7 +13,11 @@ function MainController($scope, $rootScope, $location, $routeParams, $timeout, $ //the null is important because we do an explicit bool check on this in the view //the avatar is by default the umbraco logo $scope.authenticated = null; - $scope.avatar = "assets/img/application/logo.png"; + $scope.avatar = [ + { value: "assets/img/application/logo.png" }, + { value: "assets/img/application/logo@2x.png" }, + { value: "assets/img/application/logo@3x.png" } + ]; $scope.touchDevice = appState.getGlobalState("touchDevice"); @@ -84,24 +88,44 @@ function MainController($scope, $rootScope, $location, $routeParams, $timeout, $ tmhDynamicLocale.set($scope.user.locale); } - if($scope.user.emailHash){ - $timeout(function () { - //yes this is wrong.. - $("#avatar-img").fadeTo(1000, 0, function () { - $timeout(function () { - //this can be null if they time out - if ($scope.user && $scope.user.emailHash) { - $scope.avatar = "//www.gravatar.com/avatar/" + $scope.user.emailHash + ".jpg?s=64&d=mm"; - } + if ($scope.user.emailHash) { + + //let's attempt to load the avatar, it might not exist or we might not have + // internet access so we'll detect it first + $http.get("https://www.gravatar.com/avatar/" + $scope.user.emailHash + ".jpg?s=64&d=404") + .then( + function successCallback(response) { + $("#avatar-img").fadeTo(1000, 0, function () { + $scope.$apply(function () { + //this can be null if they time out + if ($scope.user && $scope.user.emailHash) { + var avatarBaseUrl = "https://www.gravatar.com/avatar/", + hash = $scope.user.emailHash; + + $scope.avatar = [ + { value: avatarBaseUrl + hash + ".jpg?s=30&d=mm" }, + { value: avatarBaseUrl + hash + ".jpg?s=60&d=mm" }, + { value: avatarBaseUrl + hash + ".jpg?s=90&d=mm" } + ]; + } + }); + $("#avatar-img").fadeTo(1000, 1); + }); + }, function errorCallback(response) { + //cannot load it from the server so we cannot do anything }); - $("#avatar-img").fadeTo(1000, 1); - }); - - }, 3000); } })); + evts.push(eventsService.on("app.ysod", function(name, error) { + $scope.ysodOverlay = { + view: "ysod", + error: error, + show: true + }; + })); + //ensure to unregister from all events! $scope.$on('$destroy', function () { for (var e in evts) { diff --git a/src/Umbraco.Web.UI.Client/src/index.html b/src/Umbraco.Web.UI.Client/src/index.html index a16987ce2c..50d78c315b 100644 --- a/src/Umbraco.Web.UI.Client/src/index.html +++ b/src/Umbraco.Web.UI.Client/src/index.html @@ -9,20 +9,19 @@ - +
    -
    -
    +
    + - - + diff --git a/src/Umbraco.Web.UI.Client/src/init.js b/src/Umbraco.Web.UI.Client/src/init.js index cb79c8e402..ded5ba08e4 100644 --- a/src/Umbraco.Web.UI.Client/src/init.js +++ b/src/Umbraco.Web.UI.Client/src/init.js @@ -9,8 +9,8 @@ app.run(['userService', '$log', '$rootScope', '$location', 'navigationService', beforeSend: function (xhr) { xhr.setRequestHeader("X-XSRF-TOKEN", $cookies["XSRF-TOKEN"]); } - }); - + }); + /** Listens for authentication and checks if our required assets are loaded, if/once they are we'll broadcast a ready event */ eventsService.on("app.authenticated", function(evt, data) { assetsService._loadInitAssets().then(function() { @@ -24,14 +24,14 @@ app.run(['userService', '$log', '$rootScope', '$location', 'navigationService', /** execute code on each successful route */ $rootScope.$on('$routeChangeSuccess', function(event, current, previous) { - + if(current.params.section){ $rootScope.locationTitle = current.params.section + " - " + $location.$$host; } else { $rootScope.locationTitle = "Umbraco - " + $location.$$host; } - + //reset the editorState on each successful route chage editorState.reset(); @@ -49,25 +49,17 @@ app.run(['userService', '$log', '$rootScope', '$location', 'navigationService', var returnPath = null; if (rejection.path == "/login" || rejection.path.startsWith("/login/")) { //Set the current path before redirecting so we know where to redirect back to - returnPath = encodeURIComponent($location.url()); + returnPath = encodeURIComponent($location.url()); } $location.path(rejection.path) if (returnPath) { $location.search("returnPath", returnPath); } - + }); - /** For debug mode, always clear template cache to cut down on - dev frustration and chrome cache on templates */ - if(Umbraco.Sys.ServerVariables.isDebuggingEnabled){ - $rootScope.$on('$viewContentLoaded', function() { - $templateCache.removeAll(); - }); - } - /* this will initialize the navigation service once the application has started */ navigationService.init(); @@ -75,4 +67,4 @@ app.run(['userService', '$log', '$rootScope', '$location', 'navigationService', //var touchDevice = ("ontouchstart" in window || window.touch || window.navigator.msMaxTouchPoints === 5 || window.DocumentTouch && document instanceof DocumentTouch); var touchDevice = /android|webos|iphone|ipad|ipod|blackberry|iemobile|touch/i.test(navigator.userAgent.toLowerCase()); appState.setGlobalState("touchDevice", touchDevice); - }]); \ No newline at end of file + }]); diff --git a/src/Umbraco.Web.UI.Client/src/installer/installer.service.js b/src/Umbraco.Web.UI.Client/src/installer/installer.service.js index 1ab84900f4..4b732bf56e 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/installer.service.js +++ b/src/Umbraco.Web.UI.Client/src/installer/installer.service.js @@ -17,9 +17,9 @@ angular.module("umbraco.install").factory('installerService', function($rootScop //add to umbraco installer facts here var facts = ['Umbraco helped millions of people watch a man jump from the edge of space', - 'Over 250.000 websites are currently powered by Umbraco', + 'Over 300 000 websites are currently powered by Umbraco', "At least 2 people have named their cat 'Umbraco'", - 'On an average day, more than 1.000 people download Umbraco', + 'On an average day, more than 1000 people download Umbraco', 'umbraco.tv is the premier source of Umbraco video tutorials to get you started', 'You can find the world\'s friendliest CMS community at our.umbraco.org', 'You can become a certified Umbraco developer by attending one of the official courses', @@ -28,12 +28,13 @@ angular.module("umbraco.install").factory('installerService', function($rootScop 'Umbraco is the best of both worlds: 100% free and open source, and backed by a professional and profitable company', "There's a pretty big chance, you've visited a website powered by Umbraco today", "'Umbraco-spotting' is the game of spotting big brands running Umbraco", - "At least 2 people have the Umbraco logo tattooed on them", + "At least 4 people have the Umbraco logo tattooed on them", "'Umbraco' is the danish name for an allen key", "Umbraco has been around since 2005, that's a looong time in IT", - "More than 400 people from all over the world meet each year in Denmark in June for our annual conference CodeGarden", + "More than 400 people from all over the world meet each year in Denmark in June for our annual conference CodeGarden", "While you are installing Umbraco someone else on the other side of the planet is probably doing it too", - "You can extend Umbraco without modifying the source code and using either JavaScript or C#" + "You can extend Umbraco without modifying the source code using either JavaScript or C#", + "Umbraco was installed in more than 165 countries in 2015" ]; /** @@ -335,4 +336,4 @@ angular.module("umbraco.install").factory('installerService', function($rootScop }; return service; -}); \ No newline at end of file +}); diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/database.html b/src/Umbraco.Web.UI.Client/src/installer/steps/database.html index 0e9ebc7526..0cc7511f89 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/steps/database.html +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/database.html @@ -42,7 +42,7 @@
    - + Enter server domain or IP
    diff --git a/src/Umbraco.Web.UI.Client/src/less/alerts.less b/src/Umbraco.Web.UI.Client/src/less/alerts.less index 1f725946d2..2beb855560 100644 --- a/src/Umbraco.Web.UI.Client/src/less/alerts.less +++ b/src/Umbraco.Web.UI.Client/src/less/alerts.less @@ -31,6 +31,10 @@ line-height: @baseLineHeight; } +.close.-align-right { + right: 0; +} + // Alternate styles // ------------------------- @@ -63,11 +67,15 @@ } .alert-form { - background-color: @grayLighter; + background-color: #ECECEC; border: 1px solid @gray !important; color: @gray; } +.alert-form.-no-border { + border: none !important; +} + .alert-form h4 { color: @gray; } diff --git a/src/Umbraco.Web.UI.Client/src/less/animations.less b/src/Umbraco.Web.UI.Client/src/less/animations.less deleted file mode 100644 index ee17d37194..0000000000 --- a/src/Umbraco.Web.UI.Client/src/less/animations.less +++ /dev/null @@ -1,191 +0,0 @@ -// Animations -// ------------------------- - -.fade-hide, .fade-show , .fade-leave, .fade-enter { - -webkit-transition: all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.3s; - -moz-transition: all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.3s; - -o-transition: all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.3s; - transition: all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.3s; -} -.fade-enter { - opacity: 1; -} - -.fade-hide.fade-hide-active { - opacity: 0; -} -.fade-leave { - opacity: 0; -} - -.fade-show.fade-show-active { - opacity: 1; -} - -.slide-hide, .slide-show { - -webkit-transition: all cubic-bezier(0.770, 0.000, 0.175, 1.000) 0.5s; - -moz-transition: all cubic-bezier(0.770, 0.000, 0.175, 1.000) 0.5s; - -o-transition: all cubic-bezier(0.770, 0.000, 0.175, 1.000) 0.5s; - transition: all cubic-bezier(0.770, 0.000, 0.175, 1.000) 0.5s; -} - -.slide-hide { - -webkit-transform: translateX(0%); - -moz-transform: translateX(0%); - -ms-transform: translateX(0%); - transform: translateX(0%); -} - -.slide-hide.slide-hide-active { - -webkit-transform: translateX(-100%); - -moz-transform: translateX(-100%); - -ms-transform: translateX(-100%); - transform: translateX(-100%); -} - -.slide-show { - -webkit-transform: translateX(-100%); - -moz-transform: translateX(-100%); - -ms-transform: translateX(-100%); - transform: translateX(-100%); -} - -.slide-show.slide-show-active { - -webkit-transform: translateX(0%); - -moz-transform: translateX(0%); - -ms-transform: translateX(0%); - transform: translateX(0%); -} - -.tree-node-delete-leave { - -webkit-animation: leave 600ms cubic-bezier(0.445, 0.050, 0.550, 0.950); - -moz-animation: leave 600ms cubic-bezier(0.445, 0.050, 0.550, 0.950); - -o-animation: leave 600ms cubic-bezier(0.445, 0.050, 0.550, 0.950); - animation: leave 600ms cubic-bezier(0.445, 0.050, 0.550, 0.950); - display: block; - position: relative; -} - -@-webkit-keyframes leave { - to { - opacity: 0; - height: 0px; - bottom: -70px; - } - 25% { - bottom: 15px; - } - from { - opacity: 1; - height: 30px; - bottom: 0px; - } -} -@-moz-keyframes leave { - to { - opacity: 0; - height: 0px; - bottom: -70px; - } - 25% { - bottom: 15px; - } - from { - opacity: 1; - height: 30px; - bottom: 0px; - } -} -@-ms-keyframes leave { - to { - opacity: 0; - height: 0px; - bottom: -70px; - } - 25% { - bottom: 15px; - } - from { - opacity: 1; - height: 30px; - bottom: 0px; - } -} -@-o-keyframes leave { - to { - opacity: 0; - height: 0px; - bottom: -70px; - } - 25% { - bottom: 15px; - } - from { - opacity: 1; - height: 30px; - bottom: 0px; - } -} -@keyframes leave { - to { - opacity: 0; - height: 0px; - bottom: -70px; - } - 25% { - bottom: 15px; - } - from { - opacity: 1; - height: 30px; - bottom: 0px; - } -} - -.tree-node-delete-leave * { - color:@red !important; -} - - -.tree-node-slide-up -{ - opacity:1; - top: 0px; - -webkit-transition: 700ms cubic-bezier(0.000, 0.000, 0.580, 1.000) all; - -moz-transition: 700ms cubic-bezier(0.000, 0.000, 0.580, 1.000) all; - -ms-transition: 700ms cubic-bezier(0.000, 0.000, 0.580, 1.000) all; - -o-transition: 700ms cubic-bezier(0.000, 0.000, 0.580, 1.000) all; - transition: 700ms cubic-bezier(0.000, 0.000, 0.580, 1.000) all; -} -.tree-node-slide-up * { - font-size:100%; - -webkit-transition:font-size 700ms; - -moz-transition:font-size 700ms; - -ms-transition:font-size 700ms; - -o-transition:font-size 700ms; - transition:font-size 700ms; -} -.tree-node-slide-up.tree-node-slide-up-hide-active { - opacity: 0; - top: -100px; -} -.tree-node-slide-up.tree-node-slide-up-hide-active * { - font-size:120%; -} - -.tree-fade-out-hide , -.tree-fade-out-show, -.tree-fade-out-hide div:not(.tree-node-slide-up-hide-active), -.tree-fade-out-show div:not(.tree-node-slide-up-hide-active) { - -webkit-transition: 700ms cubic-bezier(0.075, 0.820, 0.165, 1.000) all; - -moz-transition: 700ms cubic-bezier(0.075, 0.820, 0.165, 1.000) all; - -ms-transition: 700ms cubic-bezier(0.075, 0.820, 0.165, 1.000) all; - -o-transition: 700ms cubic-bezier(0.075, 0.820, 0.165, 1.000) all; - transition: 700ms cubic-bezier(0.075, 0.820, 0.165, 1.000) all; -} -.tree-fade-out-show.tree-fade-out-show-active div:not(.tree-node-slide-up-hide-active){ - opacity: 1; -} -.tree-fade-out-hide.tree-fade-out-hide-active div:not(.tree-node-slide-up-hide-active){ - opacity: 0; -} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/less/application/animations.less b/src/Umbraco.Web.UI.Client/src/less/application/animations.less new file mode 100644 index 0000000000..dae428c18a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/application/animations.less @@ -0,0 +1,180 @@ +// Animations +// ------------------------- + +//Animate.css - http://daneden.me/animate +//Licensed under the MIT license - http://opensource.org/licenses/MIT +//Copyright (c) 2013 Daniel Eden +.animated{-webkit-animation-duration:1s;animation-duration:1s;-webkit-animation-fill-mode:both;animation-fill-mode:both}.animated.infinite{-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite}.animated.hinge{-webkit-animation-duration:2s;animation-duration:2s}@-webkit-keyframes bounce{0%,100%,20%,53%,80%{-webkit-transition-timing-function:cubic-bezier(0.215,.61,.355,1);transition-timing-function:cubic-bezier(0.215,.61,.355,1);-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}40%,43%{-webkit-transition-timing-function:cubic-bezier(0.755,.050,.855,.060);transition-timing-function:cubic-bezier(0.755,.050,.855,.060);-webkit-transform:translate3d(0,-30px,0);transform:translate3d(0,-30px,0)}70%{-webkit-transition-timing-function:cubic-bezier(0.755,.050,.855,.060);transition-timing-function:cubic-bezier(0.755,.050,.855,.060);-webkit-transform:translate3d(0,-15px,0);transform:translate3d(0,-15px,0)}90%{-webkit-transform:translate3d(0,-4px,0);transform:translate3d(0,-4px,0)}}@keyframes bounce{0%,100%,20%,53%,80%{-webkit-transition-timing-function:cubic-bezier(0.215,.61,.355,1);transition-timing-function:cubic-bezier(0.215,.61,.355,1);-webkit-transform:translate3d(0,0,0);-ms-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}40%,43%{-webkit-transition-timing-function:cubic-bezier(0.755,.050,.855,.060);transition-timing-function:cubic-bezier(0.755,.050,.855,.060);-webkit-transform:translate3d(0,-30px,0);-ms-transform:translate3d(0,-30px,0);transform:translate3d(0,-30px,0)}70%{-webkit-transition-timing-function:cubic-bezier(0.755,.050,.855,.060);transition-timing-function:cubic-bezier(0.755,.050,.855,.060);-webkit-transform:translate3d(0,-15px,0);-ms-transform:translate3d(0,-15px,0);transform:translate3d(0,-15px,0)}90%{-webkit-transform:translate3d(0,-4px,0);-ms-transform:translate3d(0,-4px,0);transform:translate3d(0,-4px,0)}}.bounce{-webkit-animation-name:bounce;animation-name:bounce;-webkit-transform-origin:center bottom;-ms-transform-origin:center bottom;transform-origin:center bottom}@-webkit-keyframes flash{0%,100%,50%{opacity:1}25%,75%{opacity:0}}@keyframes flash{0%,100%,50%{opacity:1}25%,75%{opacity:0}}.flash{-webkit-animation-name:flash;animation-name:flash}@-webkit-keyframes pulse{0%{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}50%{-webkit-transform:scale3d(1.05,1.05,1.05);transform:scale3d(1.05,1.05,1.05)}100%{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}}@keyframes pulse{0%{-webkit-transform:scale3d(1,1,1);-ms-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}50%{-webkit-transform:scale3d(1.05,1.05,1.05);-ms-transform:scale3d(1.05,1.05,1.05);transform:scale3d(1.05,1.05,1.05)}100%{-webkit-transform:scale3d(1,1,1);-ms-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}}.pulse{-webkit-animation-name:pulse;animation-name:pulse}@-webkit-keyframes rubberBand{0%{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}30%{-webkit-transform:scale3d(1.25,.75,1);transform:scale3d(1.25,.75,1)}40%{-webkit-transform:scale3d(0.75,1.25,1);transform:scale3d(0.75,1.25,1)}50%{-webkit-transform:scale3d(1.15,.85,1);transform:scale3d(1.15,.85,1)}65%{-webkit-transform:scale3d(.95,1.05,1);transform:scale3d(.95,1.05,1)}75%{-webkit-transform:scale3d(1.05,.95,1);transform:scale3d(1.05,.95,1)}100%{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}}@keyframes rubberBand{0%{-webkit-transform:scale3d(1,1,1);-ms-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}30%{-webkit-transform:scale3d(1.25,.75,1);-ms-transform:scale3d(1.25,.75,1);transform:scale3d(1.25,.75,1)}40%{-webkit-transform:scale3d(0.75,1.25,1);-ms-transform:scale3d(0.75,1.25,1);transform:scale3d(0.75,1.25,1)}50%{-webkit-transform:scale3d(1.15,.85,1);-ms-transform:scale3d(1.15,.85,1);transform:scale3d(1.15,.85,1)}65%{-webkit-transform:scale3d(.95,1.05,1);-ms-transform:scale3d(.95,1.05,1);transform:scale3d(.95,1.05,1)}75%{-webkit-transform:scale3d(1.05,.95,1);-ms-transform:scale3d(1.05,.95,1);transform:scale3d(1.05,.95,1)}100%{-webkit-transform:scale3d(1,1,1);-ms-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}}.rubberBand{-webkit-animation-name:rubberBand;animation-name:rubberBand}@-webkit-keyframes shake{0%,100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}10%,30%,50%,70%,90%{-webkit-transform:translate3d(-10px,0,0);transform:translate3d(-10px,0,0)}20%,40%,60%,80%{-webkit-transform:translate3d(10px,0,0);transform:translate3d(10px,0,0)}}@keyframes shake{0%,100%{-webkit-transform:translate3d(0,0,0);-ms-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}10%,30%,50%,70%,90%{-webkit-transform:translate3d(-10px,0,0);-ms-transform:translate3d(-10px,0,0);transform:translate3d(-10px,0,0)}20%,40%,60%,80%{-webkit-transform:translate3d(10px,0,0);-ms-transform:translate3d(10px,0,0);transform:translate3d(10px,0,0)}}.shake{-webkit-animation-name:shake;animation-name:shake}@-webkit-keyframes swing{20%{-webkit-transform:rotate3d(0,0,1,15deg);transform:rotate3d(0,0,1,15deg)}40%{-webkit-transform:rotate3d(0,0,1,-10deg);transform:rotate3d(0,0,1,-10deg)}60%{-webkit-transform:rotate3d(0,0,1,5deg);transform:rotate3d(0,0,1,5deg)}80%{-webkit-transform:rotate3d(0,0,1,-5deg);transform:rotate3d(0,0,1,-5deg)}100%{-webkit-transform:rotate3d(0,0,1,0deg);transform:rotate3d(0,0,1,0deg)}}@keyframes swing{20%{-webkit-transform:rotate3d(0,0,1,15deg);-ms-transform:rotate3d(0,0,1,15deg);transform:rotate3d(0,0,1,15deg)}40%{-webkit-transform:rotate3d(0,0,1,-10deg);-ms-transform:rotate3d(0,0,1,-10deg);transform:rotate3d(0,0,1,-10deg)}60%{-webkit-transform:rotate3d(0,0,1,5deg);-ms-transform:rotate3d(0,0,1,5deg);transform:rotate3d(0,0,1,5deg)}80%{-webkit-transform:rotate3d(0,0,1,-5deg);-ms-transform:rotate3d(0,0,1,-5deg);transform:rotate3d(0,0,1,-5deg)}100%{-webkit-transform:rotate3d(0,0,1,0deg);-ms-transform:rotate3d(0,0,1,0deg);transform:rotate3d(0,0,1,0deg)}}.swing{-webkit-transform-origin:top center;-ms-transform-origin:top center;transform-origin:top center;-webkit-animation-name:swing;animation-name:swing}@-webkit-keyframes tada{0%{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}10%,20%{-webkit-transform:scale3d(.9,.9,.9) rotate3d(0,0,1,-3deg);transform:scale3d(.9,.9,.9) rotate3d(0,0,1,-3deg)}30%,50%,70%,90%{-webkit-transform:scale3d(1.1,1.1,1.1) rotate3d(0,0,1,3deg);transform:scale3d(1.1,1.1,1.1) rotate3d(0,0,1,3deg)}40%,60%,80%{-webkit-transform:scale3d(1.1,1.1,1.1) rotate3d(0,0,1,-3deg);transform:scale3d(1.1,1.1,1.1) rotate3d(0,0,1,-3deg)}100%{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}}@keyframes tada{0%{-webkit-transform:scale3d(1,1,1);-ms-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}10%,20%{-webkit-transform:scale3d(.9,.9,.9) rotate3d(0,0,1,-3deg);-ms-transform:scale3d(.9,.9,.9) rotate3d(0,0,1,-3deg);transform:scale3d(.9,.9,.9) rotate3d(0,0,1,-3deg)}30%,50%,70%,90%{-webkit-transform:scale3d(1.1,1.1,1.1) rotate3d(0,0,1,3deg);-ms-transform:scale3d(1.1,1.1,1.1) rotate3d(0,0,1,3deg);transform:scale3d(1.1,1.1,1.1) rotate3d(0,0,1,3deg)}40%,60%,80%{-webkit-transform:scale3d(1.1,1.1,1.1) rotate3d(0,0,1,-3deg);-ms-transform:scale3d(1.1,1.1,1.1) rotate3d(0,0,1,-3deg);transform:scale3d(1.1,1.1,1.1) rotate3d(0,0,1,-3deg)}100%{-webkit-transform:scale3d(1,1,1);-ms-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}}.tada{-webkit-animation-name:tada;animation-name:tada}@-webkit-keyframes wobble{0%{-webkit-transform:none;transform:none}15%{-webkit-transform:translate3d(-25%,0,0) rotate3d(0,0,1,-5deg);transform:translate3d(-25%,0,0) rotate3d(0,0,1,-5deg)}30%{-webkit-transform:translate3d(20%,0,0) rotate3d(0,0,1,3deg);transform:translate3d(20%,0,0) rotate3d(0,0,1,3deg)}45%{-webkit-transform:translate3d(-15%,0,0) rotate3d(0,0,1,-3deg);transform:translate3d(-15%,0,0) rotate3d(0,0,1,-3deg)}60%{-webkit-transform:translate3d(10%,0,0) rotate3d(0,0,1,2deg);transform:translate3d(10%,0,0) rotate3d(0,0,1,2deg)}75%{-webkit-transform:translate3d(-5%,0,0) rotate3d(0,0,1,-1deg);transform:translate3d(-5%,0,0) rotate3d(0,0,1,-1deg)}100%{-webkit-transform:none;transform:none}}@keyframes wobble{0%{-webkit-transform:none;-ms-transform:none;transform:none}15%{-webkit-transform:translate3d(-25%,0,0) rotate3d(0,0,1,-5deg);-ms-transform:translate3d(-25%,0,0) rotate3d(0,0,1,-5deg);transform:translate3d(-25%,0,0) rotate3d(0,0,1,-5deg)}30%{-webkit-transform:translate3d(20%,0,0) rotate3d(0,0,1,3deg);-ms-transform:translate3d(20%,0,0) rotate3d(0,0,1,3deg);transform:translate3d(20%,0,0) rotate3d(0,0,1,3deg)}45%{-webkit-transform:translate3d(-15%,0,0) rotate3d(0,0,1,-3deg);-ms-transform:translate3d(-15%,0,0) rotate3d(0,0,1,-3deg);transform:translate3d(-15%,0,0) rotate3d(0,0,1,-3deg)}60%{-webkit-transform:translate3d(10%,0,0) rotate3d(0,0,1,2deg);-ms-transform:translate3d(10%,0,0) rotate3d(0,0,1,2deg);transform:translate3d(10%,0,0) rotate3d(0,0,1,2deg)}75%{-webkit-transform:translate3d(-5%,0,0) rotate3d(0,0,1,-1deg);-ms-transform:translate3d(-5%,0,0) rotate3d(0,0,1,-1deg);transform:translate3d(-5%,0,0) rotate3d(0,0,1,-1deg)}100%{-webkit-transform:none;-ms-transform:none;transform:none}}.wobble{-webkit-animation-name:wobble;animation-name:wobble}@-webkit-keyframes bounceIn{0%,100%,20%,40%,60%,80%{-webkit-transition-timing-function:cubic-bezier(0.215,.61,.355,1);transition-timing-function:cubic-bezier(0.215,.61,.355,1)}0%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}20%{-webkit-transform:scale3d(1.1,1.1,1.1);transform:scale3d(1.1,1.1,1.1)}40%{-webkit-transform:scale3d(.9,.9,.9);transform:scale3d(.9,.9,.9)}60%{opacity:1;-webkit-transform:scale3d(1.03,1.03,1.03);transform:scale3d(1.03,1.03,1.03)}80%{-webkit-transform:scale3d(.97,.97,.97);transform:scale3d(.97,.97,.97)}100%{opacity:1;-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}}@keyframes bounceIn{0%,100%,20%,40%,60%,80%{-webkit-transition-timing-function:cubic-bezier(0.215,.61,.355,1);transition-timing-function:cubic-bezier(0.215,.61,.355,1)}0%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);-ms-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}20%{-webkit-transform:scale3d(1.1,1.1,1.1);-ms-transform:scale3d(1.1,1.1,1.1);transform:scale3d(1.1,1.1,1.1)}40%{-webkit-transform:scale3d(.9,.9,.9);-ms-transform:scale3d(.9,.9,.9);transform:scale3d(.9,.9,.9)}60%{opacity:1;-webkit-transform:scale3d(1.03,1.03,1.03);-ms-transform:scale3d(1.03,1.03,1.03);transform:scale3d(1.03,1.03,1.03)}80%{-webkit-transform:scale3d(.97,.97,.97);-ms-transform:scale3d(.97,.97,.97);transform:scale3d(.97,.97,.97)}100%{opacity:1;-webkit-transform:scale3d(1,1,1);-ms-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}}.bounceIn{-webkit-animation-name:bounceIn;animation-name:bounceIn;-webkit-animation-duration:.75s;animation-duration:.75s}@-webkit-keyframes bounceInDown{0%,100%,60%,75%,90%{-webkit-transition-timing-function:cubic-bezier(0.215,.61,.355,1);transition-timing-function:cubic-bezier(0.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(0,-3000px,0);transform:translate3d(0,-3000px,0)}60%{opacity:1;-webkit-transform:translate3d(0,25px,0);transform:translate3d(0,25px,0)}75%{-webkit-transform:translate3d(0,-10px,0);transform:translate3d(0,-10px,0)}90%{-webkit-transform:translate3d(0,5px,0);transform:translate3d(0,5px,0)}100%{-webkit-transform:none;transform:none}}@keyframes bounceInDown{0%,100%,60%,75%,90%{-webkit-transition-timing-function:cubic-bezier(0.215,.61,.355,1);transition-timing-function:cubic-bezier(0.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(0,-3000px,0);-ms-transform:translate3d(0,-3000px,0);transform:translate3d(0,-3000px,0)}60%{opacity:1;-webkit-transform:translate3d(0,25px,0);-ms-transform:translate3d(0,25px,0);transform:translate3d(0,25px,0)}75%{-webkit-transform:translate3d(0,-10px,0);-ms-transform:translate3d(0,-10px,0);transform:translate3d(0,-10px,0)}90%{-webkit-transform:translate3d(0,5px,0);-ms-transform:translate3d(0,5px,0);transform:translate3d(0,5px,0)}100%{-webkit-transform:none;-ms-transform:none;transform:none}}.bounceInDown{-webkit-animation-name:bounceInDown;animation-name:bounceInDown}@-webkit-keyframes bounceInLeft{0%,100%,60%,75%,90%{-webkit-transition-timing-function:cubic-bezier(0.215,.61,.355,1);transition-timing-function:cubic-bezier(0.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(-3000px,0,0);transform:translate3d(-3000px,0,0)}60%{opacity:1;-webkit-transform:translate3d(25px,0,0);transform:translate3d(25px,0,0)}75%{-webkit-transform:translate3d(-10px,0,0);transform:translate3d(-10px,0,0)}90%{-webkit-transform:translate3d(5px,0,0);transform:translate3d(5px,0,0)}100%{-webkit-transform:none;transform:none}}@keyframes bounceInLeft{0%,100%,60%,75%,90%{-webkit-transition-timing-function:cubic-bezier(0.215,.61,.355,1);transition-timing-function:cubic-bezier(0.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(-3000px,0,0);-ms-transform:translate3d(-3000px,0,0);transform:translate3d(-3000px,0,0)}60%{opacity:1;-webkit-transform:translate3d(25px,0,0);-ms-transform:translate3d(25px,0,0);transform:translate3d(25px,0,0)}75%{-webkit-transform:translate3d(-10px,0,0);-ms-transform:translate3d(-10px,0,0);transform:translate3d(-10px,0,0)}90%{-webkit-transform:translate3d(5px,0,0);-ms-transform:translate3d(5px,0,0);transform:translate3d(5px,0,0)}100%{-webkit-transform:none;-ms-transform:none;transform:none}}.bounceInLeft{-webkit-animation-name:bounceInLeft;animation-name:bounceInLeft}@-webkit-keyframes bounceInRight{0%,100%,60%,75%,90%{-webkit-transition-timing-function:cubic-bezier(0.215,.61,.355,1);transition-timing-function:cubic-bezier(0.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(3000px,0,0);transform:translate3d(3000px,0,0)}60%{opacity:1;-webkit-transform:translate3d(-25px,0,0);transform:translate3d(-25px,0,0)}75%{-webkit-transform:translate3d(10px,0,0);transform:translate3d(10px,0,0)}90%{-webkit-transform:translate3d(-5px,0,0);transform:translate3d(-5px,0,0)}100%{-webkit-transform:none;transform:none}}@keyframes bounceInRight{0%,100%,60%,75%,90%{-webkit-transition-timing-function:cubic-bezier(0.215,.61,.355,1);transition-timing-function:cubic-bezier(0.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(3000px,0,0);-ms-transform:translate3d(3000px,0,0);transform:translate3d(3000px,0,0)}60%{opacity:1;-webkit-transform:translate3d(-25px,0,0);-ms-transform:translate3d(-25px,0,0);transform:translate3d(-25px,0,0)}75%{-webkit-transform:translate3d(10px,0,0);-ms-transform:translate3d(10px,0,0);transform:translate3d(10px,0,0)}90%{-webkit-transform:translate3d(-5px,0,0);-ms-transform:translate3d(-5px,0,0);transform:translate3d(-5px,0,0)}100%{-webkit-transform:none;-ms-transform:none;transform:none}}.bounceInRight{-webkit-animation-name:bounceInRight;animation-name:bounceInRight}@-webkit-keyframes bounceInUp{0%,100%,60%,75%,90%{-webkit-transition-timing-function:cubic-bezier(0.215,.61,.355,1);transition-timing-function:cubic-bezier(0.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(0,3000px,0);transform:translate3d(0,3000px,0)}60%{opacity:1;-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0)}75%{-webkit-transform:translate3d(0,10px,0);transform:translate3d(0,10px,0)}90%{-webkit-transform:translate3d(0,-5px,0);transform:translate3d(0,-5px,0)}100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}@keyframes bounceInUp{0%,100%,60%,75%,90%{-webkit-transition-timing-function:cubic-bezier(0.215,.61,.355,1);transition-timing-function:cubic-bezier(0.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(0,3000px,0);-ms-transform:translate3d(0,3000px,0);transform:translate3d(0,3000px,0)}60%{opacity:1;-webkit-transform:translate3d(0,-20px,0);-ms-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0)}75%{-webkit-transform:translate3d(0,10px,0);-ms-transform:translate3d(0,10px,0);transform:translate3d(0,10px,0)}90%{-webkit-transform:translate3d(0,-5px,0);-ms-transform:translate3d(0,-5px,0);transform:translate3d(0,-5px,0)}100%{-webkit-transform:translate3d(0,0,0);-ms-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.bounceInUp{-webkit-animation-name:bounceInUp;animation-name:bounceInUp}@-webkit-keyframes bounceOut{20%{-webkit-transform:scale3d(.9,.9,.9);transform:scale3d(.9,.9,.9)}50%,55%{opacity:1;-webkit-transform:scale3d(1.1,1.1,1.1);transform:scale3d(1.1,1.1,1.1)}100%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}}@keyframes bounceOut{20%{-webkit-transform:scale3d(.9,.9,.9);-ms-transform:scale3d(.9,.9,.9);transform:scale3d(.9,.9,.9)}50%,55%{opacity:1;-webkit-transform:scale3d(1.1,1.1,1.1);-ms-transform:scale3d(1.1,1.1,1.1);transform:scale3d(1.1,1.1,1.1)}100%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);-ms-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}}.bounceOut{-webkit-animation-name:bounceOut;animation-name:bounceOut;-webkit-animation-duration:.75s;animation-duration:.75s}@-webkit-keyframes bounceOutDown{20%{-webkit-transform:translate3d(0,10px,0);transform:translate3d(0,10px,0)}40%,45%{opacity:1;-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0)}100%{opacity:0;-webkit-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)}}@keyframes bounceOutDown{20%{-webkit-transform:translate3d(0,10px,0);-ms-transform:translate3d(0,10px,0);transform:translate3d(0,10px,0)}40%,45%{opacity:1;-webkit-transform:translate3d(0,-20px,0);-ms-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0)}100%{opacity:0;-webkit-transform:translate3d(0,2000px,0);-ms-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)}}.bounceOutDown{-webkit-animation-name:bounceOutDown;animation-name:bounceOutDown}@-webkit-keyframes bounceOutLeft{20%{opacity:1;-webkit-transform:translate3d(20px,0,0);transform:translate3d(20px,0,0)}100%{opacity:0;-webkit-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)}}@keyframes bounceOutLeft{20%{opacity:1;-webkit-transform:translate3d(20px,0,0);-ms-transform:translate3d(20px,0,0);transform:translate3d(20px,0,0)}100%{opacity:0;-webkit-transform:translate3d(-2000px,0,0);-ms-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)}}.bounceOutLeft{-webkit-animation-name:bounceOutLeft;animation-name:bounceOutLeft}@-webkit-keyframes bounceOutRight{20%{opacity:1;-webkit-transform:translate3d(-20px,0,0);transform:translate3d(-20px,0,0)}100%{opacity:0;-webkit-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)}}@keyframes bounceOutRight{20%{opacity:1;-webkit-transform:translate3d(-20px,0,0);-ms-transform:translate3d(-20px,0,0);transform:translate3d(-20px,0,0)}100%{opacity:0;-webkit-transform:translate3d(2000px,0,0);-ms-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)}}.bounceOutRight{-webkit-animation-name:bounceOutRight;animation-name:bounceOutRight}@-webkit-keyframes bounceOutUp{20%{-webkit-transform:translate3d(0,-10px,0);transform:translate3d(0,-10px,0)}40%,45%{opacity:1;-webkit-transform:translate3d(0,20px,0);transform:translate3d(0,20px,0)}100%{opacity:0;-webkit-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)}}@keyframes bounceOutUp{20%{-webkit-transform:translate3d(0,-10px,0);-ms-transform:translate3d(0,-10px,0);transform:translate3d(0,-10px,0)}40%,45%{opacity:1;-webkit-transform:translate3d(0,20px,0);-ms-transform:translate3d(0,20px,0);transform:translate3d(0,20px,0)}100%{opacity:0;-webkit-transform:translate3d(0,-2000px,0);-ms-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)}}.bounceOutUp{-webkit-animation-name:bounceOutUp;animation-name:bounceOutUp}@-webkit-keyframes fadeIn{0%{opacity:0}100%{opacity:1}}@keyframes fadeIn{0%{opacity:0}100%{opacity:1}}.fadeIn{-webkit-animation-name:fadeIn;animation-name:fadeIn}@-webkit-keyframes fadeInDown{0%{opacity:0;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}100%{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInDown{0%{opacity:0;-webkit-transform:translate3d(0,-100%,0);-ms-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}100%{opacity:1;-webkit-transform:none;-ms-transform:none;transform:none}}.fadeInDown{-webkit-animation-name:fadeInDown;animation-name:fadeInDown}@-webkit-keyframes fadeInDownBig{0%{opacity:0;-webkit-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)}100%{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInDownBig{0%{opacity:0;-webkit-transform:translate3d(0,-2000px,0);-ms-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)}100%{opacity:1;-webkit-transform:none;-ms-transform:none;transform:none}}.fadeInDownBig{-webkit-animation-name:fadeInDownBig;animation-name:fadeInDownBig}@-webkit-keyframes fadeInLeft{0%{opacity:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}100%{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInLeft{0%{opacity:0;-webkit-transform:translate3d(-100%,0,0);-ms-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}100%{opacity:1;-webkit-transform:none;-ms-transform:none;transform:none}}.fadeInLeft{-webkit-animation-name:fadeInLeft;animation-name:fadeInLeft}@-webkit-keyframes fadeInLeftBig{0%{opacity:0;-webkit-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)}100%{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInLeftBig{0%{opacity:0;-webkit-transform:translate3d(-2000px,0,0);-ms-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)}100%{opacity:1;-webkit-transform:none;-ms-transform:none;transform:none}}.fadeInLeftBig{-webkit-animation-name:fadeInLeftBig;animation-name:fadeInLeftBig}@-webkit-keyframes fadeInRight{0%{opacity:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}100%{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInRight{0%{opacity:0;-webkit-transform:translate3d(100%,0,0);-ms-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}100%{opacity:1;-webkit-transform:none;-ms-transform:none;transform:none}}.fadeInRight{-webkit-animation-name:fadeInRight;animation-name:fadeInRight}@-webkit-keyframes fadeInRightBig{0%{opacity:0;-webkit-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)}100%{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInRightBig{0%{opacity:0;-webkit-transform:translate3d(2000px,0,0);-ms-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)}100%{opacity:1;-webkit-transform:none;-ms-transform:none;transform:none}}.fadeInRightBig{-webkit-animation-name:fadeInRightBig;animation-name:fadeInRightBig}@-webkit-keyframes fadeInUp{0%{opacity:0;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}100%{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInUp{0%{opacity:0;-webkit-transform:translate3d(0,100%,0);-ms-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}100%{opacity:1;-webkit-transform:none;-ms-transform:none;transform:none}}.fadeInUp{-webkit-animation-name:fadeInUp;animation-name:fadeInUp}@-webkit-keyframes fadeInUpBig{0%{opacity:0;-webkit-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)}100%{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInUpBig{0%{opacity:0;-webkit-transform:translate3d(0,2000px,0);-ms-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)}100%{opacity:1;-webkit-transform:none;-ms-transform:none;transform:none}}.fadeInUpBig{-webkit-animation-name:fadeInUpBig;animation-name:fadeInUpBig}@-webkit-keyframes fadeOut{0%{opacity:1}100%{opacity:0}}@keyframes fadeOut{0%{opacity:1}100%{opacity:0}}.fadeOut{-webkit-animation-name:fadeOut;animation-name:fadeOut}@-webkit-keyframes fadeOutDown{0%{opacity:1}100%{opacity:0;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}}@keyframes fadeOutDown{0%{opacity:1}100%{opacity:0;-webkit-transform:translate3d(0,100%,0);-ms-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}}.fadeOutDown{-webkit-animation-name:fadeOutDown;animation-name:fadeOutDown}@-webkit-keyframes fadeOutDownBig{0%{opacity:1}100%{opacity:0;-webkit-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)}}@keyframes fadeOutDownBig{0%{opacity:1}100%{opacity:0;-webkit-transform:translate3d(0,2000px,0);-ms-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)}}.fadeOutDownBig{-webkit-animation-name:fadeOutDownBig;animation-name:fadeOutDownBig}@-webkit-keyframes fadeOutLeft{0%{opacity:1}100%{opacity:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}}@keyframes fadeOutLeft{0%{opacity:1}100%{opacity:0;-webkit-transform:translate3d(-100%,0,0);-ms-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}}.fadeOutLeft{-webkit-animation-name:fadeOutLeft;animation-name:fadeOutLeft}@-webkit-keyframes fadeOutLeftBig{0%{opacity:1}100%{opacity:0;-webkit-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)}}@keyframes fadeOutLeftBig{0%{opacity:1}100%{opacity:0;-webkit-transform:translate3d(-2000px,0,0);-ms-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)}}.fadeOutLeftBig{-webkit-animation-name:fadeOutLeftBig;animation-name:fadeOutLeftBig}@-webkit-keyframes fadeOutRight{0%{opacity:1}100%{opacity:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}}@keyframes fadeOutRight{0%{opacity:1}100%{opacity:0;-webkit-transform:translate3d(100%,0,0);-ms-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}}.fadeOutRight{-webkit-animation-name:fadeOutRight;animation-name:fadeOutRight}@-webkit-keyframes fadeOutRightBig{0%{opacity:1}100%{opacity:0;-webkit-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)}}@keyframes fadeOutRightBig{0%{opacity:1}100%{opacity:0;-webkit-transform:translate3d(2000px,0,0);-ms-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)}}.fadeOutRightBig{-webkit-animation-name:fadeOutRightBig;animation-name:fadeOutRightBig}@-webkit-keyframes fadeOutUp{0%{opacity:1}100%{opacity:0;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}}@keyframes fadeOutUp{0%{opacity:1}100%{opacity:0;-webkit-transform:translate3d(0,-100%,0);-ms-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}}.fadeOutUp{-webkit-animation-name:fadeOutUp;animation-name:fadeOutUp}@-webkit-keyframes fadeOutUpBig{0%{opacity:1}100%{opacity:0;-webkit-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)}}@keyframes fadeOutUpBig{0%{opacity:1}100%{opacity:0;-webkit-transform:translate3d(0,-2000px,0);-ms-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)}}.fadeOutUpBig{-webkit-animation-name:fadeOutUpBig;animation-name:fadeOutUpBig}@-webkit-keyframes flip{0%{-webkit-transform:perspective(400px) rotate3d(0,1,0,-360deg);transform:perspective(400px) rotate3d(0,1,0,-360deg);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}40%{-webkit-transform:perspective(400px) translate3d(0,0,150px) rotate3d(0,1,0,-190deg);transform:perspective(400px) translate3d(0,0,150px) rotate3d(0,1,0,-190deg);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}50%{-webkit-transform:perspective(400px) translate3d(0,0,150px) rotate3d(0,1,0,-170deg);transform:perspective(400px) translate3d(0,0,150px) rotate3d(0,1,0,-170deg);-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}80%{-webkit-transform:perspective(400px) scale3d(.95,.95,.95);transform:perspective(400px) scale3d(.95,.95,.95);-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}100%{-webkit-transform:perspective(400px);transform:perspective(400px);-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}}@keyframes flip{0%{-webkit-transform:perspective(400px) rotate3d(0,1,0,-360deg);-ms-transform:perspective(400px) rotate3d(0,1,0,-360deg);transform:perspective(400px) rotate3d(0,1,0,-360deg);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}40%{-webkit-transform:perspective(400px) translate3d(0,0,150px) rotate3d(0,1,0,-190deg);-ms-transform:perspective(400px) translate3d(0,0,150px) rotate3d(0,1,0,-190deg);transform:perspective(400px) translate3d(0,0,150px) rotate3d(0,1,0,-190deg);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}50%{-webkit-transform:perspective(400px) translate3d(0,0,150px) rotate3d(0,1,0,-170deg);-ms-transform:perspective(400px) translate3d(0,0,150px) rotate3d(0,1,0,-170deg);transform:perspective(400px) translate3d(0,0,150px) rotate3d(0,1,0,-170deg);-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}80%{-webkit-transform:perspective(400px) scale3d(.95,.95,.95);-ms-transform:perspective(400px) scale3d(.95,.95,.95);transform:perspective(400px) scale3d(.95,.95,.95);-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}100%{-webkit-transform:perspective(400px);-ms-transform:perspective(400px);transform:perspective(400px);-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}}.animated.flip{-webkit-backface-visibility:visible;-ms-backface-visibility:visible;backface-visibility:visible;-webkit-animation-name:flip;animation-name:flip}@-webkit-keyframes flipInX{0%{-webkit-transform:perspective(400px) rotate3d(1,0,0,90deg);transform:perspective(400px) rotate3d(1,0,0,90deg);-webkit-transition-timing-function:ease-in;transition-timing-function:ease-in;opacity:0}40%{-webkit-transform:perspective(400px) rotate3d(1,0,0,-20deg);transform:perspective(400px) rotate3d(1,0,0,-20deg);-webkit-transition-timing-function:ease-in;transition-timing-function:ease-in}60%{-webkit-transform:perspective(400px) rotate3d(1,0,0,10deg);transform:perspective(400px) rotate3d(1,0,0,10deg);opacity:1}80%{-webkit-transform:perspective(400px) rotate3d(1,0,0,-5deg);transform:perspective(400px) rotate3d(1,0,0,-5deg)}100%{-webkit-transform:perspective(400px);transform:perspective(400px)}}@keyframes flipInX{0%{-webkit-transform:perspective(400px) rotate3d(1,0,0,90deg);-ms-transform:perspective(400px) rotate3d(1,0,0,90deg);transform:perspective(400px) rotate3d(1,0,0,90deg);-webkit-transition-timing-function:ease-in;transition-timing-function:ease-in;opacity:0}40%{-webkit-transform:perspective(400px) rotate3d(1,0,0,-20deg);-ms-transform:perspective(400px) rotate3d(1,0,0,-20deg);transform:perspective(400px) rotate3d(1,0,0,-20deg);-webkit-transition-timing-function:ease-in;transition-timing-function:ease-in}60%{-webkit-transform:perspective(400px) rotate3d(1,0,0,10deg);-ms-transform:perspective(400px) rotate3d(1,0,0,10deg);transform:perspective(400px) rotate3d(1,0,0,10deg);opacity:1}80%{-webkit-transform:perspective(400px) rotate3d(1,0,0,-5deg);-ms-transform:perspective(400px) rotate3d(1,0,0,-5deg);transform:perspective(400px) rotate3d(1,0,0,-5deg)}100%{-webkit-transform:perspective(400px);-ms-transform:perspective(400px);transform:perspective(400px)}}.flipInX{-webkit-backface-visibility:visible!important;-ms-backface-visibility:visible!important;backface-visibility:visible!important;-webkit-animation-name:flipInX;animation-name:flipInX}@-webkit-keyframes flipInY{0%{-webkit-transform:perspective(400px) rotate3d(0,1,0,90deg);transform:perspective(400px) rotate3d(0,1,0,90deg);-webkit-transition-timing-function:ease-in;transition-timing-function:ease-in;opacity:0}40%{-webkit-transform:perspective(400px) rotate3d(0,1,0,-20deg);transform:perspective(400px) rotate3d(0,1,0,-20deg);-webkit-transition-timing-function:ease-in;transition-timing-function:ease-in}60%{-webkit-transform:perspective(400px) rotate3d(0,1,0,10deg);transform:perspective(400px) rotate3d(0,1,0,10deg);opacity:1}80%{-webkit-transform:perspective(400px) rotate3d(0,1,0,-5deg);transform:perspective(400px) rotate3d(0,1,0,-5deg)}100%{-webkit-transform:perspective(400px);transform:perspective(400px)}}@keyframes flipInY{0%{-webkit-transform:perspective(400px) rotate3d(0,1,0,90deg);-ms-transform:perspective(400px) rotate3d(0,1,0,90deg);transform:perspective(400px) rotate3d(0,1,0,90deg);-webkit-transition-timing-function:ease-in;transition-timing-function:ease-in;opacity:0}40%{-webkit-transform:perspective(400px) rotate3d(0,1,0,-20deg);-ms-transform:perspective(400px) rotate3d(0,1,0,-20deg);transform:perspective(400px) rotate3d(0,1,0,-20deg);-webkit-transition-timing-function:ease-in;transition-timing-function:ease-in}60%{-webkit-transform:perspective(400px) rotate3d(0,1,0,10deg);-ms-transform:perspective(400px) rotate3d(0,1,0,10deg);transform:perspective(400px) rotate3d(0,1,0,10deg);opacity:1}80%{-webkit-transform:perspective(400px) rotate3d(0,1,0,-5deg);-ms-transform:perspective(400px) rotate3d(0,1,0,-5deg);transform:perspective(400px) rotate3d(0,1,0,-5deg)}100%{-webkit-transform:perspective(400px);-ms-transform:perspective(400px);transform:perspective(400px)}}.flipInY{-webkit-backface-visibility:visible!important;-ms-backface-visibility:visible!important;backface-visibility:visible!important;-webkit-animation-name:flipInY;animation-name:flipInY}@-webkit-keyframes flipOutX{0%{-webkit-transform:perspective(400px);transform:perspective(400px)}30%{-webkit-transform:perspective(400px) rotate3d(1,0,0,-20deg);transform:perspective(400px) rotate3d(1,0,0,-20deg);opacity:1}100%{-webkit-transform:perspective(400px) rotate3d(1,0,0,90deg);transform:perspective(400px) rotate3d(1,0,0,90deg);opacity:0}}@keyframes flipOutX{0%{-webkit-transform:perspective(400px);-ms-transform:perspective(400px);transform:perspective(400px)}30%{-webkit-transform:perspective(400px) rotate3d(1,0,0,-20deg);-ms-transform:perspective(400px) rotate3d(1,0,0,-20deg);transform:perspective(400px) rotate3d(1,0,0,-20deg);opacity:1}100%{-webkit-transform:perspective(400px) rotate3d(1,0,0,90deg);-ms-transform:perspective(400px) rotate3d(1,0,0,90deg);transform:perspective(400px) rotate3d(1,0,0,90deg);opacity:0}}.flipOutX{-webkit-animation-name:flipOutX;animation-name:flipOutX;-webkit-animation-duration:.75s;animation-duration:.75s;-webkit-backface-visibility:visible!important;-ms-backface-visibility:visible!important;backface-visibility:visible!important}@-webkit-keyframes flipOutY{0%{-webkit-transform:perspective(400px);transform:perspective(400px)}30%{-webkit-transform:perspective(400px) rotate3d(0,1,0,-15deg);transform:perspective(400px) rotate3d(0,1,0,-15deg);opacity:1}100%{-webkit-transform:perspective(400px) rotate3d(0,1,0,90deg);transform:perspective(400px) rotate3d(0,1,0,90deg);opacity:0}}@keyframes flipOutY{0%{-webkit-transform:perspective(400px);-ms-transform:perspective(400px);transform:perspective(400px)}30%{-webkit-transform:perspective(400px) rotate3d(0,1,0,-15deg);-ms-transform:perspective(400px) rotate3d(0,1,0,-15deg);transform:perspective(400px) rotate3d(0,1,0,-15deg);opacity:1}100%{-webkit-transform:perspective(400px) rotate3d(0,1,0,90deg);-ms-transform:perspective(400px) rotate3d(0,1,0,90deg);transform:perspective(400px) rotate3d(0,1,0,90deg);opacity:0}}.flipOutY{-webkit-backface-visibility:visible!important;-ms-backface-visibility:visible!important;backface-visibility:visible!important;-webkit-animation-name:flipOutY;animation-name:flipOutY;-webkit-animation-duration:.75s;animation-duration:.75s}@-webkit-keyframes lightSpeedIn{0%{-webkit-transform:translate3d(100%,0,0) skewX(-30deg);transform:translate3d(100%,0,0) skewX(-30deg);opacity:0}60%{-webkit-transform:skewX(20deg);transform:skewX(20deg);opacity:1}80%{-webkit-transform:skewX(-5deg);transform:skewX(-5deg);opacity:1}100%{-webkit-transform:none;transform:none;opacity:1}}@keyframes lightSpeedIn{0%{-webkit-transform:translate3d(100%,0,0) skewX(-30deg);-ms-transform:translate3d(100%,0,0) skewX(-30deg);transform:translate3d(100%,0,0) skewX(-30deg);opacity:0}60%{-webkit-transform:skewX(20deg);-ms-transform:skewX(20deg);transform:skewX(20deg);opacity:1}80%{-webkit-transform:skewX(-5deg);-ms-transform:skewX(-5deg);transform:skewX(-5deg);opacity:1}100%{-webkit-transform:none;-ms-transform:none;transform:none;opacity:1}}.lightSpeedIn{-webkit-animation-name:lightSpeedIn;animation-name:lightSpeedIn;-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}@-webkit-keyframes lightSpeedOut{0%{opacity:1}100%{-webkit-transform:translate3d(100%,0,0) skewX(30deg);transform:translate3d(100%,0,0) skewX(30deg);opacity:0}}@keyframes lightSpeedOut{0%{opacity:1}100%{-webkit-transform:translate3d(100%,0,0) skewX(30deg);-ms-transform:translate3d(100%,0,0) skewX(30deg);transform:translate3d(100%,0,0) skewX(30deg);opacity:0}}.lightSpeedOut{-webkit-animation-name:lightSpeedOut;animation-name:lightSpeedOut;-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}@-webkit-keyframes rotateIn{0%{-webkit-transform-origin:center;transform-origin:center;-webkit-transform:rotate3d(0,0,1,-200deg);transform:rotate3d(0,0,1,-200deg);opacity:0}100%{-webkit-transform-origin:center;transform-origin:center;-webkit-transform:none;transform:none;opacity:1}}@keyframes rotateIn{0%{-webkit-transform-origin:center;-ms-transform-origin:center;transform-origin:center;-webkit-transform:rotate3d(0,0,1,-200deg);-ms-transform:rotate3d(0,0,1,-200deg);transform:rotate3d(0,0,1,-200deg);opacity:0}100%{-webkit-transform-origin:center;-ms-transform-origin:center;transform-origin:center;-webkit-transform:none;-ms-transform:none;transform:none;opacity:1}}.rotateIn{-webkit-animation-name:rotateIn;animation-name:rotateIn}@-webkit-keyframes rotateInDownLeft{0%{-webkit-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:rotate3d(0,0,1,-45deg);transform:rotate3d(0,0,1,-45deg);opacity:0}100%{-webkit-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:none;transform:none;opacity:1}}@keyframes rotateInDownLeft{0%{-webkit-transform-origin:left bottom;-ms-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:rotate3d(0,0,1,-45deg);-ms-transform:rotate3d(0,0,1,-45deg);transform:rotate3d(0,0,1,-45deg);opacity:0}100%{-webkit-transform-origin:left bottom;-ms-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:none;-ms-transform:none;transform:none;opacity:1}}.rotateInDownLeft{-webkit-animation-name:rotateInDownLeft;animation-name:rotateInDownLeft}@-webkit-keyframes rotateInDownRight{0%{-webkit-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:rotate3d(0,0,1,45deg);transform:rotate3d(0,0,1,45deg);opacity:0}100%{-webkit-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:none;transform:none;opacity:1}}@keyframes rotateInDownRight{0%{-webkit-transform-origin:right bottom;-ms-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:rotate3d(0,0,1,45deg);-ms-transform:rotate3d(0,0,1,45deg);transform:rotate3d(0,0,1,45deg);opacity:0}100%{-webkit-transform-origin:right bottom;-ms-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:none;-ms-transform:none;transform:none;opacity:1}}.rotateInDownRight{-webkit-animation-name:rotateInDownRight;animation-name:rotateInDownRight}@-webkit-keyframes rotateInUpLeft{0%{-webkit-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:rotate3d(0,0,1,45deg);transform:rotate3d(0,0,1,45deg);opacity:0}100%{-webkit-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:none;transform:none;opacity:1}}@keyframes rotateInUpLeft{0%{-webkit-transform-origin:left bottom;-ms-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:rotate3d(0,0,1,45deg);-ms-transform:rotate3d(0,0,1,45deg);transform:rotate3d(0,0,1,45deg);opacity:0}100%{-webkit-transform-origin:left bottom;-ms-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:none;-ms-transform:none;transform:none;opacity:1}}.rotateInUpLeft{-webkit-animation-name:rotateInUpLeft;animation-name:rotateInUpLeft}@-webkit-keyframes rotateInUpRight{0%{-webkit-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:rotate3d(0,0,1,-90deg);transform:rotate3d(0,0,1,-90deg);opacity:0}100%{-webkit-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:none;transform:none;opacity:1}}@keyframes rotateInUpRight{0%{-webkit-transform-origin:right bottom;-ms-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:rotate3d(0,0,1,-90deg);-ms-transform:rotate3d(0,0,1,-90deg);transform:rotate3d(0,0,1,-90deg);opacity:0}100%{-webkit-transform-origin:right bottom;-ms-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:none;-ms-transform:none;transform:none;opacity:1}}.rotateInUpRight{-webkit-animation-name:rotateInUpRight;animation-name:rotateInUpRight}@-webkit-keyframes rotateOut{0%{-webkit-transform-origin:center;transform-origin:center;opacity:1}100%{-webkit-transform-origin:center;transform-origin:center;-webkit-transform:rotate3d(0,0,1,200deg);transform:rotate3d(0,0,1,200deg);opacity:0}}@keyframes rotateOut{0%{-webkit-transform-origin:center;-ms-transform-origin:center;transform-origin:center;opacity:1}100%{-webkit-transform-origin:center;-ms-transform-origin:center;transform-origin:center;-webkit-transform:rotate3d(0,0,1,200deg);-ms-transform:rotate3d(0,0,1,200deg);transform:rotate3d(0,0,1,200deg);opacity:0}}.rotateOut{-webkit-animation-name:rotateOut;animation-name:rotateOut}@-webkit-keyframes rotateOutDownLeft{0%{-webkit-transform-origin:left bottom;transform-origin:left bottom;opacity:1}100%{-webkit-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:rotate(0,0,1,45deg);transform:rotate(0,0,1,45deg);opacity:0}}@keyframes rotateOutDownLeft{0%{-webkit-transform-origin:left bottom;-ms-transform-origin:left bottom;transform-origin:left bottom;opacity:1}100%{-webkit-transform-origin:left bottom;-ms-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:rotate(0,0,1,45deg);-ms-transform:rotate(0,0,1,45deg);transform:rotate(0,0,1,45deg);opacity:0}}.rotateOutDownLeft{-webkit-animation-name:rotateOutDownLeft;animation-name:rotateOutDownLeft}@-webkit-keyframes rotateOutDownRight{0%{-webkit-transform-origin:right bottom;transform-origin:right bottom;opacity:1}100%{-webkit-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:rotate3d(0,0,1,-45deg);transform:rotate3d(0,0,1,-45deg);opacity:0}}@keyframes rotateOutDownRight{0%{-webkit-transform-origin:right bottom;-ms-transform-origin:right bottom;transform-origin:right bottom;opacity:1}100%{-webkit-transform-origin:right bottom;-ms-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:rotate3d(0,0,1,-45deg);-ms-transform:rotate3d(0,0,1,-45deg);transform:rotate3d(0,0,1,-45deg);opacity:0}}.rotateOutDownRight{-webkit-animation-name:rotateOutDownRight;animation-name:rotateOutDownRight}@-webkit-keyframes rotateOutUpLeft{0%{-webkit-transform-origin:left bottom;transform-origin:left bottom;opacity:1}100%{-webkit-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:rotate3d(0,0,1,-45deg);transform:rotate3d(0,0,1,-45deg);opacity:0}}@keyframes rotateOutUpLeft{0%{-webkit-transform-origin:left bottom;-ms-transform-origin:left bottom;transform-origin:left bottom;opacity:1}100%{-webkit-transform-origin:left bottom;-ms-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:rotate3d(0,0,1,-45deg);-ms-transform:rotate3d(0,0,1,-45deg);transform:rotate3d(0,0,1,-45deg);opacity:0}}.rotateOutUpLeft{-webkit-animation-name:rotateOutUpLeft;animation-name:rotateOutUpLeft}@-webkit-keyframes rotateOutUpRight{0%{-webkit-transform-origin:right bottom;transform-origin:right bottom;opacity:1}100%{-webkit-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:rotate3d(0,0,1,90deg);transform:rotate3d(0,0,1,90deg);opacity:0}}@keyframes rotateOutUpRight{0%{-webkit-transform-origin:right bottom;-ms-transform-origin:right bottom;transform-origin:right bottom;opacity:1}100%{-webkit-transform-origin:right bottom;-ms-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:rotate3d(0,0,1,90deg);-ms-transform:rotate3d(0,0,1,90deg);transform:rotate3d(0,0,1,90deg);opacity:0}}.rotateOutUpRight{-webkit-animation-name:rotateOutUpRight;animation-name:rotateOutUpRight}@-webkit-keyframes hinge{0%{-webkit-transform-origin:top left;transform-origin:top left;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}20%,60%{-webkit-transform:rotate3d(0,0,1,80deg);transform:rotate3d(0,0,1,80deg);-webkit-transform-origin:top left;transform-origin:top left;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}40%,80%{-webkit-transform:rotate3d(0,0,1,60deg);transform:rotate3d(0,0,1,60deg);-webkit-transform-origin:top left;transform-origin:top left;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out;opacity:1}100%{-webkit-transform:translate3d(0,700px,0);transform:translate3d(0,700px,0);opacity:0}}@keyframes hinge{0%{-webkit-transform-origin:top left;-ms-transform-origin:top left;transform-origin:top left;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}20%,60%{-webkit-transform:rotate3d(0,0,1,80deg);-ms-transform:rotate3d(0,0,1,80deg);transform:rotate3d(0,0,1,80deg);-webkit-transform-origin:top left;-ms-transform-origin:top left;transform-origin:top left;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}40%,80%{-webkit-transform:rotate3d(0,0,1,60deg);-ms-transform:rotate3d(0,0,1,60deg);transform:rotate3d(0,0,1,60deg);-webkit-transform-origin:top left;-ms-transform-origin:top left;transform-origin:top left;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out;opacity:1}100%{-webkit-transform:translate3d(0,700px,0);-ms-transform:translate3d(0,700px,0);transform:translate3d(0,700px,0);opacity:0}}.hinge{-webkit-animation-name:hinge;animation-name:hinge}@-webkit-keyframes rollIn{0%{opacity:0;-webkit-transform:translate3d(-100%,0,0) rotate3d(0,0,1,-120deg);transform:translate3d(-100%,0,0) rotate3d(0,0,1,-120deg)}100%{opacity:1;-webkit-transform:none;transform:none}}@keyframes rollIn{0%{opacity:0;-webkit-transform:translate3d(-100%,0,0) rotate3d(0,0,1,-120deg);-ms-transform:translate3d(-100%,0,0) rotate3d(0,0,1,-120deg);transform:translate3d(-100%,0,0) rotate3d(0,0,1,-120deg)}100%{opacity:1;-webkit-transform:none;-ms-transform:none;transform:none}}.rollIn{-webkit-animation-name:rollIn;animation-name:rollIn}@-webkit-keyframes rollOut{0%{opacity:1}100%{opacity:0;-webkit-transform:translate3d(100%,0,0) rotate3d(0,0,1,120deg);transform:translate3d(100%,0,0) rotate3d(0,0,1,120deg)}}@keyframes rollOut{0%{opacity:1}100%{opacity:0;-webkit-transform:translate3d(100%,0,0) rotate3d(0,0,1,120deg);-ms-transform:translate3d(100%,0,0) rotate3d(0,0,1,120deg);transform:translate3d(100%,0,0) rotate3d(0,0,1,120deg)}}.rollOut{-webkit-animation-name:rollOut;animation-name:rollOut}@-webkit-keyframes zoomIn{0%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}50%{opacity:1}}@keyframes zoomIn{0%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);-ms-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}50%{opacity:1}}.zoomIn{-webkit-animation-name:zoomIn;animation-name:zoomIn}@-webkit-keyframes zoomInDown{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,-1000px,0);transform:scale3d(.1,.1,.1) translate3d(0,-1000px,0);-webkit-animation-timing-function:cubic-bezier(0.55,.055,.675,.19);animation-timing-function:cubic-bezier(0.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,60px,0);transform:scale3d(.475,.475,.475) translate3d(0,60px,0);-webkit-animation-timing-function:cubic-bezier(0.175,.885,.32,1);animation-timing-function:cubic-bezier(0.175,.885,.32,1)}}@keyframes zoomInDown{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,-1000px,0);-ms-transform:scale3d(.1,.1,.1) translate3d(0,-1000px,0);transform:scale3d(.1,.1,.1) translate3d(0,-1000px,0);-webkit-animation-timing-function:cubic-bezier(0.55,.055,.675,.19);animation-timing-function:cubic-bezier(0.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,60px,0);-ms-transform:scale3d(.475,.475,.475) translate3d(0,60px,0);transform:scale3d(.475,.475,.475) translate3d(0,60px,0);-webkit-animation-timing-function:cubic-bezier(0.175,.885,.32,1);animation-timing-function:cubic-bezier(0.175,.885,.32,1)}}.zoomInDown{-webkit-animation-name:zoomInDown;animation-name:zoomInDown}@-webkit-keyframes zoomInLeft{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(-1000px,0,0);transform:scale3d(.1,.1,.1) translate3d(-1000px,0,0);-webkit-animation-timing-function:cubic-bezier(0.55,.055,.675,.19);animation-timing-function:cubic-bezier(0.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(10px,0,0);transform:scale3d(.475,.475,.475) translate3d(10px,0,0);-webkit-animation-timing-function:cubic-bezier(0.175,.885,.32,1);animation-timing-function:cubic-bezier(0.175,.885,.32,1)}}@keyframes zoomInLeft{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(-1000px,0,0);-ms-transform:scale3d(.1,.1,.1) translate3d(-1000px,0,0);transform:scale3d(.1,.1,.1) translate3d(-1000px,0,0);-webkit-animation-timing-function:cubic-bezier(0.55,.055,.675,.19);animation-timing-function:cubic-bezier(0.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(10px,0,0);-ms-transform:scale3d(.475,.475,.475) translate3d(10px,0,0);transform:scale3d(.475,.475,.475) translate3d(10px,0,0);-webkit-animation-timing-function:cubic-bezier(0.175,.885,.32,1);animation-timing-function:cubic-bezier(0.175,.885,.32,1)}}.zoomInLeft{-webkit-animation-name:zoomInLeft;animation-name:zoomInLeft}@-webkit-keyframes zoomInRight{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(1000px,0,0);transform:scale3d(.1,.1,.1) translate3d(1000px,0,0);-webkit-animation-timing-function:cubic-bezier(0.55,.055,.675,.19);animation-timing-function:cubic-bezier(0.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(-10px,0,0);transform:scale3d(.475,.475,.475) translate3d(-10px,0,0);-webkit-animation-timing-function:cubic-bezier(0.175,.885,.32,1);animation-timing-function:cubic-bezier(0.175,.885,.32,1)}}@keyframes zoomInRight{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(1000px,0,0);-ms-transform:scale3d(.1,.1,.1) translate3d(1000px,0,0);transform:scale3d(.1,.1,.1) translate3d(1000px,0,0);-webkit-animation-timing-function:cubic-bezier(0.55,.055,.675,.19);animation-timing-function:cubic-bezier(0.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(-10px,0,0);-ms-transform:scale3d(.475,.475,.475) translate3d(-10px,0,0);transform:scale3d(.475,.475,.475) translate3d(-10px,0,0);-webkit-animation-timing-function:cubic-bezier(0.175,.885,.32,1);animation-timing-function:cubic-bezier(0.175,.885,.32,1)}}.zoomInRight{-webkit-animation-name:zoomInRight;animation-name:zoomInRight}@-webkit-keyframes zoomInUp{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,1000px,0);transform:scale3d(.1,.1,.1) translate3d(0,1000px,0);-webkit-animation-timing-function:cubic-bezier(0.55,.055,.675,.19);animation-timing-function:cubic-bezier(0.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);-webkit-animation-timing-function:cubic-bezier(0.175,.885,.32,1);animation-timing-function:cubic-bezier(0.175,.885,.32,1)}}@keyframes zoomInUp{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,1000px,0);-ms-transform:scale3d(.1,.1,.1) translate3d(0,1000px,0);transform:scale3d(.1,.1,.1) translate3d(0,1000px,0);-webkit-animation-timing-function:cubic-bezier(0.55,.055,.675,.19);animation-timing-function:cubic-bezier(0.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);-ms-transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);-webkit-animation-timing-function:cubic-bezier(0.175,.885,.32,1);animation-timing-function:cubic-bezier(0.175,.885,.32,1)}}.zoomInUp{-webkit-animation-name:zoomInUp;animation-name:zoomInUp}@-webkit-keyframes zoomOut{0%{opacity:1}50%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}100%{opacity:0}}@keyframes zoomOut{0%{opacity:1}50%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);-ms-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}100%{opacity:0}}.zoomOut{-webkit-animation-name:zoomOut;animation-name:zoomOut}@-webkit-keyframes zoomOutDown{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);-webkit-animation-timing-function:cubic-bezier(0.55,.055,.675,.19);animation-timing-function:cubic-bezier(0.55,.055,.675,.19)}100%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,2000px,0);transform:scale3d(.1,.1,.1) translate3d(0,2000px,0);-webkit-transform-origin:center bottom;transform-origin:center bottom;-webkit-animation-timing-function:cubic-bezier(0.175,.885,.32,1);animation-timing-function:cubic-bezier(0.175,.885,.32,1)}}@keyframes zoomOutDown{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);-ms-transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);-webkit-animation-timing-function:cubic-bezier(0.55,.055,.675,.19);animation-timing-function:cubic-bezier(0.55,.055,.675,.19)}100%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,2000px,0);-ms-transform:scale3d(.1,.1,.1) translate3d(0,2000px,0);transform:scale3d(.1,.1,.1) translate3d(0,2000px,0);-webkit-transform-origin:center bottom;-ms-transform-origin:center bottom;transform-origin:center bottom;-webkit-animation-timing-function:cubic-bezier(0.175,.885,.32,1);animation-timing-function:cubic-bezier(0.175,.885,.32,1)}}.zoomOutDown{-webkit-animation-name:zoomOutDown;animation-name:zoomOutDown}@-webkit-keyframes zoomOutLeft{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(42px,0,0);transform:scale3d(.475,.475,.475) translate3d(42px,0,0)}100%{opacity:0;-webkit-transform:scale(.1) translate3d(-2000px,0,0);transform:scale(.1) translate3d(-2000px,0,0);-webkit-transform-origin:left center;transform-origin:left center}}@keyframes zoomOutLeft{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(42px,0,0);-ms-transform:scale3d(.475,.475,.475) translate3d(42px,0,0);transform:scale3d(.475,.475,.475) translate3d(42px,0,0)}100%{opacity:0;-webkit-transform:scale(.1) translate3d(-2000px,0,0);-ms-transform:scale(.1) translate3d(-2000px,0,0);transform:scale(.1) translate3d(-2000px,0,0);-webkit-transform-origin:left center;-ms-transform-origin:left center;transform-origin:left center}}.zoomOutLeft{-webkit-animation-name:zoomOutLeft;animation-name:zoomOutLeft}@-webkit-keyframes zoomOutRight{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(-42px,0,0);transform:scale3d(.475,.475,.475) translate3d(-42px,0,0)}100%{opacity:0;-webkit-transform:scale(.1) translate3d(2000px,0,0);transform:scale(.1) translate3d(2000px,0,0);-webkit-transform-origin:right center;transform-origin:right center}}@keyframes zoomOutRight{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(-42px,0,0);-ms-transform:scale3d(.475,.475,.475) translate3d(-42px,0,0);transform:scale3d(.475,.475,.475) translate3d(-42px,0,0)}100%{opacity:0;-webkit-transform:scale(.1) translate3d(2000px,0,0);-ms-transform:scale(.1) translate3d(2000px,0,0);transform:scale(.1) translate3d(2000px,0,0);-webkit-transform-origin:right center;-ms-transform-origin:right center;transform-origin:right center}}.zoomOutRight{-webkit-animation-name:zoomOutRight;animation-name:zoomOutRight}@-webkit-keyframes zoomOutUp{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,60px,0);transform:scale3d(.475,.475,.475) translate3d(0,60px,0);-webkit-animation-timing-function:cubic-bezier(0.55,.055,.675,.19);animation-timing-function:cubic-bezier(0.55,.055,.675,.19)}100%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,-2000px,0);transform:scale3d(.1,.1,.1) translate3d(0,-2000px,0);-webkit-transform-origin:center bottom;transform-origin:center bottom;-webkit-animation-timing-function:cubic-bezier(0.175,.885,.32,1);animation-timing-function:cubic-bezier(0.175,.885,.32,1)}}@keyframes zoomOutUp{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,60px,0);-ms-transform:scale3d(.475,.475,.475) translate3d(0,60px,0);transform:scale3d(.475,.475,.475) translate3d(0,60px,0);-webkit-animation-timing-function:cubic-bezier(0.55,.055,.675,.19);animation-timing-function:cubic-bezier(0.55,.055,.675,.19)}100%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,-2000px,0);-ms-transform:scale3d(.1,.1,.1) translate3d(0,-2000px,0);transform:scale3d(.1,.1,.1) translate3d(0,-2000px,0);-webkit-transform-origin:center bottom;-ms-transform-origin:center bottom;transform-origin:center bottom;-webkit-animation-timing-function:cubic-bezier(0.175,.885,.32,1);animation-timing-function:cubic-bezier(0.175,.885,.32,1)}}.zoomOutUp{-webkit-animation-name:zoomOutUp;animation-name:zoomOutUp} + +.animated.-half-second { + animation-duration: 0.5s; +} + +.slide-in-left.ng-hide-remove { + -webkit-animation: fadeInLeft 0.6s; + animation: fadeInLeft 0.6s; +} + +.slide-in-left.ng-hide-add { + -webkit-animation: fadeOutLeft 0.6s; + animation: fadeOutLeft 0.6s; + display: block !important; +} + +.slide-in-right.ng-hide-remove { + -webkit-animation: fadeInRight 0.6s; + animation: fadeInRight 0.6s; +} + +.slide-in-right.ng-hide-add { + -webkit-animation: fadeOutRight 0.6s; + animation: fadeOutRight 0.6s; + display: block !important; +} + +.slide-in-up.ng-hide-remove { + -webkit-animation: fadeInUp 0.6s; + animation: fadeInUp 0.6s; +} + +.slide-in-up.ng-hide-add { + -webkit-animation: fadeOutDown 0.6s; + animation: fadeOutDown 0.6s; + display: block !important; +} + + +// TREE ANIMATION + +.tree-node-delete-leave { + -webkit-animation: leave 600ms cubic-bezier(0.445, 0.050, 0.550, 0.950); + -moz-animation: leave 600ms cubic-bezier(0.445, 0.050, 0.550, 0.950); + -o-animation: leave 600ms cubic-bezier(0.445, 0.050, 0.550, 0.950); + animation: leave 600ms cubic-bezier(0.445, 0.050, 0.550, 0.950); + display: block; + position: relative; +} + +@-webkit-keyframes leave { + to { + opacity: 0; + height: 0px; + bottom: -70px; + } + 25% { + bottom: 15px; + } + from { + opacity: 1; + height: 30px; + bottom: 0px; + } +} +@-moz-keyframes leave { + to { + opacity: 0; + height: 0px; + bottom: -70px; + } + 25% { + bottom: 15px; + } + from { + opacity: 1; + height: 30px; + bottom: 0px; + } +} +@-ms-keyframes leave { + to { + opacity: 0; + height: 0px; + bottom: -70px; + } + 25% { + bottom: 15px; + } + from { + opacity: 1; + height: 30px; + bottom: 0px; + } +} +@-o-keyframes leave { + to { + opacity: 0; + height: 0px; + bottom: -70px; + } + 25% { + bottom: 15px; + } + from { + opacity: 1; + height: 30px; + bottom: 0px; + } +} +@keyframes leave { + to { + opacity: 0; + height: 0px; + bottom: -70px; + } + 25% { + bottom: 15px; + } + from { + opacity: 1; + height: 30px; + bottom: 0px; + } +} + +.tree-node-delete-leave * { + color:@red !important; +} + + +.tree-node-slide-up +{ + opacity:1; + top: 0px; + -webkit-transition: 700ms cubic-bezier(0.000, 0.000, 0.580, 1.000) all; + -moz-transition: 700ms cubic-bezier(0.000, 0.000, 0.580, 1.000) all; + -ms-transition: 700ms cubic-bezier(0.000, 0.000, 0.580, 1.000) all; + -o-transition: 700ms cubic-bezier(0.000, 0.000, 0.580, 1.000) all; + transition: 700ms cubic-bezier(0.000, 0.000, 0.580, 1.000) all; +} +.tree-node-slide-up * { + font-size:100%; + -webkit-transition:font-size 700ms; + -moz-transition:font-size 700ms; + -ms-transition:font-size 700ms; + -o-transition:font-size 700ms; + transition:font-size 700ms; +} +.tree-node-slide-up.tree-node-slide-up-hide-active { + opacity: 0; + top: -100px; +} +.tree-node-slide-up.tree-node-slide-up-hide-active * { + font-size:120%; +} + +.tree-fade-out-hide , +.tree-fade-out-show, +.tree-fade-out-hide div:not(.tree-node-slide-up-hide-active), +.tree-fade-out-show div:not(.tree-node-slide-up-hide-active) { + -webkit-transition: 700ms cubic-bezier(0.075, 0.820, 0.165, 1.000) all; + -moz-transition: 700ms cubic-bezier(0.075, 0.820, 0.165, 1.000) all; + -ms-transition: 700ms cubic-bezier(0.075, 0.820, 0.165, 1.000) all; + -o-transition: 700ms cubic-bezier(0.075, 0.820, 0.165, 1.000) all; + transition: 700ms cubic-bezier(0.075, 0.820, 0.165, 1.000) all; +} +.tree-fade-out-show.tree-fade-out-show-active div:not(.tree-node-slide-up-hide-active){ + opacity: 1; +} +.tree-fade-out-hide.tree-fade-out-hide-active div:not(.tree-node-slide-up-hide-active){ + opacity: 0; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/grid.less b/src/Umbraco.Web.UI.Client/src/less/application/grid.less similarity index 58% rename from src/Umbraco.Web.UI.Client/src/less/grid.less rename to src/Umbraco.Web.UI.Client/src/less/application/grid.less index 74c2d17f7e..a169e2d40e 100644 --- a/src/Umbraco.Web.UI.Client/src/less/grid.less +++ b/src/Umbraco.Web.UI.Client/src/less/application/grid.less @@ -1,187 +1,273 @@ -// Grid -// ------------------------- - -/* CONTAINS BASIC APPLICATION LAYOUT, POSITIONING AND AREA DIMENSIONS */ - -html, body { - height: 100%; - overflow: hidden; -} - -body { - margin: 0; - padding: 0; - height: 100%; - width: 100%; - - font-family: @baseFontFamily; - font-size: @baseFontSize; - line-height: @baseLineHeight; - color: @textColor; - background-color: @bodyBackground; -} - - -.padded { - padding: 20px -} - - -#layout { - position: relative; - height: 100%; - padding: 0; - z-index: 1; -} - -#mainwrapper { - height: 100%; - width: 100%; - margin: 0; -} - -#contentwrapper, #contentcolumn { - position: absolute; - top: 0px; bottom: 0px; right: 0px; left: 80px; - z-index: 10; - margin: 0 -} - -#contentcolumn { - left: 0px; -} - -#contentcolumn iframe#right { - display: block; - position: relative; - height: 100%; - width: 100%; - border: none; -} - -#leftcolumn { - height: 100%; - z-index: 20; - width: 80px; - float: left; - position: absolute; -} - -#applications { - z-index: 1000; - height: 100%; - left: 0px; - top: 0; - bottom: 0; - position: absolute; - text-align: center -} - -#applications-tray { - z-index: 900; - left: 80px; - top: 0; - bottom: 0; - position: absolute; - height: 100%; - text-align: center; -} - -#search-form { - display: block; - margin: 0px; - z-index: 100; - position: absolute; - top: 0; - left: 0; - right: 0; -} - -#search-form form{ - margin-top: 10px; -} - -#navigation { - left: 80px; - top: 0; - bottom: 0; - position: absolute; - z-index: 100; - background: @white; - height: 100%; - -} - -.navigation-inner-container{ - position: absolute; - top: 0px; - bottom: 0px; - left: 0px; - right: 0px; - padding-top: 100px; - border-right: 1px solid @grayLight; -} - -#dialog { - min-width: 500px; - left: 100%; - top: 0; - position: absolute; - z-index: 50; - display: inline-block; -} - -#tree { - padding: 0px; - z-index: 100 !important; - overflow: auto; -} - -#tree .umb-tree { - padding: 0px 0px 20px 0px; -} - -#search-results { - z-index: 200; -} - -#contextMenu { - z-index: 50; - position: absolute; - top: 0px; - left: 100%; - min-width: 250px; -} - -#speechbubble { - z-index: 1000; - position: absolute; - bottom: 100px; - left: 0; - right: 0; - border-bottom: none; - margin: auto; - padding: 0px; - border: none; - background: none; - border-radius: 0; -} - -@media (min-width: 1101px) { - #contentwrapper {left: 440px;} - #speechbubble {left: 360px;} -} - -//empty section modification -.emptySection #contentwrapper {left: 80px;} -.emptySection #speechbubble {left: 0;} -.emptySection #navigation {display: none} - - -.login-only #speechbubble { - z-index: 10000; - left: 0 !important; -} -.login-only #speechbubble ul { - padding-left:20px -} \ No newline at end of file +// Grid +// ------------------------- + +/* CONTAINS BASIC APPLICATION LAYOUT, POSITIONING AND AREA DIMENSIONS */ + +html, body { + height: 100%; + overflow: hidden; +} + +body { + margin: 0; + padding: 0; + height: 100%; + width: 100%; + + font-family: @baseFontFamily; + font-size: @baseFontSize; + line-height: @baseLineHeight; + color: @textColor; + background-color: @bodyBackground; +} + + +.padded { + padding: 20px +} + + +#layout { + position: relative; + height: 100%; + padding: 0; + z-index: 1; +} + +#mainwrapper { + height: 100%; + width: 100%; + margin: 0; +} + +#contentwrapper, #contentcolumn { + position: absolute; + top: 0px; bottom: 0px; right: 0px; left: 80px; + z-index: 10; + margin: 0 +} + +#umb-notifications-wrapper { + left: 80px; +} + +#contentcolumn { + left: 0px; +} + +#contentcolumn iframe#right { + display: block; + position: relative; + height: 100%; + width: 100%; + border: none; +} + +#leftcolumn { + height: 100%; + z-index: 20; + width: 80px; + float: left; + position: absolute; +} + +#applications { + z-index: 1000; + height: 100%; + left: 0px; + top: 0; + bottom: 0; + position: absolute; + text-align: center +} + +#applications-tray { + z-index: 900; + left: 80px; + top: 0; + bottom: 0; + position: absolute; + height: 100%; + text-align: center; +} + +#search-form { + display: block; + margin: 0px; + z-index: 100; + position: absolute; + top: 0; + left: 0; + right: 0; +} + +#search-form form{ + margin-top: 17px; +} + +#navigation { + left: 80px; + top: 0; + bottom: 0; + position: absolute; + z-index: 100; + background: @white; + height: 100%; + +} + +.navigation-inner-container{ + position: absolute; + top: 0px; + bottom: 0px; + left: 0px; + right: 0px; + padding-top: 100px; + border-right: 1px solid @grayLight; + z-index: 100; +} + +#dialog { + min-width: 500px; + left: 100%; + top: 0; + position: absolute; + z-index: 50; + display: inline-block; +} + +#tree { + padding: 0px; + z-index: 100 !important; + overflow: auto; +} + +#tree .umb-tree { + padding: 0px 0px 20px 0px; +} + +#search-results { + z-index: 200; +} + +#contextMenu { + z-index: 50; + position: absolute; + top: 0px; + left: 100%; + min-width: 250px; +} + +#speechbubble { + z-index: 1060; + position: absolute; + bottom: 100px; + left: 0; + right: 0; + border-bottom: none; + margin: auto; + padding: 0px; + border: none; + background: none; + border-radius: 0; +} + +.ui-resizable-e { + cursor: e-resize; + width: 4px; + right: -5px; + top: 0; + bottom: 0; + background-color: @grayLighter; + border: solid 1px @grayLight; + position:absolute; + z-index:9999 !important; + +} + +@media (min-width: 1101px) { + #contentwrapper, #umb-notifications-wrapper {left: 440px;} + #speechbubble {left: 360px;} +} + +//empty section modification +.emptySection #contentwrapper, .emptySection #umb-notifications-wrapper {left: 80px;} +.emptySection #speechbubble {left: 0;} +.emptySection #navigation {display: none} + +.login-only #speechbubble { + z-index: 10000; + left: 0 !important; +} +.login-only #speechbubble ul { + padding-left:20px +} + + + +@media (max-width: 767px) { + + // make leftcolumn smaller on tablets + #leftcolumn { + width: 60px; + } + #applications ul.sections { + width: 60px; + } + ul.sections.sections-tray { + width: 60px; + } + #applications-tray { + left: 60px; + } + #navigation { + left: 60px; + } + #contentwrapper, #contentcolumn, #umb-notifications-wrapper { + left: 30px; + } + #umbracoMainPageBody .umb-modal-left.fade.in { + margin-left: 60px; + } +} + + + +@media (max-width: 500px) { + + // make leftcolumn smaller on mobiles + #leftcolumn { + width: 40px; + } + #applications ul.sections { + width: 40px; + } + ul.sections li [class^="icon-"]:before { + font-size: 25px!important; + } + #applications ul.sections li.avatar a img { + width: 25px; + } + ul.sections a span { + display:none !important; + } + #applications ul.sections-tray { + width: 40px; + } + ul.sections.sections-tray { + width: 40px; + } + #applications-tray { + left: 40px; + } + #navigation { + left: 40px; + } + #contentwrapper, #contentcolumn, #umb-notifications-wrapper { + left: 20px; + } + #umbracoMainPageBody .umb-modal-left.fade.in { + margin-left: 40px; + width: 85%!important; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/less/application/shadows.less b/src/Umbraco.Web.UI.Client/src/less/application/shadows.less new file mode 100644 index 0000000000..3df8ca5758 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/application/shadows.less @@ -0,0 +1,15 @@ +.shadow-depth-1{ + box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24); +} +.shadow-depth-2{ + box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23); +} +.shadow-depth-3{ + box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23); +} +.shadow-depth-4{ + box-shadow: 0 14px 28px rgba(0,0,0,0.25), 0 10px 10px rgba(0,0,0,0.22); +} +.shadow-depth-5{ + box-shadow: 0 19px 38px rgba(0,0,0,0.30), 0 15px 12px rgba(0,0,0,0.22); +} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/less/belle.less b/src/Umbraco.Web.UI.Client/src/less/belle.less index 79b16605ad..191d75dd4f 100644 --- a/src/Umbraco.Web.UI.Client/src/less/belle.less +++ b/src/Umbraco.Web.UI.Client/src/less/belle.less @@ -1,11 +1,3 @@ -/*! - * Belle v1.0.0 - * - * Copyright 2013 Umbraco - * Licensed under the Apache License v2.0 - * http://www.apache.org/licenses/LICENSE-2.0 - * - */ // Core variables and mixins @import "fonts.less"; // Loading fonts @@ -43,7 +35,6 @@ @import "../../lib/bootstrap/less/pagination.less"; @import "../../lib/bootstrap/less/pager.less"; - // Components: Popovers @import "../../lib/bootstrap/less/modals.less"; @import "../../lib/bootstrap/less/tooltip.less"; @@ -59,12 +50,17 @@ @import "../../lib/bootstrap/less/carousel.less"; @import "../../lib/bootstrap/less/hero-unit.less"; + // Utility classes @import "../../lib/bootstrap/less/utilities.less"; // Has to be last to override when necessary + +// Application wide styles (refactor is WIP) +@import "application/grid.less"; +@import "application/shadows.less"; +@import "application/animations.less"; + // Belle styles -@import "grid.less"; -@import "login.less"; @import "buttons.less"; @import "forms.less"; @import "modals.less"; @@ -76,12 +72,55 @@ @import "listview.less"; @import "gridview.less"; @import "footer.less"; -@import "animations.less"; @import "dragdrop.less"; -@import "dashboards.less"; +@import "dashboards.less"; + +@import "forms/umb-validation-label.less"; + +// Umbraco Components +@import "components/editor.less"; +@import "components/overlays.less"; +@import "components/card.less"; +@import "components/umb-sub-views.less"; +@import "components/umb-editor-navigation.less"; +@import "components/umb-editor-sub-views.less"; +@import "components/umb-editor-toolbar.less"; +@import "components/editor/subheader/umb-editor-sub-header.less"; +@import "components/umb-grid-selector.less"; +@import "components/umb-child-selector.less"; +@import "components/umb-group-builder.less"; +@import "components/umb-list-view.less"; +@import "components/umb-table.less"; +@import "components/umb-confirm-action.less"; +@import "components/umb-keyboard-shortcuts-overview.less"; +@import "components/umb-checkbox-list.less"; +@import "components/umb-locked-field.less"; +@import "components/umb-tabs.less"; +@import "components/umb-load-indicator.less"; +@import "components/umb-breadcrumbs.less"; +@import "components/umb-media-grid.less"; +@import "components/umb-folder-grid.less"; +@import "components/umb-content-grid.less"; +@import "components/umb-layout-selector.less"; +@import "components/tooltip/umb-tooltip.less"; +@import "components/tooltip/umb-tooltip-list.less"; +@import "components/overlays/umb-overlay-backdrop.less"; +@import "components/umb-grid.less"; + +@import "components/buttons/umb-button.less"; +@import "components/buttons/umb-button-group.less"; + +@import "components/notifications/umb-notifications.less"; +@import "components/umb-file-dropzone.less"; + +//page specific styles +@import "pages/document-type-editor.less"; +@import "pages/login.less"; + //used for property editors @import "property-editors.less"; + @import "typeahead.less"; @import "hacks.less"; diff --git a/src/Umbraco.Web.UI.Client/src/less/canvasdesigner.less b/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less similarity index 98% rename from src/Umbraco.Web.UI.Client/src/less/canvasdesigner.less rename to src/Umbraco.Web.UI.Client/src/less/canvas-designer.less index b28522db1c..52c4052975 100644 --- a/src/Umbraco.Web.UI.Client/src/less/canvasdesigner.less +++ b/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less @@ -2,8 +2,8 @@ /******* font-face *******/ @font-face { - src: url('/Umbraco/assets/fonts/helveticons/helveticons.eot') !important; - src: url('/Umbraco/assets/fonts/helveticons/helveticons.eot?#iefix') format('embedded-opentype'), url('/Umbraco/assets/fonts/helveticons/helveticons.ttf') format('truetype'), url('/Umbraco/assets/fonts/helveticons/helveticons.svg#icomoon') format('svg') !important; + src: url('assets/fonts/helveticons/helveticons.eot') !important; + src: url('assets/fonts/helveticons/helveticons.eot?#iefix') format('embedded-opentype'), url('assets/fonts/helveticons/helveticons.ttf') format('truetype'), url('assets/fonts/helveticons/helveticons.svg#icomoon') format('svg') !important; } /****************************/ diff --git a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button-group.less b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button-group.less new file mode 100644 index 0000000000..0b063d094d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button-group.less @@ -0,0 +1,10 @@ +.umb-button-group__toggle { + padding-left: 8px; + padding-right: 8px; + margin-left: -1px; +} + +.umb-button-group__sub-buttons.-align-right { + right: 0; + left: auto; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button.less b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button.less new file mode 100644 index 0000000000..d08864f5f7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button.less @@ -0,0 +1,94 @@ +.umb-button { + position: relative; + overflow: hidden; +} + + .umb-button__button:focus { + outline: none; +} + +.umb-button__content { + opacity: 1; + transition: opacity 0.25s ease; +} + +.umb-button__content.-hidden { + opacity: 0; +} + +.umb-button__progress { + position: absolute; + left: 50%; + top: 50%; + width: 14px; + height: 14px; + margin-left: -9px; + margin-top: -9px; + z-index: 100; + border-radius: 40px; + border: 2px solid @btnBorder; + border-left-color: @green; + opacity: 1; + animation: rotating 0.4s linear infinite; + transition: opacity 0.25s ease; +} + +.umb-button__progress.-hidden { + opacity: 0; + z-index: 0; +} + +.umb-button__progress.-white { + border-color: rgba(255, 255, 255, 0.4); + border-left-color: #ffffff; +} + +.umb-button__success, +.umb-button__error { + position: absolute; + top: 50%; + left: 50%; + z-index: 10; + transform: translate(-50%, -50%); + opacity: 1; + font-size: 20px; + color: @green; + transition: opacity 0.25s ease; +} + +.umb-button__success { + color: @green; +} + +.umb-button__error { + color: @red; +} + +.umb-button__success.-hidden, +.umb-button__error.-hidden { + opacity: 0; + z-index: 0; +} + +.umb-button__success.-white, +.umb-button__error.-white { + color: #ffffff; +} + +.umb-button__overlay { + position: absolute; + width: 100%; + height: 100%; + z-index: 10; + background: #ffffff; + opacity: 0; +} + +@keyframes rotating { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/card.less b/src/Umbraco.Web.UI.Client/src/less/components/card.less new file mode 100644 index 0000000000..4f27dafe37 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/card.less @@ -0,0 +1,168 @@ +/* + Library of card related compoents, like the right-hand icon list on the grid "cards" +*/ + +.umb-card{ + position: relative; + padding: 5px 10px 5px 10px; + background: white; + + .title{padding: 12px; color: @gray; border-bottom: 1px solid @grayLight; font-weight: 400; font-size: 16px; text-transform: none; margin: 0 -10px 10px -10px;} + +} + +.umb-card-thumb{ + text-align: center; + + i{ + text-align: center; + font-size: 20px; + line-height: 40px; + color: @blue; + display: block; + padding-top: 5px + } +} + +.umb-card-content{ + .item-title{color: @blackLight; font-weight: 400; border: none; font-size: 16px; text-transform: none; margin-bottom: 3px;} + p{color: @gray; margin-bottom: 1px;} +} + +.umb-card-actions{ + padding-top: 10px; + border-top: @grayLighter 1px solid; + clear: both; +} + +.umb-card-icons{ + text-align: center; + vertical-align: center; + display: block; + list-style: none; + margin: 0; + padding: 0; +} + +.umb-card-icons.vertical{ + position: absolute; + top: 7px; + right: 7px; + text-align: right; + width: 1px; +} + +.umb-card-icons li{ + display: inline-block; + margin: 0 2px 0 2px; +} + +.umb-card-icons.vertical li{ + float: right; + display: block; + margin-bottom: 3px; +} + +//card iocn list +.umb-card-list{ + display: block; + padding: 0; + margin: 0; + } + +.umb-card-list li{ + border-bottom: @grayLighter 1px solid; + padding-bottom: 3px; + display: block; +} + + + + +//Card icon grid for picking items off a card +.umb-card-grid{ + display: block; + padding: 0; + margin: 0; + list-style: none; + } + +// clearfix floats + .umb-card-grid:after { + content:""; + display:table; + clear:both; +} + +.umb-card-grid li{ + float: left; + width: 90px; + height: 80px; + margin: 5px; + padding: 5px; + overflow: hidden; + font-size: 11px; + text-align: center; + } + +.umb-card-grid li:hover, .umb-card-grid li:hover *{ + background: @blue; + color: white; + } + +.umb-card-grid a{ + color: #222; + text-decoration: none; + } + +.umb-card-grid i{ + font-size: 30px; + line-height: 50px; + display: block; + color: @grayDark; + } + + + + + +//Round icon-like button - this should be somewhere else +.umb-btn-round{ + padding: 4px 6px 4px 6px; + display: inline-block; + cursor: pointer; + border-radius: 200px; + background: rgba(255,255,255, 1); + border:1px solid rgb(182, 182, 182); + margin: 2px; +} + +.umb-btn-round:hover, .umb-btn-round:hover *{ + background: @blue !important; + color: white !important; + border-color: @blue !important; + text-decoration:none; + } + + .umb-btn-round a:hover { + text-decoration:none; + color: white !important; + } + + .umb-btn-round i { + font-size:16px !important; + color: #grayLight; + display:block; + } + + .umb-btn-round.alert:hover, .umb-btn-round.alert:hover *{ + background: @red !important; + color: white !important; + border-color: @red !important; + text-decoration:none; + } + +.umb-btn-round.no-border{ + border: none !important; + background: none !important; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/editor.less b/src/Umbraco.Web.UI.Client/src/less/components/editor.less new file mode 100644 index 0000000000..b90271b5e5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/editor.less @@ -0,0 +1,59 @@ +/* + contains styling for all main editor directives +*/ + +.umb-editor-wrapper{ + background: white; + position: absolute; + top: 0px; bottom: 0px; left: 0px; right: 0px; +} + + +.umb-editor-header{ + background: @grayLighter; + border-bottom: 1px solid @grayLight; + position: absolute; + height: 99px; + top: 0px; + right: 0px; + left: 0px; +} + + +.umb-editor-container{ + top: 101px; + left: 0px; + right: 0px; + bottom: 60px; + position: absolute; + clear: both; + overflow: auto; +} + +.umb-editor-container.-stop-scrolling { + overflow: hidden; +} + +.umb-editor-drawer{ + margin: 0; + padding: 10px 20px; + z-index: 999; + position: absolute; + bottom: 0px; + left: 0px; + right: 0px; + height: 31px; + + background: @grayLighter; + border-top: 1px solid @grayLight; +} + + +.umb-editor-actions{ + list-style: none; + margin: 0; padding: 0; +} + +.umb-editor-actions li{ + display: inline-block; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/editor/subheader/umb-editor-sub-header.less b/src/Umbraco.Web.UI.Client/src/less/components/editor/subheader/umb-editor-sub-header.less new file mode 100644 index 0000000000..0d68eeb0dd --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/editor/subheader/umb-editor-sub-header.less @@ -0,0 +1,57 @@ +.umb-editor-sub-header { + padding: 15px 0; + margin-bottom: 30px; + background: #ffffff; + display: flex; + justify-content: space-between; + margin-top: -30px; + position: relative; +} + +.umb-editor-sub-header.-umb-sticky-bar { + box-shadow: + 0 5px 0 rgba(0, 0, 0, 0.08), + 0 1px 0 rgba(0, 0, 0, 0.16); + transition: box-shadow 1s; +} + +.umb-group-builder__property-preview .umb-editor-sub-header { + display: none; +} + +.umb-editor-sub-header__content-left { + margin-right: auto; +} + +.umb-editor-sub-header__content-right { + margin-left: auto; +} + +.umb-editor-sub-header__content-left, +.umb-editor-sub-header__content-right { + display: flex; + align-items: stretch; +} + +.umb-editor-sub-header__section { + border-left: 1px solid @grayLight; + display: flex; + align-items: center; + padding-left: 20px; + padding-right: 20px; +} + +.umb-editor-sub-header__content-left .umb-editor-sub-header__section:first-child { + border-left: none; + padding-left: 0; +} + +.umb-editor-sub-header__content-right .umb-editor-sub-header__section { + border-left: none; + border-right: 1px solid @grayLight; +} + +.umb-editor-sub-header__content-right .umb-editor-sub-header__section:last-child { + border-right: none; + padding-right: 0; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/notifications/umb-notifications.less b/src/Umbraco.Web.UI.Client/src/less/components/notifications/umb-notifications.less new file mode 100644 index 0000000000..1bd4ecf1df --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/notifications/umb-notifications.less @@ -0,0 +1,35 @@ +.umb-notifications { + z-index: 1000; + position: absolute; + bottom: 52px; + left: 0; + right: 0; + border-bottom: none; + margin: auto; + padding: 0px; + border: none; + background: none; + border-radius: 0; +} + +.umb-notifications__notifications { + list-style: none; + margin: 0; + position: relative; +} + + +.umb-notifications__notification { + padding: 5px 20px; + text-shadow: none; + font-size: 12px; + border: none; + border-radius: 0; + position: relative; + margin-bottom: 0; +} + +.umb-notifications__notification.-extra-padding { + padding-top: 20px; + padding-bottom: 20px; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/overlays.less b/src/Umbraco.Web.UI.Client/src/less/components/overlays.less new file mode 100644 index 0000000000..49153ea6a8 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/overlays.less @@ -0,0 +1,197 @@ +.umb-overlay { + position: fixed; + overflow: hidden; + background: white; + z-index: 996660; + animation: fadeIn 0.2s; + box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23); +} + +.umb-overlay .umb-overlay-header { + background: @grayLighter; + border-bottom: 1px solid @grayLight; + padding: 10px; + margin-top: 0; +} + + +.umb-overlay .umb-overlay__title { + font-size: @fontSizeLarge; + color: @black; + font-weight: bold; + + margin: 7px 0; +} + +.umb-overlay .umb-overlay__subtitle { + font-size: @fontSizeSmall; + color: @gray; +} + +.umb-overlay .umb-overlay-container { + top: 50px; + left: 7px; + right: 7px; + bottom: 7px; + position: absolute; + overflow-y: auto; + overflow-x: hidden; +} + +.umb-overlay .umb-overlay-drawer { + position: absolute; + right: 0; + bottom: 0; + left: 0; + height: 31px; + padding: 10px; + margin: 0; + background: #ffffff; +} + +.umb-overlay .umb-overlay-drawer .umb-overlay-drawer-content { + display: flex; + justify-content: flex-end; +} + +.umb-overlay .umb-overlay-drawer .umb-overlay-drawer-content .dropdown-menu { + right: 0; + left: auto; +} + +/* ---------- OVERLAY CENTER ---------- */ +.umb-overlay.umb-overlay-center { + position: absolute; + width: 600px; + height: 500px; + top: 10px; + left: 50%; + transform: translate(-50%, 0); +} + +.umb-overlay.umb-overlay-center .umb-overlay-header { + text-align: center; +} + +.umb-overlay.umb-overlay-center .umb-overlay-container { + top: 68px; + padding: 20px; +} + +/* ---------- OVERLAY TARGET ---------- */ +.umb-overlay.umb-overlay-target { + width: 400px; + height: 400px; + box-sizing: border-box; +} + +.umb-overlay.umb-overlay-target .umb-overlay-header { + text-align: center; +} + +.umb-overlay.umb-overlay-target .umb-overlay-container { + top: 68px; + bottom: 58px; +} + +/* ---------- OVERLAY RIGHT ---------- */ +.umb-overlay.umb-overlay-right { + width: 500px; + top: 0; + right: 0; + bottom: 0; + border: none; + box-shadow: 0 0 20px rgba(0,0,0,0.19), 0 0 6px rgba(0,0,0,0.23); +} + +.umb-overlay.umb-overlay-right .umb-overlay-header { + height: 100px; + padding: 20px; + box-sizing: border-box; +} + +.umb-overlay.umb-overlay-right .umb-overlay-container { + top: 100px; + left: 0; + right: 0; + bottom: 51px; + padding: 20px; +} + +/* ---------- OVERLAY LEFT ---------- */ +.umb-overlay.umb-overlay-left { + width: 500px; + top: 0; + left: 0; + bottom: 0; + border: none; + box-shadow: 0 0 20px rgba(0,0,0,0.19), 0 0 6px rgba(0,0,0,0.23); + margin-left: 80px; +} + +.umb-overlay.umb-overlay-left .umb-overlay-header { + height: 100px; + padding: 20px; + box-sizing: border-box; +} + +.umb-overlay.umb-overlay-left .umb-overlay-container { + top: 100px; + left: 0; + right: 0; + bottom: 51px; + padding: 20px; +} + +@media (max-width: 767px) { + .umb-overlay.umb-overlay-left { + margin-left: 60px; + } +} + +/* ---------- OVERLAY ITEM DETAILS ---------- */ +.umb-overlay__item-details { + position: absolute; + left: 0; + bottom: 51px; + width: 100%; + padding: 20px; + box-sizing: border-box; + border-bottom: 1px solid @grayLight; + background: @grayLighter; +} + +.umb-overlay__item-details-title-wrapper { + display: flex; + flex-direction: row; + align-items: center; +} + +.umb-overlay__item-details-icon { + font-size: 16px; + margin-right: 10px; + vertical-align: middle; + color: #999; +} + +.umb-overlay__item-details-title { + margin-top: 0; + margin-bottom: 0; +} + +.umb-overlay__item-details-description { + margin-top: 10px; + font-size: 12px; +} + +/*ensures dialogs doesnt have side-by-side labels*/ +.umb-overlay .control-label { + width: 100%; + display: block; + box-sizing: border-box; + margin-bottom: 10px; +} + +.umb-overlay .controls-row { + margin-left: 0 !important; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/overlays/umb-overlay-backdrop.less b/src/Umbraco.Web.UI.Client/src/less/components/overlays/umb-overlay-backdrop.less new file mode 100644 index 0000000000..a05e9c8f95 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/overlays/umb-overlay-backdrop.less @@ -0,0 +1,10 @@ +.umb-overlay-backdrop { + position: fixed; + width: 100%; + height: 100%; + background-color: rgba(255, 255, 255, 0.50); + z-index: 2000; + top: 0; + left: 0; + animation: fadeIn 0.2s; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/tooltip/umb-tooltip-list.less b/src/Umbraco.Web.UI.Client/src/less/components/tooltip/umb-tooltip-list.less new file mode 100644 index 0000000000..4f704be511 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/tooltip/umb-tooltip-list.less @@ -0,0 +1,19 @@ +.umb-tooltip-list { + list-style: none; + margin-left: 0; + margin-bottom: 0; + padding: 10px; +} + +.umb-tooltip-list__item { + margin-bottom: 5px; +} + +.umb-tooltip-list__item:last-child { + margin-bottom: 0; +} + +.umb-tooltip-list__item-label { + font-weight: bold; + margin-bottom: -3px; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/tooltip/umb-tooltip.less b/src/Umbraco.Web.UI.Client/src/less/components/tooltip/umb-tooltip.less new file mode 100644 index 0000000000..b058cd1b4e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/tooltip/umb-tooltip.less @@ -0,0 +1,13 @@ +.umb-tooltip { + position: fixed; + display: flex; + background: #ffffff; + padding: 10px; + z-index: 1000; + max-width: 200px; + font-size: 12px; + animation-duration: 0.1s; + animation-timing-function: ease-in; + animation: fadeIn; + margin-top: 15px; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-breadcrumbs.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-breadcrumbs.less new file mode 100644 index 0000000000..5854599722 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-breadcrumbs.less @@ -0,0 +1,31 @@ +.umb-breadcrumbs { + list-style: none; + margin-bottom: 0; + margin-left: 0; +} + +.umb-breadcrumbs__ancestor { + display: inline-block; +} + +.umb-breadcrumbs__ancestor-link, +.umb-breadcrumbs__ancestor-text { + font-size: 11px; + color: #555; +} + +.umb-breadcrumbs__ancestor-link { + text-decoration: underline; +} + +.umb-breadcrumbs__ancestor-link:hover { + color: #000; +} + +.umb-breadcrumbs__seperator { + position: relative; + top: 1px; + margin-left: 5px; + margin-right: 5px; + color: #ccc; +} 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 new file mode 100644 index 0000000000..6098eb07e7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-checkbox-list.less @@ -0,0 +1,46 @@ +.umb-checkbox-list { + list-style: none; + margin-left: 0; +} + +.umb-checkbox-list__item { + display: flex; + align-items: center; + margin-bottom: 1px; + //border-bottom: 1px solid @grayLight; +} + +.umb-checkbox-list__item.-selected { + background: @blue; + color: white; +} + +.umb-checkbox-list__item-checkbox { + display: flex; + justify-content: center; + align-items: center; + background: @grayLighter; + flex: 0 0 30px; + height: 30px; + margin-right: 10px; +} + +.umb-checkbox-list__item-checkbox.-selected { + background: @blue; +} + +.umb-checkbox-list__item-icon { + margin-right: 5px; +} + +.umb-checkbox-list__item-text { + font-size: 14px; +} + +.umb-checkbox-list__no-data { + text-align: center; + padding-top: 50px; + color: #ccc; + font-size: 16px; + line-height: 1.8em; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-child-selector.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-child-selector.less new file mode 100644 index 0000000000..f52c7eb47f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-child-selector.less @@ -0,0 +1,61 @@ +.umb-child-selector__child { + background: @grayLighter; + padding: 5px 15px; + margin-bottom: 5px; + min-width: 300px; + display: flex; + border-radius: 5px; + animation: fadeIn 0.5s; +} + +.umb-child-selector__child.-parent { + background: #f1f1f1; + padding-top: 10px; + padding-bottom: 10px; +} + +.umb-child-selector__child.-placeholder { + border: 1px dashed @grayLight; + background: none; + border-radius: 5px; + cursor: pointer; + text-align: center; + justify-content: center; +} + +.umb-child-selector__children-container { + margin-left: 30px; +} + +.umb-child-selector__child-description { + flex: 1; +} + +.umb-child-selector__child-icon-holder { + margin-right: 5px; + width: 20px; + text-align: center; + display: inline-block; +} + +.umb-child-selector__child-icon { + font-size: 16px; + vertical-align: middle; +} + +.umb-child-selector__child-name { + font-size: 13px; +} + +.umb-child-selector__child-name.-blue { + color: @blue; +} + +.umb-child-selector__child-actions { + flex: 0 0 50px; + text-align: right; +} + +.umb-child-selector__child-remove { + cursor: pointer; +} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-confirm-action.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-confirm-action.less new file mode 100644 index 0000000000..1c31b97baa --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-confirm-action.less @@ -0,0 +1,130 @@ +// OVERLAY +.umb_confirm-action__overlay { + position: absolute; + z-index: 999999; + display: flex; +} + +// positions +.umb_confirm-action__overlay.-top { + top: -50px; + right: auto; + bottom: auto; + left: 0; + animation: fadeInUp 0.2s; + flex-direction: column; + + .umb_confirm-action__overlay-action { + margin-bottom: 5px; + } + + .umb_confirm-action__overlay-action.-confirm { + order: 1; + } + + .umb_confirm-action__overlay-action.-cancel { + order: 2; + } + +} + +.umb_confirm-action__overlay.-right { + top: 0; + right: -50px; + bottom: auto; + left: auto; + animation: fadeInLeft 0.2s; + flex-direction: row; + + .umb_confirm-action__overlay-action { + margin-left: 5px; + } + + .umb_confirm-action__overlay-action.-confirm { + order: 2; + } + + .umb_confirm-action__overlay-action.-cancel { + order: 1; + } +} + +.umb_confirm-action__overlay.-bottom { + top: auto; + right: auto; + bottom: -50px; + left: 0; + animation: fadeInDown 0.2s; + flex-direction: column; + + .umb_confirm-action__overlay-action { + margin-top: 5px; + } + + .umb_confirm-action__overlay-action.-confirm { + order: 2; + } + + .umb_confirm-action__overlay-action.-cancel { + order: 1; + } +} + +.umb_confirm-action__overlay.-left { + top: 0; + right: auto; + bottom: auto; + left: -50px; + animation: fadeInRight 0.2s; + flex-direction: row; + + .umb_confirm-action__overlay-action { + margin-right: 5px; + } + + .umb_confirm-action__overlay-action.-confirm { + order: 1; + } + + .umb_confirm-action__overlay-action.-cancel { + order: 2; + } +} + +// BUTTONS +.umb_confirm-action__overlay-action { + width: 20px; + height: 20px; + display: flex; + align-items: center; + justify-content: center; + color: #ffffff; + border-radius: 40px; + box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.26); + font-size: 18px; +} + +.umb_confirm-action__overlay-action:hover { + text-decoration: none; + color: #ffffff; +} + +// confirm button +.umb_confirm-action__overlay-action.-confirm { + background: #ffffff; + color: @green !important; +} + +.umb_confirm-action__overlay-action.-confirm:hover { + color: @green !important; +} + +// cancel button +.umb_confirm-action__overlay-action.-cancel { + background: #ffffff; + color: @red !important; +} + +.umb_confirm-action__overlay-action.-cancel:hover { + color: @red !important; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-content-grid.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-content-grid.less new file mode 100644 index 0000000000..a4e18abb20 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-content-grid.less @@ -0,0 +1,124 @@ +.umb-content-grid { + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: center; +} + +.umb-content-grid__item { + background: @grayLighter; + flex: 0 1 200px; + cursor: pointer; + position: relative; + margin: 10px; + border: 1px solid #ffffff; + user-select: none; +} + +.umb-content-grid__item:hover { + border: 1px solid @blue; +} + +.umb-content-grid__item:hover .umb-content-grid__action { + opacity: 1; +} + +.umb-content-grid__icon-container { + background: lighten(@grayLight, 7%); + height: 75px; + display: flex; + align-items: center; + justify-content: center; +} + +.umb-content-grid__icon { + font-size: 40px; + color: lighten(@gray, 20%); +} + +.umb-content-grid__icon.-light { + color: @grayLight; +} + + +.umb-content-grid__content { + box-sizing: border-box; + padding: 15px; +} + +.umb-content-grid__item-name { + font-weight: bold; + color: @grayDark; + margin-bottom: 10px; + color: black; + padding-bottom: 10px; + line-height: 1.4em; + border-bottom: 1px solid @grayLight; +} + +.umb-content-grid__item-name.-light { + color: @grayLight; +} + +.umb-content-grid__details-list { + list-style: none; + margin-bottom: 0; + margin-left: 0; + font-size: 11px; +} + +.umb-content-grid__details-list.-light { + color: @grayLight; +} + +.umb-content-grid__details-label { + font-weight: bold; + display: inline-block; +} + +.umb-content-grid__details-value { + display: inline-block; +} + +.umb-content-grid__action { + position: absolute; + opacity: 0; + top: 10px; + right: 10px; + border: 1px solid #ffffff; + width: 25px; + height: 25px; + background: rgba(0, 0, 0, 0.4); + border-radius: 50px; + box-sizing: border-box; + display: flex; + justify-content: center; + align-items: center; + color: #ffffff; + cursor: pointer; +} + +.umb-content-grid__action:hover { + background: @blue; + transition: background 0.1s; +} + +.umb-content-grid__action.-selected { + opacity: 1; + background: @blue; +} + +.umb-content-grid__item:hover .umb-content-grid__action:not(.-selected) { + opacity: 1; + animation: fadeIn; + animation-duration: 0.2s; + animation-timing-function: ease-in-out; +} + +.umb-content-grid__no-items { + font-size: 16px; + font-weight: bold; + color: @grayLight; + padding-top: 50px; + padding-bottom: 50px; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-navigation.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-navigation.less new file mode 100644 index 0000000000..b5e7cf7bed --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-navigation.less @@ -0,0 +1,39 @@ +.umb-sub-views-nav { + list-style: none; + display: flex; + margin: 0; +} + +.umb-sub-views-nav-item { + text-align: center; + margin-left: 20px; + cursor: pointer; + display: block; +} + +.umb-sub-views-nav-item:focus { + outline: none; +} + +.umb-sub-views-nav-item:hover, +.umb-sub-views-nav-item:focus { + text-decoration: none; +} + +.umb-sub-views-nav-item.is-active { + color: @blue; +} + +.show-validation .umb-sub-views-nav-item.-has-error { + color: @red; +} + +.umb-sub-views-nav-item .icon { + font-size: 24px; + display: block; + text-align: center; +} + +.umb-sub-views-nav-item-text { + font-size: 11px; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-sub-views.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-sub-views.less new file mode 100644 index 0000000000..ac034cf33b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-sub-views.less @@ -0,0 +1,23 @@ +/* --------- COLUMNS --------- */ + +.sub-view-columns { + display: flex; + margin-bottom: 40px; +} + +.sub-view-columns h5 { + margin-top: 0; +} + +.sub-view-column-left { + flex: 0 0 250px; + margin-right: 70px; +} + +.sub-view-column-right { + flex: 1; +} + +.sub-view-column-section { + margin-bottom: 20px; +} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-toolbar.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-toolbar.less new file mode 100644 index 0000000000..a4f3c902ed --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-toolbar.less @@ -0,0 +1,18 @@ +.umb-editor-toolbar { + display: flex; + justify-content: flex-end; + margin-bottom: 20px; + height: 25px; +} + +.umb-editor-toolbar__tool { + margin-left: 20px; + cursor: pointer; + display: flex; + align-items: center; +} + +.umb-editor-toolbar__tool-icon { + font-size: 20px; + margin-right: 5px; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-file-dropzone.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-file-dropzone.less new file mode 100644 index 0000000000..d4e82a6419 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-file-dropzone.less @@ -0,0 +1,129 @@ + +.umb-file-dropzone-directive{ + + // drop zone + // tall and small version - animate height + .dropzone { + height: 400px; + width: 100%; + padding: 50px 0; + border: 1px dashed @grayLight; + text-align: center; + color: @gray; + margin: 0 0 20px 0; + position: relative; + -webkit-transition: height 0.8s; + -moz-transition: height 0.8s; + transition: height 0.8s; + .illustration { + width: 300px; + } + &.is-small { + height: 100px; + .illustration { + width: 200px; + } + } + &.drag-over { + border: 1px dashed @grayDark; + } + } + + + // center the content of the drop zone + .content { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%,-50%); + } + + + // file select link + .file-select { + font-size: 15px; + color: @blue; + cursor: pointer; + + margin-top: 10px; + + &:hover { + color: @blueDark; + text-decoration: underline; + } + } + + // uploading / uploaded file list + .file-list { + list-style: none; + margin: 0 0 30px 0; + background: @grayLighter; + padding: 10px 20px; + + .file { + //border-bottom: 1px dashed @orange; + display: block; + width: 100%; + padding: 5px 0; + position: relative; + border-top: 1px solid @grayLight; + &:first-child { + border-top: none; + } + + &.ng-enter { + animation: fadeIn 0.5s; + } + &.ng-leave { + animation: fadeOut 2s; + } + .file-description { + color: @grayDarker; + font-size: 12px; + width: 100%; + display: block; + } + + .file-upload-progress { + display: block; + width: 100%; + } + + .file-icon { + position: absolute; + right: 0; + bottom: 0; + + .icon { + font-size: 20px; + &.ng-enter { + animation: fadeIn 0.5s; + } + &.ng-leave { + animation: fadeIn 0.5s; + } + } + } + } + } + + + // progress bars + // could be moved to its own less file + .file-progress { + height: 4px; + position: relative; + padding: 2px; + + .file-progress-indicator { + display: block; + height: 100%; + border-radius: 20px; + background-color: @blue; + position: relative; + overflow: hidden; + width: 0; + } + } + +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-folder-grid.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-folder-grid.less new file mode 100644 index 0000000000..b82a18e3bb --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-folder-grid.less @@ -0,0 +1,81 @@ +.umb-folder-grid { + display: flex; + flex-direction: row; + flex-wrap: wrap; + width: 100%; + margin-bottom: 30px; +} + +.umb-folder-grid__folder { + background: @grayLighter; + margin: 5px; + display: flex; + align-items: center; + padding: 10px 20px; + box-sizing: border-box; + flex: 1 1 200px; + border: 1px solid transparent; + transition: border 0.2s; + position: relative; + justify-content: space-between; + cursor: pointer; + user-select: none; +} + +.umb-folder-grid__folder:focus, +.umb-folder-grid__folder:active { + text-decoration: none; +} + +.umb-folder-grid__folder:hover { + text-decoration: none; + border: 1px solid @blue; +} + +.umb-folder-grid__folder-description { + display: flex; + align-items: center; +} + +.umb-folder-grid__folder-icon { + font-size: 20px; + color: @gray; + margin-right: 20px; +} + +.umb-folder-grid__folder-name { + font-size: 13px; +} + +.umb-folder-grid__action { + opacity: 0; + border: 1px solid #ffffff; + width: 25px; + height: 25px; + background: rgba(0, 0, 0, 0.4); + border-radius: 50px; + box-sizing: border-box; + display: flex; + justify-content: center; + align-items: center; + color: #ffffff; + margin-left: 7px; + cursor: pointer; +} + +.umb-folder-grid__action:hover { + background: @blue; + transition: background 0.1s; +} + +.umb-folder-grid__action.-selected { + opacity: 1; + background: @blue; +} + +.umb-folder-grid__folder:hover .umb-folder-grid__action:not(.-selected) { + opacity: 1; + animation: fadeIn; + animation-duration: 0.2s; + animation-timing-function: ease-in-out; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-grid-selector.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-grid-selector.less new file mode 100644 index 0000000000..068894884e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-grid-selector.less @@ -0,0 +1,75 @@ +.umb-grid-selector__items { + display: flex; + flex-direction: row; + flex-wrap: wrap; +} + +.umb-grid-selector__item { + width: 125px; + height: 150px; + padding: 20px; + background: #f8f8f8; + border: 1px solid #CCCCCC; + text-align: center; + margin: 0 20px 20px 0; + display: flex; + align-items: center; + justify-content: center; + border-radius: 5px; + animation: fadeIn 0.5s; + position: relative; +} + +.umb-grid-selector__item.-default { + border-width: 2px; +} + +.umb-grid-selector__item.-placeholder { + border: 1px dashed #d9d9d9; + background: none; + cursor: pointer; +} + +.umb-grid-selector__item-content { + margin-top: 10px; +} + +.umb-grid-selector__item-icon { + font-size: 50px; + color: #d9d9d9; + display: block; + line-height: 50px; + margin-bottom: 5px; +} + +.umb-grid-selector__item-label { + font-size: 13px; + font-weight: bold; +} + +.umb-grid-selector__item-label.-blue { + color: @blue; +} + +.umb-grid-selector__item-remove { + position: absolute; + top: 5px; + right: 5px; + cursor: pointer; +} + +.umb-grid-selector__item-default-label { + font-size: 10px; + color: @gray; + font-weight: bold; + position: relative; + top: -3px; +} + +.umb-grid-selector__item-default-label.-blue { + color: @blue; +} + +.umb-grid-selector__item-add { + color: @blue; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-grid.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-grid.less new file mode 100644 index 0000000000..99e6378cca --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-grid.less @@ -0,0 +1,1097 @@ +// TODO General cleanup in !important (Round 2) + +// Gridview +// ------------------------- +.umb-grid IFRAME { + overflow: hidden; +} + + + +// Sortabel +// ------------------------- + +// sortable-helper +.umb-grid .ui-sortable-helper { + position: absolute !important; + background-color: @blue !important; + height: 42px !important; + width: 42px !important; + overflow: hidden; + padding: 5px; + border-radius: 2px; + text-align: center; + font-family: "icomoon"; + box-shadow: 3px 3px 12px 0 rgba(50, 50, 50, 0.45); + + &:after { + line-height: 42px; + font-size: 22px; + content: "\e126"; + color: #ffffff; + } + + * { + display: none; + } +} + +.umb-grid .ui-sortable-helper .umb-row-title-bar, +.umb-grid .ui-sortable-helper .cell-tools-add { + display: none !important; +} + +// sortable-placeholder +.umb-grid .ui-sortable-placeholder { + position: absolute; + left: 0; + right: 0; + background-color: @blue; + height: 2px; + margin-bottom: 20px; + + &:before, &:after { + position: absolute; + top: -9px; + font-family: "icomoon"; + font-size: 18px; + color: @blue; + } + + &:before { + left: -5px; + content: "\e0e9"; + } + + &:after { + right: -5px; + content: "\e0d7"; + } +} + +.umb-grid .umb-cell .ui-sortable-placeholder { + left: 10px; + right: 10px; +} + + + + +// utils +// ------------------------- +.umb-grid-width { + margin: 20px auto; + width: 100%; +} + +.umb-grid .right { + float: right; +} + + + +// general layout components +// ------------------------- +.umb-grid .tb { + width: 100%; +} + +.umb-grid .td { + width: 100%; + display: inline-block; + vertical-align: top; + box-sizing: border-box; +} + +.umb-grid .middle { + text-align: center; +} + +.umb-grid .mainTd { + position: relative; +} + + + +// COLUMN +// ------------------------- +.umb-grid .umb-column { + position: relative; +} + + + +// ROW +// ------------------------- +.umb-grid .umb-row { + position: relative; + margin-bottom: 40px; + padding-top: 10px; +} + +.umb-grid .row-tools a { + text-decoration: none; +} + + + +// CELL +// ------------------------- +.umb-grid .umb-cell { + position: relative; +} + +.umb-grid .umb-cell-content { + position: relative; + display: block; + box-sizing: border-box; + margin: 10px; + border: 1px solid transparent; +} + +.umb-grid .umb-row .umb-cell-placeholder { + min-height: 130px; + background-color: @grayLighter; + border-width: 2px; + border-style: dashed; + border-color: @grayLight; + transition: border-color 100ms linear; + + &:hover { + border-color: @blue; + cursor: pointer; + } +} + +.umb-grid .umb-cell-content.-has-editors { + padding-top: 38px; + background-color: #ffffff; + border-width: 1px; + border-style: solid; + border-color: @grayLight; + + &:hover { + cursor: auto; + } +} + +.umb-grid .umb-cell-content.-has-editors.-collapsed { + padding-top: 10px; +} + + + +.umb-grid .cell-tools { + position: absolute; + top: 10px; + right: 10px; + color: @gray; + font-size: 16px; +} + + +.umb-grid .cell-tool { + cursor: pointer; + float: right; + + &:hover { + color: @blueDark; + } +} + + +.umb-grid .cell-tools-add { + color: @blue; + + &:focus, &:hover { + text-decoration: none; + } +} + +.umb-grid .cell-tools-add.-center { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + color: @blue; +} + +.umb-grid .cell-tools-add.-bar { + display: block; + background: @grayLighter; + text-align: center; + padding: 5px; + border: 1px dashed #ccc; + margin: 10px; + border-radius: 5px; +} + + +.umb-grid .cell-tools-remove { + display: inline-block; + position: relative; +} + +.umb-grid .cell-tools-remove .iconBox:hover, +.umb-grid .cell-tools-remove .iconBox:hover * { + background: @red !important; + border-color: @red !important; +} + +.umb-grid .cell-tools-move { + display: inline-block; +} + +.umb-grid .cell-tools-edit { + display: inline-block; + color: @white; +} + +.umb-grid .drop-overlay { + position: absolute; + z-index: 10; + top: 0; + left: 0; + background: #ffffff; + opacity: 0.9; + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + font-size: 14px; + box-sizing: border-box; + text-align: center; + line-height: 1.3em; + font-weight: bold; + flex-direction: column; +} + +.drop-overlay.-disable { + color: @red; +} + +.drop-overlay.-allow { + color: @green; +} + +.umb-grid .drop-overlay .drop-icon { + font-size: 40px; + margin-bottom: 20px; +} + + + +// CONTROL +// ------------------------- +.umb-grid .umb-control { + position: relative; + display: block; + overflow: hidden; + margin-left: 10px; + margin-right: 10px; + margin-bottom: 10px; +} + +.umb-control-collapsed { + background-color: @grayLighter; + padding: 5px 10px; + border-width: 1px; + border-style: solid; + border-color: transparent; + cursor: move; +} + +.umb-control-collapsed:hover { + border-color: @blue; +} + +.umb-grid .umb-control-click-overlay { + position: absolute; + width: 100%; + height: 100%; + z-index: 5; + top: 0; + left: 0; + opacity: 0; + cursor: pointer; + + &:hover { + opacity: 0.1; + background-color: @blue; + transition: opacity 0.1s; + } +} + + +.umb-grid .umb-row-title-bar { + display: flex; + padding-left: 10px; + padding-right: 10px; +} + +.umb-grid .umb-row-title { + display: inline-block; + cursor: pointer; + font-size: 15px; + font-weight: bold; + color: @black; + + margin-right: 6px; +} + +.umb-grid .row-tools { + display: inline-block; + margin-left: 10px; + font-size: 18px; + color: @gray; +} + +.umb-grid .row-tool { + cursor: pointer; +} + +.umb-grid .umb-add-row { + text-align: center; +} + + + +// CONTROL PLACEHOLDER +// ------------------------- +.umb-grid .umb-control-placeholder { + min-height: 20px; + position: relative; + text-align: center; + text-align: -moz-center; + cursor: text; +} + +.umb-grid .umb-control-placeholder .placeholder { + font-size: 14px; + opacity: 0.7; + text-align: left; + padding: 5px; + border: 1px solid rgba(182, 182, 182, 0.3); + height: 20px; +} + +.umb-grid .umb-control-placeholder:hover .placeholder { + border: 1px solid rgba(182, 182, 182, 0.8); +} + + + +// EDITOR PLACEHOLDER +// ------------------------- +.umb-grid .umb-editor-placeholder { + min-height: 65px; + padding: 20px; + padding-bottom: 30px; + position: relative; + background-color: @white; + border: 4px dashed @grayLight; + text-align: center; + text-align: -moz-center; + cursor: pointer; +} + +.umb-grid .umb-editor-placeholder i { + color: @grayLight; + font-size: 85px; + line-height: 85px; + display: block; + margin-bottom: 10px; +} + + + +// Active states +// ------------------------- + +// Row states +.umb-grid .umb-row.-active { + background-color: @blue; + + .umb-row-title-bar { + cursor: move; + } + + .row-tool, + .umb-row-title { + color: #fff; + } + + .umb-grid-has-config { + color: fade(@white, 66); + } + + .umb-cell { + .umb-grid-has-config { + color: fade(@black, 44); + } + } + + .umb-cell .umb-cell-content { + border-color: transparent; + } +} + + +.umb-grid .umb-row.-active-child { + background-color: @grayLighter; + + .umb-row-title-bar { + cursor: default; + } + + .umb-row-title { + color: @gray; + } + + .row-tool { + color: fade(@black, 23); + } + + .umb-grid-has-config { + color: fade(@black, 44); + } + + .umb-cell-content.-placeholder { + border-color: @grayLight; + + &:hover { + border-color: fade(@gray, 44); + } + } +} + + +// Cell states + +.umb-grid .umb-row .umb-cell.-active { + border-color: @grayLight; + + .umb-cell-content.-has-editors { + box-shadow: 3px 3px 6px rgba(0, 0, 0, .07); + border-color: @blue; + } +} + +.umb-grid .umb-row .umb-cell.-active-child { + + .cell-tool { + color: fade(@black, 23); + } + + .umb-cell-content.-has-editors { + border-color: rgba(113, 136, 160, .44); + } +} + + + + + +// Title bar and tools +.umb-grid .umb-row-title-bar { + display: flex; +} + +.umb-grid .umb-grid-right { + display: flex; + flex-direction: row; + justify-content: center; +} + +.umb-grid .umb-tools { + margin-left: auto; +} + + +// Add more content button +.umb-grid-add-more-content { + text-align: center; +} + +.umb-grid .newbtn { + width: auto; + padding: 6px 15px; + border-style: solid; + + font-size: 12px; + font-weight: bold; + + display: inline-block; + + margin: 10px auto 20px; + + border-color: #E2E2E2; + + &:hover { + cursor: pointer; + opacity: .77; + } +} + + + + +// Form elements +// ------------------------- +.umb-grid textarea.textstring { + display: block; + overflow: hidden; + border: none; + background: #fff; + outline: none; + resize: none; + color: @gray; +} + +.umb-grid .umb-cell-rte textarea { + display: none !important; +} + +.umb-grid .umb-cell-media .caption { + display: block; + overflow: hidden; + border: none; + background: #fff; + outline: none; + width: 98%; + resize: none; + font-style: italic; +} + +.umb-grid .cellPanelRte { + min-height: 60px; +} + +.umb-grid .umb-cell-embed iframe { + width: 100%; +} + +.umb-grid .umb-cell-rte { + border-color: transparent; +} + + + +// ICONS +// ------------------------- +.umb-grid .iconBox { + padding: 4px 6px; + display: inline-block; + cursor: pointer; + border-radius: 200px; + background: rgba(255,255,255, 1); + border: 1px solid rgb(182, 182, 182); + margin: 2px; + + &:hover, &:hover * { + background: @blue !important; + color: white !important; + border-color: @blue !important; + text-decoration: none; + } +} + +.umb-grid .iconBox span.prompt { + display: block; + white-space: nowrap; + text-align: center; +} + +.umb-grid .iconBox span.prompt > a { + text-decoration: underline; +} + +.umb-grid .iconBox a:hover { + text-decoration: none; + color: white !important; +} + +.umb-grid .iconBox.selected { + -webkit-appearance: none; + background-image: linear-gradient(to bottom,#e6e6e6,#bfbfbf); + background-repeat: repeat-x; + filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe6e6e6',endColorstr='#ffbfbfbf',GradientType=0); + zoom: 1; + border-color: #bfbfbf #bfbfbf #999; + border-color: rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25); + box-shadow: inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05); + border-radius: 3px; + background: transparent; +} + +.umb-grid .iconBox i { + font-size: 16px !important; + color: #5F5F5F; + display: block; +} + +.umb-grid .help-text { + color: @black; + font-size: 14px; + font-weight: bold; + display: inline-block; + clear: both; +} + + + +// TINYMCE EDITOR +// ------------------------- + +.umb-grid .mce-panel { + background: transparent !important; + border: none !important; + clear: both; +} + +.umb-grid .mce-btn button { + padding: 8px 6px; + line-height: inherit; +} + +.umb-grid .mce-toolbar { + border-bottom: 1px solid rgba(207, 207, 207, 0.7); + background-color: rgba(250, 250, 250, 1); + display: none; +} + +.umb-grid .umb-control.-active .mce-toolbar { + display: block; +} + +.umb-grid .mce-flow-layout-item { + margin: 0; +} + +.umb-grid .mceContentBody { + overflow-y: hidden!important; +} + + +// MEDIA EDITOR +// ------------------------- +.umb-grid .fullSizeImage { + width: 100%; +} + + + +// Width +// ------------------------- +.umb-grid .boxWidth { + text-align: right; + margin-bottom: 10px; +} + +.umb-grid .boxWidth input { + text-align: center; + width: 40px; +} + +.umb-grid .boxWidth label { + font-size: 10px; + padding: 0; + margin: 5px 5px 0 0; + color: #808080; +} + + + +// Margin Control +// ------------------------- +.umb-grid .umb-control { + border-width: 1px; + border-style: solid; + border-color: transparent; +} + +.umb-grid .umb-control.-active { + border-color: @blue; +} + +.umb-grid .umb-templates-columns { + margin-top: 30px; +} + +.umb-grid .umb-control-inner { + position: relative; +} + +.umb-grid .umb-control-bar { + opacity: 0; + background: @blue; + padding: 2px 5px; + color: #ffffff; + font-size: 10px; + height: 0; + display: flex; + transition: height 80ms linear, opacity 80ms linear; + align-items: center; +} + +.umb-grid .umb-control-title { + display: flex; + align-items: center; +} + +.umb-grid .umb-control.-active .umb-control-bar { + opacity: 1; + height: 25px; + cursor: move; +} + +.umb-grid .umb-control-tools { + display: inline-block; + margin-left: 10px; +} + +.umb-grid .umb-control-tool { + font-size: 16px; + margin-right: 5px; + position: relative; + cursor: pointer; +} + + + +// Template +// ------------------------- +.umb-grid .umb-templates { + text-align: center; + overflow: hidden; + width: 100%; +} + +.umb-grid .umb-templates-template { + display: inline-block; + width: 100px; + padding-right: 30px; + margin: 20px; +} + +.umb-grid .umb-templates-template a.tb:hover { + border: 5px solid @blue; +} + +.umb-grid .umb-templates-template .tb { + width: 100%; + height: 150px; + padding: 10px; + background-color: @grayLighter; + border: 5px solid @grayLight; + cursor: pointer; + position: relative; +} + +.umb-grid .umb-templates-template .tr { + height: 100%; + position: relative; +} + +.umb-grid .umb-templates-template .tb .umb-templates-column { + height: 100%; + border: 1px dashed @grayLight; + border-right: none; +} + +.umb-grid .umb-templates-template .tb .umb-templates-column.last { + border-right: 1px dashed @grayLight !important; +} + +.umb-grid a.umb-templates-column:hover, +.umb-grid a.umb-templates-column.selected { + background-color: @blue; +} + + + +// Template Column +// ------------------------- +/* New template preview */ +.umb-grid { + .templates-preview { + display: inline-block; + width: 100%; + text-align: center; + + small { + position: absolute; + width: 100%; + left: 0; + bottom: -25px; + padding-top: 15px; + } + + .help-text { + margin: 35px 35px 0 0; + } + } + + .preview-rows { + display: inline-block; + position: relative; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + width: 125px; + margin: 15px; + border: 3px solid @grayLight; + transition: border 100ms linear; + + &.prevalues-rows { + margin: 0 20px 20px 0; + width: 80px; + float: left; + } + + &.prevalues-templates { + margin: 0 20px 20px 0; + float: left; + } + + &:hover { + border-color: @blue; + cursor: pointer; + } + + .preview-row { + display: inline-block; + width: 100%; + vertical-align: bottom; + } + } + + .preview-rows.layout { + padding: 2px; + + .preview-row { + height: 100%; + } + + .preview-col { + height: 180px; + } + + .preview-cell { + background-color: @grayLighter; + } + + .preview-overlay { + display: none; + } + } + + .preview-rows.columns { + min-height: 16px; + line-height: 11px; + padding: 1px; + + &.prevalues-rows { + min-height: 30px; + } + } + + .preview-rows { + .preview-col { + display: block; + float: left; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + width: 33.3%; + + /* temp value */ + height: 10px; + margin: 0; + border: 1px solid @white; + + .preview-cell { + display: block; + width: 100%; + height: 100%; + background-color: @grayLight; + margin: 0 1px 1px 0; + } + } + + &.prevalues-templates { + .preview-col { + height: 80px; + } + } + } + + .preview-overlay { + display: block; + width: 100%; + position: absolute; + height: 100%; + top: 0; + box-sizing: border-box; + left: 0; + border: 3px solid white; + } +} + +// Has Config +// ------------------------- + +.umb-grid .umb-grid-has-config { + display: inline; + + font-size: 12px; + color: fade(@black, 44); +} + +.umb-grid .umb-cell { + .umb-grid-has-config { + position: absolute; + top: 10px; + left: 10px; + } +} + + +// Overlay +// ------------------------- +.umb-grid .cell-tools-menu { + position: absolute; + width: 360px; + height: 380px; + overflow: auto; + border: 1px solid #ccc; + margin-top: -270px; + margin-left: -150px; + background: @white; + padding: 7px; + top: 0; + left: 50%; + z-index: 6660; + box-shadow: 3px 3px 12px 0 rgba(50, 50, 50, 0.45); +} + +.umb-grid .cell-tools-menu h5 { + border-bottom: 1px solid #d9d9d9; + color: #999; + padding: 10px; + margin-top: 0; +} + +.umb-grid .elements { + display: block; + padding: 0; + margin: 0; +} + +.umb-grid .elements li { + display: inline-block; + width: 90px; + height: 80px; + margin: 5px; + padding: 5px; + overflow: hidden; + font-size: 11px; + + &:hover, &:hover * { + background: #2e8aea; + color: @white; + } +} + +.umb-grid .elements a { + color: #222; + text-decoration: none; +} + +.umb-grid .elements i { + font-size: 30px; + line-height: 50px; + color: #999; + display: block; +} + + + +// Configuration specific styles +// ------------------------- +.umb-grid-configuration .umb-templates { + text-align: left; +} + +.umb-grid-configuration ul { + display: block; +} + +.umb-grid-configuration ul li { + display: block; + width: auto; + text-align: left; +} + +.umb-grid-configuration .umb-templates .umb-templates-template .tb { + max-height: 50px; + border-width: 2px !important; + padding: 0; + border-spacing: 2px; + overflow: hidden; +} + +.umb-grid-configuration .umb-templates .umb-templates-template span { + background: @grayLight; + display: inline-block; +} + +.umb-grid-configuration .umb-templates .umb-templates-template .tb:hover { + border-width: 2px !important; +} + +.umb-grid-configuration .umb-templates-column { + display: block; + float: left; + margin-left: -1px; + border: 1px white solid !important; + background: @grayLight; +} + +.umb-grid-configuration .umb-templates-column.last { + margin-right: -1px; +} + +.umb-grid-configuration .umb-templates-column.add { + text-align: center; + font-size: 20px; + line-height: 70px; + color: #ccc; + text-decoration: none; + background: @white; +} + +.umb-grid-configuration .mainTdpt { + height: initial; + border: none; +} + +.umb-grid-configuration .umb-templates-rows .umb-templates-row { + margin: 0 50px 20px 0; + width: 60px; +} + +.umb-grid-configuration .umb-templates-rows .umb-templates-row .tb { + border-width: 2px !important; + padding: 0; + border-spacing: 2px; +} + +.umb-grid-configuration .umb-templates-rows .mainTdpt { + height: 10px !important; +} + +.umb-grid-configuration a.umb-templates-column { + height: 70px !important; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less new file mode 100644 index 0000000000..2768c370c9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less @@ -0,0 +1,505 @@ +/* ---------- GROUPS ---------- */ + +.umb-group-builder__groups { + list-style: none; + margin: 0; + padding: 0; +} + +.umb-group-builder__group { + min-height: 86px; + margin: 50px 0 0 0; + border: 2px solid #CCCCCC; + border-radius: 0 10px 10px 10px; + position: relative; + padding: 10px 10px 5px 10px; + box-sizing: border-box; +} + +.umb-group-builder__group.-active { + border-color: @blue; +} + +.umb-group-builder__group.-inherited { + background: #FDFDFD; + border-color: #F2F2F2; + animation: fadeIn 0.5s; +} + +.umb-group-builder__group.-placeholder { + min-height: 86px; + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; + border: 1px dashed @grayLight; + color: @blue; + font-weight: bold; +} + +.umb-group-builder__group.-sortable { + min-height: initial; + cursor: move; +} + +.umb-group-builder__group-actions { + position: absolute; + top: 5px; + right: 5px; + visibility: hidden; + opacity: 0; + z-index: 10; +} + +.umb-group-builder__group-action { + display: inline-block; +} + +.umb-group-builder__group-remove { + position: absolute; + top: -30px; + right: 20px; + font-size: 18px; +} + +.umb-group-builder__group-remove:hover { + cursor: pointer; + color: @blueDark; +} + +.umb-group-builder__group-title-wrapper { + position: absolute; + left: -2px; + top: -45px; + display: flex; + align-items: center; +} + +.umb-group-builder__group-title-wrapper.-placeholder { + left: -1px; + top: -44px; +} + +.umb-group-builder__group-title { + padding: 5px 9px 0 9px; + height: 38px; + background: white; + border: 2px solid #CCCCCC; + border-bottom: none; + border-radius: 10px 10px 0 0; + font-weight: bold; + display: flex; + align-items: center; + margin-right: 10px; +} + +.umb-group-builder__group-title-icon { + margin-left: 5px; +} + +.umb-group-builder__group-title.-active { + border-color: @blue; +} + +.umb-group-builder__group-title.-placeholder { + border: 1px dashed @grayLight; + border-bottom: none; + width: 70px; +} + +.umb-group-builder__group-title.-inherited { + border-color: #F2F2F2; + background: #FDFDFD; +} + +input.umb-group-builder__group-title-input { + border-color: transparent; + background: transparent; + font-weight: bold; + color: #515151; + margin-bottom: 0; +} + +.umb-group-builder__group-title-input:hover { + border-color: @inputBorder; +} + +.umb-group-builder__group-title-input.-placeholder { + border: 1px dashed #979797; +} + +.umb-group-builder__group-inherited-label { + font-size: 13px; + color: @grayLight; + display: inline-block; + position: relative; + top: 2px; +} + +input.umb-group-builder__group-sort-value { + font-size: 11px; + padding: 0px 0 0px 5px; + width: 40px; + margin-bottom: 0; + margin-right: 10px; + border-color: #d9d9d9; + position: relative; + top: 2px; +} + +/* ---------- PROPERTIES ---------- */ + +.umb-group-builder__properties { + list-style: none; + margin: 0; + padding: 0; + min-height: 35px; // the height of a sortable property +} + +.umb-group-builder__property { + position: relative; + display: flex; + flex-flow: row; + margin-bottom: 5px; + border: 1px solid transparent; + border-radius: 5px; + padding: 5px; + box-sizing: border-box; +} + +.umb-group-builder__property:hover { + border: 1px dashed @inputBorder; +} + +.umb-group-builder__property.-placeholder { + background: #ffffff; + border: 1px dashed @grayLight; + border-radius: 5px; + cursor: pointer; + align-items: center; + animation: fadeIn 0.5s; +} + +.umb-group-builder__property.-inherited { + border: transparent; + background: #FDFDFD; + animation: fadeIn 0.5s; +} + +.umb-group-builder__property.-inherited:hover { + border: transparent; +} + +.umb-group-builder__property.-sortable, +.umb-group-builder__property.-sortable-locked { + min-height: 35px; + border-radius: 5px; + border: none; + animation: none; + align-items: center; +} + +.umb-group-builder__property.-sortable { + background: #E9E9E9; + color: @grayDarker; + cursor: move; +} + +.umb-group-builder__property.-sortable-locked { + background: @grayLighter; + padding-left: 30px; +} + + +.umb-group-builder__property-meta { + flex: 0 0 175px; + margin-right: 20px; +} + +.umb-group-builder__property-meta.-full-width { + flex: 1; +} + +.umb-group-builder__property-meta-alias { + font-size: 10px; + color: @gray; + word-wrap: break-word; +} + +.umb-group-builder__property-meta-label textarea { + font-size: 14px; + font-weight: bold; + margin-bottom: 0; + color: @grayDarker; + width: 100%; + padding: 0; + min-height: 25px; + box-sizing: border-box; + resize: none; + overflow: hidden; + border-color: transparent; + background: transparent; +} + +.umb-group-builder__property-meta-label textarea.ng-invalid { + border: none; +} + +.umb-group-builder__property-meta-description textarea { + font-size: 12px; + color: @grayDark; + margin-bottom: 0; + padding: 0; + width: 100%; + min-height: 25px; + box-sizing: border-box; + resize: none; + overflow: hidden; + border-color: transparent; + background: transparent; +} + +.umb-group-builder__property-preview { + flex: 1; + height: 30px; + overflow: hidden; + position: relative; + padding: 25px 10px; +} + +.umb-group-builder__property-preview:hover { + cursor: pointer; +} + +.umb-group-builder__property-preview:focus { + outline: none; +} + +.umb-group-builder__property-preview.-not-clickable:hover { + cursor: auto; +} + +.umb-group-builder__property-preview-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 10; + border-radius: 10px; + background: url("../img/checkered-background.png"); + opacity: 0.2; +} + +.umb-group-builder__property-preview-label { + font-size: 11px; + position: absolute; + top: 0; + left: 0; + text-transform: uppercase; + z-index: 15; + background: @grayLighter; + padding: 3px; + line-height: 12px; + opacity: 0.8 +} + +.umb-group-builder__property-actions { + flex: 0 0 40px; + text-align: center; + margin: 15px 0 0 15px; +} + +.umb-group-builder__property-action { + margin: 0 0 10px 0; + display: block; + font-size: 18px; + position: relative; + cursor: pointer; +} + +.umb-group-builder__property-action:hover { + color: @blueDark; +} + +.umb-group-builder__property-inherited-label { + font-size: 11px; + background-color: #E9E9E9; + margin-left: 5px; + position: absolute; + right: 0; + z-index: 100; + padding: 0 10px 0 5px; + top: 0; +} + +/* ---------- PLACEHOLDER BOX ---------- */ + +.umb-group-builder__placeholder-box { + background: #E9E9E9; + box-sizing: border-box; + display: flex; + align-items: center; + justify-content: center; + font-size: 12px; + font-weight: bold; + color: @blue; +} + +.umb-group-builder__placeholder-box.-input { + height: 10px; + margin-bottom: 5px; +} + +.umb-group-builder__placeholder-box.-input-small { + height: 5px; + margin-bottom: 5px; + width: 25%; +} + +.umb-group-builder__placeholder-box.-text-full-width { + height: 3px; + margin-bottom: 3px; +} + +.umb-group-builder__placeholder-box.-text-short { + height: 3px; + margin-bottom: 3px; + width: 75%; +} + +/* ---------- SORTABLE ---------- */ + +.umb-group-builder__group-sortable-placeholder { + background: transparent; + border: 1px dashed @grayLight; + margin: 0 0 70px 0; + border-radius: 10px; + border-radius: 5px; +} + +.umb-group-builder__property_sortable-placeholder { + background: transparent; + border: 1px dashed @grayLight; + margin-bottom: 5px; + border-radius: 5px; +} + +.umb-group-builder__no-data-text { + padding-top: 50px; + font-size: 16px; + line-height: 1.8em; + color: #ccc; + text-align: center; +} + + +/* ---------- DIALOGS ---------- */ + +.umb-overlay .show-validation .ng-invalid-val-required-component .editor-placeholder { + border-color: @red; + color: @red; +} + +.content-type-editor-dialog.edit-property-settings { + + .validation-wrapper { + position: relative; + } + + .validation-label { + position: absolute; + top: 50%; + right: 0; + font-size: 12px; + color: @red; + transform: translate(0, -50%); + } + + textarea.editor-label { + border-color:transparent; + box-shadow: none; + width: 100%; + box-sizing: border-box; + margin-bottom: 0; + font-size: 16px; + font-weight: bold; + resize: none; + line-height: 1.5em; + padding-left: 0; + border: none; + &:focus { + outline: none; + box-shadow: none !important; + } + } + + .editor-placeholder { + border: 1px dashed @grayLight; + width: 100%; + height: 80px; + line-height: 80px; + text-align: center; + display: block; + border-radius: 5px; + color: @gray; + font-weight: bold; + font-size: 13px; + color: @blue; + &:hover { + text-decoration: none; + } + } + + .editor { + margin-bottom: 10px; + .editor-icon-wrapper { + border: 1px solid @grayLight; + width: 60px; + height: 60px; + text-align: center; + line-height: 60px; + border-radius: 5px; + float: left; + margin-right: 20px; + .icon { + font-size: 26px; + } + } + .editor-details { + float: left; + margin-top: 10px; + .editor-name { + display: block; + font-weight: bold; + } + .editor-editor { + display: block; + font-size: 12px; + } + } + .editor-settings-icon { + font-size: 18px; + margin-top: 8px; + } + } + + .checkbox { + margin-bottom: 20px; + } + + .editor-description, + .editor-validation-pattern { + min-width: 100%; + min-height: 25px; + resize: none; + box-sizing: border-box; + border: none; + overflow: hidden; + } + + .umb-dropdown { + width: 100%; + } + +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-keyboard-shortcuts-overview.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-keyboard-shortcuts-overview.less new file mode 100644 index 0000000000..ab82eeec85 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-keyboard-shortcuts-overview.less @@ -0,0 +1,85 @@ + +/* ---------- OVERLAY ---------- */ + +.umb-keyboard-shortcuts-overview__overlay { + position: fixed; + width: 100%; + height: 100%; + top: 0; + left: 0; + background: rgb(255, 255, 255); + z-index: 1000; + animation: fadeIn 0.2s; + box-sizing: border-box; + padding-left: 440px; +} + +.umb-keyboard-shortcuts-overview__overlay-close { + position: absolute; + top: 10px; + right: 10px; +} + +.umb-keyboard-shortcuts-overview__overlay-content { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + width: 100%; + height: 100%; +} + +.umb-keyboard-shortcuts-overview__keyboard-shortcuts-group { + width: 50%; + margin-bottom: 10px; +} + +.umb-keyboard-shortcuts-overview__keyboard-shortcuts-group-name { + margin-bottom: 0; +} + +.umb-keyboard-shortcuts-overview__keyboard-shortcut { + display: flex; + justify-content: space-between; + align-items: center; + padding: 7px 0; + border-bottom: 1px solid @grayLight; +} + +.umb-keyboard-shortcuts-overview__keyboard-shortcut.-no-air { + padding: 0; +} + +.umb-keyboard-shortcuts-overview__keyboard-shortcut.-no-stroke { + border-bottom: none; +} + +.umb-keyboard-shortcuts-overview__description { + font-weight: bold; + font-size: 13px; + margin-right: 10px; +} + +/* ---------- KEYBOARD KEYS ---------- */ + +.umb-keyboard-keys { + list-style: none; + display: flex; + font-size: 12px; + align-items: center; +} + +.umb-keyboard-key-wrapper { + display: flex; + margin-right: 5px; + align-items: center; +} + +.umb-keyboard-key { + background: #ffffff; + border: 1px solid @grayLight; + color: @gray; + border-radius: 5px; + margin-right: 5px; + padding: 1px 7px; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-layout-selector.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-layout-selector.less new file mode 100644 index 0000000000..122107f88c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-layout-selector.less @@ -0,0 +1,58 @@ +.umb-layout-selector { + display: inline-block; + position: relative; +} + +.umb-layout-selector__active-layout { + box-sizing: border-box; + border: 1px solid transparent; + cursor: pointer; + height: 30px; + width: 30px; + font-size: 20px; + display: flex; + justify-content: center; + align-items: center; +} + +.umb-layout-selector__active-layout:hover { + border-color: @grayLight; +} + +.umb-layout-selector__dropdown { + position: absolute; + padding: 5px; + background: #333; + z-index: 999; + display: flex; + background: #fff; + flex-wrap: wrap; + flex-direction: column; + transform: translate(-50%,0); + left: 50%; +} + +.umb-layout-selector__dropdown-item { + padding: 5px; + margin: 3px 5px; + display: flex; + align-content: center; + justify-content: center; + border: 1px solid transparent; + flex-direction: column; + cursor: pointer; +} + +.umb-layout-selector__dropdown-item:hover { + border: 1px solid @grayLight; +} + +.umb-layout-selector__dropdown-item.-active { + border: 1px solid @blue; +} + +.umb-layout-selector__dropdown-item-icon { + font-size: 20px; + color: @gray; + text-align: center; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-list-view.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-list-view.less new file mode 100644 index 0000000000..aadcd9dc89 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-list-view.less @@ -0,0 +1,52 @@ +.umb-list-view__box { + background: #f8f8f8; + border: 1px solid #CCCCCC; + display: flex; + border-radius: 5px; + animation: fadeIn 0.5s; + padding: 15px; + position: relative; +} + +.umb-list-view__trigger { + margin-bottom: 20px; +} + +.umb-list-view__box.-open { + border-bottom: transparent; + border-radius: 5px 5px 0 0; +} + +.umb-list-view__content { + display: flex; +} + +.umb-list-view__list-view-icon { + font-size: 20px; + color: #d9d9d9; + margin-right: 10px; +} + +.umb-list-view__name { + margin-right: 5px; + font-size: 14px; + font-weight: bold; + float: left; +} + +.umb-list-view__create-new { + font-size: 12px; + color: @blue; +} + +.umb-list-view__remove-new { + font-size: 12px; + color: @red; +} + +.umb-list-view__settings { + border: 1px dashed #d9d9d9; + border-top: none; + border-radius: 0 0 5px 5px; + padding: 20px; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-load-indicator.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-load-indicator.less new file mode 100644 index 0000000000..b2d03147a0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-load-indicator.less @@ -0,0 +1,55 @@ +.umb-load-indicator { + list-style: none; + margin: 0; + padding: 0; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + font-size: 0; +} + +.umb-load-indicator__bubble { + height: 0; + position: absolute; + top: 50%; + left: 0; + width: 0; + margin: 0; + height: 6px; + width: 6px; + border: 2px solid @blue; + border-radius: 100%; + transform: transformZ(0); + animation: umbLoadIndicatorAnimation 1.4s infinite; +} + +.umb-load-indicator__bubble:nth-child(1n) { + left: -16px; + animation-delay: 0s; +} + +.umb-load-indicator__bubble:nth-child(2n) { + left: 0; + animation-delay: 0.15s; +} + +.umb-load-indicator__bubble:nth-child(3n) { + left: 16px; + animation-delay: 0.30s; +} + +@keyframes umbLoadIndicatorAnimation { + 0% { + transform: scale(0.5); + background: @blue; + } + 50% { + transform: scale(1); + background: white; + } + 100% { + transform: scale(0.5); + background: @blue; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-locked-field.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-locked-field.less new file mode 100644 index 0000000000..d9c9b16f40 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-locked-field.less @@ -0,0 +1,49 @@ +.umb-locked-field { + font-size: 13px; + color: #ccc; + position: relative; + display: block; +} + +.umb-locked-field__wrapper { + display: flex; + align-items: center; + margin-bottom: 5px; +} + +.umb-locked-field__toggle { + margin-right: 3px; +} + +.umb-locked-field__toggle:hover, +.umb-locked-field__toggle:focus { + text-decoration: none; +} + +.umb-locked-field__lock-icon { + color: #ccc; + transition: color 0.25s; + //vertical-align: top; +} + +.umb-locked-field__lock-icon.-unlocked { + color: #515151; +} + +input.umb-locked-field__input { + background: rgba(255, 255, 255, 0); // if using transparent it will hide the text in safari + border-color: transparent !important; + font-size: 13px; + margin-bottom: 0; + color: #ccc; + transition: color 0.25s; + padding: 0; +} + +input.umb-locked-field__input:focus { + box-shadow: none !important; +} + +input.umb-locked-field__input.-unlocked { + color: #515151; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-media-grid.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-media-grid.less new file mode 100644 index 0000000000..01c1cf5206 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-media-grid.less @@ -0,0 +1,138 @@ +.umb-media-grid { + display: flex; + flex-wrap: wrap; + flex-direction: row; + flex-wrap: wrap; + align-items: center; + width: 100%; + margin-bottom: 30px; +} + +.umb-media-grid__item { + margin: 2px; + position: relative; + background: @grayLighter; + overflow: hidden; +} + +.umb-media-grid__item:hover { + text-decoration: none; +} + +.umb-media-grid__item-image { + width: 100%; + max-width: 100%; + height: auto; +} + +.umb-media-grid__item-image.-faded { + opacity: 0.5; + transition: opacity 100ms ease-in; +} + +.umb-media-grid__item-overlay { + display: flex; + justify-content: center; + align-items: center; + opacity: 0; + position: absolute; + right: 0; + bottom: 0; + left: 0; + z-index: 100; + padding: 5px 10px; + box-sizing: border-box; + font-size: 13px; + overflow: hidden; + color: white; + white-space: nowrap; + background: rgba(0, 0, 0, 0.4); + background: linear-gradient(to bottom, transparent, rgba(0, 0, 0, 0.75)); +} + +.umb-media-grid__item:hover .umb-media-grid__item-overlay { + opacity: 1; + animation: fadeIn; + animation-duration: 0.2s; + animation-timing-function: ease-in-out; +} + +.umb-media-grid__item-name { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.umb-media-grid__item-non-image-name { + position: absolute; + top: 20px; + font-size: 12px; + text-align: center; + width: 100%; + padding: 0 10px; + box-sizing: border-box; +} + + +.umb-media-grid__item-icon { + color: @gray; + position: absolute; + top: 50%; + left: 50%; + font-size: 40px; + transform: translate(-50%,-50%); +} + +.umb-media-grid__actions { + position: absolute; + z-index: 2; + width: 100%; + top: 0; + padding: 10px; + box-sizing: border-box; + display: flex; + align-items: center; + justify-content: flex-end; +} + +.umb-media-grid__action { + opacity: 0; + border: 1px solid #ffffff; + width: 25px; + height: 25px; + background: rgba(0, 0, 0, 0.4); + border-radius: 50px; + box-sizing: border-box; + display: flex; + justify-content: center; + align-items: center; + color: #ffffff; + margin-left: 7px; + cursor: pointer; +} + +.umb-media-grid__action.-not-clickable { + cursor: default; +} + +.umb-media-grid__action:first-child { + margin-left: 0; +} + +.umb-media-grid__action:hover { + background: @blue; + transition: background 0.1s; +} + +.umb-media-grid__action.-selected { + opacity: 1; + background: @blue; +} + +.umb-media-grid__item:hover .umb-media-grid__action:not(.-selected), +.umb-media-grid__folder:hover .umb-media-grid__action:not(.-selected) { + opacity: 1; + animation: fadeIn; + animation-duration: 0.2s; + animation-timing-function: ease-in-out; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-sub-views.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-sub-views.less new file mode 100644 index 0000000000..b71d5fcc22 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-sub-views.less @@ -0,0 +1,40 @@ +.umb-sub-views { + + .umb-sub-views-action-bar { + margin-bottom: 40px; + } + + .umb-sub-views-action-bar .btn-link { + padding-left: 0; + padding-right: 0; + &:focus { + outline: none; + text-decoration: none; + } + } + + .umb-sub-views-nav { + float: right; + margin: 0; + .umb-sub-views-nav-item { + display: inline-block; + margin-left: 15px; + &.is-active { + .btn-link { + color: @blue !important; + } + } + } + } + + .umb-sub-views-tools { + float: left; + margin: 0; + .umb-sub-views-tool { + display: inline-block; + margin-right: 15px; + } + } + +} + 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 new file mode 100644 index 0000000000..631d7f5730 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less @@ -0,0 +1,223 @@ +// Table Styles +.umb-table { + display: flex; + flex-direction: column; + + border: 1px solid @grayLight; + + flex-wrap: nowrap; + justify-content: space-between; + + min-width: 640px; +} + +.umb-table a { + text-decoration: none; + cursor: pointer; + + &:focus { + outline: none; + text-decoration: none; + } +} + +input.umb-table__input { + margin: 0 auto; +} + + + + +// Table Head Styles +.umb-table-head { + text-transform: uppercase; + + background-color: @grayLighter; + + font-size: 11px; + font-weight: 600; +} + +.umb-table-head__link { + position: relative; + + cursor: default; + text-decoration: none; + + color: fade(@gray, 75%); + + + &:hover { + text-decoration: none; + color: fade(@gray, 75%); + } +} + +.umb-table-head__link .sortable { + &:hover { + text-decoration: none; + cursor: pointer; + color: @black; + } +} + +.umb-table-thead__icon { + position: absolute; + + padding-top: 1px; + padding-left: 3px; + + font-size: 13px; + + cursor: default; +} + +.umb-table-thead .sortable:hover { + cursor: pointer; + text-decoration: none; +} + + + + + +// Table Body Styles +.umb-table-body .umb-table-row { + color: fade(@gray, 75%); + border-top: 1px solid @grayLight; + + font-size: 13px; + + &:hover { + background-color: fade(@grayLighter, 90%); + } +} + +.umb-table-body__link { + text-decoration: none; + + &:hover { + text-decoration: underline; + } +} + +.umb-table-body__icon { + margin: 0 auto; + + font-size: 22px; + color: @blueDark; +} + +.umb-table-body__checkicon { + display: none; + font-size: 16px; +} + +.umb-table-body .umb-table__name { + color: @black; + font-size: 14px; + font-weight: bold; +} + +// Styles of no items in the listview +.umb-table-body__empty { + font-size: 16px; + text-align: center; + + color: @gray; + + padding: 20px 0; + + height: 100%; +} + +// Show checkmark when checked, hide file icon +.-selected { + .umb-table-body__fileicon { + display: none; + } + + .umb-table-body__checkicon { + display: inline-block; + } +} + + +// Styling for when item is published or not +.-published { + +} + +.-content .-unpublished { + .umb-table__name > * { + opacity: .4; + } + + .umb-table-body__icon { + opacity: .4; + } +} + +.-selected.-unpublished { + .umb-table-body__icon { + opacity: 1; + } +} + + + + + + +// Table Row Styles +.umb-table-row { + display: flex; + flex-flow: row nowrap; + align-items: center; + + user-select: none; +} + +.umb-table-row.-selected, +.umb-table-row.-selected:hover { + background-color: fade(@blueDark, 4%); +} + + + + +// Table Cell Styles +.umb-table-cell { + display: flex; + flex-flow: row nowrap; + flex: 1 1 1%; //NOTE 1% is a Internet Explore hack, so that cells don't collapse + + position: relative; + + margin: auto 14px; + padding: 6px 2px; + + text-align: left; +} + +.umb-table-cell > * { + overflow: hidden; + white-space: nowrap; //NOTE Disable/Enable this to keep textstring on one line + text-overflow: ellipsis; + + cursor: default; +} + +.umb-table-cell:first-of-type { + flex: 0 0 25px; + + margin: 0 0 0 15px; + padding: 15px 0; +} + + +// Increases the space for the name cell +.umb-table__name { + flex: 1 1 25%; + max-width: 25%; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-tabs.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-tabs.less new file mode 100644 index 0000000000..df5306c86d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-tabs.less @@ -0,0 +1,15 @@ +.umb-nav-tabs { + position: absolute; + z-index: 10; +} + +.umb-nav-tabs.-padding-left { + padding-left: 20px; +} + +.umb-tab-content { + padding-top: 20px; + position: relative; + top: 22px; + border-top: 1px solid @grayLight; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/forms.less b/src/Umbraco.Web.UI.Client/src/less/forms.less index 1091a95ea8..056dc0fc8a 100644 --- a/src/Umbraco.Web.UI.Client/src/less/forms.less +++ b/src/Umbraco.Web.UI.Client/src/less/forms.less @@ -87,6 +87,10 @@ form { margin: 0 0 @baseLineHeight; } +form.-no-margin-bottom { + margin-bottom: 0; +} + fieldset { padding: 0; margin: 0; @@ -167,6 +171,12 @@ input[type="color"], vertical-align: middle; } +input.-full-width-input { + width: 100%; + box-sizing: border-box; + padding: 4px 6px; +} + // Reset appearance properties for textual inputs and textarea // Declare width for legacy (can't be on input[type=*] selectors or it's too specific) input, @@ -302,6 +312,12 @@ textarea { min-height: @baseLineHeight; // clear the floating input if there is no label text padding-left: 20px; } + +.radio.no-indent, +.checkbox.no-indent { + padding-left: 0; +} + .radio input[type="radio"], .checkbox input[type="checkbox"] { float: left; @@ -423,44 +439,18 @@ input[type="checkbox"][readonly] { //SD: NOTE: Had to change these to use our 'form' prefixed colors since we cannot -// share colors with the notifications/alerts. Also had to change them so that +// share colors with the notifications/alerts. Also had to change them so that // we do not show any errors unless the containing element has the show-validation // class assigned. // FORM FIELD FEEDBACK STATES // -------------------------- -// Warning -.show-validation.ng-invalid .control-group.warning { - .formFieldState(@formWarningText, @formWarningText, @formWarningBackground); -} // Error -.show-validation.ng-invalid .control-group.error { +.show-validation.ng-invalid .control-group.error, +.show-validation.ng-invalid .umb-panel-header-content-wrapper { .formFieldState(@formErrorText, @formErrorText, @formErrorBackground); } -// Success -.show-validation.ng-invalid .control-group.success { - .formFieldState(@formSuccessText, @formSuccessText, @formSuccessBackground); -} -// Success -.show-validation.ng-invalid .control-group.info { - .formFieldState(@formInfoText, @formInfoText, @formInfoBackground); -} - -// HTML5 invalid states -// Shares styles with the .control-group.error above - -.show-validation input:focus:invalid, -.show-validation textarea:focus:invalid, -.show-validation select:focus:invalid { - color: @formErrorText; - border-color: #ee5f5b; - &:focus { - border-color: darken(#ee5f5b, 10%); - @shadow: 0 0 6px lighten(#ee5f5b, 20%); - .box-shadow(@shadow); - } -} //val-highlight directive styling .highlight-error { @@ -735,6 +725,11 @@ input.search-query { margin-bottom: @baseLineHeight / 2; } +//modifier for control group +.control-group.-no-margin { + margin-bottom:0; +} + // Legend collapses margin, so next element is responsible for spacing legend + .control-group { margin-top: @baseLineHeight; @@ -794,3 +789,17 @@ legend + .control-group { position: relative; z-index: 1000; } + + + +@media (max-width: 767px) { + + // Labels on own row + .form-horizontal .control-label { + width: 100%; + } + .form-horizontal .controls { + margin-left: 0; + } + +} diff --git a/src/Umbraco.Web.UI.Client/src/less/forms/umb-validation-label.less b/src/Umbraco.Web.UI.Client/src/less/forms/umb-validation-label.less new file mode 100644 index 0000000000..52ea313829 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/forms/umb-validation-label.less @@ -0,0 +1,44 @@ +.umb-validation-label { + position: relative; + padding: 1px 5px; + background: @red; + color: white; + font-size: 10px; + line-height: 1.5em; +} + +.umb-validation-label:after { + bottom: 100%; + left: 10px; + border: solid transparent; + content: " "; + height: 0; + width: 0; + position: absolute; + pointer-events: none; + border-color: rgba(255, 255, 255, 0); + border-bottom-color: @red; + border-width: 4px; + margin-left: -4px; +} + +.umb-validation-label.-arrow-left { + margin-left: 10px; +} + +.umb-validation-label.-arrow-left:after { + right: 100%; + top: 50%; + left: auto; + bottom: auto; + border: solid transparent; + content: " "; + height: 0; + width: 0; + position: absolute; + pointer-events: none; + border-color: rgba(255, 255, 255, 0); + border-right-color: @red; + border-width: 4px; + margin-top: -4px; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/gridview.less b/src/Umbraco.Web.UI.Client/src/less/gridview.less index 29a0ac9454..db3dbee640 100644 --- a/src/Umbraco.Web.UI.Client/src/less/gridview.less +++ b/src/Umbraco.Web.UI.Client/src/less/gridview.less @@ -5,7 +5,7 @@ overflow-y:hidden!important; } -IFRAME {overflow:hidden;} +.usky-grid IFRAME {overflow:hidden;} // Sortabel @@ -279,12 +279,12 @@ IFRAME {overflow:hidden;} .usky-grid .usky-control-inner.selectedControl , .usky-grid .usky-row-inner.selectedRow{ border: 1px dashed @grayLight; - + > ins.item-label { display: block; z-index:100000; } -} +} @@ -322,12 +322,12 @@ IFRAME {overflow:hidden;} padding-bottom: 30px; position: relative; background-color: white; - border: 4px dashed @grayLight; + border: 4px dashed @grayLighter; text-align: center; text-align: -moz-center; } .usky-grid .usky-editor-placeholder i{ - color: @grayLight; + color: @grayLighter; font-size: 85px; line-height: 85px; display: block; @@ -380,7 +380,7 @@ IFRAME {overflow:hidden;} border-radius: 200px; background: rgba(255,255,255, 1); border:1px solid rgb(182, 182, 182); - margin: 2px; + margin: 2px; } .usky-grid .iconBox span.prompt { @@ -464,7 +464,7 @@ IFRAME {overflow:hidden;} // TINYMCE EDITOR // ------------------------- .usky-grid .mce-panel { - border:none !important; + border: none !important; clear:both; } @@ -499,6 +499,9 @@ IFRAME {overflow:hidden;} background-color: #F7F7F7 !important; } +.usky-cell-rte{ + border: 1px solid @grayLighter; +} // MEDIA EDITOR // ------------------------- diff --git a/src/Umbraco.Web.UI.Client/src/less/listview.less b/src/Umbraco.Web.UI.Client/src/less/listview.less index f033ee2d5b..27971007ae 100644 --- a/src/Umbraco.Web.UI.Client/src/less/listview.less +++ b/src/Umbraco.Web.UI.Client/src/less/listview.less @@ -13,9 +13,9 @@ box-shadow: 0 5px 10px rgba(0, 0, 0, 0); } -.dropdown-menu > li > a:hover, -.dropdown-menu > li > a:focus, -.dropdown-submenu:hover > a, +.dropdown-menu > li > a:hover, +.dropdown-menu > li > a:focus, +.dropdown-submenu:hover > a, .dropdown-submenu:focus > a { color: @black; background: @grayLighter; @@ -32,27 +32,49 @@ } .umb-sub-header { - padding: 0 0 20px 0 + padding: 0 0 20px 0; +} + +.umb-sub-header .header-content-right { + float: right; +} + +/* listview search */ +.form-search { + .inner-addon { + position: relative; + + [class^="icon-"], [class*=" icon-"] { + position: absolute; + padding: 5px 8px; + pointer-events: none; + top: 0; + } + + input[type="text"] { + width: 190px; + } + } + + /* align icon */ + .left-addon [class^="icon-"], .left-addon [class*=" icon-"] { left: 0; right: inherit; } + .right-addon [class^="icon-"], .right-addon [class*=" icon-"] { right: 0; left: inherit; } + + /* add padding */ + .left-addon input[type="text"] { padding-left: 30px !important; padding-right: 6px; } + .right-addon input[type="text"] { padding-right: 30px; padding-left: 6px !important; } } .umb-listview table form { position: relative; - margin: 0 -} - -.umb-listview table form .icon-search { - position: absolute; - top: 8px; - left:5px; - color: @gray; - cursor:pointer; + margin: 0; } .umb-listview table input[type="text"] { background: none; - border: none; -webkit-transition: all .5s; -moz-transition: all .5s; + -o-transition: all .5s; transition: all .5s; width: 60px; padding: 4px 0 4px 20px; @@ -102,7 +124,7 @@ } .umb-listview .selected i.icon, .umb-listview tbody tr:hover i.icon{display: none} -.umb-listview .selected input[type="checkbox"], +.umb-listview .selected input[type="checkbox"], .umb-listview tr:hover input[type="checkbox"]{display: inline-block !important;} .umb-listview .inactive{color: @grayLight;} @@ -112,7 +134,7 @@ font-size: 11px; font-weight: 600; text-transform: uppercase; - background-color: @grayLighter; + background-color: @white; } .umb-listview table tfoot { @@ -159,6 +181,10 @@ color: #b0b0b0 } +.umb-listview .pagination { + text-align: center; +} + .umb-listview .pagination ul { -webkit-border-radius: 0px; -moz-border-radius: 0px; @@ -173,7 +199,7 @@ border:none; padding: 8px 4px 2px 4px; background: none; - font-size: 11px; + font-size: 12px; color: #b0b0b0 } @@ -220,3 +246,87 @@ .table-striped tbody i:hover { display: none !important } + +/* ---------- LAYOUTS ---------- */ + +.list-view-layouts { + +} + +.list-view-layout { + display: flex; + align-items: center; + padding: 10px 15px; + background: @grayLighter; + border-radius: 5px; + margin-bottom: 1px; +} + +.list-view-layout__sort-handle { + font-size: 14px; + color: @grayLight; + margin-right: 15px; +} + +.list-view-layout__name { + flex: 5; + font-weight: bold; + margin-right: 15px; + display: flex; + align-content: center; + flex-wrap: wrap; + line-height: 1.2em; +} + +.list-view-layout__name-text { + margin-right: 3px; +} + +.list-view-layout__system { + font-size: 10px; + font-weight: normal; +} + +.list-view-layout__path { + flex: 10; + margin-right: 15px; +} + +.list-view-layout__icon { + font-size: 18px; + margin-right: 10px; + vertical-align: middle; + border: 1px solid @grayLight; + border-radius: 5px; + background: #ffffff; + padding: 6px 8px; + display: block; +} + +.list-view-layout__icon:hover, +.list-view-layout__icon:focus, +.list-view-layout__icon:active { + text-decoration: none; +} + +.list-view-layout__remove-layout { + flex: 2; + text-align: right; + font-size: 20px; +} + +.list-view-add-layout { + margin-top: 10px; + color: @blue; + border: 1px dashed #d9d9d9; + border-radius: 5px; + display: flex; + align-items: center; + justify-content: center; + padding: 5px 0; + box-sizing: border-box; +} + +.list-view-add-layout:hover { + text-decoration: none; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/main.less b/src/Umbraco.Web.UI.Client/src/less/main.less index 379f65b10a..bfcb3c9220 100644 --- a/src/Umbraco.Web.UI.Client/src/less/main.less +++ b/src/Umbraco.Web.UI.Client/src/less/main.less @@ -34,11 +34,6 @@ h5{ margin-top: 15px; } - - - - - /* MISC FORM ELEMENTS */ .umb-form-actions { background: none; @@ -106,6 +101,11 @@ h5{ margin-bottom: 15px !important; } +.umb-control-group.-no-border { + border: none; +} + + /*COMPACT MODE */ .compact .umb-pane{margin: 0px 0px 15px 0px;} .compact .umb-control-group { @@ -133,7 +133,7 @@ h5{ .umb-control-group label.control-label { text-align: left } -.umb-control-group label .help-block, +.umb-control-group label .help-block, .umb-control-group label small { font-size: 11px; color: #a0a0a0; @@ -146,13 +146,23 @@ h5{ .controls-row { padding-top: 5px; - margin-left: 240px !important; + margin-left: 240px; } + +.umb-user-panel .controls-row { + margin-left: 0px; +} + .controls-row label { display: inline-block } -.hidelabel > div > .controls-row, .hidelabel > .controls-row { +.block-form .controls-row { + margin-left: 0; + padding-top: 0; +} + +.hidelabel > div > .controls-row, .hidelabel > .controls-row, .hidelabel .controls { padding: 0px; border: none; margin: 0px !important; @@ -189,29 +199,10 @@ h5{ .umb-dashboard-control a{text-decoration: underline;} -.umb-dashboard-control +.umb-dashboard-control iframe{ position: absolute; display: block; width: 99%; height: 99%; overflow: auto !important;} -/* NOTIFICATIONS */ -.umb-notification ul { - list-style: none; - margin: 0; - padding-left: 100px; - padding-right: 20px; - position: relative; -} -.umb-notification li { - padding: 5px 30px 5px 20px; - text-shadow: none; - font-size: 12px; - margin: auto; - margin-top: 5px; - border: none; - border-radius: 5; - position: relative; -} - /* TABLE */ .umb-table { table-layout: fixed; @@ -234,16 +225,16 @@ table thead a { .umb-table tbody.ui-sortable tr.ui-sortable-helper { background-color: @sortableHelperBg; - border: none; + border: none; } -.umb-table tbody.ui-sortable tr.ui-sortable-helper td +.umb-table tbody.ui-sortable tr.ui-sortable-helper td { border:none; } .umb-table tbody.ui-sortable tr.ui-sortable-placeholder { background-color: @sortablePlaceholderBg; - border:none; + border:none; } .umb-table tbody.ui-sortable tr.ui-sortable-placeholder td @@ -363,7 +354,7 @@ table thead a { background: @inputBorder; height: 1px; margin: 20px 0 0 0; - width: 82%; + width: 82%; float: left; } @@ -432,7 +423,7 @@ table thead a { color:@green; } -// Loading Animation +// Loading Animation // ------------------------ .umb-loader{ @@ -518,11 +509,94 @@ height:1px; } .umb-loader-wrapper { - + position: absolute; right: 0; left: 0; margin: 10px 0; - overflow: hidden; + overflow: hidden; -} \ No newline at end of file +} + +.umb-loader-wrapper.-bottom { + bottom: 0; +} + +// Helpers + +.strong { + font-weight: bold; +} + +.inline { + display: inline; +} + + +// Input label styles +// @Simon: not sure where to put this part yet +// --- TODO Needs to be divided into the right .less directories + + +// Titles for input fields +.input-label--title { + font-weight: bold; + color: @black; + + margin-bottom: 3px; +} + + +// Used for input checkmark fields +.input-label--small { + display: inline; + + font-size: 12px; + font-weight: bold; + color: fade(@black, 70); + + &:hover { + color: @black; + } +} + +input[type=checkbox]:checked + .input-label--small { + color: @blue; +} + + +// Use this for headers in the panels +.panel-dialog--header { + border-bottom: 1px solid @gray; + + margin: 10px 0; + padding-bottom: 10px; + + font-size: @fontSizeLarge; + font-weight: bold; + line-height: 20px; +} + +.umb-empty-state-text { + font-size: @fontSizeMedium; + line-height: 1.8em; + color: @grayMed; + text-align: center; +} + +.umb-empty-state-text.-small { + font-size: @fontSizeSmall; +} + +.umb-empty-state-text.-large { + font-size: @fontSizeLarge; +} + +.umb-empty-state-text.-center { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%,-50%); + width: 80%; + max-width: 400px; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/mixins.less b/src/Umbraco.Web.UI.Client/src/less/mixins.less index e1f9170424..38df48a457 100644 --- a/src/Umbraco.Web.UI.Client/src/less/mixins.less +++ b/src/Umbraco.Web.UI.Client/src/less/mixins.less @@ -182,12 +182,6 @@ select.ng-invalid, textarea.ng-invalid { border-color: @borderColor; - .box-shadow(inset 0 1px 1px rgba(0,0,0,.075)); // Redeclare so transitions work - &:focus { - border-color: darken(@borderColor, 10%); - @shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 6px lighten(@borderColor, 20%); - .box-shadow(@shadow); - } } // Give a small background color for input-prepend/-append .input-prepend .add-on, @@ -196,6 +190,18 @@ background-color: @backgroundColor; border-color: @textColor; } + //SD: We could do this but need to get the colors right + /*input.ng-invalid { + &:-moz-placeholder { + color: lighten(@textColor, 50%) !important; + } + &:-ms-input-placeholder { + color: lighten(@textColor, 50%) !important; + } + &::-webkit-input-placeholder { + color: lighten(@textColor, 50%) !important; + } + }*/ } // CSS3 PROPERTIES diff --git a/src/Umbraco.Web.UI.Client/src/less/modals.less b/src/Umbraco.Web.UI.Client/src/less/modals.less index 541d3c1a95..0c2c2f07ad 100644 --- a/src/Umbraco.Web.UI.Client/src/less/modals.less +++ b/src/Umbraco.Web.UI.Client/src/less/modals.less @@ -20,7 +20,6 @@ font-size: @fontSizeLarge; font-weight: 400; padding-top: 10px !important; - text-transform: capitalize !important; } .umb-modalcolumn-body { diff --git a/src/Umbraco.Web.UI.Client/src/less/pages/document-type-editor.less b/src/Umbraco.Web.UI.Client/src/less/pages/document-type-editor.less new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/Umbraco.Web.UI.Client/src/less/login.less b/src/Umbraco.Web.UI.Client/src/less/pages/login.less similarity index 84% rename from src/Umbraco.Web.UI.Client/src/less/login.less rename to src/Umbraco.Web.UI.Client/src/less/pages/login.less index d01dec8733..79f3797197 100644 --- a/src/Umbraco.Web.UI.Client/src/less/login.less +++ b/src/Umbraco.Web.UI.Client/src/less/pages/login.less @@ -1,73 +1,82 @@ -// Login -// ------------------------- - -.login-overlay { - width: 100%; - height: 100%; - background: @blackLight url(../img/application/logo.png) no-repeat 25px 30px fixed !important; - background-size: 30px 30px !important; - color: @white; - position: absolute; - z-index: 2000; - top: 0px; - left: 0px; - margin: 0 !Important; - padding: 0; - border-radius: 0; -} - -.login-overlay .umb-modalcolumn { - background: none; - border: none; -} - -.login-overlay .form { - display: block; - padding-top: 100px; - padding-left: 165px; - width: 370px; - text-align: right; -} - -.login-overlay h1 { - display: block; - text-align: right; - color: @white; - font-size: 18px; - font-weight: normal; -} - -.login-overlay .alert.alert-error { - display: inline-block; - padding-right: 6px; - padding-left: 6px; - margin-top: 10px; - text-align: center; -} - -#hrOr { - height: 30px; - text-align: center; - position: relative; - padding-top: 20px; -} - -#hrOr hr { - margin: 0px; - border: none; - background-color: @gray; - height: 1px; -} - -#hrOr div { - background-color: black; - position: relative; - top: -16px; - border: 1px solid @gray; - padding: 4px; - border-radius: 50%; - width: 20px; - height: 20px; - margin: auto; - color: @grayLight; -} +// Login +// ------------------------- + +.login-overlay { + width: 100%; + height: 100%; + background: @blackLight url(../img/application/logo.png) no-repeat 25px 30px fixed !important; + background-size: 30px 30px !important; + color: @white; + position: absolute; + z-index: 10000; + top: 0px; + left: 0px; + margin: 0 !Important; + padding: 0; + border-radius: 0; +} + +.login-overlay .umb-modalcolumn { + background: none; + border: none; +} + +.login-overlay .form { + position:fixed; + display: block; + top: 100px; + left: 165px; + width: 370px; + text-align: right; +} + +.login-overlay h1 { + display: block; + text-align: right; + color: @white; + font-size: 18px; + font-weight: normal; +} + +.login-overlay .alert.alert-error { + display: inline-block; + padding-right: 6px; + padding-left: 6px; + margin-top: 10px; + text-align: center; +} + +@media (max-width: 565px) { + // Remove padding on login-form on smaller devices + .login-overlay .form { + left: inherit; + right:25px; + } +} + +#hrOr { + height: 30px; + text-align: center; + position: relative; + padding-top: 20px; +} + +#hrOr hr { + margin: 0px; + border: none; + background-color: @gray; + height: 1px; +} + +#hrOr div { + background-color: black; + position: relative; + top: -16px; + border: 1px solid @gray; + padding: 4px; + border-radius: 50%; + width: 20px; + height: 20px; + margin: auto; + color: @grayLight; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/panel.less b/src/Umbraco.Web.UI.Client/src/less/panel.less index 0a0d2c213f..36c2812cc7 100644 --- a/src/Umbraco.Web.UI.Client/src/less/panel.less +++ b/src/Umbraco.Web.UI.Client/src/less/panel.less @@ -1,19 +1,27 @@ // Panel // ------------------------- -.umb-panel{ - background: white; - position: absolute; - top: 0px; bottom: 0px; left: 0px; right: 0px;} +.umb-panel { + background: white; + position: absolute; + top: 0px; + bottom: 0px; + left: 0px; + right: 0px; +} + +.umb-panel-nobody { + padding-top: 100px; + overflow: auto; +} -.umb-panel-nobody{padding-top: 100px; overflow: auto;} .umb-panel-header { - background: @grayLighter; - border-bottom: 1px solid @grayLight; - position: absolute; - height: 99px; - top: 0px; - right: 0px; - left: 0px; + background: @grayLighter; + border-bottom: 1px solid @grayLight; + position: absolute; + height: 99px; + top: 0px; + right: 0px; + left: 0px; } .umb-panel-body { @@ -25,6 +33,7 @@ clear: both; overflow: auto; } + .umb-panel-body.no-header { top: 20px; } @@ -33,82 +42,93 @@ bottom: 90px; } +.umb-mediapicker-upload { + display: -ms-flexbox; + display: -webkit-box; + display: -webkit-flex; + display: flex; + + .form-search { + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; + } + + .upload-button { + margin-left: 16px; + } +} + .umb-panel.editor-breadcrumb .umb-panel-body, .umb-panel.editor-breadcrumb .umb-bottom-bar { bottom: 31px !important; } -/* -.umb-tab-buttons.umb-bottom-bar { - bottom: 50px !important; -}*/ - .umb-panel-header .umb-headline, .umb-panel-header h1 { - font-size: 16px; - border: none; - background: none; - margin: 15px 0 0 20px; - padding: 3px 5px; - line-height: 1.4; - height: auto; - width: 100%; - border: 1px solid @grayLighter; + font-size: 16px; + border: none; + background: none; + margin: 15px 0 0 20px; + padding: 3px 5px; + line-height: 1.4; + height: auto; + width: 100%; + border: 1px solid @grayLighter; } -.umb-panel-header .umb-headline:focus,.umb-panel-header .umb-headline:active { - border: 1px solid @grayLight; - background-color: @white; +.umb-panel-header .umb-headline:focus, .umb-panel-header .umb-headline:active { + border: 1px solid @grayLight; + background-color: @white; } -.umb-headline-editor-wrapper{ - position: relative; +.umb-headline-editor-wrapper { + position: relative; } -.umb-headline-editor-wrapper .help-inline{ - right: 0px; - top: 25px; - position: absolute; - font-size: 10px; - color: @red; - } - -.umb-panel-header .umb-nav-tabs{ - bottom: -1px; - position: absolute; - padding: 0px 0px 0px 20px; +.umb-headline-editor-wrapper .help-inline { + right: 0px; + top: 25px; + position: absolute; + font-size: 10px; + color: @red; } - .umb-panel-header p { - margin:0px 20px; + margin: 0px 20px; } -.umb-btn-toolbar .dimmed, .umb-dimmed{ - opacity: 0.6; +.umb-btn-toolbar .dimmed, .umb-dimmed { + opacity: 0.6; } -.umb-headline-editor-wrapper input { - background: none; - border: none; - margin: -6px 0 0 0; - padding: 0 0 2px 0; - border-radius: 0; - line-height: normal; - border: 1px solid transparent; - color: @black; - letter-spacing: -0.01em -} -.umb-headline-editor-wrapper input.ng-invalid { - color: @red; +.umb-headline-editor-wrapper input { + background: none; + border: none; + margin: -6px 0 0 0; + padding: 0 0 2px 0; + border-radius: 0; + line-height: normal; + border: 1px solid transparent; + color: @black; + letter-spacing: -0.01em; } -.umb-headline-editor-wrapper input.ng-invalid::-webkit-input-placeholder {color: @red; line-height: 22px;} -.umb-headline-editor-wrapper input.ng-invalid::-moz-placeholder {color: @red; line-height: 22px;} -.umb-headline-editor-wrapper input.ng-invalid:-ms-input-placeholder {color: @red; line-height: 22px;} +.umb-headline-editor-wrapper input.ng-invalid { + color: @red; +} +.umb-headline-editor-wrapper input.ng-invalid::-moz-placeholder, +.umb-headline-editor-wrapper input.ng-invalid:-ms-input-placeholder, +.umb-headline-editor-wrapper input.ng-invalid::-webkit-input-placeholder { + color: @red; + line-height: 22px; +} + +/* .umb-panel-header i { - font-size: 13px; - vertical-align: middle; + font-size: 13px; + vertical-align: middle; } +*/ .umb-panel-header-meta { height: 50px; @@ -120,65 +140,85 @@ } .umb-panel-footer { - margin: 0; - padding: 20px; - z-index: 999; - position: absolute; - bottom: 0px; - left: 0px; - right: 0px; + margin: 0; + padding: 20px; + z-index: 999; + position: absolute; + bottom: 0px; + left: 0px; + right: 0px; } /* Publish */ .umb-btn-toolbar .dropdown-menu { - right: 0; - left: auto; - border-radius: @tabsBorderRadius; - box-shadow: none; - padding: 0 - } - - .umb-btn-toolbar .dropdown-menu small { - background: #fef9db; - display: block; - padding: 10px 20px; - } - - .umb-btn-toolbar .dropdown-menu .btn { - margin: 20px 29px; - width: 80px - } - -/* tab buttons */ -.umb-bottom-bar{ - background: white; - - -webkit-box-shadow: 0px -18px 20px rgba(255, 255, 255, 1); - -moz-box-shadow: 0px -18px 20px rgba(255, 255, 255, 1); - box-shadow: 0px -18px 20px rgba(255, 255, 255, 1); - - border-top: 1px solid @grayLighter; - - padding: 10px 0 10px 0; - - position: fixed; - bottom: 0px; - left: 100px; - right: 20px; - z-index: 6010; -}; - -@media (min-width: 1101px) { - .umb-bottom-bar {left: 460px;} + right: 0; + left: auto; + border-radius: @tabsBorderRadius; + box-shadow: none; + padding: 0; + z-index: 6020; } -.umb-tab-buttons{padding-left: 240px;} -.umb-tab-pane{padding-bottom: 90px} +.umb-btn-toolbar .dropdown-menu small { + background: #fef9db; + display: block; + padding: 10px 20px; +} -.tab-content{overflow: visible; } +.umb-btn-toolbar .dropdown-menu .btn  { + margin: 20px 29px; + width: 80px; +} -.umb-panel-footer-nav{ +/* tab buttons */ +.umb-bottom-bar { + background: white; + -webkit-box-shadow: 0px -18px 20px rgba(255, 255, 255, 1); + -moz-box-shadow: 0px -18px 20px rgba(255, 255, 255, 1); + box-shadow: 0px -18px 20px rgba(255, 255, 255, 1); + border-top: 1px solid @grayLighter; + padding: 10px 0 10px 0; + position: fixed; + bottom: 0; + left: 100px; + right: 40px; + z-index: 6010; + + @media (min-width: 1101px) { + left: 460px; + } + + @media (max-width: 767px) { + left: 80px; + } + + @media (max-width: 500px) { + left: 60px; + } +} + +.umb-tab-buttons { + padding-left: 0; + + > .btn-group:not([style*="display:none"]):not([style*="display: none"]) { + margin-left: 0; + } + + @media (min-width: 768px) { + padding-left: 180px; + } +} + +.umb-tab-pane { + padding-bottom: 90px; +} + +.tab-content { + overflow: visible; +} + +.umb-panel-footer-nav { position: absolute; bottom: 0px; height: 30px; @@ -192,100 +232,265 @@ } .umb-panel-footer-nav li a { - border-radius: 0; - display: block; - float: left; - height: 30px; - background: @grayLighter; - text-align: center; - padding: 8px 0px 8px 30px; - position: relative; - margin: 0 1px 0 0; - text-decoration: none; - color: @gray; - font-size: 11px; + border-radius: 0; + display: block; + float: left; + height: 30px; + background: @grayLighter; + text-align: center; + padding: 8px 0px 8px 30px; + position: relative; + margin: 0 1px 0 0; + text-decoration: none; + color: @gray; + font-size: 11px; } .umb-panel-footer-nav li a:after { - content: ""; - border-top: 16px solid transparent; - border-bottom: 16px solid transparent; - border-left: 16px solid @grayLighter; - position: absolute; right: -16px; top: 0; - z-index: 1; -} - -.umb-panel-footer-nav li a:before { - content: ""; - border-top: 16px solid transparent; - border-bottom: 16px solid transparent; - border-left: 16px solid @grayLight; - position: absolute; left: 0; top: 0; + content: ""; + border-top: 16px solid transparent; + border-bottom: 16px solid transparent; + border-left: 16px solid @grayLighter; + position: absolute; + right: -16px; + top: 0; + z-index: 1; } -.umb-panel-footer-nav li:first-child a{ - padding-left: 20px; +.umb-panel-footer-nav li a:before { + content: ""; + border-top: 16px solid transparent; + border-bottom: 16px solid transparent; + border-left: 16px solid @grayLight; + position: absolute; + left: 0; + top: 0; +} + +.umb-panel-footer-nav li:first-child a { + padding-left: 20px; } .umb-panel-footer-nav li:first-child a:before { - display: none; + display: none; } .umb-panel-footer-nav li:last-child a:after { - display: none; + display: none; } +// Utility classes - - - // Utility classes - - //SD: Had to add these because if we want to use the bootstrap text colors - // in a panel/editor they'll all show up as white on white - so we need to use the - // form styles +//SD: Had to add these because if we want to use the bootstrap text colors +// in a panel/editor they'll all show up as white on white - so we need to use the +// form styles .umb-dialog .muted, -.umb-panel .muted { color: @grayLight; } +.umb-panel .muted { + color: @grayLight; +} .umb-dialog a.muted:hover, .umb-dialog a.muted:focus, .umb-panel a.muted:hover, -.umb-panel a.muted:focus { color: darken(@grayLight, 10%); } +.umb-panel a.muted:focus { + color: darken(@grayLight, 10%); +} .umb-dialog .text-warning, -.umb-panel .text-warning { color: @formWarningText; } +.umb-panel .text-warning { + color: @formWarningText; +} .umb-dialog a.text-warning:hover, .umb-dialog a.text-warning:focus, .umb-panel a.text-warning:hover, -.umb-panel a.text-warning:focus { color: darken(@formWarningText, 10%); } +.umb-panel a.text-warning:focus { + color: darken(@formWarningText, 10%); +} .umb-dialog .text-error, -.umb-panel .text-error { color: @formErrorText; } +.umb-panel .text-error { + color: @formErrorText; +} .umb-dialog a.text-error:hover, .umb-dialog a.text-error:focus, .umb-panel a.text-error:hover, -.umb-panel a.text-error:focus { color: darken(@formErrorText, 10%); } +.umb-panel a.text-error:focus { + color: darken(@formErrorText, 10%); +} .umb-dialog .text-info, -.umb-panel .text-info { color: @formInfoText; } +.umb-panel .text-info { + color: @formInfoText; +} .umb-dialog a.text-info:hover, .umb-dialog a.text-info:focus, .umb-panel a.text-info:hover, -.umb-panel a.text-info:focus { color: darken(@formInfoText, 10%); } +.umb-panel a.text-info:focus { + color: darken(@formInfoText, 10%); +} .umb-dialog .text-success, -.umb-panel .text-success { color: @formSuccessText; } +.umb-panel .text-success { + color: @formSuccessText; +} .umb-dialog a.text-success:hover, .umb-dialog a.text-success:focus, .umb-panel a.text-success:hover, -.umb-panel a.text-success:focus { color: darken(@formSuccessText, 10%); } +.umb-panel a.text-success:focus { + color: darken(@formSuccessText, 10%); +} .external-logins form { margin:0; } .external-logins button { margin:5px; -} +} + +/* --------- UMB PANEL HEADER ---------- */ + +.umb-panel-header-content-wrapper { + display: flex; + flex-direction: column; + height: 100px; + padding: 0 20px; +} + +.umb-panel-header-content { + display: flex; + align-items: center; + flex: 1; +} + +.umb-panel-header-content.-top-position { + position: relative; + top: -12px; +} + +.umb-panel-header-left-side { + display: flex; + flex: 1; + flex-direction: row; +} + +.umb-panel-header-icon { + cursor: pointer; + margin-right: 5px; + height: 55px; + display: flex; + justify-content: center; + align-items: center; + background: #ffffff; + border: 1px solid #ECECEC; + border-radius: 5px; + animation: fadeIn 0.5s; + flex: 0 0 55px; +} + +.umb-panel-header-title-wrapper { + position: relative; + width: 80%; +} + +.umb-panel-header-alias { + position: absolute; + top: 5px; + right: 10px; +} + +.umb-panel-header-alias .umb-locked-field { + display: flex; + align-items: center; +} + +.umb-panel-header-alias .umb-locked-field, +.umb-panel-header-alias .umb-locked-field .umb-locked-field__wrapper { + margin-bottom: 0; +} + +.umb-panel-header-alias .umb-validation-label:after { + visibility: hidden; +} + +.umb-panel-header-alias .umb-locked-field:after { + display: none; +} + +.umb-panel-header-icon.-placeholder { + border: 1px dashed @grayLight; +} + +.umb-panel-header-icon .icon { + font-size: 35px; + color: @grayLight; +} + +.umb-panel-header-icon-text { + color: @blue; + font-weight: bold; + font-size: 10px; +} + +.umb-panel-header .umb-nav-tabs { + bottom: -1px; +} + +input.umb-panel-header-name-input { + border-color: #ECECEC; + font-size: 15px; + color: #000000; + margin-bottom: 0; + font-weight: bold; + box-sizing: border-box; + height: 30px; + line-height: 30px; + width: 100%; + &:hover { + background: #ffffff; + border: 1px solid #cccccc; + } +} + +input.umb-panel-header-name-input.name-is-empty { + border: 1px dashed @grayLight; + background: #ffffff; +} + +.umb-panel-header-name { + font-size: 16px; + font-weight: bold; +} + + +input.umb-panel-header-description { + background: transparent; + border-color: transparent; + margin-bottom: 0; + font-size: 12px; + box-sizing: border-box; + height: 25px; + line-height: 25px; + width: 100%; + &:hover { + background: #ffffff; + border-color: #cccccc; + } +} + +.umb-editor-drawer-content { + display: flex; + align-items: center; + //justify-content: space-between; +} + +.umb-editor-drawer-content__right-side { + margin-left: auto; +} + +.umb-editor-drawer-content__left-side { + margin-right: auto; +} 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 01430ac2ec..082e8d079d 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -40,8 +40,16 @@ padding: 10px; } -.umb-contentpicker small a { +.umb-contentpicker small { + + &:not(:last-child) { + padding-right: 3px; + border-right: 1px solid @grayMed; + } + + a { color: @gray; + } } /* CODEMIRROR DATATYPE */ @@ -294,7 +302,90 @@ ul.color-picker li a { margin-top: 7px; width: 320px; } + .umb-cropper-gravity .viewport, .umb-cropper-gravity, .umb-cropper-imageholder { + display: inline-block; + } + .umb-cropper-imageholder { + float: left; + } + + .cropList { + display: inline-block; + position: relative; + vertical-align: top; + } + + .gravity-container .viewport { + width: 494px; + } + + .imagecropper .umb-sortable-thumbnails { + border-left: 2px solid #f8f8f8; + margin-left: 4px; + padding-left: 2px; + float: left; + width: 100px; + } + + .imagecropper .umb-sortable-thumbnails li { + display: block; + } + + .imagecropper .umb-sortable-thumbnails li.current a, .imagecropper .umb-sortable-thumbnails li.current a:hover { + background: #eeeeee; + text-decoration: none; + } + + .imagecropper .umb-sortable-thumbnails li:first-of-type { + margin-top: 0; + padding-top: 0; + } + + .imagecropper .umb-sortable-thumbnails li .crop-name, .imagecropper .umb-sortable-thumbnails li .crop-size { + display: block; + text-align: left; + font-size: 11px; + } + + .imagecropper .umb-sortable-thumbnails li .crop-size { + font-size: 10px; + font-style: italic; + color: #666; + } + + .imagecropper .umb-sortable-thumbnails li a { + display: block; + padding: 5px; + } + + .imagecropper .umb-sortable-thumbnails li a:hover { + background: #f8f8f8; + text-decoration: none; + } + + .imagecropper .umb-sortable-thumbnails li a:hover .crop-text { + text-decoration: none; + } + + .btn-crop-delete { + display: block; + text-align: left; + } + + .imagecropper .umb-sortable-thumbnails.many { + width: 210px; + } + + .imagecropper .umb-sortable-thumbnails.many li { + width: 85px; + float: left; + } + + .imagecropper .umb-sortable-thumbnails.many li:nth-child(2) { + margin-top: 0; + padding-top: 0; + } // // folder-browser @@ -422,12 +513,11 @@ ul.color-picker li a { } .umb-photo-folder .umb-non-thumbnail span{ + position: absolute; display: block; margin: auto; - /*vertically aligns */ - position: static; - top: 50%; - transform: translateY(-50%); + width: 100%; + top: 20px; } .umb-photo-folder .selected{ @@ -523,3 +613,14 @@ ul.color-picker li a { .bootstrap-datetimepicker-widget .btn{padding: 0;} .bootstrap-datetimepicker-widget .picker-switch .btn{ background: none; border: none;} .umb-datepicker .input-append .add-on{cursor: pointer;} +.umb-datepicker p {margin-top:10px;} +.umb-datepicker p a{color: @gray;} + +// +// Code mirror - even though this isn't a proprety editor right now, it could be so I'm putting the styles here +// -------------------------------------------------- + +.CodeMirror, .CodeMirror-scroll { + height: 100%; + min-height:200px; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/sections.less b/src/Umbraco.Web.UI.Client/src/less/sections.less index 9ba3855b60..f57feeb2ad 100644 --- a/src/Umbraco.Web.UI.Client/src/less/sections.less +++ b/src/Umbraco.Web.UI.Client/src/less/sections.less @@ -65,8 +65,8 @@ ul.sections:hover a span { // ------------------------- ul.sections li.avatar { - height: 67px; - padding: 30px 0 2px 0; + height: 75px; + padding: 22px 0 2px 0; text-align: center; margin: 0 0 0 -4px; border-bottom: 1px solid @grayDark; @@ -81,8 +81,8 @@ ul.sections li.avatar a { } ul.sections li.avatar a img { - border-radius: 16px; - width: 30px + border-radius: 50%; + width: 30px; } .faded ul.sections li { diff --git a/src/Umbraco.Web.UI.Client/src/less/tree.less b/src/Umbraco.Web.UI.Client/src/less/tree.less index b26778ef26..4e2e3e77e8 100644 --- a/src/Umbraco.Web.UI.Client/src/less/tree.less +++ b/src/Umbraco.Web.UI.Client/src/less/tree.less @@ -3,14 +3,13 @@ .umb-item-list { - margin: 0px; + margin: 0; width: auto; display: block } .umb-item-list li { display: block; width: auto; - display: block } @@ -20,7 +19,7 @@ // ------------------------- .umb-tree { - margin: 0px; + margin: 0; min-width: 100%; width: auto; } @@ -29,17 +28,17 @@ display: block; min-width: 100%; width: auto; - display: block } .umb-tree li.current > div, .umb-tree div.selected { background: @blue; } -.umb-tree li.current > div a.umb-options i, .umb-tree div.selected i{ +.umb-tree li.current > div a.umb-options i, .umb-tree div.selected i { background: #fff; border-color: @blue; } .umb-tree li.current > div a, -.umb-tree li.current > div i.icon{ +.umb-tree li.current > div i.icon, +.umb-tree li.current > div ins { color: white !important; background: @blue; border-color: @blue; @@ -57,8 +56,8 @@ white-space: nowrap } .umb-tree ul { - padding: 0px; - margin: 0px; + padding: 0; + margin: 0; min-width: 100%; width: 100%; //display: table @@ -111,21 +110,19 @@ display: inline-block; visibility: hidden; text-decoration: none; + font-size: 12px; } + .umb-tree li:hover ins { visibility: visible; cursor: pointer } -.umb-tree ins { - font-size: 12px; -} - .umb-tree .icon { vertical-align: middle; margin: 1px 13px 1px 0px; color: #21201C; - font-size: 15px; + font-size: 20px; } .umb-tree i.noSpr { display: inline-block; @@ -434,7 +431,7 @@ overflow:hidden; } -body.touch .umb-tree .icon{font-size: 17px;} +/*body.touch .umb-tree .icon{font-size: 19px;}*/ body.touch .umb-tree ins{font-size: 14px; visibility: visible; padding: 7px;} body.touch .umb-tree li div { padding-top: 8px; diff --git a/src/Umbraco.Web.UI.Client/src/less/variables.less b/src/Umbraco.Web.UI.Client/src/less/variables.less index ec73e62c0c..58303a7f37 100644 --- a/src/Umbraco.Web.UI.Client/src/less/variables.less +++ b/src/Umbraco.Web.UI.Client/src/less/variables.less @@ -14,6 +14,7 @@ @grayDarker: #222; @grayDark: #343434; @gray: #555; +@grayMed: #999; @grayLight: #d9d9d9; @grayLighter: #f8f8f8; @white: #fff; @@ -112,7 +113,7 @@ @btnSuccessBackground: #53a93f; @btnSuccessBackgroundHighlight: #53a93f; -@btnWarningBackground: lighten(@orange, 15%); +@btnWarningBackground: @orange; @btnWarningBackgroundHighlight: @orange; @btnDangerBackground: #ee5f5b; @@ -348,4 +349,4 @@ // SORTABLE // -------------------------------------------------- @sortableHelperBg: rgba(4, 156, 219, 0.5); -@sortablePlaceholderBg : @blue; \ No newline at end of file +@sortablePlaceholderBg : @blue; \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/loader.js b/src/Umbraco.Web.UI.Client/src/loader.dev.js similarity index 71% rename from src/Umbraco.Web.UI.Client/src/loader.js rename to src/Umbraco.Web.UI.Client/src/loader.dev.js index 32def61618..2e164f0eef 100644 --- a/src/Umbraco.Web.UI.Client/src/loader.js +++ b/src/Umbraco.Web.UI.Client/src/loader.dev.js @@ -2,27 +2,25 @@ LazyLoad.js( [ 'lib/jquery/jquery.min.js', 'lib/jquery-ui/jquery-ui.min.js', + /* 1.1.5 */ 'lib/angular/1.1.5/angular.min.js', 'lib/angular/1.1.5/angular-cookies.min.js', - 'lib/angular/1.1.5/angular-mobile.min.js', - 'lib/angular/1.1.5/angular-mocks.js', + 'lib/angular/1.1.5/angular-sanitize.min.js', 'lib/angular/angular-ui-sortable.js', 'lib/angular-dynamic-locale/tmhDynamicLocale.min.js', - - /* App-wide file-upload helper */ - 'lib/jquery-file-upload/jquery.fileupload.js', - 'lib/jquery-file-upload/jquery.fileupload-process.js', - 'lib/jquery-file-upload/jquery.fileupload-angular.js', 'lib/bootstrap/js/bootstrap.2.3.2.min.js', - 'lib/underscore/underscore-min.js', + 'lib/bootstrap-tabdrop/bootstrap-tabdrop.min.js', 'lib/umbraco/Extensions.js', + 'lib/umbraco/NamespaceManager.js', + 'lib/umbraco/LegacyUmbClientMgr.js', + 'lib/umbraco/LegacySpeechBubble.js', 'js/umbraco.servervariables.js', 'js/app.dev.js', diff --git a/src/Umbraco.Web.UI.Client/src/routes.js b/src/Umbraco.Web.UI.Client/src/routes.js index 3f4b28597d..bd122a6c7c 100644 --- a/src/Umbraco.Web.UI.Client/src/routes.js +++ b/src/Umbraco.Web.UI.Client/src/routes.js @@ -1,11 +1,11 @@ app.config(function ($routeProvider) { - - /** This checks if the user is authenticated for a route and what the isRequired is set to. + + /** This checks if the user is authenticated for a route and what the isRequired is set to. Depending on whether isRequired = true, it first check if the user is authenticated and will resolve successfully otherwise the route will fail and the $routeChangeError event will execute, in that handler we will redirect to the rejected path that is resolved from this method and prevent default (prevent the route from executing) */ var canRoute = function(isRequired) { - + return { /** Checks that the user is authenticated, then ensures that are requires assets are loaded */ isAuthenticatedAndReady: function ($q, userService, $route, assetsService, appState) { @@ -17,12 +17,12 @@ app.config(function ($routeProvider) { deferred.resolve(true); return deferred.promise; } - + userService.isAuthenticated() .then(function () { assetsService._loadInitAssets().then(function() { - + //This could be the first time has loaded after the user has logged in, in this case // we need to broadcast the authenticated event - this will be handled by the startup (init) // handler to set/broadcast the ready state @@ -59,7 +59,7 @@ app.config(function ($routeProvider) { //this will resolve successfully so the route will continue deferred.resolve(true); } - }); + }); return deferred.promise; } }; @@ -85,16 +85,16 @@ app.config(function ($routeProvider) { $routeProvider .when('/login', { templateUrl: 'views/common/login.html', - //ensure auth is *not* required so it will redirect to / + //ensure auth is *not* required so it will redirect to / resolve: canRoute(false) }) .when('/login/:check', { templateUrl: 'views/common/login.html', - //ensure auth is *not* required so it will redirect to / + //ensure auth is *not* required so it will redirect to / resolve: canRoute(false) }) .when('/logout', { - redirectTo: '/login/false', + redirectTo: '/login/false', resolve: doLogout() }) .when('/:section', { @@ -118,7 +118,7 @@ app.config(function ($routeProvider) { return 'views/common/legacy.html'; }, resolve: canRoute(true) - }) + }) .when('/:section/:tree/:method', { templateUrl: function (rp) { if (!rp.method) @@ -130,7 +130,7 @@ app.config(function ($routeProvider) { // angular dashboards working. Perhaps a normal section dashboard would list out the registered // dashboards (as tabs if we wanted) and each tab could actually be a route link to one of these views? - return 'views/' + rp.tree + '/' + rp.method + '.html'; + return ('views/' + rp.tree + '/' + rp.method + '.html').toLowerCase(); }, resolve: canRoute(true) }) @@ -147,27 +147,27 @@ app.config(function ($routeProvider) { // Here we need to figure out if this route is for a package tree and if so then we need // to change it's convention view path to: // /App_Plugins/{mypackage}/backoffice/{treetype}/{method}.html - + // otherwise if it is a core tree we use the core paths: // views/{treetype}/{method}.html var packageTreeFolder = treeService.getTreePackageFolder($routeParams.tree); if (packageTreeFolder) { - $scope.templateUrl = Umbraco.Sys.ServerVariables.umbracoSettings.appPluginsPath + + $scope.templateUrl = (Umbraco.Sys.ServerVariables.umbracoSettings.appPluginsPath + "/" + packageTreeFolder + - "/backoffice/" + $routeParams.tree + "/" + $routeParams.method + ".html"; + "/backoffice/" + $routeParams.tree + "/" + $routeParams.method + ".html").toLowerCase(); } else { - $scope.templateUrl = 'views/' + $routeParams.tree + '/' + $routeParams.method + '.html'; + $scope.templateUrl = ('views/' + $routeParams.tree + '/' + $routeParams.method + '.html').toLowerCase(); } - - }, + + }, resolve: canRoute(true) - }) + }) .otherwise({ redirectTo: '/login' }); }).config(function ($locationProvider) { //$locationProvider.html5Mode(false).hashPrefix('!'); //turn html5 mode off // $locationProvider.html5Mode(true); //turn html5 mode on -}); \ No newline at end of file + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dashboard.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dashboard.controller.js index 006146095a..e722817959 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dashboard.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dashboard.controller.js @@ -9,6 +9,11 @@ */ function DashboardController($scope, $routeParams, dashboardResource, localizationService) { + + $scope.page = {}; + $scope.page.nameLocked = true; + $scope.page.loading = true; + $scope.dashboard = {}; localizationService.localize("sections_" + $routeParams.section).then(function(name){ $scope.dashboard.name = name; @@ -16,9 +21,10 @@ function DashboardController($scope, $routeParams, dashboardResource, localizati dashboardResource.getDashboard($routeParams.section).then(function(tabs){ $scope.dashboard.tabs = tabs; + $scope.page.loading = false; }); } //register it -angular.module('umbraco').controller("Umbraco.DashboardController", DashboardController); \ No newline at end of file +angular.module('umbraco').controller("Umbraco.DashboardController", DashboardController); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dashboard.html b/src/Umbraco.Web.UI.Client/src/views/common/dashboard.html index 0ee402a63f..452743d19f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dashboard.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/dashboard.html @@ -1,31 +1,49 @@ -
    - - -
    -

    {{dashboard.name}}

    -
    -
    - - -
    -
    +
    -
    -

    {{property.caption}}

    -
    -
    + -
    -

    {{property.caption}}

    - - -
    + -
    -
    - - - - \ No newline at end of file + + + + + + + + + + +
    + +
    +

    {{property.caption}}

    +
    +
    + +
    +

    {{property.caption}}

    + + +
    + +
    + +
    +
    + +
    + +
    + +  +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/help.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/help.controller.js index 7fabd5c7df..d898ec48f8 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/help.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/help.controller.js @@ -1,14 +1,21 @@ angular.module("umbraco") - .controller("Umbraco.Dialogs.HelpController", function ($scope, $location, $routeParams, helpService, userService) { + .controller("Umbraco.Dialogs.HelpController", function ($scope, $location, $routeParams, helpService, userService, localizationService) { $scope.section = $routeParams.section; $scope.version = Umbraco.Sys.ServerVariables.application.version + " assembly: " + Umbraco.Sys.ServerVariables.application.assemblyVersion; if(!$scope.section){ - $scope.section ="content"; + $scope.section = "content"; } + $scope.sectionName = $scope.section; + var rq = {}; rq.section = $scope.section; + + //translate section name + localizationService.localize("sections_" + rq.section).then(function (value) { + $scope.sectionName = value; + }); userService.getCurrentUser().then(function(user){ diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/help.html b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/help.html index 80ce8f80c0..b9428fb242 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/help.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/help.html @@ -1,13 +1,13 @@
    -

    Help

    +

    Help

    Umbraco version {{version}}
    -
    Help topics for: {{section}}
    +
    Help topics for: {{sectionName}}
    -
    Video chapters for: {{section}}
    +
    Video chapters for: {{sectionName}}
    diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js index e73c60685f..325af02bbc 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js @@ -1,5 +1,5 @@ angular.module("umbraco").controller("Umbraco.Dialogs.LoginController", - function ($scope, localizationService, userService, externalLoginInfo) { + function ($scope, $cookies, localizationService, userService, externalLoginInfo) { /** * @ngdoc function @@ -10,18 +10,39 @@ * @description * signs the user in */ - var d = new Date(); - //var weekday = new Array("Super Sunday", "Manic Monday", "Tremendous Tuesday", "Wonderful Wednesday", "Thunder Thursday", "Friendly Friday", "Shiny Saturday"); - localizationService.localize("login_greeting" + d.getDay()).then(function (label) { - $scope.greeting = label; - }); // weekday[d.getDay()]; + + + var d = new Date(); + var konamiGreetings = new Array("Suze Sunday", "Malibu Monday", "Tequila Tuesday", "Whiskey Wednesday", "Negroni Day", "Fernet Friday", "Sancerre Saturday"); + var konamiMode = $cookies.konamiLogin; + //var weekday = new Array("Super Sunday", "Manic Monday", "Tremendous Tuesday", "Wonderful Wednesday", "Thunder Thursday", "Friendly Friday", "Shiny Saturday"); + if (konamiMode == "1") { + $scope.greeting = "Happy " + konamiGreetings[d.getDay()]; + } else { + localizationService.localize("login_greeting" + d.getDay()).then(function (label) { + $scope.greeting = label; + }); // weekday[d.getDay()]; + } $scope.errorMsg = ""; $scope.externalLoginFormAction = Umbraco.Sys.ServerVariables.umbracoUrls.externalLoginsUrl; $scope.externalLoginProviders = externalLoginInfo.providers; $scope.externalLoginInfo = externalLoginInfo; + $scope.activateKonamiMode = function () { + if ($cookies.konamiLogin == "1") { + // somehow I can't update the cookie value using $cookies, so going native + document.cookie = "konamiLogin=; expires=Thu, 01 Jan 1970 00:00:01 GMT;"; + document.location.reload(); + } else { + document.cookie = "konamiLogin=1; expires=Tue, 01 Jan 2030 00:00:01 GMT;"; + $scope.$apply(function () { + $scope.greeting = "Happy " + konamiGreetings[d.getDay()]; + }); + } + } + $scope.loginSubmit = function (login, password) { //if the login and password are not empty we need to automatically diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html index 25a8b28976..3ca9bcda10 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html @@ -1,6 +1,5 @@ 
    -
    - +

    {{greeting}}

    @@ -41,7 +40,7 @@

    - +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.controller.js index efbfff62d6..b29388be23 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.controller.js @@ -10,20 +10,7 @@ angular.module("umbraco") $scope.multiPicker = (dialogOptions.multiPicker && dialogOptions.multiPicker !== "0") ? true : false; $scope.startNodeId = dialogOptions.startNodeId ? dialogOptions.startNodeId : -1; $scope.cropSize = dialogOptions.cropSize; - - $scope.filesUploading = 0; - $scope.dropping = false; - $scope.progress = 0; - $scope.options = { - url: umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostAddFile") + "?origin=blueimp", - autoUpload: true, - dropZone: $element.find(".umb-dialogs-mediapicker.browser"), - fileInput: $element.find("input.uploader"), - formData: { - currentFolder: -1 - } - }; //preload selected item $scope.target = undefined; @@ -31,14 +18,27 @@ angular.module("umbraco") $scope.target = dialogOptions.currentTarget; } + $scope.upload = function(v){ + angular.element(".umb-file-dropzone-directive .file-select").click(); + }; + + $scope.dragLeave = function(el, event){ + $scope.activeDrag = false; + }; + + $scope.dragEnter = function(el, event){ + $scope.activeDrag = true; + }; + $scope.submitFolder = function(e) { if (e.keyCode === 13) { e.preventDefault(); - $scope.showFolderInput = false; - + mediaResource - .addFolder($scope.newFolderName, $scope.options.formData.currentFolder) + .addFolder($scope.newFolderName, $scope.currentFolder.id) .then(function(data) { + $scope.showFolderInput = false; + $scope.newFolderName = ""; //we've added a new folder so lets clear the tree cache for that specific item treeService.clearCache({ @@ -77,45 +77,10 @@ angular.module("umbraco") $scope.images = data.items ? data.items : []; }); - $scope.options.formData.currentFolder = folder.id; $scope.currentFolder = folder; }; - - //This executes prior to the whole processing which we can use to get the UI going faster, - //this also gives us the start callback to invoke to kick of the whole thing - $scope.$on('fileuploadadd', function (e, data) { - $scope.$apply(function () { - $scope.filesUploading++; - }); - }); - - //when one is finished - $scope.$on('fileuploaddone', function (e, data) { - $scope.filesUploading--; - if ($scope.filesUploading == 0) { - $scope.$apply(function () { - $scope.progress = 0; - $scope.gotoFolder($scope.currentFolder); - }); - } - }); - - // All these sit-ups are to add dropzone area and make sure it gets removed if dragging is aborted! - $scope.$on('fileuploaddragover', function (e, data) { - if (!$scope.dragClearTimeout) { - $scope.$apply(function () { - $scope.dropping = true; - }); - } - else { - $timeout.cancel($scope.dragClearTimeout); - } - $scope.dragClearTimeout = $timeout(function () { - $scope.dropping = null; - $scope.dragClearTimeout = null; - }, 300); - }); - + + $scope.clickHandler = function(image, ev, select) { ev.preventDefault(); @@ -145,7 +110,13 @@ angular.module("umbraco") $scope.target = undefined; }; - + $scope.onUploadComplete = function () { + $scope.gotoFolder($scope.currentFolder); + }; + + $scope.onFilesQueue = function(){ + $scope.activeDrag = false; + }; //default root item if(!$scope.target){ diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.html b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.html index 597dcdaa4b..540a19d64a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.html @@ -6,6 +6,7 @@
  • {{item.name}} /
  • - +
  • @@ -103,24 +107,32 @@ ng-show="showFolderInput" ng-model="newFolderName" ng-keydown="submitFolder($event)" - on-blur="showFolderInput = false"> + on-blur="showFolderInput = false" + focus-when="{{showFolderInput}}">
  • + @@ -144,4 +156,4 @@ - \ No newline at end of file + diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/membergrouppicker.html b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/membergrouppicker.html index 1020fdaed2..48302cea02 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/membergrouppicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/membergrouppicker.html @@ -4,7 +4,7 @@
    + + diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/treepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/treepicker.controller.js index ef64247613..2d8d38cf50 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/treepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/treepicker.controller.js @@ -26,7 +26,8 @@ angular.module("umbraco").controller("Umbraco.Dialogs.TreePickerController", searchText = value + "..."; }); - var entityType = "Document"; + // Allow the entity type to be passed in but defaults to Document for backwards compatibility. + var entityType = dialogOptions.entityType ? dialogOptions.entityType : "Document"; //min / max values diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/user.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/user.controller.js index 68579bb442..bac8fcded7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/user.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/user.controller.js @@ -1,105 +1,167 @@ -angular.module("umbraco") - .controller("Umbraco.Dialogs.UserController", function ($scope, $location, $timeout, userService, historyService, eventsService, externalLoginInfo, authResource) { - - $scope.history = historyService.getCurrent(); - $scope.version = Umbraco.Sys.ServerVariables.application.version + " assembly: " + Umbraco.Sys.ServerVariables.application.assemblyVersion; - - $scope.externalLoginProviders = externalLoginInfo.providers; - $scope.externalLinkLoginFormAction = Umbraco.Sys.ServerVariables.umbracoUrls.externalLinkLoginsUrl; - var evts = []; - evts.push(eventsService.on("historyService.add", function (e, args) { - $scope.history = args.all; - })); - evts.push(eventsService.on("historyService.remove", function (e, args) { - $scope.history = args.all; - })); - evts.push(eventsService.on("historyService.removeAll", function (e, args) { - $scope.history = []; - })); - - $scope.logout = function () { - - //Add event listener for when there are pending changes on an editor which means our route was not successful - var pendingChangeEvent = eventsService.on("valFormManager.pendingChanges", function (e, args) { - //one time listener, remove the event - pendingChangeEvent(); - $scope.close(); - }); - - - //perform the path change, if it is successful then the promise will resolve otherwise it will fail - $scope.close(); - $location.path("/logout"); - }; - - $scope.gotoHistory = function (link) { - $location.path(link); - $scope.close(); - }; - - //Manually update the remaining timeout seconds - function updateTimeout() { - $timeout(function () { - if ($scope.remainingAuthSeconds > 0) { - $scope.remainingAuthSeconds--; - $scope.$digest(); - //recurse - updateTimeout(); - } - - }, 1000, false); // 1 second, do NOT execute a global digest - } - - function updateUserInfo() { - //get the user - userService.getCurrentUser().then(function (user) { - $scope.user = user; - if ($scope.user) { - $scope.remainingAuthSeconds = $scope.user.remainingAuthSeconds; - $scope.canEditProfile = _.indexOf($scope.user.allowedSections, "users") > -1; - //set the timer - updateTimeout(); - - authResource.getCurrentUserLinkedLogins().then(function(logins) { - //reset all to be un-linked - for (var provider in $scope.externalLoginProviders) { - $scope.externalLoginProviders[provider].linkedProviderKey = undefined; - } - - //set the linked logins - for (var login in logins) { - var found = _.find($scope.externalLoginProviders, function (i) { - return i.authType == login; - }); - if (found) { - found.linkedProviderKey = logins[login]; - } - } - }); - } - }); - } - - $scope.unlink = function (e, loginProvider, providerKey) { - var result = confirm("Are you sure you want to unlink this account?"); - if (!result) { - e.preventDefault(); - return; - } - - authResource.unlinkLogin(loginProvider, providerKey).then(function (a, b, c) { - updateUserInfo(); - }); - } - - updateUserInfo(); - - //remove all event handlers - $scope.$on('$destroy', function () { - for (var e = 0; e < evts.length; e++) { - evts[e](); - } - - }); - - }); \ No newline at end of file +angular.module("umbraco") + .controller("Umbraco.Dialogs.UserController", function ($scope, $location, $timeout, userService, historyService, eventsService, externalLoginInfo, authResource, currentUserResource, formHelper) { + + $scope.history = historyService.getCurrent(); + $scope.version = Umbraco.Sys.ServerVariables.application.version + " assembly: " + Umbraco.Sys.ServerVariables.application.assemblyVersion; + $scope.showPasswordFields = false; + $scope.changePasswordButtonState = "init"; + + $scope.externalLoginProviders = externalLoginInfo.providers; + $scope.externalLinkLoginFormAction = Umbraco.Sys.ServerVariables.umbracoUrls.externalLinkLoginsUrl; + var evts = []; + evts.push(eventsService.on("historyService.add", function (e, args) { + $scope.history = args.all; + })); + evts.push(eventsService.on("historyService.remove", function (e, args) { + $scope.history = args.all; + })); + evts.push(eventsService.on("historyService.removeAll", function (e, args) { + $scope.history = []; + })); + + $scope.logout = function () { + + //Add event listener for when there are pending changes on an editor which means our route was not successful + var pendingChangeEvent = eventsService.on("valFormManager.pendingChanges", function (e, args) { + //one time listener, remove the event + pendingChangeEvent(); + $scope.close(); + }); + + + //perform the path change, if it is successful then the promise will resolve otherwise it will fail + $scope.close(); + $location.path("/logout"); + }; + + $scope.gotoHistory = function (link) { + $location.path(link); + $scope.close(); + }; + + //Manually update the remaining timeout seconds + function updateTimeout() { + $timeout(function () { + if ($scope.remainingAuthSeconds > 0) { + $scope.remainingAuthSeconds--; + $scope.$digest(); + //recurse + updateTimeout(); + } + + }, 1000, false); // 1 second, do NOT execute a global digest + } + + function updateUserInfo() { + //get the user + userService.getCurrentUser().then(function (user) { + $scope.user = user; + if ($scope.user) { + $scope.remainingAuthSeconds = $scope.user.remainingAuthSeconds; + $scope.canEditProfile = _.indexOf($scope.user.allowedSections, "users") > -1; + //set the timer + updateTimeout(); + + authResource.getCurrentUserLinkedLogins().then(function(logins) { + //reset all to be un-linked + for (var provider in $scope.externalLoginProviders) { + $scope.externalLoginProviders[provider].linkedProviderKey = undefined; + } + + //set the linked logins + for (var login in logins) { + var found = _.find($scope.externalLoginProviders, function (i) { + return i.authType == login; + }); + if (found) { + found.linkedProviderKey = logins[login]; + } + } + }); + } + }); + } + + $scope.unlink = function (e, loginProvider, providerKey) { + var result = confirm("Are you sure you want to unlink this account?"); + if (!result) { + e.preventDefault(); + return; + } + + authResource.unlinkLogin(loginProvider, providerKey).then(function (a, b, c) { + updateUserInfo(); + }); + } + + updateUserInfo(); + + //remove all event handlers + $scope.$on('$destroy', function () { + for (var e = 0; e < evts.length; e++) { + evts[e](); + } + + }); + + /* ---------- UPDATE PASSWORD ---------- */ + + //create the initial model for change password property editor + $scope.changePasswordModel = { + alias: "_umb_password", + view: "changepassword", + config: {}, + value: {} + }; + + //go get the config for the membership provider and add it to the model + currentUserResource.getMembershipProviderConfig().then(function(data) { + $scope.changePasswordModel.config = data; + //ensure the hasPassword config option is set to true (the user of course has a password already assigned) + //this will ensure the oldPassword is shown so they can change it + // disable reset password functionality beacuse it does not make sense inside the backoffice + $scope.changePasswordModel.config.hasPassword = true; + $scope.changePasswordModel.config.disableToggle = true; + $scope.changePasswordModel.config.enableReset = false; + }); + + $scope.changePassword = function() { + + if (formHelper.submitForm({ scope: $scope })) { + + $scope.changePasswordButtonState = "busy"; + + currentUserResource.changePassword($scope.changePasswordModel.value).then(function(data) { + + //if the password has been reset, then update our model + if (data.value) { + $scope.changePasswordModel.value.generatedPassword = data.value; + } + + formHelper.resetForm({ scope: $scope, notifications: data.notifications }); + + $scope.changePasswordButtonState = "success"; + + }, function (err) { + + formHelper.handleError(err); + + $scope.changePasswordButtonState = "error"; + + }); + + } + + }; + + $scope.togglePasswordFields = function() { + clearPasswordFields(); + $scope.showPasswordFields = !$scope.showPasswordFields; + } + + function clearPasswordFields() { + $scope.changePasswordModel.value.newPassword = ""; + $scope.changePasswordModel.confirm = ""; + } + + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/user.html b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/user.html index 8346acfabb..83be1a7782 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/user.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/user.html @@ -8,6 +8,7 @@

    {{user.name}}

    + Umbraco version {{version}}

    : {{remainingAuthSeconds | timespan}} @@ -19,18 +20,27 @@

    -
    -
    -

    - - Edit - -

    +
    + +
    + + + Edit + + + + +
    -
    +
    External login providers
    @@ -63,7 +73,7 @@
    -
    +
    • @@ -74,8 +84,36 @@
    -
    +
    + +
    Change password
    + +
    + + + + + + + + + +
    + +
    - Umbraco version {{version}}
    -
    \ No newline at end of file +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/common/legacy.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/legacy.controller.js index 8e58535c74..fcc8e8c2ed 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/legacy.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/legacy.controller.js @@ -9,7 +9,7 @@ */ function LegacyController($scope, $routeParams, $element) { - var url = decodeURIComponent($routeParams.url.toLowerCase().replace(/javascript\:/g, "")); + var url = decodeURIComponent($routeParams.url.replace(/javascript\:/gi, "")); //split into path and query var urlParts = url.split("?"); var extIndex = urlParts[0].lastIndexOf("."); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/contentpicker/contentpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/contentpicker/contentpicker.html new file mode 100644 index 0000000000..8e70ad163c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/contentpicker/contentpicker.html @@ -0,0 +1,34 @@ +
    + +
    + + +
    + + + + +
    + + +
    + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/compositions/compositions.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/compositions/compositions.html new file mode 100644 index 0000000000..1e06166970 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/compositions/compositions.html @@ -0,0 +1,37 @@ +
    + +
    + +
    + Inherit tabs and properties from an existing document type. New tabs will be added to the current document type or merged if a tab with an identical name exists. +
    + +
    + This content type is used in a composition, and therefore cannot be composed itself. +
    + +
      +
    • + +
      + +
      + +
      + + {{ compositeContentType.name }} +
      + +
    • +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/editorpicker/editorpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/editorpicker/editorpicker.controller.js new file mode 100644 index 0000000000..5b2dbbb26d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/editorpicker/editorpicker.controller.js @@ -0,0 +1,200 @@ +/** + * @ngdoc controller + * @name Umbraco.Editors.DocumentType.PropertyController + * @function + * + * @description + * The controller for the content type editor property dialog + */ + + (function() { + "use strict"; + + function EditorPickerOverlay($scope, dataTypeResource, dataTypeHelper, contentTypeResource) { + + var vm = this; + + vm.searchTerm = ""; + vm.showTabs = false; + vm.tabsLoaded = 0; + vm.tabs = [ + { + active: true, + id: 1, + label: "Available Editors", + alias: "Default", + typesAndEditors: [] + }, + { + active: false, + id: 2, + label: "Re-use", + alias: "Reuse", + userConfigured: [] + } + ]; + + vm.filterItems = filterItems; + vm.showDetailsOverlay = showDetailsOverlay; + vm.hideDetailsOverlay = hideDetailsOverlay; + vm.pickEditor = pickEditor; + vm.pickDataType = pickDataType; + + function activate() { + + getGroupedDataTypes(); + getGroupedPropertyEditors(); + + } + + function getGroupedPropertyEditors() { + + dataTypeResource.getGroupedPropertyEditors().then(function(data){ + vm.tabs[0].typesAndEditors = data; + vm.tabsLoaded = vm.tabsLoaded + 1; + checkIfTabContentIsLoaded(); + }); + + } + + function getGroupedDataTypes() { + + dataTypeResource.getGroupedDataTypes().then(function (data) { + vm.tabs[1].userConfigured = data; + vm.tabsLoaded = vm.tabsLoaded + 1; + checkIfTabContentIsLoaded(); + }); + + } + + function checkIfTabContentIsLoaded() { + if(vm.tabsLoaded === 2) { + vm.showTabs = true; + } + } + + function filterItems() { + // clear item details + $scope.model.itemDetails = null; + } + + function showDetailsOverlay(property) { + + var propertyDetails = {}; + propertyDetails.icon = property.icon; + propertyDetails.title = property.name; + + $scope.model.itemDetails = propertyDetails; + + } + + function hideDetailsOverlay() { + $scope.model.itemDetails = null; + } + + function pickEditor(editor) { + + var parentId = -1; + + dataTypeResource.getScaffold(parentId).then(function(dataType) { + + // set alias + dataType.selectedEditor = editor.alias; + + // set name + var nameArray = []; + + if($scope.model.contentTypeName) { + nameArray.push($scope.model.contentTypeName); + } + + if($scope.model.property.label) { + nameArray.push($scope.model.property.label); + } + + if(editor.name) { + nameArray.push(editor.name); + } + + // make name + dataType.name = nameArray.join(" - "); + + // get pre values + dataTypeResource.getPreValues(dataType.selectedEditor).then(function(preValues) { + + dataType.preValues = preValues; + + openEditorSettingsOverlay(dataType, true); + + }); + + }); + + } + + function pickDataType(selectedDataType) { + + dataTypeResource.getById(selectedDataType.id).then(function(dataType) { + contentTypeResource.getPropertyTypeScaffold(dataType.id).then(function(propertyType) { + submitOverlay(dataType, propertyType, false); + }); + }); + + } + + function openEditorSettingsOverlay(dataType, isNew) { + vm.editorSettingsOverlay = {}; + vm.editorSettingsOverlay.title = "Editor settings"; + vm.editorSettingsOverlay.dataType = dataType; + vm.editorSettingsOverlay.view = "views/common/overlays/contenttypeeditor/editorsettings/editorsettings.html"; + vm.editorSettingsOverlay.show = true; + + vm.editorSettingsOverlay.submit = function(model) { + + var preValues = dataTypeHelper.createPreValueProps(model.dataType.preValues); + + dataTypeResource.save(model.dataType, preValues, isNew).then(function(newDataType) { + + contentTypeResource.getPropertyTypeScaffold(newDataType.id).then(function(propertyType) { + + submitOverlay(newDataType, propertyType, true); + + vm.editorSettingsOverlay.show = false; + vm.editorSettingsOverlay = null; + + }); + + }); + + }; + + vm.editorSettingsOverlay.close = function(oldModel) { + vm.editorSettingsOverlay.show = false; + vm.editorSettingsOverlay = null; + }; + + } + + function submitOverlay(dataType, propertyType, isNew) { + + // update property + $scope.model.property.config = propertyType.config; + $scope.model.property.editor = propertyType.editor; + $scope.model.property.view = propertyType.view; + $scope.model.property.dataTypeId = dataType.id; + $scope.model.property.dataTypeIcon = dataType.icon; + $scope.model.property.dataTypeName = dataType.name; + + $scope.model.updateSameDataTypes = isNew; + + $scope.model.submit($scope.model); + + } + + activate(); + + } + + angular.module("umbraco").controller("Umbraco.Overlays.EditorPickerOverlay", EditorPickerOverlay); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/editorpicker/editorpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/editorpicker/editorpicker.html new file mode 100644 index 0000000000..671d9083c4 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/editorpicker/editorpicker.html @@ -0,0 +1,79 @@ +
    + +
    + +
    + +
    + + + + + + +
    + +
    + +
    +
    {{key}}
    + + + +
    + +
    +
    + +
    + +
    + +
    +
    {{key}}
    + + + +
    + +
    + +
    + +
    + +
    + +
    + + + + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/editorsettings/editorsettings.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/editorsettings/editorsettings.html new file mode 100644 index 0000000000..4b391f3ccf --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/editorsettings/editorsettings.html @@ -0,0 +1,19 @@ +
    + + All Document types using this editor will get updated with the new settings. +
    + +
    +
    + +
    + +
    +
    +
    + +
    Configuration
    + + + + diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/propertysettings/propertysettings.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/propertysettings/propertysettings.controller.js new file mode 100644 index 0000000000..70fc5665fb --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/propertysettings/propertysettings.controller.js @@ -0,0 +1,203 @@ +/** + * @ngdoc controller + * @name Umbraco.Editors.DocumentType.PropertyController + * @function + * + * @description + * The controller for the content type editor property dialog + */ + + (function() { + "use strict"; + + function PropertySettingsOverlay($scope, contentTypeResource, dataTypeResource, dataTypeHelper) { + + var vm = this; + + vm.showValidationPattern = false; + vm.focusOnPatternField = false; + vm.focusOnMandatoryField = false; + vm.selectedValidationType = {}; + vm.validationTypes = [ + { + "name": "Validate as email", + "key": "email", + "pattern": "[a-zA-Z0-9_\.\+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-\.]+", + "enableEditing": true + }, + { + "name": "Validate as a number", + "key": "number", + "pattern": "^[0-9]*$", + "enableEditing": true + }, + { + "name": "Validate as a Url", + "key": "url", + "pattern": "https?\:\/\/[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,}", + "enableEditing": true + }, + { + "name": "...or enter a custom validation", + "key": "custom", + "pattern": "", + "enableEditing": true + } + ]; + + vm.changeValidationType = changeValidationType; + vm.changeValidationPattern = changeValidationPattern; + vm.openEditorPickerOverlay = openEditorPickerOverlay; + vm.openEditorSettingsOverlay = openEditorSettingsOverlay; + + function activate() { + + matchValidationType(); + + } + + function changeValidationPattern() { + matchValidationType(); + } + + function openEditorPickerOverlay(property) { + + vm.focusOnMandatoryField = false; + + vm.editorPickerOverlay = {}; + vm.editorPickerOverlay.title = "Choose editor"; + vm.editorPickerOverlay.property = $scope.model.property; + vm.editorPickerOverlay.contentTypeName = $scope.model.contentTypeName; + vm.editorPickerOverlay.view = "views/common/overlays/contenttypeeditor/editorpicker/editorpicker.html"; + vm.editorPickerOverlay.show = true; + + vm.editorPickerOverlay.submit = function(model) { + + $scope.model.updateSameDataTypes = model.updateSameDataTypes; + + vm.focusOnMandatoryField = true; + + // update property + property.config = model.property.config; + property.editor = model.property.editor; + property.view = model.property.view; + property.dataTypeId = model.property.dataTypeId; + property.dataTypeIcon = model.property.dataTypeIcon; + property.dataTypeName = model.property.dataTypeName; + + vm.editorPickerOverlay.show = false; + vm.editorPickerOverlay = null; + }; + + vm.editorPickerOverlay.close = function(model) { + vm.editorPickerOverlay.show = false; + vm.editorPickerOverlay = null; + }; + + } + + function openEditorSettingsOverlay(property) { + + vm.focusOnMandatoryField = false; + + // get data type + dataTypeResource.getById(property.dataTypeId).then(function(dataType) { + + vm.editorSettingsOverlay = {}; + vm.editorSettingsOverlay.title = "Editor settings"; + vm.editorSettingsOverlay.view = "views/common/overlays/contenttypeeditor/editorsettings/editorsettings.html"; + vm.editorSettingsOverlay.dataType = dataType; + vm.editorSettingsOverlay.show = true; + + vm.editorSettingsOverlay.submit = function(model) { + + var preValues = dataTypeHelper.createPreValueProps(model.dataType.preValues); + + dataTypeResource.save(model.dataType, preValues, false).then(function(newDataType) { + + contentTypeResource.getPropertyTypeScaffold(newDataType.id).then(function(propertyType) { + + // update editor + property.config = propertyType.config; + property.editor = propertyType.editor; + property.view = propertyType.view; + property.dataTypeId = newDataType.id; + property.dataTypeIcon = newDataType.icon; + property.dataTypeName = newDataType.name; + + // set flag to update same data types + $scope.model.updateSameDataTypes = true; + + vm.focusOnMandatoryField = true; + + vm.editorSettingsOverlay.show = false; + vm.editorSettingsOverlay = null; + + }); + + }); + + }; + + vm.editorSettingsOverlay.close = function(oldModel) { + vm.editorSettingsOverlay.show = false; + vm.editorSettingsOverlay = null; + }; + + }); + + } + + function matchValidationType() { + + if($scope.model.property.validation.pattern !== null && $scope.model.property.validation.pattern !== "" && $scope.model.property.validation.pattern !== undefined) { + + var match = false; + + // find and show if a match from the list has been chosen + angular.forEach(vm.validationTypes, function(validationType, index){ + if($scope.model.property.validation.pattern === validationType.pattern) { + vm.selectedValidationType = vm.validationTypes[index]; + vm.showValidationPattern = true; + match = true; + } + }); + + // if there is no match - choose the custom validation option. + if(!match) { + angular.forEach(vm.validationTypes, function(validationType){ + if(validationType.key === "custom") { + vm.selectedValidationType = validationType; + vm.showValidationPattern = true; + } + }); + } + } + + } + + function changeValidationType(selectedValidationType) { + + if(selectedValidationType) { + $scope.model.property.validation.pattern = selectedValidationType.pattern; + vm.showValidationPattern = true; + + // set focus on textarea + if(selectedValidationType.key === "custom") { + vm.focusOnPatternField = true; + } + + } else { + $scope.model.property.validation.pattern = ""; + vm.showValidationPattern = false; + } + + } + + activate(); + + } + + angular.module("umbraco").controller("Umbraco.Overlay.PropertySettingsOverlay", PropertySettingsOverlay); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/propertysettings/propertysettings.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/propertysettings/propertysettings.html new file mode 100644 index 0000000000..19938ecbc7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/propertysettings/propertysettings.html @@ -0,0 +1,95 @@ +
    + +
    +
    + +
    Required label
    +
    +
    + +
    +
    + +
    + +
    + + + +
    + + + + + + + +
    + + + + + + + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/copy/copy.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/copy/copy.controller.js new file mode 100644 index 0000000000..1476b852f2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/copy/copy.controller.js @@ -0,0 +1,114 @@ + (function() { + "use strict"; + + function CopyOverlay($scope, localizationService, eventsService) { + + var vm = this; + + vm.hideSearch = hideSearch; + vm.selectResult = selectResult; + vm.onSearchResults = onSearchResults; + + var dialogOptions = $scope.model; + var searchText = "Search..."; + var node = dialogOptions.currentNode; + + localizationService.localize("general_search").then(function (value) { + searchText = value + "..."; + }); + + $scope.model.relateToOriginal = true; + $scope.dialogTreeEventHandler = $({}); + + vm.searchInfo = { + searchFromId: null, + searchFromName: null, + showSearch: false, + results: [], + selectedSearchResults: [] + }; + + function nodeSelectHandler(ev, args) { + args.event.preventDefault(); + args.event.stopPropagation(); + + if (args.node.metaData.listViewNode) { + //check if list view 'search' node was selected + + vm.searchInfo.showSearch = true; + vm.searchInfo.searchFromId = args.node.metaData.listViewNode.id; + vm.searchInfo.searchFromName = args.node.metaData.listViewNode.name; + } + else { + //eventsService.emit("editors.content.copyController.select", args); + + if ($scope.model.target) { + //un-select if there's a current one selected + $scope.model.target.selected = false; + } + + $scope.model.target = args.node; + $scope.model.target.selected = true; + } + + } + + function nodeExpandedHandler(ev, args) { + if (angular.isArray(args.children)) { + + //iterate children + _.each(args.children, function (child) { + //check if any of the items are list views, if so we need to add a custom + // child: A node to activate the search + if (child.metaData.isContainer) { + child.hasChildren = true; + child.children = [ + { + level: child.level + 1, + hasChildren: false, + name: searchText, + metaData: { + listViewNode: child, + }, + cssClass: "icon umb-tree-icon sprTree icon-search", + cssClasses: ["not-published"] + } + ]; + } + }); + } + } + + function hideSearch() { + vm.searchInfo.showSearch = false; + vm.searchInfo.searchFromId = null; + vm.searchInfo.searchFromName = null; + vm.searchInfo.results = []; + } + + // method to select a search result + function selectResult(evt, result) { + result.selected = result.selected === true ? false : true; + nodeSelectHandler(evt, { event: evt, node: result }); + } + + //callback when there are search results + function onSearchResults(results) { + vm.searchInfo.results = results; + vm.searchInfo.showSearch = true; + } + + $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); + $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler); + + $scope.$on('$destroy', function () { + $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); + $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler); + }); + + + } + + angular.module("umbraco").controller("Umbraco.Overlays.CopyOverlay", CopyOverlay); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/copy/copy.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/copy/copy.html new file mode 100644 index 0000000000..a97a90c431 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/copy/copy.html @@ -0,0 +1,38 @@ +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/embed/embed.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/embed/embed.controller.js new file mode 100644 index 0000000000..20568c8ae5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/embed/embed.controller.js @@ -0,0 +1,99 @@ +(function() { + "use strict"; + + function EmbedOverlay($scope, $http, umbRequestHelper) { + + var vm = this; + var origWidth = 500; + var origHeight = 300; + + $scope.model.title = "Embed"; + $scope.model.embed = { + url: "", + width: 360, + height: 240, + constrain: true, + preview: "", + success: false, + info: "", + supportsDimensions: "" + }; + + vm.showPreview = showPreview; + vm.changeSize = changeSize; + + function showPreview() { + + if ($scope.model.embed.url) { + $scope.model.embed.show = true; + $scope.model.embed.preview = "
    "; + $scope.model.embed.info = ""; + $scope.model.embed.success = false; + + $http({ + method: 'GET', + url: umbRequestHelper.getApiUrl("embedApiBaseUrl", "GetEmbed"), + params: { + url: $scope.model.embed.url, + width: $scope.model.embed.width, + height: $scope.model.embed.height + } + }) + .success(function(data) { + + $scope.model.embed.preview = ""; + + switch (data.Status) { + case 0: + //not supported + $scope.model.embed.info = "Not supported"; + break; + case 1: + //error + $scope.model.embed.info = "Computer says no"; + break; + case 2: + $scope.model.embed.preview = data.Markup; + $scope.model.embed.supportsDimensions = data.SupportsDimensions; + $scope.model.embed.success = true; + break; + } + }) + .error(function() { + $scope.model.embed.supportsDimensions = false; + $scope.model.embed.preview = ""; + $scope.model.embed.info = "Computer says no"; + }); + } else { + $scope.model.embed.supportsDimensions = false; + $scope.model.embed.preview = ""; + $scope.model.embed.info = "Please enter a URL"; + } + } + + function changeSize(type) { + + var width, height; + + if ($scope.model.embed.constrain) { + width = parseInt($scope.model.embed.width, 10); + height = parseInt($scope.model.embed.height, 10); + if (type == 'width') { + origHeight = Math.round((width / origWidth) * height); + $scope.model.embed.height = origHeight; + } else { + origWidth = Math.round((height / origHeight) * width); + $scope.model.embed.width = origWidth; + } + } + if ($scope.model.embed.url !== "") { + showPreview(); + } + + } + + } + + angular.module("umbraco").controller("Umbraco.Overlays.EmbedOverlay", EmbedOverlay); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/embed/embed.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/embed/embed.html new file mode 100644 index 0000000000..22088a2a1a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/embed/embed.html @@ -0,0 +1,27 @@ +
    + + + + + + + +

    +
    +
    + +
    + + + + + + + + + + + +
    + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/help/help.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/help/help.controller.js new file mode 100644 index 0000000000..13b2f4edb6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/help/help.controller.js @@ -0,0 +1,58 @@ +angular.module("umbraco") + .controller("Umbraco.Overlays.HelpController", function ($scope, $location, $routeParams, helpService, userService, localizationService) { + $scope.section = $routeParams.section; + $scope.version = Umbraco.Sys.ServerVariables.application.version + " assembly: " + Umbraco.Sys.ServerVariables.application.assemblyVersion; + $scope.model.title = "Help"; + $scope.model.subtitle = "Umbraco version" + " " + $scope.version; + + if(!$scope.section){ + $scope.section = "content"; + } + + $scope.sectionName = $scope.section; + + var rq = {}; + rq.section = $scope.section; + + //translate section name + localizationService.localize("sections_" + rq.section).then(function (value) { + $scope.sectionName = value; + }); + + // translate dialog title + localizationService.localize("sections_help").then(function (value) { + $scope.model.title = value; + }); + + userService.getCurrentUser().then(function(user){ + + rq.usertype = user.userType; + rq.lang = user.locale; + + if($routeParams.url){ + rq.path = decodeURIComponent($routeParams.url); + + if(rq.path.indexOf(Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath) === 0){ + rq.path = rq.path.substring(Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath.length); + } + + if(rq.path.indexOf(".aspx") > 0){ + rq.path = rq.path.substring(0, rq.path.indexOf(".aspx")); + } + + }else{ + rq.path = rq.section + "/" + $routeParams.tree + "/" + $routeParams.method; + } + + helpService.findHelp(rq).then(function(topics){ + $scope.topics = topics; + }); + + helpService.findVideos(rq).then(function(videos){ + $scope.videos = videos; + }); + + }); + + + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/help/help.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/help/help.html new file mode 100644 index 0000000000..2e068448bd --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/help/help.html @@ -0,0 +1,48 @@ +
    + +
    +
    Help topics for: {{sectionName}}
    + + +
    + +
    +
    Video chapters for: {{sectionName}}
    + + +
    + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/iconpicker/iconpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/iconpicker/iconpicker.controller.js new file mode 100644 index 0000000000..9d01c0d579 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/iconpicker/iconpicker.controller.js @@ -0,0 +1,31 @@ +/** + * @ngdoc controller + * @name Umbraco.Editors.DocumentType.PropertyController + * @function + * + * @description + * The controller for the content type editor property dialog + */ +function IconPickerOverlay($scope, iconHelper) { + + $scope.loading = true; + $scope.model.hideSubmitButton = true; + + if(!$scope.model.title) { + $scope.model.title = "Select an icon"; + } + + iconHelper.getIcons().then(function(icons) { + $scope.icons = icons; + $scope.loading = false; + }); + + $scope.selectIcon = function(icon, color) { + $scope.model.icon = icon; + $scope.model.color = color; + $scope.submitForm($scope.model); + }; + +} + +angular.module("umbraco").controller("Umbraco.Overlays.IconPickerOverlay", IconPickerOverlay); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/iconpicker/iconpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/iconpicker/iconpicker.html new file mode 100644 index 0000000000..842c205b22 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/iconpicker/iconpicker.html @@ -0,0 +1,38 @@ +
    + +
    + +
    + +
    + +
    + + + +
    + +
    + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/itempicker/itempicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/itempicker/itempicker.controller.js new file mode 100644 index 0000000000..db858db679 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/itempicker/itempicker.controller.js @@ -0,0 +1,12 @@ +function ItemPickerOverlay($scope) { + + $scope.model.hideSubmitButton = true; + + $scope.selectItem = function(item) { + $scope.model.selectedItem = item; + $scope.submitForm($scope.model); + }; + +} + +angular.module("umbraco").controller("Umbraco.Overlays.ItemPickerOverlay", ItemPickerOverlay); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/itempicker/itempicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/itempicker/itempicker.html new file mode 100644 index 0000000000..bcf1cc7d5a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/itempicker/itempicker.html @@ -0,0 +1,22 @@ +
    + + + + + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js new file mode 100644 index 0000000000..7458f04011 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js @@ -0,0 +1,157 @@ +//used for the media picker dialog +angular.module("umbraco").controller("Umbraco.Overlays.LinkPickerController", + function ($scope, eventsService, dialogService, entityResource, contentResource, mediaHelper, userService, localizationService) { + var dialogOptions = $scope.model; + + var searchText = "Search..."; + localizationService.localize("general_search").then(function (value) { + searchText = value + "..."; + }); + + if(!$scope.model.title) { + $scope.model.title = "Link picker"; + } + + $scope.dialogTreeEventHandler = $({}); + $scope.model.target = {}; + $scope.searchInfo = { + searchFromId: null, + searchFromName: null, + showSearch: false, + results: [], + selectedSearchResults: [] + }; + + if (dialogOptions.currentTarget) { + $scope.model.target = dialogOptions.currentTarget; + + //if we have a node ID, we fetch the current node to build the form data + if ($scope.model.target.id) { + + if (!$scope.model.target.path) { + entityResource.getPath($scope.model.target.id, "Document").then(function (path) { + $scope.model.target.path = path; + //now sync the tree to this path + $scope.dialogTreeEventHandler.syncTree({ path: $scope.model.target.path, tree: "content" }); + }); + } + + contentResource.getNiceUrl($scope.model.target.id).then(function (url) { + $scope.model.target.url = url; + }); + } + } + + function nodeSelectHandler(ev, args) { + args.event.preventDefault(); + args.event.stopPropagation(); + + if (args.node.metaData.listViewNode) { + //check if list view 'search' node was selected + + $scope.searchInfo.showSearch = true; + $scope.searchInfo.searchFromId = args.node.metaData.listViewNode.id; + $scope.searchInfo.searchFromName = args.node.metaData.listViewNode.name; + } + else { + eventsService.emit("dialogs.linkPicker.select", args); + + if ($scope.currentNode) { + //un-select if there's a current one selected + $scope.currentNode.selected = false; + } + + $scope.currentNode = args.node; + $scope.currentNode.selected = true; + $scope.model.target.id = args.node.id; + $scope.model.target.name = args.node.name; + + if (args.node.id < 0) { + $scope.model.target.url = "/"; + } + else { + contentResource.getNiceUrl(args.node.id).then(function (url) { + $scope.model.target.url = url; + }); + } + + if (!angular.isUndefined($scope.model.target.isMedia)) { + delete $scope.model.target.isMedia; + } + } + } + + function nodeExpandedHandler(ev, args) { + if (angular.isArray(args.children)) { + + //iterate children + _.each(args.children, function (child) { + //check if any of the items are list views, if so we need to add a custom + // child: A node to activate the search + if (child.metaData.isContainer) { + child.hasChildren = true; + child.children = [ + { + level: child.level + 1, + hasChildren: false, + name: searchText, + metaData: { + listViewNode: child, + }, + cssClass: "icon umb-tree-icon sprTree icon-search", + cssClasses: ["not-published"] + } + ]; + } + }); + } + } + + $scope.switchToMediaPicker = function () { + userService.getCurrentUser().then(function (userData) { + $scope.mediaPickerOverlay = { + view: "mediapicker", + startNodeId: userData.startMediaId, + show: true, + submit: function(model) { + var media = model.selectedImages[0]; + + $scope.model.target.id = media.id; + $scope.model.target.isMedia = true; + $scope.model.target.name = media.name; + $scope.model.target.url = mediaHelper.resolveFile(media); + + $scope.mediaPickerOverlay.show = false; + $scope.mediaPickerOverlay = null; + } + }; + }); + }; + + $scope.hideSearch = function () { + $scope.searchInfo.showSearch = false; + $scope.searchInfo.searchFromId = null; + $scope.searchInfo.searchFromName = null; + $scope.searchInfo.results = []; + } + + // method to select a search result + $scope.selectResult = function (evt, result) { + result.selected = result.selected === true ? false : true; + nodeSelectHandler(evt, {event: evt, node: result}); + }; + + //callback when there are search results + $scope.onSearchResults = function (results) { + $scope.searchInfo.results = results; + $scope.searchInfo.showSearch = true; + }; + + $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); + $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler); + + $scope.$on('$destroy', function () { + $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); + $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler); + }); + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.html new file mode 100644 index 0000000000..c8b21eae81 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.html @@ -0,0 +1,74 @@ +
    + + + + + + + + + + + + + +
    +
    Link to page
    + + + + +
    + + + + +
    + + +
    + +
    + +
    +
    Link to media
    + Select media +
    + + + + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/macropicker/macropicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/macropicker/macropicker.controller.js new file mode 100644 index 0000000000..8fef90b607 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/macropicker/macropicker.controller.js @@ -0,0 +1,113 @@ +function MacroPickerController($scope, entityResource, macroResource, umbPropEditorHelper, macroService, formHelper) { + + + $scope.model.title = "Macro picker"; + $scope.macros = []; + $scope.model.selectedMacro = null; + $scope.wizardStep = "macroSelect"; + $scope.model.macroParams = []; + $scope.noMacroParams = false; + + $scope.changeMacro = function() { + if ($scope.wizardStep === "macroSelect") { + editParams(); + } else { + submitForm(); + } + }; + + /** changes the view to edit the params of the selected macro */ + function editParams() { + //get the macro params if there are any + macroResource.getMacroParameters($scope.model.selectedMacro.id) + .then(function (data) { + + //go to next page if there are params otherwise we can just exit + if (!angular.isArray(data) || data.length === 0) { + + $scope.noMacroParams = true; + + } else { + $scope.wizardStep = "paramSelect"; + $scope.model.macroParams = data; + + //fill in the data if we are editing this macro + if ($scope.model.dialogData && $scope.model.dialogData.macroData && $scope.model.dialogData.macroData.macroParamsDictionary) { + _.each($scope.model.dialogData.macroData.macroParamsDictionary, function (val, key) { + var prop = _.find($scope.model.macroParams, function (item) { + return item.alias == key; + }); + if (prop) { + + if (_.isString(val)) { + //we need to unescape values as they have most likely been escaped while inserted + val = _.unescape(val); + + //detect if it is a json string + if (val.detectIsJson()) { + try { + //Parse it to json + prop.value = angular.fromJson(val); + } + catch (e) { + // not json + prop.value = val; + } + } + else { + prop.value = val; + } + } + else { + prop.value = val; + } + } + }); + + } + } + + }); + } + + //here we check to see if we've been passed a selected macro and if so we'll set the + //editor to start with parameter editing + if ($scope.model.dialogData && $scope.model.dialogData.macroData) { + $scope.wizardStep = "paramSelect"; + } + + //get the macro list - pass in a filter if it is only for rte + entityResource.getAll("Macro", ($scope.model.dialogData && $scope.model.dialogData.richTextEditor && $scope.model.dialogData.richTextEditor === true) ? "UseInEditor=true" : null) + .then(function (data) { + + //if 'allowedMacros' is specified, we need to filter + if (angular.isArray($scope.model.dialogData.allowedMacros) && $scope.model.dialogData.allowedMacros.length > 0) { + $scope.macros = _.filter(data, function(d) { + return _.contains($scope.model.dialogData.allowedMacros, d.alias); + }); + } + else { + $scope.macros = data; + } + + + //check if there's a pre-selected macro and if it exists + if ($scope.model.dialogData && $scope.model.dialogData.macroData && $scope.model.dialogData.macroData.macroAlias) { + var found = _.find(data, function (item) { + return item.alias === $scope.model.dialogData.macroData.macroAlias; + }); + if (found) { + //select the macro and go to next screen + $scope.model.selectedMacro = found; + editParams(); + return; + } + } + //we don't have a pre-selected macro so ensure the correct step is set + $scope.wizardStep = "macroSelect"; + }); + + +} + +angular.module("umbraco").controller("Umbraco.Overlays.MacroPickerController", MacroPickerController); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/macropicker/macropicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/macropicker/macropicker.html new file mode 100644 index 0000000000..ecd8943a63 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/macropicker/macropicker.html @@ -0,0 +1,38 @@ +
    + +
    + + + + + + + +
    + +
    {{model.selectedMacro.name}}
    + +
      +
    • + + + + + + + +
    • +
    + +
    There are no parameters for this macro
    + +
    +
    + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js new file mode 100644 index 0000000000..48ac33125c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js @@ -0,0 +1,250 @@ +//used for the media picker dialog +angular.module("umbraco") + .controller("Umbraco.Overlays.MediaPickerController", + function ($scope, mediaResource, umbRequestHelper, entityResource, $log, mediaHelper, eventsService, treeService, $element, $timeout, $cookies, $cookieStore) { + + var dialogOptions = $scope.model; + + $scope.onlyImages = dialogOptions.onlyImages; + $scope.showDetails = dialogOptions.showDetails; + $scope.multiPicker = (dialogOptions.multiPicker && dialogOptions.multiPicker !== "0") ? true : false; + $scope.startNodeId = dialogOptions.startNodeId ? dialogOptions.startNodeId : -1; + $scope.cropSize = dialogOptions.cropSize; + $scope.lastOpenedNode = $cookieStore.get("umbLastOpenedMediaNodeId"); + $scope.acceptedFileTypes = mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes); + $scope.maxFileSize = Umbraco.Sys.ServerVariables.umbracoSettings.maxFileSize + "KB"; + + $scope.model.selectedImages = []; + + //preload selected item + $scope.target = undefined; + if(dialogOptions.currentTarget){ + $scope.target = dialogOptions.currentTarget; + } + + $scope.upload = function(v){ + angular.element(".umb-file-dropzone-directive .file-select").click(); + }; + + $scope.dragLeave = function(el, event){ + $scope.activeDrag = false; + }; + + $scope.dragEnter = function(el, event){ + $scope.activeDrag = true; + }; + + $scope.submitFolder = function() { + + if ($scope.newFolderName) { + + mediaResource + .addFolder($scope.newFolderName, $scope.currentFolder.id) + .then(function(data) { + + //we've added a new folder so lets clear the tree cache for that specific item + treeService.clearCache({ + cacheKey: "__media", //this is the main media tree cache key + childrenOf: data.parentId //clear the children of the parent + }); + + $scope.gotoFolder(data); + + $scope.showFolderInput = false; + + $scope.newFolderName = ""; + + }); + + } else { + $scope.showFolderInput = false; + } + + }; + + $scope.enterSubmitFolder = function(event) { + if (event.keyCode === 13) { + $scope.submitFolder(); + event.stopPropagation(); + } + }; + + $scope.gotoFolder = function(folder) { + + if(!folder){ + folder = {id: -1, name: "Media", icon: "icon-folder"}; + } + + if (folder.id > 0) { + entityResource.getAncestors(folder.id, "media") + .then(function(anc) { + // anc.splice(0,1); + $scope.path = _.filter(anc, function (f) { + return f.path.indexOf($scope.startNodeId) !== -1; + }); + }); + } + else { + $scope.path = []; + } + + //mediaResource.rootMedia() + mediaResource.getChildren(folder.id) + .then(function(data) { + $scope.searchTerm = ""; + $scope.images = data.items ? data.items : []; + + // set already selected images to selected + for (var folderImageIndex = 0; folderImageIndex < $scope.images.length; folderImageIndex++) { + + var folderImage = $scope.images[folderImageIndex]; + var imageIsSelected = false; + + for (var selectedImageIndex = 0; selectedImageIndex < $scope.model.selectedImages.length; selectedImageIndex++) { + var selectedImage = $scope.model.selectedImages[selectedImageIndex]; + + if(folderImage.key === selectedImage.key) { + imageIsSelected = true; + } + } + + if(imageIsSelected) { + folderImage.selected = true; + } + } + + }); + + $scope.currentFolder = folder; + + // for some reason i cannot set cookies with cookieStore + document.cookie="umbLastOpenedMediaNodeId=" + folder.id; + + }; + + $scope.clickHandler = function(image, event, index) { + + if (image.isFolder) { + $scope.gotoFolder(image); + } else { + + eventsService.emit("dialogs.mediaPicker.select", image); + + if($scope.showDetails) { + $scope.target = image; + $scope.target.url = mediaHelper.resolveFile(image); + $scope.openDetailsDialog(); + } else { + selectImage(image); + } + + } + + }; + + function selectImage(image) { + + if ($scope.model.selectedImages.length > 0) { + + var selectImage = false; + + for (var i = 0; i < $scope.model.selectedImages.length; i++) { + + var selectedImage = $scope.model.selectedImages[i]; + + if (image.key === selectedImage.key) { + image.selected = false; + $scope.model.selectedImages.splice(i, 1); + selectImage = false; + } else { + selectImage = true; + } + + } + + if (selectImage) { + + if(!$scope.multiPicker) { + deselectAllImages($scope.model.selectedImages); + } + + image.selected = true; + $scope.model.selectedImages.push(image); + } + + } else { + $scope.model.selectedImages.push(image); + image.selected = true; + } + + } + + function deselectAllImages(images) { + for (var i = 0; i < images.length; i++) { + var image = images[i]; + image.selected = false; + } + images.length = 0; + } + + $scope.onUploadComplete = function () { + $scope.gotoFolder($scope.currentFolder); + }; + + $scope.onFilesQueue = function(){ + $scope.activeDrag = false; + }; + + //default root item + if (!$scope.target) { + + if($scope.lastOpenedNode && $scope.lastOpenedNode !== -1) { + + entityResource.getById($scope.lastOpenedNode, "media") + .then(function(node){ + + // make sure that las opened node is on the same path as start node + var nodePath = node.path.split(","); + + if(nodePath.indexOf($scope.startNodeId.toString()) !== -1) { + $scope.gotoFolder({id: $scope.lastOpenedNode, name: "Media", icon: "icon-folder"}); + } else { + $scope.gotoFolder({id: $scope.startNodeId, name: "Media", icon: "icon-folder"}); + } + + }, function (err) { + $scope.gotoFolder({id: $scope.startNodeId, name: "Media", icon: "icon-folder"}); + }); + + } else { + + $scope.gotoFolder({id: $scope.startNodeId, name: "Media", icon: "icon-folder"}); + + } + + } + + $scope.openDetailsDialog = function() { + + $scope.mediaPickerDetailsOverlay = {}; + $scope.mediaPickerDetailsOverlay.show = true; + + $scope.mediaPickerDetailsOverlay.submit = function(model) { + + $scope.model.selectedImages.push($scope.target); + $scope.model.submit($scope.model); + + $scope.mediaPickerDetailsOverlay.show = false; + $scope.mediaPickerDetailsOverlay = null; + + }; + + $scope.mediaPickerDetailsOverlay.close = function(oldModel) { + $scope.mediaPickerDetailsOverlay.show = false; + $scope.mediaPickerDetailsOverlay = null; + }; + + }; + + + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.html new file mode 100644 index 0000000000..68752df784 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.html @@ -0,0 +1,127 @@ +
    + +
    + +
    + + + +
    + + +
    + +
    + +
    + + +
    + + + + + + + +
    + + + +
    + +
    + + +
    + +
    + +
    Preview
    + + + + +
    + +
    + +
    + + +
    + +
    + + +
    + + +
    + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/membergrouppicker/membergrouppicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/membergrouppicker/membergrouppicker.controller.js new file mode 100644 index 0000000000..4457ac8f90 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/membergrouppicker/membergrouppicker.controller.js @@ -0,0 +1,60 @@ +//used for the member picker dialog +angular.module("umbraco").controller("Umbraco.Overlays.MemberGroupPickerController", + function($scope, eventsService, entityResource, searchService, $log) { + + $scope.dialogTreeEventHandler = $({}); + $scope.multiPicker = $scope.model.multiPicker; + + function activate() { + + if($scope.multiPicker) { + $scope.model.selectedMemberGroups = []; + } else { + $scope.model.selectedMemberGroup = ""; + } + + } + + function selectMemberGroup(id) { + $scope.model.selectedMemberGroup = id; + } + + function selectMemberGroups(id) { + $scope.model.selectedMemberGroups.push(id); + } + + /** Method used for selecting a node */ + function select(text, id) { + + if ($scope.model.multiPicker) { + selectMemberGroups(id); + } + else { + selectMemberGroup(id); + $scope.model.submit($scope.model); + } + } + + function nodeSelectHandler(ev, args) { + args.event.preventDefault(); + args.event.stopPropagation(); + + eventsService.emit("dialogs.memberGroupPicker.select", args); + + //This is a tree node, so we don't have an entity to pass in, it will need to be looked up + //from the server in this method. + select(args.node.name, args.node.id); + + //toggle checked state + args.node.selected = args.node.selected === true ? false : true; + } + + $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); + + $scope.$on('$destroy', function () { + $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); + }); + + activate(); + + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/membergrouppicker/membergrouppicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/membergrouppicker/membergrouppicker.html new file mode 100644 index 0000000000..22fdeaf8be --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/membergrouppicker/membergrouppicker.html @@ -0,0 +1,12 @@ +
    + + + + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/memberpicker/memberpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/memberpicker/memberpicker.html new file mode 100644 index 0000000000..e4a8d36263 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/memberpicker/memberpicker.html @@ -0,0 +1,34 @@ +
    + +
    + + +
    + + + + +
    + + +
    + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/move/move.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/move/move.controller.js new file mode 100644 index 0000000000..abe58b9a02 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/move/move.controller.js @@ -0,0 +1,114 @@ + (function() { + "use strict"; + + function MoveOverlay($scope, localizationService, eventsService) { + + var vm = this; + + vm.hideSearch = hideSearch; + vm.selectResult = selectResult; + vm.onSearchResults = onSearchResults; + + var dialogOptions = $scope.model; + var searchText = "Search..."; + var node = dialogOptions.currentNode; + + localizationService.localize("general_search").then(function (value) { + searchText = value + "..."; + }); + + $scope.model.relateToOriginal = true; + $scope.dialogTreeEventHandler = $({}); + + vm.searchInfo = { + searchFromId: null, + searchFromName: null, + showSearch: false, + results: [], + selectedSearchResults: [] + }; + + function nodeSelectHandler(ev, args) { + args.event.preventDefault(); + args.event.stopPropagation(); + + if (args.node.metaData.listViewNode) { + //check if list view 'search' node was selected + + vm.searchInfo.showSearch = true; + vm.searchInfo.searchFromId = args.node.metaData.listViewNode.id; + vm.searchInfo.searchFromName = args.node.metaData.listViewNode.name; + } + else { + //eventsService.emit("editors.content.copyController.select", args); + + if ($scope.model.target) { + //un-select if there's a current one selected + $scope.model.target.selected = false; + } + + $scope.model.target = args.node; + $scope.model.target.selected = true; + } + + } + + function nodeExpandedHandler(ev, args) { + if (angular.isArray(args.children)) { + + //iterate children + _.each(args.children, function (child) { + //check if any of the items are list views, if so we need to add a custom + // child: A node to activate the search + if (child.metaData.isContainer) { + child.hasChildren = true; + child.children = [ + { + level: child.level + 1, + hasChildren: false, + name: searchText, + metaData: { + listViewNode: child, + }, + cssClass: "icon umb-tree-icon sprTree icon-search", + cssClasses: ["not-published"] + } + ]; + } + }); + } + } + + function hideSearch() { + vm.searchInfo.showSearch = false; + vm.searchInfo.searchFromId = null; + vm.searchInfo.searchFromName = null; + vm.searchInfo.results = []; + } + + // method to select a search result + function selectResult(evt, result) { + result.selected = result.selected === true ? false : true; + nodeSelectHandler(evt, { event: evt, node: result }); + } + + //callback when there are search results + function onSearchResults(results) { + vm.searchInfo.results = results; + vm.searchInfo.showSearch = true; + } + + $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); + $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler); + + $scope.$on('$destroy', function () { + $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); + $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler); + }); + + + } + + angular.module("umbraco").controller("Umbraco.Overlays.MoveOverlay", MoveOverlay); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/move/move.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/move/move.html new file mode 100644 index 0000000000..c30f911cd9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/move/move.html @@ -0,0 +1,33 @@ +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js new file mode 100644 index 0000000000..76fc8509ce --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js @@ -0,0 +1,478 @@ +//used for the media picker dialog +angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController", + function ($scope, entityResource, eventsService, $log, searchService, angularHelper, $timeout, localizationService, treeService) { + + var tree = null; + var dialogOptions = $scope.model; + $scope.dialogTreeEventHandler = $({}); + $scope.section = dialogOptions.section; + $scope.treeAlias = dialogOptions.treeAlias; + $scope.multiPicker = dialogOptions.multiPicker; + $scope.hideHeader = true; + $scope.searchInfo = { + searchFromId: dialogOptions.startNodeId, + searchFromName: null, + showSearch: false, + results: [], + selectedSearchResults: [] + } + + $scope.model.selection = []; + + $scope.init = function(contentType) { + + if(contentType === "content") { + entityType = "Document"; + } else if(contentType === "member") { + entityType = "Member"; + } else if(contentType === "media") { + entityType = "Media"; + } + } + + //create the custom query string param for this tree + $scope.customTreeParams = dialogOptions.startNodeId ? "startNodeId=" + dialogOptions.startNodeId : ""; + $scope.customTreeParams += dialogOptions.customTreeParams ? "&" + dialogOptions.customTreeParams : ""; + + var searchText = "Search..."; + localizationService.localize("general_search").then(function (value) { + searchText = value + "..."; + }); + + // Allow the entity type to be passed in but defaults to Document for backwards compatibility. + var entityType = dialogOptions.entityType ? dialogOptions.entityType : "Document"; + + + //min / max values + if (dialogOptions.minNumber) { + dialogOptions.minNumber = parseInt(dialogOptions.minNumber, 10); + } + if (dialogOptions.maxNumber) { + dialogOptions.maxNumber = parseInt(dialogOptions.maxNumber, 10); + } + + if (dialogOptions.section === "member") { + entityType = "Member"; + } + else if (dialogOptions.section === "media") { + entityType = "Media"; + } + + //Configures filtering + if (dialogOptions.filter) { + + dialogOptions.filterExclude = false; + dialogOptions.filterAdvanced = false; + + //used advanced filtering + if (angular.isFunction(dialogOptions.filter)) { + dialogOptions.filterAdvanced = true; + } + else if (angular.isObject(dialogOptions.filter)) { + dialogOptions.filterAdvanced = true; + } + else { + if (dialogOptions.filter.startsWith("!")) { + dialogOptions.filterExclude = true; + dialogOptions.filter = dialogOptions.filter.substring(1); + } + + //used advanced filtering + if (dialogOptions.filter.startsWith("{")) { + dialogOptions.filterAdvanced = true; + //convert to object + dialogOptions.filter = angular.fromJson(dialogOptions.filter); + } + } + } + + function nodeExpandedHandler(ev, args) { + if (angular.isArray(args.children)) { + + //iterate children + _.each(args.children, function (child) { + + //check if any of the items are list views, if so we need to add some custom + // children: A node to activate the search, any nodes that have already been + // selected in the search + if (child.metaData.isContainer) { + child.hasChildren = true; + child.children = [ + { + level: child.level + 1, + hasChildren: false, + parent: function () { + return child; + }, + name: searchText, + metaData: { + listViewNode: child, + }, + cssClass: "icon-search", + cssClasses: ["not-published"] + } + ]; + //add base transition classes to this node + child.cssClasses.push("tree-node-slide-up"); + + var listViewResults = _.filter($scope.searchInfo.selectedSearchResults, function(i) { + return i.parentId == child.id; + }); + _.each(listViewResults, function(item) { + child.children.unshift({ + id: item.id, + name: item.name, + cssClass: "icon umb-tree-icon sprTree " + item.icon, + level: child.level + 1, + metaData: { + isSearchResult: true + }, + hasChildren: false, + parent: function () { + return child; + } + }); + }); + } + + //now we need to look in the already selected search results and + // toggle the check boxes for those ones that are listed + var exists = _.find($scope.searchInfo.selectedSearchResults, function (selected) { + return child.id == selected.id; + }); + if (exists) { + child.selected = true; + } + }); + + //check filter + performFiltering(args.children); + } + } + + //gets the tree object when it loads + function treeLoadedHandler(ev, args) { + tree = args.tree; + } + + //wires up selection + function nodeSelectHandler(ev, args) { + args.event.preventDefault(); + args.event.stopPropagation(); + + if (args.node.metaData.listViewNode) { + //check if list view 'search' node was selected + + $scope.searchInfo.showSearch = true; + $scope.searchInfo.searchFromId = args.node.metaData.listViewNode.id; + $scope.searchInfo.searchFromName = args.node.metaData.listViewNode.name; + + //add transition classes + var listViewNode = args.node.parent(); + listViewNode.cssClasses.push('tree-node-slide-up-hide-active'); + } + else if (args.node.metaData.isSearchResult) { + //check if the item selected was a search result from a list view + + //unselect + select(args.node.name, args.node.id); + + //remove it from the list view children + var listView = args.node.parent(); + listView.children = _.reject(listView.children, function(child) { + return child.id == args.node.id; + }); + + //remove it from the custom tracked search result list + $scope.searchInfo.selectedSearchResults = _.reject($scope.searchInfo.selectedSearchResults, function (i) { + return i.id == args.node.id; + }); + } + else { + eventsService.emit("dialogs.treePickerController.select", args); + + if (args.node.filtered) { + return; + } + + //This is a tree node, so we don't have an entity to pass in, it will need to be looked up + //from the server in this method. + select(args.node.name, args.node.id); + + //toggle checked state + args.node.selected = args.node.selected === true ? false : true; + } + } + + /** Method used for selecting a node */ + function select(text, id, entity) { + //if we get the root, we just return a constructed entity, no need for server data + if (id < 0) { + if ($scope.multiPicker) { + + if (entity) { + multiSelectItem(entity); + } else { + //otherwise we have to get it from the server + entityResource.getById(id, entityType).then(function (ent) { + multiSelectItem(ent); + }); + } + + } + else { + var node = { + alias: null, + icon: "icon-folder", + id: id, + name: text + }; + $scope.model.selection.push(node); + $scope.model.submit($scope.model); + } + } + else { + + if ($scope.multiPicker) { + + if (entity) { + multiSelectItem(entity); + } else { + //otherwise we have to get it from the server + entityResource.getById(id, entityType).then(function (ent) { + multiSelectItem(ent); + }); + } + + } + + else { + + $scope.hideSearch(); + + //if an entity has been passed in, use it + if (entity) { + $scope.model.selection.push(entity); + $scope.model.submit($scope.model); + } else { + //otherwise we have to get it from the server + entityResource.getById(id, entityType).then(function (ent) { + $scope.model.selection.push(ent); + $scope.model.submit($scope.model); + }); + } + } + } + } + + function multiSelectItem(item) { + + var i = $scope.model.selection.indexOf(item); + + if (i < 0) { + $scope.model.selection.push(item); + } else { + $scope.model.selection.splice(i, 1); + } + + } + + function performFiltering(nodes) { + + if (!dialogOptions.filter) { + return; + } + + //remove any list view search nodes from being filtered since these are special nodes that always must + // be allowed to be clicked on + nodes = _.filter(nodes, function(n) { + return !angular.isObject(n.metaData.listViewNode); + }); + + if (dialogOptions.filterAdvanced) { + + //filter either based on a method or an object + var filtered = angular.isFunction(dialogOptions.filter) + ? _.filter(nodes, dialogOptions.filter) + : _.where(nodes, dialogOptions.filter); + + angular.forEach(filtered, function (value, key) { + value.filtered = true; + if (dialogOptions.filterCssClass) { + if (!value.cssClasses) { + value.cssClasses = []; + } + value.cssClasses.push(dialogOptions.filterCssClass); + } + }); + } else { + var a = dialogOptions.filter.toLowerCase().split(','); + angular.forEach(nodes, function (value, key) { + + var found = a.indexOf(value.metaData.contentType.toLowerCase()) >= 0; + + if (!dialogOptions.filterExclude && !found || dialogOptions.filterExclude && found) { + value.filtered = true; + + if (dialogOptions.filterCssClass) { + if (!value.cssClasses) { + value.cssClasses = []; + } + value.cssClasses.push(dialogOptions.filterCssClass); + } + } + }); + } + } + + $scope.multiSubmit = function (result) { + entityResource.getByIds(result, entityType).then(function (ents) { + $scope.submit(ents); + }); + }; + + /** method to select a search result */ + $scope.selectResult = function (evt, result) { + + if (result.filtered) { + return; + } + + result.selected = result.selected === true ? false : true; + + //since result = an entity, we'll pass it in so we don't have to go back to the server + select(result.name, result.id, result); + + //add/remove to our custom tracked list of selected search results + if (result.selected) { + $scope.searchInfo.selectedSearchResults.push(result); + } + else { + $scope.searchInfo.selectedSearchResults = _.reject($scope.searchInfo.selectedSearchResults, function(i) { + return i.id == result.id; + }); + } + + //ensure the tree node in the tree is checked/unchecked if it already exists there + if (tree) { + var found = treeService.getDescendantNode(tree.root, result.id); + if (found) { + found.selected = result.selected; + } + } + + }; + + $scope.hideSearch = function () { + + //Traverse the entire displayed tree and update each node to sync with the selected search results + if (tree) { + + //we need to ensure that any currently displayed nodes that get selected + // from the search get updated to have a check box! + function checkChildren(children) { + _.each(children, function (child) { + //check if the id is in the selection, if so ensure it's flagged as selected + var exists = _.find($scope.searchInfo.selectedSearchResults, function (selected) { + return child.id == selected.id; + }); + //if the curr node exists in selected search results, ensure it's checked + if (exists) { + child.selected = true; + } + //if the curr node does not exist in the selected search result, and the curr node is a child of a list view search result + else if (child.metaData.isSearchResult) { + //if this tree node is under a list view it means that the node was added + // to the tree dynamically under the list view that was searched, so we actually want to remove + // it all together from the tree + var listView = child.parent(); + listView.children = _.reject(listView.children, function(c) { + return c.id == child.id; + }); + } + + //check if the current node is a list view and if so, check if there's any new results + // that need to be added as child nodes to it based on search results selected + if (child.metaData.isContainer) { + + child.cssClasses = _.reject(child.cssClasses, function(c) { + return c === 'tree-node-slide-up-hide-active'; + }); + + var listViewResults = _.filter($scope.searchInfo.selectedSearchResults, function (i) { + return i.parentId == child.id; + }); + _.each(listViewResults, function (item) { + var childExists = _.find(child.children, function(c) { + return c.id == item.id; + }); + if (!childExists) { + var parent = child; + child.children.unshift({ + id: item.id, + name: item.name, + cssClass: "icon umb-tree-icon sprTree " + item.icon, + level: child.level + 1, + metaData: { + isSearchResult: true + }, + hasChildren: false, + parent: function () { + return parent; + } + }); + } + }); + } + + //recurse + if (child.children && child.children.length > 0) { + checkChildren(child.children); + } + }); + } + checkChildren(tree.root.children); + } + + + $scope.searchInfo.showSearch = false; + $scope.searchInfo.searchFromId = dialogOptions.startNodeId; + $scope.searchInfo.searchFromName = null; + $scope.searchInfo.results = []; + } + + $scope.onSearchResults = function(results) { + + //filter all items - this will mark an item as filtered + performFiltering(results); + + //now actually remove all filtered items so they are not even displayed + results = _.filter(results, function(item) { + return !item.filtered; + }); + + $scope.searchInfo.results = results; + + //sync with the curr selected results + _.each($scope.searchInfo.results, function (result) { + var exists = _.find($scope.model.selection, function (selectedId) { + return result.id == selectedId; + }); + if (exists) { + result.selected = true; + } + }); + + $scope.searchInfo.showSearch = true; + }; + + $scope.dialogTreeEventHandler.bind("treeLoaded", treeLoadedHandler); + $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler); + $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); + + $scope.$on('$destroy', function () { + $scope.dialogTreeEventHandler.unbind("treeLoaded", treeLoadedHandler); + $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler); + $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); + }); + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html new file mode 100644 index 0000000000..4e9f503013 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html @@ -0,0 +1,34 @@ +
    + +
    + + +
    + + + + +
    + + +
    + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.controller.js new file mode 100644 index 0000000000..d76dac9bdf --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.controller.js @@ -0,0 +1,169 @@ +angular.module("umbraco") + .controller("Umbraco.Overlays.UserController", function ($scope, $location, $timeout, userService, historyService, eventsService, externalLoginInfo, authResource, currentUserResource, formHelper) { + + $scope.history = historyService.getCurrent(); + $scope.version = Umbraco.Sys.ServerVariables.application.version + " assembly: " + Umbraco.Sys.ServerVariables.application.assemblyVersion; + $scope.showPasswordFields = false; + $scope.changePasswordButtonState = "init"; + $scope.model.subtitle = "Umbraco version" + " " + $scope.version; + + $scope.externalLoginProviders = externalLoginInfo.providers; + $scope.externalLinkLoginFormAction = Umbraco.Sys.ServerVariables.umbracoUrls.externalLinkLoginsUrl; + var evts = []; + evts.push(eventsService.on("historyService.add", function (e, args) { + $scope.history = args.all; + })); + evts.push(eventsService.on("historyService.remove", function (e, args) { + $scope.history = args.all; + })); + evts.push(eventsService.on("historyService.removeAll", function (e, args) { + $scope.history = []; + })); + + $scope.logout = function () { + + //Add event listener for when there are pending changes on an editor which means our route was not successful + var pendingChangeEvent = eventsService.on("valFormManager.pendingChanges", function (e, args) { + //one time listener, remove the event + pendingChangeEvent(); + $scope.closeOverLay(); + }); + + + //perform the path change, if it is successful then the promise will resolve otherwise it will fail + $scope.closeOverLay(); + $location.path("/logout"); + }; + + $scope.gotoHistory = function (link) { + $location.path(link); + $scope.closeOverLay(); + }; + + //Manually update the remaining timeout seconds + function updateTimeout() { + $timeout(function () { + if ($scope.remainingAuthSeconds > 0) { + $scope.remainingAuthSeconds--; + $scope.$digest(); + //recurse + updateTimeout(); + } + + }, 1000, false); // 1 second, do NOT execute a global digest + } + + function updateUserInfo() { + //get the user + userService.getCurrentUser().then(function (user) { + $scope.user = user; + if ($scope.user) { + $scope.model.title = user.name; + $scope.remainingAuthSeconds = $scope.user.remainingAuthSeconds; + $scope.canEditProfile = _.indexOf($scope.user.allowedSections, "users") > -1; + //set the timer + updateTimeout(); + + authResource.getCurrentUserLinkedLogins().then(function(logins) { + //reset all to be un-linked + for (var provider in $scope.externalLoginProviders) { + $scope.externalLoginProviders[provider].linkedProviderKey = undefined; + } + + //set the linked logins + for (var login in logins) { + var found = _.find($scope.externalLoginProviders, function (i) { + return i.authType == login; + }); + if (found) { + found.linkedProviderKey = logins[login]; + } + } + }); + } + }); + } + + $scope.unlink = function (e, loginProvider, providerKey) { + var result = confirm("Are you sure you want to unlink this account?"); + if (!result) { + e.preventDefault(); + return; + } + + authResource.unlinkLogin(loginProvider, providerKey).then(function (a, b, c) { + updateUserInfo(); + }); + } + + updateUserInfo(); + + //remove all event handlers + $scope.$on('$destroy', function () { + for (var e = 0; e < evts.length; e++) { + evts[e](); + } + + }); + + /* ---------- UPDATE PASSWORD ---------- */ + + //create the initial model for change password property editor + $scope.changePasswordModel = { + alias: "_umb_password", + view: "changepassword", + config: {}, + value: {} + }; + + //go get the config for the membership provider and add it to the model + currentUserResource.getMembershipProviderConfig().then(function(data) { + $scope.changePasswordModel.config = data; + //ensure the hasPassword config option is set to true (the user of course has a password already assigned) + //this will ensure the oldPassword is shown so they can change it + // disable reset password functionality beacuse it does not make sense inside the backoffice + $scope.changePasswordModel.config.hasPassword = true; + $scope.changePasswordModel.config.disableToggle = true; + $scope.changePasswordModel.config.enableReset = false; + }); + + $scope.changePassword = function() { + + if (formHelper.submitForm({ scope: $scope })) { + + $scope.changePasswordButtonState = "busy"; + + currentUserResource.changePassword($scope.changePasswordModel.value).then(function(data) { + + //if the password has been reset, then update our model + if (data.value) { + $scope.changePasswordModel.value.generatedPassword = data.value; + } + + formHelper.resetForm({ scope: $scope, notifications: data.notifications }); + + $scope.changePasswordButtonState = "success"; + + }, function (err) { + + formHelper.handleError(err); + + $scope.changePasswordButtonState = "error"; + + }); + + } + + }; + + $scope.togglePasswordFields = function() { + clearPasswordFields(); + $scope.showPasswordFields = !$scope.showPasswordFields; + } + + function clearPasswordFields() { + $scope.changePasswordModel.value.newPassword = ""; + $scope.changePasswordModel.confirm = ""; + } + + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.html new file mode 100644 index 0000000000..4d4a37dd33 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.html @@ -0,0 +1,108 @@ +
    + +
    + +
    + +

    + + : {{remainingAuthSeconds | timespan}} + +

    + + + Edit + + + + + + + +
    + +
    + +
    External login providers
    + +
    + +
    + + +
    + + +
    + +
    + +
    +
    + +
    + +
    + +
    Change password
    + +
    + + + + + + + + + +
    + +
    + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/ysod/ysod.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/ysod/ysod.controller.js new file mode 100644 index 0000000000..2b07b8830f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/ysod/ysod.controller.js @@ -0,0 +1,13 @@ +angular.module("umbraco") + .controller("Umbraco.Overlays.YsodController", function ($scope, legacyResource, treeService, navigationService) { + + if (!$scope.model.title) { + $scope.model.title = "Received an error from the server"; + } + + if ($scope.model.error && $scope.model.error.data && $scope.model.error.data.StackTrace) { + //trim whitespace + $scope.model.error.data.StackTrace = $scope.model.error.data.StackTrace.trim(); + } + + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/ysod/ysod.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/ysod/ysod.html new file mode 100644 index 0000000000..4214f2d2d0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/ysod/ysod.html @@ -0,0 +1,15 @@ +
    + +

    {{model.error.errorMsg}}

    +

    {{model.error.data.ExceptionMessage || model.error.data.Message}}

    + +
    +
    Exception Details:
    + {{model.error.data.ExceptionType}}: {{model.error.data.ExceptionMessage}} +
    + +
    +
    Stacktrace:
    +
    {{model.error.data.StackTrace}}
    +
    +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/directives/umb-contextmenu.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-contextmenu.html similarity index 100% rename from src/Umbraco.Web.UI.Client/src/views/directives/umb-contextmenu.html rename to src/Umbraco.Web.UI.Client/src/views/components/application/umb-contextmenu.html diff --git a/src/Umbraco.Web.UI.Client/src/views/directives/umb-navigation.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-navigation.html similarity index 94% rename from src/Umbraco.Web.UI.Client/src/views/directives/umb-navigation.html rename to src/Umbraco.Web.UI.Client/src/views/components/application/umb-navigation.html index c686e569e7..c88dda456c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/directives/umb-navigation.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-navigation.html @@ -5,9 +5,9 @@ - \ No newline at end of file + + + + + + + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/buttons/umb-button-group.html b/src/Umbraco.Web.UI.Client/src/views/components/buttons/umb-button-group.html new file mode 100644 index 0000000000..087865a501 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/buttons/umb-button-group.html @@ -0,0 +1,24 @@ +
    + + + + + + + + + + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/buttons/umb-button.html b/src/Umbraco.Web.UI.Client/src/views/components/buttons/umb-button.html new file mode 100644 index 0000000000..003031f834 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/buttons/umb-button.html @@ -0,0 +1,35 @@ +
    + +
    + +
    + +
    + +
    + + + + + {{label}} + {{label}} + + + + + + + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/editor/subheader/umb-editor-sub-header-content-left.html b/src/Umbraco.Web.UI.Client/src/views/components/editor/subheader/umb-editor-sub-header-content-left.html new file mode 100644 index 0000000000..9c5297908b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/editor/subheader/umb-editor-sub-header-content-left.html @@ -0,0 +1 @@ +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/editor/subheader/umb-editor-sub-header-content-right.html b/src/Umbraco.Web.UI.Client/src/views/components/editor/subheader/umb-editor-sub-header-content-right.html new file mode 100644 index 0000000000..982ea1868a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/editor/subheader/umb-editor-sub-header-content-right.html @@ -0,0 +1 @@ +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/editor/subheader/umb-editor-sub-header-section.html b/src/Umbraco.Web.UI.Client/src/views/components/editor/subheader/umb-editor-sub-header-section.html new file mode 100644 index 0000000000..1b2ab2e639 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/editor/subheader/umb-editor-sub-header-section.html @@ -0,0 +1 @@ +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/editor/subheader/umb-editor-sub-header.html b/src/Umbraco.Web.UI.Client/src/views/components/editor/subheader/umb-editor-sub-header.html new file mode 100644 index 0000000000..5a8e38c47c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/editor/subheader/umb-editor-sub-header.html @@ -0,0 +1,6 @@ +
    +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-breadcrumbs.html b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-breadcrumbs.html new file mode 100644 index 0000000000..a63880819b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-breadcrumbs.html @@ -0,0 +1,10 @@ + diff --git a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-container.html b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-container.html new file mode 100644 index 0000000000..44ced2e100 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-container.html @@ -0,0 +1,6 @@ +
    + +
    +
    +
    +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-footer-content-left.html b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-footer-content-left.html new file mode 100644 index 0000000000..3bab2ea3de --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-footer-content-left.html @@ -0,0 +1 @@ +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-footer-content-right.html b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-footer-content-right.html new file mode 100644 index 0000000000..9d58a97506 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-footer-content-right.html @@ -0,0 +1 @@ +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-footer.html b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-footer.html new file mode 100644 index 0000000000..3052636851 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-footer.html @@ -0,0 +1,7 @@ +
    + +
    + +
    + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-header.html b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-header.html new file mode 100644 index 0000000000..e637df94c6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-header.html @@ -0,0 +1,53 @@ +
    + +
    + +
    + +
    + +
    + +
    Add icon
    +
    + +
    + + + + + +
    {{ name }}
    + + + + + +
    + +
    + + + + +
    + + + +
    + + + + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-menu.html b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-menu.html new file mode 100644 index 0000000000..dbb145e533 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-menu.html @@ -0,0 +1,22 @@ +
    + + + + Actions + + + + + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-navigation.html b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-navigation.html new file mode 100644 index 0000000000..ec3260b646 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-navigation.html @@ -0,0 +1,15 @@ + diff --git a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-sub-views.html b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-sub-views.html new file mode 100644 index 0000000000..6327e9f310 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-sub-views.html @@ -0,0 +1,10 @@ +
    +
    +
    +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-toolbar.html b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-toolbar.html new file mode 100644 index 0000000000..3dc4bfa2e7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-toolbar.html @@ -0,0 +1,5 @@ +
    +
    + {{ tool.name }} +
    +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-view.html b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-view.html new file mode 100644 index 0000000000..a04ae13145 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-view.html @@ -0,0 +1,3 @@ +
    + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/directives/html/umb-control-group.html b/src/Umbraco.Web.UI.Client/src/views/components/html/umb-control-group.html similarity index 100% rename from src/Umbraco.Web.UI.Client/src/views/directives/html/umb-control-group.html rename to src/Umbraco.Web.UI.Client/src/views/components/html/umb-control-group.html diff --git a/src/Umbraco.Web.UI.Client/src/views/directives/html/umb-pane.html b/src/Umbraco.Web.UI.Client/src/views/components/html/umb-pane.html similarity index 100% rename from src/Umbraco.Web.UI.Client/src/views/directives/html/umb-pane.html rename to src/Umbraco.Web.UI.Client/src/views/components/html/umb-pane.html diff --git a/src/Umbraco.Web.UI.Client/src/views/directives/html/umb-panel.html b/src/Umbraco.Web.UI.Client/src/views/components/html/umb-panel.html similarity index 100% rename from src/Umbraco.Web.UI.Client/src/views/directives/html/umb-panel.html rename to src/Umbraco.Web.UI.Client/src/views/components/html/umb-panel.html diff --git a/src/Umbraco.Web.UI.Client/src/views/directives/imaging/umb-image-crop.html b/src/Umbraco.Web.UI.Client/src/views/components/imaging/umb-image-crop.html similarity index 100% rename from src/Umbraco.Web.UI.Client/src/views/directives/imaging/umb-image-crop.html rename to src/Umbraco.Web.UI.Client/src/views/components/imaging/umb-image-crop.html diff --git a/src/Umbraco.Web.UI.Client/src/views/directives/imaging/umb-image-gravity.html b/src/Umbraco.Web.UI.Client/src/views/components/imaging/umb-image-gravity.html similarity index 100% rename from src/Umbraco.Web.UI.Client/src/views/directives/imaging/umb-image-gravity.html rename to src/Umbraco.Web.UI.Client/src/views/components/imaging/umb-image-gravity.html diff --git a/src/Umbraco.Web.UI.Client/src/views/directives/imaging/umb-image-thumbnail.html b/src/Umbraco.Web.UI.Client/src/views/components/imaging/umb-image-thumbnail.html similarity index 100% rename from src/Umbraco.Web.UI.Client/src/views/directives/imaging/umb-image-thumbnail.html rename to src/Umbraco.Web.UI.Client/src/views/components/imaging/umb-image-thumbnail.html diff --git a/src/Umbraco.Web.UI.Client/src/views/components/notifications/umb-notifications.html b/src/Umbraco.Web.UI.Client/src/views/components/notifications/umb-notifications.html new file mode 100644 index 0000000000..9962e2dd84 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/notifications/umb-notifications.html @@ -0,0 +1,21 @@ +
    + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/overlays/umb-overlay-backdrop.html b/src/Umbraco.Web.UI.Client/src/views/components/overlays/umb-overlay-backdrop.html new file mode 100644 index 0000000000..23ffbf643f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/overlays/umb-overlay-backdrop.html @@ -0,0 +1 @@ +
    {{ numberOfOverlays }}
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/overlays/umb-overlay.html b/src/Umbraco.Web.UI.Client/src/views/components/overlays/umb-overlay.html new file mode 100644 index 0000000000..ad115f1afd --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/overlays/umb-overlay.html @@ -0,0 +1,31 @@ +
    +
    + +
    +

    {{model.title}}

    +

    {{model.subtitle}}

    +
    + +
    +
    +
    + +
    + +
    + +
    {{ model.itemDetails.title }}
    +
    + +
    {{ model.itemDetails.description }}
    + +
    + +
    +
    + + +
    +
    +
    +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property-editor.html b/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property-editor.html new file mode 100644 index 0000000000..b191662ad1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property-editor.html @@ -0,0 +1 @@ +
    \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property-group.html b/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property-group.html new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/Umbraco.Web.UI.Client/src/views/directives/umb-property.html b/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html similarity index 79% rename from src/Umbraco.Web.UI.Client/src/views/directives/umb-property.html rename to src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html index 16432bc1c4..7517795136 100644 --- a/src/Umbraco.Web.UI.Client/src/views/directives/umb-property.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html @@ -1,18 +1,19 @@ -
    - -
    - - - -
    - - -
    -
    -
    -
    -
    -
    +
    + +
    + + + +
    + + + +
    +
    +
    +
    +
    +
    \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/directives/umb-tab.html b/src/Umbraco.Web.UI.Client/src/views/components/tabs/umb-tab.html similarity index 100% rename from src/Umbraco.Web.UI.Client/src/views/directives/umb-tab.html rename to src/Umbraco.Web.UI.Client/src/views/components/tabs/umb-tab.html diff --git a/src/Umbraco.Web.UI.Client/src/views/components/tabs/umb-tabs-content.html b/src/Umbraco.Web.UI.Client/src/views/components/tabs/umb-tabs-content.html new file mode 100644 index 0000000000..8e1c7d4d67 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/tabs/umb-tabs-content.html @@ -0,0 +1 @@ +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/tabs/umb-tabs-nav.html b/src/Umbraco.Web.UI.Client/src/views/components/tabs/umb-tabs-nav.html new file mode 100644 index 0000000000..8522bc3b6a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/tabs/umb-tabs-nav.html @@ -0,0 +1,5 @@ + diff --git a/src/Umbraco.Web.UI.Client/src/views/directives/umb-tree-search-box.html b/src/Umbraco.Web.UI.Client/src/views/components/tree/umb-tree-search-box.html similarity index 100% rename from src/Umbraco.Web.UI.Client/src/views/directives/umb-tree-search-box.html rename to src/Umbraco.Web.UI.Client/src/views/components/tree/umb-tree-search-box.html diff --git a/src/Umbraco.Web.UI.Client/src/views/directives/umb-tree-search-results.html b/src/Umbraco.Web.UI.Client/src/views/components/tree/umb-tree-search-results.html similarity index 100% rename from src/Umbraco.Web.UI.Client/src/views/directives/umb-tree-search-results.html rename to src/Umbraco.Web.UI.Client/src/views/components/tree/umb-tree-search-results.html diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-child-selector.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-child-selector.html new file mode 100644 index 0000000000..c6a9be19c0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-child-selector.html @@ -0,0 +1,35 @@ +
    + +
    +
    +
    + +
    + + {{ parentName }} + + (Current) +
    +
    + +
    + +
    +
    +
    + +
    + {{ selectedChild.name }} +
    +
    + +
    +
    + + +
    Add child
    +
    + +
    + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-confirm-action.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-confirm-action.html new file mode 100644 index 0000000000..c34c3f9283 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-confirm-action.html @@ -0,0 +1,16 @@ +
    + + + + + + + + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/directives/umb-confirm.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-confirm.html similarity index 100% rename from src/Umbraco.Web.UI.Client/src/views/directives/umb-confirm.html rename to src/Umbraco.Web.UI.Client/src/views/components/umb-confirm.html 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 new file mode 100644 index 0000000000..52d7f3542b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-content-grid.html @@ -0,0 +1,32 @@ +
    + +
    + + + +
    + +
    + +
    + +
    {{ item.name }}
    + +
      +
    • +
      {{ property.header }}:
      +
      {{ item[property.alias] }}
      +
    • +
    + +
    + +
    + +
    There are no items to show
    + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-folder-grid.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-folder-grid.html new file mode 100644 index 0000000000..9d658a31a8 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-folder-grid.html @@ -0,0 +1,18 @@ +
    + +
    + +
    + +
    {{ folder.name }}
    +
    + + + +
    + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-generate-alias.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-generate-alias.html new file mode 100644 index 0000000000..d68b1cc104 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-generate-alias.html @@ -0,0 +1,10 @@ +
    + {{ alias }} +
    + + +
    +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-grid-selector.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-grid-selector.html new file mode 100644 index 0000000000..b269972e96 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-grid-selector.html @@ -0,0 +1,43 @@ +
    + +
    + +
    +
    + +
    {{ defaultItem.name }}
    + (Default {{itemLabel}}) +
    + +
    + +
    +
    + +
    {{ selectedItem.name }}
    + Set as default +
    + +
    + + +
    +
    Choose extra {{ itemLabel }}
    +
    Choose default {{ itemLabel }}
    +
    +
    + +
    + +
    + All {{itemLabel}}s are added +
    + + + + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-groups-builder.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-groups-builder.html new file mode 100644 index 0000000000..c46142bf38 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-groups-builder.html @@ -0,0 +1,207 @@ +
    + + + +
    You have not added any tabs to reorder
    + +
      + +
    • + + + + +
      +
      +
      + +
      Add new tab
      +
      Add another tab
      + +
      + + +
      + +
      + + + +
      + +
      + + +
      + + + +
      +
      + +
      +
      + + + +
      + Inherited from {{ tab.inheritedFromName }} + + {{ contentTypeName }} + , + +
      + +
      + +
        + +
      • + + + + +
        +
        +
        +
        +
        +
        +
        +
        +
        + +
        +
        Add property
        +
        + +
        + +
        + +
        + + +
        + +
        + Inherited from {{property.contentTypeName}} +
        + + +
        + +
        {{ property.alias }}
        + + + +
        + + +
        +
        Required label
        +
        + +
        + +
        +
        +
        + +
        + + {{ property.label }} + ({{ property.alias }}) +
        + +
        + + +
        + + + + + {{property.dataTypeName}} + Preview + + + + +
        + + +
        + +
        + + +
        + +
        + + +
        + + + +
        + +
        + +
        + +
        + +
      • + +
      + +
      + +
      + +
    • + +
    + + + + + + + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-keyboard-shortcuts-overview.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-keyboard-shortcuts-overview.html new file mode 100644 index 0000000000..b253123779 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-keyboard-shortcuts-overview.html @@ -0,0 +1,60 @@ +
    + +
    +
    show shortcuts
    +
    +
    +
    alt
    +
    +
    +
    +
    +
    shift
    +
    +
    +
    +
    +
    k
    +
    +
    +
    + +
    + + + + + +
    + +
    + +
    {{ keyboardShortcutGroup.name }}
    + +
    + +
    +
    {{ keyboardShortcut.description }}
    +
    + +
    + +
    +
    {{ key.key }}
    +
    + + + - +
    +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-layout-selector.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-layout-selector.html new file mode 100644 index 0000000000..a7b2e0e556 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-layout-selector.html @@ -0,0 +1,15 @@ +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-list-view-layout.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-list-view-layout.html new file mode 100644 index 0000000000..a4ea2d2481 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-list-view-layout.html @@ -0,0 +1,3 @@ +
    +
    +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-list-view-settings.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-list-view-settings.html new file mode 100644 index 0000000000..f9e5eef63a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-list-view-settings.html @@ -0,0 +1,39 @@ +
    + +
    + +
    + + +
    +
    + +
    + +
    +
    +
    {{ dataType.name }} (default)
    + +
    + Create custom list view + Remove custom list view +
    +
    + +
    + + +
    + + + + + + + + +
    +
    +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-load-indicator.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-load-indicator.html new file mode 100644 index 0000000000..6573dcbfde --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-load-indicator.html @@ -0,0 +1,5 @@ +
      +
    • +
    • +
    • +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-locked-field.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-locked-field.html new file mode 100644 index 0000000000..dca8a01ef8 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-locked-field.html @@ -0,0 +1,32 @@ + + +
    + + + + + + + + + + + +
    + +
    Required alias
    +
    Invalid alias
    +
    + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-media-grid.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-media-grid.html new file mode 100644 index 0000000000..bdcf0da482 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-media-grid.html @@ -0,0 +1,24 @@ + diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-pagination.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-pagination.html new file mode 100644 index 0000000000..9e4a1697a3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-pagination.html @@ -0,0 +1,26 @@ + 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 new file mode 100644 index 0000000000..06dd1c16b2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-table.html @@ -0,0 +1,75 @@ +
    + +
    + + +
    +
    + +
    + +
    + + + + + +
    +
    + + +
    +
    + + +
    + + +
    + +
    + + +
    + +
    + {{item[column.alias]}} +
    + +
    +
    + +
    + + +
    + There are no items show in the list. +
    + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-tooltip.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-tooltip.html new file mode 100644 index 0000000000..18095e599c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-tooltip.html @@ -0,0 +1 @@ +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/upload/umb-file-dropzone.html b/src/Umbraco.Web.UI.Client/src/views/components/upload/umb-file-dropzone.html new file mode 100644 index 0000000000..dc05556bab --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/upload/umb-file-dropzone.html @@ -0,0 +1,101 @@ +
    + + + + +
    + + +
    + + + + + + +
    + - or click here to choose files +
    +
    +
    + + +
      + + +
    • + + +
      {{ file.name }}
      + + +
      + +
      + +
    • + +
    • + + +
      {{ currentFile.name }}
      + + +
      + +
      +
    • + + +
    • + + +
      {{ queued.name }}
      +
    • + +
    • + + +
      + + {{ file.name }} + + + (Only allowed file types are: "{{ accept }}") + (Max file size is "{{maxFileSize}}") + + + + ({{file.serverErrorMessage}}) + + +
      + + +
      + +
      + +
    • +
    + +
    + +
    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 fc3015ed09..9fb6e60c66 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 @@ -1,5 +1,5 @@ angular.module("umbraco").controller("Umbraco.Editors.Content.CopyController", - function ($scope, eventsService, contentResource, navigationService, appState, treeService, localizationService) { + function ($scope, eventsService, contentResource, navigationService, appState, treeService, localizationService, notificationsService) { var dialogOptions = $scope.dialogOptions; var searchText = "Search..."; @@ -7,7 +7,8 @@ angular.module("umbraco").controller("Umbraco.Editors.Content.CopyController", searchText = value + "..."; }); - $scope.relateToOriginal = false; + $scope.relateToOriginal = true; + $scope.recursive = true; $scope.dialogTreeEventHandler = $({}); $scope.busy = false; $scope.searchInfo = { @@ -42,7 +43,7 @@ angular.module("umbraco").controller("Umbraco.Editors.Content.CopyController", $scope.target = args.node; $scope.target.selected = true; } - + } function nodeExpandedHandler(ev, args) { @@ -50,7 +51,7 @@ angular.module("umbraco").controller("Umbraco.Editors.Content.CopyController", //iterate children _.each(args.children, function (child) { - //check if any of the items are list views, if so we need to add a custom + //check if any of the items are list views, if so we need to add a custom // child: A node to activate the search if (child.metaData.isContainer) { child.hasChildren = true; @@ -78,24 +79,24 @@ angular.module("umbraco").controller("Umbraco.Editors.Content.CopyController", $scope.searchInfo.results = []; } - // method to select a search result + // method to select a search result $scope.selectResult = function (evt, result) { result.selected = result.selected === true ? false : true; nodeSelectHandler(evt, { event: evt, node: result }); }; - //callback when there are search results + //callback when there are search results $scope.onSearchResults = function (results) { $scope.searchInfo.results = results; $scope.searchInfo.showSearch = true; }; - + $scope.copy = function () { $scope.busy = true; $scope.error = false; - contentResource.copy({ parentId: $scope.target.id, id: node.id, relateToOriginal: $scope.relateToOriginal }) + contentResource.copy({ parentId: $scope.target.id, id: node.id, relateToOriginal: $scope.relateToOriginal, recursive: $scope.recursive }) .then(function (path) { $scope.error = false; $scope.success = true; @@ -119,6 +120,12 @@ angular.module("umbraco").controller("Umbraco.Editors.Content.CopyController", $scope.success = false; $scope.error = err; $scope.busy = false; + //show any notifications + if (angular.isArray(err.data.notifications)) { + for (var i = 0; i < err.data.notifications.length; i++) { + notificationsService.showNotification(err.data.notifications[i]); + } + } }); }; @@ -129,4 +136,4 @@ angular.module("umbraco").controller("Umbraco.Editors.Content.CopyController", $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler); }); - }); \ No newline at end of file + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.delete.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.delete.controller.js index be27f3a663..1d24fee190 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.delete.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.delete.controller.js @@ -6,7 +6,7 @@ * @description * The controller for deleting content */ -function ContentDeleteController($scope, contentResource, treeService, navigationService, editorState, $location) { +function ContentDeleteController($scope, contentResource, treeService, navigationService, editorState, $location, dialogService, notificationsService) { $scope.performDelete = function() { @@ -31,10 +31,30 @@ function ContentDeleteController($scope, contentResource, treeService, navigatio //if the current edited item is the same one as we're deleting, we need to navigate elsewhere if (editorState.current && editorState.current.id == $scope.currentNode.id) { - $location.path("/content/content/edit/" + $scope.currentNode.parentId); + + //If the deleted item lived at the root then just redirect back to the root, otherwise redirect to the item's parent + var location = "/content"; + if ($scope.currentNode.parentId.toString() !== "-1") + location = "/content/content/edit/" + $scope.currentNode.parentId; + + $location.path(location); } navigationService.hideMenu(); + }, function(err) { + + $scope.currentNode.loading = false; + + //check if response is ysod + if (err.status && err.status >= 500) { + dialogService.ysodDialog(err); + } + + if (err.data && angular.isArray(err.data.notifications)) { + for (var i = 0; i < err.data.notifications.length; i++) { + notificationsService.showNotification(err.data.notifications[i]); + } + } }); }; diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js index 03de8af21e..0634471df1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js @@ -10,11 +10,17 @@ function ContentEditController($scope, $rootScope, $routeParams, $q, $timeout, $ //setup scope vars $scope.defaultButton = null; - $scope.subButtons = []; - $scope.currentSection = appState.getSectionState("currentSection"); - $scope.currentNode = null; //the editors affiliated node - $scope.isNew = $routeParams.create; - + $scope.subButtons = []; + + $scope.page = {}; + $scope.page.loading = false; + $scope.page.menu = {}; + $scope.page.menu.currentNode = null; + $scope.page.menu.currentSection = appState.getSectionState("currentSection"); + $scope.page.listViewPath = null; + $scope.page.isNew = $routeParams.create; + $scope.page.buttonGroupState = "init"; + function init(content) { var buttons = contentEditingHelper.configureContentEditorButtons({ @@ -48,7 +54,7 @@ function ContentEditController($scope, $rootScope, $routeParams, $q, $timeout, $ if (!$scope.content.isChildOfListView) { navigationService.syncTree({ tree: "content", path: path.split(","), forceReload: initialLoad !== true }).then(function (syncArgs) { - $scope.currentNode = syncArgs.node; + $scope.page.menu.currentNode = syncArgs.node; }); } else if (initialLoad === true) { @@ -61,7 +67,7 @@ function ContentEditController($scope, $rootScope, $routeParams, $q, $timeout, $ umbRequestHelper.resourcePromise( $http.get(content.treeNodeUrl), 'Failed to retrieve data for child node ' + content.id).then(function (node) { - $scope.currentNode = node; + $scope.page.menu.currentNode = node; }); } } @@ -70,6 +76,8 @@ function ContentEditController($scope, $rootScope, $routeParams, $q, $timeout, $ function performSave(args) { var deferred = $q.defer(); + $scope.page.buttonGroupState = "busy"; + contentEditingHelper.contentEditorPerformSave({ statusMessage: args.statusMessage, saveMethod: args.saveMethod, @@ -80,12 +88,17 @@ function ContentEditController($scope, $rootScope, $routeParams, $q, $timeout, $ init($scope.content); syncTreeNode($scope.content, data.path); + $scope.page.buttonGroupState = "success"; + deferred.resolve(data); }, function (err) { //error if (err) { editorState.set($scope.content); } + + $scope.page.buttonGroupState = "error"; + deferred.reject(err); }); @@ -102,26 +115,35 @@ function ContentEditController($scope, $rootScope, $routeParams, $q, $timeout, $ } if ($routeParams.create) { + + $scope.page.loading = true; + //we are creating so get an empty content item contentResource.getScaffold($routeParams.id, $routeParams.doctype) .then(function (data) { - $scope.loaded = true; + $scope.content = data; init($scope.content); resetLastListPageNumber($scope.content); + + $scope.page.loading = false; + }); } else { + + $scope.page.loading = true; + //we are editing so get the content item from the server contentResource.getById($routeParams.id) .then(function (data) { - $scope.loaded = true; + $scope.content = data; if (data.isChildOfListView && data.trashed === false) { - $scope.listViewPath = ($routeParams.page) + $scope.page.listViewPath = ($routeParams.page) ? "/content/content/edit/" + data.parentId + "?page=" + $routeParams.page : "/content/content/edit/" + data.parentId; } @@ -137,6 +159,9 @@ function ContentEditController($scope, $rootScope, $routeParams, $q, $timeout, $ syncTreeNode($scope.content, data.path, true); resetLastListPageNumber($scope.content); + + $scope.page.loading = false; + }); } @@ -145,6 +170,8 @@ function ContentEditController($scope, $rootScope, $routeParams, $q, $timeout, $ if (formHelper.submitForm({ scope: $scope, statusMessage: "Unpublishing...", skipValidation: true })) { + $scope.page.buttonGroupState = "busy"; + contentResource.unPublish($scope.content.id) .then(function (data) { @@ -160,6 +187,8 @@ function ContentEditController($scope, $rootScope, $routeParams, $q, $timeout, $ syncTreeNode($scope.content, data.path); + $scope.page.buttonGroupState = "success"; + }); } @@ -194,19 +223,7 @@ function ContentEditController($scope, $rootScope, $routeParams, $q, $timeout, $ } - - }; - // this method is called for all action buttons and then we proxy based on the btn definition - $scope.performAction = function (btn) { - - if (!btn || !angular.isFunction(btn.handler)) { - throw "btn.handler must be a function reference"; - } - - if (!$scope.busy) { - btn.handler.apply(this); - } }; } 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 5f65199269..75969730a0 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 @@ -1,5 +1,5 @@ angular.module("umbraco").controller("Umbraco.Editors.Content.MoveController", - function ($scope, eventsService, contentResource, navigationService, appState, treeService, localizationService) { + function ($scope, eventsService, contentResource, navigationService, appState, treeService, localizationService, notificationsService) { var dialogOptions = $scope.dialogOptions; var searchText = "Search..."; @@ -120,6 +120,12 @@ angular.module("umbraco").controller("Umbraco.Editors.Content.MoveController", $scope.success = false; $scope.error = err; $scope.busy = false; + //show any notifications + if (angular.isArray(err.data.notifications)) { + for (var i = 0; i < err.data.notifications.length; i++) { + notificationsService.showNotification(err.data.notifications[i]); + } + } }); }; diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.recyclebin.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.recyclebin.controller.js index a644e52a2c..0e4fe07339 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.recyclebin.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.recyclebin.controller.js @@ -8,10 +8,14 @@ * */ -function ContentRecycleBinController($scope, $routeParams, dataTypeResource) { +function ContentRecycleBinController($scope, $routeParams, dataTypeResource, navigationService, localizationService) { //ensures the list view doesn't actually load until we query for the list view config // for the section + $scope.page = {}; + $scope.page.name = "Recycle Bin"; + $scope.page.nameLocked = true; + $scope.listViewPath = null; $routeParams.id = "-20"; @@ -24,6 +28,20 @@ function ContentRecycleBinController($scope, $routeParams, dataTypeResource) { $scope.model = { config: { entityType: $routeParams.section } }; + // sync tree node + navigationService.syncTree({ tree: "content", path: ["-1", $routeParams.id], forceReload: false }); + + localizePageName(); + + function localizePageName() { + + var pageName = "general_recycleBin"; + + localizationService.localize(pageName).then(function(value) { + $scope.page.name = value; + }); + + } } angular.module('umbraco').controller("Umbraco.Editors.Content.RecycleBinController", ContentRecycleBinController); diff --git a/src/Umbraco.Web.UI.Client/src/views/content/copy.html b/src/Umbraco.Web.UI.Client/src/views/content/copy.html index fddc07db22..3f2bfcdd3b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/copy.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/copy.html @@ -9,9 +9,9 @@
    -
    -

    {{error.errorMsg}}

    -

    {{error.data.Message}}

    +
    +
    {{error.errorMsg}}
    +

    {{error.data.message}}

    @@ -56,6 +56,12 @@ + + + + + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/content/create.html b/src/Umbraco.Web.UI.Client/src/views/content/create.html index e02ea36b5a..d3bc28bc55 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/create.html @@ -39,7 +39,7 @@ \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/content/delete.html b/src/Umbraco.Web.UI.Client/src/views/content/delete.html index d3e44fce25..f3bdb0d121 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/delete.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/delete.html @@ -2,7 +2,7 @@

    - Are you sure you want to delete {{currentNode.name}} ? + Are you sure you want to delete {{currentNode.name}} ?

    diff --git a/src/Umbraco.Web.UI.Client/src/views/content/edit.html b/src/Umbraco.Web.UI.Client/src/views/content/edit.html index e29ee99925..5bf9f8a4a5 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/edit.html @@ -1,89 +1,86 @@ -
    - - - +
    -
    - -
    + -
    -
    -
    - -
    + - - + -
    -
    - - - - - - - + - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/content/move.html b/src/Umbraco.Web.UI.Client/src/views/content/move.html index 0ee919f933..b64511ec22 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/move.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/move.html @@ -3,24 +3,20 @@

    - Choose where to move {{currentNode.name}} to in the tree structure below + Choose where to move {{currentNode.name}} to in the tree structure below

    -
    -

    {{error.errorMsg}}

    -

    {{error.data.Message}}

    +
    +
    {{error.errorMsg}}
    +

    {{error.data.message}}

    -
    -

    - {{currentNode.name}} was moved underneath - {{target.name}} -

    - +
    +
    {{currentNode.name}} was moved underneath {{target.name}}
    diff --git a/src/Umbraco.Web.UI.Client/src/views/content/recyclebin.html b/src/Umbraco.Web.UI.Client/src/views/content/recyclebin.html index c468d67c62..f4c22c0220 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/recyclebin.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/recyclebin.html @@ -1,14 +1,17 @@ - - -
    -

    Recycle Bin

    -
    -
    - - - -
    -
    -
    -
    -
    + + + + + + + +
    + +
    + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/contenttype/contenttype.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/contenttype/contenttype.edit.controller.js deleted file mode 100644 index 55af70f287..0000000000 --- a/src/Umbraco.Web.UI.Client/src/views/contenttype/contenttype.edit.controller.js +++ /dev/null @@ -1,65 +0,0 @@ -/** - * @ngdoc controller - * @name Umbraco.Editors.ContentType.EditController - * @function - * - * @description - * The controller for the content type editor - */ -function ContentTypeEditController($scope, $routeParams, $log, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, entityResource) { - - $scope.tabs = []; - $scope.page = {}; - $scope.contentType = {tabs: [], name: "My content type", alias:"myType", icon:"icon-folder", allowedChildren: [], allowedTemplate: []}; - $scope.contentType.tabs = [ - {name: "Content", properties:[ {name: "test"}]}, - {name: "Generic Properties", properties:[]} - ]; - - - - $scope.dataTypesOptions ={ - group: "properties", - onDropHandler: function(item, args){ - args.sourceScope.move(args); - }, - onReleaseHandler: function(item, args){ - var a = args; - } - }; - - $scope.tabOptions ={ - group: "tabs", - drop: false, - nested: true, - onDropHandler: function(item, args){ - - }, - onReleaseHandler: function(item, args){ - - } - }; - - $scope.propertiesOptions ={ - group: "properties", - onDropHandler: function(item, args){ - //alert("dropped on properties"); - //args.targetScope.ngModel.$modelValue.push({name: "bong"}); - }, - onReleaseHandler: function(item, args){ - //alert("released from properties"); - //args.targetScope.ngModel.$modelValue.push({name: "bong"}); - }, - }; - - - $scope.omg = function(){ - alert("wat"); - }; - - entityResource.getAll("Datatype").then(function(data){ - $scope.page.datatypes = data; - }); -} - -angular.module("umbraco").controller("Umbraco.Editors.ContentType.EditController", ContentTypeEditController); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/contenttype/edit.html b/src/Umbraco.Web.UI.Client/src/views/contenttype/edit.html deleted file mode 100644 index b99e2bd3fd..0000000000 --- a/src/Umbraco.Web.UI.Client/src/views/contenttype/edit.html +++ /dev/null @@ -1,74 +0,0 @@ -
    - - - -
    - - -
    - -
    -
    - -
    - -
    -
    -
    -
    - - - -
    -
    - -
    -
      -
    • -
      {{tab.name}}
      - -
        -
      • - {{property.name}} -
      • -
      - -
    • -
    - - {{page.datatypes | json}} -
    - -
    - -
    -
    -
    -
    - - -
    - - - -
    -
    -
    -
    -
    diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/ChangePassword.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/ChangePassword.html index 9588ebd714..7008012a9e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/ChangePassword.html +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/ChangePassword.html @@ -3,15 +3,16 @@ ng-submit="changePassword()" val-form-manager> -

    Change password

    +

    Change password

    - + - diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/dashboard.tabs.controller.js b/src/Umbraco.Web.UI.Client/src/views/dashboard/dashboard.tabs.controller.js index cbfc126f26..b400185b02 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/dashboard.tabs.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/dashboard.tabs.controller.js @@ -43,7 +43,7 @@ function FormsController($scope, $route, $cookieStore, packageResource) { $scope.complete = function(result){ var url = window.location.href + "?init=true"; - $cookieStore.put("umbPackageInstallId", result.packageGuid); + $cookieStore.put("umbPackageInstallId", result.packageGuid); window.location.reload(true); }; @@ -186,60 +186,29 @@ function startupLatestEditsController($scope) { } angular.module("umbraco").controller("Umbraco.Dashboard.StartupLatestEditsController", startupLatestEditsController); -function MediaFolderBrowserDashboardController($rootScope, $scope, assetsService, $routeParams, $timeout, $element, $location, umbRequestHelper,navigationService, mediaResource, $cookies) { - var dialogOptions = $scope.dialogOptions; +function MediaFolderBrowserDashboardController($rootScope, $scope, contentTypeResource) { - $scope.filesUploading = []; - $scope.nodeId = -1; + //get the system media listview + contentTypeResource.getPropertyTypeScaffold(-96) + .then(function(dt) { - $scope.onUploadComplete = function () { - navigationService.reloadSection("media"); - } + $scope.fakeProperty = { + alias: "contents", + config: dt.config, + description: "", + editor: dt.editor, + hideLabel: true, + id: 1, + label: "Contents:", + validation: { + mandatory: false, + pattern: null + }, + value: "", + view: dt.view + }; + + }); } angular.module("umbraco").controller("Umbraco.Dashboard.MediaFolderBrowserDashboardController", MediaFolderBrowserDashboardController); - - -function ChangePasswordDashboardController($scope, xmlhelper, $log, currentUserResource, formHelper) { - - //create the initial model for change password property editor - $scope.changePasswordModel = { - alias: "_umb_password", - view: "changepassword", - config: {}, - value: {} - }; - - //go get the config for the membership provider and add it to the model - currentUserResource.getMembershipProviderConfig().then(function(data) { - $scope.changePasswordModel.config = data; - //ensure the hasPassword config option is set to true (the user of course has a password already assigned) - //this will ensure the oldPassword is shown so they can change it - $scope.changePasswordModel.config.hasPassword = true; - $scope.changePasswordModel.config.disableToggle = true; - }); - - ////this is the model we will pass to the service - //$scope.profile = {}; - - $scope.changePassword = function() { - - if (formHelper.submitForm({ scope: $scope })) { - currentUserResource.changePassword($scope.changePasswordModel.value).then(function(data) { - - //if the password has been reset, then update our model - if (data.value) { - $scope.changePasswordModel.value.generatedPassword = data.value; - } - - formHelper.resetForm({ scope: $scope, notifications: data.notifications }); - - }, function (err) { - - formHelper.handleError(err); - - }); - } - }; -} -angular.module("umbraco").controller("Umbraco.Dashboard.StartupChangePasswordController", ChangePasswordDashboardController); diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/default/StartupDashboardIntro.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/default/StartupDashboardIntro.html index e0c476ad82..b97ca465ac 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/default/StartupDashboardIntro.html +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/default/StartupDashboardIntro.html @@ -4,11 +4,11 @@

    Thank you for choosing Umbraco - we think this could be the beginning of something beautiful. While it may feel overwhelming at first, we've done a lot to make the learning curve as smooth and fast as possible:

    \ No newline at end of file +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/default/StartupDashboardKits.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/default/StartupDashboardKits.html index 726dd8d8a1..2daed229ec 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/default/StartupDashboardKits.html +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/default/StartupDashboardKits.html @@ -12,4 +12,4 @@
    -
    \ No newline at end of file + diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/default/StartupDashboardVideos.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/default/StartupDashboardVideos.html index ec55790bd9..478688f585 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/default/StartupDashboardVideos.html +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/default/StartupDashboardVideos.html @@ -2,11 +2,11 @@

    Want to master Umbraco? Spend a couple of minutes learning some best practices by watching one of these videos about using Umbraco. And visit umbraco.tv for even more Umbraco videos

    To get you started:

    - \ No newline at end of file + diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/default/ourumbraco.png b/src/Umbraco.Web.UI.Client/src/views/dashboard/default/ourumbraco.png index 3795b27228..68f0cffe30 100644 Binary files a/src/Umbraco.Web.UI.Client/src/views/dashboard/default/ourumbraco.png and b/src/Umbraco.Web.UI.Client/src/views/dashboard/default/ourumbraco.png differ diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/examinemanagement.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/examinemanagement.html index aaf170b221..5dafd5f330 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/examinemanagement.html +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/examinemanagement.html @@ -29,9 +29,11 @@ -
    -
    + +
    +
    +
    The process is taking longer than expected, check the umbraco log to see if there have been any errors during this operation
    @@ -169,12 +171,12 @@
    -
    @@ -183,10 +185,11 @@
    - -
    -
    + +
    +
    + diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/examinemgmt.controller.js b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/examinemgmt.controller.js index 125f6bc7f4..4a19ea9926 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/examinemgmt.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/examinemgmt.controller.js @@ -10,7 +10,7 @@ function examineMgmtController($scope, umbRequestHelper, $log, $http, $q, $timeo 'Failed to check index processing') .then(function(data) { - if (data) { + if (data !== null && data !== "null") { //copy all resulting properties for (var k in data) { @@ -43,7 +43,7 @@ function examineMgmtController($scope, umbRequestHelper, $log, $http, $q, $timeo umbRequestHelper.resourcePromise( $http.get(umbRequestHelper.getApiUrl("examineMgmtBaseUrl", "GetSearchResults", { searcherName: searcher.name, - query: searcher.searchText, + query: encodeURIComponent(searcher.searchText), queryType: searcher.searchType })), 'Failed to search') @@ -67,7 +67,9 @@ function examineMgmtController($scope, umbRequestHelper, $log, $http, $q, $timeo "Depending on how much content there is in your site this could take a while. " + "It is not recommended to rebuild an index during times of high website traffic " + "or when editors are editing content.")) { + indexer.isProcessing = true; + indexer.processingAttempts = 0; umbRequestHelper.resourcePromise( $http.post(umbRequestHelper.getApiUrl("examineMgmtBaseUrl", "PostRebuildIndex", { indexerName: indexer.name })), diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/xmldataintegrityreport.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/xmldataintegrityreport.html index edfac077c0..d2851a2492 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/xmldataintegrityreport.html +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/xmldataintegrityreport.html @@ -16,9 +16,9 @@ OkError
    -
    diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/forms/formsdashboardintro.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/forms/formsdashboardintro.html index 1e7aa3bf9e..018c520c52 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/forms/formsdashboardintro.html +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/forms/formsdashboardintro.html @@ -11,7 +11,7 @@
    diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/media/mediafolderbrowser.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/media/mediafolderbrowser.html index 2d14476e69..d49bdd89a5 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/media/mediafolderbrowser.html +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/media/mediafolderbrowser.html @@ -1,5 +1,3 @@
    - +
    - diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/settingsdashboardintro.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/settingsdashboardintro.html index afda190327..facb38e577 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/settingsdashboardintro.html +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/settingsdashboardintro.html @@ -1,7 +1,7 @@

    Start here

    This section contains the building blocks for your Umbraco site

    Follow the below links to find out more about working with the items in the Setings section:

    - +

    Find out more:

    \ No newline at end of file + diff --git a/src/Umbraco.Web.UI.Client/src/views/datatype/delete.html b/src/Umbraco.Web.UI.Client/src/views/datatype/delete.html deleted file mode 100644 index cddc1b34ad..0000000000 --- a/src/Umbraco.Web.UI.Client/src/views/datatype/delete.html +++ /dev/null @@ -1,12 +0,0 @@ -
    -
    - -

    - Are you sure you want to delete {{currentNode.name}} ? -

    - - - - -
    -
    diff --git a/src/Umbraco.Web.UI.Client/src/views/datatype/edit.html b/src/Umbraco.Web.UI.Client/src/views/datatype/edit.html deleted file mode 100644 index 4d365174fb..0000000000 --- a/src/Umbraco.Web.UI.Client/src/views/datatype/edit.html +++ /dev/null @@ -1,68 +0,0 @@ -
    - - - - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    - - Required -
    - -
    - - -
    {{content.selectedEditor}}
    -
    - - - - - - - - -
    -
    - -
    -
    - -
    -
    -
    - -
    - diff --git a/src/Umbraco.Web.UI.Client/src/views/datatypes/create.controller.js b/src/Umbraco.Web.UI.Client/src/views/datatypes/create.controller.js new file mode 100644 index 0000000000..d5d8128155 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/datatypes/create.controller.js @@ -0,0 +1,48 @@ +/** + * @ngdoc controller + * @name Umbraco.Editors.DataType.CreateController + * @function + * + * @description + * The controller for the data type creation dialog + */ +function DataTypeCreateController($scope, $location, navigationService, dataTypeResource, formHelper, appState) { + + $scope.model = { + folderName: "", + creatingFolder: false + }; + + var node = $scope.dialogOptions.currentNode; + + $scope.showCreateFolder = function() { + $scope.model.creatingFolder = true; + } + + $scope.createContainer = function () { + if (formHelper.submitForm({ scope: $scope, formCtrl: this.createFolderForm, statusMessage: "Creating folder..." })) { + dataTypeResource.createContainer(node.id, $scope.model.folderName).then(function (folderId) { + + navigationService.hideMenu(); + var currPath = node.path ? node.path : "-1"; + navigationService.syncTree({ tree: "datatypes", path: currPath + "," + folderId, forceReload: true, activate: true }); + + formHelper.resetForm({ scope: $scope }); + + var section = appState.getSectionState("currentSection"); + + }, function(err) { + + //TODO: Handle errors + }); + }; + } + + $scope.createDataType = function() { + $location.search('create', null); + $location.path("/developer/datatypes/edit/" + node.id).search("create", "true"); + navigationService.hideMenu(); + } +} + +angular.module('umbraco').controller("Umbraco.Editors.DataType.CreateController", DataTypeCreateController); diff --git a/src/Umbraco.Web.UI.Client/src/views/datatypes/create.html b/src/Umbraco.Web.UI.Client/src/views/datatypes/create.html new file mode 100644 index 0000000000..af60632f2b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/datatypes/create.html @@ -0,0 +1,50 @@ + + + diff --git a/src/Umbraco.Web.UI.Client/src/views/datatypes/datatype.delete.controller.js b/src/Umbraco.Web.UI.Client/src/views/datatypes/datatype.delete.controller.js new file mode 100644 index 0000000000..101d74b16b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/datatypes/datatype.delete.controller.js @@ -0,0 +1,50 @@ +/** + * @ngdoc controller + * @name Umbraco.Editors.ContentDeleteController + * @function + * + * @description + * The controller for deleting content + */ +function DataTypeDeleteController($scope, dataTypeResource, treeService, navigationService) { + + $scope.performDelete = function() { + + //mark it for deletion (used in the UI) + $scope.currentNode.loading = true; + dataTypeResource.deleteById($scope.currentNode.id).then(function () { + $scope.currentNode.loading = false; + + //get the root node before we remove it + var rootNode = treeService.getTreeRoot($scope.currentNode); + + //TODO: Need to sync tree, etc... + treeService.removeNode($scope.currentNode); + navigationService.hideMenu(); + }); + + }; + + $scope.performContainerDelete = function () { + + //mark it for deletion (used in the UI) + $scope.currentNode.loading = true; + dataTypeResource.deleteContainerById($scope.currentNode.id).then(function () { + $scope.currentNode.loading = false; + + //get the root node before we remove it + var rootNode = treeService.getTreeRoot($scope.currentNode); + + //TODO: Need to sync tree, etc... + treeService.removeNode($scope.currentNode); + navigationService.hideMenu(); + }); + + }; + + $scope.cancel = function() { + navigationService.hideDialog(); + }; +} + +angular.module("umbraco").controller("Umbraco.Editors.DataType.DeleteController", DataTypeDeleteController); diff --git a/src/Umbraco.Web.UI.Client/src/views/datatype/datatype.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/datatypes/datatype.edit.controller.js similarity index 71% rename from src/Umbraco.Web.UI.Client/src/views/datatype/datatype.edit.controller.js rename to src/Umbraco.Web.UI.Client/src/views/datatypes/datatype.edit.controller.js index 9731e6a63f..f0dff7e2f3 100644 --- a/src/Umbraco.Web.UI.Client/src/views/datatype/datatype.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/datatypes/datatype.edit.controller.js @@ -2,15 +2,19 @@ * @ngdoc controller * @name Umbraco.Editors.DataType.EditController * @function - * + * * @description * The controller for the content editor */ -function DataTypeEditController($scope, $routeParams, $location, appState, navigationService, treeService, dataTypeResource, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, formHelper, editorState) { +function DataTypeEditController($scope, $routeParams, $location, appState, navigationService, treeService, dataTypeResource, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, formHelper, editorState, dataTypeHelper) { - //setup scope vars - $scope.currentSection = appState.getSectionState("currentSection"); - $scope.currentNode = null; //the editors affiliated node + //setup scope vars + $scope.page = {}; + $scope.page.loading = false; + $scope.page.nameLocked = false; + $scope.page.menu = {}; + $scope.page.menu.currentSection = appState.getSectionState("currentSection"); + $scope.page.menu.currentNode = null; //method used to configure the pre-values when we retrieve them from the server function createPreValueProps(preVals) { @@ -39,32 +43,45 @@ function DataTypeEditController($scope, $routeParams, $location, appState, navig label: "Property editor alias" } }; - + //setup the pre-values as props $scope.preValues = []; if ($routeParams.create) { + + $scope.page.loading = true; + //we are creating so get an empty data type item - dataTypeResource.getScaffold() + dataTypeResource.getScaffold($routeParams.id) .then(function(data) { - $scope.loaded = true; + $scope.preValuesLoaded = true; $scope.content = data; + setHeaderNameState($scope.content); + //set a shared state editorState.set($scope.content); + + $scope.page.loading = false; + }); } else { + + $scope.page.loading = true; + //we are editing so get the content item from the server dataTypeResource.getById($routeParams.id) .then(function(data) { - $scope.loaded = true; + $scope.preValuesLoaded = true; $scope.content = data; createPreValueProps($scope.content.preValues); - + + setHeaderNameState($scope.content); + //share state editorState.set($scope.content); @@ -73,13 +90,16 @@ function DataTypeEditController($scope, $routeParams, $location, appState, navig // after the redirect, so we will bind all subscriptions which will show the server validation errors // if there are any and then clear them so the collection no longer persists them. serverValidationManager.executeAndClearAllSubscriptions(); - - navigationService.syncTree({ tree: "datatype", path: [String(data.id)] }).then(function (syncArgs) { - $scope.currentNode = syncArgs.node; + + navigationService.syncTree({ tree: "datatypes", path: data.path }).then(function (syncArgs) { + $scope.page.menu.currentNode = syncArgs.node; }); + + $scope.page.loading = false; + }); } - + $scope.$watch("content.selectedEditor", function (newVal, oldVal) { //when the value changes, we need to dynamically load in the new editor @@ -91,17 +111,29 @@ function DataTypeEditController($scope, $routeParams, $location, appState, navig $scope.preValuesLoaded = true; $scope.content.preValues = data; createPreValueProps($scope.content.preValues); - + + setHeaderNameState($scope.content); + //share state editorState.set($scope.content); }); } }); + function setHeaderNameState(content) { + + if(content.isSystem == 1) { + $scope.page.nameLocked = true; + } + + } + $scope.save = function() { + $scope.page.saveButtonState = "busy"; + if (formHelper.submitForm({ scope: $scope, statusMessage: "Saving..." })) { - + dataTypeResource.save($scope.content, $scope.preValues, $routeParams.create) .then(function(data) { @@ -115,24 +147,34 @@ function DataTypeEditController($scope, $routeParams, $location, appState, navig } }); + setHeaderNameState($scope.content); + //share state editorState.set($scope.content); - navigationService.syncTree({ tree: "datatype", path: [String(data.id)], forceReload: true }).then(function (syncArgs) { - $scope.currentNode = syncArgs.node; + navigationService.syncTree({ tree: "datatypes", path: data.path, forceReload: true }).then(function (syncArgs) { + $scope.page.menu.currentNode = syncArgs.node; }); - + + $scope.page.saveButtonState = "success"; + + dataTypeHelper.rebindChangedProperties($scope.content, data); + }, function(err) { - //NOTE: in the case of data type values we are setting the orig/new props + //NOTE: in the case of data type values we are setting the orig/new props // to be the same thing since that only really matters for content/media. contentEditingHelper.handleSaveError({ redirectOnFailure: false, err: err }); - + + $scope.page.saveButtonState = "error"; + //share state editorState.set($scope.content); + + dataTypeHelper.rebindChangedProperties($scope.content, data); }); } diff --git a/src/Umbraco.Web.UI.Client/src/views/datatypes/delete.html b/src/Umbraco.Web.UI.Client/src/views/datatypes/delete.html new file mode 100644 index 0000000000..fe75a54982 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/datatypes/delete.html @@ -0,0 +1,34 @@ +
    +
    + +

    + Are you sure you want to delete {{currentNode.name}} ? +

    + + +
    + + +
    + +
    +

    + All property types & property data + using this data type will be deleted permanently, please confirm you want to delete these as well. +

    + +
    + + + + + +
    +
    + +
    +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/datatypes/edit.html b/src/Umbraco.Web.UI.Client/src/views/datatypes/edit.html new file mode 100644 index 0000000000..1c80fa44c1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/datatypes/edit.html @@ -0,0 +1,72 @@ +
    + + + +
    + + + + + + + + + + +
    + + Required +
    + +
    + + +
    {{content.selectedEditor}}
    +
    + + + + + + + +
    + + + + + + + + + + + + + + +
    + + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/datatypes/move.controller.js b/src/Umbraco.Web.UI.Client/src/views/datatypes/move.controller.js new file mode 100644 index 0000000000..580c08fd1a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/datatypes/move.controller.js @@ -0,0 +1,66 @@ +angular.module("umbraco") +.controller("Umbraco.Editors.DataType.MoveController", + function ($scope, dataTypeResource, treeService, navigationService, notificationsService, appState) { + var dialogOptions = $scope.dialogOptions; + $scope.dialogTreeEventHandler = $({}); + + function nodeSelectHandler(ev, args) { + args.event.preventDefault(); + args.event.stopPropagation(); + + if ($scope.target) { + //un-select if there's a current one selected + $scope.target.selected = false; + } + + $scope.target = args.node; + $scope.target.selected = true; + } + + $scope.move = function () { + + $scope.busy = true; + $scope.error = false; + + dataTypeResource.move({ parentId: $scope.target.id, id: dialogOptions.currentNode.id }) + .then(function (path) { + $scope.error = false; + $scope.success = true; + $scope.busy = false; + + //first we need to remove the node that launched the dialog + treeService.removeNode($scope.currentNode); + + //get the currently edited node (if any) + var activeNode = appState.getTreeState("selectedNode"); + + //we need to do a double sync here: first sync to the moved content - but don't activate the node, + //then sync to the currenlty edited content (note: this might not be the content that was moved!!) + + navigationService.syncTree({ tree: "dataTypes", path: path, forceReload: true, activate: false }).then(function (args) { + if (activeNode) { + var activeNodePath = treeService.getPath(activeNode).join(); + //sync to this node now - depending on what was copied this might already be synced but might not be + navigationService.syncTree({ tree: "dataTypes", path: activeNodePath, forceReload: false, activate: true }); + } + }); + + }, function (err) { + $scope.success = false; + $scope.error = err; + $scope.busy = false; + //show any notifications + if (angular.isArray(err.data.notifications)) { + for (var i = 0; i < err.data.notifications.length; i++) { + notificationsService.showNotification(err.data.notifications[i]); + } + } + }); + }; + + $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); + + $scope.$on('$destroy', function () { + $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); + }); + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/datatypes/move.html b/src/Umbraco.Web.UI.Client/src/views/datatypes/move.html new file mode 100644 index 0000000000..9725c7ec5f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/datatypes/move.html @@ -0,0 +1,50 @@ +
    + +
    +
    + +

    + Select the folder to move {{currentNode.name}} to in the tree structure below +

    + +
    +
    +
    + +
    +
    {{error.errorMsg}}
    +

    {{error.data.message}}

    +
    + +
    +
    {{currentNode.name}} was moved underneath {{target.name}}
    + +
    + +
    + +
    + + +
    + +
    +
    +
    + + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/directives/umb-content-name.html b/src/Umbraco.Web.UI.Client/src/views/directives/_obsolete/umb-content-name.html similarity index 100% rename from src/Umbraco.Web.UI.Client/src/views/directives/umb-content-name.html rename to src/Umbraco.Web.UI.Client/src/views/directives/_obsolete/umb-content-name.html diff --git a/src/Umbraco.Web.UI.Client/src/views/directives/umb-header.html b/src/Umbraco.Web.UI.Client/src/views/directives/_obsolete/umb-header.html similarity index 58% rename from src/Umbraco.Web.UI.Client/src/views/directives/umb-header.html rename to src/Umbraco.Web.UI.Client/src/views/directives/_obsolete/umb-header.html index f7798bba09..383b52f83c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/directives/umb-header.html +++ b/src/Umbraco.Web.UI.Client/src/views/directives/_obsolete/umb-header.html @@ -1,14 +1,14 @@
    - +
    - -
    -
    \ No newline at end of file +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/directives/umb-item-sorter.html b/src/Umbraco.Web.UI.Client/src/views/directives/_obsolete/umb-item-sorter.html similarity index 100% rename from src/Umbraco.Web.UI.Client/src/views/directives/umb-item-sorter.html rename to src/Umbraco.Web.UI.Client/src/views/directives/_obsolete/umb-item-sorter.html diff --git a/src/Umbraco.Web.UI.Client/src/views/directives/umb-login.html b/src/Umbraco.Web.UI.Client/src/views/directives/_obsolete/umb-login.html similarity index 100% rename from src/Umbraco.Web.UI.Client/src/views/directives/umb-login.html rename to src/Umbraco.Web.UI.Client/src/views/directives/_obsolete/umb-login.html diff --git a/src/Umbraco.Web.UI.Client/src/views/directives/umb-optionsmenu.html b/src/Umbraco.Web.UI.Client/src/views/directives/_obsolete/umb-optionsmenu.html similarity index 82% rename from src/Umbraco.Web.UI.Client/src/views/directives/umb-optionsmenu.html rename to src/Umbraco.Web.UI.Client/src/views/directives/_obsolete/umb-optionsmenu.html index a46421b513..f834dfb93b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/directives/umb-optionsmenu.html +++ b/src/Umbraco.Web.UI.Client/src/views/directives/_obsolete/umb-optionsmenu.html @@ -1,5 +1,5 @@ -
    - +
    + Actions @@ -7,7 +7,7 @@ -
    diff --git a/src/Umbraco.Web.UI.Client/src/views/directives/html/umb-photo-folder.html b/src/Umbraco.Web.UI.Client/src/views/directives/_obsolete/umb-photo-folder.html similarity index 100% rename from src/Umbraco.Web.UI.Client/src/views/directives/html/umb-photo-folder.html rename to src/Umbraco.Web.UI.Client/src/views/directives/_obsolete/umb-photo-folder.html diff --git a/src/Umbraco.Web.UI.Client/src/views/directives/umb-tab-view.html b/src/Umbraco.Web.UI.Client/src/views/directives/_obsolete/umb-tab-view.html similarity index 55% rename from src/Umbraco.Web.UI.Client/src/views/directives/umb-tab-view.html rename to src/Umbraco.Web.UI.Client/src/views/directives/_obsolete/umb-tab-view.html index 157e02648d..6fda65e3e1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/directives/umb-tab-view.html +++ b/src/Umbraco.Web.UI.Client/src/views/directives/_obsolete/umb-tab-view.html @@ -1,5 +1,5 @@
    -
    +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/directives/html/umb-upload-dropzone.html b/src/Umbraco.Web.UI.Client/src/views/directives/_obsolete/umb-upload-dropzone.html similarity index 100% rename from src/Umbraco.Web.UI.Client/src/views/directives/html/umb-upload-dropzone.html rename to src/Umbraco.Web.UI.Client/src/views/directives/_obsolete/umb-upload-dropzone.html diff --git a/src/Umbraco.Web.UI.Client/src/views/directives/button-group.html b/src/Umbraco.Web.UI.Client/src/views/directives/button-group.html deleted file mode 100644 index 83ee647361..0000000000 --- a/src/Umbraco.Web.UI.Client/src/views/directives/button-group.html +++ /dev/null @@ -1,21 +0,0 @@ - \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/directives/imaging/umb-image-folder.html b/src/Umbraco.Web.UI.Client/src/views/directives/imaging/umb-image-folder.html deleted file mode 100644 index e580b4c956..0000000000 --- a/src/Umbraco.Web.UI.Client/src/views/directives/imaging/umb-image-folder.html +++ /dev/null @@ -1,34 +0,0 @@ -
    - -
    - -
    - -

    Click to upload

    - -
    - - - -
    - - - -
    -
    -
    - -
    - - - - - \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/directives/umb-avatar.html b/src/Umbraco.Web.UI.Client/src/views/directives/umb-avatar.html deleted file mode 100644 index b89bd2065a..0000000000 --- a/src/Umbraco.Web.UI.Client/src/views/directives/umb-avatar.html +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/directives/umb-editor.html b/src/Umbraco.Web.UI.Client/src/views/directives/umb-editor.html deleted file mode 100644 index cb7f084650..0000000000 --- a/src/Umbraco.Web.UI.Client/src/views/directives/umb-editor.html +++ /dev/null @@ -1,3 +0,0 @@ -
    - -
    \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/directives/umb-notifications.html b/src/Umbraco.Web.UI.Client/src/views/directives/umb-notifications.html deleted file mode 100644 index cd611d85e6..0000000000 --- a/src/Umbraco.Web.UI.Client/src/views/directives/umb-notifications.html +++ /dev/null @@ -1,18 +0,0 @@ -
    - -
    diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.controller.js b/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.controller.js new file mode 100644 index 0000000000..a62024e704 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.controller.js @@ -0,0 +1,63 @@ +/** + * @ngdoc controller + * @name Umbraco.Editors.DocumentType.CreateController + * @function + * + * @description + * The controller for the doc type creation dialog + */ +function DocumentTypesCreateController($scope, $location, navigationService, contentTypeResource, formHelper, appState, notificationsService) { + + $scope.model = { + folderName: "", + creatingFolder: false, + }; + + var node = $scope.dialogOptions.currentNode; + + $scope.showCreateFolder = function() { + $scope.model.creatingFolder = true; + } + + $scope.createContainer = function () { + if (formHelper.submitForm({ scope: $scope, formCtrl: this.createFolderForm, statusMessage: "Creating folder..." })) { + contentTypeResource.createContainer(node.id, $scope.model.folderName).then(function (folderId) { + + navigationService.hideMenu(); + var currPath = node.path ? node.path : "-1"; + navigationService.syncTree({ tree: "documenttypes", path: currPath + "," + folderId, forceReload: true, activate: true }); + + formHelper.resetForm({ scope: $scope }); + + var section = appState.getSectionState("currentSection"); + + }, function(err) { + + $scope.error = err; + + //show any notifications + if (angular.isArray(err.data.notifications)) { + for (var i = 0; i < err.data.notifications.length; i++) { + notificationsService.showNotification(err.data.notifications[i]); + } + } + }); + }; + } + + $scope.createDocType = function() { + $location.search('create', null); + $location.search('notemplate', null); + $location.path("/settings/documenttypes/edit/" + node.id).search("create", "true"); + navigationService.hideMenu(); + } + + $scope.createComponent = function() { + $location.search('create', null); + $location.search('notemplate', null); + $location.path("/settings/documenttypes/edit/" + node.id).search("create", "true").search("notemplate", "true"); + navigationService.hideMenu(); + } +} + +angular.module('umbraco').controller("Umbraco.Editors.DocumentTypes.CreateController", DocumentTypesCreateController); diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html b/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html new file mode 100644 index 0000000000..3539c84cbe --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html @@ -0,0 +1,58 @@ + + + diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/delete.controller.js b/src/Umbraco.Web.UI.Client/src/views/documenttypes/delete.controller.js new file mode 100644 index 0000000000..c920e05480 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/delete.controller.js @@ -0,0 +1,50 @@ +/** + * @ngdoc controller + * @name Umbraco.Editors.DocumentType.DeleteController + * @function + * + * @description + * The controller for deleting content + */ +function DocumentTypesDeleteController($scope, dataTypeResource, contentTypeResource, treeService, navigationService) { + + $scope.performDelete = function() { + + //mark it for deletion (used in the UI) + $scope.currentNode.loading = true; + contentTypeResource.deleteById($scope.currentNode.id).then(function () { + $scope.currentNode.loading = false; + + //get the root node before we remove it + var rootNode = treeService.getTreeRoot($scope.currentNode); + + //TODO: Need to sync tree, etc... + treeService.removeNode($scope.currentNode); + navigationService.hideMenu(); + }); + + }; + + $scope.performContainerDelete = function() { + + //mark it for deletion (used in the UI) + $scope.currentNode.loading = true; + contentTypeResource.deleteContainerById($scope.currentNode.id).then(function () { + $scope.currentNode.loading = false; + + //get the root node before we remove it + var rootNode = treeService.getTreeRoot($scope.currentNode); + + //TODO: Need to sync tree, etc... + treeService.removeNode($scope.currentNode); + navigationService.hideMenu(); + }); + + }; + + $scope.cancel = function() { + navigationService.hideDialog(); + }; +} + +angular.module("umbraco").controller("Umbraco.Editors.DocumentTypes.DeleteController", DocumentTypesDeleteController); diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/delete.html b/src/Umbraco.Web.UI.Client/src/views/documenttypes/delete.html new file mode 100644 index 0000000000..68a193c3ce --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/delete.html @@ -0,0 +1,38 @@ +
    + +
    + +

    + Are you sure you want to delete + {{currentNode.name}} ? +

    + + +
    + + +
    + +
    +

    + All Documents + using this document type will be deleted permanently, please confirm you want to delete these as well. +

    + +
    + + + + + +
    +
    + + +
    +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js new file mode 100644 index 0000000000..621e1aac9e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js @@ -0,0 +1,289 @@ +/** + * @ngdoc controller + * @name Umbraco.Editors.DocumentType.EditController + * @function + * + * @description + * The controller for the content type editor + */ +(function () { + "use strict"; + + function DocumentTypesEditController($scope, $routeParams, modelsResource, contentTypeResource, dataTypeResource, editorState, contentEditingHelper, formHelper, navigationService, iconHelper, contentTypeHelper, notificationsService, $filter, $q, localizationService) { + + var vm = this; + + vm.save = save; + + vm.currentNode = null; + vm.contentType = {}; + vm.page = {}; + vm.page.loading = false; + vm.page.saveButtonState = "init"; + vm.page.navigation = [ + { + "name": "Design", + "icon": "icon-document-dashed-line", + "view": "views/documenttypes/views/design/design.html", + "active": true + }, + { + "name": "List view", + "icon": "icon-list", + "view": "views/documenttypes/views/listview/listview.html" + }, + { + "name": "Permissions", + "icon": "icon-keychain", + "view": "views/documenttypes/views/permissions/permissions.html" + }, + { + "name": "Templates", + "icon": "icon-layout", + "view": "views/documenttypes/views/templates/templates.html" + } + ]; + + vm.page.keyboardShortcutsOverview = [ + { + "name": "Sections", + "shortcuts": [ + { + "description": "Navigate sections", + "keys": [{ "key": "1" }, { "key": "4" }], + "keyRange": true + } + ] + }, + { + "name": "Design", + "shortcuts": [ + { + "description": "Add tab", + "keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "t" }] + }, + { + "description": "Add property", + "keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "p" }] + }, + { + "description": "Add editor", + "keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "e" }] + }, + { + "description": "Edit data type", + "keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "d" }] + } + ] + }, + { + "name": "List view", + "shortcuts": [ + { + "description": "Toggle list view", + "keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "l" }] + } + ] + }, + { + "name": "Permissions", + "shortcuts": [ + { + "description": "Toggle allow as root", + "keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "r" }] + }, + { + "description": "Add child node", + "keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "c" }] + } + ] + }, + { + "name": "Templates", + "shortcuts": [ + { + "description": "Add template", + "keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "t" }] + } + ] + } + ]; + + if ($routeParams.create) { + vm.page.loading = true; + + //we are creating so get an empty data type item + contentTypeResource.getScaffold($routeParams.id) + .then(function (dt) { + + init(dt); + + vm.page.loading = false; + + }); + } + else { + vm.page.loading = true; + + contentTypeResource.getById($routeParams.id).then(function (dt) { + init(dt); + + syncTreeNode(vm.contentType, dt.path, true); + + vm.page.loading = false; + + }); + } + + + /* ---------- SAVE ---------- */ + + function save() { + var deferred = $q.defer(); + + vm.page.saveButtonState = "busy"; + + // reformat allowed content types to array if id's + vm.contentType.allowedContentTypes = contentTypeHelper.createIdArray(vm.contentType.allowedContentTypes); + + contentEditingHelper.contentEditorPerformSave({ + statusMessage: "Saving...", + saveMethod: contentTypeResource.save, + scope: $scope, + content: vm.contentType, + // we need to rebind... the IDs that have been created! + rebindCallback: function (origContentType, savedContentType) { + vm.contentType.id = savedContentType.id; + vm.contentType.groups.forEach(function(group) { + if (!group.name) return; + + var k = 0; + while (k < savedContentType.groups.length && savedContentType.groups[k].name != group.name) + k++; + if (k == savedContentType.groups.length) { + group.id = 0; + return; + } + + var savedGroup = savedContentType.groups[k]; + if (!group.id) group.id = savedGroup.id; + + group.properties.forEach(function (property) { + if (property.id || !property.alias) return; + + k = 0; + while (k < savedGroup.properties.length && savedGroup.properties[k].alias != property.alias) + k++; + if (k == savedGroup.properties.length) { + property.id = 0; + return; + } + + var savedProperty = savedGroup.properties[k]; + property.id = savedProperty.id; + }); + }); + } + }).then(function (data) { + //success + syncTreeNode(vm.contentType, data.path); + + vm.page.saveButtonState = "success"; + + deferred.resolve(data); + }, function (err) { + //error + if (err) { + editorState.set($scope.content); + } + else { + localizationService.localize("speechBubbles_validationFailedHeader").then(function (headerValue) { + localizationService.localize("speechBubbles_validationFailedMessage").then(function(msgValue) { + notificationsService.error(headerValue, msgValue); + }); + }); + } + vm.page.saveButtonState = "error"; + + deferred.reject(err); + }); + return deferred.promise; + + } + + function init(contentType) { + //get available composite types + contentTypeResource.getAvailableCompositeContentTypes(contentType.id).then(function (result) { + contentType.availableCompositeContentTypes = result; + // convert icons for composite content types + iconHelper.formatContentTypeIcons(contentType.availableCompositeContentTypes); + }); + + // set all tab to inactive + if (contentType.groups.length !== 0) { + angular.forEach(contentType.groups, function (group) { + + angular.forEach(group.properties, function (property) { + // get data type details for each property + getDataTypeDetails(property); + }); + + }); + } + + // sort properties after sort order + angular.forEach(contentType.groups, function (group) { + group.properties = $filter('orderBy')(group.properties, 'sortOrder'); + }); + + // insert template on new doc types + if (!$routeParams.notemplate && contentType.id === 0) { + contentType.defaultTemplate = contentTypeHelper.insertDefaultTemplatePlaceholder(contentType.defaultTemplate); + contentType.allowedTemplates = contentTypeHelper.insertTemplatePlaceholder(contentType.allowedTemplates); + } + + // convert icons for content type + convertLegacyIcons(contentType); + + //set a shared state + editorState.set(contentType); + + vm.contentType = contentType; + } + + function convertLegacyIcons(contentType) { + // make array to store contentType icon + var contentTypeArray = []; + + // push icon to array + contentTypeArray.push({ "icon": contentType.icon }); + + // run through icon method + iconHelper.formatContentTypeIcons(contentTypeArray); + + // set icon back on contentType + contentType.icon = contentTypeArray[0].icon; + } + + function getDataTypeDetails(property) { + if (property.propertyState !== "init") { + + dataTypeResource.getById(property.dataTypeId) + .then(function (dataType) { + property.dataTypeIcon = dataType.icon; + property.dataTypeName = dataType.name; + }); + } + } + + /** Syncs the content type to it's tree node - this occurs on first load and after saving */ + function syncTreeNode(dt, path, initialLoad) { + navigationService.syncTree({ tree: "documenttypes", path: path.split(","), forceReload: initialLoad !== true }).then(function (syncArgs) { + vm.currentNode = syncArgs.node; + }); + } + + } + + angular.module("umbraco").controller("Umbraco.Editors.DocumentTypes.EditController", DocumentTypesEditController); +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.html b/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.html new file mode 100644 index 0000000000..bfe6dd746a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.html @@ -0,0 +1,67 @@ +
    + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/move.controller.js b/src/Umbraco.Web.UI.Client/src/views/documenttypes/move.controller.js new file mode 100644 index 0000000000..3cf3634371 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/move.controller.js @@ -0,0 +1,66 @@ +angular.module("umbraco") +.controller("Umbraco.Editors.DocumentTypes.MoveController", + function ($scope, contentTypeResource, treeService, navigationService, notificationsService, appState) { + var dialogOptions = $scope.dialogOptions; + $scope.dialogTreeEventHandler = $({}); + + function nodeSelectHandler(ev, args) { + args.event.preventDefault(); + args.event.stopPropagation(); + + if ($scope.target) { + //un-select if there's a current one selected + $scope.target.selected = false; + } + + $scope.target = args.node; + $scope.target.selected = true; + } + + $scope.move = function () { + + $scope.busy = true; + $scope.error = false; + + contentTypeResource.move({ parentId: $scope.target.id, id: dialogOptions.currentNode.id }) + .then(function (path) { + $scope.error = false; + $scope.success = true; + $scope.busy = false; + + //first we need to remove the node that launched the dialog + treeService.removeNode($scope.currentNode); + + //get the currently edited node (if any) + var activeNode = appState.getTreeState("selectedNode"); + + //we need to do a double sync here: first sync to the moved content - but don't activate the node, + //then sync to the currenlty edited content (note: this might not be the content that was moved!!) + + navigationService.syncTree({ tree: "documentTypes", path: path, forceReload: true, activate: false }).then(function (args) { + if (activeNode) { + var activeNodePath = treeService.getPath(activeNode).join(); + //sync to this node now - depending on what was copied this might already be synced but might not be + navigationService.syncTree({ tree: "documentTypes", path: activeNodePath, forceReload: false, activate: true }); + } + }); + + }, function (err) { + $scope.success = false; + $scope.error = err; + $scope.busy = false; + //show any notifications + if (angular.isArray(err.data.notifications)) { + for (var i = 0; i < err.data.notifications.length; i++) { + notificationsService.showNotification(err.data.notifications[i]); + } + } + }); + }; + + $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); + + $scope.$on('$destroy', function () { + $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); + }); + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/move.html b/src/Umbraco.Web.UI.Client/src/views/documenttypes/move.html new file mode 100644 index 0000000000..506db13d06 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/move.html @@ -0,0 +1,50 @@ +
    + +
    +
    + +

    + Select the folder to move {{currentNode.name}} to in the tree structure below +

    + +
    +
    +
    + +
    +
    {{error.errorMsg}}
    +

    {{error.data.message}}

    +
    + +
    +
    {{currentNode.name}} was moved underneath {{target.name}}
    + +
    + +
    + +
    + + +
    + +
    +
    +
    + + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/property.html b/src/Umbraco.Web.UI.Client/src/views/documenttypes/property.html new file mode 100644 index 0000000000..ccd632246c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/property.html @@ -0,0 +1,4 @@ +
    + + {{property.description}} +
    \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/propertygroup.html b/src/Umbraco.Web.UI.Client/src/views/documenttypes/propertygroup.html new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/views/design/design.html b/src/Umbraco.Web.UI.Client/src/views/documenttypes/views/design/design.html new file mode 100644 index 0000000000..cd6b629b42 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/views/design/design.html @@ -0,0 +1,4 @@ + + diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/views/listview/listview.html b/src/Umbraco.Web.UI.Client/src/views/documenttypes/views/listview/listview.html new file mode 100644 index 0000000000..4c681fe14e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/views/listview/listview.html @@ -0,0 +1,21 @@ +
    + +
    + +
    Enable list view
    + Configures the content item to show a sortable & searchable list of its children, the children will not be shown in the tree + +
    + +
    + + + + +
    + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/views/permissions/permissions.controller.js b/src/Umbraco.Web.UI.Client/src/views/documenttypes/views/permissions/permissions.controller.js new file mode 100644 index 0000000000..35ac54ae7c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/views/permissions/permissions.controller.js @@ -0,0 +1,76 @@ +/** + * @ngdoc controller + * @name Umbraco.Editors.DocumentType.PropertyController + * @function + * + * @description + * The controller for the content type editor property dialog + */ +(function() { + 'use strict'; + + function PermissionsController($scope, contentTypeResource, iconHelper, contentTypeHelper) { + + /* ----------- SCOPE VARIABLES ----------- */ + + var vm = this; + + vm.contentTypes = []; + vm.selectedChildren = []; + + vm.addChild = addChild; + vm.removeChild = removeChild; + + /* ---------- INIT ---------- */ + + init(); + + function init() { + + contentTypeResource.getAll().then(function(contentTypes){ + + vm.contentTypes = contentTypes; + + // convert legacy icons + iconHelper.formatContentTypeIcons(vm.contentTypes); + + vm.selectedChildren = contentTypeHelper.makeObjectArrayFromId($scope.model.allowedContentTypes, vm.contentTypes); + + if($scope.model.id === 0) { + contentTypeHelper.insertChildNodePlaceholder(vm.contentTypes, $scope.model.name, $scope.model.icon, $scope.model.id); + } + + }); + + } + + function addChild($event) { + vm.childNodeSelectorOverlay = { + view: "itempicker", + title: "Choose child node", + availableItems: vm.contentTypes, + selectedItems: vm.selectedChildren, + event: $event, + show: true, + submit: function(model) { + vm.selectedChildren.push(model.selectedItem); + $scope.model.allowedContentTypes.push(model.selectedItem.id); + vm.childNodeSelectorOverlay.show = false; + vm.childNodeSelectorOverlay = null; + } + }; + } + + function removeChild(selectedChild, index) { + // remove from vm + vm.selectedChildren.splice(index, 1); + + // remove from content type model + var selectedChildIndex = $scope.model.allowedContentTypes.indexOf(selectedChild.id); + $scope.model.allowedContentTypes.splice(selectedChildIndex, 1); + } + + } + + angular.module("umbraco").controller("Umbraco.Editors.DocumentType.PermissionsController", PermissionsController); +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/views/permissions/permissions.html b/src/Umbraco.Web.UI.Client/src/views/documenttypes/views/permissions/permissions.html new file mode 100644 index 0000000000..8563d9fbfb --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/views/permissions/permissions.html @@ -0,0 +1,46 @@ +
    + +
    + +
    +
    Allow as root
    + Allow editors to create content if this type in the root of the content tree +
    +
    + +
    + +
    + +
    + +
    +
    Allowed child node types
    + Allow content of these types to be created underneath content this type +
    + +
    + + + + + + +
    + +
    + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/views/templates/templates.controller.js b/src/Umbraco.Web.UI.Client/src/views/documenttypes/views/templates/templates.controller.js new file mode 100644 index 0000000000..fe1b0f0e7e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/views/templates/templates.controller.js @@ -0,0 +1,45 @@ +/** + * @ngdoc controller + * @name Umbraco.Editors.DocumentType.TemplatesController + * @function + * + * @description + * The controller for the content type editor templates sub view + */ +(function() { + 'use strict'; + + function TemplatesController($scope, entityResource, contentTypeHelper, $routeParams) { + + /* ----------- SCOPE VARIABLES ----------- */ + + var vm = this; + + vm.availableTemplates = []; + vm.updateTemplatePlaceholder = false; + + + /* ---------- INIT ---------- */ + + init(); + + function init() { + + entityResource.getAll("Template").then(function(templates){ + + vm.availableTemplates = templates; + + // update placeholder template information on new doc types + if (!$routeParams.notemplate && $scope.model.id === 0) { + vm.updateTemplatePlaceholder = true; + vm.availableTemplates = contentTypeHelper.insertTemplatePlaceholder(vm.availableTemplates); + } + + }); + + } + + } + + angular.module("umbraco").controller("Umbraco.Editors.DocumentType.TemplatesController", TemplatesController); +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/views/templates/templates.html b/src/Umbraco.Web.UI.Client/src/views/documenttypes/views/templates/templates.html new file mode 100644 index 0000000000..edf9680591 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/views/templates/templates.html @@ -0,0 +1,23 @@ +
    + +
    +
    Allowed Templates
    + Choose which templates editors are allowed to use on content of this type +
    + +
    + + + + +
    + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/media/delete.html b/src/Umbraco.Web.UI.Client/src/views/media/delete.html index dcbedf8ed7..3c9716eb3f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/media/delete.html +++ b/src/Umbraco.Web.UI.Client/src/views/media/delete.html @@ -2,7 +2,7 @@

    - Are you sure you want to delete {{currentNode.name}} ? + Are you sure you want to delete {{currentNode.name}} ?

    diff --git a/src/Umbraco.Web.UI.Client/src/views/media/edit.html b/src/Umbraco.Web.UI.Client/src/views/media/edit.html index 20497ed3f7..5bcd4cb750 100644 --- a/src/Umbraco.Web.UI.Client/src/views/media/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/media/edit.html @@ -1,64 +1,76 @@ -
    + + + + - - -
    - -
    + -
    -
    -
    - -
    + + - - -
    -
    -
    + - - + + -
    - + + + - - + + -
    + - + -
    - -
    -
    -
    + -
    -
    + + - -
    - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/media/media.delete.controller.js b/src/Umbraco.Web.UI.Client/src/views/media/media.delete.controller.js index eab7bbe4ad..99f40d14bb 100644 --- a/src/Umbraco.Web.UI.Client/src/views/media/media.delete.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/media/media.delete.controller.js @@ -6,7 +6,7 @@ * @description * The controller for deleting content */ -function MediaDeleteController($scope, mediaResource, treeService, navigationService, editorState, $location) { +function MediaDeleteController($scope, mediaResource, treeService, navigationService, editorState, $location, dialogService, notificationsService) { $scope.performDelete = function() { @@ -31,13 +31,31 @@ function MediaDeleteController($scope, mediaResource, treeService, navigationSer //if the current edited item is the same one as we're deleting, we need to navigate elsewhere if (editorState.current && editorState.current.id == $scope.currentNode.id) { - $location.path("/media/media/edit/" + $scope.currentNode.parentId); + + //If the deleted item lived at the root then just redirect back to the root, otherwise redirect to the item's parent + var location = "/media"; + if ($scope.currentNode.parentId.toString() !== "-1") + location = "/media/media/edit/" + $scope.currentNode.parentId; + + $location.path(location); } navigationService.hideMenu(); - },function() { + }, function (err) { + $scope.currentNode.loading = false; + + //check if response is ysod + if (err.status && err.status >= 500) { + dialogService.ysodDialog(err); + } + + if (err.data && angular.isArray(err.data.notifications)) { + for (var i = 0; i < err.data.notifications.length; i++) { + notificationsService.showNotification(err.data.notifications[i]); + } + } }); }; diff --git a/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js index b74cfeb463..fcbb9dd0c7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js @@ -12,12 +12,20 @@ function mediaEditController($scope, $routeParams, appState, mediaResource, enti $scope.currentSection = appState.getSectionState("currentSection"); $scope.currentNode = null; //the editors affiliated node + $scope.page = {}; + $scope.page.loading = false; + $scope.page.menu = {}; + $scope.page.menu.currentSection = appState.getSectionState("currentSection"); + $scope.page.menu.currentNode = null; //the editors affiliated node + $scope.page.listViewPath = null; + $scope.page.saveButtonState = "init"; + /** Syncs the content item to it's tree node - this occurs on first load and after saving */ function syncTreeNode(content, path, initialLoad) { if (!$scope.content.isChildOfListView) { navigationService.syncTree({ tree: "media", path: path.split(","), forceReload: initialLoad !== true }).then(function (syncArgs) { - $scope.currentNode = syncArgs.node; + $scope.page.menu.currentNode = syncArgs.node; }); } else if (initialLoad === true) { @@ -30,29 +38,36 @@ function mediaEditController($scope, $routeParams, appState, mediaResource, enti umbRequestHelper.resourcePromise( $http.get(content.treeNodeUrl), 'Failed to retrieve data for child node ' + content.id).then(function (node) { - $scope.currentNode = node; + $scope.page.menu.currentNode = node; }); } } if ($routeParams.create) { + $scope.page.loading = true; + mediaResource.getScaffold($routeParams.id, $routeParams.doctype) .then(function (data) { - $scope.loaded = true; $scope.content = data; editorState.set($scope.content); + + $scope.page.loading = false; + }); } else { + + $scope.page.loading = true; + mediaResource.getById($routeParams.id) .then(function (data) { - $scope.loaded = true; + $scope.content = data; if (data.isChildOfListView && data.trashed === false) { - $scope.listViewPath = ($routeParams.page) + $scope.page.listViewPath = ($routeParams.page) ? "/media/media/edit/" + data.parentId + "?page=" + $routeParams.page : "/media/media/edit/" + data.parentId; } @@ -75,7 +90,9 @@ function mediaEditController($scope, $routeParams, appState, mediaResource, enti }); } - }); + $scope.page.loading = false; + + }); } $scope.save = function () { @@ -83,6 +100,7 @@ function mediaEditController($scope, $routeParams, appState, mediaResource, enti if (!$scope.busy && formHelper.submitForm({ scope: $scope, statusMessage: "Saving..." })) { $scope.busy = true; + $scope.page.saveButtonState = "busy"; mediaResource.save($scope.content, $routeParams.create, fileManager.getFiles()) .then(function(data) { @@ -100,6 +118,8 @@ function mediaEditController($scope, $routeParams, appState, mediaResource, enti syncTreeNode($scope.content, data.path); + $scope.page.saveButtonState = "success"; + }, function(err) { contentEditingHelper.handleSaveError({ @@ -108,8 +128,17 @@ function mediaEditController($scope, $routeParams, appState, mediaResource, enti rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, err.data) }); + //show any notifications + if (angular.isArray(err.data.notifications)) { + for (var i = 0; i < err.data.notifications.length; i++) { + notificationsService.showNotification(err.data.notifications[i]); + } + } + editorState.set($scope.content); $scope.busy = false; + $scope.page.saveButtonState = "error"; + }); }else{ $scope.busy = false; diff --git a/src/Umbraco.Web.UI.Client/src/views/media/media.recyclebin.controller.js b/src/Umbraco.Web.UI.Client/src/views/media/media.recyclebin.controller.js index bedb1e4da1..8134d0ad26 100644 --- a/src/Umbraco.Web.UI.Client/src/views/media/media.recyclebin.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/media/media.recyclebin.controller.js @@ -8,7 +8,11 @@ * */ -function MediaRecycleBinController($scope, $routeParams, dataTypeResource) { +function MediaRecycleBinController($scope, $routeParams, dataTypeResource, navigationService, localizationService) { + + $scope.page = {}; + $scope.page.name = "Recycle Bin"; + $scope.page.nameLocked = true; //ensures the list view doesn't actually load until we query for the list view config // for the section @@ -24,6 +28,21 @@ function MediaRecycleBinController($scope, $routeParams, dataTypeResource) { $scope.model = { config: { entityType: $routeParams.section } }; + // sync tree node + navigationService.syncTree({ tree: "media", path: ["-1", $routeParams.id], forceReload: false }); + + localizePageName(); + + function localizePageName() { + + var pageName = "general_recycleBin"; + + localizationService.localize(pageName).then(function(value) { + $scope.page.name = value; + }); + + } + } angular.module('umbraco').controller("Umbraco.Editors.Media.RecycleBinController", MediaRecycleBinController); diff --git a/src/Umbraco.Web.UI.Client/src/views/media/recyclebin.html b/src/Umbraco.Web.UI.Client/src/views/media/recyclebin.html index 9005fe13c3..bfdfcbf8bf 100644 --- a/src/Umbraco.Web.UI.Client/src/views/media/recyclebin.html +++ b/src/Umbraco.Web.UI.Client/src/views/media/recyclebin.html @@ -1,14 +1,17 @@ - - -
    -

    Recycle Bin

    -
    -
    - - - -
    -
    -
    -
    -
    + + + + + + + +
    + +
    + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/mediatypes/create.controller.js b/src/Umbraco.Web.UI.Client/src/views/mediatypes/create.controller.js new file mode 100644 index 0000000000..44f615ac7f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/mediatypes/create.controller.js @@ -0,0 +1,48 @@ +/** + * @ngdoc controller + * @name Umbraco.Editors.MediaType.CreateController + * @function + * + * @description + * The controller for the media type creation dialog + */ +function MediaTypesCreateController($scope, $location, navigationService, mediaTypeResource, formHelper, appState) { + + $scope.model = { + folderName: "", + creatingFolder: false + }; + + var node = $scope.dialogOptions.currentNode; + + $scope.showCreateFolder = function() { + $scope.model.creatingFolder = true; + } + + $scope.createContainer = function () { + if (formHelper.submitForm({ scope: $scope, formCtrl: this.createFolderForm, statusMessage: "Creating folder..." })) { + mediaTypeResource.createContainer(node.id, $scope.model.folderName).then(function (folderId) { + + navigationService.hideMenu(); + var currPath = node.path ? node.path : "-1"; + navigationService.syncTree({ tree: "mediatypes", path: currPath + "," + folderId, forceReload: true, activate: true }); + + formHelper.resetForm({ scope: $scope }); + + var section = appState.getSectionState("currentSection"); + + }, function(err) { + + //TODO: Handle errors + }); + }; + } + + $scope.createMediaType = function() { + $location.search('create', null); + $location.path("/settings/mediatypes/edit/" + node.id).search("create", "true"); + navigationService.hideMenu(); + } +} + +angular.module('umbraco').controller("Umbraco.Editors.MediaTypes.CreateController", MediaTypesCreateController); diff --git a/src/Umbraco.Web.UI.Client/src/views/mediatypes/create.html b/src/Umbraco.Web.UI.Client/src/views/mediatypes/create.html new file mode 100644 index 0000000000..4f056fef91 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/mediatypes/create.html @@ -0,0 +1,51 @@ + + + diff --git a/src/Umbraco.Web.UI.Client/src/views/mediatypes/delete.controller.js b/src/Umbraco.Web.UI.Client/src/views/mediatypes/delete.controller.js new file mode 100644 index 0000000000..785550684b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/mediatypes/delete.controller.js @@ -0,0 +1,50 @@ +/** + * @ngdoc controller + * @name Umbraco.Editors.MediaType.DeleteController + * @function + * + * @description + * The controller for the media type delete dialog + */ +function MediaTypesDeleteController($scope, dataTypeResource, mediaTypeResource, treeService, navigationService) { + + $scope.performDelete = function() { + + //mark it for deletion (used in the UI) + $scope.currentNode.loading = true; + mediaTypeResource.deleteById($scope.currentNode.id).then(function () { + $scope.currentNode.loading = false; + + //get the root node before we remove it + var rootNode = treeService.getTreeRoot($scope.currentNode); + + //TODO: Need to sync tree, etc... + treeService.removeNode($scope.currentNode); + navigationService.hideMenu(); + }); + + }; + + $scope.performContainerDelete = function() { + + //mark it for deletion (used in the UI) + $scope.currentNode.loading = true; + mediaTypeResource.deleteContainerById($scope.currentNode.id).then(function () { + $scope.currentNode.loading = false; + + //get the root node before we remove it + var rootNode = treeService.getTreeRoot($scope.currentNode); + + //TODO: Need to sync tree, etc... + treeService.removeNode($scope.currentNode); + navigationService.hideMenu(); + }); + + }; + + $scope.cancel = function() { + navigationService.hideDialog(); + }; +} + +angular.module("umbraco").controller("Umbraco.Editors.MediaTypes.DeleteController", MediaTypesDeleteController); diff --git a/src/Umbraco.Web.UI.Client/src/views/mediatypes/delete.html b/src/Umbraco.Web.UI.Client/src/views/mediatypes/delete.html new file mode 100644 index 0000000000..c051cb5a1e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/mediatypes/delete.html @@ -0,0 +1,36 @@ +
    +
    + +

    + Are you sure you want to delete {{currentNode.name}} ? +

    + + +
    + + +
    + +
    +

    + All media items + using this media type will be deleted permanently, please confirm you want to delete these as well. +

    + +
    + + + + + +
    +
    + + +
    +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/mediatypes/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/mediatypes/edit.controller.js new file mode 100644 index 0000000000..28f7cf59f5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/mediatypes/edit.controller.js @@ -0,0 +1,234 @@ +/** + * @ngdoc controller + * @name Umbraco.Editors.MediaType.EditController + * @function + * + * @description + * The controller for the media type editor + */ +(function () { + "use strict"; + + function MediaTypesEditController($scope, $routeParams, mediaTypeResource, dataTypeResource, editorState, contentEditingHelper, formHelper, navigationService, iconHelper, contentTypeHelper, notificationsService, $filter, $q, localizationService) { + var vm = this; + + vm.save = save; + + vm.currentNode = null; + vm.contentType = {}; + vm.page = {}; + vm.page.loading = false; + vm.page.saveButtonState = "init"; + vm.page.navigation = [ + { + "name": "Design", + "icon": "icon-document-dashed-line", + "view": "views/mediatypes/views/design/design.html", + "active": true + }, + { + "name": "List view", + "icon": "icon-list", + "view": "views/mediatypes/views/listview/listview.html" + }, + { + "name": "Permissions", + "icon": "icon-keychain", + "view": "views/mediatypes/views/permissions/permissions.html" + } + ]; + + vm.page.keyboardShortcutsOverview = [ + { + "name": "Sections", + "shortcuts": [ + { + "description": "Navigate sections", + "keys": [{ "key": "1" }, { "key": "3" }], + "keyRange": true + } + ] + }, + { + "name": "Design", + "shortcuts": [ + { + "description": "Add tab", + "keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "t" }] + }, + { + "description": "Add property", + "keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "p" }] + }, + { + "description": "Add editor", + "keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "e" }] + }, + { + "description": "Edit data type", + "keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "d" }] + } + ] + }, + { + "name": "List view", + "shortcuts": [ + { + "description": "Toggle list view", + "keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "l" }] + } + ] + }, + { + "name": "Permissions", + "shortcuts": [ + { + "description": "Toggle allow as root", + "keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "r" }] + }, + { + "description": "Add child node", + "keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "c" }] + } + ] + } + ]; + + if ($routeParams.create) { + vm.page.loading = true; + + //we are creating so get an empty data type item + mediaTypeResource.getScaffold($routeParams.id) + .then(function(dt) { + init(dt); + + vm.page.loading = false; + }); + } + else { + vm.page.loading = true; + + mediaTypeResource.getById($routeParams.id).then(function(dt) { + init(dt); + + syncTreeNode(vm.contentType, dt.path, true); + + vm.page.loading = false; + }); + } + + /* ---------- SAVE ---------- */ + + function save() { + var deferred = $q.defer(); + + vm.page.saveButtonState = "busy"; + + // reformat allowed content types to array if id's + vm.contentType.allowedContentTypes = contentTypeHelper.createIdArray(vm.contentType.allowedContentTypes); + + contentEditingHelper.contentEditorPerformSave({ + statusMessage: "Saving...", + saveMethod: mediaTypeResource.save, + scope: $scope, + content: vm.contentType, + //no-op for rebind callback... we don't really need to rebind for content types + rebindCallback: angular.noop + }).then(function (data) { + //success + syncTreeNode(vm.contentType, data.path); + + vm.page.saveButtonState = "success"; + + deferred.resolve(data); + }, function (err) { + //error + if (err) { + editorState.set($scope.content); + } + else { + localizationService.localize("speechBubbles_validationFailedHeader").then(function (headerValue) { + localizationService.localize("speechBubbles_validationFailedMessage").then(function (msgValue) { + notificationsService.error(headerValue, msgValue); + }); + }); + } + + vm.page.saveButtonState = "error"; + + deferred.reject(err); + }); + + return deferred.promise; + } + + function init(contentType) { + //get available composite types + mediaTypeResource.getAvailableCompositeContentTypes(contentType.id).then(function (result) { + contentType.availableCompositeContentTypes = result; + // convert legacy icons + iconHelper.formatContentTypeIcons(contentType.availableCompositeContentTypes); + }); + + // set all tab to inactive + if (contentType.groups.length !== 0) { + angular.forEach(contentType.groups, function (group) { + + angular.forEach(group.properties, function (property) { + // get data type details for each property + getDataTypeDetails(property); + }); + + }); + } + + // sort properties after sort order + angular.forEach(contentType.groups, function (group) { + group.properties = $filter('orderBy')(group.properties, 'sortOrder'); + }); + + // convert icons for content type + convertLegacyIcons(contentType); + + //set a shared state + editorState.set(contentType); + + vm.contentType = contentType; + } + + function convertLegacyIcons(contentType) { + // make array to store contentType icon + var contentTypeArray = []; + + // push icon to array + contentTypeArray.push({ "icon": contentType.icon }); + + // run through icon method + iconHelper.formatContentTypeIcons(contentTypeArray); + + // set icon back on contentType + contentType.icon = contentTypeArray[0].icon; + } + + function getDataTypeDetails(property) { + if (property.propertyState !== "init") { + + dataTypeResource.getById(property.dataTypeId) + .then(function(dataType) { + property.dataTypeIcon = dataType.icon; + property.dataTypeName = dataType.name; + }); + } + } + + + /** Syncs the content type to it's tree node - this occurs on first load and after saving */ + function syncTreeNode(dt, path, initialLoad) { + navigationService.syncTree({ tree: "mediatypes", path: path.split(","), forceReload: initialLoad !== true }).then(function(syncArgs) { + vm.currentNode = syncArgs.node; + }); + } + } + + angular.module("umbraco").controller("Umbraco.Editors.MediaTypes.EditController", MediaTypesEditController); +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/mediatypes/edit.html b/src/Umbraco.Web.UI.Client/src/views/mediatypes/edit.html new file mode 100644 index 0000000000..93da205db6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/mediatypes/edit.html @@ -0,0 +1,61 @@ +
    + + + +
    + + + + + + + +
    + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/mediatypes/move.controller.js b/src/Umbraco.Web.UI.Client/src/views/mediatypes/move.controller.js new file mode 100644 index 0000000000..a8ce1e461a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/mediatypes/move.controller.js @@ -0,0 +1,67 @@ +angular.module("umbraco") +.controller("Umbraco.Editors.MediaTypes.MoveController", + function ($scope, mediaTypeResource, treeService, navigationService, notificationsService, appState) { + + var dialogOptions = $scope.dialogOptions; + $scope.dialogTreeEventHandler = $({}); + + function nodeSelectHandler(ev, args) { + args.event.preventDefault(); + args.event.stopPropagation(); + + if ($scope.target) { + //un-select if there's a current one selected + $scope.target.selected = false; + } + + $scope.target = args.node; + $scope.target.selected = true; + } + + $scope.move = function () { + + $scope.busy = true; + $scope.error = false; + + mediaTypeResource.move({ parentId: $scope.target.id, id: dialogOptions.currentNode.id }) + .then(function (path) { + $scope.error = false; + $scope.success = true; + $scope.busy = false; + + //first we need to remove the node that launched the dialog + treeService.removeNode($scope.currentNode); + + //get the currently edited node (if any) + var activeNode = appState.getTreeState("selectedNode"); + + //we need to do a double sync here: first sync to the moved content - but don't activate the node, + //then sync to the currenlty edited content (note: this might not be the content that was moved!!) + + navigationService.syncTree({ tree: "mediaTypes", path: path, forceReload: true, activate: false }).then(function (args) { + if (activeNode) { + var activeNodePath = treeService.getPath(activeNode).join(); + //sync to this node now - depending on what was copied this might already be synced but might not be + navigationService.syncTree({ tree: "mediaTypes", path: activeNodePath, forceReload: false, activate: true }); + } + }); + + }, function (err) { + $scope.success = false; + $scope.error = err; + $scope.busy = false; + //show any notifications + if (angular.isArray(err.data.notifications)) { + for (var i = 0; i < err.data.notifications.length; i++) { + notificationsService.showNotification(err.data.notifications[i]); + } + } + }); + }; + + $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); + + $scope.$on('$destroy', function () { + $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); + }); + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/mediatypes/move.html b/src/Umbraco.Web.UI.Client/src/views/mediatypes/move.html new file mode 100644 index 0000000000..22b6d14d62 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/mediatypes/move.html @@ -0,0 +1,51 @@ +
    + +
    +
    + +

    + Select the folder to move {{currentNode.name}} to in the tree structure below +

    + +
    +
    +
    + +
    +
    {{error.errorMsg}}
    +

    {{error.data.message}}

    +
    + +
    +
    {{currentNode.name}} was moved underneath {{target.name}}
    + +
    + +
    + +
    + + +
    + +
    +
    +
    + + + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/mediatypes/views/design/design.html b/src/Umbraco.Web.UI.Client/src/views/mediatypes/views/design/design.html new file mode 100644 index 0000000000..215664e4db --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/mediatypes/views/design/design.html @@ -0,0 +1,4 @@ + + diff --git a/src/Umbraco.Web.UI.Client/src/views/mediatypes/views/listview/listview.html b/src/Umbraco.Web.UI.Client/src/views/mediatypes/views/listview/listview.html new file mode 100644 index 0000000000..4aab0613b8 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/mediatypes/views/listview/listview.html @@ -0,0 +1,21 @@ +
    + +
    + +
    Enable list view
    + Configures the content item to show a sortable & searchable list of its children, the children will not be shown in the tree + +
    + +
    + + + + +
    + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/mediatypes/views/permissions/permissions.controller.js b/src/Umbraco.Web.UI.Client/src/views/mediatypes/views/permissions/permissions.controller.js new file mode 100644 index 0000000000..e42d89c6c6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/mediatypes/views/permissions/permissions.controller.js @@ -0,0 +1,68 @@ +(function() { + 'use strict'; + + function PermissionsController($scope, mediaTypeResource, iconHelper, contentTypeHelper) { + + /* ----------- SCOPE VARIABLES ----------- */ + + var vm = this; + + vm.mediaTypes = []; + vm.selectedChildren = []; + + vm.addChild = addChild; + vm.removeChild = removeChild; + + /* ---------- INIT ---------- */ + + init(); + + function init() { + + mediaTypeResource.getAll().then(function(mediaTypes){ + + vm.mediaTypes = mediaTypes; + + // convert legacy icons + iconHelper.formatContentTypeIcons(vm.mediaTypes); + + vm.selectedChildren = contentTypeHelper.makeObjectArrayFromId($scope.model.allowedContentTypes, vm.mediaTypes); + + if($scope.model.id === 0) { + contentTypeHelper.insertChildNodePlaceholder(vm.mediaTypes, $scope.model.name, $scope.model.icon, $scope.model.id); + } + + }); + + } + + function addChild($event) { + vm.childNodeSelectorOverlay = { + view: "itempicker", + title: "Choose child node", + availableItems: vm.mediaTypes, + selectedItems: vm.selectedChildren, + event: $event, + show: true, + submit: function(model) { + vm.selectedChildren.push(model.selectedItem); + $scope.model.allowedContentTypes.push(model.selectedItem.id); + vm.childNodeSelectorOverlay.show = false; + vm.childNodeSelectorOverlay = null; + } + }; + } + + function removeChild(selectedChild, index) { + // remove from vm + vm.selectedChildren.splice(index, 1); + + // remove from content type model + var selectedChildIndex = $scope.model.allowedContentTypes.indexOf(selectedChild.id); + $scope.model.allowedContentTypes.splice(selectedChildIndex, 1); + } + + } + + angular.module("umbraco").controller("Umbraco.Editors.MediaType.PermissionsController", PermissionsController); +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/mediatypes/views/permissions/permissions.html b/src/Umbraco.Web.UI.Client/src/views/mediatypes/views/permissions/permissions.html new file mode 100644 index 0000000000..44fc6374f3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/mediatypes/views/permissions/permissions.html @@ -0,0 +1,48 @@ +
    + +
    + +
    +
    Allow as root
    + Allow editors to create content if this type in the root of the content tree +
    +
    + +
    + +
    + +
    + +
    +
    Allowed child node types
    + Allow content of these types to be created underneath content this type +
    + +
    + + + + + + + + +
    + +
    + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/member/create.html b/src/Umbraco.Web.UI.Client/src/views/member/create.html index 9dcd7ff836..03f0237e38 100644 --- a/src/Umbraco.Web.UI.Client/src/views/member/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/member/create.html @@ -1,7 +1,7 @@ diff --git a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/mediapicker.controller.js index baf7ada8a8..38dd99c204 100644 --- a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/mediapicker.controller.js @@ -13,25 +13,38 @@ function mediaPickerController($scope, dialogService, entityResource, $log, icon multiPicker: false, entityType: "Media", section: "media", - treeAlias: "media", - callback: function(data) { - if (angular.isArray(data)) { - _.each(data, function (item, i) { - $scope.add(item); - }); - } - else { - $scope.clear(); - $scope.add(data); - } - } + treeAlias: "media" }; - $scope.openContentPicker = function(){ - var d = dialogService.treePicker(dialogOptions); - }; + $scope.openContentPicker = function() { + $scope.contentPickerOverlay = dialogOptions; + $scope.contentPickerOverlay.view = "treePicker"; + $scope.contentPickerOverlay.show = true; - $scope.remove =function(index){ + $scope.contentPickerOverlay.submit = function(model) { + + if ($scope.contentPickerOverlay.multiPicker) { + _.each(model.selection, function (item, i) { + $scope.add(item); + }); + } + else { + $scope.clear(); + $scope.add(model.selection[0]); + } + + $scope.contentPickerOverlay.show = false; + $scope.contentPickerOverlay = null; + }; + + $scope.contentPickerOverlay.close = function(oldModel) { + $scope.contentPickerOverlay.show = false; + $scope.contentPickerOverlay = null; + }; + } + + $scope.remove =function(index, event){ + event.preventDefault(); $scope.renderModel.splice(index, 1); }; diff --git a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/mediapicker.html b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/mediapicker.html index cd4f6b20b5..978ae63ece 100644 --- a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/mediapicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/mediapicker.html @@ -4,7 +4,7 @@ ng-model="renderModel">
  • - + {{node.name}} @@ -19,4 +19,12 @@
  • -
    \ No newline at end of file + + + + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/number.html b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/number.html index 6d7b8c4c78..e7490e2703 100644 --- a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/number.html +++ b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/number.html @@ -3,7 +3,7 @@ type="number" ng-model="model.value" val-server="value" - fix-number /> + fix-number /> Not a number {{propertyForm.requiredField.errorMsg}} diff --git a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treepicker.controller.js index 7bb02ba751..d2ff7bd778 100644 --- a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treepicker.controller.js @@ -24,16 +24,33 @@ angular.module('umbraco') }); }); } - - $scope.openContentPicker =function() { - var d = dialogService.treePicker({ - section: config.type, - treeAlias: config.type, - multiPicker: config.multiPicker, - callback: populate - }); - }; + $scope.openContentPicker = function() { + $scope.treePickerOverlay = {}; + $scope.treePickerOverlay.section = config.type; + $scope.treePickerOverlay.treeAlias = config.treeAlias; + $scope.treePickerOverlay.multiPicker = config.multiPicker; + $scope.treePickerOverlay.view = "treePicker"; + $scope.treePickerOverlay.show = true; + + $scope.treePickerOverlay.submit = function(model) { + + if(config.multiPicker) { + populate(model.selection); + } else { + populate(model.selection[0]); + } + + $scope.treePickerOverlay.show = false; + $scope.treePickerOverlay = null; + }; + + $scope.treePickerOverlay.close = function(oldModel) { + $scope.treePickerOverlay.show = false; + $scope.treePickerOverlay = null; + }; + + } $scope.remove =function(index){ $scope.renderModel.splice(index, 1); @@ -82,4 +99,4 @@ angular.module('umbraco') $scope.add(data); } } -}); \ No newline at end of file +}); diff --git a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treepicker.html b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treepicker.html index 71ce43b883..fc7a388cc1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treepicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treepicker.html @@ -19,4 +19,12 @@ -
    \ No newline at end of file + + + + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treesource.controller.js b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treesource.controller.js index 087447b184..ff5e31b8eb 100644 --- a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treesource.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treesource.controller.js @@ -28,11 +28,19 @@ angular.module('umbraco') $scope.openContentPicker =function(){ - var d = dialogService.treePicker({ - section: $scope.model.value.type, - treeAlias: $scope.model.value.type, - multiPicker: false, - callback: populate}); + $scope.treePickerOverlay = { + view: "treepicker", + section: $scope.model.value.type, + treeAlias: $scope.model.value.type, + multiPicker: false, + show: true, + submit: function(model) { + var item = model.selection[0]; + populate(item); + $scope.treePickerOverlay.show = false; + $scope.treePickerOverlay = null; + } + }; }; $scope.clear = function() { diff --git a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treesource.html b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treesource.html index 8811b549b5..08bd4913f6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treesource.html +++ b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treesource.html @@ -6,9 +6,11 @@ -
      -
    • - + - \ No newline at end of file + + + + + diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/changepassword/changepassword.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/changepassword/changepassword.controller.js index 9c322f3f5d..71d84a8412 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/changepassword/changepassword.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/changepassword/changepassword.controller.js @@ -61,7 +61,7 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.ChangePasswordCont if (!isNew) { $scope.model.confirm = ""; } - else if ($scope.model.value.newPassword.length > 0) { + else if ($scope.model.value.newPassword && $scope.model.value.newPassword.length > 0) { //if it is new and a new password has been set, then set the confirm password too $scope.model.confirm = $scope.model.value.newPassword; } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/changepassword/changepassword.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/changepassword/changepassword.html index c224d04fae..c6c9bb115f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/changepassword/changepassword.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/changepassword/changepassword.html @@ -20,29 +20,32 @@ - + val-server="oldPassword" no-dirty-check + autocomplete="off"/> Required - + val-server="value" + ng-minlength="{{$parent.model.config.minPasswordLength}}" no-dirty-check + autocomplete="off" /> Required Minimum {{$parent.model.config.minPasswordLength}} characters - + val-compare="password" no-dirty-check + autocomplete="off" /> Passwords must match diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/multicolorpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/multicolorpicker.controller.js index ddf20adb19..d31ac911a9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/multicolorpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/multicolorpicker.controller.js @@ -9,7 +9,7 @@ assetsService.load([ //"lib/spectrum/tinycolor.js", "lib/spectrum/spectrum.js" - ]).then(function () { + ], $scope).then(function () { var elem = $element.find("input"); elem.spectrum({ color: null, diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js index 48f4ed5bda..667dfd0f22 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js @@ -1,7 +1,7 @@ //this controller simply tells the dialogs service to open a mediaPicker window //with a specified callback, this callback will receive an object with a selection on it -function contentPickerController($scope, dialogService, entityResource, editorState, $log, iconHelper, $routeParams, fileManager, contentEditingHelper) { +function contentPickerController($scope, dialogService, entityResource, editorState, $log, iconHelper, $routeParams, fileManager, contentEditingHelper, angularHelper, navigationService, $location) { function trim(str, chr) { var rgxtrim = (!chr) ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g'); @@ -49,7 +49,9 @@ function contentPickerController($scope, dialogService, entityResource, editorSt //the default pre-values var defaultConfig = { multiPicker: false, - showEditButton: false, + showOpenButton: false, + showEditButton: false, + showPathOnHover: false, startNode: { query: "", type: "content", @@ -64,13 +66,17 @@ function contentPickerController($scope, dialogService, entityResource, editorSt //Umbraco persists boolean for prevalues as "0" or "1" so we need to convert that! $scope.model.config.multiPicker = ($scope.model.config.multiPicker === "1" ? true : false); + $scope.model.config.showOpenButton = ($scope.model.config.showOpenButton === "1" ? true : false); $scope.model.config.showEditButton = ($scope.model.config.showEditButton === "1" ? true : false); - + $scope.model.config.showPathOnHover = ($scope.model.config.showPathOnHover === "1" ? true : false); + var entityType = $scope.model.config.startNode.type === "member" ? "Member" : $scope.model.config.startNode.type === "media" ? "Media" : "Document"; + $scope.allowOpenButton = entityType === "Document" || entityType === "Media"; + $scope.allowEditButton = entityType === "Document"; //the dialog options for the picker var dialogOptions = { @@ -87,6 +93,7 @@ function contentPickerController($scope, dialogService, entityResource, editorSt $scope.clear(); $scope.add(data); } + angularHelper.getCurrentForm($scope).$setDirty(); }, treeAlias: $scope.model.config.startNode.type, section: $scope.model.config.startNode.type @@ -132,17 +139,53 @@ function contentPickerController($scope, dialogService, entityResource, editorSt }); } else { dialogOptions.startNodeId = $scope.model.config.startNode.id; - } - + } + //dialog - $scope.openContentPicker = function () { - var d = dialogService.treePicker(dialogOptions); + $scope.openContentPicker = function() { + $scope.contentPickerOverlay = dialogOptions; + $scope.contentPickerOverlay.view = "treepicker"; + $scope.contentPickerOverlay.show = true; + + $scope.contentPickerOverlay.submit = function(model) { + + if (angular.isArray(model.selection)) { + _.each(model.selection, function (item, i) { + $scope.add(item); + }); + } + + $scope.contentPickerOverlay.show = false; + $scope.contentPickerOverlay = null; + } + + $scope.contentPickerOverlay.close = function(oldModel) { + $scope.contentPickerOverlay.show = false; + $scope.contentPickerOverlay = null; + } + }; $scope.remove = function (index) { $scope.renderModel.splice(index, 1); + angularHelper.getCurrentForm($scope).$setDirty(); }; - + + $scope.showNode = function (index) { + var item = $scope.renderModel[index]; + var id = item.id; + var section = $scope.model.config.startNode.type.toLowerCase(); + + entityResource.getPath(id, entityType).then(function (path) { + navigationService.changeSection(section); + navigationService.showTree(section, { + tree: section, path: path, forceReload: false, activate: true + }); + var routePath = section + "/" + section + "/edit/" + id.toString(); + $location.path(routePath).search(""); + }); + } + $scope.add = function (item) { var currIds = _.map($scope.renderModel, function (i) { return i.id; @@ -150,7 +193,7 @@ function contentPickerController($scope, dialogService, entityResource, editorSt if (currIds.indexOf(item.id) < 0) { item.icon = iconHelper.convertFromLegacyIcon(item.icon); - $scope.renderModel.push({ name: item.name, id: item.id, icon: item.icon }); + $scope.renderModel.push({ name: item.name, id: item.id, icon: item.icon, path: item.path }); } }; @@ -182,7 +225,7 @@ function contentPickerController($scope, dialogService, entityResource, editorSt if (entity) { entity.icon = iconHelper.convertFromLegacyIcon(entity.icon); - $scope.renderModel.push({ name: entity.name, id: entity.id, icon: entity.icon }); + $scope.renderModel.push({ name: entity.name, id: entity.id, icon: entity.icon, path: entity.path }); } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html index c5de0a69db..9f5f3fb60f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html @@ -1,23 +1,22 @@
      - + - @@ -29,7 +28,7 @@
    - + @@ -37,11 +36,19 @@
    You need to add at least {{model.config.minNumber}} items
    - +
    You can only have {{model.config.maxNumber}} items selected
    - \ No newline at end of file + + + + + diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.controller.js index 9ad177285c..fe413e0159 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.controller.js @@ -17,10 +17,22 @@ function dateTimePickerController($scope, notificationsService, assetsService, a //map the user config $scope.model.config = angular.extend(config, $scope.model.config); + //ensure the format doesn't get overwritten with an empty string + if ($scope.model.config.format === "" || $scope.model.config.format === undefined || $scope.model.config.format === null) { + $scope.model.config.format = $scope.model.config.pickTime ? "YYYY-MM-DD HH:mm:ss" : "YYYY-MM-DD"; + } + + $scope.hasDatetimePickerValue = $scope.model.value ? true : false; + $scope.datetimePickerValue = null; //hide picker if clicking on the document $scope.hidePicker = function () { - $element.find("div:first").datetimepicker("hide"); + //$element.find("div:first").datetimepicker("hide"); + // Sometimes the statement above fails and generates errors in the browser console. The following statements fix that. + var dtp = $element.find("div:first"); + if (dtp && dtp.datetimepicker) { + dtp.datetimepicker("hide"); + } }; $(document).bind("click", $scope.hidePicker); @@ -28,13 +40,14 @@ function dateTimePickerController($scope, notificationsService, assetsService, a function applyDate(e) { angularHelper.safeApply($scope, function() { // when a date is changed, update the model - if (e.date) { - if ($scope.model.config.pickTime) { - $scope.model.value = e.date.format("YYYY-MM-DD HH:mm:ss"); - } - else { - $scope.model.value = e.date.format("YYYY-MM-DD"); - } + if (e.date && e.date.isValid()) { + $scope.datePickerForm.datepicker.$setValidity("pickerError", true); + $scope.hasDatetimePickerValue = true; + $scope.datetimePickerValue = e.date.format($scope.model.config.format); + } + else { + $scope.hasDatetimePickerValue = false; + $scope.datetimePickerValue = null; } if (!$scope.model.config.pickTime) { @@ -43,6 +56,15 @@ function dateTimePickerController($scope, notificationsService, assetsService, a }); } + var picker = null; + + $scope.clearDate = function() { + $scope.hasDatetimePickerValue = false; + $scope.datetimePickerValue = null; + $scope.model.value = null; + $scope.datePickerForm.datepicker.$setValidity("pickerError", true); + } + //get the current user to see if we can localize this picker userService.getCurrentUser().then(function (user) { @@ -55,34 +77,87 @@ function dateTimePickerController($scope, notificationsService, assetsService, a $scope.model.config.language = user.locale; - assetsService.load(filesToLoad).then( - function () { + assetsService.load(filesToLoad, $scope).then( + function () { //The Datepicker js and css files are available and all components are ready to use. // Get the id of the datepicker button that was clicked var pickerId = $scope.model.alias; - // Open the datepicker and add a changeDate eventlistener - $element.find("div:first") - .datetimepicker($scope.model.config) - .on("dp.change", applyDate); + var element = $element.find("div:first"); - //manually assign the date to the plugin - $element.find("div:first").datetimepicker("setValue", $scope.model.value ? $scope.model.value : null); + // Open the datepicker and add a changeDate eventlistener + element + .datetimepicker(angular.extend({ useCurrent: true }, $scope.model.config)) + .on("dp.change", applyDate) + .on("dp.error", function(a, b, c) { + $scope.hasDatetimePickerValue = false; + $scope.datePickerForm.datepicker.$setValidity("pickerError", false); + }); + + if ($scope.hasDatetimePickerValue) { + + //assign value to plugin/picker + var dateVal = $scope.model.value ? moment($scope.model.value, $scope.model.config.format) : moment(); + element.datetimepicker("setValue", dateVal); + $scope.datetimePickerValue = moment($scope.model.value).format($scope.model.config.format); + } + + element.find("input").bind("blur", function() { + //we need to force an apply here + $scope.$apply(); + }); //Ensure to remove the event handler when this instance is destroyted - $scope.$on('$destroy', function () { - $element.find("div:first").datetimepicker("destroy"); - }); + $scope.$on('$destroy', function () { + element.find("input").unbind("blur"); + element.datetimepicker("destroy"); + }); + + + var unsubscribe = $scope.$on("formSubmitting", function (ev, args) { + if ($scope.hasDatetimePickerValue) { + var elementData = $element.find("div:first").data().DateTimePicker; + if ($scope.model.config.pickTime) { + $scope.model.value = elementData.getDate().format("YYYY-MM-DD HH:mm:ss"); + } + else { + $scope.model.value = elementData.getDate().format("YYYY-MM-DD"); + } + } + else { + $scope.model.value = null; + } + }); + //unbind doc click event! + $scope.$on('$destroy', function () { + unsubscribe(); + }); + + }); }); - }); + var unsubscribe = $scope.$on("formSubmitting", function (ev, args) { + if ($scope.hasDatetimePickerValue) { + if ($scope.model.config.pickTime) { + $scope.model.value = $element.find("div:first").data().DateTimePicker.getDate().format("YYYY-MM-DD HH:mm:ss"); + } + else { + $scope.model.value = $element.find("div:first").data().DateTimePicker.getDate().format("YYYY-MM-DD"); + } + } + else { + $scope.model.value = null; + } + }); + //unbind doc click event! $scope.$on('$destroy', function () { $(document).unbind("click", $scope.hidePicker); + unsubscribe(); }); } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.html index 95452e0f19..898afe88a3 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.html @@ -1,14 +1,26 @@
    -
    - - - - -
    + +
    + + - Required - {{propertyForm.datepicker.errorMsg}} + + + + +
    + + Required + {{datePickerForm.datepicker.errorMsg}} + Invalid date + +

    + Clear date +

    + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/decimal/decimal.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/decimal/decimal.html new file mode 100644 index 0000000000..64730bbfb2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/decimal/decimal.html @@ -0,0 +1,12 @@ +
    + + + Not a number + {{propertyForm.requiredField.errorMsg}} +
    \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js index 37dea64c21..47a4f90187 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js @@ -2,16 +2,16 @@ * @ngdoc controller * @name Umbraco.Editors.FileUploadController * @function - * + * * @description * The controller for the file upload property editor. It is important to note that the $scope.model.value - * doesn't necessarily depict what is saved for this property editor. $scope.model.value can be empty when we + * doesn't necessarily depict what is saved for this property editor. $scope.model.value can be empty when we * are submitting files because in that case, we are adding files to the fileManager which is what gets peristed * on the server. However, when we are clearing files, we are setting $scope.model.value to "{clearFiles: true}" - * to indicate on the server that we are removing files for this property. We will keep the $scope.model.value to + * to indicate on the server that we are removing files for this property. We will keep the $scope.model.value to * be the name of the file selected (if it is a newly selected file) or keep it to be it's original value, this allows * for the editors to check if the value has changed and to re-bind the property if that is true. - * + * */ function fileUploadController($scope, $element, $compile, imageHelper, fileManager, umbRequestHelper, mediaHelper) { @@ -21,11 +21,14 @@ function fileUploadController($scope, $element, $compile, imageHelper, fileManag fileManager.setFiles($scope.model.alias, []); //clear the current files $scope.files = []; - if ($scope.propertyForm.fileCount) { - //this is required to re-validate - $scope.propertyForm.fileCount.$setViewValue($scope.files.length); + + if($scope.propertyForm) { + if ($scope.propertyForm.fileCount) { + //this is required to re-validate + $scope.propertyForm.fileCount.$setViewValue($scope.files.length); + } } - + } /** this method is used to initialize the data and to re-initialize it if the server value is changed */ @@ -37,7 +40,7 @@ function fileUploadController($scope, $element, $compile, imageHelper, fileManag index = 1; } - //this is used in order to tell the umb-single-file-upload directive to + //this is used in order to tell the umb-single-file-upload directive to //rebuild the html input control (and thus clearing the selected file) since //that is the only way to manipulate the html for the file input control. $scope.rebuildInput = { @@ -49,7 +52,7 @@ function fileUploadController($scope, $element, $compile, imageHelper, fileManag $scope.originalValue = $scope.model.value; //create the property to show the list of files currently saved - if ($scope.model.value != "") { + if ($scope.model.value != "" && $scope.model.value != undefined) { var images = $scope.model.value.split(","); @@ -95,7 +98,9 @@ function fileUploadController($scope, $element, $compile, imageHelper, fileManag //reset to original value $scope.model.value = $scope.originalValue; //this is required to re-validate - $scope.propertyForm.fileCount.$setViewValue($scope.files.length); + if($scope.propertyForm) { + $scope.propertyForm.fileCount.$setViewValue($scope.files.length); + } } }); @@ -127,12 +132,12 @@ function fileUploadController($scope, $element, $compile, imageHelper, fileManag //listen for when the model value has changed $scope.$watch("model.value", function (newVal, oldVal) { - //cannot just check for !newVal because it might be an empty string which we + //cannot just check for !newVal because it might be an empty string which we //want to look for. if (newVal !== null && newVal !== undefined && newVal !== oldVal) { //now we need to check if we need to re-initialize our structure which is kind of tricky // since we only want to do that if the server has changed the value, not if this controller - // has changed the value. There's only 2 scenarios where we change the value internall so + // has changed the value. There's only 2 scenarios where we change the value internall so // we know what those values can be, if they are not either of them, then we'll re-initialize. if (newVal.clearFiles !== true && newVal !== $scope.originalValue && !newVal.selectedFiles) { @@ -158,17 +163,17 @@ angular.module("umbraco") "imagesApiBaseUrl", "GetBigThumbnail", [{ originalImagePath: property.value }]); - + return thumbnailUrl; } else { return null; } - + } else { return property.value; } }); } - }); \ No newline at end of file + }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/folderbrowser/folderbrowser.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/folderbrowser/folderbrowser.controller.js index 5b76cbae81..5e0cc40db9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/folderbrowser/folderbrowser.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/folderbrowser/folderbrowser.controller.js @@ -1,20 +1,29 @@ angular.module("umbraco") +//this controller is obsolete and should not be used anymore +//it proxies everything to the system media list view which has overtaken +//all the work this property editor used to perform .controller("Umbraco.PropertyEditors.FolderBrowserController", - function ($rootScope, $scope, $routeParams, $timeout, editorState, navigationService) { + function ($rootScope, $scope, contentTypeResource) { + //get the system media listview + contentTypeResource.getPropertyTypeScaffold(-96) + .then(function(dt) { - var dialogOptions = $scope.dialogOptions; - $scope.creating = $routeParams.create; - $scope.nodeId = $routeParams.id; + $scope.fakeProperty = { + alias: "contents", + config: dt.config, + description: "", + editor: dt.editor, + hideLabel: true, + id: 1, + label: "Contents:", + validation: { + mandatory: false, + pattern: null + }, + value: "", + view: dt.view + }; - $scope.onUploadComplete = function () { - - //sync the tree - don't force reload since we're not updating this particular node (i.e. its name or anything), - // then we'll get the resulting tree node which we can then use to reload it's children. - var path = editorState.current.path; - navigationService.syncTree({ tree: "media", path: path, forceReload: false }).then(function (syncArgs) { - navigationService.reloadNode(syncArgs.node); - }); - - } + }); }); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/folderbrowser/folderbrowser.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/folderbrowser/folderbrowser.html index 4e3814b9ab..76b362f2c9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/folderbrowser/folderbrowser.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/folderbrowser/folderbrowser.html @@ -1,5 +1,3 @@
    - -
    \ No newline at end of file + + diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/googlemaps/googlemaps.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/googlemaps/googlemaps.controller.js index bb9a3b3e7d..f8e89d7200 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/googlemaps/googlemaps.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/googlemaps/googlemaps.controller.js @@ -1,6 +1,6 @@ angular.module("umbraco") .controller("Umbraco.PropertyEditors.GoogleMapsController", - function ($rootScope, $scope, notificationsService, dialogService, assetsService, $log, $timeout) { + function ($element, $rootScope, $scope, notificationsService, dialogService, assetsService, $log, $timeout) { assetsService.loadJs('http://www.google.com/jsapi') .then(function () { @@ -58,8 +58,21 @@ angular.module("umbraco") }); }); - $('a[data-toggle="tab"]').on('shown', function (e) { + var tabShown = function(e) { google.maps.event.trigger(map, 'resize'); + }; + + //listen for tab changes + if (tabsCtrl != null) { + tabsCtrl.onTabShown(function (args) { + tabShown(); + }); + } + + $element.closest('.umb-panel.tabbable').on('shown', '.nav-tabs a', tabShown); + + $scope.$on('$destroy', function () { + $element.closest('.umb-panel.tabbable').off('shown', '.nav-tabs a', tabShown); }); } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/additem.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/additem.html deleted file mode 100644 index 77e2c27bd5..0000000000 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/additem.html +++ /dev/null @@ -1,16 +0,0 @@ -
    -
    - - - -
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/config.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/config.controller.js deleted file mode 100644 index 731912c951..0000000000 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/config.controller.js +++ /dev/null @@ -1,74 +0,0 @@ -angular.module("umbraco") - .controller("Umbraco.PropertyEditors.GridController.Dialogs.Config", - function ($scope, $http) { - - var placeHolder = "{0}"; - var addModifier = function(val, modifier){ - if (!modifier || modifier.indexOf(placeHolder) < 0) { - return val; - } else { - return modifier.replace(placeHolder, val); - } - } - - var stripModifier = function (val, modifier) { - if (!val || !modifier || modifier.indexOf(placeHolder) < 0) { - return val; - } else { - var paddArray = modifier.split(placeHolder); - if(paddArray.length == 1){ - if (modifier.indexOf(placeHolder) === 0) { - return val.slice(0, -paddArray[0].length); - } else { - return val.slice(paddArray[0].length, 0); - } - }else{ - return val.slice(paddArray[0].length, -paddArray[1].length); - } - } - } - - - $scope.styles = _.filter( angular.copy($scope.dialogOptions.config.items.styles), function(item){return (item.applyTo === undefined || item.applyTo === $scope.dialogOptions.itemType); }); - $scope.config = _.filter( angular.copy($scope.dialogOptions.config.items.config), function(item){return (item.applyTo === undefined || item.applyTo === $scope.dialogOptions.itemType); }); - - - var element = $scope.dialogOptions.gridItem; - if(angular.isObject(element.config)){ - _.each($scope.config, function(cfg){ - var val = element.config[cfg.key]; - if(val){ - cfg.value = stripModifier(val, cfg.modifier); - } - }); - } - - if(angular.isObject(element.styles)){ - _.each($scope.styles, function(style){ - var val = element.styles[style.key]; - if(val){ - style.value = stripModifier(val, style.modifier); - } - }); - } - - - $scope.saveAndClose = function(){ - var styleObject = {}; - var configObject = {}; - - _.each($scope.styles, function(style){ - if(style.value){ - styleObject[style.key] = addModifier(style.value, style.modifier); - } - }); - _.each($scope.config, function (cfg) { - if (cfg.value) { - configObject[cfg.key] = addModifier(cfg.value, cfg.modifier); - } - }); - - $scope.submit({config: configObject, styles: styleObject}); - }; - - }); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/config.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/config.html index 429bd68abd..aa96348130 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/config.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/config.html @@ -1,44 +1,16 @@ - -
    - - - - - +
    + + + +
    + +
    +

    Style

    + + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/editconfig.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/editconfig.controller.js deleted file mode 100644 index f1016f6c79..0000000000 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/editconfig.controller.js +++ /dev/null @@ -1,15 +0,0 @@ -angular.module("umbraco") - .controller("Umbraco.PropertyEditors.GridPrevalueEditorController.Dialogs.EditConfig", - function ($scope, $http) { - - $scope.renderModel = {}; - $scope.renderModel.name = $scope.dialogOptions.name; - $scope.renderModel.config = $scope.dialogOptions.config; - - $scope.saveAndClose = function(isValid){ - if(isValid){ - $scope.submit($scope.renderModel.config); - } - }; - - }); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/editconfig.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/editconfig.html index 61465f95ae..1c67eb0212 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/editconfig.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/editconfig.html @@ -1,35 +1,14 @@
    -
    - - - - -
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/layoutconfig.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/layoutconfig.controller.js index edb7f768d1..e886cb977e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/layoutconfig.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/layoutconfig.controller.js @@ -2,9 +2,9 @@ angular.module("umbraco") .controller("Umbraco.PropertyEditors.GridPrevalueEditor.LayoutConfigController", function ($scope) { - $scope.currentLayout = $scope.dialogOptions.currentLayout; - $scope.columns = $scope.dialogOptions.columns; - $scope.rows = $scope.dialogOptions.rows; + $scope.currentLayout = $scope.model.currentLayout; + $scope.columns = $scope.model.columns; + $scope.rows = $scope.model.rows; $scope.scaleUp = function(section, max, overflow){ var add = 1; @@ -21,7 +21,7 @@ angular.module("umbraco") }; $scope.percentage = function(spans){ - return ((spans / $scope.columns) * 100).toFixed(1); + return ((spans / $scope.columns) * 100).toFixed(8); }; $scope.toggleCollection = function(collection, toggle){ @@ -68,4 +68,4 @@ angular.module("umbraco") $scope.availableLayoutSpace = $scope.columns - total; } }, true); - }); \ No newline at end of file + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/layoutconfig.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/layoutconfig.html index de17dcf638..9cbe74bae2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/layoutconfig.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/layoutconfig.html @@ -1,116 +1,108 @@ -
    - diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.controller.js index 70f9951de5..09fa263354 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.controller.js @@ -1,8 +1,8 @@ function RowConfigController($scope) { - - $scope.currentRow = angular.copy($scope.dialogOptions.currentRow); - $scope.editors = $scope.dialogOptions.editors; - $scope.columns = $scope.dialogOptions.columns; + + $scope.currentRow = $scope.model.currentRow; + $scope.editors = $scope.model.editors; + $scope.columns = $scope.model.columns; $scope.scaleUp = function(section, max, overflow) { var add = 1; @@ -19,7 +19,7 @@ function RowConfigController($scope) { }; $scope.percentage = function(spans) { - return ((spans / $scope.columns) * 100).toFixed(1); + return ((spans / $scope.columns) * 100).toFixed(8); }; $scope.toggleCollection = function(collection, toggle) { @@ -88,10 +88,6 @@ function RowConfigController($scope) { } }, true); - $scope.complete = function () { - angular.extend($scope.dialogOptions.currentRow, $scope.currentRow); - $scope.close(); - } } -angular.module("umbraco").controller("Umbraco.PropertyEditors.GridPrevalueEditor.RowConfigController", RowConfigController); \ No newline at end of file +angular.module("umbraco").controller("Umbraco.PropertyEditors.GridPrevalueEditor.RowConfigController", RowConfigController); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.html index 63e250645f..b1343a960b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.html @@ -1,102 +1,87 @@ -
    - + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowdeleteconfirm.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowdeleteconfirm.html index db416396a1..c69ae2cb8f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowdeleteconfirm.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowdeleteconfirm.html @@ -1,26 +1,18 @@ -
    - \ No newline at end of file +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/embed.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/embed.controller.js index d6abe24974..dfc9cfdcef 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/embed.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/embed.controller.js @@ -1,13 +1,23 @@ angular.module("umbraco") .controller("Umbraco.PropertyEditors.Grid.EmbedController", - function ($scope, $rootScope, $timeout, dialogService) { + function ($scope, $rootScope, $timeout) { $scope.setEmbed = function(){ - dialogService.embedDialog({ - callback: function (data) { - $scope.control.value = data; - } - }); + $scope.embedDialog = {}; + $scope.embedDialog.view = "embed"; + $scope.embedDialog.show = true; + + $scope.embedDialog.submit = function(model) { + $scope.control.value = model.embed.preview; + $scope.embedDialog.show = false; + $scope.embedDialog = null; + }; + + $scope.embedDialog.close = function(oldModel) { + $scope.embedDialog.show = false; + $scope.embedDialog = null; + }; + }; $timeout(function(){ @@ -16,4 +26,3 @@ angular.module("umbraco") } }, 200); }); - diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/embed.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/embed.html index f2c49e8f39..e506ac5880 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/embed.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/embed.html @@ -1,10 +1,17 @@
    -
    +
    Click to embed
    -
    + + + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/macro.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/macro.controller.js index ac5399cb75..d536d9174a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/macro.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/macro.controller.js @@ -5,23 +5,40 @@ angular.module("umbraco") $scope.title = "Click to insert macro"; $scope.setMacro = function(){ - dialogService.macroPicker({ - dialogData: { - richTextEditor: true, - macroData: $scope.control.value || { - macroAlias: $scope.control.editor.config && $scope.control.editor.config.macroAlias - ? $scope.control.editor.config.macroAlias : "" - } - }, - callback: function (data) { - $scope.control.value = { - macroAlias: data.macroAlias, - macroParamsDictionary: data.macroParamsDictionary - }; - $scope.setPreview($scope.control.value ); + var dialogData = { + richTextEditor: true, + macroData: $scope.control.value || { + macroAlias: $scope.control.editor.config && $scope.control.editor.config.macroAlias + ? $scope.control.editor.config.macroAlias : "" } - }); + }; + + $scope.macroPickerOverlay = {}; + $scope.macroPickerOverlay.view = "macropicker"; + $scope.macroPickerOverlay.dialogData = dialogData; + $scope.macroPickerOverlay.show = true; + + $scope.macroPickerOverlay.submit = function(model) { + + var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, dialogData.renderingEngine); + + $scope.control.value = { + macroAlias: macroObject.macroAlias, + macroParamsDictionary: macroObject.macroParamsDictionary + }; + + $scope.setPreview($scope.control.value ); + + $scope.macroPickerOverlay.show = false; + $scope.macroPickerOverlay = null; + }; + + $scope.macroPickerOverlay.close = function(oldModel) { + $scope.macroPickerOverlay.show = false; + $scope.macroPickerOverlay = null; + }; + }; $scope.setPreview = function(macro){ @@ -45,4 +62,3 @@ angular.module("umbraco") } }, 200); }); - diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/macro.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/macro.html index ad646b361c..732b551b52 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/macro.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/macro.html @@ -1,6 +1,6 @@
    -
    +
    {{title}}
    @@ -13,5 +13,11 @@
    -
    + + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js index b4da2512da..8a2e71cf0e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js @@ -1,25 +1,34 @@ angular.module("umbraco") .controller("Umbraco.PropertyEditors.Grid.MediaController", - function ($scope, $rootScope, $timeout, dialogService) { + function ($scope, $rootScope, $timeout) { $scope.setImage = function(){ + $scope.mediaPickerOverlay = {}; + $scope.mediaPickerOverlay.view = "mediapicker"; + $scope.mediaPickerOverlay.cropSize = $scope.control.editor.config && $scope.control.editor.config.size ? $scope.control.editor.config.size : undefined; + $scope.mediaPickerOverlay.showDetails = true; + $scope.mediaPickerOverlay.show = true; - dialogService.mediaPicker({ - startNodeId: $scope.control.editor.config && $scope.control.editor.config.startNodeId ? $scope.control.editor.config.startNodeId : undefined, - multiPicker: false, - cropSize: $scope.control.editor.config && $scope.control.editor.config.size ? $scope.control.editor.config.size : undefined, - showDetails: true, - callback: function (data) { + $scope.mediaPickerOverlay.submit = function(model) { + var selectedImage = model.selectedImages[0]; - $scope.control.value = { - focalPoint: data.focalPoint, - id: data.id, - image: data.image - }; + $scope.control.value = { + focalPoint: selectedImage.focalPoint, + id: selectedImage.id, + image: selectedImage.image, + altText: selectedImage.altText + }; - $scope.setUrl(); - } - }); + $scope.setUrl(); + + $scope.mediaPickerOverlay.show = false; + $scope.mediaPickerOverlay = null; + }; + + $scope.mediaPickerOverlay.close = function(oldModel) { + $scope.mediaPickerOverlay.show = false; + $scope.mediaPickerOverlay = null; + }; }; $scope.setUrl = function(){ @@ -44,7 +53,7 @@ angular.module("umbraco") $timeout(function(){ if($scope.control.$initializing){ $scope.setImage(); - }else{ + }else if($scope.control.value){ $scope.setUrl(); } }, 200); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.html index efb6c3e290..dd35757397 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.html @@ -1,6 +1,6 @@
    -
    +
    Click to insert image
    @@ -13,4 +13,12 @@ class="fullSizeImage" />
    + + + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js new file mode 100644 index 0000000000..e406f87aed --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js @@ -0,0 +1,74 @@ +(function() { + "use strict"; + + function GridRichTextEditorController($scope, tinyMceService, macroService) { + + var vm = this; + + vm.openLinkPicker = openLinkPicker; + vm.openMediaPicker = openMediaPicker; + vm.openMacroPicker = openMacroPicker; + vm.openEmbed = openEmbed; + + function openLinkPicker(editor, currentTarget, anchorElement) { + vm.linkPickerOverlay = { + view: "linkpicker", + currentTarget: currentTarget, + show: true, + submit: function(model) { + tinyMceService.insertLinkInEditor(editor, model.target, anchorElement); + vm.linkPickerOverlay.show = false; + vm.linkPickerOverlay = null; + } + }; + } + + function openMediaPicker(editor, currentTarget, userData) { + vm.mediaPickerOverlay = { + currentTarget: currentTarget, + onlyImages: true, + showDetails: true, + startNodeId: userData.startMediaId, + view: "mediapicker", + show: true, + submit: function(model) { + tinyMceService.insertMediaInEditor(editor, model.selectedImages[0]); + vm.mediaPickerOverlay.show = false; + vm.mediaPickerOverlay = null; + } + }; + } + + function openEmbed(editor) { + vm.embedOverlay = { + view: "embed", + show: true, + submit: function(model) { + tinyMceService.insertEmbeddedMediaInEditor(editor, model.embed.preview); + vm.embedOverlay.show = false; + vm.embedOverlay = null; + } + }; + } + + function openMacroPicker(editor, dialogData) { + vm.macroPickerOverlay = { + view: "macropicker", + dialogData: dialogData, + show: true, + submit: function(model) { + var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, dialogData.renderingEngine); + tinyMceService.insertMacroInEditor(editor, macroObject, $scope); + vm.macroPickerOverlay.show = false; + vm.macroPickerOverlay = null; + } + }; + } + + + + } + + angular.module("umbraco").controller("Umbraco.PropertyEditors.Grid.RichTextEditorController", GridRichTextEditorController); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.html index 93229ecd4a..7bceed6a60 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.html @@ -1,5 +1,42 @@ -
    +
    + +
    +
    + + + + + + + + + + + + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.controller.js index 3ccbffcfa6..ea15d1df40 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.controller.js @@ -1,14 +1,18 @@ angular.module("umbraco") .controller("Umbraco.PropertyEditors.GridController", - function ($scope, $http, assetsService, $rootScope, dialogService, gridService, mediaResource, imageHelper, $timeout, umbRequestHelper) { + function ($scope, $http, assetsService, localizationService, $rootScope, dialogService, gridService, mediaResource, imageHelper, $timeout, umbRequestHelper) { // Grid status variables + var placeHolder = ""; $scope.currentRow = null; $scope.currentCell = null; $scope.currentToolsControl = null; $scope.currentControl = null; $scope.openRTEToolbarId = null; $scope.hasSettings = false; + $scope.showRowConfigurations = true; + $scope.sortMode = false; + $scope.reorderKey = "general_reorder"; // ********************************************* // Sortable options @@ -16,47 +20,57 @@ angular.module("umbraco") var draggedRteSettings; - $scope.sortableOptions = { + $scope.sortableOptionsRow = { distance: 10, cursor: "move", - placeholder: 'ui-sortable-placeholder', - handle: '.cell-tools-move', + placeholder: "ui-sortable-placeholder", + handle: ".umb-row-title-bar", + helper: "clone", forcePlaceholderSize: true, tolerance: "pointer", zIndex: 999999999999999999, scrollSensitivity: 100, cursorAt: { - top: 45, - left: 90 + top: 40, + left: 60 }, sort: function (event, ui) { /* prevent vertical scroll out of the screen */ - var max = $(".usky-grid").width() - 150; - if (parseInt(ui.helper.css('left')) > max) { - ui.helper.css({ 'left': max + "px" }) + var max = $(".umb-grid").width() - 150; + if (parseInt(ui.helper.css("left")) > max) { + ui.helper.css({ "left": max + "px" }); } - if (parseInt(ui.helper.css('left')) < 20) { - ui.helper.css({ 'left': 20 }) + if (parseInt(ui.helper.css("left")) < 20) { + ui.helper.css({ "left": 20 }); } }, start: function (e, ui) { + + // Fade out row when sorting + ui.item.context.style.display = "block"; + ui.item.context.style.opacity = "0.5"; + draggedRteSettings = {}; - ui.item.find('.mceNoEditor').each(function () { + ui.item.find(".mceNoEditor").each(function () { // remove all RTEs in the dragged row and save their settings - var id = $(this).attr('id'); + var id = $(this).attr("id"); draggedRteSettings[id] = _.findWhere(tinyMCE.editors, { id: id }).settings; - tinyMCE.execCommand('mceRemoveEditor', false, id); + // tinyMCE.execCommand("mceRemoveEditor", false, id); }); }, stop: function (e, ui) { + + // Fade in row when sorting stops + ui.item.context.style.opacity = "1"; + // reset all RTEs affected by the dragging - ui.item.parents(".usky-column").find('.mceNoEditor').each(function () { - var id = $(this).attr('id'); + ui.item.parents(".umb-column").find(".mceNoEditor").each(function () { + var id = $(this).attr("id"); draggedRteSettings[id] = draggedRteSettings[id] || _.findWhere(tinyMCE.editors, { id: id }).settings; - tinyMCE.execCommand('mceRemoveEditor', false, id); + tinyMCE.execCommand("mceRemoveEditor", false, id); tinyMCE.init(draggedRteSettings[id]); }); } @@ -69,8 +83,9 @@ angular.module("umbraco") distance: 10, cursor: "move", placeholder: "ui-sortable-placeholder", - handle: '.cell-tools-move', - connectWith: ".usky-cell", + handle: ".umb-control-handle", + helper: "clone", + connectWith: ".umb-cell-inner", forcePlaceholderSize: true, tolerance: "pointer", zIndex: 999999999999999999, @@ -81,125 +96,161 @@ angular.module("umbraco") }, sort: function (event, ui) { + /* prevent vertical scroll out of the screen */ - var position = parseInt(ui.item.parent().offset().left) + parseInt(ui.helper.css('left')) - parseInt($(".usky-grid").offset().left); - var max = $(".usky-grid").width() - 220; + var position = parseInt(ui.item.parent().offset().left) + parseInt(ui.helper.css("left")) - parseInt($(".umb-grid").offset().left); + var max = $(".umb-grid").width() - 220; if (position > max) { - ui.helper.css({ 'left': max - parseInt(ui.item.parent().offset().left) + parseInt($(".usky-grid").offset().left) + "px" }) + ui.helper.css({ "left": max - parseInt(ui.item.parent().offset().left) + parseInt($(".umb-grid").offset().left) + "px" }); } if (position < 0) { - ui.helper.css({ 'left': 0 - parseInt(ui.item.parent().offset().left) + parseInt($(".usky-grid").offset().left) + "px" }) + ui.helper.css({ "left": 0 - parseInt(ui.item.parent().offset().left) + parseInt($(".umb-grid").offset().left) + "px" }); } }, over: function (event, ui) { - allowedEditors = $(event.target).scope().area.allowed; + var allowedEditors = $(event.target).scope().area.allowed; if ($.inArray(ui.item.scope().control.editor.alias, allowedEditors) < 0 && allowedEditors) { + + $scope.$apply(function () { + $(event.target).scope().area.dropNotAllowed = true; + }); + ui.placeholder.hide(); cancelMove = true; } else { - ui.placeholder.show(); + if ($(event.target).scope().area.controls.length == 0){ + + $scope.$apply(function () { + $(event.target).scope().area.dropOnEmpty = true; + }); + ui.placeholder.hide(); + } else { + ui.placeholder.show(); + } cancelMove = false; } + }, + out: function(event, ui) { + $scope.$apply(function () { + $(event.target).scope().area.dropNotAllowed = false; + $(event.target).scope().area.dropOnEmpty = false; + }); }, update: function (event, ui) { - // add all RTEs which are affected by the dragging + /* add all RTEs which are affected by the dragging */ if (!ui.sender) { if (cancelMove) { ui.item.sortable.cancel(); } - ui.item.parents(".usky-cell").find('.mceNoEditor').each(function () { - if ($.inArray($(this).attr('id'), notIncludedRte) < 0) { - notIncludedRte.splice(0, 0, $(this).attr('id')); + ui.item.parents(".umb-cell.content").find(".mceNoEditor").each(function () { + if ($.inArray($(this).attr("id"), notIncludedRte) < 0) { + notIncludedRte.splice(0, 0, $(this).attr("id")); } }); } else { - $(event.target).find('.mceNoEditor').each(function () { - if ($.inArray($(this).attr('id'), notIncludedRte) < 0) { - notIncludedRte.splice(0, 0, $(this).attr('id')); + $(event.target).find(".mceNoEditor").each(function () { + if ($.inArray($(this).attr("id"), notIncludedRte) < 0) { + notIncludedRte.splice(0, 0, $(this).attr("id")); } }); } - }, start: function (e, ui) { - ui.item.find('.mceNoEditor').each(function () { + + // fade out control when sorting + ui.item.context.style.display = "block"; + ui.item.context.style.opacity = "0.5"; + + // reset dragged RTE settings in case a RTE isn't dragged + draggedRteSettings = undefined; + ui.item.context.style.display = "block"; + ui.item.find(".mceNoEditor").each(function () { notIncludedRte = []; + var editors = _.findWhere(tinyMCE.editors, { id: $(this).attr("id") }); // save the dragged RTE settings - draggedRteSettings = _.findWhere(tinyMCE.editors, { id: $(this).attr('id') }).settings; + if(editors) { + draggedRteSettings = editors.settings; + + // remove the dragged RTE + tinyMCE.execCommand("mceRemoveEditor", false, $(this).attr("id")); + + } - // remove the dragged RTE - tinyMCE.execCommand('mceRemoveEditor', false, $(this).attr('id')); }); }, stop: function (e, ui) { - ui.item.parents(".usky-cell").find('.mceNoEditor').each(function () { - if ($.inArray($(this).attr('id'), notIncludedRte) < 0) { + + // Fade in control when sorting stops + ui.item.context.style.opacity = "1"; + + ui.item.parents(".umb-cell-content").find(".mceNoEditor").each(function () { + if ($.inArray($(this).attr("id"), notIncludedRte) < 0) { // add all dragged's neighbouring RTEs in the new cell - notIncludedRte.splice(0, 0, $(this).attr('id')); + notIncludedRte.splice(0, 0, $(this).attr("id")); } }); $timeout(function () { - // reconstruct the dragged RTE - tinyMCE.init(draggedRteSettings); + // reconstruct the dragged RTE (could be undefined when dragging something else than RTE) + if (draggedRteSettings !== undefined) { + tinyMCE.init(draggedRteSettings); + } _.forEach(notIncludedRte, function (id) { // reset all the other RTEs - if (id != draggedRteSettings.id) { + if (draggedRteSettings === undefined || id !== draggedRteSettings.id) { var rteSettings = _.findWhere(tinyMCE.editors, { id: id }).settings; - tinyMCE.execCommand('mceRemoveEditor', false, id); + tinyMCE.execCommand("mceRemoveEditor", false, id); tinyMCE.init(rteSettings); } }); }, 500, false); + + $scope.$apply(function () { + + var cell = $(e.target).scope().area; + cell.hasActiveChild = hasActiveChild(cell, cell.controls); + cell.active = false; + }); } }; + $scope.toggleSortMode = function() { + $scope.sortMode = !$scope.sortMode; + if($scope.sortMode) { + $scope.reorderKey = "general_reorderDone"; + } else { + $scope.reorderKey = "general_reorder"; + } + }; + // ********************************************* // Add items overlay menu // ********************************************* - $scope.overlayMenu = { - show: false, - style: {}, - area: undefined, - key: undefined - }; - - $scope.addItemOverlay = function (event, area, index, key) { - $scope.overlayMenu.area = area; - $scope.overlayMenu.index = index; - $scope.overlayMenu.style = {}; - $scope.overlayMenu.key = key; - - //todo calculate position... - var offset = $(event.target).offset(); - var height = $(window).height(); - var width = $(window).width(); - - if ((height - offset.top) < 250) { - $scope.overlayMenu.style.bottom = 0; - $scope.overlayMenu.style.top = "initial"; - } else if (offset.top < 300) { - $scope.overlayMenu.style.top = 190; - } - - $scope.overlayMenu.show = true; - }; - - $scope.closeItemOverlay = function () { - $scope.currentControl = null; - $scope.overlayMenu.show = false; - $scope.overlayMenu.key = undefined; - }; + $scope.openEditorOverlay = function(event, area, index, key) { + $scope.editorOverlay = { + view: "itempicker", + filter: false, + title: localizationService.localize("grid_insertControl").then(function (value) {return value;}), + availableItems: area.$allowedEditors, + event: event, + show: true, + submit: function(model) { + $scope.addControl(model.selectedItem, area, index); + $scope.editorOverlay.show = false; + $scope.editorOverlay = null; + } + }; + }; // ********************************************* // Template management functions @@ -219,41 +270,31 @@ angular.module("umbraco") // Row management function // ********************************************* - $scope.setCurrentRow = function (row) { - $scope.currentRow = row; + $scope.clickRow = function(index, rows) { + rows[index].active = true; }; - $scope.disableCurrentRow = function () { - $scope.currentRow = null; + $scope.clickOutsideRow = function(index, rows) { + rows[index].active = false; }; - $scope.setWarnighlightRow = function (row) { - $scope.currentWarnhighlightRow = row; - }; + function getAllowedLayouts(section) { - $scope.disableWarnhighlightRow = function () { - $scope.currentWarnhighlightRow = null; - }; - - $scope.setInfohighlightRow = function (row) { - $scope.currentInfohighlightRow = row; - }; - - $scope.disableInfohighlightRow = function () { - $scope.currentInfohighlightRow = null; - }; - - $scope.getAllowedLayouts = function (column) { var layouts = $scope.model.config.items.layouts; - if (column.allowed && column.allowed.length > 0) { + //This will occur if it is a new section which has been + // created from a 'template' + if (section.allowed && section.allowed.length > 0) { return _.filter(layouts, function (layout) { - return _.indexOf(column.allowed, layout.name) >= 0; + return _.indexOf(section.allowed, layout.name) >= 0; }); - } else { + } + else { + + return layouts; } - }; + } $scope.addRow = function (section, layout) { @@ -267,6 +308,9 @@ angular.module("umbraco") if (row) { section.rows.push(row); } + + $scope.showRowConfigurations = false; + }; $scope.removeRow = function (section, $index) { @@ -274,38 +318,128 @@ angular.module("umbraco") section.rows.splice($index, 1); $scope.currentRow = null; $scope.openRTEToolbarId = null; - $scope.initContent(); + + //$scope.initContent(); + } + + if(section.rows.length === 0) { + $scope.showRowConfigurations = true; } }; $scope.editGridItemSettings = function (gridItem, itemType) { - dialogService.open( - { - template: "views/propertyeditors/grid/dialogs/config.html", - gridItem: gridItem, - config: $scope.model.config, - itemType: itemType, - callback: function (data) { + placeHolder = "{0}"; + var styles = _.filter( angular.copy($scope.model.config.items.styles), function(item){return (item.applyTo === undefined || item.applyTo === itemType); }); + var config = _.filter( angular.copy($scope.model.config.items.config), function(item){return (item.applyTo === undefined || item.applyTo === itemType); }); - gridItem.styles = data.styles; - gridItem.config = data.config; + if(angular.isObject(gridItem.config)){ + _.each(config, function(cfg){ + var val = gridItem.config[cfg.key]; + if(val){ + cfg.value = stripModifier(val, cfg.modifier); + } + }); + } + if(angular.isObject(gridItem.styles)){ + _.each(styles, function(style){ + var val = gridItem.styles[style.key]; + if(val){ + style.value = stripModifier(val, style.modifier); + } + }); + } + + $scope.gridItemSettingsDialog = {}; + $scope.gridItemSettingsDialog.view = "views/propertyeditors/grid/dialogs/config.html"; + $scope.gridItemSettingsDialog.title = "Settings"; + $scope.gridItemSettingsDialog.styles = styles; + $scope.gridItemSettingsDialog.config = config; + + $scope.gridItemSettingsDialog.show = true; + + $scope.gridItemSettingsDialog.submit = function(model) { + + var styleObject = {}; + var configObject = {}; + + _.each(model.styles, function(style){ + if(style.value){ + styleObject[style.key] = addModifier(style.value, style.modifier); + } + }); + _.each(model.config, function (cfg) { + if (cfg.value) { + configObject[cfg.key] = addModifier(cfg.value, cfg.modifier); } }); + gridItem.styles = styleObject; + gridItem.config = configObject; + gridItem.hasConfig = gridItemHasConfig(styleObject, configObject); + + $scope.gridItemSettingsDialog.show = false; + $scope.gridItemSettingsDialog = null; + }; + + $scope.gridItemSettingsDialog.close = function(oldModel) { + $scope.gridItemSettingsDialog.show = false; + $scope.gridItemSettingsDialog = null; + }; + }; + function stripModifier(val, modifier) { + if (!val || !modifier || modifier.indexOf(placeHolder) < 0) { + return val; + } else { + var paddArray = modifier.split(placeHolder); + if(paddArray.length == 1){ + if (modifier.indexOf(placeHolder) === 0) { + return val.slice(0, -paddArray[0].length); + } else { + return val.slice(paddArray[0].length, 0); + } + } else { + if (paddArray[1].length === 0) { + return val.slice(paddArray[0].length); + } + return val.slice(paddArray[0].length, -paddArray[1].length); + } + } + } + + var addModifier = function(val, modifier){ + if (!modifier || modifier.indexOf(placeHolder) < 0) { + return val; + } else { + return modifier.replace(placeHolder, val); + } + }; + + function gridItemHasConfig(styles, config) { + + if(_.isEmpty(styles) && _.isEmpty(config)) { + return false; + } else { + return true; + } + + } + // ********************************************* // Area management functions // ********************************************* - $scope.setCurrentCell = function (cell) { - $scope.currentCell = cell; + $scope.clickCell = function(index, cells, row) { + cells[index].active = true; + row.hasActiveChild = true; }; - $scope.disableCurrentCell = function () { - $scope.currentCell = null; + $scope.clickOutsideCell = function(index, cells, row) { + cells[index].active = false; + row.hasActiveChild = hasActiveChild(row, cells); }; $scope.cellPreview = function (cell) { @@ -317,53 +451,38 @@ angular.module("umbraco") } }; - $scope.setInfohighlightArea = function (cell) { - $scope.currentInfohighlightArea = cell; - }; - - $scope.disableInfohighlightArea = function () { - $scope.currentInfohighlightArea = null; - }; - // ********************************************* // Control management functions // ********************************************* - $scope.setCurrentControl = function (Control) { - $scope.currentControl = Control; + $scope.clickControl = function (index, controls, cell) { + controls[index].active = true; + cell.hasActiveChild = true; }; - $scope.disableCurrentControl = function (Control) { - $scope.currentControl = null; + $scope.clickOutsideControl = function (index, controls, cell) { + controls[index].active = false; + cell.hasActiveChild = hasActiveChild(cell, controls); }; - $scope.setCurrentToolsControl = function (Control) { - $scope.currentToolsControl = Control; - }; + function hasActiveChild(item, children) { - $scope.disableCurrentToolsControl = function (Control) { - $scope.currentToolsControl = null; - }; + var activeChild = false; - $scope.setWarnhighlightControl = function (Control) { - $scope.currentWarnhighlightControl = Control; - }; + for(var i = 0; children.length > i; i++) { + var child = children[i]; - $scope.disableWarnhighlightControl = function (Control) { - $scope.currentWarnhighlightControl = null; - }; + if(child.active) { + activeChild = true; + } + } - $scope.setInfohighlightControl = function (Control) { - $scope.currentInfohighlightControl = Control; - }; + if(activeChild) { + return true; + } - $scope.disableInfohighlightControl = function (Control) { - $scope.currentInfohighlightControl = null; - }; + } - $scope.setUniqueId = function (cell, index) { - return guid(); - }; var guid = (function () { function s4() { @@ -372,28 +491,36 @@ angular.module("umbraco") .substring(1); } return function () { - return s4() + s4() + '-' + s4() + '-' + s4() + '-' + - s4() + '-' + s4() + s4() + s4(); + return s4() + s4() + "-" + s4() + "-" + s4() + "-" + + s4() + "-" + s4() + s4() + s4(); }; })(); - $scope.addControl = function (editor, cell, index) { - $scope.closeItemOverlay(); + $scope.setUniqueId = function (cell, index) { + return guid(); + }; + + $scope.addControl = function (editor, cell, index, initialize) { + + initialize = (initialize !== false); var newControl = { value: null, editor: editor, - $initializing: true + $initializing: initialize }; if (index === undefined) { index = cell.controls.length; } + newControl.active = true; + //populate control $scope.initControl(newControl, index + 1); - cell.controls.splice(index + 1, 0, newControl); + cell.controls.push(newControl); + }; $scope.addTinyMce = function (cell) { @@ -411,7 +538,7 @@ angular.module("umbraco") }; $scope.percentage = function (spans) { - return ((spans / $scope.model.config.items.columns) * 100).toFixed(1); + return ((spans / $scope.model.config.items.columns) * 100).toFixed(8); }; @@ -419,11 +546,19 @@ angular.module("umbraco") scopedObject.deletePrompt = false; e.preventDefault(); e.stopPropagation(); - } + }; - $scope.showPrompt = function (scopedObject) { - scopedObject.deletePrompt = true; - } + $scope.togglePrompt = function (scopedObject) { + scopedObject.deletePrompt = !scopedObject.deletePrompt; + }; + + $scope.hidePrompt = function (scopedObject) { + scopedObject.deletePrompt = false; + }; + + $scope.toggleAddRow = function() { + $scope.showRowConfigurations = !$scope.showRowConfigurations; + }; // ********************************************* @@ -442,18 +577,44 @@ angular.module("umbraco") var clear = true; //settings indicator shortcut - if ($scope.model.config.items.config || $scope.model.config.items.styles) { + if ( ($scope.model.config.items.config && $scope.model.config.items.config.length > 0) || ($scope.model.config.items.styles && $scope.model.config.items.styles.length > 0)) { $scope.hasSettings = true; } - //ensure the grid has a column value set, if nothing is found, set it to 12 + //ensure the grid has a column value set, + //if nothing is found, set it to 12 if ($scope.model.config.items.columns && angular.isString($scope.model.config.items.columns)) { $scope.model.config.items.columns = parseInt($scope.model.config.items.columns); } else { $scope.model.config.items.columns = 12; } - if ($scope.model.value && $scope.model.value.sections && $scope.model.value.sections.length > 0) { + if ($scope.model.value && $scope.model.value.sections && $scope.model.value.sections.length > 0 && $scope.model.value.sections[0].rows && $scope.model.value.sections[0].rows.length > 0) { + + if ($scope.model.value.name && angular.isArray($scope.model.config.items.templates)) { + + //This will occur if it is an existing value, in which case + // we need to determine which layout was applied by looking up + // the name + // TODO: We need to change this to an immutable ID!! + + var found = _.find($scope.model.config.items.templates, function (t) { + return t.name === $scope.model.value.name; + }); + + if (found && angular.isArray(found.sections) && found.sections.length === $scope.model.value.sections.length) { + + //Cool, we've found the template associated with our current value with matching sections counts, now we need to + // merge this template data on to our current value (as if it was new) so that we can preserve what is and isn't + // allowed for this template based on the current config. + + _.each(found.sections, function (templateSection, index) { + angular.extend($scope.model.value.sections[index], angular.copy(templateSection)); + }); + + } + } + _.forEach($scope.model.value.sections, function (section, index) { if (section.grid > 0) { @@ -469,6 +630,7 @@ angular.module("umbraco") }); } else if ($scope.model.config.items.templates && $scope.model.config.items.templates.length === 1) { $scope.addTemplate($scope.model.config.items.templates[0]); + clear = false; } if (clear) { @@ -479,18 +641,13 @@ angular.module("umbraco") $scope.initSection = function (section) { section.$percentage = $scope.percentage(section.grid); - var layouts = $scope.model.config.items.layouts; + section.$allowedLayouts = getAllowedLayouts(section); - if (section.allowed && section.allowed.length > 0) { - section.$allowedLayouts = _.filter(layouts, function (layout) { - return _.indexOf(section.allowed, layout.name) >= 0; - }); - } else { - section.$allowedLayouts = layouts; - } - - if (!section.rows) { + if (!section.rows || section.rows.length === 0) { section.rows = []; + if(section.$allowedLayouts.length === 1){ + $scope.addRow(section, section.$allowedLayouts[0]); + } } else { _.forEach(section.rows, function (row, index) { if (!row.$initialized) { @@ -504,6 +661,9 @@ angular.module("umbraco") } } }); + + // if there is more than one row added - hide row add tools + $scope.showRowConfigurations = false; } }; @@ -524,6 +684,8 @@ angular.module("umbraco") original = angular.copy(original); original.styles = row.styles; original.config = row.config; + original.hasConfig = gridItemHasConfig(row.styles, row.config); + //sync area configuration _.each(original.areas, function (area, areaIndex) { @@ -535,24 +697,9 @@ angular.module("umbraco") if (currentArea) { area.config = currentArea.config; area.styles = currentArea.styles; + area.hasConfig = gridItemHasConfig(currentArea.styles, currentArea.config); } - //copy over existing controls into the new areas - if (row.areas.length > areaIndex && row.areas[areaIndex].controls) { - area.controls = currentArea.controls; - - _.forEach(area.controls, function (control, controlIndex) { - $scope.initControl(control, controlIndex); - }); - - } else { - area.controls = []; - } - - //set width - area.$percentage = $scope.percentage(area.grid); - area.$uniqueId = $scope.setUniqueId(); - //set editor permissions if (!area.allowed || area.allowAll === true) { area.$allowedEditors = $scope.availableEditors; @@ -566,6 +713,29 @@ angular.module("umbraco") area.$allowsRTE = true; } } + + //copy over existing controls into the new areas + if (row.areas.length > areaIndex && row.areas[areaIndex].controls) { + area.controls = currentArea.controls; + + _.forEach(area.controls, function (control, controlIndex) { + $scope.initControl(control, controlIndex); + }); + + } else { + //if empty + area.controls = []; + + //if only one allowed editor + if(area.$allowedEditors.length === 1){ + $scope.addControl(area.$allowedEditors[0], area, 0, false); + } + } + + //set width + area.$percentage = $scope.percentage(area.grid); + area.$uniqueId = $scope.setUniqueId(); + } else { original.areas.splice(areaIndex, 1); } @@ -595,7 +765,7 @@ angular.module("umbraco") control.$uniqueId = $scope.setUniqueId(); //error handling in case of missing editor.. - //should only happen if stripped earlier + //should only happen if stripped earlier if (!control.editor) { control.$editorPath = "views/propertyeditors/grid/editors/error.html"; } @@ -635,4 +805,50 @@ angular.module("umbraco") $scope.initContent(); }); + + //Clean the grid value before submitting to the server, we don't need + // all of that grid configuration in the value to be stored!! All of that + // needs to be merged in at runtime to ensure that the real config values are used + // if they are ever updated. + + var unsubscribe = $scope.$on("formSubmitting", function () { + + if ($scope.model.value && $scope.model.value.sections) { + _.each($scope.model.value.sections, function(section) { + if (section.rows) { + _.each(section.rows, function (row) { + if (row.areas) { + _.each(row.areas, function (area) { + + //Remove the 'editors' - these are the allowed editors, these will + // be injected at runtime to this editor, it should not be persisted + + if (area.editors) { + delete area.editors; + } + + if (area.controls) { + _.each(area.controls, function (control) { + if (control.editor) { + //replace + var alias = control.editor.alias; + control.editor = { + alias: alias + }; + } + }); + } + }); + } + }); + } + }); + } + }); + + //when the scope is destroyed we need to unsubscribe + $scope.$on("$destroy", function () { + unsubscribe(); + }); + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.html index 05809cad02..c9bec51f94 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.html @@ -1,4 +1,19 @@ -
    +
    + + + + + + + + + +
    @@ -7,6 +22,8 @@
    +

    +
    @@ -14,7 +31,6 @@
    @@ -28,187 +44,189 @@
    - {{layout.name}} + {{template.name}}
    -

    - -
    + -
    +
    -
    -
    +
    -
    + + +
    - -
    +
    - -
    -
    +
    +
    {{row.name}}
    - - - - - - - +
    +
    -
    - - - -
    + +
    + +
    + +
    + +
    + + + +
    + +
    -
    - - - -
    -
    +
    - {{row.name}} -
    -
    +
    - -
    + +
    - -
    + +
    + + +
    - -
    -
    + +
    + + +
    - - - - - +
    + +
    - -
    -
    +
    +
    + +
    +
    - -
    - - - -
    +
    - -
    - - - -
    + +
    +
    + + +
    +
    -
    + +
    -
    - - {{control.editor.name}} +
    - -
    -
    -
    -
    - - -
    -
    - -

    -
    +
    + {{control.editor.name}} +
    -
    +
    - - - +
    -
    -
    +
    + {{control.editor.name}} +
    + +
    + +
    + + + +
    + +
    + +
    + + +
    +
    + +
    +
    + +
    + + + +
    +
    +
    + +
    +
    @@ -222,7 +240,22 @@ -
    +
    + + + + + + + +
    + +
    + +

    -

    - -

    - -
    @@ -261,4 +289,18 @@
    -
    \ No newline at end of file + + + + + + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.prevalues.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.prevalues.controller.js index 229a7992f5..810623320c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.prevalues.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.prevalues.controller.js @@ -74,25 +74,40 @@ angular.module("umbraco") template *****************/ - $scope.configureTemplate = function(template){ - if(template === undefined){ - template = { - name: "", - sections:[ + $scope.configureTemplate = function(template) { - ] - }; - $scope.model.value.templates.push(template); - } - - dialogService.open( - { - template: "views/propertyEditors/grid/dialogs/layoutconfig.html", - currentLayout: template, - rows: $scope.model.value.layouts, - columns: $scope.model.value.columns - } - ); + var templatesCopy = angular.copy($scope.model.value.templates); + + if (template === undefined) { + template = { + name: "", + sections: [ + + ] + }; + $scope.model.value.templates.push(template); + } + + $scope.layoutConfigOverlay = {}; + $scope.layoutConfigOverlay.view = "views/propertyEditors/grid/dialogs/layoutconfig.html"; + $scope.layoutConfigOverlay.currentLayout = template; + $scope.layoutConfigOverlay.rows = $scope.model.value.layouts; + $scope.layoutConfigOverlay.columns = $scope.model.value.columns; + $scope.layoutConfigOverlay.show = true; + + $scope.layoutConfigOverlay.submit = function(model) { + $scope.layoutConfigOverlay.show = false; + $scope.layoutConfigOverlay = null; + }; + + $scope.layoutConfigOverlay.close = function(oldModel) { + + //reset templates + $scope.model.value.templates = templatesCopy; + + $scope.layoutConfigOverlay.show = false; + $scope.layoutConfigOverlay = null; + } }; @@ -105,48 +120,64 @@ angular.module("umbraco") Row *****************/ - $scope.configureLayout = function(layout){ + $scope.configureLayout = function(layout) { - if(layout === undefined){ - layout = { - name: "", - areas:[ + var layoutsCopy = angular.copy($scope.model.value.layouts); - ] - }; - $scope.model.value.layouts.push(layout); - } + if(layout === undefined){ + layout = { + name: "", + areas:[ + + ] + }; + $scope.model.value.layouts.push(layout); + } + + $scope.rowConfigOverlay = {}; + $scope.rowConfigOverlay.view = "views/propertyEditors/grid/dialogs/rowconfig.html"; + $scope.rowConfigOverlay.currentRow = layout; + $scope.rowConfigOverlay.editors = $scope.editors; + $scope.rowConfigOverlay.columns = $scope.model.value.columns; + $scope.rowConfigOverlay.show = true; + + $scope.rowConfigOverlay.submit = function(model) { + $scope.rowConfigOverlay.show = false; + $scope.rowConfigOverlay = null; + }; + + $scope.rowConfigOverlay.close = function(oldModel) { + $scope.model.value.layouts = layoutsCopy; + $scope.rowConfigOverlay.show = false; + $scope.rowConfigOverlay = null; + }; - dialogService.open( - { - template: "views/propertyEditors/grid/dialogs/rowconfig.html", - currentRow: layout, - editors: $scope.editors, - columns: $scope.model.value.columns - } - ); }; //var rowDeletesPending = false; - $scope.deleteLayout = function (index) { - //rowDeletesPending = true; + $scope.deleteLayout = function(index) { + + $scope.rowDeleteOverlay = {}; + $scope.rowDeleteOverlay.view = "views/propertyEditors/grid/dialogs/rowdeleteconfirm.html"; + $scope.rowDeleteOverlay.dialogData = { + rowName: $scope.model.value.layouts[index].name + }; + $scope.rowDeleteOverlay.show = true; + + $scope.rowDeleteOverlay.submit = function(model) { + + $scope.model.value.layouts.splice(index, 1); + + $scope.rowDeleteOverlay.show = false; + $scope.rowDeleteOverlay = null; + }; + + $scope.rowDeleteOverlay.close = function(oldModel) { + $scope.rowDeleteOverlay.show = false; + $scope.rowDeleteOverlay = null; + }; - //show ok/cancel dialog - var confirmDialog = dialogService.open( - { - template: "views/propertyEditors/grid/dialogs/rowdeleteconfirm.html", - show: true, - callback: function() { - $scope.model.value.layouts.splice(index, 1); - }, - dialogData: { - rowName: $scope.model.value.layouts[index].name - } - } - ); - }; - /**************** @@ -161,7 +192,7 @@ angular.module("umbraco") }; $scope.percentage = function(spans){ - return ((spans / $scope.model.value.columns) * 100).toFixed(1); + return ((spans / $scope.model.value.columns) * 100).toFixed(8); }; $scope.zeroWidthFilter = function (cell) { @@ -176,30 +207,40 @@ angular.module("umbraco") collection.splice(index, 1); }; - var editConfigCollection = function(configValues, title, callbackOnSave){ - dialogService.open( - { - template: "views/propertyeditors/grid/dialogs/editconfig.html", - config: configValues, - name: title, - callback: function(data){ - callbackOnSave(data); - } - }); + var editConfigCollection = function(configValues, title, callback) { + + $scope.editConfigCollectionOverlay = {}; + $scope.editConfigCollectionOverlay.view = "views/propertyeditors/grid/dialogs/editconfig.html"; + $scope.editConfigCollectionOverlay.config = configValues; + $scope.editConfigCollectionOverlay.title = title; + $scope.editConfigCollectionOverlay.show = true; + + $scope.editConfigCollectionOverlay.submit = function(model) { + + callback(model.config) + + $scope.editConfigCollectionOverlay.show = false; + $scope.editConfigCollectionOverlay = null; + }; + + $scope.editConfigCollectionOverlay.close = function(oldModel) { + $scope.editConfigCollectionOverlay.show = false; + $scope.editConfigCollectionOverlay = null; + }; + }; - $scope.editConfig = function(){ - editConfigCollection($scope.model.value.config, "Settings", function(data){ - $scope.model.value.config = data; - }); - }; - - $scope.editStyles = function(){ - editConfigCollection($scope.model.value.styles, "Styling", function(data){ - $scope.model.value.styles = data; - }); + $scope.editConfig = function() { + editConfigCollection($scope.model.value.config, "Settings", function(data) { + $scope.model.value.config = data; + }); }; + $scope.editStyles = function() { + editConfigCollection($scope.model.value.styles, "Styling", function(data){ + $scope.model.value.styles = data; + }); + }; /**************** editors diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.prevalues.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.prevalues.html index b7f46f0996..3061f01c29 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.prevalues.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.prevalues.html @@ -32,7 +32,7 @@ ng-click="deleteTemplate($index)">
    -
    @@ -160,4 +160,33 @@
    + + + + + + + + + + + + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.html index 34b976e078..29148b32eb 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.html @@ -1,63 +1,65 @@ -
    +
    - - -

    Click to upload

    - -
    - + + +

    Click to upload

    + +
    -
    -
    -
    - +
    -
    - -
    - - Reset - -
    -
    +
    -
    -
    - -
    +
    +
    -
    + - +
    + +
    - Remove file -
    -
    + + Reset + +
    +
    +
    + +
    + + + + +
    + Remove file + +
    \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.prevalues.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.prevalues.html index 69d54244e8..3ba6979905 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.prevalues.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.prevalues.html @@ -6,7 +6,7 @@ ui-sortable ng-model="model.value">
  • - + {{node.alias}} diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/integer/integer.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/integer/integer.html index 251a6066d8..ff82be99b2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/integer/integer.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/integer/integer.html @@ -1,10 +1,12 @@ 
    - + Not a number {{propertyForm.requiredField.errorMsg}} -
    \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts.prevalues.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts.prevalues.controller.js new file mode 100644 index 0000000000..24ba8edaf6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts.prevalues.controller.js @@ -0,0 +1,78 @@ +/** + * @ngdoc controller + * @name Umbraco.PrevalueEditors.ListViewLayoutsPreValsController + * @function + * + * @description + * The controller for configuring layouts for list views + */ +(function() { + "use strict"; + + function ListViewLayoutsPreValsController($scope) { + + var vm = this; + vm.focusLayoutName = false; + + vm.layoutsSortableOptions = { + distance: 10, + tolerance: "pointer", + opacity: 0.7, + scroll: true, + cursor: "move", + handle: ".list-view-layout__sort-handle" + }; + + vm.addLayout = addLayout; + vm.removeLayout = removeLayout; + vm.openIconPicker = openIconPicker; + + function activate() { + + + + } + + function addLayout() { + + vm.focusLayoutName = false; + + var layout = { + "name": "", + "path": "", + "icon": "icon-stop", + "selected": true + }; + + $scope.model.value.push(layout); + + } + + function removeLayout($index, layout) { + $scope.model.value.splice($index, 1); + } + + function openIconPicker(layout) { + vm.iconPickerDialog = { + view: "iconpicker", + show: true, + submit: function(model) { + if (model.color) { + layout.icon = model.icon + " " + model.color; + } else { + layout.icon = model.icon; + } + vm.focusLayoutName = true; + vm.iconPickerDialog.show = false; + vm.iconPickerDialog = null; + } + }; + } + + activate(); + + } + + angular.module("umbraco").controller("Umbraco.PrevalueEditors.ListViewLayoutsPreValsController", ListViewLayoutsPreValsController); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts.prevalues.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts.prevalues.html new file mode 100644 index 0000000000..2d74be6640 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts.prevalues.html @@ -0,0 +1,49 @@ +
    + +
    + +
    + + + +
    + + + + + +
    + +
    + +
    + +
    + + {{ layout.name }} + (system layout) +
    + +
    + +
    + +
    + + +
    + +
    + + Add layout + +
    + + + + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.html new file mode 100644 index 0000000000..ccf18b0236 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.html @@ -0,0 +1,58 @@ +
    + +
    + + + + +
    + +
    + + + + + + + + + + + +
      +
    • +
      {{ property.header }}
      +
      {{ vm.mediaDetailsTooltip.item[property.alias] }}
      +
    • +
    +
    + +
    + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.listviewlayout.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.listviewlayout.controller.js new file mode 100644 index 0000000000..e4f7cb3c8b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.listviewlayout.controller.js @@ -0,0 +1,106 @@ +/** + * @ngdoc controller + * @name Umbraco.Editors.DocumentType.EditController + * @function + * + * @description + * The controller for the content type editor + */ +(function() { + "use strict"; + + function ListViewGridLayoutController($scope, $routeParams, mediaHelper, mediaResource, $location, listViewHelper) { + + var vm = this; + + vm.nodeId = $scope.contentId; + vm.acceptedFileTypes = mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes); + vm.maxFileSize = Umbraco.Sys.ServerVariables.umbracoSettings.maxFileSize + "KB"; + vm.activeDrag = false; + vm.mediaDetailsTooltip = {}; + vm.itemsWithoutFolders = []; + + vm.dragEnter = dragEnter; + vm.dragLeave = dragLeave; + vm.onFilesQueue = onFilesQueue; + vm.onUploadComplete = onUploadComplete; + vm.hoverMediaItemDetails = hoverMediaItemDetails; + vm.selectItem = selectItem; + vm.selectFolder = selectFolder; + vm.clickItem = clickItem; + + function activate() { + vm.itemsWithoutFolders = filterOutFolders($scope.items); + } + + function filterOutFolders(items) { + + var newArray = []; + + if(items && items.length ) { + + for (var i = 0; items.length > i; i++) { + var item = items[i]; + var isFolder = !mediaHelper.hasFilePropertyType(item); + + if (!isFolder) { + newArray.push(item); + } + } + + } + + return newArray; + } + + function dragEnter(el, event) { + vm.activeDrag = true; + } + + function dragLeave(el, event) { + vm.activeDrag = false; + } + + function onFilesQueue() { + vm.activeDrag = false; + } + + function onUploadComplete() { + $scope.getContent($scope.contentId); + } + + function hoverMediaItemDetails(item, event, hover) { + + if (hover && !vm.mediaDetailsTooltip.show) { + + vm.mediaDetailsTooltip.event = event; + vm.mediaDetailsTooltip.item = item; + vm.mediaDetailsTooltip.show = true; + + } else if (!hover && vm.mediaDetailsTooltip.show) { + + vm.mediaDetailsTooltip.show = false; + + } + + } + + function selectItem(selectedItem, $event, index) { + listViewHelper.selectHandler(selectedItem, index, vm.itemsWithoutFolders, $scope.selection, $event); + } + + function selectFolder(selectedItem, $event, index) { + listViewHelper.selectHandler(selectedItem, index, $scope.folders, $scope.selection, $event); + } + + function clickItem(item) { + $location.path($scope.entityType + '/' + $scope.entityType + '/edit/' + item.id); + } + + activate(); + + } + + angular.module("umbraco").controller("Umbraco.PropertyEditors.ListView.GridLayoutController", ListViewGridLayoutController); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.html new file mode 100644 index 0000000000..6f52b14af1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.html @@ -0,0 +1,49 @@ +
    + +
    + + + + + + + +
    + +
    + + + + +
    + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.listviewlayout.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.listviewlayout.controller.js new file mode 100644 index 0000000000..3716f9010a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.listviewlayout.controller.js @@ -0,0 +1,73 @@ +(function() { + "use strict"; + + function ListViewListLayoutController($scope, listViewHelper, $location, mediaHelper) { + + var vm = this; + + + vm.nodeId = $scope.contentId; + vm.acceptedFileTypes = mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes); + vm.maxFileSize = Umbraco.Sys.ServerVariables.umbracoSettings.maxFileSize + "KB"; + vm.activeDrag = false; + + vm.selectItem = selectItem; + vm.clickItem = clickItem; + vm.selectAll = selectAll; + vm.isSelectedAll = isSelectedAll; + vm.isSortDirection = isSortDirection; + vm.sort = sort; + vm.dragEnter = dragEnter; + vm.dragLeave = dragLeave; + vm.onFilesQueue = onFilesQueue; + vm.onUploadComplete = onUploadComplete; + + function selectAll($event) { + listViewHelper.selectAllItems($scope.items, $scope.selection, $event); + } + + function isSelectedAll() { + return listViewHelper.isSelectedAll($scope.items, $scope.selection); + } + + function selectItem(selectedItem, $index, $event) { + listViewHelper.selectHandler(selectedItem, $index, $scope.items, $scope.selection, $event); + } + + function clickItem(item) { + $location.path($scope.entityType + '/' + $scope.entityType + '/edit/' + item.id); + } + + function isSortDirection(col, direction) { + return listViewHelper.setSortingDirection(col, direction, $scope.options); + } + + function sort(field, allow) { + if (allow) { + listViewHelper.setSorting(field, allow, $scope.options); + $scope.getContent($scope.contentId); + } + } + + // Dropzone upload functions + function dragEnter(el, event) { + vm.activeDrag = true; + } + + function dragLeave(el, event) { + vm.activeDrag = false; + } + + function onFilesQueue() { + vm.activeDrag = false; + } + + function onUploadComplete() { + $scope.getContent($scope.contentId); + } + + } + + angular.module("umbraco").controller("Umbraco.PropertyEditors.ListView.ListLayoutController", ListViewListLayoutController); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js index a0c0c8b517..63db542ee7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js @@ -1,6 +1,6 @@ -function listViewController($rootScope, $scope, $routeParams, $injector, notificationsService, iconHelper, dialogService, editorState, localizationService, $location, appState) { +function listViewController($rootScope, $scope, $routeParams, $injector, $cookieStore, notificationsService, iconHelper, dialogService, editorState, localizationService, $location, appState, $timeout, $q, mediaResource, listViewHelper) { - //this is a quick check to see if we're in create mode, if so just exit - we cannot show children for content + //this is a quick check to see if we're in create mode, if so just exit - we cannot show children for content // that isn't created yet, if we continue this will use the parent id in the route params which isn't what // we want. NOTE: This is just a safety check since when we scaffold an empty model on the server we remove // the list view tab entirely when it's new. @@ -11,7 +11,7 @@ function listViewController($rootScope, $scope, $routeParams, $injector, notific //Now we need to check if this is for media, members or content because that will depend on the resources we use var contentResource, getContentTypesCallback, getListResultsCallback, deleteItemCallback, getIdCallback, createEditUrlCallback; - + //check the config for the entity type, or the current section name (since the config is only set in c#, not in pre-vals) if (($scope.model.config.entityType && $scope.model.config.entityType === "member") || (appState.getSectionState("currentSection") === "member")) { $scope.entityType = "member"; @@ -31,12 +31,12 @@ function listViewController($rootScope, $scope, $routeParams, $injector, notific if (($scope.model.config.entityType && $scope.model.config.entityType === "media") || (appState.getSectionState("currentSection") === "media")) { $scope.entityType = "media"; contentResource = $injector.get('mediaResource'); - getContentTypesCallback = $injector.get('mediaTypeResource').getAllowedTypes; + getContentTypesCallback = $injector.get('mediaTypeResource').getAllowedTypes; } else { $scope.entityType = "content"; contentResource = $injector.get('contentResource'); - getContentTypesCallback = $injector.get('contentTypeResource').getAllowedTypes; + getContentTypesCallback = $injector.get('contentTypeResource').getAllowedTypes; } getListResultsCallback = contentResource.getChildren; deleteItemCallback = contentResource.deleteById; @@ -51,6 +51,8 @@ function listViewController($rootScope, $scope, $routeParams, $injector, notific $scope.pagination = []; $scope.isNew = false; $scope.actionInProgress = false; + $scope.selection = []; + $scope.folders = []; $scope.listViewResultSet = { totalPages: 0, items: [] @@ -66,14 +68,20 @@ function listViewController($rootScope, $scope, $routeParams, $injector, notific { alias: 'updateDate', header: 'Last edited', isSystem: 1 }, { alias: 'updater', header: 'Last edited by', isSystem: 1 } ], + layout: { + layouts: $scope.model.config.layouts, + activeLayout: listViewHelper.getLayout($routeParams.id, $scope.model.config.layouts) + }, allowBulkPublish: true, allowBulkUnpublish: true, - allowBulkDelete: true, + allowBulkCopy: true, + allowBulkMove: true, + allowBulkDelete: true, }; //update all of the system includeProperties to enable sorting _.each($scope.options.includeProperties, function(e, i) { - + if (e.isSystem) { //NOTE: special case for contentTypeAlias, it's a system property that cannot be sorted @@ -82,7 +90,7 @@ function listViewController($rootScope, $scope, $routeParams, $injector, notific if (e.alias != "contentTypeAlias") { e.allowSorting = true; } - + //localize the header var key = getLocalizedKey(e.alias); localizationService.localize(key).then(function (v) { @@ -91,57 +99,81 @@ function listViewController($rootScope, $scope, $routeParams, $injector, notific } }); - $scope.isSortDirection = function (col, direction) { - return $scope.options.orderBy.toUpperCase() == col.toUpperCase() && $scope.options.orderDirection == direction; + $scope.selectLayout = function(selectedLayout) { + $scope.options.layout.activeLayout = listViewHelper.setLayout($routeParams.id, selectedLayout, $scope.model.config.layouts); + }; + + function showNotificationsAndReset(err, reload, successMsg) { + + //check if response is ysod + if(err.status && err.status >= 500) { + + // Open ysod overlay + $scope.ysodOverlay = { + view : "ysod", + error : err, + show : true + }; + } + + $timeout(function() { + $scope.bulkStatus = ""; + $scope.actionInProgress = false; + }, 500); + + if (reload === true) { + $scope.reloadView($scope.contentId); + } + + if (err.data && angular.isArray(err.data.notifications)) { + for (var i = 0; i < err.data.notifications.length; i++) { + notificationsService.showNotification(err.data.notifications[i]); + } + } + else if (successMsg) { + notificationsService.success("Done", successMsg); + } } - $scope.next = function() { - if ($scope.options.pageNumber < $scope.listViewResultSet.totalPages) { - $scope.options.pageNumber++; - $scope.reloadView($scope.contentId); - //TODO: this would be nice but causes the whole view to reload - //$location.search("page", $scope.options.pageNumber); - } + $scope.next = function(pageNumber) { + $scope.options.pageNumber = pageNumber; + $scope.reloadView($scope.contentId); }; $scope.goToPage = function(pageNumber) { - $scope.options.pageNumber = pageNumber + 1; - $scope.reloadView($scope.contentId); - //TODO: this would be nice but causes the whole view to reload - //$location.search("page", $scope.options.pageNumber); + $scope.options.pageNumber = pageNumber; + $scope.reloadView($scope.contentId); }; - $scope.sort = function(field, allow) { - if (allow) { - $scope.options.orderBy = field; - - if ($scope.options.orderDirection === "desc") { - $scope.options.orderDirection = "asc"; - } - else { - $scope.options.orderDirection = "desc"; - } - - $scope.reloadView($scope.contentId); - } + $scope.prev = function(pageNumber) { + $scope.options.pageNumber = pageNumber; + $scope.reloadView($scope.contentId); }; - $scope.prev = function() { - if ($scope.options.pageNumber > 1) { - $scope.options.pageNumber--; - $scope.reloadView($scope.contentId); - //TODO: this would be nice but causes the whole view to reload - //$location.search("page", $scope.options.pageNumber); - } - }; - /*Loads the search results, based on parameters set in prev,next,sort and so on*/ /*Pagination is done by an array of objects, due angularJS's funky way of monitoring state with simple values */ - $scope.reloadView = function(id) { + $scope.reloadView = function (id) { + + $scope.viewLoaded = false; + + listViewHelper.clearSelection($scope.listViewResultSet.items, $scope.folders, $scope.selection); + + if($scope.entityType === 'media') { + + mediaResource.getChildFolders($scope.contentId) + .then(function(folders) { + $scope.folders = folders; + }); + + } + getListResultsCallback(id, $scope.options).then(function (data) { + + $scope.actionInProgress = false; + $scope.listViewResultSet = data; //update all values for display @@ -161,201 +193,175 @@ function listViewController($rootScope, $scope, $routeParams, $injector, notific $scope.reloadView(id); } - $scope.pagination = []; - - //list 10 pages as per normal - if ($scope.listViewResultSet.totalPages <= 10) { - for (var i = 0; i < $scope.listViewResultSet.totalPages; i++) { - $scope.pagination.push({ - val: (i + 1), - isActive: $scope.options.pageNumber == (i + 1) - }); - } - } - else { - //if there is more than 10 pages, we need to do some fancy bits - - //get the max index to start - var maxIndex = $scope.listViewResultSet.totalPages - 10; - //set the start, but it can't be below zero - var start = Math.max($scope.options.pageNumber - 5, 0); - //ensure that it's not too far either - start = Math.min(maxIndex, start); - - for (var i = start; i < (10 + start) ; i++) { - $scope.pagination.push({ - val: (i + 1), - isActive: $scope.options.pageNumber == (i + 1) - }); - } - - //now, if the start is greater than 0 then '1' will not be displayed, so do the elipses thing - if (start > 0) { - $scope.pagination.unshift({ name: "First", val: 1, isActive: false }, {val: "...",isActive: false}); - } - - //same for the end - if (start < maxIndex) { - $scope.pagination.push({ val: "...", isActive: false }, { name: "Last", val: $scope.listViewResultSet.totalPages, isActive: false }); - } - } + $scope.viewLoaded = true; }); }; - //assign debounce method to the search to limit the queries - $scope.search = _.debounce(function() { - $scope.options.pageNumber = 1; - $scope.reloadView($scope.contentId); - }, 100); + $scope.$watch(function() { + return $scope.options.filter; + }, _.debounce(function(newVal, oldVal) { + $scope.$apply(function() { + if (newVal !== null && newVal !== undefined && newVal !== oldVal) { + $scope.options.pageNumber = 1; + $scope.actionInProgress = true; + $scope.reloadView($scope.contentId); + } + }); + }, 1000)); - $scope.enterSearch = function($event) { + $scope.filterResults = function (ev) { + //13: enter + + switch (ev.keyCode) { + case 13: + $scope.options.pageNumber = 1; + $scope.actionInProgress = true; + $scope.reloadView($scope.contentId); + break; + } + }; + + $scope.enterSearch = function ($event) { $($event.target).next().focus(); - } - - $scope.selectAll = function($event) { - var checkbox = $event.target; - if (!angular.isArray($scope.listViewResultSet.items)) { - return; - } - for (var i = 0; i < $scope.listViewResultSet.items.length; i++) { - var entity = $scope.listViewResultSet.items[i]; - entity.selected = checkbox.checked; - } - }; - - $scope.isSelectedAll = function() { - if (!angular.isArray($scope.listViewResultSet.items)) { - return false; - } - return _.every($scope.listViewResultSet.items, function(item) { - return item.selected; - }); }; $scope.isAnythingSelected = function() { - if (!angular.isArray($scope.listViewResultSet.items)) { - return false; - } - return _.some($scope.listViewResultSet.items, function(item) { - return item.selected; - }); + if ($scope.selection.length === 0) { + return false; + } else { + return true; + } + }; + + $scope.selectedItemsCount = function() { + return $scope.selection.length; + }; + + $scope.clearSelection = function() { + listViewHelper.clearSelection($scope.listViewResultSet.items, $scope.folders, $scope.selection); }; $scope.getIcon = function(entry) { return iconHelper.convertFromLegacyIcon(entry.icon); }; - $scope.delete = function() { - var selected = _.filter($scope.listViewResultSet.items, function(item) { - return item.selected; + function serial(selected, fn, getStatusMsg, index) { + return fn(selected, index).then(function (content) { + index++; + $scope.bulkStatus = getStatusMsg(index, selected.length); + return index < selected.length ? serial(selected, fn, getStatusMsg, index) : content; + }, function (err) { + var reload = index > 0; + showNotificationsAndReset(err, reload); + return err; }); - var total = selected.length; - if (total === 0) { + } + + function applySelected(fn, getStatusMsg, getSuccessMsg, confirmMsg) { + var selected = $scope.selection; + if (selected.length === 0) return; - } - - if (confirm("Sure you want to delete?") == true) { - $scope.actionInProgress = true; - $scope.bulkStatus = "Starting with delete"; - var current = 1; - - var pluralSuffix = total == 1 ? "" : "s"; - - for (var i = 0; i < selected.length; i++) { - $scope.bulkStatus = "Deleted item " + current + " out of " + total + " item" + pluralSuffix; - deleteItemCallback(getIdCallback(selected[i])).then(function (data) { - if (current === total) { - notificationsService.success("Bulk action", "Deleted " + total + " item" + pluralSuffix); - $scope.bulkStatus = ""; - $scope.reloadView($scope.contentId); - $scope.actionInProgress = false; - } - current++; - }); - } - } - - }; - - $scope.publish = function() { - var selected = _.filter($scope.listViewResultSet.items, function(item) { - return item.selected; - }); - var total = selected.length; - if (total === 0) { + if (confirmMsg && !confirm(confirmMsg)) return; - } $scope.actionInProgress = true; - $scope.bulkStatus = "Starting with publish"; - var current = 1; + $scope.bulkStatus = getStatusMsg(0, selected.length); - var pluralSuffix = total == 1 ? "" : "s"; + serial(selected, fn, getStatusMsg, 0).then(function (result) { + // executes once the whole selection has been processed + // in case of an error (caught by serial), result will be the error + if (!(result.data && angular.isArray(result.data.notifications))) + showNotificationsAndReset(result, true, getSuccessMsg(selected.length)); + }); + }; - for (var i = 0; i < selected.length; i++) { - $scope.bulkStatus = "Publishing " + current + " out of " + total + " document" + pluralSuffix; + $scope.delete = function () { + applySelected( + function (selected, index) { return deleteItemCallback(getIdCallback(selected[index])) }, + function (count, total) { return "Deleted " + count + " out of " + total + " item" + (total > 1 ? "s" : "") }, + function (total) { return "Deleted " + total + " item" + (total > 1 ? "s" : "") }, + "Sure you want to delete?"); + }; - contentResource.publishById(getIdCallback(selected[i])) - .then(function(content) { - if (current == total) { - notificationsService.success("Bulk action", "Published " + total + " document" + pluralSuffix); - $scope.bulkStatus = ""; - $scope.reloadView($scope.contentId); - $scope.actionInProgress = false; - } - current++; - }, function(err) { - - $scope.bulkStatus = ""; - $scope.reloadView($scope.contentId); - $scope.actionInProgress = false; - - //if there are validation errors for publishing then we need to show them - if (err.status === 400 && err.data && err.data.Message) { - notificationsService.error("Publish error", err.data.Message); - } - else { - dialogService.ysodDialog(err); - } - }); - - } + $scope.publish = function () { + applySelected( + function (selected, index) { return contentResource.publishById(getIdCallback(selected[index])); }, + function (count, total) { return "Published " + count + " out of " + total + " item" + (total > 1 ? "s" : "") }, + function (total) { return "Published " + total + " item" + (total > 1 ? "s" : "") }); }; $scope.unpublish = function() { - var selected = _.filter($scope.listViewResultSet.items, function(item) { - return item.selected; - }); - var total = selected.length; - if (total === 0) { - return; - } - - $scope.actionInProgress = true; - $scope.bulkStatus = "Starting with publish"; - var current = 1; - - var pluralSuffix = total == 1 ? "" : "s"; - - for (var i = 0; i < selected.length; i++) { - $scope.bulkStatus = "Unpublishing " + current + " out of " + total + " document" + pluralSuffix; - - contentResource.unPublish(getIdCallback(selected[i])) - .then(function(content) { - - if (current == total) { - notificationsService.success("Bulk action", "Unpublished " + total + " document" + pluralSuffix); - $scope.bulkStatus = ""; - $scope.reloadView($scope.contentId); - $scope.actionInProgress = false; - } - - current++; - }); - } + applySelected( + function (selected, index) { return contentResource.unPublish(getIdCallback(selected[index])); }, + function (count, total) { return "Unpublished " + count + " out of " + total + " item" + (total > 1 ? "s" : "") }, + function (total) { return "Unpublished " + total + " item" + (total > 1 ? "s" : "") }); }; + $scope.move = function() { + $scope.moveDialog = {}; + $scope.moveDialog.title = "Move"; + $scope.moveDialog.section = $scope.entityType; + $scope.moveDialog.currentNode = $scope.contentId; + $scope.moveDialog.view = "move"; + $scope.moveDialog.show = true; + + $scope.moveDialog.submit = function(model) { + + if(model.target) { + performMove(model.target); + } + + $scope.moveDialog.show = false; + $scope.moveDialog = null; + }; + + $scope.moveDialog.close = function(oldModel) { + $scope.moveDialog.show = false; + $scope.moveDialog = null; + }; + + }; + + function performMove(target) { + + applySelected( + function(selected, index) {return contentResource.move({parentId: target.id, id: getIdCallback(selected[index])}); }, + function(count, total) {return "Moved " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); }, + function(total) {return "Moved " + total + " item" + (total > 1 ? "s" : ""); }); + } + + $scope.copy = function() { + $scope.copyDialog = {}; + $scope.copyDialog.title = "Copy"; + $scope.copyDialog.section = $scope.entityType; + $scope.copyDialog.currentNode = $scope.contentId; + $scope.copyDialog.view = "copy"; + $scope.copyDialog.show = true; + + $scope.copyDialog.submit = function(model) { + if(model.target) { + performCopy(model.target, model.relateToOriginal); + } + + $scope.copyDialog.show = false; + $scope.copyDialog = null; + }; + + $scope.copyDialog.close = function(oldModel) { + $scope.copyDialog.show = false; + $scope.copyDialog = null; + }; + + }; + + function performCopy(target, relateToOriginal) { + applySelected( + function(selected, index) {return contentResource.copy({parentId: target.id, id: getIdCallback(selected[index]), relateToOriginal: relateToOriginal}); }, + function(count, total) {return "Copied " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); }, + function(total) {return "Copied " + total + " item" + (total > 1 ? "s" : ""); }); + } + function getCustomPropertyValue(alias, properties) { var value = ''; var index = 0; @@ -385,9 +391,9 @@ function listViewController($rootScope, $scope, $routeParams, $injector, notific var alias = e.alias; - // First try to pull the value directly from the alias (e.g. updatedBy) + // First try to pull the value directly from the alias (e.g. updatedBy) var value = result[alias]; - + // If this returns an object, look for the name property of that (e.g. owner.name) if (value === Object(value)) { value = value['name']; @@ -418,14 +424,18 @@ function listViewController($rootScope, $scope, $routeParams, $injector, notific }; function initView() { - if ($routeParams.id) { - $scope.listViewAllowedTypes = getContentTypesCallback($routeParams.id); - - $scope.contentId = $routeParams.id; - $scope.isTrashed = $routeParams.id === "-20" || $routeParams.id === "-21"; - - $scope.reloadView($scope.contentId); + //default to root id if the id is undefined + var id = $routeParams.id; + if(id === undefined){ + id = -1; } + + $scope.listViewAllowedTypes = getContentTypesCallback(id); + + $scope.contentId = id; + $scope.isTrashed = id === "-20" || id === "-21"; + + $scope.reloadView($scope.contentId); }; function getLocalizedKey(alias) { @@ -459,4 +469,4 @@ function listViewController($rootScope, $scope, $routeParams, $injector, notific } -angular.module("umbraco").controller("Umbraco.PropertyEditors.ListViewController", listViewController); \ No newline at end of file +angular.module("umbraco").controller("Umbraco.PropertyEditors.ListViewController", listViewController); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html index 599d8e5569..879471e6bc 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html @@ -1,138 +1,179 @@
    - -
    - + +
    + +
    + +
    + + + + + + + + + + + + + + + + {{ selectedItemsCount() }} of {{ listViewResultSet.items.length }} selected + + +
    +
    +
    +
    + +
    + + + + + + + + + + + + + +
    + + +
    + +
    + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + + + +
    -
  • - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - - Name - - - - - - - - -
    - - -
    -
    -

    There are no items show in the list.

    -
    - - - - - - {{result[column.alias]}} -
    -
    -
    - - -
    -
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/macrocontainer/macrocontainer.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/macrocontainer/macrocontainer.controller.js index 14cf1592e6..b74e30004e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/macrocontainer/macrocontainer.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/macrocontainer/macrocontainer.controller.js @@ -43,21 +43,33 @@ angular.module('umbraco') var macro = $scope.renderModel[index]; dialogData["macroData"] = macro; } - - dialogService.macroPicker({ - dialogData : dialogData, - callback: function(data) { - collectDetails(data); + $scope.macroPickerOverlay = {}; + $scope.macroPickerOverlay.view = "macropicker"; + $scope.macroPickerOverlay.dialogData = dialogData; + $scope.macroPickerOverlay.show = true; - //update the raw syntax and the list... - if(index !== null && $scope.renderModel[index]) { - $scope.renderModel[index] = data; - } else { - $scope.renderModel.push(data); - } + $scope.macroPickerOverlay.submit = function(model) { + + var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, dialogData.renderingEngine); + collectDetails(macroObject); + + //update the raw syntax and the list... + if(index !== null && $scope.renderModel[index]) { + $scope.renderModel[index] = macroObject; + } else { + $scope.renderModel.push(macroObject); } - }); + + $scope.macroPickerOverlay.show = false; + $scope.macroPickerOverlay = null; + }; + + $scope.macroPickerOverlay.close = function(oldModel) { + $scope.macroPickerOverlay.show = false; + $scope.macroPickerOverlay = null; + }; + } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/macrocontainer/macrocontainer.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/macrocontainer/macrocontainer.html index af3b82a257..96c74c9c55 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/macrocontainer/macrocontainer.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/macrocontainer/macrocontainer.html @@ -25,4 +25,12 @@ - \ No newline at end of file + + + + + diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/markdowneditor/markdowneditor.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/markdowneditor/markdowneditor.controller.js index 8893ebd523..ef5f3efde6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/markdowneditor/markdowneditor.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/markdowneditor/markdowneditor.controller.js @@ -8,6 +8,28 @@ function MarkdownEditorController($scope, assetsService, dialogService, $timeout $scope.model.value = $scope.model.config.defaultValue; } + function openMediaPicker(callback) { + + $scope.mediaPickerOverlay = {}; + $scope.mediaPickerOverlay.view = "mediaPicker"; + $scope.mediaPickerOverlay.show = true; + + $scope.mediaPickerOverlay.submit = function(model) { + + var selectedImagePath = model.selectedImages[0].image; + callback(selectedImagePath); + + $scope.mediaPickerOverlay.show = false; + $scope.mediaPickerOverlay = null; + }; + + $scope.mediaPickerOverlay.close = function(model) { + $scope.mediaPickerOverlay.show = false; + $scope.mediaPickerOverlay = null; + }; + + } + assetsService .load([ "lib/markdown/markdown.converter.js", @@ -29,15 +51,10 @@ function MarkdownEditorController($scope, assetsService, dialogService, $timeout //subscribe to the image dialog clicks editor2.hooks.set("insertImageDialog", function (callback) { - - dialogService.mediaPicker({ - callback: function (data) { - callback(data.image); - } - }); - + openMediaPicker(callback); return true; // tell the editor that we'll take care of getting the image url }); + }, 200); }); @@ -46,4 +63,4 @@ function MarkdownEditorController($scope, assetsService, dialogService, $timeout }) } -angular.module("umbraco").controller("Umbraco.PropertyEditors.MarkdownEditorController", MarkdownEditorController); \ No newline at end of file +angular.module("umbraco").controller("Umbraco.PropertyEditors.MarkdownEditorController", MarkdownEditorController); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/markdowneditor/markdowneditor.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/markdowneditor/markdowneditor.html index 482a0ae1cc..64ea1e8e2b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/markdowneditor/markdowneditor.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/markdowneditor/markdowneditor.html @@ -1,7 +1,15 @@ -
    -
    - - - -
    -
    \ No newline at end of file +
    +
    + + + +
    + + + + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js index d2342ec5bc..0859e70cdd 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js @@ -57,31 +57,39 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl $scope.sync(); }; - $scope.add = function() { - dialogService.mediaPicker({ - startNodeId: $scope.model.config.startNodeId, - multiPicker: multiPicker, - callback: function(data) { - - //it's only a single selector, so make it into an array - if (!multiPicker) { - data = [data]; - } - - _.each(data, function(media, i) { + $scope.add = function() { - if (!media.thumbnail) { - media.thumbnail = mediaHelper.resolveFileFromEntity(media, true); - } + $scope.mediaPickerOverlay = {}; + $scope.mediaPickerOverlay.startNodeId = $scope.model.config.startNodeId; + $scope.mediaPickerOverlay.multiPicker = multiPicker; + $scope.mediaPickerOverlay.view = "mediaPicker"; + $scope.mediaPickerOverlay.title = "Select media"; + $scope.mediaPickerOverlay.show = true; - $scope.images.push(media); - $scope.ids.push(media.id); - }); + $scope.mediaPickerOverlay.submit = function(model) { - $scope.sync(); - } - }); - }; + _.each(model.selectedImages, function(media, i) { + + if (!media.thumbnail) { + media.thumbnail = mediaHelper.resolveFileFromEntity(media, true); + } + + $scope.images.push(media); + $scope.ids.push(media.id); + }); + + $scope.sync(); + + $scope.mediaPickerOverlay.show = false; + $scope.mediaPickerOverlay = null; + }; + + $scope.mediaPickerOverlay.close = function(oldModel) { + $scope.mediaPickerOverlay.show = false; + $scope.mediaPickerOverlay = null; + }; + + }; $scope.sortableOptions = { update: function(e, ui) { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html index 9b74e9267f..9af1c7b10d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html @@ -15,10 +15,18 @@ - \ No newline at end of file + + + + + diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/membergrouppicker/membergrouppicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/membergrouppicker/membergrouppicker.controller.js index 1c801cddb3..5ebbd37217 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/membergrouppicker/membergrouppicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/membergrouppicker/membergrouppicker.controller.js @@ -15,38 +15,38 @@ function memberGroupPicker($scope, dialogService){ $scope.renderModel.push({ name: item, id: item, icon: 'icon-users' }); }); } - - var dialogOptions = { - multiPicker: true, - entityType: "MemberGroup", - section: "membergroup", - treeAlias: "memberGroup", - filter: "", - filterCssClass: "not-allowed", - callback: function (data) { - if (angular.isArray(data)) { - _.each(data, function (item, i) { - $scope.add(item); - }); - } else { - $scope.clear(); - $scope.add(data); - } - } + $scope.openMemberGroupPicker = function() { + + $scope.memberGroupPicker = {}; + $scope.memberGroupPicker.multiPicker = true; + $scope.memberGroupPicker.view = "memberGroupPicker"; + $scope.memberGroupPicker.show = true; + + $scope.memberGroupPicker.submit = function(model) { + + if(model.selectedMemberGroups) { + _.each(model.selectedMemberGroups, function (item, i) { + $scope.add(item); + }); + } + + if(model.selectedMemberGroup) { + $scope.clear(); + $scope.add(model.selectedMemberGroup); + } + + $scope.memberGroupPicker.show = false; + $scope.memberGroupPicker = null; + }; + + $scope.memberGroupPicker.close = function(oldModel) { + $scope.memberGroupPicker.show = false; + $scope.memberGroupPicker = null; + }; + }; - //since most of the pre-value config's are used in the dialog options (i.e. maxNumber, minNumber, etc...) we'll merge the - // pre-value config on to the dialog options - if($scope.model.config){ - angular.extend(dialogOptions, $scope.model.config); - } - - $scope.openMemberGroupPicker =function() { - var d = dialogService.memberGroupPicker(dialogOptions); - }; - - $scope.remove =function(index){ $scope.renderModel.splice(index, 1); }; @@ -79,4 +79,4 @@ function memberGroupPicker($scope, dialogService){ } -angular.module('umbraco').controller("Umbraco.PropertyEditors.MemberGroupPickerController", memberGroupPicker); \ No newline at end of file +angular.module('umbraco').controller("Umbraco.PropertyEditors.MemberGroupPickerController", memberGroupPicker); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/membergrouppicker/membergrouppicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/membergrouppicker/membergrouppicker.html index 316ad168f3..5258968c00 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/membergrouppicker/membergrouppicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/membergrouppicker/membergrouppicker.html @@ -1,25 +1,33 @@
    - -
    \ No newline at end of file + + + + + diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/memberpicker/memberpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/memberpicker/memberpicker.controller.js index 7d5ad22fb8..9f20e121d9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/memberpicker/memberpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/memberpicker/memberpicker.controller.js @@ -1,6 +1,6 @@ //this controller simply tells the dialogs service to open a memberPicker window //with a specified callback, this callback will receive an object with a selection on it -function memberPickerController($scope, dialogService, entityResource, $log, iconHelper){ +function memberPickerController($scope, dialogService, entityResource, $log, iconHelper, angularHelper){ function trim(str, chr) { var rgxtrim = (!chr) ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g'); @@ -27,19 +27,39 @@ function memberPickerController($scope, dialogService, entityResource, $log, ico $scope.clear(); $scope.add(data); } + angularHelper.getCurrentForm($scope).$setDirty(); } }; - //since most of the pre-value config's are used in the dialog options (i.e. maxNumber, minNumber, etc...) we'll merge the + //since most of the pre-value config's are used in the dialog options (i.e. maxNumber, minNumber, etc...) we'll merge the // pre-value config on to the dialog options if ($scope.model.config) { angular.extend(dialogOptions, $scope.model.config); } - - $scope.openMemberPicker =function() { - var d = dialogService.memberPicker(dialogOptions); - }; + $scope.openMemberPicker = function() { + $scope.memberPickerOverlay = dialogOptions; + $scope.memberPickerOverlay.view = "memberPicker"; + $scope.memberPickerOverlay.show = true; + + $scope.memberPickerOverlay.submit = function(model) { + + if (model.selection) { + _.each(model.selection, function(item, i) { + $scope.add(item); + }); + } + + $scope.memberPickerOverlay.show = false; + $scope.memberPickerOverlay = null; + }; + + $scope.memberPickerOverlay.close = function(oldModel) { + $scope.memberPickerOverlay.show = false; + $scope.memberPickerOverlay = null; + }; + + }; $scope.remove =function(index){ $scope.renderModel.splice(index, 1); @@ -52,14 +72,14 @@ function memberPickerController($scope, dialogService, entityResource, $log, ico if (currIds.indexOf(item.id) < 0) { item.icon = iconHelper.convertFromLegacyIcon(item.icon); - $scope.renderModel.push({name: item.name, id: item.id, icon: item.icon}); - } + $scope.renderModel.push({name: item.name, id: item.id, icon: item.icon}); + } }; $scope.clear = function() { $scope.renderModel = []; }; - + var unsubscribe = $scope.$on("formSubmitting", function (ev, args) { var currIds = _.map($scope.renderModel, function (i) { return i.id; @@ -83,4 +103,4 @@ function memberPickerController($scope, dialogService, entityResource, $log, ico } -angular.module('umbraco').controller("Umbraco.PropertyEditors.MemberPickerController", memberPickerController); \ No newline at end of file +angular.module('umbraco').controller("Umbraco.PropertyEditors.MemberPickerController", memberPickerController); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/memberpicker/memberpicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/memberpicker/memberpicker.html index a223828046..b537a80113 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/memberpicker/memberpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/memberpicker/memberpicker.html @@ -1,25 +1,33 @@
    - -
    \ No newline at end of file + + + + + diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js index 6abd80cbcd..26d9768c29 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js @@ -15,17 +15,55 @@ $scope.currentEditLink = null; $scope.hasError = false; - $scope.internal = function ($event) { - $scope.currentEditLink = null; - var d = dialogService.contentPicker({ multipicker: false, callback: select }); - $event.preventDefault(); - }; - - $scope.selectInternal = function ($event, link) { + $scope.internal = function($event) { + + $scope.currentEditLink = null; + + $scope.contentPickerOverlay = {}; + $scope.contentPickerOverlay.view = "contentpicker"; + $scope.contentPickerOverlay.multiPicker = false; + $scope.contentPickerOverlay.show = true; + + $scope.contentPickerOverlay.submit = function(model) { + + select(model.selection[0]); + + $scope.contentPickerOverlay.show = false; + $scope.contentPickerOverlay = null; + }; + + $scope.contentPickerOverlay.close = function(oldModel) { + $scope.contentPickerOverlay.show = false; + $scope.contentPickerOverlay = null; + }; + + $event.preventDefault(); + }; + + $scope.selectInternal = function($event, link) { + + $scope.currentEditLink = link; + + $scope.contentPickerOverlay = {}; + $scope.contentPickerOverlay.view = "contentpicker"; + $scope.contentPickerOverlay.multiPicker = false; + $scope.contentPickerOverlay.show = true; + + $scope.contentPickerOverlay.submit = function(model) { + + select(model.selection[0]); + + $scope.contentPickerOverlay.show = false; + $scope.contentPickerOverlay = null; + }; + + $scope.contentPickerOverlay.close = function(oldModel) { + $scope.contentPickerOverlay.show = false; + $scope.contentPickerOverlay = null; + }; + + $event.preventDefault(); - $scope.currentEditLink = link; - var d = dialogService.contentPicker({ multipicker: false, callback: select }); - $event.preventDefault(); }; $scope.edit = function (idx) { @@ -147,5 +185,5 @@ $scope.newInternal = data.id; $scope.newInternalName = data.name; } - } - }); \ No newline at end of file + } + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.html index 2f32f3875c..a5eae94491 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.html @@ -1,5 +1,5 @@  \ No newline at end of file + + + + + diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js index 3ce153b893..ed2017eb17 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js @@ -1,7 +1,7 @@ angular.module("umbraco") .controller("Umbraco.PropertyEditors.RTEController", - function ($rootScope, $scope, $q, dialogService, $log, imageHelper, assetsService, $timeout, tinyMceService, angularHelper, stylesheetResource) { - + function ($rootScope, $scope, $q, dialogService, $log, imageHelper, assetsService, $timeout, tinyMceService, angularHelper, stylesheetResource, macroService) { + $scope.isLoading = true; //To id the html textarea we need to use the datetime ticks because we can have multiple rte's per a single property alias @@ -64,8 +64,7 @@ angular.module("umbraco") //queue rules loading angular.forEach(editorConfig.stylesheets, function (val, key) { - stylesheets.push("../css/" + val + ".css?" + new Date().getTime()); - + stylesheets.push(Umbraco.Sys.ServerVariables.umbracoSettings.cssPath + "/" + val + ".css?" + new Date().getTime()); await.push(stylesheetResource.getRulesByName(val).then(function (rules) { angular.forEach(rules, function (rule) { var r = {}; @@ -222,23 +221,78 @@ angular.module("umbraco") editor.on('ObjectResized', function (e) { - var qs = "?width=" + e.width + "px&height=" + e.height + "px"; + var qs = "?width=" + e.width + "&height=" + e.height; var srcAttr = $(e.target).attr("src"); var path = srcAttr.split("?")[0]; $(e.target).attr("data-mce-src", path + qs); - + syncContent(editor); }); + tinyMceService.createLinkPicker(editor, $scope, function(currentTarget, anchorElement) { + $scope.linkPickerOverlay = { + view: "linkpicker", + currentTarget: currentTarget, + show: true, + submit: function(model) { + tinyMceService.insertLinkInEditor(editor, model.target, anchorElement); + $scope.linkPickerOverlay.show = false; + $scope.linkPickerOverlay = null; + } + }; + }); //Create the insert media plugin - tinyMceService.createMediaPicker(editor, $scope); + tinyMceService.createMediaPicker(editor, $scope, function(currentTarget, userData){ + + $scope.mediaPickerOverlay = { + currentTarget: currentTarget, + onlyImages: true, + showDetails: true, + startNodeId: userData.startMediaId, + view: "mediapicker", + show: true, + submit: function(model) { + tinyMceService.insertMediaInEditor(editor, model.selectedImages[0]); + $scope.mediaPickerOverlay.show = false; + $scope.mediaPickerOverlay = null; + } + }; + + }); //Create the embedded plugin - tinyMceService.createInsertEmbeddedMedia(editor, $scope); + tinyMceService.createInsertEmbeddedMedia(editor, $scope, function() { + + $scope.embedOverlay = { + view: "embed", + show: true, + submit: function(model) { + tinyMceService.insertEmbeddedMediaInEditor(editor, model.embed.preview); + $scope.embedOverlay.show = false; + $scope.embedOverlay = null; + } + }; + + }); + //Create the insert macro plugin - tinyMceService.createInsertMacro(editor, $scope); + tinyMceService.createInsertMacro(editor, $scope, function(dialogData) { + + $scope.macroPickerOverlay = { + view: "macropicker", + dialogData: dialogData, + show: true, + submit: function(model) { + var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, dialogData.renderingEngine); + tinyMceService.insertMacroInEditor(editor, macroObject, $scope); + $scope.macroPickerOverlay.show = false; + $scope.macroPickerOverlay = null; + } + }; + + }); }; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.html index cb808726ba..656f5b7490 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.html @@ -3,4 +3,33 @@ - \ No newline at end of file + + + + + + + + + + + + + + diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/slider/handle.prevalues.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/slider/handle.prevalues.html new file mode 100644 index 0000000000..e12cb2a869 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/slider/handle.prevalues.html @@ -0,0 +1,11 @@ +
    + + + + Required + +
    \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/slider/orientation.prevalues.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/slider/orientation.prevalues.html index ee2958d195..b79bbc6c59 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/slider/orientation.prevalues.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/slider/orientation.prevalues.html @@ -3,6 +3,8 @@ - - Required + + + Required + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/slider/slider.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/slider/slider.controller.js index 2f26572fde..76d1599508 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/slider/slider.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/slider/slider.controller.js @@ -4,6 +4,13 @@ if (!$scope.model.config.orientation) { $scope.model.config.orientation = "horizontal"; } + if (!$scope.model.config.enableRange) { + $scope.model.config.enableRange = false; + } + else { + $scope.model.config.enableRange = $scope.model.config.enableRange === "1" ? true : false; + } + if (!$scope.model.config.initVal1) { $scope.model.config.initVal1 = 0; } @@ -34,17 +41,88 @@ else { $scope.model.config.step = parseFloat($scope.model.config.step); } + + if (!$scope.model.config.handle) { + $scope.model.config.handle = "round"; + } + + if (!$scope.model.config.reversed) { + $scope.model.config.reversed = false; + } + else { + $scope.model.config.reversed = $scope.model.config.reversed === "1" ? true : false; + } + + if (!$scope.model.config.tooltip) { + $scope.model.config.tooltip = "show"; + } + + if (!$scope.model.config.tooltipSplit) { + $scope.model.config.tooltipSplit = false; + } + else { + $scope.model.config.tooltipSplit = $scope.model.config.tooltipSplit === "1" ? true : false; + } + + if ($scope.model.config.tooltipFormat) { + $scope.model.config.formatter = function (value) { + if (angular.isArray(value) && $scope.model.config.enableRange) { + return $scope.model.config.tooltipFormat.replace("{0}", value[0]).replace("{1}", value[1]); + } else { + return $scope.model.config.tooltipFormat.replace("{0}", value); + } + } + } + + if (!$scope.model.config.ticks) { + $scope.model.config.ticks = []; + } + else { + // returns comma-separated string to an array, e.g. [0, 100, 200, 300, 400] + $scope.model.config.ticks = _.map($scope.model.config.ticks.split(','), function (item) { + return parseInt(item.trim()); + }); + } + + if (!$scope.model.config.ticksPositions) { + $scope.model.config.ticksPositions = []; + } + else { + // returns comma-separated string to an array, e.g. [0, 30, 60, 70, 90, 100] + $scope.model.config.ticksPositions = _.map($scope.model.config.ticksPositions.split(','), function (item) { + return parseInt(item.trim()); + }); + console.log($scope.model.config.ticksPositions); + } + + if (!$scope.model.config.ticksLabels) { + $scope.model.config.ticksLabels = []; + } + else { + // returns comma-separated string to an array, e.g. ['$0', '$100', '$200', '$300', '$400'] + $scope.model.config.ticksLabels = _.map($scope.model.config.ticksLabels.split(','), function (item) { + return item.trim(); + }); + } + + if (!$scope.model.config.ticksSnapBounds) { + $scope.model.config.ticksSnapBounds = 0; + } + else { + $scope.model.config.ticksSnapBounds = parseFloat($scope.model.config.ticksSnapBounds); + } /** This creates the slider with the model values - it's called on startup and if the model value changes */ function createSlider() { - //the value that we'll give the slider - if it's a range, we store our value as a comma seperated val but this slider expects an array + //the value that we'll give the slider - if it's a range, we store our value as a comma separated val but this slider expects an array var sliderVal = null; //configure the model value based on if range is enabled or not - if ($scope.model.config.enableRange === "1") { + if ($scope.model.config.enableRange == true) { //If no value saved yet - then use default value - if (!$scope.model.value) { + //If it contains a single value - then also create a new array value + if (!$scope.model.value || $scope.model.value.indexOf(",") == -1) { var i1 = parseFloat($scope.model.config.initVal1); var i2 = parseFloat($scope.model.config.initVal2); sliderVal = [ @@ -75,18 +153,30 @@ } //initiate slider, add event handler and get the instance reference (stored in data) - var slider = $element.find('.slider-item').slider({ + var slider = $element.find('.slider-item').bootstrapSlider({ max: $scope.model.config.maxVal, min: $scope.model.config.minVal, orientation: $scope.model.config.orientation, - selection: "after", + selection: $scope.model.config.reversed ? "after" : "before", step: $scope.model.config.step, - tooltip: "show", + precision: $scope.model.config.precision, + tooltip: $scope.model.config.tooltip, + tooltip_split: $scope.model.config.tooltipSplit, + tooltip_position: $scope.model.config.tooltipPosition, + handle: $scope.model.config.handle, + reversed: $scope.model.config.reversed, + ticks: $scope.model.config.ticks, + ticks_positions: $scope.model.config.ticksPositions, + ticks_labels: $scope.model.config.ticksLabels, + ticks_snap_bounds: $scope.model.config.ticksSnapBounds, + formatter: $scope.model.config.formatter, + range: $scope.model.config.enableRange, //set the slider val - we cannot do this with data- attributes when using ranges value: sliderVal - }).on('slideStop', function () { + }).on('slideStop', function (e) { + var value = e.value; angularHelper.safeApply($scope, function () { - setModelValueFromSlider(slider.getValue()); + setModelValueFromSlider(value); }); }).data('slider'); } @@ -95,7 +185,7 @@ the model with the currently selected slider value(s) **/ function setModelValueFromSlider(sliderVal) { //Get the value from the slider and format it correctly, if it is a range we want a comma delimited value - if ($scope.model.config.enableRange === "1") { + if ($scope.model.config.enableRange == true) { $scope.model.value = sliderVal.join(","); } else { @@ -121,8 +211,8 @@ }); - //load the seperate css for the editor to avoid it blocking our js loading - assetsService.loadCss("lib/slider/slider.css"); - + //load the separate css for the editor to avoid it blocking our js loading + assetsService.loadCss("lib/slider/bootstrap-slider.css"); + assetsService.loadCss("lib/slider/bootstrap-slider-custom.css"); } angular.module("umbraco").controller("Umbraco.PropertyEditors.SliderController", sliderController); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/slider/tooltip.prevalues.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/slider/tooltip.prevalues.html new file mode 100644 index 0000000000..9baf195eb9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/slider/tooltip.prevalues.html @@ -0,0 +1,11 @@ +
    + + + + Required + +
    \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/tags/tags.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/tags/tags.controller.js index 9c0d016189..a1e48bbc99 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/tags/tags.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/tags/tags.controller.js @@ -7,7 +7,7 @@ angular.module("umbraco") $scope.isLoading = true; $scope.tagToAdd = ""; - assetsService.loadJs("lib/typeahead-js/typeahead.bundle.min.js").then(function () { + assetsService.loadJs("lib/typeahead.js/typeahead.bundle.min.js").then(function () { $scope.isLoading = false; @@ -20,7 +20,9 @@ angular.module("umbraco") $scope.model.value = []; } else { - $scope.model.value = $scope.model.value.split(","); + if($scope.model.value.length > 0) { + $scope.model.value = $scope.model.value.split(","); + } } } } @@ -188,4 +190,4 @@ angular.module("umbraco") }); } -); \ No newline at end of file +); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html index b2c57b4737..8b9babfdfa 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html @@ -2,9 +2,6 @@ - - Required diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/urllist/urllist.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/urllist/urllist.controller.js index 65b3aed589..2637d23f69 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/urllist/urllist.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/urllist/urllist.controller.js @@ -1,13 +1,29 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.UrlListController", function($rootScope, $scope, $filter) { - function formatDisplayValue() { - $scope.renderModel = _.map($scope.model.value.split(","), function (item) { - return { - url: item, - urlTarget: ($scope.config && $scope.config.target) ? $scope.config.target : "_blank" - }; - }); + function formatDisplayValue() { + if (angular.isArray($scope.model.value)) { + //it's the json value + $scope.renderModel = _.map($scope.model.value, function (item) { + return { + url: item.url, + linkText: item.linkText, + urlTarget: (item.target) ? item.target : "_blank", + icon: (item.icon) ? item.icon : "icon-out" + }; + }); + } + else { + //it's the default csv value + $scope.renderModel = _.map($scope.model.value.split(","), function (item) { + return { + url: item, + linkText: "", + urlTarget: ($scope.config && $scope.config.target) ? $scope.config.target : "_blank", + icon: ($scope.config && $scope.config.icon) ? $scope.config.icon : "icon-out" + }; + }); + } } $scope.getUrl = function(valueUrl) { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/urllist/urllist.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/urllist/urllist.html index f26f4dd9c1..f154a2a7b9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/urllist/urllist.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/urllist/urllist.html @@ -1,7 +1,11 @@  \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/test/config/app.unit.js b/src/Umbraco.Web.UI.Client/test/config/app.unit.js index fd29fd30b8..b1d192c495 100644 --- a/src/Umbraco.Web.UI.Client/test/config/app.unit.js +++ b/src/Umbraco.Web.UI.Client/test/config/app.unit.js @@ -8,6 +8,6 @@ var app = angular.module('umbraco', [ 'ngCookies' ]); -/* For Angular 1.2: we need to load in Routing seperately +/* For Angular 1.2: we need to load in Routing separately 'ngRoute' */ \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/test/unit/app/content/edit-content-controller.spec.js b/src/Umbraco.Web.UI.Client/test/unit/app/content/edit-content-controller.spec.js index 0fc5a4cedd..0d9b8a29e2 100644 --- a/src/Umbraco.Web.UI.Client/test/unit/app/content/edit-content-controller.spec.js +++ b/src/Umbraco.Web.UI.Client/test/unit/app/content/edit-content-controller.spec.js @@ -5,7 +5,7 @@ describe('edit content controller tests', function () { beforeEach(module('umbraco')); //inject the contentMocks service - beforeEach(inject(function ($rootScope, $controller, angularHelper, $httpBackend, contentMocks, entityMocks, mocksUtils) { + beforeEach(inject(function ($rootScope, $controller, angularHelper, $httpBackend, contentMocks, entityMocks, mocksUtils, localizationMocks) { //for these tests we don't want any authorization to occur mocksUtils.disableAuth(); @@ -17,6 +17,7 @@ describe('edit content controller tests', function () { //see /mocks/content.mocks.js for how its setup contentMocks.register(); entityMocks.register(); + localizationMocks.register(); //this controller requires an angular form controller applied to it scope.contentForm = angularHelper.getNullForm("contentForm"); diff --git a/src/Umbraco.Web.UI.Client/test/unit/app/media/edit-media-controller.spec.js b/src/Umbraco.Web.UI.Client/test/unit/app/media/edit-media-controller.spec.js index 0d2b1a7bd9..35179c5646 100644 --- a/src/Umbraco.Web.UI.Client/test/unit/app/media/edit-media-controller.spec.js +++ b/src/Umbraco.Web.UI.Client/test/unit/app/media/edit-media-controller.spec.js @@ -5,7 +5,7 @@ describe('edit media controller tests', function () { beforeEach(module('umbraco')); //inject the contentMocks service - beforeEach(inject(function ($rootScope, $controller, angularHelper, $httpBackend, mediaMocks, entityMocks, mocksUtils) { + beforeEach(inject(function ($rootScope, $controller, angularHelper, $httpBackend, mediaMocks, entityMocks, mocksUtils, localizationMocks) { //for these tests we don't want any authorization to occur mocksUtils.disableAuth(); @@ -16,6 +16,7 @@ describe('edit media controller tests', function () { //see /mocks/content.mocks.js for how its setup mediaMocks.register(); entityMocks.register(); + localizationMocks.register(); //this controller requires an angular form controller applied to it scope.contentForm = angularHelper.getNullForm("contentForm"); diff --git a/src/Umbraco.Web.UI.Client/test/unit/app/propertyeditors/content-picker-controller.spec.js b/src/Umbraco.Web.UI.Client/test/unit/app/propertyeditors/content-picker-controller.spec.js index 3444f39407..e6d1312109 100644 --- a/src/Umbraco.Web.UI.Client/test/unit/app/propertyeditors/content-picker-controller.spec.js +++ b/src/Umbraco.Web.UI.Client/test/unit/app/propertyeditors/content-picker-controller.spec.js @@ -5,7 +5,7 @@ describe('Content picker controller tests', function () { beforeEach(module('umbraco')); //inject the contentMocks service - beforeEach(inject(function ($rootScope, $controller, angularHelper, $httpBackend, entityMocks, mocksUtils) { + beforeEach(inject(function ($rootScope, $controller, angularHelper, $httpBackend, entityMocks, mocksUtils, localizationMocks) { //for these tests we don't want any authorization to occur mocksUtils.disableAuth(); @@ -28,6 +28,7 @@ describe('Content picker controller tests', function () { //have the contentMocks register its expect urls on the httpbackend //see /mocks/content.mocks.js for how its setup entityMocks.register(); + localizationMocks.register(); controller = $controller('Umbraco.PropertyEditors.ContentPickerController', { $scope: scope, diff --git a/src/Umbraco.Web.UI.Client/test/unit/common/services/content-editing-helper.spec.js b/src/Umbraco.Web.UI.Client/test/unit/common/services/content-editing-helper.spec.js index 0fa55c2a11..ad336cf544 100644 --- a/src/Umbraco.Web.UI.Client/test/unit/common/services/content-editing-helper.spec.js +++ b/src/Umbraco.Web.UI.Client/test/unit/common/services/content-editing-helper.spec.js @@ -8,7 +8,9 @@ describe('contentEditingHelper tests', function () { //Only for 1.2: beforeEach(module('ngRoute')); - beforeEach(inject(function ($injector) { + beforeEach(inject(function ($injector, localizationMocks) { + localizationMocks.register(); + contentEditingHelper = $injector.get('contentEditingHelper'); $routeParams = $injector.get('$routeParams'); serverValidationManager = $injector.get('serverValidationManager'); @@ -108,7 +110,7 @@ describe('contentEditingHelper tests', function () { var allProps = contentEditingHelper.getAllProps(content); //act - formHelper.handleServerValidation({ "Property.bodyText": ["Required"] }); + formHelper.handleServerValidation({ "_Properties.bodyText": ["Required"] }); //assert expect(serverValidationManager.items.length).toBe(1); @@ -124,7 +126,7 @@ describe('contentEditingHelper tests', function () { var allProps = contentEditingHelper.getAllProps(content); //act - formHelper.handleServerValidation({ "Property.bodyText.value": ["Required"] }); + formHelper.handleServerValidation({ "_Properties.bodyText.value": ["Required"] }); //assert expect(serverValidationManager.items.length).toBe(1); @@ -144,8 +146,8 @@ describe('contentEditingHelper tests', function () { { "Name": ["Required"], "UpdateDate": ["Invalid date"], - "Property.bodyText.value": ["Required field"], - "Property.textarea": ["Invalid format"] + "_Properties.bodyText.value": ["Required field"], + "_Properties.textarea": ["Invalid format"] }); //assert diff --git a/src/Umbraco.Web.UI.Client/test/unit/common/services/macro-service.spec.js b/src/Umbraco.Web.UI.Client/test/unit/common/services/macro-service.spec.js index bdb9cfbd17..ff71516fb5 100644 --- a/src/Umbraco.Web.UI.Client/test/unit/common/services/macro-service.spec.js +++ b/src/Umbraco.Web.UI.Client/test/unit/common/services/macro-service.spec.js @@ -40,6 +40,18 @@ describe('macro service tests', function () { expect(result.macroParamsDictionary.test2).toBe("hello"); }); + it('can parse syntax for macros when macroAlias is not the first parameter', function () { + + var result = macroService.parseMacroSyntax(""); + + expect(result).not.toBeNull(); + expect(result.macroAlias).toBe("Map.Test"); + expect(result.macroParamsDictionary.test).not.toBeUndefined(); + expect(result.macroParamsDictionary.test).toBe("asdf"); + expect(result.macroParamsDictionary.test2).not.toBeUndefined(); + expect(result.macroParamsDictionary.test2).toBe("hello"); + }); + it('can parse syntax for macros with aliases containing whitespace and other chars', function () { var result = macroService.parseMacroSyntax(""); diff --git a/src/Umbraco.Web.UI/Properties/PublishProfiles/File export.pubxml b/src/Umbraco.Web.UI/Properties/PublishProfiles/File export.pubxml new file mode 100644 index 0000000000..c594b3a591 --- /dev/null +++ b/src/Umbraco.Web.UI/Properties/PublishProfiles/File export.pubxml @@ -0,0 +1,17 @@ + + + + + FileSystem + Debug + Any CPU + + True + True + C:\Users\Shannon\SkyDrive\Documents\Umbraco\Keynote\RestDemo + False + + \ No newline at end of file diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 8eab74211e..60711cb7d8 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -40,13 +40,15 @@ v4.5 true - + + ..\ true true + bin\ @@ -107,36 +109,36 @@ UmbracoExamine - False ..\packages\AutoMapper.3.0.0\lib\net40\AutoMapper.dll + True - False ..\packages\AutoMapper.3.0.0\lib\net40\AutoMapper.Net4.dll + True - - False - ..\packages\ClientDependency.1.8.3.1\lib\net45\ClientDependency.Core.dll + + ..\packages\ClientDependency.1.8.4\lib\net45\ClientDependency.Core.dll + True - False - ..\packages\ClientDependency-Mvc.1.8.0.0\lib\net45\ClientDependency.Core.Mvc.dll + ..\packages\ClientDependency-Mvc5.1.8.0.0\lib\net45\ClientDependency.Core.Mvc.dll + True - - ..\packages\Examine.0.1.63.0\lib\Examine.dll + + ..\packages\Examine.0.1.68.0\lib\Examine.dll True False ..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll - - False - ..\packages\ImageProcessor.1.9.5.0\lib\ImageProcessor.dll + + ..\packages\ImageProcessor.2.3.0.0\lib\net45\ImageProcessor.dll + True - - False - ..\packages\ImageProcessor.Web.3.3.1.0\lib\net45\ImageProcessor.Web.dll + + ..\packages\ImageProcessor.Web.4.4.0.0\lib\net45\ImageProcessor.Web.dll + True False @@ -147,12 +149,12 @@ ..\packages\Lucene.Net.2.9.4.1\lib\net40\Lucene.Net.dll - ..\packages\Microsoft.AspNet.Identity.Core.2.2.0\lib\net45\Microsoft.AspNet.Identity.Core.dll + ..\packages\Microsoft.AspNet.Identity.Core.2.2.1\lib\net45\Microsoft.AspNet.Identity.Core.dll True False - ..\packages\Microsoft.AspNet.Identity.Owin.2.2.0\lib\net45\Microsoft.AspNet.Identity.Owin.dll + ..\packages\Microsoft.AspNet.Identity.Owin.2.2.1\lib\net45\Microsoft.AspNet.Identity.Owin.dll @@ -179,21 +181,17 @@ True ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - - False - ..\packages\Microsoft.AspNet.Mvc.FixedDisplayModes.1.0.1\lib\net40\Microsoft.Web.Mvc.FixedDisplayModes.dll - False ..\packages\MiniProfiler.2.1.0\lib\net40\MiniProfiler.dll False - ..\packages\MySql.Data.6.9.6\lib\net45\MySql.Data.dll + ..\packages\MySql.Data.6.9.8\lib\net45\MySql.Data.dll - False - ..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll + ..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll + True ..\packages\Owin.1.0\lib\net40\Owin.dll @@ -247,40 +245,40 @@ - + + ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.Helpers.dll True - ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.Helpers.dll False ..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll - + + False ..\packages\Microsoft.AspNet.WebApi.WebHost.5.2.3\lib\net45\System.Web.Http.WebHost.dll + + + ..\packages\Microsoft.AspNet.Mvc.5.2.3\lib\net45\System.Web.Mvc.dll True - + + ..\packages\Microsoft.AspNet.Razor.3.2.3\lib\net45\System.Web.Razor.dll True - ..\packages\Microsoft.AspNet.Mvc.4.0.40804.0\lib\net40\System.Web.Mvc.dll - - - True - ..\packages\Microsoft.AspNet.Razor.2.0.30506.0\lib\net40\System.Web.Razor.dll System.Web.Services - + + ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.dll True - ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.WebPages.dll - + + ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Deployment.dll True - ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.WebPages.Deployment.dll - + + ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Razor.dll True - ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.WebPages.Razor.dll System.XML @@ -349,12 +347,6 @@ True Settings.settings - - ContentTypeControlNew.ascx - - - ContentTypeControlNew.ascx - passwordChanger.ascx ASPXCodeBehind @@ -487,12 +479,6 @@ umbracoPage.Master - - EditNodeTypeNew.aspx - - - EditNodeTypeNew.aspx - ASPXCodeBehind @@ -528,13 +514,30 @@ treeInit.aspx + + + + + + + + + + + + + + + + + + - umbraco.aspx ASPXCodeBehind @@ -672,158 +675,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -894,7 +745,6 @@ - @@ -902,14 +752,6 @@ - - - - - - - - @@ -1047,6 +889,7 @@ + @@ -1118,15 +961,9 @@ - - - - - - - - - + + Designer + Designer @@ -1181,7 +1018,6 @@ UserControl - @@ -1388,7 +1224,6 @@ - @@ -1398,15 +1233,9 @@ Form - - - Form - - - @@ -1417,7 +1246,6 @@ - Form @@ -1506,7 +1334,6 @@ - Designer @@ -1525,17 +1352,10 @@ - - - - - - - @@ -1559,7 +1379,7 @@ xcopy "$(ProjectDir)"..\packages\SqlServerCE.4.0.0.0\x86\*.* "$(TargetDir)x86\" True True - 7300 + 7400 / http://localhost:8000 False diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj.DotSettings b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj.DotSettings new file mode 100644 index 0000000000..662f95686e --- /dev/null +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj.DotSettings @@ -0,0 +1,2 @@ + + CSharp50 \ No newline at end of file diff --git a/src/Umbraco.Web.UI/Umbraco/Images/editor/renderbody.gif b/src/Umbraco.Web.UI/Umbraco/Images/editor/renderbody.gif new file mode 100644 index 0000000000..1a84493fe9 Binary files /dev/null and b/src/Umbraco.Web.UI/Umbraco/Images/editor/renderbody.gif differ diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/RegisterMember.cshtml b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/RegisterMember.cshtml index 988641d324..787cfbfcc7 100644 --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/RegisterMember.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/RegisterMember.cshtml @@ -75,13 +75,16 @@ else @if (registerModel.MemberProperties != null) { + @* + It will only displays properties marked as "Member can edit" on the "Info" tab of the Member Type. + *@ for (var i = 0; i < registerModel.MemberProperties.Count; i++) { @Html.LabelFor(m => registerModel.MemberProperties[i].Value, registerModel.MemberProperties[i].Name) - @* - By default this will render a textbox but if you want to change the editor template for this property you can + @* + By default this will render a textbox but if you want to change the editor template for this property you can easily change it. For example, if you wanted to render a custom editor for this field called "MyEditor" you would - create a file at ~/Views/Shared/EditorTemplates/MyEditor.cshtml", then you will change the next line of code to + create a file at ~/Views/Shared/EditorTemplates/MyEditor.cshtml", then you will change the next line of code to render your specific editor template like: @Html.EditorFor(m => profileModel.MemberProperties[i].Value, "MyEditor") *@ diff --git a/src/Umbraco.Web.UI/Umbraco/Views/AuthorizeUpgrade.cshtml b/src/Umbraco.Web.UI/Umbraco/Views/AuthorizeUpgrade.cshtml index dcad153fce..3d4f6baa7e 100644 --- a/src/Umbraco.Web.UI/Umbraco/Views/AuthorizeUpgrade.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/Views/AuthorizeUpgrade.cshtml @@ -59,9 +59,15 @@ redirectUrl = Url.Action("AuthorizeUpgrade", "BackOffice") }); } - @Html.BareMinimumServerVariables(Url, externalLoginUrl) + @Html.BareMinimumServerVariablesScript(Url, externalLoginUrl) - @Html.AngularExternalLoginInfoValues((IEnumerable)ViewBag.ExternalSignInError) + @*And finally we can load in our angular app*@ diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml index d4116e0be9..8eaf434995 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml @@ -375,6 +375,7 @@ Zobrazit stránku při odeslání Rozměr Seřadit + Submit Typ Pro hledání pište... Nahoru @@ -391,6 +392,8 @@ Ano Složka Výsledky hledání + Reorder + I am done reordering Background color @@ -818,6 +821,40 @@ Rychlá příručka k šablonovým značkám umbraca Šablona + + Choose type of content + Choose a layout + Add a row + Add content + Drop content + Settings applied + + This content is not allowed here + This content is allowed here + + Click to embed + Click to insert image + Image caption... + Write here... + + Grid Layouts + Layouts are the overall work area for the grid editor, usually you only need one or two different layouts + Add Grid Layout + Adjust the layout by setting column widths and adding additional sections + Row configurations + Rows are predefined cells arranged horizontally + Add row configuration + Adjust the row by setting cell widths and adding additional cells + Columns + Total combined number of columns in the grid layout + Settings + Configure what settings editors can change + Styles + Configure what styling editors can change + Settings will only save if the entered json configuration is valid + Allow all editors + Allow all row configurations + Alternativní pole Alternativní text @@ -899,7 +936,7 @@ Prohlížeč mezipaměti Koš Vytvořené balíčky - Datové typy + Datové typy Slovník Instalované balíčky Instalovat téma @@ -909,10 +946,10 @@ Makra Typy medií Členové - Skupiny členů + Skupiny členů Role - Typy členů - Typy dokumentů + Typy členů + Typy dokumentů Balíčky Balíčky Soubory python diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml new file mode 100644 index 0000000000..8731d206db --- /dev/null +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml @@ -0,0 +1,819 @@ + + + + The Umbraco community + http://our.umbraco.org/documentation/Extending-Umbraco/Language-Files + + + Angi domene + Revisjoner + Bla gjennom + Kopier + CREATE + Opprett pakke + Slett + Deaktiver + Tøm papirkurv + Eksporter dokumenttype + Importer documenttype + Importer pakke + Rediger i Canvas + Lukk Umbraco + Flytt + Varsling + Offentlig tilgang + Publiser + Oppdater noder + Republiser hele siten + Rettigheter + Reverser + Send til publisering + Send til oversetting + Sorter + Send til publisering + Oversett + Oppdater + Avpubliser + + + Legg til domene + Domene + Domene '%0%' er nå opprettet og tilknyttet siden + Domenet '%0%' er nå slettet + Domenet '%0%' er allerede tilknyttet + Gyldige domenenavn er: "eksempel.no", "www.eksempel.no", "eksempel.no:8080" eller "https://www.eksempel.no/".<br/><br/>Stier med ett nivå støttes, f.eks. "eksempel.com/no". Imidlertid bør det unngås. Bruk heller språkinnstillingen over. + Domenet '%0%' er nå oppdatert + eller rediger eksisterende domener + Ingen tilgang. + Fjern + Ugyldig node. + Ugyldig domeneformat. + Domene er allerede tilknyttet. + Språk + Arv + Språk + Sett språk for underordnede noder eller arv språk fra overordnet.<br/>Vil også gjelde denne noden, med mindre et underordnet domene også gjelder. + Domener + + + Viser for + + + Fet + Reduser innrykk + Sett inn skjemafelt + Sett inn grafisk overskrift + Rediger HTML + Øk innrykk + Kursiv + Midtstill + Juster tekst venstre + Juster tekst høyre + Sett inn lenke + Sett inn lokal lenke (anker) + Punktmerking + Nummerering + Sett inn makro + Sett inn bilde + Rediger relasjoner + Lagre + Lagre og publiser + Lagre og send til publisering + Forhåndsvis + Velg formattering + Vis stiler + Sett inn tabell + Forhåndsvisning er deaktivert siden det ikke er angitt noen mal + + + Om siden + Alternativ lenke + (hvordan du ville beskrevet bildet over telefon) + Alternative lenker + Klikk for å redigere denne noden + Opprettet av + Opprettet den + Dokumenttype + Redigerer + Utløpsdato + Denne noden er endret siden siste publisering + Denne noden er enda ikke publisert + Sist publisert + Mediatype + Medlemsgruppe + Rolle + Medlemstype + Ingen dato valgt + Sidetittel + Egenskaper + Dette dokumentet er publisert, men ikke synlig ettersom den overliggende siden '%0%' ikke er publisert + Publisert + Publiseringsstatus + Publiseringsdato + Fjern dato + Sorteringsrekkefølgen er oppdatert + Trekk og slipp nodene eller klikk på kolonneoverskriftene for å sortere. Du kan velge flere noder ved å holde shift eller control tastene mens du velger. + Statistikk + Tittel (valgfri) + Type + Avpubliser + Sist endret + Fjern fil + Lenke til dokument + Link til media + Intern feil: dokumentet er publisert men finnes ikke i hurtigbuffer + + + Hvor ønsker du å oprette den nye %0% + Opprett under + Velg en type og skriv en tittel + + + Til ditt nettsted + - Skjul + Hvis Umbraco ikke starter, kan det skyldes at pop-up vinduer ikke er tillatt + er åpnet i nytt vindu + Omstart + Besøk + Velkommen + + + Navn på lokal link + Rediger domener + Lukk dette vinduet + Er du sikker på at du vil slette + Er du sikker på at du vil deaktivere + Vennligst kryss av i denne boksen for å bekrefte sletting av %0% element(er) + Er du sikker på at du vil forlate Umbraco? + Er du sikker? + Klipp ut + Rediger ordboksnøkkel + Rediger språk + Sett inn lokal link + Sett inn spesialtegn + Sett inn grafisk overskrift + Sett inn bilde + Sett inn lenke + Sett inn makro + Sett inn tabell + Sist redigert + Lenke + Intern link: + Ved lokal link, sett inn "#" foran link + Åpne i nytt vindu? + Makroinnstillinger + Denne makroen har ingen egenskaper du kan endre + Lim inn + Endre rettigheter for + Innholdet i papirkurven blir nå slettet. Vennligst ikke lukk dette vinduet mens denne operasjonen foregår + Papirkurven er nå tom + Når elementer blir slettet fra papirkurven vil de være slettet for alltid + <a target='_blank' href='http://regexlib.com'>regexlib.com</a> tjenesten opplever for tiden problemer som vi ikke har kontroll over. Vi beklager denne ubeleiligheten. + Søk etter et regulært uttrykk for å legge inn validering til et felt. Eksempel: 'email, 'zip-code' 'url' + Fjern makro + Obligatorisk + Nettstedet er indeksert + Hurtigbufferen er blitt oppdatert. Alt publisert innhold er nå à jour. Alt upublisert innhold er fortsatt ikke publisert. + Hurtigbufferen for siden vil bli oppdatert. Alt publisert innhold vil bli oppdatert, mens upublisert innhold vil forbli upublisert. + Antall kolonner + Antall rader + <strong>Sett en plassholder-ID</strong><br/>Ved å sette en ID på plassholderen kan du legge inn innhold i denne malen fra underliggende maler, ved å referere denne ID'en ved hjelp av et <code>&lt;asp:content /&gt;</code> element. + <strong>Velg en plassholder ID</strong> fra listen under. Du kan bare velge ID'er fra den gjeldende malens overordnede mal. + Klikk på bildet for å se det i full størrelse + Velg punkt + Se buffret node + + + Rediger de forskjellige språkversjonene for ordbokelementet '<em>%0%</em>' under.<br/>Du kan legge til flere språk under 'språk' i menyen til venstre. + Språk + + + Tillatte underordnede noder + Opprett + Slett arkfane + Beskrivelse + Ny arkfane + Arkfane + Miniatyrbilde + + + Legg til forhåndsverdi + Database datatype + Kontrollelement GUID + Kontrollelement + Knapper + Aktiver avanserte instillinger for + Aktiver kontektsmeny + Maksimum standard størrelse på innsatte bilder + Beslektede stilark + Vis etikett + Bredde og høyde + + + Dine data har blitt lagret, men før du kan publisere denne siden må du rette noen feil: + Den gjeldende Membership Provider støtter ikke endring av passord. (EnablePasswordRetrieval må være satt til sann) + %0% finnes allerede + Det var feil i dokumentet: + Det var feil i skjemaet: + Passordet bør være minst %0% tegn og inneholde minst %1% numeriske tegn + %0% må være et heltall + %0% under %1% er obligatorisk + %0% er obligatorisk + %0% under %1% er ikke i et korrekt format + %0% er ikke i et korrekt format + + + NB! Selv om CodeMirror er aktivert i konfigurasjon er det deaktivert i Internet Explorer pga. ustabilitet. + Fyll ut både alias og navn på den nye egenskapstypen! + Det er et problem med lese/skrive rettighetene til en fil eller mappe + Tittel mangler + Type mangler + Du er i ferd med å gjøre bildet større enn originalen. Det vil forringe kvaliteten på bildet, ønsker du å fortsette? + Feil i python-skriptet + Python-skriptet ble ikke lagret fordi det inneholder en eller flere feil + Startnode er slettet. Kontakt din administrator + Du må markere innhold før du kan endre stil + Det er ingen aktive stiler eller formateringer på denne siden + Sett markøren til venstre i de 2 cellene du ønsker å slå sammen + Du kan ikke dele en celle som allerede er delt. + Feil i XSLT kode + XSLT ble ikke lagret på grunn av feil i koden + Filtypen er deaktivert av administrator + + + Om + Handling + Legg til + Alias + Er du sikker? + Ramme + eller + Avbryt + Cellemargin + Velg + Lukk + Lukk vindu + Kommentar + Bekreft + Behold proposjoner + Fortsett + Kopier + Opprett + Database + Dato + Standard + Slett + Slettet + Sletter... + Design + Dimensjoner + Ned + Last ned + Rediger + Endret + Elementer + E-post + Feil + Finn + Høyde + Hjelp + Ikon + Importer + Indre margin + Sett inn + Installer + Justering + Språk + Layout + Laster + Låst + Logg inn + Logg ut + Logg ut + Makro + Flytt + Navn + Ny + Neste + Nei + av + OK + Åpne + eller + Passord + Sti + Plassholder ID + Ett øyeblikk... + Forrige + Egenskaper + E-post som innholdet i skjemaet skal sendes til + Papirkurv + Gjenværende + Gi nytt navn + Forny + Prøv igjen + Rettigheter + Søk + Server + Vis + Hvilken side skal vises etter at skjemaet er sendt + Størrelse + Sorter + Type + Søk... + Opp + Oppdater + Oppgrader + Last opp + Url + Bruker + Brukernavn + Verdi + Visning + Velkommen... + Bredde + Ja + Mappe + + + Bakgrunnsfarge + Fet + Tekstfarge + Skrifttype + Tekst + + + Side + + + Installasjonsprogrammet kan ikke koble til databasen + Kunne ikke lagre Web.Config-filen. Vennligst endre databasens tilkoblingsstreng manuelt. + Din database er funnet og identifisert som + Databasekonfigurasjon + Klikk <strong>installer</strong>-knappen for å installere Umbraco %0% databasen + Umbraco %0% har nå blitt kopiert til din database. Trykk <strong>Neste</strong> for å fortsette. + <p>Databasen ble ikke funnet! Vennligst sjekk at informasjonen i "connection string" i "web.config"-filen er korrekt.</p><p>For å fortsette, vennligst rediger "web.config"-filen (bruk Visual Studio eller din favoritteditor), rull ned til bunnen, og legg til tilkoblingsstrengen for din database i nøkkelen "umbracoDbDSN" og lagre filen.</p><p>Klikk <strong>prøv på nytt</strong> når du er ferdig.<br /> <a href="http://our.umbraco.org/documentation/Using-Umbraco/Config-files/webconfig7" target="_blank">Mer informasjon om redigering av web.config her.</a></p> + For å fullføre dette steget, må du vite en del informasjon om din database server ("tilkoblingsstreng").<br/> Vennligst kontakt din ISP om nødvendig. Hvis du installerer på en lokal maskin eller server, må du kanskje skaffe informasjonen fra din systemadministrator. + <p> Trykk på knappen <strong>oppgrader</strong> for å oppgradere databasen din til Umbraco %0%</p> <p> Ikke vær urolig - intet innhold vil bli slettet og alt vil fortsette å virke etterpå! </p> + Databasen din har blitt oppgradert til den siste utgaven, %0%.<br/>Trykk <strong>Neste</strong> for å fortsette. + Databasen din er av nyeste versjon! Klikk <strong>neste</strong> for å fortsette konfigurasjonsveiviseren + <strong>Passordet til standardbrukeren må endres! + <strong>Standardbrukeren har blitt deaktivert eller har ingen tilgang til Umbraco!</strong></p><p>Ingen videre handling er nødvendig. Klikk <b>neste</b> for å fortsette. + <strong>Passordet til standardbrukeren har blitt forandret etter installasjonen!</strong></p><p>Ingen videre handling er nødvendig. Klikk <strong>Neste</strong> for å fortsette. + Passordet er blitt endret! + <p> Umbraco skaper en standard bruker med login <strong> ( "admin") </ strong> og passord <strong> ( "default") </ strong>. Det er <strong> viktig </ strong> at passordet er endret til noe unikt. </ p> <p> Dette trinnet vil sjekke standard brukerens passord og foreslår hvis det må skiftes </ p> + Få en god start med våre introduksjonsvideoer + Ved å klikke på Neste-knappen (eller endre UmbracoConfigurationStatus i Web.config), godtar du lisensen for denne programvaren som angitt i boksen nedenfor. Legg merke til at denne Umbraco distribusjon består av to ulike lisenser, åpen kilde MIT lisens for rammen og Umbraco frivareverktøy lisens som dekker brukergrensesnittet. + Ikke installert. + Berørte filer og mapper + Mer informasjon om å sette opp rettigheter for Umbraco her + Du må gi ASP.NET brukeren rettigheter til å endre de følgende filer og mapper + <strong>Rettighetene er nesten perfekt satt opp!</strong><br/><br/> Du kan kjøre Umbraco uten problemer, men du vil ikke være i stand til å installere de anbefalte pakkene for å utnytte Umbraco fullt ut. + Hvordan løse problemet + Klikk her for å lese tekstversjonen + Se vår <strong>innføringsvideo</strong> om å sette opp rettigheter for Umbraco eller les tekstversjonen. + <strong>Rettighetsinnstillingene kan være et problem!</strong><br/><br/> Du kan kjøre Umbraco uten problemer, men du vil ikke være i stand til å installere de anbefalte pakkene for å utnytte Umbraco fullt ut. + <strong>Rettighetsinstillingene er ikke klargjort for Umbraco!</strong><br/><br/> For å kunne kjøre Umbraco, må du oppdatere rettighetsinnstillingene dine. + <strong>Rettighetsinnstillingene er perfekt!</strong><br/><br/>Du er klar for å kjøre Umbraco og installere pakker! + Løser mappeproblem + Følg denne linken for mer informasjon om problemer med ASP.NET og oppretting av mapper + Konfigurerer mappetillatelser + Umbraco trenger skrive/endre tilgang til enkelte mapper for å kunne lagre filer som bilder og PDF-dokumenter. Den lagrer også midlertidig data (aka: hurtiglager) for å øke ytelsen på websiden din. + Jeg ønsker å starte fra bunnen. + Din website er helt tom for øyeblikket. Dette er perfekt hvis du vil begynne helt forfra og lage dine egne dokumenttyper og maler. (<a href="http://Umbraco.tv/documentation/videos/for-site-builders/foundation/document-types">lær hvordan</a>) Du kan fortsatt velge å installere Runway senere. Vennligst gå til Utvikler-seksjonen og velg Pakker. + Du har akkurat satt opp en ren Umbraco plattform. Hva vil du gjøre nå? + Runway er installert + Du har nå fundamentet på plass. Velg hvilke moduler du ønsker å installer på toppen av det.<br/> Dette er vår liste av anbefalte moduler- Kryss av de du ønsker å installere, eller se den<a href="#" onclick="toggleModules(); return false;" id="toggleModuleList">fulle listen av moduler</a> + Bare anbefalt for erfarne brukere + Jeg vil starte med en enkel webside + <p> "Runway" er en enkel webside som utstyrer deg med noen grunnleggende dokumenttyper og maler. Veiviseren kan sette opp Runway for deg automatisk, men du kan enkelt endre, utvide eller slette den. Runway er ikke nødvendig, og du kan enkelt bruke Umbraco uten den. Imidlertidig tilbyr Runway et enkelt fundament basert på de beste metodene for å hjelpe deg i gang fortere enn noensinne. Hvis du velger å installere Runway, kan du også velge blant grunnleggende byggeklosser kalt Runway Moduler for å forøke dine Runway-sider. </p> <small> <em>Sider inkludert i Runway:</em> Hjemmeside, Komme-i-gang, Installere moduler.<br /> <em>Valgfrie Moduler:</em> Toppnavigasjon, Sidekart, Kontakt, Galleri. </small> + Hva er Runway + Steg 1/5 Godta lisens + Steg 2/5 Database konfigurasjon + Steg 3/5: Valider filrettigheter + Steg 4/5: Skjekk Umbraco sikkerheten + Steg 5/5: Umbraco er klar for deg til å starte! + Tusen takk for at du valgte Umbraco! + <h3>Se ditt nye nettsted</h3> Du har installert Runway, hvorfor ikke se hvordan ditt nettsted ser ut. + <h3>Mer hjelp og info</h3> Få hjelp fra vårt prisbelønte samfunn, bla gjennom dokumentasjonen eller se noen gratis videoer på hvordan man bygger et enkelt nettsted, hvordan bruke pakker og en rask guide til Umbraco terminologi + Umbraco %0% er installert og klar til bruk + For å fullføre installasjonen, må du manuelt endre <strong>web.config</strong> filen, og oppdatere AppSetting-nøkkelen <strong>UmbracoConfigurationStatus</strong> til verdien <strong>'%0%'</strong> + Du kan <strong>starte øyeblikkelig</strong> ved å klikke på "Start Umbraco" knappen nedenfor. <br/>Hvis du er <strong>ny på Umbraco</strong>, kan du finne mange ressurser på våre komme-i-gang sider. + <h3>Start Umbraco</h3> For å administrere din webside, åpne Umbraco og begynn å legge til innhold, oppdatere maler og stilark eller utvide funksjonaliteten + Tilkobling til databasen mislyktes. + Umbraco Versjon 3 + Umbraco Versjon 4 + Pass på + Denne veiviseren vil hjelpe deg gjennom prosessen med å konfigurere <strong>Umbraco %0%</strong> for en ny installasjon eller oppgradering fra versjon 3.0. <br/><br/> Trykk <strong>"neste"</strong> for å starte veiviseren. + + + Språkkode + Språk + + + Du har vært inaktiv og vil logges ut automatisk om + Forny innlogging for å lagre + + + <p style="text-align:right;">&copy; 2001 - %0% <br /><a href="http://umbraco.com" style="text-decoration: none" target="_blank">umbraco.org</a></p> + Velkommen til Umbraco, skriv inn ditt brukernavn og passord i feltene under: + + + Skrivebord + Seksjoner + Innhold + + + Velg side over... + %0% er nå kopiert til %1% + Kopier til + %0% er nå flyttet til %1% + Flytt til + har blitt valgt som rot til ditt nye innhold, klikk 'ok' nedenfor. + Ingen node er valgt, vennligst velg en node i listen over før du klikker 'fortsett' + Gjeldende nodes type tillates ikke under valgt node + Gjeldende node kan ikke legges under en underordnet node + Handlingen tillates ikke. Du mangler tilgang til en eller flere underordnede noder. + Relater kopierte elementer til original(e) + + + Rediger dine varsler for %0% + +Hei %0% + +Dette er en automatisk mail for å informere om at handlingen '%1%' +er utført på siden '%2%' +av brukeren '%3%' + +Gå til http://%4%/Umbraco/default.aspx?section=content&id=%5% for å redigere. + +Ha en fin dag! + +Vennlig hilsen Umbraco roboten + + <p>Hei %0%</p> + + <p>Dette er en automatisk mail for å informere om at handlingen '%1%' + er blitt utført på siden <a href="http://%4%/actions/preview.aspx?id=%5%"><strong>'%2%'</strong></a> + av brukeren <strong>'%3%'</strong> + </p> + <div style="margin: 8px 0; padding: 8px; display: block;"> + <br /> + <a style="color: white; font-weight: bold; background-color: #5372c3; text-decoration : none; margin-right: 20px; border: 8px solid #5372c3; width: 150px;" href="http://%4%/Umbraco/actions/editContent.aspx?id=%5%">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;REDIGER&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</a> &nbsp; + <br /> + </div> + <p> + <h3>Rettelser:</h3> + <table style="width: 100%;"> + %6% + </table> + </p> + + <div style="margin: 8px 0; padding: 8px; display: block;"> + <br /> + <a style="color: white; font-weight: bold; background-color: #5372c3; text-decoration : none; margin-right: 20px; border: 8px solid #5372c3; width: 150px;" href="http://%4%/Umbraco/actions/editContent.aspx?id=%5%">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;REDIGER&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</a> &nbsp; + <br /> + </div> + + <p>Ha en fin dag!<br /><br /> + Vennlig hilsen Umbraco roboten + </p> + [%0%] Varsling om %1% utført på %2% + Varsling + + + Klikke browse og velg pakke fra lokal disk. Umbraco-pakker har vanligvis endelsen ".umb" eller ".zip". + Utvikler + Demonstrasjon + Dokumentasjon + Metadata + Pakkenavn + Pakken inneholder ingen elementer + Denne pakkefilen inneholder ingen elementer å avinstallere.<br/><br/>Du kan trygt fjerne pakken fra systemet ved å klikke "avinstaller pakke" nedenfor. + Ingen oppdateringer tilgjengelig + Alternativer for pakke + Lesmeg for pakke + Pakkebrønn + Bekreft avinstallering + Pakken ble avinstallert + Pakken ble vellykket avinstallert + Avinstaller pakke + Du kan velge bort elementer du ikke vil slette på dette tidspunkt, nedenfor. Når du klikker "bekreft avinstallering" vil alle elementer som er krysset av bli slettet.<br/> <span style="color:red;font-weight:bold;">Advarsel:</span> alle dokumenter, media, etc. som som er avhengig av elementene du sletter, vil slutte å virke, noe som kan føre til ustabilitet, så avinstaller med forsiktighet. Hvis du er i tvil, kontakt pakkeutvikleren. + Last ned oppdatering fra pakkeregisteret + Oppgrader pakke + Oppgraderingsinstrukser + Det er en oppdatering tilgjengelig for denne pakken. Du kan laste den ned direkte fra pakkebrønnen. + Pakkeversjon + Se pakkens nettsted + + + Lim inn med full formattering (Anbefales ikke) + Teksten du er i ferd med å lime inn, inneholder spesialtegn eller formattering. Dette kan skyldes at du kopierer fra f.eks. Microsoft Word. Umbraco kan fjerne denne spesialformatteringen automatisk slik at innholdet er mer velegnet for visning på en webside. + Lim inn som ren tekst, dvs. fjern al formattering + Lim inn og fjern uegnet formatering (anbefalt) + + + Avansert: Beskytt ved å velge hvilke brukergrupper som har tilgang til siden + Om du ønsker å kontrollere tilgang til siden ved å bruke rolle-basert autentisering,<br /> ved å bruke Umbraco's medlems-grupper + Du må opprette en medlemsgruppe før du kan bruke <br /> rollebasert autentikasjon. + Feilside + Brukt når personer logger på, men ikke har tilgang + Hvordan vil du beskytte siden din? + %0% er nå beskyttet + Beskyttelse fjernet fra %0% + Innloggingsside + Velg siden som har loginformularet + Fjern beskyttelse + Velg sidene som inneholder login-skjema og feilmelding ved feil innolgging. + Velg rollene som har tilgang til denne siden + Sett brukernavn og passord for denne siden + Enkelt: Beskytt ved hjelp av brukernavn og passord + Om du ønsker å bruke enkel autentisering via ett enkelt brukernavn og passord + + + %0% kunne ikke publiseres fordi et tredjepartstillegg avbrøt handlingen. + Inkluder upubliserte undersider + Publiserer - vennligst vent... + %0% av %1% sider har blitt publisert... + %0% er nå publisert + %0% og alle undersider er nå publisert + Publiser alle undersider + Klikk <em>ok</em> for å publisere <strong>%0%</strong> og dermed gjøre innholdet synlig for alle.<br/><br />Du kan publisere denne siden og alle dens undersider ved å krysse av <em>Publiser alle undersider</em> nedenfor. + %0% ble ikke publisert. Ett eller flere felter ble ikke godkjent av validering. + %0% kan ikke publiseres fordi en overordnet side ikke er publisert. + + + Legg til ekstern lenke + Legg til intern lenke + Legg til + Tittel + Intern side + Url + Flytt ned + Flytt opp + Åpne i nytt vindu + Fjern lenke + + + Gjeldende versjon + Dette viser forskjellene mellom den gjeldende og den valgte versjonen<br /><del>Rød</del> tekst vil ikke bli vist i den valgte versjonen. , <ins>grønn betyr lagt til</ins> + Dokumentet er tilbakeført til en tidligere versjon + Dette viser den valgte versjonen som HTML, bruk avviksvisningen hvis du ønsker å se forksjellene mellom to versjoner samtidig. + Tilbakefør til + Velg versjon + Vis + + + Rediger scriptfilen + + + Concierge + Innhold + Courier + Utvikler + Umbraco konfigurasjonsveiviser + Mediaarkiv + Medlemmer + Nyhetsbrev + Innstillinger + Statistikk + Oversettelse + Brukere + + + Standardmal + Ordboksnøkkel + For å importere en dokumenttype, finn ".udt" filen på datamaskinen din ved å klikke "Utforsk" knappen og klikk "Importer" (du vil bli spurt om bekreftelse i det neste skjermbildet) + Ny tittel på arkfane + Nodetype + Type + Stilark + Stilark-egenskap + Arkfane + Tittel på arkfane + Arkfaner + Hovedinnholdstype aktivert + Denne dokumenttypen bruker + som hoveddokumenttype. Arkfaner fra hoveddokumenttyper vises ikke og kan kun endres på hoveddokumenttypen selv. + + + Sortering ferdig. + Dra elementene opp eller ned for å arrangere dem. Du kan også klikke kolonneoverskriftene for å sortere alt på en gang. + Vennligst vent. Elementene blir sortert, dette kan ta litt tid.<br/> <br/> Ikke lukk dette vinduet under sortering + + + Publisering ble avbrutt av et tredjepartstillegg + Egenskaptypen finnes allerede + Egenskapstype opprettet + Navn: %0% <br /> DataType: %1% + Egenskapstype slettet + Innholdstype lagret + Du har opprettet en arkfane + Arkfane slettet + Arkfane med id: %0% slettet + Stilarket ble ikke lagret + Stilarket ble lagret + Stilark lagret uten feil + Datatype lagret + Ordbokelement lagret + Publiseringen feilet fordi den overliggende siden ikke er publisert + Innhold publisert + og er nå synlig for besøkende + Innhold lagret + Husk å publisere for å gjøre endringene synlig for besøkende + Sendt for godkjenning + Endringer har blitt sendt til godkjenning + Medlem lagret + Stilarksegenskap lagret + Stilark lagret + Mal lagret + Feil ved lagring av bruker (sjekk loggen) + Bruker lagret + Filen ble ikke lagret + Filen kunne ikke lagres. Vennligst sjekk filrettigheter + Filen ble lagret + Filen ble lagret uten feil + Språk lagret + Python-skriptet ble ikke lagret + Python-skriptet kunne ikke lagres fordi det inneholder en eller flere feil + Python-skriptet er lagret! + Ingen feil i python-skriptet! + Malen ble ikke lagret + Vennligst forviss deg om at du ikke har to maler med samme alias + Malen ble lagret + Malen ble lagret uten feil! + XSLT-koden ble ikke lagret + XSLT-koden inneholdt en feil + XSLT-koden ble ikke lagret, sjekk filrettigheter + XSLT lagret + Ingen feil i XSLT! + Media lagret + Brukertypen lagret + Innhold avpublisert + Delmal lagret + Delmal lagret uten feil + Delmal ble ikke lagret! + En feil oppsto ved lagring av delmal + + + Bruk CSS syntaks f.eks: h1, .redHeader, .blueText + Rediger stilark + Rediger egenskap for stilark + Navn for å identifisere stilarksegenskapen i rik-tekst editoren + Forhåndsvis + Stiler + + + Rediger mal + Sett inn innholdsområde + Sett inn plassholder for innholdsområde + Sett inn ordbokselement + Sett inn makro + Sett inn Umbraco sidefelt + Hovedmal + Hurtigguide til Umbraco sine maltagger + Mal + + + Alternativt felt + Alternativ tekst + Store/små bokstaver + Felt som skal settes inn + Konverter linjeskift + Erstatter et linjeskift med htmltaggen &lt;br&gt; + Ja, kun dato + Formatter som dato + HTML koding + Formater spesialtegn med tilsvarende HTML-tegn. + Denne teksten vil settes inn etter verdien av feltet + Denne teksten vil settes inn før verdien av feltet + Små bokstaver + Ingen + Sett inn etter felt + Sett inn før felt + Rekursivt + Fjern paragraftagger + Fjerner eventuelle &lt;P&gt; rundt teksten + Store bokstaver + URL koding + Dersom innholdet av feltene skal sendes til en URL skal spesialtegn formatteres + Denne teksten vil benyttes dersom feltene over er tomme + Dette feltet vil benyttes dersom feltet over er tomt + Ja, med klokkeslett. Dato/tid separator: + Egendefinerte felt + Standardfelter + + + Oppgaver satt til deg + Listen nedenfor viser oversettelsesoppgaver <strong>som du er tildelt</strong>. For å se en detaljert visning inkludert kommentarer, klikk på "Detaljer" eller navnet på siden. Du kan også laste ned siden som XML direkte ved å klikke på linken "Last ned XML". <br/> For å lukke en oversettelsesoppgave, vennligst gå til detaljvisningen og klikk på "Lukk" knappen. + Lukk oppgave + Oversettelses detaljer + Last ned all oversettelsesoppgaver som XML + Last ned XML + Last ned XML DTD + Felt + Inkluder undersider + + Hei %0% + + Dette er en automatisk mail for å informere deg om at dokumentet '%1%' + har blitt anmodet oversatt til '%5%' av %2%. + + Gå til http://%3%/Umbraco/translation/default.aspx?id=%4% for å redigere. + + Ha en fin dag! + + Vennlig hilsen Umbraco Robot. + + [%0%] Oversettingsoppgave for %1% + Ingen oversettelses-bruker funnet. Vennligst opprett en oversettelses-bruker før du begynner å sende innhold til oversetting + Oppgaver opprettet av deg + Listen under viser sider <strong>opprettet av deg</strong>. For å se en detaljert visning inkludert kommentarer, klikk på "Detaljer" eller navnet på siden. Du kan også laste ned siden som XML direkte ved å klikke på linken "Last ned XML". For å lukke en oversettelsesoppgave, vennligst gå til detaljvisningen og klikk på "Lukk" knappen. + Siden '%0%' har blitt sendt til oversetting + Send til oversetting + Tildelt av + Oppgave åpnet + Antall ord + Oversett til + Oversetting fullført. + Du kan forhåndsvise sidene du nettopp har oversatt ved å klikke nedenfor. Hvis den originale siden finnes, vil du få en sammenligning av sidene. + Oversetting mislykkes, XML filen kan være korrupt + Alternativer for oversetting + Oversetter + Last opp XML med oversettelse + + + Hurtigbufferleser + Papirkurv + Opprettede pakker + Datatyper + Ordbok + Installerte pakker + Installer utseende + Installer startpakke + Språk + Installer lokal pakke + Makroer + Mediatyper + Medlemmer + Medlemsgrupper + Roller + Medlemstyper + Dokumenttyper + Pakker + Pakker + Python Filer + Installer fra pakkeregister + Installer Runway + Runway moduler + Skriptfiler + Skript + Stiler + Maler + XSLT Filer + + + Ny oppdatering er klar + %0% er klar, klikk her for å laste ned + Ingen forbindelse til server + Kunne ikke sjekke etter ny oppdatering. Se trace for mere info. + + + Administrator + Kategorifelt + Bytt passord + Du kan endre passordet til Umbraco ved å fylle ut skjemaet under og klikke "Bytt passord" knappen. + Innholdskanal + Beskrivelsesfelt + Deaktiver bruker + Dokumenttype + Redaktør + Utdragsfelt + Språk + Login + Øverste nivå i Media + Moduler + Deaktiver tilgang til Umbraco + Passord + Passordet er endret + Bekreft nytt passord + Nytt passord + Nytt passord kan ikke være blankt + Nytt og bekreftet passord må være like + Nytt og bekreftet passord må være like + Overskriv tillatelser på undernoder + Du redigerer for øyeblikket tillatelser for sidene: + Velg sider for å redigere deres tillatelser + Søk i alle undersider + Startnode + Brukernavn + Brukertillatelser + Brukertype + Brukertyper + Forfatter + Nytt passord + Bekreft nytt passord + Gjeldende passord + Feil passord + + \ No newline at end of file diff --git a/src/Umbraco.Web.UI/Umbraco/js/install.loader.js b/src/Umbraco.Web.UI/Umbraco/js/install.loader.js deleted file mode 100644 index 869521ec7d..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/js/install.loader.js +++ /dev/null @@ -1,17 +0,0 @@ -LazyLoad.js( [ - 'lib/jquery/jquery.min.js', - /* 1.1.5 */ - 'lib/angular/1.1.5/angular.min.js', - 'lib/angular/1.1.5/angular-cookies.min.js', - 'lib/angular/1.1.5/angular-mobile.min.js', - 'lib/angular/1.1.5/angular-mocks.js', - 'lib/angular/1.1.5/angular-sanitize.min.js', - 'lib/underscore/underscore-min.js', - 'js/umbraco.installer.js', - 'js/umbraco.directives.js' - ], function () { - jQuery(document).ready(function () { - angular.bootstrap(document, ['ngSanitize', 'umbraco.install', 'umbraco.directives.validation']); - }); - } -); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Media.cshtml b/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Media.cshtml index 09d04219f2..f5dfc6459c 100644 --- a/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Media.cshtml +++ b/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Media.cshtml @@ -14,7 +14,7 @@ } } - @Model.value.caption + @Model.value.altText if (Model.value.caption != null) { diff --git a/src/Umbraco.Web.UI/Views/Web.config b/src/Umbraco.Web.UI/Views/Web.config index 0da8fd5ad1..c8cd836ade 100644 --- a/src/Umbraco.Web.UI/Views/Web.config +++ b/src/Umbraco.Web.UI/Views/Web.config @@ -2,14 +2,14 @@ - -
    -
    + +
    +
    - + @@ -22,6 +22,7 @@ + @@ -44,11 +45,11 @@ --> + pageParserFilterType="System.Web.Mvc.ViewTypeParserFilter, System.Web.Mvc, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" + pageBaseType="System.Web.Mvc.ViewPage, System.Web.Mvc, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" + userControlBaseType="System.Web.Mvc.ViewUserControl, System.Web.Mvc, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"> - + diff --git a/src/Umbraco.Web.UI/config/Dashboard.Release.config b/src/Umbraco.Web.UI/config/Dashboard.Release.config index 14fd92ada0..4a998b0b80 100644 --- a/src/Umbraco.Web.UI/config/Dashboard.Release.config +++ b/src/Umbraco.Web.UI/config/Dashboard.Release.config @@ -37,6 +37,11 @@ views/dashboard/developer/examinemanagement.html + + + views/dashboard/developer/xmldataintegrityreport.html + +
    @@ -67,12 +72,7 @@ views/dashboard/default/startupdashboardintro.html - - - - views/dashboard/ChangePassword.html - - +
    diff --git a/src/Umbraco.Web.UI/config/Dashboard.config b/src/Umbraco.Web.UI/config/Dashboard.config index 90de4110b9..212049916b 100644 --- a/src/Umbraco.Web.UI/config/Dashboard.config +++ b/src/Umbraco.Web.UI/config/Dashboard.config @@ -10,7 +10,6 @@
    -
    forms @@ -21,7 +20,6 @@
    -
    developer @@ -52,7 +50,6 @@
    -
    forms @@ -63,7 +60,6 @@
    -
    translator @@ -79,11 +75,6 @@ views/dashboard/default/startupdashboardintro.html - - - views/dashboard/ChangePassword.html - -
    @@ -100,7 +91,7 @@ contour - /umbraco/plugins/umbracocontour/formsdashboard.ascx + plugins/umbracocontour/formsdashboard.ascx
    - \ No newline at end of file + diff --git a/src/Umbraco.Web.UI/config/EmbeddedMedia.Release.config b/src/Umbraco.Web.UI/config/EmbeddedMedia.Release.config index fe58a8fa84..6472968e44 100644 --- a/src/Umbraco.Web.UI/config/EmbeddedMedia.Release.config +++ b/src/Umbraco.Web.UI/config/EmbeddedMedia.Release.config @@ -1,17 +1,11 @@ - + - - - - - - @@ -20,16 +14,54 @@ xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - @@ -67,5 +99,21 @@ - + + + + + + + + + + + + + + + + + diff --git a/src/Umbraco.Web.UI/config/EmbeddedMedia.config b/src/Umbraco.Web.UI/config/EmbeddedMedia.config index 86f00b1ef4..cf9fb4f22d 100644 --- a/src/Umbraco.Web.UI/config/EmbeddedMedia.config +++ b/src/Umbraco.Web.UI/config/EmbeddedMedia.config @@ -1,17 +1,11 @@  - + - - - - - - @@ -20,16 +14,54 @@ xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + @@ -68,4 +100,20 @@ + + + + + + + + + + + + + + + + diff --git a/src/Umbraco.Web.UI/config/ExamineIndex.Release.config b/src/Umbraco.Web.UI/config/ExamineIndex.Release.config index c95f24d1e2..b56a5a19e8 100644 --- a/src/Umbraco.Web.UI/config/ExamineIndex.Release.config +++ b/src/Umbraco.Web.UI/config/ExamineIndex.Release.config @@ -8,10 +8,10 @@ More information and documentation can be found on CodePlex: http://umbracoexami --> - + - + @@ -24,5 +24,5 @@ More information and documentation can be found on CodePlex: http://umbracoexami - + \ No newline at end of file diff --git a/src/Umbraco.Web.UI/config/ExamineIndex.config b/src/Umbraco.Web.UI/config/ExamineIndex.config index a853847ecb..7212053ca0 100644 --- a/src/Umbraco.Web.UI/config/ExamineIndex.config +++ b/src/Umbraco.Web.UI/config/ExamineIndex.config @@ -9,10 +9,10 @@ More information and documentation can be found on CodePlex: http://umbracoexami - + - + @@ -25,6 +25,6 @@ More information and documentation can be found on CodePlex: http://umbracoexami - + \ No newline at end of file diff --git a/src/Umbraco.Web.UI/config/ExamineSettings.config b/src/Umbraco.Web.UI/config/ExamineSettings.config index 2bc4ab6ec7..6759b5a21d 100644 --- a/src/Umbraco.Web.UI/config/ExamineSettings.config +++ b/src/Umbraco.Web.UI/config/ExamineSettings.config @@ -6,21 +6,24 @@ Index sets can be defined in the ExamineIndex.config if you're using the standar More information and documentation can be found on CodePlex: http://umbracoexamine.codeplex.com --> - + + analyzer="Lucene.Net.Analysis.WhitespaceAnalyzer, Lucene.Net" + useTempStorage="Sync"/> + analyzer="Lucene.Net.Analysis.Standard.StandardAnalyzer, Lucene.Net" + useTempStorage="Sync"/> - + @@ -28,12 +31,16 @@ More information and documentation can be found on CodePlex: http://umbracoexami + analyzer="Lucene.Net.Analysis.WhitespaceAnalyzer, Lucene.Net" + useTempStorage="Sync"/> - + + analyzer="Lucene.Net.Analysis.Standard.StandardAnalyzer, Lucene.Net" + enableLeadingWildcard="true" + useTempStorage="Sync"/> diff --git a/src/Umbraco.Web.UI/config/Lang/cs-CZ.user.xml b/src/Umbraco.Web.UI/config/Lang/cs-CZ.user.xml new file mode 100644 index 0000000000..d4902d563d --- /dev/null +++ b/src/Umbraco.Web.UI/config/Lang/cs-CZ.user.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/Umbraco.Web.UI/config/Lang/da-DK.user.xml b/src/Umbraco.Web.UI/config/Lang/da-DK.user.xml new file mode 100644 index 0000000000..7a8ce2c28a --- /dev/null +++ b/src/Umbraco.Web.UI/config/Lang/da-DK.user.xml @@ -0,0 +1,3 @@ + + + diff --git a/src/Umbraco.Web.UI/config/Lang/de-DE.user.xml b/src/Umbraco.Web.UI/config/Lang/de-DE.user.xml new file mode 100644 index 0000000000..7a8ce2c28a --- /dev/null +++ b/src/Umbraco.Web.UI/config/Lang/de-DE.user.xml @@ -0,0 +1,3 @@ + + + diff --git a/src/Umbraco.Web.UI/config/Lang/en-GB.user.xml b/src/Umbraco.Web.UI/config/Lang/en-GB.user.xml new file mode 100644 index 0000000000..7a8ce2c28a --- /dev/null +++ b/src/Umbraco.Web.UI/config/Lang/en-GB.user.xml @@ -0,0 +1,3 @@ + + + diff --git a/src/Umbraco.Web.UI/config/Lang/en-US.user.xml b/src/Umbraco.Web.UI/config/Lang/en-US.user.xml new file mode 100644 index 0000000000..7a8ce2c28a --- /dev/null +++ b/src/Umbraco.Web.UI/config/Lang/en-US.user.xml @@ -0,0 +1,3 @@ + + + diff --git a/src/Umbraco.Web.UI/config/Lang/es-ES.user.xml b/src/Umbraco.Web.UI/config/Lang/es-ES.user.xml new file mode 100644 index 0000000000..7a8ce2c28a --- /dev/null +++ b/src/Umbraco.Web.UI/config/Lang/es-ES.user.xml @@ -0,0 +1,3 @@ + + + diff --git a/src/Umbraco.Web.UI/config/Lang/fr-FR.user.xml b/src/Umbraco.Web.UI/config/Lang/fr-FR.user.xml new file mode 100644 index 0000000000..7a8ce2c28a --- /dev/null +++ b/src/Umbraco.Web.UI/config/Lang/fr-FR.user.xml @@ -0,0 +1,3 @@ + + + diff --git a/src/Umbraco.Web.UI/config/Lang/he-IL.user.xml b/src/Umbraco.Web.UI/config/Lang/he-IL.user.xml new file mode 100644 index 0000000000..3a0ad355c3 --- /dev/null +++ b/src/Umbraco.Web.UI/config/Lang/he-IL.user.xml @@ -0,0 +1,3 @@ + + + diff --git a/src/Umbraco.Web.UI/config/Lang/it-IT.user.xml b/src/Umbraco.Web.UI/config/Lang/it-IT.user.xml new file mode 100644 index 0000000000..3a0ad355c3 --- /dev/null +++ b/src/Umbraco.Web.UI/config/Lang/it-IT.user.xml @@ -0,0 +1,3 @@ + + + diff --git a/src/Umbraco.Web.UI/config/Lang/ja-JP.user.xml b/src/Umbraco.Web.UI/config/Lang/ja-JP.user.xml new file mode 100644 index 0000000000..7a8ce2c28a --- /dev/null +++ b/src/Umbraco.Web.UI/config/Lang/ja-JP.user.xml @@ -0,0 +1,3 @@ + + + diff --git a/src/Umbraco.Web.UI/config/Lang/ko-KR.user.xml b/src/Umbraco.Web.UI/config/Lang/ko-KR.user.xml new file mode 100644 index 0000000000..3a0ad355c3 --- /dev/null +++ b/src/Umbraco.Web.UI/config/Lang/ko-KR.user.xml @@ -0,0 +1,3 @@ + + + diff --git a/src/Umbraco.Web.UI/config/Lang/nb-NO.user.xml b/src/Umbraco.Web.UI/config/Lang/nb-NO.user.xml new file mode 100644 index 0000000000..3a0ad355c3 --- /dev/null +++ b/src/Umbraco.Web.UI/config/Lang/nb-NO.user.xml @@ -0,0 +1,3 @@ + + + diff --git a/src/Umbraco.Web.UI/config/Lang/nl-NL.user.xml b/src/Umbraco.Web.UI/config/Lang/nl-NL.user.xml new file mode 100644 index 0000000000..7a8ce2c28a --- /dev/null +++ b/src/Umbraco.Web.UI/config/Lang/nl-NL.user.xml @@ -0,0 +1,3 @@ + + + diff --git a/src/Umbraco.Web.UI/config/Lang/pl-PL.user.xml b/src/Umbraco.Web.UI/config/Lang/pl-PL.user.xml new file mode 100644 index 0000000000..3a0ad355c3 --- /dev/null +++ b/src/Umbraco.Web.UI/config/Lang/pl-PL.user.xml @@ -0,0 +1,3 @@ + + + diff --git a/src/Umbraco.Web.UI/config/Lang/pt-BR.user.xml b/src/Umbraco.Web.UI/config/Lang/pt-BR.user.xml new file mode 100644 index 0000000000..3a0ad355c3 --- /dev/null +++ b/src/Umbraco.Web.UI/config/Lang/pt-BR.user.xml @@ -0,0 +1,3 @@ + + + diff --git a/src/Umbraco.Web.UI/config/Lang/ru-RU.user.xml b/src/Umbraco.Web.UI/config/Lang/ru-RU.user.xml new file mode 100644 index 0000000000..7a8ce2c28a --- /dev/null +++ b/src/Umbraco.Web.UI/config/Lang/ru-RU.user.xml @@ -0,0 +1,3 @@ + + + diff --git a/src/Umbraco.Web.UI/config/Lang/sv-SE.user.xml b/src/Umbraco.Web.UI/config/Lang/sv-SE.user.xml new file mode 100644 index 0000000000..3a0ad355c3 --- /dev/null +++ b/src/Umbraco.Web.UI/config/Lang/sv-SE.user.xml @@ -0,0 +1,3 @@ + + + diff --git a/src/Umbraco.Web.UI/config/Lang/zh-CN.user.xml b/src/Umbraco.Web.UI/config/Lang/zh-CN.user.xml new file mode 100644 index 0000000000..8d2add98dd --- /dev/null +++ b/src/Umbraco.Web.UI/config/Lang/zh-CN.user.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/Umbraco.Web.UI/config/log4net.Release.config b/src/Umbraco.Web.UI/config/log4net.Release.config index 13297c8a49..10b8c9eb7a 100644 --- a/src/Umbraco.Web.UI/config/log4net.Release.config +++ b/src/Umbraco.Web.UI/config/log4net.Release.config @@ -5,19 +5,23 @@ - - - + + + - + + + + + diff --git a/src/Umbraco.Web.UI/config/log4net.config b/src/Umbraco.Web.UI/config/log4net.config index 0a0e9e4ff7..eca84962a1 100644 --- a/src/Umbraco.Web.UI/config/log4net.config +++ b/src/Umbraco.Web.UI/config/log4net.config @@ -6,19 +6,22 @@ - - - + + - + + + + + diff --git a/src/Umbraco.Web.UI/config/metablogConfig.config b/src/Umbraco.Web.UI/config/metablogConfig.config index a3f43d9480..3ff4c0e547 100644 --- a/src/Umbraco.Web.UI/config/metablogConfig.config +++ b/src/Umbraco.Web.UI/config/metablogConfig.config @@ -5,7 +5,7 @@ 0 1080 False - Articulate + pup diff --git a/src/Umbraco.Web.UI/config/splashes/noNodes.aspx b/src/Umbraco.Web.UI/config/splashes/noNodes.aspx index 38415e3222..5e7f289d2f 100644 --- a/src/Umbraco.Web.UI/config/splashes/noNodes.aspx +++ b/src/Umbraco.Web.UI/config/splashes/noNodes.aspx @@ -16,15 +16,8 @@ - - - - - @@ -62,7 +55,7 @@
    - + \ No newline at end of file diff --git a/src/Umbraco.Web.UI/config/tinyMceConfig.Release.config b/src/Umbraco.Web.UI/config/tinyMceConfig.Release.config index db84225ed0..4b0c9fbba4 100644 --- a/src/Umbraco.Web.UI/config/tinyMceConfig.Release.config +++ b/src/Umbraco.Web.UI/config/tinyMceConfig.Release.config @@ -10,6 +10,12 @@ 1 + codemirror + images/editor/code.gif + codemirror + 1 + + removeformat images/editor/removeformat.gif removeformat @@ -199,19 +205,21 @@ - code + code + codemirror paste umbracolink anchor charmap table lists + hr - { "indentOnInit": false, - "path": "../../../../../umbraco_client/CodeMirror/Js", + "path": "../../../../lib/codemirror", "config": { }, "jsFiles": [ diff --git a/src/Umbraco.Web.UI/config/tinyMceConfig.config b/src/Umbraco.Web.UI/config/tinyMceConfig.config index a4cdb5f3f6..4da0083d30 100644 --- a/src/Umbraco.Web.UI/config/tinyMceConfig.config +++ b/src/Umbraco.Web.UI/config/tinyMceConfig.config @@ -204,21 +204,21 @@ - + code codemirror paste - umbracolink anchor charmap table lists + hr - { "indentOnInit": false, - "path": "../../../../../umbraco_client/CodeMirror/Js", + "path": "../../../../lib/codemirror", "config": { }, "jsFiles": [ @@ -247,4 +247,4 @@ param[name|value|_value|class],embed[type|width|height|src|class|*],map[name|cla } - \ No newline at end of file + diff --git a/src/Umbraco.Web.UI/config/trees.Release.config b/src/Umbraco.Web.UI/config/trees.Release.config index bb60356cd4..4d1b0eb349 100644 --- a/src/Umbraco.Web.UI/config/trees.Release.config +++ b/src/Umbraco.Web.UI/config/trees.Release.config @@ -3,39 +3,47 @@ + + - + + + + - - - - + - - + + + - + + + - - - + + + + + + \ No newline at end of file diff --git a/src/Umbraco.Web.UI/config/trees.config b/src/Umbraco.Web.UI/config/trees.config index 8a85f26788..4d1b0eb349 100644 --- a/src/Umbraco.Web.UI/config/trees.config +++ b/src/Umbraco.Web.UI/config/trees.config @@ -3,39 +3,47 @@ + + - + + + + - - - - + - - + + + - + + + - - - + + + + + + iconClosed="icon-folder" iconOpen="icon-folder" sortOrder="10" />--> + \ No newline at end of file diff --git a/src/Umbraco.Web.UI/config/umbracoSettings.Release.config b/src/Umbraco.Web.UI/config/umbracoSettings.Release.config index 4c21cedd54..7aabc1ac63 100644 --- a/src/Umbraco.Web.UI/config/umbracoSettings.Release.config +++ b/src/Umbraco.Web.UI/config/umbracoSettings.Release.config @@ -130,7 +130,7 @@ @trySkipIisCustomErrors Tries to skip IIS custom errors. Starting with IIS 7.5, this must be set to true for Umbraco 404 pages to show. Else, IIS will take - over and render its build-in error page. See MS doc for HttpResponseBase.TrySkipIisCustomErrors. + over and render its built-in error page. See MS doc for HttpResponseBase.TrySkipIisCustomErrors. The default value is false, for backward compatibility reasons, which means that IIS _will_ take over, and _prevent_ Umbraco 404 pages to show. @internalRedirectPreservesTemplate @@ -147,10 +147,15 @@ By default you can call any content Id in the url and show the content with that id, for example: http://mysite.com/1092 or http://mysite.com/1092.aspx would render the content with id 1092. Setting this setting to true stops that behavior + @umbracoApplicationUrl + The url of the Umbraco application. By default, Umbraco will figure it out from the first request. + Configure it here if you need anything specific. Needs to be a complete url with scheme and umbraco + path, eg http://mysite.com/umbraco. NOT just "mysite.com" or "mysite.com/umbraco" or "http://mysite.com". --> + internalRedirectPreservesTemplate="false" disableAlternativeTemplates="false" disableFindContentByIdPath="false" + umbracoApplicationUrl=""> diff --git a/src/Umbraco.Web.UI/config/umbracoSettings.config b/src/Umbraco.Web.UI/config/umbracoSettings.config index cd57e3c9c9..0348b3af79 100644 --- a/src/Umbraco.Web.UI/config/umbracoSettings.config +++ b/src/Umbraco.Web.UI/config/umbracoSettings.config @@ -10,7 +10,7 @@ umbracoWidth - umbracoHeight + umbracoHeight umbracoBytes umbracoExtension @@ -124,15 +124,15 @@ star - ae - oe - aa - ae - oe - ue - ss - ae - oe + ae + oe + aa + ae + oe + ue + ss + ae + oe - @@ -248,7 +248,7 @@ @trySkipIisCustomErrors Tries to skip IIS custom errors. Starting with IIS 7.5, this must be set to true for Umbraco 404 pages to show. Else, IIS will take - over and render its build-in error page. See MS doc for HttpResponseBase.TrySkipIisCustomErrors. + over and render its built-in error page. See MS doc for HttpResponseBase.TrySkipIisCustomErrors. The default value is false, for backward compatibility reasons, which means that IIS _will_ take over, and _prevent_ Umbraco 404 pages to show. @internalRedirectPreservesTemplate @@ -265,10 +265,15 @@ By default you can call any content Id in the url and show the content with that id, for example: http://mysite.com/1092 or http://mysite.com/1092.aspx would render the content with id 1092. Setting this setting to true stops that behavior + @umbracoApplicationUrl + The url of the Umbraco application. By default, Umbraco will figure it out from the first request. + Configure it here if you need anything specific. Needs to be a complete url with scheme and umbraco + path, eg http://mysite.com/umbraco. NOT just "mysite.com" or "mysite.com/umbraco" or "http://mysite.com". --> - + \ No newline at end of file diff --git a/src/Umbraco.Web.UI/packages.config b/src/Umbraco.Web.UI/packages.config index e3bd7088f2..f945663ebe 100644 --- a/src/Umbraco.Web.UI/packages.config +++ b/src/Umbraco.Web.UI/packages.config @@ -1,23 +1,22 @@  - - - - - - + + + + + + - - - - - + + + + - + @@ -27,10 +26,10 @@ - - + + - - + + \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco/ClientRedirect.aspx b/src/Umbraco.Web.UI/umbraco/ClientRedirect.aspx index 72232c99cd..6f0bd7c008 100644 --- a/src/Umbraco.Web.UI/umbraco/ClientRedirect.aspx +++ b/src/Umbraco.Web.UI/umbraco/ClientRedirect.aspx @@ -10,7 +10,7 @@ - @Html.BareMinimumServerVariables(Url, Url.Action("ExternalLogin", "BackOffice", new { area = ViewBag.UmbracoPath })) - @Html.AngularExternalLoginInfoValues((IEnumerable)ViewBag.ExternalSignInError) - @*And finally we can load in our angular app*@ @@ -79,5 +87,12 @@ @Html.RenderProfiler() } + + + diff --git a/src/Umbraco.Web.UI/umbraco/config/create/UI.Release.xml b/src/Umbraco.Web.UI/umbraco/config/create/UI.Release.xml index 0e13eba345..9fca6d082a 100644 --- a/src/Umbraco.Web.UI/umbraco/config/create/UI.Release.xml +++ b/src/Umbraco.Web.UI/umbraco/config/create/UI.Release.xml @@ -1,21 +1,5 @@ - - -
    Nodetype
    - /create/nodeType.ascx - - - - -
    - -
    Nodetype
    - /create/nodeType.ascx - - - - -
    +
    Template
    /create/simple.ascx @@ -73,28 +57,7 @@ -
    - -
    Datatype
    - /create/simple.ascx - - - -
    - -
    Datatype
    - /create/simple.ascx - - - -
    - -
    membertype
    - /create/simple.ascx - - - -
    +
    membergroup
    /create/simple.ascx @@ -115,78 +78,28 @@ -
    - -
    mediatype
    - /create/simple.ascx +
    + +
    member
    + /create/member.ascx - +
    - -
    Medie type
    - /create/simple.ascx + +
    member
    + /create/member.ascx - - +
    - -
    member
    - /create/member.ascx - - - -
    - -
    member
    - /create/member.ascx - - - -
    - -
    member
    - /create/member.ascx - - - -
    - -
    membertype
    - /create/member.ascx - - - -
    - +
    membergroup
    /create/simple.ascx -
    - - +
    Stylesheet editor egenskab
    /create/simple.ascx diff --git a/src/Umbraco.Web.UI/umbraco/config/create/UI.xml b/src/Umbraco.Web.UI/umbraco/config/create/UI.xml index 1ab0f4180c..f6859c6423 100644 --- a/src/Umbraco.Web.UI/umbraco/config/create/UI.xml +++ b/src/Umbraco.Web.UI/umbraco/config/create/UI.xml @@ -1,21 +1,5 @@  - - -
    Nodetype
    - /create/nodeType.ascx - - - - -
    - -
    Nodetype
    - /create/nodeType.ascx - - - - -
    +
    Template
    /create/simple.ascx @@ -73,28 +57,7 @@ -
    - -
    Datatype
    - /create/simple.ascx - - - -
    - -
    Datatype
    - /create/simple.ascx - - - -
    - -
    membertype
    - /create/simple.ascx - - - -
    +
    membergroup
    /create/simple.ascx @@ -115,78 +78,14 @@ -
    - -
    mediatype
    - /create/simple.ascx - - - -
    - -
    Medie type
    - /create/simple.ascx - - - - -
    - -
    member
    - /create/member.ascx - - - -
    - -
    member
    - /create/member.ascx - - - -
    - -
    member
    - /create/member.ascx - - - -
    - -
    membertype
    - /create/member.ascx - - - -
    - + +
    membergroup
    /create/simple.ascx
    - -
    Stylesheet editor egenskab
    /create/simple.ascx diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml index ad7737e5be..5c4ba9bcb0 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml @@ -8,7 +8,7 @@ Tilføj domæne Revisions spor Gennemse elementer - Skift dokumenttype + Skift dokumenttype Kopier Opret Opret pakke @@ -26,8 +26,10 @@ Notificeringer Offentlig adgang Udgiv + Fortryd udgivelse Genindlæs elementer Genudgiv hele siten + Gendan Rettigheder Fortryd ændringer Send til udgivelse @@ -36,22 +38,29 @@ Send til udgivelse Oversæt Opdatér + Standard værdi + Tilladelse nægtet. Tilføj nyt domæne + fjern + Ugyldig node. + Ugyldigt domæne-format. + Domæne er allerede blevet tildelt. + Sprog Domæne Domænet '%0%' er nu oprettet og tilknyttet siden Domænet '%0%' er nu slettet Domænet '%0%' er oprettet - f.eks. ditdomaene.com, www.ditdomaene.com Domænet '%0%' er nu opdateret eller rediger nuværende domæner + f.eks. ditdomaene.com, www.ditdomaene.com For - Vælg + Vælg Vælg nuværende mappe Gør noget andet Fed @@ -71,15 +80,16 @@ Indsæt makro Indsæt billede Redigér relationer + Tilbage til listen Gem Gem og udgiv Gem og send til udgivelse - Vælg - Flere muligheder... Se siden + Preview er deaktiveret fordi der ikke er nogen skabelon tildelt Vælg formattering Vis koder Indsæt tabel + Generer modeller For at skifte det valgte indholds dokumenttype, skal du først vælge en ny dokumenttype, som er gyldig på denne placering. @@ -102,7 +112,7 @@ Kun andre dokumenttyper, der er gyldige på denne placering, vises. - Udgivet + Udgivet Om siden Alias (hvordan ville du f.eks. beskrive billedet via telefonen?) @@ -119,9 +129,9 @@ Dette punkt er ændret siden udgivelsen Dette punkt er endnu ikke udgivet Sidst udgivet - Der er ingen elementer at vise på listen. + Der er ingen elementer at vise på listen. Medietype - Link til medie(r) + Link til medie(r) Medlemsgruppe Rolle Medlemstype @@ -129,24 +139,24 @@ Sidetitel Egenskaber Dette dokument er udgivet, men ikke synligt da den overliggende side '%0%' ikke er udgivet! - Upd: dette dokument er udgiver, men er ikke i cachen (intern fejl) + Upd: dette dokument er udgiver, men er ikke i cachen (intern fejl) Udgivet Udgivelsesstatus Udgivelsesdato - Dato for Fortryd udgivelse + Dato for Fortryd udgivelse Fjern dato Sorteringrækkefølgen er opdateret For at sortere, træk siderne eller klik på en af kolonnehovederne. Du kan vælge flere sider ved at holde "shift" eller "control" nede mens du vælger. Statistik Titel (valgfri) - Alternativ tekst (valgfri) + Alternativ tekst (valgfri) Type Fortryd udgivelse Sidst redigeret Tidspunkt for seneste redigering Fjern fil Link til dokument - Medlem af grupper(ne) + Medlem af grupper(ne) Ikke medlem af grupper(ne) Undersider Åben i vindue @@ -155,6 +165,10 @@ Klik for at uploade Slip filerne her... + + Opret et nyt medlem + Alle medlemmer + Hvor ønsker du at oprette den nye %0% Opret under @@ -346,7 +360,7 @@ Log ud Makro Flyt - Mere + Mere Navn Ny Næste @@ -366,7 +380,7 @@ Mangler Omdøb Forny - Påkrævet + Påkrævet Prøv igen Rettigheder Søg @@ -375,6 +389,7 @@ Hvilken side skal vises efter at formularen er sendt Størrelse Sortér + Submit Type Skriv for at søge... Op @@ -391,6 +406,8 @@ Ja Mappe Søgeresultater + Reorder + I am done reordering Baggrundsfarve @@ -407,6 +424,9 @@ Kunne ikke gemme web.config filen. Du bedes venligst manuelt ændre database forbindelses strengen. Din database er blevet fundet og identificeret som Database konfiguration + installér
    knappen for at installere Umbraco %0% databasen + ]]> installér
    knappen for at installere Umbraco %0% databasen]]> Næste for at fortsætte.]]> Databasen er ikke fundet. Kontrollér venligst at informationen i database forbindelsesstrengen i "web.config" filen er korrekt.

    @@ -609,9 +629,15 @@ Mange hilsner fra Umbraco robotten Nulstil - Indsæt element - Tilføj rækker til dit layout - nedenfor og tilføje det første element]]> + Vælg indholdstype + Vælg layout + Tilføj række + Tilføj indhold + Slip indhold + Instillinger tilføjet + + Indholdet er ikke tilladt her + Indholdet er tilladt her Klik for at indlejre Klik for at indsætte et billede @@ -669,6 +695,14 @@ Mange hilsner fra Umbraco robotten Oversættelse Brugere Hjælp + Formularer + Analytics + + + gå til + Hjælpeemner for + Videokapitler for + De bedste Umbraco video tutorials Standardskabelon @@ -678,6 +712,7 @@ Mange hilsner fra Umbraco robotten Nodetype Type Stylesheet + Script Stylesheetegenskab Faneblad Titel på faneblad @@ -691,6 +726,8 @@ Mange hilsner fra Umbraco robotten
    Luk ikke dette vindue imens]]>
    + Annulleret + Handlingen blev annulleret af et 3. part tilføjelsesprogram Udgivelsen blev standset af et 3. parts modul Property type eksisterer allerede Egenskabstype oprettet @@ -712,7 +749,7 @@ Mange hilsner fra Umbraco robotten Husk at publicere for at gøre det synligt for besøgende Send til Godkendelse Rettelser er blevet sendt til godkendelse - Medie gemt + Medie gemt Medie gemt uden problemer Medlem gemt Stylesheetegenskab gemt @@ -720,6 +757,7 @@ Mange hilsner fra Umbraco robotten Template gemt Der er opstået en fejl under redigering Bruger gemt + Brugertype gemt Fil ikke gemt Filen kunne ikke gemmes. Tjek filrettighederne Fil gemt @@ -738,6 +776,11 @@ Mange hilsner fra Umbraco robotten XSLT kunne ikke gemmes, check filrettigheder XSLT gemt Der var ingen fejl i din XSLT! + Indhold fjernet fra udgivelse + Partial view gemt + Partial view gemt uden fejl! + Partial view ikke gemt + Der opstod en fejl ved at gemme filen. Bruger CSS-syntax f.eks. h1, .redheader, .blueTex @@ -778,6 +821,7 @@ Mange hilsner fra Umbraco robotten Rekursivt Fjern paragraf-tags Fjerner eventuelle &lt;P&gt; omkring teksten + Standard felter Uppercase URL encode Hvis indholdet af felterne skal sendes til en url, skal denne slåes til så specialtegn formateres @@ -817,7 +861,7 @@ Mange hilsner fra Umbraco robotten Cacheviser Papirkurv Oprettede pakker - Datatyper + Datatyper Ordbog Installerede pakker Installér et skin' @@ -827,21 +871,24 @@ Mange hilsner fra Umbraco robotten Makroer Medietyper Medlemmer - Medlemsgruppe + Medlemsgruppe Roller - Medlemstype - Dokumenttyper + Medlemstype + Dokumenttyper + Dokumenttyper + Pakker - Pakker + Pakker Python Installer fra "repository" Installer Runway Runway-moduler Scripting filer - Script + Scripts Stylesheets Skabeloner XSLT-filer + Analytics Ny opdatering er klar @@ -863,16 +910,17 @@ Mange hilsner fra Umbraco robotten Redaktør Uddragsfelt Sprog - Login + Brugernavn Startnode i mediearkivet Moduler Deaktivér adgang til Umbraco Adgangskode + Nulstil kodeord Dit kodeord er blevet ændret! Bekræft venligst dit nye kodeord Indtast dit nye kodeord - Nuværende kodeord - ugyldig nuværende kodeord + Nuværende kodeord + ugyldig nuværende kodeord Dit nye kodeord må ikke være tomt! Dit nye kodeord og dit bekræftede kodeord var ikke ens, forsøg venligst igen! Det bekræftede kodeord matcher ikke det nye kodeord @@ -881,12 +929,13 @@ Mange hilsner fra Umbraco robotten Vælg sider for at ændre deres rettigheder Søg alle 'børn' Start node - Brugernavn - Bruger tilladelser + Navn + Brugertilladelser Brugertype - Bruger typer + Brugertyper Forfatter - + Oversætter + Skift Din profil Din historik Session udløber diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/de.xml b/src/Umbraco.Web.UI/umbraco/config/lang/de.xml index 92e76f26a7..e227be61e1 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/de.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/de.xml @@ -1,5 +1,5 @@ - - + + The Umbraco community http://our.umbraco.org/documentation/Extending-Umbraco/Language-Files @@ -8,6 +8,7 @@ Hostnamen verwalten Protokoll Durchsuchen + Dokumenttyp ändern Kopieren Erstellen Paket erstellen @@ -23,8 +24,10 @@ Benachrichtigungen Öffentlicher Zugriff Veröffentlichen + Veröffentlichung zurücknehmen Aktualisieren Erneut veröffentlichen + Wiederherstellen Berechtigungen Zurücksetzen Zur Veröffentlichung einreichen @@ -33,32 +36,35 @@ Zur Veröffentlichung einreichen Übersetzen Aktualisieren - Veröffentlichung zurücknehmen + Standardwert - Neue Domain hinzufügen - Domain - Domain '%0%' hinzugefügt - Domain '%0%' entfernt - Die Domain '%0%' ist bereits zugeordnet - Beispiel: example.com, www.example.com - Domain '%0%' aktualisiert - Domains bearbeiten Erlaubnis verweigert. + Neue Domain hinzufügen entfernen Ungültiges Element. Format der Domain ungültig. Domain wurde bereits zugewiesen. Sprache + Domain + Domain '%0%' hinzugefügt + Domain '%0%' entfernt + Die Domain '%0%' ist bereits zugeordnet + Domain '%0%' aktualisiert + Domains bearbeiten + Beispiel: example.com, www.example.com Vererben Kultur - Domains Definiert die Kultureinstellung für untergeordnete Elemente dieses Elements oder vererbt vom übergeordneten Element. Wird auch auf das aktuelle Element angewendet, sofern auf tieferer Ebene keine Domain zugeordnet ist. + Domains Ansicht für + Auswählen + Aktuellen Ordner auswählen + Etwas anderes machen Fett Ausrücken Formularelement einfügen @@ -76,32 +82,57 @@ Makro einfügen Abbildung einfügen Datenbeziehungen bearbeiten + Zurück zur Liste Speichern Speichern und veröffentlichen Speichern und zur Abnahme übergeben Vorschau + Die Vorschaufunktion ist deaktiviert, da keine Vorlage zugewiesen ist Stil auswählen Stil anzeigen Tabelle einfügen - Die Vorschaufunktion ist deaktiviert, da keine Vorlage zugewiesen ist - Auswählen - Etwas anders machen + + + Um den Typ des ausgewählten Dokuments zu ändern, wählen Sie bitte zunächst aus der Liste der an dieser Stelle erlaubten Dokumenttypen. + Im Anschluss bestätigen oder korrigieren Sie die Zuordnung der Eigenschaften und klicken Sie auf 'Speichern'. + Der Inhalt wurde neu veröffentlicht. + Derzeitige Eigenschaft + Derzeitiger Datentyp + Der Typ dieses Dokuments kann nicht geändert werden, da an dieser Stelle keine Alternativen zugelassen sind. Ein alternativer Dokumenttyp kann nur dann verwendet werden, wenn er unterhalb des diesem Dokument übergeordneten Elements angelegt werden darf. + Dokumenttyp geändert + Eigenschaften zuordnen + Dieser Eigenschaft zuordnen + Neue Vorlage + Neuer Typ + keiner + Inhalt + Neuen Dokumenttyp auswählen + Der Typ des ausgewählten Dokuments wurde erfolgreich zu [new type] geändert und die Eigenschaften wie folgend zugeordnet: + nach + Die Zuordnung der Eigenschaften kann nicht abgeschlossen werden, da mindestens eine Eigenschaft mehrfach zugeordnet werden soll. + Nur an dieser Stelle erlaubte Dokumenttypen werden angezeigt. + Ist veröffentlicht Über dieses Dokument Alias (Wie würden Sie das Bild über das Telefon beschreiben?) Alternative Links Klicken, um das Dokument zu bearbeiten Erstellt von + Ursprünglicher Autor + Aktualisiert von Erstellt am + Erstellungszeitpunkt des Dokuments Dokumenttyp In Bearbeitung Veröffentlichung aufheben am Dieses Dokument wurde nach dem Veröffentlichen bearbeitet. Dieses Dokument ist nicht veröffentlicht. Zuletzt veröffentlicht + Diese Liste enthält keine Einträge. Medientyp + Verweis auf Medienobjekt(e) Mitgliedergruppe Mitgliederrolle Mitglieder-Typ @@ -109,36 +140,42 @@ Name des Dokument Eigenschaften Dieses Dokument ist veröffentlicht aber nicht sichtbar, da das übergeordnete Dokument '%0%' nicht publiziert ist + Ups! Dieses Dokument ist veröffentlicht aber nicht im internen Cache aufzufinden: Systemfehler. Veröffentlichen Publikationsstatus Veröffentlichen am + Veröffentlichung widerrufen am Datum entfernen Sortierung abgeschlossen Um die Dokumente zu sortieren, ziehen Sie sie einfach an die gewünschte Position. Sie können mehrere Zeilen markieren indem Sie die Umschalttaste ("Shift") oder die Steuerungstaste ("Strg") gedrückt halten Statistiken Titel (optional) + Alternativtext (optional) Typ - Ausblenden + Veröffentlichung widerrufen Zuletzt bearbeitet am + Letzter Änderungszeitpunkt des Dokuments Datei entfernen Link zum Dokument - Verweis auf Medienobjekt(e) - Ups! Dieses Dokument ist veröffentlicht aber nicht im internen Cache aufzufinden: Systemfehler. - Ursprünglicher Autor - Aktualisiert von - Erstellungszeitpunkt des Dokuments - Veröffentlichung widerrufen am - Letzter Änderungszeitpunkt des Dokuments Mitglied der Gruppe(n) Kein Mitglied der Gruppe(n) - Untergeordnete Elemente + Untergeordnete Elemente + Ziel + + + Für Upload klicken + Dateien hier fallen lassen ... + + + Neues Mitglied anlegen + Alle Mitglieder An welcher Stellen wollen Sie das Element erstellen Erstellen unter Wählen Sie einen Namen und einen Typ - Es stehen keine erlaubten Dokumenttypen zur Verfügung. Sie müssen diese in den Einstellungen (unter "Dokumenttypen") aktivieren. - Es stehen keine erlaubten Medientypen zur Verfügung. Sie müssen diese in den Einstellungen (unter "Medientypen") aktivieren. + Es stehen keine erlaubten Dokumenttypen zur Verfügung. Sie müssen diese in den Einstellungen (unter "Dokumenttypen") aktivieren. + Es stehen keine erlaubten Medientypen zur Verfügung. Sie müssen diese in den Einstellungen (unter "Medientypen") aktivieren. Website anzeigen @@ -199,8 +236,20 @@ Bearbeiten Sie nachfolgend die verschiedenen Sprachversionen für den Wörterbucheintrag '<em>%0%</em>'. <br/>Unter dem links angezeigten Menüpunkt 'Sprachen' können Sie weitere hinzufügen. Name der Kultur + + Benutzername eingeben + Kennwort eingeben + %0% benennen ... + Name angeben ... + Durchsuchen ... + Filtern ... + Tippen, um Tags hinzuzufügen (nach jedem Tag die Eingabetaste drücken) ... + + Auf oberster Ebene erlauben + Nur diese Dokumenttypen können auf oberster Ebene in Inhalte und Medien angelegt werden Dokumenttypen, die unterhalb dieses Typs erlaubt sind + Zusammengesetzte Dokumenttypen Erstellen Registerkarte löschen Beschreibung @@ -208,6 +257,11 @@ Registerkarte Illustration Listenansicht aktivieren + Aktiviert eine durchsuch- und sortierbare Listendarstellung der untergeordneten Elemente anstelle diese in der Baumstruktur anzuzeigen + Aktuelle Listenansicht + Der Datentyp für die aktuelle Ansicht der Liste + Angepasste Listenansicht erstellen + Angepasste Listenansicht entfernen Vorgabewert hinzufügen @@ -236,6 +290,7 @@ '%0%' hat ein falsches Format + Dieser Dateityp wird durch die Systemeinstellungen blockiert ACHTUNG! Obwohl CodeMirror in den Einstellungen aktiviert ist, bleibt das Modul wegen mangelnder Stabilität in Internet Explorer deaktiviert. Bitte geben Sie die Bezeichnung und den Alias des neuen Dokumenttyps ein. Es besteht ein Problem mit den Lese-/Schreibrechten auf eine Datei oder einen Ordner @@ -251,11 +306,12 @@ Sie können keine Zelle trennen, die nicht zuvor aus mehreren zusammengeführt wurde. Fehler im XSLT Das XSLT ist fehlerhaft und wurde daher nicht gespeichert. - Dieser Dateityp wird durch die Systemeinstellungen blockiert + Es liegt ein Konfigurationsfehler beim Datentyp dieser Eigenschaft vor. Bitte prüfen Sie den Datentyp bzw. die Eigenschaft. Info Aktion + Aktionen Hinzufügen Alias Sind Sie sicher? @@ -305,6 +361,7 @@ Abmelden Makro Verschieben + Mehr Name Neu Weiter @@ -324,6 +381,7 @@ Verbleibend Umbenennen Erneuern + Pflichtangabe Wiederholen Berechtigungen Suchen @@ -332,8 +390,9 @@ Seite beim Senden anzeigen Größe Sortieren + Submit Typ - nach Inhalten suchen ... + Durchsuchen ... nach oben Aktualisieren Update @@ -347,10 +406,9 @@ Breite Ja Ordner - Aktionen - Mehr - Pflichtangabe Suchergebnisse + Reorder + I am done reordering Hintergrundfarbe @@ -465,15 +523,16 @@ Wenn Sie sich für Runway entscheiden, können Sie optional Blöcke nutzen, die Erneuern Sie, um Ihre Arbeit zu speichern ... - <p style="text-align:right;">&copy; 2001 - %0% <br /><a href="http://umbraco.com" style="text-decoration: none" target="_blank">umbraco.org</a></p> - Einen wunderbaren Sonntag - Frohen freundlichen Freitag - Donnerwetter Donnerstag - Schönen Montag - Einen großartigen Dienstag - Wunderbaren Mittwoch - Wunderbaren sonnigen Samstag + Einen wunderbaren Sonntag + Schönen Montag + Einen großartigen Dienstag + Wunderbaren Mittwoch + Donnerwetter Donnerstag + Frohen freundlichen Freitag + Wunderbaren sonnigen Samstag Hier anmelden: + Sitzung abgelaufen + <p style="text-align:right;">&copy; 2001 - %0% <br /><a href="http://umbraco.com" style="text-decoration: none" target="_blank">umbraco.org</a></p> Dashboard @@ -490,9 +549,9 @@ Wenn Sie sich für Runway entscheiden, können Sie optional Blöcke nutzen, die Es ist noch kein Element ausgewählt. Bitte wählen Sie ein Element aus der Liste aus, bevor Sie fortfahren. Das aktuelle Element kann aufgrund seines Dokumenttyps nicht an diese Stelle verschoben werden. Das ausgewählte Element kann nicht zu einem seiner eigenen Unterelemente verschoben werden. + Dieses Element kann nicht auf der obersten Ebene platziert werden. Diese Aktion ist nicht erlaubt, da Sie unzureichende Berechtigungen für mindestens ein untergeordnetes Element haben. Kopierte Elemente mit dem Original verknüpfen - Dieses Element kann nicht auf der obersten Ebene platziert werden. Bearbeiten Sie Ihre Benachrichtigungseinstellungen für '%0%' @@ -507,30 +566,33 @@ Wenn Sie sich für Runway entscheiden, können Sie optional Blöcke nutzen, die Ihr freundlicher Umbraco-Robot
    -<p>Hallo %0%,</p> +Hallo %0%

    +

    Dies ist eine automatisch E-Mail, welche Sie informiert, dass die Aufgabe '%1%' + an der Seite '%2%' + vom Benutzer '%3%' ausgeführt wurde. +

    + +

    +

    Zusammenfassung der Aktualisierung:

    + + %6% +
    +

    -<p>die Aufgabe <strong>'%1%'</strong> (von Benutzer '%3%') an der Seite <a href="http://%4%/actions/preview.aspx?id=%5%"><strong>'%2%'</strong></a> wurde ausgeführt.</p> -<div style="margin: 8px 0; padding: 8px; display: block;"> - <br /> - <a style="color: white; font-weight: bold; background-color: #66cc66; text-decoration : none; margin-right: 20px; border: 8px solid #66cc66; width: 150px;" href="http://%4%/Umbraco/actions/publish.aspx?id=%5%">&nbsp;&nbsp;VERÖFFENTLICHEN&nbsp;&nbsp;</a> &nbsp; - <a style="color: white; font-weight: bold; background-color: #5372c3; text-decoration : none; margin-right: 20px; border: 8px solid #5372c3; width: 150px;" href="http://%4%/Umbraco/actions/editContent.aspx?id=%5%">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;BEARBEITEN&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</a> &nbsp; - <a style="color: white; font-weight: bold; background-color: #ca4a4a; text-decoration : none; margin-right: 20px; border: 8px solid #ca4a4a; width: 150px;" href="http://%4%/Umbraco/actions/delete.aspx?id=%5%">&nbsp;&nbsp;&nbsp;&nbsp;LÖSCHEN&nbsp;&nbsp;&nbsp;&nbsp;</a> - <br /> -</div> -<p> - <h3>Zusammenfassung der Aktualisierung:</h3> - <table style="width: 100%;"> - %6% - </table> - </p> -<div style="margin: 8px 0; padding: 8px; display: block;"> - <br /> - <a style="color: white; font-weight: bold; background-color: #66cc66; text-decoration : none; margin-right: 20px; border: 8px solid #66cc66; width: 150px;" href="http://%4%/Umbraco/actions/publish.aspx?id=%5%">&nbsp;&nbsp;VERÖFFENTLICHEN&nbsp;&nbsp;</a> &nbsp; - <a style="color: white; font-weight: bold; background-color: #5372c3; text-decoration : none; margin-right: 20px; border: 8px solid #5372c3; width: 150px;" href="http://%4%/Umbraco/actions/editContent.aspx?id=%5%">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;BEARBEITEN&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</a> &nbsp; - <a style="color: white; font-weight: bold; background-color: #ca4a4a; text-decoration : none; margin-right: 20px; border: 8px solid #ca4a4a; width: 150px;" href="http://%4%/Umbraco/actions/delete.aspx?id=%5%">&nbsp;&nbsp;&nbsp;&nbsp;LÖSCHEN&nbsp;&nbsp;&nbsp;&nbsp;</a> - <br /> -</div> -<p>Einen schönen Tag wünscht<br />Ihr freundlicher Umbraco-Robot</p> + + +

    Einen schönen Tag wünscht

    + Ihr freundlicher Umbraco-Robot +

    +]]>
    [%0%] Benachrichtigung: %1% ausgeführt an Seite '%2%' Benachrichtigungen @@ -558,8 +620,8 @@ Wenn Sie sich für Runway entscheiden, können Sie optional Blöcke nutzen, die Hinweise für die Durchführung des Updates Es ist ein Update für dieses Paket verfügbar. Sie können es direkt vom Umbraco-Paket-Repository herunterladen. Version des Pakets - Paket-Webseite aufrufen Versionsverlauf des Pakets + Paket-Webseite aufrufen Einfügen mit Formatierung (Nicht empfohlen) @@ -586,7 +648,10 @@ Wenn Sie sich für Runway entscheiden, können Sie optional Blöcke nutzen, die Wenn Sie einen einfachen Zugriffsschutz unter Verwendung eines einzelnen Logins mit Kennwort aktivieren wollen + %0% kann nicht veröffentlicht werden, da die Veröffentlichung zeitlich geplant ist. + %0% konnte nicht veröffentlicht werden, da einige Daten die Gültigkeitsprüfung nicht bestanden haben. %0% konnte nicht veröffentlicht werden, da ein Plug-In die Aktion abgebrochen hat. + %0% kann nicht veröffentlicht werden, da das übergeordnete Dokument nicht veröffentlicht ist. Unveröffentlichte Unterelemente einschließen Bitte warten, Veröffentlichung läuft... %0% Elemente veröffentlicht, %1% Elemente ausstehend ... @@ -594,21 +659,21 @@ Wenn Sie sich für Runway entscheiden, können Sie optional Blöcke nutzen, die %0% und die untergeordneten Elemente wurden veröffentlicht %0% und alle untergeordneten Elemente veröffentlichen Mit <em>Ok</em> wird <strong>%0%</strong> veröffentlicht und auf der Website sichtbar.<br/><br />Sie können dieses Element mitsamt seinen untergeordneten Elementen veröffentlichen, indem Sie <em>Unveröffentlichte Unterelemente einschließen</em> aktivieren. - %0% konnte nicht veröffentlicht werden, da einige Daten die Gültigkeitsprüfung nicht bestanden haben. - %0% kann nicht veröffentlicht werden, da das übergeordnete Dokument nicht veröffentlicht ist. - %0% kann nicht veröffentlicht werden, da die Veröffentlichung zeitlich geplant ist. + + + Sie haben keine freigegeben Farben konfiguriert - Neuer externer Link - Neuer interner Link - Link hinzufügen + Externen Link eingeben + Internen Link auswählen Beschriftung - interne Seite - URL - nach unten - nach oben + Link In neuem Fenster öffnen - Link entfernen + Bezeichnung eingeben + Link eingeben + + + Zurücksetzen Aktuelle Version @@ -635,8 +700,15 @@ Wenn Sie sich für Runway entscheiden, können Sie optional Blöcke nutzen, die Statistiken Übersetzung Benutzer - Umbraco Contour - Hilfe + Hilfe + Formulare + Auswertungen + + + go to + Hilfethemen zu + Video-Tutorials für + Die besten Umbraco-Video-Tutorials Standardvorlage @@ -646,14 +718,17 @@ Wenn Sie sich für Runway entscheiden, können Sie optional Blöcke nutzen, die Elementtyp Typ Stylesheet + Skript Stylesheet-Eigenschaft Registerkarte Registerkartenbeschriftung Registerkarten - Dieser Dokumenttyp verwendet Masterdokumenttyp aktiviert + Dieser Dokumenttyp verwendet als Masterdokumenttyp. Register vom Masterdokumenttyp werden nicht angezeigt und können nur im Masterdokumenttyp selbst bearbeitet werden Für dieses Register sind keine Eigenschaften definiert. Klicken Sie oben auf "neue Eigenschaft hinzufügen", um eine neue Eigenschaft hinzuzufügen. + Masterdokumenttyp + Zugehörige Vorlage anlegen Sortierung abgeschlossen. @@ -661,6 +736,10 @@ Wenn Sie sich für Runway entscheiden, können Sie optional Blöcke nutzen, die Bitte warten, die Seiten werden sortiert. Das kann einen Moment dauern. Bitte schließen Sie dieses Fenster nicht, bis der Sortiervorgang abgeschlossen ist. + Fehlgeschlagen + Unzureichende Benutzerberechtigungen. Vorgang kann nicht abgeschlossen werden. + Abgebrochen + Vorgang wurde durch eine benutzerdefinierte Erweiterung abgebrochen Das Veröffentlichen wurde von einem individuellen Ereignishandler abgebrochen Eigenschaft existiert bereits Eigenschaft erstellt @@ -677,17 +756,20 @@ Wenn Sie sich für Runway entscheiden, können Sie optional Blöcke nutzen, die Wörterbucheintrag gespeichert Veröffentlichung nicht möglich, da das übergeordnete Dokument nicht veröffentlicht ist. Inhalte veröffentlicht - und sichtbar auf der Webseite + Sichtbar auf der Webseite Inhalte gespeichert - Denken Sie daran die Inhalte zu veröffentlichen, um die Änderungen sichtbar zu machen + Denken Sie daran, die Inhalte zu veröffentlichen, um die Änderungen sichtbar zu machen Zur Abnahme eingereicht Die Änderungen wurden zur Abnahme eingereicht + Medium gespeichert + Medium fehlerfrei gespeichert Mitglied gespeichert Stylesheet-Regel gespeichert Stylesheet gespeichert Vorlage gespeichert Fehler beim Speichern des Benutzers. Benutzer gespeichert + Benutzertyp gepsichert Datei wurde nicht gespeichert Datei konnte nicht gespeichert werden. Bitte überprüfen Sie die Schreibrechte auf Dateiebene. Datei gespeichert @@ -706,14 +788,16 @@ Wenn Sie sich für Runway entscheiden, können Sie optional Blöcke nutzen, die XSLT kann nicht gespeichert werden. Bitte überprüfen Sie die Schreibrechte auf Dateiebene. XSLT gespeichert Keine Fehler im XSLT - Medium gespeichert Veröffentlichung des Inhalts aufgehoben Partielle Ansicht gespeichert Partielle Ansicht ohne Fehler gespeichert. Partielle Ansicht nicht gespeichert Fehler beim Speichern der Datei. - Benutzertyp gepsichert - Medium fehlerfrei gespeichert + Skript gespeichert + Skript fehlerfrei gespeichert! + Skript nicht gespeichert + Fehler beim Speichern der Datei. + Fehler beim Speichern der Datei. Gewünschter CSS-Selektor, zum Beispiel 'h1', '.bigHeader' oder 'p.infoText' @@ -734,13 +818,42 @@ Wenn Sie sich für Runway entscheiden, können Sie optional Blöcke nutzen, die Schnellübersicht zu den verfügbaren Umbraco-Feldern Vorlage + + Element hinzufügen + Zeilenlayout auswählen + Einfach auf <i class="icon icon-add blue"></i> klicken, um das erste Element anzulegen + Drop content + Klicken, um Inhalt einzubetten + Klicken, um Abbildung einzufügen + Beschriftung ... + Hier schreiben ... + Layouts + Layouts sind die grundlegenden Arbeitsflächen für das Gestaltungsraster. Üblicherweise sind nicht mehr als ein oder zwei Layouts nötig. + Layout hinzufügen + Passen Sie das Layout an, indem Sie die Spaltenbreiten einstellen und Abschnitte hinzufügen. + Einstellungen für das Zeilenlayout + Zeilen sind vordefinierte horizontale Zellenanordnungen + Zeilenlayout hinzufügen + Pasen Sie das Zeilenlayout an, indem Sie die Zellenbreite einstellen und Zellen hinzufügen. + Spalten + Insgesamte Spaltenanzahl im Layout + Einstellungen + Legen Sie fest, welche Einstellungen die Autoren anpassen können. + CSS-Stile + Legen Sie fest, welche Stile die Autoren anpassen können. + Die Einstellungen werden nur gespeichert, wenn die angegebene JSON-Konfiguration gültig ist. + Alle Elemente erlauben + Alle Zeilenlayouts erlauben + Alternatives Feld Alternativer Text Groß- und Kleinschreibung + Kodierung Feld auswählen Zeilenumbrüche ersetzen Ersetzt Zeilenumbrüche durch das HTML-Tag <br /> + Benutzerdefinierte Felder nur Datum Als Datum formatieren HTML kodieren @@ -754,15 +867,13 @@ Wenn Sie sich für Runway entscheiden, können Sie optional Blöcke nutzen, die Rekursiv Textabsatz entfernen Alle <p> am Anfang und am Ende des Feldinhalts werden entfernt + Standardfelder Großbuchstaben URL kodieren Wandelt Sonderzeichen zur Verwendung in URLs um Wird nur verwendet, wenn beide vorgenannten Felder leer sind Dieses Feld wird nur verwendet, wenn das primäre Feld leer ist Datum und Zeit mit Trennzeichen: - Benutzerdefinierte Felder - Standardfelder - Kodierung Ihre Aufgaben @@ -807,7 +918,7 @@ Ihr freundlicher Umbraco-Robot Zwischenspeicher Papierkorb Erstellte Pakete - Datentypen + Datentypen Wörterbuch Installierte Pakete Design-Skin installieren @@ -817,10 +928,10 @@ Ihr freundlicher Umbraco-Robot Makros Medientypen Mitglieder - Mitgliedergruppen + Mitgliedergruppen Mitgliederrollen - Mitglieder-Typen - Dokumenttypen + Mitglieder-Typen + Dokumententypen Pakete Pakete Python-Dateien @@ -832,6 +943,7 @@ Ihr freundlicher Umbraco-Robot Stylesheets Vorlagen XSLT-Dateien + Auswertungen Neues Update verfügbar @@ -843,7 +955,9 @@ Ihr freundlicher Umbraco-Robot Administrator Feld für Kategorie Kennwort ändern - Sie können Ihr Kennwort für den Zugriff auf den Umbraco-Verwaltungsbereich ändern, indem Sie das nachfolgende Formular ausfüllen und auf die 'Kennwort ändern'-Schaltfläche klicken + Neues Kennwort + Neues Kennwort (Bestätigung) + Sie können Ihr Kennwort für den Zugriff auf den Umbraco-Verwaltungsbereich ändern, indem Sie das nachfolgende Formular ausfüllen und auf 'Kennwort ändern' klicken Schnittstelle für externe Editoren Feld für Beschreibung Benutzer endgültig deaktivieren @@ -856,13 +970,15 @@ Ihr freundlicher Umbraco-Robot Freigegebene Bereiche Zugang sperren Kennwort + Kennwort zurücksetzen Ihr Kennwort wurde geändert! - Aktuelle Kennwort - Ungültig aktuelle Kennwort + Bitte bestätigen Sie das neue Kennwort Geben Sie Ihr neues Kennwort ein Ihr neues Kennwort darf nicht leer sein! + Aktuelles Kennwort + Aktuelles Kennwort falsch Ihr neues Kennwort und die Wiederholung Ihres neuen Kennworts stimmen nicht überein. Bitte versuchen Sie es erneut! - Die Wiederholung Ihres Kennworts stimmt nicht mit dem neuen Kennwort überein! + Die Bestätigung Ihres Kennworts stimmt nicht mit dem angegebenen neuen Kennwort überein! Die Berechtigungen der untergeordneten Elemente ersetzen Die Berechtigungen für folgende Seiten werden angepasst: Dokumente auswählen, um deren Berechtigungen zu ändern @@ -873,12 +989,9 @@ Ihr freundlicher Umbraco-Robot Rolle Rollen Autor - Neues Kennwort - Neues Kennwort (Bestätigung) - Bitte bestätigen Sie das neue Kennwort - Kennwort zurücksetzen - Ihr Profil - Ihr Verlauf - Sitzung läuft ab in + Übersetzer + Ihr Profil + Ihr Verlauf + Sitzung läuft ab in diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index db2d34c7a5..fdbbe3c581 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -53,7 +53,7 @@ Domain '%0%' has been updated Edit Current Domains
    One-level paths in domains are supported, eg. "example.com/en". However, they + "https://www.example.com/". One-level paths in domains are supported, eg. "example.com/en". However, they should be avoided. Better use the culture setting above.]]>
    Inherit Culture @@ -94,6 +94,7 @@ Choose style Show styles Insert table + Generate models To change the document type for the selected content, first select from the list of valid types for this location. @@ -169,6 +170,10 @@ Click to upload Drop your files here... + + Create a new member + All Members + Where do you want to create the new %0% Create an item under @@ -297,6 +302,12 @@ NOTE! Even though CodeMirror is enabled by configuration, it is disabled in Internet Explorer because it's not stable enough. Please fill both alias and name on the new property type! There is a problem with read/write access to a specific file or folder + Error loading Partial View script (file: %0%) + Error loading userControl '%0%' + Error loading customControl (Assembly: %0%, Type: '%1%') + Error loading MacroEngine script (file: %0%) + "Error parsing XSLT file: %0% + "Error reading XSLT file: %0% Please enter a title Please choose a type You're about to make the picture larger than the original size. Are you sure that you want to proceed? @@ -393,6 +404,7 @@ Show page on Send Size Sort + Submit Type Type to search... Up @@ -409,6 +421,8 @@ Yes Folder Search results + Reorder + I am done reordering Background colour @@ -748,6 +762,12 @@ To manage your website, simply open the Umbraco back office and start adding con Forms Analytics + + go to + Help topics for + Video chapters for + The best Umbraco video tutorials + Default template Dictionary Key @@ -774,6 +794,12 @@ To manage your website, simply open the Umbraco back office and start adding con
    Do not close this window during sorting]]>
    + Validation + Validation errors must be fixed before the item can be saved + Failed + Insufficient user permissions, could not complete the operation + Cancelled + Operation was cancelled by a 3rd party add-in Publishing was cancelled by a 3rd party add-in Property type already exists Property type created @@ -827,6 +853,11 @@ To manage your website, simply open the Umbraco back office and start adding con Partial view saved without any errors! Partial view not saved An error occurred saving the file. + Script view saved + Script view saved without any errors! + Script view not saved + An error occurred saving the file. + An error occurred saving the file. Uses CSS syntax ex: h1, .redHeader, .blueTex @@ -847,22 +878,26 @@ To manage your website, simply open the Umbraco back office and start adding con Quick Guide to Umbraco template tags Template - - Insert control - Choose a layout for the page - below and add your first element]]> + Choose type of content + Choose a layout + Add a row + Add content + Drop content + Settings applied + + This content is not allowed here + This content is allowed here Click to embed Click to insert image Image caption... Write here... - - Grid layouts + + Grid Layouts Layouts are the overall work area for the grid editor, usually you only need one or two different layouts - Add grid layout + Add Grid Layout Adjust the layout by setting column widths and adding additional sections - Row configurations Rows are predefined cells arranged horizontally Add row configuration @@ -870,11 +905,10 @@ To manage your website, simply open the Umbraco back office and start adding con Columns Total combined number of columns in the grid layout - + Settings Configure what settings editors can change - Styles Configure what styling editors can change @@ -966,7 +1000,7 @@ To manage your website, simply open the Umbraco back office and start adding con Cache Browser Recycle Bin Created packages - Data Types + Data Types Dictionary Installed packages Install skin @@ -976,10 +1010,11 @@ To manage your website, simply open the Umbraco back office and start adding con Macros Media Types Members - Member Groups + Member Groups Roles - Member Types - Document Types + Member Types + Document Types + Relation Types Packages Packages Python Files @@ -1038,6 +1073,7 @@ To manage your website, simply open the Umbraco back office and start adding con User types Writer Translator + Change Your profile Your recent history Session expires in diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml index e8492423cd..8bbdbab0b5 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -53,7 +53,7 @@ Domain '%0%' has been updated Edit Current Domains
    One-level paths in domains are supported, eg. "example.com/en". However, they + "https://www.example.com/". One-level paths in domains are supported, eg. "example.com/en". However, they should be avoided. Better use the culture setting above.]]>
    Inherit Culture @@ -67,7 +67,7 @@ Select Select current folder - Do something else + Do something else Bold Cancel Paragraph Indent Insert form field @@ -169,6 +169,10 @@ Click to upload Drop your files here... + + Create a new member + All Members + Where do you want to create the new %0% Create an item under @@ -298,6 +302,12 @@ NOTE! Even though CodeMirror is enabled by configuration, it is disabled in Internet Explorer because it's not stable enough. Please fill both alias and name on the new property type! There is a problem with read/write access to a specific file or folder + Error loading Partial View script (file: %0%) + Error loading userControl '%0%' + Error loading customControl (Assembly: %0%, Type: '%1%') + Error loading MacroEngine script (file: %0%) + "Error parsing XSLT file: %0% + "Error reading XSLT file: %0% Please enter a title Please choose a type You're about to make the picture larger than the original size. Are you sure that you want to proceed? @@ -394,6 +404,7 @@ Show page on Send Size Sort + Submit Type Type to search... Up @@ -410,6 +421,8 @@ Yes Folder Search results + Reorder + I am done reordering Background color @@ -749,6 +762,12 @@ To manage your website, simply open the Umbraco back office and start adding con Forms Analytics + + go to + Help topics for + Video chapters for + The best Umbraco video tutorials + Default template Dictionary Key @@ -775,6 +794,12 @@ To manage your website, simply open the Umbraco back office and start adding con
    Do not close this window during sorting]]>
    + Validation + Validation errors must be fixed before the item can be saved + Failed + Insufficient user permissions, could not complete the operation + Cancelled + Operation was cancelled by a 3rd party add-in Publishing was cancelled by a 3rd party add-in Property type already exists Property type created @@ -828,6 +853,11 @@ To manage your website, simply open the Umbraco back office and start adding con Partial view saved without any errors! Partial view not saved An error occurred saving the file. + Script view saved + Script view saved without any errors! + Script view not saved + An error occurred saving the file. + An error occurred saving the file. Uses CSS syntax ex: h1, .redHeader, .blueTex @@ -849,19 +879,25 @@ To manage your website, simply open the Umbraco back office and start adding con Template - Insert control - Choose a layout for the page - below and add your first element]]> + Choose type of content + Choose a layout + Add a row + Add content + Drop content + Settings applied + + This content is not allowed here + This content is allowed here Click to embed Click to insert image Image caption... Write here... - Grid layouts - Layouts are the overall work area for the grid editor, usually you only need one or two different layouts - Add grid layout - Adjust the layout by setting column widths and adding additional sections + Grid Layouts + Layouts are the overall work area for the grid editor, usually you only need one or two different layouts + Add Grid Layout + Adjust the layout by setting column widths and adding additional sections Row configurations Rows are predefined cells arranged horizontally Add row configuration @@ -964,7 +1000,7 @@ To manage your website, simply open the Umbraco back office and start adding con Cache Browser Recycle Bin Created packages - Data Types + Data Types Dictionary Installed packages Install skin @@ -974,10 +1010,11 @@ To manage your website, simply open the Umbraco back office and start adding con Macros Media Types Members - Member Groups - Roles - Member Types - Document Types + Member Groups + Member Roles + Member Types + Document Types + Relation Types Packages Packages Python Files @@ -1036,6 +1073,7 @@ To manage your website, simply open the Umbraco back office and start adding con User types Writer Translator + Change Your profile Your recent history Session expires in diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/es.xml b/src/Umbraco.Web.UI/umbraco/config/lang/es.xml index fab7d30f03..9b3a69c34d 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/es.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/es.xml @@ -393,6 +393,7 @@ Mostrar página al enviar Tamaño Ordenar + Submit Tipo Tipo que buscar... Arriba @@ -407,6 +408,8 @@ Bienvenido... Ancho Si + Reorder + I am done reordering Color de fondo @@ -717,14 +720,20 @@ Insertar control + Choose layout Añade más filas - y añade tu primer contenido]]> + Add content + Drop content + Settings applied + + This content is not allowed here + This content is allowed here Plantillas de Grid Las plantillas son el área de trabajo para el editor de grids, normalmente sólo necesitas una o dos plantillas diferentes Añadir plantilla de grid Ajusta la plantilla configurando la anchura de las columnas y añadiendo más secciones - + Configuraciones de filas Las filas son celdas predefinidas que se disponen horizontalmente Añade una configuración de fila @@ -732,7 +741,7 @@ Columnas Número total de columnas en la plantilla del grid - + Configuración Configura qué ajustes pueden cambiar los editores @@ -814,7 +823,7 @@ Caché del navegador Papelera de reciclaje Paquetes creados - Tipos de datos + Tipos de datos Diccionario Paquetes instalados Instalar skin @@ -824,10 +833,10 @@ Macros Tipos de medios Miembros - Grupos de miembros + Grupos de miembros Roles - Tipos de miembros - Tipos de documento + Tipos de miembros + Tipos de documento Paquetes Paquetes Ficheros Python diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/fr.xml b/src/Umbraco.Web.UI/umbraco/config/lang/fr.xml index af1c39345b..90d7fa7c55 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/fr.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/fr.xml @@ -380,6 +380,7 @@ Afficher la page à l'envoi Taille Trier + Submit Type Rechercher... Haut @@ -396,6 +397,8 @@ Oui Dossier Résultats de recherche + Reorder + I am done reordering Background color @@ -833,6 +836,45 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Guide rapide aux tags des modèles Umbraco Modèle + + Choose type of content + Choose a layout + Add a row + Add content + Drop content + Settings applied + + This content is not allowed here + This content is allowed here + + Click to embed + Click to insert image + Image caption... + Write here... + + Grid Layouts + Layouts are the overall work area for the grid editor, usually you only need one or two different layouts + Add Grid Layout + Adjust the layout by setting column widths and adding additional sections + Row configurations + Rows are predefined cells arranged horizontally + Add row configuration + Adjust the row by setting cell widths and adding additional cells + + Columns + Total combined number of columns in the grid layout + + Settings + Configure what settings editors can change + + Styles + Configure what styling editors can change + + Settings will only save if the entered json configuration is valid + + Allow all editors + Allow all row configurations + Champ alternatif Texte alternatif @@ -915,7 +957,7 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Navigateur de cache Corbeille Packages créés - Typesde données + Typesde données Dictionnaire Packages installés Installer un skin @@ -925,10 +967,10 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Macros Types de médias Membres - Groupes de membres + Groupes de membres Rôles - Types de membres - Types de documents + Types de membres + Types de documents Packages Packages Fichiers Python diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/he.xml b/src/Umbraco.Web.UI/umbraco/config/lang/he.xml index 81cf4b2ff5..2ec3890367 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/he.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/he.xml @@ -316,6 +316,7 @@ הצג עמוד בשליחה גודל סדר + Submit סוג הקלד לחיפוש... למעלה @@ -330,6 +331,8 @@ ברוכים הבאים... רוחב כן + Reorder + I am done reordering צבע רקע @@ -722,6 +725,45 @@ To manage your website, simply open the Umbraco back office and start adding con מדריך מהיר עבור תבנית תגיות באומברקו תבנית + + Choose type of content + Choose a layout + Add a row + Add content + Drop content + Settings applied + + This content is not allowed here + This content is allowed here + + Click to embed + Click to insert image + Image caption... + Write here... + + Grid Layouts + Layouts are the overall work area for the grid editor, usually you only need one or two different layouts + Add Grid Layout + Adjust the layout by setting column widths and adding additional sections + Row configurations + Rows are predefined cells arranged horizontally + Add row configuration + Adjust the row by setting cell widths and adding additional cells + + Columns + Total combined number of columns in the grid layout + + Settings + Configure what settings editors can change + + Styles + Configure what styling editors can change + + Settings will only save if the entered json configuration is valid + + Allow all editors + Allow all row configurations + שדה אלטרנטיבי טקסט חלופי @@ -800,7 +842,7 @@ To manage your website, simply open the Umbraco back office and start adding con זיכרון מטמון בדפדפן סל מיחזור יצירת חבילות - סוגי מידע + סוגי מידע מילון חבילות מותקנות התקן עיצוב @@ -810,10 +852,10 @@ To manage your website, simply open the Umbraco back office and start adding con מקרו סוגי מדיה משתמשים - קבוצות משתמשים + קבוצות משתמשים כללים - סוגי משתמשים - סוגי מסמכים + סוגי משתמשים + סוגי מסמכים חבילות חבילות קבצי פייתון diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/it.xml b/src/Umbraco.Web.UI/umbraco/config/lang/it.xml index 975ce0bb6a..1ab6d883fd 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/it.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/it.xml @@ -309,6 +309,7 @@ Mostra la pagina inviata Dimensione Ordina + Submit Tipo Su @@ -323,6 +324,8 @@ Benvenuto... Larghezza Si + Reorder + I am done reordering Colore di sfondo @@ -692,6 +695,45 @@ Per gestire il tuo sito web, è sufficiente aprire il back office di Umbraco e i Template + + Choose type of content + Choose a layout + Add a row + Add content + Drop content + Settings applied + + This content is not allowed here + This content is allowed here + + Click to embed + Click to insert image + Image caption... + Write here... + + Grid Layouts + Layouts are the overall work area for the grid editor, usually you only need one or two different layouts + Add Grid Layout + Adjust the layout by setting column widths and adding additional sections + Row configurations + Rows are predefined cells arranged horizontally + Add row configuration + Adjust the row by setting cell widths and adding additional cells + + Columns + Total combined number of columns in the grid layout + + Settings + Configure what settings editors can change + + Styles + Configure what styling editors can change + + Settings will only save if the entered json configuration is valid + + Allow all editors + Allow all row configurations + @@ -773,7 +815,7 @@ Per gestire il tuo sito web, è sufficiente aprire il back office di Umbraco e i Cache Browser Cestino Pacchetti creati - Tipi di dato + Tipi di dato Dizionario Pacchetti installati Installare skin @@ -783,10 +825,10 @@ Per gestire il tuo sito web, è sufficiente aprire il back office di Umbraco e i Macros Tipi di media Membri - Gruppi di Membri + Gruppi di Membri Ruoli - Tipologia Membri - Tipi di documento + Tipologia Membri + Tipi di documento Pacchetti Pacchetti Files Python diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/ja.xml b/src/Umbraco.Web.UI/umbraco/config/lang/ja.xml index cd0bfe03d7..4771b24aa0 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/ja.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/ja.xml @@ -8,6 +8,7 @@ ドメインの割り当て 動作記録 ノードの参照 + ドキュメントタイプの変更 コピー 新規作成 パッケージの作成 @@ -26,6 +27,7 @@ 公開を止める 最新の情報に更新 サイトのリフレッシュ + 復元 アクセス権 以前の版に戻る 公開に送る @@ -34,22 +36,35 @@ 公開する 翻訳 更新 + 初期値 + アクセスが拒否されました ドメインの割り当て + ドメインの削除 + 適当でないノード名 適当でないホスト名 + そのホスト名は既に利用されています + 言語コード ドメイン ドメイン '%0%' が新たに割り当てられました ドメイン '%0%' は削除されました ドメイン '%0%' は既に割り当てられています - 例: yourdomain.com, www.yourdomain.com ドメイン '%0%' は更新されました ドメインの編集 + + Inherit + カルチャの割り当て + + ドメイン割り当て これらを表示 + 選択 + 現在のフォルダを選択 + その他のアクション 太字 インデント解除 フィールドから挿入 @@ -67,6 +82,7 @@ マクロの挿入 画像の挿入 関係性の編集 + リストに戻る 保存 保存及び公開 保存して承認に送る @@ -76,20 +92,45 @@ スタイルの表示 表の挿入 + + ドキュメントタイプを変更するには、まず有効なドキュメントタイプのリストから選択します + 確認および現在のドキュメントタイプからのマッピングを割り当て、保存します。 + コンテントは再公開されています + 現在のプロパティ + 現在のドキュメントタイプ + 有効な代替タイプが存在しないため変更することができません。選択されたコンテントの親の下に許可されたドキュメントタイプへのみ変更ができます + ドキュメントタイプを変更しました + プロパティを割り当てる + 割り当てるプロパティ + 新しいテンプレート + 新しいドキュメントタイプ + None + コンテント + ドキュメントタイプを変更する + プロパティが以下のように割り当てられました + から + 1つ以上のプロパティを割り当てられませんでした。プロパティが定義が重複しています + 有効なドキュメントタイプのみが表示されます + + 公開されました このページについて エイリアス (画像を電話でわかるように言葉で説明) 別名のリンク クリックでアイテムを編集する 作成者 + 作成者 + 更新者 作成日時 + このドキュメントが作成された日時 ドキュメントタイプ 変種中 公開終了日時 このページは公開後変更されています このページは公開されていません 公開日時 + リストに表示するアイテムはありません メディアタイプ メディアの項目へのリンク メンバーグループ @@ -99,24 +140,42 @@ タイトル プロパティ このページは公開されましたが、親ページの '%0%' が非公開のため閲覧できません + このコンテントは公開されていますがキャッシュされていません(内部エラー) 公開 公開状態 公開開始日時 + 公開停止日時 日時の消去 並び順が更新されました ノードをドラッグ、クリック、または列のヘッダーをクリックする事でノードを簡単にソートできます。SHIFT、CONTROLキーを使い複数のノードを選択する事もできます。 統計 タイトル (オプション) + 代替テキスト (オプション) 非公開 最終更新日時 + このドキュメントが最後に更新された日時 ファイルの消去 ページへのリンク + グループのメンバー + グループのメンバーではありません + 子コンテンツ + ターゲット + + + クリックしてアップロードする + ファイルをここへドロップ.. + + + メンバーの新規作成 + 全てのメンバー どこに新しい %0% を作りますか ここに作成 型とタイトルを選んでください + "document types".]]> + "media types".]]> ウェブサイトを参照する @@ -181,14 +240,32 @@ ]]> カルチャ名 + + ユーザー名を入力... + パスワードを入力... + %0%と命名します... + ここに名称を入力してください... + 検索する... + 条件で絞り込む... + タグを追加します... + + ルートノードとして許可する + これを有効にするとコンテンツとメディアツリーのルートレベルに作成することができます 子ノードとして許可するタイプ + Document Type Compositions 新規作成 削除 説明 新規見出し 見出し サムネイル + リストビューを有効にする + 子ノードをツリーに表示せずにリストビューに表示します + 現在のリストビュー + 有効なリストビューデータタイプ + カスタムリストビューを作成する + カスタムリストビューを削除する 値の前に追加 @@ -217,7 +294,8 @@ %0% は正しい書式ではありません - 注意! CodeMirrorが設定で有効かされていますが、 Internet Explorerでは不安定なので無効化してください。 + 指定されたファイルタイプは管理者のみに許可されます + 注意! CodeMirrorが設定で有効化されていますが、 Internet Explorerでは不安定なので無効化してください。 新しいプロパティ型のエイリアスと名前の両方を設定してください! 特定のファイルまたはフォルタの読み込み/書き込みアクセスに問題があります タイトルを入力してください @@ -232,10 +310,12 @@ このセルは結合されたものではないので分離する事はできません。 XSLTソースにエラーがあります 1つ以上のエラーがあるのでこのXSLTは保存できませんでした + このプロパティに使用されているデータタイプにエラーがあります Umbracoについて アクション + アクション選択 追加 エイリアス 確かですか? @@ -285,6 +365,7 @@ ログアウト マクロ 移動 + もっと 名前 新規 次へ @@ -304,6 +385,7 @@ 残り 名前の変更 更新 + この項目は必須です 再試行 許可 検索 @@ -312,8 +394,9 @@ 送信後にページを表示 サイズ 並べ替え + Submit - 探す型... + 検索... 更新 アップグレード @@ -327,6 +410,9 @@ はい フォルダー + 検索結果 + Reorder + I am done reordering 背景色 @@ -348,10 +434,10 @@ ]]> 次へを押して続行してください。]]> データベースを見つけられません!"web.config"ファイルの中の"接続文字列"を確認してください。

    -

    続行するには"web.config"ファイルを編集(Visual Studioないし使い慣れたテキストエディタで)し、下の方にスクロールし、"UmbracoDbDSN"という名前のキーでデータベースの接続文字列を追加して保存します。

    +

    続行するには"web.config"ファイルを編集(Visual Studioないし使い慣れたテキストエディタで)し、下の方にスクロールし、"umbracoDbDSN"という名前のキーでデータベースの接続文字列を追加して保存します。

    再施行ボタンをクリックして - 続けます。
    + 続けます。
    より詳細にはこちらの web.config を編集します。

    ]]>
    必要ならISPに連絡するなどしてみてください。 @@ -380,7 +466,7 @@

    ]]>
    始めに、ビデオによる解説を見ましょう - 次へボタンをクリック(またはweb.configのUmbracoConfigurationStatusを編集)すると、あなたはここに示されるこのソフトウェアのライセンスを承諾したと見做されます。注意として、UmbracoはMITライセンスをフレームワークへ、フリーウェアライセンスをUIへ、それぞれ異なる2つのライセンスを採用しています。 + 次へボタンをクリック(またはweb.configのumbracoConfigurationStatusを編集)すると、あなたはここに示されるこのソフトウェアのライセンスを承諾したと見做されます。注意として、UmbracoはMITライセンスをフレームワークへ、フリーウェアライセンスをUIへ、それぞれ異なる2つのライセンスを採用しています。 まだインストールは完了していません。 影響するファイルとフォルダ Umbracoに必要なアクセス権の設定についての詳細はこちらをどうぞ @@ -408,7 +494,7 @@ スクラッチから始めたい どうしたらいいの?) + (どうしたらいいの?) 後からRunwayをインストールする事もできます。そうしたくなった時は、Developerセクションのパッケージへどうぞ。 ]]> Umbracoプラットフォームのクリーンセットアップが完了しました。この後はどうしますか? @@ -444,7 +530,7 @@ Runwayをインストールして作られた新しいウェブサイトがど 我々の認めるコミュニティから手助けを得られるでしょう。どうしたら簡単なサイトを構築できるか、どうしたらパッケージを使えるかについてのビデオや文書、またUmbracoの用語のクイックガイドも見る事ができます。]]> Umbraco %0% のインストールは完了、準備が整いました /web.config fileを手作業で編集し、'%0%'の下にあるUmbracoConfigurationStatusキーを設定してください。]]> + /web.config fileを手作業で編集し、'%0%'の下にあるumbracoConfigurationStatusキーを設定してください。]]> 今すぐ開始できます。
    もしUmbracoの初心者なら、 私たちの初心者向けのたくさんの情報を参考にしてください。]]>
    Umbracoの開始 @@ -453,7 +539,7 @@ Runwayをインストールして作られた新しいウェブサイトがど Umbraco Version 3 Umbraco Version 4 見る - Umbraco %0% の新規インストールまたは3.0からの更新について設定方法を案内します。 + umbraco %0% の新規インストールまたは3.0からの更新について設定方法を案内します。

    "次へ"を押してウィザードを開始します。]]>
    @@ -466,7 +552,16 @@ Runwayをインストールして作られた新しいウェブサイトがど 作業を保存して今すぐ更新 - © 2001 - %0%
    umbraco.com

    ]]>
    + Happy super sunday + Happy manic monday + Happy tubular tuesday + Happy wonderful wednesday + Happy thunder thursday + Happy funky friday + Happy caturday + ウェブサイトにログインします。 + セッションタイムアウトしました。 + © 2001 - %0%
    umbraco.org

    ]]>
    Umbraco にようこそ。ユーザー名とパスワードを入力してください: @@ -549,7 +644,7 @@ Runwayをインストールして作られた新しいウェブサイトがど 本当にアンインストールしますか パッケージのアンインストールが終了しました パッケージが正常にアンインストールされました - パッケージのアンンストール + パッケージのアンインストール 注意: 全ての、文書やメディアなどに依存したアイテムを削除する場合はそれらの作業を一端止めてからアンインストールしなければシステムが不安定になる恐れがあります。 疑問点などあればパッケージの作者へ連絡してください。]]> @@ -558,6 +653,7 @@ Runwayをインストールして作られた新しいウェブサイトがど 更新の手順 このパッケージの更新があります。Umbracoのパッケージリポジトリから直接ダウンロードできます。 パッケージのバージョン + パッケージのバージョン履歴 パッケージのウェブサイトを見る @@ -585,14 +681,21 @@ Runwayをインストールして作られた新しいウェブサイトがど 単一のログインとパスワードで単純に保護したい場合に適します - + + + + - - + ]]>
    非公開の子ページも含めます 公開を進めています - 少々お待ちください... %1% ページ中 %0% ページが公開されました... @@ -603,18 +706,29 @@ Runwayをインストールして作られた新しいウェブサイトがど このページとその全ての子ページも公開したければ 全ての子ページを公開 をチェック。 ]]>
    + + 設定済みの色はありません。 + 外部リンクを追加 内部リンクを追加 追加 タイトル + キャプション表示を入力 + 内部リンクを選択 + 外部リンクを入力 + リンクを入力 内部ページ + リンク URL 下に移動 上に移動 新規ウィンドウで開く リンクを削除 + + リセット + 現在の版 の文字列は以前の版にはない部分で、緑の文字列は以前の版にのみある部分です。]]> @@ -640,6 +754,15 @@ Runwayをインストールして作られた新しいウェブサイトがど 統計 翻訳 ユーザー + ヘルプ + フォーム + アナリティクス + + + 該当のヘルプへ + ヘルプトピック: + ヘルプの動画: + 動画によるチュートリアル 既定のテンプレート @@ -649,6 +772,7 @@ Runwayをインストールして作られた新しいウェブサイトがど ノードのタイプ タイプ スタイルシート + スクリプト スタイルシートのプロパティ タブ タブの名前 @@ -656,6 +780,9 @@ Runwayをインストールして作られた新しいウェブサイトがど マスターコンテンツタイプが有効 このコンテンツタイプの使用 マスターコンテンツタイプについては、マスターコンテンツタイプからのタブは表示されず、マスターコンテンツタイプでのみ編集することができます。 + このタブにはプロパティが定義されていません、上部のリンクから新しいプロパティを作成してください + マスタードキュメントタイプ + テンプレートを作成する ソートが完了しました。 @@ -663,6 +790,10 @@ Runwayをインストールして作られた新しいウェブサイトがど
    並び替え中はウィンドウを閉じないでください。]]>
    + 失敗しました + 不十分なユーザー権限により操作を完了できませんでした + キャンセルされました + サードパーティのアドインにより操作はキャンセルされました サードパーティのアドインにより公開はキャンセルされました プロパティの方は既に存在しています プロパティの型を作成しました @@ -684,6 +815,8 @@ Runwayをインストールして作られた新しいウェブサイトがど 変更を適用する為に公開する事を忘れないでください 承認へ送りました 変更は承認へと送られます + メディアを保存しました + メディアをエラーなく保存しました メンバーを保存しました スタイルシートのプロパティを保存しました スタイルシートを保存しました @@ -729,15 +862,59 @@ Runwayをインストールして作られた新しいウェブサイトがど コンテンツ領域プレースホルダーの挿入 dictionary item の挿入 マクロの挿入 - Umbraco ページフィールドの挿入 + umbraco ページフィールドの挿入 マスターテンプレート - Umbraco テンプレートタグのクイックガイド + umbraco テンプレートタグのクイックガイド テンプレート + + + 挿入するアイテムを選択する + Choose a layout + ここからレイアウトを選択します + 最初の要素を追加します]]> + Drop content + Settings applied + + This content is not allowed here + This content is allowed here + + クリックして埋め込む + クリックして画像を挿入する + キャプション... + ここに記入する... + + レイアウト + レイアウトは通常1つまたは2つの異なるレイアウトを必要とする、グリッドエディタの全体的な作業エリアです + レイアウトを追加する + 追加のセクションの横幅を設定し、レイアウトを調整する + + 行の構成 + 定義された構成の行が水平に配置されます + 行の構成を追加 + 追加のセルのセル幅を設定することで調整します + + + グリッドレイアウトの列を合計した数 + + 設定 + 編集者が設定できる項目 + + + スタイル + 編集者が設定できるスタイル + + 入力されたJSONが正しい場合のみ設定が保存されます + + すべてのエディタを許可する + すべての行の構成を許可する + + 代替フィールド 代替テキスト 大文字小文字変換 + エンコーディング フィールドの選択 改行コードの変換 改行コードをhtmlタグ &lt;br&gt; に変換する @@ -813,7 +990,7 @@ Runwayをインストールして作られた新しいウェブサイトがど キャッシュの参照 ごみ箱 パッケージの作成 - データ型 + データ型 ディクショナリ インストール済のパッケージ スキンのインストール @@ -823,10 +1000,10 @@ Runwayをインストールして作られた新しいウェブサイトがど マクロ メディアタイプ メンバー - メンバーのグループ + メンバーのグループ 役割 - メンバーの種類 - ドキュメントタイプ + メンバーの種類 + ドキュメントタイプ パッケージ パッケージ Python ファイル @@ -838,6 +1015,7 @@ Runwayをインストールして作られた新しいウェブサイトがど スタイルシート テンプレート XSLT ファイル + アナリティクス 新しい更新があります @@ -864,6 +1042,7 @@ Runwayをインストールして作られた新しいウェブサイトがど セクション Umbracoへのアクセスを無効にする パスワード + パスワードのリセット パスワードが変更されました! 新しいパスワードの確認 新しいパスワードの入力 @@ -882,5 +1061,9 @@ Runwayをインストールして作られた新しいウェブサイトがど ユーザーの種類 ユーザーの種類 投稿者 + 翻訳者 + あなたのプロフィール + あなたの最新の履歴 + セッションの期限 diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/ko.xml b/src/Umbraco.Web.UI/umbraco/config/lang/ko.xml index b3f6a3a384..247d1a85c8 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/ko.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/ko.xml @@ -309,6 +309,7 @@ 전송된 페이지보기 사이즈 정렬 + Submit 타입 검색유형... 위로 @@ -323,6 +324,8 @@ 환영합니다... 너비 + Reorder + I am done reordering 배경색 @@ -698,6 +701,45 @@ Umbraco 템플릿태그 퀵가이드 템플릿 + + Choose type of content + Choose a layout + Add a row + Add content + Drop content + Settings applied + + This content is not allowed here + This content is allowed here + + Click to embed + Click to insert image + Image caption... + Write here... + + Grid Layouts + Layouts are the overall work area for the grid editor, usually you only need one or two different layouts + Add Grid Layout + Adjust the layout by setting column widths and adding additional sections + Row configurations + Rows are predefined cells arranged horizontally + Add row configuration + Adjust the row by setting cell widths and adding additional cells + + Columns + Total combined number of columns in the grid layout + + Settings + Configure what settings editors can change + + Styles + Configure what styling editors can change + + Settings will only save if the entered json configuration is valid + + Allow all editors + Allow all row configurations + 대체 필드 대체 글꼴 @@ -777,7 +819,7 @@ 캐시 브라우저 휴지통 생성된 패키지 - 데이터 타입 + 데이터 타입 사전 설치된 패키지 TRANSLATE ME: 'Install skin' @@ -787,10 +829,10 @@ 매크로 미디어 타입 구성원 - 구성원 그룹 + 구성원 그룹 역할 - 구성원 유형 - 문서 타입 + 구성원 유형 + 문서 타입 패키지 패키지 Python 파일 diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml b/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml index 05e9de462e..01d49a18c1 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml @@ -16,8 +16,6 @@ Uitschakelen Prullenbak leegmaken Documenttype exporteren - Exporteer naar .NET - Exporteer naar .NET Documenttype importeren Package importeren Aanpassen in Canvas @@ -51,11 +49,11 @@ Nieuw domein '%0%' is aangemaakt Domein '%0%' is verwijderd Domein '%0' is al aanwezig + Domein '%0%' is bijgewerkt + Bewerk huidige domeinen
    Zgn. 'one-level' paden in domeinen worden ondersteund, bijv. "example.com/en". Echter, ze zouden moeten worden vermeden. Gebruik bij voorkeur de cultuurinstelling hierboven.]]>
    - Domein '%0%' is bijgewerkt - Bewerk huidige domeinen Overerven Cultuur of erf de cultuur over van de ouder nodes. Zal ook van toepassing
    @@ -87,6 +85,7 @@ Macro invoegen Afbeelding invoegen Relaties wijzigen + Terug naar overzicht Opslaan Opslaan en publiceren Opslaan en verzenden voor goedkeuring @@ -117,6 +116,7 @@ Alleen alternatieve types geldig voor de huidige locatie worden weergegeven. + Is gepubliceerd Over deze pagina Alternatieve link (hoe zou jij de foto beschrijven via de telefoon) @@ -133,6 +133,7 @@ Dit item is gewijzigd na publicatie Dit item is niet gepubliceerd Laatst gepubliceerd op + Nog geen items om weer te geven. Mediatype Link naar media item(s) Ledengroep @@ -152,11 +153,12 @@ Om nodes te sorteren, sleep de nodes of klik op één van de kolomtitels. Je kan meerdere nodes tegelijk selecteren door de "shift"- of "control"knop in te drukken tijdens het selecteren. Statistieken Titel (optioneel) + Alternatieve tekst (optioneel) Type Depubliceren Laatst gewijzigd Date/time this document was edited - Bestand verwijderen + Bestand(en) verwijderen Link naar het document Lid van groep(en) Geen lid van groep(en) @@ -170,7 +172,7 @@ Waar wil je de nieuwe %0% aanmaken? - Aanmaken op + Aanmaken onder Kies een type en een titel "Documenttypes".]]> @@ -241,16 +243,20 @@ Cultuurnaam - Type je gebruikersnaam - Type je wachtwoord + Typ je gebruikersnaam + Typ je wachtwoord Benoem de %0%... - Type een naam... - Type om te zoeken... - Type om te filteren... + Typ een naam... + Typ om te zoeken... + Typ om te filteren... + Typ om tags toe te voegen (druk op enter na elke tag)... + Toestaan op root-niveau + Wanneer aangevinkt dan mag dit document type aangemaakt worden op het root-niveau van content of media trees. Toegelaten subnodetypes + Document Type Composities Nieuw Tab verwijderen Omschrijving @@ -258,6 +264,11 @@ Tab Miniatuur Lijstweergave inschakelen + Stelt het content item in zodat een sorteer- en zoekbare lijstweergave van onderliggende nodes wordt getoond. De onderliggende nodes worden niet in de tree getoond + Huidige lijstweergave + De actieve data type in lijstweergave + Maak een aangepaste lijstweergave + Verwijder aangepaste lijstweergave Prevalue toevoegen @@ -386,6 +397,7 @@ Toon pagina bij versturen Formaat Sorteren + Submit Type Type om te zoeken... Omhoog @@ -401,6 +413,8 @@ Breedte Ja Map + Reorder + I am done reordering Zoekresultaten @@ -501,7 +515,6 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je Sessie is verlopen © 2001 - %0%
    umbraco.com

    ]]>
    - Welkom bij Umbraco, geef je gebruikersnaam en wachtwoord op in de onderstaande velden: Dashboard @@ -662,6 +675,9 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je Open in nieuw venster Verwijder link + + Reset + Huidige versie Rode tekst wordt niet getoond in de geselecteerde versie , groen betekent toegevoegd]]> @@ -690,6 +706,8 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je Umbraco Contour Help + Formulieren + Analytics Standaard template @@ -708,6 +726,8 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je Dit inhoudstype gebruikt als basis inhoudstype. Tabs van basis inhoudstypes worden niet getoond en kunnen alleen worden aangepast op het basis inhoudstype zelf Geen eigenschappen gedefinieerd op dit tabblad. Klik op de link "voeg een nieuwe eigenschap" aan de bovenkant om een ​​nieuwe eigenschap te creëren. + Master Document Type + Maak een bijbehorend template Sorteren gereed. @@ -788,36 +808,46 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je Quick Guide voor Umbraco template tags Sjabloon - - Item toevoegen - Een rij aan de lay-out toevoegen - teken onderaan en voeg je eerste item toe]]> + + Item toevoegen + Choose a layout + Een rij aan de lay-out toevoegen + teken onderaan en voeg je eerste item toe]]> + Drop content + Settings applied - Grid lay-outs - Lay-outs zijn het globale werkgebied voor de grid editor. Je hebt meestal maar één of twee verschillende lay-outs nodig - Een grid layout toevoegen - De lay-out aanpassen door de kolombreedte aan te passen en extra kolommen toe te voegen + This content is not allowed here + This content is allowed here - Rijconfiguratie - Rijen zijn voorgedefinieerde cellen die horizontaal zijn gerangschikt - Een rijconfiguratie toevoegen - De rijconfiguratie aanpassen door de breedte van de cel in te stellen en extra cellen toe te voegen + Klik om een item te embedden + Klik om een afbeelding in te voegen + Afbeelding ondertitel... + Typ hier...... + Grid lay-outs + Lay-outs zijn het globale werkgebied voor de grid editor. Je hebt meestal maar één of twee verschillende lay-outs nodig + Een grid layout toevoegen + De lay-out aanpassen door de kolombreedte aan te passen en extra kolommen toe te voegen - Kolommen - Het totaal aantal gecombineerde kolommen in de grid layout + Rijconfiguratie + Rijen zijn voorgedefinieerde cellen die horizontaal zijn gerangschikt + Een rijconfiguratie toevoegen + De rijconfiguratie aanpassen door de breedte van de cel in te stellen en extra cellen toe te voegen - Instellingen - Configureren welke instellingen de editors kunnen aanpassen + Kolommen + Het totaal aantal gecombineerde kolommen in de grid layout + + Instellingen + Configureren welke instellingen de editors kunnen aanpassen - Styles - Configureren welke stijlen de editors kunnen aanpassen + Styles + Configureren welke stijlen de editors kunnen aanpassen - De instellingen worden enkel bewaard indien de ingevoerde Json geldig is + De instellingen worden enkel bewaard indien de ingevoerde Json geldig is - Alle editors toelaten - Alle rijconfiguraties toelaten - + Alle editors toelaten + Alle rijconfiguraties toelaten + Alternatief veld Alternatieve tekst @@ -894,7 +924,7 @@ Om een vertalingstaak te sluiten, ga aub naar het detailoverzicht en klik op de Cachebrowser Prullenbak Gemaakte packages - Datatypes + Datatypes Woordenboek Geïnstalleerde packages Installeer skin @@ -904,10 +934,10 @@ Om een vertalingstaak te sluiten, ga aub naar het detailoverzicht en klik op de Macro's Mediatypes Leden - Ledengroepen + Ledengroepen Rollen - Ledentypes - Documenttypes + Ledentypes + Documenttypes Packages Packages Python-bestanden @@ -964,6 +994,7 @@ Om een vertalingstaak te sluiten, ga aub naar het detailoverzicht en klik op de Gebruikerstype Gebruikerstypes Auteur + Wijzig Je profiel Je recente historie diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/no.xml b/src/Umbraco.Web.UI/umbraco/config/lang/no.xml index f5d5fe9773..184112d998 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/no.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/no.xml @@ -1,4 +1,4 @@ - + The Umbraco community @@ -8,6 +8,7 @@ Angi domene Revisjoner Bla gjennom + Skift dokumenttype Kopier Opprett Opprett pakke @@ -15,16 +16,18 @@ Deaktiver Tøm papirkurv Eksporter dokumenttype - Importer documenttype + Importer dokumenttype Importer pakke Rediger i Canvas - Lukk Umbraco + Logg av Flytt - Varsling + Varslinger Offentlig tilgang Publiser + Avpubliser Oppdater noder Republiser hele siten + Gjenopprett Rettigheter Reverser Send til publisering @@ -33,32 +36,35 @@ Send til publisering Oversett Oppdater - Avpubliser + Standard verdi - Legg til domene - Domene - Domene '%0%' er nå opprettet og tilknyttet siden - Domenet '%0%' er nå slettet - Domenet '%0%' er allerede tilknyttet - Gyldige domenenavn er: "eksempel.no", "www.eksempel.no", "eksempel.no:8080" eller "https://www.eksempel.no/".<br/><br/>Stier med ett nivå støttes, f.eks. "eksempel.com/no". Imidlertid bør det unngås. Bruk heller språkinnstillingen over. - Domenet '%0%' er nå oppdatert - eller rediger eksisterende domener Ingen tilgang. + Legg til domene Fjern Ugyldig node. Ugyldig domeneformat. Domene er allerede tilknyttet. Språk + Domene + Domene '%0%' er nå opprettet og tilknyttet siden + Domenet '%0%' er nå slettet + Domenet '%0%' er allerede tilknyttet + Domenet '%0%' er nå oppdatert + eller rediger eksisterende domener +
    Stier med ett nivå støttes, f.eks. "eksempel.com/no". Imidlertid bør det unngås. Bruk heller språkinnstillingen over.]]>
    Arv Språk - Sett språk for underordnede noder eller arv språk fra overordnet.<br/>Vil også gjelde denne noden, med mindre et underordnet domene også gjelder. + Vil også gjelde denne noden, med mindre et underordnet domene også gjelder.]]> Domener Viser for + Velg + Velg gjeldende mappe + Gjør noe annet Fet Reduser innrykk Sett inn skjemafelt @@ -76,30 +82,57 @@ Sett inn makro Sett inn bilde Rediger relasjoner + Tilbake til listen Lagre Lagre og publiser Lagre og send til publisering Forhåndsvis + Forhåndsvisning er deaktivert siden det ikke er angitt noen mal Velg formattering Vis stiler Sett inn tabell - Forhåndsvisning er deaktivert siden det ikke er angitt noen mal + + + For å endre det valge innholdets dokumenttype, velger du først en ny dokumenttype som er gyldig på gjeldende plassering. + Kontroller deretter at alle egenskaper blir overført riktig til den nye dokumenttypen og klikk på Lagre. + Innholdet har blitt republisert. + Nåværende egenskap + Nåværende type + Du kan ikke endre dokumenttype, ettersom det ikke er andre gyldige dokumenttyper på denne plasseringen. + Dokumenttype endret + Overfør egenskaper + Overfør til egenskap + Ny mal + Ny type + ingen + Innhold + Velg ny dokumenttype + Dokumenttypen på det valgte innhold ble endret til [new type], og følgende egenskaper ble overført: + til + Overføringen av egenskaper kunne ikke fullføres da en eller flere egenskaper er satt til å bli overført mer enn en gang. + Kun andre dokumenttyper som er gyldige for denne plasseringen vises. + Publisert Om siden - Alternativ lenke + Alias (hvordan du ville beskrevet bildet over telefon) Alternative lenker Klikk for å redigere denne noden Opprettet av + Opprinnelig forfatter + Oppdatert av Opprettet den + Tidspunkt for opprettelse Dokumenttype Redigerer Utløpsdato Denne noden er endret siden siste publisering Denne noden er enda ikke publisert Sist publisert + Det er ingen elementer å vise i listen. Mediatype + Link til media Medlemsgruppe Rolle Medlemstype @@ -107,26 +140,42 @@ Sidetittel Egenskaper Dette dokumentet er publisert, men ikke synlig ettersom den overliggende siden '%0%' ikke er publisert + Intern feil: dokumentet er publisert men finnes ikke i hurtigbuffer Publisert Publiseringsstatus Publiseringsdato + Dato for avpublisering Fjern dato Sorteringsrekkefølgen er oppdatert Trekk og slipp nodene eller klikk på kolonneoverskriftene for å sortere. Du kan velge flere noder ved å holde shift eller control tastene mens du velger. Statistikk Tittel (valgfri) + Alternativ tekst (valgfri) Type Avpubliser Sist endret + Tidspunkt for siste endring Fjern fil Lenke til dokument - Link til media - Intern feil: dokumentet er publisert men finnes ikke i hurtigbuffer + Medlem av gruppe(ne) + Ikke medlem av gruppe(ne) + Undersider + Åpne i vindu + + + Klikk for å laste opp + Slipp filene her... + + + Opprett et nytt medlem + Alle medlemmer Hvor ønsker du å oprette den nye %0% Opprett under Velg en type og skriv en tittel + "dokumenttyper".]]> + "mediatyper".]]> Til ditt nettsted @@ -168,7 +217,7 @@ Innholdet i papirkurven blir nå slettet. Vennligst ikke lukk dette vinduet mens denne operasjonen foregår Papirkurven er nå tom Når elementer blir slettet fra papirkurven vil de være slettet for alltid - <a target='_blank' href='http://regexlib.com'>regexlib.com</a> tjenesten opplever for tiden problemer som vi ikke har kontroll over. Vi beklager denne ubeleiligheten. + regexlib.com tjenesten opplever for tiden problemer som vi ikke har kontroll over. Vi beklager denne ubeleiligheten.]]> Søk etter et regulært uttrykk for å legge inn validering til et felt. Eksempel: 'email, 'zip-code' 'url' Fjern makro Obligatorisk @@ -177,24 +226,42 @@ Hurtigbufferen for siden vil bli oppdatert. Alt publisert innhold vil bli oppdatert, mens upublisert innhold vil forbli upublisert. Antall kolonner Antall rader - <strong>Sett en plassholder-ID</strong><br/>Ved å sette en ID på plassholderen kan du legge inn innhold i denne malen fra underliggende maler, ved å referere denne ID'en ved hjelp av et <code>&lt;asp:content /&gt;</code> element. - <strong>Velg en plassholder ID</strong> fra listen under. Du kan bare velge ID'er fra den gjeldende malens overordnede mal. + Sett en plassholder-ID
    Ved å sette en ID på plassholderen kan du legge inn innhold i denne malen fra underliggende maler, ved å referere denne ID'en ved hjelp av et <asp:content /> element.]]>
    + Velg en plassholder ID fra listen under. Du kan bare velge ID'er fra den gjeldende malens overordnede mal.]]> Klikk på bildet for å se det i full størrelse Velg punkt Se buffret node - Rediger de forskjellige språkversjonene for ordbokelementet '<em>%0%</em>' under.<br/>Du kan legge til flere språk under 'språk' i menyen til venstre. + %0%' under.
    Du kan legge til flere språk under 'språk' i menyen til venstre.]]>
    Språk + + Skriv inn ditt brukernavn + Skriv inn ditt passord + Navngi %0%... + Skriv inn navn... + Søk... + Filtrer... + Skriv inn nøkkelord (trykk på Enter etter hvert nøkkelord)... + + Tillat på rotnivå + Kun dokumenttyper med denne innstillingen aktivert kan opprettes på rotnivå under Innhold og Mediearkiv Tillatte underordnede noder + Sammensetting av dokumenttyper Opprett Slett arkfane Beskrivelse Ny arkfane Arkfane Miniatyrbilde + Aktiver listevisning + Viser undersider i en søkbar liste, undersider vises ikke i innholdstreet + Gjeldende listevisning + Den aktive listevisningsdatatypen + Opprett brukerdefinert listevisning + Fjern brukerdefinert listevisning Legg til forhåndsverdi @@ -223,6 +290,7 @@ %0% er ikke i et korrekt format + Filtypen er deaktivert av administrator NB! Selv om CodeMirror er aktivert i konfigurasjon er det deaktivert i Internet Explorer pga. ustabilitet. Fyll ut både alias og navn på den nye egenskapstypen! Det er et problem med lese/skrive rettighetene til en fil eller mappe @@ -238,16 +306,17 @@ Du kan ikke dele en celle som allerede er delt. Feil i XSLT kode XSLT ble ikke lagret på grunn av feil i koden - Filtypen er deaktivert av administrator + Det er et problem dem datatypen som brukes til denne egenskapen. Kontroller innstillingene og prøv igjen. Om Handling + Muligheter Legg til Alias Er du sikker? Ramme - eller + av Avbryt Cellemargin Velg @@ -292,6 +361,7 @@ Logg ut Makro Flytt + Mer Navn Ny Neste @@ -311,6 +381,7 @@ Gjenværende Gi nytt navn Forny + Påkrevd Prøv igjen Rettigheter Søk @@ -319,6 +390,7 @@ Hvilken side skal vises etter at skjemaet er sendt Størrelse Sorter + Submit Type Søk... Opp @@ -334,6 +406,7 @@ Bredde Ja Mappe + Søkeresultater Bakgrunnsfarge @@ -350,43 +423,43 @@ Kunne ikke lagre Web.Config-filen. Vennligst endre databasens tilkoblingsstreng manuelt. Din database er funnet og identifisert som Databasekonfigurasjon - Klikk <strong>installer</strong>-knappen for å installere Umbraco %0% databasen - Umbraco %0% har nå blitt kopiert til din database. Trykk <strong>Neste</strong> for å fortsette. - <p>Databasen ble ikke funnet! Vennligst sjekk at informasjonen i "connection string" i "web.config"-filen er korrekt.</p><p>For å fortsette, vennligst rediger "web.config"-filen (bruk Visual Studio eller din favoritteditor), rull ned til bunnen, og legg til tilkoblingsstrengen for din database i nøkkelen "umbracoDbDSN" og lagre filen.</p><p>Klikk <strong>prøv på nytt</strong> når du er ferdig.<br /> <a href="http://our.umbraco.org/documentation/Using-Umbraco/Config-files/webconfig7" target="_blank">Mer informasjon om redigering av web.config her.</a></p> - For å fullføre dette steget, må du vite en del informasjon om din database server ("tilkoblingsstreng").<br/> Vennligst kontakt din ISP om nødvendig. Hvis du installerer på en lokal maskin eller server, må du kanskje skaffe informasjonen fra din systemadministrator. - <p> Trykk på knappen <strong>oppgrader</strong> for å oppgradere databasen din til Umbraco %0%</p> <p> Ikke vær urolig - intet innhold vil bli slettet og alt vil fortsette å virke etterpå! </p> - Databasen din har blitt oppgradert til den siste utgaven, %0%.<br/>Trykk <strong>Neste</strong> for å fortsette. - Databasen din er av nyeste versjon! Klikk <strong>neste</strong> for å fortsette konfigurasjonsveiviseren - <strong>Passordet til standardbrukeren må endres! - <strong>Standardbrukeren har blitt deaktivert eller har ingen tilgang til Umbraco!</strong></p><p>Ingen videre handling er nødvendig. Klikk <b>neste</b> for å fortsette. - <strong>Passordet til standardbrukeren har blitt forandret etter installasjonen!</strong></p><p>Ingen videre handling er nødvendig. Klikk <strong>Neste</strong> for å fortsette. + installer-knappen for å installere Umbraco %0% databasen]]> + Neste for å fortsette.]]> + Databasen ble ikke funnet! Vennligst sjekk at informasjonen i "connection string" i "web.config"-filen er korrekt.

    For å fortsette, vennligst rediger "web.config"-filen (bruk Visual Studio eller din favoritteditor), rull ned til bunnen, og legg til tilkoblingsstrengen for din database i nøkkelen "umbracoDbDSN" og lagre filen.

    Klikk prøv på nytt når du er ferdig.
    Mer informasjon om redigering av web.config her.

    ]]>
    + Vennligst kontakt din ISP om nødvendig. Hvis du installerer på en lokal maskin eller server, må du kanskje skaffe informasjonen fra din systemadministrator.]]> + Trykk på knappen oppgrader for å oppgradere databasen din til Umbraco %0%

    Ikke vær urolig - intet innhold vil bli slettet og alt vil fortsette å virke etterpå!

    ]]>
    + Trykk Neste for å fortsette.]]> + neste for å fortsette konfigurasjonsveiviseren]]> + Passordet til standardbrukeren må endres!]]> + Standardbrukeren har blitt deaktivert eller har ingen tilgang til Umbraco!

    Ingen videre handling er nødvendig. Klikk neste for å fortsette.]]> + Passordet til standardbrukeren har blitt forandret etter installasjonen!

    Ingen videre handling er nødvendig. Klikk Neste for å fortsette.]]> Passordet er blitt endret! - <p> Umbraco skaper en standard bruker med login <strong> ( "admin") </ strong> og passord <strong> ( "default") </ strong>. Det er <strong> viktig </ strong> at passordet er endret til noe unikt. </ p> <p> Dette trinnet vil sjekke standard brukerens passord og foreslår hvis det må skiftes </ p> + Umbraco skaper en standard bruker med login ( "admin") og passord ( "default") . Det er viktig at passordet er endret til noe unikt.

    Dette trinnet vil sjekke standard brukerens passord og foreslår hvis det må skiftes ]]> Få en god start med våre introduksjonsvideoer Ved å klikke på Neste-knappen (eller endre UmbracoConfigurationStatus i Web.config), godtar du lisensen for denne programvaren som angitt i boksen nedenfor. Legg merke til at denne Umbraco distribusjon består av to ulike lisenser, åpen kilde MIT lisens for rammen og Umbraco frivareverktøy lisens som dekker brukergrensesnittet. Ikke installert. Berørte filer og mapper Mer informasjon om å sette opp rettigheter for Umbraco her Du må gi ASP.NET brukeren rettigheter til å endre de følgende filer og mapper - <strong>Rettighetene er nesten perfekt satt opp!</strong><br/><br/> Du kan kjøre Umbraco uten problemer, men du vil ikke være i stand til å installere de anbefalte pakkene for å utnytte Umbraco fullt ut. + Rettighetene er nesten perfekt satt opp!

    Du kan kjøre Umbraco uten problemer, men du vil ikke være i stand til å installere de anbefalte pakkene for å utnytte Umbraco fullt ut.]]> Hvordan løse problemet Klikk her for å lese tekstversjonen - Se vår <strong>innføringsvideo</strong> om å sette opp rettigheter for Umbraco eller les tekstversjonen. - <strong>Rettighetsinnstillingene kan være et problem!</strong><br/><br/> Du kan kjøre Umbraco uten problemer, men du vil ikke være i stand til å installere de anbefalte pakkene for å utnytte Umbraco fullt ut. - <strong>Rettighetsinstillingene er ikke klargjort for Umbraco!</strong><br/><br/> For å kunne kjøre Umbraco, må du oppdatere rettighetsinnstillingene dine. - <strong>Rettighetsinnstillingene er perfekt!</strong><br/><br/>Du er klar for å kjøre Umbraco og installere pakker! + innføringsvideo
    om å sette opp rettigheter for Umbraco eller les tekstversjonen.]]> + Rettighetsinnstillingene kan være et problem!


    Du kan kjøre Umbraco uten problemer, men du vil ikke være i stand til å installere de anbefalte pakkene for å utnytte Umbraco fullt ut.]]> + Rettighetsinstillingene er ikke klargjort for Umbraco!

    For å kunne kjøre Umbraco, må du oppdatere rettighetsinnstillingene dine.]]>
    + Rettighetsinnstillingene er perfekt!

    Du er klar for å kjøre Umbraco og installere pakker!]]>
    Løser mappeproblem Følg denne linken for mer informasjon om problemer med ASP.NET og oppretting av mapper Konfigurerer mappetillatelser - Umbraco trenger skrive/endre tilgang til enkelte mapper for å kunne lagre filer som bilder og PDF-dokumenter. Den lagrer også midlertidig data (aka: hurtiglager) for å øke ytelsen på websiden din. + Jeg ønsker å starte fra bunnen. - Din website er helt tom for øyeblikket. Dette er perfekt hvis du vil begynne helt forfra og lage dine egne dokumenttyper og maler. (<a href="http://Umbraco.tv/documentation/videos/for-site-builders/foundation/document-types">lær hvordan</a>) Du kan fortsatt velge å installere Runway senere. Vennligst gå til Utvikler-seksjonen og velg Pakker. + lær hvordan) Du kan fortsatt velge å installere Runway senere. Vennligst gå til Utvikler-seksjonen og velg Pakker.]]> Du har akkurat satt opp en ren Umbraco plattform. Hva vil du gjøre nå? Runway er installert - Du har nå fundamentet på plass. Velg hvilke moduler du ønsker å installer på toppen av det.<br/> Dette er vår liste av anbefalte moduler- Kryss av de du ønsker å installere, eller se den<a href="#" onclick="toggleModules(); return false;" id="toggleModuleList">fulle listen av moduler</a> + Dette er vår liste av anbefalte moduler- Kryss av de du ønsker å installere, eller se denfulle listen av moduler ]]> Bare anbefalt for erfarne brukere Jeg vil starte med en enkel webside - <p> "Runway" er en enkel webside som utstyrer deg med noen grunnleggende dokumenttyper og maler. Veiviseren kan sette opp Runway for deg automatisk, men du kan enkelt endre, utvide eller slette den. Runway er ikke nødvendig, og du kan enkelt bruke Umbraco uten den. Imidlertidig tilbyr Runway et enkelt fundament basert på de beste metodene for å hjelpe deg i gang fortere enn noensinne. Hvis du velger å installere Runway, kan du også velge blant grunnleggende byggeklosser kalt Runway Moduler for å forøke dine Runway-sider. </p> <small> <em>Sider inkludert i Runway:</em> Hjemmeside, Komme-i-gang, Installere moduler.<br /> <em>Valgfrie Moduler:</em> Toppnavigasjon, Sidekart, Kontakt, Galleri. </small> + "Runway" er en enkel webside som utstyrer deg med noen grunnleggende dokumenttyper og maler. Veiviseren kan sette opp Runway for deg automatisk, men du kan enkelt endre, utvide eller slette den. Runway er ikke nødvendig, og du kan enkelt bruke Umbraco uten den. Imidlertidig tilbyr Runway et enkelt fundament basert på de beste metodene for å hjelpe deg i gang fortere enn noensinne. Hvis du velger å installere Runway, kan du også velge blant grunnleggende byggeklosser kalt Runway Moduler for å forøke dine Runway-sider.

    Sider inkludert i Runway: Hjemmeside, Komme-i-gang, Installere moduler.
    Valgfrie Moduler: Toppnavigasjon, Sidekart, Kontakt, Galleri.
    ]]>
    Hva er Runway Steg 1/5 Godta lisens Steg 2/5 Database konfigurasjon @@ -394,17 +467,17 @@ Steg 4/5: Skjekk Umbraco sikkerheten Steg 5/5: Umbraco er klar for deg til å starte! Tusen takk for at du valgte Umbraco! - <h3>Se ditt nye nettsted</h3> Du har installert Runway, hvorfor ikke se hvordan ditt nettsted ser ut. - <h3>Mer hjelp og info</h3> Få hjelp fra vårt prisbelønte samfunn, bla gjennom dokumentasjonen eller se noen gratis videoer på hvordan man bygger et enkelt nettsted, hvordan bruke pakker og en rask guide til Umbraco terminologi + Se ditt nye nettsted Du har installert Runway, hvorfor ikke se hvordan ditt nettsted ser ut.]]> + Mer hjelp og info Få hjelp fra vårt prisbelønte samfunn, bla gjennom dokumentasjonen eller se noen gratis videoer på hvordan man bygger et enkelt nettsted, hvordan bruke pakker og en rask guide til Umbraco terminologi]]> Umbraco %0% er installert og klar til bruk - For å fullføre installasjonen, må du manuelt endre <strong>web.config</strong> filen, og oppdatere AppSetting-nøkkelen <strong>UmbracoConfigurationStatus</strong> til verdien <strong>'%0%'</strong> - Du kan <strong>starte øyeblikkelig</strong> ved å klikke på "Start Umbraco" knappen nedenfor. <br/>Hvis du er <strong>ny på Umbraco</strong>, kan du finne mange ressurser på våre komme-i-gang sider. - <h3>Start Umbraco</h3> For å administrere din webside, åpne Umbraco og begynn å legge til innhold, oppdatere maler og stilark eller utvide funksjonaliteten + web.config filen, og oppdatere AppSetting-nøkkelen UmbracoConfigurationStatus til verdien '%0%']]> + starte øyeblikkelig ved å klikke på "Start Umbraco" knappen nedenfor.
    Hvis du er ny på Umbraco, kan du finne mange ressurser på våre komme-i-gang sider.]]>
    + Start Umbraco For å administrere din webside, åpne Umbraco og begynn å legge til innhold, oppdatere maler og stilark eller utvide funksjonaliteten]]> Tilkobling til databasen mislyktes. Umbraco Versjon 3 Umbraco Versjon 4 - Pass på - Denne veiviseren vil hjelpe deg gjennom prosessen med å konfigurere <strong>Umbraco %0%</strong> for en ny installasjon eller oppgradering fra versjon 3.0. <br/><br/> Trykk <strong>"neste"</strong> for å starte veiviseren. + Se + Umbraco %0% for en ny installasjon eller oppgradering fra versjon 3.0.

    Trykk "neste" for å starte veiviseren.]]>
    Språkkode @@ -415,8 +488,16 @@ Forny innlogging for å lagre - <p style="text-align:right;">&copy; 2001 - %0% <br /><a href="http://umbraco.com" style="text-decoration: none" target="_blank">umbraco.org</a></p> - Velkommen til Umbraco, skriv inn ditt brukernavn og passord i feltene under: + Da er det søndag! + Smil, det er mandag! + Hurra, det er tirsdag! + For en herlig onsdag! + Gledelig torsdag! + Endelig fredag! + Gledelig lørdag + Logg på nedenfor + Din sesjon er utløpt + © 2001 - %0%
    umbraco.com

    ]]>
    Skrivebord @@ -433,63 +514,64 @@ Ingen node er valgt, vennligst velg en node i listen over før du klikker 'fortsett' Gjeldende nodes type tillates ikke under valgt node Gjeldende node kan ikke legges under en underordnet node + Denne noden kan ikke ligge på rotnivå Handlingen tillates ikke. Du mangler tilgang til en eller flere underordnede noder. Relater kopierte elementer til original(e) Rediger dine varsler for %0% - + - <p>Hei %0%</p> + ]]> + Hei %0%

    - <p>Dette er en automatisk mail for å informere om at handlingen '%1%' - er blitt utført på siden <a href="http://%4%/actions/preview.aspx?id=%5%"><strong>'%2%'</strong></a> - av brukeren <strong>'%3%'</strong> - </p> - <div style="margin: 8px 0; padding: 8px; display: block;"> - <br /> - <a style="color: white; font-weight: bold; background-color: #5372c3; text-decoration : none; margin-right: 20px; border: 8px solid #5372c3; width: 150px;" href="http://%4%/Umbraco/actions/editContent.aspx?id=%5%">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;REDIGER&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</a> &nbsp; - <br /> - </div> - <p> - <h3>Rettelser:</h3> - <table style="width: 100%;"> +

    Dette er en automatisk mail for å informere om at handlingen '%1%' + er blitt utført på siden '%2%' + av brukeren '%3%' +

    + +

    +

    Rettelser:

    + %6% - </table> - </p> +
    +

    - <div style="margin: 8px 0; padding: 8px; display: block;"> - <br /> - <a style="color: white; font-weight: bold; background-color: #5372c3; text-decoration : none; margin-right: 20px; border: 8px solid #5372c3; width: 150px;" href="http://%4%/Umbraco/actions/editContent.aspx?id=%5%">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;REDIGER&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</a> &nbsp; - <br /> - </div> + - <p>Ha en fin dag!<br /><br /> +

    Ha en fin dag!

    Vennlig hilsen Umbraco roboten - </p> +

    ]]>
    [%0%] Varsling om %1% utført på %2% - Varsling + Varslinger - Klikke browse og velg pakke fra lokal disk. Umbraco-pakker har vanligvis endelsen ".umb" eller ".zip". + Umbraco-pakker har vanligvis endelsen ".umb" eller ".zip".]]> Utvikler Demonstrasjon Dokumentasjon Metadata Pakkenavn Pakken inneholder ingen elementer - Denne pakkefilen inneholder ingen elementer å avinstallere.<br/><br/>Du kan trygt fjerne pakken fra systemet ved å klikke "avinstaller pakke" nedenfor. +
    Du kan trygt fjerne pakken fra systemet ved å klikke "avinstaller pakke" nedenfor.]]>
    Ingen oppdateringer tilgjengelig Alternativer for pakke Lesmeg for pakke @@ -498,12 +580,13 @@ Vennlig hilsen Umbraco roboten Pakken ble avinstallert Pakken ble vellykket avinstallert Avinstaller pakke - Du kan velge bort elementer du ikke vil slette på dette tidspunkt, nedenfor. Når du klikker "bekreft avinstallering" vil alle elementer som er krysset av bli slettet.<br/> <span style="color:red;font-weight:bold;">Advarsel:</span> alle dokumenter, media, etc. som som er avhengig av elementene du sletter, vil slutte å virke, noe som kan føre til ustabilitet, så avinstaller med forsiktighet. Hvis du er i tvil, kontakt pakkeutvikleren. + Advarsel: alle dokumenter, media, etc. som som er avhengig av elementene du sletter, vil slutte å virke, noe som kan føre til ustabilitet, så avinstaller med forsiktighet. Hvis du er i tvil, kontakt pakkeutvikleren.]]> Last ned oppdatering fra pakkeregisteret Oppgrader pakke Oppgraderingsinstrukser Det er en oppdatering tilgjengelig for denne pakken. Du kan laste den ned direkte fra pakkebrønnen. Pakkeversjon + Pakkeversjonshistorie Se pakkens nettsted @@ -514,8 +597,8 @@ Vennlig hilsen Umbraco roboten Avansert: Beskytt ved å velge hvilke brukergrupper som har tilgang til siden - Om du ønsker å kontrollere tilgang til siden ved å bruke rolle-basert autentisering,<br /> ved å bruke Umbraco's medlems-grupper - Du må opprette en medlemsgruppe før du kan bruke <br /> rollebasert autentikasjon. + ved å bruke Umbraco's medlems-grupper]]> + rollebasert autentikasjon.]]> Feilside Brukt når personer logger på, men ikke har tilgang Hvordan vil du beskytte siden din? @@ -531,32 +614,36 @@ Vennlig hilsen Umbraco roboten Om du ønsker å bruke enkel autentisering via ett enkelt brukernavn og passord + %0% kunne ikke publiseres fordi den har planlagt utgivelsesdato. + %0% ble ikke publisert. Ett eller flere felter ble ikke godkjent av validering. %0% kunne ikke publiseres fordi et tredjepartstillegg avbrøt handlingen. + %0% kan ikke publiseres fordi en overordnet side ikke er publisert. Inkluder upubliserte undersider Publiserer - vennligst vent... %0% av %1% sider har blitt publisert... %0% er nå publisert %0% og alle undersider er nå publisert Publiser alle undersider - Klikk <em>ok</em> for å publisere <strong>%0%</strong> og dermed gjøre innholdet synlig for alle.<br/><br />Du kan publisere denne siden og alle dens undersider ved å krysse av <em>Publiser alle undersider</em> nedenfor. - %0% ble ikke publisert. Ett eller flere felter ble ikke godkjent av validering. - %0% kan ikke publiseres fordi en overordnet side ikke er publisert. + ok for å publisere %0% og dermed gjøre innholdet synlig for alle.

    Du kan publisere denne siden og alle dens undersider ved å krysse av Publiser alle undersider nedenfor.]]>
    + + + Du har ikke konfigurert noen godkjente farger - Legg til ekstern lenke - Legg til intern lenke - Legg til + skriv inn ekstern lenke + velg en intern side Tittel - Intern side - Url - Flytt ned - Flytt opp + Lenke Åpne i nytt vindu - Fjern lenke + Skriv inn en tekst + Skriv inn en lenke + + + Nullstill Gjeldende versjon - Dette viser forskjellene mellom den gjeldende og den valgte versjonen<br /><del>Rød</del> tekst vil ikke bli vist i den valgte versjonen. , <ins>grønn betyr lagt til</ins> + Rød tekst vil ikke bli vist i den valgte versjonen. , grønn betyr lagt til]]> Dokumentet er tilbakeført til en tidligere versjon Dette viser den valgte versjonen som HTML, bruk avviksvisningen hvis du ønsker å se forksjellene mellom to versjoner samtidig. Tilbakefør til @@ -579,6 +666,15 @@ Vennlig hilsen Umbraco roboten Statistikk Oversettelse Brukere + Hjelp + Skjemaer + Analytics + + + gå til + Hjelpeemner for + Videokapitler for + De beste Umbraco opplæringsvideoer Standardmal @@ -588,6 +684,7 @@ Vennlig hilsen Umbraco roboten Nodetype Type Stilark + Script Stilark-egenskap Arkfane Tittel på arkfane @@ -595,17 +692,24 @@ Vennlig hilsen Umbraco roboten Hovedinnholdstype aktivert Denne dokumenttypen bruker som hoveddokumenttype. Arkfaner fra hoveddokumenttyper vises ikke og kan kun endres på hoveddokumenttypen selv. + Ingen egenskaper definert i denne arkfanen. Klikk på "legg til ny egenskap" lenken i toppen for å opprette en ny egenskap. + Hovedinnholdstype + Opprett tilhørende mal Sortering ferdig. Dra elementene opp eller ned for å arrangere dem. Du kan også klikke kolonneoverskriftene for å sortere alt på en gang. - Vennligst vent. Elementene blir sortert, dette kan ta litt tid.<br/> <br/> Ikke lukk dette vinduet under sortering +
    Ikke lukk dette vinduet under sortering]]>
    + En feil oppsto + Utilstrekkelige brukertillatelser, kunne ikke fullføre operasjonen + Avbrutt + Handlingen ble avbrutt av et tredjepartstillegg Publisering ble avbrutt av et tredjepartstillegg Egenskaptypen finnes allerede Egenskapstype opprettet - Navn: %0% <br /> DataType: %1% + DataType: %1%]]> Egenskapstype slettet Innholdstype lagret Du har opprettet en arkfane @@ -623,12 +727,15 @@ Vennlig hilsen Umbraco roboten Husk å publisere for å gjøre endringene synlig for besøkende Sendt for godkjenning Endringer har blitt sendt til godkjenning + Media lagret + Media lagret uten feil Medlem lagret Stilarksegenskap lagret Stilark lagret Mal lagret Feil ved lagring av bruker (sjekk loggen) Bruker lagret + Brukertypen lagret Filen ble ikke lagret Filen kunne ikke lagres. Vennligst sjekk filrettigheter Filen ble lagret @@ -647,13 +754,16 @@ Vennlig hilsen Umbraco roboten XSLT-koden ble ikke lagret, sjekk filrettigheter XSLT lagret Ingen feil i XSLT! - Media lagret - Brukertypen lagret Innhold avpublisert Delmal lagret Delmal lagret uten feil Delmal ble ikke lagret! En feil oppsto ved lagring av delmal + Script visning lagret + Script visning lagret uten feil! + Script visning ikke lagret + En feil oppsto under lagring av filen. + En feil oppsto under lagring av filen. Bruk CSS syntaks f.eks: h1, .redHeader, .blueText @@ -674,13 +784,41 @@ Vennlig hilsen Umbraco roboten Hurtigguide til Umbraco sine maltagger Mal + + Sett inn element + Velg ett oppsett for denne seksjonen + nedenfor og legg til det første elementet]]> + Klikk for å bygge inn + Klikk for å sette inn et bilde + Bildetekst... + Skriv her... + Rutenettoppsett + Et oppsett er det overordnede arbeidsområdet til ditt rutenett - du vil typisk kun behøve ét eller to + Legg til rutenettoppsett + Juster oppsettet ved at justere kolonnebredder og legg til ytterligere seksjoner + Radkonfigurasjoner + Rader er forhåndsdefinerte celler arrangert vannrett + Legg til radkonfigurasjon + Juster raden ved å sette celle bredder og legge til flere celler + Kolonner + Totale antallet kolonner i rutenettet + Innstillinger + Konfigurer hvilke innstillinger brukeren kan endre + Stiler + Konfigurer hvilke stiler redaktørene kan endre + Innstillingene lagres kun når json-konfigurasjonen er gyldig + Tillatt alle editorer + Tillat alle radkonfigurasjoner + Alternativt felt Alternativ tekst Store/små bokstaver + Encoding Felt som skal settes inn Konverter linjeskift - Erstatter et linjeskift med htmltaggen &lt;br&gt; + Erstatter et linjeskift med htmltaggen <br> + Egendefinerte felt Ja, kun dato Formatter som dato HTML koding @@ -693,19 +831,18 @@ Vennlig hilsen Umbraco roboten Sett inn før felt Rekursivt Fjern paragraftagger - Fjerner eventuelle &lt;P&gt; rundt teksten + Fjerner eventuelle <P> rundt teksten + Standardfelter Store bokstaver URL koding Dersom innholdet av feltene skal sendes til en URL skal spesialtegn formatteres Denne teksten vil benyttes dersom feltene over er tomme Dette feltet vil benyttes dersom feltet over er tomt Ja, med klokkeslett. Dato/tid separator: - Egendefinerte felt - Standardfelter Oppgaver satt til deg - Listen nedenfor viser oversettelsesoppgaver <strong>som du er tildelt</strong>. For å se en detaljert visning inkludert kommentarer, klikk på "Detaljer" eller navnet på siden. Du kan også laste ned siden som XML direkte ved å klikke på linken "Last ned XML". <br/> For å lukke en oversettelsesoppgave, vennligst gå til detaljvisningen og klikk på "Lukk" knappen. + som du er tildelt. For å se en detaljert visning inkludert kommentarer, klikk på "Detaljer" eller navnet på siden. Du kan også laste ned siden som XML direkte ved å klikke på linken "Last ned XML".
    For å lukke en oversettelsesoppgave, vennligst gå til detaljvisningen og klikk på "Lukk" knappen.]]>
    Lukk oppgave Oversettelses detaljer Last ned all oversettelsesoppgaver som XML @@ -713,7 +850,7 @@ Vennlig hilsen Umbraco roboten Last ned XML DTD Felt Inkluder undersider - + + ]]> [%0%] Oversettingsoppgave for %1% Ingen oversettelses-bruker funnet. Vennligst opprett en oversettelses-bruker før du begynner å sende innhold til oversetting Oppgaver opprettet av deg - Listen under viser sider <strong>opprettet av deg</strong>. For å se en detaljert visning inkludert kommentarer, klikk på "Detaljer" eller navnet på siden. Du kan også laste ned siden som XML direkte ved å klikke på linken "Last ned XML". For å lukke en oversettelsesoppgave, vennligst gå til detaljvisningen og klikk på "Lukk" knappen. + opprettet av deg. For å se en detaljert visning inkludert kommentarer, klikk på "Detaljer" eller navnet på siden. Du kan også laste ned siden som XML direkte ved å klikke på linken "Last ned XML". For å lukke en oversettelsesoppgave, vennligst gå til detaljvisningen og klikk på "Lukk" knappen.]]> Siden '%0%' har blitt sendt til oversetting Send til oversetting Tildelt av @@ -771,6 +908,7 @@ Vennlig hilsen Umbraco roboten Stiler Maler XSLT Filer + Analytics Ny oppdatering er klar @@ -782,6 +920,8 @@ Vennlig hilsen Umbraco roboten Administrator Kategorifelt Bytt passord + Nytt passord + Bekreft nytt passord Du kan endre passordet til Umbraco ved å fylle ut skjemaet under og klikke "Bytt passord" knappen. Innholdskanal Beskrivelsesfelt @@ -790,15 +930,18 @@ Vennlig hilsen Umbraco roboten Redaktør Utdragsfelt Språk - Login + Brukernavn Øverste nivå i Media Moduler Deaktiver tilgang til Umbraco Passord + Nullstill passord Passordet er endret Bekreft nytt passord Nytt passord Nytt passord kan ikke være blankt + Gjeldende passord + Feil passord Nytt og bekreftet passord må være like Nytt og bekreftet passord må være like Overskriv tillatelser på undernoder @@ -806,14 +949,15 @@ Vennlig hilsen Umbraco roboten Velg sider for å redigere deres tillatelser Søk i alle undersider Startnode - Brukernavn + Navn Brukertillatelser Brukertype Brukertyper Forfatter - Nytt passord - Bekreft nytt passord - Gjeldende passord - Feil passord + Oversetter + Endre + Din profil + Din historikk + Sesjonen utløper om
    \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/pl.xml b/src/Umbraco.Web.UI/umbraco/config/lang/pl.xml index 1b59676ae7..45e1cce6bf 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/pl.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/pl.xml @@ -307,6 +307,7 @@ Możesz dodać dodatkowe języki w menu "Języki" po lewej stronie.]]>
    Pokaż stronę "wyślij" Rozmiar Sortuj + Submit Typ Szukaj W górę @@ -321,6 +322,8 @@ Możesz dodać dodatkowe języki w menu "Języki" po lewej stronie.]]> Witaj... Szerokość Tak + Reorder + I am done reordering Kolor tła @@ -614,6 +617,45 @@ Miłego dnia!]]> Szybki przewodnik po tagach szablonu Umbraco Szablon + + Choose type of content + Choose a layout + Add a row + Add content + Drop content + Settings applied + + This content is not allowed here + This content is allowed here + + Click to embed + Click to insert image + Image caption... + Write here... + + Grid Layouts + Layouts are the overall work area for the grid editor, usually you only need one or two different layouts + Add Grid Layout + Adjust the layout by setting column widths and adding additional sections + Row configurations + Rows are predefined cells arranged horizontally + Add row configuration + Adjust the row by setting cell widths and adding additional cells + + Columns + Total combined number of columns in the grid layout + + Settings + Configure what settings editors can change + + Styles + Configure what styling editors can change + + Settings will only save if the entered json configuration is valid + + Allow all editors + Allow all row configurations + Pole alternatywne Tekst alternatywny @@ -673,7 +715,7 @@ Miłego dnia!]]> Cache przeglądarki Kosz Utworzone pakiety - Typy danych + Typy danych Słownik Zainstalowane pakiety TRANSLATE ME: 'Install skin' @@ -683,10 +725,10 @@ Miłego dnia!]]> Makra Typy mediów Członkowie - Grupy członków + Grupy członków Role - Typ członka - Typy dokumentów + Typ członka + Typy dokumentów Pakiety Pakiety Pliki Python diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/pt.xml b/src/Umbraco.Web.UI/umbraco/config/lang/pt.xml index a713ba8d5b..7cd7264fe3 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/pt.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/pt.xml @@ -306,6 +306,7 @@ Mostrar página durante envio Tamanho Classificar + Submit Tipo Digite para buscar... Acima @@ -320,6 +321,8 @@ Bem Vindo(a)... Largura Sim + Reorder + I am done reordering Cor de fundo @@ -687,6 +690,45 @@ Você pode publicar esta página e todas suas sub-páginas ao selecionar pub Guia rápido para etiquetas de modelos Umbraco Modelo + + Choose type of content + Choose a layout + Add a row + Add content + Drop content + Settings applied + + This content is not allowed here + This content is allowed here + + Click to embed + Click to insert image + Image caption... + Write here... + + Grid Layouts + Layouts are the overall work area for the grid editor, usually you only need one or two different layouts + Add Grid Layout + Adjust the layout by setting column widths and adding additional sections + Row configurations + Rows are predefined cells arranged horizontally + Add row configuration + Adjust the row by setting cell widths and adding additional cells + + Columns + Total combined number of columns in the grid layout + + Settings + Configure what settings editors can change + + Styles + Configure what styling editors can change + + Settings will only save if the entered json configuration is valid + + Allow all editors + Allow all row configurations + Campo alternativo Texto alternativo @@ -761,7 +803,7 @@ Para fechar a tarefa de tradução vá até os detalhes e clique no botão "Fech Navegador de Cache Lixeira Pacotes criados - Tipo de Dado + Tipo de Dado Dicionário Pacotes instalados Instalar tema @@ -771,10 +813,10 @@ Para fechar a tarefa de tradução vá até os detalhes e clique no botão "Fech Macros Tipos de Mídia Membros - Grupos de Membros + Grupos de Membros Funções - Tipo de Membro - Tipos de Documentos + Tipo de Membro + Tipos de Documentos Pacotes Pacotes Arquivos Python diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml b/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml index 41dd15fbf2..e679003a25 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml @@ -27,6 +27,7 @@ Опубликовать Обновить узлы Опубликовать весь сайт + Восстановить Разрешения Откатить Направить на публикацию @@ -81,6 +82,7 @@ Вставить макрос Вставить изображение Править связи + Вернуться к списку Сохранить Сохранить и опубликовать Сохранить и направить на публикацию @@ -99,7 +101,8 @@ Узел переопубликован. Текущее свойство Текущий тип - Тип документа не может быть изменен, так как для данного расположения нет разрешенных альтернатив. + Тип документа не может быть изменен, так как для данного расположения нет разрешенных альтернатив. + Альтернативный тип станет доступным, если его разрешить как тип, пригодный для создания дочерних узлов внутри родительского узла данного документа. Тип документа изменен Сопоставление свойств Сопоставлено свойству @@ -121,7 +124,8 @@ Алиас (как бы Вы описали изображение по телефону) Альтернативные ссылки - Дочерние узлы + Альтернативный текст (необязательно) + Элементы списка Нажмите для правки этого элемента Создано пользователем Исходный автор @@ -130,9 +134,11 @@ Тип документа Редактирование Скрыть + Опубликовано Этот документ был изменен после публикации Этот документ не опубликован Документ опубликован + В этом списке пока нет элементов. Ссылка на медиа-элементы Тип медиа-контента Группа участников @@ -165,9 +171,9 @@ Где вы хотите создать новый %0% - Создать в - "document types".]]> - "media types".]]> + Создать в узле + "Типы документов".]]> + "Типы медиа-материалов".]]> Выберите тип и заголовок @@ -234,12 +240,20 @@ Название языка (культуры) + Допустим как корневой + Только узлы таких типов (с установленным флагом) могут быть созданы в корневом уровне дерева содержимого или медиа-библиотеки Допустимые типы дочерних узлов + Составные типы документов Создать + Создать пользовательский список + Текущий список + Текущий тип данных в виде списка Удалить вкладку Описание Включить представление в виде списка + Включает представление узлов, дочерних к узлу данного типа, в виде сортируемого списка с функцией фильтра и поиска. Такие дочерние узлы при этом перестают быть видимыми в дереве. Новая вкладка + Удалить пользовательский список Вкладка Миниатюра @@ -266,7 +280,7 @@ %0% должно быть целочисленным значением %0% в %1% является обязательным полем %0% является обязательным полем - %0% в %1%: данные введены в некорректном формате + %0% в %1%: данные в некорректном формате %0% - данные в некорректном формате @@ -372,6 +386,7 @@ Показать страницу при отправке Размер Сортировать + Submit Тип Что искать? Вверх @@ -386,6 +401,8 @@ Добро пожаловать... Ширина Да + Reorder + I am done reordering Цвет фона @@ -394,9 +411,49 @@ Шрифт Текст + + Add content + Drop content + Добавить шаблон сетки + Настройте шаблон, задавая ширину колонок или добавляя дополнительные секции + Добавить конфигурацию строки + Настройте строку, задавая ширину ячеек или добавляя дополнительные ячейки + Добавить новые строки + Доступны все редакторы + Доступны все конфигурации строк + Choose a layout + Кликните для встраивания + Кликните для вставки изображения + Колонки + This content is not allowed here + This content is allowed here + Суммарное число колонок в шаблоне сетки + Шаблоны сетки + Шаблоны являются рабочим пространством для редактора сетки, обычно Вам понадобится не более одного или двух шаблонов + Вставить элемент + Заголовок для изображения... + Напишите... + Конфигурации строк + Строки - это последовательности ячеек с горизонтальным расположением + Установки будут сохранены только если они указаны в корректном формате json + Установки + Settings applied + Задайте установки, доступные редакторам для изменения + Стили + Задайте стили, доступные редакторам для изменения + Страница + + перейти к + Разделы справки для + Обучающие видео для + Лучшие обучающие видеокурсы по Umbraco + + + Сбросить + Программа установки не может установить подключение к базе данных. Невозможно сохранить изменения в файл web.config. Пожалуйста, вручную измените настройки строки подключения к базе данных. @@ -533,7 +590,7 @@ Рыбный день Слава Богу, сегодня пятница Понедельник начинается в субботу - Введите имя пользователя и пароль + Укажите имя пользователя и пароль Время сессии истекло @@ -545,6 +602,10 @@ Нажмите, чтобы загрузить Перетащите файлы сюда... + + Создать нового участника + Все участники + Выберите страницу... Узел %0% был скопирован в %1% @@ -643,11 +704,12 @@ Вставить с очисткой форматирования (рекомендуется) - Введите имя... - Введите фильтр... + Укажите имя... + Укажите теги (нажимайте Enter после каждого тега)... + Укажите фильтр... Назовите %0%... Пароль - Введите для поиска... + Что искать... Имя пользователя @@ -692,16 +754,13 @@ ]]> - Добавить внешнюю ссылку - Добавить внутреннюю ссылку - Добавить ссылку Заголовок - Внутренняя страница - Внешний URL - Переместить вниз - Переместить вверх - Открывать в новом окне - Удалить ссылку + Укажите заголовок + выбрать страницу сайта + указать внешнюю ссылку + Укажите ссылку + Ссылка + В новом окне Текущая версия @@ -716,10 +775,12 @@ Править файл скрипта + Аналитика Смотритель Содержимое Курьер Для Разработчиков + Формы Помощь Мастер конфигурирования Umbraco Медиа-материалы @@ -734,9 +795,11 @@ в качестве родительского типа. Вкладки родительского типа не показаны и могут быть изменены непосредственно в родительском типе Родительский тип контента разрешен Данный тип контента использует + Создать шаблон для документов этого типа Шаблон по-умолчанию Словарная статья Чтобы импортировать тип документа, найдите файл ".udt" на своем компьютере, нажав на кнопку "Обзор", затем нажмите "Импортировать" (на следующем экране будет запрошено подтверждение для этой операции). + Родительский тип документа Заголовок новой вкладки Тип узла (документа) Для данной вкладки не определены свойства. Кликните по ссылке "Click here to add a new property" сверху, чтобы создать новое свойство. @@ -906,10 +969,11 @@ Загрузить переведенный xml + Аналитика Обзор кэша Корзина Созданные пакеты - Типы данных + Типы данных Словарь Установленные пакеты Установить тему @@ -919,10 +983,10 @@ Макросы Типы медиа-материалов Участники - Группы участников + Группы участников Роли участников - Типы участников - Типы документов + Типы участников + Типы документов Пакеты дополнений Пакеты дополнений Скрипты Python @@ -975,6 +1039,7 @@ Поиск всех дочерних документов Сессия истекает через Начальный узел содержимого + Переводчик Имя пользователя Разрешения для пользователя Тип пользователя diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml b/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml index aed36d5d92..13ea501406 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml @@ -40,11 +40,11 @@ Lägg till nytt domännamn Domännamn - Har skapat domännamnet {0} - Har tagit bort domännamnet {0} - Domänen {0} är redan tillagd + Har skapat domännamnet %0% + Har tagit bort domännamnet %0% + Domänen %0% är redan tillagd t.ex.: dittdomannamn.se, www.dittdomannamn.se - Domännamnet {0} har uppdaterats + Domännamnet %0% har uppdaterats Domänen är redan tilldelad Ärv Ogiltigt domännamn @@ -145,7 +145,7 @@ Sidnamn Ej medlem av grupp(er) Egenskaper - Detta dokument är publicerat men syns inte eftersom den överordnade sidan {0} inte är publicerad + Detta dokument är publicerat men syns inte eftersom den överordnade sidan %0% inte är publicerad Oops: detta dokument är publicerat men finns inte i cacheminnet (internt fel) Publicera Publiceringsstatus @@ -166,7 +166,7 @@ Länk till dokument - Var vill du skapa den nya {0} + Var vill du skapa den nya %0% Skapa innehåll under "dokumenttyper".]]> "mediatyper".]]> @@ -187,7 +187,7 @@ Stäng fönstret Är du säker på att du vill ta bort Är du säker på att du vill avaktivera - Kryssa i denna ruta för att bekräfta att {0} objekt tas bort + Kryssa i denna ruta för att bekräfta att %0% objekt tas bort Är du säker? Är du säker? Klipp ut @@ -228,7 +228,7 @@ Se cachat objekt - Redigera de olika översättningarna för ordboksinlägget {0} nedan. Du kan lägga till ytterligare språk under 'språk' i menyn till vänster. + Redigera de olika översättningarna för ordboksinlägget %0% nedan. Du kan lägga till ytterligare språk under 'språk' i menyn till vänster. Språknamn @@ -265,15 +265,15 @@ Informationen har sparats, men innan du kan publicera denna sida måste du åtgärda följande fel: Det går inte att byta lösenord i den medlemshanterare du har valt (EnablePasswordRetrieval måste vara satt till 'true'). - {0} redan finns + %0% redan finns Följande fel inträffade: Följande fel inträffade: - Lösenordet måste bestå av minst {0} tecken varav minst {1} är icke-alfanumeriska tecken (t.ex. %, #, !, @). - {0} måste vara ett heltal - {0} under {1} är ett obligatoriskt fält - {0} är ett obligatoriskt fält - {0} under {1} har ett felaktigt format - {0} har ett felaktigt format + Lösenordet måste bestå av minst %0% tecken varav minst %1% är icke-alfanumeriska tecken (t.ex. %, #, !, @). + %0% måste vara ett heltal + %0% under %1% är ett obligatoriskt fält + %0% är ett obligatoriskt fält + %0% under %1% har ett felaktigt format + %0% har ett felaktigt format Även om CodeMirror är aktiverad i konfigurationen, så är den avaktiverad i Internet Explorer på grund av att den inte är tillräckligt stabil @@ -378,6 +378,7 @@ Vilken sida skall visas när formuläret är skickat Storlek Sortera + Submit Skriv Skriv för att söka... Upp @@ -392,6 +393,8 @@ Bredd Titta på Ja + Reorder + I am done reordering Bakgrundsfärg @@ -408,12 +411,12 @@ Kunde inte spara filen web.config. Vänligen ändra databasanslutnings-inställningarna manuellt. Din databas har lokaliserats och är identifierad som Databaskonfiguration - installera]]> - Nästa för att fortsätta.]]> + installera]]> + Nästa för att fortsätta.]]> Databasen kunde inte hittas! Kontrollera att informationen i databasanslutnings-inställningarna i filen "web.config" är rätt.

    För att fortsätta måste du redigera filen "web.config" (du kan använda Visual Studio eller din favorit text-redigerare), bläddra till slutet, lägg till databasanslutnings-inställningarna för din databas i fältet som heter "umbracoDbDSN" och spara filen.

    Klicka på Försök igen knappen när du är klar.
    > Mer information om att redigera web.config hittar du här.

    ]]>
    Eventuellt kan du behöva kontakta ditt webb-hotell. Om du installerar på en lokal maskin eller server kan du få informationen från din systemadministratör.]]> - Tryck Uppgradera knappen för att uppgradera din databas till Umbraco {0}

    Du behöver inte vara orolig. Inget innehåll kommer att raderas och efteråt kommer allt att fungera som vanligt!

    ]]>
    - Tryck Nästa för att fortsätta.]]> + Tryck Uppgradera knappen för att uppgradera din databas till Umbraco %0%

    Du behöver inte vara orolig. Inget innehåll kommer att raderas och efteråt kommer allt att fungera som vanligt!

    ]]>
    + Tryck Nästa för att fortsätta.]]> Nästa för att fortsätta med konfigurationsguiden]]> Lösenordet på standardanvändaren måste bytas!]]> Standardanvändaren har avaktiverats eller har inte åtkomst till Umbraco!

    Du behöver inte göra något ytterligare här. Klicka Next för att fortsätta.]]> @@ -455,13 +458,13 @@ Tack för att du valde Umbraco Besök din nya webbplats Du installerade Runway, så varför inte se hur din nya webbplats ser ut.]]> Ytterligare hjälp och information Få hjälp från våra prisbelönta community, bläddra i dokumentationen eller titta på några gratis videor om hur man bygger en enkel webbplats, hur du använder paket eller en snabbguide till Umbracos terminologi]]> - Umbraco {0} är installerat och klart för användning - /web.config filen och ändra AppSettingsnyckeln UmbracoConfigurationStatus på slutet till {0}]]> + Umbraco %0% är installerat och klart för användning + /web.config filen och ändra AppSettingsnyckeln UmbracoConfigurationStatus på slutet till %0%]]> börja omedelbart genom att klicka på "Starta Umbraco"-knappen nedan.
    Om du är en ny Umbraco användarekan du hitta massor av resurser på våra kom igång sidor.]]>
    Starta Umbraco För att administrera din webbplats öppnar du bara Umbraco back office och börjar lägga till innehåll, uppdatera mallar och stilmallar eller lägga till nya funktioner.]]> Anslutningen till databasen misslyckades. Se - Umbraco {0} antingen för en ny installation eller en uppgradering från version 3.0.

    Tryck på "next" för att börja.]]>
    + Umbraco %0% antingen för en ny installation eller en uppgradering från version 3.0.

    Tryck på "next" för att börja.]]>
    Umbraco Version 3 Umbraco Version 4 @@ -474,7 +477,7 @@ Förnya nu för att spara ditt arbete - © 2001 - {0}
    umbraco.com

    ]]>
    + © 2001 - %0%
    umbraco.com

    ]]>
    Happy super Sunday Happy manic Monday Happy tremendous Tuesday @@ -496,10 +499,10 @@ Välj sida ovan... - {0} har kopierats till {1} - Ange mål att kopiera sidan {0} till nedan - {0} har flyttats till {1} - Ange vart sidan {0} skall flyttas till nedan + %0% har kopierats till %1% + Ange mål att kopiera sidan %0% till nedan + %0% har flyttats till %1% + Ange vart sidan %0% skall flyttas till nedan är nu roten för ditt nya innehåll. Klicka 'ok' nedan. Du har inte valt någon sida än. Välj en sida i listan ovan och klicka sedan 'fortsätt'. Aktuell nod får inte existera i roten @@ -550,10 +553,11 @@ Fyll i ett namn... Skriv för att filtrera... - Namnge {0}... + Namnge %0%... Fyll i ditt lösenord Skriv för att söka... Fyll i ditt lösenord + Skriv för att lägga till taggar (och tryck enter efter varje tagg)... Rollbaserat lösenordsskydd @@ -562,8 +566,8 @@ Sida med felmeddelande Används när en användare är inloggad, men saknar rättigheter att se sidan Välj hur du vill lösenordsskydda sidan - {0} är nu lösenordsskyddad - Lösenordsskyddet är nu borttaget på {0} + %0% är nu lösenordsskyddad + Lösenordsskyddet är nu borttaget på %0% Inloggningssida Välj sidan med inloggningsformuläret Ta bort lösenordsskydd @@ -574,29 +578,29 @@ Välj detta alternativ om du vill skydda sidan med ett enkelt användarnamn och lösenord. Alla loggar då in med samma inloggningsuppgifter. - {0} kunde inte publiceras på grund av dess tidsinställda publicering. - {0} kunde inte publiceras på grund av att ett tredjepartstillägg avbröt publiceringen. - {0} kan inte publiceras, på grund av att överordnad nod inte är publicerad. - {0} kunde inte publiceras på grund av följande orsaker: {1} passerade inte valideringen. + %0% kunde inte publiceras på grund av dess tidsinställda publicering. + %0% kunde inte publiceras på grund av att ett tredjepartstillägg avbröt publiceringen. + %0% kan inte publiceras, på grund av att överordnad nod inte är publicerad. + %0% kunde inte publiceras på grund av följande orsaker: %1% passerade inte valideringen. Inkludera opublicerade undersidor Publicering pågår - vänligen vänta... - {0} av {1} sidor har publicerats... - {0} har publicerats - {0} och underliggande sidor har publicerats - Publicera {0} och alla dess underordnade sidor - ok
    för att publicera {0}. Därmed blir innehållet publikt.

    Du kan publicera denna sida och alla dess undersidor genom att kryssa i publicera alla undersidor. ]]> + %0% av %1% sidor har publicerats... + %0% har publicerats + %0% och underliggande sidor har publicerats + Publicera %0% och alla dess underordnade sidor + ok för att publicera %0%. Därmed blir innehållet publikt.

    Du kan publicera denna sida och alla dess undersidor genom att kryssa i publicera alla undersidor. ]]>
    - Lägg till extern länk - Lägg till intern länk - Lägg till + ange en extern länk + ange en intern sida Rubrik - Intern sida - URL - Flytta ner - Flytta upp + Länk Öppna i nytt fönster - Ta bort länk + Ange visningstext + Ange adress + + + Återställ Nuvarande version @@ -644,6 +648,8 @@ Flik Fliknamn Flikar + Huvuddokumenttyp + Skapa matchande mall Sortering klar @@ -654,12 +660,12 @@ Publiceringen avbröts av ett tredjepartstillägg Egenskapstyp finns redan Egenskapstyp skapad - Datatyp: {1}]]> + Datatyp: %1%]]> Egenskapstypen har tagits bort Innehållstypen har sparats Ny flik skapad Fliken har tagits bort - Fliken med id: {0} har tagits bort + Fliken med id: %0% har tagits bort Innehållet är avpublicerat Stilmallen kunde inte sparas Stilmallen sparades @@ -726,8 +732,13 @@ Lägg till + Choose layout Lägg till rad - nedan för att lägga till ditt första element]]> + Add content + Drop content + Styles applied + Indholdet er ikke tilladt her + Indholdet er tilladt her Klicka för att lägga in Klicka för att lägga till bild Bildtext... @@ -790,13 +801,13 @@ Ladda hem DTD för XML Fält Inkludera undersidor - Hej {0}. Detta är ett automatisk mail skickat for att informera dig om att det finns en översättningsförfrågan på dokument '%1' till '{5}' skickad av {2}. För att redigere, besök http://{3}/translation/details.aspx?id={4}. För att få en översikt över dina översättningsuppgigter loggar du in i Umbraco på: http://{3} - [{0}] Översättningsuppgit för {1} + Hej %0%. Detta är ett automatisk mail skickat for att informera dig om att det finns en översättningsförfrågan på dokument '%1%' till '%5%' skickad av %2%. För att redigera, besök http://%3%/translation/details.aspx?id=%4%. För att få en översikt över dina översättningsuppgigter loggar du in i Umbraco på: http://%3% + [%0%] Översättningsuppgift för %1% Hittade inga användare som är översättare. Vänligen skapa en användare som är översättare innan du börjar skicka innehåll för översättning Arbetsuppgifter som du har skapat som du har skapat. För att se en detaljvy med kommentarer, klicka på "Detaljer" eller på sidans namn. Du kan också ladda ned sidan i XML-format genom att klicka på länken "Ladda ned XML".
    För att markera ett översättningsjobb som avslutat, gå till detaljvyn och klicka på knappen "Stäng".]]>
    - Sidan {0} har skickats för översättning - Skicka sidan {0} för översättning + Sidan %0% har skickats för översättning + Skicka sidan %0% för översättning Tilldelat av Arbetsuppgift öppnad Totalt antal ord @@ -813,7 +824,7 @@ Cacha webbläsare Papperskorg Skapade paket - Datatyper + Datatyper Ordbok Installerade paket Installera skin @@ -823,10 +834,10 @@ Makron Mediatyper Medlem - Medlemsgrupper + Medlemsgrupper Roller - Medlemstyper - Dokumenttyper + Medlemstyper + Dokumenttyper Paket Paket Python-filer @@ -841,7 +852,7 @@ Ny uppdatering tillgänglig - {0} är klart, klicka här för att ladda ner + %0% är klart, klicka här för att ladda ner Ingen kontakt med server Fel vid kontroll av uppdatering. Se trace-stack för mer information. @@ -885,6 +896,7 @@ Användartyper Skribent Din nuvarande historik + Översättare Din profil \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/zh.xml b/src/Umbraco.Web.UI/umbraco/config/lang/zh.xml index a45a626366..2a6286871c 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/zh.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/zh.xml @@ -349,6 +349,7 @@ 在发送时预览 大小 排序 + Submit 类型 输入内容开始查找… @@ -363,6 +364,8 @@ 欢迎… + Reorder + I am done reordering 背景色 @@ -773,6 +776,45 @@ 模板标签快速指南 模板 + + Choose type of content + Choose a layout + Add a row + Add content + Drop content + Settings applied + + This content is not allowed here + This content is allowed here + + Click to embed + Click to insert image + Image caption... + Write here... + + Grid Layouts + Layouts are the overall work area for the grid editor, usually you only need one or two different layouts + Add Grid Layout + Adjust the layout by setting column widths and adding additional sections + Row configurations + Rows are predefined cells arranged horizontally + Add row configuration + Adjust the row by setting cell widths and adding additional cells + + Columns + Total combined number of columns in the grid layout + + Settings + Configure what settings editors can change + + Styles + Configure what styling editors can change + + Settings will only save if the entered json configuration is valid + + Allow all editors + Allow all row configurations + 替代字段 替代文本 @@ -851,7 +893,7 @@ 缓存浏览 回收站 创建扩展包 - 数据类型 + 数据类型 字典 已安装的扩展包 安装皮肤 @@ -861,10 +903,10 @@ 媒体类型 会员 - 会员组 + 会员组 角色 - 会员类型 - 文档类型 + 会员类型 + 文档类型 扩展包 扩展包 Python文件 diff --git a/src/Umbraco.Web.UI/umbraco/controls/ContentTypeControlNew.ascx b/src/Umbraco.Web.UI/umbraco/controls/ContentTypeControlNew.ascx deleted file mode 100644 index 21230a343d..0000000000 --- a/src/Umbraco.Web.UI/umbraco/controls/ContentTypeControlNew.ascx +++ /dev/null @@ -1,240 +0,0 @@ -<%@ Control Language="c#" AutoEventWireup="True" Codebehind="ContentTypeControlNew.ascx.cs" - Inherits="Umbraco.Web.UI.Umbraco.Controls.ContentTypeControlNew" TargetSchema="http://schemas.microsoft.com/intellisense/ie5" %> -<%@ Import Namespace="umbraco" %> -<%@ Register TagPrefix="cc1" Namespace="umbraco.uicontrols" Assembly="controls" %> -<%@ Register TagPrefix="cc2" Namespace="umbraco.uicontrols" Assembly="controls" %> -<%@ Register TagPrefix="cdf" Namespace="ClientDependency.Core.Controls" Assembly="ClientDependency.Core" %> - - - - - - - - - - -

    <%=ui.GetText("settings", "contentTypeEnabled")%>
    <%=umbraco.ui.GetText("settings", "contentTypeUses")%> <%=umbraco.ui.GetText("settings", "asAContentMasterType")%>

    -
    - - - -   - - - - - - - - - - - - - - - - - -

    -

    -
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -
    - - - - - <%if (cb_isContainer.Checked) { %> - -
    - -
    -
    -
    -
    - - -
    - - -  (<%=ui.Text("general", "default", Security.CurrentUser) %>) - -
    - - <%=ui.Text("general", "edit", Security.CurrentUser) %> - -
    - -
    - - -
    - -
    -
    -
    - -
    -
    - - <%--Scripting to for configuring a list view--%> - - - <%} %> - -
    - -
    - - - - - - - - - - - - - - -
    - - - - -

    Master Content Type enabled
    This Content Type uses as a Master Content Type. Properties from Master Content Types are not shown and can only be edited on the Master Content Type itself

    -
    - - -
    - - -
    -
    -
    -<%-- cannot put a <%= block here 'cos it prevents the Controls collection from being modified = use a literal --%> - \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco/controls/ContentTypeControlNew.ascx.cs b/src/Umbraco.Web.UI/umbraco/controls/ContentTypeControlNew.ascx.cs deleted file mode 100644 index 41606afeb6..0000000000 --- a/src/Umbraco.Web.UI/umbraco/controls/ContentTypeControlNew.ascx.cs +++ /dev/null @@ -1,35 +0,0 @@ -//TODO: This needs to be rebuild in angular and then removed - -//using System; -//using System.Collections.Generic; -//using System.ComponentModel.DataAnnotations; -//using System.Linq; -//using System.Web; -//using System.Web.UI.WebControls; -//using AutoMapper; -//using Umbraco.Core; -//using Umbraco.Core.Models; -//using Umbraco.Web.Editors; -//using Umbraco.Web.Models.ContentEditing; - -//namespace Umbraco.Web.UI.Umbraco.Controls -//{ -// public partial class ContentTypeControlNew : global::umbraco.controls.ContentTypeControlNew -// { -// protected string DataTypeControllerUrl { get; private set; } -// protected string ContentTypeControllerUrl { get; private set; } - -// /// -// /// Raises the event. -// /// -// /// The object that contains the event data. -// protected override void OnLoad(EventArgs e) -// { -// base.OnLoad(e); - -// DataTypeControllerUrl = Url.GetUmbracoApiServiceBaseUrl(x => x.GetById(0)); -// ContentTypeControllerUrl = Url.GetUmbracoApiServiceBaseUrl(x => x.GetAssignedListViewDataType(0)); -// } - -// } -//} \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco/controls/ContentTypeControlNew.ascx.designer.cs b/src/Umbraco.Web.UI/umbraco/controls/ContentTypeControlNew.ascx.designer.cs deleted file mode 100644 index 2ad0b89587..0000000000 --- a/src/Umbraco.Web.UI/umbraco/controls/ContentTypeControlNew.ascx.designer.cs +++ /dev/null @@ -1,24 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Umbraco.Web.UI.Umbraco.Controls { - - - public partial class ContentTypeControlNew { - - /// - /// JsInclude control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.JsInclude JsInclude; - } -} diff --git a/src/Umbraco.Web.UI/umbraco/developer/Macros/EditMacro.aspx.cs b/src/Umbraco.Web.UI/umbraco/developer/Macros/EditMacro.aspx.cs index 57c5e715ad..73aa21647b 100644 --- a/src/Umbraco.Web.UI/umbraco/developer/Macros/EditMacro.aspx.cs +++ b/src/Umbraco.Web.UI/umbraco/developer/Macros/EditMacro.aspx.cs @@ -67,21 +67,23 @@ namespace Umbraco.Web.UI.Umbraco.Developer.Macros //get all the partials in the normal /MacroPartials folder var foundMacroPartials = GetPartialViewFiles(partialsDir, partialsDir, SystemDirectories.MvcViews + "/MacroPartials"); //now try to find all of them int he App_Plugins/[PackageName]/Views/MacroPartials folder - var partialPluginsDir = new DirectoryInfo(IOHelper.MapPath(SystemDirectories.AppPlugins)); - foreach(var d in partialPluginsDir.GetDirectories()) - { - var viewsFolder = d.GetDirectories("Views"); - if (viewsFolder.Any()) - { - var macroPartials = viewsFolder.First().GetDirectories("MacroPartials"); - if (macroPartials.Any()) - { - foundMacroPartials = foundMacroPartials.Concat( - GetPartialViewFiles(macroPartials.First().FullName, macroPartials.First().FullName, SystemDirectories.AppPlugins + "/" + d.Name + "/Views/MacroPartials")); - } - } - } - + var appPluginsFolder = new DirectoryInfo(IOHelper.MapPath(SystemDirectories.AppPlugins)); + if (appPluginsFolder.Exists) + { + foreach (var d in appPluginsFolder.GetDirectories()) + { + var viewsFolder = d.GetDirectories("Views"); + if (viewsFolder.Any()) + { + var macroPartials = viewsFolder.First().GetDirectories("MacroPartials"); + if (macroPartials.Any()) + { + foundMacroPartials = foundMacroPartials.Concat( + GetPartialViewFiles(macroPartials.First().FullName, macroPartials.First().FullName, SystemDirectories.AppPlugins + "/" + d.Name + "/Views/MacroPartials")); + } + } + } + } PartialViewList.DataSource = foundMacroPartials; diff --git a/src/Umbraco.Web.UI/umbraco/developer/Packages/installedPackage.aspx b/src/Umbraco.Web.UI/umbraco/developer/Packages/installedPackage.aspx index ac18284a34..44b2991ada 100644 --- a/src/Umbraco.Web.UI/umbraco/developer/Packages/installedPackage.aspx +++ b/src/Umbraco.Web.UI/umbraco/developer/Packages/installedPackage.aspx @@ -148,10 +148,37 @@ - -
    -

    <%= umbraco.ui.Text("packager", "packageUninstalledText") %>

    + + +
    + Package uninstall in progress, please wait while the browser is reloaded...
    + + + +
    diff --git a/src/Umbraco.Web.UI/umbraco/developer/Packages/installer.aspx b/src/Umbraco.Web.UI/umbraco/developer/Packages/installer.aspx index 27ccf1cd63..a7fe23357d 100644 --- a/src/Umbraco.Web.UI/umbraco/developer/Packages/installer.aspx +++ b/src/Umbraco.Web.UI/umbraco/developer/Packages/installer.aspx @@ -294,6 +294,14 @@ + + + +

    <%= umbraco.ui.Text("packager", "packageUninstalledText") %>

    + +
    +
    + diff --git a/src/Umbraco.Web.UI/umbraco/developer/RelationTypes/EditRelationType.aspx b/src/Umbraco.Web.UI/umbraco/developer/RelationTypes/EditRelationType.aspx index 94292344c7..e0559a3807 100644 --- a/src/Umbraco.Web.UI/umbraco/developer/RelationTypes/EditRelationType.aspx +++ b/src/Umbraco.Web.UI/umbraco/developer/RelationTypes/EditRelationType.aspx @@ -28,8 +28,8 @@ table.relations td.DataType {} /* direction icons */ - table.relations td.parentToChild { background-image: url('/umbraco/developer/RelationTypes/Images/ParentToChild.png'); } - table.relations td.bidirectional { background-image: url('/umbraco/developer/RelationTypes/Images/Bidirectional.png'); } + table.relations td.parentToChild { background-image: url('../../developer/RelationTypes/Images/ParentToChild.png'); } + table.relations td.bidirectional { background-image: url('../../developer/RelationTypes/Images/Bidirectional.png'); } @@ -142,4 +142,4 @@ - \ No newline at end of file + diff --git a/src/Umbraco.Web.UI/umbraco/developer/RelationTypes/TreeMenu/ActionDeleteRelationType.js b/src/Umbraco.Web.UI/umbraco/developer/RelationTypes/TreeMenu/ActionDeleteRelationType.js index a776e01632..2a2d36eda1 100644 --- a/src/Umbraco.Web.UI/umbraco/developer/RelationTypes/TreeMenu/ActionDeleteRelationType.js +++ b/src/Umbraco.Web.UI/umbraco/developer/RelationTypes/TreeMenu/ActionDeleteRelationType.js @@ -3,7 +3,7 @@ if (confirm('Are you sure you want to delete "' + relationTypeName + '"?')) { $.ajax({ type: "POST", - url: "/umbraco/developer/RelationTypes/RelationTypesWebService.asmx/DeleteRelationType", + url: "developer/RelationTypes/RelationTypesWebService.asmx/DeleteRelationType", data: "{ 'relationTypeId' : '" + relationTypeId + "' }", contentType: "application/json; charset=utf-8", dataType: "json", diff --git a/src/Umbraco.Web.UI/umbraco/dialogs/AssignDomain2.aspx b/src/Umbraco.Web.UI/umbraco/dialogs/AssignDomain2.aspx index 9a69576706..3346927b0f 100644 --- a/src/Umbraco.Web.UI/umbraco/dialogs/AssignDomain2.aspx +++ b/src/Umbraco.Web.UI/umbraco/dialogs/AssignDomain2.aspx @@ -40,6 +40,7 @@ + <%=umbraco.ui.Text("assignDomain", "domainHelp") %> @@ -51,7 +52,7 @@ - + @@ -60,7 +61,6 @@ - <%=umbraco.ui.Text("assignDomain", "domainHelp") %> diff --git a/src/Umbraco.Web.UI/umbraco/dialogs/RegexWs.aspx b/src/Umbraco.Web.UI/umbraco/dialogs/RegexWs.aspx index 6dd0954156..c2cbe45978 100644 --- a/src/Umbraco.Web.UI/umbraco/dialogs/RegexWs.aspx +++ b/src/Umbraco.Web.UI/umbraco/dialogs/RegexWs.aspx @@ -1,4 +1,5 @@ <%@ Page Language="C#" AutoEventWireup="true" MasterPageFile="../masterpages/umbracoDialog.Master" CodeBehind="RegexWs.aspx.cs" Inherits="umbraco.presentation.dialogs.RegexWs" %> +<%@ Import Namespace="Umbraco.Web" %> <%@ Register TagPrefix="cc1" Namespace="umbraco.uicontrols" Assembly="controls" %> <%@ Register TagPrefix="umb" Namespace="ClientDependency.Core.Controls" Assembly="ClientDependency.Core" %> @@ -40,7 +41,7 @@ - diff --git a/src/Umbraco.Web.UI/umbraco/settings/EditNodeTypeNew.aspx b/src/Umbraco.Web.UI/umbraco/settings/EditNodeTypeNew.aspx deleted file mode 100644 index b2ed9df718..0000000000 --- a/src/Umbraco.Web.UI/umbraco/settings/EditNodeTypeNew.aspx +++ /dev/null @@ -1,57 +0,0 @@ -<%@ Page Language="c#" CodeBehind="EditNodeTypeNew.aspx.cs" AutoEventWireup="True" ValidateRequest="false" - Async="true" AsyncTimeOut="300" Trace="false" Inherits="Umbraco.Web.UI.Umbraco.Settings.EditNodeTypeNew" MasterPageFile="../masterpages/umbracoPage.Master" %> - -<%@ Register TagPrefix="cc1" Namespace="umbraco.uicontrols" Assembly="controls" %> -<%@ Register TagPrefix="uc1" TagName="ContentTypeControlNew" Src="../controls/ContentTypeControlNew.ascx" %> - - - - - - - - - - -
    - -
    -
    - - - -
    -
    diff --git a/src/Umbraco.Web.UI/umbraco/settings/EditNodeTypeNew.aspx.cs b/src/Umbraco.Web.UI/umbraco/settings/EditNodeTypeNew.aspx.cs deleted file mode 100644 index ca2156abf1..0000000000 --- a/src/Umbraco.Web.UI/umbraco/settings/EditNodeTypeNew.aspx.cs +++ /dev/null @@ -1,14 +0,0 @@ - -//TODO: This needs to be rebuild in angular and then removed - -//using System; -//using System.Collections.Generic; -//using System.Linq; -//using System.Web; - -//namespace Umbraco.Web.UI.Umbraco.Settings -//{ -// public partial class EditNodeTypeNew : global::umbraco.settings.EditContentTypeNew -// { -// } -//} \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco/settings/EditNodeTypeNew.aspx.designer.cs b/src/Umbraco.Web.UI/umbraco/settings/EditNodeTypeNew.aspx.designer.cs deleted file mode 100644 index e322b494bd..0000000000 --- a/src/Umbraco.Web.UI/umbraco/settings/EditNodeTypeNew.aspx.designer.cs +++ /dev/null @@ -1,33 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Umbraco.Web.UI.Umbraco.Settings { - - - public partial class EditNodeTypeNew { - - /// - /// allowedTemplates control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::umbraco.uicontrols.PropertyPanel allowedTemplates; - - /// - /// defaultTemplate control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::umbraco.uicontrols.PropertyPanel defaultTemplate; - } -} diff --git a/src/Umbraco.Web.UI/umbraco/settings/scripts/editScript.aspx b/src/Umbraco.Web.UI/umbraco/settings/scripts/editScript.aspx index e68922bd98..be0d68d3ba 100644 --- a/src/Umbraco.Web.UI/umbraco/settings/scripts/editScript.aspx +++ b/src/Umbraco.Web.UI/umbraco/settings/scripts/editScript.aspx @@ -1,6 +1,7 @@ <%@ Page Language="C#" MasterPageFile="../../masterpages/umbracoPage.Master" AutoEventWireup="true" CodeBehind="editScript.aspx.cs" Inherits="umbraco.cms.presentation.settings.scripts.editScript" ValidateRequest="False" %> +<%@ Import Namespace="Umbraco.Core" %> <%@ Register TagPrefix="cc1" Namespace="umbraco.uicontrols" Assembly="controls" %> <%@ Register TagPrefix="umb" Namespace="ClientDependency.Core.Controls" Assembly="ClientDependency.Core" %> @@ -21,13 +22,10 @@ nameTxtBox: $('#<%= NameTxt.ClientID %>'), originalFileName: '<%= NameTxt.Text %>', saveButton: $("#<%= ((Control)SaveButton).ClientID %>"), + restServiceLocation: "<%= Url.GetSaveFileServicePath() %>", editorSourceElement: $('#<%= editorSource.ClientID %>'), - text: { - fileErrorHeader: '<%= HttpUtility.JavaScriptStringEncode(umbraco.ui.Text("speechBubbles", "fileErrorHeader")) %>', - fileSavedHeader: '<%= HttpUtility.JavaScriptStringEncode(umbraco.ui.Text("speechBubbles", "fileSavedHeader")) %>', - fileSavedText: '', - fileErrorText: '<%= HttpUtility.JavaScriptStringEncode(umbraco.ui.Text("speechBubbles", "fileErrorText")) %>', - } + treeSyncPath: "<%= ScriptTreeSyncPath %>", + lttPathElement: $('#<%= lttPath.ClientID %>') }); editor.init(); diff --git a/src/Umbraco.Web.UI/umbraco/settings/stylesheet/editstylesheet.aspx b/src/Umbraco.Web.UI/umbraco/settings/stylesheet/editstylesheet.aspx index 55b243c94c..2f40b04ae0 100644 --- a/src/Umbraco.Web.UI/umbraco/settings/stylesheet/editstylesheet.aspx +++ b/src/Umbraco.Web.UI/umbraco/settings/stylesheet/editstylesheet.aspx @@ -1,5 +1,6 @@ <%@ Page Language="c#" MasterPageFile="../../masterpages/umbracoPage.Master" CodeBehind="editstylesheet.aspx.cs" AutoEventWireup="True" Inherits="Umbraco.Web.UI.Umbraco.Settings.Stylesheet.EditStyleSheet" ValidateRequest="False" %> +<%@ Import Namespace="Umbraco.Core" %> <%@ Register TagPrefix="cc1" Namespace="umbraco.uicontrols" Assembly="controls" %> <%@ Register TagPrefix="cdf" Namespace="ClientDependency.Core.Controls" Assembly="ClientDependency.Core" %> @@ -13,16 +14,12 @@ $(document).ready(function () { var editor = new Umbraco.Editors.EditStyleSheet({ nameTxtBox: $('#<%= NameTxt.ClientID %>'), - originalFileName: '<%= NameTxt.Text.Replace("\\", "\\\\") %>', - cssId: '<%= Request.QueryString["id"] %>', + originalFileName: '<%= NameTxt.Text %>', saveButton: $("#<%= ((Control)SaveButton).ClientID %>"), editorSourceElement: $('#<%= editorSource.ClientID %>'), - text: { - cssErrorHeader: '<%= HttpUtility.JavaScriptStringEncode(umbraco.ui.Text("speechBubbles", "cssErrorHeader")) %>', - cssSavedHeader: '<%= HttpUtility.JavaScriptStringEncode(umbraco.ui.Text("speechBubbles", "cssSavedHeader")) %>', - cssSavedText: '<%= HttpUtility.JavaScriptStringEncode(umbraco.ui.Text("speechBubbles", "cssSavedText")) %>', - cssErrorText: 'Please make sure that you have permissions set correctly', - } + restServiceLocation: "<%= Url.GetSaveFileServicePath() %>", + treeSyncPath: "<%= TreeSyncPath %>", + lttPathElement: $('#<%= lttPath.ClientID %>') }); editor.init(); diff --git a/src/Umbraco.Web.UI/umbraco/settings/views/EditView.aspx b/src/Umbraco.Web.UI/umbraco/settings/views/EditView.aspx index ba464262d0..6f5518706c 100644 --- a/src/Umbraco.Web.UI/umbraco/settings/views/EditView.aspx +++ b/src/Umbraco.Web.UI/umbraco/settings/views/EditView.aspx @@ -6,6 +6,7 @@ <%@ Import Namespace="Umbraco.Core" %> <%@ Import Namespace="Umbraco.Core.IO" %> +<%@ Import Namespace="Umbraco.Web" %> <%@ Register TagPrefix="cc1" Namespace="umbraco.uicontrols" Assembly="controls" %> <%@ Register TagPrefix="umb" Namespace="ClientDependency.Core.Controls" Assembly="ClientDependency.Core" %> @@ -35,7 +36,7 @@ nameTxtBox: $("#<%= NameTxt.ClientID %>"), aliasTxtBox: $("#<%= AliasTxt.ClientID %>"), saveButton: $("#<%= ((Control)SaveButton).ClientID %>"), - templateId: '<%= Request.QueryString["templateID"] %>', + templateId: '<%= Request.CleanForXss("templateID") %>', codeEditorElementId: '<%= editorSource.ClientID %>', modalUrl: "<%= IOHelper.ResolveUrl(SystemDirectories.Umbraco) %>/dialogs/editMacro.aspx" }); @@ -66,6 +67,7 @@ + diff --git a/src/Umbraco.Web.UI/umbraco/settings/views/EditView.aspx.cs b/src/Umbraco.Web.UI/umbraco/settings/views/EditView.aspx.cs index e4d0692385..c08e4ba48c 100644 --- a/src/Umbraco.Web.UI/umbraco/settings/views/EditView.aspx.cs +++ b/src/Umbraco.Web.UI/umbraco/settings/views/EditView.aspx.cs @@ -66,7 +66,7 @@ namespace Umbraco.Web.UI.Umbraco.Settings.Views { return TreeDefinitionCollection.Instance.FindTree().Tree.Alias; } - return Request.QueryString["treeType"]; + return Request.CleanForXss("treeType"); } } @@ -114,28 +114,23 @@ namespace Umbraco.Web.UI.Umbraco.Settings.Views NameTxt.Text = _template.GetRawText(); AliasTxt.Text = _template.Alias; editorSource.Text = _template.Design; - + PathPrefix.Visible = false; } else { //configure editor for editing a file.... NameTxt.Text = OriginalFileName; - var file = IOHelper.MapPath(SystemDirectories.MvcViews.EnsureEndsWith('/') + OriginalFileName); + var svce = ApplicationContext.Current.Services.FileService; + var file = EditorType == ViewEditorType.PartialView + ? svce.GetPartialView(OriginalFileName) + : svce.GetPartialViewMacro(OriginalFileName); + editorSource.Text = file.Content; - // validate file path - if (file.StartsWith(IOHelper.MapPath(SystemDirectories.MvcViews.EnsureEndsWith('/')))) { - - using (var sr = File.OpenText(file)) - { - var s = sr.ReadToEnd(); - editorSource.Text = s; - } - } else - { - throw new ArgumentException("Couldn't open file - illegal path"); - } - + const string prefixFormat = "{0}"; + PathPrefix.Text = string.Format(prefixFormat, EditorType == ViewEditorType.PartialView + ? "Partials/" + : "MacroPartials/"); } } @@ -253,29 +248,33 @@ namespace Umbraco.Web.UI.Umbraco.Settings.Views /// private void InitializeEditorForTemplate() { - - //TODO: implement content placeholders, etc... just like we had in v5 - //Panel1.Menu.InsertSplitter(); + //TODO: implement content placeholders, etc... just like we had in v5 - //MenuIconI umbContainer = Panel1.Menu.NewIcon(); - //umbContainer.ImageURL = UmbracoPath + "/images/editor/masterpagePlaceHolder.gif"; - //umbContainer.AltText = ui.Text("template", "insertContentAreaPlaceHolder"); - //umbContainer.OnClickCommand = - // ClientTools.Scripts.OpenModalWindow( - // IOHelper.ResolveUrl(SystemDirectories.Umbraco) + - // "/dialogs/insertMasterpagePlaceholder.aspx?&id=" + _template.Id, - // ui.Text("template", "insertContentAreaPlaceHolder"), 470, 320); + editorSource.Menu.InsertSplitter(); - //MenuIconI umbContent = Panel1.Menu.NewIcon(); - //umbContent.ImageURL = UmbracoPath + "/images/editor/masterpageContent.gif"; - //umbContent.AltText = ui.Text("template", "insertContentArea"); - //umbContent.OnClickCommand = - // ClientTools.Scripts.OpenModalWindow( - // IOHelper.ResolveUrl(SystemDirectories.Umbraco) + "/dialogs/insertMasterpageContent.aspx?id=" + - // _template.Id, ui.Text("template", "insertContentArea"), 470, 300); - - } + MenuIconI umbRenderBody = editorSource.Menu.NewIcon(); + umbRenderBody.ImageURL = UmbracoPath + "/images/editor/renderbody.gif"; + //umbContainer.AltText = ui.Text("template", "insertContentAreaPlaceHolder"); + umbRenderBody.AltText = "Insert @RenderBody()"; - } + umbRenderBody.OnClickCommand = "editViewEditor.insertRenderBody()"; + + MenuIconI umbSection = editorSource.Menu.NewIcon(); + umbSection.ImageURL = UmbracoPath + "/images/editor/masterpagePlaceHolder.gif"; + //umbContainer.AltText = ui.Text("template", "insertContentAreaPlaceHolder"); + umbSection.AltText = "Insert Section"; + + umbSection.OnClickCommand = "editViewEditor.openSnippetModal('section')"; + + MenuIconI umbRenderSection = editorSource.Menu.NewIcon(); + umbRenderSection.ImageURL = UmbracoPath + "/images/editor/masterpageContent.gif"; + //umbContainer.AltText = ui.Text("template", "insertContentAreaPlaceHolder"); + umbRenderSection.AltText = "Insert @RenderSection"; + + umbRenderSection.OnClickCommand = "editViewEditor.openSnippetModal('rendersection')"; + + } + + } } \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco/settings/views/EditView.aspx.designer.cs b/src/Umbraco.Web.UI/umbraco/settings/views/EditView.aspx.designer.cs index 4f523639a3..dd81da023a 100644 --- a/src/Umbraco.Web.UI/umbraco/settings/views/EditView.aspx.designer.cs +++ b/src/Umbraco.Web.UI/umbraco/settings/views/EditView.aspx.designer.cs @@ -75,6 +75,15 @@ namespace Umbraco.Web.UI.Umbraco.Settings.Views { /// protected global::umbraco.uicontrols.PropertyPanel pp_name; + /// + /// PathPrefix control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal PathPrefix; + /// /// NameTxt control. /// diff --git a/src/Umbraco.Web.UI/umbraco/webService.asmx b/src/Umbraco.Web.UI/umbraco/webService.asmx deleted file mode 100644 index 19848579d6..0000000000 --- a/src/Umbraco.Web.UI/umbraco/webService.asmx +++ /dev/null @@ -1 +0,0 @@ -<%@ WebService Language="c#" Codebehind="webService.asmx.cs" Class="umbraco.webService" %> diff --git a/src/Umbraco.Web.UI/umbraco_client/Application/Extensions.js b/src/Umbraco.Web.UI/umbraco_client/Application/Extensions.js index 66c97c4db7..63870fad08 100644 --- a/src/Umbraco.Web.UI/umbraco_client/Application/Extensions.js +++ b/src/Umbraco.Web.UI/umbraco_client/Application/Extensions.js @@ -357,4 +357,20 @@ }); + //This sets the default jquery ajax headers to include our csrf token, we + // need to user the beforeSend method because our token changes per user/login so + // it cannot be static + $.ajaxSetup({ + beforeSend: function (xhr) { + + function getCookie(name) { + var value = "; " + document.cookie; + var parts = value.split("; " + name + "="); + if (parts.length === 2) return parts.pop().split(";").shift(); + } + + xhr.setRequestHeader("X-XSRF-TOKEN", getCookie("XSRF-TOKEN")); + } + }); + })(jQuery); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeArea/javascript.js b/src/Umbraco.Web.UI/umbraco_client/CodeArea/javascript.js index 6d37c171be..77c5557286 100644 --- a/src/Umbraco.Web.UI/umbraco_client/CodeArea/javascript.js +++ b/src/Umbraco.Web.UI/umbraco_client/CodeArea/javascript.js @@ -8,23 +8,6 @@ function resizeTextArea(textEditor, offsetX, offsetY) { } } -var currentHandle = null, currentLine; -function updateLineInfo(cm) { - - var line = cm.getCursor().line, handle = cm.getLineHandle(line); - if (handle == currentHandle && line == currentLine) return; - - if (currentHandle) { - cm.setLineClass(currentHandle, null, null); - // cm.clearMarker(currentHandle); - } - - currentHandle = handle; currentLine = line; - cm.setLineClass(currentHandle, null, "activeline"); - //cm.setMarker(currentHandle, String(line + 1)); -} - - function UmbracoCodeSnippet() { this.BeginTag = ""; this.EndTag = ""; @@ -36,7 +19,7 @@ function UmbracoCodeSnippet() { // Ctrl + S support var ctrlDown = false; var shiftDown = false; -var keycode = 0 +var keycode = 0; function shortcutCheckKeysDown(e) { diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeArea/styles.css b/src/Umbraco.Web.UI/umbraco_client/CodeArea/styles.css index cfbd5f220e..0a64e1e40e 100644 --- a/src/Umbraco.Web.UI/umbraco_client/CodeArea/styles.css +++ b/src/Umbraco.Web.UI/umbraco_client/CodeArea/styles.css @@ -2,7 +2,7 @@ border: none !Important; font-size: 15px !Important; } - .CodeMirror-gutter{background: none !Important; border: none; padding-rigth: 5px;} + .CodeMirror-gutter{background: none !Important; border: none; padding-right: 5px;} span.cm-at{background: yellow !Important;} textarea.codepress{display: block; width: 100% !Important; font-size: 14px;} \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/LICENSE b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/LICENSE deleted file mode 100644 index 1598230894..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/LICENSE +++ /dev/null @@ -1,23 +0,0 @@ - Copyright (c) 2007-2009 Marijn Haverbeke - - This software is provided 'as-is', without any express or implied - warranty. In no event will the authors be held liable for any - damages arising from the use of this software. - - Permission is granted to anyone to use this software for any - purpose, including commercial applications, and to alter it and - redistribute it freely, subject to the following restrictions: - - 1. The origin of this software must not be misrepresented; you must - not claim that you wrote the original software. If you use this - software in a product, an acknowledgment in the product - documentation would be appreciated but is not required. - - 2. Altered source versions must be plainly marked as such, and must - not be misrepresented as being the original software. - - 3. This notice may not be removed or altered from any source - distribution. - - Marijn Haverbeke - marijnh at gmail diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/css/csscolors.css b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/css/csscolors.css deleted file mode 100644 index b4b0a7d078..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/css/csscolors.css +++ /dev/null @@ -1,55 +0,0 @@ -html { - cursor: text; -} - -.editbox { - margin: .4em; - padding: 0; - font-family: monospace; - font-size: 10pt; - color: black; -} - -pre.code, .editbox { - color: #666; -} - -.editbox p { - margin: 0; -} - -span.css-at { - color: #708; -} - -span.css-unit { - color: #281; -} - -span.css-value { - color: #708; -} - -span.css-identifier { - color: black; -} - -span.css-selector { - color: #11B; -} - -span.css-important { - color: #00F; -} - -span.css-colorcode { - color: #299; -} - -span.css-comment { - color: #A70; -} - -span.css-string { - color: #A22; -} diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/css/docs.css b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/css/docs.css deleted file mode 100644 index 6ed872e819..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/css/docs.css +++ /dev/null @@ -1,158 +0,0 @@ -body { - font-family: Arial, sans-serif; - line-height: 1.5; - max-width: 64.3em; - margin: 3em auto; - padding: 0 1em; -} -body.droid { - font-family: Droid Sans, Arial, sans-serif; -} - -h1 { - letter-spacing: -3px; - font-size: 3.23em; - font-weight: bold; - margin: 0; -} - -h2 { - font-size: 1.23em; - font-weight: bold; - margin: .5em 0; - letter-spacing: -1px; -} - -h3 { - font-size: 1em; - font-weight: bold; - margin: .4em 0; -} - -pre { - font-family: Courier New, monospaced; - background-color: #eee; - -moz-border-radius: 6px; - -webkit-border-radius: 6px; - border-radius: 6px; - padding: 1em; -} - -pre.code { - margin: 0 1em; -} - -.grey { - font-size: 2em; - padding: .5em 1em; - line-height: 1.2em; - margin-top: .5em; - position: relative; -} - -img.logo { - position: absolute; - right: -25px; - bottom: 4px; -} - -a:link, a:visited, .quasilink { - color: #df0019; - cursor: pointer; - text-decoration: none; -} - -a:hover, .quasilink:hover { - color: #800004; -} - -h1 a:link, h1 a:visited, h1 a:hover { - color: black; -} - -ul { - margin: 0; - padding-left: 1.2em; -} - -a.download { - color: white; - background-color: #df0019; - width: 100%; - display: block; - text-align: center; - font-size: 1.23em; - font-weight: bold; - text-decoration: none; - -moz-border-radius: 6px; - -webkit-border-radius: 6px; - border-radius: 6px; - padding: .5em 0; - margin-bottom: 1em; -} - -a.download:hover { - background-color: #bb0010; -} - -.rel { - margin-bottom: 0; -} - -.rel-note { - color: #777; - font-size: .9em; - margin-top: .1em; -} - -.logo-braces { - color: #df0019; - position: relative; - top: -4px; -} - -.blk { - float: left; -} - -.left { - width: 37em; - padding-right: 6.53em; - padding-bottom: 1em; -} - -.left1 { - width: 15.24em; - padding-right: 6.45em; -} - -.left2 { - width: 15.24em; -} - -.right { - width: 20.68em; -} - -.leftbig { - width: 42.44em; - padding-right: 6.53em; -} - -.rightsmall { - width: 15.24em; -} - -.clear:after { - visibility: hidden; - display: block; - font-size: 0; - content: " "; - clear: both; - height: 0; -} -.clear { display: inline-block; } -/* start commented backslash hack \*/ -* html .clear { height: 1%; } -.clear { display: block; } -/* close commented backslash hack */ diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/css/jscolors.css b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/css/jscolors.css deleted file mode 100644 index 915d740d18..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/css/jscolors.css +++ /dev/null @@ -1,59 +0,0 @@ -html { - cursor: text; -} - -.editbox { - margin: .4em; - padding: 0; - font-family: monospace; - font-size: 10pt; - color: black; -} - -pre.code, .editbox { - color: #666666; -} - -.editbox p { - margin: 0; -} - -span.js-punctuation { - color: #666666; -} - -span.js-operator { - color: #666666; -} - -span.js-keyword { - color: #770088; -} - -span.js-atom { - color: #228811; -} - -span.js-variable { - color: black; -} - -span.js-variabledef { - color: #0000FF; -} - -span.js-localvariable { - color: #004499; -} - -span.js-property { - color: black; -} - -span.js-comment { - color: #AA7700; -} - -span.js-string { - color: #AA2222; -} diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/css/people.jpg b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/css/people.jpg deleted file mode 100644 index 734789542b..0000000000 Binary files a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/css/people.jpg and /dev/null differ diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/css/pythoncolors.css b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/css/pythoncolors.css deleted file mode 100644 index 14e6727f32..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/css/pythoncolors.css +++ /dev/null @@ -1,54 +0,0 @@ -.editbox { - padding: .4em; - margin: 0; - font-family: monospace; - font-size: 10pt; - line-height: 1.1em; - color: black; -} - -pre.code, .editbox { - color: #666666; -} - -.editbox p { - margin: 0; -} - -span.py-delimiter, span.py-special { - color: #666666; -} - -span.py-operator { - color: #666666; -} - -span.py-error { - background-color: #660000; - color: #FFFFFF; -} - -span.py-keyword { - color: #770088; - font-weight: bold; -} - -span.py-literal { - color: #228811; -} - -span.py-identifier, span.py-func { - color: black; -} - -span.py-type, span.py-decorator { - color: #0000FF; -} - -span.py-comment { - color: #AA7700; -} - -span.py-string, span.py-bytes, span.py-raw, span.py-unicode { - color: #AA2222; -} diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/css/sparqlcolors.css b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/css/sparqlcolors.css deleted file mode 100644 index dd93c9a0bb..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/css/sparqlcolors.css +++ /dev/null @@ -1,43 +0,0 @@ -html { - cursor: text; -} - -.editbox { - margin: .4em; - padding: 0; - font-family: monospace; - font-size: 10pt; - color: black; -} - -.editbox p { - margin: 0; -} - -span.sp-keyword { - color: #708; -} - -span.sp-prefixed { - color: #5d1; -} - -span.sp-var { - color: #00c; -} - -span.sp-comment { - color: #a70; -} - -span.sp-literal { - color: #a22; -} - -span.sp-uri { - color: #292; -} - -span.sp-operator { - color: #088; -} diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/css/umbracoCustom.css b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/css/umbracoCustom.css deleted file mode 100644 index 89ee4921ba..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/css/umbracoCustom.css +++ /dev/null @@ -1,3 +0,0 @@ -.CodeMirror, .CodeMirror-scroll { - height: 100%; -} \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/css/xmlcolors.css b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/css/xmlcolors.css deleted file mode 100644 index d9fb3705f2..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/css/xmlcolors.css +++ /dev/null @@ -1,55 +0,0 @@ -html { - cursor: text; -} - -.editbox { - margin: .4em; - padding: 0; - font-family: monospace; - font-size: 10pt; - color: black; -} - -.editbox p { - margin: 0; -} - -span.xml-tagname { - color: #A0B; -} - -span.xml-attribute { - color: #281; -} - -span.xml-punctuation { - color: black; -} - -span.xml-attname { - color: #00F; -} - -span.xml-comment { - color: #A70; -} - -span.xml-cdata { - color: #48A; -} - -span.xml-processing { - color: #999; -} - -span.xml-entity { - color: #A22; -} - -span.xml-error { - color: #F00 !important; -} - -span.xml-text { - color: black; -} diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/codemirror.css b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/codemirror.css deleted file mode 100644 index 6fdf23e2b2..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/codemirror.css +++ /dev/null @@ -1,173 +0,0 @@ -.CodeMirror { - line-height: 1em; - font-family: monospace; - - /* Necessary so the scrollbar can be absolutely positioned within the wrapper on Lion. */ - position: relative; - /* This prevents unwanted scrollbars from showing up on the body and wrapper in IE. */ - overflow: hidden; -} - -.CodeMirror-scroll { - overflow: auto; - height: 300px; - /* This is needed to prevent an IE[67] bug where the scrolled content - is visible outside of the scrolling box. */ - position: relative; - outline: none; -} - -/* Vertical scrollbar */ -.CodeMirror-scrollbar { - position: absolute; - right: 0; top: 0; - overflow-x: hidden; - overflow-y: scroll; - z-index: 5; -} -.CodeMirror-scrollbar-inner { - /* This needs to have a nonzero width in order for the scrollbar to appear - in Firefox and IE9. */ - width: 1px; -} -.CodeMirror-scrollbar.cm-sb-overlap { - /* Ensure that the scrollbar appears in Lion, and that it overlaps the content - rather than sitting to the right of it. */ - position: absolute; - z-index: 1; - float: none; - right: 0; - min-width: 12px; -} -.CodeMirror-scrollbar.cm-sb-nonoverlap { - min-width: 12px; -} -.CodeMirror-scrollbar.cm-sb-ie7 { - min-width: 18px; -} - -.CodeMirror-gutter { - position: absolute; left: 0; top: 0; - z-index: 10; - background-color: #f7f7f7; - border-right: 1px solid #eee; - min-width: 2em; - height: 100%; -} -.CodeMirror-gutter-text { - color: #aaa; - text-align: right; - padding: .4em .2em .4em .4em; - white-space: pre !important; - cursor: default; -} -.CodeMirror-lines { - padding: .4em; - white-space: pre; - cursor: text; -} - -.CodeMirror pre { - -moz-border-radius: 0; - -webkit-border-radius: 0; - -o-border-radius: 0; - border-radius: 0; - border-width: 0; margin: 0; padding: 0; background: transparent; - font-family: inherit; - font-size: inherit; - padding: 0; margin: 0; - white-space: pre; - word-wrap: normal; - line-height: inherit; - color: inherit; -} - -.CodeMirror-wrap pre { - word-wrap: break-word; - white-space: pre-wrap; - word-break: normal; -} -.CodeMirror-wrap .CodeMirror-scroll { - overflow-x: hidden; -} - -.CodeMirror textarea { - outline: none !important; -} - -.CodeMirror pre.CodeMirror-cursor { - z-index: 10; - position: absolute; - visibility: hidden; - border-left: 1px solid black; - border-right: none; - width: 0; -} -.cm-keymap-fat-cursor pre.CodeMirror-cursor { - width: auto; - border: 0; - background: transparent; - background: rgba(0, 200, 0, .4); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#6600c800, endColorstr=#4c00c800); -} -/* Kludge to turn off filter in ie9+, which also accepts rgba */ -.cm-keymap-fat-cursor pre.CodeMirror-cursor:not(#nonsense_id) { - filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); -} -.CodeMirror pre.CodeMirror-cursor.CodeMirror-overwrite {} -.CodeMirror-focused pre.CodeMirror-cursor { - visibility: visible; -} - -div.CodeMirror-selected { background: #d9d9d9; } -.CodeMirror-focused div.CodeMirror-selected { background: #d7d4f0; } - -.CodeMirror-searching { - background: #ffa; - background: rgba(255, 255, 0, .4); -} - -/* Default theme */ - -.cm-s-default span.cm-keyword {color: #708;} -.cm-s-default span.cm-atom {color: #219;} -.cm-s-default span.cm-number {color: #164;} -.cm-s-default span.cm-def {color: #00f;} -.cm-s-default span.cm-variable {color: black;} -.cm-s-default span.cm-variable-2 {color: #05a;} -.cm-s-default span.cm-variable-3 {color: #085;} -.cm-s-default span.cm-property {color: black;} -.cm-s-default span.cm-operator {color: black;} -.cm-s-default span.cm-comment {color: #a50;} -.cm-s-default span.cm-string {color: #a11;} -.cm-s-default span.cm-string-2 {color: #f50;} -.cm-s-default span.cm-meta {color: #555;} -.cm-s-default span.cm-error {color: #f00;} -.cm-s-default span.cm-qualifier {color: #555;} -.cm-s-default span.cm-builtin {color: #30a;} -.cm-s-default span.cm-bracket {color: #997;} -.cm-s-default span.cm-tag {color: #170;} -.cm-s-default span.cm-attribute {color: #00c;} -.cm-s-default span.cm-header {color: blue;} -.cm-s-default span.cm-quote {color: #090;} -.cm-s-default span.cm-hr {color: #999;} -.cm-s-default span.cm-link {color: #00c;} - -span.cm-header, span.cm-strong {font-weight: bold;} -span.cm-em {font-style: italic;} -span.cm-emstrong {font-style: italic; font-weight: bold;} -span.cm-link {text-decoration: underline;} - -span.cm-invalidchar {color: #f00;} - -div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} -div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} - -@media print { - - /* Hide the cursor when printing */ - .CodeMirror pre.CodeMirror-cursor { - visibility: hidden; - } - -} diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/codemirror.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/codemirror.js deleted file mode 100755 index f61f04a5dd..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/codemirror.js +++ /dev/null @@ -1,3147 +0,0 @@ -// CodeMirror version 2.34 - -// All functions that need access to the editor's state live inside -// the CodeMirror function. Below that, at the bottom of the file, -// some utilities are defined. - -// CodeMirror is the only global var we claim -window.CodeMirror = (function() { - "use strict"; - // This is the function that produces an editor instance. Its - // closure is used to store the editor state. - function CodeMirror(place, givenOptions) { - // Determine effective options based on given values and defaults. - var options = {}, defaults = CodeMirror.defaults; - for (var opt in defaults) - if (defaults.hasOwnProperty(opt)) - options[opt] = (givenOptions && givenOptions.hasOwnProperty(opt) ? givenOptions : defaults)[opt]; - - var input = elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em"); - input.setAttribute("wrap", "off"); input.setAttribute("autocorrect", "off"); input.setAttribute("autocapitalize", "off"); - // Wraps and hides input textarea - var inputDiv = elt("div", [input], null, "overflow: hidden; position: relative; width: 3px; height: 0px;"); - // The empty scrollbar content, used solely for managing the scrollbar thumb. - var scrollbarInner = elt("div", null, "CodeMirror-scrollbar-inner"); - // The vertical scrollbar. Horizontal scrolling is handled by the scroller itself. - var scrollbar = elt("div", [scrollbarInner], "CodeMirror-scrollbar"); - // DIVs containing the selection and the actual code - var lineDiv = elt("div"), selectionDiv = elt("div", null, null, "position: relative; z-index: -1"); - // Blinky cursor, and element used to ensure cursor fits at the end of a line - var cursor = elt("pre", "\u00a0", "CodeMirror-cursor"), widthForcer = elt("pre", "\u00a0", "CodeMirror-cursor", "visibility: hidden"); - // Used to measure text size - var measure = elt("div", null, null, "position: absolute; width: 100%; height: 0px; overflow: hidden; visibility: hidden;"); - var lineSpace = elt("div", [measure, cursor, widthForcer, selectionDiv, lineDiv], null, "position: relative; z-index: 0"); - var gutterText = elt("div", null, "CodeMirror-gutter-text"), gutter = elt("div", [gutterText], "CodeMirror-gutter"); - // Moved around its parent to cover visible view - var mover = elt("div", [gutter, elt("div", [lineSpace], "CodeMirror-lines")], null, "position: relative"); - // Set to the height of the text, causes scrolling - var sizer = elt("div", [mover], null, "position: relative"); - // Provides scrolling - var scroller = elt("div", [sizer], "CodeMirror-scroll"); - scroller.setAttribute("tabIndex", "-1"); - // The element in which the editor lives. - var wrapper = elt("div", [inputDiv, scrollbar, scroller], "CodeMirror" + (options.lineWrapping ? " CodeMirror-wrap" : "")); - if (place.appendChild) place.appendChild(wrapper); else place(wrapper); - - themeChanged(); keyMapChanged(); - // Needed to hide big blue blinking cursor on Mobile Safari - if (ios) input.style.width = "0px"; - if (!webkit) scroller.draggable = true; - lineSpace.style.outline = "none"; - if (options.tabindex != null) input.tabIndex = options.tabindex; - if (options.autofocus) focusInput(); - if (!options.gutter && !options.lineNumbers) gutter.style.display = "none"; - // Needed to handle Tab key in KHTML - if (khtml) inputDiv.style.height = "1px", inputDiv.style.position = "absolute"; - - // Check for OS X >= 10.7. This has transparent scrollbars, so the - // overlaying of one scrollbar with another won't work. This is a - // temporary hack to simply turn off the overlay scrollbar. See - // issue #727. - if (mac_geLion) { scrollbar.style.zIndex = -2; scrollbar.style.visibility = "hidden"; } - // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8). - else if (ie_lt8) scrollbar.style.minWidth = "18px"; - - // Delayed object wrap timeouts, making sure only one is active. blinker holds an interval. - var poll = new Delayed(), highlight = new Delayed(), blinker; - - // mode holds a mode API object. doc is the tree of Line objects, - // frontier is the point up to which the content has been parsed, - // and history the undo history (instance of History constructor). - var mode, doc = new BranchChunk([new LeafChunk([new Line("")])]), frontier = 0, focused; - loadMode(); - // The selection. These are always maintained to point at valid - // positions. Inverted is used to remember that the user is - // selecting bottom-to-top. - var sel = {from: {line: 0, ch: 0}, to: {line: 0, ch: 0}, inverted: false}; - // Selection-related flags. shiftSelecting obviously tracks - // whether the user is holding shift. - var shiftSelecting, lastClick, lastDoubleClick, lastScrollTop = 0, draggingText, - overwrite = false, suppressEdits = false; - // Variables used by startOperation/endOperation to track what - // happened during the operation. - var updateInput, userSelChange, changes, textChanged, selectionChanged, - gutterDirty, callbacks; - // Current visible range (may be bigger than the view window). - var displayOffset = 0, showingFrom = 0, showingTo = 0, lastSizeC = 0; - // bracketHighlighted is used to remember that a bracket has been - // marked. - var bracketHighlighted; - // Tracks the maximum line length so that the horizontal scrollbar - // can be kept static when scrolling. - var maxLine = getLine(0), updateMaxLine = false, maxLineChanged = true; - var pollingFast = false; // Ensures slowPoll doesn't cancel fastPoll - var goalColumn = null; - - // Initialize the content. - operation(function(){setValue(options.value || ""); updateInput = false;})(); - var history = new History(); - - // Register our event handlers. - connect(scroller, "mousedown", operation(onMouseDown)); - connect(scroller, "dblclick", operation(onDoubleClick)); - connect(lineSpace, "selectstart", e_preventDefault); - // Gecko browsers fire contextmenu *after* opening the menu, at - // which point we can't mess with it anymore. Context menu is - // handled in onMouseDown for Gecko. - if (!gecko) connect(scroller, "contextmenu", onContextMenu); - connect(scroller, "scroll", onScrollMain); - connect(scrollbar, "scroll", onScrollBar); - connect(scrollbar, "mousedown", function() {if (focused) setTimeout(focusInput, 0);}); - var resizeHandler = connect(window, "resize", function() { - if (wrapper.parentNode) updateDisplay(true); - else resizeHandler(); - }, true); - connect(input, "keyup", operation(onKeyUp)); - connect(input, "input", fastPoll); - connect(input, "keydown", operation(onKeyDown)); - connect(input, "keypress", operation(onKeyPress)); - connect(input, "focus", onFocus); - connect(input, "blur", onBlur); - - function drag_(e) { - if (options.onDragEvent && options.onDragEvent(instance, addStop(e))) return; - e_stop(e); - } - if (options.dragDrop) { - connect(scroller, "dragstart", onDragStart); - connect(scroller, "dragenter", drag_); - connect(scroller, "dragover", drag_); - connect(scroller, "drop", operation(onDrop)); - } - connect(scroller, "paste", function(){focusInput(); fastPoll();}); - connect(input, "paste", fastPoll); - connect(input, "cut", operation(function(){ - if (!options.readOnly) replaceSelection(""); - })); - - // Needed to handle Tab key in KHTML - if (khtml) connect(sizer, "mouseup", function() { - if (document.activeElement == input) input.blur(); - focusInput(); - }); - - // IE throws unspecified error in certain cases, when - // trying to access activeElement before onload - var hasFocus; try { hasFocus = (document.activeElement == input); } catch(e) { } - if (hasFocus || options.autofocus) setTimeout(onFocus, 20); - else onBlur(); - - function isLine(l) {return l >= 0 && l < doc.size;} - // The instance object that we'll return. Mostly calls out to - // local functions in the CodeMirror function. Some do some extra - // range checking and/or clipping. operation is used to wrap the - // call so that changes it makes are tracked, and the display is - // updated afterwards. - var instance = wrapper.CodeMirror = { - getValue: getValue, - setValue: operation(setValue), - getSelection: getSelection, - replaceSelection: operation(replaceSelection), - focus: function(){window.focus(); focusInput(); onFocus(); fastPoll();}, - setOption: function(option, value) { - var oldVal = options[option]; - options[option] = value; - if (option == "mode" || option == "indentUnit") loadMode(); - else if (option == "readOnly" && value == "nocursor") {onBlur(); input.blur();} - else if (option == "readOnly" && !value) {resetInput(true);} - else if (option == "theme") themeChanged(); - else if (option == "lineWrapping" && oldVal != value) operation(wrappingChanged)(); - else if (option == "tabSize") updateDisplay(true); - else if (option == "keyMap") keyMapChanged(); - if (option == "lineNumbers" || option == "gutter" || option == "firstLineNumber" || - option == "theme" || option == "lineNumberFormatter") { - gutterChanged(); - updateDisplay(true); - } - }, - getOption: function(option) {return options[option];}, - getMode: function() {return mode;}, - undo: operation(undo), - redo: operation(redo), - indentLine: operation(function(n, dir) { - if (typeof dir != "string") { - if (dir == null) dir = options.smartIndent ? "smart" : "prev"; - else dir = dir ? "add" : "subtract"; - } - if (isLine(n)) indentLine(n, dir); - }), - indentSelection: operation(indentSelected), - historySize: function() {return {undo: history.done.length, redo: history.undone.length};}, - clearHistory: function() {history = new History();}, - setHistory: function(histData) { - history = new History(); - history.done = histData.done; - history.undone = histData.undone; - }, - getHistory: function() { - function cp(arr) { - for (var i = 0, nw = [], nwelt; i < arr.length; ++i) { - nw.push(nwelt = []); - for (var j = 0, elt = arr[i]; j < elt.length; ++j) { - var old = [], cur = elt[j]; - nwelt.push({start: cur.start, added: cur.added, old: old}); - for (var k = 0; k < cur.old.length; ++k) old.push(hlText(cur.old[k])); - } - } - return nw; - } - return {done: cp(history.done), undone: cp(history.undone)}; - }, - matchBrackets: operation(function(){matchBrackets(true);}), - getTokenAt: operation(function(pos) { - pos = clipPos(pos); - return getLine(pos.line).getTokenAt(mode, getStateBefore(pos.line), options.tabSize, pos.ch); - }), - getStateAfter: function(line) { - line = clipLine(line == null ? doc.size - 1: line); - return getStateBefore(line + 1); - }, - cursorCoords: function(start, mode) { - if (start == null) start = sel.inverted; - return this.charCoords(start ? sel.from : sel.to, mode); - }, - charCoords: function(pos, mode) { - pos = clipPos(pos); - if (mode == "local") return localCoords(pos, false); - if (mode == "div") return localCoords(pos, true); - return pageCoords(pos); - }, - coordsChar: function(coords) { - var off = eltOffset(lineSpace); - return coordsChar(coords.x - off.left, coords.y - off.top); - }, - markText: operation(markText), - setBookmark: setBookmark, - findMarksAt: findMarksAt, - setMarker: operation(addGutterMarker), - clearMarker: operation(removeGutterMarker), - setLineClass: operation(setLineClass), - hideLine: operation(function(h) {return setLineHidden(h, true);}), - showLine: operation(function(h) {return setLineHidden(h, false);}), - onDeleteLine: function(line, f) { - if (typeof line == "number") { - if (!isLine(line)) return null; - line = getLine(line); - } - (line.handlers || (line.handlers = [])).push(f); - return line; - }, - lineInfo: lineInfo, - getViewport: function() { return {from: showingFrom, to: showingTo};}, - addWidget: function(pos, node, scroll, vert, horiz) { - pos = localCoords(clipPos(pos)); - var top = pos.yBot, left = pos.x; - node.style.position = "absolute"; - sizer.appendChild(node); - if (vert == "over") top = pos.y; - else if (vert == "near") { - var vspace = Math.max(scroller.offsetHeight, doc.height * textHeight()), - hspace = Math.max(sizer.clientWidth, lineSpace.clientWidth) - paddingLeft(); - if (pos.yBot + node.offsetHeight > vspace && pos.y > node.offsetHeight) - top = pos.y - node.offsetHeight; - if (left + node.offsetWidth > hspace) - left = hspace - node.offsetWidth; - } - node.style.top = (top + paddingTop()) + "px"; - node.style.left = node.style.right = ""; - if (horiz == "right") { - left = sizer.clientWidth - node.offsetWidth; - node.style.right = "0px"; - } else { - if (horiz == "left") left = 0; - else if (horiz == "middle") left = (sizer.clientWidth - node.offsetWidth) / 2; - node.style.left = (left + paddingLeft()) + "px"; - } - if (scroll) - scrollIntoView(left, top, left + node.offsetWidth, top + node.offsetHeight); - }, - - lineCount: function() {return doc.size;}, - clipPos: clipPos, - getCursor: function(start) { - if (start == null) start = sel.inverted; - return copyPos(start ? sel.from : sel.to); - }, - somethingSelected: function() {return !posEq(sel.from, sel.to);}, - setCursor: operation(function(line, ch, user) { - if (ch == null && typeof line.line == "number") setCursor(line.line, line.ch, user); - else setCursor(line, ch, user); - }), - setSelection: operation(function(from, to, user) { - (user ? setSelectionUser : setSelection)(clipPos(from), clipPos(to || from)); - }), - getLine: function(line) {if (isLine(line)) return getLine(line).text;}, - getLineHandle: function(line) {if (isLine(line)) return getLine(line);}, - setLine: operation(function(line, text) { - if (isLine(line)) replaceRange(text, {line: line, ch: 0}, {line: line, ch: getLine(line).text.length}); - }), - removeLine: operation(function(line) { - if (isLine(line)) replaceRange("", {line: line, ch: 0}, clipPos({line: line+1, ch: 0})); - }), - replaceRange: operation(replaceRange), - getRange: function(from, to, lineSep) {return getRange(clipPos(from), clipPos(to), lineSep);}, - - triggerOnKeyDown: operation(onKeyDown), - execCommand: function(cmd) {return commands[cmd](instance);}, - // Stuff used by commands, probably not much use to outside code. - moveH: operation(moveH), - deleteH: operation(deleteH), - moveV: operation(moveV), - toggleOverwrite: function() { - if(overwrite){ - overwrite = false; - cursor.className = cursor.className.replace(" CodeMirror-overwrite", ""); - } else { - overwrite = true; - cursor.className += " CodeMirror-overwrite"; - } - }, - - posFromIndex: function(off) { - var lineNo = 0, ch; - doc.iter(0, doc.size, function(line) { - var sz = line.text.length + 1; - if (sz > off) { ch = off; return true; } - off -= sz; - ++lineNo; - }); - return clipPos({line: lineNo, ch: ch}); - }, - indexFromPos: function (coords) { - if (coords.line < 0 || coords.ch < 0) return 0; - var index = coords.ch; - doc.iter(0, coords.line, function (line) { - index += line.text.length + 1; - }); - return index; - }, - scrollTo: function(x, y) { - if (x != null) scroller.scrollLeft = x; - if (y != null) scrollbar.scrollTop = scroller.scrollTop = y; - updateDisplay([]); - }, - getScrollInfo: function() { - return {x: scroller.scrollLeft, y: scrollbar.scrollTop, - height: scrollbar.scrollHeight, width: scroller.scrollWidth}; - }, - setSize: function(width, height) { - function interpret(val) { - val = String(val); - return /^\d+$/.test(val) ? val + "px" : val; - } - if (width != null) wrapper.style.width = interpret(width); - if (height != null) scroller.style.height = interpret(height); - instance.refresh(); - }, - - operation: function(f){return operation(f)();}, - compoundChange: function(f){return compoundChange(f);}, - refresh: function(){ - updateDisplay(true, null, lastScrollTop); - if (scrollbar.scrollHeight > lastScrollTop) - scrollbar.scrollTop = lastScrollTop; - }, - getInputField: function(){return input;}, - getWrapperElement: function(){return wrapper;}, - getScrollerElement: function(){return scroller;}, - getGutterElement: function(){return gutter;} - }; - - function getLine(n) { return getLineAt(doc, n); } - function updateLineHeight(line, height) { - gutterDirty = true; - var diff = height - line.height; - for (var n = line; n; n = n.parent) n.height += diff; - } - - function lineContent(line, wrapAt) { - if (!line.styles) - line.highlight(mode, line.stateAfter = getStateBefore(lineNo(line)), options.tabSize); - return line.getContent(options.tabSize, wrapAt, options.lineWrapping); - } - - function setValue(code) { - var top = {line: 0, ch: 0}; - updateLines(top, {line: doc.size - 1, ch: getLine(doc.size-1).text.length}, - splitLines(code), top, top); - updateInput = true; - } - function getValue(lineSep) { - var text = []; - doc.iter(0, doc.size, function(line) { text.push(line.text); }); - return text.join(lineSep || "\n"); - } - - function onScrollBar(e) { - if (scrollbar.scrollTop != lastScrollTop) { - lastScrollTop = scroller.scrollTop = scrollbar.scrollTop; - updateDisplay([]); - } - } - - function onScrollMain(e) { - if (options.fixedGutter && gutter.style.left != scroller.scrollLeft + "px") - gutter.style.left = scroller.scrollLeft + "px"; - if (scroller.scrollTop != lastScrollTop) { - lastScrollTop = scroller.scrollTop; - if (scrollbar.scrollTop != lastScrollTop) - scrollbar.scrollTop = lastScrollTop; - updateDisplay([]); - } - if (options.onScroll) options.onScroll(instance); - } - - function onMouseDown(e) { - setShift(e_prop(e, "shiftKey")); - // Check whether this is a click in a widget - for (var n = e_target(e); n != wrapper; n = n.parentNode) - if (n.parentNode == sizer && n != mover) return; - - // See if this is a click in the gutter - for (var n = e_target(e); n != wrapper; n = n.parentNode) - if (n.parentNode == gutterText) { - if (options.onGutterClick) - options.onGutterClick(instance, indexOf(gutterText.childNodes, n) + showingFrom, e); - return e_preventDefault(e); - } - - var start = posFromMouse(e); - - switch (e_button(e)) { - case 3: - if (gecko) onContextMenu(e); - return; - case 2: - if (start) setCursor(start.line, start.ch, true); - setTimeout(focusInput, 20); - e_preventDefault(e); - return; - } - // For button 1, if it was clicked inside the editor - // (posFromMouse returning non-null), we have to adjust the - // selection. - if (!start) {if (e_target(e) == scroller) e_preventDefault(e); return;} - - if (!focused) onFocus(); - - var now = +new Date, type = "single"; - if (lastDoubleClick && lastDoubleClick.time > now - 400 && posEq(lastDoubleClick.pos, start)) { - type = "triple"; - e_preventDefault(e); - setTimeout(focusInput, 20); - selectLine(start.line); - } else if (lastClick && lastClick.time > now - 400 && posEq(lastClick.pos, start)) { - type = "double"; - lastDoubleClick = {time: now, pos: start}; - e_preventDefault(e); - var word = findWordAt(start); - setSelectionUser(word.from, word.to); - } else { lastClick = {time: now, pos: start}; } - - function dragEnd(e2) { - if (webkit) scroller.draggable = false; - draggingText = false; - up(); drop(); - if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) { - e_preventDefault(e2); - setCursor(start.line, start.ch, true); - focusInput(); - } - } - var last = start, going; - if (options.dragDrop && dragAndDrop && !options.readOnly && !posEq(sel.from, sel.to) && - !posLess(start, sel.from) && !posLess(sel.to, start) && type == "single") { - // Let the drag handler handle this. - if (webkit) scroller.draggable = true; - var up = connect(document, "mouseup", operation(dragEnd), true); - var drop = connect(scroller, "drop", operation(dragEnd), true); - draggingText = true; - // IE's approach to draggable - if (scroller.dragDrop) scroller.dragDrop(); - return; - } - e_preventDefault(e); - if (type == "single") setCursor(start.line, start.ch, true); - - var startstart = sel.from, startend = sel.to; - - function doSelect(cur) { - if (type == "single") { - setSelectionUser(start, cur); - } else if (type == "double") { - var word = findWordAt(cur); - if (posLess(cur, startstart)) setSelectionUser(word.from, startend); - else setSelectionUser(startstart, word.to); - } else if (type == "triple") { - if (posLess(cur, startstart)) setSelectionUser(startend, clipPos({line: cur.line, ch: 0})); - else setSelectionUser(startstart, clipPos({line: cur.line + 1, ch: 0})); - } - } - - function extend(e) { - var cur = posFromMouse(e, true); - if (cur && !posEq(cur, last)) { - if (!focused) onFocus(); - last = cur; - doSelect(cur); - updateInput = false; - var visible = visibleLines(); - if (cur.line >= visible.to || cur.line < visible.from) - going = setTimeout(operation(function(){extend(e);}), 150); - } - } - - function done(e) { - clearTimeout(going); - var cur = posFromMouse(e); - if (cur) doSelect(cur); - e_preventDefault(e); - focusInput(); - updateInput = true; - move(); up(); - } - var move = connect(document, "mousemove", operation(function(e) { - clearTimeout(going); - e_preventDefault(e); - if (!ie && !e_button(e)) done(e); - else extend(e); - }), true); - var up = connect(document, "mouseup", operation(done), true); - } - function onDoubleClick(e) { - for (var n = e_target(e); n != wrapper; n = n.parentNode) - if (n.parentNode == gutterText) return e_preventDefault(e); - e_preventDefault(e); - } - function onDrop(e) { - if (options.onDragEvent && options.onDragEvent(instance, addStop(e))) return; - e_preventDefault(e); - var pos = posFromMouse(e, true), files = e.dataTransfer.files; - if (!pos || options.readOnly) return; - if (files && files.length && window.FileReader && window.File) { - var n = files.length, text = Array(n), read = 0; - var loadFile = function(file, i) { - var reader = new FileReader; - reader.onload = function() { - text[i] = reader.result; - if (++read == n) { - pos = clipPos(pos); - operation(function() { - var end = replaceRange(text.join(""), pos, pos); - setSelectionUser(pos, end); - })(); - } - }; - reader.readAsText(file); - }; - for (var i = 0; i < n; ++i) loadFile(files[i], i); - } else { - // Don't do a replace if the drop happened inside of the selected text. - if (draggingText && !(posLess(pos, sel.from) || posLess(sel.to, pos))) return; - try { - var text = e.dataTransfer.getData("Text"); - if (text) { - compoundChange(function() { - var curFrom = sel.from, curTo = sel.to; - setSelectionUser(pos, pos); - if (draggingText) replaceRange("", curFrom, curTo); - replaceSelection(text); - focusInput(); - }); - } - } - catch(e){} - } - } - function onDragStart(e) { - var txt = getSelection(); - e.dataTransfer.setData("Text", txt); - - // Use dummy image instead of default browsers image. - if (e.dataTransfer.setDragImage) - e.dataTransfer.setDragImage(elt('img'), 0, 0); - } - - function doHandleBinding(bound, dropShift) { - if (typeof bound == "string") { - bound = commands[bound]; - if (!bound) return false; - } - var prevShift = shiftSelecting; - try { - if (options.readOnly) suppressEdits = true; - if (dropShift) shiftSelecting = null; - bound(instance); - } catch(e) { - if (e != Pass) throw e; - return false; - } finally { - shiftSelecting = prevShift; - suppressEdits = false; - } - return true; - } - var maybeTransition; - function handleKeyBinding(e) { - // Handle auto keymap transitions - var startMap = getKeyMap(options.keyMap), next = startMap.auto; - clearTimeout(maybeTransition); - if (next && !isModifierKey(e)) maybeTransition = setTimeout(function() { - if (getKeyMap(options.keyMap) == startMap) { - options.keyMap = (next.call ? next.call(null, instance) : next); - } - }, 50); - - var name = keyNames[e_prop(e, "keyCode")], handled = false; - var flipCtrlCmd = opera && mac; - if (name == null || e.altGraphKey) return false; - if (e_prop(e, "altKey")) name = "Alt-" + name; - if (e_prop(e, flipCtrlCmd ? "metaKey" : "ctrlKey")) name = "Ctrl-" + name; - if (e_prop(e, flipCtrlCmd ? "ctrlKey" : "metaKey")) name = "Cmd-" + name; - - var stopped = false; - function stop() { stopped = true; } - - if (e_prop(e, "shiftKey")) { - handled = lookupKey("Shift-" + name, options.extraKeys, options.keyMap, - function(b) {return doHandleBinding(b, true);}, stop) - || lookupKey(name, options.extraKeys, options.keyMap, function(b) { - if (typeof b == "string" && /^go[A-Z]/.test(b)) return doHandleBinding(b); - }, stop); - } else { - handled = lookupKey(name, options.extraKeys, options.keyMap, doHandleBinding, stop); - } - if (stopped) handled = false; - if (handled) { - e_preventDefault(e); - restartBlink(); - if (ie) { e.oldKeyCode = e.keyCode; e.keyCode = 0; } - } - return handled; - } - function handleCharBinding(e, ch) { - var handled = lookupKey("'" + ch + "'", options.extraKeys, - options.keyMap, function(b) { return doHandleBinding(b, true); }); - if (handled) { - e_preventDefault(e); - restartBlink(); - } - return handled; - } - - var lastStoppedKey = null; - function onKeyDown(e) { - if (!focused) onFocus(); - if (ie && e.keyCode == 27) { e.returnValue = false; } - if (pollingFast) { if (readInput()) pollingFast = false; } - if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return; - var code = e_prop(e, "keyCode"); - // IE does strange things with escape. - setShift(code == 16 || e_prop(e, "shiftKey")); - // First give onKeyEvent option a chance to handle this. - var handled = handleKeyBinding(e); - if (opera) { - lastStoppedKey = handled ? code : null; - // Opera has no cut event... we try to at least catch the key combo - if (!handled && code == 88 && e_prop(e, mac ? "metaKey" : "ctrlKey")) - replaceSelection(""); - } - } - function onKeyPress(e) { - if (pollingFast) readInput(); - if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return; - var keyCode = e_prop(e, "keyCode"), charCode = e_prop(e, "charCode"); - if (opera && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;} - if (((opera && (!e.which || e.which < 10)) || khtml) && handleKeyBinding(e)) return; - var ch = String.fromCharCode(charCode == null ? keyCode : charCode); - if (options.electricChars && mode.electricChars && options.smartIndent && !options.readOnly) { - if (mode.electricChars.indexOf(ch) > -1) - setTimeout(operation(function() {indentLine(sel.to.line, "smart");}), 75); - } - if (handleCharBinding(e, ch)) return; - fastPoll(); - } - function onKeyUp(e) { - if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return; - if (e_prop(e, "keyCode") == 16) shiftSelecting = null; - } - - function onFocus() { - if (options.readOnly == "nocursor") return; - if (!focused) { - if (options.onFocus) options.onFocus(instance); - focused = true; - if (scroller.className.search(/\bCodeMirror-focused\b/) == -1) - scroller.className += " CodeMirror-focused"; - } - slowPoll(); - restartBlink(); - } - function onBlur() { - if (focused) { - if (options.onBlur) options.onBlur(instance); - focused = false; - if (bracketHighlighted) - operation(function(){ - if (bracketHighlighted) { bracketHighlighted(); bracketHighlighted = null; } - })(); - scroller.className = scroller.className.replace(" CodeMirror-focused", ""); - } - clearInterval(blinker); - setTimeout(function() {if (!focused) shiftSelecting = null;}, 150); - } - - // Replace the range from from to to by the strings in newText. - // Afterwards, set the selection to selFrom, selTo. - function updateLines(from, to, newText, selFrom, selTo) { - if (suppressEdits) return; - var old = []; - doc.iter(from.line, to.line + 1, function(line) { - old.push(newHL(line.text, line.markedSpans)); - }); - if (history) { - history.addChange(from.line, newText.length, old); - while (history.done.length > options.undoDepth) history.done.shift(); - } - var lines = updateMarkedSpans(hlSpans(old[0]), hlSpans(lst(old)), from.ch, to.ch, newText); - updateLinesNoUndo(from, to, lines, selFrom, selTo); - } - function unredoHelper(from, to) { - if (!from.length) return; - var set = from.pop(), out = []; - for (var i = set.length - 1; i >= 0; i -= 1) { - var change = set[i]; - var replaced = [], end = change.start + change.added; - doc.iter(change.start, end, function(line) { replaced.push(newHL(line.text, line.markedSpans)); }); - out.push({start: change.start, added: change.old.length, old: replaced}); - var pos = {line: change.start + change.old.length - 1, - ch: editEnd(hlText(lst(replaced)), hlText(lst(change.old)))}; - updateLinesNoUndo({line: change.start, ch: 0}, {line: end - 1, ch: getLine(end-1).text.length}, - change.old, pos, pos); - } - updateInput = true; - to.push(out); - } - function undo() {unredoHelper(history.done, history.undone);} - function redo() {unredoHelper(history.undone, history.done);} - - function updateLinesNoUndo(from, to, lines, selFrom, selTo) { - if (suppressEdits) return; - var recomputeMaxLength = false, maxLineLength = maxLine.text.length; - if (!options.lineWrapping) - doc.iter(from.line, to.line + 1, function(line) { - if (!line.hidden && line.text.length == maxLineLength) {recomputeMaxLength = true; return true;} - }); - if (from.line != to.line || lines.length > 1) gutterDirty = true; - - var nlines = to.line - from.line, firstLine = getLine(from.line), lastLine = getLine(to.line); - var lastHL = lst(lines); - - // First adjust the line structure - if (from.ch == 0 && to.ch == 0 && hlText(lastHL) == "") { - // This is a whole-line replace. Treated specially to make - // sure line objects move the way they are supposed to. - var added = [], prevLine = null; - for (var i = 0, e = lines.length - 1; i < e; ++i) - added.push(new Line(hlText(lines[i]), hlSpans(lines[i]))); - lastLine.update(lastLine.text, hlSpans(lastHL)); - if (nlines) doc.remove(from.line, nlines, callbacks); - if (added.length) doc.insert(from.line, added); - } else if (firstLine == lastLine) { - if (lines.length == 1) { - firstLine.update(firstLine.text.slice(0, from.ch) + hlText(lines[0]) + firstLine.text.slice(to.ch), hlSpans(lines[0])); - } else { - for (var added = [], i = 1, e = lines.length - 1; i < e; ++i) - added.push(new Line(hlText(lines[i]), hlSpans(lines[i]))); - added.push(new Line(hlText(lastHL) + firstLine.text.slice(to.ch), hlSpans(lastHL))); - firstLine.update(firstLine.text.slice(0, from.ch) + hlText(lines[0]), hlSpans(lines[0])); - doc.insert(from.line + 1, added); - } - } else if (lines.length == 1) { - firstLine.update(firstLine.text.slice(0, from.ch) + hlText(lines[0]) + lastLine.text.slice(to.ch), hlSpans(lines[0])); - doc.remove(from.line + 1, nlines, callbacks); - } else { - var added = []; - firstLine.update(firstLine.text.slice(0, from.ch) + hlText(lines[0]), hlSpans(lines[0])); - lastLine.update(hlText(lastHL) + lastLine.text.slice(to.ch), hlSpans(lastHL)); - for (var i = 1, e = lines.length - 1; i < e; ++i) - added.push(new Line(hlText(lines[i]), hlSpans(lines[i]))); - if (nlines > 1) doc.remove(from.line + 1, nlines - 1, callbacks); - doc.insert(from.line + 1, added); - } - if (options.lineWrapping) { - var perLine = Math.max(5, scroller.clientWidth / charWidth() - 3); - doc.iter(from.line, from.line + lines.length, function(line) { - if (line.hidden) return; - var guess = Math.ceil(line.text.length / perLine) || 1; - if (guess != line.height) updateLineHeight(line, guess); - }); - } else { - doc.iter(from.line, from.line + lines.length, function(line) { - var l = line.text; - if (!line.hidden && l.length > maxLineLength) { - maxLine = line; maxLineLength = l.length; maxLineChanged = true; - recomputeMaxLength = false; - } - }); - if (recomputeMaxLength) updateMaxLine = true; - } - - // Adjust frontier, schedule worker - frontier = Math.min(frontier, from.line); - startWorker(400); - - var lendiff = lines.length - nlines - 1; - // Remember that these lines changed, for updating the display - changes.push({from: from.line, to: to.line + 1, diff: lendiff}); - if (options.onChange) { - // Normalize lines to contain only strings, since that's what - // the change event handler expects - for (var i = 0; i < lines.length; ++i) - if (typeof lines[i] != "string") lines[i] = lines[i].text; - var changeObj = {from: from, to: to, text: lines}; - if (textChanged) { - for (var cur = textChanged; cur.next; cur = cur.next) {} - cur.next = changeObj; - } else textChanged = changeObj; - } - - // Update the selection - function updateLine(n) {return n <= Math.min(to.line, to.line + lendiff) ? n : n + lendiff;} - setSelection(clipPos(selFrom), clipPos(selTo), - updateLine(sel.from.line), updateLine(sel.to.line)); - } - - function needsScrollbar() { - var realHeight = doc.height * textHeight() + 2 * paddingTop(); - return realHeight * .99 > scroller.offsetHeight ? realHeight : false; - } - - function updateVerticalScroll(scrollTop) { - var scrollHeight = needsScrollbar(); - scrollbar.style.display = scrollHeight ? "block" : "none"; - if (scrollHeight) { - scrollbarInner.style.height = sizer.style.minHeight = scrollHeight + "px"; - scrollbar.style.height = scroller.clientHeight + "px"; - if (scrollTop != null) { - scrollbar.scrollTop = scroller.scrollTop = scrollTop; - // 'Nudge' the scrollbar to work around a Webkit bug where, - // in some situations, we'd end up with a scrollbar that - // reported its scrollTop (and looked) as expected, but - // *behaved* as if it was still in a previous state (i.e. - // couldn't scroll up, even though it appeared to be at the - // bottom). - if (webkit) setTimeout(function() { - if (scrollbar.scrollTop != scrollTop) return; - scrollbar.scrollTop = scrollTop + (scrollTop ? -1 : 1); - scrollbar.scrollTop = scrollTop; - }, 0); - } - } else { - sizer.style.minHeight = ""; - } - // Position the mover div to align with the current virtual scroll position - mover.style.top = displayOffset * textHeight() + "px"; - } - - function computeMaxLength() { - maxLine = getLine(0); maxLineChanged = true; - var maxLineLength = maxLine.text.length; - doc.iter(1, doc.size, function(line) { - var l = line.text; - if (!line.hidden && l.length > maxLineLength) { - maxLineLength = l.length; maxLine = line; - } - }); - updateMaxLine = false; - } - - function replaceRange(code, from, to) { - from = clipPos(from); - if (!to) to = from; else to = clipPos(to); - code = splitLines(code); - function adjustPos(pos) { - if (posLess(pos, from)) return pos; - if (!posLess(to, pos)) return end; - var line = pos.line + code.length - (to.line - from.line) - 1; - var ch = pos.ch; - if (pos.line == to.line) - ch += lst(code).length - (to.ch - (to.line == from.line ? from.ch : 0)); - return {line: line, ch: ch}; - } - var end; - replaceRange1(code, from, to, function(end1) { - end = end1; - return {from: adjustPos(sel.from), to: adjustPos(sel.to)}; - }); - return end; - } - function replaceSelection(code, collapse) { - replaceRange1(splitLines(code), sel.from, sel.to, function(end) { - if (collapse == "end") return {from: end, to: end}; - else if (collapse == "start") return {from: sel.from, to: sel.from}; - else return {from: sel.from, to: end}; - }); - } - function replaceRange1(code, from, to, computeSel) { - var endch = code.length == 1 ? code[0].length + from.ch : lst(code).length; - var newSel = computeSel({line: from.line + code.length - 1, ch: endch}); - updateLines(from, to, code, newSel.from, newSel.to); - } - - function getRange(from, to, lineSep) { - var l1 = from.line, l2 = to.line; - if (l1 == l2) return getLine(l1).text.slice(from.ch, to.ch); - var code = [getLine(l1).text.slice(from.ch)]; - doc.iter(l1 + 1, l2, function(line) { code.push(line.text); }); - code.push(getLine(l2).text.slice(0, to.ch)); - return code.join(lineSep || "\n"); - } - function getSelection(lineSep) { - return getRange(sel.from, sel.to, lineSep); - } - - function slowPoll() { - if (pollingFast) return; - poll.set(options.pollInterval, function() { - readInput(); - if (focused) slowPoll(); - }); - } - function fastPoll() { - var missed = false; - pollingFast = true; - function p() { - var changed = readInput(); - if (!changed && !missed) {missed = true; poll.set(60, p);} - else {pollingFast = false; slowPoll();} - } - poll.set(20, p); - } - - // Previnput is a hack to work with IME. If we reset the textarea - // on every change, that breaks IME. So we look for changes - // compared to the previous content instead. (Modern browsers have - // events that indicate IME taking place, but these are not widely - // supported or compatible enough yet to rely on.) - var prevInput = ""; - function readInput() { - if (!focused || hasSelection(input) || options.readOnly) return false; - var text = input.value; - if (text == prevInput) return false; - if (!nestedOperation) startOperation(); - shiftSelecting = null; - var same = 0, l = Math.min(prevInput.length, text.length); - while (same < l && prevInput[same] == text[same]) ++same; - if (same < prevInput.length) - sel.from = {line: sel.from.line, ch: sel.from.ch - (prevInput.length - same)}; - else if (overwrite && posEq(sel.from, sel.to)) - sel.to = {line: sel.to.line, ch: Math.min(getLine(sel.to.line).text.length, sel.to.ch + (text.length - same))}; - replaceSelection(text.slice(same), "end"); - if (text.length > 1000) { input.value = prevInput = ""; } - else prevInput = text; - if (!nestedOperation) endOperation(); - return true; - } - function resetInput(user) { - if (!posEq(sel.from, sel.to)) { - prevInput = ""; - input.value = getSelection(); - if (focused) selectInput(input); - } else if (user) prevInput = input.value = ""; - } - - function focusInput() { - if (options.readOnly != "nocursor") input.focus(); - } - - function scrollCursorIntoView() { - var coords = calculateCursorCoords(); - scrollIntoView(coords.x, coords.y, coords.x, coords.yBot); - if (!focused) return; - var box = sizer.getBoundingClientRect(), doScroll = null; - if (coords.y + box.top < 0) doScroll = true; - else if (coords.y + box.top + textHeight() > (window.innerHeight || document.documentElement.clientHeight)) doScroll = false; - if (doScroll != null) { - var hidden = cursor.style.display == "none"; - if (hidden) { - cursor.style.display = ""; - cursor.style.left = coords.x + "px"; - cursor.style.top = (coords.y - displayOffset) + "px"; - } - cursor.scrollIntoView(doScroll); - if (hidden) cursor.style.display = "none"; - } - } - function calculateCursorCoords() { - var cursor = localCoords(sel.inverted ? sel.from : sel.to); - var x = options.lineWrapping ? Math.min(cursor.x, lineSpace.offsetWidth) : cursor.x; - return {x: x, y: cursor.y, yBot: cursor.yBot}; - } - function scrollIntoView(x1, y1, x2, y2) { - var scrollPos = calculateScrollPos(x1, y1, x2, y2); - if (scrollPos.scrollLeft != null) {scroller.scrollLeft = scrollPos.scrollLeft;} - if (scrollPos.scrollTop != null) {scrollbar.scrollTop = scroller.scrollTop = scrollPos.scrollTop;} - } - function calculateScrollPos(x1, y1, x2, y2) { - var pl = paddingLeft(), pt = paddingTop(); - y1 += pt; y2 += pt; x1 += pl; x2 += pl; - var screen = scroller.clientHeight, screentop = scrollbar.scrollTop, result = {}; - var docBottom = needsScrollbar() || Infinity; - var atTop = y1 < pt + 10, atBottom = y2 + pt > docBottom - 10; - if (y1 < screentop) result.scrollTop = atTop ? 0 : Math.max(0, y1); - else if (y2 > screentop + screen) result.scrollTop = (atBottom ? docBottom : y2) - screen; - - var screenw = scroller.clientWidth, screenleft = scroller.scrollLeft; - var gutterw = options.fixedGutter ? gutter.clientWidth : 0; - var atLeft = x1 < gutterw + pl + 10; - if (x1 < screenleft + gutterw || atLeft) { - if (atLeft) x1 = 0; - result.scrollLeft = Math.max(0, x1 - 10 - gutterw); - } else if (x2 > screenw + screenleft - 3) { - result.scrollLeft = x2 + 10 - screenw; - } - return result; - } - - function visibleLines(scrollTop) { - var lh = textHeight(), top = (scrollTop != null ? scrollTop : scrollbar.scrollTop) - paddingTop(); - var fromHeight = Math.max(0, Math.floor(top / lh)); - var toHeight = Math.ceil((top + scroller.clientHeight) / lh); - return {from: lineAtHeight(doc, fromHeight), - to: lineAtHeight(doc, toHeight)}; - } - // Uses a set of changes plus the current scroll position to - // determine which DOM updates have to be made, and makes the - // updates. - function updateDisplay(changes, suppressCallback, scrollTop) { - if (!scroller.clientWidth) { - showingFrom = showingTo = displayOffset = 0; - return; - } - // Compute the new visible window - // If scrollTop is specified, use that to determine which lines - // to render instead of the current scrollbar position. - var visible = visibleLines(scrollTop); - // Bail out if the visible area is already rendered and nothing changed. - if (changes !== true && changes.length == 0 && visible.from > showingFrom && visible.to < showingTo) { - updateVerticalScroll(scrollTop); - return; - } - var from = Math.max(visible.from - 100, 0), to = Math.min(doc.size, visible.to + 100); - if (showingFrom < from && from - showingFrom < 20) from = showingFrom; - if (showingTo > to && showingTo - to < 20) to = Math.min(doc.size, showingTo); - - // Create a range of theoretically intact lines, and punch holes - // in that using the change info. - var intact = changes === true ? [] : - computeIntact([{from: showingFrom, to: showingTo, domStart: 0}], changes); - // Clip off the parts that won't be visible - var intactLines = 0; - for (var i = 0; i < intact.length; ++i) { - var range = intact[i]; - if (range.from < from) {range.domStart += (from - range.from); range.from = from;} - if (range.to > to) range.to = to; - if (range.from >= range.to) intact.splice(i--, 1); - else intactLines += range.to - range.from; - } - if (intactLines == to - from && from == showingFrom && to == showingTo) { - updateVerticalScroll(scrollTop); - return; - } - intact.sort(function(a, b) {return a.domStart - b.domStart;}); - - var th = textHeight(), gutterDisplay = gutter.style.display; - lineDiv.style.display = "none"; - patchDisplay(from, to, intact); - lineDiv.style.display = gutter.style.display = ""; - - var different = from != showingFrom || to != showingTo || lastSizeC != scroller.clientHeight + th; - // This is just a bogus formula that detects when the editor is - // resized or the font size changes. - if (different) lastSizeC = scroller.clientHeight + th; - if (from != showingFrom || to != showingTo && options.onViewportChange) - setTimeout(function(){ - if (options.onViewportChange) options.onViewportChange(instance, from, to); - }); - showingFrom = from; showingTo = to; - displayOffset = heightAtLine(doc, from); - startWorker(100); - - // Since this is all rather error prone, it is honoured with the - // only assertion in the whole file. - if (lineDiv.childNodes.length != showingTo - showingFrom) - throw new Error("BAD PATCH! " + JSON.stringify(intact) + " size=" + (showingTo - showingFrom) + - " nodes=" + lineDiv.childNodes.length); - - function checkHeights() { - var curNode = lineDiv.firstChild, heightChanged = false; - doc.iter(showingFrom, showingTo, function(line) { - // Work around bizarro IE7 bug where, sometimes, our curNode - // is magically replaced with a new node in the DOM, leaving - // us with a reference to an orphan (nextSibling-less) node. - if (!curNode) return; - if (!line.hidden) { - var height = Math.round(curNode.offsetHeight / th) || 1; - if (line.height != height) { - updateLineHeight(line, height); - gutterDirty = heightChanged = true; - } - } - curNode = curNode.nextSibling; - }); - return heightChanged; - } - - if (options.lineWrapping) checkHeights(); - - gutter.style.display = gutterDisplay; - if (different || gutterDirty) { - // If the gutter grew in size, re-check heights. If those changed, re-draw gutter. - updateGutter() && options.lineWrapping && checkHeights() && updateGutter(); - } - updateVerticalScroll(scrollTop); - updateSelection(); - if (!suppressCallback && options.onUpdate) options.onUpdate(instance); - return true; - } - - function computeIntact(intact, changes) { - for (var i = 0, l = changes.length || 0; i < l; ++i) { - var change = changes[i], intact2 = [], diff = change.diff || 0; - for (var j = 0, l2 = intact.length; j < l2; ++j) { - var range = intact[j]; - if (change.to <= range.from && change.diff) - intact2.push({from: range.from + diff, to: range.to + diff, - domStart: range.domStart}); - else if (change.to <= range.from || change.from >= range.to) - intact2.push(range); - else { - if (change.from > range.from) - intact2.push({from: range.from, to: change.from, domStart: range.domStart}); - if (change.to < range.to) - intact2.push({from: change.to + diff, to: range.to + diff, - domStart: range.domStart + (change.to - range.from)}); - } - } - intact = intact2; - } - return intact; - } - - function patchDisplay(from, to, intact) { - function killNode(node) { - var tmp = node.nextSibling; - node.parentNode.removeChild(node); - return tmp; - } - // The first pass removes the DOM nodes that aren't intact. - if (!intact.length) removeChildren(lineDiv); - else { - var domPos = 0, curNode = lineDiv.firstChild, n; - for (var i = 0; i < intact.length; ++i) { - var cur = intact[i]; - while (cur.domStart > domPos) {curNode = killNode(curNode); domPos++;} - for (var j = 0, e = cur.to - cur.from; j < e; ++j) {curNode = curNode.nextSibling; domPos++;} - } - while (curNode) curNode = killNode(curNode); - } - // This pass fills in the lines that actually changed. - var nextIntact = intact.shift(), curNode = lineDiv.firstChild, j = from; - doc.iter(from, to, function(line) { - if (nextIntact && nextIntact.to == j) nextIntact = intact.shift(); - if (!nextIntact || nextIntact.from > j) { - if (line.hidden) var lineElement = elt("pre"); - else { - var lineElement = lineContent(line); - if (line.className) lineElement.className = line.className; - // Kludge to make sure the styled element lies behind the selection (by z-index) - if (line.bgClassName) { - var pre = elt("pre", "\u00a0", line.bgClassName, "position: absolute; left: 0; right: 0; top: 0; bottom: 0; z-index: -2"); - lineElement = elt("div", [pre, lineElement], null, "position: relative"); - } - } - lineDiv.insertBefore(lineElement, curNode); - } else { - curNode = curNode.nextSibling; - } - ++j; - }); - } - - function updateGutter() { - if (!options.gutter && !options.lineNumbers) return; - var hText = mover.offsetHeight, hEditor = scroller.clientHeight; - gutter.style.height = (hText - hEditor < 2 ? hEditor : hText) + "px"; - var fragment = document.createDocumentFragment(), i = showingFrom, normalNode; - doc.iter(showingFrom, Math.max(showingTo, showingFrom + 1), function(line) { - if (line.hidden) { - fragment.appendChild(elt("pre")); - } else { - var marker = line.gutterMarker; - var text = options.lineNumbers ? options.lineNumberFormatter(i + options.firstLineNumber) : null; - if (marker && marker.text) - text = marker.text.replace("%N%", text != null ? text : ""); - else if (text == null) - text = "\u00a0"; - var markerElement = fragment.appendChild(elt("pre", null, marker && marker.style)); - markerElement.innerHTML = text; - for (var j = 1; j < line.height; ++j) { - markerElement.appendChild(elt("br")); - markerElement.appendChild(document.createTextNode("\u00a0")); - } - if (!marker) normalNode = i; - } - ++i; - }); - gutter.style.display = "none"; - removeChildrenAndAdd(gutterText, fragment); - // Make sure scrolling doesn't cause number gutter size to pop - if (normalNode != null && options.lineNumbers) { - var node = gutterText.childNodes[normalNode - showingFrom]; - var minwidth = String(doc.size).length, val = eltText(node.firstChild), pad = ""; - while (val.length + pad.length < minwidth) pad += "\u00a0"; - if (pad) node.insertBefore(document.createTextNode(pad), node.firstChild); - } - gutter.style.display = ""; - var resized = Math.abs((parseInt(lineSpace.style.marginLeft) || 0) - gutter.offsetWidth) > 2; - lineSpace.style.marginLeft = gutter.offsetWidth + "px"; - gutterDirty = false; - return resized; - } - function updateSelection() { - var collapsed = posEq(sel.from, sel.to); - var fromPos = localCoords(sel.from, true); - var toPos = collapsed ? fromPos : localCoords(sel.to, true); - var headPos = sel.inverted ? fromPos : toPos, th = textHeight(); - var wrapOff = eltOffset(wrapper), lineOff = eltOffset(lineDiv); - inputDiv.style.top = Math.max(0, Math.min(scroller.offsetHeight, headPos.y + lineOff.top - wrapOff.top)) + "px"; - inputDiv.style.left = Math.max(0, Math.min(scroller.offsetWidth, headPos.x + lineOff.left - wrapOff.left)) + "px"; - if (collapsed) { - cursor.style.top = headPos.y + "px"; - cursor.style.left = (options.lineWrapping ? Math.min(headPos.x, lineSpace.offsetWidth) : headPos.x) + "px"; - cursor.style.display = ""; - selectionDiv.style.display = "none"; - } else { - var sameLine = fromPos.y == toPos.y, fragment = document.createDocumentFragment(); - var clientWidth = lineSpace.clientWidth || lineSpace.offsetWidth; - var clientHeight = lineSpace.clientHeight || lineSpace.offsetHeight; - var add = function(left, top, right, height) { - var rstyle = quirksMode ? "width: " + (!right ? clientWidth : clientWidth - right - left) + "px" - : "right: " + right + "px"; - fragment.appendChild(elt("div", null, "CodeMirror-selected", "position: absolute; left: " + left + - "px; top: " + top + "px; " + rstyle + "; height: " + height + "px")); - }; - if (sel.from.ch && fromPos.y >= 0) { - var right = sameLine ? clientWidth - toPos.x : 0; - add(fromPos.x, fromPos.y, right, th); - } - var middleStart = Math.max(0, fromPos.y + (sel.from.ch ? th : 0)); - var middleHeight = Math.min(toPos.y, clientHeight) - middleStart; - if (middleHeight > 0.2 * th) - add(0, middleStart, 0, middleHeight); - if ((!sameLine || !sel.from.ch) && toPos.y < clientHeight - .5 * th) - add(0, toPos.y, clientWidth - toPos.x, th); - removeChildrenAndAdd(selectionDiv, fragment); - cursor.style.display = "none"; - selectionDiv.style.display = ""; - } - } - - function setShift(val) { - if (val) shiftSelecting = shiftSelecting || (sel.inverted ? sel.to : sel.from); - else shiftSelecting = null; - } - function setSelectionUser(from, to) { - var sh = shiftSelecting && clipPos(shiftSelecting); - if (sh) { - if (posLess(sh, from)) from = sh; - else if (posLess(to, sh)) to = sh; - } - setSelection(from, to); - userSelChange = true; - } - // Update the selection. Last two args are only used by - // updateLines, since they have to be expressed in the line - // numbers before the update. - function setSelection(from, to, oldFrom, oldTo) { - goalColumn = null; - if (oldFrom == null) {oldFrom = sel.from.line; oldTo = sel.to.line;} - if (posEq(sel.from, from) && posEq(sel.to, to)) return; - if (posLess(to, from)) {var tmp = to; to = from; from = tmp;} - - // Skip over hidden lines. - if (from.line != oldFrom) { - var from1 = skipHidden(from, oldFrom, sel.from.ch); - // If there is no non-hidden line left, force visibility on current line - if (!from1) setLineHidden(from.line, false); - else from = from1; - } - if (to.line != oldTo) to = skipHidden(to, oldTo, sel.to.ch); - - if (posEq(from, to)) sel.inverted = false; - else if (posEq(from, sel.to)) sel.inverted = false; - else if (posEq(to, sel.from)) sel.inverted = true; - - if (options.autoClearEmptyLines && posEq(sel.from, sel.to)) { - var head = sel.inverted ? from : to; - if (head.line != sel.from.line && sel.from.line < doc.size) { - var oldLine = getLine(sel.from.line); - if (/^\s+$/.test(oldLine.text)) - setTimeout(operation(function() { - if (oldLine.parent && /^\s+$/.test(oldLine.text)) { - var no = lineNo(oldLine); - replaceRange("", {line: no, ch: 0}, {line: no, ch: oldLine.text.length}); - } - }, 10)); - } - } - - sel.from = from; sel.to = to; - selectionChanged = true; - } - function skipHidden(pos, oldLine, oldCh) { - function getNonHidden(dir) { - var lNo = pos.line + dir, end = dir == 1 ? doc.size : -1; - while (lNo != end) { - var line = getLine(lNo); - if (!line.hidden) { - var ch = pos.ch; - if (toEnd || ch > oldCh || ch > line.text.length) ch = line.text.length; - return {line: lNo, ch: ch}; - } - lNo += dir; - } - } - var line = getLine(pos.line); - var toEnd = pos.ch == line.text.length && pos.ch != oldCh; - if (!line.hidden) return pos; - if (pos.line >= oldLine) return getNonHidden(1) || getNonHidden(-1); - else return getNonHidden(-1) || getNonHidden(1); - } - function setCursor(line, ch, user) { - var pos = clipPos({line: line, ch: ch || 0}); - (user ? setSelectionUser : setSelection)(pos, pos); - } - - function clipLine(n) {return Math.max(0, Math.min(n, doc.size-1));} - function clipPos(pos) { - if (pos.line < 0) return {line: 0, ch: 0}; - if (pos.line >= doc.size) return {line: doc.size-1, ch: getLine(doc.size-1).text.length}; - var ch = pos.ch, linelen = getLine(pos.line).text.length; - if (ch == null || ch > linelen) return {line: pos.line, ch: linelen}; - else if (ch < 0) return {line: pos.line, ch: 0}; - else return pos; - } - - function findPosH(dir, unit) { - var end = sel.inverted ? sel.from : sel.to, line = end.line, ch = end.ch; - var lineObj = getLine(line); - function findNextLine() { - for (var l = line + dir, e = dir < 0 ? -1 : doc.size; l != e; l += dir) { - var lo = getLine(l); - if (!lo.hidden) { line = l; lineObj = lo; return true; } - } - } - function moveOnce(boundToLine) { - if (ch == (dir < 0 ? 0 : lineObj.text.length)) { - if (!boundToLine && findNextLine()) ch = dir < 0 ? lineObj.text.length : 0; - else return false; - } else ch += dir; - return true; - } - if (unit == "char") moveOnce(); - else if (unit == "column") moveOnce(true); - else if (unit == "word") { - var sawWord = false; - for (;;) { - if (dir < 0) if (!moveOnce()) break; - if (isWordChar(lineObj.text.charAt(ch))) sawWord = true; - else if (sawWord) {if (dir < 0) {dir = 1; moveOnce();} break;} - if (dir > 0) if (!moveOnce()) break; - } - } - return {line: line, ch: ch}; - } - function moveH(dir, unit) { - var pos = dir < 0 ? sel.from : sel.to; - if (shiftSelecting || posEq(sel.from, sel.to)) pos = findPosH(dir, unit); - setCursor(pos.line, pos.ch, true); - } - function deleteH(dir, unit) { - if (!posEq(sel.from, sel.to)) replaceRange("", sel.from, sel.to); - else if (dir < 0) replaceRange("", findPosH(dir, unit), sel.to); - else replaceRange("", sel.from, findPosH(dir, unit)); - userSelChange = true; - } - function moveV(dir, unit) { - var dist = 0, pos = localCoords(sel.inverted ? sel.from : sel.to, true); - if (goalColumn != null) pos.x = goalColumn; - if (unit == "page") { - var screen = Math.min(scroller.clientHeight, window.innerHeight || document.documentElement.clientHeight); - var target = coordsChar(pos.x, pos.y + screen * dir); - } else if (unit == "line") { - var th = textHeight(); - var target = coordsChar(pos.x, pos.y + .5 * th + dir * th); - } - if (unit == "page") scrollbar.scrollTop += localCoords(target, true).y - pos.y; - setCursor(target.line, target.ch, true); - goalColumn = pos.x; - } - - function findWordAt(pos) { - var line = getLine(pos.line).text; - var start = pos.ch, end = pos.ch; - if (line) { - if (pos.after === false || end == line.length) --start; else ++end; - var startChar = line.charAt(start); - var check = isWordChar(startChar) ? isWordChar : - /\s/.test(startChar) ? function(ch) {return /\s/.test(ch);} : - function(ch) {return !/\s/.test(ch) && !isWordChar(ch);}; - while (start > 0 && check(line.charAt(start - 1))) --start; - while (end < line.length && check(line.charAt(end))) ++end; - } - return {from: {line: pos.line, ch: start}, to: {line: pos.line, ch: end}}; - } - function selectLine(line) { - setSelectionUser({line: line, ch: 0}, clipPos({line: line + 1, ch: 0})); - } - function indentSelected(mode) { - if (posEq(sel.from, sel.to)) return indentLine(sel.from.line, mode); - var e = sel.to.line - (sel.to.ch ? 0 : 1); - for (var i = sel.from.line; i <= e; ++i) indentLine(i, mode); - } - - function indentLine(n, how) { - if (!how) how = "add"; - if (how == "smart") { - if (!mode.indent) how = "prev"; - else var state = getStateBefore(n); - } - - var line = getLine(n), curSpace = line.indentation(options.tabSize), - curSpaceString = line.text.match(/^\s*/)[0], indentation; - if (how == "smart") { - indentation = mode.indent(state, line.text.slice(curSpaceString.length), line.text); - if (indentation == Pass) how = "prev"; - } - if (how == "prev") { - if (n) indentation = getLine(n-1).indentation(options.tabSize); - else indentation = 0; - } - else if (how == "add") indentation = curSpace + options.indentUnit; - else if (how == "subtract") indentation = curSpace - options.indentUnit; - indentation = Math.max(0, indentation); - var diff = indentation - curSpace; - - var indentString = "", pos = 0; - if (options.indentWithTabs) - for (var i = Math.floor(indentation / options.tabSize); i; --i) {pos += options.tabSize; indentString += "\t";} - if (pos < indentation) indentString += spaceStr(indentation - pos); - - if (indentString != curSpaceString) - replaceRange(indentString, {line: n, ch: 0}, {line: n, ch: curSpaceString.length}); - } - - function loadMode() { - mode = CodeMirror.getMode(options, options.mode); - doc.iter(0, doc.size, function(line) { line.stateAfter = null; }); - frontier = 0; - startWorker(100); - } - function gutterChanged() { - var visible = options.gutter || options.lineNumbers; - gutter.style.display = visible ? "" : "none"; - if (visible) gutterDirty = true; - else lineDiv.parentNode.style.marginLeft = 0; - } - function wrappingChanged(from, to) { - if (options.lineWrapping) { - wrapper.className += " CodeMirror-wrap"; - var perLine = scroller.clientWidth / charWidth() - 3; - doc.iter(0, doc.size, function(line) { - if (line.hidden) return; - var guess = Math.ceil(line.text.length / perLine) || 1; - if (guess != 1) updateLineHeight(line, guess); - }); - lineSpace.style.minWidth = widthForcer.style.left = ""; - } else { - wrapper.className = wrapper.className.replace(" CodeMirror-wrap", ""); - computeMaxLength(); - doc.iter(0, doc.size, function(line) { - if (line.height != 1 && !line.hidden) updateLineHeight(line, 1); - }); - } - changes.push({from: 0, to: doc.size}); - } - function themeChanged() { - scroller.className = scroller.className.replace(/\s*cm-s-\S+/g, "") + - options.theme.replace(/(^|\s)\s*/g, " cm-s-"); - } - function keyMapChanged() { - var style = keyMap[options.keyMap].style; - wrapper.className = wrapper.className.replace(/\s*cm-keymap-\S+/g, "") + - (style ? " cm-keymap-" + style : ""); - } - - function TextMarker(type, style) { this.lines = []; this.type = type; if (style) this.style = style; } - TextMarker.prototype.clear = operation(function() { - var min = Infinity, max = -Infinity; - for (var i = 0; i < this.lines.length; ++i) { - var line = this.lines[i]; - var span = getMarkedSpanFor(line.markedSpans, this, true); - if (span.from != null || span.to != null) { - var lineN = lineNo(line); - min = Math.min(min, lineN); max = Math.max(max, lineN); - } - } - if (min != Infinity) - changes.push({from: min, to: max + 1}); - this.lines.length = 0; - }); - TextMarker.prototype.find = function() { - var from, to; - for (var i = 0; i < this.lines.length; ++i) { - var line = this.lines[i]; - var span = getMarkedSpanFor(line.markedSpans, this); - if (span.from != null || span.to != null) { - var found = lineNo(line); - if (span.from != null) from = {line: found, ch: span.from}; - if (span.to != null) to = {line: found, ch: span.to}; - } - } - if (this.type == "bookmark") return from; - return from && {from: from, to: to}; - }; - - function markText(from, to, className, options) { - from = clipPos(from); to = clipPos(to); - var marker = new TextMarker("range", className); - if (options) for (var opt in options) if (options.hasOwnProperty(opt)) - marker[opt] = options[opt]; - var curLine = from.line; - doc.iter(curLine, to.line + 1, function(line) { - var span = {from: curLine == from.line ? from.ch : null, - to: curLine == to.line ? to.ch : null, - marker: marker}; - (line.markedSpans || (line.markedSpans = [])).push(span); - marker.lines.push(line); - ++curLine; - }); - changes.push({from: from.line, to: to.line + 1}); - return marker; - } - - function setBookmark(pos) { - pos = clipPos(pos); - var marker = new TextMarker("bookmark"), line = getLine(pos.line); - var span = {from: pos.ch, to: pos.ch, marker: marker}; - (line.markedSpans || (line.markedSpans = [])).push(span); - marker.lines.push(line); - return marker; - } - - function findMarksAt(pos) { - pos = clipPos(pos); - var markers = [], spans = getLine(pos.line).markedSpans; - if (spans) for (var i = 0; i < spans.length; ++i) { - var span = spans[i]; - if ((span.from == null || span.from <= pos.ch) && - (span.to == null || span.to >= pos.ch)) - markers.push(span.marker); - } - return markers; - } - - function addGutterMarker(line, text, className) { - if (typeof line == "number") line = getLine(clipLine(line)); - line.gutterMarker = {text: text, style: className}; - gutterDirty = true; - return line; - } - function removeGutterMarker(line) { - if (typeof line == "number") line = getLine(clipLine(line)); - line.gutterMarker = null; - gutterDirty = true; - } - - function changeLine(handle, op) { - var no = handle, line = handle; - if (typeof handle == "number") line = getLine(clipLine(handle)); - else no = lineNo(handle); - if (no == null) return null; - if (op(line, no)) changes.push({from: no, to: no + 1}); - else return null; - return line; - } - function setLineClass(handle, className, bgClassName) { - return changeLine(handle, function(line) { - if (line.className != className || line.bgClassName != bgClassName) { - line.className = className; - line.bgClassName = bgClassName; - return true; - } - }); - } - function setLineHidden(handle, hidden) { - return changeLine(handle, function(line, no) { - if (line.hidden != hidden) { - line.hidden = hidden; - if (!options.lineWrapping) { - if (hidden && line.text.length == maxLine.text.length) { - updateMaxLine = true; - } else if (!hidden && line.text.length > maxLine.text.length) { - maxLine = line; updateMaxLine = false; - } - } - updateLineHeight(line, hidden ? 0 : 1); - var fline = sel.from.line, tline = sel.to.line; - if (hidden && (fline == no || tline == no)) { - var from = fline == no ? skipHidden({line: fline, ch: 0}, fline, 0) : sel.from; - var to = tline == no ? skipHidden({line: tline, ch: 0}, tline, 0) : sel.to; - // Can't hide the last visible line, we'd have no place to put the cursor - if (!to) return; - setSelection(from, to); - } - return (gutterDirty = true); - } - }); - } - - function lineInfo(line) { - if (typeof line == "number") { - if (!isLine(line)) return null; - var n = line; - line = getLine(line); - if (!line) return null; - } else { - var n = lineNo(line); - if (n == null) return null; - } - var marker = line.gutterMarker; - return {line: n, handle: line, text: line.text, markerText: marker && marker.text, - markerClass: marker && marker.style, lineClass: line.className, bgClass: line.bgClassName}; - } - - function measureLine(line, ch) { - if (ch == 0) return {top: 0, left: 0}; - var wbr = options.lineWrapping && ch < line.text.length && - spanAffectsWrapping.test(line.text.slice(ch - 1, ch + 1)); - var pre = lineContent(line, ch); - removeChildrenAndAdd(measure, pre); - var anchor = pre.anchor; - var top = anchor.offsetTop, left = anchor.offsetLeft; - // Older IEs report zero offsets for spans directly after a wrap - if (ie && top == 0 && left == 0) { - var backup = elt("span", "x"); - anchor.parentNode.insertBefore(backup, anchor.nextSibling); - top = backup.offsetTop; - } - return {top: top, left: left}; - } - function localCoords(pos, inLineWrap) { - var x, lh = textHeight(), y = lh * (heightAtLine(doc, pos.line) - (inLineWrap ? displayOffset : 0)); - if (pos.ch == 0) x = 0; - else { - var sp = measureLine(getLine(pos.line), pos.ch); - x = sp.left; - if (options.lineWrapping) y += Math.max(0, sp.top); - } - return {x: x, y: y, yBot: y + lh}; - } - // Coords must be lineSpace-local - function coordsChar(x, y) { - var th = textHeight(), cw = charWidth(), heightPos = displayOffset + Math.floor(y / th); - if (heightPos < 0) return {line: 0, ch: 0}; - var lineNo = lineAtHeight(doc, heightPos); - if (lineNo >= doc.size) return {line: doc.size - 1, ch: getLine(doc.size - 1).text.length}; - var lineObj = getLine(lineNo), text = lineObj.text; - var tw = options.lineWrapping, innerOff = tw ? heightPos - heightAtLine(doc, lineNo) : 0; - if (x <= 0 && innerOff == 0) return {line: lineNo, ch: 0}; - var wrongLine = false; - function getX(len) { - var sp = measureLine(lineObj, len); - if (tw) { - var off = Math.round(sp.top / th); - wrongLine = off != innerOff; - return Math.max(0, sp.left + (off - innerOff) * scroller.clientWidth); - } - return sp.left; - } - var from = 0, fromX = 0, to = text.length, toX; - // Guess a suitable upper bound for our search. - var estimated = Math.min(to, Math.ceil((x + innerOff * scroller.clientWidth * .9) / cw)); - for (;;) { - var estX = getX(estimated); - if (estX <= x && estimated < to) estimated = Math.min(to, Math.ceil(estimated * 1.2)); - else {toX = estX; to = estimated; break;} - } - if (x > toX) return {line: lineNo, ch: to}; - // Try to guess a suitable lower bound as well. - estimated = Math.floor(to * 0.8); estX = getX(estimated); - if (estX < x) {from = estimated; fromX = estX;} - // Do a binary search between these bounds. - for (;;) { - if (to - from <= 1) { - var after = x - fromX < toX - x; - return {line: lineNo, ch: after ? from : to, after: after}; - } - var middle = Math.ceil((from + to) / 2), middleX = getX(middle); - if (middleX > x) {to = middle; toX = middleX; if (wrongLine) toX += 1000; } - else {from = middle; fromX = middleX;} - } - } - function pageCoords(pos) { - var local = localCoords(pos, true), off = eltOffset(lineSpace); - return {x: off.left + local.x, y: off.top + local.y, yBot: off.top + local.yBot}; - } - - var cachedHeight, cachedHeightFor, measurePre; - function textHeight() { - if (measurePre == null) { - measurePre = elt("pre"); - for (var i = 0; i < 49; ++i) { - measurePre.appendChild(document.createTextNode("x")); - measurePre.appendChild(elt("br")); - } - measurePre.appendChild(document.createTextNode("x")); - } - var offsetHeight = lineDiv.clientHeight; - if (offsetHeight == cachedHeightFor) return cachedHeight; - cachedHeightFor = offsetHeight; - removeChildrenAndAdd(measure, measurePre.cloneNode(true)); - cachedHeight = measure.firstChild.offsetHeight / 50 || 1; - removeChildren(measure); - return cachedHeight; - } - var cachedWidth, cachedWidthFor = 0; - function charWidth() { - if (scroller.clientWidth == cachedWidthFor) return cachedWidth; - cachedWidthFor = scroller.clientWidth; - var anchor = elt("span", "x"); - var pre = elt("pre", [anchor]); - removeChildrenAndAdd(measure, pre); - return (cachedWidth = anchor.offsetWidth || 10); - } - function paddingTop() {return lineSpace.offsetTop;} - function paddingLeft() {return lineSpace.offsetLeft;} - - function posFromMouse(e, liberal) { - var offW = eltOffset(scroller, true), x, y; - // Fails unpredictably on IE[67] when mouse is dragged around quickly. - try { x = e.clientX; y = e.clientY; } catch (e) { return null; } - // This is a mess of a heuristic to try and determine whether a - // scroll-bar was clicked or not, and to return null if one was - // (and !liberal). - if (!liberal && (x - offW.left > scroller.clientWidth || y - offW.top > scroller.clientHeight)) - return null; - var offL = eltOffset(lineSpace, true); - return coordsChar(x - offL.left, y - offL.top); - } - var detectingSelectAll; - function onContextMenu(e) { - var pos = posFromMouse(e), scrollPos = scrollbar.scrollTop; - if (!pos || opera) return; // Opera is difficult. - if (posEq(sel.from, sel.to) || posLess(pos, sel.from) || !posLess(pos, sel.to)) - operation(setCursor)(pos.line, pos.ch); - - var oldCSS = input.style.cssText; - inputDiv.style.position = "absolute"; - input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) + - "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: white; " + - "border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);"; - focusInput(); - resetInput(true); - // Adds "Select all" to context menu in FF - if (posEq(sel.from, sel.to)) input.value = prevInput = " "; - - function rehide() { - inputDiv.style.position = "relative"; - input.style.cssText = oldCSS; - if (ie_lt9) scrollbar.scrollTop = scrollPos; - slowPoll(); - - // Try to detect the user choosing select-all - if (input.selectionStart != null) { - clearTimeout(detectingSelectAll); - var extval = input.value = " " + (posEq(sel.from, sel.to) ? "" : input.value), i = 0; - prevInput = " "; - input.selectionStart = 1; input.selectionEnd = extval.length; - detectingSelectAll = setTimeout(function poll(){ - if (prevInput == " " && input.selectionStart == 0) - operation(commands.selectAll)(instance); - else if (i++ < 10) detectingSelectAll = setTimeout(poll, 500); - else resetInput(); - }, 200); - } - } - - if (gecko) { - e_stop(e); - var mouseup = connect(window, "mouseup", function() { - mouseup(); - setTimeout(rehide, 20); - }, true); - } else { - setTimeout(rehide, 50); - } - } - - // Cursor-blinking - function restartBlink() { - clearInterval(blinker); - var on = true; - cursor.style.visibility = ""; - blinker = setInterval(function() { - cursor.style.visibility = (on = !on) ? "" : "hidden"; - }, options.cursorBlinkRate); - } - - var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<"}; - function matchBrackets(autoclear) { - var head = sel.inverted ? sel.from : sel.to, line = getLine(head.line), pos = head.ch - 1; - var match = (pos >= 0 && matching[line.text.charAt(pos)]) || matching[line.text.charAt(++pos)]; - if (!match) return; - var ch = match.charAt(0), forward = match.charAt(1) == ">", d = forward ? 1 : -1, st = line.styles; - for (var off = pos + 1, i = 0, e = st.length; i < e; i+=2) - if ((off -= st[i].length) <= 0) {var style = st[i+1]; break;} - - var stack = [line.text.charAt(pos)], re = /[(){}[\]]/; - function scan(line, from, to) { - if (!line.text) return; - var st = line.styles, pos = forward ? 0 : line.text.length - 1, cur; - for (var i = forward ? 0 : st.length - 2, e = forward ? st.length : -2; i != e; i += 2*d) { - var text = st[i]; - if (st[i+1] != style) {pos += d * text.length; continue;} - for (var j = forward ? 0 : text.length - 1, te = forward ? text.length : -1; j != te; j += d, pos+=d) { - if (pos >= from && pos < to && re.test(cur = text.charAt(j))) { - var match = matching[cur]; - if (match.charAt(1) == ">" == forward) stack.push(cur); - else if (stack.pop() != match.charAt(0)) return {pos: pos, match: false}; - else if (!stack.length) return {pos: pos, match: true}; - } - } - } - } - for (var i = head.line, e = forward ? Math.min(i + 100, doc.size) : Math.max(-1, i - 100); i != e; i+=d) { - var line = getLine(i), first = i == head.line; - var found = scan(line, first && forward ? pos + 1 : 0, first && !forward ? pos : line.text.length); - if (found) break; - } - if (!found) found = {pos: null, match: false}; - var style = found.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket"; - var one = markText({line: head.line, ch: pos}, {line: head.line, ch: pos+1}, style), - two = found.pos != null && markText({line: i, ch: found.pos}, {line: i, ch: found.pos + 1}, style); - var clear = operation(function(){one.clear(); two && two.clear();}); - if (autoclear) setTimeout(clear, 800); - else bracketHighlighted = clear; - } - - // Finds the line to start with when starting a parse. Tries to - // find a line with a stateAfter, so that it can start with a - // valid state. If that fails, it returns the line with the - // smallest indentation, which tends to need the least context to - // parse correctly. - function findStartLine(n) { - var minindent, minline; - for (var search = n, lim = n - 40; search > lim; --search) { - if (search == 0) return 0; - var line = getLine(search-1); - if (line.stateAfter) return search; - var indented = line.indentation(options.tabSize); - if (minline == null || minindent > indented) { - minline = search - 1; - minindent = indented; - } - } - return minline; - } - function getStateBefore(n) { - var pos = findStartLine(n), state = pos && getLine(pos-1).stateAfter; - if (!state) state = startState(mode); - else state = copyState(mode, state); - doc.iter(pos, n, function(line) { - line.process(mode, state, options.tabSize); - line.stateAfter = (pos == n - 1 || pos % 5 == 0) ? copyState(mode, state) : null; - }); - return state; - } - function highlightWorker() { - if (frontier >= showingTo) return; - var end = +new Date + options.workTime, state = copyState(mode, getStateBefore(frontier)); - var startFrontier = frontier; - doc.iter(frontier, showingTo, function(line) { - if (frontier >= showingFrom) { // Visible - line.highlight(mode, state, options.tabSize); - line.stateAfter = copyState(mode, state); - } else { - line.process(mode, state, options.tabSize); - line.stateAfter = frontier % 5 == 0 ? copyState(mode, state) : null; - } - ++frontier; - if (+new Date > end) { - startWorker(options.workDelay); - return true; - } - }); - if (showingTo > startFrontier && frontier >= showingFrom) - operation(function() {changes.push({from: startFrontier, to: frontier});})(); - } - function startWorker(time) { - if (frontier < showingTo) - highlight.set(time, highlightWorker); - } - - // Operations are used to wrap changes in such a way that each - // change won't have to update the cursor and display (which would - // be awkward, slow, and error-prone), but instead updates are - // batched and then all combined and executed at once. - function startOperation() { - updateInput = userSelChange = textChanged = null; - changes = []; selectionChanged = false; callbacks = []; - } - function endOperation() { - if (updateMaxLine) computeMaxLength(); - if (maxLineChanged && !options.lineWrapping) { - var cursorWidth = widthForcer.offsetWidth, left = measureLine(maxLine, maxLine.text.length).left; - if (!ie_lt8) { - widthForcer.style.left = left + "px"; - lineSpace.style.minWidth = (left + cursorWidth) + "px"; - } - maxLineChanged = false; - } - var newScrollPos, updated; - if (selectionChanged) { - var coords = calculateCursorCoords(); - newScrollPos = calculateScrollPos(coords.x, coords.y, coords.x, coords.yBot); - } - if (changes.length || newScrollPos && newScrollPos.scrollTop != null) - updated = updateDisplay(changes, true, newScrollPos && newScrollPos.scrollTop); - if (!updated) { - if (selectionChanged) updateSelection(); - if (gutterDirty) updateGutter(); - } - if (newScrollPos) scrollCursorIntoView(); - if (selectionChanged) restartBlink(); - - if (focused && (updateInput === true || (updateInput !== false && selectionChanged))) - resetInput(userSelChange); - - if (selectionChanged && options.matchBrackets) - setTimeout(operation(function() { - if (bracketHighlighted) {bracketHighlighted(); bracketHighlighted = null;} - if (posEq(sel.from, sel.to)) matchBrackets(false); - }), 20); - var sc = selectionChanged, cbs = callbacks; // these can be reset by callbacks - if (textChanged && options.onChange && instance) - options.onChange(instance, textChanged); - if (sc && options.onCursorActivity) - options.onCursorActivity(instance); - for (var i = 0; i < cbs.length; ++i) cbs[i](instance); - if (updated && options.onUpdate) options.onUpdate(instance); - } - var nestedOperation = 0; - function operation(f) { - return function() { - if (!nestedOperation++) startOperation(); - try {var result = f.apply(this, arguments);} - finally {if (!--nestedOperation) endOperation();} - return result; - }; - } - - function compoundChange(f) { - history.startCompound(); - try { return f(); } finally { history.endCompound(); } - } - - for (var ext in extensions) - if (extensions.propertyIsEnumerable(ext) && - !instance.propertyIsEnumerable(ext)) - instance[ext] = extensions[ext]; - for (var i = 0; i < initHooks.length; ++i) initHooks[i](instance); - return instance; - } // (end of function CodeMirror) - - // The default configuration options. - CodeMirror.defaults = { - value: "", - mode: null, - theme: "default", - indentUnit: 2, - indentWithTabs: false, - smartIndent: true, - tabSize: 4, - keyMap: "default", - extraKeys: null, - electricChars: true, - autoClearEmptyLines: false, - onKeyEvent: null, - onDragEvent: null, - lineWrapping: false, - lineNumbers: false, - gutter: false, - fixedGutter: false, - firstLineNumber: 1, - readOnly: false, - dragDrop: true, - onChange: null, - onCursorActivity: null, - onViewportChange: null, - onGutterClick: null, - onUpdate: null, - onFocus: null, onBlur: null, onScroll: null, - matchBrackets: false, - cursorBlinkRate: 530, - workTime: 100, - workDelay: 200, - pollInterval: 100, - undoDepth: 40, - tabindex: null, - autofocus: null, - lineNumberFormatter: function(integer) { return integer; } - }; - - var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent); - var mac = ios || /Mac/.test(navigator.platform); - var win = /Win/.test(navigator.platform); - - // Known modes, by name and by MIME - var modes = CodeMirror.modes = {}, mimeModes = CodeMirror.mimeModes = {}; - CodeMirror.defineMode = function(name, mode) { - if (!CodeMirror.defaults.mode && name != "null") CodeMirror.defaults.mode = name; - if (arguments.length > 2) { - mode.dependencies = []; - for (var i = 2; i < arguments.length; ++i) mode.dependencies.push(arguments[i]); - } - modes[name] = mode; - }; - CodeMirror.defineMIME = function(mime, spec) { - mimeModes[mime] = spec; - }; - CodeMirror.resolveMode = function(spec) { - if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) - spec = mimeModes[spec]; - else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) - return CodeMirror.resolveMode("application/xml"); - if (typeof spec == "string") return {name: spec}; - else return spec || {name: "null"}; - }; - CodeMirror.getMode = function(options, spec) { - var spec = CodeMirror.resolveMode(spec); - var mfactory = modes[spec.name]; - if (!mfactory) return CodeMirror.getMode(options, "text/plain"); - var modeObj = mfactory(options, spec); - if (modeExtensions.hasOwnProperty(spec.name)) { - var exts = modeExtensions[spec.name]; - for (var prop in exts) if (exts.hasOwnProperty(prop)) modeObj[prop] = exts[prop]; - } - modeObj.name = spec.name; - return modeObj; - }; - CodeMirror.listModes = function() { - var list = []; - for (var m in modes) - if (modes.propertyIsEnumerable(m)) list.push(m); - return list; - }; - CodeMirror.listMIMEs = function() { - var list = []; - for (var m in mimeModes) - if (mimeModes.propertyIsEnumerable(m)) list.push({mime: m, mode: mimeModes[m]}); - return list; - }; - - var extensions = CodeMirror.extensions = {}; - CodeMirror.defineExtension = function(name, func) { - extensions[name] = func; - }; - - var initHooks = []; - CodeMirror.defineInitHook = function(f) {initHooks.push(f);}; - - var modeExtensions = CodeMirror.modeExtensions = {}; - CodeMirror.extendMode = function(mode, properties) { - var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeExtensions[mode] = {}); - for (var prop in properties) if (properties.hasOwnProperty(prop)) - exts[prop] = properties[prop]; - }; - - var commands = CodeMirror.commands = { - selectAll: function(cm) {cm.setSelection({line: 0, ch: 0}, {line: cm.lineCount() - 1});}, - killLine: function(cm) { - var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to); - if (!sel && cm.getLine(from.line).length == from.ch) cm.replaceRange("", from, {line: from.line + 1, ch: 0}); - else cm.replaceRange("", from, sel ? to : {line: from.line}); - }, - deleteLine: function(cm) {var l = cm.getCursor().line; cm.replaceRange("", {line: l, ch: 0}, {line: l});}, - undo: function(cm) {cm.undo();}, - redo: function(cm) {cm.redo();}, - goDocStart: function(cm) {cm.setCursor(0, 0, true);}, - goDocEnd: function(cm) {cm.setSelection({line: cm.lineCount() - 1}, null, true);}, - goLineStart: function(cm) {cm.setCursor(cm.getCursor().line, 0, true);}, - goLineStartSmart: function(cm) { - var cur = cm.getCursor(); - var text = cm.getLine(cur.line), firstNonWS = Math.max(0, text.search(/\S/)); - cm.setCursor(cur.line, cur.ch <= firstNonWS && cur.ch ? 0 : firstNonWS, true); - }, - goLineEnd: function(cm) {cm.setSelection({line: cm.getCursor().line}, null, true);}, - goLineUp: function(cm) {cm.moveV(-1, "line");}, - goLineDown: function(cm) {cm.moveV(1, "line");}, - goPageUp: function(cm) {cm.moveV(-1, "page");}, - goPageDown: function(cm) {cm.moveV(1, "page");}, - goCharLeft: function(cm) {cm.moveH(-1, "char");}, - goCharRight: function(cm) {cm.moveH(1, "char");}, - goColumnLeft: function(cm) {cm.moveH(-1, "column");}, - goColumnRight: function(cm) {cm.moveH(1, "column");}, - goWordLeft: function(cm) {cm.moveH(-1, "word");}, - goWordRight: function(cm) {cm.moveH(1, "word");}, - delCharLeft: function(cm) {cm.deleteH(-1, "char");}, - delCharRight: function(cm) {cm.deleteH(1, "char");}, - delWordLeft: function(cm) {cm.deleteH(-1, "word");}, - delWordRight: function(cm) {cm.deleteH(1, "word");}, - indentAuto: function(cm) {cm.indentSelection("smart");}, - indentMore: function(cm) {cm.indentSelection("add");}, - indentLess: function(cm) {cm.indentSelection("subtract");}, - insertTab: function(cm) {cm.replaceSelection("\t", "end");}, - defaultTab: function(cm) { - if (cm.somethingSelected()) cm.indentSelection("add"); - else cm.replaceSelection("\t", "end"); - }, - transposeChars: function(cm) { - var cur = cm.getCursor(), line = cm.getLine(cur.line); - if (cur.ch > 0 && cur.ch < line.length - 1) - cm.replaceRange(line.charAt(cur.ch) + line.charAt(cur.ch - 1), - {line: cur.line, ch: cur.ch - 1}, {line: cur.line, ch: cur.ch + 1}); - }, - newlineAndIndent: function(cm) { - cm.replaceSelection("\n", "end"); - cm.indentLine(cm.getCursor().line); - }, - toggleOverwrite: function(cm) {cm.toggleOverwrite();} - }; - - var keyMap = CodeMirror.keyMap = {}; - keyMap.basic = { - "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown", - "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown", - "Delete": "delCharRight", "Backspace": "delCharLeft", "Tab": "defaultTab", "Shift-Tab": "indentAuto", - "Enter": "newlineAndIndent", "Insert": "toggleOverwrite" - }; - // Note that the save and find-related commands aren't defined by - // default. Unknown commands are simply ignored. - keyMap.pcDefault = { - "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo", - "Ctrl-Home": "goDocStart", "Alt-Up": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Down": "goDocEnd", - "Ctrl-Left": "goWordLeft", "Ctrl-Right": "goWordRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd", - "Ctrl-Backspace": "delWordLeft", "Ctrl-Delete": "delWordRight", "Ctrl-S": "save", "Ctrl-F": "find", - "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll", - "Ctrl-[": "indentLess", "Ctrl-]": "indentMore", - fallthrough: "basic" - }; - keyMap.macDefault = { - "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo", - "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goWordLeft", - "Alt-Right": "goWordRight", "Cmd-Left": "goLineStart", "Cmd-Right": "goLineEnd", "Alt-Backspace": "delWordLeft", - "Ctrl-Alt-Backspace": "delWordRight", "Alt-Delete": "delWordRight", "Cmd-S": "save", "Cmd-F": "find", - "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll", - "Cmd-[": "indentLess", "Cmd-]": "indentMore", - fallthrough: ["basic", "emacsy"] - }; - keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault; - keyMap.emacsy = { - "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown", - "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd", - "Ctrl-V": "goPageUp", "Shift-Ctrl-V": "goPageDown", "Ctrl-D": "delCharRight", "Ctrl-H": "delCharLeft", - "Alt-D": "delWordRight", "Alt-Backspace": "delWordLeft", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars" - }; - - function getKeyMap(val) { - if (typeof val == "string") return keyMap[val]; - else return val; - } - function lookupKey(name, extraMap, map, handle, stop) { - function lookup(map) { - map = getKeyMap(map); - var found = map[name]; - if (found === false) { - if (stop) stop(); - return true; - } - if (found != null && handle(found)) return true; - if (map.nofallthrough) { - if (stop) stop(); - return true; - } - var fallthrough = map.fallthrough; - if (fallthrough == null) return false; - if (Object.prototype.toString.call(fallthrough) != "[object Array]") - return lookup(fallthrough); - for (var i = 0, e = fallthrough.length; i < e; ++i) { - if (lookup(fallthrough[i])) return true; - } - return false; - } - if (extraMap && lookup(extraMap)) return true; - return lookup(map); - } - function isModifierKey(event) { - var name = keyNames[e_prop(event, "keyCode")]; - return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod"; - } - - CodeMirror.fromTextArea = function(textarea, options) { - if (!options) options = {}; - options.value = textarea.value; - if (!options.tabindex && textarea.tabindex) - options.tabindex = textarea.tabindex; - // Set autofocus to true if this textarea is focused, or if it has - // autofocus and no other element is focused. - if (options.autofocus == null) { - var hasFocus = document.body; - // doc.activeElement occasionally throws on IE - try { hasFocus = document.activeElement; } catch(e) {} - options.autofocus = hasFocus == textarea || - textarea.getAttribute("autofocus") != null && hasFocus == document.body; - } - - function save() {textarea.value = instance.getValue();} - if (textarea.form) { - // Deplorable hack to make the submit method do the right thing. - var rmSubmit = connect(textarea.form, "submit", save, true); - if (typeof textarea.form.submit == "function") { - var realSubmit = textarea.form.submit; - textarea.form.submit = function wrappedSubmit() { - save(); - textarea.form.submit = realSubmit; - textarea.form.submit(); - textarea.form.submit = wrappedSubmit; - }; - } - } - - textarea.style.display = "none"; - var instance = CodeMirror(function(node) { - textarea.parentNode.insertBefore(node, textarea.nextSibling); - }, options); - instance.save = save; - instance.getTextArea = function() { return textarea; }; - instance.toTextArea = function() { - save(); - textarea.parentNode.removeChild(instance.getWrapperElement()); - textarea.style.display = ""; - if (textarea.form) { - rmSubmit(); - if (typeof textarea.form.submit == "function") - textarea.form.submit = realSubmit; - } - }; - return instance; - }; - - var gecko = /gecko\/\d{7}/i.test(navigator.userAgent); - var ie = /MSIE \d/.test(navigator.userAgent); - var ie_lt8 = /MSIE [1-7]\b/.test(navigator.userAgent); - var ie_lt9 = /MSIE [1-8]\b/.test(navigator.userAgent); - var quirksMode = ie && document.documentMode == 5; - var webkit = /WebKit\//.test(navigator.userAgent); - var chrome = /Chrome\//.test(navigator.userAgent); - var opera = /Opera\//.test(navigator.userAgent); - var safari = /Apple Computer/.test(navigator.vendor); - var khtml = /KHTML\//.test(navigator.userAgent); - var mac_geLion = /Mac OS X 10\D([7-9]|\d\d)\D/.test(navigator.userAgent); - - // Utility functions for working with state. Exported because modes - // sometimes need to do this. - function copyState(mode, state) { - if (state === true) return state; - if (mode.copyState) return mode.copyState(state); - var nstate = {}; - for (var n in state) { - var val = state[n]; - if (val instanceof Array) val = val.concat([]); - nstate[n] = val; - } - return nstate; - } - CodeMirror.copyState = copyState; - function startState(mode, a1, a2) { - return mode.startState ? mode.startState(a1, a2) : true; - } - CodeMirror.startState = startState; - CodeMirror.innerMode = function(mode, state) { - while (mode.innerMode) { - var info = mode.innerMode(state); - state = info.state; - mode = info.mode; - } - return info || {mode: mode, state: state}; - }; - - // The character stream used by a mode's parser. - function StringStream(string, tabSize) { - this.pos = this.start = 0; - this.string = string; - this.tabSize = tabSize || 8; - } - StringStream.prototype = { - eol: function() {return this.pos >= this.string.length;}, - sol: function() {return this.pos == 0;}, - peek: function() {return this.string.charAt(this.pos) || undefined;}, - next: function() { - if (this.pos < this.string.length) - return this.string.charAt(this.pos++); - }, - eat: function(match) { - var ch = this.string.charAt(this.pos); - if (typeof match == "string") var ok = ch == match; - else var ok = ch && (match.test ? match.test(ch) : match(ch)); - if (ok) {++this.pos; return ch;} - }, - eatWhile: function(match) { - var start = this.pos; - while (this.eat(match)){} - return this.pos > start; - }, - eatSpace: function() { - var start = this.pos; - while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) ++this.pos; - return this.pos > start; - }, - skipToEnd: function() {this.pos = this.string.length;}, - skipTo: function(ch) { - var found = this.string.indexOf(ch, this.pos); - if (found > -1) {this.pos = found; return true;} - }, - backUp: function(n) {this.pos -= n;}, - column: function() {return countColumn(this.string, this.start, this.tabSize);}, - indentation: function() {return countColumn(this.string, null, this.tabSize);}, - match: function(pattern, consume, caseInsensitive) { - if (typeof pattern == "string") { - var cased = function(str) {return caseInsensitive ? str.toLowerCase() : str;}; - if (cased(this.string).indexOf(cased(pattern), this.pos) == this.pos) { - if (consume !== false) this.pos += pattern.length; - return true; - } - } else { - var match = this.string.slice(this.pos).match(pattern); - if (match && match.index > 0) return null; - if (match && consume !== false) this.pos += match[0].length; - return match; - } - }, - current: function(){return this.string.slice(this.start, this.pos);} - }; - CodeMirror.StringStream = StringStream; - - function MarkedSpan(from, to, marker) { - this.from = from; this.to = to; this.marker = marker; - } - - function getMarkedSpanFor(spans, marker, del) { - if (spans) for (var i = 0; i < spans.length; ++i) { - var span = spans[i]; - if (span.marker == marker) { - if (del) spans.splice(i, 1); - return span; - } - } - } - - function markedSpansBefore(old, startCh, endCh) { - if (old) for (var i = 0, nw; i < old.length; ++i) { - var span = old[i], marker = span.marker; - var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh); - if (startsBefore || marker.type == "bookmark" && span.from == startCh && span.from != endCh) { - var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh); - (nw || (nw = [])).push({from: span.from, - to: endsAfter ? null : span.to, - marker: marker}); - } - } - return nw; - } - - function markedSpansAfter(old, endCh) { - if (old) for (var i = 0, nw; i < old.length; ++i) { - var span = old[i], marker = span.marker; - var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= endCh : span.to > endCh); - if (endsAfter || marker.type == "bookmark" && span.from == endCh) { - var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= endCh : span.from < endCh); - (nw || (nw = [])).push({from: startsBefore ? null : span.from - endCh, - to: span.to == null ? null : span.to - endCh, - marker: marker}); - } - } - return nw; - } - - function updateMarkedSpans(oldFirst, oldLast, startCh, endCh, newText) { - if (!oldFirst && !oldLast) return newText; - // Get the spans that 'stick out' on both sides - var first = markedSpansBefore(oldFirst, startCh); - var last = markedSpansAfter(oldLast, endCh); - - // Next, merge those two ends - var sameLine = newText.length == 1, offset = lst(newText).length + (sameLine ? startCh : 0); - if (first) { - // Fix up .to properties of first - for (var i = 0; i < first.length; ++i) { - var span = first[i]; - if (span.to == null) { - var found = getMarkedSpanFor(last, span.marker); - if (!found) span.to = startCh; - else if (sameLine) span.to = found.to == null ? null : found.to + offset; - } - } - } - if (last) { - // Fix up .from in last (or move them into first in case of sameLine) - for (var i = 0; i < last.length; ++i) { - var span = last[i]; - if (span.to != null) span.to += offset; - if (span.from == null) { - var found = getMarkedSpanFor(first, span.marker); - if (!found) { - span.from = offset; - if (sameLine) (first || (first = [])).push(span); - } - } else { - span.from += offset; - if (sameLine) (first || (first = [])).push(span); - } - } - } - - var newMarkers = [newHL(newText[0], first)]; - if (!sameLine) { - // Fill gap with whole-line-spans - var gap = newText.length - 2, gapMarkers; - if (gap > 0 && first) - for (var i = 0; i < first.length; ++i) - if (first[i].to == null) - (gapMarkers || (gapMarkers = [])).push({from: null, to: null, marker: first[i].marker}); - for (var i = 0; i < gap; ++i) - newMarkers.push(newHL(newText[i+1], gapMarkers)); - newMarkers.push(newHL(lst(newText), last)); - } - return newMarkers; - } - - // hl stands for history-line, a data structure that can be either a - // string (line without markers) or a {text, markedSpans} object. - function hlText(val) { return typeof val == "string" ? val : val.text; } - function hlSpans(val) { return typeof val == "string" ? null : val.markedSpans; } - function newHL(text, spans) { return spans ? {text: text, markedSpans: spans} : text; } - - function detachMarkedSpans(line) { - var spans = line.markedSpans; - if (!spans) return; - for (var i = 0; i < spans.length; ++i) { - var lines = spans[i].marker.lines; - var ix = indexOf(lines, line); - lines.splice(ix, 1); - } - line.markedSpans = null; - } - - function attachMarkedSpans(line, spans) { - if (!spans) return; - for (var i = 0; i < spans.length; ++i) - var marker = spans[i].marker.lines.push(line); - line.markedSpans = spans; - } - - // When measuring the position of the end of a line, different - // browsers require different approaches. If an empty span is added, - // many browsers report bogus offsets. Of those, some (Webkit, - // recent IE) will accept a space without moving the whole span to - // the next line when wrapping it, others work with a zero-width - // space. - var eolSpanContent = " "; - if (gecko || (ie && !ie_lt8)) eolSpanContent = "\u200b"; - else if (opera) eolSpanContent = ""; - - // Line objects. These hold state related to a line, including - // highlighting info (the styles array). - function Line(text, markedSpans) { - this.text = text; - this.height = 1; - attachMarkedSpans(this, markedSpans); - } - Line.prototype = { - update: function(text, markedSpans) { - this.text = text; - this.stateAfter = this.styles = null; - detachMarkedSpans(this); - attachMarkedSpans(this, markedSpans); - }, - // Run the given mode's parser over a line, update the styles - // array, which contains alternating fragments of text and CSS - // classes. - highlight: function(mode, state, tabSize) { - var stream = new StringStream(this.text, tabSize), st = this.styles || (this.styles = []); - var pos = st.length = 0; - if (this.text == "" && mode.blankLine) mode.blankLine(state); - while (!stream.eol()) { - var style = mode.token(stream, state), substr = stream.current(); - stream.start = stream.pos; - if (pos && st[pos-1] == style) { - st[pos-2] += substr; - } else if (substr) { - st[pos++] = substr; st[pos++] = style; - } - // Give up when line is ridiculously long - if (stream.pos > 5000) { - st[pos++] = this.text.slice(stream.pos); st[pos++] = null; - break; - } - } - }, - process: function(mode, state, tabSize) { - var stream = new StringStream(this.text, tabSize); - if (this.text == "" && mode.blankLine) mode.blankLine(state); - while (!stream.eol() && stream.pos <= 5000) { - mode.token(stream, state); - stream.start = stream.pos; - } - }, - // Fetch the parser token for a given character. Useful for hacks - // that want to inspect the mode state (say, for completion). - getTokenAt: function(mode, state, tabSize, ch) { - var txt = this.text, stream = new StringStream(txt, tabSize); - while (stream.pos < ch && !stream.eol()) { - stream.start = stream.pos; - var style = mode.token(stream, state); - } - return {start: stream.start, - end: stream.pos, - string: stream.current(), - className: style || null, - state: state}; - }, - indentation: function(tabSize) {return countColumn(this.text, null, tabSize);}, - // Produces an HTML fragment for the line, taking selection, - // marking, and highlighting into account. - getContent: function(tabSize, wrapAt, compensateForWrapping) { - var first = true, col = 0, specials = /[\t\u0000-\u0019\u200b\u2028\u2029\uFEFF]/g; - var pre = elt("pre"); - function span_(html, text, style) { - if (!text) return; - // Work around a bug where, in some compat modes, IE ignores leading spaces - if (first && ie && text.charAt(0) == " ") text = "\u00a0" + text.slice(1); - first = false; - if (!specials.test(text)) { - col += text.length; - var content = document.createTextNode(text); - } else { - var content = document.createDocumentFragment(), pos = 0; - while (true) { - specials.lastIndex = pos; - var m = specials.exec(text); - var skipped = m ? m.index - pos : text.length - pos; - if (skipped) { - content.appendChild(document.createTextNode(text.slice(pos, pos + skipped))); - col += skipped; - } - if (!m) break; - pos += skipped + 1; - if (m[0] == "\t") { - var tabWidth = tabSize - col % tabSize; - content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab")); - col += tabWidth; - } else { - var token = elt("span", "\u2022", "cm-invalidchar"); - token.title = "\\u" + m[0].charCodeAt(0).toString(16); - content.appendChild(token); - col += 1; - } - } - } - if (style) html.appendChild(elt("span", [content], style)); - else html.appendChild(content); - } - var span = span_; - if (wrapAt != null) { - var outPos = 0, anchor = pre.anchor = elt("span"); - span = function(html, text, style) { - var l = text.length; - if (wrapAt >= outPos && wrapAt < outPos + l) { - if (wrapAt > outPos) { - span_(html, text.slice(0, wrapAt - outPos), style); - // See comment at the definition of spanAffectsWrapping - if (compensateForWrapping) html.appendChild(elt("wbr")); - } - html.appendChild(anchor); - var cut = wrapAt - outPos; - span_(anchor, opera ? text.slice(cut, cut + 1) : text.slice(cut), style); - if (opera) span_(html, text.slice(cut + 1), style); - wrapAt--; - outPos += l; - } else { - outPos += l; - span_(html, text, style); - if (outPos == wrapAt && outPos == len) { - setTextContent(anchor, eolSpanContent); - html.appendChild(anchor); - } - // Stop outputting HTML when gone sufficiently far beyond measure - else if (outPos > wrapAt + 10 && /\s/.test(text)) span = function(){}; - } - }; - } - - var st = this.styles, allText = this.text, marked = this.markedSpans; - var len = allText.length; - function styleToClass(style) { - if (!style) return null; - return "cm-" + style.replace(/ +/g, " cm-"); - } - if (!allText && wrapAt == null) { - span(pre, " "); - } else if (!marked || !marked.length) { - for (var i = 0, ch = 0; ch < len; i+=2) { - var str = st[i], style = st[i+1], l = str.length; - if (ch + l > len) str = str.slice(0, len - ch); - ch += l; - span(pre, str, styleToClass(style)); - } - } else { - marked.sort(function(a, b) { return a.from - b.from; }); - var pos = 0, i = 0, text = "", style, sg = 0; - var nextChange = marked[0].from || 0, marks = [], markpos = 0; - var advanceMarks = function() { - var m; - while (markpos < marked.length && - ((m = marked[markpos]).from == pos || m.from == null)) { - if (m.marker.type == "range") marks.push(m); - ++markpos; - } - nextChange = markpos < marked.length ? marked[markpos].from : Infinity; - for (var i = 0; i < marks.length; ++i) { - var to = marks[i].to; - if (to == null) to = Infinity; - if (to == pos) marks.splice(i--, 1); - else nextChange = Math.min(to, nextChange); - } - }; - var m = 0; - while (pos < len) { - if (nextChange == pos) advanceMarks(); - var upto = Math.min(len, nextChange); - while (true) { - if (text) { - var end = pos + text.length; - var appliedStyle = style; - for (var j = 0; j < marks.length; ++j) { - var mark = marks[j]; - appliedStyle = (appliedStyle ? appliedStyle + " " : "") + mark.marker.style; - if (mark.marker.endStyle && mark.to === Math.min(end, upto)) appliedStyle += " " + mark.marker.endStyle; - if (mark.marker.startStyle && mark.from === pos) appliedStyle += " " + mark.marker.startStyle; - } - span(pre, end > upto ? text.slice(0, upto - pos) : text, appliedStyle); - if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;} - pos = end; - } - text = st[i++]; style = styleToClass(st[i++]); - } - } - } - return pre; - }, - cleanUp: function() { - this.parent = null; - detachMarkedSpans(this); - } - }; - - // Data structure that holds the sequence of lines. - function LeafChunk(lines) { - this.lines = lines; - this.parent = null; - for (var i = 0, e = lines.length, height = 0; i < e; ++i) { - lines[i].parent = this; - height += lines[i].height; - } - this.height = height; - } - LeafChunk.prototype = { - chunkSize: function() { return this.lines.length; }, - remove: function(at, n, callbacks) { - for (var i = at, e = at + n; i < e; ++i) { - var line = this.lines[i]; - this.height -= line.height; - line.cleanUp(); - if (line.handlers) - for (var j = 0; j < line.handlers.length; ++j) callbacks.push(line.handlers[j]); - } - this.lines.splice(at, n); - }, - collapse: function(lines) { - lines.splice.apply(lines, [lines.length, 0].concat(this.lines)); - }, - insertHeight: function(at, lines, height) { - this.height += height; - this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at)); - for (var i = 0, e = lines.length; i < e; ++i) lines[i].parent = this; - }, - iterN: function(at, n, op) { - for (var e = at + n; at < e; ++at) - if (op(this.lines[at])) return true; - } - }; - function BranchChunk(children) { - this.children = children; - var size = 0, height = 0; - for (var i = 0, e = children.length; i < e; ++i) { - var ch = children[i]; - size += ch.chunkSize(); height += ch.height; - ch.parent = this; - } - this.size = size; - this.height = height; - this.parent = null; - } - BranchChunk.prototype = { - chunkSize: function() { return this.size; }, - remove: function(at, n, callbacks) { - this.size -= n; - for (var i = 0; i < this.children.length; ++i) { - var child = this.children[i], sz = child.chunkSize(); - if (at < sz) { - var rm = Math.min(n, sz - at), oldHeight = child.height; - child.remove(at, rm, callbacks); - this.height -= oldHeight - child.height; - if (sz == rm) { this.children.splice(i--, 1); child.parent = null; } - if ((n -= rm) == 0) break; - at = 0; - } else at -= sz; - } - if (this.size - n < 25) { - var lines = []; - this.collapse(lines); - this.children = [new LeafChunk(lines)]; - this.children[0].parent = this; - } - }, - collapse: function(lines) { - for (var i = 0, e = this.children.length; i < e; ++i) this.children[i].collapse(lines); - }, - insert: function(at, lines) { - var height = 0; - for (var i = 0, e = lines.length; i < e; ++i) height += lines[i].height; - this.insertHeight(at, lines, height); - }, - insertHeight: function(at, lines, height) { - this.size += lines.length; - this.height += height; - for (var i = 0, e = this.children.length; i < e; ++i) { - var child = this.children[i], sz = child.chunkSize(); - if (at <= sz) { - child.insertHeight(at, lines, height); - if (child.lines && child.lines.length > 50) { - while (child.lines.length > 50) { - var spilled = child.lines.splice(child.lines.length - 25, 25); - var newleaf = new LeafChunk(spilled); - child.height -= newleaf.height; - this.children.splice(i + 1, 0, newleaf); - newleaf.parent = this; - } - this.maybeSpill(); - } - break; - } - at -= sz; - } - }, - maybeSpill: function() { - if (this.children.length <= 10) return; - var me = this; - do { - var spilled = me.children.splice(me.children.length - 5, 5); - var sibling = new BranchChunk(spilled); - if (!me.parent) { // Become the parent node - var copy = new BranchChunk(me.children); - copy.parent = me; - me.children = [copy, sibling]; - me = copy; - } else { - me.size -= sibling.size; - me.height -= sibling.height; - var myIndex = indexOf(me.parent.children, me); - me.parent.children.splice(myIndex + 1, 0, sibling); - } - sibling.parent = me.parent; - } while (me.children.length > 10); - me.parent.maybeSpill(); - }, - iter: function(from, to, op) { this.iterN(from, to - from, op); }, - iterN: function(at, n, op) { - for (var i = 0, e = this.children.length; i < e; ++i) { - var child = this.children[i], sz = child.chunkSize(); - if (at < sz) { - var used = Math.min(n, sz - at); - if (child.iterN(at, used, op)) return true; - if ((n -= used) == 0) break; - at = 0; - } else at -= sz; - } - } - }; - - function getLineAt(chunk, n) { - while (!chunk.lines) { - for (var i = 0;; ++i) { - var child = chunk.children[i], sz = child.chunkSize(); - if (n < sz) { chunk = child; break; } - n -= sz; - } - } - return chunk.lines[n]; - } - function lineNo(line) { - if (line.parent == null) return null; - var cur = line.parent, no = indexOf(cur.lines, line); - for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) { - for (var i = 0, e = chunk.children.length; ; ++i) { - if (chunk.children[i] == cur) break; - no += chunk.children[i].chunkSize(); - } - } - return no; - } - function lineAtHeight(chunk, h) { - var n = 0; - outer: do { - for (var i = 0, e = chunk.children.length; i < e; ++i) { - var child = chunk.children[i], ch = child.height; - if (h < ch) { chunk = child; continue outer; } - h -= ch; - n += child.chunkSize(); - } - return n; - } while (!chunk.lines); - for (var i = 0, e = chunk.lines.length; i < e; ++i) { - var line = chunk.lines[i], lh = line.height; - if (h < lh) break; - h -= lh; - } - return n + i; - } - function heightAtLine(chunk, n) { - var h = 0; - outer: do { - for (var i = 0, e = chunk.children.length; i < e; ++i) { - var child = chunk.children[i], sz = child.chunkSize(); - if (n < sz) { chunk = child; continue outer; } - n -= sz; - h += child.height; - } - return h; - } while (!chunk.lines); - for (var i = 0; i < n; ++i) h += chunk.lines[i].height; - return h; - } - - // The history object 'chunks' changes that are made close together - // and at almost the same time into bigger undoable units. - function History() { - this.time = 0; - this.done = []; this.undone = []; - this.compound = 0; - this.closed = false; - } - History.prototype = { - addChange: function(start, added, old) { - this.undone.length = 0; - var time = +new Date, cur = lst(this.done), last = cur && lst(cur); - var dtime = time - this.time; - - if (this.compound && cur && !this.closed) { - cur.push({start: start, added: added, old: old}); - } else if (dtime > 400 || !last || this.closed || - last.start > start + old.length || last.start + last.added < start) { - this.done.push([{start: start, added: added, old: old}]); - this.closed = false; - } else { - var startBefore = Math.max(0, last.start - start), - endAfter = Math.max(0, (start + old.length) - (last.start + last.added)); - for (var i = startBefore; i > 0; --i) last.old.unshift(old[i - 1]); - for (var i = endAfter; i > 0; --i) last.old.push(old[old.length - i]); - if (startBefore) last.start = start; - last.added += added - (old.length - startBefore - endAfter); - } - this.time = time; - }, - startCompound: function() { - if (!this.compound++) this.closed = true; - }, - endCompound: function() { - if (!--this.compound) this.closed = true; - } - }; - - function stopMethod() {e_stop(this);} - // Ensure an event has a stop method. - function addStop(event) { - if (!event.stop) event.stop = stopMethod; - return event; - } - - function e_preventDefault(e) { - if (e.preventDefault) e.preventDefault(); - else e.returnValue = false; - } - function e_stopPropagation(e) { - if (e.stopPropagation) e.stopPropagation(); - else e.cancelBubble = true; - } - function e_stop(e) {e_preventDefault(e); e_stopPropagation(e);} - CodeMirror.e_stop = e_stop; - CodeMirror.e_preventDefault = e_preventDefault; - CodeMirror.e_stopPropagation = e_stopPropagation; - - function e_target(e) {return e.target || e.srcElement;} - function e_button(e) { - var b = e.which; - if (b == null) { - if (e.button & 1) b = 1; - else if (e.button & 2) b = 3; - else if (e.button & 4) b = 2; - } - if (mac && e.ctrlKey && b == 1) b = 3; - return b; - } - - // Allow 3rd-party code to override event properties by adding an override - // object to an event object. - function e_prop(e, prop) { - var overridden = e.override && e.override.hasOwnProperty(prop); - return overridden ? e.override[prop] : e[prop]; - } - - // Event handler registration. If disconnect is true, it'll return a - // function that unregisters the handler. - function connect(node, type, handler, disconnect) { - if (typeof node.addEventListener == "function") { - node.addEventListener(type, handler, false); - if (disconnect) return function() {node.removeEventListener(type, handler, false);}; - } else { - var wrapHandler = function(event) {handler(event || window.event);}; - node.attachEvent("on" + type, wrapHandler); - if (disconnect) return function() {node.detachEvent("on" + type, wrapHandler);}; - } - } - CodeMirror.connect = connect; - - function Delayed() {this.id = null;} - Delayed.prototype = {set: function(ms, f) {clearTimeout(this.id); this.id = setTimeout(f, ms);}}; - - var Pass = CodeMirror.Pass = {toString: function(){return "CodeMirror.Pass";}}; - - // Detect drag-and-drop - var dragAndDrop = function() { - // There is *some* kind of drag-and-drop support in IE6-8, but I - // couldn't get it to work yet. - if (ie_lt9) return false; - var div = elt('div'); - return "draggable" in div || "dragDrop" in div; - }(); - - // Feature-detect whether newlines in textareas are converted to \r\n - var lineSep = function () { - var te = elt("textarea"); - te.value = "foo\nbar"; - if (te.value.indexOf("\r") > -1) return "\r\n"; - return "\n"; - }(); - - // For a reason I have yet to figure out, some browsers disallow - // word wrapping between certain characters *only* if a new inline - // element is started between them. This makes it hard to reliably - // measure the position of things, since that requires inserting an - // extra span. This terribly fragile set of regexps matches the - // character combinations that suffer from this phenomenon on the - // various browsers. - var spanAffectsWrapping = /^$/; // Won't match any two-character string - if (gecko) spanAffectsWrapping = /$'/; - else if (safari) spanAffectsWrapping = /\-[^ \-?]|\?[^ !'\"\),.\-\/:;\?\]\}]/; - else if (chrome) spanAffectsWrapping = /\-[^ \-\.?]|\?[^ \-\.?\]\}:;!'\"\),\/]|[\.!\"#&%\)*+,:;=>\]|\}~][\(\{\[<]|\$'/; - - // Counts the column offset in a string, taking tabs into account. - // Used mostly to find indentation. - function countColumn(string, end, tabSize) { - if (end == null) { - end = string.search(/[^\s\u00a0]/); - if (end == -1) end = string.length; - } - for (var i = 0, n = 0; i < end; ++i) { - if (string.charAt(i) == "\t") n += tabSize - (n % tabSize); - else ++n; - } - return n; - } - - function eltOffset(node, screen) { - // Take the parts of bounding client rect that we are interested in so we are able to edit if need be, - // since the returned value cannot be changed externally (they are kept in sync as the element moves within the page) - try { var box = node.getBoundingClientRect(); box = { top: box.top, left: box.left }; } - catch(e) { box = {top: 0, left: 0}; } - if (!screen) { - // Get the toplevel scroll, working around browser differences. - if (window.pageYOffset == null) { - var t = document.documentElement || document.body.parentNode; - if (t.scrollTop == null) t = document.body; - box.top += t.scrollTop; box.left += t.scrollLeft; - } else { - box.top += window.pageYOffset; box.left += window.pageXOffset; - } - } - return box; - } - - function eltText(node) { - return node.textContent || node.innerText || node.nodeValue || ""; - } - - var spaceStrs = [""]; - function spaceStr(n) { - while (spaceStrs.length <= n) - spaceStrs.push(lst(spaceStrs) + " "); - return spaceStrs[n]; - } - - function lst(arr) { return arr[arr.length-1]; } - - function selectInput(node) { - if (ios) { // Mobile Safari apparently has a bug where select() is broken. - node.selectionStart = 0; - node.selectionEnd = node.value.length; - } else node.select(); - } - - // Operations on {line, ch} objects. - function posEq(a, b) {return a.line == b.line && a.ch == b.ch;} - function posLess(a, b) {return a.line < b.line || (a.line == b.line && a.ch < b.ch);} - function copyPos(x) {return {line: x.line, ch: x.ch};} - - function elt(tag, content, className, style) { - var e = document.createElement(tag); - if (className) e.className = className; - if (style) e.style.cssText = style; - if (typeof content == "string") setTextContent(e, content); - else if (content) for (var i = 0; i < content.length; ++i) e.appendChild(content[i]); - return e; - } - function removeChildren(e) { - e.innerHTML = ""; - return e; - } - function removeChildrenAndAdd(parent, e) { - removeChildren(parent).appendChild(e); - } - function setTextContent(e, str) { - if (ie_lt9) { - e.innerHTML = ""; - e.appendChild(document.createTextNode(str)); - } else e.textContent = str; - } - - // Used to position the cursor after an undo/redo by finding the - // last edited character. - function editEnd(from, to) { - if (!to) return 0; - if (!from) return to.length; - for (var i = from.length, j = to.length; i >= 0 && j >= 0; --i, --j) - if (from.charAt(i) != to.charAt(j)) break; - return j + 1; - } - - function indexOf(collection, elt) { - if (collection.indexOf) return collection.indexOf(elt); - for (var i = 0, e = collection.length; i < e; ++i) - if (collection[i] == elt) return i; - return -1; - } - function isWordChar(ch) { - return /\w/.test(ch) || ch.toUpperCase() != ch.toLowerCase(); - } - - // See if "".split is the broken IE version, if so, provide an - // alternative way to split lines. - var splitLines = "\n\nb".split(/\n/).length != 3 ? function(string) { - var pos = 0, result = [], l = string.length; - while (pos <= l) { - var nl = string.indexOf("\n", pos); - if (nl == -1) nl = string.length; - var line = string.slice(pos, string.charAt(nl - 1) == "\r" ? nl - 1 : nl); - var rt = line.indexOf("\r"); - if (rt != -1) { - result.push(line.slice(0, rt)); - pos += rt + 1; - } else { - result.push(line); - pos = nl + 1; - } - } - return result; - } : function(string){return string.split(/\r\n?|\n/);}; - CodeMirror.splitLines = splitLines; - - var hasSelection = window.getSelection ? function(te) { - try { return te.selectionStart != te.selectionEnd; } - catch(e) { return false; } - } : function(te) { - try {var range = te.ownerDocument.selection.createRange();} - catch(e) {} - if (!range || range.parentElement() != te) return false; - return range.compareEndPoints("StartToEnd", range) != 0; - }; - - CodeMirror.defineMode("null", function() { - return {token: function(stream) {stream.skipToEnd();}}; - }); - CodeMirror.defineMIME("text/plain", "null"); - - var keyNames = {3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt", - 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End", - 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert", - 46: "Delete", 59: ";", 91: "Mod", 92: "Mod", 93: "Mod", 109: "-", 107: "=", 127: "Delete", - 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", - 221: "]", 222: "'", 63276: "PageUp", 63277: "PageDown", 63275: "End", 63273: "Home", - 63234: "Left", 63232: "Up", 63235: "Right", 63233: "Down", 63302: "Insert", 63272: "Delete"}; - CodeMirror.keyNames = keyNames; - (function() { - // Number keys - for (var i = 0; i < 10; i++) keyNames[i + 48] = String(i); - // Alphabetic keys - for (var i = 65; i <= 90; i++) keyNames[i] = String.fromCharCode(i); - // Function keys - for (var i = 1; i <= 12; i++) keyNames[i + 111] = keyNames[i + 63235] = "F" + i; - })(); - - CodeMirror.version = "2.34"; - - return CodeMirror; -})(); diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/closetag.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/closetag.js deleted file mode 100644 index 5096678473..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/closetag.js +++ /dev/null @@ -1,164 +0,0 @@ -/** - * Tag-closer extension for CodeMirror. - * - * This extension adds a "closeTag" utility function that can be used with key bindings to - * insert a matching end tag after the ">" character of a start tag has been typed. It can - * also complete " - * Contributed under the same license terms as CodeMirror. - */ -(function() { - /** Option that allows tag closing behavior to be toggled. Default is true. */ - CodeMirror.defaults['closeTagEnabled'] = true; - - /** Array of tag names to add indentation after the start tag for. Default is the list of block-level html tags. */ - CodeMirror.defaults['closeTagIndent'] = ['applet', 'blockquote', 'body', 'button', 'div', 'dl', 'fieldset', 'form', 'frameset', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'html', 'iframe', 'layer', 'legend', 'object', 'ol', 'p', 'select', 'table', 'ul']; - - /** Array of tag names where an end tag is forbidden. */ - CodeMirror.defaults['closeTagVoid'] = ['area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr']; - - function innerState(cm, state) { - return CodeMirror.innerMode(cm.getMode(), state).state; - } - - - /** - * Call during key processing to close tags. Handles the key event if the tag is closed, otherwise throws CodeMirror.Pass. - * - cm: The editor instance. - * - ch: The character being processed. - * - indent: Optional. An array of tag names to indent when closing. Omit or pass true to use the default indentation tag list defined in the 'closeTagIndent' option. - * Pass false to disable indentation. Pass an array to override the default list of tag names. - * - vd: Optional. An array of tag names that should not be closed. Omit to use the default void (end tag forbidden) tag list defined in the 'closeTagVoid' option. Ignored in xml mode. - */ - CodeMirror.defineExtension("closeTag", function(cm, ch, indent, vd) { - if (!cm.getOption('closeTagEnabled')) { - throw CodeMirror.Pass; - } - - /* - * Relevant structure of token: - * - * htmlmixed - * className - * state - * htmlState - * type - * tagName - * context - * tagName - * mode - * - * xml - * className - * state - * tagName - * type - */ - - var pos = cm.getCursor(); - var tok = cm.getTokenAt(pos); - var state = innerState(cm, tok.state); - - if (state) { - - if (ch == '>') { - var type = state.type; - - if (tok.className == 'tag' && type == 'closeTag') { - throw CodeMirror.Pass; // Don't process the '>' at the end of an end-tag. - } - - cm.replaceSelection('>'); // Mode state won't update until we finish the tag. - pos = {line: pos.line, ch: pos.ch + 1}; - cm.setCursor(pos); - - tok = cm.getTokenAt(cm.getCursor()); - state = innerState(cm, tok.state); - if (!state) throw CodeMirror.Pass; - var type = state.type; - - if (tok.className == 'tag' && type != 'selfcloseTag') { - var tagName = state.tagName; - if (tagName.length > 0 && shouldClose(cm, vd, tagName)) { - insertEndTag(cm, indent, pos, tagName); - } - return; - } - - // Undo the '>' insert and allow cm to handle the key instead. - cm.setSelection({line: pos.line, ch: pos.ch - 1}, pos); - cm.replaceSelection(""); - - } else if (ch == '/') { - if (tok.className == 'tag' && tok.string == '<') { - var ctx = state.context, tagName = ctx ? ctx.tagName : ''; - if (tagName.length > 0) { - completeEndTag(cm, pos, tagName); - return; - } - } - } - - } - - throw CodeMirror.Pass; // Bubble if not handled - }); - - function insertEndTag(cm, indent, pos, tagName) { - if (shouldIndent(cm, indent, tagName)) { - cm.replaceSelection('\n\n', 'end'); - cm.indentLine(pos.line + 1); - cm.indentLine(pos.line + 2); - cm.setCursor({line: pos.line + 1, ch: cm.getLine(pos.line + 1).length}); - } else { - cm.replaceSelection(''); - cm.setCursor(pos); - } - } - - function shouldIndent(cm, indent, tagName) { - if (typeof indent == 'undefined' || indent == null || indent == true) { - indent = cm.getOption('closeTagIndent'); - } - if (!indent) { - indent = []; - } - return indexOf(indent, tagName.toLowerCase()) != -1; - } - - function shouldClose(cm, vd, tagName) { - if (cm.getOption('mode') == 'xml') { - return true; // always close xml tags - } - if (typeof vd == 'undefined' || vd == null) { - vd = cm.getOption('closeTagVoid'); - } - if (!vd) { - vd = []; - } - return indexOf(vd, tagName.toLowerCase()) == -1; - } - - // C&P from codemirror.js...would be nice if this were visible to utilities. - function indexOf(collection, elt) { - if (collection.indexOf) return collection.indexOf(elt); - for (var i = 0, e = collection.length; i < e; ++i) - if (collection[i] == elt) return i; - return -1; - } - - function completeEndTag(cm, pos, tagName) { - cm.replaceSelection('/' + tagName + '>'); - cm.setCursor({line: pos.line, ch: pos.ch + tagName.length + 2 }); - } - -})(); diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/dialog.css b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/dialog.css deleted file mode 100644 index 3849476771..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/dialog.css +++ /dev/null @@ -1,27 +0,0 @@ -.CodeMirror-dialog { - position: relative; -} - -.CodeMirror-dialog > div { - position: absolute; - top: 0; left: 0; right: 0; - background: white; - border-bottom: 1px solid #eee; - z-index: 15; - padding: .1em .8em; - overflow: hidden; - color: #333; -} - -.CodeMirror-dialog input { - border: none; - outline: none; - background: transparent; - width: 20em; - color: inherit; - font-family: monospace; -} - -.CodeMirror-dialog button { - font-size: 70%; -} \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/dialog.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/dialog.js deleted file mode 100644 index bcbe774461..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/dialog.js +++ /dev/null @@ -1,70 +0,0 @@ -// Open simple dialogs on top of an editor. Relies on dialog.css. - -(function() { - function dialogDiv(cm, template) { - var wrap = cm.getWrapperElement(); - var dialog = wrap.insertBefore(document.createElement("div"), wrap.firstChild); - dialog.className = "CodeMirror-dialog"; - dialog.innerHTML = '
    ' + template + '
    '; - return dialog; - } - - CodeMirror.defineExtension("openDialog", function(template, callback) { - var dialog = dialogDiv(this, template); - var closed = false, me = this; - function close() { - if (closed) return; - closed = true; - dialog.parentNode.removeChild(dialog); - } - var inp = dialog.getElementsByTagName("input")[0], button; - if (inp) { - CodeMirror.connect(inp, "keydown", function(e) { - if (e.keyCode == 13 || e.keyCode == 27) { - CodeMirror.e_stop(e); - close(); - me.focus(); - if (e.keyCode == 13) callback(inp.value); - } - }); - inp.focus(); - CodeMirror.connect(inp, "blur", close); - } else if (button = dialog.getElementsByTagName("button")[0]) { - CodeMirror.connect(button, "click", function() { - close(); - me.focus(); - }); - button.focus(); - CodeMirror.connect(button, "blur", close); - } - return close; - }); - - CodeMirror.defineExtension("openConfirm", function(template, callbacks) { - var dialog = dialogDiv(this, template); - var buttons = dialog.getElementsByTagName("button"); - var closed = false, me = this, blurring = 1; - function close() { - if (closed) return; - closed = true; - dialog.parentNode.removeChild(dialog); - me.focus(); - } - buttons[0].focus(); - for (var i = 0; i < buttons.length; ++i) { - var b = buttons[i]; - (function(callback) { - CodeMirror.connect(b, "click", function(e) { - CodeMirror.e_preventDefault(e); - close(); - if (callback) callback(me); - }); - })(callbacks[i]); - CodeMirror.connect(b, "blur", function() { - --blurring; - setTimeout(function() { if (blurring <= 0) close(); }, 200); - }); - CodeMirror.connect(b, "focus", function() { ++blurring; }); - } - }); -})(); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/foldcode.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/foldcode.js deleted file mode 100644 index 3681867856..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/foldcode.js +++ /dev/null @@ -1,196 +0,0 @@ -// the tagRangeFinder function is -// Copyright (C) 2011 by Daniel Glazman -// released under the MIT license (../../LICENSE) like the rest of CodeMirror -CodeMirror.tagRangeFinder = function(cm, line, hideEnd) { - var nameStartChar = "A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD"; - var nameChar = nameStartChar + "\-\:\.0-9\\u00B7\\u0300-\\u036F\\u203F-\\u2040"; - var xmlNAMERegExp = new RegExp("^[" + nameStartChar + "][" + nameChar + "]*"); - - var lineText = cm.getLine(line); - var found = false; - var tag = null; - var pos = 0; - while (!found) { - pos = lineText.indexOf("<", pos); - if (-1 == pos) // no tag on line - return; - if (pos + 1 < lineText.length && lineText[pos + 1] == "/") { // closing tag - pos++; - continue; - } - // ok we weem to have a start tag - if (!lineText.substr(pos + 1).match(xmlNAMERegExp)) { // not a tag name... - pos++; - continue; - } - var gtPos = lineText.indexOf(">", pos + 1); - if (-1 == gtPos) { // end of start tag not in line - var l = line + 1; - var foundGt = false; - var lastLine = cm.lineCount(); - while (l < lastLine && !foundGt) { - var lt = cm.getLine(l); - var gt = lt.indexOf(">"); - if (-1 != gt) { // found a > - foundGt = true; - var slash = lt.lastIndexOf("/", gt); - if (-1 != slash && slash < gt) { - var str = lineText.substr(slash, gt - slash + 1); - if (!str.match( /\/\s*\>/ )) { // yep, that's the end of empty tag - if (hideEnd === true) l++; - return l; - } - } - } - l++; - } - found = true; - } - else { - var slashPos = lineText.lastIndexOf("/", gtPos); - if (-1 == slashPos) { // cannot be empty tag - found = true; - // don't continue - } - else { // empty tag? - // check if really empty tag - var str = lineText.substr(slashPos, gtPos - slashPos + 1); - if (!str.match( /\/\s*\>/ )) { // finally not empty - found = true; - // don't continue - } - } - } - if (found) { - var subLine = lineText.substr(pos + 1); - tag = subLine.match(xmlNAMERegExp); - if (tag) { - // we have an element name, wooohooo ! - tag = tag[0]; - // do we have the close tag on same line ??? - if (-1 != lineText.indexOf("", pos)) // yep - { - found = false; - } - // we don't, so we have a candidate... - } - else - found = false; - } - if (!found) - pos++; - } - - if (found) { - var startTag = "(\\<\\/" + tag + "\\>)|(\\<" + tag + "\\>)|(\\<" + tag + "\\s)|(\\<" + tag + "$)"; - var startTagRegExp = new RegExp(startTag, "g"); - var endTag = ""; - var depth = 1; - var l = line + 1; - var lastLine = cm.lineCount(); - while (l < lastLine) { - lineText = cm.getLine(l); - var match = lineText.match(startTagRegExp); - if (match) { - for (var i = 0; i < match.length; i++) { - if (match[i] == endTag) - depth--; - else - depth++; - if (!depth) { - if (hideEnd === true) l++; - return l; - } - } - } - l++; - } - return; - } -}; - -CodeMirror.braceRangeFinder = function(cm, line, hideEnd) { - var lineText = cm.getLine(line), at = lineText.length, startChar, tokenType; - for (;;) { - var found = lineText.lastIndexOf("{", at); - if (found < 0) break; - tokenType = cm.getTokenAt({line: line, ch: found}).className; - if (!/^(comment|string)/.test(tokenType)) { startChar = found; break; } - at = found - 1; - } - if (startChar == null || lineText.lastIndexOf("}") > startChar) return; - var count = 1, lastLine = cm.lineCount(), end; - outer: for (var i = line + 1; i < lastLine; ++i) { - var text = cm.getLine(i), pos = 0; - for (;;) { - var nextOpen = text.indexOf("{", pos), nextClose = text.indexOf("}", pos); - if (nextOpen < 0) nextOpen = text.length; - if (nextClose < 0) nextClose = text.length; - pos = Math.min(nextOpen, nextClose); - if (pos == text.length) break; - if (cm.getTokenAt({line: i, ch: pos + 1}).className == tokenType) { - if (pos == nextOpen) ++count; - else if (!--count) { end = i; break outer; } - } - ++pos; - } - } - if (end == null || end == line + 1) return; - if (hideEnd === true) end++; - return end; -}; - -CodeMirror.indentRangeFinder = function(cm, line) { - var tabSize = cm.getOption("tabSize"); - var myIndent = cm.getLineHandle(line).indentation(tabSize), last; - for (var i = line + 1, end = cm.lineCount(); i < end; ++i) { - var handle = cm.getLineHandle(i); - if (!/^\s*$/.test(handle.text)) { - if (handle.indentation(tabSize) <= myIndent) break; - last = i; - } - } - if (!last) return null; - return last + 1; -}; - -CodeMirror.newFoldFunction = function(rangeFinder, markText, hideEnd) { - var folded = []; - if (markText == null) markText = '
    %N%'; - - function isFolded(cm, n) { - for (var i = 0; i < folded.length; ++i) { - var start = cm.lineInfo(folded[i].start); - if (!start) folded.splice(i--, 1); - else if (start.line == n) return {pos: i, region: folded[i]}; - } - } - - function expand(cm, region) { - cm.clearMarker(region.start); - for (var i = 0; i < region.hidden.length; ++i) - cm.showLine(region.hidden[i]); - } - - return function(cm, line) { - cm.operation(function() { - var known = isFolded(cm, line); - if (known) { - folded.splice(known.pos, 1); - expand(cm, known.region); - } else { - var end = rangeFinder(cm, line, hideEnd); - if (end == null) return; - var hidden = []; - for (var i = line + 1; i < end; ++i) { - var handle = cm.hideLine(i); - if (handle) hidden.push(handle); - } - var first = cm.setMarker(line, markText); - var region = {start: first, hidden: hidden}; - cm.onDeleteLine(first, function() { expand(cm, region); }); - folded.push(region); - } - }); - }; -}; diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/formatting.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/formatting.js deleted file mode 100644 index e83b85cece..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/formatting.js +++ /dev/null @@ -1,193 +0,0 @@ -// ============== Formatting extensions ============================ -(function() { - // Define extensions for a few modes - CodeMirror.extendMode("css", { - commentStart: "/*", - commentEnd: "*/", - wordWrapChars: [";", "\\{", "\\}"], - autoFormatLineBreaks: function (text) { - return text.replace(new RegExp("(;|\\{|\\})([^\r\n])", "g"), "$1\n$2"); - } - }); - - function jsNonBreakableBlocks(text) { - var nonBreakableRegexes = [/for\s*?\((.*?)\)/, - /\"(.*?)(\"|$)/, - /\'(.*?)(\'|$)/, - /\/\*(.*?)(\*\/|$)/, - /\/\/.*/]; - var nonBreakableBlocks = []; - for (var i = 0; i < nonBreakableRegexes.length; i++) { - var curPos = 0; - while (curPos < text.length) { - var m = text.substr(curPos).match(nonBreakableRegexes[i]); - if (m != null) { - nonBreakableBlocks.push({ - start: curPos + m.index, - end: curPos + m.index + m[0].length - }); - curPos += m.index + Math.max(1, m[0].length); - } - else { // No more matches - break; - } - } - } - nonBreakableBlocks.sort(function (a, b) { - return a.start - b.start; - }); - - return nonBreakableBlocks; - } - - CodeMirror.extendMode("javascript", { - commentStart: "/*", - commentEnd: "*/", - wordWrapChars: [";", "\\{", "\\}"], - - autoFormatLineBreaks: function (text) { - var curPos = 0; - var reLinesSplitter = /(;|\{|\})([^\r\n;])/g; - var nonBreakableBlocks = jsNonBreakableBlocks(text); - if (nonBreakableBlocks != null) { - var res = ""; - for (var i = 0; i < nonBreakableBlocks.length; i++) { - if (nonBreakableBlocks[i].start > curPos) { // Break lines till the block - res += text.substring(curPos, nonBreakableBlocks[i].start).replace(reLinesSplitter, "$1\n$2"); - curPos = nonBreakableBlocks[i].start; - } - if (nonBreakableBlocks[i].start <= curPos - && nonBreakableBlocks[i].end >= curPos) { // Skip non-breakable block - res += text.substring(curPos, nonBreakableBlocks[i].end); - curPos = nonBreakableBlocks[i].end; - } - } - if (curPos < text.length) - res += text.substr(curPos).replace(reLinesSplitter, "$1\n$2"); - return res; - } else { - return text.replace(reLinesSplitter, "$1\n$2"); - } - } - }); - - CodeMirror.extendMode("xml", { - commentStart: "", - wordWrapChars: [">"], - - autoFormatLineBreaks: function (text) { - var lines = text.split("\n"); - var reProcessedPortion = new RegExp("(^\\s*?<|^[^<]*?)(.+)(>\\s*?$|[^>]*?$)"); - var reOpenBrackets = new RegExp("<", "g"); - var reCloseBrackets = new RegExp("(>)([^\r\n])", "g"); - for (var i = 0; i < lines.length; i++) { - var mToProcess = lines[i].match(reProcessedPortion); - if (mToProcess != null && mToProcess.length > 3) { // The line starts with whitespaces and ends with whitespaces - lines[i] = mToProcess[1] - + mToProcess[2].replace(reOpenBrackets, "\n$&").replace(reCloseBrackets, "$1\n$2") - + mToProcess[3]; - continue; - } - } - return lines.join("\n"); - } - }); - - function localModeAt(cm, pos) { - return CodeMirror.innerMode(cm.getMode(), cm.getTokenAt(pos).state).mode; - } - - function enumerateModesBetween(cm, line, start, end) { - var outer = cm.getMode(), text = cm.getLine(line); - if (end == null) end = text.length; - if (!outer.innerMode) return [{from: start, to: end, mode: outer}]; - var state = cm.getTokenAt({line: line, ch: start}).state; - var mode = CodeMirror.innerMode(outer, state).mode; - var found = [], stream = new CodeMirror.StringStream(text); - stream.pos = stream.start = start; - for (;;) { - outer.token(stream, state); - var curMode = CodeMirror.innerMode(outer, state).mode; - if (curMode != mode) { - var cut = stream.start; - // Crappy heuristic to deal with the fact that a change in - // mode can occur both at the end and the start of a token, - // and we don't know which it was. - if (mode.name == "xml" && text.charAt(stream.pos - 1) == ">") cut = stream.pos; - found.push({from: start, to: cut, mode: mode}); - start = cut; - mode = curMode; - } - if (stream.pos >= end) break; - stream.start = stream.pos; - } - if (start < end) found.push({from: start, to: end, mode: mode}); - return found; - } - - // Comment/uncomment the specified range - CodeMirror.defineExtension("commentRange", function (isComment, from, to) { - var curMode = localModeAt(this, from), cm = this; - this.operation(function() { - if (isComment) { // Comment range - cm.replaceRange(curMode.commentEnd, to); - cm.replaceRange(curMode.commentStart, from); - if (from.line == to.line && from.ch == to.ch) // An empty comment inserted - put cursor inside - cm.setCursor(from.line, from.ch + curMode.commentStart.length); - } else { // Uncomment range - var selText = cm.getRange(from, to); - var startIndex = selText.indexOf(curMode.commentStart); - var endIndex = selText.lastIndexOf(curMode.commentEnd); - if (startIndex > -1 && endIndex > -1 && endIndex > startIndex) { - // Take string till comment start - selText = selText.substr(0, startIndex) - // From comment start till comment end - + selText.substring(startIndex + curMode.commentStart.length, endIndex) - // From comment end till string end - + selText.substr(endIndex + curMode.commentEnd.length); - } - cm.replaceRange(selText, from, to); - } - }); - }); - - // Applies automatic mode-aware indentation to the specified range - CodeMirror.defineExtension("autoIndentRange", function (from, to) { - var cmInstance = this; - this.operation(function () { - for (var i = from.line; i <= to.line; i++) { - cmInstance.indentLine(i, "smart"); - } - }); - }); - - // Applies automatic formatting to the specified range - CodeMirror.defineExtension("autoFormatRange", function (from, to) { - var cm = this; - cm.operation(function () { - for (var cur = from.line, end = to.line; cur <= end; ++cur) { - var f = {line: cur, ch: cur == from.line ? from.ch : 0}; - var t = {line: cur, ch: cur == end ? to.ch : null}; - var modes = enumerateModesBetween(cm, cur, f.ch, t.ch), mangled = ""; - var text = cm.getRange(f, t); - for (var i = 0; i < modes.length; ++i) { - var part = modes.length > 1 ? text.slice(modes[i].from, modes[i].to) : text; - if (mangled) mangled += "\n"; - if (modes[i].mode.autoFormatLineBreaks) { - mangled += modes[i].mode.autoFormatLineBreaks(part); - } else mangled += text; - } - if (mangled != text) { - for (var count = 0, pos = mangled.indexOf("\n"); pos != -1; pos = mangled.indexOf("\n", pos + 1), ++count) {} - cm.replaceRange(mangled, f, t); - cur += count; - end += count; - } - } - for (var cur = from.line + 1; cur <= end; ++cur) - cm.indentLine(cur, "smart"); - cm.setSelection(from, cm.getCursor(false)); - }); - }); -})(); diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/javascript-hint.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/javascript-hint.js deleted file mode 100644 index 5fce1d9153..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/javascript-hint.js +++ /dev/null @@ -1,134 +0,0 @@ -(function () { - function forEach(arr, f) { - for (var i = 0, e = arr.length; i < e; ++i) f(arr[i]); - } - - function arrayContains(arr, item) { - if (!Array.prototype.indexOf) { - var i = arr.length; - while (i--) { - if (arr[i] === item) { - return true; - } - } - return false; - } - return arr.indexOf(item) != -1; - } - - function scriptHint(editor, keywords, getToken) { - // Find the token at the cursor - var cur = editor.getCursor(), token = getToken(editor, cur), tprop = token; - // If it's not a 'word-style' token, ignore the token. - if (!/^[\w$_]*$/.test(token.string)) { - token = tprop = {start: cur.ch, end: cur.ch, string: "", state: token.state, - className: token.string == "." ? "property" : null}; - } - // If it is a property, find out what it is a property of. - while (tprop.className == "property") { - tprop = getToken(editor, {line: cur.line, ch: tprop.start}); - if (tprop.string != ".") return; - tprop = getToken(editor, {line: cur.line, ch: tprop.start}); - if (tprop.string == ')') { - var level = 1; - do { - tprop = getToken(editor, {line: cur.line, ch: tprop.start}); - switch (tprop.string) { - case ')': level++; break; - case '(': level--; break; - default: break; - } - } while (level > 0); - tprop = getToken(editor, {line: cur.line, ch: tprop.start}); - if (tprop.className == 'variable') - tprop.className = 'function'; - else return; // no clue - } - if (!context) var context = []; - context.push(tprop); - } - return {list: getCompletions(token, context, keywords), - from: {line: cur.line, ch: token.start}, - to: {line: cur.line, ch: token.end}}; - } - - CodeMirror.javascriptHint = function(editor) { - return scriptHint(editor, javascriptKeywords, - function (e, cur) {return e.getTokenAt(cur);}); - }; - - function getCoffeeScriptToken(editor, cur) { - // This getToken, it is for coffeescript, imitates the behavior of - // getTokenAt method in javascript.js, that is, returning "property" - // type and treat "." as indepenent token. - var token = editor.getTokenAt(cur); - if (cur.ch == token.start + 1 && token.string.charAt(0) == '.') { - token.end = token.start; - token.string = '.'; - token.className = "property"; - } - else if (/^\.[\w$_]*$/.test(token.string)) { - token.className = "property"; - token.start++; - token.string = token.string.replace(/\./, ''); - } - return token; - } - - CodeMirror.coffeescriptHint = function(editor) { - return scriptHint(editor, coffeescriptKeywords, getCoffeeScriptToken); - }; - - var stringProps = ("charAt charCodeAt indexOf lastIndexOf substring substr slice trim trimLeft trimRight " + - "toUpperCase toLowerCase split concat match replace search").split(" "); - var arrayProps = ("length concat join splice push pop shift unshift slice reverse sort indexOf " + - "lastIndexOf every some filter forEach map reduce reduceRight ").split(" "); - var funcProps = "prototype apply call bind".split(" "); - var javascriptKeywords = ("break case catch continue debugger default delete do else false finally for function " + - "if in instanceof new null return switch throw true try typeof var void while with").split(" "); - var coffeescriptKeywords = ("and break catch class continue delete do else extends false finally for " + - "if in instanceof isnt new no not null of off on or return switch then throw true try typeof until void while with yes").split(" "); - - function getCompletions(token, context, keywords) { - var found = [], start = token.string; - function maybeAdd(str) { - if (str.indexOf(start) == 0 && !arrayContains(found, str)) found.push(str); - } - function gatherCompletions(obj) { - if (typeof obj == "string") forEach(stringProps, maybeAdd); - else if (obj instanceof Array) forEach(arrayProps, maybeAdd); - else if (obj instanceof Function) forEach(funcProps, maybeAdd); - for (var name in obj) maybeAdd(name); - } - - if (context) { - // If this is a property, see if it belongs to some object we can - // find in the current environment. - var obj = context.pop(), base; - if (obj.className == "variable") - base = window[obj.string]; - else if (obj.className == "string") - base = ""; - else if (obj.className == "atom") - base = 1; - else if (obj.className == "function") { - if (window.jQuery != null && (obj.string == '$' || obj.string == 'jQuery') && - (typeof window.jQuery == 'function')) - base = window.jQuery(); - else if (window._ != null && (obj.string == '_') && (typeof window._ == 'function')) - base = window._(); - } - while (base != null && context.length) - base = base[context.pop().string]; - if (base != null) gatherCompletions(base); - } - else { - // If not, just look in the window object and any local scope - // (reading into JS mode internals to get at the local variables) - for (var v = token.state.localVars; v; v = v.next) maybeAdd(v.name); - gatherCompletions(window); - forEach(keywords, maybeAdd); - } - return found; - } -})(); diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/loadmode.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/loadmode.js deleted file mode 100644 index a6c56f7344..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/loadmode.js +++ /dev/null @@ -1,51 +0,0 @@ -(function() { - if (!CodeMirror.modeURL) CodeMirror.modeURL = "../mode/%N/%N.js"; - - var loading = {}; - function splitCallback(cont, n) { - var countDown = n; - return function() { if (--countDown == 0) cont(); }; - } - function ensureDeps(mode, cont) { - var deps = CodeMirror.modes[mode].dependencies; - if (!deps) return cont(); - var missing = []; - for (var i = 0; i < deps.length; ++i) { - if (!CodeMirror.modes.hasOwnProperty(deps[i])) - missing.push(deps[i]); - } - if (!missing.length) return cont(); - var split = splitCallback(cont, missing.length); - for (var i = 0; i < missing.length; ++i) - CodeMirror.requireMode(missing[i], split); - } - - CodeMirror.requireMode = function(mode, cont) { - if (typeof mode != "string") mode = mode.name; - if (CodeMirror.modes.hasOwnProperty(mode)) return ensureDeps(mode, cont); - if (loading.hasOwnProperty(mode)) return loading[mode].push(cont); - - var script = document.createElement("script"); - script.src = CodeMirror.modeURL.replace(/%N/g, mode); - var others = document.getElementsByTagName("script")[0]; - others.parentNode.insertBefore(script, others); - var list = loading[mode] = [cont]; - var count = 0, poll = setInterval(function() { - if (++count > 100) return clearInterval(poll); - if (CodeMirror.modes.hasOwnProperty(mode)) { - clearInterval(poll); - loading[mode] = null; - ensureDeps(mode, function() { - for (var i = 0; i < list.length; ++i) list[i](); - }); - } - }, 200); - }; - - CodeMirror.autoLoadMode = function(instance, mode) { - if (!CodeMirror.modes.hasOwnProperty(mode)) - CodeMirror.requireMode(mode, function() { - instance.setOption("mode", instance.getOption("mode")); - }); - }; -}()); diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/match-highlighter.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/match-highlighter.js deleted file mode 100644 index bc3dd4be30..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/match-highlighter.js +++ /dev/null @@ -1,44 +0,0 @@ -// Define match-highlighter commands. Depends on searchcursor.js -// Use by attaching the following function call to the onCursorActivity event: - //myCodeMirror.matchHighlight(minChars); -// And including a special span.CodeMirror-matchhighlight css class (also optionally a separate one for .CodeMirror-focused -- see demo matchhighlighter.html) - -(function() { - var DEFAULT_MIN_CHARS = 2; - - function MatchHighlightState() { - this.marked = []; - } - function getMatchHighlightState(cm) { - return cm._matchHighlightState || (cm._matchHighlightState = new MatchHighlightState()); - } - - function clearMarks(cm) { - var state = getMatchHighlightState(cm); - for (var i = 0; i < state.marked.length; ++i) - state.marked[i].clear(); - state.marked = []; - } - - function markDocument(cm, className, minChars) { - clearMarks(cm); - minChars = (typeof minChars !== 'undefined' ? minChars : DEFAULT_MIN_CHARS); - if (cm.somethingSelected() && cm.getSelection().replace(/^\s+|\s+$/g, "").length >= minChars) { - var state = getMatchHighlightState(cm); - var query = cm.getSelection(); - cm.operation(function() { - if (cm.lineCount() < 2000) { // This is too expensive on big documents. - for (var cursor = cm.getSearchCursor(query); cursor.findNext();) { - //Only apply matchhighlight to the matches other than the one actually selected - if (!(cursor.from().line === cm.getCursor(true).line && cursor.from().ch === cm.getCursor(true).ch)) - state.marked.push(cm.markText(cursor.from(), cursor.to(), className)); - } - } - }); - } - } - - CodeMirror.defineExtension("matchHighlight", function(className, minChars) { - markDocument(this, className, minChars); - }); -})(); diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/multiplex.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/multiplex.js deleted file mode 100644 index 874a16a289..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/multiplex.js +++ /dev/null @@ -1,77 +0,0 @@ -CodeMirror.multiplexingMode = function(outer /*, others */) { - // Others should be {open, close, mode [, delimStyle]} objects - var others = Array.prototype.slice.call(arguments, 1); - var n_others = others.length; - - function indexOf(string, pattern, from) { - if (typeof pattern == "string") return string.indexOf(pattern, from); - var m = pattern.exec(from ? string.slice(from) : string); - return m ? m.index + from : -1; - } - - return { - startState: function() { - return { - outer: CodeMirror.startState(outer), - innerActive: null, - inner: null - }; - }, - - copyState: function(state) { - return { - outer: CodeMirror.copyState(outer, state.outer), - innerActive: state.innerActive, - inner: state.innerActive && CodeMirror.copyState(state.innerActive.mode, state.inner) - }; - }, - - token: function(stream, state) { - if (!state.innerActive) { - var cutOff = Infinity, oldContent = stream.string; - for (var i = 0; i < n_others; ++i) { - var other = others[i]; - var found = indexOf(oldContent, other.open, stream.pos); - if (found == stream.pos) { - stream.match(other.open); - state.innerActive = other; - state.inner = CodeMirror.startState(other.mode, outer.indent ? outer.indent(state.outer, "") : 0); - return other.delimStyle; - } else if (found != -1 && found < cutOff) { - cutOff = found; - } - } - if (cutOff != Infinity) stream.string = oldContent.slice(0, cutOff); - var outerToken = outer.token(stream, state.outer); - if (cutOff != Infinity) stream.string = oldContent; - return outerToken; - } else { - var curInner = state.innerActive, oldContent = stream.string; - var found = indexOf(oldContent, curInner.close, stream.pos); - if (found == stream.pos) { - stream.match(curInner.close); - state.innerActive = state.inner = null; - return curInner.delimStyle; - } - if (found > -1) stream.string = oldContent.slice(0, found); - var innerToken = curInner.mode.token(stream, state.inner); - if (found > -1) stream.string = oldContent; - var cur = stream.current(), found = cur.indexOf(curInner.close); - if (found > -1) stream.backUp(cur.length - found); - return innerToken; - } - }, - - indent: function(state, textAfter) { - var mode = state.innerActive ? state.innerActive.mode : outer; - if (!mode.indent) return CodeMirror.Pass; - return mode.indent(state.innerActive ? state.inner : state.outer, textAfter); - }, - - electricChars: outer.electricChars, - - innerMode: function(state) { - return state.inner ? {state: state.inner, mode: state.innerActive.mode} : {state: state.outer, mode: outer}; - } - }; -}; diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/overlay.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/overlay.js deleted file mode 100644 index ad54deeec1..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/overlay.js +++ /dev/null @@ -1,54 +0,0 @@ -// Utility function that allows modes to be combined. The mode given -// as the base argument takes care of most of the normal mode -// functionality, but a second (typically simple) mode is used, which -// can override the style of text. Both modes get to parse all of the -// text, but when both assign a non-null style to a piece of code, the -// overlay wins, unless the combine argument was true, in which case -// the styles are combined. - -// overlayParser is the old, deprecated name -CodeMirror.overlayMode = CodeMirror.overlayParser = function(base, overlay, combine) { - return { - startState: function() { - return { - base: CodeMirror.startState(base), - overlay: CodeMirror.startState(overlay), - basePos: 0, baseCur: null, - overlayPos: 0, overlayCur: null - }; - }, - copyState: function(state) { - return { - base: CodeMirror.copyState(base, state.base), - overlay: CodeMirror.copyState(overlay, state.overlay), - basePos: state.basePos, baseCur: null, - overlayPos: state.overlayPos, overlayCur: null - }; - }, - - token: function(stream, state) { - if (stream.start == state.basePos) { - state.baseCur = base.token(stream, state.base); - state.basePos = stream.pos; - } - if (stream.start == state.overlayPos) { - stream.pos = stream.start; - state.overlayCur = overlay.token(stream, state.overlay); - state.overlayPos = stream.pos; - } - stream.pos = Math.min(state.basePos, state.overlayPos); - if (stream.eol()) state.basePos = state.overlayPos = 0; - - if (state.overlayCur == null) return state.baseCur; - if (state.baseCur != null && combine) return state.baseCur + " " + state.overlayCur; - else return state.overlayCur; - }, - - indent: base.indent && function(state, textAfter) { - return base.indent(state.base, textAfter); - }, - electricChars: base.electricChars, - - innerMode: function(state) { return {state: state.base, mode: base}; } - }; -}; diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/pig-hint.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/pig-hint.js deleted file mode 100644 index 416c0a1676..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/pig-hint.js +++ /dev/null @@ -1,123 +0,0 @@ -(function () { - function forEach(arr, f) { - for (var i = 0, e = arr.length; i < e; ++i) f(arr[i]); - } - - function arrayContains(arr, item) { - if (!Array.prototype.indexOf) { - var i = arr.length; - while (i--) { - if (arr[i] === item) { - return true; - } - } - return false; - } - return arr.indexOf(item) != -1; - } - - function scriptHint(editor, keywords, getToken) { - // Find the token at the cursor - var cur = editor.getCursor(), token = getToken(editor, cur), tprop = token; - // If it's not a 'word-style' token, ignore the token. - - if (!/^[\w$_]*$/.test(token.string)) { - token = tprop = {start: cur.ch, end: cur.ch, string: "", state: token.state, - className: token.string == ":" ? "pig-type" : null}; - } - - if (!context) var context = []; - context.push(tprop); - - var completionList = getCompletions(token, context); - completionList = completionList.sort(); - //prevent autocomplete for last word, instead show dropdown with one word - if(completionList.length == 1) { - completionList.push(" "); - } - - return {list: completionList, - from: {line: cur.line, ch: token.start}, - to: {line: cur.line, ch: token.end}}; - } - - CodeMirror.pigHint = function(editor) { - return scriptHint(editor, pigKeywordsU, function (e, cur) {return e.getTokenAt(cur);}); - }; - - function toTitleCase(str) { - return str.replace(/(?:^|\s)\w/g, function(match) { - return match.toUpperCase(); - }); - } - - var pigKeywords = "VOID IMPORT RETURNS DEFINE LOAD FILTER FOREACH ORDER CUBE DISTINCT COGROUP " - + "JOIN CROSS UNION SPLIT INTO IF OTHERWISE ALL AS BY USING INNER OUTER ONSCHEMA PARALLEL " - + "PARTITION GROUP AND OR NOT GENERATE FLATTEN ASC DESC IS STREAM THROUGH STORE MAPREDUCE " - + "SHIP CACHE INPUT OUTPUT STDERROR STDIN STDOUT LIMIT SAMPLE LEFT RIGHT FULL EQ GT LT GTE LTE " - + "NEQ MATCHES TRUE FALSE"; - var pigKeywordsU = pigKeywords.split(" "); - var pigKeywordsL = pigKeywords.toLowerCase().split(" "); - - var pigTypes = "BOOLEAN INT LONG FLOAT DOUBLE CHARARRAY BYTEARRAY BAG TUPLE MAP"; - var pigTypesU = pigTypes.split(" "); - var pigTypesL = pigTypes.toLowerCase().split(" "); - - var pigBuiltins = "ABS ACOS ARITY ASIN ATAN AVG BAGSIZE BINSTORAGE BLOOM BUILDBLOOM CBRT CEIL " - + "CONCAT COR COS COSH COUNT COUNT_STAR COV CONSTANTSIZE CUBEDIMENSIONS DIFF DISTINCT DOUBLEABS " - + "DOUBLEAVG DOUBLEBASE DOUBLEMAX DOUBLEMIN DOUBLEROUND DOUBLESUM EXP FLOOR FLOATABS FLOATAVG " - + "FLOATMAX FLOATMIN FLOATROUND FLOATSUM GENERICINVOKER INDEXOF INTABS INTAVG INTMAX INTMIN " - + "INTSUM INVOKEFORDOUBLE INVOKEFORFLOAT INVOKEFORINT INVOKEFORLONG INVOKEFORSTRING INVOKER " - + "ISEMPTY JSONLOADER JSONMETADATA JSONSTORAGE LAST_INDEX_OF LCFIRST LOG LOG10 LOWER LONGABS " - + "LONGAVG LONGMAX LONGMIN LONGSUM MAX MIN MAPSIZE MONITOREDUDF NONDETERMINISTIC OUTPUTSCHEMA " - + "PIGSTORAGE PIGSTREAMING RANDOM REGEX_EXTRACT REGEX_EXTRACT_ALL REPLACE ROUND SIN SINH SIZE " - + "SQRT STRSPLIT SUBSTRING SUM STRINGCONCAT STRINGMAX STRINGMIN STRINGSIZE TAN TANH TOBAG " - + "TOKENIZE TOMAP TOP TOTUPLE TRIM TEXTLOADER TUPLESIZE UCFIRST UPPER UTF8STORAGECONVERTER"; - var pigBuiltinsU = pigBuiltins.split(" ").join("() ").split(" "); - var pigBuiltinsL = pigBuiltins.toLowerCase().split(" ").join("() ").split(" "); - var pigBuiltinsC = ("BagSize BinStorage Bloom BuildBloom ConstantSize CubeDimensions DoubleAbs " - + "DoubleAvg DoubleBase DoubleMax DoubleMin DoubleRound DoubleSum FloatAbs FloatAvg FloatMax " - + "FloatMin FloatRound FloatSum GenericInvoker IntAbs IntAvg IntMax IntMin IntSum " - + "InvokeForDouble InvokeForFloat InvokeForInt InvokeForLong InvokeForString Invoker " - + "IsEmpty JsonLoader JsonMetadata JsonStorage LongAbs LongAvg LongMax LongMin LongSum MapSize " - + "MonitoredUDF Nondeterministic OutputSchema PigStorage PigStreaming StringConcat StringMax " - + "StringMin StringSize TextLoader TupleSize Utf8StorageConverter").split(" ").join("() ").split(" "); - - function getCompletions(token, context) { - var found = [], start = token.string; - function maybeAdd(str) { - if (str.indexOf(start) == 0 && !arrayContains(found, str)) found.push(str); - } - - function gatherCompletions(obj) { - if(obj == ":") { - forEach(pigTypesL, maybeAdd); - } - else { - forEach(pigBuiltinsU, maybeAdd); - forEach(pigBuiltinsL, maybeAdd); - forEach(pigBuiltinsC, maybeAdd); - forEach(pigTypesU, maybeAdd); - forEach(pigTypesL, maybeAdd); - forEach(pigKeywordsU, maybeAdd); - forEach(pigKeywordsL, maybeAdd); - } - } - - if (context) { - // If this is a property, see if it belongs to some object we can - // find in the current environment. - var obj = context.pop(), base; - - if (obj.className == "pig-word") - base = obj.string; - else if(obj.className == "pig-type") - base = ":" + obj.string; - - while (base != null && context.length) - base = base[context.pop().string]; - if (base != null) gatherCompletions(base); - } - return found; - } -})(); diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/razor-hint.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/razor-hint.js deleted file mode 100644 index 47840ffcf7..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/razor-hint.js +++ /dev/null @@ -1,119 +0,0 @@ -(function () { - - CodeMirror.razorHints = []; - - - function arrayContains(arr, item) { - if (!Array.prototype.indexOf) { - var i = arr.length; - while (i--) { - if (arr[i] === item) { - return true; - } - } - return false; - } - return arr.indexOf(item) != -1; - } - - - CodeMirror.razorHint = function (editor, char) { - - if (char.length > 0) { - var cursor = editor.getCursor(); - editor.replaceSelection(char); - cursor = { line: cursor.line, ch: cursor.ch + 1 }; - editor.setCursor(cursor); - } - - // dirty hack for simple-hint to receive getHint event on space - var getTokenAt = editor.getTokenAt; - editor.getTokenAt = function () { return 'disabled'; }; - CodeMirror.simpleHint(editor, getHint); - editor.getTokenAt = getTokenAt; - - //return scriptHint(editor, pigKeywordsU, char, function (e, cur) { return e.getTokenAt(cur); }); - } - - var getHint = function (cm) { - - var cursor = cm.getCursor(); - if (cursor.ch > 0) { - - var text = cm.getRange({ line: 0, ch: 0 }, cursor); - var typed = ''; - var simbol = ''; - - for (var i = text.length - 1; i >= 0; i--) { - if (text[i] == '@' || text[i] == '.') { - simbol = text[i]; - break; - } - else { - typed = text[i] + typed; - } - } - } - - text = text.slice(0, text.length - typed.length); - - var hints = getCompletions(text, simbol, typed); - return { - list: hints, - from: { line: cursor.line, ch: cursor.ch - typed.length }, - to: cursor - }; - }; - - function searchHints(text, trigger, search) { - if (search == "") { - var hints = CodeMirror.razorHints[text]; - if (typeof hints === 'undefined') - hints = CodeMirror.razorHints[simbol]; - - return hints; - } else { - - } - } - - function getCompletions(text, trigger, search) { - var found = []; - - function maybeAdd(str) { - var match = str; - if (typeof match === 'string') { - match = [str, str]; - } - - if (search == "" && !arrayContains(found, match)) found.push(match); - else if (match[0].toLowerCase().indexOf(search) == 0 && !arrayContains(found, match)) found.push(match); - } - - forEach(CodeMirror.razorHints[text], maybeAdd); - forEach(CodeMirror.razorHints[trigger], maybeAdd); - - return found.sort(function (a, b) { - var nameA = a[0].toLowerCase(), nameB = b[0].toLowerCase() - if (nameA < nameB) - return -1 - if (nameA > nameB) - return 1 - - return 0 //default return value (no sorting) - }) - } - - function forEach(arr, f) { - if (typeof arr != 'undefined') - for (var i = 0, e = arr.length; i < e; ++i) f(arr[i]); - } - - - function toTitleCase(str) { - return str.replace(/(?:^|\s)\w/g, function (match) { - return match.toUpperCase(); - }); - } - -})(); diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/razor-hints.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/razor-hints.js deleted file mode 100644 index 19bee60c85..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/razor-hints.js +++ /dev/null @@ -1,81 +0,0 @@ -(function() { - - CodeMirror.razorHints['@'] = [ - 'inherits', - 'Library', - 'Model', - 'Parameter', - 'using', - 'Dictionary', - ['if/else', 'if(SomeCondition){\n\n}else{\n\n}\n'], - ['if', 'if(SomeCondition){\n\n}\n'], - ['foreach', 'foreach(var item in collection){\n\n}\n'], - ['context', 'inherits umbraco.MacroEngines.DynamicNodeContext\n\n'], - ['helper', 'helperMethod(Model)\n\n@helperMethod(dynamic val){\n\t

    Hello @val.Name\n}\n\n'], - ]; - - CodeMirror.razorHints['.'] = [ - 'Ancestors', - 'AncestorsOrSelf', - 'Children', - 'Descendants', - 'DescendantsOrSelf', - 'Parent', - 'First()', - 'Last()', - 'Up()', - 'Next()', - 'Previous()', - 'AncestorOrSelf()', - 'Where()', - 'OrderBy()', - 'GroupBy()', - 'InGroupsOf()', - 'Pluck()', - 'Take()', - 'Skip()', - 'Count()', - 'XPath()', - 'Search()', - - 'Id', - 'Template', - 'SortOrder', - 'Name', - 'Visible', - 'Url', - 'UrlName', - 'NodeTypeAlias', - 'WriterName', - 'CreatorName', - 'WriterId', - 'CreatorId', - 'Path', - 'CreateDate', - 'UpdateDate', - 'NiceUrl', - 'Level', - ]; - - CodeMirror.razorHints['@Library.'] = [ - 'Search()', - 'NodeById()', - ]; - - CodeMirror.razorHints['@Model.'] = - CodeMirror.razorHints['<'] = - CodeMirror.razorHints['<'] = [ - 'second', - 'two' - ]; - - CodeMirror.razorHints['<'] = [ - 'three', - 'x-three' - ]; - -})(); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/runmode-standalone.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/runmode-standalone.js deleted file mode 100644 index 481a46bc94..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/runmode-standalone.js +++ /dev/null @@ -1,90 +0,0 @@ -/* Just enough of CodeMirror to run runMode under node.js */ - -function splitLines(string){ return string.split(/\r?\n|\r/); }; - -function StringStream(string) { - this.pos = this.start = 0; - this.string = string; -} -StringStream.prototype = { - eol: function() {return this.pos >= this.string.length;}, - sol: function() {return this.pos == 0;}, - peek: function() {return this.string.charAt(this.pos) || null;}, - next: function() { - if (this.pos < this.string.length) - return this.string.charAt(this.pos++); - }, - eat: function(match) { - var ch = this.string.charAt(this.pos); - if (typeof match == "string") var ok = ch == match; - else var ok = ch && (match.test ? match.test(ch) : match(ch)); - if (ok) {++this.pos; return ch;} - }, - eatWhile: function(match) { - var start = this.pos; - while (this.eat(match)){} - return this.pos > start; - }, - eatSpace: function() { - var start = this.pos; - while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) ++this.pos; - return this.pos > start; - }, - skipToEnd: function() {this.pos = this.string.length;}, - skipTo: function(ch) { - var found = this.string.indexOf(ch, this.pos); - if (found > -1) {this.pos = found; return true;} - }, - backUp: function(n) {this.pos -= n;}, - column: function() {return this.start;}, - indentation: function() {return 0;}, - match: function(pattern, consume, caseInsensitive) { - if (typeof pattern == "string") { - function cased(str) {return caseInsensitive ? str.toLowerCase() : str;} - if (cased(this.string).indexOf(cased(pattern), this.pos) == this.pos) { - if (consume !== false) this.pos += pattern.length; - return true; - } - } - else { - var match = this.string.slice(this.pos).match(pattern); - if (match && consume !== false) this.pos += match[0].length; - return match; - } - }, - current: function(){return this.string.slice(this.start, this.pos);} -}; -exports.StringStream = StringStream; - -exports.startState = function(mode, a1, a2) { - return mode.startState ? mode.startState(a1, a2) : true; -}; - -var modes = exports.modes = {}, mimeModes = exports.mimeModes = {}; -exports.defineMode = function(name, mode) { modes[name] = mode; }; -exports.defineMIME = function(mime, spec) { mimeModes[mime] = spec; }; -exports.getMode = function(options, spec) { - if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) - spec = mimeModes[spec]; - if (typeof spec == "string") - var mname = spec, config = {}; - else if (spec != null) - var mname = spec.name, config = spec; - var mfactory = modes[mname]; - if (!mfactory) throw new Error("Unknown mode: " + spec); - return mfactory(options, config || {}); -}; - -exports.runMode = function(string, modespec, callback) { - var mode = exports.getMode({indentUnit: 2}, modespec); - var lines = splitLines(string), state = exports.startState(mode); - for (var i = 0, e = lines.length; i < e; ++i) { - if (i) callback("\n"); - var stream = new exports.StringStream(lines[i]); - while (!stream.eol()) { - var style = mode.token(stream, state); - callback(stream.current(), style, i, stream.start); - stream.start = stream.pos; - } - } -}; diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/runmode.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/runmode.js deleted file mode 100644 index a988bde43f..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/runmode.js +++ /dev/null @@ -1,53 +0,0 @@ -CodeMirror.runMode = function(string, modespec, callback, options) { - function esc(str) { - return str.replace(/[<&]/, function(ch) { return ch == "<" ? "<" : "&"; }); - } - - var mode = CodeMirror.getMode(CodeMirror.defaults, modespec); - var isNode = callback.nodeType == 1; - var tabSize = (options && options.tabSize) || CodeMirror.defaults.tabSize; - if (isNode) { - var node = callback, accum = [], col = 0; - callback = function(text, style) { - if (text == "\n") { - accum.push("
    "); - col = 0; - return; - } - var escaped = ""; - // HTML-escape and replace tabs - for (var pos = 0;;) { - var idx = text.indexOf("\t", pos); - if (idx == -1) { - escaped += esc(text.slice(pos)); - col += text.length - pos; - break; - } else { - col += idx - pos; - escaped += esc(text.slice(pos, idx)); - var size = tabSize - col % tabSize; - col += size; - for (var i = 0; i < size; ++i) escaped += " "; - pos = idx + 1; - } - } - - if (style) - accum.push("" + escaped + ""); - else - accum.push(escaped); - }; - } - var lines = CodeMirror.splitLines(string), state = CodeMirror.startState(mode); - for (var i = 0, e = lines.length; i < e; ++i) { - if (i) callback("\n"); - var stream = new CodeMirror.StringStream(lines[i]); - while (!stream.eol()) { - var style = mode.token(stream, state); - callback(stream.current(), style, i, stream.start); - stream.start = stream.pos; - } - } - if (isNode) - node.innerHTML = accum.join(""); -}; diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/search.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/search.js deleted file mode 100644 index 6338c1ffbf..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/search.js +++ /dev/null @@ -1,118 +0,0 @@ -// Define search commands. Depends on dialog.js or another -// implementation of the openDialog method. - -// Replace works a little oddly -- it will do the replace on the next -// Ctrl-G (or whatever is bound to findNext) press. You prevent a -// replace by making sure the match is no longer selected when hitting -// Ctrl-G. - -(function() { - function SearchState() { - this.posFrom = this.posTo = this.query = null; - this.marked = []; - } - function getSearchState(cm) { - return cm._searchState || (cm._searchState = new SearchState()); - } - function getSearchCursor(cm, query, pos) { - // Heuristic: if the query string is all lowercase, do a case insensitive search. - return cm.getSearchCursor(query, pos, typeof query == "string" && query == query.toLowerCase()); - } - function dialog(cm, text, shortText, f) { - if (cm.openDialog) cm.openDialog(text, f); - else f(prompt(shortText, "")); - } - function confirmDialog(cm, text, shortText, fs) { - if (cm.openConfirm) cm.openConfirm(text, fs); - else if (confirm(shortText)) fs[0](); - } - function parseQuery(query) { - var isRE = query.match(/^\/(.*)\/([a-z]*)$/); - return isRE ? new RegExp(isRE[1], isRE[2].indexOf("i") == -1 ? "" : "i") : query; - } - var queryDialog = - 'Search: (Use /re/ syntax for regexp search)'; - function doSearch(cm, rev) { - var state = getSearchState(cm); - if (state.query) return findNext(cm, rev); - dialog(cm, queryDialog, "Search for:", function(query) { - cm.operation(function() { - if (!query || state.query) return; - state.query = parseQuery(query); - if (cm.lineCount() < 2000) { // This is too expensive on big documents. - for (var cursor = getSearchCursor(cm, state.query); cursor.findNext();) - state.marked.push(cm.markText(cursor.from(), cursor.to(), "CodeMirror-searching")); - } - state.posFrom = state.posTo = cm.getCursor(); - findNext(cm, rev); - }); - }); - } - function findNext(cm, rev) {cm.operation(function() { - var state = getSearchState(cm); - var cursor = getSearchCursor(cm, state.query, rev ? state.posFrom : state.posTo); - if (!cursor.find(rev)) { - cursor = getSearchCursor(cm, state.query, rev ? {line: cm.lineCount() - 1} : {line: 0, ch: 0}); - if (!cursor.find(rev)) return; - } - cm.setSelection(cursor.from(), cursor.to()); - state.posFrom = cursor.from(); state.posTo = cursor.to(); - });} - function clearSearch(cm) {cm.operation(function() { - var state = getSearchState(cm); - if (!state.query) return; - state.query = null; - for (var i = 0; i < state.marked.length; ++i) state.marked[i].clear(); - state.marked.length = 0; - });} - - var replaceQueryDialog = - 'Replace: (Use /re/ syntax for regexp search)'; - var replacementQueryDialog = 'With: '; - var doReplaceConfirm = "Replace? "; - function replace(cm, all) { - dialog(cm, replaceQueryDialog, "Replace:", function(query) { - if (!query) return; - query = parseQuery(query); - dialog(cm, replacementQueryDialog, "Replace with:", function(text) { - if (all) { - cm.compoundChange(function() { cm.operation(function() { - for (var cursor = getSearchCursor(cm, query); cursor.findNext();) { - if (typeof query != "string") { - var match = cm.getRange(cursor.from(), cursor.to()).match(query); - cursor.replace(text.replace(/\$(\d)/, function(w, i) {return match[i];})); - } else cursor.replace(text); - } - });}); - } else { - clearSearch(cm); - var cursor = getSearchCursor(cm, query, cm.getCursor()); - function advance() { - var start = cursor.from(), match; - if (!(match = cursor.findNext())) { - cursor = getSearchCursor(cm, query); - if (!(match = cursor.findNext()) || - (start && cursor.from().line == start.line && cursor.from().ch == start.ch)) return; - } - cm.setSelection(cursor.from(), cursor.to()); - confirmDialog(cm, doReplaceConfirm, "Replace?", - [function() {doReplace(match);}, advance]); - } - function doReplace(match) { - cursor.replace(typeof query == "string" ? text : - text.replace(/\$(\d)/, function(w, i) {return match[i];})); - advance(); - } - advance(); - } - }); - }); - } - - CodeMirror.commands.find = function(cm) {clearSearch(cm); doSearch(cm);}; - CodeMirror.commands.findNext = doSearch; - CodeMirror.commands.findPrev = function(cm) {doSearch(cm, true);}; - CodeMirror.commands.clearSearch = clearSearch; - CodeMirror.commands.replace = replace; - CodeMirror.commands.replaceAll = function(cm) {replace(cm, true);}; -})(); diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/searchcursor.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/searchcursor.js deleted file mode 100644 index c05f4886b8..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/searchcursor.js +++ /dev/null @@ -1,119 +0,0 @@ -(function(){ - function SearchCursor(cm, query, pos, caseFold) { - this.atOccurrence = false; this.cm = cm; - if (caseFold == null && typeof query == "string") caseFold = false; - - pos = pos ? cm.clipPos(pos) : {line: 0, ch: 0}; - this.pos = {from: pos, to: pos}; - - // The matches method is filled in based on the type of query. - // It takes a position and a direction, and returns an object - // describing the next occurrence of the query, or null if no - // more matches were found. - if (typeof query != "string") { // Regexp match - if (!query.global) query = new RegExp(query.source, query.ignoreCase ? "ig" : "g"); - this.matches = function(reverse, pos) { - if (reverse) { - query.lastIndex = 0; - var line = cm.getLine(pos.line).slice(0, pos.ch), match = query.exec(line), start = 0; - while (match) { - start += match.index; - line = line.slice(match.index); - query.lastIndex = 0; - var newmatch = query.exec(line); - if (newmatch) match = newmatch; - else break; - start++; - } - } else { - query.lastIndex = pos.ch; - var line = cm.getLine(pos.line), match = query.exec(line), - start = match && match.index; - } - if (match) - return {from: {line: pos.line, ch: start}, - to: {line: pos.line, ch: start + match[0].length}, - match: match}; - }; - } else { // String query - if (caseFold) query = query.toLowerCase(); - var fold = caseFold ? function(str){return str.toLowerCase();} : function(str){return str;}; - var target = query.split("\n"); - // Different methods for single-line and multi-line queries - if (target.length == 1) - this.matches = function(reverse, pos) { - var line = fold(cm.getLine(pos.line)), len = query.length, match; - if (reverse ? (pos.ch >= len && (match = line.lastIndexOf(query, pos.ch - len)) != -1) - : (match = line.indexOf(query, pos.ch)) != -1) - return {from: {line: pos.line, ch: match}, - to: {line: pos.line, ch: match + len}}; - }; - else - this.matches = function(reverse, pos) { - var ln = pos.line, idx = (reverse ? target.length - 1 : 0), match = target[idx], line = fold(cm.getLine(ln)); - var offsetA = (reverse ? line.indexOf(match) + match.length : line.lastIndexOf(match)); - if (reverse ? offsetA >= pos.ch || offsetA != match.length - : offsetA <= pos.ch || offsetA != line.length - match.length) - return; - for (;;) { - if (reverse ? !ln : ln == cm.lineCount() - 1) return; - line = fold(cm.getLine(ln += reverse ? -1 : 1)); - match = target[reverse ? --idx : ++idx]; - if (idx > 0 && idx < target.length - 1) { - if (line != match) return; - else continue; - } - var offsetB = (reverse ? line.lastIndexOf(match) : line.indexOf(match) + match.length); - if (reverse ? offsetB != line.length - match.length : offsetB != match.length) - return; - var start = {line: pos.line, ch: offsetA}, end = {line: ln, ch: offsetB}; - return {from: reverse ? end : start, to: reverse ? start : end}; - } - }; - } - } - - SearchCursor.prototype = { - findNext: function() {return this.find(false);}, - findPrevious: function() {return this.find(true);}, - - find: function(reverse) { - var self = this, pos = this.cm.clipPos(reverse ? this.pos.from : this.pos.to); - function savePosAndFail(line) { - var pos = {line: line, ch: 0}; - self.pos = {from: pos, to: pos}; - self.atOccurrence = false; - return false; - } - - for (;;) { - if (this.pos = this.matches(reverse, pos)) { - this.atOccurrence = true; - return this.pos.match || true; - } - if (reverse) { - if (!pos.line) return savePosAndFail(0); - pos = {line: pos.line-1, ch: this.cm.getLine(pos.line-1).length}; - } - else { - var maxLine = this.cm.lineCount(); - if (pos.line == maxLine - 1) return savePosAndFail(maxLine); - pos = {line: pos.line+1, ch: 0}; - } - } - }, - - from: function() {if (this.atOccurrence) return this.pos.from;}, - to: function() {if (this.atOccurrence) return this.pos.to;}, - - replace: function(newText) { - var self = this; - if (this.atOccurrence) - self.pos.to = this.cm.replaceRange(newText, self.pos.from, self.pos.to); - } - }; - - CodeMirror.defineExtension("getSearchCursor", function(query, pos, caseFold) { - return new SearchCursor(this, query, pos, caseFold); - }); -})(); diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/simple-hint-customized.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/simple-hint-customized.js deleted file mode 100644 index 4771938758..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/simple-hint-customized.js +++ /dev/null @@ -1,86 +0,0 @@ -(function () { - CodeMirror.simpleHint = function (editor, getHints) { - // We want a single cursor position. - if (editor.somethingSelected()) return; - //don't show completion if the token is empty - var tempToken = editor.getTokenAt(editor.getCursor()); - if (!(/[\S]/gi.test(tempToken.string))) return; - - var result = getHints(editor); - if (!result || !result.list.length) return; - var completions = result.list; - function insert(str) { - editor.replaceRange(str, result.from, result.to); - } - // When there is only one completion, use it directly. - if (completions.length == 1) { insert(completions[0]); return true; } - - // Build the select widget - var complete = document.createElement("div"); - complete.className = "CodeMirror-completions"; - var sel = complete.appendChild(document.createElement("select")); - // Opera doesn't move the selection when pressing up/down in a - // multi-select, but it does properly support the size property on - // single-selects, so no multi-select is necessary. - if (!window.opera) sel.multiple = true; - - for (var i = 0; i < completions.length; ++i) { - var opt = sel.appendChild(document.createElement("option")); - var completion = completions[i]; - - if (typeof completion === 'object') { - opt.appendChild(document.createTextNode(completion[0])); - opt.setAttribute("value", completion[1]); - } - else if(typeof completion === 'string') { - opt.appendChild(document.createTextNode(completion)); - opt.setAttribute("value", completion); - } - } - sel.firstChild.selected = true; - sel.size = Math.min(10, completions.length); - var pos = editor.cursorCoords(); - complete.style.left = pos.x + "px"; - complete.style.top = pos.yBot + "px"; - document.body.appendChild(complete); - // If we're at the edge of the screen, then we want the menu to appear on the left of the cursor. - var winW = window.innerWidth || Math.max(document.body.offsetWidth, document.documentElement.offsetWidth); - if (winW - pos.x < sel.clientWidth) - complete.style.left = (pos.x - sel.clientWidth) + "px"; - // Hack to hide the scrollbar. - if (completions.length <= 10) - complete.style.width = (sel.clientWidth - 1) + "px"; - - var done = false; - function close() { - if (done) return; - done = true; - complete.parentNode.removeChild(complete); - } - function pick() { - insert(completions[sel.selectedIndex][1]); - close(); - setTimeout(function () { editor.focus(); }, 50); - } - CodeMirror.connect(sel, "blur", close); - CodeMirror.connect(sel, "keydown", function (event) { - var code = event.keyCode; - // Enter - if (code == 13) { CodeMirror.e_stop(event); pick(); } - // Escape - else if (code == 27) { CodeMirror.e_stop(event); close(); editor.focus(); } - else if (code != 38 && code != 40) { - close(); editor.focus(); - // Pass the event to the CodeMirror instance so that it can handle things like backspace properly. - editor.triggerOnKeyDown(event); - setTimeout(function () { CodeMirror.simpleHint(editor, getHints); }, 50); - } - }); - CodeMirror.connect(sel, "dblclick", pick); - - sel.focus(); - // Opera sometimes ignores focusing a freshly created node - if (window.opera) setTimeout(function () { if (!done) sel.focus(); }, 100); - return true; - }; -})(); diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/simple-hint.css b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/simple-hint.css deleted file mode 100644 index 1872bdebe7..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/simple-hint.css +++ /dev/null @@ -1,16 +0,0 @@ -.CodeMirror-completions { - position: absolute; - z-index: 10; - overflow: hidden; - -webkit-box-shadow: 2px 3px 5px rgba(0,0,0,.2); - -moz-box-shadow: 2px 3px 5px rgba(0,0,0,.2); - box-shadow: 2px 3px 5px rgba(0,0,0,.2); -} -.CodeMirror-completions select { - background: #fafafa; - outline: none; - border: none; - padding: 0; - margin: 0; - font-family: monospace; -} diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/simple-hint.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/simple-hint.js deleted file mode 100644 index a481a0845f..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/simple-hint.js +++ /dev/null @@ -1,97 +0,0 @@ -(function() { - CodeMirror.simpleHint = function(editor, getHints, givenOptions) { - // Determine effective options based on given values and defaults. - var options = {}, defaults = CodeMirror.simpleHint.defaults; - for (var opt in defaults) - if (defaults.hasOwnProperty(opt)) - options[opt] = (givenOptions && givenOptions.hasOwnProperty(opt) ? givenOptions : defaults)[opt]; - - function collectHints(previousToken) { - // We want a single cursor position. - if (editor.somethingSelected()) return; - - var tempToken = editor.getTokenAt(editor.getCursor()); - - // Don't show completions if token has changed and the option is set. - if (options.closeOnTokenChange && previousToken != null && - (tempToken.start != previousToken.start || tempToken.className != previousToken.className)) { - return; - } - - var result = getHints(editor); - if (!result || !result.list.length) return; - var completions = result.list; - function insert(str) { - editor.replaceRange(str, result.from, result.to); - } - // When there is only one completion, use it directly. - if (completions.length == 1) {insert(completions[0]); return true;} - - // Build the select widget - var complete = document.createElement("div"); - complete.className = "CodeMirror-completions"; - var sel = complete.appendChild(document.createElement("select")); - // Opera doesn't move the selection when pressing up/down in a - // multi-select, but it does properly support the size property on - // single-selects, so no multi-select is necessary. - if (!window.opera) sel.multiple = true; - for (var i = 0; i < completions.length; ++i) { - var opt = sel.appendChild(document.createElement("option")); - opt.appendChild(document.createTextNode(completions[i])); - } - sel.firstChild.selected = true; - sel.size = Math.min(10, completions.length); - var pos = editor.cursorCoords(); - complete.style.left = pos.x + "px"; - complete.style.top = pos.yBot + "px"; - document.body.appendChild(complete); - // If we're at the edge of the screen, then we want the menu to appear on the left of the cursor. - var winW = window.innerWidth || Math.max(document.body.offsetWidth, document.documentElement.offsetWidth); - if(winW - pos.x < sel.clientWidth) - complete.style.left = (pos.x - sel.clientWidth) + "px"; - // Hack to hide the scrollbar. - if (completions.length <= 10) - complete.style.width = (sel.clientWidth - 1) + "px"; - - var done = false; - function close() { - if (done) return; - done = true; - complete.parentNode.removeChild(complete); - } - function pick() { - insert(completions[sel.selectedIndex]); - close(); - setTimeout(function(){editor.focus();}, 50); - } - CodeMirror.connect(sel, "blur", close); - CodeMirror.connect(sel, "keydown", function(event) { - var code = event.keyCode; - // Enter - if (code == 13) {CodeMirror.e_stop(event); pick();} - // Escape - else if (code == 27) {CodeMirror.e_stop(event); close(); editor.focus();} - else if (code != 38 && code != 40 && code != 33 && code != 34) { - close(); editor.focus(); - // Pass the event to the CodeMirror instance so that it can handle things like backspace properly. - editor.triggerOnKeyDown(event); - // Don't show completions if the code is backspace and the option is set. - if (!options.closeOnBackspace || code != 8) { - setTimeout(function(){collectHints(tempToken);}, 50); - } - } - }); - CodeMirror.connect(sel, "dblclick", pick); - - sel.focus(); - // Opera sometimes ignores focusing a freshly created node - if (window.opera) setTimeout(function(){if (!done) sel.focus();}, 100); - return true; - } - return collectHints(); - }; - CodeMirror.simpleHint.defaults = { - closeOnBackspace: true, - closeOnTokenChange: false - }; -})(); diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/xml-hint.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/xml-hint.js deleted file mode 100644 index f47855683d..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/lib/util/xml-hint.js +++ /dev/null @@ -1,137 +0,0 @@ - -(function() { - - CodeMirror.xmlHints = []; - - CodeMirror.xmlHint = function(cm, simbol) { - - if(simbol.length > 0) { - var cursor = cm.getCursor(); - cm.replaceSelection(simbol); - cursor = {line: cursor.line, ch: cursor.ch + 1}; - cm.setCursor(cursor); - } - - // dirty hack for simple-hint to receive getHint event on space - var getTokenAt = editor.getTokenAt; - - editor.getTokenAt = function() { return 'disabled'; }; - CodeMirror.simpleHint(cm, getHint); - - editor.getTokenAt = getTokenAt; - }; - - var getHint = function(cm) { - - var cursor = cm.getCursor(); - - if (cursor.ch > 0) { - - var text = cm.getRange({line: 0, ch: 0}, cursor); - var typed = ''; - var simbol = ''; - for(var i = text.length - 1; i >= 0; i--) { - if(text[i] == ' ' || text[i] == '<') { - simbol = text[i]; - break; - } - else { - typed = text[i] + typed; - } - } - - text = text.slice(0, text.length - typed.length); - - var path = getActiveElement(cm, text) + simbol; - var hints = CodeMirror.xmlHints[path]; - - if(typeof hints === 'undefined') - hints = ['']; - else { - hints = hints.slice(0); - for (var i = hints.length - 1; i >= 0; i--) { - if(hints[i].indexOf(typed) != 0) - hints.splice(i, 1); - } - } - - return { - list: hints, - from: { line: cursor.line, ch: cursor.ch - typed.length }, - to: cursor - }; - }; - }; - - var getActiveElement = function(codeMirror, text) { - - var element = ''; - - if(text.length >= 0) { - - var regex = new RegExp('<([^!?][^\\s/>]*).*?>', 'g'); - - var matches = []; - var match; - while ((match = regex.exec(text)) != null) { - matches.push({ - tag: match[1], - selfclose: (match[0].slice(match[0].length - 2) === '/>') - }); - } - - for (var i = matches.length - 1, skip = 0; i >= 0; i--) { - - var item = matches[i]; - - if (item.tag[0] == '/') - { - skip++; - } - else if (item.selfclose == false) - { - if (skip > 0) - { - skip--; - } - else - { - element = '<' + item.tag + '>' + element; - } - } - } - - element += getOpenTag(text); - } - - return element; - }; - - var getOpenTag = function(text) { - - var open = text.lastIndexOf('<'); - var close = text.lastIndexOf('>'); - - if (close < open) - { - text = text.slice(open); - - if(text != '<') { - - var space = text.indexOf(' '); - if(space < 0) - space = text.indexOf('\t'); - if(space < 0) - space = text.indexOf('\n'); - - if (space < 0) - space = text.length; - - return text.slice(0, space); - } - } - - return ''; - }; - -})(); diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/clike/clike.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/clike/clike.js deleted file mode 100644 index df90300b40..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/clike/clike.js +++ /dev/null @@ -1,284 +0,0 @@ -CodeMirror.defineMode("clike", function(config, parserConfig) { - var indentUnit = config.indentUnit, - keywords = parserConfig.keywords || {}, - builtin = parserConfig.builtin || {}, - blockKeywords = parserConfig.blockKeywords || {}, - atoms = parserConfig.atoms || {}, - hooks = parserConfig.hooks || {}, - multiLineStrings = parserConfig.multiLineStrings; - var isOperatorChar = /[+\-*&%=<>!?|\/]/; - - var curPunc; - - function tokenBase(stream, state) { - var ch = stream.next(); - if (hooks[ch]) { - var result = hooks[ch](stream, state); - if (result !== false) return result; - } - if (ch == '"' || ch == "'") { - state.tokenize = tokenString(ch); - return state.tokenize(stream, state); - } - if (/[\[\]{}\(\),;\:\.]/.test(ch)) { - curPunc = ch; - return null; - } - if (/\d/.test(ch)) { - stream.eatWhile(/[\w\.]/); - return "number"; - } - if (ch == "/") { - if (stream.eat("*")) { - state.tokenize = tokenComment; - return tokenComment(stream, state); - } - if (stream.eat("/")) { - stream.skipToEnd(); - return "comment"; - } - } - if (isOperatorChar.test(ch)) { - stream.eatWhile(isOperatorChar); - return "operator"; - } - stream.eatWhile(/[\w\$_]/); - var cur = stream.current(); - if (keywords.propertyIsEnumerable(cur)) { - if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement"; - return "keyword"; - } - if (builtin.propertyIsEnumerable(cur)) { - if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement"; - return "builtin"; - } - if (atoms.propertyIsEnumerable(cur)) return "atom"; - return "variable"; - } - - function tokenString(quote) { - return function(stream, state) { - var escaped = false, next, end = false; - while ((next = stream.next()) != null) { - if (next == quote && !escaped) {end = true; break;} - escaped = !escaped && next == "\\"; - } - if (end || !(escaped || multiLineStrings)) - state.tokenize = null; - return "string"; - }; - } - - function tokenComment(stream, state) { - var maybeEnd = false, ch; - while (ch = stream.next()) { - if (ch == "/" && maybeEnd) { - state.tokenize = null; - break; - } - maybeEnd = (ch == "*"); - } - return "comment"; - } - - function Context(indented, column, type, align, prev) { - this.indented = indented; - this.column = column; - this.type = type; - this.align = align; - this.prev = prev; - } - function pushContext(state, col, type) { - return state.context = new Context(state.indented, col, type, null, state.context); - } - function popContext(state) { - var t = state.context.type; - if (t == ")" || t == "]" || t == "}") - state.indented = state.context.indented; - return state.context = state.context.prev; - } - - // Interface - - return { - startState: function(basecolumn) { - return { - tokenize: null, - context: new Context((basecolumn || 0) - indentUnit, 0, "top", false), - indented: 0, - startOfLine: true - }; - }, - - token: function(stream, state) { - var ctx = state.context; - if (stream.sol()) { - if (ctx.align == null) ctx.align = false; - state.indented = stream.indentation(); - state.startOfLine = true; - } - if (stream.eatSpace()) return null; - curPunc = null; - var style = (state.tokenize || tokenBase)(stream, state); - if (style == "comment" || style == "meta") return style; - if (ctx.align == null) ctx.align = true; - - if ((curPunc == ";" || curPunc == ":") && ctx.type == "statement") popContext(state); - else if (curPunc == "{") pushContext(state, stream.column(), "}"); - else if (curPunc == "[") pushContext(state, stream.column(), "]"); - else if (curPunc == "(") pushContext(state, stream.column(), ")"); - else if (curPunc == "}") { - while (ctx.type == "statement") ctx = popContext(state); - if (ctx.type == "}") ctx = popContext(state); - while (ctx.type == "statement") ctx = popContext(state); - } - else if (curPunc == ctx.type) popContext(state); - else if (ctx.type == "}" || ctx.type == "top" || (ctx.type == "statement" && curPunc == "newstatement")) - pushContext(state, stream.column(), "statement"); - state.startOfLine = false; - return style; - }, - - indent: function(state, textAfter) { - if (state.tokenize != tokenBase && state.tokenize != null) return 0; - var ctx = state.context, firstChar = textAfter && textAfter.charAt(0); - if (ctx.type == "statement" && firstChar == "}") ctx = ctx.prev; - var closing = firstChar == ctx.type; - if (ctx.type == "statement") return ctx.indented + (firstChar == "{" ? 0 : indentUnit); - else if (ctx.align) return ctx.column + (closing ? 0 : 1); - else return ctx.indented + (closing ? 0 : indentUnit); - }, - - electricChars: "{}" - }; -}); - -(function() { - function words(str) { - var obj = {}, words = str.split(" "); - for (var i = 0; i < words.length; ++i) obj[words[i]] = true; - return obj; - } - var cKeywords = "auto if break int case long char register continue return default short do sizeof " + - "double static else struct entry switch extern typedef float union for unsigned " + - "goto while enum void const signed volatile"; - - function cppHook(stream, state) { - if (!state.startOfLine) return false; - stream.skipToEnd(); - return "meta"; - } - - // C#-style strings where "" escapes a quote. - function tokenAtString(stream, state) { - var next; - while ((next = stream.next()) != null) { - if (next == '"' && !stream.eat('"')) { - state.tokenize = null; - break; - } - } - return "string"; - } - - function mimes(ms, mode) { - for (var i = 0; i < ms.length; ++i) CodeMirror.defineMIME(ms[i], mode); - } - - mimes(["text/x-csrc", "text/x-c", "text/x-chdr"], { - name: "clike", - keywords: words(cKeywords), - blockKeywords: words("case do else for if switch while struct"), - atoms: words("null"), - hooks: {"#": cppHook} - }); - mimes(["text/x-c++src", "text/x-c++hdr"], { - name: "clike", - keywords: words(cKeywords + " asm dynamic_cast namespace reinterpret_cast try bool explicit new " + - "static_cast typeid catch operator template typename class friend private " + - "this using const_cast inline public throw virtual delete mutable protected " + - "wchar_t"), - blockKeywords: words("catch class do else finally for if struct switch try while"), - atoms: words("true false null"), - hooks: {"#": cppHook} - }); - CodeMirror.defineMIME("text/x-java", { - name: "clike", - keywords: words("abstract assert boolean break byte case catch char class const continue default " + - "do double else enum extends final finally float for goto if implements import " + - "instanceof int interface long native new package private protected public " + - "return short static strictfp super switch synchronized this throw throws transient " + - "try void volatile while"), - blockKeywords: words("catch class do else finally for if switch try while"), - atoms: words("true false null"), - hooks: { - "@": function(stream, state) { - stream.eatWhile(/[\w\$_]/); - return "meta"; - } - } - }); - CodeMirror.defineMIME("text/x-csharp", { - name: "clike", - keywords: words("abstract as base break case catch checked class const continue" + - " default delegate do else enum event explicit extern finally fixed for" + - " foreach goto if implicit in interface internal is lock namespace new" + - " operator out override params private protected public readonly ref return sealed" + - " sizeof stackalloc static struct switch this throw try typeof unchecked" + - " unsafe using virtual void volatile while add alias ascending descending dynamic from get" + - " global group into join let orderby partial remove select set value var yield"), - blockKeywords: words("catch class do else finally for foreach if struct switch try while"), - builtin: words("Boolean Byte Char DateTime DateTimeOffset Decimal Double" + - " Guid Int16 Int32 Int64 Object SByte Single String TimeSpan UInt16 UInt32" + - " UInt64 bool byte char decimal double short int long object" + - " sbyte float string ushort uint ulong"), - atoms: words("true false null"), - hooks: { - "@": function(stream, state) { - if (stream.eat('"')) { - state.tokenize = tokenAtString; - return tokenAtString(stream, state); - } - stream.eatWhile(/[\w\$_]/); - return "meta"; - } - } - }); - CodeMirror.defineMIME("text/x-scala", { - name: "clike", - keywords: words( - - /* scala */ - "abstract case catch class def do else extends false final finally for forSome if " + - "implicit import lazy match new null object override package private protected return " + - "sealed super this throw trait try trye type val var while with yield _ : = => <- <: " + - "<% >: # @ " + - - /* package scala */ - "assert assume require print println printf readLine readBoolean readByte readShort " + - "readChar readInt readLong readFloat readDouble " + - - "AnyVal App Application Array BufferedIterator BigDecimal BigInt Char Console Either " + - "Enumeration Equiv Error Exception Fractional Function IndexedSeq Integral Iterable " + - "Iterator List Map Numeric Nil NotNull Option Ordered Ordering PartialFunction PartialOrdering " + - "Product Proxy Range Responder Seq Serializable Set Specializable Stream StringBuilder " + - "StringContext Symbol Throwable Traversable TraversableOnce Tuple Unit Vector :: #:: " + - - /* package java.lang */ - "Boolean Byte Character CharSequence Class ClassLoader Cloneable Comparable " + - "Compiler Double Exception Float Integer Long Math Number Object Package Pair Process " + - "Runtime Runnable SecurityManager Short StackTraceElement StrictMath String " + - "StringBuffer System Thread ThreadGroup ThreadLocal Throwable Triple Void" - - - ), - blockKeywords: words("catch class do else finally for forSome if match switch try while"), - atoms: words("true false null"), - hooks: { - "@": function(stream, state) { - stream.eatWhile(/[\w\$_]/); - return "meta"; - } - } - }); -}()); diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/clike/index.html b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/clike/index.html deleted file mode 100644 index ae86b9b603..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/clike/index.html +++ /dev/null @@ -1,102 +0,0 @@ - - - - - CodeMirror: C-like mode - - - - - - - -

    CodeMirror: C-like mode

    - -
    - - - -

    Simple mode that tries to handle C-like languages as well as it - can. Takes two configuration parameters: keywords, an - object whose property names are the keywords in the language, - and useCPP, which determines whether C preprocessor - directives are recognized.

    - -

    MIME types defined: text/x-csrc - (C code), text/x-c++src (C++ - code), text/x-java (Java - code), text/x-csharp (C#).

    - - diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/clike/scala.html b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/clike/scala.html deleted file mode 100644 index 4004610766..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/clike/scala.html +++ /dev/null @@ -1,766 +0,0 @@ - - - - - CodeMirror: C-like mode - - - - - - - - -
    - - - - - - diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/clojure/clojure.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/clojure/clojure.js deleted file mode 100644 index d90e2e3f8f..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/clojure/clojure.js +++ /dev/null @@ -1,206 +0,0 @@ -/** - * Author: Hans Engel - * Branched from CodeMirror's Scheme mode (by Koh Zi Han, based on implementation by Koh Zi Chun) - */ -CodeMirror.defineMode("clojure", function (config, mode) { - var BUILTIN = "builtin", COMMENT = "comment", STRING = "string", TAG = "tag", - ATOM = "atom", NUMBER = "number", BRACKET = "bracket", KEYWORD = "keyword"; - var INDENT_WORD_SKIP = 2, KEYWORDS_SKIP = 1; - - function makeKeywords(str) { - var obj = {}, words = str.split(" "); - for (var i = 0; i < words.length; ++i) obj[words[i]] = true; - return obj; - } - - var atoms = makeKeywords("true false nil"); - - var keywords = makeKeywords( - "defn defn- def def- defonce defmulti defmethod defmacro defstruct deftype defprotocol defrecord defproject deftest slice defalias defhinted defmacro- defn-memo defnk defnk defonce- defunbound defunbound- defvar defvar- let letfn do case cond condp for loop recur when when-not when-let when-first if if-let if-not . .. -> ->> doto and or dosync doseq dotimes dorun doall load import unimport ns in-ns refer try catch finally throw with-open with-local-vars binding gen-class gen-and-load-class gen-and-save-class handler-case handle"); - - var builtins = makeKeywords( - "* *1 *2 *3 *agent* *allow-unresolved-vars* *assert *clojure-version* *command-line-args* *compile-files* *compile-path* *e *err* *file* *flush-on-newline* *in* *macro-meta* *math-context* *ns* *out* *print-dup* *print-length* *print-level* *print-meta* *print-readably* *read-eval* *source-path* *use-context-classloader* *warn-on-reflection* + - / < <= = == > >= accessor aclone agent agent-errors aget alength alias all-ns alter alter-meta! alter-var-root amap ancestors and apply areduce array-map aset aset-boolean aset-byte aset-char aset-double aset-float aset-int aset-long aset-short assert assoc assoc! assoc-in associative? atom await await-for await1 bases bean bigdec bigint binding bit-and bit-and-not bit-clear bit-flip bit-not bit-or bit-set bit-shift-left bit-shift-right bit-test bit-xor boolean boolean-array booleans bound-fn bound-fn* butlast byte byte-array bytes case cast char char-array char-escape-string char-name-string char? chars chunk chunk-append chunk-buffer chunk-cons chunk-first chunk-next chunk-rest chunked-seq? class class? clear-agent-errors clojure-version coll? comment commute comp comparator compare compare-and-set! compile complement concat cond condp conj conj! cons constantly construct-proxy contains? count counted? create-ns create-struct cycle dec decimal? declare definline defmacro defmethod defmulti defn defn- defonce defstruct delay delay? deliver deref derive descendants destructure disj disj! dissoc dissoc! distinct distinct? doall doc dorun doseq dosync dotimes doto double double-array doubles drop drop-last drop-while empty empty? ensure enumeration-seq eval even? every? extend extend-protocol extend-type extends? extenders false? ffirst file-seq filter find find-doc find-ns find-var first float float-array float? floats flush fn fn? fnext for force format future future-call future-cancel future-cancelled? future-done? future? gen-class gen-interface gensym get get-in get-method get-proxy-class get-thread-bindings get-validator hash hash-map hash-set identical? identity if-let if-not ifn? import in-ns inc init-proxy instance? int int-array integer? interleave intern interpose into into-array ints io! isa? iterate iterator-seq juxt key keys keyword keyword? last lazy-cat lazy-seq let letfn line-seq list list* list? load load-file load-reader load-string loaded-libs locking long long-array longs loop macroexpand macroexpand-1 make-array make-hierarchy map map? mapcat max max-key memfn memoize merge merge-with meta method-sig methods min min-key mod name namespace neg? newline next nfirst nil? nnext not not-any? not-empty not-every? not= ns ns-aliases ns-imports ns-interns ns-map ns-name ns-publics ns-refers ns-resolve ns-unalias ns-unmap nth nthnext num number? odd? or parents partial partition pcalls peek persistent! pmap pop pop! pop-thread-bindings pos? pr pr-str prefer-method prefers primitives-classnames print print-ctor print-doc print-dup print-method print-namespace-doc print-simple print-special-doc print-str printf println println-str prn prn-str promise proxy proxy-call-with-super proxy-mappings proxy-name proxy-super push-thread-bindings pvalues quot rand rand-int range ratio? rational? rationalize re-find re-groups re-matcher re-matches re-pattern re-seq read read-line read-string reify reduce ref ref-history-count ref-max-history ref-min-history ref-set refer refer-clojure release-pending-sends rem remove remove-method remove-ns repeat repeatedly replace replicate require reset! reset-meta! resolve rest resultset-seq reverse reversible? rseq rsubseq satisfies? second select-keys send send-off seq seq? seque sequence sequential? set set-validator! set? short short-array shorts shutdown-agents slurp some sort sort-by sorted-map sorted-map-by sorted-set sorted-set-by sorted? special-form-anchor special-symbol? split-at split-with str stream? string? struct struct-map subs subseq subvec supers swap! symbol symbol? sync syntax-symbol-anchor take take-last take-nth take-while test the-ns time to-array to-array-2d trampoline transient tree-seq true? type unchecked-add unchecked-dec unchecked-divide unchecked-inc unchecked-multiply unchecked-negate unchecked-remainder unchecked-subtract underive unquote unquote-splicing update-in update-proxy use val vals var-get var-set var? vary-meta vec vector vector? when when-first when-let when-not while with-bindings with-bindings* with-in-str with-loading-context with-local-vars with-meta with-open with-out-str with-precision xml-seq"); - - var indentKeys = makeKeywords( - // Built-ins - "ns fn def defn defmethod bound-fn if if-not case condp when while when-not when-first do future comment doto locking proxy with-open with-precision reify deftype defrecord defprotocol extend extend-protocol extend-type try catch " + - - // Binding forms - "let letfn binding loop for doseq dotimes when-let if-let " + - - // Data structures - "defstruct struct-map assoc " + - - // clojure.test - "testing deftest " + - - // contrib - "handler-case handle dotrace deftrace"); - - var tests = { - digit: /\d/, - digit_or_colon: /[\d:]/, - hex: /[0-9a-f]/i, - sign: /[+-]/, - exponent: /e/i, - keyword_char: /[^\s\(\[\;\)\]]/, - basic: /[\w\$_\-]/, - lang_keyword: /[\w*+!\-_?:\/]/ - }; - - function stateStack(indent, type, prev) { // represents a state stack object - this.indent = indent; - this.type = type; - this.prev = prev; - } - - function pushStack(state, indent, type) { - state.indentStack = new stateStack(indent, type, state.indentStack); - } - - function popStack(state) { - state.indentStack = state.indentStack.prev; - } - - function isNumber(ch, stream){ - // hex - if ( ch === '0' && stream.eat(/x/i) ) { - stream.eatWhile(tests.hex); - return true; - } - - // leading sign - if ( ( ch == '+' || ch == '-' ) && ( tests.digit.test(stream.peek()) ) ) { - stream.eat(tests.sign); - ch = stream.next(); - } - - if ( tests.digit.test(ch) ) { - stream.eat(ch); - stream.eatWhile(tests.digit); - - if ( '.' == stream.peek() ) { - stream.eat('.'); - stream.eatWhile(tests.digit); - } - - if ( stream.eat(tests.exponent) ) { - stream.eat(tests.sign); - stream.eatWhile(tests.digit); - } - - return true; - } - - return false; - } - - return { - startState: function () { - return { - indentStack: null, - indentation: 0, - mode: false - }; - }, - - token: function (stream, state) { - if (state.indentStack == null && stream.sol()) { - // update indentation, but only if indentStack is empty - state.indentation = stream.indentation(); - } - - // skip spaces - if (stream.eatSpace()) { - return null; - } - var returnType = null; - - switch(state.mode){ - case "string": // multi-line string parsing mode - var next, escaped = false; - while ((next = stream.next()) != null) { - if (next == "\"" && !escaped) { - - state.mode = false; - break; - } - escaped = !escaped && next == "\\"; - } - returnType = STRING; // continue on in string mode - break; - default: // default parsing mode - var ch = stream.next(); - - if (ch == "\"") { - state.mode = "string"; - returnType = STRING; - } else if (ch == "'" && !( tests.digit_or_colon.test(stream.peek()) )) { - returnType = ATOM; - } else if (ch == ";") { // comment - stream.skipToEnd(); // rest of the line is a comment - returnType = COMMENT; - } else if (isNumber(ch,stream)){ - returnType = NUMBER; - } else if (ch == "(" || ch == "[") { - var keyWord = '', indentTemp = stream.column(), letter; - /** - Either - (indent-word .. - (non-indent-word .. - (;something else, bracket, etc. - */ - - if (ch == "(") while ((letter = stream.eat(tests.keyword_char)) != null) { - keyWord += letter; - } - - if (keyWord.length > 0 && (indentKeys.propertyIsEnumerable(keyWord) || - /^(?:def|with)/.test(keyWord))) { // indent-word - pushStack(state, indentTemp + INDENT_WORD_SKIP, ch); - } else { // non-indent word - // we continue eating the spaces - stream.eatSpace(); - if (stream.eol() || stream.peek() == ";") { - // nothing significant after - // we restart indentation 1 space after - pushStack(state, indentTemp + 1, ch); - } else { - pushStack(state, indentTemp + stream.current().length, ch); // else we match - } - } - stream.backUp(stream.current().length - 1); // undo all the eating - - returnType = BRACKET; - } else if (ch == ")" || ch == "]") { - returnType = BRACKET; - if (state.indentStack != null && state.indentStack.type == (ch == ")" ? "(" : "[")) { - popStack(state); - } - } else if ( ch == ":" ) { - stream.eatWhile(tests.lang_keyword); - return ATOM; - } else { - stream.eatWhile(tests.basic); - - if (keywords && keywords.propertyIsEnumerable(stream.current())) { - returnType = KEYWORD; - } else if (builtins && builtins.propertyIsEnumerable(stream.current())) { - returnType = BUILTIN; - } else if (atoms && atoms.propertyIsEnumerable(stream.current())) { - returnType = ATOM; - } else returnType = null; - } - } - - return returnType; - }, - - indent: function (state, textAfter) { - if (state.indentStack == null) return state.indentation; - return state.indentStack.indent; - } - }; -}); - -CodeMirror.defineMIME("text/x-clojure", "clojure"); diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/clojure/index.html b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/clojure/index.html deleted file mode 100644 index 450ec78fb1..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/clojure/index.html +++ /dev/null @@ -1,67 +0,0 @@ - - - - - CodeMirror: Clojure mode - - - - - - - -

    CodeMirror: Clojure mode

    -
    - - -

    MIME types defined: text/x-clojure.

    - - - diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/coffeescript/coffeescript.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/coffeescript/coffeescript.js deleted file mode 100644 index b987cae426..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/coffeescript/coffeescript.js +++ /dev/null @@ -1,346 +0,0 @@ -/** - * Link to the project's GitHub page: - * https://github.com/pickhardt/coffeescript-codemirror-mode - */ -CodeMirror.defineMode('coffeescript', function(conf) { - var ERRORCLASS = 'error'; - - function wordRegexp(words) { - return new RegExp("^((" + words.join(")|(") + "))\\b"); - } - - var singleOperators = new RegExp("^[\\+\\-\\*/%&|\\^~<>!\?]"); - var singleDelimiters = new RegExp('^[\\(\\)\\[\\]\\{\\},:`=;\\.]'); - var doubleOperators = new RegExp("^((\->)|(\=>)|(\\+\\+)|(\\+\\=)|(\\-\\-)|(\\-\\=)|(\\*\\*)|(\\*\\=)|(\\/\\/)|(\\/\\=)|(==)|(!=)|(<=)|(>=)|(<>)|(<<)|(>>)|(//))"); - var doubleDelimiters = new RegExp("^((\\.\\.)|(\\+=)|(\\-=)|(\\*=)|(%=)|(/=)|(&=)|(\\|=)|(\\^=))"); - var tripleDelimiters = new RegExp("^((\\.\\.\\.)|(//=)|(>>=)|(<<=)|(\\*\\*=))"); - var identifiers = new RegExp("^[_A-Za-z$][_A-Za-z$0-9]*"); - var properties = new RegExp("^(@|this\.)[_A-Za-z$][_A-Za-z$0-9]*"); - - var wordOperators = wordRegexp(['and', 'or', 'not', - 'is', 'isnt', 'in', - 'instanceof', 'typeof']); - var indentKeywords = ['for', 'while', 'loop', 'if', 'unless', 'else', - 'switch', 'try', 'catch', 'finally', 'class']; - var commonKeywords = ['break', 'by', 'continue', 'debugger', 'delete', - 'do', 'in', 'of', 'new', 'return', 'then', - 'this', 'throw', 'when', 'until']; - - var keywords = wordRegexp(indentKeywords.concat(commonKeywords)); - - indentKeywords = wordRegexp(indentKeywords); - - - var stringPrefixes = new RegExp("^('{3}|\"{3}|['\"])"); - var regexPrefixes = new RegExp("^(/{3}|/)"); - var commonConstants = ['Infinity', 'NaN', 'undefined', 'null', 'true', 'false', 'on', 'off', 'yes', 'no']; - var constants = wordRegexp(commonConstants); - - // Tokenizers - function tokenBase(stream, state) { - // Handle scope changes - if (stream.sol()) { - var scopeOffset = state.scopes[0].offset; - if (stream.eatSpace()) { - var lineOffset = stream.indentation(); - if (lineOffset > scopeOffset) { - return 'indent'; - } else if (lineOffset < scopeOffset) { - return 'dedent'; - } - return null; - } else { - if (scopeOffset > 0) { - dedent(stream, state); - } - } - } - if (stream.eatSpace()) { - return null; - } - - var ch = stream.peek(); - - // Handle docco title comment (single line) - if (stream.match("####")) { - stream.skipToEnd(); - return 'comment'; - } - - // Handle multi line comments - if (stream.match("###")) { - state.tokenize = longComment; - return state.tokenize(stream, state); - } - - // Single line comment - if (ch === '#') { - stream.skipToEnd(); - return 'comment'; - } - - // Handle number literals - if (stream.match(/^-?[0-9\.]/, false)) { - var floatLiteral = false; - // Floats - if (stream.match(/^-?\d*\.\d+(e[\+\-]?\d+)?/i)) { - floatLiteral = true; - } - if (stream.match(/^-?\d+\.\d*/)) { - floatLiteral = true; - } - if (stream.match(/^-?\.\d+/)) { - floatLiteral = true; - } - - if (floatLiteral) { - // prevent from getting extra . on 1.. - if (stream.peek() == "."){ - stream.backUp(1); - } - return 'number'; - } - // Integers - var intLiteral = false; - // Hex - if (stream.match(/^-?0x[0-9a-f]+/i)) { - intLiteral = true; - } - // Decimal - if (stream.match(/^-?[1-9]\d*(e[\+\-]?\d+)?/)) { - intLiteral = true; - } - // Zero by itself with no other piece of number. - if (stream.match(/^-?0(?![\dx])/i)) { - intLiteral = true; - } - if (intLiteral) { - return 'number'; - } - } - - // Handle strings - if (stream.match(stringPrefixes)) { - state.tokenize = tokenFactory(stream.current(), 'string'); - return state.tokenize(stream, state); - } - // Handle regex literals - if (stream.match(regexPrefixes)) { - if (stream.current() != '/' || stream.match(/^.*\//, false)) { // prevent highlight of division - state.tokenize = tokenFactory(stream.current(), 'string-2'); - return state.tokenize(stream, state); - } else { - stream.backUp(1); - } - } - - // Handle operators and delimiters - if (stream.match(tripleDelimiters) || stream.match(doubleDelimiters)) { - return 'punctuation'; - } - if (stream.match(doubleOperators) - || stream.match(singleOperators) - || stream.match(wordOperators)) { - return 'operator'; - } - if (stream.match(singleDelimiters)) { - return 'punctuation'; - } - - if (stream.match(constants)) { - return 'atom'; - } - - if (stream.match(keywords)) { - return 'keyword'; - } - - if (stream.match(identifiers)) { - return 'variable'; - } - - if (stream.match(properties)) { - return 'property'; - } - - // Handle non-detected items - stream.next(); - return ERRORCLASS; - } - - function tokenFactory(delimiter, outclass) { - var singleline = delimiter.length == 1; - return function tokenString(stream, state) { - while (!stream.eol()) { - stream.eatWhile(/[^'"\/\\]/); - if (stream.eat('\\')) { - stream.next(); - if (singleline && stream.eol()) { - return outclass; - } - } else if (stream.match(delimiter)) { - state.tokenize = tokenBase; - return outclass; - } else { - stream.eat(/['"\/]/); - } - } - if (singleline) { - if (conf.mode.singleLineStringErrors) { - outclass = ERRORCLASS; - } else { - state.tokenize = tokenBase; - } - } - return outclass; - }; - } - - function longComment(stream, state) { - while (!stream.eol()) { - stream.eatWhile(/[^#]/); - if (stream.match("###")) { - state.tokenize = tokenBase; - break; - } - stream.eatWhile("#"); - } - return "comment"; - } - - function indent(stream, state, type) { - type = type || 'coffee'; - var indentUnit = 0; - if (type === 'coffee') { - for (var i = 0; i < state.scopes.length; i++) { - if (state.scopes[i].type === 'coffee') { - indentUnit = state.scopes[i].offset + conf.indentUnit; - break; - } - } - } else { - indentUnit = stream.column() + stream.current().length; - } - state.scopes.unshift({ - offset: indentUnit, - type: type - }); - } - - function dedent(stream, state) { - if (state.scopes.length == 1) return; - if (state.scopes[0].type === 'coffee') { - var _indent = stream.indentation(); - var _indent_index = -1; - for (var i = 0; i < state.scopes.length; ++i) { - if (_indent === state.scopes[i].offset) { - _indent_index = i; - break; - } - } - if (_indent_index === -1) { - return true; - } - while (state.scopes[0].offset !== _indent) { - state.scopes.shift(); - } - return false; - } else { - state.scopes.shift(); - return false; - } - } - - function tokenLexer(stream, state) { - var style = state.tokenize(stream, state); - var current = stream.current(); - - // Handle '.' connected identifiers - if (current === '.') { - style = state.tokenize(stream, state); - current = stream.current(); - if (style === 'variable') { - return 'variable'; - } else { - return ERRORCLASS; - } - } - - // Handle scope changes. - if (current === 'return') { - state.dedent += 1; - } - if (((current === '->' || current === '=>') && - !state.lambda && - state.scopes[0].type == 'coffee' && - stream.peek() === '') - || style === 'indent') { - indent(stream, state); - } - var delimiter_index = '[({'.indexOf(current); - if (delimiter_index !== -1) { - indent(stream, state, '])}'.slice(delimiter_index, delimiter_index+1)); - } - if (indentKeywords.exec(current)){ - indent(stream, state); - } - if (current == 'then'){ - dedent(stream, state); - } - - - if (style === 'dedent') { - if (dedent(stream, state)) { - return ERRORCLASS; - } - } - delimiter_index = '])}'.indexOf(current); - if (delimiter_index !== -1) { - if (dedent(stream, state)) { - return ERRORCLASS; - } - } - if (state.dedent > 0 && stream.eol() && state.scopes[0].type == 'coffee') { - if (state.scopes.length > 1) state.scopes.shift(); - state.dedent -= 1; - } - - return style; - } - - var external = { - startState: function(basecolumn) { - return { - tokenize: tokenBase, - scopes: [{offset:basecolumn || 0, type:'coffee'}], - lastToken: null, - lambda: false, - dedent: 0 - }; - }, - - token: function(stream, state) { - var style = tokenLexer(stream, state); - - state.lastToken = {style:style, content: stream.current()}; - - if (stream.eol() && stream.lambda) { - state.lambda = false; - } - - return style; - }, - - indent: function(state, textAfter) { - if (state.tokenize != tokenBase) { - return 0; - } - - return state.scopes[0].offset; - } - - }; - return external; -}); - -CodeMirror.defineMIME('text/x-coffeescript', 'coffeescript'); diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/coffeescript/index.html b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/coffeescript/index.html deleted file mode 100644 index d970bdd310..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/coffeescript/index.html +++ /dev/null @@ -1,728 +0,0 @@ - - - - - CodeMirror: CoffeeScript mode - - - - - - - -

    CodeMirror: CoffeeScript mode

    -
    - - -

    MIME types defined: text/x-coffeescript.

    - -

    The CoffeeScript mode was written by Jeff Pickhardt (license).

    - - - diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/commonlisp/commonlisp.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/commonlisp/commonlisp.js deleted file mode 100644 index 24785c4aca..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/commonlisp/commonlisp.js +++ /dev/null @@ -1,101 +0,0 @@ -CodeMirror.defineMode("commonlisp", function (config) { - var assumeBody = /^with|^def|^do|^prog|case$|^cond$|bind$|when$|unless$/; - var numLiteral = /^(?:[+\-]?(?:\d+|\d*\.\d+)(?:[efd][+\-]?\d+)?|[+\-]?\d+(?:\/[+\-]?\d+)?|#b[+\-]?[01]+|#o[+\-]?[0-7]+|#x[+\-]?[\da-f]+)/; - var symbol = /[^\s'`,@()\[\]";]/; - var type; - - function readSym(stream) { - var ch; - while (ch = stream.next()) { - if (ch == "\\") stream.next(); - else if (!symbol.test(ch)) { stream.backUp(1); break; } - } - return stream.current(); - } - - function base(stream, state) { - if (stream.eatSpace()) {type = "ws"; return null;} - if (stream.match(numLiteral)) return "number"; - var ch = stream.next(); - if (ch == "\\") ch = stream.next(); - - if (ch == '"') return (state.tokenize = inString)(stream, state); - else if (ch == "(") { type = "open"; return "bracket"; } - else if (ch == ")" || ch == "]") { type = "close"; return "bracket"; } - else if (ch == ";") { stream.skipToEnd(); type = "ws"; return "comment"; } - else if (/['`,@]/.test(ch)) return null; - else if (ch == "|") { - if (stream.skipTo("|")) { stream.next(); return "symbol"; } - else { stream.skipToEnd(); return "error"; } - } else if (ch == "#") { - var ch = stream.next(); - if (ch == "[") { type = "open"; return "bracket"; } - else if (/[+\-=\.']/.test(ch)) return null; - else if (/\d/.test(ch) && stream.match(/^\d*#/)) return null; - else if (ch == "|") return (state.tokenize = inComment)(stream, state); - else if (ch == ":") { readSym(stream); return "meta"; } - else return "error"; - } else { - var name = readSym(stream); - if (name == ".") return null; - type = "symbol"; - if (name == "nil" || name == "t") return "atom"; - if (name.charAt(0) == ":") return "keyword"; - if (name.charAt(0) == "&") return "variable-2"; - return "variable"; - } - } - - function inString(stream, state) { - var escaped = false, next; - while (next = stream.next()) { - if (next == '"' && !escaped) { state.tokenize = base; break; } - escaped = !escaped && next == "\\"; - } - return "string"; - } - - function inComment(stream, state) { - var next, last; - while (next = stream.next()) { - if (next == "#" && last == "|") { state.tokenize = base; break; } - last = next; - } - type = "ws"; - return "comment"; - } - - return { - startState: function () { - return {ctx: {prev: null, start: 0, indentTo: 0}, tokenize: base}; - }, - - token: function (stream, state) { - if (stream.sol() && typeof state.ctx.indentTo != "number") - state.ctx.indentTo = state.ctx.start + 1; - - type = null; - var style = state.tokenize(stream, state); - if (type != "ws") { - if (state.ctx.indentTo == null) { - if (type == "symbol" && assumeBody.test(stream.current())) - state.ctx.indentTo = state.ctx.start + config.indentUnit; - else - state.ctx.indentTo = "next"; - } else if (state.ctx.indentTo == "next") { - state.ctx.indentTo = stream.column(); - } - } - if (type == "open") state.ctx = {prev: state.ctx, start: stream.column(), indentTo: null}; - else if (type == "close") state.ctx = state.ctx.prev || state.ctx; - return style; - }, - - indent: function (state, textAfter) { - var i = state.ctx.indentTo; - return typeof i == "number" ? i : state.ctx.start + 1; - } - }; -}); - -CodeMirror.defineMIME("text/x-common-lisp", "commonlisp"); diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/commonlisp/index.html b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/commonlisp/index.html deleted file mode 100644 index 842e1360fd..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/commonlisp/index.html +++ /dev/null @@ -1,165 +0,0 @@ - - - - - CodeMirror: Common Lisp mode - - - - - - - -

    CodeMirror: Common Lisp mode

    -
    - - -

    MIME types defined: text/x-common-lisp.

    - - - diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/css/css.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/css/css.js deleted file mode 100644 index 882015c8d4..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/css/css.js +++ /dev/null @@ -1,448 +0,0 @@ -CodeMirror.defineMode("css", function(config) { - var indentUnit = config.indentUnit, type; - - var atMediaTypes = keySet([ - "all", "aural", "braille", "handheld", "print", "projection", "screen", - "tty", "tv", "embossed" - ]); - - var atMediaFeatures = keySet([ - "width", "min-width", "max-width", "height", "min-height", "max-height", - "device-width", "min-device-width", "max-device-width", "device-height", - "min-device-height", "max-device-height", "aspect-ratio", - "min-aspect-ratio", "max-aspect-ratio", "device-aspect-ratio", - "min-device-aspect-ratio", "max-device-aspect-ratio", "color", "min-color", - "max-color", "color-index", "min-color-index", "max-color-index", - "monochrome", "min-monochrome", "max-monochrome", "resolution", - "min-resolution", "max-resolution", "scan", "grid" - ]); - - var propertyKeywords = keySet([ - "align-content", "align-items", "align-self", "alignment-adjust", - "alignment-baseline", "anchor-point", "animation", "animation-delay", - "animation-direction", "animation-duration", "animation-iteration-count", - "animation-name", "animation-play-state", "animation-timing-function", - "appearance", "azimuth", "backface-visibility", "background", - "background-attachment", "background-clip", "background-color", - "background-image", "background-origin", "background-position", - "background-repeat", "background-size", "baseline-shift", "binding", - "bleed", "bookmark-label", "bookmark-level", "bookmark-state", - "bookmark-target", "border", "border-bottom", "border-bottom-color", - "border-bottom-left-radius", "border-bottom-right-radius", - "border-bottom-style", "border-bottom-width", "border-collapse", - "border-color", "border-image", "border-image-outset", - "border-image-repeat", "border-image-slice", "border-image-source", - "border-image-width", "border-left", "border-left-color", - "border-left-style", "border-left-width", "border-radius", "border-right", - "border-right-color", "border-right-style", "border-right-width", - "border-spacing", "border-style", "border-top", "border-top-color", - "border-top-left-radius", "border-top-right-radius", "border-top-style", - "border-top-width", "border-width", "bottom", "box-decoration-break", - "box-shadow", "box-sizing", "break-after", "break-before", "break-inside", - "caption-side", "clear", "clip", "color", "color-profile", "column-count", - "column-fill", "column-gap", "column-rule", "column-rule-color", - "column-rule-style", "column-rule-width", "column-span", "column-width", - "columns", "content", "counter-increment", "counter-reset", "crop", "cue", - "cue-after", "cue-before", "cursor", "direction", "display", - "dominant-baseline", "drop-initial-after-adjust", - "drop-initial-after-align", "drop-initial-before-adjust", - "drop-initial-before-align", "drop-initial-size", "drop-initial-value", - "elevation", "empty-cells", "fit", "fit-position", "flex", "flex-basis", - "flex-direction", "flex-flow", "flex-grow", "flex-shrink", "flex-wrap", - "float", "float-offset", "font", "font-feature-settings", "font-family", - "font-kerning", "font-language-override", "font-size", "font-size-adjust", - "font-stretch", "font-style", "font-synthesis", "font-variant", - "font-variant-alternates", "font-variant-caps", "font-variant-east-asian", - "font-variant-ligatures", "font-variant-numeric", "font-variant-position", - "font-weight", "grid-cell", "grid-column", "grid-column-align", - "grid-column-sizing", "grid-column-span", "grid-columns", "grid-flow", - "grid-row", "grid-row-align", "grid-row-sizing", "grid-row-span", - "grid-rows", "grid-template", "hanging-punctuation", "height", "hyphens", - "icon", "image-orientation", "image-rendering", "image-resolution", - "inline-box-align", "justify-content", "left", "letter-spacing", - "line-break", "line-height", "line-stacking", "line-stacking-ruby", - "line-stacking-shift", "line-stacking-strategy", "list-style", - "list-style-image", "list-style-position", "list-style-type", "margin", - "margin-bottom", "margin-left", "margin-right", "margin-top", - "marker-offset", "marks", "marquee-direction", "marquee-loop", - "marquee-play-count", "marquee-speed", "marquee-style", "max-height", - "max-width", "min-height", "min-width", "move-to", "nav-down", "nav-index", - "nav-left", "nav-right", "nav-up", "opacity", "order", "orphans", "outline", - "outline-color", "outline-offset", "outline-style", "outline-width", - "overflow", "overflow-style", "overflow-wrap", "overflow-x", "overflow-y", - "padding", "padding-bottom", "padding-left", "padding-right", "padding-top", - "page", "page-break-after", "page-break-before", "page-break-inside", - "page-policy", "pause", "pause-after", "pause-before", "perspective", - "perspective-origin", "pitch", "pitch-range", "play-during", "position", - "presentation-level", "punctuation-trim", "quotes", "rendering-intent", - "resize", "rest", "rest-after", "rest-before", "richness", "right", - "rotation", "rotation-point", "ruby-align", "ruby-overhang", - "ruby-position", "ruby-span", "size", "speak", "speak-as", "speak-header", - "speak-numeral", "speak-punctuation", "speech-rate", "stress", "string-set", - "tab-size", "table-layout", "target", "target-name", "target-new", - "target-position", "text-align", "text-align-last", "text-decoration", - "text-decoration-color", "text-decoration-line", "text-decoration-skip", - "text-decoration-style", "text-emphasis", "text-emphasis-color", - "text-emphasis-position", "text-emphasis-style", "text-height", - "text-indent", "text-justify", "text-outline", "text-shadow", - "text-space-collapse", "text-transform", "text-underline-position", - "text-wrap", "top", "transform", "transform-origin", "transform-style", - "transition", "transition-delay", "transition-duration", - "transition-property", "transition-timing-function", "unicode-bidi", - "vertical-align", "visibility", "voice-balance", "voice-duration", - "voice-family", "voice-pitch", "voice-range", "voice-rate", "voice-stress", - "voice-volume", "volume", "white-space", "widows", "width", "word-break", - "word-spacing", "word-wrap", "z-index" - ]); - - var colorKeywords = keySet([ - "black", "silver", "gray", "white", "maroon", "red", "purple", "fuchsia", - "green", "lime", "olive", "yellow", "navy", "blue", "teal", "aqua" - ]); - - var valueKeywords = keySet([ - "above", "absolute", "activeborder", "activecaption", "afar", - "after-white-space", "ahead", "alias", "all", "all-scroll", "alternate", - "always", "amharic", "amharic-abegede", "antialiased", "appworkspace", - "arabic-indic", "armenian", "asterisks", "auto", "avoid", "background", - "backwards", "baseline", "below", "bidi-override", "binary", "bengali", - "blink", "block", "block-axis", "bold", "bolder", "border", "border-box", - "both", "bottom", "break-all", "break-word", "button", "button-bevel", - "buttonface", "buttonhighlight", "buttonshadow", "buttontext", "cambodian", - "capitalize", "caps-lock-indicator", "caption", "captiontext", "caret", - "cell", "center", "checkbox", "circle", "cjk-earthly-branch", - "cjk-heavenly-stem", "cjk-ideographic", "clear", "clip", "close-quote", - "col-resize", "collapse", "compact", "condensed", "contain", "content", - "content-box", "context-menu", "continuous", "copy", "cover", "crop", - "cross", "crosshair", "currentcolor", "cursive", "dashed", "decimal", - "decimal-leading-zero", "default", "default-button", "destination-atop", - "destination-in", "destination-out", "destination-over", "devanagari", - "disc", "discard", "document", "dot-dash", "dot-dot-dash", "dotted", - "double", "down", "e-resize", "ease", "ease-in", "ease-in-out", "ease-out", - "element", "ellipsis", "embed", "end", "ethiopic", "ethiopic-abegede", - "ethiopic-abegede-am-et", "ethiopic-abegede-gez", "ethiopic-abegede-ti-er", - "ethiopic-abegede-ti-et", "ethiopic-halehame-aa-er", - "ethiopic-halehame-aa-et", "ethiopic-halehame-am-et", - "ethiopic-halehame-gez", "ethiopic-halehame-om-et", - "ethiopic-halehame-sid-et", "ethiopic-halehame-so-et", - "ethiopic-halehame-ti-er", "ethiopic-halehame-ti-et", - "ethiopic-halehame-tig", "ew-resize", "expanded", "extra-condensed", - "extra-expanded", "fantasy", "fast", "fill", "fixed", "flat", "footnotes", - "forwards", "from", "geometricPrecision", "georgian", "graytext", "groove", - "gujarati", "gurmukhi", "hand", "hangul", "hangul-consonant", "hebrew", - "help", "hidden", "hide", "higher", "highlight", "highlighttext", - "hiragana", "hiragana-iroha", "horizontal", "hsl", "hsla", "icon", "ignore", - "inactiveborder", "inactivecaption", "inactivecaptiontext", "infinite", - "infobackground", "infotext", "inherit", "initial", "inline", "inline-axis", - "inline-block", "inline-table", "inset", "inside", "intrinsic", "invert", - "italic", "justify", "kannada", "katakana", "katakana-iroha", "khmer", - "landscape", "lao", "large", "larger", "left", "level", "lighter", - "line-through", "linear", "lines", "list-item", "listbox", "listitem", - "local", "logical", "loud", "lower", "lower-alpha", "lower-armenian", - "lower-greek", "lower-hexadecimal", "lower-latin", "lower-norwegian", - "lower-roman", "lowercase", "ltr", "malayalam", "match", - "media-controls-background", "media-current-time-display", - "media-fullscreen-button", "media-mute-button", "media-play-button", - "media-return-to-realtime-button", "media-rewind-button", - "media-seek-back-button", "media-seek-forward-button", "media-slider", - "media-sliderthumb", "media-time-remaining-display", "media-volume-slider", - "media-volume-slider-container", "media-volume-sliderthumb", "medium", - "menu", "menulist", "menulist-button", "menulist-text", - "menulist-textfield", "menutext", "message-box", "middle", "min-intrinsic", - "mix", "mongolian", "monospace", "move", "multiple", "myanmar", "n-resize", - "narrower", "navy", "ne-resize", "nesw-resize", "no-close-quote", "no-drop", - "no-open-quote", "no-repeat", "none", "normal", "not-allowed", "nowrap", - "ns-resize", "nw-resize", "nwse-resize", "oblique", "octal", "open-quote", - "optimizeLegibility", "optimizeSpeed", "oriya", "oromo", "outset", - "outside", "overlay", "overline", "padding", "padding-box", "painted", - "paused", "persian", "plus-darker", "plus-lighter", "pointer", "portrait", - "pre", "pre-line", "pre-wrap", "preserve-3d", "progress", "push-button", - "radio", "read-only", "read-write", "read-write-plaintext-only", "relative", - "repeat", "repeat-x", "repeat-y", "reset", "reverse", "rgb", "rgba", - "ridge", "right", "round", "row-resize", "rtl", "run-in", "running", - "s-resize", "sans-serif", "scroll", "scrollbar", "se-resize", "searchfield", - "searchfield-cancel-button", "searchfield-decoration", - "searchfield-results-button", "searchfield-results-decoration", - "semi-condensed", "semi-expanded", "separate", "serif", "show", "sidama", - "single", "skip-white-space", "slide", "slider-horizontal", - "slider-vertical", "sliderthumb-horizontal", "sliderthumb-vertical", "slow", - "small", "small-caps", "small-caption", "smaller", "solid", "somali", - "source-atop", "source-in", "source-out", "source-over", "space", "square", - "square-button", "start", "static", "status-bar", "stretch", "stroke", - "sub", "subpixel-antialiased", "super", "sw-resize", "table", - "table-caption", "table-cell", "table-column", "table-column-group", - "table-footer-group", "table-header-group", "table-row", "table-row-group", - "telugu", "text", "text-bottom", "text-top", "textarea", "textfield", "thai", - "thick", "thin", "threeddarkshadow", "threedface", "threedhighlight", - "threedlightshadow", "threedshadow", "tibetan", "tigre", "tigrinya-er", - "tigrinya-er-abegede", "tigrinya-et", "tigrinya-et-abegede", "to", "top", - "transparent", "ultra-condensed", "ultra-expanded", "underline", "up", - "upper-alpha", "upper-armenian", "upper-greek", "upper-hexadecimal", - "upper-latin", "upper-norwegian", "upper-roman", "uppercase", "urdu", "url", - "vertical", "vertical-text", "visible", "visibleFill", "visiblePainted", - "visibleStroke", "visual", "w-resize", "wait", "wave", "white", "wider", - "window", "windowframe", "windowtext", "x-large", "x-small", "xor", - "xx-large", "xx-small", "yellow" - ]); - - function keySet(array) { var keys = {}; for (var i = 0; i < array.length; ++i) keys[array[i]] = true; return keys; } - function ret(style, tp) {type = tp; return style;} - - function tokenBase(stream, state) { - var ch = stream.next(); - if (ch == "@") {stream.eatWhile(/[\w\\\-]/); return ret("def", stream.current());} - else if (ch == "/" && stream.eat("*")) { - state.tokenize = tokenCComment; - return tokenCComment(stream, state); - } - else if (ch == "<" && stream.eat("!")) { - state.tokenize = tokenSGMLComment; - return tokenSGMLComment(stream, state); - } - else if (ch == "=") ret(null, "compare"); - else if ((ch == "~" || ch == "|") && stream.eat("=")) return ret(null, "compare"); - else if (ch == "\"" || ch == "'") { - state.tokenize = tokenString(ch); - return state.tokenize(stream, state); - } - else if (ch == "#") { - stream.eatWhile(/[\w\\\-]/); - return ret("atom", "hash"); - } - else if (ch == "!") { - stream.match(/^\s*\w*/); - return ret("keyword", "important"); - } - else if (/\d/.test(ch)) { - stream.eatWhile(/[\w.%]/); - return ret("number", "unit"); - } - else if (ch === "-") { - if (/\d/.test(stream.peek())) { - stream.eatWhile(/[\w.%]/); - return ret("number", "unit"); - } else if (stream.match(/^[^-]+-/)) { - return ret("meta", type); - } - } - else if (/[,+>*\/]/.test(ch)) { - return ret(null, "select-op"); - } - else if (ch == "." && stream.match(/^\w+/)) { - return ret("qualifier", type); - } - else if (ch == ":") { - return ret("operator", ch); - } - else if (/[;{}\[\]\(\)]/.test(ch)) { - return ret(null, ch); - } - else { - stream.eatWhile(/[\w\\\-]/); - return ret("property", "variable"); - } - } - - function tokenCComment(stream, state) { - var maybeEnd = false, ch; - while ((ch = stream.next()) != null) { - if (maybeEnd && ch == "/") { - state.tokenize = tokenBase; - break; - } - maybeEnd = (ch == "*"); - } - return ret("comment", "comment"); - } - - function tokenSGMLComment(stream, state) { - var dashes = 0, ch; - while ((ch = stream.next()) != null) { - if (dashes >= 2 && ch == ">") { - state.tokenize = tokenBase; - break; - } - dashes = (ch == "-") ? dashes + 1 : 0; - } - return ret("comment", "comment"); - } - - function tokenString(quote) { - return function(stream, state) { - var escaped = false, ch; - while ((ch = stream.next()) != null) { - if (ch == quote && !escaped) - break; - escaped = !escaped && ch == "\\"; - } - if (!escaped) state.tokenize = tokenBase; - return ret("string", "string"); - }; - } - - return { - startState: function(base) { - return {tokenize: tokenBase, - baseIndent: base || 0, - stack: []}; - }, - - token: function(stream, state) { - - // Use these terms when applicable (see http://www.xanthir.com/blog/b4E50) - // - // rule** or **ruleset: - // A selector + braces combo, or an at-rule. - // - // declaration block: - // A sequence of declarations. - // - // declaration: - // A property + colon + value combo. - // - // property value: - // The entire value of a property. - // - // component value: - // A single piece of a property value. Like the 5px in - // text-shadow: 0 0 5px blue;. Can also refer to things that are - // multiple terms, like the 1-4 terms that make up the background-size - // portion of the background shorthand. - // - // term: - // The basic unit of author-facing CSS, like a single number (5), - // dimension (5px), string ("foo"), or function. Officially defined - // by the CSS 2.1 grammar (look for the 'term' production) - // - // - // simple selector: - // A single atomic selector, like a type selector, an attr selector, a - // class selector, etc. - // - // compound selector: - // One or more simple selectors without a combinator. div.example is - // compound, div > .example is not. - // - // complex selector: - // One or more compound selectors chained with combinators. - // - // combinator: - // The parts of selectors that express relationships. There are four - // currently - the space (descendant combinator), the greater-than - // bracket (child combinator), the plus sign (next sibling combinator), - // and the tilda (following sibling combinator). - // - // sequence of selectors: - // One or more of the named type of selector chained with commas. - - if (stream.eatSpace()) return null; - var style = state.tokenize(stream, state); - - // Changing style returned based on context - var context = state.stack[state.stack.length-1]; - if (style == "property") { - if (context == "propertyValue"){ - if (valueKeywords[stream.current()]) { - style = "string-2"; - } else if (colorKeywords[stream.current()]) { - style = "keyword"; - } else { - style = "variable-2"; - } - } else if (context == "rule") { - if (!propertyKeywords[stream.current()]) { - style += " error"; - } - } else if (!context || context == "@media{") { - style = "tag"; - } else if (context == "@media") { - if (atMediaTypes[stream.current()]) { - style = "attribute"; // Known attribute - } else if (/^(only|not)$/i.test(stream.current())) { - style = "keyword"; - } else if (stream.current().toLowerCase() == "and") { - style = "error"; // "and" is only allowed in @mediaType - } else if (atMediaFeatures[stream.current()]) { - style = "error"; // Known property, should be in @mediaType( - } else { - // Unknown, expecting keyword or attribute, assuming attribute - style = "attribute error"; - } - } else if (context == "@mediaType") { - if (atMediaTypes[stream.current()]) { - style = "attribute"; - } else if (stream.current().toLowerCase() == "and") { - style = "operator"; - } else if (/^(only|not)$/i.test(stream.current())) { - style = "error"; // Only allowed in @media - } else if (atMediaFeatures[stream.current()]) { - style = "error"; // Known property, should be in parentheses - } else { - // Unknown attribute or property, but expecting property (preceded - // by "and"). Should be in parentheses - style = "error"; - } - } else if (context == "@mediaType(") { - if (propertyKeywords[stream.current()]) { - // do nothing, remains "property" - } else if (atMediaTypes[stream.current()]) { - style = "error"; // Known property, should be in parentheses - } else if (stream.current().toLowerCase() == "and") { - style = "operator"; - } else if (/^(only|not)$/i.test(stream.current())) { - style = "error"; // Only allowed in @media - } else { - style += " error"; - } - } else { - style = "error"; - } - } else if (style == "atom") { - if(!context || context == "@media{") { - style = "builtin"; - } else if (context == "propertyValue") { - if (!/^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/.test(stream.current())) { - style += " error"; - } - } else { - style = "error"; - } - } else if (context == "@media" && type == "{") { - style = "error"; - } - - // Push/pop context stack - if (type == "{") { - if (context == "@media" || context == "@mediaType") { - state.stack.pop(); - state.stack[state.stack.length-1] = "@media{"; - } - else state.stack.push("rule"); - } - else if (type == "}") { - state.stack.pop(); - if (context == "propertyValue") state.stack.pop(); - } - else if (type == "@media") state.stack.push("@media"); - else if (context == "@media" && /\b(keyword|attribute)\b/.test(style)) - state.stack.push("@mediaType"); - else if (context == "@mediaType" && stream.current() == ",") state.stack.pop(); - else if (context == "@mediaType" && type == "(") state.stack.push("@mediaType("); - else if (context == "@mediaType(" && type == ")") state.stack.pop(); - else if (context == "rule" && type == ":") state.stack.push("propertyValue"); - else if (context == "propertyValue" && type == ";") state.stack.pop(); - return style; - }, - - indent: function(state, textAfter) { - var n = state.stack.length; - if (/^\}/.test(textAfter)) - n -= state.stack[state.stack.length-1] == "propertyValue" ? 2 : 1; - return state.baseIndent + n * indentUnit; - }, - - electricChars: "}" - }; -}); - -CodeMirror.defineMIME("text/css", "css"); diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/css/index.html b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/css/index.html deleted file mode 100644 index 4cbcdf0d96..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/css/index.html +++ /dev/null @@ -1,58 +0,0 @@ - - - - - CodeMirror: CSS mode - - - - - - - -

    CodeMirror: CSS mode

    -
    - - -

    MIME types defined: text/css.

    - -

    Parsing/Highlighting Tests: normal, verbose.

    - - - diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/css/test.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/css/test.js deleted file mode 100644 index fdd237361a..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/css/test.js +++ /dev/null @@ -1,501 +0,0 @@ -// Initiate ModeTest and set defaults -var MT = ModeTest; -MT.modeName = 'css'; -MT.modeOptions = {}; - -// Requires at least one media query -MT.testMode( - 'atMediaEmpty', - '@media { }', - [ - 'def', '@media', - null, ' ', - 'error', '{', - null, ' }' - ] -); - -MT.testMode( - 'atMediaMultiple', - '@media not screen and (color), not print and (color) { }', - [ - 'def', '@media', - null, ' ', - 'keyword', 'not', - null, ' ', - 'attribute', 'screen', - null, ' ', - 'operator', 'and', - null, ' (', - 'property', 'color', - null, '), ', - 'keyword', 'not', - null, ' ', - 'attribute', 'print', - null, ' ', - 'operator', 'and', - null, ' (', - 'property', 'color', - null, ') { }' - ] -); - -MT.testMode( - 'atMediaCheckStack', - '@media screen { } foo { }', - [ - 'def', '@media', - null, ' ', - 'attribute', 'screen', - null, ' { } ', - 'tag', 'foo', - null, ' { }' - ] -); - -MT.testMode( - 'atMediaCheckStack', - '@media screen (color) { } foo { }', - [ - 'def', '@media', - null, ' ', - 'attribute', 'screen', - null, ' (', - 'property', 'color', - null, ') { } ', - 'tag', 'foo', - null, ' { }' - ] -); - -MT.testMode( - 'atMediaCheckStackInvalidAttribute', - '@media foobarhello { } foo { }', - [ - 'def', '@media', - null, ' ', - 'attribute error', 'foobarhello', - null, ' { } ', - 'tag', 'foo', - null, ' { }' - ] -); - -// Error, because "and" is only allowed immediately preceding a media expression -MT.testMode( - 'atMediaInvalidAttribute', - '@media foobarhello { }', - [ - 'def', '@media', - null, ' ', - 'attribute error', 'foobarhello', - null, ' { }' - ] -); - -// Error, because "and" is only allowed immediately preceding a media expression -MT.testMode( - 'atMediaInvalidAnd', - '@media and screen { }', - [ - 'def', '@media', - null, ' ', - 'error', 'and', - null, ' ', - 'attribute', 'screen', - null, ' { }' - ] -); - -// Error, because "not" is only allowed as the first item in each media query -MT.testMode( - 'atMediaInvalidNot', - '@media screen not (not) { }', - [ - 'def', '@media', - null, ' ', - 'attribute', 'screen', - null, ' ', - 'error', 'not', - null, ' (', - 'error', 'not', - null, ') { }' - ] -); - -// Error, because "only" is only allowed as the first item in each media query -MT.testMode( - 'atMediaInvalidOnly', - '@media screen only (only) { }', - [ - 'def', '@media', - null, ' ', - 'attribute', 'screen', - null, ' ', - 'error', 'only', - null, ' (', - 'error', 'only', - null, ') { }' - ] -); - -// Error, because "foobarhello" is neither a known type or property, but -// property was expected (after "and"), and it should be in parenthese. -MT.testMode( - 'atMediaUnknownType', - '@media screen and foobarhello { }', - [ - 'def', '@media', - null, ' ', - 'attribute', 'screen', - null, ' ', - 'operator', 'and', - null, ' ', - 'error', 'foobarhello', - null, ' { }' - ] -); - -// Error, because "color" is not a known type, but is a known property, and -// should be in parentheses. -MT.testMode( - 'atMediaInvalidType', - '@media screen and color { }', - [ - 'def', '@media', - null, ' ', - 'attribute', 'screen', - null, ' ', - 'operator', 'and', - null, ' ', - 'error', 'color', - null, ' { }' - ] -); - -// Error, because "print" is not a known property, but is a known type, -// and should not be in parenthese. -MT.testMode( - 'atMediaInvalidProperty', - '@media screen and (print) { }', - [ - 'def', '@media', - null, ' ', - 'attribute', 'screen', - null, ' ', - 'operator', 'and', - null, ' (', - 'error', 'print', - null, ') { }' - ] -); - -// Soft error, because "foobarhello" is not a known property or type. -MT.testMode( - 'atMediaUnknownProperty', - '@media screen and (foobarhello) { }', - [ - 'def', '@media', - null, ' ', - 'attribute', 'screen', - null, ' ', - 'operator', 'and', - null, ' (', - 'property error', 'foobarhello', - null, ') { }' - ] -); - -MT.testMode( - 'tagSelector', - 'foo { }', - [ - 'tag', 'foo', - null, ' { }' - ] -); - -MT.testMode( - 'classSelector', - '.foo { }', - [ - 'qualifier', '.foo', - null, ' { }' - ] -); - -MT.testMode( - 'idSelector', - '#foo { #foo }', - [ - 'builtin', '#foo', - null, ' { ', - 'error', '#foo', - null, ' }' - ] -); - -MT.testMode( - 'tagSelectorUnclosed', - 'foo { margin: 0 } bar { }', - [ - 'tag', 'foo', - null, ' { ', - 'property', 'margin', - 'operator', ':', - null, ' ', - 'number', '0', - null, ' } ', - 'tag', 'bar', - null, ' { }' - ] -); - -MT.testMode( - 'tagStringNoQuotes', - 'foo { font-family: hello world; }', - [ - 'tag', 'foo', - null, ' { ', - 'property', 'font-family', - 'operator', ':', - null, ' ', - 'variable-2', 'hello', - null, ' ', - 'variable-2', 'world', - null, '; }' - ] -); - -MT.testMode( - 'tagStringDouble', - 'foo { font-family: "hello world"; }', - [ - 'tag', 'foo', - null, ' { ', - 'property', 'font-family', - 'operator', ':', - null, ' ', - 'string', '"hello world"', - null, '; }' - ] -); - -MT.testMode( - 'tagStringSingle', - 'foo { font-family: \'hello world\'; }', - [ - 'tag', 'foo', - null, ' { ', - 'property', 'font-family', - 'operator', ':', - null, ' ', - 'string', '\'hello world\'', - null, '; }' - ] -); - -MT.testMode( - 'tagColorKeyword', - 'foo { color: black; }', - [ - 'tag', 'foo', - null, ' { ', - 'property', 'color', - 'operator', ':', - null, ' ', - 'keyword', 'black', - null, '; }' - ] -); - -MT.testMode( - 'tagColorHex3', - 'foo { background: #fff; }', - [ - 'tag', 'foo', - null, ' { ', - 'property', 'background', - 'operator', ':', - null, ' ', - 'atom', '#fff', - null, '; }' - ] -); - -MT.testMode( - 'tagColorHex6', - 'foo { background: #ffffff; }', - [ - 'tag', 'foo', - null, ' { ', - 'property', 'background', - 'operator', ':', - null, ' ', - 'atom', '#ffffff', - null, '; }' - ] -); - -MT.testMode( - 'tagColorHex4', - 'foo { background: #ffff; }', - [ - 'tag', 'foo', - null, ' { ', - 'property', 'background', - 'operator', ':', - null, ' ', - 'atom error', '#ffff', - null, '; }' - ] -); - -MT.testMode( - 'tagColorHexInvalid', - 'foo { background: #ffg; }', - [ - 'tag', 'foo', - null, ' { ', - 'property', 'background', - 'operator', ':', - null, ' ', - 'atom error', '#ffg', - null, '; }' - ] -); - -MT.testMode( - 'tagNegativeNumber', - 'foo { margin: -5px; }', - [ - 'tag', 'foo', - null, ' { ', - 'property', 'margin', - 'operator', ':', - null, ' ', - 'number', '-5px', - null, '; }' - ] -); - -MT.testMode( - 'tagPositiveNumber', - 'foo { padding: 5px; }', - [ - 'tag', 'foo', - null, ' { ', - 'property', 'padding', - 'operator', ':', - null, ' ', - 'number', '5px', - null, '; }' - ] -); - -MT.testMode( - 'tagVendor', - 'foo { -foo-box-sizing: -foo-border-box; }', - [ - 'tag', 'foo', - null, ' { ', - 'meta', '-foo-', - 'property', 'box-sizing', - 'operator', ':', - null, ' ', - 'meta', '-foo-', - 'string-2', 'border-box', - null, '; }' - ] -); - -MT.testMode( - 'tagBogusProperty', - 'foo { barhelloworld: 0; }', - [ - 'tag', 'foo', - null, ' { ', - 'property error', 'barhelloworld', - 'operator', ':', - null, ' ', - 'number', '0', - null, '; }' - ] -); - -MT.testMode( - 'tagTwoProperties', - 'foo { margin: 0; padding: 0; }', - [ - 'tag', 'foo', - null, ' { ', - 'property', 'margin', - 'operator', ':', - null, ' ', - 'number', '0', - null, '; ', - 'property', 'padding', - 'operator', ':', - null, ' ', - 'number', '0', - null, '; }' - ] -); -// -//MT.testMode( -// 'tagClass', -// '@media only screen and (min-width: 500px), print {foo.bar#hello { color: black !important; background: #f00; margin: -5px; padding: 5px; -foo-box-sizing: border-box; } /* world */}', -// [ -// 'def', '@media', -// null, ' ', -// 'keyword', 'only', -// null, ' ', -// 'attribute', 'screen', -// null, ' ', -// 'operator', 'and', -// null, ' ', -// 'bracket', '(', -// 'property', 'min-width', -// 'operator', ':', -// null, ' ', -// 'number', '500px', -// 'bracket', ')', -// null, ', ', -// 'attribute', 'print', -// null, ' {', -// 'tag', 'foo', -// 'qualifier', '.bar', -// 'header', '#hello', -// null, ' { ', -// 'property', 'color', -// 'operator', ':', -// null, ' ', -// 'keyword', 'black', -// null, ' ', -// 'keyword', '!important', -// null, '; ', -// 'property', 'background', -// 'operator', ':', -// null, ' ', -// 'atom', '#f00', -// null, '; ', -// 'property', 'padding', -// 'operator', ':', -// null, ' ', -// 'number', '5px', -// null, '; ', -// 'property', 'margin', -// 'operator', ':', -// null, ' ', -// 'number', '-5px', -// null, '; ', -// 'meta', '-foo-', -// 'property', 'box-sizing', -// 'operator', ':', -// null, ' ', -// 'string-2', 'border-box', -// null, '; } ', -// 'comment', '/* world */', -// null, '}' -// ] -//); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/diff/diff.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/diff/diff.js deleted file mode 100644 index 3fd33faac9..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/diff/diff.js +++ /dev/null @@ -1,32 +0,0 @@ -CodeMirror.defineMode("diff", function() { - - var TOKEN_NAMES = { - '+': 'tag', - '-': 'string', - '@': 'meta' - }; - - return { - token: function(stream) { - var tw_pos = stream.string.search(/[\t ]+?$/); - - if (!stream.sol() || tw_pos === 0) { - stream.skipToEnd(); - return ("error " + ( - TOKEN_NAMES[stream.string.charAt(0)] || '')).replace(/ $/, ''); - } - - var token_name = TOKEN_NAMES[stream.peek()] || stream.skipToEnd(); - - if (tw_pos === -1) { - stream.skipToEnd(); - } else { - stream.pos = tw_pos; - } - - return token_name; - } - }; -}); - -CodeMirror.defineMIME("text/x-diff", "diff"); diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/diff/index.html b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/diff/index.html deleted file mode 100644 index 05f57ff72e..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/diff/index.html +++ /dev/null @@ -1,105 +0,0 @@ - - - - - CodeMirror: Diff mode - - - - - - - -

    CodeMirror: Diff mode

    -
    - - -

    MIME types defined: text/x-diff.

    - - - diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/ecl/ecl.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/ecl/ecl.js deleted file mode 100644 index c0e4479270..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/ecl/ecl.js +++ /dev/null @@ -1,203 +0,0 @@ -CodeMirror.defineMode("ecl", function(config) { - - function words(str) { - var obj = {}, words = str.split(" "); - for (var i = 0; i < words.length; ++i) obj[words[i]] = true; - return obj; - } - - function metaHook(stream, state) { - if (!state.startOfLine) return false; - stream.skipToEnd(); - return "meta"; - } - - function tokenAtString(stream, state) { - var next; - while ((next = stream.next()) != null) { - if (next == '"' && !stream.eat('"')) { - state.tokenize = null; - break; - } - } - return "string"; - } - - var indentUnit = config.indentUnit; - var keyword = words("abs acos allnodes ascii asin asstring atan atan2 ave case choose choosen choosesets clustersize combine correlation cos cosh count covariance cron dataset dedup define denormalize distribute distributed distribution ebcdic enth error evaluate event eventextra eventname exists exp failcode failmessage fetch fromunicode getisvalid global graph group hash hash32 hash64 hashcrc hashmd5 having if index intformat isvalid iterate join keyunicode length library limit ln local log loop map matched matchlength matchposition matchtext matchunicode max merge mergejoin min nolocal nonempty normalize parse pipe power preload process project pull random range rank ranked realformat recordof regexfind regexreplace regroup rejected rollup round roundup row rowdiff sample set sin sinh sizeof soapcall sort sorted sqrt stepped stored sum table tan tanh thisnode topn tounicode transfer trim truncate typeof ungroup unicodeorder variance which workunit xmldecode xmlencode xmltext xmlunicode"); - var variable = words("apply assert build buildindex evaluate fail keydiff keypatch loadxml nothor notify output parallel sequential soapcall wait"); - var variable_2 = words("__compressed__ all and any as atmost before beginc++ best between case const counter csv descend encrypt end endc++ endmacro except exclusive expire export extend false few first flat from full function group header heading hole ifblock import in interface joined keep keyed last left limit load local locale lookup macro many maxcount maxlength min skew module named nocase noroot noscan nosort not of only opt or outer overwrite packed partition penalty physicallength pipe quote record relationship repeat return right scan self separator service shared skew skip sql store terminator thor threshold token transform trim true type unicodeorder unsorted validate virtual whole wild within xml xpath"); - var variable_3 = words("ascii big_endian boolean data decimal ebcdic integer pattern qstring real record rule set of string token udecimal unicode unsigned varstring varunicode"); - var builtin = words("checkpoint deprecated failcode failmessage failure global independent onwarning persist priority recovery stored success wait when"); - var blockKeywords = words("catch class do else finally for if switch try while"); - var atoms = words("true false null"); - var hooks = {"#": metaHook}; - var multiLineStrings; - var isOperatorChar = /[+\-*&%=<>!?|\/]/; - - var curPunc; - - function tokenBase(stream, state) { - var ch = stream.next(); - if (hooks[ch]) { - var result = hooks[ch](stream, state); - if (result !== false) return result; - } - if (ch == '"' || ch == "'") { - state.tokenize = tokenString(ch); - return state.tokenize(stream, state); - } - if (/[\[\]{}\(\),;\:\.]/.test(ch)) { - curPunc = ch; - return null; - } - if (/\d/.test(ch)) { - stream.eatWhile(/[\w\.]/); - return "number"; - } - if (ch == "/") { - if (stream.eat("*")) { - state.tokenize = tokenComment; - return tokenComment(stream, state); - } - if (stream.eat("/")) { - stream.skipToEnd(); - return "comment"; - } - } - if (isOperatorChar.test(ch)) { - stream.eatWhile(isOperatorChar); - return "operator"; - } - stream.eatWhile(/[\w\$_]/); - var cur = stream.current().toLowerCase(); - if (keyword.propertyIsEnumerable(cur)) { - if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement"; - return "keyword"; - } else if (variable.propertyIsEnumerable(cur)) { - if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement"; - return "variable"; - } else if (variable_2.propertyIsEnumerable(cur)) { - if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement"; - return "variable-2"; - } else if (variable_3.propertyIsEnumerable(cur)) { - if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement"; - return "variable-3"; - } else if (builtin.propertyIsEnumerable(cur)) { - if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement"; - return "builtin"; - } else { //Data types are of from KEYWORD## - var i = cur.length - 1; - while(i >= 0 && (!isNaN(cur[i]) || cur[i] == '_')) - --i; - - if (i > 0) { - var cur2 = cur.substr(0, i + 1); - if (variable_3.propertyIsEnumerable(cur2)) { - if (blockKeywords.propertyIsEnumerable(cur2)) curPunc = "newstatement"; - return "variable-3"; - } - } - } - if (atoms.propertyIsEnumerable(cur)) return "atom"; - return null; - } - - function tokenString(quote) { - return function(stream, state) { - var escaped = false, next, end = false; - while ((next = stream.next()) != null) { - if (next == quote && !escaped) {end = true; break;} - escaped = !escaped && next == "\\"; - } - if (end || !(escaped || multiLineStrings)) - state.tokenize = tokenBase; - return "string"; - }; - } - - function tokenComment(stream, state) { - var maybeEnd = false, ch; - while (ch = stream.next()) { - if (ch == "/" && maybeEnd) { - state.tokenize = tokenBase; - break; - } - maybeEnd = (ch == "*"); - } - return "comment"; - } - - function Context(indented, column, type, align, prev) { - this.indented = indented; - this.column = column; - this.type = type; - this.align = align; - this.prev = prev; - } - function pushContext(state, col, type) { - return state.context = new Context(state.indented, col, type, null, state.context); - } - function popContext(state) { - var t = state.context.type; - if (t == ")" || t == "]" || t == "}") - state.indented = state.context.indented; - return state.context = state.context.prev; - } - - // Interface - - return { - startState: function(basecolumn) { - return { - tokenize: null, - context: new Context((basecolumn || 0) - indentUnit, 0, "top", false), - indented: 0, - startOfLine: true - }; - }, - - token: function(stream, state) { - var ctx = state.context; - if (stream.sol()) { - if (ctx.align == null) ctx.align = false; - state.indented = stream.indentation(); - state.startOfLine = true; - } - if (stream.eatSpace()) return null; - curPunc = null; - var style = (state.tokenize || tokenBase)(stream, state); - if (style == "comment" || style == "meta") return style; - if (ctx.align == null) ctx.align = true; - - if ((curPunc == ";" || curPunc == ":") && ctx.type == "statement") popContext(state); - else if (curPunc == "{") pushContext(state, stream.column(), "}"); - else if (curPunc == "[") pushContext(state, stream.column(), "]"); - else if (curPunc == "(") pushContext(state, stream.column(), ")"); - else if (curPunc == "}") { - while (ctx.type == "statement") ctx = popContext(state); - if (ctx.type == "}") ctx = popContext(state); - while (ctx.type == "statement") ctx = popContext(state); - } - else if (curPunc == ctx.type) popContext(state); - else if (ctx.type == "}" || ctx.type == "top" || (ctx.type == "statement" && curPunc == "newstatement")) - pushContext(state, stream.column(), "statement"); - state.startOfLine = false; - return style; - }, - - indent: function(state, textAfter) { - if (state.tokenize != tokenBase && state.tokenize != null) return 0; - var ctx = state.context, firstChar = textAfter && textAfter.charAt(0); - if (ctx.type == "statement" && firstChar == "}") ctx = ctx.prev; - var closing = firstChar == ctx.type; - if (ctx.type == "statement") return ctx.indented + (firstChar == "{" ? 0 : indentUnit); - else if (ctx.align) return ctx.column + (closing ? 0 : 1); - else return ctx.indented + (closing ? 0 : indentUnit); - }, - - electricChars: "{}" - }; -}); - -CodeMirror.defineMIME("text/x-ecl", "ecl"); diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/ecl/index.html b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/ecl/index.html deleted file mode 100644 index d6b41f4e5f..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/ecl/index.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - CodeMirror: ECL mode - - - - - - - -

    CodeMirror: ECL mode

    -
    - - -

    Based on CodeMirror's clike mode. For more information see HPCC Systems web site.

    -

    MIME types defined: text/x-ecl.

    - - - diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/erlang/erlang.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/erlang/erlang.js deleted file mode 100644 index 1b095289aa..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/erlang/erlang.js +++ /dev/null @@ -1,463 +0,0 @@ -// block; "begin", "case", "fun", "if", "receive", "try": closed by "end" -// block internal; "after", "catch", "of" -// guard; "when", closed by "->" -// "->" opens a clause, closed by ";" or "." -// "<<" opens a binary, closed by ">>" -// "," appears in arglists, lists, tuples and terminates lines of code -// "." resets indentation to 0 -// obsolete; "cond", "let", "query" - -CodeMirror.defineMIME("text/x-erlang", "erlang"); - -CodeMirror.defineMode("erlang", function(cmCfg, modeCfg) { - - function rval(state,stream,type) { - // distinguish between "." as terminator and record field operator - if (type == "record") { - state.context = "record"; - }else{ - state.context = false; - } - - // remember last significant bit on last line for indenting - if (type != "whitespace" && type != "comment") { - state.lastToken = stream.current(); - } - // erlang -> CodeMirror tag - switch (type) { - case "atom": return "atom"; - case "attribute": return "attribute"; - case "builtin": return "builtin"; - case "comment": return "comment"; - case "fun": return "meta"; - case "function": return "tag"; - case "guard": return "property"; - case "keyword": return "keyword"; - case "macro": return "variable-2"; - case "number": return "number"; - case "operator": return "operator"; - case "record": return "bracket"; - case "string": return "string"; - case "type": return "def"; - case "variable": return "variable"; - case "error": return "error"; - case "separator": return null; - case "open_paren": return null; - case "close_paren": return null; - default: return null; - } - } - - var typeWords = [ - "-type", "-spec", "-export_type", "-opaque"]; - - var keywordWords = [ - "after","begin","catch","case","cond","end","fun","if", - "let","of","query","receive","try","when"]; - - var separatorWords = [ - "->",";",":",".",","]; - - var operatorWords = [ - "and","andalso","band","bnot","bor","bsl","bsr","bxor", - "div","not","or","orelse","rem","xor"]; - - var symbolWords = [ - "+","-","*","/",">",">=","<","=<","=:=","==","=/=","/=","||","<-"]; - - var openParenWords = [ - "<<","(","[","{"]; - - var closeParenWords = [ - "}","]",")",">>"]; - - var guardWords = [ - "is_atom","is_binary","is_bitstring","is_boolean","is_float", - "is_function","is_integer","is_list","is_number","is_pid", - "is_port","is_record","is_reference","is_tuple", - "atom","binary","bitstring","boolean","function","integer","list", - "number","pid","port","record","reference","tuple"]; - - var bifWords = [ - "abs","adler32","adler32_combine","alive","apply","atom_to_binary", - "atom_to_list","binary_to_atom","binary_to_existing_atom", - "binary_to_list","binary_to_term","bit_size","bitstring_to_list", - "byte_size","check_process_code","contact_binary","crc32", - "crc32_combine","date","decode_packet","delete_module", - "disconnect_node","element","erase","exit","float","float_to_list", - "garbage_collect","get","get_keys","group_leader","halt","hd", - "integer_to_list","internal_bif","iolist_size","iolist_to_binary", - "is_alive","is_atom","is_binary","is_bitstring","is_boolean", - "is_float","is_function","is_integer","is_list","is_number","is_pid", - "is_port","is_process_alive","is_record","is_reference","is_tuple", - "length","link","list_to_atom","list_to_binary","list_to_bitstring", - "list_to_existing_atom","list_to_float","list_to_integer", - "list_to_pid","list_to_tuple","load_module","make_ref","module_loaded", - "monitor_node","node","node_link","node_unlink","nodes","notalive", - "now","open_port","pid_to_list","port_close","port_command", - "port_connect","port_control","pre_loaded","process_flag", - "process_info","processes","purge_module","put","register", - "registered","round","self","setelement","size","spawn","spawn_link", - "spawn_monitor","spawn_opt","split_binary","statistics", - "term_to_binary","time","throw","tl","trunc","tuple_size", - "tuple_to_list","unlink","unregister","whereis"]; - - // ignored for indenting purposes - var ignoreWords = [ - ",", ":", "catch", "after", "of", "cond", "let", "query"]; - - - var smallRE = /[a-z_]/; - var largeRE = /[A-Z_]/; - var digitRE = /[0-9]/; - var octitRE = /[0-7]/; - var anumRE = /[a-z_A-Z0-9]/; - var symbolRE = /[\+\-\*\/<>=\|:]/; - var openParenRE = /[<\(\[\{]/; - var closeParenRE = /[>\)\]\}]/; - var sepRE = /[\->\.,:;]/; - - function isMember(element,list) { - return (-1 < list.indexOf(element)); - } - - function isPrev(stream,string) { - var start = stream.start; - var len = string.length; - if (len <= start) { - var word = stream.string.slice(start-len,start); - return word == string; - }else{ - return false; - } - } - - function tokenize(stream, state) { - if (stream.eatSpace()) { - return rval(state,stream,"whitespace"); - } - - // attributes and type specs - if ((peekToken(state).token == "" || peekToken(state).token == ".") && - stream.peek() == '-') { - stream.next(); - if (stream.eat(smallRE) && stream.eatWhile(anumRE)) { - if (isMember(stream.current(),typeWords)) { - return rval(state,stream,"type"); - }else{ - return rval(state,stream,"attribute"); - } - } - stream.backUp(1); - } - - var ch = stream.next(); - - // comment - if (ch == '%') { - stream.skipToEnd(); - return rval(state,stream,"comment"); - } - - // macro - if (ch == '?') { - stream.eatWhile(anumRE); - return rval(state,stream,"macro"); - } - - // record - if ( ch == "#") { - stream.eatWhile(anumRE); - return rval(state,stream,"record"); - } - - // char - if ( ch == "$") { - if (stream.next() == "\\") { - if (!stream.eatWhile(octitRE)) { - stream.next(); - } - } - return rval(state,stream,"string"); - } - - // quoted atom - if (ch == '\'') { - if (singleQuote(stream)) { - return rval(state,stream,"atom"); - }else{ - return rval(state,stream,"error"); - } - } - - // string - if (ch == '"') { - if (doubleQuote(stream)) { - return rval(state,stream,"string"); - }else{ - return rval(state,stream,"error"); - } - } - - // variable - if (largeRE.test(ch)) { - stream.eatWhile(anumRE); - return rval(state,stream,"variable"); - } - - // atom/keyword/BIF/function - if (smallRE.test(ch)) { - stream.eatWhile(anumRE); - - if (stream.peek() == "/") { - stream.next(); - if (stream.eatWhile(digitRE)) { - return rval(state,stream,"fun"); // f/0 style fun - }else{ - stream.backUp(1); - return rval(state,stream,"atom"); - } - } - - var w = stream.current(); - - if (isMember(w,keywordWords)) { - pushToken(state,stream); - return rval(state,stream,"keyword"); - } - if (stream.peek() == "(") { - // 'put' and 'erlang:put' are bifs, 'foo:put' is not - if (isMember(w,bifWords) && - (!isPrev(stream,":") || isPrev(stream,"erlang:"))) { - return rval(state,stream,"builtin"); - }else{ - return rval(state,stream,"function"); - } - } - if (isMember(w,guardWords)) { - return rval(state,stream,"guard"); - } - if (isMember(w,operatorWords)) { - return rval(state,stream,"operator"); - } - if (stream.peek() == ":") { - if (w == "erlang") { - return rval(state,stream,"builtin"); - } else { - return rval(state,stream,"function"); - } - } - return rval(state,stream,"atom"); - } - - // number - if (digitRE.test(ch)) { - stream.eatWhile(digitRE); - if (stream.eat('#')) { - stream.eatWhile(digitRE); // 16#10 style integer - } else { - if (stream.eat('.')) { // float - stream.eatWhile(digitRE); - } - if (stream.eat(/[eE]/)) { - stream.eat(/[-+]/); // float with exponent - stream.eatWhile(digitRE); - } - } - return rval(state,stream,"number"); // normal integer - } - - // open parens - if (nongreedy(stream,openParenRE,openParenWords)) { - pushToken(state,stream); - return rval(state,stream,"open_paren"); - } - - // close parens - if (nongreedy(stream,closeParenRE,closeParenWords)) { - pushToken(state,stream); - return rval(state,stream,"close_paren"); - } - - // separators - if (greedy(stream,sepRE,separatorWords)) { - // distinguish between "." as terminator and record field operator - if (state.context == false) { - pushToken(state,stream); - } - return rval(state,stream,"separator"); - } - - // operators - if (greedy(stream,symbolRE,symbolWords)) { - return rval(state,stream,"operator"); - } - - return rval(state,stream,null); - } - - function nongreedy(stream,re,words) { - if (stream.current().length == 1 && re.test(stream.current())) { - stream.backUp(1); - while (re.test(stream.peek())) { - stream.next(); - if (isMember(stream.current(),words)) { - return true; - } - } - stream.backUp(stream.current().length-1); - } - return false; - } - - function greedy(stream,re,words) { - if (stream.current().length == 1 && re.test(stream.current())) { - while (re.test(stream.peek())) { - stream.next(); - } - while (0 < stream.current().length) { - if (isMember(stream.current(),words)) { - return true; - }else{ - stream.backUp(1); - } - } - stream.next(); - } - return false; - } - - function doubleQuote(stream) { - return quote(stream, '"', '\\'); - } - - function singleQuote(stream) { - return quote(stream,'\'','\\'); - } - - function quote(stream,quoteChar,escapeChar) { - while (!stream.eol()) { - var ch = stream.next(); - if (ch == quoteChar) { - return true; - }else if (ch == escapeChar) { - stream.next(); - } - } - return false; - } - - function Token(stream) { - this.token = stream ? stream.current() : ""; - this.column = stream ? stream.column() : 0; - this.indent = stream ? stream.indentation() : 0; - } - - function myIndent(state,textAfter) { - var indent = cmCfg.indentUnit; - var outdentWords = ["after","catch"]; - var token = (peekToken(state)).token; - var wordAfter = takewhile(textAfter,/[^a-z]/); - - if (isMember(token,openParenWords)) { - return (peekToken(state)).column+token.length; - }else if (token == "." || token == ""){ - return 0; - }else if (token == "->") { - if (wordAfter == "end") { - return peekToken(state,2).column; - }else if (peekToken(state,2).token == "fun") { - return peekToken(state,2).column+indent; - }else{ - return (peekToken(state)).indent+indent; - } - }else if (isMember(wordAfter,outdentWords)) { - return (peekToken(state)).indent; - }else{ - return (peekToken(state)).column+indent; - } - } - - function takewhile(str,re) { - var m = str.match(re); - return m ? str.slice(0,m.index) : str; - } - - function popToken(state) { - return state.tokenStack.pop(); - } - - function peekToken(state,depth) { - var len = state.tokenStack.length; - var dep = (depth ? depth : 1); - if (len < dep) { - return new Token; - }else{ - return state.tokenStack[len-dep]; - } - } - - function pushToken(state,stream) { - var token = stream.current(); - var prev_token = peekToken(state).token; - if (isMember(token,ignoreWords)) { - return false; - }else if (drop_both(prev_token,token)) { - popToken(state); - return false; - }else if (drop_first(prev_token,token)) { - popToken(state); - return pushToken(state,stream); - }else{ - state.tokenStack.push(new Token(stream)); - return true; - } - } - - function drop_first(open, close) { - switch (open+" "+close) { - case "when ->": return true; - case "-> end": return true; - case "-> .": return true; - case ". .": return true; - default: return false; - } - } - - function drop_both(open, close) { - switch (open+" "+close) { - case "( )": return true; - case "[ ]": return true; - case "{ }": return true; - case "<< >>": return true; - case "begin end": return true; - case "case end": return true; - case "fun end": return true; - case "if end": return true; - case "receive end": return true; - case "try end": return true; - case "-> ;": return true; - default: return false; - } - } - - return { - startState: - function() { - return {tokenStack: [], - context: false, - lastToken: null}; - }, - - token: - function(stream, state) { - return tokenize(stream, state); - }, - - indent: - function(state, textAfter) { -// console.log(state.tokenStack); - return myIndent(state,textAfter); - } - }; -}); diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/erlang/index.html b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/erlang/index.html deleted file mode 100644 index 8694c3d95b..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/erlang/index.html +++ /dev/null @@ -1,63 +0,0 @@ - - - - - CodeMirror: Erlang mode - - - - - - - - -

    CodeMirror: Erlang mode

    - -
    - - - -

    MIME types defined: text/x-erlang.

    - - diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/gfm/gfm.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/gfm/gfm.js deleted file mode 100644 index dedee8849f..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/gfm/gfm.js +++ /dev/null @@ -1,150 +0,0 @@ -CodeMirror.defineMode("gfm", function(config, parserConfig) { - var mdMode = CodeMirror.getMode(config, "markdown"); - var aliases = { - html: "htmlmixed", - js: "javascript", - json: "application/json", - c: "text/x-csrc", - "c++": "text/x-c++src", - java: "text/x-java", - csharp: "text/x-csharp", - "c#": "text/x-csharp" - }; - - // make this lazy so that we don't need to load GFM last - var getMode = (function () { - var i, modes = {}, mimes = {}, mime; - - var list = CodeMirror.listModes(); - for (i = 0; i < list.length; i++) { - modes[list[i]] = list[i]; - } - var mimesList = CodeMirror.listMIMEs(); - for (i = 0; i < mimesList.length; i++) { - mime = mimesList[i].mime; - mimes[mime] = mimesList[i].mime; - } - - for (var a in aliases) { - if (aliases[a] in modes || aliases[a] in mimes) - modes[a] = aliases[a]; - } - - return function (lang) { - return modes[lang] ? CodeMirror.getMode(config, modes[lang]) : null; - }; - }()); - - function markdown(stream, state) { - // intercept fenced code blocks - if (stream.sol() && stream.match(/^```([\w+#]*)/)) { - // try switching mode - state.localMode = getMode(RegExp.$1); - if (state.localMode) - state.localState = state.localMode.startState(); - - state.token = local; - return 'code'; - } - - return mdMode.token(stream, state.mdState); - } - - function local(stream, state) { - if (stream.sol() && stream.match(/^```/)) { - state.localMode = state.localState = null; - state.token = markdown; - return 'code'; - } - else if (state.localMode) { - return state.localMode.token(stream, state.localState); - } else { - stream.skipToEnd(); - return 'code'; - } - } - - // custom handleText to prevent emphasis in the middle of a word - // and add autolinking - function handleText(stream, mdState) { - var match; - if (stream.match(/^\w+:\/\/\S+/)) { - return 'link'; - } - if (stream.match(/^[^\[*\\<>` _][^\[*\\<>` ]*[^\[*\\<>` _]/)) { - return mdMode.getType(mdState); - } - if (match = stream.match(/^[^\[*\\<>` ]+/)) { - var word = match[0]; - if (word[0] === '_' && word[word.length-1] === '_') { - stream.backUp(word.length); - return undefined; - } - return mdMode.getType(mdState); - } - if (stream.eatSpace()) { - return null; - } - } - - return { - startState: function() { - var mdState = mdMode.startState(); - mdState.text = handleText; - return {token: markdown, mode: "markdown", mdState: mdState, - localMode: null, localState: null}; - }, - - copyState: function(state) { - return {token: state.token, mdState: CodeMirror.copyState(mdMode, state.mdState), - localMode: state.localMode, - localState: state.localMode ? CodeMirror.copyState(state.localMode, state.localState) : null}; - }, - - token: function(stream, state) { - /* Parse GFM double bracket links */ - var ch; - if ((ch = stream.peek()) != undefined && ch == '[') { - stream.next(); // Advance the stream - - /* Only handle double bracket links */ - if ((ch = stream.peek()) == undefined || ch != '[') { - stream.backUp(1); - return state.token(stream, state); - } - - while ((ch = stream.next()) != undefined && ch != ']') {} - - if (ch == ']' && (ch = stream.next()) != undefined && ch == ']') - return 'link'; - - /* If we did not find the second ']' */ - stream.backUp(1); - } - - /* Match GFM latex formulas, as well as latex formulas within '$' */ - if (stream.match(/^\$[^\$]+\$/)) { - return "string"; - } - - if (stream.match(/^\\\((.*?)\\\)/)) { - return "string"; - } - - if (stream.match(/^\$\$[^\$]+\$\$/)) { - return "string"; - } - - if (stream.match(/^\\\[(.*?)\\\]/)) { - return "string"; - } - - return state.token(stream, state); - }, - - innerMode: function(state) { - if (state.token == markdown) return {state: state.mdState, mode: mdMode}; - else return {state: state.localState, mode: state.localMode}; - } - }; -}, "markdown"); diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/gfm/index.html b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/gfm/index.html deleted file mode 100644 index c99e6a44ff..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/gfm/index.html +++ /dev/null @@ -1,48 +0,0 @@ - - - - - CodeMirror: GFM mode - - - - - - - - - - - -

    CodeMirror: GFM mode

    - - -
    - - - - - diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/go/go.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/go/go.js deleted file mode 100644 index dd7e3ce104..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/go/go.js +++ /dev/null @@ -1,170 +0,0 @@ -CodeMirror.defineMode("go", function(config, parserConfig) { - var indentUnit = config.indentUnit; - - var keywords = { - "break":true, "case":true, "chan":true, "const":true, "continue":true, - "default":true, "defer":true, "else":true, "fallthrough":true, "for":true, - "func":true, "go":true, "goto":true, "if":true, "import":true, - "interface":true, "map":true, "package":true, "range":true, "return":true, - "select":true, "struct":true, "switch":true, "type":true, "var":true, - "bool":true, "byte":true, "complex64":true, "complex128":true, - "float32":true, "float64":true, "int8":true, "int16":true, "int32":true, - "int64":true, "string":true, "uint8":true, "uint16":true, "uint32":true, - "uint64":true, "int":true, "uint":true, "uintptr":true - }; - - var atoms = { - "true":true, "false":true, "iota":true, "nil":true, "append":true, - "cap":true, "close":true, "complex":true, "copy":true, "imag":true, - "len":true, "make":true, "new":true, "panic":true, "print":true, - "println":true, "real":true, "recover":true - }; - - var blockKeywords = { - "else":true, "for":true, "func":true, "if":true, "interface":true, - "select":true, "struct":true, "switch":true - }; - - var isOperatorChar = /[+\-*&^%:=<>!|\/]/; - - var curPunc; - - function tokenBase(stream, state) { - var ch = stream.next(); - if (ch == '"' || ch == "'" || ch == "`") { - state.tokenize = tokenString(ch); - return state.tokenize(stream, state); - } - if (/[\d\.]/.test(ch)) { - if (ch == ".") { - stream.match(/^[0-9]+([eE][\-+]?[0-9]+)?/); - } else if (ch == "0") { - stream.match(/^[xX][0-9a-fA-F]+/) || stream.match(/^0[0-7]+/); - } else { - stream.match(/^[0-9]*\.?[0-9]*([eE][\-+]?[0-9]+)?/); - } - return "number"; - } - if (/[\[\]{}\(\),;\:\.]/.test(ch)) { - curPunc = ch; - return null; - } - if (ch == "/") { - if (stream.eat("*")) { - state.tokenize = tokenComment; - return tokenComment(stream, state); - } - if (stream.eat("/")) { - stream.skipToEnd(); - return "comment"; - } - } - if (isOperatorChar.test(ch)) { - stream.eatWhile(isOperatorChar); - return "operator"; - } - stream.eatWhile(/[\w\$_]/); - var cur = stream.current(); - if (keywords.propertyIsEnumerable(cur)) { - if (cur == "case" || cur == "default") curPunc = "case"; - return "keyword"; - } - if (atoms.propertyIsEnumerable(cur)) return "atom"; - return "variable"; - } - - function tokenString(quote) { - return function(stream, state) { - var escaped = false, next, end = false; - while ((next = stream.next()) != null) { - if (next == quote && !escaped) {end = true; break;} - escaped = !escaped && next == "\\"; - } - if (end || !(escaped || quote == "`")) - state.tokenize = tokenBase; - return "string"; - }; - } - - function tokenComment(stream, state) { - var maybeEnd = false, ch; - while (ch = stream.next()) { - if (ch == "/" && maybeEnd) { - state.tokenize = tokenBase; - break; - } - maybeEnd = (ch == "*"); - } - return "comment"; - } - - function Context(indented, column, type, align, prev) { - this.indented = indented; - this.column = column; - this.type = type; - this.align = align; - this.prev = prev; - } - function pushContext(state, col, type) { - return state.context = new Context(state.indented, col, type, null, state.context); - } - function popContext(state) { - var t = state.context.type; - if (t == ")" || t == "]" || t == "}") - state.indented = state.context.indented; - return state.context = state.context.prev; - } - - // Interface - - return { - startState: function(basecolumn) { - return { - tokenize: null, - context: new Context((basecolumn || 0) - indentUnit, 0, "top", false), - indented: 0, - startOfLine: true - }; - }, - - token: function(stream, state) { - var ctx = state.context; - if (stream.sol()) { - if (ctx.align == null) ctx.align = false; - state.indented = stream.indentation(); - state.startOfLine = true; - if (ctx.type == "case") ctx.type = "}"; - } - if (stream.eatSpace()) return null; - curPunc = null; - var style = (state.tokenize || tokenBase)(stream, state); - if (style == "comment") return style; - if (ctx.align == null) ctx.align = true; - - if (curPunc == "{") pushContext(state, stream.column(), "}"); - else if (curPunc == "[") pushContext(state, stream.column(), "]"); - else if (curPunc == "(") pushContext(state, stream.column(), ")"); - else if (curPunc == "case") ctx.type = "case"; - else if (curPunc == "}" && ctx.type == "}") ctx = popContext(state); - else if (curPunc == ctx.type) popContext(state); - state.startOfLine = false; - return style; - }, - - indent: function(state, textAfter) { - if (state.tokenize != tokenBase && state.tokenize != null) return 0; - var ctx = state.context, firstChar = textAfter && textAfter.charAt(0); - if (ctx.type == "case" && /^(?:case|default)\b/.test(textAfter)) { - state.context.type = "}"; - return ctx.indented; - } - var closing = firstChar == ctx.type; - if (ctx.align) return ctx.column + (closing ? 0 : 1); - else return ctx.indented + (closing ? 0 : indentUnit); - }, - - electricChars: "{}:" - }; -}); - -CodeMirror.defineMIME("text/x-go", "go"); diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/go/index.html b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/go/index.html deleted file mode 100644 index d723baf7dd..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/go/index.html +++ /dev/null @@ -1,73 +0,0 @@ - - - - - CodeMirror: Go mode - - - - - - - - -

    CodeMirror: Go mode

    - -
    - - - -

    MIME type: text/x-go

    - - diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/groovy/groovy.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/groovy/groovy.js deleted file mode 100644 index 017ab5b192..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/groovy/groovy.js +++ /dev/null @@ -1,210 +0,0 @@ -CodeMirror.defineMode("groovy", function(config, parserConfig) { - function words(str) { - var obj = {}, words = str.split(" "); - for (var i = 0; i < words.length; ++i) obj[words[i]] = true; - return obj; - } - var keywords = words( - "abstract as assert boolean break byte case catch char class const continue def default " + - "do double else enum extends final finally float for goto if implements import in " + - "instanceof int interface long native new package private protected public return " + - "short static strictfp super switch synchronized threadsafe throw throws transient " + - "try void volatile while"); - var blockKeywords = words("catch class do else finally for if switch try while enum interface def"); - var atoms = words("null true false this"); - - var curPunc; - function tokenBase(stream, state) { - var ch = stream.next(); - if (ch == '"' || ch == "'") { - return startString(ch, stream, state); - } - if (/[\[\]{}\(\),;\:\.]/.test(ch)) { - curPunc = ch; - return null; - } - if (/\d/.test(ch)) { - stream.eatWhile(/[\w\.]/); - if (stream.eat(/eE/)) { stream.eat(/\+\-/); stream.eatWhile(/\d/); } - return "number"; - } - if (ch == "/") { - if (stream.eat("*")) { - state.tokenize.push(tokenComment); - return tokenComment(stream, state); - } - if (stream.eat("/")) { - stream.skipToEnd(); - return "comment"; - } - if (expectExpression(state.lastToken)) { - return startString(ch, stream, state); - } - } - if (ch == "-" && stream.eat(">")) { - curPunc = "->"; - return null; - } - if (/[+\-*&%=<>!?|\/~]/.test(ch)) { - stream.eatWhile(/[+\-*&%=<>|~]/); - return "operator"; - } - stream.eatWhile(/[\w\$_]/); - if (ch == "@") { stream.eatWhile(/[\w\$_\.]/); return "meta"; } - if (state.lastToken == ".") return "property"; - if (stream.eat(":")) { curPunc = "proplabel"; return "property"; } - var cur = stream.current(); - if (atoms.propertyIsEnumerable(cur)) { return "atom"; } - if (keywords.propertyIsEnumerable(cur)) { - if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement"; - return "keyword"; - } - return "variable"; - } - tokenBase.isBase = true; - - function startString(quote, stream, state) { - var tripleQuoted = false; - if (quote != "/" && stream.eat(quote)) { - if (stream.eat(quote)) tripleQuoted = true; - else return "string"; - } - function t(stream, state) { - var escaped = false, next, end = !tripleQuoted; - while ((next = stream.next()) != null) { - if (next == quote && !escaped) { - if (!tripleQuoted) { break; } - if (stream.match(quote + quote)) { end = true; break; } - } - if (quote == '"' && next == "$" && !escaped && stream.eat("{")) { - state.tokenize.push(tokenBaseUntilBrace()); - return "string"; - } - escaped = !escaped && next == "\\"; - } - if (end) state.tokenize.pop(); - return "string"; - } - state.tokenize.push(t); - return t(stream, state); - } - - function tokenBaseUntilBrace() { - var depth = 1; - function t(stream, state) { - if (stream.peek() == "}") { - depth--; - if (depth == 0) { - state.tokenize.pop(); - return state.tokenize[state.tokenize.length-1](stream, state); - } - } else if (stream.peek() == "{") { - depth++; - } - return tokenBase(stream, state); - } - t.isBase = true; - return t; - } - - function tokenComment(stream, state) { - var maybeEnd = false, ch; - while (ch = stream.next()) { - if (ch == "/" && maybeEnd) { - state.tokenize.pop(); - break; - } - maybeEnd = (ch == "*"); - } - return "comment"; - } - - function expectExpression(last) { - return !last || last == "operator" || last == "->" || /[\.\[\{\(,;:]/.test(last) || - last == "newstatement" || last == "keyword" || last == "proplabel"; - } - - function Context(indented, column, type, align, prev) { - this.indented = indented; - this.column = column; - this.type = type; - this.align = align; - this.prev = prev; - } - function pushContext(state, col, type) { - return state.context = new Context(state.indented, col, type, null, state.context); - } - function popContext(state) { - var t = state.context.type; - if (t == ")" || t == "]" || t == "}") - state.indented = state.context.indented; - return state.context = state.context.prev; - } - - // Interface - - return { - startState: function(basecolumn) { - return { - tokenize: [tokenBase], - context: new Context((basecolumn || 0) - config.indentUnit, 0, "top", false), - indented: 0, - startOfLine: true, - lastToken: null - }; - }, - - token: function(stream, state) { - var ctx = state.context; - if (stream.sol()) { - if (ctx.align == null) ctx.align = false; - state.indented = stream.indentation(); - state.startOfLine = true; - // Automatic semicolon insertion - if (ctx.type == "statement" && !expectExpression(state.lastToken)) { - popContext(state); ctx = state.context; - } - } - if (stream.eatSpace()) return null; - curPunc = null; - var style = state.tokenize[state.tokenize.length-1](stream, state); - if (style == "comment") return style; - if (ctx.align == null) ctx.align = true; - - if ((curPunc == ";" || curPunc == ":") && ctx.type == "statement") popContext(state); - // Handle indentation for {x -> \n ... } - else if (curPunc == "->" && ctx.type == "statement" && ctx.prev.type == "}") { - popContext(state); - state.context.align = false; - } - else if (curPunc == "{") pushContext(state, stream.column(), "}"); - else if (curPunc == "[") pushContext(state, stream.column(), "]"); - else if (curPunc == "(") pushContext(state, stream.column(), ")"); - else if (curPunc == "}") { - while (ctx.type == "statement") ctx = popContext(state); - if (ctx.type == "}") ctx = popContext(state); - while (ctx.type == "statement") ctx = popContext(state); - } - else if (curPunc == ctx.type) popContext(state); - else if (ctx.type == "}" || ctx.type == "top" || (ctx.type == "statement" && curPunc == "newstatement")) - pushContext(state, stream.column(), "statement"); - state.startOfLine = false; - state.lastToken = curPunc || style; - return style; - }, - - indent: function(state, textAfter) { - if (!state.tokenize[state.tokenize.length-1].isBase) return 0; - var firstChar = textAfter && textAfter.charAt(0), ctx = state.context; - if (ctx.type == "statement" && !expectExpression(state.lastToken)) ctx = ctx.prev; - var closing = firstChar == ctx.type; - if (ctx.type == "statement") return ctx.indented + (firstChar == "{" ? 0 : config.indentUnit); - else if (ctx.align) return ctx.column + (closing ? 0 : 1); - else return ctx.indented + (closing ? 0 : config.indentUnit); - }, - - electricChars: "{}" - }; -}); - -CodeMirror.defineMIME("text/x-groovy", "groovy"); diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/groovy/index.html b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/groovy/index.html deleted file mode 100644 index 9208cfa734..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/groovy/index.html +++ /dev/null @@ -1,72 +0,0 @@ - - - - - CodeMirror: Groovy mode - - - - - - - -

    CodeMirror: Groovy mode

    - -
    - - - -

    MIME types defined: text/x-groovy

    - - diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/haskell/haskell.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/haskell/haskell.js deleted file mode 100644 index ee893c9040..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/haskell/haskell.js +++ /dev/null @@ -1,242 +0,0 @@ -CodeMirror.defineMode("haskell", function(cmCfg, modeCfg) { - - function switchState(source, setState, f) { - setState(f); - return f(source, setState); - } - - // These should all be Unicode extended, as per the Haskell 2010 report - var smallRE = /[a-z_]/; - var largeRE = /[A-Z]/; - var digitRE = /[0-9]/; - var hexitRE = /[0-9A-Fa-f]/; - var octitRE = /[0-7]/; - var idRE = /[a-z_A-Z0-9']/; - var symbolRE = /[-!#$%&*+.\/<=>?@\\^|~:]/; - var specialRE = /[(),;[\]`{}]/; - var whiteCharRE = /[ \t\v\f]/; // newlines are handled in tokenizer - - function normal(source, setState) { - if (source.eatWhile(whiteCharRE)) { - return null; - } - - var ch = source.next(); - if (specialRE.test(ch)) { - if (ch == '{' && source.eat('-')) { - var t = "comment"; - if (source.eat('#')) { - t = "meta"; - } - return switchState(source, setState, ncomment(t, 1)); - } - return null; - } - - if (ch == '\'') { - if (source.eat('\\')) { - source.next(); // should handle other escapes here - } - else { - source.next(); - } - if (source.eat('\'')) { - return "string"; - } - return "error"; - } - - if (ch == '"') { - return switchState(source, setState, stringLiteral); - } - - if (largeRE.test(ch)) { - source.eatWhile(idRE); - if (source.eat('.')) { - return "qualifier"; - } - return "variable-2"; - } - - if (smallRE.test(ch)) { - source.eatWhile(idRE); - return "variable"; - } - - if (digitRE.test(ch)) { - if (ch == '0') { - if (source.eat(/[xX]/)) { - source.eatWhile(hexitRE); // should require at least 1 - return "integer"; - } - if (source.eat(/[oO]/)) { - source.eatWhile(octitRE); // should require at least 1 - return "number"; - } - } - source.eatWhile(digitRE); - var t = "number"; - if (source.eat('.')) { - t = "number"; - source.eatWhile(digitRE); // should require at least 1 - } - if (source.eat(/[eE]/)) { - t = "number"; - source.eat(/[-+]/); - source.eatWhile(digitRE); // should require at least 1 - } - return t; - } - - if (symbolRE.test(ch)) { - if (ch == '-' && source.eat(/-/)) { - source.eatWhile(/-/); - if (!source.eat(symbolRE)) { - source.skipToEnd(); - return "comment"; - } - } - var t = "variable"; - if (ch == ':') { - t = "variable-2"; - } - source.eatWhile(symbolRE); - return t; - } - - return "error"; - } - - function ncomment(type, nest) { - if (nest == 0) { - return normal; - } - return function(source, setState) { - var currNest = nest; - while (!source.eol()) { - var ch = source.next(); - if (ch == '{' && source.eat('-')) { - ++currNest; - } - else if (ch == '-' && source.eat('}')) { - --currNest; - if (currNest == 0) { - setState(normal); - return type; - } - } - } - setState(ncomment(type, currNest)); - return type; - }; - } - - function stringLiteral(source, setState) { - while (!source.eol()) { - var ch = source.next(); - if (ch == '"') { - setState(normal); - return "string"; - } - if (ch == '\\') { - if (source.eol() || source.eat(whiteCharRE)) { - setState(stringGap); - return "string"; - } - if (source.eat('&')) { - } - else { - source.next(); // should handle other escapes here - } - } - } - setState(normal); - return "error"; - } - - function stringGap(source, setState) { - if (source.eat('\\')) { - return switchState(source, setState, stringLiteral); - } - source.next(); - setState(normal); - return "error"; - } - - - var wellKnownWords = (function() { - var wkw = {}; - function setType(t) { - return function () { - for (var i = 0; i < arguments.length; i++) - wkw[arguments[i]] = t; - }; - } - - setType("keyword")( - "case", "class", "data", "default", "deriving", "do", "else", "foreign", - "if", "import", "in", "infix", "infixl", "infixr", "instance", "let", - "module", "newtype", "of", "then", "type", "where", "_"); - - setType("keyword")( - "\.\.", ":", "::", "=", "\\", "\"", "<-", "->", "@", "~", "=>"); - - setType("builtin")( - "!!", "$!", "$", "&&", "+", "++", "-", ".", "/", "/=", "<", "<=", "=<<", - "==", ">", ">=", ">>", ">>=", "^", "^^", "||", "*", "**"); - - setType("builtin")( - "Bool", "Bounded", "Char", "Double", "EQ", "Either", "Enum", "Eq", - "False", "FilePath", "Float", "Floating", "Fractional", "Functor", "GT", - "IO", "IOError", "Int", "Integer", "Integral", "Just", "LT", "Left", - "Maybe", "Monad", "Nothing", "Num", "Ord", "Ordering", "Rational", "Read", - "ReadS", "Real", "RealFloat", "RealFrac", "Right", "Show", "ShowS", - "String", "True"); - - setType("builtin")( - "abs", "acos", "acosh", "all", "and", "any", "appendFile", "asTypeOf", - "asin", "asinh", "atan", "atan2", "atanh", "break", "catch", "ceiling", - "compare", "concat", "concatMap", "const", "cos", "cosh", "curry", - "cycle", "decodeFloat", "div", "divMod", "drop", "dropWhile", "either", - "elem", "encodeFloat", "enumFrom", "enumFromThen", "enumFromThenTo", - "enumFromTo", "error", "even", "exp", "exponent", "fail", "filter", - "flip", "floatDigits", "floatRadix", "floatRange", "floor", "fmap", - "foldl", "foldl1", "foldr", "foldr1", "fromEnum", "fromInteger", - "fromIntegral", "fromRational", "fst", "gcd", "getChar", "getContents", - "getLine", "head", "id", "init", "interact", "ioError", "isDenormalized", - "isIEEE", "isInfinite", "isNaN", "isNegativeZero", "iterate", "last", - "lcm", "length", "lex", "lines", "log", "logBase", "lookup", "map", - "mapM", "mapM_", "max", "maxBound", "maximum", "maybe", "min", "minBound", - "minimum", "mod", "negate", "not", "notElem", "null", "odd", "or", - "otherwise", "pi", "pred", "print", "product", "properFraction", - "putChar", "putStr", "putStrLn", "quot", "quotRem", "read", "readFile", - "readIO", "readList", "readLn", "readParen", "reads", "readsPrec", - "realToFrac", "recip", "rem", "repeat", "replicate", "return", "reverse", - "round", "scaleFloat", "scanl", "scanl1", "scanr", "scanr1", "seq", - "sequence", "sequence_", "show", "showChar", "showList", "showParen", - "showString", "shows", "showsPrec", "significand", "signum", "sin", - "sinh", "snd", "span", "splitAt", "sqrt", "subtract", "succ", "sum", - "tail", "take", "takeWhile", "tan", "tanh", "toEnum", "toInteger", - "toRational", "truncate", "uncurry", "undefined", "unlines", "until", - "unwords", "unzip", "unzip3", "userError", "words", "writeFile", "zip", - "zip3", "zipWith", "zipWith3"); - - return wkw; - })(); - - - - return { - startState: function () { return { f: normal }; }, - copyState: function (s) { return { f: s.f }; }, - - token: function(stream, state) { - var t = state.f(stream, function(s) { state.f = s; }); - var w = stream.current(); - return (w in wellKnownWords) ? wellKnownWords[w] : t; - } - }; - -}); - -CodeMirror.defineMIME("text/x-haskell", "haskell"); diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/haskell/index.html b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/haskell/index.html deleted file mode 100644 index e5292de78d..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/haskell/index.html +++ /dev/null @@ -1,61 +0,0 @@ - - - - - CodeMirror: Haskell mode - - - - - - - - -

    CodeMirror: Haskell mode

    - -
    - - - -

    MIME types defined: text/x-haskell.

    - - diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/haxe/haxe.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/haxe/haxe.js deleted file mode 100644 index 3513dcfe1d..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/haxe/haxe.js +++ /dev/null @@ -1,429 +0,0 @@ -CodeMirror.defineMode("haxe", function(config, parserConfig) { - var indentUnit = config.indentUnit; - - // Tokenizer - - var keywords = function(){ - function kw(type) {return {type: type, style: "keyword"};} - var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c"); - var operator = kw("operator"), atom = {type: "atom", style: "atom"}, attribute = {type:"attribute", style: "attribute"}; - var type = kw("typedef"); - return { - "if": A, "while": A, "else": B, "do": B, "try": B, - "return": C, "break": C, "continue": C, "new": C, "throw": C, - "var": kw("var"), "inline":attribute, "static": attribute, "using":kw("import"), - "public": attribute, "private": attribute, "cast": kw("cast"), "import": kw("import"), "macro": kw("macro"), - "function": kw("function"), "catch": kw("catch"), "untyped": kw("untyped"), "callback": kw("cb"), - "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"), - "in": operator, "never": kw("property_access"), "trace":kw("trace"), - "class": type, "enum":type, "interface":type, "typedef":type, "extends":type, "implements":type, "dynamic":type, - "true": atom, "false": atom, "null": atom - }; - }(); - - var isOperatorChar = /[+\-*&%=<>!?|]/; - - function chain(stream, state, f) { - state.tokenize = f; - return f(stream, state); - } - - function nextUntilUnescaped(stream, end) { - var escaped = false, next; - while ((next = stream.next()) != null) { - if (next == end && !escaped) - return false; - escaped = !escaped && next == "\\"; - } - return escaped; - } - - // Used as scratch variables to communicate multiple values without - // consing up tons of objects. - var type, content; - function ret(tp, style, cont) { - type = tp; content = cont; - return style; - } - - function haxeTokenBase(stream, state) { - var ch = stream.next(); - if (ch == '"' || ch == "'") - return chain(stream, state, haxeTokenString(ch)); - else if (/[\[\]{}\(\),;\:\.]/.test(ch)) - return ret(ch); - else if (ch == "0" && stream.eat(/x/i)) { - stream.eatWhile(/[\da-f]/i); - return ret("number", "number"); - } - else if (/\d/.test(ch) || ch == "-" && stream.eat(/\d/)) { - stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/); - return ret("number", "number"); - } - else if (state.reAllowed && (ch == "~" && stream.eat(/\//))) { - nextUntilUnescaped(stream, "/"); - stream.eatWhile(/[gimsu]/); - return ret("regexp", "string-2"); - } - else if (ch == "/") { - if (stream.eat("*")) { - return chain(stream, state, haxeTokenComment); - } - else if (stream.eat("/")) { - stream.skipToEnd(); - return ret("comment", "comment"); - } - else { - stream.eatWhile(isOperatorChar); - return ret("operator", null, stream.current()); - } - } - else if (ch == "#") { - stream.skipToEnd(); - return ret("conditional", "meta"); - } - else if (ch == "@") { - stream.eat(/:/); - stream.eatWhile(/[\w_]/); - return ret ("metadata", "meta"); - } - else if (isOperatorChar.test(ch)) { - stream.eatWhile(isOperatorChar); - return ret("operator", null, stream.current()); - } - else { - var word; - if(/[A-Z]/.test(ch)) - { - stream.eatWhile(/[\w_<>]/); - word = stream.current(); - return ret("type", "variable-3", word); - } - else - { - stream.eatWhile(/[\w_]/); - var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word]; - return (known && state.kwAllowed) ? ret(known.type, known.style, word) : - ret("variable", "variable", word); - } - } - } - - function haxeTokenString(quote) { - return function(stream, state) { - if (!nextUntilUnescaped(stream, quote)) - state.tokenize = haxeTokenBase; - return ret("string", "string"); - }; - } - - function haxeTokenComment(stream, state) { - var maybeEnd = false, ch; - while (ch = stream.next()) { - if (ch == "/" && maybeEnd) { - state.tokenize = haxeTokenBase; - break; - } - maybeEnd = (ch == "*"); - } - return ret("comment", "comment"); - } - - // Parser - - var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true}; - - function HaxeLexical(indented, column, type, align, prev, info) { - this.indented = indented; - this.column = column; - this.type = type; - this.prev = prev; - this.info = info; - if (align != null) this.align = align; - } - - function inScope(state, varname) { - for (var v = state.localVars; v; v = v.next) - if (v.name == varname) return true; - } - - function parseHaxe(state, style, type, content, stream) { - var cc = state.cc; - // Communicate our context to the combinators. - // (Less wasteful than consing up a hundred closures on every call.) - cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc; - - if (!state.lexical.hasOwnProperty("align")) - state.lexical.align = true; - - while(true) { - var combinator = cc.length ? cc.pop() : statement; - if (combinator(type, content)) { - while(cc.length && cc[cc.length - 1].lex) - cc.pop()(); - if (cx.marked) return cx.marked; - if (type == "variable" && inScope(state, content)) return "variable-2"; - if (type == "variable" && imported(state, content)) return "variable-3"; - return style; - } - } - } - - function imported(state, typename) - { - if (/[a-z]/.test(typename.charAt(0))) - return false; - var len = state.importedtypes.length; - for (var i = 0; i= 0; i--) cx.cc.push(arguments[i]); - } - function cont() { - pass.apply(null, arguments); - return true; - } - function register(varname) { - var state = cx.state; - if (state.context) { - cx.marked = "def"; - for (var v = state.localVars; v; v = v.next) - if (v.name == varname) return; - state.localVars = {name: varname, next: state.localVars}; - } - } - - // Combinators - - var defaultVars = {name: "this", next: null}; - function pushcontext() { - if (!cx.state.context) cx.state.localVars = defaultVars; - cx.state.context = {prev: cx.state.context, vars: cx.state.localVars}; - } - function popcontext() { - cx.state.localVars = cx.state.context.vars; - cx.state.context = cx.state.context.prev; - } - function pushlex(type, info) { - var result = function() { - var state = cx.state; - state.lexical = new HaxeLexical(state.indented, cx.stream.column(), type, null, state.lexical, info); - }; - result.lex = true; - return result; - } - function poplex() { - var state = cx.state; - if (state.lexical.prev) { - if (state.lexical.type == ")") - state.indented = state.lexical.indented; - state.lexical = state.lexical.prev; - } - } - poplex.lex = true; - - function expect(wanted) { - return function expecting(type) { - if (type == wanted) return cont(); - else if (wanted == ";") return pass(); - else return cont(arguments.callee); - }; - } - - function statement(type) { - if (type == "@") return cont(metadef); - if (type == "var") return cont(pushlex("vardef"), vardef1, expect(";"), poplex); - if (type == "keyword a") return cont(pushlex("form"), expression, statement, poplex); - if (type == "keyword b") return cont(pushlex("form"), statement, poplex); - if (type == "{") return cont(pushlex("}"), pushcontext, block, poplex, popcontext); - if (type == ";") return cont(); - if (type == "attribute") return cont(maybeattribute); - if (type == "function") return cont(functiondef); - if (type == "for") return cont(pushlex("form"), expect("("), pushlex(")"), forspec1, expect(")"), - poplex, statement, poplex); - if (type == "variable") return cont(pushlex("stat"), maybelabel); - if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"), - block, poplex, poplex); - if (type == "case") return cont(expression, expect(":")); - if (type == "default") return cont(expect(":")); - if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"), - statement, poplex, popcontext); - if (type == "import") return cont(importdef, expect(";")); - if (type == "typedef") return cont(typedef); - return pass(pushlex("stat"), expression, expect(";"), poplex); - } - function expression(type) { - if (atomicTypes.hasOwnProperty(type)) return cont(maybeoperator); - if (type == "function") return cont(functiondef); - if (type == "keyword c") return cont(maybeexpression); - if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeoperator); - if (type == "operator") return cont(expression); - if (type == "[") return cont(pushlex("]"), commasep(expression, "]"), poplex, maybeoperator); - if (type == "{") return cont(pushlex("}"), commasep(objprop, "}"), poplex, maybeoperator); - return cont(); - } - function maybeexpression(type) { - if (type.match(/[;\}\)\],]/)) return pass(); - return pass(expression); - } - - function maybeoperator(type, value) { - if (type == "operator" && /\+\+|--/.test(value)) return cont(maybeoperator); - if (type == "operator" || type == ":") return cont(expression); - if (type == ";") return; - if (type == "(") return cont(pushlex(")"), commasep(expression, ")"), poplex, maybeoperator); - if (type == ".") return cont(property, maybeoperator); - if (type == "[") return cont(pushlex("]"), expression, expect("]"), poplex, maybeoperator); - } - - function maybeattribute(type, value) { - if (type == "attribute") return cont(maybeattribute); - if (type == "function") return cont(functiondef); - if (type == "var") return cont(vardef1); - } - - function metadef(type, value) { - if(type == ":") return cont(metadef); - if(type == "variable") return cont(metadef); - if(type == "(") return cont(pushlex(")"), comasep(metaargs, ")"), poplex, statement); - } - function metaargs(type, value) { - if(typ == "variable") return cont(); - } - - function importdef (type, value) { - if(type == "variable" && /[A-Z]/.test(value.charAt(0))) { registerimport(value); return cont(); } - else if(type == "variable" || type == "property" || type == ".") return cont(importdef); - } - - function typedef (type, value) - { - if(type == "variable" && /[A-Z]/.test(value.charAt(0))) { registerimport(value); return cont(); } - } - - function maybelabel(type) { - if (type == ":") return cont(poplex, statement); - return pass(maybeoperator, expect(";"), poplex); - } - function property(type) { - if (type == "variable") {cx.marked = "property"; return cont();} - } - function objprop(type) { - if (type == "variable") cx.marked = "property"; - if (atomicTypes.hasOwnProperty(type)) return cont(expect(":"), expression); - } - function commasep(what, end) { - function proceed(type) { - if (type == ",") return cont(what, proceed); - if (type == end) return cont(); - return cont(expect(end)); - } - return function commaSeparated(type) { - if (type == end) return cont(); - else return pass(what, proceed); - }; - } - function block(type) { - if (type == "}") return cont(); - return pass(statement, block); - } - function vardef1(type, value) { - if (type == "variable"){register(value); return cont(typeuse, vardef2);} - return cont(); - } - function vardef2(type, value) { - if (value == "=") return cont(expression, vardef2); - if (type == ",") return cont(vardef1); - } - function forspec1(type, value) { - if (type == "variable") { - register(value); - } - return cont(pushlex(")"), pushcontext, forin, expression, poplex, statement, popcontext); - } - function forin(type, value) { - if (value == "in") return cont(); - } - function functiondef(type, value) { - if (type == "variable") {register(value); return cont(functiondef);} - if (value == "new") return cont(functiondef); - if (type == "(") return cont(pushlex(")"), pushcontext, commasep(funarg, ")"), poplex, typeuse, statement, popcontext); - } - function typeuse(type, value) { - if(type == ":") return cont(typestring); - } - function typestring(type, value) { - if(type == "type") return cont(); - if(type == "variable") return cont(); - if(type == "{") return cont(pushlex("}"), commasep(typeprop, "}"), poplex); - } - function typeprop(type, value) { - if(type == "variable") return cont(typeuse); - } - function funarg(type, value) { - if (type == "variable") {register(value); return cont(typeuse);} - } - - // Interface - - return { - startState: function(basecolumn) { - var defaulttypes = ["Int", "Float", "String", "Void", "Std", "Bool", "Dynamic", "Array"]; - return { - tokenize: haxeTokenBase, - reAllowed: true, - kwAllowed: true, - cc: [], - lexical: new HaxeLexical((basecolumn || 0) - indentUnit, 0, "block", false), - localVars: parserConfig.localVars, - importedtypes: defaulttypes, - context: parserConfig.localVars && {vars: parserConfig.localVars}, - indented: 0 - }; - }, - - token: function(stream, state) { - if (stream.sol()) { - if (!state.lexical.hasOwnProperty("align")) - state.lexical.align = false; - state.indented = stream.indentation(); - } - if (stream.eatSpace()) return null; - var style = state.tokenize(stream, state); - if (type == "comment") return style; - state.reAllowed = !!(type == "operator" || type == "keyword c" || type.match(/^[\[{}\(,;:]$/)); - state.kwAllowed = type != '.'; - return parseHaxe(state, style, type, content, stream); - }, - - indent: function(state, textAfter) { - if (state.tokenize != haxeTokenBase) return 0; - var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical; - if (lexical.type == "stat" && firstChar == "}") lexical = lexical.prev; - var type = lexical.type, closing = firstChar == type; - if (type == "vardef") return lexical.indented + 4; - else if (type == "form" && firstChar == "{") return lexical.indented; - else if (type == "stat" || type == "form") return lexical.indented + indentUnit; - else if (lexical.info == "switch" && !closing) - return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit); - else if (lexical.align) return lexical.column + (closing ? 0 : 1); - else return lexical.indented + (closing ? 0 : indentUnit); - }, - - electricChars: "{}" - }; -}); - -CodeMirror.defineMIME("text/x-haxe", "haxe"); diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/haxe/index.html b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/haxe/index.html deleted file mode 100644 index b08092facc..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/haxe/index.html +++ /dev/null @@ -1,91 +0,0 @@ - - - - - CodeMirror: Haxe mode - - - - - - - -

    CodeMirror: Haxe mode

    - -
    - - - -

    MIME types defined: text/x-haxe.

    - - diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/htmlembedded/htmlembedded.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/htmlembedded/htmlembedded.js deleted file mode 100644 index eca6ee5f8e..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/htmlembedded/htmlembedded.js +++ /dev/null @@ -1,72 +0,0 @@ -CodeMirror.defineMode("htmlembedded", function(config, parserConfig) { - - //config settings - var scriptStartRegex = parserConfig.scriptStartRegex || /^<%/i, - scriptEndRegex = parserConfig.scriptEndRegex || /^%>/i; - - //inner modes - var scriptingMode, htmlMixedMode; - - //tokenizer when in html mode - function htmlDispatch(stream, state) { - if (stream.match(scriptStartRegex, false)) { - state.token=scriptingDispatch; - return scriptingMode.token(stream, state.scriptState); - } - else - return htmlMixedMode.token(stream, state.htmlState); - } - - //tokenizer when in scripting mode - function scriptingDispatch(stream, state) { - if (stream.match(scriptEndRegex, false)) { - state.token=htmlDispatch; - return htmlMixedMode.token(stream, state.htmlState); - } - else - return scriptingMode.token(stream, state.scriptState); - } - - - return { - startState: function() { - scriptingMode = scriptingMode || CodeMirror.getMode(config, parserConfig.scriptingModeSpec); - htmlMixedMode = htmlMixedMode || CodeMirror.getMode(config, "htmlmixed"); - return { - token : parserConfig.startOpen ? scriptingDispatch : htmlDispatch, - htmlState : htmlMixedMode.startState(), - scriptState : scriptingMode.startState() - }; - }, - - token: function(stream, state) { - return state.token(stream, state); - }, - - indent: function(state, textAfter) { - if (state.token == htmlDispatch) - return htmlMixedMode.indent(state.htmlState, textAfter); - else - return scriptingMode.indent(state.scriptState, textAfter); - }, - - copyState: function(state) { - return { - token : state.token, - htmlState : CodeMirror.copyState(htmlMixedMode, state.htmlState), - scriptState : CodeMirror.copyState(scriptingMode, state.scriptState) - }; - }, - - electricChars: "/{}:", - - innerMode: function(state) { - if (state.token == scriptingDispatch) return {state: state.scriptState, mode: scriptingMode}; - else return {state: state.htmlState, mode: htmlMixedMode}; - } - }; -}, "htmlmixed"); - -CodeMirror.defineMIME("application/x-ejs", { name: "htmlembedded", scriptingModeSpec:"javascript"}); -CodeMirror.defineMIME("application/x-aspx", { name: "htmlembedded", scriptingModeSpec:"text/x-csharp"}); -CodeMirror.defineMIME("application/x-jsp", { name: "htmlembedded", scriptingModeSpec:"text/x-java"}); diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/htmlembedded/index.html b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/htmlembedded/index.html deleted file mode 100644 index 788ea83ae4..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/htmlembedded/index.html +++ /dev/null @@ -1,50 +0,0 @@ - - - - - CodeMirror: Html Embedded Scripts mode - - - - - - - - - - - -

    CodeMirror: Html Embedded Scripts mode

    - -
    - - - -

    Mode for html embedded scripts like JSP and ASP.NET. Depends on HtmlMixed which in turn depends on - JavaScript, CSS and XML.
    Other dependancies include those of the scriping language chosen.

    - -

    MIME types defined: application/x-aspx (ASP.NET), - application/x-ejs (Embedded Javascript), application/x-jsp (JavaServer Pages)

    - - diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/htmlmixed/htmlmixed.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/htmlmixed/htmlmixed.js deleted file mode 100644 index 91ca0b9b3e..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/htmlmixed/htmlmixed.js +++ /dev/null @@ -1,84 +0,0 @@ -CodeMirror.defineMode("htmlmixed", function(config) { - var htmlMode = CodeMirror.getMode(config, {name: "xml", htmlMode: true}); - var jsMode = CodeMirror.getMode(config, "javascript"); - var cssMode = CodeMirror.getMode(config, "css"); - - function html(stream, state) { - var style = htmlMode.token(stream, state.htmlState); - if (style == "tag" && stream.current() == ">" && state.htmlState.context) { - if (/^script$/i.test(state.htmlState.context.tagName)) { - state.token = javascript; - state.localState = jsMode.startState(htmlMode.indent(state.htmlState, "")); - } - else if (/^style$/i.test(state.htmlState.context.tagName)) { - state.token = css; - state.localState = cssMode.startState(htmlMode.indent(state.htmlState, "")); - } - } - return style; - } - function maybeBackup(stream, pat, style) { - var cur = stream.current(); - var close = cur.search(pat), m; - if (close > -1) stream.backUp(cur.length - close); - else if (m = cur.match(/<\/?$/)) { - stream.backUp(cur[0].length); - if (!stream.match(pat, false)) stream.match(cur[0]); - } - return style; - } - function javascript(stream, state) { - if (stream.match(/^<\/\s*script\s*>/i, false)) { - state.token = html; - state.localState = null; - return html(stream, state); - } - return maybeBackup(stream, /<\/\s*script\s*>/, - jsMode.token(stream, state.localState)); - } - function css(stream, state) { - if (stream.match(/^<\/\s*style\s*>/i, false)) { - state.token = html; - state.localState = null; - return html(stream, state); - } - return maybeBackup(stream, /<\/\s*style\s*>/, - cssMode.token(stream, state.localState)); - } - - return { - startState: function() { - var state = htmlMode.startState(); - return {token: html, localState: null, mode: "html", htmlState: state}; - }, - - copyState: function(state) { - if (state.localState) - var local = CodeMirror.copyState(state.token == css ? cssMode : jsMode, state.localState); - return {token: state.token, localState: local, mode: state.mode, - htmlState: CodeMirror.copyState(htmlMode, state.htmlState)}; - }, - - token: function(stream, state) { - return state.token(stream, state); - }, - - indent: function(state, textAfter) { - if (state.token == html || /^\s*<\//.test(textAfter)) - return htmlMode.indent(state.htmlState, textAfter); - else if (state.token == javascript) - return jsMode.indent(state.localState, textAfter); - else - return cssMode.indent(state.localState, textAfter); - }, - - electricChars: "/{}:", - - innerMode: function(state) { - var mode = state.token == html ? htmlMode : state.token == javascript ? jsMode : cssMode; - return {state: state.localState || state.htmlState, mode: mode}; - } - }; -}, "xml", "javascript", "css"); - -CodeMirror.defineMIME("text/html", "htmlmixed"); diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/htmlmixed/index.html b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/htmlmixed/index.html deleted file mode 100644 index 198e7b01ce..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/htmlmixed/index.html +++ /dev/null @@ -1,52 +0,0 @@ - - - - - CodeMirror: HTML mixed mode - - - - - - - - - - -

    CodeMirror: HTML mixed mode

    -
    - - -

    The HTML mixed mode depends on the XML, JavaScript, and CSS modes.

    - -

    MIME types defined: text/html - (redefined, only takes effect if you load this parser after the - XML parser).

    - - - diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/javascript/index.html b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/javascript/index.html deleted file mode 100644 index ca3f0bd550..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/javascript/index.html +++ /dev/null @@ -1,78 +0,0 @@ - - - - - CodeMirror: JavaScript mode - - - - - - - -

    CodeMirror: JavaScript mode

    - -
    - - - -

    JavaScript mode supports a single configuration - option, json, which will set the mode to expect JSON - data rather than a JavaScript program.

    - -

    MIME types defined: text/javascript, application/json.

    - - diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/javascript/javascript.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/javascript/javascript.js deleted file mode 100644 index bae3081d35..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/javascript/javascript.js +++ /dev/null @@ -1,361 +0,0 @@ -CodeMirror.defineMode("javascript", function(config, parserConfig) { - var indentUnit = config.indentUnit; - var jsonMode = parserConfig.json; - - // Tokenizer - - var keywords = function(){ - function kw(type) {return {type: type, style: "keyword"};} - var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c"); - var operator = kw("operator"), atom = {type: "atom", style: "atom"}; - return { - "if": A, "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B, - "return": C, "break": C, "continue": C, "new": C, "delete": C, "throw": C, - "var": kw("var"), "const": kw("var"), "let": kw("var"), - "function": kw("function"), "catch": kw("catch"), - "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"), - "in": operator, "typeof": operator, "instanceof": operator, - "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom - }; - }(); - - var isOperatorChar = /[+\-*&%=<>!?|]/; - - function chain(stream, state, f) { - state.tokenize = f; - return f(stream, state); - } - - function nextUntilUnescaped(stream, end) { - var escaped = false, next; - while ((next = stream.next()) != null) { - if (next == end && !escaped) - return false; - escaped = !escaped && next == "\\"; - } - return escaped; - } - - // Used as scratch variables to communicate multiple values without - // consing up tons of objects. - var type, content; - function ret(tp, style, cont) { - type = tp; content = cont; - return style; - } - - function jsTokenBase(stream, state) { - var ch = stream.next(); - if (ch == '"' || ch == "'") - return chain(stream, state, jsTokenString(ch)); - else if (/[\[\]{}\(\),;\:\.]/.test(ch)) - return ret(ch); - else if (ch == "0" && stream.eat(/x/i)) { - stream.eatWhile(/[\da-f]/i); - return ret("number", "number"); - } - else if (/\d/.test(ch) || ch == "-" && stream.eat(/\d/)) { - stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/); - return ret("number", "number"); - } - else if (ch == "/") { - if (stream.eat("*")) { - return chain(stream, state, jsTokenComment); - } - else if (stream.eat("/")) { - stream.skipToEnd(); - return ret("comment", "comment"); - } - else if (state.reAllowed) { - nextUntilUnescaped(stream, "/"); - stream.eatWhile(/[gimy]/); // 'y' is "sticky" option in Mozilla - return ret("regexp", "string-2"); - } - else { - stream.eatWhile(isOperatorChar); - return ret("operator", null, stream.current()); - } - } - else if (ch == "#") { - stream.skipToEnd(); - return ret("error", "error"); - } - else if (isOperatorChar.test(ch)) { - stream.eatWhile(isOperatorChar); - return ret("operator", null, stream.current()); - } - else { - stream.eatWhile(/[\w\$_]/); - var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word]; - return (known && state.kwAllowed) ? ret(known.type, known.style, word) : - ret("variable", "variable", word); - } - } - - function jsTokenString(quote) { - return function(stream, state) { - if (!nextUntilUnescaped(stream, quote)) - state.tokenize = jsTokenBase; - return ret("string", "string"); - }; - } - - function jsTokenComment(stream, state) { - var maybeEnd = false, ch; - while (ch = stream.next()) { - if (ch == "/" && maybeEnd) { - state.tokenize = jsTokenBase; - break; - } - maybeEnd = (ch == "*"); - } - return ret("comment", "comment"); - } - - // Parser - - var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true}; - - function JSLexical(indented, column, type, align, prev, info) { - this.indented = indented; - this.column = column; - this.type = type; - this.prev = prev; - this.info = info; - if (align != null) this.align = align; - } - - function inScope(state, varname) { - for (var v = state.localVars; v; v = v.next) - if (v.name == varname) return true; - } - - function parseJS(state, style, type, content, stream) { - var cc = state.cc; - // Communicate our context to the combinators. - // (Less wasteful than consing up a hundred closures on every call.) - cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc; - - if (!state.lexical.hasOwnProperty("align")) - state.lexical.align = true; - - while(true) { - var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement; - if (combinator(type, content)) { - while(cc.length && cc[cc.length - 1].lex) - cc.pop()(); - if (cx.marked) return cx.marked; - if (type == "variable" && inScope(state, content)) return "variable-2"; - return style; - } - } - } - - // Combinator utils - - var cx = {state: null, column: null, marked: null, cc: null}; - function pass() { - for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]); - } - function cont() { - pass.apply(null, arguments); - return true; - } - function register(varname) { - var state = cx.state; - if (state.context) { - cx.marked = "def"; - for (var v = state.localVars; v; v = v.next) - if (v.name == varname) return; - state.localVars = {name: varname, next: state.localVars}; - } - } - - // Combinators - - var defaultVars = {name: "this", next: {name: "arguments"}}; - function pushcontext() { - cx.state.context = {prev: cx.state.context, vars: cx.state.localVars}; - cx.state.localVars = defaultVars; - } - function popcontext() { - cx.state.localVars = cx.state.context.vars; - cx.state.context = cx.state.context.prev; - } - function pushlex(type, info) { - var result = function() { - var state = cx.state; - state.lexical = new JSLexical(state.indented, cx.stream.column(), type, null, state.lexical, info); - }; - result.lex = true; - return result; - } - function poplex() { - var state = cx.state; - if (state.lexical.prev) { - if (state.lexical.type == ")") - state.indented = state.lexical.indented; - state.lexical = state.lexical.prev; - } - } - poplex.lex = true; - - function expect(wanted) { - return function expecting(type) { - if (type == wanted) return cont(); - else if (wanted == ";") return pass(); - else return cont(arguments.callee); - }; - } - - function statement(type) { - if (type == "var") return cont(pushlex("vardef"), vardef1, expect(";"), poplex); - if (type == "keyword a") return cont(pushlex("form"), expression, statement, poplex); - if (type == "keyword b") return cont(pushlex("form"), statement, poplex); - if (type == "{") return cont(pushlex("}"), block, poplex); - if (type == ";") return cont(); - if (type == "function") return cont(functiondef); - if (type == "for") return cont(pushlex("form"), expect("("), pushlex(")"), forspec1, expect(")"), - poplex, statement, poplex); - if (type == "variable") return cont(pushlex("stat"), maybelabel); - if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"), - block, poplex, poplex); - if (type == "case") return cont(expression, expect(":")); - if (type == "default") return cont(expect(":")); - if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"), - statement, poplex, popcontext); - return pass(pushlex("stat"), expression, expect(";"), poplex); - } - function expression(type) { - if (atomicTypes.hasOwnProperty(type)) return cont(maybeoperator); - if (type == "function") return cont(functiondef); - if (type == "keyword c") return cont(maybeexpression); - if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeoperator); - if (type == "operator") return cont(expression); - if (type == "[") return cont(pushlex("]"), commasep(expression, "]"), poplex, maybeoperator); - if (type == "{") return cont(pushlex("}"), commasep(objprop, "}"), poplex, maybeoperator); - return cont(); - } - function maybeexpression(type) { - if (type.match(/[;\}\)\],]/)) return pass(); - return pass(expression); - } - - function maybeoperator(type, value) { - if (type == "operator" && /\+\+|--/.test(value)) return cont(maybeoperator); - if (type == "operator" && value == "?") return cont(expression, expect(":"), expression); - if (type == ";") return; - if (type == "(") return cont(pushlex(")"), commasep(expression, ")"), poplex, maybeoperator); - if (type == ".") return cont(property, maybeoperator); - if (type == "[") return cont(pushlex("]"), expression, expect("]"), poplex, maybeoperator); - } - function maybelabel(type) { - if (type == ":") return cont(poplex, statement); - return pass(maybeoperator, expect(";"), poplex); - } - function property(type) { - if (type == "variable") {cx.marked = "property"; return cont();} - } - function objprop(type) { - if (type == "variable") cx.marked = "property"; - if (atomicTypes.hasOwnProperty(type)) return cont(expect(":"), expression); - } - function commasep(what, end) { - function proceed(type) { - if (type == ",") return cont(what, proceed); - if (type == end) return cont(); - return cont(expect(end)); - } - return function commaSeparated(type) { - if (type == end) return cont(); - else return pass(what, proceed); - }; - } - function block(type) { - if (type == "}") return cont(); - return pass(statement, block); - } - function vardef1(type, value) { - if (type == "variable"){register(value); return cont(vardef2);} - return cont(); - } - function vardef2(type, value) { - if (value == "=") return cont(expression, vardef2); - if (type == ",") return cont(vardef1); - } - function forspec1(type) { - if (type == "var") return cont(vardef1, forspec2); - if (type == ";") return pass(forspec2); - if (type == "variable") return cont(formaybein); - return pass(forspec2); - } - function formaybein(type, value) { - if (value == "in") return cont(expression); - return cont(maybeoperator, forspec2); - } - function forspec2(type, value) { - if (type == ";") return cont(forspec3); - if (value == "in") return cont(expression); - return cont(expression, expect(";"), forspec3); - } - function forspec3(type) { - if (type != ")") cont(expression); - } - function functiondef(type, value) { - if (type == "variable") {register(value); return cont(functiondef);} - if (type == "(") return cont(pushlex(")"), pushcontext, commasep(funarg, ")"), poplex, statement, popcontext); - } - function funarg(type, value) { - if (type == "variable") {register(value); return cont();} - } - - // Interface - - return { - startState: function(basecolumn) { - return { - tokenize: jsTokenBase, - reAllowed: true, - kwAllowed: true, - cc: [], - lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false), - localVars: parserConfig.localVars, - context: parserConfig.localVars && {vars: parserConfig.localVars}, - indented: 0 - }; - }, - - token: function(stream, state) { - if (stream.sol()) { - if (!state.lexical.hasOwnProperty("align")) - state.lexical.align = false; - state.indented = stream.indentation(); - } - if (stream.eatSpace()) return null; - var style = state.tokenize(stream, state); - if (type == "comment") return style; - state.reAllowed = !!(type == "operator" || type == "keyword c" || type.match(/^[\[{}\(,;:]$/)); - state.kwAllowed = type != '.'; - return parseJS(state, style, type, content, stream); - }, - - indent: function(state, textAfter) { - if (state.tokenize != jsTokenBase) return 0; - var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical; - if (lexical.type == "stat" && firstChar == "}") lexical = lexical.prev; - var type = lexical.type, closing = firstChar == type; - if (type == "vardef") return lexical.indented + 4; - else if (type == "form" && firstChar == "{") return lexical.indented; - else if (type == "stat" || type == "form") return lexical.indented + indentUnit; - else if (lexical.info == "switch" && !closing) - return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit); - else if (lexical.align) return lexical.column + (closing ? 0 : 1); - else return lexical.indented + (closing ? 0 : indentUnit); - }, - - electricChars: ":{}" - }; -}); - -CodeMirror.defineMIME("text/javascript", "javascript"); -CodeMirror.defineMIME("application/json", {name: "javascript", json: true}); diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/jinja2/index.html b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/jinja2/index.html deleted file mode 100644 index 2ca7b5466f..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/jinja2/index.html +++ /dev/null @@ -1,38 +0,0 @@ - - - - - CodeMirror: Jinja2 mode - - - - - - - -

    CodeMirror: Jinja2 mode

    -
    - - - diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/jinja2/jinja2.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/jinja2/jinja2.js deleted file mode 100644 index 92a3092cc4..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/jinja2/jinja2.js +++ /dev/null @@ -1,42 +0,0 @@ -CodeMirror.defineMode("jinja2", function(config, parserConf) { - var keywords = ["block", "endblock", "for", "endfor", "in", "true", "false", - "loop", "none", "self", "super", "if", "as", "not", "and", - "else", "import", "with", "without", "context"]; - keywords = new RegExp("^((" + keywords.join(")|(") + "))\\b"); - - function tokenBase (stream, state) { - var ch = stream.next(); - if (ch == "{") { - if (ch = stream.eat(/\{|%|#/)) { - stream.eat("-"); - state.tokenize = inTag(ch); - return "tag"; - } - } - } - function inTag (close) { - if (close == "{") { - close = "}"; - } - return function (stream, state) { - var ch = stream.next(); - if ((ch == close || (ch == "-" && stream.eat(close))) - && stream.eat("}")) { - state.tokenize = tokenBase; - return "tag"; - } - if (stream.match(keywords)) { - return "keyword"; - } - return close == "#" ? "comment" : "string"; - }; - } - return { - startState: function () { - return {tokenize: tokenBase}; - }, - token: function (stream, state) { - return state.tokenize(stream, state); - } - }; -}); diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/less/index.html b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/less/index.html deleted file mode 100644 index cdd08e90aa..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/less/index.html +++ /dev/null @@ -1,740 +0,0 @@ - - - - - CodeMirror: LESS mode - - - - - - - - -

    CodeMirror: LESS mode

    -
    - - -

    MIME types defined: text/x-less, text/css (if not previously defined).

    - - diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/less/less.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/less/less.js deleted file mode 100644 index 612d790fb7..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/less/less.js +++ /dev/null @@ -1,266 +0,0 @@ -/* - LESS mode - http://www.lesscss.org/ - Ported to CodeMirror by Peter Kroon - Report bugs/issues here: https://github.com/marijnh/CodeMirror/issues GitHub: @peterkroon -*/ - -CodeMirror.defineMode("less", function(config) { - var indentUnit = config.indentUnit, type; - function ret(style, tp) {type = tp; return style;} - //html tags - var tags = "a abbr acronym address applet area article aside audio b base basefont bdi bdo big blockquote body br button canvas caption cite code col colgroup command datalist dd del details dfn dir div dl dt em embed fieldset figcaption figure font footer form frame frameset h1 h2 h3 h4 h5 h6 head header hgroup hr html i iframe img input ins keygen kbd label legend li link map mark menu meta meter nav noframes noscript object ol optgroup option output p param pre progress q rp rt ruby s samp script section select small source span strike strong style sub summary sup table tbody td textarea tfoot th thead time title tr track tt u ul var video wbr".split(' '); - - function inTagsArray(val){ - for(var i=0; i*\/]/.test(ch)) { - if(stream.peek() == "=" || type == "a")return ret("string", "string"); - return ret(null, "select-op"); - } - else if (/[;{}:\[\]()~\|]/.test(ch)) { - if(ch == ":"){ - stream.eatWhile(/[a-z\\\-]/); - if( selectors.test(stream.current()) ){ - return ret("tag", "tag"); - }else if(stream.peek() == ":"){//::-webkit-search-decoration - stream.next(); - stream.eatWhile(/[a-z\\\-]/); - if(stream.current().match(/\:\:\-(o|ms|moz|webkit)\-/))return ret("string", "string"); - if( selectors.test(stream.current().substring(1)) )return ret("tag", "tag"); - return ret(null, ch); - }else{ - return ret(null, ch); - } - }else if(ch == "~"){ - if(type == "r")return ret("string", "string"); - }else{ - return ret(null, ch); - } - } - else if (ch == ".") { - if(type == "(" || type == "string")return ret("string", "string"); // allow url(../image.png) - stream.eatWhile(/[\a-zA-Z0-9\-_]/); - if(stream.peek() == " ")stream.eatSpace(); - if(stream.peek() == ")")return ret("number", "unit");//rgba(0,0,0,.25); - return ret("tag", "tag"); - } - else if (ch == "#") { - //we don't eat white-space, we want the hex color and or id only - stream.eatWhile(/[A-Za-z0-9]/); - //check if there is a proper hex color length e.g. #eee || #eeeEEE - if(stream.current().length == 4 || stream.current().length == 7){ - if(stream.current().match(/[A-Fa-f0-9]{6}|[A-Fa-f0-9]{3}/,false) != null){//is there a valid hex color value present in the current stream - //when not a valid hex value, parse as id - if(stream.current().substring(1) != stream.current().match(/[A-Fa-f0-9]{6}|[A-Fa-f0-9]{3}/,false))return ret("atom", "tag"); - //eat white-space - stream.eatSpace(); - //when hex value declaration doesn't end with [;,] but is does with a slash/cc comment treat it as an id, just like the other hex values that don't end with[;,] - if( /[\/<>.(){!$%^&*_\-\\?=+\|#'~`]/.test(stream.peek()) )return ret("atom", "tag"); - //#time { color: #aaa } - else if(stream.peek() == "}" )return ret("number", "unit"); - //we have a valid hex color value, parse as id whenever an element/class is defined after the hex(id) value e.g. #eee aaa || #eee .aaa - else if( /[a-zA-Z\\]/.test(stream.peek()) )return ret("atom", "tag"); - //when a hex value is on the end of a line, parse as id - else if(stream.eol())return ret("atom", "tag"); - //default - else return ret("number", "unit"); - }else{//when not a valid hexvalue in the current stream e.g. #footer - stream.eatWhile(/[\w\\\-]/); - return ret("atom", "tag"); - } - }else{//when not a valid hexvalue length - stream.eatWhile(/[\w\\\-]/); - return ret("atom", "tag"); - } - } - else if (ch == "&") { - stream.eatWhile(/[\w\-]/); - return ret(null, ch); - } - else { - stream.eatWhile(/[\w\\\-_%.{]/); - if(type == "string"){ - return ret("string", "string"); - }else if(stream.current().match(/(^http$|^https$)/) != null){ - stream.eatWhile(/[\w\\\-_%.{:\/]/); - return ret("string", "string"); - }else if(stream.peek() == "<" || stream.peek() == ">"){ - return ret("tag", "tag"); - }else if( /\(/.test(stream.peek()) ){ - return ret(null, ch); - }else if (stream.peek() == "/" && state.stack[state.stack.length-1] != undefined){ // url(dir/center/image.png) - return ret("string", "string"); - }else if( stream.current().match(/\-\d|\-.\d/) ){ // match e.g.: -5px -0.4 etc... only colorize the minus sign - //commment out these 2 comment if you want the minus sign to be parsed as null -500px - //stream.backUp(stream.current().length-1); - //return ret(null, ch); //console.log( stream.current() ); - return ret("number", "unit"); - }else if( inTagsArray(stream.current().toLowerCase()) ){ // match html tags - return ret("tag", "tag"); - }else if( /\/|[\s\)]/.test(stream.peek() || stream.eol() || (stream.eatSpace() && stream.peek() == "/")) && stream.current().indexOf(".") !== -1){ - if(stream.current().substring(stream.current().length-1,stream.current().length) == "{"){ - stream.backUp(1); - return ret("tag", "tag"); - }//end if - stream.eatSpace(); - if( /[{<>.a-zA-Z\/]/.test(stream.peek()) || stream.eol() )return ret("tag", "tag"); // e.g. button.icon-plus - return ret("string", "string"); // let url(/images/logo.png) without quotes return as string - }else if( stream.eol() || stream.peek() == "[" || stream.peek() == "#" || type == "tag" ){ - if(stream.current().substring(stream.current().length-1,stream.current().length) == "{")stream.backUp(1); - return ret("tag", "tag"); - }else if(type == "compare" || type == "a" || type == "("){ - return ret("string", "string"); - }else if(type == "|" || stream.current() == "-" || type == "["){ - return ret(null, ch); - }else if(stream.peek() == ":") { - stream.next(); - var t_v = stream.peek() == ":" ? true : false; - if(!t_v){ - var old_pos = stream.pos; - var sc = stream.current().length; - stream.eatWhile(/[a-z\\\-]/); - var new_pos = stream.pos; - if(stream.current().substring(sc-1).match(selectors) != null){ - stream.backUp(new_pos-(old_pos-1)); - return ret("tag", "tag"); - } else stream.backUp(new_pos-(old_pos-1)); - }else{ - stream.backUp(1); - } - if(t_v)return ret("tag", "tag"); else return ret("variable", "variable"); - }else{ - return ret("variable", "variable"); - } - } - } - - function tokenSComment(stream, state) { // SComment = Slash comment - stream.skipToEnd(); - state.tokenize = tokenBase; - return ret("comment", "comment"); - } - - function tokenCComment(stream, state) { - var maybeEnd = false, ch; - while ((ch = stream.next()) != null) { - if (maybeEnd && ch == "/") { - state.tokenize = tokenBase; - break; - } - maybeEnd = (ch == "*"); - } - return ret("comment", "comment"); - } - - function tokenSGMLComment(stream, state) { - var dashes = 0, ch; - while ((ch = stream.next()) != null) { - if (dashes >= 2 && ch == ">") { - state.tokenize = tokenBase; - break; - } - dashes = (ch == "-") ? dashes + 1 : 0; - } - return ret("comment", "comment"); - } - - function tokenString(quote) { - return function(stream, state) { - var escaped = false, ch; - while ((ch = stream.next()) != null) { - if (ch == quote && !escaped) - break; - escaped = !escaped && ch == "\\"; - } - if (!escaped) state.tokenize = tokenBase; - return ret("string", "string"); - }; - } - - return { - startState: function(base) { - return {tokenize: tokenBase, - baseIndent: base || 0, - stack: []}; - }, - - token: function(stream, state) { - if (stream.eatSpace()) return null; - var style = state.tokenize(stream, state); - - var context = state.stack[state.stack.length-1]; - if (type == "hash" && context == "rule") style = "atom"; - else if (style == "variable") { - if (context == "rule") style = null; //"tag" - else if (!context || context == "@media{") { - style = stream.current() == "when" ? "variable" : - /[\s,|\s\)|\s]/.test(stream.peek()) ? "tag" : type; - } - } - - if (context == "rule" && /^[\{\};]$/.test(type)) - state.stack.pop(); - if (type == "{") { - if (context == "@media") state.stack[state.stack.length-1] = "@media{"; - else state.stack.push("{"); - } - else if (type == "}") state.stack.pop(); - else if (type == "@media") state.stack.push("@media"); - else if (context == "{" && type != "comment") state.stack.push("rule"); - return style; - }, - - indent: function(state, textAfter) { - var n = state.stack.length; - if (/^\}/.test(textAfter)) - n -= state.stack[state.stack.length-1] == "rule" ? 2 : 1; - return state.baseIndent + n * indentUnit; - }, - - electricChars: "}" - }; -}); - -CodeMirror.defineMIME("text/x-less", "less"); -if (!CodeMirror.mimeModes.hasOwnProperty("text/css")) - CodeMirror.defineMIME("text/css", "less"); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/lua/index.html b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/lua/index.html deleted file mode 100644 index dd597d967b..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/lua/index.html +++ /dev/null @@ -1,73 +0,0 @@ - - - - - CodeMirror: Lua mode - - - - - - - - -

    CodeMirror: Lua mode

    -
    - - -

    Loosely based on Franciszek - Wawrzak's CodeMirror - 1 mode. One configuration parameter is - supported, specials, to which you can provide an - array of strings to have those identifiers highlighted with - the lua-special style.

    -

    MIME types defined: text/x-lua.

    - - - diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/lua/lua.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/lua/lua.js deleted file mode 100644 index ede8b07036..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/lua/lua.js +++ /dev/null @@ -1,140 +0,0 @@ -// LUA mode. Ported to CodeMirror 2 from Franciszek Wawrzak's -// CodeMirror 1 mode. -// highlights keywords, strings, comments (no leveling supported! ("[==[")), tokens, basic indenting - -CodeMirror.defineMode("lua", function(config, parserConfig) { - var indentUnit = config.indentUnit; - - function prefixRE(words) { - return new RegExp("^(?:" + words.join("|") + ")", "i"); - } - function wordRE(words) { - return new RegExp("^(?:" + words.join("|") + ")$", "i"); - } - var specials = wordRE(parserConfig.specials || []); - - // long list of standard functions from lua manual - var builtins = wordRE([ - "_G","_VERSION","assert","collectgarbage","dofile","error","getfenv","getmetatable","ipairs","load", - "loadfile","loadstring","module","next","pairs","pcall","print","rawequal","rawget","rawset","require", - "select","setfenv","setmetatable","tonumber","tostring","type","unpack","xpcall", - - "coroutine.create","coroutine.resume","coroutine.running","coroutine.status","coroutine.wrap","coroutine.yield", - - "debug.debug","debug.getfenv","debug.gethook","debug.getinfo","debug.getlocal","debug.getmetatable", - "debug.getregistry","debug.getupvalue","debug.setfenv","debug.sethook","debug.setlocal","debug.setmetatable", - "debug.setupvalue","debug.traceback", - - "close","flush","lines","read","seek","setvbuf","write", - - "io.close","io.flush","io.input","io.lines","io.open","io.output","io.popen","io.read","io.stderr","io.stdin", - "io.stdout","io.tmpfile","io.type","io.write", - - "math.abs","math.acos","math.asin","math.atan","math.atan2","math.ceil","math.cos","math.cosh","math.deg", - "math.exp","math.floor","math.fmod","math.frexp","math.huge","math.ldexp","math.log","math.log10","math.max", - "math.min","math.modf","math.pi","math.pow","math.rad","math.random","math.randomseed","math.sin","math.sinh", - "math.sqrt","math.tan","math.tanh", - - "os.clock","os.date","os.difftime","os.execute","os.exit","os.getenv","os.remove","os.rename","os.setlocale", - "os.time","os.tmpname", - - "package.cpath","package.loaded","package.loaders","package.loadlib","package.path","package.preload", - "package.seeall", - - "string.byte","string.char","string.dump","string.find","string.format","string.gmatch","string.gsub", - "string.len","string.lower","string.match","string.rep","string.reverse","string.sub","string.upper", - - "table.concat","table.insert","table.maxn","table.remove","table.sort" - ]); - var keywords = wordRE(["and","break","elseif","false","nil","not","or","return", - "true","function", "end", "if", "then", "else", "do", - "while", "repeat", "until", "for", "in", "local" ]); - - var indentTokens = wordRE(["function", "if","repeat","do", "\\(", "{"]); - var dedentTokens = wordRE(["end", "until", "\\)", "}"]); - var dedentPartial = prefixRE(["end", "until", "\\)", "}", "else", "elseif"]); - - function readBracket(stream) { - var level = 0; - while (stream.eat("=")) ++level; - stream.eat("["); - return level; - } - - function normal(stream, state) { - var ch = stream.next(); - if (ch == "-" && stream.eat("-")) { - if (stream.eat("[")) - return (state.cur = bracketed(readBracket(stream), "comment"))(stream, state); - stream.skipToEnd(); - return "comment"; - } - if (ch == "\"" || ch == "'") - return (state.cur = string(ch))(stream, state); - if (ch == "[" && /[\[=]/.test(stream.peek())) - return (state.cur = bracketed(readBracket(stream), "string"))(stream, state); - if (/\d/.test(ch)) { - stream.eatWhile(/[\w.%]/); - return "number"; - } - if (/[\w_]/.test(ch)) { - stream.eatWhile(/[\w\\\-_.]/); - return "variable"; - } - return null; - } - - function bracketed(level, style) { - return function(stream, state) { - var curlev = null, ch; - while ((ch = stream.next()) != null) { - if (curlev == null) {if (ch == "]") curlev = 0;} - else if (ch == "=") ++curlev; - else if (ch == "]" && curlev == level) { state.cur = normal; break; } - else curlev = null; - } - return style; - }; - } - - function string(quote) { - return function(stream, state) { - var escaped = false, ch; - while ((ch = stream.next()) != null) { - if (ch == quote && !escaped) break; - escaped = !escaped && ch == "\\"; - } - if (!escaped) state.cur = normal; - return "string"; - }; - } - - return { - startState: function(basecol) { - return {basecol: basecol || 0, indentDepth: 0, cur: normal}; - }, - - token: function(stream, state) { - if (stream.eatSpace()) return null; - var style = state.cur(stream, state); - var word = stream.current(); - if (style == "variable") { - if (keywords.test(word)) style = "keyword"; - else if (builtins.test(word)) style = "builtin"; - else if (specials.test(word)) style = "variable-2"; - } - if ((style != "comment") && (style != "string")){ - if (indentTokens.test(word)) ++state.indentDepth; - else if (dedentTokens.test(word)) --state.indentDepth; - } - return style; - }, - - indent: function(state, textAfter) { - var closing = dedentPartial.test(textAfter); - return state.basecol + indentUnit * (state.indentDepth - (closing ? 1 : 0)); - } - }; -}); - -CodeMirror.defineMIME("text/x-lua", "lua"); diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/markdown/index.html b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/markdown/index.html deleted file mode 100644 index 66f07ccda4..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/markdown/index.html +++ /dev/null @@ -1,343 +0,0 @@ - - - - - CodeMirror: Markdown mode - - - - - - - - -

    CodeMirror: Markdown mode

    - - -
    - - - -

    Optionally depends on the XML mode for properly highlighted inline XML blocks.

    - -

    MIME types defined: text/x-markdown.

    - -

    Parsing/Highlighting Tests: normal, verbose.

    - - - diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/markdown/markdown.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/markdown/markdown.js deleted file mode 100644 index 8740290034..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/markdown/markdown.js +++ /dev/null @@ -1,382 +0,0 @@ -CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { - - var htmlFound = CodeMirror.mimeModes.hasOwnProperty("text/html"); - var htmlMode = CodeMirror.getMode(cmCfg, htmlFound ? "text/html" : "text/plain"); - - var codeDepth = 0; - var prevLineHasContent = false - , thisLineHasContent = false; - - var header = 'header' - , code = 'comment' - , quote = 'quote' - , list = 'string' - , hr = 'hr' - , linkinline = 'link' - , linkemail = 'link' - , linktext = 'link' - , linkhref = 'string' - , em = 'em' - , strong = 'strong' - , emstrong = 'emstrong'; - - var hrRE = /^([*\-=_])(?:\s*\1){2,}\s*$/ - , ulRE = /^[*\-+]\s+/ - , olRE = /^[0-9]+\.\s+/ - , headerRE = /^(?:\={1,}|-{1,})$/ - , textRE = /^[^\[*_\\<>` "'(]+/; - - function switchInline(stream, state, f) { - state.f = state.inline = f; - return f(stream, state); - } - - function switchBlock(stream, state, f) { - state.f = state.block = f; - return f(stream, state); - } - - - // Blocks - - function blankLine(state) { - // Reset linkTitle state - state.linkTitle = false; - // Reset CODE state - state.code = false; - // Reset EM state - state.em = false; - // Reset STRONG state - state.strong = false; - // Reset state.quote - state.quote = false; - if (!htmlFound && state.f == htmlBlock) { - state.f = inlineNormal; - state.block = blockNormal; - } - return null; - } - - function blockNormal(stream, state) { - var match; - - if (state.list !== false && state.indentationDiff >= 0) { // Continued list - if (state.indentationDiff < 4) { // Only adjust indentation if *not* a code block - state.indentation -= state.indentationDiff; - } - state.list = null; - } else { // No longer a list - state.list = false; - } - - if (state.indentationDiff >= 4) { - state.indentation -= 4; - stream.skipToEnd(); - return code; - } else if (stream.eatSpace()) { - return null; - } else if (stream.peek() === '#' || (prevLineHasContent && stream.match(headerRE)) ) { - state.header = true; - } else if (stream.eat('>')) { - state.indentation++; - state.quote = true; - } else if (stream.peek() === '[') { - return switchInline(stream, state, footnoteLink); - } else if (stream.match(hrRE, true)) { - return hr; - } else if (match = stream.match(ulRE, true) || stream.match(olRE, true)) { - state.indentation += 4; - state.list = true; - } - - return switchInline(stream, state, state.inline); - } - - function htmlBlock(stream, state) { - var style = htmlMode.token(stream, state.htmlState); - if (htmlFound && style === 'tag' && state.htmlState.type !== 'openTag' && !state.htmlState.context) { - state.f = inlineNormal; - state.block = blockNormal; - } - if (state.md_inside && stream.current().indexOf(">")!=-1) { - state.f = inlineNormal; - state.block = blockNormal; - state.htmlState.context = undefined; - } - return style; - } - - - // Inline - function getType(state) { - var styles = []; - - if (state.strong) { styles.push(state.em ? emstrong : strong); } - else if (state.em) { styles.push(em); } - - if (state.code) { styles.push(code); } - - if (state.header) { styles.push(header); } - if (state.quote) { styles.push(quote); } - if (state.list !== false) { styles.push(list); } - - return styles.length ? styles.join(' ') : null; - } - - function handleText(stream, state) { - if (stream.match(textRE, true)) { - return getType(state); - } - return undefined; - } - - function inlineNormal(stream, state) { - var style = state.text(stream, state); - if (typeof style !== 'undefined') - return style; - - if (state.list) { // List marker (*, +, -, 1., etc) - state.list = null; - return list; - } - - var ch = stream.next(); - - if (ch === '\\') { - stream.next(); - return getType(state); - } - - // Matches link titles present on next line - if (state.linkTitle) { - state.linkTitle = false; - var matchCh = ch; - if (ch === '(') { - matchCh = ')'; - } - matchCh = (matchCh+'').replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1"); - var regex = '^\\s*(?:[^' + matchCh + '\\\\]+|\\\\\\\\|\\\\.)' + matchCh; - if (stream.match(new RegExp(regex), true)) { - return linkhref; - } - } - - if (ch === '`') { - var t = getType(state); - var before = stream.pos; - stream.eatWhile('`'); - var difference = 1 + stream.pos - before; - if (!state.code) { - codeDepth = difference; - state.code = true; - return getType(state); - } else { - if (difference === codeDepth) { // Must be exact - state.code = false; - return t; - } - return getType(state); - } - } else if (state.code) { - return getType(state); - } - - if (ch === '[' && stream.match(/.*\] ?(?:\(|\[)/, false)) { - return switchInline(stream, state, linkText); - } - - if (ch === '<' && stream.match(/^(https?|ftps?):\/\/(?:[^\\>]|\\.)+>/, true)) { - return switchInline(stream, state, inlineElement(linkinline, '>')); - } - - if (ch === '<' && stream.match(/^[^> \\]+@(?:[^\\>]|\\.)+>/, true)) { - return switchInline(stream, state, inlineElement(linkemail, '>')); - } - - if (ch === '<' && stream.match(/^\w/, false)) { - var md_inside = false; - if (stream.string.indexOf(">")!=-1) { - var atts = stream.string.substring(1,stream.string.indexOf(">")); - if (/markdown\s*=\s*('|"){0,1}1('|"){0,1}/.test(atts)) { - state.md_inside = true; - } - } - stream.backUp(1); - return switchBlock(stream, state, htmlBlock); - } - - if (ch === '<' && stream.match(/^\/\w*?>/)) { - state.md_inside = false; - return "tag"; - } - - var t = getType(state); - if (ch === '*' || ch === '_') { - if (state.strong === ch && stream.eat(ch)) { // Remove STRONG - state.strong = false; - return t; - } else if (!state.strong && stream.eat(ch)) { // Add STRONG - state.strong = ch; - return getType(state); - } else if (state.em === ch) { // Remove EM - state.em = false; - return t; - } else if (!state.em) { // Add EM - state.em = ch; - return getType(state); - } - } else if (ch === ' ') { - if (stream.eat('*') || stream.eat('_')) { // Probably surrounded by spaces - if (stream.peek() === ' ') { // Surrounded by spaces, ignore - return getType(state); - } else { // Not surrounded by spaces, back up pointer - stream.backUp(1); - } - } - } - - return getType(state); - } - - function linkText(stream, state) { - while (!stream.eol()) { - var ch = stream.next(); - if (ch === '\\') stream.next(); - if (ch === ']') { - state.inline = state.f = linkHref; - return linktext; - } - } - return linktext; - } - - function linkHref(stream, state) { - // Check if space, and return NULL if so (to avoid marking the space) - if(stream.eatSpace()){ - return null; - } - var ch = stream.next(); - if (ch === '(' || ch === '[') { - return switchInline(stream, state, inlineElement(linkhref, ch === '(' ? ')' : ']')); - } - return 'error'; - } - - function footnoteLink(stream, state) { - if (stream.match(/^[^\]]*\]:/, true)) { - state.f = footnoteUrl; - return linktext; - } - return switchInline(stream, state, inlineNormal); - } - - function footnoteUrl(stream, state) { - // Check if space, and return NULL if so (to avoid marking the space) - if(stream.eatSpace()){ - return null; - } - // Match URL - stream.match(/^[^\s]+/, true); - // Check for link title - if (stream.peek() === undefined) { // End of line, set flag to check next line - state.linkTitle = true; - } else { // More content on line, check if link title - stream.match(/^(?:\s+(?:"(?:[^"\\]|\\\\|\\.)+"|'(?:[^'\\]|\\\\|\\.)+'|\((?:[^)\\]|\\\\|\\.)+\)))?/, true); - } - state.f = state.inline = inlineNormal; - return linkhref; - } - - function inlineRE(endChar) { - if (!inlineRE[endChar]) { - // Escape endChar for RegExp (taken from http://stackoverflow.com/a/494122/526741) - endChar = (endChar+'').replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1"); - // Match any non-endChar, escaped character, as well as the closing - // endChar. - inlineRE[endChar] = new RegExp('^(?:[^\\\\]+?|\\\\.)*?(' + endChar + ')'); - } - return inlineRE[endChar]; - } - - function inlineElement(type, endChar, next) { - next = next || inlineNormal; - return function(stream, state) { - stream.match(inlineRE(endChar)); - state.inline = state.f = next; - return type; - }; - } - - return { - startState: function() { - return { - f: blockNormal, - - block: blockNormal, - htmlState: CodeMirror.startState(htmlMode), - indentation: 0, - - inline: inlineNormal, - text: handleText, - linkTitle: false, - em: false, - strong: false, - header: false, - list: false, - quote: false - }; - }, - - copyState: function(s) { - return { - f: s.f, - - block: s.block, - htmlState: CodeMirror.copyState(htmlMode, s.htmlState), - indentation: s.indentation, - - inline: s.inline, - text: s.text, - linkTitle: s.linkTitle, - em: s.em, - strong: s.strong, - header: s.header, - list: s.list, - quote: s.quote, - md_inside: s.md_inside - }; - }, - - token: function(stream, state) { - if (stream.sol()) { - if (stream.match(/^\s*$/, true)) { - prevLineHasContent = false; - return blankLine(state); - } else { - if(thisLineHasContent){ - prevLineHasContent = true; - thisLineHasContent = false; - } - thisLineHasContent = true; - } - - // Reset state.header - state.header = false; - - state.f = state.block; - var indentation = stream.match(/^\s*/, true)[0].replace(/\t/g, ' ').length; - state.indentationDiff = indentation - state.indentation; - state.indentation = indentation; - if (indentation > 0) { return null; } - } - return state.f(stream, state); - }, - - blankLine: blankLine, - - getType: getType - }; - -}, "xml"); - -CodeMirror.defineMIME("text/x-markdown", "markdown"); diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/markdown/test.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/markdown/test.js deleted file mode 100644 index 5245fb72fc..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/markdown/test.js +++ /dev/null @@ -1,1084 +0,0 @@ -// Initiate ModeTest and set defaults -var MT = ModeTest; -MT.modeName = 'markdown'; -MT.modeOptions = {}; - -MT.testMode( - 'plainText', - 'foo', - [ - null, 'foo' - ] -); - -// Code blocks using 4 spaces (regardless of CodeMirror.tabSize value) -MT.testMode( - 'codeBlocksUsing4Spaces', - ' foo', - [ - null, ' ', - 'comment', 'foo' - ] -); - -// Code blocks using 1 tab (regardless of CodeMirror.indentWithTabs value) -MT.testMode( - 'codeBlocksUsing1Tab', - '\tfoo', - [ - null, '\t', - 'comment', 'foo' - ] -); - -// Inline code using backticks -MT.testMode( - 'inlineCodeUsingBackticks', - 'foo `bar`', - [ - null, 'foo ', - 'comment', '`bar`' - ] -); - -// Unclosed backticks -// Instead of simply marking as CODE, it would be nice to have an -// incomplete flag for CODE, that is styled slightly different. -MT.testMode( - 'unclosedBackticks', - 'foo `bar', - [ - null, 'foo ', - 'comment', '`bar' - ] -); - -// Per documentation: "To include a literal backtick character within a -// code span, you can use multiple backticks as the opening and closing -// delimiters" -MT.testMode( - 'doubleBackticks', - '``foo ` bar``', - [ - 'comment', '``foo ` bar``' - ] -); - -// Tests based on Dingus -// http://daringfireball.net/projects/markdown/dingus -// -// Multiple backticks within an inline code block -MT.testMode( - 'consecutiveBackticks', - '`foo```bar`', - [ - 'comment', '`foo```bar`' - ] -); -// Multiple backticks within an inline code block with a second code block -MT.testMode( - 'consecutiveBackticks', - '`foo```bar` hello `world`', - [ - 'comment', '`foo```bar`', - null, ' hello ', - 'comment', '`world`' - ] -); -// Unclosed with several different groups of backticks -MT.testMode( - 'unclosedBackticks', - '``foo ``` bar` hello', - [ - 'comment', '``foo ``` bar` hello' - ] -); -// Closed with several different groups of backticks -MT.testMode( - 'closedBackticks', - '``foo ``` bar` hello`` world', - [ - 'comment', '``foo ``` bar` hello``', - null, ' world' - ] -); - -// atx headers -// http://daringfireball.net/projects/markdown/syntax#header -// -// H1 -MT.testMode( - 'atxH1', - '# foo', - [ - 'header', '# foo' - ] -); -// H2 -MT.testMode( - 'atxH2', - '## foo', - [ - 'header', '## foo' - ] -); -// H3 -MT.testMode( - 'atxH3', - '### foo', - [ - 'header', '### foo' - ] -); -// H4 -MT.testMode( - 'atxH4', - '#### foo', - [ - 'header', '#### foo' - ] -); -// H5 -MT.testMode( - 'atxH5', - '##### foo', - [ - 'header', '##### foo' - ] -); -// H6 -MT.testMode( - 'atxH6', - '###### foo', - [ - 'header', '###### foo' - ] -); -// H6 - 7x '#' should still be H6, per Dingus -// http://daringfireball.net/projects/markdown/dingus -MT.testMode( - 'atxH6NotH7', - '####### foo', - [ - 'header', '####### foo' - ] -); - -// Setext headers - H1, H2 -// Per documentation, "Any number of underlining =’s or -’s will work." -// http://daringfireball.net/projects/markdown/syntax#header -// Ideally, the text would be marked as `header` as well, but this is -// not really feasible at the moment. So, instead, we're testing against -// what works today, to avoid any regressions. -// -// Check if single underlining = works -MT.testMode( - 'setextH1', - 'foo\n=', - [ - null, 'foo', - 'header', '=' - ] -); -// Check if 3+ ='s work -MT.testMode( - 'setextH1', - 'foo\n===', - [ - null, 'foo', - 'header', '===' - ] -); -// Check if single underlining - works -MT.testMode( - 'setextH2', - 'foo\n-', - [ - null, 'foo', - 'header', '-' - ] -); -// Check if 3+ -'s work -MT.testMode( - 'setextH2', - 'foo\n---', - [ - null, 'foo', - 'header', '---' - ] -); - -// Single-line blockquote with trailing space -MT.testMode( - 'blockquoteSpace', - '> foo', - [ - 'quote', '> foo' - ] -); - -// Single-line blockquote -MT.testMode( - 'blockquoteNoSpace', - '>foo', - [ - 'quote', '>foo' - ] -); - -// Single-line blockquote followed by normal paragraph -MT.testMode( - 'blockquoteThenParagraph', - '>foo\n\nbar', - [ - 'quote', '>foo', - null, 'bar' - ] -); - -// Multi-line blockquote (lazy mode) -MT.testMode( - 'multiBlockquoteLazy', - '>foo\nbar', - [ - 'quote', '>foo', - 'quote', 'bar' - ] -); - -// Multi-line blockquote followed by normal paragraph (lazy mode) -MT.testMode( - 'multiBlockquoteLazyThenParagraph', - '>foo\nbar\n\nhello', - [ - 'quote', '>foo', - 'quote', 'bar', - null, 'hello' - ] -); - -// Multi-line blockquote (non-lazy mode) -MT.testMode( - 'multiBlockquote', - '>foo\n>bar', - [ - 'quote', '>foo', - 'quote', '>bar' - ] -); - -// Multi-line blockquote followed by normal paragraph (non-lazy mode) -MT.testMode( - 'multiBlockquoteThenParagraph', - '>foo\n>bar\n\nhello', - [ - 'quote', '>foo', - 'quote', '>bar', - null, 'hello' - ] -); - -// Check list types -MT.testMode( - 'listAsterisk', - '* foo\n* bar', - [ - 'string', '* foo', - 'string', '* bar' - ] -); -MT.testMode( - 'listPlus', - '+ foo\n+ bar', - [ - 'string', '+ foo', - 'string', '+ bar' - ] -); -MT.testMode( - 'listDash', - '- foo\n- bar', - [ - 'string', '- foo', - 'string', '- bar' - ] -); -MT.testMode( - 'listNumber', - '1. foo\n2. bar', - [ - 'string', '1. foo', - 'string', '2. bar' - ] -); - -// Formatting in lists (*) -MT.testMode( - 'listAsteriskFormatting', - '* *foo* bar\n\n* **foo** bar\n\n* ***foo*** bar\n\n* `foo` bar', - [ - 'string', '* ', - 'string em', '*foo*', - 'string', ' bar', - 'string', '* ', - 'string strong', '**foo**', - 'string', ' bar', - 'string', '* ', - 'string strong', '**', - 'string emstrong', '*foo**', - 'string em', '*', - 'string', ' bar', - 'string', '* ', - 'string comment', '`foo`', - 'string', ' bar' - ] -); -// Formatting in lists (+) -MT.testMode( - 'listPlusFormatting', - '+ *foo* bar\n\n+ **foo** bar\n\n+ ***foo*** bar\n\n+ `foo` bar', - [ - 'string', '+ ', - 'string em', '*foo*', - 'string', ' bar', - 'string', '+ ', - 'string strong', '**foo**', - 'string', ' bar', - 'string', '+ ', - 'string strong', '**', - 'string emstrong', '*foo**', - 'string em', '*', - 'string', ' bar', - 'string', '+ ', - 'string comment', '`foo`', - 'string', ' bar' - ] -); -// Formatting in lists (-) -MT.testMode( - 'listDashFormatting', - '- *foo* bar\n\n- **foo** bar\n\n- ***foo*** bar\n\n- `foo` bar', - [ - 'string', '- ', - 'string em', '*foo*', - 'string', ' bar', - 'string', '- ', - 'string strong', '**foo**', - 'string', ' bar', - 'string', '- ', - 'string strong', '**', - 'string emstrong', '*foo**', - 'string em', '*', - 'string', ' bar', - 'string', '- ', - 'string comment', '`foo`', - 'string', ' bar' - ] -); -// Formatting in lists (1.) -MT.testMode( - 'listNumberFormatting', - '1. *foo* bar\n\n2. **foo** bar\n\n3. ***foo*** bar\n\n4. `foo` bar', - [ - 'string', '1. ', - 'string em', '*foo*', - 'string', ' bar', - 'string', '2. ', - 'string strong', '**foo**', - 'string', ' bar', - 'string', '3. ', - 'string strong', '**', - 'string emstrong', '*foo**', - 'string em', '*', - 'string', ' bar', - 'string', '4. ', - 'string comment', '`foo`', - 'string', ' bar' - ] -); - -// Paragraph lists -MT.testMode( - 'listParagraph', - '* foo\n\n* bar', - [ - 'string', '* foo', - 'string', '* bar' - ] -); - -// Multi-paragraph lists -// -// 4 spaces -MT.testMode( - 'listMultiParagraph', - '* foo\n\n* bar\n\n hello', - [ - 'string', '* foo', - 'string', '* bar', - null, ' ', - 'string', 'hello' - ] -); -// 4 spaces, extra blank lines (should still be list, per Dingus) -MT.testMode( - 'listMultiParagraphExtra', - '* foo\n\n* bar\n\n\n hello', - [ - 'string', '* foo', - 'string', '* bar', - null, ' ', - 'string', 'hello' - ] -); -// 4 spaces, plus 1 space (should still be list, per Dingus) -MT.testMode( - 'listMultiParagraphExtraSpace', - '* foo\n\n* bar\n\n hello\n\n world', - [ - 'string', '* foo', - 'string', '* bar', - null, ' ', - 'string', 'hello', - null, ' ', - 'string', 'world' - ] -); -// 1 tab -MT.testMode( - 'listTab', - '* foo\n\n* bar\n\n\thello', - [ - 'string', '* foo', - 'string', '* bar', - null, '\t', - 'string', 'hello' - ] -); -// No indent -MT.testMode( - 'listNoIndent', - '* foo\n\n* bar\n\nhello', - [ - 'string', '* foo', - 'string', '* bar', - null, 'hello' - ] -); -// Blockquote -MT.testMode( - 'blockquote', - '* foo\n\n* bar\n\n > hello', - [ - 'string', '* foo', - 'string', '* bar', - null, ' ', - 'string quote', '> hello' - ] -); -// Code block -MT.testMode( - 'blockquoteCode', - '* foo\n\n* bar\n\n > hello\n\n world', - [ - 'string', '* foo', - 'string', '* bar', - null, ' ', - 'comment', '> hello', - null, ' ', - 'string', 'world' - ] -); -// Code block followed by text -MT.testMode( - 'blockquoteCodeText', - '* foo\n\n bar\n\n hello\n\n world', - [ - 'string', '* foo', - null, ' ', - 'string', 'bar', - null, ' ', - 'comment', 'hello', - null, ' ', - 'string', 'world' - ] -); - -// Nested list -// -// * -MT.testMode( - 'listAsteriskNested', - '* foo\n\n * bar', - [ - 'string', '* foo', - null, ' ', - 'string', '* bar' - ] -); -// + -MT.testMode( - 'listPlusNested', - '+ foo\n\n + bar', - [ - 'string', '+ foo', - null, ' ', - 'string', '+ bar' - ] -); -// - -MT.testMode( - 'listDashNested', - '- foo\n\n - bar', - [ - 'string', '- foo', - null, ' ', - 'string', '- bar' - ] -); -// 1. -MT.testMode( - 'listNumberNested', - '1. foo\n\n 2. bar', - [ - 'string', '1. foo', - null, ' ', - 'string', '2. bar' - ] -); -// Mixed -MT.testMode( - 'listMixed', - '* foo\n\n + bar\n\n - hello\n\n 1. world', - [ - 'string', '* foo', - null, ' ', - 'string', '+ bar', - null, ' ', - 'string', '- hello', - null, ' ', - 'string', '1. world' - ] -); -// Blockquote -MT.testMode( - 'listBlockquote', - '* foo\n\n + bar\n\n > hello', - [ - 'string', '* foo', - null, ' ', - 'string', '+ bar', - null, ' ', - 'quote string', '> hello' - ] -); -// Code -MT.testMode( - 'listCode', - '* foo\n\n + bar\n\n hello', - [ - 'string', '* foo', - null, ' ', - 'string', '+ bar', - null, ' ', - 'comment', 'hello' - ] -); -// Code followed by text -MT.testMode( - 'listCodeText', - '* foo\n\n bar\n\nhello', - [ - 'string', '* foo', - null, ' ', - 'comment', 'bar', - null, 'hello' - ] -); - -// Following tests directly from official Markdown documentation -// http://daringfireball.net/projects/markdown/syntax#hr -MT.testMode( - 'hrSpace', - '* * *', - [ - 'hr', '* * *' - ] -); - -MT.testMode( - 'hr', - '***', - [ - 'hr', '***' - ] -); - -MT.testMode( - 'hrLong', - '*****', - [ - 'hr', '*****' - ] -); - -MT.testMode( - 'hrSpaceDash', - '- - -', - [ - 'hr', '- - -' - ] -); - -MT.testMode( - 'hrDashLong', - '---------------------------------------', - [ - 'hr', '---------------------------------------' - ] -); - -// Inline link with title -MT.testMode( - 'linkTitle', - '[foo](http://example.com/ "bar") hello', - [ - 'link', '[foo]', - 'string', '(http://example.com/ "bar")', - null, ' hello' - ] -); - -// Inline link without title -MT.testMode( - 'linkNoTitle', - '[foo](http://example.com/) bar', - [ - 'link', '[foo]', - 'string', '(http://example.com/)', - null, ' bar' - ] -); - -// Reference-style links -MT.testMode( - 'linkReference', - '[foo][bar] hello', - [ - 'link', '[foo]', - 'string', '[bar]', - null, ' hello' - ] -); - -// Reference-style links with optional space separator (per docuentation) -// "You can optionally use a space to separate the sets of brackets" -MT.testMode( - 'linkReferenceSpace', - '[foo] [bar] hello', - [ - 'link', '[foo]', - null, ' ', - 'string', '[bar]', - null, ' hello' - ] -); -// Should only allow a single space ("...use *a* space...") -MT.testMode( - 'linkReferenceDoubleSpace', - '[foo] [bar] hello', - [ - null, '[foo] [bar] hello' - ] -); - -// Reference-style links with implicit link name -MT.testMode( - 'linkImplicit', - '[foo][] hello', - [ - 'link', '[foo]', - 'string', '[]', - null, ' hello' - ] -); - -// @todo It would be nice if, at some point, the document was actually -// checked to see if the referenced link exists - -// Link label, for reference-style links (taken from documentation) -// -// No title -MT.testMode( - 'labelNoTitle', - '[foo]: http://example.com/', - [ - 'link', '[foo]:', - null, ' ', - 'string', 'http://example.com/' - ] -); -// Space in ID and title -MT.testMode( - 'labelSpaceTitle', - '[foo bar]: http://example.com/ "hello"', - [ - 'link', '[foo bar]:', - null, ' ', - 'string', 'http://example.com/ "hello"' - ] -); -// Double title -MT.testMode( - 'labelDoubleTitle', - '[foo bar]: http://example.com/ "hello" "world"', - [ - 'link', '[foo bar]:', - null, ' ', - 'string', 'http://example.com/ "hello"', - null, ' "world"' - ] -); -// Double quotes around title -MT.testMode( - 'labelTitleDoubleQuotes', - '[foo]: http://example.com/ "bar"', - [ - 'link', '[foo]:', - null, ' ', - 'string', 'http://example.com/ "bar"' - ] -); -// Single quotes around title -MT.testMode( - 'labelTitleSingleQuotes', - '[foo]: http://example.com/ \'bar\'', - [ - 'link', '[foo]:', - null, ' ', - 'string', 'http://example.com/ \'bar\'' - ] -); -// Parentheses around title -MT.testMode( - 'labelTitleParenthese', - '[foo]: http://example.com/ (bar)', - [ - 'link', '[foo]:', - null, ' ', - 'string', 'http://example.com/ (bar)' - ] -); -// Invalid title -MT.testMode( - 'labelTitleInvalid', - '[foo]: http://example.com/ bar', - [ - 'link', '[foo]:', - null, ' ', - 'string', 'http://example.com/', - null, ' bar' - ] -); -// Angle brackets around URL -MT.testMode( - 'labelLinkAngleBrackets', - '[foo]: "bar"', - [ - 'link', '[foo]:', - null, ' ', - 'string', ' "bar"' - ] -); -// Title on next line per documentation (double quotes) -MT.testMode( - 'labelTitleNextDoubleQuotes', - '[foo]: http://example.com/\n"bar" hello', - [ - 'link', '[foo]:', - null, ' ', - 'string', 'http://example.com/', - 'string', '"bar"', - null, ' hello' - ] -); -// Title on next line per documentation (single quotes) -MT.testMode( - 'labelTitleNextSingleQuotes', - '[foo]: http://example.com/\n\'bar\' hello', - [ - 'link', '[foo]:', - null, ' ', - 'string', 'http://example.com/', - 'string', '\'bar\'', - null, ' hello' - ] -); -// Title on next line per documentation (parentheses) -MT.testMode( - 'labelTitleNextParenthese', - '[foo]: http://example.com/\n(bar) hello', - [ - 'link', '[foo]:', - null, ' ', - 'string', 'http://example.com/', - 'string', '(bar)', - null, ' hello' - ] -); -// Title on next line per documentation (mixed) -MT.testMode( - 'labelTitleNextMixed', - '[foo]: http://example.com/\n(bar" hello', - [ - 'link', '[foo]:', - null, ' ', - 'string', 'http://example.com/', - null, '(bar" hello' - ] -); - -// Automatic links -MT.testMode( - 'linkWeb', - ' foo', - [ - 'link', '', - null, ' foo' - ] -); - -// Automatic email links -MT.testMode( - 'linkEmail', - ' foo', - [ - 'link', '', - null, ' foo' - ] -); - -// Single asterisk -MT.testMode( - 'emAsterisk', - '*foo* bar', - [ - 'em', '*foo*', - null, ' bar' - ] -); - -// Single underscore -MT.testMode( - 'emUnderscore', - '_foo_ bar', - [ - 'em', '_foo_', - null, ' bar' - ] -); - -// Emphasis characters within a word -MT.testMode( - 'emInWordAsterisk', - 'foo*bar*hello', - [ - null, 'foo', - 'em', '*bar*', - null, 'hello' - ] -); -MT.testMode( - 'emInWordUnderscore', - 'foo_bar_hello', - [ - null, 'foo', - 'em', '_bar_', - null, 'hello' - ] -); -// Per documentation: "...surround an * or _ with spaces, it’ll be -// treated as a literal asterisk or underscore." -// -// Inside EM -MT.testMode( - 'emEscapedBySpaceIn', - 'foo _bar _ hello_ world', - [ - null, 'foo ', - 'em', '_bar _ hello_', - null, ' world' - ] -); -// Outside EM -MT.testMode( - 'emEscapedBySpaceOut', - 'foo _ bar_hello_world', - [ - null, 'foo _ bar', - 'em', '_hello_', - null, 'world' - ] -); - -// Unclosed emphasis characters -// Instead of simply marking as EM / STRONG, it would be nice to have an -// incomplete flag for EM and STRONG, that is styled slightly different. -MT.testMode( - 'emIncompleteAsterisk', - 'foo *bar', - [ - null, 'foo ', - 'em', '*bar' - ] -); -MT.testMode( - 'emIncompleteUnderscore', - 'foo _bar', - [ - null, 'foo ', - 'em', '_bar' - ] -); - -// Double asterisk -MT.testMode( - 'strongAsterisk', - '**foo** bar', - [ - 'strong', '**foo**', - null, ' bar' - ] -); - -// Double underscore -MT.testMode( - 'strongUnderscore', - '__foo__ bar', - [ - 'strong', '__foo__', - null, ' bar' - ] -); - -// Triple asterisk -MT.testMode( - 'emStrongAsterisk', - '*foo**bar*hello** world', - [ - 'em', '*foo', - 'emstrong', '**bar*', - 'strong', 'hello**', - null, ' world' - ] -); - -// Triple underscore -MT.testMode( - 'emStrongUnderscore', - '_foo__bar_hello__ world', - [ - 'em', '_foo', - 'emstrong', '__bar_', - 'strong', 'hello__', - null, ' world' - ] -); - -// Triple mixed -// "...same character must be used to open and close an emphasis span."" -MT.testMode( - 'emStrongMixed', - '_foo**bar*hello__ world', - [ - 'em', '_foo', - 'emstrong', '**bar*hello__ world' - ] -); - -MT.testMode( - 'emStrongMixed', - '*foo__bar_hello** world', - [ - 'em', '*foo', - 'emstrong', '__bar_hello** world' - ] -); - -// These characters should be escaped: -// \ backslash -// ` backtick -// * asterisk -// _ underscore -// {} curly braces -// [] square brackets -// () parentheses -// # hash mark -// + plus sign -// - minus sign (hyphen) -// . dot -// ! exclamation mark -// -// Backtick (code) -MT.testMode( - 'escapeBacktick', - 'foo \\`bar\\`', - [ - null, 'foo \\`bar\\`' - ] -); -MT.testMode( - 'doubleEscapeBacktick', - 'foo \\\\`bar\\\\`', - [ - null, 'foo \\\\', - 'comment', '`bar\\\\`' - ] -); -// Asterisk (em) -MT.testMode( - 'escapeAsterisk', - 'foo \\*bar\\*', - [ - null, 'foo \\*bar\\*' - ] -); -MT.testMode( - 'doubleEscapeAsterisk', - 'foo \\\\*bar\\\\*', - [ - null, 'foo \\\\', - 'em', '*bar\\\\*' - ] -); -// Underscore (em) -MT.testMode( - 'escapeUnderscore', - 'foo \\_bar\\_', - [ - null, 'foo \\_bar\\_' - ] -); -MT.testMode( - 'doubleEscapeUnderscore', - 'foo \\\\_bar\\\\_', - [ - null, 'foo \\\\', - 'em', '_bar\\\\_' - ] -); -// Hash mark (headers) -MT.testMode( - 'escapeHash', - '\\# foo', - [ - null, '\\# foo' - ] -); -MT.testMode( - 'doubleEscapeHash', - '\\\\# foo', - [ - null, '\\\\# foo' - ] -); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/mysql/index.html b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/mysql/index.html deleted file mode 100644 index 8bf72fe44d..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/mysql/index.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - - CodeMirror: MySQL mode - - - - - - - -

    CodeMirror: MySQL mode

    -
    - - -

    MIME types defined: text/x-mysql.

    - - - diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/mysql/mysql.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/mysql/mysql.js deleted file mode 100644 index fc8d799d36..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/mysql/mysql.js +++ /dev/null @@ -1,186 +0,0 @@ -/* - * MySQL Mode for CodeMirror 2 by MySQL-Tools - * @author James Thorne (partydroid) - * @link http://github.com/partydroid/MySQL-Tools - * @link http://mysqltools.org - * @version 02/Jan/2012 -*/ -CodeMirror.defineMode("mysql", function(config) { - var indentUnit = config.indentUnit; - var curPunc; - - function wordRegexp(words) { - return new RegExp("^(?:" + words.join("|") + ")$", "i"); - } - var ops = wordRegexp(["str", "lang", "langmatches", "datatype", "bound", "sameterm", "isiri", "isuri", - "isblank", "isliteral", "union", "a"]); - var keywords = wordRegexp([ - ('ACCESSIBLE'),('ALTER'),('AS'),('BEFORE'),('BINARY'),('BY'),('CASE'),('CHARACTER'),('COLUMN'),('CONTINUE'),('CROSS'),('CURRENT_TIMESTAMP'),('DATABASE'),('DAY_MICROSECOND'),('DEC'),('DEFAULT'), - ('DESC'),('DISTINCT'),('DOUBLE'),('EACH'),('ENCLOSED'),('EXIT'),('FETCH'),('FLOAT8'),('FOREIGN'),('GRANT'),('HIGH_PRIORITY'),('HOUR_SECOND'),('IN'),('INNER'),('INSERT'),('INT2'),('INT8'), - ('INTO'),('JOIN'),('KILL'),('LEFT'),('LINEAR'),('LOCALTIME'),('LONG'),('LOOP'),('MATCH'),('MEDIUMTEXT'),('MINUTE_SECOND'),('NATURAL'),('NULL'),('OPTIMIZE'),('OR'),('OUTER'),('PRIMARY'), - ('RANGE'),('READ_WRITE'),('REGEXP'),('REPEAT'),('RESTRICT'),('RIGHT'),('SCHEMAS'),('SENSITIVE'),('SHOW'),('SPECIFIC'),('SQLSTATE'),('SQL_CALC_FOUND_ROWS'),('STARTING'),('TERMINATED'), - ('TINYINT'),('TRAILING'),('UNDO'),('UNLOCK'),('USAGE'),('UTC_DATE'),('VALUES'),('VARCHARACTER'),('WHERE'),('WRITE'),('ZEROFILL'),('ALL'),('AND'),('ASENSITIVE'),('BIGINT'),('BOTH'),('CASCADE'), - ('CHAR'),('COLLATE'),('CONSTRAINT'),('CREATE'),('CURRENT_TIME'),('CURSOR'),('DAY_HOUR'),('DAY_SECOND'),('DECLARE'),('DELETE'),('DETERMINISTIC'),('DIV'),('DUAL'),('ELSEIF'),('EXISTS'),('FALSE'), - ('FLOAT4'),('FORCE'),('FULLTEXT'),('HAVING'),('HOUR_MINUTE'),('IGNORE'),('INFILE'),('INSENSITIVE'),('INT1'),('INT4'),('INTERVAL'),('ITERATE'),('KEYS'),('LEAVE'),('LIMIT'),('LOAD'),('LOCK'), - ('LONGTEXT'),('MASTER_SSL_VERIFY_SERVER_CERT'),('MEDIUMINT'),('MINUTE_MICROSECOND'),('MODIFIES'),('NO_WRITE_TO_BINLOG'),('ON'),('OPTIONALLY'),('OUT'),('PRECISION'),('PURGE'),('READS'), - ('REFERENCES'),('RENAME'),('REQUIRE'),('REVOKE'),('SCHEMA'),('SELECT'),('SET'),('SPATIAL'),('SQLEXCEPTION'),('SQL_BIG_RESULT'),('SSL'),('TABLE'),('TINYBLOB'),('TO'),('TRUE'),('UNIQUE'), - ('UPDATE'),('USING'),('UTC_TIMESTAMP'),('VARCHAR'),('WHEN'),('WITH'),('YEAR_MONTH'),('ADD'),('ANALYZE'),('ASC'),('BETWEEN'),('BLOB'),('CALL'),('CHANGE'),('CHECK'),('CONDITION'),('CONVERT'), - ('CURRENT_DATE'),('CURRENT_USER'),('DATABASES'),('DAY_MINUTE'),('DECIMAL'),('DELAYED'),('DESCRIBE'),('DISTINCTROW'),('DROP'),('ELSE'),('ESCAPED'),('EXPLAIN'),('FLOAT'),('FOR'),('FROM'), - ('GROUP'),('HOUR_MICROSECOND'),('IF'),('INDEX'),('INOUT'),('INT'),('INT3'),('INTEGER'),('IS'),('KEY'),('LEADING'),('LIKE'),('LINES'),('LOCALTIMESTAMP'),('LONGBLOB'),('LOW_PRIORITY'), - ('MEDIUMBLOB'),('MIDDLEINT'),('MOD'),('NOT'),('NUMERIC'),('OPTION'),('ORDER'),('OUTFILE'),('PROCEDURE'),('READ'),('REAL'),('RELEASE'),('REPLACE'),('RETURN'),('RLIKE'),('SECOND_MICROSECOND'), - ('SEPARATOR'),('SMALLINT'),('SQL'),('SQLWARNING'),('SQL_SMALL_RESULT'),('STRAIGHT_JOIN'),('THEN'),('TINYTEXT'),('TRIGGER'),('UNION'),('UNSIGNED'),('USE'),('UTC_TIME'),('VARBINARY'),('VARYING'), - ('WHILE'),('XOR'),('FULL'),('COLUMNS'),('MIN'),('MAX'),('STDEV'),('COUNT') - ]); - var operatorChars = /[*+\-<>=&|]/; - - function tokenBase(stream, state) { - var ch = stream.next(); - curPunc = null; - if (ch == "$" || ch == "?") { - stream.match(/^[\w\d]*/); - return "variable-2"; - } - else if (ch == "<" && !stream.match(/^[\s\u00a0=]/, false)) { - stream.match(/^[^\s\u00a0>]*>?/); - return "atom"; - } - else if (ch == "\"" || ch == "'") { - state.tokenize = tokenLiteral(ch); - return state.tokenize(stream, state); - } - else if (ch == "`") { - state.tokenize = tokenOpLiteral(ch); - return state.tokenize(stream, state); - } - else if (/[{}\(\),\.;\[\]]/.test(ch)) { - curPunc = ch; - return null; - } - else if (ch == "-") { - var ch2 = stream.next(); - if (ch2=="-") { - stream.skipToEnd(); - return "comment"; - } - } - else if (operatorChars.test(ch)) { - stream.eatWhile(operatorChars); - return null; - } - else if (ch == ":") { - stream.eatWhile(/[\w\d\._\-]/); - return "atom"; - } - else { - stream.eatWhile(/[_\w\d]/); - if (stream.eat(":")) { - stream.eatWhile(/[\w\d_\-]/); - return "atom"; - } - var word = stream.current(), type; - if (ops.test(word)) - return null; - else if (keywords.test(word)) - return "keyword"; - else - return "variable"; - } - } - - function tokenLiteral(quote) { - return function(stream, state) { - var escaped = false, ch; - while ((ch = stream.next()) != null) { - if (ch == quote && !escaped) { - state.tokenize = tokenBase; - break; - } - escaped = !escaped && ch == "\\"; - } - return "string"; - }; - } - - function tokenOpLiteral(quote) { - return function(stream, state) { - var escaped = false, ch; - while ((ch = stream.next()) != null) { - if (ch == quote && !escaped) { - state.tokenize = tokenBase; - break; - } - escaped = !escaped && ch == "\\"; - } - return "variable-2"; - }; - } - - - function pushContext(state, type, col) { - state.context = {prev: state.context, indent: state.indent, col: col, type: type}; - } - function popContext(state) { - state.indent = state.context.indent; - state.context = state.context.prev; - } - - return { - startState: function(base) { - return {tokenize: tokenBase, - context: null, - indent: 0, - col: 0}; - }, - - token: function(stream, state) { - if (stream.sol()) { - if (state.context && state.context.align == null) state.context.align = false; - state.indent = stream.indentation(); - } - if (stream.eatSpace()) return null; - var style = state.tokenize(stream, state); - - if (style != "comment" && state.context && state.context.align == null && state.context.type != "pattern") { - state.context.align = true; - } - - if (curPunc == "(") pushContext(state, ")", stream.column()); - else if (curPunc == "[") pushContext(state, "]", stream.column()); - else if (curPunc == "{") pushContext(state, "}", stream.column()); - else if (/[\]\}\)]/.test(curPunc)) { - while (state.context && state.context.type == "pattern") popContext(state); - if (state.context && curPunc == state.context.type) popContext(state); - } - else if (curPunc == "." && state.context && state.context.type == "pattern") popContext(state); - else if (/atom|string|variable/.test(style) && state.context) { - if (/[\}\]]/.test(state.context.type)) - pushContext(state, "pattern", stream.column()); - else if (state.context.type == "pattern" && !state.context.align) { - state.context.align = true; - state.context.col = stream.column(); - } - } - - return style; - }, - - indent: function(state, textAfter) { - var firstChar = textAfter && textAfter.charAt(0); - var context = state.context; - if (/[\]\}]/.test(firstChar)) - while (context && context.type == "pattern") context = context.prev; - - var closing = context && firstChar == context.type; - if (!context) - return 0; - else if (context.type == "pattern") - return context.col; - else if (context.align) - return context.col + (closing ? 0 : 1); - else - return context.indent + (closing ? 0 : indentUnit); - } - }; -}); - -CodeMirror.defineMIME("text/x-mysql", "mysql"); diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/ntriples/index.html b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/ntriples/index.html deleted file mode 100644 index 33e92dea7b..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/ntriples/index.html +++ /dev/null @@ -1,33 +0,0 @@ - - - - - CodeMirror: NTriples mode - - - - - - - -

    CodeMirror: NTriples mode

    -
    - - - - -

    MIME types defined: text/n-triples.

    - - diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/ntriples/ntriples.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/ntriples/ntriples.js deleted file mode 100644 index c66a6930fa..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/ntriples/ntriples.js +++ /dev/null @@ -1,172 +0,0 @@ -/********************************************************** -* This script provides syntax highlighting support for -* the Ntriples format. -* Ntriples format specification: -* http://www.w3.org/TR/rdf-testcases/#ntriples -***********************************************************/ - -/* - The following expression defines the defined ASF grammar transitions. - - pre_subject -> - { - ( writing_subject_uri | writing_bnode_uri ) - -> pre_predicate - -> writing_predicate_uri - -> pre_object - -> writing_object_uri | writing_object_bnode | - ( - writing_object_literal - -> writing_literal_lang | writing_literal_type - ) - -> post_object - -> BEGIN - } otherwise { - -> ERROR - } -*/ -CodeMirror.defineMode("ntriples", function() { - - var Location = { - PRE_SUBJECT : 0, - WRITING_SUB_URI : 1, - WRITING_BNODE_URI : 2, - PRE_PRED : 3, - WRITING_PRED_URI : 4, - PRE_OBJ : 5, - WRITING_OBJ_URI : 6, - WRITING_OBJ_BNODE : 7, - WRITING_OBJ_LITERAL : 8, - WRITING_LIT_LANG : 9, - WRITING_LIT_TYPE : 10, - POST_OBJ : 11, - ERROR : 12 - }; - function transitState(currState, c) { - var currLocation = currState.location; - var ret; - - // Opening. - if (currLocation == Location.PRE_SUBJECT && c == '<') ret = Location.WRITING_SUB_URI; - else if(currLocation == Location.PRE_SUBJECT && c == '_') ret = Location.WRITING_BNODE_URI; - else if(currLocation == Location.PRE_PRED && c == '<') ret = Location.WRITING_PRED_URI; - else if(currLocation == Location.PRE_OBJ && c == '<') ret = Location.WRITING_OBJ_URI; - else if(currLocation == Location.PRE_OBJ && c == '_') ret = Location.WRITING_OBJ_BNODE; - else if(currLocation == Location.PRE_OBJ && c == '"') ret = Location.WRITING_OBJ_LITERAL; - - // Closing. - else if(currLocation == Location.WRITING_SUB_URI && c == '>') ret = Location.PRE_PRED; - else if(currLocation == Location.WRITING_BNODE_URI && c == ' ') ret = Location.PRE_PRED; - else if(currLocation == Location.WRITING_PRED_URI && c == '>') ret = Location.PRE_OBJ; - else if(currLocation == Location.WRITING_OBJ_URI && c == '>') ret = Location.POST_OBJ; - else if(currLocation == Location.WRITING_OBJ_BNODE && c == ' ') ret = Location.POST_OBJ; - else if(currLocation == Location.WRITING_OBJ_LITERAL && c == '"') ret = Location.POST_OBJ; - else if(currLocation == Location.WRITING_LIT_LANG && c == ' ') ret = Location.POST_OBJ; - else if(currLocation == Location.WRITING_LIT_TYPE && c == '>') ret = Location.POST_OBJ; - - // Closing typed and language literal. - else if(currLocation == Location.WRITING_OBJ_LITERAL && c == '@') ret = Location.WRITING_LIT_LANG; - else if(currLocation == Location.WRITING_OBJ_LITERAL && c == '^') ret = Location.WRITING_LIT_TYPE; - - // Spaces. - else if( c == ' ' && - ( - currLocation == Location.PRE_SUBJECT || - currLocation == Location.PRE_PRED || - currLocation == Location.PRE_OBJ || - currLocation == Location.POST_OBJ - ) - ) ret = currLocation; - - // Reset. - else if(currLocation == Location.POST_OBJ && c == '.') ret = Location.PRE_SUBJECT; - - // Error - else ret = Location.ERROR; - - currState.location=ret; - } - - var untilSpace = function(c) { return c != ' '; }; - var untilEndURI = function(c) { return c != '>'; }; - return { - startState: function() { - return { - location : Location.PRE_SUBJECT, - uris : [], - anchors : [], - bnodes : [], - langs : [], - types : [] - }; - }, - token: function(stream, state) { - var ch = stream.next(); - if(ch == '<') { - transitState(state, ch); - var parsedURI = ''; - stream.eatWhile( function(c) { if( c != '#' && c != '>' ) { parsedURI += c; return true; } return false;} ); - state.uris.push(parsedURI); - if( stream.match('#', false) ) return 'variable'; - stream.next(); - transitState(state, '>'); - return 'variable'; - } - if(ch == '#') { - var parsedAnchor = ''; - stream.eatWhile(function(c) { if(c != '>' && c != ' ') { parsedAnchor+= c; return true; } return false;}); - state.anchors.push(parsedAnchor); - return 'variable-2'; - } - if(ch == '>') { - transitState(state, '>'); - return 'variable'; - } - if(ch == '_') { - transitState(state, ch); - var parsedBNode = ''; - stream.eatWhile(function(c) { if( c != ' ' ) { parsedBNode += c; return true; } return false;}); - state.bnodes.push(parsedBNode); - stream.next(); - transitState(state, ' '); - return 'builtin'; - } - if(ch == '"') { - transitState(state, ch); - stream.eatWhile( function(c) { return c != '"'; } ); - stream.next(); - if( stream.peek() != '@' && stream.peek() != '^' ) { - transitState(state, '"'); - } - return 'string'; - } - if( ch == '@' ) { - transitState(state, '@'); - var parsedLang = ''; - stream.eatWhile(function(c) { if( c != ' ' ) { parsedLang += c; return true; } return false;}); - state.langs.push(parsedLang); - stream.next(); - transitState(state, ' '); - return 'string-2'; - } - if( ch == '^' ) { - stream.next(); - transitState(state, '^'); - var parsedType = ''; - stream.eatWhile(function(c) { if( c != '>' ) { parsedType += c; return true; } return false;} ); - state.types.push(parsedType); - stream.next(); - transitState(state, '>'); - return 'variable'; - } - if( ch == ' ' ) { - transitState(state, ch); - } - if( ch == '.' ) { - transitState(state, ch); - } - } - }; -}); - -CodeMirror.defineMIME("text/n-triples", "ntriples"); diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/ocaml/index.html b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/ocaml/index.html deleted file mode 100644 index ee81a42667..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/ocaml/index.html +++ /dev/null @@ -1,130 +0,0 @@ - - -CodeMirror: OCaml mode - - - - - - - - - -

    CodeMirror: OCaml mode

    - - - - - -

    MIME types defined: text/x-ocaml.

    diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/ocaml/ocaml.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/ocaml/ocaml.js deleted file mode 100644 index e113ea49dd..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/ocaml/ocaml.js +++ /dev/null @@ -1,114 +0,0 @@ -CodeMirror.defineMode('ocaml', function(config) { - - var words = { - 'true': 'atom', - 'false': 'atom', - 'let': 'keyword', - 'rec': 'keyword', - 'in': 'keyword', - 'of': 'keyword', - 'and': 'keyword', - 'succ': 'keyword', - 'if': 'keyword', - 'then': 'keyword', - 'else': 'keyword', - 'for': 'keyword', - 'to': 'keyword', - 'while': 'keyword', - 'do': 'keyword', - 'done': 'keyword', - 'fun': 'keyword', - 'function': 'keyword', - 'val': 'keyword', - 'type': 'keyword', - 'mutable': 'keyword', - 'match': 'keyword', - 'with': 'keyword', - 'try': 'keyword', - 'raise': 'keyword', - 'begin': 'keyword', - 'end': 'keyword', - 'open': 'builtin', - 'trace': 'builtin', - 'ignore': 'builtin', - 'exit': 'builtin', - 'print_string': 'builtin', - 'print_endline': 'builtin' - }; - - function tokenBase(stream, state) { - var sol = stream.sol(); - var ch = stream.next(); - - if (ch === '"') { - state.tokenize = tokenString; - return state.tokenize(stream, state); - } - if (ch === '(') { - if (stream.eat('*')) { - state.commentLevel++; - state.tokenize = tokenComment; - return state.tokenize(stream, state); - } - } - if (ch === '~') { - stream.eatWhile(/\w/); - return 'variable-2'; - } - if (ch === '`') { - stream.eatWhile(/\w/); - return 'quote'; - } - if (/\d/.test(ch)) { - stream.eatWhile(/[\d]/); - if (stream.eat('.')) { - stream.eatWhile(/[\d]/); - } - return 'number'; - } - if ( /[+\-*&%=<>!?|]/.test(ch)) { - return 'operator'; - } - stream.eatWhile(/\w/); - var cur = stream.current(); - return words[cur] || 'variable'; - } - - function tokenString(stream, state) { - var next, end = false, escaped = false; - while ((next = stream.next()) != null) { - if (next === '"' && !escaped) { - end = true; - break; - } - escaped = !escaped && next === '\\'; - } - if (end && !escaped) { - state.tokenize = tokenBase; - } - return 'string'; - }; - - function tokenComment(stream, state) { - var prev, next; - while(state.commentLevel > 0 && (next = stream.next()) != null) { - if (prev === '(' && next === '*') state.commentLevel++; - if (prev === '*' && next === ')') state.commentLevel--; - prev = next; - } - if (state.commentLevel <= 0) { - state.tokenize = tokenBase; - } - return 'comment'; - } - - return { - startState: function() {return {tokenize: tokenBase, commentLevel: 0};}, - token: function(stream, state) { - if (stream.eatSpace()) return null; - return state.tokenize(stream, state); - } - }; -}); - -CodeMirror.defineMIME('text/x-ocaml', 'ocaml'); diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/pascal/LICENSE b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/pascal/LICENSE deleted file mode 100644 index 5a9e588d00..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/pascal/LICENSE +++ /dev/null @@ -1,7 +0,0 @@ -Copyright (c) 2011 souceLair - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/pascal/index.html b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/pascal/index.html deleted file mode 100644 index c66bb8be6d..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/pascal/index.html +++ /dev/null @@ -1,49 +0,0 @@ - - - - - CodeMirror: Pascal mode - - - - - - - -

    CodeMirror: Pascal mode

    - -
    - - - -

    MIME types defined: text/x-pascal.

    - - diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/pascal/pascal.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/pascal/pascal.js deleted file mode 100644 index cd3397fa0b..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/pascal/pascal.js +++ /dev/null @@ -1,94 +0,0 @@ -CodeMirror.defineMode("pascal", function(config) { - function words(str) { - var obj = {}, words = str.split(" "); - for (var i = 0; i < words.length; ++i) obj[words[i]] = true; - return obj; - } - var keywords = words("and array begin case const div do downto else end file for forward integer " + - "boolean char function goto if in label mod nil not of or packed procedure " + - "program record repeat set string then to type until var while with"); - var atoms = {"null": true}; - - var isOperatorChar = /[+\-*&%=<>!?|\/]/; - - function tokenBase(stream, state) { - var ch = stream.next(); - if (ch == "#" && state.startOfLine) { - stream.skipToEnd(); - return "meta"; - } - if (ch == '"' || ch == "'") { - state.tokenize = tokenString(ch); - return state.tokenize(stream, state); - } - if (ch == "(" && stream.eat("*")) { - state.tokenize = tokenComment; - return tokenComment(stream, state); - } - if (/[\[\]{}\(\),;\:\.]/.test(ch)) { - return null; - } - if (/\d/.test(ch)) { - stream.eatWhile(/[\w\.]/); - return "number"; - } - if (ch == "/") { - if (stream.eat("/")) { - stream.skipToEnd(); - return "comment"; - } - } - if (isOperatorChar.test(ch)) { - stream.eatWhile(isOperatorChar); - return "operator"; - } - stream.eatWhile(/[\w\$_]/); - var cur = stream.current(); - if (keywords.propertyIsEnumerable(cur)) return "keyword"; - if (atoms.propertyIsEnumerable(cur)) return "atom"; - return "variable"; - } - - function tokenString(quote) { - return function(stream, state) { - var escaped = false, next, end = false; - while ((next = stream.next()) != null) { - if (next == quote && !escaped) {end = true; break;} - escaped = !escaped && next == "\\"; - } - if (end || !escaped) state.tokenize = null; - return "string"; - }; - } - - function tokenComment(stream, state) { - var maybeEnd = false, ch; - while (ch = stream.next()) { - if (ch == ")" && maybeEnd) { - state.tokenize = null; - break; - } - maybeEnd = (ch == "*"); - } - return "comment"; - } - - // Interface - - return { - startState: function(basecolumn) { - return {tokenize: null}; - }, - - token: function(stream, state) { - if (stream.eatSpace()) return null; - var style = (state.tokenize || tokenBase)(stream, state); - if (style == "comment" || style == "meta") return style; - return style; - }, - - electricChars: "{}" - }; -}); - -CodeMirror.defineMIME("text/x-pascal", "pascal"); diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/perl/LICENSE b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/perl/LICENSE deleted file mode 100644 index dca9ab25c6..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/perl/LICENSE +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (C) 2011 by Sabaca under the MIT license. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/perl/index.html b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/perl/index.html deleted file mode 100644 index 5be7c592fa..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/perl/index.html +++ /dev/null @@ -1,63 +0,0 @@ - - - - - CodeMirror: Perl mode - - - - - - - -

    CodeMirror: Perl mode

    - -
    - - - -

    MIME types defined: text/x-perl.

    - - diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/perl/perl.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/perl/perl.js deleted file mode 100644 index 0d5d57ccb4..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/perl/perl.js +++ /dev/null @@ -1,816 +0,0 @@ -// CodeMirror2 mode/perl/perl.js (text/x-perl) beta 0.10 (2011-11-08) -// This is a part of CodeMirror from https://github.com/sabaca/CodeMirror_mode_perl (mail@sabaca.com) -CodeMirror.defineMode("perl",function(config,parserConfig){ - // http://perldoc.perl.org - var PERL={ // null - magic touch - // 1 - keyword - // 2 - def - // 3 - atom - // 4 - operator - // 5 - variable-2 (predefined) - // [x,y] - x=1,2,3; y=must be defined if x{...} - // PERL operators - '->' : 4, - '++' : 4, - '--' : 4, - '**' : 4, - // ! ~ \ and unary + and - - '=~' : 4, - '!~' : 4, - '*' : 4, - '/' : 4, - '%' : 4, - 'x' : 4, - '+' : 4, - '-' : 4, - '.' : 4, - '<<' : 4, - '>>' : 4, - // named unary operators - '<' : 4, - '>' : 4, - '<=' : 4, - '>=' : 4, - 'lt' : 4, - 'gt' : 4, - 'le' : 4, - 'ge' : 4, - '==' : 4, - '!=' : 4, - '<=>' : 4, - 'eq' : 4, - 'ne' : 4, - 'cmp' : 4, - '~~' : 4, - '&' : 4, - '|' : 4, - '^' : 4, - '&&' : 4, - '||' : 4, - '//' : 4, - '..' : 4, - '...' : 4, - '?' : 4, - ':' : 4, - '=' : 4, - '+=' : 4, - '-=' : 4, - '*=' : 4, // etc. ??? - ',' : 4, - '=>' : 4, - '::' : 4, - // list operators (rightward) - 'not' : 4, - 'and' : 4, - 'or' : 4, - 'xor' : 4, - // PERL predefined variables (I know, what this is a paranoid idea, but may be needed for people, who learn PERL, and for me as well, ...and may be for you?;) - 'BEGIN' : [5,1], - 'END' : [5,1], - 'PRINT' : [5,1], - 'PRINTF' : [5,1], - 'GETC' : [5,1], - 'READ' : [5,1], - 'READLINE' : [5,1], - 'DESTROY' : [5,1], - 'TIE' : [5,1], - 'TIEHANDLE' : [5,1], - 'UNTIE' : [5,1], - 'STDIN' : 5, - 'STDIN_TOP' : 5, - 'STDOUT' : 5, - 'STDOUT_TOP' : 5, - 'STDERR' : 5, - 'STDERR_TOP' : 5, - '$ARG' : 5, - '$_' : 5, - '@ARG' : 5, - '@_' : 5, - '$LIST_SEPARATOR' : 5, - '$"' : 5, - '$PROCESS_ID' : 5, - '$PID' : 5, - '$$' : 5, - '$REAL_GROUP_ID' : 5, - '$GID' : 5, - '$(' : 5, - '$EFFECTIVE_GROUP_ID' : 5, - '$EGID' : 5, - '$)' : 5, - '$PROGRAM_NAME' : 5, - '$0' : 5, - '$SUBSCRIPT_SEPARATOR' : 5, - '$SUBSEP' : 5, - '$;' : 5, - '$REAL_USER_ID' : 5, - '$UID' : 5, - '$<' : 5, - '$EFFECTIVE_USER_ID' : 5, - '$EUID' : 5, - '$>' : 5, - '$a' : 5, - '$b' : 5, - '$COMPILING' : 5, - '$^C' : 5, - '$DEBUGGING' : 5, - '$^D' : 5, - '${^ENCODING}' : 5, - '$ENV' : 5, - '%ENV' : 5, - '$SYSTEM_FD_MAX' : 5, - '$^F' : 5, - '@F' : 5, - '${^GLOBAL_PHASE}' : 5, - '$^H' : 5, - '%^H' : 5, - '@INC' : 5, - '%INC' : 5, - '$INPLACE_EDIT' : 5, - '$^I' : 5, - '$^M' : 5, - '$OSNAME' : 5, - '$^O' : 5, - '${^OPEN}' : 5, - '$PERLDB' : 5, - '$^P' : 5, - '$SIG' : 5, - '%SIG' : 5, - '$BASETIME' : 5, - '$^T' : 5, - '${^TAINT}' : 5, - '${^UNICODE}' : 5, - '${^UTF8CACHE}' : 5, - '${^UTF8LOCALE}' : 5, - '$PERL_VERSION' : 5, - '$^V' : 5, - '${^WIN32_SLOPPY_STAT}' : 5, - '$EXECUTABLE_NAME' : 5, - '$^X' : 5, - '$1' : 5, // - regexp $1, $2... - '$MATCH' : 5, - '$&' : 5, - '${^MATCH}' : 5, - '$PREMATCH' : 5, - '$`' : 5, - '${^PREMATCH}' : 5, - '$POSTMATCH' : 5, - "$'" : 5, - '${^POSTMATCH}' : 5, - '$LAST_PAREN_MATCH' : 5, - '$+' : 5, - '$LAST_SUBMATCH_RESULT' : 5, - '$^N' : 5, - '@LAST_MATCH_END' : 5, - '@+' : 5, - '%LAST_PAREN_MATCH' : 5, - '%+' : 5, - '@LAST_MATCH_START' : 5, - '@-' : 5, - '%LAST_MATCH_START' : 5, - '%-' : 5, - '$LAST_REGEXP_CODE_RESULT' : 5, - '$^R' : 5, - '${^RE_DEBUG_FLAGS}' : 5, - '${^RE_TRIE_MAXBUF}' : 5, - '$ARGV' : 5, - '@ARGV' : 5, - 'ARGV' : 5, - 'ARGVOUT' : 5, - '$OUTPUT_FIELD_SEPARATOR' : 5, - '$OFS' : 5, - '$,' : 5, - '$INPUT_LINE_NUMBER' : 5, - '$NR' : 5, - '$.' : 5, - '$INPUT_RECORD_SEPARATOR' : 5, - '$RS' : 5, - '$/' : 5, - '$OUTPUT_RECORD_SEPARATOR' : 5, - '$ORS' : 5, - '$\\' : 5, - '$OUTPUT_AUTOFLUSH' : 5, - '$|' : 5, - '$ACCUMULATOR' : 5, - '$^A' : 5, - '$FORMAT_FORMFEED' : 5, - '$^L' : 5, - '$FORMAT_PAGE_NUMBER' : 5, - '$%' : 5, - '$FORMAT_LINES_LEFT' : 5, - '$-' : 5, - '$FORMAT_LINE_BREAK_CHARACTERS' : 5, - '$:' : 5, - '$FORMAT_LINES_PER_PAGE' : 5, - '$=' : 5, - '$FORMAT_TOP_NAME' : 5, - '$^' : 5, - '$FORMAT_NAME' : 5, - '$~' : 5, - '${^CHILD_ERROR_NATIVE}' : 5, - '$EXTENDED_OS_ERROR' : 5, - '$^E' : 5, - '$EXCEPTIONS_BEING_CAUGHT' : 5, - '$^S' : 5, - '$WARNING' : 5, - '$^W' : 5, - '${^WARNING_BITS}' : 5, - '$OS_ERROR' : 5, - '$ERRNO' : 5, - '$!' : 5, - '%OS_ERROR' : 5, - '%ERRNO' : 5, - '%!' : 5, - '$CHILD_ERROR' : 5, - '$?' : 5, - '$EVAL_ERROR' : 5, - '$@' : 5, - '$OFMT' : 5, - '$#' : 5, - '$*' : 5, - '$ARRAY_BASE' : 5, - '$[' : 5, - '$OLD_PERL_VERSION' : 5, - '$]' : 5, - // PERL blocks - 'if' :[1,1], - elsif :[1,1], - 'else' :[1,1], - 'while' :[1,1], - unless :[1,1], - 'for' :[1,1], - foreach :[1,1], - // PERL functions - 'abs' :1, // - absolute value function - accept :1, // - accept an incoming socket connect - alarm :1, // - schedule a SIGALRM - 'atan2' :1, // - arctangent of Y/X in the range -PI to PI - bind :1, // - binds an address to a socket - binmode :1, // - prepare binary files for I/O - bless :1, // - create an object - bootstrap :1, // - 'break' :1, // - break out of a "given" block - caller :1, // - get context of the current subroutine call - chdir :1, // - change your current working directory - chmod :1, // - changes the permissions on a list of files - chomp :1, // - remove a trailing record separator from a string - chop :1, // - remove the last character from a string - chown :1, // - change the owership on a list of files - chr :1, // - get character this number represents - chroot :1, // - make directory new root for path lookups - close :1, // - close file (or pipe or socket) handle - closedir :1, // - close directory handle - connect :1, // - connect to a remote socket - 'continue' :[1,1], // - optional trailing block in a while or foreach - 'cos' :1, // - cosine function - crypt :1, // - one-way passwd-style encryption - dbmclose :1, // - breaks binding on a tied dbm file - dbmopen :1, // - create binding on a tied dbm file - 'default' :1, // - defined :1, // - test whether a value, variable, or function is defined - 'delete' :1, // - deletes a value from a hash - die :1, // - raise an exception or bail out - 'do' :1, // - turn a BLOCK into a TERM - dump :1, // - create an immediate core dump - each :1, // - retrieve the next key/value pair from a hash - endgrent :1, // - be done using group file - endhostent :1, // - be done using hosts file - endnetent :1, // - be done using networks file - endprotoent :1, // - be done using protocols file - endpwent :1, // - be done using passwd file - endservent :1, // - be done using services file - eof :1, // - test a filehandle for its end - 'eval' :1, // - catch exceptions or compile and run code - 'exec' :1, // - abandon this program to run another - exists :1, // - test whether a hash key is present - exit :1, // - terminate this program - 'exp' :1, // - raise I to a power - fcntl :1, // - file control system call - fileno :1, // - return file descriptor from filehandle - flock :1, // - lock an entire file with an advisory lock - fork :1, // - create a new process just like this one - format :1, // - declare a picture format with use by the write() function - formline :1, // - internal function used for formats - getc :1, // - get the next character from the filehandle - getgrent :1, // - get next group record - getgrgid :1, // - get group record given group user ID - getgrnam :1, // - get group record given group name - gethostbyaddr :1, // - get host record given its address - gethostbyname :1, // - get host record given name - gethostent :1, // - get next hosts record - getlogin :1, // - return who logged in at this tty - getnetbyaddr :1, // - get network record given its address - getnetbyname :1, // - get networks record given name - getnetent :1, // - get next networks record - getpeername :1, // - find the other end of a socket connection - getpgrp :1, // - get process group - getppid :1, // - get parent process ID - getpriority :1, // - get current nice value - getprotobyname :1, // - get protocol record given name - getprotobynumber :1, // - get protocol record numeric protocol - getprotoent :1, // - get next protocols record - getpwent :1, // - get next passwd record - getpwnam :1, // - get passwd record given user login name - getpwuid :1, // - get passwd record given user ID - getservbyname :1, // - get services record given its name - getservbyport :1, // - get services record given numeric port - getservent :1, // - get next services record - getsockname :1, // - retrieve the sockaddr for a given socket - getsockopt :1, // - get socket options on a given socket - given :1, // - glob :1, // - expand filenames using wildcards - gmtime :1, // - convert UNIX time into record or string using Greenwich time - 'goto' :1, // - create spaghetti code - grep :1, // - locate elements in a list test true against a given criterion - hex :1, // - convert a string to a hexadecimal number - 'import' :1, // - patch a module's namespace into your own - index :1, // - find a substring within a string - 'int' :1, // - get the integer portion of a number - ioctl :1, // - system-dependent device control system call - 'join' :1, // - join a list into a string using a separator - keys :1, // - retrieve list of indices from a hash - kill :1, // - send a signal to a process or process group - last :1, // - exit a block prematurely - lc :1, // - return lower-case version of a string - lcfirst :1, // - return a string with just the next letter in lower case - length :1, // - return the number of bytes in a string - 'link' :1, // - create a hard link in the filesytem - listen :1, // - register your socket as a server - local : 2, // - create a temporary value for a global variable (dynamic scoping) - localtime :1, // - convert UNIX time into record or string using local time - lock :1, // - get a thread lock on a variable, subroutine, or method - 'log' :1, // - retrieve the natural logarithm for a number - lstat :1, // - stat a symbolic link - m :null, // - match a string with a regular expression pattern - map :1, // - apply a change to a list to get back a new list with the changes - mkdir :1, // - create a directory - msgctl :1, // - SysV IPC message control operations - msgget :1, // - get SysV IPC message queue - msgrcv :1, // - receive a SysV IPC message from a message queue - msgsnd :1, // - send a SysV IPC message to a message queue - my : 2, // - declare and assign a local variable (lexical scoping) - 'new' :1, // - next :1, // - iterate a block prematurely - no :1, // - unimport some module symbols or semantics at compile time - oct :1, // - convert a string to an octal number - open :1, // - open a file, pipe, or descriptor - opendir :1, // - open a directory - ord :1, // - find a character's numeric representation - our : 2, // - declare and assign a package variable (lexical scoping) - pack :1, // - convert a list into a binary representation - 'package' :1, // - declare a separate global namespace - pipe :1, // - open a pair of connected filehandles - pop :1, // - remove the last element from an array and return it - pos :1, // - find or set the offset for the last/next m//g search - print :1, // - output a list to a filehandle - printf :1, // - output a formatted list to a filehandle - prototype :1, // - get the prototype (if any) of a subroutine - push :1, // - append one or more elements to an array - q :null, // - singly quote a string - qq :null, // - doubly quote a string - qr :null, // - Compile pattern - quotemeta :null, // - quote regular expression magic characters - qw :null, // - quote a list of words - qx :null, // - backquote quote a string - rand :1, // - retrieve the next pseudorandom number - read :1, // - fixed-length buffered input from a filehandle - readdir :1, // - get a directory from a directory handle - readline :1, // - fetch a record from a file - readlink :1, // - determine where a symbolic link is pointing - readpipe :1, // - execute a system command and collect standard output - recv :1, // - receive a message over a Socket - redo :1, // - start this loop iteration over again - ref :1, // - find out the type of thing being referenced - rename :1, // - change a filename - require :1, // - load in external functions from a library at runtime - reset :1, // - clear all variables of a given name - 'return' :1, // - get out of a function early - reverse :1, // - flip a string or a list - rewinddir :1, // - reset directory handle - rindex :1, // - right-to-left substring search - rmdir :1, // - remove a directory - s :null, // - replace a pattern with a string - say :1, // - print with newline - scalar :1, // - force a scalar context - seek :1, // - reposition file pointer for random-access I/O - seekdir :1, // - reposition directory pointer - select :1, // - reset default output or do I/O multiplexing - semctl :1, // - SysV semaphore control operations - semget :1, // - get set of SysV semaphores - semop :1, // - SysV semaphore operations - send :1, // - send a message over a socket - setgrent :1, // - prepare group file for use - sethostent :1, // - prepare hosts file for use - setnetent :1, // - prepare networks file for use - setpgrp :1, // - set the process group of a process - setpriority :1, // - set a process's nice value - setprotoent :1, // - prepare protocols file for use - setpwent :1, // - prepare passwd file for use - setservent :1, // - prepare services file for use - setsockopt :1, // - set some socket options - shift :1, // - remove the first element of an array, and return it - shmctl :1, // - SysV shared memory operations - shmget :1, // - get SysV shared memory segment identifier - shmread :1, // - read SysV shared memory - shmwrite :1, // - write SysV shared memory - shutdown :1, // - close down just half of a socket connection - 'sin' :1, // - return the sine of a number - sleep :1, // - block for some number of seconds - socket :1, // - create a socket - socketpair :1, // - create a pair of sockets - 'sort' :1, // - sort a list of values - splice :1, // - add or remove elements anywhere in an array - 'split' :1, // - split up a string using a regexp delimiter - sprintf :1, // - formatted print into a string - 'sqrt' :1, // - square root function - srand :1, // - seed the random number generator - stat :1, // - get a file's status information - state :1, // - declare and assign a state variable (persistent lexical scoping) - study :1, // - optimize input data for repeated searches - 'sub' :1, // - declare a subroutine, possibly anonymously - 'substr' :1, // - get or alter a portion of a stirng - symlink :1, // - create a symbolic link to a file - syscall :1, // - execute an arbitrary system call - sysopen :1, // - open a file, pipe, or descriptor - sysread :1, // - fixed-length unbuffered input from a filehandle - sysseek :1, // - position I/O pointer on handle used with sysread and syswrite - system :1, // - run a separate program - syswrite :1, // - fixed-length unbuffered output to a filehandle - tell :1, // - get current seekpointer on a filehandle - telldir :1, // - get current seekpointer on a directory handle - tie :1, // - bind a variable to an object class - tied :1, // - get a reference to the object underlying a tied variable - time :1, // - return number of seconds since 1970 - times :1, // - return elapsed time for self and child processes - tr :null, // - transliterate a string - truncate :1, // - shorten a file - uc :1, // - return upper-case version of a string - ucfirst :1, // - return a string with just the next letter in upper case - umask :1, // - set file creation mode mask - undef :1, // - remove a variable or function definition - unlink :1, // - remove one link to a file - unpack :1, // - convert binary structure into normal perl variables - unshift :1, // - prepend more elements to the beginning of a list - untie :1, // - break a tie binding to a variable - use :1, // - load in a module at compile time - utime :1, // - set a file's last access and modify times - values :1, // - return a list of the values in a hash - vec :1, // - test or set particular bits in a string - wait :1, // - wait for any child process to die - waitpid :1, // - wait for a particular child process to die - wantarray :1, // - get void vs scalar vs list context of current subroutine call - warn :1, // - print debugging info - when :1, // - write :1, // - print a picture record - y :null}; // - transliterate a string - - var RXstyle="string-2"; - var RXmodifiers=/[goseximacplud]/; // NOTE: "m", "s", "y" and "tr" need to correct real modifiers for each regexp type - - function tokenChain(stream,state,chain,style,tail){ // NOTE: chain.length > 2 is not working now (it's for s[...][...]geos;) - state.chain=null; // 12 3tail - state.style=null; - state.tail=null; - state.tokenize=function(stream,state){ - var e=false,c,i=0; - while(c=stream.next()){ - if(c===chain[i]&&!e){ - if(chain[++i]!==undefined){ - state.chain=chain[i]; - state.style=style; - state.tail=tail;} - else if(tail) - stream.eatWhile(tail); - state.tokenize=tokenPerl; - return style;} - e=!e&&c=="\\";} - return style;}; - return state.tokenize(stream,state);} - - function tokenSOMETHING(stream,state,string){ - state.tokenize=function(stream,state){ - if(stream.string==string) - state.tokenize=tokenPerl; - stream.skipToEnd(); - return "string";}; - return state.tokenize(stream,state);} - - function tokenPerl(stream,state){ - if(stream.eatSpace()) - return null; - if(state.chain) - return tokenChain(stream,state,state.chain,state.style,state.tail); - if(stream.match(/^\-?[\d\.]/,false)) - if(stream.match(/^(\-?(\d*\.\d+(e[+-]?\d+)?|\d+\.\d*)|0x[\da-fA-F]+|0b[01]+|\d+(e[+-]?\d+)?)/)) - return 'number'; - if(stream.match(/^<<(?=\w)/)){ // NOTE: <"],RXstyle,RXmodifiers);} - if(/[\^'"!~\/]/.test(c)){ - stream.eatSuffix(1); - return tokenChain(stream,state,[stream.eat(c)],RXstyle,RXmodifiers);}} - else if(c=="q"){ - c=stream.look(1); - if(c=="("){ - stream.eatSuffix(2); - return tokenChain(stream,state,[")"],"string");} - if(c=="["){ - stream.eatSuffix(2); - return tokenChain(stream,state,["]"],"string");} - if(c=="{"){ - stream.eatSuffix(2); - return tokenChain(stream,state,["}"],"string");} - if(c=="<"){ - stream.eatSuffix(2); - return tokenChain(stream,state,[">"],"string");} - if(/[\^'"!~\/]/.test(c)){ - stream.eatSuffix(1); - return tokenChain(stream,state,[stream.eat(c)],"string");}} - else if(c=="w"){ - c=stream.look(1); - if(c=="("){ - stream.eatSuffix(2); - return tokenChain(stream,state,[")"],"bracket");} - if(c=="["){ - stream.eatSuffix(2); - return tokenChain(stream,state,["]"],"bracket");} - if(c=="{"){ - stream.eatSuffix(2); - return tokenChain(stream,state,["}"],"bracket");} - if(c=="<"){ - stream.eatSuffix(2); - return tokenChain(stream,state,[">"],"bracket");} - if(/[\^'"!~\/]/.test(c)){ - stream.eatSuffix(1); - return tokenChain(stream,state,[stream.eat(c)],"bracket");}} - else if(c=="r"){ - c=stream.look(1); - if(c=="("){ - stream.eatSuffix(2); - return tokenChain(stream,state,[")"],RXstyle,RXmodifiers);} - if(c=="["){ - stream.eatSuffix(2); - return tokenChain(stream,state,["]"],RXstyle,RXmodifiers);} - if(c=="{"){ - stream.eatSuffix(2); - return tokenChain(stream,state,["}"],RXstyle,RXmodifiers);} - if(c=="<"){ - stream.eatSuffix(2); - return tokenChain(stream,state,[">"],RXstyle,RXmodifiers);} - if(/[\^'"!~\/]/.test(c)){ - stream.eatSuffix(1); - return tokenChain(stream,state,[stream.eat(c)],RXstyle,RXmodifiers);}} - else if(/[\^'"!~\/(\[{<]/.test(c)){ - if(c=="("){ - stream.eatSuffix(1); - return tokenChain(stream,state,[")"],"string");} - if(c=="["){ - stream.eatSuffix(1); - return tokenChain(stream,state,["]"],"string");} - if(c=="{"){ - stream.eatSuffix(1); - return tokenChain(stream,state,["}"],"string");} - if(c=="<"){ - stream.eatSuffix(1); - return tokenChain(stream,state,[">"],"string");} - if(/[\^'"!~\/]/.test(c)){ - return tokenChain(stream,state,[stream.eat(c)],"string");}}}} - if(ch=="m"){ - var c=stream.look(-2); - if(!(c&&/\w/.test(c))){ - c=stream.eat(/[(\[{<\^'"!~\/]/); - if(c){ - if(/[\^'"!~\/]/.test(c)){ - return tokenChain(stream,state,[c],RXstyle,RXmodifiers);} - if(c=="("){ - return tokenChain(stream,state,[")"],RXstyle,RXmodifiers);} - if(c=="["){ - return tokenChain(stream,state,["]"],RXstyle,RXmodifiers);} - if(c=="{"){ - return tokenChain(stream,state,["}"],RXstyle,RXmodifiers);} - if(c=="<"){ - return tokenChain(stream,state,[">"],RXstyle,RXmodifiers);}}}} - if(ch=="s"){ - var c=/[\/>\]})\w]/.test(stream.look(-2)); - if(!c){ - c=stream.eat(/[(\[{<\^'"!~\/]/); - if(c){ - if(c=="[") - return tokenChain(stream,state,["]","]"],RXstyle,RXmodifiers); - if(c=="{") - return tokenChain(stream,state,["}","}"],RXstyle,RXmodifiers); - if(c=="<") - return tokenChain(stream,state,[">",">"],RXstyle,RXmodifiers); - if(c=="(") - return tokenChain(stream,state,[")",")"],RXstyle,RXmodifiers); - return tokenChain(stream,state,[c,c],RXstyle,RXmodifiers);}}} - if(ch=="y"){ - var c=/[\/>\]})\w]/.test(stream.look(-2)); - if(!c){ - c=stream.eat(/[(\[{<\^'"!~\/]/); - if(c){ - if(c=="[") - return tokenChain(stream,state,["]","]"],RXstyle,RXmodifiers); - if(c=="{") - return tokenChain(stream,state,["}","}"],RXstyle,RXmodifiers); - if(c=="<") - return tokenChain(stream,state,[">",">"],RXstyle,RXmodifiers); - if(c=="(") - return tokenChain(stream,state,[")",")"],RXstyle,RXmodifiers); - return tokenChain(stream,state,[c,c],RXstyle,RXmodifiers);}}} - if(ch=="t"){ - var c=/[\/>\]})\w]/.test(stream.look(-2)); - if(!c){ - c=stream.eat("r");if(c){ - c=stream.eat(/[(\[{<\^'"!~\/]/); - if(c){ - if(c=="[") - return tokenChain(stream,state,["]","]"],RXstyle,RXmodifiers); - if(c=="{") - return tokenChain(stream,state,["}","}"],RXstyle,RXmodifiers); - if(c=="<") - return tokenChain(stream,state,[">",">"],RXstyle,RXmodifiers); - if(c=="(") - return tokenChain(stream,state,[")",")"],RXstyle,RXmodifiers); - return tokenChain(stream,state,[c,c],RXstyle,RXmodifiers);}}}} - if(ch=="`"){ - return tokenChain(stream,state,[ch],"variable-2");} - if(ch=="/"){ - if(!/~\s*$/.test(stream.prefix())) - return "operator"; - else - return tokenChain(stream,state,[ch],RXstyle,RXmodifiers);} - if(ch=="$"){ - var p=stream.pos; - if(stream.eatWhile(/\d/)||stream.eat("{")&&stream.eatWhile(/\d/)&&stream.eat("}")) - return "variable-2"; - else - stream.pos=p;} - if(/[$@%]/.test(ch)){ - var p=stream.pos; - if(stream.eat("^")&&stream.eat(/[A-Z]/)||!/[@$%&]/.test(stream.look(-2))&&stream.eat(/[=|\\\-#?@;:&`~\^!\[\]*'"$+.,\/<>()]/)){ - var c=stream.current(); - if(PERL[c]) - return "variable-2";} - stream.pos=p;} - if(/[$@%&]/.test(ch)){ - if(stream.eatWhile(/[\w$\[\]]/)||stream.eat("{")&&stream.eatWhile(/[\w$\[\]]/)&&stream.eat("}")){ - var c=stream.current(); - if(PERL[c]) - return "variable-2"; - else - return "variable";}} - if(ch=="#"){ - if(stream.look(-2)!="$"){ - stream.skipToEnd(); - return "comment";}} - if(/[:+\-\^*$&%@=<>!?|\/~\.]/.test(ch)){ - var p=stream.pos; - stream.eatWhile(/[:+\-\^*$&%@=<>!?|\/~\.]/); - if(PERL[stream.current()]) - return "operator"; - else - stream.pos=p;} - if(ch=="_"){ - if(stream.pos==1){ - if(stream.suffix(6)=="_END__"){ - return tokenChain(stream,state,['\0'],"comment");} - else if(stream.suffix(7)=="_DATA__"){ - return tokenChain(stream,state,['\0'],"variable-2");} - else if(stream.suffix(7)=="_C__"){ - return tokenChain(stream,state,['\0'],"string");}}} - if(/\w/.test(ch)){ - var p=stream.pos; - if(stream.look(-2)=="{"&&(stream.look(0)=="}"||stream.eatWhile(/\w/)&&stream.look(0)=="}")) - return "string"; - else - stream.pos=p;} - if(/[A-Z]/.test(ch)){ - var l=stream.look(-2); - var p=stream.pos; - stream.eatWhile(/[A-Z_]/); - if(/[\da-z]/.test(stream.look(0))){ - stream.pos=p;} - else{ - var c=PERL[stream.current()]; - if(!c) - return "meta"; - if(c[1]) - c=c[0]; - if(l!=":"){ - if(c==1) - return "keyword"; - else if(c==2) - return "def"; - else if(c==3) - return "atom"; - else if(c==4) - return "operator"; - else if(c==5) - return "variable-2"; - else - return "meta";} - else - return "meta";}} - if(/[a-zA-Z_]/.test(ch)){ - var l=stream.look(-2); - stream.eatWhile(/\w/); - var c=PERL[stream.current()]; - if(!c) - return "meta"; - if(c[1]) - c=c[0]; - if(l!=":"){ - if(c==1) - return "keyword"; - else if(c==2) - return "def"; - else if(c==3) - return "atom"; - else if(c==4) - return "operator"; - else if(c==5) - return "variable-2"; - else - return "meta";} - else - return "meta";} - return null;} - - return{ - startState:function(){ - return{ - tokenize:tokenPerl, - chain:null, - style:null, - tail:null};}, - token:function(stream,state){ - return (state.tokenize||tokenPerl)(stream,state);}, - electricChars:"{}"};}); - -CodeMirror.defineMIME("text/x-perl", "perl"); - -// it's like "peek", but need for look-ahead or look-behind if index < 0 -CodeMirror.StringStream.prototype.look=function(c){ - return this.string.charAt(this.pos+(c||0));}; - -// return a part of prefix of current stream from current position -CodeMirror.StringStream.prototype.prefix=function(c){ - if(c){ - var x=this.pos-c; - return this.string.substr((x>=0?x:0),c);} - else{ - return this.string.substr(0,this.pos-1);}}; - -// return a part of suffix of current stream from current position -CodeMirror.StringStream.prototype.suffix=function(c){ - var y=this.string.length; - var x=y-this.pos+1; - return this.string.substr(this.pos,(c&&c=(y=this.string.length-1)) - this.pos=y; - else - this.pos=x;}; diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/php/index.html b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/php/index.html deleted file mode 100644 index 9ddefef3e0..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/php/index.html +++ /dev/null @@ -1,49 +0,0 @@ - - - - - CodeMirror: PHP mode - - - - - - - - - - - -

    CodeMirror: PHP mode

    - -
    - - - -

    Simple HTML/PHP mode based on - the C-like mode. Depends on XML, - JavaScript, CSS, and C-like modes.

    - -

    MIME types defined: application/x-httpd-php (HTML with PHP code), text/x-php (plain, non-wrapped PHP code).

    - - diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/php/php.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/php/php.js deleted file mode 100644 index f84f46c11b..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/php/php.js +++ /dev/null @@ -1,148 +0,0 @@ -(function() { - function keywords(str) { - var obj = {}, words = str.split(" "); - for (var i = 0; i < words.length; ++i) obj[words[i]] = true; - return obj; - } - function heredoc(delim) { - return function(stream, state) { - if (stream.match(delim)) state.tokenize = null; - else stream.skipToEnd(); - return "string"; - }; - } - var phpConfig = { - name: "clike", - keywords: keywords("abstract and array as break case catch class clone const continue declare default " + - "do else elseif enddeclare endfor endforeach endif endswitch endwhile extends final " + - "for foreach function global goto if implements interface instanceof namespace " + - "new or private protected public static switch throw trait try use var while xor " + - "die echo empty exit eval include include_once isset list require require_once return " + - "print unset __halt_compiler self static parent"), - blockKeywords: keywords("catch do else elseif for foreach if switch try while"), - atoms: keywords("true false null TRUE FALSE NULL"), - multiLineStrings: true, - hooks: { - "$": function(stream, state) { - stream.eatWhile(/[\w\$_]/); - return "variable-2"; - }, - "<": function(stream, state) { - if (stream.match(/<", false)) stream.next(); - return "comment"; - }, - "/": function(stream, state) { - if (stream.eat("/")) { - while (!stream.eol() && !stream.match("?>", false)) stream.next(); - return "comment"; - } - return false; - } - } - }; - - CodeMirror.defineMode("php", function(config, parserConfig) { - var htmlMode = CodeMirror.getMode(config, {name: "xml", htmlMode: true}); - var jsMode = CodeMirror.getMode(config, "javascript"); - var cssMode = CodeMirror.getMode(config, "css"); - var phpMode = CodeMirror.getMode(config, phpConfig); - - function dispatch(stream, state) { // TODO open PHP inside text/css - var isPHP = state.curMode == phpMode; - if (stream.sol() && state.pending != '"') state.pending = null; - if (state.curMode == htmlMode) { - if (stream.match(/^<\?\w*/)) { - state.curMode = phpMode; - state.curState = state.php; - state.curClose = "?>"; - return "meta"; - } - if (state.pending == '"') { - while (!stream.eol() && stream.next() != '"') {} - var style = "string"; - } else if (state.pending && stream.pos < state.pending.end) { - stream.pos = state.pending.end; - var style = state.pending.style; - } else { - var style = htmlMode.token(stream, state.curState); - } - state.pending = null; - var cur = stream.current(), openPHP = cur.search(/<\?/); - if (openPHP != -1) { - if (style == "string" && /\"$/.test(cur) && !/\?>/.test(cur)) state.pending = '"'; - else state.pending = {end: stream.pos, style: style}; - stream.backUp(cur.length - openPHP); - } else if (style == "tag" && stream.current() == ">" && state.curState.context) { - if (/^script$/i.test(state.curState.context.tagName)) { - state.curMode = jsMode; - state.curState = jsMode.startState(htmlMode.indent(state.curState, "")); - state.curClose = /^<\/\s*script\s*>/i; - } - else if (/^style$/i.test(state.curState.context.tagName)) { - state.curMode = cssMode; - state.curState = cssMode.startState(htmlMode.indent(state.curState, "")); - state.curClose = /^<\/\s*style\s*>/i; - } - } - return style; - } else if ((!isPHP || state.php.tokenize == null) && - stream.match(state.curClose, isPHP)) { - state.curMode = htmlMode; - state.curState = state.html; - state.curClose = null; - if (isPHP) return "meta"; - else return dispatch(stream, state); - } else { - return state.curMode.token(stream, state.curState); - } - } - - return { - startState: function() { - var html = htmlMode.startState(); - return {html: html, - php: phpMode.startState(), - curMode: parserConfig.startOpen ? phpMode : htmlMode, - curState: parserConfig.startOpen ? phpMode.startState() : html, - curClose: parserConfig.startOpen ? /^\?>/ : null, - mode: parserConfig.startOpen ? "php" : "html", - pending: null}; - }, - - copyState: function(state) { - var html = state.html, htmlNew = CodeMirror.copyState(htmlMode, html), - php = state.php, phpNew = CodeMirror.copyState(phpMode, php), cur; - if (state.curState == html) cur = htmlNew; - else if (state.curState == php) cur = phpNew; - else cur = CodeMirror.copyState(state.curMode, state.curState); - return {html: htmlNew, php: phpNew, curMode: state.curMode, curState: cur, - curClose: state.curClose, mode: state.mode, - pending: state.pending}; - }, - - token: dispatch, - - indent: function(state, textAfter) { - if ((state.curMode != phpMode && /^\s*<\//.test(textAfter)) || - (state.curMode == phpMode && /^\?>/.test(textAfter))) - return htmlMode.indent(state.html, textAfter); - return state.curMode.indent(state.curState, textAfter); - }, - - electricChars: "/{}:", - - innerMode: function(state) { return {state: state.curState, mode: state.curMode}; } - }; - }, "xml", "clike", "javascript", "css"); - CodeMirror.defineMIME("application/x-httpd-php", "php"); - CodeMirror.defineMIME("application/x-httpd-php-open", {name: "php", startOpen: true}); - CodeMirror.defineMIME("text/x-php", phpConfig); -})(); diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/pig/index.html b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/pig/index.html deleted file mode 100644 index 57c034d89e..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/pig/index.html +++ /dev/null @@ -1,43 +0,0 @@ - - - - - CodeMirror: Pig Latin mode - - - - - - - -

    CodeMirror: Pig Latin mode

    - -
    - - - -

    - Simple mode that handles Pig Latin language. -

    - -

    MIME type defined: text/x-pig - (PIG code) - diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/pig/pig.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/pig/pig.js deleted file mode 100644 index 8d7dedce17..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/pig/pig.js +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Pig Latin Mode for CodeMirror 2 - * @author Prasanth Jayachandran - * @link https://github.com/prasanthj/pig-codemirror-2 - * This implementation is adapted from PL/SQL mode in CodeMirror 2. -*/ -CodeMirror.defineMode("pig", function(config, parserConfig) { - var indentUnit = config.indentUnit, - keywords = parserConfig.keywords, - builtins = parserConfig.builtins, - types = parserConfig.types, - multiLineStrings = parserConfig.multiLineStrings; - - var isOperatorChar = /[*+\-%<>=&?:\/!|]/; - - function chain(stream, state, f) { - state.tokenize = f; - return f(stream, state); - } - - var type; - function ret(tp, style) { - type = tp; - return style; - } - - function tokenComment(stream, state) { - var isEnd = false; - var ch; - while(ch = stream.next()) { - if(ch == "/" && isEnd) { - state.tokenize = tokenBase; - break; - } - isEnd = (ch == "*"); - } - return ret("comment", "comment"); - } - - function tokenString(quote) { - return function(stream, state) { - var escaped = false, next, end = false; - while((next = stream.next()) != null) { - if (next == quote && !escaped) { - end = true; break; - } - escaped = !escaped && next == "\\"; - } - if (end || !(escaped || multiLineStrings)) - state.tokenize = tokenBase; - return ret("string", "error"); - }; - } - - function tokenBase(stream, state) { - var ch = stream.next(); - - // is a start of string? - if (ch == '"' || ch == "'") - return chain(stream, state, tokenString(ch)); - // is it one of the special chars - else if(/[\[\]{}\(\),;\.]/.test(ch)) - return ret(ch); - // is it a number? - else if(/\d/.test(ch)) { - stream.eatWhile(/[\w\.]/); - return ret("number", "number"); - } - // multi line comment or operator - else if (ch == "/") { - if (stream.eat("*")) { - return chain(stream, state, tokenComment); - } - else { - stream.eatWhile(isOperatorChar); - return ret("operator", "operator"); - } - } - // single line comment or operator - else if (ch=="-") { - if(stream.eat("-")){ - stream.skipToEnd(); - return ret("comment", "comment"); - } - else { - stream.eatWhile(isOperatorChar); - return ret("operator", "operator"); - } - } - // is it an operator - else if (isOperatorChar.test(ch)) { - stream.eatWhile(isOperatorChar); - return ret("operator", "operator"); - } - else { - // get the while word - stream.eatWhile(/[\w\$_]/); - // is it one of the listed keywords? - if (keywords && keywords.propertyIsEnumerable(stream.current().toUpperCase())) { - if (stream.eat(")") || stream.eat(".")) { - //keywords can be used as variables like flatten(group), group.$0 etc.. - } - else { - return ("keyword", "keyword"); - } - } - // is it one of the builtin functions? - if (builtins && builtins.propertyIsEnumerable(stream.current().toUpperCase())) - { - return ("keyword", "variable-2"); - } - // is it one of the listed types? - if (types && types.propertyIsEnumerable(stream.current().toUpperCase())) - return ("keyword", "variable-3"); - // default is a 'variable' - return ret("variable", "pig-word"); - } - } - - // Interface - return { - startState: function(basecolumn) { - return { - tokenize: tokenBase, - startOfLine: true - }; - }, - - token: function(stream, state) { - if(stream.eatSpace()) return null; - var style = state.tokenize(stream, state); - return style; - } - }; -}); - -(function() { - function keywords(str) { - var obj = {}, words = str.split(" "); - for (var i = 0; i < words.length; ++i) obj[words[i]] = true; - return obj; - } - - // builtin funcs taken from trunk revision 1303237 - var pBuiltins = "ABS ACOS ARITY ASIN ATAN AVG BAGSIZE BINSTORAGE BLOOM BUILDBLOOM CBRT CEIL " - + "CONCAT COR COS COSH COUNT COUNT_STAR COV CONSTANTSIZE CUBEDIMENSIONS DIFF DISTINCT DOUBLEABS " - + "DOUBLEAVG DOUBLEBASE DOUBLEMAX DOUBLEMIN DOUBLEROUND DOUBLESUM EXP FLOOR FLOATABS FLOATAVG " - + "FLOATMAX FLOATMIN FLOATROUND FLOATSUM GENERICINVOKER INDEXOF INTABS INTAVG INTMAX INTMIN " - + "INTSUM INVOKEFORDOUBLE INVOKEFORFLOAT INVOKEFORINT INVOKEFORLONG INVOKEFORSTRING INVOKER " - + "ISEMPTY JSONLOADER JSONMETADATA JSONSTORAGE LAST_INDEX_OF LCFIRST LOG LOG10 LOWER LONGABS " - + "LONGAVG LONGMAX LONGMIN LONGSUM MAX MIN MAPSIZE MONITOREDUDF NONDETERMINISTIC OUTPUTSCHEMA " - + "PIGSTORAGE PIGSTREAMING RANDOM REGEX_EXTRACT REGEX_EXTRACT_ALL REPLACE ROUND SIN SINH SIZE " - + "SQRT STRSPLIT SUBSTRING SUM STRINGCONCAT STRINGMAX STRINGMIN STRINGSIZE TAN TANH TOBAG " - + "TOKENIZE TOMAP TOP TOTUPLE TRIM TEXTLOADER TUPLESIZE UCFIRST UPPER UTF8STORAGECONVERTER "; - - // taken from QueryLexer.g - var pKeywords = "VOID IMPORT RETURNS DEFINE LOAD FILTER FOREACH ORDER CUBE DISTINCT COGROUP " - + "JOIN CROSS UNION SPLIT INTO IF OTHERWISE ALL AS BY USING INNER OUTER ONSCHEMA PARALLEL " - + "PARTITION GROUP AND OR NOT GENERATE FLATTEN ASC DESC IS STREAM THROUGH STORE MAPREDUCE " - + "SHIP CACHE INPUT OUTPUT STDERROR STDIN STDOUT LIMIT SAMPLE LEFT RIGHT FULL EQ GT LT GTE LTE " - + "NEQ MATCHES TRUE FALSE "; - - // data types - var pTypes = "BOOLEAN INT LONG FLOAT DOUBLE CHARARRAY BYTEARRAY BAG TUPLE MAP "; - - CodeMirror.defineMIME("text/x-pig", { - name: "pig", - builtins: keywords(pBuiltins), - keywords: keywords(pKeywords), - types: keywords(pTypes) - }); -}()); diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/plsql/index.html b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/plsql/index.html deleted file mode 100644 index 86aa53ee33..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/plsql/index.html +++ /dev/null @@ -1,63 +0,0 @@ - - - - - CodeMirror: Oracle PL/SQL mode - - - - - - - -

    CodeMirror: Oracle PL/SQL mode

    - -
    - - - -

    - Simple mode that handles Oracle PL/SQL language (and Oracle SQL, of course). -

    - -

    MIME type defined: text/x-plsql - (PLSQL code) - diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/plsql/plsql.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/plsql/plsql.js deleted file mode 100644 index 1ac0010386..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/plsql/plsql.js +++ /dev/null @@ -1,217 +0,0 @@ -CodeMirror.defineMode("plsql", function(config, parserConfig) { - var indentUnit = config.indentUnit, - keywords = parserConfig.keywords, - functions = parserConfig.functions, - types = parserConfig.types, - sqlplus = parserConfig.sqlplus, - multiLineStrings = parserConfig.multiLineStrings; - var isOperatorChar = /[+\-*&%=<>!?:\/|]/; - function chain(stream, state, f) { - state.tokenize = f; - return f(stream, state); - } - - var type; - function ret(tp, style) { - type = tp; - return style; - } - - function tokenBase(stream, state) { - var ch = stream.next(); - // start of string? - if (ch == '"' || ch == "'") - return chain(stream, state, tokenString(ch)); - // is it one of the special signs []{}().,;? Seperator? - else if (/[\[\]{}\(\),;\.]/.test(ch)) - return ret(ch); - // start of a number value? - else if (/\d/.test(ch)) { - stream.eatWhile(/[\w\.]/); - return ret("number", "number"); - } - // multi line comment or simple operator? - else if (ch == "/") { - if (stream.eat("*")) { - return chain(stream, state, tokenComment); - } - else { - stream.eatWhile(isOperatorChar); - return ret("operator", "operator"); - } - } - // single line comment or simple operator? - else if (ch == "-") { - if (stream.eat("-")) { - stream.skipToEnd(); - return ret("comment", "comment"); - } - else { - stream.eatWhile(isOperatorChar); - return ret("operator", "operator"); - } - } - // pl/sql variable? - else if (ch == "@" || ch == "$") { - stream.eatWhile(/[\w\d\$_]/); - return ret("word", "variable"); - } - // is it a operator? - else if (isOperatorChar.test(ch)) { - stream.eatWhile(isOperatorChar); - return ret("operator", "operator"); - } - else { - // get the whole word - stream.eatWhile(/[\w\$_]/); - // is it one of the listed keywords? - if (keywords && keywords.propertyIsEnumerable(stream.current().toLowerCase())) return ret("keyword", "keyword"); - // is it one of the listed functions? - if (functions && functions.propertyIsEnumerable(stream.current().toLowerCase())) return ret("keyword", "builtin"); - // is it one of the listed types? - if (types && types.propertyIsEnumerable(stream.current().toLowerCase())) return ret("keyword", "variable-2"); - // is it one of the listed sqlplus keywords? - if (sqlplus && sqlplus.propertyIsEnumerable(stream.current().toLowerCase())) return ret("keyword", "variable-3"); - // default: just a "variable" - return ret("word", "variable"); - } - } - - function tokenString(quote) { - return function(stream, state) { - var escaped = false, next, end = false; - while ((next = stream.next()) != null) { - if (next == quote && !escaped) {end = true; break;} - escaped = !escaped && next == "\\"; - } - if (end || !(escaped || multiLineStrings)) - state.tokenize = tokenBase; - return ret("string", "plsql-string"); - }; - } - - function tokenComment(stream, state) { - var maybeEnd = false, ch; - while (ch = stream.next()) { - if (ch == "/" && maybeEnd) { - state.tokenize = tokenBase; - break; - } - maybeEnd = (ch == "*"); - } - return ret("comment", "plsql-comment"); - } - - // Interface - - return { - startState: function(basecolumn) { - return { - tokenize: tokenBase, - startOfLine: true - }; - }, - - token: function(stream, state) { - if (stream.eatSpace()) return null; - var style = state.tokenize(stream, state); - return style; - } - }; -}); - -(function() { - function keywords(str) { - var obj = {}, words = str.split(" "); - for (var i = 0; i < words.length; ++i) obj[words[i]] = true; - return obj; - } - var cKeywords = "abort accept access add all alter and any array arraylen as asc assert assign at attributes audit " + - "authorization avg " + - "base_table begin between binary_integer body boolean by " + - "case cast char char_base check close cluster clusters colauth column comment commit compress connect " + - "connected constant constraint crash create current currval cursor " + - "data_base database date dba deallocate debugoff debugon decimal declare default definition delay delete " + - "desc digits dispose distinct do drop " + - "else elsif enable end entry escape exception exception_init exchange exclusive exists exit external " + - "fast fetch file for force form from function " + - "generic goto grant group " + - "having " + - "identified if immediate in increment index indexes indicator initial initrans insert interface intersect " + - "into is " + - "key " + - "level library like limited local lock log logging long loop " + - "master maxextents maxtrans member minextents minus mislabel mode modify multiset " + - "new next no noaudit nocompress nologging noparallel not nowait number_base " + - "object of off offline on online only open option or order out " + - "package parallel partition pctfree pctincrease pctused pls_integer positive positiven pragma primary prior " + - "private privileges procedure public " + - "raise range raw read rebuild record ref references refresh release rename replace resource restrict return " + - "returning reverse revoke rollback row rowid rowlabel rownum rows run " + - "savepoint schema segment select separate session set share snapshot some space split sql start statement " + - "storage subtype successful synonym " + - "tabauth table tables tablespace task terminate then to trigger truncate type " + - "union unique unlimited unrecoverable unusable update use using " + - "validate value values variable view views " + - "when whenever where while with work"; - - var cFunctions = "abs acos add_months ascii asin atan atan2 average " + - "bfilename " + - "ceil chartorowid chr concat convert cos cosh count " + - "decode deref dual dump dup_val_on_index " + - "empty error exp " + - "false floor found " + - "glb greatest " + - "hextoraw " + - "initcap instr instrb isopen " + - "last_day least lenght lenghtb ln lower lpad ltrim lub " + - "make_ref max min mod months_between " + - "new_time next_day nextval nls_charset_decl_len nls_charset_id nls_charset_name nls_initcap nls_lower " + - "nls_sort nls_upper nlssort no_data_found notfound null nvl " + - "others " + - "power " + - "rawtohex reftohex round rowcount rowidtochar rpad rtrim " + - "sign sin sinh soundex sqlcode sqlerrm sqrt stddev substr substrb sum sysdate " + - "tan tanh to_char to_date to_label to_multi_byte to_number to_single_byte translate true trunc " + - "uid upper user userenv " + - "variance vsize"; - - var cTypes = "bfile blob " + - "character clob " + - "dec " + - "float " + - "int integer " + - "mlslabel " + - "natural naturaln nchar nclob number numeric nvarchar2 " + - "real rowtype " + - "signtype smallint string " + - "varchar varchar2"; - - var cSqlplus = "appinfo arraysize autocommit autoprint autorecovery autotrace " + - "blockterminator break btitle " + - "cmdsep colsep compatibility compute concat copycommit copytypecheck " + - "define describe " + - "echo editfile embedded escape exec execute " + - "feedback flagger flush " + - "heading headsep " + - "instance " + - "linesize lno loboffset logsource long longchunksize " + - "markup " + - "native newpage numformat numwidth " + - "pagesize pause pno " + - "recsep recsepchar release repfooter repheader " + - "serveroutput shiftinout show showmode size spool sqlblanklines sqlcase sqlcode sqlcontinue sqlnumber " + - "sqlpluscompatibility sqlprefix sqlprompt sqlterminator suffix " + - "tab term termout time timing trimout trimspool ttitle " + - "underline " + - "verify version " + - "wrap"; - - CodeMirror.defineMIME("text/x-plsql", { - name: "plsql", - keywords: keywords(cKeywords), - functions: keywords(cFunctions), - types: keywords(cTypes), - sqlplus: keywords(cSqlplus) - }); -}()); diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/properties/index.html b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/properties/index.html deleted file mode 100644 index 2d212c06e6..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/properties/index.html +++ /dev/null @@ -1,41 +0,0 @@ - - - - - CodeMirror: Properties files mode - - - - - - - -

    CodeMirror: Properties files mode

    -
    - - -

    MIME types defined: text/x-properties, - text/x-ini.

    - - - diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/properties/properties.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/properties/properties.js deleted file mode 100644 index bee7763387..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/properties/properties.js +++ /dev/null @@ -1,63 +0,0 @@ -CodeMirror.defineMode("properties", function() { - return { - token: function(stream, state) { - var sol = stream.sol() || state.afterSection; - var eol = stream.eol(); - - state.afterSection = false; - - if (sol) { - if (state.nextMultiline) { - state.inMultiline = true; - state.nextMultiline = false; - } else { - state.position = "def"; - } - } - - if (eol && ! state.nextMultiline) { - state.inMultiline = false; - state.position = "def"; - } - - if (sol) { - while(stream.eatSpace()); - } - - var ch = stream.next(); - - if (sol && (ch === "#" || ch === "!" || ch === ";")) { - state.position = "comment"; - stream.skipToEnd(); - return "comment"; - } else if (sol && ch === "[") { - state.afterSection = true; - stream.skipTo("]"); stream.eat("]"); - return "header"; - } else if (ch === "=" || ch === ":") { - state.position = "quote"; - return null; - } else if (ch === "\\" && state.position === "quote") { - if (stream.next() !== "u") { // u = Unicode sequence \u1234 - // Multiline value - state.nextMultiline = true; - } - } - - return state.position; - }, - - startState: function() { - return { - position : "def", // Current position, "def", "quote" or "comment" - nextMultiline : false, // Is the next line multiline value - inMultiline : false, // Is the current line a multiline value - afterSection : false // Did we just open a section - }; - } - - }; -}); - -CodeMirror.defineMIME("text/x-properties", "properties"); -CodeMirror.defineMIME("text/x-ini", "properties"); diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/python/LICENSE.txt b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/python/LICENSE.txt deleted file mode 100644 index c42d27f475..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/python/LICENSE.txt +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License - -Copyright (c) 2010 Timothy Farrell - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/python/index.html b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/python/index.html deleted file mode 100644 index 77790c16ef..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/python/index.html +++ /dev/null @@ -1,123 +0,0 @@ - - - - - CodeMirror: Python mode - - - - - - - -

    CodeMirror: Python mode

    - -
    - -

    Configuration Options:

    -
      -
    • version - 2/3 - The version of Python to recognize. Default is 2.
    • -
    • singleLineStringErrors - true/false - If you have a single-line string that is not terminated at the end of the line, this will show subsequent lines as errors if true, otherwise it will consider the newline as the end of the string. Default is false.
    • -
    - -

    MIME types defined: text/x-python.

    - - diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/python/python.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/python/python.js deleted file mode 100644 index 03b029c4fe..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/python/python.js +++ /dev/null @@ -1,338 +0,0 @@ -CodeMirror.defineMode("python", function(conf, parserConf) { - var ERRORCLASS = 'error'; - - function wordRegexp(words) { - return new RegExp("^((" + words.join(")|(") + "))\\b"); - } - - var singleOperators = new RegExp("^[\\+\\-\\*/%&|\\^~<>!]"); - var singleDelimiters = new RegExp('^[\\(\\)\\[\\]\\{\\}@,:`=;\\.]'); - var doubleOperators = new RegExp("^((==)|(!=)|(<=)|(>=)|(<>)|(<<)|(>>)|(//)|(\\*\\*))"); - var doubleDelimiters = new RegExp("^((\\+=)|(\\-=)|(\\*=)|(%=)|(/=)|(&=)|(\\|=)|(\\^=))"); - var tripleDelimiters = new RegExp("^((//=)|(>>=)|(<<=)|(\\*\\*=))"); - var identifiers = new RegExp("^[_A-Za-z][_A-Za-z0-9]*"); - - var wordOperators = wordRegexp(['and', 'or', 'not', 'is', 'in']); - var commonkeywords = ['as', 'assert', 'break', 'class', 'continue', - 'def', 'del', 'elif', 'else', 'except', 'finally', - 'for', 'from', 'global', 'if', 'import', - 'lambda', 'pass', 'raise', 'return', - 'try', 'while', 'with', 'yield']; - var commonBuiltins = ['abs', 'all', 'any', 'bin', 'bool', 'bytearray', 'callable', 'chr', - 'classmethod', 'compile', 'complex', 'delattr', 'dict', 'dir', 'divmod', - 'enumerate', 'eval', 'filter', 'float', 'format', 'frozenset', - 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', - 'input', 'int', 'isinstance', 'issubclass', 'iter', 'len', - 'list', 'locals', 'map', 'max', 'memoryview', 'min', 'next', - 'object', 'oct', 'open', 'ord', 'pow', 'property', 'range', - 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', - 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', - 'type', 'vars', 'zip', '__import__', 'NotImplemented', - 'Ellipsis', '__debug__']; - var py2 = {'builtins': ['apply', 'basestring', 'buffer', 'cmp', 'coerce', 'execfile', - 'file', 'intern', 'long', 'raw_input', 'reduce', 'reload', - 'unichr', 'unicode', 'xrange', 'False', 'True', 'None'], - 'keywords': ['exec', 'print']}; - var py3 = {'builtins': ['ascii', 'bytes', 'exec', 'print'], - 'keywords': ['nonlocal', 'False', 'True', 'None']}; - - if (!!parserConf.version && parseInt(parserConf.version, 10) === 3) { - commonkeywords = commonkeywords.concat(py3.keywords); - commonBuiltins = commonBuiltins.concat(py3.builtins); - var stringPrefixes = new RegExp("^(([rb]|(br))?('{3}|\"{3}|['\"]))", "i"); - } else { - commonkeywords = commonkeywords.concat(py2.keywords); - commonBuiltins = commonBuiltins.concat(py2.builtins); - var stringPrefixes = new RegExp("^(([rub]|(ur)|(br))?('{3}|\"{3}|['\"]))", "i"); - } - var keywords = wordRegexp(commonkeywords); - var builtins = wordRegexp(commonBuiltins); - - var indentInfo = null; - - // tokenizers - function tokenBase(stream, state) { - // Handle scope changes - if (stream.sol()) { - var scopeOffset = state.scopes[0].offset; - if (stream.eatSpace()) { - var lineOffset = stream.indentation(); - if (lineOffset > scopeOffset) { - indentInfo = 'indent'; - } else if (lineOffset < scopeOffset) { - indentInfo = 'dedent'; - } - return null; - } else { - if (scopeOffset > 0) { - dedent(stream, state); - } - } - } - if (stream.eatSpace()) { - return null; - } - - var ch = stream.peek(); - - // Handle Comments - if (ch === '#') { - stream.skipToEnd(); - return 'comment'; - } - - // Handle Number Literals - if (stream.match(/^[0-9\.]/, false)) { - var floatLiteral = false; - // Floats - if (stream.match(/^\d*\.\d+(e[\+\-]?\d+)?/i)) { floatLiteral = true; } - if (stream.match(/^\d+\.\d*/)) { floatLiteral = true; } - if (stream.match(/^\.\d+/)) { floatLiteral = true; } - if (floatLiteral) { - // Float literals may be "imaginary" - stream.eat(/J/i); - return 'number'; - } - // Integers - var intLiteral = false; - // Hex - if (stream.match(/^0x[0-9a-f]+/i)) { intLiteral = true; } - // Binary - if (stream.match(/^0b[01]+/i)) { intLiteral = true; } - // Octal - if (stream.match(/^0o[0-7]+/i)) { intLiteral = true; } - // Decimal - if (stream.match(/^[1-9]\d*(e[\+\-]?\d+)?/)) { - // Decimal literals may be "imaginary" - stream.eat(/J/i); - // TODO - Can you have imaginary longs? - intLiteral = true; - } - // Zero by itself with no other piece of number. - if (stream.match(/^0(?![\dx])/i)) { intLiteral = true; } - if (intLiteral) { - // Integer literals may be "long" - stream.eat(/L/i); - return 'number'; - } - } - - // Handle Strings - if (stream.match(stringPrefixes)) { - state.tokenize = tokenStringFactory(stream.current()); - return state.tokenize(stream, state); - } - - // Handle operators and Delimiters - if (stream.match(tripleDelimiters) || stream.match(doubleDelimiters)) { - return null; - } - if (stream.match(doubleOperators) - || stream.match(singleOperators) - || stream.match(wordOperators)) { - return 'operator'; - } - if (stream.match(singleDelimiters)) { - return null; - } - - if (stream.match(keywords)) { - return 'keyword'; - } - - if (stream.match(builtins)) { - return 'builtin'; - } - - if (stream.match(identifiers)) { - return 'variable'; - } - - // Handle non-detected items - stream.next(); - return ERRORCLASS; - } - - function tokenStringFactory(delimiter) { - while ('rub'.indexOf(delimiter.charAt(0).toLowerCase()) >= 0) { - delimiter = delimiter.substr(1); - } - var singleline = delimiter.length == 1; - var OUTCLASS = 'string'; - - return function tokenString(stream, state) { - while (!stream.eol()) { - stream.eatWhile(/[^'"\\]/); - if (stream.eat('\\')) { - stream.next(); - if (singleline && stream.eol()) { - return OUTCLASS; - } - } else if (stream.match(delimiter)) { - state.tokenize = tokenBase; - return OUTCLASS; - } else { - stream.eat(/['"]/); - } - } - if (singleline) { - if (parserConf.singleLineStringErrors) { - return ERRORCLASS; - } else { - state.tokenize = tokenBase; - } - } - return OUTCLASS; - }; - } - - function indent(stream, state, type) { - type = type || 'py'; - var indentUnit = 0; - if (type === 'py') { - if (state.scopes[0].type !== 'py') { - state.scopes[0].offset = stream.indentation(); - return; - } - for (var i = 0; i < state.scopes.length; ++i) { - if (state.scopes[i].type === 'py') { - indentUnit = state.scopes[i].offset + conf.indentUnit; - break; - } - } - } else { - indentUnit = stream.column() + stream.current().length; - } - state.scopes.unshift({ - offset: indentUnit, - type: type - }); - } - - function dedent(stream, state, type) { - type = type || 'py'; - if (state.scopes.length == 1) return; - if (state.scopes[0].type === 'py') { - var _indent = stream.indentation(); - var _indent_index = -1; - for (var i = 0; i < state.scopes.length; ++i) { - if (_indent === state.scopes[i].offset) { - _indent_index = i; - break; - } - } - if (_indent_index === -1) { - return true; - } - while (state.scopes[0].offset !== _indent) { - state.scopes.shift(); - } - return false; - } else { - if (type === 'py') { - state.scopes[0].offset = stream.indentation(); - return false; - } else { - if (state.scopes[0].type != type) { - return true; - } - state.scopes.shift(); - return false; - } - } - } - - function tokenLexer(stream, state) { - indentInfo = null; - var style = state.tokenize(stream, state); - var current = stream.current(); - - // Handle '.' connected identifiers - if (current === '.') { - style = stream.match(identifiers, false) ? null : ERRORCLASS; - if (style === null && state.lastToken === 'meta') { - // Apply 'meta' style to '.' connected identifiers when - // appropriate. - style = 'meta'; - } - return style; - } - - // Handle decorators - if (current === '@') { - return stream.match(identifiers, false) ? 'meta' : ERRORCLASS; - } - - if ((style === 'variable' || style === 'builtin') - && state.lastToken === 'meta') { - style = 'meta'; - } - - // Handle scope changes. - if (current === 'pass' || current === 'return') { - state.dedent += 1; - } - if (current === 'lambda') state.lambda = true; - if ((current === ':' && !state.lambda && state.scopes[0].type == 'py') - || indentInfo === 'indent') { - indent(stream, state); - } - var delimiter_index = '[({'.indexOf(current); - if (delimiter_index !== -1) { - indent(stream, state, '])}'.slice(delimiter_index, delimiter_index+1)); - } - if (indentInfo === 'dedent') { - if (dedent(stream, state)) { - return ERRORCLASS; - } - } - delimiter_index = '])}'.indexOf(current); - if (delimiter_index !== -1) { - if (dedent(stream, state, current)) { - return ERRORCLASS; - } - } - if (state.dedent > 0 && stream.eol() && state.scopes[0].type == 'py') { - if (state.scopes.length > 1) state.scopes.shift(); - state.dedent -= 1; - } - - return style; - } - - var external = { - startState: function(basecolumn) { - return { - tokenize: tokenBase, - scopes: [{offset:basecolumn || 0, type:'py'}], - lastToken: null, - lambda: false, - dedent: 0 - }; - }, - - token: function(stream, state) { - var style = tokenLexer(stream, state); - - state.lastToken = style; - - if (stream.eol() && stream.lambda) { - state.lambda = false; - } - - return style; - }, - - indent: function(state, textAfter) { - if (state.tokenize != tokenBase) { - return 0; - } - - return state.scopes[0].offset; - } - - }; - return external; -}); - -CodeMirror.defineMIME("text/x-python", "python"); diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/r/LICENSE b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/r/LICENSE deleted file mode 100644 index 8b9c75dcc9..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/r/LICENSE +++ /dev/null @@ -1,24 +0,0 @@ -Copyright (c) 2011, Ubalo, Inc. -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the Ubalo, Inc nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL UBALO, INC BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/r/index.html b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/r/index.html deleted file mode 100644 index ea2d0c7b20..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/r/index.html +++ /dev/null @@ -1,74 +0,0 @@ - - - - - CodeMirror: R mode - - - - - - - -

    CodeMirror: R mode

    -
    - - -

    MIME types defined: text/x-rsrc.

    - -

    Development of the CodeMirror R mode was kindly sponsored - by Ubalo, who hold - the license.

    - - - diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/r/r.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/r/r.js deleted file mode 100644 index e5c43a3745..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/r/r.js +++ /dev/null @@ -1,141 +0,0 @@ -CodeMirror.defineMode("r", function(config) { - function wordObj(str) { - var words = str.split(" "), res = {}; - for (var i = 0; i < words.length; ++i) res[words[i]] = true; - return res; - } - var atoms = wordObj("NULL NA Inf NaN NA_integer_ NA_real_ NA_complex_ NA_character_"); - var builtins = wordObj("list quote bquote eval return call parse deparse"); - var keywords = wordObj("if else repeat while function for in next break"); - var blockkeywords = wordObj("if else repeat while function for"); - var opChars = /[+\-*\/^<>=!&|~$:]/; - var curPunc; - - function tokenBase(stream, state) { - curPunc = null; - var ch = stream.next(); - if (ch == "#") { - stream.skipToEnd(); - return "comment"; - } else if (ch == "0" && stream.eat("x")) { - stream.eatWhile(/[\da-f]/i); - return "number"; - } else if (ch == "." && stream.eat(/\d/)) { - stream.match(/\d*(?:e[+\-]?\d+)?/); - return "number"; - } else if (/\d/.test(ch)) { - stream.match(/\d*(?:\.\d+)?(?:e[+\-]\d+)?L?/); - return "number"; - } else if (ch == "'" || ch == '"') { - state.tokenize = tokenString(ch); - return "string"; - } else if (ch == "." && stream.match(/.[.\d]+/)) { - return "keyword"; - } else if (/[\w\.]/.test(ch) && ch != "_") { - stream.eatWhile(/[\w\.]/); - var word = stream.current(); - if (atoms.propertyIsEnumerable(word)) return "atom"; - if (keywords.propertyIsEnumerable(word)) { - if (blockkeywords.propertyIsEnumerable(word)) curPunc = "block"; - return "keyword"; - } - if (builtins.propertyIsEnumerable(word)) return "builtin"; - return "variable"; - } else if (ch == "%") { - if (stream.skipTo("%")) stream.next(); - return "variable-2"; - } else if (ch == "<" && stream.eat("-")) { - return "arrow"; - } else if (ch == "=" && state.ctx.argList) { - return "arg-is"; - } else if (opChars.test(ch)) { - if (ch == "$") return "dollar"; - stream.eatWhile(opChars); - return "operator"; - } else if (/[\(\){}\[\];]/.test(ch)) { - curPunc = ch; - if (ch == ";") return "semi"; - return null; - } else { - return null; - } - } - - function tokenString(quote) { - return function(stream, state) { - if (stream.eat("\\")) { - var ch = stream.next(); - if (ch == "x") stream.match(/^[a-f0-9]{2}/i); - else if ((ch == "u" || ch == "U") && stream.eat("{") && stream.skipTo("}")) stream.next(); - else if (ch == "u") stream.match(/^[a-f0-9]{4}/i); - else if (ch == "U") stream.match(/^[a-f0-9]{8}/i); - else if (/[0-7]/.test(ch)) stream.match(/^[0-7]{1,2}/); - return "string-2"; - } else { - var next; - while ((next = stream.next()) != null) { - if (next == quote) { state.tokenize = tokenBase; break; } - if (next == "\\") { stream.backUp(1); break; } - } - return "string"; - } - }; - } - - function push(state, type, stream) { - state.ctx = {type: type, - indent: state.indent, - align: null, - column: stream.column(), - prev: state.ctx}; - } - function pop(state) { - state.indent = state.ctx.indent; - state.ctx = state.ctx.prev; - } - - return { - startState: function(base) { - return {tokenize: tokenBase, - ctx: {type: "top", - indent: -config.indentUnit, - align: false}, - indent: 0, - afterIdent: false}; - }, - - token: function(stream, state) { - if (stream.sol()) { - if (state.ctx.align == null) state.ctx.align = false; - state.indent = stream.indentation(); - } - if (stream.eatSpace()) return null; - var style = state.tokenize(stream, state); - if (style != "comment" && state.ctx.align == null) state.ctx.align = true; - - var ctype = state.ctx.type; - if ((curPunc == ";" || curPunc == "{" || curPunc == "}") && ctype == "block") pop(state); - if (curPunc == "{") push(state, "}", stream); - else if (curPunc == "(") { - push(state, ")", stream); - if (state.afterIdent) state.ctx.argList = true; - } - else if (curPunc == "[") push(state, "]", stream); - else if (curPunc == "block") push(state, "block", stream); - else if (curPunc == ctype) pop(state); - state.afterIdent = style == "variable" || style == "keyword"; - return style; - }, - - indent: function(state, textAfter) { - if (state.tokenize != tokenBase) return 0; - var firstChar = textAfter && textAfter.charAt(0), ctx = state.ctx, - closing = firstChar == ctx.type; - if (ctx.type == "block") return ctx.indent + (firstChar == "{" ? 0 : config.indentUnit); - else if (ctx.align) return ctx.column + (closing ? 0 : 1); - else return ctx.indent + (closing ? 0 : config.indentUnit); - } - }; -}); - -CodeMirror.defineMIME("text/x-rsrc", "r"); diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/razor/LICENSE b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/razor/LICENSE deleted file mode 100644 index eeddf0f23b..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/razor/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -The MIT License - -Copyright (c) 2011 Jeff Pickhardt -Modified from the Python CodeMirror mode, Copyright (c) 2010 Timothy Farrell - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/rpm/changes/changes.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/rpm/changes/changes.js deleted file mode 100644 index 34eee61567..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/rpm/changes/changes.js +++ /dev/null @@ -1,19 +0,0 @@ -CodeMirror.defineMode("changes", function(config, modeConfig) { - var headerSeperator = /^-+$/; - var headerLine = /^(Mon|Tue|Wed|Thu|Fri|Sat|Sun) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) ?\d{1,2} \d{2}:\d{2}(:\d{2})? [A-Z]{3,4} \d{4} - /; - var simpleEmail = /^[\w+.-]+@[\w.-]+/; - - return { - token: function(stream) { - if (stream.sol()) { - if (stream.match(headerSeperator)) { return 'tag'; } - if (stream.match(headerLine)) { return 'tag'; } - } - if (stream.match(simpleEmail)) { return 'string'; } - stream.next(); - return null; - } - }; -}); - -CodeMirror.defineMIME("text/x-rpm-changes", "changes"); diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/rpm/changes/index.html b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/rpm/changes/index.html deleted file mode 100644 index a65db04ecb..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/rpm/changes/index.html +++ /dev/null @@ -1,54 +0,0 @@ - - - - - CodeMirror: RPM changes mode - - - - - - - -

    CodeMirror: RPM changes mode

    - -
    - - -

    MIME types defined: text/x-rpm-changes.

    - - diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/rpm/spec/index.html b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/rpm/spec/index.html deleted file mode 100644 index bc69c41141..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/rpm/spec/index.html +++ /dev/null @@ -1,100 +0,0 @@ - - - - - CodeMirror: RPM spec mode - - - - - - - - -

    CodeMirror: RPM spec mode

    - -
    - - -

    MIME types defined: text/x-rpm-spec.

    - - diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/rpm/spec/spec.css b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/rpm/spec/spec.css deleted file mode 100644 index 408b80ca2a..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/rpm/spec/spec.css +++ /dev/null @@ -1,5 +0,0 @@ -.cm-s-default span.cm-preamble {color: #b26818; font-weight: bold;} -.cm-s-default span.cm-macro {color: #b218b2;} -.cm-s-default span.cm-section {color: green; font-weight: bold;} -.cm-s-default span.cm-script {color: red;} -.cm-s-default span.cm-issue {color: yellow;} diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/rpm/spec/spec.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/rpm/spec/spec.js deleted file mode 100644 index c2c824e9f1..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/rpm/spec/spec.js +++ /dev/null @@ -1,66 +0,0 @@ -// Quick and dirty spec file highlighting - -CodeMirror.defineMode("spec", function(config, modeConfig) { - var arch = /^(i386|i586|i686|x86_64|ppc64|ppc|ia64|s390x|s390|sparc64|sparcv9|sparc|noarch|alphaev6|alpha|hppa|mipsel)/; - - var preamble = /^(Name|Version|Release|License|Summary|Url|Group|Source|BuildArch|BuildRequires|BuildRoot|AutoReqProv|Provides|Requires(\(\w+\))?|Obsoletes|Conflicts|Recommends|Source\d*|Patch\d*|ExclusiveArch|NoSource|Supplements):/; - var section = /^%(debug_package|package|description|prep|build|install|files|clean|changelog|preun|postun|pre|post|triggerin|triggerun|pretrans|posttrans|verifyscript|check|triggerpostun|triggerprein|trigger)/; - var control_flow_complex = /^%(ifnarch|ifarch|if)/; // rpm control flow macros - var control_flow_simple = /^%(else|endif)/; // rpm control flow macros - var operators = /^(\!|\?|\<\=|\<|\>\=|\>|\=\=|\&\&|\|\|)/; // operators in control flow macros - - return { - startState: function () { - return { - controlFlow: false, - macroParameters: false, - section: false - }; - }, - token: function (stream, state) { - var ch = stream.peek(); - if (ch == "#") { stream.skipToEnd(); return "comment"; } - - if (stream.sol()) { - if (stream.match(preamble)) { return "preamble"; } - if (stream.match(section)) { return "section"; } - } - - if (stream.match(/^\$\w+/)) { return "def"; } // Variables like '$RPM_BUILD_ROOT' - if (stream.match(/^\$\{\w+\}/)) { return "def"; } // Variables like '${RPM_BUILD_ROOT}' - - if (stream.match(control_flow_simple)) { return "keyword"; } - if (stream.match(control_flow_complex)) { - state.controlFlow = true; - return "keyword"; - } - if (state.controlFlow) { - if (stream.match(operators)) { return "operator"; } - if (stream.match(/^(\d+)/)) { return "number"; } - if (stream.eol()) { state.controlFlow = false; } - } - - if (stream.match(arch)) { return "number"; } - - // Macros like '%make_install' or '%attr(0775,root,root)' - if (stream.match(/^%[\w]+/)) { - if (stream.match(/^\(/)) { state.macroParameters = true; } - return "macro"; - } - if (state.macroParameters) { - if (stream.match(/^\d+/)) { return "number";} - if (stream.match(/^\)/)) { - state.macroParameters = false; - return "macro"; - } - } - if (stream.match(/^%\{\??[\w \-]+\}/)) { return "macro"; } // Macros like '%{defined fedora}' - - //TODO: Include bash script sub-parser (CodeMirror supports that) - stream.next(); - return null; - } - }; -}); - -CodeMirror.defineMIME("text/x-rpm-spec", "spec"); diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/rst/index.html b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/rst/index.html deleted file mode 100644 index 38eda5f53a..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/rst/index.html +++ /dev/null @@ -1,526 +0,0 @@ - - - - - CodeMirror: reStructuredText mode - - - - - - - -

    CodeMirror: reStructuredText mode

    - -
    - - -

    The reStructuredText mode supports one configuration parameter:

    -
    -
    verbatim (string)
    -
    A name or MIME type of a mode that will be used for highlighting - verbatim blocks. By default, reStructuredText mode uses uniform color - for whole block of verbatim text if no mode is given.
    -
    -

    If python mode is available, - it will be used for highlighting blocks containing Python/IPython terminal - sessions (blocks starting with >>> (for Python) or - In [num]: (for IPython). - -

    MIME types defined: text/x-rst.

    - - - diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/rst/rst.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/rst/rst.js deleted file mode 100644 index 0813c8683d..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/rst/rst.js +++ /dev/null @@ -1,326 +0,0 @@ -CodeMirror.defineMode('rst', function(config, options) { - function setState(state, fn, ctx) { - state.fn = fn; - setCtx(state, ctx); - } - - function setCtx(state, ctx) { - state.ctx = ctx || {}; - } - - function setNormal(state, ch) { - if (ch && (typeof ch !== 'string')) { - var str = ch.current(); - ch = str[str.length-1]; - } - - setState(state, normal, {back: ch}); - } - - function hasMode(mode) { - if (mode) { - var modes = CodeMirror.listModes(); - - for (var i in modes) { - if (modes[i] == mode) { - return true; - } - } - } - - return false; - } - - function getMode(mode) { - if (hasMode(mode)) { - return CodeMirror.getMode(config, mode); - } else { - return null; - } - } - - var verbatimMode = getMode(options.verbatim); - var pythonMode = getMode('python'); - - var reSection = /^[!"#$%&'()*+,-./:;<=>?@[\\\]^_`{|}~]/; - var reDirective = /^\s*\w([-:.\w]*\w)?::(\s|$)/; - var reHyperlink = /^\s*_[\w-]+:(\s|$)/; - var reFootnote = /^\s*\[(\d+|#)\](\s|$)/; - var reCitation = /^\s*\[[A-Za-z][\w-]*\](\s|$)/; - var reFootnoteRef = /^\[(\d+|#)\]_/; - var reCitationRef = /^\[[A-Za-z][\w-]*\]_/; - var reDirectiveMarker = /^\.\.(\s|$)/; - var reVerbatimMarker = /^::\s*$/; - var rePreInline = /^[-\s"([{/:.,;!?\\_]/; - var reEnumeratedList = /^\s*((\d+|[A-Za-z#])[.)]|\((\d+|[A-Z-a-z#])\))\s/; - var reBulletedList = /^\s*[-\+\*]\s/; - var reExamples = /^\s+(>>>|In \[\d+\]:)\s/; - - function normal(stream, state) { - var ch, sol, i; - - if (stream.eat(/\\/)) { - ch = stream.next(); - setNormal(state, ch); - return null; - } - - sol = stream.sol(); - - if (sol && (ch = stream.eat(reSection))) { - for (i = 0; stream.eat(ch); i++); - - if (i >= 3 && stream.match(/^\s*$/)) { - setNormal(state, null); - return 'header'; - } else { - stream.backUp(i + 1); - } - } - - if (sol && stream.match(reDirectiveMarker)) { - if (!stream.eol()) { - setState(state, directive); - } - return 'meta'; - } - - if (stream.match(reVerbatimMarker)) { - if (!verbatimMode) { - setState(state, verbatim); - } else { - var mode = verbatimMode; - - setState(state, verbatim, { - mode: mode, - local: mode.startState() - }); - } - return 'meta'; - } - - if (sol && stream.match(reExamples, false)) { - if (!pythonMode) { - setState(state, verbatim); - return 'meta'; - } else { - var mode = pythonMode; - - setState(state, verbatim, { - mode: mode, - local: mode.startState() - }); - - return null; - } - } - - function testBackward(re) { - return sol || !state.ctx.back || re.test(state.ctx.back); - } - - function testForward(re) { - return stream.eol() || stream.match(re, false); - } - - function testInline(re) { - return stream.match(re) && testBackward(/\W/) && testForward(/\W/); - } - - if (testInline(reFootnoteRef)) { - setNormal(state, stream); - return 'footnote'; - } - - if (testInline(reCitationRef)) { - setNormal(state, stream); - return 'citation'; - } - - ch = stream.next(); - - if (testBackward(rePreInline)) { - if ((ch === ':' || ch === '|') && stream.eat(/\S/)) { - var token; - - if (ch === ':') { - token = 'builtin'; - } else { - token = 'atom'; - } - - setState(state, inline, { - ch: ch, - wide: false, - prev: null, - token: token - }); - - return token; - } - - if (ch === '*' || ch === '`') { - var orig = ch, - wide = false; - - ch = stream.next(); - - if (ch == orig) { - wide = true; - ch = stream.next(); - } - - if (ch && !/\s/.test(ch)) { - var token; - - if (orig === '*') { - token = wide ? 'strong' : 'em'; - } else { - token = wide ? 'string' : 'string-2'; - } - - setState(state, inline, { - ch: orig, // inline() has to know what to search for - wide: wide, // are we looking for `ch` or `chch` - prev: null, // terminator must not be preceeded with whitespace - token: token // I don't want to recompute this all the time - }); - - return token; - } - } - } - - setNormal(state, ch); - return null; - } - - function inline(stream, state) { - var ch = stream.next(), - token = state.ctx.token; - - function finish(ch) { - state.ctx.prev = ch; - return token; - } - - if (ch != state.ctx.ch) { - return finish(ch); - } - - if (/\s/.test(state.ctx.prev)) { - return finish(ch); - } - - if (state.ctx.wide) { - ch = stream.next(); - - if (ch != state.ctx.ch) { - return finish(ch); - } - } - - if (!stream.eol() && !rePostInline.test(stream.peek())) { - if (state.ctx.wide) { - stream.backUp(1); - } - - return finish(ch); - } - - setState(state, normal); - setNormal(state, ch); - - return token; - } - - function directive(stream, state) { - var token = null; - - if (stream.match(reDirective)) { - token = 'attribute'; - } else if (stream.match(reHyperlink)) { - token = 'link'; - } else if (stream.match(reFootnote)) { - token = 'quote'; - } else if (stream.match(reCitation)) { - token = 'quote'; - } else { - stream.eatSpace(); - - if (stream.eol()) { - setNormal(state, stream); - return null; - } else { - stream.skipToEnd(); - setState(state, comment); - return 'comment'; - } - } - - // FIXME this is unreachable - setState(state, body, {start: true}); - return token; - } - - function body(stream, state) { - var token = 'body'; - - if (!state.ctx.start || stream.sol()) { - return block(stream, state, token); - } - - stream.skipToEnd(); - setCtx(state); - - return token; - } - - function comment(stream, state) { - return block(stream, state, 'comment'); - } - - function verbatim(stream, state) { - if (!verbatimMode) { - return block(stream, state, 'meta'); - } else { - if (stream.sol()) { - if (!stream.eatSpace()) { - setNormal(state, stream); - } - - return null; - } - - return verbatimMode.token(stream, state.ctx.local); - } - } - - function block(stream, state, token) { - if (stream.eol() || stream.eatSpace()) { - stream.skipToEnd(); - return token; - } else { - setNormal(state, stream); - return null; - } - } - - return { - startState: function() { - return {fn: normal, ctx: {}}; - }, - - copyState: function(state) { - return {fn: state.fn, ctx: state.ctx}; - }, - - token: function(stream, state) { - var token = state.fn(stream, state); - return token; - } - }; -}, "python"); - -CodeMirror.defineMIME("text/x-rst", "rst"); diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/ruby/LICENSE b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/ruby/LICENSE deleted file mode 100644 index 7bf42f8ac7..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/ruby/LICENSE +++ /dev/null @@ -1,24 +0,0 @@ -Copyright (c) 2011, Ubalo, Inc. -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the Ubalo, Inc. nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL UBALO, INC BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/ruby/index.html b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/ruby/index.html deleted file mode 100644 index 7fbbfb49c5..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/ruby/index.html +++ /dev/null @@ -1,172 +0,0 @@ - - - - - CodeMirror: Ruby mode - - - - - - - -

    CodeMirror: Ruby mode

    -
    - - -

    MIME types defined: text/x-ruby.

    - -

    Development of the CodeMirror Ruby mode was kindly sponsored - by Ubalo, who hold - the license.

    - - - diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/ruby/ruby.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/ruby/ruby.js deleted file mode 100644 index 59b79e3487..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/ruby/ruby.js +++ /dev/null @@ -1,195 +0,0 @@ -CodeMirror.defineMode("ruby", function(config, parserConfig) { - function wordObj(words) { - var o = {}; - for (var i = 0, e = words.length; i < e; ++i) o[words[i]] = true; - return o; - } - var keywords = wordObj([ - "alias", "and", "BEGIN", "begin", "break", "case", "class", "def", "defined?", "do", "else", - "elsif", "END", "end", "ensure", "false", "for", "if", "in", "module", "next", "not", "or", - "redo", "rescue", "retry", "return", "self", "super", "then", "true", "undef", "unless", - "until", "when", "while", "yield", "nil", "raise", "throw", "catch", "fail", "loop", "callcc", - "caller", "lambda", "proc", "public", "protected", "private", "require", "load", - "require_relative", "extend", "autoload" - ]); - var indentWords = wordObj(["def", "class", "case", "for", "while", "do", "module", "then", - "catch", "loop", "proc", "begin"]); - var dedentWords = wordObj(["end", "until"]); - var matching = {"[": "]", "{": "}", "(": ")"}; - var curPunc; - - function chain(newtok, stream, state) { - state.tokenize.push(newtok); - return newtok(stream, state); - } - - function tokenBase(stream, state) { - curPunc = null; - if (stream.sol() && stream.match("=begin") && stream.eol()) { - state.tokenize.push(readBlockComment); - return "comment"; - } - if (stream.eatSpace()) return null; - var ch = stream.next(), m; - if (ch == "`" || ch == "'" || ch == '"' || - (ch == "/" && !stream.eol() && stream.peek() != " ")) { - return chain(readQuoted(ch, "string", ch == '"' || ch == "`"), stream, state); - } else if (ch == "%") { - var style, embed = false; - if (stream.eat("s")) style = "atom"; - else if (stream.eat(/[WQ]/)) { style = "string"; embed = true; } - else if (stream.eat(/[wxqr]/)) style = "string"; - var delim = stream.eat(/[^\w\s]/); - if (!delim) return "operator"; - if (matching.propertyIsEnumerable(delim)) delim = matching[delim]; - return chain(readQuoted(delim, style, embed, true), stream, state); - } else if (ch == "#") { - stream.skipToEnd(); - return "comment"; - } else if (ch == "<" && (m = stream.match(/^<-?[\`\"\']?([a-zA-Z_?]\w*)[\`\"\']?(?:;|$)/))) { - return chain(readHereDoc(m[1]), stream, state); - } else if (ch == "0") { - if (stream.eat("x")) stream.eatWhile(/[\da-fA-F]/); - else if (stream.eat("b")) stream.eatWhile(/[01]/); - else stream.eatWhile(/[0-7]/); - return "number"; - } else if (/\d/.test(ch)) { - stream.match(/^[\d_]*(?:\.[\d_]+)?(?:[eE][+\-]?[\d_]+)?/); - return "number"; - } else if (ch == "?") { - while (stream.match(/^\\[CM]-/)) {} - if (stream.eat("\\")) stream.eatWhile(/\w/); - else stream.next(); - return "string"; - } else if (ch == ":") { - if (stream.eat("'")) return chain(readQuoted("'", "atom", false), stream, state); - if (stream.eat('"')) return chain(readQuoted('"', "atom", true), stream, state); - stream.eatWhile(/[\w\?]/); - return "atom"; - } else if (ch == "@") { - stream.eat("@"); - stream.eatWhile(/[\w\?]/); - return "variable-2"; - } else if (ch == "$") { - stream.next(); - stream.eatWhile(/[\w\?]/); - return "variable-3"; - } else if (/\w/.test(ch)) { - stream.eatWhile(/[\w\?]/); - if (stream.eat(":")) return "atom"; - return "ident"; - } else if (ch == "|" && (state.varList || state.lastTok == "{" || state.lastTok == "do")) { - curPunc = "|"; - return null; - } else if (/[\(\)\[\]{}\\;]/.test(ch)) { - curPunc = ch; - return null; - } else if (ch == "-" && stream.eat(">")) { - return "arrow"; - } else if (/[=+\-\/*:\.^%<>~|]/.test(ch)) { - stream.eatWhile(/[=+\-\/*:\.^%<>~|]/); - return "operator"; - } else { - return null; - } - } - - function tokenBaseUntilBrace() { - var depth = 1; - return function(stream, state) { - if (stream.peek() == "}") { - depth--; - if (depth == 0) { - state.tokenize.pop(); - return state.tokenize[state.tokenize.length-1](stream, state); - } - } else if (stream.peek() == "{") { - depth++; - } - return tokenBase(stream, state); - }; - } - function readQuoted(quote, style, embed, unescaped) { - return function(stream, state) { - var escaped = false, ch; - while ((ch = stream.next()) != null) { - if (ch == quote && (unescaped || !escaped)) { - state.tokenize.pop(); - break; - } - if (embed && ch == "#" && !escaped && stream.eat("{")) { - state.tokenize.push(tokenBaseUntilBrace(arguments.callee)); - break; - } - escaped = !escaped && ch == "\\"; - } - return style; - }; - } - function readHereDoc(phrase) { - return function(stream, state) { - if (stream.match(phrase)) state.tokenize.pop(); - else stream.skipToEnd(); - return "string"; - }; - } - function readBlockComment(stream, state) { - if (stream.sol() && stream.match("=end") && stream.eol()) - state.tokenize.pop(); - stream.skipToEnd(); - return "comment"; - } - - return { - startState: function() { - return {tokenize: [tokenBase], - indented: 0, - context: {type: "top", indented: -config.indentUnit}, - continuedLine: false, - lastTok: null, - varList: false}; - }, - - token: function(stream, state) { - if (stream.sol()) state.indented = stream.indentation(); - var style = state.tokenize[state.tokenize.length-1](stream, state), kwtype; - if (style == "ident") { - var word = stream.current(); - style = keywords.propertyIsEnumerable(stream.current()) ? "keyword" - : /^[A-Z]/.test(word) ? "tag" - : (state.lastTok == "def" || state.lastTok == "class" || state.varList) ? "def" - : "variable"; - if (indentWords.propertyIsEnumerable(word)) kwtype = "indent"; - else if (dedentWords.propertyIsEnumerable(word)) kwtype = "dedent"; - else if ((word == "if" || word == "unless") && stream.column() == stream.indentation()) - kwtype = "indent"; - } - if (curPunc || (style && style != "comment")) state.lastTok = word || curPunc || style; - if (curPunc == "|") state.varList = !state.varList; - - if (kwtype == "indent" || /[\(\[\{]/.test(curPunc)) - state.context = {prev: state.context, type: curPunc || style, indented: state.indented}; - else if ((kwtype == "dedent" || /[\)\]\}]/.test(curPunc)) && state.context.prev) - state.context = state.context.prev; - - if (stream.eol()) - state.continuedLine = (curPunc == "\\" || style == "operator"); - return style; - }, - - indent: function(state, textAfter) { - if (state.tokenize[state.tokenize.length-1] != tokenBase) return 0; - var firstChar = textAfter && textAfter.charAt(0); - var ct = state.context; - var closing = ct.type == matching[firstChar] || - ct.type == "keyword" && /^(?:end|until|else|elsif|when|rescue)\b/.test(textAfter); - return ct.indented + (closing ? 0 : config.indentUnit) + - (state.continuedLine ? config.indentUnit : 0); - }, - electricChars: "}de" // enD and rescuE - - }; -}); - -CodeMirror.defineMIME("text/x-ruby", "ruby"); - diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/rust/index.html b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/rust/index.html deleted file mode 100644 index e659ee56a7..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/rust/index.html +++ /dev/null @@ -1,49 +0,0 @@ - - - - - CodeMirror: Rust mode - - - - - - - -

    CodeMirror: Rust mode

    - -
    - - - -

    MIME types defined: text/x-rustsrc.

    - - diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/rust/rust.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/rust/rust.js deleted file mode 100644 index 0483e8e8ec..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/rust/rust.js +++ /dev/null @@ -1,432 +0,0 @@ -CodeMirror.defineMode("rust", function() { - var indentUnit = 4, altIndentUnit = 2; - var valKeywords = { - "if": "if-style", "while": "if-style", "else": "else-style", - "do": "else-style", "ret": "else-style", "fail": "else-style", - "break": "atom", "cont": "atom", "const": "let", "resource": "fn", - "let": "let", "fn": "fn", "for": "for", "alt": "alt", "iface": "iface", - "impl": "impl", "type": "type", "enum": "enum", "mod": "mod", - "as": "op", "true": "atom", "false": "atom", "assert": "op", "check": "op", - "claim": "op", "native": "ignore", "unsafe": "ignore", "import": "else-style", - "export": "else-style", "copy": "op", "log": "op", "log_err": "op", - "use": "op", "bind": "op", "self": "atom" - }; - var typeKeywords = function() { - var keywords = {"fn": "fn", "block": "fn", "obj": "obj"}; - var atoms = "bool uint int i8 i16 i32 i64 u8 u16 u32 u64 float f32 f64 str char".split(" "); - for (var i = 0, e = atoms.length; i < e; ++i) keywords[atoms[i]] = "atom"; - return keywords; - }(); - var operatorChar = /[+\-*&%=<>!?|\.@]/; - - // Tokenizer - - // Used as scratch variable to communicate multiple values without - // consing up tons of objects. - var tcat, content; - function r(tc, style) { - tcat = tc; - return style; - } - - function tokenBase(stream, state) { - var ch = stream.next(); - if (ch == '"') { - state.tokenize = tokenString; - return state.tokenize(stream, state); - } - if (ch == "'") { - tcat = "atom"; - if (stream.eat("\\")) { - if (stream.skipTo("'")) { stream.next(); return "string"; } - else { return "error"; } - } else { - stream.next(); - return stream.eat("'") ? "string" : "error"; - } - } - if (ch == "/") { - if (stream.eat("/")) { stream.skipToEnd(); return "comment"; } - if (stream.eat("*")) { - state.tokenize = tokenComment(1); - return state.tokenize(stream, state); - } - } - if (ch == "#") { - if (stream.eat("[")) { tcat = "open-attr"; return null; } - stream.eatWhile(/\w/); - return r("macro", "meta"); - } - if (ch == ":" && stream.match(":<")) { - return r("op", null); - } - if (ch.match(/\d/) || (ch == "." && stream.eat(/\d/))) { - var flp = false; - if (!stream.match(/^x[\da-f]+/i) && !stream.match(/^b[01]+/)) { - stream.eatWhile(/\d/); - if (stream.eat(".")) { flp = true; stream.eatWhile(/\d/); } - if (stream.match(/^e[+\-]?\d+/i)) { flp = true; } - } - if (flp) stream.match(/^f(?:32|64)/); - else stream.match(/^[ui](?:8|16|32|64)/); - return r("atom", "number"); - } - if (ch.match(/[()\[\]{}:;,]/)) return r(ch, null); - if (ch == "-" && stream.eat(">")) return r("->", null); - if (ch.match(operatorChar)) { - stream.eatWhile(operatorChar); - return r("op", null); - } - stream.eatWhile(/\w/); - content = stream.current(); - if (stream.match(/^::\w/)) { - stream.backUp(1); - return r("prefix", "variable-2"); - } - if (state.keywords.propertyIsEnumerable(content)) - return r(state.keywords[content], content.match(/true|false/) ? "atom" : "keyword"); - return r("name", "variable"); - } - - function tokenString(stream, state) { - var ch, escaped = false; - while (ch = stream.next()) { - if (ch == '"' && !escaped) { - state.tokenize = tokenBase; - return r("atom", "string"); - } - escaped = !escaped && ch == "\\"; - } - // Hack to not confuse the parser when a string is split in - // pieces. - return r("op", "string"); - } - - function tokenComment(depth) { - return function(stream, state) { - var lastCh = null, ch; - while (ch = stream.next()) { - if (ch == "/" && lastCh == "*") { - if (depth == 1) { - state.tokenize = tokenBase; - break; - } else { - state.tokenize = tokenComment(depth - 1); - return state.tokenize(stream, state); - } - } - if (ch == "*" && lastCh == "/") { - state.tokenize = tokenComment(depth + 1); - return state.tokenize(stream, state); - } - lastCh = ch; - } - return "comment"; - }; - } - - // Parser - - var cx = {state: null, stream: null, marked: null, cc: null}; - function pass() { - for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]); - } - function cont() { - pass.apply(null, arguments); - return true; - } - - function pushlex(type, info) { - var result = function() { - var state = cx.state; - state.lexical = {indented: state.indented, column: cx.stream.column(), - type: type, prev: state.lexical, info: info}; - }; - result.lex = true; - return result; - } - function poplex() { - var state = cx.state; - if (state.lexical.prev) { - if (state.lexical.type == ")") - state.indented = state.lexical.indented; - state.lexical = state.lexical.prev; - } - } - function typecx() { cx.state.keywords = typeKeywords; } - function valcx() { cx.state.keywords = valKeywords; } - poplex.lex = typecx.lex = valcx.lex = true; - - function commasep(comb, end) { - function more(type) { - if (type == ",") return cont(comb, more); - if (type == end) return cont(); - return cont(more); - } - return function(type) { - if (type == end) return cont(); - return pass(comb, more); - }; - } - - function stat_of(comb, tag) { - return cont(pushlex("stat", tag), comb, poplex, block); - } - function block(type) { - if (type == "}") return cont(); - if (type == "let") return stat_of(letdef1, "let"); - if (type == "fn") return stat_of(fndef); - if (type == "type") return cont(pushlex("stat"), tydef, endstatement, poplex, block); - if (type == "enum") return stat_of(enumdef); - if (type == "mod") return stat_of(mod); - if (type == "iface") return stat_of(iface); - if (type == "impl") return stat_of(impl); - if (type == "open-attr") return cont(pushlex("]"), commasep(expression, "]"), poplex); - if (type == "ignore" || type.match(/[\]\);,]/)) return cont(block); - return pass(pushlex("stat"), expression, poplex, endstatement, block); - } - function endstatement(type) { - if (type == ";") return cont(); - return pass(); - } - function expression(type) { - if (type == "atom" || type == "name") return cont(maybeop); - if (type == "{") return cont(pushlex("}"), exprbrace, poplex); - if (type.match(/[\[\(]/)) return matchBrackets(type, expression); - if (type.match(/[\]\)\};,]/)) return pass(); - if (type == "if-style") return cont(expression, expression); - if (type == "else-style" || type == "op") return cont(expression); - if (type == "for") return cont(pattern, maybetype, inop, expression, expression); - if (type == "alt") return cont(expression, altbody); - if (type == "fn") return cont(fndef); - if (type == "macro") return cont(macro); - return cont(); - } - function maybeop(type) { - if (content == ".") return cont(maybeprop); - if (content == "::<"){return cont(typarams, maybeop);} - if (type == "op" || content == ":") return cont(expression); - if (type == "(" || type == "[") return matchBrackets(type, expression); - return pass(); - } - function maybeprop(type) { - if (content.match(/^\w+$/)) {cx.marked = "variable"; return cont(maybeop);} - return pass(expression); - } - function exprbrace(type) { - if (type == "op") { - if (content == "|") return cont(blockvars, poplex, pushlex("}", "block"), block); - if (content == "||") return cont(poplex, pushlex("}", "block"), block); - } - if (content == "mutable" || (content.match(/^\w+$/) && cx.stream.peek() == ":" - && !cx.stream.match("::", false))) - return pass(record_of(expression)); - return pass(block); - } - function record_of(comb) { - function ro(type) { - if (content == "mutable" || content == "with") {cx.marked = "keyword"; return cont(ro);} - if (content.match(/^\w*$/)) {cx.marked = "variable"; return cont(ro);} - if (type == ":") return cont(comb, ro); - if (type == "}") return cont(); - return cont(ro); - } - return ro; - } - function blockvars(type) { - if (type == "name") {cx.marked = "def"; return cont(blockvars);} - if (type == "op" && content == "|") return cont(); - return cont(blockvars); - } - - function letdef1(type) { - if (type.match(/[\]\)\};]/)) return cont(); - if (content == "=") return cont(expression, letdef2); - if (type == ",") return cont(letdef1); - return pass(pattern, maybetype, letdef1); - } - function letdef2(type) { - if (type.match(/[\]\)\};,]/)) return pass(letdef1); - else return pass(expression, letdef2); - } - function maybetype(type) { - if (type == ":") return cont(typecx, rtype, valcx); - return pass(); - } - function inop(type) { - if (type == "name" && content == "in") {cx.marked = "keyword"; return cont();} - return pass(); - } - function fndef(type) { - if (content == "@" || content == "~") {cx.marked = "keyword"; return cont(fndef);} - if (type == "name") {cx.marked = "def"; return cont(fndef);} - if (content == "<") return cont(typarams, fndef); - if (type == "{") return pass(expression); - if (type == "(") return cont(pushlex(")"), commasep(argdef, ")"), poplex, fndef); - if (type == "->") return cont(typecx, rtype, valcx, fndef); - if (type == ";") return cont(); - return cont(fndef); - } - function tydef(type) { - if (type == "name") {cx.marked = "def"; return cont(tydef);} - if (content == "<") return cont(typarams, tydef); - if (content == "=") return cont(typecx, rtype, valcx); - return cont(tydef); - } - function enumdef(type) { - if (type == "name") {cx.marked = "def"; return cont(enumdef);} - if (content == "<") return cont(typarams, enumdef); - if (content == "=") return cont(typecx, rtype, valcx, endstatement); - if (type == "{") return cont(pushlex("}"), typecx, enumblock, valcx, poplex); - return cont(enumdef); - } - function enumblock(type) { - if (type == "}") return cont(); - if (type == "(") return cont(pushlex(")"), commasep(rtype, ")"), poplex, enumblock); - if (content.match(/^\w+$/)) cx.marked = "def"; - return cont(enumblock); - } - function mod(type) { - if (type == "name") {cx.marked = "def"; return cont(mod);} - if (type == "{") return cont(pushlex("}"), block, poplex); - return pass(); - } - function iface(type) { - if (type == "name") {cx.marked = "def"; return cont(iface);} - if (content == "<") return cont(typarams, iface); - if (type == "{") return cont(pushlex("}"), block, poplex); - return pass(); - } - function impl(type) { - if (content == "<") return cont(typarams, impl); - if (content == "of" || content == "for") {cx.marked = "keyword"; return cont(rtype, impl);} - if (type == "name") {cx.marked = "def"; return cont(impl);} - if (type == "{") return cont(pushlex("}"), block, poplex); - return pass(); - } - function typarams(type) { - if (content == ">") return cont(); - if (content == ",") return cont(typarams); - if (content == ":") return cont(rtype, typarams); - return pass(rtype, typarams); - } - function argdef(type) { - if (type == "name") {cx.marked = "def"; return cont(argdef);} - if (type == ":") return cont(typecx, rtype, valcx); - return pass(); - } - function rtype(type) { - if (type == "name") {cx.marked = "variable-3"; return cont(rtypemaybeparam); } - if (content == "mutable") {cx.marked = "keyword"; return cont(rtype);} - if (type == "atom") return cont(rtypemaybeparam); - if (type == "op" || type == "obj") return cont(rtype); - if (type == "fn") return cont(fntype); - if (type == "{") return cont(pushlex("{"), record_of(rtype), poplex); - return matchBrackets(type, rtype); - } - function rtypemaybeparam(type) { - if (content == "<") return cont(typarams); - return pass(); - } - function fntype(type) { - if (type == "(") return cont(pushlex("("), commasep(rtype, ")"), poplex, fntype); - if (type == "->") return cont(rtype); - return pass(); - } - function pattern(type) { - if (type == "name") {cx.marked = "def"; return cont(patternmaybeop);} - if (type == "atom") return cont(patternmaybeop); - if (type == "op") return cont(pattern); - if (type.match(/[\]\)\};,]/)) return pass(); - return matchBrackets(type, pattern); - } - function patternmaybeop(type) { - if (type == "op" && content == ".") return cont(); - if (content == "to") {cx.marked = "keyword"; return cont(pattern);} - else return pass(); - } - function altbody(type) { - if (type == "{") return cont(pushlex("}", "alt"), altblock1, poplex); - return pass(); - } - function altblock1(type) { - if (type == "}") return cont(); - if (type == "|") return cont(altblock1); - if (content == "when") {cx.marked = "keyword"; return cont(expression, altblock2);} - if (type.match(/[\]\);,]/)) return cont(altblock1); - return pass(pattern, altblock2); - } - function altblock2(type) { - if (type == "{") return cont(pushlex("}", "alt"), block, poplex, altblock1); - else return pass(altblock1); - } - - function macro(type) { - if (type.match(/[\[\(\{]/)) return matchBrackets(type, expression); - return pass(); - } - function matchBrackets(type, comb) { - if (type == "[") return cont(pushlex("]"), commasep(comb, "]"), poplex); - if (type == "(") return cont(pushlex(")"), commasep(comb, ")"), poplex); - if (type == "{") return cont(pushlex("}"), commasep(comb, "}"), poplex); - return cont(); - } - - function parse(state, stream, style) { - var cc = state.cc; - // Communicate our context to the combinators. - // (Less wasteful than consing up a hundred closures on every call.) - cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc; - - while (true) { - var combinator = cc.length ? cc.pop() : block; - if (combinator(tcat)) { - while(cc.length && cc[cc.length - 1].lex) - cc.pop()(); - return cx.marked || style; - } - } - } - - return { - startState: function() { - return { - tokenize: tokenBase, - cc: [], - lexical: {indented: -indentUnit, column: 0, type: "top", align: false}, - keywords: valKeywords, - indented: 0 - }; - }, - - token: function(stream, state) { - if (stream.sol()) { - if (!state.lexical.hasOwnProperty("align")) - state.lexical.align = false; - state.indented = stream.indentation(); - } - if (stream.eatSpace()) return null; - tcat = content = null; - var style = state.tokenize(stream, state); - if (style == "comment") return style; - if (!state.lexical.hasOwnProperty("align")) - state.lexical.align = true; - if (tcat == "prefix") return style; - if (!content) content = stream.current(); - return parse(state, stream, style); - }, - - indent: function(state, textAfter) { - if (state.tokenize != tokenBase) return 0; - var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical, - type = lexical.type, closing = firstChar == type; - if (type == "stat") return lexical.indented + indentUnit; - if (lexical.align) return lexical.column + (closing ? 0 : 1); - return lexical.indented + (closing ? 0 : (lexical.info == "alt" ? altIndentUnit : indentUnit)); - }, - - electricChars: "{}" - }; -}); - -CodeMirror.defineMIME("text/x-rustsrc", "rust"); diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/scheme/index.html b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/scheme/index.html deleted file mode 100644 index 84038e327e..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/scheme/index.html +++ /dev/null @@ -1,65 +0,0 @@ - - - - - CodeMirror: Scheme mode - - - - - - - -

    CodeMirror: Scheme mode

    -
    - - -

    MIME types defined: text/x-scheme.

    - - - diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/scheme/scheme.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/scheme/scheme.js deleted file mode 100644 index 5a74100590..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/scheme/scheme.js +++ /dev/null @@ -1,230 +0,0 @@ -/** - * Author: Koh Zi Han, based on implementation by Koh Zi Chun - */ -CodeMirror.defineMode("scheme", function (config, mode) { - var BUILTIN = "builtin", COMMENT = "comment", STRING = "string", - ATOM = "atom", NUMBER = "number", BRACKET = "bracket", KEYWORD="keyword"; - var INDENT_WORD_SKIP = 2, KEYWORDS_SKIP = 1; - - function makeKeywords(str) { - var obj = {}, words = str.split(" "); - for (var i = 0; i < words.length; ++i) obj[words[i]] = true; - return obj; - } - - var keywords = makeKeywords("λ case-lambda call/cc class define-class exit-handler field import inherit init-field interface let*-values let-values let/ec mixin opt-lambda override protect provide public rename require require-for-syntax syntax syntax-case syntax-error unit/sig unless when with-syntax and begin call-with-current-continuation call-with-input-file call-with-output-file case cond define define-syntax delay do dynamic-wind else for-each if lambda let let* let-syntax letrec letrec-syntax map or syntax-rules abs acos angle append apply asin assoc assq assv atan boolean? caar cadr call-with-input-file call-with-output-file call-with-values car cdddar cddddr cdr ceiling char->integer char-alphabetic? char-ci<=? char-ci=? char-ci>? char-downcase char-lower-case? char-numeric? char-ready? char-upcase char-upper-case? char-whitespace? char<=? char=? char>? char? close-input-port close-output-port complex? cons cos current-input-port current-output-port denominator display eof-object? eq? equal? eqv? eval even? exact->inexact exact? exp expt #f floor force gcd imag-part inexact->exact inexact? input-port? integer->char integer? interaction-environment lcm length list list->string list->vector list-ref list-tail list? load log magnitude make-polar make-rectangular make-string make-vector max member memq memv min modulo negative? newline not null-environment null? number->string number? numerator odd? open-input-file open-output-file output-port? pair? peek-char port? positive? procedure? quasiquote quote quotient rational? rationalize read read-char real-part real? remainder reverse round scheme-report-environment set! set-car! set-cdr! sin sqrt string string->list string->number string->symbol string-append string-ci<=? string-ci=? string-ci>? string-copy string-fill! string-length string-ref string-set! string<=? string=? string>? string? substring symbol->string symbol? #t tan transcript-off transcript-on truncate values vector vector->list vector-fill! vector-length vector-ref vector-set! with-input-from-file with-output-to-file write write-char zero?"); - var indentKeys = makeKeywords("define let letrec let* lambda"); - - function stateStack(indent, type, prev) { // represents a state stack object - this.indent = indent; - this.type = type; - this.prev = prev; - } - - function pushStack(state, indent, type) { - state.indentStack = new stateStack(indent, type, state.indentStack); - } - - function popStack(state) { - state.indentStack = state.indentStack.prev; - } - - var binaryMatcher = new RegExp(/^(?:[-+]i|[-+][01]+#*(?:\/[01]+#*)?i|[-+]?[01]+#*(?:\/[01]+#*)?@[-+]?[01]+#*(?:\/[01]+#*)?|[-+]?[01]+#*(?:\/[01]+#*)?[-+](?:[01]+#*(?:\/[01]+#*)?)?i|[-+]?[01]+#*(?:\/[01]+#*)?)(?=[()\s;"]|$)/i); - var octalMatcher = new RegExp(/^(?:[-+]i|[-+][0-7]+#*(?:\/[0-7]+#*)?i|[-+]?[0-7]+#*(?:\/[0-7]+#*)?@[-+]?[0-7]+#*(?:\/[0-7]+#*)?|[-+]?[0-7]+#*(?:\/[0-7]+#*)?[-+](?:[0-7]+#*(?:\/[0-7]+#*)?)?i|[-+]?[0-7]+#*(?:\/[0-7]+#*)?)(?=[()\s;"]|$)/i); - var hexMatcher = new RegExp(/^(?:[-+]i|[-+][\da-f]+#*(?:\/[\da-f]+#*)?i|[-+]?[\da-f]+#*(?:\/[\da-f]+#*)?@[-+]?[\da-f]+#*(?:\/[\da-f]+#*)?|[-+]?[\da-f]+#*(?:\/[\da-f]+#*)?[-+](?:[\da-f]+#*(?:\/[\da-f]+#*)?)?i|[-+]?[\da-f]+#*(?:\/[\da-f]+#*)?)(?=[()\s;"]|$)/i); - var decimalMatcher = new RegExp(/^(?:[-+]i|[-+](?:(?:(?:\d+#+\.?#*|\d+\.\d*#*|\.\d+#*|\d+)(?:[esfdl][-+]?\d+)?)|\d+#*\/\d+#*)i|[-+]?(?:(?:(?:\d+#+\.?#*|\d+\.\d*#*|\.\d+#*|\d+)(?:[esfdl][-+]?\d+)?)|\d+#*\/\d+#*)@[-+]?(?:(?:(?:\d+#+\.?#*|\d+\.\d*#*|\.\d+#*|\d+)(?:[esfdl][-+]?\d+)?)|\d+#*\/\d+#*)|[-+]?(?:(?:(?:\d+#+\.?#*|\d+\.\d*#*|\.\d+#*|\d+)(?:[esfdl][-+]?\d+)?)|\d+#*\/\d+#*)[-+](?:(?:(?:\d+#+\.?#*|\d+\.\d*#*|\.\d+#*|\d+)(?:[esfdl][-+]?\d+)?)|\d+#*\/\d+#*)?i|(?:(?:(?:\d+#+\.?#*|\d+\.\d*#*|\.\d+#*|\d+)(?:[esfdl][-+]?\d+)?)|\d+#*\/\d+#*))(?=[()\s;"]|$)/i); - - function isBinaryNumber (stream) { - return stream.match(binaryMatcher); - } - - function isOctalNumber (stream) { - return stream.match(octalMatcher); - } - - function isDecimalNumber (stream, backup) { - if (backup === true) { - stream.backUp(1); - } - return stream.match(decimalMatcher); - } - - function isHexNumber (stream) { - return stream.match(hexMatcher); - } - - return { - startState: function () { - return { - indentStack: null, - indentation: 0, - mode: false, - sExprComment: false - }; - }, - - token: function (stream, state) { - if (state.indentStack == null && stream.sol()) { - // update indentation, but only if indentStack is empty - state.indentation = stream.indentation(); - } - - // skip spaces - if (stream.eatSpace()) { - return null; - } - var returnType = null; - - switch(state.mode){ - case "string": // multi-line string parsing mode - var next, escaped = false; - while ((next = stream.next()) != null) { - if (next == "\"" && !escaped) { - - state.mode = false; - break; - } - escaped = !escaped && next == "\\"; - } - returnType = STRING; // continue on in scheme-string mode - break; - case "comment": // comment parsing mode - var next, maybeEnd = false; - while ((next = stream.next()) != null) { - if (next == "#" && maybeEnd) { - - state.mode = false; - break; - } - maybeEnd = (next == "|"); - } - returnType = COMMENT; - break; - case "s-expr-comment": // s-expr commenting mode - state.mode = false; - if(stream.peek() == "(" || stream.peek() == "["){ - // actually start scheme s-expr commenting mode - state.sExprComment = 0; - }else{ - // if not we just comment the entire of the next token - stream.eatWhile(/[^/s]/); // eat non spaces - returnType = COMMENT; - break; - } - default: // default parsing mode - var ch = stream.next(); - - if (ch == "\"") { - state.mode = "string"; - returnType = STRING; - - } else if (ch == "'") { - returnType = ATOM; - } else if (ch == '#') { - if (stream.eat("|")) { // Multi-line comment - state.mode = "comment"; // toggle to comment mode - returnType = COMMENT; - } else if (stream.eat(/[tf]/i)) { // #t/#f (atom) - returnType = ATOM; - } else if (stream.eat(';')) { // S-Expr comment - state.mode = "s-expr-comment"; - returnType = COMMENT; - } else { - var numTest = null, hasExactness = false, hasRadix = true; - if (stream.eat(/[ei]/i)) { - hasExactness = true; - } else { - stream.backUp(1); // must be radix specifier - } - if (stream.match(/^#b/i)) { - numTest = isBinaryNumber; - } else if (stream.match(/^#o/i)) { - numTest = isOctalNumber; - } else if (stream.match(/^#x/i)) { - numTest = isHexNumber; - } else if (stream.match(/^#d/i)) { - numTest = isDecimalNumber; - } else if (stream.match(/^[-+0-9.]/, false)) { - hasRadix = false; - numTest = isDecimalNumber; - // re-consume the intial # if all matches failed - } else if (!hasExactness) { - stream.eat('#'); - } - if (numTest != null) { - if (hasRadix && !hasExactness) { - // consume optional exactness after radix - stream.match(/^#[ei]/i); - } - if (numTest(stream)) - returnType = NUMBER; - } - } - } else if (/^[-+0-9.]/.test(ch) && isDecimalNumber(stream, true)) { // match non-prefixed number, must be decimal - returnType = NUMBER; - } else if (ch == ";") { // comment - stream.skipToEnd(); // rest of the line is a comment - returnType = COMMENT; - } else if (ch == "(" || ch == "[") { - var keyWord = ''; var indentTemp = stream.column(), letter; - /** - Either - (indent-word .. - (non-indent-word .. - (;something else, bracket, etc. - */ - - while ((letter = stream.eat(/[^\s\(\[\;\)\]]/)) != null) { - keyWord += letter; - } - - if (keyWord.length > 0 && indentKeys.propertyIsEnumerable(keyWord)) { // indent-word - - pushStack(state, indentTemp + INDENT_WORD_SKIP, ch); - } else { // non-indent word - // we continue eating the spaces - stream.eatSpace(); - if (stream.eol() || stream.peek() == ";") { - // nothing significant after - // we restart indentation 1 space after - pushStack(state, indentTemp + 1, ch); - } else { - pushStack(state, indentTemp + stream.current().length, ch); // else we match - } - } - stream.backUp(stream.current().length - 1); // undo all the eating - - if(typeof state.sExprComment == "number") state.sExprComment++; - - returnType = BRACKET; - } else if (ch == ")" || ch == "]") { - returnType = BRACKET; - if (state.indentStack != null && state.indentStack.type == (ch == ")" ? "(" : "[")) { - popStack(state); - - if(typeof state.sExprComment == "number"){ - if(--state.sExprComment == 0){ - returnType = COMMENT; // final closing bracket - state.sExprComment = false; // turn off s-expr commenting mode - } - } - } - } else { - stream.eatWhile(/[\w\$_\-!$%&*+\.\/:<=>?@\^~]/); - - if (keywords && keywords.propertyIsEnumerable(stream.current())) { - returnType = BUILTIN; - } else returnType = "variable"; - } - } - return (typeof state.sExprComment == "number") ? COMMENT : returnType; - }, - - indent: function (state, textAfter) { - if (state.indentStack == null) return state.indentation; - return state.indentStack.indent; - } - }; -}); - -CodeMirror.defineMIME("text/x-scheme", "scheme"); diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/shell/index.html b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/shell/index.html deleted file mode 100644 index 91b8707dbc..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/shell/index.html +++ /dev/null @@ -1,50 +0,0 @@ - - -CodeMirror: Shell mode - - - - - - - - - -

    CodeMirror: Shell mode

    - - - - - -

    MIME types defined: text/x-sh.

    diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/shell/shell.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/shell/shell.js deleted file mode 100644 index 1e75d3b3af..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/shell/shell.js +++ /dev/null @@ -1,118 +0,0 @@ -CodeMirror.defineMode('shell', function(config) { - - var words = {}; - function define(style, string) { - var split = string.split(' '); - for(var i = 0; i < split.length; i++) { - words[split[i]] = style; - } - }; - - // Atoms - define('atom', 'true false'); - - // Keywords - define('keyword', 'if then do else elif while until for in esac fi fin ' + - 'fil done exit set unset export function'); - - // Commands - define('builtin', 'ab awk bash beep cat cc cd chown chmod chroot clear cp ' + - 'curl cut diff echo find gawk gcc get git grep kill killall ln ls make ' + - 'mkdir openssl mv nc node npm ping ps restart rm rmdir sed service sh ' + - 'shopt shred source sort sleep ssh start stop su sudo tee telnet top ' + - 'touch vi vim wall wc wget who write yes zsh'); - - function tokenBase(stream, state) { - - var sol = stream.sol(); - var ch = stream.next(); - - if (ch === '\'' || ch === '"' || ch === '`') { - state.tokens.unshift(tokenString(ch)); - return tokenize(stream, state); - } - if (ch === '#') { - if (sol && stream.eat('!')) { - stream.skipToEnd(); - return 'meta'; // 'comment'? - } - stream.skipToEnd(); - return 'comment'; - } - if (ch === '$') { - state.tokens.unshift(tokenDollar); - return tokenize(stream, state); - } - if (ch === '+' || ch === '=') { - return 'operator'; - } - if (ch === '-') { - stream.eat('-'); - stream.eatWhile(/\w/); - return 'attribute'; - } - if (/\d/.test(ch)) { - stream.eatWhile(/\d/); - if(!/\w/.test(stream.peek())) { - return 'number'; - } - } - stream.eatWhile(/\w/); - var cur = stream.current(); - if (stream.peek() === '=' && /\w+/.test(cur)) return 'def'; - return words.hasOwnProperty(cur) ? words[cur] : null; - } - - function tokenString(quote) { - return function(stream, state) { - var next, end = false, escaped = false; - while ((next = stream.next()) != null) { - if (next === quote && !escaped) { - end = true; - break; - } - if (next === '$' && !escaped && quote !== '\'') { - escaped = true; - stream.backUp(1); - state.tokens.unshift(tokenDollar); - break; - } - escaped = !escaped && next === '\\'; - } - if (end || !escaped) { - state.tokens.shift(); - } - return (quote === '`' || quote === ')' ? 'quote' : 'string'); - }; - }; - - var tokenDollar = function(stream, state) { - if (state.tokens.length > 1) stream.eat('$'); - var ch = stream.next(), hungry = /\w/; - if (ch === '{') hungry = /[^}]/; - if (ch === '(') { - state.tokens[0] = tokenString(')'); - return tokenize(stream, state); - } - if (!/\d/.test(ch)) { - stream.eatWhile(hungry); - stream.eat('}'); - } - state.tokens.shift(); - return 'def'; - }; - - function tokenize(stream, state) { - return (state.tokens[0] || tokenBase) (stream, state); - }; - - return { - startState: function() {return {tokens:[]};}, - token: function(stream, state) { - if (stream.eatSpace()) return null; - return tokenize(stream, state); - } - }; -}); - -CodeMirror.defineMIME('text/x-sh', 'shell'); diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/sieve/LICENSE b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/sieve/LICENSE deleted file mode 100644 index 43be294cb8..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/sieve/LICENSE +++ /dev/null @@ -1,23 +0,0 @@ -Copyright (C) 2012 Thomas Schmid - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - -Please note that some subdirectories of the CodeMirror distribution -include their own LICENSE files, and are released under different -licences. diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/sieve/index.html b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/sieve/index.html deleted file mode 100644 index ad2566f740..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/sieve/index.html +++ /dev/null @@ -1,81 +0,0 @@ - - - - - CodeMirror: Sieve (RFC5228) mode - - - - - - - -

    CodeMirror: Sieve (RFC5228) mode

    -
    - - -

    MIME types defined: application/sieve.

    - - - diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/sieve/sieve.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/sieve/sieve.js deleted file mode 100644 index e5a653c3e4..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/sieve/sieve.js +++ /dev/null @@ -1,156 +0,0 @@ -/* - * See LICENSE in this directory for the license under which this code - * is released. - */ - -CodeMirror.defineMode("sieve", function(config) { - function words(str) { - var obj = {}, words = str.split(" "); - for (var i = 0; i < words.length; ++i) obj[words[i]] = true; - return obj; - } - - var keywords = words("if elsif else stop require"); - var atoms = words("true false not"); - var indentUnit = config.indentUnit; - - function tokenBase(stream, state) { - - var ch = stream.next(); - if (ch == "/" && stream.eat("*")) { - state.tokenize = tokenCComment; - return tokenCComment(stream, state); - } - - if (ch === '#') { - stream.skipToEnd(); - return "comment"; - } - - if (ch == "\"") { - state.tokenize = tokenString(ch); - return state.tokenize(stream, state); - } - - if (ch === "{") - { - state._indent++; - return null; - } - - if (ch === "}") - { - state._indent--; - return null; - } - - if (/[{}\(\),;]/.test(ch)) - return null; - - // 1*DIGIT "K" / "M" / "G" - if (/\d/.test(ch)) { - stream.eatWhile(/[\d]/); - stream.eat(/[KkMmGg]/); - return "number"; - } - - // ":" (ALPHA / "_") *(ALPHA / DIGIT / "_") - if (ch == ":") { - stream.eatWhile(/[a-zA-Z_]/); - stream.eatWhile(/[a-zA-Z0-9_]/); - - return "operator"; - } - - stream.eatWhile(/[\w\$_]/); - var cur = stream.current(); - - // "text:" *(SP / HTAB) (hash-comment / CRLF) - // *(multiline-literal / multiline-dotstart) - // "." CRLF - if ((cur == "text") && stream.eat(":")) - { - state.tokenize = tokenMultiLineString; - return "string"; - } - - if (keywords.propertyIsEnumerable(cur)) - return "keyword"; - - if (atoms.propertyIsEnumerable(cur)) - return "atom"; - } - - function tokenMultiLineString(stream, state) - { - state._multiLineString = true; - // the first line is special it may contain a comment - if (!stream.sol()) { - stream.eatSpace(); - - if (stream.peek() == "#") { - stream.skipToEnd(); - return "comment"; - } - - stream.skipToEnd(); - return "string"; - } - - if ((stream.next() == ".") && (stream.eol())) - { - state._multiLineString = false; - state.tokenize = tokenBase; - } - - return "string"; - } - - function tokenCComment(stream, state) { - var maybeEnd = false, ch; - while ((ch = stream.next()) != null) { - if (maybeEnd && ch == "/") { - state.tokenize = tokenBase; - break; - } - maybeEnd = (ch == "*"); - } - return "comment"; - } - - function tokenString(quote) { - return function(stream, state) { - var escaped = false, ch; - while ((ch = stream.next()) != null) { - if (ch == quote && !escaped) - break; - escaped = !escaped && ch == "\\"; - } - if (!escaped) state.tokenize = tokenBase; - return "string"; - }; - } - - return { - startState: function(base) { - return {tokenize: tokenBase, - baseIndent: base || 0, - _indent: 0}; - }, - - token: function(stream, state) { - if (stream.eatSpace()) - return null; - - return (state.tokenize || tokenBase)(stream, state);; - }, - - indent: function(state, textAfter) { - return state.baseIndent + state._indent * indentUnit; - }, - - electricChars: "}" - }; -}); - -CodeMirror.defineMIME("application/sieve", "sieve"); diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/smalltalk/index.html b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/smalltalk/index.html deleted file mode 100644 index 811a7a0fdf..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/smalltalk/index.html +++ /dev/null @@ -1,56 +0,0 @@ - - - - - CodeMirror: Smalltalk mode - - - - - - - -

    CodeMirror: Smalltalk mode

    - -
    - - - -

    Simple Smalltalk mode.

    - -

    MIME types defined: text/x-stsrc.

    - - diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/smalltalk/smalltalk.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/smalltalk/smalltalk.js deleted file mode 100644 index 572f2026ab..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/smalltalk/smalltalk.js +++ /dev/null @@ -1,139 +0,0 @@ -CodeMirror.defineMode('smalltalk', function(config, modeConfig) { - - var specialChars = /[+\-/\\*~<>=@%|&?!.:;^]/; - var keywords = /true|false|nil|self|super|thisContext/; - - var Context = function(tokenizer, parent) { - this.next = tokenizer; - this.parent = parent; - }; - - var Token = function(name, context, eos) { - this.name = name; - this.context = context; - this.eos = eos; - }; - - var State = function() { - this.context = new Context(next, null); - this.expectVariable = true; - this.indentation = 0; - this.userIndentationDelta = 0; - }; - - State.prototype.userIndent = function(indentation) { - this.userIndentationDelta = indentation > 0 ? (indentation / config.indentUnit - this.indentation) : 0; - }; - - var next = function(stream, context, state) { - var token = new Token(null, context, false); - var aChar = stream.next(); - - if (aChar === '"') { - token = nextComment(stream, new Context(nextComment, context)); - - } else if (aChar === '\'') { - token = nextString(stream, new Context(nextString, context)); - - } else if (aChar === '#') { - stream.eatWhile(/[^ .]/); - token.name = 'string-2'; - - } else if (aChar === '$') { - stream.eatWhile(/[^ ]/); - token.name = 'string-2'; - - } else if (aChar === '|' && state.expectVariable) { - token.context = new Context(nextTemporaries, context); - - } else if (/[\[\]{}()]/.test(aChar)) { - token.name = 'bracket'; - token.eos = /[\[{(]/.test(aChar); - - if (aChar === '[') { - state.indentation++; - } else if (aChar === ']') { - state.indentation = Math.max(0, state.indentation - 1); - } - - } else if (specialChars.test(aChar)) { - stream.eatWhile(specialChars); - token.name = 'operator'; - token.eos = aChar !== ';'; // ; cascaded message expression - - } else if (/\d/.test(aChar)) { - stream.eatWhile(/[\w\d]/); - token.name = 'number'; - - } else if (/[\w_]/.test(aChar)) { - stream.eatWhile(/[\w\d_]/); - token.name = state.expectVariable ? (keywords.test(stream.current()) ? 'keyword' : 'variable') : null; - - } else { - token.eos = state.expectVariable; - } - - return token; - }; - - var nextComment = function(stream, context) { - stream.eatWhile(/[^"]/); - return new Token('comment', stream.eat('"') ? context.parent : context, true); - }; - - var nextString = function(stream, context) { - stream.eatWhile(/[^']/); - return new Token('string', stream.eat('\'') ? context.parent : context, false); - }; - - var nextTemporaries = function(stream, context, state) { - var token = new Token(null, context, false); - var aChar = stream.next(); - - if (aChar === '|') { - token.context = context.parent; - token.eos = true; - - } else { - stream.eatWhile(/[^|]/); - token.name = 'variable'; - } - - return token; - }; - - return { - startState: function() { - return new State; - }, - - token: function(stream, state) { - state.userIndent(stream.indentation()); - - if (stream.eatSpace()) { - return null; - } - - var token = state.context.next(stream, state.context, state); - state.context = token.context; - state.expectVariable = token.eos; - - state.lastToken = token; - return token.name; - }, - - blankLine: function(state) { - state.userIndent(0); - }, - - indent: function(state, textAfter) { - var i = state.context.next === next && textAfter && textAfter.charAt(0) === ']' ? -1 : state.userIndentationDelta; - return (state.indentation + i) * config.indentUnit; - }, - - electricChars: ']' - }; - -}); - -CodeMirror.defineMIME('text/x-stsrc', {name: 'smalltalk'}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/smarty/index.html b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/smarty/index.html deleted file mode 100644 index 03314e0155..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/smarty/index.html +++ /dev/null @@ -1,83 +0,0 @@ - - - - - CodeMirror: Smarty mode - - - - - - - -

    CodeMirror: Smarty mode

    - -
    - - - -
    - -
    - - - -

    A plain text/Smarty mode which allows for custom delimiter tags (defaults to { and }).

    - -

    MIME types defined: text/x-smarty

    - - diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/smarty/smarty.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/smarty/smarty.js deleted file mode 100644 index dbee0848c5..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/smarty/smarty.js +++ /dev/null @@ -1,148 +0,0 @@ -CodeMirror.defineMode("smarty", function(config, parserConfig) { - var keyFuncs = ["debug", "extends", "function", "include", "literal"]; - var last; - var regs = { - operatorChars: /[+\-*&%=<>!?]/, - validIdentifier: /[a-zA-Z0-9\_]/, - stringChar: /[\'\"]/ - }; - var leftDelim = (typeof config.mode.leftDelimiter != 'undefined') ? config.mode.leftDelimiter : "{"; - var rightDelim = (typeof config.mode.rightDelimiter != 'undefined') ? config.mode.rightDelimiter : "}"; - function ret(style, lst) { last = lst; return style; } - - - function tokenizer(stream, state) { - function chain(parser) { - state.tokenize = parser; - return parser(stream, state); - } - - if (stream.match(leftDelim, true)) { - if (stream.eat("*")) { - return chain(inBlock("comment", "*" + rightDelim)); - } - else { - state.tokenize = inSmarty; - return "tag"; - } - } - else { - // I'd like to do an eatWhile() here, but I can't get it to eat only up to the rightDelim string/char - stream.next(); - return null; - } - } - - function inSmarty(stream, state) { - if (stream.match(rightDelim, true)) { - state.tokenize = tokenizer; - return ret("tag", null); - } - - var ch = stream.next(); - if (ch == "$") { - stream.eatWhile(regs.validIdentifier); - return ret("variable-2", "variable"); - } - else if (ch == ".") { - return ret("operator", "property"); - } - else if (regs.stringChar.test(ch)) { - state.tokenize = inAttribute(ch); - return ret("string", "string"); - } - else if (regs.operatorChars.test(ch)) { - stream.eatWhile(regs.operatorChars); - return ret("operator", "operator"); - } - else if (ch == "[" || ch == "]") { - return ret("bracket", "bracket"); - } - else if (/\d/.test(ch)) { - stream.eatWhile(/\d/); - return ret("number", "number"); - } - else { - if (state.last == "variable") { - if (ch == "@") { - stream.eatWhile(regs.validIdentifier); - return ret("property", "property"); - } - else if (ch == "|") { - stream.eatWhile(regs.validIdentifier); - return ret("qualifier", "modifier"); - } - } - else if (state.last == "whitespace") { - stream.eatWhile(regs.validIdentifier); - return ret("attribute", "modifier"); - } - else if (state.last == "property") { - stream.eatWhile(regs.validIdentifier); - return ret("property", null); - } - else if (/\s/.test(ch)) { - last = "whitespace"; - return null; - } - - var str = ""; - if (ch != "/") { - str += ch; - } - var c = ""; - while ((c = stream.eat(regs.validIdentifier))) { - str += c; - } - var i, j; - for (i=0, j=keyFuncs.length; i - - - - CodeMirror: SPARQL mode - - - - - - - -

    CodeMirror: SPARQL mode

    -
    - - -

    MIME types defined: application/x-sparql-query.

    - - - diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/sparql/sparql.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/sparql/sparql.js deleted file mode 100644 index 0dcdfac703..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/sparql/sparql.js +++ /dev/null @@ -1,143 +0,0 @@ -CodeMirror.defineMode("sparql", function(config) { - var indentUnit = config.indentUnit; - var curPunc; - - function wordRegexp(words) { - return new RegExp("^(?:" + words.join("|") + ")$", "i"); - } - var ops = wordRegexp(["str", "lang", "langmatches", "datatype", "bound", "sameterm", "isiri", "isuri", - "isblank", "isliteral", "union", "a"]); - var keywords = wordRegexp(["base", "prefix", "select", "distinct", "reduced", "construct", "describe", - "ask", "from", "named", "where", "order", "limit", "offset", "filter", "optional", - "graph", "by", "asc", "desc"]); - var operatorChars = /[*+\-<>=&|]/; - - function tokenBase(stream, state) { - var ch = stream.next(); - curPunc = null; - if (ch == "$" || ch == "?") { - stream.match(/^[\w\d]*/); - return "variable-2"; - } - else if (ch == "<" && !stream.match(/^[\s\u00a0=]/, false)) { - stream.match(/^[^\s\u00a0>]*>?/); - return "atom"; - } - else if (ch == "\"" || ch == "'") { - state.tokenize = tokenLiteral(ch); - return state.tokenize(stream, state); - } - else if (/[{}\(\),\.;\[\]]/.test(ch)) { - curPunc = ch; - return null; - } - else if (ch == "#") { - stream.skipToEnd(); - return "comment"; - } - else if (operatorChars.test(ch)) { - stream.eatWhile(operatorChars); - return null; - } - else if (ch == ":") { - stream.eatWhile(/[\w\d\._\-]/); - return "atom"; - } - else { - stream.eatWhile(/[_\w\d]/); - if (stream.eat(":")) { - stream.eatWhile(/[\w\d_\-]/); - return "atom"; - } - var word = stream.current(), type; - if (ops.test(word)) - return null; - else if (keywords.test(word)) - return "keyword"; - else - return "variable"; - } - } - - function tokenLiteral(quote) { - return function(stream, state) { - var escaped = false, ch; - while ((ch = stream.next()) != null) { - if (ch == quote && !escaped) { - state.tokenize = tokenBase; - break; - } - escaped = !escaped && ch == "\\"; - } - return "string"; - }; - } - - function pushContext(state, type, col) { - state.context = {prev: state.context, indent: state.indent, col: col, type: type}; - } - function popContext(state) { - state.indent = state.context.indent; - state.context = state.context.prev; - } - - return { - startState: function(base) { - return {tokenize: tokenBase, - context: null, - indent: 0, - col: 0}; - }, - - token: function(stream, state) { - if (stream.sol()) { - if (state.context && state.context.align == null) state.context.align = false; - state.indent = stream.indentation(); - } - if (stream.eatSpace()) return null; - var style = state.tokenize(stream, state); - - if (style != "comment" && state.context && state.context.align == null && state.context.type != "pattern") { - state.context.align = true; - } - - if (curPunc == "(") pushContext(state, ")", stream.column()); - else if (curPunc == "[") pushContext(state, "]", stream.column()); - else if (curPunc == "{") pushContext(state, "}", stream.column()); - else if (/[\]\}\)]/.test(curPunc)) { - while (state.context && state.context.type == "pattern") popContext(state); - if (state.context && curPunc == state.context.type) popContext(state); - } - else if (curPunc == "." && state.context && state.context.type == "pattern") popContext(state); - else if (/atom|string|variable/.test(style) && state.context) { - if (/[\}\]]/.test(state.context.type)) - pushContext(state, "pattern", stream.column()); - else if (state.context.type == "pattern" && !state.context.align) { - state.context.align = true; - state.context.col = stream.column(); - } - } - - return style; - }, - - indent: function(state, textAfter) { - var firstChar = textAfter && textAfter.charAt(0); - var context = state.context; - if (/[\]\}]/.test(firstChar)) - while (context && context.type == "pattern") context = context.prev; - - var closing = context && firstChar == context.type; - if (!context) - return 0; - else if (context.type == "pattern") - return context.col; - else if (context.align) - return context.col + (closing ? 0 : 1); - else - return context.indent + (closing ? 0 : indentUnit); - } - }; -}); - -CodeMirror.defineMIME("application/x-sparql-query", "sparql"); diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/stex/index.html b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/stex/index.html deleted file mode 100644 index 1e8c03a0e4..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/stex/index.html +++ /dev/null @@ -1,98 +0,0 @@ - - - - - CodeMirror: sTeX mode - - - - - - - -

    CodeMirror: sTeX mode

    -
    - - -

    MIME types defined: text/x-stex.

    - -

    Parsing/Highlighting Tests: normal, verbose.

    - - - diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/stex/stex.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/stex/stex.js deleted file mode 100644 index afc071375b..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/stex/stex.js +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Author: Constantin Jucovschi (c.jucovschi@jacobs-university.de) - * Licence: MIT - */ - -CodeMirror.defineMode("stex", function(cmCfg, modeCfg) -{ - function pushCommand(state, command) { - state.cmdState.push(command); - } - - function peekCommand(state) { - if (state.cmdState.length>0) - return state.cmdState[state.cmdState.length-1]; - else - return null; - } - - function popCommand(state) { - if (state.cmdState.length>0) { - var plug = state.cmdState.pop(); - plug.closeBracket(); - } - } - - function applyMostPowerful(state) { - var context = state.cmdState; - for (var i = context.length - 1; i >= 0; i--) { - var plug = context[i]; - if (plug.name=="DEFAULT") - continue; - return plug.styleIdentifier(); - } - return null; - } - - function addPluginPattern(pluginName, cmdStyle, brackets, styles) { - return function () { - this.name=pluginName; - this.bracketNo = 0; - this.style=cmdStyle; - this.styles = styles; - this.brackets = brackets; - - this.styleIdentifier = function(content) { - if (this.bracketNo<=this.styles.length) - return this.styles[this.bracketNo-1]; - else - return null; - }; - this.openBracket = function(content) { - this.bracketNo++; - return "bracket"; - }; - this.closeBracket = function(content) { - }; - }; - } - - var plugins = new Array(); - - plugins["importmodule"] = addPluginPattern("importmodule", "tag", "{[", ["string", "builtin"]); - plugins["documentclass"] = addPluginPattern("documentclass", "tag", "{[", ["", "atom"]); - plugins["usepackage"] = addPluginPattern("documentclass", "tag", "[", ["atom"]); - plugins["begin"] = addPluginPattern("documentclass", "tag", "[", ["atom"]); - plugins["end"] = addPluginPattern("documentclass", "tag", "[", ["atom"]); - - plugins["DEFAULT"] = function () { - this.name="DEFAULT"; - this.style="tag"; - - this.styleIdentifier = function(content) { - }; - this.openBracket = function(content) { - }; - this.closeBracket = function(content) { - }; - }; - - function setState(state, f) { - state.f = f; - } - - function normal(source, state) { - if (source.match(/^\\[a-zA-Z@]+/)) { - var cmdName = source.current(); - cmdName = cmdName.substr(1, cmdName.length-1); - var plug; - if (plugins.hasOwnProperty(cmdName)) { - plug = plugins[cmdName]; - } else { - plug = plugins["DEFAULT"]; - } - plug = new plug(); - pushCommand(state, plug); - setState(state, beginParams); - return plug.style; - } - - // escape characters - if (source.match(/^\\[$&%#{}_]/)) { - return "tag"; - } - - // white space control characters - if (source.match(/^\\[,;!\/]/)) { - return "tag"; - } - - var ch = source.next(); - if (ch == "%") { - // special case: % at end of its own line; stay in same state - if (!source.eol()) { - setState(state, inCComment); - } - return "comment"; - } - else if (ch=='}' || ch==']') { - plug = peekCommand(state); - if (plug) { - plug.closeBracket(ch); - setState(state, beginParams); - } else - return "error"; - return "bracket"; - } else if (ch=='{' || ch=='[') { - plug = plugins["DEFAULT"]; - plug = new plug(); - pushCommand(state, plug); - return "bracket"; - } - else if (/\d/.test(ch)) { - source.eatWhile(/[\w.%]/); - return "atom"; - } - else { - source.eatWhile(/[\w-_]/); - return applyMostPowerful(state); - } - } - - function inCComment(source, state) { - source.skipToEnd(); - setState(state, normal); - return "comment"; - } - - function beginParams(source, state) { - var ch = source.peek(); - if (ch == '{' || ch == '[') { - var lastPlug = peekCommand(state); - var style = lastPlug.openBracket(ch); - source.eat(ch); - setState(state, normal); - return "bracket"; - } - if (/[ \t\r]/.test(ch)) { - source.eat(ch); - return null; - } - setState(state, normal); - lastPlug = peekCommand(state); - if (lastPlug) { - popCommand(state); - } - return normal(source, state); - } - - return { - startState: function() { return { f:normal, cmdState:[] }; }, - copyState: function(s) { return { f: s.f, cmdState: s.cmdState.slice(0, s.cmdState.length) }; }, - - token: function(stream, state) { - var t = state.f(stream, state); - var w = stream.current(); - return t; - } - }; -}); - -CodeMirror.defineMIME("text/x-stex", "stex"); -CodeMirror.defineMIME("text/x-latex", "stex"); diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/stex/test.html b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/stex/test.html deleted file mode 100644 index e295f0a476..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/stex/test.html +++ /dev/null @@ -1,263 +0,0 @@ - - - - CodeMirror: sTeX mode - - - - - - - - -

    Tests for the sTeX Mode

    -

    Basics

    - - -

    Tags

    - - -

    Comments

    - - -

    Errors

    - - -

    Character Escapes

    - - -

    Spacing control

    - - - -

    New Commands

    - - Should be able to define a new command that happens to be a method on Array - (e.g. pop): - - -

    Summary

    - - - - - diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/stex/test.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/stex/test.js deleted file mode 100644 index d4f0343568..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/stex/test.js +++ /dev/null @@ -1,343 +0,0 @@ -var MT = ModeTest; -MT.modeName = 'stex'; -MT.modeOptions = {}; - -MT.testMode( - 'word', - 'foo', - [ - null, 'foo' - ] -); - -MT.testMode( - 'twoWords', - 'foo bar', - [ - null, 'foo bar' - ] -); - -MT.testMode( - 'beginEndDocument', - '\\begin{document}\n\\end{document}', - [ - 'tag', '\\begin', - 'bracket', '{', - 'atom', 'document', - 'bracket', '}', - 'tag', '\\end', - 'bracket', '{', - 'atom', 'document', - 'bracket', '}' - ] -); - -MT.testMode( - 'beginEndEquation', - '\\begin{equation}\n E=mc^2\n\\end{equation}', - [ - 'tag', '\\begin', - 'bracket', '{', - 'atom', 'equation', - 'bracket', '}', - null, ' E=mc^2', - 'tag', '\\end', - 'bracket', '{', - 'atom', 'equation', - 'bracket', '}' - ] -); - -MT.testMode( - 'beginModule', - '\\begin{module}[]', - [ - 'tag', '\\begin', - 'bracket', '{', - 'atom', 'module', - 'bracket', '}[]' - ] -); - -MT.testMode( - 'beginModuleId', - '\\begin{module}[id=bbt-size]', - [ - 'tag', '\\begin', - 'bracket', '{', - 'atom', 'module', - 'bracket', '}[', - null, 'id=bbt-size', - 'bracket', ']' - ] -); - -MT.testMode( - 'importModule', - '\\importmodule[b-b-t]{b-b-t}', - [ - 'tag', '\\importmodule', - 'bracket', '[', - 'string', 'b-b-t', - 'bracket', ']{', - 'builtin', 'b-b-t', - 'bracket', '}' - ] -); - -MT.testMode( - 'importModulePath', - '\\importmodule[\\KWARCslides{dmath/en/cardinality}]{card}', - [ - 'tag', '\\importmodule', - 'bracket', '[', - 'tag', '\\KWARCslides', - 'bracket', '{', - 'string', 'dmath/en/cardinality', - 'bracket', '}]{', - 'builtin', 'card', - 'bracket', '}' - ] -); - -MT.testMode( - 'psForPDF', - '\\PSforPDF[1]{#1}', // could treat #1 specially - [ - 'tag', '\\PSforPDF', - 'bracket', '[', - 'atom', '1', - 'bracket', ']{', - null, '#1', - 'bracket', '}' - ] -); - -MT.testMode( - 'comment', - '% foo', - [ - 'comment', '% foo' - ] -); - -MT.testMode( - 'tagComment', - '\\item% bar', - [ - 'tag', '\\item', - 'comment', '% bar' - ] -); - -MT.testMode( - 'commentTag', - ' % \\item', - [ - null, ' ', - 'comment', '% \\item' - ] -); - -MT.testMode( - 'commentLineBreak', - '%\nfoo', - [ - 'comment', '%', - null, 'foo' - ] -); - -MT.testMode( - 'tagErrorCurly', - '\\begin}{', - [ - 'tag', '\\begin', - 'error', '}', - 'bracket', '{' - ] -); - -MT.testMode( - 'tagErrorSquare', - '\\item]{', - [ - 'tag', '\\item', - 'error', ']', - 'bracket', '{' - ] -); - -MT.testMode( - 'commentCurly', - '% }', - [ - 'comment', '% }' - ] -); - -MT.testMode( - 'tagHash', - 'the \\# key', - [ - null, 'the ', - 'tag', '\\#', - null, ' key' - ] -); - -MT.testMode( - 'tagNumber', - 'a \\$5 stetson', - [ - null, 'a ', - 'tag', '\\$', - 'atom', 5, - null, ' stetson' - ] -); - -MT.testMode( - 'tagPercent', - '100\\% beef', - [ - 'atom', '100', - 'tag', '\\%', - null, ' beef' - ] -); - -MT.testMode( - 'tagAmpersand', - 'L \\& N', - [ - null, 'L ', - 'tag', '\\&', - null, ' N' - ] -); - -MT.testMode( - 'tagUnderscore', - 'foo\\_bar', - [ - null, 'foo', - 'tag', '\\_', - null, 'bar' - ] -); - -MT.testMode( - 'tagBracketOpen', - '\\emph{\\{}', - [ - 'tag', '\\emph', - 'bracket', '{', - 'tag', '\\{', - 'bracket', '}' - ] -); - -MT.testMode( - 'tagBracketClose', - '\\emph{\\}}', - [ - 'tag', '\\emph', - 'bracket', '{', - 'tag', '\\}', - 'bracket', '}' - ] -); - -MT.testMode( - 'tagLetterNumber', - 'section \\S1', - [ - null, 'section ', - 'tag', '\\S', - 'atom', '1' - ] -); - -MT.testMode( - 'textTagNumber', - 'para \\P2', - [ - null, 'para ', - 'tag', '\\P', - 'atom', '2' - ] -); - -MT.testMode( - 'thinspace', - 'x\\,y', // thinspace - [ - null, 'x', - 'tag', '\\,', - null, 'y' - ] -); - -MT.testMode( - 'thickspace', - 'x\\;y', // thickspace - [ - null, 'x', - 'tag', '\\;', - null, 'y' - ] -); - -MT.testMode( - 'negativeThinspace', - 'x\\!y', // negative thinspace - [ - null, 'x', - 'tag', '\\!', - null, 'y' - ] -); - -MT.testMode( - 'periodNotSentence', - 'J.\\ L.\\ is', // period not ending a sentence - [ - null, 'J.\\ L.\\ is' - ] -); // maybe could be better - -MT.testMode( - 'periodSentence', - 'X\\@. The', // period ending a sentence - [ - null, 'X', - 'tag', '\\@', - null, '. The' - ] -); - -MT.testMode( - 'italicCorrection', - '{\\em If\\/} I', // italic correction - [ - 'bracket', '{', - 'tag', '\\em', - null, ' If', - 'tag', '\\/', - 'bracket', '}', - null, ' I' - ] -); - -MT.testMode( - 'tagBracket', - '\\newcommand{\\pop}', - [ - 'tag', '\\newcommand', - 'bracket', '{', - 'tag', '\\pop', - 'bracket', '}' - ] -); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/tiddlywiki/index.html b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/tiddlywiki/index.html deleted file mode 100644 index f0dff188f9..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/tiddlywiki/index.html +++ /dev/null @@ -1,141 +0,0 @@ - - - - - CodeMirror: TiddlyWiki mode - - - - - - - - -

    CodeMirror: TiddlyWiki mode

    - -
    - - - -

    TiddlyWiki mode supports a single configuration.

    - -

    MIME types defined: text/x-tiddlywiki.

    - - diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/tiddlywiki/tiddlywiki.css b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/tiddlywiki/tiddlywiki.css deleted file mode 100644 index 0c37210d21..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/tiddlywiki/tiddlywiki.css +++ /dev/null @@ -1,14 +0,0 @@ -span.cm-underlined { - text-decoration: underline; -} -span.cm-strikethrough { - text-decoration: line-through; -} -span.cm-brace { - color: #170; - font-weight: bold; -} -span.cm-table { - color: blue; - font-weight: bold; -} diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/tiddlywiki/tiddlywiki.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/tiddlywiki/tiddlywiki.js deleted file mode 100644 index 7ec91e2ff5..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/tiddlywiki/tiddlywiki.js +++ /dev/null @@ -1,384 +0,0 @@ -/*** -|''Name''|tiddlywiki.js| -|''Description''|Enables TiddlyWikiy syntax highlighting using CodeMirror| -|''Author''|PMario| -|''Version''|0.1.7| -|''Status''|''stable''| -|''Source''|[[GitHub|https://github.com/pmario/CodeMirror2/blob/tw-syntax/mode/tiddlywiki]]| -|''Documentation''|http://codemirror.tiddlyspace.com/| -|''License''|[[MIT License|http://www.opensource.org/licenses/mit-license.php]]| -|''CoreVersion''|2.5.0| -|''Requires''|codemirror.js| -|''Keywords''|syntax highlighting color code mirror codemirror| -! Info -CoreVersion parameter is needed for TiddlyWiki only! -***/ -//{{{ -CodeMirror.defineMode("tiddlywiki", function (config, parserConfig) { - var indentUnit = config.indentUnit; - - // Tokenizer - var textwords = function () { - function kw(type) { - return { - type: type, - style: "text" - }; - } - return {}; - }(); - - var keywords = function () { - function kw(type) { - return { type: type, style: "macro"}; - } - return { - "allTags": kw('allTags'), "closeAll": kw('closeAll'), "list": kw('list'), - "newJournal": kw('newJournal'), "newTiddler": kw('newTiddler'), - "permaview": kw('permaview'), "saveChanges": kw('saveChanges'), - "search": kw('search'), "slider": kw('slider'), "tabs": kw('tabs'), - "tag": kw('tag'), "tagging": kw('tagging'), "tags": kw('tags'), - "tiddler": kw('tiddler'), "timeline": kw('timeline'), - "today": kw('today'), "version": kw('version'), "option": kw('option'), - - "with": kw('with'), - "filter": kw('filter') - }; - }(); - - var isSpaceName = /[\w_\-]/i, - reHR = /^\-\-\-\-+$/, //
    - reWikiCommentStart = /^\/\*\*\*$/, // /*** - reWikiCommentStop = /^\*\*\*\/$/, // ***/ - reBlockQuote = /^<<<$/, - - reJsCodeStart = /^\/\/\{\{\{$/, // //{{{ js block start - reJsCodeStop = /^\/\/\}\}\}$/, // //}}} js stop - reXmlCodeStart = /^$/, // xml block start - reXmlCodeStop = /^$/, // xml stop - - reCodeBlockStart = /^\{\{\{$/, // {{{ TW text div block start - reCodeBlockStop = /^\}\}\}$/, // }}} TW text stop - - reCodeStart = /\{\{\{/, // {{{ code span start - reUntilCodeStop = /.*?\}\}\}/; - - function chain(stream, state, f) { - state.tokenize = f; - return f(stream, state); - } - - // used for strings - function nextUntilUnescaped(stream, end) { - var escaped = false, - next; - while ((next = stream.next()) != null) { - if (next == end && !escaped) return false; - escaped = !escaped && next == "\\"; - } - return escaped; - } - - // Used as scratch variables to communicate multiple values without - // consing up tons of objects. - var type, content; - - function ret(tp, style, cont) { - type = tp; - content = cont; - return style; - } - - function jsTokenBase(stream, state) { - var sol = stream.sol(), - ch, tch; - - state.block = false; // indicates the start of a code block. - - ch = stream.peek(); // don't eat, to make matching simpler - - // check start of blocks - if (sol && /[<\/\*{}\-]/.test(ch)) { - if (stream.match(reCodeBlockStart)) { - state.block = true; - return chain(stream, state, twTokenCode); - } - if (stream.match(reBlockQuote)) { - return ret('quote', 'quote'); - } - if (stream.match(reWikiCommentStart) || stream.match(reWikiCommentStop)) { - return ret('code', 'comment'); - } - if (stream.match(reJsCodeStart) || stream.match(reJsCodeStop) || stream.match(reXmlCodeStart) || stream.match(reXmlCodeStop)) { - return ret('code', 'comment'); - } - if (stream.match(reHR)) { - return ret('hr', 'hr'); - } - } // sol - ch = stream.next(); - - if (sol && /[\/\*!#;:>|]/.test(ch)) { - if (ch == "!") { // tw header - stream.skipToEnd(); - return ret("header", "header"); - } - if (ch == "*") { // tw list - stream.eatWhile('*'); - return ret("list", "comment"); - } - if (ch == "#") { // tw numbered list - stream.eatWhile('#'); - return ret("list", "comment"); - } - if (ch == ";") { // definition list, term - stream.eatWhile(';'); - return ret("list", "comment"); - } - if (ch == ":") { // definition list, description - stream.eatWhile(':'); - return ret("list", "comment"); - } - if (ch == ">") { // single line quote - stream.eatWhile(">"); - return ret("quote", "quote"); - } - if (ch == '|') { - return ret('table', 'header'); - } - } - - if (ch == '{' && stream.match(/\{\{/)) { - return chain(stream, state, twTokenCode); - } - - // rudimentary html:// file:// link matching. TW knows much more ... - if (/[hf]/i.test(ch)) { - if (/[ti]/i.test(stream.peek()) && stream.match(/\b(ttps?|tp|ile):\/\/[\-A-Z0-9+&@#\/%?=~_|$!:,.;]*[A-Z0-9+&@#\/%=~_|$]/i)) { - return ret("link", "link"); - } - } - // just a little string indicator, don't want to have the whole string covered - if (ch == '"') { - return ret('string', 'string'); - } - if (ch == '~') { // _no_ CamelCase indicator should be bold - return ret('text', 'brace'); - } - if (/[\[\]]/.test(ch)) { // check for [[..]] - if (stream.peek() == ch) { - stream.next(); - return ret('brace', 'brace'); - } - } - if (ch == "@") { // check for space link. TODO fix @@...@@ highlighting - stream.eatWhile(isSpaceName); - return ret("link", "link"); - } - if (/\d/.test(ch)) { // numbers - stream.eatWhile(/\d/); - return ret("number", "number"); - } - if (ch == "/") { // tw invisible comment - if (stream.eat("%")) { - return chain(stream, state, twTokenComment); - } - else if (stream.eat("/")) { // - return chain(stream, state, twTokenEm); - } - } - if (ch == "_") { // tw underline - if (stream.eat("_")) { - return chain(stream, state, twTokenUnderline); - } - } - // strikethrough and mdash handling - if (ch == "-") { - if (stream.eat("-")) { - // if strikethrough looks ugly, change CSS. - if (stream.peek() != ' ') - return chain(stream, state, twTokenStrike); - // mdash - if (stream.peek() == ' ') - return ret('text', 'brace'); - } - } - if (ch == "'") { // tw bold - if (stream.eat("'")) { - return chain(stream, state, twTokenStrong); - } - } - if (ch == "<") { // tw macro - if (stream.eat("<")) { - return chain(stream, state, twTokenMacro); - } - } - else { - return ret(ch); - } - - // core macro handling - stream.eatWhile(/[\w\$_]/); - var word = stream.current(), - known = textwords.propertyIsEnumerable(word) && textwords[word]; - - return known ? ret(known.type, known.style, word) : ret("text", null, word); - - } // jsTokenBase() - - function twTokenString(quote) { - return function (stream, state) { - if (!nextUntilUnescaped(stream, quote)) state.tokenize = jsTokenBase; - return ret("string", "string"); - }; - } - - // tw invisible comment - function twTokenComment(stream, state) { - var maybeEnd = false, - ch; - while (ch = stream.next()) { - if (ch == "/" && maybeEnd) { - state.tokenize = jsTokenBase; - break; - } - maybeEnd = (ch == "%"); - } - return ret("comment", "comment"); - } - - // tw strong / bold - function twTokenStrong(stream, state) { - var maybeEnd = false, - ch; - while (ch = stream.next()) { - if (ch == "'" && maybeEnd) { - state.tokenize = jsTokenBase; - break; - } - maybeEnd = (ch == "'"); - } - return ret("text", "strong"); - } - - // tw code - function twTokenCode(stream, state) { - var ch, sb = state.block; - - if (sb && stream.current()) { - return ret("code", "comment"); - } - - if (!sb && stream.match(reUntilCodeStop)) { - state.tokenize = jsTokenBase; - return ret("code", "comment"); - } - - if (sb && stream.sol() && stream.match(reCodeBlockStop)) { - state.tokenize = jsTokenBase; - return ret("code", "comment"); - } - - ch = stream.next(); - return (sb) ? ret("code", "comment") : ret("code", "comment"); - } - - // tw em / italic - function twTokenEm(stream, state) { - var maybeEnd = false, - ch; - while (ch = stream.next()) { - if (ch == "/" && maybeEnd) { - state.tokenize = jsTokenBase; - break; - } - maybeEnd = (ch == "/"); - } - return ret("text", "em"); - } - - // tw underlined text - function twTokenUnderline(stream, state) { - var maybeEnd = false, - ch; - while (ch = stream.next()) { - if (ch == "_" && maybeEnd) { - state.tokenize = jsTokenBase; - break; - } - maybeEnd = (ch == "_"); - } - return ret("text", "underlined"); - } - - // tw strike through text looks ugly - // change CSS if needed - function twTokenStrike(stream, state) { - var maybeEnd = false, - ch, nr; - - while (ch = stream.next()) { - if (ch == "-" && maybeEnd) { - state.tokenize = jsTokenBase; - break; - } - maybeEnd = (ch == "-"); - } - return ret("text", "strikethrough"); - } - - // macro - function twTokenMacro(stream, state) { - var ch, tmp, word, known; - - if (stream.current() == '<<') { - return ret('brace', 'macro'); - } - - ch = stream.next(); - if (!ch) { - state.tokenize = jsTokenBase; - return ret(ch); - } - if (ch == ">") { - if (stream.peek() == '>') { - stream.next(); - state.tokenize = jsTokenBase; - return ret("brace", "macro"); - } - } - - stream.eatWhile(/[\w\$_]/); - word = stream.current(); - known = keywords.propertyIsEnumerable(word) && keywords[word]; - - if (known) { - return ret(known.type, known.style, word); - } - else { - return ret("macro", null, word); - } - } - - // Interface - return { - startState: function (basecolumn) { - return { - tokenize: jsTokenBase, - indented: 0, - level: 0 - }; - }, - - token: function (stream, state) { - if (stream.eatSpace()) return null; - var style = state.tokenize(stream, state); - return style; - }, - - electricChars: "" - }; -}); - -CodeMirror.defineMIME("text/x-tiddlywiki", "tiddlywiki"); -//}}} diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/tiki/index.html b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/tiki/index.html deleted file mode 100644 index 0424d7ffac..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/tiki/index.html +++ /dev/null @@ -1,83 +0,0 @@ - - - - CodeMirror: Tiki wiki mode - - - - - - - - -

    CodeMirror: Tiki wiki mode

    - -
    - - - - - diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/tiki/tiki.css b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/tiki/tiki.css deleted file mode 100644 index da1cc8e049..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/tiki/tiki.css +++ /dev/null @@ -1,26 +0,0 @@ -.cm-tw-syntaxerror { - color: #FFFFFF; - background-color: #990000; -} - -.cm-tw-deleted { - text-decoration: line-through; -} - -.cm-tw-header5 { - font-weight: bold; -} -.cm-tw-listitem:first-child { /*Added first child to fix duplicate padding when highlighting*/ - padding-left: 10px; -} - -.cm-tw-box { - border-top-width: 0px ! important; - border-style: solid; - border-width: 1px; - border-color: inherit; -} - -.cm-tw-underline { - text-decoration: underline; -} \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/tiki/tiki.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/tiki/tiki.js deleted file mode 100644 index d032a4adf5..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/tiki/tiki.js +++ /dev/null @@ -1,309 +0,0 @@ -CodeMirror.defineMode('tiki', function(config, parserConfig) { - function inBlock(style, terminator, returnTokenizer) { - return function(stream, state) { - while (!stream.eol()) { - if (stream.match(terminator)) { - state.tokenize = inText; - break; - } - stream.next(); - } - - if (returnTokenizer) state.tokenize = returnTokenizer; - - return style; - }; - } - - function inLine(style, terminator) { - return function(stream, state) { - while(!stream.eol()) { - stream.next(); - } - state.tokenize = inText; - return style; - }; - } - - function inText(stream, state) { - function chain(parser) { - state.tokenize = parser; - return parser(stream, state); - } - - var sol = stream.sol(); - var ch = stream.next(); - - //non start of line - switch (ch) { //switch is generally much faster than if, so it is used here - case "{": //plugin - var type = stream.eat("/") ? "closeTag" : "openTag"; - stream.eatSpace(); - var tagName = ""; - var c; - while ((c = stream.eat(/[^\s\u00a0=\"\'\/?(}]/))) tagName += c; - state.tokenize = inPlugin; - return "tag"; - break; - case "_": //bold - if (stream.eat("_")) { - return chain(inBlock("strong", "__", inText)); - } - break; - case "'": //italics - if (stream.eat("'")) { - // Italic text - return chain(inBlock("em", "''", inText)); - } - break; - case "(":// Wiki Link - if (stream.eat("(")) { - return chain(inBlock("variable-2", "))", inText)); - } - break; - case "[":// Weblink - return chain(inBlock("variable-3", "]", inText)); - break; - case "|": //table - if (stream.eat("|")) { - return chain(inBlock("comment", "||")); - } - break; - case "-": - if (stream.eat("=")) {//titleBar - return chain(inBlock("header string", "=-", inText)); - } else if (stream.eat("-")) {//deleted - return chain(inBlock("error tw-deleted", "--", inText)); - } - break; - case "=": //underline - if (stream.match("==")) { - return chain(inBlock("tw-underline", "===", inText)); - } - break; - case ":": - if (stream.eat(":")) { - return chain(inBlock("comment", "::")); - } - break; - case "^": //box - return chain(inBlock("tw-box", "^")); - break; - case "~": //np - if (stream.match("np~")) { - return chain(inBlock("meta", "~/np~")); - } - break; - } - - //start of line types - if (sol) { - switch (ch) { - case "!": //header at start of line - if (stream.match('!!!!!')) { - return chain(inLine("header string")); - } else if (stream.match('!!!!')) { - return chain(inLine("header string")); - } else if (stream.match('!!!')) { - return chain(inLine("header string")); - } else if (stream.match('!!')) { - return chain(inLine("header string")); - } else { - return chain(inLine("header string")); - } - break; - case "*": //unordered list line item, or
  • at start of line - case "#": //ordered list line item, or
  • at start of line - case "+": //ordered list line item, or
  • at start of line - return chain(inLine("tw-listitem bracket")); - break; - } - } - - //stream.eatWhile(/[&{]/); was eating up plugins, turned off to act less like html and more like tiki - return null; - } - - var indentUnit = config.indentUnit; - - // Return variables for tokenizers - var pluginName, type; - function inPlugin(stream, state) { - var ch = stream.next(); - var peek = stream.peek(); - - if (ch == "}") { - state.tokenize = inText; - //type = ch == ")" ? "endPlugin" : "selfclosePlugin"; inPlugin - return "tag"; - } else if (ch == "(" || ch == ")") { - return "bracket"; - } else if (ch == "=") { - type = "equals"; - - if (peek == ">") { - ch = stream.next(); - peek = stream.peek(); - } - - //here we detect values directly after equal character with no quotes - if (!/[\'\"]/.test(peek)) { - state.tokenize = inAttributeNoQuote(); - } - //end detect values - - return "operator"; - } else if (/[\'\"]/.test(ch)) { - state.tokenize = inAttribute(ch); - return state.tokenize(stream, state); - } else { - stream.eatWhile(/[^\s\u00a0=\"\'\/?]/); - return "keyword"; - } - } - - function inAttribute(quote) { - return function(stream, state) { - while (!stream.eol()) { - if (stream.next() == quote) { - state.tokenize = inPlugin; - break; - } - } - return "string"; - }; - } - - function inAttributeNoQuote() { - return function(stream, state) { - while (!stream.eol()) { - var ch = stream.next(); - var peek = stream.peek(); - if (ch == " " || ch == "," || /[ )}]/.test(peek)) { - state.tokenize = inPlugin; - break; - } - } - return "string"; - }; - } - - var curState, setStyle; - function pass() { - for (var i = arguments.length - 1; i >= 0; i--) curState.cc.push(arguments[i]); - } - - function cont() { - pass.apply(null, arguments); - return true; - } - - function pushContext(pluginName, startOfLine) { - var noIndent = curState.context && curState.context.noIndent; - curState.context = { - prev: curState.context, - pluginName: pluginName, - indent: curState.indented, - startOfLine: startOfLine, - noIndent: noIndent - }; - } - - function popContext() { - if (curState.context) curState.context = curState.context.prev; - } - - function element(type) { - if (type == "openPlugin") {curState.pluginName = pluginName; return cont(attributes, endplugin(curState.startOfLine));} - else if (type == "closePlugin") { - var err = false; - if (curState.context) { - err = curState.context.pluginName != pluginName; - popContext(); - } else { - err = true; - } - if (err) setStyle = "error"; - return cont(endcloseplugin(err)); - } - else if (type == "string") { - if (!curState.context || curState.context.name != "!cdata") pushContext("!cdata"); - if (curState.tokenize == inText) popContext(); - return cont(); - } - else return cont(); - } - - function endplugin(startOfLine) { - return function(type) { - if ( - type == "selfclosePlugin" || - type == "endPlugin" - ) - return cont(); - if (type == "endPlugin") {pushContext(curState.pluginName, startOfLine); return cont();} - return cont(); - }; - } - - function endcloseplugin(err) { - return function(type) { - if (err) setStyle = "error"; - if (type == "endPlugin") return cont(); - return pass(); - }; - } - - function attributes(type) { - if (type == "keyword") {setStyle = "attribute"; return cont(attributes);} - if (type == "equals") return cont(attvalue, attributes); - return pass(); - } - function attvalue(type) { - if (type == "keyword") {setStyle = "string"; return cont();} - if (type == "string") return cont(attvaluemaybe); - return pass(); - } - function attvaluemaybe(type) { - if (type == "string") return cont(attvaluemaybe); - else return pass(); - } - return { - startState: function() { - return {tokenize: inText, cc: [], indented: 0, startOfLine: true, pluginName: null, context: null}; - }, - token: function(stream, state) { - if (stream.sol()) { - state.startOfLine = true; - state.indented = stream.indentation(); - } - if (stream.eatSpace()) return null; - - setStyle = type = pluginName = null; - var style = state.tokenize(stream, state); - if ((style || type) && style != "comment") { - curState = state; - while (true) { - var comb = state.cc.pop() || element; - if (comb(type || style)) break; - } - } - state.startOfLine = false; - return setStyle || style; - }, - indent: function(state, textAfter) { - var context = state.context; - if (context && context.noIndent) return 0; - if (context && /^{\//.test(textAfter)) - context = context.prev; - while (context && !context.startOfLine) - context = context.prev; - if (context) return context.indent + indentUnit; - else return 0; - }, - electricChars: "/" - }; -}); - -//I figure, why not -CodeMirror.defineMIME("text/tiki", "tiki"); diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/vb/LICENSE.txt b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/vb/LICENSE.txt deleted file mode 100644 index 22af4bf380..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/vb/LICENSE.txt +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License - -Copyright (c) 2012 Codility Limited, 107 Cheapside, London EC2V 6DN, UK - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/vb/index.html b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/vb/index.html deleted file mode 100644 index 6a310f239e..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/vb/index.html +++ /dev/null @@ -1,89 +0,0 @@ - - - - CodeMirror: VB.NET mode - - - - - - - - - -

    CodeMirror: VB.NET mode

    - - - -
    - -
    -
    
    -  

    MIME type defined: text/x-vb.

    - - diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/vb/vb.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/vb/vb.js deleted file mode 100644 index e1ff1f1d8e..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/vb/vb.js +++ /dev/null @@ -1,260 +0,0 @@ -CodeMirror.defineMode("vb", function(conf, parserConf) { - var ERRORCLASS = 'error'; - - function wordRegexp(words) { - return new RegExp("^((" + words.join(")|(") + "))\\b", "i"); - } - - var singleOperators = new RegExp("^[\\+\\-\\*/%&\\\\|\\^~<>!]"); - var singleDelimiters = new RegExp('^[\\(\\)\\[\\]\\{\\}@,:`=;\\.]'); - var doubleOperators = new RegExp("^((==)|(<>)|(<=)|(>=)|(<>)|(<<)|(>>)|(//)|(\\*\\*))"); - var doubleDelimiters = new RegExp("^((\\+=)|(\\-=)|(\\*=)|(%=)|(/=)|(&=)|(\\|=)|(\\^=))"); - var tripleDelimiters = new RegExp("^((//=)|(>>=)|(<<=)|(\\*\\*=))"); - var identifiers = new RegExp("^[_A-Za-z][_A-Za-z0-9]*"); - - var openingKeywords = ['class','module', 'sub','enum','select','while','if','function', 'get','set','property', 'try']; - var middleKeywords = ['else','elseif','case', 'catch']; - var endKeywords = ['next','loop']; - - var wordOperators = wordRegexp(['and', 'or', 'not', 'xor', 'in']); - var commonkeywords = ['as', 'dim', 'break', 'continue','optional', 'then', 'until', - 'goto', 'byval','byref','new','handles','property', 'return', - 'const','private', 'protected', 'friend', 'public', 'shared', 'static', 'true','false']; - var commontypes = ['integer','string','double','decimal','boolean','short','char', 'float','single']; - - var keywords = wordRegexp(commonkeywords); - var types = wordRegexp(commontypes); - var stringPrefixes = '"'; - - var opening = wordRegexp(openingKeywords); - var middle = wordRegexp(middleKeywords); - var closing = wordRegexp(endKeywords); - var doubleClosing = wordRegexp(['end']); - var doOpening = wordRegexp(['do']); - - var indentInfo = null; - - - - - function indent(stream, state) { - state.currentIndent++; - } - - function dedent(stream, state) { - state.currentIndent--; - } - // tokenizers - function tokenBase(stream, state) { - if (stream.eatSpace()) { - return null; - } - - var ch = stream.peek(); - - // Handle Comments - if (ch === "'") { - stream.skipToEnd(); - return 'comment'; - } - - - // Handle Number Literals - if (stream.match(/^((&H)|(&O))?[0-9\.a-f]/i, false)) { - var floatLiteral = false; - // Floats - if (stream.match(/^\d*\.\d+F?/i)) { floatLiteral = true; } - else if (stream.match(/^\d+\.\d*F?/)) { floatLiteral = true; } - else if (stream.match(/^\.\d+F?/)) { floatLiteral = true; } - - if (floatLiteral) { - // Float literals may be "imaginary" - stream.eat(/J/i); - return 'number'; - } - // Integers - var intLiteral = false; - // Hex - if (stream.match(/^&H[0-9a-f]+/i)) { intLiteral = true; } - // Octal - else if (stream.match(/^&O[0-7]+/i)) { intLiteral = true; } - // Decimal - else if (stream.match(/^[1-9]\d*F?/)) { - // Decimal literals may be "imaginary" - stream.eat(/J/i); - // TODO - Can you have imaginary longs? - intLiteral = true; - } - // Zero by itself with no other piece of number. - else if (stream.match(/^0(?![\dx])/i)) { intLiteral = true; } - if (intLiteral) { - // Integer literals may be "long" - stream.eat(/L/i); - return 'number'; - } - } - - // Handle Strings - if (stream.match(stringPrefixes)) { - state.tokenize = tokenStringFactory(stream.current()); - return state.tokenize(stream, state); - } - - // Handle operators and Delimiters - if (stream.match(tripleDelimiters) || stream.match(doubleDelimiters)) { - return null; - } - if (stream.match(doubleOperators) - || stream.match(singleOperators) - || stream.match(wordOperators)) { - return 'operator'; - } - if (stream.match(singleDelimiters)) { - return null; - } - if (stream.match(doOpening)) { - indent(stream,state); - state.doInCurrentLine = true; - return 'keyword'; - } - if (stream.match(opening)) { - if (! state.doInCurrentLine) - indent(stream,state); - else - state.doInCurrentLine = false; - return 'keyword'; - } - if (stream.match(middle)) { - return 'keyword'; - } - - if (stream.match(doubleClosing)) { - dedent(stream,state); - dedent(stream,state); - return 'keyword'; - } - if (stream.match(closing)) { - dedent(stream,state); - return 'keyword'; - } - - if (stream.match(types)) { - return 'keyword'; - } - - if (stream.match(keywords)) { - return 'keyword'; - } - - if (stream.match(identifiers)) { - return 'variable'; - } - - // Handle non-detected items - stream.next(); - return ERRORCLASS; - } - - function tokenStringFactory(delimiter) { - var singleline = delimiter.length == 1; - var OUTCLASS = 'string'; - - return function tokenString(stream, state) { - while (!stream.eol()) { - stream.eatWhile(/[^'"]/); - if (stream.match(delimiter)) { - state.tokenize = tokenBase; - return OUTCLASS; - } else { - stream.eat(/['"]/); - } - } - if (singleline) { - if (parserConf.singleLineStringErrors) { - return ERRORCLASS; - } else { - state.tokenize = tokenBase; - } - } - return OUTCLASS; - }; - } - - - function tokenLexer(stream, state) { - var style = state.tokenize(stream, state); - var current = stream.current(); - - // Handle '.' connected identifiers - if (current === '.') { - style = state.tokenize(stream, state); - current = stream.current(); - if (style === 'variable') { - return 'variable'; - } else { - return ERRORCLASS; - } - } - - - var delimiter_index = '[({'.indexOf(current); - if (delimiter_index !== -1) { - indent(stream, state ); - } - if (indentInfo === 'dedent') { - if (dedent(stream, state)) { - return ERRORCLASS; - } - } - delimiter_index = '])}'.indexOf(current); - if (delimiter_index !== -1) { - if (dedent(stream, state)) { - return ERRORCLASS; - } - } - - return style; - } - - var external = { - electricChars:"dDpPtTfFeE ", - startState: function(basecolumn) { - return { - tokenize: tokenBase, - lastToken: null, - currentIndent: 0, - nextLineIndent: 0, - doInCurrentLine: false - - - }; - }, - - token: function(stream, state) { - if (stream.sol()) { - state.currentIndent += state.nextLineIndent; - state.nextLineIndent = 0; - state.doInCurrentLine = 0; - } - var style = tokenLexer(stream, state); - - state.lastToken = {style:style, content: stream.current()}; - - - - return style; - }, - - indent: function(state, textAfter) { - var trueText = textAfter.replace(/^\s+|\s+$/g, '') ; - if (trueText.match(closing) || trueText.match(doubleClosing) || trueText.match(middle)) return conf.indentUnit*(state.currentIndent-1); - if(state.currentIndent < 0) return 0; - return state.currentIndent * conf.indentUnit; - } - - }; - return external; -}); - -CodeMirror.defineMIME("text/x-vb", "vb"); - diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/vbscript/index.html b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/vbscript/index.html deleted file mode 100644 index 8ba4548839..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/vbscript/index.html +++ /dev/null @@ -1,43 +0,0 @@ - - - - - CodeMirror: VBScript mode - - - - - - - -

    CodeMirror: VBScript mode

    - -
    - - - -

    MIME types defined: text/vbscript.

    - - - diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/vbscript/vbscript.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/vbscript/vbscript.js deleted file mode 100644 index 04dcef3015..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/vbscript/vbscript.js +++ /dev/null @@ -1,26 +0,0 @@ -CodeMirror.defineMode("vbscript", function() { - var regexVBScriptKeyword = /^(?:Call|Case|CDate|Clear|CInt|CLng|Const|CStr|Description|Dim|Do|Each|Else|ElseIf|End|Err|Error|Exit|False|For|Function|If|LCase|Loop|LTrim|Next|Nothing|Now|Number|On|Preserve|Quit|ReDim|Resume|RTrim|Select|Set|Sub|Then|To|Trim|True|UBound|UCase|Until|VbCr|VbCrLf|VbLf|VbTab)$/im; - - return { - token: function(stream) { - if (stream.eatSpace()) return null; - var ch = stream.next(); - if (ch == "'") { - stream.skipToEnd(); - return "comment"; - } - if (ch == '"') { - stream.skipTo('"'); - return "string"; - } - - if (/\w/.test(ch)) { - stream.eatWhile(/\w/); - if (regexVBScriptKeyword.test(stream.current())) return "keyword"; - } - return null; - } - }; -}); - -CodeMirror.defineMIME("text/vbscript", "vbscript"); diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/velocity/index.html b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/velocity/index.html deleted file mode 100644 index 7dca8564f2..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/velocity/index.html +++ /dev/null @@ -1,104 +0,0 @@ - - - - - CodeMirror: Velocity mode - - - - - - - - -

    CodeMirror: Velocity mode

    -
    - - -

    MIME types defined: text/velocity.

    - - - diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/velocity/velocity.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/velocity/velocity.js deleted file mode 100644 index 2af58b10b8..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/velocity/velocity.js +++ /dev/null @@ -1,146 +0,0 @@ -CodeMirror.defineMode("velocity", function(config) { - function parseWords(str) { - var obj = {}, words = str.split(" "); - for (var i = 0; i < words.length; ++i) obj[words[i]] = true; - return obj; - } - - var indentUnit = config.indentUnit; - var keywords = parseWords("#end #else #break #stop #[[ #]] " + - "#{end} #{else} #{break} #{stop}"); - var functions = parseWords("#if #elseif #foreach #set #include #parse #macro #define #evaluate " + - "#{if} #{elseif} #{foreach} #{set} #{include} #{parse} #{macro} #{define} #{evaluate}"); - var specials = parseWords("$foreach.count $foreach.hasNext $foreach.first $foreach.last $foreach.topmost $foreach.parent $velocityCount"); - var isOperatorChar = /[+\-*&%=<>!?:\/|]/; - var multiLineStrings =true; - - function chain(stream, state, f) { - state.tokenize = f; - return f(stream, state); - } - function tokenBase(stream, state) { - var beforeParams = state.beforeParams; - state.beforeParams = false; - var ch = stream.next(); - // start of string? - if ((ch == '"' || ch == "'") && state.inParams) - return chain(stream, state, tokenString(ch)); - // is it one of the special signs []{}().,;? Seperator? - else if (/[\[\]{}\(\),;\.]/.test(ch)) { - if (ch == "(" && beforeParams) state.inParams = true; - else if (ch == ")") state.inParams = false; - return null; - } - // start of a number value? - else if (/\d/.test(ch)) { - stream.eatWhile(/[\w\.]/); - return "number"; - } - // multi line comment? - else if (ch == "#" && stream.eat("*")) { - return chain(stream, state, tokenComment); - } - // unparsed content? - else if (ch == "#" && stream.match(/ *\[ *\[/)) { - return chain(stream, state, tokenUnparsed); - } - // single line comment? - else if (ch == "#" && stream.eat("#")) { - stream.skipToEnd(); - return "comment"; - } - // variable? - else if (ch == "$") { - stream.eatWhile(/[\w\d\$_\.{}]/); - // is it one of the specials? - if (specials && specials.propertyIsEnumerable(stream.current().toLowerCase())) { - return "keyword"; - } - else { - state.beforeParams = true; - return "builtin"; - } - } - // is it a operator? - else if (isOperatorChar.test(ch)) { - stream.eatWhile(isOperatorChar); - return "operator"; - } - else { - // get the whole word - stream.eatWhile(/[\w\$_{}]/); - var word = stream.current().toLowerCase(); - // is it one of the listed keywords? - if (keywords && keywords.propertyIsEnumerable(word)) - return "keyword"; - // is it one of the listed functions? - if (functions && functions.propertyIsEnumerable(word) || - stream.current().match(/^#[a-z0-9_]+ *$/i) && stream.peek()=="(") { - state.beforeParams = true; - return "keyword"; - } - // default: just a "word" - return null; - } - } - - function tokenString(quote) { - return function(stream, state) { - var escaped = false, next, end = false; - while ((next = stream.next()) != null) { - if (next == quote && !escaped) { - end = true; - break; - } - escaped = !escaped && next == "\\"; - } - if (end) state.tokenize = tokenBase; - return "string"; - }; - } - - function tokenComment(stream, state) { - var maybeEnd = false, ch; - while (ch = stream.next()) { - if (ch == "#" && maybeEnd) { - state.tokenize = tokenBase; - break; - } - maybeEnd = (ch == "*"); - } - return "comment"; - } - - function tokenUnparsed(stream, state) { - var maybeEnd = 0, ch; - while (ch = stream.next()) { - if (ch == "#" && maybeEnd == 2) { - state.tokenize = tokenBase; - break; - } - if (ch == "]") - maybeEnd++; - else if (ch != " ") - maybeEnd = 0; - } - return "meta"; - } - // Interface - - return { - startState: function(basecolumn) { - return { - tokenize: tokenBase, - beforeParams: false, - inParams: false - }; - }, - - token: function(stream, state) { - if (stream.eatSpace()) return null; - return state.tokenize(stream, state); - } - }; -}); - -CodeMirror.defineMIME("text/velocity", "velocity"); diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/verilog/index.html b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/verilog/index.html deleted file mode 100644 index 7b85e1b1c4..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/verilog/index.html +++ /dev/null @@ -1,211 +0,0 @@ - - - - - CodeMirror: Verilog mode - - - - - - - -

    CodeMirror: Verilog mode

    - -
    - - - -

    Simple mode that tries to handle Verilog-like languages as well as it - can. Takes one configuration parameters: keywords, an - object whose property names are the keywords in the language.

    - -

    MIME types defined: text/x-verilog (Verilog code).

    - - diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/verilog/verilog.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/verilog/verilog.js deleted file mode 100644 index 3e3eca9b9c..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/verilog/verilog.js +++ /dev/null @@ -1,194 +0,0 @@ -CodeMirror.defineMode("verilog", function(config, parserConfig) { - var indentUnit = config.indentUnit, - keywords = parserConfig.keywords || {}, - blockKeywords = parserConfig.blockKeywords || {}, - atoms = parserConfig.atoms || {}, - hooks = parserConfig.hooks || {}, - multiLineStrings = parserConfig.multiLineStrings; - var isOperatorChar = /[&|~> - - - - CodeMirror: XML mode - - - - - - - -

    CodeMirror: XML mode

    -
    - -

    The XML mode supports two configuration parameters:

    -
    -
    htmlMode (boolean)
    -
    This switches the mode to parse HTML instead of XML. This - means attributes do not have to be quoted, and some elements - (such as br) do not require a closing tag.
    -
    alignCDATA (boolean)
    -
    Setting this to true will force the opening tag of CDATA - blocks to not be indented.
    -
    - -

    MIME types defined: application/xml, text/html.

    - - diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/xml/xml.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/xml/xml.js deleted file mode 100644 index f5bbf56e09..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/xml/xml.js +++ /dev/null @@ -1,318 +0,0 @@ -CodeMirror.defineMode("xml", function(config, parserConfig) { - var indentUnit = config.indentUnit; - var Kludges = parserConfig.htmlMode ? { - autoSelfClosers: {'area': true, 'base': true, 'br': true, 'col': true, 'command': true, - 'embed': true, 'frame': true, 'hr': true, 'img': true, 'input': true, - 'keygen': true, 'link': true, 'meta': true, 'param': true, 'source': true, - 'track': true, 'wbr': true}, - implicitlyClosed: {'dd': true, 'li': true, 'optgroup': true, 'option': true, 'p': true, - 'rp': true, 'rt': true, 'tbody': true, 'td': true, 'tfoot': true, - 'th': true, 'tr': true}, - contextGrabbers: { - 'dd': {'dd': true, 'dt': true}, - 'dt': {'dd': true, 'dt': true}, - 'li': {'li': true}, - 'option': {'option': true, 'optgroup': true}, - 'optgroup': {'optgroup': true}, - 'p': {'address': true, 'article': true, 'aside': true, 'blockquote': true, 'dir': true, - 'div': true, 'dl': true, 'fieldset': true, 'footer': true, 'form': true, - 'h1': true, 'h2': true, 'h3': true, 'h4': true, 'h5': true, 'h6': true, - 'header': true, 'hgroup': true, 'hr': true, 'menu': true, 'nav': true, 'ol': true, - 'p': true, 'pre': true, 'section': true, 'table': true, 'ul': true}, - 'rp': {'rp': true, 'rt': true}, - 'rt': {'rp': true, 'rt': true}, - 'tbody': {'tbody': true, 'tfoot': true}, - 'td': {'td': true, 'th': true}, - 'tfoot': {'tbody': true}, - 'th': {'td': true, 'th': true}, - 'thead': {'tbody': true, 'tfoot': true}, - 'tr': {'tr': true} - }, - doNotIndent: {"pre": true}, - allowUnquoted: true, - allowMissing: true - } : { - autoSelfClosers: {}, - implicitlyClosed: {}, - contextGrabbers: {}, - doNotIndent: {}, - allowUnquoted: false, - allowMissing: false - }; - var alignCDATA = parserConfig.alignCDATA; - - // Return variables for tokenizers - var tagName, type; - - function inText(stream, state) { - function chain(parser) { - state.tokenize = parser; - return parser(stream, state); - } - - var ch = stream.next(); - if (ch == "<") { - if (stream.eat("!")) { - if (stream.eat("[")) { - if (stream.match("CDATA[")) return chain(inBlock("atom", "]]>")); - else return null; - } - else if (stream.match("--")) return chain(inBlock("comment", "-->")); - else if (stream.match("DOCTYPE", true, true)) { - stream.eatWhile(/[\w\._\-]/); - return chain(doctype(1)); - } - else return null; - } - else if (stream.eat("?")) { - stream.eatWhile(/[\w\._\-]/); - state.tokenize = inBlock("meta", "?>"); - return "meta"; - } - else { - type = stream.eat("/") ? "closeTag" : "openTag"; - stream.eatSpace(); - tagName = ""; - var c; - while ((c = stream.eat(/[^\s\u00a0=<>\"\'\/?]/))) tagName += c; - state.tokenize = inTag; - return "tag"; - } - } - else if (ch == "&") { - var ok; - if (stream.eat("#")) { - if (stream.eat("x")) { - ok = stream.eatWhile(/[a-fA-F\d]/) && stream.eat(";"); - } else { - ok = stream.eatWhile(/[\d]/) && stream.eat(";"); - } - } else { - ok = stream.eatWhile(/[\w\.\-:]/) && stream.eat(";"); - } - return ok ? "atom" : "error"; - } - else { - stream.eatWhile(/[^&<]/); - return null; - } - } - - function inTag(stream, state) { - var ch = stream.next(); - if (ch == ">" || (ch == "/" && stream.eat(">"))) { - state.tokenize = inText; - type = ch == ">" ? "endTag" : "selfcloseTag"; - return "tag"; - } - else if (ch == "=") { - type = "equals"; - return null; - } - else if (/[\'\"]/.test(ch)) { - state.tokenize = inAttribute(ch); - return state.tokenize(stream, state); - } - else { - stream.eatWhile(/[^\s\u00a0=<>\"\'\/?]/); - return "word"; - } - } - - function inAttribute(quote) { - return function(stream, state) { - while (!stream.eol()) { - if (stream.next() == quote) { - state.tokenize = inTag; - break; - } - } - return "string"; - }; - } - - function inBlock(style, terminator) { - return function(stream, state) { - while (!stream.eol()) { - if (stream.match(terminator)) { - state.tokenize = inText; - break; - } - stream.next(); - } - return style; - }; - } - function doctype(depth) { - return function(stream, state) { - var ch; - while ((ch = stream.next()) != null) { - if (ch == "<") { - state.tokenize = doctype(depth + 1); - return state.tokenize(stream, state); - } else if (ch == ">") { - if (depth == 1) { - state.tokenize = inText; - break; - } else { - state.tokenize = doctype(depth - 1); - return state.tokenize(stream, state); - } - } - } - return "meta"; - }; - } - - var curState, setStyle; - function pass() { - for (var i = arguments.length - 1; i >= 0; i--) curState.cc.push(arguments[i]); - } - function cont() { - pass.apply(null, arguments); - return true; - } - - function pushContext(tagName, startOfLine) { - var noIndent = Kludges.doNotIndent.hasOwnProperty(tagName) || (curState.context && curState.context.noIndent); - curState.context = { - prev: curState.context, - tagName: tagName, - indent: curState.indented, - startOfLine: startOfLine, - noIndent: noIndent - }; - } - function popContext() { - if (curState.context) curState.context = curState.context.prev; - } - - function element(type) { - if (type == "openTag") { - curState.tagName = tagName; - return cont(attributes, endtag(curState.startOfLine)); - } else if (type == "closeTag") { - var err = false; - if (curState.context) { - if (curState.context.tagName != tagName) { - if (Kludges.implicitlyClosed.hasOwnProperty(curState.context.tagName.toLowerCase())) { - popContext(); - } - err = !curState.context || curState.context.tagName != tagName; - } - } else { - err = true; - } - if (err) setStyle = "error"; - return cont(endclosetag(err)); - } - return cont(); - } - function endtag(startOfLine) { - return function(type) { - if (type == "selfcloseTag" || - (type == "endTag" && Kludges.autoSelfClosers.hasOwnProperty(curState.tagName.toLowerCase()))) { - maybePopContext(curState.tagName.toLowerCase()); - return cont(); - } - if (type == "endTag") { - maybePopContext(curState.tagName.toLowerCase()); - pushContext(curState.tagName, startOfLine); - return cont(); - } - return cont(); - }; - } - function endclosetag(err) { - return function(type) { - if (err) setStyle = "error"; - if (type == "endTag") { popContext(); return cont(); } - setStyle = "error"; - return cont(arguments.callee); - }; - } - function maybePopContext(nextTagName) { - var parentTagName; - while (true) { - if (!curState.context) { - return; - } - parentTagName = curState.context.tagName.toLowerCase(); - if (!Kludges.contextGrabbers.hasOwnProperty(parentTagName) || - !Kludges.contextGrabbers[parentTagName].hasOwnProperty(nextTagName)) { - return; - } - popContext(); - } - } - - function attributes(type) { - if (type == "word") {setStyle = "attribute"; return cont(attribute, attributes);} - if (type == "endTag" || type == "selfcloseTag") return pass(); - setStyle = "error"; - return cont(attributes); - } - function attribute(type) { - if (type == "equals") return cont(attvalue, attributes); - if (!Kludges.allowMissing) setStyle = "error"; - return (type == "endTag" || type == "selfcloseTag") ? pass() : cont(); - } - function attvalue(type) { - if (type == "string") return cont(attvaluemaybe); - if (type == "word" && Kludges.allowUnquoted) {setStyle = "string"; return cont();} - setStyle = "error"; - return (type == "endTag" || type == "selfCloseTag") ? pass() : cont(); - } - function attvaluemaybe(type) { - if (type == "string") return cont(attvaluemaybe); - else return pass(); - } - - return { - startState: function() { - return {tokenize: inText, cc: [], indented: 0, startOfLine: true, tagName: null, context: null}; - }, - - token: function(stream, state) { - if (stream.sol()) { - state.startOfLine = true; - state.indented = stream.indentation(); - } - if (stream.eatSpace()) return null; - - setStyle = type = tagName = null; - var style = state.tokenize(stream, state); - state.type = type; - if ((style || type) && style != "comment") { - curState = state; - while (true) { - var comb = state.cc.pop() || element; - if (comb(type || style)) break; - } - } - state.startOfLine = false; - return setStyle || style; - }, - - indent: function(state, textAfter, fullLine) { - var context = state.context; - if ((state.tokenize != inTag && state.tokenize != inText) || - context && context.noIndent) - return fullLine ? fullLine.match(/^(\s*)/)[0].length : 0; - if (alignCDATA && / - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/xquery/index.html b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/xquery/index.html deleted file mode 100644 index 1422ff1f24..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/xquery/index.html +++ /dev/null @@ -1,223 +0,0 @@ - - - - - - CodeMirror: XQuery mode - - - - - - - - -

    CodeMirror: XQuery mode

    - -
    - -
    - - - -

    MIME types defined: application/xquery.

    - -

    Development of the CodeMirror XQuery mode was sponsored by - MarkLogic and developed by - Mike Brevoort. -

    - - - diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/xquery/xquery.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/xquery/xquery.js deleted file mode 100644 index a7f0933187..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/xquery/xquery.js +++ /dev/null @@ -1,451 +0,0 @@ -/* -Copyright (C) 2011 by MarkLogic Corporation -Author: Mike Brevoort - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ -CodeMirror.defineMode("xquery", function(config, parserConfig) { - - // The keywords object is set to the result of this self executing - // function. Each keyword is a property of the keywords object whose - // value is {type: atype, style: astyle} - var keywords = function(){ - // conveinence functions used to build keywords object - function kw(type) {return {type: type, style: "keyword"};} - var A = kw("keyword a") - , B = kw("keyword b") - , C = kw("keyword c") - , operator = kw("operator") - , atom = {type: "atom", style: "atom"} - , punctuation = {type: "punctuation", style: ""} - , qualifier = {type: "axis_specifier", style: "qualifier"}; - - // kwObj is what is return from this function at the end - var kwObj = { - 'if': A, 'switch': A, 'while': A, 'for': A, - 'else': B, 'then': B, 'try': B, 'finally': B, 'catch': B, - 'element': C, 'attribute': C, 'let': C, 'implements': C, 'import': C, 'module': C, 'namespace': C, - 'return': C, 'super': C, 'this': C, 'throws': C, 'where': C, 'private': C, - ',': punctuation, - 'null': atom, 'fn:false()': atom, 'fn:true()': atom - }; - - // a list of 'basic' keywords. For each add a property to kwObj with the value of - // {type: basic[i], style: "keyword"} e.g. 'after' --> {type: "after", style: "keyword"} - var basic = ['after','ancestor','ancestor-or-self','and','as','ascending','assert','attribute','before', - 'by','case','cast','child','comment','declare','default','define','descendant','descendant-or-self', - 'descending','document','document-node','element','else','eq','every','except','external','following', - 'following-sibling','follows','for','function','if','import','in','instance','intersect','item', - 'let','module','namespace','node','node','of','only','or','order','parent','precedes','preceding', - 'preceding-sibling','processing-instruction','ref','return','returns','satisfies','schema','schema-element', - 'self','some','sortby','stable','text','then','to','treat','typeswitch','union','variable','version','where', - 'xquery', 'empty-sequence']; - for(var i=0, l=basic.length; i < l; i++) { kwObj[basic[i]] = kw(basic[i]);}; - - // a list of types. For each add a property to kwObj with the value of - // {type: "atom", style: "atom"} - var types = ['xs:string', 'xs:float', 'xs:decimal', 'xs:double', 'xs:integer', 'xs:boolean', 'xs:date', 'xs:dateTime', - 'xs:time', 'xs:duration', 'xs:dayTimeDuration', 'xs:time', 'xs:yearMonthDuration', 'numeric', 'xs:hexBinary', - 'xs:base64Binary', 'xs:anyURI', 'xs:QName', 'xs:byte','xs:boolean','xs:anyURI','xf:yearMonthDuration']; - for(var i=0, l=types.length; i < l; i++) { kwObj[types[i]] = atom;}; - - // each operator will add a property to kwObj with value of {type: "operator", style: "keyword"} - var operators = ['eq', 'ne', 'lt', 'le', 'gt', 'ge', ':=', '=', '>', '>=', '<', '<=', '.', '|', '?', 'and', 'or', 'div', 'idiv', 'mod', '*', '/', '+', '-']; - for(var i=0, l=operators.length; i < l; i++) { kwObj[operators[i]] = operator;}; - - // each axis_specifiers will add a property to kwObj with value of {type: "axis_specifier", style: "qualifier"} - var axis_specifiers = ["self::", "attribute::", "child::", "descendant::", "descendant-or-self::", "parent::", - "ancestor::", "ancestor-or-self::", "following::", "preceding::", "following-sibling::", "preceding-sibling::"]; - for(var i=0, l=axis_specifiers.length; i < l; i++) { kwObj[axis_specifiers[i]] = qualifier; }; - - return kwObj; - }(); - - // Used as scratch variables to communicate multiple values without - // consing up tons of objects. - var type, content; - - function ret(tp, style, cont) { - type = tp; content = cont; - return style; - } - - function chain(stream, state, f) { - state.tokenize = f; - return f(stream, state); - } - - // the primary mode tokenizer - function tokenBase(stream, state) { - var ch = stream.next(), - mightBeFunction = false, - isEQName = isEQNameAhead(stream); - - // an XML tag (if not in some sub, chained tokenizer) - if (ch == "<") { - if(stream.match("!--", true)) - return chain(stream, state, tokenXMLComment); - - if(stream.match("![CDATA", false)) { - state.tokenize = tokenCDATA; - return ret("tag", "tag"); - } - - if(stream.match("?", false)) { - return chain(stream, state, tokenPreProcessing); - } - - var isclose = stream.eat("/"); - stream.eatSpace(); - var tagName = "", c; - while ((c = stream.eat(/[^\s\u00a0=<>\"\'\/?]/))) tagName += c; - - return chain(stream, state, tokenTag(tagName, isclose)); - } - // start code block - else if(ch == "{") { - pushStateStack(state,{ type: "codeblock"}); - return ret("", ""); - } - // end code block - else if(ch == "}") { - popStateStack(state); - return ret("", ""); - } - // if we're in an XML block - else if(isInXmlBlock(state)) { - if(ch == ">") - return ret("tag", "tag"); - else if(ch == "/" && stream.eat(">")) { - popStateStack(state); - return ret("tag", "tag"); - } - else - return ret("word", "variable"); - } - // if a number - else if (/\d/.test(ch)) { - stream.match(/^\d*(?:\.\d*)?(?:E[+\-]?\d+)?/); - return ret("number", "atom"); - } - // comment start - else if (ch === "(" && stream.eat(":")) { - pushStateStack(state, { type: "comment"}); - return chain(stream, state, tokenComment); - } - // quoted string - else if ( !isEQName && (ch === '"' || ch === "'")) - return chain(stream, state, tokenString(ch)); - // variable - else if(ch === "$") { - return chain(stream, state, tokenVariable); - } - // assignment - else if(ch ===":" && stream.eat("=")) { - return ret("operator", "keyword"); - } - // open paren - else if(ch === "(") { - pushStateStack(state, { type: "paren"}); - return ret("", ""); - } - // close paren - else if(ch === ")") { - popStateStack(state); - return ret("", ""); - } - // open paren - else if(ch === "[") { - pushStateStack(state, { type: "bracket"}); - return ret("", ""); - } - // close paren - else if(ch === "]") { - popStateStack(state); - return ret("", ""); - } - else { - var known = keywords.propertyIsEnumerable(ch) && keywords[ch]; - - // if there's a EQName ahead, consume the rest of the string portion, it's likely a function - if(isEQName && ch === '\"') while(stream.next() !== '"'){} - if(isEQName && ch === '\'') while(stream.next() !== '\''){} - - // gobble up a word if the character is not known - if(!known) stream.eatWhile(/[\w\$_-]/); - - // gobble a colon in the case that is a lib func type call fn:doc - var foundColon = stream.eat(":"); - - // if there's not a second colon, gobble another word. Otherwise, it's probably an axis specifier - // which should get matched as a keyword - if(!stream.eat(":") && foundColon) { - stream.eatWhile(/[\w\$_-]/); - } - // if the next non whitespace character is an open paren, this is probably a function (if not a keyword of other sort) - if(stream.match(/^[ \t]*\(/, false)) { - mightBeFunction = true; - } - // is the word a keyword? - var word = stream.current(); - known = keywords.propertyIsEnumerable(word) && keywords[word]; - - // if we think it's a function call but not yet known, - // set style to variable for now for lack of something better - if(mightBeFunction && !known) known = {type: "function_call", style: "variable def"}; - - // if the previous word was element, attribute, axis specifier, this word should be the name of that - if(isInXmlConstructor(state)) { - popStateStack(state); - return ret("word", "variable", word); - } - // as previously checked, if the word is element,attribute, axis specifier, call it an "xmlconstructor" and - // push the stack so we know to look for it on the next word - if(word == "element" || word == "attribute" || known.type == "axis_specifier") pushStateStack(state, {type: "xmlconstructor"}); - - // if the word is known, return the details of that else just call this a generic 'word' - return known ? ret(known.type, known.style, word) : - ret("word", "variable", word); - } - } - - // handle comments, including nested - function tokenComment(stream, state) { - var maybeEnd = false, maybeNested = false, nestedCount = 0, ch; - while (ch = stream.next()) { - if (ch == ")" && maybeEnd) { - if(nestedCount > 0) - nestedCount--; - else { - popStateStack(state); - break; - } - } - else if(ch == ":" && maybeNested) { - nestedCount++; - } - maybeEnd = (ch == ":"); - maybeNested = (ch == "("); - } - - return ret("comment", "comment"); - } - - // tokenizer for string literals - // optionally pass a tokenizer function to set state.tokenize back to when finished - function tokenString(quote, f) { - return function(stream, state) { - var ch; - - if(isInString(state) && stream.current() == quote) { - popStateStack(state); - if(f) state.tokenize = f; - return ret("string", "string"); - } - - pushStateStack(state, { type: "string", name: quote, tokenize: tokenString(quote, f) }); - - // if we're in a string and in an XML block, allow an embedded code block - if(stream.match("{", false) && isInXmlAttributeBlock(state)) { - state.tokenize = tokenBase; - return ret("string", "string"); - } - - - while (ch = stream.next()) { - if (ch == quote) { - popStateStack(state); - if(f) state.tokenize = f; - break; - } - else { - // if we're in a string and in an XML block, allow an embedded code block in an attribute - if(stream.match("{", false) && isInXmlAttributeBlock(state)) { - state.tokenize = tokenBase; - return ret("string", "string"); - } - - } - } - - return ret("string", "string"); - }; - } - - // tokenizer for variables - function tokenVariable(stream, state) { - var isVariableChar = /[\w\$_-]/; - - // a variable may start with a quoted EQName so if the next character is quote, consume to the next quote - if(stream.eat("\"")) { - while(stream.next() !== '\"'){}; - stream.eat(":"); - } else { - stream.eatWhile(isVariableChar); - if(!stream.match(":=", false)) stream.eat(":"); - } - stream.eatWhile(isVariableChar); - state.tokenize = tokenBase; - return ret("variable", "variable"); - } - - // tokenizer for XML tags - function tokenTag(name, isclose) { - return function(stream, state) { - stream.eatSpace(); - if(isclose && stream.eat(">")) { - popStateStack(state); - state.tokenize = tokenBase; - return ret("tag", "tag"); - } - // self closing tag without attributes? - if(!stream.eat("/")) - pushStateStack(state, { type: "tag", name: name, tokenize: tokenBase}); - if(!stream.eat(">")) { - state.tokenize = tokenAttribute; - return ret("tag", "tag"); - } - else { - state.tokenize = tokenBase; - } - return ret("tag", "tag"); - }; - } - - // tokenizer for XML attributes - function tokenAttribute(stream, state) { - var ch = stream.next(); - - if(ch == "/" && stream.eat(">")) { - if(isInXmlAttributeBlock(state)) popStateStack(state); - if(isInXmlBlock(state)) popStateStack(state); - return ret("tag", "tag"); - } - if(ch == ">") { - if(isInXmlAttributeBlock(state)) popStateStack(state); - return ret("tag", "tag"); - } - if(ch == "=") - return ret("", ""); - // quoted string - if (ch == '"' || ch == "'") - return chain(stream, state, tokenString(ch, tokenAttribute)); - - if(!isInXmlAttributeBlock(state)) - pushStateStack(state, { type: "attribute", name: name, tokenize: tokenAttribute}); - - stream.eat(/[a-zA-Z_:]/); - stream.eatWhile(/[-a-zA-Z0-9_:.]/); - stream.eatSpace(); - - // the case where the attribute has not value and the tag was closed - if(stream.match(">", false) || stream.match("/", false)) { - popStateStack(state); - state.tokenize = tokenBase; - } - - return ret("attribute", "attribute"); - } - - // handle comments, including nested - function tokenXMLComment(stream, state) { - var ch; - while (ch = stream.next()) { - if (ch == "-" && stream.match("->", true)) { - state.tokenize = tokenBase; - return ret("comment", "comment"); - } - } - } - - - // handle CDATA - function tokenCDATA(stream, state) { - var ch; - while (ch = stream.next()) { - if (ch == "]" && stream.match("]", true)) { - state.tokenize = tokenBase; - return ret("comment", "comment"); - } - } - } - - // handle preprocessing instructions - function tokenPreProcessing(stream, state) { - var ch; - while (ch = stream.next()) { - if (ch == "?" && stream.match(">", true)) { - state.tokenize = tokenBase; - return ret("comment", "comment meta"); - } - } - } - - - // functions to test the current context of the state - function isInXmlBlock(state) { return isIn(state, "tag"); } - function isInXmlAttributeBlock(state) { return isIn(state, "attribute"); } - function isInCodeBlock(state) { return isIn(state, "codeblock"); } - function isInXmlConstructor(state) { return isIn(state, "xmlconstructor"); } - function isInString(state) { return isIn(state, "string"); } - - function isEQNameAhead(stream) { - // assume we've already eaten a quote (") - if(stream.current() === '"') - return stream.match(/^[^\"]+\"\:/, false); - else if(stream.current() === '\'') - return stream.match(/^[^\"]+\'\:/, false); - else - return false; - } - - function isIn(state, type) { - return (state.stack.length && state.stack[state.stack.length - 1].type == type); - } - - function pushStateStack(state, newState) { - state.stack.push(newState); - } - - function popStateStack(state) { - var popped = state.stack.pop(); - var reinstateTokenize = state.stack.length && state.stack[state.stack.length-1].tokenize; - state.tokenize = reinstateTokenize || tokenBase; - } - - // the interface for the mode API - return { - startState: function(basecolumn) { - return { - tokenize: tokenBase, - cc: [], - stack: [] - }; - }, - - token: function(stream, state) { - if (stream.eatSpace()) return null; - var style = state.tokenize(stream, state); - return style; - } - }; - -}); - -CodeMirror.defineMIME("application/xquery", "xquery"); diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/yaml/index.html b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/yaml/index.html deleted file mode 100644 index 5afd725702..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/yaml/index.html +++ /dev/null @@ -1,68 +0,0 @@ - - - - - CodeMirror: YAML mode - - - - - - - -

    CodeMirror: YAML mode

    -
    - - -

    MIME types defined: text/x-yaml.

    - - - diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/yaml/yaml.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/yaml/yaml.js deleted file mode 100644 index 0d83c712e2..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/yaml/yaml.js +++ /dev/null @@ -1,95 +0,0 @@ -CodeMirror.defineMode("yaml", function() { - - var cons = ['true', 'false', 'on', 'off', 'yes', 'no']; - var keywordRegex = new RegExp("\\b(("+cons.join(")|(")+"))$", 'i'); - - return { - token: function(stream, state) { - var ch = stream.peek(); - var esc = state.escaped; - state.escaped = false; - /* comments */ - if (ch == "#") { stream.skipToEnd(); return "comment"; } - if (state.literal && stream.indentation() > state.keyCol) { - stream.skipToEnd(); return "string"; - } else if (state.literal) { state.literal = false; } - if (stream.sol()) { - state.keyCol = 0; - state.pair = false; - state.pairStart = false; - /* document start */ - if(stream.match(/---/)) { return "def"; } - /* document end */ - if (stream.match(/\.\.\./)) { return "def"; } - /* array list item */ - if (stream.match(/\s*-\s+/)) { return 'meta'; } - } - /* pairs (associative arrays) -> key */ - if (!state.pair && stream.match(/^\s*([a-z0-9\._-])+(?=\s*:)/i)) { - state.pair = true; - state.keyCol = stream.indentation(); - return "atom"; - } - if (state.pair && stream.match(/^:\s*/)) { state.pairStart = true; return 'meta'; } - - /* inline pairs/lists */ - if (stream.match(/^(\{|\}|\[|\])/)) { - if (ch == '{') - state.inlinePairs++; - else if (ch == '}') - state.inlinePairs--; - else if (ch == '[') - state.inlineList++; - else - state.inlineList--; - return 'meta'; - } - - /* list seperator */ - if (state.inlineList > 0 && !esc && ch == ',') { - stream.next(); - return 'meta'; - } - /* pairs seperator */ - if (state.inlinePairs > 0 && !esc && ch == ',') { - state.keyCol = 0; - state.pair = false; - state.pairStart = false; - stream.next(); - return 'meta'; - } - - /* start of value of a pair */ - if (state.pairStart) { - /* block literals */ - if (stream.match(/^\s*(\||\>)\s*/)) { state.literal = true; return 'meta'; }; - /* references */ - if (stream.match(/^\s*(\&|\*)[a-z0-9\._-]+\b/i)) { return 'variable-2'; } - /* numbers */ - if (state.inlinePairs == 0 && stream.match(/^\s*-?[0-9\.\,]+\s?$/)) { return 'number'; } - if (state.inlinePairs > 0 && stream.match(/^\s*-?[0-9\.\,]+\s?(?=(,|}))/)) { return 'number'; } - /* keywords */ - if (stream.match(keywordRegex)) { return 'keyword'; } - } - - /* nothing found, continue */ - state.pairStart = false; - state.escaped = (ch == '\\'); - stream.next(); - return null; - }, - startState: function() { - return { - pair: false, - pairStart: false, - keyCol: 0, - inlinePairs: 0, - inlineList: 0, - literal: false, - escaped: false - }; - } - }; -}); - -CodeMirror.defineMIME("text/x-yaml", "yaml"); diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/theme/ambiance.css b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/theme/ambiance.css deleted file mode 100644 index d8e8b01c1a..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/theme/ambiance.css +++ /dev/null @@ -1,81 +0,0 @@ -/* ambiance theme for code-mirror */ - -/* Color scheme */ - -.cm-s-ambiance .cm-keyword { color: #cda869; } -.cm-s-ambiance .cm-atom { color: #CF7EA9; } -.cm-s-ambiance .cm-number { color: #78CF8A; } -.cm-s-ambiance .cm-def { color: #aac6e3; } -.cm-s-ambiance .cm-variable { color: #ffb795; } -.cm-s-ambiance .cm-variable-2 { color: #eed1b3; } -.cm-s-ambiance .cm-variable-3 { color: #faded3; } -.cm-s-ambiance .cm-property { color: #eed1b3; } -.cm-s-ambiance .cm-operator {color: #fa8d6a;} -.cm-s-ambiance .cm-comment { color: #555; font-style:italic; } -.cm-s-ambiance .cm-string { color: #8f9d6a; } -.cm-s-ambiance .cm-string-2 { color: #9d937c; } -.cm-s-ambiance .cm-meta { color: #D2A8A1; } -.cm-s-ambiance .cm-error { color: #AF2018; } -.cm-s-ambiance .cm-qualifier { color: yellow; } -.cm-s-ambiance .cm-builtin { color: #9999cc; } -.cm-s-ambiance .cm-bracket { color: #24C2C7; } -.cm-s-ambiance .cm-tag { color: #fee4ff } -.cm-s-ambiance .cm-attribute { color: #9B859D; } -.cm-s-ambiance .cm-header {color: blue;} -.cm-s-ambiance .cm-quote { color: #24C2C7; } -.cm-s-ambiance .cm-hr { color: pink; } -.cm-s-ambiance .cm-link { color: #F4C20B; } -.cm-s-ambiance .cm-special { color: #FF9D00; } - -.cm-s-ambiance .CodeMirror-matchingbracket { color: #0f0; } -.cm-s-ambiance .CodeMirror-nonmatchingbracket { color: #f22; } - -.cm-s-ambiance .CodeMirror-selected { - background: rgba(255, 255, 255, 0.15); -} -.CodeMirror-focused .cm-s-ambiance .CodeMirror-selected { - background: rgba(255, 255, 255, 0.10); -} - -/* Editor styling */ - -.cm-s-ambiance { - line-height: 1.40em; - font-family: Monaco, Menlo,"Andale Mono","lucida console","Courier New",monospace !important; - color: #E6E1DC; - background-color: #202020; - -webkit-box-shadow: inset 0 0 10px black; - -moz-box-shadow: inset 0 0 10px black; - -o-box-shadow: inset 0 0 10px black; - box-shadow: inset 0 0 10px black; -} - -.cm-s-ambiance .CodeMirror-gutter { - background: #3D3D3D; - padding: 0 5px; - text-shadow: #333 1px 1px; - border-right: 1px solid #4D4D4D; - box-shadow: 0 10px 20px black; -} - -.cm-s-ambiance .CodeMirror-gutter .CodeMirror-gutter-text { - text-shadow: 0px 1px 1px #4d4d4d; - color: #222; -} - -.cm-s-ambiance .CodeMirror-lines { - -} - -.cm-s-ambiance .CodeMirror-lines .CodeMirror-cursor { - border-left: 1px solid #7991E8; -} - -.cm-s-ambiance .activeline { - background: none repeat scroll 0% 0% rgba(255, 255, 255, 0.031); -} - -.cm-s-ambiance, -.cm-s-ambiance .CodeMirror-gutter { - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAQAAAAHUWYVAABFFUlEQVQYGbzBCeDVU/74/6fj9HIcx/FRHx9JCFmzMyGRURhLZIkUsoeRfUjS2FNDtr6WkMhO9sm+S8maJfu+Jcsg+/o/c+Z4z/t97/vezy3z+z8ekGlnYICG/o7gdk+wmSHZ1z4pJItqapjoKXWahm8NmV6eOTbWUOp6/6a/XIg6GQqmenJ2lDHyvCFZ2cBDbmtHA043VFhHwXxClWmeYAdLhV00Bd85go8VmaFCkbVkzlQENzfBDZ5gtN7HwF0KDrTwJ0dypSOzpaKCMwQHKTIreYIxlmhXTzTWkVm+LTynZhiSBT3RZQ7aGfjGEd3qyXQ1FDymqbKxpspERQN2MiRjNZlFFQXfCNFm9nM1zpAsoYjmtRTc5ajwuaXc5xrWskT97RaKzAGe5ARHhVUsDbjKklziiX5WROcJwSNCNI+9w1Jwv4Zb2r7lCMZ4oq5C0EdTx+2GzNuKpJ+iFf38JEWkHJn9DNF7mmBDITrWEg0VWL3pHU20tSZnuqWu+R3BtYa8XxV1HO7GyD32UkOpL/yDloINFTmvtId+nmAjxRw40VMwVKiwrKLE4bK5UOVntYwhOcSSXKrJHKPJedocpGjVz/ZMIbnYUPB10/eKCrs5apqpgVmWzBYWpmtKHecJPjaUuEgRDDaU0oZghCJ6zNMQ5ZhDYx05r5v2muQdM0EILtXUsaKiQX9WMEUotagQzFbUNN6NUPC2nm5pxEWGCjMc3GdJHjSU2kORLK/JGSrkfGEIjncU/CYUnOipoYemwj8tST9NsJmB7TUVXtbUtXATJVZXBMvYeTXJfobgJUPmGMP/yFaWonaa6BcFO3nqcIqCozSZoZoSr1g4zJOzuyGnxTEX3lUEJ7WcZgme8ddaWvWJo2AJR9DZU3CUIbhCSG6ybSwN6qtJVnCU2svDTP2ZInOw2cBTrqtQahtNZn9NcJ4l2NaSmSkkP1noZWnVwkLmdUPOwLZEwy2Z3S3R+4rIG9hcbpPXHFVWcQdZkn2FOta3cKWQnNRC5g1LsJah4GCzSVsKnCOY5OAFRTBekyyryeyilhFKva75r4Mc0aWanGEaThcy31s439KKxTzJYY5WTHPU1FtIHjQU3Oip4xlNzj/lBw23dYZVliQa7WAXf4shetcQfatI+jWRDBPmyNeW6A1P5kdDgyYJlba0BIM8BZu1JfrFwItyjcAMR3K0BWOIrtMEXyhyrlVEx3ui5dUBjmB/Q3CXW85R4mBD0s7B+4q5tKUjOlb9qqmhi5AZ6GFIC5HXtOobdYGlVdMVbNJ8toNTFcHxnoL+muBagcctjWnbNMuR00uI7nQESwg5q2qqrKWIfrNUmeQocY6HuyxJV02wj36w00yhpmUFenv4p6fUkZYqLyuinx2RGOjhCXYyJF84oiU00YMOOhhquNdfbOB7gU88pY4xJO8LVdp6/q2voeB4R04vIdhSE40xZObx1HGGJ/ja0LBthFInKaLPPFzuCaYaoj8JjPME8yoyxo6zlBqkiUZYgq00OYMswbWO5NGmq+xhipxHLRW29ARjNKXO0wRnear8XSg4XFPLKEPUS1GqvyLwiuBUoa7zpZ0l5xxFwWmWZC1H5h5FwU8eQ7K+g8UcVY6TMQreVQT/8uQ8Z+ALIXnSEa2pYZQneE9RZbSBNYXfWYJzW/h/4j4Dp1tYVcFIC5019Vyi4ThPqSFCzjGWaHQTBU8q6vrVwgxP9Lkm840imWKpcLCjYTtrKuwvsKSnrvHCXGkSMk9p6lhckfRpIeis+N2PiszT+mFLspyGleUhDwcLrZqmyeylxwjBcKHEapqkmyangyLZRVOijwOtCY5SsG5zL0OwlCJ4y5KznF3EUNDDrinwiyLZRzOXtlBbK5ITHFGLp8Q0R6ab6mS7enI2cFrxOyHvOCFaT1HThS1krjCwqWeurCkk+willhCC+RSZnRXBiZaC5RXRIZYKp2lyfrHwiKPKR0JDzrdU2EFgpidawlFDR6FgXUMNa+g1FY3bUQh2cLCwosRdnuQTS/S+JVrGLeWIvtQUvONJxlqSQYYKpwoN2kaocLjdVsis4Mk80ESF2YpSkzwldjHkjFCUutI/r+EHDU8oCs6yzL3PhWiEooZdFMkymlas4AcI3KmoMMNSQ3tHzjGWCrcJJdYyZC7QFGwjRL9p+MrRkAGWzIaWCn9W0F3TsK01c2ZvQw0byvxuQU0r1lM0qJO7wW0kRIMdDTtXEdzi4VIh+EoIHm0mWtAtpCixlabgn83fKTI7anJe9ST7WIK1DMGpQmYeA58ImV6ezOGOzK2Kgq01pd60cKWiUi9Lievb/0vIDPHQ05Kzt4ddPckQBQtoaurjyHnek/nKzpQLrVgKPjIkh2v4uyezpv+Xoo7fPFXaGFp1vaLKxQ4uUpQQS5VuQs7BCq4xRJv7fwpVvvFEB3j+620haOuocqMhWd6TTPAEx+mdFNGHdranFe95WrWmIvlY4F1Dle2ECgc6cto7SryuqGGGha0tFQ5V53migUKmg6XKAo4qS3mik+0OZpAhOLeZKicacgaYcyx5hypYQE02ZA4xi/pNhOQxR4klNKyqacj+mpxnLTnnGSo85++3ZCZq6lrZkXlGEX3o+C9FieccJbZWVFjC0Yo1FZnJhoYMFoI1hEZ9r6hwg75HwzBNhbZCdJEfJwTPGzJvaKImw1yYX1HDAmpXR+ZJQ/SmgqMNVQb5vgamGwLtt7VwvP7Qk1xpiM5x5Cyv93E06MZmgs0Nya2azIKOYKCGBQQW97RmhKNKF02JZqHEJ4o58qp7X5EcZmc56trXEqzjCBZ1MFGR87Ql2tSTs6CGxS05PTzRQorkbw7aKoKXFDXsYW42VJih/q+FP2BdTzDTwVqOYB13liM50vG7wy28qagyuIXMeQI/Oqq8bcn5wJI50xH00CRntyfpL1T4hydYpoXgNiFzoIUTDZnLNRzh4TBHwbYGDvZkxmlyJloyr6tRihpeUG94GnKtIznREF0tzJG/OOr73JBcrSh1k6WuTprgLU+mnSGnv6Zge0NNz+kTDdH8nuAuTdJDCNb21LCiIuqlYbqGzT3RAoZofQfjFazkqeNWdYaGvYTM001EW2oKPvVk1ldUGSgUtHFwjKM1h9jnFcmy5lChoLNaQMGGDsYbKixlaMBmmsx1QjCfflwTfO/gckW0ruZ3jugKR3R5W9hGUWqCgxuFgsuaCHorotGKzGaeZB9DMsaTnKCpMtwTvOzhYk0rdrArKCqcaWmVk1+F372ur1YkKxgatI8Qfe1gIX9wE9FgS8ESmuABIXnRUbCapcKe+nO7slClSZFzpV/LkLncEb1qiO42fS3R855Su2mCLh62t1SYZZYVmKwIHjREF2uihTzB20JOkz7dkxzYQnK0UOU494wh+VWRc6Un2kpTaVgLDFEkJ/uhzRcI0YKGgpGWOlocBU/a4fKoJ/pEaNV6jip3+Es9VXY078rGnmAdf7t9ylPXS34RBSuYPs1UecZTU78WanhBCHpZ5sAoTz0LGZKjPf9TRypqWEiTvOFglL1fCEY3wY/++rbk7C8bWebA6p6om6PgOL2kp44TFJlVNBXae2rqqdZztOJpT87GQsE9jqCPIe9VReZuQ/CIgacsyZdCpIScSYqcZk8r+nsyCzhyfhOqHGOIvrLknC8wTpFcaYiGC/RU1NRbUeUpocQOnkRpGOrIOcNRx+1uA0UrzhSSt+VyS3SJpnFWkzNDqOFGIWcfR86DnmARTQ1HKIL33ExPiemeOhYSSjzlSUZZuE4TveoJLnBUOFof6KiysCbnAEcZgcUNTDOwkqWu3RWtmGpZwlHhJENdZ3miGz0lJlsKnjbwqSHQjpxnFDlTLLwqJPMZMjd7KrzkSG7VsxXBZE+F8YZkb01Oe00yyRK9psh5SYh29ySPKBo2ylNht7ZkZnsKenjKNJu9PNEyZpaCHv4Kt6RQsLvAVp7M9kIimmCUwGeWqLMmGuIotYMmWNpSahkhZw9FqZsVnKJhsjAHvtHMsTM9fCI06Dx/u3vfUXCqfsKRc4oFY2jMsoo/7DJDwZ1CsIKnJu+J9ldkpmiCxQx1rWjI+T9FwcWWzOuaYH0Hj7klNRVWEQpmaqosakiGNTFHdjS/qnUdmf0NJW5xsL0HhimCCZZSRzmSPTXJQ4aaztAwtZnoabebJ+htCaZ7Cm535ByoqXKbX1WRc4Eh2MkRXWzImVc96Cj4VdOKVxR84VdQsIUM8Psoou2byVHyZFuq7O8otbSQ2UAoeEWTudATLGSpZzVLlXVkPU2Jc+27lsw2jmg5T5VhbeE3BT083K9WsTTkFU/Osi0rC5lRlpwRHUiesNS0sOvmqGML1aRbPAxTJD9ZKtxuob+hhl8cwYGWpJ8nub7t5p6coYbMovZ1BTdaKn1jYD6h4GFDNFyT/Kqe1XCXphXHOKLZmuRSRdBPEfVUXQzJm5YGPGGJdvAEr7hHNdGZnuBvrpciGmopOLf5N0uVMy0FfYToJk90uUCbJupaVpO53UJXR2bVpoU00V2KOo4zMFrBd0Jtz2pa0clT5Q5L8IpQ177mWQejPMEJhuQjS10ref6HHjdEhy1P1EYR7GtO0uSsKJQYLiTnG1rVScj5lyazpqWGl5uBbRWl7m6ixGOOnEsMJR7z8J0n6KMnCdxhiNYQCoZ6CmYLnO8omC3MkW3bktlPmEt/VQQHejL3+dOE5FlPdK/Mq8hZxxJtLyRrepLThYKbLZxkSb5W52vYxNOaOxUF0yxMUPwBTYqCzy01XayYK0sJyWBLqX0MwU5CzoymRzV0EjjeUeLgDpTo6ij42ZAzvD01dHUUTPLU96MdLbBME8nFBn7zJCMtJcZokn8YoqU0FS5WFKyniHobguMcmW8N0XkWZjkyN3hqOMtS08r+/xTBwpZSZ3qiVRX8SzMHHjfUNFjgHEPmY9PL3ykEzxkSre/1ZD6z/NuznuB0RcE1TWTm9zRgfUWVJiG6yrzgmWPXC8EAR4Wxhlad0ZbgQyEz3pG5RVEwwDJH2mgKpjcTiCOzn1lfUWANFbZ2BA8balnEweJC9J0iuaeZoI+ippFCztEKVvckR2iice1JvhVytrQwUAZpgsubCPaU7xUe9vWnaOpaSBEspalykhC9bUlOMpT42ZHca6hyrqKmw/wMR8H5ZmdFoBVJb03O4UL0tSNnvIeRmkrLWqrs78gcrEn2tpcboh0UPOW3UUR9PMk4T4nnNKWmCjlrefhCwxRNztfmIQVdDElvS4m1/WuOujoZCs5XVOjtKPGokJzsYCtFYoWonSPT21DheU/wWhM19FcElwqNGOsp9Q8N/cwXaiND1MmeL1Q5XROtYYgGeFq1aTMsoMmcrKjQrOFQTQ1fmBYhmW6o8Jkjc7iDJRTBIo5kgJD5yMEYA3srCg7VFKwiVJkmRCc5ohGOKhsYMn/XBLdo5taZjlb9YAlGWRimqbCsoY7HFAXLa5I1HPRxMMsQDHFkWtRNniqT9UEeNjcE7RUlrCJ4R2CSJuqlKHWvJXjAUNcITYkenuBRB84TbeepcqTj3zZyFJzgYQdHnqfgI0ddUwS6GqWpsKWhjq9cV0vBAEMN2znq+EBfIWT+pClYw5xsTlJU6GeIBsjGmmANTzJZiIYpgrM0Oa8ZMjd7NP87jxhqGOhJlnQtjuQpB+8aEE00wZFznSJPyHxgH3HkPOsJFvYk8zqCHzTs1BYOa4J3PFU+UVRZxlHDM4YavlNUuMoRveiZA2d7grMNc2g+RbSCEKzmgYsUmWmazFJyoiOZ4KnyhKOGRzWJa0+moyV4TVHDzn51Awtqaphfk/lRQ08FX1iiqxTB/kLwd0VynKfEvI6cd4XMV5bMhZ7gZUWVzYQ6Nm2BYzxJbw3bGthEUUMfgbGeorae6DxHtJoZ6alhZ0+ytiVoK1R4z5PTrOECT/SugseEOlb1MMNR4VRNcJy+V1Hg9ONClSZFZjdHlc6W6FBLdJja2MC5hhpu0DBYEY1TFGwiFAxRRCsYkiM9JRb0JNMVkW6CZYT/2EiTGWmo8k+h4FhDNE7BvppoTSFnmCV5xZKzvcCdDo7VVPnIU+I+Rc68juApC90MwcFCsJ5hDqxgScYKreruyQwTqrzoqDCmhWi4IbhB0Yrt3RGa6GfDv52rKXWhh28dyZaWUvcZeMTBaZoSGyiCtRU5J8iviioHaErs7Jkj61syVzTTgOcUOQ8buFBTYWdL5g3T4qlpe0+wvD63heAXRfCCIed9RbCsp2CiI7raUOYOTU13N8PNHvpaGvayo4a3LLT1lDrVEPT2zLUlheB1R+ZTRfKWJ+dcocLJfi11vyJ51lLqJ0WD7tRwryezjiV5W28uJO9qykzX8JDe2lHl/9oyBwa2UMfOngpXCixvKdXTk3wrsKmiVYdZIqsoWEERjbcUNDuiaQomGoIbFdEHmsyWnuR+IeriKDVLnlawlyNHKwKlSU631PKep8J4Q+ayjkSLKYLhalNHlYvttb6fHm0p6OApsZ4l2VfdqZkjuysy6ysKLlckf1KUutCTs39bmCgEyyoasIWlVaMF7mgmWtBT8Kol5xpH9IGllo8cJdopcvZ2sImlDmMIbtDk3KIpeNiS08lQw11NFPTwVFlPP6pJ2gvRfI7gQUfmNAtf6Gs0wQxDsKGlVBdF8rCa3jzdwMaGHOsItrZk7hAyOzpK9VS06j5F49b0VNGOOfKs3lDToMsMBe9ZWtHFEgxTJLs7qrygKZjUnmCYoeAqeU6jqWuLJup4WghOdvCYJnrSkSzoyRkm5M2StQwVltPkfCAk58tET/CSg+8MUecmotMEnhBKfWBIZsg2ihruMJQaoIm+tkTLKEqspMh00w95gvFCQRtDwTT1gVDDSEVdlwqZfxoQRbK0g+tbiBZxzKlpnpypejdDwTaeOvorMk/IJE10h9CqRe28hhLbe0pMsdSwv4ZbhKivo2BjDWfL8UKJgeavwlwb5KlwhyE4u4XkGE2ytZCznKLCDZZq42VzT8HLCrpruFbIfOIINmh/qCdZ1ZBc65kLHR1Bkyf5zn6pN3SvGKIlFNGplhrO9QSXanLOMQTLCa0YJCRrCZm/CZmrLTm7WzCK4GJDiWUdFeYx1LCFg3NMd0XmCuF3Y5rITLDUsYS9zoHVzwnJoYpSTQoObyEzr4cFBNqYTopoaU/wkyLZ2lPhX/5Y95ulxGTV7KjhWrOZgl8MyUUafjYraNjNU1N3IWcjT5WzWqjwtoarHSUObGYO3GCJZpsBlnJGPd6ZYLyl1GdCA2625IwwJDP8GUKymbzuyPlZlvTUsaUh5zFDhRWFzPKKZLAlWdcQbObgF9tOqOsmB1dqcqYJmWstFbZRRI9poolmqiLnU0POvxScpah2iSL5UJNzgScY5+AuIbpO0YD3NCW+dLMszFSdFCWGqG6eVq2uYVNDdICGD6W7EPRWZEY5gpsE9rUkS3mijzzJnm6UpUFXG1hCUeVoS5WfNcFpblELL2qqrCvMvRfd45oalvKU2tiQ6ePJOVMRXase9iTtLJztPxJKLWpo2CRDcJwn2sWSLKIO1WQWNTCvpVUvOZhgSC40JD0dOctaSqzkCRbXsKlb11Oip6PCJ0IwSJM31j3akRxlP7Rwn6aGaUL0qiLnJkvB3xWZ2+Q1TfCwpQH3G0o92UzmX4o/oJNQMMSQc547wVHhdk+VCw01DFYEnTxzZKAm74QmeNNR1w6WzEhNK15VJzuCdxQ53dRUDws5KvwgBMOEgpcVNe0hZI6RXT1Jd0cyj5nsaEAHgVmGaJIlWdsc5Ui2ElrRR6jrRAttNMEAIWrTDFubkZaok7/AkzfIwfuWVq0jHzuCK4QabtLUMVPB3kJ0oyHTSVFlqMALilJf2Rf8k5aaHtMfayocLBS8L89oKoxpJvnAkDPa0qp5DAUTHKWmCcnthlou8iCKaFFLHWcINd1nyIwXqrSxMNmSs6KmoL2QrKuWtlQ5V0120xQ5vRyZS1rgFkWwhiOwiuQbR0OOVhQM9iS3tiXp4RawRPMp5tDletOOBL95MpM01dZTBM9pkn5qF010rIeHFcFZhmSGpYpTsI6nwhqe5C9ynhlpp5ophuRb6WcJFldkVnVEwwxVfrVkvnWUuNLCg5bgboFHPDlDPDmnK7hUrWiIbjadDclujlZcaokOFup4Ri1kacV6jmrrK1hN9bGwpKEBQ4Q6DvIUXOmo6U5LqQM6EPyiKNjVkPnJkDPNEaxhiFay5ExW1NXVUGqcpYYdPcGiCq7z/TSlbhL4pplWXKd7NZO5QQFrefhRQW/NHOsqcIglc4UhWklR8K0QzbAw08CBDnpbgqXdeD/QUsM4RZXDFBW6WJKe/mFPdH0LtBgiq57wFLzlyQzz82qYx5D5WJP5yVJDW01BfyHnS6HKO/reZqId1WGa4Hkh2kWodJ8i6KoIPlAj2hPt76CzXsVR6koPRzWTfKqIentatYpQw2me4AA3y1Kind3SwoOKZDcFXTwl9tWU6mfgRk9d71sKtlNwrjnYw5tC5n5LdKiGry3JKNlHEd3oaMCFHrazBPMp/uNJ+V7IudcSbeOIdjUEdwl0VHCOZo5t6YluEuaC9mQeMgSfOyKnYGFHcIeQ84yQWbuJYJpZw5CzglDH7gKnWqqM9ZTaXcN0TeYhR84eQtJT76JJ1lREe7WnnvsMmRc9FQ7SBBM9mV3lCUdmHk/S2RAMt0QjFNFqQpWjDPQ01DXWUdDBkXziKPjGEP3VP+zIWU2t7im41FOloyWzn/L6dkUy3VLDaZ6appgDLHPjJEsyvJngWEPUyVBiAaHCTEXwrLvSEbV1e1gKJniicWorC1MUrVjB3uDhJE/wgSOzk1DXpk0k73qCM8xw2UvD5kJmDUfOomqMpWCkJRlvKXGmoeBm18USjVIk04SClxTB6YrgLAPLWYK9HLUt5cmc0vYES8GnTeRc6skZbQkWdxRsIcyBRzx1DbTk9FbU0caTPOgJHhJKnOGIVhQqvKmo0llRw9sabrZkDtdg3PqaKi9oatjY8B+G371paMg6+mZFNNtQ04mWBq3rYLOmtWWQp8KJnpy9DdFensyjdqZ+yY40VJlH8wcdLzC8PZnvHMFUTZUrDTkLyQaGus5X5LzpYAf3i+e/ZlhqGqWhh6Ou6xTR9Z6oi5AZZtp7Mj2EEm8oSpxiYZCHU/1fbGdNNNRRoZMhmilEb2gqHOEJDtXkHK/JnG6IrvbPCwV3NhONVdS1thBMs1T4QOBcTWa2IzhMk2nW5Kyn9tXUtpv9RsG2msxk+ZsQzRQacJncpgke0+T8y5Fzj8BiGo7XlJjaTIlpQs7KFjpqGnKuoyEPeIKnFMkZHvopgh81ySxNFWvJWcKRs70j2FOT012IllEEO1n4pD1513Yg2ssQPOThOkvyrqHUdEXOSEsihmBbTbKX1kLBPWqWkLOqJbjB3GBIZmoa8qWl4CG/iZ7oiA72ZL7TJNeZUY7kFQftDcHHluBzRbCegzMtrRjVQpX2lgoPKKLJAkcbMl01XK2p7yhL8pCBbQ3BN2avJgKvttcrWDK3CiUOVxQ8ZP+pqXKyIxnmBymCg5vJjNfkPK4+c8cIfK8ocVt7kmfd/I5SR1hKvCzUtb+lhgc00ZaO6CyhIQP1Uv4yIZjload72PXX0OIJvnFU+0Zf6MhsJwTfW0r0UwQfW4LNLZl5HK261JCZ4qnBaAreVAS3WrjV0LBnNDUNNDToCEeFfwgcb4gOEqLRhirWkexrCEYKVV711DLYEE1XBEsp5tpTGjorkomKYF9FDXv7fR3BGwbettSxnyL53MBPjsxDZjMh+VUW9NRxq1DhVk+FSxQcaGjV9Pawv6eGByw5qzoy7xk4RsOShqjJwWKe/1pEEfzkobeD/dQJmpqedcyBTy2sr4nGNRH0c0SPWTLrqAc0OQcb/gemKgqucQT7ySWKCn2EUotoCvpZct7RO2sy/QW0IWcXd7pQRQyZVwT2USRO87uhjioTLKV2brpMUcMQRbKH/N2T+UlTpaMls6cmc6CCNy3JdYYSUzzJQ4oSD3oKLncULOiJvjBEC2oqnCJkJluCYy2ZQ5so9YYlZ1VLlQU1mXEW1jZERwj/MUSRc24TdexlqLKfQBtDTScJUV8FszXBEY5ktpD5Ur9hYB4Nb1iikw3JoYpkKX+RodRKFt53MMuRnKSpY31PwYaGaILh3wxJGz9TkTPEETxoCWZrgvOlmyMzxFEwVJE5xZKzvyJ4WxEc16Gd4Xe3Weq4XH2jKRikqOkGQ87hQnC7wBmGYLAnesX3M+S87eFATauuN+Qcrh7xIxXJbUIdMw3JGE3ylCWzrieaqCn4zhGM19TQ3z1oH1AX+pWEqIc7wNGAkULBo/ZxRaV9NNyh4Br3rCHZzbzmSfawBL0dNRwpW1kK9mxPXR9povcdrGSZK9c2k0xwFGzjuniCtRSZCZ6ccZ7gaktmgAOtKbG/JnOkJrjcQTdFMsxRQ2cLY3WTIrlCw1eWKn8R6pvt4GFDso3QoL4a3nLk3G6JrtME3dSenpx7PNFTmga0EaJTLQ061sEeQoWXhSo9LTXsaSjoJQRXeZLtDclbCrYzfzHHeaKjHCVOUkQHO3JeEepr56mhiyaYYKjjNU+Fed1wS5VlhWSqI/hYUdDOkaxiKehoyOnrCV5yBHtbWFqTHCCwtpDcYolesVR5yUzTZBb3RNMd0d6WP+SvhuBmRcGxnuQzT95IC285cr41cLGQ6aJJhmi4TMGempxeimBRQw1tFKV+8jd6KuzoSTqqDxzRtpZkurvKEHxlqXKRIjjfUNNXQsNOsRScoWFLT+YeRZVD3GRN0MdQcKqQjHDMrdGGVu3iYJpQx3WGUvfbmxwFfR20WBq0oYY7LMFhhgYtr8jpaEnaOzjawWWaTP8mMr0t/EPDPoqcnxTBI5o58L7uoWnMrpoqPwgVrlAUWE+V+TQl9rawoyP6QGAlQw2TPRX+YSkxyBC8Z6jhHkXBgQL7WII3DVFnRfCrBfxewv9D6xsyjys4VkhWb9pUU627JllV0YDNHMku/ldNMMXDEo4aFnAkk4U6frNEU4XgZUPmEKHUl44KrzmYamjAbh0JFvGnaTLPu1s9jPCwjFpYiN7z1DTOk/nc07CfDFzmCf7i+bfNHXhDtLeBXzTBT5rkMvWOIxpl4EMh2LGJBu2syDnAEx2naEhHDWMMzPZEhygyS1mS5RTJr5ZkoKbEUoYqr2kqdDUE8ztK7OaIntJkFrIECwv8LJTaVx5XJE86go8dFeZ3FN3rjabCAYpoYEeC9zzJVULBbmZhDyd7ko09ydpNZ3nm2Kee4FPPXHnYEF1nqOFEC08LUVcDvYXkJHW8gTaKCk9YGOeIJhqiE4ToPEepdp7IWFjdwnWaufGMwJJCMtUTTBBK9BGCOy2tGGrJTHIwyEOzp6aPzNMOtlZkDvcEWpP5SVNhfkvDxhmSazTJXYrM9U1E0xwFVwqZQwzJxw6+kGGGUj2FglGGmnb1/G51udRSMNlTw6GGnCcUwVcOpmsqTHa06o72sw1RL02p9z0VbnMLOaIX3QKaYKSCFQzBKEUNHTSc48k53RH9wxGMtpQa5KjjW0W0n6XCCCG4yxNNdhQ4R4l1Ff+2sSd6UFHiIEOyqqFgT01mEUMD+joy75jPhOA+oVVLm309FR4yVOlp4RhLiScNmSmaYF5Pw0STrOIoWMSR2UkRXOMp+M4SHW8o8Zoi6OZgjKOaFar8zZDzkWzvKOjkKBjmCXby8JahhjXULY4KlzgKLvAwxVGhvyd4zxB1d9T0piazmKLCVZY5sKiD0y2ZSYrkUEPUbIk+dlQ4SJHTR50k1DPaUWIdTZW9NJwnJMOECgd7ou/MnppMJ02O1VT4Wsh85MnZzcFTngpXGKo84qmwgKbCL/orR/SzJ2crA+t6Mp94KvxJUeIbT3CQu1uIdlQEOzlKfS3UMcrTiFmOuroocrZrT2AcmamOKg8YomeEKm/rlT2sociMaybaUlFhuqHCM2qIJ+rg4EcDFymiDSxzaHdPcpE62pD5kyM5SBMoA1PaUtfIthS85ig1VPiPPYXgYEMNk4Qq7TXBgo7oT57gPUdwgCHzhIVFPFU6OYJzHAX9m5oNrVjeE61miDrqQ4VSa1oiURTsKHC0IfjNwU2WzK6eqK8jWln4g15TVBnqmDteCJ501PGAocJhhqjZdtBEB6lnhLreFJKxmlKbeGrqLiSThVIbCdGzloasa6lpMQXHCME2boLpJgT7yWaemu6wBONbqGNVRS0PKIL7LckbjmQtR7K8I5qtqel+T/ChJTNIKLjdUMNIRyvOEko9YYl2cwQveBikCNawJKcLBbc7+JM92mysNvd/Fqp8a0k6CNEe7cnZrxlW0wQXaXjaktnRwNOGZKYiONwS7a1JVheq3WgJHlQUGKHKmp4KAxXR/ULURcNgoa4zhKSLpZR3kxRRb0NmD0OFn+UCS7CzI1nbP6+o4x47QZE5xRCt3ZagnYcvmpYQktXdk5YKXTzBC57kKEe0VVuiSYqapssMS3C9p2CKkHOg8B8Pa8p5atrIw3qezIWanMGa5HRDNF6RM9wcacl0N+Q8Z8hsIkSnaIIdHRUOEebAPy1zbCkhM062FCJtif7PU+UtoVXzWKqM1PxXO8cfdruhFQ/a6x3JKYagvVDhQEtNiyiiSQ7OsuRsZUku0CRNDs4Sog6KKjsZgk2bYJqijgsEenoKeniinRXBn/U3lgpPdyDZynQx8IiioMnCep5Ky8mjGs6Wty0l1hUQTcNWswS3WRp2kCNZwJG8omG8JphPUaFbC8lEfabwP7VtM9yoaNCAjpR41VNhrD9LkbN722v0CoZMByFzhaW+MyzRYEWFDQwN2M4/JiT76PuljT3VU/A36eaIThb+R9oZGOAJ9tewkgGvqOMNRWYjT/Cwu99Q8LqDE4TgbLWxJ1jaDDAERsFOFrobgjUsBScaguXU8kKm2RL19tRypSHnHNlHiIZqgufs4opgQdVdwxBNNFBR6kVFqb8ogimOzB6a6HTzrlDHEpYaxjiiA4TMQobkDg2vejjfwJGWmnbVFAw3H3hq2NyQfG7hz4aC+w3BbwbesG0swYayvpAs6++Ri1Vfzx93mFChvyN5xVHTS+0p9aqCAxyZ6ZacZyw5+7uuQkFPR9DDk9NOiE7X1PCYJVjVUqq7JlrHwWALF5nfHNGjApdpqgzx5OwilDhCiDYTgnc9waGW4BdLNNUQvOtpzDOWHDH8D7TR/A/85KljEQu3NREc4Pl/6B1Hhc8Umb5CsKMmGC9EPcxoT2amwHNCmeOEnOPbklnMkbOgIvO5UMOpQrS9UGVdt6iH/fURjhI/WOpaW9OKLYRod6HCUEdOX000wpDZQ6hwg6LgZfOqo1RfT/CrJzjekXOGhpc1VW71ZLbXyyp+93ILbC1kPtIEYx0FIx1VDrLoVzXRKRYWk809yYlC9ImcrinxtabKnzRJk3lAU1OLEN1j2zrYzr2myHRXJFf4h4QKT1qSTzTB5+ZNTzTRkAxX8FcLV2uS8eoQQ2aAkFzvCM72sJIcJET3WPjRk5wi32uSS9rfZajpWEvj9hW42F4o5NytSXYy8IKHay10VYdrcl4SkqscrXpMwyGOgtkajheSxdQqmpxP1L3t4R5PqasFnrQEjytq6qgp9Y09Qx9o4S1FzhUCn1kyHSzBWLemoSGvOqLNhZyBjmCaAUYpMgt4Ck7wBBMMwWKWgjsUwTaGVsxWC1mYoKiyqqeGKYqonSIRQ3KIkHO0pmAxTdBHkbOvfllfr+AA+7gnc50huVKYK393FOyg7rbPO/izI7hE4CnHHHnJ0ogNPRUGeUpsrZZTBJcrovUcJe51BPsr6GkJdhCCsZ6aTtMEb2pqWkqeVtDXE/QVggsU/Nl86d9RMF3DxvZTA58agu810RWawCiSzzXBeU3MMW9oyJUedvNEvQyNu1f10BSMddR1vaLCYpYa/mGocLSiYDcLbQz8aMn5iyF4xBNMs1P0QEOV7o5gaWGuzSeLue4tt3ro7y4Tgm4G/mopdZgl6q0o6KzJWE3mMksNr3r+a6CbT8g5wZNzT9O7fi/zpaOmnz3BRoqos+tv9zMbdpxsqDBOEewtJLt7cg5wtKKbvldpSzRRCD43VFheCI7yZLppggMVBS/KMAdHODJvOwq2NQSbKKKPLdFWQs7Fqo+mpl01JXYRgq8dnGLhTiFzqmWsUMdpllZdbKlyvSdYxhI9YghOtxR8LgSLWHK62mGGVoxzBE8LNWzqH9CUesQzFy5RQzTc56mhi6fgXEWwpKfE5Z7M05ZgZUPmo6auiv8YKzDYwWBLMErIbKHJvOwIrvEdhOBcQ9JdU1NHQ7CXn2XIDFBKU2WAgcX9UAUzDXWd5alwuyJ41Z9rjKLCL4aCp4WarhPm2rH+SaHUYE001JDZ2ZAzXPjdMpZWvC9wmqIB2lLhQ01D5jO06hghWMndbM7yRJMsoCj1vYbnFQVrW9jak3OlEJ3s/96+p33dEPRV5GxiqaGjIthUU6FFEZyqCa5qJrpBdzSw95IUnOPIrCUUjRZQFrbw5PR0R1qiYx3cb6nrWUMrBmmiBQxVHtTew5ICP/ip6g4hed/Akob/32wvBHsIOX83cI8hGeNeNPCIkPmXe8fPKx84OMSRM1MTdXSwjCZ4S30jVGhvqTRak/OVhgGazHuOCud5onEO1lJr6ecVyaOK6H7zqlBlIaHE0oroCgfvGJIdPcmfLNGLjpz7hZwZQpUbFME0A1cIJa7VNORkgfsMBatbKgwwJM9bSvQXeNOvbIjelg6WWvo5kvbKaJJNHexkKNHL9xRyFlH8Ti2riB5wVPhUk7nGkJnoCe428LR/wRGdYIlmWebCyxou1rCk4g/ShugBDX0V0ZQWkh0dOVsagkM0yV6OoLd5ye+pRlsCr0n+KiQrGuq5yJDzrTAXHtLUMduTDBVKrSm3eHL+6ijxhFDX9Z5gVU/wliHYTMiMFpKLNMEywu80wd3meoFmt6VbRMPenhrOc6DVe4pgXU8DnnHakLOIIrlF4FZPIw6R+zxBP0dyq6OOZ4Q5sLKCcz084ok+VsMMyQhNZmmBgX5xIXOEJTmi7VsGTvMTNdHHhpzdbE8Du2oKxgvBqQKdDDnTFOylCFaxR1syz2iqrOI/FEpNc3C6f11/7+ASS6l2inq2ciTrCCzgyemrCL5SVPjQkdPZUmGy2c9Sw9FtR1sS30RmsKPCS4rkIC/2U0MduwucYolGaPjKEyhzmiPYXagyWbYz8LWBDdzRimAXzxx4z8K9hpzlhLq+NiQ97HuKorMUfK/OVvC2JfiHUPCQI/q7J2gjK+tTDNxkCc4TMssqCs4TGtLVwQihyoAWgj9bosU80XGW6Ac9TJGziaUh5+hnFcHOnlaM1iRn29NaqGENTTTSUHCH2tWTeV0osUhH6psuVLjRUmGWhm6OZEshGeNowABHcJ2Bpy2ZszRcKkRXd2QuKVEeXnbfaEq825FguqfgfE2whlChSRMdron+LATTPQ2Z369t4B9C5gs/ylzv+CMmepIDPclFQl13W0rspPd1JOcbghGOEutqCv5qacURQl3dDKyvyJlqKXGPgcM9FfawJAMVmdcspcYKOZc4GjDYkFlK05olNMHyHn4zFNykyOxt99RkHlfwmiHo60l2EKI+mhreEKp080Tbug08BVPcgoqC5zWt+NLDTZ7oNSF51N1qie7Va3uCCwyZbkINf/NED6jzOsBdZjFN8oqG3wxVunqCSYYKf3EdhJyf9YWGf7tRU2oH3VHgPr1fe5J9hOgHd7xQ0y7qBwXr23aGErP0cm64JVjZwsOGqL+mhNgZmhJLW2oY4UhedsyBgzrCKrq7BmcpNVhR6jBPq64Vgi+kn6XE68pp8J5/+0wRHGOpsKenQn9DZntPzjRLZpDAdD2fnSgkG9tmIXnUwQ6WVighs7Yi2MxQ0N3CqYaCXkJ0oyOztMDJjmSSpcpvlrk0RMMOjmArQ04PRV1DO1FwhCVaUVPpKUM03JK5SxPsIWRu8/CGHi8UHChiqGFDTbSRJWeYUDDcH6vJWUxR4k1FXbMUwV6e4AJFXS8oMqsZKqzvYQ9DDQdZckY4aGsIhtlubbd2r3j4QBMoTamdPZk7O/Bf62lacZwneNjQoGcdVU7zJOd7ghsUHOkosagic6cnWc8+4gg285R6zZP5s1/LUbCKIznTwK36PkdwlOrl4U1LwfdCCa+IrvFkmgw1PCAUXKWo0sURXWcI2muKJlgyFzhynCY4RBOsqCjoI1R5zREco0n2Vt09BQtYSizgKNHfUmUrQ5UOCh51BFcLmY7umhYqXKQomOop8bUnWNNQcIiBcYaC6xzMNOS8JQQfeqKBmmglB+97ok/lfk3ygaHSyZaCRTzRxQo6GzLfa2jWBPepw+UmT7SQEJyiyRkhBLMVOfcoMjcK0eZChfUNzFAUzCsEN5vP/X1uP/n/aoMX+K+nw/Hjr/9xOo7j7Pju61tLcgvJpTWXNbfN5jLpi6VfCOviTktKlFusQixdEKWmEBUKNaIpjZRSSOXSgzaaKLdabrm1/9nZ+/f+vd/vz/v9+Xy+zZ7PRorYoZqyLrCwQdEAixxVOEXNNnjX2nUSRlkqGmWowk8lxR50JPy9Bo6qJXaXwNvREBvnThPEPrewryLhcAnj5WE15Fqi8W7R1sAuEu86S4ENikItFN4xkv9Af4nXSnUVcLiA9xzesFpivRRVeFKtsMRaKBhuSbjOELnAUtlSQUpXgdfB4Z1oSbnFEetbQ0IrAe+Y+pqnDcEJFj6S8LDZzZHwY4e3XONNlARraomNEt2bkvGsosA3ioyHm+6jCMbI59wqt4eeara28IzEmyPgoRaUOEDhTVdEJhmCoTWfC0p8aNkCp0oYqih2iqGi4yXeMkOsn4LdLLnmKfh/YogjNsPebeFGR4m9BJHLzB61XQ3BtpISfS2FugsK9FAtLWX1dCRcrCnUp44CNzuCowUZmxSRgYaE6Za0W2u/E7CVXCiI/UOR8aAm1+OSyE3mOUcwyc1zBBeoX1kiKy0Zfxck1Gsyulti11i83QTBF5Kg3pDQThFMVHiPSlK+0cSedng/VaS8bOZbtsBcTcZAR8JP5KeqQ1OYKAi20njdNNRpgnsU//K+JnaXJaGTomr7aYIphoRn9aeShJWKEq9LcozSF7QleEfDI5LYm5bgVkFkRwVDBCVu0DDIkGupo8TZBq+/pMQURYErJQmPKGKjNDkWOLx7Jd5QizdUweIaKrlP7SwJDhZvONjLkOsBBX9UpGxnydhXkfBLQ8IxgojQbLFnJf81JytSljclYYyEFyx0kVBvKWOFJmONpshGAcsduQY5giVNCV51eOdJYo/pLhbvM0uDHSevNKRcrKZIqnCtJeEsO95RoqcgGK4ocZcho1tTYtcZvH41pNQ7vA0WrhIfOSraIIntIAi+NXWCErdbkvrWwjRLrt0NKUdL6KSOscTOdMSOUtBHwL6OLA0vNSdynaWQEnCpIvKaIrJJEbvHkmuNhn6OjM8VkSGSqn1uYJCGHnq9I3aLhNME3t6GjIkO7xrNFumpyTNX/NrwX7CrIRiqqWijI9JO4d1iieykyfiposQIQ8YjjsjlBh6oHWbwRjgYJQn2NgSnNycmJAk3NiXhx44Sxykihxm8ybUwT1OVKySc7vi3OXVkdBJ4AyXBeksDXG0IhgtYY0lY5ahCD0ehborIk5aUWRJviMA7Xt5kyRjonrXENkm8yYqgs8VzgrJmClK20uMM3jRJ0FiQICQF9hdETlLQWRIb5ki6WDfWRPobvO6a4GP5mcOrNzDFELtTkONLh9dXE8xypEg7z8A9jkhrQ6Fhjlg/QVktJXxt4WXzT/03Q8IaQWSqIuEvloQ2mqC9Jfi7wRul4RX3pSPlzpoVlmCtI2jvKHCFhjcM3sN6lqF6HxnKelLjXWbwrpR4xzuCrTUZx2qq9oAh8p6ixCUGr78g8oyjRAtB5CZFwi80VerVpI0h+IeBxa6Zg6kWvpDHaioYYuEsRbDC3eOmC2JvGYLeioxGknL2UATNJN6hmtj1DlpLvDVmocYbrGCVJKOrg4X6DgddLA203BKMFngdJJFtFd7vJLm6KEpc5yjQrkk7M80SGe34X24nSex1Ra5Omgb71JKyg8SrU3i/kARKwWpH0kOGhKkObyfd0ZGjvyXlAkVZ4xRbYJ2irFMkFY1SwyWxr2oo4zlNiV+7zmaweFpT4kR3kaDAFW6xpSqzJay05FtYR4HmZhc9UxKbbfF2V8RG1MBmSaE+kmC6JnaRXK9gsiXhJHl/U0qM0WTcbyhwkYIvFGwjSbjfwhiJt8ZSQU+Bd5+marPMOkVkD0muxYLIfEuhh60x/J92itguihJSEMySVPQnTewnEm+620rTQEMsOfo4/kP/0ARvWjitlpSX7GxBgcMEsd3EEeYWvdytd+Saawi6aCIj1CkGb6Aj9rwhx16Cf3vAwFy5pyLhVonXzy51FDpdEblbkdJbUcEPDEFzQ8qNmhzzLTmmKWKbFCXeEuRabp6rxbvAtLF442QjQ+wEA9eL1xSR7Q0JXzlSHjJ4exq89yR0laScJ/FW6z4a73pFMEfDiRZvuvijIt86RaSFOl01riV2mD1UEvxGk/Geg5aWwGki1zgKPG9J2U8PEg8qYvMsZeytiTRXBMslCU8JSlxi8EabjwUldlDNLfzTUmCgxWsjqWCOHavYAqsknKFIO0yQ61VL5AVFxk6WhEaCAkdJgt9aSkzXlKNX2jEa79waYuc7gq0N3GDJGCBhoiTXUEPsdknCUE1CK0fwsiaylSF2uiDyO4XX3pFhNd7R4itFGc0k/ElBZwWvq+GC6szVeEoS/MZ+qylwpKNKv9Z469UOjqCjwlusicyTxG6VpNxcQ8IncoR4RhLbR+NdpGGmJWOcIzJGUuKPGpQg8rrG21dOMqQssJQ4RxH5jaUqnZuQ0F4Q+cjxLwPtpZbIAk3QTJHQWBE5S1BokoVtDd6lhqr9UpHSUxMcIYl9pojsb8h4SBOsMQcqvOWC2E8EVehqiJ1hrrAEbQxeK0NGZ0Gkq+guSRgniM23bIHVkqwx4hiHd7smaOyglyIyQuM978j4VS08J/A2G1KeMBRo4fBaSNhKUEZfQewVQ/C1I+MgfbEleEzCUw7mKXI0M3hd1EESVji8x5uQ41nxs1q4RMJCCXs7Iq9acpxn22oSDnQ/sJTxsCbHIYZiLyhY05TY0ZLIOQrGaSJDDN4t8pVaIrsqqFdEegtizc1iTew5Q4ayBDMUsQMkXocaYkc0hZua412siZ1rSXlR460zRJ5SlHGe5j801RLMlJTxtaOM3Q1pvxJ45zUlWFD7rsAbpfEm1JHxG0eh8w2R7QQVzBUw28FhFp5QZzq8t2rx2joqulYTWSuJdTYfWwqMFMcovFmSyJPNyLhE4E10pHzYjOC3huArRa571ZsGajQpQx38SBP5pyZB6lMU3khDnp0MBV51BE9o2E+TY5Ml2E8S7C0o6w1xvCZjf0HkVEHCzFoyNmqC+9wdcqN+Tp7jSDheE9ws8Y5V0NJCn2bk2tqSY4okdrEhx1iDN8cSudwepWmAGXKcJXK65H9to8jYQRH7SBF01ESUJdd0TayVInaWhLkOjlXE5irKGOnI6GSWGCJa482zBI9rCr0jyTVcEuzriC1vcr6mwFGSiqy5zMwxBH/TJHwjSPhL8+01kaaSUuMFKTcLEvaUePcrSmwn8DZrgikWb7CGPxkSjhQwrRk57tctmxLsb9sZvL9LSlyuSLlWkqOjwduo8b6Uv1DkmudIeFF2dHCgxVtk8dpIvHpBxhEOdhKk7OLIUSdJ+cSRY57B+0DgGUUlNfpthTfGkauzxrvTsUUaCVhlKeteTXCoJDCa2NOKhOmC4G1H8JBd4OBZReSRGkqcb/CO1PyLJTLB4j1q8JYaIutEjSLX8YKM+a6phdMsdLFUoV5RTm9JSkuDN8WcIon0NZMNZWh1q8C7SJEwV5HxrmnnTrf3KoJBlmCYI2ilSLlfEvlE4011NNgjgthzEua0oKK7JLE7HZHlEl60BLMVFewg4EWNt0ThrVNEVkkiTwpKXSWJzdRENgvKGq4IhjsiezgSFtsfCUq8qki5S1LRQeYQQ4nemmCkImWMw3tFUoUBZk4NOeZYEp4XRKTGa6wJjrWNHBVJR4m3FCnbuD6aak2WsMTh3SZImGCIPKNgsDpVwnsa70K31lCFJZYcwwSMFcQulGTsZuEaSdBXkPGZhu0FsdUO73RHjq8MPGGIfaGIbVTk6iuI3GFgucHrIQkmWSJdBd7BBu+uOryWAhY7+Lki9rK5wtEQzWwvtbqGhIMFwWRJsElsY4m9IIg9L6lCX0VklaPAYkfkZEGDnOWowlBJjtMUkcGK4Lg6EtoZInMUBVYLgn0UsdmCyCz7gIGHFfk+k1QwTh5We7A9x+IdJ6CvIkEagms0hR50eH9UnTQJ+2oiKyVlLFUE+8gBGu8MQ3CppUHesnjTHN4QB/UGPhCTHLFPHMFrCqa73gqObUJGa03wgbhHkrCfpEpzNLE7JDS25FMKhlhKKWKfCgqstLCPu1zBXy0J2ztwjtixBu8UTRn9LVtkmCN2iyFhtME70JHRQ1KVZXqKI/KNIKYMCYs1GUMEKbM1bKOI9LDXC7zbHS+bt+1MTWS9odA9DtrYtpbImQJ2VHh/lisEwaHqUk1kjKTAKknkBEXkbkdMGwq0dnhzLJF3NJH3JVwrqOB4Sca2hti75nmJN0WzxS6UxDYoEpxpa4htVlRjkYE7DZGzJVU72uC9IyhQL4i8YfGWSYLLNcHXloyz7QhNifmKSE9JgfGmuyLhc403Xm9vqcp6gXe3xuuv8F6VJNxkyTHEkHG2g0aKXL0MsXc1bGfgas2//dCONXiNLCX+5mB7eZIl1kHh7ajwpikyzlUUWOVOsjSQlsS+M0R+pPje/dzBXRZGO0rMtgQrLLG9VSu9n6CMXS3BhwYmSoIBhsjNBmZbgusE9BCPCP5triU4VhNbJfE+swSP27aayE8tuTpYYjtrYjMVGZdp2NpS1s6aBnKSHDsbKuplKbHM4a0wMFd/5/DmGyKrJSUaW4IBrqUhx0vyfzTBBLPIUcnZdrAkNsKR0sWRspumSns6Ch0v/qqIbBYUWKvPU/CFoyrDJGwSNFhbA/MlzKqjrO80hRbpKx0Jewsi/STftwGSlKc1JZyAzx05dhLEdnfQvhZOqiHWWEAHC7+30FuRcZUgaO5gpaIK+xsiHRUsqaPElTV40xQZQ107Q9BZE1nryDVGU9ZSQ47bmhBpLcYpUt7S+xuK/FiT8qKjwXYw5ypS2iuCv7q1gtgjhuBuB8LCFY5cUuCNtsQOFcT+4Ih9JX+k8Ea6v0iCIRZOtCT0Et00JW5UeC85Cg0ScK0k411HcG1zKtre3SeITBRk7WfwDhEvaYLTHP9le0m8By0JDwn4TlLW/aJOvGHxdjYUes+ScZigCkYQdNdEOhkiezgShqkx8ueKjI8lDfK2oNiOFvrZH1hS+tk7NV7nOmLHicGWEgubkXKdwdtZknCLJXaCpkrjZBtLZFsDP9CdxWsSr05Sxl6CMmoFbCOgryX40uDtamB7SVmXW4Ihlgpmq+00tBKUUa83WbjLUNkzDmY7cow1JDygyPGlhgGKYKz4vcV7QBNbJIgM11TUqZaMdwTeSguH6rOaw1JRKzaaGyxVm2EJ/uCIrVWUcZUkcp2grMsEjK+DMwS59jQk3Kd6SEq1d0S6uVmO4Bc1lDXTUcHjluCXEq+1OlBDj1pi9zgiXxnKuE0SqTXwhqbETW6RggMEnGl/q49UT2iCzgJvRwVXS2K/d6+ZkyUl7jawSVLit46EwxVljDZwoSQ20sDBihztHfk2yA8NVZghiXwrYHQdfKAOtzsayjhY9bY0yE2CWEeJ9xfzO423xhL5syS2TFJofO2pboHob0nY4GiAgRrvGQEDa/FWSsoaaYl0syRsEt3kWoH3B01shCXhTUWe9w3Bt44SC9QCh3eShQctwbaK2ApLroGCMlZrYqvlY3qYhM0aXpFkPOuoqJ3Dm6fxXrGwVF9gCWZagjPqznfkuMKQ8DPTQRO8ZqG1hPGKEm9IgpGW4DZDgTNriTxvFiq+Lz+0cKfp4wj6OCK9JSnzNSn9LFU7UhKZZMnYwcJ8s8yRsECScK4j5UOB95HFO0CzhY4xJxuCix0lDlEUeMdS6EZBkTsUkZ4K74dugyTXS7aNgL8aqjDfkCE0ZbwkCXpaWCKhl8P7VD5jxykivSyxyZrYERbe168LYu9ZYh86IkscgVLE7tWPKmJv11CgoyJltMEbrohtVAQfO4ImltiHEroYEs7RxAarVpY8AwXMcMReFOTYWe5iiLRQxJ5Q8DtJ8LQhWOhIeFESPGsILhbNDRljNbHzNRlTFbk2S3L0NOS6V1KFJYKUbSTcIIhM0wQ/s2TM0SRMNcQmSap3jCH4yhJZKSkwyRHpYYgsFeQ4U7xoCB7VVOExhXepo9ABBsYbvGWKXPME3lyH95YioZ0gssQRWWbI+FaSMkXijZXwgiTlYdPdkNLaETxlyDVIwqeaEus0aTcYcg0RVOkpR3CSJqIddK+90JCxzsDVloyrFd5ZAr4TBKfaWa6boEA7C7s6EpYaeFPjveooY72mjIccLHJ9HUwVlDhKkmutJDJBwnp1rvulJZggKDRfbXAkvC/4l3ozQOG9a8lxjx0i7nV4jSXc7vhe3OwIxjgSHjdEhhsif9YkPGlus3iLFDnWOFhtCZbJg0UbQcIaR67JjthoCyMEZRwhiXWyxO5QxI6w5NhT4U1WsJvDO60J34fW9hwzwlKij6ZAW9ne4L0s8C6XeBMEkd/LQy1VucBRot6QMlbivaBhoBgjqGiCJNhsqVp/S2SsG6DIONCR0dXhvWbJ+MRRZJkkuEjgDXJjFQW6SSL7GXK8Z2CZg7cVsbWGoKmEpzQ5elpiy8Ryg7dMkLLUEauzeO86CuwlSOlgYLojZWeJ9xM3S1PWfEfKl5ISLQ0MEKR8YOB2QfCxJBjrKPCN4f9MkaSsqoVXJBmP7EpFZ9UQfOoOFwSzBN4MQ8LsGrymlipcJQhmy0GaQjPqCHaXRwuCZwRbqK2Fg9wlClZqYicrIgMdZfxTQ0c7TBIbrChxmuzoKG8XRaSrIhhiyNFJkrC7oIAWMEOQa5aBekPCRknCo4IKPrYkvCDI8aYmY7WFtprgekcJZ3oLIqssCSMtFbQTJKwXYy3BY5oCh2iKPCpJOE+zRdpYgi6O2KmOAgvVCYaU4ySRek1sgyFhJ403QFHiVEmJHwtybO1gs8Hr5+BETQX3War0qZngYGgtVZtoqd6vFSk/UwdZElYqyjrF4HXUeFspIi9IGKf4j92pKGAdCYMVsbcV3kRF0N+R8LUd5PCsIGWoxDtBkCI0nKofdJQxT+LtZflvuc8Q3CjwWkq8KwUpHzkK/NmSsclCL0nseQdj5FRH5CNHSgtLiW80Of5HU9Hhlsga9bnBq3fEVltKfO5IaSTmGjjc4J0otcP7QsJUSQM8pEj5/wCuUuC2DWz8AAAAAElFTkSuQmCC"); -} diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/theme/blackboard.css b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/theme/blackboard.css deleted file mode 100644 index 24e83b7d28..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/theme/blackboard.css +++ /dev/null @@ -1,25 +0,0 @@ -/* Port of TextMate's Blackboard theme */ - -.cm-s-blackboard { background: #0C1021; color: #F8F8F8; } -.cm-s-blackboard .CodeMirror-selected { background: #253B76 !important; } -.cm-s-blackboard .CodeMirror-gutter { background: #0C1021; border-right: 0; } -.cm-s-blackboard .CodeMirror-gutter-text { color: #888; } -.cm-s-blackboard .CodeMirror-cursor { border-left: 1px solid #A7A7A7 !important; } - -.cm-s-blackboard .cm-keyword { color: #FBDE2D; } -.cm-s-blackboard .cm-atom { color: #D8FA3C; } -.cm-s-blackboard .cm-number { color: #D8FA3C; } -.cm-s-blackboard .cm-def { color: #8DA6CE; } -.cm-s-blackboard .cm-variable { color: #FF6400; } -.cm-s-blackboard .cm-operator { color: #FBDE2D;} -.cm-s-blackboard .cm-comment { color: #AEAEAE; } -.cm-s-blackboard .cm-string { color: #61CE3C; } -.cm-s-blackboard .cm-string-2 { color: #61CE3C; } -.cm-s-blackboard .cm-meta { color: #D8FA3C; } -.cm-s-blackboard .cm-error { background: #9D1E15; color: #F8F8F8; } -.cm-s-blackboard .cm-builtin { color: #8DA6CE; } -.cm-s-blackboard .cm-tag { color: #8DA6CE; } -.cm-s-blackboard .cm-attribute { color: #8DA6CE; } -.cm-s-blackboard .cm-header { color: #FF6400; } -.cm-s-blackboard .cm-hr { color: #AEAEAE; } -.cm-s-blackboard .cm-link { color: #8DA6CE; } diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/theme/cobalt.css b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/theme/cobalt.css deleted file mode 100644 index 9f8897cf64..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/theme/cobalt.css +++ /dev/null @@ -1,18 +0,0 @@ -.cm-s-cobalt { background: #002240; color: white; } -.cm-s-cobalt div.CodeMirror-selected { background: #b36539 !important; } -.cm-s-cobalt .CodeMirror-gutter { background: #002240; border-right: 1px solid #aaa; } -.cm-s-cobalt .CodeMirror-gutter-text { color: #d0d0d0; } -.cm-s-cobalt .CodeMirror-cursor { border-left: 1px solid white !important; } - -.cm-s-cobalt span.cm-comment { color: #08f; } -.cm-s-cobalt span.cm-atom { color: #845dc4; } -.cm-s-cobalt span.cm-number, .cm-s-cobalt span.cm-attribute { color: #ff80e1; } -.cm-s-cobalt span.cm-keyword { color: #ffee80; } -.cm-s-cobalt span.cm-string { color: #3ad900; } -.cm-s-cobalt span.cm-meta { color: #ff9d00; } -.cm-s-cobalt span.cm-variable-2, .cm-s-cobalt span.cm-tag { color: #9effff; } -.cm-s-cobalt span.cm-variable-3, .cm-s-cobalt span.cm-def { color: white; } -.cm-s-cobalt span.cm-error { color: #9d1e15; } -.cm-s-cobalt span.cm-bracket { color: #d8d8d8; } -.cm-s-cobalt span.cm-builtin, .cm-s-cobalt span.cm-special { color: #ff9e59; } -.cm-s-cobalt span.cm-link { color: #845dc4; } diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/theme/eclipse.css b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/theme/eclipse.css deleted file mode 100644 index ad226fb334..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/theme/eclipse.css +++ /dev/null @@ -1,25 +0,0 @@ -.cm-s-eclipse span.cm-meta {color: #FF1717;} -.cm-s-eclipse span.cm-keyword { line-height: 1em; font-weight: bold; color: #7F0055; } -.cm-s-eclipse span.cm-atom {color: #219;} -.cm-s-eclipse span.cm-number {color: #164;} -.cm-s-eclipse span.cm-def {color: #00f;} -.cm-s-eclipse span.cm-variable {color: black;} -.cm-s-eclipse span.cm-variable-2 {color: #0000C0;} -.cm-s-eclipse span.cm-variable-3 {color: #0000C0;} -.cm-s-eclipse span.cm-property {color: black;} -.cm-s-eclipse span.cm-operator {color: black;} -.cm-s-eclipse span.cm-comment {color: #3F7F5F;} -.cm-s-eclipse span.cm-string {color: #2A00FF;} -.cm-s-eclipse span.cm-string-2 {color: #f50;} -.cm-s-eclipse span.cm-error {color: #f00;} -.cm-s-eclipse span.cm-qualifier {color: #555;} -.cm-s-eclipse span.cm-builtin {color: #30a;} -.cm-s-eclipse span.cm-bracket {color: #cc7;} -.cm-s-eclipse span.cm-tag {color: #170;} -.cm-s-eclipse span.cm-attribute {color: #00c;} -.cm-s-eclipse span.cm-link {color: #219;} - -.cm-s-eclipse .CodeMirror-matchingbracket { - border:1px solid grey; - color:black !important;; -} diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/theme/elegant.css b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/theme/elegant.css deleted file mode 100644 index 770e342c88..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/theme/elegant.css +++ /dev/null @@ -1,10 +0,0 @@ -.cm-s-elegant span.cm-number, .cm-s-elegant span.cm-string, .cm-s-elegant span.cm-atom {color: #762;} -.cm-s-elegant span.cm-comment {color: #262; font-style: italic; line-height: 1em;} -.cm-s-elegant span.cm-meta {color: #555; font-style: italic; line-height: 1em;} -.cm-s-elegant span.cm-variable {color: black;} -.cm-s-elegant span.cm-variable-2 {color: #b11;} -.cm-s-elegant span.cm-qualifier {color: #555;} -.cm-s-elegant span.cm-keyword {color: #730;} -.cm-s-elegant span.cm-builtin {color: #30a;} -.cm-s-elegant span.cm-error {background-color: #fdd;} -.cm-s-elegant span.cm-link {color: #762;} diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/theme/erlang-dark.css b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/theme/erlang-dark.css deleted file mode 100644 index c8c5099639..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/theme/erlang-dark.css +++ /dev/null @@ -1,21 +0,0 @@ -.cm-s-erlang-dark { background: #002240; color: white; } -.cm-s-erlang-dark div.CodeMirror-selected { background: #b36539 !important; } -.cm-s-erlang-dark .CodeMirror-gutter { background: #002240; border-right: 1px solid #aaa; } -.cm-s-erlang-dark .CodeMirror-gutter-text { color: #d0d0d0; } -.cm-s-erlang-dark .CodeMirror-cursor { border-left: 1px solid white !important; } - -.cm-s-erlang-dark span.cm-atom { color: #845dc4; } -.cm-s-erlang-dark span.cm-attribute { color: #ff80e1; } -.cm-s-erlang-dark span.cm-bracket { color: #ff9d00; } -.cm-s-erlang-dark span.cm-builtin { color: #eeaaaa; } -.cm-s-erlang-dark span.cm-comment { color: #7777ff; } -.cm-s-erlang-dark span.cm-def { color: #ee77aa; } -.cm-s-erlang-dark span.cm-error { color: #9d1e15; } -.cm-s-erlang-dark span.cm-keyword { color: #ffee80; } -.cm-s-erlang-dark span.cm-meta { color: #50fefe; } -.cm-s-erlang-dark span.cm-number { color: #ffd0d0; } -.cm-s-erlang-dark span.cm-operator { color: #dd1111; } -.cm-s-erlang-dark span.cm-string { color: #3ad900; } -.cm-s-erlang-dark span.cm-tag { color: #9effff; } -.cm-s-erlang-dark span.cm-variable { color: #50fe50; } -.cm-s-erlang-dark span.cm-variable-2 { color: #ee00ee; } diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/theme/lesser-dark.css b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/theme/lesser-dark.css deleted file mode 100644 index 241efe001c..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/theme/lesser-dark.css +++ /dev/null @@ -1,44 +0,0 @@ -/* -http://lesscss.org/ dark theme -Ported to CodeMirror by Peter Kroon -*/ -.cm-s-lesser-dark { - line-height: 1.3em; -} -.cm-s-lesser-dark { - font-family: 'Bitstream Vera Sans Mono', 'DejaVu Sans Mono', 'Monaco', Courier, monospace !important; -} - -.cm-s-lesser-dark { background: #262626; color: #EBEFE7; text-shadow: 0 -1px 1px #262626; } -.cm-s-lesser-dark div.CodeMirror-selected {background: #45443B !important;} /* 33322B*/ -.cm-s-lesser-dark .CodeMirror-cursor { border-left: 1px solid white !important; } -.cm-s-lesser-dark .CodeMirror-lines { margin-left:3px; margin-right:3px; }/*editable code holder*/ - -div.CodeMirror span.CodeMirror-matchingbracket { color: #7EFC7E; }/*65FC65*/ - -.cm-s-lesser-dark .CodeMirror-gutter { background: #262626; border-right:1px solid #aaa; padding-right:3px; min-width:2.5em; } -.cm-s-lesser-dark .CodeMirror-gutter-text { color: #777; } - -.cm-s-lesser-dark span.cm-keyword { color: #599eff; } -.cm-s-lesser-dark span.cm-atom { color: #C2B470; } -.cm-s-lesser-dark span.cm-number { color: #B35E4D; } -.cm-s-lesser-dark span.cm-def {color: white;} -.cm-s-lesser-dark span.cm-variable { color:#D9BF8C; } -.cm-s-lesser-dark span.cm-variable-2 { color: #669199; } -.cm-s-lesser-dark span.cm-variable-3 { color: white; } -.cm-s-lesser-dark span.cm-property {color: #92A75C;} -.cm-s-lesser-dark span.cm-operator {color: #92A75C;} -.cm-s-lesser-dark span.cm-comment { color: #666; } -.cm-s-lesser-dark span.cm-string { color: #BCD279; } -.cm-s-lesser-dark span.cm-string-2 {color: #f50;} -.cm-s-lesser-dark span.cm-meta { color: #738C73; } -.cm-s-lesser-dark span.cm-error { color: #9d1e15; } -.cm-s-lesser-dark span.cm-qualifier {color: #555;} -.cm-s-lesser-dark span.cm-builtin { color: #ff9e59; } -.cm-s-lesser-dark span.cm-bracket { color: #EBEFE7; } -.cm-s-lesser-dark span.cm-tag { color: #669199; } -.cm-s-lesser-dark span.cm-attribute {color: #00c;} -.cm-s-lesser-dark span.cm-header {color: #a0a;} -.cm-s-lesser-dark span.cm-quote {color: #090;} -.cm-s-lesser-dark span.cm-hr {color: #999;} -.cm-s-lesser-dark span.cm-link {color: #00c;} diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/theme/monokai.css b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/theme/monokai.css deleted file mode 100644 index decc176efc..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/theme/monokai.css +++ /dev/null @@ -1,28 +0,0 @@ -/* Based on Sublime Text's Monokai theme */ - -.cm-s-monokai {background: #272822; color: #f8f8f2;} -.cm-s-monokai div.CodeMirror-selected {background: #49483E !important;} -.cm-s-monokai .CodeMirror-gutter {background: #272822; border-right: 0px;} -.cm-s-monokai .CodeMirror-gutter-text {color: #d0d0d0;} -.cm-s-monokai .CodeMirror-cursor {border-left: 1px solid #f8f8f0 !important;} - -.cm-s-monokai span.cm-comment {color: #75715e;} -.cm-s-monokai span.cm-atom {color: #ae81ff;} -.cm-s-monokai span.cm-number {color: #ae81ff;} - -.cm-s-monokai span.cm-property, .cm-s-monokai span.cm-attribute {color: #a6e22e;} -.cm-s-monokai span.cm-keyword {color: #f92672;} -.cm-s-monokai span.cm-string {color: #e6db74;} - -.cm-s-monokai span.cm-variable {color: #a6e22e;} -.cm-s-monokai span.cm-variable-2 {color: #9effff;} -.cm-s-monokai span.cm-def {color: #fd971f;} -.cm-s-monokai span.cm-error {background: #f92672; color: #f8f8f0;} -.cm-s-monokai span.cm-bracket {color: #f8f8f2;} -.cm-s-monokai span.cm-tag {color: #f92672;} -.cm-s-monokai span.cm-link {color: #ae81ff;} - -.cm-s-monokai .CodeMirror-matchingbracket { - text-decoration: underline; - color: white !important; -} diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/theme/neat.css b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/theme/neat.css deleted file mode 100644 index a71ab432b8..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/theme/neat.css +++ /dev/null @@ -1,9 +0,0 @@ -.cm-s-neat span.cm-comment { color: #a86; } -.cm-s-neat span.cm-keyword { line-height: 1em; font-weight: bold; color: blue; } -.cm-s-neat span.cm-string { color: #a22; } -.cm-s-neat span.cm-builtin { line-height: 1em; font-weight: bold; color: #077; } -.cm-s-neat span.cm-special { line-height: 1em; font-weight: bold; color: #0aa; } -.cm-s-neat span.cm-variable { color: black; } -.cm-s-neat span.cm-number, .cm-s-neat span.cm-atom { color: #3a3; } -.cm-s-neat span.cm-meta {color: #555;} -.cm-s-neat span.cm-link { color: #3a3; } diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/theme/night.css b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/theme/night.css deleted file mode 100644 index 0800b7ae6c..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/theme/night.css +++ /dev/null @@ -1,21 +0,0 @@ -/* Loosely based on the Midnight Textmate theme */ - -.cm-s-night { background: #0a001f; color: #f8f8f8; } -.cm-s-night div.CodeMirror-selected { background: #447 !important; } -.cm-s-night .CodeMirror-gutter { background: #0a001f; border-right: 1px solid #aaa; } -.cm-s-night .CodeMirror-gutter-text { color: #f8f8f8; } -.cm-s-night .CodeMirror-cursor { border-left: 1px solid white !important; } - -.cm-s-night span.cm-comment { color: #6900a1; } -.cm-s-night span.cm-atom { color: #845dc4; } -.cm-s-night span.cm-number, .cm-s-night span.cm-attribute { color: #ffd500; } -.cm-s-night span.cm-keyword { color: #599eff; } -.cm-s-night span.cm-string { color: #37f14a; } -.cm-s-night span.cm-meta { color: #7678e2; } -.cm-s-night span.cm-variable-2, .cm-s-night span.cm-tag { color: #99b2ff; } -.cm-s-night span.cm-variable-3, .cm-s-night span.cm-def { color: white; } -.cm-s-night span.cm-error { color: #9d1e15; } -.cm-s-night span.cm-bracket { color: #8da6ce; } -.cm-s-night span.cm-comment { color: #6900a1; } -.cm-s-night span.cm-builtin, .cm-s-night span.cm-special { color: #ff9e59; } -.cm-s-night span.cm-link { color: #845dc4; } diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/theme/rubyblue.css b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/theme/rubyblue.css deleted file mode 100644 index bf4e66b0b8..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/theme/rubyblue.css +++ /dev/null @@ -1,21 +0,0 @@ -.cm-s-rubyblue { font:13px/1.4em Trebuchet, Verdana, sans-serif; } /* - customized editor font - */ - -.cm-s-rubyblue { background: #112435; color: white; } -.cm-s-rubyblue div.CodeMirror-selected { background: #38566F !important; } -.cm-s-rubyblue .CodeMirror-gutter { background: #1F4661; border-right: 7px solid #3E7087; min-width:2.5em; } -.cm-s-rubyblue .CodeMirror-gutter-text { color: white; } -.cm-s-rubyblue .CodeMirror-cursor { border-left: 1px solid white !important; } - -.cm-s-rubyblue span.cm-comment { color: #999; font-style:italic; line-height: 1em; } -.cm-s-rubyblue span.cm-atom { color: #F4C20B; } -.cm-s-rubyblue span.cm-number, .cm-s-rubyblue span.cm-attribute { color: #82C6E0; } -.cm-s-rubyblue span.cm-keyword { color: #F0F; } -.cm-s-rubyblue span.cm-string { color: #F08047; } -.cm-s-rubyblue span.cm-meta { color: #F0F; } -.cm-s-rubyblue span.cm-variable-2, .cm-s-rubyblue span.cm-tag { color: #7BD827; } -.cm-s-rubyblue span.cm-variable-3, .cm-s-rubyblue span.cm-def { color: white; } -.cm-s-rubyblue span.cm-error { color: #AF2018; } -.cm-s-rubyblue span.cm-bracket { color: #F0F; } -.cm-s-rubyblue span.cm-link { color: #F4C20B; } -.cm-s-rubyblue span.CodeMirror-matchingbracket { color:#F0F !important; } -.cm-s-rubyblue span.cm-builtin, .cm-s-rubyblue span.cm-special { color: #FF9D00; } diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/theme/vibrant-ink.css b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/theme/vibrant-ink.css deleted file mode 100644 index 3f82e79a57..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/theme/vibrant-ink.css +++ /dev/null @@ -1,27 +0,0 @@ -/* Taken from the popular Visual Studio Vibrant Ink Schema */ - -.cm-s-vibrant-ink { background: black; color: white; } -.cm-s-vibrant-ink .CodeMirror-selected { background: #35493c !important; } - -.cm-s-vibrant-ink .CodeMirror-gutter { background: #002240; border-right: 1px solid #aaa; } -.cm-s-vibrant-ink .CodeMirror-gutter-text { color: #d0d0d0; } -.cm-s-vibrant-ink .CodeMirror-cursor { border-left: 1px solid white !important; } - -.cm-s-vibrant-ink .cm-keyword { color: #CC7832; } -.cm-s-vibrant-ink .cm-atom { color: #FC0; } -.cm-s-vibrant-ink .cm-number { color: #FFEE98; } -.cm-s-vibrant-ink .cm-def { color: #8DA6CE; } -.cm-s-vibrant-ink span.cm-variable-2, .cm-s-cobalt span.cm-tag { color: #FFC66D } -.cm-s-vibrant-ink span.cm-variable-3, .cm-s-cobalt span.cm-def { color: #FFC66D } -.cm-s-vibrant-ink .cm-operator { color: #888; } -.cm-s-vibrant-ink .cm-comment { color: gray; font-weight: bold; } -.cm-s-vibrant-ink .cm-string { color: #A5C25C } -.cm-s-vibrant-ink .cm-string-2 { color: red } -.cm-s-vibrant-ink .cm-meta { color: #D8FA3C; } -.cm-s-vibrant-ink .cm-error { border-bottom: 1px solid red; } -.cm-s-vibrant-ink .cm-builtin { color: #8DA6CE; } -.cm-s-vibrant-ink .cm-tag { color: #8DA6CE; } -.cm-s-vibrant-ink .cm-attribute { color: #8DA6CE; } -.cm-s-vibrant-ink .cm-header { color: #FF6400; } -.cm-s-vibrant-ink .cm-hr { color: #AEAEAE; } -.cm-s-vibrant-ink .cm-link { color: blue; } diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/theme/xq-dark.css b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/theme/xq-dark.css deleted file mode 100644 index cacf7809be..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/theme/xq-dark.css +++ /dev/null @@ -1,46 +0,0 @@ -/* -Copyright (C) 2011 by MarkLogic Corporation -Author: Mike Brevoort - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ -.cm-s-xq-dark { background: #0a001f; color: #f8f8f8; } -.cm-s-xq-dark span.CodeMirror-selected { background: #a8f !important; } -.cm-s-xq-dark .CodeMirror-gutter { background: #0a001f; border-right: 1px solid #aaa; } -.cm-s-xq-dark .CodeMirror-gutter-text { color: #f8f8f8; } -.cm-s-xq-dark .CodeMirror-cursor { border-left: 1px solid white !important; } - -.cm-s-xq-dark span.cm-keyword {color: #FFBD40;} -.cm-s-xq-dark span.cm-atom {color: #6C8CD5;} -.cm-s-xq-dark span.cm-number {color: #164;} -.cm-s-xq-dark span.cm-def {color: #FFF; text-decoration:underline;} -.cm-s-xq-dark span.cm-variable {color: #FFF;} -.cm-s-xq-dark span.cm-variable-2 {color: #EEE;} -.cm-s-xq-dark span.cm-variable-3 {color: #DDD;} -.cm-s-xq-dark span.cm-property {} -.cm-s-xq-dark span.cm-operator {} -.cm-s-xq-dark span.cm-comment {color: gray;} -.cm-s-xq-dark span.cm-string {color: #9FEE00;} -.cm-s-xq-dark span.cm-meta {color: yellow;} -.cm-s-xq-dark span.cm-error {color: #f00;} -.cm-s-xq-dark span.cm-qualifier {color: #FFF700;} -.cm-s-xq-dark span.cm-builtin {color: #30a;} -.cm-s-xq-dark span.cm-bracket {color: #cc7;} -.cm-s-xq-dark span.cm-tag {color: #FFBD40;} -.cm-s-xq-dark span.cm-attribute {color: #FFF700;} diff --git a/src/Umbraco.Web.UI/umbraco_client/Dialogs/AssignDomain2.js b/src/Umbraco.Web.UI/umbraco_client/Dialogs/AssignDomain2.js index cfd33541cb..b49e72fcc7 100644 --- a/src/Umbraco.Web.UI/umbraco_client/Dialogs/AssignDomain2.js +++ b/src/Umbraco.Web.UI/umbraco_client/Dialogs/AssignDomain2.js @@ -63,9 +63,17 @@ return ret; }, self._opts.invalidDomain); + function getDuplicateMessage(val, el) { + var other = $(el).nextAll('input').val(); + var msg = self._opts.duplicateDomain + if (other != "" && other != "!!!") + msg = msg + ' (' + other + ')'; + return msg; + } + $.validator.addMethod("duplicate", function (value, element, param) { - return $(element).nextAll('input').val() == 0 && !self._isRepeated($(element)); - }, self._opts.duplicateDomain); + return $(element).nextAll('input').val() == "" && !self._isRepeated($(element)); + }, getDuplicateMessage); $.validator.addClassRules({ domain: { domain: true }, @@ -80,7 +88,7 @@ $('form input.domain').live('focus', function(event) { if (event.type != 'focusin') return; - $(this).nextAll('input').val(0); + $(this).nextAll('input').val(""); }); // force validation *now* @@ -105,14 +113,14 @@ } else { var inputs = $('form input.domain'); - inputs.each(function() { $(this).nextAll('input').val(0); }); + inputs.each(function() { $(this).nextAll('input').val(""); }); for (var i = 0; i < json.Domains.length; i++) { var d = json.Domains[i]; - if (d.Duplicate == 1) + if (d.Duplicate) inputs.each(function() { var input = $(this); if (input.val() == d.Name) - input.nextAll('input').val(1); + input.nextAll('input').val(d.Other ? d.Other : "!!!"); }); } $('form').valid(); diff --git a/src/Umbraco.Web.UI/umbraco_client/Dialogs/SortDialog.js b/src/Umbraco.Web.UI/umbraco_client/Dialogs/SortDialog.js index a7bb1d9f29..eb62afd805 100644 --- a/src/Umbraco.Web.UI/umbraco_client/Dialogs/SortDialog.js +++ b/src/Umbraco.Web.UI/umbraco_client/Dialogs/SortDialog.js @@ -59,7 +59,7 @@ $.ajax({ type: "POST", url: self._opts.serviceUrl, - data: '{ "ParentId": ' + parseInt(self._opts.currentId) + ', "SortOrder": "' + sortOrder + '"}', + data: '{ "ParentId": "' + self._opts.currentId + '", "SortOrder": "' + sortOrder + '"}', contentType: "application/json; charset=utf-8", dataType: "json", success: function(msg) { diff --git a/src/Umbraco.Web.UI/umbraco_client/Editors/EditContentType.js b/src/Umbraco.Web.UI/umbraco_client/Editors/EditContentType.js deleted file mode 100644 index 18f47196b8..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/Editors/EditContentType.js +++ /dev/null @@ -1,108 +0,0 @@ -Umbraco.Sys.registerNamespace("Umbraco.Editors"); - -(function ($) { - - var _model = {}; - var _opts = null; - - //updates the UI elements - function updateElements() { - _opts.configPanel.find("strong").html(_model.listViewName); - if (_model.isSystem) { - _opts.createListViewButton.show(); - _opts.removeListViewButton.hide(); - _opts.configPanel.find("em").show(); - } - else { - _opts.createListViewButton.hide(); - _opts.removeListViewButton.show(); - _opts.configPanel.find("em").hide(); - } - } - - function populateData() { - //get init data - - $.get(_opts.contentTypeServiceBaseUrl + "GetAssignedListViewDataType?contentTypeId=" + _opts.contentTypeId, function (result) { - _model.isSystem = result.isSystem; - _model.listViewName = result.name; - _model.listViewId = result.id; - updateElements(); - }); - } - - $.ajaxSetup({ - beforeSend: function (xhr) { - xhr.setRequestHeader("X-XSRF-TOKEN", $.cookie("XSRF-TOKEN")); - }, - contentType: 'application/json;charset=utf-8', - dataType: "json", - dataFilter: function (data, dataType) { - if ((typeof data) === "string") { - //trim the csrf bits off - data = data.replace(/^\)\]\}\'\,\n/, ""); - } - return data; - } - }); - - Umbraco.Editors.EditContentType = base2.Base.extend({ - - // Constructor - constructor: function(opts) { - // Merge options with default - _opts = $.extend({ - // Default options go here - }, opts); - }, - - init: function () { - //wire up handlers - - _opts.configPanel.find("a").click(function() { - UmbClientMgr.contentFrame('#/developer/datatype/edit/' + _model.listViewId); - }); - - _opts.isContainerChk.on("change", function () { - if ($(this).is(":checked")) { - _opts.configPanel.slideDown(); - } - else { - _opts.configPanel.slideUp(); - } - }); - - _opts.createListViewButton.click(function (event) { - event.preventDefault(); - - var data = { - parentId: -1, - id: 0, - preValues: [], - action: "SaveNew", - name: "List View - " + _opts.contentTypeAlias, - selectedEditor: "Umbraco.ListView" - }; - - $.post(_opts.dataTypeServiceBaseUrl + "PostSave", JSON.stringify(data), function (result) { - _model.isSystem = result.isSystem; - _model.listViewName = result.name; - _model.listViewId = result.id; - updateElements(); - }); - }); - - _opts.removeListViewButton.click(function (event) { - event.preventDefault(); - - $.post(_opts.dataTypeServiceBaseUrl + "DeleteById?id=" + _model.listViewId, function (result) { - //re-get the data - populateData(); - }); - }); - - populateData(); - - } - }); -})(jQuery); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/Editors/EditScript.js b/src/Umbraco.Web.UI/umbraco_client/Editors/EditScript.js index 7c66dc9a1d..c1506b9ffd 100644 --- a/src/Umbraco.Web.UI/umbraco_client/Editors/EditScript.js +++ b/src/Umbraco.Web.UI/umbraco_client/Editors/EditScript.js @@ -30,63 +30,87 @@ doSubmit: function () { var self = this; - var fileName = this._opts.nameTxtBox.val(); - var codeVal = this._opts.editorSourceElement.val(); + var filename = this._opts.nameTxtBox.val(); + var codeval = this._opts.editorSourceElement.val(); //if CodeMirror is not defined, then the code editor is disabled. if (typeof (CodeMirror) != "undefined") { - codeVal = UmbEditor.GetCode(); + codeval = UmbEditor.GetCode(); } - umbraco.presentation.webservices.codeEditorSave.SaveScript( - fileName, self._opts.originalFileName, codeVal, - function (t) { self.submitSucces(t); }, - function (t) { self.submitFailure(t); }); + this.save( + filename, + self._opts.originalFileName, + codeval); }, - submitSucces: function(t) { + save: function (filename, oldName, contents) { + var self = this; - if (t != 'true') { - top.UmbSpeechBubble.ShowMessage('error', unescape(this._opts.text.fileErrorHeader), unescape(this._opts.text.fileErrorText)); + $.post(self._opts.restServiceLocation + "SaveScript", + JSON.stringify({ + filename: filename, + oldName: oldName, + contents: contents + }), + function (e) { + if (e.success) { + self.submitSuccess(e); + } else { + self.submitFailure(e.message, e.header); + } + }); + }, + + submitSuccess: function (args) { + var msg = args.message; + var header = args.header; + var path = this._opts.treeSyncPath; + var pathChanged = false; + if (args.path) { + if (path != args.path) { + pathChanged = true; + } + path = args.path; + } + if (args.contents) { + UmbEditor.SetCode(args.contents); } - var newFilePath = this._opts.nameTxtBox.val(); - - //if the filename changes, we need to redirect since the file name is used in the url - if (this._opts.originalFileName != newFilePath) { - var newLocation = window.location.pathname + "?" + "&file=" + newFilePath; + UmbClientMgr.mainTree().setActiveTreeType("scripts"); + if (pathChanged) { + // no! file is used in url so we need to redirect + //UmbClientMgr.mainTree().moveNode(this._opts.originalFileName, path); + //this._opts.treeSyncPath = args.path; + //this._opts.lttPathElement.prop("href", args.url).html(args.url); - UmbClientMgr.contentFrame(newLocation); + var qs = window.location.search; + if (qs.startsWith("?")) qs = qs.substring("?".length); + var qp1 = qs.split("&"); + var qp2 = []; + for (var i = 0; i < qp1.length; i++) + if (!qp1[i].startsWith("file=")) + qp2.push(qp1[i]); - //we need to do this after we navigate otherwise the navigation will wait unti lthe message timeout is done! - top.UmbSpeechBubble.ShowMessage('save', unescape(this._opts.text.fileSavedHeader), unescape(this._opts.text.fileSavedText)); + var location = window.location.pathname + "?" + qp2.join("&") + "&file=" + args.name; + UmbClientMgr.contentFrame(location); + + // need to do it after we navigate otherwise the navigation waits until the message timeout is done + top.UmbSpeechBubble.ShowMessage("save", header, msg); } else { - - top.UmbSpeechBubble.ShowMessage('save', unescape(this._opts.text.fileSavedHeader), unescape(this._opts.text.fileSavedText)); - UmbClientMgr.mainTree().setActiveTreeType('scripts'); - - //we need to create a list of ids for each folder/file. Each folder/file's id is it's full path so we need to build each one. - var paths = []; - var parts = this._opts.originalFileName.split('/'); - for (var i = 0;i < parts.length;i++) { - if (paths.length > 0) { - paths.push(paths[i - 1] + "/" + parts[i]); - } - else { - paths.push(parts[i]); - } - } - - //we need to pass in the newId parameter so it knows which node to resync after retreival from the server - UmbClientMgr.mainTree().syncTree("-1,init," + paths.join(','), true, null, newFilePath); - //set the original file path to the new one - this._opts.originalFileName = newFilePath; + top.UmbSpeechBubble.ShowMessage("save", header, msg); + this._opts.lttPathElement.prop("href", args.url).html(args.url); + this._opts.originalFileName = args.name; + this._opts.treeSyncPath = args.path; + UmbClientMgr.mainTree().syncTree(path, true); } - + + //this._opts.lttPathElement.prop("href", args.url).html(args.url); + //this._opts.originalFileName = args.name; }, - submitFailure: function(t) { - top.UmbSpeechBubble.ShowMessage('error', unescape(this._opts.text.fileErrorHeader), unescape(this._opts.text.fileErrorText)); + submitFailure: function(err, header) { + top.UmbSpeechBubble.ShowMessage('error', header, err); } }); })(jQuery); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/Editors/EditStyleSheet.js b/src/Umbraco.Web.UI/umbraco_client/Editors/EditStyleSheet.js index 7d0ab93bd8..990ed3edee 100644 --- a/src/Umbraco.Web.UI/umbraco_client/Editors/EditStyleSheet.js +++ b/src/Umbraco.Web.UI/umbraco_client/Editors/EditStyleSheet.js @@ -30,37 +30,80 @@ doSubmit: function() { var self = this; - var fileName = this._opts.nameTxtBox.val(); - var codeVal = this._opts.editorSourceElement.val(); + var filename = this._opts.nameTxtBox.val(); + var codeval = this._opts.editorSourceElement.val(); //if CodeMirror is not defined, then the code editor is disabled. if (typeof(CodeMirror) != "undefined") { - codeVal = UmbEditor.GetCode(); + codeval = UmbEditor.GetCode(); } - umbraco.presentation.webservices.codeEditorSave.SaveCss( - fileName, self._opts.originalFileName, codeVal, 0, - function (t) { self.submitSucces(t, fileName); }, - function(t) { self.submitFailure(t); }); - + this.save( + filename, + self._opts.originalFileName, + codeval); }, - submitSucces: function (t, fileName) { - if (t != 'true') { - top.UmbSpeechBubble.ShowMessage('error', unescape(this._opts.text.cssErrorHeader), unescape(this._opts.text.cssErrorText)); + save: function (filename, oldName, contents) { + var self = this; + + $.post(self._opts.restServiceLocation + "SaveStylesheet", + JSON.stringify({ + filename: filename, + oldName: oldName, + contents: contents + }), + function (e) { + if (e.success) { + self.submitSuccess(e); + } else { + self.submitFailure(e.message, e.header); + } + }); + }, + + submitSuccess: function (args) { + var msg = args.message; + var header = args.header; + var path = this._opts.treeSyncPath; + var pathChanged = false; + if (args.path) { + if (path != args.path) { + pathChanged = true; + } + path = args.path; + } + if (args.contents) { + UmbEditor.SetCode(args.contents); + } + + UmbClientMgr.mainTree().setActiveTreeType("stylesheets"); + if (pathChanged) { + // file is used in url so we need to redirect + var qs = window.location.search; + if (qs.startsWith("?")) qs = qs.substring("?".length); + var qp1 = qs.split("&"); + var qp2 = []; + for (var i = 0; i < qp1.length; i++) + if (!qp1[i].startsWith("id=")) + qp2.push(qp1[i]); + + var location = window.location.pathname + "?" + qp2.join("&") + "&id=" + args.name; + UmbClientMgr.contentFrame(location); + + // need to do it after we navigate otherwise the navigation waits until the message timeout is done + top.UmbSpeechBubble.ShowMessage("save", header, msg); } else { - top.UmbSpeechBubble.ShowMessage('save', unescape(this._opts.text.cssSavedHeader), unescape(this._opts.text.cssSavedText)); + top.UmbSpeechBubble.ShowMessage("save", header, msg); + this._opts.lttPathElement.prop("href", args.url).html(args.url); + this._opts.originalFileName = args.name; + this._opts.treeSyncPath = args.path; + UmbClientMgr.mainTree().syncTree(path, true); } - - UmbClientMgr.mainTree().setActiveTreeType('stylesheets'); - UmbClientMgr.mainTree().syncTree("-1,init," + fileName, true); - - //update the originalFileName prop - this._opts.originalFileName = this._opts.nameTxtBox.val(); }, - submitFailure: function(t) { - top.UmbSpeechBubble.ShowMessage('error', unescape(this._opts.text.cssErrorHeader), unescape(this._opts.text.cssErrorText)); + submitFailure: function(err, header) { + top.UmbSpeechBubble.ShowMessage('error', header, err); } }); })(jQuery); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/Editors/EditView.js b/src/Umbraco.Web.UI/umbraco_client/Editors/EditView.js index 554d0bafc8..509de8ed28 100644 --- a/src/Umbraco.Web.UI/umbraco_client/Editors/EditView.js +++ b/src/Umbraco.Web.UI/umbraco_client/Editors/EditView.js @@ -8,24 +8,6 @@ //private methods/variables _opts: null, - _updateNewFileProperties: function(filePath) { - /// Updates the current treeSyncPath and original file name to have the new file name - - //update the originalFileName prop - this._opts.originalFileName = filePath; - - //re-create the new path - var subPath = this._opts.treeSyncPath.split(","); - //remove the last element - subPath.pop(); - //add the new element - var parts = filePath.split("/"); - //remove the first bit which will either be "Partials" or "MacroPartials" - parts.shift(); - subPath.push(parts.join("/")); - this._opts.treeSyncPath = subPath.join(); - }, - // Constructor constructor: function (opts) { // Merge options with default @@ -54,6 +36,10 @@ UmbEditor.Insert("@Umbraco.RenderMacro(\"" + alias + "\")", "", this._opts.codeEditorElementId); }, + + insertRenderBody: function() { + UmbEditor.Insert("@RenderBody()", "", this._opts.codeEditorElementId); + }, openMacroModal: function (alias) { /// callback used to display the modal dialog to insert a macro with parameters @@ -72,6 +58,37 @@ }); }, + openSnippetModal: function (type) { + /// callback used to display the modal dialog to insert a macro with parameters + + var self = this; + + UmbClientMgr.openAngularModalWindow({ + template: "views/common/dialogs/template/snippet.html", + callback: function (data) { + + var code = ""; + + if (type === 'section') { + code = "\n@section " + data.name + "{\n"; + code += "\n" + + "}\n"; + } + + if (type === 'rendersection') { + if (data.required) { + code = "\n@RenderSection(\"" + data.name + "\", true);\n"; + } else { + code = "\n@RenderSection(\"" + data.name + "\" false);\n"; + } + } + + UmbEditor.Insert(code, '', self._opts.codeEditorElementId); + }, + type: type + }); + }, + openQueryModal: function () { /// callback used to display the modal dialog to insert a macro with parameters @@ -207,8 +224,12 @@ top.UmbSpeechBubble.ShowMessage('save', header, msg); - //then we need to update our current tree sync path to represent the new one - this._updateNewFileProperties(newFilePath); + if (args && args.name) { + this._opts.originalFileName = args.name; + } + if (args && args.path) { + this._opts.treeSyncPath = args.path; + } UmbClientMgr.mainTree().syncTree(path, true, null, newFilePath.split("/")[1]); } diff --git a/src/Umbraco.Web.UI/umbraco_client/GenericProperty/genericProperty.js b/src/Umbraco.Web.UI/umbraco_client/GenericProperty/genericProperty.js deleted file mode 100644 index 4ca59e82de..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/GenericProperty/genericProperty.js +++ /dev/null @@ -1,53 +0,0 @@ -var activeDragId = ""; -function expandCollapse(theId) { - - var edit = document.getElementById("edit" + theId); - - if (edit.style.display == 'none') { - edit.style.display = 'block'; - document.getElementById("desc" + theId).style.display = 'none'; - } - else { - edit.style.display = 'none'; - document.getElementById("desc" + theId).style.display = 'block'; - } -} -function duplicatePropertyNameAsSafeAlias(propertySelector) { - $(propertySelector).each(function() { - var prop = $(this); - var inputName = prop.find('.prop-name'); - var inputAlias = prop.find('.prop-alias'); - inputName.on('input blur', function (event) { - getSafeAlias(inputAlias, inputName.val(), false, function (alias) { - if (!inputAlias.data('dirty')) - inputAlias.val(alias); - }); - }); - inputAlias.on('input', function(event) { - inputName.off('input blur'); - }); - }); -} - -function checkAlias(aliasSelector) { - $(aliasSelector).on('input', function (event) { - var input = $(this); - input.data('dirty', true); - var value = input.val(); - validateSafeAlias(input, value, false, function (isSafe) { - input.toggleClass('highlight-error', !isSafe); - }); - }).on('blur', function(event) { - var input = $(this); - if (!input.data('dirty')) return; - input.removeData('dirty'); - var value = input.val(); - getSafeAlias(input, value, true, function (alias) { - if (value.toLowerCase() != alias.toLowerCase()) - input.val(alias); - input.removeClass('highlight-error'); - }); - }); -} - -// validateSafeAlias and getSafeAlias are defined by UmbracoCasingRules.aspx diff --git a/src/Umbraco.Web.UI/umbraco_client/GenericProperty/genericproperty.css b/src/Umbraco.Web.UI/umbraco_client/GenericProperty/genericproperty.css deleted file mode 100644 index 5183a9bccd..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/GenericProperty/genericproperty.css +++ /dev/null @@ -1,107 +0,0 @@ - -.genericPropertyForm { - -} -.genericPropertyForm h2 { - font-size: 16px; - line-height: 20px; - margin-bottom: 2px; -} - -.genericPropertyList .header{ - padding: 4px; -} -.genericPropertyList .umb-pane{ - margin: 10px; -} - -.genericPropertyList { - margin: 0px 0px 30px 0px; - list-style: none; -} - -.genericPropertyList .delete-button, .genericPropertyList .toggle-button{ - float: right; - margin-left: 3px; - border: none; - background: none; - text-decoration: none; -} - -.genericPropertyList .delete-button i { - background: none; - border: none; -} - -.handle{ - color: #ccc; - font-size: 12px; -} - -.addNewProperty .handle { - display: none; -} - -.genericPropertyList li { - display: block; - position: relative; - border-bottom: 1px solid #eee; - margin-bottom: 3px; - cursor: move; -} - -.genericPropertyList li .delete-button i { - color: #b94a48; -} - -.addNewProperty li { - cursor: default; -} - -.genericPropertyList li table { - display: block; - margin-top: 10px; -} - -.genericPropertyList li table th { - width: 140px; - padding: 5px; -} - -.propertyFormInput { - width: 300px; - z-index: 9999; -} - -.propertyForm SELECT { - width: 300px; -} - -.propertyForm h3 a { - color: #000; - text-decoration: none; - font-size: 14px; - font-weight: normal; -} - - - -.genericPropertyList li img { - background: red; - z-index: 9999; - position: relative; - border-right: medium none; - border-top: medium none; - border-left: medium none; - margin-right: 5px; - border-bottom: medium none; -} -.genericPropertyList li h3 input { - position: relative; -} - -.aliasValidationError -{ - background-color: #ff9999; - border: 1px solid: red; -} \ No newline at end of file diff --git a/src/Umbraco.Web.UI/web.Template.Debug.config b/src/Umbraco.Web.UI/web.Template.Debug.config index 7be1e75201..c18f1e1a8e 100644 --- a/src/Umbraco.Web.UI/web.Template.Debug.config +++ b/src/Umbraco.Web.UI/web.Template.Debug.config @@ -58,8 +58,10 @@ + + - + @@ -93,6 +95,7 @@ + @@ -122,35 +125,42 @@ xdt:Locator="Condition(_defaultNamespace:assemblyIdentity[@name='System.Web.Helpers']])" /> - + - + - - + + + + + + + + - + - + diff --git a/src/Umbraco.Web.UI/web.Template.Release.config b/src/Umbraco.Web.UI/web.Template.Release.config index 0745ffe7ae..3ddea3b6a6 100644 --- a/src/Umbraco.Web.UI/web.Template.Release.config +++ b/src/Umbraco.Web.UI/web.Template.Release.config @@ -1,7 +1,7 @@ - - - - - - - + + + + + + - - - - - - - + + + + + + + \ No newline at end of file diff --git a/src/Umbraco.Web.UI/web.Template.config b/src/Umbraco.Web.UI/web.Template.config index 73d42df1a6..1904c47d7e 100644 --- a/src/Umbraco.Web.UI/web.Template.config +++ b/src/Umbraco.Web.UI/web.Template.config @@ -77,7 +77,7 @@ - + @@ -141,6 +144,11 @@ + + + + + @@ -183,6 +191,9 @@ + + + @@ -205,6 +216,13 @@ + + + + + + + @@ -224,21 +242,21 @@ - + - + - + - + @@ -252,14 +270,21 @@ - - + + + + + + - + + + + @@ -269,8 +294,8 @@ - - + + diff --git a/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs b/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs index 94d83cdcab..730efd380b 100644 --- a/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs +++ b/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs @@ -1,10 +1,14 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Web; +using Newtonsoft.Json; +using umbraco.interfaces; using Umbraco.Core; -using Umbraco.Core.Logging; +using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Sync; using Umbraco.Web.Routing; +using Umbraco.Core.Logging; namespace Umbraco.Web { @@ -14,47 +18,43 @@ namespace Umbraco.Web /// /// This binds to appropriate umbraco events in order to trigger the Boot(), Sync() & FlushBatch() calls /// - public class BatchedDatabaseServerMessenger : Core.Sync.BatchedDatabaseServerMessenger + public class BatchedDatabaseServerMessenger : DatabaseServerMessenger { - public BatchedDatabaseServerMessenger(UmbracoApplicationBase appBase, ApplicationContext appContext, bool enableDistCalls, DatabaseServerMessengerOptions options) + public BatchedDatabaseServerMessenger(ApplicationContext appContext, bool enableDistCalls, DatabaseServerMessengerOptions options) : base(appContext, enableDistCalls, options) + { } + + // invoked by BatchedDatabaseServerMessengerStartup which is an ApplicationEventHandler + // with default "ShouldExecute", so that method will run if app IsConfigured and database + // context IsDatabaseConfigured - we still want to check CanConnect though to be safe + internal void Startup() { - appBase.ApplicationStarted += Application_Started; UmbracoModule.EndRequest += UmbracoModule_EndRequest; UmbracoModule.RouteAttempt += UmbracoModule_RouteAttempt; - } - private void Application_Started(object sender, EventArgs eventArgs) - { - if (ApplicationContext.IsConfigured == false - || ApplicationContext.DatabaseContext.IsDatabaseConfigured == false - || ApplicationContext.DatabaseContext.CanConnect == false) - - LogHelper.Warn("The app is not configured or cannot connect to the database, this server cannot be initialized with " - + typeof(BatchedDatabaseServerMessenger) + ", distributed calls will not be enabled for this server"); - - // because .ApplicationStarted triggers only once, this is thread-safe - Boot(); + if (ApplicationContext.DatabaseContext.CanConnect == false) + { + ApplicationContext.ProfilingLogger.Logger.Warn( + "Cannot connect to the database, distributed calls will not be enabled for this server."); + } + else + { + Boot(); + } } private void UmbracoModule_RouteAttempt(object sender, RoutableAttemptEventArgs e) { + // as long as umbraco is ready & configured, sync switch (e.Outcome) { case EnsureRoutableOutcome.IsRoutable: - Sync(); - break; case EnsureRoutableOutcome.NotDocumentRequest: - //so it's not a document request, we'll check if it's a back office request - if (e.HttpContext.Request.Url.IsBackOfficeRequest(HttpRuntime.AppDomainAppVirtualPath)) - { - //it's a back office request, we should sync! - Sync(); - } + case EnsureRoutableOutcome.NoContent: + Sync(); break; //case EnsureRoutableOutcome.NotReady: //case EnsureRoutableOutcome.NotConfigured: - //case EnsureRoutableOutcome.NoContent: //default: // break; } @@ -66,23 +66,79 @@ namespace Umbraco.Web FlushBatch(); } - protected override ICollection GetBatch(bool ensureHttpContext) + protected override void DeliverRemote(IEnumerable servers, ICacheRefresher refresher, MessageType messageType, IEnumerable ids = null, string json = null) { - var httpContext = UmbracoContext.Current == null ? null : UmbracoContext.Current.HttpContext; - if (httpContext == null) + var idsA = ids == null ? null : ids.ToArray(); + + Type arrayType; + if (GetArrayType(idsA, out arrayType) == false) + throw new ArgumentException("All items must be of the same type, either int or Guid.", "ids"); + + BatchMessage(servers, refresher, messageType, idsA, arrayType, json); + } + + public void FlushBatch() + { + var batch = GetBatch(false); + if (batch == null) return; + + var instructions = batch.SelectMany(x => x.Instructions).ToArray(); + batch.Clear(); + if (instructions.Length == 0) return; + WriteInstructions(instructions); + } + + private void WriteInstructions(RefreshInstruction[] instructions) + { + var dto = new CacheInstructionDto { - if (ensureHttpContext) - throw new NotSupportedException("Cannot execute without a valid/current UmbracoContext with an HttpContext assigned."); - return null; - } + UtcStamp = DateTime.UtcNow, + Instructions = JsonConvert.SerializeObject(instructions, Formatting.None), + OriginIdentity = LocalIdentity + }; + + ApplicationContext.DatabaseContext.Database.Insert(dto); + } + + protected ICollection GetBatch(bool create) + { + // try get the http context from the UmbracoContext, we do this because in the case we are launching an async + // thread and we know that the cache refreshers will execute, we will ensure the UmbracoContext and therefore we + // can get the http context from it + var httpContext = (UmbracoContext.Current == null ? null : UmbracoContext.Current.HttpContext) + // if this is null, it could be that an async thread is calling this method that we weren't aware of and the UmbracoContext + // wasn't ensured at the beginning of the thread. We can try to see if the HttpContext.Current is available which might be + // the case if the asp.net synchronization context has kicked in + ?? (HttpContext.Current == null ? null : new HttpContextWrapper(HttpContext.Current)); + + // if no context was found, return null - we cannot not batch + if (httpContext == null) return null; var key = typeof (BatchedDatabaseServerMessenger).Name; // no thread-safety here because it'll run in only 1 thread (request) at a time var batch = (ICollection)httpContext.Items[key]; - if (batch == null && ensureHttpContext) + if (batch == null && create) httpContext.Items[key] = batch = new List(); return batch; } + + protected void BatchMessage( + IEnumerable servers, + ICacheRefresher refresher, + MessageType messageType, + IEnumerable ids = null, + Type idType = null, + string json = null) + { + var batch = GetBatch(true); + var instructions = RefreshInstruction.GetInstructions(refresher, messageType, ids, idType, json); + + // batch if we can, else write to DB immediately + if (batch == null) + WriteInstructions(instructions.ToArray()); + else + batch.Add(new RefreshInstructionEnvelope(servers, refresher, instructions)); + } } } \ No newline at end of file diff --git a/src/Umbraco.Web/BatchedDatabaseServerMessengerStartup.cs b/src/Umbraco.Web/BatchedDatabaseServerMessengerStartup.cs new file mode 100644 index 0000000000..a5b88a08ec --- /dev/null +++ b/src/Umbraco.Web/BatchedDatabaseServerMessengerStartup.cs @@ -0,0 +1,22 @@ +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Core.Sync; + +namespace Umbraco.Web +{ + /// + /// Used to boot up the server messenger once the application succesfully starts + /// + internal class BatchedDatabaseServerMessengerStartup : ApplicationEventHandler + { + protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) + { + var messenger = ServerMessengerResolver.HasCurrent + ? ServerMessengerResolver.Current.Messenger as BatchedDatabaseServerMessenger + : null; + + if (messenger != null) + messenger.Startup(); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/BatchedWebServiceServerMessenger.cs b/src/Umbraco.Web/BatchedWebServiceServerMessenger.cs index 5fc3b48eee..63536ca9f7 100644 --- a/src/Umbraco.Web/BatchedWebServiceServerMessenger.cs +++ b/src/Umbraco.Web/BatchedWebServiceServerMessenger.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Web; using Umbraco.Core.Sync; namespace Umbraco.Web @@ -38,11 +39,19 @@ namespace Umbraco.Web protected override ICollection GetBatch(bool ensureHttpContext) { - var httpContext = UmbracoContext.Current == null ? null : UmbracoContext.Current.HttpContext; + //try get the http context from the UmbracoContext, we do this because in the case we are launching an async + // thread and we know that the cache refreshers will execute, we will ensure the UmbracoContext and therefore we + // can get the http context from it + var httpContext = (UmbracoContext.Current == null ? null : UmbracoContext.Current.HttpContext) + //if this is null, it could be that an async thread is calling this method that we weren't aware of and the UmbracoContext + // wasn't ensured at the beginning of the thread. We can try to see if the HttpContext.Current is available which might be + // the case if the asp.net synchronization context has kicked in + ?? (HttpContext.Current == null ? null : new HttpContextWrapper(HttpContext.Current)); + if (httpContext == null) { if (ensureHttpContext) - throw new NotSupportedException("Cannot execute without a valid/current UmbracoContext with an HttpContext assigned."); + throw new NotSupportedException("Cannot execute without a valid/current HttpContext assigned."); return null; } diff --git a/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs b/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs index c102bb823e..4249276302 100644 --- a/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs +++ b/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs @@ -10,6 +10,7 @@ using umbraco.BusinessLogic; using umbraco.cms.businesslogic; using System.Linq; using umbraco.cms.businesslogic.web; +using Umbraco.Core.Logging; using Umbraco.Core.Publishing; using Content = Umbraco.Core.Models.Content; using ApplicationTree = Umbraco.Core.Models.ApplicationTree; @@ -23,7 +24,9 @@ namespace Umbraco.Web.Cache public class CacheRefresherEventHandler : ApplicationEventHandler { protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) - { + { + LogHelper.Info("Initializing Umbraco internal event handlers for cache refreshing"); + //bind to application tree events ApplicationTreeService.Deleted += ApplicationTreeDeleted; ApplicationTreeService.Updated += ApplicationTreeUpdated; diff --git a/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs b/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs index b9a90b643f..86114d5f77 100644 --- a/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs @@ -27,7 +27,7 @@ namespace Umbraco.Web.Cache var serializer = new JavaScriptSerializer(); var jsonObject = serializer.Deserialize(json); return jsonObject; - } + } /// /// Creates the custom Json payload used to refresh cache amongst the servers @@ -41,8 +41,7 @@ namespace Umbraco.Web.Cache var json = serializer.Serialize(items); return json; } - - + /// /// Converts a macro to a jsonPayload object /// diff --git a/src/Umbraco.Web/Cache/DistributedCache.cs b/src/Umbraco.Web/Cache/DistributedCache.cs index eb46f91ad1..27c30d89e8 100644 --- a/src/Umbraco.Web/Cache/DistributedCache.cs +++ b/src/Umbraco.Web/Cache/DistributedCache.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel; using System.Linq; using Umbraco.Core; using Umbraco.Core.Sync; @@ -145,6 +146,16 @@ namespace Umbraco.Web.Cache id); } + public void RefreshByPayload(Guid factoryGuid, object payload) + { + if (factoryGuid == Guid.Empty || payload == null) return; + + ServerMessengerResolver.Current.Messenger.PerformRefresh( + ServerRegistrarResolver.Current.Registrar.Registrations, + GetRefresherById(factoryGuid), + payload); + } + /// /// Notifies the distributed cache, for a specified . /// @@ -182,24 +193,27 @@ namespace Umbraco.Web.Cache public void RefreshAll(Guid factoryGuid) { if (factoryGuid == Guid.Empty) return; - RefreshAll(factoryGuid, true); + + ServerMessengerResolver.Current.Messenger.PerformRefreshAll( + ServerRegistrarResolver.Current.Registrar.Registrations, + GetRefresherById(factoryGuid)); } - /// - /// Notifies the distributed cache of a global invalidation for a specified . - /// - /// The unique identifier of the ICacheRefresher. - /// If true, all servers in the load balancing environment are notified; otherwise, - /// only the local server is notified. + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("This method is no longer in use and does not work as advertised, the allServers parameter doesnt have any affect for database server messengers, do not use!")] public void RefreshAll(Guid factoryGuid, bool allServers) { if (factoryGuid == Guid.Empty) return; - - ServerMessengerResolver.Current.Messenger.PerformRefreshAll( - allServers - ? ServerRegistrarResolver.Current.Registrar.Registrations - : Enumerable.Empty(), //this ensures it will only execute against the current server - GetRefresherById(factoryGuid)); + if (allServers) + { + RefreshAll(factoryGuid); + } + else + { + ServerMessengerResolver.Current.Messenger.PerformRefreshAll( + Enumerable.Empty(), + GetRefresherById(factoryGuid)); + } } /// diff --git a/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs b/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs index 22a4c3ba25..66cb82ff7c 100644 --- a/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs +++ b/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs @@ -5,6 +5,7 @@ using Umbraco.Core.Events; using Umbraco.Core.Models; using umbraco; using umbraco.cms.businesslogic.web; +using Umbraco.Core.Persistence.Repositories; namespace Umbraco.Web.Cache { @@ -125,7 +126,7 @@ namespace Umbraco.Web.Cache #endregion - #region Data type cache + #region Data type cache public static void RefreshDataTypeCache(this DistributedCache dc, IDataTypeDefinition dataType) { @@ -262,8 +263,8 @@ namespace Umbraco.Web.Cache public static void ClearAllMacroCacheOnCurrentServer(this DistributedCache dc) { - // NOTE: The 'false' ensure that it will only refresh on the current server, not post to all servers - dc.RefreshAll(DistributedCache.MacroCacheRefresherGuid, false); + var macroRefresher = CacheRefreshersResolver.Current.GetById(DistributedCache.MacroCacheRefresherGuid); + macroRefresher.RefreshAll(); } public static void RefreshMacroCache(this DistributedCache dc, IMacro macro) @@ -400,6 +401,12 @@ namespace Umbraco.Web.Cache dc.Remove(DistributedCache.DomainCacheRefresherGuid, domain.Id); } + public static void ClearDomainCacheOnCurrentServer(this DistributedCache dc) + { + var domainRefresher = CacheRefreshersResolver.Current.GetById(DistributedCache.DomainCacheRefresherGuid); + domainRefresher.RefreshAll(); + } + #endregion #region Language Cache diff --git a/src/Umbraco.Web/Cache/DomainCacheRefresher.cs b/src/Umbraco.Web/Cache/DomainCacheRefresher.cs index aabe95548f..51a2c79b2d 100644 --- a/src/Umbraco.Web/Cache/DomainCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/DomainCacheRefresher.cs @@ -1,6 +1,7 @@ using System; using Umbraco.Core; using Umbraco.Core.Cache; +using Umbraco.Core.Models; using Umbraco.Core.Persistence.Repositories; using Umbraco.Web.PublishedCache; using Umbraco.Web.PublishedCache.XmlPublishedCache; @@ -41,7 +42,7 @@ namespace Umbraco.Web.Cache private void ClearCache() { - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); + ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); // SD: we need to clear the routes cache here! // diff --git a/src/Umbraco.Web/Cache/MediaCacheRefresher.cs b/src/Umbraco.Web/Cache/MediaCacheRefresher.cs index 429bf313f6..dce512b1dc 100644 --- a/src/Umbraco.Web/Cache/MediaCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/MediaCacheRefresher.cs @@ -11,6 +11,7 @@ using Umbraco.Core.Models; using Umbraco.Core.Persistence.Repositories; using umbraco.interfaces; using System.Linq; +using Umbraco.Web.PublishedCache.XmlPublishedCache; namespace Umbraco.Web.Cache { @@ -29,7 +30,7 @@ namespace Umbraco.Web.Cache /// /// /// - internal static JsonPayload[] DeserializeFromJsonPayload(string json) + public static JsonPayload[] DeserializeFromJsonPayload(string json) { var serializer = new JavaScriptSerializer(); var jsonObject = serializer.Deserialize(json); @@ -98,14 +99,14 @@ namespace Umbraco.Web.Cache #region Sub classes - internal enum OperationType + public enum OperationType { Saved, Trashed, Deleted } - internal class JsonPayload + public class JsonPayload { public string Path { get; set; } public int Id { get; set; } @@ -184,9 +185,11 @@ namespace Umbraco.Web.Cache if (idPart == payload.Id.ToString(CultureInfo.InvariantCulture)) ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch( string.Format("{0}_{1}", CacheKeys.MediaCacheKey, payload.Id)); - } - } + } + + // published cache... + PublishedMediaCache.ClearCache(payload.Id); }); diff --git a/src/Umbraco.Web/Cache/TemplateCacheRefresher.cs b/src/Umbraco.Web/Cache/TemplateCacheRefresher.cs index ed67c24fe1..22bce3d5bf 100644 --- a/src/Umbraco.Web/Cache/TemplateCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/TemplateCacheRefresher.cs @@ -47,6 +47,14 @@ namespace Umbraco.Web.Cache public override void Remove(int id) { RemoveFromCache(id); + + //During removal we need to clear the runtime cache for templates, content and content type instances!!! + // all three of these types are referenced by templates, and the cache needs to be cleared on every server, + // otherwise things like looking up content type's after a template is removed is still going to show that + // it has an associated template. + ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); + ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); + base.Remove(id); } @@ -57,8 +65,7 @@ namespace Umbraco.Web.Cache ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheItem( string.Format("{0}{1}", CacheKeys.TemplateFrontEndCacheKey, id)); - //need to clear the runtime cache for template instances - //NOTE: This is temp until we implement the correct ApplicationCache and then we can remove the RuntimeCache, etc... + //need to clear the runtime cache for templates ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); } diff --git a/src/Umbraco.Web/Cache/UnpublishedPageCacheRefresher.cs b/src/Umbraco.Web/Cache/UnpublishedPageCacheRefresher.cs index 47a2d73bdb..e3601188f4 100644 --- a/src/Umbraco.Web/Cache/UnpublishedPageCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/UnpublishedPageCacheRefresher.cs @@ -79,6 +79,7 @@ namespace Umbraco.Web.Cache { ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); + DistributedCache.Instance.ClearDomainCacheOnCurrentServer(); base.RefreshAll(); } @@ -87,6 +88,7 @@ namespace Umbraco.Web.Cache ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheItem(RepositoryBase.GetCacheIdKey(id)); ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); content.Instance.UpdateSortOrder(id); + DistributedCache.Instance.ClearDomainCacheOnCurrentServer(); base.Refresh(id); } @@ -94,6 +96,7 @@ namespace Umbraco.Web.Cache { ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheItem(RepositoryBase.GetCacheIdKey(id)); ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); + DistributedCache.Instance.ClearDomainCacheOnCurrentServer(); base.Remove(id); } @@ -103,6 +106,7 @@ namespace Umbraco.Web.Cache ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheItem(RepositoryBase.GetCacheIdKey(instance.Id)); ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); content.Instance.UpdateSortOrder(instance); + DistributedCache.Instance.ClearDomainCacheOnCurrentServer(); base.Refresh(instance); } @@ -110,6 +114,7 @@ namespace Umbraco.Web.Cache { ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheItem(RepositoryBase.GetCacheIdKey(instance.Id)); ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); + DistributedCache.Instance.ClearDomainCacheOnCurrentServer(); base.Remove(instance); } @@ -127,6 +132,8 @@ namespace Umbraco.Web.Cache content.Instance.UpdateSortOrder(payload.Id); } + DistributedCache.Instance.ClearDomainCacheOnCurrentServer(); + OnCacheUpdated(Instance, new CacheRefresherEventArgs(jsonPayload, MessageType.RefreshByJson)); } diff --git a/src/Umbraco.Web/Controllers/UmbLoginController.cs b/src/Umbraco.Web/Controllers/UmbLoginController.cs index 3ae00f6970..e86e48a18b 100644 --- a/src/Umbraco.Web/Controllers/UmbLoginController.cs +++ b/src/Umbraco.Web/Controllers/UmbLoginController.cs @@ -25,6 +25,8 @@ namespace Umbraco.Web.Controllers return CurrentUmbracoPage(); } + TempData["LoginSuccess"] = true; + //if there is a specified path to redirect to then use it if (model.RedirectUrl.IsNullOrWhiteSpace() == false) { @@ -32,7 +34,7 @@ namespace Umbraco.Web.Controllers } //redirect to current page by default - TempData["LoginSuccess"] = true; + return RedirectToCurrentUmbracoPage(); //return RedirectToCurrentUmbracoUrl(); } diff --git a/src/Umbraco.Web/Controllers/UmbLoginStatusController.cs b/src/Umbraco.Web/Controllers/UmbLoginStatusController.cs index 457ed77767..9bb8ae7c9a 100644 --- a/src/Umbraco.Web/Controllers/UmbLoginStatusController.cs +++ b/src/Umbraco.Web/Controllers/UmbLoginStatusController.cs @@ -8,6 +8,7 @@ using Umbraco.Core; namespace Umbraco.Web.Controllers { + [MemberAuthorize] public class UmbLoginStatusController : SurfaceController { [HttpPost] @@ -23,6 +24,8 @@ namespace Umbraco.Web.Controllers FormsAuthentication.SignOut(); } + TempData["LogoutSuccess"] = true; + //if there is a specified path to redirect to then use it if (model.RedirectUrl.IsNullOrWhiteSpace() == false) { @@ -30,7 +33,7 @@ namespace Umbraco.Web.Controllers } //redirect to current page by default - TempData["LogoutSuccess"] = true; + return RedirectToCurrentUmbracoPage(); } } diff --git a/src/Umbraco.Web/Controllers/UmbProfileController.cs b/src/Umbraco.Web/Controllers/UmbProfileController.cs index a9d2a29513..b45723ed30 100644 --- a/src/Umbraco.Web/Controllers/UmbProfileController.cs +++ b/src/Umbraco.Web/Controllers/UmbProfileController.cs @@ -11,6 +11,7 @@ using Umbraco.Core; namespace Umbraco.Web.Controllers { + [MemberAuthorize] public class UmbProfileController : SurfaceController { [HttpPost] @@ -35,14 +36,15 @@ namespace Umbraco.Web.Controllers return CurrentUmbracoPage(); } + TempData["ProfileUpdateSuccess"] = true; + //if there is a specified path to redirect to then use it if (model.RedirectUrl.IsNullOrWhiteSpace() == false) { return Redirect(model.RedirectUrl); } - //redirect to current page by default - TempData["ProfileUpdateSuccess"] = true; + //redirect to current page by default return RedirectToCurrentUmbracoPage(); } } diff --git a/src/Umbraco.Web/Controllers/UmbRegisterController.cs b/src/Umbraco.Web/Controllers/UmbRegisterController.cs index 5f0e864cae..f9665f713e 100644 --- a/src/Umbraco.Web/Controllers/UmbRegisterController.cs +++ b/src/Umbraco.Web/Controllers/UmbRegisterController.cs @@ -27,13 +27,16 @@ namespace Umbraco.Web.Controllers switch (status) { case MembershipCreateStatus.Success: + + TempData["FormSuccess"] = true; + //if there is a specified path to redirect to then use it if (model.RedirectUrl.IsNullOrWhiteSpace() == false) { return Redirect(model.RedirectUrl); } //redirect to current page by default - TempData["FormSuccess"] = true; + return RedirectToCurrentUmbracoPage(); case MembershipCreateStatus.InvalidUserName: ModelState.AddModelError((model.UsernameIsEmail || model.Username == null) diff --git a/src/Umbraco.Web/Dictionary/UmbracoCultureDictionary.cs b/src/Umbraco.Web/Dictionary/UmbracoCultureDictionary.cs index e7ebb3cb6a..29954701ea 100644 --- a/src/Umbraco.Web/Dictionary/UmbracoCultureDictionary.cs +++ b/src/Umbraco.Web/Dictionary/UmbracoCultureDictionary.cs @@ -1,17 +1,61 @@ using System; +using System.Collections.Generic; using System.Dynamic; using System.Globalization; +using System.Linq; using System.Web; using Umbraco.Core.Logging; using umbraco.cms.businesslogic; using umbraco.cms.businesslogic.language; +using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Models; +using Umbraco.Core.Services; namespace Umbraco.Web.Dictionary { + /// + /// A culture dictionary that uses the Umbraco ILocalizationService + /// public class DefaultCultureDictionary : Umbraco.Core.Dictionary.ICultureDictionary { - /// + private readonly ILocalizationService _localizationService; + private readonly ICacheProvider _requestCacheProvider; + private readonly CultureInfo _specificCulture = null; + + public DefaultCultureDictionary() + : this(ApplicationContext.Current.Services.LocalizationService, ApplicationContext.Current.ApplicationCache.RequestCache) + { + + } + + public DefaultCultureDictionary(ILocalizationService localizationService, ICacheProvider requestCacheProvider) + { + if (localizationService == null) throw new ArgumentNullException("localizationService"); + if (requestCacheProvider == null) throw new ArgumentNullException("requestCacheProvider"); + _localizationService = localizationService; + _requestCacheProvider = requestCacheProvider; + } + + public DefaultCultureDictionary(CultureInfo specificCulture) + : this(ApplicationContext.Current.Services.LocalizationService, ApplicationContext.Current.ApplicationCache.RequestCache) + { + if (specificCulture == null) throw new ArgumentNullException("specificCulture"); + _specificCulture = specificCulture; + } + + public DefaultCultureDictionary(CultureInfo specificCulture, ILocalizationService localizationService, ICacheProvider requestCacheProvider) + { + if (specificCulture == null) throw new ArgumentNullException("specificCulture"); + if (localizationService == null) throw new ArgumentNullException("localizationService"); + if (requestCacheProvider == null) throw new ArgumentNullException("requestCacheProvider"); + _localizationService = localizationService; + _requestCacheProvider = requestCacheProvider; + _specificCulture = specificCulture; + } + + /// /// Returns the dictionary value based on the key supplied /// /// @@ -20,15 +64,19 @@ namespace Umbraco.Web.Dictionary { get { - try - { - return new global::umbraco.cms.businesslogic.Dictionary.DictionaryItem(key).Value(Language.id); - } - catch (Exception e) - { - LogHelper.WarnWithException("Error returning dictionary item '" + key + "'", true, e); - return string.Empty; - } + var found = _localizationService.GetDictionaryItemByKey(key); + if (found == null) + { + return string.Empty; + } + + var byLang = found.Translations.FirstOrDefault(x => x.Language.Equals(Language)); + if (byLang == null) + { + return string.Empty; + } + + return byLang.Value; } } @@ -37,12 +85,50 @@ namespace Umbraco.Web.Dictionary /// public CultureInfo Culture { - get { return System.Threading.Thread.CurrentThread.CurrentUICulture; } + get { return _specificCulture ?? System.Threading.Thread.CurrentThread.CurrentUICulture; } } - private Language Language + /// + /// Returns the child dictionary entries for a given key + /// + /// + /// + public IDictionary GetChildren(string key) + { + var result = new Dictionary(); + + var found = _localizationService.GetDictionaryItemByKey(key); + if (found == null) + { + return result; + } + + var children = _localizationService.GetDictionaryItemChildren(found.Key); + if (children == null) + { + return result; + } + + foreach (var dictionaryItem in children) + { + var byLang = dictionaryItem.Translations.FirstOrDefault((x => x.Language.Equals(Language))); + if (byLang != null) + { + result.Add(dictionaryItem.ItemKey, byLang.Value); + } + } + + return result; + } + + private ILanguage Language { - get { return Language.GetByCultureCode(System.Threading.Thread.CurrentThread.CurrentUICulture.Name); } + get + { + //ensure it's stored/retrieved from request cache + return _requestCacheProvider.GetCacheItem(typeof (DefaultCultureDictionary).Name + "Culture", + () => _localizationService.GetLanguageByIsoCode(Culture.Name)); + } } } diff --git a/src/Umbraco.Web/Editors/AuthenticationController.cs b/src/Umbraco.Web/Editors/AuthenticationController.cs index 6f97eb3d05..53f777fdf9 100644 --- a/src/Umbraco.Web/Editors/AuthenticationController.cs +++ b/src/Umbraco.Web/Editors/AuthenticationController.cs @@ -4,6 +4,7 @@ using System.Net; using System.Net.Http; using System.Collections.Generic; using System.Security.Claims; +using System.ServiceModel.Channels; using System.Threading.Tasks; using System.Web.Http; using AutoMapper; @@ -21,6 +22,7 @@ using Microsoft.AspNet.Identity.Owin; using Umbraco.Core.Logging; using Newtonsoft.Json.Linq; using Umbraco.Core.Models.Identity; +using Umbraco.Web.Security.Identity; using IUser = Umbraco.Core.Models.Membership.IUser; namespace Umbraco.Web.Editors @@ -37,6 +39,7 @@ namespace Umbraco.Web.Editors { private BackOfficeUserManager _userManager; + private BackOfficeSignInManager _signInManager; protected BackOfficeUserManager UserManager { @@ -55,26 +58,24 @@ namespace Umbraco.Web.Editors } } - /// - /// This is a special method that will return the current users' remaining session seconds, the reason - /// it is special is because this route is ignored in the UmbracoModule so that the auth ticket doesn't get - /// updated with a new timeout. - /// - /// - [WebApi.UmbracoAuthorize] - [ValidateAngularAntiForgeryToken] - public double GetRemainingTimeoutSeconds() + protected BackOfficeSignInManager SignInManager { - var httpContextAttempt = TryGetHttpContext(); - if (httpContextAttempt.Success) + get { - return httpContextAttempt.Result.GetRemainingAuthSeconds(); + if (_signInManager == null) + { + var mgr = TryGetOwinContext().Result.Get(); + if (mgr == null) + { + throw new NullReferenceException("Could not resolve an instance of " + typeof(BackOfficeSignInManager) + " from the " + typeof(IOwinContext)); + } + _signInManager = mgr; + } + return _signInManager; } - - //we need an http context - throw new NotSupportedException("An HttpContext is required for this request"); } + [WebApi.UmbracoAuthorize] [ValidateAngularAntiForgeryToken] public async Task PostUnLinkLogin(UnLinkLoginModel unlinkLoginModel) @@ -86,7 +87,7 @@ namespace Umbraco.Web.Editors if (result.Succeeded) { var user = await UserManager.FindByIdAsync(User.Identity.GetUserId()); - await SignInAsync(user, isPersistent: false); + await SignInManager.SignInAsync(user, isPersistent: true, rememberBrowser: false); return Request.CreateResponse(HttpStatusCode.OK); } else @@ -156,36 +157,68 @@ namespace Umbraco.Web.Editors if (http.Success == false) throw new InvalidOperationException("This method requires that an HttpContext be active"); - if (UmbracoContext.Security.ValidateBackOfficeCredentials(loginModel.Username, loginModel.Password)) + var result = await SignInManager.PasswordSignInAsync( + loginModel.Username, loginModel.Password, isPersistent: true, shouldLockout: true); + + switch (result) { - //get the user - var user = Security.GetBackOfficeUser(loginModel.Username); - var userDetail = Mapper.Map(user); + case SignInStatus.Success: - //create a response with the userDetail object - var response = Request.CreateResponse(HttpStatusCode.OK, userDetail); + //get the user + var user = Security.GetBackOfficeUser(loginModel.Username); + var userDetail = Mapper.Map(user); + //update the userDetail and set their remaining seconds + userDetail.SecondsUntilTimeout = TimeSpan.FromMinutes(GlobalSettings.TimeOutInMinutes).TotalSeconds; + + //create a response with the userDetail object + var response = Request.CreateResponse(HttpStatusCode.OK, userDetail); - //set the response cookies with the ticket (NOTE: This needs to be done with the custom webapi extension because - // we cannot mix HttpContext.Response.Cookies and the way WebApi/Owin work) - var ticket = response.UmbracoLoginWebApi(user); + //ensure the user is set for the current request + Request.SetPrincipalForRequest(user); - //Identity does some of it's own checks as well so we need to use it's sign in process too... this will essentially re-create the - // ticket/cookie above but we need to create the ticket now so we can assign the Current Thread User/IPrinciple below - await SignInAsync(Mapper.Map(user), isPersistent: true); - //This ensure the current principal is set, otherwise any logic executing after this wouldn't actually be authenticated - http.Result.AuthenticateCurrentRequest(ticket, false); - - //update the userDetail and set their remaining seconds - userDetail.SecondsUntilTimeout = ticket.GetRemainingAuthSeconds(); + return response; - return response; + case SignInStatus.RequiresVerification: + + var twofactorOptions = UserManager as IUmbracoBackOfficeTwoFactorOptions; + if (twofactorOptions == null) + { + throw new HttpResponseException( + Request.CreateErrorResponse( + HttpStatusCode.BadRequest, + "UserManager does not implement " + typeof(IUmbracoBackOfficeTwoFactorOptions))); + } + + var twofactorView = twofactorOptions.GetTwoFactorView( + TryGetOwinContext().Result, + UmbracoContext, + loginModel.Username); + + if (twofactorView.IsNullOrWhiteSpace()) + { + throw new HttpResponseException( + Request.CreateErrorResponse( + HttpStatusCode.BadRequest, + typeof(IUmbracoBackOfficeTwoFactorOptions) + ".GetTwoFactorView returned an empty string")); + } + + //create a with information to display a custom two factor send code view + var verifyResponse = Request.CreateResponse(HttpStatusCode.OK, new + { + twoFactorView = twofactorView + }); + + return verifyResponse; + + case SignInStatus.LockedOut: + case SignInStatus.Failure: + default: + //return BadRequest (400), we don't want to return a 401 because that get's intercepted + // by our angular helper because it thinks that we need to re-perform the request once we are + // authorized and we don't want to return a 403 because angular will show a warning msg indicating + // that the user doesn't have access to perform this function, we just want to return a normal invalid msg. + throw new HttpResponseException(HttpStatusCode.BadRequest); } - - //return BadRequest (400), we don't want to return a 401 because that get's intercepted - // by our angular helper because it thinks that we need to re-perform the request once we are - // authorized and we don't want to return a 403 because angular will show a warning msg indicating - // that the user doesn't have access to perform this function, we just want to return a normal invalid msg. - throw new HttpResponseException(HttpStatusCode.BadRequest); } @@ -193,11 +226,18 @@ namespace Umbraco.Web.Editors /// Logs the current user out /// /// - [UmbracoBackOfficeLogout] [ClearAngularAntiForgeryToken] [ValidateAngularAntiForgeryToken] public HttpResponseMessage PostLogout() - { + { + Request.TryGetOwinContext().Result.Authentication.SignOut( + Core.Constants.Security.BackOfficeAuthenticationType, + Core.Constants.Security.BackOfficeExternalAuthenticationType); + + Logger.Info("User {0} from IP address {1} has logged out", + () => User.Identity == null ? "UNKNOWN" : User.Identity.Name, + () => TryGetOwinContext().Result.Request.RemoteIpAddress); + return Request.CreateResponse(HttpStatusCode.OK); } @@ -209,16 +249,5 @@ namespace Umbraco.Web.Editors } } - private async Task SignInAsync(BackOfficeIdentityUser user, bool isPersistent) - { - var owinContext = TryGetOwinContext().Result; - - owinContext.Authentication.SignOut(Core.Constants.Security.BackOfficeExternalAuthenticationType); - - owinContext.Authentication.SignIn( - new AuthenticationProperties() { IsPersistent = isPersistent }, - await user.GenerateUserIdentityAsync(UserManager)); - } - } } \ No newline at end of file diff --git a/src/Umbraco.Web/Editors/BackOfficeController.cs b/src/Umbraco.Web/Editors/BackOfficeController.cs index d70c541cf2..0dd1272a3b 100644 --- a/src/Umbraco.Web/Editors/BackOfficeController.cs +++ b/src/Umbraco.Web/Editors/BackOfficeController.cs @@ -1,45 +1,40 @@ using System; using System.Collections.Generic; +using System.Configuration; using System.Globalization; using System.IO; using System.Linq; -using System.Security.Claims; -using System.ServiceModel.Security; using System.Text; -using System.Text.RegularExpressions; using System.Threading.Tasks; +using System.Web; +using System.Web.Configuration; using System.Web.Mvc; using System.Web.UI; -using dotless.Core.Parser.Tree; +using ClientDependency.Core.Config; using Microsoft.AspNet.Identity; -using Microsoft.Owin; +using Microsoft.AspNet.Identity.Owin; using Microsoft.Owin.Security; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using Umbraco.Core.Configuration; -using Umbraco.Core.IO; -using Umbraco.Core.Manifest; using Umbraco.Core; using Umbraco.Core.Cache; +using Umbraco.Core.Configuration; +using Umbraco.Core.IO; using Umbraco.Core.Logging; +using Umbraco.Core.Manifest; using Umbraco.Core.Models; -using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Models.Identity; using Umbraco.Core.Security; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Mvc; +using Umbraco.Web.PropertyEditors; +using Umbraco.Web.Security.Identity; using Umbraco.Web.Trees; using Umbraco.Web.UI.JavaScript; -using Umbraco.Web.PropertyEditors; -using Umbraco.Web.Models; -using Umbraco.Web.WebServices; using Umbraco.Web.WebApi.Filters; -using System.Web; -using AutoMapper; -using Microsoft.AspNet.Identity.Owin; -using Umbraco.Core.Models.Identity; -using Umbraco.Core.Security; -using Task = System.Threading.Tasks.Task; -using Umbraco.Web.Security.Identity; +using Umbraco.Web.WebServices; +using Action = umbraco.BusinessLogic.Actions.Action; +using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.Editors { @@ -51,12 +46,23 @@ namespace Umbraco.Web.Editors public class BackOfficeController : UmbracoController { private BackOfficeUserManager _userManager; + private BackOfficeSignInManager _signInManager; + + protected BackOfficeSignInManager SignInManager + { + get { return _signInManager ?? (_signInManager = OwinContext.Get()); } + } protected BackOfficeUserManager UserManager { get { return _userManager ?? (_userManager = OwinContext.GetUserManager()); } } + protected IAuthenticationManager AuthenticationManager + { + get { return OwinContext.Authentication; } + } + /// /// Render the default view /// @@ -91,10 +97,10 @@ namespace Umbraco.Web.Editors [HttpGet] public JsonNetResult LocalizedText(string culture = null) { - var cultureInfo = culture == null + var cultureInfo = string.IsNullOrWhiteSpace(culture) //if the user is logged in, get their culture, otherwise default to 'en' - ? User.Identity.IsAuthenticated - ? Security.CurrentUser.GetUserCulture(Services.TextService) + ? Security.IsAuthenticated() + ? Security.CurrentUser.GetUserCulture(Services.TextService) : CultureInfo.GetCultureInfo("en") : CultureInfo.GetCultureInfo(culture); @@ -120,10 +126,10 @@ namespace Umbraco.Web.Editors //get the legacy ActionJs file references to append as well var legacyActionJsRef = new JArray(GetLegacyActionJs(LegacyJsActionType.JsUrl)); - + var result = initJs.GetJavascriptInitialization(HttpContext, JsInitialization.GetDefaultInitialization(), legacyActionJsRef); result += initCss.GetStylesheetInitialization(HttpContext); - + return JavaScript(result); } @@ -150,57 +156,32 @@ namespace Umbraco.Web.Editors var result = HttpContext.IsDebuggingEnabled ? getResult() : ApplicationContext.ApplicationCache.RuntimeCache.GetCacheItem( - typeof (BackOfficeController) + "GetManifestAssetList", + typeof(BackOfficeController) + "GetManifestAssetList", () => getResult(), new TimeSpan(0, 10, 0)); return new JsonNetResult { Data = result, Formatting = Formatting.Indented }; } - + [UmbracoAuthorize(Order = 0)] [HttpGet] public JsonNetResult GetGridConfig() { - Func> getResult = () => - { - var parser = GetManifestParser(); - var editors = new List(); - var gridConfig = Server.MapPath("~/Config/grid.editors.config.js"); - if (System.IO.File.Exists(gridConfig)) - { - try - { - var arr = JArray.Parse(System.IO.File.ReadAllText(gridConfig)); - //ensure the contents parse correctly to objects - var parsed = parser.GetGridEditors(arr); - editors.AddRange(parsed); - } - catch (Exception ex) - { - LogHelper.Error("Could not parse the contents of grid.editors.config.js into a JSON array", ex); - } - } - var builder = new ManifestBuilder(ApplicationContext.ApplicationCache.RuntimeCache, parser); - foreach (var gridEditor in builder.GridEditors) - { - //no duplicates! (based on alias) - if (editors.Contains(gridEditor) == false) - { - editors.Add(gridEditor); - } - } - return editors; - }; + var gridConfig = UmbracoConfig.For.GridConfig( + Logger, + ApplicationContext.ApplicationCache.RuntimeCache, + new DirectoryInfo(Server.MapPath(SystemDirectories.AppPlugins)), + new DirectoryInfo(Server.MapPath(SystemDirectories.Config)), + HttpContext.IsDebuggingEnabled); - //cache the result if debugging is disabled - var result = HttpContext.IsDebuggingEnabled - ? getResult() - : ApplicationContext.ApplicationCache.RuntimeCache.GetCacheItem>( - typeof(BackOfficeController) + "GetGridConfig", - () => getResult(), - new TimeSpan(0, 10, 0)); - - return new JsonNetResult { Data = result, Formatting = Formatting.Indented }; + return new JsonNetResult { Data = gridConfig.EditorsConfig.Editors, Formatting = Formatting.Indented }; + } + + private string GetMaxRequestLength() + { + var section = ConfigurationManager.GetSection("system.web/httpRuntime") as HttpRuntimeSection; + if (section == null) return string.Empty; + return section.MaxRequestLength.ToString(); } /// @@ -208,18 +189,23 @@ namespace Umbraco.Web.Editors /// /// [UmbracoAuthorize(Order = 0)] - [MinifyJavaScriptResult(Order = 1)] + [MinifyJavaScriptResult(Order = 1)] public JavaScriptResult ServerVariables() { Func getResult = () => - { + { var defaultVals = new Dictionary { { "umbracoUrls", new Dictionary { - {"externalLoginsUrl", Url.Action("ExternalLogin", "BackOffice")}, - {"externalLinkLoginsUrl", Url.Action("LinkLogin", "BackOffice")}, + //TODO: Add 'umbracoApiControllerBaseUrl' which people can use in JS + // to prepend their URL. We could then also use this in our own resources instead of + // having each url defined here explicitly - we can do that in v8! for now + // for umbraco services we'll stick to explicitly defining the endpoints. + + {"externalLoginsUrl", Url.Action("ExternalLogin", "BackOffice")}, + {"externalLinkLoginsUrl", Url.Action("LinkLogin", "BackOffice")}, {"legacyTreeJs", Url.Action("LegacyTreeJs", "BackOffice")}, {"manifestAssetList", Url.Action("GetManifestAssetList", "BackOffice")}, {"gridConfig", Url.Action("GetGridConfig", "BackOffice")}, @@ -302,10 +288,10 @@ namespace Umbraco.Web.Editors controller => controller.Fetch(string.Empty)) }, { - "relationApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetById(0)) - }, - { + "relationApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( + controller => controller.GetById(0)) + }, + { "rteApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( controller => controller.GetConfiguration()) }, @@ -361,7 +347,12 @@ namespace Umbraco.Web.Editors "imageFileTypes", string.Join(",", UmbracoConfig.For.UmbracoSettings().Content.ImageFileTypes) }, + { + "maxFileSize", + GetMaxRequestLength() + }, {"keepUserLoggedIn", UmbracoConfig.For.UmbracoSettings().Security.KeepUserLoggedIn}, + {"cssPath", IOHelper.ResolveUrl(SystemDirectories.Css).TrimEnd('/')}, } }, { @@ -370,26 +361,28 @@ namespace Umbraco.Web.Editors {"trees", GetTreePluginsMetaData()} } }, - {"isDebuggingEnabled", HttpContext.IsDebuggingEnabled}, - { - "application", GetApplicationState() - }, - { - "externalLogins", new Dictionary { + "isDebuggingEnabled", HttpContext.IsDebuggingEnabled + }, + { + "application", GetApplicationState() + }, + { + "externalLogins", new Dictionary { - "providers", HttpContext.GetOwinContext().Authentication.GetExternalAuthenticationTypes() - .Where(p => p.Properties.ContainsKey("UmbracoBackOffice")) - .Select(p => new - { - authType = p.AuthenticationType, caption = p.Caption, - //TODO: Need to see if this exposes any sensitive data! - properties = p.Properties - }) - .ToArray() + { + "providers", HttpContext.GetOwinContext().Authentication.GetExternalAuthenticationTypes() + .Where(p => p.Properties.ContainsKey("UmbracoBackOffice")) + .Select(p => new + { + authType = p.AuthenticationType, caption = p.Caption, + //TODO: Need to see if this exposes any sensitive data! + properties = p.Properties + }) + .ToArray() + } } } - } }; //Parse the variables to a string @@ -406,7 +399,7 @@ namespace Umbraco.Web.Editors return JavaScript(result); } - + [HttpPost] public ActionResult ExternalLogin(string provider, string redirectUrl = null) { @@ -429,15 +422,15 @@ namespace Umbraco.Web.Editors User.Identity.GetUserId()); } - + [HttpGet] public async Task ExternalLinkLoginCallback() { var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync( - Core.Constants.Security.BackOfficeExternalAuthenticationType, + Constants.Security.BackOfficeExternalAuthenticationType, XsrfKey, User.Identity.GetUserId()); - + if (loginInfo == null) { //Add error and redirect for it to be displayed @@ -484,9 +477,9 @@ namespace Umbraco.Web.Editors //First check if there's external login info, if there's not proceed as normal var loginInfo = await OwinContext.Authentication.GetExternalLoginInfoAsync( - Core.Constants.Security.BackOfficeExternalAuthenticationType); + Constants.Security.BackOfficeExternalAuthenticationType); - if (loginInfo == null) + if (loginInfo == null || loginInfo.ExternalIdentity.IsAuthenticated == false) { return defaultResponse(); } @@ -511,36 +504,123 @@ namespace Umbraco.Web.Editors // that the ticket is created and stored and that the user is logged in. //sign in - await SignInAsync(user, isPersistent: false); + await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false); } else { - ViewBag.ExternalSignInError = new[] { "The requested provider (" + loginInfo.Login.LoginProvider + ") has not been linked to to an account" }; + if (await AutoLinkAndSignInExternalAccount(loginInfo) == false) + { + ViewBag.ExternalSignInError = new[] { "The requested provider (" + loginInfo.Login.LoginProvider + ") has not been linked to to an account" }; + } //Remove the cookie otherwise this message will keep appearing - if (Response.Cookies[Core.Constants.Security.BackOfficeExternalCookieName] != null) + if (Response.Cookies[Constants.Security.BackOfficeExternalCookieName] != null) { - Response.Cookies[Core.Constants.Security.BackOfficeExternalCookieName].Expires = DateTime.MinValue; + Response.Cookies[Constants.Security.BackOfficeExternalCookieName].Expires = DateTime.MinValue; } } return response(); } - private async Task SignInAsync(BackOfficeIdentityUser user, bool isPersistent) + private async Task AutoLinkAndSignInExternalAccount(ExternalLoginInfo loginInfo) { - OwinContext.Authentication.SignOut(Core.Constants.Security.BackOfficeExternalAuthenticationType); - - OwinContext.Authentication.SignIn( - new AuthenticationProperties() {IsPersistent = isPersistent}, - await user.GenerateUserIdentityAsync(UserManager)); + //Here we can check if the provider associated with the request has been configured to allow + // new users (auto-linked external accounts). This would never be used with public providers such as + // Google, unless you for some reason wanted anybody to be able to access the backend if they have a Google account + // .... not likely! + + var authType = OwinContext.Authentication.GetExternalAuthenticationTypes().FirstOrDefault(x => x.AuthenticationType == loginInfo.Login.LoginProvider); + if (authType == null) + { + Logger.Warn("Could not find external authentication provider registered: " + loginInfo.Login.LoginProvider); + return false; + } + + var autoLinkOptions = authType.GetExternalAuthenticationOptions(); + if (autoLinkOptions != null) + { + if (autoLinkOptions.ShouldAutoLinkExternalAccount(UmbracoContext, loginInfo)) + { + //we are allowing auto-linking/creating of local accounts + if (loginInfo.Email.IsNullOrWhiteSpace()) + { + ViewBag.ExternalSignInError = new[] { "The requested provider (" + loginInfo.Login.LoginProvider + ") has not provided an email address, the account cannot be linked." }; + } + else + { + + //Now we need to perform the auto-link, so first we need to lookup/create a user with the email address + var foundByEmail = Services.UserService.GetByEmail(loginInfo.Email); + if (foundByEmail != null) + { + ViewBag.ExternalSignInError = new[] { "A user with this email address already exists locally. You will need to login locally to Umbraco and link this external provider: " + loginInfo.Login.LoginProvider }; + } + else + { + var defaultUserType = autoLinkOptions.GetDefaultUserType(UmbracoContext, loginInfo); + var userType = Services.UserService.GetUserTypeByAlias(defaultUserType); + if (userType == null) + { + ViewBag.ExternalSignInError = new[] { "Could not auto-link this account, the specified User Type does not exist: " + defaultUserType }; + } + else + { + + var autoLinkUser = new BackOfficeIdentityUser() + { + Email = loginInfo.Email, + Name = loginInfo.ExternalIdentity.Name, + UserTypeAlias = userType.Alias, + AllowedSections = autoLinkOptions.GetDefaultAllowedSections(UmbracoContext, loginInfo), + Culture = autoLinkOptions.GetDefaultCulture(UmbracoContext, loginInfo), + UserName = loginInfo.Email + }; + + //call the callback if one is assigned + if (autoLinkOptions.OnAutoLinking != null) + { + autoLinkOptions.OnAutoLinking(autoLinkUser, loginInfo); + } + + var userCreationResult = await UserManager.CreateAsync(autoLinkUser); + + if (userCreationResult.Succeeded == false) + { + ViewBag.ExternalSignInError = userCreationResult.Errors; + } + else + { + var linkResult = await UserManager.AddLoginAsync(autoLinkUser.Id, loginInfo.Login); + if (linkResult.Succeeded == false) + { + ViewBag.ExternalSignInError = linkResult.Errors; + + //If this fails, we should really delete the user since it will be in an inconsistent state! + var deleteResult = await UserManager.DeleteAsync(autoLinkUser); + if (deleteResult.Succeeded == false) + { + //DOH! ... this isn't good, combine all errors to be shown + ViewBag.ExternalSignInError = linkResult.Errors.Concat(deleteResult.Errors); + } + } + else + { + //sign in + await SignInManager.SignInAsync(autoLinkUser, isPersistent: false, rememberBrowser: false); + } + } + } + } + + } + } + return true; + } + + return false; } - private IAuthenticationManager AuthenticationManager - { - get { return OwinContext.Authentication; } - } - /// /// Returns the server variables regarding the application state /// @@ -555,27 +635,26 @@ namespace Umbraco.Web.Editors {"assemblyVersion", UmbracoVersion.AssemblyVersion} }; - var version = string.IsNullOrEmpty(UmbracoVersion.CurrentComment) - ? UmbracoVersion.Current.ToString(3) - : string.Format("{0}-{1}", UmbracoVersion.Current.ToString(3), UmbracoVersion.CurrentComment); + var version = UmbracoVersion.GetSemanticVersion().ToSemanticString(); app.Add("version", version); - app.Add("cdf", ClientDependency.Core.Config.ClientDependencySettings.Instance.Version); + app.Add("cdf", ClientDependencySettings.Instance.Version); //useful for dealing with virtual paths on the client side when hosted in virtual directories especially app.Add("applicationPath", HttpContext.Request.ApplicationPath.EnsureEndsWith('/')); return app; } - + private IEnumerable> GetTreePluginsMetaData() { var treeTypes = PluginManager.Current.ResolveAttributedTreeControllers(); //get all plugin trees with their attributes var treesWithAttributes = treeTypes.Select(x => new - { - tree = x, attributes = + { + tree = x, + attributes = x.GetCustomAttributes(false) - }).ToArray(); - + }).ToArray(); + var pluginTreesWithAttributes = treesWithAttributes //don't resolve any tree decorated with CoreTreeAttribute .Where(x => x.attributes.All(a => (a is CoreTreeAttribute) == false)) @@ -625,48 +704,21 @@ namespace Umbraco.Web.Editors return JavaScript(result); } - /// - /// Renders out all JavaScript references that have bee declared in IActions - /// - private static IEnumerable GetLegacyActionJs(LegacyJsActionType type) + internal static IEnumerable GetLegacyActionJsForActions(LegacyJsActionType type, IEnumerable values) { var blockList = new List(); var urlList = new List(); - foreach (var jsFile in global::umbraco.BusinessLogic.Actions.Action.GetJavaScriptFileReferences()) + foreach (var jsFile in values) { - //validate that this is a url, if it is not, we'll assume that it is a text block and render it as a text - //block instead. - var isValid = true; - - if (Uri.IsWellFormedUriString(jsFile, UriKind.RelativeOrAbsolute)) + var isJsPath = jsFile.DetectIsJavaScriptPath(); + if (isJsPath.Success) + { - //ok it validates, but so does alert('hello'); ! so we need to do more checks - - //here are the valid chars in a url without escaping - if (Regex.IsMatch(jsFile, @"[^a-zA-Z0-9-._~:/?#\[\]@!$&'\(\)*\+,%;=]")) - isValid = false; - - //we'll have to be smarter and just check for certain js patterns now too! - var jsPatterns = new string[] {@"\+\s*\=", @"\);", @"function\s*\(", @"!=", @"=="}; - if (jsPatterns.Any(p => Regex.IsMatch(jsFile, p))) - { - isValid = false; - } - if (isValid) - { - //it is a valid URL add to Url list - urlList.Add(jsFile); - } + urlList.Add(isJsPath.Result); } else { - isValid = false; - } - - if (isValid == false) - { - //it isn't a valid URL, must be a js block - blockList.Add(jsFile); + blockList.Add(isJsPath.Result); } } @@ -680,8 +732,16 @@ namespace Umbraco.Web.Editors return blockList; } - - private enum LegacyJsActionType + + /// + /// Renders out all JavaScript references that have bee declared in IActions + /// + private static IEnumerable GetLegacyActionJs(LegacyJsActionType type) + { + return GetLegacyActionJsForActions(type, Action.GetJavaScriptFileReferences()); + } + + internal enum LegacyJsActionType { JsBlock, JsUrl @@ -717,12 +777,29 @@ namespace Umbraco.Web.Editors //Ensure the forms auth module doesn't do a redirect! context.HttpContext.Response.SuppressFormsAuthenticationRedirect = true; + var owinCtx = context.HttpContext.GetOwinContext(); + + //First, see if a custom challenge result callback is specified for the provider + // and use it instead of the default if one is supplied. + var loginProvider = owinCtx.Authentication + .GetExternalAuthenticationTypes() + .FirstOrDefault(p => p.AuthenticationType == LoginProvider); + if (loginProvider != null) + { + var providerChallengeResult = loginProvider.GetSignInChallengeResult(owinCtx); + if (providerChallengeResult != null) + { + owinCtx.Authentication.Challenge(providerChallengeResult, LoginProvider); + return; + } + } + var properties = new AuthenticationProperties() { RedirectUri = RedirectUri.EnsureEndsWith('/') }; if (UserId != null) { properties.Dictionary[XsrfKey] = UserId; } - context.HttpContext.GetOwinContext().Authentication.Challenge(properties, LoginProvider); + owinCtx.Authentication.Challenge(properties, LoginProvider); } } } diff --git a/src/Umbraco.Web/Editors/BackOfficeNotificationsController.cs b/src/Umbraco.Web/Editors/BackOfficeNotificationsController.cs new file mode 100644 index 0000000000..f4350bc596 --- /dev/null +++ b/src/Umbraco.Web/Editors/BackOfficeNotificationsController.cs @@ -0,0 +1,21 @@ +using Umbraco.Web.WebApi.Filters; + +namespace Umbraco.Web.Editors +{ + /// + /// An abstract controller that automatically checks if any request is a non-GET and if the + /// resulting message is INotificationModel in which case it will append any Event Messages + /// currently in the request. + /// + [AppendCurrentEventMessages] + public abstract class BackOfficeNotificationsController : UmbracoAuthorizedJsonController + { + protected BackOfficeNotificationsController() + { + } + + protected BackOfficeNotificationsController(UmbracoContext umbracoContext) : base(umbracoContext) + { + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 81ea419b75..192b49e2cf 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -30,7 +30,9 @@ using Umbraco.Core.Dynamics; using umbraco.BusinessLogic.Actions; using umbraco.cms.businesslogic.web; using umbraco.presentation.preview; +using Umbraco.Web.UI; using Constants = Umbraco.Core.Constants; +using Notification = Umbraco.Web.Models.ContentEditing.Notification; namespace Umbraco.Web.Editors { @@ -181,8 +183,14 @@ namespace Umbraco.Web.Editors return pagedResult; } - [HttpGet] + [Obsolete("Dont use this, it is incorrectly named, use HasPermission instead")] public bool GetHasPermission(string permissionToCheck, int nodeId) + { + return HasPermission(permissionToCheck, nodeId); + } + + [HttpGet] + public bool HasPermission(string permissionToCheck, int nodeId) { var p = Services.UserService.GetPermissions(Security.CurrentUser, nodeId).FirstOrDefault(); if (p != null && p.AssignedPermissions.Contains(permissionToCheck.ToString(CultureInfo.InvariantCulture))) @@ -244,22 +252,26 @@ namespace Umbraco.Web.Editors //initialize this to successful var publishStatus = Attempt.Succeed(); + var wasCancelled = false; if (contentItem.Action == ContentSaveAction.Save || contentItem.Action == ContentSaveAction.SaveNew) { //save the item - Services.ContentService.Save(contentItem.PersistedContent, Security.CurrentUser.Id); + var saveResult = Services.ContentService.WithResult().Save(contentItem.PersistedContent, Security.CurrentUser.Id); + + wasCancelled = saveResult.Success == false && saveResult.Result.StatusType == OperationStatusType.FailedCancelledByEvent; } else if (contentItem.Action == ContentSaveAction.SendPublish || contentItem.Action == ContentSaveAction.SendPublishNew) { - Services.ContentService.SendToPublication(contentItem.PersistedContent, Security.CurrentUser.Id); + var sendResult = Services.ContentService.SendToPublication(contentItem.PersistedContent, Security.CurrentUser.Id); + wasCancelled = sendResult == false; } else { //publish the item and check if it worked, if not we will show a diff msg below publishStatus = Services.ContentService.SaveAndPublishWithStatus(contentItem.PersistedContent, Security.CurrentUser.Id); + wasCancelled = publishStatus.Result.StatusType == PublishStatusType.FailedCancelledByEvent; } - //return the updated model var display = Mapper.Map(contentItem.PersistedContent); @@ -272,11 +284,29 @@ namespace Umbraco.Web.Editors { case ContentSaveAction.Save: case ContentSaveAction.SaveNew: - display.AddSuccessNotification(ui.Text("speechBubbles", "editContentSavedHeader"), ui.Text("speechBubbles", "editContentSavedText")); + if (wasCancelled == false) + { + display.AddSuccessNotification( + Services.TextService.Localize("speechBubbles/editContentSavedHeader"), + Services.TextService.Localize("speechBubbles/editContentSavedText")); + } + else + { + AddCancelMessage(display); + } break; case ContentSaveAction.SendPublish: case ContentSaveAction.SendPublishNew: - display.AddSuccessNotification(ui.Text("speechBubbles", "editContentSendToPublish"), ui.Text("speechBubbles", "editContentSendToPublishText")); + if (wasCancelled == false) + { + display.AddSuccessNotification( + Services.TextService.Localize("speechBubbles/editContentSendToPublish"), + Services.TextService.Localize("speechBubbles/editContentSendToPublishText")); + } + else + { + AddCancelMessage(display); + } break; case ContentSaveAction.Publish: case ContentSaveAction.PublishNew: @@ -286,60 +316,18 @@ namespace Umbraco.Web.Editors UpdatePreviewContext(contentItem.PersistedContent.Id); + //If the item is new and the operation was cancelled, we need to return a different + // status code so the UI can handle it since it won't be able to redirect since there + // is no Id to redirect to! + if (wasCancelled && IsCreatingAction(contentItem.Action)) + { + throw new HttpResponseException(Request.CreateValidationErrorResponse(display)); + } + return display; } - /// - /// Checks if the user is currently in preview mode and if so will update the preview content for this item - /// - /// - private void UpdatePreviewContext(int contentId) - { - var previewId = Request.GetPreviewCookieValue(); - if (previewId.IsNullOrWhiteSpace()) return; - Guid id; - if (Guid.TryParse(previewId, out id)) - { - var d = new Document(contentId); - var pc = new PreviewContent(UmbracoUser, id, false); - pc.PrepareDocument(UmbracoUser, d, true); - pc.SavePreviewSet(); - } - } - - /// - /// Maps the dto property values to the persisted model - /// - /// - private void MapPropertyValues(ContentItemSave contentItem) - { - UpdateName(contentItem); - - //TODO: We need to support 'send to publish' - - contentItem.PersistedContent.ExpireDate = contentItem.ExpireDate; - contentItem.PersistedContent.ReleaseDate = contentItem.ReleaseDate; - //only set the template if it didn't change - var templateChanged = (contentItem.PersistedContent.Template == null && contentItem.TemplateAlias.IsNullOrWhiteSpace() == false) - || (contentItem.PersistedContent.Template != null && contentItem.PersistedContent.Template.Alias != contentItem.TemplateAlias) - || (contentItem.PersistedContent.Template != null && contentItem.TemplateAlias.IsNullOrWhiteSpace()); - if (templateChanged) - { - var template = Services.FileService.GetTemplate(contentItem.TemplateAlias); - if (template == null && contentItem.TemplateAlias.IsNullOrWhiteSpace() == false) - { - //ModelState.AddModelError("Template", "No template exists with the specified alias: " + contentItem.TemplateAlias); - LogHelper.Warn("No template exists with the specified alias: " + contentItem.TemplateAlias); - } - else - { - //NOTE: this could be null if there was a template and the posted template is null, this should remove the assigned template - contentItem.PersistedContent.Template = template; - } - } - - base.MapPropertyValues(contentItem); - } + /// /// Publishes a document with a given ID @@ -361,31 +349,12 @@ namespace Umbraco.Web.Editors return HandleContentNotFound(id, false); } - var publishResult = Services.ContentService.PublishWithStatus(foundContent, UmbracoUser.Id); + var publishResult = Services.ContentService.PublishWithStatus(foundContent, Security.GetUserId()); if (publishResult.Success == false) { - switch (publishResult.Result.StatusType) - { - case PublishStatusType.FailedPathNotPublished: - return Request.CreateValidationErrorResponse( - ui.Text("publish", "contentPublishedFailedByParent", - string.Format("{0} ({1})", publishResult.Result.ContentItem.Name, publishResult.Result.ContentItem.Id), - Security.CurrentUser).Trim()); - case PublishStatusType.FailedCancelledByEvent: - return Request.CreateValidationErrorResponse( - ui.Text("speechBubbles", "contentPublishedFailedByEvent")); - case PublishStatusType.FailedHasExpired: - case PublishStatusType.FailedAwaitingRelease: - case PublishStatusType.FailedIsTrashed: - case PublishStatusType.FailedContentInvalid: - return Request.CreateValidationErrorResponse( - ui.Text("publish", "contentPublishedFailedInvalid", - new[] - { - string.Format("{0} ({1})", publishResult.Result.ContentItem.Name, publishResult.Result.ContentItem.Id), - string.Join(",", publishResult.Result.InvalidProperties.Select(x => x.Alias)) - }, Security.CurrentUser)); - } + var notificationModel = new SimpleNotificationModel(); + ShowMessageForPublishStatus(publishResult.Result, notificationModel); + return Request.CreateValidationErrorResponse(notificationModel); } //return ok @@ -417,11 +386,23 @@ namespace Umbraco.Web.Editors //if the current item is in the recycle bin if (foundContent.IsInRecycleBin() == false) { - Services.ContentService.MoveToRecycleBin(foundContent, UmbracoUser.Id); + var moveResult = Services.ContentService.WithResult().MoveToRecycleBin(foundContent, Security.GetUserId()); + if (moveResult == false) + { + //returning an object of INotificationModel will ensure that any pending + // notification messages are added to the response. + return Request.CreateValidationErrorResponse(new SimpleNotificationModel()); + } } else { - Services.ContentService.Delete(foundContent, UmbracoUser.Id); + var deleteResult = Services.ContentService.WithResult().Delete(foundContent, Security.GetUserId()); + if (deleteResult == false) + { + //returning an object of INotificationModel will ensure that any pending + // notification messages are added to the response. + return Request.CreateValidationErrorResponse(new SimpleNotificationModel()); + } } return Request.CreateResponse(HttpStatusCode.OK); @@ -510,7 +491,7 @@ namespace Umbraco.Web.Editors { var toCopy = ValidateMoveOrCopy(copy); - var c = Services.ContentService.Copy(toCopy, copy.ParentId, copy.RelateToOriginal); + var c = Services.ContentService.Copy(toCopy, copy.ParentId, copy.RelateToOriginal, copy.Recursive); var response = Request.CreateResponse(HttpStatusCode.OK); response.Content = new StringContent(c.Path, Encoding.UTF8, "application/json"); @@ -529,13 +510,73 @@ namespace Umbraco.Web.Editors if (foundContent == null) HandleContentNotFound(id); + + var unpublishResult = Services.ContentService.WithResult().UnPublish(foundContent, Security.CurrentUser.Id); - Services.ContentService.UnPublish(foundContent); var content = Mapper.Map(foundContent); - content.AddSuccessNotification(ui.Text("content", "unPublish"), ui.Text("speechBubbles", "contentUnpublished")); + if (unpublishResult == false) + { + AddCancelMessage(content); + throw new HttpResponseException(Request.CreateValidationErrorResponse(content)); + } + else + { + content.AddSuccessNotification(Services.TextService.Localize("content/unPublish"), Services.TextService.Localize("speechBubbles/contentUnpublished")); + return content; + } + } - return content; + /// + /// Checks if the user is currently in preview mode and if so will update the preview content for this item + /// + /// + private void UpdatePreviewContext(int contentId) + { + var previewId = Request.GetPreviewCookieValue(); + if (previewId.IsNullOrWhiteSpace()) return; + Guid id; + if (Guid.TryParse(previewId, out id)) + { + var d = new Document(contentId); + var pc = new PreviewContent(UmbracoUser, id, false); + pc.PrepareDocument(UmbracoUser, d, true); + pc.SavePreviewSet(); + } + } + + /// + /// Maps the dto property values to the persisted model + /// + /// + private void MapPropertyValues(ContentItemSave contentItem) + { + UpdateName(contentItem); + + //TODO: We need to support 'send to publish' + + contentItem.PersistedContent.ExpireDate = contentItem.ExpireDate; + contentItem.PersistedContent.ReleaseDate = contentItem.ReleaseDate; + //only set the template if it didn't change + var templateChanged = (contentItem.PersistedContent.Template == null && contentItem.TemplateAlias.IsNullOrWhiteSpace() == false) + || (contentItem.PersistedContent.Template != null && contentItem.PersistedContent.Template.Alias != contentItem.TemplateAlias) + || (contentItem.PersistedContent.Template != null && contentItem.TemplateAlias.IsNullOrWhiteSpace()); + if (templateChanged) + { + var template = Services.FileService.GetTemplate(contentItem.TemplateAlias); + if (template == null && contentItem.TemplateAlias.IsNullOrWhiteSpace() == false) + { + //ModelState.AddModelError("Template", "No template exists with the specified alias: " + contentItem.TemplateAlias); + LogHelper.Warn("No template exists with the specified alias: " + contentItem.TemplateAlias); + } + else + { + //NOTE: this could be null if there was a template and the posted template is null, this should remove the assigned template + contentItem.PersistedContent.Template = template; + } + } + + base.MapPropertyValues(contentItem); } /// @@ -562,7 +603,8 @@ namespace Umbraco.Web.Editors if (toMove.ContentType.AllowedAsRoot == false) { throw new HttpResponseException( - Request.CreateValidationErrorResponse(ui.Text("moveOrCopy", "notAllowedAtRoot", Security.CurrentUser))); + Request.CreateNotificationValidationErrorResponse( + Services.TextService.Localize("moveOrCopy/notAllowedAtRoot"))); } } else @@ -578,51 +620,46 @@ namespace Umbraco.Web.Editors .Any(x => x.Value == toMove.ContentType.Id) == false) { throw new HttpResponseException( - Request.CreateValidationErrorResponse(ui.Text("moveOrCopy", "notAllowedByContentType", Security.CurrentUser))); + Request.CreateNotificationValidationErrorResponse( + Services.TextService.Localize("moveOrCopy/notAllowedByContentType"))); } // Check on paths if ((string.Format(",{0},", parent.Path)).IndexOf(string.Format(",{0},", toMove.Id), StringComparison.Ordinal) > -1) - { + { throw new HttpResponseException( - Request.CreateValidationErrorResponse(ui.Text("moveOrCopy", "notAllowedByPath", Security.CurrentUser))); + Request.CreateNotificationValidationErrorResponse( + Services.TextService.Localize("moveOrCopy/notAllowedByPath"))); } } return toMove; } - private void ShowMessageForPublishStatus(PublishStatus status, ContentItemDisplay display) + private void ShowMessageForPublishStatus(PublishStatus status, INotificationModel display) { switch (status.StatusType) { case PublishStatusType.Success: case PublishStatusType.SuccessAlreadyPublished: display.AddSuccessNotification( - ui.Text("speechBubbles", "editContentPublishedHeader", UmbracoUser), - ui.Text("speechBubbles", "editContentPublishedText", UmbracoUser)); + Services.TextService.Localize("speechBubbles/editContentPublishedHeader"), + Services.TextService.Localize("speechBubbles/editContentPublishedText")); break; case PublishStatusType.FailedPathNotPublished: display.AddWarningNotification( - ui.Text("publish"), - ui.Text("publish", "contentPublishedFailedByParent", - string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id), - UmbracoUser).Trim()); + Services.TextService.Localize("publish"), + Services.TextService.Localize("publish/contentPublishedFailedByParent", + new[] {string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id)}).Trim()); break; case PublishStatusType.FailedCancelledByEvent: - display.AddWarningNotification( - ui.Text("publish"), - ui.Text("speechBubbles", "contentPublishedFailedByEvent")); + AddCancelMessage(display, "publish", "speechBubbles/contentPublishedFailedByEvent"); break; case PublishStatusType.FailedAwaitingRelease: display.AddWarningNotification( - ui.Text("publish"), - ui.Text("publish", "contentPublishedFailedAwaitingRelease", - new[] - { - string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id) - }, - UmbracoUser).Trim()); + Services.TextService.Localize("publish"), + Services.TextService.Localize("publish/contentPublishedFailedAwaitingRelease", + new[] {string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id)}).Trim()); break; case PublishStatusType.FailedHasExpired: //TODO: We should add proper error messaging for this! @@ -630,14 +667,13 @@ namespace Umbraco.Web.Editors //TODO: We should add proper error messaging for this! case PublishStatusType.FailedContentInvalid: display.AddWarningNotification( - ui.Text("publish"), - ui.Text("publish", "contentPublishedFailedInvalid", - new[] - { - string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id), - string.Join(",", status.InvalidProperties.Select(x => x.Alias)) - }, - UmbracoUser).Trim()); + Services.TextService.Localize("publish"), + Services.TextService.Localize("publish/contentPublishedFailedInvalid", + new[] + { + string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id), + string.Join(",", status.InvalidProperties.Select(x => x.Alias)) + }).Trim()); break; default: throw new IndexOutOfRangeException(); diff --git a/src/Umbraco.Web/Editors/ContentControllerBase.cs b/src/Umbraco.Web/Editors/ContentControllerBase.cs index 5f02819f4d..bf9f2056b3 100644 --- a/src/Umbraco.Web/Editors/ContentControllerBase.cs +++ b/src/Umbraco.Web/Editors/ContentControllerBase.cs @@ -13,7 +13,7 @@ using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.WebApi; using Umbraco.Web.WebApi.Filters; -using Umbraco.Core.Models; + namespace Umbraco.Web.Editors { @@ -21,7 +21,7 @@ namespace Umbraco.Web.Editors /// An abstract base controller used for media/content (and probably members) to try to reduce code replication. /// [OutgoingDateTimeFormat] - public abstract class ContentControllerBase : UmbracoAuthorizedJsonController + public abstract class ContentControllerBase : BackOfficeNotificationsController { /// /// Constructor @@ -172,5 +172,19 @@ namespace Umbraco.Web.Editors return (action.ToString().EndsWith("New")); } + protected void AddCancelMessage(INotificationModel display, + string header = "speechBubbles/operationCancelledHeader", + string message = "speechBubbles/operationCancelledText", + bool localizeHeader = true, + bool localizeMessage = true) + { + //if there's already a default event message, don't add our default one + var msgs = UmbracoContext.GetCurrentEventMessages(); + if (msgs != null && msgs.GetAll().Any(x => x.IsDefaultEventMessage)) return; + + display.AddWarningNotification( + localizeHeader ? Services.TextService.Localize(header) : header, + localizeMessage ? Services.TextService.Localize(message): message); + } } } diff --git a/src/Umbraco.Web/Editors/ContentTypeController.cs b/src/Umbraco.Web/Editors/ContentTypeController.cs index 4c2ea733b6..aea6690bf2 100644 --- a/src/Umbraco.Web/Editors/ContentTypeController.cs +++ b/src/Umbraco.Web/Editors/ContentTypeController.cs @@ -13,28 +13,32 @@ using System.Linq; using Umbraco.Web.WebApi.Filters; using Constants = Umbraco.Core.Constants; using Newtonsoft.Json; +using Umbraco.Core.PropertyEditors; +using System; +using System.Net.Http; +using Umbraco.Core.Services; namespace Umbraco.Web.Editors { - //TODO: We'll need to be careful about the security on this controller, when we start implementing // methods to modify content types we'll need to enforce security on the individual methods, we - // cannot put security on the whole controller because things like GetAllowedChildren are required for content editing. + // cannot put security on the whole controller because things like + // GetAllowedChildren, GetPropertyTypeScaffold, GetAllPropertyTypeAliases are required for content editing. /// /// An API controller used for dealing with content types /// [PluginController("UmbracoApi")] + [UmbracoTreeAuthorize(Constants.Trees.DocumentTypes)] + [EnableOverrideAuthorization] public class ContentTypeController : ContentTypeControllerBase { - private ICultureDictionary _cultureDictionary; - /// /// Constructor /// public ContentTypeController() : this(UmbracoContext.Current) - { + { } /// @@ -47,19 +51,172 @@ namespace Umbraco.Web.Editors } + public ContentTypeDisplay GetById(int id) + { + var ct = Services.ContentTypeService.GetContentType(id); + if (ct == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + var dto = Mapper.Map(ct); + return dto; + } + + /// + /// Deletes a document type wth a given ID + /// + /// + /// + [HttpDelete] + [HttpPost] + public HttpResponseMessage DeleteById(int id) + { + var foundType = Services.ContentTypeService.GetContentType(id); + if (foundType == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + Services.ContentTypeService.Delete(foundType, Security.CurrentUser.Id); + return Request.CreateResponse(HttpStatusCode.OK); + } + /// /// Gets all user defined properties. /// /// + [UmbracoTreeAuthorize( + Constants.Trees.DocumentTypes, Constants.Trees.Content, + Constants.Trees.MediaTypes, Constants.Trees.Media, + Constants.Trees.MemberTypes, Constants.Trees.Members)] public IEnumerable GetAllPropertyTypeAliases() { return ApplicationContext.Services.ContentTypeService.GetAllPropertyTypeAliases(); } + public IEnumerable GetAvailableCompositeContentTypes(int contentTypeId) + { + return PerformGetAvailableCompositeContentTypes(contentTypeId, UmbracoObjectTypes.DocumentType); + } + + [UmbracoTreeAuthorize( + Constants.Trees.DocumentTypes, Constants.Trees.Content, + Constants.Trees.MediaTypes, Constants.Trees.Media, + Constants.Trees.MemberTypes, Constants.Trees.Members)] + public ContentPropertyDisplay GetPropertyTypeScaffold(int id) + { + var dataTypeDiff = Services.DataTypeService.GetDataTypeDefinitionById(id); + + if (dataTypeDiff == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + var preVals = UmbracoContext.Current.Application.Services.DataTypeService.GetPreValuesCollectionByDataTypeId(id); + var editor = PropertyEditorResolver.Current.GetByAlias(dataTypeDiff.PropertyEditorAlias); + + return new ContentPropertyDisplay() + { + Editor = dataTypeDiff.PropertyEditorAlias, + Validation = new PropertyTypeValidation() { }, + View = editor.ValueEditor.View, + Config = editor.PreValueEditor.ConvertDbToEditor(editor.DefaultPreValues, preVals) + }; + } + + /// + /// Deletes a document type container wth a given ID + /// + /// + /// + [HttpDelete] + [HttpPost] + public HttpResponseMessage DeleteContainer(int id) + { + Services.ContentTypeService.DeleteContentTypeContainer(id, Security.CurrentUser.Id); + + return Request.CreateResponse(HttpStatusCode.OK); + } + + public HttpResponseMessage PostCreateContainer(int parentId, string name) + { + var result = Services.ContentTypeService.CreateContentTypeContainer(parentId, name, Security.CurrentUser.Id); + + return result + ? Request.CreateResponse(HttpStatusCode.OK, result.Result) //return the id + : Request.CreateNotificationValidationErrorResponse(result.Exception.Message); + } + + public ContentTypeDisplay PostSave(ContentTypeSave contentTypeSave) + { + var savedCt = PerformPostSave( + contentTypeSave: contentTypeSave, + getContentType: i => Services.ContentTypeService.GetContentType(i), + saveContentType: type => Services.ContentTypeService.Save(type), + beforeCreateNew: ctSave => + { + //create a default template if it doesnt exist -but only if default template is == to the content type + //TODO: Is this really what we want? What if we don't want any template assigned at all ? + if (ctSave.DefaultTemplate.IsNullOrWhiteSpace() == false && ctSave.DefaultTemplate == ctSave.Alias) + { + var template = Services.FileService.GetTemplate(ctSave.Alias); + if (template == null) + { + template = new Template(ctSave.Name, ctSave.Alias); + Services.FileService.SaveTemplate(template); + } + + //make sure the template alias is set on the default and allowed template so we can map it back + ctSave.DefaultTemplate = template.Alias; + } + }); + + var display = Mapper.Map(savedCt); + + display.AddSuccessNotification( + Services.TextService.Localize("speechBubbles/contentTypeSavedHeader"), + string.Empty); + + return display; + } + + /// + /// Returns an empty content type for use as a scaffold when creating a new type + /// + /// + /// + public ContentTypeDisplay GetEmpty(int parentId) + { + var ct = new ContentType(parentId); + ct.Icon = "icon-document"; + + var dto = Mapper.Map(ct); + return dto; + } + + + /// + /// Returns all content type objects + /// + public IEnumerable GetAll() + { + var types = Services.ContentTypeService.GetAllContentTypes(); + var basics = types.Select(Mapper.Map); + + return basics.Select(basic => + { + basic.Name = TranslateItem(basic.Name); + basic.Description = TranslateItem(basic.Description); + return basic; + }); + } + /// /// Returns the allowed child content type objects for the content item id passed in /// /// + [UmbracoTreeAuthorize(Constants.Trees.DocumentTypes, Constants.Trees.Content)] public IEnumerable GetAllowedChildren(int contentId) { if (contentId == Constants.System.RecycleBinContent) @@ -71,7 +228,7 @@ namespace Umbraco.Web.Editors types = Services.ContentTypeService.GetAllContentTypes().ToList(); //if no allowed root types are set, just return everything - if(types.Any(x => x.AllowedAsRoot)) + if (types.Any(x => x.AllowedAsRoot)) types = types.Where(x => x.AllowedAsRoot); } else @@ -79,11 +236,11 @@ namespace Umbraco.Web.Editors var contentItem = Services.ContentService.GetById(contentId); if (contentItem == null) { - throw new HttpResponseException(HttpStatusCode.NotFound); + return Enumerable.Empty(); } var ids = contentItem.ContentType.AllowedContentTypes.Select(x => x.Id.Value).ToArray(); - + if (ids.Any() == false) return Enumerable.Empty(); types = Services.ContentTypeService.GetAllContentTypes(ids).ToList(); @@ -100,29 +257,19 @@ namespace Umbraco.Web.Editors return basics; } - // TODO: This should really be centralized and used anywhere globalization applies. - internal string TranslateItem(string text) + /// + /// Move the media type + /// + /// + /// + public HttpResponseMessage PostMove(MoveOrCopy move) { - if (text == null) - { - return null; - } - - if (text.StartsWith("#") == false) - return text; - - text = text.Substring(1); - return CultureDictionary[text].IfNullOrWhiteSpace(text); + return PerformMove( + move, + getContentType: i => Services.ContentTypeService.GetContentType(i), + doMove: (type, i) => Services.ContentTypeService.MoveContentType(type, i)); } - private ICultureDictionary CultureDictionary - { - get - { - return - _cultureDictionary ?? - (_cultureDictionary = CultureDictionaryFactoryResolver.Current.Factory.CreateDictionary()); - } - } + } } \ No newline at end of file diff --git a/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs b/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs index 0c3b61d9eb..443b1dcbff 100644 --- a/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs +++ b/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs @@ -2,14 +2,20 @@ using System.Collections.Generic; using System.Linq; using System.Net; +using System.Net.Http; +using System.Text; using System.Text.RegularExpressions; using System.Web.Http; using AutoMapper; using Newtonsoft.Json; using Umbraco.Core; +using Umbraco.Core.Dictionary; using Umbraco.Core.Models; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Mvc; +using Umbraco.Web.WebApi; using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.Editors @@ -18,8 +24,11 @@ namespace Umbraco.Web.Editors /// Am abstract API controller providing functionality used for dealing with content and media types /// [PluginController("UmbracoApi")] + [PrefixlessBodyModelValidator] public abstract class ContentTypeControllerBase : UmbracoAuthorizedJsonController - { + { + private ICultureDictionary _cultureDictionary; + /// /// Constructor /// @@ -37,37 +46,323 @@ namespace Umbraco.Web.Editors { } - public DataTypeBasic GetAssignedListViewDataType(int contentTypeId) + /// + /// Returns the available composite content types for a given content type + /// + /// + protected IEnumerable PerformGetAvailableCompositeContentTypes(int contentTypeId, UmbracoObjectTypes type) { - var objectType = Services.EntityService.GetObjectType(contentTypeId); + IContentTypeComposition source = null; - switch (objectType) - { - case UmbracoObjectTypes.MemberType: - var memberType = Services.MemberTypeService.Get(contentTypeId); - var dtMember = Services.DataTypeService.GetDataTypeDefinitionByName(Constants.Conventions.DataTypes.ListViewPrefix + memberType.Alias); - return dtMember == null - ? Mapper.Map( - Services.DataTypeService.GetDataTypeDefinitionByName(Constants.Conventions.DataTypes.ListViewPrefix + "Member")) - : Mapper.Map(dtMember); - case UmbracoObjectTypes.MediaType: - var mediaType = Services.ContentTypeService.GetMediaType(contentTypeId); - var dtMedia = Services.DataTypeService.GetDataTypeDefinitionByName(Constants.Conventions.DataTypes.ListViewPrefix + mediaType.Alias); - return dtMedia == null - ? Mapper.Map( - Services.DataTypeService.GetDataTypeDefinitionByName(Constants.Conventions.DataTypes.ListViewPrefix + "Media")) - : Mapper.Map(dtMedia); + //below is all ported from the old doc type editor and comes with the same weaknesses /insanity / magic + + IContentTypeComposition[] allContentTypes; + + switch (type) + { case UmbracoObjectTypes.DocumentType: - var docType = Services.ContentTypeService.GetContentType(contentTypeId); - var dtDoc = Services.DataTypeService.GetDataTypeDefinitionByName(Constants.Conventions.DataTypes.ListViewPrefix + docType.Alias); - return dtDoc == null - ? Mapper.Map( - Services.DataTypeService.GetDataTypeDefinitionByName(Constants.Conventions.DataTypes.ListViewPrefix + "Content")) - : Mapper.Map(dtDoc); + if (contentTypeId > 0) + { + source = Services.ContentTypeService.GetContentType(contentTypeId); + if (source == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); + } + allContentTypes = Services.ContentTypeService.GetAllContentTypes().Cast().ToArray(); + break; + + case UmbracoObjectTypes.MediaType: + if (contentTypeId > 0) + { + source = Services.ContentTypeService.GetMediaType(contentTypeId); + if (source == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); + } + allContentTypes = Services.ContentTypeService.GetAllMediaTypes().Cast().ToArray(); + break; + + case UmbracoObjectTypes.MemberType: + if (contentTypeId > 0) + { + source = Services.MemberTypeService.Get(contentTypeId); + if (source == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); + } + allContentTypes = Services.MemberTypeService.GetAll().Cast().ToArray(); + break; + + default: + throw new ArgumentOutOfRangeException("The entity type was not a content type"); + } + + // note: there are many sanity checks missing here and there ;-(( + // make sure once and for all + //if (allContentTypes.Any(x => x.ParentId > 0 && x.ContentTypeComposition.Any(y => y.Id == x.ParentId) == false)) + // throw new Exception("A parent does not belong to a composition."); + + // find out if any content type uses this content type + var isUsing = allContentTypes.Where(x => x.ContentTypeComposition.Any(y => y.Id == contentTypeId)).ToArray(); + if (isUsing.Length > 0) + { + //if already in use a composition, do not allow any composited types + return new List(); + } + + // if it is not used then composition is possible + // hashset guarantees unicity on Id + var list = new HashSet(new DelegateEqualityComparer( + (x, y) => x.Id == y.Id, + x => x.Id)); + + // usable types are those that are top-level + var usableContentTypes = allContentTypes + .Where(x => x.ContentTypeComposition.Any() == false).ToArray(); + foreach (var x in usableContentTypes) + list.Add(x); + + // indirect types are those that we use, directly or indirectly + var indirectContentTypes = GetIndirect(source).ToArray(); + foreach (var x in indirectContentTypes) + list.Add(x); + + //// directContentTypes are those we use directly + //// they are already in indirectContentTypes, no need to add to the list + //var directContentTypes = source.ContentTypeComposition.ToArray(); + + //var enabled = usableContentTypes.Select(x => x.Id) // those we can use + // .Except(indirectContentTypes.Select(x => x.Id)) // except those that are indirectly used + // .Union(directContentTypes.Select(x => x.Id)) // but those that are directly used + // .Where(x => x != source.ParentId) // but not the parent + // .Distinct() + // .ToArray(); + + return list + .Where(x => x.Id != contentTypeId) + .OrderBy(x => x.Name) + .Select(Mapper.Map) + .Select(x => + { + x.Name = TranslateItem(x.Name); + return x; + }) + .ToList(); + } + + private static IEnumerable GetIndirect(IContentTypeComposition ctype) + { + // hashset guarantees unicity on Id + var all = new HashSet(new DelegateEqualityComparer( + (x, y) => x.Id == y.Id, + x => x.Id)); + + var stack = new Stack(); + + if (ctype != null) + { + foreach (var x in ctype.ContentTypeComposition) + stack.Push(x); + } + + while (stack.Count > 0) + { + var x = stack.Pop(); + all.Add(x); + foreach (var y in x.ContentTypeComposition) + stack.Push(y); + } + + return all; + } + + /// + /// Validates the composition and adds errors to the model state if any are found then throws an error response if there are errors + /// + /// + /// + /// + protected void ValidateComposition(ContentTypeSave contentTypeSave, IContentTypeComposition composition) + { + var validateAttempt = Services.ContentTypeService.ValidateComposition(composition); + if (validateAttempt == false) + { + //if it's not successful then we need to return some model state for the property aliases that + // are duplicated + var propertyAliases = validateAttempt.Result.Distinct(); + foreach (var propertyAlias in propertyAliases) + { + //find the property relating to these + var prop = contentTypeSave.Groups.SelectMany(x => x.Properties).Single(x => x.Alias == propertyAlias); + var group = contentTypeSave.Groups.Single(x => x.Properties.Contains(prop)); + var propIndex = group.Properties.IndexOf(prop); + var groupIndex = contentTypeSave.Groups.IndexOf(group); + + var key = string.Format("Groups[{0}].Properties[{1}].Alias", groupIndex, propIndex); + ModelState.AddModelError(key, "Duplicate property aliases not allowed between compositions"); + } + + var display = Mapper.Map(composition); + //map the 'save' data on top + display = Mapper.Map(contentTypeSave, display); + display.Errors = ModelState.ToErrorDictionary(); + throw new HttpResponseException(Request.CreateValidationErrorResponse(display)); + } + + } + + protected string TranslateItem(string text) + { + if (text == null) + { + return null; + } + + if (text.StartsWith("#") == false) + return text; + + text = text.Substring(1); + return CultureDictionary[text].IfNullOrWhiteSpace(text); + } + + protected TContentType PerformPostSave( + ContentTypeSave contentTypeSave, + Func getContentType, + Action saveContentType, + bool validateComposition = true, + Action beforeCreateNew = null) + where TContentType : IContentTypeComposition + where TContentTypeDisplay : ContentTypeCompositionDisplay + { + var ctId = Convert.ToInt32(contentTypeSave.Id); + + if (ModelState.IsValid == false) + { + var ct = getContentType(ctId); + //Required data is invalid so we cannot continue + var forDisplay = Mapper.Map(ct); + //map the 'save' data on top + forDisplay = Mapper.Map(contentTypeSave, forDisplay); + forDisplay.Errors = ModelState.ToErrorDictionary(); + throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay)); + } + + //filter out empty properties + contentTypeSave.Groups = contentTypeSave.Groups.Where(x => x.Name.IsNullOrWhiteSpace() == false).ToList(); + foreach (var group in contentTypeSave.Groups) + { + group.Properties = group.Properties.Where(x => x.Alias.IsNullOrWhiteSpace() == false).ToList(); + } + + if (ctId > 0) + { + //its an update to an existing + var found = getContentType(ctId); + if (found == null) + throw new HttpResponseException(HttpStatusCode.NotFound); + + Mapper.Map(contentTypeSave, found); + + if (validateComposition) + { + //NOTE: this throws an error response if it is not valid + ValidateComposition(contentTypeSave, found); + } + + saveContentType(found); + + return found; + } + else + { + if (beforeCreateNew != null) + { + beforeCreateNew(contentTypeSave); + } + + //set id to null to ensure its handled as a new type + contentTypeSave.Id = null; + contentTypeSave.CreateDate = DateTime.Now; + contentTypeSave.UpdateDate = DateTime.Now; + + //check if the type is trying to allow type 0 below itself - id zero refers to the currently unsaved type + //always filter these 0 types out + var allowItselfAsChild = false; + if (contentTypeSave.AllowedContentTypes != null) + { + allowItselfAsChild = contentTypeSave.AllowedContentTypes.Any(x => x == 0); + contentTypeSave.AllowedContentTypes = contentTypeSave.AllowedContentTypes.Where(x => x > 0).ToList(); + } + + //save as new + var newCt = Mapper.Map(contentTypeSave); + + if (validateComposition) + { + //NOTE: this throws an error response if it is not valid + ValidateComposition(contentTypeSave, newCt); + } + + saveContentType(newCt); + + //we need to save it twice to allow itself under itself. + if (allowItselfAsChild) + { + //NOTE: This will throw if the composition isn't right... but it shouldn't be at this stage + newCt.AddContentType(newCt); + saveContentType(newCt); + } + return newCt; + } + } + + /// + /// Change the sort order for media + /// + /// + /// + /// + /// + protected HttpResponseMessage PerformMove( + MoveOrCopy move, + Func getContentType, + Func>> doMove) + where TContentType : IContentTypeComposition + { + var toMove = getContentType(move.Id); + if (toMove == null) + { + return Request.CreateResponse(HttpStatusCode.NotFound); + } + + var result = doMove(toMove, move.ParentId); + if (result.Success) + { + var response = Request.CreateResponse(HttpStatusCode.OK); + response.Content = new StringContent(toMove.Path, Encoding.UTF8, "application/json"); + return response; + } + + switch (result.Result.StatusType) + { + case MoveOperationStatusType.FailedParentNotFound: + return Request.CreateResponse(HttpStatusCode.NotFound); + case MoveOperationStatusType.FailedCancelledByEvent: + //returning an object of INotificationModel will ensure that any pending + // notification messages are added to the response. + return Request.CreateValidationErrorResponse(new SimpleNotificationModel()); + case MoveOperationStatusType.FailedNotAllowedByPath: + var notificationModel = new SimpleNotificationModel(); + notificationModel.AddErrorNotification(Services.TextService.Localize("moveOrCopy/notAllowedByPath"), ""); + return Request.CreateValidationErrorResponse(notificationModel); default: throw new ArgumentOutOfRangeException(); } + } + + private ICultureDictionary CultureDictionary + { + get + { + return + _cultureDictionary ?? + (_cultureDictionary = CultureDictionaryFactoryResolver.Current.Factory.CreateDictionary()); + } } - + } } \ No newline at end of file diff --git a/src/Umbraco.Web/Editors/DataTypeController.cs b/src/Umbraco.Web/Editors/DataTypeController.cs index 67b5619e3c..c2d7ec37d2 100644 --- a/src/Umbraco.Web/Editors/DataTypeController.cs +++ b/src/Umbraco.Web/Editors/DataTypeController.cs @@ -19,6 +19,7 @@ using Umbraco.Web.WebApi.Filters; using umbraco; using Constants = Umbraco.Core.Constants; using System.Net.Http; +using System.Text; namespace Umbraco.Web.Editors { @@ -26,14 +27,19 @@ namespace Umbraco.Web.Editors /// The API controller used for editing data types /// /// - /// This controller is decorated with the UmbracoApplicationAuthorizeAttribute which means that any user requesting - /// access to ALL of the methods on this controller will need access to the developer application. + /// The security for this controller is defined to allow full CRUD access to data types if the user has access to either: + /// Content Types, Member Types or Media Types ... and of course to Data Types /// [PluginController("UmbracoApi")] - [UmbracoTreeAuthorize(Constants.Trees.DataTypes)] + [UmbracoTreeAuthorize(Constants.Trees.DataTypes, Constants.Trees.DocumentTypes, Constants.Trees.MediaTypes, Constants.Trees.MemberTypes)] [EnableOverrideAuthorization] public class DataTypeController : UmbracoAuthorizedJsonController { + /// + /// Gets data type by name + /// + /// + /// public DataTypeDisplay GetByName(string name) { var dataType = Services.DataTypeService.GetDataTypeDefinitionByName(name); @@ -41,14 +47,10 @@ namespace Umbraco.Web.Editors } /// - /// Gets the content json for the content id + /// Gets the datatype json for the datatype id /// /// /// - /// - /// Permission is granted to this method if the user has access to any of these trees: DataTypes, Content or Media - /// - [UmbracoTreeAuthorize(Constants.Trees.DataTypes, Constants.Trees.Content, Constants.Trees.Media)] public DataTypeDisplay GetById(int id) { var dataType = Services.DataTypeService.GetDataTypeDefinitionById(id); @@ -74,17 +76,53 @@ namespace Umbraco.Web.Editors throw new HttpResponseException(HttpStatusCode.NotFound); } - Services.DataTypeService.Delete(foundType, UmbracoUser.Id); + Services.DataTypeService.Delete(foundType, Security.CurrentUser.Id); return Request.CreateResponse(HttpStatusCode.OK); } - public DataTypeDisplay GetEmpty() + public DataTypeDisplay GetEmpty(int parentId) { - var dt = new DataTypeDefinition(-1, ""); + var dt = new DataTypeDefinition(parentId, ""); return Mapper.Map(dt); } + /// + /// Returns a custom listview, based on a content type alias, if found + /// + /// + /// a DataTypeDisplay + public DataTypeDisplay GetCustomListView(string contentTypeAlias) + { + var dt = Services.DataTypeService.GetDataTypeDefinitionByName(Constants.Conventions.DataTypes.ListViewPrefix + contentTypeAlias); + if (dt == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + return Mapper.Map(dt); + } + + /// + /// Creates a custom list view - give a document type alias + /// + /// + /// + public DataTypeDisplay PostCreateCustomListView(string contentTypeAlias) + { + var dt = Services.DataTypeService.GetDataTypeDefinitionByName(Constants.Conventions.DataTypes.ListViewPrefix + contentTypeAlias); + + //if it doesnt exist yet, we will create it. + if (dt == null) + { + dt = new DataTypeDefinition( Constants.PropertyEditors.ListViewAlias ); + dt.Name = Constants.Conventions.DataTypes.ListViewPrefix + contentTypeAlias; + Services.DataTypeService.Save(dt); + } + + return Mapper.Map(dt); + } + /// /// Returns the pre-values for the specified property editor /// @@ -125,7 +163,28 @@ namespace Umbraco.Web.Editors return Mapper.Map>(propEd); } - //TODO: Generally there probably won't be file uploads for pre-values but we should allow them just like we do for the content editor + /// + /// Deletes a data type container wth a given ID + /// + /// + /// + [HttpDelete] + [HttpPost] + public HttpResponseMessage DeleteContainer(int id) + { + Services.DataTypeService.DeleteContainer(id, Security.CurrentUser.Id); + + return Request.CreateResponse(HttpStatusCode.OK); + } + + public HttpResponseMessage PostCreateContainer(int parentId, string name) + { + var result = Services.DataTypeService.CreateContainer(parentId, name, Security.CurrentUser.Id); + + return result + ? Request.CreateResponse(HttpStatusCode.OK, result.Result) //return the id + : Request.CreateNotificationValidationErrorResponse(result.Exception.Message); + } /// /// Saves the data type @@ -169,5 +228,141 @@ namespace Umbraco.Web.Editors //now return the updated model return display; } + + /// + /// Move the media type + /// + /// + /// + public HttpResponseMessage PostMove(MoveOrCopy move) + { + var toMove = Services.DataTypeService.GetDataTypeDefinitionById(move.Id); + if (toMove == null) + { + return Request.CreateResponse(HttpStatusCode.NotFound); + } + + var result = Services.DataTypeService.Move(toMove, move.ParentId); + if (result.Success) + { + var response = Request.CreateResponse(HttpStatusCode.OK); + response.Content = new StringContent(toMove.Path, Encoding.UTF8, "application/json"); + return response; + } + + switch (result.Result.StatusType) + { + case MoveOperationStatusType.FailedParentNotFound: + return Request.CreateResponse(HttpStatusCode.NotFound); + case MoveOperationStatusType.FailedCancelledByEvent: + //returning an object of INotificationModel will ensure that any pending + // notification messages are added to the response. + return Request.CreateValidationErrorResponse(new SimpleNotificationModel()); + case MoveOperationStatusType.FailedNotAllowedByPath: + var notificationModel = new SimpleNotificationModel(); + notificationModel.AddErrorNotification(Services.TextService.Localize("moveOrCopy/notAllowedByPath"), ""); + return Request.CreateValidationErrorResponse(notificationModel); + default: + throw new ArgumentOutOfRangeException(); + } + } + + #region ReadOnly actions to return basic data - allow access for: content ,media, members, settings, developer + /// + /// Gets the content json for all data types + /// + /// + /// + /// Permission is granted to this method if the user has access to any of these sections: Content, media, settings, developer, members + /// + [UmbracoApplicationAuthorize( + Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members, + Constants.Applications.Settings, Constants.Applications.Developer)] + public IEnumerable GetAll() + { + return Services.DataTypeService + .GetAllDataTypeDefinitions() + .Select(Mapper.Map).Where(x => x.IsSystemDataType == false); + } + + /// + /// Returns all data types grouped by their property editor group + /// + /// + /// + /// Permission is granted to this method if the user has access to any of these sections: Content, media, settings, developer, members + /// + [UmbracoTreeAuthorize( + Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members, + Constants.Applications.Settings, Constants.Applications.Developer)] + public IDictionary> GetGroupedDataTypes() + { + var dataTypes = Services.DataTypeService + .GetAllDataTypeDefinitions() + .Select(Mapper.Map); + + var propertyEditors = PropertyEditorResolver.Current.PropertyEditors.ToArray(); + + foreach (var dataType in dataTypes) + { + var propertyEditor = propertyEditors.Single(x => x.Alias == dataType.Alias); + dataType.HasPrevalues = propertyEditor.PreValueEditor.Fields.Any(); ; + } + + var grouped = dataTypes + .GroupBy(x => x.Group.IsNullOrWhiteSpace() ? "" : x.Group.ToLower()) + .ToDictionary(group => group.Key, group => group.OrderBy(d => d.Name).AsEnumerable()); + + return grouped; + } + + /// + /// Returns all property editors grouped + /// + /// + /// + /// Permission is granted to this method if the user has access to any of these sections: Content, media, settings, developer, members + /// + [UmbracoTreeAuthorize( + Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members, + Constants.Applications.Settings, Constants.Applications.Developer)] + public IDictionary> GetGroupedPropertyEditors() + { + var datatypes = new List(); + + var propertyEditors = PropertyEditorResolver.Current.PropertyEditors; + foreach (var propertyEditor in propertyEditors) + { + var hasPrevalues = propertyEditor.PreValueEditor.Fields.Any(); + var basic = Mapper.Map(propertyEditor); + basic.HasPrevalues = hasPrevalues; + datatypes.Add(basic); + } + + var grouped = datatypes + .GroupBy(x => x.Group.IsNullOrWhiteSpace() ? "" : x.Group.ToLower()) + .ToDictionary(group => group.Key, group => group.OrderBy(d => d.Name).AsEnumerable()); + + return grouped; + } + + + /// + /// Gets all property editors defined + /// + /// + /// + /// Permission is granted to this method if the user has access to any of these sections: Content, media, settings, developer, members + /// + [UmbracoTreeAuthorize( + Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members, + Constants.Applications.Settings, Constants.Applications.Developer)] + public IEnumerable GetAllPropertyEditors() + { + return PropertyEditorResolver.Current.PropertyEditors + .OrderBy(x => x.Name) + .Select(Mapper.Map); + } + #endregion } } \ No newline at end of file diff --git a/src/Umbraco.Web/Editors/DataTypeValidateAttribute.cs b/src/Umbraco.Web/Editors/DataTypeValidateAttribute.cs index 6757082a49..ddf694f630 100644 --- a/src/Umbraco.Web/Editors/DataTypeValidateAttribute.cs +++ b/src/Umbraco.Web/Editors/DataTypeValidateAttribute.cs @@ -42,6 +42,9 @@ namespace Umbraco.Web.Editors { var dataType = (DataTypeSave)actionContext.ActionArguments["dataType"]; + dataType.Name = dataType.Name.CleanForXss('[', ']', '(', ')'); + dataType.Alias = dataType.Name.CleanForXss('[', ']', '(', ')'); + //Validate that the property editor exists var propertyEditor = PropertyEditorResolver.Current.GetByAlias(dataType.SelectedEditor); if (propertyEditor == null) diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index 90b15a73c7..596e27e3a5 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -42,6 +42,23 @@ namespace Umbraco.Web.Editors [PluginController("UmbracoApi")] public class EntityController : UmbracoAuthorizedJsonController { + /// + /// Returns an Umbraco alias given a string + /// + /// + /// + /// + public dynamic GetSafeAlias(string value, bool camelCase = true) + { + var returnValue = (string.IsNullOrWhiteSpace(value)) ? string.Empty : value.ToSafeAlias(camelCase); + dynamic returnObj = new System.Dynamic.ExpandoObject(); + returnObj.alias = returnValue; + returnObj.original = value; + returnObj.camelCase = camelCase; + + return returnObj; + } + /// /// Searches for results based on the entity type /// @@ -535,6 +552,11 @@ namespace Umbraco.Web.Editors //now we need to convert the unknown ones switch (entityType) { + case UmbracoEntityTypes.Template: + var templates = Services.FileService.GetTemplates(); + var filteredTemplates = ExecutePostFilter(templates, postFilter, postFilterParams); + return filteredTemplates.Select(Mapper.Map); + case UmbracoEntityTypes.Macro: //Get all macros from the macro service var macros = Services.MacroService.GetAll().WhereNotNull().OrderBy(x => x.Name); @@ -717,8 +739,6 @@ namespace Umbraco.Web.Editors return UmbracoObjectTypes.Media; case UmbracoEntityTypes.MemberType: return UmbracoObjectTypes.MediaType; - case UmbracoEntityTypes.Template: - return UmbracoObjectTypes.Template; case UmbracoEntityTypes.MemberGroup: return UmbracoObjectTypes.MemberGroup; case UmbracoEntityTypes.ContentItem: diff --git a/src/Umbraco.Web/Editors/ImagesController.cs b/src/Umbraco.Web/Editors/ImagesController.cs index 54938be76d..f69f84ca86 100644 --- a/src/Umbraco.Web/Editors/ImagesController.cs +++ b/src/Umbraco.Web/Editors/ImagesController.cs @@ -1,175 +1,176 @@ -using System; -using System.Drawing; -using System.IO; -using System.Net; -using System.Net.Http; -using System.Net.Http.Headers; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.IO; -using Umbraco.Core.Media; -using Umbraco.Web.Mvc; -using Umbraco.Web.WebApi; -using Umbraco.Web.WebApi.Filters; -using Constants = Umbraco.Core.Constants; - -namespace Umbraco.Web.Editors -{ - /// - /// A controller used to return images for media - /// - [PluginController("UmbracoApi")] - public class ImagesController : UmbracoAuthorizedApiController - { - /// - /// Gets the big thumbnail image for the media id - /// - /// - /// - /// - /// If there is no media, image property or image file is found then this will return not found. - /// - public HttpResponseMessage GetBigThumbnail(int mediaId) - { - var media = Services.MediaService.GetById(mediaId); - if (media == null) - { - return Request.CreateResponse(HttpStatusCode.NotFound); - } - var imageProp = media.Properties[Constants.Conventions.Media.File]; - if (imageProp == null) - { - return Request.CreateResponse(HttpStatusCode.NotFound); - } - - var imagePath = imageProp.Value.ToString(); - - return GetBigThumbnail(imagePath); - } - - /// - /// Gets the big thumbnail image for the original image path - /// - /// - /// - /// - /// If there is no original image is found then this will return not found. - /// - public HttpResponseMessage GetBigThumbnail(string originalImagePath) - { - if (string.IsNullOrWhiteSpace(originalImagePath)) - return Request.CreateResponse(HttpStatusCode.OK); - - return GetResized(originalImagePath, 500, "big-thumb"); - } - - /// - /// Gets a resized image for the media id - /// - /// - /// - /// - /// - /// If there is no media, image property or image file is found then this will return not found. - /// - public HttpResponseMessage GetResized(int mediaId, int width) - { - var media = Services.MediaService.GetById(mediaId); - if (media == null) - { - return new HttpResponseMessage(HttpStatusCode.NotFound); - } - var imageProp = media.Properties[Constants.Conventions.Media.File]; - if (imageProp == null) - { - return new HttpResponseMessage(HttpStatusCode.NotFound); - } - - var imagePath = imageProp.Value.ToString(); - - return GetResized(imagePath, width); - } - - /// - /// Gets a resized image for the image at the given path - /// - /// - /// - /// - /// - /// If there is no media, image property or image file is found then this will return not found. - /// - public HttpResponseMessage GetResized(string imagePath, int width) - { - return GetResized(imagePath, width, Convert.ToString(width)); - } - - //TODO: We should delegate this to ImageProcessing - - /// - /// Gets a resized image - if the requested max width is greater than the original image, only the original image will be returned. - /// - /// - /// - /// - /// - private HttpResponseMessage GetResized(string imagePath, int width, string suffix) - { - var mediaFileSystem = FileSystemProviderManager.Current.GetFileSystemProvider(); - var ext = Path.GetExtension(imagePath); - - //we need to check if it is an image by extension - if (UmbracoConfig.For.UmbracoSettings().Content.ImageFileTypes.InvariantContains(ext.TrimStart('.')) == false) - { - return Request.CreateResponse(HttpStatusCode.NotFound); - } - - var thumbFilePath = imagePath.TrimEnd(ext) + "_" + suffix + ".jpg"; - var fullOrgPath = mediaFileSystem.GetFullPath(mediaFileSystem.GetRelativePath(imagePath)); - var fullNewPath = mediaFileSystem.GetFullPath(mediaFileSystem.GetRelativePath(thumbFilePath)); - var thumbIsNew = mediaFileSystem.FileExists(fullNewPath) == false; - if (thumbIsNew) - { - //we need to generate it - if (mediaFileSystem.FileExists(fullOrgPath) == false) - { - return Request.CreateResponse(HttpStatusCode.NotFound); - } - - using (var fileStream = mediaFileSystem.OpenFile(fullOrgPath)) - { - if (fileStream.CanSeek) fileStream.Seek(0, 0); - using (var originalImage = Image.FromStream(fileStream)) - { - //If it is bigger, then do the resize - if (originalImage.Width >= width && originalImage.Height >= width) - { - ImageHelper.GenerateThumbnail( - originalImage, - width, - fullNewPath, - "jpg", - mediaFileSystem); - } - else - { - //just return the original image - fullNewPath = fullOrgPath; - } - - } - } - } - - var result = Request.CreateResponse(HttpStatusCode.OK); - //NOTE: That we are not closing this stream as the framework will do that for us, if we try it will - // fail. See http://stackoverflow.com/questions/9541351/returning-binary-file-from-controller-in-asp-net-web-api - var stream = mediaFileSystem.OpenFile(fullNewPath); - if (stream.CanSeek) stream.Seek(0, 0); - result.Content = new StreamContent(stream); - result.Headers.Date = mediaFileSystem.GetLastModified(imagePath); - result.Content.Headers.ContentType = new MediaTypeHeaderValue("image/jpeg"); - return result; - } - } +using System; +using System.Drawing; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using Umbraco.Core; +using Umbraco.Core.Configuration; +using Umbraco.Core.IO; +using Umbraco.Core.Media; +using Umbraco.Web.Mvc; +using Umbraco.Web.WebApi; +using Umbraco.Web.WebApi.Filters; +using Constants = Umbraco.Core.Constants; + +namespace Umbraco.Web.Editors +{ + /// + /// A controller used to return images for media + /// + [PluginController("UmbracoApi")] + public class ImagesController : UmbracoAuthorizedApiController + { + /// + /// Gets the big thumbnail image for the media id + /// + /// + /// + /// + /// If there is no media, image property or image file is found then this will return not found. + /// + public HttpResponseMessage GetBigThumbnail(int mediaId) + { + var media = Services.MediaService.GetById(mediaId); + if (media == null) + { + return Request.CreateResponse(HttpStatusCode.NotFound); + } + var imageProp = media.Properties[Constants.Conventions.Media.File]; + if (imageProp == null) + { + return Request.CreateResponse(HttpStatusCode.NotFound); + } + + var imagePath = imageProp.Value.ToString(); + + return GetBigThumbnail(imagePath); + } + + /// + /// Gets the big thumbnail image for the original image path + /// + /// + /// + /// + /// If there is no original image is found then this will return not found. + /// + public HttpResponseMessage GetBigThumbnail(string originalImagePath) + { + if (string.IsNullOrWhiteSpace(originalImagePath)) + return Request.CreateResponse(HttpStatusCode.OK); + + return GetResized(originalImagePath, 500, "big-thumb"); + } + + /// + /// Gets a resized image for the media id + /// + /// + /// + /// + /// + /// If there is no media, image property or image file is found then this will return not found. + /// + public HttpResponseMessage GetResized(int mediaId, int width) + { + var media = Services.MediaService.GetById(mediaId); + if (media == null) + { + return new HttpResponseMessage(HttpStatusCode.NotFound); + } + var imageProp = media.Properties[Constants.Conventions.Media.File]; + if (imageProp == null) + { + return new HttpResponseMessage(HttpStatusCode.NotFound); + } + + var imagePath = imageProp.Value.ToString(); + + return GetResized(imagePath, width); + } + + /// + /// Gets a resized image for the image at the given path + /// + /// + /// + /// + /// + /// If there is no media, image property or image file is found then this will return not found. + /// + public HttpResponseMessage GetResized(string imagePath, int width) + { + return GetResized(imagePath, width, Convert.ToString(width)); + } + + //TODO: We should delegate this to ImageProcessing + + /// + /// Gets a resized image - if the requested max width is greater than the original image, only the original image will be returned. + /// + /// + /// + /// + /// + private HttpResponseMessage GetResized(string imagePath, int width, string suffix) + { + var mediaFileSystem = FileSystemProviderManager.Current.GetFileSystemProvider(); + var ext = Path.GetExtension(imagePath); + + //we need to check if it is an image by extension + if (UmbracoConfig.For.UmbracoSettings().Content.ImageFileTypes.InvariantContains(ext.TrimStart('.')) == false) + { + return Request.CreateResponse(HttpStatusCode.NotFound); + } + + var thumbFilePath = imagePath.TrimEnd(ext) + "_" + suffix + ext; + var fullOrgPath = mediaFileSystem.GetFullPath(mediaFileSystem.GetRelativePath(imagePath)); + var fullNewPath = mediaFileSystem.GetFullPath(mediaFileSystem.GetRelativePath(thumbFilePath)); + var thumbIsNew = mediaFileSystem.FileExists(fullNewPath) == false; + if (thumbIsNew) + { + //we need to generate it + if (mediaFileSystem.FileExists(fullOrgPath) == false) + { + return Request.CreateResponse(HttpStatusCode.NotFound); + } + + using (var fileStream = mediaFileSystem.OpenFile(fullOrgPath)) + { + if (fileStream.CanSeek) fileStream.Seek(0, 0); + using (var originalImage = Image.FromStream(fileStream)) + { + //If it is bigger, then do the resize + if (originalImage.Width >= width && originalImage.Height >= width) + { + ImageHelper.GenerateThumbnail( + originalImage, + width, + fullNewPath, + ext.Replace(".", ""), + mediaFileSystem); + } + else + { + //just return the original image + fullNewPath = fullOrgPath; + } + + } + } + } + + var result = Request.CreateResponse(HttpStatusCode.OK); + //NOTE: That we are not closing this stream as the framework will do that for us, if we try it will + // fail. See http://stackoverflow.com/questions/9541351/returning-binary-file-from-controller-in-asp-net-web-api + var stream = mediaFileSystem.OpenFile(fullNewPath); + if (stream.CanSeek) stream.Seek(0, 0); + result.Content = new StreamContent(stream); + result.Headers.Date = mediaFileSystem.GetLastModified(imagePath); + result.Content.Headers.ContentType = new MediaTypeHeaderValue(System.Web.MimeMapping.GetMimeMapping(imagePath)); + + return result; + } + } } \ No newline at end of file diff --git a/src/Umbraco.Web/Editors/LegacyController.cs b/src/Umbraco.Web/Editors/LegacyController.cs index 8def2cc7e8..59218771e8 100644 --- a/src/Umbraco.Web/Editors/LegacyController.cs +++ b/src/Umbraco.Web/Editors/LegacyController.cs @@ -52,7 +52,7 @@ namespace Umbraco.Web.Editors if (httpContextAttempt.Success) { //this is a hack check based on legacy - if (nodeType == "memberGroup") + if (nodeType == "memberGroups") { LegacyDialogHandler.Delete(httpContextAttempt.Result, UmbracoUser, nodeType, 0, alias); return Request.CreateResponse(HttpStatusCode.OK); diff --git a/src/Umbraco.Web/Editors/MacroController.cs b/src/Umbraco.Web/Editors/MacroController.cs index 11fef4a25d..88af605f61 100644 --- a/src/Umbraco.Web/Editors/MacroController.cs +++ b/src/Umbraco.Web/Editors/MacroController.cs @@ -49,7 +49,33 @@ namespace Umbraco.Web.Editors /// /// /// - public HttpResponseMessage GetMacroResultAsHtmlForEditor(string macroAlias, int pageId, [FromUri]IDictionary macroParams) + [HttpGet] + public HttpResponseMessage GetMacroResultAsHtmlForEditor(string macroAlias, int pageId, [FromUri] IDictionary macroParams) + { + return GetMacroResultAsHtml(macroAlias, pageId, macroParams); + } + + /// + /// Gets a rendered macro as html for rendering in the rich text editor. + /// Using HTTP POST instead of GET allows for more parameters to be passed as it's not dependant on URL-length limitations like GET. + /// The method using GET is kept to maintain backwards compatibility + /// + /// + /// + [HttpPost] + public HttpResponseMessage GetMacroResultAsHtmlForEditor(MacroParameterModel model) + { + return GetMacroResultAsHtml(model.MacroAlias, model.PageId, model.MacroParams); + } + + public class MacroParameterModel + { + public string MacroAlias { get; set; } + public int PageId { get; set; } + public IDictionary MacroParams { get; set; } + } + + private HttpResponseMessage GetMacroResultAsHtml(string macroAlias, int pageId, IDictionary macroParams) { // note - here we should be using the cache, provided that the preview content is in the cache... @@ -81,7 +107,7 @@ namespace Umbraco.Web.Editors //the 'easiest' way might be to create an IPublishedContent manually and populate the legacy 'page' object with that //and then set the legacy parameters. - var legacyPage = new global::umbraco.page(doc); + var legacyPage = new global::umbraco.page(doc); UmbracoContext.HttpContext.Items["pageID"] = doc.Id; UmbracoContext.HttpContext.Items["pageElements"] = legacyPage.Elements; UmbracoContext.HttpContext.Items[global::Umbraco.Core.Constants.Conventions.Url.AltTemplate] = null; diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index 7f9d703763..a2637e68e8 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -26,6 +26,7 @@ using Umbraco.Web.Models.Mapping; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi; using System.Linq; +using System.Runtime.Serialization; using Umbraco.Web.WebApi.Binders; using Umbraco.Web.WebApi.Filters; using umbraco; @@ -33,6 +34,8 @@ using umbraco.BusinessLogic.Actions; using Constants = Umbraco.Core.Constants; using Umbraco.Core.Configuration; using Umbraco.Core.Persistence.FaultHandling; +using Umbraco.Web.UI; +using Notification = Umbraco.Web.Models.ContentEditing.Notification; namespace Umbraco.Web.Editors { @@ -115,6 +118,21 @@ namespace Umbraco.Web.Editors return foundMedia.Select(Mapper.Map); } + /// + /// Returns media items known to be a container of other media items + /// + /// + /// + [FilterAllowedOutgoingMedia(typeof(IEnumerable>))] + public IEnumerable> GetChildFolders(int id = -1) + { + //Suggested convention for folder mediatypes - we can make this more or less complicated as long as we document it... + //if you create a media type, which has an alias that ends with ...Folder then its a folder: ex: "secureFolder", "bannerFolder", "Folder" + var folderTypes = Services.ContentTypeService.GetAllMediaTypes().ToArray().Where(x => x.Alias.EndsWith("Folder")).Select(x => x.Id); + + var children = (id < 0) ? Services.MediaService.GetRootMedia() : Services.MediaService.GetById(id).Children(); + return children.Where(x => folderTypes.Contains(x.ContentTypeId)).Select(Mapper.Map>); + } /// /// Returns the root media objects @@ -182,11 +200,23 @@ namespace Umbraco.Web.Editors //if the current item is in the recycle bin if (foundMedia.IsInRecycleBin() == false) { - Services.MediaService.MoveToRecycleBin(foundMedia, (int)Security.CurrentUser.Id); + var moveResult = Services.MediaService.WithResult().MoveToRecycleBin(foundMedia, (int)Security.CurrentUser.Id); + if (moveResult == false) + { + //returning an object of INotificationModel will ensure that any pending + // notification messages are added to the response. + return Request.CreateValidationErrorResponse(new SimpleNotificationModel()); + } } else { - Services.MediaService.Delete(foundMedia, (int)Security.CurrentUser.Id); + var deleteResult = Services.MediaService.WithResult().Delete(foundMedia, (int)Security.CurrentUser.Id); + if (deleteResult == false) + { + //returning an object of INotificationModel will ensure that any pending + // notification messages are added to the response. + return Request.CreateValidationErrorResponse(new SimpleNotificationModel()); + } } return Request.CreateResponse(HttpStatusCode.OK); @@ -234,7 +264,7 @@ namespace Umbraco.Web.Editors // then we cannot continue saving, we can only display errors // * If there are validation errors and they were attempting to publish, we can only save, NOT publish and display // a message indicating this - if (!ModelState.IsValid) + if (ModelState.IsValid == false) { if (ValidationHelper.ModelHasRequiredForPersistenceErrors(contentItem) && (contentItem.Action == ContentSaveAction.SaveNew)) @@ -248,7 +278,7 @@ namespace Umbraco.Web.Editors } //save the item - Services.MediaService.Save(contentItem.PersistedContent, (int)Security.CurrentUser.Id); + var saveStatus = Services.MediaService.WithResult().Save(contentItem.PersistedContent, (int)Security.CurrentUser.Id); //return the updated model var display = Mapper.Map(contentItem.PersistedContent); @@ -261,7 +291,25 @@ namespace Umbraco.Web.Editors { case ContentSaveAction.Save: case ContentSaveAction.SaveNew: - display.AddSuccessNotification(ui.Text("speechBubbles", "editMediaSaved"), ui.Text("speechBubbles", "editMediaSavedText")); + if (saveStatus.Success) + { + display.AddSuccessNotification( + Services.TextService.Localize("speechBubbles/editMediaSaved"), + Services.TextService.Localize("speechBubbles/editMediaSavedText")); + } + else + { + AddCancelMessage(display); + + //If the item is new and the operation was cancelled, we need to return a different + // status code so the UI can handle it since it won't be able to redirect since there + // is no Id to redirect to! + if (saveStatus.Result.StatusType == OperationStatusType.FailedCancelledByEvent && IsCreatingAction(contentItem.Action)) + { + throw new HttpResponseException(Request.CreateValidationErrorResponse(display)); + } + } + break; } @@ -337,7 +385,7 @@ namespace Umbraco.Web.Editors { var mediaService = ApplicationContext.Services.MediaService; var f = mediaService.CreateMedia(folder.Name, folder.ParentId, Constants.Conventions.MediaTypes.Folder); - mediaService.Save(f); + mediaService.Save(f, Security.CurrentUser.Id); return Mapper.Map(f); } @@ -374,37 +422,90 @@ namespace Umbraco.Web.Editors int parentId; if (int.TryParse(result.FormData["currentFolder"], out parentId) == false) { - var response = Request.CreateResponse(HttpStatusCode.BadRequest); - response.ReasonPhrase = "The request was not formatted correctly, the currentFolder is not an integer"; - throw new HttpResponseException(response); + return Request.CreateValidationErrorResponse("The request was not formatted correctly, the currentFolder is not an integer"); } - + //ensure the user has access to this folder by parent id! if (CheckPermissions( new Dictionary(), Security.CurrentUser, Services.MediaService, parentId) == false) { - return Request.CreateResponse(HttpStatusCode.Unauthorized); + return Request.CreateResponse( + HttpStatusCode.Unauthorized, + new SimpleNotificationModel(new Notification( + Services.TextService.Localize("speechBubbles/operationFailedHeader"), + Services.TextService.Localize("speechBubbles/invalidUserPermissionsText"), + SpeechBubbleIcon.Warning))); } - + var tempFiles = new PostedFiles(); + var mediaService = ApplicationContext.Services.MediaService; - //get the files + + //in case we pass a path with a folder in it, we will create it and upload media to it. + if (result.FormData.ContainsKey("path")) + { + + var folders = result.FormData["path"].Split('/'); + + for (int i = 0; i < folders.Length - 1; i++) + { + var folderName = folders[i]; + IMedia folderMediaItem; + + //if uploading directly to media root and not a subfolder + if (parentId == -1) + { + //look for matching folder + folderMediaItem = + mediaService.GetRootMedia().FirstOrDefault(x => x.Name == folderName && x.ContentType.Alias == Constants.Conventions.MediaTypes.Folder); + if (folderMediaItem == null) + { + //if null, create a folder + folderMediaItem = mediaService.CreateMedia(folderName, -1, Constants.Conventions.MediaTypes.Folder); + mediaService.Save(folderMediaItem); + } + } + else + { + //get current parent + var mediaRoot = mediaService.GetById(parentId); + + //if the media root is null, something went wrong, we'll abort + if (mediaRoot == null) + return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, + "The folder: " + folderName + " could not be used for storing images, its ID: " + parentId + + " returned null"); + + //look for matching folder + folderMediaItem = mediaRoot.Children().FirstOrDefault(x => x.Name == folderName && x.ContentType.Alias == Constants.Conventions.MediaTypes.Folder); + if (folderMediaItem == null) + { + //if null, create a folder + folderMediaItem = mediaService.CreateMedia(folderName, mediaRoot, Constants.Conventions.MediaTypes.Folder); + mediaService.Save(folderMediaItem); + } + } + //set the media root to the folder id so uploaded files will end there. + parentId = folderMediaItem.Id; + } + } + + //get the files foreach (var file in result.FileData) { var fileName = file.Headers.ContentDisposition.FileName.Trim(new[] { '\"' }); var ext = fileName.Substring(fileName.LastIndexOf('.')+1).ToLower(); - if (!UmbracoConfig.For.UmbracoSettings().Content.DisallowedUploadFiles.Contains(ext)) + if (UmbracoConfig.For.UmbracoSettings().Content.DisallowedUploadFiles.Contains(ext) == false) { var mediaType = Constants.Conventions.MediaTypes.File; if (UmbracoConfig.For.UmbracoSettings().Content.ImageFileTypes.Contains(ext)) mediaType = Constants.Conventions.MediaTypes.Image; - var mediaService = ApplicationContext.Services.MediaService; - var f = mediaService.CreateMedia(fileName, parentId, mediaType); + var f = mediaService.CreateMedia(fileName, parentId, mediaType, Security.CurrentUser.Id); var fileInfo = new FileInfo(file.LocalFileName); var fs = fileInfo.OpenReadWithRetry(); @@ -414,19 +515,30 @@ namespace Umbraco.Web.Editors f.SetValue(Constants.Conventions.Media.File, fileName, fs); } - mediaService.Save(f); + var saveResult = mediaService.WithResult().Save(f, Security.CurrentUser.Id); + if (saveResult == false) + { + AddCancelMessage(tempFiles, + message: Services.TextService.Localize("speechBubbles/operationCancelledText") + " -- " + fileName, + localizeMessage: false); + } + else + { + tempFiles.UploadedFiles.Add(new ContentItemFile + { + FileName = fileName, + PropertyAlias = Constants.Conventions.Media.File, + TempFilePath = file.LocalFileName + }); + } } else { - LogHelper.Warn("Cannot upload file " + file + ", it is not an approved file type"); + tempFiles.Notifications.Add(new Notification( + Services.TextService.Localize("speechBubbles/operationFailedHeader"), + "Cannot upload file " + file.Headers.ContentDisposition.FileName + ", it is not an approved file type", + SpeechBubbleIcon.Warning)); } - - tempFiles.UploadedFiles.Add(new ContentItemFile - { - FileName = fileName, - PropertyAlias = Constants.Conventions.Media.File, - TempFilePath = file.LocalFileName - }); } //Different response if this is a 'blueimp' request @@ -436,13 +548,7 @@ namespace Umbraco.Web.Editors if (origin.Value == "blueimp") { return Request.CreateResponse(HttpStatusCode.OK, - tempFiles.UploadedFiles.Select(x => new - { - name = x.FileName, - size = "", - url = "", - thumbnailUrl = "" - }), + tempFiles, //Don't output the angular xsrf stuff, blue imp doesn't like that new JsonMediaTypeFormatter()); } @@ -455,13 +561,18 @@ namespace Umbraco.Web.Editors /// This is used for the response of PostAddFile so that we can analyze the response in a filter and remove the /// temporary files that were created. /// - private class PostedFiles : IHaveUploadedFiles + [DataContract] + private class PostedFiles : IHaveUploadedFiles, INotificationModel { public PostedFiles() { UploadedFiles = new List(); + Notifications = new List(); } public List UploadedFiles { get; private set; } + + [DataMember(Name = "notifications")] + public List Notifications { get; private set; } } /// @@ -487,8 +598,9 @@ namespace Umbraco.Web.Editors //cannot move if the content item is not allowed at the root if (toMove.ContentType.AllowedAsRoot == false) { - throw new HttpResponseException( - Request.CreateValidationErrorResponse(ui.Text("moveOrCopy", "notAllowedAtRoot", Security.CurrentUser))); + var notificationModel = new SimpleNotificationModel(); + notificationModel.AddErrorNotification(Services.TextService.Localize("moveOrCopy/notAllowedAtRoot"), ""); + throw new HttpResponseException(Request.CreateValidationErrorResponse(notificationModel)); } } else @@ -503,15 +615,17 @@ namespace Umbraco.Web.Editors if (parent.ContentType.AllowedContentTypes.Select(x => x.Id).ToArray() .Any(x => x.Value == toMove.ContentType.Id) == false) { - throw new HttpResponseException( - Request.CreateValidationErrorResponse(ui.Text("moveOrCopy", "notAllowedByContentType", Security.CurrentUser))); + var notificationModel = new SimpleNotificationModel(); + notificationModel.AddErrorNotification(Services.TextService.Localize("moveOrCopy/notAllowedByContentType"), ""); + throw new HttpResponseException(Request.CreateValidationErrorResponse(notificationModel)); } // Check on paths if ((string.Format(",{0},", parent.Path)).IndexOf(string.Format(",{0},", toMove.Id), StringComparison.Ordinal) > -1) { - throw new HttpResponseException( - Request.CreateValidationErrorResponse(ui.Text("moveOrCopy", "notAllowedByPath", Security.CurrentUser))); + var notificationModel = new SimpleNotificationModel(); + notificationModel.AddErrorNotification(Services.TextService.Localize("moveOrCopy/notAllowedByPath"), ""); + throw new HttpResponseException(Request.CreateValidationErrorResponse(notificationModel)); } } diff --git a/src/Umbraco.Web/Editors/MediaTypeController.cs b/src/Umbraco.Web/Editors/MediaTypeController.cs index e8cad40803..d54981ff45 100644 --- a/src/Umbraco.Web/Editors/MediaTypeController.cs +++ b/src/Umbraco.Web/Editors/MediaTypeController.cs @@ -1,26 +1,36 @@ using System.Collections.Generic; using System.Linq; -using System.Net; -using System.Web.Http; +using System.Web.Security; using AutoMapper; -using Newtonsoft.Json; +using Umbraco.Core; using Umbraco.Core.Models; +using Umbraco.Core.Security; using Umbraco.Web.Models.ContentEditing; -using Umbraco.Web.Models.Mapping; using Umbraco.Web.Mvc; +using Umbraco.Web.WebApi.Filters; +using Constants = Umbraco.Core.Constants; +using System.Web.Http; +using System.Net; +using Umbraco.Core.PropertyEditors; +using System; +using System.Net.Http; +using System.Text; using Umbraco.Web.WebApi; +using ContentType = System.Net.Mime.ContentType; +using Umbraco.Core.Services; namespace Umbraco.Web.Editors { - //TODO: We'll need to be careful about the security on this controller, when we start implementing // methods to modify content types we'll need to enforce security on the individual methods, we // cannot put security on the whole controller because things like GetAllowedChildren are required for content editing. /// - /// An API controller used for dealing with media types + /// An API controller used for dealing with content types /// [PluginController("UmbracoApi")] + [UmbracoTreeAuthorize(Constants.Trees.MediaTypes)] + [EnableOverrideAuthorization] public class MediaTypeController : ContentTypeControllerBase { /// @@ -38,35 +48,163 @@ namespace Umbraco.Web.Editors public MediaTypeController(UmbracoContext umbracoContext) : base(umbracoContext) { + } + + public ContentTypeCompositionDisplay GetById(int id) + { + var ct = Services.ContentTypeService.GetMediaType(id); + if (ct == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + var dto = Mapper.Map(ct); + return dto; + } + + /// + /// Deletes a document type wth a given ID + /// + /// + /// + [HttpDelete] + [HttpPost] + public HttpResponseMessage DeleteById(int id) + { + var foundType = Services.ContentTypeService.GetMediaType(id); + if (foundType == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + Services.ContentTypeService.Delete(foundType, Security.CurrentUser.Id); + return Request.CreateResponse(HttpStatusCode.OK); + } + + public IEnumerable GetAvailableCompositeMediaTypes(int contentTypeId) + { + return PerformGetAvailableCompositeContentTypes(contentTypeId, UmbracoObjectTypes.MediaType); + } + + public ContentTypeCompositionDisplay GetEmpty(int parentId) + { + var ct = new MediaType(parentId); + ct.Icon = "icon-picture"; + + var dto = Mapper.Map(ct); + return dto; + } + + + /// + /// Returns all member types + /// + public IEnumerable GetAll() + { + + return Services.ContentTypeService.GetAllMediaTypes() + .Select(Mapper.Map); + } + + /// + /// Deletes a document type container wth a given ID + /// + /// + /// + [HttpDelete] + [HttpPost] + public HttpResponseMessage DeleteContainer(int id) + { + Services.ContentTypeService.DeleteMediaTypeContainer(id, Security.CurrentUser.Id); + + return Request.CreateResponse(HttpStatusCode.OK); + } + + public HttpResponseMessage PostCreateContainer(int parentId, string name) + { + var result = Services.ContentTypeService.CreateMediaTypeContainer(parentId, name, Security.CurrentUser.Id); + + return result + ? Request.CreateResponse(HttpStatusCode.OK, result.Result) //return the id + : Request.CreateNotificationValidationErrorResponse(result.Exception.Message); + } + + public ContentTypeCompositionDisplay PostSave(ContentTypeSave contentTypeSave) + { + var savedCt = PerformPostSave( + contentTypeSave: contentTypeSave, + getContentType: i => Services.ContentTypeService.GetMediaType(i), + saveContentType: type => Services.ContentTypeService.Save(type)); + + var display = Mapper.Map(savedCt); + + display.AddSuccessNotification( + Services.TextService.Localize("speechBubbles/contentTypeSavedHeader"), + string.Empty); + + return display; + } + + /// /// Returns the allowed child content type objects for the content item id passed in /// /// + [UmbracoTreeAuthorize(Constants.Trees.MediaTypes, Constants.Trees.Media)] public IEnumerable GetAllowedChildren(int contentId) { - if (contentId == Core.Constants.System.RecycleBinMedia) + if (contentId == Constants.System.RecycleBinContent) return Enumerable.Empty(); - if (contentId == Core.Constants.System.Root) + IEnumerable types; + if (contentId == Constants.System.Root) { - return Services.ContentTypeService.GetAllMediaTypes() - .Where(x => x.AllowedAsRoot) - .Select(Mapper.Map); + types = Services.ContentTypeService.GetAllMediaTypes().ToList(); + + //if no allowed root types are set, just return everything + if (types.Any(x => x.AllowedAsRoot)) + types = types.Where(x => x.AllowedAsRoot); + } + else + { + var contentItem = Services.MediaService.GetById(contentId); + if (contentItem == null) + { + return Enumerable.Empty(); + } + + var ids = contentItem.ContentType.AllowedContentTypes.Select(x => x.Id.Value).ToArray(); + + if (ids.Any() == false) return Enumerable.Empty(); + + types = Services.ContentTypeService.GetAllMediaTypes(ids).ToList(); } - var contentItem = Services.MediaService.GetById(contentId); - if (contentItem == null) + var basics = types.Select(Mapper.Map).ToList(); + + foreach (var basic in basics) { - throw new HttpResponseException(HttpStatusCode.NotFound); + basic.Name = TranslateItem(basic.Name); + basic.Description = TranslateItem(basic.Description); } - var ids = contentItem.ContentType.AllowedContentTypes.Select(x => x.Id.Value).ToArray(); - if (ids.Any() == false) return Enumerable.Empty(); - - return Services.ContentTypeService.GetAllMediaTypes(ids) - .Select(Mapper.Map); + return basics; } + + /// + /// Move the media type + /// + /// + /// + public HttpResponseMessage PostMove(MoveOrCopy move) + { + return PerformMove( + move, + getContentType: i => Services.ContentTypeService.GetMediaType(i), + doMove: (type, i) => Services.ContentTypeService.MoveMediaType(type, i)); + } + } } \ No newline at end of file diff --git a/src/Umbraco.Web/Editors/MemberController.cs b/src/Umbraco.Web/Editors/MemberController.cs index 30320b278e..4405f257c4 100644 --- a/src/Umbraco.Web/Editors/MemberController.cs +++ b/src/Umbraco.Web/Editors/MemberController.cs @@ -6,6 +6,7 @@ using System.Net; using System.Net.Http; using System.Text; using System.Threading.Tasks; +using System.Web; using System.Web.Http; using System.Web.Http.ModelBinding; using System.Web.Security; @@ -80,7 +81,7 @@ namespace Umbraco.Web.Editors string filter = "", string memberTypeAlias = null) { - int totalRecords; + if (pageNumber <= 0 || pageSize <= 0) { throw new NotSupportedException("Both pageNumber and pageSize must be greater than zero"); @@ -88,6 +89,7 @@ namespace Umbraco.Web.Editors if (MembershipScenario == MembershipScenario.NativeUmbraco) { + long totalRecords; var members = Services.MemberService.GetAll((pageNumber - 1), pageSize, out totalRecords, orderBy, orderDirection, memberTypeAlias, filter).ToArray(); if (totalRecords == 0) { @@ -100,6 +102,7 @@ namespace Umbraco.Web.Editors } else { + int totalRecords; var members = _provider.GetAllUsers((pageNumber - 1), pageSize, out totalRecords); if (totalRecords == 0) { @@ -282,6 +285,9 @@ namespace Umbraco.Web.Editors case ContentSaveAction.SaveNew: MembershipCreateStatus status; CreateWithMembershipProvider(contentItem, out status); + + // save the ID of the creator + contentItem.PersistedContent.CreatorId = Security.CurrentUser.Id; break; default: //we don't support anything else for members @@ -385,6 +391,7 @@ namespace Umbraco.Web.Editors } var shouldReFetchMember = false; + var providedUserName = contentItem.PersistedContent.Username; //Update the membership user if it has changed try @@ -444,7 +451,9 @@ namespace Umbraco.Web.Editors if (shouldReFetchMember) { RefetchMemberData(contentItem, LookupType.ByKey); + RestoreProvidedUserName(contentItem, providedUserName); } + return null; } @@ -456,6 +465,7 @@ namespace Umbraco.Web.Editors if (shouldReFetchMember) { RefetchMemberData(contentItem, LookupType.ByKey); + RestoreProvidedUserName(contentItem, providedUserName); } //even if we weren't resetting this, it is the correct value (null), otherwise if we were resetting then it will contain the new pword @@ -467,7 +477,6 @@ namespace Umbraco.Web.Editors passwordChangeResult.Result.ChangeError, string.Format("{0}password", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); - return null; } @@ -528,6 +537,17 @@ namespace Umbraco.Web.Editors } } + /// + /// Following a refresh of member data called during an update if the membership provider has changed some underlying data, + /// we don't want to lose the provided, and potentiallly changed, username + /// + /// + /// + private static void RestoreProvidedUserName(MemberSave contentItem, string providedUserName) + { + contentItem.PersistedContent.Username = providedUserName; + } + /// /// This is going to create the user with the membership provider and check for validation /// diff --git a/src/Umbraco.Web/Editors/MemberTypeController.cs b/src/Umbraco.Web/Editors/MemberTypeController.cs index c47f1d0662..7716f6ea00 100644 --- a/src/Umbraco.Web/Editors/MemberTypeController.cs +++ b/src/Umbraco.Web/Editors/MemberTypeController.cs @@ -2,24 +2,30 @@ using System.Linq; using System.Web.Security; using AutoMapper; +using Umbraco.Core; using Umbraco.Core.Models; +using Umbraco.Core.Services; using Umbraco.Core.Security; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi.Filters; using Constants = Umbraco.Core.Constants; +using System.Web.Http; +using System.Net; +using Umbraco.Core.PropertyEditors; +using System; +using System.Net.Http; +using ContentType = System.Net.Mime.ContentType; namespace Umbraco.Web.Editors { - //TODO: We'll need to be careful about the security on this controller, when we start implementing - // methods to modify content types we'll need to enforce security on the individual methods, we - // cannot put security on the whole controller because things like GetAllowedChildren are required for content editing. - + /// /// An API controller used for dealing with content types /// [PluginController("UmbracoApi")] - public class MemberTypeController : UmbracoAuthorizedJsonController + [UmbracoTreeAuthorize(Constants.Trees.MemberTypes)] + public class MemberTypeController : ContentTypeControllerBase { /// /// Constructor @@ -42,6 +48,52 @@ namespace Umbraco.Web.Editors private readonly MembershipProvider _provider; + public ContentTypeCompositionDisplay GetById(int id) + { + var ct = Services.MemberTypeService.Get(id); + if (ct == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + var dto = Mapper.Map(ct); + return dto; + } + + /// + /// Deletes a document type wth a given ID + /// + /// + /// + [HttpDelete] + [HttpPost] + public HttpResponseMessage DeleteById(int id) + { + var foundType = Services.MemberTypeService.Get(id); + if (foundType == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + Services.MemberTypeService.Delete(foundType, Security.CurrentUser.Id); + return Request.CreateResponse(HttpStatusCode.OK); + } + + public IEnumerable GetAvailableCompositeMemberTypes(int contentTypeId) + { + return PerformGetAvailableCompositeContentTypes(contentTypeId, UmbracoObjectTypes.MemberType); + } + + public ContentTypeCompositionDisplay GetEmpty() + { + var ct = new MemberType(-1); + ct.Icon = "icon-user"; + + var dto = Mapper.Map(ct); + return dto; + } + + /// /// Returns all member types /// @@ -53,7 +105,23 @@ namespace Umbraco.Web.Editors .Select(Mapper.Map); } return Enumerable.Empty(); + } + public ContentTypeCompositionDisplay PostSave(ContentTypeSave contentTypeSave) + { + var savedCt = PerformPostSave( + contentTypeSave: contentTypeSave, + getContentType: i => Services.MemberTypeService.Get(i), + saveContentType: type => Services.MemberTypeService.Save(type), + validateComposition: false); + + var display = Mapper.Map(savedCt); + + display.AddSuccessNotification( + Services.TextService.Localize("speechBubbles/contentTypeSavedHeader"), + string.Empty); + + return display; } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Editors/TemplateQueryController.cs b/src/Umbraco.Web/Editors/TemplateQueryController.cs index 2c38287d9a..4b81715ead 100644 --- a/src/Umbraco.Web/Editors/TemplateQueryController.cs +++ b/src/Umbraco.Web/Editors/TemplateQueryController.cs @@ -83,29 +83,31 @@ namespace Umbraco.Web.Editors { var targetNode = umbraco.TypedContent(model.Source.Id); - var aliases = this.GetChildContentTypeAliases(targetNode, currentPage).Reverse(); - - foreach (var contentTypeAlias in aliases) + if (targetNode != null) { - timer.Start(); + var aliases = this.GetChildContentTypeAliases(targetNode, currentPage).Reverse(); - pointerNode = pointerNode.FirstChild(x => x.DocumentTypeAlias == contentTypeAlias); + foreach (var contentTypeAlias in aliases) + { + timer.Start(); - if (pointerNode == null) break; + pointerNode = pointerNode.FirstChild(x => x.DocumentTypeAlias == contentTypeAlias); - timer.Stop(); - - sb.AppendFormat(".FirstChild(\"{0}\")", contentTypeAlias); + if (pointerNode == null) break; + + timer.Stop(); + + sb.AppendFormat(".FirstChild(\"{0}\")", contentTypeAlias); + } + + if (pointerNode == null || pointerNode.Id != model.Source.Id) + { + // we did not find the path + sb.Clear(); + sb.AppendFormat("Umbraco.Content({0})", model.Source.Id); + pointerNode = targetNode; + } } - - if (pointerNode == null || pointerNode.Id != model.Source.Id) - { - // we did not find the path - sb.Clear(); - sb.AppendFormat("Umbraco.Content({0})", model.Source.Id); - pointerNode = targetNode; - } - } // TYPE to return if filtered by type diff --git a/src/Umbraco.Web/GridTemplateExtensions.cs b/src/Umbraco.Web/GridTemplateExtensions.cs index e4af759c88..cf267897d4 100644 --- a/src/Umbraco.Web/GridTemplateExtensions.cs +++ b/src/Umbraco.Web/GridTemplateExtensions.cs @@ -20,7 +20,7 @@ namespace Umbraco.Web public static MvcHtmlString GetGridHtml(this HtmlHelper html, IPublishedProperty property, string framework = "bootstrap3") { var asString = property.Value as string; - if (asString.IsNullOrWhiteSpace()) return new MvcHtmlString(string.Empty); + if (asString != null && string.IsNullOrEmpty(asString)) return new MvcHtmlString(string.Empty); var view = "Grid/" + framework; return html.Partial(view, property.Value); @@ -56,7 +56,7 @@ namespace Umbraco.Web public static MvcHtmlString GetGridHtml(this IPublishedProperty property, HtmlHelper html, string framework = "bootstrap3") { var asString = property.Value as string; - if (asString.IsNullOrWhiteSpace()) return new MvcHtmlString(string.Empty); + if (asString != null && string.IsNullOrEmpty(asString)) return new MvcHtmlString(string.Empty); var view = "Grid/" + framework; return html.Partial(view, property.Value); @@ -87,23 +87,23 @@ namespace Umbraco.Web } - //[Obsolete("This should not be used, GetGridHtml methods accepting HtmlHelper as a parameter or GetGridHtml extensions on HtmlHelper should be used instead")] + [Obsolete("This should not be used, GetGridHtml methods accepting HtmlHelper as a parameter or GetGridHtml extensions on HtmlHelper should be used instead")] public static MvcHtmlString GetGridHtml(this IPublishedProperty property, string framework = "bootstrap3") { var asString = property.Value as string; - if (asString.IsNullOrWhiteSpace()) return new MvcHtmlString(string.Empty); + if (asString != null && string.IsNullOrEmpty(asString)) return new MvcHtmlString(string.Empty); var htmlHelper = CreateHtmlHelper(property.Value); return htmlHelper.GetGridHtml(property, framework); } - //[Obsolete("This should not be used, GetGridHtml methods accepting HtmlHelper as a parameter or GetGridHtml extensions on HtmlHelper should be used instead")] + [Obsolete("This should not be used, GetGridHtml methods accepting HtmlHelper as a parameter or GetGridHtml extensions on HtmlHelper should be used instead")] public static MvcHtmlString GetGridHtml(this IPublishedContent contentItem) { return GetGridHtml(contentItem, "bodyText", "bootstrap3"); } - //[Obsolete("This should not be used, GetGridHtml methods accepting HtmlHelper as a parameter or GetGridHtml extensions on HtmlHelper should be used instead")] + [Obsolete("This should not be used, GetGridHtml methods accepting HtmlHelper as a parameter or GetGridHtml extensions on HtmlHelper should be used instead")] public static MvcHtmlString GetGridHtml(this IPublishedContent contentItem, string propertyAlias) { Mandate.ParameterNotNullOrEmpty(propertyAlias, "propertyAlias"); @@ -111,7 +111,7 @@ namespace Umbraco.Web return GetGridHtml(contentItem, propertyAlias, "bootstrap3"); } - //[Obsolete("This should not be used, GetGridHtml methods accepting HtmlHelper as a parameter or GetGridHtml extensions on HtmlHelper should be used instead")] + [Obsolete("This should not be used, GetGridHtml methods accepting HtmlHelper as a parameter or GetGridHtml extensions on HtmlHelper should be used instead")] public static MvcHtmlString GetGridHtml(this IPublishedContent contentItem, string propertyAlias, string framework) { Mandate.ParameterNotNullOrEmpty(propertyAlias, "propertyAlias"); @@ -127,7 +127,7 @@ namespace Umbraco.Web return htmlHelper.GetGridHtml(contentItem, propertyAlias, framework); } - //[Obsolete("This shouldn't need to be used but because the obsolete extension methods above don't have access to the current HtmlHelper, we need to create a fake one, unfortunately however this will not pertain the current views viewdata, tempdata or model state so should not be used")] + [Obsolete("This shouldn't need to be used but because the obsolete extension methods above don't have access to the current HtmlHelper, we need to create a fake one, unfortunately however this will not pertain the current views viewdata, tempdata or model state so should not be used")] private static HtmlHelper CreateHtmlHelper(object model) { var cc = new ControllerContext diff --git a/src/Umbraco.Web/HtmlHelperBackOfficeExtensions.cs b/src/Umbraco.Web/HtmlHelperBackOfficeExtensions.cs index e43cd21a0e..75e628851e 100644 --- a/src/Umbraco.Web/HtmlHelperBackOfficeExtensions.cs +++ b/src/Umbraco.Web/HtmlHelperBackOfficeExtensions.cs @@ -28,7 +28,7 @@ namespace Umbraco.Web /// These are the bare minimal server variables that are required for the application to start without being authenticated, /// we will load the rest of the server vars after the user is authenticated. /// - public static IHtmlString BareMinimumServerVariables(this HtmlHelper html, UrlHelper uri, string externalLoginsUrl) + public static IHtmlString BareMinimumServerVariablesScript(this HtmlHelper html, UrlHelper uri, string externalLoginsUrl) { var str = @""; return html.Raw(str); - } + } /// - /// Used to render the script tag that will pass in the angular externalLoginInfo service on page load + /// Used to render the script that will pass in the angular externalLoginInfo service on page load /// /// /// /// - public static IHtmlString AngularExternalLoginInfoValues(this HtmlHelper html, IEnumerable externalLoginErrors) + public static IHtmlString AngularExternalLoginInfoValuesScript(this HtmlHelper html, IEnumerable externalLoginErrors) { var loginProviders = html.ViewContext.HttpContext.GetOwinContext().Authentication.GetExternalAuthenticationTypes() .Where(p => p.Properties.ContainsKey("UmbracoBackOffice")) @@ -67,12 +67,8 @@ namespace Umbraco.Web }) .ToArray(); - - //define a callback that is executed when we bootstrap angular, this is used to inject angular values - //with server side info - - var sb = new StringBuilder(@""); return html.Raw(sb.ToString()); } diff --git a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs index 7ae519a44b..df304a0b79 100644 --- a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs +++ b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Text; using System.Web; @@ -11,10 +10,12 @@ using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Dynamics; using Umbraco.Core.IO; +using Umbraco.Core.Models; using Umbraco.Core.Profiling; +using Umbraco.Web.Models; using Umbraco.Web.Mvc; -using umbraco; -using umbraco.cms.businesslogic.member; +using Constants = Umbraco.Core.Constants; +using Member = umbraco.cms.businesslogic.member.Member; namespace Umbraco.Web { @@ -194,12 +195,123 @@ namespace Umbraco.Web return htmlHelper.Action(actionName, metaData.ControllerName, routeVals); } - #region BeginUmbracoForm + #region GetCropUrl - /// - /// Used for rendering out the Form for BeginUmbracoForm - /// - internal class UmbracoForm : MvcForm + /// + /// Gets the ImageProcessor Url of a media item by the crop alias (using default media item property alias of "umbracoFile") + /// + /// + /// + /// + /// + public static IHtmlString GetCropUrl(this HtmlHelper htmlHelper, IPublishedContent mediaItem, string cropAlias) + { + return new HtmlString(mediaItem.GetCropUrl(cropAlias: cropAlias, useCropDimensions: true)); + } + + /// + /// Gets the ImageProcessor Url of a media item by the property alias and crop alias. + /// + /// + /// + /// + /// + /// + public static IHtmlString GetCropUrl(this HtmlHelper htmlHelper, IPublishedContent mediaItem, string propertyAlias, string cropAlias) + { + return new HtmlString(mediaItem.GetCropUrl(propertyAlias: propertyAlias, cropAlias: cropAlias, useCropDimensions: true)); + } + + /// + /// Gets the ImageProcessor Url of a media item + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public static IHtmlString GetCropUrl(this HtmlHelper htmlHelper, + IPublishedContent mediaItem, + int? width = null, + int? height = null, + string propertyAlias = Constants.Conventions.Media.File, + string cropAlias = null, + int? quality = null, + ImageCropMode? imageCropMode = null, + ImageCropAnchor? imageCropAnchor = null, + bool preferFocalPoint = false, + bool useCropDimensions = false, + bool cacheBuster = true, + string furtherOptions = null, + ImageCropRatioMode? ratioMode = null, + bool upScale = true) + { + return + new HtmlString(mediaItem.GetCropUrl(width, height, propertyAlias, cropAlias, quality, imageCropMode, + imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBuster, furtherOptions, ratioMode, + upScale)); + } + + /// + /// Gets the ImageProcessor Url from the media path + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public static IHtmlString GetCropUrl(this HtmlHelper htmlHelper, + string imageUrl, + int? width = null, + int? height = null, + string imageCropperValue = null, + string cropAlias = null, + int? quality = null, + ImageCropMode? imageCropMode = null, + ImageCropAnchor? imageCropAnchor = null, + bool preferFocalPoint = false, + bool useCropDimensions = false, + string cacheBusterValue = null, + string furtherOptions = null, + ImageCropRatioMode? ratioMode = null, + bool upScale = true) + { + return + new HtmlString(imageUrl.GetCropUrl(width, height, imageCropperValue, cropAlias, quality, imageCropMode, + imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBusterValue, furtherOptions, ratioMode, + upScale)); + } + + #endregion + + #region BeginUmbracoForm + + /// + /// Used for rendering out the Form for BeginUmbracoForm + /// + internal class UmbracoForm : MvcForm { /// /// Creates an UmbracoForm @@ -756,8 +868,7 @@ namespace Umbraco.Web } #endregion - - + #region Wrap public static HtmlTagWrapper Wrap(this HtmlHelper html, string tag, string innerText, params IHtmlTagWrapper[] children) @@ -823,7 +934,71 @@ namespace Umbraco.Web return item; } - #endregion + #endregion - } + #region canvasdesigner + + public static IHtmlString EnableCanvasDesigner(this HtmlHelper html, + UrlHelper url, + UmbracoContext umbCtx) + { + return html.EnableCanvasDesigner(url, umbCtx, string.Empty, string.Empty); + } + + public static IHtmlString EnableCanvasDesigner(this HtmlHelper html, + UrlHelper url, + UmbracoContext umbCtx, string canvasdesignerConfigPath) + { + return html.EnableCanvasDesigner(url, umbCtx, canvasdesignerConfigPath, string.Empty); + } + + public static IHtmlString EnableCanvasDesigner(this HtmlHelper html, + UrlHelper url, + UmbracoContext umbCtx, string canvasdesignerConfigPath, string canvasdesignerPalettesPath) + { + + var umbracoPath = url.Content(SystemDirectories.Umbraco); + + string previewLink = @"" + + @"" + + @"" + + @"" + + @""; + + string noPreviewLinks = @""; + + // Get page value + int pageId = umbCtx.PublishedContentRequest.UmbracoPage.PageID; + string[] path = umbCtx.PublishedContentRequest.UmbracoPage.SplitPath; + string result = string.Empty; + string cssPath = CanvasDesignerUtility.GetStylesheetPath(path, false); + + if (umbCtx.InPreviewMode) + { + canvasdesignerConfigPath = string.IsNullOrEmpty(canvasdesignerConfigPath) == false + ? canvasdesignerConfigPath + : string.Format("{0}/js/canvasdesigner.config.js", umbracoPath); + canvasdesignerPalettesPath = string.IsNullOrEmpty(canvasdesignerPalettesPath) == false + ? canvasdesignerPalettesPath + : string.Format("{0}/js/canvasdesigner.palettes.js", umbracoPath); + + if (string.IsNullOrEmpty(cssPath) == false) + result = string.Format(noPreviewLinks, cssPath) + Environment.NewLine; + + result = result + string.Format(previewLink, umbracoPath, canvasdesignerConfigPath, canvasdesignerPalettesPath, pageId); + } + else + { + // Get css path for current page + if (string.IsNullOrEmpty(cssPath) == false) + result = string.Format(noPreviewLinks, cssPath); + } + + return new HtmlString(result); + + } + + #endregion + + } } diff --git a/src/Umbraco.Web/HttpCookieExtensions.cs b/src/Umbraco.Web/HttpCookieExtensions.cs index dc6d087fb3..7aec1466da 100644 --- a/src/Umbraco.Web/HttpCookieExtensions.cs +++ b/src/Umbraco.Web/HttpCookieExtensions.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Net.Http; using System.Net.Http.Headers; using System.Web; +using Microsoft.Owin; using Umbraco.Core; namespace Umbraco.Web @@ -62,6 +63,16 @@ namespace Umbraco.Web return request.Cookies[Constants.Web.PreviewCookieName] != null; } + /// + /// Does a preview cookie exist ? + /// + /// + /// + public static bool HasPreviewCookie(this IOwinRequest request) + { + return request.Cookies[Constants.Web.PreviewCookieName] != null; + } + /// /// Does a cookie exist with the specified key ? /// diff --git a/src/Umbraco.Web/HttpRequestExtensions.cs b/src/Umbraco.Web/HttpRequestExtensions.cs index e700f96571..d7f9e409c8 100644 --- a/src/Umbraco.Web/HttpRequestExtensions.cs +++ b/src/Umbraco.Web/HttpRequestExtensions.cs @@ -32,7 +32,7 @@ namespace Umbraco.Web /// public static string GetItemAsString(this HttpRequest request, string key, string valueIfNotFound = "") { - return new HttpRequestWrapper(request).GetItemAsString(key); + return new HttpRequestWrapper(request).GetItemAsString(key, valueIfNotFound); } /// @@ -44,7 +44,7 @@ namespace Umbraco.Web /// public static string GetItemAsString(this HttpRequestBase request, string key, string valueIfNotFound = "") { - var val = HttpContext.Current.Request[key]; + var val = request[key]; return !val.IsNullOrWhiteSpace() ? val : valueIfNotFound; } @@ -57,7 +57,7 @@ namespace Umbraco.Web /// public static T GetItemAs(this HttpRequestBase request, string key) { - var val = HttpContext.Current.Request[key]; + var val = request[key]; var whitespaceCheck = !val.IsNullOrWhiteSpace() ? val : string.Empty; if (whitespaceCheck.IsNullOrWhiteSpace()) return (T) typeof (T).GetDefaultValue(); diff --git a/src/Umbraco.Web/IUmbracoContextAccessor.cs b/src/Umbraco.Web/IUmbracoContextAccessor.cs index e3614a1e86..997b25145b 100644 --- a/src/Umbraco.Web/IUmbracoContextAccessor.cs +++ b/src/Umbraco.Web/IUmbracoContextAccessor.cs @@ -2,11 +2,8 @@ namespace Umbraco.Web { /// /// Used to retrieve the Umbraco context - /// - /// - /// TODO: We could expose this to make working with UmbracoContext easier if we were to use it throughout the codebase - /// - internal interface IUmbracoContextAccessor + /// + public interface IUmbracoContextAccessor { UmbracoContext Value { get; } } diff --git a/src/Umbraco.Web/ImageCropperBaseExtensions.cs b/src/Umbraco.Web/ImageCropperBaseExtensions.cs index cceac8ab31..b870335c91 100644 --- a/src/Umbraco.Web/ImageCropperBaseExtensions.cs +++ b/src/Umbraco.Web/ImageCropperBaseExtensions.cs @@ -81,6 +81,7 @@ { return null; } + if ((preferFocalPoint && cropDataSet.HasFocalPoint()) || (crop != null && crop.Coordinates == null && cropDataSet.HasFocalPoint()) || (string.IsNullOrEmpty(cropAlias) && cropDataSet.HasFocalPoint())) { cropUrl.Append("?center=" + cropDataSet.FocalPoint.Top.ToString(CultureInfo.InvariantCulture) + "," + cropDataSet.FocalPoint.Left.ToString(CultureInfo.InvariantCulture)); @@ -100,6 +101,7 @@ cropUrl.Append("?anchor=center"); cropUrl.Append("&mode=crop"); } + return cropUrl.ToString(); } } diff --git a/src/Umbraco.Web/ImageCropperTemplateExtensions.cs b/src/Umbraco.Web/ImageCropperTemplateExtensions.cs index de0f8a225e..a76b39f187 100644 --- a/src/Umbraco.Web/ImageCropperTemplateExtensions.cs +++ b/src/Umbraco.Web/ImageCropperTemplateExtensions.cs @@ -110,9 +110,8 @@ bool useCropDimensions = false, bool cacheBuster = true, string furtherOptions = null, - ImageCropRatioMode? ratioMode = null, - bool upScale = true - ) + ImageCropRatioMode? ratioMode = null, + bool upScale = true) { string imageCropperValue = null; @@ -153,6 +152,12 @@ /// /// The height of the output image. /// + /// + /// The Json data from the Umbraco Core Image Cropper property editor + /// + /// + /// The crop alias. + /// /// /// Quality percentage of the output image. /// @@ -162,17 +167,11 @@ /// /// The image crop anchor. /// - /// - /// The Json data from the Umbraco Core Image Cropper property editor - /// - /// - /// The crop alias. - /// /// /// Use focal point to generate an output image using the focal point instead of the predefined crop if there is one /// /// - /// Use crop dimensions to have the output image sized according to the predefined crop sizes, this will override the width and height parameters>. + /// Use crop dimensions to have the output image sized according to the predefined crop sizes, this will override the width and height parameters /// /// /// Add a serialised date of the last edit of the item to ensure client cache refresh when updated @@ -182,10 +181,10 @@ /// /// /// Use a dimension as a ratio - /// + /// /// /// If the image should be upscaled to requested dimensions - /// + /// /// /// The . /// @@ -203,12 +202,11 @@ string cacheBusterValue = null, string furtherOptions = null, ImageCropRatioMode? ratioMode = null, - bool upScale = true - ) + bool upScale = true) { if (string.IsNullOrEmpty(imageUrl) == false) { - var imageResizerUrl = new StringBuilder(); + var imageProcessorUrl = new StringBuilder(); if (string.IsNullOrEmpty(imageCropperValue) == false && imageCropperValue.DetectIsJson() && (imageCropMode == ImageCropMode.Crop || imageCropMode == null)) { @@ -217,95 +215,111 @@ { var crop = cropDataSet.GetCrop(cropAlias); - imageResizerUrl.Append(cropDataSet.Src); + imageProcessorUrl.Append(cropDataSet.Src); var cropBaseUrl = cropDataSet.GetCropBaseUrl(cropAlias, preferFocalPoint); if (cropBaseUrl != null) { - imageResizerUrl.Append(cropBaseUrl); + imageProcessorUrl.Append(cropBaseUrl); } else { return null; } - if (crop!= null & useCropDimensions) + if (crop != null & useCropDimensions) { width = crop.Width; height = crop.Height; } + + // If a predefined crop has been specified & there are no coordinates & no ratio mode, but a width parameter has been passed we can get the crop ratio for the height + if (crop != null && string.IsNullOrEmpty(cropAlias) == false && crop.Coordinates == null && ratioMode == null && width != null && height == null) + { + var heightRatio = (decimal)crop.Height / (decimal)crop.Width; + imageProcessorUrl.Append("&heightratio=" + heightRatio.ToString(CultureInfo.InvariantCulture)); + } + + // If a predefined crop has been specified & there are no coordinates & no ratio mode, but a height parameter has been passed we can get the crop ratio for the width + if (crop != null && string.IsNullOrEmpty(cropAlias) == false && crop.Coordinates == null && ratioMode == null && width == null && height != null) + { + var widthRatio = (decimal)crop.Width / (decimal)crop.Height; + imageProcessorUrl.Append("&widthratio=" + widthRatio.ToString(CultureInfo.InvariantCulture)); + } } } else { - imageResizerUrl.Append(imageUrl); + imageProcessorUrl.Append(imageUrl); if (imageCropMode == null) { imageCropMode = ImageCropMode.Pad; } - imageResizerUrl.Append("?mode=" + imageCropMode.ToString().ToLower()); + imageProcessorUrl.Append("?mode=" + imageCropMode.ToString().ToLower()); if (imageCropAnchor != null) { - imageResizerUrl.Append("&anchor=" + imageCropAnchor.ToString().ToLower()); + imageProcessorUrl.Append("&anchor=" + imageCropAnchor.ToString().ToLower()); } } if (quality != null) { - imageResizerUrl.Append("&quality=" + quality); + imageProcessorUrl.Append("&quality=" + quality); } if (width != null && ratioMode != ImageCropRatioMode.Width) { - imageResizerUrl.Append("&width=" + width); + imageProcessorUrl.Append("&width=" + width); } if (height != null && ratioMode != ImageCropRatioMode.Height) { - imageResizerUrl.Append("&height=" + height); + imageProcessorUrl.Append("&height=" + height); } if (ratioMode == ImageCropRatioMode.Width && height != null) { - //if only height specified then assume a sqaure + // if only height specified then assume a sqaure if (width == null) { width = height; } - var widthRatio = (decimal)width/(decimal)height; - imageResizerUrl.Append("&widthratio=" + widthRatio.ToString(CultureInfo.InvariantCulture)); + + var widthRatio = (decimal)width / (decimal)height; + imageProcessorUrl.Append("&widthratio=" + widthRatio.ToString(CultureInfo.InvariantCulture)); } if (ratioMode == ImageCropRatioMode.Height && width != null) { - //if only width specified then assume a sqaure + // if only width specified then assume a sqaure if (height == null) { height = width; } - var heightRatio = (decimal)height/(decimal)width; - imageResizerUrl.Append("&heightratio=" + heightRatio.ToString(CultureInfo.InvariantCulture)); + + var heightRatio = (decimal)height / (decimal)width; + imageProcessorUrl.Append("&heightratio=" + heightRatio.ToString(CultureInfo.InvariantCulture)); } if (upScale == false) { - imageResizerUrl.Append("&upscale=false"); + imageProcessorUrl.Append("&upscale=false"); } if (furtherOptions != null) { - imageResizerUrl.Append(furtherOptions); + imageProcessorUrl.Append(furtherOptions); } if (cacheBusterValue != null) { - imageResizerUrl.Append("&rnd=").Append(cacheBusterValue); + imageProcessorUrl.Append("&rnd=").Append(cacheBusterValue); } - return imageResizerUrl.ToString(); + return imageProcessorUrl.ToString(); } return string.Empty; diff --git a/src/Umbraco.Web/Install/Controllers/InstallController.cs b/src/Umbraco.Web/Install/Controllers/InstallController.cs index 3d6f397f9f..572bd0a192 100644 --- a/src/Umbraco.Web/Install/Controllers/InstallController.cs +++ b/src/Umbraco.Web/Install/Controllers/InstallController.cs @@ -37,30 +37,23 @@ namespace Umbraco.Web.Install.Controllers [HttpGet] public ActionResult Index() { - //if this is not an upgrade we will log in with the default user. - // It's not considered an upgrade if the ConfigurationStatus is missing or empty or if the db is not configured. - if (string.IsNullOrWhiteSpace(GlobalSettings.ConfigurationStatus) == false - && ApplicationContext.Current.DatabaseContext.IsDatabaseConfigured) + if (ApplicationContext.Current.IsConfigured) { - Version current; - if (Version.TryParse(GlobalSettings.ConfigurationStatus, out current)) - { - //check if we are on the current version, and not let the installer execute - if (current == UmbracoVersion.Current) - { - return Redirect(SystemDirectories.Umbraco.EnsureEndsWith('/')); - } - } + return Redirect(SystemDirectories.Umbraco.EnsureEndsWith('/')); + } + if (ApplicationContext.Current.IsUpgrading) + { var result = _umbracoContext.Security.ValidateCurrentUser(false); switch (result) { case ValidateRequestAttempt.FailedNoPrivileges: - case ValidateRequestAttempt.FailedNoContextId: - return Redirect(SystemDirectories.Umbraco + "/AuthorizeUpgrade?redir=" + Server.UrlEncode(Request.RawUrl)); + case ValidateRequestAttempt.FailedNoContextId: + return Redirect(SystemDirectories.Umbraco + "/AuthorizeUpgrade?redir=" + Server.UrlEncode(Request.RawUrl)); } - } + } + //gen the install base url ViewBag.InstallApiBaseUrl = Url.GetUmbracoApiService("GetSetup", "InstallApi", "UmbracoInstall").TrimEnd("GetSetup"); diff --git a/src/Umbraco.Web/Install/FilePermissionHelper.cs b/src/Umbraco.Web/Install/FilePermissionHelper.cs index 013f40fc0e..e9835e0f32 100644 --- a/src/Umbraco.Web/Install/FilePermissionHelper.cs +++ b/src/Umbraco.Web/Install/FilePermissionHelper.cs @@ -42,8 +42,10 @@ namespace Umbraco.Web.Install { errorReport = new List(); bool succes = true; - foreach (string dir in PermissionDirs) + foreach (string dir in directories) { + if (Directory.Exists(dir) == false) continue; + bool result = SaveAndDeleteFile(IOHelper.MapPath(dir + "/configWizardPermissionTest.txt")); if (result == false) @@ -98,7 +100,7 @@ namespace Umbraco.Web.Install // that and we might get lock issues. try { - var xmlFile = content.Instance.UmbracoXmlDiskCacheFileName + ".tmp"; + var xmlFile = content.GetUmbracoXmlDiskFileName() + ".tmp"; SaveAndDeleteFile(xmlFile); return true; } diff --git a/src/Umbraco.Web/Install/HttpInstallAuthorizeAttribute.cs b/src/Umbraco.Web/Install/HttpInstallAuthorizeAttribute.cs index bab2bddc40..4559a42740 100644 --- a/src/Umbraco.Web/Install/HttpInstallAuthorizeAttribute.cs +++ b/src/Umbraco.Web/Install/HttpInstallAuthorizeAttribute.cs @@ -5,6 +5,7 @@ using System.Web.Http.Controllers; using Umbraco.Core; using Umbraco.Web.Security; using umbraco.BasePages; +using Umbraco.Core.Logging; namespace Umbraco.Web.Install { @@ -52,6 +53,7 @@ namespace Umbraco.Web.Install return true; } var umbCtx = GetUmbracoContext(); + //otherwise we need to ensure that a user is logged in var isLoggedIn = GetUmbracoContext().Security.ValidateCurrentUser(); if (isLoggedIn) @@ -60,8 +62,9 @@ namespace Umbraco.Web.Install } return false; } - catch (Exception) + catch (Exception ex) { + LogHelper.Error("An error occurred determining authorization", ex); return false; } } diff --git a/src/Umbraco.Web/Install/InstallHelper.cs b/src/Umbraco.Web/Install/InstallHelper.cs index c882a83cd8..143067f6a1 100644 --- a/src/Umbraco.Web/Install/InstallHelper.cs +++ b/src/Umbraco.Web/Install/InstallHelper.cs @@ -46,6 +46,7 @@ namespace Umbraco.Web.Install new UpgradeStep(), new FilePermissionsStep(), new MajorVersion7UpgradeReport(_umbContext.Application), + new Version73FileCleanup(_umbContext.HttpContext, _umbContext.Application.ProfilingLogger.Logger), new DatabaseConfigureStep(_umbContext.Application), new DatabaseInstallStep(_umbContext.Application), new DatabaseUpgradeStep(_umbContext.Application), @@ -102,9 +103,9 @@ namespace Umbraco.Web.Install string userAgent = _umbContext.HttpContext.Request.UserAgent; // Check for current install Id - Guid installId = Guid.NewGuid(); - StateHelper.Cookies.Cookie installCookie = new StateHelper.Cookies.Cookie("umb_installId", 1); - if (!String.IsNullOrEmpty(installCookie.GetValue())) + var installId = Guid.NewGuid(); + var installCookie = new StateHelper.Cookies.Cookie("umb_installId", 1); + if (string.IsNullOrEmpty(installCookie.GetValue()) == false) { if (Guid.TryParse(installCookie.GetValue(), out installId)) { @@ -115,13 +116,13 @@ namespace Umbraco.Web.Install } installCookie.SetValue(installId.ToString()); - string dbProvider = String.Empty; - if (!IsBrandNewInstall) + string dbProvider = string.Empty; + if (IsBrandNewInstall == false) dbProvider = ApplicationContext.Current.DatabaseContext.DatabaseProvider.ToString(); org.umbraco.update.CheckForUpgrade check = new org.umbraco.update.CheckForUpgrade(); check.Install(installId, - !IsBrandNewInstall, + IsBrandNewInstall == false, isCompleted, DateTime.Now, UmbracoVersion.Current.Major, @@ -134,7 +135,7 @@ namespace Umbraco.Web.Install } catch (Exception ex) { - + LogHelper.Error("An error occurred in InstallStatus trying to check upgrades", ex); } } diff --git a/src/Umbraco.Web/Install/InstallSteps/DatabaseInstallStep.cs b/src/Umbraco.Web/Install/InstallSteps/DatabaseInstallStep.cs index dc3c27c7f5..5508197860 100644 --- a/src/Umbraco.Web/Install/InstallSteps/DatabaseInstallStep.cs +++ b/src/Umbraco.Web/Install/InstallSteps/DatabaseInstallStep.cs @@ -24,7 +24,7 @@ namespace Umbraco.Web.Install.InstallSteps if (_applicationContext.IsConfigured) throw new Exception("Umbraco is already configured!"); - var result = _applicationContext.DatabaseContext.CreateDatabaseSchemaAndData(); + var result = _applicationContext.DatabaseContext.CreateDatabaseSchemaAndData(_applicationContext); if (result.Success == false) { diff --git a/src/Umbraco.Web/Install/InstallSteps/DatabaseUpgradeStep.cs b/src/Umbraco.Web/Install/InstallSteps/DatabaseUpgradeStep.cs index c6140f41db..dff79d07c7 100644 --- a/src/Umbraco.Web/Install/InstallSteps/DatabaseUpgradeStep.cs +++ b/src/Umbraco.Web/Install/InstallSteps/DatabaseUpgradeStep.cs @@ -31,7 +31,7 @@ namespace Umbraco.Web.Install.InstallSteps { LogHelper.Info("Running 'Upgrade' service"); - var result = _applicationContext.DatabaseContext.UpgradeSchemaAndData(MigrationResolver.Current); + var result = _applicationContext.DatabaseContext.UpgradeSchemaAndData(_applicationContext.Services.MigrationEntryService); if (result.Success == false) { diff --git a/src/Umbraco.Web/Install/InstallSteps/SetUmbracoVersionStep.cs b/src/Umbraco.Web/Install/InstallSteps/SetUmbracoVersionStep.cs index e4f18fdcab..6523925505 100644 --- a/src/Umbraco.Web/Install/InstallSteps/SetUmbracoVersionStep.cs +++ b/src/Umbraco.Web/Install/InstallSteps/SetUmbracoVersionStep.cs @@ -34,7 +34,7 @@ namespace Umbraco.Web.Install.InstallSteps DistributedCache.Instance.RefreshAllPageCache(); // Update configurationStatus - GlobalSettings.ConfigurationStatus = UmbracoVersion.Current.ToString(3); + GlobalSettings.ConfigurationStatus = UmbracoVersion.GetSemanticVersion().ToSemanticString(); // Update ClientDependency version var clientDependencyConfig = new ClientDependencyConfiguration(_applicationContext.ProfilingLogger.Logger); @@ -43,14 +43,8 @@ namespace Umbraco.Web.Install.InstallSteps var security = new WebSecurity(_httpContext, _applicationContext); security.PerformLogin(0); - ////Clear the auth cookie - this is required so that the login screen is displayed after upgrade and so the - //// csrf anti-forgery tokens are created, otherwise there will just be JS errors if the user has an old - //// login token from a previous version when we didn't have csrf tokens in place - //var security = new WebSecurity(new HttpContextWrapper(Context), ApplicationContext.Current); - //security.ClearCurrentLogin(); - //reports the ended install - InstallHelper ih = new InstallHelper(UmbracoContext.Current); + var ih = new InstallHelper(UmbracoContext.Current); ih.InstallStatus(true, ""); return null; diff --git a/src/Umbraco.Web/Install/InstallSteps/Version73FileCleanup.cs b/src/Umbraco.Web/Install/InstallSteps/Version73FileCleanup.cs new file mode 100644 index 0000000000..f15463dc5f --- /dev/null +++ b/src/Umbraco.Web/Install/InstallSteps/Version73FileCleanup.cs @@ -0,0 +1,90 @@ +using System; +using System.IO; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Web; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Web.Install.Models; + +namespace Umbraco.Web.Install.InstallSteps +{ + [InstallSetupStep(InstallationType.Upgrade, + "Version73FileCleanup", 2, + "Performing some housecleaning...", + PerformsAppRestart = true)] + internal class Version73FileCleanup : InstallSetupStep + { + private readonly HttpContextBase _httpContext; + private readonly ILogger _logger; + + public Version73FileCleanup(HttpContextBase httpContext, ILogger logger) + { + _httpContext = httpContext; + _logger = logger; + } + + /// + /// The step execution method + /// + /// + /// + public override InstallSetupResult Execute(object model) + { + //first cleanup all web.configs + + var root = new DirectoryInfo(_httpContext.Server.MapPath("~/")); + ProcessWebConfigs(root); + + //now remove the dll + + var bin = root.GetDirectories("bin", SearchOption.TopDirectoryOnly); + if (bin.Length == 1) + { + var dll = bin[0].GetFiles("Microsoft.Web.Mvc.FixedDisplayModes.dll", SearchOption.TopDirectoryOnly); + if (dll.Length == 1) + { + _logger.Info("Deleting non-compatible and no longer used DLL: {0}", () => dll[0].FullName); + File.Delete(dll[0].FullName); + } + } + + return null; + } + + /// + /// Determines if this step needs to execute based on the current state of the application and/or install process + /// + /// + public override bool RequiresExecution(object model) + { + return UmbracoVersion.Current == Version.Parse("7.3.0"); + } + + private void ProcessWebConfigs(DirectoryInfo dir) + { + //Do the processing of files + var found = dir.GetFiles("web.config", SearchOption.AllDirectories); + + foreach (var configFile in found) + { + var fileName = configFile.FullName; + _logger.Info("Cleaning up web.config file: {0}", () => fileName); + + var contents = File.ReadAllText(fileName); + contents = _microsoftWebHelpers.Replace(contents, string.Empty); + contents = _webPagesRazorVersion.Replace(contents, "$1=3.0.0.0"); + contents = _mvcVersion.Replace(contents, "$1=5.2.3.0"); + using (var writer = new StreamWriter(configFile.FullName, false)) + { + writer.Write(contents); + } + } + } + + private readonly Regex _microsoftWebHelpers = new Regex(@"", RegexOptions.Compiled); + private readonly Regex _webPagesRazorVersion = new Regex(@"(System\.Web\.WebPages\.Razor,\s*?Version)(=2\.0\.0\.0)", RegexOptions.Compiled); + private readonly Regex _mvcVersion = new Regex(@"(System\.Web\.Mvc,\s*?Version)(=4\.0\.0\.0)", RegexOptions.Compiled); + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Install/UmbracoInstallArea.cs b/src/Umbraco.Web/Install/UmbracoInstallArea.cs index c1e47f4a66..789fe854f4 100644 --- a/src/Umbraco.Web/Install/UmbracoInstallArea.cs +++ b/src/Umbraco.Web/Install/UmbracoInstallArea.cs @@ -3,7 +3,6 @@ using System.Linq; using System.Web.Http; using System.Web.Mvc; using System.Web.Routing; -using Microsoft.Web.Mvc; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Web.Editors; diff --git a/src/Umbraco.Web/Macros/PartialViewMacroEngine.cs b/src/Umbraco.Web/Macros/PartialViewMacroEngine.cs index bf3586a174..b4171add35 100644 --- a/src/Umbraco.Web/Macros/PartialViewMacroEngine.cs +++ b/src/Umbraco.Web/Macros/PartialViewMacroEngine.cs @@ -140,12 +140,11 @@ namespace Umbraco.Web.Macros string output; using (var controller = new PartialViewMacroController(macro, content)) { - //bubble up the model state from the main view context to our custom controller. - //when merging we'll create a new dictionary, otherwise you might run into an enumeration error - // caused from ModelStateDictionary - controller.ModelState.Merge(new ModelStateDictionary(viewContext.ViewData.ModelState)); + controller.ViewData = viewContext.ViewData; + controller.ControllerContext = new ControllerContext(request, controller); - //call the action to render + + //call the action to render var result = controller.Index(); output = controller.RenderViewResultAsString(result); } diff --git a/src/Umbraco.Web/Media/EmbedProviders/AbstractOEmbedProvider.cs b/src/Umbraco.Web/Media/EmbedProviders/AbstractOEmbedProvider.cs index 22b912e8b1..51622ed504 100644 --- a/src/Umbraco.Web/Media/EmbedProviders/AbstractOEmbedProvider.cs +++ b/src/Umbraco.Web/Media/EmbedProviders/AbstractOEmbedProvider.cs @@ -1,10 +1,15 @@ using System.Text; using System.Xml; using System.Collections.Generic; +using System.Net; +using System.Net.Http; +using Newtonsoft.Json; using Umbraco.Core.Media; namespace Umbraco.Web.Media.EmbedProviders { + //TODO: Make all Http calls async + public abstract class AbstractOEmbedProvider: IEmbedProvider { public virtual bool SupportsDimensions @@ -39,16 +44,34 @@ namespace Umbraco.Web.Media.EmbedProviders return fullUrl.ToString(); } + public virtual string DownloadResponse(string url) + { + using (var webClient = new WebClient()) + { + return webClient.DownloadString(url); + } + } + + public virtual T GetJsonResponse(string url) where T : class + { + var response = DownloadResponse(url); + return JsonConvert.DeserializeObject(response); + } + public virtual XmlDocument GetXmlResponse(string url) { - var webClient = new System.Net.WebClient(); - - var response = webClient.DownloadString(url); - + var response = DownloadResponse(url); var doc = new XmlDocument(); doc.LoadXml(response); return doc; } + + public virtual string GetXmlProperty(XmlDocument doc, string property) + { + var selectSingleNode = doc.SelectSingleNode(property); + return selectSingleNode != null ? selectSingleNode.InnerText : string.Empty; + } + } } \ No newline at end of file diff --git a/src/Umbraco.Web/Media/EmbedProviders/Flickr.cs b/src/Umbraco.Web/Media/EmbedProviders/Flickr.cs index 3e9cae2675..944513ee4f 100644 --- a/src/Umbraco.Web/Media/EmbedProviders/Flickr.cs +++ b/src/Umbraco.Web/Media/EmbedProviders/Flickr.cs @@ -1,21 +1,25 @@ -using System.Web; - -namespace Umbraco.Web.Media.EmbedProviders -{ - public class Flickr : AbstractOEmbedProvider - { - public override string GetMarkup(string url, int maxWidth, int maxHeight) - { - var flickrUrl = BuildFullUrl(url, maxWidth, maxHeight); - var doc = GetXmlResponse(flickrUrl); - - string imageUrl = doc.SelectSingleNode("/oembed/url").InnerText; - string imageWidth = doc.SelectSingleNode("/oembed/width").InnerText; - string imageHeight = doc.SelectSingleNode("/oembed/height").InnerText; - string imageTitle = doc.SelectSingleNode("/oembed/title").InnerText; - - return string.Format("\"{3}\"", - imageUrl, imageWidth, imageHeight, HttpUtility.HtmlEncode(imageTitle)); - } - } +using System; +using System.ComponentModel; +using System.Web; + +namespace Umbraco.Web.Media.EmbedProviders +{ + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("This is no longer used and will be removed from the codebase in the future, for Flickr, use the Umbraco.Web.Media.EmbedProviders.OEmbedPhoto provider")] + public class Flickr : AbstractOEmbedProvider + { + public override string GetMarkup(string url, int maxWidth, int maxHeight) + { + var flickrUrl = BuildFullUrl(url, maxWidth, maxHeight); + var doc = GetXmlResponse(flickrUrl); + + string imageUrl = doc.SelectSingleNode("/oembed/url").InnerText; + string imageWidth = doc.SelectSingleNode("/oembed/width").InnerText; + string imageHeight = doc.SelectSingleNode("/oembed/height").InnerText; + string imageTitle = doc.SelectSingleNode("/oembed/title").InnerText; + + return string.Format("\"{3}\"", + imageUrl, imageWidth, imageHeight, HttpUtility.HtmlEncode(imageTitle)); + } + } } \ No newline at end of file diff --git a/src/Umbraco.Web/Media/EmbedProviders/OEmbedJson.cs b/src/Umbraco.Web/Media/EmbedProviders/OEmbedJson.cs new file mode 100644 index 0000000000..161294be76 --- /dev/null +++ b/src/Umbraco.Web/Media/EmbedProviders/OEmbedJson.cs @@ -0,0 +1,13 @@ +namespace Umbraco.Web.Media.EmbedProviders +{ + public class OEmbedJson : AbstractOEmbedProvider + { + public override string GetMarkup(string url, int maxWidth, int maxHeight) + { + string requestUrl = BuildFullUrl(url, maxWidth, maxHeight); + + var jsonResponse = GetJsonResponse(requestUrl); + return jsonResponse.GetHtml(); + } + } +} diff --git a/src/Umbraco.Web/Media/EmbedProviders/OEmbedPhoto.cs b/src/Umbraco.Web/Media/EmbedProviders/OEmbedPhoto.cs new file mode 100644 index 0000000000..acc75cb876 --- /dev/null +++ b/src/Umbraco.Web/Media/EmbedProviders/OEmbedPhoto.cs @@ -0,0 +1,22 @@ +using System.Web; +using System.Xml; + +namespace Umbraco.Web.Media.EmbedProviders +{ + public class OEmbedPhoto : AbstractOEmbedProvider + { + public override string GetMarkup(string url, int maxWidth, int maxHeight) + { + string requestUrl = BuildFullUrl(url, maxWidth, maxHeight); + + XmlDocument doc = GetXmlResponse(requestUrl); + string imageUrl = GetXmlProperty(doc, "/oembed/url"); + string imageWidth = GetXmlProperty(doc, "/oembed/width"); + string imageHeight = GetXmlProperty(doc, "/oembed/height"); + string imageTitle = GetXmlProperty(doc, "/oembed/title"); + + return string.Format("\"{3}\"", + imageUrl, imageWidth, imageHeight, HttpUtility.HtmlEncode(imageTitle)); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Media/EmbedProviders/OEmbedResponse.cs b/src/Umbraco.Web/Media/EmbedProviders/OEmbedResponse.cs new file mode 100644 index 0000000000..84164bc47f --- /dev/null +++ b/src/Umbraco.Web/Media/EmbedProviders/OEmbedResponse.cs @@ -0,0 +1,61 @@ +using System.Text; +using System.Web; +using Newtonsoft.Json; + +namespace Umbraco.Web.Media.EmbedProviders +{ + /// + /// Wrapper class for OEmbed response + /// + public class OEmbedResponse + { + public string Type { get; set; } + + public string Version { get; set; } + + public string Title { get; set; } + + [JsonProperty("author_name")] + public string AuthorName { get; set; } + + [JsonProperty("author_url")] + public string AuthorUrl { get; set; } + + [JsonProperty("provider_name")] + public string ProviderName { get; set; } + + [JsonProperty("provider_url")] + public string ProviderUrl { get; set; } + + [JsonProperty("thumbnail_url")] + public string ThumbnailUrl { get; set; } + + [JsonProperty("thumbnail_height")] + public int? ThumbnailHeight { get; set; } + + [JsonProperty("thumbnail_width")] + public int? ThumbnailWidth { get; set; } + + public string Html { get; set; } + + public string Url { get; set; } + + public int? Height { get; set; } + + public int? Width { get; set; } + + /// + /// Gets the HTML. + /// + /// The response html + public string GetHtml() + { + if (Type == "photo") + { + return "\"""; + } + + return string.IsNullOrEmpty(Html) == false ? Html : string.Empty; + } + } +} diff --git a/src/Umbraco.Web/Media/EmbedProviders/OEmbedVideo.cs b/src/Umbraco.Web/Media/EmbedProviders/OEmbedVideo.cs index c0e1e3f986..3fb0b3d1e0 100644 --- a/src/Umbraco.Web/Media/EmbedProviders/OEmbedVideo.cs +++ b/src/Umbraco.Web/Media/EmbedProviders/OEmbedVideo.cs @@ -6,12 +6,10 @@ namespace Umbraco.Web.Media.EmbedProviders { public override string GetMarkup(string url, int maxWidth, int maxHeight) { - string videoUrl = BuildFullUrl(url, maxWidth, maxHeight) ; - - XmlDocument doc = GetXmlResponse(videoUrl); - - // add xslt transformation to return markup - return doc.SelectSingleNode("/oembed/html").InnerText; + string requestUrl = BuildFullUrl(url, maxWidth, maxHeight); + + XmlDocument doc = GetXmlResponse(requestUrl); + return GetXmlProperty(doc, "/oembed/html"); } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Media/EmbedProviders/Twitgoo.cs b/src/Umbraco.Web/Media/EmbedProviders/Twitgoo.cs index 624c0634d8..a28dd97809 100644 --- a/src/Umbraco.Web/Media/EmbedProviders/Twitgoo.cs +++ b/src/Umbraco.Web/Media/EmbedProviders/Twitgoo.cs @@ -6,10 +6,7 @@ namespace Umbraco.Web.Media.EmbedProviders { public override bool SupportsDimensions { - get - { - return false; - } + get { return false; } } public override string GetMarkup(string url, int maxWidth, int maxHeight) diff --git a/src/Umbraco.Web/Media/ThumbnailProviders/ImageThumbnailProvider.cs b/src/Umbraco.Web/Media/ThumbnailProviders/ImageThumbnailProvider.cs index 2e59eece7f..9e02e4a0c8 100644 --- a/src/Umbraco.Web/Media/ThumbnailProviders/ImageThumbnailProvider.cs +++ b/src/Umbraco.Web/Media/ThumbnailProviders/ImageThumbnailProvider.cs @@ -40,7 +40,7 @@ namespace Umbraco.Web.Media.ThumbnailProviders return false; // Make sure the thumbnail exists - var tmpThumbUrl = fileUrl.Replace(ext, "_thumb.jpg"); + var tmpThumbUrl = fileUrl.Replace(ext, "_thumb" + ext); try { diff --git a/src/Umbraco.Web/Media/ThumbnailProviders/ThumbnailProvidersResolver.cs b/src/Umbraco.Web/Media/ThumbnailProviders/ThumbnailProvidersResolver.cs index b6007dcc14..34fee93fb0 100644 --- a/src/Umbraco.Web/Media/ThumbnailProviders/ThumbnailProvidersResolver.cs +++ b/src/Umbraco.Web/Media/ThumbnailProviders/ThumbnailProvidersResolver.cs @@ -13,7 +13,7 @@ using umbraco.BusinessLogic.Utils; namespace Umbraco.Web.Media.ThumbnailProviders { - internal sealed class ThumbnailProvidersResolver : ContainerManyObjectsResolver + public sealed class ThumbnailProvidersResolver : ManyObjectsResolverBase { /// /// Constructor diff --git a/src/Umbraco.Web/ModelStateExtensions.cs b/src/Umbraco.Web/ModelStateExtensions.cs index 08868e4e5f..5df479ce76 100644 --- a/src/Umbraco.Web/ModelStateExtensions.cs +++ b/src/Umbraco.Web/ModelStateExtensions.cs @@ -64,7 +64,7 @@ namespace Umbraco.Web if (!result.MemberNames.Any()) { //add a model state error for the entire property - modelState.AddModelError(string.Format("{0}.{1}", "Properties", propertyAlias), result.ErrorMessage); + modelState.AddModelError(string.Format("{0}.{1}", "_Properties", propertyAlias), result.ErrorMessage); } else { @@ -72,7 +72,7 @@ namespace Umbraco.Web // so that we can try to match it up to a real sub field of this editor foreach (var field in result.MemberNames) { - modelState.AddModelError(string.Format("{0}.{1}.{2}", "Properties", propertyAlias, field), result.ErrorMessage); + modelState.AddModelError(string.Format("{0}.{1}.{2}", "_Properties", propertyAlias, field), result.ErrorMessage); } } } diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentItemDisplayBase.cs b/src/Umbraco.Web/Models/ContentEditing/ContentItemDisplayBase.cs index c01470f7e4..87de7b435c 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentItemDisplayBase.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentItemDisplayBase.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.ComponentModel; using System.Runtime.Serialization; using Umbraco.Core.Models; @@ -11,6 +12,7 @@ namespace Umbraco.Web.Models.ContentEditing protected ContentItemDisplayBase() { Notifications = new List(); + Errors = new Dictionary(); } /// @@ -29,6 +31,7 @@ namespace Umbraco.Web.Models.ContentEditing /// This is used to add custom localized messages/strings to the response for the app to use for localized UI purposes. /// [DataMember(Name = "notifications")] + [ReadOnly(true)] public List Notifications { get; private set; } /// @@ -42,6 +45,7 @@ namespace Umbraco.Web.Models.ContentEditing /// NOTE: The ProperCase is important because when we return ModeState normally it will always be proper case. /// [DataMember(Name = "ModelState")] + [ReadOnly(true)] public IDictionary Errors { get; set; } } } diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentPropertyDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/ContentPropertyDisplay.cs index a563910d4f..343b018000 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentPropertyDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentPropertyDisplay.cs @@ -34,6 +34,6 @@ namespace Umbraco.Web.Models.ContentEditing public bool HideLabel { get; set; } [DataMember(Name = "validation")] - public PropertyTypeValidation Validation { get; set; } + public PropertyTypeValidation Validation { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentTypeBasic.cs b/src/Umbraco.Web/Models/ContentEditing/ContentTypeBasic.cs index 2e527cfd93..8dde1f1f97 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentTypeBasic.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentTypeBasic.cs @@ -1,7 +1,11 @@ -using System.ComponentModel.DataAnnotations; +using System; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; using Umbraco.Core; +using Umbraco.Core.Configuration; using Umbraco.Core.IO; +using Umbraco.Core.Models.Validation; namespace Umbraco.Web.Models.ContentEditing { @@ -14,6 +18,21 @@ namespace Umbraco.Web.Models.ContentEditing [DataContract(Name = "contentType", Namespace = "")] public class ContentTypeBasic : EntityBasic { + /// + /// Overridden to apply our own validation attributes since this is not always required for other classes + /// + [Required] + [RegularExpression(@"^([a-zA-Z]\w.*)$", ErrorMessage = "Invalid alias")] + [DataMember(Name = "alias")] + public override string Alias { get; set; } + + [DataMember(Name = "updateDate")] + [ReadOnly(true)] + public DateTime UpdateDate { get; set; } + + [DataMember(Name = "createDate")] + [ReadOnly(true)] + public DateTime CreateDate { get; set; } [DataMember(Name = "description")] public string Description { get; set; } @@ -25,6 +44,7 @@ namespace Umbraco.Web.Models.ContentEditing /// Returns true if the icon represents a CSS class instead of a file path /// [DataMember(Name = "iconIsClass")] + [ReadOnly(true)] public bool IconIsClass { get @@ -42,13 +62,14 @@ namespace Umbraco.Web.Models.ContentEditing /// Returns the icon file path if the icon is not a class, otherwise returns an empty string /// [DataMember(Name = "iconFilePath")] + [ReadOnly(true)] public string IconFilePath { get { return IconIsClass - ? string.Empty - : IOHelper.ResolveUrl("~/umbraco/images/umbraco/" + Icon); + ? string.Empty + : string.Format("{0}images/umbraco/{1}", GlobalSettings.Path.EnsureEndsWith("/"), Icon); } } @@ -56,6 +77,7 @@ namespace Umbraco.Web.Models.ContentEditing /// Returns true if the icon represents a CSS class instead of a file path /// [DataMember(Name = "thumbnailIsClass")] + [ReadOnly(true)] public bool ThumbnailIsClass { get @@ -73,6 +95,7 @@ namespace Umbraco.Web.Models.ContentEditing /// Returns the icon file path if the icon is not a class, otherwise returns an empty string /// [DataMember(Name = "thumbnailFilePath")] + [ReadOnly(true)] public string ThumbnailFilePath { get @@ -83,4 +106,4 @@ namespace Umbraco.Web.Models.ContentEditing } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentTypeCompositionDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/ContentTypeCompositionDisplay.cs new file mode 100644 index 0000000000..9a138dd828 --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/ContentTypeCompositionDisplay.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; +using Umbraco.Core.Models.Validation; + +namespace Umbraco.Web.Models.ContentEditing +{ + [DataContract(Name = "contentType", Namespace = "")] + public class ContentTypeCompositionDisplay : ContentTypeBasic, INotificationModel + { + public ContentTypeCompositionDisplay() + { + //initialize collections so at least their never null + Groups = new List(); + AllowedContentTypes = new List(); + CompositeContentTypes = new List(); + Notifications = new List(); + } + + //name, alias, icon, thumb, desc, inherited from basic + + //List view + [DataMember(Name = "isContainer")] + public bool IsContainer { get; set; } + + [DataMember(Name = "listViewEditorName")] + [ReadOnly(true)] + public string ListViewEditorName { get; set; } + + //Tabs + [DataMember(Name = "groups")] + public IEnumerable Groups { get; set; } + + //Allowed child types + [DataMember(Name = "allowedContentTypes")] + public IEnumerable AllowedContentTypes { get; set; } + + //Compositions + [DataMember(Name = "compositeContentTypes")] + public IEnumerable CompositeContentTypes { get; set; } + + [DataMember(Name = "allowAsRoot")] + public bool AllowAsRoot { get; set; } + + /// + /// This is used to add custom localized messages/strings to the response for the app to use for localized UI purposes. + /// + [DataMember(Name = "notifications")] + [ReadOnly(true)] + public List Notifications { get; private set; } + + /// + /// This is used for validation of a content item. + /// + /// + /// A content item can be invalid but still be saved. This occurs when there's property validation errors, we will + /// still save the item but it cannot be published. So we need a way of returning validation errors as well as the + /// updated model. + /// + /// NOTE: The ProperCase is important because when we return ModeState normally it will always be proper case. + /// + [DataMember(Name = "ModelState")] + [ReadOnly(true)] + public IDictionary Errors { get; set; } + } +} diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentTypeDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/ContentTypeDisplay.cs new file mode 100644 index 0000000000..e0165450ed --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/ContentTypeDisplay.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; + +namespace Umbraco.Web.Models.ContentEditing +{ + + [DataContract(Name = "contentType", Namespace = "")] + public class ContentTypeDisplay : ContentTypeCompositionDisplay + { + public ContentTypeDisplay() + { + //initialize collections so at least their never null + AllowedTemplates = new List(); + } + + //name, alias, icon, thumb, desc, inherited from the content type + + // Templates + [DataMember(Name = "allowedTemplates")] + public IEnumerable AllowedTemplates { get; set; } + + [DataMember(Name = "defaultTemplate")] + public EntityBasic DefaultTemplate { get; set; } + + } +} diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentTypeSave.cs b/src/Umbraco.Web/Models/ContentEditing/ContentTypeSave.cs new file mode 100644 index 0000000000..17eb6bb35e --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/ContentTypeSave.cs @@ -0,0 +1,91 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Runtime.Serialization; +using Umbraco.Core; + +namespace Umbraco.Web.Models.ContentEditing +{ + [DataContract(Name = "contentType", Namespace = "")] + public class ContentTypeSave : ContentTypeBasic, IValidatableObject + { + public ContentTypeSave() + { + //initialize collections so at least their never null + Groups = new List>(); + AllowedContentTypes = new List(); + CompositeContentTypes = new List(); + } + + //Compositions + [DataMember(Name = "compositeContentTypes")] + public IEnumerable CompositeContentTypes { get; set; } + + [DataMember(Name = "isContainer")] + public bool IsContainer { get; set; } + + [DataMember(Name = "allowAsRoot")] + public bool AllowAsRoot { get; set; } + + /// + /// The list of allowed templates to assign (template alias) + /// + [DataMember(Name = "allowedTemplates")] + public IEnumerable AllowedTemplates { get; set; } + + //Allowed child types + [DataMember(Name = "allowedContentTypes")] + public IEnumerable AllowedContentTypes { get; set; } + + /// + /// The default template to assign (template alias) + /// + [DataMember(Name = "defaultTemplate")] + public string DefaultTemplate { get; set; } + + //Tabs + [DataMember(Name = "groups")] + public IEnumerable> Groups { get; set; } + + /// + /// Custom validation + /// + /// + /// + public IEnumerable Validate(ValidationContext validationContext) + { + if (AllowedTemplates.Any(x => x.IsNullOrWhiteSpace())) + yield return new ValidationResult("Template value cannot be null", new[] {"AllowedTemplates"}); + + if (CompositeContentTypes.Any(x => x.IsNullOrWhiteSpace())) + yield return new ValidationResult("Composite Content Type value cannot be null", new[] { "CompositeContentTypes" }); + + var duplicateGroups = Groups.GroupBy(x => x.Name).Where(x => x.Count() > 1).ToArray(); + if (duplicateGroups.Any()) + { + //we need to return the field name with an index so it's wired up correctly + var firstIndex = Groups.IndexOf(duplicateGroups.First().First()); + yield return new ValidationResult("Duplicate group names not allowed", new[] + { + string.Format("Groups[{0}].Name", firstIndex) + }); + } + + var duplicateProperties = Groups.SelectMany(x => x.Properties).Where(x => x.Inherited == false).GroupBy(x => x.Alias).Where(x => x.Count() > 1).ToArray(); + if (duplicateProperties.Any()) + { + //we need to return the field name with an index so it's wired up correctly + var firstProperty = duplicateProperties.First().First(); + var propertyGroup = Groups.Single(x => x.Properties.Contains(firstProperty)); + var groupIndex = Groups.IndexOf(propertyGroup); + var propertyIndex = propertyGroup.Properties.IndexOf(firstProperty); + + yield return new ValidationResult("Duplicate property aliases not allowed", new[] + { + string.Format("Groups[{0}].Properties[{1}].Alias", groupIndex, propertyIndex) + }); + } + + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ContentEditing/DataTypeBasic.cs b/src/Umbraco.Web/Models/ContentEditing/DataTypeBasic.cs index 0efb67f567..1165ec8fd6 100644 --- a/src/Umbraco.Web/Models/ContentEditing/DataTypeBasic.cs +++ b/src/Umbraco.Web/Models/ContentEditing/DataTypeBasic.cs @@ -15,5 +15,13 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "isSystem")] [ReadOnly(true)] public bool IsSystemDataType { get; set; } + + [DataMember(Name = "group")] + [ReadOnly(true)] + public string Group { get; set; } + + [DataMember(Name = "hasPrevalues")] + [ReadOnly(true)] + public bool HasPrevalues { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ContentEditing/EntityBasic.cs b/src/Umbraco.Web/Models/ContentEditing/EntityBasic.cs index 520ff677b0..40d884d653 100644 --- a/src/Umbraco.Web/Models/ContentEditing/EntityBasic.cs +++ b/src/Umbraco.Web/Models/ContentEditing/EntityBasic.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Runtime.Serialization; @@ -29,6 +30,7 @@ namespace Umbraco.Web.Models.ContentEditing public string Icon { get; set; } [DataMember(Name = "trashed")] + [ReadOnly(true)] public bool Trashed { get; set; } /// @@ -44,8 +46,11 @@ namespace Umbraco.Web.Models.ContentEditing /// /// This will only be populated for some entities like macros /// + /// + /// This is overrideable to specify different validation attributes if required + /// [DataMember(Name = "alias")] - public string Alias { get; set; } + public virtual string Alias { get; set; } /// /// The path of the entity @@ -57,6 +62,7 @@ namespace Umbraco.Web.Models.ContentEditing /// A collection of extra data that is available for this specific entity/entity type /// [DataMember(Name = "metaData")] + [ReadOnly(true)] public IDictionary AdditionalData { get; private set; } } } diff --git a/src/Umbraco.Web/Models/ContentEditing/MoveOrCopy.cs b/src/Umbraco.Web/Models/ContentEditing/MoveOrCopy.cs index a2cc45c446..a387876a45 100644 --- a/src/Umbraco.Web/Models/ContentEditing/MoveOrCopy.cs +++ b/src/Umbraco.Web/Models/ContentEditing/MoveOrCopy.cs @@ -33,6 +33,13 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "relateToOriginal", IsRequired = true)] [Required] public bool RelateToOriginal { get; set; } + + /// + /// Boolean indicating whether copying the object should be recursive + /// + [DataMember(Name = "recursive", IsRequired = true)] + [Required] + public bool Recursive { get; set; } } } diff --git a/src/Umbraco.Web/Models/ContentEditing/Notification.cs b/src/Umbraco.Web/Models/ContentEditing/Notification.cs index cc9ffb5010..0f042b6cbc 100644 --- a/src/Umbraco.Web/Models/ContentEditing/Notification.cs +++ b/src/Umbraco.Web/Models/ContentEditing/Notification.cs @@ -6,6 +6,18 @@ namespace Umbraco.Web.Models.ContentEditing [DataContract(Name = "notification", Namespace = "")] public class Notification { + public Notification() + { + + } + + public Notification(string header, string message, SpeechBubbleIcon notificationType) + { + Header = header; + Message = message; + NotificationType = notificationType; + } + [DataMember(Name = "header")] public string Header { get; set; } [DataMember(Name = "message")] diff --git a/src/Umbraco.Web/Models/ContentEditing/PropertyEditorBasic.cs b/src/Umbraco.Web/Models/ContentEditing/PropertyEditorBasic.cs index 17df4fde63..50b8641b19 100644 --- a/src/Umbraco.Web/Models/ContentEditing/PropertyEditorBasic.cs +++ b/src/Umbraco.Web/Models/ContentEditing/PropertyEditorBasic.cs @@ -14,5 +14,8 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "name")] public string Name { get; set; } + + [DataMember(Name = "icon")] + public string Icon { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ContentEditing/PropertyGroupBasic.cs b/src/Umbraco.Web/Models/ContentEditing/PropertyGroupBasic.cs new file mode 100644 index 0000000000..b8560ba342 --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/PropertyGroupBasic.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models.ContentEditing +{ + [DataContract(Name = "propertyGroup", Namespace = "")] + public class PropertyGroupBasic + where TPropertyType: PropertyTypeBasic + { + public PropertyGroupBasic() + { + Properties = new List(); + } + + /// + /// Gets the special generic properties tab identifier. + /// + public const int GenericPropertiesGroupId = -666; + + /// + /// Gets a value indicating whether this tab is the generic properties tab. + /// + [IgnoreDataMember] + public bool IsGenericProperties { get { return Id == GenericPropertiesGroupId; } } + + /// + /// Gets a value indicating whether the property group is inherited through + /// content types composition. + /// + /// A property group can be inherited and defined on the content type + /// currently being edited, at the same time. Inherited is true when there exists at least + /// one property group higher in the composition, with the same alias. + [DataMember(Name = "inherited")] + public bool Inherited { get; set; } + + // needed - so we can handle alias renames + [DataMember(Name = "id")] + public int Id { get; set; } + + [DataMember(Name = "properties")] + public IEnumerable Properties { get; set; } + + [DataMember(Name = "sortOrder")] + public int SortOrder { get; set; } + + [Required] + [DataMember(Name = "name")] + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ContentEditing/PropertyGroupDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/PropertyGroupDisplay.cs new file mode 100644 index 0000000000..1bae9c96d5 --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/PropertyGroupDisplay.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using System.ComponentModel; +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models.ContentEditing +{ + [DataContract(Name = "propertyGroup", Namespace = "")] + public class PropertyGroupDisplay : PropertyGroupBasic + { + public PropertyGroupDisplay() + { + Properties = new List(); + ParentTabContentTypeNames = new List(); + ParentTabContentTypes = new List(); + } + + /// + /// Gets the context content type. + /// + [DataMember(Name = "contentTypeId")] + [ReadOnly(true)] + public int ContentTypeId { get; set; } + + /// + /// Gets the identifiers of the content types that define this group. + /// + [DataMember(Name = "parentTabContentTypes")] + [ReadOnly(true)] + public IEnumerable ParentTabContentTypes { get; set; } + + /// + /// Gets the name of the content types that define this group. + /// + [DataMember(Name = "parentTabContentTypeNames")] + [ReadOnly(true)] + public IEnumerable ParentTabContentTypeNames { get; set; } + } +} diff --git a/src/Umbraco.Web/Models/ContentEditing/PropertyTypeBasic.cs b/src/Umbraco.Web/Models/ContentEditing/PropertyTypeBasic.cs new file mode 100644 index 0000000000..107ae287fa --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/PropertyTypeBasic.cs @@ -0,0 +1,50 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models.ContentEditing +{ + [DataContract(Name = "propertyType")] + public class PropertyTypeBasic + { + /// + /// Gets a value indicating whether the property type is inherited through + /// content types composition. + /// + /// Inherited is true when the property is defined by a content type + /// higher in the composition, and not by the content type currently being + /// edited. + [DataMember(Name = "inherited")] + public bool Inherited { get; set; } + + // needed - so we can handle alias renames + [DataMember(Name = "id")] + public int Id { get; set; } + + [Required] + [RegularExpression(@"^([a-zA-Z]\w.*)$", ErrorMessage = "Invalid alias")] + [DataMember(Name = "alias")] + public string Alias { get; set; } + + [DataMember(Name = "description")] + public string Description { get; set; } + + [DataMember(Name = "validation")] + public PropertyTypeValidation Validation { get; set; } + + [DataMember(Name = "label")] + [Required] + public string Label { get; set; } + + [DataMember(Name = "sortOrder")] + public int SortOrder { get; set; } + + [DataMember(Name = "dataTypeId")] + [Required] + public int DataTypeId { get; set; } + + //SD: Is this really needed ? + [DataMember(Name = "groupId")] + public int GroupId { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ContentEditing/PropertyTypeDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/PropertyTypeDisplay.cs new file mode 100644 index 0000000000..a05924ad5c --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/PropertyTypeDisplay.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; + +namespace Umbraco.Web.Models.ContentEditing +{ + [DataContract(Name = "propertyType")] + public class PropertyTypeDisplay : PropertyTypeBasic + { + [DataMember(Name = "editor")] + [ReadOnly(true)] + public string Editor { get; set; } + + [DataMember(Name = "view")] + [ReadOnly(true)] + public string View { get; set; } + + [DataMember(Name = "config")] + [ReadOnly(true)] + public IDictionary Config { get; set; } + + //SD: Seems strange that this is needed + [DataMember(Name = "contentTypeId")] + [ReadOnly(true)] + public int ContentTypeId { get; set; } + + //SD: Seems strange that this is needed + [DataMember(Name = "contentTypeName")] + [ReadOnly(true)] + public string ContentTypeName { get; set; } + } +} diff --git a/src/Umbraco.Web/Models/ContentEditing/SimpleNotificationModel.cs b/src/Umbraco.Web/Models/ContentEditing/SimpleNotificationModel.cs new file mode 100644 index 0000000000..83b00662c4 --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/SimpleNotificationModel.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models.ContentEditing +{ + [DataContract(Name = "notificationModel", Namespace = "")] + public class SimpleNotificationModel : INotificationModel + { + public SimpleNotificationModel() + { + Notifications = new List(); + } + + public SimpleNotificationModel(params Notification[] notifications) + { + Notifications = new List(notifications); + } + + /// + /// This is used to add custom localized messages/strings to the response for the app to use for localized UI purposes. + /// + [DataMember(Name = "notifications")] + public List Notifications { get; private set; } + + /// + /// A default msg + /// + [DataMember(Name = "message")] + public string Message { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ContentExtensions.cs b/src/Umbraco.Web/Models/ContentExtensions.cs index a832235bb0..37fe9151b0 100644 --- a/src/Umbraco.Web/Models/ContentExtensions.cs +++ b/src/Umbraco.Web/Models/ContentExtensions.cs @@ -80,13 +80,14 @@ namespace Umbraco.Web.Models : domainHelper.DomainForNode(int.Parse(route.Substring(0, pos)), current).UmbracoDomain; } - if (domain == null) + if (domain == null || domain.LanguageIsoCode.IsNullOrWhiteSpace()) return GetDefaultCulture(localizationService); - var wcDomain = DomainHelper.FindWildcardDomainInPath(domainService.GetAll(true), contentPath, domain.RootContent.Id); + var wcDomain = DomainHelper.FindWildcardDomainInPath(domainService.GetAll(true), contentPath, domain.RootContentId); + return wcDomain == null - ? new CultureInfo(domain.Language.IsoCode) - : new CultureInfo(wcDomain.Language.IsoCode); + ? new CultureInfo(domain.LanguageIsoCode) + : new CultureInfo(wcDomain.LanguageIsoCode); } private static CultureInfo GetDefaultCulture(ILocalizationService localizationService) diff --git a/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs index 7684636605..8c3095a455 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs @@ -2,22 +2,16 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; -using System.Linq.Expressions; -using System.Runtime.Serialization; -using System.Threading; using System.Web; using System.Web.Mvc; using System.Web.Routing; using AutoMapper; -using Newtonsoft.Json; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Models.Mapping; -using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Trees; -using umbraco; using Umbraco.Web.Routing; using umbraco.BusinessLogic.Actions; @@ -51,11 +45,7 @@ namespace Umbraco.Web.Models.Mapping .ForMember( dto => dto.IsContainer, expression => expression.MapFrom(content => content.ContentType.IsContainer)) - .ForMember( - dto => dto.IsChildOfListView, - //TODO: Fix this shorthand .Parent() lookup, at least have an overload to use the current - // application context so it's testable! - expression => expression.MapFrom(content => content.Parent().ContentType.IsContainer)) + .ForMember(display => display.IsChildOfListView, expression => expression.Ignore()) .ForMember( dto => dto.Trashed, expression => expression.MapFrom(content => content.Trashed)) @@ -78,7 +68,8 @@ namespace Umbraco.Web.Models.Mapping .ForMember(display => display.Tabs, expression => expression.ResolveUsing()) .ForMember(display => display.AllowedActions, expression => expression.ResolveUsing( new ActionButtonsResolver(new Lazy(() => applicationContext.Services.UserService)))) - .AfterMap((media, display) => AfterMap(media, display, applicationContext.Services.DataTypeService, applicationContext.Services.TextService)); + .AfterMap((media, display) => AfterMap(media, display, applicationContext.Services.DataTypeService, applicationContext.Services.TextService, + applicationContext.Services.ContentTypeService)); //FROM IContent TO ContentItemBasic config.CreateMap>() @@ -119,8 +110,37 @@ namespace Umbraco.Web.Models.Mapping /// /// /// - private static void AfterMap(IContent content, ContentItemDisplay display, IDataTypeService dataTypeService, ILocalizedTextService localizedText) + /// + private static void AfterMap(IContent content, ContentItemDisplay display, IDataTypeService dataTypeService, + ILocalizedTextService localizedText, IContentTypeService contentTypeService) { + //map the IsChildOfListView (this is actually if it is a descendant of a list view!) + //TODO: Fix this shorthand .Ancestors() lookup, at least have an overload to use the current + if (content.HasIdentity) + { + var ancesctorListView = content.Ancestors().FirstOrDefault(x => x.ContentType.IsContainer); + display.IsChildOfListView = ancesctorListView != null; + } + else + { + //it's new so it doesn't have a path, so we need to look this up by it's parent + ancestors + var parent = content.Parent(); + if (parent == null) + { + display.IsChildOfListView = false; + } + else if (parent.ContentType.IsContainer) + { + display.IsChildOfListView = true; + } + else + { + var ancesctorListView = parent.Ancestors().FirstOrDefault(x => x.ContentType.IsContainer); + display.IsChildOfListView = ancesctorListView != null; + } + } + + //map the tree node url if (HttpContext.Current != null) { @@ -142,44 +162,76 @@ namespace Umbraco.Web.Models.Mapping TabsAndPropertiesResolver.AddListView(display, "content", dataTypeService); } - TabsAndPropertiesResolver.MapGenericProperties( - content, display, + var properties = new List + { new ContentPropertyDisplay - { - Alias = string.Format("{0}releasedate", Constants.PropertyEditors.InternalGenericPropertiesPrefix), - Label = localizedText.Localize("content/releaseDate"), - Value = display.ReleaseDate.HasValue ? display.ReleaseDate.Value.ToIsoString() : null, - View = "datepicker" //TODO: Hard coding this because the templatepicker doesn't necessarily need to be a resolvable (real) property editor - }, + { + Alias = string.Format("{0}releasedate", Constants.PropertyEditors.InternalGenericPropertiesPrefix), + Label = localizedText.Localize("content/releaseDate"), + Value = display.ReleaseDate.HasValue ? display.ReleaseDate.Value.ToIsoString() : null, + View = "datepicker" //TODO: Hard coding this because the templatepicker doesn't necessarily need to be a resolvable (real) property editor + }, new ContentPropertyDisplay - { - Alias = string.Format("{0}expiredate", Constants.PropertyEditors.InternalGenericPropertiesPrefix), - Label = localizedText.Localize("content/unpublishDate"), - Value = display.ExpireDate.HasValue ? display.ExpireDate.Value.ToIsoString() : null, - View = "datepicker" //TODO: Hard coding this because the templatepicker doesn't necessarily need to be a resolvable (real) property editor - }, + { + Alias = string.Format("{0}expiredate", Constants.PropertyEditors.InternalGenericPropertiesPrefix), + Label = localizedText.Localize("content/unpublishDate"), + Value = display.ExpireDate.HasValue ? display.ExpireDate.Value.ToIsoString() : null, + View = "datepicker" //TODO: Hard coding this because the templatepicker doesn't necessarily need to be a resolvable (real) property editor + }, new ContentPropertyDisplay + { + Alias = string.Format("{0}template", Constants.PropertyEditors.InternalGenericPropertiesPrefix), + Label = "Template", //TODO: localize this? + Value = display.TemplateAlias, + View = "dropdown", //TODO: Hard coding until we make a real dropdown property editor to lookup + Config = new Dictionary { - Alias = string.Format("{0}template", Constants.PropertyEditors.InternalGenericPropertiesPrefix), - Label = "Template", //TODO: localize this? - Value = display.TemplateAlias, - View = "dropdown", //TODO: Hard coding until we make a real dropdown property editor to lookup - Config = new Dictionary + {"items", templateItemConfig} + } + }, + new ContentPropertyDisplay + { + Alias = string.Format("{0}urls", Constants.PropertyEditors.InternalGenericPropertiesPrefix), + Label = localizedText.Localize("content/urls"), + Value = string.Join(",", display.Urls), + View = "urllist" //TODO: Hard coding this because the templatepicker doesn't necessarily need to be a resolvable (real) property editor + } + }; + + TabsAndPropertiesResolver.MapGenericProperties(content, display, properties.ToArray(), + genericProperties => + { + //TODO: This would be much nicer with the IUmbracoContextAccessor so we don't use singletons + //If this is a web request and there's a user signed in and the + // user has access to the settings section, we will + if (HttpContext.Current != null && UmbracoContext.Current != null && UmbracoContext.Current.Security.CurrentUser != null + && UmbracoContext.Current.Security.CurrentUser.AllowedSections.Any(x => x.Equals(Constants.Applications.Settings))) + { + var currentDocumentType = contentTypeService.GetContentType(display.ContentTypeAlias); + var currentDocumentTypeName = currentDocumentType == null ? string.Empty : currentDocumentType.Name; + + var currentDocumentTypeId = currentDocumentType == null ? string.Empty : currentDocumentType.Id.ToString(CultureInfo.InvariantCulture); + //TODO: Hard coding this is not good + var docTypeLink = string.Format("#/settings/documenttypes/edit/{0}", currentDocumentTypeId); + + //Replace the doc type property + var docTypeProp = genericProperties.First(x => x.Alias == string.Format("{0}doctype", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); + docTypeProp.Value = new List + { + new { - {"items", templateItemConfig} + linkText = currentDocumentTypeName, + url = docTypeLink, + target = "_self", icon = "icon-item-arrangement" } - }, - new ContentPropertyDisplay - { - Alias = string.Format("{0}urls", Constants.PropertyEditors.InternalGenericPropertiesPrefix), - Label = localizedText.Localize("content/urls"), - Value = string.Join(",", display.Urls), - View = "urllist" //TODO: Hard coding this because the templatepicker doesn't necessarily need to be a resolvable (real) property editor - }); + }; + //TODO: Hard coding this because the templatepicker doesn't necessarily need to be a resolvable (real) property editor + docTypeProp.View = "urllist"; + } + }); + } - - /// /// Gets the published date value for the IContent object /// @@ -221,8 +273,16 @@ namespace Umbraco.Web.Models.Mapping } var svc = _userService.Value; - var permissions = svc.GetPermissions(UmbracoContext.Current.Security.CurrentUser, source.Id) - .FirstOrDefault(); + var permissions = svc.GetPermissions( + //TODO: This is certainly not ideal usage here - perhaps the best way to deal with this in the future is + // with the IUmbracoContextAccessor. In the meantime, if used outside of a web app this will throw a null + // refrence exception :( + UmbracoContext.Current.Security.CurrentUser, + // Here we need to do a special check since this could be new content, in which case we need to get the permissions + // from the parent, not the existing one otherwise permissions would be coming from the root since Id is 0. + source.HasIdentity ? source.Id : source.ParentId) + .FirstOrDefault(); + if (permissions == null) { return Enumerable.Empty(); diff --git a/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapper.cs index a09592a49d..39f4a57b32 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapper.cs @@ -1,8 +1,15 @@ -using AutoMapper; +using System; +using System.Linq; +using System.Threading; +using AutoMapper; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Models.Mapping; +using Umbraco.Core.PropertyEditors; using Umbraco.Web.Models.ContentEditing; +using System.Collections.Generic; +using AutoMapper.Internal; +using Property = umbraco.NodeFactory.Property; namespace Umbraco.Web.Models.Mapping { @@ -10,12 +17,227 @@ namespace Umbraco.Web.Models.Mapping /// Defines mappings for content/media/members type mappings /// internal class ContentTypeModelMapper : MapperConfiguration - { + { + private readonly Lazy _propertyEditorResolver; + + //default ctor + public ContentTypeModelMapper() + { + _propertyEditorResolver = new Lazy(() => PropertyEditorResolver.Current); + } + + //ctor can be used for testing + public ContentTypeModelMapper(Lazy propertyEditorResolver) + { + _propertyEditorResolver = propertyEditorResolver; + } + public override void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext) { + + config.CreateMap() + .ConstructUsing(basic => new PropertyType(applicationContext.Services.DataTypeService.GetDataTypeDefinitionById(basic.DataTypeId))) + .ForMember(type => type.ValidationRegExp, expression => expression.ResolveUsing(basic => basic.Validation.Pattern)) + .ForMember(type => type.Mandatory, expression => expression.ResolveUsing(basic => basic.Validation.Mandatory)) + .ForMember(type => type.Name, expression => expression.ResolveUsing(basic => basic.Label)) + .ForMember(type => type.DataTypeDefinitionId, expression => expression.ResolveUsing(basic => basic.DataTypeId)) + .ForMember(type => type.DataTypeId, expression => expression.Ignore()) + .ForMember(type => type.PropertyEditorAlias, expression => expression.Ignore()) + .ForMember(type => type.HelpText, expression => expression.Ignore()) + .ForMember(type => type.Key, expression => expression.Ignore()) + .ForMember(type => type.CreateDate, expression => expression.Ignore()) + .ForMember(type => type.UpdateDate, expression => expression.Ignore()) + .ForMember(type => type.HasIdentity, expression => expression.Ignore()); + + config.CreateMap() + //do the base mapping + .MapBaseContentTypeSaveToEntity(applicationContext) + .ConstructUsing((source) => new ContentType(source.ParentId)) + .ForMember(source => source.AllowedTemplates, expression => expression.Ignore()) + .ForMember(dto => dto.DefaultTemplate, expression => expression.Ignore()) + .AfterMap((source, dest) => + { + dest.AllowedTemplates = source.AllowedTemplates + .Where(x => x != null) + .Select(s => applicationContext.Services.FileService.GetTemplate(s)) + .ToArray(); + + if (source.DefaultTemplate != null) + dest.SetDefaultTemplate(applicationContext.Services.FileService.GetTemplate(source.DefaultTemplate)); + + ContentTypeModelMapperExtensions.AfterMapContentTypeSaveToEntity(source, dest, applicationContext); + }); + + config.CreateMap() + //do the base mapping + .MapBaseContentTypeSaveToEntity(applicationContext) + .ConstructUsing((source) => new MediaType(source.ParentId)) + .AfterMap((source, dest) => + { + ContentTypeModelMapperExtensions.AfterMapContentTypeSaveToEntity(source, dest, applicationContext); + }); + + config.CreateMap() + //do the base mapping + .MapBaseContentTypeSaveToEntity(applicationContext) + .ConstructUsing((source) => new MemberType(source.ParentId)) + .AfterMap((source, dest) => + { + ContentTypeModelMapperExtensions.AfterMapContentTypeSaveToEntity(source, dest, applicationContext); + }); + + config.CreateMap().ConvertUsing(x => x.Alias); + + config.CreateMap() + //map base logic + .MapBaseContentTypeEntityToDisplay(applicationContext, _propertyEditorResolver); + + config.CreateMap() + //map base logic + .MapBaseContentTypeEntityToDisplay(applicationContext, _propertyEditorResolver) + .AfterMap((source, dest) => + { + //default listview + dest.ListViewEditorName = Constants.Conventions.DataTypes.ListViewPrefix + "Media"; + + if (string.IsNullOrEmpty(source.Name) == false) + { + var name = Constants.Conventions.DataTypes.ListViewPrefix + source.Name; + if (applicationContext.Services.DataTypeService.GetDataTypeDefinitionByName(name) != null) + dest.ListViewEditorName = name; + } + }); + + config.CreateMap() + //map base logic + .MapBaseContentTypeEntityToDisplay(applicationContext, _propertyEditorResolver) + .ForMember(dto => dto.AllowedTemplates, expression => expression.Ignore()) + .ForMember(dto => dto.DefaultTemplate, expression => expression.Ignore()) + .ForMember(display => display.Notifications, expression => expression.Ignore()) + .AfterMap((source, dest) => + { + //sync templates + dest.AllowedTemplates = source.AllowedTemplates.Select(Mapper.Map).ToArray(); + + if (source.DefaultTemplate != null) + dest.DefaultTemplate = Mapper.Map(source.DefaultTemplate); + + //default listview + dest.ListViewEditorName = Constants.Conventions.DataTypes.ListViewPrefix + "Content"; + + if (string.IsNullOrEmpty(source.Name) == false) + { + var name = Constants.Conventions.DataTypes.ListViewPrefix + source.Name; + if (applicationContext.Services.DataTypeService.GetDataTypeDefinitionByName(name) != null) + dest.ListViewEditorName = name; + } + + }); + + config.CreateMap(); config.CreateMap(); config.CreateMap(); - config.CreateMap(); + + config.CreateMap() + + .ConstructUsing((PropertyTypeBasic propertyTypeBasic) => + { + var dataType = applicationContext.Services.DataTypeService.GetDataTypeDefinitionById(propertyTypeBasic.DataTypeId); + if (dataType == null) throw new NullReferenceException("No data type found with id " + propertyTypeBasic.DataTypeId); + return new PropertyType(dataType, propertyTypeBasic.Alias); + }) + + //only map if it is actually set + .ForMember(dest => dest.Id, expression => expression.Condition(source => source.Id > 0)) + .ForMember(dto => dto.CreateDate, expression => expression.Ignore()) + .ForMember(dto => dto.UpdateDate, expression => expression.Ignore()) + //only map if it is actually set, if it's not set, it needs to be handled differently and will be taken care of in the + // IContentType.AddPropertyType + .ForMember(dest => dest.PropertyGroupId, expression => expression.Condition(source => source.GroupId > 0)) + .ForMember(type => type.PropertyGroupId, expression => expression.MapFrom(display => new Lazy(() => display.GroupId, false))) + .ForMember(type => type.Key, expression => expression.Ignore()) + .ForMember(type => type.HelpText, expression => expression.Ignore()) + .ForMember(type => type.HasIdentity, expression => expression.Ignore()) + //ignore because this is set in the ctor NOT ON UPDATE, STUPID! + //.ForMember(type => type.Alias, expression => expression.Ignore()) + //ignore because this is obsolete and shouldn't be used + .ForMember(type => type.DataTypeId, expression => expression.Ignore()) + .ForMember(type => type.Mandatory, expression => expression.MapFrom(display => display.Validation.Mandatory)) + .ForMember(type => type.ValidationRegExp, expression => expression.MapFrom(display => display.Validation.Pattern)) + .ForMember(type => type.DataTypeDefinitionId, expression => expression.MapFrom(display => display.DataTypeId)) + .ForMember(type => type.Name, expression => expression.MapFrom(display => display.Label)); + + #region *** Used for mapping on top of an existing display object from a save object *** + + config.CreateMap() + .MapBaseContentTypeSaveToDisplay(); + + config.CreateMap() + .MapBaseContentTypeSaveToDisplay() + .ForMember(dto => dto.AllowedTemplates, expression => expression.Ignore()) + .ForMember(dto => dto.DefaultTemplate, expression => expression.Ignore()) + .AfterMap((source, dest) => + { + //sync templates + var destAllowedTemplateAliases = dest.AllowedTemplates.Select(x => x.Alias); + //if the dest is set and it's the same as the source, then don't change + if (destAllowedTemplateAliases.SequenceEqual(source.AllowedTemplates) == false) + { + var templates = applicationContext.Services.FileService.GetTemplates(source.AllowedTemplates.ToArray()); + dest.AllowedTemplates = source.AllowedTemplates.Select(x => Mapper.Map(templates.Single(t => t.Alias == x))).ToArray(); + } + + if (source.DefaultTemplate.IsNullOrWhiteSpace() == false) + { + //if the dest is set and it's the same as the source, then don't change + if (dest.DefaultTemplate == null || source.DefaultTemplate != dest.DefaultTemplate.Alias) + { + var template = applicationContext.Services.FileService.GetTemplate(source.DefaultTemplate); + dest.DefaultTemplate = template == null ? null : Mapper.Map(template); + } + } + else + { + dest.DefaultTemplate = null; + } + }); + + config.CreateMap, PropertyGroup>() + .ForMember(dest => dest.Id, map => map.Condition(source => source.Id > 0)) + .ForMember(dest => dest.Key, map => map.Ignore()) + .ForMember(dest => dest.HasIdentity, map => map.Ignore()) + .ForMember(dest => dest.CreateDate, map => map.Ignore()) + .ForMember(dest => dest.UpdateDate, map => map.Ignore()) + // fixme + // this is basically *replacing* dest properties by a mapped version of + // *every* source properties (including, I guess, inherited properties?) + // also, ContentTypeModelMapperExtensions will map properties *again* so + // this makes little sense - ignore for now + .ForMember(dest => dest.PropertyTypes, map => map.Ignore()); + //.ForMember(dest => dest.PropertyTypes, map => map.MapFrom(source => + // source.Properties.Select(Mapper.Map))); + + config.CreateMap, PropertyGroupDisplay>() + .ForMember(dest => dest.Id, expression => expression.Condition(source => source.Id > 0)) + .ForMember(g => g.ContentTypeId, expression => expression.Ignore()) + .ForMember(g => g.ParentTabContentTypes, expression => expression.Ignore()) + .ForMember(g => g.ParentTabContentTypeNames, expression => expression.Ignore()) + .ForMember(g => g.Properties, expression => expression.MapFrom(display => display.Properties.Select(Mapper.Map))); + + config.CreateMap() + .ForMember(g => g.Editor, expression => expression.Ignore()) + .ForMember(g => g.View, expression => expression.Ignore()) + .ForMember(g => g.Config, expression => expression.Ignore()) + .ForMember(g => g.ContentTypeId, expression => expression.Ignore()) + .ForMember(g => g.ContentTypeName, expression => expression.Ignore()); + + #endregion + + + + } + + } } \ No newline at end of file diff --git a/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapperExtensions.cs b/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapperExtensions.cs new file mode 100644 index 0000000000..e59a474afb --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapperExtensions.cs @@ -0,0 +1,270 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using AutoMapper; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.PropertyEditors; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Models.Mapping +{ + /// + /// Used as a shared way to do the underlying mapping for content types base classes + /// + /// + /// We used to use 'Include' Automapper inheritance functionality and although this works, the unit test + /// to assert mappings fails which is an Automapper bug. So instead we will use an extension method for the mappings + /// to re-use mappings. + /// + internal static class ContentTypeModelMapperExtensions + { + + public static void AfterMapContentTypeSaveToEntity( + TSource source, TDestination dest, + ApplicationContext applicationContext) + where TSource : ContentTypeSave + where TDestination : IContentTypeComposition + { + //sync compositions + var current = dest.CompositionAliases().ToArray(); + var proposed = source.CompositeContentTypes; + + var remove = current.Where(x => proposed.Contains(x) == false); + var add = proposed.Where(x => current.Contains(x) == false); + + foreach (var rem in remove) + { + dest.RemoveContentType(rem); + } + + foreach (var a in add) + { + //TODO: Remove N+1 lookup + var addCt = applicationContext.Services.ContentTypeService.GetContentType(a); + if (addCt != null) + dest.AddContentType(addCt); + } + } + + public static IMappingExpression MapBaseContentTypeSaveToDisplay( + this IMappingExpression mapping) + where TSource : ContentTypeSave + where TDestination : ContentTypeCompositionDisplay + { + return mapping + .ForMember(dto => dto.CreateDate, expression => expression.Ignore()) + .ForMember(dto => dto.UpdateDate, expression => expression.Ignore()) + .ForMember(dto => dto.ListViewEditorName, expression => expression.Ignore()) + .ForMember(dto => dto.Notifications, expression => expression.Ignore()) + .ForMember(dto => dto.Errors, expression => expression.Ignore()); + } + + public static IMappingExpression MapBaseContentTypeEntityToDisplay( + this IMappingExpression mapping, ApplicationContext applicationContext, Lazy propertyEditorResolver) + where TSource : IContentTypeComposition + where TDestination : ContentTypeCompositionDisplay + { + return mapping + .ForMember(display => display.Notifications, expression => expression.Ignore()) + .ForMember(display => display.Errors, expression => expression.Ignore()) + .ForMember(display => display.AllowAsRoot, expression => expression.MapFrom(type => type.AllowedAsRoot)) + .ForMember(display => display.ListViewEditorName, expression => expression.Ignore()) + //Ignore because this is not actually used for content types + .ForMember(display => display.Trashed, expression => expression.Ignore()) + + .ForMember( + dto => dto.AllowedContentTypes, + expression => expression.MapFrom(dto => dto.AllowedContentTypes.Select(x => x.Id.Value))) + + .ForMember( + dto => dto.CompositeContentTypes, + expression => expression.MapFrom(dto => dto.ContentTypeComposition)) + + .ForMember( + dto => dto.Groups, + expression => expression.ResolveUsing(new PropertyTypeGroupResolver(applicationContext, propertyEditorResolver))); + } + + /// + /// Display -> Entity class base mapping logic + /// + /// + /// + /// + /// + /// + public static IMappingExpression MapBaseContentTypeSaveToEntity( + this IMappingExpression mapping, ApplicationContext applicationContext) + //where TSource : ContentTypeCompositionDisplay + where TSource : ContentTypeSave + where TDestination : IContentTypeComposition + { + return mapping + //only map id if set to something higher then zero + .ForMember(dto => dto.Id, expression => expression.Condition(display => (Convert.ToInt32(display.Id) > 0))) + .ForMember(dto => dto.Id, expression => expression.MapFrom(display => Convert.ToInt32(display.Id))) + + //These get persisted as part of the saving procedure, nothing to do with the display model + .ForMember(dto => dto.CreateDate, expression => expression.Ignore()) + .ForMember(dto => dto.UpdateDate, expression => expression.Ignore()) + + .ForMember(dto => dto.AllowedAsRoot, expression => expression.MapFrom(display => display.AllowAsRoot)) + .ForMember(dto => dto.CreatorId, expression => expression.Ignore()) + .ForMember(dto => dto.Level, expression => expression.Ignore()) + .ForMember(dto => dto.SortOrder, expression => expression.Ignore()) + //ignore, we'll do this in after map + .ForMember(dto => dto.PropertyGroups, expression => expression.Ignore()) + .ForMember(dto => dto.NoGroupPropertyTypes, expression => expression.Ignore()) + // ignore, composition is managed in AfterMapContentTypeSaveToEntity + .ForMember(dest => dest.ContentTypeComposition, opt => opt.Ignore()) + + .ForMember( + dto => dto.AllowedContentTypes, + expression => expression.MapFrom(dto => dto.AllowedContentTypes.Select((t, i) => new ContentTypeSort(t, i)))) + + .AfterMap((source, dest) => + { + // handle property groups and property types + // note that ContentTypeSave has + // - all groups, inherited and local; only *one* occurence per group *name* + // - potentially including the generic properties group + // - all properties, inherited and local + // + // also, see PropertyTypeGroupResolver.ResolveCore: + // - if a group is local *and* inherited, then Inherited is true + // and the identifier is the identifier of the *local* group + // + // IContentTypeComposition AddPropertyGroup, AddPropertyType methods do some + // unique-alias-checking, etc that is *not* compatible with re-mapping everything + // the way we do it here, so we should exclusively do it by + // - managing a property group's PropertyTypes collection + // - managing the content type's PropertyTypes collection (for generic properties) + + // handle actual groups (non-generic-properties) + var destOrigGroups = dest.PropertyGroups.ToArray(); // local groups + var destOrigProperties = dest.PropertyTypes.ToArray(); // all properties, in groups or not + var destGroups = new List(); + var sourceGroups = source.Groups.Where(x => x.IsGenericProperties == false).ToArray(); + foreach (var sourceGroup in sourceGroups) + { + // get the dest group + var destGroup = MapSaveGroup(sourceGroup, destOrigGroups); + + // handle local properties + var destProperties = sourceGroup.Properties + .Where(x => x.Inherited == false) + .Select(x => MapSaveProperty(x, destOrigProperties)) + .ToArray(); + + // if the group has no local properties, skip it, ie sort-of garbage-collect + // local groups which would not have local properties anymore + if (destProperties.Length == 0) + continue; + + // ensure no duplicate alias, then assign the group properties collection + EnsureUniqueAliases(destProperties); + destGroup.PropertyTypes = new PropertyTypeCollection(destProperties); + destGroups.Add(destGroup); + } + + // ensure no duplicate name, then assign the groups collection + EnsureUniqueNames(destGroups); + dest.PropertyGroups = new PropertyGroupCollection(destGroups); + + // because the property groups collection was rebuilt, there is no need to remove + // the old groups - they are just gone and will be cleared by the repository + + // handle non-grouped (ie generic) properties + var genericPropertiesGroup = source.Groups.FirstOrDefault(x => x.IsGenericProperties); + if (genericPropertiesGroup != null) + { + // handle local properties + var destProperties = genericPropertiesGroup.Properties + .Where(x => x.Inherited == false) + .Select(x => MapSaveProperty(x, destOrigProperties)) + .ToArray(); + + // ensure no duplicate alias, then assign the generic properties collection + EnsureUniqueAliases(destProperties); + dest.NoGroupPropertyTypes = new PropertyTypeCollection(destProperties); + } + + // because all property collections were rebuilt, there is no need to remove + // some old properties, they are just gone and will be cleared by the repository + }); + } + + private static PropertyGroup MapSaveGroup(PropertyGroupBasic sourceGroup, IEnumerable destOrigGroups) + { + PropertyGroup destGroup; + if (sourceGroup.Id > 0) + { + // update an existing group + // ensure it is still there, then map/update + destGroup = destOrigGroups.FirstOrDefault(x => x.Id == sourceGroup.Id); + if (destGroup != null) + { + Mapper.Map(sourceGroup, destGroup); + return destGroup; + } + + // force-clear the ID as it does not match anything + sourceGroup.Id = 0; + } + + // insert a new group, or update an existing group that has + // been deleted in the meantime and we need to re-create + // map/create + destGroup = Mapper.Map(sourceGroup); + return destGroup; + } + + private static PropertyType MapSaveProperty(PropertyTypeBasic sourceProperty, IEnumerable destOrigProperties) + { + PropertyType destProperty; + if (sourceProperty.Id > 0) + { + // updateg an existing property + // ensure it is still there, then map/update + destProperty = destOrigProperties.FirstOrDefault(x => x.Id == sourceProperty.Id); + if (destProperty != null) + { + Mapper.Map(sourceProperty, destProperty); + return destProperty; + } + + // force-clear the ID as it does not match anything + sourceProperty.Id = 0; + } + + // insert a new property, or update an existing property that has + // been deletedin the meantime and we need to re-create + // map/create + destProperty = Mapper.Map(sourceProperty); + return destProperty; + } + + private static void EnsureUniqueAliases(IEnumerable properties) + { + var propertiesA = properties.ToArray(); + var distinctProperties = propertiesA + .Select(x => x.Alias.ToUpperInvariant()) + .Distinct() + .Count(); + if (distinctProperties != propertiesA.Length) + throw new InvalidOperationException("Cannot map properties due to alias conflict."); + } + + private static void EnsureUniqueNames(IEnumerable groups) + { + var groupsA = groups.ToArray(); + var distinctProperties = groupsA + .Select(x => x.Name.ToUpperInvariant()) + .Distinct() + .Count(); + if (distinctProperties != groupsA.Length) + throw new InvalidOperationException("Cannot map groups due to name conflict."); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/Mapping/DataTypeModelMapper.cs b/src/Umbraco.Web/Models/Mapping/DataTypeModelMapper.cs index 79fbab8f66..3df195e073 100644 --- a/src/Umbraco.Web/Models/Mapping/DataTypeModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/DataTypeModelMapper.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using AutoMapper; -using System.Collections.Generic; using System.Linq; using Umbraco.Core; using Umbraco.Core.Models; @@ -34,10 +33,32 @@ namespace Umbraco.Web.Models.Mapping Constants.System.DefaultMembersListViewDataTypeId }; - config.CreateMap() + config.CreateMap() + .ForMember(x => x.HasPrevalues, expression => expression.Ignore()) + .ForMember(x => x.IsSystemDataType, expression => expression.Ignore()) + .ForMember(x => x.Id, expression => expression.Ignore()) + .ForMember(x => x.Trashed, expression => expression.Ignore()) + .ForMember(x => x.Key, expression => expression.Ignore()) + .ForMember(x => x.ParentId, expression => expression.Ignore()) + .ForMember(x => x.Path, expression => expression.Ignore()) + .ForMember(x => x.AdditionalData, expression => expression.Ignore()); + + config.CreateMap() + .ForMember(x => x.HasPrevalues, expression => expression.Ignore()) .ForMember(x => x.Icon, expression => expression.Ignore()) .ForMember(x => x.Alias, expression => expression.Ignore()) - .ForMember(x => x.IsSystemDataType, expression => expression.MapFrom(definition => systemIds.Contains(definition.Id))); + .ForMember(x => x.Group, expression => expression.Ignore()) + .ForMember(x => x.IsSystemDataType, expression => expression.MapFrom(definition => systemIds.Contains(definition.Id))) + .AfterMap((def, basic) => + { + var editor = PropertyEditorResolver.Current.GetByAlias(def.PropertyEditorAlias); + if (editor != null) + { + basic.Alias = editor.Alias; + basic.Group = editor.Group; + basic.Icon = editor.Icon; + } + }); config.CreateMap() .ForMember(display => display.AvailableEditors, expression => expression.ResolveUsing()) @@ -45,10 +66,21 @@ namespace Umbraco.Web.Models.Mapping new PreValueDisplayResolver(lazyDataTypeService))) .ForMember(display => display.SelectedEditor, expression => expression.MapFrom( definition => definition.PropertyEditorAlias.IsNullOrWhiteSpace() ? null : definition.PropertyEditorAlias)) + .ForMember(x => x.HasPrevalues, expression => expression.Ignore()) .ForMember(x => x.Notifications, expression => expression.Ignore()) .ForMember(x => x.Icon, expression => expression.Ignore()) .ForMember(x => x.Alias, expression => expression.Ignore()) - .ForMember(x => x.IsSystemDataType, expression => expression.MapFrom(definition => systemIds.Contains(definition.Id))); + .ForMember(x => x.Group, expression => expression.Ignore()) + .ForMember(x => x.IsSystemDataType, expression => expression.MapFrom(definition => systemIds.Contains(definition.Id))) + .AfterMap((def, basic) => + { + var editor = PropertyEditorResolver.Current.GetByAlias(def.PropertyEditorAlias); + if (editor != null) + { + basic.Group = editor.Group; + basic.Icon = editor.Icon; + } + }); //gets a list of PreValueFieldDisplay objects from the data type definition config.CreateMap>() @@ -59,14 +91,13 @@ namespace Umbraco.Web.Models.Mapping }); config.CreateMap() - .ConstructUsing(save => new DataTypeDefinition(-1, save.SelectedEditor) {CreateDate = DateTime.Now}) + .ConstructUsing(save => new DataTypeDefinition(save.SelectedEditor) {CreateDate = DateTime.Now}) .ForMember(definition => definition.Id, expression => expression.MapFrom(save => Convert.ToInt32(save.Id))) //we have to ignore the Key otherwise this will reset the UniqueId field which should never change! // http://issues.umbraco.org/issue/U4-3911 .ForMember(definition => definition.Key, expression => expression.Ignore()) .ForMember(definition => definition.Path, expression => expression.Ignore()) .ForMember(definition => definition.PropertyEditorAlias, expression => expression.MapFrom(save => save.SelectedEditor)) - .ForMember(definition => definition.ParentId, expression => expression.MapFrom(save => -1)) .ForMember(definition => definition.DatabaseType, expression => expression.ResolveUsing()) .ForMember(x => x.CreatorId, expression => expression.Ignore()) .ForMember(x => x.Level, expression => expression.Ignore()) diff --git a/src/Umbraco.Web/Models/Mapping/EntityModelMapper.cs b/src/Umbraco.Web/Models/Mapping/EntityModelMapper.cs index fb0a809c24..5610a70008 100644 --- a/src/Umbraco.Web/Models/Mapping/EntityModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/EntityModelMapper.cs @@ -45,6 +45,36 @@ namespace Umbraco.Web.Models.Mapping .ForMember(dto => dto.Trashed, expression => expression.Ignore()) .ForMember(x => x.AdditionalData, expression => expression.Ignore()); + config.CreateMap() + .ForMember(basic => basic.Icon, expression => expression.UseValue("icon-layout")) + .ForMember(basic => basic.Path, expression => expression.MapFrom(template => template.Path)) + .ForMember(basic => basic.ParentId, expression => expression.UseValue(-1)) + .ForMember(dto => dto.Trashed, expression => expression.Ignore()) + .ForMember(x => x.AdditionalData, expression => expression.Ignore()); + + //config.CreateMap() + // .ConstructUsing(basic => new Template(basic.Name, basic.Alias) + // { + // Id = Convert.ToInt32(basic.Id), + // Key = basic.Key + // }) + // .ForMember(t => t.Path, expression => expression.Ignore()) + // .ForMember(t => t.Id, expression => expression.MapFrom(template => Convert.ToInt32(template.Id))) + // .ForMember(x => x.VirtualPath, expression => expression.Ignore()) + // .ForMember(x => x.CreateDate, expression => expression.Ignore()) + // .ForMember(x => x.UpdateDate, expression => expression.Ignore()) + // .ForMember(x => x.Content, expression => expression.Ignore()); + + config.CreateMap() + .ForMember(x => x.Id, expression => expression.MapFrom(entity => new Lazy(() => Convert.ToInt32(entity.Id)))) + .ForMember(x => x.SortOrder, expression => expression.Ignore()); + + config.CreateMap() + .ForMember(basic => basic.Path, expression => expression.MapFrom(x => x.Path)) + .ForMember(basic => basic.ParentId, expression => expression.MapFrom(x => x.ParentId)) + .ForMember(dto => dto.Trashed, expression => expression.Ignore()) + .ForMember(x => x.AdditionalData, expression => expression.Ignore()); + config.CreateMap() //default to document icon .ForMember(x => x.Icon, expression => expression.Ignore()) diff --git a/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs b/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs index 241d5b3c97..3c27d0f183 100644 --- a/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs @@ -35,11 +35,7 @@ namespace Umbraco.Web.Models.Mapping .ForMember( dto => dto.ContentTypeAlias, expression => expression.MapFrom(content => content.ContentType.Alias)) - .ForMember( - dto => dto.IsChildOfListView, - //TODO: Fix this shorthand .Parent() lookup, at least have an overload to use the current - // application context so it's testable! - expression => expression.MapFrom(content => content.Parent().ContentType.IsContainer)) + .ForMember(display => display.IsChildOfListView, expression => expression.Ignore()) .ForMember( dto => dto.Trashed, expression => expression.MapFrom(content => content.Trashed)) @@ -88,6 +84,33 @@ namespace Umbraco.Web.Models.Mapping private static void AfterMap(IMedia media, MediaItemDisplay display, IDataTypeService dataTypeService) { + // Adapted from ContentModelMapper + //map the IsChildOfListView (this is actually if it is a descendant of a list view!) + //TODO: Fix this shorthand .Ancestors() lookup, at least have an overload to use the current + if (media.HasIdentity) + { + var ancesctorListView = media.Ancestors().FirstOrDefault(x => x.ContentType.IsContainer); + display.IsChildOfListView = ancesctorListView != null; + } + else + { + //it's new so it doesn't have a path, so we need to look this up by it's parent + ancestors + var parent = media.Parent(); + if (parent == null) + { + display.IsChildOfListView = false; + } + else if (parent.ContentType.IsContainer) + { + display.IsChildOfListView = true; + } + else + { + var ancesctorListView = parent.Ancestors().FirstOrDefault(x => x.ContentType.IsContainer); + display.IsChildOfListView = ancesctorListView != null; + } + } + //map the tree node url if (HttpContext.Current != null) { diff --git a/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs b/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs index 9cfe88865b..44d4fb0f50 100644 --- a/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs @@ -178,45 +178,48 @@ namespace Umbraco.Web.Models.Mapping display.TreeNodeUrl = url; } - TabsAndPropertiesResolver.MapGenericProperties( - member, display, + var genericProperties = new List + { GetLoginProperty(memberService, member, display), new ContentPropertyDisplay - { - Alias = string.Format("{0}email", Constants.PropertyEditors.InternalGenericPropertiesPrefix), - Label = ui.Text("general", "email"), - Value = display.Email, - View = "email", - Validation = { Mandatory = true } - }, + { + Alias = string.Format("{0}email", Constants.PropertyEditors.InternalGenericPropertiesPrefix), + Label = ui.Text("general", "email"), + Value = display.Email, + View = "email", + Validation = {Mandatory = true} + }, new ContentPropertyDisplay + { + Alias = string.Format("{0}password", Constants.PropertyEditors.InternalGenericPropertiesPrefix), + Label = ui.Text("password"), + //NOTE: The value here is a json value - but the only property we care about is the generatedPassword one if it exists, the newPassword exists + // only when creating a new member and we want to have a generated password pre-filled. + Value = new Dictionary { - Alias = string.Format("{0}password", Constants.PropertyEditors.InternalGenericPropertiesPrefix), - Label = ui.Text("password"), - //NOTE: The value here is a json value - but the only property we care about is the generatedPassword one if it exists, the newPassword exists - // only when creating a new member and we want to have a generated password pre-filled. - Value = new Dictionary - { - {"generatedPassword", member.GetAdditionalDataValueIgnoreCase("GeneratedPassword", null) }, - {"newPassword", member.GetAdditionalDataValueIgnoreCase("NewPassword", null) }, - }, - //TODO: Hard coding this because the changepassword doesn't necessarily need to be a resolvable (real) property editor - View = "changepassword", - //initialize the dictionary with the configuration from the default membership provider - Config = new Dictionary(membersProvider.GetConfiguration()) - { - //the password change toggle will only be displayed if there is already a password assigned. - {"hasPassword", member.RawPasswordValue.IsNullOrWhiteSpace() == false} - } + {"generatedPassword", member.GetAdditionalDataValueIgnoreCase("GeneratedPassword", null)}, + {"newPassword", member.GetAdditionalDataValueIgnoreCase("NewPassword", null)}, }, - new ContentPropertyDisplay + //TODO: Hard coding this because the changepassword doesn't necessarily need to be a resolvable (real) property editor + View = "changepassword", + //initialize the dictionary with the configuration from the default membership provider + Config = new Dictionary(membersProvider.GetConfiguration()) { - Alias = string.Format("{0}membergroup", Constants.PropertyEditors.InternalGenericPropertiesPrefix), - Label = ui.Text("content", "membergroup"), - Value = GetMemberGroupValue(display.Username), - View = "membergroups", - Config = new Dictionary { { "IsRequired", true } } - }); + //the password change toggle will only be displayed if there is already a password assigned. + {"hasPassword", member.RawPasswordValue.IsNullOrWhiteSpace() == false} + } + }, + new ContentPropertyDisplay + { + Alias = string.Format("{0}membergroup", Constants.PropertyEditors.InternalGenericPropertiesPrefix), + Label = ui.Text("content", "membergroup"), + Value = GetMemberGroupValue(display.Username), + View = "membergroups", + Config = new Dictionary {{"IsRequired", true}} + } + }; + + TabsAndPropertiesResolver.MapGenericProperties(member, display, genericProperties); //check if there's an approval field var provider = membersProvider as IUmbracoMemberTypeMembershipProvider; @@ -273,10 +276,10 @@ namespace Umbraco.Web.Models.Mapping var result = new Dictionary(); foreach (var role in Roles.GetAllRoles().Distinct()) { - result.Add(role, false); // if a role starts with __umbracoRole we won't show it as it's an internal role used for public access if (role.StartsWith(Constants.Conventions.Member.InternalRolePrefix) == false) { + result.Add(role, false); if (username.IsNullOrWhiteSpace()) continue; if (Roles.IsUserInRole(username, role)) { diff --git a/src/Umbraco.Web/Models/Mapping/PropertyTypeGroupResolver.cs b/src/Umbraco.Web/Models/Mapping/PropertyTypeGroupResolver.cs new file mode 100644 index 0000000000..f9ae01e9cd --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/PropertyTypeGroupResolver.cs @@ -0,0 +1,186 @@ +using AutoMapper; +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.PropertyEditors; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Models.Mapping +{ + internal class PropertyTypeGroupResolver : ValueResolver> + { + private readonly ApplicationContext _applicationContext; + private readonly Lazy _propertyEditorResolver; + + public PropertyTypeGroupResolver(ApplicationContext applicationContext, Lazy propertyEditorResolver) + { + _applicationContext = applicationContext; + _propertyEditorResolver = propertyEditorResolver; + } + + /// + /// Gets the content type that defines a property group, within a composition. + /// + /// The composition. + /// The identifier of the property group. + /// The composition content type that defines the specified property group. + private static IContentTypeComposition GetContentTypeForPropertyGroup(IContentTypeComposition contentType, int propertyGroupId) + { + // test local groups + if (contentType.PropertyGroups.Any(x => x.Id == propertyGroupId)) + return contentType; + + // test composition types groups + // .ContentTypeComposition is just the local ones, not recursive, + // so we have to recurse here + return contentType.ContentTypeComposition + .Select(x => GetContentTypeForPropertyGroup(x, propertyGroupId)) + .FirstOrDefault(x => x != null); + } + + protected override IEnumerable ResolveCore(IContentTypeComposition source) + { + // deal with groups + var groups = new List(); + + // add groups local to this content type + foreach (var tab in source.PropertyGroups) + { + var group = new PropertyGroupDisplay + { + Id = tab.Id, + Inherited = false, + Name = tab.Name, + SortOrder = tab.SortOrder, + ContentTypeId = source.Id + }; + + group.Properties = MapProperties(tab.PropertyTypes, source, tab.Id, false); + groups.Add(group); + } + + // add groups inherited through composition + var localGroupIds = groups.Select(x => x.Id).ToArray(); + foreach (var tab in source.CompositionPropertyGroups) + { + // skip those that are local to this content type + if (localGroupIds.Contains(tab.Id)) continue; + + // get the content type that defines this group + var definingContentType = GetContentTypeForPropertyGroup(source, tab.Id); + if (definingContentType == null) + throw new Exception("PropertyGroup with id=" + tab.Id + " was not found on any of the content type's compositions."); + + var group = new PropertyGroupDisplay + { + Id = tab.Id, + Inherited = true, + Name = tab.Name, + SortOrder = tab.SortOrder, + ContentTypeId = definingContentType.Id, + ParentTabContentTypes = new[] { definingContentType.Id }, + ParentTabContentTypeNames = new[] { definingContentType.Name } + }; + + group.Properties = MapProperties(tab.PropertyTypes, definingContentType, tab.Id, true); + groups.Add(group); + } + + // deal with generic properties + var genericProperties = new List(); + + // add generic properties local to this content type + var entityGenericProperties = source.PropertyTypes.Where(x => x.PropertyGroupId == null); + genericProperties.AddRange(MapProperties(entityGenericProperties, source, PropertyGroupDisplay.GenericPropertiesGroupId, false)); + + // add generic properties inherited through compositions + var localGenericPropertyIds = genericProperties.Select(x => x.Id).ToArray(); + var compositionGenericProperties = source.CompositionPropertyTypes + .Where(x => x.PropertyGroupId == null // generic + && localGenericPropertyIds.Contains(x.Id) == false); // skip those that are local + genericProperties.AddRange(MapProperties(compositionGenericProperties, source, PropertyGroupDisplay.GenericPropertiesGroupId, true)); + + // if there are any generic properties, add the corresponding tab + if (genericProperties.Any()) + { + var genericTab = new PropertyGroupDisplay + { + Id = PropertyGroupDisplay.GenericPropertiesGroupId, + Name = "Generic properties", + ContentTypeId = source.Id, + SortOrder = 999, + Inherited = false, + Properties = genericProperties + }; + groups.Add(genericTab); + } + + // now merge tabs based on names + // as for one name, we might have one local tab, plus some inherited tabs + var groupsGroupsByName = groups.GroupBy(x => x.Name).ToArray(); + groups = new List(); // start with a fresh list + foreach (var groupsByName in groupsGroupsByName) + { + // single group, just use it + if (groupsByName.Count() == 1) + { + groups.Add(groupsByName.First()); + continue; + } + + // multiple groups, merge + var group = groupsByName.FirstOrDefault(x => x.Inherited == false) // try local + ?? groupsByName.First(); // else pick one randomly + groups.Add(group); + + // in case we use the local one, flag as inherited + group.Inherited = true; + + // merge (and sort) properties + var properties = groupsByName.SelectMany(x => x.Properties).OrderBy(x => x.SortOrder).ToArray(); + group.Properties = properties; + + // collect parent group info + var parentGroups = groupsByName.Where(x => x.ContentTypeId != source.Id).ToArray(); + group.ParentTabContentTypes = parentGroups.SelectMany(x => x.ParentTabContentTypes).ToArray(); + group.ParentTabContentTypeNames = parentGroups.SelectMany(x => x.ParentTabContentTypeNames).ToArray(); + } + + return groups.OrderBy(x => x.SortOrder); + } + + private IEnumerable MapProperties(IEnumerable properties, IContentTypeBase contentType, int groupId, bool inherited) + { + var mappedProperties = new List(); + + foreach (var p in properties.Where(x => x.DataTypeDefinitionId != 0) ) + { + var propertyEditor = _propertyEditorResolver.Value.GetByAlias(p.PropertyEditorAlias); + var preValues = _applicationContext.Services.DataTypeService.GetPreValuesCollectionByDataTypeId(p.DataTypeDefinitionId); + + mappedProperties.Add(new PropertyTypeDisplay + { + Id = p.Id, + Alias = p.Alias, + Description = p.Description, + Editor = p.PropertyEditorAlias, + Validation = new PropertyTypeValidation { Mandatory = p.Mandatory, Pattern = p.ValidationRegExp }, + Label = p.Name, + View = propertyEditor.ValueEditor.View, + Config = propertyEditor.PreValueEditor.ConvertDbToEditor(propertyEditor.DefaultPreValues, preValues) , + //Value = "", + ContentTypeId = contentType.Id, + ContentTypeName = contentType.Name, + GroupId = groupId, + Inherited = inherited, + DataTypeId = p.DataTypeDefinitionId, + SortOrder = p.SortOrder + }); + } + + return mappedProperties; + } + } +} diff --git a/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs b/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs index 325b8df3e3..3a1a756940 100644 --- a/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs @@ -1,10 +1,11 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; +using System.Web; using AutoMapper; using Umbraco.Core; using Umbraco.Core.Dictionary; -using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; @@ -17,7 +18,7 @@ namespace Umbraco.Web.Models.Mapping /// Creates the tabs collection with properties assigned for display models /// internal class TabsAndPropertiesResolver : ValueResolver>> - { + { private ICultureDictionary _cultureDictionary; protected IEnumerable IgnoreProperties { get; set; } @@ -27,7 +28,7 @@ namespace Umbraco.Web.Models.Mapping } public TabsAndPropertiesResolver(IEnumerable ignoreProperties) - { + { if (ignoreProperties == null) throw new ArgumentNullException("ignoreProperties"); IgnoreProperties = ignoreProperties; } @@ -40,6 +41,7 @@ namespace Umbraco.Web.Models.Mapping /// /// Any additional custom properties to assign to the generic properties tab. /// + /// /// /// The generic properties tab is mapped during AfterMap and is responsible for /// setting up the properties such as Created date, updated date, template selected, etc... @@ -47,11 +49,10 @@ namespace Umbraco.Web.Models.Mapping public static void MapGenericProperties( TPersisted content, ContentItemDisplayBase display, - params ContentPropertyDisplay[] customProperties) + IEnumerable customProperties = null, + Action> onGenericPropertiesMapped = null) where TPersisted : IContentBase { - - var genericProps = display.Tabs.Single(x => x.Id == 0); //store the current props to append to the newly inserted ones @@ -60,56 +61,67 @@ namespace Umbraco.Web.Models.Mapping var labelEditor = PropertyEditorResolver.Current.GetByAlias(Constants.PropertyEditors.NoEditAlias).ValueEditor.View; var contentProps = new List + { + new ContentPropertyDisplay { - new ContentPropertyDisplay - { - Alias = string.Format("{0}id", Constants.PropertyEditors.InternalGenericPropertiesPrefix), - Label = "Id", - Value = Convert.ToInt32(display.Id).ToInvariantString() + "
    " + display.Key + "", - View = labelEditor - }, - new ContentPropertyDisplay - { - Alias = string.Format("{0}creator", Constants.PropertyEditors.InternalGenericPropertiesPrefix), - Label = ui.Text("content", "createBy"), - Description = ui.Text("content", "createByDesc"), //TODO: Localize this - Value = display.Owner.Name, - View = labelEditor - }, - new ContentPropertyDisplay - { - Alias = string.Format("{0}createdate", Constants.PropertyEditors.InternalGenericPropertiesPrefix), - Label = ui.Text("content", "createDate"), - Description = ui.Text("content", "createDateDesc"), - Value = display.CreateDate.ToIsoString(), - View = labelEditor - }, - new ContentPropertyDisplay - { - Alias = string.Format("{0}updatedate", Constants.PropertyEditors.InternalGenericPropertiesPrefix), - Label = ui.Text("content", "updateDate"), - Description = ui.Text("content", "updateDateDesc"), - Value = display.UpdateDate.ToIsoString(), - View = labelEditor - }, - new ContentPropertyDisplay - { - Alias = string.Format("{0}doctype", Constants.PropertyEditors.InternalGenericPropertiesPrefix), - Label = ui.Text("content", "documentType"), - Value = TranslateItem(display.ContentTypeName, CreateDictionary()), - View = labelEditor - } - }; + Alias = string.Format("{0}id", Constants.PropertyEditors.InternalGenericPropertiesPrefix), + Label = "Id", + Value = Convert.ToInt32(display.Id).ToInvariantString() + "
    " + display.Key + "", + View = labelEditor + }, + new ContentPropertyDisplay + { + Alias = string.Format("{0}creator", Constants.PropertyEditors.InternalGenericPropertiesPrefix), + Label = ui.Text("content", "createBy"), + Description = ui.Text("content", "createByDesc"), //TODO: Localize this + Value = display.Owner.Name, + View = labelEditor + }, + new ContentPropertyDisplay + { + Alias = string.Format("{0}createdate", Constants.PropertyEditors.InternalGenericPropertiesPrefix), + Label = ui.Text("content", "createDate"), + Description = ui.Text("content", "createDateDesc"), + Value = display.CreateDate.ToIsoString(), + View = labelEditor + }, + new ContentPropertyDisplay + { + Alias = string.Format("{0}updatedate", Constants.PropertyEditors.InternalGenericPropertiesPrefix), + Label = ui.Text("content", "updateDate"), + Description = ui.Text("content", "updateDateDesc"), + Value = display.UpdateDate.ToIsoString(), + View = labelEditor + }, + new ContentPropertyDisplay + { + Alias = string.Format("{0}doctype", Constants.PropertyEditors.InternalGenericPropertiesPrefix), + Label = ui.Text("content", "documentType"), + Value = TranslateItem(display.ContentTypeName, CreateDictionary()), + View = labelEditor + } + }; - //add the custom ones - contentProps.AddRange(customProperties); + if (customProperties != null) + { + //add the custom ones + contentProps.AddRange(customProperties); + } //now add the user props contentProps.AddRange(currProps); + //callback + if (onGenericPropertiesMapped != null) + { + onGenericPropertiesMapped(contentProps); + } + //re-assign genericProps.Properties = contentProps; + + } /// @@ -189,62 +201,66 @@ namespace Umbraco.Web.Models.Mapping protected override IEnumerable> ResolveCore(IContentBase content) { - var aggregateTabs = new List>(); + var tabs = new List>(); - //now we need to aggregate the tabs and properties since we might have duplicate tabs (based on aliases) because - // of how content composition works. - foreach (var propertyGroups in content.PropertyGroups.OrderBy(x => x.SortOrder).GroupBy(x => x.Name)) + // add the tabs, for properties that belong to a tab + // need to aggregate the tabs, as content.PropertyGroups contains all the composition tabs, + // and there might be duplicates (content does not work like contentType and there is no + // content.CompositionPropertyGroups). + var groupsGroupsByName = content.PropertyGroups.OrderBy(x => x.SortOrder).GroupBy(x => x.Name); + foreach (var groupsByName in groupsGroupsByName) { - var aggregateProperties = new List(); + var properties = new List(); - //add the properties from each composite property group - foreach (var current in propertyGroups) + // merge properties for groups with the same name + foreach (var group in groupsByName) { - var propsForGroup = content.GetPropertiesForGroup(current) - .Where(x => IgnoreProperties.Contains(x.Alias) == false); //don't include ignored props + var groupProperties = content.GetPropertiesForGroup(group) + .Where(x => IgnoreProperties.Contains(x.Alias) == false); // skip ignored - aggregateProperties.AddRange( - Mapper.Map, IEnumerable>( - propsForGroup)); + properties.AddRange(Mapper.Map, IEnumerable>(groupProperties)); } - - if (aggregateProperties.Count == 0) + + if (properties.Count == 0) continue; - TranslateProperties(aggregateProperties); + TranslateProperties(properties); - //then we'll just use the root group's data to make the composite tab - var rootGroup = propertyGroups.First(x => x.ParentId == null); - aggregateTabs.Add(new Tab - { - Id = rootGroup.Id, - Alias = rootGroup.Name, - Label = TranslateItem(rootGroup.Name), - Properties = aggregateProperties, - IsActive = false - }); + // add the tab + // we need to pick an identifier... there is no "right" way... + var g = groupsByName.FirstOrDefault(x => x.Id == content.ContentTypeId) // try local + ?? groupsByName.First(); // else pick one randomly + var groupId = g.Id; + var groupName = groupsByName.Key; + tabs.Add(new Tab + { + Id = groupId, + Alias = groupName, + Label = TranslateItem(groupName), + Properties = properties, + IsActive = false + }); } - //now add the generic properties tab for any properties that don't belong to a tab - var orphanProperties = content.GetNonGroupedProperties() - .Where(x => IgnoreProperties.Contains(x.Alias) == false); //don't include ignored props - - //now add the generic properties tab - var genericproperties = Mapper.Map, IEnumerable>(orphanProperties).ToList(); + // add the generic properties tab, for properties that don't belong to a tab + // get the properties, map and translate them, then add the tab + var noGroupProperties = content.GetNonGroupedProperties() + .Where(x => IgnoreProperties.Contains(x.Alias) == false); // skip ignored + var genericproperties = Mapper.Map, IEnumerable>(noGroupProperties).ToList(); TranslateProperties(genericproperties); - aggregateTabs.Add(new Tab - { - Id = 0, - Label = ui.Text("general", "properties"), - Alias = "Generic properties", - Properties = genericproperties - }); + tabs.Add(new Tab + { + Id = 0, + Label = ui.Text("general", "properties"), + Alias = "Generic properties", + Properties = genericproperties + }); - //set the first tab to active - aggregateTabs.First().IsActive = true; + // activate the first tab + tabs.First().IsActive = true; - return aggregateTabs; + return tabs; } private void TranslateProperties(IEnumerable properties) diff --git a/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs b/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs index afae0eb122..e1c3cc4f1e 100644 --- a/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs @@ -52,7 +52,7 @@ namespace Umbraco.Web.Models.Mapping .ForMember(detail => detail.Username, opt => opt.MapFrom(user => user.Username)) .ForMember(detail => detail.Culture, opt => opt.MapFrom(user => user.GetUserCulture(applicationContext.Services.TextService))) .ForMember(detail => detail.SessionId, opt => opt.MapFrom(user => user.SecurityStamp.IsNullOrWhiteSpace() ? Guid.NewGuid().ToString("N") : user.SecurityStamp)); - + } private static int GetIntId(object id) diff --git a/src/Umbraco.Web/Models/PublishedContentWithKeyBase.cs b/src/Umbraco.Web/Models/PublishedContentWithKeyBase.cs new file mode 100644 index 0000000000..52bd8b2f59 --- /dev/null +++ b/src/Umbraco.Web/Models/PublishedContentWithKeyBase.cs @@ -0,0 +1,15 @@ +using System; +using System.Diagnostics; +using Umbraco.Core.Models; + +namespace Umbraco.Web.Models +{ + /// + /// Provide an abstract base class for IPublishedContent implementations. + /// + [DebuggerDisplay("Content Id: {Id}, Name: {Name}")] + public abstract class PublishedContentWithKeyBase : PublishedContentBase, IPublishedContentWithKey + { + public abstract Guid Key { get; } + } +} diff --git a/src/Umbraco.Web/Models/RegisterModel.cs b/src/Umbraco.Web/Models/RegisterModel.cs index 51217d1b1f..237f6d7845 100644 --- a/src/Umbraco.Web/Models/RegisterModel.cs +++ b/src/Umbraco.Web/Models/RegisterModel.cs @@ -28,7 +28,6 @@ namespace Umbraco.Web.Models { MemberTypeAlias = Constants.Conventions.MemberTypes.DefaultAlias; RedirectOnSucces = false; - RedirectUrl = "/"; UsernameIsEmail = true; MemberProperties = new List(); LoginOnSuccess = true; diff --git a/src/Umbraco.Web/Models/Trees/TreeNode.cs b/src/Umbraco.Web/Models/Trees/TreeNode.cs index c413edea37..5c8d36dd6b 100644 --- a/src/Umbraco.Web/Models/Trees/TreeNode.cs +++ b/src/Umbraco.Web/Models/Trees/TreeNode.cs @@ -2,6 +2,7 @@ using Umbraco.Core.IO; using System.Collections.Generic; using Umbraco.Core; +using Umbraco.Core.Configuration; using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Web.Models.Trees @@ -109,7 +110,7 @@ namespace Umbraco.Web.Models.Trees return IOHelper.ResolveUrl("~" + Icon.TrimStart('~')); //legacy icon path - return IOHelper.ResolveUrl("~/umbraco/images/umbraco/" + Icon); + return string.Format("{0}images/umbraco/{1}", GlobalSettings.Path.EnsureEndsWith("/"), Icon); } } diff --git a/src/Umbraco.Web/Mvc/AdminTokenAuthorizeAttribute.cs b/src/Umbraco.Web/Mvc/AdminTokenAuthorizeAttribute.cs index cbd2e0e519..dc8aa15f6a 100644 --- a/src/Umbraco.Web/Mvc/AdminTokenAuthorizeAttribute.cs +++ b/src/Umbraco.Web/Mvc/AdminTokenAuthorizeAttribute.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using System.Net.Http.Headers; using System.Text; using System.Text.RegularExpressions; using System.Web; @@ -36,22 +37,33 @@ namespace Umbraco.Web.Mvc return _applicationContext ?? ApplicationContext.Current; } + public const string AuthorizationType = "AToken"; + /// - /// Used to return the value that needs to go in the Authorization header + /// Used to return the full value that needs to go in the Authorization header /// /// /// public static string GetAuthHeaderTokenVal(ApplicationContext appContext) { - var admin = appContext.Services.UserService.GetUserById(0); + return string.Format("{0} {1}", AuthorizationType, GetAuthHeaderVal(appContext)); + } + public static AuthenticationHeaderValue GetAuthenticationHeaderValue(ApplicationContext appContext) + { + return new AuthenticationHeaderValue(AuthorizationType, GetAuthHeaderVal(appContext)); + } + + private static string GetAuthHeaderVal(ApplicationContext appContext) + { + var admin = appContext.Services.UserService.GetUserById(0); var token = string.Format("{0}u____u{1}u____u{2}", admin.Email, admin.Username, admin.RawPasswordValue); var encrypted = token.EncryptWithMachineKey(); var bytes = Encoding.UTF8.GetBytes(encrypted); var base64 = Convert.ToBase64String(bytes); - return "AToken val=\"" + base64 + "\""; + return string.Format("val=\"{0}\"", base64); } /// diff --git a/src/Umbraco.Web/Mvc/ControllerExtensions.cs b/src/Umbraco.Web/Mvc/ControllerExtensions.cs index 03e31f34c4..c15520b57c 100644 --- a/src/Umbraco.Web/Mvc/ControllerExtensions.cs +++ b/src/Umbraco.Web/Mvc/ControllerExtensions.cs @@ -130,25 +130,13 @@ namespace Umbraco.Web.Mvc /// /// Normally in MVC the way that the View object gets assigned to the result is to Execute the ViewResult, this however /// will write to the Response output stream which isn't what we want. Instead, this method will use the same logic inside - /// of MVC to assign the View object to the result but without executing it. This also ensures that the ViewData and the TempData - /// is assigned from the controller. + /// of MVC to assign the View object to the result but without executing it. /// This is only relavent for view results of PartialViewResult or ViewResult. /// /// /// - internal static void EnsureViewObjectDataOnResult(this ControllerBase controller, ViewResultBase result) - { - //when merging we'll create a new dictionary, otherwise you might run into an enumeration error - // caused from ModelStateDictionary - result.ViewData.ModelState.Merge(new ModelStateDictionary(controller.ViewData.ModelState)); - - // Temporarily copy the dictionary to avoid enumerator-modification errors - var newViewDataDict = new ViewDataDictionary(controller.ViewData); - foreach (var d in newViewDataDict) - result.ViewData[d.Key] = d.Value; - - result.TempData = controller.TempData; - + private static void EnsureViewObjectDataOnResult(this ControllerBase controller, ViewResultBase result) + { if (result.View != null) return; if (string.IsNullOrEmpty(result.ViewName)) diff --git a/src/Umbraco.Web/Mvc/DefaultRenderMvcControllerResolver.cs b/src/Umbraco.Web/Mvc/DefaultRenderMvcControllerResolver.cs index 7e9313289c..6cf3e30e54 100644 --- a/src/Umbraco.Web/Mvc/DefaultRenderMvcControllerResolver.cs +++ b/src/Umbraco.Web/Mvc/DefaultRenderMvcControllerResolver.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using System.Text; using System.Web.Mvc; @@ -34,6 +35,8 @@ namespace Umbraco.Web.Mvc } /// + [Obsolete("This method will be removed in future versions and should not be used to resolve a controller instance, the IControllerFactory is used for that purpose")] + [EditorBrowsable(EditorBrowsableState.Never)] /// Sets the default RenderMvcController type /// /// @@ -49,9 +52,9 @@ namespace Umbraco.Web.Mvc /// private void ValidateType(Type type) { - if (TypeHelper.IsTypeAssignableFrom(type) == false) + if (TypeHelper.IsTypeAssignableFrom(type) == false) { - throw new InvalidOperationException("The Type specified (" + type + ") is not of type " + typeof(IRenderMvcController)); + throw new InvalidOperationException("The Type specified (" + type + ") is not of type " + typeof (IRenderController)); } } diff --git a/src/Umbraco.Web/Mvc/IRenderController.cs b/src/Umbraco.Web/Mvc/IRenderController.cs new file mode 100644 index 0000000000..e4303e06e5 --- /dev/null +++ b/src/Umbraco.Web/Mvc/IRenderController.cs @@ -0,0 +1,12 @@ +using System.Web.Mvc; + +namespace Umbraco.Web.Mvc +{ + /// + /// A marker interface to designate that a controller will be used for Umbraco front-end requests and/or route hijacking + /// + public interface IRenderController : IController + { + + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Mvc/IRenderMvcController.cs b/src/Umbraco.Web/Mvc/IRenderMvcController.cs index 755ccd5854..53865cd4ee 100644 --- a/src/Umbraco.Web/Mvc/IRenderMvcController.cs +++ b/src/Umbraco.Web/Mvc/IRenderMvcController.cs @@ -1,5 +1,7 @@ +using System; using System.Web.Http.Filters; using System.Web.Mvc; +using System.Web.Routing; using System.Windows.Forms; using Umbraco.Web.Models; @@ -8,7 +10,7 @@ namespace Umbraco.Web.Mvc /// /// The interface that must be implemented for a controller to be designated to execute for route hijacking /// - public interface IRenderMvcController : IController + public interface IRenderMvcController : IRenderController { /// /// The default action to render the front-end view diff --git a/src/Umbraco.Web/Mvc/MemberAuthorizeAttribute.cs b/src/Umbraco.Web/Mvc/MemberAuthorizeAttribute.cs index dd9720cdcf..71ebfb8288 100644 --- a/src/Umbraco.Web/Mvc/MemberAuthorizeAttribute.cs +++ b/src/Umbraco.Web/Mvc/MemberAuthorizeAttribute.cs @@ -46,6 +46,7 @@ namespace Umbraco.Web.Mvc /// /// This is the same as applying the [AllowAnonymous] attribute /// + [Obsolete("Use [AllowAnonymous] instead")] public bool AllowAll { get; set; } /// diff --git a/src/Umbraco.Web/Mvc/PluginViewEngine.cs b/src/Umbraco.Web/Mvc/PluginViewEngine.cs index cc4174b76a..b4b72e36d5 100644 --- a/src/Umbraco.Web/Mvc/PluginViewEngine.cs +++ b/src/Umbraco.Web/Mvc/PluginViewEngine.cs @@ -2,7 +2,6 @@ using System.IO; using System.Linq; using System.Web.Mvc; using Lucene.Net.Util; -using Microsoft.Web.Mvc; using Umbraco.Core.IO; namespace Umbraco.Web.Mvc @@ -10,7 +9,7 @@ namespace Umbraco.Web.Mvc /// /// A view engine to look into the App_Plugins folder for views for packaged controllers /// - public class PluginViewEngine : ReflectedFixedRazorViewEngine + public class PluginViewEngine : RazorViewEngine { /// diff --git a/src/Umbraco.Web/Mvc/RedirectToUmbracoPageResult.cs b/src/Umbraco.Web/Mvc/RedirectToUmbracoPageResult.cs index e0b73e05f3..747776c3b0 100644 --- a/src/Umbraco.Web/Mvc/RedirectToUmbracoPageResult.cs +++ b/src/Umbraco.Web/Mvc/RedirectToUmbracoPageResult.cs @@ -42,6 +42,11 @@ namespace Umbraco.Web.Mvc } } + public int PageId + { + get { return _pageId; } + } + public IPublishedContent PublishedContent { get diff --git a/src/Umbraco.Web/Mvc/ReflectedFixedRazorViewEngine.cs b/src/Umbraco.Web/Mvc/ReflectedFixedRazorViewEngine.cs index 5cb082c032..320e8c6d8a 100644 --- a/src/Umbraco.Web/Mvc/ReflectedFixedRazorViewEngine.cs +++ b/src/Umbraco.Web/Mvc/ReflectedFixedRazorViewEngine.cs @@ -7,6 +7,7 @@ namespace Umbraco.Web.Mvc /// /// This is here to support compatibility with both MVC4 and MVC5 /// + [Obsolete("MVC5 does not have a 'fixed' viewengine so there's no reason to use this any more", false)] public abstract class ReflectedFixedRazorViewEngine : IViewEngine { protected ReflectedFixedRazorViewEngine() diff --git a/src/Umbraco.Web/Mvc/RenderActionInvoker.cs b/src/Umbraco.Web/Mvc/RenderActionInvoker.cs index f6b3c673bf..55be6591ad 100644 --- a/src/Umbraco.Web/Mvc/RenderActionInvoker.cs +++ b/src/Umbraco.Web/Mvc/RenderActionInvoker.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Concurrent; +using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using System.Web.Mvc; using System.Web.Mvc.Async; @@ -10,9 +12,8 @@ namespace Umbraco.Web.Mvc /// Ensures that if an action for the Template name is not explicitly defined by a user, that the 'Index' action will execute /// public class RenderActionInvoker : AsyncControllerActionInvoker - { + { - private static readonly ConcurrentDictionary IndexDescriptors = new ConcurrentDictionary(); /// /// Ensures that if an action for the Template name is not explicitly defined by a user, that the 'Index' action will execute @@ -28,23 +29,14 @@ namespace Umbraco.Web.Mvc //now we need to check if it exists, if not we need to return the Index by default if (ad == null) { - //check if the controller is an instance of IRenderMvcController - if (controllerContext.Controller is IRenderMvcController) + //check if the controller is an instance of IRenderController and find the index + if (controllerContext.Controller is IRenderController) { - return IndexDescriptors.GetOrAdd(controllerContext.Controller.GetType(), - type => new ReflectedActionDescriptor( - controllerContext.Controller.GetType().GetMethods() - .First(x => x.Name == "Index" && - x.GetCustomAttributes(typeof (NonActionAttribute), false).Any() == false), - "Index", - controllerDescriptor)); - - - - } + return controllerDescriptor.FindAction(controllerContext, "Index"); + } } return ad; } - + } } \ No newline at end of file diff --git a/src/Umbraco.Web/Mvc/RenderIndexActionSelectorAttribute.cs b/src/Umbraco.Web/Mvc/RenderIndexActionSelectorAttribute.cs new file mode 100644 index 0000000000..1d1c56db11 --- /dev/null +++ b/src/Umbraco.Web/Mvc/RenderIndexActionSelectorAttribute.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Concurrent; +using System.Linq; +using System.Reflection; +using System.Web.Mvc; + +namespace Umbraco.Web.Mvc +{ + /// + /// A custom ActionMethodSelector which will ensure that the RenderMvcController.Index(RenderModel model) action will be executed + /// if the + /// + internal class RenderIndexActionSelectorAttribute : ActionMethodSelectorAttribute + { + private static readonly ConcurrentDictionary ControllerDescCache = new ConcurrentDictionary(); + + /// + /// Determines whether the action method selection is valid for the specified controller context. + /// + /// + /// true if the action method selection is valid for the specified controller context; otherwise, false. + /// + /// The controller context.Information about the action method. + public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo) + { + var currType = methodInfo.ReflectedType; + var baseType = methodInfo.DeclaringType; + + //It's the same type, so this must be the Index action to use + if (currType == baseType) return true; + + if (currType == null) return false; + + var controllerDesc = ControllerDescCache.GetOrAdd(currType, type => new ReflectedControllerDescriptor(currType)); + var actions = controllerDesc.GetCanonicalActions(); + + //If there are more than one Index action for this controller, then + // this base class one should not be matched + return actions.Count(x => x.ActionName == "Index") <= 1; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Mvc/RenderMvcController.cs b/src/Umbraco.Web/Mvc/RenderMvcController.cs index 37c8c6630a..dd06b3ced1 100644 --- a/src/Umbraco.Web/Mvc/RenderMvcController.cs +++ b/src/Umbraco.Web/Mvc/RenderMvcController.cs @@ -1,22 +1,17 @@ using System; -using System.IO; using System.Web.Mvc; -using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Models; -using Umbraco.Core.Services; -using Umbraco.Core.Models; using Umbraco.Web.Models; using Umbraco.Web.Routing; -using Umbraco.Web.Security; namespace Umbraco.Web.Mvc { /// - /// A controller to render front-end requests + /// The default controller to render front-end requests /// - [PreRenderViewActionFilter] + [PreRenderViewActionFilter] public class RenderMvcController : UmbracoController, IRenderMvcController { @@ -64,7 +59,7 @@ namespace Umbraco.Web.Mvc { if (_publishedContentRequest != null) return _publishedContentRequest; - if (!RouteData.DataTokens.ContainsKey("umbraco-doc-request")) + if (RouteData.DataTokens.ContainsKey("umbraco-doc-request") == false) { throw new InvalidOperationException("DataTokens must contain an 'umbraco-doc-request' key with a PublishedContentRequest object"); } @@ -101,10 +96,8 @@ namespace Umbraco.Web.Mvc protected ActionResult CurrentTemplate(T model) { var template = ControllerContext.RouteData.Values["action"].ToString(); - if (!EnsurePhsyicalViewExists(template)) - { - return Content(""); - } + if (EnsurePhsyicalViewExists(template) == false) + throw new Exception("No physical template file was found for template " + template); return View(template, model); } @@ -113,10 +106,10 @@ namespace Umbraco.Web.Mvc /// /// /// + [RenderIndexActionSelector] public virtual ActionResult Index(RenderModel model) { return CurrentTemplate(model); } - } } \ No newline at end of file diff --git a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs index 64dcb2a04b..bce3515c1b 100644 --- a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs +++ b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs @@ -310,8 +310,8 @@ namespace Umbraco.Web.Mvc //check if that controller exists if (controllerType != null) { - //ensure the controller is of type 'IRenderMvcController' and ControllerBase - if (TypeHelper.IsTypeAssignableFrom(controllerType) + //ensure the controller is of type IRenderMvcController and ControllerBase + if (TypeHelper.IsTypeAssignableFrom(controllerType) && TypeHelper.IsTypeAssignableFrom(controllerType)) { //set the controller and name to the custom one @@ -328,7 +328,7 @@ namespace Umbraco.Web.Mvc "The current Document Type {0} matches a locally declared controller of type {1}. Custom Controllers for Umbraco routing must implement '{2}' and inherit from '{3}'.", () => publishedContentRequest.PublishedContent.DocumentTypeAlias, () => controllerType.FullName, - () => typeof(IRenderMvcController).FullName, + () => typeof(IRenderController).FullName, () => typeof(ControllerBase).FullName); //we cannot route to this custom controller since it is not of the correct type so we'll continue with the defaults diff --git a/src/Umbraco.Web/Mvc/RenderViewEngine.cs b/src/Umbraco.Web/Mvc/RenderViewEngine.cs index 794eecf0ca..ed7b941580 100644 --- a/src/Umbraco.Web/Mvc/RenderViewEngine.cs +++ b/src/Umbraco.Web/Mvc/RenderViewEngine.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Web.Mvc; -using Microsoft.Web.Mvc; using Umbraco.Core; using Umbraco.Core.IO; using Umbraco.Web.Models; diff --git a/src/Umbraco.Web/Mvc/UmbracoPageResult.cs b/src/Umbraco.Web/Mvc/UmbracoPageResult.cs index 1c12e7e498..f23515c1d9 100644 --- a/src/Umbraco.Web/Mvc/UmbracoPageResult.cs +++ b/src/Umbraco.Web/Mvc/UmbracoPageResult.cs @@ -110,7 +110,7 @@ namespace Umbraco.Web.Mvc tempDataDictionary.Save(context, new SessionStateTempDataProvider()); var viewCtx = new ViewContext(context, new DummyView(), new ViewDataDictionary(), tempDataDictionary, new StringWriter()); - viewCtx.ViewData.ModelState.Merge(context.Controller.ViewData.ModelState); + viewCtx.ViewData.ModelState.Merge(new ModelStateDictionary(context.Controller.ViewData.ModelState)); foreach (var d in context.Controller.ViewData) viewCtx.ViewData[d.Key] = d.Value; diff --git a/src/Umbraco.Web/Mvc/UmbracoViewPageOfTModel.cs b/src/Umbraco.Web/Mvc/UmbracoViewPageOfTModel.cs index 0cf248fd48..480f182855 100644 --- a/src/Umbraco.Web/Mvc/UmbracoViewPageOfTModel.cs +++ b/src/Umbraco.Web/Mvc/UmbracoViewPageOfTModel.cs @@ -238,7 +238,7 @@ namespace Umbraco.Web.Mvc { if (UmbracoContext.Current.IsDebug || UmbracoContext.Current.InPreviewMode) { - var text = value.ToString().ToLowerInvariant(); + var text = value.ToString(); var pos = text.IndexOf("", StringComparison.InvariantCultureIgnoreCase); if (pos > -1) @@ -294,4 +294,4 @@ namespace Umbraco.Web.Mvc return WebViewPageExtensions.RenderSection(this, name, defaultContents); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Mvc/ValidateMvcAngularAntiForgeryTokenAttribute.cs b/src/Umbraco.Web/Mvc/ValidateMvcAngularAntiForgeryTokenAttribute.cs new file mode 100644 index 0000000000..977f37473b --- /dev/null +++ b/src/Umbraco.Web/Mvc/ValidateMvcAngularAntiForgeryTokenAttribute.cs @@ -0,0 +1,61 @@ +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Security.Claims; +using System.Web.Mvc; +using Umbraco.Web.WebApi.Filters; + +namespace Umbraco.Web.Mvc +{ + /// + /// A filter to check for the csrf token based on Angular's standard approach + /// + /// + /// Code derived from http://ericpanorel.net/2013/07/28/spa-authentication-and-csrf-mvc4-antiforgery-implementation/ + /// + /// If the authentication type is cookie based, then this filter will execute, otherwise it will be disabled + /// + public sealed class ValidateMvcAngularAntiForgeryTokenAttribute : ActionFilterAttribute + { + public override void OnActionExecuting(ActionExecutingContext filterContext) + { + var userIdentity = filterContext.HttpContext.User.Identity as ClaimsIdentity; + if (userIdentity != null) + { + //if there is not CookiePath claim, then exist + if (userIdentity.HasClaim(x => x.Type == ClaimTypes.CookiePath) == false) + { + base.OnActionExecuting(filterContext); + return; + } + } + + string failedReason; + var headers = new List>>(); + foreach (var key in filterContext.HttpContext.Request.Headers.AllKeys) + { + if (headers.Any(x => x.Key == key)) + { + var found = headers.First(x => x.Key == key); + found.Value.Add(filterContext.HttpContext.Request.Headers[key]); + } + else + { + headers.Add(new KeyValuePair>(key, new List { filterContext.HttpContext.Request.Headers[key] })); + } + } + var cookie = filterContext.HttpContext.Request.Cookies[AngularAntiForgeryHelper.CsrfValidationCookieName]; + if (AngularAntiForgeryHelper.ValidateHeaders( + headers.Select(x => new KeyValuePair>(x.Key, x.Value)).ToArray(), + cookie == null ? "" : cookie.Value, + out failedReason) == false) + { + var result = new HttpStatusCodeResult(HttpStatusCode.ExpectationFailed); + filterContext.Result = result; + return; + } + + base.OnActionExecuting(filterContext); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Properties/AssemblyInfo.cs b/src/Umbraco.Web/Properties/AssemblyInfo.cs index ad54647119..c881ba448a 100644 --- a/src/Umbraco.Web/Properties/AssemblyInfo.cs +++ b/src/Umbraco.Web/Properties/AssemblyInfo.cs @@ -30,9 +30,10 @@ using System.Security; [assembly: InternalsVisibleTo("Umbraco.Tests")] [assembly: InternalsVisibleTo("umbraco.MacroEngines")] [assembly: InternalsVisibleTo("Umbraco.Web.UI")] -[assembly: InternalsVisibleTo("Umbraco.Courier.Persistence")] [assembly: InternalsVisibleTo("umbraco.webservices")] [assembly: InternalsVisibleTo("Concorde.Sync")] -[assembly: InternalsVisibleTo("Umbraco.Belle")] +[assembly: InternalsVisibleTo("Umbraco.Courier.Core")] +[assembly: InternalsVisibleTo("Umbraco.Courier.Persistence")] +[assembly: InternalsVisibleTo("Umbraco.VisualStudio")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/CheckBoxListPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/CheckBoxListPropertyEditor.cs index 6bf1519ab0..2cebd665cd 100644 --- a/src/Umbraco.Web/PropertyEditors/CheckBoxListPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/CheckBoxListPropertyEditor.cs @@ -12,7 +12,7 @@ namespace Umbraco.Web.PropertyEditors /// as INT and we have logic in here to ensure it is formatted correctly including ensuring that the string value is published /// in cache and not the int ID. /// - [PropertyEditor(Constants.PropertyEditors.CheckBoxListAlias, "Checkbox list", "checkboxlist")] + [PropertyEditor(Constants.PropertyEditors.CheckBoxListAlias, "Checkbox list", "checkboxlist", Icon="icon-bulleted-list", Group="lists")] public class CheckBoxListPropertyEditor : PropertyEditor { /// diff --git a/src/Umbraco.Web/PropertyEditors/ColorPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/ColorPickerPropertyEditor.cs index 3a1cb8f509..95d2c5f07e 100644 --- a/src/Umbraco.Web/PropertyEditors/ColorPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ColorPickerPropertyEditor.cs @@ -4,7 +4,7 @@ using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.ColorPickerAlias, "Color Picker", "colorpicker")] + [PropertyEditor(Constants.PropertyEditors.ColorPickerAlias, "Color Picker", "colorpicker", Icon="icon-colorpicker", Group="Pickers")] public class ColorPickerPropertyEditor : PropertyEditor { /// diff --git a/src/Umbraco.Web/PropertyEditors/ContentPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/ContentPickerPropertyEditor.cs index d5f8c10bc3..774dabb6b3 100644 --- a/src/Umbraco.Web/PropertyEditors/ContentPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ContentPickerPropertyEditor.cs @@ -5,17 +5,19 @@ using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.ContentPickerAlias, "Content Picker", "INT", "contentpicker", IsParameterEditor = true)] + [PropertyEditor(Constants.PropertyEditors.ContentPickerAlias, "Content Picker", "INT", "contentpicker", IsParameterEditor = true, Group = "Pickers")] public class ContentPickerPropertyEditor : PropertyEditor { public ContentPickerPropertyEditor(ILogger logger) : base(logger) { _internalPreValues = new Dictionary - { - {"showEditButton", "0"}, - {"startNodeId", "-1"} - }; + { + {"startNodeId", "-1"}, + {"showOpenButton", "0"}, + {"showEditButton", "0"}, + {"showPathOnHover", "0"} + }; } private IDictionary _internalPreValues; @@ -32,12 +34,17 @@ namespace Umbraco.Web.PropertyEditors internal class ContentPickerPreValueEditor : PreValueEditor { + [PreValueField("showOpenButton", "Show open button", "boolean")] + public string ShowOpenButton { get; set; } + [PreValueField("showEditButton", "Show edit button (this feature is in preview!)", "boolean")] public string ShowEditButton { get; set; } [PreValueField("startNodeId", "Start node", "treepicker")] public int StartNodeId { get; set; } + [PreValueField("showPathOnHover", "Show path when hovering items", "boolean")] + public bool ShowPathOnHover { get; set; } } } } \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/DatePreValueEditor.cs b/src/Umbraco.Web/PropertyEditors/DatePreValueEditor.cs new file mode 100644 index 0000000000..dde4d6c2f1 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/DatePreValueEditor.cs @@ -0,0 +1,10 @@ +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + internal class DatePreValueEditor : PreValueEditor + { + [PreValueField("format", "Date format", "textstring", Description = "If left empty then the format is YYYY-MM-DD. (see momentjs.com for supported formats)")] + public string DefaultValue { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/DatePropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/DatePropertyEditor.cs index 7b8830269e..f226fe1ee5 100644 --- a/src/Umbraco.Web/PropertyEditors/DatePropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/DatePropertyEditor.cs @@ -10,7 +10,7 @@ using Umbraco.Core.Services; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.DateAlias, "Date", "DATE", "datepicker")] + [PropertyEditor(Constants.PropertyEditors.DateAlias, "Date", "DATE", "datepicker", Icon="icon-calendar")] public class DatePropertyEditor : PropertyEditor { public DatePropertyEditor(ILogger logger): base(logger) @@ -24,9 +24,6 @@ namespace Umbraco.Web.PropertyEditors private IDictionary _defaultPreVals; - /// - /// Overridden because we ONLY support Date (no time) format and we don't have pre-values in the db. - /// public override IDictionary DefaultPreValues { get { return _defaultPreVals; } @@ -61,5 +58,10 @@ namespace Umbraco.Web.PropertyEditors } } + + protected override PreValueEditor CreatePreValueEditor() + { + return new DatePreValueEditor(); + } } } \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/DateTimePropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/DateTimePropertyEditor.cs index 06795caa88..9dca5fb435 100644 --- a/src/Umbraco.Web/PropertyEditors/DateTimePropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/DateTimePropertyEditor.cs @@ -6,7 +6,7 @@ using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.DateTimeAlias, "Date/Time", "datepicker", ValueType = "DATETIME")] + [PropertyEditor(Constants.PropertyEditors.DateTimeAlias, "Date/Time", "datepicker", ValueType = "DATETIME", Icon="icon-time")] public class DateTimePropertyEditor : PropertyEditor { public DateTimePropertyEditor(ILogger logger): base(logger) @@ -22,7 +22,7 @@ namespace Umbraco.Web.PropertyEditors private IDictionary _defaultPreVals; /// - /// Overridden because we ONLY support Date + Time format and we don't have pre-values in the db. + /// Overridden because we ONLY support Date + Time format /// public override IDictionary DefaultPreValues { @@ -38,5 +38,10 @@ namespace Umbraco.Web.PropertyEditors return editor; } + + protected override PreValueEditor CreatePreValueEditor() + { + return new DatePreValueEditor(); + } } } \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/DecimalPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/DecimalPropertyEditor.cs new file mode 100644 index 0000000000..f636e8cdb2 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/DecimalPropertyEditor.cs @@ -0,0 +1,57 @@ +using Umbraco.Core; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + [PropertyEditor(Constants.PropertyEditors.DecimalAlias, "Decimal", "decimal", "decimal", IsParameterEditor = true)] + public class DecimalPropertyEditor : PropertyEditor + { + /// + /// Overridden to ensure that the value is validated + /// + /// + protected override PropertyValueEditor CreateValueEditor() + { + var editor = base.CreateValueEditor(); + editor.Validators.Add(new DecimalValidator()); + return editor; + } + + protected override PreValueEditor CreatePreValueEditor() + { + return new DecimalPreValueEditor(); + } + + /// + /// A custom pre-value editor class to deal with the legacy way that the pre-value data is stored. + /// + internal class DecimalPreValueEditor : PreValueEditor + { + public DecimalPreValueEditor() + { + //create the fields + Fields.Add(new PreValueField(new DecimalValidator()) + { + Description = "Enter the minimum amount of number to be entered", + Key = "min", + View = "decimal", + Name = "Minimum" + }); + Fields.Add(new PreValueField(new DecimalValidator()) + { + Description = "Enter the intervals amount between each step of number to be entered", + Key = "step", + View = "decimal", + Name = "Step Size" + }); + Fields.Add(new PreValueField(new DecimalValidator()) + { + Description = "Enter the maximum amount of number to be entered", + Key = "max", + View = "decimal", + Name = "Maximum" + }); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/DropDownMultiplePropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/DropDownMultiplePropertyEditor.cs index c51ef91258..e7cf1bb87b 100644 --- a/src/Umbraco.Web/PropertyEditors/DropDownMultiplePropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/DropDownMultiplePropertyEditor.cs @@ -15,7 +15,7 @@ namespace Umbraco.Web.PropertyEditors [ParameterEditor("propertyTypePickerMultiple", "Name", "textbox")] [ParameterEditor("contentTypeMultiple", "Name", "textbox")] [ParameterEditor("tabPickerMultiple", "Name", "textbox")] - [PropertyEditor(Constants.PropertyEditors.DropDownListMultipleAlias, "Dropdown list multiple", "dropdown")] + [PropertyEditor(Constants.PropertyEditors.DropDownListMultipleAlias, "Dropdown list multiple", "dropdown", Group = "lists", Icon="icon-bulleted-list")] public class DropDownMultiplePropertyEditor : DropDownMultipleWithKeysPropertyEditor { /// diff --git a/src/Umbraco.Web/PropertyEditors/DropDownMultipleWithKeysPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/DropDownMultipleWithKeysPropertyEditor.cs index 10b638d5a3..9540c4fdc5 100644 --- a/src/Umbraco.Web/PropertyEditors/DropDownMultipleWithKeysPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/DropDownMultipleWithKeysPropertyEditor.cs @@ -13,7 +13,7 @@ namespace Umbraco.Web.PropertyEditors /// Due to maintaining backwards compatibility this data type stores the value as a string which is a comma separated value of the /// ids of the individual items so we have logic in here to deal with that. /// - [PropertyEditor(Constants.PropertyEditors.DropdownlistMultiplePublishKeysAlias, "Dropdown list multiple, publish keys", "dropdown")] + [PropertyEditor(Constants.PropertyEditors.DropdownlistMultiplePublishKeysAlias, "Dropdown list multiple, publish keys", "dropdown", Group = "lists", Icon = "icon-bulleted-list")] public class DropDownMultipleWithKeysPropertyEditor : DropDownPropertyEditor { /// diff --git a/src/Umbraco.Web/PropertyEditors/DropDownPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/DropDownPropertyEditor.cs index 9c0217d054..3607a8b5fa 100644 --- a/src/Umbraco.Web/PropertyEditors/DropDownPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/DropDownPropertyEditor.cs @@ -17,7 +17,7 @@ namespace Umbraco.Web.PropertyEditors /// as INT and we have logic in here to ensure it is formatted correctly including ensuring that the string value is published /// in cache and not the int ID. /// - [PropertyEditor(Constants.PropertyEditors.DropDownListAlias, "Dropdown list", "dropdown", ValueType = "STRING")] + [PropertyEditor(Constants.PropertyEditors.DropDownListAlias, "Dropdown list", "dropdown", ValueType = "STRING", Group = "lists", Icon = "icon-indent")] public class DropDownPropertyEditor : DropDownWithKeysPropertyEditor { /// diff --git a/src/Umbraco.Web/PropertyEditors/DropDownWithKeysPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/DropDownWithKeysPropertyEditor.cs index c9f5f093a7..b4138d170a 100644 --- a/src/Umbraco.Web/PropertyEditors/DropDownWithKeysPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/DropDownWithKeysPropertyEditor.cs @@ -12,7 +12,7 @@ namespace Umbraco.Web.PropertyEditors /// as INT and we have logic in here to ensure it is formatted correctly including ensuring that the INT ID value is published /// in cache and not the string value. /// - [PropertyEditor(Constants.PropertyEditors.DropdownlistPublishingKeysAlias, "Dropdown list, publishing keys", "dropdown", ValueType = "INT")] + [PropertyEditor(Constants.PropertyEditors.DropdownlistPublishingKeysAlias, "Dropdown list, publishing keys", "dropdown", ValueType = "INT", Group = "lists", Icon = "icon-indent")] public class DropDownWithKeysPropertyEditor : PropertyEditor { /// diff --git a/src/Umbraco.Web/PropertyEditors/EmailAddressPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/EmailAddressPropertyEditor.cs index 2045c59c9b..2c695f2e5f 100644 --- a/src/Umbraco.Web/PropertyEditors/EmailAddressPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/EmailAddressPropertyEditor.cs @@ -4,7 +4,7 @@ using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.EmailAddressAlias, "Email address", "email")] + [PropertyEditor(Constants.PropertyEditors.EmailAddressAlias, "Email address", "email", Icon="icon-message")] public class EmailAddressPropertyEditor : PropertyEditor { /// diff --git a/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs index d59c3c5b2b..d4864c9243 100644 --- a/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs @@ -20,7 +20,7 @@ using Umbraco.Core.Services; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.UploadFieldAlias, "File upload", "fileupload")] + [PropertyEditor(Constants.PropertyEditors.UploadFieldAlias, "File upload", "fileupload", Icon = "icon-download-alt", Group = "media")] public class FileUploadPropertyEditor : PropertyEditor, IApplicationEventHandler { private readonly MediaFileSystem _mediaFileSystem; @@ -33,6 +33,8 @@ namespace Umbraco.Web.PropertyEditors if (contentSettings == null) throw new ArgumentNullException("contentSettings"); _mediaFileSystem = mediaFileSystem; _contentSettings = contentSettings; + MemberService.Deleted += (sender, args) => + args.MediaFilesToDelete.AddRange(ServiceDeleted(args.DeletedEntities.Cast())); } /// diff --git a/src/Umbraco.Web/PropertyEditors/FileUploadPropertyValueEditor.cs b/src/Umbraco.Web/PropertyEditors/FileUploadPropertyValueEditor.cs index 1fd223611a..4729b736d4 100644 --- a/src/Umbraco.Web/PropertyEditors/FileUploadPropertyValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/FileUploadPropertyValueEditor.cs @@ -146,8 +146,7 @@ namespace Umbraco.Web.PropertyEditors { var thumbnailSizes = thumbs.First().Value.Value; // additional thumbnails configured as prevalues on the DataType - var sep = (thumbnailSizes.Contains("") == false && thumbnailSizes.Contains(",")) ? ',' : ';'; - foreach (var thumb in thumbnailSizes.Split(sep)) + foreach (var thumb in thumbnailSizes.Split(new[] { ";", "," }, StringSplitOptions.RemoveEmptyEntries)) { int thumbSize; if (thumb == "" || int.TryParse(thumb, out thumbSize) == false) continue; diff --git a/src/Umbraco.Web/PropertyEditors/FolderBrowserPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/FolderBrowserPropertyEditor.cs index 96b516434a..5a4e60d3c6 100644 --- a/src/Umbraco.Web/PropertyEditors/FolderBrowserPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/FolderBrowserPropertyEditor.cs @@ -1,4 +1,7 @@ -using System.ComponentModel; +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; using System.Web.Mvc; using Umbraco.Core; using Umbraco.Core.Logging; @@ -6,14 +9,10 @@ using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.FolderBrowserAlias, "Folder Browser", "folderbrowser", HideLabel=true)] + [Obsolete("This is no longer used by default, use the ListViewPropertyEditor instead")] + [PropertyEditor(Constants.PropertyEditors.FolderBrowserAlias, "(Obsolete) Folder Browser", "folderbrowser", HideLabel=true, Icon="icon-folder", Group="media")] public class FolderBrowserPropertyEditor : PropertyEditor { - /// - /// The constructor will setup the property editor based on the attribute if one is found - /// - public FolderBrowserPropertyEditor(ILogger logger) : base(logger) - { - } + } } \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs index 073730b1ff..3697879570 100644 --- a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs @@ -3,12 +3,13 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; -using Umbraco.Core.Logging; +using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Core.Constants.PropertyEditors.GridAlias, "Grid layout", "grid", HideLabel=true, IsParameterEditor = false, ValueType="JSON")] + [PropertyEditor(Core.Constants.PropertyEditors.GridAlias, "Grid layout", "grid", HideLabel = true, IsParameterEditor = false, ValueType = "JSON", Group="rich content", Icon="icon-layout")] public class GridPropertyEditor : PropertyEditor { /// @@ -24,23 +25,33 @@ namespace Umbraco.Web.PropertyEditors /// protected override PropertyValueEditor CreateValueEditor() { - var editor = base.CreateValueEditor(); - - return editor; + var baseEditor = base.CreateValueEditor(); + return new GridPropertyValueEditor(baseEditor); } protected override PreValueEditor CreatePreValueEditor() { return new GridPreValueEditor(); } + + internal class GridPropertyValueEditor : PropertyValueEditorWrapper + { + public GridPropertyValueEditor(PropertyValueEditor wrapped) + : base(wrapped) + { + } + + } + + internal class GridPreValueEditor : PreValueEditor + { + [PreValueField("items", "Grid", "views/propertyeditors/grid/grid.prevalues.html", Description = "Grid configuration")] + public string Items { get; set; } + + [PreValueField("rte", "Rich text editor", "views/propertyeditors/rte/rte.prevalues.html", Description = "Rich text editor configuration")] + public string Rte { get; set; } + } } - internal class GridPreValueEditor : PreValueEditor - { - [PreValueField("items", "Grid", "views/propertyeditors/grid/grid.prevalues.html", Description = "Grid configuration")] - public string Items { get; set; } - [PreValueField("rte", "Rich text editor", "views/propertyeditors/rte/rte.prevalues.html", Description = "Rich text editor configuration")] - public string Rte { get; set; } - } } diff --git a/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs index 2667bc91d6..87bb38fa1d 100644 --- a/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs @@ -16,7 +16,7 @@ using Umbraco.Core.Services; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.ImageCropperAlias, "Image Cropper", "imagecropper", ValueType = "JSON", HideLabel = false)] + [PropertyEditor(Constants.PropertyEditors.ImageCropperAlias, "Image Cropper", "imagecropper", ValueType = "JSON", HideLabel = false, Group="media", Icon="icon-crop")] public class ImageCropperPropertyEditor : PropertyEditor, IApplicationEventHandler { private readonly MediaFileSystem _mediaFileSystem; @@ -35,6 +35,8 @@ namespace Umbraco.Web.PropertyEditors {"focalPoint", "{left: 0.5, top: 0.5}"}, {"src", ""} }; + MemberService.Deleted += (sender, args) => + args.MediaFilesToDelete.AddRange(ServiceDeleted(args.DeletedEntities.Cast())); } /// diff --git a/src/Umbraco.Web/PropertyEditors/IntegerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/IntegerPropertyEditor.cs index a0344b5f5c..dc134bc87f 100644 --- a/src/Umbraco.Web/PropertyEditors/IntegerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/IntegerPropertyEditor.cs @@ -4,7 +4,7 @@ using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.IntegerAlias, "Numeric", "integer", IsParameterEditor = true)] + [PropertyEditor(Constants.PropertyEditors.IntegerAlias, "Numeric", "integer", IsParameterEditor = true, ValueType = "integer")] public class IntegerPropertyEditor : PropertyEditor { /// @@ -24,14 +24,14 @@ namespace Umbraco.Web.PropertyEditors editor.Validators.Add(new IntegerValidator()); return editor; } - - protected override PreValueEditor CreatePreValueEditor() + + protected override PreValueEditor CreatePreValueEditor() { - return new IntegerPreValueEditor(); - } - - /// - /// A custom pre-value editor class to deal with the legacy way that the pre-value data is stored. + return new IntegerPreValueEditor(); + } + + /// + /// A custom pre-value editor class to deal with the legacy way that the pre-value data is stored. /// internal class IntegerPreValueEditor : PreValueEditor { diff --git a/src/Umbraco.Web/PropertyEditors/LabelPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/LabelPropertyEditor.cs index 209a8b43e5..7c9b95f098 100644 --- a/src/Umbraco.Web/PropertyEditors/LabelPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/LabelPropertyEditor.cs @@ -9,7 +9,7 @@ using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.NoEditAlias, "Label", "readonlyvalue")] + [PropertyEditor(Constants.PropertyEditors.NoEditAlias, "Label", "readonlyvalue", Icon="icon-readonly")] public class LabelPropertyEditor : PropertyEditor { /// diff --git a/src/Umbraco.Web/PropertyEditors/ListViewPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/ListViewPropertyEditor.cs index f105f9f699..9184892c3d 100644 --- a/src/Umbraco.Web/PropertyEditors/ListViewPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ListViewPropertyEditor.cs @@ -10,7 +10,7 @@ using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.ListViewAlias, "List view", "listview", HideLabel = true)] + [PropertyEditor(Constants.PropertyEditors.ListViewAlias, "List view", "listview", HideLabel = true, Group = "lists", Icon = "icon-item-arrangement")] public class ListViewPropertyEditor : PropertyEditor { /// @@ -41,6 +41,13 @@ namespace Umbraco.Web.PropertyEditors new {alias = "updateDate", header = "Last edited", isSystem = 1}, new {alias = "owner", header = "Created by", isSystem = 1} } + }, + { + "layouts", new[] + { + new {name = "List", path = "views/propertyeditors/listview/layouts/list/list.html", icon = "icon-list", isSystem = 1, selected = true}, + new {name = "Grid", path = "views/propertyeditors/listview/layouts/grid/grid.html", icon = "icon-thumbnails-small", isSystem = 1, selected = true} + } } }; } @@ -59,6 +66,9 @@ namespace Umbraco.Web.PropertyEditors [PreValueField("orderDirection", "Order Direction", "views/propertyeditors/listview/orderdirection.prevalues.html")] public int OrderDirection { get; set; } + [PreValueField("layouts", "Layouts", "views/propertyeditors/listview/layouts.prevalues.html")] + public int Layouts { get; set; } + [PreValueField("includeProperties", "Columns Displayed", "views/propertyeditors/listview/includeproperties.prevalues.html", Description = "The properties that will be displayed for each column")] public object IncludeProperties { get; set; } diff --git a/src/Umbraco.Web/PropertyEditors/MacroContainerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MacroContainerPropertyEditor.cs index b2836e599d..3fbf46a840 100644 --- a/src/Umbraco.Web/PropertyEditors/MacroContainerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MacroContainerPropertyEditor.cs @@ -10,7 +10,7 @@ using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.MacroContainerAlias, "Macro container", "macrocontainer", ValueType = "TEXT")] + [PropertyEditor(Constants.PropertyEditors.MacroContainerAlias, "Macro container", "macrocontainer", ValueType = "TEXT", Group="rich content", Icon="icon-settings-alt")] public class MacroContainerPropertyEditor : PropertyEditor { /// diff --git a/src/Umbraco.Web/PropertyEditors/MarkdownPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MarkdownPropertyEditor.cs index 0773ba8df1..9abf0933df 100644 --- a/src/Umbraco.Web/PropertyEditors/MarkdownPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MarkdownPropertyEditor.cs @@ -4,7 +4,7 @@ using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.MarkdownEditorAlias, "Markdown editor", "markdowneditor", ValueType = "TEXT")] + [PropertyEditor(Constants.PropertyEditors.MarkdownEditorAlias, "Markdown editor", "markdowneditor", ValueType = "TEXT", Icon="icon-code", Group="rich content")] public class MarkdownPropertyEditor : PropertyEditor { /// diff --git a/src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs index 82d02300ed..e90669711c 100644 --- a/src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs @@ -10,7 +10,7 @@ using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.MediaPickerAlias, "Legacy Media Picker", "INT", "mediapicker")] + [PropertyEditor(Constants.PropertyEditors.MediaPickerAlias, "Legacy Media Picker", "INT", "mediapicker", Group="media", Icon="icon-picture")] public class MediaPickerPropertyEditor : PropertyEditor { public MediaPickerPropertyEditor(ILogger logger) : base(logger) diff --git a/src/Umbraco.Web/PropertyEditors/MemberGroupPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MemberGroupPickerPropertyEditor.cs index 7a5b58c5bf..19233f57db 100644 --- a/src/Umbraco.Web/PropertyEditors/MemberGroupPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MemberGroupPickerPropertyEditor.cs @@ -9,7 +9,7 @@ using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.MemberGroupPickerAlias, "Member Group Picker", "membergrouppicker")] + [PropertyEditor(Constants.PropertyEditors.MemberGroupPickerAlias, "Member Group Picker", "membergrouppicker", Group="People", Icon="icon-users")] public class MemberGroupPickerPropertyEditor : PropertyEditor { /// diff --git a/src/Umbraco.Web/PropertyEditors/MemberPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MemberPickerPropertyEditor.cs index 3f6374763b..cfc8d933ba 100644 --- a/src/Umbraco.Web/PropertyEditors/MemberPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MemberPickerPropertyEditor.cs @@ -9,7 +9,7 @@ using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.MemberPickerAlias, "Member Picker", "INT", "memberpicker")] + [PropertyEditor(Constants.PropertyEditors.MemberPickerAlias, "Member Picker", "INT", "memberpicker", Group = "People", Icon = "icon-user")] public class MemberPickerPropertyEditor : PropertyEditor { /// diff --git a/src/Umbraco.Web/PropertyEditors/MultiNodeTreePickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MultiNodeTreePickerPropertyEditor.cs index 6c291a474a..98c174b5ef 100644 --- a/src/Umbraco.Web/PropertyEditors/MultiNodeTreePickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MultiNodeTreePickerPropertyEditor.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Collections.Generic; using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Models; @@ -10,16 +6,18 @@ using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.MultiNodeTreePickerAlias, "Multinode Treepicker", "contentpicker")] + [PropertyEditor(Constants.PropertyEditors.MultiNodeTreePickerAlias, "Multinode Treepicker", "contentpicker", Group="pickers", Icon="icon-page-add")] public class MultiNodeTreePickerPropertyEditor : PropertyEditor { public MultiNodeTreePickerPropertyEditor(ILogger logger) : base(logger) { _internalPreValues = new Dictionary - { - {"multiPicker", "1"}, - {"showEditButton", "0"} - }; + { + {"multiPicker", "1"}, + {"showOpenButton", "0"}, + {"showEditButton", "0"}, + {"showPathOnHover", "0"} + }; } protected override PreValueEditor CreatePreValueEditor() @@ -48,9 +46,16 @@ namespace Umbraco.Web.PropertyEditors [PreValueField("maxNumber", "Maximum number of items", "number")] public string MaxNumber { get; set; } + + [PreValueField("showOpenButton", "Show open button", "boolean")] + public string ShowOpenButton { get; set; } + [PreValueField("showEditButton", "Show edit button (this feature is in preview!)", "boolean")] public string ShowEditButton { get; set; } + [PreValueField("showPathOnHover", "Show path when hovering items", "boolean")] + public bool ShowPathOnHover { get; set; } + /// /// This ensures the multiPicker pre-val is set based on the maxNumber of nodes set /// diff --git a/src/Umbraco.Web/PropertyEditors/MultipleMediaPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MultipleMediaPickerPropertyEditor.cs index 5c8048cf7f..5f3409cfc8 100644 --- a/src/Umbraco.Web/PropertyEditors/MultipleMediaPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MultipleMediaPickerPropertyEditor.cs @@ -4,7 +4,7 @@ using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.MultipleMediaPickerAlias, "Media Picker", "mediapicker")] + [PropertyEditor(Constants.PropertyEditors.MultipleMediaPickerAlias, "Media Picker", "mediapicker", Group = "media", Icon = "icon-pictures-alt-2")] public class MultipleMediaPickerPropertyEditor : MediaPickerPropertyEditor { public MultipleMediaPickerPropertyEditor(ILogger logger): base(logger) diff --git a/src/Umbraco.Web/PropertyEditors/MultipleTextStringPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MultipleTextStringPropertyEditor.cs index c514008c66..d668535feb 100644 --- a/src/Umbraco.Web/PropertyEditors/MultipleTextStringPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MultipleTextStringPropertyEditor.cs @@ -14,7 +14,7 @@ using Umbraco.Core.Services; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.MultipleTextstringAlias, "Multiple Textbox", "multipletextbox", ValueType = "TEXT")] + [PropertyEditor(Constants.PropertyEditors.MultipleTextstringAlias, "Repeatable textstrings", "multipletextbox", ValueType = "TEXT", Icon="icon-ordered-list", Group="lists")] public class MultipleTextStringPropertyEditor : PropertyEditor { /// diff --git a/src/Umbraco.Web/PropertyEditors/PublishValueValueEditor.cs b/src/Umbraco.Web/PropertyEditors/PublishValueValueEditor.cs index 053c349ccb..98a1ecefa1 100644 --- a/src/Umbraco.Web/PropertyEditors/PublishValueValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/PublishValueValueEditor.cs @@ -39,6 +39,9 @@ namespace Umbraco.Web.PropertyEditors /// public override string ConvertDbToString(Property property, PropertyType propertyType, IDataTypeService dataTypeService) { + if (property.Value == null) + return null; + var idAttempt = property.Value.TryConvertTo(); if (idAttempt.Success) { diff --git a/src/Umbraco.Web/PropertyEditors/PublishValuesMultipleValueEditor.cs b/src/Umbraco.Web/PropertyEditors/PublishValuesMultipleValueEditor.cs index 3d68936118..044a958deb 100644 --- a/src/Umbraco.Web/PropertyEditors/PublishValuesMultipleValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/PublishValuesMultipleValueEditor.cs @@ -39,6 +39,9 @@ namespace Umbraco.Web.PropertyEditors /// public override string ConvertDbToString(Property property, PropertyType propertyType, IDataTypeService dataTypeService) { + if (property.Value == null) + return null; + //publishing ids, so just need to return the value as-is if (_publishIds) { diff --git a/src/Umbraco.Web/PropertyEditors/RadioButtonsPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/RadioButtonsPropertyEditor.cs index 92db979edb..24c3a275fb 100644 --- a/src/Umbraco.Web/PropertyEditors/RadioButtonsPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/RadioButtonsPropertyEditor.cs @@ -12,7 +12,7 @@ namespace Umbraco.Web.PropertyEditors /// as INT and we have logic in here to ensure it is formatted correctly including ensuring that the INT ID value is published /// in cache and not the string value. /// - [PropertyEditor(Constants.PropertyEditors.RadioButtonListAlias, "Radio button list", "radiobuttons", ValueType = "INT")] + [PropertyEditor(Constants.PropertyEditors.RadioButtonListAlias, "Radio button list", "radiobuttons", ValueType = "INT", Group="lists", Icon="icon-target")] public class RadioButtonsPropertyEditor : DropDownWithKeysPropertyEditor { /// diff --git a/src/Umbraco.Web/PropertyEditors/RelatedLinksPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/RelatedLinksPropertyEditor.cs index 43d0b05d70..c45697e912 100644 --- a/src/Umbraco.Web/PropertyEditors/RelatedLinksPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/RelatedLinksPropertyEditor.cs @@ -9,7 +9,7 @@ using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.RelatedLinksAlias, "Related links", "relatedlinks", ValueType ="JSON")] + [PropertyEditor(Constants.PropertyEditors.RelatedLinksAlias, "Related links", "relatedlinks", ValueType ="JSON", Icon="icon-thumbnail-list", Group="pickers")] public class RelatedLinksPropertyEditor : PropertyEditor { /// diff --git a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs index 2d900beb91..de460d0196 100644 --- a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs @@ -8,7 +8,7 @@ using Umbraco.Core.Services; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.TinyMCEAlias, "Rich Text Editor", "rte", ValueType = "TEXT", HideLabel = false)] + [PropertyEditor(Constants.PropertyEditors.TinyMCEAlias, "Rich Text Editor", "rte", ValueType = "TEXT", HideLabel = false, Group="Rich Content", Icon="icon-browser-window")] public class RichTextPropertyEditor : PropertyEditor { /// diff --git a/src/Umbraco.Web/PropertyEditors/SliderPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/SliderPropertyEditor.cs index c501752e3c..7548574871 100644 --- a/src/Umbraco.Web/PropertyEditors/SliderPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/SliderPropertyEditor.cs @@ -4,7 +4,7 @@ using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.SliderAlias, "Slider", "slider")] + [PropertyEditor(Constants.PropertyEditors.SliderAlias, "Slider", "slider", Icon="icon-navigation-horizontal")] public class SliderPropertyEditor : PropertyEditor { /// @@ -23,7 +23,10 @@ namespace Umbraco.Web.PropertyEditors { [PreValueField("enableRange", "Enable range", "boolean")] - public string Type { get; set; } + public string Range { get; set; } + + [PreValueField("orientation", "Orientation", "views/propertyeditors/slider/orientation.prevalues.html")] + public string Orientation { get; set; } [PreValueField("initVal1", "Initial value", "number")] public int InitialValue { get; set; } @@ -40,9 +43,38 @@ namespace Umbraco.Web.PropertyEditors [PreValueField("step", "Step increments", "number")] public int StepIncrements { get; set; } - [PreValueField("orientation", "Orientation", "views/propertyeditors/slider/orientation.prevalues.html")] - public string Orientation { get; set; } + [PreValueField("precision", "Precision", "number", Description = "The number of digits shown after the decimal. Defaults to the number of digits after the decimal of step value.")] + public int Precision { get; set; } + [PreValueField("handle", "Handle", "views/propertyeditors/slider/handle.prevalues.html", Description = "Handle shape. Default is 'round\'")] + public string Handle { get; set; } + + [PreValueField("tooltip", "Tooltip", "views/propertyeditors/slider/tooltip.prevalues.html", Description = "Whether to show the tooltip on drag, hide the tooltip, or always show the tooltip. Accepts: 'show', 'hide', or 'always'")] + public string Tooltip { get; set; } + + [PreValueField("tooltipSplit", "Tooltip split", "boolean", Description = "If false show one tootip if true show two tooltips one for each handler")] + public string TooltipSplit { get; set; } + + [PreValueField("tooltipFormat", "Tooltip format", "textstring", Description = "The value wanted to be displayed in the tooltip. Use {0} and {1} for current values - {1} is only for range slider and if not using tooltip split.")] + public string TooltipFormat { get; set; } + + [PreValueField("tooltipPosition", "Tooltip position", "textstring", Description = "Position of tooltip, relative to slider. Accepts 'top'/'bottom' for horizontal sliders and 'left'/'right' for vertically orientated sliders. Default positions are 'top' for horizontal and 'right' for vertical slider.")] + public string TooltipPosition { get; set; } + + [PreValueField("reversed", "Reversed", "boolean", Description = "Whether or not the slider should be reversed")] + public string Reversed { get; set; } + + [PreValueField("ticks", "Ticks", "textstring", Description = "Comma-separated values. Used to define the values of ticks. Tick marks are indicators to denote special values in the range. This option overwrites min and max options.")] + public string Ticks { get; set; } + + [PreValueField("ticksPositions", "Ticks positions", "textstring", Description = "Comma-separated values. Defines the positions of the tick values in percentages. The first value should always be 0, the last value should always be 100 percent.")] + public string TicksPositions { get; set; } + + [PreValueField("ticksLabels", "Ticks labels", "textstring", Description = "Comma-separated values. Defines the labels below the tick marks. Accepts HTML input.")] + public string TicksLabels { get; set; } + + [PreValueField("ticksSnapBounds", "Ticks snap bounds", "number", Description = "Used to define the snap bounds of a tick. Snaps to the tick if value is within these bounds.")] + public string TicksSnapBounds { get; set; } } } } \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/TagsPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/TagsPropertyEditor.cs index 772a1342f2..e0cddceb45 100644 --- a/src/Umbraco.Web/PropertyEditors/TagsPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/TagsPropertyEditor.cs @@ -12,7 +12,7 @@ using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { [SupportTags(typeof(TagPropertyEditorTagDefinition), ValueType = TagValueType.CustomTagList)] - [PropertyEditor(Constants.PropertyEditors.TagsAlias, "Tags", "tags")] + [PropertyEditor(Constants.PropertyEditors.TagsAlias, "Tags", "tags", Icon="icon-tags")] public class TagsPropertyEditor : PropertyEditor { public TagsPropertyEditor(ILogger logger) : base(logger) diff --git a/src/Umbraco.Web/PropertyEditors/TextAreaPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/TextAreaPropertyEditor.cs index 13084e1939..09b85dee1f 100644 --- a/src/Umbraco.Web/PropertyEditors/TextAreaPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/TextAreaPropertyEditor.cs @@ -4,7 +4,7 @@ using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.TextboxMultipleAlias, "Textarea", "textarea", IsParameterEditor = true, ValueType = "TEXT")] + [PropertyEditor(Constants.PropertyEditors.TextboxMultipleAlias, "Textarea", "textarea", IsParameterEditor = true, ValueType = "TEXT", Icon="icon-application-window-alt")] public class TextAreaPropertyEditor : PropertyEditor { /// diff --git a/src/Umbraco.Web/PropertyEditors/TextboxPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/TextboxPropertyEditor.cs index 5bbd6dc062..8a22eb1ddd 100644 --- a/src/Umbraco.Web/PropertyEditors/TextboxPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/TextboxPropertyEditor.cs @@ -11,7 +11,7 @@ using Umbraco.Core.Services; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.TextboxAlias, "Textbox", "textbox", IsParameterEditor = true)] + [PropertyEditor(Constants.PropertyEditors.TextboxAlias, "Textbox", "textbox", IsParameterEditor = true, Group = "Common")] public class TextboxPropertyEditor : PropertyEditor { /// diff --git a/src/Umbraco.Web/PropertyEditors/TrueFalsePropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/TrueFalsePropertyEditor.cs index 56de9a382d..c78e9c455d 100644 --- a/src/Umbraco.Web/PropertyEditors/TrueFalsePropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/TrueFalsePropertyEditor.cs @@ -4,7 +4,7 @@ using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.TrueFalseAlias, "True/False", "INT", "boolean", IsParameterEditor = true)] + [PropertyEditor(Constants.PropertyEditors.TrueFalseAlias, "True/False", "INT", "boolean", IsParameterEditor = true, Group = "Common", Icon="icon-checkbox")] public class TrueFalsePropertyEditor : PropertyEditor { /// diff --git a/src/Umbraco.Web/PropertyEditors/UserPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/UserPickerPropertyEditor.cs index 75efb0718e..6b6de5a1b3 100644 --- a/src/Umbraco.Web/PropertyEditors/UserPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/UserPickerPropertyEditor.cs @@ -7,7 +7,7 @@ using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.UserPickerAlias, "User picker", "INT", "entitypicker")] + [PropertyEditor(Constants.PropertyEditors.UserPickerAlias, "User picker", "INT", "entitypicker", Group="People", Icon="icon-user")] public class UserPickerPropertyEditor : PropertyEditor { private IDictionary _defaultPreValues; diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/RelatedLinksEditorValueConvertor.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/RelatedLinksEditorValueConvertor.cs index 974046473f..e776902feb 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/RelatedLinksEditorValueConvertor.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/RelatedLinksEditorValueConvertor.cs @@ -60,7 +60,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters } catch (Exception ex) { - LogHelper.Error("Could not parse the string " + sourceString + " to a json object", ex); + LogHelper.Error("Could not parse the string " + sourceString + " to a json object", ex); } } @@ -99,7 +99,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters } catch (Exception ex) { - LogHelper.Error("Could not parse the string " + sourceString + " to a json object", ex); + LogHelper.Error("Could not parse the string " + sourceString + " to a json object", ex); } } diff --git a/src/Umbraco.Web/PublishedCache/ContextualPublishedCache.cs b/src/Umbraco.Web/PublishedCache/ContextualPublishedCache.cs index 944b85c576..b5c3d850d2 100644 --- a/src/Umbraco.Web/PublishedCache/ContextualPublishedCache.cs +++ b/src/Umbraco.Web/PublishedCache/ContextualPublishedCache.cs @@ -24,15 +24,6 @@ namespace Umbraco.Web.PublishedCache UmbracoContext = umbracoContext; } - /// - /// Informs the contextual cache that content has changed. - /// - /// The contextual cache may, although that is not mandatory, provide an immutable snapshot of - /// the content over the duration of the context. If you make changes to the content and do want to have - /// the cache update its snapshot, you have to explicitely ask it to do so by calling ContentHasChanged. - public virtual void ContentHasChanged() - { } - /// /// Gets a content identified by its unique identifier. /// diff --git a/src/Umbraco.Web/PublishedCache/MemberPublishedContent.cs b/src/Umbraco.Web/PublishedCache/MemberPublishedContent.cs index 5c0050c2a1..35b3d6ee62 100644 --- a/src/Umbraco.Web/PublishedCache/MemberPublishedContent.cs +++ b/src/Umbraco.Web/PublishedCache/MemberPublishedContent.cs @@ -19,7 +19,7 @@ namespace Umbraco.Web.PublishedCache /// /// Exposes a member object as IPublishedContent /// - public sealed class MemberPublishedContent : PublishedContentBase + public sealed class MemberPublishedContent : PublishedContentWithKeyBase { private readonly IMember _member; @@ -150,6 +150,11 @@ namespace Umbraco.Web.PublishedCache get { return _member.Id; } } + public override Guid Key + { + get { return _member.Key; } + } + public override int TemplateId { get { throw new NotSupportedException(); } diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs index 123a53b2b3..29d9abd5a6 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Configuration; using System.IO; using System.Linq; using System.Xml.XPath; @@ -18,6 +19,9 @@ using Umbraco.Core.Xml; using Umbraco.Web.Models; using UmbracoExamine; using umbraco; +using Umbraco.Core.Cache; +using Umbraco.Core.Sync; +using Umbraco.Web.Cache; namespace Umbraco.Web.PublishedCache.XmlPublishedCache { @@ -52,6 +56,11 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache _indexProvider = indexProvider; } + static PublishedMediaCache() + { + InitializeCacheConfig(); + } + private readonly ApplicationContext _applicationContext; private readonly BaseSearchProvider _searchProvider; private readonly BaseIndexProvider _indexProvider; @@ -167,9 +176,20 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache return null; } - private IPublishedContent GetUmbracoMedia(int id) - { - var searchProvider = GetSearchProviderSafe(); + private IPublishedContent GetUmbracoMedia(int id) + { + // this recreates an IPublishedContent and model each time + // it is called, but at least it should NOT hit the database + // nor Lucene each time, relying on the memory cache instead + + var cacheValues = GetCacheValues(id, GetUmbracoMediaCacheValues); + + return cacheValues == null ? null : CreateFromCacheValues(cacheValues); + } + + private CacheValues GetUmbracoMediaCacheValues(int id) + { + var searchProvider = GetSearchProviderSafe(); if (searchProvider != null) { @@ -198,16 +218,16 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache } } - LogHelper.Debug( + LogHelper.Warn( "Could not retrieve media {0} from Examine index, reverting to looking up media via legacy library.GetMedia method", () => id); - var media = global::umbraco.library.GetMedia(id, true); + var media = global::umbraco.library.GetMedia(id, false); return ConvertFromXPathNodeIterator(media, id); } - internal IPublishedContent ConvertFromXPathNodeIterator(XPathNodeIterator media, int id) + internal CacheValues ConvertFromXPathNodeIterator(XPathNodeIterator media, int id) { if (media != null && media.Current != null) { @@ -216,14 +236,14 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache : ConvertFromXPathNavigator(media.Current); } - LogHelper.Debug( + LogHelper.Warn( "Could not retrieve media {0} from Examine index or from legacy library.GetMedia method", () => id); return null; } - internal IPublishedContent ConvertFromSearchResult(SearchResult searchResult) + internal CacheValues ConvertFromSearchResult(SearchResult searchResult) { //NOTE: Some fields will not be included if the config section for the internal index has been //mucked around with. It should index everything and so the index definition should simply be: @@ -253,19 +273,28 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache values.Add("level", values["__Path"].Split(',').Length.ToString()); } + // because, migration + if (values.ContainsKey("key") == false) + values["key"] = Guid.Empty.ToString(); - var content = new DictionaryPublishedContent(values, - d => d.ParentId != -1 //parent should be null if -1 - ? GetUmbracoMedia(d.ParentId) - : null, - //callback to return the children of the current node - d => GetChildrenMedia(d.Id), - GetProperty, - true); - return content.CreateModel(); + return new CacheValues + { + Values = values, + FromExamine = true + }; + + //var content = new DictionaryPublishedContent(values, + // d => d.ParentId != -1 //parent should be null if -1 + // ? GetUmbracoMedia(d.ParentId) + // : null, + // //callback to return the children of the current node + // d => GetChildrenMedia(d.Id), + // GetProperty, + // true); + //return content.CreateModel(); } - internal IPublishedContent ConvertFromXPathNavigator(XPathNavigator xpath) + internal CacheValues ConvertFromXPathNavigator(XPathNavigator xpath, bool forceNav = false) { if (xpath == null) throw new ArgumentNullException("xpath"); @@ -293,6 +322,9 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache result.Current.MoveToParent(); } } + // because, migration + if (values.ContainsKey("key") == false) + values["key"] = Guid.Empty.ToString(); //add the user props while (result.MoveNext()) { @@ -310,15 +342,21 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache } } - var content = new DictionaryPublishedContent(values, - d => d.ParentId != -1 //parent should be null if -1 - ? GetUmbracoMedia(d.ParentId) - : null, - //callback to return the children of the current node based on the xml structure already found - d => GetChildrenMedia(d.Id, xpath), - GetProperty, - false); - return content.CreateModel(); + return new CacheValues + { + Values = values, + XPath = forceNav ? xpath : null // outside of tests we do NOT want to cache the navigator! + }; + + //var content = new DictionaryPublishedContent(values, + // d => d.ParentId != -1 //parent should be null if -1 + // ? GetUmbracoMedia(d.ParentId) + // : null, + // //callback to return the children of the current node based on the xml structure already found + // d => GetChildrenMedia(d.Id, xpath), + // GetProperty, + // false); + //return content.CreateModel(); } /// @@ -395,9 +433,17 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache if (results.Any()) { - return useLuceneSort - ? results.Select(ConvertFromSearchResult) //will already be sorted by lucene - : results.Select(ConvertFromSearchResult).OrderBy(x => x.SortOrder); + // var medias = results.Select(ConvertFromSearchResult); + var medias = results.Select(x => + { + int nid; + if (int.TryParse(x["__NodeId"], out nid) == false && int.TryParse(x["NodeId"], out nid) == false) + throw new Exception("Failed to extract NodeId from search result."); + var cacheValues = GetCacheValues(nid, id => ConvertFromSearchResult(x)); + return CreateFromCacheValues(cacheValues); + }); + + return useLuceneSort ? medias : medias.OrderBy(x => x.SortOrder); } else { @@ -429,29 +475,51 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache } } - //The xpath might be the whole xpath including the current ones ancestors so we need to select the current node - var item = xpath.Select("//*[@id='" + parentId + "']"); - if (item.Current == null) - { - return Enumerable.Empty(); - } - var children = item.Current.SelectChildren(XPathNodeType.Element); + var mediaList = new List(); + + // this is so bad, really + var item = xpath.Select("//*[@id='" + parentId + "']"); + if (item.Current == null) + return Enumerable.Empty(); + var items = item.Current.SelectChildren(XPathNodeType.Element); + + // and this does not work, because... meh + //var q = "//* [@id='" + parentId + "']/* [@id]"; + //var items = xpath.Select(q); + + foreach (XPathNavigator itemm in items) + { + int id; + if (int.TryParse(itemm.GetAttribute("id", ""), out id) == false) + continue; // wtf? + var captured = itemm; + var cacheValues = GetCacheValues(id, idd => ConvertFromXPathNavigator(captured)); + mediaList.Add(CreateFromCacheValues(cacheValues)); + } + + ////The xpath might be the whole xpath including the current ones ancestors so we need to select the current node + //var item = xpath.Select("//*[@id='" + parentId + "']"); + //if (item.Current == null) + //{ + // return Enumerable.Empty(); + //} + //var children = item.Current.SelectChildren(XPathNodeType.Element); + + //foreach(XPathNavigator x in children) + //{ + // //NOTE: I'm not sure why this is here, it is from legacy code of ExamineBackedMedia, but + // // will leave it here as it must have done something! + // if (x.Name != "contents") + // { + // //make sure it's actually a node, not a property + // if (!string.IsNullOrEmpty(x.GetAttribute("path", "")) && + // !string.IsNullOrEmpty(x.GetAttribute("id", ""))) + // { + // mediaList.Add(ConvertFromXPathNavigator(x)); + // } + // } + //} - var mediaList = new List(); - foreach(XPathNavigator x in children) - { - //NOTE: I'm not sure why this is here, it is from legacy code of ExamineBackedMedia, but - // will leave it here as it must have done something! - if (x.Name != "contents") - { - //make sure it's actually a node, not a property - if (!string.IsNullOrEmpty(x.GetAttribute("path", "")) && - !string.IsNullOrEmpty(x.GetAttribute("id", ""))) - { - mediaList.Add(ConvertFromXPathNavigator(x)); - } - } - } return mediaList; } @@ -462,7 +530,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache /// This is a helper class and definitely not intended for public use, it expects that all of the values required /// to create an IPublishedContent exist in the dictionary by specific aliases. /// - internal class DictionaryPublishedContent : PublishedContentBase + internal class DictionaryPublishedContent : PublishedContentWithKeyBase { // note: I'm not sure this class fully complies with IPublishedContent rules especially // I'm not sure that _properties contains all properties including those without a value, @@ -470,27 +538,30 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache // List of properties that will appear in the XML and do not match // anything in the ContentType, so they must be ignored. - private static readonly string[] IgnoredKeys = { "version", "isDoc", "key" }; + private static readonly string[] IgnoredKeys = { "version", "isDoc" }; public DictionaryPublishedContent( IDictionary valueDictionary, - Func getParent, - Func> getChildren, + Func getParent, + Func> getChildren, Func getProperty, + XPathNavigator nav, bool fromExamine) { if (valueDictionary == null) throw new ArgumentNullException("valueDictionary"); if (getParent == null) throw new ArgumentNullException("getParent"); if (getProperty == null) throw new ArgumentNullException("getProperty"); - _getParent = getParent; - _getChildren = getChildren; + _getParent = new Lazy(() => getParent(ParentId)); + _getChildren = new Lazy>(() => getChildren(Id, nav)); _getProperty = getProperty; LoadedFromExamine = fromExamine; ValidateAndSetProperty(valueDictionary, val => _id = int.Parse(val), "id", "nodeId", "__NodeId"); //should validate the int! - ValidateAndSetProperty(valueDictionary, val => _templateId = int.Parse(val), "template", "templateId"); + ValidateAndSetProperty(valueDictionary, val => _key = Guid.Parse(val), "key"); + // wtf are we dealing with templates for medias?! + ValidateAndSetProperty(valueDictionary, val => _templateId = int.Parse(val), "template", "templateId"); ValidateAndSetProperty(valueDictionary, val => _sortOrder = int.Parse(val), "sortOrder"); ValidateAndSetProperty(valueDictionary, val => _name = val, "nodeName", "__nodeName"); ValidateAndSetProperty(valueDictionary, val => _urlName = val, "urlName"); @@ -573,8 +644,10 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache /// internal bool LoadedFromExamine { get; private set; } - private readonly Func _getParent; - private readonly Func> _getChildren; + //private readonly Func _getParent; + private readonly Lazy _getParent; + //private readonly Func> _getChildren; + private readonly Lazy> _getChildren; private readonly Func _getProperty; /// @@ -587,7 +660,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache public override IPublishedContent Parent { - get { return _getParent(this); } + get { return _getParent.Value; } } public int ParentId { get; private set; } @@ -596,6 +669,8 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache get { return _id; } } + public override Guid Key { get { return _key; } } + public override int TemplateId { get @@ -687,7 +762,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache public override IEnumerable Children { - get { return _getChildren(this); } + get { return _getChildren.Value; } } public override IPublishedProperty GetProperty(string alias) @@ -735,6 +810,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache private readonly List _keysAdded = new List(); private int _id; + private Guid _key; private int _templateId; private int _sortOrder; private string _name; @@ -765,5 +841,93 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache _keysAdded.Add(key); } } - } + + // REFACTORING + + // caching the basic atomic values - and the parent id + // but NOT caching actual parent nor children and NOT even + // the list of children ids - BUT caching the path + + internal class CacheValues + { + public IDictionary Values { get; set; } + public XPathNavigator XPath { get; set; } + public bool FromExamine { get; set; } + } + + public const string PublishedMediaCacheKey = "MediaCacheMeh."; + private const int PublishedMediaCacheTimespanSeconds = 4 * 60; // 4 mins + private static TimeSpan _publishedMediaCacheTimespan; + private static bool _publishedMediaCacheEnabled; + + private static void InitializeCacheConfig() + { + var value = ConfigurationManager.AppSettings["Umbraco.PublishedMediaCache.Seconds"]; + int seconds; + if (int.TryParse(value, out seconds) == false) + seconds = PublishedMediaCacheTimespanSeconds; + if (seconds > 0) + { + _publishedMediaCacheEnabled = true; + _publishedMediaCacheTimespan = TimeSpan.FromSeconds(seconds); + } + else + { + _publishedMediaCacheEnabled = false; + } + } + + internal IPublishedContent CreateFromCacheValues(CacheValues cacheValues) + { + var content = new DictionaryPublishedContent( + cacheValues.Values, + parentId => parentId < 0 ? null : GetUmbracoMedia(parentId), + GetChildrenMedia, + GetProperty, + cacheValues.XPath, // though, outside of tests, that should be null + cacheValues.FromExamine + ); + return content.CreateModel(); + } + + private static CacheValues GetCacheValues(int id, Func func) + { + if (_publishedMediaCacheEnabled == false) + return func(id); + + var cache = ApplicationContext.Current.ApplicationCache.RuntimeCache; + var key = PublishedMediaCacheKey + id; + return (CacheValues) cache.GetCacheItem(key, () => func(id), _publishedMediaCacheTimespan); + } + + internal static void ClearCache(int id) + { + var cache = ApplicationContext.Current.ApplicationCache.RuntimeCache; + var sid = id.ToString(); + var key = PublishedMediaCacheKey + sid; + + // we do clear a lot of things... but the cache refresher is somewhat + // convoluted and it's hard to tell what to clear exactly ;-( + + // clear the parent - NOT (why?) + //var exist = (CacheValues) cache.GetCacheItem(key); + //if (exist != null) + // cache.ClearCacheItem(PublishedMediaCacheKey + GetValuesValue(exist.Values, "parentID")); + + // clear the item + cache.ClearCacheItem(key); + + // clear all children - in case we moved and their path has changed + var fid = "/" + sid + "/"; + cache.ClearCacheObjectTypes((k, v) => + GetValuesValue(v.Values, "path", "__Path").Contains(fid)); + } + + private static string GetValuesValue(IDictionary d, params string[] keys) + { + string value = null; + var ignored = keys.Any(x => d.TryGetValue(x, out value)); + return value ?? ""; + } + } } diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlCacheFilePersister.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlCacheFilePersister.cs index 4aa52fb95f..3fa11a0dc2 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlCacheFilePersister.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlCacheFilePersister.cs @@ -17,12 +17,11 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache /// if multiple threads are performing publishing tasks that the file will be persisted in accordance with the final resulting /// xml structure since the file writes are queued. /// - internal class XmlCacheFilePersister : ILatchedBackgroundTask + internal class XmlCacheFilePersister : LatchedBackgroundTaskBase { private readonly IBackgroundTaskRunner _runner; private readonly content _content; private readonly ProfilingLogger _logger; - private readonly ManualResetEventSlim _latch = new ManualResetEventSlim(false); private readonly object _locko = new object(); private bool _released; private Timer _timer; @@ -39,7 +38,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache private const int MaxWaitMilliseconds = 30000; // save the cache after some time (ie no more than 30s of changes) // save the cache when the app goes down - public bool RunsOnShutdown { get { return true; } } + public override bool RunsOnShutdown { get { return _timer != null; } } // initialize the first instance, which is inactive (not touched yet) public XmlCacheFilePersister(IBackgroundTaskRunner runner, content content, ProfilingLogger logger) @@ -132,7 +131,8 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache } if (runNow) - Run(); + //Run(); + LogHelper.Warn("Cannot write now because we are going down, changes may be lost."); return ret; // this, by default, unless we created a new one } @@ -142,28 +142,13 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache lock (_locko) { _logger.Logger.Debug("Timer: release."); - if (_timer != null) - _timer.Dispose(); - _timer = null; _released = true; - // if running (because of shutdown) this will have no effect - // else it tells the runner it is time to run the task - _latch.Set(); + Release(); } } - public WaitHandle Latch - { - get { return _latch.WaitHandle; } - } - - public bool IsLatched - { - get { return true; } - } - - public async Task RunAsync(CancellationToken token) + public override async Task RunAsync(CancellationToken token) { lock (_locko) { @@ -188,15 +173,12 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache } } - public bool IsAsync + public override bool IsAsync { get { return true; } } - public void Dispose() - { } - - public void Run() + public override void Run() { lock (_locko) { @@ -210,5 +192,15 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache _content.SaveXmlToFile(); } } + + protected override void DisposeResources() + { + base.DisposeResources(); + + // stop the timer + if (_timer == null) return; + _timer.Change(Timeout.Infinite, Timeout.Infinite); + _timer.Dispose(); + } } } \ No newline at end of file diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedContent.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedContent.cs index 428f6bdead..d3b9ebee6a 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedContent.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedContent.cs @@ -19,7 +19,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache /// [Serializable] [XmlType(Namespace = "http://umbraco.org/webservices/")] - internal class XmlPublishedContent : PublishedContentBase + internal class XmlPublishedContent : PublishedContentWithKeyBase { /// /// Initializes a new instance of the XmlPublishedContent class with an Xml node. @@ -64,6 +64,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache private IPublishedContent _parent; private int _id; + private Guid _key; private int _template; private string _name; private string _docTypeAlias; @@ -150,6 +151,16 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache } } + public override Guid Key + { + get + { + if (_initialized == false) + Initialize(); + return _key; + } + } + public override int TemplateId { get @@ -348,6 +359,8 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache if (_xmlNode.Attributes != null) { _id = int.Parse(_xmlNode.Attributes.GetNamedItem("id").Value); + if (_xmlNode.Attributes.GetNamedItem("key") != null) // because, migration + _key = Guid.Parse(_xmlNode.Attributes.GetNamedItem("key").Value); if (_xmlNode.Attributes.GetNamedItem("template") != null) _template = int.Parse(_xmlNode.Attributes.GetNamedItem("template").Value); if (_xmlNode.Attributes.GetNamedItem("sortOrder") != null) diff --git a/src/Umbraco.Web/PublishedContentExtensions.cs b/src/Umbraco.Web/PublishedContentExtensions.cs index a735df88f9..d6cf3b3141 100644 --- a/src/Umbraco.Web/PublishedContentExtensions.cs +++ b/src/Umbraco.Web/PublishedContentExtensions.cs @@ -19,7 +19,17 @@ namespace Umbraco.Web /// Provides extension methods for IPublishedContent. /// public static class PublishedContentExtensions - { + { + #region Key + + public static Guid GetKey(this IPublishedContent content) + { + var contentWithKey = content as IPublishedContentWithKey; + return contentWithKey == null ? Guid.Empty : contentWithKey.Key; + } + + #endregion + #region Urls /// @@ -1734,7 +1744,7 @@ namespace Umbraco.Web public static IPublishedContent FirstChild(this IPublishedContent content) { - return content.Children().First(); + return content.Children().FirstOrDefault(); } public static IPublishedContent FirstChild(this IPublishedContent content, Func predicate) diff --git a/src/Umbraco.Web/PublishedContentQuery.cs b/src/Umbraco.Web/PublishedContentQuery.cs index afeddd1bf1..16f42b8197 100644 --- a/src/Umbraco.Web/PublishedContentQuery.cs +++ b/src/Umbraco.Web/PublishedContentQuery.cs @@ -211,7 +211,7 @@ namespace Umbraco.Web private IEnumerable TypedDocumentsByIds(ContextualPublishedCache cache, IEnumerable ids) { - return ids.Select(eachId => TypedDocumentById(eachId, cache)); + return ids.Select(eachId => TypedDocumentById(eachId, cache)).WhereNotNull(); } private IEnumerable TypedDocumentsByXPath(string xpath, XPathVariable[] vars, ContextualPublishedContentCache cache) diff --git a/src/Umbraco.Web/RequestLifespanMessagesFactory.cs b/src/Umbraco.Web/RequestLifespanMessagesFactory.cs new file mode 100644 index 0000000000..ec3a8b2442 --- /dev/null +++ b/src/Umbraco.Web/RequestLifespanMessagesFactory.cs @@ -0,0 +1,28 @@ +using System; +using Umbraco.Core.Events; + +namespace Umbraco.Web +{ + /// + /// Stores the instance of EventMessages in the current request so all events will share the same instance + /// + internal class RequestLifespanMessagesFactory : IEventMessagesFactory + { + private readonly IUmbracoContextAccessor _ctxAccessor; + + public RequestLifespanMessagesFactory(IUmbracoContextAccessor ctxAccessor) + { + if (ctxAccessor == null) throw new ArgumentNullException("ctxAccessor"); + _ctxAccessor = ctxAccessor; + } + + public EventMessages Get() + { + if (_ctxAccessor.Value.HttpContext.Items[typeof (RequestLifespanMessagesFactory).Name] == null) + { + _ctxAccessor.Value.HttpContext.Items[typeof(RequestLifespanMessagesFactory).Name] = new EventMessages(); + } + return (EventMessages)_ctxAccessor.Value.HttpContext.Items[typeof (RequestLifespanMessagesFactory).Name]; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Routing/DomainAndUri.cs b/src/Umbraco.Web/Routing/DomainAndUri.cs index cef8231375..6395e937df 100644 --- a/src/Umbraco.Web/Routing/DomainAndUri.cs +++ b/src/Umbraco.Web/Routing/DomainAndUri.cs @@ -32,7 +32,7 @@ namespace Umbraco.Web.Routing { var name = domain.DomainName.ToCSharpString(); throw new ArgumentException(string.Format("Failed to parse invalid domain: node id={0}, hostname=\"{1}\"." - + " Hostname should be a valid uri.", domain.RootContent.Id, name), "domain"); + + " Hostname should be a valid uri.", domain.RootContentId, name), "domain"); } } diff --git a/src/Umbraco.Web/Routing/DomainHelper.cs b/src/Umbraco.Web/Routing/DomainHelper.cs index 72360d9f22..6934b08cf2 100644 --- a/src/Umbraco.Web/Routing/DomainHelper.cs +++ b/src/Umbraco.Web/Routing/DomainHelper.cs @@ -235,7 +235,7 @@ namespace Umbraco.Web.Routing .Reverse() .Select(int.Parse) .TakeWhile(id => id != stopNodeId) - .Select(id => domains.FirstOrDefault(d => d.RootContent.Id == id && d.IsWildcard == false)) + .Select(id => domains.FirstOrDefault(d => d.RootContentId == id && d.IsWildcard == false)) .SkipWhile(domain => domain == null) .FirstOrDefault(); } @@ -256,7 +256,7 @@ namespace Umbraco.Web.Routing .Reverse() .Select(int.Parse) .TakeWhile(id => id != stopNodeId) - .Select(id => domains.FirstOrDefault(d => d.RootContent.Id == id && d.IsWildcard)) + .Select(id => domains.FirstOrDefault(d => d.RootContentId == id && d.IsWildcard)) .FirstOrDefault(domain => domain != null); } diff --git a/src/Umbraco.Web/Routing/NotFoundHandlerHelper.cs b/src/Umbraco.Web/Routing/NotFoundHandlerHelper.cs index 8ecda149fd..ec64696106 100644 --- a/src/Umbraco.Web/Routing/NotFoundHandlerHelper.cs +++ b/src/Umbraco.Web/Routing/NotFoundHandlerHelper.cs @@ -37,15 +37,13 @@ namespace Umbraco.Web.Routing // try to get the 404 based on current culture (via domain) IContentErrorPage cultureErr; - //TODO: Remove the dependency on this legacy Domain service, - // in 7.3 the real domain service should be passed in as a parameter. - if (domainService.Exists(requestServerName)) - { - var d = domainService.GetByName(requestServerName); + var d = domainService.GetByName(requestServerName); + if (d != null && d.LanguageId.HasValue) + { // test if a 404 page exists with current culture cultureErr = error404Collection - .FirstOrDefault(x => x.Culture == d.Language.IsoCode); + .FirstOrDefault(x => x.Culture == d.LanguageIsoCode); if (cultureErr != null) { diff --git a/src/Umbraco.Web/Routing/PublishedContentNotFoundHandler.cs b/src/Umbraco.Web/Routing/PublishedContentNotFoundHandler.cs index df5584ce11..d737885020 100644 --- a/src/Umbraco.Web/Routing/PublishedContentNotFoundHandler.cs +++ b/src/Umbraco.Web/Routing/PublishedContentNotFoundHandler.cs @@ -44,8 +44,6 @@ namespace Umbraco.Web.Routing response.Write("

    This page can be replaced with a custom 404. Check the documentation for \"custom 404\".

    "); response.Write("

    This page is intentionally left ugly ;-)

    "); response.Write(""); - - response.End(); } public bool IsReusable diff --git a/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs b/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs index 7b0267f227..65371ecba4 100644 --- a/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs +++ b/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs @@ -104,11 +104,14 @@ namespace Umbraco.Web.Routing { // find the document & template FindPublishedContentAndTemplate(); - - // set the culture on the thread -- again, 'cos it might have changed due to a wildcard domain - Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = _pcr.Culture; } + // handle wildcard domains + HandleWildcardDomains(); + + // set the culture on the thread -- again, 'cos it might have changed due to a finder or wildcard domain + Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = _pcr.Culture; + // trigger the Prepared event - at that point it is still possible to change about anything // even though the request might be flagged for redirection - we'll redirect _after_ the event // @@ -239,27 +242,27 @@ namespace Umbraco.Web.Routing var domainAndUri = DomainHelper.DomainForUri(Services.DomainService.GetAll(false), _pcr.Uri); // handle domain - if (domainAndUri != null) + if (domainAndUri != null && domainAndUri.UmbracoDomain.LanguageIsoCode.IsNullOrWhiteSpace() == false) { - // matching an existing domain - ProfilingLogger.Logger.Debug("{0}Matches domain=\"{1}\", rootId={2}, culture=\"{3}\"", - () => tracePrefix, - () => domainAndUri.UmbracoDomain.DomainName, - () => domainAndUri.UmbracoDomain.RootContent.Id, - () => domainAndUri.UmbracoDomain.Language.IsoCode); + // matching an existing domain + ProfilingLogger.Logger.Debug("{0}Matches domain=\"{1}\", rootId={2}, culture=\"{3}\"", + () => tracePrefix, + () => domainAndUri.UmbracoDomain.DomainName, + () => domainAndUri.UmbracoDomain.RootContentId, + () => domainAndUri.UmbracoDomain.LanguageIsoCode); _pcr.UmbracoDomain = domainAndUri.UmbracoDomain; - _pcr.DomainUri = domainAndUri.Uri; - _pcr.Culture = new CultureInfo(domainAndUri.UmbracoDomain.Language.IsoCode); + _pcr.DomainUri = domainAndUri.Uri; + _pcr.Culture = new CultureInfo(domainAndUri.UmbracoDomain.LanguageIsoCode); - // canonical? not implemented at the moment - // if (...) - // { - // _pcr.RedirectUrl = "..."; - // return true; - // } - } - else + // canonical? not implemented at the moment + // if (...) + // { + // _pcr.RedirectUrl = "..."; + // return true; + // } + } + else { // not matching any existing domain ProfilingLogger.Logger.Debug("{0}Matches no domain", () => tracePrefix); @@ -285,15 +288,15 @@ namespace Umbraco.Web.Routing var nodePath = _pcr.PublishedContent.Path; ProfilingLogger.Logger.Debug("{0}Path=\"{1}\"", () => tracePrefix, () => nodePath); - var rootNodeId = _pcr.HasDomain ? _pcr.UmbracoDomain.RootContent.Id : (int?)null; + var rootNodeId = _pcr.HasDomain ? _pcr.UmbracoDomain.RootContentId : (int?)null; var domain = DomainHelper.FindWildcardDomainInPath(Services.DomainService.GetAll(true), nodePath, rootNodeId); - if (domain != null) + if (domain != null && domain.LanguageIsoCode.IsNullOrWhiteSpace() == false) { - _pcr.Culture = new CultureInfo(domain.Language.IsoCode); - ProfilingLogger.Logger.Debug("{0}Got domain on node {1}, set culture to \"{2}\".", () => tracePrefix, - () => domain.RootContent.Id, () => _pcr.Culture.Name); - } + _pcr.Culture = new CultureInfo(domain.LanguageIsoCode); + ProfilingLogger.Logger.Debug("{0}Got domain on node {1}, set culture to \"{2}\".", () => tracePrefix, + () => domain.RootContentId, () => _pcr.Culture.Name); + } else { ProfilingLogger.Logger.Debug("{0}No match.", () => tracePrefix); @@ -380,9 +383,6 @@ namespace Umbraco.Web.Routing // handle umbracoRedirect FollowExternalRedirect(); - - // handle wildcard domains - HandleWildcardDomains(); } /// diff --git a/src/Umbraco.Web/Scheduling/BackgroundTaskRunner.cs b/src/Umbraco.Web/Scheduling/BackgroundTaskRunner.cs index dc873f82ce..afa5eb8132 100644 --- a/src/Umbraco.Web/Scheduling/BackgroundTaskRunner.cs +++ b/src/Umbraco.Web/Scheduling/BackgroundTaskRunner.cs @@ -1,48 +1,76 @@ using System; using System.Collections.Concurrent; -using System.Globalization; -using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using System.Web.Hosting; -using Umbraco.Core.Logging; using Umbraco.Core.Events; +using Umbraco.Core.Logging; namespace Umbraco.Web.Scheduling { + // exists for logging purposes + internal class BackgroundTaskRunner + { } + /// /// Manages a queue of tasks of type and runs them in the background. /// /// The type of the managed tasks. /// The task runner is web-aware and will ensure that it shuts down correctly when the AppDomain /// shuts down (ie is unloaded). - internal class BackgroundTaskRunner : IBackgroundTaskRunner + internal class BackgroundTaskRunner : BackgroundTaskRunner, IBackgroundTaskRunner where T : class, IBackgroundTask { + private readonly string _logPrefix; private readonly BackgroundTaskRunnerOptions _options; private readonly ILogger _logger; private readonly BlockingCollection _tasks = new BlockingCollection(); private readonly object _locker = new object(); - private readonly ManualResetEventSlim _completedEvent = new ManualResetEventSlim(false); - private BackgroundTaskRunnerAwaiter _awaiter; + // that event is used to stop the pump when it is alive and waiting + // on a latched task - so it waits on the latch, the cancellation token, + // and the completed event + private readonly ManualResetEventSlim _completedEvent = new ManualResetEventSlim(false); + + // in various places we are testing these vars outside a lock, so make them volatile private volatile bool _isRunning; // is running private volatile bool _isCompleted; // does not accept tasks anymore, may still be running - private Task _runningTask; + private Task _runningTask; private CancellationTokenSource _tokenSource; + private bool _terminating; // ensures we raise that event only once + private bool _terminated; // remember we've terminated + private TaskCompletionSource _terminatedSource; // awaitable source + internal event TypedEventHandler, TaskEventArgs> TaskError; internal event TypedEventHandler, TaskEventArgs> TaskStarting; internal event TypedEventHandler, TaskEventArgs> TaskCompleted; internal event TypedEventHandler, TaskEventArgs> TaskCancelled; - internal event TypedEventHandler, EventArgs> Completed; + + // triggers when the runner stops (but could start again if a task is added to it) + internal event TypedEventHandler, EventArgs> Stopped; + + // triggers when the hosting environment requests that the runner terminates + internal event TypedEventHandler, EventArgs> Terminating; + + // triggers when the runner terminates (no task can be added, no task is running) + internal event TypedEventHandler, EventArgs> Terminated; /// /// Initializes a new instance of the class. /// public BackgroundTaskRunner(ILogger logger) - : this(new BackgroundTaskRunnerOptions(), logger) + : this(typeof (T).FullName, new BackgroundTaskRunnerOptions(), logger) + { } + + /// + /// Initializes a new instance of the class. + /// + /// The name of the runner. + /// + public BackgroundTaskRunner(string name, ILogger logger) + : this(name, new BackgroundTaskRunnerOptions(), logger) { } /// @@ -51,13 +79,25 @@ namespace Umbraco.Web.Scheduling /// The set of options. /// public BackgroundTaskRunner(BackgroundTaskRunnerOptions options, ILogger logger) + : this(typeof (T).FullName, options, logger) + { } + + /// + /// Initializes a new instance of the class with a set of options. + /// + /// The name of the runner. + /// The set of options. + /// + public BackgroundTaskRunner(string name, BackgroundTaskRunnerOptions options, ILogger logger) { if (options == null) throw new ArgumentNullException("options"); if (logger == null) throw new ArgumentNullException("logger"); _options = options; + _logPrefix = "[" + name + "] "; _logger = logger; - HostingEnvironment.RegisterObject(this); + if (options.Hosted) + HostingEnvironment.RegisterObject(this); if (options.AutoStart) StartUp(); @@ -88,7 +128,7 @@ namespace Umbraco.Web.Scheduling } /// - /// Gets an awaiter used to await the running Threading.Task. + /// Gets the running task as an immutable object. /// /// There is no running task. /// @@ -96,32 +136,54 @@ namespace Umbraco.Web.Scheduling /// a background task is added to the queue. Unless the KeepAlive option is true, there /// will be no running task when the queue is empty. /// - public ThreadingTaskAwaiter CurrentThreadingTask + public ThreadingTaskImmutable CurrentThreadingTask { get { - if (_runningTask == null) - throw new InvalidOperationException("There is no current Threading.Task."); - return new ThreadingTaskAwaiter(_runningTask); + lock (_locker) + { + if (_runningTask == null) + throw new InvalidOperationException("There is no current Threading.Task."); + return new ThreadingTaskImmutable(_runningTask); + } } } /// - /// Gets an awaiter used to await the BackgroundTaskRunner running operation + /// Gets an awaitable used to await the runner running operation. /// - /// An awaiter for the BackgroundTaskRunner running operation - /// - /// This is used to wait until the background task runner is no longer running (IsRunning == false) - /// - /// So long as we have a method called GetAwaiter() that returns an instance of INotifyCompletion - /// we can await anything. In this case we are awaiting with a custom BackgroundTaskRunnerAwaiter - /// which waits for the Completed event to be raised. - /// ref: http://blogs.msdn.com/b/pfxteam/archive/2011/01/13/10115642.aspx - /// - /// - public BackgroundTaskRunnerAwaiter GetAwaiter() + /// An awaitable instance. + /// Used to wait until the runner is no longer running (IsRunning == false), + /// though the runner could be started again afterwards by adding tasks to it. + public ThreadingTaskImmutable StoppedAwaitable { - return _awaiter ?? (_awaiter = new BackgroundTaskRunnerAwaiter(this, _logger)); + get + { + lock (_locker) + { + var task = _runningTask ?? Task.FromResult(0); + return new ThreadingTaskImmutable(task); + } + } + } + + /// + /// Gets an awaitable used to await the runner. + /// + /// An awaitable instance. + /// Used to wait until the runner is terminated. + public ThreadingTaskImmutable TerminatedAwaitable + { + get + { + lock (_locker) + { + if (_terminatedSource == null && _terminated == false) + _terminatedSource = new TaskCompletionSource(); + var task = _terminatedSource == null ? Task.FromResult(0) : _terminatedSource.Task; + return new ThreadingTaskImmutable(task); + } + } } /// @@ -137,7 +199,7 @@ namespace Umbraco.Web.Scheduling throw new InvalidOperationException("The task runner has completed."); // add task - _logger.Debug>("Task added {0}", task.GetType); + _logger.Debug(_logPrefix + "Task added {0}", task.GetType); _tasks.Add(task); // start @@ -158,7 +220,7 @@ namespace Umbraco.Web.Scheduling if (_isCompleted) return false; // add task - _logger.Debug>("Task added {0}", task.GetType); + _logger.Debug(_logPrefix + "Task added {0}", task.GetType); _tasks.Add(task); // start @@ -199,7 +261,7 @@ namespace Umbraco.Web.Scheduling // create a new token source since this is a new process _tokenSource = new CancellationTokenSource(); _runningTask = PumpIBackgroundTasks(Task.Factory, _tokenSource.Token); - _logger.Debug>("Starting"); + _logger.Debug(_logPrefix + "Starting"); } /// @@ -225,7 +287,7 @@ namespace Umbraco.Web.Scheduling if (force) { // we must bring everything down, now - Thread.Sleep(100); // give time to CompleAdding() + Thread.Sleep(100); // give time to CompleteAdding() lock (_locker) { // was CompleteAdding() enough? @@ -240,7 +302,6 @@ namespace Umbraco.Web.Scheduling // tasks in the queue will be executed... if (wait == false) return; _runningTask.Wait(); // wait for whatever is running to end... - } /// @@ -260,27 +321,35 @@ namespace Umbraco.Web.Scheduling // because the pump does not lock, there's a race condition, // the pump may stop and then we still have tasks to process, // and then we must restart the pump - lock to avoid race cond + var onStopped = false; lock (_locker) { if (token.IsCancellationRequested || _tasks.Count == 0) { - _logger.Debug>("_isRunning = false"); + _logger.Debug(_logPrefix + "Stopping"); - _isRunning = false; // done if (_options.PreserveRunningTask == false) _runningTask = null; - //raise event - OnCompleted(); - return; + + // stopped + _isRunning = false; + onStopped = true; } } + if (onStopped) + { + OnEvent(Stopped, "Stopped"); + return; + } + // if _runningTask is taskSource.Task then we must keep continuing it, // not starting a new taskSource, else _runningTask would complete and // something may be waiting on it //PumpIBackgroundTasks(factory, token); // restart - // ReSharper disable once MethodSupportsCancellation // always run + // ReSharper disable MethodSupportsCancellation // always run t.ContinueWithTask(_ => PumpIBackgroundTasks(factory, token)); // restart + // ReSharper restore MethodSupportsCancellation }); Action pump = null; @@ -292,7 +361,7 @@ namespace Umbraco.Web.Scheduling if (task != null && task.IsFaulted) { var exception = task.Exception; - _logger.Error>("Task runner exception.", exception); + _logger.Error(_logPrefix + "Task runner exception.", exception); } // is it ok to run? @@ -302,6 +371,7 @@ namespace Umbraco.Web.Scheduling // the blocking MoveNext will end if token is cancelled or collection is completed T bgTask; var hasBgTask = _options.KeepAlive + // ReSharper disable once PossibleNullReferenceException ? (bgTask = enumerator.MoveNext() ? enumerator.Current : null) != null // blocking : _tasks.TryTake(out bgTask); // non-blocking @@ -322,6 +392,7 @@ namespace Umbraco.Web.Scheduling // still latched & not running on shutdown = stop here if (dbgTask.IsLatched && dbgTask.RunsOnShutdown == false) { + dbgTask.Dispose(); // will not run TaskSourceCompleted(taskSource, token); return; } @@ -347,7 +418,7 @@ namespace Umbraco.Web.Scheduling return taskSourceContinuing; } - private bool TaskSourceCanceled(TaskCompletionSource taskSource, CancellationToken token) + private static bool TaskSourceCanceled(TaskCompletionSource taskSource, CancellationToken token) { if (token.IsCancellationRequested) { @@ -357,7 +428,7 @@ namespace Umbraco.Web.Scheduling return false; } - private void TaskSourceCompleted(TaskCompletionSource taskSource, CancellationToken token) + private static void TaskSourceCompleted(TaskCompletionSource taskSource, CancellationToken token) { if (token.IsCancellationRequested) taskSource.SetCanceled(); @@ -379,7 +450,7 @@ namespace Umbraco.Web.Scheduling try { - using (bgTask) // ensure it's disposed + try { if (bgTask.IsAsync) //configure await = false since we don't care about the context, we're on a background thread. @@ -387,6 +458,12 @@ namespace Umbraco.Web.Scheduling else bgTask.Run(); } + finally // ensure we disposed - unless latched (again) + { + var lbgTask = bgTask as ILatchedBackgroundTask; + if (lbgTask == null || lbgTask.IsLatched == false) + bgTask.Dispose(); + } } catch (Exception e) { @@ -398,85 +475,55 @@ namespace Umbraco.Web.Scheduling } catch (Exception ex) { - _logger.Error>("Task has failed.", ex); + _logger.Error(_logPrefix + "Task has failed", ex); } } #region Events + private void OnEvent(TypedEventHandler, EventArgs> handler, string name) + { + if (handler == null) return; + OnEvent(handler, name, EventArgs.Empty); + } + + private void OnEvent(TypedEventHandler, TArgs> handler, string name, TArgs e) + { + if (handler == null) return; + + try + { + handler(this, e); + } + catch (Exception ex) + { + _logger.Error(_logPrefix + name + " exception occurred", ex); + } + } + protected virtual void OnTaskError(TaskEventArgs e) { - var handler = TaskError; - if (handler != null) handler(this, e); + OnEvent(TaskError, "TaskError", e); } protected virtual void OnTaskStarting(TaskEventArgs e) { - var handler = TaskStarting; - if (handler != null) - { - try - { - handler(this, e); - } - catch (Exception ex) - { - _logger.Error>("TaskStarting exception occurred", ex); - } - } + OnEvent(TaskStarting, "TaskStarting", e); } protected virtual void OnTaskCompleted(TaskEventArgs e) { - var handler = TaskCompleted; - if (handler != null) - { - try - { - handler(this, e); - } - catch (Exception ex) - { - _logger.Error>("TaskCompleted exception occurred", ex); - } - } + OnEvent(TaskCompleted, "TaskCompleted", e); } protected virtual void OnTaskCancelled(TaskEventArgs e) { - var handler = TaskCancelled; - if (handler != null) - { - try - { - handler(this, e); - } - catch (Exception ex) - { - _logger.Error>("TaskCancelled exception occurred", ex); - } - } + OnEvent(TaskCancelled, "TaskCancelled", e); //dispose it e.Task.Dispose(); } - protected virtual void OnCompleted() - { - var handler = Completed; - if (handler != null) - { - try - { - handler(this, EventArgs.Empty); - } - catch (Exception ex) - { - _logger.Error>("OnCompleted exception occurred", ex); - } - } - } - #endregion #region IDisposable @@ -486,7 +533,7 @@ namespace Umbraco.Web.Scheduling ~BackgroundTaskRunner() { - this.Dispose(false); + Dispose(false); } public void Dispose() @@ -497,7 +544,7 @@ namespace Umbraco.Web.Scheduling protected virtual void Dispose(bool disposing) { - if (this.IsDisposed || disposing == false) + if (IsDisposed || disposing == false) return; lock (_disposalLocker) @@ -529,28 +576,41 @@ namespace Umbraco.Web.Scheduling /// public void Stop(bool immediate) { + // the first time the hosting environment requests that the runner terminates, + // raise the Terminating event - that could be used to prevent any process that + // would expect the runner to be available from starting. + var onTerminating = false; + lock (_locker) + { + if (_terminating == false) + { + _terminating = true; + _logger.Info(_logPrefix + "Terminating" + (immediate ? " (immediate)" : "")); + onTerminating = true; + } + } + + if (onTerminating) + OnEvent(Terminating, "Terminating"); + if (immediate == false) { // The Stop method is first called with the immediate parameter set to false. The object can either complete // processing, call the UnregisterObject method, and then return or it can return immediately and complete // processing asynchronously before calling the UnregisterObject method. - _logger.Debug>("Shutting down, waiting for tasks to complete."); + _logger.Info(_logPrefix + "Waiting for tasks to complete"); Shutdown(false, false); // do not accept any more tasks, flush the queue, do not wait + // raise the completed event only after the running task has completed + // and there's no more task running + lock (_locker) { if (_runningTask != null) - _runningTask.ContinueWith(_ => - { - HostingEnvironment.UnregisterObject(this); - _logger.Info>("Down, tasks completed."); - }); + _runningTask.ContinueWith(_ => Terminate(false)); else - { - HostingEnvironment.UnregisterObject(this); - _logger.Info>("Down, tasks completed."); - } + Terminate(false); } } else @@ -560,13 +620,31 @@ namespace Umbraco.Web.Scheduling // immediate parameter is true, the registered object must call the UnregisterObject method before returning; // otherwise, its registration will be removed by the application manager. - _logger.Info>("Shutting down immediately."); + _logger.Info(_logPrefix + "Cancelling tasks"); Shutdown(true, true); // cancel all tasks, wait for the current one to end - HostingEnvironment.UnregisterObject(this); - _logger.Info>("Down."); + Terminate(true); } } - + private void Terminate(bool immediate) + { + // signal the environment we have terminated + // log + // raise the Terminated event + // complete the awaitable completion source, if any + + HostingEnvironment.UnregisterObject(this); + _logger.Info(_logPrefix + "Tasks " + (immediate ? "cancelled" : "completed") + ", terminated"); + OnEvent(Terminated, "Terminated"); + + TaskCompletionSource terminatedSource; + lock (_locker) + { + _terminated = true; + terminatedSource = _terminatedSource; + } + if (terminatedSource != null) + terminatedSource.SetResult(0); + } } } diff --git a/src/Umbraco.Web/Scheduling/BackgroundTaskRunnerAwaiter.cs b/src/Umbraco.Web/Scheduling/BackgroundTaskRunnerAwaiter.cs deleted file mode 100644 index f57d01a9ef..0000000000 --- a/src/Umbraco.Web/Scheduling/BackgroundTaskRunnerAwaiter.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System; -using System.Runtime.CompilerServices; -using System.Threading.Tasks; -using ClientDependency.Core.Logging; -using Umbraco.Core.Logging; -using ILogger = Umbraco.Core.Logging.ILogger; - -namespace Umbraco.Web.Scheduling -{ - /// - /// Custom awaiter used to await when the BackgroundTaskRunner is completed (IsRunning == false) - /// - /// - /// - /// This custom awaiter simply uses a TaskCompletionSource to set the result when the Completed event of the - /// BackgroundTaskRunner executes. - /// A custom awaiter requires implementing INotifyCompletion as well as IsCompleted, OnCompleted and GetResult - /// see: http://blogs.msdn.com/b/pfxteam/archive/2011/01/13/10115642.aspx - /// - internal class BackgroundTaskRunnerAwaiter : INotifyCompletion where T : class, IBackgroundTask - { - private readonly BackgroundTaskRunner _runner; - private readonly ILogger _logger; - private readonly TaskCompletionSource _tcs; - private readonly TaskAwaiter _awaiter; - - public BackgroundTaskRunnerAwaiter(BackgroundTaskRunner runner, ILogger logger) - { - if (runner == null) throw new ArgumentNullException("runner"); - if (logger == null) throw new ArgumentNullException("logger"); - _runner = runner; - _logger = logger; - - _tcs = new TaskCompletionSource(); - - _awaiter = _tcs.Task.GetAwaiter(); - - if (_runner.IsRunning) - { - _runner.Completed += (s, e) => - { - _logger.Debug>("Setting result"); - - _tcs.SetResult(0); - }; - } - else - { - //not running, just set the result - _tcs.SetResult(0); - } - - } - - public BackgroundTaskRunnerAwaiter GetAwaiter() - { - return this; - } - - /// - /// This is completed when the runner is finished running - /// - public bool IsCompleted - { - get - { - _logger.Debug>("IsCompleted :: " + _tcs.Task.IsCompleted + ", " + (_runner.IsRunning == false)); - - //Need to check if the task is completed because it might already be done on the ctor and the runner never runs - return _tcs.Task.IsCompleted || _runner.IsRunning == false; - } - } - - public void OnCompleted(Action continuation) - { - _awaiter.OnCompleted(continuation); - } - - public void GetResult() - { - _awaiter.GetResult(); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Scheduling/BackgroundTaskRunnerOptions.cs b/src/Umbraco.Web/Scheduling/BackgroundTaskRunnerOptions.cs index 4688ff37d6..55df42d3b7 100644 --- a/src/Umbraco.Web/Scheduling/BackgroundTaskRunnerOptions.cs +++ b/src/Umbraco.Web/Scheduling/BackgroundTaskRunnerOptions.cs @@ -15,6 +15,8 @@ namespace Umbraco.Web.Scheduling LongRunning = false; KeepAlive = false; AutoStart = false; + PreserveRunningTask = false; + Hosted = true; } /// @@ -36,9 +38,16 @@ namespace Umbraco.Web.Scheduling public bool AutoStart { get; set; } /// - /// Gets or setes a value indicating whether the running task should be preserved + /// Gets or sets a value indicating whether the running task should be preserved /// once completed, or reset to null. For unit tests. /// public bool PreserveRunningTask { get; set; } + + /// + /// Gets or sets a value indicating whether the runner should register with (and be + /// stopped by) the hosting. Otherwise, something else should take care of stopping + /// the runner. True by default. + /// + public bool Hosted { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Scheduling/DelayedRecurringTaskBase.cs b/src/Umbraco.Web/Scheduling/DelayedRecurringTaskBase.cs deleted file mode 100644 index f7cec0079b..0000000000 --- a/src/Umbraco.Web/Scheduling/DelayedRecurringTaskBase.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace Umbraco.Web.Scheduling -{ - /// - /// Provides a base class for recurring background tasks. - /// - /// The type of the managed tasks. - internal abstract class DelayedRecurringTaskBase : RecurringTaskBase, ILatchedBackgroundTask - where T : class, IBackgroundTask - { - private readonly ManualResetEventSlim _latch; - private Timer _timer; - - protected DelayedRecurringTaskBase(IBackgroundTaskRunner runner, int delayMilliseconds, int periodMilliseconds) - : base(runner, periodMilliseconds) - { - if (delayMilliseconds > 0) - { - _latch = new ManualResetEventSlim(false); - _timer = new Timer(_ => - { - _timer.Dispose(); - _timer = null; - _latch.Set(); - }); - _timer.Change(delayMilliseconds, 0); - } - } - - protected DelayedRecurringTaskBase(DelayedRecurringTaskBase source) - : base(source) - { - // no latch on recurring instances - _latch = null; - } - - public override void Run() - { - if (_latch != null) - _latch.Dispose(); - base.Run(); - } - - public override async Task RunAsync(CancellationToken token) - { - if (_latch != null) - _latch.Dispose(); - await base.RunAsync(token); - } - - public WaitHandle Latch - { - get - { - if (_latch == null) - throw new InvalidOperationException("The task is not latched."); - return _latch.WaitHandle; - } - } - - public bool IsLatched - { - get { return _latch != null; } - } - - public virtual bool RunsOnShutdown - { - get { return true; } - } - } -} diff --git a/src/Umbraco.Web/Scheduling/ILatchedBackgroundTask.cs b/src/Umbraco.Web/Scheduling/ILatchedBackgroundTask.cs index 0ad4d42bdf..79379cb966 100644 --- a/src/Umbraco.Web/Scheduling/ILatchedBackgroundTask.cs +++ b/src/Umbraco.Web/Scheduling/ILatchedBackgroundTask.cs @@ -21,6 +21,7 @@ namespace Umbraco.Web.Scheduling /// /// Gets a value indicating whether the task is latched. /// + /// Should return false as soon as the condition is met. bool IsLatched { get; } /// diff --git a/src/Umbraco.Web/Scheduling/KeepAlive.cs b/src/Umbraco.Web/Scheduling/KeepAlive.cs index c1b43b57c6..0557e56e57 100644 --- a/src/Umbraco.Web/Scheduling/KeepAlive.cs +++ b/src/Umbraco.Web/Scheduling/KeepAlive.cs @@ -1,47 +1,78 @@ using System; using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; using Umbraco.Core; -using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; -using Umbraco.Core.Sync; namespace Umbraco.Web.Scheduling { - internal class KeepAlive + internal class KeepAlive : RecurringTaskBase { - public static void Start(ApplicationContext appContext, IUmbracoSettingsSection settings) + private readonly ApplicationContext _appContext; + + public KeepAlive(IBackgroundTaskRunner runner, int delayMilliseconds, int periodMilliseconds, + ApplicationContext appContext) + : base(runner, delayMilliseconds, periodMilliseconds) { + _appContext = appContext; + } + + public override bool PerformRun() + { + throw new NotImplementedException(); + } + + public override async Task PerformRunAsync(CancellationToken token) + { + if (_appContext == null) return true; // repeat... + + // ensure we do not run if not main domain, but do NOT lock it + if (_appContext.MainDom.IsMainDom == false) + { + LogHelper.Debug("Does not run if not MainDom."); + return false; // do NOT repeat, going down + } + using (DisposableTimer.DebugDuration(() => "Keep alive executing", () => "Keep alive complete")) - { - var umbracoBaseUrl = ServerEnvironmentHelper.GetCurrentServerUmbracoBaseUrl( - appContext, - settings); + { + string umbracoAppUrl = null; - if (string.IsNullOrWhiteSpace(umbracoBaseUrl)) + try { - LogHelper.Warn("No url for service (yet), skip."); + umbracoAppUrl = _appContext.UmbracoApplicationUrl; + if (umbracoAppUrl.IsNullOrWhiteSpace()) + { + LogHelper.Warn("No url for service (yet), skip."); + return true; // repeat + } + + var url = umbracoAppUrl + "/ping.aspx"; + using (var wc = new HttpClient()) + { + var request = new HttpRequestMessage(HttpMethod.Get, url); + var result = await wc.SendAsync(request, token); + } } - else + catch (Exception e) { - var url = string.Format("{0}ping.aspx", umbracoBaseUrl.EnsureEndsWith('/')); - - try - { - using (var wc = new WebClient()) - { - wc.DownloadString(url); - } - } - catch (Exception ee) - { - LogHelper.Error( - string.Format("Error in ping. The base url used in the request was: {0}, see http://our.umbraco.org/documentation/Using-Umbraco/Config-files/umbracoSettings/#ScheduledTasks documentation for details on setting a baseUrl if this is in error", umbracoBaseUrl) - , ee); - } + LogHelper.Error(string.Format("Failed (at \"{0}\").", umbracoAppUrl), e); } } - + + return true; // repeat + } + + public override bool IsAsync + { + get { return true; } + } + + public override bool RunsOnShutdown + { + get { return false; } } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Scheduling/LatchedBackgroundTaskBase.cs b/src/Umbraco.Web/Scheduling/LatchedBackgroundTaskBase.cs new file mode 100644 index 0000000000..3315fa7c34 --- /dev/null +++ b/src/Umbraco.Web/Scheduling/LatchedBackgroundTaskBase.cs @@ -0,0 +1,63 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Umbraco.Core; + +namespace Umbraco.Web.Scheduling +{ + internal abstract class LatchedBackgroundTaskBase : DisposableObject, ILatchedBackgroundTask + { + private readonly ManualResetEventSlim _latch; + + protected LatchedBackgroundTaskBase() + { + _latch = new ManualResetEventSlim(false); + } + + /// + /// Implements IBackgroundTask.Run(). + /// + public abstract void Run(); + + /// + /// Implements IBackgroundTask.RunAsync(). + /// + public abstract Task RunAsync(CancellationToken token); + + /// + /// Indicates whether the background task can run asynchronously. + /// + public abstract bool IsAsync { get; } + + public WaitHandle Latch + { + get { return _latch.WaitHandle; } + } + + public bool IsLatched + { + get { return _latch.IsSet == false; } + } + + protected void Release() + { + _latch.Set(); + } + + protected void Reset() + { + _latch.Reset(); + } + + public abstract bool RunsOnShutdown { get; } + + // the task is going to be disposed after execution, + // unless it is latched again, thus indicating it wants to + // remain active + + protected override void DisposeResources() + { + _latch.Dispose(); + } + } +} diff --git a/src/Umbraco.Web/Scheduling/LogScrubber.cs b/src/Umbraco.Web/Scheduling/LogScrubber.cs index d6c096bf74..b3a5f303e3 100644 --- a/src/Umbraco.Web/Scheduling/LogScrubber.cs +++ b/src/Umbraco.Web/Scheduling/LogScrubber.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; using System.Threading.Tasks; using System.Web; using System.Web.Caching; @@ -6,15 +7,16 @@ using umbraco.BusinessLogic; using Umbraco.Core; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; +using Umbraco.Core.Sync; namespace Umbraco.Web.Scheduling { - internal class LogScrubber : DelayedRecurringTaskBase + internal class LogScrubber : RecurringTaskBase { private readonly ApplicationContext _appContext; private readonly IUmbracoSettingsSection _settings; - public LogScrubber(IBackgroundTaskRunner runner, int delayMilliseconds, int periodMilliseconds, + public LogScrubber(IBackgroundTaskRunner runner, int delayMilliseconds, int periodMilliseconds, ApplicationContext appContext, IUmbracoSettingsSection settings) : base(runner, delayMilliseconds, periodMilliseconds) { @@ -22,20 +24,8 @@ namespace Umbraco.Web.Scheduling _settings = settings; } - public LogScrubber(LogScrubber source) - : base(source) - { - _appContext = source._appContext; - _settings = source._settings; - } - - protected override LogScrubber GetRecurring() - { - return new LogScrubber(this); - } - // maximum age, in minutes - private int GetLogScrubbingMaximumAge(IUmbracoSettingsSection settings) + private static int GetLogScrubbingMaximumAge(IUmbracoSettingsSection settings) { var maximumAge = 24 * 60; // 24 hours, in minutes try @@ -45,7 +35,7 @@ namespace Umbraco.Web.Scheduling } catch (Exception e) { - LogHelper.Error("Unable to locate a log scrubbing maximum age. Defaulting to 24 hours.", e); + LogHelper.Error("Unable to locate a log scrubbing maximum age. Defaulting to 24 hours.", e); } return maximumAge; @@ -66,15 +56,36 @@ namespace Umbraco.Web.Scheduling return interval; } - public override void PerformRun() + public override bool PerformRun() { + if (_appContext == null) return true; // repeat... + + switch (_appContext.GetCurrentServerRole()) + { + case ServerRole.Slave: + LogHelper.Debug("Does not run on slave servers."); + return true; // DO repeat, server role can change + case ServerRole.Unknown: + LogHelper.Debug("Does not run on servers with unknown role."); + return true; // DO repeat, server role can change + } + + // ensure we do not run if not main domain, but do NOT lock it + if (_appContext.MainDom.IsMainDom == false) + { + LogHelper.Debug("Does not run if not MainDom."); + return false; // do NOT repeat, going down + } + using (DisposableTimer.DebugDuration("Log scrubbing executing", "Log scrubbing complete")) { Log.CleanLogs(GetLogScrubbingMaximumAge(_settings)); - } + } + + return true; // repeat } - public override Task PerformRunAsync() + public override Task PerformRunAsync(CancellationToken token) { throw new NotImplementedException(); } diff --git a/src/Umbraco.Web/Scheduling/RecurringTaskBase.cs b/src/Umbraco.Web/Scheduling/RecurringTaskBase.cs index d710a70e03..567f85f1f5 100644 --- a/src/Umbraco.Web/Scheduling/RecurringTaskBase.cs +++ b/src/Umbraco.Web/Scheduling/RecurringTaskBase.cs @@ -6,57 +6,51 @@ namespace Umbraco.Web.Scheduling /// /// Provides a base class for recurring background tasks. /// - /// The type of the managed tasks. - internal abstract class RecurringTaskBase : IBackgroundTask - where T : class, IBackgroundTask + internal abstract class RecurringTaskBase : LatchedBackgroundTaskBase { - private readonly IBackgroundTaskRunner _runner; + private readonly IBackgroundTaskRunner _runner; private readonly int _periodMilliseconds; - private Timer _timer; - private T _recurrent; + private readonly Timer _timer; + private bool _disposed; /// - /// Initializes a new instance of the class with a tasks runner and a period. + /// Initializes a new instance of the class. /// /// The task runner. + /// The delay. /// The period. /// The task will repeat itself periodically. Use this constructor to create a new task. - protected RecurringTaskBase(IBackgroundTaskRunner runner, int periodMilliseconds) + protected RecurringTaskBase(IBackgroundTaskRunner runner, int delayMilliseconds, int periodMilliseconds) { _runner = runner; _periodMilliseconds = periodMilliseconds; - } - /// - /// Initializes a new instance of the class with a source task. - /// - /// The source task. - /// Use this constructor to create a new task from a source task in GetRecurring. - protected RecurringTaskBase(RecurringTaskBase source) - { - _runner = source._runner; - _timer = source._timer; - _periodMilliseconds = source._periodMilliseconds; + // note + // must use the single-parameter constructor on Timer to avoid it from being GC'd + // read http://stackoverflow.com/questions/4962172/why-does-a-system-timers-timer-survive-gc-but-not-system-threading-timer + + _timer = new Timer(_ => Release()); + _timer.Change(delayMilliseconds, 0); } /// /// Implements IBackgroundTask.Run(). /// /// Classes inheriting from RecurringTaskBase must implement PerformRun. - public virtual void Run() + public override void Run() { - PerformRun(); - Repeat(); + var shouldRepeat = PerformRun(); + if (shouldRepeat) Repeat(); } /// /// Implements IBackgroundTask.RunAsync(). /// /// Classes inheriting from RecurringTaskBase must implement PerformRun. - public virtual async Task RunAsync(CancellationToken token) + public override async Task RunAsync(CancellationToken token) { - await PerformRunAsync(); - Repeat(); + var shouldRepeat = await PerformRunAsync(token); + if (shouldRepeat) Repeat(); } private void Repeat() @@ -64,52 +58,39 @@ namespace Umbraco.Web.Scheduling // again? if (_runner.IsCompleted) return; // fail fast - if (_periodMilliseconds == 0) return; + if (_periodMilliseconds == 0) return; // safe - _recurrent = GetRecurring(); - if (_recurrent == null) - { - _timer.Dispose(); - _timer = null; - return; // done - } + Reset(); // re-latch - // note - // must use the single-parameter constructor on Timer to avoid it from being GC'd - // read http://stackoverflow.com/questions/4962172/why-does-a-system-timers-timer-survive-gc-but-not-system-threading-timer - - _timer = _timer ?? new Timer(_ => _runner.TryAdd(_recurrent)); - _timer.Change(_periodMilliseconds, 0); + // try to add again (may fail if runner has completed) + // if added, re-start the timer, else kill it + if (_runner.TryAdd(this)) + _timer.Change(_periodMilliseconds, 0); + else + Dispose(true); } - /// - /// Indicates whether the background task can run asynchronously. - /// - public abstract bool IsAsync { get; } - /// /// Runs the background task. /// - public abstract void PerformRun(); + /// A value indicating whether to repeat the task. + public abstract bool PerformRun(); /// /// Runs the task asynchronously. /// - /// A instance representing the execution of the background task. - public abstract Task PerformRunAsync(); + /// A cancellation token. + /// A instance representing the execution of the background task, + /// and returning a value indicating whether to repeat the task. + public abstract Task PerformRunAsync(CancellationToken token); - /// - /// Gets a new occurence of the recurring task. - /// - /// A new task instance to be queued, or null to terminate the recurring task. - /// The new task instance must be created via the RecurringTaskBase(RecurringTaskBase{T} source) constructor, - /// where source is the current task, eg: return new MyTask(this); - protected abstract T GetRecurring(); + protected override void DisposeResources() + { + base.DisposeResources(); - /// - /// Dispose the task. - /// - public virtual void Dispose() - { } + // stop the timer + _timer.Change(Timeout.Infinite, Timeout.Infinite); + _timer.Dispose(); + } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs b/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs index 9db21fba8a..0f7e3f0183 100644 --- a/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs +++ b/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs @@ -1,25 +1,21 @@ using System; -using System.Diagnostics; -using System.Net; -using System.Text; +using System.Net.Http; +using System.Threading; using System.Threading.Tasks; using Umbraco.Core; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; -using Umbraco.Core.Publishing; using Umbraco.Core.Sync; using Umbraco.Web.Mvc; namespace Umbraco.Web.Scheduling { - internal class ScheduledPublishing : DelayedRecurringTaskBase + internal class ScheduledPublishing : RecurringTaskBase { private readonly ApplicationContext _appContext; private readonly IUmbracoSettingsSection _settings; - private static bool _isPublishingRunning; - - public ScheduledPublishing(IBackgroundTaskRunner runner, int delayMilliseconds, int periodMilliseconds, + public ScheduledPublishing(IBackgroundTaskRunner runner, int delayMilliseconds, int periodMilliseconds, ApplicationContext appContext, IUmbracoSettingsSection settings) : base(runner, delayMilliseconds, periodMilliseconds) { @@ -27,75 +23,70 @@ namespace Umbraco.Web.Scheduling _settings = settings; } - private ScheduledPublishing(ScheduledPublishing source) - : base(source) - { - _appContext = source._appContext; - _settings = source._settings; - } - - protected override ScheduledPublishing GetRecurring() - { - return new ScheduledPublishing(this); - } - - public override void PerformRun() - { - if (_appContext == null) return; - if (ServerEnvironmentHelper.GetStatus(_settings) == CurrentServerEnvironmentStatus.Slave) - { - LogHelper.Debug("Does not run on slave servers."); - return; - } - - using (DisposableTimer.DebugDuration(() => "Scheduled publishing executing", () => "Scheduled publishing complete")) - { - if (_isPublishingRunning) return; - - _isPublishingRunning = true; - - var umbracoBaseUrl = ServerEnvironmentHelper.GetCurrentServerUmbracoBaseUrl(_appContext, _settings); - - try - { - - if (string.IsNullOrWhiteSpace(umbracoBaseUrl)) - { - LogHelper.Warn("No url for service (yet), skip."); - } - else - { - var url = string.Format("{0}RestServices/ScheduledPublish/Index", umbracoBaseUrl.EnsureEndsWith('/')); - using (var wc = new WebClient()) - { - //pass custom the authorization header - wc.Headers.Set("Authorization", AdminTokenAuthorizeAttribute.GetAuthHeaderTokenVal(_appContext)); - - var result = wc.UploadString(url, ""); - } - } - } - catch (Exception ee) - { - LogHelper.Error( - string.Format("An error occurred with the scheduled publishing. The base url used in the request was: {0}, see http://our.umbraco.org/documentation/Using-Umbraco/Config-files/umbracoSettings/#ScheduledTasks documentation for details on setting a baseUrl if this is in error", umbracoBaseUrl) - , ee); - } - finally - { - _isPublishingRunning = false; - } - } - } - - public override Task PerformRunAsync() + public override bool PerformRun() { throw new NotImplementedException(); } + public override async Task PerformRunAsync(CancellationToken token) + { + if (_appContext == null) return true; // repeat... + + switch (_appContext.GetCurrentServerRole()) + { + case ServerRole.Slave: + LogHelper.Debug("Does not run on slave servers."); + return true; // DO repeat, server role can change + case ServerRole.Unknown: + LogHelper.Debug("Does not run on servers with unknown role."); + return true; // DO repeat, server role can change + } + + // ensure we do not run if not main domain, but do NOT lock it + if (_appContext.MainDom.IsMainDom == false) + { + LogHelper.Debug("Does not run if not MainDom."); + return false; // do NOT repeat, going down + } + + using (DisposableTimer.DebugDuration(() => "Scheduled publishing executing", () => "Scheduled publishing complete")) + { + string umbracoAppUrl = null; + + try + { + umbracoAppUrl = _appContext.UmbracoApplicationUrl; + if (umbracoAppUrl.IsNullOrWhiteSpace()) + { + LogHelper.Warn("No url for service (yet), skip."); + return true; // repeat + } + + var url = umbracoAppUrl + "/RestServices/ScheduledPublish/Index"; + using (var wc = new HttpClient()) + { + var request = new HttpRequestMessage(HttpMethod.Post, url) + { + Content = new StringContent(string.Empty) + }; + //pass custom the authorization header + request.Headers.Authorization = AdminTokenAuthorizeAttribute.GetAuthenticationHeaderValue(_appContext); + + var result = await wc.SendAsync(request, token); + } + } + catch (Exception e) + { + LogHelper.Error(string.Format("Failed (at \"{0}\").", umbracoAppUrl), e); + } + } + + return true; // repeat + } + public override bool IsAsync { - get { return false; } + get { return true; } } public override bool RunsOnShutdown diff --git a/src/Umbraco.Web/Scheduling/ScheduledTasks.cs b/src/Umbraco.Web/Scheduling/ScheduledTasks.cs index cba3cb4fc8..3f0a9f2a97 100644 --- a/src/Umbraco.Web/Scheduling/ScheduledTasks.cs +++ b/src/Umbraco.Web/Scheduling/ScheduledTasks.cs @@ -1,15 +1,13 @@ using System; using System.Collections; -using System.Linq; using System.Net; +using System.Net.Http; +using System.Threading; using System.Threading.Tasks; -using System.Xml; -using Umbraco.Core.Configuration; +using Umbraco.Core; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; -using Umbraco.Core.Publishing; using Umbraco.Core.Sync; -using Umbraco.Core; namespace Umbraco.Web.Scheduling { @@ -17,14 +15,13 @@ namespace Umbraco.Web.Scheduling // would need to be a publicly available task (URL) which isn't really very good :( // We should really be using the AdminTokenAuthorizeAttribute for this stuff - internal class ScheduledTasks : DelayedRecurringTaskBase + internal class ScheduledTasks : RecurringTaskBase { private readonly ApplicationContext _appContext; private readonly IUmbracoSettingsSection _settings; private static readonly Hashtable ScheduledTaskTimes = new Hashtable(); - private static bool _isPublishingRunning = false; - public ScheduledTasks(IBackgroundTaskRunner runner, int delayMilliseconds, int periodMilliseconds, + public ScheduledTasks(IBackgroundTaskRunner runner, int delayMilliseconds, int periodMilliseconds, ApplicationContext appContext, IUmbracoSettingsSection settings) : base(runner, delayMilliseconds, periodMilliseconds) { @@ -32,30 +29,19 @@ namespace Umbraco.Web.Scheduling _settings = settings; } - public ScheduledTasks(ScheduledTasks source) - : base(source) - { - _appContext = source._appContext; - _settings = source._settings; - } - - protected override ScheduledTasks GetRecurring() - { - return new ScheduledTasks(this); - } - - private void ProcessTasks() + private async Task ProcessTasksAsync(CancellationToken token) { var scheduledTasks = _settings.ScheduledTasks.Tasks; foreach (var t in scheduledTasks) { var runTask = false; - if (!ScheduledTaskTimes.ContainsKey(t.Alias)) + if (ScheduledTaskTimes.ContainsKey(t.Alias) == false) { runTask = true; ScheduledTaskTimes.Add(t.Alias, DateTime.Now); } - /// Add 1 second to timespan to compensate for differencies in timer + + // Add 1 second to timespan to compensate for differencies in timer else if ( new TimeSpan( DateTime.Now.Ticks - ((DateTime)ScheduledTaskTimes[t.Alias]).Ticks).TotalSeconds + 1 >= t.Interval) @@ -66,69 +52,79 @@ namespace Umbraco.Web.Scheduling if (runTask) { - bool taskResult = GetTaskByHttp(t.Url); + var taskResult = await GetTaskByHttpAync(t.Url, token); if (t.Log) LogHelper.Info(string.Format("{0} has been called with response: {1}", t.Alias, taskResult)); } } } - private bool GetTaskByHttp(string url) + private async Task GetTaskByHttpAync(string url, CancellationToken token) { - var myHttpWebRequest = (HttpWebRequest)WebRequest.Create(url); - - try + using (var wc = new HttpClient()) { - using (var response = (HttpWebResponse)myHttpWebRequest.GetResponse()) - { - return response.StatusCode == HttpStatusCode.OK; - } - } - catch (Exception ex) - { - LogHelper.Error("An error occurred calling web task for url: " + url, ex); - } + var request = new HttpRequestMessage(HttpMethod.Get, url); - return false; + //TODO: pass custom the authorization header, currently these aren't really secured! + //request.Headers.Authorization = AdminTokenAuthorizeAttribute.GetAuthenticationHeaderValue(_appContext); + + try + { + var result = await wc.SendAsync(request, token).ConfigureAwait(false); // ConfigureAwait(false) is recommended? http://blog.stephencleary.com/2012/07/dont-block-on-async-code.html + return result.StatusCode == HttpStatusCode.OK; + } + catch (Exception ex) + { + LogHelper.Error("An error occurred calling web task for url: " + url, ex); + } + return false; + } } - public override void PerformRun() + public override bool PerformRun() { - if (ServerEnvironmentHelper.GetStatus(_settings) == CurrentServerEnvironmentStatus.Slave) + throw new NotImplementedException(); + } + + public override async Task PerformRunAsync(CancellationToken token) + { + if (_appContext == null) return true; // repeat... + + switch (_appContext.GetCurrentServerRole()) { - LogHelper.Debug("Does not run on slave servers."); - return; + case ServerRole.Slave: + LogHelper.Debug("Does not run on slave servers."); + return true; // DO repeat, server role can change + case ServerRole.Unknown: + LogHelper.Debug("Does not run on servers with unknown role."); + return true; // DO repeat, server role can change + } + + // ensure we do not run if not main domain, but do NOT lock it + if (_appContext.MainDom.IsMainDom == false) + { + LogHelper.Debug("Does not run if not MainDom."); + return false; // do NOT repeat, going down } using (DisposableTimer.DebugDuration(() => "Scheduled tasks executing", () => "Scheduled tasks complete")) { - if (_isPublishingRunning) return; - - _isPublishingRunning = true; - try { - ProcessTasks(); + await ProcessTasksAsync(token); } catch (Exception ee) { LogHelper.Error("Error executing scheduled task", ee); } - finally - { - _isPublishingRunning = false; - } } - } - public override Task PerformRunAsync() - { - throw new NotImplementedException(); + return true; // repeat } public override bool IsAsync { - get { return false; } + get { return true; } } public override bool RunsOnShutdown diff --git a/src/Umbraco.Web/Scheduling/Scheduler.cs b/src/Umbraco.Web/Scheduling/Scheduler.cs index 55daf3cab2..c93347b030 100644 --- a/src/Umbraco.Web/Scheduling/Scheduler.cs +++ b/src/Umbraco.Web/Scheduling/Scheduler.cs @@ -1,11 +1,7 @@ -using System; -using System.Threading; -using System.Web; +using System.Web; using Umbraco.Core; using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; -using Umbraco.Core.Sync; namespace Umbraco.Web.Scheduling { @@ -18,7 +14,7 @@ namespace Umbraco.Web.Scheduling /// internal sealed class Scheduler : ApplicationEventHandler { - private static Timer _pingTimer; + private static BackgroundTaskRunner _keepAliveRunner; private static BackgroundTaskRunner _publishingRunner; private static BackgroundTaskRunner _tasksRunner; private static BackgroundTaskRunner _scrubberRunner; @@ -53,30 +49,24 @@ namespace Umbraco.Web.Scheduling LogHelper.Debug(() => "Initializing the scheduler"); // backgrounds runners are web aware, if the app domain dies, these tasks will wind down correctly - _publishingRunner = new BackgroundTaskRunner(applicationContext.ProfilingLogger.Logger); - _tasksRunner = new BackgroundTaskRunner(applicationContext.ProfilingLogger.Logger); - _scrubberRunner = new BackgroundTaskRunner(applicationContext.ProfilingLogger.Logger); + _keepAliveRunner = new BackgroundTaskRunner("KeepAlive", applicationContext.ProfilingLogger.Logger); + _publishingRunner = new BackgroundTaskRunner("ScheduledPublishing", applicationContext.ProfilingLogger.Logger); + _tasksRunner = new BackgroundTaskRunner("ScheduledTasks", applicationContext.ProfilingLogger.Logger); + _scrubberRunner = new BackgroundTaskRunner("LogScrubber", applicationContext.ProfilingLogger.Logger); var settings = UmbracoConfig.For.UmbracoSettings(); - // note - // must use the single-parameter constructor on Timer to avoid it from being GC'd - // also make the timer static to ensure further GC safety - // read http://stackoverflow.com/questions/4962172/why-does-a-system-timers-timer-survive-gc-but-not-system-threading-timer - - // ping/keepalive - no need for a background runner - does not need to be web aware, ok if the app domain dies - _pingTimer = new Timer(state => KeepAlive.Start(applicationContext, UmbracoConfig.For.UmbracoSettings())); - _pingTimer.Change(60000, 300000); + // ping/keepalive + // on all servers + _keepAliveRunner.Add(new KeepAlive(_keepAliveRunner, 60000, 300000, applicationContext)); // scheduled publishing/unpublishing // install on all, will only run on non-slaves servers - // both are delayed recurring tasks _publishingRunner.Add(new ScheduledPublishing(_publishingRunner, 60000, 60000, applicationContext, settings)); _tasksRunner.Add(new ScheduledTasks(_tasksRunner, 60000, 60000, applicationContext, settings)); // log scrubbing - // install & run on all servers - // LogScrubber is a delayed recurring task + // install on all, will only run on non-slaves servers _scrubberRunner.Add(new LogScrubber(_scrubberRunner, 60000, LogScrubber.GetLogScrubbingInterval(settings), applicationContext, settings)); } } diff --git a/src/Umbraco.Web/Scheduling/ThreadingTaskAwaiter.cs b/src/Umbraco.Web/Scheduling/ThreadingTaskAwaiter.cs deleted file mode 100644 index 36c00dfa9e..0000000000 --- a/src/Umbraco.Web/Scheduling/ThreadingTaskAwaiter.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using System.Runtime.CompilerServices; -using System.Threading.Tasks; - -namespace Umbraco.Web.Scheduling -{ - /// - /// This is used to return an awaitable instance from a Task without actually returning the - /// underlying Task instance since it shouldn't be mutable. - /// - internal class ThreadingTaskAwaiter - { - private readonly Task _task; - - public ThreadingTaskAwaiter(Task task) - { - if (task == null) throw new ArgumentNullException("task"); - _task = task; - } - - /// - /// With a GetAwaiter declared it means that this instance can be awaited on with the await keyword - /// - /// - public TaskAwaiter GetAwaiter() - { - return _task.GetAwaiter(); - } - - /// - /// Gets the status of the running task. - /// - /// There is no running task. - /// Unless the AutoStart option is true, there will be no running task until - /// a background task is added to the queue. Unless the KeepAlive option is true, there - /// will be no running task when the queue is empty. - public TaskStatus Status - { - get { return _task.Status; } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Scheduling/ThreadingTaskImmutable.cs b/src/Umbraco.Web/Scheduling/ThreadingTaskImmutable.cs new file mode 100644 index 0000000000..e8ccbeac0e --- /dev/null +++ b/src/Umbraco.Web/Scheduling/ThreadingTaskImmutable.cs @@ -0,0 +1,44 @@ +using System; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; + +namespace Umbraco.Web.Scheduling +{ + /// + /// Wraps a Task within an object that gives access to its GetAwaiter method and Status + /// property while ensuring that it cannot be modified in any way. + /// + internal class ThreadingTaskImmutable + { + private readonly Task _task; + + /// + /// Initializes a new instance of the class with a Task. + /// + /// The task. + public ThreadingTaskImmutable(Task task) + { + if (task == null) + throw new ArgumentNullException("task"); + _task = task; + } + + /// + /// Gets an awaiter used to await the task. + /// + /// An awaiter instance. + public TaskAwaiter GetAwaiter() + { + return _task.GetAwaiter(); + } + + /// + /// Gets the TaskStatus of the task. + /// + /// The current TaskStatus of the task. + public TaskStatus Status + { + get { return _task.Status; } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs b/src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs index c829efe593..4420e1983b 100644 --- a/src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs +++ b/src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; -using System.Threading.Tasks; +using System.Globalization; +using System.Threading; using System.Web; using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.Owin; @@ -12,6 +13,7 @@ using Microsoft.Owin.Security.Cookies; using Owin; using Umbraco.Core; using Umbraco.Core.Configuration; +using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models.Identity; using Umbraco.Core.Security; @@ -28,7 +30,7 @@ namespace Umbraco.Web.Security.Identity public static void SetUmbracoLoggerFactory(this IAppBuilder app) { app.SetLoggerFactory(new OwinLoggerFactory()); - } + } #region Backoffice @@ -38,16 +40,13 @@ namespace Umbraco.Web.Security.Identity /// /// /// - public static void ConfigureUserManagerForUmbracoBackOffice(this IAppBuilder app, - ApplicationContext appContext, + public static void ConfigureUserManagerForUmbracoBackOffice(this IAppBuilder app, + ApplicationContext appContext, MembershipProviderBase userMembershipProvider) { if (appContext == null) throw new ArgumentNullException("appContext"); if (userMembershipProvider == null) throw new ArgumentNullException("userMembershipProvider"); - //Don't proceed if the app is not ready - if (appContext.IsUpgrading == false && appContext.IsConfigured == false) return; - //Configure Umbraco user manager to be created per request app.CreatePerOwinContext( (options, owinContext) => BackOfficeUserManager.Create( @@ -55,6 +54,9 @@ namespace Umbraco.Web.Security.Identity appContext.Services.UserService, appContext.Services.ExternalLoginService, userMembershipProvider)); + + //Create a sign in manager per request + app.CreatePerOwinContext((options, context) => BackOfficeSignInManager.Create(options, context, app.CreateLogger())); } /// @@ -73,15 +75,15 @@ namespace Umbraco.Web.Security.Identity if (userMembershipProvider == null) throw new ArgumentNullException("userMembershipProvider"); if (customUserStore == null) throw new ArgumentNullException("customUserStore"); - //Don't proceed if the app is not ready - if (appContext.IsUpgrading == false && appContext.IsConfigured == false) return; - //Configure Umbraco user manager to be created per request app.CreatePerOwinContext( (options, owinContext) => BackOfficeUserManager.Create( options, customUserStore, userMembershipProvider)); + + //Create a sign in manager per request + app.CreatePerOwinContext((options, context) => BackOfficeSignInManager.Create(options, context, app.CreateLogger(typeof(BackOfficeSignInManager).FullName))); } /// @@ -93,46 +95,96 @@ namespace Umbraco.Web.Security.Identity public static void ConfigureUserManagerForUmbracoBackOffice(this IAppBuilder app, ApplicationContext appContext, Func, IOwinContext, TManager> userManager) - where TManager : BackOfficeUserManager + where TManager : BackOfficeUserManager where TUser : BackOfficeIdentityUser { if (appContext == null) throw new ArgumentNullException("appContext"); if (userManager == null) throw new ArgumentNullException("userManager"); - //Don't proceed if the app is not ready - if (appContext.IsUpgrading == false && appContext.IsConfigured == false) return; - //Configure Umbraco user manager to be created per request app.CreatePerOwinContext(userManager); + + //Create a sign in manager per request + app.CreatePerOwinContext((options, context) => BackOfficeSignInManager.Create(options, context, app.CreateLogger(typeof(BackOfficeSignInManager).FullName))); } /// /// Ensures that the UmbracoBackOfficeAuthenticationMiddleware is assigned to the pipeline /// /// + /// /// - public static IAppBuilder UseUmbracoBackOfficeCookieAuthentication(this IAppBuilder app) + public static IAppBuilder UseUmbracoBackOfficeCookieAuthentication(this IAppBuilder app, ApplicationContext appContext) { if (app == null) throw new ArgumentNullException("app"); + if (appContext == null) throw new ArgumentNullException("appContext"); + var cookieAuthProvider = new BackOfficeCookieAuthenticationProvider + { + // Enables the application to validate the security stamp when the user + // logs in. This is a security feature which is used when you + // change a password or add an external login to your account. + OnValidateIdentity = SecurityStampValidator + .OnValidateIdentity( + TimeSpan.FromMinutes(30), + (manager, user) => user.GenerateUserIdentityAsync(manager), + identity => identity.GetUserId()), + }; - app.UseCookieAuthentication(new UmbracoBackOfficeCookieAuthOptions( + var authOptions = new UmbracoBackOfficeCookieAuthOptions( + UmbracoConfig.For.UmbracoSettings().Security, + GlobalSettings.TimeOutInMinutes, + GlobalSettings.UseSSL) + { + Provider = cookieAuthProvider + }; + + app.UseUmbracoBackOfficeCookieAuthentication(authOptions, appContext); + + //don't apply if app isnot ready + if (appContext.IsUpgrading || appContext.IsConfigured) + { + var getSecondsOptions = new UmbracoBackOfficeCookieAuthOptions( + //This defines the explicit path read cookies from for this middleware + new[]{string.Format("{0}/backoffice/UmbracoApi/Authentication/GetRemainingTimeoutSeconds", GlobalSettings.Path)}, UmbracoConfig.For.UmbracoSettings().Security, GlobalSettings.TimeOutInMinutes, GlobalSettings.UseSSL) + { + Provider = cookieAuthProvider + }; + + //This is a custom middleware, we need to return the user's remaining logged in seconds + app.Use( + getSecondsOptions, + UmbracoConfig.For.UmbracoSettings().Security, + app.CreateLogger()); + } + + return app; + } + + internal static IAppBuilder UseUmbracoBackOfficeCookieAuthentication(this IAppBuilder app, CookieAuthenticationOptions options, ApplicationContext appContext) + { + if (app == null) { - Provider = new CookieAuthenticationProvider - { - // Enables the application to validate the security stamp when the user - // logs in. This is a security feature which is used when you - // change a password or add an external login to your account. - OnValidateIdentity = SecurityStampValidator - .OnValidateIdentity( - TimeSpan.FromMinutes(30), - (manager, user) => user.GenerateUserIdentityAsync(manager), - identity => identity.GetUserId()) - } - }); + throw new ArgumentNullException("app"); + } + + //First the normal cookie middleware + app.Use(typeof(CookieAuthenticationMiddleware), app, options); + app.UseStageMarker(PipelineStage.Authenticate); + + //don't apply if app isnot ready + if (appContext.IsUpgrading || appContext.IsConfigured) + { + //Then our custom middlewares + app.Use(typeof(ForceRenewalCookieAuthenticationMiddleware), app, options, new SingletonUmbracoContextAccessor()); + app.UseStageMarker(PipelineStage.Authenticate); + app.Use(typeof(FixWindowsAuthMiddlware)); + app.UseStageMarker(PipelineStage.Authenticate); + } + return app; } @@ -142,10 +194,12 @@ namespace Umbraco.Web.Security.Identity /// Umbraco back office configuration /// /// + /// /// - public static IAppBuilder UseUmbracoBackOfficeExternalCookieAuthentication(this IAppBuilder app) + public static IAppBuilder UseUmbracoBackOfficeExternalCookieAuthentication(this IAppBuilder app, ApplicationContext appContext) { if (app == null) throw new ArgumentNullException("app"); + if (appContext == null) throw new ArgumentNullException("appContext"); app.UseCookieAuthentication(new CookieAuthenticationOptions { @@ -162,8 +216,12 @@ namespace Umbraco.Web.Security.Identity }); return app; - } + } #endregion + public static void SanitizeThreadCulture(this IAppBuilder app) + { + Thread.CurrentThread.SanitizeThreadCulture(); + } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Security/Identity/AuthenticationOptionsExtensions.cs b/src/Umbraco.Web/Security/Identity/AuthenticationOptionsExtensions.cs index 43b995bb06..7074a6db9f 100644 --- a/src/Umbraco.Web/Security/Identity/AuthenticationOptionsExtensions.cs +++ b/src/Umbraco.Web/Security/Identity/AuthenticationOptionsExtensions.cs @@ -1,3 +1,4 @@ +using System; using Microsoft.Owin; using Microsoft.Owin.Security; using Umbraco.Core; @@ -7,6 +8,47 @@ namespace Umbraco.Web.Security.Identity { public static class AuthenticationOptionsExtensions { + + public static void SetSignInChallengeResultCallback( + this AuthenticationOptions authOptions, + Func authProperties) + { + authOptions.Description.Properties["ChallengeResultCallback"] = authProperties; + } + + public static AuthenticationProperties GetSignInChallengeResult(this AuthenticationDescription authenticationDescription, IOwinContext ctx) + { + if (authenticationDescription.Properties.ContainsKey("ChallengeResultCallback") == false) return null; + var cb = authenticationDescription.Properties["ChallengeResultCallback"] as Func; + if (cb == null) return null; + return cb(ctx); + } + + /// + /// Used during the External authentication process to assign external sign-in options + /// that are used by the Umbraco authentication process. + /// + /// + /// + public static void SetExternalSignInAutoLinkOptions( + this AuthenticationOptions authOptions, + ExternalSignInAutoLinkOptions options) + { + authOptions.Description.Properties["ExternalSignInAutoLinkOptions"] = options; + } + + /// + /// Used during the External authentication process to retrieve external sign-in options + /// that have been set with SetExternalAuthenticationOptions + /// + /// + public static ExternalSignInAutoLinkOptions GetExternalAuthenticationOptions(this AuthenticationDescription authenticationDescription) + { + if (authenticationDescription.Properties.ContainsKey("ExternalSignInAutoLinkOptions") == false) return null; + var options = authenticationDescription.Properties["ExternalSignInAutoLinkOptions"] as ExternalSignInAutoLinkOptions; + return options; + } + /// /// Configures the properties of the authentication description instance for use with Umbraco back office /// diff --git a/src/Umbraco.Web/Security/Identity/BackOfficeCookieManager.cs b/src/Umbraco.Web/Security/Identity/BackOfficeCookieManager.cs index 7eedf2ecb0..76bd80037c 100644 --- a/src/Umbraco.Web/Security/Identity/BackOfficeCookieManager.cs +++ b/src/Umbraco.Web/Security/Identity/BackOfficeCookieManager.cs @@ -1,6 +1,12 @@ -using Microsoft.Owin; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using Microsoft.Owin; using Microsoft.Owin.Infrastructure; using Umbraco.Core; +using Umbraco.Core.Configuration; +using Umbraco.Core.IO; namespace Umbraco.Web.Security.Identity { @@ -14,10 +20,19 @@ namespace Umbraco.Web.Security.Identity internal class BackOfficeCookieManager : ChunkingCookieManager, ICookieManager { private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private readonly string[] _explicitPaths; + private readonly string _getRemainingSecondsPath; public BackOfficeCookieManager(IUmbracoContextAccessor umbracoContextAccessor) + : this(umbracoContextAccessor, null) + { + + } + public BackOfficeCookieManager(IUmbracoContextAccessor umbracoContextAccessor, IEnumerable explicitPaths) { _umbracoContextAccessor = umbracoContextAccessor; + _explicitPaths = explicitPaths == null ? null : explicitPaths.ToArray(); + _getRemainingSecondsPath = string.Format("{0}/backoffice/UmbracoApi/Authentication/GetRemainingTimeoutSeconds", GlobalSettings.Path); } /// @@ -32,14 +47,60 @@ namespace Umbraco.Web.Security.Identity { return null; } - - return UmbracoModule.ShouldAuthenticateRequest( - context.HttpContextFromOwinContext().Request, + + return ShouldAuthenticateRequest( + context, _umbracoContextAccessor.Value.OriginalRequestUrl) == false //Don't auth request, don't return a cookie ? null //Return the default implementation : base.GetRequestCookie(context, key); } + + /// + /// Determines if we should authenticate the request + /// + /// + /// + /// + /// + /// + /// We auth the request when: + /// * it is a back office request + /// * it is an installer request + /// * it is a /base request + /// * it is a preview request + /// + internal bool ShouldAuthenticateRequest(IOwinContext ctx, Uri originalRequestUrl, bool checkForceAuthTokens = true) + { + var request = ctx.Request; + var httpCtx = ctx.TryGetHttpContext(); + + //check the explicit paths + if (_explicitPaths != null) + { + return _explicitPaths.Any(x => x.InvariantEquals(request.Uri.AbsolutePath)); + } + + //check user seconds path + if (request.Uri.AbsolutePath.InvariantEquals(_getRemainingSecondsPath)) return false; + + if (//check the explicit flag + (checkForceAuthTokens && ctx.Get("umbraco-force-auth") != null) + || (checkForceAuthTokens && httpCtx.Success && httpCtx.Result.Items["umbraco-force-auth"] != null) + //check back office + || request.Uri.IsBackOfficeRequest(HttpRuntime.AppDomainAppVirtualPath) + //check installer + || request.Uri.IsInstallerRequest() + //detect in preview + || (request.HasPreviewCookie() && request.Uri != null && request.Uri.AbsolutePath.StartsWith(IOHelper.ResolveUrl(SystemDirectories.Umbraco)) == false) + //check for base + || BaseRest.BaseRestHandler.IsBaseRestRequest(originalRequestUrl)) + { + return true; + } + return false; + } + } } \ No newline at end of file diff --git a/src/Umbraco.Web/Security/Identity/ExternalSignInAutoLinkOptions.cs b/src/Umbraco.Web/Security/Identity/ExternalSignInAutoLinkOptions.cs new file mode 100644 index 0000000000..832f0b3a30 --- /dev/null +++ b/src/Umbraco.Web/Security/Identity/ExternalSignInAutoLinkOptions.cs @@ -0,0 +1,77 @@ +using System; +using Microsoft.AspNet.Identity.Owin; +using Microsoft.Owin; +using Umbraco.Core; +using Umbraco.Core.Configuration; +using Umbraco.Core.Models.Identity; + +namespace Umbraco.Web.Security.Identity +{ + /// + /// Options used to configure auto-linking external OAuth providers + /// + public sealed class ExternalSignInAutoLinkOptions + { + public ExternalSignInAutoLinkOptions( + bool autoLinkExternalAccount = false, + string defaultUserType = "editor", + string[] defaultAllowedSections = null, + string defaultCulture = null) + { + Mandate.ParameterNotNullOrEmpty(defaultUserType, "defaultUserType"); + + _defaultUserType = defaultUserType; + _defaultAllowedSections = defaultAllowedSections ?? new[] { "content", "media" }; + _autoLinkExternalAccount = autoLinkExternalAccount; + _defaultCulture = defaultCulture ?? GlobalSettings.DefaultUILanguage; + } + + private readonly string _defaultUserType; + + /// + /// A callback executed during account auto-linking and before the user is persisted + /// + public Action OnAutoLinking { get; set; } + + /// + /// The default User Type alias to use for auto-linking users + /// + public string GetDefaultUserType(UmbracoContext umbracoContext, ExternalLoginInfo loginInfo) + { + return _defaultUserType; + } + + private readonly string[] _defaultAllowedSections; + + /// + /// The default allowed sections to use for auto-linking users + /// + public string[] GetDefaultAllowedSections(UmbracoContext umbracoContext, ExternalLoginInfo loginInfo) + { + return _defaultAllowedSections; + } + + private readonly bool _autoLinkExternalAccount; + + /// + /// For private external auth providers such as Active Directory, which when set to true will automatically + /// create a local user if the external provider login was successful. + /// + /// For public auth providers this should always be false!!! + /// + public bool ShouldAutoLinkExternalAccount(UmbracoContext umbracoContext, ExternalLoginInfo loginInfo) + { + return _autoLinkExternalAccount; + } + + private readonly string _defaultCulture; + + /// + /// The default Culture to use for auto-linking users + /// + public string GetDefaultCulture(UmbracoContext umbracoContext, ExternalLoginInfo loginInfo) + { + return _defaultCulture; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Security/Identity/FixWindowsAuthMiddlware.cs b/src/Umbraco.Web/Security/Identity/FixWindowsAuthMiddlware.cs new file mode 100644 index 0000000000..911a304019 --- /dev/null +++ b/src/Umbraco.Web/Security/Identity/FixWindowsAuthMiddlware.cs @@ -0,0 +1,51 @@ +using System.Linq; +using System.Security.Claims; +using System.Security.Principal; +using System.Threading.Tasks; +using Microsoft.Owin; +using Umbraco.Core; +using Umbraco.Core.Security; + +namespace Umbraco.Web.Security.Identity +{ + /// + /// This is used to inspect the request to see if 2 x identities are assigned: A windows one and a back office one. + /// When this is the case, it means that auth has executed for Windows & auth has executed for our back office cookie + /// handler and now two identities have been assigned. Unfortunately, at some stage in the pipeline - I'm pretty sure + /// it's the Role Provider Module - it again changes the user's Principal to a RolePrincipal and discards the second + /// Identity which is the Back office identity thus preventing a user from accessing the back office... it's very annoying. + /// + /// To fix this, we re-set the user Principal to only have a single identity: the back office one, since we know this is + /// for a back office request. + /// + internal class FixWindowsAuthMiddlware : OwinMiddleware + { + public FixWindowsAuthMiddlware(OwinMiddleware next) : base(next) + { + } + + public override async Task Invoke(IOwinContext context) + { + if (context.Request.Uri.IsClientSideRequest() == false) + { + var claimsPrincipal = context.Request.User as ClaimsPrincipal; + if (claimsPrincipal != null + && claimsPrincipal.Identities.Count() > 1 + && claimsPrincipal.Identities.Any(x => x is WindowsIdentity) + && claimsPrincipal.Identities.Any(x => x is UmbracoBackOfficeIdentity)) + { + var backOfficeIdentity = claimsPrincipal.Identities.First(x => x is UmbracoBackOfficeIdentity); + if (backOfficeIdentity.IsAuthenticated) + { + context.Request.User = new ClaimsPrincipal(backOfficeIdentity); + } + } + } + + if (Next != null) + { + await Next.Invoke(context); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Security/Identity/ForceRenewalCookieAuthenticationHandler.cs b/src/Umbraco.Web/Security/Identity/ForceRenewalCookieAuthenticationHandler.cs new file mode 100644 index 0000000000..c7030b2558 --- /dev/null +++ b/src/Umbraco.Web/Security/Identity/ForceRenewalCookieAuthenticationHandler.cs @@ -0,0 +1,128 @@ +using System; +using Umbraco.Core; +using System.Threading.Tasks; +using Microsoft.Owin; +using Microsoft.Owin.Security; +using Microsoft.Owin.Security.Cookies; +using Microsoft.Owin.Security.Infrastructure; + +namespace Umbraco.Web.Security.Identity +{ + /// + /// If a flag is set on the context to force renew the ticket, this will do it + /// + internal class ForceRenewalCookieAuthenticationHandler : AuthenticationHandler + { + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + + public ForceRenewalCookieAuthenticationHandler(IUmbracoContextAccessor umbracoContextAccessor) + { + _umbracoContextAccessor = umbracoContextAccessor; + } + + /// + /// This handler doesn't actually do any auth so we return null; + /// + /// + protected override Task AuthenticateCoreAsync() + { + return Task.FromResult((AuthenticationTicket)null); + } + + /// + /// Gets the ticket from the request + /// + /// + private AuthenticationTicket GetTicket() + { + var cookie = Options.CookieManager.GetRequestCookie(Context, Options.CookieName); + if (string.IsNullOrWhiteSpace(cookie)) + { + return null; + } + var ticket = Options.TicketDataFormat.Unprotect(cookie); + if (ticket == null) + { + return null; + } + return ticket; + } + + /// + /// This will check if the token exists in the request to force renewal + /// + /// + protected override Task ApplyResponseGrantAsync() + { + if (_umbracoContextAccessor.Value == null || Context.Request.Uri.IsClientSideRequest()) + { + return Task.FromResult(0); + } + + //Now we need to check if we should force renew this based on a flag in the context and whether this is a request that is not normally renewed by OWIN... + // which means that it is not a normal URL that is authenticated. + + var normalAuthUrl = ((BackOfficeCookieManager) Options.CookieManager) + .ShouldAuthenticateRequest(Context, _umbracoContextAccessor.Value.OriginalRequestUrl, + //Pass in false, we want to know if this is a normal auth'd page + checkForceAuthTokens: false); + //This is auth'd normally, so OWIN will naturally take care of the cookie renewal + if (normalAuthUrl) return Task.FromResult(0); + + var httpCtx = Context.TryGetHttpContext(); + //check for the special flag in either the owin or http context + var shouldRenew = Context.Get("umbraco-force-auth") != null || (httpCtx.Success && httpCtx.Result.Items["umbraco-force-auth"] != null); + + if (shouldRenew) + { + var signin = Helper.LookupSignIn(Options.AuthenticationType); + var shouldSignin = signin != null; + var signout = Helper.LookupSignOut(Options.AuthenticationType, Options.AuthenticationMode); + var shouldSignout = signout != null; + + //we don't care about the sign in/sign out scenario, only renewal + if (shouldSignin == false && shouldSignout == false) + { + //get the ticket + var ticket = GetTicket(); + if (ticket != null) + { + var currentUtc = Options.SystemClock.UtcNow; + var issuedUtc = ticket.Properties.IssuedUtc; + var expiresUtc = ticket.Properties.ExpiresUtc; + + if (expiresUtc.HasValue && issuedUtc.HasValue) + { + var timeElapsed = currentUtc.Subtract(issuedUtc.Value); + var timeRemaining = expiresUtc.Value.Subtract(currentUtc); + + //if it's time to renew, then do it + if (timeRemaining < timeElapsed) + { + //renew the date/times + ticket.Properties.IssuedUtc = currentUtc; + var timeSpan = expiresUtc.Value.Subtract(issuedUtc.Value); + ticket.Properties.ExpiresUtc = currentUtc.Add(timeSpan); + + //now save back all the required cookie details + var cookieValue = Options.TicketDataFormat.Protect(ticket); + + var cookieOptions = ((UmbracoBackOfficeCookieAuthOptions)Options).CreateRequestCookieOptions(Context, ticket); + + Options.CookieManager.AppendResponseCookie( + Context, + Options.CookieName, + cookieValue, + cookieOptions); + } + + + } + } + } + } + + return Task.FromResult(0); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Security/Identity/ForceRenewalCookieAuthenticationMiddleware.cs b/src/Umbraco.Web/Security/Identity/ForceRenewalCookieAuthenticationMiddleware.cs new file mode 100644 index 0000000000..77a7a335f0 --- /dev/null +++ b/src/Umbraco.Web/Security/Identity/ForceRenewalCookieAuthenticationMiddleware.cs @@ -0,0 +1,29 @@ +using Microsoft.Owin; +using Microsoft.Owin.Security.Cookies; +using Microsoft.Owin.Security.Infrastructure; +using Owin; + +namespace Umbraco.Web.Security.Identity +{ + /// + /// This middleware is used simply to force renew the auth ticket if a flag to do so is found in the request + /// + internal class ForceRenewalCookieAuthenticationMiddleware : CookieAuthenticationMiddleware + { + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + + public ForceRenewalCookieAuthenticationMiddleware( + OwinMiddleware next, + IAppBuilder app, + UmbracoBackOfficeCookieAuthOptions options, + IUmbracoContextAccessor umbracoContextAccessor) : base(next, app, options) + { + _umbracoContextAccessor = umbracoContextAccessor; + } + + protected override AuthenticationHandler CreateHandler() + { + return new ForceRenewalCookieAuthenticationHandler(_umbracoContextAccessor); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Security/Identity/FormsAuthenticationSecureDataFormat.cs b/src/Umbraco.Web/Security/Identity/FormsAuthenticationSecureDataFormat.cs index ba46bba881..77e0fe9faf 100644 --- a/src/Umbraco.Web/Security/Identity/FormsAuthenticationSecureDataFormat.cs +++ b/src/Umbraco.Web/Security/Identity/FormsAuthenticationSecureDataFormat.cs @@ -27,12 +27,16 @@ namespace Umbraco.Web.Security.Identity { var backofficeIdentity = (UmbracoBackOfficeIdentity)data.Identity; var userDataString = JsonConvert.SerializeObject(backofficeIdentity.UserData); - + var ticket = new FormsAuthenticationTicket( 5, data.Identity.Name, - data.Properties.IssuedUtc.HasValue ? data.Properties.IssuedUtc.Value.LocalDateTime : DateTime.Now, - data.Properties.ExpiresUtc.HasValue ? data.Properties.ExpiresUtc.Value.LocalDateTime : DateTime.Now.AddMinutes(_loginTimeoutMinutes), + data.Properties.IssuedUtc.HasValue + ? data.Properties.IssuedUtc.Value.LocalDateTime + : DateTime.Now, + data.Properties.ExpiresUtc.HasValue + ? data.Properties.ExpiresUtc.Value.LocalDateTime + : DateTime.Now.AddMinutes(_loginTimeoutMinutes), data.Properties.IsPersistent, userDataString, "/" @@ -65,7 +69,8 @@ namespace Umbraco.Web.Security.Identity { ExpiresUtc = decrypt.Expiration.ToUniversalTime(), IssuedUtc = decrypt.IssueDate.ToUniversalTime(), - IsPersistent = decrypt.IsPersistent + IsPersistent = decrypt.IsPersistent, + AllowRefresh = true }); return ticket; diff --git a/src/Umbraco.Web/Security/Identity/GetUserSecondsMiddleWare.cs b/src/Umbraco.Web/Security/Identity/GetUserSecondsMiddleWare.cs new file mode 100644 index 0000000000..8ec49c4681 --- /dev/null +++ b/src/Umbraco.Web/Security/Identity/GetUserSecondsMiddleWare.cs @@ -0,0 +1,125 @@ +using System; +using System.Diagnostics; +using System.Globalization; +using System.Threading.Tasks; +using Microsoft.Owin; +using Microsoft.Owin.Logging; +using Microsoft.Owin.Security.Cookies; +using Umbraco.Core; +using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.UmbracoSettings; + +namespace Umbraco.Web.Security.Identity +{ + /// + /// Custom middleware to return the remaining seconds the user has before they are logged out + /// + /// + /// This is quite a custom request because in most situations we just want to return the seconds and don't want + /// to renew the auth ticket, however if KeepUserLoggedIn is true, then we do want to renew the auth ticket for + /// this request! + /// + internal class GetUserSecondsMiddleWare : OwinMiddleware + { + private readonly UmbracoBackOfficeCookieAuthOptions _authOptions; + private readonly ISecuritySection _security; + private readonly ILogger _logger; + + public GetUserSecondsMiddleWare( + OwinMiddleware next, + UmbracoBackOfficeCookieAuthOptions authOptions, + ISecuritySection security, + ILogger logger) + : base(next) + { + if (authOptions == null) throw new ArgumentNullException("authOptions"); + if (logger == null) throw new ArgumentNullException("logger"); + _authOptions = authOptions; + _security = security; + _logger = logger; + } + + public override async Task Invoke(IOwinContext context) + { + var request = context.Request; + var response = context.Response; + + if (request.Uri.Scheme.InvariantStartsWith("http") + && request.Uri.AbsolutePath.InvariantEquals( + string.Format("{0}/backoffice/UmbracoApi/Authentication/GetRemainingTimeoutSeconds", GlobalSettings.Path))) + { + var cookie = _authOptions.CookieManager.GetRequestCookie(context, _security.AuthCookieName); + if (cookie.IsNullOrWhiteSpace() == false) + { + var ticket = _authOptions.TicketDataFormat.Unprotect(cookie); + if (ticket != null) + { + var remainingSeconds = ticket.Properties.ExpiresUtc.HasValue + ? (ticket.Properties.ExpiresUtc.Value - _authOptions.SystemClock.UtcNow).TotalSeconds + : 0; + + response.ContentType = "application/json; charset=utf-8"; + response.StatusCode = 200; + response.Headers.Add("Cache-Control", new[] { "no-cache" }); + response.Headers.Add("Pragma", new[] { "no-cache" }); + response.Headers.Add("Expires", new[] { "-1" }); + response.Headers.Add("Date", new[] { _authOptions.SystemClock.UtcNow.ToString("R") }); + + //Ok, so here we need to check if we want to process/renew the auth ticket for each + // of these requests. If that is the case, the user will really never be logged out until they + // close their browser (there will be edge cases of that, especially when debugging) + if (_security.KeepUserLoggedIn) + { + var currentUtc = _authOptions.SystemClock.UtcNow; + var issuedUtc = ticket.Properties.IssuedUtc; + var expiresUtc = ticket.Properties.ExpiresUtc; + + if (expiresUtc.HasValue && issuedUtc.HasValue) + { + var timeElapsed = currentUtc.Subtract(issuedUtc.Value); + var timeRemaining = expiresUtc.Value.Subtract(currentUtc); + + //if it's time to renew, then do it + if (timeRemaining < timeElapsed) + { + ticket.Properties.IssuedUtc = currentUtc; + var timeSpan = expiresUtc.Value.Subtract(issuedUtc.Value); + ticket.Properties.ExpiresUtc = currentUtc.Add(timeSpan); + + var cookieValue = _authOptions.TicketDataFormat.Protect(ticket); + + var cookieOptions = _authOptions.CreateRequestCookieOptions(context, ticket); + + _authOptions.CookieManager.AppendResponseCookie( + context, + _authOptions.CookieName, + cookieValue, + cookieOptions); + + remainingSeconds = (ticket.Properties.ExpiresUtc.Value - currentUtc).TotalSeconds; + } + } + } + else if (remainingSeconds <= 30) + { + //NOTE: We are using 30 seconds because that is what is coded into angular to force logout to give some headway in + // the timeout process. + + _logger.WriteCore(TraceEventType.Information, 0, + string.Format("User logged will be logged out due to timeout: {0}, IP Address: {1}", ticket.Identity.Name, request.RemoteIpAddress), + null, null); + } + + await response.WriteAsync(remainingSeconds.ToString(CultureInfo.InvariantCulture)); + return; + } + } + response.StatusCode = 401; + } + else if (Next != null) + { + await Next.Invoke(context); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Security/Identity/IUmbracoBackOfficeTwoFactorOptions.cs b/src/Umbraco.Web/Security/Identity/IUmbracoBackOfficeTwoFactorOptions.cs new file mode 100644 index 0000000000..54d1d6bdce --- /dev/null +++ b/src/Umbraco.Web/Security/Identity/IUmbracoBackOfficeTwoFactorOptions.cs @@ -0,0 +1,12 @@ +using Microsoft.Owin; + +namespace Umbraco.Web.Security.Identity +{ + /// + /// Used to display a custom view in the back office if developers choose to implement their own custom 2 factor authentication + /// + public interface IUmbracoBackOfficeTwoFactorOptions + { + string GetTwoFactorView(IOwinContext owinContext, UmbracoContext umbracoContext, string username); + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Security/Identity/OwinExtensions.cs b/src/Umbraco.Web/Security/Identity/OwinExtensions.cs deleted file mode 100644 index 4b83f97bd3..0000000000 --- a/src/Umbraco.Web/Security/Identity/OwinExtensions.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Web; -using Microsoft.Owin; - -namespace Umbraco.Web.Security.Identity -{ - internal static class OwinExtensions - { - /// - /// Nasty little hack to get httpcontextbase from an owin context - /// - /// - /// - public static HttpContextBase HttpContextFromOwinContext(this IOwinContext owinContext) - { - return owinContext.Get(typeof(HttpContextBase).FullName); - } - - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Security/Identity/UmbracoBackOfficeCookieAuthOptions.cs b/src/Umbraco.Web/Security/Identity/UmbracoBackOfficeCookieAuthOptions.cs index 1751a57462..f90faae7d3 100644 --- a/src/Umbraco.Web/Security/Identity/UmbracoBackOfficeCookieAuthOptions.cs +++ b/src/Umbraco.Web/Security/Identity/UmbracoBackOfficeCookieAuthOptions.cs @@ -1,5 +1,8 @@ -using System.Security.Claims; +using System; +using System.Security.Claims; using System.Threading.Tasks; +using Microsoft.Owin; +using Microsoft.Owin.Security; using Microsoft.Owin.Security.Cookies; using Umbraco.Core; using Umbraco.Core.Configuration; @@ -12,34 +15,81 @@ namespace Umbraco.Web.Security.Identity /// public sealed class UmbracoBackOfficeCookieAuthOptions : CookieAuthenticationOptions { + public int LoginTimeoutMinutes { get; private set; } + public UmbracoBackOfficeCookieAuthOptions() : this(UmbracoConfig.For.UmbracoSettings().Security, GlobalSettings.TimeOutInMinutes, GlobalSettings.UseSSL) { } - - public UmbracoBackOfficeCookieAuthOptions( - ISecuritySection securitySection, - int loginTimeoutMinutes, - bool forceSsl, - bool useLegacyFormsAuthDataFormat = true) + + public UmbracoBackOfficeCookieAuthOptions( + string[] explicitPaths, + ISecuritySection securitySection, + int loginTimeoutMinutes, + bool forceSsl, + bool useLegacyFormsAuthDataFormat = true) { + LoginTimeoutMinutes = loginTimeoutMinutes; AuthenticationType = Constants.Security.BackOfficeAuthenticationType; if (useLegacyFormsAuthDataFormat) { //If this is not explicitly set it will fall back to the default automatically - TicketDataFormat = new FormsAuthenticationSecureDataFormat(loginTimeoutMinutes); + TicketDataFormat = new FormsAuthenticationSecureDataFormat(LoginTimeoutMinutes); } + SlidingExpiration = true; + ExpireTimeSpan = TimeSpan.FromMinutes(LoginTimeoutMinutes); CookieDomain = securitySection.AuthCookieDomain; CookieName = securitySection.AuthCookieName; CookieHttpOnly = true; CookieSecure = forceSsl ? CookieSecureOption.Always : CookieSecureOption.SameAsRequest; - CookiePath = "/"; //Custom cookie manager so we can filter requests - CookieManager = new BackOfficeCookieManager(new SingletonUmbracoContextAccessor()); - } + CookieManager = new BackOfficeCookieManager(new SingletonUmbracoContextAccessor(), explicitPaths); + } + + public UmbracoBackOfficeCookieAuthOptions( + ISecuritySection securitySection, + int loginTimeoutMinutes, + bool forceSsl, + bool useLegacyFormsAuthDataFormat = true) + : this(null, securitySection, loginTimeoutMinutes, forceSsl, useLegacyFormsAuthDataFormat) + { + } + + /// + /// Creates the cookie options for saving the auth cookie + /// + /// + /// + /// + public CookieOptions CreateRequestCookieOptions(IOwinContext ctx, AuthenticationTicket ticket) + { + if (ctx == null) throw new ArgumentNullException("ctx"); + if (ticket == null) throw new ArgumentNullException("ticket"); + + var issuedUtc = ticket.Properties.IssuedUtc ?? SystemClock.UtcNow; + var expiresUtc = ticket.Properties.ExpiresUtc ?? issuedUtc.Add(ExpireTimeSpan); + + var cookieOptions = new CookieOptions + { + Path = "/", + Domain = this.CookieDomain ?? null, + HttpOnly = true, + Secure = this.CookieSecure == CookieSecureOption.Always + || (this.CookieSecure == CookieSecureOption.SameAsRequest && ctx.Request.IsSecure), + }; + + if (ticket.Properties.IsPersistent) + { + cookieOptions.Expires = expiresUtc.ToUniversalTime().DateTime; + } + + return cookieOptions; + } + + } } \ No newline at end of file diff --git a/src/Umbraco.Web/Security/MembershipHelper.cs b/src/Umbraco.Web/Security/MembershipHelper.cs index 3284674338..3bb916ed13 100644 --- a/src/Umbraco.Web/Security/MembershipHelper.cs +++ b/src/Umbraco.Web/Security/MembershipHelper.cs @@ -125,9 +125,7 @@ namespace Umbraco.Web.Security .Where(p => member.ContentType.PropertyTypeExists(p.Alias)) .Where(property => member.Properties.Contains(property.Alias)) //needs to be editable - .Where(p => member.ContentType.MemberCanEditProperty(p.Alias)) - //needs to have a value - .Where(p => p.Value != null)) + .Where(p => member.ContentType.MemberCanEditProperty(p.Alias))) { member.Properties[property.Alias].Value = property.Value; } diff --git a/src/Umbraco.Web/Security/OwinExtensions.cs b/src/Umbraco.Web/Security/OwinExtensions.cs new file mode 100644 index 0000000000..d1f1fbc1ed --- /dev/null +++ b/src/Umbraco.Web/Security/OwinExtensions.cs @@ -0,0 +1,22 @@ +using System.Web; +using Microsoft.Owin; +using Umbraco.Core; + +namespace Umbraco.Web.Security +{ + internal static class OwinExtensions + { + + /// + /// Nasty little hack to get httpcontextbase from an owin context + /// + /// + /// + internal static Attempt TryGetHttpContext(this IOwinContext owinContext) + { + var ctx = owinContext.Get(typeof(HttpContextBase).FullName); + return ctx == null ? Attempt.Fail() : Attempt.Succeed(ctx); + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs b/src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs index 65f90d8127..8482eb72a2 100644 --- a/src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs +++ b/src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs @@ -6,6 +6,7 @@ using System.Text; using System.Web.Configuration; using System.Web.Security; using Umbraco.Core; +using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Models.Membership; using Umbraco.Core.Persistence.Querying; @@ -344,7 +345,10 @@ namespace Umbraco.Web.Security.Providers //don't raise events for this! It just sets the member dates, if we do raise events this will // cause all distributed cache to execute - which will clear out some caches we don't want. // http://issues.umbraco.org/issue/U4-3451 - MemberService.Save(member, false); + + // when upgrating from 7.2 to 7.3 trying to save will throw + if (UmbracoVersion.Current >= new Version(7, 3, 0, 0)) + MemberService.Save(member, false); } return ConvertToMembershipUser(member); @@ -590,7 +594,10 @@ namespace Umbraco.Web.Security.Providers // http://issues.umbraco.org/issue/U4-3451 //TODO: In v8 we aren't going to have an overload to disable events, so we'll need to make a different method // for this type of thing (i.e. UpdateLastLogin or similar). - MemberService.Save(member, false); + + // when upgrating from 7.2 to 7.3 trying to save will throw + if (UmbracoVersion.Current >= new Version(7, 3, 0, 0)) + MemberService.Save(member, false); return authenticated; } diff --git a/src/Umbraco.Web/Security/WebAuthExtensions.cs b/src/Umbraco.Web/Security/WebAuthExtensions.cs new file mode 100644 index 0000000000..52963d1852 --- /dev/null +++ b/src/Umbraco.Web/Security/WebAuthExtensions.cs @@ -0,0 +1,74 @@ +using System.Net.Http; +using System.Security.Claims; +using System.Security.Principal; +using System.ServiceModel.Channels; +using System.Threading; +using System.Web; +using AutoMapper; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Security; +using Umbraco.Web.WebApi; + +namespace Umbraco.Web.Security +{ + internal static class WebAuthExtensions + { + /// + /// This will set a an authenticated IPrincipal to the current request given the IUser object + /// + /// + /// + /// + internal static IPrincipal SetPrincipalForRequest(this HttpRequestMessage request, IUser user) + { + var principal = new ClaimsPrincipal( + new UmbracoBackOfficeIdentity( + new ClaimsIdentity(), + Mapper.Map(user))); + + //It is actually not good enough to set this on the current app Context and the thread, it also needs + // to be set explicitly on the HttpContext.Current !! This is a strange web api thing that is actually + // an underlying fault of asp.net not propogating the User correctly. + if (HttpContext.Current != null) + { + HttpContext.Current.User = principal; + } + var http = request.TryGetHttpContext(); + if (http) + { + http.Result.User = principal; + } + Thread.CurrentPrincipal = principal; + + //For WebAPI + request.SetUserPrincipal(principal); + + return principal; + } + + /// + /// This will set a an authenticated IPrincipal to the current request given the IUser object + /// + /// + /// + /// + internal static IPrincipal SetPrincipalForRequest(this HttpContextBase httpContext, UserData userData) + { + var principal = new ClaimsPrincipal( + new UmbracoBackOfficeIdentity( + new ClaimsIdentity(), + userData)); + + //It is actually not good enough to set this on the current app Context and the thread, it also needs + // to be set explicitly on the HttpContext.Current !! This is a strange web api thing that is actually + // an underlying fault of asp.net not propogating the User correctly. + if (HttpContext.Current != null) + { + HttpContext.Current.User = principal; + } + httpContext.User = principal; + Thread.CurrentPrincipal = principal; + return principal; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Security/WebSecurity.cs b/src/Umbraco.Web/Security/WebSecurity.cs index 463b2c84e3..eb0a1eb66a 100644 --- a/src/Umbraco.Web/Security/WebSecurity.cs +++ b/src/Umbraco.Web/Security/WebSecurity.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using System.Web; using System.Web.Security; @@ -8,8 +9,10 @@ using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Models.Membership; using Umbraco.Core.Security; -using umbraco; +using Microsoft.AspNet.Identity.Owin; +using Microsoft.Owin; using umbraco.businesslogic.Exceptions; +using Umbraco.Web.Models.ContentEditing; using GlobalSettings = Umbraco.Core.Configuration.GlobalSettings; using User = umbraco.BusinessLogic.User; @@ -77,6 +80,42 @@ namespace Umbraco.Web.Security } } + private BackOfficeSignInManager _signInManager; + private BackOfficeSignInManager SignInManager + { + get + { + if (_signInManager == null) + { + var mgr = _httpContext.GetOwinContext().Get(); + if (mgr == null) + { + throw new NullReferenceException("Could not resolve an instance of " + typeof(BackOfficeSignInManager) + " from the " + typeof(IOwinContext)); + } + _signInManager = mgr; + } + return _signInManager; + } + } + + private BackOfficeUserManager _userManager; + protected BackOfficeUserManager UserManager + { + get + { + if (_userManager == null) + { + var mgr = _httpContext.GetOwinContext().GetUserManager(); + if (mgr == null) + { + throw new NullReferenceException("Could not resolve an instance of " + typeof(BackOfficeUserManager) + " from the " + typeof(IOwinContext) + " GetUserManager method"); + } + _userManager = mgr; + } + return _userManager; + } + } + /// /// Logs a user in. /// @@ -84,19 +123,23 @@ namespace Umbraco.Web.Security /// returns the number of seconds until their session times out public virtual double PerformLogin(int userId) { - var user = _applicationContext.Services.UserService.GetUserById(userId); - return PerformLogin(user).GetRemainingAuthSeconds(); + var owinCtx = _httpContext.GetOwinContext(); + //ensure it's done for owin too + owinCtx.Authentication.SignOut(Constants.Security.BackOfficeExternalAuthenticationType); + + var user = UserManager.FindByIdAsync(userId).Result; + var userData = Mapper.Map(user); + _httpContext.SetPrincipalForRequest(userData); + + SignInManager.SignInAsync(user, isPersistent: true, rememberBrowser: false).Wait(); + return TimeSpan.FromMinutes(GlobalSettings.TimeOutInMinutes).TotalSeconds; } - /// - /// Logs the user in - /// - /// - /// returns the Forms Auth ticket created which is used to log them in + [Obsolete("This method should not be used, login is performed by the OWIN pipeline, use the overload that returns double and accepts a UserId instead")] public virtual FormsAuthenticationTicket PerformLogin(IUser user) { - //clear the external cookie - we do this without owin context because we're writing cookies directly to httpcontext - // and cookie handling is different with httpcontext vs webapi and owin, normally we'd do: + //clear the external cookie - we do this first without owin context because we're writing cookies directly to httpcontext + // and cookie handling is different with httpcontext vs webapi and owin, normally we'd just do: //_httpContext.GetOwinContext().Authentication.SignOut(Constants.Security.BackOfficeExternalAuthenticationType); var externalLoginCookie = _httpContext.Request.Cookies.Get(Constants.Security.BackOfficeExternalCookieName); @@ -106,10 +149,10 @@ namespace Umbraco.Web.Security _httpContext.Response.Cookies.Set(externalLoginCookie); } - var ticket = _httpContext.CreateUmbracoAuthTicket(Mapper.Map(user)); - - LogHelper.Info("User Id: {0} logged in", () => user.Id); + //ensure it's done for owin too + _httpContext.GetOwinContext().Authentication.SignOut(Constants.Security.BackOfficeExternalAuthenticationType); + var ticket = _httpContext.CreateUmbracoAuthTicket(Mapper.Map(user)); return ticket; } @@ -119,6 +162,9 @@ namespace Umbraco.Web.Security public virtual void ClearCurrentLogin() { _httpContext.UmbracoLogout(); + _httpContext.GetOwinContext().Authentication.SignOut( + Core.Constants.Security.BackOfficeAuthenticationType, + Core.Constants.Security.BackOfficeExternalAuthenticationType); } /// @@ -323,7 +369,7 @@ namespace Umbraco.Web.Security // since the authentication happens in the Module, that authentication also checks the ticket expiry. We don't // need to check it a second time because that requires another decryption phase and nothing can tamper with it during the request. - if (_httpContext.User.Identity.IsAuthenticated == false) + if (IsAuthenticated() == false) { //There is no user if (throwExceptions) throw new InvalidOperationException("The user has no umbraco contextid - try logging in"); @@ -403,11 +449,21 @@ namespace Umbraco.Web.Security { } } - + + /// + /// Ensures that a back office user is logged in + /// + /// + public bool IsAuthenticated() + { + return _httpContext.User.Identity.IsAuthenticated && _httpContext.GetCurrentIdentity(false) != null; + } + protected override void DisposeResources() { _httpContext = null; - _applicationContext = null; } + + } } diff --git a/src/Umbraco.Web/Strategies/Migrations/EnsureListViewDataTypeIsCreated.cs b/src/Umbraco.Web/Strategies/Migrations/EnsureListViewDataTypeIsCreated.cs index 7244be8710..118ece4701 100644 --- a/src/Umbraco.Web/Strategies/Migrations/EnsureListViewDataTypeIsCreated.cs +++ b/src/Umbraco.Web/Strategies/Migrations/EnsureListViewDataTypeIsCreated.cs @@ -38,9 +38,6 @@ namespace Umbraco.Web.Strategies.Migrations private void EnsureListViewDataTypeCreated(MigrationEventArgs e) { - var exists = e.MigrationContext.Database.ExecuteScalar("SELECT COUNT(*) FROM umbracoNode WHERE id=1037"); - if (exists > 0) return; - using (var transaction = e.MigrationContext.Database.GetTransaction()) { try diff --git a/src/Umbraco.Web/Strategies/ServerRegistrationEventHandler.cs b/src/Umbraco.Web/Strategies/ServerRegistrationEventHandler.cs index 6d8faa782f..2a61d4177d 100644 --- a/src/Umbraco.Web/Strategies/ServerRegistrationEventHandler.cs +++ b/src/Umbraco.Web/Strategies/ServerRegistrationEventHandler.cs @@ -3,6 +3,7 @@ using System.Web; using Newtonsoft.Json; using Umbraco.Core; using Umbraco.Core.Logging; +using Umbraco.Core.Services; using Umbraco.Core.Sync; using Umbraco.Web.Routing; @@ -21,18 +22,23 @@ namespace Umbraco.Web.Strategies /// public sealed class ServerRegistrationEventHandler : ApplicationEventHandler { - private static DateTime _lastUpdated = DateTime.MinValue; + private readonly object _locko = new object(); + private DatabaseServerRegistrar _registrar; + private DateTime _lastUpdated = DateTime.MinValue; // bind to events protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) { + _registrar = ServerRegistrarResolver.Current.Registrar as DatabaseServerRegistrar; + // only for the DatabaseServerRegistrar - if (ServerRegistrarResolver.Current.Registrar is DatabaseServerRegistrar) - UmbracoModule.RouteAttempt += UmbracoModuleRouteAttempt; + if (_registrar == null) return; + + UmbracoModule.RouteAttempt += UmbracoModuleRouteAttempt; } // handles route attempts. - private static void UmbracoModuleRouteAttempt(object sender, RoutableAttemptEventArgs e) + private void UmbracoModuleRouteAttempt(object sender, RoutableAttemptEventArgs e) { if (e.HttpContext.Request == null || e.HttpContext.Request.Url == null) return; @@ -60,31 +66,26 @@ namespace Umbraco.Web.Strategies } // register current server (throttled). - private static void RegisterServer(UmbracoRequestEventArgs e) + private void RegisterServer(UmbracoRequestEventArgs e) { - var reg = (DatabaseServerRegistrar) ServerRegistrarResolver.Current.Registrar; - var options = reg.Options; - var secondsSinceLastUpdate = DateTime.Now.Subtract(_lastUpdated).TotalSeconds; - if (secondsSinceLastUpdate < options.ThrottleSeconds) return; + lock (_locko) // ensure we trigger only once + { + var secondsSinceLastUpdate = DateTime.Now.Subtract(_lastUpdated).TotalSeconds; + if (secondsSinceLastUpdate < _registrar.Options.ThrottleSeconds) return; + _lastUpdated = DateTime.Now; + } - _lastUpdated = DateTime.Now; - - var url = e.HttpContext.Request.Url; var svc = e.UmbracoContext.Application.Services.ServerRegistrationService; + // because + // - ApplicationContext.UmbracoApplicationUrl is initialized by UmbracoModule in BeginRequest + // - RegisterServer is called on UmbracoModule.RouteAttempt which is triggered in ProcessRequest + // we are safe, UmbracoApplicationUrl has been initialized + var serverAddress = e.UmbracoContext.Application.UmbracoApplicationUrl; + try { - if (url == null) - throw new Exception("Request.Url is null."); - - var serverAddress = url.GetLeftPart(UriPartial.Authority); - var serverIdentity = JsonConvert.SerializeObject(new - { - machineName = NetworkHelper.MachineName, - appDomainAppId = HttpRuntime.AppDomainAppId - }); - - svc.TouchServer(serverAddress, serverIdentity, options.StaleServerTimeout); + svc.TouchServer(serverAddress, svc.CurrentServerIdentity, _registrar.Options.StaleServerTimeout); } catch (Exception ex) { diff --git a/src/Umbraco.Web/TagQuery.cs b/src/Umbraco.Web/TagQuery.cs index ded4dea66e..807a0699cb 100644 --- a/src/Umbraco.Web/TagQuery.cs +++ b/src/Umbraco.Web/TagQuery.cs @@ -13,6 +13,11 @@ namespace Umbraco.Web /// public class TagQuery : ITagQuery { + + //TODO: This class also acts as a wrapper for ITagQuery due to breaking changes, need to fix in + // version 8: http://issues.umbraco.org/issue/U4-6899 + private readonly ITagQuery _wrappedQuery; + private readonly ITagService _tagService; private readonly ITypedPublishedContentQuery _typedContentQuery; @@ -22,7 +27,7 @@ namespace Umbraco.Web { } - [Obsolete("Use the alternate constructor specifying the ITypedPublishedContentQuery instead")] + [Obsolete("Use the alternate constructor specifying the ITypedPublishedContentQuery instead")] public TagQuery(ITagService tagService, PublishedContentQuery contentQuery) { if (tagService == null) throw new ArgumentNullException("tagService"); @@ -31,6 +36,16 @@ namespace Umbraco.Web _typedContentQuery = contentQuery; } + /// + /// Constructor for wrapping ITagQuery, see http://issues.umbraco.org/issue/U4-6899 + /// + /// + internal TagQuery(ITagQuery wrappedQuery) + { + if (wrappedQuery == null) throw new ArgumentNullException("wrappedQuery"); + _wrappedQuery = wrappedQuery; + } + /// /// Constructor /// @@ -52,6 +67,9 @@ namespace Umbraco.Web /// public IEnumerable GetContentByTag(string tag, string tagGroup = null) { + //TODO: http://issues.umbraco.org/issue/U4-6899 + if (_wrappedQuery != null) return _wrappedQuery.GetContentByTag(tag, tagGroup); + var ids = _tagService.GetTaggedContentByTag(tag, tagGroup) .Select(x => x.EntityId); return _typedContentQuery.TypedContent(ids) @@ -65,6 +83,9 @@ namespace Umbraco.Web /// public IEnumerable GetContentByTagGroup(string tagGroup) { + //TODO: http://issues.umbraco.org/issue/U4-6899 + if (_wrappedQuery != null) return _wrappedQuery.GetContentByTagGroup(tagGroup); + var ids = _tagService.GetTaggedContentByTagGroup(tagGroup) .Select(x => x.EntityId); return _typedContentQuery.TypedContent(ids) @@ -79,6 +100,9 @@ namespace Umbraco.Web /// public IEnumerable GetMediaByTag(string tag, string tagGroup = null) { + //TODO: http://issues.umbraco.org/issue/U4-6899 + if (_wrappedQuery != null) return _wrappedQuery.GetMediaByTag(tag, tagGroup); + var ids = _tagService.GetTaggedMediaByTag(tag, tagGroup) .Select(x => x.EntityId); return _typedContentQuery.TypedMedia(ids) @@ -92,6 +116,9 @@ namespace Umbraco.Web /// public IEnumerable GetMediaByTagGroup(string tagGroup) { + //TODO: http://issues.umbraco.org/issue/U4-6899 + if (_wrappedQuery != null) return _wrappedQuery.GetMediaByTagGroup(tagGroup); + var ids = _tagService.GetTaggedMediaByTagGroup(tagGroup) .Select(x => x.EntityId); return _typedContentQuery.TypedMedia(ids) @@ -113,6 +140,9 @@ namespace Umbraco.Web /// public IEnumerable GetAllTags(string group = null) { + //TODO: http://issues.umbraco.org/issue/U4-6899 + if (_wrappedQuery != null) return _wrappedQuery.GetAllTags(group); + return Mapper.Map>(_tagService.GetAllTags(group)); } @@ -123,6 +153,9 @@ namespace Umbraco.Web /// public IEnumerable GetAllContentTags(string group = null) { + //TODO: http://issues.umbraco.org/issue/U4-6899 + if (_wrappedQuery != null) return _wrappedQuery.GetAllContentTags(group); + return Mapper.Map>(_tagService.GetAllContentTags(group)); } @@ -133,6 +166,9 @@ namespace Umbraco.Web /// public IEnumerable GetAllMediaTags(string group = null) { + //TODO: http://issues.umbraco.org/issue/U4-6899 + if (_wrappedQuery != null) return _wrappedQuery.GetAllMediaTags(group); + return Mapper.Map>(_tagService.GetAllMediaTags(group)); } @@ -143,6 +179,9 @@ namespace Umbraco.Web /// public IEnumerable GetAllMemberTags(string group = null) { + //TODO: http://issues.umbraco.org/issue/U4-6899 + if (_wrappedQuery != null) return _wrappedQuery.GetAllMemberTags(group); + return Mapper.Map>(_tagService.GetAllMemberTags(group)); } @@ -155,6 +194,9 @@ namespace Umbraco.Web /// public IEnumerable GetTagsForProperty(int contentId, string propertyTypeAlias, string tagGroup = null) { + //TODO: http://issues.umbraco.org/issue/U4-6899 + if (_wrappedQuery != null) return _wrappedQuery.GetTagsForProperty(contentId, propertyTypeAlias, tagGroup); + return Mapper.Map>(_tagService.GetTagsForProperty(contentId, propertyTypeAlias, tagGroup)); } @@ -166,6 +208,9 @@ namespace Umbraco.Web /// public IEnumerable GetTagsForEntity(int contentId, string tagGroup = null) { + //TODO: http://issues.umbraco.org/issue/U4-6899 + if (_wrappedQuery != null) return _wrappedQuery.GetTagsForEntity(contentId, tagGroup); + return Mapper.Map>(_tagService.GetTagsForEntity(contentId, tagGroup)); } } diff --git a/src/Umbraco.Web/Templates/TemplateRenderer.cs b/src/Umbraco.Web/Templates/TemplateRenderer.cs index 22d6b5b54b..d7d331d887 100644 --- a/src/Umbraco.Web/Templates/TemplateRenderer.cs +++ b/src/Umbraco.Web/Templates/TemplateRenderer.cs @@ -81,9 +81,9 @@ namespace Umbraco.Web.Templates //set the doc that was found by id contentRequest.PublishedContent = doc; //set the template, either based on the AltTemplate found or the standard template of the doc - contentRequest.TemplateModel = UmbracoConfig.For.UmbracoSettings().WebRouting.DisableAlternativeTemplates || AltTemplate.HasValue == false - ? ApplicationContext.Current.Services.FileService.GetTemplate(doc.TemplateId) - : ApplicationContext.Current.Services.FileService.GetTemplate(AltTemplate.Value); + contentRequest.TemplateModel = UmbracoConfig.For.UmbracoSettings().WebRouting.DisableAlternativeTemplates || AltTemplate.HasValue == false + ? _umbracoContext.Application.Services.FileService.GetTemplate(doc.TemplateId) + : _umbracoContext.Application.Services.FileService.GetTemplate(AltTemplate.Value); //if there is not template then exit if (!contentRequest.HasTemplate) @@ -103,14 +103,20 @@ namespace Umbraco.Web.Templates //after this page has rendered. SaveExistingItems(); - //set the new items on context objects for this templates execution - SetNewItemsOnContextObjects(contentRequest); + try + { + //set the new items on context objects for this templates execution + SetNewItemsOnContextObjects(contentRequest); - //Render the template - ExecuteTemplateRendering(writer, contentRequest); - - //restore items on context objects to continuing rendering the parent template - RestoreItems(); + //Render the template + ExecuteTemplateRendering(writer, contentRequest); + } + finally + { + //restore items on context objects to continuing rendering the parent template + RestoreItems(); + } + } private void ExecuteTemplateRendering(TextWriter sw, PublishedContentRequest contentRequest) diff --git a/src/Umbraco.Web/Trees/ContentTreeController.cs b/src/Umbraco.Web/Trees/ContentTreeController.cs index 50267f1d05..9f33a44ea9 100644 --- a/src/Umbraco.Web/Trees/ContentTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTreeController.cs @@ -32,7 +32,7 @@ namespace Umbraco.Web.Trees Constants.Applications.Developer, Constants.Applications.Members)] [LegacyBaseTree(typeof(loadContent))] - [Tree(Constants.Applications.Content, Constants.Trees.Content, "Content")] + [Tree(Constants.Applications.Content, Constants.Trees.Content)] [PluginController("UmbracoTrees")] [CoreTree] public class ContentTreeController : ContentTreeControllerBase diff --git a/src/Umbraco.Web/Trees/ContentTypeTreeController.cs b/src/Umbraco.Web/Trees/ContentTypeTreeController.cs new file mode 100644 index 0000000000..43cf60a178 --- /dev/null +++ b/src/Umbraco.Web/Trees/ContentTypeTreeController.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http.Formatting; +using System.Text; +using System.Threading.Tasks; +using umbraco; +using umbraco.BusinessLogic.Actions; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Models.EntityBase; +using Umbraco.Web.Models.Trees; +using Umbraco.Web.WebApi.Filters; +using Umbraco.Core.Services; + +namespace Umbraco.Web.Trees +{ + [UmbracoTreeAuthorize(Constants.Trees.DocumentTypes)] + [Tree(Constants.Applications.Settings, Constants.Trees.DocumentTypes, null, sortOrder: 6)] + [Mvc.PluginController("UmbracoTrees")] + [CoreTree] + [LegacyBaseTree(typeof(loadNodeTypes))] + public class ContentTypeTreeController : TreeController + { + protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) + { + var intId = id.TryConvertTo(); + if (intId == false) throw new InvalidOperationException("Id must be an integer"); + + var nodes = new TreeNodeCollection(); + + nodes.AddRange( + Services.EntityService.GetChildren(intId.Result, UmbracoObjectTypes.DocumentTypeContainer) + .OrderBy(entity => entity.Name) + .Select(dt => + { + var node = CreateTreeNode(dt.Id.ToString(), id, queryStrings, dt.Name, "icon-folder", dt.HasChildren(), ""); + node.Path = dt.Path; + node.NodeType = "container"; + //TODO: This isn't the best way to ensure a noop process for clicking a node but it works for now. + node.AdditionalData["jsClickCallback"] = "javascript:void(0);"; + return node; + })); + + //if the request is for folders only then just return + if (queryStrings["foldersonly"].IsNullOrWhiteSpace() == false && queryStrings["foldersonly"] == "1") return nodes; + + nodes.AddRange( + Services.EntityService.GetChildren(intId.Result, UmbracoObjectTypes.DocumentType) + .OrderBy(entity => entity.Name) + .Select(dt => + { + var node = CreateTreeNode(dt.Id.ToString(), id, queryStrings, dt.Name, "icon-item-arrangement", + //NOTE: This is legacy now but we need to support upgrades. From 7.4+ we don't allow 'child' creations since + // this is an organiational thing and we do that with folders now. + dt.HasChildren()); + + node.Path = dt.Path; + return node; + })); + + return nodes; + } + + protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) + { + var menu = new MenuItemCollection(); + + if (id == Constants.System.Root.ToInvariantString()) + { + //set the default to create + menu.DefaultMenuAlias = ActionNew.Instance.Alias; + + // root actions + menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias))); + menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias))); + return menu; + } + + var container = Services.EntityService.Get(int.Parse(id), UmbracoObjectTypes.DocumentTypeContainer); + if (container != null) + { + //set the default to create + menu.DefaultMenuAlias = ActionNew.Instance.Alias; + + menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias))); + + if (container.HasChildren() == false) + { + //can delete doc type + menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionDelete.Instance.Alias))); + } + menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), hasSeparator: true); + } + else + { + menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionDelete.Instance.Alias))); + menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionMove.Instance.Alias)), hasSeparator: true); + } + + return menu; + } + } +} diff --git a/src/Umbraco.Web/Trees/DataTypeTreeController.cs b/src/Umbraco.Web/Trees/DataTypeTreeController.cs index fdc8b4c949..67da0cf355 100644 --- a/src/Umbraco.Web/Trees/DataTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/DataTypeTreeController.cs @@ -12,32 +12,60 @@ using Umbraco.Web.Mvc; using Umbraco.Web.WebApi.Filters; using umbraco; using umbraco.BusinessLogic.Actions; +using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Services; using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.Trees { [UmbracoTreeAuthorize(Constants.Trees.DataTypes)] - [Tree(Constants.Applications.Developer, Constants.Trees.DataTypes, "Data Types")] + [Tree(Constants.Applications.Developer, Constants.Trees.DataTypes)] [PluginController("UmbracoTrees")] [CoreTree] public class DataTypeTreeController : TreeController { protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) { - //we only support one tree level for data types - if (id != Constants.System.Root.ToInvariantString()) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } + var intId = id.TryConvertTo(); + if (intId == false) throw new InvalidOperationException("Id must be an integer"); + + var nodes = new TreeNodeCollection(); + //Folders first + nodes.AddRange( + Services.EntityService.GetChildren(intId.Result, UmbracoObjectTypes.DataTypeContainer) + .OrderBy(entity => entity.Name) + .Select(dt => + { + var node = CreateTreeNode(dt.Id.ToString(), id, queryStrings, dt.Name, "icon-folder", dt.HasChildren(), ""); + node.Path = dt.Path; + node.NodeType = "container"; + //TODO: This isn't the best way to ensure a noop process for clicking a node but it works for now. + node.AdditionalData["jsClickCallback"] = "javascript:void(0);"; + return node; + })); + + //if the request is for folders only then just return + if (queryStrings["foldersonly"].IsNullOrWhiteSpace() == false && queryStrings["foldersonly"] == "1") return nodes; + + //Normal nodes var sysIds = GetSystemIds(); - var collection = new TreeNodeCollection(); - collection.AddRange( - Services.DataTypeService.GetAllDataTypeDefinitions() - .OrderBy(x => x.Name) - .Select(dt => CreateTreeNode(id, queryStrings, sysIds, dt))); - return collection; + nodes.AddRange( + Services.EntityService.GetChildren(intId.Result, UmbracoObjectTypes.DataType) + .OrderBy(entity => entity.Name) + .Select(dt => + { + var node = CreateTreeNode(dt.Id.ToInvariantString(), id, queryStrings, dt.Name, "icon-autofill", false); + node.Path = dt.Path; + if (sysIds.Contains(dt.Id)) + { + node.Icon = "icon-thumbnail-list"; + } + return node; + })); + + return nodes; } private IEnumerable GetSystemIds() @@ -50,43 +78,47 @@ namespace Umbraco.Web.Trees }; return systemIds; } - - private TreeNode CreateTreeNode(string id, FormDataCollection queryStrings, IEnumerable systemIds, IDataTypeDefinition dt) - { - var node = CreateTreeNode( - dt.Id.ToInvariantString(), - id, - queryStrings, - dt.Name, - "icon-autofill", - false); - - if (systemIds.Contains(dt.Id)) - { - node.Icon = "icon-thumbnail-list"; - } - - return node; - } - + protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) { var menu = new MenuItemCollection(); if (id == Constants.System.Root.ToInvariantString()) { + //set the default to create + menu.DefaultMenuAlias = ActionNew.Instance.Alias; + // root actions - menu.Items.Add(ui.Text("actions", ActionNew.Instance.Alias)); + menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias))); menu.Items.Add(ui.Text("actions", ActionRefresh.Instance.Alias), true); return menu; } - var sysIds = GetSystemIds(); - - if (sysIds.Contains(int.Parse(id)) == false) + var container = Services.EntityService.Get(int.Parse(id), UmbracoObjectTypes.DataTypeContainer); + if (container != null) { - //only have delete for each node - menu.Items.Add(ui.Text("actions", ActionDelete.Instance.Alias)); + //set the default to create + menu.DefaultMenuAlias = ActionNew.Instance.Alias; + + menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias))); + + if (container.HasChildren() == false) + { + //can delete data type + menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionDelete.Instance.Alias))); + } + menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), hasSeparator: true); + } + else + { + var sysIds = GetSystemIds(); + + if (sysIds.Contains(int.Parse(id)) == false) + { + menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionDelete.Instance.Alias))); + } + + menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionMove.Instance.Alias)), hasSeparator: true); } return menu; diff --git a/src/Umbraco.Web/Trees/LanguageTreeController.cs b/src/Umbraco.Web/Trees/LanguageTreeController.cs index fefbf82307..5481b7d40c 100644 --- a/src/Umbraco.Web/Trees/LanguageTreeController.cs +++ b/src/Umbraco.Web/Trees/LanguageTreeController.cs @@ -17,7 +17,7 @@ namespace Umbraco.Web.Trees { [UmbracoTreeAuthorize(Constants.Trees.Languages)] [LegacyBaseTree(typeof(loadLanguages))] - [Tree(Constants.Applications.Settings, Constants.Trees.Languages, "Languages", sortOrder: 4)] + [Tree(Constants.Applications.Settings, Constants.Trees.Languages, null, sortOrder: 4)] [PluginController("UmbracoTrees")] [CoreTree] public class LanguageTreeController : TreeController @@ -47,7 +47,7 @@ namespace Umbraco.Web.Trees language.Id.ToString(CultureInfo.InvariantCulture), "-1", queryStrings, language.CultureInfo.DisplayName, "icon-flag-alt", false, //TODO: Rebuild the language editor in angular, then we dont need to have this at all (which is just a path to the legacy editor) "/" + queryStrings.GetValue("application") + "/framed/" + - Uri.EscapeDataString("/umbraco/settings/editLanguage.aspx?id=" + language.Id))); + Uri.EscapeDataString("settings/editLanguage.aspx?id=" + language.Id))); } } @@ -96,4 +96,4 @@ namespace Umbraco.Web.Trees return menu; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Trees/MediaTreeController.cs b/src/Umbraco.Web/Trees/MediaTreeController.cs index c553acbfc3..e945b932a3 100644 --- a/src/Umbraco.Web/Trees/MediaTreeController.cs +++ b/src/Umbraco.Web/Trees/MediaTreeController.cs @@ -25,7 +25,7 @@ namespace Umbraco.Web.Trees Constants.Applications.Settings, Constants.Applications.Developer, Constants.Applications.Members)] - [Tree(Constants.Applications.Media, Constants.Trees.Media, "Media")] + [Tree(Constants.Applications.Media, Constants.Trees.Media)] [PluginController("UmbracoTrees")] [CoreTree] public class MediaTreeController : ContentTreeControllerBase diff --git a/src/Umbraco.Web/Trees/MediaTypeTreeController.cs b/src/Umbraco.Web/Trees/MediaTypeTreeController.cs new file mode 100644 index 0000000000..a7da33898d --- /dev/null +++ b/src/Umbraco.Web/Trees/MediaTypeTreeController.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http.Formatting; +using System.Text; +using System.Threading.Tasks; +using umbraco; +using umbraco.BusinessLogic.Actions; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Models.EntityBase; +using Umbraco.Web.Models.Trees; +using Umbraco.Web.WebApi.Filters; +using Umbraco.Core.Services; + +namespace Umbraco.Web.Trees +{ + [UmbracoTreeAuthorize(Constants.Trees.MediaTypes)] + [Tree(Constants.Applications.Settings, Constants.Trees.MediaTypes, null, sortOrder:5)] + [Mvc.PluginController("UmbracoTrees")] + [CoreTree] + [LegacyBaseTree(typeof(loadMediaTypes))] + public class MediaTypeTreeController : TreeController + { + protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) + { + var intId = id.TryConvertTo(); + if (intId == false) throw new InvalidOperationException("Id must be an integer"); + + var nodes = new TreeNodeCollection(); + + nodes.AddRange( + Services.EntityService.GetChildren(intId.Result, UmbracoObjectTypes.MediaTypeContainer) + .OrderBy(entity => entity.Name) + .Select(dt => + { + var node = CreateTreeNode(dt.Id.ToString(), id, queryStrings, dt.Name, "icon-folder", dt.HasChildren(), ""); + node.Path = dt.Path; + node.NodeType = "container"; + //TODO: This isn't the best way to ensure a noop process for clicking a node but it works for now. + node.AdditionalData["jsClickCallback"] = "javascript:void(0);"; + return node; + })); + + //if the request is for folders only then just return + if (queryStrings["foldersonly"].IsNullOrWhiteSpace() == false && queryStrings["foldersonly"] == "1") return nodes; + + nodes.AddRange( + Services.EntityService.GetChildren(intId.Result, UmbracoObjectTypes.MediaType) + .OrderBy(entity => entity.Name) + .Select(dt => + { + var node = CreateTreeNode(dt.Id.ToString(), id, queryStrings, dt.Name, "icon-item-arrangement", false); + node.Path = dt.Path; + return node; + })); + + return nodes; + } + + protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) + { + var menu = new MenuItemCollection(); + + if (id == Constants.System.Root.ToInvariantString()) + { + //set the default to create + menu.DefaultMenuAlias = ActionNew.Instance.Alias; + + // root actions + menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias))); + menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias))); + return menu; + } + + var container = Services.EntityService.Get(int.Parse(id), UmbracoObjectTypes.MediaTypeContainer); + if (container != null) + { + //set the default to create + menu.DefaultMenuAlias = ActionNew.Instance.Alias; + + menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias))); + + if (container.HasChildren() == false) + { + //can delete doc type + menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionDelete.Instance.Alias))); + } + menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), hasSeparator: true); + } + else + { + menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionDelete.Instance.Alias))); + menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionMove.Instance.Alias)), hasSeparator: true); + } + + return menu; + } + } +} diff --git a/src/Umbraco.Web/Trees/MemberTreeController.cs b/src/Umbraco.Web/Trees/MemberTreeController.cs index d9859d9c50..f04e72becc 100644 --- a/src/Umbraco.Web/Trees/MemberTreeController.cs +++ b/src/Umbraco.Web/Trees/MemberTreeController.cs @@ -26,7 +26,7 @@ namespace Umbraco.Web.Trees Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members)] - [Tree(Constants.Applications.Members, Constants.Trees.Members, "Members")] + [Tree(Constants.Applications.Members, Constants.Trees.Members)] [PluginController("UmbracoTrees")] [CoreTree] public class MemberTreeController : TreeController @@ -121,7 +121,7 @@ namespace Umbraco.Web.Trees if (id == Constants.System.Root.ToInvariantString()) { nodes.Add( - CreateTreeNode(Constants.Conventions.MemberTypes.AllMembersListId, id, queryStrings, "All Members", "icon-users", false, + CreateTreeNode(Constants.Conventions.MemberTypes.AllMembersListId, id, queryStrings, ui.Text("member", "allMembers"), "icon-users", false, queryStrings.GetValue("application") + TreeAlias.EnsureStartsWith('/') + "/list/" + Constants.Conventions.MemberTypes.AllMembersListId)); if (_isUmbracoProvider) diff --git a/src/Umbraco.Web/Trees/MemberTypeTreeController.cs b/src/Umbraco.Web/Trees/MemberTypeTreeController.cs new file mode 100644 index 0000000000..d739c2a661 --- /dev/null +++ b/src/Umbraco.Web/Trees/MemberTypeTreeController.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http.Formatting; +using System.Text; +using System.Threading.Tasks; +using umbraco; +using umbraco.BusinessLogic.Actions; +using Umbraco.Core; +using Umbraco.Web.Models.Trees; +using Umbraco.Web.WebApi.Filters; + +namespace Umbraco.Web.Trees +{ + [UmbracoTreeAuthorize(Constants.Trees.MemberTypes)] + [Tree(Constants.Applications.Members, Constants.Trees.MemberTypes, null, sortOrder:2 )] + [Mvc.PluginController("UmbracoTrees")] + [CoreTree] + [LegacyBaseTree(typeof(loadMemberTypes))] + public class MemberTypeTreeController : TreeController + { + protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) + { + var nodes = new TreeNodeCollection(); + nodes.AddRange( + Services.MemberTypeService.GetAll() + .OrderBy(x => x.Name) + .Select(dt => CreateTreeNode(dt.Id.ToString(), id, queryStrings, dt.Name, "icon-item-arrangement", false))); + + return nodes; + } + + protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) + { + var menu = new MenuItemCollection(); + + if (id == Constants.System.Root.ToInvariantString()) + { + // root actions + menu.Items.Add(ui.Text("actions", ActionNew.Instance.Alias)); + menu.Items.Add(ui.Text("actions", ActionRefresh.Instance.Alias), true); + return menu; + } + else + { + //delete member type + menu.Items.Add(ui.Text("actions", ActionDelete.Instance.Alias)); + } + + return menu; + } + } +} diff --git a/src/Umbraco.Web/Trees/PartialViewMacrosTree.cs b/src/Umbraco.Web/Trees/PartialViewMacrosTree.cs index f5a9df97c2..dfc0636804 100644 --- a/src/Umbraco.Web/Trees/PartialViewMacrosTree.cs +++ b/src/Umbraco.Web/Trees/PartialViewMacrosTree.cs @@ -23,12 +23,10 @@ namespace Umbraco.Web.Trees public override void RenderJS(ref StringBuilder javascript) { - //NOTE: Notice the MacroPartials%2f string below, this is a URLEncoded string of "MacroPartials/" so that the editor knows - // to load the file from the correct location javascript.Append( @" function openMacroPartialView(id) { - UmbClientMgr.contentFrame('Settings/Views/EditView.aspx?treeType=partialViewMacros&file=MacroPartials%2f' + id); + UmbClientMgr.contentFrame('Settings/Views/EditView.aspx?treeType=partialViewMacros&file=' + id); } "); diff --git a/src/Umbraco.Web/Trees/PartialViewsTree.cs b/src/Umbraco.Web/Trees/PartialViewsTree.cs index 08bbd5aadd..ac937864ab 100644 --- a/src/Umbraco.Web/Trees/PartialViewsTree.cs +++ b/src/Umbraco.Web/Trees/PartialViewsTree.cs @@ -25,12 +25,10 @@ namespace Umbraco.Web.Trees public override void RenderJS(ref StringBuilder javascript) { - //NOTE: Notice the Partials%2f string below, this is a URLEncoded string of "Partials/" so that the editor knows - // to load the file from the correct location javascript.Append( @" function openPartialView(id) { - UmbClientMgr.contentFrame('Settings/Views/EditView.aspx?treeType=partialViews&file=Partials%2f' + id); + UmbClientMgr.contentFrame('Settings/Views/EditView.aspx?treeType=partialViews&file=' + id); } "); } diff --git a/src/Umbraco.Web/Trees/TemplatesTreeController.cs b/src/Umbraco.Web/Trees/TemplatesTreeController.cs index 9e4b6f71d8..61cff8f862 100644 --- a/src/Umbraco.Web/Trees/TemplatesTreeController.cs +++ b/src/Umbraco.Web/Trees/TemplatesTreeController.cs @@ -131,9 +131,9 @@ namespace Umbraco.Web.Trees return Services.FileService.DetermineTemplateRenderingEngine(template) == RenderingEngine.WebForms ? "/" + queryStrings.GetValue("application") + "/framed/" + - Uri.EscapeDataString("/umbraco/settings/editTemplate.aspx?templateID=" + template.Id) + Uri.EscapeDataString("settings/editTemplate.aspx?templateID=" + template.Id) : "/" + queryStrings.GetValue("application") + "/framed/" + - Uri.EscapeDataString("/umbraco/settings/Views/EditView.aspx?treeType=" + Constants.Trees.Templates + "&templateID=" + template.Id); + Uri.EscapeDataString("settings/Views/EditView.aspx?treeType=" + Constants.Trees.Templates + "&templateID=" + template.Id); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Trees/TreeAttribute.cs b/src/Umbraco.Web/Trees/TreeAttribute.cs index 72ae4044e9..e3e302fb69 100644 --- a/src/Umbraco.Web/Trees/TreeAttribute.cs +++ b/src/Umbraco.Web/Trees/TreeAttribute.cs @@ -8,6 +8,16 @@ namespace Umbraco.Web.Trees [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] public class TreeAttribute : Attribute { + /// + /// Initializes a new instance of the class. + /// + /// The app alias. + /// The alias. + public TreeAttribute(string appAlias, + string alias) : this(appAlias, alias, null) + { + } + /// /// Initializes a new instance of the class. /// @@ -35,6 +45,8 @@ namespace Umbraco.Web.Trees SortOrder = sortOrder; } + + public string ApplicationAlias { get; private set; } public string Alias { get; private set; } public string Title { get; private set; } diff --git a/src/Umbraco.Web/Trees/TreeController.cs b/src/Umbraco.Web/Trees/TreeController.cs index 14a25b6f02..aad0d8330d 100644 --- a/src/Umbraco.Web/Trees/TreeController.cs +++ b/src/Umbraco.Web/Trees/TreeController.cs @@ -1,10 +1,15 @@ using System; using System.Collections.Concurrent; +using System.Globalization; using System.Linq; using System.Net.Http.Formatting; +using System.Threading; +using System.Web.Security; using Umbraco.Core; +using Umbraco.Core.Models; using Umbraco.Web.Models.Trees; using Umbraco.Web.Mvc; +using Umbraco.Core.Services; namespace Umbraco.Web.Trees { @@ -37,7 +42,22 @@ namespace Umbraco.Web.Trees /// public override string RootNodeDisplayName { - get { return _attribute.Title; } + get + { + + //if title is defined, return that + if(string.IsNullOrEmpty(_attribute.Title) == false) + return _attribute.Title; + + + //try to look up a tree header matching the tree alias + var localizedLabel = Services.TextService.Localize("treeHeaders/" + _attribute.Alias); + if (string.IsNullOrEmpty(localizedLabel) == false) + return localizedLabel; + + //is returned to signal that a label was not found + return "[" + _attribute.Alias + "]"; + } } /// diff --git a/src/Umbraco.Web/UI/JavaScript/JsInitialize.js b/src/Umbraco.Web/UI/JavaScript/JsInitialize.js index 3ff9884ba7..9c4ea749b4 100644 --- a/src/Umbraco.Web/UI/JavaScript/JsInitialize.js +++ b/src/Umbraco.Web/UI/JavaScript/JsInitialize.js @@ -4,23 +4,20 @@ 'lib/underscore/underscore-min.js', 'lib/jquery-ui/jquery-ui.min.js', + 'lib/jquery-ui-touch-punch/jquery.ui.touch-punch.js', 'lib/angular/1.1.5/angular-cookies.min.js', 'lib/angular/1.1.5/angular-mobile.js', 'lib/angular/1.1.5/angular-sanitize.min.js', - + 'lib/angular/angular-ui-sortable.js', 'lib/angular-dynamic-locale/tmhDynamicLocale.min.js', - - 'lib/blueimp-load-image/load-image.all.min.js', - 'lib/jquery-file-upload/jquery.fileupload.js', - 'lib/jquery-file-upload/jquery.fileupload-process.js', - 'lib/jquery-file-upload/jquery.fileupload-image.js', - 'lib/jquery-file-upload/jquery.fileupload-angular.js', + 'lib/ng-file-upload/ng-file-upload.min.js', 'lib/bootstrap/js/bootstrap.2.3.2.min.js', - 'lib/umbraco/Extensions.js', + 'lib/bootstrap-tabdrop/bootstrap-tabdrop.js', + 'lib/umbraco/Extensions.js', 'lib/umbraco/NamespaceManager.js', 'lib/umbraco/LegacyUmbClientMgr.js', @@ -36,4 +33,4 @@ 'js/umbraco.controllers.js', 'js/routes.js', 'js/init.js' -] \ No newline at end of file +] diff --git a/src/Umbraco.Web/UI/LegacyDialogHandler.cs b/src/Umbraco.Web/UI/LegacyDialogHandler.cs index 5ae7d0c627..dfa7fbc153 100644 --- a/src/Umbraco.Web/UI/LegacyDialogHandler.cs +++ b/src/Umbraco.Web/UI/LegacyDialogHandler.cs @@ -193,10 +193,18 @@ namespace Umbraco.Web.UI typeInstance.Alias = text; // check for returning url - var returnUrlTask = typeInstance as LegacyDialogTask; - - returnUrlTask.AdditionalValues = additionalValues; - + ITaskReturnUrl returnUrlTask = typeInstance as LegacyDialogTask; + if (returnUrlTask != null) + { + // if castable to LegacyDialogTask: add in additionalValues + ((LegacyDialogTask) returnUrlTask).AdditionalValues = additionalValues; + } + else + { + // otherwise cast to returnUrl interface + returnUrlTask = typeInstance as ITaskReturnUrl; + } + typeInstance.Save(); return returnUrlTask != null diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 32de99196a..afd20a70fa 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -98,15 +98,16 @@ UmbracoExamine - False ..\packages\AutoMapper.3.0.0\lib\net40\AutoMapper.dll + True - + ..\packages\AutoMapper.3.0.0\lib\net40\AutoMapper.Net4.dll + True - - False - ..\packages\ClientDependency.1.8.3.1\lib\net45\ClientDependency.Core.dll + + ..\packages\ClientDependency.1.8.4\lib\net45\ClientDependency.Core.dll + True False @@ -115,13 +116,13 @@ ..\packages\dotless.1.4.1.0\lib\dotless.Core.dll - - False - ..\packages\Examine.0.1.63.0\lib\Examine.dll + + ..\packages\Examine.0.1.68.0\lib\Examine.dll + True - + False - ..\packages\HtmlAgilityPack.1.4.6\lib\Net40\HtmlAgilityPack.dll + ..\packages\HtmlAgilityPack.1.4.9\lib\Net45\HtmlAgilityPack.dll False @@ -133,11 +134,11 @@ False - ..\packages\Microsoft.AspNet.Identity.Core.2.2.0\lib\net45\Microsoft.AspNet.Identity.Core.dll + ..\packages\Microsoft.AspNet.Identity.Core.2.2.1\lib\net45\Microsoft.AspNet.Identity.Core.dll False - ..\packages\Microsoft.AspNet.Identity.Owin.2.2.0\lib\net45\Microsoft.AspNet.Identity.Owin.dll + ..\packages\Microsoft.AspNet.Identity.Owin.2.2.1\lib\net45\Microsoft.AspNet.Identity.Owin.dll @@ -164,22 +165,22 @@ True ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - - False - ..\packages\Microsoft.AspNet.Mvc.FixedDisplayModes.1.0.1\lib\net40\Microsoft.Web.Mvc.FixedDisplayModes.dll - False ..\packages\MiniProfiler.2.1.0\lib\net40\MiniProfiler.dll - False - ..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll + ..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll + True False ..\packages\Owin.1.0\lib\net40\Owin.dll + + False + ..\packages\semver.1.1.2\lib\net45\Semver.dll + System @@ -195,17 +196,10 @@ - + + False - False - - ..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll - True - - - False - False @@ -218,40 +212,40 @@ - + + ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.Helpers.dll True - ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.Helpers.dll - - False + ..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll + True False ..\packages\Microsoft.AspNet.WebApi.WebHost.5.2.3\lib\net45\System.Web.Http.WebHost.dll - + + ..\packages\Microsoft.AspNet.Mvc.5.2.3\lib\net45\System.Web.Mvc.dll True - ..\packages\Microsoft.AspNet.Mvc.4.0.40804.0\lib\net40\System.Web.Mvc.dll - + + ..\packages\Microsoft.AspNet.Razor.3.2.3\lib\net45\System.Web.Razor.dll True - ..\packages\Microsoft.AspNet.Razor.2.0.30506.0\lib\net40\System.Web.Razor.dll System.Web.Services - + + ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.dll True - ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.WebPages.dll - + + ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Deployment.dll True - ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.WebPages.Deployment.dll - + + ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Razor.dll True - ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.WebPages.Razor.dll @@ -299,11 +293,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -322,10 +352,10 @@ - + - + @@ -552,9 +582,7 @@ - - @@ -563,7 +591,6 @@ - @@ -621,6 +648,7 @@ + @@ -807,7 +835,6 @@ ASPXCodeBehind - ASPXCodeBehind @@ -870,6 +897,8 @@ + + @@ -892,7 +921,6 @@ - @@ -993,9 +1021,6 @@ ASPXCodeBehind - - ASPXCodeBehind - @@ -1440,12 +1465,6 @@ EditMemberGroup.aspx - - EditMemberType.aspx - - - EditMemberType.aspx - search.aspx ASPXCodeBehind @@ -1481,13 +1500,6 @@ editLanguage.aspx - - EditMediaType.aspx - ASPXCodeBehind - - - EditMediaType.aspx - editScript.aspx ASPXCodeBehind @@ -1610,10 +1622,6 @@ - - webService.asmx - Component - CacheRefresher.asmx @@ -1829,9 +1837,6 @@ Form - - ASPXCodeBehind - Designer @@ -1839,14 +1844,10 @@ ASPXCodeBehind - - ASPXCodeBehind - ASPXCodeBehind - Form diff --git a/src/Umbraco.Web/Umbraco.Web.csproj.DotSettings b/src/Umbraco.Web/Umbraco.Web.csproj.DotSettings new file mode 100644 index 0000000000..662f95686e --- /dev/null +++ b/src/Umbraco.Web/Umbraco.Web.csproj.DotSettings @@ -0,0 +1,2 @@ + + CSharp50 \ No newline at end of file diff --git a/src/Umbraco.Web/UmbracoApplication.cs b/src/Umbraco.Web/UmbracoApplication.cs index 6fa4b91691..0f6fc03b80 100644 --- a/src/Umbraco.Web/UmbracoApplication.cs +++ b/src/Umbraco.Web/UmbracoApplication.cs @@ -23,6 +23,32 @@ namespace Umbraco.Web /// public class UmbracoApplication : UmbracoApplicationBase { + private ManifestWatcher _mw; + + protected override void OnApplicationStarted(object sender, EventArgs e) + { + base.OnApplicationStarted(sender, e); + + if (ApplicationContext.Current.IsConfigured && GlobalSettings.DebugMode) + { + var appPluginFolder = IOHelper.MapPath("~/App_Plugins/"); + if (Directory.Exists(appPluginFolder)) + { + _mw = new ManifestWatcher(LoggerResolver.Current.Logger); + _mw.Start(Directory.GetDirectories(IOHelper.MapPath("~/App_Plugins/"))); + } + } + } + + protected override void OnApplicationEnd(object sender, EventArgs e) + { + base.OnApplicationEnd(sender, e); + + if (_mw != null) + { + _mw.Dispose(); + } + } protected override IBootManager GetBootManager() { diff --git a/src/Umbraco.Web/UmbracoContext.cs b/src/Umbraco.Web/UmbracoContext.cs index fa719aadbf..3c2b7267a6 100644 --- a/src/Umbraco.Web/UmbracoContext.cs +++ b/src/Umbraco.Web/UmbracoContext.cs @@ -353,7 +353,7 @@ namespace Umbraco.Web /// /// Gets/sets the RoutingContext object /// - internal RoutingContext RoutingContext { get; set; } + public RoutingContext RoutingContext { get; internal set; } /// /// Gets/sets the PublishedContentRequest object @@ -462,8 +462,6 @@ namespace Umbraco.Web Security.DisposeIfDisposable(); Security = null; _umbracoContext = null; - //ensure not to dispose this! - Application = null; //Before we set these to null but in fact these are application lifespan singletons so //there's no reason we need to set them to null and this also caused a problem with packages @@ -471,7 +469,8 @@ namespace Umbraco.Web //http://issues.umbraco.org/issue/U4-2734 //http://our.umbraco.org/projects/developer-tools/301-url-tracker/version-2/44327-Issues-with-URL-Tracker-in-614 //ContentCache = null; - //MediaCache = null; + //MediaCache = null; + //Application = null; } } } \ No newline at end of file diff --git a/src/Umbraco.Web/UmbracoContextExtensions.cs b/src/Umbraco.Web/UmbracoContextExtensions.cs index f75487ac66..ec85b4dad6 100644 --- a/src/Umbraco.Web/UmbracoContextExtensions.cs +++ b/src/Umbraco.Web/UmbracoContextExtensions.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using Umbraco.Core.Events; namespace Umbraco.Web { @@ -10,20 +11,18 @@ namespace Umbraco.Web /// public static class UmbracoContextExtensions { + /// - /// Informs the context that content has changed. + /// If there are event messages in the current request this will return them , otherwise it will return null /// - /// The context. - /// - /// The contextual caches may, although that is not mandatory, provide an immutable snapshot of - /// the content over the duration of the context. If you make changes to the content and do want to have - /// the caches update their snapshot, you have to explicitely ask them to do so by calling ContentHasChanged. - /// The context informs the contextual caches that content has changed. - /// - public static void ContentHasChanged(this UmbracoContext context) + /// + /// + public static EventMessages GetCurrentEventMessages(this UmbracoContext umbracoContext) { - context.ContentCache.ContentHasChanged(); - context.MediaCache.ContentHasChanged(); + var msgs = umbracoContext.HttpContext.Items[typeof (RequestLifespanMessagesFactory).Name]; + if (msgs == null) return null; + return (EventMessages) msgs; } + } } diff --git a/src/Umbraco.Web/UmbracoDefaultOwinStartup.cs b/src/Umbraco.Web/UmbracoDefaultOwinStartup.cs index 4415016c9d..b810d515d2 100644 --- a/src/Umbraco.Web/UmbracoDefaultOwinStartup.cs +++ b/src/Umbraco.Web/UmbracoDefaultOwinStartup.cs @@ -21,6 +21,8 @@ namespace Umbraco.Web { public virtual void Configuration(IAppBuilder app) { + app.SanitizeThreadCulture(); + app.SetUmbracoLoggerFactory(); //Configure the Identity user manager for use with Umbraco Back office @@ -32,8 +34,8 @@ namespace Umbraco.Web //Ensure owin is configured for Umbraco back office authentication. If you have any front-end OWIN // cookie configuration, this must be declared after it. app - .UseUmbracoBackOfficeCookieAuthentication() - .UseUmbracoBackOfficeExternalCookieAuthentication(); + .UseUmbracoBackOfficeCookieAuthentication(ApplicationContext.Current) + .UseUmbracoBackOfficeExternalCookieAuthentication(ApplicationContext.Current); } } } diff --git a/src/Umbraco.Web/UmbracoHelper.cs b/src/Umbraco.Web/UmbracoHelper.cs index 383897b2f2..6d5e33b239 100644 --- a/src/Umbraco.Web/UmbracoHelper.cs +++ b/src/Umbraco.Web/UmbracoHelper.cs @@ -14,6 +14,9 @@ using Umbraco.Core.Xml; using Umbraco.Web.Routing; using Umbraco.Web.Security; using System.Collections.Generic; +using System.IO; +using System.Web.Mvc; +using System.Web.Routing; using Umbraco.Core.Cache; namespace Umbraco.Web @@ -26,14 +29,13 @@ namespace Umbraco.Web private readonly UmbracoContext _umbracoContext; private readonly IPublishedContent _currentPage; private readonly ITypedPublishedContentQuery _typedQuery; - private readonly IDynamicPublishedContentQuery _dynamicQuery; - + private readonly IDynamicPublishedContentQuery _dynamicQuery; private readonly HtmlStringUtilities _stringUtilities = new HtmlStringUtilities(); private IUmbracoComponentRenderer _componentRenderer; private PublishedContentQuery _query; private MembershipHelper _membershipHelper; - private ITagQuery _tag; + private TagQuery _tag; private IDataTypeService _dataTypeService; private UrlProvider _urlProvider; private ICultureDictionary _cultureDictionary; @@ -41,9 +43,18 @@ namespace Umbraco.Web /// /// Lazy instantiates the tag context /// - public ITagQuery TagQuery + public TagQuery TagQuery { - get { return _tag ?? (_tag = new TagQuery(UmbracoContext.Application.Services.TagService, _typedQuery)); } + //TODO: Unfortunately we cannot change this return value to be ITagQuery + // since it's a breaking change, need to fix it for v8 + // http://issues.umbraco.org/issue/U4-6899 + + get + { + return _tag ?? + (_tag = new TagQuery(UmbracoContext.Application.Services.TagService, + _typedQuery ?? ContentQuery)); + } } /// @@ -155,7 +166,7 @@ namespace Umbraco.Web if (membershipHelper == null) throw new ArgumentNullException("membershipHelper"); _umbracoContext = umbracoContext; - _tag = tagQuery; + _tag = new TagQuery(tagQuery); _dataTypeService = dataTypeService; _urlProvider = urlProvider; _cultureDictionary = cultureDictionary; @@ -313,7 +324,7 @@ namespace Umbraco.Web bool formatAsDateWithTime = false, string formatAsDateWithTimeSeparator = "") { - return _componentRenderer.Field(AssignedContentItem, fieldAlias, altFieldAlias, + return UmbracoComponentRenderer.Field(AssignedContentItem, fieldAlias, altFieldAlias, altText, insertBefore, insertAfter, recursive, convertLineBreaks, removeParagraphTags, casing, encoding, formatAsDate, formatAsDateWithTime, formatAsDateWithTimeSeparator); } @@ -346,7 +357,7 @@ namespace Umbraco.Web bool formatAsDateWithTime = false, string formatAsDateWithTimeSeparator = "") { - return _componentRenderer.Field(currentPage, fieldAlias, altFieldAlias, + return UmbracoComponentRenderer.Field(currentPage, fieldAlias, altFieldAlias, altText, insertBefore, insertAfter, recursive, convertLineBreaks, removeParagraphTags, casing, encoding, formatAsDate, formatAsDateWithTime, formatAsDateWithTimeSeparator); } @@ -362,14 +373,25 @@ namespace Umbraco.Web /// public string GetDictionaryValue(string key) { - if (_cultureDictionary == null) - { - var factory = CultureDictionaryFactoryResolver.Current.Factory; - _cultureDictionary = factory.CreateDictionary(); - } - return _cultureDictionary[key]; + return CultureDictionary[key]; } + /// + /// Returns the ICultureDictionary for access to dictionary items + /// + public ICultureDictionary CultureDictionary + { + get + { + if (_cultureDictionary == null) + { + var factory = CultureDictionaryFactoryResolver.Current.Factory; + _cultureDictionary = factory.CreateDictionary(); + } + return _cultureDictionary; + } + } + #endregion #region Membership @@ -565,27 +587,57 @@ namespace Umbraco.Web return ContentQuery.TypedContent(ConvertIdsObjectToInts(ids)); } + /// + /// Gets the contents corresponding to the identifiers. + /// + /// The content identifiers. + /// The existing contents corresponding to the identifiers. + /// If an identifier does not match an existing content, it will be missing in the returned value. public IEnumerable TypedContent(params int[] ids) { return ContentQuery.TypedContent(ids); } - public IEnumerable TypedContent(params string[] ids) + /// + /// Gets the contents corresponding to the identifiers. + /// + /// The content identifiers. + /// The existing contents corresponding to the identifiers. + /// If an identifier does not match an existing content, it will be missing in the returned value. + public IEnumerable TypedContent(params string[] ids) { return ContentQuery.TypedContent(ConvertIdsObjectToInts(ids)); } - public IEnumerable TypedContent(IEnumerable ids) + /// + /// Gets the contents corresponding to the identifiers. + /// + /// The content identifiers. + /// The existing contents corresponding to the identifiers. + /// If an identifier does not match an existing content, it will be missing in the returned value. + public IEnumerable TypedContent(IEnumerable ids) { return ContentQuery.TypedContent(ConvertIdsObjectToInts(ids)); } - public IEnumerable TypedContent(IEnumerable ids) + /// + /// Gets the contents corresponding to the identifiers. + /// + /// The content identifiers. + /// The existing contents corresponding to the identifiers. + /// If an identifier does not match an existing content, it will be missing in the returned value. + public IEnumerable TypedContent(IEnumerable ids) { return ContentQuery.TypedContent(ConvertIdsObjectToInts(ids)); } - public IEnumerable TypedContent(IEnumerable ids) + /// + /// Gets the contents corresponding to the identifiers. + /// + /// The content identifiers. + /// The existing contents corresponding to the identifiers. + /// If an identifier does not match an existing content, it will be missing in the returned value. + public IEnumerable TypedContent(IEnumerable ids) { return ContentQuery.TypedContent(ids); } @@ -632,32 +684,68 @@ namespace Umbraco.Web return ContentQuery.ContentSingleAtXPath(xpath, vars); } + /// + /// Gets the contents corresponding to the identifiers. + /// + /// The content identifiers. + /// The existing contents corresponding to the identifiers. + /// If an identifier does not match an existing content, it will be missing in the returned value. public dynamic Content(params object[] ids) { return ContentQuery.Content(ConvertIdsObjectToInts(ids)); } - public dynamic Content(params int[] ids) + /// + /// Gets the contents corresponding to the identifiers. + /// + /// The content identifiers. + /// The existing contents corresponding to the identifiers. + /// If an identifier does not match an existing content, it will be missing in the returned value. + public dynamic Content(params int[] ids) { return ContentQuery.Content(ids); } - public dynamic Content(params string[] ids) + /// + /// Gets the contents corresponding to the identifiers. + /// + /// The content identifiers. + /// The existing contents corresponding to the identifiers. + /// If an identifier does not match an existing content, it will be missing in the returned value. + public dynamic Content(params string[] ids) { return ContentQuery.Content(ConvertIdsObjectToInts(ids)); } - public dynamic Content(IEnumerable ids) + /// + /// Gets the contents corresponding to the identifiers. + /// + /// The content identifiers. + /// The existing contents corresponding to the identifiers. + /// If an identifier does not match an existing content, it will be missing in the returned value. + public dynamic Content(IEnumerable ids) { return ContentQuery.Content(ConvertIdsObjectToInts(ids)); } - public dynamic Content(IEnumerable ids) + /// + /// Gets the contents corresponding to the identifiers. + /// + /// The content identifiers. + /// The existing contents corresponding to the identifiers. + /// If an identifier does not match an existing content, it will be missing in the returned value. + public dynamic Content(IEnumerable ids) { return ContentQuery.Content(ids); } - public dynamic Content(IEnumerable ids) + /// + /// Gets the contents corresponding to the identifiers. + /// + /// The content identifiers. + /// The existing contents corresponding to the identifiers. + /// If an identifier does not match an existing content, it will be missing in the returned value. + public dynamic Content(IEnumerable ids) { return ContentQuery.Content(ConvertIdsObjectToInts(ids)); } @@ -739,32 +827,68 @@ namespace Umbraco.Web return ConvertIdObjectToInt(id, out intId) ? ContentQuery.TypedMedia(intId) : null; } - public IEnumerable TypedMedia(params object[] ids) + /// + /// Gets the medias corresponding to the identifiers. + /// + /// The media identifiers. + /// The existing medias corresponding to the identifiers. + /// If an identifier does not match an existing media, it will be missing in the returned value. + public IEnumerable TypedMedia(params object[] ids) { return ContentQuery.TypedMedia(ConvertIdsObjectToInts(ids)); } - public IEnumerable TypedMedia(params int[] ids) + /// + /// Gets the medias corresponding to the identifiers. + /// + /// The media identifiers. + /// The existing medias corresponding to the identifiers. + /// If an identifier does not match an existing media, it will be missing in the returned value. + public IEnumerable TypedMedia(params int[] ids) { return ContentQuery.TypedMedia(ids); } - public IEnumerable TypedMedia(params string[] ids) + /// + /// Gets the medias corresponding to the identifiers. + /// + /// The media identifiers. + /// The existing medias corresponding to the identifiers. + /// If an identifier does not match an existing media, it will be missing in the returned value. + public IEnumerable TypedMedia(params string[] ids) { return ContentQuery.TypedMedia(ConvertIdsObjectToInts(ids)); } - public IEnumerable TypedMedia(IEnumerable ids) + /// + /// Gets the medias corresponding to the identifiers. + /// + /// The media identifiers. + /// The existing medias corresponding to the identifiers. + /// If an identifier does not match an existing media, it will be missing in the returned value. + public IEnumerable TypedMedia(IEnumerable ids) { return ContentQuery.TypedMedia(ConvertIdsObjectToInts(ids)); } - public IEnumerable TypedMedia(IEnumerable ids) + /// + /// Gets the medias corresponding to the identifiers. + /// + /// The media identifiers. + /// The existing medias corresponding to the identifiers. + /// If an identifier does not match an existing media, it will be missing in the returned value. + public IEnumerable TypedMedia(IEnumerable ids) { return ContentQuery.TypedMedia(ids); } - public IEnumerable TypedMedia(IEnumerable ids) + /// + /// Gets the medias corresponding to the identifiers. + /// + /// The media identifiers. + /// The existing medias corresponding to the identifiers. + /// If an identifier does not match an existing media, it will be missing in the returned value. + public IEnumerable TypedMedia(IEnumerable ids) { return ContentQuery.TypedMedia(ConvertIdsObjectToInts(ids)); } @@ -791,32 +915,68 @@ namespace Umbraco.Web return ConvertIdObjectToInt(id, out intId) ? ContentQuery.Media(intId) : DynamicNull.Null; } - public dynamic Media(params object[] ids) + /// + /// Gets the medias corresponding to the identifiers. + /// + /// The media identifiers. + /// The existing medias corresponding to the identifiers. + /// If an identifier does not match an existing media, it will be missing in the returned value. + public dynamic Media(params object[] ids) { return ContentQuery.Media(ConvertIdsObjectToInts(ids)); } - public dynamic Media(params int[] ids) + /// + /// Gets the medias corresponding to the identifiers. + /// + /// The media identifiers. + /// The existing medias corresponding to the identifiers. + /// If an identifier does not match an existing media, it will be missing in the returned value. + public dynamic Media(params int[] ids) { return ContentQuery.Media(ids); } - public dynamic Media(params string[] ids) + /// + /// Gets the medias corresponding to the identifiers. + /// + /// The media identifiers. + /// The existing medias corresponding to the identifiers. + /// If an identifier does not match an existing media, it will be missing in the returned value. + public dynamic Media(params string[] ids) { return ContentQuery.Media(ConvertIdsObjectToInts(ids)); } - public dynamic Media(IEnumerable ids) + /// + /// Gets the medias corresponding to the identifiers. + /// + /// The media identifiers. + /// The existing medias corresponding to the identifiers. + /// If an identifier does not match an existing media, it will be missing in the returned value. + public dynamic Media(IEnumerable ids) { return ContentQuery.Media(ConvertIdsObjectToInts(ids)); } - public dynamic Media(IEnumerable ids) + /// + /// Gets the medias corresponding to the identifiers. + /// + /// The media identifiers. + /// The existing medias corresponding to the identifiers. + /// If an identifier does not match an existing media, it will be missing in the returned value. + public dynamic Media(IEnumerable ids) { return ContentQuery.Media(ids); } - public dynamic Media(IEnumerable ids) + /// + /// Gets the medias corresponding to the identifiers. + /// + /// The media identifiers. + /// The existing medias corresponding to the identifiers. + /// If an identifier does not match an existing media, it will be missing in the returned value. + public dynamic Media(IEnumerable ids) { return ContentQuery.Media(ConvertIdsObjectToInts(ids)); } @@ -1013,52 +1173,44 @@ namespace Umbraco.Web #region canvasdesigner - public HtmlString EnableCanvasDesigner() + [Obsolete("Use EnableCanvasDesigner on the HtmlHelper extensions instead")] + public IHtmlString EnableCanvasDesigner() { return EnableCanvasDesigner(string.Empty, string.Empty); } - public HtmlString EnableCanvasDesigner(string canvasdesignerConfigPath) + [Obsolete("Use EnableCanvasDesigner on the HtmlHelper extensions instead")] + public IHtmlString EnableCanvasDesigner(string canvasdesignerConfigPath) { return EnableCanvasDesigner(canvasdesignerConfigPath, string.Empty); } - public HtmlString EnableCanvasDesigner(string canvasdesignerConfigPath, string canvasdesignerPalettesPath) + [Obsolete("Use EnableCanvasDesigner on the HtmlHelper extensions instead")] + public IHtmlString EnableCanvasDesigner(string canvasdesignerConfigPath, string canvasdesignerPalettesPath) { + var html = CreateHtmlHelper(""); + var urlHelper = new UrlHelper(UmbracoContext.HttpContext.Request.RequestContext); + return html.EnableCanvasDesigner(urlHelper, UmbracoContext, canvasdesignerConfigPath, canvasdesignerPalettesPath); + } - string previewLink = @"" + - @"" + - @"" + - @"" + - @""; - - string noPreviewLinks = @""; - - // Get page value - int pageId = UmbracoContext.PublishedContentRequest.UmbracoPage.PageID; - string[] path = UmbracoContext.PublishedContentRequest.UmbracoPage.SplitPath; - string result = string.Empty; - string cssPath = CanvasDesignerUtility.GetStylesheetPath(path, false); - - if (UmbracoContext.Current.InPreviewMode) + [Obsolete("This shouldn't need to be used but because the obsolete extension methods above don't have access to the current HtmlHelper, we need to create a fake one, unfortunately however this will not pertain the current views viewdata, tempdata or model state so should not be used")] + private HtmlHelper CreateHtmlHelper(object model) + { + var cc = new ControllerContext { - canvasdesignerConfigPath = !string.IsNullOrEmpty(canvasdesignerConfigPath) ? canvasdesignerConfigPath : "/umbraco/js/canvasdesigner.config.js"; - canvasdesignerPalettesPath = !string.IsNullOrEmpty(canvasdesignerPalettesPath) ? canvasdesignerPalettesPath : "/umbraco/js/canvasdesigner.palettes.js"; - - if (!string.IsNullOrEmpty(cssPath)) - result = string.Format(noPreviewLinks, cssPath) + Environment.NewLine; + RequestContext = UmbracoContext.HttpContext.Request.RequestContext + }; + var viewContext = new ViewContext(cc, new FakeView(), new ViewDataDictionary(model), new TempDataDictionary(), new StringWriter()); + var htmlHelper = new HtmlHelper(viewContext, new ViewPage()); + return htmlHelper; + } - result = result + string.Format(previewLink, canvasdesignerConfigPath, canvasdesignerPalettesPath, pageId); - } - else + [Obsolete("This shouldn't need to be used but because the obsolete extension methods above don't have access to the current HtmlHelper, we need to create a fake one, unfortunately however this will not pertain the current views viewdata, tempdata or model state so should not be used")] + private class FakeView : IView + { + public void Render(ViewContext viewContext, TextWriter writer) { - // Get css path for current page - if (!string.IsNullOrEmpty(cssPath)) - result = string.Format(noPreviewLinks, cssPath); } - - return new HtmlString(result); - } #endregion diff --git a/src/Umbraco.Web/UmbracoModule.cs b/src/Umbraco.Web/UmbracoModule.cs index 185d149da5..c44a65652b 100644 --- a/src/Umbraco.Web/UmbracoModule.cs +++ b/src/Umbraco.Web/UmbracoModule.cs @@ -20,6 +20,7 @@ using Umbraco.Web.Editors; using Umbraco.Web.Routing; using Umbraco.Web.Security; using umbraco; +using Umbraco.Core.Sync; using GlobalSettings = Umbraco.Core.Configuration.GlobalSettings; using ObjectExtensions = Umbraco.Core.ObjectExtensions; using RenderingEngine = Umbraco.Core.RenderingEngine; @@ -42,31 +43,10 @@ namespace Umbraco.Web /// static void BeginRequest(HttpContextBase httpContext) { + // ensure application url is initialized + ApplicationUrlHelper.EnsureApplicationUrl(ApplicationContext.Current, httpContext.Request); - //we need to set the initial url in our ApplicationContext, this is so our keep alive service works and this must - //exist on a global context because the keep alive service doesn't run in a web context. - //we are NOT going to put a lock on this because locking will slow down the application and we don't really care - //if two threads write to this at the exact same time during first page hit. - //see: http://issues.umbraco.org/issue/U4-2059 - if (ApplicationContext.Current.OriginalRequestUrl.IsNullOrWhiteSpace()) - { - // If (HTTP and SSL not required) or (HTTPS and SSL required), use ports from request to configure OriginalRequestUrl. - // Otherwise, user may need to set baseUrl manually per http://our.umbraco.org/documentation/Using-Umbraco/Config-files/umbracoSettings/#ScheduledTasks if non-standard ports used. - if ((!httpContext.Request.IsSecureConnection && !GlobalSettings.UseSSL) || (httpContext.Request.IsSecureConnection && GlobalSettings.UseSSL)) - { - // Use port from request. - ApplicationContext.Current.OriginalRequestUrl = string.Format("{0}:{1}{2}", httpContext.Request.ServerVariables["SERVER_NAME"], httpContext.Request.ServerVariables["SERVER_PORT"], IOHelper.ResolveUrl(SystemDirectories.Umbraco)); - } - else - { - // Omit port entirely. - ApplicationContext.Current.OriginalRequestUrl = string.Format("{0}{1}", httpContext.Request.ServerVariables["SERVER_NAME"], IOHelper.ResolveUrl(SystemDirectories.Umbraco)); - } - - LogHelper.Info("Setting OriginalRequestUrl: " + ApplicationContext.Current.OriginalRequestUrl); - } - - // do not process if client-side request + // do not process if client-side request if (httpContext.Request.Url.IsClientSideRequest()) return; @@ -163,70 +143,7 @@ namespace Umbraco.Web #endregion #region Methods - - /// - /// Determines if we should authenticate the request - /// - /// - /// - /// - /// - /// We auth the request when: - /// * it is a back office request - /// * it is an installer request - /// * it is a /base request - /// * it is a preview request - /// - internal static bool ShouldAuthenticateRequest(HttpRequestBase request, Uri originalRequestUrl) - { - if (//check back office - request.Url.IsBackOfficeRequest(HttpRuntime.AppDomainAppVirtualPath) - //check installer - || request.Url.IsInstallerRequest() - //detect in preview - || (request.HasPreviewCookie() && request.Url != null && request.Url.AbsolutePath.StartsWith(IOHelper.ResolveUrl(SystemDirectories.Umbraco)) == false)) - { - return true; - } - return false; - } - - private static readonly ConcurrentHashSet IgnoreTicketRenewUrls = new ConcurrentHashSet(); - /// - /// Determines if the authentication ticket should be renewed with a new timeout - /// - /// - /// - /// - /// - /// We do not want to renew the ticket when we are checking for the user's remaining timeout unless - - /// UmbracoConfig.For.UmbracoSettings().Security.KeepUserLoggedIn == true - /// - internal static bool ShouldIgnoreTicketRenew(Uri url, HttpContextBase httpContext) - { - //this setting will renew the ticket for all requests. - if (UmbracoConfig.For.UmbracoSettings().Security.KeepUserLoggedIn) - { - return false; - } - - //initialize the ignore ticket urls - we don't need to lock this, it's concurrent and a hashset - // we don't want to have to gen the url each request so this will speed things up a teeny bit. - if (IgnoreTicketRenewUrls.Any() == false) - { - var urlHelper = new UrlHelper(new RequestContext(httpContext, new RouteData())); - var checkSessionUrl = urlHelper.GetUmbracoApiService(controller => controller.GetRemainingTimeoutSeconds()); - IgnoreTicketRenewUrls.Add(checkSessionUrl); - } - - if (IgnoreTicketRenewUrls.Any(x => url.AbsolutePath.StartsWith(x))) - { - return true; - } - - return false; - } - + /// /// Checks the current request and ensures that it is routable based on the structure of the request and URI /// @@ -364,15 +281,23 @@ namespace Umbraco.Web return false; } + private bool _notConfiguredReported; + // ensures Umbraco is configured // if not, redirect to install and return false // if yes, return true - private static bool EnsureIsConfigured(HttpContextBase httpContext, Uri uri) + private bool EnsureIsConfigured(HttpContextBase httpContext, Uri uri) { if (ApplicationContext.Current.IsConfigured) return true; - LogHelper.Warn("Umbraco is not configured"); + if (_notConfiguredReported) + { + // remember it's been reported so we don't flood the log + // no thread-safety so there may be a few log entries, doesn't matter + _notConfiguredReported = true; + LogHelper.Warn("Umbraco is not configured"); + } var installPath = UriUtility.ToAbsolute(SystemDirectories.Install); var installUrl = string.Format("{0}/?redir=true&url={1}", installPath, HttpUtility.UrlEncode(uri.ToString())); @@ -404,6 +329,9 @@ namespace Umbraco.Web { response.StatusCode = 404; response.TrySkipIisCustomErrors = UmbracoConfig.For.UmbracoSettings().WebRouting.TrySkipIisCustomErrors; + + if (response.TrySkipIisCustomErrors == false) + LogHelper.Warn("Status code is 404 yet TrySkipIisCustomErrors is false - IIS will take over."); } if (pcr.ResponseStatusCode > 0) @@ -545,6 +473,26 @@ namespace Umbraco.Web BeginRequest(new HttpContextWrapper(httpContext)); }; + //disable asp.net headers (security) + // This is the correct place to modify headers according to MS: + // https://our.umbraco.org/forum/umbraco-7/using-umbraco-7/65241-Heap-error-from-header-manipulation?p=0#comment220889 + app.PostReleaseRequestState += (sender, args) => + { + var httpContext = ((HttpApplication)sender).Context; + try + { + httpContext.Response.Headers.Remove("Server"); + //this doesn't normally work since IIS sets it but we'll keep it here anyways. + httpContext.Response.Headers.Remove("X-Powered-By"); + httpContext.Response.Headers.Remove("X-AspNet-Version"); + httpContext.Response.Headers.Remove("X-AspNetMvc-Version"); + } + catch (PlatformNotSupportedException ex) + { + // can't remove headers this way on IIS6 or cassini. + } + }; + app.PostResolveRequestCache += (sender, e) => { var httpContext = ((HttpApplication)sender).Context; @@ -565,21 +513,6 @@ namespace Umbraco.Web DisposeHttpContextItems(httpContext); }; - //disable asp.net headers (security) - app.PreSendRequestHeaders += (sender, args) => - { - var httpContext = ((HttpApplication)sender).Context; - try - { - httpContext.Response.Headers.Remove("Server"); - //this doesn't normally work since IIS sets it but we'll keep it here anyways. - httpContext.Response.Headers.Remove("X-Powered-By"); - } - catch (PlatformNotSupportedException ex) - { - // can't remove headers this way on IIS6 or cassini. - } - }; } public void Dispose() diff --git a/src/Umbraco.Web/UriUtility.cs b/src/Umbraco.Web/UriUtility.cs index 86a97e2857..8a1c55e758 100644 --- a/src/Umbraco.Web/UriUtility.cs +++ b/src/Umbraco.Web/UriUtility.cs @@ -51,7 +51,8 @@ namespace Umbraco.Web // see also VirtualPathUtility.ToAppRelative public static string ToAppRelative(string virtualPath) { - if (virtualPath.InvariantStartsWith(_appPathPrefix)) + if (virtualPath.InvariantStartsWith(_appPathPrefix) + && (virtualPath.Length == _appPathPrefix.Length || virtualPath[_appPathPrefix.Length] == '/')) virtualPath = virtualPath.Substring(_appPathPrefix.Length); if (virtualPath.Length == 0) virtualPath = "/"; diff --git a/src/Umbraco.Web/WebApi/Binders/ContentItemBaseBinder.cs b/src/Umbraco.Web/WebApi/Binders/ContentItemBaseBinder.cs index b5510ec85d..dda19f9ff9 100644 --- a/src/Umbraco.Web/WebApi/Binders/ContentItemBaseBinder.cs +++ b/src/Umbraco.Web/WebApi/Binders/ContentItemBaseBinder.cs @@ -162,7 +162,7 @@ namespace Umbraco.Web.WebApi.Binders //finally, let's lookup the real content item and create the DTO item model.PersistedContent = GetExisting(model); } - + //create the dto from the persisted model if (model.PersistedContent != null) { @@ -173,7 +173,9 @@ namespace Umbraco.Web.WebApi.Binders //now map all of the saved values to the dto MapPropertyValuesFromSaved(model, model.ContentDto); } - + + model.Name = model.Name.Trim(); + return model; } diff --git a/src/Umbraco.Web/WebApi/Filters/AngularAntiForgeryHelper.cs b/src/Umbraco.Web/WebApi/Filters/AngularAntiForgeryHelper.cs index d48cd66077..962183f7ef 100644 --- a/src/Umbraco.Web/WebApi/Filters/AngularAntiForgeryHelper.cs +++ b/src/Umbraco.Web/WebApi/Filters/AngularAntiForgeryHelper.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Collections.Specialized; using System.Linq; using System.Net.Http; using System.Net.Http.Headers; @@ -28,6 +30,8 @@ namespace Umbraco.Web.WebApi.Filters /// public const string AngularHeadername = "X-XSRF-TOKEN"; + + /// /// Returns 2 tokens - one for the cookie value and one that angular should set as the header value /// @@ -64,13 +68,10 @@ namespace Umbraco.Web.WebApi.Filters return true; } - /// - /// Validates the headers/cookies passed in for the request - /// - /// - /// - /// - public static bool ValidateHeaders(HttpRequestHeaders requestHeaders, out string failedReason) + internal static bool ValidateHeaders( + KeyValuePair>[] requestHeaders, + string cookieToken, + out string failedReason) { failedReason = ""; @@ -85,12 +86,7 @@ namespace Umbraco.Web.WebApi.Filters .Select(z => z.Value) .SelectMany(z => z) .FirstOrDefault(); - - var cookieToken = requestHeaders - .GetCookies() - .Select(c => c[CsrfValidationCookieName]) - .FirstOrDefault(); - + // both header and cookie must be there if (cookieToken == null || headerToken == null) { @@ -98,13 +94,32 @@ namespace Umbraco.Web.WebApi.Filters return false; } - if (ValidateTokens(cookieToken.Value, headerToken) == false) + if (ValidateTokens(cookieToken, headerToken) == false) { failedReason = "Invalid token"; return false; } - + return true; } + + /// + /// Validates the headers/cookies passed in for the request + /// + /// + /// + /// + public static bool ValidateHeaders(HttpRequestHeaders requestHeaders, out string failedReason) + { + var cookieToken = requestHeaders + .GetCookies() + .Select(c => c[CsrfValidationCookieName]) + .FirstOrDefault(); + + return ValidateHeaders( + requestHeaders.ToDictionary(x => x.Key, x => x.Value).ToArray(), + cookieToken == null ? null : cookieToken.Value, + out failedReason); + } } } \ No newline at end of file diff --git a/src/Umbraco.Web/WebApi/Filters/AppendCurrentEventMessagesAttribute.cs b/src/Umbraco.Web/WebApi/Filters/AppendCurrentEventMessagesAttribute.cs new file mode 100644 index 0000000000..8cf70826fe --- /dev/null +++ b/src/Umbraco.Web/WebApi/Filters/AppendCurrentEventMessagesAttribute.cs @@ -0,0 +1,65 @@ +using System; +using System.Net.Http; +using System.Web.Http.Filters; +using Umbraco.Core.Events; +using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web.UI; + +namespace Umbraco.Web.WebApi.Filters +{ + /// + /// Automatically checks if any request is a non-GET and if the + /// resulting message is INotificationModel in which case it will append any Event Messages + /// currently in the request. + /// + internal sealed class AppendCurrentEventMessagesAttribute : ActionFilterAttribute + { + public override void OnActionExecuted(HttpActionExecutedContext context) + { + if (context.Response == null) return; + if (context.Request.Method == HttpMethod.Get) return; + if (UmbracoContext.Current == null) return; + + var obj = context.Response.Content as ObjectContent; + if (obj == null) return; + + var notifications = obj.Value as INotificationModel; + if (notifications == null) return; + + var msgs = UmbracoContext.Current.GetCurrentEventMessages(); + if (msgs == null) return; + + foreach (var eventMessage in msgs.GetAll()) + { + SpeechBubbleIcon msgType; + switch (eventMessage.MessageType) + { + case EventMessageType.Default: + msgType = SpeechBubbleIcon.Save; + break; + case EventMessageType.Info: + msgType = SpeechBubbleIcon.Info; + break; + case EventMessageType.Error: + msgType = SpeechBubbleIcon.Error; + break; + case EventMessageType.Success: + msgType = SpeechBubbleIcon.Success; + break; + case EventMessageType.Warning: + msgType = SpeechBubbleIcon.Warning; + break; + default: + throw new ArgumentOutOfRangeException(); + } + + notifications.Notifications.Add(new Notification + { + Message = eventMessage.Message, + Header = eventMessage.Category, + NotificationType = msgType + }); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/WebApi/Filters/ContentItemValidationHelper.cs b/src/Umbraco.Web/WebApi/Filters/ContentItemValidationHelper.cs index 239b9bd576..88eda3c64e 100644 --- a/src/Umbraco.Web/WebApi/Filters/ContentItemValidationHelper.cs +++ b/src/Umbraco.Web/WebApi/Filters/ContentItemValidationHelper.cs @@ -152,10 +152,26 @@ namespace Umbraco.Web.WebApi.Filters if (p.ValidationRegExp.IsNullOrWhiteSpace() == false) { - foreach (var result in p.PropertyEditor.ValueEditor.RegexValidator.Validate(postedValue, p.ValidationRegExp, preValues, editor)) + + //We only want to execute the regex statement if: + // * the value is null or empty AND it is required OR + // * the value is not null or empty + //See: http://issues.umbraco.org/issue/U4-4669 + + var asString = postedValue as string; + + if ( + //Value is not null or empty + (postedValue != null && asString.IsNullOrWhiteSpace() == false) + //It's required + || (p.IsRequired)) { - actionContext.ModelState.AddPropertyError(result, p.Alias); + foreach (var result in p.PropertyEditor.ValueEditor.RegexValidator.Validate(postedValue, p.ValidationRegExp, preValues, editor)) + { + actionContext.ModelState.AddPropertyError(result, p.Alias); + } } + } } diff --git a/src/Umbraco.Web/WebApi/Filters/FileUploadCleanupFilterAttribute.cs b/src/Umbraco.Web/WebApi/Filters/FileUploadCleanupFilterAttribute.cs index f2dbfd7cba..ef2b70e44c 100644 --- a/src/Umbraco.Web/WebApi/Filters/FileUploadCleanupFilterAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/FileUploadCleanupFilterAttribute.cs @@ -1,4 +1,7 @@ -using System.Linq; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; using System.Net.Http; using System.Web.Http.Filters; using Umbraco.Core; @@ -37,6 +40,8 @@ namespace Umbraco.Web.WebApi.Filters { base.OnActionExecuted(actionExecutedContext); + var tempFolders = new List(); + if (_incomingModel) { if (actionExecutedContext.ActionContext.ActionArguments.Any()) @@ -47,7 +52,21 @@ namespace Umbraco.Web.WebApi.Filters //cleanup any files associated foreach (var f in contentItem.UploadedFiles) { - File.Delete(f.TempFilePath); + //track all temp folders so we can remove old files afterwords + var dir = Path.GetDirectoryName(f.TempFilePath); + if (tempFolders.Contains(dir) == false) + { + tempFolders.Add(dir); + } + + try + { + File.Delete(f.TempFilePath); + } + catch (System.Exception ex) + { + LogHelper.Error("Could not delete temp file " + f.TempFilePath, ex); + } } } } @@ -94,8 +113,24 @@ namespace Umbraco.Web.WebApi.Filters { if (f.TempFilePath.IsNullOrWhiteSpace() == false) { + //track all temp folders so we can remove old files afterwords + var dir = Path.GetDirectoryName(f.TempFilePath); + if (tempFolders.Contains(dir) == false) + { + tempFolders.Add(dir); + } + LogHelper.Debug("Removing temp file " + f.TempFilePath); - File.Delete(f.TempFilePath); + + try + { + File.Delete(f.TempFilePath); + } + catch (System.Exception ex) + { + LogHelper.Error("Could not delete temp file " + f.TempFilePath, ex); + } + //clear out the temp path so it's not returned in the response f.TempFilePath = ""; } @@ -120,6 +155,27 @@ namespace Umbraco.Web.WebApi.Filters LogHelper.Warn("The actionExecutedContext.Request.Content is not ObjectContent, it is " + actionExecutedContext.Request.Content.GetType()); } } + + //Now remove all old files so that the temp folder(s) never grow + foreach (var tempFolder in tempFolders.Distinct()) + { + var files = Directory.GetFiles(tempFolder); + foreach (var file in files) + { + if (DateTime.UtcNow - File.GetLastWriteTimeUtc(file) > TimeSpan.FromDays(1)) + { + try + { + File.Delete(file); + } + catch (System.Exception ex) + { + LogHelper.Error("Could not delete temp file " + file, ex); + } + } + } + + } } } diff --git a/src/Umbraco.Web/WebApi/Filters/OutgoingDateTimeFormatAttribute.cs b/src/Umbraco.Web/WebApi/Filters/OutgoingDateTimeFormatAttribute.cs index d8e185963a..4c4d831241 100644 --- a/src/Umbraco.Web/WebApi/Filters/OutgoingDateTimeFormatAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/OutgoingDateTimeFormatAttribute.cs @@ -2,7 +2,6 @@ using System.Linq; using System.Net.Http.Formatting; using System.Web.Http.Controllers; -using System.Web.Http.Filters; using Umbraco.Core; namespace Umbraco.Web.WebApi.Filters @@ -10,7 +9,7 @@ namespace Umbraco.Web.WebApi.Filters /// /// Sets the json outgoing/serialized datetime format /// - internal sealed class OutgoingDateTimeFormatAttribute : Attribute, IControllerConfiguration + internal sealed class OutgoingDateTimeFormatAttribute : Attribute, IControllerConfiguration { private readonly string _format = "yyyy-MM-dd HH:mm:ss"; @@ -27,8 +26,9 @@ namespace Umbraco.Web.WebApi.Filters /// /// Will use the standard ISO format /// - public OutgoingDateTimeFormatAttribute(){ - + public OutgoingDateTimeFormatAttribute() + { + } public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor) diff --git a/src/Umbraco.Web/WebApi/Filters/UmbracoBackOfficeLogoutAttribute.cs b/src/Umbraco.Web/WebApi/Filters/UmbracoBackOfficeLogoutAttribute.cs index 693d45c792..8912ca68ca 100644 --- a/src/Umbraco.Web/WebApi/Filters/UmbracoBackOfficeLogoutAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/UmbracoBackOfficeLogoutAttribute.cs @@ -1,23 +1,27 @@ -using System.Web.Http.Filters; +using System; +using System.ComponentModel; +using System.Web.Http.Filters; using Umbraco.Core.Security; namespace Umbraco.Web.WebApi.Filters { - /// - /// A filter that is used to remove the authorization cookie for the current user when the request is successful - /// - /// - /// This is used so that we can log a user OUT in conjunction with using other filters that modify the cookies collection. - /// SD: I beleive this is a bug with web api since if you modify the cookies collection on the HttpContext.Current and then - /// use a filter to write the cookie headers, the filter seems to have no affect at all. - /// + [Obsolete("This is no longer used and will be removed from the codebase in the future, use OWIN IAuthenticationManager.SignOut instead")] + [EditorBrowsable(EditorBrowsableState.Never)] public sealed class UmbracoBackOfficeLogoutAttribute : ActionFilterAttribute { public override void OnActionExecuted(HttpActionExecutedContext context) { if (context.Response == null) return; if (context.Response.IsSuccessStatusCode == false) return; + + //this clears out all of our cookies context.Response.UmbracoLogoutWebApi(); + + //this calls the underlying owin sign out logic - which should call the + // auth providers middleware callbacks if using custom auth middleware + context.Request.TryGetOwinContext().Result.Authentication.SignOut( + Core.Constants.Security.BackOfficeAuthenticationType, + Core.Constants.Security.BackOfficeExternalAuthenticationType); } } } \ No newline at end of file diff --git a/src/Umbraco.Web/WebApi/HttpRequestMessageExtensions.cs b/src/Umbraco.Web/WebApi/HttpRequestMessageExtensions.cs index bd2290ce17..04035c0017 100644 --- a/src/Umbraco.Web/WebApi/HttpRequestMessageExtensions.cs +++ b/src/Umbraco.Web/WebApi/HttpRequestMessageExtensions.cs @@ -11,6 +11,7 @@ using System.Web.Http; using System.Web.Http.ModelBinding; using Microsoft.Owin; using Umbraco.Core; +using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Web.WebApi { @@ -107,6 +108,22 @@ namespace Umbraco.Web.WebApi return msg; } + /// + /// Creates an error response with notifications in the result to be displayed in the UI + /// + /// + /// + /// + public static HttpResponseMessage CreateNotificationValidationErrorResponse(this HttpRequestMessage request, string errorMessage) + { + var notificationModel = new SimpleNotificationModel + { + Message = errorMessage + }; + notificationModel.AddErrorNotification(errorMessage, string.Empty); + return request.CreateValidationErrorResponse(notificationModel); + } + /// /// Create a 400 response message indicating that a validation error occurred /// diff --git a/src/Umbraco.Web/WebApi/MemberAuthorizeAttribute.cs b/src/Umbraco.Web/WebApi/MemberAuthorizeAttribute.cs index 88bb2107de..1ea7be719c 100644 --- a/src/Umbraco.Web/WebApi/MemberAuthorizeAttribute.cs +++ b/src/Umbraco.Web/WebApi/MemberAuthorizeAttribute.cs @@ -43,6 +43,7 @@ namespace Umbraco.Web.WebApi /// /// This is the same as applying the [AllowAnonymous] attribute /// + [Obsolete("Use [AllowAnonymous] instead")] public bool AllowAll { get; set; } /// diff --git a/src/Umbraco.Web/WebApi/PrefixlessBodyModelValidator.cs b/src/Umbraco.Web/WebApi/PrefixlessBodyModelValidator.cs new file mode 100644 index 0000000000..c5563e6509 --- /dev/null +++ b/src/Umbraco.Web/WebApi/PrefixlessBodyModelValidator.cs @@ -0,0 +1,37 @@ +using System; +using System.Web.Http.Controllers; +using System.Web.Http.Metadata; +using System.Web.Http.Validation; + +namespace Umbraco.Web.WebApi +{ + /// + /// By default WebApi always appends a prefix to any ModelState error but we don't want this, + /// so this is a custom validator that ensures there is no prefix set. + /// + /// + /// We were already doing this with the content/media/members validation since we had to manually validate because we + /// were posting multi-part values. We were always passing in an empty prefix so it worked. However for other editors we + /// are validating with normal data annotations (for the most part) and we don't want the prefix there either. + /// + internal class PrefixlessBodyModelValidator : IBodyModelValidator + { + private readonly IBodyModelValidator _innerValidator; + + public PrefixlessBodyModelValidator(IBodyModelValidator innerValidator) + { + if (innerValidator == null) + { + throw new ArgumentNullException("innerValidator"); + } + + _innerValidator = innerValidator; + } + + public bool Validate(object model, Type type, ModelMetadataProvider metadataProvider, HttpActionContext actionContext, string keyPrefix) + { + // Remove the keyPrefix but otherwise let innerValidator do what it normally does. + return _innerValidator.Validate(model, type, metadataProvider, actionContext, string.Empty); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/WebApi/PrefixlessBodyModelValidatorAttribute.cs b/src/Umbraco.Web/WebApi/PrefixlessBodyModelValidatorAttribute.cs new file mode 100644 index 0000000000..e018881f8a --- /dev/null +++ b/src/Umbraco.Web/WebApi/PrefixlessBodyModelValidatorAttribute.cs @@ -0,0 +1,20 @@ +using System; +using System.Web.Http; +using System.Web.Http.Controllers; +using System.Web.Http.Validation; + +namespace Umbraco.Web.WebApi +{ + /// + /// Applying this attribute to any webapi controller will ensure that it only contains one json formatter compatible with the angular json vulnerability prevention. + /// + internal class PrefixlessBodyModelValidatorAttribute : Attribute, IControllerConfiguration + { + public virtual void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor) + { + //replace the normal validator with our custom one for this controller + controllerSettings.Services.Replace(typeof(IBodyModelValidator), + new PrefixlessBodyModelValidator(controllerSettings.Services.GetBodyModelValidator())); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/WebApi/UmbracoAuthorizeAttribute.cs b/src/Umbraco.Web/WebApi/UmbracoAuthorizeAttribute.cs index 4517464553..028c835d90 100644 --- a/src/Umbraco.Web/WebApi/UmbracoAuthorizeAttribute.cs +++ b/src/Umbraco.Web/WebApi/UmbracoAuthorizeAttribute.cs @@ -4,6 +4,7 @@ using Umbraco.Core; namespace Umbraco.Web.WebApi { + /// /// Ensures authorization is successful for a back office user /// diff --git a/src/Umbraco.Web/WebApi/UmbracoAuthorizedApiController.cs b/src/Umbraco.Web/WebApi/UmbracoAuthorizedApiController.cs index 97448b9976..ad29726d4e 100644 --- a/src/Umbraco.Web/WebApi/UmbracoAuthorizedApiController.cs +++ b/src/Umbraco.Web/WebApi/UmbracoAuthorizedApiController.cs @@ -35,7 +35,7 @@ namespace Umbraco.Web.WebApi /// /// Returns the currently logged in Umbraco User /// - [Obsolete("This should no longer be used since it returns the legacy user object, use The Security.CurrentUser instead to return the proper user object")] + [Obsolete("This should no longer be used since it returns the legacy user object, use The Security.CurrentUser instead to return the proper user object, or Security.GetUserId() if you want to just get the user id")] protected User UmbracoUser { get diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs index 5b0fed9359..3ad1eba2a1 100644 --- a/src/Umbraco.Web/WebBootManager.cs +++ b/src/Umbraco.Web/WebBootManager.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.Configuration; using System.Linq; +using System.Reflection; using System.Web; using System.Web.Configuration; using System.Web.Http; @@ -11,6 +12,7 @@ using System.Web.Mvc; using System.Web.Routing; using ClientDependency.Core.Config; using Examine; +using Examine.Config; using umbraco; using Umbraco.Core; using Umbraco.Core.Configuration; @@ -41,6 +43,10 @@ using Umbraco.Web.Scheduling; using Umbraco.Web.UI.JavaScript; using Umbraco.Web.WebApi; using umbraco.BusinessLogic; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.UnitOfWork; +using Umbraco.Core.Publishing; +using Umbraco.Core.Services; using GlobalSettings = Umbraco.Core.Configuration.GlobalSettings; using ProfilingViewEngine = Umbraco.Core.Profiling.ProfilingViewEngine; using TypeHelper = Umbraco.Core.TypeHelper; @@ -59,22 +65,43 @@ namespace Umbraco.Web private readonly List _indexesToRebuild = new List(); public WebBootManager(UmbracoApplicationBase umbracoApplication) - : this(umbracoApplication, false) + : base(umbracoApplication) { - + _isForTesting = false; } /// /// Constructor for unit tests, ensures some resolvers are not initialized /// /// + /// /// - internal WebBootManager(UmbracoApplicationBase umbracoApplication, bool isForTesting) - : base(umbracoApplication) + internal WebBootManager(UmbracoApplicationBase umbracoApplication, ProfilingLogger logger, bool isForTesting) + : base(umbracoApplication, logger) { _isForTesting = isForTesting; } + /// + /// Creates and returns the service context for the app + /// + /// + /// + /// + protected override ServiceContext CreateServiceContext(DatabaseContext dbContext, IDatabaseFactory dbFactory) + { + //use a request based messaging factory + var evtMsgs = new RequestLifespanMessagesFactory(new SingletonUmbracoContextAccessor()); + return new ServiceContext( + new RepositoryFactory(ApplicationCache, ProfilingLogger.Logger, dbContext.SqlSyntax, UmbracoConfig.For.UmbracoSettings()), + new PetaPocoUnitOfWorkProvider(dbFactory), + new FileUnitOfWorkProvider(), + new PublishingStrategy(evtMsgs, ProfilingLogger.Logger), + ApplicationCache, + ProfilingLogger.Logger, + evtMsgs); + } + /// /// Initialize objects before anything during the boot cycle happens /// @@ -82,10 +109,10 @@ namespace Umbraco.Web public override IBootManager Initialize() { //This is basically a hack for this item: http://issues.umbraco.org/issue/U4-5976 - // when Examine initializes it will try to rebuild if the indexes are empty, however in many cases not all of Examine's - // event handlers will be assigned during bootup when the rebuilding starts which is a problem. So with the examine 0.1.58.2941 build - // it has an event we can subscribe to in order to cancel this rebuilding process, but what we'll do is cancel it and postpone the rebuilding until the - // boot process has completed. It's a hack but it works. + // when Examine initializes it will try to rebuild if the indexes are empty, however in many cases not all of Examine's + // event handlers will be assigned during bootup when the rebuilding starts which is a problem. So with the examine 0.1.58.2941 build + // it has an event we can subscribe to in order to cancel this rebuilding process, but what we'll do is cancel it and postpone the rebuilding until the + // boot process has completed. It's a hack but it works. ExamineManager.Instance.BuildingEmptyIndexOnStartup += OnInstanceOnBuildingEmptyIndexOnStartup; base.Initialize(); @@ -112,7 +139,10 @@ namespace Umbraco.Web }); ClientDependencySettings.Instance.MvcRendererCollection.Add(renderer); - var insHelper = new InstallHelper(UmbracoContext.Current); + // Disable the X-AspNetMvc-Version HTTP Header + MvcHandler.DisableMvcResponseHeader = true; + + InstallHelper insHelper = new InstallHelper(UmbracoContext.Current); insHelper.DeleteLegacyInstaller(); return this; @@ -132,7 +162,10 @@ namespace Umbraco.Web UmbracoContext.EnsureContext( httpContext, ApplicationContext, - new WebSecurity(httpContext, ApplicationContext)); + new WebSecurity(httpContext, ApplicationContext), + UmbracoConfig.For.UmbracoSettings(), + UrlProviderResolver.Current.Providers, + false); } /// @@ -141,9 +174,6 @@ namespace Umbraco.Web protected override IProfiler CreateProfiler() { return new WebProfiler(); - } - - /// /// Ensure that the OnApplicationStarted methods of the IApplicationEvents are called /// /// @@ -259,7 +289,6 @@ namespace Umbraco.Web } } - private void RouteLocalApiController(Type controller, string umbracoPath) { var meta = PluginController.GetMetadata(controller); @@ -281,6 +310,7 @@ namespace Umbraco.Web } route.DataTokens.Add("umbraco", "api"); //ensure the umbraco token is set } + private void RouteLocalSurfaceController(Type controller, string umbracoPath) { var meta = PluginController.GetMetadata(controller); @@ -329,32 +359,86 @@ namespace Umbraco.Web //set the default RenderMvcController DefaultRenderMvcControllerResolver.Current = new DefaultRenderMvcControllerResolver(typeof(RenderMvcController)); - ServerMessengerResolver.Current.SetServerMessenger(new BatchedWebServiceServerMessenger(() => + //Override the default server messenger, we need to check if the legacy dist calls is enabled, if that is the + // case, then we'll set the default messenger to be the old one, otherwise we'll set it to the db messenger + // which will always be on. + if (UmbracoConfig.For.UmbracoSettings().DistributedCall.Enabled) { - //we should not proceed to change this if the app/database is not configured since there will - // be no user, plus we don't need to have server messages sent if this is the case. - if (ApplicationContext.IsConfigured && ApplicationContext.DatabaseContext.IsDatabaseConfigured) + //set the legacy one by default - this maintains backwards compat + ServerMessengerResolver.Current.SetServerMessenger(new BatchedWebServiceServerMessenger(() => { - //disable if they are not enabled - if (UmbracoConfig.For.UmbracoSettings().DistributedCall.Enabled == false) + //we should not proceed to change this if the app/database is not configured since there will + // be no user, plus we don't need to have server messages sent if this is the case. + if (ApplicationContext.IsConfigured && ApplicationContext.DatabaseContext.IsDatabaseConfigured) { - return null; - } + //disable if they are not enabled + if (UmbracoConfig.For.UmbracoSettings().DistributedCall.Enabled == false) + { + return null; + } - try - { - var user = User.GetUser(UmbracoConfig.For.UmbracoSettings().DistributedCall.UserId); - return new System.Tuple(user.LoginName, user.GetPassword()); - } - catch (Exception e) - { + try + { + var user = ApplicationContext.Services.UserService.GetUserById(UmbracoConfig.For.UmbracoSettings().DistributedCall.UserId); + return new Tuple(user.Username, user.RawPasswordValue); + } + catch (Exception e) + { ProfilingLogger.Logger.Error("An error occurred trying to set the IServerMessenger during application startup", e); - return null; + return null; + } } - } ProfilingLogger.Logger.Warn("Could not initialize the DefaultServerMessenger, the application is not configured or the database is not configured"); - return null; - })); + return null; + })); + } + else + { + + // NOTE: This is IMPORTANT! ... we don't want to rebuild any index that is already flagged to be re-indexed + // on startup based on our _indexesToRebuild variable and how Examine auto-rebuilds when indexes are empty + // this callback is used below for the DatabaseServerMessenger startup options + Action rebuildIndexes = () => + { + //If the developer has explicitly opted out of rebuilding indexes on startup then we + // should adhere to that and not do it, this means that if they are load balancing things will be + // out of sync if they are auto-scaling but there's not much we can do about that. + if (ExamineSettings.Instance.RebuildOnAppStart == false) return; + + if (_indexesToRebuild.Any()) + { + var otherIndexes = ExamineManager.Instance.IndexProviderCollection.Except(_indexesToRebuild); + foreach (var otherIndex in otherIndexes) + { + otherIndex.RebuildIndex(); + } + } + else + { + //rebuild them all + ExamineManager.Instance.RebuildIndex(); + } + }; + + ServerMessengerResolver.Current.SetServerMessenger(new BatchedDatabaseServerMessenger( + ApplicationContext, + true, + //Default options for web including the required callbacks to build caches + new DatabaseServerMessengerOptions + { + //These callbacks will be executed if the server has not been synced + // (i.e. it is a new server or the lastsynced.txt file has been removed) + InitializingCallbacks = new Action[] + { + //rebuild the xml cache file if the server is not synced + () => global::umbraco.content.Instance.RefreshContentFromDatabase(), + //rebuild indexes if the server is not synced + // NOTE: This will rebuild ALL indexes including the members, if developers want to target specific + // indexes then they can adjust this logic themselves. + rebuildIndexes + } + })); + } SurfaceControllerResolver.Current = new SurfaceControllerResolver( ServiceProvider, ProfilingLogger.Logger, @@ -449,7 +533,6 @@ namespace Umbraco.Web GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerSelector), new NamespaceHttpControllerSelector(GlobalConfiguration.Configuration)); } - private void OnInstanceOnBuildingEmptyIndexOnStartup(object sender, BuildingEmptyIndexOnStartupEventArgs args) { //store the indexer that needs rebuilding because it's empty for when the boot process diff --git a/src/Umbraco.Web/WebServices/BulkPublishController.cs b/src/Umbraco.Web/WebServices/BulkPublishController.cs index 652acbcfae..ad61dab622 100644 --- a/src/Umbraco.Web/WebServices/BulkPublishController.cs +++ b/src/Umbraco.Web/WebServices/BulkPublishController.cs @@ -15,6 +15,7 @@ namespace Umbraco.Web.WebServices /// /// A REST controller used for the publish dialog in order to publish bulk items at once /// + [ValidateMvcAngularAntiForgeryToken] public class BulkPublishController : UmbracoAuthorizedController { /// diff --git a/src/Umbraco.Web/WebServices/DomainsApiController.cs b/src/Umbraco.Web/WebServices/DomainsApiController.cs index a72f7537b0..3ea24135a7 100644 --- a/src/Umbraco.Web/WebServices/DomainsApiController.cs +++ b/src/Umbraco.Web/WebServices/DomainsApiController.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; +using System.Text; using System.Web.Http; using System.Web.Services.Description; using Umbraco.Core; @@ -12,6 +13,7 @@ using Umbraco.Web.WebApi; //using umbraco.cms.businesslogic.language; using umbraco.BusinessLogic.Actions; using umbraco.cms.businesslogic.web; +using Umbraco.Web.WebApi.Filters; namespace Umbraco.Web.WebServices { @@ -19,6 +21,7 @@ namespace Umbraco.Web.WebServices /// A REST controller used for managing domains. /// /// Nothing to do with Active Directory. + [ValidateAngularAntiForgeryToken] public class DomainsApiController : UmbracoAuthorizedApiController { [HttpPost] @@ -54,16 +57,24 @@ namespace Umbraco.Web.WebServices { var wildcard = domains.FirstOrDefault(d => d.IsWildcard); if (wildcard != null) - wildcard.Language = language; + wildcard.LanguageId = language.Id; else { // yet there is a race condition here... var newDomain = new UmbracoDomain("*" + model.NodeId) { - Language = Services.LocalizationService.GetLanguageById(model.Language), - RootContent = Services.ContentService.GetById(model.NodeId) + LanguageId = model.Language, + RootContentId = model.NodeId }; - Services.DomainService.Save(newDomain); + + var saveAttempt = Services.DomainService.Save(newDomain); + if (saveAttempt == false) + { + var response = Request.CreateResponse(HttpStatusCode.BadRequest); + response.Content = new StringContent("Saving new domain failed"); + response.ReasonPhrase = saveAttempt.Result.StatusType.ToString(); + throw new HttpResponseException(response); + } } } @@ -103,18 +114,46 @@ namespace Umbraco.Web.WebServices names.Add(name); var domain = domains.FirstOrDefault(d => d.DomainName.InvariantEquals(domainModel.Name)); if (domain != null) - domain.Language = language; + { + domain.LanguageId = language.Id; + Services.DomainService.Save(domain); + } else if (Services.DomainService.Exists(domainModel.Name)) + { domainModel.Duplicate = true; + var xdomain = Services.DomainService.GetByName(domainModel.Name); + var xrcid = xdomain.RootContentId; + if (xrcid.HasValue) + { + var xcontent = Services.ContentService.GetById(xrcid.Value); + var xnames = new List(); + while (xcontent != null) + { + xnames.Add(xcontent.Name); + if (xcontent.ParentId < -1) + xnames.Add("Recycle Bin"); + xcontent = xcontent.Parent(); + } + xnames.Reverse(); + domainModel.Other = "/" + string.Join("/", xnames); + } + } else { // yet there is a race condition here... var newDomain = new UmbracoDomain(name) { - Language = Services.LocalizationService.GetLanguageById(domainModel.Lang), - RootContent = Services.ContentService.GetById(model.NodeId) + LanguageId = domainModel.Lang, + RootContentId = model.NodeId }; - Services.DomainService.Save(newDomain); + var saveAttempt = Services.DomainService.Save(newDomain); + if (saveAttempt == false) + { + var response = Request.CreateResponse(HttpStatusCode.BadRequest); + response.Content = new StringContent("Saving new domain failed"); + response.ReasonPhrase = saveAttempt.Result.StatusType.ToString(); + throw new HttpResponseException(response); + } } } @@ -144,6 +183,7 @@ namespace Umbraco.Web.WebServices public string Name { get; private set; } public int Lang { get; private set; } public bool Duplicate { get; set; } + public string Other { get; set; } } #endregion diff --git a/src/Umbraco.Web/WebServices/ExamineManagementApiController.cs b/src/Umbraco.Web/WebServices/ExamineManagementApiController.cs index b56c2b1dae..31ef4d23f2 100644 --- a/src/Umbraco.Web/WebServices/ExamineManagementApiController.cs +++ b/src/Umbraco.Web/WebServices/ExamineManagementApiController.cs @@ -13,9 +13,11 @@ using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Web.Search; using Umbraco.Web.WebApi; +using Umbraco.Web.WebApi.Filters; namespace Umbraco.Web.WebServices { + [ValidateAngularAntiForgeryToken] public class ExamineManagementApiController : UmbracoAuthorizedApiController { /// @@ -164,12 +166,23 @@ namespace Umbraco.Web.WebServices var msg = ValidateLuceneIndexer(indexerName, out indexer); if (msg.IsSuccessStatusCode) { + //remove it in case there's a handler there alraedy + indexer.IndexOperationComplete -= Indexer_IndexOperationComplete; + //now add a single handler + indexer.IndexOperationComplete += Indexer_IndexOperationComplete; + + var cacheKey = "temp_indexing_op_" + indexer.Name; + //put temp val in cache which is used as a rudimentary way to know when the indexing is done + ApplicationContext.ApplicationCache.RuntimeCache.InsertCacheItem(cacheKey, () => "tempValue", TimeSpan.FromMinutes(5), isSliding: false); + try { indexer.RebuildIndex(); } catch (Exception ex) { + //ensure it's not listening + indexer.IndexOperationComplete -= Indexer_IndexOperationComplete; LogHelper.Error("An error occurred rebuilding index", ex); var response = Request.CreateResponse(HttpStatusCode.Conflict); response.Content = new StringContent(string.Format("The index could not be rebuilt at this time, most likely there is another thread currently writing to the index. Error: {0}", ex)); @@ -180,14 +193,26 @@ namespace Umbraco.Web.WebServices return msg; } + //static listener so it's not GC'd + private static void Indexer_IndexOperationComplete(object sender, EventArgs e) + { + var indexer = (LuceneIndexer) sender; + + //ensure it's not listening anymore + indexer.IndexOperationComplete -= Indexer_IndexOperationComplete; + + var cacheKey = "temp_indexing_op_" + indexer.Name; + ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheItem(cacheKey); + } + /// /// Check if the index has been rebuilt /// /// /// /// - /// This is kind of rudimentary since there's no way we can know that the index has rebuilt, we'll just check - /// if the index is locked based on Lucene apis + /// This is kind of rudimentary since there's no way we can know that the index has rebuilt, we + /// have a listener for the index op complete so we'll just check if that key is no longer there in the runtime cache /// public ExamineIndexerModel PostCheckRebuildIndex(string indexerName) { @@ -195,9 +220,11 @@ namespace Umbraco.Web.WebServices var msg = ValidateLuceneIndexer(indexerName, out indexer); if (msg.IsSuccessStatusCode) { - var isLocked = indexer.IsIndexLocked(); - return isLocked - ? null + var cacheKey = "temp_indexing_op_" + indexerName; + var found = ApplicationContext.ApplicationCache.RuntimeCache.GetCacheItem(cacheKey); + //if its still there then it's not done + return found != null + ? null : CreateModel(indexer); } throw new HttpResponseException(msg); @@ -233,19 +260,37 @@ namespace Umbraco.Web.WebServices //ignore these properties .Where(x => new[] {"IndexerData", "Description", "WorkingFolder"}.InvariantContains(x.Name) == false) .OrderBy(x => x.Name); + foreach (var p in props) { - indexerModel.ProviderProperties.Add(p.Name, p.GetValue(indexer, null).ToString()); + var val = p.GetValue(indexer, null); + if (val == null) + { + LogHelper.Warn("Property value was null when setting up property on indexer: " + indexer.Name + " property: " + p.Name); + val = string.Empty; + } + indexerModel.ProviderProperties.Add(p.Name, val.ToString()); } var luceneIndexer = indexer as LuceneIndexer; - if (luceneIndexer != null && luceneIndexer.IndexExists()) + if (luceneIndexer != null) { indexerModel.IsLuceneIndex = true; - indexerModel.DocumentCount = luceneIndexer.GetIndexDocumentCount(); - indexerModel.FieldCount = luceneIndexer.GetIndexFieldCount(); - indexerModel.IsOptimized = luceneIndexer.IsIndexOptimized(); - indexerModel.DeletionCount = luceneIndexer.GetDeletedDocumentsCount(); + + if (luceneIndexer.IndexExists()) + { + indexerModel.DocumentCount = luceneIndexer.GetIndexDocumentCount(); + indexerModel.FieldCount = luceneIndexer.GetIndexFieldCount(); + indexerModel.IsOptimized = luceneIndexer.IsIndexOptimized(); + indexerModel.DeletionCount = luceneIndexer.GetDeletedDocumentsCount(); + } + else + { + indexerModel.DocumentCount = 0; + indexerModel.FieldCount = 0; + indexerModel.IsOptimized = true; + indexerModel.DeletionCount = 0; + } } return indexerModel; } diff --git a/src/Umbraco.Web/WebServices/SaveFileController.cs b/src/Umbraco.Web/WebServices/SaveFileController.cs index 651b372988..660fe9f35a 100644 --- a/src/Umbraco.Web/WebServices/SaveFileController.cs +++ b/src/Umbraco.Web/WebServices/SaveFileController.cs @@ -12,8 +12,9 @@ using Umbraco.Web.Mvc; using umbraco; using umbraco.cms.businesslogic.macro; using System.Collections.Generic; +using umbraco.cms.helpers; using Umbraco.Core; - +using Umbraco.Core.Configuration; using Template = umbraco.cms.businesslogic.template.Template; namespace Umbraco.Web.WebServices @@ -25,6 +26,7 @@ namespace Umbraco.Web.WebServices /// This isn't fully implemented yet but we should migrate all of the logic in the umbraco.presentation.webservices.codeEditorSave /// over to this controller. /// + [ValidateMvcAngularAntiForgeryToken] public class SaveFileController : UmbracoAuthorizedController { /// @@ -37,56 +39,14 @@ namespace Umbraco.Web.WebServices [HttpPost] public JsonResult SavePartialViewMacro(string filename, string oldName, string contents) { - //NOTE: This is a bit of a hack because we're sharing the View editor with templates/partial views/partial view macros, so the path starts with - // 'Partials' which we need to remove because the path that we construct the partial view with is a relative path to the root path of - // Views/Partials - if (filename.InvariantStartsWith("MacroPartials/")) - { - filename = filename.TrimStart("MacroPartials/"); - } - if (oldName.IsNullOrWhiteSpace() == false) - { - if (oldName.InvariantStartsWith("MacroPartials/")) - { - oldName = oldName.TrimStart("MacroPartials/"); - } - } + var svce = (FileService) Services.FileService; - var fileService = (FileService)Services.FileService; - - //try to get the file by the old name first if they are different and delete that file - if (filename.Trim().InvariantEquals(oldName.Trim()) == false) - { - var existing = fileService.GetPartialViewMacro(oldName); - if (existing != null) - { - var success = fileService.DeletePartialViewMacro(existing.Path, Security.GetUserId()); - if (success == false) - { - return Failed( - ui.Text("speechBubbles", "partialViewErrorText"), ui.Text("speechBubbles", "partialViewErrorHeader"), - //pass in a new exception ... this will also append the the message - new Exception("Could not delete old file: " + oldName)); - } - } - } - - var partialView = new PartialView(filename) - { - Content = contents - }; - - var attempt = fileService.SavePartialViewMacro(partialView); - - if (attempt.Success == false) - { - return Failed( - ui.Text("speechBubbles", "partialViewErrorText"), ui.Text("speechBubbles", "partialViewErrorHeader"), - //pass in a new exception ... this will also append the the message - attempt.Exception); - } - - return Success(ui.Text("speechBubbles", "partialViewSavedText"), ui.Text("speechBubbles", "partialViewSavedHeader")); + return SavePartialView(svce, + filename, oldName, contents, + "MacroPartials/", + (s, n) => s.GetPartialViewMacro(n), + (s, v) => s.ValidatePartialViewMacro((PartialView) v), + (s, v) => s.SavePartialViewMacro(v)); } /// @@ -99,56 +59,81 @@ namespace Umbraco.Web.WebServices [HttpPost] public JsonResult SavePartialView(string filename, string oldName, string contents) { - //NOTE: This is a bit of a hack because we're sharing the View editor with templates/partial views/partial view macros, so the path starts with - // 'Partials' which we need to remove because the path that we construct the partial view with is a relative path to the root path of - // Views/Partials - if (filename.InvariantStartsWith("Partials/")) + var svce = (FileService) Services.FileService; + + return SavePartialView(svce, + filename, oldName, contents, + "Partials/", + (s, n) => s.GetPartialView(n), + (s, v) => s.ValidatePartialView((PartialView) v), + (s, v) => s.SavePartialView(v)); + } + + private JsonResult SavePartialView(IFileService svce, + string filename, string oldname, string contents, + string pathPrefix, + Func get, + Func validate, + Func> save) + { + // sanitize input - partial view names have an extension + filename = filename + .Replace('\\', '/') + .TrimStart('/'); + + // sharing the editor with partial views & partial view macros, + // using path prefix to differenciate, + // but the file service manages different filesystems, + // and we need to come back to filesystem-relative paths + + // not sure why we still need this but not going to change it now + + if (filename.InvariantStartsWith(pathPrefix)) + filename = filename.TrimStart(pathPrefix); + + if (oldname != null) { - filename = filename.TrimStart("Partials/"); - } - if (oldName.IsNullOrWhiteSpace() == false) - { - if (oldName.InvariantStartsWith("Partials/")) - { - oldName = oldName.TrimStart("Partials/"); - } + oldname = oldname.TrimStart('/', '\\'); + if (oldname.InvariantStartsWith(pathPrefix)) + oldname = oldname.TrimStart(pathPrefix); } - var fileService = (FileService)Services.FileService; + var currentView = oldname.IsNullOrWhiteSpace() + ? get(svce, filename) + : get(svce, oldname); + + if (currentView == null) + currentView = new PartialView(filename); + else + currentView.Path = filename; + currentView.Content = contents; + - //try to get the file by the old name first if they are different and delete that file - if (filename.Trim().InvariantEquals(oldName.Trim()) == false) + + + Attempt attempt; + try { - var existing = fileService.GetPartialView(oldName); - if (existing != null) - { - var success = fileService.DeletePartialView(existing.Path, Security.GetUserId()); - if (success == false) - { - return Failed( - ui.Text("speechBubbles", "partialViewErrorText"), ui.Text("speechBubbles", "partialViewErrorHeader"), - //pass in a new exception ... this will also append the the message - new Exception("Could not delete old file: " + oldName)); - } - } + var partialView = currentView as PartialView; + if (partialView != null && validate != null && validate(svce, partialView) == false) + return Failed(ui.Text("speechBubbles", "partialViewErrorText"), ui.Text("speechBubbles", "partialViewErrorHeader"), + new FileSecurityException("File '" + currentView.Path + "' is not a valid partial view file.")); + + attempt = save(svce, currentView); } - - var partialView = new PartialView(filename) + catch (Exception e) { - Content = contents - }; - - var attempt = fileService.SavePartialView(partialView); + return Failed(ui.Text("speechBubbles", "partialViewErrorText"), ui.Text("speechBubbles", "partialViewErrorHeader"), e); + } if (attempt.Success == false) { - return Failed( - ui.Text("speechBubbles", "partialViewErrorText"), ui.Text("speechBubbles", "partialViewErrorHeader"), - //pass in a new exception ... this will also append the the message - attempt.Exception); + return Failed(ui.Text("speechBubbles", "partialViewErrorText"), ui.Text("speechBubbles", "partialViewErrorHeader"), + attempt.Exception); } - return Success(ui.Text("speechBubbles", "partialViewSavedText"), ui.Text("speechBubbles", "partialViewSavedHeader")); + + return Success(ui.Text("speechBubbles", "partialViewSavedText"), ui.Text("speechBubbles", "partialViewSavedHeader"), new { name = currentView.Name, path = currentView.Path }); } /// @@ -171,8 +156,8 @@ namespace Umbraco.Web.WebServices { t = new Template(templateId) { - Text = templateName, - Alias = templateAlias, + Text = templateName.CleanForXss('[', ']', '(', ')'), + Alias = templateAlias.CleanForXss('[', ']', '(', ')'), Design = templateContents }; @@ -216,6 +201,85 @@ namespace Umbraco.Web.WebServices } } + [HttpPost] + public JsonResult SaveScript(string filename, string oldName, string contents) + { + // sanitize input - script names have an extension + filename = filename + .Replace('\\', '/') + .TrimStart('/'); + + var svce = (FileService) Services.FileService; + var script = svce.GetScriptByName(oldName); + if (script == null) + script = new Script(filename); + else + script.Path = filename; + script.Content = contents; + + try + { + if (svce.ValidateScript(script) == false) + return Failed(ui.Text("speechBubbles", "scriptErrorText"), ui.Text("speechBubbles", "scriptErrorHeader"), + new FileSecurityException("File '" + filename + "' is not a valid script file.")); + + svce.SaveScript(script); + } + catch (Exception e) + { + return Failed(ui.Text("speechBubbles", "scriptErrorText"), ui.Text("speechBubbles", "scriptErrorHeader"), e); + } + + return Success(ui.Text("speechBubbles", "scriptSavedText"), ui.Text("speechBubbles", "scriptSavedHeader"), + new + { + path = DeepLink.GetTreePathFromFilePath(script.Path), + name = script.Path, + url = script.VirtualPath, + contents = script.Content + }); + } + + [HttpPost] + public JsonResult SaveStylesheet(string filename, string oldName, string contents) + { + // sanitize input - stylesheet names have no extension + filename = filename + .Replace('\\', '/') + .TrimStart('/') + .EnsureEndsWith(".css"); + + var svce = (FileService) Services.FileService; + var stylesheet = svce.GetStylesheetByName(oldName); + if (stylesheet == null) + stylesheet = new Stylesheet(filename); + else + stylesheet.Path = filename; + stylesheet.Content = contents; + + try + { + if (svce.ValidateStylesheet(stylesheet) == false) + return Failed(ui.Text("speechBubbles", "cssErrorText"), ui.Text("speechBubbles", "cssErrorHeader"), + new FileSecurityException("File '" + filename + "' is not a valid stylesheet file.")); + + svce.SaveStylesheet(stylesheet); + } + catch (Exception e) + { + return Failed(ui.Text("speechBubbles", "cssErrorText"), ui.Text("speechBubbles", "cssErrorHeader"), e); + } + + return Success(ui.Text("speechBubbles", "cssSavedText"), ui.Text("speechBubbles", "cssSavedHeader"), + new + { + path = DeepLink.GetTreePathFromFilePath(stylesheet.Path), + name = stylesheet.Path, + url = stylesheet.VirtualPath, + contents = stylesheet.Content + }); + } + /// /// Returns a successful message /// diff --git a/src/Umbraco.Web/WebServices/XmlDataIntegrityController.cs b/src/Umbraco.Web/WebServices/XmlDataIntegrityController.cs index 4029fc1dcf..3e95c9ee22 100644 --- a/src/Umbraco.Web/WebServices/XmlDataIntegrityController.cs +++ b/src/Umbraco.Web/WebServices/XmlDataIntegrityController.cs @@ -4,10 +4,11 @@ using Umbraco.Core; using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence; using Umbraco.Web.WebApi; +using Umbraco.Web.WebApi.Filters; namespace Umbraco.Web.WebServices { - + [ValidateAngularAntiForgeryToken] public class XmlDataIntegrityController : UmbracoAuthorizedApiController { [HttpPost] diff --git a/src/Umbraco.Web/app.config b/src/Umbraco.Web/app.config index 548dde0987..2b15194775 100644 --- a/src/Umbraco.Web/app.config +++ b/src/Umbraco.Web/app.config @@ -29,11 +29,11 @@ - + - + diff --git a/src/Umbraco.Web/packages.config b/src/Umbraco.Web/packages.config index 695ae995a5..d33bbcd37a 100644 --- a/src/Umbraco.Web/packages.config +++ b/src/Umbraco.Web/packages.config @@ -1,24 +1,23 @@  - + - - + + - - - - - + + + + - + @@ -26,9 +25,10 @@ - + + - - + + \ No newline at end of file diff --git a/src/Umbraco.Web/umbraco.presentation/EnsureSystemPathsApplicationStartupHandler.cs b/src/Umbraco.Web/umbraco.presentation/EnsureSystemPathsApplicationStartupHandler.cs index dc354be71d..00ec43229a 100644 --- a/src/Umbraco.Web/umbraco.presentation/EnsureSystemPathsApplicationStartupHandler.cs +++ b/src/Umbraco.Web/umbraco.presentation/EnsureSystemPathsApplicationStartupHandler.cs @@ -16,25 +16,11 @@ namespace umbraco.presentation { base.ApplicationInitialized(umbracoApplication, applicationContext); - EnsurePathExists("~/App_Code"); - EnsurePathExists("~/App_Data"); - EnsurePathExists(SystemDirectories.AppPlugins); - EnsurePathExists(SystemDirectories.Css); - EnsurePathExists(SystemDirectories.Masterpages); - EnsurePathExists(SystemDirectories.Media); - EnsurePathExists(SystemDirectories.Scripts); - EnsurePathExists(SystemDirectories.UserControls); - EnsurePathExists(SystemDirectories.Xslt); - EnsurePathExists(SystemDirectories.MvcViews); - EnsurePathExists(SystemDirectories.MvcViews + "/Partials"); - EnsurePathExists(SystemDirectories.MvcViews + "/MacroPartials"); - } - - public void EnsurePathExists(string path) - { - var absolutePath = IOHelper.MapPath(path); - if (!Directory.Exists(absolutePath)) - Directory.CreateDirectory(absolutePath); + IOHelper.EnsurePathExists("~/App_Data"); + IOHelper.EnsurePathExists(SystemDirectories.Media); + IOHelper.EnsurePathExists(SystemDirectories.MvcViews); + IOHelper.EnsurePathExists(SystemDirectories.MvcViews + "/Partials"); + IOHelper.EnsurePathExists(SystemDirectories.MvcViews + "/MacroPartials"); } } } \ No newline at end of file diff --git a/src/Umbraco.Web/umbraco.presentation/content.cs b/src/Umbraco.Web/umbraco.presentation/content.cs index 6628b7ec76..23e44896f4 100644 --- a/src/Umbraco.Web/umbraco.presentation/content.cs +++ b/src/Umbraco.Web/umbraco.presentation/content.cs @@ -1,29 +1,30 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Globalization; using System.IO; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Web; -using System.Web.Hosting; using System.Xml; +using umbraco.BusinessLogic; +using umbraco.cms.businesslogic; +using umbraco.cms.businesslogic.web; +using umbraco.DataLayer; +using umbraco.presentation.nodeFactory; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; using Umbraco.Core.IO; using Umbraco.Core.Logging; -using umbraco.BusinessLogic; -using umbraco.cms.businesslogic; -using umbraco.cms.businesslogic.web; using Umbraco.Core.Models; using Umbraco.Core.Profiling; -using umbraco.DataLayer; using Umbraco.Web; using Umbraco.Web.PublishedCache.XmlPublishedCache; using Umbraco.Web.Scheduling; using File = System.IO.File; +using Node = umbraco.NodeFactory.Node; +using Task = System.Threading.Tasks.Task; namespace umbraco { @@ -34,35 +35,61 @@ namespace umbraco { private XmlCacheFilePersister _persisterTask; + private volatile bool _released; + #region Constructors private content() { - if (SyncToXmlFile == false) return; - - var logger = LoggerResolver.HasCurrent ? LoggerResolver.Current.Logger : new DebugDiagnosticsLogger(); - var profingLogger = new ProfilingLogger( - logger, - ProfilerResolver.HasCurrent ? ProfilerResolver.Current.Profiler : new LogProfiler(logger)); - - // there's always be one task keeping a ref to the runner - // so it's safe to just create it as a local var here - var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions + if (SyncToXmlFile) { - LongRunning = true, - KeepAlive = true - }, logger); + var logger = LoggerResolver.HasCurrent ? LoggerResolver.Current.Logger : new DebugDiagnosticsLogger(); + var profingLogger = new ProfilingLogger( + logger, + ProfilerResolver.HasCurrent ? ProfilerResolver.Current.Profiler : new LogProfiler(logger)); + + // prepare the persister task + // there's always be one task keeping a ref to the runner + // so it's safe to just create it as a local var here + var runner = new BackgroundTaskRunner("XmlCacheFilePersister", new BackgroundTaskRunnerOptions + { + LongRunning = true, + KeepAlive = true, + Hosted = false // main domain will take care of stopping the runner (see below) + }, logger); - // create (and add to runner) - _persisterTask = new XmlCacheFilePersister(runner, this, profingLogger); + // create (and add to runner) + _persisterTask = new XmlCacheFilePersister(runner, this, profingLogger); - InitializeFileLock(); + var registered = ApplicationContext.Current.MainDom.Register( + null, + () => + { + // once released, the cache still works but does not write to file anymore, + // which is OK with database server messenger but will cause data loss with + // another messenger... + + runner.Shutdown(false, true); // wait until flushed + _released = true; + }); + + // failed to become the main domain, we will never use the file + if (registered == false) + runner.Shutdown(false, true); + + _released = (registered == false); + } // initialize content - populate the cache using (var safeXml = GetSafeXmlWriter(false)) { bool registerXmlChange; + + // if we don't use the file then LoadXmlLocked will not even + // read from the file and will go straight to database LoadXmlLocked(safeXml, out registerXmlChange); + // if we use the file and registerXmlChange is true this will + // write to file, else it will not safeXml.Commit(registerXmlChange); } } @@ -90,23 +117,26 @@ namespace umbraco private static readonly object DbReadSyncLock = new object(); private const string XmlContextContentItemKey = "UmbracoXmlContextContent"; - private string _umbracoXmlDiskCacheFileName = string.Empty; + private static string _umbracoXmlDiskCacheFileName = string.Empty; private volatile XmlDocument _xmlContent; /// /// Gets the path of the umbraco XML disk cache file. /// /// The name of the umbraco XML disk cache file. + public static string GetUmbracoXmlDiskFileName() + { + if (string.IsNullOrEmpty(_umbracoXmlDiskCacheFileName)) + { + _umbracoXmlDiskCacheFileName = IOHelper.MapPath(SystemFiles.ContentCacheXml); + } + return _umbracoXmlDiskCacheFileName; + } + + [Obsolete("Use the safer static GetUmbracoXmlDiskFileName() method instead to retrieve this value")] public string UmbracoXmlDiskCacheFileName { - get - { - if (string.IsNullOrEmpty(_umbracoXmlDiskCacheFileName)) - { - _umbracoXmlDiskCacheFileName = IOHelper.MapPath(SystemFiles.ContentCacheXml); - } - return _umbracoXmlDiskCacheFileName; - } + get { return GetUmbracoXmlDiskFileName(); } set { _umbracoXmlDiskCacheFileName = value; } } @@ -126,7 +156,13 @@ namespace umbraco #endregion #region Public Methods - + + [Obsolete("This is no longer used and will be removed in future versions, if you use this method it will not refresh 'async' it will perform the refresh on the current thread which is how it should be doing it")] + public virtual void RefreshContentFromDatabaseAsync() + { + RefreshContentFromDatabase(); + } + /// /// Load content from database and replaces active content when done. /// @@ -161,8 +197,29 @@ namespace umbraco var node = GetPreviewOrPublishedNode(d, xmlContentCopy, false); var attr = ((XmlElement)node).GetAttributeNode("sortOrder"); attr.Value = d.sortOrder.ToString(); - AddOrUpdateXmlNode(xmlContentCopy, d.Id, d.Level, parentId, node); + xmlContentCopy = GetAddOrUpdateXmlNode(xmlContentCopy, d.Id, d.Level, parentId, node); + // update sitemapprovider + if (updateSitemapProvider && SiteMap.Provider is UmbracoSiteMapProvider) + { + try + { + var prov = (UmbracoSiteMapProvider)SiteMap.Provider; + var n = new Node(d.Id, true); + if (string.IsNullOrEmpty(n.Url) == false && n.Url != "/#") + { + prov.UpdateNode(n); + } + else + { + LogHelper.Debug(string.Format("Can't update Sitemap Provider due to empty Url in node id: {0}", d.Id)); + } + } + catch (Exception ee) + { + LogHelper.Error(string.Format("Error adding node to Sitemap Provider in PublishNodeDo(): {0}", d.Id), ee); + } + } } return xmlContentCopy; @@ -186,7 +243,9 @@ namespace umbraco /// The parent node identifier. public void SortNodes(int parentId) { - var childNodesXPath = "./* [@id]"; + var childNodesXPath = UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema + ? "./node" + : "./* [@id]"; using (var safeXml = GetSafeXmlWriter(false)) { @@ -286,10 +345,56 @@ namespace umbraco } } + /// + /// Updates the document cache for multiple documents + /// + /// The documents. + [Obsolete("This is not used and will be removed from the codebase in future versions")] + public virtual void UpdateDocumentCache(List Documents) + { + // We need to lock content cache here, because we cannot allow other threads + // making changes at the same time, they need to be queued + int parentid = Documents[0].Id; + + + using (var safeXml = GetSafeXmlWriter()) + { + foreach (Document d in Documents) + { + safeXml.Xml = PublishNodeDo(d, safeXml.Xml, true); + } + } + + ClearContextCache(); + } + + [Obsolete("Method obsolete in version 4.1 and later, please use UpdateDocumentCache", true)] + public virtual void UpdateDocumentCacheAsync(int documentId) + { + UpdateDocumentCache(documentId); + } + + [Obsolete("Method obsolete in version 4.1 and later, please use ClearDocumentCache", true)] + public virtual void ClearDocumentCacheAsync(int documentId) + { + ClearDocumentCache(documentId); + } + public virtual void ClearDocumentCache(int documentId) { // Get the document - var d = new Document(documentId); + Document d; + try + { + d = new Document(documentId); + } + catch + { + // if we need the document to remove it... this cannot be LB?! + // shortcut everything here + ClearDocumentXmlCache(documentId); + return; + } ClearDocumentCache(d); } @@ -310,34 +415,56 @@ namespace umbraco // remove from xml db cache doc.XmlRemoveFromDB(); - // We need to lock content cache here, because we cannot allow other threads - // making changes at the same time, they need to be queued - using (var safeXml = GetSafeXmlReader()) - { - // Check if node present, before cloning - x = safeXml.Xml.GetElementById(doc.Id.ToString()); - if (x == null) - return; - - safeXml.UpgradeToWriter(false); - - // Find the document in the xml cache - x = safeXml.Xml.GetElementById(doc.Id.ToString()); - if (x != null) - { - // The document already exists in cache, so repopulate it - x.ParentNode.RemoveChild(x); - safeXml.Commit(); - } - } + // clear xml cache + ClearDocumentXmlCache(doc.Id); ClearContextCache(); //SD: changed to fire event BEFORE running the sitemap!! argh. FireAfterClearDocumentCache(doc, e); - + + // update sitemapprovider + if (SiteMap.Provider is UmbracoSiteMapProvider) + { + var prov = (UmbracoSiteMapProvider)SiteMap.Provider; + prov.RemoveNode(doc.Id); + } } - } + } + + internal void ClearDocumentXmlCache(int id) + { + // We need to lock content cache here, because we cannot allow other threads + // making changes at the same time, they need to be queued + using (var safeXml = GetSafeXmlReader()) + { + // Check if node present, before cloning + var x = safeXml.Xml.GetElementById(id.ToString()); + if (x == null) + return; + + safeXml.UpgradeToWriter(false); + + // Find the document in the xml cache + x = safeXml.Xml.GetElementById(id.ToString()); + if (x != null) + { + // The document already exists in cache, so repopulate it + x.ParentNode.RemoveChild(x); + safeXml.Commit(); + } + } + } + + /// + /// Unpublishes the node. + /// + /// The document id. + [Obsolete("Please use: umbraco.content.ClearDocumentCache", true)] + public virtual void UnPublishNode(int documentId) + { + ClearDocumentCache(documentId); + } #endregion @@ -497,19 +624,49 @@ order by umbracoNode.level, umbracoNode.sortOrder"; if (hierarchy.TryGetValue(parentId, out children)) { - XmlNode childContainer = parentNode; + XmlNode childContainer = UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema || + String.IsNullOrEmpty(UmbracoSettings.TEMP_FRIENDLY_XML_CHILD_CONTAINER_NODENAME) + ? parentNode + : parentNode.SelectSingleNode( + UmbracoSettings.TEMP_FRIENDLY_XML_CHILD_CONTAINER_NODENAME); + + if (!UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema && + !String.IsNullOrEmpty(UmbracoSettings.TEMP_FRIENDLY_XML_CHILD_CONTAINER_NODENAME)) + { + if (childContainer == null) + { + childContainer = xmlHelper.addTextNode(parentNode.OwnerDocument, + UmbracoSettings. + TEMP_FRIENDLY_XML_CHILD_CONTAINER_NODENAME, ""); + parentNode.AppendChild(childContainer); + } + } foreach (int childId in children) { XmlNode childNode = nodeIndex[childId]; - parentNode.AppendChild(childNode); - + + if (UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema || + String.IsNullOrEmpty(UmbracoSettings.TEMP_FRIENDLY_XML_CHILD_CONTAINER_NODENAME)) + { + parentNode.AppendChild(childNode); + } + else + { + childContainer.AppendChild(childNode); + } + // Recursively build the content tree under the current child GenerateXmlDocument(hierarchy, nodeIndex, childId, childNode); } } } + [Obsolete("This method should not be used and does nothing, xml file persistence is done in a queue using a BackgroundTaskRunner")] + public void PersistXmlToFile() + { + } + /// /// Adds a task to the xml cache file persister /// @@ -522,9 +679,9 @@ order by umbracoNode.level, umbracoNode.sortOrder"; { //TODO: Should there be a try/catch here in case the file is being written to while this is trying to be executed? - if (File.Exists(UmbracoXmlDiskCacheFileName)) + if (File.Exists(GetUmbracoXmlDiskFileName())) { - return new FileInfo(UmbracoXmlDiskCacheFileName).LastWriteTimeUtc; + return new FileInfo(GetUmbracoXmlDiskFileName()).LastWriteTimeUtc; } return DateTime.MinValue; @@ -562,6 +719,36 @@ order by umbracoNode.level, umbracoNode.sortOrder"; get { return UmbracoConfig.For.UmbracoSettings().Content.CloneXmlContent; } } + // whether to use the legacy schema + private static bool UseLegacySchema + { + get { return UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema; } + } + + // whether to keep version of everything (incl. medias & members) in cmsPreviewXml + // for audit purposes - false by default, not in umbracoSettings.config + // whether to... no idea what that one does + // it is false by default and not in UmbracoSettings.config anymore - ignoring + /* + private static bool GlobalPreviewStorageEnabled + { + get { return UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled; } + } + */ + + // ensures config is valid + private void EnsureConfigurationIsValid() + { + if (SyncToXmlFile && SyncFromXmlFile) + throw new Exception("Cannot run with both ContinouslyUpdateXmlDiskCache and XmlContentCheckForDiskChanges being true."); + + if (XmlIsImmutable == false) + //LogHelper.Warn("Running with CloneXmlContent being false is a bad idea."); + LogHelper.Warn("CloneXmlContent is false - ignored, we always clone."); + + // note: if SyncFromXmlFile then we should also disable / warn that local edits are going to cause issues... + } + #endregion #region Xml @@ -590,7 +777,11 @@ order by umbracoNode.level, umbracoNode.sortOrder"; } } - + [Obsolete("Please use: content.Instance.XmlContent")] + public static XmlDocument xmlContent + { + get { return Instance.XmlContent; } + } // to be used by content.Instance protected internal virtual XmlDocument XmlContentInternal @@ -620,7 +811,7 @@ order by umbracoNode.level, umbracoNode.sortOrder"; return xmlDoc == null ? null : (XmlDocument)xmlDoc.CloneNode(true); } - private static void EnsureSchema(string contentTypeAlias, XmlDocument xml) + private static XmlDocument EnsureSchema(string contentTypeAlias, XmlDocument xml) { string subset = null; @@ -633,15 +824,22 @@ order by umbracoNode.level, umbracoNode.sortOrder"; // ensure it contains the content type if (subset != null && subset.Contains(string.Format("", contentTypeAlias))) - return; + return xml; - // remove current doctype - xml.RemoveChild(n); + // alas, that does not work, replacing a doctype is ignored and GetElementById fails + // + //// remove current doctype, set new doctype + //xml.RemoveChild(n); + //subset = string.Format("{0}{0}{2}", Environment.NewLine, contentTypeAlias, subset); + //var doctype = xml.CreateDocumentType("root", null, null, subset); + //xml.InsertAfter(doctype, xml.FirstChild); - // set new doctype + var xml2 = new XmlDocument(); subset = string.Format("{0}{0}{2}", Environment.NewLine, contentTypeAlias, subset); - var doctype = xml.CreateDocumentType("root", null, null, subset); - xml.InsertAfter(doctype, xml.FirstChild); + var doctype = xml2.CreateDocumentType("root", null, null, subset); + xml2.AppendChild(doctype); + xml2.AppendChild(xml2.ImportNode(xml.DocumentElement, true)); + return xml2; } private static void InitializeXml(XmlDocument xml, string dtd) @@ -771,12 +969,22 @@ order by umbracoNode.level, umbracoNode.sortOrder"; private static string ChildNodesXPath { - get { return "./* [@id]"; } + get + { + return UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema + ? "./node" + : "./* [@id]"; + } } private static string DataNodesXPath { - get { return "./* [not(@id)]"; } + get + { + return UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema + ? "./data" + : "./* [not(@id)]"; + } } #endregion @@ -786,100 +994,17 @@ order by umbracoNode.level, umbracoNode.sortOrder"; private readonly string _xmlFileName = IOHelper.MapPath(SystemFiles.ContentCacheXml); private DateTime _lastFileRead; // last time the file was read private DateTime _nextFileCheck; // last time we checked whether the file was changed - private AsyncLock _fileLock; // protects the file - private IDisposable _fileLocked; // protects the file - private const int FileLockTimeoutMilliseconds = 4 * 60 * 1000; // 4' - - private void InitializeFileLock() - { - // initialize file lock - // ApplicationId will look like "/LM/W3SVC/1/Root/AppName" - // name is system-wide and must be less than 260 chars - // - // From MSDN C++ CreateSemaphore doc: - // "The name can have a "Global\" or "Local\" prefix to explicitly create the object in - // the global or session namespace. The remainder of the name can contain any character - // except the backslash character (\). For more information, see Kernel Object Namespaces." - // - // From MSDN "Kernel object namespaces" doc: - // "The separate client session namespaces enable multiple clients to run the same - // applications without interfering with each other. For processes started under - // a client session, the system uses the session namespace by default. However, these - // processes can use the global namespace by prepending the "Global\" prefix to the object name." - // - // just use "default" (whatever it is) for now - ie, no prefix - // - var name = HostingEnvironment.ApplicationID + "/XmlStore/XmlFile"; - _fileLock = new AsyncLock(name); - - // the file lock works with a shared, system-wide, semaphore - and we don't want - // to leak a count on that semaphore else the whole process will hang - so we have - // to ensure we dispose of the locker when the domain goes down - in theory the - // async lock should do it via its finalizer, but then there are some weird cases - // where the semaphore has been disposed of before it's been released, and then - // we'd need to GC-pin the semaphore... better dispose the locker explicitely - // when the app domain unloads. - - if (AppDomain.CurrentDomain.IsDefaultAppDomain()) - { - LogHelper.Debug("Registering Unload handler for default app domain."); - AppDomain.CurrentDomain.ProcessExit += OnDomainUnloadReleaseFileLock; - } - else - { - LogHelper.Debug("Registering Unload handler for non-default app domain."); - AppDomain.CurrentDomain.DomainUnload += OnDomainUnloadReleaseFileLock; - } - } - - private void EnsureFileLock() - { - if (_fileLock == null) return; // not locking (testing?) - if (_fileLocked != null) return; // locked already - - // thread-safety, acquire lock only once! - // lock something that's readonly and not null.. - lock (_xmlFileName) - { - // double-check - if (_fileLock == null) return; - if (_fileLocked != null) return; - - // don't hang forever, throws if it cannot lock within the timeout - LogHelper.Debug("Acquiring exclusive access to file for this AppDomain..."); - _fileLocked = _fileLock.Lock(FileLockTimeoutMilliseconds); - LogHelper.Debug("Acquired exclusive access to file for this AppDomain."); - } - } - - private void OnDomainUnloadReleaseFileLock(object sender, EventArgs args) - { - // the unload event triggers AFTER all hosted objects (eg the file persister - // background task runner) have been stopped, so we should NOT be accessing - // the file from now one - release the lock - - // NOTE - // trying to write to the log via LogHelper at that point is a BAD idea - // it can lead to ugly deadlocks with the named semaphore - DONT do it - - if (_fileLock == null) return; // not locking (testing?) - if (_fileLocked == null) return; // not locked - - // thread-safety - // lock something that's readonly and not null.. - lock (_xmlFileName) - { - // double-check - if (_fileLocked == null) return; - - // in case you really need to debug... that should be safe... - //System.IO.File.AppendAllText(HostingEnvironment.MapPath("~/App_Data/log.txt"), string.Format("{0} {1} unlock", DateTime.Now, AppDomain.CurrentDomain.Id)); - _fileLocked.Dispose(); - - _fileLock = null; // ensure we don't lock again - } - } + // not used - just try to read the file + //private bool XmlFileExists + //{ + // get + // { + // // check that the file exists and has content (is not empty) + // var fileInfo = new FileInfo(_xmlFileName); + // return fileInfo.Exists && fileInfo.Length > 0; + // } + //} private DateTime XmlFileLastWriteTime { @@ -890,16 +1015,18 @@ order by umbracoNode.level, umbracoNode.sortOrder"; } } - // assumes file lock + // invoked by XmlCacheFilePersister ONLY and that one manages the MainDom, ie it + // will NOT try to save once the current app domain is not the main domain anymore + // (no need to test _released) internal void SaveXmlToFile() { LogHelper.Info("Save Xml to file..."); - EnsureFileLock(); - - var xml = _xmlContent; // capture (atomic + volatile), immutable anyway try { + var xml = _xmlContent; // capture (atomic + volatile), immutable anyway + if (xml == null) return; + // delete existing file, if any DeleteXmlFile(); @@ -907,7 +1034,7 @@ order by umbracoNode.level, umbracoNode.sortOrder"; var directoryName = Path.GetDirectoryName(_xmlFileName); if (directoryName == null) throw new Exception(string.Format("Invalid XmlFileName \"{0}\".", _xmlFileName)); - if (System.IO.File.Exists(_xmlFileName) == false && Directory.Exists(directoryName) == false) + if (File.Exists(_xmlFileName) == false && Directory.Exists(directoryName) == false) Directory.CreateDirectory(directoryName); // save @@ -917,7 +1044,7 @@ order by umbracoNode.level, umbracoNode.sortOrder"; fs.Write(bytes, 0, bytes.Length); } - LogHelper.Debug("Saved Xml to file."); + LogHelper.Info("Saved Xml to file."); } catch (Exception e) { @@ -928,16 +1055,18 @@ order by umbracoNode.level, umbracoNode.sortOrder"; } } - // assumes file lock - internal async System.Threading.Tasks.Task SaveXmlToFileAsync() + // invoked by XmlCacheFilePersister ONLY and that one manages the MainDom, ie it + // will NOT try to save once the current app domain is not the main domain anymore + // (no need to test _released) + internal async Task SaveXmlToFileAsync() { LogHelper.Info("Save Xml to file..."); - EnsureFileLock(); - - var xml = _xmlContent; // capture (atomic + volatile), immutable anyway try { + var xml = _xmlContent; // capture (atomic + volatile), immutable anyway + if (xml == null) return; + // delete existing file, if any DeleteXmlFile(); @@ -945,7 +1074,7 @@ order by umbracoNode.level, umbracoNode.sortOrder"; var directoryName = Path.GetDirectoryName(_xmlFileName); if (directoryName == null) throw new Exception(string.Format("Invalid XmlFileName \"{0}\".", _xmlFileName)); - if (System.IO.File.Exists(_xmlFileName) == false && Directory.Exists(directoryName) == false) + if (File.Exists(_xmlFileName) == false && Directory.Exists(directoryName) == false) Directory.CreateDirectory(directoryName); // save @@ -955,7 +1084,7 @@ order by umbracoNode.level, umbracoNode.sortOrder"; await fs.WriteAsync(bytes, 0, bytes.Length); } - LogHelper.Debug("Saved Xml to file."); + LogHelper.Info("Saved Xml to file."); } catch (Exception e) { @@ -994,11 +1123,12 @@ order by umbracoNode.level, umbracoNode.sortOrder"; return sb.ToString(); } - // assumes file lock private XmlDocument LoadXmlFromFile() { + // do NOT try to load if we are not the main domain anymore + if (_released) return null; + LogHelper.Info("Load Xml from file..."); - EnsureFileLock(); try { @@ -1008,9 +1138,14 @@ order by umbracoNode.level, umbracoNode.sortOrder"; xml.Load(fs); } _lastFileRead = DateTime.UtcNow; - LogHelper.Info("Successfully loaded Xml from file."); + LogHelper.Info("Loaded Xml from file."); return xml; } + catch (FileNotFoundException) + { + LogHelper.Warn("Failed to load Xml, file does not exist."); + return null; + } catch (Exception e) { LogHelper.Error("Failed to load Xml from file.", e); @@ -1019,12 +1154,11 @@ order by umbracoNode.level, umbracoNode.sortOrder"; } } - // (files is always locked) private void DeleteXmlFile() { - if (System.IO.File.Exists(_xmlFileName) == false) return; - System.IO.File.SetAttributes(_xmlFileName, FileAttributes.Normal); - System.IO.File.Delete(_xmlFileName); + if (File.Exists(_xmlFileName) == false) return; + File.SetAttributes(_xmlFileName, FileAttributes.Normal); + File.Delete(_xmlFileName); } private void ReloadXmlFromFileIfChanged() @@ -1054,8 +1188,15 @@ order by umbracoNode.level, umbracoNode.sortOrder"; #region Manage change - // adds or updates a node (docNode) into a cache (xml) + //TODO remove as soon as we can break backward compatibility + [Obsolete("Use GetAddOrUpdateXmlNode which returns an updated Xml document.", false)] public static void AddOrUpdateXmlNode(XmlDocument xml, int id, int level, int parentId, XmlNode docNode) + { + GetAddOrUpdateXmlNode(xml, id, level, parentId, docNode); + } + + // adds or updates a node (docNode) into a cache (xml) + public static XmlDocument GetAddOrUpdateXmlNode(XmlDocument xml, int id, int level, int parentId, XmlNode docNode) { // sanity checks if (id != docNode.AttributeValue("id")) @@ -1068,8 +1209,13 @@ order by umbracoNode.level, umbracoNode.sortOrder"; // if the document is not there already then it's a new document // we must make sure that its document type exists in the schema - if (currentNode == null) - EnsureSchema(docNode.Name, xml); + if (currentNode == null && UseLegacySchema == false) + { + var xml2 = EnsureSchema(docNode.Name, xml); + if (ReferenceEquals(xml, xml2) == false) + docNode = xml2.ImportNode(docNode, true); + xml = xml2; + } // find the parent XmlNode parentNode = level == 1 @@ -1078,7 +1224,7 @@ order by umbracoNode.level, umbracoNode.sortOrder"; // no parent = cannot do anything if (parentNode == null) - return; + return xml; // insert/move the node under the parent if (currentNode == null) @@ -1146,6 +1292,7 @@ order by umbracoNode.level, umbracoNode.sortOrder"; // then we just need to ensure that currentNode is at the right position. // should be faster that moving all the nodes around. XmlHelper.SortNode(parentNode, ChildNodesXPath, currentNode, x => x.AttributeValue("sortOrder")); + return xml; } private static void TransferValuesFromDocumentXmlToPublishedXml(XmlNode documentNode, XmlNode publishedNode) diff --git a/src/Umbraco.Web/umbraco.presentation/helper.cs b/src/Umbraco.Web/umbraco.presentation/helper.cs index 205b6a064b..c1c90ee7ba 100644 --- a/src/Umbraco.Web/umbraco.presentation/helper.cs +++ b/src/Umbraco.Web/umbraco.presentation/helper.cs @@ -112,14 +112,17 @@ namespace umbraco foreach (var attributeValueItem in attributeValueSplit) { attributeValue = attributeValueItem; + var trimmedValue = attributeValue.Trim(); // Check for special variables (always in square-brackets like [name]) - if (attributeValueItem.StartsWith("[") && - attributeValueItem.EndsWith("]")) + if (trimmedValue.StartsWith("[") && + trimmedValue.EndsWith("]")) { + attributeValue = trimmedValue; + // find key name - var keyName = attributeValueItem.Substring(2, attributeValueItem.Length - 3); - var keyType = attributeValueItem.Substring(1, 1); + var keyName = attributeValue.Substring(2, attributeValue.Length - 3); + var keyType = attributeValue.Substring(1, 1); switch (keyType) { diff --git a/src/Umbraco.Web/umbraco.presentation/item.cs b/src/Umbraco.Web/umbraco.presentation/item.cs index 74a793ea7d..fc7abcb744 100644 --- a/src/Umbraco.Web/umbraco.presentation/item.cs +++ b/src/Umbraco.Web/umbraco.presentation/item.cs @@ -62,60 +62,51 @@ namespace umbraco else { // Loop through XML children we need to find the fields recursive - if (helper.FindAttribute(attributes, "recursive") == "true") + var recursive = helper.FindAttribute(attributes, "recursive") == "true"; + + if (publishedContent == null) { - if (publishedContent == null) - { - var recursiveVal = GetRecursiveValueLegacy(elements); - _fieldContent = recursiveVal.IsNullOrWhiteSpace() ? _fieldContent : recursiveVal; - } - else - { - var pval = publishedContent.GetPropertyValue(_fieldName, true); - var rval = pval == null ? string.Empty : pval.ToString(); - _fieldContent = rval.IsNullOrWhiteSpace() ? _fieldContent : rval; - } + var recursiveVal = GetRecursiveValueLegacy(elements); + _fieldContent = recursiveVal.IsNullOrWhiteSpace() ? _fieldContent : recursiveVal; + } + + //check for published content and get its value using that + if (publishedContent != null && (publishedContent.HasProperty(_fieldName) || recursive)) + { + var pval = publishedContent.GetPropertyValue(_fieldName, recursive); + var rval = pval == null ? string.Empty : pval.ToString(); + _fieldContent = rval.IsNullOrWhiteSpace() ? _fieldContent : rval; } else { - //check for published content and get its value using that - if (publishedContent != null && publishedContent.HasProperty(_fieldName)) - { - var pval = publishedContent.GetPropertyValue(_fieldName); - var rval = pval == null ? string.Empty : pval.ToString(); - _fieldContent = rval.IsNullOrWhiteSpace() ? _fieldContent : rval; - } - else - { - //get the vaue the legacy way (this will not parse locallinks, etc... since that is handled with ipublishedcontent) - var elt = elements[_fieldName]; - if (elt != null && string.IsNullOrEmpty(elt.ToString()) == false) - _fieldContent = elt.ToString().Trim(); - } - - //now we check if the value is still empty and if so we'll check useIfEmpty - if (string.IsNullOrEmpty(_fieldContent)) - { - var altFieldName = helper.FindAttribute(attributes, "useIfEmpty"); - if (string.IsNullOrEmpty(altFieldName) == false) - { - if (publishedContent != null && publishedContent.HasProperty(altFieldName)) - { - var pval = publishedContent.GetPropertyValue(altFieldName); - var rval = pval == null ? string.Empty : pval.ToString(); - _fieldContent = rval.IsNullOrWhiteSpace() ? _fieldContent : rval; - } - else - { - //get the vaue the legacy way (this will not parse locallinks, etc... since that is handled with ipublishedcontent) - var elt = elements[altFieldName]; - if (elt != null && string.IsNullOrEmpty(elt.ToString()) == false) - _fieldContent = elt.ToString().Trim(); - } - } - } - + //get the vaue the legacy way (this will not parse locallinks, etc... since that is handled with ipublishedcontent) + var elt = elements[_fieldName]; + if (elt != null && string.IsNullOrEmpty(elt.ToString()) == false) + _fieldContent = elt.ToString().Trim(); } + + //now we check if the value is still empty and if so we'll check useIfEmpty + if (string.IsNullOrEmpty(_fieldContent)) + { + var altFieldName = helper.FindAttribute(attributes, "useIfEmpty"); + if (string.IsNullOrEmpty(altFieldName) == false) + { + if (publishedContent != null && (publishedContent.HasProperty(altFieldName) || recursive)) + { + var pval = publishedContent.GetPropertyValue(altFieldName, recursive); + var rval = pval == null ? string.Empty : pval.ToString(); + _fieldContent = rval.IsNullOrWhiteSpace() ? _fieldContent : rval; + } + else + { + //get the vaue the legacy way (this will not parse locallinks, etc... since that is handled with ipublishedcontent) + var elt = elements[altFieldName]; + if (elt != null && string.IsNullOrEmpty(elt.ToString()) == false) + _fieldContent = elt.ToString().Trim(); + } + } + } + } ParseItem(attributes); diff --git a/src/Umbraco.Web/umbraco.presentation/library.cs b/src/Umbraco.Web/umbraco.presentation/library.cs index eb71c179ea..9439cde851 100644 --- a/src/Umbraco.Web/umbraco.presentation/library.cs +++ b/src/Umbraco.Web/umbraco.presentation/library.cs @@ -552,7 +552,7 @@ namespace umbraco XmlDocument mXml = new XmlDocument(); mXml.LoadXml(m.ToXml(mXml, false).OuterXml); XPathNavigator xp = mXml.CreateNavigator(); - return xp.Select("/node"); + return xp.Select("/node()"); } XmlDocument xd = new XmlDocument(); diff --git a/src/Umbraco.Web/umbraco.presentation/macro.cs b/src/Umbraco.Web/umbraco.presentation/macro.cs index dd74c370f1..753f04d459 100644 --- a/src/Umbraco.Web/umbraco.presentation/macro.cs +++ b/src/Umbraco.Web/umbraco.presentation/macro.cs @@ -24,6 +24,7 @@ using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Macros; using Umbraco.Core.Models; +using Umbraco.Core.Services; using Umbraco.Core.Xml.XPath; using Umbraco.Core.Profiling; using umbraco.interfaces; @@ -60,7 +61,7 @@ namespace umbraco private readonly StringBuilder _content = new StringBuilder(); private const string MacrosAddedKey = "macrosAdded"; public IList Exceptions = new List(); - + protected static ISqlHelper SqlHelper { get { return Application.SqlHelper; } @@ -155,7 +156,7 @@ namespace umbraco public macro(string alias) { Macro m = Macro.GetByAlias(alias); - Model = new MacroModel(m); + Model = new MacroModel(m); } public MacroModel Model { get; set; } @@ -166,7 +167,7 @@ namespace umbraco } public static macro GetMacro(int id) - { + { return new macro(id); } @@ -235,7 +236,7 @@ namespace umbraco /// An event that is raised just before the macro is rendered allowing developers to modify the macro before it executes. /// public static event TypedEventHandler MacroRendering; - + /// /// Raises the MacroRendering event /// @@ -263,7 +264,7 @@ namespace umbraco using (DisposableTimer.DebugDuration(macroInfo)) { - TraceInfo("renderMacro", macroInfo, excludeProfiling:true); + TraceInfo("renderMacro", macroInfo, excludeProfiling: true); StateHelper.SetContextValue(MacrosAddedKey, StateHelper.GetContextValue(MacrosAddedKey) + 1); @@ -283,12 +284,13 @@ namespace umbraco { var renderFailed = false; var macroType = Model.MacroType != MacroTypes.Unknown - ? (int) Model.MacroType + ? (int)Model.MacroType : MacroType; + var textService = ApplicationContext.Current.Services.TextService; switch (macroType) { - case (int) MacroTypes.PartialView: + case (int)MacroTypes.PartialView: //error handler for partial views, is an action because we need to re-use it twice below Func handleError = e => @@ -304,12 +306,14 @@ namespace umbraco Exception = e, Behaviour = UmbracoConfig.For.UmbracoSettings().Content.MacroErrorBehaviour }; - return GetControlForErrorBehavior("Error loading Partial View script (file: " + ScriptFile + ")", macroErrorEventArgs); + + var errorMessage = textService.Localize("errors/macroErrorLoadingPartialView", new[] { ScriptFile }); + return GetControlForErrorBehavior(errorMessage, macroErrorEventArgs); }; using (DisposableTimer.DebugDuration("Executing Partial View: " + Model.TypeName)) { - TraceInfo("umbracoMacro", "Partial View added (" + Model.TypeName + ")", excludeProfiling:true); + TraceInfo("umbracoMacro", "Partial View added (" + Model.TypeName + ")", excludeProfiling: true); try { var result = LoadPartialViewMacro(Model); @@ -344,13 +348,13 @@ namespace umbraco break; } - case (int) MacroTypes.UserControl: + case (int)MacroTypes.UserControl: using (DisposableTimer.DebugDuration("Executing UserControl: " + Model.TypeName)) { try { - TraceInfo("umbracoMacro", "Usercontrol added (" + Model.TypeName + ")", excludeProfiling:true); + TraceInfo("umbracoMacro", "Usercontrol added (" + Model.TypeName + ")", excludeProfiling: true); // Add tilde for v4 defined macros if (string.IsNullOrEmpty(Model.TypeName) == false && @@ -376,7 +380,8 @@ namespace umbraco Behaviour = UmbracoConfig.For.UmbracoSettings().Content.MacroErrorBehaviour }; - macroControl = GetControlForErrorBehavior("Error loading userControl '" + Model.TypeName + "'", macroErrorEventArgs); + var errorMessage = textService.Localize("errors/macroErrorLoadingUsercontrol", new[] { Model.TypeName }); + macroControl = GetControlForErrorBehavior(errorMessage, macroErrorEventArgs); //if it is null, then we are supposed to throw the (original) exception // see: http://issues.umbraco.org/issue/U4-497 at the end if (macroControl == null) @@ -390,9 +395,9 @@ namespace umbraco case (int) MacroTypes.Xslt: macroControl = LoadMacroXslt(this, Model, pageElements, true); - break; + break; - case (int) MacroTypes.Unknown: + case (int)MacroTypes.Unknown: default: if (GlobalSettings.DebugMode) { @@ -424,8 +429,8 @@ namespace umbraco /// private Control AddMacroResultToCache(Control macroControl) { - // Add result to cache if successful - if (Model.CacheDuration > 0) + // Add result to cache if successful (and cache is enabled) + if (UmbracoContext.Current.InPreviewMode == false && Model.CacheDuration > 0) { // do not add to cache if there's no member and it should cache by personalization if (!Model.CacheByMember || (Model.CacheByMember && Member.IsLoggedOn())) @@ -488,9 +493,9 @@ namespace umbraco CacheItemPriority.NotRemovable, new TimeSpan(0, 0, Model.CacheDuration), () => DateTime.Now); - + } - + } } } @@ -678,12 +683,12 @@ namespace umbraco CacheItemPriority.Default, new CacheDependency(IOHelper.MapPath(SystemDirectories.Xslt + "/" + XsltFile)), () => + { + using (var xslReader = new XmlTextReader(IOHelper.MapPath(SystemDirectories.Xslt.EnsureEndsWith('/') + XsltFile))) { - using (var xslReader = new XmlTextReader(IOHelper.MapPath(SystemDirectories.Xslt.EnsureEndsWith('/') + XsltFile))) - { - return CreateXsltTransform(xslReader, GlobalSettings.DebugMode); - } - }); + return CreateXsltTransform(xslReader, GlobalSettings.DebugMode); + } + }); } public void UpdateMacroModel(Hashtable attributes) @@ -758,7 +763,7 @@ namespace umbraco if (!canNavigate) { - // get master xml document + // get master xml document var cache = UmbracoContext.Current.ContentCache.InnerCache as Umbraco.Web.PublishedCache.XmlPublishedCache.PublishedContentCache; if (cache == null) throw new Exception("Unsupported IPublishedContentCache, only the Xml one is supported."); XmlDocument umbracoXml = cache.GetXml(UmbracoContext.Current, UmbracoContext.Current.InPreviewMode); @@ -790,7 +795,8 @@ namespace umbraco "

    " + HttpContext.Current.Server.HtmlEncode(outerXml) + "

    "); } - + + var textService = ApplicationContext.Current.Services.TextService; try { var xsltFile = GetXslt(XsltFile); @@ -799,9 +805,9 @@ namespace umbraco { try { - var transformed = canNavigate - ? GetXsltTransformResult(macroNavigator, contentNavigator, xsltFile) // better? - : GetXsltTransformResult(macroXml, xsltFile); // document + var transformed = canNavigate + ? GetXsltTransformResult(macroNavigator, contentNavigator, xsltFile) // better? + : GetXsltTransformResult(macroXml, xsltFile); // document var result = CreateControlsFromText(transformed); return result; @@ -810,9 +816,11 @@ namespace umbraco { Exceptions.Add(e); LogHelper.WarnWithException("Error parsing XSLT file", e); - + var macroErrorEventArgs = new MacroErrorEventArgs { Name = Model.Name, Alias = Model.Alias, ItemKey = Model.Xslt, Exception = e, Behaviour = UmbracoConfig.For.UmbracoSettings().Content.MacroErrorBehaviour }; - var macroControl = GetControlForErrorBehavior("Error parsing XSLT file: \\xslt\\" + XsltFile, macroErrorEventArgs); + + var errorMessage = textService.Localize("errors/macroErrorParsingXSLTFile", new[] { XsltFile }); + var macroControl = GetControlForErrorBehavior(errorMessage, macroErrorEventArgs); //if it is null, then we are supposed to throw the (original) exception // see: http://issues.umbraco.org/issue/U4-497 at the end if (macroControl == null && throwError) @@ -820,8 +828,8 @@ namespace umbraco throw; } return macroControl; - } - } + } + } } catch (Exception e) { @@ -830,7 +838,8 @@ namespace umbraco // Invoke any error handlers for this macro var macroErrorEventArgs = new MacroErrorEventArgs { Name = Model.Name, Alias = Model.Alias, ItemKey = Model.Xslt, Exception = e, Behaviour = UmbracoConfig.For.UmbracoSettings().Content.MacroErrorBehaviour }; - var macroControl = GetControlForErrorBehavior("Error reading XSLT file: \\xslt\\" + XsltFile, macroErrorEventArgs); + var errorMessage = textService.Localize("errors/macroErrorReadingXSLTFile", new[] { XsltFile }); + var macroControl = GetControlForErrorBehavior(errorMessage + XsltFile, macroErrorEventArgs); //if it is null, then we are supposed to throw the (original) exception // see: http://issues.umbraco.org/issue/U4-497 at the end if (macroControl == null && throwError) @@ -839,7 +848,7 @@ namespace umbraco } return macroControl; } - } + } } // gets the control for the macro, using GetXsltTransform methods for execution @@ -920,12 +929,12 @@ namespace umbraco XsltArgumentList xslArgs; using (DisposableTimer.DebugDuration("Adding XSLT Extensions")) - { + { xslArgs = AddXsltExtensions(); var lib = new library(); xslArgs.AddExtensionObject("urn:umbraco.library", lib); } - + // Add parameters if (parameters == null || !parameters.ContainsKey("currentPage")) { @@ -941,8 +950,8 @@ namespace umbraco using (DisposableTimer.DebugDuration("Executing XSLT transform")) { xslt.Transform(macroXml.CreateNavigator(), xslArgs, tw); - } - return TemplateUtilities.ResolveUrlsFromTextString(tw.ToString()); + } + return TemplateUtilities.ResolveUrlsFromTextString(tw.ToString()); } // gets the result of the xslt transform with no parameters - Navigator mode @@ -1002,7 +1011,7 @@ namespace umbraco return XsltExtensionsResolver.Current.XsltExtensions .ToDictionary(x => x.Namespace, x => x.ExtensionObject); } - + /// /// Returns an XSLT argument list with all XSLT extensions added, /// both predefined and configured ones. @@ -1016,9 +1025,9 @@ namespace umbraco { string extensionNamespace = "urn:" + extension.Key; xslArgs.AddExtensionObject(extensionNamespace, extension.Value); - TraceInfo("umbracoXsltExtension", - String.Format("Extension added: {0}, {1}", - extensionNamespace, extension.Value.GetType().Name)); + TraceInfo("umbracoXsltExtension", + String.Format("Extension added: {0}, {1}", + extensionNamespace, extension.Value.GetType().Name)); } return xslArgs; @@ -1036,12 +1045,12 @@ namespace umbraco // if no value is passed, then use the current "pageID" as value var contentId = macroPropertyValue == string.Empty ? UmbracoContext.Current.PageId.ToString() : macroPropertyValue; - TraceInfo("umbracoMacro", - "Xslt node adding search start (" + macroPropertyAlias + ",'" + - macroPropertyValue + "')"); - + TraceInfo("umbracoMacro", + "Xslt node adding search start (" + macroPropertyAlias + ",'" + + macroPropertyValue + "')"); + //TODO: WE need to fix this so that we give control of this stuff over to the actual parameter editors! - + switch (macroPropertyType) { case "contentTree": @@ -1071,7 +1080,7 @@ namespace umbraco macroXmlNode.AppendChild(currentNode); - break; + break; case "contentAll": macroXmlNode.AppendChild(macroXml.ImportNode(umbracoXml.DocumentElement, true)); @@ -1079,33 +1088,33 @@ namespace umbraco case "contentRandom": XmlNode source = umbracoXml.GetElementById(contentId); - if (source != null) - { - var sourceList = source.SelectNodes("node|*[@isDoc]"); - if (sourceList.Count > 0) - { - int rndNumber; - var r = library.GetRandom(); - lock (r) - { - rndNumber = r.Next(sourceList.Count); - } - var node = macroXml.ImportNode(sourceList[rndNumber], true); - // remove all sub content nodes - foreach (XmlNode n in node.SelectNodes("node|*[@isDoc]")) - node.RemoveChild(n); + if (source != null) + { + var sourceList = source.SelectNodes("node|*[@isDoc]"); + if (sourceList.Count > 0) + { + int rndNumber; + var r = library.GetRandom(); + lock (r) + { + rndNumber = r.Next(sourceList.Count); + } + var node = macroXml.ImportNode(sourceList[rndNumber], true); + // remove all sub content nodes + foreach (XmlNode n in node.SelectNodes("node|*[@isDoc]")) + node.RemoveChild(n); - macroXmlNode.AppendChild(node); - } - else - TraceWarn("umbracoMacro", - "Error adding random node - parent (" + macroPropertyValue + - ") doesn't have children!"); - } - else - TraceWarn("umbracoMacro", - "Error adding random node - parent (" + macroPropertyValue + - ") doesn't exists!"); + macroXmlNode.AppendChild(node); + } + else + TraceWarn("umbracoMacro", + "Error adding random node - parent (" + macroPropertyValue + + ") doesn't have children!"); + } + else + TraceWarn("umbracoMacro", + "Error adding random node - parent (" + macroPropertyValue + + ") doesn't exists!"); break; case "mediaCurrent": @@ -1126,14 +1135,14 @@ namespace umbraco // add parameters to the macro parameters collection private void AddMacroParameter(ICollection parameters, NavigableNavigator contentNavigator, NavigableNavigator mediaNavigator, - string macroPropertyAlias,string macroPropertyType, string macroPropertyValue) + string macroPropertyAlias, string macroPropertyType, string macroPropertyValue) { // if no value is passed, then use the current "pageID" as value var contentId = macroPropertyValue == string.Empty ? UmbracoContext.Current.PageId.ToString() : macroPropertyValue; - TraceInfo("umbracoMacro", - "Xslt node adding search start (" + macroPropertyAlias + ",'" + - macroPropertyValue + "')"); + TraceInfo("umbracoMacro", + "Xslt node adding search start (" + macroPropertyAlias + ",'" + + macroPropertyValue + "')"); // beware! do not use the raw content- or media- navigators, but clones !! @@ -1189,15 +1198,15 @@ namespace umbraco if (node != null) { nav = contentNavigator.CloneWithNewRoot(node.Id.ToString(CultureInfo.InvariantCulture)); - parameters.Add(new MacroNavigator.MacroParameter(macroPropertyAlias, nav, 0)); + parameters.Add(new MacroNavigator.MacroParameter(macroPropertyAlias, nav, 0)); } else throw new InvalidOperationException("Iterator contains non-INavigableContent elements."); } else - TraceWarn("umbracoMacro", - "Error adding random node - parent (" + macroPropertyValue + - ") doesn't have children!"); + TraceWarn("umbracoMacro", + "Error adding random node - parent (" + macroPropertyValue + + ") doesn't have children!"); } else TraceWarn("umbracoMacro", @@ -1220,13 +1229,13 @@ namespace umbraco #endregion - /// - /// Renders a Partial View Macro - /// - /// - /// + /// + /// Renders a Partial View Macro + /// + /// + /// internal PartialViewMacroResult LoadPartialViewMacro(MacroModel macro) - { + { var retVal = new PartialViewMacroResult(); IMacroEngine engine = null; @@ -1236,7 +1245,7 @@ namespace umbraco retVal.Result = ret; return retVal; } - + /// /// Loads a custom or webcontrol using reflection into the macro object /// @@ -1265,8 +1274,8 @@ namespace umbraco if (!File.Exists(currentAss)) return new LiteralControl("Unable to load user control because is does not exist: " + fileName); asm = Assembly.LoadFrom(currentAss); - - TraceInfo("umbracoMacro", "Assembly file " + currentAss + " LOADED!!"); + + TraceInfo("umbracoMacro", "Assembly file " + currentAss + " LOADED!!"); } catch { @@ -1275,7 +1284,7 @@ namespace umbraco ".dll"))); } - TraceInfo("umbracoMacro", string.Format("Assembly Loaded from ({0}.dll)", fileName)); + TraceInfo("umbracoMacro", string.Format("Assembly Loaded from ({0}.dll)", fileName)); type = asm.GetType(controlName); if (type == null) return new LiteralControl(string.Format("Unable to get type {0} from assembly {1}", @@ -1302,8 +1311,8 @@ namespace umbraco { var prop = type.GetProperty(mp.Key); if (prop == null) - { - TraceWarn("macro", string.Format("control property '{0}' doesn't exist or aren't accessible (public)", mp.Key)); + { + TraceWarn("macro", string.Format("control property '{0}' doesn't exist or aren't accessible (public)", mp.Key)); continue; } @@ -1339,17 +1348,17 @@ namespace umbraco } } - /// - /// Loads an usercontrol using reflection into the macro object - /// - /// Filename of the usercontrol - ie. ~wulff.ascx - /// - /// The page elements. - /// + /// + /// Loads an usercontrol using reflection into the macro object + /// + /// Filename of the usercontrol - ie. ~wulff.ascx + /// + /// The page elements. + /// public Control LoadUserControl(string fileName, MacroModel model, Hashtable pageElements) { - Mandate.ParameterNotNullOrEmpty(fileName, "fileName"); - Mandate.ParameterNotNull(model, "model"); + Mandate.ParameterNotNullOrEmpty(fileName, "fileName"); + Mandate.ParameterNotNull(model, "model"); try { @@ -1378,7 +1387,7 @@ namespace umbraco } catch (Exception e) { - LogHelper.WarnWithException(string.Format("Error creating usercontrol ({0})", fileName), true, e); + LogHelper.WarnWithException(string.Format("Error creating usercontrol ({0})", fileName), true, e); throw; } } @@ -1392,7 +1401,7 @@ namespace umbraco //Trace out to profiling... doesn't actually profile, just for informational output. if (excludeProfiling == false) { - using (ProfilerResolver.Current.Profiler.Step(string.Format("{0}", message))) + using (ApplicationContext.Current.ProfilingLogger.TraceDuration(string.Format("{0}", message))) { } } @@ -1401,30 +1410,30 @@ namespace umbraco private static void TraceWarn(string category, string message, bool excludeProfiling = false) { if (HttpContext.Current != null) - HttpContext.Current.Trace.Warn(category, message); + HttpContext.Current.Trace.Warn(category, message); //Trace out to profiling... doesn't actually profile, just for informational output. if (excludeProfiling == false) { - using (ProfilerResolver.Current.Profiler.Step(string.Format("Warning: {0}", message))) + using (ApplicationContext.Current.ProfilingLogger.TraceDuration(string.Format("Warning: {0}", message))) { } } } private static void TraceWarn(string category, string message, Exception ex, bool excludeProfiling = false) - { - if (HttpContext.Current != null) - HttpContext.Current.Trace.Warn(category, message, ex); + { + if (HttpContext.Current != null) + HttpContext.Current.Trace.Warn(category, message, ex); //Trace out to profiling... doesn't actually profile, just for informational output. if (excludeProfiling == false) { - using (ProfilerResolver.Current.Profiler.Step(string.Format("{0}, Error: {1}", message, ex))) + using (ApplicationContext.Current.ProfilingLogger.TraceDuration(string.Format("{0}, Error: {1}", message, ex))) { } } - } + } public static string RenderMacroStartTag(Hashtable attributes, int pageId, Guid versionId) { @@ -1492,7 +1501,7 @@ namespace umbraco { return "Cannot render macro content in the rich text editor when the application is running in a Partial Trust environment"; } - + string tempAlias = (attributes["macroalias"] != null) ? attributes["macroalias"].ToString() : attributes["macroAlias"].ToString(); @@ -1519,6 +1528,10 @@ namespace umbraco // propagate the user's context // zb-00004 #29956 : refactor cookies names & handling + + //TODO: This is the worst thing ever. This will also not work if people decide to put their own + // custom auth system in place. + HttpCookie inCookie = StateHelper.Cookies.UserContext.RequestCookie; var cookie = new Cookie(inCookie.Name, inCookie.Value, inCookie.Path, HttpContext.Current.Request.ServerVariables["SERVER_NAME"]); diff --git a/src/Umbraco.Web/umbraco.presentation/template.cs b/src/Umbraco.Web/umbraco.presentation/template.cs index eeee729ec8..93f58aa942 100644 --- a/src/Umbraco.Web/umbraco.presentation/template.cs +++ b/src/Umbraco.Web/umbraco.presentation/template.cs @@ -485,8 +485,12 @@ namespace umbraco var t = ApplicationContext.Current.ApplicationCache.GetCacheItem( string.Format("{0}{1}", CacheKeys.TemplateFrontEndCacheKey, tId), () => { - using (var templateData = SqlHelper.ExecuteReader("select nodeId, alias, master, text, design from cmsTemplate inner join umbracoNode node on node.id = cmsTemplate.nodeId where nodeId = @templateID", SqlHelper.CreateParameter("@templateID", templateID))) - { + using (var templateData = SqlHelper.ExecuteReader(@"select nodeId, alias, node.parentID as master, text, design +from cmsTemplate +inner join umbracoNode node on (node.id = cmsTemplate.nodeId) +where nodeId = @templateID", + SqlHelper.CreateParameter("@templateID", templateID))) + { if (templateData.Read()) { // Get template master and replace content where the template diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/FileSystemTree.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/FileSystemTree.cs index 2349d8943f..aed6009d86 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/FileSystemTree.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/FileSystemTree.cs @@ -16,18 +16,18 @@ namespace umbraco.cms.presentation.Trees { public FileSystemTree(string application) : base(application) { } - + public override abstract void RenderJS(ref System.Text.StringBuilder Javascript); protected override abstract void CreateRootNode(ref XmlTreeNode rootNode); - protected abstract string FilePath { get;} - protected abstract string FileSearchPattern { get;} + protected abstract string FilePath { get; } + protected abstract string FileSearchPattern { get; } /// /// Inheritors can override this method to modify the file node that is created. /// /// - protected virtual void OnRenderFileNode(ref XmlTreeNode xNode) { } + protected virtual void OnRenderFileNode(ref XmlTreeNode xNode) { } /// /// Inheritors can override this method to modify the folder node that is created. @@ -51,7 +51,9 @@ namespace umbraco.cms.presentation.Trees } DirectoryInfo dirInfo = new DirectoryInfo(path); - DirectoryInfo[] dirInfos = dirInfo.GetDirectories(); + + + DirectoryInfo[] dirInfos = dirInfo.Exists ? dirInfo.GetDirectories() : new DirectoryInfo[] { }; var args = new TreeEventArgs(tree); OnBeforeTreeRender(dirInfo, args); @@ -61,15 +63,15 @@ namespace umbraco.cms.presentation.Trees if ((dir.Attributes & FileAttributes.Hidden) == 0) { XmlTreeNode xDirNode = XmlTreeNode.Create(this); - xDirNode.NodeID = orgPath + dir.Name; + xDirNode.NodeID = orgPath + dir.Name; xDirNode.Menu.Clear(); xDirNode.Text = dir.Name; xDirNode.Action = string.Empty; xDirNode.Source = GetTreeServiceUrl(orgPath + dir.Name); xDirNode.Icon = FolderIcon; xDirNode.OpenIcon = FolderIconOpen; - xDirNode.HasChildren = dir.GetFiles().Length > 0 || dir.GetDirectories().Length > 0; - + xDirNode.HasChildren = dir.GetFiles().Length > 0 || dir.GetDirectories().Length > 0; + OnRenderFolderNode(ref xDirNode); OnBeforeNodeRender(ref tree, ref xDirNode, EventArgs.Empty); if (xDirNode != null) @@ -77,8 +79,8 @@ namespace umbraco.cms.presentation.Trees tree.Add(xDirNode); OnAfterNodeRender(ref tree, ref xDirNode, EventArgs.Empty); } - - + + } } @@ -88,24 +90,28 @@ namespace umbraco.cms.presentation.Trees bool filterByMultipleExtensions = FileSearchPattern.Contains(","); FileInfo[] fileInfo; - if (filterByMultipleExtensions){ - fileInfo = dirInfo.GetFiles(); + if (filterByMultipleExtensions) + { + fileInfo = dirInfo.Exists ? dirInfo.GetFiles() : new FileInfo[] {}; allowedExtensions = FileSearchPattern.ToLower().Split(','); - }else - fileInfo = dirInfo.GetFiles(FileSearchPattern); - + } + else + { + fileInfo = dirInfo.Exists ? dirInfo.GetFiles(FileSearchPattern) : new FileInfo[] { }; + } + foreach (FileInfo file in fileInfo) { if ((file.Attributes & FileAttributes.Hidden) == 0) { if (filterByMultipleExtensions && Array.IndexOf(allowedExtensions, file.Extension.ToLower().Trim('.')) < 0) continue; - + XmlTreeNode xFileNode = XmlTreeNode.Create(this); - xFileNode.NodeID = orgPath + file.Name; + xFileNode.NodeID = orgPath + file.Name; xFileNode.Text = file.Name; if (!((orgPath == ""))) - xFileNode.Action = "javascript:openFile('" + orgPath + file.Name + "');"; + xFileNode.Action = "javascript:openFile('" + orgPath + file.Name + "');"; else xFileNode.Action = "javascript:openFile('" + file.Name + "');"; xFileNode.Icon = "doc.gif"; @@ -118,11 +124,11 @@ namespace umbraco.cms.presentation.Trees tree.Add(xFileNode); OnAfterNodeRender(ref tree, ref xFileNode, EventArgs.Empty); } - + } } OnAfterTreeRender(dirInfo, args); - } + } } } diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeDefinitionCollection.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeDefinitionCollection.cs index 498af0da4a..6f52b480a3 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeDefinitionCollection.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeDefinitionCollection.cs @@ -70,7 +70,7 @@ namespace umbraco.cms.presentation.Trees var foundTree = this.Find( delegate(TreeDefinition t) { - // zb-00002 #29929 : use IsAssignableFrom instead of Equal, otherwise you can't override build-in + // zb-00002 #29929 : use IsAssignableFrom instead of Equal, otherwise you can't override built-in // trees because for ex. PermissionEditor.aspx.cs OnInit calls FindTree() return typeof(T).IsAssignableFrom(t.TreeType); } diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadDictionary.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadDictionary.cs index 821f50a5e8..500721198a 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadDictionary.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadDictionary.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Text; using umbraco.businesslogic; using umbraco.cms.businesslogic; @@ -53,7 +54,7 @@ namespace umbraco else tmp = new Dictionary.DictionaryItem(this.id).Children; - foreach (Dictionary.DictionaryItem di in tmp) + foreach (Dictionary.DictionaryItem di in tmp.OrderBy(a => a.key)) { XmlTreeNode xNode = XmlTreeNode.Create(this); xNode.NodeID = di.id.ToString(); //dictionary_ + id.. diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadMediaTypes.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadMediaTypes.cs index f3493ef8e3..2d097f44f5 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadMediaTypes.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadMediaTypes.cs @@ -12,7 +12,7 @@ using Umbraco.Web.umbraco.presentation.umbraco.Trees; namespace umbraco { - [Tree(Constants.Applications.Settings, "mediaTypes", "Media Types", sortOrder: 5)] + [Obsolete("This class is no longer used and will be removed from the codebase in future versions")] public class loadMediaTypes : BaseTree { public loadMediaTypes(string application) : base(application) { } diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadMemberGroups.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadMemberGroups.cs index 78d630c1ff..3e6b72647a 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadMemberGroups.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadMemberGroups.cs @@ -12,7 +12,7 @@ namespace umbraco /// /// Handles loading of the member groups into the application tree /// - [Tree(Constants.Applications.Members, "memberGroup", "Member Groups", sortOrder: 1)] + [Tree(Constants.Applications.Members, "memberGroups", "Member Groups", sortOrder: 1)] public class loadMemberGroups : BaseTree { public loadMemberGroups(string application) : base(application) { } @@ -24,7 +24,7 @@ namespace umbraco { rootNode.Text = ui.Text("memberRoles"); } - rootNode.NodeType = "init" + TreeAlias; + rootNode.NodeType = "initmemberGroup"; rootNode.NodeID = "init"; } @@ -48,29 +48,26 @@ function openMemberGroup(id) { Array.Sort(roles); foreach(string role in roles) { -// MemberGroup[] MemberGroups = MemberGroup.GetAll; - -// for (int i = 0; i < MemberGroups.Length; i++) -// { - XmlTreeNode xNode = XmlTreeNode.Create(this); - xNode.NodeID = role; - xNode.Text = role; - xNode.Action = "javascript:openMemberGroup('" + HttpContext.Current.Server.UrlEncode(role.Replace("'", "\\'")) + "');"; - xNode.Icon = "icon-users"; - if (!Member.IsUsingUmbracoRoles()) + if (role.StartsWith(Constants.Conventions.Member.InternalRolePrefix) == false) { - xNode.Menu = null; - } + XmlTreeNode xNode = XmlTreeNode.Create(this); + xNode.NodeID = role; + xNode.Text = role; + xNode.Action = "javascript:openMemberGroup('" + HttpContext.Current.Server.UrlEncode(role.Replace("'", "\\'")) + "');"; + xNode.Icon = "icon-users"; + if (!Member.IsUsingUmbracoRoles()) + { + xNode.Menu = null; + } - OnBeforeNodeRender(ref tree, ref xNode, EventArgs.Empty); - if (xNode != null) - { - tree.Add(xNode); + OnBeforeNodeRender(ref tree, ref xNode, EventArgs.Empty); + if (xNode != null) + { + tree.Add(xNode); + } + OnAfterNodeRender(ref tree, ref xNode, EventArgs.Empty); } - OnAfterNodeRender(ref tree, ref xNode, EventArgs.Empty); } } - } - } diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadMemberTypes.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadMemberTypes.cs index a0017fe450..ce8aaeb9eb 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadMemberTypes.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadMemberTypes.cs @@ -10,10 +10,10 @@ using Umbraco.Core; namespace umbraco { - /// - /// Handles loading of the member types into the application tree - /// - [Tree(Constants.Applications.Members, "memberType", "Member Types", sortOrder: 2)] + /// + /// Handles loading of the member types into the application tree + /// + [Obsolete("This class is no longer used and will be removed from the codebase in future versions")] public class loadMemberTypes : BaseTree { public loadMemberTypes(string application) : base(application) { } @@ -82,7 +82,7 @@ function openMemberType(id) { treeElement.SetAttribute("src", ""); treeElement.SetAttribute("icon", "icon-users"); treeElement.SetAttribute("openIcon", "icon-users"); - treeElement.SetAttribute("nodeType", "memberType"); + treeElement.SetAttribute("nodeType", "memberTypes"); root.AppendChild(treeElement); } } diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadNodeTypes.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadNodeTypes.cs index 8df83291fe..c2940a8a5d 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadNodeTypes.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadNodeTypes.cs @@ -14,7 +14,7 @@ using Umbraco.Web.umbraco.presentation.umbraco.Trees; namespace umbraco { - [Tree(Constants.Applications.Settings, "nodeTypes", "Document Types", sortOrder: 6)] + [Obsolete("This class is no longer used and will be removed from the codebase in future versions")] public class loadNodeTypes : BaseTree { diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadTemplates.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadTemplates.cs index 4da68849aa..560776181f 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadTemplates.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadTemplates.cs @@ -69,7 +69,7 @@ namespace umbraco private void RenderTemplateFolderItems(string folder, string folderPath, ref XmlTree tree) { string relPath = SystemDirectories.Masterpages + "/" + folder; - if (!string.IsNullOrEmpty(folderPath)) + if (string.IsNullOrEmpty(folderPath) == false) relPath += folderPath; string fullPath = IOHelper.MapPath(relPath); @@ -142,26 +142,31 @@ namespace umbraco { if (base.m_id == -1) { - foreach (string s in Directory.GetDirectories(IOHelper.MapPath(SystemDirectories.Masterpages))) + var dir = new DirectoryInfo(IOHelper.MapPath(SystemDirectories.Masterpages)); + if (dir.Exists) { - var _s = Path.GetFileNameWithoutExtension(s); - - XmlTreeNode xNode = XmlTreeNode.Create(this); - xNode.NodeID = _s; - xNode.Text = _s; - xNode.Icon = "icon-folder"; - xNode.OpenIcon = "icon-folder"; - xNode.Source = GetTreeServiceUrl(_s) + "&folder=" + _s; - xNode.HasChildren = true; - xNode.Menu.Clear(); - xNode.Menu.Add(ActionRefresh.Instance); - - OnBeforeNodeRender(ref tree, ref xNode, EventArgs.Empty); - if (xNode != null) + foreach (var s in dir.GetDirectories()) { - tree.Add(xNode); - OnAfterNodeRender(ref tree, ref xNode, EventArgs.Empty); + var _s = Path.GetFileNameWithoutExtension(s.FullName); + + XmlTreeNode xNode = XmlTreeNode.Create(this); + xNode.NodeID = _s; + xNode.Text = _s; + xNode.Icon = "icon-folder"; + xNode.OpenIcon = "icon-folder"; + xNode.Source = GetTreeServiceUrl(_s) + "&folder=" + _s; + xNode.HasChildren = true; + xNode.Menu.Clear(); + xNode.Menu.Add(ActionRefresh.Instance); + + OnBeforeNodeRender(ref tree, ref xNode, EventArgs.Empty); + if (xNode != null) + { + tree.Add(xNode); + OnAfterNodeRender(ref tree, ref xNode, EventArgs.Empty); + } } + } } } diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs deleted file mode 100644 index f50597388d..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs +++ /dev/null @@ -1,1644 +0,0 @@ -//TODO: This needs a full rewrite in angular! kept here for reference for now - -//using System; -//using System.Collections; -//using System.Collections.Generic; -//using System.Data; -//using System.Globalization; -//using System.Linq; -//using System.Web; -//using System.Web.Mvc; -//using System.Threading.Tasks; -//using System.Web; -//using System.Web.Routing; -//using System.Web.UI; -//using System.Web.UI.HtmlControls; -//using System.Web.UI.WebControls; -//using ClientDependency.Core; -//using Umbraco.Core; -//using Umbraco.Core.Configuration; -//using Umbraco.Core.Logging; -//using Umbraco.Core.Models; -//using Umbraco.Core.Strings; -//using Umbraco.Web.UI.Controls; -//using umbraco.BusinessLogic; -//using umbraco.cms.businesslogic; -//using umbraco.cms.businesslogic.propertytype; -//using umbraco.cms.businesslogic.web; -//using umbraco.controls.GenericProperties; -//using umbraco.presentation; -//using umbraco.BasePages; -//using Constants = Umbraco.Core.Constants; -//using ContentType = umbraco.cms.businesslogic.ContentType; -//using PropertyType = Umbraco.Core.Models.PropertyType; - -//namespace umbraco.controls -//{ - -// [ClientDependency(ClientDependencyType.Javascript, "ui/jqueryui.js", "UmbracoClient")] -// [ClientDependency(ClientDependencyType.Javascript, "ui/jquery.dd.js", "UmbracoClient")] -// [ClientDependency(ClientDependencyType.Css, "ui/dd.css", "UmbracoClient")] -// [ClientDependency(ClientDependencyType.Css, "GenericProperty/genericproperty.css", "UmbracoClient")] -// [ClientDependency(ClientDependencyType.Javascript, "GenericProperty/genericproperty.js", "UmbracoClient")] -// public partial class ContentTypeControlNew : UmbracoUserControl -// { -// // General Private members -// private ContentType _contentType; -// public bool HideStructure { get; set; } -// public Func DocumentTypeCallback { get; set; } - -// protected string ContentTypeAlias -// { -// get { return _contentType.Alias; } -// } -// protected int ContentTypeId -// { -// get { return _contentType.Id; } -// } - -// // "Tab" tab -// protected uicontrols.Pane Pane8; - -// // "Structure" tab -// protected DualSelectbox DualAllowedContentTypes = new DualSelectbox(); - -// // "Structure" tab - Compositions -// protected DualSelectbox DualContentTypeCompositions = new DualSelectbox(); - -// // "Info" tab -// public uicontrols.TabPage InfoTabPage; - -// // "Generic properties" tab -// public uicontrols.TabPage GenericPropertiesTabPage; - -// public GenericPropertyWrapper gp; -// private DataTable _dataTypeTable; -// private ArrayList _genericProperties = new ArrayList(); -// private ArrayList _sortLists = new ArrayList(); - -// //the async saving task -// private Action _asyncSaveTask; -// //the async delete property task -// private Action _asyncDeleteTask; - -// internal event SavingContentTypeEventHandler SavingContentType; -// internal delegate void SavingContentTypeEventHandler(ContentType e); - -// override protected void OnInit(EventArgs e) -// { -// base.OnInit(e); - -// LoadContentType(); - -// SetupInfoPane(); - -// if (HideStructure) -// { -// pnlStructure.Visible = false; -// } -// else -// { -// SetupStructurePane(); -// SetupCompositionsPane(); -// } - -// SetupGenericPropertiesPane(); -// SetupTabPane(); - -// // [ClientDependency(ClientDependencyType.Javascript, "js/UmbracoCasingRules.aspx", "UmbracoRoot")] -// var loader = ClientDependency.Core.Controls.ClientDependencyLoader.GetInstance(new HttpContextWrapper(Context)); -// var helper = new UrlHelper(new RequestContext(new HttpContextWrapper(Context), new RouteData())); -// loader.RegisterDependency(helper.GetCoreStringsControllerPath() + "ServicesJavaScript", ClientDependencyType.Javascript); -// } - -// protected void Page_Load(object sender, EventArgs e) -// { -// pp_newTab.Text = ui.Text("newtab", Security.CurrentUser); -// pp_alias.Text = ui.Text("alias", Security.CurrentUser); -// pp_name.Text = ui.Text("name", Security.CurrentUser); -// pp_allowedChildren.Text = ui.Text("allowedchildnodetypes", Security.CurrentUser); -// pp_compositions.Text = ui.Text("contenttypecompositions", Security.CurrentUser); -// pp_description.Text = ui.Text("editcontenttype", "description", Security.CurrentUser); -// pp_icon.Text = ui.Text("icon", Security.CurrentUser); -// pp_Root.Text = ui.Text("editcontenttype", "allowAtRoot", Security.CurrentUser) + "
    " + ui.Text("editcontenttype", "allowAtRootDesc", Security.CurrentUser) + ""; -// pp_isContainer.Text = ui.Text("editcontenttype", "hasListView", Security.CurrentUser) + "
    " + ui.Text("editcontenttype", "hasListViewDesc", Security.CurrentUser) + ""; - -// // we'll disable this... -// if (!Page.IsPostBack && _contentType.MasterContentType != 0) -// { -// string masterName = ContentType.GetContentType(_contentType.MasterContentType).Text; -// tabsMasterContentTypeName.Text = masterName; -// propertiesMasterContentTypeName.Text = masterName; -// PaneTabsInherited.Visible = true; -// PanePropertiesInherited.Visible = true; -// } - -// if (string.IsNullOrEmpty(_contentType.IconUrl)) -// lt_icon.Text = "icon-document"; -// else -// lt_icon.Text = _contentType.IconUrl.TrimStart('.'); - -// checkTxtAliasJs.Text = string.Format("checkAlias('#{0}');", txtAlias.ClientID); - -// } - -// /// -// /// A class to track the async state for deleting a doc type property -// /// -// private class DeleteAsyncState -// { -// public Umbraco.Web.UmbracoContext UmbracoContext { get; private set; } -// public GenericPropertyWrapper GenericPropertyWrapper { get; private set; } - -// public DeleteAsyncState( -// Umbraco.Web.UmbracoContext umbracoContext, -// GenericPropertyWrapper genericPropertyWrapper) -// { -// UmbracoContext = umbracoContext; -// GenericPropertyWrapper = genericPropertyWrapper; -// } -// } - -// /// -// /// A class to track the async state for saving the doc type -// /// -// private class SaveAsyncState -// { -// public SaveAsyncState( -// Umbraco.Web.UmbracoContext umbracoContext, -// SaveClickEventArgs saveArgs, -// string originalAlias, -// string originalName, -// string newAlias, -// string newName, -// string[] originalPropertyAliases) -// { -// UmbracoContext = umbracoContext; -// SaveArgs = saveArgs; -// _originalAlias = originalAlias; -// _originalName = originalName; -// _newAlias = newAlias; -// _originalPropertyAliases = originalPropertyAliases; -// _newName = newName; -// } - -// public Umbraco.Web.UmbracoContext UmbracoContext { get; private set; } -// public SaveClickEventArgs SaveArgs { get; private set; } -// private readonly string _originalAlias; -// private readonly string _originalName; -// private readonly string _newAlias; -// private readonly string _newName; -// private readonly string[] _originalPropertyAliases; - - -// public bool HasAliasChanged() -// { -// return (string.Compare(_originalAlias, _newAlias, StringComparison.OrdinalIgnoreCase) != 0); -// } -// public bool HasNameChanged() -// { -// return (string.Compare(_originalName, _newName, StringComparison.OrdinalIgnoreCase) != 0); -// } - -// /// -// /// Returns true if any property has been removed or if any alias has changed -// /// -// /// -// /// -// public bool HasAnyPropertyAliasChanged(ContentType contentType) -// { -// var newAliases = contentType.PropertyTypes.Select(x => x.Alias).ToArray(); -// //if any have been removed, return true -// if (newAliases.Length < _originalPropertyAliases.Count()) -// { -// return true; -// } -// //otherwise ensure that all of the original aliases are still existing -// return newAliases.ContainsAll(_originalPropertyAliases) == false; -// } -// } - -// /// -// /// Called asynchronously in order to persist all of the data to the database -// /// -// /// -// /// -// /// -// /// -// /// -// /// -// /// This can be a long running operation depending on how many content nodes exist and if the node type alias -// /// has changed as this will need to regenerate XML for all of the nodes. -// /// -// private IAsyncResult BeginAsyncSaveOperation(object sender, EventArgs e, AsyncCallback cb, object state) -// { -// Trace.Write("ContentTypeControlNew", "Start async operation"); - -// //get the args from the async state -// var args = (SaveAsyncState)state; - -// //start the task -// var result = _asyncSaveTask.BeginInvoke(args, cb, args); -// return result; -// } - -// /// -// /// Occurs once the async database save operation has completed -// /// -// /// -// /// -// /// This updates the UI elements -// /// -// private void EndAsyncSaveOperation(IAsyncResult ar) -// { -// Trace.Write("ContentTypeControlNew", "ending async operation"); - -// //get the args from the async state -// var state = (SaveAsyncState)ar.AsyncState; - -// // reload content type (due to caching) -// LoadContentType(); -// BindTabs(); -// BindDataGenericProperties(true); - -// // we need to re-bind the alias as the SafeAlias method can have changed it -// txtAlias.Text = _contentType.Alias; - -// //Notify the parent control -// RaiseBubbleEvent(new object(), state.SaveArgs); - -// if (state.HasNameChanged()) -// UpdateTreeNode(); - -// Trace.Write("ContentTypeControlNew", "async operation ended"); - -// //complete it -// _asyncSaveTask.EndInvoke(ar); -// } - -// /// -// /// The save button click event handlers -// /// -// /// -// /// -// protected void save_click(object sender, EventArgs e) -// { - -// //sync state betweet lt and hidden value -// lt_icon.Text = tb_icon.Value; - -// var state = new SaveAsyncState( -// UmbracoContext, -// new SaveClickEventArgs("Saved") -// { -// IconType = BasePage.speechBubbleIcon.success -// }, _contentType.Alias, _contentType.Text, txtAlias.Text, txtName.Text, _contentType.PropertyTypes.Select(x => x.Alias).ToArray()); - -// //Add the async operation to the page -// //NOTE: Must pass in a null and do not pass in a true to the 'executeInParallel', this is changed in .net 4.5 for the better, otherwise you'll get a ysod. -// Page.RegisterAsyncTask(new PageAsyncTask(BeginAsyncSaveOperation, EndAsyncSaveOperation, null, state)); - -// //create the save task to be executed async -// _asyncSaveTask = asyncState => -// { -// Trace.Write("ContentTypeControlNew", "executing task"); - -// //we need to re-set the UmbracoContext since it will be nulled and our cache handlers need it -// //global::Umbraco.Web.UmbracoContext.Current = asyncState.UmbracoContext; - -// _contentType.ContentTypeItem.Name = txtName.Text; -// _contentType.ContentTypeItem.Alias = txtAlias.Text; // raw, contentType.Alias takes care of it -// _contentType.ContentTypeItem.Icon = tb_icon.Value; -// _contentType.ContentTypeItem.Description = description.Text; -// //_contentType.ContentTypeItem.Thumbnail = ddlThumbnails.SelectedValue; -// _contentType.ContentTypeItem.AllowedAsRoot = allowAtRoot.Checked; -// _contentType.ContentTypeItem.IsContainer = cb_isContainer.Checked; - -// int i = 0; -// var ids = SaveAllowedChildTypes(); -// _contentType.ContentTypeItem.AllowedContentTypes = ids.Select(x => new ContentTypeSort {Id = new Lazy(() => x), SortOrder = i++}); - -// //Saving ContentType Compositions -// var compositionIds = SaveCompositionContentTypes(); -// var existingCompsitionIds = _contentType.ContentTypeItem.CompositionIds().ToList(); -// if (compositionIds.Any()) -// { -// //Iterate ContentType Ids from the save-collection -// foreach (var compositionId in compositionIds) -// { -// //If the compositionId is the Id of the current ContentType we skip it -// if(_contentType.Id.Equals(compositionId)) continue; - -// //If the Id already exists we'll just skip it -// if (existingCompsitionIds.Any(x => x.Equals(compositionId))) continue; - -// //New Ids will get added to the collection -// var compositionType = Services.ContentTypeService.GetContentType(compositionId); -// var added = _contentType.ContentTypeItem.AddContentType(compositionType); -// //TODO if added=false then return error message -// } - -// //Iterate the set except of existing and new Ids -// var removeIds = existingCompsitionIds.Except(compositionIds); -// foreach (var removeId in removeIds) -// { -// //Remove ContentTypes that was deselected in the list -// var compositionType = Services.ContentTypeService.GetContentType(removeId); -// var removed = _contentType.ContentTypeItem.RemoveContentType(compositionType.Alias); -// } -// } -// else if (existingCompsitionIds.Any()) -// { -// //Iterate the set except of existing and new Ids -// var removeIds = existingCompsitionIds.Except(compositionIds); -// foreach (var removeId in removeIds) -// { -// //Remove ContentTypes that was deselected in the list -// var compositionType = Services.ContentTypeService.GetContentType(removeId); -// var removed = _contentType.ContentTypeItem.RemoveContentType(compositionType.Alias); -// } -// } - - -// var tabs = SaveTabs(); -// foreach (var tab in tabs) -// { -// if (_contentType.ContentTypeItem.PropertyGroups.Contains(tab.Item2)) -// { -// _contentType.ContentTypeItem.PropertyGroups[tab.Item2].SortOrder = tab.Item3; -// } -// else -// { -// _contentType.ContentTypeItem.PropertyGroups.Add(new PropertyGroup {Id = tab.Item1, Name = tab.Item2, SortOrder = tab.Item3}); -// } -// } - -// SavePropertyType(asyncState.SaveArgs, _contentType.ContentTypeItem); -// //SavePropertyType(state.SaveArgs, _contentType.ContentTypeItem); -// UpdatePropertyTypes(_contentType.ContentTypeItem); - -// if (DocumentTypeCallback != null) -// { -// var documentType = _contentType as DocumentType; -// if (documentType != null) -// { -// var result = DocumentTypeCallback(documentType); -// } -// } - -// if (SavingContentType != null) -// { -// SavingContentType(_contentType); -// } - -// try -// { -// _contentType.Save(); -// } -// catch (DuplicateNameException ex) -// { -// DuplicateAliasValidator.IsValid = false; -// //asyncState.SaveArgs.IconType = BasePage.speechBubbleIcon.error; -// state.SaveArgs.IconType = BasePage.speechBubbleIcon.error; -// //asyncState.SaveArgs.Message = ex.Message; -// state.SaveArgs.Message = ex.Message; -// return; -// } - -// Trace.Write("ContentTypeControlNew", "task completing"); -// }; - -// //execute the async tasks -// Page.ExecuteRegisteredAsyncTasks(); -// } - -// /// -// /// Loads the current ContentType from the id found in the querystring. -// /// The correct type is loaded based on editing location (DocumentType, MediaType or MemberType). -// /// -// private void LoadContentType() -// { -// int docTypeId = int.Parse(Request.QueryString["id"]); -// LoadContentType(docTypeId); -// } - -// private void LoadContentType(int docTypeId) -// { -// //Fairly hacky code to load the ContentType as the real type instead of its base type, so it can be properly saved. -// if (Request.Path.ToLowerInvariant().Contains("editnodetypenew.aspx")) -// { -// _contentType = new DocumentType(docTypeId); -// } -// else if (Request.Path.ToLowerInvariant().Contains("editmediatype.aspx")) -// { -// _contentType = new cms.businesslogic.media.MediaType(docTypeId); -// } -// else if (Request.Path.ToLowerInvariant().Contains("editmembertype.aspx")) -// { -// _contentType = new cms.businesslogic.member.MemberType(docTypeId); -// } -// else -// { -// _contentType = new ContentType(docTypeId); -// } -// } - -// /// -// /// Updates the Node in the Tree -// /// -// private void UpdateTreeNode() -// { -// var clientTools = new ClientTools(this.Page); -// clientTools -// .SyncTree(_contentType.Path, true); -// } - -// #region "Info" Pane - -// private void SetupInfoPane() -// { -// InfoTabPage = TabView1.NewTabPage("Info"); -// InfoTabPage.Controls.Add(pnlInfo); - -// var Save = TabView1.Menu.NewButton(); -// Save.Click += save_click; -// Save.Text = ui.Text("save", Security.CurrentUser); -// Save.ID = "save"; -// Save.ButtonType = uicontrols.MenuButtonType.Primary; - -// txtName.Text = _contentType.GetRawText(); -// txtAlias.Text = _contentType.Alias; -// description.Text = _contentType.GetRawDescription(); -// tb_icon.Value = _contentType.IconUrl; - -// if(string.IsNullOrEmpty(_contentType.IconUrl)) -// lt_icon.Text = "icon-document"; -// else -// lt_icon.Text = _contentType.IconUrl.TrimStart('.'); - -// } - -// #endregion - -// #region "Structure" Pane - -// private void SetupStructurePane() -// { -// DualAllowedContentTypes.ID = "allowedContentTypes"; -// DualAllowedContentTypes.Width = 175; - -// uicontrols.TabPage tp = TabView1.NewTabPage("Structure"); -// tp.Controls.Add(pnlStructure); - -// int[] allowedIds = _contentType.AllowedChildContentTypeIDs; -// if (!Page.IsPostBack) -// { -// string chosenContentTypeIDs = ""; -// ContentType[] contentTypes = _contentType.GetAll(); -// foreach (ContentType ct in contentTypes.OrderBy(x => x.Text)) -// { -// ListItem li = new ListItem(ct.Text, ct.Id.ToString()); -// DualAllowedContentTypes.Items.Add(li); -// lstAllowedContentTypes.Items.Add(li); -// foreach (int i in allowedIds) -// { -// if (i == ct.Id) -// { -// li.Selected = true; -// chosenContentTypeIDs += ct.Id + ","; -// } -// } -// } -// DualAllowedContentTypes.Value = chosenContentTypeIDs; -// } - -// allowAtRoot.Checked = _contentType.AllowAtRoot; -// cb_isContainer.Checked = _contentType.IsContainerContentType; -// } - -// private int[] SaveAllowedChildTypes() -// { -// var tmp = new ArrayList(); -// foreach (ListItem li in lstAllowedContentTypes.Items) -// { -// if (li.Selected) -// tmp.Add(int.Parse(li.Value)); -// } -// var ids = new int[tmp.Count]; -// for (int i = 0; i < tmp.Count; i++) ids[i] = (int)tmp[i]; - -// return ids; -// } - -// #endregion - -// #region Compositions Pane - -// private void SetupCompositionsPane() -// { -// DualContentTypeCompositions.ID = "compositionContentTypes"; -// DualContentTypeCompositions.Width = 175; - -// int[] compositionIds = _contentType.ContentTypeItem.CompositionIds().ToArray(); -// if (!Page.IsPostBack) -// { -// string chosenContentTypeIDs = ""; -// ContentType[] contentTypes = _contentType.GetAll(); -// foreach (ContentType ct in contentTypes.OrderBy(x => x.Text)) -// { -// ListItem li = new ListItem(ct.Text, ct.Id.ToString()); -// if (ct.Id == _contentType.Id) -// li.Enabled = false; - -// DualContentTypeCompositions.Items.Add(li); -// lstContentTypeCompositions.Items.Add(li); - -// foreach (int i in compositionIds) -// { -// if (i == ct.Id) -// { -// li.Selected = true; -// chosenContentTypeIDs += ct.Id + ","; -// } -// } -// } -// DualContentTypeCompositions.Value = chosenContentTypeIDs; -// } -// } - -// private int[] SaveCompositionContentTypes() -// { -// var tmp = new ArrayList(); -// foreach (ListItem li in lstContentTypeCompositions.Items) -// { -// if (li.Selected) -// tmp.Add(int.Parse(li.Value)); -// } -// var ids = new int[tmp.Count]; -// for (int i = 0; i < tmp.Count; i++) ids[i] = (int)tmp[i]; - -// return ids; -// } - -// #endregion - -// #region "Generic properties" Pane - -// private void SetupGenericPropertiesPane() -// { -// GenericPropertiesTabPage = TabView1.NewTabPage("Generic properties"); -// GenericPropertiesTabPage.Controls.Add(pnlProperties); -// BindDataGenericProperties(false); -// } - -// private void BindDataGenericProperties(bool refresh) -// { -// var tabs = _contentType.getVirtualTabs; -// var propertyTypeGroups = _contentType.PropertyTypeGroups.ToList(); -// var dtds = cms.businesslogic.datatype.DataTypeDefinition.GetAll(); - -// PropertyTypes.Controls.Clear(); - -// // Add new property -// if (PropertyTypeNew.Controls.Count == 0) -// { -// PropertyTypeNew.Controls.Add(new LiteralControl("

    Add New Property

      ")); -// gp = new GenericPropertyWrapper(); -// gp.ID = "GenericPropertyNew"; -// gp.Tabs = tabs; -// gp.DataTypeDefinitions = dtds; -// PropertyTypeNew.Controls.Add(gp); -// PropertyTypeNew.Controls.Add(new LiteralControl("
    ")); -// } -// else if (refresh) -// { -// gp = (GenericPropertyWrapper)PropertyTypeNew.Controls[1]; -// gp.ID = "GenericPropertyNew"; -// gp.Tabs = tabs; -// gp.DataTypeDefinitions = dtds; -// gp.UpdateEditControl(); -// gp.GenricPropertyControl.UpdateInterface(); -// gp.GenricPropertyControl.Clear(); -// } - -// _genericProperties.Clear(); -// var inTab = new Hashtable(); -// int counter = 0; - -// PropertyTypes.Controls.Add(new LiteralControl("
    ")); // opens draggable container for properties on tabs - -// foreach (var tab in tabs) -// { -// string tabName = tab.GetRawCaption(); -// string tabCaption = tabName; -// if (tab.ContentType != _contentType.Id) -// { -// tabCaption += " (inherited from " + new ContentType(tab.ContentType).Text + ")"; -// } - -// PropertyTypes.Controls.Add(new LiteralControl("

    Tab: " + tabCaption + "

    ")); - -// var propertyGroup = propertyTypeGroups.SingleOrDefault(x => x.ParentId == tab.Id); -// var propertyTypes = (propertyGroup == null -// ? tab.GetPropertyTypes(_contentType.Id, false) -// : propertyGroup.GetPropertyTypes()).ToArray(); - -// var propertyGroupId = tab.Id; - -// var propSort = new HtmlInputHidden(); -// propSort.ID = "propSort_" + propertyGroupId + "_Content"; -// PropertyTypes.Controls.Add(propSort); -// _sortLists.Add(propSort); - -// if (propertyTypes.Any(x => x.ContentTypeId == _contentType.Id)) -// { -// PropertyTypes.Controls.Add(new LiteralControl("
      ")); - -// foreach (var pt in propertyTypes) -// { -// //If the PropertyType doesn't belong on this ContentType skip it and continue to the next one -// if (pt.ContentTypeId != _contentType.Id) continue; - -// cms.businesslogic.datatype.DataTypeDefinition[] filteredDtds; -// var gpw = GetPropertyWrapperForPropertyType(pt, dtds, out filteredDtds); -// gpw.ID = "gpw_" + pt.Id; -// gpw.PropertyType = pt; -// gpw.Tabs = tabs; -// gpw.TabId = propertyGroupId; -// gpw.DataTypeDefinitions = filteredDtds; -// gpw.Delete += gpw_Delete; -// gpw.FullId = "t_" + propertyGroupId + "_Contents_" + +pt.Id; - -// PropertyTypes.Controls.Add(gpw); -// _genericProperties.Add(gpw); -// if (refresh) -// gpw.GenricPropertyControl.UpdateInterface(); - -// inTab.Add(pt.Id.ToString(CultureInfo.InvariantCulture), ""); -// counter++; -// } - -// PropertyTypes.Controls.Add(new LiteralControl("
    ")); -// } -// else -// { -// AddNoPropertiesDefinedMessage(); -// } - -// var jsSortable = GetJavaScriptForPropertySorting(propSort.ClientID); -// Page.ClientScript.RegisterStartupScript(this.GetType(), propSort.ClientID, jsSortable, true); - -// PropertyTypes.Controls.Add(new LiteralControl("
    ")); -// } - -// // Generic properties tab -// counter = 0; -// bool propertyTabHasProperties = false; -// var propertiesPh = new PlaceHolder(); -// propertiesPh.ID = "propertiesPH"; -// PropertyTypes.Controls.Add(new LiteralControl("

    Tab: Generic Properties

    ")); -// PropertyTypes.Controls.Add(propertiesPh); - -// var propSortGp = new HtmlInputHidden(); -// propSortGp.ID = "propSort_general_Content"; -// PropertyTypes.Controls.Add(propSortGp); -// _sortLists.Add(propSortGp); - -// propertiesPh.Controls.Add(new LiteralControl("
      ")); -// foreach (var pt in _contentType.PropertyTypes) -// { -// //This use to be: -// if (pt.ContentTypeId == _contentType.Id && inTab.ContainsKey(pt.Id.ToString(CultureInfo.InvariantCulture)) == false) -// //But seriously, if it's not on a tab the tabId is 0, it's a lot easier to read IMO -// //if (pt.ContentTypeId == _contentType.Id && pt.TabId == 0) -// { -// cms.businesslogic.datatype.DataTypeDefinition[] filteredDtds; -// var gpw = GetPropertyWrapperForPropertyType(pt, dtds, out filteredDtds); - -// // Changed by duckie, was: -// // gpw.ID = "gpw_" + editPropertyType.Alias; -// // Which is NOT unique! -// gpw.ID = "gpw_" + pt.Id; - -// gpw.PropertyType = pt; -// gpw.Tabs = tabs; -// gpw.DataTypeDefinitions = filteredDtds; -// gpw.Delete += new EventHandler(gpw_Delete); -// gpw.FullId = "t_general_Contents_" + pt.Id; - -// propertiesPh.Controls.Add(gpw); -// _genericProperties.Add(gpw); -// if (refresh) -// gpw.GenricPropertyControl.UpdateInterface(); -// inTab.Add(pt.Id, ""); -// propertyTabHasProperties = true; -// counter++; -// } -// } - -// propertiesPh.Controls.Add(new LiteralControl("
    ")); - -// var jsSortable_gp = GetJavaScriptForPropertySorting(propSortGp.ClientID); - -// Page.ClientScript.RegisterStartupScript(this.GetType(), "propSort_gp", jsSortable_gp, true); - - -// if (!propertyTabHasProperties) -// { -// AddNoPropertiesDefinedMessage(); -// PropertyTypes.Controls.Remove(PropertyTypes.FindControl("propertiesPH")); -// } -// else -// { -// PropertyTypes.Controls.Add(propertiesPh); -// } - -// PropertyTypes.Controls.Add(new LiteralControl("
    ")); // closes draggable container for properties on tabs - -// } - -// /// -// /// Returns a generic property wrapper for a given property - this determines if the property type should be -// /// allowed to be editable. -// /// -// /// -// private GenericPropertyWrapper GetPropertyWrapperForPropertyType( -// cms.businesslogic.propertytype.PropertyType pt, -// cms.businesslogic.datatype.DataTypeDefinition[] allDtds, -// out cms.businesslogic.datatype.DataTypeDefinition[] filteredDefinitions) -// { -// filteredDefinitions = allDtds; - -// //not editable if any of the built in member types -// if (_contentType.ContentTypeItem is IMemberType) -// { -// var builtInAliases = global::Umbraco.Core.Constants.Conventions.Member.GetStandardPropertyTypeStubs().Select(x => x.Key).ToArray(); -// var gpw = new GenericPropertyWrapper(builtInAliases.Contains(pt.Alias) == false); -// return gpw; -// } - -// //not editable if prefixed with the special internal prefix -// if (pt.Alias.StartsWith(Constants.PropertyEditors.InternalGenericPropertiesPrefix)) -// { -// var gpw = new GenericPropertyWrapper(false); -// return gpw; -// } - -// //the rest are editable -// return new GenericPropertyWrapper(); -// } - -// private string GetJavaScriptForPropertySorting(string propSortClientId) -// { -// return @"(function($) { -// var propSortId = ""#" + propSortClientId + @"""; -// $(document).ready(function() { -// $(propSortId).next("".genericPropertyList"").sortable({ -// containment: '#tabs-container', -// connectWith: '.genericPropertyList', -// cancel: 'li.no-properties-on-tab, .propertyForm div[id^=""editbody""]', -// tolerance: 'pointer', -// start: function() { -// $('#tabs-container').addClass('doc-type-property-drop-zone'); -// }, -// stop: function() { -// $('#tabs-container').removeClass('doc-type-property-drop-zone'); -// }, -// update: function(event, ui) { -// -// // Save new sort details for tab -// $(propSortId).val($(this).sortable('serialize')); -// -// // Handle move to new tab -// // - find tab name -// var tabName = $(this).siblings('h2').attr('data-tabname'); -// -// // - find tab drop-down for item and set option selected that matches tab name -// var tabDropDownList = $(""select[name$='ddlTab']"", ui.item); -// $('option', tabDropDownList).each(function() { -// if ($(this).text() == tabName) { -// $(this).attr('selected', 'selected'); -// } -// }); -// -// // Remove any no properties messages for tabs that now have a property -// $('li.no-properties-on-tab', $(this)).remove(); -// -// // Add a no properties message for tabs that now have no properties -// $('#tabs-container ul.genericPropertyList:not(:has(li))').append('" + GetHtmlForNoPropertiesMessageListItem() + @"'); -// -// }}); -// }); -// })(jQuery);"; -// } - -// private void AddNoPropertiesDefinedMessage() -// { -// // Create no properties message as a ul in order to allow dragging of properties to it from other tabs -// PropertyTypes.Controls.Add(new LiteralControl("
      " + GetHtmlForNoPropertiesMessageListItem() + "
    ")); -// } - -// private string GetHtmlForNoPropertiesMessageListItem() -// { -// return @"
  • " + ui.Text("settings", "noPropertiesDefinedOnTab") + "
  • "; -// } - -// private void SavePropertyType(SaveClickEventArgs e, IContentTypeComposition contentTypeItem) -// { -// this.CreateChildControls(); - -// //The GenericPropertyWrapper control, which contains the details for the PropertyType being added -// GenericProperty gpData = gp.GenricPropertyControl; -// if (string.IsNullOrEmpty(gpData.Name.Trim()) == false && string.IsNullOrEmpty(gpData.Alias.Trim()) == false) -// { -// // when creating a property don't do anything special, propertyType.Alias will take care of it -// // don't enforce camel here because the user might have changed what the CoreStringsController returned -// var propertyTypeAlias = gpData.Alias; -// if (contentTypeItem.PropertyTypeExists(propertyTypeAlias) == false) -// { -// //Find the DataTypeDefinition that the PropertyType should be based on -// var dataTypeDefinition = ApplicationContext.Current.Services.DataTypeService.GetDataTypeDefinitionById(gpData.Type); -// var propertyType = new PropertyType(dataTypeDefinition) -// { -// Alias = propertyTypeAlias, -// Name = gpData.Name.Trim(), -// Mandatory = gpData.Mandatory, -// ValidationRegExp = gpData.Validation, -// Description = gpData.Description -// }; -// //gpData.Tab == 0 Generic Properties / No Group -// if (gpData.Tab == 0) -// { -// contentTypeItem.AddPropertyType(propertyType); -// } -// else -// { -// //Find the PropertyGroup by its Id and then set the PropertyType on that group -// var exists = contentTypeItem.CompositionPropertyGroups.Any(x => x.Id == gpData.Tab); -// if (exists) -// { -// var propertyGroup = contentTypeItem.CompositionPropertyGroups.First(x => x.Id == gpData.Tab); -// contentTypeItem.AddPropertyType(propertyType, propertyGroup.Name); -// } -// else -// { -// var tab = gpData.Tabs.FirstOrDefault(x => x.Id == gpData.Tab); -// if (tab != null) -// { -// var caption = tab.GetRawCaption(); -// contentTypeItem.AddPropertyType(propertyType, caption); -// } -// } -// } -// gpData.Clear(); -// } -// else -// { -// e.Message = ui.Text("contentTypeDublicatePropertyType"); -// e.IconType = BasePage.speechBubbleIcon.warning; -// } -// } -// } - -// private void UpdatePropertyTypes(IContentTypeComposition contentTypeItem) -// { -// //Loop through the _genericProperties ArrayList and update all existing PropertyTypes -// foreach (GenericPropertyWrapper gpw in _genericProperties) -// { -// if (gpw.PropertyType == null) continue; -// if (contentTypeItem.PropertyTypes == null || contentTypeItem.PropertyTypes.Any(x => x.Alias == gpw.PropertyType.Alias) == false) continue; -// var propertyType = contentTypeItem.PropertyTypes.First(x => x.Alias == gpw.PropertyType.Alias); -// if (propertyType == null) continue; -// var dataTypeDefinition = ApplicationContext.Current.Services.DataTypeService.GetDataTypeDefinitionById(gpw.GenricPropertyControl.Type); -// // when saving, respect user's casing, so do nothing here as propertyType takes care of it -// propertyType.Alias = gpw.GenricPropertyControl.Alias; -// propertyType.Name = gpw.GenricPropertyControl.Name; -// propertyType.Description = gpw.GenricPropertyControl.Description; -// propertyType.ValidationRegExp = gpw.GenricPropertyControl.Validation; -// propertyType.Mandatory = gpw.GenricPropertyControl.Mandatory; -// propertyType.DataTypeDatabaseType = dataTypeDefinition.DatabaseType; -// propertyType.DataTypeDefinitionId = dataTypeDefinition.Id; -// propertyType.PropertyEditorAlias = dataTypeDefinition.PropertyEditorAlias; - -// if (propertyType.PropertyGroupId == null || propertyType.PropertyGroupId.Value != gpw.GenricPropertyControl.Tab) -// { -// if (gpw.GenricPropertyControl.Tab == 0) -// { -// propertyType.PropertyGroupId = new Lazy(() => 0); -// } -// else if (contentTypeItem.PropertyGroups.Any(x => x.Id == gpw.GenricPropertyControl.Tab)) -// { -// propertyType.PropertyGroupId = new Lazy(() => gpw.GenricPropertyControl.Tab); -// } -// else if (contentTypeItem.PropertyGroups.Any(x => x.ParentId == gpw.GenricPropertyControl.Tab)) -// { -// var propertyGroup = contentTypeItem.PropertyGroups.First(x => x.ParentId == gpw.GenricPropertyControl.Tab); -// propertyType.PropertyGroupId = new Lazy(() => propertyGroup.Id); -// } -// else -// { -// var propertyGroup = contentTypeItem.CompositionPropertyGroups.First(x => x.Id == gpw.GenricPropertyControl.Tab); -// contentTypeItem.AddPropertyGroup(propertyGroup.Name); -// contentTypeItem.MovePropertyType(propertyType.Alias, propertyGroup.Name); -// } -// } -// } - -// //Update the SortOrder of the PropertyTypes -// foreach (HtmlInputHidden propSorter in _sortLists) -// { -// if (propSorter.Value.Trim() != "") -// { -// string[] newSortOrders = propSorter.Value.Split("&".ToCharArray()); -// for (int i = 0; i < newSortOrders.Length; i++) -// { -// var propertyTypeId = int.Parse(newSortOrders[i].Split("=".ToCharArray())[1]); -// if (contentTypeItem.PropertyTypes != null && -// contentTypeItem.PropertyTypes.Any(x => x.Id == propertyTypeId)) -// { -// var propertyType = contentTypeItem.PropertyTypes.First(x => x.Id == propertyTypeId); -// propertyType.SortOrder = i; -// } -// } -// } -// } -// } - -// /// -// /// Called asynchronously in order to delete a content type property -// /// -// /// -// /// -// /// -// /// -// /// -// private IAsyncResult BeginAsyncDeleteOperation(object sender, EventArgs e, AsyncCallback cb, object state) -// { -// Trace.Write("ContentTypeControlNew", "Start async operation"); - -// //get the args from the async state -// var args = (DeleteAsyncState)state; - -// //start the task -// var result = _asyncDeleteTask.BeginInvoke(args, cb, args); -// return result; -// } - -// /// -// /// Occurs once the async database delete operation has completed -// /// -// /// -// /// -// /// This updates the UI elements -// /// -// private void EndAsyncDeleteOperation(IAsyncResult ar) -// { -// Trace.Write("ContentTypeControlNew", "ending async operation"); - -// // reload content type (due to caching) -// LoadContentType(_contentType.Id); -// BindDataGenericProperties(true); - -// Trace.Write("ContentTypeControlNew", "async operation ended"); - -// //complete it -// _asyncDeleteTask.EndInvoke(ar); -// } - -// /// -// /// Removes a PropertyType from the current ContentType when user clicks "red x" -// /// -// /// -// /// -// protected void gpw_Delete(object sender, EventArgs e) -// { -// var state = new DeleteAsyncState( -// UmbracoContext, -// (GenericPropertyWrapper)sender); - -// //Add the async operation to the page -// //NOTE: Must pass in a null and do not pass in a true to the 'executeInParallel', this is changed in .net 4.5 for the better, otherwise you'll get a ysod. -// Page.RegisterAsyncTask(new PageAsyncTask(BeginAsyncDeleteOperation, EndAsyncDeleteOperation, null, state)); - -// //create the save task to be executed async -// _asyncDeleteTask = asyncState => -// { -// Trace.Write("ContentTypeControlNew", "executing task"); - -// //we need to re-set the UmbracoContext since it will be nulled and our cache handlers need it -// global::Umbraco.Web.UmbracoContext.Current = asyncState.UmbracoContext; - -// //if (_contentType.ContentTypeItem is IContentType -// // || _contentType.ContentTypeItem is IMediaType -// // || _contentType.ContentTypeItem is IMemberType) -// //{ -// _contentType.ContentTypeItem.RemovePropertyType(asyncState.GenericPropertyWrapper.PropertyType.Alias); -// _contentType.Save(); -// //} -// //else -// //{ -// // //if it is not a document type or a media type, then continue to call the legacy delete() method. -// // //the new API for document type and media type's will ensure that the data is removed correctly and that -// // //the cache is flushed correctly (using events). If it is not one of these types, we'll rever to the -// // //legacy operation (... like for members i suppose ?) -// // asyncState.GenericPropertyWrapper.GenricPropertyControl.PropertyType.delete(); - -// //} - -// Trace.Write("ContentTypeControlNew", "task completing"); -// }; - -// //execute the async tasks -// Page.ExecuteRegisteredAsyncTasks(); -// } - -// #endregion - -// #region "Tab" Pane - -// private void SetupTabPane() -// { -// uicontrols.TabPage tp = TabView1.NewTabPage("Tabs"); -// tp.Controls.Add(pnlTab); -// BindTabs(); -// } - -// private IEnumerable> SaveTabs() -// { -// var tabs = new List>();//TabId, TabName, TabSortOrder -// foreach (DataGridItem dgi in dgTabs.Items) -// { -// int tabid = int.Parse(dgi.Cells[0].Text); -// string tabName = ((TextBox)dgi.FindControl("txtTab")).Text.Replace("'", "''"); -// int tabSortOrder; -// if (Int32.TryParse(((TextBox)dgi.FindControl("txtSortOrder")).Text, out tabSortOrder)) -// { -// tabs.Add(new Tuple(tabid, tabName, tabSortOrder)); -// } -// } -// return tabs; -// } - -// private void BindTabs() -// { -// DataTable dt = new DataTable(); -// dt.Columns.Add("name"); -// dt.Columns.Add("id"); -// dt.Columns.Add("order"); - -// foreach (var grp in _contentType.PropertyTypeGroups.OrderBy(p => p.SortOrder)) -// { -// if (grp.ContentTypeId == _contentType.Id && grp.ParentId == 0) -// { -// DataRow dr = dt.NewRow(); -// dr["name"] = grp.Name; -// dr["id"] = grp.Id; -// dr["order"] = grp.SortOrder; -// dt.Rows.Add(dr); -// } -// } - -// if (dt.Rows.Count == 0) -// { -// lttNoTabs.Text = "No custom tabs defined"; -// dgTabs.Visible = false; -// } -// else -// { -// lttNoTabs.Text = ""; -// dgTabs.Visible = true; -// } -// dgTabs.DataSource = dt; -// dgTabs.DataBind(); -// } - -// public DataTable DataTypeTable -// { -// get -// { -// if (_dataTypeTable == null) -// { -// _dataTypeTable = new DataTable(); -// _dataTypeTable.Columns.Add("name"); -// _dataTypeTable.Columns.Add("id"); - -// foreach (var dataType in cms.businesslogic.datatype.DataTypeDefinition.GetAll()) -// { -// DataRow dr = _dataTypeTable.NewRow(); -// dr["name"] = dataType.Text; -// dr["id"] = dataType.Id.ToString(); -// _dataTypeTable.Rows.Add(dr); -// } -// } -// return _dataTypeTable; -// } -// } - -// public DataTable TabTable -// { -// get -// { -// if (dgTabs.DataSource == null) -// BindTabs(); - -// DataTable dt = new DataTable(); -// dt.Columns.Add("name"); -// dt.Columns.Add("id"); - -// foreach (DataRow dr in ((DataTable)dgTabs.DataSource).Rows) -// { -// DataRow dr2 = dt.NewRow(); -// dr2["name"] = dr["name"]; -// dr2["id"] = dr["id"]; -// dt.Rows.Add(dr2); -// } - -// DataRow dr1 = dt.NewRow(); -// dr1["name"] = "General properties"; -// dr1["id"] = 0; -// dt.Rows.Add(dr1); - -// return dt; -// } -// } - -// /// -// /// Adds a new Tab to current ContentType when user clicks 'New Tab'-button -// /// -// /// -// /// -// protected void btnNewTab_Click(object sender, EventArgs e) -// { -// if (txtNewTab.Text.Trim() != "") -// { -// //if (_contentType.ContentTypeItem is IContentType -// // || _contentType.ContentTypeItem is IMediaType -// // || _contentType.ContentTypeItem is IMemberType) -// //{ -// _contentType.ContentTypeItem.AddPropertyGroup(txtNewTab.Text); -// _contentType.Save(); -// //} -// //else -// //{ -// // _contentType.AddVirtualTab(txtNewTab.Text); -// //} - -// LoadContentType(); - -// var ea = new SaveClickEventArgs(ui.Text("contentTypeTabCreated")); -// ea.IconType = BasePage.speechBubbleIcon.success; - -// RaiseBubbleEvent(new object(), ea); - -// txtNewTab.Text = ""; - -// BindTabs(); -// BindDataGenericProperties(true); -// } - -// } - -// /// -// /// Removes a Tab from current ContentType when user clicks Delete button -// /// -// /// -// /// -// protected void dgTabs_ItemCommand(object source, DataGridCommandEventArgs e) -// { -// if (e.CommandName == "Delete") -// { -// int propertyGroupId = int.Parse(e.Item.Cells[0].Text); -// //if (_contentType.ContentTypeItem is IContentType -// // || _contentType.ContentTypeItem is IMediaType -// // || _contentType.ContentTypeItem is IMemberType) -// //{ -// var propertyGroup = _contentType.ContentTypeItem.PropertyGroups.FirstOrDefault(x => x.Id == propertyGroupId); -// if (propertyGroup != null && string.IsNullOrEmpty(propertyGroup.Name) == false) -// { -// _contentType.ContentTypeItem.PropertyGroups.Remove(propertyGroup.Name); -// _contentType.Save(); -// } -// //} - -// _contentType.DeleteVirtualTab(propertyGroupId); - -// LoadContentType(); - -// var ea = new SaveClickEventArgs(ui.Text("contentTypeTabDeleted")); -// ea.IconType = BasePage.speechBubbleIcon.success; - -// RaiseBubbleEvent(new object(), ea); - -// } - -// BindTabs(); -// BindDataGenericProperties(true); -// } - -// protected void dgTabs_itemdatabound(object sender, DataGridItemEventArgs e) -// { -// if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem) -// { -// ((DropDownList)e.Item.FindControl("dllTab")).SelectedValue = -// ((DataRowView)e.Item.DataItem).Row["propertyTypeGroupId"].ToString(); -// ((DropDownList)e.Item.FindControl("ddlType")).SelectedValue = -// ((DataRowView)e.Item.DataItem).Row["type"].ToString(); -// } - -// } - -// #endregion - -// /// -// /// TabView1 control. -// /// -// /// -// /// Auto-generated field. -// /// To modify move field declaration from designer file to code-behind file. -// /// -// protected global::umbraco.uicontrols.TabView TabView1; - -// /// -// /// pnlGeneral control. -// /// -// /// -// /// Auto-generated field. -// /// To modify move field declaration from designer file to code-behind file. -// /// -// protected global::System.Web.UI.WebControls.Panel pnlGeneral; - -// /// -// /// pnlTab control. -// /// -// /// -// /// Auto-generated field. -// /// To modify move field declaration from designer file to code-behind file. -// /// -// protected global::System.Web.UI.WebControls.Panel pnlTab; - -// /// -// /// PaneTabsInherited control. -// /// -// /// -// /// Auto-generated field. -// /// To modify move field declaration from designer file to code-behind file. -// /// -// protected global::umbraco.uicontrols.Pane PaneTabsInherited; - -// /// -// /// tabsMasterContentTypeName control. -// /// -// /// -// /// Auto-generated field. -// /// To modify move field declaration from designer file to code-behind file. -// /// -// protected global::System.Web.UI.WebControls.Literal tabsMasterContentTypeName; - -// /// -// /// Pane2 control. -// /// -// /// -// /// Auto-generated field. -// /// To modify move field declaration from designer file to code-behind file. -// /// -// protected global::umbraco.uicontrols.Pane Pane2; - -// /// -// /// pp_newTab control. -// /// -// /// -// /// Auto-generated field. -// /// To modify move field declaration from designer file to code-behind file. -// /// -// protected global::umbraco.uicontrols.PropertyPanel pp_newTab; - -// protected global::umbraco.uicontrols.PropertyPanel pp_isContainer; -// protected global::System.Web.UI.WebControls.CheckBox cb_isContainer; - -// /// -// /// txtNewTab control. -// /// -// /// -// /// Auto-generated field. -// /// To modify move field declaration from designer file to code-behind file. -// /// -// protected global::System.Web.UI.WebControls.TextBox txtNewTab; - -// /// -// /// btnNewTab control. -// /// -// /// -// /// Auto-generated field. -// /// To modify move field declaration from designer file to code-behind file. -// /// -// protected global::System.Web.UI.WebControls.Button btnNewTab; - -// /// -// /// Pane1 control. -// /// -// /// -// /// Auto-generated field. -// /// To modify move field declaration from designer file to code-behind file. -// /// -// protected global::umbraco.uicontrols.Pane Pane1; - -// /// -// /// dgTabs control. -// /// -// /// -// /// Auto-generated field. -// /// To modify move field declaration from designer file to code-behind file. -// /// -// protected global::System.Web.UI.WebControls.DataGrid dgTabs; - -// /// -// /// lttNoTabs control. -// /// -// /// -// /// Auto-generated field. -// /// To modify move field declaration from designer file to code-behind file. -// /// -// protected global::System.Web.UI.WebControls.Literal lttNoTabs; - -// /// -// /// pnlInfo control. -// /// -// /// -// /// Auto-generated field. -// /// To modify move field declaration from designer file to code-behind file. -// /// -// protected global::System.Web.UI.WebControls.Panel pnlInfo; - -// /// -// /// Pane3 control. -// /// -// /// -// /// Auto-generated field. -// /// To modify move field declaration from designer file to code-behind file. -// /// -// protected global::umbraco.uicontrols.Pane Pane3; - -// /// -// /// pp_name control. -// /// -// /// -// /// Auto-generated field. -// /// To modify move field declaration from designer file to code-behind file. -// /// -// protected global::umbraco.uicontrols.PropertyPanel pp_name; - -// /// -// /// txtName control. -// /// -// /// -// /// Auto-generated field. -// /// To modify move field declaration from designer file to code-behind file. -// /// -// protected global::System.Web.UI.WebControls.TextBox txtName; - -// /// -// /// RequiredFieldValidator1 control. -// /// -// /// -// /// Auto-generated field. -// /// To modify move field declaration from designer file to code-behind file. -// /// -// protected global::System.Web.UI.WebControls.RequiredFieldValidator RequiredFieldValidator1; - -// /// -// /// pp_alias control. -// /// -// /// -// /// Auto-generated field. -// /// To modify move field declaration from designer file to code-behind file. -// /// -// protected global::umbraco.uicontrols.PropertyPanel pp_alias; - -// /// -// /// txtAlias control. -// /// -// /// -// /// Auto-generated field. -// /// To modify move field declaration from designer file to code-behind file. -// /// -// protected global::System.Web.UI.WebControls.TextBox txtAlias; - -// /// -// /// pp_icon control. -// /// -// /// -// /// Auto-generated field. -// /// To modify move field declaration from designer file to code-behind file. -// /// -// protected global::umbraco.uicontrols.PropertyPanel pp_icon; - -// /// -// /// ddlIcons control. -// /// -// /// -// /// Auto-generated field. -// /// To modify move field declaration from designer file to code-behind file. -// /// -// protected global::System.Web.UI.WebControls.HiddenField tb_icon; -// protected global::System.Web.UI.WebControls.Literal lt_icon; - - -// /// -// /// pp_description control. -// /// -// /// -// /// Auto-generated field. -// /// To modify move field declaration from designer file to code-behind file. -// /// -// protected global::umbraco.uicontrols.PropertyPanel pp_description; - -// /// -// /// description control. -// /// -// /// -// /// Auto-generated field. -// /// To modify move field declaration from designer file to code-behind file. -// /// -// protected global::System.Web.UI.WebControls.TextBox description; - -// /// -// /// pnlStructure control. -// /// -// /// -// /// Auto-generated field. -// /// To modify move field declaration from designer file to code-behind file. -// /// -// protected global::System.Web.UI.WebControls.Panel pnlStructure; - -// /// -// /// Pane6 control. -// /// -// /// -// /// Auto-generated field. -// /// To modify move field declaration from designer file to code-behind file. -// /// -// protected global::umbraco.uicontrols.Pane Pane6; - -// /// -// /// pp_Root control. -// /// -// /// -// /// Auto-generated field. -// /// To modify move field declaration from designer file to code-behind file. -// /// -// protected global::umbraco.uicontrols.PropertyPanel pp_Root; - -// /// -// /// allowAtRoot control. -// /// -// /// -// /// Auto-generated field. -// /// To modify move field declaration from designer file to code-behind file. -// /// -// protected global::System.Web.UI.WebControls.CheckBox allowAtRoot; - -// /// -// /// Pane5 control. -// /// -// /// -// /// Auto-generated field. -// /// To modify move field declaration from designer file to code-behind file. -// /// -// protected global::umbraco.uicontrols.Pane Pane5; - -// /// -// /// pp_allowedChildren control. -// /// -// /// -// /// Auto-generated field. -// /// To modify move field declaration from designer file to code-behind file. -// /// -// protected global::umbraco.uicontrols.PropertyPanel pp_allowedChildren; - -// /// -// /// lstAllowedContentTypes control. -// /// -// /// -// /// Auto-generated field. -// /// To modify move field declaration from designer file to code-behind file. -// /// -// protected global::System.Web.UI.WebControls.CheckBoxList lstAllowedContentTypes; - -// /// -// /// PlaceHolderAllowedContentTypes control. -// /// -// /// -// /// Auto-generated field. -// /// To modify move field declaration from designer file to code-behind file. -// /// -// protected global::System.Web.UI.WebControls.PlaceHolder PlaceHolderAllowedContentTypes; - -// /// -// /// pnlProperties control. -// /// -// /// -// /// Auto-generated field. -// /// To modify move field declaration from designer file to code-behind file. -// /// -// protected global::System.Web.UI.WebControls.Panel pnlProperties; - -// /// -// /// PanePropertiesInherited control. -// /// -// /// -// /// Auto-generated field. -// /// To modify move field declaration from designer file to code-behind file. -// /// -// protected global::umbraco.uicontrols.Pane PanePropertiesInherited; - -// /// -// /// propertiesMasterContentTypeName control. -// /// -// /// -// /// Auto-generated field. -// /// To modify move field declaration from designer file to code-behind file. -// /// -// protected global::System.Web.UI.WebControls.Literal propertiesMasterContentTypeName; - -// /// -// /// Pane4 control. -// /// -// /// -// /// Auto-generated field. -// /// To modify move field declaration from designer file to code-behind file. -// /// -// protected global::umbraco.uicontrols.Pane Pane4; - -// /// -// /// PropertyTypeNew control. -// /// -// /// -// /// Auto-generated field. -// /// To modify move field declaration from designer file to code-behind file. -// /// -// protected global::System.Web.UI.WebControls.PlaceHolder PropertyTypeNew; - -// /// -// /// PropertyTypes control. -// /// -// /// -// /// Auto-generated field. -// /// To modify move field declaration from designer file to code-behind file. -// /// -// protected global::System.Web.UI.WebControls.PlaceHolder PropertyTypes; - -// /// -// /// checkTxtAliasJs control. -// /// -// /// -// /// Auto-generated field. -// /// To modify move field declaration from designer file to code-behind file. -// /// -// protected global::System.Web.UI.WebControls.Literal checkTxtAliasJs; - -// /// -// /// DuplicateAliasValidator control. -// /// -// /// -// /// Auto-generated field. -// /// To modify move field declaration from designer file to code-behind file. -// /// -// protected global::System.Web.UI.WebControls.CustomValidator DuplicateAliasValidator; - -// /// -// /// Pane9 control. -// /// -// /// -// /// Auto-generated field. -// /// To modify move field declaration from designer file to code-behind file. -// /// -// protected global::umbraco.uicontrols.Pane Pane9; - -// /// -// /// pp_compositions control. -// /// -// /// -// /// Auto-generated field. -// /// To modify move field declaration from designer file to code-behind file. -// /// -// protected global::umbraco.uicontrols.PropertyPanel pp_compositions; - -// /// -// /// lstContentTypeCompositions control. -// /// -// /// -// /// Auto-generated field. -// /// To modify move field declaration from designer file to code-behind file. -// /// -// protected global::System.Web.UI.WebControls.CheckBoxList lstContentTypeCompositions; - -// /// -// /// PlaceHolderContentTypeCompositions control. -// /// -// /// -// /// Auto-generated field. -// /// To modify move field declaration from designer file to code-behind file. -// /// -// protected global::System.Web.UI.WebControls.PlaceHolder PlaceHolderContentTypeCompositions; -// } -//} \ No newline at end of file diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/create/MemberGroupTasks.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/create/MemberGroupTasks.cs index 38912b7f4f..42f8419723 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/create/MemberGroupTasks.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/create/MemberGroupTasks.cs @@ -21,7 +21,7 @@ namespace umbraco public override bool PerformDelete() { - // only build-in roles can be deleted + // only built-in roles can be deleted if (Member.IsUsingUmbracoRoles()) { MemberGroup.GetByName(Alias).delete(); diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/create/PartialViewTasksBase.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/create/PartialViewTasksBase.cs index cd9a2fcc26..042bf312d1 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/create/PartialViewTasksBase.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/create/PartialViewTasksBase.cs @@ -52,12 +52,7 @@ namespace umbraco if (IsPartialViewMacro == false) { var attempt = fileService.CreatePartialView(model, snippetName, User.Id); - - //TODO: We currently need to hack this because we are using the same editor for views, partial views, partial view macros and - // the editor is using normal UI whereas the partial view repo and these classes are using IFileSystem with relative references - // so the model.Path is a relative reference to the ~/Views/Partials folder, we need to ensure it's prefixed with "Partials/" - - _returnUrl = string.Format("settings/views/EditView.aspx?treeType=partialViews&file={0}", model.Path.TrimStart('/').EnsureStartsWith("Partials/")); + _returnUrl = string.Format("settings/views/EditView.aspx?treeType=partialViews&file={0}", model.Path.TrimStart('/')); return attempt.Success; } else @@ -73,11 +68,7 @@ namespace umbraco macroService.Save(new Macro(attempt.Result.Alias, attempt.Result.Alias) { ScriptPath = virtualPath }); } - //TODO: We currently need to hack this because we are using the same editor for views, partial views, partial view macros and - // the editor is using normal UI whereas the partial view repo and these classes are using IFileSystem with relative references - // so the model.Path is a relative reference to the ~/Views/Partials folder, we need to ensure it's prefixed with "MacroPartials/" - - _returnUrl = string.Format("settings/views/EditView.aspx?treeType=partialViewMacros&file={0}", model.Path.TrimStart('/').EnsureStartsWith("MacroPartials/")); + _returnUrl = string.Format("settings/views/EditView.aspx?treeType=partialViewMacros&file={0}", model.Path.TrimStart('/')); return attempt.Success; } diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/create/XsltTasks.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/create/XsltTasks.cs index 7a9b777c83..6c6174c0bb 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/create/XsltTasks.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/create/XsltTasks.cs @@ -1,5 +1,6 @@ using System; using System.Data; +using System.IO; using System.Web.Security; using Umbraco.Core; using Umbraco.Core.IO; @@ -9,6 +10,7 @@ using umbraco.BusinessLogic; using umbraco.DataLayer; using umbraco.BasePages; using umbraco.cms.businesslogic.member; +using Umbraco.Core.FileResources; namespace umbraco { @@ -22,14 +24,17 @@ namespace umbraco public override bool PerformSave() { + IOHelper.EnsurePathExists(SystemDirectories.Xslt); + IOHelper.EnsureFileExists(Path.Combine(IOHelper.MapPath(SystemDirectories.Xslt), "web.config"), Files.BlockingWebConfig); + var template = Alias.Substring(0, Alias.IndexOf("|||")); var fileName = Alias.Substring(Alias.IndexOf("|||") + 3, Alias.Length - Alias.IndexOf("|||") - 3).Replace(" ", ""); - if (!fileName.ToLowerInvariant().EndsWith(".xslt")) + if (fileName.ToLowerInvariant().EndsWith(".xslt") == false) fileName += ".xslt"; var xsltTemplateSource = IOHelper.MapPath(SystemDirectories.Umbraco + "/xslt/templates/" + template); var xsltNewFilename = IOHelper.MapPath(SystemDirectories.Xslt + "/" + fileName); - if (!System.IO.File.Exists(xsltNewFilename)) + if (File.Exists(xsltNewFilename) == false) { if (fileName.Contains("/")) //if there's a / create the folder structure for it { diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dashboard/FeedProxy.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dashboard/FeedProxy.aspx.cs index a938935d2b..b304cda0b6 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dashboard/FeedProxy.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/dashboard/FeedProxy.aspx.cs @@ -27,7 +27,9 @@ namespace dashboardUtilities if (Uri.TryCreate(url, UriKind.Absolute, out requestUri)) { var feedProxyXml = xmlHelper.OpenAsXmlDocument(IOHelper.MapPath(SystemFiles.FeedProxyConfig)); - if (feedProxyXml != null && feedProxyXml.SelectSingleNode(string.Concat("//allow[@host = '", requestUri.Host, "']")) != null) + if (feedProxyXml != null + && feedProxyXml.SelectSingleNode(string.Concat("//allow[@host = '", requestUri.Host, "']")) != null + && requestUri.Port == 80) { using (var client = new WebClient()) { diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Macros/editMacro.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Macros/editMacro.aspx.cs index 8d54f9dadc..c7e2db58a5 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Macros/editMacro.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Macros/editMacro.aspx.cs @@ -125,6 +125,8 @@ namespace umbraco.cms.presentation.developer { var dirInfo = new DirectoryInfo(path); + if (dirInfo.Exists == false) return; + // Populate subdirectories var dirInfos = dirInfo.GetDirectories(); foreach (var dir in dirInfos) @@ -240,6 +242,7 @@ namespace umbraco.cms.presentation.developer private void PopulateUserControls(string path) { var directoryInfo = new DirectoryInfo(path); + if (directoryInfo.Exists == false) return; var rootDir = IOHelper.MapPath(SystemDirectories.UserControls); diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/BrowseRepository.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/BrowseRepository.aspx.cs index f7b3a1bda9..84e4a38d24 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/BrowseRepository.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/BrowseRepository.aspx.cs @@ -12,6 +12,7 @@ using System.Xml; using System.Xml.XPath; using Umbraco.Core.Configuration; using Umbraco.Core.IO; +using Umbraco.Web; namespace umbraco.presentation.developer.packages { public partial class BrowseRepository : BasePages.UmbracoEnsuredPage { @@ -31,8 +32,8 @@ namespace umbraco.presentation.developer.packages { fb.Text = "" + ui.Text("errors", "filePermissionsError") + ":
    " + ex.Message; } - string category = Request.QueryString["category"]; - string repoGuid = Request.QueryString["repoGuid"]; + string category = Request.CleanForXss("category"); + string repoGuid = Request.CleanForXss("repoGuid"); var repo = cms.businesslogic.packager.repositories.Repository.getByGuid(repoGuid); if (repo == null) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/installedPackage.aspx b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/installedPackage.aspx index 2547040969..aa565cde1d 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/installedPackage.aspx +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/installedPackage.aspx @@ -31,9 +31,6 @@ - - - @@ -145,12 +142,39 @@

    - + -
    -

    <%= umbraco.ui.Text("packager", "packageUninstalledText") %>

    + +
    + Please wait while the browser is reloaded...
    + + + + - + diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/installedPackage.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/installedPackage.aspx.cs index f0b98db7f6..984866433e 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/installedPackage.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/installedPackage.aspx.cs @@ -397,9 +397,11 @@ namespace umbraco.presentation.developer.packages if (int.TryParse(li.Value, out nId)) { - var s = new Template(nId); - s.RemoveAllReferences(); - s.delete(); + var found = ApplicationContext.Services.FileService.GetTemplate(nId); + if (found != null) + { + ApplicationContext.Services.FileService.DeleteTemplate(found.Alias, UmbracoUser.Id); + } _pack.Data.Templates.Remove(nId.ToString()); } } diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/installer.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/installer.aspx.cs index 481313a52e..9db4048381 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/installer.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/installer.aspx.cs @@ -271,6 +271,9 @@ namespace umbraco.presentation.developer.packages case "finished": PerformFinishedAction(packageId, dir, Request.GetItemAsString("customUrl")); break; + case "uninstalled": + PerformUninstalledAction(); + break; default: break; } @@ -299,6 +302,13 @@ namespace umbraco.presentation.developer.packages PerformPostInstallCleanup(packageId, dir); } + private void PerformUninstalledAction() + { + HideAllPanes(); + Panel1.Text = "Package has been uninstalled"; + pane_uninstalled.Visible = true; + } + /// /// Perform the 'Refresh' action of the installer /// @@ -732,6 +742,9 @@ namespace umbraco.presentation.developer.packages /// protected global::umbraco.uicontrols.Pane pane_installing; + protected global::umbraco.uicontrols.Pane pane_uninstalled; + + /// /// progBar2 control. /// diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/RelationTypes/NewRelationType.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/RelationTypes/NewRelationType.aspx.cs index 38e4064cd4..8911e0de8f 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/RelationTypes/NewRelationType.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/RelationTypes/NewRelationType.aspx.cs @@ -63,7 +63,7 @@ namespace umbraco.cms.presentation.developer.RelationTypes var newRelationTypeId = relationService.GetRelationTypeByAlias(newRelationTypeAlias).Id; - ClientTools.ChangeContentFrameUrl("/umbraco/developer/RelationTypes/EditRelationType.aspx?id=" + newRelationTypeId).CloseModalWindow().ChildNodeCreated(); + ClientTools.ChangeContentFrameUrl("developer/RelationTypes/EditRelationType.aspx?id=" + newRelationTypeId).CloseModalWindow().ChildNodeCreated(); } } @@ -87,4 +87,4 @@ namespace umbraco.cms.presentation.developer.RelationTypes dropDownList.Items.Add(new ListItem(UmbracoObjectTypes.RecycleBin.GetFriendlyName(), Umbraco.Core.Constants.ObjectTypes.ContentRecycleBin)); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/RelationTypes/TreeMenu/ActionDeleteRelationType.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/RelationTypes/TreeMenu/ActionDeleteRelationType.cs index c9d7a21dfb..27c8d9e92e 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/RelationTypes/TreeMenu/ActionDeleteRelationType.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/RelationTypes/TreeMenu/ActionDeleteRelationType.cs @@ -67,7 +67,7 @@ namespace umbraco.cms.presentation.developer.RelationTypes.TreeMenu ///
    public string JsSource { - get { return "/umbraco/developer/RelationTypes/TreeMenu/ActionDeleteRelationType.js"; } + get { return "developer/RelationTypes/TreeMenu/ActionDeleteRelationType.js"; } } /// diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/RelationTypes/TreeMenu/ActionNewRelationType.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/RelationTypes/TreeMenu/ActionNewRelationType.cs index 2dcbbfb077..8640decb60 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/RelationTypes/TreeMenu/ActionNewRelationType.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/RelationTypes/TreeMenu/ActionNewRelationType.cs @@ -67,7 +67,7 @@ namespace umbraco.cms.presentation.developer.RelationTypes.TreeMenu /// public string JsSource { - get { return "/umbraco/developer/RelationTypes/TreeMenu/ActionNewRelationType.js"; } + get { return "developer/RelationTypes/TreeMenu/ActionNewRelationType.js"; } } /// diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/AssignDomain2.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/AssignDomain2.aspx.cs index 136cc1a27d..4f71e20d08 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/AssignDomain2.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/AssignDomain2.aspx.cs @@ -54,12 +54,12 @@ namespace umbraco.dialogs sb.AppendFormat("{0}{{ \"Id\": {1}, \"Code\": \"{2}\" }}", (i++ == 0 ? "" : ","), language.Id, language.IsoCode); sb.Append("]\r\n"); - sb.AppendFormat(",language: {0}", wildcard == null ? "undefined" : wildcard.Language.Id.ToString()); + sb.AppendFormat(",language: {0}", wildcard == null ? "undefined" : wildcard.LanguageId.ToString()); sb.Append(",domains: ["); i = 0; foreach (var domain in nodeDomains.Where(d => d.IsWildcard == false)) - sb.AppendFormat("{0}{{ \"Name\": \"{1}\", \"Lang\": \"{2}\" }}", (i++ == 0 ? "" :","), domain.DomainName, domain.Language.Id); + sb.AppendFormat("{0}{{ \"Name\": \"{1}\", \"Lang\": \"{2}\" }}", (i++ == 0 ? "" :","), domain.DomainName, domain.LanguageId); sb.Append("]\r\n"); data.Text = sb.ToString(); diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/about.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/about.aspx.cs index 463361a635..a30cb1eb7b 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/about.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/about.aspx.cs @@ -1,5 +1,6 @@ using System; using System.Globalization; +using Umbraco.Core; using Umbraco.Core.Configuration; namespace umbraco.dialogs @@ -14,9 +15,7 @@ namespace umbraco.dialogs { // Put user code to initialize the page here thisYear.Text = DateTime.Now.Year.ToString(CultureInfo.InvariantCulture); - version.Text = string.IsNullOrEmpty(UmbracoVersion.CurrentComment) - ? string.Format("{0} (Assembly version: {1})", UmbracoVersion.Current.ToString(3), UmbracoVersion.AssemblyVersion) - : string.Format("{0}-{1} (Assembly version: {2})", UmbracoVersion.Current.ToString(3), UmbracoVersion.CurrentComment, UmbracoVersion.AssemblyVersion); + version.Text = UmbracoVersion.GetSemanticVersion().ToSemanticString(); } #region Web Form Designer generated code diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/protectPage.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/protectPage.aspx.cs index ab88df2b4a..40e2532107 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/protectPage.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/protectPage.aspx.cs @@ -12,6 +12,7 @@ using umbraco.cms.businesslogic.web; using umbraco.controls; using umbraco.cms.helpers; using umbraco.BasePages; +using Umbraco.Core.Persistence; using Umbraco.Core.Security; namespace umbraco.presentation.umbraco.dialogs @@ -78,7 +79,7 @@ namespace umbraco.presentation.umbraco.dialogs if (IsPostBack == false) { - if (Access.IsProtected(documentId, documentObject.Path) && Access.GetProtectionType(documentId) != ProtectionType.NotProtected) + if (Access.IsProtected(documentId) && Access.GetProtectionType(documentId) != ProtectionType.NotProtected) { bt_buttonRemoveProtection.Visible = true; bt_buttonRemoveProtection.Attributes.Add("onClick", "return confirm('" + ui.Text("areyousure") + "')"); @@ -262,9 +263,11 @@ namespace umbraco.presentation.umbraco.dialogs p_buttons.Visible = false; pane_advanced.Visible = false; pane_simple.Visible = false; - - ClientTools.ReloadActionNode(true, false); - + var content = ApplicationContext.Current.Services.ContentService.GetById(pageId); + //reloads the current node in the tree + ClientTools.SyncTree(content.Path, true); + //reloads the current node's children in the tree + ClientTools.ReloadActionNode(false, true); feedback.type = global::umbraco.uicontrols.Feedback.feedbacktype.success; } } @@ -281,8 +284,11 @@ namespace umbraco.presentation.umbraco.dialogs feedback.Text = ui.Text("publicAccess", "paIsRemoved", new cms.businesslogic.CMSNode(pageId).Text) + "

    " + ui.Text("closeThisWindow") + ""; - ClientTools.ReloadActionNode(true, false); - + var content = ApplicationContext.Current.Services.ContentService.GetById(pageId); + //reloads the current node in the tree + ClientTools.SyncTree(content.Path, true); + //reloads the current node's children in the tree + ClientTools.ReloadActionNode(false, true); feedback.type = global::umbraco.uicontrols.Feedback.feedbacktype.success; } diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/sendToTranslation.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/sendToTranslation.aspx.cs index 588bbe5bfc..80118b4836 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/sendToTranslation.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/sendToTranslation.aspx.cs @@ -70,7 +70,7 @@ namespace umbraco.presentation.dialogs // Translators foreach (var u in BusinessLogic.User.getAll()) - if (u.UserType.Alias.ToLower() == "translator") + if (u.UserType.Alias.ToLower() == "translator" || UserHasTranslatePermission(u, _currentPage)) translator.Items.Add(new ListItem(u.Name, u.Id.ToString())); if (translator.Items.Count == 0) { @@ -84,6 +84,12 @@ namespace umbraco.presentation.dialogs } } + private bool UserHasTranslatePermission(BusinessLogic.User u, CMSNode node) + { + //the permissions column in umbracoUserType is legacy and needs to be rewritten but for now this is the only way to test + return u.GetPermissions(node.Path).Contains("4"); + } + protected void doTranslation_Click(object sender, EventArgs e) { // testing translate diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/sort.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/sort.aspx.cs index 2b104404b4..547d2c2ac7 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/sort.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/sort.aspx.cs @@ -23,6 +23,12 @@ namespace umbraco.cms.presentation { private readonly List _nodes = new List(); + protected bool HideDateColumn + { + set { ViewState["HideDateColumn"] = value; } + get { return ViewState["HideDateColumn"] == null ? false : (bool) ViewState["HideDateColumn"]; } + } + protected override void OnInit(EventArgs e) { CurrentApp = helper.Request("app"); @@ -56,13 +62,13 @@ namespace umbraco.cms.presentation if (parentId == -1) { foreach (var child in mediaService.GetRootMedia().ToList().OrderBy(x => x.SortOrder)) - _nodes.Add(CreateNode(child.Id, child.SortOrder, child.Name, child.CreateDate, icon)); + _nodes.Add(CreateNode(child.Id.ToInvariantString(), child.SortOrder, child.Name, child.CreateDate, icon)); } else { var children = mediaService.GetChildren(parentId); foreach (var child in children.OrderBy(x => x.SortOrder)) - _nodes.Add(CreateNode(child.Id, child.SortOrder, child.Name, child.CreateDate, icon)); + _nodes.Add(CreateNode(child.Id.ToInvariantString(), child.SortOrder, child.Name, child.CreateDate, icon)); } } @@ -73,29 +79,41 @@ namespace umbraco.cms.presentation if (parentId == -1) { foreach (var child in contentService.GetRootContent().ToList().OrderBy(x => x.SortOrder)) - _nodes.Add(CreateNode(child.Id, child.SortOrder, child.Name, child.CreateDate, icon)); + _nodes.Add(CreateNode(child.Id.ToInvariantString(), child.SortOrder, child.Name, child.CreateDate, icon)); } else { var children = contentService.GetChildren(parentId); foreach (var child in children) - _nodes.Add(CreateNode(child.Id, child.SortOrder, child.Name, child.CreateDate, icon)); + _nodes.Add(CreateNode(child.Id.ToInvariantString(), child.SortOrder, child.Name, child.CreateDate, icon)); } } - + + bindNodesToList(string.Empty); + } + else + { // hack for stylesheet, used to sort stylesheet properties if (app == Constants.Applications.Settings) { icon = "../images/umbraco/settingCss.gif"; - var ss = new StyleSheet(parentId); - foreach (var child in ss.Properties) - { - var node = new CMSNode(child.Id); - _nodes.Add(CreateNode(child.Id, node.sortOrder, child.Text, child.CreateDateTime, icon)); - } - } - bindNodesToList(string.Empty); + HideDateColumn = true; + + var stylesheetName = Request.GetItemAsString("ID"); + if (stylesheetName.IsNullOrWhiteSpace())throw new NullReferenceException("No Id passed in to editor"); + var stylesheet = Services.FileService.GetStylesheetByName(stylesheetName.EnsureEndsWith(".css")); + if (stylesheet == null) throw new InvalidOperationException("No stylesheet found by name " + stylesheetName); + + var sort = 0; + foreach (var child in stylesheet.Properties) + { + _nodes.Add(CreateNode(child.Name, sort, child.Name, DateTime.Now, icon)); + sort++; + } + + bindNodesToList(string.Empty); + } } } @@ -115,10 +133,12 @@ namespace umbraco.cms.presentation } foreach (var n in _nodes) - lt_nodes.Text += string.Format("

  • ", n.id, n.Name, n.createDate.ToShortDateString(), n.createDate.ToShortTimeString(), n.sortOder); + lt_nodes.Text += string.Format( + "", + n.id, n.Name, n.createDate.ToShortDateString(), n.createDate.ToShortTimeString(), n.sortOder, HideDateColumn ? "none" : "block"); } - private static SortableNode CreateNode(int id, int sortOrder, string name, DateTime createDateTime, string icon) + private static SortableNode CreateNode(string id, int sortOrder, string name, DateTime createDateTime, string icon) { var node = new SortableNode { @@ -133,7 +153,7 @@ namespace umbraco.cms.presentation public struct SortableNode { - public int id; + public string id; public int sortOder; public string Name; public string icon; diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/members/EditMemberType.aspx b/src/Umbraco.Web/umbraco.presentation/umbraco/members/EditMemberType.aspx deleted file mode 100644 index eb9cc90070..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/members/EditMemberType.aspx +++ /dev/null @@ -1,35 +0,0 @@ - -<%@ Page language="c#" MasterPageFile="../masterpages/umbracoPage.Master" Codebehind="EditMemberType.aspx.cs" AutoEventWireup="True" Inherits="umbraco.cms.presentation.members.EditMemberType" %> -<%@ Register TagPrefix="cc1" Namespace="umbraco.uicontrols" Assembly="controls" %> -<%@ Register TagPrefix="uc1" TagName="ContentTypeControlNew" Src="../controls/ContentTypeControlNew.ascx" %> -<%@ Register Namespace="umbraco" TagPrefix="umb" Assembly="umbraco" %> - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/members/EditMemberType.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/members/EditMemberType.aspx.cs deleted file mode 100644 index 36b6f2166e..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/members/EditMemberType.aspx.cs +++ /dev/null @@ -1,144 +0,0 @@ -//TODO: This needs a full rewrite in angular! kept here for reference for now - -//using System; -//using System.ComponentModel; -//using System.Data; -//using System.Drawing; -//using System.Globalization; -//using System.Linq; -//using System.Web; -//using System.Web.SessionState; -//using System.Web.UI; -//using System.Web.UI.WebControls; -//using System.Web.UI.HtmlControls; -//using ClientDependency.Core; -//using umbraco.cms.businesslogic.member; -//using umbraco.cms.presentation.Trees; -//using umbraco.controls; - -//namespace umbraco.cms.presentation.members -//{ -// public partial class EditMemberType : BasePages.UmbracoEnsuredPage -// { - -// public EditMemberType() -// { -// CurrentApp = BusinessLogic.DefaultApps.member.ToString(); - -// } -// protected PlaceHolder plc; -// private businesslogic.member.MemberType _memberType; - -// protected ContentTypeControlNew ContentTypeControlNew1; - -// protected override void OnInit(EventArgs e) -// { -// base.OnInit(e); - -// ContentTypeControlNew1.SavingContentType += ContentTypeControlNew1_SavingContentType; -// } - -// protected override void OnLoad(EventArgs e) -// { -// base.OnLoad(e); - -// _memberType = new businesslogic.member.MemberType(int.Parse(Request.QueryString["id"])); - -// ContentTypeControlNew1.InfoTabPage.Controls.Add(Pane1andmore); - -// if (!IsPostBack) -// { -// SetupExtraEditorControls(); - -// ClientTools -// .SetActiveTreeType(TreeDefinitionCollection.Instance.FindTree().Tree.Alias) -// .SyncTree(_memberType.Id.ToString(CultureInfo.InvariantCulture), false); -// } -// } - -// protected override bool OnBubbleEvent(object source, EventArgs args) -// { -// var handled = false; -// var eventArgs = args as SaveClickEventArgs; -// if (eventArgs != null) -// { -// var e = eventArgs; -// if (e.Message == "Saved") -// { -// SetupExtraEditorControls(); - -// ClientTools -// .ShowSpeechBubble(speechBubbleIcon.save, "Membertype saved", "") -// .SyncTree(_memberType.Id.ToString(CultureInfo.InvariantCulture), true); - -// } -// else -// { -// ClientTools -// .ShowSpeechBubble(e.IconType, e.Message, "") -// .SyncTree(_memberType.Id.ToString(CultureInfo.InvariantCulture), true); - -// } -// handled = true; -// } - -// return handled; -// } - -// private void SetupExtraEditorControls() -// { -// var dt1 = new DataTable(); -// dt1.Columns.Add("id"); -// dt1.Columns.Add("name"); -// dt1.Columns.Add("canedit"); -// dt1.Columns.Add("canview"); - -// //filter out the 'built-in' property types as we don't want to display these options for them -// var builtIns = Umbraco.Core.Constants.Conventions.Member.GetStandardPropertyTypeStubs().Select(x => x.Key).ToArray(); -// var propTypes = _memberType.PropertyTypes.Where(x => builtIns.Contains(x.Alias) == false); - -// foreach (var pt in propTypes) -// { -// var dr = dt1.NewRow(); -// dr["name"] = pt.Name; -// dr["id"] = pt.Id; -// dt1.Rows.Add(dr); -// } -// dgEditExtras.DataSource = dt1; -// dgEditExtras.DataBind(); -// } - -// /// -// /// Executes some code before the member type is saved, this allows us to save the member can edit/member can view information -// /// before the Save() command is executed. -// /// -// /// -// void ContentTypeControlNew1_SavingContentType(businesslogic.ContentType e) -// { -// var mt = e as MemberType; -// if (mt == null) return; //This should not happen! -// foreach (DataGridItem dgi in dgEditExtras.Items) -// { -// if (dgi.ItemType == ListItemType.Item || dgi.ItemType == ListItemType.AlternatingItem) -// { -// var pt = cms.businesslogic.propertytype.PropertyType.GetPropertyType(int.Parse(dgi.Cells[0].Text)); -// mt.setMemberCanEdit(pt, ((CheckBox)dgi.FindControl("ckbMemberCanEdit")).Checked); -// mt.setMemberViewOnProfile(pt, ((CheckBox)dgi.FindControl("ckbMemberCanView")).Checked); -// } -// } -// } - -// protected void dgEditExtras_itemdatabound(object sender,DataGridItemEventArgs e) -// { -// if(e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem) -// { -// var pt = cms.businesslogic.propertytype.PropertyType.GetPropertyType(int.Parse(((DataRowView)e.Item.DataItem).Row["id"].ToString())); -// ((CheckBox)e.Item.FindControl("ckbMemberCanEdit")).Checked = _memberType.MemberCanEdit(pt); -// ((CheckBox)e.Item.FindControl("ckbMemberCanView")).Checked = _memberType.ViewOnProfile(pt); -// } -// } - -// } - - -//} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/members/EditMemberType.aspx.designer.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/members/EditMemberType.aspx.designer.cs deleted file mode 100644 index f976022cbb..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/members/EditMemberType.aspx.designer.cs +++ /dev/null @@ -1,34 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:2.0.50727.3074 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace umbraco.cms.presentation.members { - - - public partial class EditMemberType { - - /// - /// Pane1andmore control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::umbraco.uicontrols.Pane Pane1andmore; - - /// - /// dgEditExtras control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.DataGrid dgEditExtras; - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/ping.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/ping.aspx.cs deleted file mode 100644 index 758c602942..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/ping.aspx.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.Collections; -using System.ComponentModel; -using System.Data; -using System.Drawing; -using System.Web; -using System.Web.SessionState; -using System.Web.UI; -using System.Web.UI.WebControls; -using System.Web.UI.HtmlControls; - -namespace umbraco.presentation -{ - - [Obsolete("This class is no longer used and will be removed in future versions.")] - public partial class ping : System.Web.UI.Page - { - #region Web Form Designer generated code - override protected void OnInit(EventArgs e) - { - // - // CODEGEN: This call is required by the ASP.NET Web Form Designer. - // - InitializeComponent(); - base.OnInit(e); - } - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - } - #endregion - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/preview/PreviewContent.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/preview/PreviewContent.cs index bc67292387..782a41d4be 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/preview/PreviewContent.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/preview/PreviewContent.cs @@ -104,7 +104,7 @@ namespace umbraco.presentation.preview if (document.ContentEntity.Published == false && ApplicationContext.Current.Services.ContentService.HasPublishedVersion(document.Id)) previewXml.Attributes.Append(XmlContent.CreateAttribute("isDraft")); - content.AddOrUpdateXmlNode(XmlContent, document.Id, document.Level, parentId, previewXml); + XmlContent = content.GetAddOrUpdateXmlNode(XmlContent, document.Id, document.Level, parentId, previewXml); } if (includeSubs) @@ -114,7 +114,7 @@ namespace umbraco.presentation.preview var previewXml = XmlContent.ReadNode(XmlReader.Create(new StringReader(prevNode.Xml))); if (prevNode.IsDraft) previewXml.Attributes.Append(XmlContent.CreateAttribute("isDraft")); - content.AddOrUpdateXmlNode(XmlContent, prevNode.NodeId, prevNode.Level, prevNode.ParentId, previewXml); + XmlContent = content.GetAddOrUpdateXmlNode(XmlContent, prevNode.NodeId, prevNode.Level, prevNode.ParentId, previewXml); } } @@ -193,10 +193,11 @@ namespace umbraco.presentation.preview { DeletePreviewFile(userId, file); } - // also delete any files accessed more than one hour ago + // also delete any files accessed more than 10 minutes ago + var now = DateTime.Now; foreach (FileInfo file in dir.GetFiles("*.config")) { - if ((DateTime.Now - file.LastAccessTime).TotalMinutes > 1) + if ((now - file.LastAccessTime).TotalMinutes > 10) DeletePreviewFile(userId, file); } } diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/settings/EditMediaType.aspx b/src/Umbraco.Web/umbraco.presentation/umbraco/settings/EditMediaType.aspx deleted file mode 100644 index ab35ae3a63..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/settings/EditMediaType.aspx +++ /dev/null @@ -1,15 +0,0 @@ -<%@ Register TagPrefix="cc2" Namespace="umbraco.uicontrols" Assembly="controls" %> - -<%@ Page Language="c#" CodeBehind="EditMediaType.aspx.cs" MasterPageFile="../masterpages/umbracoPage.Master" - Async="true" AsyncTimeOut="300" - AutoEventWireup="True" Inherits="umbraco.cms.presentation.settings.EditMediaType" %> - -<%@ Register TagPrefix="uc1" TagName="ContentTypeControlNew" Src="../controls/ContentTypeControlNew.ascx" %> - - - - diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/settings/EditMediaType.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/settings/EditMediaType.aspx.cs deleted file mode 100644 index 93e060c01d..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/settings/EditMediaType.aspx.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using umbraco.cms.presentation.Trees; - -namespace umbraco.cms.presentation.settings -{ - /// - /// Summary description for EditMediaType. - /// - public partial class EditMediaType : BasePages.UmbracoEnsuredPage - { - public EditMediaType() - { - CurrentApp = BusinessLogic.DefaultApps.settings.ToString(); - } - - protected void Page_Load(object sender, EventArgs e) - { - if (!IsPostBack) - { - ClientTools - .SetActiveTreeType(TreeDefinitionCollection.Instance.FindTree().Tree.Alias) - .SyncTree("-1,init," + helper.Request("id"), false); - } - } - - protected override bool OnBubbleEvent(object source, EventArgs e) - { - if (e is controls.SaveClickEventArgs) - { - var sce = (controls.SaveClickEventArgs)e; - - if (sce.Message == "Saved") - { - ClientTools.ShowSpeechBubble(speechBubbleIcon.save, "Mediatype saved", "Mediatype was successfully saved"); - } - else if (sce.Message.Contains("Tab")) - { - ClientTools.ShowSpeechBubble(sce.IconType, sce.Message, ""); - } - else - { - ClientTools.ShowSpeechBubble(sce.IconType, sce.Message, ""); - } - - return true; - } - - return false; - } - - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/settings/EditMediaType.aspx.designer.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/settings/EditMediaType.aspx.designer.cs deleted file mode 100644 index 3bf60caa2c..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/settings/EditMediaType.aspx.designer.cs +++ /dev/null @@ -1,24 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace umbraco.cms.presentation.settings { - - - public partial class EditMediaType { - - /// - /// ContentTypeControlNew1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.UserControl ContentTypeControlNew1; - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/settings/EditNodeTypeNew.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/settings/EditNodeTypeNew.aspx.cs deleted file mode 100644 index 2d92016528..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/settings/EditNodeTypeNew.aspx.cs +++ /dev/null @@ -1,168 +0,0 @@ -//TODO: This needs a full rewrite in angular! kept here for reference for now - -//using System; -//using System.Collections; -//using System.Collections.Generic; -//using System.Globalization; -//using System.Web.UI.WebControls; -//using umbraco.cms.presentation.Trees; -//using umbraco.cms.businesslogic.web; -//using System.Linq; -//using umbraco.controls; - -//namespace umbraco.settings -//{ -// public partial class EditContentTypeNew : BasePages.UmbracoEnsuredPage -// { -// public EditContentTypeNew() -// { -// CurrentApp = BusinessLogic.DefaultApps.settings.ToString(); -// } - -// protected controls.ContentTypeControlNew ContentTypeControlNew1; -// private DocumentType _dt; - -// override protected void OnInit(EventArgs e) -// { -// ContentTypeControlNew1.DocumentTypeCallback = new Func(UpdateAllowedTemplates); -// ContentTypeControlNew1.InfoTabPage.Controls.Add(tmpPane); -// base.OnInit(e); -// } - -// protected void Page_Load(object sender, EventArgs e) -// { -// _dt = new DocumentType(int.Parse(Request.QueryString["id"])); -// if (!Page.IsPostBack) -// { -// BindTemplates(); - -// ClientTools -// .SetActiveTreeType(TreeDefinitionCollection.Instance.FindTree().Tree.Alias) -// .SyncTree("-1,init," + _dt.Path.Replace("-1,", ""), false); -// } -// } - -// protected override bool OnBubbleEvent(object source, EventArgs args) -// { -// bool handled = false; -// var eventArgs = args as SaveClickEventArgs; -// if (eventArgs != null) -// { -// var e = eventArgs; -// if (e.Message == "Saved") -// { -// ClientTools.ShowSpeechBubble(e.IconType, ui.Text("contentTypeSavedHeader"), ""); - -// BindTemplates(); -// } -// else -// { -// ClientTools.ShowSpeechBubble(e.IconType, e.Message, ""); -// } -// handled = true; -// } -// return handled; -// } - -// protected void dgTemplate_itemdatabound(object sender, DataGridItemEventArgs e) -// { -// if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem) -// { -// ((CheckBox)e.Item.FindControl("ckbAllowTemplate")).Checked = true; -// } -// } - -// private DocumentType UpdateAllowedTemplates(DocumentType documentType) -// { -// var tmp = new ArrayList(); - -// foreach (ListItem li in templateList.Items) -// { -// if (li.Selected) -// tmp.Add(new cms.businesslogic.template.Template(int.Parse(li.Value))); -// } - -// var tt = new cms.businesslogic.template.Template[tmp.Count]; -// for (int i = 0; i < tt.Length; i++) -// { -// tt[i] = (cms.businesslogic.template.Template)tmp[i]; -// } - -// documentType.allowedTemplates = tt; - -// if (documentType.allowedTemplates.Length > 0 && ddlTemplates.SelectedIndex >= 0) -// { -// documentType.DefaultTemplate = int.Parse(ddlTemplates.SelectedValue); -// } -// else -// { -// documentType.RemoveDefaultTemplate(); -// } - -// _dt = documentType; - -// return documentType; -// } - -// private void BindTemplates() -// { -// var templates = (from t in cms.businesslogic.template.Template.GetAllAsList() -// join at in _dt.allowedTemplates on t.Id equals at.Id into at_l -// from at in at_l.DefaultIfEmpty() -// select new -// { -// Id = t.Id, -// Name = t.Text, -// Selected = at != null -// }).ToList(); - -// templateList.Items.Clear(); -// templateList.Items.AddRange(templates.ConvertAll(item => -// { -// var li = new ListItem { Text = item.Name, Value = item.Id.ToString(CultureInfo.InvariantCulture), Selected = item.Selected }; -// return li; -// }).ToArray()); - - -// ddlTemplates.Enabled = templates.Any(); -// ddlTemplates.Items.Clear(); -// ddlTemplates.Items.Insert(0, new ListItem(ui.Text("choose") + "...", "0")); -// ddlTemplates.Items.AddRange(templates.ConvertAll(item => -// { -// var li = new ListItem { Text = item.Name, Value = item.Id.ToString(CultureInfo.InvariantCulture) }; -// return li; -// }).ToArray()); - -// var ddlTemplatesSelect = ddlTemplates.Items.FindByValue(_dt.DefaultTemplate.ToString(CultureInfo.InvariantCulture)); -// if (ddlTemplatesSelect != null) -// ddlTemplatesSelect.Selected = true; -// } - -// /// -// /// tmpPane control. -// /// -// /// -// /// Auto-generated field. -// /// To modify move field declaration from designer file to code-behind file. -// /// -// protected global::umbraco.uicontrols.Pane tmpPane; - -// /// -// /// templateList control. -// /// -// /// -// /// Auto-generated field. -// /// To modify move field declaration from designer file to code-behind file. -// /// -// protected global::System.Web.UI.WebControls.CheckBoxList templateList; - -// /// -// /// ddlTemplates control. -// /// -// /// -// /// Auto-generated field. -// /// To modify move field declaration from designer file to code-behind file. -// /// -// protected global::System.Web.UI.WebControls.DropDownList ddlTemplates; -// } -//} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/settings/scripts/editScript.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/settings/scripts/editScript.aspx.cs index ee54297f06..8cd9082b5b 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/settings/scripts/editScript.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/settings/scripts/editScript.aspx.cs @@ -41,58 +41,42 @@ namespace umbraco.cms.presentation.settings.scripts protected MenuButton SaveButton; - private string file; + private string filename; + protected string ScriptTreeSyncPath { get; private set; } protected override void OnLoad(EventArgs e) { base.OnLoad(e); - NameTxt.Text = file; - string path = ""; - if (file.StartsWith("~/")) - path = IOHelper.ResolveUrl(file); - else - path = IOHelper.ResolveUrl(SystemDirectories.Scripts + "/" + file); + // get the script, ensure it exists (not null) and validate (because + // the file service ensures that it loads scripts from the proper location + // but does not seem to validate extensions?) - in case of an error, + // throw - that's what we did anyways. + // also scrapping the code that added .cshtml and .vbhtml extensions, and + // ~/Views directory - we're not using editScript.aspx for views anymore. - lttPath.Text = "" + path + ""; + var svce = ApplicationContext.Current.Services.FileService; + var script = svce.GetScriptByName(filename); + if (script == null) // not found + throw new FileNotFoundException("Could not find file '" + filename + "'."); - var exts = UmbracoConfig.For.UmbracoSettings().Content.ScriptFileTypes.ToList(); - if (UmbracoConfig.For.UmbracoSettings().Templates.DefaultRenderingEngine == RenderingEngine.Mvc) - { - exts.Add("cshtml"); - exts.Add("vbhtml"); - } + lttPath.Text = "" + script.VirtualPath + ""; + editorSource.Text = script.Content; + ScriptTreeSyncPath = DeepLink.GetTreePathFromFilePath(filename); - var dirs = SystemDirectories.Scripts; - if (UmbracoConfig.For.UmbracoSettings().Templates.DefaultRenderingEngine == RenderingEngine.Mvc) - dirs += "," + SystemDirectories.MvcViews; - - // validate file - IOHelper.ValidateEditPath(IOHelper.MapPath(path), dirs.Split(',')); - - // validate extension - IOHelper.ValidateFileExtension(IOHelper.MapPath(path), exts); - - - StreamReader SR; - string S; - SR = File.OpenText(IOHelper.MapPath(path)); - S = SR.ReadToEnd(); - SR.Close(); - - editorSource.Text = S; + // name derives from filename, clean for xss + NameTxt.Text = filename.CleanForXss('\\', '/'); Panel1.Text = ui.Text("editscript", base.getUser()); pp_name.Text = ui.Text("name", base.getUser()); pp_path.Text = ui.Text("path", base.getUser()); - if (!IsPostBack) + if (IsPostBack == false) { - string sPath = DeepLink.GetTreePathFromFilePath(file); ClientTools .SetActiveTreeType(TreeDefinitionCollection.Instance.FindTree().Tree.Alias) - .SyncTree(sPath, false); + .SyncTree(ScriptTreeSyncPath, false); } } @@ -100,12 +84,12 @@ namespace umbraco.cms.presentation.settings.scripts { base.OnInit(e); - file = Request.QueryString["file"].TrimStart('/'); + filename = Request.QueryString["file"].Replace('\\', '/').TrimStart('/'); //need to change the editor type if it is XML - if (file.EndsWith("xml")) + if (filename.EndsWith("xml")) editorSource.CodeBase = uicontrols.CodeArea.EditorType.XML; - else if (file.EndsWith("master")) + else if (filename.EndsWith("master")) editorSource.CodeBase = uicontrols.CodeArea.EditorType.HTML; @@ -153,7 +137,6 @@ namespace umbraco.cms.presentation.settings.scripts } } - protected override void OnPreRender(EventArgs e) { diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/settings/stylesheet/editstylesheet.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/settings/stylesheet/editstylesheet.aspx.cs index f93d40da21..c570e45a8c 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/settings/stylesheet/editstylesheet.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/settings/stylesheet/editstylesheet.aspx.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using System.Linq; using System.Web.UI; using Umbraco.Core.IO; @@ -6,6 +7,7 @@ using Umbraco.Web; using umbraco.BasePages; using umbraco.BusinessLogic; using umbraco.cms.businesslogic.web; +using umbraco.cms.helpers; using umbraco.cms.presentation.Trees; using umbraco.uicontrols; using Umbraco.Core; @@ -17,10 +19,11 @@ namespace umbraco.cms.presentation.settings.stylesheet /// public partial class editstylesheet : UmbracoEnsuredPage { - private Umbraco.Core.Models.Stylesheet _sheet; - protected MenuButton SaveButton; + private string filename; + protected string TreeSyncPath { get; private set; } + public editstylesheet() { CurrentApp = DefaultApps.settings.ToString(); @@ -30,6 +33,7 @@ namespace umbraco.cms.presentation.settings.stylesheet { base.OnInit(e); + filename = Request.QueryString["id"].Replace('\\', '/').TrimStart('/'); var editor = Panel1.NewTabPage(ui.Text("stylesheet")); editor.Controls.Add(Pane7); @@ -37,7 +41,6 @@ namespace umbraco.cms.presentation.settings.stylesheet var props = Panel1.NewTabPage(ui.Text("properties")); props.Controls.Add(Pane8); - SaveButton = Panel1.Menu.NewButton(); SaveButton.Text = ui.Text("save"); SaveButton.ButtonType = MenuButtonType.Primary; @@ -46,28 +49,27 @@ namespace umbraco.cms.presentation.settings.stylesheet } protected void Page_Load(object sender, EventArgs e) - { - + { Panel1.Text = ui.Text("stylesheet", "editstylesheet", UmbracoUser); pp_name.Text = ui.Text("name", UmbracoUser); pp_path.Text = ui.Text("path", UmbracoUser); - _sheet = Services.FileService.GetStylesheetByName(Request.QueryString["id"]); - if (_sheet == null) throw new InvalidOperationException("No stylesheet found with name: " + Request.QueryString["id"]); + var stylesheet = Services.FileService.GetStylesheetByName(filename); + if (stylesheet == null) // not found + throw new FileNotFoundException("Could not find file '" + filename + "'."); - lttPath.Text = "" + _sheet.VirtualPath + ""; + lttPath.Text = "" + stylesheet.VirtualPath + ""; + editorSource.Text = stylesheet.Content; + TreeSyncPath = DeepLink.GetTreePathFromFilePath(filename); + // name derives from path, without the .css extension, clean for xss + NameTxt.Text = stylesheet.Path.TrimEnd(".css").CleanForXss('\\', '/'); if (IsPostBack == false) { - NameTxt.Text = _sheet.Path.TrimEnd(".css"); - editorSource.Text = _sheet.Content; - ClientTools .SetActiveTreeType(Constants.Trees.Stylesheets) - .SyncTree("-1,init," + _sheet.Path - //needs a double escape to work with JS - .Replace("\\", "\\\\").TrimEnd(".css"), false); + .SyncTree(TreeSyncPath, false); } } diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/settings/stylesheet/property/EditStyleSheetProperty.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/settings/stylesheet/property/EditStyleSheetProperty.aspx.cs index 75471cd676..f058b31bb7 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/settings/stylesheet/property/EditStyleSheetProperty.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/settings/stylesheet/property/EditStyleSheetProperty.aspx.cs @@ -45,7 +45,7 @@ namespace umbraco.cms.presentation.settings.stylesheet var propName = IsPostBack ? OriginalName.Value : Request.QueryString["prop"]; - _stylesheetproperty = _sheet.Properties.FirstOrDefault(x => x.Name == propName); + _stylesheetproperty = _sheet.Properties.FirstOrDefault(x => x.Name.InvariantEquals(propName)); if (_stylesheetproperty == null) throw new InvalidOperationException("No stylesheet property found with name: " + Request.QueryString["prop"]); Panel1.Text = ui.Text("stylesheet", "editstylesheetproperty", UmbracoUser); diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUser.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUser.aspx.cs index 4982d6af5b..2254e96b4c 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUser.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUser.aspx.cs @@ -15,13 +15,15 @@ using Umbraco.Web; using Umbraco.Web.Security; using umbraco.BasePages; using umbraco.BusinessLogic; -using umbraco.cms.businesslogic.propertytype; using umbraco.cms.businesslogic.web; using umbraco.controls; using umbraco.uicontrols; using umbraco.cms.presentation.Trees; using Umbraco.Core.IO; using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Services; +using PropertyType = umbraco.cms.businesslogic.propertytype.PropertyType; namespace umbraco.cms.presentation.user { @@ -99,38 +101,26 @@ namespace umbraco.cms.presentation.user userType.Items.Add(li); } } + + var userCulture = UserExtensions.GetUserCulture(u.Language, Services.TextService); // Populate ui language lsit - foreach ( - string f in - Directory.GetFiles(IOHelper.MapPath(SystemDirectories.Umbraco + "/config/lang"), "*.xml") - ) + foreach (var lang in Services.TextService.GetSupportedCultures()) { - XmlDocument x = new XmlDocument(); - x.Load(f); + var regionCode = Services.TextService.ConvertToRegionCodeFromSupportedCulture(lang); + + var li = new ListItem(lang.DisplayName, regionCode); - var alias = x.DocumentElement.Attributes.GetNamedItem("alias").Value; + if (Equals(lang, userCulture)) + li.Selected = true; - //ensure that only unique languages are added - if (userLanguage.Items.FindByValue(alias) == null) - { - ListItem li = - new ListItem(x.DocumentElement.Attributes.GetNamedItem("intName").Value, - alias); - - - if (x.DocumentElement.Attributes.GetNamedItem("alias").Value == u.Language) - li.Selected = true; - - userLanguage.Items.Add(li); - } - + userLanguage.Items.Add(li); } // Console access and disabling NoConsole.Checked = u.NoConsole; Disabled.Checked = u.Disabled; - + PlaceHolder medias = new PlaceHolder(); mediaPicker.AppAlias = Constants.Applications.Media; mediaPicker.TreeAlias = "media"; @@ -155,19 +145,19 @@ namespace umbraco.cms.presentation.user // Add password changer - var passwordChanger = (passwordChanger) LoadControl(SystemDirectories.Umbraco + "/controls/passwordChanger.ascx"); + var passwordChanger = (passwordChanger)LoadControl(SystemDirectories.Umbraco + "/controls/passwordChanger.ascx"); passwordChanger.MembershipProviderName = UmbracoConfig.For.UmbracoSettings().Providers.DefaultBackOfficeUserProvider; - + //Add a custom validation message for the password changer var passwordValidation = new CustomValidator - { - ID = "PasswordChangerValidator" - }; + { + ID = "PasswordChangerValidator" + }; var validatorContainer = new HtmlGenericControl("div") - { - Visible = false, - EnableViewState = false - }; + { + Visible = false, + EnableViewState = false + }; validatorContainer.Attributes["class"] = "alert alert-error"; validatorContainer.Style.Add(HtmlTextWriterStyle.MarginTop, "10px"); validatorContainer.Style.Add(HtmlTextWriterStyle.Width, "300px"); @@ -188,7 +178,7 @@ namespace umbraco.cms.presentation.user Pane ppNodes = new Pane(); ppNodes.addProperty(ui.Text("user", "startnode", UmbracoUser), content); ppNodes.addProperty(ui.Text("user", "mediastartnode", UmbracoUser), medias); - + //Generel umrbaco access Pane ppAccess = new Pane(); ppAccess.addProperty(ui.Text("user", "noConsole", UmbracoUser), NoConsole); @@ -202,12 +192,12 @@ namespace umbraco.cms.presentation.user TabPage userInfo = UserTabs.NewTabPage(u.Name); userInfo.Controls.Add(pp); - + userInfo.Controls.Add(ppAccess); userInfo.Controls.Add(ppNodes); userInfo.Controls.Add(ppModules); - + userInfo.HasMenu = true; var save = userInfo.Menu.NewButton(); @@ -221,7 +211,7 @@ namespace umbraco.cms.presentation.user sectionValidator.ControlToValidate = lapps.ID; sectionValidator.ErrorMessage = ui.Text("errorHandling", "errorMandatoryWithoutTab", ui.Text("user", "modules", UmbracoUser), UmbracoUser); sectionValidator.CssClass = "error"; - sectionValidator.Style.Add("color", "red"); + sectionValidator.Style.Add("color", "red"); SetupForm(); @@ -310,7 +300,7 @@ namespace umbraco.cms.presentation.user //now do the actual change var changePassResult = _membershipHelper.ChangePassword( - membershipUser.UserName, changePasswordModel, BackOfficeProvider); + membershipUser.UserName, changePasswordModel, BackOfficeProvider); if (changePassResult.Success) { @@ -345,8 +335,8 @@ namespace umbraco.cms.presentation.user throw new ProviderException("Could not find user in the membership provider with login name " + u.LoginName); } - var passwordChangerControl = (passwordChanger) passw.Controls[0]; - var passwordChangerValidator = (CustomValidator) passw.Controls[1].Controls[0].Controls[0]; + var passwordChangerControl = (passwordChanger)passw.Controls[0]; + var passwordChangerValidator = (CustomValidator)passw.Controls[1].Controls[0].Controls[0]; //perform the changing password logic ChangePassword(passwordChangerControl, membershipUser, passwordChangerValidator); @@ -359,7 +349,7 @@ namespace umbraco.cms.presentation.user u.Name = uname.Text.Trim(); u.Language = userLanguage.SelectedValue; u.UserType = UserType.GetUserType(int.Parse(userType.SelectedValue)); - u.Email = email.Text.Trim(); + u.Email = email.Text.Trim(); u.LoginName = lname.Text; u.Disabled = Disabled.Checked; u.NoConsole = NoConsole.Checked; @@ -373,9 +363,9 @@ namespace umbraco.cms.presentation.user else startNode = -1; } - u.StartNodeId = startNode; - - + u.StartNodeId = startNode; + + int mstartNode; if (int.TryParse(mediaPicker.Value, out mstartNode) == false) { diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/users/NodePermissions.ascx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/users/NodePermissions.ascx.cs index e7b585f975..26a601a9ac 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/users/NodePermissions.ascx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/users/NodePermissions.ascx.cs @@ -61,7 +61,7 @@ namespace umbraco.cms.presentation.user } /// - /// The JavaScript method to call when a node is checked. The method will receive a comma seperated list of node IDs that are checked. + /// The JavaScript method to call when a node is checked. The method will receive a comma separated list of node IDs that are checked. /// public string OnClientItemChecked { diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/webService.asmx b/src/Umbraco.Web/umbraco.presentation/umbraco/webService.asmx deleted file mode 100644 index 19848579d6..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/webService.asmx +++ /dev/null @@ -1 +0,0 @@ -<%@ WebService Language="c#" Codebehind="webService.asmx.cs" Class="umbraco.webService" %> diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/webService.asmx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/webService.asmx.cs deleted file mode 100644 index ed525614cf..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/webService.asmx.cs +++ /dev/null @@ -1,217 +0,0 @@ -using System; -using System.Collections; -using System.ComponentModel; -using System.Data; -using System.Diagnostics; -using System.Web; -using System.Web.Services; -using System.Xml; -using UmbracoExamine; -using System.Collections.Generic; -using Examine; -using umbraco.presentation; -using System.Linq; - -namespace umbraco -{ - - //TODO: There's no app checking security in here which means that any authorized user can query for all content and all media - // with all information exposed even when they don't have access to content or media. - - /// - /// Summary description for webService. - /// - /// - - [WebService(Namespace = "http://umbraco.org/webservices/")] - public class webService : System.Web.Services.WebService - { - public webService() - { - //CODEGEN: This call is required by the ASP.NET Web Services Designer - InitializeComponent(); - } - - [WebMethod] - public XmlNode GetNode(int NodeId, string ContextID) - { - XmlDocument xd = new XmlDocument(); - if (BasePages.BasePage.ValidateUserContextID(ContextID)) - { - return new cms.businesslogic.CMSNode(NodeId).ToXml(xd, false); - } - else - return null; - } - - [WebMethod] - public XmlNode GetNodeValidate(int NodeId, string Login, string Password) - { - XmlDocument xd = new XmlDocument(); - if (BusinessLogic.User.validateCredentials(Login, Password)) - { - return new cms.businesslogic.CMSNode(NodeId).ToXml(xd, false); - } - else - return null; - } - - [WebMethod] - public XmlNode GetDocument(int NodeId, string ContextID) - { - XmlDocument xd = new XmlDocument(); - if (BasePages.BasePage.ValidateUserContextID(ContextID)) - { - return new cms.businesslogic.web.Document(NodeId).ToXml(xd, false); - } - else - return null; - } - - [WebMethod] - public XmlNode GetMedia(int NodeId, string ContextID) - { - XmlDocument xd = new XmlDocument(); - if (BasePages.BasePage.ValidateUserContextID(ContextID)) - { - return new cms.businesslogic.media.Media(NodeId).ToXml(xd, false); - } - else - return null; - } - - [WebMethod] - public XmlNode GetMediaValidate(int NodeId, string Login, string Password) - { - XmlDocument xd = new XmlDocument(); - if (BusinessLogic.User.validateCredentials(Login, Password)) - { - return new cms.businesslogic.media.Media(NodeId).ToXml(xd, false); - } - else - return null; - } - - - [WebMethod] - public XmlNode GetDocumentValidate(int NodeId, string Login, string Password) - { - XmlDocument xd = new XmlDocument(); - if (BusinessLogic.User.validateCredentials(Login, Password)) - { - return new cms.businesslogic.web.Document(NodeId).ToXml(xd, false); - } - else - return null; - } - - [WebMethod] - public XmlNode GetDocumentsBySearchValidate(string Query, int StartNodeId, string Login, string Password) - { - XmlDocument xd = new XmlDocument(); - if (BusinessLogic.User.validateCredentials(Login, Password)) - { - return doQuery(Query, xd, StartNodeId); - } - else - { - XmlNode result = xd.CreateNode(XmlNodeType.Element, "error", ""); - result.AppendChild(xmlHelper.addTextNode(xd, "error", "Not a valid login")); - return result; - } - } - - [WebMethod] - public XmlNode GetDocumentsBySearch(string Query, int StartNodeId, string ContextID) - { - XmlDocument xd = new XmlDocument(); - if (BasePages.BasePage.ValidateUserContextID(ContextID)) - { - return doQuery(Query.ToLower(), xd, StartNodeId); - } - else - { - XmlNode result = xd.CreateNode(XmlNodeType.Element, "error", ""); - result.AppendChild(xmlHelper.addTextNode(xd, "error", "Not a valid login")); - return result; - } - } - - private XmlNode doQuery(string Query, XmlDocument xd, int StartNodeId) - { - XmlNode result = xd.CreateNode(XmlNodeType.Element, "documents", ""); - try - { - //if the query starts with "*" then query all fields - var internalSearcher = ExamineManager.Instance.SearchProviderCollection["InternalSearcher"]; - var criteria = internalSearcher.CreateSearchCriteria(IndexTypes.Content); - IEnumerable results; - if (Query.StartsWith("*")) - { - results = internalSearcher.Search("*", true); - } - else - { - var operation = criteria.NodeName(Query.ToLower()); - if (StartNodeId > 0) - { - operation.Or().Id(StartNodeId); - } - - results = internalSearcher.Search(operation.Compile()).Take(20); - } - - //var criteria = new SearchCriteria(Query - // , Query.StartsWith("*") ? new string[] { } : new string[] { "nodeName", "id" } - // , new string[] { } - // , false - // , StartNodeId > 0 ? (int?)StartNodeId : null - // , 20); - - foreach (var r in results) - { - XmlElement x = xd.CreateElement("document"); - x.SetAttribute("id", r.Id.ToString()); - x.SetAttribute("nodeName", r.Fields["nodeName"]); - result.AppendChild(x); - } - } - catch (Exception ee) - { - XmlElement x = xd.CreateElement("document"); - x.SetAttribute("id", "0"); - x.SetAttribute("nodeName", "Error in search: " + ee.ToString()); - result.AppendChild(x); - } - return result; - } - - #region Component Designer generated code - - //Required by the Web Services Designer - private IContainer components = null; - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - } - - /// - /// Clean up any resources being used. - /// - protected override void Dispose(bool disposing) - { - if (disposing && components != null) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #endregion - - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/CheckForUpgrade.asmx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/CheckForUpgrade.asmx.cs index 2d6beced38..909b89b96b 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/CheckForUpgrade.asmx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/CheckForUpgrade.asmx.cs @@ -56,9 +56,8 @@ namespace umbraco.presentation.webservices // Check for current install Id Guid installId = Guid.NewGuid(); - BusinessLogic.StateHelper.Cookies.Cookie installCookie = - new BusinessLogic.StateHelper.Cookies.Cookie("umb_installId", 1); - if (!String.IsNullOrEmpty(installCookie.GetValue())) + var installCookie = new BusinessLogic.StateHelper.Cookies.Cookie("umb_installId", 1); + if (string.IsNullOrEmpty(installCookie.GetValue()) == false) { if (Guid.TryParse(installCookie.GetValue(), out installId)) { @@ -70,8 +69,8 @@ namespace umbraco.presentation.webservices } installCookie.SetValue(installId.ToString()); - string dbProvider = String.Empty; - if (!String.IsNullOrEmpty(global::Umbraco.Core.Configuration.GlobalSettings.ConfigurationStatus)) + string dbProvider = string.Empty; + if (string.IsNullOrEmpty(global::Umbraco.Core.Configuration.GlobalSettings.ConfigurationStatus) == false) dbProvider = ApplicationContext.Current.DatabaseContext.DatabaseProvider.ToString(); var check = new global::Umbraco.Web.org.umbraco.update.CheckForUpgrade(); diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/codeEditorSave.asmx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/codeEditorSave.asmx.cs index 665d522efb..6b927a4d29 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/codeEditorSave.asmx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/codeEditorSave.asmx.cs @@ -33,7 +33,8 @@ namespace umbraco.presentation.webservices [ScriptService] public class codeEditorSave : UmbracoAuthorizedWebService { - + + [Obsolete("This method has been superceded by the REST service /Umbraco/RestServices/SaveFile/SaveStylesheet which is powered by the SaveFileController.")] [WebMethod] public string SaveCss(string fileName, string oldName, string fileContents, int fileID) { @@ -61,6 +62,7 @@ namespace umbraco.presentation.webservices { if (AuthorizeRequest(DefaultApps.developer.ToString())) { + IOHelper.EnsurePathExists(SystemDirectories.Xslt); // validate file IOHelper.ValidateEditPath(IOHelper.MapPath(SystemDirectories.Xslt + "/" + fileName), @@ -68,8 +70,7 @@ namespace umbraco.presentation.webservices // validate extension IOHelper.ValidateFileExtension(IOHelper.MapPath(SystemDirectories.Xslt + "/" + fileName), new List() { "xsl", "xslt" }); - - + StreamWriter SW; string tempFileName = IOHelper.MapPath(SystemDirectories.Xslt + "/" + DateTime.Now.Ticks + "_temp.xslt"); SW = File.CreateText(tempFileName); @@ -258,6 +259,7 @@ namespace umbraco.presentation.webservices // return "false"; //} + [Obsolete("This method has been superceded by the REST service /Umbraco/RestServices/SaveFile/SaveScript which is powered by the SaveFileController.")] [WebMethod] public string SaveScript(string filename, string oldName, string contents) { @@ -295,7 +297,10 @@ namespace umbraco.presentation.webservices if (File.Exists(saveOldPath)) File.Delete(saveOldPath); } - + + //ensure the folder exists before saving + Directory.CreateDirectory(Path.GetDirectoryName(savePath)); + using (var sw = File.CreateText(savePath)) { sw.Write(val); diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/legacyAjaxCalls.asmx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/legacyAjaxCalls.asmx.cs index 3f20c55eb6..4ff115e067 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/legacyAjaxCalls.asmx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/legacyAjaxCalls.asmx.cs @@ -68,7 +68,7 @@ namespace umbraco.presentation.webservices //check which parameters to pass depending on the types passed in int intNodeId; - if (nodeType == "memberGroup") + if (nodeType == "memberGroups") { LegacyDialogHandler.Delete( new HttpContextWrapper(HttpContext.Current), @@ -270,7 +270,9 @@ namespace umbraco.presentation.webservices } private string SaveXslt(string fileName, string fileContents, bool ignoreDebugging) - { + { + IOHelper.EnsurePathExists(SystemDirectories.Xslt); + var tempFileName = IOHelper.MapPath(SystemDirectories.Xslt + "/" + System.DateTime.Now.Ticks + "_temp.xslt"); using (var sw = File.CreateText(tempFileName)) { @@ -406,6 +408,9 @@ namespace umbraco.presentation.webservices //Directory check.. only allow files in script dir and below to be edited if (savePath.StartsWith(IOHelper.MapPath(SystemDirectories.Scripts + "/"))) { + //ensure the folder exists before saving + Directory.CreateDirectory(Path.GetDirectoryName(savePath)); + using (var sw = File.CreateText(IOHelper.MapPath(SystemDirectories.Scripts + "/" + filename))) { sw.Write(val); diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/nodeSorter.asmx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/nodeSorter.asmx.cs index 2177fedeb1..1ab2346054 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/nodeSorter.asmx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/nodeSorter.asmx.cs @@ -31,54 +31,75 @@ namespace umbraco.presentation.webservices public class nodeSorter : UmbracoAuthorizedWebService { [WebMethod] - public SortNode GetNodes(int ParentId, string App) + public SortNode GetNodes(string ParentId, string App) { if (BasePage.ValidateUserContextID(BasePage.umbracoUserContextID)) { - var parent = new SortNode { Id = ParentId }; var nodes = new List(); - var entityService = base.ApplicationContext.Services.EntityService; - // Root nodes? - if (ParentId == -1) + // "hack for stylesheet" + if (App == "settings") { - if (App == "media") + var stylesheet = Services.FileService.GetStylesheetByName(ParentId.EnsureEndsWith(".css")); + if (stylesheet == null) throw new InvalidOperationException("No stylesheet found by name " + ParentId); + + var sort = 0; + foreach (var child in stylesheet.Properties) { - var rootMedia = entityService.GetRootEntities(UmbracoObjectTypes.Media); - nodes.AddRange(rootMedia.Select(media => new SortNode(media.Id, media.SortOrder, media.Name, media.CreateDate))); + nodes.Add(new SortNode(child.Name.GetHashCode(), sort, child.Name, DateTime.Now)); + sort++; } - else + + return new SortNode() { - var rootContent = entityService.GetRootEntities(UmbracoObjectTypes.Document); - nodes.AddRange(rootContent.Select(content => new SortNode(content.Id, content.SortOrder, content.Name, content.CreateDate))); - } + SortNodes = nodes.ToArray() + }; } else { - // "hack for stylesheet" - if (App == "settings") + var asInt = int.Parse(ParentId); + + var parent = new SortNode { Id = asInt }; + + var entityService = base.ApplicationContext.Services.EntityService; + + // Root nodes? + if (asInt == -1) { - var cmsNode = new cms.businesslogic.CMSNode(ParentId); - var styleSheet = new StyleSheet(cmsNode.Id); - nodes.AddRange(styleSheet.Properties.Select(child => new SortNode(child.Id, child.sortOrder, child.Text, child.CreateDateTime))); + if (App == "media") + { + var rootMedia = entityService.GetRootEntities(UmbracoObjectTypes.Media); + nodes.AddRange(rootMedia.Select(media => new SortNode(media.Id, media.SortOrder, media.Name, media.CreateDate))); + } + else + { + var rootContent = entityService.GetRootEntities(UmbracoObjectTypes.Document); + nodes.AddRange(rootContent.Select(content => new SortNode(content.Id, content.SortOrder, content.Name, content.CreateDate))); + } } else { - var children = entityService.GetChildren(ParentId); + var children = entityService.GetChildren(asInt); nodes.AddRange(children.Select(child => new SortNode(child.Id, child.SortOrder, child.Name, child.CreateDate))); } + + + parent.SortNodes = nodes.ToArray(); + + return parent; } - - parent.SortNodes = nodes.ToArray(); - - return parent; } throw new ArgumentException("User not logged in"); } - [WebMethod] public void UpdateSortOrder(int ParentId, string SortOrder) + { + UpdateSortOrder(ParentId.ToString(), SortOrder); + } + + [WebMethod] + public void UpdateSortOrder(string ParentId, string SortOrder) { if (AuthorizeRequest() == false) return; if (SortOrder.Trim().Length <= 0) return; @@ -92,13 +113,17 @@ namespace umbraco.presentation.webservices var ids = SortOrder.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries); if (isContent) - SortContent(ids, ParentId); - - if (isMedia) + { + SortContent(ids, int.Parse(ParentId)); + } + else if (isMedia) + { SortMedia(ids); - - if (isContent == false && isMedia == false) - SortStylesheetProperties(ids); + } + else + { + SortStylesheetProperties(ParentId, ids); + } } private void SortMedia(string[] ids) @@ -123,20 +148,27 @@ namespace umbraco.presentation.webservices } } - private void SortStylesheetProperties(string[] ids) + + private void SortStylesheetProperties(string stylesheetName, string[] names) { - try + var stylesheet = Services.FileService.GetStylesheetByName(stylesheetName.EnsureEndsWith(".css")); + if (stylesheet == null) throw new InvalidOperationException("No stylesheet found by name " + stylesheetName); + + var currProps = stylesheet.Properties.ToArray(); + //remove them all first + foreach (var prop in currProps) { - for (var i = 0; i < ids.Length; i++) - { - var id = int.Parse(ids[i]); - new cms.businesslogic.CMSNode(id).sortOrder = i; - } + stylesheet.RemoveProperty(prop.Name); } - catch (Exception ex) + + //re-add them in the right order + for (var i = 0; i < names.Length; i++) { - LogHelper.Error("Could not update stylesheet property sort order", ex); + var found = currProps.Single(x => x.Name == names[i]); + stylesheet.AddProperty(found); } + + Services.FileService.SaveStylesheet(stylesheet); } private void SortContent(string[] ids, int parentId) @@ -168,7 +200,7 @@ namespace umbraco.presentation.webservices LogHelper.Error("Could not update content sort order", ex); } } - + } [Serializable] diff --git a/src/UmbracoExamine/BaseUmbracoIndexer.cs b/src/UmbracoExamine/BaseUmbracoIndexer.cs index fef78cf811..ee77f5d05c 100644 --- a/src/UmbracoExamine/BaseUmbracoIndexer.cs +++ b/src/UmbracoExamine/BaseUmbracoIndexer.cs @@ -19,6 +19,7 @@ using UmbracoExamine.DataServices; using Examine; using System.IO; using System.Xml.Linq; +using Lucene.Net.Store; using UmbracoExamine.LocalStorage; namespace UmbracoExamine @@ -65,14 +66,14 @@ namespace UmbracoExamine /// Used for unit tests /// internal static bool? DisableInitializationCheck = null; - private readonly LocalTempStorageIndexer _localTempStorageHelper = new LocalTempStorageIndexer(); + private readonly LocalTempStorageIndexer _localTempStorageIndexer = new LocalTempStorageIndexer(); private BaseLuceneSearcher _internalTempStorageSearcher = null; #region Properties public bool UseTempStorage { - get { return _localTempStorageHelper.LuceneDirectory != null; } + get { return _localTempStorageIndexer.LuceneDirectory != null; } } public string TempStorageLocation @@ -80,7 +81,7 @@ namespace UmbracoExamine get { if (UseTempStorage == false) return string.Empty; - return _localTempStorageHelper.TempPath; + return _localTempStorageIndexer.TempPath; } } @@ -162,22 +163,32 @@ namespace UmbracoExamine if (config["useTempStorage"] != null) { - //Use the temp storage directory which will store the index in the local/codegen folder, this is useful - // for websites that are running from a remove file server and file IO latency becomes an issue - var attemptUseTempStorage = config["useTempStorage"].TryConvertTo(); - if (attemptUseTempStorage) + var fsDir = base.GetLuceneDirectory() as FSDirectory; + if (fsDir != null) { - - var indexSet = IndexSets.Instance.Sets[IndexSetName]; - var configuredPath = indexSet.IndexPath; + //Use the temp storage directory which will store the index in the local/codegen folder, this is useful + // for websites that are running from a remove file server and file IO latency becomes an issue + var attemptUseTempStorage = config["useTempStorage"].TryConvertTo(); + if (attemptUseTempStorage) + { - _localTempStorageHelper.Initialize(config, configuredPath, base.GetLuceneDirectory(), IndexingAnalyzer, attemptUseTempStorage.Result); + var indexSet = IndexSets.Instance.Sets[IndexSetName]; + var configuredPath = indexSet.IndexPath; + + _localTempStorageIndexer.Initialize(config, configuredPath, fsDir, IndexingAnalyzer, attemptUseTempStorage.Result); + } } + } } #endregion + /// + /// Used to aquire the internal searcher + /// + private readonly object _internalSearcherLocker = new object(); + protected override BaseSearchProvider InternalSearcher { get @@ -185,21 +196,29 @@ namespace UmbracoExamine //if temp local storage is configured use that, otherwise return the default if (UseTempStorage) { - //create one if one has not been created already - return _internalTempStorageSearcher - ?? (_internalTempStorageSearcher = new LuceneSearcher(_localTempStorageHelper.LuceneDirectory, IndexingAnalyzer)); + if (_internalTempStorageSearcher == null) + { + lock (_internalSearcherLocker) + { + if (_internalTempStorageSearcher == null) + { + _internalTempStorageSearcher = new LuceneSearcher(GetIndexWriter(), IndexingAnalyzer); + } + } + } + return _internalTempStorageSearcher; } return base.InternalSearcher; } } - + public override Lucene.Net.Store.Directory GetLuceneDirectory() { //if temp local storage is configured use that, otherwise return the default if (UseTempStorage) { - return _localTempStorageHelper.LuceneDirectory; + return _localTempStorageIndexer.LuceneDirectory; } return base.GetLuceneDirectory(); @@ -441,8 +460,19 @@ namespace UmbracoExamine if (xDoc != null) { var rootNode = xDoc.Root; - - AddNodesToIndex(rootNode.Elements(), type); + if (rootNode != null) + { + //the result will either be a single doc with an id as the root, or it will + // be multiple docs with a wrapper, we need to check for this + if (rootNode.HasAttributes) + { + AddNodesToIndex(new[] {rootNode}, type); + } + else + { + AddNodesToIndex(rootNode.Elements(), type); + } + } } } diff --git a/src/UmbracoExamine/Config/IndexSetExtensions.cs b/src/UmbracoExamine/Config/IndexSetExtensions.cs index ac432b16ec..1255f50a3c 100644 --- a/src/UmbracoExamine/Config/IndexSetExtensions.cs +++ b/src/UmbracoExamine/Config/IndexSetExtensions.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Text; using Examine; @@ -17,60 +16,7 @@ namespace UmbracoExamine.Config internal static IIndexCriteria ToIndexCriteria(this IndexSet set, IDataService svc, IEnumerable indexFieldPolicies) { - - var attributeFields = set.IndexAttributeFields.Cast().ToArray(); - var userFields = set.IndexUserFields.Cast().ToArray(); - var includeNodeTypes = set.IncludeNodeTypes.ToList().Select(x => x.Name).ToArray(); - var excludeNodeTypes = set.ExcludeNodeTypes.ToList().Select(x => x.Name).ToArray(); - var parentId = set.IndexParentId; - - //if there are no user fields defined, we'll populate them from the data source (include them all) - if (set.IndexUserFields.Count == 0) - { - //we need to add all user fields to the collection if it is empty (this is the default if none are specified) - var userProps = svc.ContentService.GetAllUserPropertyNames(); - var fields = new List(); - foreach (var u in userProps) - { - var field = new IndexField() { Name = u }; - var policy = indexFieldPolicies.FirstOrDefault(x => x.Name == u); - if (policy != null) - { - field.Type = policy.Type; - field.EnableSorting = policy.EnableSorting; - } - fields.Add(field); - } - userFields = fields.ToArray(); - } - - //if there are no attribute fields defined, we'll populate them from the data source (include them all) - if (set.IndexAttributeFields.Count == 0) - { - //we need to add all system fields to the collection if it is empty (this is the default if none are specified) - var sysProps = svc.ContentService.GetAllSystemPropertyNames(); - var fields = new List(); - foreach (var s in sysProps) - { - var field = new IndexField() { Name = s }; - var policy = indexFieldPolicies.FirstOrDefault(x => x.Name == s); - if (policy != null) - { - field.Type = policy.Type; - field.EnableSorting = policy.EnableSorting; - } - fields.Add(field); - } - attributeFields = fields.ToArray(); - } - - - return new IndexCriteria( - attributeFields, - userFields, - includeNodeTypes, - excludeNodeTypes, - parentId); + return new LazyIndexCriteria(set, svc, indexFieldPolicies); } /// diff --git a/src/UmbracoExamine/Config/LazyIndexCriteria.cs b/src/UmbracoExamine/Config/LazyIndexCriteria.cs new file mode 100644 index 0000000000..72ab3f31ba --- /dev/null +++ b/src/UmbracoExamine/Config/LazyIndexCriteria.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Examine; +using Examine.LuceneEngine.Config; +using UmbracoExamine.DataServices; + +namespace UmbracoExamine.Config +{ + internal class LazyIndexCriteria : IIndexCriteria + { + public LazyIndexCriteria( + IndexSet set, + IDataService svc, + IEnumerable indexFieldPolicies) + { + if (set == null) throw new ArgumentNullException("set"); + if (indexFieldPolicies == null) throw new ArgumentNullException("indexFieldPolicies"); + if (svc == null) throw new ArgumentNullException("svc"); + + _lazyCriteria = new Lazy(() => + { + var attributeFields = set.IndexAttributeFields.Cast().ToArray(); + var userFields = set.IndexUserFields.Cast().ToArray(); + var includeNodeTypes = set.IncludeNodeTypes.Cast().Select(x => x.Name).ToArray(); + var excludeNodeTypes = set.ExcludeNodeTypes.Cast().Select(x => x.Name).ToArray(); + var parentId = set.IndexParentId; + + //if there are no user fields defined, we'll populate them from the data source (include them all) + if (set.IndexUserFields.Count == 0) + { + //we need to add all user fields to the collection if it is empty (this is the default if none are specified) + var userProps = svc.ContentService.GetAllUserPropertyNames(); + var fields = new List(); + foreach (var u in userProps) + { + var field = new IndexField() { Name = u }; + var policy = indexFieldPolicies.FirstOrDefault(x => x.Name == u); + if (policy != null) + { + field.Type = policy.Type; + field.EnableSorting = policy.EnableSorting; + } + fields.Add(field); + } + userFields = fields.ToArray(); + } + + //if there are no attribute fields defined, we'll populate them from the data source (include them all) + if (set.IndexAttributeFields.Count == 0) + { + //we need to add all system fields to the collection if it is empty (this is the default if none are specified) + var sysProps = svc.ContentService.GetAllSystemPropertyNames(); + var fields = new List(); + foreach (var s in sysProps) + { + var field = new IndexField() { Name = s }; + var policy = indexFieldPolicies.FirstOrDefault(x => x.Name == s); + if (policy != null) + { + field.Type = policy.Type; + field.EnableSorting = policy.EnableSorting; + } + fields.Add(field); + } + attributeFields = fields.ToArray(); + } + + + return new IndexCriteria( + attributeFields, + userFields, + includeNodeTypes, + excludeNodeTypes, + parentId); + }); + } + + private readonly Lazy _lazyCriteria; + + public IEnumerable ExcludeNodeTypes + { + get { return _lazyCriteria.Value.ExcludeNodeTypes; } + } + + public IEnumerable IncludeNodeTypes + { + get { return _lazyCriteria.Value.IncludeNodeTypes; } + } + + public int? ParentNodeId + { + get { return _lazyCriteria.Value.ParentNodeId; } + } + + public IEnumerable StandardFields + { + get { return _lazyCriteria.Value.StandardFields; } + } + + public IEnumerable UserFields + { + get { return _lazyCriteria.Value.UserFields; } + } + } +} \ No newline at end of file diff --git a/src/UmbracoExamine/LocalStorage/LocalTempStorageDirectory.cs b/src/UmbracoExamine/LocalStorage/LocalTempStorageDirectory.cs index 4c133d5d41..b1dce7ef2d 100644 --- a/src/UmbracoExamine/LocalStorage/LocalTempStorageDirectory.cs +++ b/src/UmbracoExamine/LocalStorage/LocalTempStorageDirectory.cs @@ -1,32 +1,46 @@ -using System.Collections.Generic; +using System; using System.IO; -using System.Text; -using System.Threading.Tasks; +using System.Linq; using Lucene.Net.Store; +using Directory = Lucene.Net.Store.Directory; namespace UmbracoExamine.LocalStorage { /// /// Used to read data from local temp storage and write to both local storage and main storage - /// - public class LocalTempStorageDirectory : SimpleFSDirectory + /// + public class LocalTempStorageDirectory : Directory { - private readonly Lucene.Net.Store.Directory _realDirectory; - + private readonly FSDirectory _tempStorageDir; + private readonly FSDirectory _realDirectory; + private LockFactory _lockFactory; + public LocalTempStorageDirectory( DirectoryInfo tempStorageDir, - Lucene.Net.Store.Directory realDirectory) - : base(tempStorageDir) + FSDirectory realDirectory) { - _realDirectory = realDirectory; - Enabled = true; - } + if (tempStorageDir == null) throw new ArgumentNullException("tempStorageDir"); + if (realDirectory == null) throw new ArgumentNullException("realDirectory"); + _tempStorageDir = new SimpleFSDirectory(tempStorageDir); + _realDirectory = realDirectory; + _lockFactory = new MultiIndexLockFactory(_realDirectory, _tempStorageDir); + + Enabled = true; + + } + /// /// If initialization fails, it will be disabled and then this will just wrap the 'real directory' /// internal bool Enabled { get; set; } + [Obsolete("this is deprecated")] + public override string[] List() + { + return _realDirectory.List(); + } + public override string[] ListAll() { //always from the real dir @@ -52,18 +66,36 @@ namespace UmbracoExamine.LocalStorage { //always from the real dir _realDirectory.TouchFile(name); + + //perform on both dirs + if (Enabled) + { + _tempStorageDir.TouchFile(name); + } } /// Removes an existing file in the directory. public override void DeleteFile(string name) - { + { + _realDirectory.DeleteFile(name); + //perform on both dirs if (Enabled) { - base.DeleteFile(name); + _tempStorageDir.DeleteFile(name); + } + } + + [Obsolete("This is deprecated")] + public override void RenameFile(string @from, string to) + { + _realDirectory.RenameFile(@from, to); + + //perform on both dirs + if (Enabled) + { + _tempStorageDir.RenameFile(@from, to); } - - _realDirectory.DeleteFile(name); } /// Returns the length of a file in the directory. @@ -83,7 +115,7 @@ namespace UmbracoExamine.LocalStorage if (Enabled) { return new MultiIndexOutput( - base.CreateOutput(name), + _tempStorageDir.CreateOutput(name), _realDirectory.CreateOutput(name)); } @@ -98,17 +130,95 @@ namespace UmbracoExamine.LocalStorage if (Enabled) { //return the reader from the cache, not the real dir - return base.OpenInput(name); + return _tempStorageDir.OpenInput(name); } return _realDirectory.OpenInput(name); } + /// + /// Creates an IndexInput for the file with the given name. + /// + public override IndexInput OpenInput(string name, int bufferSize) + { + if (Enabled) + { + //return the reader from the cache, not the real dir + return _tempStorageDir.OpenInput(name, bufferSize); + } + return _realDirectory.OpenInput(name, bufferSize); + } + + /// + /// Ensure that any writes to this file are moved to + /// stable storage. Lucene uses this to properly commit + /// changes to the index, to prevent a machine/OS crash + /// from corrupting the index. + /// + public override void Sync(string name) + { + _realDirectory.Sync(name); + _tempStorageDir.Sync(name); + base.Sync(name); + } + + public override void Close() + { + if (Enabled) + { + _tempStorageDir.Close(); + } + _realDirectory.Close(); + } + + public override Lock MakeLock(string name) + { + return _lockFactory.MakeLock(name); + } + + /// + /// Return a string identifier that uniquely differentiates + /// this Directory instance from other Directory instances. + /// This ID should be the same if two Directory instances + /// (even in different JVMs and/or on different machines) + /// are considered "the same index". This is how locking + /// "scopes" to the right index. + /// + /// + public override string GetLockID() + { + return string.Concat(_realDirectory.GetLockID(), _tempStorageDir.GetLockID()); + } + + public override LockFactory GetLockFactory() + { + return _lockFactory; + //return _realDirectory.GetLockFactory(); + } + + public override void ClearLock(string name) + { + _lockFactory.ClearLock(name); + + //_realDirectory.ClearLock(name); + + //if (Enabled) + //{ + // _tempStorageDir.ClearLock(name); + //} + } + + public override void SetLockFactory(LockFactory lf) + { + _lockFactory = lf; + //_realDirectory.SetLockFactory(lf); + } + public override void Dispose() { if (Enabled) { - base.Dispose(); + _tempStorageDir.Dispose(); } _realDirectory.Dispose(); } diff --git a/src/UmbracoExamine/LocalStorage/LocalTempStorageDirectoryTracker.cs b/src/UmbracoExamine/LocalStorage/LocalTempStorageDirectoryTracker.cs index d3c94715b3..7cda2cee50 100644 --- a/src/UmbracoExamine/LocalStorage/LocalTempStorageDirectoryTracker.cs +++ b/src/UmbracoExamine/LocalStorage/LocalTempStorageDirectoryTracker.cs @@ -1,6 +1,6 @@ using System.Collections.Concurrent; using System.IO; -using Directory = Lucene.Net.Store.Directory; +using Lucene.Net.Store; namespace UmbracoExamine.LocalStorage { @@ -14,7 +14,7 @@ namespace UmbracoExamine.LocalStorage get { return Instance; } } - public LocalTempStorageDirectory GetDirectory(DirectoryInfo dir, Directory realDir, bool disable = false) + public LocalTempStorageDirectory GetDirectory(DirectoryInfo dir, FSDirectory realDir, bool disable = false) { var resolved = _directories.GetOrAdd(dir.FullName, s => new LocalTempStorageDirectory(dir, realDir)); diff --git a/src/UmbracoExamine/LocalStorage/LocalTempStorageIndexer.cs b/src/UmbracoExamine/LocalStorage/LocalTempStorageIndexer.cs index c4a7e170a8..dc48fdc917 100644 --- a/src/UmbracoExamine/LocalStorage/LocalTempStorageIndexer.cs +++ b/src/UmbracoExamine/LocalStorage/LocalTempStorageIndexer.cs @@ -2,6 +2,7 @@ using System.Collections.Specialized; using System.IO; using System.Linq; +using System.Threading; using System.Web; using Examine.LuceneEngine; using Lucene.Net.Analysis; @@ -14,6 +15,16 @@ using Directory = System.IO.Directory; namespace UmbracoExamine.LocalStorage { + internal enum InitializeDirectoryFlags + { + Success = 0, + SuccessNoIndexExists = 1, + + FailedCorrupt = 100, + FailedLocked = 101, + FailedFileSync = 102 + } + internal class LocalTempStorageIndexer { public Lucene.Net.Store.Directory LuceneDirectory { get; private set; } @@ -29,7 +40,7 @@ namespace UmbracoExamine.LocalStorage Snapshotter = new SnapshotDeletionPolicy(policy); } - public void Initialize(NameValueCollection config, string configuredPath, Lucene.Net.Store.Directory baseLuceneDirectory, Analyzer analyzer, LocalStorageType localStorageType) + public void Initialize(NameValueCollection config, string configuredPath, FSDirectory baseLuceneDirectory, Analyzer analyzer, LocalStorageType localStorageType) { var codegenPath = HttpRuntime.CodegenDir; @@ -45,23 +56,193 @@ namespace UmbracoExamine.LocalStorage new DirectoryInfo(TempPath), baseLuceneDirectory, //flag to disable the mirrored folder if not successful - success == false); + (int)success >= 100); + + //If the master index simply doesn't exist, we don't continue to try to open anything since there will + // actually be nothing there. + if (success == InitializeDirectoryFlags.SuccessNoIndexExists) + { + return; + } + + //Try to open the reader, this will fail if the index is corrupt and we'll need to handle that + var result = DelegateExtensions.RetryUntilSuccessOrMaxAttempts(i => + { + try + { + using (IndexReader.Open( + LuceneDirectory, + DeletePolicyTracker.Current.GetPolicy(LuceneDirectory), + true)) + { + } + + return Attempt.Succeed(true); + } + catch (Exception ex) + { + LogHelper.WarnWithException( + string.Format("Could not open an index reader, local temp storage index is empty or corrupt... retrying... {0}", configuredPath), + ex); + } + return Attempt.Fail(false); + }, 5, TimeSpan.FromSeconds(1)); + + if (result.Success == false) + { + LogHelper.Warn( + string.Format("Could not open an index reader, local temp storage index is empty or corrupt... attempting to clear index files in local temp storage, will operate from main storage only {0}", configuredPath)); + + ClearFilesInPath(TempPath); + + //create the custom lucene directory which will keep the main and temp FS's in sync + LuceneDirectory = LocalTempStorageDirectoryTracker.Current.GetDirectory( + new DirectoryInfo(TempPath), + baseLuceneDirectory, + //Disable mirrored index, we're kind of screwed here only use master index + true); + } + break; case LocalStorageType.LocalOnly: if (Directory.Exists(TempPath) == false) { Directory.CreateDirectory(TempPath); } - LuceneDirectory = DirectoryTracker.Current.GetDirectory(new DirectoryInfo(TempPath)); break; default: throw new ArgumentOutOfRangeException("localStorageType"); } - } - private bool InitializeLocalIndexAndDirectory(Lucene.Net.Store.Directory baseLuceneDirectory, Analyzer analyzer, string configuredPath) + private void ClearFilesInPath(string path) + { + if (Directory.Exists(path)) + { + foreach (var file in Directory.GetFiles(path)) + { + try + { + File.Delete(file); + } + catch (Exception exInner) + { + LogHelper.Error("Could not delete local temp storage index file", exInner); + } + } + } + } + + private bool ClearLuceneDirFiles(Lucene.Net.Store.Directory baseLuceneDirectory) + { + try + { + //unlock it! + IndexWriter.Unlock(baseLuceneDirectory); + + var fileLuceneDirectory = baseLuceneDirectory as FSDirectory; + if (fileLuceneDirectory != null) + { + foreach (var file in fileLuceneDirectory.ListAll()) + { + try + { + fileLuceneDirectory.DeleteFile(file); + } + catch (IOException) + { + if (file.InvariantEquals("write.lock")) + { + LogHelper.Warn("The lock file could not be deleted but should be removed when the writer is disposed"); + } + + } + } + return true; + } + return false; + } + catch (Exception ex) + { + LogHelper.Error("Could not clear corrupt index from main index folder, the index cannot be used", ex); + return false; + } + } + + private Attempt TryCreateWriter(Lucene.Net.Store.Directory baseLuceneDirectory, Analyzer analyzer) + { + try + { + var w = new IndexWriter( + //read from the underlying/default directory, not the temp codegen dir + baseLuceneDirectory, + analyzer, + Snapshotter, + IndexWriter.MaxFieldLength.UNLIMITED); + + //Done! + return Attempt.Succeed(w); + } + catch (Exception ex) + { + LogHelper.WarnWithException("Could not create index writer with snapshot policy for copying... retrying...", ex); + return Attempt.Fail(ex); + } + } + + /// + /// Attempts to create an index writer, it will retry on failure 5 times and on the last time will try to forcefully unlock the index files + /// + /// + /// + /// + private Attempt TryCreateWriterWithRetry(Lucene.Net.Store.Directory baseLuceneDirectory, Analyzer analyzer) + { + var maxTries = 5; + + var result = DelegateExtensions.RetryUntilSuccessOrMaxAttempts((currentTry) => + { + //last try... + if (currentTry == maxTries) + { + LogHelper.Info("Could not acquire index lock, attempting to force unlock it..."); + //unlock it! + IndexWriter.Unlock(baseLuceneDirectory); + } + + var writerAttempt = TryCreateWriter(baseLuceneDirectory, analyzer); + if (writerAttempt) return writerAttempt; + LogHelper.Info("Could not create writer on {0}, retrying ....", baseLuceneDirectory.ToString); + return Attempt.Fail(); + }, 5, TimeSpan.FromSeconds(1)); + + return result; + } + + private bool TryWaitForDirectoryUnlock(Lucene.Net.Store.Directory dir) + { + var maxTries = 5; + + var result = DelegateExtensions.RetryUntilSuccessOrMaxAttempts((currentTry) => + { + //last try... + if (currentTry == maxTries) + { + LogHelper.Info("Could not acquire directory lock, attempting to force unlock it..."); + //unlock it! + IndexWriter.Unlock(dir); + } + + if (IndexWriter.IsLocked(dir) == false) return Attempt.Succeed(true); + LogHelper.Info("Could not acquire directory lock for {0} writer, retrying ....", dir.ToString); + return Attempt.Fail(); + }, 5, TimeSpan.FromSeconds(1)); + + return result; + } + + private InitializeDirectoryFlags InitializeLocalIndexAndDirectory(Lucene.Net.Store.Directory baseLuceneDirectory, Analyzer analyzer, string configuredPath) { lock (_locker) { @@ -71,14 +252,50 @@ namespace UmbracoExamine.LocalStorage } //copy index if it exists, don't do anything if it's not there - if (IndexReader.IndexExists(baseLuceneDirectory) == false) return true; + if (IndexReader.IndexExists(baseLuceneDirectory) == false) return InitializeDirectoryFlags.SuccessNoIndexExists; - using (new IndexWriter( - //read from the underlying/default directory, not the temp codegen dir - baseLuceneDirectory, - analyzer, - Snapshotter, - IndexWriter.MaxFieldLength.UNLIMITED)) + var writerAttempt = TryCreateWriterWithRetry(baseLuceneDirectory, analyzer); + + if (writerAttempt.Success == false) + { + LogHelper.Error("Could not create index writer with snapshot policy for copying, the index cannot be used", writerAttempt.Exception); + return InitializeDirectoryFlags.FailedLocked; + } + + //Try to open the reader from the source, this will fail if the index is corrupt and we'll need to handle that + try + { + //NOTE: To date I've not seen this error occur + using (writerAttempt.Result.GetReader()) + { + } + } + catch (Exception ex) + { + writerAttempt.Result.Dispose(); + + LogHelper.Error( + string.Format("Could not open an index reader, {0} is empty or corrupt... attempting to clear index files in master folder", configuredPath), + ex); + + if (ClearLuceneDirFiles(baseLuceneDirectory) == false) + { + //hrm, not much we can do in this situation, but this shouldn't happen + LogHelper.Error("Could not open an index reader, index is corrupt.", ex); + return InitializeDirectoryFlags.FailedCorrupt; + } + + //the main index is now blank, we'll proceed as normal with a new empty index... + writerAttempt = TryCreateWriter(baseLuceneDirectory, analyzer); + if (writerAttempt.Success == false) + { + //ultra fail... + LogHelper.Error("Could not create index writer with snapshot policy for copying, the index cannot be used", writerAttempt.Exception); + return InitializeDirectoryFlags.FailedLocked; + } + } + + using (writerAttempt.Result) { try { @@ -102,72 +319,68 @@ namespace UmbracoExamine.LocalStorage .Select(x => x.Name) .Except(allSnapshotFiles); - //using (var tempDirectory = new SimpleFSDirectory(tempDir)) - //{ - //TODO: We're ignoring if it is locked right now, it shouldn't be unless for some strange reason the - // last process hasn't fully shut down, in that case we're not going to worry about it. - - //if (IndexWriter.IsLocked(tempDirectory) == false) - //{ - foreach (var file in toRemove) + using (var tempDirectory = new SimpleFSDirectory(tempDir)) { - try + if (TryWaitForDirectoryUnlock(tempDirectory)) { - File.Delete(Path.Combine(TempPath, file)); - } - catch (IOException ex) - { - LogHelper.WarnWithException("Could not delete non synced index file file, index sync will continue but old index files will remain - this shouldn't affect indexing/searching operations", ex); + foreach (var file in toRemove) + { + try + { + File.Delete(Path.Combine(TempPath, file)); + } + catch (IOException ex) + { + if (file.InvariantEquals("write.lock")) + { + //This might happen if the writer is open + LogHelper.Warn("The lock file could not be deleted but should be removed when the writer is disposed"); + } - //TODO: we're ignoring this, as old files shouldn't affect the index/search operations, lucene files are 'write once' - //quit here - //return false; + LogHelper.Debug("Could not delete non synced index file file, index sync will continue but old index files will remain - this shouldn't affect indexing/searching operations. {0}", () => ex.ToString()); + + } + } + } + else + { + //quit here, this shouldn't happen with all the checks above. + LogHelper.Warn("Cannot sync index files from main storage, the temp file index is currently locked"); + return InitializeDirectoryFlags.FailedLocked; + } + + + foreach (var fileName in allSnapshotFiles.Where(f => f.IsNullOrWhiteSpace() == false)) + { + var destination = Path.Combine(TempPath, Path.GetFileName(fileName)); + + //don't copy if it's already there, lucene is 'write once' so this file is meant to be there already + if (File.Exists(destination)) continue; + + try + { + File.Copy( + Path.Combine(basePath, "Index", fileName), + destination); + } + catch (IOException ex) + { + LogHelper.Error("Could not copy index file, could not sync from main storage", ex); + + //quit here + return InitializeDirectoryFlags.FailedFileSync; + } } } - //} - //else - //{ - // LogHelper.Warn("Cannot sync index files from main storage, the index is currently locked"); - // //quit here - // return false; - //} - - foreach (var fileName in allSnapshotFiles.Where(f => f.IsNullOrWhiteSpace() == false)) - { - var destination = Path.Combine(TempPath, Path.GetFileName(fileName)); - - //don't copy if it's already there, lucene is 'write once' so this file is meant to be there already - if (File.Exists(destination)) continue; - - try - { - File.Copy( - Path.Combine(basePath, "Index", fileName), - destination); - } - catch (IOException ex) - { - LogHelper.Error("Could not copy index file, could not sync from main storage", ex); - - //quit here - return false; - } - } - - //} - - - } finally { Snapshotter.Release(); } - - } - return true; + LogHelper.Info("Successfully sync'd main index to local temp storage for index: {0}", () => configuredPath); + return InitializeDirectoryFlags.Success; } } } diff --git a/src/UmbracoExamine/LocalStorage/MultiIndexLock.cs b/src/UmbracoExamine/LocalStorage/MultiIndexLock.cs new file mode 100644 index 0000000000..7c309075f8 --- /dev/null +++ b/src/UmbracoExamine/LocalStorage/MultiIndexLock.cs @@ -0,0 +1,54 @@ +using Lucene.Net.Store; + +namespace UmbracoExamine.LocalStorage +{ + /// + /// Lock that wraps multiple locks + /// + public class MultiIndexLock : Lock + { + private readonly Lock _dirMaster; + private readonly Lock _dirChild; + + public MultiIndexLock(Lock dirMaster, Lock dirChild) + { + _dirMaster = dirMaster; + _dirChild = dirChild; + } + + /// + /// Attempts to obtain exclusive access and immediately return + /// upon success or failure. + /// + /// + /// true iff exclusive access is obtained + /// + public override bool Obtain() + { + return _dirMaster.Obtain() && _dirChild.Obtain(); + } + + public override bool Obtain(long lockWaitTimeout) + { + return _dirMaster.Obtain(lockWaitTimeout) && _dirChild.Obtain(lockWaitTimeout); + } + + /// + /// Releases exclusive access. + /// + public override void Release() + { + _dirMaster.Release(); + _dirChild.Release(); + } + + /// + /// Returns true if the resource is currently locked. Note that one must + /// still call before using the resource. + /// + public override bool IsLocked() + { + return _dirMaster.IsLocked() || _dirChild.IsLocked(); + } + } +} \ No newline at end of file diff --git a/src/UmbracoExamine/LocalStorage/MultiIndexLockFactory.cs b/src/UmbracoExamine/LocalStorage/MultiIndexLockFactory.cs new file mode 100644 index 0000000000..8d08041021 --- /dev/null +++ b/src/UmbracoExamine/LocalStorage/MultiIndexLockFactory.cs @@ -0,0 +1,33 @@ +using System; +using Lucene.Net.Store; + +namespace UmbracoExamine.LocalStorage +{ + /// + /// Lock factory that wraps multiple factories + /// + public class MultiIndexLockFactory : LockFactory + { + private readonly FSDirectory _master; + private readonly FSDirectory _child; + + public MultiIndexLockFactory(FSDirectory master, FSDirectory child) + { + if (master == null) throw new ArgumentNullException("master"); + if (child == null) throw new ArgumentNullException("child"); + _master = master; + _child = child; + } + + public override Lock MakeLock(string lockName) + { + return new MultiIndexLock(_master.GetLockFactory().MakeLock(lockName), _child.GetLockFactory().MakeLock(lockName)); + } + + public override void ClearLock(string lockName) + { + _master.GetLockFactory().ClearLock(lockName); + _child.GetLockFactory().ClearLock(lockName); + } + } +} \ No newline at end of file diff --git a/src/UmbracoExamine/UmbracoContentIndexer.cs b/src/UmbracoExamine/UmbracoContentIndexer.cs index 6209631cc4..5f168f3237 100644 --- a/src/UmbracoExamine/UmbracoContentIndexer.cs +++ b/src/UmbracoExamine/UmbracoContentIndexer.cs @@ -117,6 +117,7 @@ namespace UmbracoExamine = new List { new StaticField("id", FieldIndexTypes.NOT_ANALYZED, false, string.Empty), + new StaticField("key", FieldIndexTypes.NOT_ANALYZED, false, string.Empty), new StaticField( "version", FieldIndexTypes.NOT_ANALYZED, false, string.Empty), new StaticField( "parentID", FieldIndexTypes.NOT_ANALYZED, false, string.Empty), new StaticField( "level", FieldIndexTypes.NOT_ANALYZED, true, "NUMBER"), @@ -320,7 +321,7 @@ namespace UmbracoExamine protected override void PerformIndexAll(string type) { - const int pageSize = 5000; + const int pageSize = 1000; var pageIndex = 0; switch (type) @@ -342,7 +343,7 @@ namespace UmbracoExamine do { - int total; + long total; var descendants = ContentService.GetPagedDescendants(contentParentId, pageIndex, pageSize, out total); //if specific types are declared we need to post filter them @@ -358,6 +359,8 @@ namespace UmbracoExamine AddNodesToIndex(GetSerializedContent(content), type); pageIndex++; + + } while (content.Length == pageSize); } @@ -373,7 +376,7 @@ namespace UmbracoExamine do { - int total; + long total; var descendants = MediaService.GetPagedDescendants(mediaParentId, pageIndex, pageSize, out total); //if specific types are declared we need to post filter them diff --git a/src/UmbracoExamine/UmbracoExamine.csproj b/src/UmbracoExamine/UmbracoExamine.csproj index 92e31579e0..885ea127a3 100644 --- a/src/UmbracoExamine/UmbracoExamine.csproj +++ b/src/UmbracoExamine/UmbracoExamine.csproj @@ -82,9 +82,9 @@ ..\Solution Items\TheFARM-Public.snk - - False - ..\packages\Examine.0.1.63.0\lib\Examine.dll + + ..\packages\Examine.0.1.68.0\lib\Examine.dll + True False @@ -111,6 +111,7 @@ + @@ -127,6 +128,8 @@ + + diff --git a/src/UmbracoExamine/UmbracoExamine.csproj.DotSettings b/src/UmbracoExamine/UmbracoExamine.csproj.DotSettings new file mode 100644 index 0000000000..662f95686e --- /dev/null +++ b/src/UmbracoExamine/UmbracoExamine.csproj.DotSettings @@ -0,0 +1,2 @@ + + CSharp50 \ No newline at end of file diff --git a/src/UmbracoExamine/UmbracoExamineSearcher.cs b/src/UmbracoExamine/UmbracoExamineSearcher.cs index f193939416..35804a35fe 100644 --- a/src/UmbracoExamine/UmbracoExamineSearcher.cs +++ b/src/UmbracoExamine/UmbracoExamineSearcher.cs @@ -168,7 +168,7 @@ namespace UmbracoExamine var directory = GetLuceneDirectory(); return IndexReader.Open( directory, - DeletePolicyTracker.Current.GetPolicy(directory), + //DeletePolicyTracker.Current.GetPolicy(directory), true); } @@ -187,9 +187,17 @@ namespace UmbracoExamine switch (_localStorageType) { case LocalStorageType.Sync: - _localTempDirectory = LocalTempStorageDirectoryTracker.Current.GetDirectory( - new DirectoryInfo(_localTempPath), - base.GetLuceneDirectory()); + var fsDir = base.GetLuceneDirectory() as FSDirectory; + if (fsDir != null) + { + _localTempDirectory = LocalTempStorageDirectoryTracker.Current.GetDirectory( + new DirectoryInfo(_localTempPath), + fsDir); + } + else + { + return base.GetLuceneDirectory(); + } break; case LocalStorageType.LocalOnly: _localTempDirectory = DirectoryTracker.Current.GetDirectory(new DirectoryInfo(_localTempPath)); diff --git a/src/UmbracoExamine/UmbracoMemberIndexer.cs b/src/UmbracoExamine/UmbracoMemberIndexer.cs index be347e5843..a65f42d562 100644 --- a/src/UmbracoExamine/UmbracoMemberIndexer.cs +++ b/src/UmbracoExamine/UmbracoMemberIndexer.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Linq; using System.Xml.Linq; using Examine.LuceneEngine.Config; @@ -19,8 +20,9 @@ using IMediaService = Umbraco.Core.Services.IMediaService; namespace UmbracoExamine { + /// - /// + /// Custom indexer for members /// public class UmbracoMemberIndexer : UmbracoContentIndexer { @@ -75,20 +77,33 @@ namespace UmbracoExamine /// protected override IIndexCriteria GetIndexerData(IndexSet indexSet) { + var indexerData = base.GetIndexerData(indexSet); + if (CanInitialize()) { - var searchableEmail = indexSet.IndexUserFields["_searchEmail"]; - if (searchableEmail == null) + //If the fields are missing a custom _searchEmail, then add it + + if (indexerData.UserFields.Any(x => x.Name == "_searchEmail") == false) { - indexSet.IndexUserFields.Add(new IndexField + var field = new IndexField {Name = "_searchEmail"}; + var policy = IndexFieldPolicies.FirstOrDefault(x => x.Name == "_searchEmail"); + if (policy != null) { - Name = "_searchEmail" - }); + field.Type = policy.Type; + field.EnableSorting = policy.EnableSorting; + } + + return new IndexCriteria( + indexerData.StandardFields, + indexerData.UserFields.Concat(new[] {field}), + indexerData.IncludeNodeTypes, + indexerData.ExcludeNodeTypes, + indexerData.ParentNodeId + ); } - return indexSet.ToIndexCriteria(DataService, IndexFieldPolicies); } - return base.GetIndexerData(indexSet); + return indexerData; } /// @@ -111,12 +126,11 @@ namespace UmbracoExamine //This only supports members if (SupportedTypes.Contains(type) == false) return; - - //Re-index all members in batches of 5000 - int memberCount = 0; + const int pageSize = 1000; var pageIndex = 0; - var serializer = new EntityXmlSerializer(); + + IMember[] members; if (IndexerData.IncludeNodeTypes.Any()) { @@ -125,16 +139,13 @@ namespace UmbracoExamine { do { - int total; - var members = _memberService.GetAll(pageIndex, pageSize, out total, "LoginName", Direction.Ascending, nodeType); - memberCount = 0; - foreach (var member in members) - { - AddNodesToIndex(new[] { serializer.Serialize(DataTypeService, member) }, type); - memberCount++; - } + long total; + members = _memberService.GetAll(pageIndex, pageSize, out total, "LoginName", Direction.Ascending, nodeType).ToArray(); + + AddNodesToIndex(GetSerializedMembers(members), type); + pageIndex++; - } while (memberCount == pageSize); + } while (members.Length == pageSize); } } else @@ -143,18 +154,21 @@ namespace UmbracoExamine do { int total; - var members = _memberService.GetAll(pageIndex, pageSize, out total); - memberCount = 0; - foreach (var member in members) - { - AddNodesToIndex(new[] { serializer.Serialize(DataTypeService, member) }, type); - memberCount++; - } + members = _memberService.GetAll(pageIndex, pageSize, out total).ToArray(); + + AddNodesToIndex(GetSerializedMembers(members), type); + pageIndex++; - } while (memberCount == pageSize); + } while (members.Length == pageSize); } } + private IEnumerable GetSerializedMembers(IEnumerable members) + { + var serializer = new EntityXmlSerializer(); + return members.Select(member => serializer.Serialize(_dataTypeService, member)); + } + protected override XDocument GetXDocument(string xPath, string type) { throw new NotSupportedException(); @@ -191,6 +205,9 @@ namespace UmbracoExamine if (e.Fields.ContainsKey("_searchEmail") == false) e.Fields.Add("_searchEmail", e.Node.Attribute("email").Value.Replace(".", " ").Replace("@", " ")); } + + if (e.Fields.ContainsKey(IconFieldName) == false) + e.Fields.Add(IconFieldName, (string)e.Node.Attribute("icon")); } } } diff --git a/src/UmbracoExamine/app.config b/src/UmbracoExamine/app.config index 0f2278a158..a0794caa99 100644 --- a/src/UmbracoExamine/app.config +++ b/src/UmbracoExamine/app.config @@ -4,11 +4,11 @@ - + - + diff --git a/src/UmbracoExamine/packages.config b/src/UmbracoExamine/packages.config index efa216d153..722e13f2d5 100644 --- a/src/UmbracoExamine/packages.config +++ b/src/UmbracoExamine/packages.config @@ -1,6 +1,6 @@  - + \ No newline at end of file diff --git a/src/umbraco.MacroEngines/umbraco.MacroEngines.csproj.DotSettings b/src/umbraco.MacroEngines/umbraco.MacroEngines.csproj.DotSettings new file mode 100644 index 0000000000..662f95686e --- /dev/null +++ b/src/umbraco.MacroEngines/umbraco.MacroEngines.csproj.DotSettings @@ -0,0 +1,2 @@ + + CSharp50 \ No newline at end of file diff --git a/src/umbraco.businesslogic/BasePages/BasePage.cs b/src/umbraco.businesslogic/BasePages/BasePage.cs index f2f560c179..d78bc7d2dd 100644 --- a/src/umbraco.businesslogic/BasePages/BasePage.cs +++ b/src/umbraco.businesslogic/BasePages/BasePage.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Web.Mvc; using System.Web.Routing; using System.Web.Security; +using System.Web.UI; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.IO; @@ -181,7 +182,14 @@ namespace umbraco.BasePages /// public static int GetUserId() { - var identity = HttpContext.Current.GetCurrentIdentity(true); + var identity = HttpContext.Current.GetCurrentIdentity( + //DO NOT AUTO-AUTH UNLESS THE CURRENT HANDLER IS WEBFORMS! + // Without this check, anything that is using this legacy API, like ui.Text will + // automatically log the back office user in even if it is a front-end request (if there is + // a back office user logged in. This can cause problems becaues the identity is changing mid + // request. For example: http://issues.umbraco.org/issue/U4-4010 + HttpContext.Current.CurrentHandler is Page); + if (identity == null) return -1; return Convert.ToInt32(identity.Id); @@ -205,7 +213,14 @@ namespace umbraco.BasePages /// public static bool ValidateCurrentUser() { - var identity = HttpContext.Current.GetCurrentIdentity(true); + var identity = HttpContext.Current.GetCurrentIdentity( + //DO NOT AUTO-AUTH UNLESS THE CURRENT HANDLER IS WEBFORMS! + // Without this check, anything that is using this legacy API, like ui.Text will + // automatically log the back office user in even if it is a front-end request (if there is + // a back office user logged in. This can cause problems becaues the identity is changing mid + // request. For example: http://issues.umbraco.org/issue/U4-4010 + HttpContext.Current.CurrentHandler is Page); + if (identity != null) { return true; @@ -232,7 +247,14 @@ namespace umbraco.BasePages { get { - var identity = HttpContext.Current.GetCurrentIdentity(true); + var identity = HttpContext.Current.GetCurrentIdentity( + //DO NOT AUTO-AUTH UNLESS THE CURRENT HANDLER IS WEBFORMS! + // Without this check, anything that is using this legacy API, like ui.Text will + // automatically log the back office user in even if it is a front-end request (if there is + // a back office user logged in. This can cause problems becaues the identity is changing mid + // request. For example: http://issues.umbraco.org/issue/U4-4010 + HttpContext.Current.CurrentHandler is Page); + return identity == null ? "" : identity.SessionId; } set diff --git a/src/umbraco.businesslogic/GlobalSettings.cs b/src/umbraco.businesslogic/GlobalSettings.cs index 5084be1d0b..3d3721f783 100644 --- a/src/umbraco.businesslogic/GlobalSettings.cs +++ b/src/umbraco.businesslogic/GlobalSettings.cs @@ -133,6 +133,7 @@ namespace umbraco /// Gets a value indicating whether the current version of umbraco is configured. /// /// true if configured; otherwise, false. + [Obsolete("Do not use this, it is no longer in use and will be removed from the codebase in future versions")] public static bool Configured { get { return Umbraco.Core.Configuration.GlobalSettings.Configured; } @@ -316,7 +317,7 @@ namespace umbraco [Obsolete("Use Umbraco.Core.Configuration.UmbracoVersion.Current instead", false)] public static string VersionComment { - get { return Umbraco.Core.Configuration.UmbracoVersion.CurrentComment; } + get { return UmbracoVersion.CurrentComment; } } diff --git a/src/umbraco.businesslogic/Properties/AssemblyInfo.cs b/src/umbraco.businesslogic/Properties/AssemblyInfo.cs index 73bfa02ee2..ca3992844b 100644 --- a/src/umbraco.businesslogic/Properties/AssemblyInfo.cs +++ b/src/umbraco.businesslogic/Properties/AssemblyInfo.cs @@ -18,4 +18,5 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("Umbraco.Tests")] [assembly: InternalsVisibleTo("umbraco")] +[assembly: InternalsVisibleTo("cms")] [assembly: InternalsVisibleTo("Umbraco.LegacyTests")] \ No newline at end of file diff --git a/src/umbraco.businesslogic/StateHelper.cs b/src/umbraco.businesslogic/StateHelper.cs index e84d3aa129..61df4318ab 100644 --- a/src/umbraco.businesslogic/StateHelper.cs +++ b/src/umbraco.businesslogic/StateHelper.cs @@ -3,6 +3,7 @@ using System.Reflection; using System.Web; using System.Web.UI; using Umbraco.Core; +using Umbraco.Core.Configuration; namespace umbraco.BusinessLogic { @@ -350,7 +351,7 @@ namespace umbraco.BusinessLogic * that actually make sense? shouldn't some cookie have _no_ expires? */ static readonly Cookie _preview = new Cookie(Constants.Web.PreviewCookieName, TimeSpan.Zero); // was "PreviewSet" - static readonly Cookie _userContext = new Cookie(Constants.Web.AuthCookieName, 30d); // was "UserContext" + static readonly Cookie _userContext = new Cookie(UmbracoConfig.For.UmbracoSettings().Security.AuthCookieName, 30d); // was "UserContext" static readonly Cookie _member = new Cookie("UMB_MEMBER", 30d); // was "umbracoMember" public static Cookie Preview { get { return _preview; } } diff --git a/src/umbraco.businesslogic/User.cs b/src/umbraco.businesslogic/User.cs index e3349f6af6..475a2614dd 100644 --- a/src/umbraco.businesslogic/User.cs +++ b/src/umbraco.businesslogic/User.cs @@ -23,7 +23,7 @@ namespace umbraco.BusinessLogic [Obsolete("Use the UserService instead")] public class User { - private IUser _user; + internal IUser UserEntity; private int? _lazyId; private bool? _defaultToLiveEditing; @@ -38,7 +38,7 @@ namespace umbraco.BusinessLogic internal User(IUser user) { - _user = user; + UserEntity = user; } /// @@ -81,8 +81,8 @@ namespace umbraco.BusinessLogic private void SetupUser(int ID) { - _user = ApplicationContext.Current.Services.UserService.GetUserById(ID); - if (_user == null) + UserEntity = ApplicationContext.Current.Services.UserService.GetUserById(ID); + if (UserEntity == null) { throw new ArgumentException("No User exists with ID " + ID); } @@ -95,7 +95,7 @@ namespace umbraco.BusinessLogic { if (_lazyId.HasValue) SetupUser(_lazyId.Value); - ApplicationContext.Current.Services.UserService.Save(_user); + ApplicationContext.Current.Services.UserService.Save(UserEntity); OnSaving(EventArgs.Empty); } @@ -109,11 +109,11 @@ namespace umbraco.BusinessLogic get { if (_lazyId.HasValue) SetupUser(_lazyId.Value); - return _user.Name; + return UserEntity.Name; } set { - _user.Name = value; + UserEntity.Name = value; } } @@ -127,11 +127,11 @@ namespace umbraco.BusinessLogic get { if (_lazyId.HasValue) SetupUser(_lazyId.Value); - return _user.Email; + return UserEntity.Email; } set { - _user.Email = value; + UserEntity.Email = value; } } @@ -144,11 +144,11 @@ namespace umbraco.BusinessLogic get { if (_lazyId.HasValue) SetupUser(_lazyId.Value); - return _user.Language; + return UserEntity.Language; } set { - _user.Language = value; + UserEntity.Language = value; } } @@ -164,7 +164,7 @@ namespace umbraco.BusinessLogic } set { - _user.RawPasswordValue = value; + UserEntity.RawPasswordValue = value; } } @@ -175,7 +175,7 @@ namespace umbraco.BusinessLogic public string GetPassword() { if (_lazyId.HasValue) SetupUser(_lazyId.Value); - return _user.RawPasswordValue; + return UserEntity.RawPasswordValue; } /// @@ -233,7 +233,7 @@ namespace umbraco.BusinessLogic var allApps = Application.getAll(); var apps = new List(); - var sections = _user.AllowedSections; + var sections = UserEntity.AllowedSections; foreach (var s in sections) { @@ -254,14 +254,14 @@ namespace umbraco.BusinessLogic get { if (_lazyId.HasValue) SetupUser(_lazyId.Value); - return _user.Username; + return UserEntity.Username; } set { if (EnsureUniqueLoginName(value, this) == false) throw new Exception(String.Format("A user with the login '{0}' already exists", value)); - _user.Username = value; + UserEntity.Username = value; } } @@ -327,11 +327,11 @@ namespace umbraco.BusinessLogic get { if (_lazyId.HasValue) SetupUser(_lazyId.Value); - return new UserType(_user.UserType); + return new UserType(UserEntity.UserType); } set { - _user.UserType = value.UserTypeItem; + UserEntity.UserType = value.UserTypeItem; } } @@ -576,7 +576,7 @@ namespace umbraco.BusinessLogic OnDeleting(EventArgs.Empty); - ApplicationContext.Current.Services.UserService.Delete(_user, true); + ApplicationContext.Current.Services.UserService.Delete(UserEntity, true); FlushFromCache(); } @@ -589,7 +589,7 @@ namespace umbraco.BusinessLogic OnDisabling(EventArgs.Empty); //delete without the true overload will perform the disable operation - ApplicationContext.Current.Services.UserService.Delete(_user); + ApplicationContext.Current.Services.UserService.Delete(UserEntity); } /// @@ -603,7 +603,7 @@ namespace umbraco.BusinessLogic var defaultPermissions = UserType.DefaultPermissions; - var cachedPermissions = ApplicationContext.Current.Services.UserService.GetPermissions(_user) + var cachedPermissions = ApplicationContext.Current.Services.UserService.GetPermissions(UserEntity) .ToArray(); // NH 4.7.1 changing default permission behavior to default to User Type permissions IF no specific permissions has been @@ -668,7 +668,7 @@ namespace umbraco.BusinessLogic { if (_lazyId.HasValue) SetupUser(_lazyId.Value); - var notifications = ApplicationContext.Current.Services.NotificationService.GetUserNotifications(_user); + var notifications = ApplicationContext.Current.Services.NotificationService.GetUserNotifications(UserEntity); foreach (var n in notifications.OrderBy(x => x.EntityId)) { int nodeId = n.EntityId; @@ -688,7 +688,7 @@ namespace umbraco.BusinessLogic /// The id. public int Id { - get { return _user.Id; } + get { return UserEntity.Id; } } /// @@ -697,9 +697,9 @@ namespace umbraco.BusinessLogic public void ClearApplications() { if (_lazyId.HasValue) SetupUser(_lazyId.Value); - foreach (var s in _user.AllowedSections.ToArray()) + foreach (var s in UserEntity.AllowedSections.ToArray()) { - _user.RemoveAllowedSection(s); + UserEntity.RemoveAllowedSection(s); } } @@ -711,13 +711,13 @@ namespace umbraco.BusinessLogic { if (_lazyId.HasValue) SetupUser(_lazyId.Value); - foreach (var s in _user.AllowedSections.ToArray()) + foreach (var s in UserEntity.AllowedSections.ToArray()) { - _user.RemoveAllowedSection(s); + UserEntity.RemoveAllowedSection(s); } //For backwards compatibility this requires an implicit save - ApplicationContext.Current.Services.UserService.Save(_user); + ApplicationContext.Current.Services.UserService.Save(UserEntity); } /// @@ -727,7 +727,7 @@ namespace umbraco.BusinessLogic public void AddApplication(string appAlias) { if (_lazyId.HasValue) SetupUser(_lazyId.Value); - _user.AddAllowedSection(appAlias); + UserEntity.AddAllowedSection(appAlias); } /// @@ -739,10 +739,10 @@ namespace umbraco.BusinessLogic { if (_lazyId.HasValue) SetupUser(_lazyId.Value); - _user.AddAllowedSection(AppAlias); + UserEntity.AddAllowedSection(AppAlias); //For backwards compatibility this requires an implicit save - ApplicationContext.Current.Services.UserService.Save(_user); + ApplicationContext.Current.Services.UserService.Save(UserEntity); } /// @@ -754,11 +754,11 @@ namespace umbraco.BusinessLogic get { if (_lazyId.HasValue) SetupUser(_lazyId.Value); - return _user.IsLockedOut; + return UserEntity.IsLockedOut; } set { - _user.IsLockedOut = value; + UserEntity.IsLockedOut = value; } } @@ -771,11 +771,11 @@ namespace umbraco.BusinessLogic get { if (_lazyId.HasValue) SetupUser(_lazyId.Value); - return _user.IsApproved == false; + return UserEntity.IsApproved == false; } set { - _user.IsApproved = value == false; + UserEntity.IsApproved = value == false; } } @@ -789,11 +789,11 @@ namespace umbraco.BusinessLogic get { if (_lazyId.HasValue) SetupUser(_lazyId.Value); - return _user.StartContentId; + return UserEntity.StartContentId; } set { - _user.StartContentId = value; + UserEntity.StartContentId = value; } } @@ -806,11 +806,11 @@ namespace umbraco.BusinessLogic get { if (_lazyId.HasValue) SetupUser(_lazyId.Value); - return _user.StartMediaId; + return UserEntity.StartMediaId; } set { - _user.StartMediaId = value; + UserEntity.StartMediaId = value; } } diff --git a/src/umbraco.businesslogic/app.config b/src/umbraco.businesslogic/app.config index 0f2278a158..a0794caa99 100644 --- a/src/umbraco.businesslogic/app.config +++ b/src/umbraco.businesslogic/app.config @@ -4,11 +4,11 @@ - + - + diff --git a/src/umbraco.businesslogic/packages.config b/src/umbraco.businesslogic/packages.config index f07166e262..4a0a1d249b 100644 --- a/src/umbraco.businesslogic/packages.config +++ b/src/umbraco.businesslogic/packages.config @@ -1,10 +1,10 @@  - - - + + + - + \ No newline at end of file diff --git a/src/umbraco.businesslogic/ui.cs b/src/umbraco.businesslogic/ui.cs index 5f2579d9ec..b289b6d6c5 100644 --- a/src/umbraco.businesslogic/ui.cs +++ b/src/umbraco.businesslogic/ui.cs @@ -28,7 +28,7 @@ namespace umbraco /// The ui class handles the multilingual text in the umbraco back-end. /// Provides access to language settings and language files used in the umbraco back-end. /// - [Obsolete("Use the ILocalizedTextService instead which is the ApplicationContext.Services")] + [Obsolete("Use the ILocalizedTextService instead which is on the ApplicationContext.Services.TextService")] public class ui { private static readonly string UmbracoDefaultUiLanguage = GlobalSettings.DefaultUILanguage; diff --git a/src/umbraco.businesslogic/umbraco.businesslogic.csproj b/src/umbraco.businesslogic/umbraco.businesslogic.csproj index 982d0f59c3..82c78df751 100644 --- a/src/umbraco.businesslogic/umbraco.businesslogic.csproj +++ b/src/umbraco.businesslogic/umbraco.businesslogic.csproj @@ -107,11 +107,12 @@ - False ..\packages\AutoMapper.3.0.0\lib\net40\AutoMapper.dll + True - + ..\packages\AutoMapper.3.0.0\lib\net40\AutoMapper.Net4.dll + True False @@ -139,29 +140,29 @@ 3.5 - + + ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.Helpers.dll True - ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.Helpers.dll - + + ..\packages\Microsoft.AspNet.Mvc.5.2.3\lib\net45\System.Web.Mvc.dll True - ..\packages\Microsoft.AspNet.Mvc.4.0.40804.0\lib\net40\System.Web.Mvc.dll - + + ..\packages\Microsoft.AspNet.Razor.3.2.3\lib\net45\System.Web.Razor.dll True - ..\packages\Microsoft.AspNet.Razor.2.0.30506.0\lib\net40\System.Web.Razor.dll - + + ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.dll True - ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.WebPages.dll - + + ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Deployment.dll True - ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.WebPages.Deployment.dll - + + ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Razor.dll True - ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.WebPages.Razor.dll System.XML diff --git a/src/umbraco.businesslogic/umbraco.businesslogic.csproj.DotSettings b/src/umbraco.businesslogic/umbraco.businesslogic.csproj.DotSettings new file mode 100644 index 0000000000..662f95686e --- /dev/null +++ b/src/umbraco.businesslogic/umbraco.businesslogic.csproj.DotSettings @@ -0,0 +1,2 @@ + + CSharp50 \ No newline at end of file diff --git a/src/umbraco.cms/Actions/Action.cs b/src/umbraco.cms/Actions/Action.cs index d3bea5941e..df6c257396 100644 --- a/src/umbraco.cms/Actions/Action.cs +++ b/src/umbraco.cms/Actions/Action.cs @@ -13,6 +13,7 @@ using umbraco.cms.businesslogic.workflow; using umbraco.interfaces; using System.Text.RegularExpressions; using System.Linq; +using Umbraco.Core.IO; using TypeFinder = Umbraco.Core.TypeFinder; namespace umbraco.BusinessLogic.Actions diff --git a/src/umbraco.cms/app.config b/src/umbraco.cms/app.config index 0f2278a158..a0794caa99 100644 --- a/src/umbraco.cms/app.config +++ b/src/umbraco.cms/app.config @@ -4,11 +4,11 @@ - + - + diff --git a/src/umbraco.cms/businesslogic/CMSNode.cs b/src/umbraco.cms/businesslogic/CMSNode.cs index 4a2ae44de1..8a34d1735a 100644 --- a/src/umbraco.cms/businesslogic/CMSNode.cs +++ b/src/umbraco.cms/businesslogic/CMSNode.cs @@ -1087,6 +1087,7 @@ order by level,sortOrder"; { // attributes x.Attributes.Append(xmlHelper.addAttribute(xd, "id", this.Id.ToString())); + x.Attributes.Append(xmlHelper.addAttribute(xd, "key", this.UniqueId.ToString())); if (this.Level > 1) x.Attributes.Append(xmlHelper.addAttribute(xd, "parentID", this.Parent.Id.ToString())); else diff --git a/src/umbraco.cms/businesslogic/Content.cs b/src/umbraco.cms/businesslogic/Content.cs index e088b02b9e..0b1e030c7c 100644 --- a/src/umbraco.cms/businesslogic/Content.cs +++ b/src/umbraco.cms/businesslogic/Content.cs @@ -268,6 +268,7 @@ namespace umbraco.cms.businesslogic + x.Attributes.Append(XmlHelper.AddAttribute(xd, "key", this.UniqueId.ToString())); /// /// Deletes the current Content object, must be overridden in the child class. diff --git a/src/umbraco.cms/businesslogic/ContentType.cs b/src/umbraco.cms/businesslogic/ContentType.cs index 0a54e9bf39..75ebe4c8d3 100644 --- a/src/umbraco.cms/businesslogic/ContentType.cs +++ b/src/umbraco.cms/businesslogic/ContentType.cs @@ -585,6 +585,8 @@ namespace umbraco.cms.businesslogic { if (m_masterContentTypes == null) { + var ct = ApplicationContext.Current.Services.ContentTypeService.GetContentType(Id); + m_masterContentTypes = ct.CompositionPropertyGroups.Select(x => x.Id).ToList(); m_masterContentTypes = ContentTypeItem == null ? new List() : ContentTypeItem.CompositionIds().ToList(); @@ -933,7 +935,7 @@ namespace umbraco.cms.businesslogic public int AddVirtualTab(string Caption) { // The method is synchronized - PropertyTypeGroup ptg = new PropertyTypeGroup(0, Id, Caption); + PropertyTypeGroup ptg = new PropertyTypeGroup(Id, Caption); ptg.Save(); // Remove from cache @@ -1229,24 +1231,16 @@ namespace umbraco.cms.businesslogic [Obsolete("Use PropertyTypeGroup methods instead", false)] private void InitializeVirtualTabs() { - // While we are initialising, we should not use the class-scoped list, as it may be used by other threads - var temporaryList = new List(); - foreach (PropertyTypeGroup ptg in PropertyTypeGroups.Where(x => x.ParentId == 0 && x.ContentTypeId == this.Id)) - temporaryList.Add(new Tab(ptg.Id, ptg.Name, ptg.SortOrder, this)); + // somewhat fixing... this whole class should be removed anyways + var ct = ContentTypeItem ?? ApplicationContext.Current.Services.ContentTypeService.GetContentType(Id); - // Master Content Type - if (MasterContentTypes.Count > 0) - { - foreach (var mct in MasterContentTypes) - temporaryList.AddRange(GetContentType(mct).getVirtualTabs.ToList()); - } - - - // sort all tabs - temporaryList.Sort((a, b) => a.SortOrder.CompareTo(b.SortOrder)); - - // now that we aren't going to modify the list, we can set it to the class-scoped variable. - _virtualTabs = temporaryList.DistinctBy(x => x.Id).ToList(); + var tmp1 = ct.PropertyGroups + .Select(x => (TabI) new Tab(x.Id, x.Name, x.SortOrder, this)) + .Union(ct.ContentTypeComposition.SelectMany(x => GetContentType(x.Id).getVirtualTabs)) + .OrderBy(x => x.SortOrder) + .DistinctBy(x => x.Id) + .ToList(); + _virtualTabs = tmp1; } private static void PopulateMasterContentTypes(PropertyType pt, int docTypeId) @@ -1411,22 +1405,13 @@ namespace umbraco.cms.businesslogic // regardless of the PropertyTypes belonging to the current ContentType. public List GetAllPropertyTypes() { - var db = ApplicationContext.Current.DatabaseContext.Database; - var propertyTypeDtos = db.Fetch("WHERE propertyTypeGroupId = @Id", new { Id = _id }); - var tmp = propertyTypeDtos - .Select(propertyTypeDto => PropertyType.GetPropertyType(propertyTypeDto.Id)) - .ToList(); - - var propertyTypeGroupDtos = db.Fetch("WHERE parentGroupId = @Id", new { Id = _id }); - foreach (var propertyTypeGroupDto in propertyTypeGroupDtos) - { - var inheritedPropertyTypeDtos = db.Fetch("WHERE propertyTypeGroupId = @Id", new { Id = propertyTypeGroupDto.Id }); - tmp.AddRange(inheritedPropertyTypeDtos - .Select(propertyTypeDto => PropertyType.GetPropertyType(propertyTypeDto.Id)) - .ToList()); - } - - return tmp; + // somewhat fixing... this whole class should be removed anyways + var ct = _contenttype.ContentTypeItem ?? ApplicationContext.Current.Services.ContentTypeService.GetContentType(_contenttype.Id); + return ct.CompositionPropertyTypes + .OrderBy(x => x.SortOrder) + .Select(x => x.Id) + .Select(PropertyType.GetPropertyType) + .ToList(); } /// diff --git a/src/umbraco.cms/businesslogic/Dictionary.cs b/src/umbraco.cms/businesslogic/Dictionary.cs index 0d34da4615..8e76122175 100644 --- a/src/umbraco.cms/businesslogic/Dictionary.cs +++ b/src/umbraco.cms/businesslogic/Dictionary.cs @@ -93,8 +93,8 @@ namespace umbraco.cms.businesslogic [Obsolete("This is no longer used and will be removed from the codebase in future versions")] public bool IsTopMostItem() - { - return _dictionaryItem.ParentId == new Guid(Constants.Conventions.Localization.DictionaryItemRootId); + { + return _dictionaryItem.ParentId.HasValue == false; } /// @@ -105,9 +105,9 @@ namespace umbraco.cms.businesslogic get { //EnsureCache(); - if (_parent == null) + if (_parent == null && _dictionaryItem.ParentId.HasValue) { - var p = ApplicationContext.Current.Services.LocalizationService.GetDictionaryItemById(_dictionaryItem.ParentId); + var p = ApplicationContext.Current.Services.LocalizationService.GetDictionaryItemById(_dictionaryItem.ParentId.Value); if (p == null) { @@ -325,7 +325,9 @@ namespace umbraco.cms.businesslogic if (!hasKey(key)) { var item = ApplicationContext.Current.Services.LocalizationService.CreateDictionaryItemWithIdentity( - key, parentId, defaultValue); + key, + parentId == TopLevelParent ? (Guid?)null : parentId, + defaultValue); return item.Id; } diff --git a/src/umbraco.cms/businesslogic/Permission.cs b/src/umbraco.cms/businesslogic/Permission.cs index ba0ad56b14..1a598452fe 100644 --- a/src/umbraco.cms/businesslogic/Permission.cs +++ b/src/umbraco.cms/businesslogic/Permission.cs @@ -2,7 +2,6 @@ using System; using System.Collections; using System.Collections.Specialized; using System.Data; -using System.Globalization; using System.Linq; using System.Runtime.CompilerServices; using Umbraco.Core; @@ -10,13 +9,12 @@ using Umbraco.Core.Events; using umbraco.DataLayer; using umbraco.cms.businesslogic; using System.Collections.Generic; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Services; using DeleteEventArgs = umbraco.cms.businesslogic.DeleteEventArgs; namespace umbraco.BusinessLogic { - - //TODO: Wrap this in the new services/repo layer! - /// /// Summary description for Permission. /// @@ -31,39 +29,17 @@ namespace umbraco.BusinessLogic /// Private constructor, this class cannot be directly instantiated /// private Permission() { } - - private static ISqlHelper SqlHelper - { - get { return Application.SqlHelper; } - } - - + public static void MakeNew(User User, CMSNode Node, char PermissionKey) { MakeNew(User, Node, PermissionKey, true); } - [MethodImpl(MethodImplOptions.Synchronized)] - internal static void MakeNew(User user, IEnumerable nodes, char permissionKey, bool raiseEvents) + private static void MakeNew(User user, IEnumerable nodes, char permissionKey, bool raiseEvents) { var asArray = nodes.ToArray(); - foreach (var node in asArray) - { - var parameters = new[] { SqlHelper.CreateParameter("@userId", user.Id), - SqlHelper.CreateParameter("@nodeId", node.Id), - SqlHelper.CreateParameter("@permission", permissionKey.ToString()) }; - // Method is synchronized so exists remains consistent (avoiding race condition) - var exists = SqlHelper.ExecuteScalar( - "SELECT COUNT(userId) FROM umbracoUser2nodePermission WHERE userId = @userId AND nodeId = @nodeId AND permission = @permission", - parameters) > 0; - - if (exists) return; - - SqlHelper.ExecuteNonQuery( - "INSERT INTO umbracoUser2nodePermission (userId, nodeId, permission) VALUES (@userId, @nodeId, @permission)", - parameters); - } + ApplicationContext.Current.Services.UserService.AssignUserPermission(user.Id, permissionKey, asArray.Select(x => x.Id).ToArray()); if (raiseEvents) { @@ -83,20 +59,17 @@ namespace umbraco.BusinessLogic /// public static IEnumerable GetUserPermissions(User user) { - var items = new List(); - using (IRecordsReader dr = SqlHelper.ExecuteReader("select * from umbracoUser2NodePermission where userId = @userId order by nodeId", SqlHelper.CreateParameter("@userId", user.Id))) - { - while (dr.Read()) + var permissions = ApplicationContext.Current.Services.UserService.GetPermissions(user.UserEntity); + + return permissions.SelectMany( + entityPermission => entityPermission.AssignedPermissions, + (entityPermission, assignedPermission) => new Permission { - items.Add(new Permission() - { - NodeId = dr.GetInt("nodeId"), - PermissionId = Convert.ToChar(dr.GetString("permission")), - UserId = dr.GetInt("userId") - }); - } - } - return items; + NodeId = entityPermission.EntityId, + PermissionId = assignedPermission[0], + UserId = entityPermission.UserId + }); + } /// @@ -106,20 +79,19 @@ namespace umbraco.BusinessLogic /// public static IEnumerable GetNodePermissions(CMSNode node) { - var items = new List(); - using (IRecordsReader dr = SqlHelper.ExecuteReader("select * from umbracoUser2NodePermission where nodeId = @nodeId order by nodeId", SqlHelper.CreateParameter("@nodeId", node.Id))) - { - while (dr.Read()) + var content = ApplicationContext.Current.Services.ContentService.GetById(node.Id); + if (content == null) return Enumerable.Empty(); + + var permissions = ApplicationContext.Current.Services.ContentService.GetPermissionsForEntity(content); + + return permissions.SelectMany( + entityPermission => entityPermission.AssignedPermissions, + (entityPermission, assignedPermission) => new Permission { - items.Add(new Permission() - { - NodeId = dr.GetInt("nodeId"), - PermissionId = Convert.ToChar(dr.GetString("permission")), - UserId = dr.GetInt("userId") - }); - } - } - return items; + NodeId = entityPermission.EntityId, + PermissionId = assignedPermission[0], + UserId = entityPermission.UserId + }); } /// @@ -134,10 +106,7 @@ namespace umbraco.BusinessLogic internal static void DeletePermissions(User user, CMSNode node, bool raiseEvents) { - // delete all settings on the node for this user - SqlHelper.ExecuteNonQuery("delete from umbracoUser2NodePermission where userId = @userId and nodeId = @nodeId", - SqlHelper.CreateParameter("@userId", user.Id), SqlHelper.CreateParameter("@nodeId", node.Id)); - + ApplicationContext.Current.Services.UserService.RemoveUserPermissions(user.Id, node.Id); if (raiseEvents) { OnDeleted(new UserPermission(user, node, null), new DeleteEventArgs()); @@ -150,19 +119,14 @@ namespace umbraco.BusinessLogic /// public static void DeletePermissions(User user) { - // delete all settings on the node for this user - SqlHelper.ExecuteNonQuery("delete from umbracoUser2NodePermission where userId = @userId", - SqlHelper.CreateParameter("@userId", user.Id)); + ApplicationContext.Current.Services.UserService.RemoveUserPermissions(user.Id); OnDeleted(new UserPermission(user, Enumerable.Empty(), null), new DeleteEventArgs()); } public static void DeletePermissions(int iUserID, int[] iNodeIDs) { - var sql = "DELETE FROM umbracoUser2NodePermission WHERE nodeID IN ({0}) AND userID=@userID"; - var nodeIDs = string.Join(",", Array.ConvertAll(iNodeIDs, Converter)); - sql = string.Format(sql, nodeIDs); - SqlHelper.ExecuteNonQuery(sql, new[] { SqlHelper.CreateParameter("@userID", iUserID) }); + ApplicationContext.Current.Services.UserService.RemoveUserPermissions(iUserID, iNodeIDs); OnDeleted(new UserPermission(iUserID, iNodeIDs), new DeleteEventArgs()); } @@ -170,10 +134,6 @@ namespace umbraco.BusinessLogic { DeletePermissions(iUserID, new[] { iNodeID }); } - private static string Converter(int from) - { - return from.ToString(CultureInfo.InvariantCulture); - } /// /// delete all permissions for this node @@ -181,14 +141,11 @@ namespace umbraco.BusinessLogic /// public static void DeletePermissions(CMSNode node) { - SqlHelper.ExecuteNonQuery( - "delete from umbracoUser2NodePermission where nodeId = @nodeId", - SqlHelper.CreateParameter("@nodeId", node.Id)); - + ApplicationContext.Current.Services.ContentService.RemoveContentPermissions(node.Id); + OnDeleted(new UserPermission(null, node, null), new DeleteEventArgs()); } - [MethodImpl(MethodImplOptions.Synchronized)] public static void UpdateCruds(User user, CMSNode node, string permissions) { ApplicationContext.Current.Services.UserService.ReplaceUserPermissions( diff --git a/src/umbraco.cms/businesslogic/propertytype/PropertyTypeGroup.cs b/src/umbraco.cms/businesslogic/propertytype/PropertyTypeGroup.cs index 6367dcbf68..a92e18800a 100644 --- a/src/umbraco.cms/businesslogic/propertytype/PropertyTypeGroup.cs +++ b/src/umbraco.cms/businesslogic/propertytype/PropertyTypeGroup.cs @@ -9,7 +9,6 @@ namespace umbraco.cms.businesslogic.propertytype public class PropertyTypeGroup { public int Id { get; set; } - public int ParentId { get; set; } public int ContentTypeId { get; set; } public string Name { get; set; } public int SortOrder { get; set; } @@ -18,17 +17,15 @@ namespace umbraco.cms.businesslogic.propertytype { } - public PropertyTypeGroup(int parentId, int contentTypeId, string name, int sortOrder) + public PropertyTypeGroup(int contentTypeId, string name, int sortOrder) { - ParentId = parentId; ContentTypeId = contentTypeId; Name = name; SortOrder = sortOrder; } - public PropertyTypeGroup(int parentId, int contentTypeId, string name) + public PropertyTypeGroup(int contentTypeId, string name) { - ParentId = parentId; ContentTypeId = contentTypeId; Name = name; SortOrder = -1; // we set this to -1 so in the save method we can get the current highest sortorder in case it's not sat after init (ie. if you want to force a sortOrder) @@ -44,19 +41,14 @@ namespace umbraco.cms.businesslogic.propertytype return PropertyType.GetPropertyTypesByGroup(Id); } - //TODO: Verify support for master doctypes / mixins! + // note: this is used to delete all groups that inherit from a group, when the group is deleted, + // see the Delete method in this class - but it is all done in an obsolete way which does not + // take compositions in account + we delete the group but do not re-allocate the properties, etc. + // ALL THIS should be either removed, or refactored to use the new APIs - so... returning nothing + // from now on, which is just another way of being broken. public IEnumerable GetPropertyTypeGroups() { - var ptgs = new List(); - using (var dr = SqlHelper.ExecuteReader(@"SELECT id FROM cmsPropertyTypeGroup WHERE parentGroupId = @id", SqlHelper.CreateParameter("@id", Id))) - { - while (dr.Read()) - { - ptgs.Add(GetPropertyTypeGroup(dr.GetInt("id"))); - } - } - - return ptgs; + return Enumerable.Empty(); } public void Save() @@ -67,7 +59,6 @@ namespace umbraco.cms.businesslogic.propertytype @"UPDATE cmsPropertyTypeGroup SET - parentGroupId = @parentGroupId, contenttypeNodeId = @contentTypeId, sortOrder = @sortOrder, text = @name @@ -75,7 +66,6 @@ namespace umbraco.cms.businesslogic.propertytype id = @id ", SqlHelper.CreateParameter("@id", Id), - SqlHelper.CreateParameter("@parentGroupId", ConvertParentId(ParentId)), SqlHelper.CreateParameter("@contentTypeId", ContentTypeId), SqlHelper.CreateParameter("@sortOrder", SortOrder), SqlHelper.CreateParameter("@name", Name) @@ -84,18 +74,17 @@ namespace umbraco.cms.businesslogic.propertytype else { if (SortOrder == -1) - SortOrder = SqlHelper.ExecuteScalar("select count(*) from cmsPropertyTypeGroup where COALESCE(parentGroupId, 0) = 0 and contenttypeNodeId = @nodeId", + SortOrder = SqlHelper.ExecuteScalar("select count(*) from cmsPropertyTypeGroup where contenttypeNodeId = @nodeId", SqlHelper.CreateParameter("@nodeId", ContentTypeId)) + 1; SqlHelper.ExecuteNonQuery( @" INSERT INTO cmsPropertyTypeGroup - (parentGroupId, contenttypeNodeId, sortOrder, text) + (contenttypeNodeId, sortOrder, text) VALUES - (@parentGroupId, @contentTypeId, @sortOrder, @name) + (@contentTypeId, @sortOrder, @name) ", - SqlHelper.CreateParameter("@parentGroupId", ConvertParentId(ParentId)), SqlHelper.CreateParameter("@contentTypeId", ContentTypeId), SqlHelper.CreateParameter("@sortOrder", SortOrder), SqlHelper.CreateParameter("@name", Name) @@ -122,12 +111,10 @@ namespace umbraco.cms.businesslogic.propertytype internal void Load() { - using (var dr = SqlHelper.ExecuteReader(@" SELECT parentGroupId, contenttypeNodeId, sortOrder, text FROM cmsPropertyTypeGroup WHERE id = @id", SqlHelper.CreateParameter("@id", Id))) + using (var dr = SqlHelper.ExecuteReader(@" SELECT contenttypeNodeId, sortOrder, text FROM cmsPropertyTypeGroup WHERE id = @id", SqlHelper.CreateParameter("@id", Id))) { if (dr.Read()) { - // if no parent, the value should just be null. The GetInt helper method returns -1 if value is null so we need to check - ParentId = dr.GetInt("parentGroupId") != -1 ? dr.GetInt("parentGroupId") : 0; SortOrder = dr.GetInt("sortOrder"); ContentTypeId = dr.GetInt("contenttypeNodeId"); Name = dr.GetString("text"); @@ -156,14 +143,6 @@ namespace umbraco.cms.businesslogic.propertytype return ptgs; } - private object ConvertParentId(int parentId) - { - if (parentId == 0) - return DBNull.Value; - - return parentId; - } - /// /// Gets the SQL helper. /// diff --git a/src/umbraco.cms/businesslogic/template/Template.cs b/src/umbraco.cms/businesslogic/template/Template.cs index 4193a56ddc..bfae620daa 100644 --- a/src/umbraco.cms/businesslogic/template/Template.cs +++ b/src/umbraco.cms/businesslogic/template/Template.cs @@ -256,6 +256,7 @@ namespace umbraco.cms.businesslogic.template foreach (var t in GetAllAsList().FindAll(t => t.MasterTemplate == Id)) { t.MasterTemplate = 0; + t.Save(); } } @@ -263,6 +264,9 @@ namespace umbraco.cms.businesslogic.template // remove from documents Document.RemoveTemplateFromDocument(this.Id); + + //save it + Save(); } public void RemoveFromDocumentTypes() diff --git a/src/umbraco.cms/businesslogic/translation/Translation.cs b/src/umbraco.cms/businesslogic/translation/Translation.cs index 351c0ac6b0..13dfe060d9 100644 --- a/src/umbraco.cms/businesslogic/translation/Translation.cs +++ b/src/umbraco.cms/businesslogic/translation/Translation.cs @@ -7,6 +7,7 @@ using umbraco.BusinessLogic; using umbraco.cms.businesslogic.language; using umbraco.cms.businesslogic.task; using umbraco.cms.businesslogic.web; +using Umbraco.Core; using Umbraco.Core.IO; namespace umbraco.cms.businesslogic.translation @@ -17,13 +18,15 @@ namespace umbraco.cms.businesslogic.translation public static void MakeNew(CMSNode Node, User User, User Translator, Language Language, string Comment, bool IncludeSubpages, bool SendEmail) { + // Get translation taskType for obsolete task constructor + var taskType = ApplicationContext.Current.Services.TaskService.GetTaskTypeByAlias("toTranslate"); + // Create pending task - Task t = new Task(); + Task t = new Task(new Umbraco.Core.Models.Task(taskType)); t.Comment = Comment; t.Node = Node; t.ParentUser = User; t.User = Translator; - t.Type = new TaskType("toTranslate"); t.Save(); // Add log entry diff --git a/src/umbraco.cms/businesslogic/web/Access.cs b/src/umbraco.cms/businesslogic/web/Access.cs index 8271fa581f..3d4470d23c 100644 --- a/src/umbraco.cms/businesslogic/web/Access.cs +++ b/src/umbraco.cms/businesslogic/web/Access.cs @@ -71,12 +71,12 @@ namespace umbraco.cms.businesslogic.web if (e.Cancel) return; - var entry = ApplicationContext.Current.Services.PublicAccessService.AddOrUpdateRule( + var entry = ApplicationContext.Current.Services.PublicAccessService.AddRule( doc.ContentEntity, Constants.Conventions.PublicAccess.MemberRoleRuleType, role); - if (entry == null) + if (entry.Success == false && entry.Result.Entity == null) { throw new Exception("Document is not protected!"); } @@ -95,12 +95,14 @@ namespace umbraco.cms.businesslogic.web if (content == null) throw new Exception("No content found with document id " + DocumentId); - var entry = ApplicationContext.Current.Services.PublicAccessService.AddOrUpdateRule( - content, - Constants.Conventions.PublicAccess.MemberGroupIdRuleType, - MemberGroupId.ToString(CultureInfo.InvariantCulture)); - - Save(); + if (ApplicationContext.Current.Services.PublicAccessService.AddRule( + content, + Constants.Conventions.PublicAccess.MemberGroupIdRuleType, + MemberGroupId.ToString(CultureInfo.InvariantCulture))) + { + Save(); + } + } [Obsolete("This method is no longer supported. Use the ASP.NET MemberShip methods instead", true)] @@ -111,12 +113,14 @@ namespace umbraco.cms.businesslogic.web if (content == null) throw new Exception("No content found with document id " + DocumentId); - ApplicationContext.Current.Services.PublicAccessService.AddOrUpdateRule( - content, - Constants.Conventions.PublicAccess.MemberIdRuleType, - MemberId.ToString(CultureInfo.InvariantCulture)); - - Save(); + if (ApplicationContext.Current.Services.PublicAccessService.AddRule( + content, + Constants.Conventions.PublicAccess.MemberIdRuleType, + MemberId.ToString(CultureInfo.InvariantCulture))) + { + Save(); + } + } public static void AddMembershipUserToDocument(int documentId, string membershipUserName) @@ -128,19 +132,22 @@ namespace umbraco.cms.businesslogic.web if (e.Cancel) return; - var entry = ApplicationContext.Current.Services.PublicAccessService.AddOrUpdateRule( + var entry = ApplicationContext.Current.Services.PublicAccessService.AddRule( doc.ContentEntity, Constants.Conventions.PublicAccess.MemberUsernameRuleType, membershipUserName); - if (entry == null) + if (entry.Success == false && entry.Result.Entity == null) { throw new Exception("Document is not protected!"); } - Save(); - - new Access().FireAfterAddMembershipUserToDocument(doc, membershipUserName, e); + if (entry) + { + Save(); + new Access().FireAfterAddMembershipUserToDocument(doc, membershipUserName, e); + } + } [Obsolete("This method is no longer supported. Use the ASP.NET MemberShip methods instead", true)] @@ -148,16 +155,20 @@ namespace umbraco.cms.businesslogic.web { var doc = new Document(DocumentId); - var entry = ApplicationContext.Current.Services.PublicAccessService.AddOrUpdateRule( + var entry = ApplicationContext.Current.Services.PublicAccessService.AddRule( doc.ContentEntity, Constants.Conventions.PublicAccess.MemberGroupIdRuleType, MemberGroupId.ToString(CultureInfo.InvariantCulture)); - if (entry == null) + if (entry.Success == false && entry.Result.Entity == null) { throw new Exception("Document is not protected!"); } - Save(); + + if (entry) + { + Save(); + } } public static void RemoveMembershipRoleFromDocument(int documentId, string role) @@ -168,14 +179,15 @@ namespace umbraco.cms.businesslogic.web if (e.Cancel) return; - ApplicationContext.Current.Services.PublicAccessService.RemoveRule( + if (ApplicationContext.Current.Services.PublicAccessService.RemoveRule( doc.ContentEntity, Constants.Conventions.PublicAccess.MemberRoleRuleType, - role); - - Save(); - - new Access().FireAfterRemoveMemberShipRoleFromDocument(doc, role, e); + role)) + { + Save(); + new Access().FireAfterRemoveMemberShipRoleFromDocument(doc, role, e); + }; + } public static bool RenameMemberShipRole(string oldRolename, string newRolename) @@ -201,7 +213,7 @@ namespace umbraco.cms.businesslogic.web var noAccessContent = ApplicationContext.Current.Services.ContentService.GetById(ErrorDocumentId); if (noAccessContent == null) throw new NullReferenceException("No content item found with id " + ErrorDocumentId); - var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(doc.ContentEntity); + var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(doc.ContentEntity.Id.ToString()); if (entry != null) { if (Simple) @@ -222,11 +234,12 @@ namespace umbraco.cms.businesslogic.web new List()); } - ApplicationContext.Current.Services.PublicAccessService.Save(entry); - - Save(); - - new Access().FireAfterAddProtection(new Document(DocumentId), e); + if (ApplicationContext.Current.Services.PublicAccessService.Save(entry)) + { + Save(); + new Access().FireAfterAddProtection(new Document(DocumentId), e); + } + } public static void RemoveProtection(int DocumentId) @@ -407,6 +420,12 @@ namespace umbraco.cms.businesslogic.web return ApplicationContext.Current.Services.PublicAccessService.IsProtected(Path.EnsureEndsWith("," + DocumentId)); } + //return the protection status of this exact document - not based on inheritance + public static bool IsProtected(int DocumentId) + { + return ApplicationContext.Current.Services.PublicAccessService.IsProtected(DocumentId.ToString()); + } + public static int GetErrorPage(string Path) { var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(Path); diff --git a/src/umbraco.cms/businesslogic/web/Document.cs b/src/umbraco.cms/businesslogic/web/Document.cs index ea98caa818..7942b43132 100644 --- a/src/umbraco.cms/businesslogic/web/Document.cs +++ b/src/umbraco.cms/businesslogic/web/Document.cs @@ -17,6 +17,7 @@ using umbraco.BusinessLogic; using umbraco.BusinessLogic.Actions; using umbraco.cms.helpers; using umbraco.DataLayer; +using Umbraco.Core.Events; using Umbraco.Core.Strings; namespace umbraco.cms.businesslogic.web @@ -917,6 +918,7 @@ namespace umbraco.cms.businesslogic.web return descendants.Select(x => new Document(x.Id, true)); } + x.Attributes.Append(addAttribute(xd, "key", UniqueId.ToString())); public override List GetNodesForPreview(bool childrenOnly) { diff --git a/src/umbraco.cms/businesslogic/web/Domain.cs b/src/umbraco.cms/businesslogic/web/Domain.cs index 370d75b4db..d47766e36e 100644 --- a/src/umbraco.cms/businesslogic/web/Domain.cs +++ b/src/umbraco.cms/businesslogic/web/Domain.cs @@ -64,22 +64,23 @@ namespace umbraco.cms.businesslogic.web public Language Language { - get { return new Language(DomainEntity.Language); } - set { DomainEntity.Language = value.LanguageEntity; } + get + { + if (DomainEntity.LanguageId.HasValue == false) return null; + var lang = ApplicationContext.Current.Services.LocalizationService.GetLanguageById(DomainEntity.LanguageId.Value); + if (lang == null) throw new InvalidOperationException("No language exists with id " + DomainEntity.LanguageId.Value); + return new Language(lang); + } + set + { + DomainEntity.LanguageId = value.LanguageEntity.Id; + } } public int RootNodeId { - get { return DomainEntity.RootContent.Id; } - set - { - var content = ApplicationContext.Current.Services.ContentService.GetById(value); - if (content == null) - { - throw new NullReferenceException("No content found with id " + value); - } - DomainEntity.RootContent = content; - } + get { return DomainEntity.RootContentId ?? -1; } + set { DomainEntity.RootContentId = value; } } public int Id @@ -106,9 +107,10 @@ namespace umbraco.cms.businesslogic.web if (!e.Cancel) { - ApplicationContext.Current.Services.DomainService.Save(DomainEntity); - - FireAfterSave(e); + if (ApplicationContext.Current.Services.DomainService.Save(DomainEntity)) + { + FireAfterSave(e); + } } } @@ -135,7 +137,7 @@ namespace umbraco.cms.businesslogic.web public static int GetRootFromDomain(string DomainName) { var found = ApplicationContext.Current.Services.DomainService.GetByName(DomainName); - return found == null ? -1 : found.RootContent.Id; + return found == null ? -1 : found.RootContentId ?? -1; } public static Domain[] GetDomainsById(int nodeId) @@ -159,21 +161,18 @@ namespace umbraco.cms.businesslogic.web public static void MakeNew(string DomainName, int RootNodeId, int LanguageId) { - var content = ApplicationContext.Current.Services.ContentService.GetById(RootNodeId); - if (content == null) throw new NullReferenceException("No content exists with id " + RootNodeId); - var lang = ApplicationContext.Current.Services.LocalizationService.GetLanguageById(LanguageId); - if (lang == null) throw new NullReferenceException("No language exists with id " + LanguageId); - var domain = new UmbracoDomain(DomainName) { - RootContent = content, - Language = lang + RootContentId = RootNodeId, + LanguageId = LanguageId }; - ApplicationContext.Current.Services.DomainService.Save(domain); - - var e = new NewEventArgs(); - var legacyModel = new Domain(domain); - legacyModel.OnNew(e); + if (ApplicationContext.Current.Services.DomainService.Save(domain)) + { + var e = new NewEventArgs(); + var legacyModel = new Domain(domain); + legacyModel.OnNew(e); + } + } #endregion diff --git a/src/umbraco.cms/businesslogic/web/StyleSheet.cs b/src/umbraco.cms/businesslogic/web/StyleSheet.cs index 5d4291bf3d..b95e81bc67 100644 --- a/src/umbraco.cms/businesslogic/web/StyleSheet.cs +++ b/src/umbraco.cms/businesslogic/web/StyleSheet.cs @@ -225,7 +225,7 @@ namespace umbraco.cms.businesslogic.web public static StyleSheet GetByName(string name) { - var found = ApplicationContext.Current.Services.FileService.GetStylesheetByName(name.EnsureEndsWith(".css")); + var found = ApplicationContext.Current.Services.FileService.GetStylesheetByName(name); if (found == null) return null; return new StyleSheet(found); } diff --git a/src/umbraco.cms/packages.config b/src/umbraco.cms/packages.config index e0c255613c..05f1a708b6 100644 --- a/src/umbraco.cms/packages.config +++ b/src/umbraco.cms/packages.config @@ -1,7 +1,7 @@  - - - - + + + + \ No newline at end of file diff --git a/src/umbraco.cms/umbraco.cms.csproj b/src/umbraco.cms/umbraco.cms.csproj index 9dd7d4cacf..ae2d1bd5b3 100644 --- a/src/umbraco.cms/umbraco.cms.csproj +++ b/src/umbraco.cms/umbraco.cms.csproj @@ -106,13 +106,13 @@ false - - False - ..\packages\ClientDependency.1.8.3.1\lib\net45\ClientDependency.Core.dll + + ..\packages\ClientDependency.1.8.4\lib\net45\ClientDependency.Core.dll + True - + False - ..\packages\HtmlAgilityPack.1.4.6\lib\Net40\HtmlAgilityPack.dll + ..\packages\HtmlAgilityPack.1.4.9\lib\Net45\HtmlAgilityPack.dll False @@ -120,8 +120,8 @@ - False - ..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll + ..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll + True System diff --git a/src/umbraco.cms/umbraco.cms.csproj.DotSettings b/src/umbraco.cms/umbraco.cms.csproj.DotSettings new file mode 100644 index 0000000000..662f95686e --- /dev/null +++ b/src/umbraco.cms/umbraco.cms.csproj.DotSettings @@ -0,0 +1,2 @@ + + CSharp50 \ No newline at end of file diff --git a/src/umbraco.controls/CodeArea.cs b/src/umbraco.controls/CodeArea.cs index e6f7dc3b68..3d4aba7a17 100644 --- a/src/umbraco.controls/CodeArea.cs +++ b/src/umbraco.controls/CodeArea.cs @@ -74,24 +74,26 @@ namespace umbraco.uicontrols if (CodeMirrorEnabled) { - ClientDependencyLoader.Instance.RegisterDependency(0, "CodeMirror/js/lib/codemirror.js", "UmbracoClient", ClientDependencyType.Javascript); + ClientDependencyLoader.Instance.RegisterDependency(0, "lib/CodeMirror/lib/codemirror.js", "UmbracoRoot", ClientDependencyType.Javascript); - ClientDependencyLoader.Instance.RegisterDependency(2, "CodeMirror/js/mode/" + CodeBase.ToString().ToLower() + "/" + CodeBase.ToString().ToLower() + ".js", "UmbracoClient", ClientDependencyType.Javascript); + ClientDependencyLoader.Instance.RegisterDependency(2, "lib/CodeMirror/mode/" + CodeBase.ToString().ToLower() + "/" + CodeBase.ToString().ToLower() + ".js", "UmbracoRoot", ClientDependencyType.Javascript); if (CodeBase == EditorType.HtmlMixed) { - ClientDependencyLoader.Instance.RegisterDependency(1, "CodeMirror/js/mode/xml/xml.js", "UmbracoClient", ClientDependencyType.Javascript); - ClientDependencyLoader.Instance.RegisterDependency(1, "CodeMirror/js/mode/javascript/javascript.js", "UmbracoClient", ClientDependencyType.Javascript); - ClientDependencyLoader.Instance.RegisterDependency(1, "CodeMirror/js/mode/css/css.js", "UmbracoClient", ClientDependencyType.Javascript); + ClientDependencyLoader.Instance.RegisterDependency(1, "lib/CodeMirror/mode/xml/xml.js", "UmbracoRoot", ClientDependencyType.Javascript); + ClientDependencyLoader.Instance.RegisterDependency(1, "lib/CodeMirror/mode/javascript/javascript.js", "UmbracoRoot", ClientDependencyType.Javascript); + ClientDependencyLoader.Instance.RegisterDependency(1, "lib/CodeMirror/mode/css/css.js", "UmbracoRoot", ClientDependencyType.Javascript); + ClientDependencyLoader.Instance.RegisterDependency(1, "lib/CodeMirror/mode/htmlmixed/htmlmixed.js", "UmbracoRoot", ClientDependencyType.Javascript); + } - ClientDependencyLoader.Instance.RegisterDependency(2, "CodeMirror/js/lib/util/search.js", "UmbracoClient", ClientDependencyType.Javascript); - ClientDependencyLoader.Instance.RegisterDependency(2, "CodeMirror/js/lib/util/searchcursor.js", "UmbracoClient", ClientDependencyType.Javascript); - ClientDependencyLoader.Instance.RegisterDependency(2, "CodeMirror/js/lib/util/dialog.js", "UmbracoClient", ClientDependencyType.Javascript); - ClientDependencyLoader.Instance.RegisterDependency(2, "CodeMirror/js/lib/util/dialog.css", "UmbracoClient", ClientDependencyType.Css); + ClientDependencyLoader.Instance.RegisterDependency(2, "lib/CodeMirror/addon/search/search.js", "UmbracoRoot", ClientDependencyType.Javascript); + ClientDependencyLoader.Instance.RegisterDependency(2, "lib/CodeMirror/addon/search/searchcursor.js", "UmbracoRoot", ClientDependencyType.Javascript); + ClientDependencyLoader.Instance.RegisterDependency(2, "lib/CodeMirror/addon/dialog/dialog.js", "UmbracoRoot", ClientDependencyType.Javascript); + ClientDependencyLoader.Instance.RegisterDependency(2, "lib/CodeMirror/addon/dialog/dialog.css", "UmbracoRoot", ClientDependencyType.Css); - ClientDependencyLoader.Instance.RegisterDependency(2, "CodeMirror/js/lib/codemirror.css", "UmbracoClient", ClientDependencyType.Css); - ClientDependencyLoader.Instance.RegisterDependency(3, "CodeMirror/css/umbracoCustom.css", "UmbracoClient", ClientDependencyType.Css); + ClientDependencyLoader.Instance.RegisterDependency(2, "lib/CodeMirror/lib/codemirror.css", "UmbracoRoot", ClientDependencyType.Css); + //ClientDependencyLoader.Instance.RegisterDependency(3, "CodeMirror/css/umbracoCustom.css", "UmbracoClient", ClientDependencyType.Css); ClientDependencyLoader.Instance.RegisterDependency(4, "CodeArea/styles.css", "UmbracoClient", ClientDependencyType.Css); } } @@ -225,15 +227,11 @@ namespace umbraco.uicontrols indentUnit: 4, indentWithTabs: true, enterMode: ""keep"", - onCursorActivity: updateLineInfo, lineWrapping: false" + editorMimetype + @", lineNumbers: true" + extraKeys + @" - }); - - //resizeTextArea(m_textEditor, " + OffSetX.ToString() + "," + OffSetY.ToString() + @"); - updateLineInfo(codeEditor); + }); "; return jsEventCode; diff --git a/src/umbraco.controls/TabView.cs b/src/umbraco.controls/TabView.cs index 06b1461ad2..b4211b4a29 100644 --- a/src/umbraco.controls/TabView.cs +++ b/src/umbraco.controls/TabView.cs @@ -33,7 +33,7 @@ namespace umbraco.uicontrols { base.CreateChildControls(); _tabList.TagName = "ul"; - _tabList.Attributes.Add("class", "nav nav-tabs umb-nav-tabs span12"); + _tabList.Attributes.Add("class", "nav nav-tabs umb-nav-tabs span12 -padding-left"); base.row.Controls.Add(_tabList); _body.TagName = "div"; @@ -41,7 +41,7 @@ namespace umbraco.uicontrols { base.Controls.Add(_body); _tabsHolder.TagName = "div"; - _tabsHolder.Attributes.Add("class", "tab-content form-horizontal umb-tab-content"); + _tabsHolder.Attributes.Add("class", "tab-content form-horizontal"); _tabsHolder.ID = this.ID + "_content"; _body.Controls.Add(_tabsHolder); diff --git a/src/umbraco.controls/app.config b/src/umbraco.controls/app.config index 0f2278a158..a0794caa99 100644 --- a/src/umbraco.controls/app.config +++ b/src/umbraco.controls/app.config @@ -4,11 +4,11 @@ - + - + diff --git a/src/umbraco.controls/packages.config b/src/umbraco.controls/packages.config index 0859f3d4e9..1e49726191 100644 --- a/src/umbraco.controls/packages.config +++ b/src/umbraco.controls/packages.config @@ -1,4 +1,4 @@  - + \ No newline at end of file diff --git a/src/umbraco.controls/umbraco.controls.csproj b/src/umbraco.controls/umbraco.controls.csproj index e1539a8322..718b1a124f 100644 --- a/src/umbraco.controls/umbraco.controls.csproj +++ b/src/umbraco.controls/umbraco.controls.csproj @@ -68,9 +68,9 @@ false - - False - ..\packages\ClientDependency.1.8.3.1\lib\net45\ClientDependency.Core.dll + + ..\packages\ClientDependency.1.8.4\lib\net45\ClientDependency.Core.dll + True diff --git a/src/umbraco.controls/umbraco.controls.csproj.DotSettings b/src/umbraco.controls/umbraco.controls.csproj.DotSettings new file mode 100644 index 0000000000..662f95686e --- /dev/null +++ b/src/umbraco.controls/umbraco.controls.csproj.DotSettings @@ -0,0 +1,2 @@ + + CSharp50 \ No newline at end of file diff --git a/src/umbraco.datalayer/packages.config b/src/umbraco.datalayer/packages.config index 6c3f4c3945..2a222078f5 100644 --- a/src/umbraco.datalayer/packages.config +++ b/src/umbraco.datalayer/packages.config @@ -1,6 +1,6 @@  - - - + + + \ No newline at end of file diff --git a/src/umbraco.datalayer/umbraco.datalayer.csproj b/src/umbraco.datalayer/umbraco.datalayer.csproj index 1002075a03..682062248d 100644 --- a/src/umbraco.datalayer/umbraco.datalayer.csproj +++ b/src/umbraco.datalayer/umbraco.datalayer.csproj @@ -78,7 +78,7 @@ False - ..\packages\MySql.Data.6.9.6\lib\net45\MySql.Data.dll + ..\packages\MySql.Data.6.9.8\lib\net45\MySql.Data.dll diff --git a/src/umbraco.datalayer/umbraco.datalayer.csproj.DotSettings b/src/umbraco.datalayer/umbraco.datalayer.csproj.DotSettings new file mode 100644 index 0000000000..662f95686e --- /dev/null +++ b/src/umbraco.datalayer/umbraco.datalayer.csproj.DotSettings @@ -0,0 +1,2 @@ + + CSharp50 \ No newline at end of file diff --git a/src/umbraco.editorControls/umbraco.editorControls.csproj.DotSettings b/src/umbraco.editorControls/umbraco.editorControls.csproj.DotSettings new file mode 100644 index 0000000000..662f95686e --- /dev/null +++ b/src/umbraco.editorControls/umbraco.editorControls.csproj.DotSettings @@ -0,0 +1,2 @@ + + CSharp50 \ No newline at end of file diff --git a/src/umbraco.interfaces/umbraco.interfaces.csproj.DotSettings b/src/umbraco.interfaces/umbraco.interfaces.csproj.DotSettings new file mode 100644 index 0000000000..662f95686e --- /dev/null +++ b/src/umbraco.interfaces/umbraco.interfaces.csproj.DotSettings @@ -0,0 +1,2 @@ + + CSharp50 \ No newline at end of file
    {1}{2} {3}{4}
    {1}{2} {3}{4}