diff --git a/.gitattributes b/.gitattributes index 0ae80905db..686f211c96 100644 --- a/.gitattributes +++ b/.gitattributes @@ -50,3 +50,5 @@ *.dbproj text=auto merge=union *.sln text=auto eol=crlf merge=union *.gitattributes text=auto merge=union + +*.gitattributes text=auto diff --git a/.github/BUILD.md b/.github/BUILD.md index c6e870f396..ad33872423 100644 --- a/.github/BUILD.md +++ b/.github/BUILD.md @@ -43,6 +43,8 @@ If you only see a build.bat-file, you're probably on the wrong branch. If you sw You might run into [Powershell quirks](#powershell-quirks). +If it runs without errors; Hooray! Now you can continue with [the next step](CONTRIBUTING.md#how-do-i-begin) and open the solution and build it. + ### Build Infrastructure The Umbraco Build infrastructure relies on a PowerShell object. The object can be retrieved with: diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index e009ee2294..5537a46ef8 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -22,13 +22,13 @@ This project and everyone participating in it, is governed by the [our Code of C [Reviews](#reviews) * [Styleguides](#styleguides) - * [The PR team](#the-pr-team) + * [The Core Contributors](#the-core-contributors-team) * [Questions?](#questions) [Working with the code](#working-with-the-code) * [Building Umbraco from source code](#building-umbraco-from-source-code) * [Working with the source code](#working-with-the-source-code) - * [Making changes after the PR was opened](#making-changes-after-the-pr-was-opened) + * [Making changes after the PR is open](#making-changes-after-the-pr-is-open) * [Which branch should I target for my contributions?](#which-branch-should-i-target-for-my-contributions) * [Keeping your Umbraco fork in sync with the main repository](#keeping-your-umbraco-fork-in-sync-with-the-main-repository) @@ -60,19 +60,19 @@ Great question! The short version goes like this: ![Clone the fork](img/clonefork.png) - * **Switch to the correct branch** - switch to the v8-dev branch + * **Switch to the correct branch** - switch to the `v8/contrib` branch * **Build** - build your fork of Umbraco locally as described in [building Umbraco from source code](BUILD.md) * **Change** - make your changes, experiment, have fun, explore and learn, and don't be afraid. We welcome all contributions and will [happily give feedback](#questions) - * **Commit** - done? Yay! 🎉 **Important:** create a new branch now and name it after the issue you're fixing, we usually follow the format: `temp-12345`. This means it's a temporary branch for the particular issue you're working on, in this case `12345`. When you have a branch, commit your changes. Don't commit to `v8/dev`, create a new branch first. + * **Commit** - done? Yay! 🎉 **Important:** create a new branch now and name it after the issue you're fixing, we usually follow the format: `temp-12345`. This means it's a temporary branch for the particular issue you're working on, in this case `12345`. When you have a branch, commit your changes. Don't commit to `v8/contrib`, create a new branch first. * **Push** - great, now you can push the changes up to your fork on GitHub - * **Create pull request** - exciting! You're ready to show us your changes (or not quite ready, you just need some feedback to progress - you can now make use of GitHub's draft pull request status, detailed [here] (https://github.blog/2019-02-14-introducing-draft-pull-requests/)). GitHub has picked up on the new branch you've pushed and will offer to create a Pull Request. Click that green button and away you go. + * **Create pull request** - exciting! You're ready to show us your changes (or not quite ready, you just need some feedback to progress - you can now make use of GitHub's draft pull request status, detailed [here](https://github.blog/2019-02-14-introducing-draft-pull-requests/)). GitHub has picked up on the new branch you've pushed and will offer to create a Pull Request. Click that green button and away you go. ![Create a pull request](img/createpullrequest.png) ### Pull requests The most successful pull requests usually look a like this: - * Fill in the required template, linking your pull request to an issue on the [issue tracker,](https://github.com/umbraco/Umbraco-CMS/issues) if applicable. + * Fill in the required template (shown when starting a PR on GitHub), and link your pull request to an issue on the [issue tracker,](https://github.com/umbraco/Umbraco-CMS/issues) if applicable. * Include screenshots and animated GIFs in your pull request whenever possible. * Unit tests, while optional, are awesome. Thank you! * New code is commented with documentation from which [the reference documentation](https://our.umbraco.com/documentation/Reference/) is generated. @@ -98,20 +98,21 @@ To be honest, we don't like rules very much. We trust you have the best of inten That said, the Umbraco development team likes to follow the hints that ReSharper gives us (no problem if you don't have this installed) and we've added a `.editorconfig` file so that Visual Studio knows what to do with whitespace, line endings, etc. -### The PR team +### The Core Contributors team -The pull request team consists of one member of Umbraco HQ, [Sebastiaan](https://github.com/nul800sebastiaan), who gets assistance from the following community members who have comitted to volunteering their free time: +The Core Contributors team consists of one member of Umbraco HQ, [Sebastiaan](https://github.com/nul800sebastiaan), who gets assistance from the following community members who have comitted to volunteering their free time: - [Anders Bjerner](https://github.com/abjerner) -- [Dave Woestenborghs](https://github.com/dawoe) - [Emma Burstow](https://github.com/emmaburstow) - [Poornima Nayar](https://github.com/poornimanayar) +- [Kenn Jacobsen](https://twitter.com/KennJacobsen_DK) + These wonderful people aim to provide you with a first reply to your PR, review and test out your changes and on occasions, they might ask more questions. If they are happy with your work, they'll let Umbraco HQ know by approving the PR. Hq will have final sign-off and will check the work again before it is merged. ### Questions? -You can get in touch with [the PR team](#the-pr-team) in multiple ways; we love open conversations and we are a friendly bunch. No question you have is stupid. Any question you have usually helps out multiple people with the same question. Ask away: +You can get in touch with [the core contributors team](#the-core-contributors-team) in multiple ways; we love open conversations and we are a friendly bunch. No question you have is stupid. Any question you have usually helps out multiple people with the same question. Ask away: - If there's an existing issue on the issue tracker then that's a good place to leave questions and discuss how to start or move forward. - Unsure where to start? Did something not work as expected? Try leaving a note in the ["Contributing to Umbraco"](https://our.umbraco.com/forum/contributing-to-umbraco-cms/) forum. The team monitors that one closely, so one of us will be on hand and ready to point you in the right direction. @@ -122,9 +123,10 @@ You can get in touch with [the PR team](#the-pr-team) in multiple ways; we love In order to build the Umbraco source code locally, first make sure you have the following installed. - * Visual Studio 2017 v15.9.7+ - * Node v10+ - * npm v6.4.1+ + * [Visual Studio 2017 v15.9.7+](https://visualstudio.microsoft.com/vs/) + * [Node.js v10+](https://nodejs.org/en/download/) + * npm v6.4.1+ (installed with Node.js) + * [Git command line](https://git-scm.com/download/) The easiest way to get started is to open `src\umbraco.sln` in Visual Studio 2017 (version 15.9.7 or higher, [the community edition is free](https://www.visualstudio.com/thank-you-downloading-visual-studio/?sku=Community&rel=15) for you to use to contribute to Open Source projects). In Visual Studio, find the Task Runner Explorer (in the View menu under Other Windows) and run the build task under the gulpfile. @@ -158,7 +160,7 @@ To find the general areas for something you're looking to fix or improve, have a ### Which branch should I target for my contributions? -We like to use [Gitflow as much as possible](https://jeffkreeftmeijer.com/git-flow/), but don't worry if you are not familiar with it. The most important thing you need to know is that when you fork the Umbraco repository, the default branch is set to something, usually `v8/dev`. If you are working on v8, this is the branch you should be targetting. For v7 contributions, please target 'v7/dev'. +We like to use [Gitflow as much as possible](https://jeffkreeftmeijer.com/git-flow/), but don't worry if you are not familiar with it. The most important thing you need to know is that when you fork the Umbraco repository, the default branch is set to something, usually `v8/contrib`. If you are working on v8, this is the branch you should be targetting. For v7 contributions, please target 'v7/dev'. Please note: we are no longer accepting features for v7 but will continue to merge bug fixes as and when they arise. @@ -184,13 +186,13 @@ Then when you want to get the changes from the main repository: ``` git fetch upstream -git rebase upstream/v8/dev +git rebase upstream/v8/contrib ``` -In this command we're syncing with the `v8/dev` branch, but you can of course choose another one if needed. +In this command we're syncing with the `v8/contrib` branch, but you can of course choose another one if needed. (More info on how this works: [http://robots.thoughtbot.com/post/5133345960/keeping-a-git-fork-updated](http://robots.thoughtbot.com/post/5133345960/keeping-a-git-fork-updated)) ### And finally -We welcome all kinds of contributions to this repository. If you don't feel you'd like to make code changes here, you can visit our [documentation repository](https://github.com/umbraco/UmbracoDocs) and use your experience to contribute to making the docs we have, even better. We also encourage community members to feel free to comment on others' pull requests and issues - the expertise we have is not limited to the PR team and HQ. So, if you see something on the issue tracker or pull requests you feel you can add to, please don't be shy. +We welcome all kinds of contributions to this repository. If you don't feel you'd like to make code changes here, you can visit our [documentation repository](https://github.com/umbraco/UmbracoDocs) and use your experience to contribute to making the docs we have, even better. We also encourage community members to feel free to comment on others' pull requests and issues - the expertise we have is not limited to the Core Contributors and HQ. So, if you see something on the issue tracker or pull requests you feel you can add to, please don't be shy. diff --git a/.github/README.md b/.github/README.md index d6d978c3d6..467ca6e5e6 100644 --- a/.github/README.md +++ b/.github/README.md @@ -1,4 +1,4 @@ -# [Umbraco CMS](https://umbraco.com) · [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](../LICENSE.md) [![Build status](https://umbraco.visualstudio.com/Umbraco%20Cms/_apis/build/status/Cms%208%20Continuous?branchName=v8/dev)](https://umbraco.visualstudio.com/Umbraco%20Cms/_build?definitionId=75) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md) [![Twitter](https://img.shields.io/twitter/follow/umbraco.svg?style=social&label=Follow)](https://twitter.com/intent/follow?screen_name=umbraco) +# [Umbraco CMS](https://umbraco.com) · [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](../LICENSE.md) [![Build status](https://umbraco.visualstudio.com/Umbraco%20Cms/_apis/build/status/Cms%208%20Continuous?branchName=v8/contrib)](https://umbraco.visualstudio.com/Umbraco%20Cms/_build?definitionId=75) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md) [![Twitter](https://img.shields.io/twitter/follow/umbraco.svg?style=social&label=Follow)](https://twitter.com/intent/follow?screen_name=umbraco) Umbraco is the friendliest, most flexible and fastest growing ASP.NET CMS, and used by more than 500,000 websites worldwide. Our mission is to help you deliver delightful digital experiences by making Umbraco friendly, simpler and social. diff --git a/.github/img/defaultbranch.png b/.github/img/defaultbranch.png index f3a5b9efbc..3550b5c34c 100644 Binary files a/.github/img/defaultbranch.png and b/.github/img/defaultbranch.png differ diff --git a/.gitignore b/.gitignore index 12ad3299ad..870d24c648 100644 --- a/.gitignore +++ b/.gitignore @@ -100,6 +100,9 @@ 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 src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/canvasdesigner.*.js +src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/navigation.controller.js +src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/main.controller.js +src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/utilities.js src/Umbraco.Web.UI/[Uu]mbraco/[Vv]iews/ src/Umbraco.Web.UI/[Uu]mbraco/[Vv]iews/**/*.js diff --git a/NuGet.Config b/NuGet.Config index 7d786702f4..31fd84e456 100644 --- a/NuGet.Config +++ b/NuGet.Config @@ -7,6 +7,5 @@ --> - diff --git a/build/NuSpecs/UmbracoCms.Web.nuspec b/build/NuSpecs/UmbracoCms.Web.nuspec index 347bde139e..72619db02e 100644 --- a/build/NuSpecs/UmbracoCms.Web.nuspec +++ b/build/NuSpecs/UmbracoCms.Web.nuspec @@ -25,7 +25,7 @@ not want this to happen as the alpha of the next major is, really, the next major already. --> - + diff --git a/build/NuSpecs/tools/Views.Web.config.install.xdt b/build/NuSpecs/tools/Views.Web.config.install.xdt index 828bb8612f..7dd2640d09 100644 --- a/build/NuSpecs/tools/Views.Web.config.install.xdt +++ b/build/NuSpecs/tools/Views.Web.config.install.xdt @@ -11,7 +11,7 @@ - + diff --git a/build/build-bootstrap.ps1 b/build/build-bootstrap.ps1 index 71a25bfd7e..82c789ff22 100644 --- a/build/build-bootstrap.ps1 +++ b/build/build-bootstrap.ps1 @@ -22,6 +22,8 @@ # get NuGet $cache = 4 $nuget = "$scriptTemp\nuget.exe" + # ensure the correct NuGet-source is used. This one is used by Umbraco + $nugetsourceUmbraco = "https://www.myget.org/F/umbracocore/api/v3/index.json" if (-not $local) { $source = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe" @@ -61,7 +63,7 @@ # get the build system if (-not $local) { - $params = "-OutputDirectory", $scriptTemp, "-Verbosity", "quiet", "-PreRelease" + $params = "-OutputDirectory", $scriptTemp, "-Verbosity", "quiet", "-PreRelease", "-Source", $nugetsourceUmbraco &$nuget install Umbraco.Build @params if (-not $?) { throw "Failed to download Umbraco.Build." } } diff --git a/build/build.ps1 b/build/build.ps1 index ea07e4516f..6e124d1508 100644 --- a/build/build.ps1 +++ b/build/build.ps1 @@ -375,11 +375,14 @@ }) + $nugetsourceUmbraco = "https://api.nuget.org/v3/index.json" + $ubuild.DefineMethod("RestoreNuGet", { Write-Host "Restore NuGet" Write-Host "Logging to $($this.BuildTemp)\nuget.restore.log" - &$this.BuildEnv.NuGet restore "$($this.SolutionRoot)\src\Umbraco.sln" > "$($this.BuildTemp)\nuget.restore.log" + $params = "-Source", $nugetsourceUmbraco + &$this.BuildEnv.NuGet restore "$($this.SolutionRoot)\src\Umbraco.sln" > "$($this.BuildTemp)\nuget.restore.log" @params if (-not $?) { throw "Failed to restore NuGet packages." } }) diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index 363677b826..5587979a6c 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -2,7 +2,7 @@ using System.Resources; [assembly: AssemblyCompany("Umbraco")] -[assembly: AssemblyCopyright("Copyright © Umbraco 2019")] +[assembly: AssemblyCopyright("Copyright © Umbraco 2020")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -18,5 +18,5 @@ using System.Resources; [assembly: AssemblyVersion("8.0.0")] // these are FYI and changed automatically -[assembly: AssemblyFileVersion("8.6.0")] -[assembly: AssemblyInformationalVersion("8.6.0")] +[assembly: AssemblyFileVersion("8.7.0")] +[assembly: AssemblyInformationalVersion("8.7.0")] diff --git a/src/Umbraco.Core/Cache/WebCachingAppCache.cs b/src/Umbraco.Core/Cache/WebCachingAppCache.cs index c6e104221a..bec596b129 100644 --- a/src/Umbraco.Core/Cache/WebCachingAppCache.cs +++ b/src/Umbraco.Core/Cache/WebCachingAppCache.cs @@ -37,23 +37,13 @@ namespace Umbraco.Core.Cache /// public object Get(string key, Func factory, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) { - CacheDependency dependency = null; - if (dependentFiles != null && dependentFiles.Any()) - { - dependency = new CacheDependency(dependentFiles); - } - return Get(key, factory, timeout, isSliding, priority, removedCallback, dependency); + return GetInternal(key, factory, timeout, isSliding, priority, removedCallback, dependentFiles); } /// public void Insert(string key, Func factory, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) { - CacheDependency dependency = null; - if (dependentFiles != null && dependentFiles.Any()) - { - dependency = new CacheDependency(dependentFiles); - } - Insert(key, factory, timeout, isSliding, priority, removedCallback, dependency); + InsertInternal(key, factory, timeout, isSliding, priority, removedCallback, dependentFiles); } #region Dictionary @@ -86,7 +76,7 @@ namespace Umbraco.Core.Cache protected override void EnterWriteLock() { - _locker.EnterWriteLock();; + _locker.EnterWriteLock(); } protected override void ExitReadLock() @@ -103,7 +93,7 @@ namespace Umbraco.Core.Cache #endregion - private object Get(string key, Func factory, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, CacheDependency dependency = null) + private object GetInternal(string key, Func factory, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) { key = GetCacheKey(key); @@ -163,6 +153,10 @@ namespace Umbraco.Core.Cache var sliding = isSliding == false ? System.Web.Caching.Cache.NoSlidingExpiration : (timeout ?? System.Web.Caching.Cache.NoSlidingExpiration); lck.UpgradeToWriteLock(); + + // create a cache dependency if one is needed. + var dependency = dependentFiles != null && dependentFiles.Length > 0 ? new CacheDependency(dependentFiles) : null; + //NOTE: 'Insert' on System.Web.Caching.Cache actually does an add or update! _cache.Insert(key, result, dependency, absolute, sliding, priority, removedCallback); } @@ -180,7 +174,7 @@ namespace Umbraco.Core.Cache return value; } - private void Insert(string cacheKey, Func getCacheItem, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, CacheDependency dependency = null) + private void InsertInternal(string cacheKey, Func getCacheItem, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) { // 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. @@ -197,6 +191,10 @@ namespace Umbraco.Core.Cache try { _locker.EnterWriteLock(); + + // create a cache dependency if one is needed. + var dependency = dependentFiles != null && dependentFiles.Length > 0 ? new CacheDependency(dependentFiles) : null; + //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/Collections/TopoGraph.cs b/src/Umbraco.Core/Collections/TopoGraph.cs index b8ded4a458..955a210465 100644 --- a/src/Umbraco.Core/Collections/TopoGraph.cs +++ b/src/Umbraco.Core/Collections/TopoGraph.cs @@ -126,7 +126,7 @@ namespace Umbraco.Core.Collections if (_items.TryGetValue(key, out value)) yield return value; else if (throwOnMissing) - throw new Exception(MissingDependencyError); + throw new Exception($"{MissingDependencyError} Error in type {typeof(TItem).Name}, with key {key}"); } } } diff --git a/src/Umbraco.Core/Compose/RelateOnCopyComponent.cs b/src/Umbraco.Core/Compose/RelateOnCopyComponent.cs index 63a7e170da..b56ff8b87e 100644 --- a/src/Umbraco.Core/Compose/RelateOnCopyComponent.cs +++ b/src/Umbraco.Core/Compose/RelateOnCopyComponent.cs @@ -26,10 +26,11 @@ namespace Umbraco.Core.Compose if (relationType == null) { - relationType = new RelationType(Constants.ObjectTypes.Document, + relationType = new RelationType(Constants.Conventions.RelationTypes.RelateDocumentOnCopyAlias, + Constants.Conventions.RelationTypes.RelateDocumentOnCopyName, + true, Constants.ObjectTypes.Document, - Constants.Conventions.RelationTypes.RelateDocumentOnCopyAlias, - Constants.Conventions.RelationTypes.RelateDocumentOnCopyName) { IsBidirectional = true }; + Constants.ObjectTypes.Document); relationService.Save(relationType); } diff --git a/src/Umbraco.Core/Compose/RelateOnTrashComponent.cs b/src/Umbraco.Core/Compose/RelateOnTrashComponent.cs index 8371f9b279..4e01c50fc6 100644 --- a/src/Umbraco.Core/Compose/RelateOnTrashComponent.cs +++ b/src/Umbraco.Core/Compose/RelateOnTrashComponent.cs @@ -63,7 +63,7 @@ namespace Umbraco.Core.Compose var documentObjectType = Constants.ObjectTypes.Document; const string relationTypeName = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteName; - relationType = new RelationType(documentObjectType, documentObjectType, relationTypeAlias, relationTypeName); + relationType = new RelationType(relationTypeName, relationTypeAlias, false, documentObjectType, documentObjectType); relationService.Save(relationType); } @@ -106,7 +106,7 @@ namespace Umbraco.Core.Compose { var documentObjectType = Constants.ObjectTypes.Document; const string relationTypeName = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteName; - relationType = new RelationType(documentObjectType, documentObjectType, relationTypeAlias, relationTypeName); + relationType = new RelationType(relationTypeName, relationTypeAlias, false, documentObjectType, documentObjectType); relationService.Save(relationType); } foreach (var item in e.MoveInfoCollection) diff --git a/src/Umbraco.Core/Composing/CollectionBuilderBase.cs b/src/Umbraco.Core/Composing/CollectionBuilderBase.cs index 41038ea4e9..0d398be83b 100644 --- a/src/Umbraco.Core/Composing/CollectionBuilderBase.cs +++ b/src/Umbraco.Core/Composing/CollectionBuilderBase.cs @@ -79,9 +79,12 @@ namespace Umbraco.Core.Composing foreach (var type in types) EnsureType(type, "register"); - // register them + // register them - ensuring that each item is registered with the same lifetime as the collection. + // NOTE: Previously each one was not registered with the same lifetime which would mean that if there + // was a dependency on an individual item, it would resolve a brand new transient instance which isn't what + // we would expect to happen. The same item should be resolved from the container as the collection. foreach (var type in types) - register.Register(type); + register.Register(type, CollectionLifetime); _registeredTypes = types; } diff --git a/src/Umbraco.Core/Composing/CompositionExtensions/Repositories.cs b/src/Umbraco.Core/Composing/CompositionExtensions/Repositories.cs index 23dc9e67c6..00b29dd97f 100644 --- a/src/Umbraco.Core/Composing/CompositionExtensions/Repositories.cs +++ b/src/Umbraco.Core/Composing/CompositionExtensions/Repositories.cs @@ -47,6 +47,8 @@ namespace Umbraco.Core.Composing.CompositionExtensions composition.RegisterUnique(); composition.RegisterUnique(); composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); return composition; } diff --git a/src/Umbraco.Core/Composing/Current.cs b/src/Umbraco.Core/Composing/Current.cs index f12bf0dd63..a06f09baf6 100644 --- a/src/Umbraco.Core/Composing/Current.cs +++ b/src/Umbraco.Core/Composing/Current.cs @@ -5,6 +5,7 @@ using Umbraco.Core.Dictionary; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Mapping; +using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PackageActions; using Umbraco.Core.Packaging; @@ -154,6 +155,9 @@ namespace Umbraco.Core.Composing public static DataEditorCollection DataEditors => Factory.GetInstance(); + public static DataValueReferenceFactoryCollection DataValueReferenceFactories + => Factory.GetInstance(); + public static PropertyEditorCollection PropertyEditors => Factory.GetInstance(); @@ -205,6 +209,8 @@ namespace Umbraco.Core.Composing public static IVariationContextAccessor VariationContextAccessor => Factory.GetInstance(); + public static IImageUrlGenerator ImageUrlGenerator + => Factory.GetInstance(); #endregion } } diff --git a/src/Umbraco.Core/CompositionExtensions.cs b/src/Umbraco.Core/CompositionExtensions.cs index 5dd33c2a60..ced9a9386a 100644 --- a/src/Umbraco.Core/CompositionExtensions.cs +++ b/src/Umbraco.Core/CompositionExtensions.cs @@ -49,6 +49,13 @@ namespace Umbraco.Core public static DataEditorCollectionBuilder DataEditors(this Composition composition) => composition.WithCollectionBuilder(); + /// + /// Gets the data value reference factory collection builder. + /// + /// The composition. + public static DataValueReferenceFactoryCollectionBuilder DataValueReferenceFactories(this Composition composition) + => composition.WithCollectionBuilder(); + /// /// Gets the property value converters collection builder. /// diff --git a/src/Umbraco.Core/Constants-AppSettings.cs b/src/Umbraco.Core/Constants-AppSettings.cs index 509be46b61..704617d90c 100644 --- a/src/Umbraco.Core/Constants-AppSettings.cs +++ b/src/Umbraco.Core/Constants-AppSettings.cs @@ -9,6 +9,8 @@ namespace Umbraco.Core /// public static class AppSettings { + public const string MainDomLock = "Umbraco.Core.MainDom.Lock"; + // TODO: Kill me - still used in Umbraco.Core.IO.SystemFiles:27 [Obsolete("We need to kill this appsetting as we do not use XML content cache umbraco.config anymore due to NuCache")] public const string ContentXML = "Umbraco.Core.ContentXML"; //umbracoContentXML @@ -41,6 +43,21 @@ namespace Umbraco.Core /// public const string Path = "Umbraco.Core.Path"; + /// + /// Gets the path to the css directory (/css by default). + /// + public const string CssPath = "umbracoCssPath"; + + /// + /// Gets the path to the scripts directory (/scripts by default). + /// + public const string ScriptsPath = "umbracoScriptsPath"; + + /// + /// Gets the path to media directory (/media by default). + /// + public const string MediaPath = "umbracoMediaPath"; + /// /// The reserved urls from web.config. /// diff --git a/src/Umbraco.Core/Constants-Conventions.cs b/src/Umbraco.Core/Constants-Conventions.cs index e78c498e66..c1d7103a1c 100644 --- a/src/Umbraco.Core/Constants-Conventions.cs +++ b/src/Umbraco.Core/Constants-Conventions.cs @@ -315,34 +315,65 @@ namespace Umbraco.Core public static class RelationTypes { /// - /// ContentType name for default relation type "Relate Document On Copy". + /// Name for default relation type "Related Media". + /// + public const string RelatedMediaName = "Related Media"; + + /// + /// Alias for default relation type "Related Media" + /// + public const string RelatedMediaAlias = "umbMedia"; + + /// + /// Name for default relation type "Related Document". + /// + public const string RelatedDocumentName = "Related Document"; + + /// + /// Alias for default relation type "Related Document" + /// + public const string RelatedDocumentAlias = "umbDocument"; + + /// + /// Name for default relation type "Relate Document On Copy". /// public const string RelateDocumentOnCopyName = "Relate Document On Copy"; /// - /// ContentType alias for default relation type "Relate Document On Copy". + /// Alias for default relation type "Relate Document On Copy". /// public const string RelateDocumentOnCopyAlias = "relateDocumentOnCopy"; /// - /// ContentType name for default relation type "Relate Parent Document On Delete". + /// Name for default relation type "Relate Parent Document On Delete". /// public const string RelateParentDocumentOnDeleteName = "Relate Parent Document On Delete"; /// - /// ContentType alias for default relation type "Relate Parent Document On Delete". + /// Alias for default relation type "Relate Parent Document On Delete". /// public const string RelateParentDocumentOnDeleteAlias = "relateParentDocumentOnDelete"; /// - /// ContentType name for default relation type "Relate Parent Media Folder On Delete". + /// Name for default relation type "Relate Parent Media Folder On Delete". /// public const string RelateParentMediaFolderOnDeleteName = "Relate Parent Media Folder On Delete"; /// - /// ContentType alias for default relation type "Relate Parent Media Folder On Delete". + /// Alias for default relation type "Relate Parent Media Folder On Delete". /// public const string RelateParentMediaFolderOnDeleteAlias = "relateParentMediaFolderOnDelete"; + + /// + /// Returns the types of relations that are automatically tracked + /// + /// + /// Developers should not manually use these relation types since they will all be cleared whenever an entity + /// (content, media or member) is saved since they are auto-populated based on property values. + /// + public static string[] AutomaticRelationTypes = new[] { RelatedMediaAlias, RelatedDocumentAlias }; + + //TODO: return a list of built in types so we can use that to prevent deletion in the uI } } } diff --git a/src/Umbraco.Core/Constants-Security.cs b/src/Umbraco.Core/Constants-Security.cs index 27d6716000..43c989805b 100644 --- a/src/Umbraco.Core/Constants-Security.cs +++ b/src/Umbraco.Core/Constants-Security.cs @@ -28,8 +28,10 @@ namespace Umbraco.Core public const string UnknownUserName = "SYTEM"; public const string AdminGroupAlias = "admin"; + public const string EditorGroupAlias = "editor"; public const string SensitiveDataGroupAlias = "sensitiveData"; public const string TranslatorGroupAlias = "translator"; + public const string WriterGroupAlias = "writer"; public const string BackOfficeAuthenticationType = "UmbracoBackOffice"; public const string BackOfficeExternalAuthenticationType = "UmbracoExternalCookie"; diff --git a/src/Umbraco.Core/Constants-SqlTemplates.cs b/src/Umbraco.Core/Constants-SqlTemplates.cs new file mode 100644 index 0000000000..984bc495b0 --- /dev/null +++ b/src/Umbraco.Core/Constants-SqlTemplates.cs @@ -0,0 +1,20 @@ +namespace Umbraco.Core +{ + public static partial class Constants + { + public static class SqlTemplates + { + public static class VersionableRepository + { + public const string GetVersionIds = "Umbraco.Core.VersionableRepository.GetVersionIds"; + public const string GetVersion = "Umbraco.Core.VersionableRepository.GetVersion"; + public const string GetVersions = "Umbraco.Core.VersionableRepository.GetVersions"; + public const string EnsureUniqueNodeName = "Umbraco.Core.VersionableRepository.EnsureUniqueNodeName"; + public const string GetSortOrder = "Umbraco.Core.VersionableRepository.GetSortOrder"; + public const string GetParentNode = "Umbraco.Core.VersionableRepository.GetParentNode"; + public const string GetReservedId = "Umbraco.Core.VersionableRepository.GetReservedId"; + } + + } + } +} diff --git a/src/Umbraco.Core/UdiEntityType.cs b/src/Umbraco.Core/Contants-UdiEntityType.cs similarity index 100% rename from src/Umbraco.Core/UdiEntityType.cs rename to src/Umbraco.Core/Contants-UdiEntityType.cs diff --git a/src/Umbraco.Core/ContentExtensions.cs b/src/Umbraco.Core/ContentExtensions.cs index 3edad0c963..7bce23e98e 100644 --- a/src/Umbraco.Core/ContentExtensions.cs +++ b/src/Umbraco.Core/ContentExtensions.cs @@ -60,6 +60,19 @@ namespace Umbraco.Core #endregion + internal static bool IsMoving(this IContentBase entity) + { + // Check if this entity is being moved as a descendant as part of a bulk moving operations. + // When this occurs, only Path + Level + UpdateDate are being changed. In this case we can bypass a lot of the below + // operations which will make this whole operation go much faster. When moving we don't need to create + // new versions, etc... because we cannot roll this operation back anyways. + var isMoving = entity.IsPropertyDirty(nameof(entity.Path)) + && entity.IsPropertyDirty(nameof(entity.Level)) + && entity.IsPropertyDirty(nameof(entity.UpdateDate)); + + return isMoving; + } + /// /// Removes characters that are not valid XML characters from all entity properties /// of type string. See: http://stackoverflow.com/a/961504/5018 diff --git a/src/Umbraco.Core/ContentVariationExtensions.cs b/src/Umbraco.Core/ContentVariationExtensions.cs index fe5a82047a..29442b78e6 100644 --- a/src/Umbraco.Core/ContentVariationExtensions.cs +++ b/src/Umbraco.Core/ContentVariationExtensions.cs @@ -12,126 +12,260 @@ namespace Umbraco.Core /// /// Determines whether the content type is invariant. /// + /// The content type. + /// + /// A value indicating whether the content type is invariant. + /// public static bool VariesByNothing(this ISimpleContentType contentType) => contentType.Variations.VariesByNothing(); - /// - /// Determines whether the content type varies by culture. - /// - public static bool VariesByCulture(this ISimpleContentType contentType) => contentType.Variations.VariesByCulture(); - - /// - /// Determines whether the content type varies by segment. - /// - public static bool VariesBySegment(this ISimpleContentType contentType) => contentType.Variations.VariesBySegment(); - /// /// Determines whether the content type is invariant. /// + /// The content type. + /// + /// A value indicating whether the content type is invariant. + /// public static bool VariesByNothing(this IContentTypeBase contentType) => contentType.Variations.VariesByNothing(); - /// - /// Determines whether the content type varies by culture. - /// - /// And then it could also vary by segment. - public static bool VariesByCulture(this IContentTypeBase contentType) => contentType.Variations.VariesByCulture(); - - /// - /// Determines whether the content type varies by segment. - /// - /// And then it could also vary by culture. - public static bool VariesBySegment(this IContentTypeBase contentType) => contentType.Variations.VariesBySegment(); - - /// - /// Determines whether the content type varies by culture and segment. - /// - public static bool VariesByCultureAndSegment(this IContentTypeBase contentType) => contentType.Variations.VariesByCultureAndSegment(); - - /// - /// Determines whether the property type is invariant. - /// - public static bool VariesByNothing(this PropertyType propertyType) => propertyType.Variations.VariesByNothing(); - - /// - /// Determines whether the property type varies by culture. - /// - /// And then it could also vary by segment. - public static bool VariesByCulture(this PropertyType propertyType) => propertyType.Variations.VariesByCulture(); - - /// - /// Determines whether the property type varies by segment. - /// - /// And then it could also vary by culture. - public static bool VariesBySegment(this PropertyType propertyType) => propertyType.Variations.VariesBySegment(); - - /// - /// Determines whether the property type varies by culture and segment. - /// - public static bool VariesByCultureAndSegment(this PropertyType propertyType) => propertyType.Variations.VariesByCultureAndSegment(); - /// /// Determines whether the content type is invariant. /// + /// The content type. + /// + /// A value indicating whether the content type is invariant. + /// public static bool VariesByNothing(this IPublishedContentType contentType) => contentType.Variations.VariesByNothing(); /// - /// Determines whether the content type varies by culture. + /// Determines whether the property type is invariant. /// - /// And then it could also vary by segment. - public static bool VariesByCulture(this IPublishedContentType contentType) => contentType.Variations.VariesByCulture(); - - /// - /// Determines whether the content type varies by segment. - /// - /// And then it could also vary by culture. - public static bool VariesBySegment(this IPublishedContentType contentType) => contentType.Variations.VariesBySegment(); - - /// - /// Determines whether the content type varies by culture and segment. - /// - public static bool VariesByCultureAndSegment(this IPublishedContentType contentType) => contentType.Variations.VariesByCultureAndSegment(); + /// The property type. + /// + /// A value indicating whether the property type is invariant. + /// + public static bool VariesByNothing(this PropertyType propertyType) => propertyType.Variations.VariesByNothing(); /// /// Determines whether the property type is invariant. /// + /// The property type. + /// + /// A value indicating whether the property type is invariant. + /// public static bool VariesByNothing(this IPublishedPropertyType propertyType) => propertyType.Variations.VariesByNothing(); - /// - /// Determines whether the property type varies by culture. - /// - public static bool VariesByCulture(this IPublishedPropertyType propertyType) => propertyType.Variations.VariesByCulture(); - - /// - /// Determines whether the property type varies by segment. - /// - public static bool VariesBySegment(this IPublishedPropertyType propertyType) => propertyType.Variations.VariesBySegment(); - - /// - /// Determines whether the property type varies by culture and segment. - /// - public static bool VariesByCultureAndSegment(this IPublishedPropertyType propertyType) => propertyType.Variations.VariesByCultureAndSegment(); - /// /// Determines whether a variation is invariant. /// + /// The variation. + /// + /// A value indicating whether the variation is invariant. + /// public static bool VariesByNothing(this ContentVariation variation) => variation == ContentVariation.Nothing; + /// + /// Determines whether the content type varies by culture. + /// + /// The content type. + /// + /// A value indicating whether the content type varies by culture. + /// + public static bool VariesByCulture(this ISimpleContentType contentType) => contentType.Variations.VariesByCulture(); + + /// + /// Determines whether the content type varies by culture. + /// + /// The content type. + /// + /// A value indicating whether the content type varies by culture. + /// + public static bool VariesByCulture(this IContentTypeBase contentType) => contentType.Variations.VariesByCulture(); + + /// + /// Determines whether the content type varies by culture. + /// + /// The content type. + /// + /// A value indicating whether the content type varies by culture. + /// + public static bool VariesByCulture(this IPublishedContentType contentType) => contentType.Variations.VariesByCulture(); + + /// + /// Determines whether the property type varies by culture. + /// + /// The property type. + /// + /// A value indicating whether the property type varies by culture. + /// + public static bool VariesByCulture(this PropertyType propertyType) => propertyType.Variations.VariesByCulture(); + + /// + /// Determines whether the property type varies by culture. + /// + /// The property type. + /// + /// A value indicating whether the property type varies by culture. + /// + public static bool VariesByCulture(this IPublishedPropertyType propertyType) => propertyType.Variations.VariesByCulture(); + /// /// Determines whether a variation varies by culture. /// - /// And then it could also vary by segment. + /// The variation. + /// + /// A value indicating whether the variation varies by culture. + /// public static bool VariesByCulture(this ContentVariation variation) => (variation & ContentVariation.Culture) > 0; + /// + /// Determines whether the content type varies by segment. + /// + /// The content type. + /// + /// A value indicating whether the content type varies by segment. + /// + public static bool VariesBySegment(this ISimpleContentType contentType) => contentType.Variations.VariesBySegment(); + + /// + /// Determines whether the content type varies by segment. + /// + /// The content type. + /// + /// A value indicating whether the content type varies by segment. + /// + public static bool VariesBySegment(this IContentTypeBase contentType) => contentType.Variations.VariesBySegment(); + + /// + /// Determines whether the content type varies by segment. + /// + /// The content type. + /// + /// A value indicating whether the content type varies by segment. + /// + public static bool VariesBySegment(this IPublishedContentType contentType) => contentType.Variations.VariesBySegment(); + + /// + /// Determines whether the property type varies by segment. + /// + /// The property type. + /// + /// A value indicating whether the property type varies by segment. + /// + public static bool VariesBySegment(this PropertyType propertyType) => propertyType.Variations.VariesBySegment(); + + /// + /// Determines whether the property type varies by segment. + /// + /// The property type. + /// + /// A value indicating whether the property type varies by segment. + /// + public static bool VariesBySegment(this IPublishedPropertyType propertyType) => propertyType.Variations.VariesBySegment(); + /// /// Determines whether a variation varies by segment. /// - /// And then it could also vary by culture. + /// The variation. + /// + /// A value indicating whether the variation varies by segment. + /// public static bool VariesBySegment(this ContentVariation variation) => (variation & ContentVariation.Segment) > 0; + /// + /// Determines whether the content type varies by culture and segment. + /// + /// The content type. + /// + /// A value indicating whether the content type varies by culture and segment. + /// + public static bool VariesByCultureAndSegment(this ISimpleContentType contentType) => contentType.Variations.VariesByCultureAndSegment(); + + /// + /// Determines whether the content type varies by culture and segment. + /// + /// The content type. + /// + /// A value indicating whether the content type varies by culture and segment. + /// + public static bool VariesByCultureAndSegment(this IContentTypeBase contentType) => contentType.Variations.VariesByCultureAndSegment(); + + /// + /// Determines whether the content type varies by culture and segment. + /// + /// The content type. + /// + /// A value indicating whether the content type varies by culture and segment. + /// + public static bool VariesByCultureAndSegment(this IPublishedContentType contentType) => contentType.Variations.VariesByCultureAndSegment(); + + /// + /// Determines whether the property type varies by culture and segment. + /// + /// The property type. + /// + /// A value indicating whether the property type varies by culture and segment. + /// + public static bool VariesByCultureAndSegment(this PropertyType propertyType) => propertyType.Variations.VariesByCultureAndSegment(); + + /// + /// Determines whether the property type varies by culture and segment. + /// + /// The property type. + /// + /// A value indicating whether the property type varies by culture and segment. + /// + public static bool VariesByCultureAndSegment(this IPublishedPropertyType propertyType) => propertyType.Variations.VariesByCultureAndSegment(); + /// /// Determines whether a variation varies by culture and segment. /// + /// The variation. + /// + /// A value indicating whether the variation varies by culture and segment. + /// public static bool VariesByCultureAndSegment(this ContentVariation variation) => (variation & ContentVariation.CultureAndSegment) == ContentVariation.CultureAndSegment; + /// + /// Sets or removes the content type variation depending on the specified value. + /// + /// The content type. + /// The variation to set or remove. + /// If set to true sets the variation; otherwise, removes the variation. + /// + /// This method does not support setting the variation to nothing. + /// + public static void SetVariesBy(this IContentTypeBase contentType, ContentVariation variation, bool value = true) => contentType.Variations = contentType.Variations.SetFlag(variation, value); + + /// + /// Sets or removes the property type variation depending on the specified value. + /// + /// The property type. + /// The variation to set or remove. + /// If set to true sets the variation; otherwise, removes the variation. + /// + /// This method does not support setting the variation to nothing. + /// + public static void SetVariesBy(this PropertyType propertyType, ContentVariation variation, bool value = true) => propertyType.Variations = propertyType.Variations.SetFlag(variation, value); + + /// + /// Returns the variations with the variation set or removed depending on the specified value. + /// + /// The existing variations. + /// The variation to set or remove. + /// If set to true sets the variation; otherwise, removes the variation. + /// + /// The variations with the variation set or removed. + /// + /// + /// This method does not support setting the variation to nothing. + /// + public static ContentVariation SetFlag(this ContentVariation variations, ContentVariation variation, bool value = true) + { + return value + ? variations | variation // Set flag using bitwise logical OR + : variations & ~variation; // Remove flag using bitwise logical AND with bitwise complement (reversing the bit) + } + /// /// Validates that a combination of culture and segment is valid for the variation. /// @@ -140,16 +274,18 @@ namespace Umbraco.Core /// The segment. /// A value indicating whether to perform exact validation. /// A value indicating whether to support wildcards. - /// A value indicating whether to throw a when the combination is invalid. - /// True if the combination is valid; otherwise false. + /// A value indicating whether to throw a when the combination is invalid. + /// + /// true if the combination is valid; otherwise false. + /// + /// Occurs when the combination is invalid, and is true. /// /// When validation is exact, the combination must match the variation exactly. For instance, if the variation is Culture, then /// a culture is required. When validation is not strict, the combination must be equivalent, or more restrictive: if the variation is /// Culture, an invariant combination is ok. /// Basically, exact is for one content type, or one property type, and !exact is for "all property types" of one content type. - /// Both and can be "*" to indicate "all of them". + /// Both and can be "*" to indicate "all of them". /// - /// Occurs when the combination is invalid, and is true. public static bool ValidateVariation(this ContentVariation variation, string culture, string segment, bool exact, bool wildcards, bool throwIfInvalid) { culture = culture.NullOrWhiteSpaceAsNull(); @@ -166,13 +302,14 @@ namespace Umbraco.Core if (variation.VariesByCulture()) { // varies by culture - // in exact mode, the culture cannot be null + // in exact mode, the culture cannot be null if (exact && culture == null) { if (throwIfInvalid) throw new NotSupportedException($"Culture may not be null because culture variation is enabled."); + return false; - } + } } else { @@ -183,9 +320,10 @@ namespace Umbraco.Core { if (throwIfInvalid) throw new NotSupportedException($"Culture \"{culture}\" is invalid because culture variation is disabled."); + return false; } - } + } // if it does not vary by segment // the segment cannot have a value @@ -195,6 +333,7 @@ namespace Umbraco.Core { if (throwIfInvalid) throw new NotSupportedException($"Segment \"{segment}\" is invalid because segment variation is disabled."); + return false; } diff --git a/src/Umbraco.Core/EnumExtensions.cs b/src/Umbraco.Core/EnumExtensions.cs index b2e5f32c9a..9097432f64 100644 --- a/src/Umbraco.Core/EnumExtensions.cs +++ b/src/Umbraco.Core/EnumExtensions.cs @@ -3,81 +3,42 @@ namespace Umbraco.Core { /// - /// Provides extension methods to enums. + /// Provides extension methods to . /// public static class EnumExtensions { - // note: - // - no need to HasFlagExact, that's basically an == test - // - HasFlagAll cannot be named HasFlag because ext. methods never take priority over instance methods - /// - /// Determines whether a flag enum has all the specified values. + /// Determines whether all the flags/bits are set within the enum value. /// - /// - /// True when all bits set in are set in , though other bits may be set too. - /// This is the behavior of the original method. - /// - public static bool HasFlagAll(this T use, T uses) + /// The enum type. + /// The enum value. + /// The flags. + /// + /// true if all the flags/bits are set within the enum value; otherwise, false. + /// + [Obsolete("Use Enum.HasFlag() or bitwise operations (if performance is important) instead.")] + public static bool HasFlagAll(this T value, T flags) where T : Enum { - var num = Convert.ToUInt64(use); - var nums = Convert.ToUInt64(uses); - - return (num & nums) == nums; + return value.HasFlag(flags); } /// - /// Determines whether a flag enum has any of the specified values. + /// Determines whether any of the flags/bits are set within the enum value. /// - /// - /// True when at least one of the bits set in is set in . - /// - public static bool HasFlagAny(this T use, T uses) + /// The enum type. + /// The value. + /// The flags. + /// + /// true if any of the flags/bits are set within the enum value; otherwise, false. + /// + public static bool HasFlagAny(this T value, T flags) where T : Enum { - var num = Convert.ToUInt64(use); - var nums = Convert.ToUInt64(uses); + var v = Convert.ToUInt64(value); + var f = Convert.ToUInt64(flags); - return (num & nums) > 0; - } - - /// - /// Sets a flag of the given input enum - /// - /// - /// Enum to set flag of - /// Flag to set - /// A new enum with the flag set - public static T SetFlag(this T input, T flag) - where T : Enum - { - var i = Convert.ToUInt64(input); - var f = Convert.ToUInt64(flag); - - // bitwise OR to set flag f of enum i - var result = i | f; - - return (T)Enum.ToObject(typeof(T), result); - } - - /// - /// Unsets a flag of the given input enum - /// - /// - /// Enum to unset flag of - /// Flag to unset - /// A new enum with the flag unset - public static T UnsetFlag(this T input, T flag) - where T : Enum - { - var i = Convert.ToUInt64(input); - var f = Convert.ToUInt64(flag); - - // bitwise AND combined with bitwise complement to unset flag f of enum i - var result = i & ~f; - - return (T)Enum.ToObject(typeof(T), result); + return (v & f) > 0; } } } diff --git a/src/Umbraco.Core/Events/UserGroupWithUsers.cs b/src/Umbraco.Core/Events/UserGroupWithUsers.cs index b69650d33f..7d456a22ea 100644 --- a/src/Umbraco.Core/Events/UserGroupWithUsers.cs +++ b/src/Umbraco.Core/Events/UserGroupWithUsers.cs @@ -3,7 +3,7 @@ using Umbraco.Core.Models.Membership; namespace Umbraco.Core.Events { - internal class UserGroupWithUsers + public class UserGroupWithUsers { public UserGroupWithUsers(IUserGroup userGroup, IUser[] addedUsers, IUser[] removedUsers) { diff --git a/src/Umbraco.Core/HashGenerator.cs b/src/Umbraco.Core/HashGenerator.cs index 80cc3c3de4..58034f05d7 100644 --- a/src/Umbraco.Core/HashGenerator.cs +++ b/src/Umbraco.Core/HashGenerator.cs @@ -41,7 +41,7 @@ namespace Umbraco.Core internal void AddDateTime(DateTime d) { - _writer.Write(d.Ticks);; + _writer.Write(d.Ticks); } internal void AddString(string s) diff --git a/src/Umbraco.Core/IO/SystemDirectories.cs b/src/Umbraco.Core/IO/SystemDirectories.cs index d6fb63b0a1..b4688d2e9f 100644 --- a/src/Umbraco.Core/IO/SystemDirectories.cs +++ b/src/Umbraco.Core/IO/SystemDirectories.cs @@ -29,13 +29,13 @@ namespace Umbraco.Core.IO public static string MacroPartials => MvcViews + "/MacroPartials/"; - public static string Media => IOHelper.ReturnPath("umbracoMediaPath", "~/media"); + public static string Media => IOHelper.ReturnPath(Constants.AppSettings.MediaPath, "~/media"); - public static string Scripts => IOHelper.ReturnPath("umbracoScriptsPath", "~/scripts"); + public static string Scripts => IOHelper.ReturnPath(Constants.AppSettings.ScriptsPath, "~/scripts"); - public static string Css => IOHelper.ReturnPath("umbracoCssPath", "~/css"); + public static string Css => IOHelper.ReturnPath(Constants.AppSettings.CssPath, "~/css"); - public static string Umbraco => IOHelper.ReturnPath("umbracoPath", "~/umbraco"); + public static string Umbraco => IOHelper.ReturnPath(Constants.AppSettings.Path, "~/umbraco"); public static string Packages => Data + "/packages"; diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs index 94d8cfbc62..9bd26749ad 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs @@ -151,6 +151,8 @@ namespace Umbraco.Core.Migrations.Install _database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.Domains, Name = "Domains" }); _database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.KeyValues, Name = "KeyValues" }); _database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.Languages, Name = "Languages" }); + + _database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.MainDom, Name = "MainDom" }); } private void CreateContentTypeData() @@ -169,8 +171,8 @@ namespace Umbraco.Core.Migrations.Install private void CreateUserGroupData() { _database.Insert(Constants.DatabaseSchema.Tables.UserGroup, "id", false, new UserGroupDto { Id = 1, StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.AdminGroupAlias, Name = "Administrators", DefaultPermissions = "CADMOSKTPIURZ:5F7ï", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-medal" }); - _database.Insert(Constants.DatabaseSchema.Tables.UserGroup, "id", false, new UserGroupDto { Id = 2, StartMediaId = -1, StartContentId = -1, Alias = "writer", Name = "Writers", DefaultPermissions = "CAH:F", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-edit" }); - _database.Insert(Constants.DatabaseSchema.Tables.UserGroup, "id", false, new UserGroupDto { Id = 3, StartMediaId = -1, StartContentId = -1, Alias = "editor", Name = "Editors", DefaultPermissions = "CADMOSKTPUZ:5Fï", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-tools" }); + _database.Insert(Constants.DatabaseSchema.Tables.UserGroup, "id", false, new UserGroupDto { Id = 2, StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.WriterGroupAlias, Name = "Writers", DefaultPermissions = "CAH:F", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-edit" }); + _database.Insert(Constants.DatabaseSchema.Tables.UserGroup, "id", false, new UserGroupDto { Id = 3, StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.EditorGroupAlias, Name = "Editors", DefaultPermissions = "CADMOSKTPUZ:5Fï", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-tools" }); _database.Insert(Constants.DatabaseSchema.Tables.UserGroup, "id", false, new UserGroupDto { Id = 4, StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.TranslatorGroupAlias, Name = "Translators", DefaultPermissions = "AF", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-globe" }); _database.Insert(Constants.DatabaseSchema.Tables.UserGroup, "id", false, new UserGroupDto { Id = 5, StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.SensitiveDataGroupAlias, Name = "Sensitive data", DefaultPermissions = "", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-lock" }); } @@ -310,14 +312,27 @@ namespace Umbraco.Core.Migrations.Install private void CreateRelationTypeData() { var relationType = new RelationTypeDto { Id = 1, Alias = Constants.Conventions.RelationTypes.RelateDocumentOnCopyAlias, ChildObjectType = Constants.ObjectTypes.Document, ParentObjectType = Constants.ObjectTypes.Document, Dual = true, Name = Constants.Conventions.RelationTypes.RelateDocumentOnCopyName }; - relationType.UniqueId = (relationType.Alias + "____" + relationType.Name).ToGuid(); + relationType.UniqueId = CreateUniqueRelationTypeId(relationType.Alias, relationType.Name); _database.Insert(Constants.DatabaseSchema.Tables.RelationType, "id", false, relationType); relationType = new RelationTypeDto { Id = 2, Alias = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias, ChildObjectType = Constants.ObjectTypes.Document, ParentObjectType = Constants.ObjectTypes.Document, Dual = false, Name = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteName }; - relationType.UniqueId = (relationType.Alias + "____" + relationType.Name).ToGuid(); + relationType.UniqueId = CreateUniqueRelationTypeId(relationType.Alias, relationType.Name); _database.Insert(Constants.DatabaseSchema.Tables.RelationType, "id", false, relationType); relationType = new RelationTypeDto { Id = 3, Alias = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteAlias, ChildObjectType = Constants.ObjectTypes.Media, ParentObjectType = Constants.ObjectTypes.Media, Dual = false, Name = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteName }; - relationType.UniqueId = (relationType.Alias + "____" + relationType.Name).ToGuid(); + relationType.UniqueId = CreateUniqueRelationTypeId(relationType.Alias, relationType.Name); _database.Insert(Constants.DatabaseSchema.Tables.RelationType, "id", false, relationType); + + relationType = new RelationTypeDto { Id = 4, Alias = Constants.Conventions.RelationTypes.RelatedMediaAlias, ChildObjectType = null, ParentObjectType = null, Dual = false, Name = Constants.Conventions.RelationTypes.RelatedMediaName }; + relationType.UniqueId = CreateUniqueRelationTypeId(relationType.Alias, relationType.Name); + _database.Insert(Constants.DatabaseSchema.Tables.RelationType, "id", false, relationType); + + relationType = new RelationTypeDto { Id = 5, Alias = Constants.Conventions.RelationTypes.RelatedDocumentAlias, ChildObjectType = null, ParentObjectType = null, Dual = false, Name = Constants.Conventions.RelationTypes.RelatedDocumentName }; + relationType.UniqueId = CreateUniqueRelationTypeId(relationType.Alias, relationType.Name); + _database.Insert(Constants.DatabaseSchema.Tables.RelationType, "id", false, relationType); + } + + internal static Guid CreateUniqueRelationTypeId(string alias, string name) + { + return (alias + "____" + name).ToGuid(); } private void CreateKeyValueData() diff --git a/src/Umbraco.Core/Migrations/MigrationBase_Extra.cs b/src/Umbraco.Core/Migrations/MigrationBase_Extra.cs index bf07e4d08f..f4c6150073 100644 --- a/src/Umbraco.Core/Migrations/MigrationBase_Extra.cs +++ b/src/Umbraco.Core/Migrations/MigrationBase_Extra.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.SqlSyntax; @@ -84,10 +85,18 @@ namespace Umbraco.Core.Migrations protected void ReplaceColumn(string tableName, string currentName, string newName) { - AddColumn(tableName, newName, out var sqls); - Execute.Sql($"UPDATE {SqlSyntax.GetQuotedTableName(tableName)} SET {SqlSyntax.GetQuotedColumnName(newName)}={SqlSyntax.GetQuotedColumnName(currentName)}").Do(); - foreach (var sql in sqls) Execute.Sql(sql).Do(); - Delete.Column(currentName).FromTable(tableName).Do(); + if (DatabaseType.IsSqlCe()) + { + AddColumn(tableName, newName, out var sqls); + Execute.Sql($"UPDATE {SqlSyntax.GetQuotedTableName(tableName)} SET {SqlSyntax.GetQuotedColumnName(newName)}={SqlSyntax.GetQuotedColumnName(currentName)}").Do(); + foreach (var sql in sqls) Execute.Sql(sql).Do(); + Delete.Column(currentName).FromTable(tableName).Do(); + } + else + { + Execute.Sql(SqlSyntax.FormatColumnRename(tableName, currentName, newName)).Do(); + AlterColumn(tableName, newName); + } } protected bool TableExists(string tableName) diff --git a/src/Umbraco.Core/Migrations/Upgrade/Common/CreateKeysAndIndexes.cs b/src/Umbraco.Core/Migrations/Upgrade/Common/CreateKeysAndIndexes.cs index 29006c8811..4872e0019d 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/Common/CreateKeysAndIndexes.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/Common/CreateKeysAndIndexes.cs @@ -12,6 +12,7 @@ namespace Umbraco.Core.Migrations.Upgrade.Common { // remove those that may already have keys Delete.KeysAndIndexes(Constants.DatabaseSchema.Tables.KeyValue).Do(); + Delete.KeysAndIndexes(Constants.DatabaseSchema.Tables.PropertyData).Do(); // re-create *all* keys and indexes foreach (var x in DatabaseSchemaCreator.OrderedTables) diff --git a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs index 3b2005bef6..f0cfc08281 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs @@ -166,6 +166,7 @@ namespace Umbraco.Core.Migrations.Upgrade .As("{0576E786-5C30-4000-B969-302B61E90CA3}"); To("{48AD6CCD-C7A4-4305-A8AB-38728AD23FC5}"); + To("{DF470D86-E5CA-42AC-9780-9D28070E25F9}"); // finish migrating from v7 - recreate all keys and indexes To("{3F9764F5-73D0-4D45-8804-1240A66E43A2}"); @@ -183,9 +184,15 @@ namespace Umbraco.Core.Migrations.Upgrade To("{0372A42B-DECF-498D-B4D1-6379E907EB94}"); To("{5B1E0D93-F5A3-449B-84BA-65366B84E2D4}"); - // to 8.6.0 + // to 8.6.0... + To("{4759A294-9860-46BC-99F9-B4C975CAE580}"); + To("{0BC866BC-0665-487A-9913-0290BD0169AD}"); To("{3D67D2C8-5E65-47D0-A9E1-DC2EE0779D6B}"); To("{EE288A91-531B-4995-8179-1D62D9AA3E2E}"); + To("{2AB29964-02A1-474D-BD6B-72148D2A53A2}"); + + + To("{a78e3369-8ea3-40ec-ad3f-5f76929d2b20}"); //FINAL } diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddPackagesSectionAccess.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddPackagesSectionAccess.cs new file mode 100644 index 0000000000..d44e637a2c --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddPackagesSectionAccess.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 +{ + public class AddPackagesSectionAccess : MigrationBase + { + public AddPackagesSectionAccess(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + // Any user group which had access to the Developer section should have access to Packages + Database.Execute($@" + insert into {Constants.DatabaseSchema.Tables.UserGroup2App} + select userGroupId, '{Constants.Applications.Packages}' + from {Constants.DatabaseSchema.Tables.UserGroup2App} + where app='developer'"); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddTypedLabels.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddTypedLabels.cs index 309f8acbc3..66f9114370 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddTypedLabels.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddTypedLabels.cs @@ -75,8 +75,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 var labelPropertyTypes = Database.Fetch(Sql() .Select(x => x.Id, x => x.Alias) .From() - .Where(x => x.DataTypeId == Constants.DataTypes.LabelString) - ); + .Where(x => x.DataTypeId == Constants.DataTypes.LabelString)); var intPropertyAliases = new[] { Constants.Conventions.Media.Width, Constants.Conventions.Media.Height, Constants.Conventions.Member.FailedPasswordAttempts }; var bigintPropertyAliases = new[] { Constants.Conventions.Media.Bytes }; @@ -101,16 +100,18 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 foreach (var value in values) Database.Execute(Sql() .Update(u => u - .Set(x => x.IntegerValue, string.IsNullOrWhiteSpace(value.VarcharValue) ? (int?) null : int.Parse(value.VarcharValue, NumberStyles.Any, CultureInfo.InvariantCulture)) - .Set(x => x.TextValue, null)) + .Set(x => x.IntegerValue, string.IsNullOrWhiteSpace(value.VarcharValue) ? (int?)null : int.Parse(value.VarcharValue, NumberStyles.Any, CultureInfo.InvariantCulture)) + .Set(x => x.TextValue, null) + .Set(x => x.VarcharValue, null)) .Where(x => x.Id == value.Id)); values = Database.Fetch(Sql().Select(x => x.Id, x => x.VarcharValue).From().WhereIn(x => x.PropertyTypeId, dtPropertyTypes)); foreach (var value in values) Database.Execute(Sql() .Update(u => u - .Set(x => x.DateValue, string.IsNullOrWhiteSpace(value.VarcharValue) ? (DateTime?) null : DateTime.Parse(value.VarcharValue, CultureInfo.InvariantCulture, DateTimeStyles.None)) - .Set(x => x.TextValue, null)) + .Set(x => x.DateValue, string.IsNullOrWhiteSpace(value.VarcharValue) ? (DateTime?)null : DateTime.Parse(value.VarcharValue, CultureInfo.InvariantCulture, DateTimeStyles.None)) + .Set(x => x.TextValue, null) + .Set(x => x.VarcharValue, null)) .Where(x => x.Id == value.Id)); // anything that's custom... ppl will have to figure it out manually, there isn't much we can do about it diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/ContentVariationMigration.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/ContentVariationMigration.cs index 84dd393b0d..eabbd34b08 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/ContentVariationMigration.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/ContentVariationMigration.cs @@ -4,8 +4,10 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using NPoco; +using Umbraco.Core.Migrations.Upgrade.V_8_0_0.Models; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 @@ -47,14 +49,14 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 } } - var propertyTypes = Database.Fetch(Sql().Select().From()); + var propertyTypes = Database.Fetch(Sql().Select().From()); foreach (var dto in propertyTypes) { dto.Variations = GetNewValue(dto.Variations); Database.Update(dto); } - var contentTypes = Database.Fetch(Sql().Select().From()); + var contentTypes = Database.Fetch(Sql().Select().From()); foreach (var dto in contentTypes) { dto.Variations = GetNewValue(dto.Variations); @@ -62,57 +64,11 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 } } - // we *need* to use this private DTO here, which does *not* have extra properties, which would kill the migration + // we *need* to use these private DTOs here, which does *not* have extra properties, which would kill the migration - [TableName(TableName)] - [PrimaryKey("pk")] - [ExplicitColumns] - private class ContentTypeDto - { - public const string TableName = Constants.DatabaseSchema.Tables.ContentType; + - [Column("pk")] - [PrimaryKeyColumn(IdentitySeed = 535)] - public int PrimaryKey { get; set; } - - [Column("nodeId")] - [ForeignKey(typeof(NodeDto))] - [Index(IndexTypes.UniqueNonClustered, Name = "IX_cmsContentType")] - public int NodeId { get; set; } - - [Column("alias")] - [NullSetting(NullSetting = NullSettings.Null)] - public string Alias { get; set; } - - [Column("icon")] - [Index(IndexTypes.NonClustered)] - [NullSetting(NullSetting = NullSettings.Null)] - public string Icon { get; set; } - - [Column("thumbnail")] - [Constraint(Default = "folder.png")] - public string Thumbnail { get; set; } - - [Column("description")] - [NullSetting(NullSetting = NullSettings.Null)] - [Length(1500)] - public string Description { get; set; } - - [Column("isContainer")] - [Constraint(Default = "0")] - public bool IsContainer { get; set; } - - [Column("allowAtRoot")] - [Constraint(Default = "0")] - public bool AllowAtRoot { get; set; } - - [Column("variations")] - [Constraint(Default = "1" /*ContentVariation.InvariantNeutral*/)] - public byte Variations { get; set; } - - [ResultColumn] - public NodeDto NodeDto { get; set; } - } + } } diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/ConvertRelatedLinksToMultiUrlPicker.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/ConvertRelatedLinksToMultiUrlPicker.cs index 1956876402..1e327346cf 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/ConvertRelatedLinksToMultiUrlPicker.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/ConvertRelatedLinksToMultiUrlPicker.cs @@ -2,8 +2,7 @@ using System.Linq; using System.Runtime.Serialization; using Newtonsoft.Json; -using Umbraco.Core.Logging; -using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Migrations.Upgrade.V_8_0_0.Models; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Dtos; @@ -34,11 +33,11 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 } var sqlPropertyTpes = Sql() - .Select() - .From() - .Where(x => dataTypeIds.Contains(x.DataTypeId)); + .Select() + .From() + .Where(x => dataTypeIds.Contains(x.DataTypeId)); - var propertyTypeIds = Database.Fetch(sqlPropertyTpes).Select(x => x.Id).ToList(); + var propertyTypeIds = Database.Fetch(sqlPropertyTpes).Select(x => x.Id).ToList(); if (propertyTypeIds.Count == 0) return; diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/MergeDateAndDateTimePropertyEditor.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/MergeDateAndDateTimePropertyEditor.cs index 0d451e8460..37a83bdd67 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/MergeDateAndDateTimePropertyEditor.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/MergeDateAndDateTimePropertyEditor.cs @@ -32,7 +32,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 if (string.IsNullOrEmpty(dataType.Configuration)) { config.Format = "YYYY-MM-DD"; - }; + } } catch (Exception ex) { diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/Models/ContentTypeDto80.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/Models/ContentTypeDto80.cs new file mode 100644 index 0000000000..bc41e5e32a --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/Models/ContentTypeDto80.cs @@ -0,0 +1,63 @@ +using NPoco; +using Umbraco.Core.Persistence.DatabaseAnnotations; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.Models +{ + + /// + /// Snapshot of the as it was at version 8.0 + /// + /// + /// This is required during migrations the schema of this table changed and running SQL against the new table would result in errors + /// + [TableName(TableName)] + [PrimaryKey("pk")] + [ExplicitColumns] + internal class ContentTypeDto80 + { + public const string TableName = Constants.DatabaseSchema.Tables.ContentType; + + [Column("pk")] + [PrimaryKeyColumn(IdentitySeed = 535)] + public int PrimaryKey { get; set; } + + [Column("nodeId")] + [ForeignKey(typeof(NodeDto))] + [Index(IndexTypes.UniqueNonClustered, Name = "IX_cmsContentType")] + public int NodeId { get; set; } + + [Column("alias")] + [NullSetting(NullSetting = NullSettings.Null)] + public string Alias { get; set; } + + [Column("icon")] + [Index(IndexTypes.NonClustered)] + [NullSetting(NullSetting = NullSettings.Null)] + public string Icon { get; set; } + + [Column("thumbnail")] + [Constraint(Default = "folder.png")] + public string Thumbnail { get; set; } + + [Column("description")] + [NullSetting(NullSetting = NullSettings.Null)] + [Length(1500)] + public string Description { get; set; } + + [Column("isContainer")] + [Constraint(Default = "0")] + public bool IsContainer { get; set; } + + [Column("allowAtRoot")] + [Constraint(Default = "0")] + public bool AllowAtRoot { get; set; } + + [Column("variations")] + [Constraint(Default = "1" /*ContentVariation.InvariantNeutral*/)] + public byte Variations { get; set; } + + [ResultColumn] + public NodeDto NodeDto { get; set; } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/Models/PropertyDataDto80.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/Models/PropertyDataDto80.cs new file mode 100644 index 0000000000..f0a36b223d --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/Models/PropertyDataDto80.cs @@ -0,0 +1,142 @@ +using NPoco; +using System; +using Umbraco.Core.Persistence.DatabaseAnnotations; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.Models +{ + /// + /// Snapshot of the as it was at version 8.0 + /// + /// + /// This is required during migrations the schema of this table changed and running SQL against the new table would result in errors + /// + [TableName(TableName)] + [PrimaryKey("id")] + [ExplicitColumns] + internal class PropertyDataDto80 + { + public const string TableName = Constants.DatabaseSchema.Tables.PropertyData; + public const int VarcharLength = 512; + public const int SegmentLength = 256; + + private decimal? _decimalValue; + + // pk, not used at the moment (never updating) + [Column("id")] + [PrimaryKeyColumn] + public int Id { get; set; } + + [Column("versionId")] + [ForeignKey(typeof(ContentVersionDto))] + [Index(IndexTypes.UniqueNonClustered, Name = "IX_" + TableName + "_VersionId", ForColumns = "versionId,propertyTypeId,languageId,segment")] + public int VersionId { get; set; } + + [Column("propertyTypeId")] + [ForeignKey(typeof(PropertyTypeDto80))] + [Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_PropertyTypeId")] + public int PropertyTypeId { get; set; } + + [Column("languageId")] + [ForeignKey(typeof(LanguageDto))] + [Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_LanguageId")] + [NullSetting(NullSetting = NullSettings.Null)] + public int? LanguageId { get; set; } + + [Column("segment")] + [Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_Segment")] + [NullSetting(NullSetting = NullSettings.Null)] + [Length(SegmentLength)] + public string Segment { get; set; } + + [Column("intValue")] + [NullSetting(NullSetting = NullSettings.Null)] + public int? IntegerValue { get; set; } + + [Column("decimalValue")] + [NullSetting(NullSetting = NullSettings.Null)] + public decimal? DecimalValue + { + get => _decimalValue; + set => _decimalValue = value?.Normalize(); + } + + [Column("dateValue")] + [NullSetting(NullSetting = NullSettings.Null)] + public DateTime? DateValue { get; set; } + + [Column("varcharValue")] + [NullSetting(NullSetting = NullSettings.Null)] + [Length(VarcharLength)] + public string VarcharValue { get; set; } + + [Column("textValue")] + [NullSetting(NullSetting = NullSettings.Null)] + [SpecialDbType(SpecialDbTypes.NTEXT)] + public string TextValue { get; set; } + + [ResultColumn] + [Reference(ReferenceType.OneToOne, ColumnName = "PropertyTypeId")] + public PropertyTypeDto80 PropertyTypeDto { get; set; } + + [Ignore] + public object Value + { + get + { + if (IntegerValue.HasValue) + return IntegerValue.Value; + + if (DecimalValue.HasValue) + return DecimalValue.Value; + + if (DateValue.HasValue) + return DateValue.Value; + + if (!string.IsNullOrEmpty(VarcharValue)) + return VarcharValue; + + if (!string.IsNullOrEmpty(TextValue)) + return TextValue; + + return null; + } + } + + public PropertyDataDto80 Clone(int versionId) + { + return new PropertyDataDto80 + { + VersionId = versionId, + PropertyTypeId = PropertyTypeId, + LanguageId = LanguageId, + Segment = Segment, + IntegerValue = IntegerValue, + DecimalValue = DecimalValue, + DateValue = DateValue, + VarcharValue = VarcharValue, + TextValue = TextValue, + PropertyTypeDto = PropertyTypeDto + }; + } + + protected bool Equals(PropertyDataDto other) + { + return Id == other.Id; + } + + public override bool Equals(object other) + { + return + !ReferenceEquals(null, other) // other is not null + && (ReferenceEquals(this, other) // and either ref-equals, or same id + || other is PropertyDataDto pdata && pdata.Id == Id); + } + + public override int GetHashCode() + { + // ReSharper disable once NonReadonlyMemberInGetHashCode + return Id; + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/Models/PropertyTypeDto80.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/Models/PropertyTypeDto80.cs new file mode 100644 index 0000000000..6e2bd7371a --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/Models/PropertyTypeDto80.cs @@ -0,0 +1,80 @@ +using NPoco; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Umbraco.Core.Persistence.DatabaseAnnotations; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.Models +{ + /// + /// Snapshot of the as it was at version 8.0 + /// + /// + /// This is required during migrations before 8.6 since the schema has changed and running SQL against the new table would result in errors + /// + [TableName(Constants.DatabaseSchema.Tables.PropertyType)] + [PrimaryKey("id")] + [ExplicitColumns] + internal class PropertyTypeDto80 + { + [Column("id")] + [PrimaryKeyColumn(IdentitySeed = 50)] + public int Id { get; set; } + + [Column("dataTypeId")] + [ForeignKey(typeof(DataTypeDto), Column = "nodeId")] + public int DataTypeId { get; set; } + + [Column("contentTypeId")] + [ForeignKey(typeof(ContentTypeDto), Column = "nodeId")] + public int ContentTypeId { get; set; } + + [Column("propertyTypeGroupId")] + [NullSetting(NullSetting = NullSettings.Null)] + [ForeignKey(typeof(PropertyTypeGroupDto))] + public int? PropertyTypeGroupId { get; set; } + + [Index(IndexTypes.NonClustered, Name = "IX_cmsPropertyTypeAlias")] + [Column("Alias")] + public string Alias { get; set; } + + [Column("Name")] + [NullSetting(NullSetting = NullSettings.Null)] + public string Name { get; set; } + + [Column("sortOrder")] + [Constraint(Default = "0")] + public int SortOrder { get; set; } + + [Column("mandatory")] + [Constraint(Default = "0")] + public bool Mandatory { get; set; } + + [Column("validationRegExp")] + [NullSetting(NullSetting = NullSettings.Null)] + public string ValidationRegExp { get; set; } + + [Column("Description")] + [NullSetting(NullSetting = NullSettings.Null)] + [Length(2000)] + public string Description { get; set; } + + [Column("variations")] + [Constraint(Default = "1" /*ContentVariation.InvariantNeutral*/)] + public byte Variations { get; set; } + + [ResultColumn] + [Reference(ReferenceType.OneToOne, ColumnName = "DataTypeId")] + 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; } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RenameMediaVersionTable.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RenameMediaVersionTable.cs index b60923fcba..2b27bdafe8 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RenameMediaVersionTable.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RenameMediaVersionTable.cs @@ -1,4 +1,5 @@ -using Umbraco.Core.Persistence.Dtos; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 { @@ -17,14 +18,24 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 AddColumn("id", out var sqls); - // SQLCE does not support UPDATE...FROM - var temp2 = Database.Fetch($@"SELECT v.versionId, v.id + if (Database.DatabaseType.IsSqlCe()) + { + // SQLCE does not support UPDATE...FROM + var versions = Database.Fetch($@"SELECT v.versionId, v.id FROM cmsContentVersion v JOIN umbracoNode n on v.contentId=n.id WHERE n.nodeObjectType='{Constants.ObjectTypes.Media}'"); - foreach (var t in temp2) - Execute.Sql($"UPDATE {Constants.DatabaseSchema.Tables.MediaVersion} SET id={t.id} WHERE versionId='{t.versionId}'").Do(); - + foreach (var t in versions) + Execute.Sql($"UPDATE {Constants.DatabaseSchema.Tables.MediaVersion} SET id={t.id} WHERE versionId='{t.versionId}'").Do(); + } + else + { + Database.Execute($@"UPDATE {Constants.DatabaseSchema.Tables.MediaVersion} SET id=v.id +FROM {Constants.DatabaseSchema.Tables.MediaVersion} m +JOIN cmsContentVersion v on m.versionId = v.versionId +JOIN umbracoNode n on v.contentId=n.id +WHERE n.nodeObjectType='{Constants.ObjectTypes.Media}'"); + } foreach (var sql in sqls) Execute.Sql(sql).Do(); diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/VariantsMigration.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/VariantsMigration.cs index 8c60d30680..adc451ef6a 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/VariantsMigration.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/VariantsMigration.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; using Umbraco.Core.Migrations.Install; @@ -19,6 +20,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 public override void Migrate() { MigratePropertyData(); + CreatePropertyDataIndexes(); MigrateContentAndPropertyTypes(); MigrateContent(); MigrateVersions(); @@ -74,10 +76,20 @@ HAVING COUNT(v2.id) <> 1").Any()) { Alter.Table(PreTables.PropertyData).AddColumn("versionId2").AsInt32().Nullable().Do(); - // SQLCE does not support UPDATE...FROM - var temp = Database.Fetch($"SELECT id, versionId FROM {PreTables.ContentVersion}"); - foreach (var t in temp) - Database.Execute($"UPDATE {PreTables.PropertyData} SET versionId2=@v2 WHERE versionId=@v1", new { v1 = t.versionId, v2 = t.id }); + if (Database.DatabaseType.IsSqlCe()) + { + // SQLCE does not support UPDATE...FROM + var versions = Database.Fetch($"SELECT id, versionId FROM {PreTables.ContentVersion}"); + foreach (var t in versions) + Database.Execute($"UPDATE {PreTables.PropertyData} SET versionId2=@v2 WHERE versionId=@v1", new { v1 = t.versionId, v2 = t.id }); + } + else + { + Database.Execute($@"UPDATE {PreTables.PropertyData} SET versionId2={PreTables.ContentVersion}.id +FROM {PreTables.ContentVersion} +INNER JOIN {PreTables.PropertyData} ON {PreTables.ContentVersion}.versionId = {PreTables.PropertyData}.versionId"); + } + Delete.Column("versionId").FromTable(PreTables.PropertyData).Do(); ReplaceColumn(PreTables.PropertyData, "versionId2", "versionId"); } @@ -90,6 +102,22 @@ HAVING COUNT(v2.id) <> 1").Any()) Rename.Table(PreTables.PropertyData).To(Constants.DatabaseSchema.Tables.PropertyData).Do(); } + private void CreatePropertyDataIndexes() + { + // Creates a temporary index on umbracoPropertyData to speed up other migrations which update property values. + // It will be removed in CreateKeysAndIndexes before the normal indexes for the table are created + var tableDefinition = Persistence.DatabaseModelDefinitions.DefinitionFactory.GetTableDefinition(typeof(PropertyDataDto), SqlSyntax); + Execute.Sql(SqlSyntax.FormatPrimaryKey(tableDefinition)).Do(); + Create.Index("IX_umbracoPropertyData_Temp").OnTable(PropertyDataDto.TableName) + .WithOptions().Unique() + .WithOptions().NonClustered() + .OnColumn("versionId").Ascending() + .OnColumn("propertyTypeId").Ascending() + .OnColumn("languageId").Ascending() + .OnColumn("segment").Ascending() + .Do(); + } + private void MigrateContentAndPropertyTypes() { if (!ColumnExists(PreTables.ContentType, "variations")) @@ -153,22 +181,40 @@ HAVING COUNT(v2.id) <> 1").Any()) ReplaceColumn(PreTables.ContentVersion, "ContentId", "nodeId"); // populate contentVersion text, current and userId columns for documents - // SQLCE does not support UPDATE...FROM - var temp1 = Database.Fetch($"SELECT versionId, text, newest, documentUser FROM {PreTables.Document}"); - foreach (var t in temp1) - Database.Execute($@"UPDATE {PreTables.ContentVersion} SET text=@text, {SqlSyntax.GetQuotedColumnName("current")}=@current, userId=@userId WHERE versionId=@versionId", - new { text = t.text, current = t.newest, userId=t.documentUser, versionId=t.versionId }); + if (Database.DatabaseType.IsSqlCe()) + { + // SQLCE does not support UPDATE...FROM + var documents = Database.Fetch($"SELECT versionId, text, published, newest, documentUser FROM {PreTables.Document}"); + foreach (var t in documents) + Database.Execute($@"UPDATE {PreTables.ContentVersion} SET text=@text, {SqlSyntax.GetQuotedColumnName("current")}=@current, userId=@userId WHERE versionId=@versionId", + new { text = t.text, current = t.newest && !t.published, userId = t.documentUser, versionId = t.versionId }); + } + else + { + Database.Execute($@"UPDATE {PreTables.ContentVersion} SET text=d.text, {SqlSyntax.GetQuotedColumnName("current")}=(d.newest & ~d.published), userId=d.documentUser +FROM {PreTables.ContentVersion} v INNER JOIN {PreTables.Document} d ON d.versionId = v.versionId"); + } // populate contentVersion text and current columns for non-documents, userId is default - // SQLCE does not support UPDATE...FROM - var temp2 = Database.Fetch($@"SELECT cver.versionId, n.text + if (Database.DatabaseType.IsSqlCe()) + { + // SQLCE does not support UPDATE...FROM + var otherContent = Database.Fetch($@"SELECT cver.versionId, n.text FROM {PreTables.ContentVersion} cver JOIN {SqlSyntax.GetQuotedTableName(Constants.DatabaseSchema.Tables.Node)} n ON cver.nodeId=n.id WHERE cver.versionId NOT IN (SELECT versionId FROM {SqlSyntax.GetQuotedTableName(PreTables.Document)})"); - foreach (var t in temp2) - Database.Execute($@"UPDATE {PreTables.ContentVersion} SET text=@text, {SqlSyntax.GetQuotedColumnName("current")}=1, userId=0 WHERE versionId=@versionId", - new { text = t.text, versionId=t.versionId }); + foreach (var t in otherContent) + Database.Execute($@"UPDATE {PreTables.ContentVersion} SET text=@text, {SqlSyntax.GetQuotedColumnName("current")}=1, userId=0 WHERE versionId=@versionId", + new { text = t.text, versionId = t.versionId }); + } + else + { + Database.Execute($@"UPDATE {PreTables.ContentVersion} SET text=n.text, {SqlSyntax.GetQuotedColumnName("current")}=1, userId=0 +FROM {PreTables.ContentVersion} cver +JOIN {SqlSyntax.GetQuotedTableName(Constants.DatabaseSchema.Tables.Node)} n ON cver.nodeId=n.id +WHERE cver.versionId NOT IN (SELECT versionId FROM {SqlSyntax.GetQuotedTableName(PreTables.Document)})"); + } // create table Create.Table(withoutKeysAndIndexes: true).Do(); @@ -179,36 +225,42 @@ SELECT cver.id, doc.templateId, doc.published FROM {SqlSyntax.GetQuotedTableName(PreTables.ContentVersion)} cver JOIN {SqlSyntax.GetQuotedTableName(PreTables.Document)} doc ON doc.nodeId=cver.nodeId AND doc.versionId=cver.versionId"); + // need to add extra rows for where published=newest // 'cos INSERT above has inserted the 'published' document version // and v8 always has a 'edited' document version too - var temp3 = Database.Fetch($@"SELECT doc.nodeId, doc.updateDate, doc.documentUser, doc.text, doc.templateId, cver.id versionId + Database.Execute($@" +INSERT INTO {SqlSyntax.GetQuotedTableName(PreTables.ContentVersion)} (nodeId, versionId, versionDate, userId, {SqlSyntax.GetQuotedColumnName("current")}, text) +SELECT doc.nodeId, NEWID(), doc.updateDate, doc.documentUser, 1, doc.text FROM {SqlSyntax.GetQuotedTableName(PreTables.Document)} doc JOIN {SqlSyntax.GetQuotedTableName(PreTables.ContentVersion)} cver ON doc.nodeId=cver.nodeId AND doc.versionId=cver.versionId WHERE doc.newest=1 AND doc.published=1"); - var getIdentity = "@@@@IDENTITY"; - foreach (var t in temp3) - { - Database.Execute($@"INSERT INTO {SqlSyntax.GetQuotedTableName(PreTables.ContentVersion)} (nodeId, versionId, versionDate, userId, {SqlSyntax.GetQuotedColumnName("current")}, text) -VALUES (@nodeId, @versionId, @versionDate, @userId, 1, @text)", new { nodeId=t.nodeId, versionId=Guid.NewGuid(), versionDate=t.updateDate, userId=t.documentUser, text=t.text }); - var id = Database.ExecuteScalar("SELECT " + getIdentity); - Database.Execute($"UPDATE {SqlSyntax.GetQuotedTableName(PreTables.ContentVersion)} SET {SqlSyntax.GetQuotedColumnName("current")}=0 WHERE nodeId=@0 AND id<>@1", (int) t.nodeId, id); - Database.Execute($@"INSERT INTO {SqlSyntax.GetQuotedTableName(Constants.DatabaseSchema.Tables.DocumentVersion)} (id, templateId, published) -VALUES (@id, @templateId, 0)", new { id=id, templateId=t.templateId }); - var versionId = (int) t.versionId; - var pdatas = Database.Fetch(Sql().Select().From().Where(x => x.VersionId == versionId)); - foreach (var pdata in pdatas) - { - pdata.VersionId = id; - Database.Insert(pdata); - } - } + Database.Execute($@" +INSERT INTO {SqlSyntax.GetQuotedTableName(Constants.DatabaseSchema.Tables.DocumentVersion)} (id, templateId, published) +SELECT cverNew.id, doc.templateId, 0 +FROM {SqlSyntax.GetQuotedTableName(PreTables.Document)} doc +JOIN {SqlSyntax.GetQuotedTableName(PreTables.ContentVersion)} cverNew ON doc.nodeId = cverNew.nodeId +WHERE doc.newest=1 AND doc.published=1 AND cverNew.{SqlSyntax.GetQuotedColumnName("current")} = 1"); + + Database.Execute($@" +INSERT INTO {SqlSyntax.GetQuotedTableName(PropertyDataDto.TableName)} (propertytypeid,languageId,segment,textValue,varcharValue,decimalValue,intValue,dateValue,versionId) +SELECT propertytypeid,languageId,segment,textValue,varcharValue,decimalValue,intValue,dateValue,cverNew.id +FROM {SqlSyntax.GetQuotedTableName(PreTables.Document)} doc +JOIN {SqlSyntax.GetQuotedTableName(PreTables.ContentVersion)} cver ON doc.nodeId=cver.nodeId AND doc.versionId=cver.versionId +JOIN {SqlSyntax.GetQuotedTableName(PreTables.ContentVersion)} cverNew ON doc.nodeId = cverNew.nodeId +JOIN {SqlSyntax.GetQuotedTableName(PropertyDataDto.TableName)} pd ON pd.versionId=cver.id +WHERE doc.newest=1 AND doc.published=1 AND cverNew.{SqlSyntax.GetQuotedColumnName("current")} = 1"); + // reduce document to 1 row per content Database.Execute($@"DELETE FROM {PreTables.Document} WHERE versionId NOT IN (SELECT (versionId) FROM {PreTables.ContentVersion} WHERE {SqlSyntax.GetQuotedColumnName("current")} = 1) AND (published<>1 OR newest<>1)"); + // ensure that documents with a published version are marked as published + Database.Execute($@"UPDATE {PreTables.Document} SET published=1 WHERE nodeId IN ( +SELECT nodeId FROM {PreTables.ContentVersion} cv INNER JOIN {Constants.DatabaseSchema.Tables.DocumentVersion} dv ON dv.id = cv.id WHERE dv.published=1)"); + // drop some document columns Delete.Column("text").FromTable(PreTables.Document).Do(); Delete.Column("templateId").FromTable(PreTables.Document).Do(); @@ -223,7 +275,7 @@ WHERE versionId NOT IN (SELECT (versionId) FROM {PreTables.ContentVersion} WHERE if (!ColumnExists(PreTables.Document, "edited")) { AddColumn(PreTables.Document, "edited", out var sqls); - Database.Execute($"UPDATE {SqlSyntax.GetQuotedTableName(PreTables.Document)} SET edited=0"); + Database.Execute($"UPDATE {SqlSyntax.GetQuotedTableName(PreTables.Document)} SET edited=~published"); foreach (var sql in sqls) Database.Execute(sql); } @@ -240,11 +292,15 @@ JOIN {Constants.DatabaseSchema.Tables.PropertyData} v1 ON cv1.id=v1.versionId JOIN {PreTables.ContentVersion} cv2 ON n.id=cv2.nodeId JOIN {Constants.DatabaseSchema.Tables.DocumentVersion} dv ON cv2.id=dv.id AND dv.published=1 JOIN {Constants.DatabaseSchema.Tables.PropertyData} v2 ON cv2.id=v2.versionId -WHERE v1.propertyTypeId=v2.propertyTypeId AND v1.languageId=v2.languageId AND v1.segment=v2.segment"); +WHERE v1.propertyTypeId=v2.propertyTypeId +AND (v1.languageId=v2.languageId OR (v1.languageId IS NULL AND v2.languageId IS NULL)) +AND (v1.segment=v2.segment OR (v1.segment IS NULL AND v2.segment IS NULL))"); + var updatedIds = new HashSet(); foreach (var t in temp) if (t.intValue1 != t.intValue2 || t.decimalValue1 != t.decimalValue2 || t.dateValue1 != t.dateValue2 || t.varcharValue1 != t.varcharValue2 || t.textValue1 != t.textValue2) - Database.Execute("UPDATE {SqlSyntax.GetQuotedTableName(PreTables.Document)} SET edited=1 WHERE nodeId=@nodeIdd", new { t.id }); + if (updatedIds.Add((int)t.id)) + Database.Execute($"UPDATE {SqlSyntax.GetQuotedTableName(PreTables.Document)} SET edited=1 WHERE nodeId=@nodeId", new { nodeId = t.id }); // drop more columns Delete.Column("versionId").FromTable(PreTables.ContentVersion).Do(); diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_1_0/ConvertTinyMceAndGridMediaUrlsToLocalLink.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_1_0/ConvertTinyMceAndGridMediaUrlsToLocalLink.cs index b68aa23d78..052b72ca26 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_1_0/ConvertTinyMceAndGridMediaUrlsToLocalLink.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_1_0/ConvertTinyMceAndGridMediaUrlsToLocalLink.cs @@ -5,6 +5,7 @@ using System.Text.RegularExpressions; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Umbraco.Core.Migrations.PostMigrations; +using Umbraco.Core.Migrations.Upgrade.V_8_0_0.Models; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Services; @@ -27,15 +28,15 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_1_0 RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); var sqlPropertyData = Sql() - .Select(r => r.Select(x => x.PropertyTypeDto, r1 => r1.Select(x => x.DataTypeDto))) - .From() - .InnerJoin().On((left, right) => left.PropertyTypeId == right.Id) - .InnerJoin().On((left, right) => left.DataTypeId == right.NodeId) + .Select(r => r.Select(x => x.PropertyTypeDto, r1 => r1.Select(x => x.DataTypeDto))) + .From() + .InnerJoin().On((left, right) => left.PropertyTypeId == right.Id) + .InnerJoin().On((left, right) => left.DataTypeId == right.NodeId) .Where(x => x.EditorAlias == Constants.PropertyEditors.Aliases.TinyMce || x.EditorAlias == Constants.PropertyEditors.Aliases.Grid); - var properties = Database.Fetch(sqlPropertyData); + var properties = Database.Fetch(sqlPropertyData); var exceptions = new List(); foreach (var property in properties) @@ -43,6 +44,8 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_1_0 var value = property.TextValue; if (string.IsNullOrWhiteSpace(value)) continue; + + bool propertyChanged = false; if (property.PropertyTypeDto.DataTypeDto.EditorAlias == Constants.PropertyEditors.Aliases.Grid) { try @@ -55,7 +58,8 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_1_0 var controlValue = control["value"]; if (controlValue?.Type == JTokenType.String) { - control["value"] = UpdateMediaUrls(mediaLinkPattern, controlValue.Value()); + control["value"] = UpdateMediaUrls(mediaLinkPattern, controlValue.Value(), out var controlChanged); + propertyChanged |= controlChanged; } } @@ -76,10 +80,11 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_1_0 } else { - property.TextValue = UpdateMediaUrls(mediaLinkPattern, value); + property.TextValue = UpdateMediaUrls(mediaLinkPattern, value, out propertyChanged); } - Database.Update(property); + if (propertyChanged) + Database.Update(property); } @@ -91,10 +96,14 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_1_0 Context.AddPostMigration(); } - private string UpdateMediaUrls(Regex mediaLinkPattern, string value) + private string UpdateMediaUrls(Regex mediaLinkPattern, string value, out bool changed) { - return mediaLinkPattern.Replace(value, match => + bool matched = false; + + var result = mediaLinkPattern.Replace(value, match => { + matched = true; + // match groups: // - 1 = from the beginning of the a tag until href attribute value begins // - 2 = the href attribute value excluding the querystring (if present) @@ -106,6 +115,10 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_1_0 ? match.Value : $"{match.Groups[1].Value}/{{localLink:{media.GetUdi()}}}{match.Groups[3].Value}"; }); + + changed = matched; + + return result; } } } diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_6_0/AddMainDomLock.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_6_0/AddMainDomLock.cs new file mode 100644 index 0000000000..6ca493ac7e --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_6_0/AddMainDomLock.cs @@ -0,0 +1,16 @@ +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_6_0 +{ + public class AddMainDomLock : MigrationBase + { + public AddMainDomLock(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + Database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.MainDom, Name = "MainDom" }); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_6_0/AddNewRelationTypes.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_6_0/AddNewRelationTypes.cs new file mode 100644 index 0000000000..2e2e00a9bc --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_6_0/AddNewRelationTypes.cs @@ -0,0 +1,33 @@ +using Umbraco.Core.Migrations.Install; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_6_0 +{ + /// + /// Ensures the new relation types are created + /// + public class AddNewRelationTypes : MigrationBase + { + public AddNewRelationTypes(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + CreateRelation( + Constants.Conventions.RelationTypes.RelatedMediaAlias, + Constants.Conventions.RelationTypes.RelatedMediaName); + + CreateRelation( + Constants.Conventions.RelationTypes.RelatedDocumentAlias, + Constants.Conventions.RelationTypes.RelatedDocumentName); + } + + private void CreateRelation(string alias, string name) + { + var uniqueId = DatabaseDataCreator.CreateUniqueRelationTypeId(alias ,name); //this is the same as how it installs so everything is consistent + Insert.IntoTable(Constants.DatabaseSchema.Tables.RelationType) + .Row(new { typeUniqueId = uniqueId, dual = 0, name, alias }) + .Do(); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_6_0/AddPropertyTypeValidationMessageColumns.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_6_0/AddPropertyTypeValidationMessageColumns.cs index 30eb30109e..f44695da69 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_6_0/AddPropertyTypeValidationMessageColumns.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_6_0/AddPropertyTypeValidationMessageColumns.cs @@ -3,6 +3,7 @@ using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Migrations.Upgrade.V_8_6_0 { + public class AddPropertyTypeValidationMessageColumns : MigrationBase { public AddPropertyTypeValidationMessageColumns(IMigrationContext context) diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_6_0/MissingContentVersionsIndexes.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_6_0/MissingContentVersionsIndexes.cs index 75de01dd7f..c744409c2f 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_6_0/MissingContentVersionsIndexes.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_6_0/MissingContentVersionsIndexes.cs @@ -4,21 +4,30 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_6_0 { public class MissingContentVersionsIndexes : MigrationBase { + private const string IndexName = "IX_" + ContentVersionDto.TableName + "_NodeId"; + public MissingContentVersionsIndexes(IMigrationContext context) : base(context) { } public override void Migrate() { - Create - .Index("IX_" + ContentVersionDto.TableName + "_NodeId") - .OnTable(ContentVersionDto.TableName) - .OnColumn("nodeId") - .Ascending() - .OnColumn("current") - .Ascending() - .WithOptions().NonClustered() - .Do(); + // We must check before we create an index because if we are upgrading from v7 we force re-create all + // indexes in the whole DB and then this would throw + + if (!IndexExists(IndexName)) + { + Create + .Index(IndexName) + .OnTable(ContentVersionDto.TableName) + .OnColumn("nodeId") + .Ascending() + .OnColumn("current") + .Ascending() + .WithOptions().NonClustered() + .Do(); + } + } } } diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_6_0/UpdateRelationTypeTable.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_6_0/UpdateRelationTypeTable.cs new file mode 100644 index 0000000000..c79f43d20f --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_6_0/UpdateRelationTypeTable.cs @@ -0,0 +1,38 @@ +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_6_0 +{ + + public class UpdateRelationTypeTable : MigrationBase + { + public UpdateRelationTypeTable(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + + Alter.Table(Constants.DatabaseSchema.Tables.RelationType).AlterColumn("parentObjectType").AsGuid().Nullable().Do(); + Alter.Table(Constants.DatabaseSchema.Tables.RelationType).AlterColumn("childObjectType").AsGuid().Nullable().Do(); + + //TODO: We have to update this field to ensure it's not null, we can just copy across the name since that is not nullable + + //drop index before we can alter the column + if (IndexExists("IX_umbracoRelationType_alias")) + Delete + .Index("IX_umbracoRelationType_alias") + .OnTable(Constants.DatabaseSchema.Tables.RelationType) + .Do(); + //change the column to non nullable + Alter.Table(Constants.DatabaseSchema.Tables.RelationType).AlterColumn("alias").AsString(100).NotNullable().Do(); + //re-create the index + Create + .Index("IX_umbracoRelationType_alias") + .OnTable(Constants.DatabaseSchema.Tables.RelationType) + .OnColumn("alias") + .Ascending() + .WithOptions().Unique().WithOptions().NonClustered() + .Do(); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_7_0/MissingDictionaryIndex.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_7_0/MissingDictionaryIndex.cs new file mode 100644 index 0000000000..fa5116c990 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_7_0/MissingDictionaryIndex.cs @@ -0,0 +1,33 @@ +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_6_0 +{ + public class MissingDictionaryIndex : MigrationBase + { + public MissingDictionaryIndex(IMigrationContext context) + : base(context) + { + + } + + /// + /// Adds an index to the foreign key column parent on DictionaryDto's table + /// if it doesn't already exist + /// + public override void Migrate() + { + var indexName = "IX_" + DictionaryDto.TableName + "_Parent"; + + if (!IndexExists(indexName)) + { + Create + .Index(indexName) + .OnTable(DictionaryDto.TableName) + .OnColumn("parent") + .Ascending() + .WithOptions().NonClustered() + .Do(); + } + } + } +} diff --git a/src/Umbraco.Core/Models/ContentDataIntegrityReport.cs b/src/Umbraco.Core/Models/ContentDataIntegrityReport.cs new file mode 100644 index 0000000000..9f0f147083 --- /dev/null +++ b/src/Umbraco.Core/Models/ContentDataIntegrityReport.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Umbraco.Core.Models +{ + public class ContentDataIntegrityReport + { + public ContentDataIntegrityReport(IReadOnlyDictionary detectedIssues) + { + DetectedIssues = detectedIssues; + } + + public bool Ok => DetectedIssues.Count == 0 || DetectedIssues.Count == DetectedIssues.Values.Count(x => x.Fixed); + + public IReadOnlyDictionary DetectedIssues { get; } + + public IReadOnlyDictionary FixedIssues + => DetectedIssues.Where(x => x.Value.Fixed).ToDictionary(x => x.Key, x => x.Value); + + public enum IssueType + { + /// + /// The item's level and path are inconsistent with it's parent's path and level + /// + InvalidPathAndLevelByParentId, + + /// + /// The item's path doesn't contain all required parts + /// + InvalidPathEmpty, + + /// + /// The item's path parts are inconsistent with it's level value + /// + InvalidPathLevelMismatch, + + /// + /// The item's path does not end with it's own ID + /// + InvalidPathById, + + /// + /// The item's path does not have it's parent Id as the 2nd last entry + /// + InvalidPathByParentId, + } + } +} diff --git a/src/Umbraco.Core/Models/ContentDataIntegrityReportEntry.cs b/src/Umbraco.Core/Models/ContentDataIntegrityReportEntry.cs new file mode 100644 index 0000000000..517b9e80dc --- /dev/null +++ b/src/Umbraco.Core/Models/ContentDataIntegrityReportEntry.cs @@ -0,0 +1,13 @@ +namespace Umbraco.Core.Models +{ + public class ContentDataIntegrityReportEntry + { + public ContentDataIntegrityReportEntry(ContentDataIntegrityReport.IssueType issueType) + { + IssueType = issueType; + } + + public ContentDataIntegrityReport.IssueType IssueType { get; } + public bool Fixed { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/ContentDataIntegrityReportOptions.cs b/src/Umbraco.Core/Models/ContentDataIntegrityReportOptions.cs new file mode 100644 index 0000000000..c4689467c1 --- /dev/null +++ b/src/Umbraco.Core/Models/ContentDataIntegrityReportOptions.cs @@ -0,0 +1,13 @@ +namespace Umbraco.Core.Models +{ + public class ContentDataIntegrityReportOptions + { + /// + /// Set to true to try to automatically resolve data integrity issues + /// + public bool FixIssues { get; set; } + + // TODO: We could define all sorts of options for the data integrity check like what to check for, what to fix, etc... + // things like Tag data consistency, etc... + } +} diff --git a/src/Umbraco.Core/Models/DeepCloneHelper.cs b/src/Umbraco.Core/Models/DeepCloneHelper.cs index 6470de912b..453b455d4b 100644 --- a/src/Umbraco.Core/Models/DeepCloneHelper.cs +++ b/src/Umbraco.Core/Models/DeepCloneHelper.cs @@ -81,9 +81,10 @@ namespace Umbraco.Core.Models if (propertyInfo.PropertyType.IsGenericType && (propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(IEnumerable<>) || propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(ICollection<>) - || propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(IList<>))) + || propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(IList<>) + || propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(IReadOnlyCollection<>))) { - //if it is a IEnumerable<>, IList or ICollection<> we'll use a List<> + //if it is a IEnumerable<>, IReadOnlyCollection, IList or ICollection<> we'll use a List<> since it implements them all var genericType = typeof(List<>).MakeGenericType(propertyInfo.PropertyType.GetGenericArguments()); return new ClonePropertyInfo(propertyInfo) { GenericListType = genericType }; } diff --git a/src/Umbraco.Core/Models/Editors/ContentPropertyFile.cs b/src/Umbraco.Core/Models/Editors/ContentPropertyFile.cs index ac236e1fdd..225e29a8a1 100644 --- a/src/Umbraco.Core/Models/Editors/ContentPropertyFile.cs +++ b/src/Umbraco.Core/Models/Editors/ContentPropertyFile.cs @@ -1,5 +1,6 @@ namespace Umbraco.Core.Models.Editors { + /// /// Represents an uploaded file for a property. /// diff --git a/src/Umbraco.Core/Models/Editors/UmbracoEntityReference.cs b/src/Umbraco.Core/Models/Editors/UmbracoEntityReference.cs new file mode 100644 index 0000000000..31d48e60cf --- /dev/null +++ b/src/Umbraco.Core/Models/Editors/UmbracoEntityReference.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; + +namespace Umbraco.Core.Models.Editors +{ + /// + /// Used to track reference to other entities in a property value + /// + public struct UmbracoEntityReference : IEquatable + { + private static readonly UmbracoEntityReference _empty = new UmbracoEntityReference(Udi.UnknownTypeUdi.Instance, string.Empty); + + public UmbracoEntityReference(Udi udi, string relationTypeAlias) + { + Udi = udi ?? throw new ArgumentNullException(nameof(udi)); + RelationTypeAlias = relationTypeAlias ?? throw new ArgumentNullException(nameof(relationTypeAlias)); + } + + public UmbracoEntityReference(Udi udi) + { + Udi = udi ?? throw new ArgumentNullException(nameof(udi)); + + switch (udi.EntityType) + { + case Constants.UdiEntityType.Media: + RelationTypeAlias = Constants.Conventions.RelationTypes.RelatedMediaAlias; + break; + default: + RelationTypeAlias = Constants.Conventions.RelationTypes.RelatedDocumentAlias; + break; + } + } + + public static UmbracoEntityReference Empty() => _empty; + + public static bool IsEmpty(UmbracoEntityReference reference) => reference == Empty(); + + public Udi Udi { get; } + public string RelationTypeAlias { get; } + + public override bool Equals(object obj) + { + return obj is UmbracoEntityReference reference && Equals(reference); + } + + public bool Equals(UmbracoEntityReference other) + { + return EqualityComparer.Default.Equals(Udi, other.Udi) && + RelationTypeAlias == other.RelationTypeAlias; + } + + public override int GetHashCode() + { + var hashCode = -487348478; + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Udi); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(RelationTypeAlias); + return hashCode; + } + + public static bool operator ==(UmbracoEntityReference left, UmbracoEntityReference right) + { + return left.Equals(right); + } + + public static bool operator !=(UmbracoEntityReference left, UmbracoEntityReference right) + { + return !(left == right); + } + } +} diff --git a/src/Umbraco.Core/Models/Entities/ITreeEntity.cs b/src/Umbraco.Core/Models/Entities/ITreeEntity.cs index afa3399202..ab63e1e1d8 100644 --- a/src/Umbraco.Core/Models/Entities/ITreeEntity.cs +++ b/src/Umbraco.Core/Models/Entities/ITreeEntity.cs @@ -24,7 +24,7 @@ /// Sets the parent entity. /// /// Use this method to set the parent entity when the parent entity is known, but has not - /// been persistent and does not yet have an identity. The parent identifier will we retrieved + /// been persistent and does not yet have an identity. The parent identifier will be retrieved /// from the parent entity when needed. If the parent entity still does not have an entity by that /// time, an exception will be thrown by getter. void SetParent(ITreeEntity parent); @@ -53,4 +53,4 @@ /// bool Trashed { get; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/Entities/MemberEntitySlim.cs b/src/Umbraco.Core/Models/Entities/MemberEntitySlim.cs index 335e269467..338f363856 100644 --- a/src/Umbraco.Core/Models/Entities/MemberEntitySlim.cs +++ b/src/Umbraco.Core/Models/Entities/MemberEntitySlim.cs @@ -1,13 +1,6 @@ namespace Umbraco.Core.Models.Entities { - public class MemberEntitySlim : EntitySlim, IMemberEntitySlim + public class MemberEntitySlim : ContentEntitySlim, IMemberEntitySlim { - public string ContentTypeAlias { get; set; } - - /// - public string ContentTypeIcon { get; set; } - - /// - public string ContentTypeThumbnail { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/IImageUrlGenerator.cs b/src/Umbraco.Core/Models/IImageUrlGenerator.cs new file mode 100644 index 0000000000..0dc2933fd9 --- /dev/null +++ b/src/Umbraco.Core/Models/IImageUrlGenerator.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Core.Models +{ + public interface IImageUrlGenerator + { + string GetImageUrl(ImageUrlGenerationOptions options); + } +} diff --git a/src/Umbraco.Core/Models/IRelation.cs b/src/Umbraco.Core/Models/IRelation.cs index 745216fba1..6bd348d72f 100644 --- a/src/Umbraco.Core/Models/IRelation.cs +++ b/src/Umbraco.Core/Models/IRelation.cs @@ -1,4 +1,5 @@ -using System.Runtime.Serialization; +using System; +using System.Runtime.Serialization; using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models @@ -11,12 +12,18 @@ namespace Umbraco.Core.Models [DataMember] int ParentId { get; set; } + [DataMember] + Guid ParentObjectType { get; set; } + /// /// Gets or sets the Child Id of the Relation (Destination) /// [DataMember] int ChildId { get; set; } + [DataMember] + Guid ChildObjectType { get; set; } + /// /// Gets or sets the for the Relation /// diff --git a/src/Umbraco.Core/Models/IRelationType.cs b/src/Umbraco.Core/Models/IRelationType.cs index 8bbe657427..9253fae8ab 100644 --- a/src/Umbraco.Core/Models/IRelationType.cs +++ b/src/Umbraco.Core/Models/IRelationType.cs @@ -29,13 +29,13 @@ namespace Umbraco.Core.Models /// /// Corresponds to the NodeObjectType in the umbracoNode table [DataMember] - Guid ParentObjectType { get; set; } + Guid? ParentObjectType { get; set; } /// /// Gets or sets the Childs object type id /// /// Corresponds to the NodeObjectType in the umbracoNode table [DataMember] - Guid ChildObjectType { get; set; } + Guid? ChildObjectType { get; set; } } } diff --git a/src/Umbraco.Core/Models/ImageUrlGenerationOptions.cs b/src/Umbraco.Core/Models/ImageUrlGenerationOptions.cs new file mode 100644 index 0000000000..f87657c33d --- /dev/null +++ b/src/Umbraco.Core/Models/ImageUrlGenerationOptions.cs @@ -0,0 +1,66 @@ +namespace Umbraco.Core.Models +{ + /// + /// These are options that are passed to the IImageUrlGenerator implementation to determine + /// the propery URL that is needed + /// + public class ImageUrlGenerationOptions + { + public ImageUrlGenerationOptions (string imageUrl) + { + ImageUrl = imageUrl; + } + + public string ImageUrl { get; } + public int? Width { get; set; } + public int? Height { get; set; } + public decimal? WidthRatio { get; set; } + public decimal? HeightRatio { get; set; } + public int? Quality { get; set; } + public string ImageCropMode { get; set; } + public string ImageCropAnchor { get; set; } + public bool DefaultCrop { get; set; } + public FocalPointPosition FocalPoint { get; set; } + public CropCoordinates Crop { get; set; } + public string CacheBusterValue { get; set; } + public string FurtherOptions { get; set; } + public bool UpScale { get; set; } = true; + public string AnimationProcessMode { get; set; } + + /// + /// The focal point position, in whatever units the registered IImageUrlGenerator uses, + /// typically a percentage of the total image from 0.0 to 1.0. + /// + public class FocalPointPosition + { + public FocalPointPosition (decimal top, decimal left) + { + Left = left; + Top = top; + } + + public decimal Left { get; } + public decimal Top { get; } + } + + /// + /// The bounds of the crop within the original image, in whatever units the registered + /// IImageUrlGenerator uses, typically a percentage between 0 and 100. + /// + public class CropCoordinates + { + public CropCoordinates (decimal x1, decimal y1, decimal x2, decimal y2) + { + X1 = x1; + Y1 = y1; + X2 = x2; + Y2 = y2; + } + + public decimal X1 { get; } + public decimal Y1 { get; } + public decimal X2 { get; } + public decimal Y2 { get; } + } + } +} diff --git a/src/Umbraco.Core/Models/InstallLog.cs b/src/Umbraco.Core/Models/InstallLog.cs new file mode 100644 index 0000000000..cb14ebd650 --- /dev/null +++ b/src/Umbraco.Core/Models/InstallLog.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Umbraco.Core.Models +{ + public class InstallLog + { + public Guid InstallId { get; } + public bool IsUpgrade { get; set; } + public bool InstallCompleted { get; set; } + public DateTime Timestamp { get; set; } + public int VersionMajor { get; } + public int VersionMinor { get; } + public int VersionPatch { get; } + public string VersionComment { get; } + public string Error { get; } + public string UserAgent { get; } + public string DbProvider { get; set; } + + public InstallLog(Guid installId, bool isUpgrade, bool installCompleted, DateTime timestamp, int versionMajor, int versionMinor, int versionPatch, string versionComment, string error, string userAgent, string dbProvider) + { + InstallId = installId; + IsUpgrade = isUpgrade; + InstallCompleted = installCompleted; + Timestamp = timestamp; + VersionMajor = versionMajor; + VersionMinor = versionMinor; + VersionPatch = versionPatch; + VersionComment = versionComment; + Error = error; + UserAgent = userAgent; + DbProvider = dbProvider; + } + } +} diff --git a/src/Umbraco.Core/Models/MediaTypeExtensions.cs b/src/Umbraco.Core/Models/MediaTypeExtensions.cs new file mode 100644 index 0000000000..4e2ae5822a --- /dev/null +++ b/src/Umbraco.Core/Models/MediaTypeExtensions.cs @@ -0,0 +1,10 @@ +namespace Umbraco.Core.Models +{ + internal static class MediaTypeExtensions + { + internal static bool IsSystemMediaType(this IMediaType mediaType) => + mediaType.Alias == Constants.Conventions.MediaTypes.File + || mediaType.Alias == Constants.Conventions.MediaTypes.Folder + || mediaType.Alias == Constants.Conventions.MediaTypes.Image; + } +} diff --git a/src/Umbraco.Core/Models/ObjectTypes.cs b/src/Umbraco.Core/Models/ObjectTypes.cs index dd943ee02b..2ddbdca77a 100644 --- a/src/Umbraco.Core/Models/ObjectTypes.cs +++ b/src/Umbraco.Core/Models/ObjectTypes.cs @@ -41,7 +41,7 @@ namespace Umbraco.Core.Models /// public static UmbracoObjectTypes GetUmbracoObjectType(string name) { - return (UmbracoObjectTypes) Enum.Parse(typeof (UmbracoObjectTypes), name, false); + return (UmbracoObjectTypes) Enum.Parse(typeof (UmbracoObjectTypes), name, true); } #region Guid object type utilities diff --git a/src/Umbraco.Core/Models/Property.cs b/src/Umbraco.Core/Models/Property.cs index 76349823ac..1d044aa20d 100644 --- a/src/Umbraco.Core/Models/Property.cs +++ b/src/Umbraco.Core/Models/Property.cs @@ -50,7 +50,7 @@ namespace Umbraco.Core.Models /// /// Represents a property value. /// - public class PropertyValue + public class PropertyValue : IDeepCloneable, IEquatable { // TODO: Either we allow change tracking at this class level, or we add some special change tracking collections to the Property // class to deal with change tracking which variants have changed @@ -95,6 +95,32 @@ namespace Umbraco.Core.Models /// public PropertyValue Clone() => new PropertyValue { _culture = _culture, _segment = _segment, PublishedValue = PublishedValue, EditedValue = EditedValue }; + + public object DeepClone() => Clone(); + + public override bool Equals(object obj) + { + return Equals(obj as PropertyValue); + } + + public bool Equals(PropertyValue other) + { + return other != null && + _culture == other._culture && + _segment == other._segment && + EqualityComparer.Default.Equals(EditedValue, other.EditedValue) && + EqualityComparer.Default.Equals(PublishedValue, other.PublishedValue); + } + + public override int GetHashCode() + { + var hashCode = 1885328050; + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(_culture); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(_segment); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(EditedValue); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(PublishedValue); + return hashCode; + } } private static readonly DelegateEqualityComparer PropertyValueComparer = new DelegateEqualityComparer( diff --git a/src/Umbraco.Core/Models/PropertyType.cs b/src/Umbraco.Core/Models/PropertyType.cs index fd23756acb..75c2a7cc00 100644 --- a/src/Umbraco.Core/Models/PropertyType.cs +++ b/src/Umbraco.Core/Models/PropertyType.cs @@ -178,7 +178,8 @@ namespace Umbraco.Core.Models /// /// For generic properties, the value is null. [DataMember] - internal Lazy PropertyGroupId + [DoNotClone] + public Lazy PropertyGroupId { get => _propertyGroupId; set => SetPropertyValueAndDetectChanges(value, ref _propertyGroupId, nameof(PropertyGroupId)); diff --git a/src/Umbraco.Core/Models/PublishedContent/ILivePublishedModelFactory.cs b/src/Umbraco.Core/Models/PublishedContent/ILivePublishedModelFactory.cs index 0810f2207b..39b9f038be 100644 --- a/src/Umbraco.Core/Models/PublishedContent/ILivePublishedModelFactory.cs +++ b/src/Umbraco.Core/Models/PublishedContent/ILivePublishedModelFactory.cs @@ -1,5 +1,21 @@ namespace Umbraco.Core.Models.PublishedContent { + /// + /// Provides a live published model creation service. + /// + public interface ILivePublishedModelFactory2 : ILivePublishedModelFactory + { + /// + /// Tells the factory that it should build a new generation of models + /// + void Reset(); + + /// + /// If the live model factory + /// + bool Enabled { get; } + } + /// /// Provides a live published model creation service. /// diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs index 1c0d39a8b8..4e154bf514 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs @@ -63,6 +63,7 @@ namespace Umbraco.Core.Models.PublishedContent /// /// Gets the name of the user who created the content item. /// + [Obsolete("Use CreatorName(IUserService) extension instead")] string CreatorName { get; } /// @@ -78,6 +79,7 @@ namespace Umbraco.Core.Models.PublishedContent /// /// Gets the name of the user who last updated the content item. /// + [Obsolete("Use WriterName(IUserService) extension instead")] string WriterName { get; } /// @@ -97,6 +99,7 @@ namespace Umbraco.Core.Models.PublishedContent /// The value of this property is contextual. It depends on the 'current' request uri, /// if any. /// + [Obsolete("Use the Url() extension instead")] string Url { get; } /// diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs index b11e991118..458b63ade3 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs @@ -105,13 +105,13 @@ namespace Umbraco.Core.Models.PublishedContent { "Email", Constants.DataTypes.Textbox }, { "Username", Constants.DataTypes.Textbox }, { "PasswordQuestion", Constants.DataTypes.Textbox }, - { "Comments", Constants.DataTypes.Textbox }, + { "Comments", Constants.DataTypes.Textarea }, { "IsApproved", Constants.DataTypes.Boolean }, { "IsLockedOut", Constants.DataTypes.Boolean }, - { "LastLockoutDate", Constants.DataTypes.DateTime }, - { "CreateDate", Constants.DataTypes.DateTime }, - { "LastLoginDate", Constants.DataTypes.DateTime }, - { "LastPasswordChangeDate", Constants.DataTypes.DateTime }, + { "LastLockoutDate", Constants.DataTypes.LabelDateTime }, + { "CreateDate", Constants.DataTypes.LabelDateTime }, + { "LastLoginDate", Constants.DataTypes.LabelDateTime }, + { "LastPasswordChangeDate", Constants.DataTypes.LabelDateTime } }; #region Content type diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs index fb41c95419..b83dc7a013 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs @@ -94,6 +94,7 @@ namespace Umbraco.Core.Models.PublishedContent public virtual DateTime UpdateDate => _content.UpdateDate; /// + [Obsolete("Use the Url() extension instead")] public virtual string Url => _content.Url; /// diff --git a/src/Umbraco.Core/Models/PublishedContent/VariationContext.cs b/src/Umbraco.Core/Models/PublishedContent/VariationContext.cs index b83002fdce..424fc7c4a8 100644 --- a/src/Umbraco.Core/Models/PublishedContent/VariationContext.cs +++ b/src/Umbraco.Core/Models/PublishedContent/VariationContext.cs @@ -23,5 +23,12 @@ /// Gets the segment. /// public string Segment { get; } + + /// + /// Gets the segment for the content item + /// + /// + /// + public virtual string GetSegment(int contentId) => Segment; } } diff --git a/src/Umbraco.Core/Models/PublishedContent/VariationContextAccessorExtensions.cs b/src/Umbraco.Core/Models/PublishedContent/VariationContextAccessorExtensions.cs index 6710d79cc6..a387ea87b8 100644 --- a/src/Umbraco.Core/Models/PublishedContent/VariationContextAccessorExtensions.cs +++ b/src/Umbraco.Core/Models/PublishedContent/VariationContextAccessorExtensions.cs @@ -3,13 +3,32 @@ public static class VariationContextAccessorExtensions { public static void ContextualizeVariation(this IVariationContextAccessor variationContextAccessor, ContentVariation variations, ref string culture, ref string segment) + => variationContextAccessor.ContextualizeVariation(variations, null, ref culture, ref segment); + + public static void ContextualizeVariation(this IVariationContextAccessor variationContextAccessor, ContentVariation variations, int contentId, ref string culture, ref string segment) + => variationContextAccessor.ContextualizeVariation(variations, (int?)contentId, ref culture, ref segment); + + private static void ContextualizeVariation(this IVariationContextAccessor variationContextAccessor, ContentVariation variations, int? contentId, ref string culture, ref string segment) { if (culture != null && segment != null) return; // use context values var publishedVariationContext = variationContextAccessor?.VariationContext; if (culture == null) culture = variations.VariesByCulture() ? publishedVariationContext?.Culture : ""; - if (segment == null) segment = variations.VariesBySegment() ? publishedVariationContext?.Segment : ""; + + if (segment == null) + { + if (variations.VariesBySegment()) + { + segment = contentId == null + ? publishedVariationContext?.Segment + : publishedVariationContext?.GetSegment(contentId.Value); + } + else + { + segment = ""; + } + } } } } diff --git a/src/Umbraco.Core/Models/Relation.cs b/src/Umbraco.Core/Models/Relation.cs index f5d13c70c4..7afa476226 100644 --- a/src/Umbraco.Core/Models/Relation.cs +++ b/src/Umbraco.Core/Models/Relation.cs @@ -17,13 +17,36 @@ namespace Umbraco.Core.Models private IRelationType _relationType; private string _comment; + /// + /// Constructor for constructing the entity to be created + /// + /// + /// + /// public Relation(int parentId, int childId, IRelationType relationType) { _parentId = parentId; _childId = childId; _relationType = relationType; } - + + /// + /// Constructor for reconstructing the entity from the data source + /// + /// + /// + /// + /// + /// + public Relation(int parentId, int childId, Guid parentObjectType, Guid childObjectType, IRelationType relationType) + { + _parentId = parentId; + _childId = childId; + _relationType = relationType; + ParentObjectType = parentObjectType; + ChildObjectType = childObjectType; + } + /// /// Gets or sets the Parent Id of the Relation (Source) @@ -35,6 +58,9 @@ namespace Umbraco.Core.Models set => SetPropertyValueAndDetectChanges(value, ref _parentId, nameof(ParentId)); } + [DataMember] + public Guid ParentObjectType { get; set; } + /// /// Gets or sets the Child Id of the Relation (Destination) /// @@ -45,6 +71,9 @@ namespace Umbraco.Core.Models set => SetPropertyValueAndDetectChanges(value, ref _childId, nameof(ChildId)); } + [DataMember] + public Guid ChildObjectType { get; set; } + /// /// Gets or sets the for the Relation /// diff --git a/src/Umbraco.Core/Models/RelationType.cs b/src/Umbraco.Core/Models/RelationType.cs index 725628bf90..25156cdb8a 100644 --- a/src/Umbraco.Core/Models/RelationType.cs +++ b/src/Umbraco.Core/Models/RelationType.cs @@ -1,5 +1,6 @@ using System; using System.Runtime.Serialization; +using Umbraco.Core.Exceptions; using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models @@ -13,28 +14,40 @@ namespace Umbraco.Core.Models { private string _name; private string _alias; - private bool _isBidrectional; - private Guid _parentObjectType; - private Guid _childObjectType; + private bool _isBidirectional; + private Guid? _parentObjectType; + private Guid? _childObjectType; - public RelationType(Guid childObjectType, Guid parentObjectType, string alias) + [Obsolete("This constructor is no longer used and will be removed in future versions, use one of the other constructors instead")] + public RelationType(string alias, string name) + : this(name: name, alias: alias, false, null, null) { - if (alias == null) throw new ArgumentNullException(nameof(alias)); - if (string.IsNullOrWhiteSpace(alias)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(alias)); - - _childObjectType = childObjectType; - _parentObjectType = parentObjectType; - _alias = alias; - Name = _alias; } - public RelationType(Guid childObjectType, Guid parentObjectType, string alias, string name) - : this(childObjectType, parentObjectType, alias) + public RelationType(string name, string alias, bool isBidrectional, Guid? parentObjectType, Guid? childObjectType) { if (name == null) throw new ArgumentNullException(nameof(name)); if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(name)); + if (alias == null) throw new ArgumentNullException(nameof(alias)); + if (string.IsNullOrWhiteSpace(alias)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(alias)); - Name = name; + _name = name; + _alias = alias; + _isBidirectional = isBidrectional; + _parentObjectType = parentObjectType; + _childObjectType = childObjectType; + } + + [Obsolete("This constructor is no longer used and will be removed in future versions, use one of the other constructors instead")] + public RelationType(Guid childObjectType, Guid parentObjectType, string alias) + : this(name: alias, alias: alias, isBidrectional: false, parentObjectType: parentObjectType, childObjectType: childObjectType) + { + } + + [Obsolete("This constructor is no longer used and will be removed in future versions, use one of the other constructors instead")] + public RelationType(Guid childObjectType, Guid parentObjectType, string alias, string name) + : this(name: name, alias: alias, isBidrectional: false, parentObjectType: parentObjectType, childObjectType: childObjectType) + { } /// @@ -63,8 +76,8 @@ namespace Umbraco.Core.Models [DataMember] public bool IsBidirectional { - get => _isBidrectional; - set => SetPropertyValueAndDetectChanges(value, ref _isBidrectional, nameof(IsBidirectional)); + get => _isBidirectional; + set => SetPropertyValueAndDetectChanges(value, ref _isBidirectional, nameof(IsBidirectional)); } /// @@ -72,7 +85,7 @@ namespace Umbraco.Core.Models /// /// Corresponds to the NodeObjectType in the umbracoNode table [DataMember] - public Guid ParentObjectType + public Guid? ParentObjectType { get => _parentObjectType; set => SetPropertyValueAndDetectChanges(value, ref _parentObjectType, nameof(ParentObjectType)); @@ -83,7 +96,7 @@ namespace Umbraco.Core.Models /// /// Corresponds to the NodeObjectType in the umbracoNode table [DataMember] - public Guid ChildObjectType + public Guid? ChildObjectType { get => _childObjectType; set => SetPropertyValueAndDetectChanges(value, ref _childObjectType, nameof(ChildObjectType)); diff --git a/src/Umbraco.Core/Models/UpgradeResult.cs b/src/Umbraco.Core/Models/UpgradeResult.cs new file mode 100644 index 0000000000..a27f6bb6a3 --- /dev/null +++ b/src/Umbraco.Core/Models/UpgradeResult.cs @@ -0,0 +1,16 @@ +namespace Umbraco.Core.Models +{ + public class UpgradeResult + { + public string UpgradeType { get; } + public string Comment { get; } + public string UpgradeUrl { get; } + + public UpgradeResult(string upgradeType, string comment, string upgradeUrl) + { + UpgradeType = upgradeType; + Comment = comment; + UpgradeUrl = upgradeUrl; + } + } +} diff --git a/src/Umbraco.Core/Models/UserExtensions.cs b/src/Umbraco.Core/Models/UserExtensions.cs index e00ac4ba15..5be66bac47 100644 --- a/src/Umbraco.Core/Models/UserExtensions.cs +++ b/src/Umbraco.Core/Models/UserExtensions.cs @@ -106,13 +106,14 @@ namespace Umbraco.Core.Models //use the custom avatar var avatarUrl = Current.MediaFileSystem.GetUrl(user.Avatar); + var urlGenerator = Current.ImageUrlGenerator; return new[] { - avatarUrl + "?width=30&height=30&mode=crop", - avatarUrl + "?width=60&height=60&mode=crop", - avatarUrl + "?width=90&height=90&mode=crop", - avatarUrl + "?width=150&height=150&mode=crop", - avatarUrl + "?width=300&height=300&mode=crop" + urlGenerator.GetImageUrl(new ImageUrlGenerationOptions(avatarUrl) { ImageCropMode = "crop", Width = 30, Height = 30 }), + urlGenerator.GetImageUrl(new ImageUrlGenerationOptions(avatarUrl) { ImageCropMode = "crop", Width = 60, Height = 60 }), + urlGenerator.GetImageUrl(new ImageUrlGenerationOptions(avatarUrl) { ImageCropMode = "crop", Width = 90, Height = 90 }), + urlGenerator.GetImageUrl(new ImageUrlGenerationOptions(avatarUrl) { ImageCropMode = "crop", Width = 150, Height = 150 }), + urlGenerator.GetImageUrl(new ImageUrlGenerationOptions(avatarUrl) { ImageCropMode = "crop", Width = 300, Height = 300 }) }; } diff --git a/src/Umbraco.Core/Packaging/ConflictingPackageData.cs b/src/Umbraco.Core/Packaging/ConflictingPackageData.cs index 82693677fb..33cb9c40d3 100644 --- a/src/Umbraco.Core/Packaging/ConflictingPackageData.cs +++ b/src/Umbraco.Core/Packaging/ConflictingPackageData.cs @@ -7,7 +7,7 @@ using Umbraco.Core.Services; namespace Umbraco.Core.Packaging { - internal class ConflictingPackageData + internal class ConflictingPackageData { private readonly IMacroService _macroService; private readonly IFileService _fileService; @@ -23,7 +23,7 @@ namespace Umbraco.Core.Packaging return stylesheetNodes .Select(n => { - var xElement = n.Element("Name") ?? n.Element("name"); ; + var xElement = n.Element("Name") ?? n.Element("name"); if (xElement == null) throw new FormatException("Missing \"Name\" element"); diff --git a/src/Umbraco.Core/Packaging/PackagesRepository.cs b/src/Umbraco.Core/Packaging/PackagesRepository.cs index 72954b238d..5549b502fa 100644 --- a/src/Umbraco.Core/Packaging/PackagesRepository.cs +++ b/src/Umbraco.Core/Packaging/PackagesRepository.cs @@ -82,7 +82,7 @@ namespace Umbraco.Core.Packaging { var packagesXml = EnsureStorage(out _); if (packagesXml?.Root == null) - yield break;; + yield break; foreach (var packageXml in packagesXml.Root.Elements("package")) yield return _parser.ToPackageDefinition(packageXml); @@ -139,7 +139,7 @@ namespace Umbraco.Core.Packaging var updatedXml = _parser.ToXml(definition); packageXml.ReplaceWith(updatedXml); } - + packagesXml.Save(packagesFile); return true; @@ -212,7 +212,7 @@ namespace Umbraco.Core.Packaging compiledPackageXml.Save(packageXmlFileName); // check if there's a packages directory below media - + if (Directory.Exists(IOHelper.MapPath(_mediaFolderPath)) == false) Directory.CreateDirectory(IOHelper.MapPath(_mediaFolderPath)); @@ -510,7 +510,6 @@ namespace Umbraco.Core.Packaging private XElement GetStylesheetXml(string name, bool includeProperties) { if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(name)); -; var sts = _fileService.GetStylesheetByName(name); if (sts == null) return null; var stylesheetXml = new XElement("Stylesheet"); @@ -562,7 +561,7 @@ namespace Umbraco.Core.Packaging package.Add(new XElement("url", definition.Url)); var requirements = new XElement("requirements"); - + requirements.Add(new XElement("major", definition.UmbracoVersion == null ? UmbracoVersion.SemanticVersion.Major.ToInvariantString() : definition.UmbracoVersion.Major.ToInvariantString())); requirements.Add(new XElement("minor", definition.UmbracoVersion == null ? UmbracoVersion.SemanticVersion.Minor.ToInvariantString() : definition.UmbracoVersion.Minor.ToInvariantString())); requirements.Add(new XElement("patch", definition.UmbracoVersion == null ? UmbracoVersion.SemanticVersion.Patch.ToInvariantString() : definition.UmbracoVersion.Build.ToInvariantString())); @@ -589,7 +588,7 @@ namespace Umbraco.Core.Packaging contributors.Add(new XElement("contributor", contributor)); } } - + info.Add(contributors); info.Add(new XElement("readme", new XCData(definition.Readme))); diff --git a/src/Umbraco.Core/Persistence/Constants-Locks.cs b/src/Umbraco.Core/Persistence/Constants-Locks.cs index 1dcd2408e7..e64f40ced7 100644 --- a/src/Umbraco.Core/Persistence/Constants-Locks.cs +++ b/src/Umbraco.Core/Persistence/Constants-Locks.cs @@ -8,6 +8,11 @@ namespace Umbraco.Core /// public static class Locks { + /// + /// The lock + /// + public const int MainDom = -1000; + /// /// All servers. /// diff --git a/src/Umbraco.Core/Persistence/Dtos/DictionaryDto.cs b/src/Umbraco.Core/Persistence/Dtos/DictionaryDto.cs index 655474b217..d357e9adbc 100644 --- a/src/Umbraco.Core/Persistence/Dtos/DictionaryDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/DictionaryDto.cs @@ -5,11 +5,12 @@ using Umbraco.Core.Persistence.DatabaseAnnotations; namespace Umbraco.Core.Persistence.Dtos { - [TableName(Constants.DatabaseSchema.Tables.DictionaryEntry)] + [TableName(TableName)] [PrimaryKey("pk")] [ExplicitColumns] internal class DictionaryDto { + public const string TableName = Constants.DatabaseSchema.Tables.DictionaryEntry; [Column("pk")] [PrimaryKeyColumn] public int PrimaryKey { get; set; } @@ -21,6 +22,7 @@ namespace Umbraco.Core.Persistence.Dtos [Column("parent")] [NullSetting(NullSetting = NullSettings.Null)] [ForeignKey(typeof(DictionaryDto), Column = "id")] + [Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_Parent")] public Guid? Parent { get; set; } [Column("key")] diff --git a/src/Umbraco.Core/Persistence/Dtos/RelationDto.cs b/src/Umbraco.Core/Persistence/Dtos/RelationDto.cs index f1fd3007d7..b21866eb8b 100644 --- a/src/Umbraco.Core/Persistence/Dtos/RelationDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/RelationDto.cs @@ -34,5 +34,13 @@ namespace Umbraco.Core.Persistence.Dtos [Column("comment")] [Length(1000)] public string Comment { get; set; } + + [ResultColumn] + [Column("parentObjectType")] + public Guid ParentObjectType { get; set; } + + [ResultColumn] + [Column("childObjectType")] + public Guid ChildObjectType { get; set; } } } diff --git a/src/Umbraco.Core/Persistence/Dtos/RelationTypeDto.cs b/src/Umbraco.Core/Persistence/Dtos/RelationTypeDto.cs index e972192844..d3e107d23f 100644 --- a/src/Umbraco.Core/Persistence/Dtos/RelationTypeDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/RelationTypeDto.cs @@ -9,7 +9,7 @@ namespace Umbraco.Core.Persistence.Dtos [ExplicitColumns] internal class RelationTypeDto { - public const int NodeIdSeed = 4; + public const int NodeIdSeed = 10; [Column("id")] [PrimaryKeyColumn(IdentitySeed = NodeIdSeed)] @@ -23,17 +23,20 @@ namespace Umbraco.Core.Persistence.Dtos public bool Dual { get; set; } [Column("parentObjectType")] - public Guid ParentObjectType { get; set; } + [NullSetting(NullSetting = NullSettings.Null)] + public Guid? ParentObjectType { get; set; } [Column("childObjectType")] - public Guid ChildObjectType { get; set; } + [NullSetting(NullSetting = NullSettings.Null)] + public Guid? ChildObjectType { get; set; } [Column("name")] + [NullSetting(NullSetting = NullSettings.NotNull)] [Index(IndexTypes.UniqueNonClustered, Name = "IX_umbracoRelationType_name")] public string Name { get; set; } [Column("alias")] - [NullSetting(NullSetting = NullSettings.Null)] + [NullSetting(NullSetting = NullSettings.NotNull)] [Length(100)] [Index(IndexTypes.UniqueNonClustered, Name = "IX_umbracoRelationType_alias")] public string Alias { get; set; } diff --git a/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs b/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs index 33dabe1b24..92d397520e 100644 --- a/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs @@ -134,15 +134,16 @@ namespace Umbraco.Core.Persistence.Factories // publishing = deal with edit and published values foreach (var propertyValue in property.Values) { - var isInvariantValue = propertyValue.Culture == null; - var isCultureValue = propertyValue.Culture != null && propertyValue.Segment == null; + var isInvariantValue = propertyValue.Culture == null && propertyValue.Segment == null; + var isCultureValue = propertyValue.Culture != null; + var isSegmentValue = propertyValue.Segment != null; // deal with published value - if (propertyValue.PublishedValue != null && publishedVersionId > 0) + if ((propertyValue.PublishedValue != null || isSegmentValue) && publishedVersionId > 0) propertyDataDtos.Add(BuildDto(publishedVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.PublishedValue)); // deal with edit value - if (propertyValue.EditedValue != null) + if (propertyValue.EditedValue != null || isSegmentValue) propertyDataDtos.Add(BuildDto(currentVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.EditedValue)); // property.Values will contain ALL of it's values, both variant and invariant which will be populated if the diff --git a/src/Umbraco.Core/Persistence/Factories/RelationFactory.cs b/src/Umbraco.Core/Persistence/Factories/RelationFactory.cs index 6abb858e94..d8f100cdbe 100644 --- a/src/Umbraco.Core/Persistence/Factories/RelationFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/RelationFactory.cs @@ -3,20 +3,11 @@ using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Persistence.Factories { - internal class RelationFactory + internal static class RelationFactory { - private readonly IRelationType _relationType; - - public RelationFactory(IRelationType relationType) + public static IRelation BuildEntity(RelationDto dto, IRelationType relationType) { - _relationType = relationType; - } - - #region Implementation of IEntityFactory - - public IRelation BuildEntity(RelationDto dto) - { - var entity = new Relation(dto.ParentId, dto.ChildId, _relationType); + var entity = new Relation(dto.ParentId, dto.ChildId, dto.ParentObjectType, dto.ChildObjectType, relationType); try { @@ -37,7 +28,7 @@ namespace Umbraco.Core.Persistence.Factories } } - public RelationDto BuildDto(IRelation entity) + public static RelationDto BuildDto(IRelation entity) { var dto = new RelationDto { @@ -54,6 +45,5 @@ namespace Umbraco.Core.Persistence.Factories return dto; } - #endregion } } diff --git a/src/Umbraco.Core/Persistence/Factories/RelationTypeFactory.cs b/src/Umbraco.Core/Persistence/Factories/RelationTypeFactory.cs index ca6928a0a1..177a0494a2 100644 --- a/src/Umbraco.Core/Persistence/Factories/RelationTypeFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/RelationTypeFactory.cs @@ -9,7 +9,7 @@ namespace Umbraco.Core.Persistence.Factories public static IRelationType BuildEntity(RelationTypeDto dto) { - var entity = new RelationType(dto.ChildObjectType, dto.ParentObjectType, dto.Alias); + var entity = new RelationType(dto.Name, dto.Alias, dto.Dual, dto.ParentObjectType, dto.ChildObjectType); try { @@ -17,8 +17,6 @@ namespace Umbraco.Core.Persistence.Factories entity.Id = dto.Id; entity.Key = dto.UniqueId; - entity.IsBidirectional = dto.Dual; - entity.Name = dto.Name; // reset dirty initial properties (U4-1946) entity.ResetDirtyProperties(false); diff --git a/src/Umbraco.Core/Persistence/NPocoDatabaseExtensions-Bulk.cs b/src/Umbraco.Core/Persistence/NPocoDatabaseExtensions-Bulk.cs index 0574e37c4c..10db1ca18e 100644 --- a/src/Umbraco.Core/Persistence/NPocoDatabaseExtensions-Bulk.cs +++ b/src/Umbraco.Core/Persistence/NPocoDatabaseExtensions-Bulk.cs @@ -14,7 +14,21 @@ namespace Umbraco.Core.Persistence /// public static partial class NPocoDatabaseExtensions { - // TODO: review NPoco native InsertBulk to replace the code below + /// + /// Configures NPoco's SqlBulkCopyHelper to use the correct SqlConnection and SqlTransaction instances from the underlying RetryDbConnection and ProfiledDbTransaction + /// + /// + /// This is required to use NPoco's own method because we use wrapped DbConnection and DbTransaction instances. + /// NPoco's InsertBulk method only caters for efficient bulk inserting records for Sql Server, it does not cater for bulk inserting of records for + /// any other database type and in which case will just insert records one at a time. + /// NPoco's InsertBulk method also deals with updating the passed in entity's PK/ID once it's inserted whereas our own BulkInsertRecords methods + /// do not handle this scenario. + /// + public static void ConfigureNPocoBulkExtensions() + { + SqlBulkCopyHelper.SqlConnectionResolver = dbConn => GetTypedConnection(dbConn); + SqlBulkCopyHelper.SqlTransactionResolver = dbTran => GetTypedTransaction(dbTran); + } /// /// Bulk-inserts records within a transaction. @@ -235,7 +249,7 @@ namespace Umbraco.Core.Persistence //we need to add column mappings here because otherwise columns will be matched by their order and if the order of them are different in the DB compared //to the order in which they are declared in the model then this will not work, so instead we will add column mappings by name so that this explicitly uses //the names instead of their ordering. - foreach(var col in bulkReader.ColumnMappings) + foreach (var col in bulkReader.ColumnMappings) { copy.ColumnMappings.Add(col.DestinationColumn, col.DestinationColumn); } diff --git a/src/Umbraco.Core/Persistence/NPocoDatabaseExtensions.cs b/src/Umbraco.Core/Persistence/NPocoDatabaseExtensions.cs index acfa51f895..152dcbe6d3 100644 --- a/src/Umbraco.Core/Persistence/NPocoDatabaseExtensions.cs +++ b/src/Umbraco.Core/Persistence/NPocoDatabaseExtensions.cs @@ -18,6 +18,48 @@ namespace Umbraco.Core.Persistence /// public static partial class NPocoDatabaseExtensions { + /// + /// Iterates over the result of a paged data set with a db reader + /// + /// + /// + /// + /// The number of rows to load per page + /// + /// + /// + /// + /// NPoco's normal Page returns a List{T} but sometimes we don't want all that in memory and instead want to + /// iterate over each row with a reader using Query vs Fetch. + /// + internal static IEnumerable QueryPaged(this IDatabase database, long pageSize, Sql sql) + { + var sqlString = sql.SQL; + var sqlArgs = sql.Arguments; + + int? itemCount = null; + long pageIndex = 0; + do + { + // Get the paged queries + database.BuildPageQueries(pageIndex * pageSize, pageSize, sqlString, ref sqlArgs, out var sqlCount, out var sqlPage); + + // get the item count once + if (itemCount == null) + { + itemCount = database.ExecuteScalar(sqlCount, sqlArgs); + } + pageIndex++; + + // iterate over rows without allocating all items to memory (Query vs Fetch) + foreach (var row in database.Query(sqlPage, sqlArgs)) + { + yield return row; + } + + } while ((pageIndex * pageSize) < itemCount); + } + // NOTE // // proper way to do it with TSQL and SQLCE diff --git a/src/Umbraco.Core/Persistence/Repositories/IContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IContentRepository.cs index 217719e144..ad9e2d27c1 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IContentRepository.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Querying; @@ -77,5 +78,7 @@ namespace Umbraco.Core.Persistence.Repositories /// Here, can be null but cannot. IEnumerable GetPage(IQuery query, long pageIndex, int pageSize, out long totalRecords, IQuery filter, Ordering ordering); + + ContentDataIntegrityReport CheckDataIntegrity(ContentDataIntegrityReportOptions options); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/IContentTypeRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/IContentTypeRepositoryBase.cs index 69b0698a96..4020244733 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IContentTypeRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IContentTypeRepositoryBase.cs @@ -26,5 +26,17 @@ namespace Umbraco.Core.Persistence.Repositories /// /// bool HasContainerInPath(string contentPath); + + /// + /// Gets a value indicating whether there is a list view content item in the path. + /// + /// + /// + bool HasContainerInPath(params int[] ids); + + /// + /// Returns true or false depending on whether content nodes have been created based on the provided content type id. + /// + bool HasContentNodes(int id); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs index 69f6ef4c5f..a0ddcac8e6 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs @@ -1,4 +1,5 @@ -using System; +using NPoco; +using System; using System.Collections.Generic; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; @@ -15,10 +16,22 @@ namespace Umbraco.Core.Persistence.Repositories IEntitySlim Get(int id, Guid objectTypeId); IEntitySlim Get(Guid key, Guid objectTypeId); - IEnumerable GetAll(Guid objectType, params int[] ids); + IEnumerable GetAll(Guid objectType, params int[] ids); IEnumerable GetAll(Guid objectType, params Guid[] keys); + /// + /// Gets entities for a query + /// + /// + /// IEnumerable GetByQuery(IQuery query); + + /// + /// Gets entities for a query and a specific object type allowing the query to be slightly more optimized + /// + /// + /// + /// IEnumerable GetByQuery(IQuery query, Guid objectType); UmbracoObjectTypes GetObjectType(int id); @@ -30,7 +43,41 @@ namespace Umbraco.Core.Persistence.Repositories bool Exists(int id); bool Exists(Guid key); + /// + /// Gets paged entities for a query and a subset of object types + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// A callback providing the ability to customize the generated SQL used to retrieve entities + /// + /// + /// A collection of mixed entity types which would be of type , , , + /// + /// + IEnumerable GetPagedResultsByQuery( + IQuery query, Guid[] objectTypes, long pageIndex, int pageSize, out long totalRecords, + IQuery filter, Ordering ordering, Action> sqlCustomization = null); + + /// + /// Gets paged entities for a query and a specific object type + /// + /// + /// + /// + /// + /// + /// + /// + /// IEnumerable GetPagedResultsByQuery(IQuery query, Guid objectType, long pageIndex, int pageSize, out long totalRecords, IQuery filter, Ordering ordering); + + } } diff --git a/src/Umbraco.Core/Persistence/Repositories/IInstallationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IInstallationRepository.cs new file mode 100644 index 0000000000..fbc7d2cfbc --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/IInstallationRepository.cs @@ -0,0 +1,11 @@ +using System; +using System.Threading.Tasks; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Persistence.Repositories +{ + public interface IInstallationRepository + { + Task SaveInstallLogAsync(InstallLog installLog); + } +} diff --git a/src/Umbraco.Core/Persistence/Repositories/IRelationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IRelationRepository.cs index 51d7656d8a..fc1be20e6f 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IRelationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IRelationRepository.cs @@ -1,9 +1,33 @@ -using Umbraco.Core.Models; +using System; +using System.Collections.Generic; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Entities; +using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Services; namespace Umbraco.Core.Persistence.Repositories { public interface IRelationRepository : IReadWriteQueryRepository { + IEnumerable GetPagedRelationsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, Ordering ordering); + /// + /// Persist multiple at once + /// + /// + void Save(IEnumerable relations); + + /// + /// Deletes all relations for a parent for any specified relation type alias + /// + /// + /// + /// A list of relation types to match for deletion, if none are specified then all relations for this parent id are deleted + /// + void DeleteByParent(int parentId, params string[] relationTypeAliases); + + IEnumerable GetPagedParentEntitiesByChildId(int childId, long pageIndex, int pageSize, out long totalRecords, params Guid[] entityTypes); + + IEnumerable GetPagedChildEntitiesByParentId(int parentId, long pageIndex, int pageSize, out long totalRecords, params Guid[] entityTypes); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/IUpgradeCheckRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IUpgradeCheckRepository.cs new file mode 100644 index 0000000000..6d56994781 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/IUpgradeCheckRepository.cs @@ -0,0 +1,11 @@ +using System.Threading.Tasks; +using Semver; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Persistence.Repositories +{ + public interface IUpgradeCheckRepository + { + Task CheckUpgradeAsync(SemVersion version); + } +} diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/AuditRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/AuditRepository.cs index 0788594e3a..4cb533e86f 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/AuditRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/AuditRepository.cs @@ -77,7 +77,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement public IEnumerable Get(AuditType type, IQuery query) { var sqlClause = GetBaseQuery(false) - .Where(x => x.Header == type.ToString()); + .Where("(logHeader=@0)", type.ToString()); + var translator = new SqlTranslator(sqlClause, query); var sql = translator.Translate(); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs index 7ab73f3f2d..845006891d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs @@ -9,6 +9,7 @@ using Umbraco.Core.Composing; using Umbraco.Core.Events; using Umbraco.Core.Logging; using Umbraco.Core.Models; +using Umbraco.Core.Models.Editors; using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Dtos; @@ -24,26 +25,48 @@ namespace Umbraco.Core.Persistence.Repositories.Implement internal sealed class ContentRepositoryBase { /// + /// /// This is used for unit tests ONLY /// public static bool ThrowOnWarning = false; } internal abstract class ContentRepositoryBase : NPocoRepositoryBase, IContentRepository - where TEntity : class, IUmbracoEntity + where TEntity : class, IContentBase where TRepository : class, IRepository { - protected ContentRepositoryBase(IScopeAccessor scopeAccessor, AppCaches cache, ILanguageRepository languageRepository, ILogger logger) + private readonly Lazy _propertyEditors; + private readonly DataValueReferenceFactoryCollection _dataValueReferenceFactories; + + /// + /// + /// + /// + /// + /// + /// + /// + /// Lazy property value collection - must be lazy because we have a circular dependency since some property editors require services, yet these services require property editors + /// + protected ContentRepositoryBase(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, + ILanguageRepository languageRepository, IRelationRepository relationRepository, IRelationTypeRepository relationTypeRepository, + Lazy propertyEditors, DataValueReferenceFactoryCollection dataValueReferenceFactories) : base(scopeAccessor, cache, logger) { LanguageRepository = languageRepository; + RelationRepository = relationRepository; + RelationTypeRepository = relationTypeRepository; + _propertyEditors = propertyEditors; + _dataValueReferenceFactories = dataValueReferenceFactories; } protected abstract TRepository This { get; } protected ILanguageRepository LanguageRepository { get; } + protected IRelationRepository RelationRepository { get; } + protected IRelationTypeRepository RelationTypeRepository { get; } - protected PropertyEditorCollection PropertyEditors => Current.PropertyEditors; // TODO: inject + protected PropertyEditorCollection PropertyEditors => _propertyEditors.Value; #region Versions @@ -60,7 +83,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // gets all version ids, current first public virtual IEnumerable GetVersionIds(int nodeId, int maxRows) { - var template = SqlContext.Templates.Get("Umbraco.Core.VersionableRepository.GetVersionIds", tsql => + var template = SqlContext.Templates.Get(Constants.SqlTemplates.VersionableRepository.GetVersionIds, tsql => tsql.Select(x => x.Id) .From() .Where(x => x.NodeId == SqlTemplate.Arg("nodeId")) @@ -76,7 +99,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // TODO: test object node type? // get the version we want to delete - var template = SqlContext.Templates.Get("Umbraco.Core.VersionableRepository.GetVersion", tsql => + var template = SqlContext.Templates.Get(Constants.SqlTemplates.VersionableRepository.GetVersion, tsql => tsql.Select().From().Where(x => x.Id == SqlTemplate.Arg("versionId")) ); var versionDto = Database.Fetch(template.Sql(new { versionId })).FirstOrDefault(); @@ -98,7 +121,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // TODO: test object node type? // get the versions we want to delete, excluding the current one - var template = SqlContext.Templates.Get("Umbraco.Core.VersionableRepository.GetVersions", tsql => + var template = SqlContext.Templates.Get(Constants.SqlTemplates.VersionableRepository.GetVersions, tsql => tsql.Select().From().Where(x => x.NodeId == SqlTemplate.Arg("nodeId") && !x.Current && x.VersionDate < SqlTemplate.Arg("versionDate")) ); var versionDtos = Database.Fetch(template.Sql(new { nodeId, versionDate })); @@ -380,7 +403,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement } // content type alias is invariant - if(ordering.OrderBy.InvariantEquals("contentTypeAlias")) + if (ordering.OrderBy.InvariantEquals("contentTypeAlias")) { var joins = Sql() .InnerJoin("ctype").On((content, contentType) => content.ContentTypeId == contentType.NodeId, aliasRight: "ctype"); @@ -454,6 +477,123 @@ namespace Umbraco.Core.Persistence.Repositories.Implement IQuery filter, Ordering ordering); + public ContentDataIntegrityReport CheckDataIntegrity(ContentDataIntegrityReportOptions options) + { + var report = new Dictionary(); + + var sql = SqlContext.Sql() + .Select() + .From() + .Where(x => x.NodeObjectType == NodeObjectTypeId) + .OrderBy(x => x.Level, x => x.ParentId, x => x.SortOrder); + + var nodesToRebuild = new Dictionary>(); + var validNodes = new Dictionary(); + var rootIds = new[] {Constants.System.Root, Constants.System.RecycleBinContent, Constants.System.RecycleBinMedia}; + var currentParentIds = new HashSet(rootIds); + var prevParentIds = currentParentIds; + var lastLevel = -1; + + // use a forward cursor (query) + foreach (var node in Database.Query(sql)) + { + if (node.Level != lastLevel) + { + // changing levels + prevParentIds = currentParentIds; + currentParentIds = null; + lastLevel = node.Level; + } + + if (currentParentIds == null) + { + // we're reset + currentParentIds = new HashSet(); + } + + currentParentIds.Add(node.NodeId); + + // paths parts without the roots + var pathParts = node.Path.Split(',').Where(x => !rootIds.Contains(int.Parse(x))).ToArray(); + + if (!prevParentIds.Contains(node.ParentId)) + { + // invalid, this will be because the level is wrong (which prob means path is wrong too) + report.Add(node.NodeId, new ContentDataIntegrityReportEntry(ContentDataIntegrityReport.IssueType.InvalidPathAndLevelByParentId)); + AppendNodeToFix(nodesToRebuild, node); + } + else if (pathParts.Length == 0) + { + // invalid path + report.Add(node.NodeId, new ContentDataIntegrityReportEntry(ContentDataIntegrityReport.IssueType.InvalidPathEmpty)); + AppendNodeToFix(nodesToRebuild, node); + } + else if (pathParts.Length != node.Level) + { + // invalid, either path or level is wrong + report.Add(node.NodeId, new ContentDataIntegrityReportEntry(ContentDataIntegrityReport.IssueType.InvalidPathLevelMismatch)); + AppendNodeToFix(nodesToRebuild, node); + } + else if (pathParts[pathParts.Length - 1] != node.NodeId.ToString()) + { + // invalid path + report.Add(node.NodeId, new ContentDataIntegrityReportEntry(ContentDataIntegrityReport.IssueType.InvalidPathById)); + AppendNodeToFix(nodesToRebuild, node); + } + else if (!rootIds.Contains(node.ParentId) && pathParts[pathParts.Length - 2] != node.ParentId.ToString()) + { + // invalid path + report.Add(node.NodeId, new ContentDataIntegrityReportEntry(ContentDataIntegrityReport.IssueType.InvalidPathByParentId)); + AppendNodeToFix(nodesToRebuild, node); + } + else + { + // it's valid! + + // don't track unless we are configured to fix + if (options.FixIssues) + validNodes.Add(node.NodeId, node); + } + } + + var updated = new List(); + + if (options.FixIssues) + { + // iterate all valid nodes to see if these are parents for invalid nodes + foreach (var (nodeId, node) in validNodes) + { + if (!nodesToRebuild.TryGetValue(nodeId, out var invalidNodes)) continue; + + // now we can try to rebuild the invalid paths. + + foreach (var invalidNode in invalidNodes) + { + invalidNode.Level = (short)(node.Level + 1); + invalidNode.Path = node.Path + "," + invalidNode.NodeId; + updated.Add(invalidNode); + } + } + + foreach (var node in updated) + { + Database.Update(node); + if (report.TryGetValue(node.NodeId, out var entry)) + entry.Fixed = true; + } + } + + return new ContentDataIntegrityReport(report); + } + + private static void AppendNodeToFix(IDictionary> nodesToRebuild, NodeDto node) + { + if (nodesToRebuild.TryGetValue(node.ParentId, out var childIds)) + childIds.Add(node); + else + nodesToRebuild[node.ParentId] = new List { node }; + } + // here, filter can be null and ordering cannot protected IEnumerable GetPage(IQuery query, long pageIndex, int pageSize, out long totalRecords, @@ -747,7 +887,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected virtual string EnsureUniqueNodeName(int parentId, string nodeName, int id = 0) { - var template = SqlContext.Templates.Get("Umbraco.Core.VersionableRepository.EnsureUniqueNodeName", tsql => tsql + var template = SqlContext.Templates.Get(Constants.SqlTemplates.VersionableRepository.EnsureUniqueNodeName, tsql => tsql .Select(x => Alias(x.NodeId, "id"), x => Alias(x.Text, "name")) .From() .Where(x => x.NodeObjectType == SqlTemplate.Arg("nodeObjectType") && x.ParentId == SqlTemplate.Arg("parentId"))); @@ -760,7 +900,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected virtual int GetNewChildSortOrder(int parentId, int first) { - var template = SqlContext.Templates.Get("Umbraco.Core.VersionableRepository.GetSortOrder", tsql => + var template = SqlContext.Templates.Get(Constants.SqlTemplates.VersionableRepository.GetSortOrder, tsql => tsql.Select($"COALESCE(MAX(sortOrder),{first - 1})").From().Where(x => x.ParentId == SqlTemplate.Arg("parentId") && x.NodeObjectType == NodeObjectTypeId) ); @@ -769,7 +909,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected virtual NodeDto GetParentNodeDto(int parentId) { - var template = SqlContext.Templates.Get("Umbraco.Core.VersionableRepository.GetParentNode", tsql => + var template = SqlContext.Templates.Get(Constants.SqlTemplates.VersionableRepository.GetParentNode, tsql => tsql.Select().From().Where(x => x.NodeId == SqlTemplate.Arg("parentId")) ); @@ -778,7 +918,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected virtual int GetReservedId(Guid uniqueId) { - var template = SqlContext.Templates.Get("Umbraco.Core.VersionableRepository.GetReservedId", tsql => + var template = SqlContext.Templates.Get(Constants.SqlTemplates.VersionableRepository.GetReservedId, tsql => tsql.Select(x => x.NodeId).From().Where(x => x.UniqueId == SqlTemplate.Arg("uniqueId") && x.NodeObjectType == Constants.ObjectTypes.IdReservation) ); var id = Database.ExecuteScalar(template.Sql(new { uniqueId = uniqueId })); @@ -797,5 +937,56 @@ namespace Umbraco.Core.Persistence.Repositories.Implement } #endregion + + protected void PersistRelations(TEntity entity) + { + // Get all references from our core built in DataEditors/Property Editors + // Along with seeing if deverlopers want to collect additional references from the DataValueReferenceFactories collection + var trackedRelations = new List(); + trackedRelations.AddRange(_dataValueReferenceFactories.GetAllReferences(entity.Properties, PropertyEditors)); + + //First delete all auto-relations for this entity + RelationRepository.DeleteByParent(entity.Id, Constants.Conventions.RelationTypes.AutomaticRelationTypes); + + if (trackedRelations.Count == 0) return; + + trackedRelations = trackedRelations.Distinct().ToList(); + var udiToGuids = trackedRelations.Select(x => x.Udi as GuidUdi) + .ToDictionary(x => (Udi)x, x => x.Guid); + + //lookup in the DB all INT ids for the GUIDs and chuck into a dictionary + var keyToIds = Database.Fetch(Sql().Select(x => x.NodeId, x => x.UniqueId).From().WhereIn(x => x.UniqueId, udiToGuids.Values)) + .ToDictionary(x => x.UniqueId, x => x.NodeId); + + var allRelationTypes = RelationTypeRepository.GetMany(Array.Empty()) + .ToDictionary(x => x.Alias, x => x); + + var toSave = trackedRelations.Select(rel => + { + if (!allRelationTypes.TryGetValue(rel.RelationTypeAlias, out var relationType)) + throw new InvalidOperationException($"The relation type {rel.RelationTypeAlias} does not exist"); + + if (!udiToGuids.TryGetValue(rel.Udi, out var guid)) + return null; // This shouldn't happen! + + if (!keyToIds.TryGetValue(guid, out var id)) + return null; // This shouldn't happen! + + return new Relation(entity.Id, id, relationType); + }).WhereNotNull(); + + // Save bulk relations + RelationRepository.Save(toSave); + + } + + private class NodeIdKey + { + [Column("id")] + public int NodeId { get; set; } + + [Column("uniqueId")] + public Guid UniqueId { get; set; } + } } } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs index e2c3d8c9b5..357798a8a9 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs @@ -1309,20 +1309,33 @@ WHERE cmsContentType." + aliasColumn + @" LIKE @pattern", return test; } - /// - /// Given the path of a content item, this will return true if the content item exists underneath a list view content item - /// - /// - /// + /// public bool HasContainerInPath(string contentPath) { - var ids = contentPath.Split(',').Select(int.Parse); + var ids = contentPath.Split(',').Select(int.Parse).ToArray(); + return HasContainerInPath(ids); + } + + /// + public bool HasContainerInPath(params int[] ids) + { var sql = new Sql($@"SELECT COUNT(*) FROM cmsContentType INNER JOIN {Constants.DatabaseSchema.Tables.Content} ON cmsContentType.nodeId={Constants.DatabaseSchema.Tables.Content}.contentTypeId WHERE {Constants.DatabaseSchema.Tables.Content}.nodeId IN (@ids) AND cmsContentType.isContainer=@isContainer", new { ids, isContainer = true }); return Database.ExecuteScalar(sql) > 0; } + /// + /// Returns true or false depending on whether content nodes have been created based on the provided content type id. + /// + public bool HasContentNodes(int id) + { + var sql = new Sql( + $"SELECT CASE WHEN EXISTS (SELECT * FROM {Constants.DatabaseSchema.Tables.Content} WHERE contentTypeId = @id) THEN 1 ELSE 0 END", + new { id }); + return Database.ExecuteScalar(sql) == 1; + } + protected override IEnumerable GetDeleteClauses() { // in theory, services should have ensured that content items of the given content type diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentBlueprintRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentBlueprintRepository.cs index d137d7ac76..e150b2e632 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentBlueprintRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentBlueprintRepository.cs @@ -2,6 +2,7 @@ using Umbraco.Core.Cache; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; +using Umbraco.Core.PropertyEditors; using Umbraco.Core.Scoping; namespace Umbraco.Core.Persistence.Repositories.Implement @@ -17,8 +18,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// internal class DocumentBlueprintRepository : DocumentRepository, IDocumentBlueprintRepository { - public DocumentBlueprintRepository(IScopeAccessor scopeAccessor, AppCaches appCaches, ILogger logger, IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository, ILanguageRepository languageRepository) - : base(scopeAccessor, appCaches, logger, contentTypeRepository, templateRepository, tagRepository, languageRepository) + public DocumentBlueprintRepository(IScopeAccessor scopeAccessor, AppCaches appCaches, ILogger logger, + IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository, ILanguageRepository languageRepository, IRelationRepository relationRepository, IRelationTypeRepository relationTypeRepository, + Lazy propertyEditorCollection, DataValueReferenceFactoryCollection dataValueReferenceFactories) + : base(scopeAccessor, appCaches, logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditorCollection, dataValueReferenceFactories) { } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs index afe1af7eb4..db1e2b350d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs @@ -11,6 +11,7 @@ using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.SqlSyntax; +using Umbraco.Core.PropertyEditors; using Umbraco.Core.Scoping; using Umbraco.Core.Services; @@ -29,8 +30,23 @@ namespace Umbraco.Core.Persistence.Repositories.Implement private readonly ContentByGuidReadRepository _contentByGuidReadRepository; private readonly IScopeAccessor _scopeAccessor; - public DocumentRepository(IScopeAccessor scopeAccessor, AppCaches appCaches, ILogger logger, IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository, ILanguageRepository languageRepository) - : base(scopeAccessor, appCaches, languageRepository, logger) + /// + /// Constructor + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// Lazy property value collection - must be lazy because we have a circular dependency since some property editors require services, yet these services require property editors + /// + public DocumentRepository(IScopeAccessor scopeAccessor, AppCaches appCaches, ILogger logger, + IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository, ILanguageRepository languageRepository, IRelationRepository relationRepository, IRelationTypeRepository relationTypeRepository, + Lazy propertyEditors, DataValueReferenceFactoryCollection dataValueReferenceFactories) + : base(scopeAccessor, appCaches, logger, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferenceFactories) { _contentTypeRepository = contentTypeRepository ?? throw new ArgumentNullException(nameof(contentTypeRepository)); _templateRepository = templateRepository ?? throw new ArgumentNullException(nameof(templateRepository)); @@ -305,7 +321,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement .InnerJoin() .On((c, d) => c.Id == d.Id) .Where(x => x.NodeId == SqlTemplate.Arg("nodeId") && !x.Current && x.VersionDate < SqlTemplate.Arg("versionDate")) - .Where( x => !x.Published) + .Where(x => !x.Published) ); var versionDtos = Database.Fetch(template.Sql(new { nodeId, versionDate })); foreach (var versionDto in versionDtos) @@ -484,6 +500,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement ClearEntityTags(entity, _tagRepository); } + PersistRelations(entity); + entity.ResetDirtyProperties(); // troubleshooting @@ -501,8 +519,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override void PersistUpdatedItem(IContent entity) { - var entityBase = entity as EntityBase; - var isEntityDirty = entityBase != null && entityBase.IsDirty(); + var isEntityDirty = entity.IsDirty(); // check if we need to make any database changes at all if ((entity.PublishedState == PublishedState.Published || entity.PublishedState == PublishedState.Unpublished) @@ -517,29 +534,41 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // update entity.UpdatingEntity(); + // Check if this entity is being moved as a descendant as part of a bulk moving operations. + // In this case we can bypass a lot of the below operations which will make this whole operation go much faster. + // When moving we don't need to create new versions, etc... because we cannot roll this operation back anyways. + var isMoving = entity.IsMoving(); + // TODO: I'm sure we can also detect a "Copy" (of a descendant) operation and probably perform similar checks below. + // There is probably more stuff that would be required for copying but I'm sure not all of this logic would be, we could more than likely boost + // copy performance by 95% just like we did for Move + + var publishing = entity.PublishedState == PublishedState.Publishing; - // check if we need to create a new version - if (publishing && entity.PublishedVersionId > 0) + if (!isMoving) { - // published version is not published anymore - Database.Execute(Sql().Update(u => u.Set(x => x.Published, false)).Where(x => x.Id == entity.PublishedVersionId)); - } + // check if we need to create a new version + if (publishing && entity.PublishedVersionId > 0) + { + // published version is not published anymore + Database.Execute(Sql().Update(u => u.Set(x => x.Published, false)).Where(x => x.Id == entity.PublishedVersionId)); + } - // sanitize names - SanitizeNames(entity, publishing); + // sanitize names + SanitizeNames(entity, publishing); - // ensure that strings don't contain characters that are invalid in xml - // TODO: do we really want to keep doing this here? - entity.SanitizeEntityPropertiesForXmlStorage(); + // ensure that strings don't contain characters that are invalid in xml + // TODO: do we really want to keep doing this here? + entity.SanitizeEntityPropertiesForXmlStorage(); - // if parent has changed, get path, level and sort order - if (entity.IsPropertyDirty("ParentId")) - { - var parent = GetParentNodeDto(entity.ParentId); - entity.Path = string.Concat(parent.Path, ",", entity.Id); - entity.Level = parent.Level + 1; - entity.SortOrder = GetNewChildSortOrder(entity.ParentId, 0); + // if parent has changed, get path, level and sort order + if (entity.IsPropertyDirty("ParentId")) + { + var parent = GetParentNodeDto(entity.ParentId); + entity.Path = string.Concat(parent.Path, ",", entity.Id); + entity.Level = parent.Level + 1; + entity.SortOrder = GetNewChildSortOrder(entity.ParentId, 0); + } } // create the dto @@ -550,144 +579,152 @@ namespace Umbraco.Core.Persistence.Repositories.Implement nodeDto.ValidatePathWithException(); Database.Update(nodeDto); - // update the content dto - Database.Update(dto.ContentDto); - - // update the content & document version dtos - var contentVersionDto = dto.DocumentVersionDto.ContentVersionDto; - var documentVersionDto = dto.DocumentVersionDto; - if (publishing) + if (!isMoving) { - documentVersionDto.Published = true; // now published - contentVersionDto.Current = false; // no more current - } - Database.Update(contentVersionDto); - Database.Update(documentVersionDto); + // update the content dto + Database.Update(dto.ContentDto); - // and, if publishing, insert new content & document version dtos - if (publishing) - { - entity.PublishedVersionId = entity.VersionId; - - contentVersionDto.Id = 0; // want a new id - contentVersionDto.Current = true; // current version - contentVersionDto.Text = entity.Name; - Database.Insert(contentVersionDto); - entity.VersionId = documentVersionDto.Id = contentVersionDto.Id; // get the new id - - documentVersionDto.Published = false; // non-published version - Database.Insert(documentVersionDto); - } - - // replace the property data (rather than updating) - // only need to delete for the version that existed, the new version (if any) has no property data yet - var versionToDelete = publishing ? entity.PublishedVersionId : entity.VersionId; - var deletePropertyDataSql = Sql().Delete().Where(x => x.VersionId == versionToDelete); - Database.Execute(deletePropertyDataSql); - - // insert property data - var propertyDataDtos = PropertyFactory.BuildDtos(entity.ContentType.Variations, entity.VersionId, publishing ? entity.PublishedVersionId : 0, - entity.Properties, LanguageRepository, out var edited, out var editedCultures); - foreach (var propertyDataDto in propertyDataDtos) - Database.Insert(propertyDataDto); - - // if !publishing, we may have a new name != current publish name, - // also impacts 'edited' - if (!publishing && entity.PublishName != entity.Name) - edited = true; - - if (entity.ContentType.VariesByCulture()) - { - // bump dates to align cultures to version + // update the content & document version dtos + var contentVersionDto = dto.DocumentVersionDto.ContentVersionDto; + var documentVersionDto = dto.DocumentVersionDto; if (publishing) - entity.AdjustDates(contentVersionDto.VersionDate); + { + documentVersionDto.Published = true; // now published + contentVersionDto.Current = false; // no more current + } + Database.Update(contentVersionDto); + Database.Update(documentVersionDto); - // names also impact 'edited' - // ReSharper disable once UseDeconstruction - foreach (var cultureInfo in entity.CultureInfos) - if (cultureInfo.Name != entity.GetPublishName(cultureInfo.Culture)) - { - edited = true; - (editedCultures ?? (editedCultures = new HashSet(StringComparer.OrdinalIgnoreCase))).Add(cultureInfo.Culture); + // and, if publishing, insert new content & document version dtos + if (publishing) + { + entity.PublishedVersionId = entity.VersionId; - // TODO: change tracking - // at the moment, we don't do any dirty tracking on property values, so we don't know whether the - // culture has just been edited or not, so we don't update its update date - that date only changes - // when the name is set, and it all works because the controller does it - but, if someone uses a - // service to change a property value and save (without setting name), the update date does not change. - } + contentVersionDto.Id = 0; // want a new id + contentVersionDto.Current = true; // current version + contentVersionDto.Text = entity.Name; + Database.Insert(contentVersionDto); + entity.VersionId = documentVersionDto.Id = contentVersionDto.Id; // get the new id - // replace the content version variations (rather than updating) + documentVersionDto.Published = false; // non-published version + Database.Insert(documentVersionDto); + } + + // replace the property data (rather than updating) // only need to delete for the version that existed, the new version (if any) has no property data yet - var deleteContentVariations = Sql().Delete().Where(x => x.VersionId == versionToDelete); - Database.Execute(deleteContentVariations); + var versionToDelete = publishing ? entity.PublishedVersionId : entity.VersionId; + var deletePropertyDataSql = Sql().Delete().Where(x => x.VersionId == versionToDelete); + Database.Execute(deletePropertyDataSql); - // replace the document version variations (rather than updating) - var deleteDocumentVariations = Sql().Delete().Where(x => x.NodeId == entity.Id); - Database.Execute(deleteDocumentVariations); + // insert property data + var propertyDataDtos = PropertyFactory.BuildDtos(entity.ContentType.Variations, entity.VersionId, publishing ? entity.PublishedVersionId : 0, + entity.Properties, LanguageRepository, out var edited, out var editedCultures); + foreach (var propertyDataDto in propertyDataDtos) + Database.Insert(propertyDataDto); - // TODO: NPoco InsertBulk issue? - // we should use the native NPoco InsertBulk here but it causes problems (not sure exactly all scenarios) - // but by using SQL Server and updating a variants name will cause: Unable to cast object of type - // 'Umbraco.Core.Persistence.FaultHandling.RetryDbConnection' to type 'System.Data.SqlClient.SqlConnection'. - // (same in PersistNewItem above) + // if !publishing, we may have a new name != current publish name, + // also impacts 'edited' + if (!publishing && entity.PublishName != entity.Name) + edited = true; - // insert content variations - Database.BulkInsertRecords(GetContentVariationDtos(entity, publishing)); + if (entity.ContentType.VariesByCulture()) + { + // bump dates to align cultures to version + if (publishing) + entity.AdjustDates(contentVersionDto.VersionDate); - // insert document variations - Database.BulkInsertRecords(GetDocumentVariationDtos(entity, editedCultures)); + // names also impact 'edited' + // ReSharper disable once UseDeconstruction + foreach (var cultureInfo in entity.CultureInfos) + if (cultureInfo.Name != entity.GetPublishName(cultureInfo.Culture)) + { + edited = true; + (editedCultures ?? (editedCultures = new HashSet(StringComparer.OrdinalIgnoreCase))).Add(cultureInfo.Culture); + + // TODO: change tracking + // at the moment, we don't do any dirty tracking on property values, so we don't know whether the + // culture has just been edited or not, so we don't update its update date - that date only changes + // when the name is set, and it all works because the controller does it - but, if someone uses a + // service to change a property value and save (without setting name), the update date does not change. + } + + // replace the content version variations (rather than updating) + // only need to delete for the version that existed, the new version (if any) has no property data yet + var deleteContentVariations = Sql().Delete().Where(x => x.VersionId == versionToDelete); + Database.Execute(deleteContentVariations); + + // replace the document version variations (rather than updating) + var deleteDocumentVariations = Sql().Delete().Where(x => x.NodeId == entity.Id); + Database.Execute(deleteDocumentVariations); + + // TODO: NPoco InsertBulk issue? + // we should use the native NPoco InsertBulk here but it causes problems (not sure exactly all scenarios) + // but by using SQL Server and updating a variants name will cause: Unable to cast object of type + // 'Umbraco.Core.Persistence.FaultHandling.RetryDbConnection' to type 'System.Data.SqlClient.SqlConnection'. + // (same in PersistNewItem above) + + // insert content variations + Database.BulkInsertRecords(GetContentVariationDtos(entity, publishing)); + + // insert document variations + Database.BulkInsertRecords(GetDocumentVariationDtos(entity, editedCultures)); + } + + // refresh content + entity.SetCultureEdited(editedCultures); + + // update the document dto + // at that point, when un/publishing, the entity still has its old Published value + // so we need to explicitly update the dto to persist the correct value + if (entity.PublishedState == PublishedState.Publishing) + dto.Published = true; + else if (entity.PublishedState == PublishedState.Unpublishing) + dto.Published = false; + entity.Edited = dto.Edited = !dto.Published || edited; // if not published, always edited + Database.Update(dto); + + //update the schedule + if (entity.IsPropertyDirty("ContentSchedule")) + PersistContentSchedule(entity, true); + + // if entity is publishing, update tags, else leave tags there + // means that implicitly unpublished, or trashed, entities *still* have tags in db + if (entity.PublishedState == PublishedState.Publishing) + SetEntityTags(entity, _tagRepository); } - // refresh content - entity.SetCultureEdited(editedCultures); - - // update the document dto - // at that point, when un/publishing, the entity still has its old Published value - // so we need to explicitly update the dto to persist the correct value - if (entity.PublishedState == PublishedState.Publishing) - dto.Published = true; - else if (entity.PublishedState == PublishedState.Unpublishing) - dto.Published = false; - entity.Edited = dto.Edited = !dto.Published || edited; // if not published, always edited - Database.Update(dto); - - //update the schedule - if (entity.IsPropertyDirty("ContentSchedule")) - PersistContentSchedule(entity, true); - - // if entity is publishing, update tags, else leave tags there - // means that implicitly unpublished, or trashed, entities *still* have tags in db - if (entity.PublishedState == PublishedState.Publishing) - SetEntityTags(entity, _tagRepository); - // trigger here, before we reset Published etc OnUowRefreshedEntity(new ScopedEntityEventArgs(AmbientScope, entity)); - // flip the entity's published property - // this also flips its published state - if (entity.PublishedState == PublishedState.Publishing) + if (!isMoving) { - entity.Published = true; - entity.PublishTemplateId = entity.TemplateId; - entity.PublisherId = entity.WriterId; - entity.PublishName = entity.Name; - entity.PublishDate = entity.UpdateDate; + // flip the entity's published property + // this also flips its published state + if (entity.PublishedState == PublishedState.Publishing) + { + entity.Published = true; + entity.PublishTemplateId = entity.TemplateId; + entity.PublisherId = entity.WriterId; + entity.PublishName = entity.Name; + entity.PublishDate = entity.UpdateDate; - SetEntityTags(entity, _tagRepository); + SetEntityTags(entity, _tagRepository); + } + else if (entity.PublishedState == PublishedState.Unpublishing) + { + entity.Published = false; + entity.PublishTemplateId = null; + entity.PublisherId = null; + entity.PublishName = null; + entity.PublishDate = null; + + ClearEntityTags(entity, _tagRepository); + } + + PersistRelations(entity); + + // TODO: note re. tags: explicitly unpublished entities have cleared tags, but masked or trashed entities *still* have tags in the db - so what? } - else if (entity.PublishedState == PublishedState.Unpublishing) - { - entity.Published = false; - entity.PublishTemplateId = null; - entity.PublisherId = null; - entity.PublishName = null; - entity.PublishDate = null; - - ClearEntityTags(entity, _tagRepository); - } - - // TODO: note re. tags: explicitly unpublished entities have cleared tags, but masked or trashed entities *still* have tags in the db - so what? entity.ResetDirtyProperties(); @@ -1163,7 +1200,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (temp.Template2Id.HasValue && templates.ContainsKey(temp.Template2Id.Value)) temp.Content.PublishTemplateId = temp.Template2Id; } - + // set properties if (loadProperties) @@ -1196,7 +1233,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement SetVariations(temp.Content, contentVariations, documentVariations); } } - + foreach (var c in content) @@ -1410,7 +1447,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement yield return dto; } - + } private class ContentVariation diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs index 161db543ba..24040bf393 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs @@ -35,21 +35,33 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected ISqlSyntaxProvider SqlSyntax => _scopeAccessor.AmbientScope.SqlContext.SqlSyntax; #region Repository - - // get a page of entities + public IEnumerable GetPagedResultsByQuery(IQuery query, Guid objectType, long pageIndex, int pageSize, out long totalRecords, IQuery filter, Ordering ordering) { - var isContent = objectType == Constants.ObjectTypes.Document || objectType == Constants.ObjectTypes.DocumentBlueprint; - var isMedia = objectType == Constants.ObjectTypes.Media; - var isMember = objectType == Constants.ObjectTypes.Member; + return GetPagedResultsByQuery(query, new[] { objectType }, pageIndex, pageSize, out totalRecords, filter, ordering); + } - var sql = GetBaseWhere(isContent, isMedia, isMember, false, x => + // get a page of entities + public IEnumerable GetPagedResultsByQuery(IQuery query, Guid[] objectTypes, long pageIndex, int pageSize, out long totalRecords, + IQuery filter, Ordering ordering, Action> sqlCustomization = null) + { + var isContent = objectTypes.Any(objectType => objectType == Constants.ObjectTypes.Document || objectType == Constants.ObjectTypes.DocumentBlueprint); + var isMedia = objectTypes.Any(objectType => objectType == Constants.ObjectTypes.Media); + var isMember = objectTypes.Any(objectType => objectType == Constants.ObjectTypes.Member); + + var sql = GetBaseWhere(isContent, isMedia, isMember, false, s => { - if (filter == null) return; - foreach (var filterClause in filter.GetWhereClauses()) - x.Where(filterClause.Item1, filterClause.Item2); - }, objectType); + sqlCustomization?.Invoke(s); + + if (filter != null) + { + foreach (var filterClause in filter.GetWhereClauses()) + s.Where(filterClause.Item1, filterClause.Item2); + } + + + }, objectTypes); ordering = ordering ?? Ordering.ByDefault(); @@ -70,35 +82,13 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // for content we must query for ContentEntityDto entities to produce the correct culture variant entity names var pageIndexToFetch = pageIndex + 1; IEnumerable dtos; - if(isContent) - { - var page = Database.Page(pageIndexToFetch, pageSize, sql); - dtos = page.Items; - totalRecords = page.TotalItems; - } - else if (isMedia) - { - var page = Database.Page(pageIndexToFetch, pageSize, sql); - dtos = page.Items; - totalRecords = page.TotalItems; - } - else if (isMember) - { - var page = Database.Page(pageIndexToFetch, pageSize, sql); - dtos = page.Items; - totalRecords = page.TotalItems; - } - else - { - var page = Database.Page(pageIndexToFetch, pageSize, sql); - dtos = page.Items; - totalRecords = page.TotalItems; - } + var page = Database.Page(pageIndexToFetch, pageSize, sql); + dtos = page.Items; + totalRecords = page.TotalItems; - var entities = dtos.Select(x => BuildEntity(isContent, isMedia, isMember, x)).ToArray(); + var entities = dtos.Select(BuildEntity).ToArray(); - if (isContent) - BuildVariants(entities.Cast()); + BuildVariants(entities.OfType()); return entities; } @@ -107,7 +97,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { var sql = GetBaseWhere(false, false, false, false, key); var dto = Database.FirstOrDefault(sql); - return dto == null ? null : BuildEntity(false, false, false, dto); + return dto == null ? null : BuildEntity(dto); } @@ -116,7 +106,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement //isContent is going to return a 1:M result now with the variants so we need to do different things if (isContent) { - var cdtos = Database.Fetch(sql); + var cdtos = Database.Fetch(sql); return cdtos.Count == 0 ? null : BuildVariants(BuildDocumentEntity(cdtos[0])); } @@ -127,7 +117,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (dto == null) return null; - var entity = BuildEntity(false, isMedia, isMember, dto); + var entity = BuildEntity(dto); return entity; } @@ -146,7 +136,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { var sql = GetBaseWhere(false, false, false, false, id); var dto = Database.FirstOrDefault(sql); - return dto == null ? null : BuildEntity(false, false, false, dto); + return dto == null ? null : BuildEntity(dto); } public IEntitySlim Get(int id, Guid objectTypeId) @@ -178,7 +168,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement //isContent is going to return a 1:M result now with the variants so we need to do different things if (isContent) { - var cdtos = Database.Fetch(sql); + var cdtos = Database.Fetch(sql); return cdtos.Count == 0 ? Enumerable.Empty() @@ -189,7 +179,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement ? (IEnumerable)Database.Fetch(sql) : Database.Fetch(sql); - var entities = dtos.Select(x => BuildEntity(false, isMedia, isMember, x)).ToArray(); + var entities = dtos.Select(BuildEntity).ToArray(); return entities; } @@ -233,7 +223,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var sql = translator.Translate(); sql = AddGroupBy(false, false, false, sql, true); var dtos = Database.Fetch(sql); - return dtos.Select(x => BuildEntity(false, false, false, x)).ToList(); + return dtos.Select(BuildEntity).ToList(); } public IEnumerable GetByQuery(IQuery query, Guid objectType) @@ -242,7 +232,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var isMedia = objectType == Constants.ObjectTypes.Media; var isMember = objectType == Constants.ObjectTypes.Member; - var sql = GetBaseWhere(isContent, isMedia, isMember, false, null, objectType); + var sql = GetBaseWhere(isContent, isMedia, isMember, false, null, new[] { objectType }); var translator = new SqlTranslator(sql, query); sql = translator.Translate(); @@ -356,14 +346,14 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // gets the full sql for a given object type, with a given filter protected Sql GetFullSqlForEntityType(bool isContent, bool isMedia, bool isMember, Guid objectType, Action> filter) { - var sql = GetBaseWhere(isContent, isMedia, isMember, false, filter, objectType); + var sql = GetBaseWhere(isContent, isMedia, isMember, false, filter, new[] { objectType }); return AddGroupBy(isContent, isMedia, isMember, sql, true); } // gets the base SELECT + FROM [+ filter] sql // always from the 'current' content version protected Sql GetBase(bool isContent, bool isMedia, bool isMember, Action> filter, bool isCount = false) - { + { var sql = Sql(); if (isCount) @@ -401,15 +391,15 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (isContent || isMedia || isMember) { sql - .InnerJoin().On((left, right) => left.NodeId == right.NodeId && right.Current) - .InnerJoin().On((left, right) => left.NodeId == right.NodeId) - .InnerJoin().On((left, right) => left.ContentTypeId == right.NodeId); + .LeftJoin().On((left, right) => left.NodeId == right.NodeId && right.Current) + .LeftJoin().On((left, right) => left.NodeId == right.NodeId) + .LeftJoin().On((left, right) => left.ContentTypeId == right.NodeId); } if (isContent) { sql - .InnerJoin().On((left, right) => left.NodeId == right.NodeId); + .LeftJoin().On((left, right) => left.NodeId == right.NodeId); } if (isMedia) @@ -433,10 +423,14 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // gets the base SELECT + FROM [+ filter] + WHERE sql // for a given object type, with a given filter - protected Sql GetBaseWhere(bool isContent, bool isMedia, bool isMember, bool isCount, Action> filter, Guid objectType) + protected Sql GetBaseWhere(bool isContent, bool isMedia, bool isMember, bool isCount, Action> filter, Guid[] objectTypes) { - return GetBase(isContent, isMedia, isMember, filter, isCount) - .Where(x => x.NodeObjectType == objectType); + var sql = GetBase(isContent, isMedia, isMember, filter, isCount); + if (objectTypes.Length > 0) + { + sql.WhereIn(x => x.NodeObjectType, objectTypes); + } + return sql; } // gets the base SELECT + FROM + WHERE sql @@ -510,8 +504,19 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (sql == null) throw new ArgumentNullException(nameof(sql)); if (ordering == null) throw new ArgumentNullException(nameof(ordering)); - // TODO: although this works for name, it probably doesn't work for others without an alias of some sort - var orderBy = ordering.OrderBy; + // TODO: although the default ordering string works for name, it wont work for others without a table or an alias of some sort + // As more things are attempted to be sorted we'll prob have to add more expressions here + string orderBy; + switch (ordering.OrderBy.ToUpperInvariant()) + { + case "PATH": + orderBy = SqlSyntax.GetQuotedColumn(NodeDto.TableName, "path"); + break; + + default: + orderBy = ordering.OrderBy; + break; + } if (ordering.Direction == Direction.Ascending) sql.OrderBy(orderBy); @@ -524,9 +529,17 @@ namespace Umbraco.Core.Persistence.Repositories.Implement #region Classes /// - /// The DTO used to fetch results for a content item with its variation info + /// The DTO used to fetch results for a generic content item which could be either a document, media or a member /// - private class ContentEntityDto : BaseDto + private class GenericContentEntityDto : DocumentEntityDto + { + public string MediaPath { get; set; } + } + + /// + /// The DTO used to fetch results for a document item with its variation info + /// + private class DocumentEntityDto : BaseDto { public ContentVariation Variations { get; set; } @@ -534,11 +547,17 @@ namespace Umbraco.Core.Persistence.Repositories.Implement public bool Edited { get; set; } } + /// + /// The DTO used to fetch results for a media item with its media path info + /// private class MediaEntityDto : BaseDto { public string MediaPath { get; set; } } + /// + /// The DTO used to fetch results for a member item + /// private class MemberEntityDto : BaseDto { } @@ -589,13 +608,13 @@ namespace Umbraco.Core.Persistence.Repositories.Implement #region Factory - private EntitySlim BuildEntity(bool isContent, bool isMedia, bool isMember, BaseDto dto) + private EntitySlim BuildEntity(BaseDto dto) { - if (isContent) + if (dto.NodeObjectType == Constants.ObjectTypes.Document) return BuildDocumentEntity(dto); - if (isMedia) + if (dto.NodeObjectType == Constants.ObjectTypes.Media) return BuildMediaEntity(dto); - if (isMember) + if (dto.NodeObjectType == Constants.ObjectTypes.Member) return BuildMemberEntity(dto); // EntitySlim does not track changes @@ -635,10 +654,14 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var entity = new MediaEntitySlim(); BuildContentEntity(entity, dto); - if (dto is MediaEntityDto contentDto) + // fill in the media info + if (dto is MediaEntityDto mediaEntityDto) { - // fill in the media info - entity.MediaPath = contentDto.MediaPath; + entity.MediaPath = mediaEntityDto.MediaPath; + } + else if (dto is GenericContentEntityDto genericContentEntityDto) + { + entity.MediaPath = genericContentEntityDto.MediaPath; } return entity; @@ -650,7 +673,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var entity = new DocumentEntitySlim(); BuildContentEntity(entity, dto); - if (dto is ContentEntityDto contentDto) + if (dto is DocumentEntityDto contentDto) { // fill in the invariant info entity.Edited = contentDto.Edited; diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/InstallationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/InstallationRepository.cs new file mode 100644 index 0000000000..49fbfe096b --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/InstallationRepository.cs @@ -0,0 +1,28 @@ +using System.Net.Http; +using System.Net.Http.Formatting; +using System.Threading.Tasks; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Persistence.Repositories.Implement +{ + internal class InstallationRepository : IInstallationRepository + { + private static HttpClient _httpClient; + private const string RestApiInstallUrl = "https://our.umbraco.com/umbraco/api/Installation/Install"; + + + public async Task SaveInstallLogAsync(InstallLog installLog) + { + try + { + if (_httpClient == null) + _httpClient = new HttpClient(); + + await _httpClient.PostAsync(RestApiInstallUrl, installLog, new JsonMediaTypeFormatter()); + } + // this occurs if the server for Our is down or cannot be reached + catch (HttpRequestException) + { } + } + } +} diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MacroRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MacroRepository.cs index f0044e225d..1f0b45614b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MacroRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MacroRepository.cs @@ -40,7 +40,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (macroDto == null) return null; - + var entity = MacroFactory.BuildEntity(macroDto); // reset dirty initial properties (U4-1946) @@ -153,7 +153,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override void PersistUpdatedItem(IMacro entity) { entity.UpdatingEntity(); -; var dto = MacroFactory.BuildDto(entity); Database.Update(dto); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs index f4fb69258c..242f21c749 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs @@ -12,6 +12,7 @@ using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.PropertyEditors; using Umbraco.Core.Scoping; using Umbraco.Core.Services; using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics; @@ -27,8 +28,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement private readonly ITagRepository _tagRepository; private readonly MediaByGuidReadRepository _mediaByGuidReadRepository; - public MediaRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IMediaTypeRepository mediaTypeRepository, ITagRepository tagRepository, ILanguageRepository languageRepository) - : base(scopeAccessor, cache, languageRepository, logger) + public MediaRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IMediaTypeRepository mediaTypeRepository, ITagRepository tagRepository, ILanguageRepository languageRepository, IRelationRepository relationRepository, IRelationTypeRepository relationTypeRepository, + Lazy propertyEditorCollection, DataValueReferenceFactoryCollection dataValueReferenceFactories) + : base(scopeAccessor, cache, logger, languageRepository, relationRepository, relationTypeRepository, propertyEditorCollection, dataValueReferenceFactories) { _mediaTypeRepository = mediaTypeRepository ?? throw new ArgumentNullException(nameof(mediaTypeRepository)); _tagRepository = tagRepository ?? throw new ArgumentNullException(nameof(tagRepository)); @@ -217,7 +219,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override void PersistNewItem(IMedia entity) { - var media = (Models.Media) entity; entity.AddingEntity(); // ensure unique name on the same level @@ -272,21 +273,23 @@ namespace Umbraco.Core.Persistence.Repositories.Implement contentVersionDto.NodeId = nodeDto.NodeId; contentVersionDto.Current = true; Database.Insert(contentVersionDto); - media.VersionId = contentVersionDto.Id; + entity.VersionId = contentVersionDto.Id; // persist the media version dto var mediaVersionDto = dto.MediaVersionDto; - mediaVersionDto.Id = media.VersionId; + mediaVersionDto.Id = entity.VersionId; Database.Insert(mediaVersionDto); // persist the property data - var propertyDataDtos = PropertyFactory.BuildDtos(media.ContentType.Variations, media.VersionId, 0, entity.Properties, LanguageRepository, out _, out _); + var propertyDataDtos = PropertyFactory.BuildDtos(entity.ContentType.Variations, entity.VersionId, 0, entity.Properties, LanguageRepository, out _, out _); foreach (var propertyDataDto in propertyDataDtos) Database.Insert(propertyDataDto); // set tags SetEntityTags(entity, _tagRepository); + PersistRelations(entity); + OnUowRefreshedEntity(new ScopedEntityEventArgs(AmbientScope, entity)); entity.ResetDirtyProperties(); @@ -294,26 +297,32 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override void PersistUpdatedItem(IMedia entity) { - var media = (Models.Media) entity; - // update - media.UpdatingEntity(); + entity.UpdatingEntity(); - // ensure unique name on the same level - entity.Name = EnsureUniqueNodeName(entity.ParentId, entity.Name, entity.Id); + // Check if this entity is being moved as a descendant as part of a bulk moving operations. + // In this case we can bypass a lot of the below operations which will make this whole operation go much faster. + // When moving we don't need to create new versions, etc... because we cannot roll this operation back anyways. + var isMoving = entity.IsMoving(); - // ensure that strings don't contain characters that are invalid in xml - // TODO: do we really want to keep doing this here? - entity.SanitizeEntityPropertiesForXmlStorage(); - - // if parent has changed, get path, level and sort order - if (entity.IsPropertyDirty("ParentId")) + if (!isMoving) { - var parent = GetParentNodeDto(entity.ParentId); + // ensure unique name on the same level + entity.Name = EnsureUniqueNodeName(entity.ParentId, entity.Name, entity.Id); - entity.Path = string.Concat(parent.Path, ",", entity.Id); - entity.Level = parent.Level + 1; - entity.SortOrder = GetNewChildSortOrder(entity.ParentId, 0); + // ensure that strings don't contain characters that are invalid in xml + // TODO: do we really want to keep doing this here? + entity.SanitizeEntityPropertiesForXmlStorage(); + + // if parent has changed, get path, level and sort order + if (entity.IsPropertyDirty(nameof(entity.ParentId))) + { + var parent = GetParentNodeDto(entity.ParentId); + + entity.Path = string.Concat(parent.Path, ",", entity.Id); + entity.Level = parent.Level + 1; + entity.SortOrder = GetNewChildSortOrder(entity.ParentId, 0); + } } // create the dto @@ -324,24 +333,29 @@ namespace Umbraco.Core.Persistence.Repositories.Implement nodeDto.ValidatePathWithException(); Database.Update(nodeDto); - // update the content dto - Database.Update(dto.ContentDto); + if (!isMoving) + { + // update the content dto + Database.Update(dto.ContentDto); - // update the content & media version dtos - var contentVersionDto = dto.MediaVersionDto.ContentVersionDto; - var mediaVersionDto = dto.MediaVersionDto; - contentVersionDto.Current = true; - Database.Update(contentVersionDto); - Database.Update(mediaVersionDto); + // update the content & media version dtos + var contentVersionDto = dto.MediaVersionDto.ContentVersionDto; + var mediaVersionDto = dto.MediaVersionDto; + contentVersionDto.Current = true; + Database.Update(contentVersionDto); + Database.Update(mediaVersionDto); - // replace the property data - var deletePropertyDataSql = SqlContext.Sql().Delete().Where(x => x.VersionId == media.VersionId); - Database.Execute(deletePropertyDataSql); - var propertyDataDtos = PropertyFactory.BuildDtos(media.ContentType.Variations, media.VersionId, 0, entity.Properties, LanguageRepository, out _, out _); - foreach (var propertyDataDto in propertyDataDtos) - Database.Insert(propertyDataDto); + // replace the property data + var deletePropertyDataSql = SqlContext.Sql().Delete().Where(x => x.VersionId == entity.VersionId); + Database.Execute(deletePropertyDataSql); + var propertyDataDtos = PropertyFactory.BuildDtos(entity.ContentType.Variations, entity.VersionId, 0, entity.Properties, LanguageRepository, out _, out _); + foreach (var propertyDataDto in propertyDataDtos) + Database.Insert(propertyDataDto); - SetEntityTags(entity, _tagRepository); + SetEntityTags(entity, _tagRepository); + + PersistRelations(entity); + } OnUowRefreshedEntity(new ScopedEntityEventArgs(AmbientScope, entity)); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs index 892122dff9..42e7d1c32f 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs @@ -10,6 +10,7 @@ using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.PropertyEditors; using Umbraco.Core.Scoping; using Umbraco.Core.Services; using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics; @@ -25,8 +26,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement private readonly ITagRepository _tagRepository; private readonly IMemberGroupRepository _memberGroupRepository; - public MemberRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IMemberTypeRepository memberTypeRepository, IMemberGroupRepository memberGroupRepository, ITagRepository tagRepository, ILanguageRepository languageRepository) - : base(scopeAccessor, cache, languageRepository, logger) + public MemberRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, + IMemberTypeRepository memberTypeRepository, IMemberGroupRepository memberGroupRepository, ITagRepository tagRepository, ILanguageRepository languageRepository, IRelationRepository relationRepository, IRelationTypeRepository relationTypeRepository, + Lazy propertyEditors, DataValueReferenceFactoryCollection dataValueReferenceFactories) + : base(scopeAccessor, cache, logger, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferenceFactories) { _memberTypeRepository = memberTypeRepository ?? throw new ArgumentNullException(nameof(memberTypeRepository)); _tagRepository = tagRepository ?? throw new ArgumentNullException(nameof(tagRepository)); @@ -321,6 +324,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement SetEntityTags(entity, _tagRepository); + PersistRelations(entity); + OnUowRefreshedEntity(new ScopedEntityEventArgs(AmbientScope, entity)); entity.ResetDirtyProperties(); @@ -386,6 +391,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement SetEntityTags(entity, _tagRepository); + PersistRelations(entity); + OnUowRefreshedEntity(new ScopedEntityEventArgs(AmbientScope, entity)); entity.ResetDirtyProperties(); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs index 4b4af505b8..56a6336f75 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs @@ -6,10 +6,14 @@ using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Scoping; +using Umbraco.Core.Services; +using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics; namespace Umbraco.Core.Persistence.Repositories.Implement { @@ -19,11 +23,13 @@ namespace Umbraco.Core.Persistence.Repositories.Implement internal class RelationRepository : NPocoRepositoryBase, IRelationRepository { private readonly IRelationTypeRepository _relationTypeRepository; + private readonly IEntityRepository _entityRepository; - public RelationRepository(IScopeAccessor scopeAccessor, ILogger logger, IRelationTypeRepository relationTypeRepository) + public RelationRepository(IScopeAccessor scopeAccessor, ILogger logger, IRelationTypeRepository relationTypeRepository, IEntityRepository entityRepository) : base(scopeAccessor, AppCaches.NoCache, logger) { _relationTypeRepository = relationTypeRepository; + _entityRepository = entityRepository; } #region Overrides of RepositoryBase @@ -39,10 +45,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var relationType = _relationTypeRepository.Get(dto.RelationType); if (relationType == null) - throw new Exception(string.Format("RelationType with Id: {0} doesn't exist", dto.RelationType)); + throw new InvalidOperationException(string.Format("RelationType with Id: {0} doesn't exist", dto.RelationType)); - var factory = new RelationFactory(relationType); - return DtoToEntity(dto, factory); + return DtoToEntity(dto, relationType); } protected override IEnumerable PerformGetAll(params int[] ids) @@ -67,26 +72,17 @@ namespace Umbraco.Core.Persistence.Repositories.Implement private IEnumerable DtosToEntities(IEnumerable dtos) { - // in most cases, the relation type will be the same for all of them, - // plus we've ordered the relations by type, so try to allocate as few - // factories as possible - bearing in mind that relation types are cached - RelationFactory factory = null; - var relationTypeId = -1; + //NOTE: This is N+1, BUT ALL relation types are cached so shouldn't matter - return dtos.Select(x => - { - if (relationTypeId != x.RelationType) - factory = new RelationFactory(_relationTypeRepository.Get(relationTypeId = x.RelationType)); - return DtoToEntity(x, factory); - }).ToList(); + return dtos.Select(x => DtoToEntity(x, _relationTypeRepository.Get(x.RelationType))).ToList(); } - private static IRelation DtoToEntity(RelationDto dto, RelationFactory factory) + private static IRelation DtoToEntity(RelationDto dto, IRelationType relationType) { - var entity = factory.BuildEntity(dto); + var entity = RelationFactory.BuildEntity(dto, relationType); // reset dirty initial properties (U4-1946) - ((BeingDirtyBase)entity).ResetDirtyProperties(false); + entity.ResetDirtyProperties(false); return entity; } @@ -97,14 +93,18 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override Sql GetBaseQuery(bool isCount) { - var sql = Sql(); + if (isCount) + { + return Sql().SelectCount().From(); + } - sql = isCount - ? sql.SelectCount() - : sql.Select(); + var sql = Sql().Select() + .AndSelect("uchild", x => Alias(x.NodeObjectType, "childObjectType")) + .AndSelect("uparent", x => Alias(x.NodeObjectType, "parentObjectType")) + .From() + .InnerJoin("uchild").On((rel, node) => rel.ChildId == node.NodeId, aliasRight: "uchild") + .InnerJoin("uparent").On((rel, node) => rel.ParentId == node.NodeId, aliasRight: "uparent"); - sql - .From(); return sql; } @@ -136,11 +136,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { entity.AddingEntity(); - var factory = new RelationFactory(entity.RelationType); - var dto = factory.BuildDto(entity); + var dto = RelationFactory.BuildDto(entity); var id = Convert.ToInt32(Database.Insert(dto)); + entity.Id = id; + PopulateObjectTypes(entity); entity.ResetDirtyProperties(); } @@ -149,13 +150,192 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { entity.UpdatingEntity(); - var factory = new RelationFactory(entity.RelationType); - var dto = factory.BuildDto(entity); + var dto = RelationFactory.BuildDto(entity); Database.Update(dto); + PopulateObjectTypes(entity); + entity.ResetDirtyProperties(); } #endregion + + /// + /// Used for joining the entity query with relations for the paging methods + /// + /// + private void SqlJoinRelations(Sql sql) + { + // add left joins for relation tables (this joins on both child or parent, so beware that this will normally return entities for + // both sides of the relation type unless the IUmbracoEntity query passed in filters one side out). + sql.LeftJoin().On((left, right) => left.NodeId == right.ChildId || left.NodeId == right.ParentId); + sql.LeftJoin().On((left, right) => left.RelationType == right.Id); + } + + public IEnumerable GetPagedParentEntitiesByChildId(int childId, long pageIndex, int pageSize, out long totalRecords, params Guid[] entityTypes) + { + // var contentObjectTypes = new[] { Constants.ObjectTypes.Document, Constants.ObjectTypes.Media, Constants.ObjectTypes.Member } + // we could pass in the contentObjectTypes so that the entity repository sql is configured to do full entity lookups so that we get the full data + // required to populate content, media or members, else we get the bare minimum data needed to populate an entity. BUT if we do this it + // means that the SQL is less efficient and returns data that is probably not needed for what we need this lookup for. For the time being we + // will just return the bare minimum entity data. + + return _entityRepository.GetPagedResultsByQuery(Query(), entityTypes, pageIndex, pageSize, out totalRecords, null, null, sql => + { + SqlJoinRelations(sql); + + sql.Where(rel => rel.ChildId == childId); + sql.Where((rel, node) => rel.ParentId == childId || node.NodeId != childId); + }); + } + + public IEnumerable GetPagedChildEntitiesByParentId(int parentId, long pageIndex, int pageSize, out long totalRecords, params Guid[] entityTypes) + { + // var contentObjectTypes = new[] { Constants.ObjectTypes.Document, Constants.ObjectTypes.Media, Constants.ObjectTypes.Member } + // we could pass in the contentObjectTypes so that the entity repository sql is configured to do full entity lookups so that we get the full data + // required to populate content, media or members, else we get the bare minimum data needed to populate an entity. BUT if we do this it + // means that the SQL is less efficient and returns data that is probably not needed for what we need this lookup for. For the time being we + // will just return the bare minimum entity data. + + return _entityRepository.GetPagedResultsByQuery(Query(), entityTypes, pageIndex, pageSize, out totalRecords, null, null, sql => + { + SqlJoinRelations(sql); + + sql.Where(rel => rel.ParentId == parentId); + sql.Where((rel, node) => rel.ChildId == parentId || node.NodeId != parentId); + }); + } + + public void Save(IEnumerable relations) + { + foreach (var hasIdentityGroup in relations.GroupBy(r => r.HasIdentity)) + { + if (hasIdentityGroup.Key) + { + // Do updates, we can't really do a bulk update so this is still a 1 by 1 operation + // however we can bulk populate the object types. It might be possible to bulk update + // with SQL but would be pretty ugly and we're not really too worried about that for perf, + // it's the bulk inserts we care about. + var asArray = hasIdentityGroup.ToArray(); + foreach (var relation in hasIdentityGroup) + { + relation.UpdatingEntity(); + var dto = RelationFactory.BuildDto(relation); + Database.Update(dto); + } + PopulateObjectTypes(asArray); + } + else + { + // Do bulk inserts + var entitiesAndDtos = hasIdentityGroup.ToDictionary( + r => // key = entity + { + r.AddingEntity(); + return r; + }, + RelationFactory.BuildDto); // value = DTO + + // Use NPoco's own InsertBulk command which will automatically re-populate the new Ids on the entities, our own + // BulkInsertRecords does not cater for this. + Database.InsertBulk(entitiesAndDtos.Values); + + // All dtos now have IDs assigned + foreach (var de in entitiesAndDtos) + { + // re-assign ID to the entity + de.Key.Id = de.Value.Id; + } + + PopulateObjectTypes(entitiesAndDtos.Keys.ToArray()); + } + } + } + + public IEnumerable GetPagedRelationsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, Ordering ordering) + { + var sql = GetBaseQuery(false); + + if (ordering == null || ordering.IsEmpty) + ordering = Ordering.By(SqlSyntax.GetQuotedColumn(Constants.DatabaseSchema.Tables.Relation, "id")); + + var translator = new SqlTranslator(sql, query); + sql = translator.Translate(); + + // apply ordering + ApplyOrdering(ref sql, ordering); + + var pageIndexToFetch = pageIndex + 1; + var page = Database.Page(pageIndexToFetch, pageSize, sql); + var dtos = page.Items; + totalRecords = page.TotalItems; + + var relTypes = _relationTypeRepository.GetMany(dtos.Select(x => x.RelationType).Distinct().ToArray()) + .ToDictionary(x => x.Id, x => x); + + var result = dtos.Select(r => + { + if (!relTypes.TryGetValue(r.RelationType, out var relType)) + throw new InvalidOperationException(string.Format("RelationType with Id: {0} doesn't exist", r.RelationType)); + return DtoToEntity(r, relType); + }).ToList(); + + return result; + } + + + public void DeleteByParent(int parentId, params string[] relationTypeAliases) + { + var subQuery = Sql().Select(x => x.Id) + .From() + .InnerJoin().On(x => x.RelationType, x => x.Id) + .Where(x => x.ParentId == parentId); + + if (relationTypeAliases.Length > 0) + { + subQuery.WhereIn(x => x.Alias, relationTypeAliases); + } + + Database.Execute(Sql().Delete().WhereIn(x => x.Id, subQuery)); + } + + /// + /// Used to populate the object types after insert/update + /// + /// + private void PopulateObjectTypes(params IRelation[] entities) + { + var entityIds = entities.Select(x => x.ParentId).Concat(entities.Select(y => y.ChildId)).Distinct(); + + var nodes = Database.Fetch(Sql().Select().From() + .WhereIn(x => x.NodeId, entityIds)) + .ToDictionary(x => x.NodeId, x => x.NodeObjectType); + + foreach (var e in entities) + { + if (nodes.TryGetValue(e.ParentId, out var parentObjectType)) + { + e.ParentObjectType = parentObjectType.GetValueOrDefault(); + } + if (nodes.TryGetValue(e.ChildId, out var childObjectType)) + { + e.ChildObjectType = childObjectType.GetValueOrDefault(); + } + } + } + + private void ApplyOrdering(ref Sql sql, Ordering ordering) + { + if (sql == null) throw new ArgumentNullException(nameof(sql)); + if (ordering == null) throw new ArgumentNullException(nameof(ordering)); + + // TODO: although this works for name, it probably doesn't work for others without an alias of some sort + var orderBy = ordering.OrderBy; + + if (ordering.Direction == Direction.Ascending) + sql.OrderBy(orderBy); + else + sql.OrderByDescending(orderBy); + } } } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/RelationTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/RelationTypeRepository.cs index 075d4aa769..623b55b6f8 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/RelationTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/RelationTypeRepository.cs @@ -134,7 +134,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override void PersistNewItem(IRelationType entity) { entity.AddingEntity(); - + + CheckNullObjectTypeValues(entity); + var dto = RelationTypeFactory.BuildDto(entity); var id = Convert.ToInt32(Database.Insert(dto)); @@ -146,7 +148,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override void PersistUpdatedItem(IRelationType entity) { entity.UpdatingEntity(); - + + CheckNullObjectTypeValues(entity); + var dto = RelationTypeFactory.BuildDto(entity); Database.Update(dto); @@ -154,5 +158,13 @@ namespace Umbraco.Core.Persistence.Repositories.Implement } #endregion + + private void CheckNullObjectTypeValues(IRelationType entity) + { + if (entity.ParentObjectType.HasValue && entity.ParentObjectType == Guid.Empty) + entity.ParentObjectType = null; + if (entity.ChildObjectType.HasValue && entity.ChildObjectType == Guid.Empty) + entity.ChildObjectType = null; + } } } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/StylesheetRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/StylesheetRepository.cs index 4c02a8f4b5..698a9f4364 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/StylesheetRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/StylesheetRepository.cs @@ -25,8 +25,17 @@ namespace Umbraco.Core.Persistence.Repositories.Implement path = path.EnsureEndsWith(".css"); - if (FileSystem.FileExists(path) == false) + // if the css directory is changed, references to the old path can still exist (ie in RTE config) + // these old references will throw an error, which breaks the RTE + // try-catch here makes the request fail silently, and allows RTE to load correctly + try + { + if (FileSystem.FileExists(path) == false) + return null; + } catch + { return null; + } // content will be lazy-loaded when required var created = FileSystem.GetCreated(path).UtcDateTime; diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/TemplateRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/TemplateRepository.cs index b348317989..2175780916 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/TemplateRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/TemplateRepository.cs @@ -215,7 +215,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement //Save updated entity to db template.UpdateDate = DateTime.Now; - ; var dto = TemplateFactory.BuildDto(template, NodeObjectTypeId, templateDto.PrimaryKey); Database.Update(dto.NodeDto); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/UpgradeCheckRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/UpgradeCheckRepository.cs new file mode 100644 index 0000000000..365e8ba481 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/UpgradeCheckRepository.cs @@ -0,0 +1,49 @@ +using System.Net.Http; +using System.Net.Http.Formatting; +using System.Threading.Tasks; +using Semver; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Persistence.Repositories.Implement +{ + internal class UpgradeCheckRepository : IUpgradeCheckRepository + { + private static HttpClient _httpClient; + private const string RestApiUpgradeChecklUrl = "https://our.umbraco.com/umbraco/api/UpgradeCheck/CheckUpgrade"; + + + public async Task CheckUpgradeAsync(SemVersion version) + { + try + { + if (_httpClient == null) + _httpClient = new HttpClient(); + + var task = await _httpClient.PostAsync(RestApiUpgradeChecklUrl, new CheckUpgradeDto(version), new JsonMediaTypeFormatter()); + var result = await task.Content.ReadAsAsync(); + + return result ?? new UpgradeResult("None", "", ""); + } + catch (HttpRequestException) + { + // this occurs if the server for Our is down or cannot be reached + return new UpgradeResult("None", "", ""); + } + } + private class CheckUpgradeDto + { + public CheckUpgradeDto(SemVersion version) + { + VersionMajor = version.Major; + VersionMinor = version.Minor; + VersionPatch = version.Patch; + VersionComment = version.Prerelease; + } + + public int VersionMajor { get; } + public int VersionMinor { get; } + public int VersionPatch { get; } + public string VersionComment { get; } + } + } +} diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs index 96abc37662..3be5102b83 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs @@ -557,6 +557,16 @@ ORDER BY colName"; } } + // If userlogin or the email has changed then need to reset security stamp + if (changedCols.Contains("userLogin") || changedCols.Contains("userEmail")) + { + userDto.EmailConfirmedDate = null; + userDto.SecurityStampToken = entity.SecurityStamp = Guid.NewGuid().ToString(); + + changedCols.Add("emailConfirmedDate"); + changedCols.Add("securityStampToken"); + } + //only update the changed cols if (changedCols.Count > 0) { diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs index 3d0adf175e..bb50fa98a1 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs @@ -250,6 +250,11 @@ where tbl.[name]=@0 and col.[name]=@1;", tableName, columnName) } public override void WriteLock(IDatabase db, params int[] lockIds) + { + WriteLock(db, TimeSpan.FromMilliseconds(1800), lockIds); + } + + public void WriteLock(IDatabase db, TimeSpan timeout, params int[] lockIds) { // soon as we get Database, a transaction is started @@ -260,7 +265,7 @@ where tbl.[name]=@0 and col.[name]=@1;", tableName, columnName) // *not* using a unique 'WHERE IN' query here because the *order* of lockIds is important to avoid deadlocks foreach (var lockId in lockIds) { - db.Execute(@"SET LOCK_TIMEOUT 1800;"); + db.Execute($"SET LOCK_TIMEOUT {timeout.TotalMilliseconds};"); var i = db.Execute(@"UPDATE umbracoLock WITH (REPEATABLEREAD) SET value = (CASE WHEN (value=1) THEN -1 ELSE 1 END) WHERE id=@id", new { id = lockId }); if (i == 0) // ensure we are actually locking! throw new ArgumentException($"LockObject with id={lockId} does not exist."); diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderExtensions.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderExtensions.cs index f7cf480830..b829f1fbc5 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderExtensions.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderExtensions.cs @@ -35,6 +35,8 @@ namespace Umbraco.Core.Persistence.SqlSyntax /// public static Sql GetDeleteSubquery(this ISqlSyntaxProvider sqlProvider, string tableName, string columnName, Sql subQuery, WhereInType whereInType = WhereInType.In) { + //TODO: This is no longer necessary since this used to be a specific requirement for MySql! + // Now we can do a Delete + sub query, see RelationRepository.DeleteByParent for example return new Sql(string.Format( diff --git a/src/Umbraco.Core/Persistence/UmbracoDatabase.cs b/src/Umbraco.Core/Persistence/UmbracoDatabase.cs index 072813b4e6..a95d95ea08 100644 --- a/src/Umbraco.Core/Persistence/UmbracoDatabase.cs +++ b/src/Umbraco.Core/Persistence/UmbracoDatabase.cs @@ -44,6 +44,8 @@ namespace Umbraco.Core.Persistence _commandRetryPolicy = commandRetryPolicy; EnableSqlTrace = EnableSqlTraceDefault; + + NPocoDatabaseExtensions.ConfigureNPocoBulkExtensions(); } /// @@ -57,6 +59,8 @@ namespace Umbraco.Core.Persistence _logger = logger; EnableSqlTrace = EnableSqlTraceDefault; + + NPocoDatabaseExtensions.ConfigureNPocoBulkExtensions(); } #endregion diff --git a/src/Umbraco.Core/PropertyEditors/DataEditor.cs b/src/Umbraco.Core/PropertyEditors/DataEditor.cs index dbb2fc467e..add523ecf6 100644 --- a/src/Umbraco.Core/PropertyEditors/DataEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/DataEditor.cs @@ -104,7 +104,7 @@ namespace Umbraco.Core.PropertyEditors /// Technically, it could be cached by datatype but let's keep things /// simple enough for now. /// - public IDataValueEditor GetValueEditor(object configuration) + public virtual IDataValueEditor GetValueEditor(object configuration) { // if an explicit value editor has been set (by the manifest parser) // then return it, and ignore the configuration, which is going to be @@ -113,7 +113,7 @@ namespace Umbraco.Core.PropertyEditors return ExplicitValueEditor; var editor = CreateValueEditor(); - ((DataValueEditor) editor).Configuration = configuration; // TODO: casting is bad + ((DataValueEditor)editor).Configuration = configuration; // TODO: casting is bad return editor; } @@ -163,7 +163,7 @@ namespace Umbraco.Core.PropertyEditors protected virtual IDataValueEditor CreateValueEditor() { if (Attribute == null) - throw new InvalidOperationException("The editor does not specify a view."); + throw new InvalidOperationException($"The editor is not attributed with {nameof(DataEditorAttribute)}"); return new DataValueEditor(Attribute); } @@ -175,7 +175,7 @@ namespace Umbraco.Core.PropertyEditors { var editor = new ConfigurationEditor(); // pass the default configuration if this is not a property value editor - if((Type & EditorType.PropertyValue) == 0) + if ((Type & EditorType.PropertyValue) == 0) { editor.DefaultConfiguration = _defaultConfiguration; } diff --git a/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollection.cs b/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollection.cs new file mode 100644 index 0000000000..e7b051e17f --- /dev/null +++ b/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollection.cs @@ -0,0 +1,61 @@ +using System.Collections.Generic; +using Umbraco.Core.Composing; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Editors; + +namespace Umbraco.Core.PropertyEditors +{ + public class DataValueReferenceFactoryCollection : BuilderCollectionBase + { + public DataValueReferenceFactoryCollection(IEnumerable items) + : base(items) + { } + + public IEnumerable GetAllReferences(PropertyCollection properties, PropertyEditorCollection propertyEditors) + { + var trackedRelations = new HashSet(); + + foreach (var p in properties) + { + if (!propertyEditors.TryGet(p.PropertyType.PropertyEditorAlias, out var editor)) continue; + + //TODO: We will need to change this once we support tracking via variants/segments + // for now, we are tracking values from ALL variants + + foreach(var propertyVal in p.Values) + { + var val = propertyVal.EditedValue; + + var valueEditor = editor.GetValueEditor(); + if (valueEditor is IDataValueReference reference) + { + var refs = reference.GetReferences(val); + foreach(var r in refs) + trackedRelations.Add(r); + } + + // Loop over collection that may be add to existing property editors + // implementation of GetReferences in IDataValueReference. + // Allows developers to add support for references by a + // package /property editor that did not implement IDataValueReference themselves + foreach (var item in this) + { + // Check if this value reference is for this datatype/editor + // Then call it's GetReferences method - to see if the value stored + // in the dataeditor/property has referecnes to media/content items + if (item.IsForEditor(editor)) + { + foreach(var r in item.GetDataValueReference().GetReferences(val)) + trackedRelations.Add(r); + } + + } + } + + + } + + return trackedRelations; + } + } +} diff --git a/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollectionBuilder.cs b/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollectionBuilder.cs new file mode 100644 index 0000000000..2cf76712c8 --- /dev/null +++ b/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollectionBuilder.cs @@ -0,0 +1,9 @@ +using Umbraco.Core.Composing; + +namespace Umbraco.Core.PropertyEditors +{ + public class DataValueReferenceFactoryCollectionBuilder : OrderedCollectionBuilderBase + { + protected override DataValueReferenceFactoryCollectionBuilder This => this; + } +} diff --git a/src/Umbraco.Core/PropertyEditors/IDataValueEditor.cs b/src/Umbraco.Core/PropertyEditors/IDataValueEditor.cs index cb68531cc7..a02fa71ec7 100644 --- a/src/Umbraco.Core/PropertyEditors/IDataValueEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/IDataValueEditor.cs @@ -7,6 +7,7 @@ using Umbraco.Core.Services; namespace Umbraco.Core.PropertyEditors { + /// /// Represents an editor for editing data values. /// @@ -63,8 +64,26 @@ namespace Umbraco.Core.PropertyEditors // TODO: / deal with this when unplugging the xml cache // why property vs propertyType? services should be injected! etc... + + /// + /// Used for serializing an item for packaging + /// + /// + /// + /// + /// + /// IEnumerable ConvertDbToXml(Property property, IDataTypeService dataTypeService, ILocalizationService localizationService, bool published); + + /// + /// Used for serializing an item for packaging + /// + /// + /// + /// + /// XNode ConvertDbToXml(PropertyType propertyType, object value, IDataTypeService dataTypeService); + string ConvertDbToString(PropertyType propertyType, object value, IDataTypeService dataTypeService); } } diff --git a/src/Umbraco.Core/PropertyEditors/IDataValueReference.cs b/src/Umbraco.Core/PropertyEditors/IDataValueReference.cs new file mode 100644 index 0000000000..6377098bfc --- /dev/null +++ b/src/Umbraco.Core/PropertyEditors/IDataValueReference.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using Umbraco.Core.Models.Editors; + +namespace Umbraco.Core.PropertyEditors +{ + /// + /// Resolve references from values + /// + public interface IDataValueReference + { + /// + /// Returns any references contained in the value + /// + /// + /// + IEnumerable GetReferences(object value); + } +} diff --git a/src/Umbraco.Core/PropertyEditors/IDataValueReferenceFactory.cs b/src/Umbraco.Core/PropertyEditors/IDataValueReferenceFactory.cs new file mode 100644 index 0000000000..6587e071bf --- /dev/null +++ b/src/Umbraco.Core/PropertyEditors/IDataValueReferenceFactory.cs @@ -0,0 +1,18 @@ +namespace Umbraco.Core.PropertyEditors +{ + public interface IDataValueReferenceFactory + { + /// + /// Gets a value indicating whether the DataValueReference lookup supports a datatype (data editor). + /// + /// The datatype. + /// A value indicating whether the converter supports a datatype. + bool IsForEditor(IDataEditor dataEditor); + + /// + /// + /// + /// + IDataValueReference GetDataValueReference(); + } +} diff --git a/src/Umbraco.Core/PropertyEditors/PropertyEditorCollection.cs b/src/Umbraco.Core/PropertyEditors/PropertyEditorCollection.cs index 712a66e55d..21854b63c1 100644 --- a/src/Umbraco.Core/PropertyEditors/PropertyEditorCollection.cs +++ b/src/Umbraco.Core/PropertyEditors/PropertyEditorCollection.cs @@ -4,6 +4,8 @@ using Umbraco.Core.Manifest; namespace Umbraco.Core.PropertyEditors { + + public class PropertyEditorCollection : BuilderCollectionBase { public PropertyEditorCollection(DataEditorCollection dataEditors, ManifestParser manifestParser) @@ -27,4 +29,4 @@ namespace Umbraco.Core.PropertyEditors return editor != null; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValue.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValue.cs index b211272b51..ab217d3870 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValue.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValue.cs @@ -7,6 +7,8 @@ using System.Runtime.Serialization; using System.Text; using System.Web; using Newtonsoft.Json; +using Umbraco.Core.Composing; +using Umbraco.Core.Models; using Umbraco.Core.Serialization; namespace Umbraco.Core.PropertyEditors.ValueConverters @@ -59,38 +61,34 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters : Crops.FirstOrDefault(x => x.Alias.InvariantEquals(alias)); } - internal void AppendCropBaseUrl(StringBuilder url, ImageCropperCrop crop, bool defaultCrop, bool preferFocalPoint) + internal ImageUrlGenerationOptions GetCropBaseOptions(string url, ImageCropperCrop crop, bool defaultCrop, bool preferFocalPoint) { if (preferFocalPoint && HasFocalPoint() || crop != null && crop.Coordinates == null && HasFocalPoint() || defaultCrop && HasFocalPoint()) { - url.Append("?center="); - url.Append(FocalPoint.Top.ToString(CultureInfo.InvariantCulture)); - url.Append(","); - url.Append(FocalPoint.Left.ToString(CultureInfo.InvariantCulture)); - url.Append("&mode=crop"); + return new ImageUrlGenerationOptions(url) { FocalPoint = new ImageUrlGenerationOptions.FocalPointPosition(FocalPoint.Top, FocalPoint.Left) }; } else if (crop != null && crop.Coordinates != null && preferFocalPoint == false) { - url.Append("?crop="); - url.Append(crop.Coordinates.X1.ToString(CultureInfo.InvariantCulture)).Append(","); - url.Append(crop.Coordinates.Y1.ToString(CultureInfo.InvariantCulture)).Append(","); - url.Append(crop.Coordinates.X2.ToString(CultureInfo.InvariantCulture)).Append(","); - url.Append(crop.Coordinates.Y2.ToString(CultureInfo.InvariantCulture)); - url.Append("&cropmode=percentage"); + return new ImageUrlGenerationOptions(url) { Crop = new ImageUrlGenerationOptions.CropCoordinates(crop.Coordinates.X1, crop.Coordinates.Y1, crop.Coordinates.X2, crop.Coordinates.Y2) }; } else { - url.Append("?anchor=center"); - url.Append("&mode=crop"); + return new ImageUrlGenerationOptions(url) { DefaultCrop = true }; } } /// /// Gets the value image url for a specified crop. /// - public string GetCropUrl(string alias, bool useCropDimensions = true, bool useFocalPoint = false, string cacheBusterValue = null) + [Obsolete("Use the overload that takes an IImageUrlGenerator")] + public string GetCropUrl(string alias, bool useCropDimensions = true, bool useFocalPoint = false, string cacheBusterValue = null) => GetCropUrl(alias, Current.ImageUrlGenerator, useCropDimensions, useFocalPoint, cacheBusterValue); + + /// + /// Gets the value image url for a specified crop. + /// + public string GetCropUrl(string alias, IImageUrlGenerator imageUrlGenerator, bool useCropDimensions = true, bool useFocalPoint = false, string cacheBusterValue = null) { var crop = GetCrop(alias); @@ -98,38 +96,37 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters if (crop == null && !string.IsNullOrWhiteSpace(alias)) return null; - var url = new StringBuilder(); - - AppendCropBaseUrl(url, crop, string.IsNullOrWhiteSpace(alias), useFocalPoint); + var options = GetCropBaseOptions(string.Empty, crop, string.IsNullOrWhiteSpace(alias), useFocalPoint); if (crop != null && useCropDimensions) { - url.Append("&width=").Append(crop.Width); - url.Append("&height=").Append(crop.Height); + options.Width = crop.Width; + options.Height = crop.Height; } - if (cacheBusterValue != null) - url.Append("&rnd=").Append(cacheBusterValue); + options.CacheBusterValue = cacheBusterValue; - return url.ToString(); + return imageUrlGenerator.GetImageUrl(options); } /// /// Gets the value image url for a specific width and height. /// - public string GetCropUrl(int width, int height, bool useFocalPoint = false, string cacheBusterValue = null) + [Obsolete("Use the overload that takes an IImageUrlGenerator")] + public string GetCropUrl(int width, int height, bool useFocalPoint = false, string cacheBusterValue = null) => GetCropUrl(width, height, Current.ImageUrlGenerator, useFocalPoint, cacheBusterValue); + + /// + /// Gets the value image url for a specific width and height. + /// + public string GetCropUrl(int width, int height, IImageUrlGenerator imageUrlGenerator, bool useFocalPoint = false, string cacheBusterValue = null) { - var url = new StringBuilder(); + var options = GetCropBaseOptions(string.Empty, null, true, useFocalPoint); - AppendCropBaseUrl(url, null, true, useFocalPoint); + options.Width = width; + options.Height = height; + options.CacheBusterValue = cacheBusterValue; - url.Append("&width=").Append(width); - url.Append("&height=").Append(height); - - if (cacheBusterValue != null) - url.Append("&rnd=").Append(cacheBusterValue); - - return url.ToString(); + return imageUrlGenerator.GetImageUrl(options); } /// diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValueConverter.cs index 6f5bd571b7..36ffddb863 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValueConverter.cs @@ -47,7 +47,7 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters value = new ImageCropperValue { Src = sourceString }; } - value.ApplyConfiguration(propertyType.DataType.ConfigurationAs()); + value?.ApplyConfiguration(propertyType.DataType.ConfigurationAs()); return value; } diff --git a/src/Umbraco.Core/PublishedModelFactoryExtensions.cs b/src/Umbraco.Core/PublishedModelFactoryExtensions.cs index ac8c9f1be7..697e3fd11f 100644 --- a/src/Umbraco.Core/PublishedModelFactoryExtensions.cs +++ b/src/Umbraco.Core/PublishedModelFactoryExtensions.cs @@ -17,6 +17,20 @@ namespace Umbraco.Core /// public static bool IsLiveFactory(this IPublishedModelFactory factory) => factory is ILivePublishedModelFactory; + /// + /// Returns true if the current is an implementation of and is enabled + /// + /// + /// + public static bool IsLiveFactoryEnabled(this IPublishedModelFactory factory) + { + if (factory is ILivePublishedModelFactory2 liveFactory2) + return liveFactory2.Enabled; + + // if it's not ILivePublishedModelFactory2 we can't determine if it's enabled or not so return true + return factory is ILivePublishedModelFactory; + } + [Obsolete("This method is no longer used or necessary and will be removed from future")] [EditorBrowsable(EditorBrowsableState.Never)] public static void WithSafeLiveFactory(this IPublishedModelFactory factory, Action action) @@ -50,15 +64,17 @@ namespace Umbraco.Core { lock (liveFactory.SyncRoot) { - // TODO: Fix this in 8.3! - We need to change the ILivePublishedModelFactory interface to have a Reset method and then when we have an embedded MB - // version we will publicize the ResetModels (and change the name to Reset). - // For now, this will suffice and we'll use reflection, there should be no other implementation of ILivePublishedModelFactory. - // Calling ResetModels resets the MB flag so that the next time EnsureModels is called (which is called when nucache lazily calls CreateModel) it will - // trigger the recompiling of pure live models. - var resetMethod = liveFactory.GetType().GetMethod("ResetModels", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); - if (resetMethod != null) - resetMethod.Invoke(liveFactory, null); - + if (liveFactory is ILivePublishedModelFactory2 liveFactory2) + { + liveFactory2.Reset(); + } + else + { + // This is purely here for backwards compat and to avoid breaking changes but this code will probably never get executed + var resetMethod = liveFactory.GetType().GetMethod("ResetModels", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); + if (resetMethod != null) + resetMethod.Invoke(liveFactory, null); + } action(); } } diff --git a/src/Umbraco.Core/Runtime/CoreInitialComposer.cs b/src/Umbraco.Core/Runtime/CoreInitialComposer.cs index 1f004846d0..617dbcd9ea 100644 --- a/src/Umbraco.Core/Runtime/CoreInitialComposer.cs +++ b/src/Umbraco.Core/Runtime/CoreInitialComposer.cs @@ -18,6 +18,7 @@ using Umbraco.Core.PropertyEditors; using Umbraco.Core.PropertyEditors.Validators; using Umbraco.Core.Scoping; using Umbraco.Core.Services; +using Umbraco.Core.Services.Implement; using Umbraco.Core.Strings; using Umbraco.Core.Sync; using IntegerValidator = Umbraco.Core.PropertyEditors.Validators.IntegerValidator; @@ -43,7 +44,7 @@ namespace Umbraco.Core.Runtime // register persistence mappers - required by database factory so needs to be done here // means the only place the collection can be modified is in a runtime - afterwards it // has been frozen and it is too late - composition.WithCollectionBuilder().AddCoreMappers(); + composition.Mappers().AddCoreMappers(); // register the scope provider composition.RegisterUnique(); // implements both IScopeProvider and IScopeAccessor @@ -70,11 +71,15 @@ namespace Umbraco.Core.Runtime composition.ManifestFilters(); // properties and parameters derive from data editors - composition.WithCollectionBuilder() + composition.DataEditors() .Add(() => composition.TypeLoader.GetDataEditors()); composition.RegisterUnique(); composition.RegisterUnique(); + // Used to determine if a datatype/editor should be storing/tracking + // references to media item/s + composition.DataValueReferenceFactories(); + // register a server registrar, by default it's the db registrar composition.RegisterUnique(f => { @@ -101,13 +106,13 @@ namespace Umbraco.Core.Runtime factory.GetInstance(), true, new DatabaseServerMessengerOptions())); - composition.WithCollectionBuilder() + composition.CacheRefreshers() .Add(() => composition.TypeLoader.GetCacheRefreshers()); - composition.WithCollectionBuilder() + composition.PackageActions() .Add(() => composition.TypeLoader.GetPackageActions()); - composition.WithCollectionBuilder() + composition.PropertyValueConverters() .Append(composition.TypeLoader.GetTypes()); composition.RegisterUnique(); @@ -115,7 +120,7 @@ namespace Umbraco.Core.Runtime composition.RegisterUnique(factory => new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(factory.GetInstance()))); - composition.WithCollectionBuilder() + composition.UrlSegmentProviders() .Append(); composition.RegisterUnique(factory => new MigrationBuilder(factory)); @@ -125,6 +130,10 @@ namespace Umbraco.Core.Runtime // by default, register a noop rebuilder composition.RegisterUnique(); + + // will be injected in controllers when needed to invoke rest endpoints on Our + composition.RegisterUnique(); + composition.RegisterUnique(); } } } diff --git a/src/Umbraco.Core/Runtime/CoreRuntime.cs b/src/Umbraco.Core/Runtime/CoreRuntime.cs index 5ceb89d7fb..b852aff2ff 100644 --- a/src/Umbraco.Core/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Core/Runtime/CoreRuntime.cs @@ -147,7 +147,7 @@ namespace Umbraco.Core.Runtime // TODO: remove this in netcore, this is purely backwards compat hacks with the empty ctor if (MainDom == null) { - MainDom = new MainDom(Logger); + MainDom = new MainDom(Logger, new MainDomSemaphoreLock(Logger)); } diff --git a/src/Umbraco.Core/IMainDom.cs b/src/Umbraco.Core/Runtime/IMainDom.cs similarity index 96% rename from src/Umbraco.Core/IMainDom.cs rename to src/Umbraco.Core/Runtime/IMainDom.cs index 31b2e2eee0..444fc1c7d0 100644 --- a/src/Umbraco.Core/IMainDom.cs +++ b/src/Umbraco.Core/Runtime/IMainDom.cs @@ -1,5 +1,6 @@ using System; +// TODO: Can't change namespace due to breaking changes, change in netcore namespace Umbraco.Core { /// diff --git a/src/Umbraco.Core/Runtime/IMainDomLock.cs b/src/Umbraco.Core/Runtime/IMainDomLock.cs new file mode 100644 index 0000000000..6a62f48194 --- /dev/null +++ b/src/Umbraco.Core/Runtime/IMainDomLock.cs @@ -0,0 +1,29 @@ +using System; +using System.Threading.Tasks; + +namespace Umbraco.Core.Runtime +{ + /// + /// An application-wide distributed lock + /// + /// + /// Disposing releases the lock + /// + public interface IMainDomLock : IDisposable + { + /// + /// Acquires an application-wide distributed lock + /// + /// + /// + /// An awaitable boolean value which will be false if the elapsed millsecondsTimeout value is exceeded + /// + Task AcquireLockAsync(int millisecondsTimeout); + + /// + /// Wait on a background thread to receive a signal from another AppDomain + /// + /// + Task ListenAsync(); + } +} diff --git a/src/Umbraco.Core/MainDom.cs b/src/Umbraco.Core/Runtime/MainDom.cs similarity index 76% rename from src/Umbraco.Core/MainDom.cs rename to src/Umbraco.Core/Runtime/MainDom.cs index e2049c0190..e6780ec876 100644 --- a/src/Umbraco.Core/MainDom.cs +++ b/src/Umbraco.Core/Runtime/MainDom.cs @@ -4,10 +4,13 @@ using System.Linq; using System.Security.Cryptography; using System.Threading; using System.Web.Hosting; +using Umbraco.Core; using Umbraco.Core.Logging; +using Umbraco.Core.Persistence; -namespace Umbraco.Core +namespace Umbraco.Core.Runtime { + /// /// Provides the full implementation of . /// @@ -20,18 +23,11 @@ namespace Umbraco.Core #region Vars private readonly ILogger _logger; + private readonly IMainDomLock _mainDomLock; // our own lock for local consistency private object _locko = new object(); - // async lock representing the main domain lock - private readonly SystemLock _systemLock; - private IDisposable _systemLocker; - - // 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; - private bool _isInitialized; // indicates whether... private bool _isMainDom; // we are the main domain @@ -40,39 +36,19 @@ namespace Umbraco.Core // actions to run before releasing the main domain private readonly List> _callbacks = new List>(); - private const int LockTimeoutMilliseconds = 90000; // (1.5 * 60 * 1000) == 1 min 30 seconds + private const int LockTimeoutMilliseconds = 40000; // 40 seconds #endregion #region Ctor // initializes a new instance of MainDom - public MainDom(ILogger logger) + public MainDom(ILogger logger, IMainDomLock systemLock) { HostingEnvironment.RegisterObject(this); _logger = logger; - - // HostingEnvironment.ApplicationID is null in unit tests, making ReplaceNonAlphanumericChars fail - var appId = HostingEnvironment.ApplicationID?.ReplaceNonAlphanumericChars(string.Empty) ?? string.Empty; - - // combining with the physical path because if running on eg IIS Express, - // two sites could have the same appId even though they are different. - // - // now what could still collide is... two sites, running in two different processes - // and having the same appId, and running on the same app physical path - // - // we *cannot* use the process ID here because when an AppPool restarts it is - // a new process for the same application path - - var appPath = HostingEnvironment.ApplicationPhysicalPath?.ToLowerInvariant() ?? string.Empty; - var hash = (appId + ":::" + appPath).GenerateHash(); - - var lockName = "UMBRACO-" + hash + "-MAINDOM-LCK"; - _systemLock = new SystemLock(lockName); - - var eventName = "UMBRACO-" + hash + "-MAINDOM-EVT"; - _signal = new EventWaitHandle(false, EventResetMode.AutoReset, eventName); + _mainDomLock = systemLock; } #endregion @@ -141,13 +117,14 @@ namespace Umbraco.Core continue; } } + _logger.Debug("Stopped ({SignalSource})", source); } finally { // in any case... _isMainDom = false; - _systemLocker?.Dispose(); + _mainDomLock.Dispose(); _logger.Info("Released ({SignalSource})", source); } @@ -167,36 +144,33 @@ namespace Umbraco.Core _logger.Info("Acquiring."); - // signal other instances that we want the lock, then wait one the lock, - // which may timeout, and this is accepted - see comments below + // Get the lock + var acquired = _mainDomLock.AcquireLockAsync(LockTimeoutMilliseconds).GetAwaiter().GetResult(); - // signal, then wait for the lock, then make sure the event is - // reset (maybe there was noone listening..) - _signal.Set(); + if (!acquired) + { + _logger.Info("Cannot acquire (timeout)."); - // if more than 1 instance reach that point, one will get the lock - // and the other one will timeout, which is accepted + // In previous versions we'd let a TimeoutException be thrown + // and the appdomain would not start. We have the opportunity to allow it to + // start without having MainDom? This would mean that it couldn't write + // to nucache/examine and would only be ok if this was a super short lived appdomain. + // maybe safer to just keep throwing in this case. + + throw new TimeoutException("Cannot acquire MainDom"); + // return false; + } - //This can throw a TimeoutException - in which case should this be in a try/finally to ensure the signal is always reset. try { - _systemLocker = _systemLock.Lock(LockTimeoutMilliseconds); + // Listen for the signal from another AppDomain coming online to release the lock + _mainDomLock.ListenAsync().ContinueWith(_ => OnSignal("signal")); } - finally + catch (OperationCanceledException ex) { - // we need to reset the event, because otherwise we would end up - // signaling ourselves and committing 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(); + // the waiting task could be canceled if this appdomain is naturally shutting down, we'll just swallow this exception + _logger.Warn(ex, ex.Message); } - - //WaitOneAsync (ext method) will wait for a signal without blocking the main thread, the waiting is done on a background thread - - _signal.WaitOneAsync() - .ContinueWith(_ => OnSignal("signal")); _logger.Info("Acquired."); return true; @@ -205,6 +179,10 @@ namespace Umbraco.Core /// /// Gets a value indicating whether the current domain is the main domain. /// + /// + /// The lazy initializer call will only call the Acquire callback when it's not been initialized, else it will just return + /// the value from _isMainDom which means when we set _isMainDom to false again after being signaled, this will return false; + /// public bool IsMainDom => LazyInitializer.EnsureInitialized(ref _isMainDom, ref _isInitialized, ref _locko, () => Acquire()); // IRegisteredObject @@ -230,8 +208,7 @@ namespace Umbraco.Core { if (disposing) { - _signal?.Close(); - _signal?.Dispose(); + _mainDomLock.Dispose(); } disposedValue = true; @@ -244,5 +221,25 @@ namespace Umbraco.Core } #endregion + + public static string GetMainDomId() + { + // HostingEnvironment.ApplicationID is null in unit tests, making ReplaceNonAlphanumericChars fail + var appId = HostingEnvironment.ApplicationID?.ReplaceNonAlphanumericChars(string.Empty) ?? string.Empty; + + // combining with the physical path because if running on eg IIS Express, + // two sites could have the same appId even though they are different. + // + // now what could still collide is... two sites, running in two different processes + // and having the same appId, and running on the same app physical path + // + // we *cannot* use the process ID here because when an AppPool restarts it is + // a new process for the same application path + + var appPath = HostingEnvironment.ApplicationPhysicalPath?.ToLowerInvariant() ?? string.Empty; + var hash = (appId + ":::" + appPath).GenerateHash(); + + return hash; + } } } diff --git a/src/Umbraco.Core/Runtime/MainDomSemaphoreLock.cs b/src/Umbraco.Core/Runtime/MainDomSemaphoreLock.cs new file mode 100644 index 0000000000..2dcc37e25f --- /dev/null +++ b/src/Umbraco.Core/Runtime/MainDomSemaphoreLock.cs @@ -0,0 +1,95 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Umbraco.Core.Logging; + +namespace Umbraco.Core.Runtime +{ + /// + /// Uses a system-wide Semaphore and EventWaitHandle to synchronize the current AppDomain + /// + internal class MainDomSemaphoreLock : IMainDomLock + { + private readonly SystemLock _systemLock; + + // 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; + private readonly ILogger _logger; + private IDisposable _lockRelease; + + public MainDomSemaphoreLock(ILogger logger) + { + var lockName = "UMBRACO-" + MainDom.GetMainDomId() + "-MAINDOM-LCK"; + _systemLock = new SystemLock(lockName); + + var eventName = "UMBRACO-" + MainDom.GetMainDomId() + "-MAINDOM-EVT"; + _signal = new EventWaitHandle(false, EventResetMode.AutoReset, eventName); + _logger = logger; + } + + //WaitOneAsync (ext method) will wait for a signal without blocking the main thread, the waiting is done on a background thread + public Task ListenAsync() => _signal.WaitOneAsync(); + + public Task AcquireLockAsync(int millisecondsTimeout) + { + // signal other instances that we want the lock, then wait on the lock, + // which may timeout, and this is accepted - see comments below + + // signal, then wait for the lock, then make sure the event is + // reset (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 + + //This can throw a TimeoutException - in which case should this be in a try/finally to ensure the signal is always reset. + try + { + _lockRelease = _systemLock.Lock(millisecondsTimeout); + return Task.FromResult(true); + } + catch (TimeoutException ex) + { + _logger.Error(ex); + return Task.FromResult(false); + } + finally + { + // we need to reset the event, because otherwise we would end up + // signaling ourselves and committing 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(); + } + } + + #region IDisposable Support + private bool disposedValue = false; // To detect redundant calls + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + _lockRelease?.Dispose(); + _signal.Close(); + _signal.Dispose(); + } + + disposedValue = true; + } + } + + // This code added to correctly implement the disposable pattern. + public void Dispose() + { + // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + Dispose(true); + } + #endregion + } +} diff --git a/src/Umbraco.Core/Runtime/SqlMainDomLock.cs b/src/Umbraco.Core/Runtime/SqlMainDomLock.cs new file mode 100644 index 0000000000..f3bfe4eefc --- /dev/null +++ b/src/Umbraco.Core/Runtime/SqlMainDomLock.cs @@ -0,0 +1,429 @@ +using System; +using System.Data; +using System.Data.SqlClient; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Dtos; +using Umbraco.Core.Persistence.Mappers; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Runtime +{ + internal class SqlMainDomLock : IMainDomLock + { + private string _lockId; + private const string MainDomKey = "Umbraco.Core.Runtime.SqlMainDom"; + private const string UpdatedSuffix = "_updated"; + private readonly ILogger _logger; + private IUmbracoDatabase _db; + private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); + private SqlServerSyntaxProvider _sqlServerSyntax = new SqlServerSyntaxProvider(); + private bool _mainDomChanging = false; + private readonly UmbracoDatabaseFactory _dbFactory; + private bool _hasError; + private object _locker = new object(); + + public SqlMainDomLock(ILogger logger) + { + // unique id for our appdomain, this is more unique than the appdomain id which is just an INT counter to its safer + _lockId = Guid.NewGuid().ToString(); + _logger = logger; + + _dbFactory = new UmbracoDatabaseFactory( + Constants.System.UmbracoConnectionName, + _logger, + new Lazy(() => new Persistence.Mappers.MapperCollection(Enumerable.Empty()))); + } + + public async Task AcquireLockAsync(int millisecondsTimeout) + { + if (!_dbFactory.Configured) + { + // if we aren't configured, then we're in an install state, in which case we have no choice but to assume we can acquire + return true; + } + + if (!(_dbFactory.SqlContext.SqlSyntax is SqlServerSyntaxProvider sqlServerSyntaxProvider)) + throw new NotSupportedException("SqlMainDomLock is only supported for Sql Server"); + + _sqlServerSyntax = sqlServerSyntaxProvider; + + _logger.Debug("Acquiring lock..."); + + var db = GetDatabase(); + + var tempId = Guid.NewGuid().ToString(); + + try + { + db.BeginTransaction(IsolationLevel.ReadCommitted); + + try + { + // wait to get a write lock + _sqlServerSyntax.WriteLock(db, TimeSpan.FromMilliseconds(millisecondsTimeout), Constants.Locks.MainDom); + } + catch (Exception ex) + { + if (IsLockTimeoutException(ex)) + { + _logger.Error(ex, "Sql timeout occurred, could not acquire MainDom."); + _hasError = true; + return false; + } + + // unexpected (will be caught below) + throw; + } + + var result = InsertLockRecord(tempId); //we change the row to a random Id to signal other MainDom to shutdown + if (result == RecordPersistenceType.Insert) + { + // if we've inserted, then there was no MainDom so we can instantly acquire + + // TODO: see the other TODO, could we just delete the row and that would indicate that we + // are MainDom? then we don't leave any orphan rows behind. + + InsertLockRecord(_lockId); // so update with our appdomain id + _logger.Debug("Acquired with ID {LockId}", _lockId); + return true; + } + + // if we've updated, this means there is an active MainDom, now we need to wait to + // for the current MainDom to shutdown which also requires releasing our write lock + } + catch (Exception ex) + { + ResetDatabase(); + // unexpected + _logger.Error(ex, "Unexpected error, cannot acquire MainDom"); + _hasError = true; + return false; + } + finally + { + db?.CompleteTransaction(); + } + + return await WaitForExistingAsync(tempId, millisecondsTimeout); + } + + public Task ListenAsync() + { + if (_hasError) + { + _logger.Warn("Could not acquire MainDom, listening is canceled."); + return Task.CompletedTask; + } + + // Create a long running task (dedicated thread) + // to poll to check if we are still the MainDom registered in the DB + return Task.Factory.StartNew(ListeningLoop, _cancellationTokenSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default); + + } + + private void ListeningLoop() + { + while (true) + { + // poll every 1 second + Thread.Sleep(1000); + + if (!_dbFactory.Configured) + { + // if we aren't configured, we just keep looping since we can't query the db + continue; + } + + lock (_locker) + { + // If cancellation has been requested we will just exit. Depending on timing of the shutdown, + // we will have already flagged _mainDomChanging = true, or we're shutting down faster than + // the other MainDom is taking to startup. In this case the db row will just be deleted and the + // new MainDom will just take over. + if (_cancellationTokenSource.IsCancellationRequested) + return; + + var db = GetDatabase(); + + try + { + db.BeginTransaction(IsolationLevel.ReadCommitted); + + // get a read lock + _sqlServerSyntax.ReadLock(db, Constants.Locks.MainDom); + + // TODO: We could in theory just check if the main dom row doesn't exist, that could indicate that + // we are still the maindom. An empty value might be better because then we won't have any orphan rows + // if the app is terminated. Could that work? + + if (!IsMainDomValue(_lockId)) + { + // we are no longer main dom, another one has come online, exit + _mainDomChanging = true; + _logger.Debug("Detected new booting application, releasing MainDom lock."); + return; + } + } + catch (Exception ex) + { + ResetDatabase(); + // unexpected + _logger.Error(ex, "Unexpected error, listening is canceled."); + _hasError = true; + return; + } + finally + { + db?.CompleteTransaction(); + } + } + + } + } + + private void ResetDatabase() + { + if (_db.InTransaction) + _db.AbortTransaction(); + _db.Dispose(); + _db = null; + } + + private IUmbracoDatabase GetDatabase() + { + if (_db != null) + return _db; + + _db = _dbFactory.CreateDatabase(); + return _db; + } + + /// + /// Wait for any existing MainDom to release so we can continue booting + /// + /// + /// + /// + private Task WaitForExistingAsync(string tempId, int millisecondsTimeout) + { + var updatedTempId = tempId + UpdatedSuffix; + + return Task.Run(() => + { + var db = GetDatabase(); + var watch = new Stopwatch(); + watch.Start(); + while(true) + { + // poll very often, we need to take over as fast as we can + Thread.Sleep(100); + + try + { + db.BeginTransaction(IsolationLevel.ReadCommitted); + + // get a read lock + _sqlServerSyntax.ReadLock(db, Constants.Locks.MainDom); + + // the row + var mainDomRows = db.Fetch("SELECT * FROM umbracoKeyValue WHERE [key] = @key", new { key = MainDomKey }); + + if (mainDomRows.Count == 0 || mainDomRows[0].Value == updatedTempId) + { + // the other main dom has updated our record + // Or the other maindom shutdown super fast and just deleted the record + // which indicates that we + // can acquire it and it has shutdown. + + _sqlServerSyntax.WriteLock(db, Constants.Locks.MainDom); + + // so now we update the row with our appdomain id + InsertLockRecord(_lockId); + _logger.Debug("Acquired with ID {LockId}", _lockId); + return true; + } + else if (mainDomRows.Count == 1 && !mainDomRows[0].Value.StartsWith(tempId)) + { + // in this case, the prefixed ID is different which means + // another new AppDomain has come online and is wanting to take over. In that case, we will not + // acquire. + + _logger.Debug("Cannot acquire, another booting application detected."); + + return false; + } + } + catch (Exception ex) + { + ResetDatabase(); + + if (IsLockTimeoutException(ex)) + { + _logger.Error(ex, "Sql timeout occurred, waiting for existing MainDom is canceled."); + _hasError = true; + return false; + } + // unexpected + _logger.Error(ex, "Unexpected error, waiting for existing MainDom is canceled."); + _hasError = true; + return false; + } + finally + { + db?.CompleteTransaction(); + } + + if (watch.ElapsedMilliseconds >= millisecondsTimeout) + { + // if the timeout has elapsed, it either means that the other main dom is taking too long to shutdown, + // or it could mean that the previous appdomain was terminated and didn't clear out the main dom SQL row + // and it's just been left as an orphan row. + // There's really know way of knowing unless we are constantly updating the row for the current maindom + // which isn't ideal. + // So... we're going to 'just' take over, if the writelock works then we'll assume we're ok + + _logger.Debug("Timeout elapsed, assuming orphan row, acquiring MainDom."); + + try + { + db.BeginTransaction(IsolationLevel.ReadCommitted); + + _sqlServerSyntax.WriteLock(db, Constants.Locks.MainDom); + + // so now we update the row with our appdomain id + InsertLockRecord(_lockId); + _logger.Debug("Acquired with ID {LockId}", _lockId); + return true; + } + catch (Exception ex) + { + ResetDatabase(); + + if (IsLockTimeoutException(ex)) + { + // something is wrong, we cannot acquire, not much we can do + _logger.Error(ex, "Sql timeout occurred, could not forcibly acquire MainDom."); + _hasError = true; + return false; + } + _logger.Error(ex, "Unexpected error, could not forcibly acquire MainDom."); + _hasError = true; + return false; + } + finally + { + db?.CompleteTransaction(); + } + } + } + }, _cancellationTokenSource.Token); + } + + /// + /// Inserts or updates the key/value row + /// + private RecordPersistenceType InsertLockRecord(string id) + { + var db = GetDatabase(); + return db.InsertOrUpdate(new KeyValueDto + { + Key = MainDomKey, + Value = id, + Updated = DateTime.Now + }); + } + + /// + /// Checks if the DB row value is equals the value + /// + /// + private bool IsMainDomValue(string val) + { + var db = GetDatabase(); + return db.ExecuteScalar("SELECT COUNT(*) FROM umbracoKeyValue WHERE [key] = @key AND [value] = @val", + new { key = MainDomKey, val = val }) == 1; + } + + /// + /// Checks if the exception is an SQL timeout + /// + /// + /// + private bool IsLockTimeoutException(Exception exception) => exception is SqlException sqlException && sqlException.Number == 1222; + + #region IDisposable Support + private bool _disposedValue = false; // To detect redundant calls + + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + lock (_locker) + { + // immediately cancel all sub-tasks, we don't want them to keep querying + _cancellationTokenSource.Cancel(); + _cancellationTokenSource.Dispose(); + + if (_dbFactory.Configured) + { + var db = GetDatabase(); + try + { + db.BeginTransaction(IsolationLevel.ReadCommitted); + + // get a write lock + _sqlServerSyntax.WriteLock(db, Constants.Locks.MainDom); + + // When we are disposed, it means we have released the MainDom lock + // and called all MainDom release callbacks, in this case + // if another maindom is actually coming online we need + // to signal to the MainDom coming online that we have shutdown. + // To do that, we update the existing main dom DB record with a suffixed "_updated" string. + // Otherwise, if we are just shutting down, we want to just delete the row. + if (_mainDomChanging) + { + _logger.Debug("Releasing MainDom, updating row, new application is booting."); + db.Execute($"UPDATE umbracoKeyValue SET [value] = [value] + '{UpdatedSuffix}' WHERE [key] = @key", new { key = MainDomKey }); + } + else + { + _logger.Debug("Releasing MainDom, deleting row, application is shutting down."); + db.Execute("DELETE FROM umbracoKeyValue WHERE [key] = @key", new { key = MainDomKey }); + } + } + catch (Exception ex) + { + ResetDatabase(); + _logger.Error(ex, "Unexpected error during dipsose."); + _hasError = true; + } + finally + { + db?.CompleteTransaction(); + ResetDatabase(); + } + } + } + } + + _disposedValue = true; + } + } + + // This code added to correctly implement the disposable pattern. + public void Dispose() + { + // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + Dispose(true); + } + #endregion + + } +} diff --git a/src/Umbraco.Core/Scoping/IScopeProvider.cs b/src/Umbraco.Core/Scoping/IScopeProvider.cs index 6c9eb63ba0..96bb939f8e 100644 --- a/src/Umbraco.Core/Scoping/IScopeProvider.cs +++ b/src/Umbraco.Core/Scoping/IScopeProvider.cs @@ -13,7 +13,7 @@ namespace Umbraco.Core.Scoping /// Provides scopes. /// public interface IScopeProvider - { + { /// /// Creates an ambient scope. /// diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index 6f9ca58821..58279fb4da 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -526,6 +526,6 @@ namespace Umbraco.Core.Services OperationResult Rollback(int id, int versionId, string culture = "*", int userId = Constants.Security.SuperUserId); #endregion - + } } diff --git a/src/Umbraco.Core/Services/IContentServiceBase.cs b/src/Umbraco.Core/Services/IContentServiceBase.cs index 439c55d0d0..c40f49347f 100644 --- a/src/Umbraco.Core/Services/IContentServiceBase.cs +++ b/src/Umbraco.Core/Services/IContentServiceBase.cs @@ -1,9 +1,16 @@ -namespace Umbraco.Core.Services +using Umbraco.Core.Models; + +namespace Umbraco.Core.Services { /// /// Placeholder for sharing logic between the content, media (and member) services /// TODO: Start sharing the logic! /// public interface IContentServiceBase : IService - { } + { + /// + /// Checks/fixes the data integrity of node paths/levels stored in the database + /// + ContentDataIntegrityReport CheckDataIntegrity(ContentDataIntegrityReportOptions options); + } } diff --git a/src/Umbraco.Core/Services/IContentTypeServiceBase.cs b/src/Umbraco.Core/Services/IContentTypeServiceBase.cs index 51e5d756eb..4c818aa87c 100644 --- a/src/Umbraco.Core/Services/IContentTypeServiceBase.cs +++ b/src/Umbraco.Core/Services/IContentTypeServiceBase.cs @@ -39,6 +39,11 @@ namespace Umbraco.Core.Services int Count(); + /// + /// Returns true or false depending on whether content nodes have been created based on the provided content type id. + /// + bool HasContentNodes(int id); + IEnumerable GetAll(params int[] ids); IEnumerable GetAll(IEnumerable ids); @@ -46,7 +51,10 @@ namespace Umbraco.Core.Services IEnumerable GetComposedOf(int id); // composition axis IEnumerable GetChildren(int id); + IEnumerable GetChildren(Guid id); + bool HasChildren(int id); + bool HasChildren(Guid id); void Save(TItem item, int userId = Constants.Security.SuperUserId); void Save(IEnumerable items, int userId = Constants.Security.SuperUserId); @@ -64,6 +72,13 @@ namespace Umbraco.Core.Services /// bool HasContainerInPath(string contentPath); + /// + /// Gets a value indicating whether there is a list view content item in the path. + /// + /// + /// + bool HasContainerInPath(params int[] ids); + Attempt> CreateContainer(int parentContainerId, string name, int userId = Constants.Security.SuperUserId); Attempt SaveContainer(EntityContainer container, int userId = Constants.Security.SuperUserId); EntityContainer GetContainer(int containerId); diff --git a/src/Umbraco.Core/Services/IInstallationService.cs b/src/Umbraco.Core/Services/IInstallationService.cs new file mode 100644 index 0000000000..334088f8ae --- /dev/null +++ b/src/Umbraco.Core/Services/IInstallationService.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Services +{ + public interface IInstallationService + { + Task LogInstall(InstallLog installLog); + } +} diff --git a/src/Umbraco.Core/Services/IMembershipRoleService.cs b/src/Umbraco.Core/Services/IMembershipRoleService.cs index 30531b5031..7389bb9799 100644 --- a/src/Umbraco.Core/Services/IMembershipRoleService.cs +++ b/src/Umbraco.Core/Services/IMembershipRoleService.cs @@ -11,6 +11,9 @@ namespace Umbraco.Core.Services IEnumerable GetAllRoles(); IEnumerable GetAllRoles(int memberId); IEnumerable GetAllRoles(string username); + IEnumerable GetAllRolesIds(); + IEnumerable GetAllRolesIds(int memberId); + IEnumerable GetAllRolesIds(string username); IEnumerable GetMembersInRole(string roleName); IEnumerable FindMembersInRole(string roleName, string usernameToMatch, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith); bool DeleteRole(string roleName, bool throwIfBeingUsed); diff --git a/src/Umbraco.Core/Services/IRelationService.cs b/src/Umbraco.Core/Services/IRelationService.cs index ef22632d6e..bf8bcd5b2a 100644 --- a/src/Umbraco.Core/Services/IRelationService.cs +++ b/src/Umbraco.Core/Services/IRelationService.cs @@ -8,136 +8,162 @@ namespace Umbraco.Core.Services public interface IRelationService : IService { /// - /// Gets a by its Id + /// Gets a by its Id /// - /// Id of the - /// A object + /// Id of the + /// A object IRelation GetById(int id); /// - /// Gets a by its Id + /// Gets a by its Id /// - /// Id of the - /// A object + /// Id of the + /// A object IRelationType GetRelationTypeById(int id); /// - /// Gets a by its Id + /// Gets a by its Id /// - /// Id of the - /// A object + /// Id of the + /// A object IRelationType GetRelationTypeById(Guid id); /// - /// Gets a by its Alias + /// Gets a by its Alias /// - /// Alias of the - /// A object + /// Alias of the + /// A object IRelationType GetRelationTypeByAlias(string alias); /// - /// Gets all objects + /// Gets all objects /// /// Optional array of integer ids to return relations for - /// An enumerable list of objects + /// An enumerable list of objects IEnumerable GetAllRelations(params int[] ids); /// - /// Gets all objects by their + /// Gets all objects by their /// - /// to retrieve Relations for - /// An enumerable list of objects - IEnumerable GetAllRelationsByRelationType(RelationType relationType); + /// to retrieve Relations for + /// An enumerable list of objects + IEnumerable GetAllRelationsByRelationType(IRelationType relationType); /// - /// Gets all objects by their 's Id + /// Gets all objects by their 's Id /// - /// Id of the to retrieve Relations for - /// An enumerable list of objects + /// Id of the to retrieve Relations for + /// An enumerable list of objects IEnumerable GetAllRelationsByRelationType(int relationTypeId); /// - /// Gets all objects + /// Gets all objects /// /// Optional array of integer ids to return relationtypes for - /// An enumerable list of objects + /// An enumerable list of objects IEnumerable GetAllRelationTypes(params int[] ids); /// - /// Gets a list of objects by their parent Id + /// Gets a list of objects by their parent Id /// /// Id of the parent to retrieve relations for - /// An enumerable list of objects + /// An enumerable list of objects IEnumerable GetByParentId(int id); /// - /// Gets a list of objects by their parent entity + /// Gets a list of objects by their parent Id + /// + /// Id of the parent to retrieve relations for + /// Alias of the type of relation to retrieve + /// An enumerable list of objects + IEnumerable GetByParentId(int id, string relationTypeAlias); + + /// + /// Gets a list of objects by their parent entity /// /// Parent Entity to retrieve relations for - /// An enumerable list of objects + /// An enumerable list of objects IEnumerable GetByParent(IUmbracoEntity parent); /// - /// Gets a list of objects by their parent entity + /// Gets a list of objects by their parent entity /// /// Parent Entity to retrieve relations for /// Alias of the type of relation to retrieve - /// An enumerable list of objects + /// An enumerable list of objects IEnumerable GetByParent(IUmbracoEntity parent, string relationTypeAlias); /// - /// Gets a list of objects by their child Id + /// Gets a list of objects by their child Id /// /// Id of the child to retrieve relations for - /// An enumerable list of objects + /// An enumerable list of objects IEnumerable GetByChildId(int id); /// - /// Gets a list of objects by their child Entity + /// Gets a list of objects by their child Id + /// + /// Id of the child to retrieve relations for + /// Alias of the type of relation to retrieve + /// An enumerable list of objects + IEnumerable GetByChildId(int id, string relationTypeAlias); + + /// + /// Gets a list of objects by their child Entity /// /// Child Entity to retrieve relations for - /// An enumerable list of objects + /// An enumerable list of objects IEnumerable GetByChild(IUmbracoEntity child); /// - /// Gets a list of objects by their child Entity + /// Gets a list of objects by their child Entity /// /// Child Entity to retrieve relations for /// Alias of the type of relation to retrieve - /// An enumerable list of objects + /// An enumerable list of objects IEnumerable GetByChild(IUmbracoEntity child, string relationTypeAlias); /// - /// Gets a list of objects by their child or parent Id. + /// Gets a list of objects by their child or parent Id. /// Using this method will get you all relations regards of it being a child or parent relation. /// /// Id of the child or parent to retrieve relations for - /// An enumerable list of objects + /// An enumerable list of objects IEnumerable GetByParentOrChildId(int id); IEnumerable GetByParentOrChildId(int id, string relationTypeAlias); /// - /// Gets a list of objects by the Name of the + /// Gets a list of objects by the Name of the /// - /// Name of the to retrieve Relations for - /// An enumerable list of objects + /// Name of the to retrieve Relations for + /// An enumerable list of objects IEnumerable GetByRelationTypeName(string relationTypeName); /// - /// Gets a list of objects by the Alias of the + /// Gets a list of objects by the Alias of the /// - /// Alias of the to retrieve Relations for - /// An enumerable list of objects + /// Alias of the to retrieve Relations for + /// An enumerable list of objects IEnumerable GetByRelationTypeAlias(string relationTypeAlias); /// - /// Gets a list of objects by the Id of the + /// Gets a list of objects by the Id of the /// - /// Id of the to retrieve Relations for - /// An enumerable list of objects + /// Id of the to retrieve Relations for + /// An enumerable list of objects IEnumerable GetByRelationTypeId(int relationTypeId); + /// + /// Gets a paged result of + /// + /// + /// + /// + /// + /// + IEnumerable GetPagedByRelationTypeId(int relationTypeId, long pageIndex, int pageSize, out long totalRecords, Ordering ordering = null); + /// /// Gets the Child object from a Relation as an /// @@ -173,6 +199,26 @@ namespace Umbraco.Core.Services /// An enumerable list of IEnumerable GetParentEntitiesFromRelations(IEnumerable relations); + /// + /// Returns paged parent entities for a related child id + /// + /// + /// + /// + /// + /// + IEnumerable GetPagedParentEntitiesByChildId(int id, long pageIndex, int pageSize, out long totalChildren, params UmbracoObjectTypes[] entityTypes); + + /// + /// Returns paged child entities for a related parent id + /// + /// + /// + /// + /// + /// + IEnumerable GetPagedChildEntitiesByParentId(int id, long pageIndex, int pageSize, out long totalChildren, params UmbracoObjectTypes[] entityTypes); + /// /// Gets the Parent and Child objects from a list of Relations as a list of objects. /// @@ -186,7 +232,7 @@ namespace Umbraco.Core.Services /// Id of the parent /// Id of the child /// The type of relation to create - /// The created + /// The created IRelation Relate(int parentId, int childId, IRelationType relationType); /// @@ -195,7 +241,7 @@ namespace Umbraco.Core.Services /// Parent entity /// Child entity /// The type of relation to create - /// The created + /// The created IRelation Relate(IUmbracoEntity parent, IUmbracoEntity child, IRelationType relationType); /// @@ -204,7 +250,7 @@ namespace Umbraco.Core.Services /// Id of the parent /// Id of the child /// Alias of the type of relation to create - /// The created + /// The created IRelation Relate(int parentId, int childId, string relationTypeAlias); /// @@ -213,14 +259,14 @@ namespace Umbraco.Core.Services /// Parent entity /// Child entity /// Alias of the type of relation to create - /// The created + /// The created IRelation Relate(IUmbracoEntity parent, IUmbracoEntity child, string relationTypeAlias); /// - /// Checks whether any relations exists for the passed in . + /// Checks whether any relations exists for the passed in . /// - /// to check for relations - /// Returns True if any relations exists for the given , otherwise False + /// to check for relations + /// Returns True if any relations exists for the given , otherwise False bool HasRelations(IRelationType relationType); /// @@ -265,33 +311,35 @@ namespace Umbraco.Core.Services bool AreRelated(int parentId, int childId, string relationTypeAlias); /// - /// Saves a + /// Saves a /// /// Relation to save void Save(IRelation relation); + void Save(IEnumerable relations); + /// - /// Saves a + /// Saves a /// /// RelationType to Save void Save(IRelationType relationType); /// - /// Deletes a + /// Deletes a /// /// Relation to Delete void Delete(IRelation relation); /// - /// Deletes a + /// Deletes a /// /// RelationType to Delete void Delete(IRelationType relationType); /// - /// Deletes all objects based on the passed in + /// Deletes all objects based on the passed in /// - /// to Delete Relations for + /// to Delete Relations for void DeleteRelationsOfType(IRelationType relationType); } } diff --git a/src/Umbraco.Core/Services/IUpgradeService.cs b/src/Umbraco.Core/Services/IUpgradeService.cs new file mode 100644 index 0000000000..70bbb68085 --- /dev/null +++ b/src/Umbraco.Core/Services/IUpgradeService.cs @@ -0,0 +1,11 @@ +using System.Threading.Tasks; +using Semver; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Services +{ + public interface IUpgradeService + { + Task CheckUpgrade(SemVersion version); + } +} diff --git a/src/Umbraco.Core/Services/Implement/AuditService.cs b/src/Umbraco.Core/Services/Implement/AuditService.cs index 5eb08f2dea..7d3be1d52b 100644 --- a/src/Umbraco.Core/Services/Implement/AuditService.cs +++ b/src/Umbraco.Core/Services/Implement/AuditService.cs @@ -142,7 +142,7 @@ namespace Umbraco.Core.Services.Implement if (pageIndex < 0) throw new ArgumentOutOfRangeException(nameof(pageIndex)); if (pageSize <= 0) throw new ArgumentOutOfRangeException(nameof(pageSize)); - if (userId < 0) + if (userId < Constants.Security.SuperUserId) { totalRecords = 0; return Enumerable.Empty(); diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index 1558b0170b..068864a558 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -601,23 +601,27 @@ namespace Umbraco.Core.Services.Implement totalChildren = 0; return Enumerable.Empty(); } - return GetPagedDescendantsLocked(contentPath[0].Path, pageIndex, pageSize, out totalChildren, filter, ordering); + return GetPagedLocked(GetPagedDescendantQuery(contentPath[0].Path), pageIndex, pageSize, out totalChildren, filter, ordering); } - return GetPagedDescendantsLocked(null, pageIndex, pageSize, out totalChildren, filter, ordering); + return GetPagedLocked(null, pageIndex, pageSize, out totalChildren, filter, ordering); } } - private IEnumerable GetPagedDescendantsLocked(string contentPath, long pageIndex, int pageSize, out long totalChildren, + private IQuery GetPagedDescendantQuery(string contentPath) + { + var query = Query(); + if (!contentPath.IsNullOrWhiteSpace()) + query.Where(x => x.Path.SqlStartsWith($"{contentPath},", TextColumnType.NVarchar)); + return query; + } + + private IEnumerable GetPagedLocked(IQuery query, long pageIndex, int pageSize, out long totalChildren, IQuery filter, Ordering ordering) { if (pageIndex < 0) throw new ArgumentOutOfRangeException(nameof(pageIndex)); if (pageSize <= 0) throw new ArgumentOutOfRangeException(nameof(pageSize)); if (ordering == null) throw new ArgumentNullException(nameof(ordering)); - var query = Query(); - if (!contentPath.IsNullOrWhiteSpace()) - query.Where(x => x.Path.SqlStartsWith($"{contentPath},", TextColumnType.NVarchar)); - return _documentRepository.GetPage(query, pageIndex, pageSize, out totalChildren, filter, ordering); } @@ -1866,7 +1870,7 @@ namespace Umbraco.Core.Services.Implement public OperationResult MoveToRecycleBin(IContent content, int userId) { var evtMsgs = EventMessagesFactory.Get(); - var moves = new List>(); + var moves = new List<(IContent, string)>(); using (var scope = ScopeProvider.CreateScope()) { @@ -1925,7 +1929,7 @@ namespace Umbraco.Core.Services.Implement return; } - var moves = new List>(); + var moves = new List<(IContent, string)>(); using (var scope = ScopeProvider.CreateScope()) { @@ -1978,7 +1982,7 @@ namespace Umbraco.Core.Services.Implement // MUST be called from within WriteLock // trash indicates whether we are trashing, un-trashing, or not changing anything private void PerformMoveLocked(IContent content, int parentId, IContent parent, int userId, - ICollection> moves, + ICollection<(IContent, string)> moves, bool? trash) { content.WriterId = userId; @@ -1990,7 +1994,7 @@ namespace Umbraco.Core.Services.Implement var paths = new Dictionary(); - moves.Add(Tuple.Create(content, content.Path)); // capture original path + moves.Add((content, content.Path)); // capture original path //need to store the original path to lookup descendants based on it below var originalPath = content.Path; @@ -2007,20 +2011,24 @@ namespace Umbraco.Core.Services.Implement paths[content.Id] = (parent == null ? (parentId == Constants.System.RecycleBinContent ? "-1,-20" : Constants.System.RootString) : parent.Path) + "," + content.Id; const int pageSize = 500; - var total = long.MaxValue; - while (total > 0) + var query = GetPagedDescendantQuery(originalPath); + long total; + do { - var descendants = GetPagedDescendantsLocked(originalPath, 0, pageSize, out total, null, Ordering.By("Path", Direction.Ascending)); + // We always page a page 0 because for each page, we are moving the result so the resulting total will be reduced + var descendants = GetPagedLocked(query, 0, pageSize, out total, null, Ordering.By("Path", Direction.Ascending)); + foreach (var descendant in descendants) { - moves.Add(Tuple.Create(descendant, descendant.Path)); // capture original path + moves.Add((descendant, descendant.Path)); // capture original path // update path and level since we do not update parentId descendant.Path = paths[descendant.Id] = paths[descendant.ParentId] + "," + descendant.Id; descendant.Level += levelDelta; PerformMoveContentLocked(descendant, userId, trash); } - } + + } while (total > pageSize); } @@ -2375,6 +2383,25 @@ namespace Umbraco.Core.Services.Implement return OperationResult.Succeed(evtMsgs); } + public ContentDataIntegrityReport CheckDataIntegrity(ContentDataIntegrityReportOptions options) + { + using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + { + scope.WriteLock(Constants.Locks.ContentTree); + + var report = _documentRepository.CheckDataIntegrity(options); + + if (report.FixedIssues.Count > 0) + { + //The event args needs a content item so we'll make a fake one with enough properties to not cause a null ref + var root = new Content("root", -1, new ContentType(-1)) {Id = -1, Key = Guid.Empty}; + scope.Events.Dispatch(TreeChanged, this, new TreeChange.EventArgs(new TreeChange(root, TreeChangeTypes.RefreshAll))); + } + + return report; + } + } + #endregion #region Internal Methods @@ -2812,7 +2839,7 @@ namespace Umbraco.Core.Services.Implement // which we need for many things like keeping caches in sync, but we can surely do this MUCH better. var changes = new List>(); - var moves = new List>(); + var moves = new List<(IContent, string)>(); var contentTypeIdsA = contentTypeIds.ToArray(); // using an immediate uow here because we keep making changes with diff --git a/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs b/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs index 7ae330f8f1..fdd2d9ceae 100644 --- a/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs @@ -321,6 +321,15 @@ namespace Umbraco.Core.Services.Implement } } + public bool HasContainerInPath(params int[] ids) + { + using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + { + // can use same repo for both content and media + return Repository.HasContainerInPath(ids); + } + } + public IEnumerable GetDescendants(int id, bool andSelf) { using (var scope = ScopeProvider.CreateScope(autoComplete: true)) @@ -372,6 +381,15 @@ namespace Umbraco.Core.Services.Implement } } + public bool HasContentNodes(int id) + { + using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + { + scope.ReadLock(ReadLockIds); + return Repository.HasContentNodes(id); + } + } + #endregion #region Save diff --git a/src/Umbraco.Core/Services/Implement/InstallationService.cs b/src/Umbraco.Core/Services/Implement/InstallationService.cs new file mode 100644 index 0000000000..a1f74e0862 --- /dev/null +++ b/src/Umbraco.Core/Services/Implement/InstallationService.cs @@ -0,0 +1,21 @@ +using System.Threading.Tasks; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Repositories; + +namespace Umbraco.Core.Services.Implement +{ + public class InstallationService : IInstallationService + { + private readonly IInstallationRepository _installationRepository; + + public InstallationService(IInstallationRepository installationRepository) + { + _installationRepository = installationRepository; + } + + public async Task LogInstall(InstallLog installLog) + { + await _installationRepository.SaveInstallLogAsync(installLog); + } + } +} diff --git a/src/Umbraco.Core/Services/Implement/MediaService.cs b/src/Umbraco.Core/Services/Implement/MediaService.cs index 528d0a0bf9..ecd4cccc8d 100644 --- a/src/Umbraco.Core/Services/Implement/MediaService.cs +++ b/src/Umbraco.Core/Services/Implement/MediaService.cs @@ -530,23 +530,27 @@ namespace Umbraco.Core.Services.Implement totalChildren = 0; return Enumerable.Empty(); } - return GetPagedDescendantsLocked(mediaPath[0].Path, pageIndex, pageSize, out totalChildren, filter, ordering); + return GetPagedLocked(GetPagedDescendantQuery(mediaPath[0].Path), pageIndex, pageSize, out totalChildren, filter, ordering); } - return GetPagedDescendantsLocked(null, pageIndex, pageSize, out totalChildren, filter, ordering); + return GetPagedLocked(GetPagedDescendantQuery(null), pageIndex, pageSize, out totalChildren, filter, ordering); } } - private IEnumerable GetPagedDescendantsLocked(string mediaPath, long pageIndex, int pageSize, out long totalChildren, + private IQuery GetPagedDescendantQuery(string mediaPath) + { + var query = Query(); + if (!mediaPath.IsNullOrWhiteSpace()) + query.Where(x => x.Path.SqlStartsWith(mediaPath + ",", TextColumnType.NVarchar)); + return query; + } + + private IEnumerable GetPagedLocked(IQuery query, long pageIndex, int pageSize, out long totalChildren, IQuery filter, Ordering ordering) { if (pageIndex < 0) throw new ArgumentOutOfRangeException(nameof(pageIndex)); if (pageSize <= 0) throw new ArgumentOutOfRangeException(nameof(pageSize)); if (ordering == null) throw new ArgumentNullException(nameof(ordering)); - var query = Query(); - if (!mediaPath.IsNullOrWhiteSpace()) - query.Where(x => x.Path.SqlStartsWith(mediaPath + ",", TextColumnType.NVarchar)); - return _mediaRepository.GetPage(query, pageIndex, pageSize, out totalChildren, filter, ordering); } @@ -888,7 +892,7 @@ namespace Umbraco.Core.Services.Implement public Attempt MoveToRecycleBin(IMedia media, int userId = Constants.Security.SuperUserId) { var evtMsgs = EventMessagesFactory.Get(); - var moves = new List>(); + var moves = new List<(IMedia, string)>(); using (var scope = ScopeProvider.CreateScope()) { @@ -940,7 +944,7 @@ namespace Umbraco.Core.Services.Implement return OperationResult.Attempt.Succeed(evtMsgs); } - var moves = new List>(); + var moves = new List<(IMedia, string)>(); using (var scope = ScopeProvider.CreateScope()) { @@ -979,7 +983,7 @@ namespace Umbraco.Core.Services.Implement // MUST be called from within WriteLock // trash indicates whether we are trashing, un-trashing, or not changing anything - private void PerformMoveLocked(IMedia media, int parentId, IMedia parent, int userId, ICollection> moves, bool? trash) + private void PerformMoveLocked(IMedia media, int parentId, IMedia parent, int userId, ICollection<(IMedia, string)> moves, bool? trash) { media.ParentId = parentId; @@ -989,7 +993,7 @@ namespace Umbraco.Core.Services.Implement var paths = new Dictionary(); - moves.Add(Tuple.Create(media, media.Path)); // capture original path + moves.Add((media, media.Path)); // capture original path //need to store the original path to lookup descendants based on it below var originalPath = media.Path; @@ -1006,21 +1010,25 @@ namespace Umbraco.Core.Services.Implement paths[media.Id] = (parent == null ? (parentId == Constants.System.RecycleBinMedia ? "-1,-21" : Constants.System.RootString) : parent.Path) + "," + media.Id; const int pageSize = 500; - var page = 0; - var total = long.MaxValue; - while (page * pageSize < total) + var query = GetPagedDescendantQuery(originalPath); + long total; + do { - var descendants = GetPagedDescendantsLocked(originalPath, page++, pageSize, out total, null, Ordering.By("Path", Direction.Ascending)); + // We always page a page 0 because for each page, we are moving the result so the resulting total will be reduced + var descendants = GetPagedLocked(query, 0, pageSize, out total, null, Ordering.By("Path", Direction.Ascending)); + foreach (var descendant in descendants) { - moves.Add(Tuple.Create(descendant, descendant.Path)); // capture original path + moves.Add((descendant, descendant.Path)); // capture original path // update path and level since we do not update parentId descendant.Path = paths[descendant.Id] = paths[descendant.ParentId] + "," + descendant.Id; descendant.Level += levelDelta; PerformMoveMediaLocked(descendant, userId, trash); } - } + + } while (total > pageSize); + } private void PerformMoveMediaLocked(IMedia media, int userId, bool? trash) @@ -1139,6 +1147,26 @@ namespace Umbraco.Core.Services.Implement } return true; + + } + + public ContentDataIntegrityReport CheckDataIntegrity(ContentDataIntegrityReportOptions options) + { + using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + { + scope.WriteLock(Constants.Locks.MediaTree); + + var report = _mediaRepository.CheckDataIntegrity(options); + + if (report.FixedIssues.Count > 0) + { + //The event args needs a content item so we'll make a fake one with enough properties to not cause a null ref + var root = new Models.Media("root", -1, new MediaType(-1)) { Id = -1, Key = Guid.Empty }; + scope.Events.Dispatch(TreeChanged, this, new TreeChange.EventArgs(new TreeChange(root, TreeChangeTypes.RefreshAll))); + } + + return report; + } } #endregion @@ -1277,7 +1305,7 @@ namespace Umbraco.Core.Services.Implement // which we need for many things like keeping caches in sync, but we can surely do this MUCH better. var changes = new List>(); - var moves = new List>(); + var moves = new List<(IMedia, string)>(); var mediaTypeIdsA = mediaTypeIds.ToArray(); using (var scope = ScopeProvider.CreateScope()) @@ -1358,5 +1386,7 @@ namespace Umbraco.Core.Services.Implement } #endregion + + } } diff --git a/src/Umbraco.Core/Services/Implement/MemberService.cs b/src/Umbraco.Core/Services/Implement/MemberService.cs index a64e30495b..9b97c6b161 100644 --- a/src/Umbraco.Core/Services/Implement/MemberService.cs +++ b/src/Umbraco.Core/Services/Implement/MemberService.cs @@ -331,7 +331,7 @@ namespace Umbraco.Core.Services.Implement saveEventArgs.CanCancel = false; scope.Events.Dispatch(Saved, this, saveEventArgs); } - + if (withIdentity == false) return; @@ -816,8 +816,8 @@ namespace Umbraco.Core.Services.Implement { //trimming username and email to make sure we have no trailing space member.Username = member.Username.Trim(); - member.Email = member.Email.Trim(); - + member.Email = member.Email.Trim(); + using (var scope = ScopeProvider.CreateScope()) { var saveEventArgs = new SaveEventArgs(member); @@ -971,6 +971,35 @@ namespace Umbraco.Core.Services.Implement } } + public IEnumerable GetAllRolesIds() + { + using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + { + scope.ReadLock(Constants.Locks.MemberTree); + return _memberGroupRepository.GetMany().Select(x => x.Id).Distinct(); + } + } + + public IEnumerable GetAllRolesIds(int memberId) + { + using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + { + scope.ReadLock(Constants.Locks.MemberTree); + var result = _memberGroupRepository.GetMemberGroupsForMember(memberId); + return result.Select(x => x.Id).Distinct(); + } + } + + public IEnumerable GetAllRolesIds(string username) + { + using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + { + scope.ReadLock(Constants.Locks.MemberTree); + var result = _memberGroupRepository.GetMemberGroupsForMember(username); + return result.Select(x => x.Id).Distinct(); + } + } + public IEnumerable GetMembersInRole(string roleName) { using (var scope = ScopeProvider.CreateScope(autoComplete: true)) @@ -1241,7 +1270,7 @@ namespace Umbraco.Core.Services.Implement /// Exports a member. /// /// - /// This is internal for now and is used to export a member in the member editor, + /// This is internal for now and is used to export a member in the member editor, /// it will raise an event so that auditing logs can be created. /// internal MemberExportModel ExportMember(Guid key) diff --git a/src/Umbraco.Core/Services/Implement/RelationService.cs b/src/Umbraco.Core/Services/Implement/RelationService.cs index 405c3a2800..4b53709de9 100644 --- a/src/Umbraco.Core/Services/Implement/RelationService.cs +++ b/src/Umbraco.Core/Services/Implement/RelationService.cs @@ -25,11 +25,7 @@ namespace Umbraco.Core.Services.Implement _entityService = entityService ?? throw new ArgumentNullException(nameof(entityService)); } - /// - /// Gets a by its Id - /// - /// Id of the - /// A object + /// public IRelation GetById(int id) { using (var scope = ScopeProvider.CreateScope(autoComplete: true)) @@ -38,11 +34,7 @@ namespace Umbraco.Core.Services.Implement } } - /// - /// Gets a by its Id - /// - /// Id of the - /// A object + /// public IRelationType GetRelationTypeById(int id) { using (var scope = ScopeProvider.CreateScope(autoComplete: true)) @@ -51,11 +43,7 @@ namespace Umbraco.Core.Services.Implement } } - /// - /// Gets a by its Id - /// - /// Id of the - /// A object + /// public IRelationType GetRelationTypeById(Guid id) { using (var scope = ScopeProvider.CreateScope(autoComplete: true)) @@ -64,25 +52,10 @@ namespace Umbraco.Core.Services.Implement } } - /// - /// Gets a by its Alias - /// - /// Alias of the - /// A object - public IRelationType GetRelationTypeByAlias(string alias) - { - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) - { - var query = Query().Where(x => x.Alias == alias); - return _relationTypeRepository.Get(query).FirstOrDefault(); - } - } + /// + public IRelationType GetRelationTypeByAlias(string alias) => GetRelationType(alias); - /// - /// Gets all objects - /// - /// Optional array of integer ids to return relations for - /// An enumerable list of objects + /// public IEnumerable GetAllRelations(params int[] ids) { using (var scope = ScopeProvider.CreateScope(autoComplete: true)) @@ -91,21 +64,13 @@ namespace Umbraco.Core.Services.Implement } } - /// - /// Gets all objects by their - /// - /// to retrieve Relations for - /// An enumerable list of objects - public IEnumerable GetAllRelationsByRelationType(RelationType relationType) + /// + public IEnumerable GetAllRelationsByRelationType(IRelationType relationType) { return GetAllRelationsByRelationType(relationType.Id); } - /// - /// Gets all objects by their 's Id - /// - /// Id of the to retrieve Relations for - /// An enumerable list of objects + /// public IEnumerable GetAllRelationsByRelationType(int relationTypeId) { using (var scope = ScopeProvider.CreateScope(autoComplete: true)) @@ -115,11 +80,7 @@ namespace Umbraco.Core.Services.Implement } } - /// - /// Gets all objects - /// - /// Optional array of integer ids to return relationtypes for - /// An enumerable list of objects + /// public IEnumerable GetAllRelationTypes(params int[] ids) { using (var scope = ScopeProvider.CreateScope(autoComplete: true)) @@ -128,82 +89,65 @@ namespace Umbraco.Core.Services.Implement } } - /// - /// Gets a list of objects by their parent Id - /// - /// Id of the parent to retrieve relations for - /// An enumerable list of objects - public IEnumerable GetByParentId(int id) + /// + public IEnumerable GetByParentId(int id) => GetByParentId(id, null); + + /// + public IEnumerable GetByParentId(int id, string relationTypeAlias) { using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { - var query = Query().Where(x => x.ParentId == id); - return _relationRepository.Get(query); + if (relationTypeAlias.IsNullOrWhiteSpace()) + { + var qry1 = Query().Where(x => x.ParentId == id); + return _relationRepository.Get(qry1); + } + + var relationType = GetRelationType(relationTypeAlias); + if (relationType == null) + return Enumerable.Empty(); + + var qry2 = Query().Where(x => x.ParentId == id && x.RelationTypeId == relationType.Id); + return _relationRepository.Get(qry2); } } - /// - /// Gets a list of objects by their parent entity - /// - /// Parent Entity to retrieve relations for - /// An enumerable list of objects - public IEnumerable GetByParent(IUmbracoEntity parent) - { - return GetByParentId(parent.Id); - } + /// + public IEnumerable GetByParent(IUmbracoEntity parent) => GetByParentId(parent.Id); - /// - /// Gets a list of objects by their parent entity - /// - /// Parent Entity to retrieve relations for - /// Alias of the type of relation to retrieve - /// An enumerable list of objects - public IEnumerable GetByParent(IUmbracoEntity parent, string relationTypeAlias) - { - return GetByParent(parent).Where(relation => relation.RelationType.Alias == relationTypeAlias); - } + /// + public IEnumerable GetByParent(IUmbracoEntity parent, string relationTypeAlias) => GetByParentId(parent.Id, relationTypeAlias); - /// - /// Gets a list of objects by their child Id - /// - /// Id of the child to retrieve relations for - /// An enumerable list of objects - public IEnumerable GetByChildId(int id) + /// + public IEnumerable GetByChildId(int id) => GetByChildId(id, null); + + /// + public IEnumerable GetByChildId(int id, string relationTypeAlias) { using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { - var query = Query().Where(x => x.ChildId == id); - return _relationRepository.Get(query); + if (relationTypeAlias.IsNullOrWhiteSpace()) + { + var qry1 = Query().Where(x => x.ChildId == id); + return _relationRepository.Get(qry1); + } + + var relationType = GetRelationType(relationTypeAlias); + if (relationType == null) + return Enumerable.Empty(); + + var qry2 = Query().Where(x => x.ChildId == id && x.RelationTypeId == relationType.Id); + return _relationRepository.Get(qry2); } } - /// - /// Gets a list of objects by their child Entity - /// - /// Child Entity to retrieve relations for - /// An enumerable list of objects - public IEnumerable GetByChild(IUmbracoEntity child) - { - return GetByChildId(child.Id); - } + /// + public IEnumerable GetByChild(IUmbracoEntity child) => GetByChildId(child.Id); - /// - /// Gets a list of objects by their child Entity - /// - /// Child Entity to retrieve relations for - /// Alias of the type of relation to retrieve - /// An enumerable list of objects - public IEnumerable GetByChild(IUmbracoEntity child, string relationTypeAlias) - { - return GetByChild(child).Where(relation => relation.RelationType.Alias == relationTypeAlias); - } + /// + public IEnumerable GetByChild(IUmbracoEntity child, string relationTypeAlias) => GetByChildId(child.Id, relationTypeAlias); - /// - /// Gets a list of objects by their child or parent Id. - /// Using this method will get you all relations regards of it being a child or parent relation. - /// - /// Id of the child or parent to retrieve relations for - /// An enumerable list of objects + /// public IEnumerable GetByParentOrChildId(int id) { using (var scope = ScopeProvider.CreateScope(autoComplete: true)) @@ -217,8 +161,7 @@ namespace Umbraco.Core.Services.Implement { using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { - var rtQuery = Query().Where(x => x.Alias == relationTypeAlias); - var relationType = _relationTypeRepository.Get(rtQuery).FirstOrDefault(); + var relationType = GetRelationType(relationTypeAlias); if (relationType == null) return Enumerable.Empty(); @@ -227,16 +170,13 @@ namespace Umbraco.Core.Services.Implement } } - /// - /// Gets a list of objects by the Name of the - /// - /// Name of the to retrieve Relations for - /// An enumerable list of objects + /// public IEnumerable GetByRelationTypeName(string relationTypeName) { List relationTypeIds; using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { + //This is a silly query - but i guess it's needed in case someone has more than one relation with the same Name (not alias), odd. var query = Query().Where(x => x.Name == relationTypeName); var relationTypes = _relationTypeRepository.Get(query); relationTypeIds = relationTypes.Select(x => x.Id).ToList(); @@ -247,31 +187,17 @@ namespace Umbraco.Core.Services.Implement : GetRelationsByListOfTypeIds(relationTypeIds); } - /// - /// Gets a list of objects by the Alias of the - /// - /// Alias of the to retrieve Relations for - /// An enumerable list of objects + /// public IEnumerable GetByRelationTypeAlias(string relationTypeAlias) { - List relationTypeIds; - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) - { - var query = Query().Where(x => x.Alias == relationTypeAlias); - var relationTypes = _relationTypeRepository.Get(query); - relationTypeIds = relationTypes.Select(x => x.Id).ToList(); - } - - return relationTypeIds.Count == 0 + var relationType = GetRelationType(relationTypeAlias); + + return relationType == null ? Enumerable.Empty() - : GetRelationsByListOfTypeIds(relationTypeIds); + : GetRelationsByListOfTypeIds(new[] { relationType.Id }); } - /// - /// Gets a list of objects by the Id of the - /// - /// Id of the to retrieve Relations for - /// An enumerable list of objects + /// public IEnumerable GetByRelationTypeId(int relationTypeId) { using (var scope = ScopeProvider.CreateScope(autoComplete: true)) @@ -281,37 +207,35 @@ namespace Umbraco.Core.Services.Implement } } - /// - /// Gets the Child object from a Relation as an - /// - /// Relation to retrieve child object from - /// An + /// + public IEnumerable GetPagedByRelationTypeId(int relationTypeId, long pageIndex, int pageSize, out long totalRecords, Ordering ordering = null) + { + using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + { + var query = Query().Where(x => x.RelationTypeId == relationTypeId); + return _relationRepository.GetPagedRelationsByQuery(query, pageIndex, pageSize, out totalRecords, ordering); + } + } + + /// public IUmbracoEntity GetChildEntityFromRelation(IRelation relation) { - var objectType = ObjectTypes.GetUmbracoObjectType(relation.RelationType.ChildObjectType); + var objectType = ObjectTypes.GetUmbracoObjectType(relation.ChildObjectType); return _entityService.Get(relation.ChildId, objectType); } - /// - /// Gets the Parent object from a Relation as an - /// - /// Relation to retrieve parent object from - /// An + /// public IUmbracoEntity GetParentEntityFromRelation(IRelation relation) { - var objectType = ObjectTypes.GetUmbracoObjectType(relation.RelationType.ParentObjectType); + var objectType = ObjectTypes.GetUmbracoObjectType(relation.ParentObjectType); return _entityService.Get(relation.ParentId, objectType); } - /// - /// Gets the Parent and Child objects from a Relation as a "/> with . - /// - /// Relation to retrieve parent and child object from - /// Returns a Tuple with Parent (item1) and Child (item2) + /// public Tuple GetEntitiesFromRelation(IRelation relation) { - var childObjectType = ObjectTypes.GetUmbracoObjectType(relation.RelationType.ChildObjectType); - var parentObjectType = ObjectTypes.GetUmbracoObjectType(relation.RelationType.ParentObjectType); + var childObjectType = ObjectTypes.GetUmbracoObjectType(relation.ChildObjectType); + var parentObjectType = ObjectTypes.GetUmbracoObjectType(relation.ParentObjectType); var child = _entityService.Get(relation.ChildId, childObjectType); var parent = _entityService.Get(relation.ParentId, parentObjectType); @@ -319,45 +243,63 @@ namespace Umbraco.Core.Services.Implement return new Tuple(parent, child); } - /// - /// Gets the Child objects from a list of Relations as a list of objects. - /// - /// List of relations to retrieve child objects from - /// An enumerable list of + /// public IEnumerable GetChildEntitiesFromRelations(IEnumerable relations) { - foreach (var relation in relations) + // Trying to avoid full N+1 lookups, so we'll group by the object type and then use the GetAll + // method to lookup batches of entities for each parent object type + + foreach (var groupedRelations in relations.GroupBy(x => ObjectTypes.GetUmbracoObjectType(x.ChildObjectType))) { - var objectType = ObjectTypes.GetUmbracoObjectType(relation.RelationType.ChildObjectType); - yield return _entityService.Get(relation.ChildId, objectType); + var objectType = groupedRelations.Key; + var ids = groupedRelations.Select(x => x.ChildId).ToArray(); + foreach (var e in _entityService.GetAll(objectType, ids)) + yield return e; } } - /// - /// Gets the Parent objects from a list of Relations as a list of objects. - /// - /// List of relations to retrieve parent objects from - /// An enumerable list of + /// public IEnumerable GetParentEntitiesFromRelations(IEnumerable relations) { - foreach (var relation in relations) + // Trying to avoid full N+1 lookups, so we'll group by the object type and then use the GetAll + // method to lookup batches of entities for each parent object type + + foreach (var groupedRelations in relations.GroupBy(x => ObjectTypes.GetUmbracoObjectType(x.ParentObjectType))) { - var objectType = ObjectTypes.GetUmbracoObjectType(relation.RelationType.ParentObjectType); - yield return _entityService.Get(relation.ParentId, objectType); + var objectType = groupedRelations.Key; + var ids = groupedRelations.Select(x => x.ParentId).ToArray(); + foreach (var e in _entityService.GetAll(objectType, ids)) + yield return e; } } - /// - /// Gets the Parent and Child objects from a list of Relations as a list of objects. - /// - /// List of relations to retrieve parent and child objects from - /// An enumerable list of with + /// + public IEnumerable GetPagedParentEntitiesByChildId(int id, long pageIndex, int pageSize, out long totalChildren, params UmbracoObjectTypes[] entityTypes) + { + using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + { + return _relationRepository.GetPagedParentEntitiesByChildId(id, pageIndex, pageSize, out totalChildren, entityTypes.Select(x => x.GetGuid()).ToArray()); + } + } + + /// + public IEnumerable GetPagedChildEntitiesByParentId(int id, long pageIndex, int pageSize, out long totalChildren, params UmbracoObjectTypes[] entityTypes) + { + using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + { + return _relationRepository.GetPagedChildEntitiesByParentId(id, pageIndex, pageSize, out totalChildren, entityTypes.Select(x => x.GetGuid()).ToArray()); + } + } + + /// public IEnumerable> GetEntitiesFromRelations(IEnumerable relations) { + //TODO: Argh! N+1 + foreach (var relation in relations) { - var childObjectType = ObjectTypes.GetUmbracoObjectType(relation.RelationType.ChildObjectType); - var parentObjectType = ObjectTypes.GetUmbracoObjectType(relation.RelationType.ParentObjectType); + var childObjectType = ObjectTypes.GetUmbracoObjectType(relation.ChildObjectType); + var parentObjectType = ObjectTypes.GetUmbracoObjectType(relation.ParentObjectType); var child = _entityService.Get(relation.ChildId, childObjectType); var parent = _entityService.Get(relation.ParentId, parentObjectType); @@ -366,19 +308,15 @@ namespace Umbraco.Core.Services.Implement } } - /// - /// Relates two objects by their entity Ids. - /// - /// Id of the parent - /// Id of the child - /// The type of relation to create - /// The created + /// public IRelation Relate(int parentId, int childId, IRelationType relationType) { // Ensure that the RelationType has an identity before using it to relate two entities if (relationType.HasIdentity == false) Save(relationType); + //TODO: We don't check if this exists first, it will throw some sort of data integrity exception if it already exists, is that ok? + var relation = new Relation(parentId, childId, relationType); using (var scope = ScopeProvider.CreateScope()) @@ -398,25 +336,13 @@ namespace Umbraco.Core.Services.Implement } } - /// - /// Relates two objects that are based on the interface. - /// - /// Parent entity - /// Child entity - /// The type of relation to create - /// The created + /// public IRelation Relate(IUmbracoEntity parent, IUmbracoEntity child, IRelationType relationType) { return Relate(parent.Id, child.Id, relationType); } - /// - /// Relates two objects by their entity Ids. - /// - /// Id of the parent - /// Id of the child - /// Alias of the type of relation to create - /// The created + /// public IRelation Relate(int parentId, int childId, string relationTypeAlias) { var relationType = GetRelationTypeByAlias(relationTypeAlias); @@ -426,13 +352,7 @@ namespace Umbraco.Core.Services.Implement return Relate(parentId, childId, relationType); } - /// - /// Relates two objects that are based on the interface. - /// - /// Parent entity - /// Child entity - /// Alias of the type of relation to create - /// The created + /// public IRelation Relate(IUmbracoEntity parent, IUmbracoEntity child, string relationTypeAlias) { var relationType = GetRelationTypeByAlias(relationTypeAlias); @@ -442,11 +362,7 @@ namespace Umbraco.Core.Services.Implement return Relate(parent.Id, child.Id, relationType); } - /// - /// Checks whether any relations exists for the passed in . - /// - /// to check for relations - /// Returns True if any relations exists for the given , otherwise False + /// public bool HasRelations(IRelationType relationType) { using (var scope = ScopeProvider.CreateScope(autoComplete: true)) @@ -456,11 +372,7 @@ namespace Umbraco.Core.Services.Implement } } - /// - /// Checks whether any relations exists for the passed in Id. - /// - /// Id of an object to check relations for - /// Returns True if any relations exists with the given Id, otherwise False + /// public bool IsRelated(int id) { using (var scope = ScopeProvider.CreateScope(autoComplete: true)) @@ -470,12 +382,7 @@ namespace Umbraco.Core.Services.Implement } } - /// - /// Checks whether two items are related - /// - /// Id of the Parent relation - /// Id of the Child relation - /// Returns True if any relations exists with the given Ids, otherwise False + /// public bool AreRelated(int parentId, int childId) { using (var scope = ScopeProvider.CreateScope(autoComplete: true)) @@ -485,13 +392,7 @@ namespace Umbraco.Core.Services.Implement } } - /// - /// Checks whether two items are related with a given relation type alias - /// - /// Id of the Parent relation - /// Id of the Child relation - /// Alias of the relation type - /// Returns True if any relations exists with the given Ids and relation type, otherwise False + /// public bool AreRelated(int parentId, int childId, string relationTypeAlias) { var relType = GetRelationTypeByAlias(relationTypeAlias); @@ -502,13 +403,7 @@ namespace Umbraco.Core.Services.Implement } - /// - /// Checks whether two items are related with a given relation type - /// - /// Id of the Parent relation - /// Id of the Child relation - /// Type of relation - /// Returns True if any relations exists with the given Ids and relation type, otherwise False + /// public bool AreRelated(int parentId, int childId, IRelationType relationType) { using (var scope = ScopeProvider.CreateScope(autoComplete: true)) @@ -518,34 +413,20 @@ namespace Umbraco.Core.Services.Implement } } - /// - /// Checks whether two items are related - /// - /// Parent entity - /// Child entity - /// Returns True if any relations exist between the entities, otherwise False + /// public bool AreRelated(IUmbracoEntity parent, IUmbracoEntity child) { return AreRelated(parent.Id, child.Id); } - /// - /// Checks whether two items are related - /// - /// Parent entity - /// Child entity - /// Alias of the type of relation to create - /// Returns True if any relations exist between the entities, otherwise False + /// public bool AreRelated(IUmbracoEntity parent, IUmbracoEntity child, string relationTypeAlias) { return AreRelated(parent.Id, child.Id, relationTypeAlias); } - /// - /// Saves a - /// - /// Relation to save + /// public void Save(IRelation relation) { using (var scope = ScopeProvider.CreateScope()) @@ -564,10 +445,25 @@ namespace Umbraco.Core.Services.Implement } } - /// - /// Saves a - /// - /// RelationType to Save + public void Save(IEnumerable relations) + { + using (var scope = ScopeProvider.CreateScope()) + { + var saveEventArgs = new SaveEventArgs(relations); + if (scope.Events.DispatchCancelable(SavingRelation, this, saveEventArgs)) + { + scope.Complete(); + return; + } + + _relationRepository.Save(relations); + scope.Complete(); + saveEventArgs.CanCancel = false; + scope.Events.Dispatch(SavedRelation, this, saveEventArgs); + } + } + + /// public void Save(IRelationType relationType) { using (var scope = ScopeProvider.CreateScope()) @@ -586,10 +482,7 @@ namespace Umbraco.Core.Services.Implement } } - /// - /// Deletes a - /// - /// Relation to Delete + /// public void Delete(IRelation relation) { using (var scope = ScopeProvider.CreateScope()) @@ -608,10 +501,7 @@ namespace Umbraco.Core.Services.Implement } } - /// - /// Deletes a - /// - /// RelationType to Delete + /// public void Delete(IRelationType relationType) { using (var scope = ScopeProvider.CreateScope()) @@ -630,10 +520,7 @@ namespace Umbraco.Core.Services.Implement } } - /// - /// Deletes all objects based on the passed in - /// - /// to Delete Relations for + /// public void DeleteRelationsOfType(IRelationType relationType) { var relations = new List(); @@ -642,6 +529,8 @@ namespace Umbraco.Core.Services.Implement var query = Query().Where(x => x.RelationTypeId == relationType.Id); relations.AddRange(_relationRepository.Get(query).ToList()); + //TODO: N+1, we should be able to do this in a single call + foreach (var relation in relations) _relationRepository.Delete(relation); @@ -653,6 +542,15 @@ namespace Umbraco.Core.Services.Implement #region Private Methods + private IRelationType GetRelationType(string relationTypeAlias) + { + using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + { + var query = Query().Where(x => x.Alias == relationTypeAlias); + return _relationTypeRepository.Get(query).FirstOrDefault(); + } + } + private IEnumerable GetRelationsByListOfTypeIds(IEnumerable relationTypeIds) { var relations = new List(); diff --git a/src/Umbraco.Core/Services/Implement/UserService.cs b/src/Umbraco.Core/Services/Implement/UserService.cs index 363bc72bc3..6f5cbfca56 100644 --- a/src/Umbraco.Core/Services/Implement/UserService.cs +++ b/src/Umbraco.Core/Services/Implement/UserService.cs @@ -372,7 +372,7 @@ namespace Umbraco.Core.Services.Implement /// public string GetDefaultMemberType() { - return "writer"; + return Constants.Security.WriterGroupAlias; } /// @@ -1197,12 +1197,12 @@ namespace Umbraco.Core.Services.Implement /// /// Occurs before Save /// - internal static event TypedEventHandler> SavingUserGroup; + public static event TypedEventHandler> SavingUserGroup; /// /// Occurs after Save /// - internal static event TypedEventHandler> SavedUserGroup; + public static event TypedEventHandler> SavedUserGroup; /// /// Occurs before Delete diff --git a/src/Umbraco.Core/Services/UserServiceExtensions.cs b/src/Umbraco.Core/Services/UserServiceExtensions.cs index 82cab07b25..c365f1ccc2 100644 --- a/src/Umbraco.Core/Services/UserServiceExtensions.cs +++ b/src/Umbraco.Core/Services/UserServiceExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Web.Security; using Umbraco.Core.Models.Membership; @@ -116,5 +117,17 @@ namespace Umbraco.Core.Services var permissionCollection = userService.GetPermissions(user, nodeId); return permissionCollection.SelectMany(c => c.AssignedPermissions).Distinct().ToArray(); } + + internal static IEnumerable GetProfilesById(this IUserService userService, params int[] ids) + { + var fullUsers = userService.GetUsersById(ids); + + return fullUsers.Select(user => + { + var asProfile = user as IProfile; + return asProfile ?? new UserProfile(user.Id, user.Name); + }); + + } } } diff --git a/src/Umbraco.Core/StringExtensions.cs b/src/Umbraco.Core/StringExtensions.cs index 4451fdbba7..c37f8bdf35 100644 --- a/src/Umbraco.Core/StringExtensions.cs +++ b/src/Umbraco.Core/StringExtensions.cs @@ -79,6 +79,21 @@ namespace Umbraco.Core } + /// + /// Determines the extension of the path or URL + /// + /// + /// Extension of the file + public static string GetFileExtension(this string file) + { + //Find any characters between the last . and the start of a query string or the end of the string + const string pattern = @"(?\.[^\.\?]+)(\?.*|$)"; + var match = Regex.Match(file, pattern); + return match.Success + ? match.Groups["extension"].Value + : string.Empty; + } + /// /// Based on the input string, this will detect if the string is a JS path or a JS snippet. /// If a path cannot be determined, then it is assumed to be a snippet the original text is returned diff --git a/src/Umbraco.Core/Udi.cs b/src/Umbraco.Core/Udi.cs index c7297b8c09..b11ce250ad 100644 --- a/src/Umbraco.Core/Udi.cs +++ b/src/Umbraco.Core/Udi.cs @@ -233,7 +233,7 @@ namespace Umbraco.Core // just pick every service connectors - just making sure that not two of them // would register the same entity type, with different udi types (would not make // much sense anyways). - var connectors = Current.TypeLoader.GetTypes(); + var connectors = Current.HasFactory ? (Current.TypeLoader?.GetTypes() ?? Enumerable.Empty()) : Enumerable.Empty(); var result = new Dictionary(); foreach (var connector in connectors) { @@ -316,7 +316,7 @@ namespace Umbraco.Core if (udiType == UdiType.GuidUdi) return new GuidUdi(uri); - if (udiType == UdiType.GuidUdi) + if (udiType == UdiType.StringUdi) return new StringUdi(uri); throw new ArgumentException(string.Format("Uri \"{0}\" is not a valid udi.", uri)); @@ -368,7 +368,7 @@ namespace Umbraco.Core return (udi1 == udi2) == false; } - private class UnknownTypeUdi : Udi + internal class UnknownTypeUdi : Udi { private UnknownTypeUdi() : base("unknown", "umb://unknown/") diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 1393971898..f15b098473 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -128,7 +128,30 @@ --> + + + + + + + + + + + + + + + + + + + + + + + @@ -237,6 +260,7 @@ + @@ -252,6 +276,9 @@ + + + @@ -277,10 +304,15 @@ + + + + + @@ -402,7 +434,7 @@ - + @@ -733,7 +765,7 @@ - + @@ -1530,12 +1562,13 @@ - + + diff --git a/src/Umbraco.Core/UpgradeService.cs b/src/Umbraco.Core/UpgradeService.cs new file mode 100644 index 0000000000..8116c0e738 --- /dev/null +++ b/src/Umbraco.Core/UpgradeService.cs @@ -0,0 +1,23 @@ +using System.Threading.Tasks; +using Semver; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Repositories; +using Umbraco.Core.Services; + +namespace Umbraco.Core +{ + internal class UpgradeService : IUpgradeService + { + private readonly IUpgradeCheckRepository _upgradeCheckRepository; + + public UpgradeService(IUpgradeCheckRepository upgradeCheckRepository) + { + _upgradeCheckRepository = upgradeCheckRepository; + } + + public async Task CheckUpgrade(SemVersion version) + { + return await _upgradeCheckRepository.CheckUpgradeAsync(version); + } + } +} diff --git a/src/Umbraco.Core/Xml/XmlHelper.cs b/src/Umbraco.Core/Xml/XmlHelper.cs index 2dd955086d..d6461ec8c6 100644 --- a/src/Umbraco.Core/Xml/XmlHelper.cs +++ b/src/Umbraco.Core/Xml/XmlHelper.cs @@ -214,9 +214,9 @@ namespace Umbraco.Core.Xml var xmlDoc = new XmlDocument(); //Load the file into the XmlDocument xmlDoc.Load(reader); - + return xmlDoc; - } + } } /// @@ -335,7 +335,7 @@ namespace Umbraco.Core.Xml var child = parent.SelectSingleNode(name); if (child != null) { - child.InnerXml = ""; ; + child.InnerXml = ""; return child; } return AddCDataNode(xd, name, value); diff --git a/src/Umbraco.Examine/ContentValueSetBuilder.cs b/src/Umbraco.Examine/ContentValueSetBuilder.cs index 9cbc311639..b8477a9047 100644 --- a/src/Umbraco.Examine/ContentValueSetBuilder.cs +++ b/src/Umbraco.Examine/ContentValueSetBuilder.cs @@ -1,9 +1,13 @@ using Examine; +using System; using System.Collections.Generic; using System.Linq; using Umbraco.Core; +using Umbraco.Core.Composing; using Umbraco.Core.Models; +using Umbraco.Core.Models.Membership; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Scoping; using Umbraco.Core.Services; using Umbraco.Core.Strings; @@ -16,20 +20,46 @@ namespace Umbraco.Examine { private readonly UrlSegmentProviderCollection _urlSegmentProviders; private readonly IUserService _userService; + private readonly IScopeProvider _scopeProvider; + + [Obsolete("Use the other ctor instead")] + public ContentValueSetBuilder(PropertyEditorCollection propertyEditors, + UrlSegmentProviderCollection urlSegmentProviders, + IUserService userService, + bool publishedValuesOnly) + : this(propertyEditors, urlSegmentProviders, userService, Current.ScopeProvider, publishedValuesOnly) + { + } public ContentValueSetBuilder(PropertyEditorCollection propertyEditors, UrlSegmentProviderCollection urlSegmentProviders, IUserService userService, + IScopeProvider scopeProvider, bool publishedValuesOnly) : base(propertyEditors, publishedValuesOnly) { _urlSegmentProviders = urlSegmentProviders; _userService = userService; + _scopeProvider = scopeProvider; } /// public override IEnumerable GetValueSets(params IContent[] content) { + Dictionary creatorIds; + Dictionary writerIds; + + // We can lookup all of the creator/writer names at once which can save some + // processing below instead of one by one. + using (var scope = _scopeProvider.CreateScope()) + { + creatorIds = _userService.GetProfilesById(content.Select(x => x.CreatorId).ToArray()) + .ToDictionary(x => x.Id, x => x); + writerIds = _userService.GetProfilesById(content.Select(x => x.WriterId).ToArray()) + .ToDictionary(x => x.Id, x => x); + scope.Complete(); + } + // TODO: There is a lot of boxing going on here and ultimately all values will be boxed by Lucene anyways // but I wonder if there's a way to reduce the boxing that we have to do or if it will matter in the end since // Lucene will do it no matter what? One idea was to create a `FieldValue` struct which would contain `object`, `object[]`, `ValueType` and `ValueType[]` @@ -58,8 +88,8 @@ namespace Umbraco.Examine {"urlName", urlValue?.Yield() ?? Enumerable.Empty()}, //Always add invariant urlName {"path", c.Path?.Yield() ?? Enumerable.Empty()}, {"nodeType", c.ContentType.Id.ToString().Yield() ?? Enumerable.Empty()}, - {"creatorName", (c.GetCreatorProfile(_userService)?.Name ?? "??").Yield() }, - {"writerName",(c.GetWriterProfile(_userService)?.Name ?? "??").Yield() }, + {"creatorName", (creatorIds.TryGetValue(c.CreatorId, out var creatorProfile) ? creatorProfile.Name : "??").Yield() }, + {"writerName", (writerIds.TryGetValue(c.WriterId, out var writerProfile) ? writerProfile.Name : "??").Yield() }, {"writerID", new object[] {c.WriterId}}, {"templateID", new object[] {c.TemplateId ?? 0}}, {UmbracoContentIndex.VariesByCultureFieldName, new object[] {"n"}}, diff --git a/src/Umbraco.ModelsBuilder.Embedded/Building/TextBuilder.cs b/src/Umbraco.ModelsBuilder.Embedded/Building/TextBuilder.cs index d1190a0374..7ef8112d11 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Building/TextBuilder.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Building/TextBuilder.cs @@ -94,7 +94,7 @@ namespace Umbraco.ModelsBuilder.Embedded.Building // private static void WriteGeneratedCodeAttribute(StringBuilder sb, string tabs) { - sb.AppendFormat("{0}[global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"Umbraco.ModelsBuilder\", \"{1}\")]\n", tabs, ApiVersion.Current.Version); + sb.AppendFormat("{0}[global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"Umbraco.ModelsBuilder.Embedded\", \"{1}\")]\n", tabs, ApiVersion.Current.Version); } private void WriteContentType(StringBuilder sb, TypeModel type) diff --git a/src/Umbraco.ModelsBuilder.Embedded/Building/TextHeaderWriter.cs b/src/Umbraco.ModelsBuilder.Embedded/Building/TextHeaderWriter.cs index a93df97806..0ffad1c5bc 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Building/TextHeaderWriter.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Building/TextHeaderWriter.cs @@ -14,7 +14,7 @@ namespace Umbraco.ModelsBuilder.Embedded.Building sb.Append("// \n"); sb.Append("// This code was generated by a tool.\n"); sb.Append("//\n"); - sb.AppendFormat("// Umbraco.ModelsBuilder v{0}\n", ApiVersion.Current.Version); + sb.AppendFormat("// Umbraco.ModelsBuilder.Embedded v{0}\n", ApiVersion.Current.Version); sb.Append("//\n"); sb.Append("// Changes to this file will be lost if the code is regenerated.\n"); sb.Append("// \n"); diff --git a/src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsBuilderConfig.cs b/src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsBuilderConfig.cs index c6bccdcf87..179fcecfcb 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsBuilderConfig.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsBuilderConfig.cs @@ -1,6 +1,7 @@ using System; using System.Configuration; using System.IO; +using System.Threading; using System.Web.Configuration; using Umbraco.Core; using Umbraco.Core.IO; @@ -12,6 +13,13 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration /// public class ModelsBuilderConfig : IModelsBuilderConfig { + private const string Prefix = "Umbraco.ModelsBuilder."; + private object _modelsModelLock; + private bool _modelsModelConfigured; + private ModelsMode _modelsMode; + private object _flagOutOfDateModelsLock; + private bool _flagOutOfDateModelsConfigured; + private bool _flagOutOfDateModels; public const string DefaultModelsNamespace = "Umbraco.Web.PublishedModels"; public const string DefaultModelsDirectory = "~/App_Data/Models"; @@ -20,11 +28,9 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration /// public ModelsBuilderConfig() { - const string prefix = "Umbraco.ModelsBuilder."; - // giant kill switch, default: false // must be explicitely set to true for anything else to happen - Enable = ConfigurationManager.AppSettings[prefix + "Enable"] == "true"; + Enable = ConfigurationManager.AppSettings[Prefix + "Enable"] == "true"; // ensure defaults are initialized for tests ModelsNamespace = DefaultModelsNamespace; @@ -34,44 +40,19 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration // stop here, everything is false if (!Enable) return; - // mode - var modelsMode = ConfigurationManager.AppSettings[prefix + "ModelsMode"]; - if (!string.IsNullOrWhiteSpace(modelsMode)) - { - switch (modelsMode) - { - case nameof(ModelsMode.Nothing): - ModelsMode = ModelsMode.Nothing; - break; - case nameof(ModelsMode.PureLive): - ModelsMode = ModelsMode.PureLive; - break; - case nameof(ModelsMode.AppData): - ModelsMode = ModelsMode.AppData; - break; - case nameof(ModelsMode.LiveAppData): - ModelsMode = ModelsMode.LiveAppData; - break; - default: - throw new ConfigurationErrorsException($"ModelsMode \"{modelsMode}\" is not a valid mode." - + " Note that modes are case-sensitive. Possible values are: " + string.Join(", ", Enum.GetNames(typeof(ModelsMode)))); - } - } - // default: false - AcceptUnsafeModelsDirectory = ConfigurationManager.AppSettings[prefix + "AcceptUnsafeModelsDirectory"].InvariantEquals("true"); + AcceptUnsafeModelsDirectory = ConfigurationManager.AppSettings[Prefix + "AcceptUnsafeModelsDirectory"].InvariantEquals("true"); // default: true - EnableFactory = !ConfigurationManager.AppSettings[prefix + "EnableFactory"].InvariantEquals("false"); - FlagOutOfDateModels = !ConfigurationManager.AppSettings[prefix + "FlagOutOfDateModels"].InvariantEquals("false"); + EnableFactory = !ConfigurationManager.AppSettings[Prefix + "EnableFactory"].InvariantEquals("false"); // default: initialized above with DefaultModelsNamespace const - var value = ConfigurationManager.AppSettings[prefix + "ModelsNamespace"]; + var value = ConfigurationManager.AppSettings[Prefix + "ModelsNamespace"]; if (!string.IsNullOrWhiteSpace(value)) ModelsNamespace = value; // default: initialized above with DefaultModelsDirectory const - value = ConfigurationManager.AppSettings[prefix + "ModelsDirectory"]; + value = ConfigurationManager.AppSettings[Prefix + "ModelsDirectory"]; if (!string.IsNullOrWhiteSpace(value)) { var root = IOHelper.MapPath("~/"); @@ -83,18 +64,14 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration } // default: 0 - value = ConfigurationManager.AppSettings[prefix + "DebugLevel"]; + value = ConfigurationManager.AppSettings[Prefix + "DebugLevel"]; if (!string.IsNullOrWhiteSpace(value)) { - int debugLevel; - if (!int.TryParse(value, out debugLevel)) + if (!int.TryParse(value, out var debugLevel)) throw new ConfigurationErrorsException($"Invalid debug level \"{value}\"."); DebugLevel = debugLevel; } - // not flagging if not generating, or live (incl. pure) - if (ModelsMode == ModelsMode.Nothing || ModelsMode.IsLive()) - FlagOutOfDateModels = false; } /// @@ -111,11 +88,11 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration int debugLevel = 0) { Enable = enable; - ModelsMode = modelsMode; + _modelsMode = modelsMode; ModelsNamespace = string.IsNullOrWhiteSpace(modelsNamespace) ? DefaultModelsNamespace : modelsNamespace; EnableFactory = enableFactory; - FlagOutOfDateModels = flagOutOfDateModels; + _flagOutOfDateModels = flagOutOfDateModels; ModelsDirectory = string.IsNullOrWhiteSpace(modelsDirectory) ? DefaultModelsDirectory : modelsDirectory; AcceptUnsafeModelsDirectory = acceptUnsafeModelsDirectory; DebugLevel = debugLevel; @@ -164,7 +141,26 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration /// /// Gets the models mode. /// - public ModelsMode ModelsMode { get; } + public ModelsMode ModelsMode => + LazyInitializer.EnsureInitialized(ref _modelsMode, ref _modelsModelConfigured, ref _modelsModelLock, () => + { + // mode + var modelsMode = ConfigurationManager.AppSettings[Prefix + "ModelsMode"]; + if (string.IsNullOrWhiteSpace(modelsMode)) return ModelsMode.Nothing; //default + switch (modelsMode) + { + case nameof(ModelsMode.Nothing): + return ModelsMode.Nothing; + case nameof(ModelsMode.PureLive): + return ModelsMode.PureLive; + case nameof(ModelsMode.AppData): + return ModelsMode.AppData; + case nameof(ModelsMode.LiveAppData): + return ModelsMode.LiveAppData; + default: + throw new ConfigurationErrorsException($"ModelsMode \"{modelsMode}\" is not a valid mode." + " Note that modes are case-sensitive. Possible values are: " + string.Join(", ", Enum.GetNames(typeof(ModelsMode)))); + } + }); /// /// Gets a value indicating whether system.web/compilation/@debug is true. @@ -196,7 +192,17 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration /// Models become out-of-date when data types or content types are updated. When this /// setting is activated the ~/App_Data/Models/ood.txt file is then created. When models are /// generated through the dashboard, the files is cleared. Default value is false. - public bool FlagOutOfDateModels { get; } + public bool FlagOutOfDateModels + => LazyInitializer.EnsureInitialized(ref _flagOutOfDateModels, ref _flagOutOfDateModelsConfigured, ref _flagOutOfDateModelsLock, () => + { + var flagOutOfDateModels = !ConfigurationManager.AppSettings[Prefix + "FlagOutOfDateModels"].InvariantEquals("false"); + if (ModelsMode == ModelsMode.Nothing || ModelsMode.IsLive()) + { + flagOutOfDateModels = false; + } + + return flagOutOfDateModels; + }); /// /// Gets the models directory. diff --git a/src/Umbraco.ModelsBuilder.Embedded/ImplementPropertyTypeAttribute.cs b/src/Umbraco.ModelsBuilder.Embedded/ImplementPropertyTypeAttribute.cs index 0359c49654..b7b2695a08 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/ImplementPropertyTypeAttribute.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/ImplementPropertyTypeAttribute.cs @@ -7,7 +7,7 @@ namespace Umbraco.ModelsBuilder.Embedded /// /// And therefore it should not be generated. [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)] - public sealed class ImplementPropertyTypeAttribute : Attribute + public class ImplementPropertyTypeAttribute : Attribute { public ImplementPropertyTypeAttribute(string alias) { diff --git a/src/Umbraco.ModelsBuilder.Embedded/Properties/AssemblyInfo.cs b/src/Umbraco.ModelsBuilder.Embedded/Properties/AssemblyInfo.cs index 68c149adde..5fa17d3c77 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Properties/AssemblyInfo.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Properties/AssemblyInfo.cs @@ -2,7 +2,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -[assembly: AssemblyTitle("Umbraco.ModelsBuilder")] +[assembly: AssemblyTitle("Umbraco.ModelsBuilder.Embedded")] [assembly: AssemblyDescription("Umbraco ModelsBuilder")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyProduct("Umbraco CMS")] diff --git a/src/Umbraco.ModelsBuilder.Embedded/PublishedElementExtensions.cs b/src/Umbraco.ModelsBuilder.Embedded/PublishedElementExtensions.cs index 29429ba74f..8a0a688942 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/PublishedElementExtensions.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/PublishedElementExtensions.cs @@ -17,7 +17,7 @@ namespace Umbraco.Web /// /// Gets the value of a property. /// - public static TValue Value(this TModel model, Expression> property, string culture = null, string segment = null, Fallback fallback = default, TValue defaultValue = default) + public static TValue ValueFor(this TModel model, Expression> property, string culture = null, string segment = null, Fallback fallback = default, TValue defaultValue = default) where TModel : IPublishedElement { var alias = GetAlias(model, property); @@ -45,7 +45,7 @@ namespace Umbraco.Web var attribute = member.GetCustomAttribute(); if (attribute == null) throw new InvalidOperationException("Property is not marked with ImplementPropertyType attribute."); - + return attribute.Alias; } } diff --git a/src/Umbraco.ModelsBuilder.Embedded/PureLiveModelFactory.cs b/src/Umbraco.ModelsBuilder.Embedded/PureLiveModelFactory.cs index 8e8a19c729..0e125759c6 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/PureLiveModelFactory.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/PureLiveModelFactory.cs @@ -21,7 +21,7 @@ using File = System.IO.File; namespace Umbraco.ModelsBuilder.Embedded { - internal class PureLiveModelFactory : ILivePublishedModelFactory, IRegisteredObject + internal class PureLiveModelFactory : ILivePublishedModelFactory2, IRegisteredObject { private Assembly _modelsAssembly; private Infos _infos = new Infos { ModelInfos = null, ModelTypeMap = new Dictionary() }; @@ -134,6 +134,16 @@ namespace Umbraco.ModelsBuilder.Embedded return ctor(); } + /// + public bool Enabled => _config.Enable; + + /// + public void Reset() + { + if (_config.Enable) + ResetModels(); + } + #endregion #region Compilation diff --git a/src/Umbraco.TestData/Properties/AssemblyInfo.cs b/src/Umbraco.TestData/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..3c4251cdf6 --- /dev/null +++ b/src/Umbraco.TestData/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Umbraco.TestData")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Umbraco.TestData")] +[assembly: AssemblyCopyright("Copyright © 2019")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("fb5676ed-7a69-492c-b802-e7b24144c0fc")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/Umbraco.TestData/SegmentTestController.cs b/src/Umbraco.TestData/SegmentTestController.cs new file mode 100644 index 0000000000..650820760e --- /dev/null +++ b/src/Umbraco.TestData/SegmentTestController.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Web.Mvc; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Web.Mvc; + +namespace Umbraco.TestData +{ + public class SegmentTestController : SurfaceController + { + + public ActionResult EnableDocTypeSegments(string alias, string propertyTypeAlias) + { + if (ConfigurationManager.AppSettings["Umbraco.TestData.Enabled"] != "true") + return HttpNotFound(); + + var ct = Services.ContentTypeService.Get(alias); + if (ct == null) + return Content($"No document type found by alias {alias}"); + + var propType = ct.PropertyTypes.FirstOrDefault(x => x.Alias == propertyTypeAlias); + if (propType == null) + return Content($"The document type {alias} does not have a property type {propertyTypeAlias ?? "null"}"); + + if (ct.Variations.VariesBySegment()) + return Content($"The document type {alias} already allows segments, nothing has been changed"); + + ct.SetVariesBy(ContentVariation.Segment); + propType.SetVariesBy(ContentVariation.Segment); + + Services.ContentTypeService.Save(ct); + return Content($"The document type {alias} and property type {propertyTypeAlias} now allows segments"); + } + + public ActionResult DisableDocTypeSegments(string alias) + { + if (ConfigurationManager.AppSettings["Umbraco.TestData.Enabled"] != "true") + return HttpNotFound(); + + var ct = Services.ContentTypeService.Get(alias); + if (ct == null) + return Content($"No document type found by alias {alias}"); + + if (!ct.VariesBySegment()) + return Content($"The document type {alias} does not allow segments, nothing has been changed"); + + ct.SetVariesBy(ContentVariation.Segment, false); + + Services.ContentTypeService.Save(ct); + return Content($"The document type {alias} no longer allows segments"); + } + + public ActionResult AddSegmentData(int contentId, string propertyAlias, string value, string segment, string culture = null) + { + var content = Services.ContentService.GetById(contentId); + if (content == null) + return Content($"No content found by id {contentId}"); + + if (propertyAlias.IsNullOrWhiteSpace() || !content.HasProperty(propertyAlias)) + return Content($"The content by id {contentId} does not contain a property with alias {propertyAlias ?? "null"}"); + + if (content.ContentType.VariesByCulture() && culture.IsNullOrWhiteSpace()) + return Content($"The content by id {contentId} varies by culture but no culture was specified"); + + if (value.IsNullOrWhiteSpace()) + return Content("'value' cannot be null"); + + if (segment.IsNullOrWhiteSpace()) + return Content("'segment' cannot be null"); + + content.SetValue(propertyAlias, value, culture, segment); + Services.ContentService.Save(content); + + return Content($"Segment value has been set on content {contentId} for property {propertyAlias}"); + } + } +} diff --git a/src/Umbraco.TestData/Umbraco.TestData.csproj b/src/Umbraco.TestData/Umbraco.TestData.csproj new file mode 100644 index 0000000000..d61321ebb8 --- /dev/null +++ b/src/Umbraco.TestData/Umbraco.TestData.csproj @@ -0,0 +1,70 @@ + + + + + Debug + AnyCPU + {FB5676ED-7A69-492C-B802-E7B24144C0FC} + Library + Properties + Umbraco.TestData + Umbraco.TestData + v4.7.2 + 512 + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + {31785bc3-256c-4613-b2f5-a1b0bdded8c1} + Umbraco.Core + + + {651e1350-91b6-44b7-bd60-7207006d7003} + Umbraco.Web + + + + + 28.4.4 + + + 5.2.7 + + + + \ No newline at end of file diff --git a/src/Umbraco.TestData/UmbracoTestDataController.cs b/src/Umbraco.TestData/UmbracoTestDataController.cs new file mode 100644 index 0000000000..02949d5345 --- /dev/null +++ b/src/Umbraco.TestData/UmbracoTestDataController.cs @@ -0,0 +1,288 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Umbraco.Core; +using System.Web.Mvc; +using Umbraco.Core.Cache; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; +using Umbraco.Web; +using Umbraco.Web.Mvc; +using System.Configuration; +using Bogus; +using Umbraco.Core.Scoping; + +namespace Umbraco.TestData +{ + /// + /// Creates test data + /// + public class UmbracoTestDataController : SurfaceController + { + private const string RichTextDataTypeName = "UmbracoTestDataContent.RTE"; + private const string MediaPickerDataTypeName = "UmbracoTestDataContent.MediaPicker"; + private const string TextDataTypeName = "UmbracoTestDataContent.Text"; + private const string TestDataContentTypeAlias = "umbTestDataContent"; + private readonly IScopeProvider _scopeProvider; + private readonly PropertyEditorCollection _propertyEditors; + + public UmbracoTestDataController(IScopeProvider scopeProvider, PropertyEditorCollection propertyEditors, IUmbracoContextAccessor umbracoContextAccessor, IUmbracoDatabaseFactory databaseFactory, ServiceContext services, AppCaches appCaches, ILogger logger, IProfilingLogger profilingLogger, UmbracoHelper umbracoHelper) : base(umbracoContextAccessor, databaseFactory, services, appCaches, logger, profilingLogger, umbracoHelper) + { + _scopeProvider = scopeProvider; + _propertyEditors = propertyEditors; + } + + /// + /// Creates a content and associated media tree (hierarchy) + /// + /// + /// + /// + /// + /// + /// Each content item created is associated to a media item via a media picker and therefore a relation is created between the two + /// + public ActionResult CreateTree(int count, int depth, string locale = "en") + { + if (ConfigurationManager.AppSettings["Umbraco.TestData.Enabled"] != "true") + return HttpNotFound(); + + if (!Validate(count, depth, out var message, out var perLevel)) + throw new InvalidOperationException(message); + + var faker = new Faker(locale); + var company = faker.Company.CompanyName(); + + using (var scope = _scopeProvider.CreateScope()) + { + var imageIds = CreateMediaTree(company, faker, count, depth).ToList(); + var contentIds = CreateContentTree(company, faker, count, depth, imageIds, out var root).ToList(); + + Services.ContentService.SaveAndPublishBranch(root, true); + + scope.Complete(); + } + + + return Content("Done"); + } + + private bool Validate(int count, int depth, out string message, out int perLevel) + { + perLevel = 0; + message = null; + + if (count <= 0) + { + message = "Count must be more than 0"; + return false; + } + + perLevel = count / depth; + if (perLevel < 1) + { + message = "Count not high enough for specified for number of levels required"; + return false; + } + + return true; + } + + /// + /// Utility to create a tree hierarchy + /// + /// + /// + /// + /// + /// + /// A callback that returns a tuple of Content and another callback to produce a Container. + /// For media, a container will be another folder, for content the container will be the Content itself. + /// + /// + private IEnumerable CreateHierarchy( + T parent, int count, int depth, + Func container)> create) + where T: class, IContentBase + { + yield return parent.GetUdi(); + + // This will not calculate a balanced tree but it will ensure that there will be enough nodes deep enough to not fill up the tree. + var totalDescendants = count - 1; + var perLevel = Math.Ceiling(totalDescendants / (double)depth); + var perBranch = Math.Ceiling(perLevel / depth); + + var tracked = new Stack<(T parent, int childCount)>(); + + var currChildCount = 0; + + for (int i = 0; i < count; i++) + { + var created = create(parent); + var contentItem = created.content; + + yield return contentItem.GetUdi(); + + currChildCount++; + + if (currChildCount == perBranch) + { + // move back up... + + var prev = tracked.Pop(); + + // restore child count + currChildCount = prev.childCount; + // restore the parent + parent = prev.parent; + + } + else if (contentItem.Level < depth) + { + // track the current parent and it's current child count + tracked.Push((parent, currChildCount)); + + // not at max depth, create below + parent = created.container(); + + currChildCount = 0; + } + + } + } + + /// + /// Creates the media tree hiearachy + /// + /// + /// + /// + /// + /// + private IEnumerable CreateMediaTree(string company, Faker faker, int count, int depth) + { + var parent = Services.MediaService.CreateMediaWithIdentity(company, -1, Constants.Conventions.MediaTypes.Folder); + + return CreateHierarchy(parent, count, depth, currParent => + { + var imageUrl = faker.Image.PicsumUrl(); + + // we are appending a &ext=.jpg to the end of this for a reason. The result of this url will be something like: + // https://picsum.photos/640/480/?image=106 + // and due to the way that we detect images there must be an extension so we'll change it to + // https://picsum.photos/640/480/?image=106&ext=.jpg + // which will trick our app into parsing this and thinking it's an image ... which it is so that's good. + // if we don't do this we don't get thumbnails in the back office. + imageUrl += "&ext=.jpg"; + + var media = Services.MediaService.CreateMedia(faker.Commerce.ProductName(), currParent, Constants.Conventions.MediaTypes.Image); + media.SetValue(Constants.Conventions.Media.File, imageUrl); + Services.MediaService.Save(media); + return (media, () => + { + // create a folder to contain child media + var container = Services.MediaService.CreateMediaWithIdentity(faker.Commerce.Department(), currParent, Constants.Conventions.MediaTypes.Folder); + return container; + }); + }); + } + + /// + /// Creates the content tree hiearachy + /// + /// + /// + /// + /// + /// + /// + private IEnumerable CreateContentTree(string company, Faker faker, int count, int depth, List imageIds, out IContent root) + { + var random = new Random(company.GetHashCode()); + + var docType = GetOrCreateContentType(); + + var parent = Services.ContentService.Create(company, -1, docType.Alias); + parent.SetValue("review", faker.Rant.Review()); + parent.SetValue("desc", company); + parent.SetValue("media", imageIds[random.Next(0, imageIds.Count - 1)]); + Services.ContentService.Save(parent); + + root = parent; + + return CreateHierarchy(parent, count, depth, currParent => + { + var content = Services.ContentService.Create(faker.Commerce.ProductName(), currParent, docType.Alias); + content.SetValue("review", faker.Rant.Review()); + content.SetValue("desc", string.Join(", ", Enumerable.Range(0, 5).Select(x => faker.Commerce.ProductAdjective()))); + content.SetValue("media", imageIds[random.Next(0, imageIds.Count - 1)]); + + Services.ContentService.Save(content); + return (content, () => content); + }); + + } + + private IContentType GetOrCreateContentType() + { + var docType = Services.ContentTypeService.Get(TestDataContentTypeAlias); + if (docType != null) + return docType; + + docType = new ContentType(-1) + { + Alias = TestDataContentTypeAlias, + Name = "Umbraco Test Data Content", + Icon = "icon-science color-green" + }; + docType.AddPropertyGroup("Content"); + docType.AddPropertyType(new PropertyType(GetOrCreateRichText(), "review") + { + Name = "Review" + }); + docType.AddPropertyType(new PropertyType(GetOrCreateMediaPicker(), "media") + { + Name = "Media" + }); + docType.AddPropertyType(new PropertyType(GetOrCreateText(), "desc") + { + Name = "Description" + }); + Services.ContentTypeService.Save(docType); + docType.AllowedContentTypes = new[] { new ContentTypeSort(docType.Id, 0) }; + Services.ContentTypeService.Save(docType); + return docType; + } + + private IDataType GetOrCreateRichText() => GetOrCreateDataType(RichTextDataTypeName, Constants.PropertyEditors.Aliases.TinyMce); + + private IDataType GetOrCreateMediaPicker() => GetOrCreateDataType(MediaPickerDataTypeName, Constants.PropertyEditors.Aliases.MediaPicker); + + private IDataType GetOrCreateText() => GetOrCreateDataType(TextDataTypeName, Constants.PropertyEditors.Aliases.TextBox); + + private IDataType GetOrCreateDataType(string name, string editorAlias) + { + var dt = Services.DataTypeService.GetDataType(name); + if (dt != null) return dt; + + var editor = _propertyEditors.FirstOrDefault(x => x.Alias == editorAlias); + if (editor == null) + throw new InvalidOperationException($"No {editorAlias} editor found"); + + dt = new DataType(editor) + { + Name = name, + Configuration = editor.GetConfigurationEditor().DefaultConfigurationObject, + DatabaseType = ValueStorageType.Ntext + }; + + Services.DataTypeService.Save(dt); + return dt; + } + } +} diff --git a/src/Umbraco.TestData/readme.md b/src/Umbraco.TestData/readme.md new file mode 100644 index 0000000000..f943326303 --- /dev/null +++ b/src/Umbraco.TestData/readme.md @@ -0,0 +1,51 @@ +## Umbraco Test Data + +This project is a utility to be able to generate large amounts of content and media in an +Umbraco installation for testing. + +Currently this project is referenced in the Umbraco.Web.UI project but only when it's being built +in Debug mode (i.e. when testing within Visual Studio). + +## Usage + +You must use SQL Server for this, using SQLCE will die if you try to bulk create huge amounts of data. + +It has to be enabled by an appSetting: + +```xml + +``` + +Once this is enabled this endpoint can be executed: + +`/umbraco/surface/umbracotestdata/CreateTree?count=100&depth=5` + +The query string options are: + +* `count` = the number of content and media nodes to create +* `depth` = how deep the trees created will be +* `locale` (optional, default = "en") = the language that the data will be generated in + +This creates a content and associated media tree (hierarchy). Each content item created is associated +to a media item via a media picker and therefore a relation is created between the two. Each content and +media tree created have the same root node name so it's easy to know which content branch relates to +which media branch. + +All values are generated using the very handy `Bogus` package. + +## Schema + +This will install some schema items: + +* `umbTestDataContent` Document Type. __TIP__: If you want to delete all of the content data generated with this tool, just delete this content type +* `UmbracoTestDataContent.RTE` Data Type +* `UmbracoTestDataContent.MediaPicker` Data Type +* `UmbracoTestDataContent.Text` Data Type + +For media, the normal folder and image is used + +## Media + +This does not upload physical files, it just uses a randomized online image as the `umbracoFile` value. +This works when viewing the media item in the media section and the image will show up and with recent changes this will also work +when editing content to view the thumbnail for the picked media. diff --git a/src/Umbraco.Tests/Cache/AppCacheTests.cs b/src/Umbraco.Tests/Cache/AppCacheTests.cs index 3a86feb90a..f0ac9a2b0a 100644 --- a/src/Umbraco.Tests/Cache/AppCacheTests.cs +++ b/src/Umbraco.Tests/Cache/AppCacheTests.cs @@ -96,7 +96,7 @@ namespace Umbraco.Tests.Cache return ""; }); - Assert.AreEqual(counter, 1); + Assert.AreEqual(1, counter); } diff --git a/src/Umbraco.Tests/Cache/SnapDictionaryTests.cs b/src/Umbraco.Tests/Cache/SnapDictionaryTests.cs index b435af9e77..00ba721bdb 100644 --- a/src/Umbraco.Tests/Cache/SnapDictionaryTests.cs +++ b/src/Umbraco.Tests/Cache/SnapDictionaryTests.cs @@ -223,7 +223,7 @@ namespace Umbraco.Tests.Cache { var d = new SnapDictionary(); d.Test.CollectAuto = false; - + // gen 1 d.Set(1, "one"); Assert.AreEqual(1, d.Test.GetValues(1).Length); @@ -321,7 +321,7 @@ namespace Umbraco.Tests.Cache { var d = new SnapDictionary(); d.Test.CollectAuto = false; - + Assert.AreEqual(0, d.Test.GetValues(1).Length); // gen 1 @@ -416,7 +416,7 @@ namespace Umbraco.Tests.Cache { var d = new SnapDictionary(); d.Test.CollectAuto = false; - + // gen 1 d.Set(1, "one"); Assert.AreEqual(1, d.Test.GetValues(1).Length); @@ -578,7 +578,7 @@ namespace Umbraco.Tests.Cache { var d = new SnapDictionary(); d.Test.CollectAuto = false; - + d.Set(1, "one"); d.Set(2, "two"); @@ -689,7 +689,7 @@ namespace Umbraco.Tests.Cache { // gen 3 Assert.AreEqual(2, d.Test.GetValues(1).Length); - d.Set(1, "ein"); + d.SetLocked(1, "ein"); Assert.AreEqual(3, d.Test.GetValues(1).Length); Assert.AreEqual(3, d.Test.LiveGen); @@ -727,31 +727,25 @@ namespace Umbraco.Tests.Cache using (var w1 = d.GetScopedWriteLock(scopeProvider)) { Assert.AreEqual(1, t.LiveGen); - Assert.AreEqual(1, t.WLocked); + Assert.IsTrue(t.IsLocked); Assert.IsTrue(t.NextGen); - using (var w2 = d.GetScopedWriteLock(scopeProvider)) + Assert.Throws(() => { - Assert.AreEqual(1, t.LiveGen); - Assert.AreEqual(2, t.WLocked); - Assert.IsTrue(t.NextGen); - - Assert.AreNotSame(w1, w2); // get a new writer each time - - d.Set(1, "one"); - - Assert.AreEqual(0, d.CreateSnapshot().Gen); - } + using (var w2 = d.GetScopedWriteLock(scopeProvider)) + { + } + }); Assert.AreEqual(1, t.LiveGen); - Assert.AreEqual(1, t.WLocked); + Assert.IsTrue(t.IsLocked); Assert.IsTrue(t.NextGen); Assert.AreEqual(0, d.CreateSnapshot().Gen); } Assert.AreEqual(1, t.LiveGen); - Assert.AreEqual(0, t.WLocked); + Assert.IsFalse(t.IsLocked); Assert.IsTrue(t.NextGen); Assert.AreEqual(1, d.CreateSnapshot().Gen); @@ -772,11 +766,14 @@ namespace Umbraco.Tests.Cache using (var w1 = d.GetScopedWriteLock(scopeProvider)) { + // This one is interesting, although we don't allow recursive locks, since this is + // using the same ScopeContext/key, the lock acquisition is only done once + using (var w2 = d.GetScopedWriteLock(scopeProvider)) { Assert.AreSame(w1, w2); - d.Set(1, "one"); + d.SetLocked(1, "one"); } } } @@ -797,19 +794,16 @@ namespace Umbraco.Tests.Cache using (var w1 = d.GetScopedWriteLock(scopeProvider1)) { Assert.AreEqual(1, t.LiveGen); - Assert.AreEqual(1, t.WLocked); + Assert.IsTrue(t.IsLocked); Assert.IsTrue(t.NextGen); - using (var w2 = d.GetScopedWriteLock(scopeProvider2)) + Assert.Throws(() => { - Assert.AreEqual(1, t.LiveGen); - Assert.AreEqual(2, t.WLocked); - Assert.IsTrue(t.NextGen); + using (var w2 = d.GetScopedWriteLock(scopeProvider2)) + { + } + }); - Assert.AreNotSame(w1, w2); - - d.Set(1, "one"); - } } } @@ -848,13 +842,13 @@ namespace Umbraco.Tests.Cache Assert.IsFalse(d.Test.NextGen); Assert.AreEqual("uno", s2.Get(1)); - var scopeProvider = GetScopeProvider(); + var scopeProvider = GetScopeProvider(); using (d.GetScopedWriteLock(scopeProvider)) { // gen 3 Assert.AreEqual(2, d.Test.GetValues(1).Length); - d.Set(1, "ein"); + d.SetLocked(1, "ein"); Assert.AreEqual(3, d.Test.GetValues(1).Length); Assert.AreEqual(3, d.Test.LiveGen); @@ -881,6 +875,7 @@ namespace Umbraco.Tests.Cache { var d = new SnapDictionary(); d.Test.CollectAuto = false; + // gen 1 d.Set(1, "one"); @@ -894,12 +889,11 @@ namespace Umbraco.Tests.Cache Assert.AreEqual("uno", s2.Get(1)); var scopeProvider = GetScopeProvider(); - using (d.GetScopedWriteLock(scopeProvider)) { // creating a snapshot in a write-lock does NOT return the "current" content // it uses the previous snapshot, so new snapshot created only on release - d.Set(1, "ein"); + d.SetLocked(1, "ein"); var s3 = d.CreateSnapshot(); Assert.AreEqual(2, s3.Gen); Assert.AreEqual("uno", s3.Get(1)); @@ -934,12 +928,11 @@ namespace Umbraco.Tests.Cache var scopeContext = new ScopeContext(); var scopeProvider = GetScopeProvider(scopeContext); - using (d.GetScopedWriteLock(scopeProvider)) { // creating a snapshot in a write-lock does NOT return the "current" content // it uses the previous snapshot, so new snapshot created only on release - d.Set(1, "ein"); + d.SetLocked(1, "ein"); var s3 = d.CreateSnapshot(); Assert.AreEqual(2, s3.Gen); Assert.AreEqual("uno", s3.Get(1)); @@ -967,7 +960,7 @@ namespace Umbraco.Tests.Cache var d = new SnapDictionary(); var t = d.Test; t.CollectAuto = false; - + // gen 1 d.Set(1, "one"); var s1 = d.CreateSnapshot(); @@ -984,12 +977,11 @@ namespace Umbraco.Tests.Cache var scopeContext = new ScopeContext(); var scopeProvider = GetScopeProvider(scopeContext); - using (d.GetScopedWriteLock(scopeProvider)) { // creating a snapshot in a write-lock does NOT return the "current" content // it uses the previous snapshot, so new snapshot created only on release - d.Set(1, "ein"); + d.SetLocked(1, "ein"); var s3 = d.CreateSnapshot(); Assert.AreEqual(2, s3.Gen); Assert.AreEqual("uno", s3.Get(1)); @@ -997,7 +989,7 @@ namespace Umbraco.Tests.Cache // we made some changes, so a next gen is required Assert.AreEqual(3, t.LiveGen); Assert.IsTrue(t.NextGen); - Assert.AreEqual(1, t.WLocked); + Assert.IsTrue(t.IsLocked); // but live snapshot contains changes var ls = t.LiveSnapshot; @@ -1008,7 +1000,7 @@ namespace Umbraco.Tests.Cache // nothing is committed until scope exits Assert.AreEqual(3, t.LiveGen); Assert.IsTrue(t.NextGen); - Assert.AreEqual(1, t.WLocked); + Assert.IsTrue(t.IsLocked); // no changes until exit var s4 = d.CreateSnapshot(); @@ -1020,7 +1012,7 @@ namespace Umbraco.Tests.Cache // now things have changed Assert.AreEqual(2, t.LiveGen); Assert.IsFalse(t.NextGen); - Assert.AreEqual(0, t.WLocked); + Assert.IsFalse(t.IsLocked); // no changes since not completed var s5 = d.CreateSnapshot(); @@ -1097,9 +1089,10 @@ namespace Umbraco.Tests.Cache // writer is scope contextual and scoped // when disposed, nothing happens // when the context exists, the writer is released + using (d.GetScopedWriteLock(scopeProvider)) { - d.Set(1, "ein"); + d.SetLocked(1, "ein"); Assert.IsTrue(d.Test.NextGen); Assert.AreEqual(3, d.Test.LiveGen); Assert.IsNotNull(d.Test.GenObj); @@ -1107,7 +1100,7 @@ namespace Umbraco.Tests.Cache } // writer has not released - Assert.AreEqual(1, d.Test.WLocked); + Assert.IsTrue(d.Test.IsLocked); Assert.IsNotNull(d.Test.GenObj); Assert.AreEqual(2, d.Test.GenObj.Gen); @@ -1118,7 +1111,7 @@ namespace Umbraco.Tests.Cache // panic! var s2 = d.CreateSnapshot(); - Assert.AreEqual(1, d.Test.WLocked); + Assert.IsTrue(d.Test.IsLocked); Assert.IsNotNull(d.Test.GenObj); Assert.AreEqual(2, d.Test.GenObj.Gen); Assert.AreEqual(3, d.Test.LiveGen); @@ -1127,7 +1120,7 @@ namespace Umbraco.Tests.Cache // release writer scopeContext.ScopeExit(true); - Assert.AreEqual(0, d.Test.WLocked); + Assert.IsFalse(d.Test.IsLocked); Assert.IsNotNull(d.Test.GenObj); Assert.AreEqual(2, d.Test.GenObj.Gen); Assert.AreEqual(3, d.Test.LiveGen); @@ -1135,7 +1128,7 @@ namespace Umbraco.Tests.Cache var s3 = d.CreateSnapshot(); - Assert.AreEqual(0, d.Test.WLocked); + Assert.IsFalse(d.Test.IsLocked); Assert.IsNotNull(d.Test.GenObj); Assert.AreEqual(3, d.Test.GenObj.Gen); Assert.AreEqual(3, d.Test.LiveGen); @@ -1150,4 +1143,45 @@ namespace Umbraco.Tests.Cache return scopeProvider; } } + + /// + /// Used for tests so that we don't have to wrap every Set/Clear call in locks + /// + public static class SnapDictionaryExtensions + { + internal static void Set(this SnapDictionary d, TKey key, TValue value) + where TValue : class + { + using (d.GetScopedWriteLock(GetScopeProvider())) + { + d.SetLocked(key, value); + } + } + + internal static void Clear(this SnapDictionary d) + where TValue : class + { + using (d.GetScopedWriteLock(GetScopeProvider())) + { + d.ClearLocked(); + } + } + + internal static void Clear(this SnapDictionary d, TKey key) + where TValue : class + { + using (d.GetScopedWriteLock(GetScopeProvider())) + { + d.ClearLocked(key); + } + } + + private static IScopeProvider GetScopeProvider() + { + var scopeProvider = Mock.Of(); + Mock.Get(scopeProvider) + .Setup(x => x.Context).Returns(() => null); + return scopeProvider; + } + } } diff --git a/src/Umbraco.Tests/Composing/CollectionBuildersTests.cs b/src/Umbraco.Tests/Composing/CollectionBuildersTests.cs index ec757e09f0..1d8390e07e 100644 --- a/src/Umbraco.Tests/Composing/CollectionBuildersTests.cs +++ b/src/Umbraco.Tests/Composing/CollectionBuildersTests.cs @@ -354,7 +354,7 @@ namespace Umbraco.Tests.Composing var col2 = factory.GetInstance(); AssertCollection(col2, typeof(Resolved1), typeof(Resolved2)); - AssertSameCollection(col1, col2); + AssertSameCollection(factory, col1, col2); } } @@ -413,11 +413,11 @@ namespace Umbraco.Tests.Composing { col1A = factory.GetInstance(); col1B = factory.GetInstance(); - } - AssertCollection(col1A, typeof(Resolved1), typeof(Resolved2)); - AssertCollection(col1B, typeof(Resolved1), typeof(Resolved2)); - AssertSameCollection(col1A, col1B); + AssertCollection(col1A, typeof(Resolved1), typeof(Resolved2)); + AssertCollection(col1B, typeof(Resolved1), typeof(Resolved2)); + AssertSameCollection(factory, col1A, col1B); + } TestCollection col2; @@ -452,7 +452,7 @@ namespace Umbraco.Tests.Composing Assert.IsInstanceOf(expected[i], colA[i]); } - private static void AssertSameCollection(IEnumerable col1, IEnumerable col2) + private static void AssertSameCollection(IFactory factory, IEnumerable col1, IEnumerable col2) { Assert.AreSame(col1, col2); @@ -460,8 +460,19 @@ namespace Umbraco.Tests.Composing var col2A = col2.ToArray(); Assert.AreEqual(col1A.Length, col2A.Length); + + // Ensure each item in each collection is the same but also + // resolve each item from the factory to ensure it's also the same since + // it should have the same lifespan. for (var i = 0; i < col1A.Length; i++) + { Assert.AreSame(col1A[i], col2A[i]); + + var itemA = factory.GetInstance(col1A[i].GetType()); + var itemB = factory.GetInstance(col2A[i].GetType()); + + Assert.AreSame(itemA, itemB); + } } private static void AssertNotSameCollection(IEnumerable col1, IEnumerable col2) @@ -472,8 +483,11 @@ namespace Umbraco.Tests.Composing var col2A = col2.ToArray(); Assert.AreEqual(col1A.Length, col2A.Length); + for (var i = 0; i < col1A.Length; i++) + { Assert.AreNotSame(col1A[i], col2A[i]); + } } #endregion diff --git a/src/Umbraco.Tests/Configurations/UmbracoSettings/ContentElementTests.cs b/src/Umbraco.Tests/Configurations/UmbracoSettings/ContentElementTests.cs index 0245159c6e..33df3caaad 100644 --- a/src/Umbraco.Tests/Configurations/UmbracoSettings/ContentElementTests.cs +++ b/src/Umbraco.Tests/Configurations/UmbracoSettings/ContentElementTests.cs @@ -16,7 +16,7 @@ namespace Umbraco.Tests.Configurations.UmbracoSettings [Test] public void EmailAddress() { - Assert.AreEqual(SettingsSection.Content.NotificationEmailAddress, "robot@umbraco.dk"); + Assert.AreEqual("robot@umbraco.dk", SettingsSection.Content.NotificationEmailAddress); } [Test] public virtual void DisableHtmlEmail() @@ -27,17 +27,17 @@ namespace Umbraco.Tests.Configurations.UmbracoSettings [Test] public virtual void Can_Set_Multiple() { - Assert.AreEqual(SettingsSection.Content.Error404Collection.Count(), 3); - Assert.AreEqual(SettingsSection.Content.Error404Collection.ElementAt(0).Culture, "default"); - Assert.AreEqual(SettingsSection.Content.Error404Collection.ElementAt(0).ContentId, 1047); + Assert.AreEqual(3, SettingsSection.Content.Error404Collection.Count()); + Assert.AreEqual("default", SettingsSection.Content.Error404Collection.ElementAt(0).Culture); + Assert.AreEqual(1047, SettingsSection.Content.Error404Collection.ElementAt(0).ContentId); Assert.IsTrue(SettingsSection.Content.Error404Collection.ElementAt(0).HasContentId); Assert.IsFalse(SettingsSection.Content.Error404Collection.ElementAt(0).HasContentKey); - Assert.AreEqual(SettingsSection.Content.Error404Collection.ElementAt(1).Culture, "en-US"); - Assert.AreEqual(SettingsSection.Content.Error404Collection.ElementAt(1).ContentXPath, "$site/error [@name = 'error']"); + Assert.AreEqual("en-US", SettingsSection.Content.Error404Collection.ElementAt(1).Culture); + Assert.AreEqual("$site/error [@name = 'error']", SettingsSection.Content.Error404Collection.ElementAt(1).ContentXPath); Assert.IsFalse(SettingsSection.Content.Error404Collection.ElementAt(1).HasContentId); Assert.IsFalse(SettingsSection.Content.Error404Collection.ElementAt(1).HasContentKey); - Assert.AreEqual(SettingsSection.Content.Error404Collection.ElementAt(2).Culture, "en-UK"); - Assert.AreEqual(SettingsSection.Content.Error404Collection.ElementAt(2).ContentKey, new Guid("8560867F-B88F-4C74-A9A4-679D8E5B3BFC")); + Assert.AreEqual("en-UK", SettingsSection.Content.Error404Collection.ElementAt(2).Culture); + Assert.AreEqual(new Guid("8560867F-B88F-4C74-A9A4-679D8E5B3BFC"), SettingsSection.Content.Error404Collection.ElementAt(2).ContentKey); Assert.IsTrue(SettingsSection.Content.Error404Collection.ElementAt(2).HasContentKey); Assert.IsFalse(SettingsSection.Content.Error404Collection.ElementAt(2).HasContentId); } @@ -47,23 +47,23 @@ namespace Umbraco.Tests.Configurations.UmbracoSettings { Assert.IsTrue(SettingsSection.Content.ImageFileTypes.All(x => "jpeg,jpg,gif,bmp,png,tiff,tif".Split(',').Contains(x))); } - + [Test] public virtual void ImageAutoFillProperties() { - Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.Count(), 2); - Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).Alias, "umbracoFile"); - Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).WidthFieldAlias, "umbracoWidth"); - Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).HeightFieldAlias, "umbracoHeight"); - Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).LengthFieldAlias, "umbracoBytes"); - Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).ExtensionFieldAlias, "umbracoExtension"); - Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).Alias, "umbracoFile2"); - Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).WidthFieldAlias, "umbracoWidth2"); - Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).HeightFieldAlias, "umbracoHeight2"); - Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).LengthFieldAlias, "umbracoBytes2"); - Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).ExtensionFieldAlias, "umbracoExtension2"); + Assert.AreEqual(2, SettingsSection.Content.ImageAutoFillProperties.Count()); + Assert.AreEqual("umbracoFile", SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).Alias); + Assert.AreEqual("umbracoWidth", SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).WidthFieldAlias); + Assert.AreEqual("umbracoHeight", SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).HeightFieldAlias); + Assert.AreEqual("umbracoBytes", SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).LengthFieldAlias); + Assert.AreEqual("umbracoExtension", SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).ExtensionFieldAlias); + Assert.AreEqual("umbracoFile2", SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).Alias); + Assert.AreEqual("umbracoWidth2", SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).WidthFieldAlias); + Assert.AreEqual("umbracoHeight2", SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).HeightFieldAlias); + Assert.AreEqual("umbracoBytes2", SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).LengthFieldAlias); + Assert.AreEqual("umbracoExtension2", SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).ExtensionFieldAlias); } - + [Test] public void PreviewBadge() { @@ -116,7 +116,7 @@ namespace Umbraco.Tests.Configurations.UmbracoSettings Debug.WriteLine("AllowedContainsExtension: {0}", allowedContainsExtension); Debug.WriteLine("DisallowedContainsExtension: {0}", disallowedContainsExtension); - Assert.AreEqual(SettingsSection.Content.IsFileAllowedForUpload(extension), expected); + Assert.AreEqual(expected, SettingsSection.Content.IsFileAllowedForUpload(extension)); } } } diff --git a/src/Umbraco.Tests/CoreThings/EnumExtensionsTests.cs b/src/Umbraco.Tests/CoreThings/EnumExtensionsTests.cs index 4a0c1d0f41..faa15b0077 100644 --- a/src/Umbraco.Tests/CoreThings/EnumExtensionsTests.cs +++ b/src/Umbraco.Tests/CoreThings/EnumExtensionsTests.cs @@ -1,6 +1,7 @@ -using NUnit.Framework; -using Umbraco.Web.Trees; +using System; +using NUnit.Framework; using Umbraco.Core; +using Umbraco.Web.Trees; namespace Umbraco.Tests.CoreThings { @@ -22,6 +23,7 @@ namespace Umbraco.Tests.CoreThings Assert.IsFalse(value.HasFlag(test)); } + [Obsolete] [TestCase(TreeUse.Dialog, TreeUse.Dialog, true)] [TestCase(TreeUse.Dialog, TreeUse.Main, false)] [TestCase(TreeUse.Dialog | TreeUse.Main, TreeUse.Dialog, true)] @@ -51,47 +53,5 @@ namespace Umbraco.Tests.CoreThings else Assert.IsFalse(value.HasFlagAny(test)); } - - [TestCase(TreeUse.None, TreeUse.None, TreeUse.None)] - [TestCase(TreeUse.None, TreeUse.Main, TreeUse.Main)] - [TestCase(TreeUse.None, TreeUse.Dialog, TreeUse.Dialog)] - [TestCase(TreeUse.None, TreeUse.Main | TreeUse.Dialog, TreeUse.Main | TreeUse.Dialog)] - [TestCase(TreeUse.Main, TreeUse.None, TreeUse.Main)] - [TestCase(TreeUse.Main, TreeUse.Main, TreeUse.Main)] - [TestCase(TreeUse.Main, TreeUse.Dialog, TreeUse.Main | TreeUse.Dialog)] - [TestCase(TreeUse.Main, TreeUse.Main | TreeUse.Dialog, TreeUse.Main | TreeUse.Dialog)] - [TestCase(TreeUse.Dialog, TreeUse.None, TreeUse.Dialog)] - [TestCase(TreeUse.Dialog, TreeUse.Main, TreeUse.Main | TreeUse.Dialog)] - [TestCase(TreeUse.Dialog, TreeUse.Dialog, TreeUse.Dialog)] - [TestCase(TreeUse.Dialog, TreeUse.Main | TreeUse.Dialog, TreeUse.Main | TreeUse.Dialog)] - [TestCase(TreeUse.Main | TreeUse.Dialog, TreeUse.None, TreeUse.Main | TreeUse.Dialog)] - [TestCase(TreeUse.Main | TreeUse.Dialog, TreeUse.Main, TreeUse.Main | TreeUse.Dialog)] - [TestCase(TreeUse.Main | TreeUse.Dialog, TreeUse.Dialog, TreeUse.Main | TreeUse.Dialog)] - [TestCase(TreeUse.Main | TreeUse.Dialog, TreeUse.Main | TreeUse.Dialog, TreeUse.Main | TreeUse.Dialog)] - public void SetFlagTests(TreeUse value, TreeUse flag, TreeUse expected) - { - Assert.AreEqual(expected, value.SetFlag(flag)); - } - - [TestCase(TreeUse.None, TreeUse.None, TreeUse.None)] - [TestCase(TreeUse.None, TreeUse.Main, TreeUse.None)] - [TestCase(TreeUse.None, TreeUse.Dialog, TreeUse.None)] - [TestCase(TreeUse.None, TreeUse.Main | TreeUse.Dialog, TreeUse.None)] - [TestCase(TreeUse.Main, TreeUse.None, TreeUse.Main)] - [TestCase(TreeUse.Main, TreeUse.Main, TreeUse.None)] - [TestCase(TreeUse.Main, TreeUse.Dialog, TreeUse.Main)] - [TestCase(TreeUse.Main, TreeUse.Main | TreeUse.Dialog, TreeUse.None)] - [TestCase(TreeUse.Dialog, TreeUse.None, TreeUse.Dialog)] - [TestCase(TreeUse.Dialog, TreeUse.Main, TreeUse.Dialog)] - [TestCase(TreeUse.Dialog, TreeUse.Dialog, TreeUse.None)] - [TestCase(TreeUse.Dialog, TreeUse.Main | TreeUse.Dialog, TreeUse.None)] - [TestCase(TreeUse.Main | TreeUse.Dialog, TreeUse.None, TreeUse.Main | TreeUse.Dialog)] - [TestCase(TreeUse.Main | TreeUse.Dialog, TreeUse.Main, TreeUse.Dialog)] - [TestCase(TreeUse.Main | TreeUse.Dialog, TreeUse.Dialog, TreeUse.Main)] - [TestCase(TreeUse.Main | TreeUse.Dialog, TreeUse.Main | TreeUse.Dialog, TreeUse.None)] - public void UnsetFlagTests(TreeUse value, TreeUse flag, TreeUse expected) - { - Assert.AreEqual(expected, value.UnsetFlag(flag)); - } } } diff --git a/src/Umbraco.Tests/CoreThings/XmlExtensionsTests.cs b/src/Umbraco.Tests/CoreThings/XmlExtensionsTests.cs index 42d8f92a5d..62e385beff 100644 --- a/src/Umbraco.Tests/CoreThings/XmlExtensionsTests.cs +++ b/src/Umbraco.Tests/CoreThings/XmlExtensionsTests.cs @@ -16,7 +16,7 @@ namespace Umbraco.Tests.CoreThings var xmlNode = cdata.GetXmlNode(xdoc); - Assert.AreEqual(xmlNode.InnerText, "hello world"); + Assert.AreEqual("hello world", xmlNode.InnerText); } [Test] @@ -27,7 +27,7 @@ namespace Umbraco.Tests.CoreThings var xmlNode = cdata.GetXmlNode(xdoc); - Assert.AreEqual(xmlNode.InnerText, "hello world"); + Assert.AreEqual("hello world", xmlNode.InnerText); } [Test] @@ -41,7 +41,7 @@ namespace Umbraco.Tests.CoreThings var xmlNode = cdata.GetXmlNode(xdoc); - Assert.AreEqual(xmlNode.InnerText, "hello world"); + Assert.AreEqual("hello world", xmlNode.InnerText); Assert.AreEqual(xml, xdoc.OuterXml); } } diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedSnapshotService.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedSnapshotService.cs index ec6b854a46..97fe9057bb 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedSnapshotService.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedSnapshotService.cs @@ -9,6 +9,7 @@ using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Persistence.Repositories; +using Umbraco.Core.Runtime; using Umbraco.Core.Scoping; using Umbraco.Core.Services; using Umbraco.Web; diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlStore.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlStore.cs index c452c4792a..803b86aec5 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlStore.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlStore.cs @@ -14,6 +14,7 @@ using Umbraco.Core.Models; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.Repositories.Implement; +using Umbraco.Core.Runtime; using Umbraco.Core.Scoping; using Umbraco.Core.Services; using Umbraco.Core.Services.Changes; diff --git a/src/Umbraco.Tests/Logging/LogviewerTests.cs b/src/Umbraco.Tests/Logging/LogviewerTests.cs index ed9deec177..08ba070b70 100644 --- a/src/Umbraco.Tests/Logging/LogviewerTests.cs +++ b/src/Umbraco.Tests/Logging/LogviewerTests.cs @@ -163,7 +163,7 @@ namespace Umbraco.Tests.Logging //Query @Level='Warning' BUT we pass in array of LogLevels for Debug & Info (Expect to get 0 results) string[] logLevelMismatch = { "Debug", "Information" }; - var filterLevelQuery = _logViewer.GetLogs(_logTimePeriod, pageNumber: 1, filterExpression: "@Level='Warning'", logLevels: logLevelMismatch); ; + var filterLevelQuery = _logViewer.GetLogs(_logTimePeriod, pageNumber: 1, filterExpression: "@Level='Warning'", logLevels: logLevelMismatch); Assert.AreEqual(0, filterLevelQuery.TotalItems); } diff --git a/src/Umbraco.Tests/Models/ContentTests.cs b/src/Umbraco.Tests/Models/ContentTests.cs index 3116087669..5bb30e1606 100644 --- a/src/Umbraco.Tests/Models/ContentTests.cs +++ b/src/Umbraco.Tests/Models/ContentTests.cs @@ -24,7 +24,6 @@ using Umbraco.Web.PropertyEditors; namespace Umbraco.Tests.Models { - [TestFixture] public class ContentTests : UmbracoTestBase { @@ -42,7 +41,7 @@ namespace Umbraco.Tests.Models // all this is required so we can validate properties... var editor = new TextboxPropertyEditor(Mock.Of()) { Alias = "test" }; - Composition.Register(_ => new DataEditorCollection(new [] { editor })); + Composition.Register(_ => new DataEditorCollection(new[] { editor })); Composition.Register(); var dataType = Mock.Of(); Mock.Get(dataType).Setup(x => x.Configuration).Returns(() => new object()); @@ -55,7 +54,7 @@ namespace Umbraco.Tests.Models var mediaTypeService = Mock.Of(); var memberTypeService = Mock.Of(); Composition.Register(_ => ServiceContext.CreatePartial(dataTypeService: dataTypeService, contentTypeBaseServiceProvider: new ContentTypeBaseServiceProvider(_contentTypeService, mediaTypeService, memberTypeService))); - + } [Test] @@ -458,7 +457,7 @@ namespace Umbraco.Tests.Models Assert.IsTrue(prop.WasPropertyDirty("Id")); } Assert.IsTrue(content.WasPropertyDirty("CultureInfos")); - foreach(var culture in content.CultureInfos) + foreach (var culture in content.CultureInfos) { Assert.IsTrue(culture.WasDirty()); Assert.IsTrue(culture.WasPropertyDirty("Name")); @@ -539,7 +538,7 @@ namespace Umbraco.Tests.Models var content = MockedContent.CreateTextpageContent(contentType, "Textpage", -1); // Act - content.PropertyValues(new { title = "This is the new title"}); + content.PropertyValues(new { title = "This is the new title" }); // Assert Assert.That(content.Properties.Any(), Is.True); @@ -603,13 +602,13 @@ namespace Umbraco.Tests.Models // Act contentType.PropertyGroups["Content"].PropertyTypes.Add(new PropertyType("test", ValueStorageType.Ntext, "subtitle") - { - Name = "Subtitle", - Description = "Optional subtitle", - Mandatory = false, - SortOrder = 3, - DataTypeId = -88 - }); + { + Name = "Subtitle", + Description = "Optional subtitle", + Mandatory = false, + SortOrder = 3, + DataTypeId = -88 + }); // Assert Assert.That(contentType.PropertyGroups["Content"].PropertyTypes.Count, Is.EqualTo(3)); @@ -626,9 +625,13 @@ namespace Umbraco.Tests.Models // Act var propertyType = new PropertyType("test", ValueStorageType.Ntext, "subtitle") - { - Name = "Subtitle", Description = "Optional subtitle", Mandatory = false, SortOrder = 3, DataTypeId = -88 - }; + { + Name = "Subtitle", + Description = "Optional subtitle", + Mandatory = false, + SortOrder = 3, + DataTypeId = -88 + }; contentType.PropertyGroups["Content"].PropertyTypes.Add(propertyType); var newProperty = new Property(propertyType); newProperty.SetValue("This is a subtitle Test"); @@ -650,14 +653,14 @@ namespace Umbraco.Tests.Models // Act var propertyType = new PropertyType("test", ValueStorageType.Ntext, "subtitle") - { - Name = "Subtitle", - Description = "Optional subtitle", - Mandatory = false, - SortOrder = 3, - DataTypeId = -88 - }; - var propertyGroup = new PropertyGroup(true) { Name = "Test Group", SortOrder = 3}; + { + Name = "Subtitle", + Description = "Optional subtitle", + Mandatory = false, + SortOrder = 3, + DataTypeId = -88 + }; + var propertyGroup = new PropertyGroup(true) { Name = "Test Group", SortOrder = 3 }; propertyGroup.PropertyTypes.Add(propertyType); contentType.PropertyGroups.Add(propertyGroup); var newProperty = new Property(propertyType); @@ -681,9 +684,13 @@ namespace Umbraco.Tests.Models // Act - note that the PropertyType's properties like SortOrder is not updated through the Content object var propertyType = new PropertyType("test", ValueStorageType.Ntext, "title") - { - Name = "Title", Description = "Title description added", Mandatory = false, SortOrder = 10, DataTypeId = -88 - }; + { + Name = "Title", + Description = "Title description added", + Mandatory = false, + SortOrder = 10, + DataTypeId = -88 + }; content.Properties.Add(new Property(propertyType)); // Assert @@ -911,13 +918,13 @@ namespace Umbraco.Tests.Models // Act var propertyType = new PropertyType("test", ValueStorageType.Ntext, "subtitle") - { - Name = "Subtitle", - Description = "Optional subtitle", - Mandatory = false, - SortOrder = 3, - DataTypeId = -88 - }; + { + Name = "Subtitle", + Description = "Optional subtitle", + Mandatory = false, + SortOrder = 3, + DataTypeId = -88 + }; contentType.PropertyGroups["Content"].PropertyTypes.Add(propertyType); // Assert diff --git a/src/Umbraco.Tests/Models/ImageProcessorImageUrlGeneratorTest.cs b/src/Umbraco.Tests/Models/ImageProcessorImageUrlGeneratorTest.cs new file mode 100644 index 0000000000..30ead90de9 --- /dev/null +++ b/src/Umbraco.Tests/Models/ImageProcessorImageUrlGeneratorTest.cs @@ -0,0 +1,232 @@ +using System; +using System.Globalization; +using Moq; +using Newtonsoft.Json; +using NUnit.Framework; +using Newtonsoft.Json.Linq; +using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.PropertyEditors.ValueConverters; +using Umbraco.Core.Services; +using Umbraco.Tests.Components; +using Umbraco.Tests.TestHelpers; +using Umbraco.Web.Models; +using Umbraco.Web; +using Umbraco.Web.PropertyEditors; +using System.Text; + +namespace Umbraco.Tests.Models +{ + [TestFixture] + public class ImageProcessorImageUrlGeneratorTest + { + private const string MediaPath = "/media/1005/img_0671.jpg"; + private static readonly ImageUrlGenerationOptions.CropCoordinates Crop = new ImageUrlGenerationOptions.CropCoordinates(0.58729977382575338m, 0.055768992440203169m, 0m, 0.32457553600198386m); + private static readonly ImageUrlGenerationOptions.FocalPointPosition Focus1 = new ImageUrlGenerationOptions.FocalPointPosition(0.80827067669172936m, 0.96m); + private static readonly ImageUrlGenerationOptions.FocalPointPosition Focus2 = new ImageUrlGenerationOptions.FocalPointPosition(0.41m, 0.4275m); + private static readonly ImageProcessorImageUrlGenerator Generator = new ImageProcessorImageUrlGenerator(); + + [Test] + public void GetCropUrl_CropAliasTest() + { + var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { Crop = Crop, Width = 100, Height = 100 }); + Assert.AreEqual(MediaPath + "?crop=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&cropmode=percentage&width=100&height=100", urlString); + } + + [Test] + public void GetCropUrl_WidthHeightTest() + { + var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { FocalPoint = Focus1, Width = 200, Height = 300 }); + Assert.AreEqual(MediaPath + "?center=0.80827067669172936,0.96&mode=crop&width=200&height=300", urlString); + } + + [Test] + public void GetCropUrl_FocalPointTest() + { + var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { FocalPoint = Focus1, Width = 100, Height = 100 }); + Assert.AreEqual(MediaPath + "?center=0.80827067669172936,0.96&mode=crop&width=100&height=100", urlString); + } + + [Test] + public void GetCropUrlFurtherOptionsTest() + { + var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { FocalPoint = Focus1, Width = 200, Height = 300, FurtherOptions = "&filter=comic&roundedcorners=radius-26|bgcolor-fff" }); + Assert.AreEqual(MediaPath + "?center=0.80827067669172936,0.96&mode=crop&width=200&height=300&filter=comic&roundedcorners=radius-26|bgcolor-fff", urlString); + } + + /// + /// Test that if a crop alias has been specified that doesn't exist the method returns null + /// + [Test] + public void GetCropUrlNullTest() + { + var urlString = Generator.GetImageUrl(null); + Assert.AreEqual(null, urlString); + } + + /// + /// Test that if a crop alias has been specified that doesn't exist the method returns null + /// + [Test] + public void GetCropUrlEmptyTest() + { + var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions(null)); + Assert.AreEqual("?mode=crop", urlString); + } + + /// + /// Test the GetCropUrl method on the ImageCropDataSet Model + /// + [Test] + public void GetBaseCropUrlFromModelTest() + { + var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions(null) { Crop = Crop, Width = 100, Height = 100 }); + Assert.AreEqual("?crop=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&cropmode=percentage&width=100&height=100", urlString); + } + + /// + /// Test the height ratio mode with predefined crop dimensions + /// + [Test] + public void GetCropUrl_CropAliasHeightRatioModeTest() + { + var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { Crop = Crop, Width = 100, HeightRatio = 1 }); + Assert.AreEqual(MediaPath + "?crop=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&cropmode=percentage&heightratio=1&width=100", urlString); + } + + /// + /// Test the height ratio mode with manual width/height dimensions + /// + [Test] + public void GetCropUrl_WidthHeightRatioModeTest() + { + var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { FocalPoint = Focus1, Width = 300, HeightRatio = 0.5m }); + Assert.AreEqual(MediaPath + "?center=0.80827067669172936,0.96&mode=crop&heightratio=0.5&width=300", urlString); + } + + /// + /// Test the height ratio mode with width/height dimensions + /// + [Test] + public void GetCropUrl_HeightWidthRatioModeTest() + { + var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { FocalPoint = Focus1, Height = 150, WidthRatio = 2 }); + Assert.AreEqual(MediaPath + "?center=0.80827067669172936,0.96&mode=crop&widthratio=2&height=150", urlString); + } + + /// + /// Test that if Crop mode is specified as anything other than Crop the image doesn't use the crop + /// + [Test] + public void GetCropUrl_SpecifiedCropModeTest() + { + var urlStringMin = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { ImageCropMode = "Min", Width = 300, Height = 150 }); + var urlStringBoxPad = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { ImageCropMode = "BoxPad", Width = 300, Height = 150 }); + var urlStringPad = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { ImageCropMode = "Pad", Width = 300, Height = 150 }); + var urlStringMax = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { ImageCropMode = "Max", Width = 300, Height = 150 }); + var urlStringStretch = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { ImageCropMode = "Stretch", Width = 300, Height = 150 }); + + Assert.AreEqual(MediaPath + "?mode=min&width=300&height=150", urlStringMin); + Assert.AreEqual(MediaPath + "?mode=boxpad&width=300&height=150", urlStringBoxPad); + Assert.AreEqual(MediaPath + "?mode=pad&width=300&height=150", urlStringPad); + Assert.AreEqual(MediaPath + "?mode=max&width=300&height=150", urlStringMax); + Assert.AreEqual(MediaPath + "?mode=stretch&width=300&height=150", urlStringStretch); + } + + /// + /// Test for upload property type + /// + [Test] + public void GetCropUrl_UploadTypeTest() + { + var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { ImageCropMode = "Crop", ImageCropAnchor = "Center", Width = 100, Height = 270 }); + Assert.AreEqual(MediaPath + "?mode=crop&anchor=center&width=100&height=270", urlString); + } + + /// + /// Test for preferFocalPoint when focal point is centered + /// + [Test] + public void GetCropUrl_PreferFocalPointCenter() + { + var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { DefaultCrop = true, Width = 300, Height = 150 }); + Assert.AreEqual(MediaPath + "?anchor=center&mode=crop&width=300&height=150", urlString); + } + + /// + /// Test to check if height ratio is returned for a predefined crop without coordinates and focal point in centre when a width parameter is passed + /// + [Test] + public void GetCropUrl_PreDefinedCropNoCoordinatesWithWidth() + { + var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { DefaultCrop = true, Width = 200, HeightRatio = 0.5962962962962962962962962963m }); + Assert.AreEqual(MediaPath + "?anchor=center&mode=crop&heightratio=0.5962962962962962962962962963&width=200", urlString); + } + + /// + /// Test to check if height ratio is returned for a predefined crop without coordinates and focal point is custom when a width parameter is passed + /// + [Test] + public void GetCropUrl_PreDefinedCropNoCoordinatesWithWidthAndFocalPoint() + { + var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { FocalPoint = Focus2, Width = 200, HeightRatio = 0.5962962962962962962962962963m }); + Assert.AreEqual(MediaPath + "?center=0.41,0.4275&mode=crop&heightratio=0.5962962962962962962962962963&width=200", urlString); + } + + /// + /// Test to check if crop ratio is ignored if useCropDimensions is true + /// + [Test] + public void GetCropUrl_PreDefinedCropNoCoordinatesWithWidthAndFocalPointIgnore() + { + var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { FocalPoint = Focus2, Width = 270, Height = 161 }); + Assert.AreEqual(MediaPath + "?center=0.41,0.4275&mode=crop&width=270&height=161", urlString); + } + + /// + /// Test to check if width ratio is returned for a predefined crop without coordinates and focal point in centre when a height parameter is passed + /// + [Test] + public void GetCropUrl_PreDefinedCropNoCoordinatesWithHeight() + { + var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { DefaultCrop = true, Height = 200, WidthRatio = 1.6770186335403726708074534161m }); + Assert.AreEqual(MediaPath + "?anchor=center&mode=crop&widthratio=1.6770186335403726708074534161&height=200", urlString); + } + + /// + /// Test to check result when only a width parameter is passed, effectivly a resize only + /// + [Test] + public void GetCropUrl_WidthOnlyParameter() + { + var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { DefaultCrop = true, Width = 200 }); + Assert.AreEqual(MediaPath + "?anchor=center&mode=crop&width=200", urlString); + } + + /// + /// Test to check result when only a height parameter is passed, effectivly a resize only + /// + [Test] + public void GetCropUrl_HeightOnlyParameter() + { + var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { DefaultCrop = true, Height = 200 }); + Assert.AreEqual(MediaPath + "?anchor=center&mode=crop&height=200", urlString); + } + + /// + /// Test to check result when using a background color with padding + /// + [Test] + public void GetCropUrl_BackgroundColorParameter() + { + var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { ImageCropMode = "Pad", Width = 400, Height = 400, FurtherOptions = "&bgcolor=fff" }); + Assert.AreEqual(MediaPath + "?mode=pad&width=400&height=400&bgcolor=fff", urlString); + } + } +} diff --git a/src/Umbraco.Tests/Models/PropertyTests.cs b/src/Umbraco.Tests/Models/PropertyTests.cs new file mode 100644 index 0000000000..a95afcdd5c --- /dev/null +++ b/src/Umbraco.Tests/Models/PropertyTests.cs @@ -0,0 +1,63 @@ +using System; +using System.Linq; +using NUnit.Framework; +using Umbraco.Core.Models; +using Umbraco.Tests.Testing; + +namespace Umbraco.Tests.Models +{ + [TestFixture] + public class PropertyTests : UmbracoTestBase + { + [Test] + public void Can_Deep_Clone() + { + // needs to be within collection to support publishing + var ptCollection = new PropertyTypeCollection(true, new[] {new PropertyType("TestPropertyEditor", ValueStorageType.Nvarchar, "test") + { + Id = 3, + CreateDate = DateTime.Now, + DataTypeId = 5, + PropertyEditorAlias = "propTest", + Description = "testing", + Key = Guid.NewGuid(), + Mandatory = true, + Name = "Test", + PropertyGroupId = new Lazy(() => 11), + SortOrder = 9, + UpdateDate = DateTime.Now, + ValidationRegExp = "xxxx", + ValueStorageType = ValueStorageType.Nvarchar + }}); + + var property = new Property(123, ptCollection[0]) + { + CreateDate = DateTime.Now, + Id = 4, + Key = Guid.NewGuid(), + UpdateDate = DateTime.Now + }; + + property.SetValue("hello"); + property.PublishValues(); + + var clone = (Property)property.DeepClone(); + + Assert.AreNotSame(clone, property); + Assert.AreNotSame(clone.Values, property.Values); + Assert.AreNotSame(property.PropertyType, clone.PropertyType); + for (int i = 0; i < property.Values.Count; i++) + { + Assert.AreNotSame(property.Values.ElementAt(i), clone.Values.ElementAt(i)); + } + + + //This double verifies by reflection + var allProps = clone.GetType().GetProperties(); + foreach (var propertyInfo in allProps) + { + Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(property, null)); + } + } + } +} diff --git a/src/Umbraco.Tests/Models/PropertyTypeTests.cs b/src/Umbraco.Tests/Models/PropertyTypeTests.cs index 568d12264d..f4c563971b 100644 --- a/src/Umbraco.Tests/Models/PropertyTypeTests.cs +++ b/src/Umbraco.Tests/Models/PropertyTypeTests.cs @@ -1,5 +1,7 @@ using System; using System.Diagnostics; +using System.Linq; +using System.Reflection; using NUnit.Framework; using Umbraco.Core.Models; using Umbraco.Core.Serialization; @@ -49,9 +51,9 @@ namespace Umbraco.Tests.Models Assert.AreEqual(clone.ValidationRegExp, pt.ValidationRegExp); Assert.AreEqual(clone.ValueStorageType, pt.ValueStorageType); - //This double verifies by reflection + //This double verifies by reflection (don't test properties marked with [DoNotClone] var allProps = clone.GetType().GetProperties(); - foreach (var propertyInfo in allProps) + foreach (var propertyInfo in allProps.Where(p => p.GetCustomAttribute(false) == null)) { Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(pt, null)); } diff --git a/src/Umbraco.Tests/Models/RelationTests.cs b/src/Umbraco.Tests/Models/RelationTests.cs index c62dcdc6eb..91560abbb3 100644 --- a/src/Umbraco.Tests/Models/RelationTests.cs +++ b/src/Umbraco.Tests/Models/RelationTests.cs @@ -12,7 +12,7 @@ namespace Umbraco.Tests.Models [Test] public void Can_Deep_Clone() { - var item = new Relation(9, 8, new RelationType(Guid.NewGuid(), Guid.NewGuid(), "test") + var item = new Relation(9, 8, new RelationType("test", "test", false, Guid.NewGuid(), Guid.NewGuid()) { Id = 66 }) @@ -52,7 +52,7 @@ namespace Umbraco.Tests.Models { var ss = new SerializationService(new JsonNetSerializer()); - var item = new Relation(9, 8, new RelationType(Guid.NewGuid(), Guid.NewGuid(), "test") + var item = new Relation(9, 8, new RelationType("test", "test", false, Guid.NewGuid(), Guid.NewGuid()) { Id = 66 }) diff --git a/src/Umbraco.Tests/Models/RelationTypeTests.cs b/src/Umbraco.Tests/Models/RelationTypeTests.cs index 9d8fdcdf25..4555b6366f 100644 --- a/src/Umbraco.Tests/Models/RelationTypeTests.cs +++ b/src/Umbraco.Tests/Models/RelationTypeTests.cs @@ -12,7 +12,7 @@ namespace Umbraco.Tests.Models [Test] public void Can_Deep_Clone() { - var item = new RelationType(Guid.NewGuid(), Guid.NewGuid(), "test") + var item = new RelationType("test", "test", false, Guid.NewGuid(), Guid.NewGuid()) { Id = 66, CreateDate = DateTime.Now, @@ -48,7 +48,7 @@ namespace Umbraco.Tests.Models { var ss = new SerializationService(new JsonNetSerializer()); - var item = new RelationType(Guid.NewGuid(), Guid.NewGuid(), "test") + var item = new RelationType("test", "test", false, Guid.NewGuid(), Guid.NewGuid()) { Id = 66, CreateDate = DateTime.Now, diff --git a/src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs b/src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs index e1c3ecc891..4f35c57d04 100644 --- a/src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs +++ b/src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs @@ -54,7 +54,7 @@ namespace Umbraco.Tests.ModelsBuilder // // This code was generated by a tool. // -// Umbraco.ModelsBuilder v" + version + @" +// Umbraco.ModelsBuilder.Embedded v" + version + @" // // Changes to this file will be lost if the code is regenerated. // @@ -76,14 +76,14 @@ namespace Umbraco.Web.PublishedModels { // helpers #pragma warning disable 0109 // new is redundant - [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder"", """ + version + @""")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder.Embedded"", """ + version + @""")] public new const string ModelTypeAlias = ""type1""; - [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder"", """ + version + @""")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder.Embedded"", """ + version + @""")] public new const PublishedItemType ModelItemType = PublishedItemType.Content; - [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder"", """ + version + @""")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder.Embedded"", """ + version + @""")] public new static IPublishedContentType GetModelContentType() => PublishedModelUtility.GetModelContentType(ModelItemType, ModelTypeAlias); - [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder"", """ + version + @""")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder.Embedded"", """ + version + @""")] public static IPublishedPropertyType GetModelPropertyType(Expression> selector) => PublishedModelUtility.GetModelPropertyType(GetModelContentType(), selector); #pragma warning restore 0109 @@ -95,7 +95,7 @@ namespace Umbraco.Web.PublishedModels // properties - [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder"", """ + version + @""")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder.Embedded"", """ + version + @""")] [ImplementPropertyType(""prop1"")] public string Prop1 => this.Value(""prop1""); } @@ -169,7 +169,7 @@ namespace Umbraco.Web.PublishedModels // // This code was generated by a tool. // -// Umbraco.ModelsBuilder v" + version + @" +// Umbraco.ModelsBuilder.Embedded v" + version + @" // // Changes to this file will be lost if the code is regenerated. // @@ -191,14 +191,14 @@ namespace Umbraco.Web.PublishedModels { // helpers #pragma warning disable 0109 // new is redundant - [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder"", """ + version + @""")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder.Embedded"", """ + version + @""")] public new const string ModelTypeAlias = ""type1""; - [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder"", """ + version + @""")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder.Embedded"", """ + version + @""")] public new const PublishedItemType ModelItemType = PublishedItemType.Content; - [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder"", """ + version + @""")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder.Embedded"", """ + version + @""")] public new static IPublishedContentType GetModelContentType() => PublishedModelUtility.GetModelContentType(ModelItemType, ModelTypeAlias); - [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder"", """ + version + @""")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder.Embedded"", """ + version + @""")] public static IPublishedPropertyType GetModelPropertyType(Expression> selector) => PublishedModelUtility.GetModelPropertyType(GetModelContentType(), selector); #pragma warning restore 0109 @@ -210,7 +210,7 @@ namespace Umbraco.Web.PublishedModels // properties - [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder"", """ + version + @""")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder.Embedded"", """ + version + @""")] [ImplementPropertyType(""foo"")] public global::System.Collections.Generic.IEnumerable Foo => this.Value>(""foo""); } diff --git a/src/Umbraco.Tests/Packaging/PackageDataInstallationTests.cs b/src/Umbraco.Tests/Packaging/PackageDataInstallationTests.cs index 51df7d1f2f..ddfced7c8f 100644 --- a/src/Umbraco.Tests/Packaging/PackageDataInstallationTests.cs +++ b/src/Umbraco.Tests/Packaging/PackageDataInstallationTests.cs @@ -26,22 +26,22 @@ namespace Umbraco.Tests.Packaging public class PackageDataInstallationTests : TestWithSomeContentBase { [HideFromTypeFinder] + [DataEditor("7e062c13-7c41-4ad9-b389-41d88aeef87c", "Editor1", "editor1")] public class Editor1 : DataEditor { public Editor1(ILogger logger) : base(logger) { - Alias = "7e062c13-7c41-4ad9-b389-41d88aeef87c"; } } [HideFromTypeFinder] + [DataEditor("d15e1281-e456-4b24-aa86-1dda3e4299d5", "Editor2", "editor2")] public class Editor2 : DataEditor { public Editor2(ILogger logger) : base(logger) { - Alias = "d15e1281-e456-4b24-aa86-1dda3e4299d5"; } } diff --git a/src/Umbraco.Tests/Persistence/BulkDataReaderTests.cs b/src/Umbraco.Tests/Persistence/BulkDataReaderTests.cs index 3ada9fcceb..c157d66b2c 100644 --- a/src/Umbraco.Tests/Persistence/BulkDataReaderTests.cs +++ b/src/Umbraco.Tests/Persistence/BulkDataReaderTests.cs @@ -1693,7 +1693,7 @@ namespace Umbraco.Tests.Persistence { Assert.IsTrue(testReader.Read()); - Assert.AreEqual(testReader.Depth, 0); + Assert.AreEqual(0, testReader.Depth); } } @@ -2067,7 +2067,7 @@ namespace Umbraco.Tests.Persistence { Assert.IsTrue(testReader.Read()); - Assert.AreEqual(testReader.RecordsAffected, -1); + Assert.AreEqual(-1, testReader.RecordsAffected); } } diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs index f953b9cce6..e592c5171a 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs @@ -8,6 +8,7 @@ using Umbraco.Core.Models; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Repositories.Implement; +using Umbraco.Core.PropertyEditors; using Umbraco.Core.Scoping; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Entities; @@ -35,7 +36,12 @@ namespace Umbraco.Tests.Persistence.Repositories var commonRepository = new ContentTypeCommonRepository(scopeAccessor, templateRepository, AppCaches.Disabled); contentTypeRepository = new ContentTypeRepository(scopeAccessor, AppCaches.Disabled, Logger, commonRepository, langRepository); var languageRepository = new LanguageRepository(scopeAccessor, AppCaches.Disabled, Logger); - var repository = new DocumentRepository(scopeAccessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository); + var relationTypeRepository = new RelationTypeRepository(scopeAccessor, AppCaches.Disabled, Logger); + var entityRepository = new EntityRepository(scopeAccessor); + var relationRepository = new RelationRepository(scopeAccessor, Logger, relationTypeRepository, entityRepository); + var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); + var dataValueReferences = new DataValueReferenceFactoryCollection(Enumerable.Empty()); + var repository = new DocumentRepository(scopeAccessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferences); return repository; } @@ -965,6 +971,32 @@ namespace Umbraco.Tests.Persistence.Repositories } } + [Test] + public void Can_Verify_Content_Type_Has_Content_Nodes() + { + // Arrange + var provider = TestObjects.GetScopeProvider(Logger); + using (var scope = provider.CreateScope()) + { + ContentTypeRepository repository; + var contentRepository = CreateRepository((IScopeAccessor)provider, out repository); + var contentTypeId = NodeDto.NodeIdSeed + 1; + var contentType = repository.Get(contentTypeId); + + // Act + var result = repository.HasContentNodes(contentTypeId); + + var subpage = MockedContent.CreateTextpageContent(contentType, "Test Page 1", contentType.Id); + contentRepository.Save(subpage); + + var result2 = repository.HasContentNodes(contentTypeId); + + // Assert + Assert.That(result, Is.False); + Assert.That(result2, Is.True); + } + } + public void CreateTestData() { //Create and Save ContentType "umbTextpage" -> (NodeDto.NodeIdSeed) diff --git a/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs index 4d62ec8301..291525ba13 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs @@ -69,7 +69,12 @@ namespace Umbraco.Tests.Persistence.Repositories var commonRepository = new ContentTypeCommonRepository(scopeAccessor, templateRepository, appCaches); var languageRepository = new LanguageRepository(scopeAccessor, appCaches, Logger); contentTypeRepository = new ContentTypeRepository(scopeAccessor, appCaches, Logger, commonRepository, languageRepository); - var repository = new DocumentRepository(scopeAccessor, appCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository); + var relationTypeRepository = new RelationTypeRepository(scopeAccessor, AppCaches.Disabled, Logger); + var entityRepository = new EntityRepository(scopeAccessor); + var relationRepository = new RelationRepository(scopeAccessor, Logger, relationTypeRepository, entityRepository); + var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); + var dataValueReferences = new DataValueReferenceFactoryCollection(Enumerable.Empty()); + var repository = new DocumentRepository(scopeAccessor, appCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferences); return repository; } diff --git a/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs index 628f8d75a7..56138faea9 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Data; using System.Linq; using Moq; @@ -6,6 +7,7 @@ using NUnit.Framework; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Repositories.Implement; +using Umbraco.Core.PropertyEditors; using Umbraco.Core.Scoping; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Entities; @@ -25,7 +27,12 @@ namespace Umbraco.Tests.Persistence.Repositories var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches); languageRepository = new LanguageRepository(accessor, Core.Cache.AppCaches.Disabled, Logger); contentTypeRepository = new ContentTypeRepository(accessor, Core.Cache.AppCaches.Disabled, Logger, commonRepository, languageRepository); - documentRepository = new DocumentRepository(accessor, Core.Cache.AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository); + var relationTypeRepository = new RelationTypeRepository(accessor, Core.Cache.AppCaches.Disabled, Logger); + var entityRepository = new EntityRepository(accessor); + var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository, entityRepository); + var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); + var dataValueReferences = new DataValueReferenceFactoryCollection(Enumerable.Empty()); + documentRepository = new DocumentRepository(accessor, Core.Cache.AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferences); var domainRepository = new DomainRepository(accessor, Core.Cache.AppCaches.Disabled, Logger); return domainRepository; } diff --git a/src/Umbraco.Tests/Persistence/Repositories/EntityRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/EntityRepositoryTest.cs new file mode 100644 index 0000000000..a4df5bcf78 --- /dev/null +++ b/src/Umbraco.Tests/Persistence/Repositories/EntityRepositoryTest.cs @@ -0,0 +1,95 @@ +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Entities; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Repositories.Implement; +using Umbraco.Core.Scoping; +using Umbraco.Tests.TestHelpers; +using Umbraco.Tests.TestHelpers.Entities; +using Umbraco.Tests.Testing; + +namespace Umbraco.Tests.Persistence.Repositories +{ + [TestFixture] + [UmbracoTest(Mapper = true, Database = UmbracoTestOptions.Database.NewSchemaPerTest)] + public class EntityRepositoryTest : TestWithDatabaseBase + { + + private EntityRepository CreateRepository(IScopeAccessor scopeAccessor) + { + var entityRepository = new EntityRepository(scopeAccessor); + return entityRepository; + } + + [Test] + public void Get_Paged_Mixed_Entities_By_Ids() + { + //Create content + + var createdContent = new List(); + var contentType = MockedContentTypes.CreateBasicContentType("blah"); + ServiceContext.ContentTypeService.Save(contentType); + for (int i = 0; i < 10; i++) + { + var c1 = MockedContent.CreateBasicContent(contentType); + ServiceContext.ContentService.Save(c1); + createdContent.Add(c1); + } + + //Create media + + var createdMedia = new List(); + var imageType = MockedContentTypes.CreateImageMediaType("myImage"); + ServiceContext.MediaTypeService.Save(imageType); + for (int i = 0; i < 10; i++) + { + var c1 = MockedMedia.CreateMediaImage(imageType, -1); + ServiceContext.MediaService.Save(c1); + createdMedia.Add(c1); + } + + // Create members + var memberType = MockedContentTypes.CreateSimpleMemberType("simple"); + ServiceContext.MemberTypeService.Save(memberType); + var createdMembers = MockedMember.CreateSimpleMember(memberType, 10).ToList(); + ServiceContext.MemberService.Save(createdMembers); + + var provider = TestObjects.GetScopeProvider(Logger); + using (var scope = provider.CreateScope()) + { + var repo = CreateRepository((IScopeAccessor)provider); + + var ids = createdContent.Select(x => x.Id).Concat(createdMedia.Select(x => x.Id)).Concat(createdMembers.Select(x => x.Id)); + + var objectTypes = new[] { Constants.ObjectTypes.Document, Constants.ObjectTypes.Media, Constants.ObjectTypes.Member }; + + var query = SqlContext.Query() + .WhereIn(e => e.Id, ids); + + var entities = repo.GetPagedResultsByQuery(query, objectTypes, 0, 20, out var totalRecords, null, null).ToList(); + + Assert.AreEqual(20, entities.Count); + Assert.AreEqual(30, totalRecords); + + //add the next page + entities.AddRange(repo.GetPagedResultsByQuery(query, objectTypes, 1, 20, out totalRecords, null, null)); + + Assert.AreEqual(30, entities.Count); + Assert.AreEqual(30, totalRecords); + + var contentEntities = entities.OfType().ToList(); + var mediaEntities = entities.OfType().ToList(); + var memberEntities = entities.OfType().ToList(); + + Assert.AreEqual(10, contentEntities.Count); + Assert.AreEqual(10, mediaEntities.Count); + Assert.AreEqual(10, memberEntities.Count); + } + + } + + } +} diff --git a/src/Umbraco.Tests/Persistence/Repositories/MacroRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MacroRepositoryTest.cs index 6f215f4a35..9eca06d4e0 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MacroRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MacroRepositoryTest.cs @@ -38,7 +38,6 @@ namespace Umbraco.Tests.Persistence.Repositories var repository = new MacroRepository((IScopeAccessor) provider, AppCaches.Disabled, Mock.Of()); var macro = new Macro("test1", "Test", "~/views/macropartials/test.cshtml", MacroTypes.PartialView); - ; Assert.Throws(() => repository.Save(macro)); } @@ -56,7 +55,7 @@ namespace Umbraco.Tests.Persistence.Repositories var macro = repository.Get(1); macro.Alias = "test2"; - + Assert.Throws(() => repository.Save(macro)); } diff --git a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs index e2123df9e3..ef436a3489 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs @@ -17,6 +17,7 @@ using Umbraco.Core.Persistence.Repositories.Implement; using Umbraco.Core.Scoping; using Umbraco.Tests.Testing; using Umbraco.Core.Services; +using Umbraco.Core.PropertyEditors; namespace Umbraco.Tests.Persistence.Repositories { @@ -41,7 +42,12 @@ namespace Umbraco.Tests.Persistence.Repositories var languageRepository = new LanguageRepository(scopeAccessor, appCaches, Logger); mediaTypeRepository = new MediaTypeRepository(scopeAccessor, appCaches, Logger, commonRepository, languageRepository); var tagRepository = new TagRepository(scopeAccessor, appCaches, Logger); - var repository = new MediaRepository(scopeAccessor, appCaches, Logger, mediaTypeRepository, tagRepository, Mock.Of()); + var relationTypeRepository = new RelationTypeRepository(scopeAccessor, AppCaches.Disabled, Logger); + var entityRepository = new EntityRepository(scopeAccessor); + var relationRepository = new RelationRepository(scopeAccessor, Logger, relationTypeRepository, entityRepository); + var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); + var dataValueReferences = new DataValueReferenceFactoryCollection(Enumerable.Empty()); + var repository = new MediaRepository(scopeAccessor, appCaches, Logger, mediaTypeRepository, tagRepository, Mock.Of(), relationRepository, relationTypeRepository, propertyEditors, dataValueReferences); return repository; } diff --git a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs index 17b16ad7ab..060478d64b 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs @@ -15,6 +15,7 @@ using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.Repositories.Implement; +using Umbraco.Core.PropertyEditors; using Umbraco.Core.Scoping; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Entities; @@ -35,7 +36,12 @@ namespace Umbraco.Tests.Persistence.Repositories memberTypeRepository = new MemberTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository, languageRepository); memberGroupRepository = new MemberGroupRepository(accessor, AppCaches.Disabled, Logger); var tagRepo = new TagRepository(accessor, AppCaches.Disabled, Logger); - var repository = new MemberRepository(accessor, AppCaches.Disabled, Logger, memberTypeRepository, memberGroupRepository, tagRepo, Mock.Of()); + var relationTypeRepository = new RelationTypeRepository(accessor, AppCaches.Disabled, Logger); + var entityRepository = new EntityRepository(accessor); + var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository, entityRepository); + var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); + var dataValueReferences = new DataValueReferenceFactoryCollection(Enumerable.Empty()); + var repository = new MemberRepository(accessor, AppCaches.Disabled, Logger, memberTypeRepository, memberGroupRepository, tagRepo, Mock.Of(), relationRepository, relationTypeRepository, propertyEditors, dataValueReferences); return repository; } diff --git a/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs index 56041c24aa..4cf440c369 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs @@ -7,6 +7,7 @@ using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Models; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Repositories.Implement; +using Umbraco.Core.PropertyEditors; using Umbraco.Core.Scoping; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Entities; @@ -310,7 +311,12 @@ namespace Umbraco.Tests.Persistence.Repositories var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches); var languageRepository = new LanguageRepository(accessor, AppCaches, Logger); contentTypeRepository = new ContentTypeRepository(accessor, AppCaches, Logger, commonRepository, languageRepository); - var repository = new DocumentRepository(accessor, AppCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository); + var relationTypeRepository = new RelationTypeRepository(accessor, AppCaches, Logger); + var entityRepository = new EntityRepository(accessor); + var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository, entityRepository); + var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); + var dataValueReferences = new DataValueReferenceFactoryCollection(Enumerable.Empty()); + var repository = new DocumentRepository(accessor, AppCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferences); return repository; } diff --git a/src/Umbraco.Tests/Persistence/Repositories/RelationRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/RelationRepositoryTest.cs index cea7f44b71..364e4e2b3f 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/RelationRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/RelationRepositoryTest.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using Moq; using NUnit.Framework; @@ -6,6 +7,7 @@ using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; +using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.Repositories.Implement; @@ -31,7 +33,8 @@ namespace Umbraco.Tests.Persistence.Repositories { var accessor = (IScopeAccessor) provider; relationTypeRepository = new RelationTypeRepository(accessor, AppCaches.Disabled, Mock.Of()); - var repository = new RelationRepository(accessor, Mock.Of(), relationTypeRepository); + var entityRepository = new EntityRepository(accessor); + var repository = new RelationRepository(accessor, Mock.Of(), relationTypeRepository, entityRepository); return repository; } @@ -168,6 +171,156 @@ namespace Umbraco.Tests.Persistence.Repositories } } + [Test] + public void Get_Paged_Parent_Entities_By_Child_Id() + { + CreateTestDataForPagingTests(out var createdContent, out var createdMembers, out var createdMedia); + + var provider = TestObjects.GetScopeProvider(Logger); + using (var scope = provider.CreateScope()) + { + var repository = CreateRepository(provider, out var relationTypeRepository); + + // Get parent entities for child id + var parents = repository.GetPagedParentEntitiesByChildId(createdMedia[0].Id, 0, 11, out var totalRecords).ToList(); + Assert.AreEqual(20, totalRecords); + Assert.AreEqual(11, parents.Count); + + //add the next page + parents.AddRange(repository.GetPagedParentEntitiesByChildId(createdMedia[0].Id, 1, 11, out totalRecords)); + Assert.AreEqual(20, totalRecords); + Assert.AreEqual(20, parents.Count); + + var contentEntities = parents.OfType().ToList(); + var mediaEntities = parents.OfType().ToList(); + var memberEntities = parents.OfType().ToList(); + + Assert.AreEqual(10, contentEntities.Count); + Assert.AreEqual(0, mediaEntities.Count); + Assert.AreEqual(10, memberEntities.Count); + + //only of a certain type + parents.AddRange(repository.GetPagedParentEntitiesByChildId(createdMedia[0].Id, 0, 100, out totalRecords, UmbracoObjectTypes.Document.GetGuid())); + Assert.AreEqual(10, totalRecords); + + parents.AddRange(repository.GetPagedParentEntitiesByChildId(createdMedia[0].Id, 0, 100, out totalRecords, UmbracoObjectTypes.Member.GetGuid())); + Assert.AreEqual(10, totalRecords); + + parents.AddRange(repository.GetPagedParentEntitiesByChildId(createdMedia[0].Id, 0, 100, out totalRecords, UmbracoObjectTypes.Media.GetGuid())); + Assert.AreEqual(0, totalRecords); + } + } + + [Test] + public void Get_Paged_Parent_Child_Entities_With_Same_Entity_Relation() + { + //Create a media item and create a relationship between itself (parent -> child) + var imageType = MockedContentTypes.CreateImageMediaType("myImage"); + ServiceContext.MediaTypeService.Save(imageType); + var media = MockedMedia.CreateMediaImage(imageType, -1); + ServiceContext.MediaService.Save(media); + var relType = ServiceContext.RelationService.GetRelationTypeByAlias(Constants.Conventions.RelationTypes.RelatedMediaAlias); + ServiceContext.RelationService.Relate(media.Id, media.Id, relType); + + var provider = TestObjects.GetScopeProvider(Logger); + using (var scope = provider.CreateScope()) + { + var repository = CreateRepository(provider, out var relationTypeRepository); + + // Get parent entities for child id + var parents = repository.GetPagedParentEntitiesByChildId(media.Id, 0, 10, out var totalRecords).ToList(); + Assert.AreEqual(1, totalRecords); + Assert.AreEqual(1, parents.Count); + + // Get child entities for parent id + var children = repository.GetPagedChildEntitiesByParentId(media.Id, 0, 10, out totalRecords).ToList(); + Assert.AreEqual(1, totalRecords); + Assert.AreEqual(1, children.Count); + } + } + + [Test] + public void Get_Paged_Child_Entities_By_Parent_Id() + { + CreateTestDataForPagingTests(out var createdContent, out var createdMembers, out var createdMedia); + + var provider = TestObjects.GetScopeProvider(Logger); + using (var scope = provider.CreateScope()) + { + var repository = CreateRepository(provider, out var relationTypeRepository); + + // Get parent entities for child id + var parents = repository.GetPagedChildEntitiesByParentId(createdContent[0].Id, 0, 6, out var totalRecords).ToList(); + Assert.AreEqual(10, totalRecords); + Assert.AreEqual(6, parents.Count); + + //add the next page + parents.AddRange(repository.GetPagedChildEntitiesByParentId(createdContent[0].Id, 1, 6, out totalRecords)); + Assert.AreEqual(10, totalRecords); + Assert.AreEqual(10, parents.Count); + + var contentEntities = parents.OfType().ToList(); + var mediaEntities = parents.OfType().ToList(); + var memberEntities = parents.OfType().ToList(); + + Assert.AreEqual(0, contentEntities.Count); + Assert.AreEqual(10, mediaEntities.Count); + Assert.AreEqual(0, memberEntities.Count); + + //only of a certain type + parents.AddRange(repository.GetPagedChildEntitiesByParentId(createdContent[0].Id, 0, 100, out totalRecords, UmbracoObjectTypes.Media.GetGuid())); + Assert.AreEqual(10, totalRecords); + + parents.AddRange(repository.GetPagedChildEntitiesByParentId(createdMembers[0].Id, 0, 100, out totalRecords, UmbracoObjectTypes.Media.GetGuid())); + Assert.AreEqual(10, totalRecords); + + parents.AddRange(repository.GetPagedChildEntitiesByParentId(createdContent[0].Id, 0, 100, out totalRecords, UmbracoObjectTypes.Member.GetGuid())); + Assert.AreEqual(0, totalRecords); + } + } + + private void CreateTestDataForPagingTests(out List createdContent, out List createdMembers, out List createdMedia) + { + //Create content + createdContent = new List(); + var contentType = MockedContentTypes.CreateBasicContentType("blah"); + ServiceContext.ContentTypeService.Save(contentType); + for (int i = 0; i < 10; i++) + { + var c1 = MockedContent.CreateBasicContent(contentType); + ServiceContext.ContentService.Save(c1); + createdContent.Add(c1); + } + + //Create media + createdMedia = new List(); + var imageType = MockedContentTypes.CreateImageMediaType("myImage"); + ServiceContext.MediaTypeService.Save(imageType); + for (int i = 0; i < 10; i++) + { + var c1 = MockedMedia.CreateMediaImage(imageType, -1); + ServiceContext.MediaService.Save(c1); + createdMedia.Add(c1); + } + + // Create members + var memberType = MockedContentTypes.CreateSimpleMemberType("simple"); + ServiceContext.MemberTypeService.Save(memberType); + createdMembers = MockedMember.CreateSimpleMember(memberType, 10).ToList(); + ServiceContext.MemberService.Save(createdMembers); + + var relType = ServiceContext.RelationService.GetRelationTypeByAlias(Constants.Conventions.RelationTypes.RelatedMediaAlias); + + // Relate content to media + foreach (var content in createdContent) + foreach (var media in createdMedia) + ServiceContext.RelationService.Relate(content.Id, media.Id, relType); + // Relate members to media + foreach (var member in createdMembers) + foreach (var media in createdMedia) + ServiceContext.RelationService.Relate(member.Id, media.Id, relType); + } + [Test] public void Can_Perform_Exists_On_RelationRepository() { @@ -260,14 +413,24 @@ namespace Umbraco.Tests.Persistence.Repositories public void CreateTestData() { - var relateContent = new RelationType(Constants.ObjectTypes.Document, new Guid("C66BA18E-EAF3-4CFF-8A22-41B16D66A972"), "relateContentOnCopy") { IsBidirectional = true, Name = "Relate Content on Copy" }; - var relateContentType = new RelationType(Constants.ObjectTypes.DocumentType, new Guid("A2CB7800-F571-4787-9638-BC48539A0EFB"), "relateContentTypeOnCopy") { IsBidirectional = true, Name = "Relate ContentType on Copy" }; + var relateContent = new RelationType( + "Relate Content on Copy", "relateContentOnCopy", true, + Constants.ObjectTypes.Document, + new Guid("C66BA18E-EAF3-4CFF-8A22-41B16D66A972")); + + var relateContentType = new RelationType("Relate ContentType on Copy", + "relateContentTypeOnCopy", + true, + Constants.ObjectTypes.DocumentType, + new Guid("A2CB7800-F571-4787-9638-BC48539A0EFB")); var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var relationTypeRepository = new RelationTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Mock.Of()); - var relationRepository = new RelationRepository((IScopeAccessor) provider, Mock.Of(), relationTypeRepository); + var accessor = (IScopeAccessor)provider; + var relationTypeRepository = new RelationTypeRepository(accessor, AppCaches.Disabled, Mock.Of()); + var entityRepository = new EntityRepository(accessor); + var relationRepository = new RelationRepository(accessor, Mock.Of(), relationTypeRepository, entityRepository); relationTypeRepository.Save(relateContent); relationTypeRepository.Save(relateContentType); diff --git a/src/Umbraco.Tests/Persistence/Repositories/RelationTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/RelationTypeRepositoryTest.cs index e52e2dfcdf..edde8e8f81 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/RelationTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/RelationTypeRepositoryTest.cs @@ -42,9 +42,7 @@ namespace Umbraco.Tests.Persistence.Repositories var repository = CreateRepository(provider); // Act - var relateMemberToContent = new RelationType(Constants.ObjectTypes.Member, - Constants.ObjectTypes.Document, - "relateMemberToContent") { IsBidirectional = true, Name = "Relate Member to Content" }; + var relateMemberToContent = new RelationType("Relate Member to Content", "relateMemberToContent", true, Constants.ObjectTypes.Member, Constants.ObjectTypes.Document); repository.Save(relateMemberToContent); @@ -109,13 +107,16 @@ namespace Umbraco.Tests.Persistence.Repositories var repository = CreateRepository(provider); // Act - var relationType = repository.Get(RelationTypeDto.NodeIdSeed); + var relationType = repository.Get(RelationTypeDto.NodeIdSeed + 2); // Assert Assert.That(relationType, Is.Not.Null); Assert.That(relationType.HasIdentity, Is.True); - Assert.That(relationType.Alias, Is.EqualTo("relateContentOnCopy")); - Assert.That(relationType.Name, Is.EqualTo("Relate Content on Copy")); + Assert.That(relationType.IsBidirectional, Is.True); + Assert.That(relationType.Alias, Is.EqualTo("relateContentToMedia")); + Assert.That(relationType.Name, Is.EqualTo("Relate Content to Media")); + Assert.That(relationType.ChildObjectType, Is.EqualTo(Constants.ObjectTypes.Media)); + Assert.That(relationType.ParentObjectType, Is.EqualTo(Constants.ObjectTypes.Document)); } } @@ -135,7 +136,7 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.That(relationTypes, Is.Not.Null); Assert.That(relationTypes.Any(), Is.True); Assert.That(relationTypes.Any(x => x == null), Is.False); - Assert.That(relationTypes.Count(), Is.EqualTo(5)); + Assert.That(relationTypes.Count(), Is.EqualTo(8)); } } @@ -192,7 +193,7 @@ namespace Umbraco.Tests.Persistence.Repositories int count = repository.Count(query); // Assert - Assert.That(count, Is.EqualTo(5)); + Assert.That(count, Is.EqualTo(6)); } } @@ -226,8 +227,9 @@ namespace Umbraco.Tests.Persistence.Repositories public void CreateTestData() { - var relateContent = new RelationType(Constants.ObjectTypes.Document, new Guid("C66BA18E-EAF3-4CFF-8A22-41B16D66A972"), "relateContentOnCopy") { IsBidirectional = true, Name = "Relate Content on Copy" }; - var relateContentType = new RelationType(Constants.ObjectTypes.DocumentType, new Guid("A2CB7800-F571-4787-9638-BC48539A0EFB"), "relateContentTypeOnCopy") { IsBidirectional = true, Name = "Relate ContentType on Copy" }; + var relateContent = new RelationType("Relate Content on Copy", "relateContentOnCopy", true, Constants.ObjectTypes.Document, Constants.ObjectTypes.Document); + var relateContentType = new RelationType("Relate ContentType on Copy", "relateContentTypeOnCopy", true, Constants.ObjectTypes.DocumentType, Constants.ObjectTypes.DocumentType); + var relateContentMedia = new RelationType("Relate Content to Media", "relateContentToMedia", true, Constants.ObjectTypes.Document, Constants.ObjectTypes.Media); var provider = TestObjects.GetScopeProvider(Logger); using (var scope = ScopeProvider.CreateScope()) @@ -236,6 +238,7 @@ namespace Umbraco.Tests.Persistence.Repositories repository.Save(relateContent);//Id 2 repository.Save(relateContentType);//Id 3 + repository.Save(relateContentMedia);//Id 4 scope.Complete(); } } diff --git a/src/Umbraco.Tests/Persistence/Repositories/StylesheetRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/StylesheetRepositoryTest.cs index f427f22796..304422d1e2 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/StylesheetRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/StylesheetRepositoryTest.cs @@ -294,15 +294,13 @@ namespace Umbraco.Tests.Persistence.Repositories stylesheet = repository.Get("missing.css"); Assert.IsNull(stylesheet); - // fixed in 7.3 - 7.2.8 used to... - Assert.Throws(() => - { - stylesheet = repository.Get("\\test-path-4.css"); // outside the filesystem, does not exist - }); - Assert.Throws(() => - { - stylesheet = repository.Get("../packages.config"); // outside the filesystem, exists - }); + // #7713 changes behaviour to return null when outside the filesystem + // to accomodate changing the CSS path and not flooding the backoffice with errors + stylesheet = repository.Get("\\test-path-4.css"); // outside the filesystem, does not exist + Assert.IsNull(stylesheet); + + stylesheet = repository.Get("../packages.config"); // outside the filesystem, exists + Assert.IsNull(stylesheet); } } diff --git a/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs index e3de2c2892..2c15e91e61 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System; +using System.Linq; using Moq; using NUnit.Framework; using Umbraco.Core.Cache; @@ -7,6 +8,7 @@ using Umbraco.Core.IO; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.Repositories.Implement; +using Umbraco.Core.PropertyEditors; using Umbraco.Core.Scoping; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Entities; @@ -74,7 +76,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (ScopeProvider.CreateScope()) { - var contentRepository = CreateContentRepository(provider, out var contentTypeRepository); + var contentRepository = CreateDocumentRepository(provider, out var contentTypeRepository); // create data to relate to var contentType = MockedContentTypes.CreateSimpleContentType("test", "Test"); @@ -104,7 +106,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (ScopeProvider.CreateScope()) { - var contentRepository = CreateContentRepository(provider, out var contentTypeRepository); + var contentRepository = CreateDocumentRepository(provider, out var contentTypeRepository); //create data to relate to var contentType = MockedContentTypes.CreateSimpleContentType("test", "Test"); @@ -143,7 +145,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (ScopeProvider.CreateScope()) { - var contentRepository = CreateContentRepository(provider, out var contentTypeRepository); + var contentRepository = CreateDocumentRepository(provider, out var contentTypeRepository); //create data to relate to var contentType = MockedContentTypes.CreateSimpleContentType("test", "Test"); @@ -185,7 +187,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (ScopeProvider.CreateScope()) { - var contentRepository = CreateContentRepository(provider, out var contentTypeRepository); + var contentRepository = CreateDocumentRepository(provider, out var contentTypeRepository); //create data to relate to var contentType = MockedContentTypes.CreateSimpleContentType("test", "Test"); @@ -225,7 +227,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (ScopeProvider.CreateScope()) { - var contentRepository = CreateContentRepository(provider, out var contentTypeRepository); + var contentRepository = CreateDocumentRepository(provider, out var contentTypeRepository); //create data to relate to var contentType = MockedContentTypes.CreateSimpleContentType("test", "Test"); @@ -261,7 +263,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (ScopeProvider.CreateScope()) { - var contentRepository = CreateContentRepository(provider, out var contentTypeRepository); + var contentRepository = CreateDocumentRepository(provider, out var contentTypeRepository); //create data to relate to var contentType = MockedContentTypes.CreateSimpleContentType("test", "Test"); @@ -305,7 +307,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (ScopeProvider.CreateScope()) { - var contentRepository = CreateContentRepository(provider, out var contentTypeRepository); + var contentRepository = CreateDocumentRepository(provider, out var contentTypeRepository); //create data to relate to var contentType = MockedContentTypes.CreateSimpleContentType("test", "Test"); @@ -349,7 +351,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (ScopeProvider.CreateScope()) { - var contentRepository = CreateContentRepository(provider, out var contentTypeRepository); + var contentRepository = CreateDocumentRepository(provider, out var contentTypeRepository); //create data to relate to var contentType = MockedContentTypes.CreateSimpleContentType("test", "Test"); @@ -394,7 +396,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (ScopeProvider.CreateScope()) { - var contentRepository = CreateContentRepository(provider, out var contentTypeRepository); + var contentRepository = CreateDocumentRepository(provider, out var contentTypeRepository); //create data to relate to var contentType = MockedContentTypes.CreateSimpleContentType("test", "Test"); @@ -429,7 +431,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (ScopeProvider.CreateScope()) { - var contentRepository = CreateContentRepository(provider, out var contentTypeRepository); + var contentRepository = CreateDocumentRepository(provider, out var contentTypeRepository); //create data to relate to var contentType = MockedContentTypes.CreateSimpleContentType("test", "Test"); @@ -469,7 +471,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (ScopeProvider.CreateScope()) { - var contentRepository = CreateContentRepository(provider, out var contentTypeRepository); + var contentRepository = CreateDocumentRepository(provider, out var contentTypeRepository); //create data to relate to var contentType = MockedContentTypes.CreateSimpleContentType("test", "Test"); @@ -513,7 +515,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (ScopeProvider.CreateScope()) { - var contentRepository = CreateContentRepository(provider, out var contentTypeRepository); + var contentRepository = CreateDocumentRepository(provider, out var contentTypeRepository); //create data to relate to var contentType = MockedContentTypes.CreateSimpleContentType("test", "Test"); @@ -557,7 +559,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (ScopeProvider.CreateScope()) { - var contentRepository = CreateContentRepository(provider, out var contentTypeRepository); + var contentRepository = CreateDocumentRepository(provider, out var contentTypeRepository); //create data to relate to var contentType = MockedContentTypes.CreateSimpleContentType("test", "Test"); @@ -601,7 +603,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (ScopeProvider.CreateScope()) { - var contentRepository = CreateContentRepository(provider, out var contentTypeRepository); + var contentRepository = CreateDocumentRepository(provider, out var contentTypeRepository); //create data to relate to var contentType = MockedContentTypes.CreateSimpleContentType("test", "Test"); @@ -646,7 +648,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (ScopeProvider.CreateScope()) { - var contentRepository = CreateContentRepository(provider, out var contentTypeRepository); + var contentRepository = CreateDocumentRepository(provider, out var contentTypeRepository); var mediaRepository = CreateMediaRepository(provider, out var mediaTypeRepository); //create data to relate to @@ -703,7 +705,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (ScopeProvider.CreateScope()) { - var contentRepository = CreateContentRepository(provider, out var contentTypeRepository); + var contentRepository = CreateDocumentRepository(provider, out var contentTypeRepository); var mediaRepository = CreateMediaRepository(provider, out var mediaTypeRepository); //create data to relate to @@ -755,7 +757,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = ScopeProvider.CreateScope()) { - var contentRepository = CreateContentRepository(provider, out var contentTypeRepository); + var contentRepository = CreateDocumentRepository(provider, out var contentTypeRepository); //create data to relate to var contentType = MockedContentTypes.CreateSimpleContentType("test", "Test"); @@ -791,7 +793,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (ScopeProvider.CreateScope()) { - var contentRepository = CreateContentRepository(provider, out var contentTypeRepository); + var contentRepository = CreateDocumentRepository(provider, out var contentTypeRepository); var mediaRepository = CreateMediaRepository(provider, out var mediaTypeRepository); //create data to relate to @@ -871,7 +873,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (ScopeProvider.CreateScope()) { - var contentRepository = CreateContentRepository(provider, out var contentTypeRepository); + var contentRepository = CreateDocumentRepository(provider, out var contentTypeRepository); var mediaRepository = CreateMediaRepository(provider, out var mediaTypeRepository); //create data to relate to @@ -950,7 +952,7 @@ namespace Umbraco.Tests.Persistence.Repositories return new TagRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger); } - private DocumentRepository CreateContentRepository(IScopeProvider provider, out ContentTypeRepository contentTypeRepository) + private DocumentRepository CreateDocumentRepository(IScopeProvider provider, out ContentTypeRepository contentTypeRepository) { var accessor = (IScopeAccessor) provider; var templateRepository = new TemplateRepository(accessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock()); @@ -958,7 +960,12 @@ namespace Umbraco.Tests.Persistence.Repositories var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches.Disabled); var languageRepository = new LanguageRepository(accessor, AppCaches.Disabled, Logger); contentTypeRepository = new ContentTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository, languageRepository); - var repository = new DocumentRepository(accessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository); + var relationTypeRepository = new RelationTypeRepository(accessor, AppCaches.Disabled, Logger); + var entityRepository = new EntityRepository(accessor); + var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository, entityRepository); + var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); + var dataValueReferences = new DataValueReferenceFactoryCollection(Enumerable.Empty()); + var repository = new DocumentRepository(accessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferences); return repository; } @@ -970,7 +977,12 @@ namespace Umbraco.Tests.Persistence.Repositories var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches.Disabled); var languageRepository = new LanguageRepository(accessor, AppCaches.Disabled, Logger); mediaTypeRepository = new MediaTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository, languageRepository); - var repository = new MediaRepository(accessor, AppCaches.Disabled, Logger, mediaTypeRepository, tagRepository, Mock.Of()); + var relationTypeRepository = new RelationTypeRepository(accessor, AppCaches.Disabled, Logger); + var entityRepository = new EntityRepository(accessor); + var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository, entityRepository); + var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); + var dataValueReferences = new DataValueReferenceFactoryCollection(Enumerable.Empty()); + var repository = new MediaRepository(accessor, AppCaches.Disabled, Logger, mediaTypeRepository, tagRepository, Mock.Of(), relationRepository, relationTypeRepository, propertyEditors, dataValueReferences); return repository; } } diff --git a/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs index b0f9a5335b..200447e30a 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs @@ -12,6 +12,7 @@ using Umbraco.Core.IO; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.Repositories.Implement; +using Umbraco.Core.PropertyEditors; using Umbraco.Core.Scoping; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Entities; @@ -237,11 +238,16 @@ namespace Umbraco.Tests.Persistence.Repositories { var templateRepository = CreateRepository(ScopeProvider); - var tagRepository = new TagRepository((IScopeAccessor) ScopeProvider, AppCaches.Disabled, Logger); + var tagRepository = new TagRepository(ScopeProvider, AppCaches.Disabled, Logger); var commonRepository = new ContentTypeCommonRepository(ScopeProvider, templateRepository, AppCaches); - var languageRepository = new LanguageRepository((IScopeAccessor) ScopeProvider, AppCaches.Disabled, Logger); - var contentTypeRepository = new ContentTypeRepository((IScopeAccessor) ScopeProvider, AppCaches.Disabled, Logger, commonRepository, languageRepository); - var contentRepo = new DocumentRepository((IScopeAccessor) ScopeProvider, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository); + var languageRepository = new LanguageRepository(ScopeProvider, AppCaches.Disabled, Logger); + var contentTypeRepository = new ContentTypeRepository(ScopeProvider, AppCaches.Disabled, Logger, commonRepository, languageRepository); + var relationTypeRepository = new RelationTypeRepository(ScopeProvider, AppCaches.Disabled, Logger); + var entityRepository = new EntityRepository(ScopeProvider); + var relationRepository = new RelationRepository(ScopeProvider, Logger, relationTypeRepository, entityRepository); + var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); + var dataValueReferences = new DataValueReferenceFactoryCollection(Enumerable.Empty()); + var contentRepo = new DocumentRepository(ScopeProvider, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferences); var contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage2", "Textpage"); ServiceContext.FileService.SaveTemplate(contentType.DefaultTemplate); // else, FK violation on contentType! diff --git a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs index 3e5919d7f3..bbefb79f6b 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs @@ -14,6 +14,8 @@ using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Entities; using Umbraco.Tests.Testing; using Umbraco.Core.Persistence; +using Umbraco.Core.PropertyEditors; +using System; namespace Umbraco.Tests.Persistence.Repositories { @@ -29,7 +31,12 @@ namespace Umbraco.Tests.Persistence.Repositories var languageRepository = new LanguageRepository(accessor, AppCaches, Logger); mediaTypeRepository = new MediaTypeRepository(accessor, AppCaches, Mock.Of(), commonRepository, languageRepository); var tagRepository = new TagRepository(accessor, AppCaches, Mock.Of()); - var repository = new MediaRepository(accessor, AppCaches, Mock.Of(), mediaTypeRepository, tagRepository, Mock.Of()); + var relationTypeRepository = new RelationTypeRepository(accessor, AppCaches.Disabled, Logger); + var entityRepository = new EntityRepository(accessor); + var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository, entityRepository); + var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); + var dataValueReferences = new DataValueReferenceFactoryCollection(Enumerable.Empty()); + var repository = new MediaRepository(accessor, AppCaches, Mock.Of(), mediaTypeRepository, tagRepository, Mock.Of(), relationRepository, relationTypeRepository, propertyEditors, dataValueReferences); return repository; } @@ -47,7 +54,12 @@ namespace Umbraco.Tests.Persistence.Repositories var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches); var languageRepository = new LanguageRepository(accessor, AppCaches, Logger); contentTypeRepository = new ContentTypeRepository(accessor, AppCaches, Logger, commonRepository, languageRepository); - var repository = new DocumentRepository(accessor, AppCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository); + var relationTypeRepository = new RelationTypeRepository(accessor, AppCaches.Disabled, Logger); + var entityRepository = new EntityRepository(accessor); + var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository, entityRepository); + var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); + var dataValueReferences = new DataValueReferenceFactoryCollection(Enumerable.Empty()); + var repository = new DocumentRepository(accessor, AppCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferences); return repository; } @@ -409,6 +421,35 @@ namespace Umbraco.Tests.Persistence.Repositories } } + [Test] + public void Can_Invalidate_SecurityStamp_On_Username_Change() + { + // Arrange + var provider = TestObjects.GetScopeProvider(Logger); + using (var scope = provider.CreateScope()) + { + var repository = CreateRepository(provider); + var userGroupRepository = CreateUserGroupRepository(provider); + + var user = CreateAndCommitUserWithGroup(repository, userGroupRepository); + var originalSecurityStamp = user.SecurityStamp; + + // Ensure when user generated a security stamp is present + Assert.That(user.SecurityStamp, Is.Not.Null); + Assert.That(user.SecurityStamp, Is.Not.Empty); + + // Update username + user.Username = user.Username + "UPDATED"; + repository.Save(user); + + // Get the user + var updatedUser = repository.Get(user.Id); + + // Ensure the Security Stamp is invalidated & no longer the same + Assert.AreNotEqual(originalSecurityStamp, updatedUser.SecurityStamp); + } + } + private void AssertPropertyValues(IUser updatedItem, IUser originalUser) { Assert.That(updatedItem.Id, Is.EqualTo(originalUser.Id)); diff --git a/src/Umbraco.Tests/PropertyEditors/DataValueReferenceFactoryCollectionTests.cs b/src/Umbraco.Tests/PropertyEditors/DataValueReferenceFactoryCollectionTests.cs new file mode 100644 index 0000000000..ae6f80b32d --- /dev/null +++ b/src/Umbraco.Tests/PropertyEditors/DataValueReferenceFactoryCollectionTests.cs @@ -0,0 +1,223 @@ +using Moq; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Editors; +using Umbraco.Core.PropertyEditors; +using Umbraco.Web.PropertyEditors; +using static Umbraco.Core.Models.Property; + +namespace Umbraco.Tests.PropertyEditors +{ + [TestFixture] + public class DataValueReferenceFactoryCollectionTests + { + [Test] + public void GetAllReferences_All_Variants_With_IDataValueReferenceFactory() + { + var collection = new DataValueReferenceFactoryCollection(new TestDataValueReferenceFactory().Yield()); + + // label does not implement IDataValueReference + var labelEditor = new LabelPropertyEditor(Mock.Of()); + var propertyEditors = new PropertyEditorCollection(new DataEditorCollection(labelEditor.Yield())); + var trackedUdi1 = Udi.Create(Constants.UdiEntityType.Media, Guid.NewGuid()).ToString(); + var trackedUdi2 = Udi.Create(Constants.UdiEntityType.Media, Guid.NewGuid()).ToString(); + var trackedUdi3 = Udi.Create(Constants.UdiEntityType.Media, Guid.NewGuid()).ToString(); + var trackedUdi4 = Udi.Create(Constants.UdiEntityType.Media, Guid.NewGuid()).ToString(); + var property = new Property(new PropertyType(new DataType(labelEditor)) + { + Variations = ContentVariation.CultureAndSegment + }) + { + Values = new List + { + // Ignored (no culture) + new PropertyValue + { + EditedValue = trackedUdi1 + }, + new PropertyValue + { + Culture = "en-US", + EditedValue = trackedUdi2 + }, + new PropertyValue + { + Culture = "en-US", + Segment = "A", + EditedValue = trackedUdi3 + }, + // Ignored (no culture) + new PropertyValue + { + Segment = "A", + EditedValue = trackedUdi4 + }, + // duplicate + new PropertyValue + { + Culture = "en-US", + Segment = "B", + EditedValue = trackedUdi3 + } + } + }; + var properties = new PropertyCollection + { + property + }; + var result = collection.GetAllReferences(properties, propertyEditors); + + Assert.AreEqual(2, result.Count()); + Assert.AreEqual(trackedUdi2, result.ElementAt(0).Udi.ToString()); + Assert.AreEqual(trackedUdi3, result.ElementAt(1).Udi.ToString()); + } + + [Test] + public void GetAllReferences_All_Variants_With_IDataValueReference_Editor() + { + var collection = new DataValueReferenceFactoryCollection(Enumerable.Empty()); + + // mediaPicker does implement IDataValueReference + var mediaPicker = new MediaPickerPropertyEditor(Mock.Of()); + var propertyEditors = new PropertyEditorCollection(new DataEditorCollection(mediaPicker.Yield())); + var trackedUdi1 = Udi.Create(Constants.UdiEntityType.Media, Guid.NewGuid()).ToString(); + var trackedUdi2 = Udi.Create(Constants.UdiEntityType.Media, Guid.NewGuid()).ToString(); + var trackedUdi3 = Udi.Create(Constants.UdiEntityType.Media, Guid.NewGuid()).ToString(); + var trackedUdi4 = Udi.Create(Constants.UdiEntityType.Media, Guid.NewGuid()).ToString(); + var property = new Property(new PropertyType(new DataType(mediaPicker)) + { + Variations = ContentVariation.CultureAndSegment + }) + { + Values = new List + { + // Ignored (no culture) + new PropertyValue + { + EditedValue = trackedUdi1 + }, + new PropertyValue + { + Culture = "en-US", + EditedValue = trackedUdi2 + }, + new PropertyValue + { + Culture = "en-US", + Segment = "A", + EditedValue = trackedUdi3 + }, + // Ignored (no culture) + new PropertyValue + { + Segment = "A", + EditedValue = trackedUdi4 + }, + // duplicate + new PropertyValue + { + Culture = "en-US", + Segment = "B", + EditedValue = trackedUdi3 + } + } + }; + var properties = new PropertyCollection + { + property + }; + var result = collection.GetAllReferences(properties, propertyEditors); + + Assert.AreEqual(2, result.Count()); + Assert.AreEqual(trackedUdi2, result.ElementAt(0).Udi.ToString()); + Assert.AreEqual(trackedUdi3, result.ElementAt(1).Udi.ToString()); + } + + [Test] + public void GetAllReferences_Invariant_With_IDataValueReference_Editor() + { + var collection = new DataValueReferenceFactoryCollection(Enumerable.Empty()); + + // mediaPicker does implement IDataValueReference + var mediaPicker = new MediaPickerPropertyEditor(Mock.Of()); + var propertyEditors = new PropertyEditorCollection(new DataEditorCollection(mediaPicker.Yield())); + var trackedUdi1 = Udi.Create(Constants.UdiEntityType.Media, Guid.NewGuid()).ToString(); + var trackedUdi2 = Udi.Create(Constants.UdiEntityType.Media, Guid.NewGuid()).ToString(); + var trackedUdi3 = Udi.Create(Constants.UdiEntityType.Media, Guid.NewGuid()).ToString(); + var trackedUdi4 = Udi.Create(Constants.UdiEntityType.Media, Guid.NewGuid()).ToString(); + var property = new Property(new PropertyType(new DataType(mediaPicker)) + { + Variations = ContentVariation.Nothing | ContentVariation.Segment + }) + { + Values = new List + { + new PropertyValue + { + EditedValue = trackedUdi1 + }, + // Ignored (has culture) + new PropertyValue + { + Culture = "en-US", + EditedValue = trackedUdi2 + }, + // Ignored (has culture) + new PropertyValue + { + Culture = "en-US", + Segment = "A", + EditedValue = trackedUdi3 + }, + new PropertyValue + { + Segment = "A", + EditedValue = trackedUdi4 + }, + // duplicate + new PropertyValue + { + Segment = "B", + EditedValue = trackedUdi4 + } + } + }; + var properties = new PropertyCollection + { + property + }; + var result = collection.GetAllReferences(properties, propertyEditors); + + Assert.AreEqual(2, result.Count()); + Assert.AreEqual(trackedUdi1, result.ElementAt(0).Udi.ToString()); + Assert.AreEqual(trackedUdi4, result.ElementAt(1).Udi.ToString()); + } + + private class TestDataValueReferenceFactory : IDataValueReferenceFactory + { + public IDataValueReference GetDataValueReference() => new TestMediaDataValueReference(); + + public bool IsForEditor(IDataEditor dataEditor) => dataEditor.Alias == Constants.PropertyEditors.Aliases.Label; + + private class TestMediaDataValueReference : IDataValueReference + { + public IEnumerable GetReferences(object value) + { + // This is the same as the media picker, it will just try to parse the value directly as a UDI + + var asString = value is string str ? str : value?.ToString(); + + if (string.IsNullOrEmpty(asString)) yield break; + + if (Udi.TryParse(asString, out var udi)) + yield return new UmbracoEntityReference(udi); + } + } + } + } +} diff --git a/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs b/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs index 8d2ab84d35..c5c2b4e61f 100644 --- a/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs +++ b/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs @@ -5,9 +5,7 @@ using Newtonsoft.Json; using NUnit.Framework; using Newtonsoft.Json.Linq; using Umbraco.Core; -using Umbraco.Core.Cache; using Umbraco.Core.Composing; -using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Logging; @@ -21,9 +19,11 @@ using Umbraco.Tests.TestHelpers; using Umbraco.Web.Models; using Umbraco.Web; using Umbraco.Web.PropertyEditors; +using System.Text; namespace Umbraco.Tests.PropertyEditors { + [TestFixture] public class ImageCropperTest { @@ -109,8 +109,8 @@ namespace Umbraco.Tests.PropertyEditors [Test] public void GetCropUrl_CropAliasTest() { - var urlString = MediaPath.GetCropUrl(imageCropperValue: CropperJson1, cropAlias: "Thumb", useCropDimensions: true); - Assert.AreEqual(MediaPath + "?crop=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&cropmode=percentage&width=100&height=100", urlString); + var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, cropAlias: "Thumb", useCropDimensions: true); + Assert.AreEqual(MediaPath + "?c=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&w=100&h=100", urlString); } /// @@ -119,29 +119,29 @@ namespace Umbraco.Tests.PropertyEditors [Test] public void GetCropUrl_CropAliasIgnoreWidthHeightTest() { - var urlString = MediaPath.GetCropUrl(imageCropperValue: CropperJson1, cropAlias: "Thumb", useCropDimensions: true, width: 50, height: 50); - Assert.AreEqual(MediaPath + "?crop=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&cropmode=percentage&width=100&height=100", urlString); + var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, cropAlias: "Thumb", useCropDimensions: true, width: 50, height: 50); + Assert.AreEqual(MediaPath + "?c=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&w=100&h=100", urlString); } [Test] public void GetCropUrl_WidthHeightTest() { - var urlString = MediaPath.GetCropUrl(imageCropperValue: CropperJson1, width: 200, height: 300); - Assert.AreEqual(MediaPath + "?center=0.80827067669172936,0.96&mode=crop&width=200&height=300", urlString); + var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, width: 200, height: 300); + Assert.AreEqual(MediaPath + "?f=0.80827067669172936x0.96&w=200&h=300", urlString); } [Test] public void GetCropUrl_FocalPointTest() { - var urlString = MediaPath.GetCropUrl(imageCropperValue: CropperJson1, cropAlias: "thumb", preferFocalPoint: true, useCropDimensions: true); - Assert.AreEqual(MediaPath + "?center=0.80827067669172936,0.96&mode=crop&width=100&height=100", urlString); + var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, cropAlias: "thumb", preferFocalPoint: true, useCropDimensions: true); + Assert.AreEqual(MediaPath + "?f=0.80827067669172936x0.96&w=100&h=100", urlString); } [Test] public void GetCropUrlFurtherOptionsTest() { - var urlString = MediaPath.GetCropUrl(imageCropperValue: CropperJson1, width: 200, height: 300, furtherOptions: "&filter=comic&roundedcorners=radius-26|bgcolor-fff"); - Assert.AreEqual(MediaPath + "?center=0.80827067669172936,0.96&mode=crop&width=200&height=300&filter=comic&roundedcorners=radius-26|bgcolor-fff", urlString); + var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, width: 200, height: 300, furtherOptions: "&filter=comic&roundedcorners=radius-26|bgcolor-fff"); + Assert.AreEqual(MediaPath + "?f=0.80827067669172936x0.96&w=200&h=300&filter=comic&roundedcorners=radius-26|bgcolor-fff", urlString); } /// @@ -150,7 +150,7 @@ namespace Umbraco.Tests.PropertyEditors [Test] public void GetCropUrlNullTest() { - var urlString = MediaPath.GetCropUrl(imageCropperValue: CropperJson1, cropAlias: "Banner", useCropDimensions: true); + var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, cropAlias: "Banner", useCropDimensions: true); Assert.AreEqual(null, urlString); } @@ -161,8 +161,8 @@ namespace Umbraco.Tests.PropertyEditors public void GetBaseCropUrlFromModelTest() { var cropDataSet = CropperJson1.DeserializeImageCropperValue(); - var urlString = cropDataSet.GetCropUrl("thumb"); - Assert.AreEqual("?crop=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&cropmode=percentage&width=100&height=100", urlString); + var urlString = cropDataSet.GetCropUrl("thumb", new TestImageUrlGenerator()); + Assert.AreEqual("?c=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&w=100&h=100", urlString); } /// @@ -171,8 +171,8 @@ namespace Umbraco.Tests.PropertyEditors [Test] public void GetCropUrl_CropAliasHeightRatioModeTest() { - var urlString = MediaPath.GetCropUrl(imageCropperValue: CropperJson1, cropAlias: "Thumb", useCropDimensions: true, ratioMode:ImageCropRatioMode.Height); - Assert.AreEqual(MediaPath + "?crop=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&cropmode=percentage&width=100&heightratio=1", urlString); + var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, cropAlias: "Thumb", useCropDimensions: true, ratioMode:ImageCropRatioMode.Height); + Assert.AreEqual(MediaPath + "?c=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&hr=1&w=100", urlString); } /// @@ -181,8 +181,8 @@ namespace Umbraco.Tests.PropertyEditors [Test] public void GetCropUrl_WidthHeightRatioModeTest() { - var urlString = MediaPath.GetCropUrl(imageCropperValue: CropperJson1, width: 300, height: 150, ratioMode:ImageCropRatioMode.Height); - Assert.AreEqual(MediaPath + "?center=0.80827067669172936,0.96&mode=crop&width=300&heightratio=0.5", urlString); + var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, width: 300, height: 150, ratioMode:ImageCropRatioMode.Height); + Assert.AreEqual(MediaPath + "?f=0.80827067669172936x0.96&hr=0.5&w=300", urlString); } /// @@ -191,8 +191,8 @@ namespace Umbraco.Tests.PropertyEditors [Test] public void GetCropUrl_HeightWidthRatioModeTest() { - var urlString = MediaPath.GetCropUrl(imageCropperValue: CropperJson1, width: 300, height: 150, ratioMode: ImageCropRatioMode.Width); - Assert.AreEqual(MediaPath + "?center=0.80827067669172936,0.96&mode=crop&height=150&widthratio=2", urlString); + var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, width: 300, height: 150, ratioMode: ImageCropRatioMode.Width); + Assert.AreEqual(MediaPath + "?f=0.80827067669172936x0.96&wr=2&h=150", urlString); } /// @@ -201,17 +201,17 @@ namespace Umbraco.Tests.PropertyEditors [Test] public void GetCropUrl_SpecifiedCropModeTest() { - var urlStringMin = MediaPath.GetCropUrl(imageCropperValue: CropperJson1, width: 300, height: 150, imageCropMode: ImageCropMode.Min); - var urlStringBoxPad = MediaPath.GetCropUrl(imageCropperValue: CropperJson1, width: 300, height: 150, imageCropMode: ImageCropMode.BoxPad); - var urlStringPad = MediaPath.GetCropUrl(imageCropperValue: CropperJson1, width: 300, height: 150, imageCropMode: ImageCropMode.Pad); - var urlString = MediaPath.GetCropUrl(imageCropperValue: CropperJson1, width: 300, height: 150, imageCropMode:ImageCropMode.Max); - var urlStringStretch = MediaPath.GetCropUrl(imageCropperValue: CropperJson1, width: 300, height: 150, imageCropMode: ImageCropMode.Stretch); + var urlStringMin = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, width: 300, height: 150, imageCropMode: ImageCropMode.Min); + var urlStringBoxPad = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, width: 300, height: 150, imageCropMode: ImageCropMode.BoxPad); + var urlStringPad = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, width: 300, height: 150, imageCropMode: ImageCropMode.Pad); + var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, width: 300, height: 150, imageCropMode:ImageCropMode.Max); + var urlStringStretch = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, width: 300, height: 150, imageCropMode: ImageCropMode.Stretch); - Assert.AreEqual(MediaPath + "?mode=min&width=300&height=150", urlStringMin); - Assert.AreEqual(MediaPath + "?mode=boxpad&width=300&height=150", urlStringBoxPad); - Assert.AreEqual(MediaPath + "?mode=pad&width=300&height=150", urlStringPad); - Assert.AreEqual(MediaPath + "?mode=max&width=300&height=150", urlString); - Assert.AreEqual(MediaPath + "?mode=stretch&width=300&height=150", urlStringStretch); + Assert.AreEqual(MediaPath + "?m=min&w=300&h=150", urlStringMin); + Assert.AreEqual(MediaPath + "?m=boxpad&w=300&h=150", urlStringBoxPad); + Assert.AreEqual(MediaPath + "?m=pad&w=300&h=150", urlStringPad); + Assert.AreEqual(MediaPath + "?m=max&w=300&h=150", urlString); + Assert.AreEqual(MediaPath + "?m=stretch&w=300&h=150", urlStringStretch); } /// @@ -220,8 +220,8 @@ namespace Umbraco.Tests.PropertyEditors [Test] public void GetCropUrl_UploadTypeTest() { - var urlString = MediaPath.GetCropUrl(width: 100, height: 270, imageCropMode: ImageCropMode.Crop, imageCropAnchor: ImageCropAnchor.Center); - Assert.AreEqual(MediaPath + "?mode=crop&anchor=center&width=100&height=270", urlString); + var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), width: 100, height: 270, imageCropMode: ImageCropMode.Crop, imageCropAnchor: ImageCropAnchor.Center); + Assert.AreEqual(MediaPath + "?m=crop&a=center&w=100&h=270", urlString); } /// @@ -232,8 +232,8 @@ namespace Umbraco.Tests.PropertyEditors { const string cropperJson = "{\"focalPoint\": {\"left\": 0.5,\"top\": 0.5},\"src\": \"/media/1005/img_0671.jpg\",\"crops\": [{\"alias\":\"thumb\",\"width\": 100,\"height\": 100,\"coordinates\": {\"x1\": 0.58729977382575338,\"y1\": 0.055768992440203169,\"x2\": 0,\"y2\": 0.32457553600198386}}]}"; - var urlString = MediaPath.GetCropUrl(imageCropperValue: cropperJson, width: 300, height: 150, preferFocalPoint:true); - Assert.AreEqual(MediaPath + "?anchor=center&mode=crop&width=300&height=150", urlString); + var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: cropperJson, width: 300, height: 150, preferFocalPoint:true); + Assert.AreEqual(MediaPath + "?m=defaultcrop&w=300&h=150", urlString); } /// @@ -244,8 +244,8 @@ namespace Umbraco.Tests.PropertyEditors { const string cropperJson = "{\"focalPoint\": {\"left\": 0.5,\"top\": 0.5},\"src\": \"/media/1005/img_0671.jpg\",\"crops\": [{\"alias\": \"home\",\"width\": 270,\"height\": 161}]}"; - var urlString = MediaPath.GetCropUrl(imageCropperValue: cropperJson, cropAlias: "home", width: 200); - Assert.AreEqual(MediaPath + "?anchor=center&mode=crop&heightratio=0.5962962962962962962962962963&width=200", urlString); + var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: cropperJson, cropAlias: "home", width: 200); + Assert.AreEqual(MediaPath + "?m=defaultcrop&hr=0.5962962962962962962962962963&w=200", urlString); } /// @@ -256,8 +256,8 @@ namespace Umbraco.Tests.PropertyEditors { const string cropperJson = "{\"focalPoint\": {\"left\": 0.4275,\"top\": 0.41},\"src\": \"/media/1005/img_0671.jpg\",\"crops\": [{\"alias\": \"home\",\"width\": 270,\"height\": 161}]}"; - var urlString = MediaPath.GetCropUrl(imageCropperValue: cropperJson, cropAlias: "home", width: 200); - Assert.AreEqual(MediaPath + "?center=0.41,0.4275&mode=crop&heightratio=0.5962962962962962962962962963&width=200", urlString); + var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: cropperJson, cropAlias: "home", width: 200); + Assert.AreEqual(MediaPath + "?f=0.41x0.4275&hr=0.5962962962962962962962962963&w=200", urlString); } /// @@ -268,8 +268,8 @@ namespace Umbraco.Tests.PropertyEditors { const string cropperJson = "{\"focalPoint\": {\"left\": 0.4275,\"top\": 0.41},\"src\": \"/media/1005/img_0671.jpg\",\"crops\": [{\"alias\": \"home\",\"width\": 270,\"height\": 161}]}"; - var urlString = MediaPath.GetCropUrl(imageCropperValue: cropperJson, cropAlias: "home", width: 200, useCropDimensions: true); - Assert.AreEqual(MediaPath + "?center=0.41,0.4275&mode=crop&width=270&height=161", urlString); + var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: cropperJson, cropAlias: "home", width: 200, useCropDimensions: true); + Assert.AreEqual(MediaPath + "?f=0.41x0.4275&w=270&h=161", urlString); } /// @@ -280,8 +280,8 @@ namespace Umbraco.Tests.PropertyEditors { const string cropperJson = "{\"focalPoint\": {\"left\": 0.5,\"top\": 0.5},\"src\": \"/media/1005/img_0671.jpg\",\"crops\": [{\"alias\": \"home\",\"width\": 270,\"height\": 161}]}"; - var urlString = MediaPath.GetCropUrl(imageCropperValue: cropperJson, cropAlias: "home", height: 200); - Assert.AreEqual(MediaPath + "?anchor=center&mode=crop&widthratio=1.6770186335403726708074534161&height=200", urlString); + var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: cropperJson, cropAlias: "home", height: 200); + Assert.AreEqual(MediaPath + "?m=defaultcrop&wr=1.6770186335403726708074534161&h=200", urlString); } /// @@ -292,8 +292,8 @@ namespace Umbraco.Tests.PropertyEditors { const string cropperJson = "{\"focalPoint\": {\"left\": 0.5,\"top\": 0.5},\"src\": \"/media/1005/img_0671.jpg\",\"crops\": [{\"alias\": \"home\",\"width\": 270,\"height\": 161}]}"; - var urlString = MediaPath.GetCropUrl(imageCropperValue: cropperJson, width: 200); - Assert.AreEqual(MediaPath + "?anchor=center&mode=crop&width=200", urlString); + var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: cropperJson, width: 200); + Assert.AreEqual(MediaPath + "?m=defaultcrop&w=200", urlString); } /// @@ -304,8 +304,8 @@ namespace Umbraco.Tests.PropertyEditors { const string cropperJson = "{\"focalPoint\": {\"left\": 0.5,\"top\": 0.5},\"src\": \"/media/1005/img_0671.jpg\",\"crops\": [{\"alias\": \"home\",\"width\": 270,\"height\": 161}]}"; - var urlString = MediaPath.GetCropUrl(imageCropperValue: cropperJson, height: 200); - Assert.AreEqual(MediaPath + "?anchor=center&mode=crop&height=200", urlString); + var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: cropperJson, height: 200); + Assert.AreEqual(MediaPath + "?m=defaultcrop&h=200", urlString); } /// @@ -316,8 +316,55 @@ namespace Umbraco.Tests.PropertyEditors { var cropperJson = "{\"focalPoint\": {\"left\": 0.5,\"top\": 0.5},\"src\": \"" + MediaPath + "\",\"crops\": [{\"alias\": \"home\",\"width\": 270,\"height\": 161}]}"; - var urlString = MediaPath.GetCropUrl(400, 400, cropperJson, imageCropMode: ImageCropMode.Pad, furtherOptions: "&bgcolor=fff"); - Assert.AreEqual(MediaPath + "?mode=pad&width=400&height=400&bgcolor=fff", urlString); + var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), 400, 400, cropperJson, imageCropMode: ImageCropMode.Pad, furtherOptions: "&bgcolor=fff"); + Assert.AreEqual(MediaPath + "?m=pad&w=400&h=400&bgcolor=fff", urlString); + } + + internal class TestImageUrlGenerator : IImageUrlGenerator + { + public string GetImageUrl(ImageUrlGenerationOptions options) + { + var imageProcessorUrl = new StringBuilder(options.ImageUrl ?? string.Empty); + + if (options.FocalPoint != null) + { + imageProcessorUrl.Append("?f="); + imageProcessorUrl.Append(options.FocalPoint.Top.ToString(CultureInfo.InvariantCulture)); + imageProcessorUrl.Append("x"); + imageProcessorUrl.Append(options.FocalPoint.Left.ToString(CultureInfo.InvariantCulture)); + } + else if (options.Crop != null) + { + imageProcessorUrl.Append("?c="); + imageProcessorUrl.Append(options.Crop.X1.ToString(CultureInfo.InvariantCulture)).Append(","); + imageProcessorUrl.Append(options.Crop.Y1.ToString(CultureInfo.InvariantCulture)).Append(","); + imageProcessorUrl.Append(options.Crop.X2.ToString(CultureInfo.InvariantCulture)).Append(","); + imageProcessorUrl.Append(options.Crop.Y2.ToString(CultureInfo.InvariantCulture)); + } + else if (options.DefaultCrop) + { + imageProcessorUrl.Append("?m=defaultcrop"); + } + else + { + imageProcessorUrl.Append("?m=" + options.ImageCropMode.ToString().ToLower()); + if (options.ImageCropAnchor != null)imageProcessorUrl.Append("&a=" + options.ImageCropAnchor.ToString().ToLower()); + } + + var hasFormat = options.FurtherOptions != null && options.FurtherOptions.InvariantContains("&f="); + if (options.Quality != null && hasFormat == false) imageProcessorUrl.Append("&q=" + options.Quality); + if (options.HeightRatio != null) imageProcessorUrl.Append("&hr=" + options.HeightRatio.Value.ToString(CultureInfo.InvariantCulture)); + if (options.WidthRatio != null) imageProcessorUrl.Append("&wr=" + options.WidthRatio.Value.ToString(CultureInfo.InvariantCulture)); + if (options.Width != null) imageProcessorUrl.Append("&w=" + options.Width); + if (options.Height != null) imageProcessorUrl.Append("&h=" + options.Height); + if (options.UpScale == false) imageProcessorUrl.Append("&u=no"); + if (options.AnimationProcessMode != null) imageProcessorUrl.Append("&apm=" + options.AnimationProcessMode); + if (options.FurtherOptions != null) imageProcessorUrl.Append(options.FurtherOptions); + if (options.Quality != null && hasFormat) imageProcessorUrl.Append("&q=" + options.Quality); + if (options.CacheBusterValue != null) imageProcessorUrl.Append("&r=").Append(options.CacheBusterValue); + + return imageProcessorUrl.ToString(); + } } } } diff --git a/src/Umbraco.Tests/Published/NestedContentTests.cs b/src/Umbraco.Tests/Published/NestedContentTests.cs index eea65568d4..adfb9d3b6b 100644 --- a/src/Umbraco.Tests/Published/NestedContentTests.cs +++ b/src/Umbraco.Tests/Published/NestedContentTests.cs @@ -11,6 +11,7 @@ using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; using Umbraco.Tests.PublishedContent; using Umbraco.Tests.TestHelpers; using Umbraco.Web; @@ -33,7 +34,7 @@ namespace Umbraco.Tests.Published var proflog = new ProfilingLogger(logger, profiler); PropertyEditorCollection editors = null; - var editor = new NestedContentPropertyEditor(logger, new Lazy(() => editors)); + var editor = new NestedContentPropertyEditor(logger, new Lazy(() => editors), Mock.Of(), Mock.Of()); editors = new PropertyEditorCollection(new DataEditorCollection(new DataEditor[] { editor })); var dataType1 = new DataType(editor) diff --git a/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs b/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs index 5d32606ee7..834d211994 100644 --- a/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs +++ b/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs @@ -1,9 +1,7 @@ using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Data; using System.Linq; -using System.Reflection; using Moq; using NUnit.Framework; using Umbraco.Core; @@ -27,7 +25,6 @@ using Umbraco.Web.Cache; using Umbraco.Web.PublishedCache; using Umbraco.Web.PublishedCache.NuCache; using Umbraco.Web.PublishedCache.NuCache.DataSource; -using Umbraco.Web.PublishedCache.NuCache.Snap; namespace Umbraco.Tests.PublishedContent { @@ -47,7 +44,7 @@ namespace Umbraco.Tests.PublishedContent _snapshotService?.Dispose(); } - private void Init(IEnumerable kits) + private void Init(Func> kits) { Current.Reset(); @@ -136,7 +133,7 @@ namespace Umbraco.Tests.PublishedContent _snapshotAccessor = new TestPublishedSnapshotAccessor(); // create a data source for NuCache - _source = new TestDataSource(kits); + _source = new TestDataSource(kits()); // at last, create the complete NuCache snapshot service! var options = new PublishedSnapshotServiceOptions { IgnoreLocalDb = true }; @@ -374,7 +371,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void EmptyTest() { - Init(Enumerable.Empty()); + Init(() => Enumerable.Empty()); var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); _snapshotAccessor.PublishedSnapshot = snapshot; @@ -386,7 +383,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void ChildrenTest() { - Init(GetInvariantKits()); + Init(GetInvariantKits); var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); _snapshotAccessor.PublishedSnapshot = snapshot; @@ -413,7 +410,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void ParentTest() { - Init(GetInvariantKits()); + Init(GetInvariantKits); var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); _snapshotAccessor.PublishedSnapshot = snapshot; @@ -439,7 +436,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void MoveToRootTest() { - Init(GetInvariantKits()); + Init(GetInvariantKits); // get snapshot var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); @@ -481,7 +478,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void MoveFromRootTest() { - Init(GetInvariantKits()); + Init(GetInvariantKits); // get snapshot var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); @@ -523,7 +520,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void ReOrderTest() { - Init(GetInvariantKits()); + Init(GetInvariantKits); // get snapshot var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); @@ -598,7 +595,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void MoveTest() { - Init(GetInvariantKits()); + Init(GetInvariantKits); // get snapshot var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); @@ -699,11 +696,61 @@ namespace Umbraco.Tests.PublishedContent Assert.AreEqual(1, snapshot.Content.GetById(7).Parent?.Id); } + [Test] + public void Clear_Branch_Locked() + { + // This test replicates an issue we saw here https://github.com/umbraco/Umbraco-CMS/pull/7907#issuecomment-610259393 + // The data was sent to me and this replicates it's structure + + var paths = new Dictionary { { -1, "-1" } }; + + Init(() => new List + { + CreateInvariantKit(1, -1, 1, paths), // first level + CreateInvariantKit(2, 1, 1, paths), // second level + CreateInvariantKit(3, 2, 1, paths), // third level + + CreateInvariantKit(4, 3, 1, paths), // fourth level (we'll copy this one to the same level) + + CreateInvariantKit(5, 4, 1, paths), // 6th level + + CreateInvariantKit(6, 5, 2, paths), // 7th level + CreateInvariantKit(7, 5, 3, paths), + CreateInvariantKit(8, 5, 4, paths), + CreateInvariantKit(9, 5, 5, paths), + CreateInvariantKit(10, 5, 6, paths) + }); + + // get snapshot + var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); + _snapshotAccessor.PublishedSnapshot = snapshot; + + var snapshotService = (PublishedSnapshotService)_snapshotService; + var contentStore = snapshotService.GetContentStore(); + //This will set a flag to force creating a new Gen next time the store is locked (i.e. In Notify) + contentStore.CreateSnapshot(); + + // notify - which ensures there are 2 generations in the cache meaning each LinkedNode has a Next value. + _snapshotService.Notify(new[] + { + new ContentCacheRefresher.JsonPayload(4, Guid.Empty, TreeChangeTypes.RefreshBranch) + }, out _, out _); + + // refresh the branch again, this used to show the issue where a null ref exception would occur + // because in the ClearBranchLocked logic, when SetValueLocked was called within a recursive call + // to a child, we null out the .Value of the LinkedNode within the while loop because we didn't capture + // this value before recursing. + Assert.DoesNotThrow(() => + _snapshotService.Notify(new[] + { + new ContentCacheRefresher.JsonPayload(4, Guid.Empty, TreeChangeTypes.RefreshBranch) + }, out _, out _)); + } + [Test] public void NestedVariationChildrenTest() { - var mixedKits = GetNestedVariantKits(); - Init(mixedKits); + Init(GetNestedVariantKits); var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); _snapshotAccessor.PublishedSnapshot = snapshot; @@ -792,7 +839,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void VariantChildrenTest() { - Init(GetVariantKits()); + Init(GetVariantKits); var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); _snapshotAccessor.PublishedSnapshot = snapshot; @@ -864,7 +911,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void RemoveTest() { - Init(GetInvariantKits()); + Init(GetInvariantKits); var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); _snapshotAccessor.PublishedSnapshot = snapshot; @@ -913,11 +960,19 @@ namespace Umbraco.Tests.PublishedContent [Test] public void UpdateTest() { - Init(GetInvariantKits()); + Init(GetInvariantKits); var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); _snapshotAccessor.PublishedSnapshot = snapshot; + var snapshotService = (PublishedSnapshotService)_snapshotService; + var contentStore = snapshotService.GetContentStore(); + + var parentNodes = contentStore.Test.GetValues(1); + var parentNode = parentNodes[0]; + AssertLinkedNode(parentNode.contentNode, -1, -1, 2, 4, 6); + Assert.AreEqual(1, parentNode.gen); + var documents = snapshot.Content.GetAtRoot().ToArray(); AssertDocuments(documents, "N1", "N2", "N3"); @@ -934,6 +989,15 @@ namespace Umbraco.Tests.PublishedContent new ContentCacheRefresher.JsonPayload(2, Guid.Empty, TreeChangeTypes.RefreshNode), }, out _, out _); + parentNodes = contentStore.Test.GetValues(1); + Assert.AreEqual(2, parentNodes.Length); + parentNode = parentNodes[1]; // get the first gen + AssertLinkedNode(parentNode.contentNode, -1, -1, 2, 4, 6); // the structure should have remained the same + Assert.AreEqual(1, parentNode.gen); + parentNode = parentNodes[0]; // get the latest gen + AssertLinkedNode(parentNode.contentNode, -1, -1, 2, 4, 6); // the structure should have remained the same + Assert.AreEqual(2, parentNode.gen); + documents = snapshot.Content.GetAtRoot().ToArray(); AssertDocuments(documents, "N1", "N2", "N3"); @@ -942,12 +1006,14 @@ namespace Umbraco.Tests.PublishedContent documents = snapshot.Content.GetById(2).Children().ToArray(); AssertDocuments(documents, "N9", "N8", "N7"); + + } [Test] public void AtRootTest() { - Init(GetVariantWithDraftKits()); + Init(GetVariantWithDraftKits); var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); _snapshotAccessor.PublishedSnapshot = snapshot; @@ -976,7 +1042,7 @@ namespace Umbraco.Tests.PublishedContent yield return CreateInvariantKit(2, 1, 1, paths); } - Init(GetKits()); + Init(GetKits); var snapshotService = (PublishedSnapshotService)_snapshotService; var contentStore = snapshotService.GetContentStore(); @@ -1015,7 +1081,7 @@ namespace Umbraco.Tests.PublishedContent yield return CreateInvariantKit(4, 1, 3, paths); } - Init(GetKits()); + Init(GetKits); var snapshotService = (PublishedSnapshotService)_snapshotService; var contentStore = snapshotService.GetContentStore(); @@ -1095,7 +1161,7 @@ namespace Umbraco.Tests.PublishedContent yield return CreateInvariantKit(40, 1, 3, paths); } - Init(GetKits()); + Init(GetKits); var snapshotService = (PublishedSnapshotService)_snapshotService; var contentStore = snapshotService.GetContentStore(); @@ -1114,7 +1180,7 @@ namespace Umbraco.Tests.PublishedContent _snapshotService.Notify(new[] { - new ContentCacheRefresher.JsonPayload(1, Guid.Empty, TreeChangeTypes.RefreshNode) + new ContentCacheRefresher.JsonPayload(1, Guid.Empty, TreeChangeTypes.RefreshNode) }, out _, out _); Assert.AreEqual(2, contentStore.Test.LiveGen); @@ -1134,7 +1200,7 @@ namespace Umbraco.Tests.PublishedContent /// 2) Save and publish it /// 3) Publish it with descendants /// 4) Repeat steps 2 and 3 - /// + /// /// Which has caused an exception. To replicate this test: /// 1) RefreshBranch with kits for a branch where the top most node is unpublished /// 2) RefreshBranch with kits for the branch where the top most node is published @@ -1162,12 +1228,12 @@ namespace Umbraco.Tests.PublishedContent //children of 1 yield return CreateInvariantKit(20, 1, 1, paths); - yield return CreateInvariantKit(30, 1, 2, paths); + yield return CreateInvariantKit(30, 1, 2, paths); yield return CreateInvariantKit(40, 1, 3, paths); } //init with all published - Init(GetKits()); + Init(GetKits); var snapshotService = (PublishedSnapshotService)_snapshotService; var contentStore = snapshotService.GetContentStore(); @@ -1184,7 +1250,7 @@ namespace Umbraco.Tests.PublishedContent //Change the root publish flag var kit = rootKit.Clone(); kit.DraftData = published ? null : kit.PublishedData; - kit.PublishedData = published? kit.PublishedData : null; + kit.PublishedData = published ? kit.PublishedData : null; _source.Kits[1] = kit; _snapshotService.Notify(new[] @@ -1199,12 +1265,12 @@ namespace Umbraco.Tests.PublishedContent var (gen, contentNode) = contentStore.Test.GetValues(1)[0]; Assert.AreEqual(assertGen, gen); //even when unpublishing/re-publishing/etc... the linked list is always maintained - AssertLinkedNode(contentNode, 100, 2, 3, 20, 40); + AssertLinkedNode(contentNode, 100, 2, 3, 20, 40); } //unpublish the root ChangePublishFlagOfRoot(false, 2, TreeChangeTypes.RefreshBranch); - + //publish the root (since it's not published, it will cause a RefreshBranch) ChangePublishFlagOfRoot(true, 3, TreeChangeTypes.RefreshBranch); @@ -1237,7 +1303,7 @@ namespace Umbraco.Tests.PublishedContent yield return CreateInvariantKit(4, 1, 3, paths); } - Init(GetKits()); + Init(GetKits); var snapshotService = (PublishedSnapshotService)_snapshotService; var contentStore = snapshotService.GetContentStore(); @@ -1293,6 +1359,17 @@ namespace Umbraco.Tests.PublishedContent AssertLinkedNode(child3.contentNode, 1, 3, -1, -1, -1); } + [Test] + public void MultipleCacheIteration() + { + //see https://github.com/umbraco/Umbraco-CMS/issues/7798 + Init(GetInvariantKits); + var snapshot = this._snapshotService.CreatePublishedSnapshot(previewToken: null); + _snapshotAccessor.PublishedSnapshot = snapshot; + + var items = snapshot.Content.GetByXPath("/root/itype"); + Assert.AreEqual(items.Count(), items.Count()); + } private void AssertLinkedNode(ContentNode node, int parent, int prevSibling, int nextSibling, int firstChild, int lastChild) { Assert.AreEqual(parent, node.ParentContentId); diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs index f1e2bf20d6..59791fc645 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs @@ -11,6 +11,8 @@ using Umbraco.Core.Models; using Umbraco.Web.PropertyEditors; using Umbraco.Core.Services; using Umbraco.Web; +using Umbraco.Web.Templates; +using Umbraco.Web.Models; namespace Umbraco.Tests.PublishedContent { @@ -38,9 +40,14 @@ namespace Umbraco.Tests.PublishedContent base.Initialize(); var converters = Factory.GetInstance(); + var umbracoContextAccessor = Mock.Of(); + var logger = Mock.Of(); + var imageSourceParser = new HtmlImageSourceParser(umbracoContextAccessor); + var pastedImages = new RichTextEditorPastedImages(umbracoContextAccessor, logger, Mock.Of(), Mock.Of()); + var localLinkParser = new HtmlLocalLinkParser(umbracoContextAccessor); var dataTypeService = new TestObjects.TestDataTypeService( - new DataType(new RichTextPropertyEditor(Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of())) { Id = 1 }); + new DataType(new RichTextPropertyEditor(logger, umbracoContextAccessor, imageSourceParser, localLinkParser, pastedImages, Mock.Of())) { Id = 1 }); var publishedContentTypeFactory = new PublishedContentTypeFactory(Mock.Of(), converters, dataTypeService); diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs index 6ef632bf90..998fc92380 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs @@ -21,6 +21,8 @@ using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing; using Umbraco.Web.Models.PublishedContent; using Umbraco.Web.PropertyEditors; +using Umbraco.Web.Templates; +using Umbraco.Web.Models; namespace Umbraco.Tests.PublishedContent { @@ -45,11 +47,14 @@ namespace Umbraco.Tests.PublishedContent var mediaService = Mock.Of(); var contentTypeBaseServiceProvider = Mock.Of(); var umbracoContextAccessor = Mock.Of(); + var imageSourceParser = new HtmlImageSourceParser(umbracoContextAccessor); + var pastedImages = new RichTextEditorPastedImages(umbracoContextAccessor, logger, mediaService, contentTypeBaseServiceProvider); + var linkParser = new HtmlLocalLinkParser(umbracoContextAccessor); var dataTypeService = new TestObjects.TestDataTypeService( new DataType(new VoidEditor(logger)) { Id = 1 }, new DataType(new TrueFalsePropertyEditor(logger)) { Id = 1001 }, - new DataType(new RichTextPropertyEditor(logger, mediaService, contentTypeBaseServiceProvider, umbracoContextAccessor)) { Id = 1002 }, + new DataType(new RichTextPropertyEditor(logger, umbracoContextAccessor, imageSourceParser, linkParser, pastedImages, Mock.Of())) { Id = 1002 }, new DataType(new IntegerPropertyEditor(logger)) { Id = 1003 }, new DataType(new TextboxPropertyEditor(logger)) { Id = 1004 }, new DataType(new MediaPickerPropertyEditor(logger)) { Id = 1005 }); diff --git a/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs b/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs index 860b9b9179..a7b6d3d18a 100644 --- a/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs +++ b/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs @@ -199,6 +199,7 @@ namespace Umbraco.Tests.PublishedContent public DateTime UpdateDate { get; set; } public Guid Version { get; set; } public int Level { get; set; } + [Obsolete("Use the Url() extension instead")] public string Url { get; set; } public PublishedItemType ItemType => PublishedItemType.Content; diff --git a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs index 7ab329b9a0..9157a76773 100644 --- a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs +++ b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs @@ -97,6 +97,7 @@ namespace Umbraco.Tests.Runtimes composition.Register(Lifetime.Singleton); composition.Register(Lifetime.Singleton); composition.Register(_ => Mock.Of(), Lifetime.Singleton); + composition.Register(_ => Mock.Of(), Lifetime.Singleton); composition.RegisterUnique(f => new DistributedCache()); composition.WithCollectionBuilder().Append(); composition.RegisterUnique(); diff --git a/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs b/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs index ef80672baf..9f4304ebee 100644 --- a/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs +++ b/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs @@ -14,6 +14,7 @@ using Umbraco.Core.Models; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.Repositories.Implement; +using Umbraco.Core.PropertyEditors; using Umbraco.Core.Scoping; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Entities; @@ -39,6 +40,23 @@ namespace Umbraco.Tests.Services Composition.Register(); } + private DocumentRepository CreateDocumentRepository(IScopeProvider provider) + { + var accessor = (IScopeAccessor)provider; + var tRepository = new TemplateRepository(accessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock()); + var tagRepo = new TagRepository(accessor, AppCaches.Disabled, Logger); + var commonRepository = new ContentTypeCommonRepository(accessor, tRepository, AppCaches); + var languageRepository = new LanguageRepository(accessor, AppCaches.Disabled, Logger); + var ctRepository = new ContentTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository, languageRepository); + var relationTypeRepository = new RelationTypeRepository(accessor, AppCaches.Disabled, Logger); + var entityRepository = new EntityRepository(accessor); + var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository, entityRepository); + var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); + var dataValueReferences = new DataValueReferenceFactoryCollection(Enumerable.Empty()); + var repository = new DocumentRepository(accessor, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferences); + return repository; + } + [Test] public void Profiler() { @@ -163,12 +181,7 @@ namespace Umbraco.Tests.Services var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var tRepository = new TemplateRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock()); - var tagRepo = new TagRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger); - var commonRepository = new ContentTypeCommonRepository((IScopeAccessor)provider, tRepository, AppCaches); - var languageRepository = new LanguageRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger); - var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository, languageRepository); - var repository = new DocumentRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository); + var repository = CreateDocumentRepository(provider); // Act Stopwatch watch = Stopwatch.StartNew(); @@ -197,12 +210,7 @@ namespace Umbraco.Tests.Services var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var tRepository = new TemplateRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock()); - var tagRepo = new TagRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger); - var commonRepository = new ContentTypeCommonRepository((IScopeAccessor)provider, tRepository, AppCaches); - var languageRepository = new LanguageRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger); - var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository, languageRepository); - var repository = new DocumentRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository); + var repository = CreateDocumentRepository(provider); // Act Stopwatch watch = Stopwatch.StartNew(); @@ -229,12 +237,7 @@ namespace Umbraco.Tests.Services var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var tRepository = new TemplateRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock()); - var tagRepo = new TagRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger); - var commonRepository = new ContentTypeCommonRepository((IScopeAccessor) provider, tRepository, AppCaches); - var languageRepository = new LanguageRepository((IScopeAccessor)provider, AppCaches.Disabled, Logger); - var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository, languageRepository); - var repository = new DocumentRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository); + var repository = CreateDocumentRepository(provider); // Act var contents = repository.GetMany(); @@ -264,12 +267,7 @@ namespace Umbraco.Tests.Services var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var tRepository = new TemplateRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock()); - var tagRepo = new TagRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger); - var commonRepository = new ContentTypeCommonRepository((IScopeAccessor)provider, tRepository, AppCaches); - var languageRepository = new LanguageRepository((IScopeAccessor)provider, AppCaches.Disabled, Logger); - var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository, languageRepository); - var repository = new DocumentRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository); + var repository = CreateDocumentRepository(provider); // Act var contents = repository.GetMany(); diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index e26e764cd1..553d1b97ba 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -448,6 +448,47 @@ namespace Umbraco.Tests.Services Assert.That(content.HasIdentity, Is.False); } + [Test] + public void Automatically_Track_Relations() + { + var mt = MockedContentTypes.CreateSimpleMediaType("testMediaType", "Test Media Type"); + ServiceContext.MediaTypeService.Save(mt); + var m1 = MockedMedia.CreateSimpleMedia(mt, "hello 1", -1); + var m2 = MockedMedia.CreateSimpleMedia(mt, "hello 1", -1); + ServiceContext.MediaService.Save(m1); + ServiceContext.MediaService.Save(m2); + + var ct = MockedContentTypes.CreateTextPageContentType("richTextTest"); + ct.AllowedTemplates = Enumerable.Empty(); + + ServiceContext.ContentTypeService.Save(ct); + + var c1 = MockedContent.CreateTextpageContent(ct, "my content 1", -1); + ServiceContext.ContentService.Save(c1); + + var c2 = MockedContent.CreateTextpageContent(ct, "my content 2", -1); + + //'bodyText' is a property with a RTE property editor which we knows tracks relations + c2.Properties["bodyText"].SetValue(@"

+ +

+

+

+ hello +

"); + + ServiceContext.ContentService.Save(c2); + + var relations = ServiceContext.RelationService.GetByParentId(c2.Id).ToList(); + Assert.AreEqual(3, relations.Count); + Assert.AreEqual(Constants.Conventions.RelationTypes.RelatedMediaAlias, relations[0].RelationType.Alias); + Assert.AreEqual(m1.Id, relations[0].ChildId); + Assert.AreEqual(Constants.Conventions.RelationTypes.RelatedMediaAlias, relations[1].RelationType.Alias); + Assert.AreEqual(m2.Id, relations[1].ChildId); + Assert.AreEqual(Constants.Conventions.RelationTypes.RelatedDocumentAlias, relations[2].RelationType.Alias); + Assert.AreEqual(c1.Id, relations[2].ChildId); + } + [Test] public void Can_Create_Content_Without_Explicitly_Set_User() { @@ -1770,7 +1811,7 @@ namespace Umbraco.Tests.Services ServiceContext.ContentService.Save(content2, Constants.Security.SuperUserId); Assert.IsTrue(ServiceContext.ContentService.SaveAndPublish(content2, userId: 0).Success); - var editorGroup = ServiceContext.UserService.GetUserGroupByAlias("editor"); + var editorGroup = ServiceContext.UserService.GetUserGroupByAlias(Constants.Security.EditorGroupAlias); editorGroup.StartContentId = content1.Id; ServiceContext.UserService.Save(editorGroup); @@ -1778,7 +1819,7 @@ namespace Umbraco.Tests.Services admin.StartContentIds = new[] {content1.Id}; ServiceContext.UserService.Save(admin); - ServiceContext.RelationService.Save(new RelationType(Constants.ObjectTypes.Document, Constants.ObjectTypes.Document, "test")); + ServiceContext.RelationService.Save(new RelationType("test", "test", false, Constants.ObjectTypes.Document, Constants.ObjectTypes.Document)); Assert.IsNotNull(ServiceContext.RelationService.Relate(content1, content2, "test")); ServiceContext.PublicAccessService.Save(new PublicAccessEntry(content1, content2, content2, new List @@ -1849,6 +1890,49 @@ namespace Umbraco.Tests.Services //Assert.AreNotEqual(content.Name, copy.Name); } + [Test] + public void Can_Copy_And_Modify_Content_With_Events() + { + // see https://github.com/umbraco/Umbraco-CMS/issues/5513 + + TypedEventHandler> copying = (sender, args) => + { + args.Copy.SetValue("title", "1"); + args.Original.SetValue("title", "2"); + }; + + TypedEventHandler> copied = (sender, args) => + { + var copyVal = args.Copy.GetValue("title"); + var origVal = args.Original.GetValue("title"); + + Assert.AreEqual("1", copyVal); + Assert.AreEqual("2", origVal); + }; + + try + { + var contentService = ServiceContext.ContentService; + + ContentService.Copying += copying; + ContentService.Copied += copied; + + var contentType = MockedContentTypes.CreateSimpleContentType(); + ServiceContext.ContentTypeService.Save(contentType); + var content = MockedContent.CreateSimpleContent(contentType); + content.SetValue("title", "New Value"); + contentService.Save(content); + + var copy = contentService.Copy(content, content.ParentId, false, Constants.Security.SuperUserId); + Assert.AreEqual("1", copy.GetValue("title")); + } + finally + { + ContentService.Copying -= copying; + ContentService.Copied -= copied; + } + } + [Test] public void Can_Copy_Recursive() { @@ -3166,7 +3250,12 @@ namespace Umbraco.Tests.Services var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches); var languageRepository = new LanguageRepository(accessor, AppCaches.Disabled, Logger); contentTypeRepository = new ContentTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository, languageRepository); - var repository = new DocumentRepository(accessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository); + var relationTypeRepository = new RelationTypeRepository(accessor, AppCaches.Disabled, Logger); + var entityRepository = new EntityRepository(accessor); + var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository, entityRepository); + var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); + var dataValueReferences = new DataValueReferenceFactoryCollection(Enumerable.Empty()); + var repository = new DocumentRepository(accessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferences); return repository; } diff --git a/src/Umbraco.Tests/Services/MemberServiceTests.cs b/src/Umbraco.Tests/Services/MemberServiceTests.cs index 57d6b67af8..1385bbe0b3 100644 --- a/src/Umbraco.Tests/Services/MemberServiceTests.cs +++ b/src/Umbraco.Tests/Services/MemberServiceTests.cs @@ -197,7 +197,17 @@ namespace Umbraco.Tests.Services Assert.AreEqual(3, found.Count()); } + [Test] + public void Can_Get_All_Roles_IDs() + { + ServiceContext.MemberService.AddRole("MyTestRole1"); + ServiceContext.MemberService.AddRole("MyTestRole2"); + ServiceContext.MemberService.AddRole("MyTestRole3"); + var found = ServiceContext.MemberService.GetAllRolesIds(); + + Assert.AreEqual(3, found.Count()); + } [Test] public void Can_Get_All_Roles_By_Member_Id() { @@ -216,7 +226,24 @@ namespace Umbraco.Tests.Services Assert.AreEqual(2, memberRoles.Count()); } + [Test] + public void Can_Get_All_Roles_Ids_By_Member_Id() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + ServiceContext.MemberTypeService.Save(memberType); + IMember member = MockedMember.CreateSimpleMember(memberType, "test", "test@test.com", "pass", "test"); + ServiceContext.MemberService.Save(member); + ServiceContext.MemberService.AddRole("MyTestRole1"); + ServiceContext.MemberService.AddRole("MyTestRole2"); + ServiceContext.MemberService.AddRole("MyTestRole3"); + ServiceContext.MemberService.AssignRoles(new[] { member.Id }, new[] { "MyTestRole1", "MyTestRole2" }); + + var memberRoles = ServiceContext.MemberService.GetAllRolesIds(member.Id); + + Assert.AreEqual(2, memberRoles.Count()); + + } [Test] public void Can_Get_All_Roles_By_Member_Username() { diff --git a/src/Umbraco.Tests/Services/RelationServiceTests.cs b/src/Umbraco.Tests/Services/RelationServiceTests.cs index cfef50a330..06de405cec 100644 --- a/src/Umbraco.Tests/Services/RelationServiceTests.cs +++ b/src/Umbraco.Tests/Services/RelationServiceTests.cs @@ -1,27 +1,247 @@ using System; +using System.Collections.Generic; +using System.Linq; using System.Threading; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Tests.TestHelpers; +using Umbraco.Tests.TestHelpers.Entities; using Umbraco.Tests.Testing; namespace Umbraco.Tests.Services { [TestFixture] [Apartment(ApartmentState.STA)] - [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerFixture)] + [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] public class RelationServiceTests : TestWithSomeContentBase { + + [Test] + public void Get_Paged_Relations_By_Relation_Type() + { + //Create content + var createdContent = new List(); + var contentType = MockedContentTypes.CreateBasicContentType("blah"); + ServiceContext.ContentTypeService.Save(contentType); + for (int i = 0; i < 10; i++) + { + var c1 = MockedContent.CreateBasicContent(contentType); + ServiceContext.ContentService.Save(c1); + createdContent.Add(c1); + } + + //Create media + var createdMedia = new List(); + var imageType = MockedContentTypes.CreateImageMediaType("myImage"); + ServiceContext.MediaTypeService.Save(imageType); + for (int i = 0; i < 10; i++) + { + var c1 = MockedMedia.CreateMediaImage(imageType, -1); + ServiceContext.MediaService.Save(c1); + createdMedia.Add(c1); + } + + var relType = ServiceContext.RelationService.GetRelationTypeByAlias(Constants.Conventions.RelationTypes.RelatedMediaAlias); + + // Relate content to media + foreach (var content in createdContent) + foreach (var media in createdMedia) + ServiceContext.RelationService.Relate(content.Id, media.Id, relType); + + var paged = ServiceContext.RelationService.GetPagedByRelationTypeId(relType.Id, 0, 51, out var totalRecs).ToList(); + + Assert.AreEqual(100, totalRecs); + Assert.AreEqual(51, paged.Count); + + //next page + paged.AddRange(ServiceContext.RelationService.GetPagedByRelationTypeId(relType.Id, 1, 51, out totalRecs)); + + Assert.AreEqual(100, totalRecs); + Assert.AreEqual(100, paged.Count); + + Assert.IsTrue(createdContent.Select(x => x.Id).ContainsAll(paged.Select(x => x.ParentId))); + Assert.IsTrue(createdMedia.Select(x => x.Id).ContainsAll(paged.Select(x => x.ChildId))); + } + + [Test] + public void Return_List_Of_Content_Items_Where_Media_Item_Referenced() + { + var mt = MockedContentTypes.CreateSimpleMediaType("testMediaType", "Test Media Type"); + ServiceContext.MediaTypeService.Save(mt); + var m1 = MockedMedia.CreateSimpleMedia(mt, "hello 1", -1); + ServiceContext.MediaService.Save(m1); + + var ct = MockedContentTypes.CreateTextPageContentType("richTextTest"); + ct.AllowedTemplates = Enumerable.Empty(); + ServiceContext.ContentTypeService.Save(ct); + + void createContentWithMediaRefs() + { + var content = MockedContent.CreateTextpageContent(ct, "my content 2", -1); + //'bodyText' is a property with a RTE property editor which we knows automatically tracks relations + content.Properties["bodyText"].SetValue(@"

+ +

"); + ServiceContext.ContentService.Save(content); + } + + for (var i = 0; i < 6; i++) + createContentWithMediaRefs(); //create 6 content items referencing the same media + + var relations = ServiceContext.RelationService.GetByChildId(m1.Id, Constants.Conventions.RelationTypes.RelatedMediaAlias).ToList(); + Assert.AreEqual(6, relations.Count); + + var entities = ServiceContext.RelationService.GetParentEntitiesFromRelations(relations).ToList(); + Assert.AreEqual(6, entities.Count); + } + [Test] public void Can_Create_RelationType_Without_Name() { var rs = ServiceContext.RelationService; - var rt = new RelationType(Constants.ObjectTypes.Document, Constants.ObjectTypes.Document, "repeatedEventOccurence"); + IRelationType rt = new RelationType("Test", "repeatedEventOccurence", false, Constants.ObjectTypes.Document, Constants.ObjectTypes.Media); Assert.DoesNotThrow(() => rs.Save(rt)); - Assert.AreEqual(rt.Name, "repeatedEventOccurence"); + //re-get + rt = ServiceContext.RelationService.GetRelationTypeById(rt.Id); + + Assert.AreEqual("Test", rt.Name); + Assert.AreEqual("repeatedEventOccurence", rt.Alias); + Assert.AreEqual(false, rt.IsBidirectional); + Assert.AreEqual(Constants.ObjectTypes.Document, rt.ParentObjectType.Value); + Assert.AreEqual(Constants.ObjectTypes.Media, rt.ChildObjectType.Value); } + + [Test] + public void Create_Relation_Type_Without_Object_Types() + { + var rs = ServiceContext.RelationService; + IRelationType rt = new RelationType("repeatedEventOccurence", "repeatedEventOccurence", false, null, null); + + Assert.DoesNotThrow(() => rs.Save(rt)); + + //re-get + rt = ServiceContext.RelationService.GetRelationTypeById(rt.Id); + + Assert.IsNull(rt.ChildObjectType); + Assert.IsNull(rt.ParentObjectType); + } + + [Test] + public void Relation_Returns_Parent_Child_Object_Types_When_Creating() + { + var r = CreateAndSaveRelation("Test", "test"); + + Assert.AreEqual(Constants.ObjectTypes.Document, r.ParentObjectType); + Assert.AreEqual(Constants.ObjectTypes.Media, r.ChildObjectType); + } + + [Test] + public void Relation_Returns_Parent_Child_Object_Types_When_Getting() + { + var r = CreateAndSaveRelation("Test", "test"); + + // re-get + r = ServiceContext.RelationService.GetById(r.Id); + + Assert.AreEqual(Constants.ObjectTypes.Document, r.ParentObjectType); + Assert.AreEqual(Constants.ObjectTypes.Media, r.ChildObjectType); + } + + [Test] + public void Insert_Bulk_Relations() + { + var rs = ServiceContext.RelationService; + + var newRelations = CreateRelations(10); + + Assert.IsTrue(newRelations.All(x => !x.HasIdentity)); + + ServiceContext.RelationService.Save(newRelations); + + Assert.IsTrue(newRelations.All(x => x.HasIdentity)); + } + + [Test] + public void Update_Bulk_Relations() + { + var rs = ServiceContext.RelationService; + + var date = DateTime.Now.AddDays(-10); + var newRelations = CreateRelations(10); + foreach (var r in newRelations) + { + r.CreateDate = date; + r.UpdateDate = date; + } + + //insert + ServiceContext.RelationService.Save(newRelations); + Assert.IsTrue(newRelations.All(x => x.UpdateDate == date)); + + var newDate = DateTime.Now.AddDays(-5); + foreach (var r in newRelations) + r.UpdateDate = newDate; + + //update + ServiceContext.RelationService.Save(newRelations); + Assert.IsTrue(newRelations.All(x => x.UpdateDate == newDate)); + } + + private IRelation CreateAndSaveRelation(string name, string alias) + { + var rs = ServiceContext.RelationService; + var rt = new RelationType(name, alias, false, null, null); + rs.Save(rt); + + var ct = MockedContentTypes.CreateBasicContentType(); + ServiceContext.ContentTypeService.Save(ct); + + var mt = MockedContentTypes.CreateImageMediaType("img"); + ServiceContext.MediaTypeService.Save(mt); + + var c1 = MockedContent.CreateBasicContent(ct); + var c2 = MockedMedia.CreateMediaImage(mt, -1); + ServiceContext.ContentService.Save(c1); + ServiceContext.MediaService.Save(c2); + + var r = new Relation(c1.Id, c2.Id, rt); + ServiceContext.RelationService.Save(r); + + return r; + } + + /// + /// Creates a bunch of content/media items return relation objects for them (unsaved) + /// + /// + /// + private IEnumerable CreateRelations(int count) + { + var rs = ServiceContext.RelationService; + var rtName = Guid.NewGuid().ToString(); + var rt = new RelationType(rtName, rtName, false, null, null); + rs.Save(rt); + + var ct = MockedContentTypes.CreateBasicContentType(); + ServiceContext.ContentTypeService.Save(ct); + + var mt = MockedContentTypes.CreateImageMediaType("img"); + ServiceContext.MediaTypeService.Save(mt); + + return Enumerable.Range(1, count).Select(index => + { + var c1 = MockedContent.CreateBasicContent(ct); + var c2 = MockedMedia.CreateMediaImage(mt, -1); + ServiceContext.ContentService.Save(c1); + ServiceContext.MediaService.Save(c2); + + return new Relation(c1.Id, c2.Id, rt); + }).ToList(); + } + + //TODO: Create a relation for entities of the wrong Entity Type (GUID) based on the Relation Type's defined parent/child object types } } diff --git a/src/Umbraco.Tests/Services/UserServiceTests.cs b/src/Umbraco.Tests/Services/UserServiceTests.cs index a96385a923..016085c352 100644 --- a/src/Umbraco.Tests/Services/UserServiceTests.cs +++ b/src/Umbraco.Tests/Services/UserServiceTests.cs @@ -924,6 +924,24 @@ namespace Umbraco.Tests.Services Assert.AreEqual(user.Id, profile.Id); } + [Test] + public void Get_By_Profile_Id_Must_return_null_if_user_not_exists() + { + var profile = ServiceContext.UserService.GetProfileById(42); + + // Assert + Assert.IsNull(profile); + } + + [Test] + public void GetProfilesById_Must_empty_if_users_not_exists() + { + var profiles = ServiceContext.UserService.GetProfilesById(42); + + // Assert + CollectionAssert.IsEmpty(profiles); + } + [Test] public void Get_User_By_Username() { diff --git a/src/Umbraco.Tests/Strings/StringExtensionsTests.cs b/src/Umbraco.Tests/Strings/StringExtensionsTests.cs index 4c365d733f..39ffeed205 100644 --- a/src/Umbraco.Tests/Strings/StringExtensionsTests.cs +++ b/src/Umbraco.Tests/Strings/StringExtensionsTests.cs @@ -70,6 +70,24 @@ namespace Umbraco.Tests.Strings Assert.AreEqual(stripped, result); } + [TestCase("../wtf.js?x=wtf", ".js")] + [TestCase(".htaccess", ".htaccess")] + [TestCase("path/to/file/image.png", ".png")] + [TestCase("c:\\abc\\def\\ghi.jkl", ".jkl")] + [TestCase("/root/folder.name/file.ext", ".ext")] + [TestCase("http://www.domain.com/folder/name/file.txt", ".txt")] + [TestCase("i/don't\\have\\an/extension", "")] + [TestCase("https://some.where/path/to/file.ext?query=this&more=that", ".ext")] + [TestCase("double_query.string/file.ext?query=abc?something.else", ".ext")] + [TestCase("test.tar.gz", ".gz")] + [TestCase("wierd.file,but._legal", "._legal")] + [TestCase("one_char.x", ".x")] + public void Get_File_Extension(string input, string result) + { + var extension = input.GetFileExtension(); + Assert.AreEqual(result, extension); + } + [TestCase("'+alert(1234)+'", "+alert1234+")] [TestCase("'+alert(56+78)+'", "+alert56+78+")] [TestCase("{{file}}", "file")] diff --git a/src/Umbraco.Tests/Templates/HtmlImageSourceParserTests.cs b/src/Umbraco.Tests/Templates/HtmlImageSourceParserTests.cs new file mode 100644 index 0000000000..bce9bd4155 --- /dev/null +++ b/src/Umbraco.Tests/Templates/HtmlImageSourceParserTests.cs @@ -0,0 +1,188 @@ +using Umbraco.Core.Logging; +using Moq; +using NUnit.Framework; +using Umbraco.Tests.Testing.Objects.Accessors; +using Umbraco.Web.Templates; +using Umbraco.Web; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Web.Routing; +using Umbraco.Tests.Testing.Objects; +using System.Web; +using System; +using System.Linq; +using Umbraco.Core.Models; +using Umbraco.Core; +using System.Diagnostics; + +namespace Umbraco.Tests.Templates +{ + [TestFixture] + public class HtmlImageSourceParserTests + { + [Test] + public void Returns_Udis_From_Data_Udi_Html_Attributes() + { + var input = @"

+

+ +
+

"; + + var umbracoContextAccessor = new TestUmbracoContextAccessor(); + var imageSourceParser = new HtmlImageSourceParser(umbracoContextAccessor); + + var result = imageSourceParser.FindUdisFromDataAttributes(input).ToList(); + Assert.AreEqual(2, result.Count); + Assert.AreEqual(Udi.Parse("umb://media/D4B18427A1544721B09AC7692F35C264"), result[0]); + Assert.AreEqual(Udi.Parse("umb://media-type/B726D735E4C446D58F703F3FBCFC97A5"), result[1]); + } + + [Test] + public void Remove_Image_Sources() + { + var umbracoContextAccessor = new TestUmbracoContextAccessor(); + var imageSourceParser = new HtmlImageSourceParser(umbracoContextAccessor); + + var result = imageSourceParser.RemoveImageSources(@"

+

+ +

+

+

+

"); + + Assert.AreEqual(@"

+

+ +

+

+

+

", result); + } + + [Test] + public void Ensure_Image_Sources() + { + //setup a mock url provider which we'll use for testing + + var mediaType = new PublishedContentType(777, "image", PublishedItemType.Media, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing); + var media = new Mock(); + media.Setup(x => x.ContentType).Returns(mediaType); + var mediaUrlProvider = new Mock(); + mediaUrlProvider.Setup(x => x.GetMediaUrl(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(UrlInfo.Url("/media/1001/my-image.jpg")); + + var umbracoContextAccessor = new TestUmbracoContextAccessor(); + + var umbracoContextFactory = TestUmbracoContextFactory.Create( + mediaUrlProvider: mediaUrlProvider.Object, + umbracoContextAccessor: umbracoContextAccessor); + + using (var reference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of())) + { + var mediaCache = Mock.Get(reference.UmbracoContext.Media); + mediaCache.Setup(x => x.GetById(It.IsAny())).Returns(media.Object); + + var imageSourceParser = new HtmlImageSourceParser(umbracoContextAccessor); + + var result = imageSourceParser.EnsureImageSources(@"

+

+ +

+

+

+

+

+

+

"); + + Assert.AreEqual(@"

+

+ +

+

+

+

+

+

+

", result); + } + } + + [TestCase( + @"
", + ExpectedResult = @"
", + TestName = "Empty source is not updated with no data-udi set" + )] + [TestCase( + @"
", + ExpectedResult = @"
", + TestName = "Empty source is updated with data-udi set" + )] + [TestCase( + @"
", + ExpectedResult = @"
", + TestName = "Filled source is overwritten with data-udi set" + )] + [TestCase( + @"
", + ExpectedResult = @"
", + TestName = "Attributes are persisted" + )] + [TestCase( + @"
", + ExpectedResult = @"
", + TestName = "Source is trimmed and parameters are prefixed" + )] + [TestCase( + @"
", + ExpectedResult = @"
", + TestName = "Parameters are prefixed" + )] + [TestCase( + @"
+ + + +
", + ExpectedResult = + @"
+ + + +
", + TestName = "Multiple img tags are handled" + )] + + [Category("Ensure image sources")] + public string Ensure_ImageSources_Processing(string sourceHtml) + { + var fakeMediaUrl = "/media/1001/image.jpg"; + var parser = new HtmlImageSourceParser((guid) => fakeMediaUrl); + var actual = parser.EnsureImageSources(sourceHtml); + + return actual; + } + + [Category("Ensure image sources")] + [Test] + public void Ensure_Large_Html_Is_Processed_Quickly() + { + int symbolCount = 25000; + int maxMsToRun = 200; + + var longText = new string('*', symbolCount); + var text = $@""; + + var fakeMediaUrl = "/media/1001/image.jpg"; + var parser = new HtmlImageSourceParser((guid) => fakeMediaUrl); + + var timer = new Stopwatch(); + timer.Start(); + var actual = parser.EnsureImageSources(text); + timer.Stop(); + + Assert.IsTrue(timer.ElapsedMilliseconds <= maxMsToRun); + } + } +} diff --git a/src/Umbraco.Tests/Templates/HtmlLocalLinkParserTests.cs b/src/Umbraco.Tests/Templates/HtmlLocalLinkParserTests.cs new file mode 100644 index 0000000000..7cd96a32ed --- /dev/null +++ b/src/Umbraco.Tests/Templates/HtmlLocalLinkParserTests.cs @@ -0,0 +1,94 @@ +using Moq; +using NUnit.Framework; +using System; +using System.Linq; +using System.Web; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Tests.Testing.Objects; +using Umbraco.Tests.Testing.Objects.Accessors; +using Umbraco.Web; +using Umbraco.Web.Routing; +using Umbraco.Web.Templates; + +namespace Umbraco.Tests.Templates +{ + [TestFixture] + public class HtmlLocalLinkParserTests + { + [Test] + public void Returns_Udis_From_LocalLinks() + { + var input = @"

+

+ + hello +
+

+hello +

"; + + var umbracoContextAccessor = new TestUmbracoContextAccessor(); + var parser = new HtmlLocalLinkParser(umbracoContextAccessor); + + var result = parser.FindUdisFromLocalLinks(input).ToList(); + + Assert.AreEqual(2, result.Count); + Assert.AreEqual(Udi.Parse("umb://document/C093961595094900AAF9170DDE6AD442"), result[0]); + Assert.AreEqual(Udi.Parse("umb://document-type/2D692FCB070B4CDA92FB6883FDBFD6E2"), result[1]); + } + + [TestCase("", "")] + [TestCase("hello href=\"{localLink:1234}\" world ", "hello href=\"/my-test-url\" world ")] + [TestCase("hello href=\"{localLink:umb://document/9931BDE0-AAC3-4BAB-B838-909A7B47570E}\" world ", "hello href=\"/my-test-url\" world ")] + [TestCase("hello href=\"{localLink:umb://document/9931BDE0AAC34BABB838909A7B47570E}\" world ", "hello href=\"/my-test-url\" world ")] + [TestCase("hello href=\"{localLink:umb://media/9931BDE0AAC34BABB838909A7B47570E}\" world ", "hello href=\"/media/1001/my-image.jpg\" world ")] + //this one has an invalid char so won't match + [TestCase("hello href=\"{localLink:umb^://document/9931BDE0-AAC3-4BAB-B838-909A7B47570E}\" world ", "hello href=\"{localLink:umb^://document/9931BDE0-AAC3-4BAB-B838-909A7B47570E}\" world ")] + [TestCase("hello href=\"{localLink:umb://document-type/9931BDE0-AAC3-4BAB-B838-909A7B47570E}\" world ", "hello href=\"#\" world ")] + public void ParseLocalLinks(string input, string result) + { + //setup a mock url provider which we'll use for testing + var contentUrlProvider = new Mock(); + contentUrlProvider + .Setup(x => x.GetUrl(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(UrlInfo.Url("/my-test-url")); + var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing); + var publishedContent = new Mock(); + publishedContent.Setup(x => x.Id).Returns(1234); + publishedContent.Setup(x => x.ContentType).Returns(contentType); + + var mediaType = new PublishedContentType(777, "image", PublishedItemType.Media, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing); + var media = new Mock(); + media.Setup(x => x.ContentType).Returns(mediaType); + var mediaUrlProvider = new Mock(); + mediaUrlProvider.Setup(x => x.GetMediaUrl(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(UrlInfo.Url("/media/1001/my-image.jpg")); + + var umbracoContextAccessor = new TestUmbracoContextAccessor(); + + var umbracoContextFactory = TestUmbracoContextFactory.Create( + urlProvider: contentUrlProvider.Object, + mediaUrlProvider: mediaUrlProvider.Object, + umbracoContextAccessor: umbracoContextAccessor); + + using (var reference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of())) + { + var contentCache = Mock.Get(reference.UmbracoContext.Content); + contentCache.Setup(x => x.GetById(It.IsAny())).Returns(publishedContent.Object); + contentCache.Setup(x => x.GetById(It.IsAny())).Returns(publishedContent.Object); + + var mediaCache = Mock.Get(reference.UmbracoContext.Media); + mediaCache.Setup(x => x.GetById(It.IsAny())).Returns(media.Object); + mediaCache.Setup(x => x.GetById(It.IsAny())).Returns(media.Object); + + var linkParser = new HtmlLocalLinkParser(umbracoContextAccessor); + + var output = linkParser.EnsureInternalLinks(input); + + Assert.AreEqual(result, output); + } + } + } +} diff --git a/src/Umbraco.Tests/Testing/Objects/TestDataSource.cs b/src/Umbraco.Tests/Testing/Objects/TestDataSource.cs index 0291715e46..4476a7464e 100644 --- a/src/Umbraco.Tests/Testing/Objects/TestDataSource.cs +++ b/src/Umbraco.Tests/Testing/Objects/TestDataSource.cs @@ -1,12 +1,15 @@ using System; using System.Collections.Generic; using System.Linq; +using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Scoping; +using Umbraco.Web; using Umbraco.Web.PublishedCache.NuCache; using Umbraco.Web.PublishedCache.NuCache.DataSource; namespace Umbraco.Tests.Testing.Objects { + internal class TestDataSource : IDataSource { public TestDataSource(params ContentNodeKit[] kits) diff --git a/src/Umbraco.Tests/Testing/Objects/TestUmbracoContextFactory.cs b/src/Umbraco.Tests/Testing/Objects/TestUmbracoContextFactory.cs new file mode 100644 index 0000000000..7f891a2580 --- /dev/null +++ b/src/Umbraco.Tests/Testing/Objects/TestUmbracoContextFactory.cs @@ -0,0 +1,49 @@ +using Moq; +using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Services; +using Umbraco.Tests.TestHelpers; +using Umbraco.Tests.Testing.Objects.Accessors; +using Umbraco.Web; +using Umbraco.Web.PublishedCache; +using Umbraco.Web.Routing; + +namespace Umbraco.Tests.Testing.Objects +{ + /// + /// Simplify creating test UmbracoContext's + /// + public class TestUmbracoContextFactory + { + public static IUmbracoContextFactory Create(IGlobalSettings globalSettings = null, IUrlProvider urlProvider = null, + IMediaUrlProvider mediaUrlProvider = null, + IUmbracoContextAccessor umbracoContextAccessor = null) + { + if (globalSettings == null) globalSettings = SettingsForTests.GenerateMockGlobalSettings(); + if (urlProvider == null) urlProvider = Mock.Of(); + if (mediaUrlProvider == null) mediaUrlProvider = Mock.Of(); + if (umbracoContextAccessor == null) umbracoContextAccessor = new TestUmbracoContextAccessor(); + + var contentCache = new Mock(); + var mediaCache = new Mock(); + var snapshot = new Mock(); + snapshot.Setup(x => x.Content).Returns(contentCache.Object); + snapshot.Setup(x => x.Media).Returns(mediaCache.Object); + var snapshotService = new Mock(); + snapshotService.Setup(x => x.CreatePublishedSnapshot(It.IsAny())).Returns(snapshot.Object); + + var umbracoContextFactory = new UmbracoContextFactory( + umbracoContextAccessor, + snapshotService.Object, + new TestVariationContextAccessor(), + new TestDefaultCultureAccessor(), + Mock.Of(section => section.WebRouting == Mock.Of(routingSection => routingSection.UrlProviderMode == "Auto")), + globalSettings, + new UrlProviderCollection(new[] { urlProvider }), + new MediaUrlProviderCollection(new[] { mediaUrlProvider }), + Mock.Of()); + + return umbracoContextFactory; + } + } +} diff --git a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs index 7e72a5aefb..468d4e8b03 100644 --- a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs +++ b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs @@ -41,6 +41,9 @@ using Umbraco.Web.Composing.CompositionExtensions; using Umbraco.Web.Sections; using Current = Umbraco.Core.Composing.Current; using FileSystems = Umbraco.Core.IO.FileSystems; +using Umbraco.Web.Templates; +using Umbraco.Web.PropertyEditors; +using Umbraco.Core.Models; namespace Umbraco.Tests.Testing { @@ -215,6 +218,9 @@ namespace Umbraco.Tests.Testing Composition.RegisterUnique(_ => Umbraco.Web.Composing.Current.UmbracoContextAccessor); Composition.RegisterUnique(); Composition.WithCollectionBuilder(); + + Composition.DataValueReferenceFactories(); + Composition.RegisterUnique(); Composition.RegisterUnique(); Composition.RegisterUnique(); @@ -230,6 +236,11 @@ namespace Umbraco.Tests.Testing .Append(); Composition.RegisterUnique(); + Composition.RegisterUnique(); + Composition.RegisterUnique(); + Composition.RegisterUnique(); + Composition.RegisterUnique(); + } protected virtual void ComposeMisc() @@ -238,6 +249,7 @@ namespace Umbraco.Tests.Testing var runtimeStateMock = new Mock(); runtimeStateMock.Setup(x => x.Level).Returns(RuntimeLevel.Run); Composition.RegisterUnique(f => runtimeStateMock.Object); + Composition.Register(_ => Mock.Of()); // ah... Composition.WithCollectionBuilder(); diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 15c8eec0e1..3c359cdde8 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -140,10 +140,14 @@ + + + + @@ -163,6 +167,7 @@ + @@ -216,6 +221,7 @@ + @@ -257,6 +263,7 @@ + diff --git a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs index 1653de827d..e9f18d8947 100644 --- a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs +++ b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs @@ -30,16 +30,22 @@ namespace Umbraco.Tests.UmbracoExamine ///
internal static class IndexInitializer { - public static ContentValueSetBuilder GetContentValueSetBuilder(PropertyEditorCollection propertyEditors, bool publishedValuesOnly) + public static ContentValueSetBuilder GetContentValueSetBuilder(PropertyEditorCollection propertyEditors, IScopeProvider scopeProvider, bool publishedValuesOnly) { - var contentValueSetBuilder = new ContentValueSetBuilder(propertyEditors, new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() }), GetMockUserService(), publishedValuesOnly); + var contentValueSetBuilder = new ContentValueSetBuilder( + propertyEditors, + new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() }), + GetMockUserService(), + scopeProvider, + publishedValuesOnly); + return contentValueSetBuilder; } - public static ContentIndexPopulator GetContentIndexRebuilder(PropertyEditorCollection propertyEditors, IContentService contentService, ISqlContext sqlContext, bool publishedValuesOnly) + public static ContentIndexPopulator GetContentIndexRebuilder(PropertyEditorCollection propertyEditors, IContentService contentService, IScopeProvider scopeProvider, bool publishedValuesOnly) { - var contentValueSetBuilder = GetContentValueSetBuilder(propertyEditors, publishedValuesOnly); - var contentIndexDataSource = new ContentIndexPopulator(true, null, contentService, sqlContext, contentValueSetBuilder); + var contentValueSetBuilder = GetContentValueSetBuilder(propertyEditors, scopeProvider, publishedValuesOnly); + var contentIndexDataSource = new ContentIndexPopulator(true, null, contentService, scopeProvider.SqlContext, contentValueSetBuilder); return contentIndexDataSource; } diff --git a/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs b/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs index 9e59422310..acb26fb8f6 100644 --- a/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs +++ b/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs @@ -29,7 +29,7 @@ namespace Umbraco.Tests.UmbracoExamine [Test] public void Index_Property_Data_With_Value_Indexer() { - var contentValueSetBuilder = IndexInitializer.GetContentValueSetBuilder(Factory.GetInstance(), false); + var contentValueSetBuilder = IndexInitializer.GetContentValueSetBuilder(Factory.GetInstance(), ScopeProvider, false); using (var luceneDir = new RandomIdRamDirectory()) using (var indexer = IndexInitializer.GetUmbracoIndexer(ProfilingLogger, luceneDir, @@ -121,7 +121,7 @@ namespace Umbraco.Tests.UmbracoExamine [Test] public void Rebuild_Index() { - var contentRebuilder = IndexInitializer.GetContentIndexRebuilder(Factory.GetInstance(), IndexInitializer.GetMockContentService(), ScopeProvider.SqlContext, false); + var contentRebuilder = IndexInitializer.GetContentIndexRebuilder(Factory.GetInstance(), IndexInitializer.GetMockContentService(), ScopeProvider, false); var mediaRebuilder = IndexInitializer.GetMediaIndexRebuilder(Factory.GetInstance(), IndexInitializer.GetMockMediaService()); using (var luceneDir = new RandomIdRamDirectory()) @@ -149,7 +149,7 @@ namespace Umbraco.Tests.UmbracoExamine [Test] public void Index_Protected_Content_Not_Indexed() { - var rebuilder = IndexInitializer.GetContentIndexRebuilder(Factory.GetInstance(), IndexInitializer.GetMockContentService(), ScopeProvider.SqlContext, false); + var rebuilder = IndexInitializer.GetContentIndexRebuilder(Factory.GetInstance(), IndexInitializer.GetMockContentService(), ScopeProvider, false); using (var luceneDir = new RandomIdRamDirectory()) @@ -274,7 +274,7 @@ namespace Umbraco.Tests.UmbracoExamine [Test] public void Index_Reindex_Content() { - var rebuilder = IndexInitializer.GetContentIndexRebuilder(Factory.GetInstance(), IndexInitializer.GetMockContentService(), ScopeProvider.SqlContext, false); + var rebuilder = IndexInitializer.GetContentIndexRebuilder(Factory.GetInstance(), IndexInitializer.GetMockContentService(), ScopeProvider, false); using (var luceneDir = new RandomIdRamDirectory()) using (var indexer = IndexInitializer.GetUmbracoIndexer(ProfilingLogger, luceneDir, validator: new ContentValueSetValidator(false))) @@ -315,7 +315,7 @@ namespace Umbraco.Tests.UmbracoExamine public void Index_Delete_Index_Item_Ensure_Heirarchy_Removed() { - var rebuilder = IndexInitializer.GetContentIndexRebuilder(Factory.GetInstance(), IndexInitializer.GetMockContentService(), ScopeProvider.SqlContext, false); + var rebuilder = IndexInitializer.GetContentIndexRebuilder(Factory.GetInstance(), IndexInitializer.GetMockContentService(), ScopeProvider, false); using (var luceneDir = new RandomIdRamDirectory()) using (var indexer = IndexInitializer.GetUmbracoIndexer(ProfilingLogger, luceneDir)) diff --git a/src/Umbraco.Tests/UmbracoExamine/SearchTests.cs b/src/Umbraco.Tests/UmbracoExamine/SearchTests.cs index a45a33ec00..96e8892cd1 100644 --- a/src/Umbraco.Tests/UmbracoExamine/SearchTests.cs +++ b/src/Umbraco.Tests/UmbracoExamine/SearchTests.cs @@ -55,7 +55,7 @@ namespace Umbraco.Tests.UmbracoExamine allRecs); var propertyEditors = Factory.GetInstance(); - var rebuilder = IndexInitializer.GetContentIndexRebuilder(propertyEditors, contentService, ScopeProvider.SqlContext, true); + var rebuilder = IndexInitializer.GetContentIndexRebuilder(propertyEditors, contentService, ScopeProvider, true); using (var luceneDir = new RandomIdRamDirectory()) using (var indexer = IndexInitializer.GetUmbracoIndexer(ProfilingLogger, luceneDir)) diff --git a/src/Umbraco.Tests/Web/AngularIntegration/AngularAntiForgeryTests.cs b/src/Umbraco.Tests/Web/AngularIntegration/AngularAntiForgeryTests.cs index 780a3e90fd..fd30f1b5ec 100644 --- a/src/Umbraco.Tests/Web/AngularIntegration/AngularAntiForgeryTests.cs +++ b/src/Umbraco.Tests/Web/AngularIntegration/AngularAntiForgeryTests.cs @@ -27,7 +27,7 @@ namespace Umbraco.Tests.Web.AngularIntegration string cookieToken, headerToken; AngularAntiForgeryHelper.GetTokens(out cookieToken, out headerToken); - Assert.AreEqual(true, AngularAntiForgeryHelper.ValidateTokens(cookieToken, headerToken)); + Assert.IsTrue(AngularAntiForgeryHelper.ValidateTokens(cookieToken, headerToken)); } } @@ -45,7 +45,7 @@ namespace Umbraco.Tests.Web.AngularIntegration string cookieToken, headerToken; AngularAntiForgeryHelper.GetTokens(out cookieToken, out headerToken); - Assert.AreEqual(true, AngularAntiForgeryHelper.ValidateTokens(cookieToken, headerToken)); + Assert.IsTrue(AngularAntiForgeryHelper.ValidateTokens(cookieToken, headerToken)); } } diff --git a/src/Umbraco.Tests/Web/ModelStateExtensionsTests.cs b/src/Umbraco.Tests/Web/ModelStateExtensionsTests.cs index 3ce43b5fc2..8d285e0375 100644 --- a/src/Umbraco.Tests/Web/ModelStateExtensionsTests.cs +++ b/src/Umbraco.Tests/Web/ModelStateExtensionsTests.cs @@ -63,7 +63,7 @@ namespace Umbraco.Tests.Web ms.AddPropertyError(new ValidationResult("no header image"), "headerImage", null); //invariant property - Assert.AreEqual("_Properties.headerImage.invariant", ms.Keys.First()); + Assert.AreEqual("_Properties.headerImage.invariant.null", ms.Keys.First()); } [Test] @@ -73,9 +73,57 @@ namespace Umbraco.Tests.Web var localizationService = new Mock(); localizationService.Setup(x => x.GetDefaultLanguageIsoCode()).Returns("en-US"); - ms.AddPropertyError(new ValidationResult("no header image"), "headerImage", "en-US"); //invariant property + ms.AddPropertyError(new ValidationResult("no header image"), "headerImage", "en-US"); //variant property - Assert.AreEqual("_Properties.headerImage.en-US", ms.Keys.First()); + Assert.AreEqual("_Properties.headerImage.en-US.null", ms.Keys.First()); + } + + [Test] + public void Add_Invariant_Segment_Property_Error() + { + var ms = new ModelStateDictionary(); + var localizationService = new Mock(); + localizationService.Setup(x => x.GetDefaultLanguageIsoCode()).Returns("en-US"); + + ms.AddPropertyError(new ValidationResult("no header image"), "headerImage", null, "mySegment"); //invariant/segment property + + Assert.AreEqual("_Properties.headerImage.invariant.mySegment", ms.Keys.First()); + } + + [Test] + public void Add_Variant_Segment_Property_Error() + { + var ms = new ModelStateDictionary(); + var localizationService = new Mock(); + localizationService.Setup(x => x.GetDefaultLanguageIsoCode()).Returns("en-US"); + + ms.AddPropertyError(new ValidationResult("no header image"), "headerImage", "en-US", "mySegment"); //variant/segment property + + Assert.AreEqual("_Properties.headerImage.en-US.mySegment", ms.Keys.First()); + } + + [Test] + public void Add_Invariant_Segment_Field_Property_Error() + { + var ms = new ModelStateDictionary(); + var localizationService = new Mock(); + localizationService.Setup(x => x.GetDefaultLanguageIsoCode()).Returns("en-US"); + + ms.AddPropertyError(new ValidationResult("no header image", new[] { "myField" }), "headerImage", null, "mySegment"); //invariant/segment property + + Assert.AreEqual("_Properties.headerImage.invariant.mySegment.myField", ms.Keys.First()); + } + + [Test] + public void Add_Variant_Segment_Field_Property_Error() + { + var ms = new ModelStateDictionary(); + var localizationService = new Mock(); + localizationService.Setup(x => x.GetDefaultLanguageIsoCode()).Returns("en-US"); + + ms.AddPropertyError(new ValidationResult("no header image", new[] { "myField" }), "headerImage", "en-US", "mySegment"); //variant/segment property + + Assert.AreEqual("_Properties.headerImage.en-US.mySegment.myField", ms.Keys.First()); } } } diff --git a/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs b/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs index 3a5405548b..ef23330431 100644 --- a/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs +++ b/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs @@ -1,6 +1,4 @@ using System; -using System.Linq; -using System.Web; using Moq; using NUnit.Framework; using Umbraco.Core; @@ -9,20 +7,16 @@ using Umbraco.Core.Composing; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; using Umbraco.Core.Models; -using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Services; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing.Objects.Accessors; -using Umbraco.Web; -using Umbraco.Web.PublishedCache; -using Umbraco.Web.Routing; using Umbraco.Web.Security; -using Umbraco.Web.Templates; using Umbraco.Core.Configuration; using Umbraco.Core.IO; namespace Umbraco.Tests.Web { + [TestFixture] public class TemplateUtilitiesTests { @@ -59,67 +53,6 @@ namespace Umbraco.Tests.Web Current.Reset(); } - [TestCase("", "")] - [TestCase("hello href=\"{localLink:1234}\" world ", "hello href=\"/my-test-url\" world ")] - [TestCase("hello href=\"{localLink:umb://document/9931BDE0-AAC3-4BAB-B838-909A7B47570E}\" world ", "hello href=\"/my-test-url\" world ")] - [TestCase("hello href=\"{localLink:umb://document/9931BDE0AAC34BABB838909A7B47570E}\" world ", "hello href=\"/my-test-url\" world ")] - [TestCase("hello href=\"{localLink:umb://media/9931BDE0AAC34BABB838909A7B47570E}\" world ", "hello href=\"/media/1001/my-image.jpg\" world ")] - //this one has an invalid char so won't match - [TestCase("hello href=\"{localLink:umb^://document/9931BDE0-AAC3-4BAB-B838-909A7B47570E}\" world ", "hello href=\"{localLink:umb^://document/9931BDE0-AAC3-4BAB-B838-909A7B47570E}\" world ")] - [TestCase("hello href=\"{localLink:umb://document-type/9931BDE0-AAC3-4BAB-B838-909A7B47570E}\" world ", "hello href=\"#\" world ")] - public void ParseLocalLinks(string input, string result) - { - var serviceCtxMock = new TestObjects(null).GetServiceContextMock(); - - //setup a mock entity service from the service context to return an integer for a GUID - var entityService = Mock.Get(serviceCtxMock.EntityService); - //entityService.Setup(x => x.GetId(It.IsAny(), It.IsAny())) - // .Returns((Guid id, UmbracoObjectTypes objType) => - // { - // return Attempt.Succeed(1234); - // }); - - //setup a mock url provider which we'll use for testing - var testUrlProvider = new Mock(); - testUrlProvider - .Setup(x => x.GetUrl(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - .Returns((UmbracoContext umbCtx, IPublishedContent content, UrlMode mode, string culture, Uri url) => UrlInfo.Url("/my-test-url")); - - var globalSettings = SettingsForTests.GenerateMockGlobalSettings(); - - var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing); - var publishedContent = Mock.Of(); - Mock.Get(publishedContent).Setup(x => x.Id).Returns(1234); - Mock.Get(publishedContent).Setup(x => x.ContentType).Returns(contentType); - var contentCache = Mock.Of(); - Mock.Get(contentCache).Setup(x => x.GetById(It.IsAny())).Returns(publishedContent); - Mock.Get(contentCache).Setup(x => x.GetById(It.IsAny())).Returns(publishedContent); - var snapshot = Mock.Of(); - Mock.Get(snapshot).Setup(x => x.Content).Returns(contentCache); - var snapshotService = Mock.Of(); - Mock.Get(snapshotService).Setup(x => x.CreatePublishedSnapshot(It.IsAny())).Returns(snapshot); - var media = Mock.Of(); - Mock.Get(media).Setup(x => x.Url).Returns("/media/1001/my-image.jpg"); - var mediaCache = Mock.Of(); - Mock.Get(mediaCache).Setup(x => x.GetById(It.IsAny())).Returns(media); - - var umbracoContextFactory = new UmbracoContextFactory( - Umbraco.Web.Composing.Current.UmbracoContextAccessor, - snapshotService, - new TestVariationContextAccessor(), - new TestDefaultCultureAccessor(), - Mock.Of(section => section.WebRouting == Mock.Of(routingSection => routingSection.UrlProviderMode == "Auto")), - globalSettings, - new UrlProviderCollection(new[] { testUrlProvider.Object }), - new MediaUrlProviderCollection(Enumerable.Empty()), - Mock.Of()); - - using (var reference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of())) - { - var output = TemplateUtilities.ParseInternalLinks(input, reference.UmbracoContext.UrlProvider, mediaCache); - - Assert.AreEqual(result, output); - } - } + } } diff --git a/src/Umbraco.Web.UI.Client/.eslintrc b/src/Umbraco.Web.UI.Client/.eslintrc index 1bfa081b9a..a4010917fb 100644 --- a/src/Umbraco.Web.UI.Client/.eslintrc +++ b/src/Umbraco.Web.UI.Client/.eslintrc @@ -19,10 +19,10 @@ "tinyMCE": false, "FileReader": false, "Umbraco": false, + "Utilities": false, "window": false, "LazyLoad": false, "ActiveXObject": false, "Bloodhound": false } - -} \ No newline at end of file +} diff --git a/src/Umbraco.Web.UI.Client/gulp/config.js b/src/Umbraco.Web.UI.Client/gulp/config.js index 59e8bf6c05..39dc9bb2a4 100755 --- a/src/Umbraco.Web.UI.Client/gulp/config.js +++ b/src/Umbraco.Web.UI.Client/gulp/config.js @@ -28,9 +28,9 @@ module.exports = { installer: { files: "./src/installer/**/*.js", out: "umbraco.installer.js" }, filters: { files: "./src/common/filters/**/*.js", out: "umbraco.filters.js" }, resources: { files: "./src/common/resources/**/*.js", out: "umbraco.resources.js" }, - services: { files: "./src/common/services/**/*.js", out: "umbraco.services.js" }, + services: { files: ["./src/common/services/**/*.js", "./src/utilities.js"], out: "umbraco.services.js" }, security: { files: "./src/common/interceptors/**/*.js", out: "umbraco.interceptors.js" }, - + //the controllers for views controllers: { files: [ diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/type.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/type.less index bf1167f950..3f93deaf56 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/type.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/type.less @@ -132,6 +132,10 @@ ol.inline { display: inline-block; padding-left: 5px; padding-right: 5px; + + &.-no-padding-left{ + padding-left: 0; + } } } diff --git a/src/Umbraco.Web.UI.Client/lib/markdown/markdown.editor.js b/src/Umbraco.Web.UI.Client/lib/markdown/markdown.editor.js index a75a7f1f3c..60118dbdb3 100644 --- a/src/Umbraco.Web.UI.Client/lib/markdown/markdown.editor.js +++ b/src/Umbraco.Web.UI.Client/lib/markdown/markdown.editor.js @@ -60,6 +60,7 @@ * its own image insertion dialog, this hook should return true, and the callback should be called with the chosen * image url (or null if the user cancelled). If this hook returns false, the default dialog will be used. */ + hooks.addFalse("insertLinkDialog"); this.getConverter = function () { return markdownConverter; } @@ -1636,7 +1637,7 @@ var that = this; // The function to be executed when you enter a link and press OK or Cancel. // Marks up the link and adds the ref. - var linkEnteredCallback = function (link) { + var linkEnteredCallback = function (link, title) { if (link !== null) { // ( $1 @@ -1667,10 +1668,10 @@ if (!chunk.selection) { if (isImage) { - chunk.selection = "enter image description here"; + chunk.selection = title || "enter image description here"; } else { - chunk.selection = "enter link description here"; + chunk.selection = title || "enter link description here"; } } } @@ -1683,7 +1684,8 @@ ui.prompt('Insert Image', imageDialogText, imageDefaultText, linkEnteredCallback); } else { - ui.prompt('Insert Link', linkDialogText, linkDefaultText, linkEnteredCallback); + if (!this.hooks.insertLinkDialog(linkEnteredCallback)) + ui.prompt('Insert Link', linkDialogText, linkDefaultText, linkEnteredCallback); } return true; } diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/af_ZA.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/af_ZA.js new file mode 100644 index 0000000000..b6068bc7ec --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/af_ZA.js @@ -0,0 +1,230 @@ +tinymce.addI18n('af_ZA',{ +"Cut": "Sny", +"Heading 5": "Opskrif 5", +"Header 2": "Hooflyn 2", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "Jou webblaaier ondersteun nie toegang tot die knipbord nie. Gebruik asb. Ctrl+X\/C\/V", +"Heading 4": "Opskrif 4", +"Div": "Div", +"Heading 2": "Opskrif 2", +"Paste": "Plak", +"Close": "Sluit", +"Font Family": "Font Familie", +"Pre": "Pre", +"Align right": "Regsgerig", +"New document": "Nuwe Dokument", +"Blockquote": "Aanhaling", +"Numbered list": "Genommerde lys", +"Heading 1": "Opskrif 1", +"Headings": "Opskrifte", +"Increase indent": "Inkeping vergroot", +"Formats": "Formate", +"Headers": "Hooflyn-tekste", +"Select all": "Alles selekteer", +"Header 3": "Hooflyn 3", +"Blocks": "Blokke", +"Undo": "Ongedaan maak", +"Strikethrough": "Deurhaal", +"Bullet list": "Opsommingsteken-lys", +"Header 1": "Hooflyn 1", +"Superscript": "Superskrif", +"Clear formatting": "Herstel Formateering", +"Font Sizes": "Font Groote", +"Subscript": "Subskrif", +"Header 6": "Hooflyn 6", +"Redo": "Herdoen", +"Paragraph": "Paragraaf", +"Ok": "OK", +"Bold": "Vetdruk", +"Code": "Kode", +"Italic": "Kursief", +"Align center": "Senteer", +"Header 5": "Hooflyn 5", +"Heading 6": "Opskrif 6", +"Heading 3": "Opskrif 3", +"Decrease indent": "Inkeping verklein", +"Header 4": "Hooflyn 4", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "Die plak funksie is nou in plat-teks modus. Teks word ingevoeg sonder enige formateering, todat jy hierdie opsie wissel.", +"Underline": "Onderlyn", +"Cancel": "Kanselleer", +"Justify": "Gerigstelling", +"Inline": "Inlyn", +"Copy": "Kopieer", +"Align left": "Linksgerig", +"Visual aids": "Hulpmiddels", +"Lower Greek": "Griekse letters", +"Square": "Vierkantjie", +"Default": "Verstek", +"Lower Alpha": "Klein letters", +"Circle": "Sirkeltjie", +"Disc": "Balletjie", +"Upper Alpha": "Hoofletters", +"Upper Roman": "Romeinse syfers groot", +"Lower Roman": "Romeinse syfers klein", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "Id moet met 'n letter begin en kan slegs deur letters, koppeltekens, syfers, punte en onderstreep-karakters gevolg word.", +"Name": "Geen", +"Anchor": "Anker", +"Id": "Id", +"You have unsaved changes are you sure you want to navigate away?": "Jy het ongestoorde wysigings op hierdier bladsy - is jy seker dat jy die bladsy wil verlaat?", +"Restore last draft": "Herstel die laatste konsep", +"Special character": "Spesiaale karakter", +"Source code": "Bron kode", +"Language": "Taal", +"Insert\/Edit code sample": "Voeg\/Redigeer voorbeeld-kode", +"B": "Blou", +"R": "Rooi", +"G": "Groen", +"Color": "Kleur", +"Right to left": "Regs na links", +"Left to right": "Links na regs", +"Emoticons": "Emoticons", +"Robots": "Robotte", +"Document properties": "Dokument eienskappe", +"Title": "Titel", +"Keywords": "Sleutelwoorde", +"Encoding": "Enkodeering", +"Description": "Beskrywing", +"Author": "Outeur", +"Fullscreen": "Volskerm", +"Horizontal line": "Horisontale lyn", +"Horizontal space": "Horisontale Spasie", +"Insert\/edit image": "Afbeelding invoeg\/bewerk", +"General": "Algemeen", +"Advanced": "Gevorderd", +"Source": "Bron", +"Border": "Rand", +"Constrain proportions": "Behou verhoudings", +"Vertical space": "Vertikale Spasie", +"Image description": "Afbeelding bemskrywing", +"Style": "Styl", +"Dimensions": "Afmetings", +"Insert image": "Afbeelding invoeg", +"Image": "Afbeelding", +"Zoom in": "Inzoem", +"Contrast": "Kontras", +"Back": "Terug", +"Gamma": "Gamma", +"Flip horizontally": "Horisontaal weerspie\\u00ebl", +"Resize": "Grootte wysig", +"Sharpen": "Verskerp", +"Zoom out": "Uitzoem", +"Image options": "Afbeelding opsies", +"Apply": "Toepas", +"Brightness": "Helderheid", +"Rotate clockwise": "Regsom draai", +"Rotate counterclockwise": "Linksom draai", +"Edit image": "Bewerk afbeelding", +"Color levels": "Kleurvlakke", +"Crop": "Afknip", +"Orientation": "Orienteering", +"Flip vertically": "Vertikaal weerspie\\u00ebl", +"Invert": "Omkeer", +"Date\/time": "Datum\/tyd", +"Insert date\/time": "Voeg datum\/tyd in", +"Remove link": "Verwyder skakel", +"Url": "URL", +"Text to display": "Skakelteks", +"Anchors": "Ankers", +"Insert link": "Skakel invoeg", +"Link": "Skakel", +"New window": "Nuwe Skerm", +"None": "Geen", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "Die URL verwys na 'n eksterne adres. Wil jy die \"http:\/\/\" voorvoegsel byvoeg?", +"Paste or type a link": "Plak of tik 'n skalel in", +"Target": "Teiken", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "Die URL lyk soos 'n eposadres. Wil jy die \"mailto:\" voorvoegsel byvoeg?", +"Insert\/edit link": "Skakel invoeg\/bewerk", +"Insert\/edit video": "Video invoeg\/bewerk", +"Media": "Media", +"Alternative source": "Alternatiewe bron", +"Paste your embed code below:": "Plak jou ingesluite kode hieronder in:", +"Insert video": "Video invoeg", +"Poster": "Plakaat", +"Insert\/edit media": "Media invoeg\/bewerk", +"Embed": "Insluit", +"Nonbreaking space": "Vaste spasie invoeg", +"Page break": "Nuwe Bladsy", +"Paste as text": "As teks plak", +"Preview": "Voorskou", +"Print": "Druk", +"Save": "Stoor", +"Could not find the specified string.": "Kon nie die gesoekde string vind nie", +"Replace": "Vervang", +"Next": "Volgende", +"Whole words": "Hele woorde", +"Find and replace": "Soek en vervang", +"Replace with": "Vervang Met", +"Find": "Soek", +"Replace all": "Vervang alles", +"Match case": "Kassensitief", +"Prev": "Vorige", +"Spellcheck": "Toets spelling", +"Finish": "Einde", +"Ignore all": "Ignoreer alles", +"Ignore": "Ignoreer", +"Add to Dictionary": "Voeg by woordeboek", +"Insert row before": "Voeg nuwe ry boaan", +"Rows": "Rye", +"Height": "Hoogte", +"Paste row after": "Plak ry na", +"Alignment": "Gerigdheid", +"Border color": "Randkleur", +"Column group": "Kolom Groep", +"Row": "Ry", +"Insert column before": "Voeg kolom vooraan", +"Split cell": "Sel split", +"Cell padding": "Ruimte binnein sel", +"Cell spacing": "Ruimte rondom sel", +"Row type": "Ry tipe", +"Insert table": "Tabel invoeg", +"Body": "Tabel Inhoud", +"Caption": "Onderskrif", +"Footer": "Voetskrif", +"Delete row": "Verwyder ry", +"Paste row before": "Plak ry vooraan", +"Scope": "Bereik", +"Delete table": "Skrap tabel", +"H Align": "Horisontaal-gerigdheid", +"Top": "Bo", +"Header cell": "Kop Sel", +"Column": "Kolom", +"Row group": "Ry Groep", +"Cell": "Sel", +"Middle": "Middel", +"Cell type": "Sel tipe", +"Copy row": "Kopieer ry", +"Row properties": "Ry eienskappe", +"Table properties": "Tabel eienskappe", +"Bottom": "Onder", +"V Align": "Vertikaal-rerigdheid", +"Header": "Kopteks", +"Right": "Regs", +"Insert column after": "Voeg kolom na", +"Cols": "Kolomme", +"Insert row after": "Voeg nuwe ry na", +"Width": "Wydte", +"Cell properties": "Sel eienskappe", +"Left": "Links", +"Cut row": "Knip ry", +"Delete column": "Verwyder kolom", +"Center": "Middel", +"Merge cells": "Selle saamvoeg", +"Insert template": "Sjabloon invoeg", +"Templates": "Sjablone", +"Background color": "Agtergrond Kleur", +"Custom...": "Spesifiek...", +"Custom color": "Spesifieke Kleur", +"No color": "Geen Kleur", +"Text color": "Teks Kleur", +"Table of Contents": "Inhoudsopgawe", +"Show blocks": "Blokke vertoon", +"Show invisible characters": "Onsigbare karakters vertoon", +"Words: {0}": "Woorde: {0}", +"Insert": "Invoeg", +"File": "L\u00eaer", +"Edit": "Wysig", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Ryk Teks Area. Druk ALT-F9 vir menu, ALT-F10 vir die nutsbalk, ALT-0 vir hulp.", +"Tools": "Gereedskap", +"View": "Formaat", +"Table": "Tabel", +"Format": "Formateer" +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/ar.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/ar.js new file mode 100644 index 0000000000..2bd07a8a91 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/ar.js @@ -0,0 +1,262 @@ +tinymce.addI18n('ar',{ +"Redo": "\u0625\u0639\u0627\u062f\u0629", +"Undo": "\u062a\u0631\u0627\u062c\u0639", +"Cut": "\u0642\u0635", +"Copy": "\u0646\u0633\u062e", +"Paste": "\u0644\u0635\u0642", +"Select all": "\u062a\u062d\u062f\u064a\u062f \u0627\u0644\u0643\u0644", +"New document": "\u0645\u0633\u062a\u0646\u062f \u062c\u062f\u064a\u062f", +"Ok": "\u0645\u0648\u0627\u0641\u0642", +"Cancel": "\u0625\u0644\u063a\u0627\u0621", +"Visual aids": "\u0627\u0644\u0645\u0639\u064a\u0646\u0627\u062a \u0627\u0644\u0628\u0635\u0631\u064a\u0629", +"Bold": "\u063a\u0627\u0645\u0642", +"Italic": "\u0645\u0627\u0626\u0644", +"Underline": "\u062a\u0633\u0637\u064a\u0631", +"Strikethrough": "\u064a\u062a\u0648\u0633\u0637 \u062e\u0637", +"Superscript": "\u0645\u0631\u062a\u0641\u0639", +"Subscript": "\u0645\u0646\u062e\u0641\u0636", +"Clear formatting": "\u0645\u0633\u062d \u0627\u0644\u062a\u0646\u0633\u064a\u0642", +"Align left": "\u0645\u062d\u0627\u0630\u0627\u0629 \u0627\u0644\u0646\u0635 \u0644\u0644\u064a\u0633\u0627\u0631", +"Align center": "\u062a\u0648\u0633\u064a\u0637", +"Align right": "\u0645\u062d\u0627\u0630\u0627\u0629 \u0627\u0644\u0646\u0635 \u0644\u0644\u064a\u0645\u064a\u0646", +"Justify": "\u0636\u0628\u0637", +"Bullet list": "\u062a\u0639\u062f\u0627\u062f \u0646\u0642\u0637\u064a", +"Numbered list": "\u062a\u0631\u0642\u064a\u0645", +"Decrease indent": "\u0625\u0646\u0642\u0627\u0635 \u0627\u0644\u0645\u0633\u0627\u0641\u0629 \u0627\u0644\u0628\u0627\u062f\u0626\u0629", +"Increase indent": "\u0632\u064a\u0627\u062f\u0629 \u0627\u0644\u0645\u0633\u0627\u0641\u0629 \u0627\u0644\u0628\u0627\u062f\u0626\u0629", +"Close": "\u0625\u063a\u0644\u0627\u0642", +"Formats": "\u0627\u0644\u062a\u0646\u0633\u064a\u0642\u0627\u062a", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "\u0645\u062a\u0635\u0641\u062d\u0643 \u0644\u0627 \u064a\u062f\u0639\u0645 \u0627\u0644\u0648\u0635\u0648\u0644 \u0627\u0644\u0645\u0628\u0627\u0634\u0631 \u0625\u0644\u0649 \u0627\u0644\u062d\u0627\u0641\u0638\u0629. \u0627\u0644\u0631\u062c\u0627\u0621 \u0627\u0633\u062a\u062e\u062f\u0627\u0645 \u0627\u062e\u062a\u0635\u0627\u0631\u0627\u062a \u0644\u0648\u062d\u0629 \u0627\u0644\u0645\u0641\u0627\u062a\u064a\u062d Ctrl+X\/C\/V \u0628\u062f\u0644\u0627 \u0645\u0646 \u0630\u0644\u0643.", +"Headers": "\u0627\u0644\u0639\u0646\u0627\u0648\u064a\u0646", +"Header 1": "\u0627\u0644\u0639\u0646\u0627\u0648\u064a\u0646 1", +"Header 2": "\u0627\u0644\u0639\u0646\u0627\u0648\u064a\u0646 2", +"Header 3": "\u0627\u0644\u0639\u0646\u0627\u0648\u064a\u0646 3", +"Header 4": "\u0627\u0644\u0639\u0646\u0627\u0648\u064a\u0646 4", +"Header 5": "\u0627\u0644\u0639\u0646\u0627\u0648\u064a\u0646 5", +"Header 6": "\u0627\u0644\u0639\u0646\u0627\u0648\u064a\u0646 6", +"Headings": "\u0627\u0644\u0639\u0646\u0627\u0648\u064a\u0646 \u0627\u0644\u0631\u0626\u064a\u0633\u064a\u0629", +"Heading 1": "\u0627\u0644\u0639\u0646\u0648\u0627\u0646 \u0627\u0644\u0631\u0626\u064a\u0633\u064a 1", +"Heading 2": "\u0627\u0644\u0639\u0646\u0648\u0627\u0646 \u0627\u0644\u0631\u0626\u064a\u0633\u064a 2", +"Heading 3": "\u0627\u0644\u0639\u0646\u0648\u0627\u0646 \u0627\u0644\u0631\u0626\u064a\u0633\u064a 3", +"Heading 4": "\u0627\u0644\u0639\u0646\u0648\u0627\u0646 \u0627\u0644\u0631\u0626\u064a\u0633\u064a 4", +"Heading 5": "\u0627\u0644\u0639\u0646\u0648\u0627\u0646 \u0627\u0644\u0631\u0626\u064a\u0633\u064a 5", +"Heading 6": "\u0627\u0644\u0639\u0646\u0648\u0627\u0646 \u0627\u0644\u0631\u0626\u064a\u0633\u064a 6", +"Preformatted": "\u0645\u0647\u064a\u0623 \u0645\u0633\u0628\u0642\u0627", +"Div": "Div", +"Pre": "\u0633\u0627\u0628\u0642", +"Code": "\u0631\u0645\u0632", +"Paragraph": "\u0641\u0642\u0631\u0629", +"Blockquote": "\u0639\u0644\u0627\u0645\u0627\u062a \u0627\u0644\u0627\u0642\u062a\u0628\u0627\u0633", +"Inline": "\u062e\u0644\u0627\u0644", +"Blocks": "\u0627\u0644\u0623\u0642\u0633\u0627\u0645", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "\u064a\u062a\u0645 \u0627\u0644\u0644\u0635\u0642 \u062d\u0627\u0644\u064a\u0627\u064b \u0643\u0646\u0635 \u0639\u0627\u062f\u064a. \u0627\u0644\u0645\u062d\u062a\u0648\u0649 \u0633\u064a\u0628\u0642\u0649 \u0643\u0646\u0635 \u0639\u0627\u062f\u064a \u062d\u062a\u0649 \u062a\u0642\u0648\u0645 \u0628\u062a\u0639\u0637\u064a\u0644 \u0647\u0630\u0627 \u0627\u0644\u062e\u064a\u0627\u0631.", +"Font Family": "\u0645\u062c\u0645\u0648\u0639\u0629 \u0627\u0644\u062e\u0637", +"Font Sizes": "\u062d\u062c\u0645 \u0627\u0644\u062e\u0637", +"Class": "\u0627\u0644\u0641\u0626\u0629", +"Browse for an image": "\u0627\u0633\u062a\u0639\u0631\u0627\u0636 \u0635\u0648\u0631\u0629", +"OR": "\u0623\u0648", +"Drop an image here": "\u0627\u0633\u0642\u0637 \u0627\u0644\u0635\u0648\u0631\u0629 \u0647\u0646\u0627", +"Upload": "\u0631\u0641\u0639", +"Block": "\u0642\u0633\u0645", +"Align": "\u0645\u062d\u0627\u0630\u0627\u0629 \u0623\u0641\u0642\u064a\u0629", +"Default": "\u0627\u0644\u0627\u0641\u062a\u0631\u0627\u0636\u064a", +"Circle": "\u062f\u0627\u0626\u0631\u0629", +"Disc": "\u0642\u0631\u0635", +"Square": "\u0645\u0631\u0628\u0639", +"Lower Alpha": "\u062a\u0631\u0642\u064a\u0645 \u0623\u062e\u0631\u0641 \u0635\u063a\u064a\u0631\u0629", +"Lower Greek": "\u062a\u0631\u0642\u064a\u0645 \u064a\u0648\u0646\u0627\u0646\u064a \u0635\u063a\u064a\u0631", +"Lower Roman": "\u062a\u0631\u0642\u064a\u0645 \u0631\u0648\u0645\u0627\u0646\u064a \u0635\u063a\u064a\u0631", +"Upper Alpha": "\u062a\u0631\u0642\u064a\u0645 \u0623\u062d\u0631\u0641 \u0643\u0628\u064a\u0631\u0629", +"Upper Roman": "\u062a\u0631\u0642\u064a\u0645 \u0631\u0648\u0645\u0627\u0646\u064a \u0643\u0628\u064a\u0631", +"Anchor": "\u0645\u0631\u0633\u0627\u0629", +"Name": "\u0627\u0644\u0627\u0633\u0645", +"Id": "\u0631\u0642\u0645 \u0627\u0644\u0645\u0639\u0631\u0641", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "\u0631\u0642\u0645 \u0627\u0644\u0645\u0639\u0631\u0641 \u064a\u062c\u0628 \u0623\u0646 \u062a\u0628\u062f\u0623 \u0628\u062d\u0631\u0641\u060c \u064a\u062a\u0628\u0639 \u0641\u0642\u0637 \u0628\u062d\u0631\u0648\u0641 \u0648\u0623\u0631\u0642\u0627\u0645\u060c \u0634\u0631\u0637\u0627\u062a\u060c \u0623\u0648 \u0627\u0644\u0646\u0642\u0627\u0637\u060c \u0627\u0644\u0646\u0642\u0637\u062a\u064a\u0646 \u0623\u0648 \u0627\u0644\u0634\u0631\u0637\u0627\u062a \u0627\u0644\u0633\u0641\u0644\u064a\u0629.", +"You have unsaved changes are you sure you want to navigate away?": "\u0644\u062f\u064a\u0643 \u062a\u063a\u064a\u064a\u0631\u0627\u062a \u0644\u0645 \u064a\u062a\u0645 \u062d\u0641\u0638\u0647\u0627 \u0647\u0644 \u0623\u0646\u062a \u0645\u062a\u0623\u0643\u062f \u0623\u0646\u0643 \u062a\u0631\u063a\u0628 \u0641\u064a \u0627\u0644\u0627\u0646\u062a\u0642\u0627\u0644 \u0628\u0639\u064a\u062f\u0627\u061f", +"Restore last draft": "\u0627\u0633\u062a\u0639\u0627\u062f\u0629 \u0623\u062e\u0631 \u0645\u0633\u0648\u062f\u0629", +"Special character": "\u0631\u0645\u0632", +"Source code": "\u0634\u0641\u0631\u0629 \u0627\u0644\u0645\u0635\u062f\u0631", +"Insert\/Edit code sample": "\u0625\u062f\u0631\u0627\u062c\/\u062a\u062d\u0631\u064a\u0631 \u0627\u0644\u0643\u0648\u062f", +"Language": "\u0627\u0644\u0644\u063a\u0629", +"Code sample": "\u0639\u064a\u0651\u0646\u0629 \u0639\u0646 \u0627\u0644\u0643\u0648\u062f \u0627\u0644\u0628\u0631\u0645\u062c\u064a", +"Color": "\u0627\u0644\u0644\u0648\u0646", +"R": "R", +"G": "G", +"B": "B", +"Left to right": "\u0645\u0646 \u0627\u0644\u064a\u0633\u0627\u0631 \u0644\u0644\u064a\u0645\u064a\u0646", +"Right to left": "\u0645\u0646 \u0627\u0644\u064a\u0645\u064a\u0646 \u0644\u0644\u064a\u0633\u0627\u0631", +"Emoticons": "\u0627\u0644\u0631\u0645\u0648\u0632", +"Document properties": "\u062e\u0635\u0627\u0626\u0635 \u0627\u0644\u0645\u0633\u062a\u0646\u062f", +"Title": "\u0627\u0644\u0639\u0646\u0648\u0627\u0646", +"Keywords": "\u0643\u0644\u0645\u0627\u062a \u0627\u0644\u0628\u062d\u062b", +"Description": "\u0627\u0644\u0648\u0635\u0641", +"Robots": "\u0627\u0644\u0631\u0648\u0628\u0648\u062a\u0627\u062a", +"Author": "\u0627\u0644\u0643\u0627\u062a\u0628", +"Encoding": "\u0627\u0644\u062a\u0631\u0645\u064a\u0632", +"Fullscreen": "\u0645\u0644\u0621 \u0627\u0644\u0634\u0627\u0634\u0629", +"Action": "\u0627\u0644\u0639\u0645\u0644\u064a\u0629", +"Shortcut": "\u0627\u0644\u0627\u062e\u062a\u0635\u0627\u0631", +"Help": "\u0627\u0644\u0645\u0633\u0627\u0639\u062f\u0629", +"Address": "\u0627\u0644\u0639\u0646\u0648\u0627\u0646", +"Focus to menubar": "\u0627\u0644\u062a\u0631\u0643\u064a\u0632 \u0639\u0644\u0649 \u0634\u0631\u064a\u0637 \u0627\u0644\u0642\u0648\u0627\u0626\u0645", +"Focus to toolbar": "\u0627\u0644\u062a\u0631\u0643\u064a\u0632 \u0639\u0644\u0649 \u0634\u0631\u064a\u0637 \u0627\u0644\u0623\u062f\u0648\u0627\u062a", +"Focus to element path": "\u0627\u0644\u062a\u0631\u0643\u064a\u0632 \u0639\u0644\u0649 \u0645\u0633\u0627\u0631 \u0627\u0644\u0639\u0646\u0635\u0631", +"Focus to contextual toolbar": "\u0627\u0644\u062a\u0631\u0643\u064a\u0632 \u0639\u0644\u0649 \u0634\u0631\u064a\u0637 \u0623\u062f\u0648\u0627\u062a \u0627\u0644\u0633\u064a\u0627\u0642", +"Insert link (if link plugin activated)": "\u0625\u0636\u0627\u0641\u0629 \u0631\u0627\u0628\u0637 (\u0625\u0630\u0627 \u0643\u0627\u0646\u062a \u0625\u0636\u0627\u0641\u0629 \u0627\u0644\u0631\u0648\u0627\u0628\u0637 \u0645\u0641\u0639\u0644\u0629)", +"Save (if save plugin activated)": "\u062d\u0641\u0638 (\u0625\u0630\u0627 \u0643\u0627\u0646\u062a \u0625\u0636\u0627\u0641\u0629 \u0627\u0644\u062d\u0641\u0638 \u0645\u0641\u0639\u0644\u0629)", +"Find (if searchreplace plugin activated)": "\u0627\u0644\u0628\u062d\u062b (\u0625\u0630\u0627 \u0643\u0627\u0646\u062a \u0625\u0636\u0627\u0641\u0629 \u0627\u0644\u0628\u062d\u062b \u0645\u0641\u0639\u0644\u0629)", +"Plugins installed ({0}):": "\u0627\u0644\u0625\u0636\u0627\u0641\u0627\u062a \u0627\u0644\u0645\u062b\u0628\u062a\u0629 ({0}):", +"Premium plugins:": "\u0627\u0644\u0625\u0636\u0627\u0641\u0627\u062a \u0627\u0644\u0645\u0645\u064a\u0632\u0629:", +"Learn more...": "\u0645\u0639\u0631\u0641\u0629 \u0627\u0644\u0645\u0632\u064a\u062f...", +"You are using {0}": "\u0623\u0646\u062a \u062a\u0633\u062a\u062e\u062f\u0645 {0}", +"Plugins": "\u0627\u0644\u0625\u0636\u0627\u0641\u0627\u062a", +"Handy Shortcuts": "\u0627\u062e\u062a\u0635\u0627\u0631\u0627\u062a \u0645\u0633\u0627\u0639\u0650\u062f\u0629", +"Horizontal line": "\u062e\u0637 \u0623\u0641\u0642\u064a", +"Insert\/edit image": "\u0625\u062f\u0631\u0627\u062c\/\u062a\u062d\u0631\u064a\u0631 \u0635\u0648\u0631\u0629", +"Image description": "\u0648\u0635\u0641 \u0627\u0644\u0635\u0648\u0631\u0629", +"Source": "\u0627\u0644\u0645\u0635\u062f\u0631", +"Dimensions": "\u0627\u0644\u0623\u0628\u0639\u0627\u062f", +"Constrain proportions": "\u0627\u0644\u062a\u0646\u0627\u0633\u0628", +"General": "\u0639\u0627\u0645", +"Advanced": "\u062e\u0635\u0627\u0626\u0635 \u0645\u062a\u0642\u062f\u0645\u0647", +"Style": "\u0627\u0644\u0646\u0645\u0637 \/ \u0627\u0644\u0634\u0643\u0644", +"Vertical space": "\u0645\u0633\u0627\u0641\u0629 \u0639\u0645\u0648\u062f\u064a\u0629", +"Horizontal space": "\u0645\u0633\u0627\u0641\u0629 \u0623\u0641\u0642\u064a\u0629", +"Border": "\u062d\u062f\u0648\u062f", +"Insert image": "\u0625\u062f\u0631\u0627\u062c \u0635\u0648\u0631\u0629", +"Image": "\u0627\u0644\u0635\u0648\u0631\u0629", +"Image list": "\u0642\u0627\u0626\u0645\u0629 \u0627\u0644\u0635\u0648\u0631", +"Rotate counterclockwise": "\u062a\u062f\u0648\u064a\u0631 \u0639\u0643\u0633 \u0627\u062a\u062c\u0627\u0647 \u0639\u0642\u0627\u0631\u0628 \u0627\u0644\u0633\u0627\u0639\u0629", +"Rotate clockwise": "\u062a\u062f\u0648\u064a\u0631 \u0641\u064a \u0627\u062a\u062c\u0627\u0647 \u0639\u0642\u0627\u0631\u0628 \u0627\u0644\u0633\u0627\u0639\u0629", +"Flip vertically": "\u0627\u0646\u0639\u0643\u0627\u0633 \u0639\u0627\u0645\u0648\u062f\u064a", +"Flip horizontally": "\u0627\u0646\u0639\u0643\u0627\u0633 \u0623\u0641\u0642\u064a", +"Edit image": "\u062a\u062d\u0631\u064a\u0631 \u0627\u0644\u0635\u0648\u0631\u0629", +"Image options": "\u0627\u0639\u062f\u0627\u062f\u0627\u062a \u0627\u0644\u0635\u0648\u0631\u0629", +"Zoom in": "\u062a\u0643\u0628\u064a\u0631", +"Zoom out": "\u062a\u0635\u063a\u064a\u0631", +"Crop": "\u0642\u0635", +"Resize": "\u062a\u063a\u064a\u064a\u0631 \u062d\u062c\u0645", +"Orientation": "\u0627\u0644\u0645\u062d\u0627\u0630\u0627\u0629", +"Brightness": "\u0627\u0644\u0625\u0636\u0627\u0621\u0629", +"Sharpen": "\u062d\u0627\u062f\u0629", +"Contrast": "\u0627\u0644\u062a\u0628\u0627\u064a\u0646", +"Color levels": "\u0645\u0633\u062a\u0648\u0649 \u0627\u0644\u0644\u0648\u0646", +"Gamma": "\u063a\u0627\u0645\u0627", +"Invert": "\u0639\u0643\u0633", +"Apply": "\u062a\u0637\u0628\u064a\u0642", +"Back": "\u0644\u0644\u062e\u0644\u0641", +"Insert date\/time": "\u0625\u062f\u0631\u0627\u062c \u062a\u0627\u0631\u064a\u062e\/\u0648\u0642\u062a", +"Date\/time": "\u0627\u0644\u062a\u0627\u0631\u064a\u062e\/\u0627\u0644\u0648\u0642\u062a", +"Insert link": "\u0625\u062f\u0631\u0627\u062c \u0631\u0627\u0628\u0637", +"Insert\/edit link": "\u0625\u062f\u0631\u0627\u062c\/\u062a\u062d\u0631\u064a\u0631 \u0631\u0627\u0628\u0637", +"Text to display": "\u0627\u0644\u0646\u0635 \u0627\u0644\u0645\u0637\u0644\u0648\u0628 \u0639\u0631\u0636\u0647", +"Url": "\u0627\u0644\u0639\u0646\u0648\u0627\u0646", +"Target": "\u0627\u0644\u0625\u0637\u0627\u0631 \u0627\u0644\u0647\u062f\u0641", +"None": "\u0628\u0644\u0627", +"New window": "\u0646\u0627\u0641\u0630\u0629 \u062c\u062f\u064a\u062f\u0629", +"Remove link": "\u062d\u0630\u0641 \u0627\u0644\u0631\u0627\u0628\u0637", +"Anchors": "\u0627\u0644\u0645\u0631\u0633\u0627\u0629", +"Link": "\u0627\u0644\u0631\u0627\u0628\u0637", +"Paste or type a link": "\u0623\u062f\u062e\u0644 \u0623\u0648 \u0627\u0643\u062a\u0628 \u0627\u0644\u0631\u0627\u0628\u0637", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "\u0627\u0644\u0631\u0627\u0628\u0637 \u0627\u0644\u0630\u064a \u0642\u0645\u062a \u0628\u0625\u062f\u0631\u0627\u062c\u0647 \u064a\u0634\u0627\u0628\u0647 \u0627\u0644\u0628\u0631\u064a\u062f \u0627\u0644\u0627\u0644\u0643\u062a\u0631\u0648\u0646\u064a. \u0647\u0644 \u062a\u0631\u064a\u062f \u0627\u0646 \u062a\u0636\u064a\u0641 \u0627\u0644\u0644\u0627\u062d\u0642\u0629 mailto: \u0645\u0639\u062a\u0628\u0631\u0627\u064b \u0647\u0630\u0627 \u0627\u0644\u0631\u0627\u0628\u0637 \u0628\u0631\u064a\u062f\u0627 \u0627\u0644\u0643\u062a\u0631\u0648\u0646\u064a\u0627\u064b\u061f", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "\u0646\u062a\u0648\u0642\u0639 \u0627\u0646\u0643 \u0642\u0645\u062a \u0628\u0625\u062f\u0631\u0627\u062c \u0631\u0627\u0628\u0637 \u0644\u0645\u0648\u0642\u0639 \u062e\u0627\u0631\u062c\u064a. \u0647\u0644 \u062a\u0631\u064a\u062f \u0627\u0646 \u0646\u0636\u064a\u0641 \u0627\u0644\u0644\u0627\u062d\u0642\u0629 http:\/\/ \u0644\u0644\u0631\u0627\u0628\u0637 \u0627\u0644\u0630\u064a \u0627\u062f\u062e\u0644\u062a\u0647\u061f", +"Link list": "\u0642\u0627\u0626\u0645\u0629 \u0627\u0644\u0631\u0648\u0627\u0628\u0637", +"Insert video": "\u0625\u062f\u0631\u0627\u062c \u0641\u064a\u062f\u064a\u0648", +"Insert\/edit video": "\u0625\u062f\u0631\u0627\u062c\/\u062a\u062d\u0631\u064a\u0631 \u0641\u064a\u062f\u064a\u0648", +"Insert\/edit media": "\u0625\u062f\u0631\u0627\u062c\/\u062a\u062d\u0631\u064a\u0631 \u0627\u0644\u0648\u0633\u0627\u0626\u0637 \u0627\u0644\u0645\u062a\u0639\u062f\u062f\u0629", +"Alternative source": "\u0645\u0635\u062f\u0631 \u0628\u062f\u064a\u0644", +"Poster": "\u0645\u0644\u0635\u0642", +"Paste your embed code below:": "\u0644\u0635\u0642 \u0643\u0648\u062f \u0627\u0644\u062a\u0636\u0645\u064a\u0646 \u0647\u0646\u0627:", +"Embed": "\u062a\u0636\u0645\u064a\u0646", +"Media": "\u0627\u0644\u0648\u0633\u0627\u0626\u0637 \u0627\u0644\u0645\u062a\u0639\u062f\u062f\u0629", +"Nonbreaking space": "\u0645\u0633\u0627\u0641\u0629 \u063a\u064a\u0631 \u0645\u0646\u0642\u0633\u0645\u0629", +"Page break": "\u0641\u0627\u0635\u0644 \u0644\u0644\u0635\u0641\u062d\u0629", +"Paste as text": "\u0644\u0635\u0642 \u0643\u0646\u0635", +"Preview": "\u0645\u0639\u0627\u064a\u0646\u0629", +"Print": "\u0637\u0628\u0627\u0639\u0629", +"Save": "\u062d\u0641\u0638", +"Find": "\u0628\u062d\u062b", +"Replace with": "\u0627\u0633\u062a\u0628\u062f\u0627\u0644 \u0628\u0640", +"Replace": "\u0627\u0633\u062a\u0628\u062f\u0627\u0644", +"Replace all": "\u0627\u0633\u062a\u0628\u062f\u0627\u0644 \u0627\u0644\u0643\u0644", +"Prev": "\u0627\u0644\u0633\u0627\u0628\u0642", +"Next": "\u0627\u0644\u062a\u0627\u0644\u064a", +"Find and replace": "\u0628\u062d\u062b \u0648\u0627\u0633\u062a\u0628\u062f\u0627\u0644", +"Could not find the specified string.": "\u062a\u0639\u0630\u0631 \u0627\u0644\u0639\u062b\u0648\u0631 \u0639\u0644\u0649 \u0627\u0644\u0643\u0644\u0645\u0629 \u0627\u0644\u0645\u062d\u062f\u062f\u0629", +"Match case": "\u0645\u0637\u0627\u0628\u0642\u0629 \u062d\u0627\u0644\u0629 \u0627\u0644\u0623\u062d\u0631\u0641", +"Whole words": "\u0645\u0637\u0627\u0628\u0642\u0629 \u0627\u0644\u0643\u0644\u0645\u0627\u062a \u0628\u0627\u0644\u0643\u0627\u0645\u0644", +"Spellcheck": "\u062a\u062f\u0642\u064a\u0642 \u0625\u0645\u0644\u0627\u0626\u064a", +"Ignore": "\u062a\u062c\u0627\u0647\u0644", +"Ignore all": "\u062a\u062c\u0627\u0647\u0644 \u0627\u0644\u0643\u0644", +"Finish": "\u0627\u0646\u062a\u0647\u064a", +"Add to Dictionary": "\u0627\u0636\u0641 \u0627\u0644\u064a \u0627\u0644\u0642\u0627\u0645\u0648\u0633", +"Insert table": "\u0625\u062f\u0631\u0627\u062c \u062c\u062f\u0648\u0644", +"Table properties": "\u062e\u0635\u0627\u0626\u0635 \u0627\u0644\u062c\u062f\u0648\u0644", +"Delete table": "\u062d\u0630\u0641 \u062c\u062f\u0648\u0644", +"Cell": "\u062e\u0644\u064a\u0629", +"Row": "\u0635\u0641", +"Column": "\u0639\u0645\u0648\u062f", +"Cell properties": "\u062e\u0635\u0627\u0626\u0635 \u0627\u0644\u062e\u0644\u064a\u0629", +"Merge cells": "\u062f\u0645\u062c \u062e\u0644\u0627\u064a\u0627", +"Split cell": "\u062a\u0642\u0633\u064a\u0645 \u0627\u0644\u062e\u0644\u0627\u064a\u0627", +"Insert row before": "\u0625\u062f\u0631\u0627\u062c \u0635\u0641 \u0644\u0644\u0623\u0639\u0644\u0649", +"Insert row after": "\u0625\u062f\u0631\u0627\u062c \u0635\u0641 \u0644\u0644\u0623\u0633\u0641\u0644", +"Delete row": "\u062d\u0630\u0641 \u0635\u0641", +"Row properties": "\u062e\u0635\u0627\u0626\u0635 \u0627\u0644\u0635\u0641", +"Cut row": "\u0642\u0635 \u0627\u0644\u0635\u0641", +"Copy row": "\u0646\u0633\u062e \u0627\u0644\u0635\u0641", +"Paste row before": "\u0644\u0635\u0642 \u0627\u0644\u0635\u0641 \u0644\u0644\u0623\u0639\u0644\u0649", +"Paste row after": "\u0644\u0635\u0642 \u0627\u0644\u0635\u0641 \u0644\u0644\u0623\u0633\u0641\u0644", +"Insert column before": "\u0625\u062f\u0631\u0627\u062c \u0639\u0645\u0648\u062f \u0644\u0644\u064a\u0633\u0627\u0631", +"Insert column after": "\u0625\u062f\u0631\u0627\u062c \u0639\u0645\u0648\u062f \u0644\u0644\u064a\u0645\u064a\u0646", +"Delete column": "\u062d\u0630\u0641 \u0639\u0645\u0648\u062f", +"Cols": "\u0639\u062f\u062f \u0627\u0644\u0623\u0639\u0645\u062f\u0629", +"Rows": "\u0639\u062f\u062f \u0627\u0644\u0635\u0641\u0648\u0641", +"Width": "\u0639\u0631\u0636", +"Height": "\u0627\u0631\u062a\u0641\u0627\u0639", +"Cell spacing": "\u0627\u0644\u0645\u0633\u0627\u0641\u0629 \u0628\u064a\u0646 \u0627\u0644\u062e\u0644\u0627\u064a\u0627", +"Cell padding": "\u062a\u0628\u0627\u0639\u062f \u0627\u0644\u062e\u0644\u064a\u0629", +"Caption": "\u0634\u0631\u062d", +"Left": "\u064a\u0633\u0627\u0631", +"Center": "\u062a\u0648\u0633\u064a\u0637", +"Right": "\u064a\u0645\u064a\u0646", +"Cell type": "\u0646\u0648\u0639 \u0627\u0644\u062e\u0644\u064a\u0629", +"Scope": "\u0627\u0644\u0645\u062c\u0627\u0644", +"Alignment": "\u0645\u062d\u0627\u0630\u0627\u0629", +"H Align": "\u0645\u062d\u0627\u0630\u0627\u0629 \u0623\u0641\u0642\u064a\u0629", +"V Align": "\u0645\u062d\u0627\u0630\u0627\u0629 \u0631\u0623\u0633\u064a\u0629", +"Top": "\u0623\u0639\u0644\u064a", +"Middle": "\u0627\u0644\u0648\u0633\u0637", +"Bottom": "\u0627\u0644\u0623\u0633\u0641\u0644", +"Header cell": "\u0631\u0623\u0633 \u0627\u0644\u062e\u0644\u064a\u0629", +"Row group": "\u0645\u062c\u0645\u0648\u0639\u0629 \u0635\u0641", +"Column group": "\u0645\u062c\u0645\u0648\u0639\u0629 \u0639\u0645\u0648\u062f", +"Row type": "\u0646\u0648\u0639 \u0627\u0644\u0635\u0641", +"Header": "\u0627\u0644\u0631\u0623\u0633", +"Body": "\u0647\u064a\u0643\u0644", +"Footer": "\u062a\u0630\u064a\u064a\u0644", +"Border color": "\u0644\u0648\u0646 \u0627\u0644\u0625\u0637\u0627\u0631", +"Insert template": "\u0625\u062f\u0631\u0627\u062c \u0642\u0627\u0644\u0628", +"Templates": "\u0642\u0648\u0627\u0644\u0628", +"Template": "\u0627\u0644\u0642\u0627\u0644\u0628", +"Text color": "\u0644\u0648\u0646 \u0627\u0644\u0646\u0635", +"Background color": "\u0644\u0648\u0646 \u0627\u0644\u062e\u0644\u0641\u064a\u0629", +"Custom...": "\u062a\u062e\u0635\u064a\u0635 ...", +"Custom color": "\u0644\u0648\u0646 \u0645\u062e\u0635\u0635", +"No color": "\u0628\u062f\u0648\u0646 \u0644\u0648\u0646", +"Table of Contents": "\u062c\u062f\u0648\u0644 \u0627\u0644\u0645\u062d\u062a\u0648\u064a\u0627\u062a", +"Show blocks": "\u0645\u0634\u0627\u0647\u062f\u0629 \u0627\u0644\u0643\u062a\u0644", +"Show invisible characters": "\u0623\u0638\u0647\u0631 \u0627\u0644\u0623\u062d\u0631\u0641 \u0627\u0644\u063a\u064a\u0631 \u0645\u0631\u0626\u064a\u0629", +"Words: {0}": "\u0627\u0644\u0643\u0644\u0645\u0627\u062a:{0}", +"{0} words": "{0} \u0643\u0644\u0645\u0627\u062a", +"File": "\u0645\u0644\u0641", +"Edit": "\u062a\u062d\u0631\u064a\u0631", +"Insert": "\u0625\u062f\u0631\u0627\u062c", +"View": "\u0639\u0631\u0636", +"Format": "\u062a\u0646\u0633\u064a\u0642", +"Table": "\u062c\u062f\u0648\u0644", +"Tools": "\u0623\u062f\u0627\u0648\u0627\u062a", +"Powered by {0}": "\u0645\u062f\u0639\u0648\u0645 \u0645\u0646 {0}", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "\u0645\u0646\u0637\u0642\u0629 \u0646\u0635 \u0645\u0646\u0633\u0642. \u0627\u0636\u063a\u0637 ALT-F9 \u0644\u0644\u0642\u0627\u0626\u0645\u0629. \u0627\u0636\u063a\u0637 ALT-F10 \u0644\u0634\u0631\u064a\u0637 \u0627\u0644\u0623\u062f\u0648\u0627\u062a. \u0627\u0636\u063a\u0637 ALT-0 \u0644\u0644\u062d\u0635\u0648\u0644 \u0639\u0644\u0649 \u0645\u0633\u0627\u0639\u062f\u0629", +"_dir": "rtl" +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/az.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/az.js new file mode 100644 index 0000000000..cbd5d4700e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/az.js @@ -0,0 +1,261 @@ +tinymce.addI18n('az',{ +"Redo": "\u0130r\u0259li", +"Undo": "Geriy\u0259", +"Cut": "K\u0259s", +"Copy": "K\u00f6\u00e7\u00fcr", +"Paste": "\u018flav\u0259 et", +"Select all": "Ham\u0131s\u0131n\u0131 se\u00e7", +"New document": "Yeni s\u0259n\u0259d", +"Ok": "Oldu", +"Cancel": "L\u0259\u011fv et", +"Visual aids": "Konturlar\u0131 g\u00f6st\u0259r", +"Bold": "Qal\u0131n", +"Italic": "Maili", +"Underline": "Alt x\u0259ttli", +"Strikethrough": "Silinmi\u015f", +"Superscript": "Yuxar\u0131 indeks", +"Subscript": "A\u015fa\u011f\u0131 indeks", +"Clear formatting": "Format\u0131 t\u0259mizl\u0259", +"Align left": "Sol t\u0259r\u0259f \u00fczr\u0259", +"Align center": "M\u0259rk\u0259z \u00fczr\u0259", +"Align right": "Sa\u011f t\u0259r\u0259f \u00fczr\u0259", +"Justify": "H\u0259r iki t\u0259r\u0259f \u00fczr\u0259", +"Bullet list": "S\u0131ras\u0131z siyah\u0131", +"Numbered list": "N\u00f6mr\u0259l\u0259nmi\u015f siyah\u0131", +"Decrease indent": "Bo\u015flu\u011fu azalt", +"Increase indent": "Bo\u015flu\u011fu art\u0131r", +"Close": "Ba\u011fla", +"Formats": "Formatlar", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "Sizin brauzeriniz m\u00fcbadil\u0259 buferin\u0259 birba\u015fa yolu d\u0259st\u0259kl\u0259mir. Z\u0259hm\u0259t olmasa, bunun yerin\u0259 klaviaturan\u0131n Ctrl+X\/C\/V d\u00fcym\u0259l\u0259rind\u0259n istifad\u0259 edin.", +"Headers": "Ba\u015fl\u0131qlar", +"Header 1": "Ba\u015fl\u0131q 1", +"Header 2": "Ba\u015fl\u0131q 2", +"Header 3": "Ba\u015fl\u0131q 3", +"Header 4": "Ba\u015fl\u0131q 4", +"Header 5": "Ba\u015fl\u0131q 5", +"Header 6": "Ba\u015fl\u0131q 6", +"Headings": "Ba\u015fl\u0131qlar", +"Heading 1": "Ba\u015fl\u0131q 1", +"Heading 2": "Ba\u015fl\u0131q 2", +"Heading 3": "Ba\u015fl\u0131q 3", +"Heading 4": "Ba\u015fl\u0131q 4", +"Heading 5": "Ba\u015fl\u0131q 5", +"Heading 6": "Ba\u015fl\u0131q 6", +"Preformatted": "\u018fvv\u0259lc\u0259d\u0259n formatland\u0131r\u0131lm\u0131\u015f", +"Div": "Div", +"Pre": "Pre", +"Code": "Kod", +"Paragraph": "Paraqraf", +"Blockquote": "Sitat", +"Inline": "S\u0259tir i\u00e7i", +"Blocks": "Bloklar", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "Hal-haz\u0131rda adi m\u0259tn rejimind\u0259 yerl\u0259\u015fdirilir. M\u0259zmun sad\u0259 m\u0259tn \u015f\u0259klind\u0259 yerl\u0259\u015fdiril\u0259c\u0259k, h\u0259l\u0259 bu se\u00e7imi d\u0259yi\u015fdirm\u0259.", +"Font Family": "Font stili", +"Font Sizes": "Font \u00f6l\u00e7\u00fcl\u0259ri", +"Class": "Sinif", +"Browse for an image": "\u015e\u0259kil se\u00e7", +"OR": "V\u018f YA", +"Drop an image here": "\u015e\u0259kli buraya s\u00fcr\u00fckl\u0259yin", +"Upload": "Y\u00fckl\u0259", +"Block": "Blokla", +"Align": "D\u00fczl\u0259ndir", +"Default": "Susmaya g\u00f6r\u0259", +"Circle": "Dair\u0259", +"Disc": "Disk", +"Square": "Sah\u0259", +"Lower Alpha": "Ki\u00e7ik Alfa \u0259lifbas\u0131", +"Lower Greek": "Ki\u00e7ik Yunan \u0259lifbas\u0131", +"Lower Roman": "Ki\u00e7ik Roma \u0259lifbas\u0131", +"Upper Alpha": "B\u00f6y\u00fck Alfa \u0259lifbas\u0131", +"Upper Roman": "B\u00f6y\u00fck Roma \u0259lifbas\u0131", +"Anchor": "L\u00f6vb\u0259r", +"Name": "Ad", +"Id": "ID", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "\u0130D h\u0259rfl\u0259 ba\u015flamal\u0131d\u0131r. Daha sonra is\u0259 h\u0259rf, r\u0259q\u0259m, tire, n\u00f6qt\u0259, qo\u015fan\u00f6qt\u0259, v\u0259 altx\u0259tt kimi simvollardan istifad\u0259 oluna bil\u0259r.", +"You have unsaved changes are you sure you want to navigate away?": "Sizd\u0259 yadda saxlan\u0131lmayan d\u0259yi\u015fiklikl\u0259r var \u0259minsiniz ki, getm\u0259k ist\u0259yirsiniz?", +"Restore last draft": "Son layih\u0259nin b\u0259rpas\u0131", +"Special character": "X\u00fcsusi simvollar", +"Source code": "M\u0259nb\u0259 kodu", +"Insert\/Edit code sample": "Kod n\u00fcmun\u0259sin\u0259 \u0259lav\u0259\/d\u00fcz\u0259li\u015f et", +"Language": "Dil", +"Code sample": "Kod n\u00fcmun\u0259si", +"Color": "R\u0259ng", +"R": "R", +"G": "G", +"B": "B", +"Left to right": "Soldan sa\u011fa", +"Right to left": "Sa\u011fdan sola", +"Emoticons": "Emosiyalar", +"Document properties": "S\u0259n\u0259din x\u00fcsusiyy\u0259tl\u0259ri", +"Title": "Ba\u015fl\u0131q", +"Keywords": "A\u00e7ar s\u00f6zl\u0259r", +"Description": "T\u0259sviri", +"Robots": "Robotlar", +"Author": "M\u00fc\u0259llif", +"Encoding": "Kodla\u015fd\u0131rma", +"Fullscreen": "Tam ekran rejimi", +"Action": "\u018fmr", +"Shortcut": "Q\u0131sayol", +"Help": "K\u00f6m\u0259k", +"Address": "Adres", +"Focus to menubar": "Menyu \u00e7ubu\u011funa diqq\u0259t et", +"Focus to toolbar": "Al\u0259tl\u0259r \u00e7ubu\u011funa diqq\u0259t et", +"Focus to element path": "Elementin m\u0259nb\u0259yin\u0259 diqq\u0259t et", +"Focus to contextual toolbar": "Kontekst menyuya diqq\u0259t et", +"Insert link (if link plugin activated)": "Link \u0259lav\u0259 et (\u0259g\u0259r link \u0259lav\u0259si aktivdirs\u0259)", +"Save (if save plugin activated)": "Yadda\u015fa yaz (\u0259g\u0259r yadda\u015fa yaz \u0259lav\u0259si aktivdirs\u0259)", +"Find (if searchreplace plugin activated)": "Tap (\u0259g\u0259r axtar\u0131\u015f \u0259lav\u0259si aktivdirs\u0259)", +"Plugins installed ({0}):": "\u018flav\u0259l\u0259r y\u00fckl\u0259ndi ({0}):", +"Premium plugins:": "Premium \u0259lav\u0259l\u0259r", +"Learn more...": "Daha \u00e7ox \u00f6yr\u0259n...", +"You are using {0}": "Siz {0} istifad\u0259 edirsiniz", +"Plugins": "\u018flav\u0259l\u0259r", +"Handy Shortcuts": "Laz\u0131ml\u0131 q\u0131sayollar", +"Horizontal line": "Horizontal x\u0259tt", +"Insert\/edit image": "\u015e\u0259kilin \u0259lav\u0259\/redakt\u0259 edilm\u0259si", +"Image description": "\u015e\u0259kilin t\u0259sviri", +"Source": "M\u0259nb\u0259", +"Dimensions": "\u00d6l\u00e7\u00fcl\u0259r", +"Constrain proportions": "Nisb\u0259tl\u0259rin saxlan\u0131lmas\u0131", +"General": "\u00dcmumi", +"Advanced": "Geni\u015fl\u0259nmi\u015f", +"Style": "Stil", +"Vertical space": "Vertikal sah\u0259", +"Horizontal space": "Horizontal sah\u0259", +"Border": "\u00c7\u0259r\u00e7iv\u0259", +"Insert image": "\u015e\u0259kil yerl\u0259\u015fdir", +"Image": "\u015e\u0259kil", +"Image list": "\u015e\u0259kil listi", +"Rotate counterclockwise": "Saat \u0259qr\u0259binin \u0259ksin\u0259 f\u0131rlat", +"Rotate clockwise": "Saat \u0259qr\u0259bi istiqam\u0259tind\u0259 f\u0131rlat", +"Flip vertically": "\u015eaquli \u00e7evir", +"Flip horizontally": "\u00dcfiqi \u00e7evir", +"Edit image": "\u015e\u0259kili redakt\u0259 et", +"Image options": "\u015e\u0259kil parametrl\u0259ri", +"Zoom in": "Yax\u0131nla\u015fd\u0131r", +"Zoom out": "Uzaqla\u015fd\u0131r", +"Crop": "K\u0259s", +"Resize": "\u00d6l\u00e7\u00fcl\u0259ri d\u0259yi\u015f", +"Orientation": "Oriyentasiya", +"Brightness": "Parlaql\u0131q", +"Sharpen": "K\u0259skinl\u0259\u015fdir", +"Contrast": "Ziddiyy\u0259t", +"Color levels": "R\u0259ng s\u0259viyy\u0259l\u0259ri", +"Gamma": "Qamma", +"Invert": "T\u0259rsin\u0259 \u00e7evir", +"Apply": "T\u0259tbiq et", +"Back": "Geri", +"Insert date\/time": "G\u00fcn\/tarix \u0259lav\u0259 et", +"Date\/time": "Tarix\/saat", +"Insert link": "Linkin \u0259lav\u0259 edilm\u0259si", +"Insert\/edit link": "Linkin \u0259lav\u0259\/redakt\u0259 edilm\u0259si", +"Text to display": "G\u00f6r\u00fcn\u0259n yaz\u0131n\u0131n t\u0259sviri", +"Url": "Linkin \u00fcnvan\u0131", +"Target": "H\u0259d\u0259f", +"None": "Yoxdur", +"New window": "Yeni p\u0259nc\u0259r\u0259d\u0259 a\u00e7\u0131ls\u0131n", +"Remove link": "Linki sil", +"Anchors": "L\u00f6vb\u0259rl\u0259r", +"Link": "Ke\u00e7id", +"Paste or type a link": "Ke\u00e7idi yerl\u0259\u015fdirin v\u0259 ya yaz\u0131n", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "Daxil etdiyiniz URL bir e-mail kimi g\u00f6r\u00fcn\u00fcr. \u018fg\u0259r t\u0259l\u0259b olunan mailto: prefix \u0259lav\u0259 etm\u0259k ist\u0259yirsiniz?", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "Daxil etdiyiniz URL bir e-mail kimi g\u00f6r\u00fcn\u00fcr. \u018fg\u0259r t\u0259l\u0259b olunan mailto: prefix \u0259lav\u0259 etm\u0259k ist\u0259yirsiniz?", +"Link list": "Ke\u00e7id listi", +"Insert video": "Videonun \u0259lav\u0259 edilm\u0259si", +"Insert\/edit video": "Videonun \u0259lav\u0259\/redakt\u0259 edilm\u0259si", +"Insert\/edit media": "Media \u0259lav\u0259\/d\u00fcz\u0259li\u015f et", +"Alternative source": "Alternativ m\u0259nb\u0259", +"Poster": "Poster", +"Paste your embed code below:": "\u00d6z kodunuzu a\u015fa\u011f\u0131 \u0259lav\u0259 edin:", +"Embed": "\u018flav\u0259 etm\u0259k \u00fc\u00e7\u00fcn kod", +"Media": "Media", +"Nonbreaking space": "Q\u0131r\u0131lmaz sah\u0259", +"Page break": "S\u0259hif\u0259nin q\u0131r\u0131lmas\u0131", +"Paste as text": "M\u0259tn kimi \u0259lav\u0259 et", +"Preview": "\u0130lkinbax\u0131\u015f", +"Print": "\u00c7ap et", +"Save": "Yadda saxla", +"Find": "Tap", +"Replace with": "Bununla d\u0259yi\u015fdir", +"Replace": "D\u0259yi\u015fdir", +"Replace all": "Ham\u0131s\u0131n\u0131 d\u0259yi\u015fdir", +"Prev": "\u018fvv\u0259lki", +"Next": "N\u00f6vb\u0259ti", +"Find and replace": "Tap v\u0259 d\u0259yi\u015fdir", +"Could not find the specified string.": "G\u00f6st\u0259ril\u0259n s\u0259tiri tapmaq olmur", +"Match case": "Registri n\u0259z\u0259r\u0259 al", +"Whole words": "Tam s\u00f6zl\u0259r", +"Spellcheck": "Orfoqrafiyan\u0131 yoxla", +"Ignore": "\u0130qnorla", +"Ignore all": "Ham\u0131s\u0131n\u0131 iqnorla", +"Finish": "Bitir", +"Add to Dictionary": "L\u00fc\u011f\u0259t\u0259 \u0259lav\u0259 edilsin", +"Insert table": "S\u0259tir \u0259lav\u0259 et", +"Table properties": "C\u0259dv\u0259lin x\u00fcsusiyy\u0259tl\u0259ri", +"Delete table": "C\u0259dv\u0259li sil", +"Cell": "H\u00fccr\u0259", +"Row": "S\u0259tir", +"Column": "S\u00fctun", +"Cell properties": "H\u00fccr\u0259nin x\u00fcsusiyy\u0259tl\u0259ri", +"Merge cells": "H\u00fccr\u0259l\u0259ri birl\u0259\u015ftir", +"Split cell": "H\u00fccr\u0259l\u0259rin say\u0131", +"Insert row before": "\u018fvv\u0259lin\u0259 s\u0259tir \u0259lav\u0259 et", +"Insert row after": "Sonras\u0131na s\u0259tir \u0259lav\u0259 et", +"Delete row": "S\u0259tri sil", +"Row properties": "S\u0259trin x\u00fcsusiyy\u0259tl\u0259ri", +"Cut row": "S\u0259tiri k\u0259s", +"Copy row": "S\u0259tiri k\u00f6\u00e7\u00fcr", +"Paste row before": "\u018fvv\u0259lin\u0259 s\u0259tir \u0259lav\u0259 et", +"Paste row after": "Sonras\u0131na s\u0259tir \u0259lav\u0259 et", +"Insert column before": "\u018fvv\u0259lin\u0259 s\u0259tir \u0259lav\u0259 et", +"Insert column after": "\u018fvv\u0259lin\u0259 s\u00fctun \u0259lav\u0259 et", +"Delete column": "S\u00fctunu sil", +"Cols": "S\u00fctunlar", +"Rows": "S\u0259tirl\u0259r", +"Width": "Eni", +"Height": "H\u00fcnd\u00fcrl\u00fcy\u00fc", +"Cell spacing": "H\u00fccr\u0259l\u0259rin aras\u0131nda m\u0259saf\u0259", +"Cell padding": "H\u00fccr\u0259l\u0259rin sah\u0259l\u0259ri", +"Caption": "Ba\u015flan\u011f\u0131c", +"Left": "Sol t\u0259r\u0259f \u00fczr\u0259", +"Center": "M\u0259rk\u0259z \u00fczr\u0259", +"Right": "Sa\u011f t\u0259r\u0259f \u00fczr\u0259", +"Cell type": "H\u00fccr\u0259nin tipi", +"Scope": "Sfera", +"Alignment": "D\u00fczl\u0259ndirm\u0259", +"H Align": "H D\u00fczl\u0259ndir", +"V Align": "V D\u00fczl\u0259ndir", +"Top": "Yuxar\u0131", +"Middle": "Orta", +"Bottom": "A\u015fa\u011f\u0131", +"Header cell": "H\u00fccr\u0259nin ba\u015fl\u0131\u011f\u0131", +"Row group": "S\u0259tirin qrupu", +"Column group": "S\u00fctunun qrupu", +"Row type": "S\u0259tirin tipi", +"Header": "Ba\u015fl\u0131q", +"Body": "K\u00fctl\u0259", +"Footer": "\u018fn a\u015fa\u011f\u0131", +"Border color": "\u00c7\u0259r\u00e7iv\u0259 r\u0259ngi", +"Insert template": "\u015eablon \u0259lav\u0259 et", +"Templates": "\u015eablonlar", +"Template": "\u015eablon", +"Text color": "M\u0259tnin r\u0259ngi", +"Background color": "Arxafon r\u0259ngi", +"Custom...": "\u00c7\u0259kilm\u0259...", +"Custom color": "\u00c7\u0259kilm\u0259 r\u0259ng", +"No color": "R\u0259ngsiz", +"Table of Contents": "M\u00fcnd\u0259ricat", +"Show blocks": "Bloklar\u0131 g\u00f6st\u0259r", +"Show invisible characters": "G\u00f6r\u00fcnm\u0259y\u0259n simvollar\u0131 g\u00f6st\u0259r", +"Words: {0}": "S\u00f6zl\u0259r: {0}", +"{0} words": "{0} s\u00f6z", +"File": "Fayl", +"Edit": "Redakt\u0259 et", +"Insert": "\u018flav\u0259 et", +"View": "G\u00f6r\u00fcn\u00fc\u015f", +"Format": "Format", +"Table": "C\u0259dv\u0259l", +"Tools": "Al\u0259tl\u0259r", +"Powered by {0}": "{0} t\u0259r\u0259find\u0259n t\u0259chiz edilib", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "B\u00f6y\u00fck m\u0259tn sah\u0259si \u0259lav\u0259 edilib. Menyu \u00fc\u00e7\u00fcn ALT-F9 d\u00fcym\u0259sini bas\u0131n. Al\u0259tl\u0259r paneli \u00fc\u00e7\u00fcn ALT-F10 d\u00fcym\u0259sini bas\u0131n. K\u00f6m\u0259k \u00fc\u00e7\u00fcn ALT-0 d\u00fcym\u0259l\u0259rin bas\u0131n." +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/be.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/be.js new file mode 100644 index 0000000000..bc9fc08f76 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/be.js @@ -0,0 +1,261 @@ +tinymce.addI18n('be',{ +"Redo": "\u0410\u0434\u043c\u044f\u043d\u0456\u0446\u044c", +"Undo": "\u0412\u044f\u0440\u043d\u0443\u0446\u044c", +"Cut": "\u0412\u044b\u0440\u0430\u0437\u0430\u0446\u044c", +"Copy": "\u041a\u0430\u043f\u0456\u0440\u0430\u0432\u0430\u0446\u044c", +"Paste": "\u0423\u0441\u0442\u0430\u0432\u0456\u0446\u044c", +"Select all": "\u0412\u044b\u043b\u0443\u0447\u044b\u0446\u044c \u0443\u0441\u0451", +"New document": "\u041d\u043e\u0432\u044b \u0434\u0430\u043a\u0443\u043c\u0435\u043d\u0442", +"Ok": "Ok", +"Cancel": "\u0410\u0434\u043c\u044f\u043d\u0456\u0446\u044c", +"Visual aids": "\u041f\u0430\u043a\u0430\u0437\u0432\u0430\u0446\u044c \u043a\u043e\u043d\u0442\u0443\u0440\u044b", +"Bold": "\u0422\u043b\u0443\u0441\u0442\u044b", +"Italic": "\u041a\u0443\u0440\u0441\u0456\u045e", +"Underline": "\u041f\u0430\u0434\u043a\u0440\u044d\u0441\u043b\u0435\u043d\u044b", +"Strikethrough": "\u0417\u0430\u043a\u0440\u044d\u0441\u043b\u0435\u043d\u044b", +"Superscript": "\u0412\u0435\u0440\u0445\u043d\u0456 \u0456\u043d\u0434\u044d\u043a\u0441", +"Subscript": "\u041d\u0456\u0436\u043d\u0456 \u0456\u043d\u0434\u044d\u043a\u0441", +"Clear formatting": "\u0410\u0447\u044b\u0441\u0446\u0456\u0446\u044c \u0444\u0430\u0440\u043c\u0430\u0442", +"Align left": "\u041f\u0430 \u043b\u0435\u0432\u044b\u043c \u043a\u0440\u0430\u0456", +"Align center": "\u041f\u0430 \u0446\u044d\u043d\u0442\u0440\u044b", +"Align right": "\u041f\u0430 \u043f\u0440\u0430\u0432\u044b\u043c \u043a\u0440\u0430\u0456", +"Justify": "\u041f\u0430 \u0448\u044b\u0440\u044b\u043d\u0456", +"Bullet list": "\u041c\u0430\u0440\u043a\u0456\u0440\u0430\u0432\u0430\u043d\u044b \u0441\u043f\u0456\u0441", +"Numbered list": "\u041d\u0443\u043c\u0430\u0440\u0430\u0432\u0430\u043d\u044b \u0441\u043f\u0456\u0441", +"Decrease indent": "\u041f\u0430\u043c\u0435\u043d\u0448\u044b\u0446\u044c \u0432\u043e\u0434\u0441\u0442\u0443\u043f", +"Increase indent": "\u041f\u0430\u0432\u044f\u043b\u0456\u0447\u044b\u0446\u044c \u0432\u043e\u0434\u0441\u0442\u0443\u043f", +"Close": "\u0417\u0430\u0447\u044b\u043d\u0456\u0446\u044c", +"Formats": "\u0424\u0430\u0440\u043c\u0430\u0442", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "\u0412\u0430\u0448 \u0431\u0440\u0430\u045e\u0437\u044d\u0440 \u043d\u0435 \u043f\u0430\u0434\u0442\u0440\u044b\u043c\u043b\u0456\u0432\u0430\u0435 \u043f\u0440\u0430\u043c\u044b \u0434\u043e\u0441\u0442\u0443\u043f \u0434\u0430 \u0431\u0443\u0444\u0435\u0440\u0430 \u0430\u0431\u043c\u0435\u043d\u0443. \u041a\u0430\u043b\u0456 \u043b\u0430\u0441\u043a\u0430, \u0432\u044b\u043a\u0430\u0440\u044b\u0441\u0442\u043e\u045e\u0432\u0430\u0439\u0446\u0435 \u043d\u0430\u0441\u0442\u0443\u043f\u043d\u044b\u044f \u0441\u043f\u0430\u043b\u0443\u0447\u044d\u043d\u043d\u044f \u043a\u043b\u0430\u0432\u0456\u0448: Ctrl + X\/C\/V.", +"Headers": "\u0417\u0430\u0433\u0430\u043b\u043e\u045e\u043a\u0456", +"Header 1": "\u0417\u0430\u0433\u0430\u043b\u043e\u0432\u0430\u043a 1", +"Header 2": "\u0417\u0430\u0433\u0430\u043b\u043e\u0432\u0430\u043a 2", +"Header 3": "\u0417\u0430\u0433\u0430\u043b\u043e\u0432\u0430\u043a 3", +"Header 4": "\u0417\u0430\u0433\u0430\u043b\u043e\u0432\u0430\u043a 4", +"Header 5": "\u0417\u0430\u0433\u0430\u043b\u043e\u0432\u0430\u043a 5", +"Header 6": "\u0417\u0430\u0433\u0430\u043b\u043e\u0432\u0430\u043a 6", +"Headings": "\u0417\u0430\u0433\u0430\u043b\u043e\u045e\u043a\u0456", +"Heading 1": "\u0417\u0430\u0433\u0430\u043b\u043e\u0432\u0430\u043a 1", +"Heading 2": "\u0417\u0430\u0433\u0430\u043b\u043e\u0432\u0430\u043a 2", +"Heading 3": "\u0417\u0430\u0433\u0430\u043b\u043e\u0432\u0430\u043a 3", +"Heading 4": "\u0417\u0430\u0433\u0430\u043b\u043e\u0432\u0430\u043a 4", +"Heading 5": "\u0417\u0430\u0433\u0430\u043b\u043e\u0432\u0430\u043a 5", +"Heading 6": "\u0417\u0430\u0433\u0430\u043b\u043e\u0432\u0430\u043a 6", +"Preformatted": "\u0412\u044b\u0440\u0430\u045e\u043d\u0430\u0432\u0430\u043d\u044b", +"Div": "\u0411\u043b\u043e\u043a", +"Pre": "\u041f\u0440\u0430\u0434\u0444\u0430\u0440\u043c\u0430\u0442\u0430\u0432\u0430\u043d\u043d\u0435", +"Code": "\u041a\u043e\u0434", +"Paragraph": "\u041f\u0430\u0440\u0430\u0433\u0440\u0430\u0444", +"Blockquote": "\u0426\u044b\u0442\u0430\u0442\u0430", +"Inline": "\u0420\u0430\u0434\u043a\u043e\u0432\u044b", +"Blocks": "\u0411\u043b\u043e\u043a\u0456", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "\u0423\u0441\u0442\u0430\u045e\u043a\u0430 \u0437\u0434\u0437\u044f\u0439\u0441\u043d\u044f\u0435\u0446\u0446\u0430 \u045e \u0432\u044b\u0433\u043b\u044f\u0434\u0437\u0435 \u043f\u0440\u043e\u0441\u0442\u0430\u0433\u0430 \u0442\u044d\u043a\u0441\u0442\u0443, \u043f\u0430\u043a\u0443\u043b\u044c \u043d\u0435 \u0430\u0434\u043a\u043b\u044e\u0447\u044b\u0446\u044c \u0434\u0430\u0434\u0437\u0435\u043d\u0443\u044e \u043e\u043f\u0446\u044b\u044e.", +"Font Family": "\u0428\u0440\u044b\u0444\u0442", +"Font Sizes": "\u041f\u0430\u043c\u0435\u0440 \u0448\u0440\u044b\u0444\u0442\u0430", +"Class": "\u041a\u043b\u0430\u0441", +"Browse for an image": "\u041f\u043e\u0448\u0443\u043a \u0432\u044b\u044f\u0432\u044b", +"OR": "\u0410\u0411\u041e", +"Drop an image here": "\u0410\u0434\u043a\u0456\u043d\u044c\u0446\u0435 \u0432\u044b\u044f\u0432\u0443 \u0442\u0443\u0442", +"Upload": "\u0417\u0430\u043f\u0430\u043c\u043f\u0430\u0432\u0430\u0446\u044c", +"Block": "\u0417\u0430\u0431\u043b\u0430\u043a\u0430\u0432\u0430\u0446\u044c", +"Align": "\u0412\u044b\u0440\u0430\u045e\u043d\u043e\u045e\u0432\u0430\u043d\u043d\u0435", +"Default": "\u0421\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b", +"Circle": "\u0410\u043a\u0440\u0443\u0436\u043d\u0430\u0441\u0446\u0456", +"Disc": "\u041a\u0440\u0443\u0433\u0456", +"Square": "\u041a\u0432\u0430\u0434\u0440\u0430\u0442\u044b", +"Lower Alpha": "\u041c\u0430\u043b\u044b\u044f \u043b\u0430\u0446\u0456\u043d\u0441\u043a\u0456\u044f \u043b\u0456\u0442\u0430\u0440\u044b", +"Lower Greek": "\u041c\u0430\u043b\u044b\u044f \u0433\u0440\u044d\u0447\u0430\u0441\u043a\u0456\u044f \u043b\u0456\u0442\u0430\u0440\u044b", +"Lower Roman": "\u041c\u0430\u043b\u044b\u044f \u0440\u044b\u043c\u0441\u043a\u0456\u044f \u043b\u0456\u0447\u0431\u044b", +"Upper Alpha": "\u0417\u0430\u0433\u0430\u043b\u043e\u045e\u043d\u044b\u044f \u043b\u0430\u0446\u0456\u043d\u0441\u043a\u0456\u044f \u043b\u0456\u0442\u0430\u0440\u044b", +"Upper Roman": "\u0417\u0430\u0433\u0430\u043b\u043e\u045e\u043d\u044b\u044f \u0440\u044b\u043c\u0441\u043a\u0456\u044f \u043b\u0456\u0447\u0431\u044b", +"Anchor": "\u042f\u043a\u0430\u0440", +"Name": "\u0406\u043c\u044f", +"Id": "Id", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "Id \u043f\u0430\u0432\u0456\u043d\u0435\u043d \u043f\u0430\u0447\u044b\u043d\u0430\u0446\u0446\u0430 \u0437 \u043b\u0456\u0442\u0430\u0440\u044b, \u0430 \u043f\u043e\u0442\u044b\u043c \u0443\u0442\u0440\u044b\u043c\u043b\u0456\u0432\u0430\u0446\u044c \u0442\u043e\u043b\u044c\u043a\u0456 \u043b\u0456\u0442\u0430\u0440\u044b, \u043b\u0456\u0447\u0431\u044b, \u043f\u0440\u0430\u0446\u044f\u0436\u043d\u0456\u043a, \u043a\u0440\u043e\u043f\u043a\u0456, \u0434\u0432\u0443\u043a\u0440\u043e\u043f'\u044f \u0446\u0456 \u043f\u0430\u0434\u043a\u0440\u044d\u0441\u043b\u0456\u0432\u0430\u043d\u043d\u0456.", +"You have unsaved changes are you sure you want to navigate away?": "\u0423 \u0432\u0430\u0441 \u0451\u0441\u0446\u044c \u043d\u0435\u0437\u0430\u0445\u0430\u0432\u0430\u043d\u044b\u044f \u0437\u043c\u0435\u043d\u044b. \u0412\u044b \u045e\u043f\u044d\u045e\u043d\u0435\u043d\u044b\u044f, \u0448\u0442\u043e \u0445\u043e\u0447\u0430\u0446\u0435 \u0432\u044b\u0439\u0441\u0446\u0456?", +"Restore last draft": "\u0410\u0434\u043d\u0430\u045e\u043b\u0435\u043d\u043d\u0435 \u0430\u043f\u043e\u0448\u043d\u044f\u0433\u0430 \u043f\u0440\u0430\u0435\u043a\u0442\u0430", +"Special character": "\u0421\u043f\u0435\u0446\u044b\u044f\u043b\u044c\u043d\u044b\u044f \u0441\u0456\u043c\u0432\u0430\u043b\u044b", +"Source code": "\u0417\u044b\u0445\u043e\u0434\u043d\u044b \u043a\u043e\u0434", +"Insert\/Edit code sample": "\u0423\u0441\u0442\u0430\u0432\u0456\u0446\u044c\/\u0440\u044d\u0434\u0430\u0433\u0430\u0432\u0430\u0446\u044c \u043a\u043e\u0434", +"Language": "\u041c\u043e\u0432\u0430", +"Code sample": "\u041f\u0440\u044b\u043a\u043b\u0430\u0434 \u043a\u043e\u0434\u0430", +"Color": "\u041a\u043e\u043b\u0435\u0440", +"R": "R", +"G": "G", +"B": "B", +"Left to right": "\u0417\u043b\u0435\u0432\u0430 \u043d\u0430\u043f\u0440\u0430\u0432\u0430", +"Right to left": "\u0421\u043f\u0440\u0430\u0432\u0430 \u043d\u0430\u043b\u0435\u0432\u0430", +"Emoticons": "\u0414\u0430\u0434\u0430\u0446\u044c \u0441\u043c\u0430\u0439\u043b", +"Document properties": "\u0423\u043b\u0430\u0441\u0446\u0456\u0432\u0430\u0441\u0446\u0456 \u0434\u0430\u043a\u0443\u043c\u0435\u043d\u0442\u0430", +"Title": "\u0417\u0430\u0433\u0430\u043b\u043e\u0432\u0430\u043a", +"Keywords": "\u041a\u043b\u044e\u0447\u0430\u0432\u044b\u044f \u0441\u043b\u043e\u0432\u044b", +"Description": "\u0410\u043f\u0456\u0441\u0430\u043d\u043d\u0435", +"Robots": "\u0420\u043e\u0431\u0430\u0442\u044b", +"Author": "\u0410\u045e\u0442\u0430\u0440", +"Encoding": "\u041a\u0430\u0434\u044b\u0440\u043e\u045e\u043a\u0430", +"Fullscreen": "\u041f\u043e\u045e\u043d\u0430\u044d\u043a\u0440\u0430\u043d\u043d\u044b \u0440\u044d\u0436\u044b\u043c", +"Action": "\u0414\u0437\u0435\u044f\u043d\u043d\u0435", +"Shortcut": "\u0428\u043e\u0440\u0442\u043a\u0430\u0442", +"Help": "\u0414\u0430\u043f\u0430\u043c\u043e\u0433\u0430", +"Address": "\u0410\u0434\u0440\u0430\u0441", +"Focus to menubar": "\u0424\u043e\u043a\u0443\u0441 \u043d\u0430 \u0440\u0430\u0434\u043e\u043a \u043c\u0435\u043d\u044e", +"Focus to toolbar": "\u0424\u043e\u043a\u0443\u0441 \u043d\u0430 \u043f\u0430\u043d\u044d\u043b\u044c \u0456\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u0430\u045e", +"Focus to element path": "\u0424\u043e\u043a\u0443\u0441 \u043d\u0430 \u0448\u043b\u044f\u0445 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430", +"Focus to contextual toolbar": "\u0424\u043e\u043a\u0443\u0441 \u043d\u0430 \u043a\u0430\u043d\u0442\u044d\u043a\u0441\u0442\u043d\u0443\u044e \u043f\u0430\u043d\u044d\u043b\u044c \u0456\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u0430\u045e", +"Insert link (if link plugin activated)": "\u040e\u0441\u0442\u0430\u0432\u0456\u0446\u044c \u0441\u043f\u0430\u0441\u044b\u043b\u043a\u0443 (\u043a\u0430\u043b\u0456 \u043f\u043b\u0430\u0433\u0456\u043d \u0441\u043f\u0430\u0441\u044b\u043b\u0430\u043a \u0430\u043a\u0442\u044b\u0432\u0430\u0432\u0430\u043d\u044b)", +"Save (if save plugin activated)": "\u0417\u0430\u0445\u0430\u0432\u0430\u0446\u044c (\u043a\u0430\u043b\u0456 \u043f\u043b\u0430\u0433\u0456\u043d \u0437\u0430\u0445\u0430\u0432\u0430\u043d\u043d\u044f \u0430\u043a\u0442\u044b\u0432\u0430\u0432\u0430\u043d\u044b)", +"Find (if searchreplace plugin activated)": "\u0428\u0443\u043a\u0430\u0446\u044c (\u043a\u0430\u043b\u0456 \u043f\u043b\u0430\u0433\u0456\u043d \u043f\u043e\u0448\u0443\u043a\u0443 \u0430\u043a\u0442\u044b\u0432\u0430\u0432\u0430\u043d\u044b)", +"Plugins installed ({0}):": "\u0423\u0441\u0442\u0430\u043b\u044f\u0432\u0430\u043d\u0430 \u043f\u043b\u0430\u0433\u0456\u043d\u0430\u045e ({0}):", +"Premium plugins:": "\u041f\u0440\u044d\u043c\u0456\u044f\u043b\u044c\u043d\u044b\u044f \u043f\u043b\u0430\u0433\u0456\u043d\u044b:", +"Learn more...": "\u041f\u0430\u0434\u0440\u0430\u0431\u044f\u0437\u043d\u0435\u0439 ...", +"You are using {0}": "\u0412\u044b \u043a\u0430\u0440\u044b\u0441\u0442\u0430\u0435\u0446\u0435\u0441\u044f {0}", +"Plugins": "\u041f\u043b\u0430\u0433\u0456\u043d\u044b", +"Handy Shortcuts": "\u0417\u0440\u0443\u0447\u043d\u044b\u044f \u0448\u043e\u0440\u0442\u043a\u0430\u0442\u044b", +"Horizontal line": "\u0413\u0430\u0440\u044b\u0437\u0430\u043d\u0442\u0430\u043b\u044c\u043d\u0430\u044f \u043b\u0456\u043d\u0456\u044f", +"Insert\/edit image": "\u0423\u0441\u0442\u0430\u0432\u0456\u0446\u044c\/\u0440\u044d\u0434\u0430\u0433\u0430\u0432\u0430\u0446\u044c \u0432\u044b\u044f\u0432\u0443", +"Image description": "\u0410\u043f\u0456\u0441\u0430\u043d\u043d\u0435 \u0432\u044b\u044f\u0432\u044b", +"Source": "\u041a\u0440\u044b\u043d\u0456\u0446\u0430", +"Dimensions": "\u041f\u0430\u043c\u0435\u0440", +"Constrain proportions": "\u0417\u0430\u0445\u0430\u0432\u0430\u0446\u044c \u043f\u0440\u0430\u043f\u043e\u0440\u0446\u044b\u0456", +"General": "\u0410\u0433\u0443\u043b\u044c\u043d\u0430\u0435", +"Advanced": "\u041f\u0430\u0448\u044b\u0440\u0430\u043d\u0430\u0435", +"Style": "\u0421\u0442\u044b\u043b\u044c", +"Vertical space": "\u0412\u0435\u0440\u0442\u044b\u043a\u0430\u043b\u044c\u043d\u044b \u0456\u043d\u0442\u044d\u0440\u0432\u0430\u043b", +"Horizontal space": "\u0413\u0430\u0440\u044b\u0437\u0430\u043d\u0442\u0430\u043b\u044c\u043d\u044b \u0456\u043d\u0442\u044d\u0440\u0432\u0430\u043b", +"Border": "\u041c\u044f\u0436\u0430", +"Insert image": "\u0423\u0441\u0442\u0430\u0432\u0456\u0446\u044c \u0432\u044b\u044f\u0432\u0443", +"Image": "\u0412\u044b\u044f\u0432\u0430", +"Image list": "\u0421\u043f\u0456\u0441 \u0432\u044b\u044f\u045e", +"Rotate counterclockwise": "\u041f\u0430\u0432\u044f\u0440\u043d\u0443\u0446\u044c counterclockwise", +"Rotate clockwise": "\u041f\u0430\u0432\u044f\u0440\u043d\u0443\u0446\u044c clockwise", +"Flip vertically": "\u0410\u0434\u043b\u044e\u0441\u0442\u0440\u0430\u0432\u0430\u0446\u044c \u0432\u0435\u0440\u0442\u044b\u043a\u0430\u043b\u044c\u043d\u0430", +"Flip horizontally": "\u0410\u0434\u043b\u044e\u0441\u0442\u0440\u0430\u0432\u0430\u0446\u044c \u0433\u0430\u0440\u044b\u0437\u0430\u043d\u0442\u0430\u043b\u044c\u043d\u0430", +"Edit image": "\u0420\u044d\u0434\u0430\u0433\u0430\u0432\u0430\u0446\u044c \u0432\u044b\u044f\u0432\u0443", +"Image options": "\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0432\u044b\u044f\u0432\u044b", +"Zoom in": "\u041f\u0430\u0432\u044f\u043b\u0456\u0447\u044b\u0446\u044c", +"Zoom out": "\u041f\u0430\u043c\u0435\u043d\u0448\u044b\u0446\u044c", +"Crop": "\u0410\u0431\u0440\u044d\u0437\u0430\u0446\u044c", +"Resize": "\u0417\u043c\u044f\u043d\u0456\u0446\u044c \u043f\u0430\u043c\u0435\u0440", +"Orientation": "\u0410\u0440\u044b\u0435\u043d\u0442\u0430\u0446\u044b\u044f", +"Brightness": "\u042f\u0440\u043a\u0430\u0441\u0446\u044c", +"Sharpen": "\u0412\u044b\u0440\u0430\u0437\u043d\u0430\u0441\u0446\u044c", +"Contrast": "\u041a\u0430\u043d\u0442\u0440\u0430\u0441\u0442", +"Color levels": "\u0423\u0437\u0440\u043e\u045e\u043d\u0456 \u043a\u043e\u043b\u0435\u0440\u0430\u045e", +"Gamma": "\u0413\u0430\u043c\u0430", +"Invert": "\u0406\u043d\u0432\u0435\u0440\u0442\u0430\u0432\u0430\u0446\u044c", +"Apply": "\u0423\u0436\u044b\u0446\u044c", +"Back": "\u041d\u0430\u0437\u0430\u0434", +"Insert date\/time": "\u0423\u0441\u0442\u0430\u0432\u0456\u0446\u044c \u0434\u0430\u0442\u0443\/\u0447\u0430\u0441", +"Date\/time": "\u0414\u0430\u0442\u0430\/\u0447\u0430\u0441", +"Insert link": "\u0423\u0441\u0442\u0430\u0432\u0456\u0446\u044c \u0441\u043f\u0430\u0441\u044b\u043b\u043a\u0443", +"Insert\/edit link": "\u0423\u0441\u0442\u0430\u0432\u0456\u0446\u044c\/\u0440\u044d\u0434\u0430\u0433\u0430\u0432\u0430\u0446\u044c \u0441\u043f\u0430\u0441\u044b\u043b\u043a\u0443", +"Text to display": "\u0422\u044d\u043a\u0441\u0442 \u0441\u043f\u0430\u0441\u044b\u043b\u043a\u0456", +"Url": "\u0410\u0434\u0440\u0430\u0441 \u0441\u043f\u0430\u0441\u044b\u043b\u043a\u0456", +"Target": "\u0410\u0434\u043a\u0440\u044b\u0432\u0430\u0446\u044c \u0441\u043f\u0430\u0441\u044b\u043b\u043a\u0443", +"None": "\u041d\u044f\u043c\u0430", +"New window": "\u0423 \u043d\u043e\u0432\u044b\u043c \u0430\u043a\u043d\u0435", +"Remove link": "\u0412\u044b\u0434\u0430\u043b\u0456\u0446\u044c \u0441\u043f\u0430\u0441\u044b\u043b\u043a\u0443", +"Anchors": "\u042f\u043a\u0430\u0440\u044b", +"Link": "\u0421\u043f\u0430\u0441\u044b\u043b\u043a\u0430", +"Paste or type a link": "\u0423\u0441\u0442\u0430\u045e\u0446\u0435 \u0430\u0431\u043e \u045e\u0432\u044f\u0434\u0437\u0456\u0446\u0435 \u0441\u043f\u0430\u0441\u044b\u043b\u043a\u0443", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "\u0423\u0432\u0435\u0434\u0437\u0435\u043d\u044b \u0430\u0434\u0440\u0430\u0441 \u043f\u0430\u0434\u043e\u0431\u043d\u044b \u043d\u0430 \u0430\u0434\u0440\u0430\u0441 \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u0430\u0439 \u043f\u043e\u0448\u0442\u044b. \u0416\u0430\u0434\u0430\u0435\u0446\u0435 \u0434\u0430\u0434\u0430\u0446\u044c \u043d\u0435\u0430\u0431\u0445\u043e\u0434\u043d\u044b mailto: \u043f\u0440\u044d\u0444\u0456\u043a\u0441?", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "\u0423\u0432\u0435\u0434\u0437\u0435\u043d\u044b \u0430\u0434\u0440\u0430\u0441 \u043f\u0430\u0434\u043e\u0431\u043d\u044b \u043d\u0430 \u0437\u043d\u0435\u0448\u043d\u044e\u044e \u0441\u043f\u0430\u0441\u044b\u043b\u043a\u0443. \u0416\u0430\u0434\u0430\u0435\u0446\u0435 \u0434\u0430\u0434\u0430\u0446\u044c \u043d\u0435\u0430\u0431\u0445\u043e\u0434\u043d\u044b http:\/\/ \u043f\u0440\u044d\u0444\u0456\u043a\u0441?", +"Link list": "\u0421\u043f\u0456\u0441 \u0441\u043f\u0430\u0441\u044b\u043b\u0430\u043a", +"Insert video": "\u0423\u0441\u0442\u0430\u0432\u0456\u0446\u044c \u0432\u0456\u0434\u044d\u0430", +"Insert\/edit video": "\u0423\u0441\u0442\u0430\u0432\u0456\u0446\u044c\/\u0440\u044d\u0434\u0430\u0433\u0430\u0432\u0430\u0446\u044c \u0432\u0456\u0434\u044d\u0430", +"Insert\/edit media": "\u0423\u0441\u0442\u0430\u0432\u0456\u0446\u044c\/\u0440\u044d\u0434\u0430\u0433\u0430\u0432\u0430\u0446\u044c \u043c\u0435\u0434\u044b\u044f", +"Alternative source": "\u0410\u043b\u044c\u0442\u044d\u0440\u043d\u0430\u0442\u044b\u045e\u043d\u0430\u044f \u043a\u0440\u044b\u043d\u0456\u0446\u0430", +"Poster": "\u0412\u044b\u044f\u0432\u0430", +"Paste your embed code below:": "\u0423\u0441\u0442\u0430\u045e\u0446\u0435 \u0432\u0430\u0448 \u043a\u043e\u0434 \u043d\u0456\u0436\u044d\u0439:", +"Embed": "\u041a\u043e\u0434 \u0434\u043b\u044f \u045e\u0441\u0442\u0430\u045e\u043a\u0456", +"Media": "\u041c\u0435\u0434\u044b\u044f", +"Nonbreaking space": "\u041d\u0435\u043f\u0430\u0440\u044b\u045e\u043d\u044b \u043f\u0440\u0430\u0431\u0435\u043b", +"Page break": "\u0420\u0430\u0437\u0440\u044b\u045e \u0441\u0442\u0430\u0440\u043e\u043d\u043a\u0456", +"Paste as text": "\u0423\u0441\u0442\u0430\u0432\u0456\u0446\u044c \u044f\u043a \u0442\u044d\u043a\u0441\u0442", +"Preview": "\u041f\u0440\u0430\u0434\u043f\u0440\u0430\u0433\u043b\u044f\u0434", +"Print": "\u0414\u0440\u0443\u043a", +"Save": "\u0417\u0430\u0445\u0430\u0432\u0430\u0446\u044c", +"Find": "\u0417\u043d\u0430\u0439\u0441\u0446\u0456", +"Replace with": "\u0417\u043c\u044f\u043d\u0456\u0446\u044c \u043d\u0430", +"Replace": "\u0417\u043c\u044f\u043d\u0456\u0446\u044c", +"Replace all": "\u0417\u043c\u044f\u043d\u0456\u0446\u044c \u0443\u0441\u0435", +"Prev": "\u0423\u0432\u0435\u0440\u0445", +"Next": "\u0423\u043d\u0456\u0437", +"Find and replace": "\u041f\u043e\u0448\u0443\u043a \u0456 \u0437\u0430\u043c\u0435\u043d\u0430", +"Could not find the specified string.": "\u0417\u0430\u0434\u0430\u0434\u0437\u0435\u043d\u044b \u0440\u0430\u0434\u043e\u043a \u043d\u0435 \u0437\u043d\u043e\u0439\u0434\u0437\u0435\u043d\u044b", +"Match case": "\u0423\u043b\u0456\u0447\u0432\u0430\u0446\u044c \u0440\u044d\u0433\u0456\u0441\u0442\u0440", +"Whole words": "\u0421\u043b\u043e\u0432\u044b \u0446\u0430\u043b\u043a\u0430\u043c", +"Spellcheck": "\u041f\u0440\u0430\u0432\u0435\u0440\u043a\u0430 \u043f\u0440\u0430\u0432\u0430\u043f\u0456\u0441\u0443", +"Ignore": "\u0406\u0433\u043d\u0430\u0440\u0430\u0432\u0430\u0446\u044c", +"Ignore all": "\u0406\u0433\u043d\u0430\u0440\u0430\u0432\u0430\u0446\u044c \u0443\u0441\u0435", +"Finish": "\u0421\u043a\u043e\u043d\u0447\u044b\u0446\u044c", +"Add to Dictionary": "\u0414\u0430\u0434\u0430\u0446\u044c \u0443 \u0441\u043b\u043e\u045e\u043d\u0456\u043a", +"Insert table": "\u0423\u0441\u0442\u0430\u0432\u0456\u0446\u044c \u0442\u0430\u0431\u043b\u0456\u0446\u0443", +"Table properties": "\u0423\u043b\u0430\u0441\u0446\u0456\u0432\u0430\u0441\u0446\u0456 \u0442\u0430\u0431\u043b\u0456\u0446\u044b", +"Delete table": "\u0412\u044b\u0434\u0430\u043b\u0456\u0446\u044c \u0442\u0430\u0431\u043b\u0456\u0446\u0443", +"Cell": "\u042f\u0447\u044d\u0439\u043a\u0430", +"Row": "\u0420\u0430\u0434\u043e\u043a", +"Column": "\u0421\u043b\u0443\u043f\u043e\u043a", +"Cell properties": "\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u044f\u0447\u044d\u0439\u043a\u0456", +"Merge cells": "\u0410\u0431'\u044f\u0434\u043d\u0430\u0446\u044c \u044f\u0447\u044d\u0439\u043a\u0456", +"Split cell": "\u0420\u0430\u0437\u0431\u0456\u0446\u044c \u044f\u0447\u044d\u0439\u043a\u0443", +"Insert row before": "\u0423\u0441\u0442\u0430\u0432\u0456\u0446\u044c \u0440\u0430\u0434\u043e\u043a \u0437\u0432\u0435\u0440\u0445\u0443", +"Insert row after": "\u0423\u0441\u0442\u0430\u0432\u0456\u0446\u044c \u0440\u0430\u0434\u043e\u043a \u0437\u043d\u0456\u0437\u0443", +"Delete row": "\u0412\u044b\u0434\u0430\u043b\u0456\u0446\u044c \u0440\u0430\u0434\u043e\u043a", +"Row properties": "\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0440\u0430\u0434\u043a\u0430", +"Cut row": "\u0412\u044b\u0440\u0430\u0437\u0430\u0446\u044c \u0440\u0430\u0434\u043e\u043a", +"Copy row": "\u041a\u0430\u043f\u0456\u044f\u0432\u0430\u0446\u044c \u0440\u0430\u0434\u043e\u043a", +"Paste row before": "\u0423\u0441\u0442\u0430\u0432\u0456\u0446\u044c \u0440\u0430\u0434\u043e\u043a \u0437\u0432\u0435\u0440\u0445\u0443", +"Paste row after": "\u0423\u0441\u0442\u0430\u0432\u0456\u0446\u044c \u0440\u0430\u0434\u043e\u043a \u0437\u043d\u0456\u0437\u0443", +"Insert column before": "\u0414\u0430\u0434\u0430\u0446\u044c \u0441\u043b\u0443\u043f\u043e\u043a \u0437\u043b\u0435\u0432\u0430", +"Insert column after": "\u0414\u0430\u0434\u0430\u0446\u044c \u0441\u043b\u0443\u043f\u043e\u043a \u0441\u043f\u0440\u0430\u0432\u0430", +"Delete column": "\u0412\u044b\u0434\u0430\u043b\u0456\u0446\u044c \u0441\u043b\u0443\u043f\u043e\u043a", +"Cols": "\u0421\u043b\u0443\u043f\u043a\u0456", +"Rows": "\u0420\u0430\u0434\u043a\u0456", +"Width": "\u0428\u044b\u0440\u044b\u043d\u044f", +"Height": "\u0412\u044b\u0448\u044b\u043d\u044f", +"Cell spacing": "\u0417\u043d\u0435\u0448\u043d\u0456 \u0432\u043e\u0434\u0441\u0442\u0443\u043f", +"Cell padding": "\u0423\u043d\u0443\u0442\u0440\u0430\u043d\u044b \u0432\u043e\u0434\u0441\u0442\u0443\u043f", +"Caption": "\u0417\u0430\u0433\u0430\u043b\u043e\u0432\u0430\u043a", +"Left": "\u041f\u0430 \u043b\u0435\u0432\u044b\u043c \u043a\u0440\u0430\u0456", +"Center": "\u041f\u0430 \u0446\u044d\u043d\u0442\u0440\u044b", +"Right": "\u041f\u0430 \u043f\u0440\u0430\u0432\u044b\u043c \u043a\u0440\u0430\u0456", +"Cell type": "\u0422\u044b\u043f \u044f\u0447\u044d\u0439\u043a\u0456", +"Scope": "\u0421\u0444\u0435\u0440\u0430", +"Alignment": "\u0412\u044b\u0440\u0430\u045e\u043d\u043e\u045e\u0432\u0430\u043d\u043d\u0435", +"H Align": "\u0413\u0430\u0440. \u0432\u044b\u0440\u0430\u045e\u043d\u043e\u045e\u0432\u0430\u043d\u043d\u0435", +"V Align": "\u0412\u0435\u0440. \u0432\u044b\u0440\u0430\u045e\u043d\u043e\u045e\u0432\u0430\u043d\u043d\u0435", +"Top": "\u0412\u0435\u0440\u0445", +"Middle": "\u0421\u044f\u0440\u044d\u0434\u0437\u0456\u043d\u0430", +"Bottom": "\u041d\u0456\u0437", +"Header cell": "\u0417\u0430\u0433\u0430\u043b\u043e\u0432\u0430\u043a", +"Row group": "\u0413\u0440\u0443\u043f\u0430 \u0440\u0430\u0434\u043a\u043e\u045e", +"Column group": "\u0413\u0440\u0443\u043f\u0430 \u0441\u043b\u0443\u043f\u043a\u043e\u045e", +"Row type": "\u0422\u044b\u043f \u0440\u0430\u0434\u043a\u0430", +"Header": "\u0428\u0430\u043f\u043a\u0430", +"Body": "\u0426\u0435\u043b\u0430", +"Footer": "\u041d\u0456\u0437", +"Border color": "\u041a\u043e\u043b\u0435\u0440 \u043c\u044f\u0436\u044b", +"Insert template": "\u0423\u0441\u0442\u0430\u0432\u0456\u0446\u044c \u0448\u0430\u0431\u043b\u043e\u043d", +"Templates": "\u0428\u0430\u0431\u043b\u043e\u043d\u044b", +"Template": "\u0428\u0430\u0431\u043b\u043e\u043d", +"Text color": "\u041a\u043e\u043b\u0435\u0440 \u0442\u044d\u043a\u0441\u0442\u0443", +"Background color": "\u041a\u043e\u043b\u0435\u0440 \u0444\u043e\u043d\u0443", +"Custom...": "\u041a\u0430\u0440\u044b\u0441\u0442\u0430\u0446\u043a\u0456...", +"Custom color": "\u041a\u0430\u0440\u044b\u0441\u0442\u0430\u0446\u043a\u0456 \u043a\u043e\u043b\u0435\u0440", +"No color": "\u0411\u0435\u0437 \u043a\u043e\u043b\u0435\u0440\u0443", +"Table of Contents": "\u0422\u0430\u0431\u043b\u0456\u0446\u0443 \u0437\u043c\u0435\u0441\u0442\u0443", +"Show blocks": "\u041f\u0430\u043a\u0430\u0437\u0432\u0430\u0446\u044c \u0431\u043b\u043e\u043a\u0456", +"Show invisible characters": "\u041f\u0430\u043a\u0430\u0437\u0432\u0430\u0446\u044c \u043d\u044f\u0431\u0430\u0447\u043d\u044b\u044f \u0441\u0456\u043c\u0432\u0430\u043b\u044b", +"Words: {0}": "\u041a\u043e\u043b\u044c\u043a\u0430\u0441\u0446\u044c \u0441\u043b\u043e\u045e: {0}", +"{0} words": "{0} \u0441\u043b\u043e\u045e", +"File": "\u0424\u0430\u0439\u043b", +"Edit": "\u0417\u043c\u044f\u043d\u0456\u0446\u044c", +"Insert": "\u0423\u0441\u0442\u0430\u0432\u0456\u0446\u044c", +"View": "\u0412\u044b\u0433\u043b\u044f\u0434", +"Format": "\u0424\u0430\u0440\u043c\u0430\u0442", +"Table": "\u0422\u0430\u0431\u043b\u0456\u0446\u0430", +"Tools": "\u041f\u0440\u044b\u043b\u0430\u0434\u044b", +"Powered by {0}": "\u041f\u0440\u0430\u0446\u0443\u0435 \u043d\u0430 {0}", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "\u0422\u044d\u043a\u0441\u0442\u0430\u0432\u0430\u0435 \u043f\u043e\u043b\u0435. \u041d\u0430\u0446\u0456\u0441\u043d\u0456\u0446\u0435 ALT-F9, \u043a\u0430\u0431 \u0432\u044b\u043a\u043b\u0456\u043a\u0430\u0446\u044c \u043c\u0435\u043d\u044e, ALT-F10 - \u043f\u0430\u043d\u044d\u043b\u044c \u043f\u0440\u044b\u043b\u0430\u0434\u0430\u045e, ALT-0 - \u0434\u043b\u044f \u0432\u044b\u043a\u043b\u0456\u043a\u0443 \u0434\u0430\u043f\u0430\u043c\u043e\u0433\u0456." +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/bg_BG.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/bg_BG.js new file mode 100644 index 0000000000..9ef72576a2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/bg_BG.js @@ -0,0 +1,261 @@ +tinymce.addI18n('bg_BG',{ +"Redo": "\u041e\u0442\u043c\u0435\u043d\u0438", +"Undo": "\u0412\u044a\u0440\u043d\u0438", +"Cut": "\u0418\u0437\u0440\u044f\u0437\u0432\u0430\u043d\u0435", +"Copy": "\u041a\u043e\u043f\u0438\u0440\u0430\u043d\u0435", +"Paste": "\u041f\u043e\u0441\u0442\u0430\u0432\u044f\u043d\u0435", +"Select all": "\u041c\u0430\u0440\u043a\u0438\u0440\u0430\u043d\u0435 \u043d\u0430 \u0446\u044f\u043b\u043e\u0442\u043e \u0441\u044a\u0434\u044a\u0440\u0436\u0430\u043d\u0438\u0435", +"New document": "\u041d\u043e\u0432 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442", +"Ok": "\u0414\u043e\u0431\u0440\u0435", +"Cancel": "\u041e\u0442\u043a\u0430\u0437", +"Visual aids": "\u0412\u0438\u0437\u0443\u0430\u043b\u043d\u043e \u043e\u0442\u043a\u0440\u043e\u044f\u0432\u0430\u043d\u0435 \u043d\u0430 \u0442\u0430\u0431\u043b\u0438\u0446\u0438 \u0431\u0435\u0437 \u043a\u0430\u043d\u0442\u043e\u0432\u0435 (\u0440\u0430\u043c\u043a\u0438)", +"Bold": "\u0423\u0434\u0435\u0431\u0435\u043b\u0435\u043d (\u043f\u043e\u043b\u0443\u0447\u0435\u0440)", +"Italic": "\u041d\u0430\u043a\u043b\u043e\u043d\u0435\u043d (\u043a\u0443\u0440\u0441\u0438\u0432)", +"Underline": "\u041f\u043e\u0434\u0447\u0435\u0440\u0442\u0430\u043d", +"Strikethrough": "\u0417\u0430\u0447\u0435\u0440\u0442\u0430\u0432\u0430\u043d\u0435", +"Superscript": "\u0413\u043e\u0440\u0435\u043d \u0438\u043d\u0434\u0435\u043a\u0441", +"Subscript": "\u0414\u043e\u043b\u0435\u043d \u0438\u043d\u0434\u0435\u043a\u0441", +"Clear formatting": "\u0418\u0437\u0447\u0438\u0441\u0442\u0438 \u0444\u043e\u0440\u043c\u0430\u0442\u0438\u0440\u0430\u043d\u0435\u0442\u043e", +"Align left": "\u041f\u043e\u0434\u0440\u0430\u0432\u043d\u044f\u0432\u0430\u043d\u0435 \u043e\u0442\u043b\u044f\u0432\u043e", +"Align center": "\u0426\u0435\u043d\u0442\u0440\u0438\u0440\u0430\u043d\u043e", +"Align right": "\u041f\u043e\u0434\u0440\u0430\u0432\u043d\u044f\u0432\u0430\u043d\u0435 \u043e\u0442\u0434\u044f\u0441\u043d\u043e", +"Justify": "\u0414\u0432\u0443\u0441\u0442\u0440\u0430\u043d\u043d\u043e \u043f\u043e\u0434\u0440\u0430\u0432\u043d\u044f\u0432\u0430\u043d\u0435", +"Bullet list": "\u0421\u043f\u0438\u0441\u044a\u043a \u0441 \u0432\u043e\u0434\u0430\u0447\u0438", +"Numbered list": "\u041d\u043e\u043c\u0435\u0440\u0438\u0440\u0430\u043d \u0441\u043f\u0438\u0441\u044a\u043a", +"Decrease indent": "\u041d\u0430\u043c\u0430\u043b\u044f\u0432\u0430\u043d\u0435 \u043d\u0430 \u043e\u0442\u0441\u0442\u044a\u043f\u0430", +"Increase indent": "\u0423\u0432\u0435\u043b\u0438\u0447\u0430\u0432\u0430\u043d\u0435 \u043d\u0430 \u043e\u0442\u0441\u0442\u044a\u043f\u0430", +"Close": "\u0417\u0430\u0442\u0432\u0430\u0440\u044f\u043d\u0435", +"Formats": "\u0424\u043e\u0440\u043c\u0430\u0442\u0438\u0440\u0430\u043d\u0435", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "\u0412\u0430\u0448\u0438\u044f\u0442 \u0431\u0440\u0430\u0443\u0437\u044a\u0440 \u043d\u0435 \u043f\u043e\u0434\u0434\u044a\u0440\u0436\u0430 \u0434\u0438\u0440\u0435\u043a\u0442\u0435\u043d \u0434\u043e\u0441\u0442\u044a\u043f \u0434\u043e \u043a\u043b\u0438\u043f\u0431\u043e\u0440\u0434\u0430. \u0412\u043c\u0435\u0441\u0442\u043e \u0442\u043e\u0432\u0430 \u0438\u0437\u043f\u043e\u043b\u0437\u0432\u0430\u0439\u0442\u0435 \u043a\u043b\u0430\u0432\u0438\u0448\u043d\u0438\u0442\u0435 \u043a\u043e\u043c\u0431\u0438\u043d\u0430\u0446\u0438\u0438 Ctrl+X (\u0437\u0430 \u0438\u0437\u0440\u044f\u0437\u0432\u0430\u043d\u0435), Ctrl+C (\u0437\u0430 \u043a\u043e\u043f\u0438\u0440\u0430\u043d\u0435) \u0438 Ctrl+V (\u0437\u0430 \u043f\u043e\u0441\u0442\u0430\u0432\u044f\u043d\u0435).", +"Headers": "\u0417\u0430\u0433\u043b\u0430\u0432\u0438\u044f", +"Header 1": "\u0417\u0430\u0433\u043b\u0430\u0432\u0438\u0435 1", +"Header 2": "\u0417\u0430\u0433\u043b\u0430\u0432\u0438\u0435 2", +"Header 3": "\u0417\u0430\u0433\u043b\u0430\u0432\u0438\u0435 3", +"Header 4": "\u0417\u0430\u0433\u043b\u0430\u0432\u0438\u0435 4", +"Header 5": "\u0417\u0430\u0433\u043b\u0430\u0432\u0438\u0435 5", +"Header 6": "\u0417\u0430\u0433\u043b\u0430\u0432\u0438\u0435 6", +"Headings": "\u0417\u0430\u0433\u043b\u0430\u0432\u0438\u044f", +"Heading 1": "\u0417\u0430\u0433\u043b\u0430\u0432\u0438\u0435 1", +"Heading 2": "\u0417\u0430\u0433\u043b\u0430\u0432\u0438\u0435 2", +"Heading 3": "\u0417\u0430\u0433\u043b\u0430\u0432\u0438\u0435 3", +"Heading 4": "\u0417\u0430\u0433\u043b\u0430\u0432\u0438\u0435 4", +"Heading 5": "\u0417\u0430\u0433\u043b\u0430\u0432\u0438\u0435 5", +"Heading 6": "\u0417\u0430\u0433\u043b\u0430\u0432\u0438\u0435 6", +"Preformatted": "\u041f\u0440\u0435\u0444\u043e\u0440\u043c\u0430\u0442\u0438\u0440\u0430\u043d", +"Div": "\u0411\u043b\u043e\u043a", +"Pre": "\u041f\u0440\u0435\u0434\u0432\u0430\u0440\u0438\u0442\u0435\u043b\u043d\u043e \u043e\u0444\u043e\u0440\u043c\u0435\u043d \u0442\u0435\u043a\u0441\u0442", +"Code": "\u041a\u043e\u0434", +"Paragraph": "\u041f\u0430\u0440\u0430\u0433\u0440\u0430\u0444", +"Blockquote": "\u0426\u0438\u0442\u0430\u0442", +"Inline": "\u041d\u0430 \u0435\u0434\u0438\u043d \u0440\u0435\u0434", +"Blocks": "\u0411\u043b\u043e\u043a\u043e\u0432\u0435", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "\u041f\u043e\u0441\u0442\u0430\u0432\u044f\u043d\u0435\u0442\u043e \u0432 \u043c\u043e\u043c\u0435\u043d\u0442\u0430 \u0435 \u0432 \u043e\u0431\u0438\u043a\u043d\u043e\u0432\u0435\u043d \u0440\u0435\u0436\u0438\u043c. \u0421\u044a\u0434\u044a\u0440\u0436\u0430\u043d\u0438\u0435\u0442\u043e \u0449\u0435 \u0431\u044a\u0434\u0435 \u043f\u043e\u0441\u0442\u0430\u0432\u0435\u043d\u043e \u043a\u0430\u0442\u043e \u043d\u0435\u0444\u043e\u0440\u043c\u0430\u0442\u0438\u0440\u0430\u043d \u0442\u0435\u043a\u0441\u0442, \u0434\u043e\u043a\u0430\u0442\u043e \u0438\u0437\u043a\u043b\u044e\u0447\u0438\u0442\u0435 \u0442\u0430\u0437\u0438 \u043e\u043f\u0446\u0438\u044f.", +"Font Family": "\u0428\u0440\u0438\u0444\u0442", +"Font Sizes": "\u0420\u0430\u0437\u043c\u0435\u0440 \u043d\u0430 \u0448\u0440\u0438\u0444\u0442\u0430", +"Class": "\u041a\u043b\u0430\u0441", +"Browse for an image": "\u041f\u043e\u0442\u044a\u0440\u0441\u0438 \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0430", +"OR": "\u0418\u041b\u0418", +"Drop an image here": "\u041f\u0443\u0441\u043d\u0435\u0442\u0435 \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0430\u0442\u0430 \u0442\u0443\u043a", +"Upload": "\u041a\u0430\u0447\u0438", +"Block": "\u0411\u043b\u043e\u043a", +"Align": "\u041f\u043e\u0434\u0440\u0430\u0432\u043d\u044f\u0432\u0430\u043d\u0435", +"Default": "\u041f\u043e \u043f\u043e\u0434\u0440\u0430\u0437\u0431\u0438\u0440\u0430\u043d\u0435", +"Circle": "\u041e\u043a\u0440\u044a\u0436\u043d\u043e\u0441\u0442\u0438", +"Disc": "\u041a\u0440\u044a\u0433\u0447\u0435\u0442\u0430", +"Square": "\u0417\u0430\u043f\u044a\u043b\u043d\u0435\u043d\u0438 \u043a\u0432\u0430\u0434\u0440\u0430\u0442\u0438", +"Lower Alpha": "\u041c\u0430\u043b\u043a\u0438 \u0431\u0443\u043a\u0432\u0438", +"Lower Greek": "\u041c\u0430\u043b\u043a\u0438 \u0433\u0440\u044a\u0446\u043a\u0438 \u0431\u0443\u043a\u0432\u0438", +"Lower Roman": "\u0420\u0438\u043c\u0441\u043a\u0438 \u0447\u0438\u0441\u043b\u0430 \u0441 \u043c\u0430\u043b\u043a\u0438 \u0431\u0443\u043a\u0432\u0438", +"Upper Alpha": "\u0413\u043b\u0430\u0432\u043d\u0438 \u0431\u0443\u043a\u0432\u0438", +"Upper Roman": "\u0420\u0438\u043c\u0441\u043a\u0438 \u0447\u0438\u0441\u043b\u0430 \u0441 \u0433\u043b\u0430\u0432\u043d\u0438 \u0431\u0443\u043a\u0432\u0438", +"Anchor": "\u041a\u043e\u0442\u0432\u0430 (\u0432\u0440\u044a\u0437\u043a\u0430 \u0432 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430)", +"Name": "\u041d\u0430\u0438\u043c\u0435\u043d\u043e\u0432\u0430\u043d\u0438\u0435", +"Id": "\u0418\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 (id)", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "\u0418\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u0430 (id) \u0442\u0440\u044f\u0431\u0432\u0430 \u0434\u0430 \u0437\u0430\u043f\u043e\u0447\u0432\u0430 \u0441 \u0431\u0443\u043a\u0432\u0430, \u043f\u043e\u0441\u043b\u0435\u0434\u0432\u0430\u043d \u043e\u0442 \u0431\u0443\u043a\u0432\u0438, \u0447\u0438\u0444\u0440\u0438, \u0442\u0438\u0440\u0435\u0442\u0430, \u0442\u043e\u0447\u043a\u0438, \u0434\u0432\u043e\u0435\u0442\u043e\u0447\u0438\u0435 \u0438 \u0434\u043e\u043b\u043d\u043e \u0442\u0438\u0440\u0435.", +"You have unsaved changes are you sure you want to navigate away?": "\u0412 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430 \u0438\u043c\u0430 \u043d\u0435\u0437\u0430\u043f\u0430\u0437\u0435\u043d\u0438 \u043f\u0440\u043e\u043c\u0435\u043d\u0438. \u0429\u0435 \u043f\u0440\u043e\u0434\u044a\u043b\u0436\u0438\u0442\u0435 \u043b\u0438?", +"Restore last draft": "\u0412\u044a\u0437\u0441\u0442\u0430\u043d\u043e\u0432\u044f\u0432\u0430\u043d\u0435 \u043d\u0430 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0430\u0442\u0430 \u0447\u0435\u0440\u043d\u043e\u0432\u0430", +"Special character": "\u0421\u043f\u0435\u0446\u0438\u0430\u043b\u0435\u043d \u0437\u043d\u0430\u043a", +"Source code": "\u0418\u0437\u0445\u043e\u0434\u0435\u043d \u043a\u043e\u0434 \u043d\u0430 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430 \u0432 HTML", +"Insert\/Edit code sample": "\u0412\u043c\u044a\u043a\u043d\u0438\/ \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u0430\u0439 \u043f\u0440\u0438\u043c\u0435\u0440\u0435\u043d \u043a\u043e\u0434", +"Language": "\u0415\u0437\u0438\u043a", +"Code sample": "\u041f\u0440\u0438\u043c\u0435\u0440\u0435\u043d \u043a\u043e\u0434", +"Color": "\u0426\u0432\u044f\u0442", +"R": "R", +"G": "G", +"B": "B", +"Left to right": "\u041e\u0442\u043b\u044f\u0432\u043e \u043d\u0430\u0434\u044f\u0441\u043d\u043e", +"Right to left": "\u041e\u0442\u0434\u044f\u0441\u043d\u043e \u043d\u0430\u043b\u044f\u0432\u043e", +"Emoticons": "\u0415\u043c\u043e\u0442\u0438\u043a\u043e\u043d\u0438", +"Document properties": "\u0421\u0432\u043e\u0439\u0441\u0442\u0432\u0430 \u043d\u0430 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430", +"Title": "\u041d\u0430\u0438\u043c\u0435\u043d\u043e\u0432\u0430\u043d\u0438\u0435", +"Keywords": "\u041a\u043b\u044e\u0447\u043e\u0432\u0438 \u0434\u0443\u043c\u0438", +"Description": "\u041e\u043f\u0438\u0441\u0430\u043d\u0438\u0435", +"Robots": "\u0420\u043e\u0431\u043e\u0442\u0438 \u043d\u0430 \u0443\u0435\u0431 \u0442\u044a\u0440\u0441\u0430\u0447\u043a\u0438", +"Author": "\u0410\u0432\u0442\u043e\u0440", +"Encoding": "\u041a\u043e\u0434\u0438\u0440\u0430\u043d\u0435 \u043d\u0430 \u0437\u043d\u0430\u0446\u0438\u0442\u0435", +"Fullscreen": "\u041d\u0430 \u0446\u044f\u043b \u0435\u043a\u0440\u0430\u043d", +"Action": "\u0414\u0435\u0439\u0441\u0442\u0432\u0438\u0435", +"Shortcut": "\u0411\u044a\u0440\u0437 \u043a\u043b\u0430\u0432\u0438\u0448", +"Help": "\u041f\u043e\u043c\u043e\u0449", +"Address": "\u0410\u0434\u0440\u0435\u0441", +"Focus to menubar": "Focus to menubar", +"Focus to toolbar": "Focus to toolbar", +"Focus to element path": "Focus to element path", +"Focus to contextual toolbar": "Focus to contextual toolbar", +"Insert link (if link plugin activated)": "\u041f\u043e\u0441\u0442\u0430\u0432\u0438 \u0432\u0440\u044a\u0437\u043a\u0430 (\u0430\u043a\u043e \u043f\u043b\u044a\u0433\u0438\u043d\u0430 \u0437\u0430 \u0432\u0440\u044a\u0437\u043a\u0438 \u0435 \u0430\u043a\u0442\u0438\u0432\u0438\u0440\u0430\u043d)", +"Save (if save plugin activated)": "\u0417\u0430\u043f\u0438\u0448\u0438 (\u0430\u043a\u043e \u043f\u043b\u044a\u0433\u0438\u043d\u0430 \u0437\u0430 \u0437\u0430\u043f\u0438\u0441 \u0435 \u0430\u043a\u0442\u0438\u0432\u0438\u0440\u0430\u043d)", +"Find (if searchreplace plugin activated)": "\u041d\u0430\u043c\u0435\u0440\u0438 (\u0430\u043a\u043e \u043f\u043b\u044a\u0433\u0438\u043d\u0430 \u0437\u0430 \u0442\u044a\u0440\u0441\u0435\u043d\u0435\/\u0437\u0430\u043c\u044f\u043d\u0430 \u0435 \u0430\u043a\u0442\u0438\u0432\u0438\u0440\u0430\u043d)", +"Plugins installed ({0}):": "\u0418\u043d\u0441\u0442\u0430\u043b\u0438\u0440\u0430\u043d\u0438 \u043f\u043b\u044a\u0433\u0438\u043d\u0438 ({0}):", +"Premium plugins:": "\u041f\u0440\u0435\u043c\u0438\u0439\u043d\u0438 \u043f\u043b\u044a\u0433\u0438\u043d\u0438:", +"Learn more...": "\u041d\u0430\u0443\u0447\u0435\u0442\u0435 \u043f\u043e\u0432\u0435\u0447\u0435...", +"You are using {0}": "\u0418\u0437\u043f\u043e\u043b\u0437\u0432\u0430\u0442\u0435 {0}", +"Plugins": "Plugins", +"Handy Shortcuts": "Handy Shortcuts", +"Horizontal line": "\u0425\u043e\u0440\u0438\u0437\u043e\u043d\u0442\u0430\u043b\u043d\u0430 \u0447\u0435\u0440\u0442\u0430", +"Insert\/edit image": "\u0414\u043e\u0431\u0430\u0432\u044f\u043d\u0435\/\u043a\u043e\u0440\u0435\u043a\u0446\u0438\u044f \u043d\u0430 \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0430", +"Image description": "\u041e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u043d\u0430 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435\u0442\u043e", +"Source": "\u0410\u0434\u0440\u0435\u0441", +"Dimensions": "\u0420\u0430\u0437\u043c\u0435\u0440", +"Constrain proportions": "\u0417\u0430\u0432\u0430\u0437\u043d\u0430\u0432\u0435 \u043d\u0430 \u043f\u0440\u043e\u043f\u043e\u0440\u0446\u0438\u0438\u0442\u0435", +"General": "\u041e\u0431\u0449\u043e", +"Advanced": "\u041f\u043e\u0434\u0440\u043e\u0431\u043d\u043e", +"Style": "\u0421\u0442\u0438\u043b", +"Vertical space": "\u0412\u0435\u0440\u0442\u0438\u043a\u0430\u043b\u043d\u043e \u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0441\u0442\u0432\u043e", +"Horizontal space": "\u0425\u043e\u0440\u0438\u0437\u043e\u043d\u0442\u0430\u043b\u043d\u043e \u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0441\u0442\u0432\u043e", +"Border": "\u041a\u0430\u043d\u0442 (\u0440\u0430\u043c\u043a\u0430)", +"Insert image": "\u0414\u043e\u0431\u0430\u0432\u044f\u043d\u0435 \u043d\u0430 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435", +"Image": "\u041a\u0430\u0440\u0442\u0438\u043d\u043a\u0430", +"Image list": "\u0421\u043f\u0438\u0441\u044a\u043a \u0441 \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0438", +"Rotate counterclockwise": "\u0417\u0430\u0432\u044a\u0440\u0442\u0430\u043d\u0435 \u043e\u0431\u0440\u0430\u0442\u043d\u043e \u043d\u0430 \u0447\u0430\u0441\u043e\u0432\u043d\u0438\u043a\u0430", +"Rotate clockwise": "\u0417\u0430\u0432\u044a\u0440\u0442\u0430\u043d\u0435 \u043f\u043e \u0447\u0430\u0441\u043e\u0432\u043d\u0438\u043a\u0430", +"Flip vertically": "\u041e\u0431\u044a\u0440\u043d\u0438 \u0432\u0435\u0440\u0442\u0438\u043a\u0430\u043b\u043d\u043e", +"Flip horizontally": "\u041e\u0431\u044a\u0440\u043d\u0438 \u0445\u043e\u0440\u0438\u0437\u043e\u043d\u0442\u0430\u043b\u043d\u043e", +"Edit image": "\u0420\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u0430\u043d\u0435 \u043d\u0430 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435\u0442\u043e", +"Image options": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043d\u0430 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435\u0442\u043e", +"Zoom in": "\u041f\u0440\u0438\u0431\u043b\u0438\u0436\u0438", +"Zoom out": "\u041e\u0442\u0434\u0430\u043b\u0435\u0447\u0438", +"Crop": "\u0418\u0437\u0440\u044f\u0437\u0432\u0430\u043d\u0435", +"Resize": "\u041f\u0440\u0435\u043e\u0440\u0430\u0437\u043c\u0435\u0440\u044f\u0432\u0430\u043d\u0435", +"Orientation": "\u041e\u0440\u0438\u0435\u043d\u0442\u0430\u0446\u0438\u044f", +"Brightness": "\u042f\u0440\u043a\u043e\u0441\u0442", +"Sharpen": "\u0418\u0437\u043e\u0441\u0442\u0440\u044f\u043d\u0435", +"Contrast": "\u041a\u043e\u043d\u0442\u0440\u0430\u0441\u0442", +"Color levels": "\u0426\u0432\u0435\u0442\u043d\u0438 \u043d\u0438\u0432\u0430", +"Gamma": "\u0413\u0430\u043c\u0430", +"Invert": "\u0418\u043d\u0432\u0435\u0440\u0441\u0438\u044f", +"Apply": "\u041f\u0440\u0438\u043b\u043e\u0436\u0438", +"Back": "\u041d\u0430\u0437\u0430\u0434", +"Insert date\/time": "\u0414\u043e\u0431\u0430\u0432\u044f\u043d\u0435 \u043d\u0430 \u0434\u0430\u0442\u0430\/\u0447\u0430\u0441", +"Date\/time": "\u0414\u0430\u0442\u0430\/\u0447\u0430\u0441", +"Insert link": "\u0414\u043e\u0431\u0430\u0432\u044f\u043d\u0435 \u043d\u0430 \u0445\u0438\u043f\u0435\u0440\u0432\u0440\u044a\u0437\u043a\u0430 (\u043b\u0438\u043d\u043a)", +"Insert\/edit link": "\u0414\u043e\u0431\u0430\u0432\u044f\u043d\u0435\/\u043a\u043e\u0440\u0435\u043a\u0446\u0438\u044f \u043d\u0430 \u0445\u0438\u043f\u0435\u0440\u0432\u0440\u044a\u0437\u043a\u0430 (\u043b\u0438\u043d\u043a)", +"Text to display": "\u0422\u0435\u043a\u0441\u0442", +"Url": "\u0410\u0434\u0440\u0435\u0441 (URL)", +"Target": "\u0426\u0435\u043b \u0432 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430", +"None": "\u0411\u0435\u0437", +"New window": "\u0412 \u043d\u043e\u0432 \u043f\u0440\u043e\u0437\u043e\u0440\u0435\u0446 (\u043f\u043e\u0434\u043f\u0440\u043e\u0437\u043e\u0440\u0435\u0446)", +"Remove link": "\u041f\u0440\u0435\u043c\u0430\u0445\u0432\u0430\u043d\u0435 \u043d\u0430 \u0445\u0438\u043f\u0435\u0440\u0432\u0440\u044a\u0437\u043a\u0430", +"Anchors": "\u041a\u043e\u0442\u0432\u0438", +"Link": "\u0412\u0440\u044a\u0437\u043a\u0430(\u043b\u0438\u043d\u043a)", +"Paste or type a link": "\u041f\u043e\u0441\u0442\u0430\u0432\u0435\u0442\u0435 \u0438\u043b\u0438 \u043d\u0430\u043f\u0438\u0448\u0435\u0442\u0435 \u0432\u0440\u044a\u0437\u043a\u0430(\u043b\u0438\u043d\u043a)", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "URL \u0430\u0434\u0440\u0435\u0441\u044a\u0442, \u043a\u043e\u0439\u0442\u043e \u0432\u044a\u0432\u0434\u043e\u0445\u0442\u0435 \u043f\u0440\u0438\u043b\u0438\u0447\u0430 \u043d\u0430 \u0435-\u043c\u0435\u0439\u043b \u0430\u0434\u0440\u0435\u0441. \u0418\u0441\u043a\u0430\u0442\u0435 \u043b\u0438 \u0434\u0430 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u0438\u044f mailto: \u043f\u0440\u0435\u0444\u0438\u043a\u0441?", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "URL \u0430\u0434\u0440\u0435\u0441\u044a\u0442, \u043a\u043e\u0439\u0442\u043e \u0432\u044a\u0432\u0434\u043e\u0445\u0442\u0435 \u043f\u0440\u0438\u043b\u0438\u0447\u0430 \u0432\u044a\u043d\u0448\u0435\u043d \u0430\u0434\u0440\u0435\u0441. \u0418\u0441\u043a\u0430\u0442\u0435 \u043b\u0438 \u0434\u0430 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u0438\u044f http:\/\/ \u043f\u0440\u0435\u0444\u0438\u043a\u0441?", +"Link list": "\u0421\u043f\u0438\u0441\u044a\u043a \u0441 \u0432\u0440\u044a\u0437\u043a\u0438", +"Insert video": "\u0414\u043e\u0431\u0430\u0432\u044f\u043d\u0435 \u043d\u0430 \u0432\u0438\u0434\u0435\u043e", +"Insert\/edit video": "\u0414\u043e\u0431\u0430\u0432\u044f\u043d\u0435\/\u043a\u043e\u0440\u0435\u043a\u0446\u0438\u044f \u043d\u0430 \u0432\u0438\u0434\u0435\u043e", +"Insert\/edit media": "\u0414\u043e\u0431\u0430\u0432\u044f\u043d\u0435\/\u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u0430\u043d\u0435 \u043d\u0430 \u043c\u0435\u0434\u0438\u044f", +"Alternative source": "\u0410\u043b\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0435\u043d \u0430\u0434\u0440\u0435\u0441", +"Poster": "\u041f\u043e\u0441\u0442\u0435\u0440", +"Paste your embed code below:": "\u041f\u043e\u0441\u0442\u0430\u0432\u0435\u0442\u0435 \u043a\u043e\u0434\u0430 \u0437\u0430 \u0432\u0433\u0440\u0430\u0436\u0434\u0430\u043d\u0435 \u0432 \u043f\u043e\u043b\u0435\u0442\u043e \u043f\u043e-\u0434\u043e\u043b\u0443:", +"Embed": "\u0412\u0433\u0440\u0430\u0436\u0434\u0430\u043d\u0435", +"Media": "\u041c\u0435\u0434\u0438\u044f", +"Nonbreaking space": "\u0418\u043d\u0442\u0435\u0440\u0432\u0430\u043b", +"Page break": "\u041d\u043e\u0432\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430", +"Paste as text": "\u041f\u043e\u0441\u0442\u0430\u0432\u0438 \u043a\u0430\u0442\u043e \u0442\u0435\u043a\u0441\u0442", +"Preview": "\u041f\u0440\u0435\u0434\u0432\u0430\u0440\u0438\u0442\u0435\u043b\u0435\u043d \u0438\u0437\u0433\u043b\u0435\u0434", +"Print": "\u041f\u0435\u0447\u0430\u0442", +"Save": "\u0421\u044a\u0445\u0440\u0430\u043d\u044f\u0432\u0430\u043d\u0435", +"Find": "\u0422\u044a\u0440\u0441\u0435\u043d\u0435 \u0437\u0430", +"Replace with": "\u0417\u0430\u043c\u044f\u043d\u0430 \u0441", +"Replace": "\u0417\u0430\u043c\u044f\u043d\u0430", +"Replace all": "\u0417\u0430\u043c\u044f\u043d\u0430 \u043d\u0430 \u0432\u0441\u0438\u0447\u043a\u0438 \u0441\u0440\u0435\u0449\u0430\u043d\u0438\u044f", +"Prev": "\u041f\u0440\u0435\u0434\u0438\u0448\u0435\u043d", +"Next": "\u0421\u043b\u0435\u0434\u0432\u0430\u0449", +"Find and replace": "\u0422\u044a\u0440\u0441\u0435\u043d\u0435 \u0438 \u0437\u0430\u043c\u044f\u043d\u0430", +"Could not find the specified string.": "\u0422\u044a\u0440\u0441\u0435\u043d\u0438\u044f\u0442 \u0442\u0435\u043a\u0441\u0442 \u043d\u0435 \u0435 \u043d\u0430\u043c\u0435\u0440\u0435\u043d.", +"Match case": "\u0421\u044a\u0432\u043f\u0430\u0434\u0435\u043d\u0438\u0435 \u043d\u0430 \u0440\u0435\u0433\u0438\u0441\u0442\u044a\u0440\u0430 (\u043c\u0430\u043b\u043a\u0438\/\u0433\u043b\u0430\u0432\u043d\u0438 \u0431\u0443\u043a\u0432\u0438)", +"Whole words": "\u0421\u0430\u043c\u043e \u0446\u0435\u043b\u0438 \u0434\u0443\u043c\u0438", +"Spellcheck": "\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u043d\u0430 \u043f\u0440\u0430\u0432\u043e\u043f\u0438\u0441\u0430", +"Ignore": "\u0418\u0433\u043d\u043e\u0440\u0438\u0440\u0430\u043d\u0435", +"Ignore all": "\u0418\u0433\u043d\u043e\u0440\u0438\u0440\u0430\u043d\u0435 \u043d\u0430 \u0432\u0441\u0438\u0447\u043a\u043e", +"Finish": "\u041a\u0440\u0430\u0439", +"Add to Dictionary": "\u0414\u043e\u0431\u0430\u0432\u0438 \u0432 \u0440\u0435\u0447\u043d\u0438\u043a\u0430", +"Insert table": "\u0414\u043e\u0431\u0430\u0432\u044f\u043d\u0435 \u043d\u0430 \u0442\u0430\u0431\u043b\u0438\u0446\u0430", +"Table properties": "\u0421\u0432\u043e\u0439\u0441\u0442\u0432\u0430 \u043d\u0430 \u0442\u0430\u0431\u043b\u0438\u0446\u0430\u0442\u0430", +"Delete table": "\u0418\u0437\u0442\u0440\u0438\u0432\u0430\u043d\u0435 \u043d\u0430 \u0442\u0430\u0431\u043b\u0438\u0446\u0430\u0442\u0430", +"Cell": "\u041a\u043b\u0435\u0442\u043a\u0430", +"Row": "\u0420\u0435\u0434", +"Column": "\u041a\u043e\u043b\u043e\u043d\u0430", +"Cell properties": "\u0421\u0432\u043e\u0439\u0441\u0442\u0432\u0430 \u043d\u0430 \u043a\u043b\u0435\u0442\u043a\u0430\u0442\u0430", +"Merge cells": "\u0421\u043b\u0438\u0432\u0430\u043d\u0435 \u043d\u0430 \u043a\u043b\u0435\u0442\u043a\u0438\u0442\u0435", +"Split cell": "\u0420\u0430\u0437\u0434\u0435\u043b\u044f\u043d\u0435 \u043d\u0430 \u043a\u043b\u0435\u0442\u043a\u0430", +"Insert row before": "\u0412\u043c\u044a\u043a\u0432\u0430\u043d\u0435 \u043d\u0430 \u0440\u0435\u0434 \u043f\u0440\u0435\u0434\u0438", +"Insert row after": "\u0412\u043c\u044a\u043a\u0432\u0430\u043d\u0435 \u043d\u0430 \u0440\u0435\u0434 \u0441\u043b\u0435\u0434", +"Delete row": "\u0418\u0437\u0442\u0440\u0438\u0432\u0430\u043d\u0435 \u043d\u0430 \u0440\u0435\u0434\u0430", +"Row properties": "\u0421\u0432\u043e\u0439\u0441\u0442\u0432\u0430 \u043d\u0430 \u0440\u0435\u0434\u0430", +"Cut row": "\u0418\u0437\u0440\u044f\u0437\u0432\u0430\u043d\u0435 \u043d\u0430 \u0440\u0435\u0434", +"Copy row": "\u041a\u043e\u043f\u0438\u0440\u0430\u043d\u0435 \u043d\u0430 \u0440\u0435\u0434", +"Paste row before": "\u041f\u043e\u0441\u0442\u0430\u0432\u044f\u043d\u0435 \u043d\u0430 \u0440\u0435\u0434 \u043f\u0440\u0435\u0434\u0438", +"Paste row after": "\u041f\u043e\u0441\u0442\u0430\u0432\u044f\u043d\u0435 \u043d\u0430 \u0440\u0435\u0434 \u0441\u043b\u0435\u0434", +"Insert column before": "\u0412\u043c\u044a\u043a\u0432\u0430\u043d\u0435 \u043d\u0430 \u043a\u043e\u043b\u043e\u043d\u0430 \u043f\u0440\u0435\u0434\u0438", +"Insert column after": "\u0412\u043c\u044a\u043a\u0432\u0430\u043d\u0435 \u043d\u0430 \u043a\u043e\u043b\u043e\u043d\u0430 \u0441\u043b\u0435\u0434", +"Delete column": "\u0418\u0437\u0442\u0440\u0438\u0432\u0430\u043d\u0435 \u043d\u0430 \u043a\u043e\u043b\u043e\u043d\u0430\u0442\u0430", +"Cols": "\u041a\u043e\u043b\u043e\u043d\u0438", +"Rows": "\u0420\u0435\u0434\u043e\u0432\u0435", +"Width": "\u0428\u0438\u0440\u0438\u043d\u0430", +"Height": "\u0412\u0438\u0441\u043e\u0447\u0438\u043d\u0430", +"Cell spacing": "\u0420\u0430\u0437\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u043c\u0435\u0436\u0434\u0443 \u043a\u043b\u0435\u0442\u043a\u0438\u0442\u0435", +"Cell padding": "\u0420\u0430\u0437\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0434\u043e \u0441\u044a\u0434\u044a\u0440\u0436\u0430\u043d\u0438\u0435\u0442\u043e", +"Caption": "\u0414\u043e\u0431\u0430\u0432\u044f\u043d\u0435 \u043d\u0430 \u0437\u0430\u0433\u043b\u0430\u0432\u0438\u0435 \u043f\u0440\u0435\u0434\u0438 \u0442\u0430\u0431\u043b\u0438\u0446\u0430\u0442\u0430", +"Left": "\u041b\u044f\u0432\u043e", +"Center": "\u0426\u0435\u043d\u0442\u0440\u0438\u0440\u0430\u043d\u043e", +"Right": "\u0414\u044f\u0441\u043d\u043e", +"Cell type": "\u0422\u0438\u043f \u043d\u0430 \u043a\u043b\u0435\u0442\u043a\u0430\u0442\u0430", +"Scope": "\u041e\u0431\u0445\u0432\u0430\u0442", +"Alignment": "\u041f\u043e\u0434\u0440\u0430\u0432\u043d\u044f\u0432\u0430\u043d\u0435", +"H Align": "\u0425\u043e\u0440\u0438\u0437\u043e\u043d\u0442\u0430\u043b\u043d\u043e \u043f\u043e\u0434\u0440\u0430\u0432\u043d\u044f\u0432\u0430\u043d\u0435", +"V Align": "\u0412\u0435\u0440\u0442\u0438\u043a\u0430\u043b\u043d\u043e \u043f\u043e\u0434\u0440\u0430\u0432\u043d\u044f\u0432\u0430\u043d\u0435", +"Top": "\u0413\u043e\u0440\u0435", +"Middle": "\u041f\u043e \u0441\u0440\u0435\u0434\u0430\u0442\u0430", +"Bottom": "\u0414\u043e\u043b\u0443", +"Header cell": "\u0417\u0430\u0433\u043b\u0430\u0432\u043d\u0430 \u043a\u043b\u0435\u0442\u043a\u0430 (\u0430\u043d\u0442\u0435\u0442\u043a\u0430)", +"Row group": "Row group", +"Column group": "Column group", +"Row type": "\u0422\u0438\u043f \u043d\u0430 \u0440\u0435\u0434\u0430", +"Header": "\u0413\u043e\u0440\u0435\u043d \u043a\u043e\u043b\u043e\u043d\u0442\u0438\u0442\u0443\u043b (header)", +"Body": "\u0421\u044a\u0434\u044a\u0440\u0436\u0430\u043d\u0438\u0435 (body)", +"Footer": "\u0414\u043e\u043b\u0435\u043d \u043a\u043e\u043b\u043e\u043d\u0442\u0438\u0442\u0443\u043b (footer)", +"Border color": "\u0426\u0432\u044f\u0442 \u043d\u0430 \u0440\u0430\u043c\u043a\u0430\u0442\u0430", +"Insert template": "\u0414\u043e\u0431\u0430\u0432\u044f\u043d\u0435 \u043d\u0430 \u0448\u0430\u0431\u043b\u043e\u043d", +"Templates": "\u0428\u0430\u0431\u043b\u043e\u043d\u0438", +"Template": "\u0428\u0430\u0431\u043b\u043e\u043d", +"Text color": "\u0426\u0432\u044f\u0442 \u043d\u0430 \u0448\u0440\u0438\u0444\u0442\u0430", +"Background color": "\u0424\u043e\u043d\u043e\u0432 \u0446\u0432\u044f\u0442", +"Custom...": "\u0418\u0437\u0431\u0440\u0430\u043d...", +"Custom color": "\u0426\u0432\u044f\u0442 \u043f\u043e \u0438\u0437\u0431\u043e\u0440", +"No color": "\u0411\u0435\u0437 \u0446\u0432\u044f\u0442", +"Table of Contents": "\u0421\u044a\u0434\u044a\u0440\u0436\u0430\u043d\u0438\u0435", +"Show blocks": "\u041f\u043e\u043a\u0430\u0437\u0432\u0430\u043d\u0435 \u043d\u0430 \u0431\u043b\u043e\u043a\u043e\u0432\u0435\u0442\u0435", +"Show invisible characters": "\u041f\u043e\u043a\u0430\u0437\u0432\u0430\u043d\u0435 \u043d\u0430 \u043d\u0435\u043f\u0435\u0447\u0430\u0442\u0430\u0435\u043c\u0438 \u0437\u043d\u0430\u0446\u0438", +"Words: {0}": "\u0411\u0440\u043e\u0439 \u0434\u0443\u043c\u0438: {0}", +"{0} words": "{0} \u0431\u0440\u043e\u0439 \u0434\u0443\u043c\u0438", +"File": "\u0424\u0430\u0439\u043b", +"Edit": "\u0420\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u0430\u043d\u0435", +"Insert": "\u0412\u043c\u044a\u043a\u0432\u0430\u043d\u0435", +"View": "\u0418\u0437\u0433\u043b\u0435\u0434", +"Format": "\u0424\u043e\u0440\u043c\u0430\u0442\u0438\u0440\u0430\u043d\u0435", +"Table": "\u0422\u0430\u0431\u043b\u0438\u0446\u0430", +"Tools": "\u0418\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u0438", +"Powered by {0}": "\u0421\u044a\u0437\u0434\u0430\u0434\u0435\u043d\u043e \u0441 {0}", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "\u041f\u043e\u043b\u0435 \u0437\u0430 \u0444\u043e\u0440\u043c\u0430\u0442\u0438\u0440\u0430\u043d \u0442\u0435\u043a\u0441\u0442. \u041d\u0430\u0442\u0438\u0441\u043d\u0435\u0442\u0435 Alt+F9 \u0437\u0430 \u043c\u0435\u043d\u044e; Alt+F10 \u0437\u0430 \u043b\u0435\u043d\u0442\u0430 \u0441 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u0438; Alt+0 \u0437\u0430 \u043f\u043e\u043c\u043e\u0449." +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/bn_BD.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/bn_BD.js new file mode 100644 index 0000000000..0ce5a29140 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/bn_BD.js @@ -0,0 +1,261 @@ +tinymce.addI18n('bn_BD',{ +"Redo": "\u09aa\u09c1\u09a8\u09b0\u09be\u09af\u09bc \u0995\u09b0\u09c1\u09a8", +"Undo": "\u09aa\u09c2\u09b0\u09cd\u09ac\u09be\u09ac\u09b8\u09cd\u09a5\u09be\u09af\u09bc \u09ab\u09bf\u09b0\u09c1\u09a8", +"Cut": "\u0995\u09b0\u09cd\u09a4\u09a8", +"Copy": "\u0985\u09a8\u09c1\u0995\u09b0\u09a3", +"Paste": "\u09aa\u09cd\u09b0\u09a4\u09bf\u09b2\u09c7\u09aa\u09a8 \u0995\u09b0\u09c1\u09a8", +"Select all": "\u09b8\u09ac \u09a8\u09bf\u09b0\u09cd\u09ac\u09be\u099a\u09a8 \u0995\u09b0\u09c1\u09a8", +"New document": "\u09a8\u09a4\u09c1\u09a8 \u09a6\u09b8\u09cd\u09a4\u09be\u09ac\u09c7\u099c", +"Ok": "\u09a0\u09bf\u0995 \u0986\u099b\u09c7", +"Cancel": "\u09ac\u09be\u09a4\u09bf\u09b2", +"Visual aids": "\u09ac\u09cd\u09af\u09be\u0996\u09cd\u09af\u09be\u09ae\u09c2\u09b2\u0995 \u09b8\u09be\u09b9\u09be\u09af\u09cd\u09af", +"Bold": "\u09b8\u09cd\u09a5\u09c2\u09b2", +"Italic": "\u09a4\u09bf\u09b0\u09cd\u09af\u0995", +"Underline": "\u09a8\u09bf\u09ae\u09cd\u09a8\u09b0\u09c7\u0996\u09be", +"Strikethrough": "\u09b8\u09cd\u099f\u09cd\u09b0\u09be\u0987\u0995\u09a5\u09cd\u09b0\u09c1", +"Superscript": "\u098a\u09b0\u09cd\u09a7\u09cd\u09ac\u09b2\u09bf\u09aa\u09bf", +"Subscript": "\u09a8\u09bf\u09ae\u09cd\u09a8\u09b2\u09bf\u09aa\u09bf", +"Clear formatting": "\u09ac\u09bf\u09a8\u09cd\u09af\u09be\u09b8 \u0985\u09aa\u09b8\u09be\u09b0\u09a3", +"Align left": "\u09ac\u09be\u09ae\u09c7 \u09b8\u09be\u09b0\u09bf\u0995\u09b0\u09a3", +"Align center": "\u09ae\u09a7\u09cd\u09af\u09b8\u09cd\u09a5\u09be\u09a8\u09c7 \u09b8\u09be\u09b0\u09bf\u0995\u09b0\u09a3", +"Align right": "\u09a1\u09be\u09a8\u09c7 \u09b8\u09be\u09b0\u09bf\u0995\u09b0\u09a3", +"Justify": "\u0989\u09ad\u09af\u09bc\u09aa\u09cd\u09b0\u09be\u09a8\u09cd\u09a4\u09c7 \u09b8\u09ae\u09be\u09a8 \u0995\u09b0\u09c1\u09a8", +"Bullet list": "\u09ac\u09c1\u09b2\u09c7\u099f \u09a4\u09be\u09b2\u09bf\u0995\u09be", +"Numbered list": "\u09b8\u0982\u0996\u09cd\u09af\u09be\u09af\u09c1\u0995\u09cd\u09a4 \u09a4\u09be\u09b2\u09bf\u0995\u09be", +"Decrease indent": "\u0987\u09a8\u09cd\u09a1\u09c7\u09a8\u09cd\u099f \u0995\u09ae\u09be\u09a8", +"Increase indent": "\u0987\u09a8\u09cd\u09a1\u09c7\u09a8\u09cd\u099f \u09ac\u09be\u09a1\u09bc\u09be\u09a8", +"Close": "\u09ac\u09a8\u09cd\u09a7", +"Formats": "\u09ac\u09bf\u09a8\u09cd\u09af\u09be\u09b8", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "\u0986\u09aa\u09a8\u09be\u09b0 \u09ac\u09cd\u09b0\u09be\u0989\u099c\u09be\u09b0 \u0995\u09cd\u09b2\u09bf\u09aa\u09ac\u09cb\u09b0\u09cd\u09a1 \u09a5\u09c7\u0995\u09c7 \u09b8\u09b0\u09be\u09b8\u09b0\u09bf \u09aa\u09cd\u09b0\u09ac\u09c7\u09b6\u09be\u09a7\u09bf\u0995\u09be\u09b0 \u09b8\u09ae\u09b0\u09cd\u09a5\u09a8 \u0995\u09b0\u09c7 \u09a8\u09be\u0964 \u0985\u09a8\u09c1\u0997\u09cd\u09b0\u09b9 \u0995\u09b0\u09c7 \u0995\u09c0\u09ac\u09cb\u09b0\u09cd\u09a1 \u09b6\u09b0\u09cd\u099f\u0995\u09be\u099f Ctrl +X\/C\/V \u09ac\u09cd\u09af\u09ac\u09b9\u09be\u09b0 \u0995\u09b0\u09c1\u09a8\u0964", +"Headers": "\u09b9\u09c7\u09a1\u09be\u09b0 \u09b8\u09ae\u09c1\u09b9", +"Header 1": "\u09b9\u09c7\u09a1\u09be\u09b0 \u09e7", +"Header 2": "\u09b9\u09c7\u09a1\u09be\u09b0 \u09e8", +"Header 3": "\u09b9\u09c7\u09a1\u09be\u09b0 \u09e9", +"Header 4": "\u09b9\u09c7\u09a1\u09be\u09b0 \u09ea", +"Header 5": "\u09b9\u09c7\u09a1\u09be\u09b0 \u09eb", +"Header 6": "\u09b9\u09c7\u09a1\u09be\u09b0 \u09ec", +"Headings": "\u09b6\u09bf\u09b0\u09cb\u09a8\u09be\u09ae", +"Heading 1": "\u09b6\u09bf\u09b0\u09cb\u09a8\u09be\u09ae \u09e7", +"Heading 2": "\u09b6\u09bf\u09b0\u09cb\u09a8\u09be\u09ae \u09e8", +"Heading 3": "\u09b6\u09bf\u09b0\u09cb\u09a8\u09be\u09ae \u09e9", +"Heading 4": "\u09b6\u09bf\u09b0\u09cb\u09a8\u09be\u09ae \u09ea", +"Heading 5": "\u09b6\u09bf\u09b0\u09cb\u09a8\u09be\u09ae \u09eb", +"Heading 6": "\u09b6\u09bf\u09b0\u09cb\u09a8\u09be\u09ae \u09ec", +"Preformatted": "\u09aa\u09c2\u09b0\u09cd\u09ac\u09ac\u09bf\u09a8\u09cd\u09af\u09be\u09b8\u09bf\u09a4", +"Div": "\u09a1\u09bf\u09ad", +"Pre": "\u09aa\u09cd\u09b0\u09be\u0995", +"Code": "\u09b8\u0982\u0995\u09c7\u09a4\u09b2\u09bf\u09aa\u09bf", +"Paragraph": "\u09aa\u09cd\u09af\u09be\u09b0\u09be\u0997\u09cd\u09b0\u09be\u09ab", +"Blockquote": "\u09ac\u09cd\u09b2\u0995\u0995\u09cb\u099f", +"Inline": "\u09b8\u0999\u09cd\u0997\u09a4\u09bf\u09aa\u09c2\u09b0\u09cd\u09a3\u09ad\u09be\u09ac\u09c7", +"Blocks": "\u09b8\u09cd\u09a5\u09c2\u09b2 ", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "\u09aa\u09c7\u09b8\u09cd\u099f \u098f\u0996\u09a8 \u09aa\u09cd\u09b2\u09c7\u0987\u09a8 \u099f\u09c7\u0995\u09cd\u09b8\u099f \u09ae\u09cb\u09a1\u09c7\u0964 \u0986\u09aa\u09a8\u09bf \u098f\u0996\u09a8 \u098f\u0987 \u09ac\u09bf\u0995\u09b2\u09cd\u09aa \u09ac\u09a8\u09cd\u09a7 \u099f\u0997\u09b2 \u09aa\u09b0\u09cd\u09af\u09a8\u09cd\u09a4 \u09ac\u09bf\u09b7\u09af\u09bc\u09ac\u09b8\u09cd\u09a4\u09c1 \u098f\u0996\u09a8 \u09aa\u09cd\u09b2\u09c7\u0987\u09a8 \u099f\u09c7\u0995\u09cd\u09b8\u099f \u09b9\u09bf\u09b8\u09be\u09ac\u09c7 \u0986\u099f\u0995\u09be\u09a8\u09cb \u09b9\u09ac\u09c7\u0964", +"Font Family": "\u09ab\u09a8\u09cd\u099f \u09ab\u09cd\u09af\u09be\u09ae\u09bf\u09b2\u09bf", +"Font Sizes": "\u09ab\u09a8\u09cd\u099f \u09ae\u09be\u09aa", +"Class": "\u0995\u09cd\u09b2\u09be\u09b8", +"Browse for an image": "\u098f\u0995\u099f\u09bf \u099b\u09ac\u09bf \u09ac\u09cd\u09b0\u09be\u0989\u099c \u0995\u09b0\u09c1\u09a8", +"OR": "\u0985\u09a5\u09ac\u09be", +"Drop an image here": "\u098f\u0996\u09be\u09a8\u09c7 \u098f\u0995\u099f\u09bf \u099b\u09ac\u09bf \u09a1\u09cd\u09b0\u09aa \u0995\u09b0\u09c1\u09a8", +"Upload": "\u0986\u09aa\u09b2\u09cb\u09a1", +"Block": "\u09ac\u09cd\u09b2\u0995", +"Align": "\u09ac\u09bf\u09a8\u09cd\u09af\u09b8\u09cd\u09a4\u0995\u09b0\u09c1\u09a8", +"Default": "\u09a1\u09bf\u09ab\u09b2\u09cd\u099f", +"Circle": "\u09ac\u09c3\u09a4\u09cd\u09a4", +"Disc": "\u09a1\u09bf\u09b8\u09cd\u0995", +"Square": "\u09ac\u09b0\u09cd\u0997\u0995\u09cd\u09b7\u09c7\u09a4\u09cd\u09b0", +"Lower Alpha": "\u09a8\u09bf\u09ae\u09cd\u09a8 \u0986\u09b2\u09ab\u09be", +"Lower Greek": "\u09a8\u09bf\u09ae\u09cd\u09a8 \u0997\u09cd\u09b0\u09bf\u0995", +"Lower Roman": "\u09a8\u09bf\u09ae\u09cd\u09a8 \u09b0\u09cb\u09ae\u09be\u09a8", +"Upper Alpha": "\u0989\u099a\u09cd\u099a\u09a4\u09b0 \u0986\u09b2\u09ab\u09be", +"Upper Roman": "\u098a\u09b0\u09cd\u09a7\u09cd\u09ac \u09b0\u09cb\u09ae\u09be\u09a8", +"Anchor": "\u09a8\u09cb\u0999\u09cd\u0997\u09b0", +"Name": "\u09a8\u09be\u09ae", +"Id": "\u0986\u0987\u09a1\u09bf", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "\u0986\u0987\u09a1\u09bf\u099f\u09bf \u0985\u0995\u09cd\u09b7\u09b0, \u09b8\u0982\u0996\u09cd\u09af\u09be, \u09a1\u09cd\u09af\u09be\u09b6, \u09a1\u099f\u09b8, \u0995\u09b2\u09cb\u09a8 \u09ac\u09be \u0986\u09a8\u09cd\u09a1\u09be\u09b0\u09b8\u09cd\u0995\u09cb\u09b0 \u09a6\u09cd\u09ac\u09be\u09b0\u09be \u0985\u09a8\u09c1\u09b8\u09b0\u09a3 \u0995\u09b0\u09be \u098f\u0995\u099f\u09bf \u099a\u09bf\u09a0\u09bf \u09a6\u09bf\u09af\u09bc\u09c7 \u09b6\u09c1\u09b0\u09c1 \u0995\u09b0\u09be \u0989\u099a\u09bf\u09a4\u0964", +"You have unsaved changes are you sure you want to navigate away?": "\u0986\u09aa\u09a8\u09be\u09b0 \u0985\u09b8\u0982\u09b0\u0995\u09cd\u09b7\u09bf\u09a4 \u09aa\u09b0\u09bf\u09ac\u09b0\u09cd\u09a4\u09a8\u0997\u09c1\u09b2\u09bf \u0986\u09aa\u09a8\u09bf \u0995\u09bf \u09a8\u09bf\u09b6\u09cd\u099a\u09bf\u09a4 \u09af\u09c7 \u0986\u09aa\u09a8\u09bf \u09a8\u09c7\u09ad\u09bf\u0997\u09c7\u099f \u0995\u09b0\u09a4\u09c7 \u099a\u09be\u09a8?", +"Restore last draft": "\u09b6\u09c7\u09b7 \u0996\u09b8\u09a1\u09bc\u09be\u099f\u09bf \u09aa\u09c1\u09a8\u09b0\u09c1\u09a6\u09cd\u09a7\u09be\u09b0 \u0995\u09b0\u09c1\u09a8", +"Special character": "\u09ac\u09bf\u09b6\u09c7\u09b7 \u099a\u09b0\u09bf\u09a4\u09cd\u09b0", +"Source code": "\u09b8\u09cb\u09b0\u09cd\u09b8 \u0995\u09cb\u09a1", +"Insert\/Edit code sample": "\u0995\u09cb\u09a1 \u09a8\u09ae\u09c1\u09a8\u09be \u09a2\u09cb\u0995\u09be\u09a8 \/ \u09b8\u09ae\u09cd\u09aa\u09be\u09a6\u09a8\u09be \u0995\u09b0\u09c1\u09a8", +"Language": "\u09ad\u09be\u09b7\u09be", +"Code sample": "\u09a8\u09ae\u09c1\u09a8\u09be \u0995\u09cb\u09a1", +"Color": "\u09b0\u0999", +"R": "R", +"G": "G", +"B": "B", +"Left to right": "\u09ac\u09be\u09ae \u09a5\u09c7\u0995\u09c7 \u09a1\u09be\u09a8", +"Right to left": "\u09a1\u09be\u09a8 \u09a5\u09c7\u0995\u09c7 \u09ac\u09be\u09ae", +"Emoticons": "\u0987\u09ae\u09cb\u099f\u09bf\u0995\u09a8", +"Document properties": "\u09a8\u09a5\u09bf\u09b0 \u09ac\u09c8\u09b6\u09bf\u09b7\u09cd\u099f\u09cd\u09af", +"Title": "\u09b6\u09bf\u09b0\u09cb\u09a8\u09be\u09ae", +"Keywords": "\u0995\u09c0\u0993\u09af\u09bc\u09be\u09b0\u09cd\u09a1", +"Description": "\u09ac\u09bf\u09ac\u09b0\u09a3", +"Robots": "\u09b0\u09cb\u09ac\u099f", +"Author": "\u09b2\u09c7\u0996\u0995", +"Encoding": "\u098f\u09a8\u0995\u09cb\u09a1\u09bf\u0982", +"Fullscreen": "\u09aa\u09c2\u09b0\u09cd\u09a3 \u09aa\u09b0\u09cd\u09a6\u09be", +"Action": "\u0995\u09b0\u09cd\u09ae", +"Shortcut": "\u09b6\u09b0\u09cd\u099f\u0995\u09be\u099f", +"Help": "\u09b8\u09be\u09b9\u09be\u09af\u09cd\u09af \u0995\u09b0\u09c1\u09a8", +"Address": "\u09a0\u09bf\u0995\u09be\u09a8\u09be", +"Focus to menubar": "\u09ae\u09c7\u09a8\u09c1\u09ac\u09be\u09b0\u09c7 \u09ab\u09cb\u0995\u09be\u09b8 \u0995\u09b0\u09c1\u09a8", +"Focus to toolbar": "\u099f\u09c1\u09b2\u09ac\u09be\u09b0\u09c7 \u09ab\u09cb\u0995\u09be\u09b8 \u0995\u09b0\u09c1\u09a8", +"Focus to element path": "\u0989\u09aa\u09be\u09a6\u09be\u09a8 \u09aa\u09be\u09a5 \u09ab\u09cb\u0995\u09be\u09b8 \u0995\u09b0\u09c1\u09a8", +"Focus to contextual toolbar": "\u09aa\u09cd\u09b0\u09be\u09b8\u0999\u09cd\u0997\u09bf\u0995 \u099f\u09c1\u09b2\u09ac\u09be\u09b0\u09c7 \u09ab\u09cb\u0995\u09be\u09b8 \u0995\u09b0\u09c1\u09a8", +"Insert link (if link plugin activated)": "\u09b2\u09bf\u0999\u09cd\u0995 \u09b8\u09a8\u09cd\u09a8\u09bf\u09ac\u09c7\u09b6 \u0995\u09b0\u09c1\u09a8 (\u09af\u09a6\u09bf \u09b2\u09bf\u0999\u09cd\u0995 \u09aa\u09cd\u09b2\u09be\u0997\u0987\u09a8 \u0985\u09cd\u09af\u09be\u0995\u09cd\u099f\u09bf\u09ad\u09c7\u099f \u0995\u09b0\u09be \u09b9\u09af\u09bc)", +"Save (if save plugin activated)": "\u09b8\u0982\u09b0\u0995\u09cd\u09b7\u09a3 \u0995\u09b0\u09c1\u09a8 (\u09aa\u09cd\u09b2\u09be\u0997\u0987\u09a8 \u0985\u09cd\u09af\u09be\u0995\u09cd\u099f\u09bf\u09ad\u09c7\u099f \u09b9\u09b2\u09c7)", +"Find (if searchreplace plugin activated)": "\u09b8\u09a8\u09cd\u09a7\u09be\u09a8 \u0995\u09b0\u09c1\u09a8 (\u09af\u09a6\u09bf \u0985\u09a8\u09c1\u09b8\u09a8\u09cd\u09a7\u09be\u09a8\u09af\u09cb\u0997\u09cd\u09af \u09aa\u09cd\u09b2\u09be\u0997\u0987\u09a8 \u09b8\u0995\u09cd\u09b0\u09bf\u09af\u09bc \u0995\u09b0\u09be \u09b9\u09af\u09bc)", +"Plugins installed ({0}):": "\u09aa\u09cd\u09b2\u09be\u0997\u0987\u09a8 \u0987\u09a8\u09b8\u09cd\u099f\u09b2 ({0}):", +"Premium plugins:": "\u09aa\u09cd\u09b0\u09bf\u09ae\u09bf\u09af\u09bc\u09be\u09ae \u09aa\u09cd\u09b2\u09be\u0997\u0987\u09a8:", +"Learn more...": "\u0986\u09b0\u0993 \u099c\u09be\u09a8\u09c1\u09a8...", +"You are using {0}": "\u0986\u09aa\u09a8\u09bf \u09ac\u09cd\u09af\u09ac\u09b9\u09be\u09b0 \u0995\u09b0\u099b\u09c7\u09a8 {0}", +"Plugins": "\u09aa\u09cd\u09b2\u09be\u0997\u0987\u09a8", +"Handy Shortcuts": "\u09b8\u09b9\u099c \u09b6\u09b0\u09cd\u099f\u0995\u09be\u099f ", +"Horizontal line": "\u0985\u09a8\u09c1\u09ad\u09c2\u09ae\u09bf\u0995 \u09b0\u09c7\u0996\u09be", +"Insert\/edit image": "\u0987\u09ae\u09c7\u099c \u09b8\u09a8\u09cd\u09a8\u09bf\u09ac\u09c7\u09b6 \/ \u09b8\u09ae\u09cd\u09aa\u09be\u09a6\u09a8\u09be \u0995\u09b0\u09c1\u09a8", +"Image description": "\u099b\u09ac\u09bf\u09b0 \u09ac\u09b0\u09cd\u09a3\u09a8\u09be", +"Source": "\u0989\u09ce\u09b8", +"Dimensions": "\u09ae\u09be\u09a4\u09cd\u09b0\u09be", +"Constrain proportions": "\u0985\u09a8\u09c1\u09aa\u09be\u09a4 \u09b8\u09c0\u09ae\u09be\u09ac\u09a6\u09cd\u09a7", +"General": "\u09b8\u09be\u09a7\u09be\u09b0\u09a3", +"Advanced": "\u0985\u0997\u09cd\u09b0\u09b8\u09b0", +"Style": "\u09b6\u09c8\u09b2\u09c0", +"Vertical space": "\u0989\u09b2\u09cd\u09b2\u09ae\u09cd\u09ac \u09b8\u09cd\u09a5\u09be\u09a8", +"Horizontal space": "\u0985\u09a8\u09c1\u09ad\u09c2\u09ae\u09bf\u0995 \u09b8\u09cd\u09a5\u09be\u09a8", +"Border": "\u09b8\u09c0\u09ae\u09be\u09a8\u09cd\u09a4", +"Insert image": "\u099a\u09bf\u09a4\u09cd\u09b0 \u09a2\u09cb\u0995\u09be\u09a8", +"Image": "\u099b\u09ac\u09bf", +"Image list": "\u099a\u09bf\u09a4\u09cd\u09b0 \u09a4\u09be\u09b2\u09bf\u0995\u09be", +"Rotate counterclockwise": "\u09ac\u09be\u09ae\u09be\u09ac\u09b0\u09cd\u09a4\u09c7 \u0998\u09cb\u09b0\u09be\u09a4\u09c7", +"Rotate clockwise": "\u0998\u09a1\u09bc\u09bf\u09b0 \u0995\u09be\u0981\u099f\u09be\u09b0 \u09a6\u09bf\u0995\u09c7 \u0998\u09cb\u09b0\u09be\u09a8", +"Flip vertically": "\u0989\u09b2\u09cd\u09b2\u09ae\u09cd\u09ac\u09ad\u09be\u09ac\u09c7 \u09ab\u09cd\u09b2\u09bf\u09aa \u0995\u09b0\u09c1\u09a8", +"Flip horizontally": "\u0985\u09a8\u09c1\u09ad\u09c2\u09ae\u09bf\u0995\u09ad\u09be\u09ac\u09c7 \u09ab\u09cd\u09b2\u09bf\u09aa \u0995\u09b0\u09c1\u09a8", +"Edit image": "\u099a\u09bf\u09a4\u09cd\u09b0 \u09b8\u09ae\u09cd\u09aa\u09be\u09a6\u09a8\u09be \u0995\u09b0\u09c1\u09a8", +"Image options": "\u099a\u09bf\u09a4\u09cd\u09b0 \u09ac\u09bf\u0995\u09b2\u09cd\u09aa\u0997\u09c1\u09b2\u09bf", +"Zoom in": "\u09aa\u09cd\u09b0\u09b8\u09be\u09b0\u09bf\u09a4 \u0995\u09b0\u09cb", +"Zoom out": "\u099b\u09cb\u099f \u0995\u09b0\u09be", +"Crop": "\u0995\u09be\u099f\u09be", +"Resize": "\u09ae\u09be\u09aa \u09aa\u09b0\u09bf\u09ac\u09b0\u09cd\u09a4\u09a8 \u0995\u09b0\u09c1\u09a8", +"Orientation": "\u099d\u09cb\u0981\u0995", +"Brightness": "\u0989\u099c\u09cd\u099c\u09cd\u09ac\u09b2\u09a4\u09be", +"Sharpen": "\u09a7\u09be\u09b0 \u0995\u09b0\u09be", +"Contrast": "\u09ac\u09bf\u09aa\u09b0\u09c0\u09a4 \u09b9\u09a4\u09cd\u09a4\u09af\u09bc\u09be", +"Color levels": "\u09b0\u0999\u09c7\u09b0 \u09ae\u09be\u09a4\u09cd\u09b0\u09be", +"Gamma": "Gamma", +"Invert": "\u09ac\u09bf\u09a8\u09b7\u09cd\u099f \u0995\u09b0\u09be", +"Apply": "\u09aa\u09cd\u09b0\u09af\u09bc\u09cb\u0997 \u0995\u09b0\u09be", +"Back": "\u09aa\u09bf\u099b\u09a8\u09c7", +"Insert date\/time": "\u09a4\u09be\u09b0\u09bf\u0996 \/ \u09b8\u09ae\u09af\u09bc \u09b8\u09a8\u09cd\u09a8\u09bf\u09ac\u09c7\u09b6 \u0995\u09b0\u09c1\u09a8", +"Date\/time": "\u09a4\u09be\u09b0\u09bf\u0996 \/ \u09b8\u09ae\u09af\u09bc", +"Insert link": "\u09b2\u09bf\u0999\u09cd\u0995 \u09b8\u09a8\u09cd\u09a8\u09bf\u09ac\u09c7\u09b6 \u0995\u09b0\u09c1\u09a8", +"Insert\/edit link": "\u09b8\u09a8\u09cd\u09a8\u09bf\u09ac\u09c7\u09b6 \/ \u09b8\u09ae\u09cd\u09aa\u09be\u09a6\u09a8\u09be \u09b2\u09bf\u0999\u09cd\u0995", +"Text to display": "\u09aa\u09cd\u09b0\u09a6\u09b0\u09cd\u09b6\u09a8 \u099f\u09c7\u0995\u09cd\u09b8\u099f", +"Url": "URL", +"Target": "\u09b2\u0995\u09cd\u09b7\u09cd\u09af", +"None": "\u09a8\u09be", +"New window": "\u09a8\u09a4\u09c1\u09a8 \u0989\u0987\u09a8\u09cd\u09a1\u09cb", +"Remove link": "\u09b2\u09bf\u0999\u09cd\u0995 \u09b8\u09b0\u09be\u09a8", +"Anchors": "\u09a8\u09cb\u0999\u09cd\u0997\u09b0", +"Link": "\u09b2\u09bf\u0982\u0995", +"Paste or type a link": "\u098f\u0995\u099f\u09bf \u09b2\u09bf\u0999\u09cd\u0995 \u0986\u099f\u0995\u09be\u09a8 \u09ac\u09be \u099f\u09be\u0987\u09aa \u0995\u09b0\u09c1\u09a8", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "\u0986\u09aa\u09a8\u09be\u09b0 \u09aa\u09cd\u09b0\u09ac\u09c7\u09b6 \u0995\u09b0\u09be URL\u099f\u09bf \u098f\u0995\u099f\u09bf \u0987\u09ae\u09c7\u09b2 \u09a0\u09bf\u0995\u09be\u09a8\u09be \u09ac\u09b2\u09c7 \u09ae\u09a8\u09c7 \u09b9\u099a\u09cd\u099b\u09c7\u0964 \u0986\u09aa\u09a8\u09bf \u09aa\u09cd\u09b0\u09af\u09bc\u09cb\u099c\u09a8\u09c0\u09af\u09bc \u09ae\u09c7\u0987\u09b2\u099f\u09cb \u09af\u09cb\u0997 \u0995\u09b0\u09a4\u09c7 \u099a\u09be\u09a8: \u0989\u09aa\u09b8\u09b0\u09cd\u0997?", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "\u0986\u09aa\u09a8\u09be\u09b0 \u09aa\u09cd\u09b0\u09ac\u09c7\u09b6 \u0995\u09b0\u09be URL\u099f\u09bf \u098f\u0995\u099f\u09bf \u09ac\u09b9\u09bf\u09b0\u09be\u0997\u09a4 \u09b2\u09bf\u0999\u09cd\u0995 \u09ac\u09b2\u09c7 \u09ae\u09a8\u09c7 \u09b9\u099a\u09cd\u099b\u09c7\u0964 \u0986\u09aa\u09a8\u09bf \u0995\u09bf \u09aa\u09cd\u09b0\u09af\u09bc\u09cb\u099c\u09a8\u09c0\u09af\u09bc http:\/\/ \u09aa\u09cd\u09b0\u09bf\u09ab\u09bf\u0995\u09cd\u09b8 \u09af\u09cb\u0997 \u0995\u09b0\u09a4\u09c7 \u099a\u09be\u09a8?", +"Link list": "\u09b2\u09bf\u0999\u09cd\u0995 \u09a4\u09be\u09b2\u09bf\u0995\u09be", +"Insert video": "\u09ad\u09bf\u09a1\u09bf\u0993 \u09a2\u09cb\u0995\u09be\u09a8", +"Insert\/edit video": "\u09ad\u09bf\u09a1\u09bf\u0993 \u09b8\u09a8\u09cd\u09a8\u09bf\u09ac\u09c7\u09b6 \/ \u09b8\u09ae\u09cd\u09aa\u09be\u09a6\u09a8\u09be \u0995\u09b0\u09c1\u09a8", +"Insert\/edit media": "\u09ae\u09bf\u09a1\u09bf\u09af\u09bc\u09be \u09b8\u09a8\u09cd\u09a8\u09bf\u09ac\u09c7\u09b6 \u0995\u09b0\u09c1\u09a8 \/ \u09b8\u09ae\u09cd\u09aa\u09be\u09a6\u09a8\u09be \u0995\u09b0\u09c1\u09a8", +"Alternative source": "\u09ac\u09bf\u0995\u09b2\u09cd\u09aa \u0989\u09ce\u09b8", +"Poster": "\u09aa\u09cb\u09b8\u09cd\u099f\u09be\u09b0", +"Paste your embed code below:": "\u09a8\u09c0\u099a\u09c7\u09b0 \u0986\u09aa\u09a8\u09be\u09b0 \u098f\u09ae\u09cd\u09ac\u09c7\u09a1 \u0995\u09cb\u09a1 \u0986\u099f\u0995\u09be\u09a8:", +"Embed": "\u098f\u09ae\u09cd\u09ac\u09c7\u09a1", +"Media": "\u09ae\u09bf\u09a1\u09bf\u09af\u09bc\u09be", +"Nonbreaking space": "\u0985\u09ac\u09bf\u099a\u09cd\u099b\u09bf\u09a8\u09cd\u09a8 \u09b8\u09cd\u09a5\u09be\u09a8", +"Page break": "\u09aa\u09c3\u09b7\u09cd\u09a0\u09be \u09ac\u09bf\u09b0\u09a4\u09bf", +"Paste as text": "\u09aa\u09be\u09a0\u09cd\u09af \u09b9\u09bf\u09b8\u09be\u09ac\u09c7 \u09aa\u09c7\u09b8\u09cd\u099f \u0995\u09b0\u09c1\u09a8", +"Preview": "\u09aa\u09c2\u09b0\u09cd\u09ac\u09b0\u09c2\u09aa", +"Print": "\u099b\u09be\u09aa\u09be", +"Save": "\u09b8\u0982\u09b0\u0995\u09cd\u09b7\u09a3", +"Find": "\u0986\u09ac\u09bf\u09b7\u09cd\u0995\u09be\u09b0", +"Replace with": "\u09aa\u09cd\u09b0\u09a4\u09bf\u09b8\u09cd\u09a5\u09be\u09aa\u09a8", +"Replace": "\u09aa\u09cd\u09b0\u09a4\u09bf\u09b8\u09cd\u09a5\u09be\u09aa\u09a8 \u0995\u09b0\u09be", +"Replace all": "\u09b8\u09ae\u09b8\u09cd\u09a4 \u09aa\u09cd\u09b0\u09a4\u09bf\u09b8\u09cd\u09a5\u09be\u09aa\u09a8", +"Prev": "\u09aa\u09c2\u09b0\u09cd\u09ac\u09ac\u09b0\u09cd\u09a4\u09c0", +"Next": "\u09aa\u09b0\u09ac\u09b0\u09cd\u09a4\u09c0", +"Find and replace": "\u0996\u09c1\u0981\u099c\u09c1\u09a8 \u0993 \u09aa\u09cd\u09b0\u09a4\u09bf\u09b8\u09cd\u09a5\u09be\u09aa\u09a8 \u0995\u09b0\u09c1\u09a8", +"Could not find the specified string.": "\u09a8\u09bf\u09b0\u09cd\u09a6\u09bf\u09b7\u09cd\u099f \u09b8\u09cd\u099f\u09cd\u09b0\u09bf\u0982\u099f\u09bf \u0996\u09c1\u0981\u099c\u09c7 \u09aa\u09be\u0993\u09af\u09bc\u09be \u09af\u09be\u09af\u09bc\u09a8\u09bf\u0964", +"Match case": "\u09ae\u09cd\u09af\u09be\u099a \u0995\u09cd\u09b7\u09c7\u09a4\u09cd\u09b0\u09c7", +"Whole words": "\u09b8\u09ae\u09cd\u09aa\u09c2\u09b0\u09cd\u09a3 \u09b6\u09ac\u09cd\u09a6\u09c7\u09b0", +"Spellcheck": "\u09ac\u09be\u09a8\u09be\u09a8 \u09af\u09be\u099a\u09be\u0987", +"Ignore": "\u0989\u09aa\u09c7\u0995\u09cd\u09b7\u09be \u0995\u09b0\u09be", +"Ignore all": "\u09b8\u09ac\u0997\u09c1\u09b2\u09cb \u0989\u09aa\u09c7\u0995\u09cd\u09b7\u09be \u0995\u09b0\u09c1\u09a8", +"Finish": "\u09b6\u09c7\u09b7", +"Add to Dictionary": "\u0985\u09ad\u09bf\u09a7\u09be\u09a8 \u09af\u09cb\u0997 \u0995\u09b0\u09c1\u09a8", +"Insert table": "\u099f\u09c7\u09ac\u09bf\u09b2 \u09b8\u09a8\u09cd\u09a8\u09bf\u09ac\u09c7\u09b6 \u0995\u09b0\u09c1\u09a8", +"Table properties": "\u099f\u09c7\u09ac\u09bf\u09b2 \u09ac\u09c8\u09b6\u09bf\u09b7\u09cd\u099f\u09cd\u09af", +"Delete table": "\u09b8\u09be\u09b0\u09a3\u09bf \u09ae\u09c1\u099b\u09c1\u09a8", +"Cell": "\u09b8\u09c7\u09b2", +"Row": "\u09b8\u09be\u09b0\u09bf", +"Column": "\u0995\u09b2\u09be\u09ae", +"Cell properties": "\u09b8\u09c7\u09b2 \u09ac\u09c8\u09b6\u09bf\u09b7\u09cd\u099f\u09cd\u09af", +"Merge cells": "\u09b8\u09c7\u09b2 \u09ae\u09be\u09b0\u09cd\u099c \u0995\u09b0\u09c1\u09a8", +"Split cell": "\u09b8\u09cd\u09aa\u09cd\u09b2\u09bf\u099f \u09b8\u09c7\u09b2", +"Insert row before": "\u0986\u0997\u09c7 \u09b8\u09be\u09b0\u09bf \u09a2\u09cb\u0995\u09be\u09a8", +"Insert row after": "\u09aa\u09b0\u09c7 \u09b8\u09be\u09b0\u09bf \u09a2\u09cb\u0995\u09be\u09a8", +"Delete row": "\u09b8\u09be\u09b0\u09bf \u09ae\u09c1\u099b\u09c1\u09a8", +"Row properties": "\u09b8\u09be\u09b0\u09bf \u09ac\u09c8\u09b6\u09bf\u09b7\u09cd\u099f\u09cd\u09af", +"Cut row": "\u09b8\u09be\u09b0\u09bf \u0995\u09be\u099f\u09c1\u09a8", +"Copy row": "\u09b8\u09be\u09b0\u09bf \u0985\u09a8\u09c1\u09b2\u09bf\u09aa\u09bf \u0995\u09b0\u09c1\u09a8", +"Paste row before": "\u0986\u0997\u09c7 \u09b8\u09be\u09b0\u09bf \u09aa\u09c7\u09b8\u09cd\u099f \u0995\u09b0\u09c1\u09a8", +"Paste row after": "\u09aa\u09b0\u09c7 \u09b8\u09be\u09b0\u09bf \u09aa\u09c7\u09b8\u09cd\u099f \u0995\u09b0\u09c1\u09a8", +"Insert column before": "\u0986\u0997\u09c7 \u0995\u09b2\u09be\u09ae \u09a2\u09cb\u0995\u09be\u09a8", +"Insert column after": "\u09aa\u09b0\u09c7 \u0995\u09b2\u09be\u09ae \u09b8\u09a8\u09cd\u09a8\u09bf\u09ac\u09c7\u09b6 \u0995\u09b0\u09c1\u09a8", +"Delete column": "\u0995\u09b2\u09be\u09ae \u09ae\u09c1\u099b\u09c1\u09a8", +"Cols": "\u0995\u09b2\u09be\u09ae \u0997\u09c1\u09b2\u09cb", +"Rows": "\u09b8\u09be\u09b0\u09bf\u0997\u09c1\u09b2\u09cb", +"Width": "\u09aa\u09cd\u09b0\u09b8\u09cd\u09a5", +"Height": "\u0989\u099a\u09cd\u099a\u09a4\u09be", +"Cell spacing": "\u09b8\u09c7\u09b2 \u09ab\u09be\u0981\u0995\u09be", +"Cell padding": "\u09b8\u09c7\u09b2 \u09aa\u09cd\u09af\u09be\u09a1\u09bf\u0982", +"Caption": "\u0995\u09cd\u09af\u09be\u09aa\u09b6\u09a8", +"Left": "\u09ac\u09be\u09ae", +"Center": "\u0995\u09c7\u09a8\u09cd\u09a6\u09cd\u09b0", +"Right": "\u09a1\u09be\u09a8", +"Cell type": "\u09b8\u09c7\u09b2 \u099f\u09be\u0987\u09aa", +"Scope": "\u09ac\u09cd\u09af\u09be\u09aa\u09cd\u09a4\u09bf", +"Alignment": "\u09b6\u09cd\u09b0\u09c7\u09a3\u09c0\u09ac\u09bf\u09a8\u09cd\u09af\u09be\u09b8", +"H Align": "H \u09b8\u09be\u09b0\u09bf\u09ac\u09a6\u09cd\u09a7", +"V Align": "V \u09b8\u09be\u09b0\u09bf\u09ac\u09a6\u09cd\u09a7", +"Top": "\u0989\u09aa\u09b0", +"Middle": "\u09ae\u09a7\u09cd\u09af\u09ae", +"Bottom": "\u09a8\u09bf\u099a\u09c7", +"Header cell": "\u09b9\u09c7\u09a1\u09be\u09b0 \u09b8\u09c7\u09b2", +"Row group": "\u09b8\u09be\u09b0\u09bf \u0997\u09cd\u09b0\u09c1\u09aa", +"Column group": "\u0995\u09b2\u09be\u09ae \u0997\u09cd\u09b0\u09c1\u09aa", +"Row type": "\u09b8\u09be\u09b0\u09bf\u09b0 \u09a7\u09b0\u09a8", +"Header": "\u09b9\u09c7\u09a1\u09be\u09b0", +"Body": "\u09ac\u09a1\u09bf", +"Footer": "\u09ab\u09c1\u099f\u09be\u09b0", +"Border color": "\u09b8\u09c0\u09ae\u09be\u09a8\u09cd\u09a4 \u09b0\u0999", +"Insert template": "\u099f\u09c7\u09ae\u09aa\u09cd\u09b2\u09c7\u099f \u09a2\u09cb\u0995\u09be\u09a8", +"Templates": "\u099f\u09c7\u09ae\u09aa\u09cd\u09b2\u09c7\u099f", +"Template": "\u099f\u09c7\u09ae\u09aa\u09cd\u09b2\u09c7\u099f", +"Text color": "\u09b2\u09c7\u0996\u09be\u09b0 \u09b0\u0999", +"Background color": "\u09aa\u09c7\u099b\u09a8\u09c7\u09b0 \u09b0\u0982", +"Custom...": "\u0995\u09be\u09b8\u09cd\u099f\u09ae...", +"Custom color": "\u0995\u09be\u09b8\u09cd\u099f\u09ae \u09b0\u0982", +"No color": "\u0995\u09cb\u09a8 \u09b0\u0982 \u09a8\u09c7\u0987", +"Table of Contents": "\u09b8\u09c1\u099a\u09bf\u09aa\u09a4\u09cd\u09b0", +"Show blocks": "\u09ac\u09cd\u09b2\u0995 \u09a6\u09c7\u0996\u09be\u09a8", +"Show invisible characters": "\u0985\u09a6\u09c3\u09b6\u09cd\u09af \u0985\u0995\u09cd\u09b7\u09b0 \u09a6\u09c7\u0996\u09be\u09a8", +"Words: {0}": "\u09b6\u09ac\u09cd\u09a6: {0}", +"{0} words": "{0} \u09b6\u09ac\u09cd\u09a6", +"File": "\u09ab\u09be\u0987\u09b2", +"Edit": "\u09b8\u09ae\u09cd\u09aa\u09be\u09a6\u09a8 \u0995\u09b0\u09be", +"Insert": "\u09b8\u09a8\u09cd\u09a8\u09bf\u09ac\u09c7\u09b6", +"View": "\u09a6\u09c3\u09b6\u09cd\u09af", +"Format": "\u09ac\u09bf\u09a8\u09cd\u09af\u09be\u09b8", +"Table": "\u099f\u09c7\u09ac\u09bf\u09b2", +"Tools": "\u09b8\u09b0\u099e\u09cd\u099c\u09be\u09ae\u09b8\u09ae\u09c2\u09b9", +"Powered by {0}": "{0} \u09a6\u09cd\u09ac\u09be\u09b0\u09be \u099a\u09be\u09b2\u09bf\u09a4", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "\u09b0\u09bf\u099a \u099f\u09c7\u0995\u09cd\u09b8\u099f \u098f\u09b0\u09bf\u09af\u09bc\u09be \u09ae\u09c7\u09a8\u09c1 \u099c\u09a8\u09cd\u09af ALT-F9 \u099a\u09be\u09aa\u09c1\u09a8 \u099f\u09c1\u09b2\u09ac\u09be\u09b0\u09c7\u09b0 \u099c\u09a8\u09cd\u09af ALT-F10 \u099f\u09bf\u09aa\u09c1\u09a8 \u09b8\u09be\u09b9\u09be\u09af\u09cd\u09af\u09c7\u09b0 \u099c\u09a8\u09cd\u09af ALT-0 \u099a\u09be\u09aa\u09c1\u09a8" +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/ca.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/ca.js new file mode 100644 index 0000000000..671e875351 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/ca.js @@ -0,0 +1,261 @@ +tinymce.addI18n('ca',{ +"Redo": "Refer", +"Undo": "Desfer", +"Cut": "Retalla", +"Copy": "Copia", +"Paste": "Enganxa", +"Select all": "Seleccionar-ho tot", +"New document": "Nou document", +"Ok": "Acceptar", +"Cancel": "Cancel\u00b7la", +"Visual aids": "Assist\u00e8ncia visual", +"Bold": "Negreta", +"Italic": "Cursiva", +"Underline": "Subratllat", +"Strikethrough": "Ratllat", +"Superscript": "Super\u00edndex", +"Subscript": "Sub\u00edndex", +"Clear formatting": "Eliminar format", +"Align left": "Aliniat a l'esquerra", +"Align center": "Centrat", +"Align right": "Aliniat a la dreta", +"Justify": "Justificat", +"Bullet list": "Llista no ordenada", +"Numbered list": "Llista enumerada", +"Decrease indent": "Disminuir sagnat", +"Increase indent": "Augmentar sagnat", +"Close": "Tanca", +"Formats": "Formats", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "El vostre navegador no suporta l'acc\u00e9s directe al portaobjectes. Si us plau, feu servir les dreceres de teclat Ctrl+X\/C\/V.", +"Headers": "Cap\u00e7aleres", +"Header 1": "Cap\u00e7alera 1", +"Header 2": "Cap\u00e7alera 2", +"Header 3": "Cap\u00e7alera 3", +"Header 4": "Cap\u00e7alera 4", +"Header 5": "Cap\u00e7alera 5", +"Header 6": "Cap\u00e7alera 6", +"Headings": "Encap\u00e7alaments", +"Heading 1": "Encap\u00e7alament 1", +"Heading 2": "Encap\u00e7alament 2", +"Heading 3": "Encap\u00e7alament 3", +"Heading 4": "Encap\u00e7alament 4", +"Heading 5": "Encap\u00e7alament 5", +"Heading 6": "Encap\u00e7alament 6", +"Preformatted": "Preformatted", +"Div": "Div", +"Pre": "Pre", +"Code": "Codi", +"Paragraph": "Par\u00e0graf", +"Blockquote": "Cita", +"Inline": "En l\u00ednia", +"Blocks": "Blocs", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "Enganxar ara est\u00e0 en mode text pla. Els continguts s'enganxaran com a text pla fins que desactivis aquesta opci\u00f3. ", +"Font Family": "Fam\u00edlia de la font", +"Font Sizes": "Mides de la font", +"Class": "Classe", +"Browse for an image": "Explorar una imatge", +"OR": "O", +"Drop an image here": "Deixar anar una imatge aqu\u00ed", +"Upload": "Pujar", +"Block": "Bloc", +"Align": "Alinear", +"Default": "Per defecte", +"Circle": "Cercle", +"Disc": "Disc", +"Square": "Quadrat", +"Lower Alpha": "Alfa menor", +"Lower Greek": "Grec menor", +"Lower Roman": "Roman menor", +"Upper Alpha": "Alfa major", +"Upper Roman": "Roman major", +"Anchor": "\u00c0ncora", +"Name": "Nom", +"Id": "Id", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "La Id ha de comen\u00e7ar amb una lletra, seguida d'altres lletres, n\u00fameros, punts, ratlles, comes, o guions baixos", +"You have unsaved changes are you sure you want to navigate away?": "Teniu canvis sense desar, esteu segur que voleu deixar-ho ara?", +"Restore last draft": "Restaurar l'\u00faltim esborrany", +"Special character": "Car\u00e0cter especial", +"Source code": "Codi font", +"Insert\/Edit code sample": "Inserir\/Editar tros de codi", +"Language": "Idioma", +"Code sample": "Mostra de codi", +"Color": "Color", +"R": "R", +"G": "G", +"B": "B", +"Left to right": "D'esquerra a dreta", +"Right to left": "De dreta a esquerra", +"Emoticons": "Emoticones", +"Document properties": "Propietats del document", +"Title": "T\u00edtol", +"Keywords": "Paraules clau", +"Description": "Descripci\u00f3", +"Robots": "Robots", +"Author": "Autor", +"Encoding": "Codificaci\u00f3", +"Fullscreen": "Pantalla completa", +"Action": "Acci\u00f3", +"Shortcut": "Drecera", +"Help": "Ajuda", +"Address": "Adre\u00e7a", +"Focus to menubar": "Enfocar la barra de men\u00fa", +"Focus to toolbar": "Enfocar la barra d'eines", +"Focus to element path": "Enfocar la ruta d'elements", +"Focus to contextual toolbar": "Enfocar la barra d'eines contextual", +"Insert link (if link plugin activated)": "Inserir enlla\u00e7 (si el complement d'enlla\u00e7 est\u00e0 activat)", +"Save (if save plugin activated)": "Desar (si el complement desar est\u00e0 activat)", +"Find (if searchreplace plugin activated)": "Cercar (si el complement cercar-reempla\u00e7ar est\u00e0 activat)", +"Plugins installed ({0}):": "Complements instal\u00b7lats ({0}):", +"Premium plugins:": "Complements premium", +"Learn more...": "Apr\u00e8n m\u00e9s...", +"You are using {0}": "Est\u00e0s utilitzant {0}", +"Plugins": "Complements", +"Handy Shortcuts": "Dreceres a m\u00e0", +"Horizontal line": "L\u00ednia horitzontal", +"Insert\/edit image": "Inserir\/editar imatge", +"Image description": "Descripci\u00f3 de la imatge", +"Source": "Font", +"Dimensions": "Dimensions", +"Constrain proportions": "Mantenir proporcions", +"General": "General", +"Advanced": "Avan\u00e7at", +"Style": "Estil", +"Vertical space": "Espai vertical", +"Horizontal space": "Espai horitzontal", +"Border": "Vora", +"Insert image": "Inserir imatge", +"Image": "Imatge", +"Image list": "Llista d'imatges", +"Rotate counterclockwise": "Girar a l'esquerra", +"Rotate clockwise": "Girar a la dreta", +"Flip vertically": "Capgirar verticalment", +"Flip horizontally": "Capgirar horitzontalment", +"Edit image": "Editar imatge", +"Image options": "Opcions d'imatge", +"Zoom in": "Ampliar", +"Zoom out": "Empetitir", +"Crop": "Escap\u00e7ar", +"Resize": "Canviar mida", +"Orientation": "Orientaci\u00f3", +"Brightness": "Brillantor", +"Sharpen": "Remarcar vores", +"Contrast": "Contrast", +"Color levels": "Nivells de color", +"Gamma": "Gamma", +"Invert": "Invertir", +"Apply": "Aplicar", +"Back": "Tornar", +"Insert date\/time": "Inserir data\/hora", +"Date\/time": "Data\/hora", +"Insert link": "Inserir enlla\u00e7", +"Insert\/edit link": "Inserir\/editar enlla\u00e7", +"Text to display": "Text per mostrar", +"Url": "URL", +"Target": "Dest\u00ed", +"None": "Cap", +"New window": "Finestra nova", +"Remove link": "Treure enlla\u00e7", +"Anchors": "\u00c0ncores", +"Link": "Enlla\u00e7", +"Paste or type a link": "Enganxa o escriu un enlla\u00e7", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "L'URL que has escrit sembla una adre\u00e7a de correu electr\u00f2nic. Vols afegir-li el prefix obligatori mailto: ?", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "L'URL que has escrit sembla un enlla\u00e7 extern. Vols afegir-li el prefix obligatori http:\/\/ ?", +"Link list": "Llista d'enlla\u00e7os", +"Insert video": "Inserir v\u00eddeo", +"Insert\/edit video": "Inserir\/editar v\u00eddeo", +"Insert\/edit media": "Inserir\/editar mitj\u00e0", +"Alternative source": "Font alternativa", +"Poster": "P\u00f3ster", +"Paste your embed code below:": "Enganxau el codi a sota:", +"Embed": "Incloure", +"Media": "Mitjans", +"Nonbreaking space": "Espai fixe", +"Page break": "Salt de p\u00e0gina", +"Paste as text": "Enganxar com a text", +"Preview": "Previsualitzaci\u00f3", +"Print": "Imprimir", +"Save": "Desa", +"Find": "Buscar", +"Replace with": "Rempla\u00e7ar amb", +"Replace": "Rempla\u00e7ar", +"Replace all": "Rempla\u00e7ar-ho tot", +"Prev": "Anterior", +"Next": "Seg\u00fcent", +"Find and replace": "Buscar i rempla\u00e7ar", +"Could not find the specified string.": "No es pot trobar el text especificat.", +"Match case": "Coincidir maj\u00fascules", +"Whole words": "Paraules senceres", +"Spellcheck": "Comprovar ortrografia", +"Ignore": "Ignorar", +"Ignore all": "Ignorar tots", +"Finish": "Finalitzar", +"Add to Dictionary": "Afegir al diccionari", +"Insert table": "Inserir taula", +"Table properties": "Propietats de taula", +"Delete table": "Esborrar taula", +"Cell": "Cel\u00b7la", +"Row": "Fila", +"Column": "Columna", +"Cell properties": "Propietats de cel\u00b7la", +"Merge cells": "Fusionar cel\u00b7les", +"Split cell": "Dividir cel\u00b7les", +"Insert row before": "Inserir fila a sobre", +"Insert row after": "Inserir fila a sota", +"Delete row": "Esborrar fila", +"Row properties": "Propietats de fila", +"Cut row": "Retallar fila", +"Copy row": "Copiar fila", +"Paste row before": "Enganxar fila a sobre", +"Paste row after": "Enganxar fila a sota", +"Insert column before": "Inserir columna abans", +"Insert column after": "Inserir columna despr\u00e9s", +"Delete column": "Esborrar columna", +"Cols": "Cols", +"Rows": "Files", +"Width": "Amplada", +"Height": "Al\u00e7ada", +"Cell spacing": "Espai entre cel\u00b7les", +"Cell padding": "Marge intern", +"Caption": "Encap\u00e7alament", +"Left": "A l'esquerra", +"Center": "Centrat", +"Right": "A la dreta", +"Cell type": "Tipus de cel\u00b7la", +"Scope": "\u00c0mbit", +"Alignment": "Aliniament", +"H Align": "Al\u00edniament H", +"V Align": "Al\u00edniament V", +"Top": "Superior", +"Middle": "Mitj\u00e0", +"Bottom": "Inferior", +"Header cell": "Cel\u00b7la de cap\u00e7alera", +"Row group": "Grup de fila", +"Column group": "Grup de columna", +"Row type": "Tipus de fila", +"Header": "Cap\u00e7alera", +"Body": "Cos", +"Footer": "Peu", +"Border color": "Color de vora", +"Insert template": "Inserir plantilla", +"Templates": "Plantilles", +"Template": "Plantilla", +"Text color": "Color del text", +"Background color": "Color del fons", +"Custom...": "Personalitzar...", +"Custom color": "Personalitzar el color", +"No color": "Sense color", +"Table of Contents": "Taula de continguts", +"Show blocks": "Mostrar blocs", +"Show invisible characters": "Mostrar car\u00e0cters invisibles", +"Words: {0}": "Paraules: {0}", +"{0} words": "{0} paraules", +"File": "Arxiu", +"Edit": "Edici\u00f3", +"Insert": "Inserir", +"View": "Veure", +"Format": "Format", +"Table": "Taula", +"Tools": "Eines", +"Powered by {0}": "Impulsat per {0}", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "\u00c0rea de text amb format. Premeu ALT-F9 per mostrar el men\u00fa, ALT F10 per la barra d'eines i ALT-0 per ajuda." +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/cs.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/cs.js new file mode 100644 index 0000000000..264b32fcd0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/cs.js @@ -0,0 +1,261 @@ +tinymce.addI18n('cs',{ +"Redo": "Znovu", +"Undo": "Zp\u011bt", +"Cut": "Vyjmout", +"Copy": "Kop\u00edrovat", +"Paste": "Vlo\u017eit", +"Select all": "Vybrat v\u0161e", +"New document": "Nov\u00fd dokument", +"Ok": "OK", +"Cancel": "Zru\u0161it", +"Visual aids": "Vizu\u00e1ln\u00ed pom\u016fcky", +"Bold": "Tu\u010dn\u00e9", +"Italic": "Kurz\u00edva", +"Underline": "Podtr\u017een\u00e9", +"Strikethrough": "P\u0159e\u0161rktnut\u00e9", +"Superscript": "Horn\u00ed index", +"Subscript": "Doln\u00ed index", +"Clear formatting": "Vymazat form\u00e1tov\u00e1n\u00ed", +"Align left": "Zarovnat vlevo", +"Align center": "Zarovnat na st\u0159ed", +"Align right": "Zarovnat vpravo", +"Justify": "Zarovnat do bloku", +"Bullet list": "Odr\u00e1\u017eky", +"Numbered list": "\u010c\u00edslov\u00e1n\u00ed", +"Decrease indent": "Zmen\u0161it odsazen\u00ed", +"Increase indent": "Zv\u011bt\u0161it odsazen\u00ed", +"Close": "Zav\u0159\u00edt", +"Formats": "Form\u00e1ty", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "V\u00e1\u0161 prohl\u00ed\u017ee\u010d nepodporuje p\u0159\u00edm\u00fd p\u0159\u00edstup do schr\u00e1nky. Pou\u017eijte pros\u00edm kl\u00e1vesov\u00e9 zkratky Ctrl+X\/C\/V.", +"Headers": "Nadpisy", +"Header 1": "Nadpis 1", +"Header 2": "Nadpis 2", +"Header 3": "Nadpis 3", +"Header 4": "Nadpis 4", +"Header 5": "Nadpis 5", +"Header 6": "Nadpis 6", +"Headings": "Nadpisy", +"Heading 1": "Nadpis 1", +"Heading 2": "Nadpis 2", +"Heading 3": "Nadpis 3", +"Heading 4": "Nadpis 4", +"Heading 5": "Nadpis 5", +"Heading 6": "Nadpis 6", +"Preformatted": "P\u0159edform\u00e1tov\u00e1no", +"Div": "Div (blok)", +"Pre": "Pre (p\u0159edform\u00e1tov\u00e1no)", +"Code": "Code (k\u00f3d)", +"Paragraph": "Odstavec", +"Blockquote": "Citace", +"Inline": "\u0158\u00e1dkov\u00e9 zobrazen\u00ed (inline)", +"Blocks": "Blokov\u00e9 zobrazen\u00ed (block)", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "Je zapnuto vkl\u00e1d\u00e1n\u00ed \u010dist\u00e9ho textu. Dokud nebude tato volba vypnuta, bude ve\u0161ker\u00fd obsah vlo\u017een jako \u010dist\u00fd text.", +"Font Family": "Typ p\u00edsma", +"Font Sizes": "Velikost p\u00edsma", +"Class": "T\u0159\u00edda", +"Browse for an image": "Vyhledat obr\u00e1zek", +"OR": "nebo", +"Drop an image here": "Nahr\u00e1t obr\u00e1zek", +"Upload": "Nahr\u00e1t", +"Block": "Blok", +"Align": "Zarovnat", +"Default": "V\u00fdchoz\u00ed", +"Circle": "Kole\u010dko", +"Disc": "Punt\u00edk", +"Square": "\u010ctvere\u010dek", +"Lower Alpha": "Norm\u00e1ln\u00ed \u010d\u00edslov\u00e1n\u00ed", +"Lower Greek": "Mal\u00e9 p\u00edsmenkov\u00e1n\u00ed", +"Lower Roman": "Mal\u00e9 \u0159\u00edmsk\u00e9 \u010d\u00edslice", +"Upper Alpha": "velk\u00e9 p\u00edsmenkov\u00e1n\u00ed", +"Upper Roman": "\u0158\u00edmsk\u00e9 \u010d\u00edslice", +"Anchor": "Kotva", +"Name": "N\u00e1zev", +"Id": "Id", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "Id by m\u011blo za\u010d\u00ednat p\u00edsmenem a d\u00e1le obsahovat pouze p\u00edsmena, \u010d\u00edsla, poml\u010dky, te\u010dky, dvojte\u010dky, nebo podtr\u017e\u00edtka.", +"You have unsaved changes are you sure you want to navigate away?": "M\u00e1te neulo\u017een\u00e9 zm\u011bny. Opravdu chcete opustit str\u00e1nku?", +"Restore last draft": "Obnovit posledn\u00ed koncept", +"Special character": "Speci\u00e1ln\u00ed znak", +"Source code": "Zdrojov\u00fd k\u00f3d", +"Insert\/Edit code sample": "Vlo\u017eit \/ Upravit uk\u00e1zkov\u00fd k\u00f3d", +"Language": "Jazyk", +"Code sample": "Uk\u00e1zkov\u00fd k\u00f3d", +"Color": "Barva", +"R": "R", +"G": "G", +"B": "B", +"Left to right": "Zleva doprava", +"Right to left": "Zprava doleva", +"Emoticons": "Emotikony", +"Document properties": "Vlastnosti dokumentu", +"Title": "Titulek", +"Keywords": "Kl\u00ed\u010dov\u00e1 slova", +"Description": "Popis", +"Robots": "Roboti", +"Author": "Autor", +"Encoding": "K\u00f3dov\u00e1n\u00ed", +"Fullscreen": "Na celou obrazovku", +"Action": "Akce", +"Shortcut": "Kl\u00e1vesov\u00e1 zkratka", +"Help": "N\u00e1pov\u011bda", +"Address": "Blok s po\u0161tovn\u00ed adresou", +"Focus to menubar": "P\u0159ej\u00edt do menu", +"Focus to toolbar": "P\u0159ej\u00edt na panel n\u00e1stroj\u016f", +"Focus to element path": "P\u0159ej\u00edt na element path", +"Focus to contextual toolbar": "P\u0159ej\u00edt na kontextov\u00fd panel n\u00e1stroj\u016f", +"Insert link (if link plugin activated)": "Vlo\u017eit odkaz (pokud je aktivn\u00ed link plugin)", +"Save (if save plugin activated)": "Ulo\u017eit (pokud je aktivni save plugin)", +"Find (if searchreplace plugin activated)": "Hledat (pokud je aktivn\u00ed plugin searchreplace)", +"Plugins installed ({0}):": "Instalovan\u00e9 pluginy ({0}):", +"Premium plugins:": "Pr\u00e9miov\u00e9 pluginy:", +"Learn more...": "Zjistit v\u00edce...", +"You are using {0}": "Pou\u017e\u00edv\u00e1te {0}", +"Plugins": "Pluginy", +"Handy Shortcuts": "U\u017eite\u010dn\u00e9 kl\u00e1vesov\u00e9 zkratky", +"Horizontal line": "Vodorovn\u00e1 \u010d\u00e1ra", +"Insert\/edit image": "Vlo\u017eit \/ upravit obr\u00e1zek", +"Image description": "Popis obr\u00e1zku", +"Source": "URL", +"Dimensions": "Rozm\u011bry", +"Constrain proportions": "Zachovat proporce", +"General": "Obecn\u00e9", +"Advanced": "Pokro\u010dil\u00e9", +"Style": "Styl", +"Vertical space": "Vertik\u00e1ln\u00ed mezera", +"Horizontal space": "Horizont\u00e1ln\u00ed mezera", +"Border": "R\u00e1me\u010dek", +"Insert image": "Vlo\u017eit obr\u00e1zek", +"Image": "Obr\u00e1zek", +"Image list": "Seznam obr\u00e1zk\u016f", +"Rotate counterclockwise": "Oto\u010dit doleva", +"Rotate clockwise": "Oto\u010dit doprava", +"Flip vertically": "P\u0159evr\u00e1tit svisle", +"Flip horizontally": "P\u0159evr\u00e1tit vodorovn\u011b", +"Edit image": "Upravit obr\u00e1zek", +"Image options": "Vlastnosti obr\u00e1zku", +"Zoom in": "P\u0159ibl\u00ed\u017eit", +"Zoom out": "Odd\u00e1lit", +"Crop": "O\u0159\u00edznout", +"Resize": "Zm\u011bnit velikost", +"Orientation": "Transformovat", +"Brightness": "Jas", +"Sharpen": "Ostrost", +"Contrast": "Kontrast", +"Color levels": "\u00darovn\u011b barev", +"Gamma": "Gama", +"Invert": "Invertovat", +"Apply": "Pou\u017e\u00edt", +"Back": "Zp\u011bt", +"Insert date\/time": "Vlo\u017eit datum \/ \u010das", +"Date\/time": "Datum\/\u010das", +"Insert link": "Vlo\u017eit odkaz", +"Insert\/edit link": "Vlo\u017eit \/ upravit odkaz", +"Text to display": "Text k zobrazen\u00ed", +"Url": "URL", +"Target": "C\u00edl", +"None": "\u017d\u00e1dn\u00e9", +"New window": "Nov\u00e9 okno", +"Remove link": "Odstranit odkaz", +"Anchors": "Kotvy", +"Link": "Odkaz", +"Paste or type a link": "Vlo\u017eit nebo napsat odkaz", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "Zadan\u00e9 URL vypad\u00e1 jako e-mailov\u00e1 adresa. Chcete doplnit povinn\u00fd prefix mailto:?", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "Zadan\u00e9 URL vypad\u00e1 jako odkaz na jin\u00fd web. Chcete doplnit povinn\u00fd prefix http:\/\/?", +"Link list": "Seznam odkaz\u016f", +"Insert video": "Vlo\u017eit video", +"Insert\/edit video": "Vlo\u017eit \/ upravit video", +"Insert\/edit media": "Vlo\u017eit \/ upravit m\u00e9dia", +"Alternative source": "Alternativn\u00ed zdroj", +"Poster": "N\u00e1hled", +"Paste your embed code below:": "Vlo\u017ete k\u00f3d pro vlo\u017een\u00ed n\u00ed\u017ee:", +"Embed": "Vlo\u017eit", +"Media": "M\u00e9dia", +"Nonbreaking space": "Pevn\u00e1 mezera", +"Page break": "Konec str\u00e1nky", +"Paste as text": "Vlo\u017eit jako \u010dist\u00fd text", +"Preview": "N\u00e1hled", +"Print": "Tisk", +"Save": "Ulo\u017eit", +"Find": "Naj\u00edt", +"Replace with": "Nahradit za", +"Replace": "Nahradit", +"Replace all": "Nahradit v\u0161e", +"Prev": "P\u0159edchoz\u00ed", +"Next": "Dal\u0161\u00ed", +"Find and replace": "Naj\u00edt a nahradit", +"Could not find the specified string.": "Zadan\u00fd \u0159et\u011bzec nebyl nalezen.", +"Match case": "Rozli\u0161ovat mal\u00e1 a velk\u00e1 p\u00edsmena", +"Whole words": "Pouze cel\u00e1 slova", +"Spellcheck": "Kontrola pravopisu", +"Ignore": "Ignorovat", +"Ignore all": "Ignorovat v\u0161e", +"Finish": "Ukon\u010dit", +"Add to Dictionary": "P\u0159idat do slovn\u00edku", +"Insert table": "Vlo\u017eit tabulku", +"Table properties": "Vlastnosti tabulky", +"Delete table": "Smazat tabulku", +"Cell": "Bu\u0148ka", +"Row": "\u0158\u00e1dek", +"Column": "Sloupec", +"Cell properties": "Vlastnosti bu\u0148ky", +"Merge cells": "Slou\u010dit bu\u0148ky", +"Split cell": "Rozd\u011blit bu\u0148ky", +"Insert row before": "Vlo\u017eit \u0159\u00e1dek nad", +"Insert row after": "Vlo\u017eit \u0159\u00e1dek pod", +"Delete row": "Smazat \u0159\u00e1dek", +"Row properties": "Vlastnosti \u0159\u00e1dku", +"Cut row": "Vyjmout \u0159\u00e1dek", +"Copy row": "Kop\u00edrovat \u0159\u00e1dek", +"Paste row before": "Vlo\u017eit \u0159\u00e1dek nad", +"Paste row after": "Vlo\u017eit \u0159\u00e1dek pod", +"Insert column before": "Vlo\u017eit sloupec vlevo", +"Insert column after": "Vlo\u017eit sloupec vpravo", +"Delete column": "Smazat sloupec", +"Cols": "Sloupc\u016f", +"Rows": "\u0158\u00e1dek", +"Width": "\u0160\u00ed\u0159ka", +"Height": "V\u00fd\u0161ka", +"Cell spacing": "Vn\u011bj\u0161\u00ed okraj bun\u011bk", +"Cell padding": "Vnit\u0159n\u00ed okraj bun\u011bk", +"Caption": "Nadpis", +"Left": "Vlevo", +"Center": "Na st\u0159ed", +"Right": "Vpravo", +"Cell type": "Typ bu\u0148ky", +"Scope": "Rozsah", +"Alignment": "Zarovn\u00e1n\u00ed", +"H Align": "Horizont\u00e1ln\u00ed zarovn\u00e1n\u00ed", +"V Align": "Vertik\u00e1ln\u00ed zarovn\u00e1n\u00ed", +"Top": "Nahoru", +"Middle": "Uprost\u0159ed", +"Bottom": "Dol\u016f", +"Header cell": "Hlavi\u010dkov\u00e1 bu\u0148ka", +"Row group": "Skupina \u0159\u00e1dk\u016f", +"Column group": "Skupina sloupc\u016f", +"Row type": "Typ \u0159\u00e1dku", +"Header": "Hlavi\u010dka", +"Body": "T\u011blo", +"Footer": "Pati\u010dka", +"Border color": "Barva r\u00e1me\u010dku", +"Insert template": "Vlo\u017eit \u0161ablonu", +"Templates": "\u0160ablony", +"Template": "\u0160ablona", +"Text color": "Barva p\u00edsma", +"Background color": "Barva pozad\u00ed", +"Custom...": "Vlastn\u00ed...", +"Custom color": "Vlastn\u00ed barva", +"No color": "Bez barvy", +"Table of Contents": "Obsah", +"Show blocks": "Uk\u00e1zat bloky", +"Show invisible characters": "Zobrazit speci\u00e1ln\u00ed znaky", +"Words: {0}": "Po\u010det slov: {0}", +"{0} words": "Po\u010det slov: {0}", +"File": "Soubor", +"Edit": "\u00dapravy", +"Insert": "Vlo\u017eit", +"View": "Zobrazit", +"Format": "Form\u00e1t", +"Table": "Tabulka", +"Tools": "N\u00e1stroje", +"Powered by {0}": "Vytvo\u0159il {0}", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Editor. Stiskn\u011bte ALT-F9 pro menu, ALT-F10 pro n\u00e1strojovou li\u0161tu a ALT-0 pro n\u00e1pov\u011bdu." +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/cs_CZ.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/cs_CZ.js new file mode 100644 index 0000000000..b5b47394c0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/cs_CZ.js @@ -0,0 +1,260 @@ +tinymce.addI18n('cs_CZ',{ +"Redo": "Znovu", +"Undo": "Zp\u011bt", +"Cut": "Vyjmout", +"Copy": "Kop\u00edrovat", +"Paste": "Vlo\u017eit", +"Select all": "Vybrat v\u0161e", +"New document": "Nov\u00fd dokument", +"Ok": "Ok", +"Cancel": "Zru\u0161it", +"Visual aids": "Vizu\u00e1ln\u00ed pom\u016fcky", +"Bold": "Tu\u010dn\u011b", +"Italic": "Kurz\u00edva", +"Underline": "Podtr\u017een\u00e9", +"Strikethrough": "P\u0159e\u0161krtnut\u00e9", +"Superscript": "Horn\u00ed index", +"Subscript": "Doln\u00ed index", +"Clear formatting": "Vymazat form\u00e1tov\u00e1n\u00ed", +"Align left": "Vlevo", +"Align center": "Na st\u0159ed", +"Align right": "Vpravo", +"Justify": "Zarovnat do bloku", +"Bullet list": "Odr\u00e1\u017eky", +"Numbered list": "\u010c\u00edslov\u00e1n\u00ed", +"Decrease indent": "Zmen\u0161it odsazen\u00ed", +"Increase indent": "Zv\u011b\u0161it odsazen\u00ed", +"Close": "Zav\u0159\u00edt", +"Formats": "Form\u00e1ty", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "V\u00e1\u0161 prohl\u00ed\u017ee\u010d nepodporuje p\u0159\u00edm\u00fd p\u0159\u00edstup do schr\u00e1nky. Pou\u017eijte pros\u00edm kl\u00e1vesov\u00e9 zkratky Ctrl+X\/C\/V.", +"Headers": "Nadpisy", +"Header 1": "Nadpis 1", +"Header 2": "Nadpis 2", +"Header 3": "Nadpis 3", +"Header 4": "Nadpis 4", +"Header 5": "Nadpis 5", +"Header 6": "Nadpis 6", +"Headings": "Nadpisy", +"Heading 1": "Nadpis 1", +"Heading 2": "Nadpis 2", +"Heading 3": "Nadpis 3", +"Heading 4": "Nadpis 4", +"Heading 5": "Nadpis 5", +"Heading 6": "Nadpis 6", +"Div": "Div (blok)", +"Pre": "Pre (p\u0159edform\u00e1tov\u00e1no)", +"Code": "Code (k\u00f3d)", +"Paragraph": "Odstavec", +"Blockquote": "Citace", +"Inline": "\u0158\u00e1dkov\u00e9 zobrazen\u00ed (inline)", +"Blocks": "Blokov\u00e9 zobrazen\u00ed (block)", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "Je zapnuto vkl\u00e1d\u00e1n\u00ed \u010dist\u00e9ho textu. Dokud nebude tato volba vypnuta, bude ve\u0161ker\u00fd obsah vlo\u017een jako \u010dist\u00fd text.", +"Font Family": "Rodina p\u00edsma", +"Font Sizes": "Velikost p\u00edsma", +"Class": "T\u0159\u00edda", +"Browse for an image": "Vybrat obr\u00e1zek", +"OR": "NEBO", +"Drop an image here": "P\u0159et\u00e1hn\u011bte obr\u00e1zek sem", +"Upload": "Nahr\u00e1t", +"Block": "Blok", +"Align": "Zarovnat", +"Default": "V\u00fdchoz\u00ed", +"Circle": "Kole\u010dko", +"Disc": "Punt\u00edk", +"Square": "\u010ctvere\u010dek", +"Lower Alpha": "Mal\u00e1 p\u00edsmena", +"Lower Greek": "\u0158eck\u00e1 p\u00edsmena", +"Lower Roman": "Mal\u00e9 \u0159\u00edmsl\u00e9 \u010d\u00edslice", +"Upper Alpha": "Velk\u00e1 p\u00edsmena", +"Upper Roman": "\u0158\u00edmsk\u00e9 \u010d\u00edslice", +"Anchor": "Kotva", +"Name": "N\u00e1zev", +"Id": "ID", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "ID by m\u011blo za\u010d\u00ednat p\u00edsmenem, n\u00e1sledovan\u00fdm pouze p\u00edsmeny, \u010d\u00edsly, poml\u010dkami, te\u010dkami, \u010d\u00e1rkami a nebo podtr\u017e\u00edtky.", +"You have unsaved changes are you sure you want to navigate away?": "M\u00e1te neulo\u017een\u00e9 zm\u011bny. Opravdu chcete opustit str\u00e1nku?", +"Restore last draft": "Obnovit posledn\u00ed koncept.", +"Special character": "Speci\u00e1ln\u00ed znak", +"Source code": "Zdrojov\u00fd k\u00f3d", +"Insert\/Edit code sample": "Vlo\u017eit\/Upravit uk\u00e1zku k\u00f3du", +"Language": "Jazyk", +"Code sample": "Uk\u00e1zka k\u00f3du", +"Color": "Barva", +"R": "R", +"G": "G", +"B": "B", +"Left to right": "Zleva doprava", +"Right to left": "Zprava doleva", +"Emoticons": "Emotikony", +"Document properties": "Vlastnosti dokumentu", +"Title": "Titulek", +"Keywords": "Kl\u00ed\u010dov\u00e1 slova", +"Description": "Popis", +"Robots": "Roboti", +"Author": "Autor", +"Encoding": "K\u00f3dov\u00e1n\u00ed", +"Fullscreen": "Celk\u00e1 obrazovka", +"Action": "Akce", +"Shortcut": "Kl\u00e1vesov\u00e1 zkratka", +"Help": "N\u00e1pov\u011bda", +"Address": "Blok s po\u0161tovn\u00ed adresou", +"Focus to menubar": "P\u0159ej\u00edt do menu", +"Focus to toolbar": "P\u0159ej\u00edt na panel n\u00e1stroj\u016f", +"Focus to element path": "Focus to element path", +"Focus to contextual toolbar": "P\u0159ej\u00edt na kontextov\u00fd panel n\u00e1stroj\u016f", +"Insert link (if link plugin activated)": "Vlo\u017eit odkaz (pokud je aktivn\u00ed link plugin)", +"Save (if save plugin activated)": "Ulo\u017eit (pokud je aktivni save plugin)", +"Find (if searchreplace plugin activated)": "Hledat (pokud je aktivn\u00ed plugin searchreplace)", +"Plugins installed ({0}):": "Instalovan\u00e9 pluginy ({0}):", +"Premium plugins:": "Pr\u00e9miov\u00e9 pluginy:", +"Learn more...": "Zjistit v\u00edce...", +"You are using {0}": "Pou\u017e\u00edv\u00e1te {0}", +"Plugins": "Pluginy", +"Handy Shortcuts": "U\u017eite\u010dn\u00e9 kl\u00e1vesov\u00e9 zkratky", +"Horizontal line": "Vodorovn\u00e1 linka", +"Insert\/edit image": "Vlo\u017eit \/ upravit obr\u00e1zek", +"Image description": "Popis obr\u00e1zku", +"Source": "URL", +"Dimensions": "Rozm\u011bry", +"Constrain proportions": "Zachovat proporce", +"General": "Obecn\u00e9", +"Advanced": "Pokro\u010dil\u00e9", +"Style": "Styl", +"Vertical space": "Vertik\u00e1ln\u00ed mezera", +"Horizontal space": "Horizont\u00e1ln\u00ed mezera", +"Border": "R\u00e1me\u010dek", +"Insert image": "Vlo\u017eit obr\u00e1zek", +"Image": "Obr\u00e1zek", +"Image list": "Seznam obr\u00e1zk\u016f", +"Rotate counterclockwise": "Oto\u010dit doleva", +"Rotate clockwise": "Oto\u010dit doprava", +"Flip vertically": "P\u0159evr\u00e1tit svisle", +"Flip horizontally": "P\u0159evr\u00e1tit vodorovn\u011b", +"Edit image": "Upravit obr\u00e1zek", +"Image options": "Vlastnosti obr\u00e1zku", +"Zoom in": "P\u0159ibl\u00ed\u017eit", +"Zoom out": "Odd\u00e1lit", +"Crop": "O\u0159\u00edznout", +"Resize": "Zm\u011bnit velikost", +"Orientation": "Orientace", +"Brightness": "Jas", +"Sharpen": "Ostrost", +"Contrast": "Kontrast", +"Color levels": "\u00darovn\u011b barev", +"Gamma": "Gama", +"Invert": "Invertovat", +"Apply": "Pou\u017e\u00edt", +"Back": "Zp\u011bt", +"Insert date\/time": "Vlo\u017eit datum \/ \u010das", +"Date\/time": "Datum\/\u010das", +"Insert link": "Vlo\u017eit odkaz", +"Insert\/edit link": "Vlo\u017eit \/ upravit odkaz", +"Text to display": "Text odkazu", +"Url": "URL", +"Target": "C\u00edl", +"None": "\u017d\u00e1dn\u00fd", +"New window": "Nov\u00e9 okno", +"Remove link": "Odstranit odkaz", +"Anchors": "Kotvy", +"Link": "Odkaz", +"Paste or type a link": "Vlo\u017ete nebo napi\u0161te adresu odkazu", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "Zadan\u00e9 URL vypad\u00e1 jako e-mailov\u00e1 adresa. Chcete doplnit povinn\u00fd prefix mailto:?", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "Zadan\u00e9 URL vypad\u00e1 jako odkaz na jin\u00fd web. Chcete doplnit povinn\u00fd prefix http:\/\/?", +"Link list": "Seznam odkaz\u016f", +"Insert video": "Vlo\u017eit video", +"Insert\/edit video": "Vlo\u017eit \/ upravit video", +"Insert\/edit media": "Vlo\u017eit\/upravit m\u00e9dia", +"Alternative source": "Alternativn\u00ed zdroj", +"Poster": "Poster", +"Paste your embed code below:": "Vlo\u017ete k\u00f3d pro vlo\u017een\u00ed", +"Embed": "Vlo\u017een\u00fd", +"Media": "M\u00e9dia", +"Nonbreaking space": "Pevn\u00e1 mezera", +"Page break": "Konec str\u00e1nky", +"Paste as text": "Vlo\u017eit jako \u010dist\u00fd text", +"Preview": "N\u00e1hled", +"Print": "Tisk", +"Save": "Ulo\u017eit", +"Find": "Naj\u00edt", +"Replace with": "Nahradit za", +"Replace": "Nahradit", +"Replace all": "Nahradit v\u0161e", +"Prev": "P\u0159edchoz\u00ed", +"Next": "Dal\u0161\u00ed", +"Find and replace": "Naj\u00edt a nahradit", +"Could not find the specified string.": "Zadan\u00fd \u0159et\u011bzec nebyl nalezen.", +"Match case": "Rozli\u0161ovat mal\u00e1 a velk\u00e1 p\u00edsmena", +"Whole words": "Pouze cel\u00e1 slova", +"Spellcheck": "Kontrola pravopisu", +"Ignore": "Ignorovat", +"Ignore all": "Ignorovat v\u0161e", +"Finish": "Dokon\u010dit", +"Add to Dictionary": "P\u0159idat do slovn\u00edku", +"Insert table": "Vlo\u017eit tabulku", +"Table properties": "Vlastnosti tabulky", +"Delete table": "Smazat tabulku", +"Cell": "Bu\u0148ka", +"Row": "\u0158\u00e1dek", +"Column": "Sloupec", +"Cell properties": "Vlastnosti bu\u0148ky", +"Merge cells": "Slou\u010dit bu\u0148ky", +"Split cell": "Rozd\u011blit bu\u0148ku", +"Insert row before": "Vlo\u017eit \u0159\u00e1dek p\u0159ed", +"Insert row after": "Vlo\u017eit \u0159\u00e1dek za", +"Delete row": "Smazat \u0159\u00e1dek", +"Row properties": "Vlastnosti \u0159\u00e1dku", +"Cut row": "Vyjmout \u0159\u00e1dek", +"Copy row": "Kop\u00edrovat \u0159\u00e1dek", +"Paste row before": "Vlo\u017eit \u0159\u00e1dek nad", +"Paste row after": "Vlo\u017eit \u0159\u00e1dek pod", +"Insert column before": "Vlo\u017eit sloupec vlevo", +"Insert column after": "Vlo\u017eit sloupec vpravo", +"Delete column": "Smazat sloupec", +"Cols": "Sloupce", +"Rows": "\u0158\u00e1dky", +"Width": "\u0160\u00ed\u0159ka", +"Height": "V\u00fd\u0161ka", +"Cell spacing": "Vn\u011bj\u0161\u00ed okraj bun\u011bk", +"Cell padding": "Vnit\u0159n\u00ed okraj bun\u011bk", +"Caption": "Titulek", +"Left": "Vlevo", +"Center": "Na st\u0159ed", +"Right": "Vpravo", +"Cell type": "Typ bu\u0148ky", +"Scope": "Rozsah", +"Alignment": "Zarovn\u00e1n\u00ed", +"H Align": "Horizont\u00e1ln\u00ed zarovn\u00e1n\u00ed", +"V Align": "Vertik\u00e1ln\u00ed zarovn\u00e1n\u00ed", +"Top": "Nahoru", +"Middle": "Na st\u0159ed", +"Bottom": "Dol\u016f", +"Header cell": "Hlavi\u010dkov\u00e1 bu\u0148ka", +"Row group": "Skupina \u0159\u00e1dk\u016f", +"Column group": "Skupina sloupc\u016f", +"Row type": "Typ \u0159\u00e1dku", +"Header": "Hlavi\u010dka", +"Body": "T\u011blo", +"Footer": "Pati\u010dka", +"Border color": "Barva r\u00e1me\u010dku", +"Insert template": "Vlo\u017eit ze \u0161ablony", +"Templates": "\u0160ablony", +"Template": "\u0160ablona", +"Text color": "Barva p\u00edsma", +"Background color": "Barva pozad\u00ed", +"Custom...": "Vlastn\u00ed", +"Custom color": "Vlastn\u00ed barva", +"No color": "Bez barvy", +"Table of Contents": "Generovat obsah", +"Show blocks": "Uk\u00e1zat bloky", +"Show invisible characters": "Uk\u00e1zat skryt\u00e9 znaky", +"Words: {0}": "Slova: {0}", +"{0} words": "{0} slov", +"File": "Soubor", +"Edit": "\u00dapravy", +"Insert": "Vlo\u017eit", +"View": "Zobrazit", +"Format": "Form\u00e1t", +"Table": "Tabulka", +"Tools": "N\u00e1stroje", +"Powered by {0}": "Powered by {0}", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "RTF dokument. Stikn\u011bte ALT-F9 pro zobrazen\u00ed menu, ALT-F10 pro zobrazen\u00ed n\u00e1strojov\u00e9 li\u0161ty, ALT-0 pro n\u00e1pov\u011bdu." +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/cy.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/cy.js new file mode 100644 index 0000000000..d2fdd077f5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/cy.js @@ -0,0 +1,230 @@ +tinymce.addI18n('cy',{ +"Cut": "Torri", +"Heading 5": "Pennawd 5", +"Header 2": "Pennawd 2", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "Dyw eich porwr ddim yn cynnal mynediad uniongyrchol i'r clipfwrdd. Yn hytrach defnyddiwch y bysellau llwybrau byr Ctrl+X\/C\/V.", +"Heading 4": "Pennawd 4", +"Div": "Div", +"Heading 2": "Pennawd 2", +"Paste": "Gludo", +"Close": "Cau", +"Font Family": "Teulu Ffont", +"Pre": "Pre", +"Align right": "Aliniad de", +"New document": "Dogfen newydd", +"Blockquote": "Dyfyniad bloc", +"Numbered list": "Rhestr rifol", +"Heading 1": "Pennawd 1", +"Headings": "Penawdau", +"Increase indent": "Cynyddu mewnoliad", +"Formats": "Fformatau", +"Headers": "Penawdau", +"Select all": "Dewis popeth", +"Header 3": "Pennawd 3", +"Blocks": "Blociau", +"Undo": "Dadwneud", +"Strikethrough": "Llinell drwodd", +"Bullet list": "Rhestr fwled", +"Header 1": "Pennawd 1", +"Superscript": "Uwchsgript", +"Clear formatting": "Clirio pob fformat", +"Font Sizes": "Meintiau Ffont", +"Subscript": "Is-sgript", +"Header 6": "Pennawd 6", +"Redo": "Ailwneud", +"Paragraph": "Paragraff", +"Ok": "Iawn", +"Bold": "Bras", +"Code": "Cod", +"Italic": "Italig", +"Align center": "Aliniad canol", +"Header 5": "Pennawd 5", +"Heading 6": "Pennawd 6", +"Heading 3": "Pennawd 3", +"Decrease indent": "Lleihau mewnoliad", +"Header 4": "Pennawd 4", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "Mae gludo nawr yn gweithio yn y modd testun plaen. Caiff testun plaen ei ludo nawr tan gaiff yr opsiwn ei doglo i'w ddiffodd.", +"Underline": "Tanlinellu", +"Cancel": "Canslo", +"Justify": "Unioni", +"Inline": "Mewnlin", +"Copy": "Cop\u00efo", +"Align left": "Aliniad chwith", +"Visual aids": "Cymorth gweledol", +"Lower Greek": "Groeg Is", +"Square": "Sgw\u00e2r", +"Default": "Diofyn", +"Lower Alpha": "Alffa Is", +"Circle": "Cylch", +"Disc": "Disg", +"Upper Alpha": "Alffa Uwch", +"Upper Roman": "Rhufeinig Uwch", +"Lower Roman": "Rhufeinig Is", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "Dylai Id gychwyn gyda llythyren ac yna dim ond llythrennau, rhifau, llinellau toriad,dotiau, colonau neu danlinellau.", +"Name": "Enw", +"Anchor": "Angor", +"Id": "Id", +"You have unsaved changes are you sure you want to navigate away?": "Mae newidiadau heb eu cadw - ydych chi wir am symud i ffwrdd?", +"Restore last draft": "Adfer y drafft olaf", +"Special character": "Nod arbennig", +"Source code": "Cod gwreiddiol", +"Language": "Iaith", +"Insert\/Edit code sample": "Mewnosod\/golygu sampl cod", +"B": "Gl", +"R": "C", +"G": "Gw", +"Color": "Lliw", +"Right to left": "De i'r chwith", +"Left to right": "Chwith i'r dde", +"Emoticons": "Gwenogluniau", +"Robots": "Robotiaid", +"Document properties": "Priodweddau'r ddogfen", +"Title": "Teitl", +"Keywords": "Allweddeiriau", +"Encoding": "Amgodiad", +"Description": "Disgrifiad", +"Author": "Awdur", +"Fullscreen": "Sgrin llawn", +"Horizontal line": "Llinell lorweddol", +"Horizontal space": "Gofod llorweddol", +"Insert\/edit image": "Mewnosod\/golygu delwedd", +"General": "Cyffredinol", +"Advanced": "Uwch", +"Source": "Ffynhonnell", +"Border": "Border", +"Constrain proportions": "Gorfodi cyfrannedd", +"Vertical space": "Gofod fertigol", +"Image description": "Disgrifiad y ddelwedd", +"Style": "Arddull", +"Dimensions": "Dimensiynau", +"Insert image": "Mewnosod delwedd", +"Image": "Delwedd", +"Zoom in": "Chwyddo mewn", +"Contrast": "Cyferbynnedd", +"Back": "Nol", +"Gamma": "Gamma", +"Flip horizontally": "Fflipio llorweddol", +"Resize": "Ailfeintio", +"Sharpen": "Hogi", +"Zoom out": "Chwyddo allan", +"Image options": "Dewisiadau delwedd", +"Apply": "Rhoi ar waith", +"Brightness": "Disgleirdeb", +"Rotate clockwise": "Troi clocwedd", +"Rotate counterclockwise": "Troi gwrthgloc", +"Edit image": "Golygu delwedd", +"Color levels": "Lefelau Lliw", +"Crop": "Tocio", +"Orientation": "Cyfeiriadaeth", +"Flip vertically": "Fflipio fertigol", +"Invert": "Gwrthdroi", +"Date\/time": "Dyddiad\/amser", +"Insert date\/time": "Mewnosod dyddiad\/amser", +"Remove link": "Tynnu dolen", +"Url": "Url", +"Text to display": "Testun i'w ddangos", +"Anchors": "Angorau", +"Insert link": "Mewnosod dolen", +"Link": "Dolen", +"New window": "Ffenest newydd", +"None": "Dim", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "Mae'n debyg mai dolen allanol yw'r URL hwn. Ydych chi am ychwanegu'r rhagddodiad http:\/\/ ?", +"Paste or type a link": "Pastio neu deipio dolen", +"Target": "Targed", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "Mae'n debyg mai cyfeiriad e-bost yw'r URL hwn. Ydych chi am ychwanegu'r rhagddoddiad mailto:?", +"Insert\/edit link": "Mewnosod\/golygu dolen", +"Insert\/edit video": "Mewnosod\/golygu fideo", +"Media": "Cyfrwng", +"Alternative source": "Ffynhonnell amgen", +"Paste your embed code below:": "Gludwch eich cod mewnosod isod:", +"Insert video": "Mewnosod fideo", +"Poster": "Poster", +"Insert\/edit media": "Mewnosod\/golygu cyfrwng", +"Embed": "Mewnosod", +"Nonbreaking space": "Bwlch heb dorri", +"Page break": "Toriad tudalen", +"Paste as text": "Gludo fel testun", +"Preview": "Rhagolwg", +"Print": "Argraffu", +"Save": "Cadw", +"Could not find the specified string.": "Methu ffeindio'r llinyn hwnnw.", +"Replace": "Amnewid", +"Next": "Nesaf", +"Whole words": "Geiriau cyfan", +"Find and replace": "Chwilio ac amnewid", +"Replace with": "Amnewid gyda", +"Find": "Chwilio", +"Replace all": "Amnewid y cwbl", +"Match case": "Cas yn cyfateb", +"Prev": "Blaenorol", +"Spellcheck": "Sillafydd", +"Finish": "Gorffen", +"Ignore all": "Amwybyddu pob", +"Ignore": "Anwybyddu", +"Add to Dictionary": "Adio i'r Geiriadur", +"Insert row before": "Mewnosod rhes cyn", +"Rows": "Rhesi", +"Height": "Uchder", +"Paste row after": "Gludo rhes ar \u00f4l", +"Alignment": "Aliniad", +"Border color": "Lliw Border", +"Column group": "Gr\u0175p colofn", +"Row": "Rhes", +"Insert column before": "Mewnosod colofn cyn", +"Split cell": "Hollti celloedd", +"Cell padding": "Padio celloedd", +"Cell spacing": "Bylchiad celloedd", +"Row type": "Math y rhes", +"Insert table": "Mewnosod tabl", +"Body": "Corff", +"Caption": "Pennawd", +"Footer": "Troedyn", +"Delete row": "Dileu rhes", +"Paste row before": "Gludo rhes cyn", +"Scope": "Cwmpas", +"Delete table": "Dileu'r tabl", +"H Align": "Aliniad Ll", +"Top": "Brig", +"Header cell": "Cell bennawd", +"Column": "Colofn", +"Row group": "Gr\u0175p rhes", +"Cell": "Cell", +"Middle": "Canol", +"Cell type": "Math y gell", +"Copy row": "Cop\u00efo rhes", +"Row properties": "Priodweddau rhes", +"Table properties": "Priodweddau tabl", +"Bottom": "Gwaelod", +"V Align": "Aliniad F", +"Header": "Pennyn", +"Right": "De", +"Insert column after": "Mewnosod colofn ar \u00f4l", +"Cols": "Colofnau", +"Insert row after": "Mewnosod rhes ar \u00f4l", +"Width": "Lled", +"Cell properties": "Priodweddau'r gell", +"Left": "Chwith", +"Cut row": "Torri rhes", +"Delete column": "Dileu colofn", +"Center": "Canol", +"Merge cells": "Cyfuno celloedd", +"Insert template": "Mewnosod templed", +"Templates": "Templedi", +"Background color": "Lliw cefndir", +"Custom...": "Personol...", +"Custom color": "Lliw personol", +"No color": "Dim Lliw", +"Text color": "Lliw testun", +"Table of Contents": "Tabl Cynnwys", +"Show blocks": "Dangos blociau", +"Show invisible characters": "Dangos nodau anweledig", +"Words: {0}": "Geiriau: {0}", +"Insert": "Mewnosod", +"File": "Ffeil", +"Edit": "Golygu", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Ardal Testun Uwch. Pwyswch ALT-F9 ar gyfer y ddewislen, Pwyswch ALT-F10 ar gyfer y bar offer. Pwyswch ALT-0 am gymorth", +"Tools": "Offer", +"View": "Dangos", +"Table": "Tabl", +"Format": "Fformat" +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/da.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/da.js index d295bf5750..90e4009d92 100644 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/da.js +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/da.js @@ -1,219 +1,261 @@ tinymce.addI18n('da',{ -"Cut": "Klip", -"Heading 5": "Overskrift 5", -"Header 2": "Overskrift 2", -"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "Din browser underst\u00f8tter ikke direkte adgang til clipboard. Benyt Ctrl+X\/C\/ keybord shortcuts i stedet for.", -"Heading 4": "Overskrift 4", -"Div": "Div", -"Heading 2": "Overskrift 2", -"Paste": "Inds\u00e6t", -"Close": "Luk", -"Font Family": "Skrifttype", -"Pre": "Pre", -"Align right": "H\u00f8jrejusteret", -"New document": "Nyt dokument", -"Blockquote": "Indrykning", -"Numbered list": "Nummerering", -"Heading 1": "Overskrift 1", -"Headings": "Overskrifter", -"Increase indent": "For\u00f8g indrykning", -"Formats": "Formater", -"Headers": "Overskrifter", -"Select all": "V\u00e6lg alle", -"Header 3": "Overskrift 3", -"Blocks": "Blokke", -"Undo": "Fortryd", -"Strikethrough": "Gennemstreg", -"Bullet list": "Punkt tegn", -"Header 1": "Overskrift 1", -"Superscript": "H\u00e6vet", -"Clear formatting": "Nulstil formattering", -"Font Sizes": "Skriftst\u00f8rrelse", -"Subscript": "S\u00e6nket", -"Header 6": "Overskrift 6", "Redo": "Genopret", -"Paragraph": "S\u00e6tning", -"Ok": "Ok", -"Bold": "Fed", -"Code": "Code", -"Italic": "Kursiv", -"Align center": "Centreret", -"Header 5": "Overskrift 5", -"Heading 6": "Overskrift 6", -"Heading 3": "Overskrift 3", -"Decrease indent": "Formindsk indrykning", -"Header 4": "Overskrift 4", -"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "S\u00e6t ind er indstillet til at inds\u00e6tte som ren tekst. Indhold bliver nu indsat uden formatering indtil du \u00e6ndrer indstillingen.", -"Underline": "Understreg", -"Cancel": "Fortryd", -"Justify": "Justering", -"Inline": "Inline", +"Undo": "Fortryd", +"Cut": "Klip", "Copy": "Kopier", -"Align left": "Venstrejusteret", +"Paste": "Inds\u00e6t", +"Select all": "V\u00e6lg alle", +"New document": "Nyt dokument", +"Ok": "Ok", +"Cancel": "Fortryd", "Visual aids": "Visuel hj\u00e6lp", -"Lower Greek": "Lower Gr\u00e6sk", -"Square": "Kvadrat", +"Bold": "Fed", +"Italic": "Kursiv", +"Underline": "Understreg", +"Strikethrough": "Gennemstreg", +"Superscript": "H\u00e6vet", +"Subscript": "S\u00e6nket", +"Clear formatting": "Nulstil formattering", +"Align left": "Venstrejusteret", +"Align center": "Centreret", +"Align right": "H\u00f8jrejusteret", +"Justify": "Justering", +"Bullet list": "Punkt tegn", +"Numbered list": "Nummerering", +"Decrease indent": "Formindsk indrykning", +"Increase indent": "For\u00f8g indrykning", +"Close": "Luk", +"Formats": "Formater", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "Din browser underst\u00f8tter ikke direkte adgang til clipboard. Benyt Ctrl+X\/C\/ keybord shortcuts i stedet for.", +"Headers": "Overskrifter", +"Header 1": "Overskrift 1", +"Header 2": "Overskrift 2", +"Header 3": "Overskrift 3", +"Header 4": "Overskrift 4", +"Header 5": "Overskrift 5", +"Header 6": "Overskrift 6", +"Headings": "Overskrifter", +"Heading 1": "Overskrift 1", +"Heading 2": "Overskrift 2", +"Heading 3": "Overskrift 3", +"Heading 4": "Overskrift 4", +"Heading 5": "Overskrift 5", +"Heading 6": "Overskrift 6", +"Preformatted": "Pr\u00e6formateret", +"Div": "Div", +"Pre": "Pre", +"Code": "Code", +"Paragraph": "S\u00e6tning", +"Blockquote": "Indrykning", +"Inline": "Inline", +"Blocks": "Blokke", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "S\u00e6t ind er indstillet til at inds\u00e6tte som ren tekst. Indhold bliver nu indsat uden formatering indtil du \u00e6ndrer indstillingen.", +"Font Family": "Skrifttype", +"Font Sizes": "Skriftst\u00f8rrelse", +"Class": "Klasse", +"Browse for an image": "S\u00f8g efter et billede", +"OR": "ELLER", +"Drop an image here": "Slip et billede her", +"Upload": "Opload", +"Block": "Blok", +"Align": "Tilpas", "Default": "Standard", -"Lower Alpha": "Lower Alpha", "Circle": "Cirkel", "Disc": "Disk", +"Square": "Kvadrat", +"Lower Alpha": "Lower Alpha", +"Lower Greek": "Lower Gr\u00e6sk", +"Lower Roman": "Lower Roman", "Upper Alpha": "Upper Alpha", "Upper Roman": "Upper Roman", -"Lower Roman": "Lower Roman", -"Name": "Navn", "Anchor": "Anchor", +"Name": "Navn", +"Id": "Id", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "Id b\u00f8r starte med et bogstav, efterfulgt af bogstaver, tal, bindestreger, punktummer, koloner eller underscores.", "You have unsaved changes are you sure you want to navigate away?": "Du har ikke gemte \u00e6ndringer. Er du sikker p\u00e5 at du vil forts\u00e6tte?", "Restore last draft": "Genopret sidste kladde", "Special character": "Specielle tegn", "Source code": "Kildekode", -"B": "B", +"Insert\/Edit code sample": "Inds\u00e6t\/Ret kodeeksempel", +"Language": "Sprog", +"Code sample": "Kodepr\u00f8ve", +"Color": "Farve", "R": "R", "G": "G", -"Color": "Farve", -"Right to left": "H\u00f8jre til venstre", +"B": "B", "Left to right": "Venstre til h\u00f8jre", +"Right to left": "H\u00f8jre til venstre", "Emoticons": "Emot-ikoner", -"Robots": "Robotter", "Document properties": "Dokument egenskaber", "Title": "Titel", "Keywords": "S\u00f8geord", -"Encoding": "Kodning", "Description": "Beskrivelse", +"Robots": "Robotter", "Author": "Forfatter", +"Encoding": "Kodning", "Fullscreen": "Fuldsk\u00e6rm", +"Action": "Handling", +"Shortcut": "Genvej", +"Help": "Hj\u00e6lp", +"Address": "Adresse", +"Focus to menubar": "Fokus p\u00e5 menulinjen", +"Focus to toolbar": "Fokus p\u00e5 v\u00e6rkt\u00f8jslinjen", +"Focus to element path": "Fokuser p\u00e5 elementvej", +"Focus to contextual toolbar": "Fokuser p\u00e5 kontekstuelle v\u00e6rkt\u00f8jslinje", +"Insert link (if link plugin activated)": "Inds\u00e6t link (hvis link plugin er aktiveret)", +"Save (if save plugin activated)": "Gem (hvis save plugin er aktiveret)", +"Find (if searchreplace plugin activated)": "Find (hvis searchreplace plugin er aktiveret)", +"Plugins installed ({0}):": "Installerede plugins ({0}):", +"Premium plugins:": "Premium plugins:", +"Learn more...": "L\u00e6r mere...", +"You are using {0}": "Du benytter {0}", +"Plugins": "Plugins", +"Handy Shortcuts": "Praktiske Genveje", "Horizontal line": "Vandret linie", -"Horizontal space": "Vandret afstand", "Insert\/edit image": "Inds\u00e6t\/ret billede", +"Image description": "Billede beskrivelse", +"Source": "Kilde", +"Dimensions": "Dimensioner", +"Constrain proportions": "Behold propertioner", "General": "Generet", "Advanced": "Avanceret", -"Source": "Kilde", -"Border": "Kant", -"Constrain proportions": "Behold propertioner", -"Vertical space": "Lodret afstand", -"Image description": "Billede beskrivelse", "Style": "Stil", -"Dimensions": "Dimensioner", +"Vertical space": "Lodret afstand", +"Horizontal space": "Vandret afstand", +"Border": "Kant", "Insert image": "Inds\u00e6t billede", -"Zoom in": "Zoom ind", -"Contrast": "Kontrast", -"Back": "Tilbage", -"Gamma": "Gamma", -"Flip horizontally": "Flip horisontalt", -"Resize": "Skaler", -"Sharpen": "G\u00f8r skarpere", -"Zoom out": "Zoom ud", -"Image options": "Billede indstillinger", -"Apply": "Anvend", -"Brightness": "Lysstyrke", -"Rotate clockwise": "Drej med urets retning", +"Image": "Billede", +"Image list": "Billede liste", "Rotate counterclockwise": "Drej modsat urets retning", -"Edit image": "Rediger billede", -"Color levels": "Farve niveauer", -"Crop": "Besk\u00e6r", -"Orientation": "Retning", +"Rotate clockwise": "Drej med urets retning", "Flip vertically": "Flip vertikalt", +"Flip horizontally": "Flip horisontalt", +"Edit image": "Rediger billede", +"Image options": "Billede indstillinger", +"Zoom in": "Zoom ind", +"Zoom out": "Zoom ud", +"Crop": "Besk\u00e6r", +"Resize": "Skaler", +"Orientation": "Retning", +"Brightness": "Lysstyrke", +"Sharpen": "G\u00f8r skarpere", +"Contrast": "Kontrast", +"Color levels": "Farve niveauer", +"Gamma": "Gamma", "Invert": "Inverter", +"Apply": "Anvend", +"Back": "Tilbage", "Insert date\/time": "Inds\u00e6t dato\/klokkeslet", -"Remove link": "Fjern link", -"Url": "Url", -"Text to display": "Vis tekst", -"Anchors": "Ankre", +"Date\/time": "Dato\/klokkeslet", "Insert link": "Inds\u00e6t link", -"New window": "Nyt vindue", -"None": "Ingen", -"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "URLen som du angav ser ud til at v\u00e6re et eksternt link. \u00d8nsker du at tilf\u00f8je det kr\u00e6vede prefiks http:\/\/ ?", -"Target": "Target", -"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "URLen som du angav ser ud til at v\u00e6re en email adresse. \u00d8nsker du at tilf\u00f8je det kr\u00e6vede prefiks mailto: ?", "Insert\/edit link": "Inds\u00e6t\/ret link", -"Insert\/edit video": "Inds\u00e6t\/ret video", -"Poster": "Poster", -"Alternative source": "Alternativ kilde", -"Paste your embed code below:": "Inds\u00e6t din embed kode herunder:", +"Text to display": "Vis tekst", +"Url": "Url", +"Target": "Target", +"None": "Ingen", +"New window": "Nyt vindue", +"Remove link": "Fjern link", +"Anchors": "Ankre", +"Link": "Link", +"Paste or type a link": "Inds\u00e6t eller skriv et link", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "URLen som du angav ser ud til at v\u00e6re en email adresse. \u00d8nsker du at tilf\u00f8je det kr\u00e6vede prefiks mailto: ?", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "URLen som du angav ser ud til at v\u00e6re et eksternt link. \u00d8nsker du at tilf\u00f8je det kr\u00e6vede prefiks http:\/\/ ?", +"Link list": "Link liste", "Insert video": "Inds\u00e6t video", +"Insert\/edit video": "Inds\u00e6t\/ret video", +"Insert\/edit media": "Inds\u00e6t\/ret medier", +"Alternative source": "Alternativ kilde", +"Poster": "Poster", +"Paste your embed code below:": "Inds\u00e6t din embed kode herunder:", "Embed": "Integrer", +"Media": "Medier", "Nonbreaking space": "H\u00e5rdt mellemrum", "Page break": "Sideskift", "Paste as text": "Inds\u00e6t som ren tekst", "Preview": "Forh\u00e5ndsvisning", "Print": "Udskriv", "Save": "Gem", -"Could not find the specified string.": "Kunne ikke finde s\u00f8getekst", -"Replace": "Erstat", -"Next": "N\u00e6ste", -"Whole words": "Hele ord", -"Find and replace": "Find og erstat", -"Replace with": "Erstat med", "Find": "Find", +"Replace with": "Erstat med", +"Replace": "Erstat", "Replace all": "Erstat alt", -"Match case": "STORE og sm\u00e5 bogstaver", "Prev": "Forrige", +"Next": "N\u00e6ste", +"Find and replace": "Find og erstat", +"Could not find the specified string.": "Kunne ikke finde s\u00f8getekst", +"Match case": "STORE og sm\u00e5 bogstaver", +"Whole words": "Hele ord", "Spellcheck": "Stavekontrol", -"Finish": "F\u00e6rdig", -"Ignore all": "Ignorer alt", "Ignore": "Ignorer", +"Ignore all": "Ignorer alt", +"Finish": "F\u00e6rdig", "Add to Dictionary": "Tilf\u00f8j til ordbog", -"Insert row before": "Inds\u00e6t r\u00e6kke f\u00f8r", -"Rows": "R\u00e6kker", -"Height": "H\u00f8jde", -"Paste row after": "Inds\u00e6t r\u00e6kke efter", -"Alignment": "Tilpasning", -"Border color": "Kant farve", -"Column group": "Kolonne gruppe", -"Row": "R\u00e6kke", -"Insert column before": "Inds\u00e6t kolonne f\u00f8r", -"Split cell": "Split celle", -"Cell padding": "Celle padding", -"Cell spacing": "Celle afstand", -"Row type": "R\u00e6kke type", "Insert table": "Inds\u00e6t tabel", -"Body": "Krop", -"Caption": "Tekst", -"Footer": "Sidefod", -"Delete row": "Slet r\u00e6kke", -"Paste row before": "Inds\u00e6t r\u00e6kke f\u00f8r", -"Scope": "Anvendelsesomr\u00e5de", -"Delete table": "Slet tabel", -"H Align": "H juster", -"Top": "Top", -"Header cell": "Sidehoved celle", -"Column": "Kolonne", -"Row group": "R\u00e6kke gruppe", -"Cell": "Celle", -"Middle": "Midt", -"Cell type": "Celle type", -"Copy row": "Kopier r\u00e6kke", -"Row properties": "R\u00e6kke egenskaber", "Table properties": "Tabel egenskaber", -"Bottom": "Bund", -"V Align": "V juster", -"Header": "Sidehoved", -"Right": "H\u00f8jre", -"Insert column after": "Inds\u00e6t kolonne efter", -"Cols": "Kolonne", -"Insert row after": "Inds\u00e6t r\u00e6kke efter", -"Width": "Bredde", +"Delete table": "Slet tabel", +"Cell": "Celle", +"Row": "R\u00e6kke", +"Column": "Kolonne", "Cell properties": "Celle egenskaber", -"Left": "Venstre", -"Cut row": "Klip r\u00e6kke", -"Delete column": "Slet kolonne", -"Center": "Centrering", "Merge cells": "Flet celler", +"Split cell": "Split celle", +"Insert row before": "Inds\u00e6t r\u00e6kke f\u00f8r", +"Insert row after": "Inds\u00e6t r\u00e6kke efter", +"Delete row": "Slet r\u00e6kke", +"Row properties": "R\u00e6kke egenskaber", +"Cut row": "Klip r\u00e6kke", +"Copy row": "Kopier r\u00e6kke", +"Paste row before": "Inds\u00e6t r\u00e6kke f\u00f8r", +"Paste row after": "Inds\u00e6t r\u00e6kke efter", +"Insert column before": "Inds\u00e6t kolonne f\u00f8r", +"Insert column after": "Inds\u00e6t kolonne efter", +"Delete column": "Slet kolonne", +"Cols": "Kolonne", +"Rows": "R\u00e6kker", +"Width": "Bredde", +"Height": "H\u00f8jde", +"Cell spacing": "Celle afstand", +"Cell padding": "Celle padding", +"Caption": "Tekst", +"Left": "Venstre", +"Center": "Centrering", +"Right": "H\u00f8jre", +"Cell type": "Celle type", +"Scope": "Anvendelsesomr\u00e5de", +"Alignment": "Tilpasning", +"H Align": "H juster", +"V Align": "V juster", +"Top": "Top", +"Middle": "Midt", +"Bottom": "Bund", +"Header cell": "Sidehoved celle", +"Row group": "R\u00e6kke gruppe", +"Column group": "Kolonne gruppe", +"Row type": "R\u00e6kke type", +"Header": "Sidehoved", +"Body": "Krop", +"Footer": "Sidefod", +"Border color": "Kant farve", "Insert template": "Inds\u00e6t skabelon", "Templates": "Skabeloner", +"Template": "Skabelon", +"Text color": "Tekst farve", "Background color": "Baggrunds farve", "Custom...": "Brugerdefineret...", "Custom color": "Brugerdefineret farve", "No color": "Ingen farve", -"Text color": "Tekst farve", +"Table of Contents": "Indholdsfortegnelse", "Show blocks": "Vis klokke", "Show invisible characters": "Vis usynlige tegn", "Words: {0}": "Ord: {0}", -"Insert": "Inds\u00e6t", +"{0} words": "{0} ord", "File": "Fil", "Edit": "Rediger", -"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Rich Text omr\u00e5de. Tryk ALT-F9 for menu. Tryk ALT-F10 for toolbar. Tryk ALT-0 for hj\u00e6lp", -"Tools": "V\u00e6rkt\u00f8j", +"Insert": "Inds\u00e6t", "View": "Vis", +"Format": "Format", "Table": "Tabel", -"Format": "Format" +"Tools": "V\u00e6rkt\u00f8j", +"Powered by {0}": "Drevet af {0}", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Rich Text omr\u00e5de. Tryk ALT-F9 for menu. Tryk ALT-F10 for toolbar. Tryk ALT-0 for hj\u00e6lp" }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/de.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/de.js index 9a31056850..32a45747bc 100644 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/de.js +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/de.js @@ -1,219 +1,261 @@ tinymce.addI18n('de',{ -"Cut": "Ausschneiden", -"Heading 5": "\u00dcberschrift 5", -"Header 2": "\u00dcberschrift 2", -"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "Ihr Browser unterst\u00fctzt leider keinen direkten Zugriff auf die Zwischenablage. Bitte benutzen Sie die Strg + X \/ C \/ V Tastenkombinationen.", -"Heading 4": "\u00dcberschrift 4", -"Div": "Textblock", -"Heading 2": "\u00dcberschrift 2", -"Paste": "Einf\u00fcgen", -"Close": "Schlie\u00dfen", -"Font Family": "Schriftart", -"Pre": "Vorformatierter Text", -"Align right": "Rechtsb\u00fcndig ausrichten", -"New document": "Neues Dokument", -"Blockquote": "Zitat", -"Numbered list": "Nummerierte Liste", -"Heading 1": "\u00dcberschrift 1", -"Headings": "\u00dcberschriften", -"Increase indent": "Einzug vergr\u00f6\u00dfern", -"Formats": "Formate", -"Headers": "\u00dcberschriften", -"Select all": "Alles ausw\u00e4hlen", -"Header 3": "\u00dcberschrift 3", -"Blocks": "Absatzformate", -"Undo": "R\u00fcckg\u00e4ngig", -"Strikethrough": "Durchgestrichen", -"Bullet list": "Aufz\u00e4hlung", -"Header 1": "\u00dcberschrift 1", -"Superscript": "Hochgestellt", -"Clear formatting": "Formatierung entfernen", -"Font Sizes": "Schriftgr\u00f6\u00dfe", -"Subscript": "Tiefgestellt", -"Header 6": "\u00dcberschrift 6", "Redo": "Wiederholen", -"Paragraph": "Absatz", -"Ok": "Ok", -"Bold": "Fett", -"Code": "Quelltext", -"Italic": "Kursiv", -"Align center": "Zentriert ausrichten", -"Header 5": "\u00dcberschrift 5", -"Heading 6": "\u00dcberschrift 6", -"Heading 3": "\u00dcberschrift 3", -"Decrease indent": "Einzug verkleinern", -"Header 4": "\u00dcberschrift 4", -"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "Einf\u00fcgen ist nun im einfachen Textmodus. Inhalte werden ab jetzt als unformatierter Text eingef\u00fcgt, bis Sie diese Einstellung wieder ausschalten!", -"Underline": "Unterstrichen", -"Cancel": "Abbrechen", -"Justify": "Blocksatz", -"Inline": "Zeichenformate", +"Undo": "R\u00fcckg\u00e4ngig", +"Cut": "Ausschneiden", "Copy": "Kopieren", -"Align left": "Linksb\u00fcndig ausrichten", +"Paste": "Einf\u00fcgen", +"Select all": "Alles ausw\u00e4hlen", +"New document": "Neues Dokument", +"Ok": "Ok", +"Cancel": "Abbrechen", "Visual aids": "Visuelle Hilfen", -"Lower Greek": "Griechische Kleinbuchstaben", -"Square": "Quadrat", +"Bold": "Fett", +"Italic": "Kursiv", +"Underline": "Unterstrichen", +"Strikethrough": "Durchgestrichen", +"Superscript": "Hochgestellt", +"Subscript": "Tiefgestellt", +"Clear formatting": "Formatierung entfernen", +"Align left": "Linksb\u00fcndig ausrichten", +"Align center": "Zentriert ausrichten", +"Align right": "Rechtsb\u00fcndig ausrichten", +"Justify": "Blocksatz", +"Bullet list": "Aufz\u00e4hlung", +"Numbered list": "Nummerierte Liste", +"Decrease indent": "Einzug verkleinern", +"Increase indent": "Einzug vergr\u00f6\u00dfern", +"Close": "Schlie\u00dfen", +"Formats": "Formate", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "Ihr Browser unterst\u00fctzt leider keinen direkten Zugriff auf die Zwischenablage. Bitte benutzen Sie die Strg + X \/ C \/ V Tastenkombinationen.", +"Headers": "\u00dcberschriften", +"Header 1": "\u00dcberschrift 1", +"Header 2": "\u00dcberschrift 2", +"Header 3": "\u00dcberschrift 3", +"Header 4": "\u00dcberschrift 4", +"Header 5": "\u00dcberschrift 5", +"Header 6": "\u00dcberschrift 6", +"Headings": "\u00dcberschriften", +"Heading 1": "\u00dcberschrift 1", +"Heading 2": "\u00dcberschrift 2", +"Heading 3": "\u00dcberschrift 3", +"Heading 4": "\u00dcberschrift 4", +"Heading 5": "\u00dcberschrift 5", +"Heading 6": "\u00dcberschrift 6", +"Preformatted": "Preformatted", +"Div": "Textblock", +"Pre": "Vorformatierter Text", +"Code": "Quelltext", +"Paragraph": "Absatz", +"Blockquote": "Zitat", +"Inline": "Zeichenformate", +"Blocks": "Absatzformate", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "Einf\u00fcgen ist nun im einfachen Textmodus. Inhalte werden ab jetzt als unformatierter Text eingef\u00fcgt, bis Sie diese Einstellung wieder ausschalten!", +"Font Family": "Schriftart", +"Font Sizes": "Schriftgr\u00f6\u00dfe", +"Class": "Klasse", +"Browse for an image": "Bild...", +"OR": "ODER", +"Drop an image here": "Bild hier ablegen", +"Upload": "Hochladen", +"Block": "Blocksatz", +"Align": "Ausrichtung", "Default": "Standard", -"Lower Alpha": "Kleinbuchstaben", "Circle": "Kreis", "Disc": "Punkt", +"Square": "Quadrat", +"Lower Alpha": "Kleinbuchstaben", +"Lower Greek": "Griechische Kleinbuchstaben", +"Lower Roman": "R\u00f6mische Zahlen (Kleinbuchstaben)", "Upper Alpha": "Gro\u00dfbuchstaben", "Upper Roman": "R\u00f6mische Zahlen (Gro\u00dfbuchstaben)", -"Lower Roman": "R\u00f6mische Zahlen (Kleinbuchstaben)", -"Name": "Name", "Anchor": "Textmarke", +"Name": "Name", +"Id": "Kennung", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "Die Kennung sollte mit einem Buchstaben anfangen. Nachfolgend nur Buchstaben, Zahlen, Striche (Minus), Punkte, Kommas und Unterstriche.", "You have unsaved changes are you sure you want to navigate away?": "Die \u00c4nderungen wurden noch nicht gespeichert, sind Sie sicher, dass Sie diese Seite verlassen wollen?", "Restore last draft": "Letzten Entwurf wiederherstellen", "Special character": "Sonderzeichen", "Source code": "Quelltext", -"B": "B", +"Insert\/Edit code sample": "Codebeispiel einf\u00fcgen\/bearbeiten", +"Language": "Sprache", +"Code sample": "Codebeispiel", +"Color": "Farbe", "R": "R", "G": "G", -"Color": "Farbe", -"Right to left": "Von rechts nach links", +"B": "B", "Left to right": "Von links nach rechts", +"Right to left": "Von rechts nach links", "Emoticons": "Emoticons", -"Robots": "Robots", "Document properties": "Dokumenteigenschaften", "Title": "Titel", "Keywords": "Sch\u00fcsselw\u00f6rter", -"Encoding": "Zeichenkodierung", "Description": "Beschreibung", +"Robots": "Robots", "Author": "Verfasser", +"Encoding": "Zeichenkodierung", "Fullscreen": "Vollbild", +"Action": "Aktion", +"Shortcut": "Shortcut", +"Help": "Hilfe", +"Address": "Adresse", +"Focus to menubar": "Fokus auf Men\u00fcleiste", +"Focus to toolbar": "Fokus auf Werkzeugleiste", +"Focus to element path": "Fokus auf Elementpfad", +"Focus to contextual toolbar": "Fokus auf kontextbezogene Werkzeugleiste", +"Insert link (if link plugin activated)": "Link einf\u00fcgen (wenn Link-Plugin aktiviert ist)", +"Save (if save plugin activated)": "Speichern (wenn Save-Plugin aktiviert ist)", +"Find (if searchreplace plugin activated)": "Suchen einf\u00fcgen (wenn Suchen\/Ersetzen-Plugin aktiviert ist)", +"Plugins installed ({0}):": "installierte Plugins ({0}):", +"Premium plugins:": "Premium Plugins:", +"Learn more...": "Erfahren Sie mehr dazu...", +"You are using {0}": "Sie verwenden {0}", +"Plugins": "Plugins", +"Handy Shortcuts": "Praktische Tastenkombinationen", "Horizontal line": "Horizontale Linie", -"Horizontal space": "Horizontaler Abstand", "Insert\/edit image": "Bild einf\u00fcgen\/bearbeiten", +"Image description": "Bildbeschreibung", +"Source": "Quelle", +"Dimensions": "Abmessungen", +"Constrain proportions": "Seitenverh\u00e4ltnis beibehalten", "General": "Allgemein", "Advanced": "Erweitert", -"Source": "Quelle", -"Border": "Rahmen", -"Constrain proportions": "Seitenverh\u00e4ltnis beibehalten", -"Vertical space": "Vertikaler Abstand", -"Image description": "Bildbeschreibung", "Style": "Stil", -"Dimensions": "Abmessungen", +"Vertical space": "Vertikaler Abstand", +"Horizontal space": "Horizontaler Abstand", +"Border": "Rahmen", "Insert image": "Bild einf\u00fcgen", -"Zoom in": "Ansicht vergr\u00f6\u00dfern", -"Contrast": "Kontrast", -"Back": "Zur\u00fcck", -"Gamma": "Gamma", -"Flip horizontally": "Horizontal spiegeln", -"Resize": "Skalieren", -"Sharpen": "Sch\u00e4rfen", -"Zoom out": "Ansicht verkleinern", -"Image options": "Bildeigenschaften", -"Apply": "Anwenden", -"Brightness": "Helligkeit", -"Rotate clockwise": "Im Uhrzeigersinn drehen", +"Image": "Bild", +"Image list": "Bildliste", "Rotate counterclockwise": "Gegen den Uhrzeigersinn drehen", -"Edit image": "Bild bearbeiten", -"Color levels": "Farbwerte", -"Crop": "Bescheiden", -"Orientation": "Ausrichtung", +"Rotate clockwise": "Im Uhrzeigersinn drehen", "Flip vertically": "Vertikal spiegeln", +"Flip horizontally": "Horizontal spiegeln", +"Edit image": "Bild bearbeiten", +"Image options": "Bildeigenschaften", +"Zoom in": "Ansicht vergr\u00f6\u00dfern", +"Zoom out": "Ansicht verkleinern", +"Crop": "Bescheiden", +"Resize": "Skalieren", +"Orientation": "Ausrichtung", +"Brightness": "Helligkeit", +"Sharpen": "Sch\u00e4rfen", +"Contrast": "Kontrast", +"Color levels": "Farbwerte", +"Gamma": "Gamma", "Invert": "Invertieren", +"Apply": "Anwenden", +"Back": "Zur\u00fcck", "Insert date\/time": "Datum\/Uhrzeit einf\u00fcgen ", -"Remove link": "Link entfernen", -"Url": "URL", -"Text to display": "Anzuzeigender Text", -"Anchors": "Textmarken", +"Date\/time": "Datum\/Uhrzeit", "Insert link": "Link einf\u00fcgen", -"New window": "Neues Fenster", -"None": "Keine", -"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "Diese Adresse scheint ein externer Link zu sein. M\u00f6chten Sie das dazu ben\u00f6tigte \"http:\/\/\" voranstellen?", -"Target": "Ziel", -"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "Diese Adresse scheint eine E-Mail-Adresse zu sein. M\u00f6chten Sie das dazu ben\u00f6tigte \"mailto:\" voranstellen?", "Insert\/edit link": "Link einf\u00fcgen\/bearbeiten", -"Insert\/edit video": "Video einf\u00fcgen\/bearbeiten", -"Poster": "Poster", -"Alternative source": "Alternative Quelle", -"Paste your embed code below:": "F\u00fcgen Sie Ihren Einbettungscode hier ein:", +"Text to display": "Anzuzeigender Text", +"Url": "URL", +"Target": "Ziel", +"None": "Keine", +"New window": "Neues Fenster", +"Remove link": "Link entfernen", +"Anchors": "Textmarken", +"Link": "Link", +"Paste or type a link": "Link einf\u00fcgen oder eintippen", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "Diese Adresse scheint eine E-Mail-Adresse zu sein. M\u00f6chten Sie das dazu ben\u00f6tigte \"mailto:\" voranstellen?", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "Diese Adresse scheint ein externer Link zu sein. M\u00f6chten Sie das dazu ben\u00f6tigte \"http:\/\/\" voranstellen?", +"Link list": "Linkliste", "Insert video": "Video einf\u00fcgen", +"Insert\/edit video": "Video einf\u00fcgen\/bearbeiten", +"Insert\/edit media": "Medien einf\u00fcgen\/bearbeiten", +"Alternative source": "Alternative Quelle", +"Poster": "Poster", +"Paste your embed code below:": "F\u00fcgen Sie Ihren Einbettungscode hier ein:", "Embed": "Einbetten", +"Media": "Medium", "Nonbreaking space": "Gesch\u00fctztes Leerzeichen", "Page break": "Seitenumbruch", "Paste as text": "Als Text einf\u00fcgen", "Preview": "Vorschau", "Print": "Drucken", "Save": "Speichern", -"Could not find the specified string.": "Die Zeichenfolge wurde nicht gefunden.", -"Replace": "Ersetzen", -"Next": "Weiter", -"Whole words": "Nur ganze W\u00f6rter", -"Find and replace": "Suchen und ersetzen", -"Replace with": "Ersetzen durch", "Find": "Suchen", +"Replace with": "Ersetzen durch", +"Replace": "Ersetzen", "Replace all": "Alles ersetzen", -"Match case": "Gro\u00df-\/Kleinschreibung beachten", "Prev": "Zur\u00fcck", +"Next": "Weiter", +"Find and replace": "Suchen und ersetzen", +"Could not find the specified string.": "Die Zeichenfolge wurde nicht gefunden.", +"Match case": "Gro\u00df-\/Kleinschreibung beachten", +"Whole words": "Nur ganze W\u00f6rter", "Spellcheck": "Rechtschreibpr\u00fcfung", -"Finish": "Ende", -"Ignore all": "Alles Ignorieren", "Ignore": "Ignorieren", +"Ignore all": "Alles Ignorieren", +"Finish": "Ende", "Add to Dictionary": "Zum W\u00f6rterbuch hinzuf\u00fcgen", -"Insert row before": "Neue Zeile davor einf\u00fcgen ", -"Rows": "Zeilen", -"Height": "H\u00f6he", -"Paste row after": "Zeile danach einf\u00fcgen", -"Alignment": "Ausrichtung", -"Border color": "Rahmenfarbe", -"Column group": "Spaltengruppe", -"Row": "Zeile", -"Insert column before": "Neue Spalte davor einf\u00fcgen", -"Split cell": "Zelle aufteilen", -"Cell padding": "Zelleninnenabstand", -"Cell spacing": "Zellenabstand", -"Row type": "Zeilentyp", "Insert table": "Tabelle einf\u00fcgen", -"Body": "Inhalt", -"Caption": "Beschriftung", -"Footer": "Fu\u00dfzeile", -"Delete row": "Zeile l\u00f6schen", -"Paste row before": "Zeile davor einf\u00fcgen", -"Scope": "G\u00fcltigkeitsbereich", -"Delete table": "Tabelle l\u00f6schen", -"H Align": "Horizontale Ausrichtung", -"Top": "Oben", -"Header cell": "Kopfzelle", -"Column": "Spalte", -"Row group": "Zeilengruppe", -"Cell": "Zelle", -"Middle": "Mitte", -"Cell type": "Zellentyp", -"Copy row": "Zeile kopieren", -"Row properties": "Zeileneigenschaften", "Table properties": "Tabelleneigenschaften", -"Bottom": "Unten", -"V Align": "Vertikale Ausrichtung", -"Header": "Kopfzeile", -"Right": "Rechtsb\u00fcndig", -"Insert column after": "Neue Spalte danach einf\u00fcgen", -"Cols": "Spalten", -"Insert row after": "Neue Zeile danach einf\u00fcgen", -"Width": "Breite", +"Delete table": "Tabelle l\u00f6schen", +"Cell": "Zelle", +"Row": "Zeile", +"Column": "Spalte", "Cell properties": "Zelleneigenschaften", -"Left": "Linksb\u00fcndig", -"Cut row": "Zeile ausschneiden", -"Delete column": "Spalte l\u00f6schen", -"Center": "Zentriert", "Merge cells": "Zellen verbinden", +"Split cell": "Zelle aufteilen", +"Insert row before": "Neue Zeile davor einf\u00fcgen ", +"Insert row after": "Neue Zeile danach einf\u00fcgen", +"Delete row": "Zeile l\u00f6schen", +"Row properties": "Zeileneigenschaften", +"Cut row": "Zeile ausschneiden", +"Copy row": "Zeile kopieren", +"Paste row before": "Zeile davor einf\u00fcgen", +"Paste row after": "Zeile danach einf\u00fcgen", +"Insert column before": "Neue Spalte davor einf\u00fcgen", +"Insert column after": "Neue Spalte danach einf\u00fcgen", +"Delete column": "Spalte l\u00f6schen", +"Cols": "Spalten", +"Rows": "Zeilen", +"Width": "Breite", +"Height": "H\u00f6he", +"Cell spacing": "Zellenabstand", +"Cell padding": "Zelleninnenabstand", +"Caption": "Beschriftung", +"Left": "Linksb\u00fcndig", +"Center": "Zentriert", +"Right": "Rechtsb\u00fcndig", +"Cell type": "Zellentyp", +"Scope": "G\u00fcltigkeitsbereich", +"Alignment": "Ausrichtung", +"H Align": "Horizontale Ausrichtung", +"V Align": "Vertikale Ausrichtung", +"Top": "Oben", +"Middle": "Mitte", +"Bottom": "Unten", +"Header cell": "Kopfzelle", +"Row group": "Zeilengruppe", +"Column group": "Spaltengruppe", +"Row type": "Zeilentyp", +"Header": "Kopfzeile", +"Body": "Inhalt", +"Footer": "Fu\u00dfzeile", +"Border color": "Rahmenfarbe", "Insert template": "Vorlage einf\u00fcgen ", "Templates": "Vorlagen", +"Template": "Vorlage", +"Text color": "Textfarbe", "Background color": "Hintergrundfarbe", "Custom...": "Benutzerdefiniert...", "Custom color": "Benutzerdefinierte Farbe", "No color": "Keine Farbe", -"Text color": "Textfarbe", -"Show blocks": " Bl\u00f6cke anzeigen", +"Table of Contents": "Inhaltsverzeichnis", +"Show blocks": "Bl\u00f6cke anzeigen", "Show invisible characters": "Unsichtbare Zeichen anzeigen", "Words: {0}": "W\u00f6rter: {0}", -"Insert": "Einf\u00fcgen", +"{0} words": "{0} W\u00f6rter", "File": "Datei", "Edit": "Bearbeiten", -"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Rich-Text- Area. Dr\u00fccken Sie ALT-F9 f\u00fcr das Men\u00fc. Dr\u00fccken Sie ALT-F10 f\u00fcr Symbolleiste. Dr\u00fccken Sie ALT-0 f\u00fcr Hilfe", -"Tools": "Werkzeuge", +"Insert": "Einf\u00fcgen", "View": "Ansicht", +"Format": "Format", "Table": "Tabelle", -"Format": "Format" +"Tools": "Werkzeuge", +"Powered by {0}": "Betrieben von {0}", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Rich-Text- Area. Dr\u00fccken Sie ALT-F9 f\u00fcr das Men\u00fc. Dr\u00fccken Sie ALT-F10 f\u00fcr Symbolleiste. Dr\u00fccken Sie ALT-0 f\u00fcr Hilfe" }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/de_AT.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/de_AT.js new file mode 100644 index 0000000000..2af071f5ce --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/de_AT.js @@ -0,0 +1,261 @@ +tinymce.addI18n('de_AT',{ +"Redo": "Wiederholen", +"Undo": "R\u00fcckg\u00e4ngig", +"Cut": "Ausschneiden", +"Copy": "Kopieren", +"Paste": "Einf\u00fcgen", +"Select all": "Alles ausw\u00e4hlen", +"New document": "Neues Dokument", +"Ok": "Ok", +"Cancel": "Abbrechen", +"Visual aids": "Hilfslinien und unsichtbare Elemente einblenden", +"Bold": "Fett", +"Italic": "Kursiv", +"Underline": "Unterstrichen", +"Strikethrough": "Durchgestrichen", +"Superscript": "Hochgestellt", +"Subscript": "Tiefgestellt", +"Clear formatting": "Formatierungen zur\u00fccksetzen", +"Align left": "Linksb\u00fcndig", +"Align center": "Zentriert", +"Align right": "Rechtsb\u00fcndig", +"Justify": "Blocksatz", +"Bullet list": "Unsortierte Liste", +"Numbered list": "Sortierte Liste", +"Decrease indent": "Ausr\u00fccken", +"Increase indent": "Einr\u00fccken", +"Close": "Schlie\u00dfen", +"Formats": "Formate", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "Ihr Browser unterst\u00fctzt keinen direkten Zugriff auf die Zwischenablage. Bitte nutzen Sie die Tastaturk\u00fcrzel Strg+X\/C\/V stattdessen.", +"Headers": "\u00dcberschriften", +"Header 1": "\u00dcberschrift 1", +"Header 2": "\u00dcberschrift 2", +"Header 3": "\u00dcberschrift 3", +"Header 4": "\u00dcberschrift 4", +"Header 5": "\u00dcberschrift 5", +"Header 6": "\u00dcberschrift 6", +"Headings": "\u00dcberschriften", +"Heading 1": "\u00dcberschrift 1", +"Heading 2": "\u00dcberschrift 2", +"Heading 3": "\u00dcberschrift 3", +"Heading 4": "\u00dcberschrift 4", +"Heading 5": "\u00dcberschrift 5", +"Heading 6": "\u00dcberschrift 6", +"Preformatted": "Vorformatiert", +"Div": "Block (div)", +"Pre": "Vorformatierter Text (pre)", +"Code": "Code (code)", +"Paragraph": "Absatz (p)", +"Blockquote": "Zitat (blockquote)", +"Inline": "Inline", +"Blocks": "Bl\u00f6cke", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "Alle Texte werden nun ohne Formatierung eingef\u00fcgt, bis diese Einstellung wieder ge\u00e4ndert wird.", +"Font Family": "Schriftart", +"Font Sizes": "Schriftgr\u00f6\u00dfen", +"Class": "Klasse", +"Browse for an image": "Bild...", +"OR": "oder", +"Drop an image here": "Bild hierher ziehen", +"Upload": "Hochladen", +"Block": "Block", +"Align": "Ausrichtung", +"Default": "Standard", +"Circle": "Kreis", +"Disc": "Gef\u00fcllter Kreis", +"Square": "Quadrat", +"Lower Alpha": "Kleinbuchstaben", +"Lower Greek": "Griechische Kleinbuchstaben", +"Lower Roman": "R\u00f6mische Zahlen (Kleinbuchstaben)", +"Upper Alpha": "Gro\u00dfbuchstaben", +"Upper Roman": "R\u00f6mische Zahlen (Gro\u00dfbuchstaben)", +"Anchor": "Anker", +"Name": "Name", +"Id": "ID", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "Eine ID f\u00e4ngt mit einem Buchstaben an, gefolgt von Buchstaben, Ziffern, Bindestrichen, Punkten, Doppelpunkten oder Unterstrichen.", +"You have unsaved changes are you sure you want to navigate away?": "Sie haben ungespeicherte \u00c4nderungen. Sind Sie sicher, dass Sie die Seite verlassen wollen?", +"Restore last draft": "Letzten Entwurf wiederherstellen.", +"Special character": "Sonderzeichen", +"Source code": "Quelltext", +"Insert\/Edit code sample": "Beispielcode einf\u00fcgen\/bearbeiten", +"Language": "Sprache", +"Code sample": "Code Beispiel", +"Color": "Farbe", +"R": "R", +"G": "G", +"B": "B", +"Left to right": "Links nach rechts", +"Right to left": "Rechts nach links", +"Emoticons": "Emoticons", +"Document properties": "Dokumenteigenschaften", +"Title": "Titel", +"Keywords": "Schl\u00fcsselw\u00f6rter", +"Description": "Beschreibung", +"Robots": "Suchmaschinen", +"Author": "Autor", +"Encoding": "Enkodierung", +"Fullscreen": "Vollbild", +"Action": "Aktion", +"Shortcut": "Tastenkombination", +"Help": "Hilfe", +"Address": "Adresse", +"Focus to menubar": "Fokus auf Men\u00fcleiste", +"Focus to toolbar": "Fokus auf Werkzeugleiste", +"Focus to element path": "Fokus auf Elementpfad", +"Focus to contextual toolbar": "Fokus auf kontextbezogene Werkzeugleiste", +"Insert link (if link plugin activated)": "Link einf\u00fcgen (wenn Plugin aktiv ist)", +"Save (if save plugin activated)": "Speichern (wenn Plugin aktiv ist)", +"Find (if searchreplace plugin activated)": "Suchen (wenn Plugin aktiv ist)", +"Plugins installed ({0}):": "Installierte Plugins ({0}):", +"Premium plugins:": "Premium Plugins:", +"Learn more...": "Mehr Informationen...", +"You are using {0}": "Sie verwenden {0}", +"Plugins": "Plugins", +"Handy Shortcuts": "Praktische Abk\u00fcrzungen", +"Horizontal line": "Horizontale Trennlinie", +"Insert\/edit image": "Bild einf\u00fcgen\/bearbeiten", +"Image description": "Bildbeschreibung", +"Source": "Adresse", +"Dimensions": "Ausma\u00dfe", +"Constrain proportions": "Seitenverh\u00e4ltnis beibehalten", +"General": "Allgemein", +"Advanced": "Erweitert", +"Style": "Format", +"Vertical space": "Vertikaler Abstand", +"Horizontal space": "Horizontaler Abstand", +"Border": "Rahmen", +"Insert image": "Bild einf\u00fcgen", +"Image": "Bild", +"Image list": "Bilderliste", +"Rotate counterclockwise": "Gegen den Uhrzeigersinn drehen", +"Rotate clockwise": "Im Uhrzeigersinn drehen", +"Flip vertically": "Vertikal kippen", +"Flip horizontally": "Horizontal kippen", +"Edit image": "Bild bearbeiten", +"Image options": "Bildeinstellungen", +"Zoom in": "Einzoomen", +"Zoom out": "Auszoomen", +"Crop": "Zuschneiden", +"Resize": "Gr\u00f6\u00dfe \u00e4ndern", +"Orientation": "Orientierung", +"Brightness": "Helligkeit", +"Sharpen": "Sch\u00e4rfen", +"Contrast": "Kontrast", +"Color levels": "Farbwerte", +"Gamma": "Gamma", +"Invert": "Invertieren", +"Apply": "Anwenden", +"Back": "Zur\u00fcck", +"Insert date\/time": "Zeit\/Datum einf\u00fcgen", +"Date\/time": "Zeit\/Datum", +"Insert link": "Link einf\u00fcgen", +"Insert\/edit link": "Link einf\u00fcgen\/bearbeiten", +"Text to display": "Angezeigter Text", +"Url": "Url", +"Target": "Ziel", +"None": "Keine", +"New window": "Neues Fenster", +"Remove link": "Link entfernen", +"Anchors": "Anker", +"Link": "Link", +"Paste or type a link": "Link einf\u00fcgen oder eintippen", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "Die eingegebene URL scheint eine E-Mail-Adresse zu sein. Soll das notwendige \"mailto:\"-Pr\u00e4fix hinzugef\u00fcgt werden?", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "Die eingegebene URL scheint eine externe Web-Adresse zu sein. Soll das notwendige \"http:\/\/\"-Pr\u00e4fix hinzugef\u00fcgt werden?", +"Link list": "Linkliste", +"Insert video": "Video einf\u00fcgen", +"Insert\/edit video": "Video einf\u00fcgen\/bearbeiten", +"Insert\/edit media": "Medien einf\u00fcgen\/bearbeiten", +"Alternative source": "Alternative Quelle", +"Poster": "Poster", +"Paste your embed code below:": "F\u00fcgen unten Sie Ihren Quellcode zum einbetten ein", +"Embed": "Einbetten", +"Media": "Medien", +"Nonbreaking space": "gesch\u00fctztes Leerzeichen", +"Page break": "Seitenumbruch", +"Paste as text": "Als Text einf\u00fcgen", +"Preview": "Vorschau", +"Print": "Drucken", +"Save": "Speichern", +"Find": "Suchen", +"Replace with": "Ersetzen durch", +"Replace": "Ersetzen", +"Replace all": "Alle ersetzen", +"Prev": "Vorheriges", +"Next": "N\u00e4chstes", +"Find and replace": "Suchen und ersetzen", +"Could not find the specified string.": "Keine \u00dcbereinstimmung gefunden", +"Match case": "Gro\u00df-\/Kleinschreibung beachten", +"Whole words": "Vollst\u00e4ndige W\u00f6rter", +"Spellcheck": "Rechtschreibung \u00fcberpr\u00fcfen", +"Ignore": "Ignorieren", +"Ignore all": "Alle ignorieren", +"Finish": "Fertig", +"Add to Dictionary": "Zum W\u00f6rterbuch hinzuf\u00fcgen", +"Insert table": "Tabelle einf\u00fcgen", +"Table properties": "Tabelleneigenschaften", +"Delete table": "Tabelle l\u00f6schen", +"Cell": "Zelle", +"Row": "Zeile", +"Column": "Spalte", +"Cell properties": "Zelleneigenschaften", +"Merge cells": "Zellen vereinen", +"Split cell": "Verbundene Zellen trennen", +"Insert row before": "Neue Zeile oberhalb einf\u00fcgen", +"Insert row after": "Neue Zeile unterhalb einf\u00fcgen", +"Delete row": "Zeile l\u00f6schen", +"Row properties": "Zeileneigenschaften", +"Cut row": "Zeile ausschneiden", +"Copy row": "Zeile kopieren", +"Paste row before": "Zeile oberhalb einf\u00fcgen", +"Paste row after": "Zeile unterhalb einf\u00fcgen", +"Insert column before": "Neue Spalte links einf\u00fcgen", +"Insert column after": "Neue Spalte rechts einf\u00fcgen", +"Delete column": "Spalte l\u00f6schen", +"Cols": "Spalten", +"Rows": "Zeilen", +"Width": "Breite", +"Height": "H\u00f6he", +"Cell spacing": "Zellenabstand", +"Cell padding": "Abstand innerhalb der Zellen", +"Caption": "Beschriftung der Tabelle", +"Left": "Links", +"Center": "Zentriert", +"Right": "Rechts", +"Cell type": "Zellentyp", +"Scope": "Geltungsbereich", +"Alignment": "Ausrichtung", +"H Align": "Ausrichtung H", +"V Align": "Ausrichtung V", +"Top": "Oben", +"Middle": "Mitte", +"Bottom": "Unten", +"Header cell": "\u00dcberschrift", +"Row group": "Zeilengruppe", +"Column group": "Spaltengruppe", +"Row type": "Zeilentyp", +"Header": "Tabellen\u00fcberschrift", +"Body": "Tabellenk\u00f6rper", +"Footer": "Tabellenfu\u00df", +"Border color": "Rahmenfarbe", +"Insert template": "Vorlage einf\u00fcgen", +"Templates": "Vorlagen", +"Template": "Vorlage", +"Text color": "Textfarbe", +"Background color": "Hintergrundfarbe", +"Custom...": "Benutzerdefiniert...", +"Custom color": "Benutzerdefinierte Farbe", +"No color": "Keine Farbe", +"Table of Contents": "Inhaltsverzeichnis", +"Show blocks": "Blockelemente einblenden", +"Show invisible characters": "Unsichtbare Zeichen einblenden", +"Words: {0}": "W\u00f6rter: {0}", +"{0} words": "{0} W\u00f6rter", +"File": "Datei", +"Edit": "Bearbeiten", +"Insert": "Einf\u00fcgen", +"View": "Ansicht", +"Format": "Format", +"Table": "Tabelle", +"Tools": "Extras", +"Powered by {0}": "Betrieben von {0}", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Rich Text Area. Dr\u00fccken Sie ALT-F9 f\u00fcr das Men\u00fc. Dr\u00fccken Sie ALT-F10 f\u00fcr die Werkzeugleiste. Dr\u00fccken Sie ALT-0 f\u00fcr Hilfe" +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/dv.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/dv.js new file mode 100644 index 0000000000..3de3a6d9ac --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/dv.js @@ -0,0 +1,230 @@ +tinymce.addI18n('dv',{ +"Cut": "\u0786\u07a6\u0793\u07b0", +"Heading 5": "\u0780\u07ac\u0791\u07a8\u0782\u07b0 5", +"Header 2": "\u0780\u07ac\u0791\u07a7 2", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "\u0786\u07b0\u078d\u07a8\u0795\u07b0\u0784\u07af\u0791\u07b0 \u0784\u07ad\u0782\u07aa\u0782\u07b0 \u0786\u07aa\u0783\u07aa\u0789\u07aa\u078e\u07ac \u0780\u07aa\u0787\u07b0\u078b\u07a6\u060c \u0784\u07b0\u0783\u07af\u0792\u07a6\u0783\u0787\u07a6\u0786\u07aa\u0782\u07b0 \u0782\u07aa\u078b\u07ad! Ctrl+X\/C\/V \u0784\u07ad\u0782\u07aa\u0782\u07b0 \u0786\u07aa\u0783\u07ad!", +"Heading 4": "\u0780\u07ac\u0791\u07a8\u0782\u07b0 4", +"Div": "\u0791\u07a6\u0787\u07a8\u0788\u07b0", +"Heading 2": "\u0780\u07ac\u0791\u07a8\u0782\u07b0 2", +"Paste": "\u0795\u07ad\u0790\u07b0\u0793\u07b0", +"Close": "\u0782\u07a8\u0787\u07b0\u0788\u07a7", +"Font Family": "\u078a\u07ae\u0782\u07b0\u0793\u07b0", +"Pre": "\u0795\u07b0\u0783\u07a9", +"Align right": "\u0786\u07a6\u0782\u07a7\u078c\u07a6\u0781\u07b0 \u0796\u07a6\u0787\u07b0\u0790\u07a7", +"New document": "\u0787\u07a7 \u0791\u07ae\u0786\u07a8\u0787\u07aa\u0789\u07ac\u0782\u07b0\u0793\u07b0", +"Blockquote": "\u0784\u07b0\u078d\u07ae\u0786\u07b0-\u0786\u07af\u0793\u07b0", +"Numbered list": "\u0782\u07a6\u0782\u07b0\u0784\u07a6\u0783\u07aa \u078d\u07a8\u0790\u07b0\u0793\u07b0", +"Heading 1": "\u0780\u07ac\u0791\u07a8\u0782\u07b0 1", +"Headings": "\u0780\u07ac\u0791\u07a8\u0782\u07b0", +"Increase indent": "\u078b\u07aa\u0783\u07aa\u0789\u07a8\u0782\u07b0 \u0784\u07ae\u0791\u07aa\u0786\u07aa\u0783\u07ad", +"Formats": "\u078a\u07af\u0789\u07ac\u0793\u07b0\u078c\u07a6\u0787\u07b0", +"Headers": "\u0780\u07ac\u0791\u07a7\u078c\u07a6\u0787\u07b0", +"Select all": "\u0790\u07ac\u078d\u07ac\u0786\u07b0\u0793\u07b0 \u0787\u07af\u078d\u07b0", +"Header 3": "\u0780\u07ac\u0791\u07a7 3", +"Blocks": "\u0784\u07b0\u078d\u07ae\u0786\u07b0\u078c\u07a6\u0787\u07b0", +"Undo": "\u0787\u07a6\u0782\u07b0\u0791\u07ab", +"Strikethrough": "\u0789\u07ac\u078b\u07aa \u0783\u07ae\u0782\u078e\u07ae", +"Bullet list": "\u0784\u07aa\u078d\u07ac\u0793\u07b0 \u078d\u07a8\u0790\u07b0\u0793\u07b0", +"Header 1": "\u0780\u07ac\u0791\u07a7 1", +"Superscript": "\u0789\u07a6\u078c\u07a9\u0787\u07a6\u0786\u07aa\u0783\u07aa", +"Clear formatting": "\u078a\u07af\u0789\u07ac\u0793\u07b0\u078c\u07a6\u0787\u07b0 \u078a\u07ae\u0780\u07ad", +"Font Sizes": "\u078a\u07ae\u0782\u07b0\u0793\u07b0 \u0790\u07a6\u0787\u07a8\u0792\u07b0", +"Subscript": "\u078c\u07a8\u0783\u07a9\u0787\u07a6\u0786\u07aa\u0783\u07aa", +"Header 6": "\u0780\u07ac\u0791\u07a7 6", +"Redo": "\u0783\u07a9\u0791\u07ab", +"Paragraph": "\u0795\u07ac\u0783\u07ac\u078e\u07b0\u0783\u07a7\u078a\u07b0", +"Ok": "\u0787\u07af\u0786\u07ad", +"Bold": "\u0784\u07af\u078d\u07b0\u0791\u07b0", +"Code": "\u0786\u07af\u0791\u07b0", +"Italic": "\u0787\u07a8\u0793\u07a6\u078d\u07a8\u0786\u07b0", +"Align center": "\u0789\u07ac\u078b\u07a6\u0781\u07b0 \u0796\u07a6\u0787\u07b0\u0790\u07a7", +"Header 5": "\u0780\u07ac\u0791\u07a7 5", +"Heading 6": "\u0780\u07ac\u0791\u07a8\u0782\u07b0 6", +"Heading 3": "\u0780\u07ac\u0791\u07a8\u0782\u07b0 3", +"Decrease indent": "\u078b\u07aa\u0783\u07aa\u0789\u07a8\u0782\u07b0 \u0786\u07aa\u0791\u07a6\u0786\u07aa\u0783\u07ad", +"Header 4": "\u0780\u07ac\u0791\u07a7 4", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "\u0795\u07ad\u0790\u07b0\u0793\u07b0 \u0786\u07aa\u0783\u07ac\u0788\u07ad\u0782\u07a9 \u0795\u07b0\u078d\u07ac\u0787\u07a8\u0782\u07b0\u0786\u07ae\u0781\u07b0! \u0784\u07a6\u078b\u07a6\u078d\u07aa \u0786\u07aa\u0783\u07ac\u0787\u07b0\u0788\u07aa\u0789\u07a6\u0781\u07b0 \u0789\u07a8 \u0787\u07ae\u0795\u07b0\u079d\u07a6\u0782\u07b0 \u0787\u07ae\u078a\u07b0 \u0786\u07ae\u0781\u07b0\u078d\u07a6\u0787\u07b0\u0788\u07a7!", +"Underline": "\u078b\u07a6\u0781\u07aa\u0783\u07ae\u0782\u078e\u07aa", +"Cancel": "\u0786\u07ac\u0782\u07b0\u0790\u07a6\u078d\u07b0", +"Justify": "\u0787\u07ac\u0787\u07b0\u0788\u07a6\u0783\u07aa \u0786\u07aa\u0783\u07ad", +"Inline": "\u0787\u07a8\u0782\u07b0\u078d\u07a6\u0787\u07a8\u0782\u07b0", +"Copy": "\u0786\u07ae\u0795\u07a9", +"Align left": "\u0788\u07a7\u078c\u07a6\u0781\u07b0 \u0796\u07a6\u0787\u07b0\u0790\u07a7", +"Visual aids": "\u0788\u07a8\u079d\u07aa\u0787\u07a6\u078d\u07b0 \u0787\u07ac\u0787\u07a8\u0791\u07b0\u0790\u07b0", +"Lower Greek": "\u078d\u07af\u0788\u07a6\u0783 \u078e\u07b0\u0783\u07a9\u0786\u07b0", +"Square": "\u078e\u07ae\u0785\u07a8", +"Default": "\u0791\u07a8\u078a\u07af\u078d\u07b0\u0793\u07b0", +"Lower Alpha": "\u078d\u07af\u0788\u07a6\u0783 \u0787\u07a6\u078d\u07b0\u078a\u07a7", +"Circle": "\u0784\u07ae\u0785\u07aa", +"Disc": "\u0788\u07a6\u0781\u07b0\u0784\u07aa\u0783\u07aa", +"Upper Alpha": "\u0787\u07a6\u0795\u07a7 \u0787\u07a6\u078d\u07b0\u078a\u07a7", +"Upper Roman": "\u0787\u07a6\u0795\u07a7 \u0783\u07af\u0789\u07a6\u0782\u07b0", +"Lower Roman": "\u078d\u07af\u0788\u07a6\u0783 \u0783\u07af\u0789\u07a6\u0782\u07b0", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "\u0787\u07a6\u0787\u07a8\u0791\u07a9 \u078a\u07ac\u0781\u07ac\u0782\u07b0\u0788\u07a7\u0782\u07a9 \u0787\u07a6\u0786\u07aa\u0783\u07a6\u0786\u07aa\u0782\u07b0\u060c \u0787\u07ad\u078e\u07ac \u078a\u07a6\u0780\u07aa\u078e\u07a6\u0787\u07a8 \u0787\u07a6\u0786\u07aa\u0783\u07aa\u078c\u07a6\u0786\u07ac\u0787\u07b0\u060c \u0782\u07a6\u0782\u07b0\u0784\u07a6\u0783\u07aa\u078c\u07a6\u0787\u07b0\u060c \u0791\u07ad\u079d\u07b0\u078c\u07a6\u0787\u07b0\u060c \u078c\u07a8\u0786\u07a8\u078c\u07a6\u0787\u07b0\u060c \u0786\u07ae\u078d\u07ae\u0782\u07b0\u078c\u07a6\u0787\u07b0 \u0782\u07aa\u0788\u07a6\u078c\u07a6 \u078b\u07a6\u0781\u07aa \u0783\u07ae\u0782\u078e\u07aa\u078c\u07a6\u0787\u07b0", +"Name": "\u0782\u07a6\u0782\u07b0", +"Anchor": "\u0787\u07ac\u0782\u07b0\u0786\u07a6\u0783", +"Id": "\u0787\u07a6\u0787\u07a8\u0791\u07a9", +"You have unsaved changes are you sure you want to navigate away?": "\u0784\u07a6\u078b\u07a6\u078d\u07aa\u078c\u07a6\u0787\u07b0 \u0790\u07ad\u0788\u07b0 \u0782\u07aa\u0786\u07ae\u0781\u07b0 \u078b\u07ab\u0786\u07ae\u0781\u07b0\u078d\u07a6\u0782\u07b0\u0788\u07a9\u078c\u07a6\u061f", +"Restore last draft": "\u078a\u07a6\u0780\u07aa\u078e\u07ac \u0791\u07b0\u0783\u07a7\u078a\u07b0\u0793\u07b0 \u0783\u07ac\u0790\u07b0\u0793\u07af \u0786\u07aa\u0783\u07ad", +"Special character": "\u079a\u07a7\u0787\u07b0\u0790\u07a6 \u0787\u07a6\u0786\u07aa\u0783\u07aa\u078c\u07a6\u0787\u07b0", +"Source code": "\u0789\u07a6\u0790\u07b0\u078b\u07a6\u0783\u07aa", +"Language": "\u0784\u07a6\u0790\u07b0", +"Insert\/Edit code sample": "\u0786\u07af\u0791\u07aa \u0789\u07a8\u0790\u07a7\u078d\u07aa \u0787\u07a8\u0782\u07b0\u0790\u07a7\u0793\u07aa\/\u0787\u07ac\u0791\u07a8\u0793\u07b0 \u0786\u07aa\u0783\u07aa\u0782\u07b0", +"B": "\u0784\u07a9", +"R": "\u0787\u07a7\u0783\u07aa", +"G": "\u0796\u07a9", +"Color": "\u0786\u07aa\u078d\u07a6", +"Right to left": "\u0786\u07a6\u0782\u07a7\u078c\u07aa\u0782\u07b0 \u0788\u07a7\u078c\u07a6\u0781\u07b0", +"Left to right": "\u0788\u07a7\u078c\u07aa\u0782\u07b0 \u0786\u07a6\u0782\u07a7\u078c\u07a6\u0781\u07b0", +"Emoticons": "\u079d\u07aa\u0787\u07ab\u0783\u07aa \u078a\u07ae\u0793\u07af", +"Robots": "\u0783\u07af\u0784\u07ae\u0793\u07b0\u0790\u07b0", +"Document properties": "\u0791\u07ae\u0786\u07a8\u0787\u07aa\u0789\u07ac\u0782\u07b0\u0793\u07b0\u078e\u07ac \u0790\u07a8\u078a\u07a6\u078c\u07a6\u0787\u07b0", +"Title": "\u0793\u07a6\u0787\u07a8\u0793\u07a6\u078d\u07b0", +"Keywords": "\u0786\u07a9\u0788\u07af\u0791\u07b0\u078c\u07a6\u0787\u07b0", +"Encoding": "\u0787\u07ac\u0782\u07b0\u0786\u07af\u0791\u07a8\u0782\u07b0", +"Description": "\u078c\u07a6\u078a\u07b0\u0790\u07a9\u078d\u07aa", +"Author": "\u0788\u07ac\u0783\u07a8\u078a\u07a6\u0783\u07a7\u078c\u07b0", +"Fullscreen": "\u078a\u07aa\u078d\u07b0\u0790\u07b0\u0786\u07b0\u0783\u07a9\u0782\u07b0", +"Horizontal line": "\u0780\u07aa\u0783\u07a6\u0790\u07b0 \u0783\u07ae\u0782\u078e\u07aa", +"Horizontal space": "\u0780\u07ae\u0783\u07a8\u0792\u07af\u0782\u07b0\u0793\u07a6\u078d\u07b0 \u0790\u07b0\u0795\u07ad\u0790\u07b0", +"Insert\/edit image": "\u078a\u07ae\u0793\u07af\u078d\u07aa\u0782\u07b0\/\u0784\u07a6\u078b\u07a6\u078d\u07aa\u0786\u07aa\u0783\u07aa\u0782\u07b0", +"General": "\u0787\u07a7\u0782\u07b0\u0789\u07aa", +"Advanced": "\u0787\u07ac\u0791\u07b0\u0788\u07a7\u0782\u07b0\u0790\u07b0\u0791\u07b0", +"Source": "\u0789\u07a6\u0790\u07b0\u078b\u07a6\u0783\u07aa", +"Border": "\u0784\u07af\u0791\u07a6\u0783\u07aa", +"Constrain proportions": "\u0788\u07a6\u0792\u07a6\u0782\u07b0 \u0780\u07a8\u078a\u07a6\u0780\u07a6\u0787\u07b0\u0793\u07a7", +"Vertical space": "\u0788\u07a7\u0793\u07a8\u0786\u07a6\u078d\u07b0 \u0790\u07b0\u0795\u07ad\u0790\u07b0", +"Image description": "\u078a\u07ae\u0793\u07af\u078e\u07ac \u078c\u07a6\u078a\u07b0\u0790\u07a9\u078d\u07aa", +"Style": "\u0790\u07b0\u0793\u07a6\u0787\u07a8\u078d\u07b0", +"Dimensions": "\u0789\u07a8\u0782\u07b0\u078c\u07a6\u0787\u07b0", +"Insert image": "\u078a\u07ae\u0793\u07af \u0787\u07a8\u0782\u07b0\u0790\u07a7\u0793\u07b0 \u0786\u07aa\u0783\u07ad", +"Image": "\u078a\u07ae\u0793\u07af", +"Zoom in": "\u0784\u07ae\u0791\u07aa\u0786\u07aa\u0783\u07ad", +"Contrast": "\u078c\u07a6\u078a\u07a7\u078c\u07aa\u0786\u07a6\u0782\u07b0", +"Back": "\u078a\u07a6\u0780\u07a6\u078c\u07a6\u0781\u07b0", +"Gamma": "\u078e\u07ad\u0789\u07a7", +"Flip horizontally": "\u0780\u07aa\u0783\u07a6\u0780\u07a6\u0781\u07b0\u0788\u07a7\u078e\u07ae\u078c\u07a6\u0781\u07b0 \u078a\u07aa\u0781\u07aa\u0782\u07b0\u0796\u07a6\u0780\u07a7", +"Resize": "\u0790\u07a6\u0787\u07a8\u0792\u07aa\u0784\u07a6\u078b\u07a6\u078d\u07aa\u0786\u07aa\u0783\u07aa\u0782\u07b0", +"Sharpen": "\u078c\u07ab\u0782\u07aa\u0786\u07a6\u0782\u07b0", +"Zoom out": "\u0786\u07aa\u0791\u07a6\u0786\u07aa\u0783\u07ad", +"Image options": "\u078a\u07ae\u0793\u07af \u0787\u07ae\u0795\u07b0\u079d\u07a6\u0782\u07b0\u078c\u07a6\u0787\u07b0", +"Apply": "\u0787\u07ac\u0795\u07b0\u078d\u07a6\u0787\u07a8\u0786\u07aa\u0783\u07ad", +"Brightness": "\u0787\u07a6\u078d\u07a8\u0789\u07a8\u0782\u07b0", +"Rotate clockwise": "\u0786\u07a6\u0782\u07a7\u078c\u07a6\u0781\u07b0 \u0787\u07a6\u0782\u0784\u07aa\u0783\u07a7", +"Rotate counterclockwise": "\u0788\u07a7\u078c\u07a6\u0781\u07b0 \u0787\u07a6\u0782\u0784\u07aa\u0783\u07a7", +"Edit image": "\u078a\u07ae\u0793\u07af \u0787\u07ac\u0791\u07a8\u0793\u07b0\u0786\u07aa\u07aa\u0783\u07aa\u0782\u07b0", +"Color levels": "\u0786\u07aa\u078d\u07a6\u0787\u07a8\u078e\u07ac \u078d\u07ac\u0788\u07ac\u078d\u07b0\u078c\u07a6\u0787\u07b0", +"Crop": "\u0786\u07b0\u0783\u07ae\u0795\u07b0\u0786\u07aa\u0783\u07aa\u0782\u07b0", +"Orientation": "\u0787\u07ae\u0783\u07a8\u0787\u07ac\u0782\u07b0\u0793\u07ad\u079d\u07a6\u0782\u07b0", +"Flip vertically": "\u0789\u07a6\u078c\u07a8\u0782\u07b0\u078c\u07a8\u0783\u07a8\u0787\u07a6\u0781\u07b0\u0788\u07a7\u078e\u07ae\u078c\u07a6\u0781\u07b0 \u078a\u07aa\u0781\u07aa\u0782\u07b0\u0796\u07a6\u0780\u07a7", +"Invert": "\u0787\u07a8\u0782\u07b0\u0788\u07a7\u0793\u07aa", +"Date\/time": "\u078c\u07a7\u0783\u07a9\u079a\u07b0\/\u0788\u07a6\u078e\u07aa\u078c\u07aa", +"Insert date\/time": "\u0788\u07a6\u078e\u07aa\u078c\u07aa\/\u078c\u07a7\u0783\u07a9\u079a\u07b0 \u078d\u07aa\u0782\u07b0", +"Remove link": "\u078d\u07a8\u0782\u07b0\u0786\u07b0 \u078a\u07ae\u0780\u07ad", +"Url": "\u0794\u07ab.\u0787\u07a7\u0783\u07b0.\u0787\u07ac\u078d\u07b0", +"Text to display": "\u078b\u07a6\u0787\u07b0\u0786\u07a6\u0782\u07b0\u0788\u07a9 \u0787\u07a8\u0784\u07a7\u0783\u07a7\u078c\u07b0", +"Anchors": "\u0787\u07ac\u0782\u07b0\u0786\u07a6\u0783\u078c\u07a6\u0787\u07b0", +"Insert link": "\u078d\u07a8\u0782\u07b0\u0786\u07b0 \u078d\u07aa\u0782\u07b0", +"Link": "\u078d\u07a8\u0782\u07b0\u0786\u07aa", +"New window": "\u0787\u07a7 \u0788\u07a8\u0782\u07b0\u0791\u07af\u0787\u07a6\u0786\u07a6\u0781\u07b0", +"None": "\u0782\u07ae\u0782\u07b0", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "\u078c\u07a8\u0794\u07a6 \u078d\u07a8\u0794\u07aa\u0787\u07b0\u0788\u07a9 \u0787\u07ac\u0780\u07ac\u0782\u07b0 \u0790\u07a6\u0787\u07a8\u0793\u07ac\u0787\u07b0\u078e\u07ac \u078d\u07a8\u0782\u07b0\u0786\u07ac\u0787\u07b0\u0786\u07a6\u0789\u07aa\u0782\u07b0 \u0787\u07ac\u0797\u07b0.\u0793\u07a9.\u0793\u07a9.\u0795\u07a9 \u0786\u07aa\u0783\u07a8\u0787\u07a6\u0781\u07b0 \u0787\u07a8\u078c\u07aa\u0783\u07aa \u0786\u07aa\u0783\u07a6\u0782\u07b0\u078c\u07af\u061f", +"Paste or type a link": "\u078d\u07a8\u0782\u07b0\u0786\u07aa \u078d\u07a8\u0794\u07aa\u0787\u07b0\u0788\u07a7 \u0782\u07aa\u0788\u07a6\u078c\u07a6 \u0795\u07ad\u0790\u07b0\u0793\u07b0 \u0786\u07aa\u0783\u07a6\u0787\u07b0\u0788\u07a7", +"Target": "\u0793\u07a7\u078e\u07ac\u0793\u07b0", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "\u0789\u07ac\u0787\u07a8\u078d\u07b0\u0793\u07ab - \u0786\u07aa\u0783\u07a8\u0787\u07a6\u0781\u07b0 \u0787\u07a8\u078c\u07aa\u0783\u07aa\u0786\u07aa\u0783\u07a6\u0787\u07b0\u0788\u07a6\u0782\u07b0 \u0784\u07ad\u0782\u07aa\u0782\u07b0\u078a\u07aa\u0785\u07aa\u078c\u07af\u061f", +"Insert\/edit link": "\u078d\u07a8\u0782\u07b0\u0786\u07b0 \u078d\u07aa\u0782\u07b0\/\u0784\u07a6\u078b\u07a6\u078d\u07aa \u078e\u07ac\u0782\u07a6\u0787\u07aa\u0782\u07b0", +"Insert\/edit video": "\u0788\u07a9\u0791\u07a8\u0787\u07af \u078d\u07aa\u0782\u07b0\/\u0784\u07a6\u078b\u07a6\u078d\u07aa \u078e\u07ac\u0782\u07a6\u0787\u07aa\u0782\u07b0", +"Media": "\u0789\u07a9\u0791\u07a8\u0787\u07a7", +"Alternative source": "\u0787\u07a6\u078d\u07b0\u0793\u07a6\u0782\u07ad\u0793\u07a8\u0788\u07b0 \u0790\u07af\u0790\u07b0", +"Paste your embed code below:": "\u0787\u07ac\u0789\u07b0\u0784\u07ac\u0791\u07b0 \u0786\u07af\u0791\u07b0 \u078c\u07a8\u0783\u07a9\u078e\u07a6\u0787\u07a8 \u0795\u07ad\u0790\u07b0\u0793\u07b0 \u0786\u07aa\u0783\u07ad", +"Insert video": "\u0788\u07a9\u0791\u07a8\u0787\u07af \u078d\u07aa\u0782\u07b0", +"Poster": "\u0795\u07af\u0790\u07b0\u0793\u07a6\u0783", +"Insert\/edit media": "\u0787\u07a8\u0782\u07b0\u0790\u07a7\u0793\u07b0\/\u0787\u07ac\u0791\u07a8\u0793\u07b0 \u0789\u07a9\u0791\u07a8\u0787\u07a7", +"Embed": "\u0787\u07ac\u0789\u07b0\u0784\u07ac\u0791\u07b0", +"Nonbreaking space": "\u0782\u07ae\u0782\u07b0 \u0784\u07b0\u0783\u07ad\u0786\u07a8\u0782\u07b0 \u0790\u07b0\u0795\u07ad\u0790\u07b0", +"Page break": "\u0795\u07ad\u0796\u07b0 \u0784\u07b0\u0783\u07ad\u0786\u07b0", +"Paste as text": "\u0793\u07ac\u0786\u07b0\u0790\u07b0\u0793\u07b0 \u078e\u07ae\u078c\u07a6\u0781\u07b0 \u0795\u07ad\u0790\u07b0\u0793\u07b0 \u0786\u07aa\u0783\u07ad", +"Preview": "\u0795\u07b0\u0783\u07a9\u0788\u07a8\u0787\u07aa", +"Print": "\u0795\u07b0\u0783\u07a8\u0782\u07b0\u0793\u07b0 \u0786\u07aa\u0783\u07ad", +"Save": "\u0790\u07ad\u0788\u07b0 \u0786\u07aa\u0783\u07ad", +"Could not find the specified string.": "\u078c\u07a8\u0794\u07a6 \u0780\u07af\u0787\u07b0\u078b\u07a6\u0788\u07a7 \u078d\u07a6\u078a\u07aa\u0792\u07ac\u0787\u07b0 \u0782\u07aa\u078a\u07ac\u0782\u07aa\u0782\u07aa", +"Replace": "\u0784\u07a6\u078b\u07a6\u078d\u07aa \u0786\u07aa\u0783\u07ad", +"Next": "\u078a\u07a6\u0780\u07a6\u078c\u07a6\u0781\u07b0", +"Whole words": "\u0784\u07a6\u0790\u07b0\u078c\u07a6\u0787\u07b0 \u0787\u07ac\u0787\u07b0\u0786\u07ae\u0781\u07b0", +"Find and replace": "\u0780\u07af\u078b\u07aa\u0789\u07a6\u0781\u07b0\u078a\u07a6\u0780\u07aa \u0784\u07a6\u078b\u07a6\u078d\u07aa \u0786\u07aa\u0783\u07aa\u0782\u07b0", +"Replace with": "\u0784\u07a6\u078b\u07a6\u078d\u07aa\u078e\u07a6\u0787\u07a8 \u0784\u07ad\u0782\u07aa\u0782\u07b0 \u0786\u07aa\u0783\u07a7\u0782\u07a9", +"Find": "\u0780\u07af\u078b\u07a7", +"Replace all": "\u0780\u07aa\u0783\u07a8\u0780\u07a7 \u0787\u07ac\u0787\u07b0\u0797\u07ac\u0787\u07b0 \u0784\u07a6\u078b\u07a6\u078d\u07aa \u0786\u07aa\u0783\u07ad", +"Match case": "\u0786\u07ad\u0790\u07b0 \u0787\u07a6\u0781\u07b0 \u0784\u07a6\u078d\u07a7", +"Prev": "\u0786\u07aa\u0783\u07a8\u0787\u07a6\u0781\u07b0", +"Spellcheck": "\u0786\u07aa\u0781\u07b0 \u0780\u07af\u078b\u07a7", +"Finish": "\u0782\u07a8\u0782\u07b0\u0789\u07a7", +"Ignore all": "\u0780\u07aa\u0783\u07a8\u0780\u07a7 \u0787\u07ac\u0787\u07b0\u0797\u07ac\u0787\u07b0 \u078b\u07ab\u0786\u07ae\u0781\u07b0\u078d\u07a7", +"Ignore": "\u078b\u07ab\u0786\u07ae\u0781\u07b0\u078d\u07a7", +"Add to Dictionary": "\u0783\u07a6\u078b\u07a9\u078a\u07a6\u0781\u07b0 \u0787\u07a8\u078c\u07aa\u0783\u07aa\u0786\u07aa\u0783\u07ad", +"Insert row before": "\u0786\u07aa\u0783\u07a8\u0787\u07a6\u0781\u07b0 \u0783\u07af\u0787\u07ac\u0787\u07b0 \u0787\u07a8\u078c\u07aa\u0783\u07aa \u0786\u07aa\u0783\u07ad", +"Rows": "\u0783\u07af", +"Height": "\u078b\u07a8\u078e\u07aa\u0789\u07a8\u0782\u07b0", +"Paste row after": "\u078a\u07a6\u0780\u07a6\u078c\u07a6\u0781\u07b0 \u0783\u07af \u0795\u07ad\u0790\u07b0\u0793\u07b0 \u0786\u07aa\u0783\u07ad", +"Alignment": "\u0787\u07ac\u078d\u07a6\u0787\u07a8\u0782\u07b0\u0789\u07ac\u0782\u07b0\u0793\u07b0", +"Border color": "\u0784\u07af\u0791\u07a6\u0783\u07aa \u0786\u07aa\u078d\u07a6", +"Column group": "\u0786\u07ae\u078d\u07a6\u0789\u07b0 \u078e\u07b0\u0783\u07ab\u0795\u07b0", +"Row": "\u0783\u07af", +"Insert column before": "\u0786\u07aa\u0783\u07a8\u0787\u07a6\u0781\u07b0 \u0786\u07ae\u078d\u07a6\u0789\u07ac\u0787\u07b0 \u0787\u07a8\u078c\u07aa\u0783\u07aa \u0786\u07aa\u0783\u07ad", +"Split cell": "\u0790\u07ac\u078d\u07b0 \u0788\u07a6\u0786\u07a8\u0786\u07aa\u0783\u07ad", +"Cell padding": "\u0790\u07ac\u078d\u07b0 \u0795\u07ac\u0791\u07a8\u0782\u07b0", +"Cell spacing": "\u0790\u07ac\u078d\u07b0 \u0790\u07b0\u0795\u07ad\u0790\u07a8\u0782\u07b0\u078e", +"Row type": "\u0783\u07af\u078e\u07ac \u0788\u07a6\u0787\u07b0\u078c\u07a6\u0783\u07aa", +"Insert table": "\u0793\u07ad\u0784\u07a6\u078d\u07b0 \u078d\u07aa\u0782\u07b0", +"Body": "\u0784\u07ae\u0791\u07a9", +"Caption": "\u0786\u07ac\u0795\u07b0\u079d\u07a6\u0782\u07b0", +"Footer": "\u078a\u07ab\u0793\u07a6\u0783", +"Delete row": "\u0783\u07af \u078a\u07ae\u0780\u07ad", +"Paste row before": "\u0786\u07aa\u0783\u07a8\u0787\u07a6\u0781\u07b0 \u0783\u07af \u0795\u07ad\u0790\u07b0\u0793\u07b0 \u0786\u07aa\u0783\u07ad", +"Scope": "\u0790\u07b0\u0786\u07af\u0795\u07b0", +"Delete table": "\u0793\u07ad\u0784\u07a6\u078d\u07b0 \u078a\u07ae\u0780\u07ad", +"H Align": "\u0780\u07aa\u0783\u07a6\u0790\u07b0 \u0787\u07ac\u078d\u07a6\u0787\u07a8\u0782\u07b0", +"Top": "\u0789\u07a6\u078c\u07a8", +"Header cell": "\u0780\u07ac\u0791\u07a7 \u0790\u07ac\u078d\u07b0", +"Column": "\u0786\u07ae\u078d\u07a6\u0789\u07b0", +"Row group": "\u0783\u07af \u078e\u07b0\u0783\u07ab\u0795\u07b0", +"Cell": "\u0790\u07ac\u078d\u07b0", +"Middle": "\u0789\u07ac\u078b\u07aa", +"Cell type": "\u0790\u07ac\u078d\u07b0\u078e\u07ac \u0788\u07a6\u0787\u07b0\u078c\u07a6\u0783\u07aa", +"Copy row": "\u0783\u07af \u0786\u07ae\u0795\u07a9\u0786\u07aa\u0783\u07ad", +"Row properties": "\u0783\u07af\u078e\u07ac \u0790\u07a8\u078a\u07a6\u078c\u07a6\u0787\u07b0", +"Table properties": "\u0793\u07ad\u0784\u07a6\u078d\u07b0\u078e\u07ac \u0790\u07a8\u078a\u07a6\u078c\u07a6\u0787\u07b0", +"Bottom": "\u078c\u07a8\u0783\u07a8", +"V Align": "\u078b\u07a8\u078e\u07a6\u0781\u07b0 \u0787\u07ac\u078d\u07a6\u0787\u07a8\u0782\u07b0", +"Header": "\u0780\u07ac\u0791\u07a7", +"Right": "\u0786\u07a6\u0782\u07a7\u078c\u07a6\u0781\u07b0", +"Insert column after": "\u078a\u07a6\u0780\u07a6\u078c\u07a6\u0781\u07b0 \u0786\u07ae\u078d\u07a6\u0789\u07ac\u0787\u07b0 \u0787\u07a8\u078c\u07aa\u0783\u07aa \u0786\u07aa\u0783\u07ad", +"Cols": "\u0786\u07ae\u078d\u07a6\u0789\u07b0", +"Insert row after": "\u078a\u07a6\u0780\u07a6\u078c\u07a6\u0781\u07b0 \u0783\u07af\u0787\u07ac\u0787\u07b0 \u0787\u07a8\u078c\u07aa\u0783\u07aa \u0786\u07aa\u0783\u07ad", +"Width": "\u078a\u07aa\u0785\u07a7\u0789\u07a8\u0782\u07b0", +"Cell properties": "\u0790\u07ac\u078d\u07b0\u078e\u07ac \u0790\u07a8\u078a\u07a6\u078c\u07a6\u0787\u07b0", +"Left": "\u0788\u07a7\u078c\u07a6\u0781\u07b0", +"Cut row": "\u0783\u07af \u0786\u07a6\u0793\u07b0\u0786\u07aa\u0783\u07ad", +"Delete column": "\u0786\u07ae\u078d\u07a6\u0789\u07b0 \u078a\u07ae\u0780\u07ad", +"Center": "\u0789\u07ac\u078b\u07a6\u0781\u07b0", +"Merge cells": "\u0790\u07ac\u078d\u07b0 \u0787\u07ac\u0787\u07b0\u0786\u07aa\u0783\u07ad", +"Insert template": "\u0793\u07ac\u0789\u07b0\u0795\u07b0\u078d\u07ad\u0793\u07b0 \u0787\u07a8\u0782\u07b0\u0790\u07a7\u0793\u07b0 \u0786\u07aa\u0783\u07aa\u0782\u07b0", +"Templates": "\u0793\u07ac\u0789\u07b0\u0795\u07b0\u078d\u07ad\u0793\u07b0\u078c\u07a6\u0787\u07b0", +"Background color": "\u0784\u07ac\u0786\u07b0\u078e\u07b0\u0783\u07a6\u0787\u07aa\u0782\u07b0\u0791\u07b0\u078e\u07ac \u0786\u07aa\u078d\u07a6", +"Custom...": "\u0787\u07a6\u0789\u07a8\u0787\u07b0\u078d\u07a6", +"Custom color": "\u0787\u07a6\u0789\u07a8\u0787\u07b0\u078d\u07a6 \u0786\u07aa\u078d\u07a6", +"No color": "\u0786\u07aa\u078d\u07a6 \u0782\u07aa\u0796\u07a6\u0787\u07b0\u0790\u07a7", +"Text color": "\u0787\u07a6\u0786\u07aa\u0783\u07aa\u078e\u07ac \u0786\u07aa\u078d\u07a6", +"Table of Contents": "\u0780\u07a8\u0789\u07ac\u0782\u07ad \u0784\u07a6\u0787\u07a8\u078c\u07a6\u0787\u07b0", +"Show blocks": "\u0784\u07b0\u078d\u07ae\u0786\u07b0\u078c\u07a6\u0787\u07b0 \u078b\u07a6\u0787\u07b0\u0786\u07a7", +"Show invisible characters": "\u0782\u07aa\u078a\u07ac\u0782\u07b0\u0782\u07a6 \u0787\u07a6\u0786\u07aa\u0783\u07aa\u078c\u07a6\u0787\u07b0 \u078b\u07a6\u0787\u07b0\u0786\u07a7", +"Words: {0}": "\u0784\u07a6\u0790\u07b0: {0}", +"Insert": "\u0787\u07a8\u0782\u07b0\u0790\u07a7\u0793\u07b0", +"File": "\u078a\u07a6\u0787\u07a8\u078d\u07b0", +"Edit": "\u0784\u07a6\u078b\u07a6\u078d\u07aa \u078e\u07ac\u0782\u07a6\u0787\u07aa\u0782\u07b0", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "\u0783\u07a8\u0797\u07b0 \u0793\u07ac\u0786\u07b0\u0790\u07b0\u0793\u07b0 \u0787\u07ad\u0783\u07a8\u0787\u07a7. \u0789\u07ac\u0782\u07ab \u0780\u07af\u078b\u07aa\u0789\u07a6\u0781\u07b0 ALT-F9. \u0793\u07ab\u078d\u07b0\u0784\u07a6\u0783 \u0780\u07af\u078b\u07aa\u0789\u07a6\u0781\u07b0 ALT-F10. \u0787\u07ac\u0780\u07a9 \u0780\u07af\u078b\u07aa\u0789\u07a6\u0781\u07b0 ALT-0", +"Tools": "\u0793\u07ab\u078d\u07b0\u078c\u07a6\u0787\u07b0", +"View": "\u0788\u07a8\u0787\u07aa", +"Table": "\u0793\u07ad\u0784\u07a6\u078d\u07b0", +"Format": "\u078a\u07af\u0789\u07ac\u0793\u07b0" +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/el.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/el.js new file mode 100644 index 0000000000..b5f840da87 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/el.js @@ -0,0 +1,261 @@ +tinymce.addI18n('el',{ +"Redo": "\u0395\u03c0\u03b1\u03bd\u03ac\u03bb\u03b7\u03c8\u03b7", +"Undo": "\u0391\u03bd\u03b1\u03af\u03c1\u03b5\u03c3\u03b7", +"Cut": "\u0391\u03c0\u03bf\u03ba\u03bf\u03c0\u03ae", +"Copy": "\u0391\u03bd\u03c4\u03b9\u03b3\u03c1\u03b1\u03c6\u03ae", +"Paste": "\u0395\u03c0\u03b9\u03ba\u03cc\u03bb\u03bb\u03b7\u03c3\u03b7", +"Select all": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03cc\u03bb\u03c9\u03bd", +"New document": "\u039d\u03ad\u03bf \u03ad\u03b3\u03b3\u03c1\u03b1\u03c6\u03bf", +"Ok": "\u0395\u03bd\u03c4\u03ac\u03be\u03b5\u03b9", +"Cancel": "\u0391\u03ba\u03cd\u03c1\u03c9\u03c3\u03b7", +"Visual aids": "O\u03c0\u03c4\u03b9\u03ba\u03ac \u03b2\u03bf\u03b7\u03b8\u03ae\u03bc\u03b1\u03c4\u03b1 ", +"Bold": "\u0388\u03bd\u03c4\u03bf\u03bd\u03b7", +"Italic": "\u03a0\u03bb\u03ac\u03b3\u03b9\u03b1", +"Underline": "\u03a5\u03c0\u03bf\u03b3\u03c1\u03ac\u03bc\u03bc\u03b9\u03c3\u03b7", +"Strikethrough": "\u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03ae \u03b4\u03b9\u03b1\u03b3\u03c1\u03b1\u03c6\u03ae", +"Superscript": "\u0395\u03ba\u03b8\u03ad\u03c4\u03b7\u03c2", +"Subscript": "\u0394\u03b5\u03af\u03ba\u03c4\u03b7\u03c2", +"Clear formatting": "\u0391\u03c0\u03b1\u03bb\u03bf\u03b9\u03c6\u03ae \u03bc\u03bf\u03c1\u03c6\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7\u03c2", +"Align left": "\u03a3\u03c4\u03bf\u03af\u03c7\u03b9\u03c3\u03b7 \u03b1\u03c1\u03b9\u03c3\u03c4\u03b5\u03c1\u03ac", +"Align center": "\u03a3\u03c4\u03bf\u03af\u03c7\u03b9\u03c3\u03b7 \u03c3\u03c4\u03bf \u03ba\u03ad\u03bd\u03c4\u03c1\u03bf", +"Align right": "\u03a3\u03c4\u03bf\u03af\u03c7\u03b9\u03c3\u03b7 \u03b4\u03b5\u03be\u03b9\u03ac", +"Justify": "\u03a0\u03bb\u03ae\u03c1\u03b7\u03c2 \u03c3\u03c4\u03bf\u03af\u03c7\u03b9\u03c3\u03b7", +"Bullet list": "\u039b\u03af\u03c3\u03c4\u03b1 \u03bc\u03b5 \u03ba\u03bf\u03c5\u03ba\u03ba\u03af\u03b4\u03b5\u03c2", +"Numbered list": "\u0391\u03c1\u03b9\u03b8\u03bc\u03b7\u03bc\u03ad\u03bd\u03b7 \u03bb\u03af\u03c3\u03c4\u03b1", +"Decrease indent": "\u039c\u03b5\u03af\u03c9\u03c3\u03b7 \u03b5\u03c3\u03bf\u03c7\u03ae\u03c2", +"Increase indent": "\u0391\u03cd\u03be\u03b7\u03c3\u03b7 \u03b5\u03c3\u03bf\u03c7\u03ae\u03c2", +"Close": "\u039a\u03bb\u03b5\u03af\u03c3\u03b9\u03bc\u03bf", +"Formats": "\u039c\u03bf\u03c1\u03c6\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "\u039f \u03c0\u03b5\u03c1\u03b9\u03b7\u03b3\u03b7\u03c4\u03ae\u03c2 \u03c3\u03b1\u03c2 \u03b4\u03b5\u03bd \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03b5\u03b9 \u03ac\u03bc\u03b5\u03c3\u03b7 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7 \u03c3\u03c4\u03bf \u03c0\u03c1\u03cc\u03c7\u03b5\u03b9\u03c1\u03bf. \u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03ce \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c3\u03c5\u03bd\u03c4\u03bf\u03bc\u03b5\u03cd\u03c3\u03b5\u03b9\u03c2 \u03c0\u03bb\u03b7\u03ba\u03c4\u03c1\u03bf\u03bb\u03bf\u03b3\u03af\u03bf\u03c5 Ctrl+X\/C\/V.", +"Headers": "\u039a\u03b5\u03c6\u03b1\u03bb\u03af\u03b4\u03b5\u03c2", +"Header 1": "\u039a\u03b5\u03c6\u03b1\u03bb\u03af\u03b4\u03b1 1", +"Header 2": "\u039a\u03b5\u03c6\u03b1\u03bb\u03af\u03b4\u03b1 2", +"Header 3": "\u039a\u03b5\u03c6\u03b1\u03bb\u03af\u03b4\u03b1 3", +"Header 4": "\u039a\u03b5\u03c6\u03b1\u03bb\u03af\u03b4\u03b1 4", +"Header 5": "\u039a\u03b5\u03c6\u03b1\u03bb\u03af\u03b4\u03b1 5", +"Header 6": "\u039a\u03b5\u03c6\u03b1\u03bb\u03af\u03b4\u03b1 6", +"Headings": "\u039a\u03b5\u03c6\u03b1\u03bb\u03af\u03b4\u03b5\u03c2", +"Heading 1": "\u039a\u03b5\u03c6\u03b1\u03bb\u03af\u03b4\u03b1 1", +"Heading 2": "\u039a\u03b5\u03c6\u03b1\u03bb\u03af\u03b4\u03b1 2", +"Heading 3": "\u039a\u03b5\u03c6\u03b1\u03bb\u03af\u03b4\u03b1 3", +"Heading 4": "\u039a\u03b5\u03c6\u03b1\u03bb\u03af\u03b4\u03b1 4", +"Heading 5": "\u039a\u03b5\u03c6\u03b1\u03bb\u03af\u03b4\u03b1 5", +"Heading 6": "\u039a\u03b5\u03c6\u03b1\u03bb\u03af\u03b4\u03b1 6", +"Preformatted": "\u03a0\u03c1\u03bf\u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03bc\u03ad\u03bd\u03bf", +"Div": "Div", +"Pre": "Pre", +"Code": "\u039a\u03ce\u03b4\u03b9\u03ba\u03b1\u03c2", +"Paragraph": "\u03a0\u03b1\u03c1\u03ac\u03b3\u03c1\u03b1\u03c6\u03bf\u03c2", +"Blockquote": "\u03a0\u03b5\u03c1\u03b9\u03bf\u03c7\u03ae \u03c0\u03b1\u03c1\u03ac\u03b8\u03b5\u03c3\u03b7\u03c2", +"Inline": "\u0395\u03bd\u03c3\u03c9\u03bc\u03b1\u03c4\u03c9\u03bc\u03ad\u03bd\u03b7", +"Blocks": "\u03a4\u03bc\u03ae\u03bc\u03b1\u03c4\u03b1", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "\u0397 \u03b5\u03c0\u03b9\u03ba\u03cc\u03bb\u03bb\u03b7\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03c4\u03ce\u03c1\u03b1 \u03c3\u03b5 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03b1\u03c0\u03bb\u03bf\u03cd \u03ba\u03b5\u03b9\u03bc\u03ad\u03bd\u03bf\u03c5. \u03a4\u03b1 \u03c0\u03b5\u03c1\u03b9\u03b5\u03c7\u03cc\u03bc\u03b5\u03bd\u03b1 \u03bc\u03b9\u03b1\u03c2 \u03b5\u03c0\u03b9\u03ba\u03cc\u03bb\u03bb\u03b7\u03c3\u03b7\u03c2 \u03b8\u03b1 \u03b5\u03c0\u03b9\u03ba\u03bf\u03bb\u03bb\u03bf\u03cd\u03bd\u03c4\u03b1\u03b9 \u03c9\u03c2 \u03b1\u03c0\u03bb\u03cc \u03ba\u03b5\u03af\u03bc\u03b5\u03bd\u03bf \u03cc\u03c3\u03bf \u03b7 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03b1\u03c5\u03c4\u03ae \u03c0\u03b1\u03c1\u03b1\u03bc\u03ad\u03bd\u03b5\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03ae.", +"Font Family": "\u0393\u03c1\u03b1\u03bc\u03bc\u03b1\u03c4\u03bf\u03c3\u03b5\u03b9\u03c1\u03ac", +"Font Sizes": "\u039c\u03ad\u03b3\u03b5\u03b8\u03bf\u03c2", +"Class": "\u039a\u03bb\u03ac\u03c3\u03b7", +"Browse for an image": "\u0391\u03bd\u03b1\u03b6\u03b7\u03c4\u03ae\u03c3\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03b5\u03b9\u03ba\u03cc\u03bd\u03b1", +"OR": "\u0389", +"Drop an image here": "\u03a1\u03af\u03be\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03b5\u03b9\u03ba\u03cc\u03bd\u03b1 \u03b5\u03b4\u03ce", +"Upload": "\u039c\u03b5\u03c4\u03b1\u03c6\u03cc\u03c1\u03c4\u03c9\u03c3\u03b7", +"Block": "\u03a4\u03bc\u03ae\u03bc\u03b1", +"Align": "\u03a3\u03c4\u03bf\u03af\u03c7\u03b9\u03c3\u03b7", +"Default": "\u03a0\u03c1\u03bf\u03ba\u03b1\u03b8\u03bf\u03c1\u03b9\u03c3\u03bc\u03ad\u03bd\u03bf", +"Circle": "\u039a\u03cd\u03ba\u03bb\u03bf\u03c2", +"Disc": "\u0394\u03af\u03c3\u03ba\u03bf\u03c2", +"Square": "\u03a4\u03b5\u03c4\u03c1\u03ac\u03b3\u03c9\u03bd\u03bf", +"Lower Alpha": "\u03a0\u03b5\u03b6\u03ac \u03bb\u03b1\u03c4\u03b9\u03bd\u03b9\u03ba\u03ac", +"Lower Greek": "\u03a0\u03b5\u03b6\u03ac \u03b5\u03bb\u03bb\u03b7\u03bd\u03b9\u03ba\u03ac", +"Lower Roman": "\u03a0\u03b5\u03b6\u03ac \u03c1\u03c9\u03bc\u03b1\u03ca\u03ba\u03ac", +"Upper Alpha": "\u039a\u03b5\u03c6\u03b1\u03bb\u03b1\u03af\u03b1 \u03bb\u03b1\u03c4\u03b9\u03bd\u03b9\u03ba\u03ac", +"Upper Roman": "\u039a\u03b5\u03c6\u03b1\u03bb\u03b1\u03af\u03b1 \u03c1\u03c9\u03bc\u03b1\u03ca\u03ba\u03ac", +"Anchor": "\u0391\u03b3\u03ba\u03cd\u03c1\u03c9\u03c3\u03b7", +"Name": "\u038c\u03bd\u03bf\u03bc\u03b1", +"Id": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "\u039f \u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b1\u03c1\u03c7\u03af\u03b6\u03b5\u03b9 \u03bc\u03b5 \u03ad\u03bd\u03b1 \u03b3\u03c1\u03ac\u03bc\u03bc\u03b1, \u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03bf\u03cd\u03bc\u03b5\u03bd\u03bf \u03bc\u03cc\u03bd\u03bf \u03b1\u03c0\u03cc \u03b3\u03c1\u03ac\u03bc\u03bc\u03b1\u03c4\u03b1, \u03b1\u03c1\u03b9\u03b8\u03bc\u03bf\u03cd\u03c2, \u03c0\u03b1\u03cd\u03bb\u03b5\u03c2, \u03c4\u03b5\u03bb\u03b5\u03af\u03b5\u03c2, \u03ac\u03bd\u03c9 \u03c4\u03b5\u03bb\u03b5\u03af\u03b1 \u03ae \u03c5\u03c0\u03bf\u03b3\u03c1\u03b1\u03bc\u03bc\u03af\u03c3\u03b5\u03b9\u03c2.", +"You have unsaved changes are you sure you want to navigate away?": "\u0388\u03c7\u03b5\u03c4\u03b5 \u03bc\u03b7 \u03b1\u03c0\u03bf\u03b8\u03b7\u03ba\u03b5\u03c5\u03bc\u03ad\u03bd\u03b5\u03c2 \u03b1\u03bb\u03bb\u03b1\u03b3\u03ad\u03c2. \u0395\u03af\u03c3\u03c4\u03b5 \u03b2\u03ad\u03b2\u03b1\u03b9\u03bf\u03b9 \u03cc\u03c4\u03b9 \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c6\u03cd\u03b3\u03b5\u03c4\u03b5 \u03b1\u03c0\u03cc \u03c4\u03b7\u03bd \u03c3\u03b5\u03bb\u03af\u03b4\u03b1;", +"Restore last draft": "\u0395\u03c0\u03b1\u03bd\u03b1\u03c6\u03bf\u03c1\u03ac \u03c4\u03b5\u03bb\u03b5\u03c5\u03c4\u03b1\u03af\u03bf\u03c5 \u03c3\u03c7\u03b5\u03b4\u03af\u03bf\u03c5", +"Special character": "\u0395\u03b9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c7\u03b1\u03c1\u03b1\u03ba\u03c4\u03ae\u03c1\u03b1\u03c2", +"Source code": "\u03a0\u03b7\u03b3\u03b1\u03af\u03bf\u03c2 \u03ba\u03ce\u03b4\u03b9\u03ba\u03b1\u03c2", +"Insert\/Edit code sample": "\u0395\u03b9\u03c3\u03b1\u03b3\u03c9\u03b3\u03ae\/\u0395\u03c0\u03b5\u03be\u03b5\u03c1\u03b3\u03b1\u03c3\u03af\u03b1 \u03b4\u03b5\u03af\u03b3\u03bc\u03b1\u03c4\u03bf\u03c2 \u03ba\u03ce\u03b4\u03b9\u03ba\u03b1", +"Language": "\u0393\u03bb\u03ce\u03c3\u03c3\u03b1", +"Code sample": "\u0394\u03b5\u03af\u03b3\u03bc\u03b1 \u039a\u03ce\u03b4\u03b9\u03ba\u03b1", +"Color": "\u03a7\u03c1\u03ce\u03bc\u03b1", +"R": "\u03ba", +"G": "\u03a0", +"B": "\u039c", +"Left to right": "\u0391\u03c0\u03cc \u03b1\u03c1\u03b9\u03c3\u03c4\u03b5\u03c1\u03ac \u03c0\u03c1\u03bf\u03c2 \u03c4\u03b1 \u03b4\u03b5\u03be\u03b9\u03ac", +"Right to left": "\u0391\u03c0\u03cc \u03b4\u03b5\u03be\u03b9\u03ac \u03c0\u03c1\u03bf\u03c2 \u03c4\u03b1 \u03b1\u03c1\u03b9\u03c3\u03c4\u03b5\u03c1\u03ac", +"Emoticons": "\u03a6\u03b1\u03c4\u03c3\u03bf\u03cd\u03bb\u03b5\u03c2", +"Document properties": "\u0399\u03b4\u03b9\u03cc\u03c4\u03b7\u03c4\u03b5\u03c2 \u03b5\u03b3\u03b3\u03c1\u03ac\u03c6\u03bf\u03c5", +"Title": "\u03a4\u03af\u03c4\u03bb\u03bf\u03c2", +"Keywords": "\u039b\u03ad\u03be\u03b5\u03b9\u03c2 \u03ba\u03bb\u03b5\u03b9\u03b4\u03b9\u03ac", +"Description": "\u03a0\u03b5\u03c1\u03b9\u03b3\u03c1\u03b1\u03c6\u03ae", +"Robots": "\u03a1\u03bf\u03bc\u03c0\u03cc\u03c4", +"Author": "\u03a3\u03c5\u03bd\u03c4\u03ac\u03ba\u03c4\u03b7\u03c2", +"Encoding": "\u039a\u03c9\u03b4\u03b9\u03ba\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7", +"Fullscreen": "\u03a0\u03bb\u03ae\u03c1\u03b7\u03c2 \u03bf\u03b8\u03cc\u03bd\u03b7", +"Action": "\u0395\u03bd\u03ad\u03c1\u03b3\u03b5\u03b9\u03b1", +"Shortcut": "\u03a3\u03c5\u03bd\u03c4\u03cc\u03bc\u03b5\u03c5\u03c3\u03b7", +"Help": "\u0392\u03bf\u03ae\u03b8\u03b5\u03b9\u03b1", +"Address": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7", +"Focus to menubar": "\u0395\u03c3\u03c4\u03af\u03b1\u03c3\u03b7 \u03c3\u03c4\u03bf \u03bc\u03b5\u03bd\u03bf\u03cd", +"Focus to toolbar": "\u0395\u03c3\u03c4\u03af\u03b1\u03c3\u03b7 \u03c3\u03c4\u03b7 \u03b3\u03c1\u03b1\u03bc\u03bc\u03ae \u03b5\u03c1\u03b3\u03b1\u03bb\u03b5\u03af\u03c9\u03bd", +"Focus to element path": "\u0395\u03c3\u03c4\u03af\u03b1\u03c3\u03b7 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03bf\u03c5", +"Focus to contextual toolbar": "\u0395\u03c3\u03c4\u03af\u03b1\u03c3\u03b7 \u03c3\u03c4\u03b7 \u03c3\u03c5\u03bd\u03b1\u03c6\u03ae \u03b3\u03c1\u03b1\u03bc\u03bc\u03ae \u03b5\u03c1\u03b3\u03b1\u03bb\u03b5\u03af\u03c9\u03bd", +"Insert link (if link plugin activated)": "\u0395\u03b9\u03c3\u03b1\u03b3\u03c9\u03b3\u03ae \u03c3\u03c5\u03bd\u03b4\u03ad\u03c3\u03bc\u03bf\u03c5 (\u03b5\u03ac\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03bf \u03c4\u03bf \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf \u03c4\u03bf\u03c5 \u03c3\u03c5\u03bd\u03b4\u03ad\u03c3\u03bc\u03bf\u03c5)", +"Save (if save plugin activated)": "\u0391\u03c0\u03bf\u03b8\u03ae\u03ba\u03b5\u03c5\u03c3\u03b7 (\u03b5\u03ac\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03bf \u03c4\u03bf \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf \u03c4\u03b7\u03c2 \u03b1\u03c0\u03bf\u03b8\u03ae\u03ba\u03b5\u03c5\u03c3\u03b7\u03c2)", +"Find (if searchreplace plugin activated)": "\u0395\u03cd\u03c1\u03b5\u03c3\u03b7 (\u03b5\u03ac\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03bf \u03c4\u03bf \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf \u03c4\u03b7\u03c2 \u03b1\u03bd\u03b1\u03b6\u03ae\u03c4\u03b7\u03c3\u03b7\u03c2)", +"Plugins installed ({0}):": "\u0395\u03b3\u03ba\u03b1\u03c4\u03b5\u03c3\u03c4\u03b7\u03bc\u03ad\u03bd\u03b1 \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03b1 ({0}):", +"Premium plugins:": "\u03a0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03b1 \u03c5\u03c8\u03b7\u03bb\u03ae\u03c2 \u03c0\u03bf\u03b9\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2:", +"Learn more...": "\u039c\u03ac\u03b8\u03b5\u03c4\u03b5 \u03c0\u03b5\u03c1\u03b9\u03c3\u03c3\u03cc\u03c4\u03b5\u03c1\u03b1...", +"You are using {0}": "\u03a7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b5 {0}", +"Plugins": "\u03a0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03b1", +"Handy Shortcuts": "\u03a7\u03c1\u03ae\u03c3\u03b9\u03bc\u03b5\u03c2 \u03c3\u03c5\u03bd\u03c4\u03bf\u03bc\u03b5\u03cd\u03c3\u03b5\u03b9\u03c2", +"Horizontal line": "\u039f\u03c1\u03b9\u03b6\u03cc\u03bd\u03c4\u03b9\u03b1 \u03b3\u03c1\u03b1\u03bc\u03bc\u03ae", +"Insert\/edit image": "\u0395\u03b9\u03c3\u03b1\u03b3\u03c9\u03b3\u03ae\/\u03b5\u03c0\u03b5\u03be\u03b5\u03c1\u03b3\u03b1\u03c3\u03af\u03b1 \u03b5\u03b9\u03ba\u03cc\u03bd\u03b1\u03c2", +"Image description": "\u03a0\u03b5\u03c1\u03b9\u03b3\u03c1\u03b1\u03c6\u03ae \u03b5\u03b9\u03ba\u03cc\u03bd\u03b1\u03c2", +"Source": "\u03a0\u03b7\u03b3\u03ae", +"Dimensions": "\u0394\u03b9\u03b1\u03c3\u03c4\u03ac\u03c3\u03b5\u03b9\u03c2", +"Constrain proportions": "\u03a0\u03b5\u03c1\u03b9\u03bf\u03c1\u03b9\u03c3\u03bc\u03cc\u03c2 \u03b1\u03bd\u03b1\u03bb\u03bf\u03b3\u03b9\u03ce\u03bd", +"General": "\u0393\u03b5\u03bd\u03b9\u03ba\u03ac", +"Advanced": "\u0393\u03b9\u03b1 \u03a0\u03c1\u03bf\u03c7\u03c9\u03c1\u03b7\u03bc\u03ad\u03bd\u03bf\u03c5\u03c2", +"Style": "\u0395\u03bc\u03c6\u03ac\u03bd\u03b9\u03c3\u03b7", +"Vertical space": "\u039a\u03ac\u03b8\u03b5\u03c4\u03bf \u03b4\u03b9\u03ac\u03c3\u03c4\u03b7\u03bc\u03b1", +"Horizontal space": "\u039f\u03c1\u03b9\u03b6\u03cc\u03bd\u03c4\u03b9\u03bf \u03b4\u03b9\u03ac\u03c3\u03c4\u03b7\u03bc\u03b1", +"Border": "\u03a0\u03bb\u03b1\u03af\u03c3\u03b9\u03bf", +"Insert image": "\u0395\u03b9\u03c3\u03b1\u03b3\u03c9\u03b3\u03ae \u03b5\u03b9\u03ba\u03cc\u03bd\u03b1\u03c2", +"Image": "\u0395\u03b9\u03ba\u03cc\u03bd\u03b1", +"Image list": "\u039b\u03af\u03c3\u03c4\u03b1 \u03b5\u03b9\u03ba\u03cc\u03bd\u03c9\u03bd", +"Rotate counterclockwise": "\u03a0\u03b5\u03c1\u03b9\u03c3\u03c4\u03c1\u03bf\u03c6\u03ae \u03b1\u03c1\u03b9\u03c3\u03c4\u03b5\u03c1\u03cc\u03c3\u03c4\u03c1\u03bf\u03c6\u03b1", +"Rotate clockwise": "\u03a0\u03b5\u03c1\u03b9\u03c3\u03c4\u03c1\u03bf\u03c6\u03ae \u03b4\u03b5\u03be\u03b9\u03cc\u03c3\u03c4\u03c1\u03bf\u03c6\u03b1", +"Flip vertically": "\u0391\u03bd\u03b1\u03c3\u03c4\u03c1\u03bf\u03c6\u03ae \u03ba\u03b1\u03b8\u03ad\u03c4\u03c9\u03c2", +"Flip horizontally": "\u0391\u03bd\u03b1\u03c3\u03c4\u03c1\u03bf\u03c6\u03ae \u03bf\u03c1\u03b9\u03b6\u03bf\u03bd\u03c4\u03af\u03c9\u03c2", +"Edit image": "\u0395\u03c0\u03b5\u03be\u03b5\u03c1\u03b3\u03b1\u03c3\u03af\u03b1 \u03b5\u03b9\u03ba\u03cc\u03bd\u03b1\u03c2", +"Image options": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 \u03b5\u03b9\u03ba\u03cc\u03bd\u03b1\u03c2", +"Zoom in": "\u039c\u03b5\u03b3\u03ad\u03b8\u03c5\u03bd\u03c3\u03b7", +"Zoom out": "\u03a3\u03bc\u03af\u03ba\u03c1\u03c5\u03bd\u03c3\u03b7", +"Crop": "\u03a0\u03b5\u03c1\u03b9\u03ba\u03bf\u03c0\u03ae", +"Resize": "\u0391\u03bb\u03bb\u03b1\u03b3\u03ae \u03bc\u03b5\u03b3\u03ad\u03b8\u03bf\u03c5\u03c2", +"Orientation": "\u03a0\u03c1\u03bf\u03c3\u03b1\u03bd\u03b1\u03c4\u03bf\u03bb\u03b9\u03c3\u03bc\u03cc\u03c2", +"Brightness": "\u03a6\u03c9\u03c4\u03b5\u03b9\u03bd\u03cc\u03c4\u03b7\u03c4\u03b1", +"Sharpen": "\u038c\u03be\u03c5\u03bd\u03c3\u03b7", +"Contrast": "\u0391\u03bd\u03c4\u03af\u03b8\u03b5\u03c3\u03b7", +"Color levels": "\u0395\u03c0\u03af\u03c0\u03b5\u03b4\u03b1 \u03c7\u03c1\u03ce\u03bc\u03b1\u03c4\u03bf\u03c2", +"Gamma": "\u0393\u03ac\u03bc\u03bc\u03b1", +"Invert": "\u0391\u03bd\u03c4\u03b9\u03c3\u03c4\u03c1\u03bf\u03c6\u03ae", +"Apply": "\u0395\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae", +"Back": "\u03a0\u03af\u03c3\u03c9", +"Insert date\/time": "\u0395\u03b9\u03c3\u03b1\u03b3\u03c9\u03b3\u03ae \u03b7\u03bc\u03b5\u03c1\u03bf\u03bc\u03b7\u03bd\u03af\u03b1\u03c2\/\u03ce\u03c1\u03b1\u03c2", +"Date\/time": "\u0397\u03bc\u03b5\u03c1\u03bf\u03bc\u03b7\u03bd\u03af\u03b1\/\u03ce\u03c1\u03b1", +"Insert link": "\u0395\u03b9\u03c3\u03b1\u03b3\u03c9\u03b3\u03ae \u03c3\u03c5\u03bd\u03b4\u03ad\u03c3\u03bc\u03bf\u03c5", +"Insert\/edit link": "\u0395\u03b9\u03c3\u03b1\u03b3\u03c9\u03b3\u03ae\/\u03b5\u03c0\u03b5\u03be\u03b5\u03c1\u03b3\u03b1\u03c3\u03af\u03b1 \u03c3\u03c5\u03bd\u03b4\u03ad\u03c3\u03bc\u03bf\u03c5", +"Text to display": "\u039a\u03b5\u03af\u03bc\u03b5\u03bd\u03bf \u03b3\u03b9\u03b1 \u03b5\u03bc\u03c6\u03ac\u03bd\u03b9\u03c3\u03b7", +"Url": "URL", +"Target": "\u03a0\u03c1\u03bf\u03bf\u03c1\u03b9\u03c3\u03bc\u03cc\u03c2", +"None": "\u039a\u03b1\u03bc\u03af\u03b1", +"New window": "\u039d\u03ad\u03bf \u03c0\u03b1\u03c1\u03ac\u03b8\u03c5\u03c1\u03bf", +"Remove link": "\u0391\u03c6\u03b1\u03af\u03c1\u03b5\u03c3\u03b7 \u03c3\u03c5\u03bd\u03b4\u03ad\u03c3\u03bc\u03bf\u03c5", +"Anchors": "\u0386\u03b3\u03ba\u03c5\u03c1\u03b5\u03c2", +"Link": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03bc\u03bf\u03c2", +"Paste or type a link": "\u0395\u03c0\u03b9\u03ba\u03bf\u03bb\u03bb\u03ae\u03c3\u03c4\u03b5 \u03ae \u03c0\u03bb\u03b7\u03ba\u03c4\u03c1\u03bf\u03bb\u03bf\u03b3\u03ae\u03c3\u03c4\u03b5 \u03ad\u03bd\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03bc\u03bf", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "\u0397 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03c0\u03bf\u03c5 \u03b5\u03b9\u03c3\u03ac\u03c7\u03b8\u03b7\u03ba\u03b5 \u03c0\u03b9\u03b8\u03b1\u03bd\u03ce\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 email. \u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03b1\u03c0\u03b1\u03b9\u03c4\u03bf\u03cd\u03bc\u03b5\u03bd\u03bf \u03c0\u03c1\u03cc\u03b8\u03b7\u03bc\u03b1 mailto:;", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "\u0397 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03c0\u03bf\u03c5 \u03b5\u03b9\u03c3\u03ac\u03c7\u03b8\u03b7\u03ba\u03b5 \u03c0\u03b9\u03b8\u03b1\u03bd\u03ce\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03be\u03c9\u03c4\u03b5\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03bc\u03bf\u03c2. \u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03b1\u03c0\u03b1\u03b9\u03c4\u03bf\u03cd\u03bc\u03b5\u03bd\u03bf \u03c0\u03c1\u03cc\u03b8\u03b7\u03bc\u03b1 http:\/\/;", +"Link list": "\u039b\u03af\u03c3\u03c4\u03b1 \u03c3\u03c5\u03bd\u03b4\u03ad\u03c3\u03bc\u03c9\u03bd", +"Insert video": "\u0395\u03b9\u03c3\u03b1\u03b3\u03c9\u03b3\u03ae \u03b2\u03af\u03bd\u03c4\u03b5\u03bf", +"Insert\/edit video": "\u0395\u03b9\u03c3\u03b1\u03b3\u03c9\u03b3\u03ae\/\u03b5\u03c0\u03b5\u03be\u03b5\u03c1\u03b3\u03b1\u03c3\u03af\u03b1 \u03b2\u03af\u03bd\u03c4\u03b5\u03bf", +"Insert\/edit media": "\u0395\u03b9\u03c3\u03b1\u03b3\u03c9\u03b3\u03ae\/\u03b5\u03c0\u03b5\u03be\u03b5\u03c1\u03b3\u03b1\u03c3\u03af\u03b1 media", +"Alternative source": "\u0395\u03bd\u03b1\u03bb\u03bb\u03b1\u03ba\u03c4\u03b9\u03ba\u03ae \u03c0\u03c1\u03bf\u03ad\u03bb\u03b5\u03c5\u03c3\u03b7", +"Poster": "\u0391\u03c6\u03af\u03c3\u03b1", +"Paste your embed code below:": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03b1\u03c4\u03c9\u03bc\u03ad\u03bd\u03bf \u03ba\u03ce\u03b4\u03b9\u03ba\u03b1 \u03c0\u03b1\u03c1\u03b1\u03ba\u03ac\u03c4\u03c9:", +"Embed": "\u0395\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7", +"Media": "\u039c\u03ad\u03c3\u03b1 (\u03bc\u03af\u03bd\u03c4\u03b9\u03b1)", +"Nonbreaking space": "\u039a\u03b5\u03bd\u03cc \u03c7\u03c9\u03c1\u03af\u03c2 \u03b4\u03b9\u03b1\u03ba\u03bf\u03c0\u03ae", +"Page break": "\u0391\u03bb\u03bb\u03b1\u03b3\u03ae \u03c3\u03b5\u03bb\u03af\u03b4\u03b1\u03c2", +"Paste as text": "\u0395\u03c0\u03b9\u03ba\u03cc\u03bb\u03bb\u03b7\u03c3\u03b7 \u03c9\u03c2 \u03ba\u03b5\u03af\u03bc\u03b5\u03bd\u03bf", +"Preview": "\u03a0\u03c1\u03bf\u03b5\u03c0\u03b9\u03c3\u03ba\u03cc\u03c0\u03b7\u03c3\u03b7", +"Print": "\u0395\u03ba\u03c4\u03cd\u03c0\u03c9\u03c3\u03b7", +"Save": "\u0391\u03c0\u03bf\u03b8\u03ae\u03ba\u03b5\u03c5\u03c3\u03b7", +"Find": "\u0395\u03cd\u03c1\u03b5\u03c3\u03b7", +"Replace with": "\u0391\u03bd\u03c4\u03b9\u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 \u03bc\u03b5", +"Replace": "\u0391\u03bd\u03c4\u03b9\u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7", +"Replace all": "\u0391\u03bd\u03c4\u03b9\u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 \u03cc\u03bb\u03c9\u03bd", +"Prev": "\u03a0\u03c1\u03bf\u03b7\u03b3.", +"Next": "\u0395\u03c0\u03cc\u03bc.", +"Find and replace": "\u0395\u03cd\u03c1\u03b5\u03c3\u03b7 \u03ba\u03b1\u03b9 \u03b1\u03bd\u03c4\u03b9\u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7", +"Could not find the specified string.": "\u0394\u03b5\u03bd \u03ae\u03c4\u03b1\u03bd \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03b5\u03cd\u03c1\u03b5\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03ba\u03b1\u03b8\u03bf\u03c1\u03b9\u03c3\u03bc\u03ad\u03bd\u03bf\u03c5 \u03b1\u03bb\u03c6\u03b1\u03c1\u03b9\u03b8\u03bc\u03b7\u03c4\u03b9\u03ba\u03bf\u03cd.", +"Match case": "\u03a4\u03b1\u03af\u03c1\u03b9\u03b1\u03c3\u03bc\u03b1 \u03c0\u03b5\u03b6\u03ce\u03bd\/\u03ba\u03b5\u03c6\u03b1\u03bb\u03b1\u03af\u03c9\u03bd", +"Whole words": "\u039f\u03bb\u03cc\u03ba\u03bb\u03b7\u03c1\u03b5\u03c2 \u03bb\u03ad\u03be\u03b5\u03b9\u03c2", +"Spellcheck": "\u039f\u03c1\u03b8\u03bf\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 ", +"Ignore": "\u03a0\u03b1\u03c1\u03ac\u03b2\u03bb\u03b5\u03c8\u03b7", +"Ignore all": "\u03a0\u03b1\u03c1\u03ac\u03b2\u03bb\u03b5\u03c8\u03b7 \u03cc\u03bb\u03c9\u03bd", +"Finish": "\u03a4\u03ad\u03bb\u03bf\u03c2", +"Add to Dictionary": "\u03a0\u03c1\u03bf\u03c3\u03b8\u03ae\u03ba\u03b7 \u03c3\u03c4\u03bf \u039b\u03b5\u03be\u03b9\u03ba\u03cc", +"Insert table": "\u0395\u03b9\u03c3\u03b1\u03b3\u03c9\u03b3\u03ae \u03c0\u03af\u03bd\u03b1\u03ba\u03b1", +"Table properties": "\u0399\u03b4\u03b9\u03cc\u03c4\u03b7\u03c4\u03b5\u03c2 \u03c0\u03af\u03bd\u03b1\u03ba\u03b1", +"Delete table": "\u0394\u03b9\u03b1\u03b3\u03c1\u03b1\u03c6\u03ae \u03c0\u03af\u03bd\u03b1\u03ba\u03b1", +"Cell": "\u039a\u03b5\u03bb\u03af", +"Row": "\u0393\u03c1\u03b1\u03bc\u03bc\u03ae", +"Column": "\u03a3\u03c4\u03ae\u03bb\u03b7", +"Cell properties": "\u0399\u03b4\u03b9\u03cc\u03c4\u03b7\u03c4\u03b5\u03c2 \u03ba\u03b5\u03bb\u03b9\u03bf\u03cd", +"Merge cells": "\u03a3\u03c5\u03b3\u03c7\u03ce\u03bd\u03b5\u03c5\u03c3\u03b7 \u03ba\u03b5\u03bb\u03b9\u03ce\u03bd", +"Split cell": "\u0394\u03b9\u03b1\u03af\u03c1\u03b5\u03c3\u03b7 \u03ba\u03b5\u03bb\u03b9\u03bf\u03cd", +"Insert row before": "\u0395\u03b9\u03c3\u03b1\u03b3\u03c9\u03b3\u03ae \u03b3\u03c1\u03b1\u03bc\u03bc\u03ae\u03c2 \u03b5\u03c0\u03ac\u03bd\u03c9", +"Insert row after": "\u0395\u03b9\u03c3\u03b1\u03b3\u03c9\u03b3\u03ae \u03b3\u03c1\u03b1\u03bc\u03bc\u03ae\u03c2 \u03ba\u03ac\u03c4\u03c9", +"Delete row": "\u0394\u03b9\u03b1\u03b3\u03c1\u03b1\u03c6\u03ae \u03b3\u03c1\u03b1\u03bc\u03bc\u03ae\u03c2", +"Row properties": "\u0399\u03b4\u03b9\u03cc\u03c4\u03b7\u03c4\u03b5\u03c2 \u03b3\u03c1\u03b1\u03bc\u03bc\u03ae\u03c2", +"Cut row": "\u0391\u03c0\u03bf\u03ba\u03bf\u03c0\u03ae \u03b3\u03c1\u03b1\u03bc\u03bc\u03ae\u03c2", +"Copy row": "\u0391\u03bd\u03c4\u03b9\u03b3\u03c1\u03b1\u03c6\u03ae \u03b3\u03c1\u03b1\u03bc\u03bc\u03ae\u03c2", +"Paste row before": "\u0395\u03c0\u03b9\u03ba\u03cc\u03bb\u03bb\u03b7\u03c3\u03b7 \u03b3\u03c1\u03b1\u03bc\u03bc\u03ae\u03c2 \u03b5\u03c0\u03ac\u03bd\u03c9", +"Paste row after": "\u0395\u03c0\u03b9\u03ba\u03cc\u03bb\u03bb\u03b7\u03c3\u03b7 \u03b3\u03c1\u03b1\u03bc\u03bc\u03ae\u03c2 \u03ba\u03ac\u03c4\u03c9", +"Insert column before": "\u0395\u03b9\u03c3\u03b1\u03b3\u03c9\u03b3\u03ae \u03c3\u03c4\u03ae\u03bb\u03b7\u03c2 \u03b1\u03c1\u03b9\u03c3\u03c4\u03b5\u03c1\u03ac", +"Insert column after": "\u0395\u03b9\u03c3\u03b1\u03b3\u03c9\u03b3\u03ae \u03c3\u03c4\u03ae\u03bb\u03b7\u03c2 \u03b4\u03b5\u03be\u03b9\u03ac", +"Delete column": "\u0394\u03b9\u03b1\u03b3\u03c1\u03b1\u03c6\u03ae \u03c3\u03c4\u03ae\u03bb\u03b7\u03c2", +"Cols": "\u03a3\u03c4\u03ae\u03bb\u03b5\u03c2", +"Rows": "\u0393\u03c1\u03b1\u03bc\u03bc\u03ad\u03c2", +"Width": "\u03a0\u03bb\u03ac\u03c4\u03bf\u03c2", +"Height": "\u038e\u03c8\u03bf\u03c2", +"Cell spacing": "\u0391\u03c0\u03cc\u03c3\u03c4\u03b1\u03c3\u03b7 \u03ba\u03b5\u03bb\u03b9\u03ce\u03bd", +"Cell padding": "\u0391\u03bd\u03b1\u03c0\u03bb\u03ae\u03c1\u03c9\u03c3\u03b7 \u03ba\u03b5\u03bb\u03b9\u03ce\u03bd", +"Caption": "\u039b\u03b5\u03b6\u03ac\u03bd\u03c4\u03b1", +"Left": "\u0391\u03c1\u03b9\u03c3\u03c4\u03b5\u03c1\u03ac", +"Center": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b1\u03c1\u03b9\u03c3\u03bc\u03ad\u03bd\u03b7", +"Right": "\u0394\u03b5\u03be\u03b9\u03ac", +"Cell type": "\u03a4\u03cd\u03c0\u03bf\u03c2 \u03ba\u03b5\u03bb\u03b9\u03bf\u03cd", +"Scope": "\u0388\u03ba\u03c4\u03b1\u03c3\u03b7", +"Alignment": "\u03a3\u03c4\u03bf\u03af\u03c7\u03b9\u03c3\u03b7", +"H Align": "\u039f\u03c1. \u03a3\u03c4\u03bf\u03af\u03c7\u03b9\u03c3\u03b7", +"V Align": "\u039a. \u03a3\u03c4\u03bf\u03af\u03c7\u03b9\u03c3\u03b7", +"Top": "\u039a\u03bf\u03c1\u03c5\u03c6\u03ae", +"Middle": "\u039c\u03ad\u03c3\u03b7", +"Bottom": "\u039a\u03ac\u03c4\u03c9", +"Header cell": "\u039a\u03b5\u03bb\u03af-\u03ba\u03b5\u03c6\u03b1\u03bb\u03af\u03b4\u03b1", +"Row group": "\u039f\u03bc\u03ac\u03b4\u03b1 \u03b3\u03c1\u03b1\u03bc\u03bc\u03ce\u03bd", +"Column group": "\u039f\u03bc\u03ac\u03b4\u03b1 \u03c3\u03c4\u03b7\u03bb\u03ce\u03bd", +"Row type": "\u03a4\u03cd\u03c0\u03bf\u03c2 \u03b3\u03c1\u03b1\u03bc\u03bc\u03ae\u03c2", +"Header": "\u039a\u03b5\u03c6\u03b1\u03bb\u03af\u03b4\u03b1", +"Body": "\u03a3\u03ce\u03bc\u03b1", +"Footer": "\u03a5\u03c0\u03bf\u03c3\u03ad\u03bb\u03b9\u03b4\u03bf", +"Border color": "\u03a7\u03c1\u03ce\u03bc\u03b1 \u03c0\u03bb\u03b1\u03b9\u03c3\u03af\u03bf\u03c5", +"Insert template": "\u0395\u03b9\u03c3\u03b1\u03b3\u03c9\u03b3\u03ae \u03c0\u03c1\u03bf\u03c4\u03cd\u03c0\u03bf\u03c5 ", +"Templates": "\u03a0\u03c1\u03cc\u03c4\u03c5\u03c0\u03b1", +"Template": "\u03a0\u03c1\u03cc\u03c4\u03c5\u03c0\u03bf", +"Text color": "\u03a7\u03c1\u03ce\u03bc\u03b1 \u03ba\u03b5\u03b9\u03bc\u03ad\u03bd\u03bf\u03c5 ", +"Background color": "\u03a7\u03c1\u03ce\u03bc\u03b1 \u03c6\u03cc\u03bd\u03c4\u03bf\u03c5", +"Custom...": "\u03a0\u03c1\u03bf\u03c3\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae...", +"Custom color": "\u03a0\u03c1\u03bf\u03c3\u03b1\u03c1\u03bc\u03bf\u03c3\u03bc\u03ad\u03bd\u03bf \u03c7\u03c1\u03ce\u03bc\u03b1", +"No color": "\u03a7\u03c9\u03c1\u03af\u03c2 \u03c7\u03c1\u03ce\u03bc\u03b1", +"Table of Contents": "\u03a0\u03af\u03bd\u03b1\u03ba\u03b1\u03c2 \u03a0\u03b5\u03c1\u03b9\u03b5\u03c7\u03bf\u03bc\u03ad\u03bd\u03c9\u03bd", +"Show blocks": "\u0395\u03bc\u03c6\u03ac\u03bd\u03b9\u03c3\u03b7 \u03c4\u03bc\u03b7\u03bc\u03ac\u03c4\u03c9\u03bd", +"Show invisible characters": "\u0395\u03bc\u03c6\u03ac\u03bd\u03b9\u03c3\u03b7 \u03ba\u03c1\u03c5\u03c6\u03ce\u03bd \u03c7\u03b1\u03c1\u03b1\u03ba\u03c4\u03ae\u03c1\u03c9\u03bd", +"Words: {0}": "\u039b\u03ad\u03be\u03b5\u03b9\u03c2: {0}", +"{0} words": "{0} \u03bb\u03ad\u03be\u03b5\u03b9\u03c2", +"File": "\u0391\u03c1\u03c7\u03b5\u03af\u03bf", +"Edit": "\u0395\u03c0\u03b5\u03be\u03b5\u03c1\u03b3\u03b1\u03c3\u03af\u03b1", +"Insert": "\u0395\u03b9\u03c3\u03b1\u03b3\u03c9\u03b3\u03ae", +"View": "\u03a0\u03c1\u03bf\u03b2\u03bf\u03bb\u03ae", +"Format": "\u039c\u03bf\u03c1\u03c6\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7", +"Table": "\u03a0\u03af\u03bd\u03b1\u03ba\u03b1\u03c2", +"Tools": "\u0395\u03c1\u03b3\u03b1\u03bb\u03b5\u03af\u03b1", +"Powered by {0}": "\u03a4\u03c1\u03bf\u03c6\u03bf\u03b4\u03bf\u03c4\u03b5\u03af\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc {0}", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "\u03a0\u03b5\u03c1\u03b9\u03bf\u03c7\u03ae \u0395\u03bc\u03c0\u03bb\u03bf\u03c5\u03c4\u03b9\u03c3\u03bc\u03ad\u03bd\u03bf \u039a\u03b5\u03b9\u03bc\u03ad\u03bd\u03bf\u03c5. \u03a0\u03b1\u03c4\u03ae\u03c3\u03c4\u03b5 ALT-F9 \u03b3\u03b9\u03b1 \u03c4\u03bf \u03bc\u03b5\u03bd\u03bf\u03cd. \u03a0\u03b1\u03c4\u03ae\u03c3\u03c4\u03b5 ALT-F10 \u03b3\u03b9\u03b1 \u03c4\u03b7 \u03b3\u03c1\u03b1\u03bc\u03bc\u03ae \u03b5\u03c1\u03b3\u03b1\u03bb\u03b5\u03af\u03c9\u03bd. \u03a0\u03b1\u03c4\u03ae\u03c3\u03c4\u03b5 ALT-0 \u03b3\u03b9\u03b1 \u03b2\u03bf\u03ae\u03b8\u03b5\u03b9\u03b1" +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/en.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/en.js deleted file mode 100644 index 19324f74cd..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/en.js +++ /dev/null @@ -1 +0,0 @@ -tinyMCE.addI18n({en:{common:{"more_colors":"More Colors...","invalid_data":"Error: Invalid values entered, these are marked in red.","popup_blocked":"Sorry, but we have noticed that your popup-blocker has disabled a window that provides application functionality. You will need to disable popup blocking on this site in order to fully utilize this tool.","clipboard_no_support":"Currently not supported by your browser, use keyboard shortcuts instead.","clipboard_msg":"Copy/Cut/Paste is not available in Mozilla and Firefox.\nDo you want more information about this issue?","not_set":"-- Not Set --","class_name":"Class",browse:"Browse",close:"Close",cancel:"Cancel",update:"Update",insert:"Insert",apply:"Apply","edit_confirm":"Do you want to use the WYSIWYG mode for this textarea?","invalid_data_number":"{#field} must be a number","invalid_data_min":"{#field} must be a number greater than {#min}","invalid_data_size":"{#field} must be a number or percentage",value:"(value)"},contextmenu:{full:"Full",right:"Right",center:"Center",left:"Left",align:"Alignment"},insertdatetime:{"day_short":"Sun,Mon,Tue,Wed,Thu,Fri,Sat,Sun","day_long":"Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday","months_short":"Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec","months_long":"January,February,March,April,May,June,July,August,September,October,November,December","inserttime_desc":"Insert Time","insertdate_desc":"Insert Date","time_fmt":"%H:%M:%S","date_fmt":"%Y-%m-%d"},print:{"print_desc":"Print"},preview:{"preview_desc":"Preview"},directionality:{"rtl_desc":"Direction Right to Left","ltr_desc":"Direction Left to Right"},layer:{content:"New layer...","absolute_desc":"Toggle Absolute Positioning","backward_desc":"Move Backward","forward_desc":"Move Forward","insertlayer_desc":"Insert New Layer"},save:{"save_desc":"Save","cancel_desc":"Cancel All Changes"},nonbreaking:{"nonbreaking_desc":"Insert Non-Breaking Space Character"},iespell:{download:"ieSpell not detected. Do you want to install it now?","iespell_desc":"Check Spelling"},advhr:{"delta_height":"","delta_width":"","advhr_desc":"Insert Horizontal Line"},emotions:{"delta_height":"","delta_width":"","emotions_desc":"Emotions"},searchreplace:{"replace_desc":"Find/Replace","delta_width":"","delta_height":"","search_desc":"Find"},advimage:{"delta_width":"","image_desc":"Insert/Edit Image","delta_height":""},advlink:{"delta_height":"","delta_width":"","link_desc":"Insert/Edit Link"},xhtmlxtras:{"attribs_delta_height":"","attribs_delta_width":"","ins_delta_height":"","ins_delta_width":"","del_delta_height":"","del_delta_width":"","acronym_delta_height":"","acronym_delta_width":"","abbr_delta_height":"","abbr_delta_width":"","cite_delta_height":"","cite_delta_width":"","attribs_desc":"Insert/Edit Attributes","ins_desc":"Insertion","del_desc":"Deletion","acronym_desc":"Acronym","abbr_desc":"Abbreviation","cite_desc":"Citation"},style:{"delta_height":"","delta_width":"",desc:"Edit CSS Style"},paste:{"plaintext_mode_stick":"Paste is now in plain text mode. Click again to toggle back to regular paste mode.","plaintext_mode":"Paste is now in plain text mode. Click again to toggle back to regular paste mode. After you paste something you will be returned to regular paste mode.","selectall_desc":"Select All","paste_word_desc":"Paste from Word","paste_text_desc":"Paste as Plain Text"},"paste_dlg":{"word_title":"Use Ctrl+V on your keyboard to paste the text into the window.","text_linebreaks":"Keep Linebreaks","text_title":"Use Ctrl+V on your keyboard to paste the text into the window."},table:{"merge_cells_delta_height":"","merge_cells_delta_width":"","table_delta_height":"","table_delta_width":"","cellprops_delta_height":"","cellprops_delta_width":"","rowprops_delta_height":"","rowprops_delta_width":"",cell:"Cell",col:"Column",row:"Row",del:"Delete Table","copy_row_desc":"Copy Table Row","cut_row_desc":"Cut Table Row","paste_row_after_desc":"Paste Table Row After","paste_row_before_desc":"Paste Table Row Before","props_desc":"Table Properties","cell_desc":"Table Cell Properties","row_desc":"Table Row Properties","merge_cells_desc":"Merge Table Cells","split_cells_desc":"Split Merged Table Cells","delete_col_desc":"Delete Column","col_after_desc":"Insert Column After","col_before_desc":"Insert Column Before","delete_row_desc":"Delete Row","row_after_desc":"Insert Row After","row_before_desc":"Insert Row Before",desc:"Insert/Edit Table"},autosave:{"warning_message":"If you restore the saved content, you will lose all the content that is currently in the editor.\n\nAre you sure you want to restore the saved content?","restore_content":"Restore auto-saved content.","unload_msg":"The changes you made will be lost if you navigate away from this page."},fullscreen:{desc:"Toggle Full Screen Mode"},media:{"delta_height":"","delta_width":"",edit:"Edit Embedded Media",desc:"Insert/Edit Embedded Media"},fullpage:{desc:"Document Properties","delta_width":"","delta_height":""},template:{desc:"Insert Predefined Template Content"},visualchars:{desc:"Show/Hide Visual Control Characters"},spellchecker:{desc:"Toggle Spell Checker",menu:"Spell Checker Settings","ignore_word":"Ignore Word","ignore_words":"Ignore All",langs:"Languages",wait:"Please wait...",sug:"Suggestions","no_sug":"No Suggestions","no_mpell":"No misspellings found.","learn_word":"Learn word"},pagebreak:{desc:"Insert Page Break for Printing"},advlist:{types:"Types",def:"Default","lower_alpha":"Lower Alpha","lower_greek":"Lower Greek","lower_roman":"Lower Roman","upper_alpha":"Upper Alpha","upper_roman":"Upper Roman",circle:"Circle",disc:"Disc",square:"Square"},colors:{"333300":"Dark olive","993300":"Burnt orange","000000":"Black","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"},aria:{"rich_text_area":"Rich Text Area"},wordcount:{words:"Words:"},visualblocks:{desc:'Show/hide block elements'}}}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/en_CA.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/en_CA.js new file mode 100644 index 0000000000..f32de01724 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/en_CA.js @@ -0,0 +1,261 @@ +tinymce.addI18n('en_CA',{ +"Redo": "Redo", +"Undo": "Undo", +"Cut": "Cut", +"Copy": "Copy", +"Paste": "Paste", +"Select all": "Select all", +"New document": "New document", +"Ok": "Ok", +"Cancel": "Cancel", +"Visual aids": "Visual aids", +"Bold": "Bold", +"Italic": "Italic", +"Underline": "Underline", +"Strikethrough": "Strikethrough", +"Superscript": "Superscript", +"Subscript": "Subscript", +"Clear formatting": "Clear formatting", +"Align left": "Align left", +"Align center": "Align center", +"Align right": "Align right", +"Justify": "Justify", +"Bullet list": "Bullet list", +"Numbered list": "Numbered list", +"Decrease indent": "Decrease indent", +"Increase indent": "Increase indent", +"Close": "Close", +"Formats": "Formats", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.", +"Headers": "Headers", +"Header 1": "Header 1", +"Header 2": "Header 2", +"Header 3": "Header 3", +"Header 4": "Header 4", +"Header 5": "Header 5", +"Header 6": "Header 6", +"Headings": "Headings", +"Heading 1": "Heading 1", +"Heading 2": "Heading 2", +"Heading 3": "Heading 3", +"Heading 4": "Heading 4", +"Heading 5": "Heading 5", +"Heading 6": "Heading 6", +"Preformatted": "Preformatted", +"Div": "Div", +"Pre": "Pre", +"Code": "Code", +"Paragraph": "Paragraph", +"Blockquote": "Blockquote", +"Inline": "Inline", +"Blocks": "Blocks", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.", +"Font Family": "Font Family", +"Font Sizes": "Font Sizes", +"Class": "Class", +"Browse for an image": "Browse for an image", +"OR": "OR", +"Drop an image here": "Drop an image here", +"Upload": "Upload", +"Block": "Blocks", +"Align": "Align", +"Default": "Default", +"Circle": "Circle", +"Disc": "Disc", +"Square": "Square", +"Lower Alpha": "Lower Alpha", +"Lower Greek": "Lower Greek", +"Lower Roman": "Lower Roman", +"Upper Alpha": "Upper Alpha", +"Upper Roman": "Upper Roman", +"Anchor": "Anchor", +"Name": "Name", +"Id": "ID", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "ID should start with a letter, followed only by letters, numbers, dashes, dots, colons, or underscores.", +"You have unsaved changes are you sure you want to navigate away?": "You have unsaved changes are you sure you want to navigate away?", +"Restore last draft": "Restore last draft", +"Special character": "Special character", +"Source code": "Source code", +"Insert\/Edit code sample": "Insert\/Edit code sample", +"Language": "Language", +"Code sample": "Code sample", +"Color": "Colour", +"R": "R", +"G": "G", +"B": "B", +"Left to right": "Left to right", +"Right to left": "Right to left", +"Emoticons": "Emoticons", +"Document properties": "Document properties", +"Title": "Title", +"Keywords": "Keywords", +"Description": "Description", +"Robots": "Robots", +"Author": "Author", +"Encoding": "Encoding", +"Fullscreen": "Fullscreen", +"Action": "Action", +"Shortcut": "Shortcut", +"Help": "Help", +"Address": "Address", +"Focus to menubar": "Focus to menubar", +"Focus to toolbar": "Focus to toolbar", +"Focus to element path": "Focus to element path", +"Focus to contextual toolbar": "Focus to contextual toolbar", +"Insert link (if link plugin activated)": "Insert link (if link plugin activated)", +"Save (if save plugin activated)": "Save (if save plugin activated)", +"Find (if searchreplace plugin activated)": "Find (if searchreplace plugin activated)", +"Plugins installed ({0}):": "Plugins installed ({0}):", +"Premium plugins:": "Premium plugins:", +"Learn more...": "Learn more...", +"You are using {0}": "You are using {0}", +"Plugins": "Plugins", +"Handy Shortcuts": "Handy Shortcuts", +"Horizontal line": "Horizontal line", +"Insert\/edit image": "Insert\/edit image", +"Image description": "Image description", +"Source": "Source", +"Dimensions": "Dimensions", +"Constrain proportions": "Constrain proportions", +"General": "General", +"Advanced": "Advanced", +"Style": "Style", +"Vertical space": "Vertical space", +"Horizontal space": "Horizontal space", +"Border": "Border", +"Insert image": "Insert image", +"Image": "Image", +"Image list": "Image list", +"Rotate counterclockwise": "Rotate counterclockwise", +"Rotate clockwise": "Rotate clockwise", +"Flip vertically": "Flip vertically", +"Flip horizontally": "Flip horizontally", +"Edit image": "Edit image", +"Image options": "Image options", +"Zoom in": "Zoom in", +"Zoom out": "Zoom out", +"Crop": "Crop", +"Resize": "Resize", +"Orientation": "Orientation", +"Brightness": "Brightness", +"Sharpen": "Sharpen", +"Contrast": "Contrast", +"Color levels": "Colour levels", +"Gamma": "Gamma", +"Invert": "Invert", +"Apply": "Apply", +"Back": "Back", +"Insert date\/time": "Insert date\/time", +"Date\/time": "Date\/time", +"Insert link": "Insert link", +"Insert\/edit link": "Insert\/edit link", +"Text to display": "Text to display", +"Url": "Url", +"Target": "Target", +"None": "None", +"New window": "New window", +"Remove link": "Remove link", +"Anchors": "Anchors", +"Link": "Link", +"Paste or type a link": "Paste or type a link", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?", +"Link list": "Link list", +"Insert video": "Insert video", +"Insert\/edit video": "Insert\/edit video", +"Insert\/edit media": "Insert\/edit media", +"Alternative source": "Alternative source", +"Poster": "Poster", +"Paste your embed code below:": "Paste your embed code below:", +"Embed": "Embed", +"Media": "Media", +"Nonbreaking space": "Nonbreaking space", +"Page break": "Page break", +"Paste as text": "Paste as text", +"Preview": "Preview", +"Print": "Print", +"Save": "Save", +"Find": "Find", +"Replace with": "Replace with", +"Replace": "Replace", +"Replace all": "Replace all", +"Prev": "Prev", +"Next": "Next", +"Find and replace": "Find and replace", +"Could not find the specified string.": "Could not find the specified string.", +"Match case": "Match case", +"Whole words": "Whole words", +"Spellcheck": "Spellcheck", +"Ignore": "Ignore", +"Ignore all": "Ignore all", +"Finish": "Finish", +"Add to Dictionary": "Add to Dictionary", +"Insert table": "Insert table", +"Table properties": "Table properties", +"Delete table": "Delete table", +"Cell": "Cell", +"Row": "Row", +"Column": "Column", +"Cell properties": "Cell properties", +"Merge cells": "Merge cells", +"Split cell": "Split cell", +"Insert row before": "Insert row before", +"Insert row after": "Insert row after", +"Delete row": "Delete row", +"Row properties": "Row properties", +"Cut row": "Cut row", +"Copy row": "Copy row", +"Paste row before": "Paste row before", +"Paste row after": "Paste row after", +"Insert column before": "Insert column before", +"Insert column after": "Insert column after", +"Delete column": "Delete column", +"Cols": "Cols", +"Rows": "Rows", +"Width": "Width", +"Height": "Height", +"Cell spacing": "Cell spacing", +"Cell padding": "Cell padding", +"Caption": "Caption", +"Left": "Left", +"Center": "Center", +"Right": "Right", +"Cell type": "Cell type", +"Scope": "Scope", +"Alignment": "Alignment", +"H Align": "H Align", +"V Align": "V Align", +"Top": "Top", +"Middle": "Middle", +"Bottom": "Bottom", +"Header cell": "Header cell", +"Row group": "Row group", +"Column group": "Column group", +"Row type": "Row type", +"Header": "Header", +"Body": "Body", +"Footer": "Footer", +"Border color": "Border colour", +"Insert template": "Insert template", +"Templates": "Templates", +"Template": "Template", +"Text color": "Text colour", +"Background color": "Background colour", +"Custom...": "Custom...", +"Custom color": "Custom colour", +"No color": "No colour", +"Table of Contents": "Table of Contents", +"Show blocks": "Show blocks", +"Show invisible characters": "Show invisible characters", +"Words: {0}": "Words: {0}", +"{0} words": "{0} words", +"File": "File", +"Edit": "Edit", +"Insert": "Insert", +"View": "View", +"Format": "Format", +"Table": "Table", +"Tools": "Tools", +"Powered by {0}": "Powered by {0}", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help" +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/en_GB.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/en_GB.js new file mode 100644 index 0000000000..312698a93b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/en_GB.js @@ -0,0 +1,261 @@ +tinymce.addI18n('en_GB',{ +"Redo": "Redo", +"Undo": "Undo", +"Cut": "Cut", +"Copy": "Copy", +"Paste": "Paste", +"Select all": "Select all", +"New document": "New document", +"Ok": "Ok", +"Cancel": "Cancel", +"Visual aids": "Visual aids", +"Bold": "Bold", +"Italic": "Italic", +"Underline": "Underline", +"Strikethrough": "Strike-through", +"Superscript": "Superscript", +"Subscript": "Subscript", +"Clear formatting": "Clear formatting", +"Align left": "Align left", +"Align center": "Align centre", +"Align right": "Align right", +"Justify": "Justify", +"Bullet list": "Bullet list", +"Numbered list": "Numbered list", +"Decrease indent": "Decrease indent", +"Increase indent": "Increase indent", +"Close": "Close", +"Formats": "Formats", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.", +"Headers": "Headers", +"Header 1": "Header 1", +"Header 2": "Header 2", +"Header 3": "Header 3", +"Header 4": "Header 4", +"Header 5": "Header 5", +"Header 6": "Header 6", +"Headings": "Headings", +"Heading 1": "Heading 1", +"Heading 2": "Heading 2", +"Heading 3": "Heading 3", +"Heading 4": "Heading 4", +"Heading 5": "Heading 5", +"Heading 6": "Heading 6", +"Preformatted": "Preformatted", +"Div": "Div", +"Pre": "Pre", +"Code": "Code", +"Paragraph": "Paragraph", +"Blockquote": "Blockquote", +"Inline": "Inline", +"Blocks": "Blocks", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.", +"Font Family": "Font Family", +"Font Sizes": "Font Sizes", +"Class": "Class", +"Browse for an image": "Browse for an image", +"OR": "OR", +"Drop an image here": "Drop an image here", +"Upload": "Upload", +"Block": "Block", +"Align": "Align", +"Default": "Default", +"Circle": "Circle", +"Disc": "Disc", +"Square": "Square", +"Lower Alpha": "Lower Alpha", +"Lower Greek": "Lower Greek", +"Lower Roman": "Lower Roman", +"Upper Alpha": "Upper Alpha", +"Upper Roman": "Upper Roman", +"Anchor": "Anchor", +"Name": "Name", +"Id": "ID", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "ID should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.", +"You have unsaved changes are you sure you want to navigate away?": "You have unsaved changes are you sure you want to navigate away?", +"Restore last draft": "Restore last draft", +"Special character": "Special character", +"Source code": "Source code", +"Insert\/Edit code sample": "Insert\/Edit code sample", +"Language": "Language", +"Code sample": "Code sample", +"Color": "Colour", +"R": "R", +"G": "G", +"B": "B", +"Left to right": "Left to right", +"Right to left": "Right to left", +"Emoticons": "Emoticons", +"Document properties": "Document properties", +"Title": "Title", +"Keywords": "Keywords", +"Description": "Description", +"Robots": "Robots", +"Author": "Author", +"Encoding": "Encoding", +"Fullscreen": "Full-screen", +"Action": "Action", +"Shortcut": "Shortcut", +"Help": "Help", +"Address": "Address", +"Focus to menubar": "Focus to menubar", +"Focus to toolbar": "Focus to toolbar", +"Focus to element path": "Focus to element path", +"Focus to contextual toolbar": "Focus to contextual toolbar", +"Insert link (if link plugin activated)": "Insert link (if link plugin activated)", +"Save (if save plugin activated)": "Save (if save plugin activated)", +"Find (if searchreplace plugin activated)": "Find (if searchreplace plugin activated)", +"Plugins installed ({0}):": "Plugins installed ({0}):", +"Premium plugins:": "Premium plugins:", +"Learn more...": "Learn more...", +"You are using {0}": "You are using {0}", +"Plugins": "Plugins", +"Handy Shortcuts": "Handy Shortcuts", +"Horizontal line": "Horizontal line", +"Insert\/edit image": "Insert\/edit image", +"Image description": "Image description", +"Source": "Source", +"Dimensions": "Dimensions", +"Constrain proportions": "Constrain proportions", +"General": "General", +"Advanced": "Advanced", +"Style": "Style", +"Vertical space": "Vertical space", +"Horizontal space": "Horizontal space", +"Border": "Border", +"Insert image": "Insert image", +"Image": "Image", +"Image list": "Image list", +"Rotate counterclockwise": "Rotate counterclockwise", +"Rotate clockwise": "Rotate clockwise", +"Flip vertically": "Flip vertically", +"Flip horizontally": "Flip horizontally", +"Edit image": "Edit image", +"Image options": "Image options", +"Zoom in": "Zoom in", +"Zoom out": "Zoom out", +"Crop": "Crop", +"Resize": "Resize", +"Orientation": "Orientation", +"Brightness": "Brightness", +"Sharpen": "Sharpen", +"Contrast": "Contrast", +"Color levels": "Colour levels", +"Gamma": "Gamma", +"Invert": "Invert", +"Apply": "Apply", +"Back": "Back", +"Insert date\/time": "Insert date\/time", +"Date\/time": "Date\/time", +"Insert link": "Insert link", +"Insert\/edit link": "Insert\/edit link", +"Text to display": "Text to display", +"Url": "URL", +"Target": "Target", +"None": "None", +"New window": "New window", +"Remove link": "Remove link", +"Anchors": "Anchors", +"Link": "Link", +"Paste or type a link": "Paste or type a link", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?", +"Link list": "Link list", +"Insert video": "Insert video", +"Insert\/edit video": "Insert\/edit video", +"Insert\/edit media": "Insert\/edit media", +"Alternative source": "Alternative source", +"Poster": "Poster", +"Paste your embed code below:": "Paste your embed code below:", +"Embed": "Embed", +"Media": "Media", +"Nonbreaking space": "Non-breaking space", +"Page break": "Page break", +"Paste as text": "Paste as text", +"Preview": "Preview", +"Print": "Print", +"Save": "Save", +"Find": "Find", +"Replace with": "Replace with", +"Replace": "Replace", +"Replace all": "Replace all", +"Prev": "Prev", +"Next": "Next", +"Find and replace": "Find and replace", +"Could not find the specified string.": "Could not find the specified string.", +"Match case": "Match case", +"Whole words": "Whole words", +"Spellcheck": "Spell-check", +"Ignore": "Ignore", +"Ignore all": "Ignore all", +"Finish": "Finish", +"Add to Dictionary": "Add to Dictionary", +"Insert table": "Insert table", +"Table properties": "Table properties", +"Delete table": "Delete table", +"Cell": "Cell", +"Row": "Row", +"Column": "Column", +"Cell properties": "Cell properties", +"Merge cells": "Merge cells", +"Split cell": "Split cell", +"Insert row before": "Insert row before", +"Insert row after": "Insert row after", +"Delete row": "Delete row", +"Row properties": "Row properties", +"Cut row": "Cut row", +"Copy row": "Copy row", +"Paste row before": "Paste row before", +"Paste row after": "Paste row after", +"Insert column before": "Insert column before", +"Insert column after": "Insert column after", +"Delete column": "Delete column", +"Cols": "Cols", +"Rows": "Rows", +"Width": "Width", +"Height": "Height", +"Cell spacing": "Cell spacing", +"Cell padding": "Cell padding", +"Caption": "Caption", +"Left": "Left", +"Center": "Centre", +"Right": "Right", +"Cell type": "Cell type", +"Scope": "Scope", +"Alignment": "Alignment", +"H Align": "H Align", +"V Align": "V Align", +"Top": "Top", +"Middle": "Middle", +"Bottom": "Bottom", +"Header cell": "Header cell", +"Row group": "Row group", +"Column group": "Column group", +"Row type": "Row type", +"Header": "Header", +"Body": "Body", +"Footer": "Footer", +"Border color": "Border colour", +"Insert template": "Insert template", +"Templates": "Templates", +"Template": "Template", +"Text color": "Text colour", +"Background color": "Background colour", +"Custom...": "Custom...", +"Custom color": "Custom colour", +"No color": "No colour", +"Table of Contents": "Table of Contents", +"Show blocks": "Show blocks", +"Show invisible characters": "Show invisible characters", +"Words: {0}": "Words: {0}", +"{0} words": "{0} words", +"File": "File", +"Edit": "Edit", +"Insert": "Insert", +"View": "View", +"Format": "Format", +"Table": "Table", +"Tools": "Tools", +"Powered by {0}": "Powered by {0}", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help" +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/en_us.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/en_us.js deleted file mode 100644 index 4e94198e5a..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/en_us.js +++ /dev/null @@ -1 +0,0 @@ -tinyMCE.addI18n({en_us:{common:{"more_colors":"More Colors...","invalid_data":"Error: Invalid values entered, these are marked in red.","popup_blocked":"Sorry, but we have noticed that your popup-blocker has disabled a window that provides application functionality. You will need to disable popup blocking on this site in order to fully utilize this tool.","clipboard_no_support":"Currently not supported by your browser, use keyboard shortcuts instead.","clipboard_msg":"Copy/Cut/Paste is not available in Mozilla and Firefox.\nDo you want more information about this issue?","not_set":"-- Not Set --","class_name":"Class",browse:"Browse",close:"Close",cancel:"Cancel",update:"Update",insert:"Insert",apply:"Apply","edit_confirm":"Do you want to use the WYSIWYG mode for this textarea?","invalid_data_number":"{#field} must be a number","invalid_data_min":"{#field} must be a number greater than {#min}","invalid_data_size":"{#field} must be a number or percentage",value:"(value)"},contextmenu:{full:"Full",right:"Right",center:"Center",left:"Left",align:"Alignment"},insertdatetime:{"day_short":"Sun,Mon,Tue,Wed,Thu,Fri,Sat,Sun","day_long":"Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday","months_short":"Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec","months_long":"January,February,March,April,May,June,July,August,September,October,November,December","inserttime_desc":"Insert Time","insertdate_desc":"Insert Date","time_fmt":"%H:%M:%S","date_fmt":"%Y-%m-%d"},print:{"print_desc":"Print"},preview:{"preview_desc":"Preview"},directionality:{"rtl_desc":"Direction Right to Left","ltr_desc":"Direction Left to Right"},layer:{content:"New layer...","absolute_desc":"Toggle Absolute Positioning","backward_desc":"Move Backward","forward_desc":"Move Forward","insertlayer_desc":"Insert New Layer"},save:{"save_desc":"Save","cancel_desc":"Cancel All Changes"},nonbreaking:{"nonbreaking_desc":"Insert Non-Breaking Space Character"},iespell:{download:"ieSpell not detected. Do you want to install it now?","iespell_desc":"Check Spelling"},advhr:{"delta_height":"","delta_width":"","advhr_desc":"Insert Horizontal Line"},emotions:{"delta_height":"","delta_width":"","emotions_desc":"Emotions"},searchreplace:{"replace_desc":"Find/Replace","delta_width":"","delta_height":"","search_desc":"Find"},advimage:{"delta_width":"","image_desc":"Insert/Edit Image","delta_height":""},advlink:{"delta_height":"","delta_width":"","link_desc":"Insert/Edit Link"},xhtmlxtras:{"attribs_delta_height":"","attribs_delta_width":"","ins_delta_height":"","ins_delta_width":"","del_delta_height":"","del_delta_width":"","acronym_delta_height":"","acronym_delta_width":"","abbr_delta_height":"","abbr_delta_width":"","cite_delta_height":"","cite_delta_width":"","attribs_desc":"Insert/Edit Attributes","ins_desc":"Insertion","del_desc":"Deletion","acronym_desc":"Acronym","abbr_desc":"Abbreviation","cite_desc":"Citation"},style:{"delta_height":"","delta_width":"",desc:"Edit CSS Style"},paste:{"plaintext_mode_stick":"Paste is now in plain text mode. Click again to toggle back to regular paste mode.","plaintext_mode":"Paste is now in plain text mode. Click again to toggle back to regular paste mode. After you paste something you will be returned to regular paste mode.","selectall_desc":"Select All","paste_word_desc":"Paste from Word","paste_text_desc":"Paste as Plain Text"},"paste_dlg":{"word_title":"Use Ctrl+V on your keyboard to paste the text into the window.","text_linebreaks":"Keep Linebreaks","text_title":"Use Ctrl+V on your keyboard to paste the text into the window."},table:{"merge_cells_delta_height":"","merge_cells_delta_width":"","table_delta_height":"","table_delta_width":"","cellprops_delta_height":"","cellprops_delta_width":"","rowprops_delta_height":"","rowprops_delta_width":"",cell:"Cell",col:"Column",row:"Row",del:"Delete Table","copy_row_desc":"Copy Table Row","cut_row_desc":"Cut Table Row","paste_row_after_desc":"Paste Table Row After","paste_row_before_desc":"Paste Table Row Before","props_desc":"Table Properties","cell_desc":"Table Cell Properties","row_desc":"Table Row Properties","merge_cells_desc":"Merge Table Cells","split_cells_desc":"Split Merged Table Cells","delete_col_desc":"Delete Column","col_after_desc":"Insert Column After","col_before_desc":"Insert Column Before","delete_row_desc":"Delete Row","row_after_desc":"Insert Row After","row_before_desc":"Insert Row Before",desc:"Insert/Edit Table"},autosave:{"warning_message":"If you restore the saved content, you will lose all the content that is currently in the editor.\n\nAre you sure you want to restore the saved content?","restore_content":"Restore auto-saved content.","unload_msg":"The changes you made will be lost if you navigate away from this page."},fullscreen:{desc:"Toggle Full Screen Mode"},media:{"delta_height":"","delta_width":"",edit:"Edit Embedded Media",desc:"Insert/Edit Embedded Media"},fullpage:{desc:"Document Properties","delta_width":"","delta_height":""},template:{desc:"Insert Predefined Template Content"},visualchars:{desc:"Show/Hide Visual Control Characters"},spellchecker:{desc:"Toggle Spell Checker",menu:"Spell Checker Settings","ignore_word":"Ignore Word","ignore_words":"Ignore All",langs:"Languages",wait:"Please wait...",sug:"Suggestions","no_sug":"No Suggestions","no_mpell":"No misspellings found.","learn_word":"Learn word"},pagebreak:{desc:"Insert Page Break for Printing"},advlist:{types:"Types",def:"Default","lower_alpha":"Lower Alpha","lower_greek":"Lower Greek","lower_roman":"Lower Roman","upper_alpha":"Upper Alpha","upper_roman":"Upper Roman",circle:"Circle",disc:"Disc",square:"Square"},colors:{"333300":"Dark olive","993300":"Burnt orange","000000":"Black","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"},aria:{"rich_text_area":"Rich Text Area"},wordcount:{words:"Words:"},visualblocks:{desc:'Show/hide block elements'}}}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/es.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/es.js new file mode 100644 index 0000000000..9cb0e9d53a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/es.js @@ -0,0 +1,261 @@ +tinymce.addI18n('es',{ +"Redo": "Rehacer", +"Undo": "Deshacer", +"Cut": "Cortar", +"Copy": "Copiar", +"Paste": "Pegar", +"Select all": "Seleccionar todo", +"New document": "Nuevo documento", +"Ok": "Ok", +"Cancel": "Cancelar", +"Visual aids": "Ayudas visuales", +"Bold": "Negrita", +"Italic": "It\u00e1lica", +"Underline": "Subrayado", +"Strikethrough": "Tachado", +"Superscript": "Super\u00edndice", +"Subscript": "Sub\u00edndice", +"Clear formatting": "Limpiar formato", +"Align left": "Alinear a la izquierda", +"Align center": "Alinear al centro", +"Align right": "Alinear a la derecha", +"Justify": "Justificar", +"Bullet list": "Lista de vi\u00f1etas", +"Numbered list": "Lista numerada", +"Decrease indent": "Disminuir sangr\u00eda", +"Increase indent": "Incrementar sangr\u00eda", +"Close": "Cerrar", +"Formats": "Formatos", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "Tu navegador no soporta acceso directo al portapapeles. Por favor usa las teclas Crtl+X\/C\/V de tu teclado", +"Headers": "Encabezados", +"Header 1": "Encabezado 1", +"Header 2": "Encabezado 2 ", +"Header 3": "Encabezado 3", +"Header 4": "Encabezado 4", +"Header 5": "Encabezado 5 ", +"Header 6": "Encabezado 6", +"Headings": "Encabezados", +"Heading 1": "Encabezado 1", +"Heading 2": "Encabezado 2", +"Heading 3": "Encabezado 3", +"Heading 4": "Encabezado 4", +"Heading 5": "Encabezado 5", +"Heading 6": "Encabezado 6", +"Preformatted": "Preformateado", +"Div": "Capa", +"Pre": "Pre", +"Code": "C\u00f3digo", +"Paragraph": "P\u00e1rrafo", +"Blockquote": "Bloque de cita", +"Inline": "en l\u00ednea", +"Blocks": "Bloques", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "Pegar est\u00e1 ahora en modo de texto plano. El contenido se pegar\u00e1 como texto plano hasta que desactive esta opci\u00f3n.", +"Font Family": "Familia de fuentes", +"Font Sizes": "Tama\u00f1os de fuente", +"Class": "Clase", +"Browse for an image": "Exporador de imagenes", +"OR": "O", +"Drop an image here": "Arrastre una imagen aqu\u00ed", +"Upload": "Subir", +"Block": "Bloque", +"Align": "Alinear", +"Default": "Por defecto", +"Circle": "C\u00edrculo", +"Disc": "Disco", +"Square": "Cuadrado", +"Lower Alpha": "Inferior Alfa", +"Lower Greek": "Inferior Griega", +"Lower Roman": "Inferior Romana", +"Upper Alpha": "Superior Alfa", +"Upper Roman": "Superior Romana", +"Anchor": "Ancla", +"Name": "Nombre", +"Id": "Id", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "Deber\u00eda comenzar por una letra, seguida solo de letras, n\u00fameros, guiones, puntos, dos puntos o guiones bajos.", +"You have unsaved changes are you sure you want to navigate away?": "Tiene cambios sin guardar. \u00bfEst\u00e1 seguro de que quiere salir?", +"Restore last draft": "Restaurar el \u00faltimo borrador", +"Special character": "Car\u00e1cter especial", +"Source code": "C\u00f3digo fuente", +"Insert\/Edit code sample": "Insertar\/editar c\u00f3digo de prueba", +"Language": "Idioma", +"Code sample": "Ejemplo de c\u00f3digo", +"Color": "Color", +"R": "R", +"G": "V", +"B": "A", +"Left to right": "De izquierda a derecha", +"Right to left": "De derecha a izquierda", +"Emoticons": "Emoticonos", +"Document properties": "Propiedades del documento", +"Title": "T\u00edtulo", +"Keywords": "Palabras clave", +"Description": "Descripci\u00f3n", +"Robots": "Robots", +"Author": "Autor", +"Encoding": "Codificaci\u00f3n", +"Fullscreen": "Pantalla completa", +"Action": "Acci\u00f3n", +"Shortcut": "Atajo", +"Help": "Ayuda", +"Address": "Direcci\u00f3n", +"Focus to menubar": "Enfocar la barra del men\u00fa", +"Focus to toolbar": "Enfocar la barra de herramientas", +"Focus to element path": "Enfocar la ruta del elemento", +"Focus to contextual toolbar": "Enfocar la barra de herramientas contextual", +"Insert link (if link plugin activated)": "Insertar enlace (si el complemento de enlace est\u00e1 activado)", +"Save (if save plugin activated)": "Guardar (si el componente de salvar est\u00e1 activado)", +"Find (if searchreplace plugin activated)": "Buscar (si el complemento buscar-remplazar est\u00e1 activado)", +"Plugins installed ({0}):": "Plugins instalados ({0}):", +"Premium plugins:": "Complementos premium:", +"Learn more...": "Aprende m\u00e1s...", +"You are using {0}": "Estas usando {0}", +"Plugins": "Complementos", +"Handy Shortcuts": "Accesos directos", +"Horizontal line": "L\u00ednea horizontal", +"Insert\/edit image": "Insertar\/editar imagen", +"Image description": "Descripci\u00f3n de la imagen", +"Source": "Enlace", +"Dimensions": "Dimensiones", +"Constrain proportions": "Restringir proporciones", +"General": "General", +"Advanced": "Avanzado", +"Style": "Estilo", +"Vertical space": "Espacio vertical", +"Horizontal space": "Espacio horizontal", +"Border": "Borde", +"Insert image": "Insertar imagen", +"Image": "Imagen", +"Image list": "Lista de im\u00e1genes", +"Rotate counterclockwise": "Girar a la izquierda", +"Rotate clockwise": "Girar a la derecha", +"Flip vertically": "Invertir verticalmente", +"Flip horizontally": "Invertir horizontalmente", +"Edit image": "Editar imagen", +"Image options": "Opciones de imagen", +"Zoom in": "Acercar", +"Zoom out": "Alejar", +"Crop": "Recortar", +"Resize": "Redimensionar", +"Orientation": "Orientaci\u00f3n", +"Brightness": "Brillo", +"Sharpen": "Forma", +"Contrast": "Contraste", +"Color levels": "Niveles de color", +"Gamma": "Gamma", +"Invert": "Invertir", +"Apply": "Aplicar", +"Back": "Atr\u00e1s", +"Insert date\/time": "Insertar fecha\/hora", +"Date\/time": "Fecha\/hora", +"Insert link": "Insertar enlace", +"Insert\/edit link": "Insertar\/editar enlace", +"Text to display": "Texto para mostrar", +"Url": "URL", +"Target": "Destino", +"None": "Ninguno", +"New window": "Nueva ventana", +"Remove link": "Quitar enlace", +"Anchors": "Anclas", +"Link": "Enlace", +"Paste or type a link": "Pega o introduce un enlace", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "El enlace que has introducido no parece ser una direcci\u00f3n de correo electr\u00f3nico. Quieres a\u00f1adir el prefijo necesario mailto: ?", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "El enlace que has introducido no parece ser una enlace externo. Quieres a\u00f1adir el prefijo necesario http:\/\/ ?", +"Link list": "Lista de enlaces", +"Insert video": "Insertar video", +"Insert\/edit video": "Insertar\/editar video", +"Insert\/edit media": "Insertar\/editar medio", +"Alternative source": "Enlace alternativo", +"Poster": "Miniatura", +"Paste your embed code below:": "Pega tu c\u00f3digo embebido debajo", +"Embed": "Incrustado", +"Media": "Media", +"Nonbreaking space": "Espacio fijo", +"Page break": "Salto de p\u00e1gina", +"Paste as text": "Pegar como texto", +"Preview": "Previsualizar", +"Print": "Imprimir", +"Save": "Guardar", +"Find": "Buscar", +"Replace with": "Reemplazar con", +"Replace": "Reemplazar", +"Replace all": "Reemplazar todo", +"Prev": "Anterior", +"Next": "Siguiente", +"Find and replace": "Buscar y reemplazar", +"Could not find the specified string.": "No se encuentra la cadena de texto especificada", +"Match case": "Coincidencia exacta", +"Whole words": "Palabras completas", +"Spellcheck": "Corrector ortogr\u00e1fico", +"Ignore": "Ignorar", +"Ignore all": "Ignorar todos", +"Finish": "Finalizar", +"Add to Dictionary": "A\u00f1adir al Diccionario", +"Insert table": "Insertar tabla", +"Table properties": "Propiedades de la tabla", +"Delete table": "Eliminar tabla", +"Cell": "Celda", +"Row": "Fila", +"Column": "Columna", +"Cell properties": "Propiedades de la celda", +"Merge cells": "Combinar celdas", +"Split cell": "Dividir celdas", +"Insert row before": "Insertar fila antes", +"Insert row after": "Insertar fila despu\u00e9s ", +"Delete row": "Eliminar fila", +"Row properties": "Propiedades de la fila", +"Cut row": "Cortar fila", +"Copy row": "Copiar fila", +"Paste row before": "Pegar la fila antes", +"Paste row after": "Pegar la fila despu\u00e9s", +"Insert column before": "Insertar columna antes", +"Insert column after": "Insertar columna despu\u00e9s", +"Delete column": "Eliminar columna", +"Cols": "Columnas", +"Rows": "Filas", +"Width": "Ancho", +"Height": "Alto", +"Cell spacing": "Espacio entre celdas", +"Cell padding": "Relleno de celda", +"Caption": "Subt\u00edtulo", +"Left": "Izquierda", +"Center": "Centrado", +"Right": "Derecha", +"Cell type": "Tipo de celda", +"Scope": "\u00c1mbito", +"Alignment": "Alineaci\u00f3n", +"H Align": "Alineamiento Horizontal", +"V Align": "Alineamiento Vertical", +"Top": "Arriba", +"Middle": "Centro", +"Bottom": "Abajo", +"Header cell": "Celda de la cebecera", +"Row group": "Grupo de filas", +"Column group": "Grupo de columnas", +"Row type": "Tipo de fila", +"Header": "Cabecera", +"Body": "Cuerpo", +"Footer": "Pie de p\u00e1gina", +"Border color": "Color del borde", +"Insert template": "Insertar plantilla", +"Templates": "Plantillas", +"Template": "Plantilla", +"Text color": "Color del texto", +"Background color": "Color de fondo", +"Custom...": "Personalizar...", +"Custom color": "Color personalizado", +"No color": "Sin color", +"Table of Contents": "Tabla de contenidos", +"Show blocks": "Mostrar bloques", +"Show invisible characters": "Mostrar caracteres invisibles", +"Words: {0}": "Palabras: {0}", +"{0} words": "{0} palabras", +"File": "Archivo", +"Edit": "Editar", +"Insert": "Insertar", +"View": "Ver", +"Format": "Formato", +"Table": "Tabla", +"Tools": "Herramientas", +"Powered by {0}": "Desarrollado por {0}", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "\u00c1rea de texto enriquecido. Pulse ALT-F9 para el menu. Pulse ALT-F10 para la barra de herramientas. Pulse ALT-0 para ayuda" +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/es_MX.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/es_MX.js new file mode 100644 index 0000000000..b0f2019ffe --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/es_MX.js @@ -0,0 +1,261 @@ +tinymce.addI18n('es_MX',{ +"Redo": "Deshacer", +"Undo": "Rehacer", +"Cut": "Cortar", +"Copy": "Copiar", +"Paste": "Pegar", +"Select all": "Seleccionar todo", +"New document": "Nuevo documento", +"Ok": "Aceptar", +"Cancel": "Cancelar", +"Visual aids": "Ayuda visual", +"Bold": "Negrita", +"Italic": "Cursiva", +"Underline": "Subrayado", +"Strikethrough": "Tachado", +"Superscript": "\u00cdndice", +"Subscript": "Sub\u00edndice", +"Clear formatting": "Limpiar formato", +"Align left": "Alinear a la izquierda", +"Align center": "Centrar", +"Align right": "Alinear a la derecha", +"Justify": "Justificar", +"Bullet list": "Lista de vi\u00f1eta", +"Numbered list": "Lista numerada", +"Decrease indent": "Decrementar identado", +"Increase indent": "Incrementar identado", +"Close": "Cerrar", +"Formats": "Formato", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "Su navegador no soporta acceso directo al portapapeles. Por favor haga uso de la combinaci\u00f3n de teclas Ctrl+X para cortar, Ctrl+C para copiar y Ctrl+V para pegar con el teclado. ", +"Headers": "Encabezado", +"Header 1": "Encabezado 1", +"Header 2": "Encabezado 2", +"Header 3": "Encabezado 3", +"Header 4": "Encabezado 4", +"Header 5": "Encabezado 5", +"Header 6": "Encabezado 6", +"Headings": "Encabezados", +"Heading 1": "Encabezados 1", +"Heading 2": "Encabezados 2", +"Heading 3": "Encabezados 3", +"Heading 4": "Encabezados 4", +"Heading 5": "Encabezados 5", +"Heading 6": "Encabezados 6", +"Preformatted": "Pre-formateado", +"Div": "Div", +"Pre": "Pre", +"Code": "C\u00f3digo", +"Paragraph": "P\u00e1rrafo", +"Blockquote": "Blockquote", +"Inline": "En l\u00ednea", +"Blocks": "Bloque", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "Se pegar\u00e1 en texto plano. El contenido se pegar\u00e1 como texto plano hasta que desactive esta opci\u00f3n.", +"Font Family": "Tipo de letra", +"Font Sizes": "Tama\u00f1o de letra", +"Class": "Clase", +"Browse for an image": "Ver por imagen", +"OR": "OR", +"Drop an image here": "Arrastra una imagen aqu\u00ed", +"Upload": "Subir", +"Block": "Bloque", +"Align": "Alineaci\u00f3n", +"Default": "Por defecto", +"Circle": "Circulo", +"Disc": "Disco", +"Square": "Cuadro", +"Lower Alpha": "Alfa min\u00fascula", +"Lower Greek": "Griega min\u00fascula", +"Lower Roman": "Romano min\u00fascula", +"Upper Alpha": "Alfa may\u00fascula", +"Upper Roman": "May\u00fascula Romana", +"Anchor": "Anclar", +"Name": "Nombre", +"Id": "Identificador", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "El Identificador debe comenzar con una letra, seguido solo por letras, n\u00fameros, puntos, guiones medios o guiones bajos. ", +"You have unsaved changes are you sure you want to navigate away?": "No se han guardado los cambios. \u00bfSeguro que desea abandonar la p\u00e1gina?", +"Restore last draft": "Restaurar el \u00faltimo borrador", +"Special character": "Caracter especial", +"Source code": "C\u00f3digo fuente", +"Insert\/Edit code sample": "Insertar\/Editar c\u00f3digo muestra", +"Language": "idioma", +"Code sample": "C\u00f3digo muestra", +"Color": "Color", +"R": "R", +"G": "G", +"B": "B", +"Left to right": "Izquierda a derecha", +"Right to left": "Derecha a Izquierda", +"Emoticons": "Emoticones", +"Document properties": "Propiedades del documento", +"Title": "T\u00edtulo", +"Keywords": "Palabras clave", +"Description": "Descripci\u00f3n ", +"Robots": "Robots", +"Author": "Autor", +"Encoding": "Codificaci\u00f3n", +"Fullscreen": "Pantalla completa", +"Action": "Acci\u00f3n", +"Shortcut": "Atajo", +"Help": "Ayuda", +"Address": "Direcci\u00f3n", +"Focus to menubar": "Enfocar en barra de menu", +"Focus to toolbar": "Enfocar en barra de herramientas", +"Focus to element path": "Enfocar ruta del elemento", +"Focus to contextual toolbar": "Enfocar en barra de herramientas contextual", +"Insert link (if link plugin activated)": "Insertar enlace (si enlace del plugin est\u00e1 activo)", +"Save (if save plugin activated)": "Guardar (si el plugin guardar est\u00e1 activo)", +"Find (if searchreplace plugin activated)": "Buscar (si el plugin buscar\/reemplazar est\u00e1 activo)", +"Plugins installed ({0}):": "Plugins instalados ({0}):", +"Premium plugins:": "Plugins premium:", +"Learn more...": "Aprende m\u00e1s...", +"You are using {0}": "est\u00e1s usando {0}", +"Plugins": "Plugins", +"Handy Shortcuts": "Atajos \u00fatiles", +"Horizontal line": "L\u00ednea Horizontal", +"Insert\/edit image": "Insertar\/editar imagen", +"Image description": "Descripci\u00f3n de imagen", +"Source": "Origen", +"Dimensions": "Dimensiones", +"Constrain proportions": "Restringir proporciones", +"General": "General", +"Advanced": "Avanzado", +"Style": "Estilo", +"Vertical space": "Espacio vertical", +"Horizontal space": "Espacio horizontal", +"Border": "Borde", +"Insert image": "Insertar imagen", +"Image": "Imagen", +"Image list": "Lista de im\u00e1genes", +"Rotate counterclockwise": "Rotar en sentido contrario a las manecillas", +"Rotate clockwise": "Rotar en sentido de las manecillas", +"Flip vertically": "Voltear verticalmente", +"Flip horizontally": "Volter horizontalmente", +"Edit image": "Editar imagen", +"Image options": "Opciones de la imagen", +"Zoom in": "Acercar", +"Zoom out": "Alejar", +"Crop": "Recortar", +"Resize": "Cambiar tama\u00f1o", +"Orientation": "Orientaci\u00f3n", +"Brightness": "Brillo", +"Sharpen": "Nitidez", +"Contrast": "Contraste", +"Color levels": "Niveles de Color", +"Gamma": "Gamma", +"Invert": "Invertir", +"Apply": "Aplicar", +"Back": "Regresar", +"Insert date\/time": "Insertar fecha\/hora", +"Date\/time": "Fecha\/hora", +"Insert link": "Insertar enlace", +"Insert\/edit link": "Inserta\/editar enlace", +"Text to display": "Texto a mostrar", +"Url": "Url", +"Target": "Objetivo", +"None": "Ninguno", +"New window": "Nueva ventana", +"Remove link": "Eliminar elnace", +"Anchors": "Anclas", +"Link": "Enlace", +"Paste or type a link": "Pega o escribe un enlace", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "El URL que ha insertado tiene formato de correo electr\u00f3nico. \u00bfDesea agregar con prefijo \"mailto:\"?", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "El URL que ha ingresado es un enlace externo. \u00bfDesea agregar el prefijo \"http:\/\/\"?", +"Link list": "Lista de enlaces", +"Insert video": "Insertar video", +"Insert\/edit video": "Insertar\/editar video", +"Insert\/edit media": "Insertar\/editar multimedia", +"Alternative source": "Fuente alternativa", +"Poster": "Cartel", +"Paste your embed code below:": "Pegue su c\u00f3digo de inserci\u00f3n abajo:", +"Embed": "Incrustar", +"Media": "Multimedia", +"Nonbreaking space": "Espacio de no separaci\u00f3n", +"Page break": "Salto de p\u00e1gina ", +"Paste as text": "Copiar como texto", +"Preview": "Vista previa ", +"Print": "Imprimir", +"Save": "Guardar", +"Find": "Buscar", +"Replace with": "Remplazar con", +"Replace": "Remplazar", +"Replace all": "Remplazar todo", +"Prev": "Anterior", +"Next": "Siguiente", +"Find and replace": "Buscar y reemplazar", +"Could not find the specified string.": "No se ha encontrado la cadena especificada.", +"Match case": "Coincidencia", +"Whole words": "Palabras completas", +"Spellcheck": "Revisi\u00f3n ortogr\u00e1fica", +"Ignore": "Ignorar", +"Ignore all": "Ignorar todo", +"Finish": "Terminar", +"Add to Dictionary": "Agregar al diccionario ", +"Insert table": "Insertar tabla", +"Table properties": "Propiedades de tabla", +"Delete table": "Eliminar tabla", +"Cell": "Celda", +"Row": "Rengl\u00f3n ", +"Column": "Columna", +"Cell properties": "Propiedades de celda", +"Merge cells": "Unir celdas", +"Split cell": "Dividir celdas", +"Insert row before": "Insertar rengl\u00f3n antes", +"Insert row after": "Insertar rengl\u00f3n despu\u00e9s", +"Delete row": "Eliminar rengl\u00f3n ", +"Row properties": "Propiedades del rengl\u00f3n ", +"Cut row": "Cortar renglon", +"Copy row": "Copiar rengl\u00f3n ", +"Paste row before": "Pegar rengl\u00f3n antes", +"Paste row after": "Pegar rengl\u00f3n despu\u00e9s", +"Insert column before": "Insertar columna antes", +"Insert column after": "Insertar columna despu\u00e9s", +"Delete column": "Eliminar columna", +"Cols": "Columnas", +"Rows": "Renglones ", +"Width": "Ancho", +"Height": "Alto", +"Cell spacing": "Espacio entre celdas", +"Cell padding": "Relleno de la celda", +"Caption": "Subt\u00edtulo", +"Left": "Izquierda", +"Center": "Centro", +"Right": "Derecha", +"Cell type": "Tipo de celda", +"Scope": "Alcance", +"Alignment": "Alineaci\u00f3n ", +"H Align": "Alineaci\u00f3n Horizontal", +"V Align": "Alineaci\u00f3n Vertical", +"Top": "Arriba", +"Middle": "Centrado", +"Bottom": "Abajo", +"Header cell": "Celda de encabezado", +"Row group": "Grupo de renglones", +"Column group": "Grupo de columnas", +"Row type": "Tipo de rengl\u00f3n ", +"Header": "Encabezado", +"Body": "Cuerpo", +"Footer": "Pie", +"Border color": "Color del borde", +"Insert template": "Insertar plantilla", +"Templates": "Plantilla", +"Template": "Plantilla", +"Text color": "Color de letra", +"Background color": "Color de fondo", +"Custom...": "Personalizar", +"Custom color": "Perzonalizar color", +"No color": "Sin color", +"Table of Contents": "Tabla de Contenidos", +"Show blocks": "Mostrar bloques", +"Show invisible characters": "Mostrar caracteres invisibles", +"Words: {0}": "Palabras:{0}", +"{0} words": "{0} palabras", +"File": "Archivo", +"Edit": "Editar", +"Insert": "Insertar", +"View": "Vistas", +"Format": "Formato", +"Table": "Tabla", +"Tools": "Herramientas", +"Powered by {0}": "Creado con {0}", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Presione dentro del \u00e1rea de texto ALT-F9 para invocar el men\u00fa, ALT-F10 para la barra de herramientas y ALT-0 para la ayuda." +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/et.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/et.js new file mode 100644 index 0000000000..c6beeeb510 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/et.js @@ -0,0 +1,261 @@ +tinymce.addI18n('et',{ +"Redo": "Tee uuesti", +"Undo": "V\u00f5ta tagasi", +"Cut": "L\u00f5ika", +"Copy": "Kopeeri", +"Paste": "Kleebi", +"Select all": "Vali k\u00f5ik", +"New document": "Uus dokument", +"Ok": "Ok", +"Cancel": "Katkesta", +"Visual aids": "N\u00e4itevahendid", +"Bold": "Rasvane", +"Italic": "Kaldkiri", +"Underline": "Allakriipsutatud", +"Strikethrough": "L\u00e4bikriipsutatud", +"Superscript": "\u00dclaindeks", +"Subscript": "Alaindeks", +"Clear formatting": "Puhasta vorming", +"Align left": "Joonda vasakule", +"Align center": "Joonda keskele", +"Align right": "Joonda paremale", +"Justify": "Joonda r\u00f6\u00f6pselt", +"Bullet list": "J\u00e4rjestamata loend", +"Numbered list": "J\u00e4rjestatud loend", +"Decrease indent": "V\u00e4henda taanet", +"Increase indent": "Suurenda taanet", +"Close": "Sulge", +"Formats": "Vormingud", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "Sinu veebilehitseja ei toeta otsest ligip\u00e4\u00e4su l\u00f5ikelauale. Palun kasuta selle asemel klaviatuuri kiirk\u00e4sklusi Ctrl+X\/C\/V.", +"Headers": "P\u00e4ised", +"Header 1": "Pealkiri 1", +"Header 2": "Pealkiri 2", +"Header 3": "Pealkiri 3", +"Header 4": "Pealkiri 4", +"Header 5": "Pealkiri 5", +"Header 6": "Pealkiri 6", +"Headings": "Pealkirjad", +"Heading 1": "Pealkiri 1", +"Heading 2": "Pealkiri 2", +"Heading 3": "Pealkiri 3", +"Heading 4": "Pealkiri 4", +"Heading 5": "Pealkiri 5", +"Heading 6": "Pealkiri 6", +"Preformatted": "Eelvormindaud", +"Div": "Sektsioon", +"Pre": "Eelvormindatud", +"Code": "Kood", +"Paragraph": "L\u00f5ik", +"Blockquote": "Plokktsitaat", +"Inline": "Reasisene", +"Blocks": "Plokid", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "Asetamine on n\u00fc\u00fcd tekstire\u017eiimis. Sisu asetatakse n\u00fc\u00fcd lihttekstina, kuni sa l\u00fclitad selle valiku v\u00e4lja.", +"Font Family": "Kirjastiilid", +"Font Sizes": "Kirja suurused", +"Class": "Klass", +"Browse for an image": "Sirvi pilte", +"OR": "V\u00d5I", +"Drop an image here": "Kukuta pilt siia", +"Upload": "\u00dcles laadimine", +"Block": "Plokk", +"Align": "Joonda", +"Default": "Vaikimisi", +"Circle": "Ring", +"Disc": "Ketas", +"Square": "Ruut", +"Lower Alpha": "V\u00e4iket\u00e4hed (a, b, c)", +"Lower Greek": "Kreeka v\u00e4iket\u00e4hed (\u03b1, \u03b2, \u03b3)", +"Lower Roman": "Rooma v\u00e4iket\u00e4hed (i, ii, iii)", +"Upper Alpha": "Suurt\u00e4hed (A, B, C)", +"Upper Roman": "Rooma suurt\u00e4hed (I, II, III)", +"Anchor": "Ankur", +"Name": "Nimi", +"Id": "ID", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "ID peaks algama t\u00e4hega ning sellele peaks j\u00e4rgnema ainult t\u00e4hed, arvud, sidekriipsud, punktid, koolonid v\u00f5i alakriipsud.", +"You have unsaved changes are you sure you want to navigate away?": "Sul on salvestamata muudatusi. Oled Sa kindel, et soovid mujale navigeeruda?", +"Restore last draft": "Taasta viimane mustand", +"Special character": "Erim\u00e4rk", +"Source code": "L\u00e4htekood", +"Insert\/Edit code sample": "Sisesta\/muuda koodin\u00e4idis", +"Language": "Keel", +"Code sample": "Koodi n\u00e4idis", +"Color": "V\u00e4rv", +"R": "R", +"G": "G", +"B": "B", +"Left to right": "Vasakult paremale", +"Right to left": "Paremalt vasakule", +"Emoticons": "Emotikonid", +"Document properties": "Dokumendi omadused", +"Title": "Pealkiri", +"Keywords": "M\u00e4rks\u00f5nad", +"Description": "Kirjeldus", +"Robots": "Robotid", +"Author": "Autor", +"Encoding": "M\u00e4rgistik", +"Fullscreen": "T\u00e4isekraan", +"Action": "Tegevus", +"Shortcut": "Otsetee", +"Help": "Abiinfo", +"Address": "Aadress", +"Focus to menubar": "Fookus men\u00fc\u00fcribale", +"Focus to toolbar": "Fookus t\u00f6\u00f6riistaribale", +"Focus to element path": "Fookus elemendi asukohale", +"Focus to contextual toolbar": "Fookus kontekstimen\u00fc\u00fcle", +"Insert link (if link plugin activated)": "Sisesta link (kui lingi plugin on aktiveeritud)", +"Save (if save plugin activated)": "Salvesta (kui salvestamise plugin on aktiveeritud)", +"Find (if searchreplace plugin activated)": "Otsi (kui plugin searchreplace on aktiveeritud)", +"Plugins installed ({0}):": "Pluginad on paigaldatud ({0}):", +"Premium plugins:": "Tasulised pluginad:", +"Learn more...": "Vaata lisainfot...", +"You are using {0}": "Sa kasutad {0}", +"Plugins": "Pluginad", +"Handy Shortcuts": "Mugavad otseteed", +"Horizontal line": "Horisontaaljoon", +"Insert\/edit image": "Lisa\/muuda pilt", +"Image description": "Pildi kirjeldus", +"Source": "Allikas", +"Dimensions": "M\u00f5\u00f5tmed", +"Constrain proportions": "S\u00e4ilita kuvasuhe", +"General": "\u00dcldine", +"Advanced": "T\u00e4iendavad seaded", +"Style": "Stiil", +"Vertical space": "P\u00fcstine vahe", +"Horizontal space": "Reavahe", +"Border": "\u00c4\u00e4ris", +"Insert image": "Lisa pilt", +"Image": "Pilt", +"Image list": "Piltide nimekiri", +"Rotate counterclockwise": "P\u00f6\u00f6ra vastup\u00e4eva", +"Rotate clockwise": "P\u00f6\u00f6ra p\u00e4rip\u00e4eva", +"Flip vertically": "Peegelda vertikaalselt", +"Flip horizontally": "Peegelda horisontaalselt", +"Edit image": "Muuda pilti", +"Image options": "Pildi valikud", +"Zoom in": "Suumi sisse", +"Zoom out": "Suumi v\u00e4lja", +"Crop": "L\u00f5ika", +"Resize": "Muuda suurust", +"Orientation": "Suund", +"Brightness": "Heledus", +"Sharpen": "Teravamaks", +"Contrast": "Kontrast", +"Color levels": "V\u00e4rvi tasemed", +"Gamma": "Gamma", +"Invert": "P\u00f6\u00f6ra v\u00e4rvid", +"Apply": "Rakenda", +"Back": "Tagasi", +"Insert date\/time": "Lisa kuup\u00e4ev\/kellaaeg", +"Date\/time": "Kuup\u00e4ev\/kellaaeg", +"Insert link": "Lisa link", +"Insert\/edit link": "Lisa\/muuda link", +"Text to display": "Kuvatav tekst", +"Url": "Viide (url)", +"Target": "Sihtm\u00e4rk", +"None": "Puudub", +"New window": "Uus aken", +"Remove link": "Eemalda link", +"Anchors": "Ankrud", +"Link": "Link", +"Paste or type a link": "Aseta v\u00f5i sisesta link", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "URL, mille sa sisestasid, n\u00e4ib olevat e-posti aadress. Kas sa soovid lisada sellele eesliite mailto: ?", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "URL, mille sa sisestasid, n\u00e4ib olevat v\u00e4line link. Kas sa soovid lisada sellele eesliite http:\/\/ ?", +"Link list": "Linkide nimekiri", +"Insert video": "Lisa video", +"Insert\/edit video": "Lisa\/muuda video", +"Insert\/edit media": "Lisa\/muuda multimeediat", +"Alternative source": "Teine allikas", +"Poster": "Lisaja", +"Paste your embed code below:": "Kleebi oma manustamiskood siia alla:", +"Embed": "Manusta", +"Media": "Multimeedia", +"Nonbreaking space": "T\u00fchim\u00e4rk (nbsp)", +"Page break": "Lehevahetus", +"Paste as text": "Aseta tekstina", +"Preview": "Eelvaade", +"Print": "Tr\u00fcki", +"Save": "Salvesta", +"Find": "Otsi", +"Replace with": "Asendus", +"Replace": "Asenda", +"Replace all": "Asenda k\u00f5ik", +"Prev": "Eelm", +"Next": "J\u00e4rg", +"Find and replace": "Otsi ja asenda", +"Could not find the specified string.": "Ei suutnud leida etteantud s\u00f5net.", +"Match case": "Erista suur- ja v\u00e4iket\u00e4hti", +"Whole words": "Terviks\u00f5nad", +"Spellcheck": "\u00d5igekirja kontroll", +"Ignore": "Eira", +"Ignore all": "Eira k\u00f5iki", +"Finish": "L\u00f5peta", +"Add to Dictionary": "Lisa s\u00f5naraamatusse", +"Insert table": "Lisa tabel", +"Table properties": "Tabeli omadused", +"Delete table": "Kustuta tabel", +"Cell": "Lahter", +"Row": "Rida", +"Column": "Tulp", +"Cell properties": "Lahtri omadused", +"Merge cells": "\u00dchenda lahtrid", +"Split cell": "T\u00fckelda lahter", +"Insert row before": "Lisa rida enne", +"Insert row after": "Lisa rida j\u00e4rele", +"Delete row": "Kustuta rida", +"Row properties": "Rea omadused", +"Cut row": "L\u00f5ika rida", +"Copy row": "Kopeeri rida", +"Paste row before": "Kleebi rida enne", +"Paste row after": "Kleebi rida j\u00e4rele", +"Insert column before": "Lisa tulp enne", +"Insert column after": "Lisa tulp j\u00e4rele", +"Delete column": "Kustuta tulp", +"Cols": "Veerud", +"Rows": "Read", +"Width": "Laius", +"Height": "K\u00f5rgus", +"Cell spacing": "Lahtrivahe", +"Cell padding": "Lahtri sisu ja tabeli \u00e4\u00e4rise vahe", +"Caption": "Alapealkiri", +"Left": "Vasakul", +"Center": "Keskel", +"Right": "Paremal", +"Cell type": "Lahtri t\u00fc\u00fcp", +"Scope": "Ulatus", +"Alignment": "Joondus", +"H Align": "H Joondus", +"V Align": "V Joondus", +"Top": "\u00dcleval", +"Middle": "Keskel", +"Bottom": "All", +"Header cell": "P\u00e4islahter", +"Row group": "Ridade r\u00fchm", +"Column group": "Veergude r\u00fchm", +"Row type": "Rea t\u00fc\u00fcp", +"Header": "P\u00e4is", +"Body": "P\u00f5hiosa", +"Footer": "Jalus", +"Border color": "Piirjoone v\u00e4rv", +"Insert template": "Lisa mall", +"Templates": "Mallid", +"Template": "Mall", +"Text color": "Teksti v\u00e4rv", +"Background color": "Tausta v\u00e4rv", +"Custom...": "Kohandatud...", +"Custom color": "Kohandatud v\u00e4rv", +"No color": "V\u00e4rvi pole", +"Table of Contents": "Sisukord", +"Show blocks": "N\u00e4ita plokke", +"Show invisible characters": "N\u00e4ita peidetud m\u00e4rke", +"Words: {0}": "S\u00f5nu: {0}", +"{0} words": "{0} s\u00f5na", +"File": "Fail", +"Edit": "Muuda", +"Insert": "Sisesta", +"View": "Vaade", +"Format": "Vorming", +"Table": "Tabel", +"Tools": "T\u00f6\u00f6riistad", +"Powered by {0}": "Kasutatud tarkvara {0}", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Rikastatud teksti ala. Men\u00fc\u00fc jaoks vajuta ALT-F9. T\u00f6\u00f6riistariba jaoks vajuta ALT-F10. Abi saamiseks vajuta ALT-0." +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/eu.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/eu.js new file mode 100644 index 0000000000..c4374394d4 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/eu.js @@ -0,0 +1,261 @@ +tinymce.addI18n('eu',{ +"Redo": "Berregin", +"Undo": "Desegin", +"Cut": "Ebaki", +"Copy": "Kopiatu", +"Paste": "Itsatsi", +"Select all": "Hautatu dena", +"New document": "Dokumentu berria", +"Ok": "Ondo", +"Cancel": "Ezeztatu", +"Visual aids": "Laguntza bisualak", +"Bold": "Lodia", +"Italic": "Etzana", +"Underline": "Azpimarratua", +"Strikethrough": "Marratua", +"Superscript": "Goi-indize", +"Subscript": "Azpiindize", +"Clear formatting": "Garbitu formatua", +"Align left": "Lerrokatu ezkerrean", +"Align center": "Lerrokatu erdian", +"Align right": "Lerrokatu eskuinean", +"Justify": "Justifikatuta", +"Bullet list": "Bulet zerrenda", +"Numbered list": "Zerrenda zenbatua", +"Decrease indent": "Txikitu koska", +"Increase indent": "Handitu koska", +"Close": "Itxi", +"Formats": "Formatuak", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "Zure nabigatzaileak ez du arbela zuzenean erabiltzeko euskarririk. Mesedez erabili CTRL+X\/C\/V teklatuko lasterbideak.", +"Headers": "Goiburuak", +"Header 1": "1 Goiburua", +"Header 2": "2 Goiburua", +"Header 3": "3 Goiburua", +"Header 4": "4 Goiburua", +"Header 5": "5 Goiburua", +"Header 6": "6 Goiburua", +"Headings": "Izenburuak", +"Heading 1": "1. izenburua", +"Heading 2": "2. izenburua", +"Heading 3": "3. izenburua", +"Heading 4": "4. izenburua", +"Heading 5": "5. izenburua", +"Heading 6": "6. izenburua", +"Preformatted": "Aurreformateatuta", +"Div": "Div", +"Pre": "Pre", +"Code": "Kodea", +"Paragraph": "Paragrafoa", +"Blockquote": "Blockquote", +"Inline": "Lerroan", +"Blocks": "Blokeak", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "Itsatsi testu arrunt moduan dago orain. Edukiak testu arruntak bezala itsatsiko dira aukera hau itzaltzen duzunera arte.", +"Font Family": "Letra-tipo familia", +"Font Sizes": "Letra-tamainak", +"Class": "Klasea", +"Browse for an image": "Irudia arakatu", +"OR": "EDO", +"Drop an image here": "Irudia hona ekarri", +"Upload": "Kargatu", +"Block": "Blokea", +"Align": "Alineatu", +"Default": "Lehenetstia", +"Circle": "Zirkulua", +"Disc": "Diskoa", +"Square": "Karratua", +"Lower Alpha": "Behe alfa", +"Lower Greek": "Behe grekoa", +"Lower Roman": "Behe erromatarra", +"Upper Alpha": "Goi alfa", +"Upper Roman": "Goi erromatarra", +"Anchor": "Esteka", +"Name": "Izena", +"Id": "Id", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "Ida hizki batekin hasi behar da, jarraian hizkiak, zenbakiak, gidoiak, puntuak, bi-puntu edo azpiko marrak bakarrik izan ditzake.", +"You have unsaved changes are you sure you want to navigate away?": "Gorde gabeko aldaketak dituzu, zihur zaude hemendik irten nahi duzula?", +"Restore last draft": "Leheneratu azken zirriborroa", +"Special character": "Karaktere bereziak", +"Source code": "Iturburu-kodea", +"Insert\/Edit code sample": "Txertatu\/editatu kode adibidea", +"Language": "Hizkuntza", +"Code sample": "Kode adibidea", +"Color": "Kolorea", +"R": "R", +"G": "G", +"B": "B", +"Left to right": "Ezkerretik eskuinera", +"Right to left": "Eskuinetik ezkerrera", +"Emoticons": "Irrifartxoak", +"Document properties": "Dokumentuaren propietateak", +"Title": "Titulua", +"Keywords": "Hitz gakoak", +"Description": "Deskribapena", +"Robots": "Robotak", +"Author": "Egilea", +"Encoding": "Encoding", +"Fullscreen": "Pantaila osoa", +"Action": "Akzioa", +"Shortcut": "Laster tekla", +"Help": "Laguntza", +"Address": "Helbidea", +"Focus to menubar": "Fokoa menu-barrara eraman", +"Focus to toolbar": "Fokoa tresna-barrara eraman", +"Focus to element path": "Fokoa elementuaren bidera eraman", +"Focus to contextual toolbar": "Fokoa kontestuko tresna-barrara eraman", +"Insert link (if link plugin activated)": "Lotura txertatu (lotura plugina aktibatuta badago)", +"Save (if save plugin activated)": "Gorde (gordetzeko plugina aktibatuta badago)", +"Find (if searchreplace plugin activated)": "Bilatu (bilatuordezkatu plugina instalatuta badago)", +"Plugins installed ({0}):": "Instalatutako pluginak ({0}):", +"Premium plugins:": "Premium pluginak:", +"Learn more...": "Gehiago ikasi...", +"You are using {0}": "{0} erabiltzen ari zara", +"Plugins": "Pluginak", +"Handy Shortcuts": "Laster-tekla erabilgarriak", +"Horizontal line": "Marra horizontala", +"Insert\/edit image": "Irudia txertatu\/editatu", +"Image description": "Irudiaren deskribapena", +"Source": "Iturburua", +"Dimensions": "Neurriak", +"Constrain proportions": "Zerraditu proportzioak", +"General": "Orokorra", +"Advanced": "Aurreratua", +"Style": "Estiloa", +"Vertical space": "Hutsune bertikala", +"Horizontal space": "Hutsune horizontala", +"Border": "Ertza", +"Insert image": "Irudia txertatu", +"Image": "Irudia", +"Image list": "Irudi zerrenda", +"Rotate counterclockwise": "Erlojuaren aurkako eran biratu", +"Rotate clockwise": "Erlojuaren eran biratu", +"Flip vertically": "Bertikalki irauli", +"Flip horizontally": "Horizontalki irauli", +"Edit image": "Irudia editatu", +"Image options": "Irudiaren aukerak", +"Zoom in": "Zooma handiagotu", +"Zoom out": "Zooma txikiagotu", +"Crop": "Moztu", +"Resize": "Tamaina aldatu", +"Orientation": "Orientazioa", +"Brightness": "Distira", +"Sharpen": "Zorroztu", +"Contrast": "Kontrastatu", +"Color levels": "Kolore mailak", +"Gamma": "Gamma", +"Invert": "Biratu", +"Apply": "Gorde", +"Back": "Atzera", +"Insert date\/time": "Data\/ordua txertatu", +"Date\/time": "Data\/ordua", +"Insert link": "Esteka txertatu", +"Insert\/edit link": "Esteka txertatu\/editatu", +"Text to display": "Bistaratzeko testua", +"Url": "Url", +"Target": "Target", +"None": "Bat ere ez", +"New window": "Lehio berria", +"Remove link": "Kendu esteka", +"Anchors": "Estekak", +"Link": "Lotura", +"Paste or type a link": "Itsatsu edo idatzi lotura", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "Sartu duzun URL-ak e-posta helbidea dela dirudi. Nahi duzu dagokion mailto: aurrizkia gehitzea?", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "Sartu duzun URL-ak kanpoko esteka dela dirudi. Nahi duzu dagokion http:\/\/ aurrizkia gehitzea?", +"Link list": "Loturen zerrenda", +"Insert video": "Bideoa txertatu", +"Insert\/edit video": "Bideoa txertatu\/editatu", +"Insert\/edit media": "Media txertatu\/editatu", +"Alternative source": "Iturburu alternatiboa", +"Poster": "Poster-a", +"Paste your embed code below:": "Itsatsi hemen zure enkapsulatzeko kodea:", +"Embed": "Kapsulatu", +"Media": "Media", +"Nonbreaking space": "Zuriune zatiezina", +"Page break": "Orrialde-jauzia", +"Paste as text": "Itsatsi testu bezala", +"Preview": "Aurrebista", +"Print": "Inprimatu", +"Save": "Gorde", +"Find": "Bilatu", +"Replace with": "Honekin ordeztu", +"Replace": "Ordeztu", +"Replace all": "Ordeztu dena", +"Prev": "Aurrekoa", +"Next": "Hurrengoa", +"Find and replace": "Bilatu eta ordeztu", +"Could not find the specified string.": "Ezin izan da zehaztutako katea aurkitu.", +"Match case": "Maiuskula\/minuskula", +"Whole words": "hitz osoak", +"Spellcheck": "Egiaztapenak", +"Ignore": "Ez ikusi", +"Ignore all": "Ez ikusi guztia", +"Finish": "Amaitu", +"Add to Dictionary": "Hiztegira gehitu", +"Insert table": "Txertatu taula", +"Table properties": "Taularen propietateak", +"Delete table": "Taula ezabatu", +"Cell": "Gelaxka", +"Row": "Errenkada", +"Column": "Zutabea", +"Cell properties": "Gelaxkaren propietateak", +"Merge cells": "Batu gelaxkak", +"Split cell": "Banatu gelaxkak", +"Insert row before": "Txertatu errenkada aurretik", +"Insert row after": "Txertatu errenkada ostean", +"Delete row": "Ezabatu errenkada", +"Row properties": "Errenkadaren propietateak", +"Cut row": "Ebaki errenkada", +"Copy row": "Kopiatu errenkada", +"Paste row before": "Itsatsi errenkada aurretik", +"Paste row after": "Itsatsi errenkada ostean", +"Insert column before": "Txertatu zutabe aurretik", +"Insert column after": "Txertatu zutabea ostean", +"Delete column": "Ezabatu zutabea", +"Cols": "Zutabeak", +"Rows": "Errenkadak", +"Width": "Zabalera", +"Height": "Altuera", +"Cell spacing": "Gelaxka arteko tartea", +"Cell padding": "Gelaxken betegarria", +"Caption": "Epigrafea", +"Left": "Ezkerra", +"Center": "Erdia", +"Right": "Eskuina", +"Cell type": "Gelaxka mota", +"Scope": "Esparrua", +"Alignment": "Lerrokatzea", +"H Align": "Lerrokatze horizontala", +"V Align": "Lerrokatze bertikala", +"Top": "Goian", +"Middle": "Erdian", +"Bottom": "Behean", +"Header cell": "Goiburuko gelaxka", +"Row group": "Lerro taldea", +"Column group": "Zutabe taldea", +"Row type": "Lerro mota", +"Header": "Goiburua", +"Body": "Gorputza", +"Footer": "Oina", +"Border color": "Inguruko marraren kolorea", +"Insert template": "Txertatu txantiloia", +"Templates": "Txantiloiak", +"Template": "Txantiloia", +"Text color": "Testuaren kolorea", +"Background color": "Atzeko kolorea", +"Custom...": "Pertsonalizatu", +"Custom color": "Pertsonalizatutako kolorea", +"No color": "Kolorerik ez", +"Table of Contents": "Edukien taula", +"Show blocks": "Erakutsi blokeak", +"Show invisible characters": "Erakutsi karaktere izkutuak", +"Words: {0}": "Hitzak: {0}", +"{0} words": "{0} hitz", +"File": "Fitxategia", +"Edit": "Editatu", +"Insert": "Sartu", +"View": "Ikusi", +"Format": "Formatua", +"Table": "Taula", +"Tools": "Tresnak", +"Powered by {0}": "{0}rekin egina", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Testu aberastuko area. Sakatu ALT-F9 menurako. Sakatu ALT-F10 tresna-barrarako. Sakatu ALT-0 laguntzarako" +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/fa_IR.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/fa_IR.js new file mode 100644 index 0000000000..7ac42b6831 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/fa_IR.js @@ -0,0 +1,262 @@ +tinymce.addI18n('fa_IR',{ +"Redo": "\u0628\u0627\u0632 \u0646\u0634\u0627\u0646", +"Undo": "\u0628\u0627\u0632 \u06af\u0631\u062f\u0627\u0646", +"Cut": "\u0628\u0631\u0634", +"Copy": "\u0631\u0648\u0646\u0648\u06cc\u0633\u06cc", +"Paste": "\u0686\u0633\u0628\u0627\u0646\u062f\u0646", +"Select all": "\u0627\u0646\u062a\u062e\u0627\u0628 \u0647\u0645\u0647", +"New document": "\u0633\u0646\u062f \u062c\u062f\u06cc\u062f", +"Ok": "\u062a\u0627\u06cc\u06cc\u062f", +"Cancel": "\u0627\u0646\u0635\u0631\u0627\u0641", +"Visual aids": "\u06a9\u0645\u06a9 \u0628\u0635\u0631\u06cc", +"Bold": "\u062f\u0631\u0634\u062a", +"Italic": "\u06a9\u062c", +"Underline": "\u0632\u06cc\u0631 \u062e\u0637", +"Strikethrough": "\u062e\u0637 \u062e\u0648\u0631\u062f\u0647", +"Superscript": "\u0646\u0645\u0627", +"Subscript": "\u067e\u0627\u06cc\u0647", +"Clear formatting": "\u067e\u0627\u06a9 \u06a9\u0631\u062f\u0646 \u0642\u0627\u0644\u0628 \u0628\u0646\u062f\u06cc", +"Align left": "\u0686\u067e \u0686\u06cc\u0646", +"Align center": "\u0648\u0633\u0637 \u0686\u06cc\u0646", +"Align right": "\u0631\u0627\u0633\u062a \u0686\u06cc\u0646", +"Justify": "\u062a\u0631\u0627\u0632 \u062f\u0648 \u0637\u0631\u0641\u0647", +"Bullet list": "\u0641\u0647\u0631\u0633\u062a \u0646\u0634\u0627\u0646\u0647 \u062f\u0627\u0631", +"Numbered list": "\u0641\u0647\u0631\u0633\u062a \u0634\u0645\u0627\u0631\u0647 \u062f\u0627\u0631", +"Decrease indent": "\u06a9\u0627\u0647\u0634 \u062a\u0648\u0631\u0641\u062a\u06af\u06cc", +"Increase indent": "\u0627\u0641\u0632\u0627\u06cc\u0634 \u062a\u0648\u0631\u0641\u062a\u06af\u06cc", +"Close": "\u0628\u0633\u062a\u0646", +"Formats": "\u0642\u0627\u0644\u0628 \u0647\u0627", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "\u0645\u0631\u0648\u0631\u06af\u0631 \u0634\u0645\u0627 \u062f\u0633\u062a\u0631\u0633\u06cc \u0645\u0633\u062a\u0642\u06cc\u0645 \u0628\u0647 \u06a9\u0644\u06cc\u067e \u0628\u0648\u0631\u062f \u0631\u0627 \u067e\u0634\u062a\u06cc\u0628\u0627\u0646\u06cc \u0646\u0645\u06cc \u06a9\u0646\u062f\u060c \u0644\u0637\u0641\u0627 \u0627\u0632 \u0645\u06cc\u0627\u0646\u0628\u0631\u0647\u0627\u06cc Ctrl+X\/C\/V \u0635\u0641\u062d\u0647 \u06a9\u0644\u06cc\u062f \u0627\u0633\u062a\u0641\u0627\u062f\u0647 \u0646\u0645\u0627\u06cc\u06cc\u062f . ", +"Headers": "\u0633\u0631 \u0622\u0645\u062f\u0647\u0627", +"Header 1": "\u0633\u0631 \u0622\u0645\u062f 1", +"Header 2": "\u0633\u0631 \u0622\u0645\u062f 2", +"Header 3": "\u0633\u0631 \u0622\u0645\u062f 3", +"Header 4": "\u0633\u0631 \u0622\u0645\u062f 4", +"Header 5": "\u0633\u0631 \u0622\u0645\u062f 5", +"Header 6": "\u0633\u0631 \u0622\u0645\u062f 6", +"Headings": "\u0639\u0646\u0627\u0648\u06cc\u0646", +"Heading 1": "\u0639\u0646\u0648\u0627\u0646 1", +"Heading 2": "\u0639\u0646\u0648\u0627\u0646 2", +"Heading 3": "\u0639\u0646\u0648\u0627\u0646 3", +"Heading 4": "\u0639\u0646\u0648\u0627\u0646 4", +"Heading 5": "\u0639\u0646\u0648\u0627\u0646 5", +"Heading 6": "\u0639\u0646\u0648\u0627\u0646 6", +"Preformatted": "\u0627\u0632 \u067e\u06cc\u0634 \u0642\u0627\u0644\u0628 \u0628\u0646\u062f\u06cc \u0634\u062f\u0647", +"Div": "\u0628\u0644\u0648\u06a9 \u062c\u062f\u0627 \u0633\u0627\u0632 (\u062a\u06af Div)", +"Pre": "\u0628\u0644\u0648\u06a9 \u0645\u062a\u0646 \u0642\u0627\u0644\u0628 \u062f\u0627\u0631 (\u062a\u06af Pre)", +"Code": "\u0628\u0644\u0648\u06a9 \u06a9\u062f\u0646\u0648\u06cc\u0633\u06cc (\u062a\u06a9 Code)", +"Paragraph": "\u067e\u0627\u0631\u0627\u06af\u0631\u0627\u0641 (\u062a\u06af P)", +"Blockquote": "\u0628\u0644\u0648\u06a9 \u0646\u0642\u0644 \u0642\u0648\u0644 (\u062a\u06af BlockQuote)", +"Inline": "\u0631\u0648 \u062e\u0637", +"Blocks": "\u0628\u0644\u0648\u06a9 \u0647\u0627", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "\u0627\u0645\u06a9\u0627\u0646 \u0686\u0633\u0628\u0627\u0646\u062f\u0646\u060c \u062f\u0631 \u062d\u0627\u0644\u062a \u0645\u062a\u0646 \u062e\u0627\u0644\u0635 \u062a\u0646\u0638\u06cc\u0645 \u06af\u0634\u062a\u0647. \u062a\u0627 \u0632\u0645\u0627\u0646 \u062a\u063a\u06cc\u06cc\u0631 \u0627\u06cc\u0646 \u062d\u0627\u0644\u062a\u060c \u0645\u062d\u062a\u0648\u0627\u06cc \u0645\u0648\u0631\u062f \u0686\u0633\u0628\u0627\u0646\u062f\u0646\u060c \u0628\u0647 \u0635\u0648\u0631\u062a \u0645\u062a\u0646 \u062e\u0627\u0644\u0635 \u062e\u0648\u0627\u0647\u062f \u0686\u0633\u0628\u06cc\u062f.", +"Font Family": "\u0646\u0648\u0639 \u0642\u0644\u0645", +"Font Sizes": "\u0627\u0646\u062f\u0627\u0632\u0647\u0621 \u0642\u0644\u0645", +"Class": "\u0631\u062f\u0647", +"Browse for an image": "\u06cc\u0627\u0641\u062a\u0646 \u06cc\u06a9 \u062a\u0635\u0648\u06cc\u0631", +"OR": "\u00ab\u06cc\u0627\u00bb", +"Drop an image here": "\u06cc\u06a9 \u062a\u0635\u0648\u06cc\u0631 \u0627\u06cc\u0646\u062c\u0627 \u0631\u0647\u0627 \u06a9\u0646\u06cc\u062f", +"Upload": "\u0628\u0627\u0631\u06af\u0630\u0627\u0631\u06cc", +"Block": "\u0628\u0644\u0648\u06a9", +"Align": "\u0686\u06cc\u062f\u0645\u0627\u0646", +"Default": "\u067e\u06cc\u0634 \u0641\u0631\u0636", +"Circle": "\u062f\u0627\u06cc\u0631\u0647", +"Disc": "\u062f\u0627\u06cc\u0631\u0647\u0621 \u062a\u0648\u067e\u0631", +"Square": "\u0686\u0647\u0627\u0631 \u06af\u0648\u0634", +"Lower Alpha": "\u062d\u0631\u0648\u0641 \u06a9\u0648\u0686\u06a9", +"Lower Greek": "\u062d\u0631\u0648\u0641 \u06a9\u0648\u0686\u06a9 \u06cc\u0648\u0646\u0627\u0646\u06cc", +"Lower Roman": "\u0627\u0631\u0642\u0627\u0645 \u06a9\u0648\u0686\u06a9 \u0631\u0648\u0645\u06cc", +"Upper Alpha": "\u062d\u0631\u0648\u0641 \u0628\u0632\u0631\u06af", +"Upper Roman": "\u0627\u0631\u0642\u0627\u0645 \u0628\u0632\u0631\u06af \u0631\u0648\u0645\u06cc", +"Anchor": "\u0642\u0644\u0627\u0628", +"Name": "\u0646\u0627\u0645", +"Id": "\u0634\u0646\u0627\u0633\u0647", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "\u0634\u0646\u0627\u0633\u0647 \u0645\u06cc \u0628\u0627\u06cc\u0633\u062a \u0628\u0627 \u06cc\u06a9 \u062d\u0631\u0641 \u0627\u0644\u0641\u0628\u0627 \u0622\u063a\u0627\u0632 \u0648 \u0628\u0627 \u062f\u0646\u0628\u0627\u0644\u0647 \u0627\u06cc \u0627\u0632 \u062d\u0631\u0648\u0641\u060c \u0627\u0639\u062f\u0627\u062f\u060c \u0639\u0644\u0627\u0645\u062a \u0645\u0650\u0646\u0647\u0627\u060c \u0646\u0642\u0637\u0647\u060c \u062f\u0648 \u0646\u0642\u0637\u0647 \u06cc\u0627 \u062e\u0637 \u062a\u06cc\u0631\u0647 \u0627\u062f\u0627\u0645\u0647 \u06cc\u0627\u0628\u062f.", +"You have unsaved changes are you sure you want to navigate away?": "\u062a\u063a\u06cc\u06cc\u0631\u0627\u062a \u0634\u0645\u0627 \u0630\u062e\u06cc\u0631\u0647 \u0646\u0634\u062f\u0647 \u0627\u0646\u062f\u060c \u0622\u06cc\u0627 \u062c\u0647\u062a \u062e\u0631\u0648\u062c \u0627\u0637\u0645\u06cc\u0646\u0627\u0646 \u062f\u0627\u0631\u06cc\u062f\u061f", +"Restore last draft": "\u0628\u0627\u0632\u06cc\u0627\u0628\u06cc \u0622\u062e\u0631\u06cc\u0646 \u067e\u06cc\u0634 \u0646\u0648\u06cc\u0633", +"Special character": "\u0646\u0648\u06cc\u0633\u0647 \u0647\u0627\u06cc \u062e\u0627\u0635", +"Source code": "\u0645\u062a\u0646 \u06a9\u062f \u0645\u0646\u0628\u0639", +"Insert\/Edit code sample": "\u062f\u0631\u062c\/\u0648\u06cc\u0631\u0627\u06cc\u0634 \u0646\u0645\u0648\u0646\u0647\u0621 \u06a9\u062f", +"Language": "\u0632\u0628\u0627\u0646", +"Code sample": "\u0646\u0645\u0648\u0646\u0647 \u06a9\u064f\u062f", +"Color": "\u0631\u0646\u06af", +"R": "\u0642\u0631\u0645\u0632", +"G": "\u0633\u0628\u0632", +"B": "\u0622\u0628\u06cc", +"Left to right": "\u0686\u067e \u0628\u0647 \u0631\u0627\u0633\u062a", +"Right to left": "\u0631\u0627\u0633\u062a \u0628\u0647 \u0686\u067e", +"Emoticons": "\u0635\u0648\u0631\u062a\u06a9 \u0647\u0627", +"Document properties": "\u062a\u0646\u0638\u06cc\u0645\u0627\u062a \u0633\u0646\u062f", +"Title": "\u0639\u0646\u0648\u0627\u0646", +"Keywords": "\u0648\u0627\u0698\u06af\u0627\u0646 \u06a9\u0644\u06cc\u062f\u06cc", +"Description": "\u062a\u0648\u0636\u06cc\u062d", +"Robots": "\u0631\u0648\u0628\u0627\u062a\u0647\u0627", +"Author": "\u0645\u0648\u0644\u0641", +"Encoding": "\u06a9\u062f\u06af\u0632\u0627\u0631\u06cc \u0645\u062a\u0646", +"Fullscreen": "\u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647", +"Action": "\u0639\u0645\u0644", +"Shortcut": "\u0645\u06cc\u0627\u0646\u0628\u064f\u0631", +"Help": "\u0631\u0627\u0647\u0646\u0645\u0627", +"Address": "\u0646\u0634\u0627\u0646\u06cc", +"Focus to menubar": "\u062a\u0645\u0631\u06a9\u0632 \u0628\u0631 \u0646\u0648\u0627\u0631 \u0645\u0646\u0648", +"Focus to toolbar": "\u062a\u0645\u0631\u06a9\u0632 \u0628\u0631 \u0646\u0648\u0627\u0631 \u0627\u0628\u0632\u0627\u0631", +"Focus to element path": "\u062a\u0645\u0631\u06a9\u0632 \u0628\u0631 \u0645\u0633\u06cc\u0631 \u0627\u0650\u0644\u0650\u0645\u0627\u0646", +"Focus to contextual toolbar": "\u062a\u0645\u0631\u06a9\u0632 \u0628\u0631 \u0646\u0648\u0627\u0631 \u0627\u0628\u0632\u0627\u0631 \u0645\u062a\u0646\u06cc", +"Insert link (if link plugin activated)": "\u062f\u0631\u062c \u067e\u06cc\u0648\u0646\u062f (\u0627\u06af\u0631 \u0627\u0641\u0632\u0648\u0646\u0647\u0621 \u067e\u06cc\u0648\u0646\u062f \u0641\u0639\u0627\u0644 \u0634\u062f)", +"Save (if save plugin activated)": "\u062b\u0628\u062a\u00a0(\u0627\u06af\u0631 \u0627\u0641\u0632\u0648\u0646\u0647\u0621 \u0630\u062e\u06cc\u0631\u0647 \u0633\u0627\u0632\u06cc \u0641\u0639\u0627\u0644 \u0634\u062f)", +"Find (if searchreplace plugin activated)": "\u06cc\u0627\u0641\u062a\u0646 (\u0627\u06af\u0631 \u0627\u0641\u0632\u0648\u0646\u0647\u0621 \u062c\u0633\u062a\u062c\u0648\/\u062c\u0627\u06cc\u06af\u0632\u06cc\u0646\u06cc \u0641\u0639\u0627\u0644 \u0634\u062f)", +"Plugins installed ({0}):": "\u0627\u0641\u0632\u0648\u0646\u0647 \u0647\u0627\u06cc\u06cc \u06a9\u0647 \u0646\u0635\u0628 \u0634\u062f\u0646\u062f ({0}):", +"Premium plugins:": "\u0627\u0641\u0632\u0648\u0646\u0647 \u0647\u0627\u06cc \u0645\u062e\u0635\u0648\u0635:", +"Learn more...": "\u06cc\u0627\u062f\u06af\u06cc\u0631\u06cc \u0628\u06cc\u0634\u062a\u0631...", +"You are using {0}": "\u0634\u0645\u0627 \u062f\u0631 \u062d\u0627\u0644 \u0627\u0633\u062a\u0641\u0627\u062f\u0647 \u0627\u0632 {0} \u0645\u06cc \u0628\u0627\u0634\u06cc\u062f", +"Plugins": "\u0627\u0641\u0632\u0648\u0646\u0647 \u0647\u0627", +"Handy Shortcuts": "\u0645\u06cc\u0627\u0646\u0628\u064f\u0631\u0647\u0627\u06cc \u0633\u0648\u062f\u0645\u0646\u062f", +"Horizontal line": "\u062e\u0637 \u0627\u0641\u0642\u06cc", +"Insert\/edit image": "\u062f\u0631\u062c\/\u0648\u06cc\u0631\u0627\u06cc\u0634 \u062a\u0635\u0648\u06cc\u0631", +"Image description": "\u062a\u0648\u0635\u06cc\u0641 \u062a\u0635\u0648\u06cc\u0631", +"Source": "\u0645\u0646\u0628\u0639", +"Dimensions": "\u0627\u0628\u0639\u0627\u062f", +"Constrain proportions": "\u062d\u0641\u0638 \u062a\u0646\u0627\u0633\u0628", +"General": "\u0639\u0645\u0648\u0645\u06cc", +"Advanced": "\u067e\u06cc\u0634\u0631\u0641\u062a\u0647", +"Style": "\u0633\u0628\u06a9", +"Vertical space": "\u0641\u0636\u0627\u06cc \u0639\u0645\u0648\u062f\u06cc", +"Horizontal space": "\u0641\u0636\u0627\u06cc \u0627\u0641\u0642\u06cc", +"Border": "\u0644\u0628\u0647", +"Insert image": "\u062f\u0631\u062c \u062a\u0635\u0648\u06cc\u0631", +"Image": "\u062a\u0635\u0648\u06cc\u0631", +"Image list": "\u0641\u0647\u0631\u0633\u062a \u062a\u0635\u0648\u06cc\u0631\u06cc", +"Rotate counterclockwise": "\u062f\u064e\u0648\u064e\u0631\u0627\u0646 \u067e\u0627\u062f \u0633\u0627\u0639\u062a \u06af\u0631\u062f", +"Rotate clockwise": "\u062f\u064e\u0648\u064e\u0631\u0627\u0646 \u0633\u0627\u0639\u062a \u06af\u0631\u062f", +"Flip vertically": "\u0642\u0631\u06cc\u0646\u0647 \u0639\u0645\u0648\u062f\u06cc", +"Flip horizontally": "\u0642\u0631\u06cc\u0646\u0647 \u0627\u0641\u0642\u06cc", +"Edit image": "\u0648\u06cc\u0631\u0627\u0633\u062a \u062a\u0635\u0648\u06cc\u0631", +"Image options": "\u062a\u0646\u0638\u06cc\u0645\u0627\u062a \u062a\u0635\u0648\u06cc\u0631", +"Zoom in": "\u0628\u0632\u0631\u06af \u0646\u0645\u0627\u06cc\u06cc", +"Zoom out": "\u06a9\u0648\u0686\u06a9 \u0646\u0645\u0627\u06cc\u06cc", +"Crop": "\u0628\u064f\u0631\u0634 \u062f\u064f\u0648\u0631", +"Resize": "\u062a\u063a\u06cc\u06cc\u0631 \u0627\u0646\u062f\u0627\u0632\u0647", +"Orientation": "\u06af\u0650\u0631\u0627", +"Brightness": "\u0631\u0648\u0634\u0646\u0627\u06cc\u06cc", +"Sharpen": "\u0628\u0647\u0628\u0648\u062f \u0644\u0628\u0647", +"Contrast": "\u062a\u0636\u0627\u062f \u0631\u0646\u06af", +"Color levels": "\u0633\u0637\u0648\u062d \u0631\u0646\u06af", +"Gamma": "\u06af\u0627\u0645\u0627", +"Invert": "\u0628\u0631\u06af\u0634\u062a \u0631\u0646\u06af", +"Apply": "\u0627\u0650\u0639\u0645\u0627\u0644", +"Back": "\u0628\u0627\u0632\u06af\u0634\u062a", +"Insert date\/time": "\u062f\u0631\u062c \u062a\u0627\u0631\u06cc\u062e\/\u0632\u0645\u0627\u0646", +"Date\/time": "\u062a\u0627\u0631\u06cc\u062e\/\u0632\u0645\u0627\u0646", +"Insert link": "\u062f\u0631\u062c \u067e\u06cc\u0648\u0646\u062f", +"Insert\/edit link": "\u062f\u0631\u062c\/\u0648\u06cc\u0631\u0627\u06cc\u0634 \u067e\u06cc\u0648\u0646\u062f", +"Text to display": "\u0645\u062a\u0646 \u0646\u0645\u0627\u06cc\u0634\u06cc", +"Url": "\u0622\u062f\u0631\u0633", +"Target": "\u0645\u0642\u0635\u062f", +"None": "\u0647\u06cc\u0686", +"New window": "\u067e\u0646\u062c\u0631\u0647\u0621 \u062c\u062f\u06cc\u062f", +"Remove link": "\u062d\u0630\u0641 \u067e\u06cc\u0648\u0646\u062f", +"Anchors": "\u0642\u0644\u0627\u0628 \u0647\u0627", +"Link": "\u067e\u06cc\u0648\u0646\u062f", +"Paste or type a link": "\u0686\u0633\u0628\u0627\u0646\u062f\u0646 \u06cc\u0627 \u062a\u0627\u06cc\u067e \u067e\u06cc\u0648\u0646\u062f", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "\u0628\u0647 \u0646\u0638\u0631 \u0645\u06cc \u0631\u0633\u062f \u0622\u062f\u0631\u0633 \u0648\u0631\u0648\u062f\u06cc \u06cc\u06a9 \u0631\u0627\u06cc\u0627\u0646\u0627\u0645\u0647 \u0628\u0627\u0634\u062f. \u0622\u06cc\u0627 \u062a\u0645\u0627\u06cc\u0644 \u0628\u0647 \u0627\u0641\u0632\u0648\u0631\u062f\u0646 \u067e\u06cc\u0634\u0648\u0646\u062f mailto: \u062f\u0627\u0631\u06cc\u062f\u061f", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "\u0628\u0647 \u0646\u0638\u0631 \u0645\u06cc \u0631\u0633\u062f \u0622\u062f\u0631\u0633 \u0648\u0631\u0648\u062f\u06cc \u0627\u0631\u062c\u0627\u0639\u06cc \u0628\u0647 \u062e\u0627\u0631\u062c \u0627\u0632 \u0627\u06cc\u0646 \u0633\u0627\u06cc\u062a \u0645\u06cc \u0628\u0627\u0634\u062f. \u0622\u06cc\u0627 \u062a\u0645\u0627\u06cc\u0644 \u0628\u0647 \u0627\u0641\u0632\u0648\u0631\u062f\u0646 \u067e\u06cc\u0634\u0648\u0646\u062f http:\/\/ \u062f\u0627\u0631\u06cc\u062f\u061f", +"Link list": "\u0641\u0647\u0631\u0633\u062a \u067e\u06cc\u0648\u0646\u062f", +"Insert video": "\u062f\u0631\u062c \u0648\u06cc\u062f\u06cc\u0648", +"Insert\/edit video": "\u062f\u0631\u062c\/\u0648\u06cc\u0631\u0627\u06cc\u0634 \u0648\u06cc\u062f\u06cc\u0648", +"Insert\/edit media": "\u062f\u0631\u062c\/\u0648\u06cc\u0631\u0627\u06cc\u0634 \u0631\u0633\u0627\u0646\u0647", +"Alternative source": "\u0645\u0646\u0628\u0639 \u062c\u0627\u06cc\u06af\u0632\u06cc\u0646", +"Poster": "\u067e\u0648\u0633\u062a\u0631", +"Paste your embed code below:": "\u0686\u0633\u0628\u0627\u0646\u062f\u0646 \u06a9\u062f \u062c\u0627\u0633\u0627\u0632\u06cc \u0634\u0645\u0627 \u062f\u0631 \u0632\u06cc\u0631: ", +"Embed": "\u062c\u0627\u0633\u0627\u0632\u06cc", +"Media": "\u0631\u0633\u0627\u0646\u0647", +"Nonbreaking space": "\u0641\u0636\u0627\u06cc \u062e\u0627\u0644\u06cc \u0628\u0631\u0634 \u0646\u0627\u067e\u0630\u06cc\u0631", +"Page break": "\u0628\u0631\u0634 \u0635\u0641\u062d\u0647", +"Paste as text": "\u0686\u0633\u0628\u0627\u0646\u062f\u0646 \u0645\u062a\u0646", +"Preview": "\u067e\u06cc\u0634 \u0646\u0645\u0627\u06cc\u0634", +"Print": "\u0686\u0627\u067e", +"Save": "\u0630\u062e\u06cc\u0631\u0647", +"Find": "\u062c\u0633\u062a\u062c\u0648", +"Replace with": "\u062c\u0627\u06cc\u06af\u0632\u06cc\u0646\u06cc \u0628\u0627", +"Replace": "\u062c\u0627\u06cc\u06af\u0632\u06cc\u0646\u06cc", +"Replace all": "\u062c\u0627\u06cc\u06af\u0632\u06cc\u0646 \u0647\u0645\u0647", +"Prev": "\u0642\u0628\u0644\u06cc", +"Next": "\u0628\u0639\u062f\u06cc", +"Find and replace": "\u062c\u0633\u062a\u062c\u0648 \u0648 \u062c\u0627\u06cc\u06af\u0632\u06cc\u0646\u06cc", +"Could not find the specified string.": "\u0631\u0634\u062a\u0647\u0621 \u0645\u0648\u0631\u062f \u0646\u0638\u0631 \u06cc\u0627\u0641\u062a \u0646\u06af\u0631\u062f\u06cc\u062f.", +"Match case": "\u062a\u0637\u0627\u0628\u0642 \u062d\u0631\u0648\u0641", +"Whole words": "\u062a\u0645\u0627\u0645 \u0648\u0627\u0698\u06af\u0627\u0646", +"Spellcheck": "\u0628\u0631\u0631\u0633\u06cc \u0627\u0645\u0644\u0627\u0621", +"Ignore": "\u0628\u06cc \u062e\u06cc\u0627\u0644", +"Ignore all": "\u0628\u06cc \u062e\u06cc\u0627\u0644 \u0647\u0645\u0647", +"Finish": "\u0627\u062a\u0645\u0627\u0645", +"Add to Dictionary": "\u0628\u0647 \u0648\u0627\u0698\u0647 \u0646\u0627\u0645\u0647 \u0628\u06cc \u0627\u0641\u0632\u0627", +"Insert table": "\u062f\u0631\u062c \u062c\u062f\u0648\u0644", +"Table properties": "\u062a\u0646\u0638\u06cc\u0645\u0627\u062a \u062c\u062f\u0648\u0644", +"Delete table": "\u062d\u0630\u0641 \u062c\u062f\u0648\u0644", +"Cell": "\u0633\u0644\u0648\u0644", +"Row": "\u0633\u0637\u0631", +"Column": "\u0633\u062a\u0648\u0646", +"Cell properties": "\u062a\u0646\u0638\u06cc\u0645\u0627\u062a \u0633\u0644\u0648\u0644", +"Merge cells": "\u067e\u06cc\u0648\u0646\u062f \u0633\u0644\u0648\u0644 \u0647\u0627", +"Split cell": "\u062c\u062f\u0627 \u0633\u0627\u0632\u06cc \u0633\u0644\u0648\u0644", +"Insert row before": "\u062f\u0631\u062c \u0633\u0637\u0631 \u062f\u0631 \u0628\u0627\u0644\u0627", +"Insert row after": "\u062f\u0631\u062c \u0633\u0637\u0631 \u062f\u0631 \u067e\u0627\u06cc\u06cc\u0646", +"Delete row": "\u062d\u0630\u0641 \u0633\u0637\u0631", +"Row properties": "\u062a\u0646\u0638\u06cc\u0645\u0627\u062a \u0633\u0637\u0631", +"Cut row": "\u0628\u0631\u0634 \u0633\u0637\u0631", +"Copy row": "\u0631\u0648\u0646\u0648\u06cc\u0633\u06cc \u0633\u0637\u0631", +"Paste row before": "\u0686\u0633\u0628\u0627\u0646\u062f\u0646 \u0633\u0637\u0631 \u062f\u0631 \u0628\u0627\u0644\u0627", +"Paste row after": "\u0686\u0633\u0628\u0627\u0646\u062f\u0646 \u0633\u0637\u0631 \u062f\u0631 \u067e\u0627\u06cc\u06cc\u0646", +"Insert column before": "\u062f\u0631\u062c \u0633\u062a\u0648\u0646 \u0642\u0628\u0644", +"Insert column after": "\u062f\u0631\u062c \u0633\u062a\u0648\u0646 \u0628\u0639\u062f", +"Delete column": "\u062d\u0630\u0641 \u0633\u062a\u0648\u0646", +"Cols": "\u0633\u062a\u0648\u0646 \u0647\u0627", +"Rows": "\u0633\u0637\u0631 \u0647\u0627", +"Width": "\u0639\u0631\u0636", +"Height": "\u0627\u0631\u062a\u0641\u0627\u0639", +"Cell spacing": "\u0641\u0627\u0635\u0644\u0647 \u0645\u06cc\u0627\u0646 \u0633\u0644\u0648\u0644\u06cc", +"Cell padding": "\u062d\u0627\u0634\u06cc\u0647 \u062f\u0631\u0648\u0646 \u0633\u0644\u0648\u0644\u06cc", +"Caption": "\u0639\u0646\u0648\u0627\u0646", +"Left": "\u0686\u067e", +"Center": "\u0645\u06cc\u0627\u0646\u0647", +"Right": "\u0631\u0627\u0633\u062a", +"Cell type": "\u0646\u0648\u0639 \u0633\u0644\u0648\u0644", +"Scope": "\u062d\u0648\u0632\u0647", +"Alignment": "\u0647\u0645 \u062a\u0631\u0627\u0632\u06cc", +"H Align": "\u062a\u0631\u0627\u0632 \u0627\u0641\u0642\u06cc", +"V Align": "\u062a\u0631\u0627\u0632 \u0639\u0645\u0648\u062f\u06cc", +"Top": "\u0628\u0627\u0644\u0627", +"Middle": "\u0645\u06cc\u0627\u0646\u0647", +"Bottom": "\u067e\u0627\u06cc\u06cc\u0646", +"Header cell": "\u0633\u0644\u0648\u0644 \u0633\u0631 \u0633\u062a\u0648\u0646", +"Row group": "\u06af\u0631\u0648\u0647 \u0633\u0637\u0631\u06cc", +"Column group": "\u06af\u0631\u0648\u0647 \u0633\u062a\u0648\u0646\u06cc", +"Row type": "\u0646\u0648\u0639 \u0633\u0637\u0631", +"Header": "\u0633\u0631 \u0622\u0645\u062f", +"Body": "\u0628\u062f\u0646\u0647", +"Footer": "\u067e\u0627 \u0646\u0648\u0634\u062a", +"Border color": "\u0631\u0646\u06af \u0644\u0628\u0647", +"Insert template": "\u062f\u0631\u062c \u0627\u0644\u06af\u0648", +"Templates": "\u0627\u0644\u06af\u0648\u0647\u0627", +"Template": "\u0627\u0644\u06af\u0648", +"Text color": "\u0631\u0646\u06af \u0645\u062a\u0646", +"Background color": "\u0631\u0646\u06af \u067e\u0633 \u0632\u0645\u06cc\u0646\u0647", +"Custom...": "\u062f\u0644\u062e\u0648\u0627\u0647...", +"Custom color": "\u0631\u0646\u06af \u062f\u0644\u062e\u0648\u0627\u0647", +"No color": "\u0628\u062f\u0648\u0646 \u0631\u0646\u06af", +"Table of Contents": "\u0641\u0647\u0631\u0633\u062a \u0639\u0646\u0627\u0648\u06cc\u0646", +"Show blocks": "\u0646\u0645\u0627\u06cc\u0634 \u0628\u0644\u0648\u06a9 \u0647\u0627", +"Show invisible characters": "\u0646\u0645\u0627\u06cc\u0634 \u0646\u0648\u06cc\u0633\u0647 \u0647\u0627\u06cc \u0646\u0627\u067e\u06cc\u062f\u0627", +"Words: {0}": "\u0648\u0627\u0698\u0647 \u0647\u0627: {0}", +"{0} words": "{0} \u0648\u0627\u0698\u0647", +"File": "\u067e\u0631\u0648\u0646\u062f\u0647", +"Edit": "\u0648\u06cc\u0631\u0627\u06cc\u0634", +"Insert": "\u062f\u0631\u062c", +"View": "\u0646\u0645\u0627\u06cc\u0634", +"Format": "\u0642\u0627\u0644\u0628", +"Table": "\u062c\u062f\u0648\u0644", +"Tools": "\u0627\u0628\u0632\u0627\u0631\u0647\u0627", +"Powered by {0}": "\u062a\u0648\u0627\u0646 \u06af\u0631\u0641\u062a\u0647 \u0627\u0632 {0}", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "\u0646\u0627\u062d\u06cc\u0647 \u0645\u062a\u0646 \u063a\u0646\u06cc.\n\u062c\u0647\u062a \u0645\u0634\u0627\u0647\u062f\u0647 \u0645\u0646\u0648 \u0627\u0632 \u06a9\u0644\u06cc\u062f\u0647\u0627\u06cc \u062a\u0631\u06a9\u06cc\u0628\u06cc ALT + F9 \u0627\u0633\u062a\u0641\u0627\u062f\u0647 \u0646\u0645\u0627\u06cc\u06cc\u062f.\n\u062c\u0647\u062a \u0645\u0634\u0627\u0647\u062f\u0647 \u0646\u0648\u0627\u0631 \u0627\u0628\u0632\u0627\u0631 \u0627\u0632 \u06a9\u0644\u06cc\u062f\u0647\u0627\u06cc \u062a\u0631\u06a9\u06cc\u0628\u06cc ALT + F10 \u0627\u0633\u062a\u0641\u0627\u062f\u0647 \u0646\u0645\u0627\u06cc\u06cc\u062f.\n\u062c\u0647\u062a \u0645\u0634\u0627\u0647\u062f\u0647 \u0631\u0627\u0647\u0646\u0645\u0627 \u0627\u0632 \u06a9\u0644\u06cc\u062f\u0647\u0627\u06cc \u062a\u0631\u06a9\u06cc\u0628\u06cc ALT + 0 \u0627\u0633\u062a\u0641\u0627\u062f\u0647 \u0646\u0645\u0627\u06cc\u06cc\u062f.", +"_dir": "rtl" +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/fi.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/fi.js index 87677bd3ff..db521d6dec 100644 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/fi.js +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/fi.js @@ -1,219 +1,261 @@ tinymce.addI18n('fi',{ -"Cut": "Leikkaa", -"Heading 5": "Otsikko 5", -"Header 2": "Otsikko 2", -"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "Selaimesi ei tue leikep\u00f6yd\u00e4n suoraa k\u00e4ytt\u00e4mist\u00e4. Ole hyv\u00e4 ja k\u00e4yt\u00e4 n\u00e4pp\u00e4imist\u00f6n Ctrl+X\/C\/V n\u00e4pp\u00e4inyhdistelmi\u00e4.", -"Heading 4": "Otsikko 4", -"Div": "Div", -"Heading 2": "Otsikko 2", -"Paste": "Liit\u00e4", -"Close": "Sulje", -"Font Family": "Fontti", -"Pre": "Esimuotoiltu", -"Align right": "Tasaa oikealle", -"New document": "Uusi dokumentti", -"Blockquote": "Lainauslohko", -"Numbered list": "J\u00e4rjestetty lista", -"Heading 1": "Otsikko 1", -"Headings": "Otsikot", -"Increase indent": "Loitonna", -"Formats": "Muotoilut", -"Headers": "Otsikot", -"Select all": "Valitse kaikki", -"Header 3": "Otsikko 3", -"Blocks": "Lohkot", -"Undo": "Peru", -"Strikethrough": "Yliviivaus", -"Bullet list": "J\u00e4rjest\u00e4m\u00e4t\u00f6n lista", -"Header 1": "Otsikko 1", -"Superscript": "Yl\u00e4indeksi", -"Clear formatting": "Poista muotoilu", -"Font Sizes": "Fonttikoko", -"Subscript": "Alaindeksi", -"Header 6": "Otsikko 6", "Redo": "Tee uudelleen", -"Paragraph": "Kappale", -"Ok": "Ok", -"Bold": "Lihavointi", -"Code": "Koodi", -"Italic": "Kursivointi", -"Align center": "Keskit\u00e4", -"Header 5": "Otsikko 5", -"Heading 6": "Otsikko 6", -"Heading 3": "Otsikko 3", -"Decrease indent": "Sisenn\u00e4", -"Header 4": "Otsikko 4", -"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "Liitt\u00e4minen on nyt pelk\u00e4n tekstin -tilassa. Sis\u00e4ll\u00f6t liitet\u00e4\u00e4n nyt pelkk\u00e4n\u00e4 tekstin\u00e4, kunnes otat vaihtoehdon pois k\u00e4yt\u00f6st\u00e4.", -"Underline": "Alleviivaus", -"Cancel": "Peruuta", -"Justify": "Tasaa", -"Inline": "Samalla rivill\u00e4", +"Undo": "Peru", +"Cut": "Leikkaa", "Copy": "Kopioi", -"Align left": "Tasaa vasemmalle", +"Paste": "Liit\u00e4", +"Select all": "Valitse kaikki", +"New document": "Uusi dokumentti", +"Ok": "Ok", +"Cancel": "Peruuta", "Visual aids": "Visuaaliset neuvot", -"Lower Greek": "pienet kirjaimet: \u03b1, \u03b2, \u03b3", -"Square": "Neli\u00f6", +"Bold": "Lihavointi", +"Italic": "Kursivointi", +"Underline": "Alleviivaus", +"Strikethrough": "Yliviivaus", +"Superscript": "Yl\u00e4indeksi", +"Subscript": "Alaindeksi", +"Clear formatting": "Poista muotoilu", +"Align left": "Tasaa vasemmalle", +"Align center": "Keskit\u00e4", +"Align right": "Tasaa oikealle", +"Justify": "Tasaa", +"Bullet list": "J\u00e4rjest\u00e4m\u00e4t\u00f6n lista", +"Numbered list": "J\u00e4rjestetty lista", +"Decrease indent": "Sisenn\u00e4", +"Increase indent": "Loitonna", +"Close": "Sulje", +"Formats": "Muotoilut", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "Selaimesi ei tue leikep\u00f6yd\u00e4n suoraa k\u00e4ytt\u00e4mist\u00e4. Ole hyv\u00e4 ja k\u00e4yt\u00e4 n\u00e4pp\u00e4imist\u00f6n Ctrl+X\/C\/V n\u00e4pp\u00e4inyhdistelmi\u00e4.", +"Headers": "Otsikot", +"Header 1": "Otsikko 1", +"Header 2": "Otsikko 2", +"Header 3": "Otsikko 3", +"Header 4": "Otsikko 4", +"Header 5": "Otsikko 5", +"Header 6": "Otsikko 6", +"Headings": "Otsikot", +"Heading 1": "Otsikko 1", +"Heading 2": "Otsikko 2", +"Heading 3": "Otsikko 3", +"Heading 4": "Otsikko 4", +"Heading 5": "Otsikko 5", +"Heading 6": "Otsikko 6", +"Preformatted": "Preformatted", +"Div": "Div", +"Pre": "Esimuotoiltu", +"Code": "Koodi", +"Paragraph": "Kappale", +"Blockquote": "Lainauslohko", +"Inline": "Samalla rivill\u00e4", +"Blocks": "Lohkot", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "Liitt\u00e4minen on nyt pelk\u00e4n tekstin -tilassa. Sis\u00e4ll\u00f6t liitet\u00e4\u00e4n nyt pelkk\u00e4n\u00e4 tekstin\u00e4, kunnes otat vaihtoehdon pois k\u00e4yt\u00f6st\u00e4.", +"Font Family": "Fontti", +"Font Sizes": "Fonttikoko", +"Class": "Luokka", +"Browse for an image": "Selaa kuvia", +"OR": "TAI", +"Drop an image here": "Pudota kuva t\u00e4h\u00e4n", +"Upload": "Vie", +"Block": "Lohko", +"Align": "Tasaa", "Default": "Oletus", -"Lower Alpha": "pienet kirjaimet: a, b, c", "Circle": "Pallo", "Disc": "Ympyr\u00e4", +"Square": "Neli\u00f6", +"Lower Alpha": "pienet kirjaimet: a, b, c", +"Lower Greek": "pienet kirjaimet: \u03b1, \u03b2, \u03b3", +"Lower Roman": "pienet kirjaimet: i, ii, iii", "Upper Alpha": "isot kirjaimet: A, B, C", "Upper Roman": "isot kirjaimet: I, II, III", -"Lower Roman": "pienet kirjaimet: i, ii, iii", -"Name": "Nimi", "Anchor": "Ankkuri", +"Name": "Nimi", +"Id": "Id", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "Id voi alkaa kirjaimella, sen j\u00e4lkeen voi k\u00e4ytt\u00e4\u00e4 kirjaimia, numeroja, viivoja, pisteit\u00e4, kaksoispistett\u00e4 ja alaviivausta", "You have unsaved changes are you sure you want to navigate away?": "Sinulla on tallentamattomia muutoksia, haluatko varmasti siirty\u00e4 toiselle sivulle?", "Restore last draft": "Palauta aiempi luonnos", "Special character": "Erikoismerkki", "Source code": "L\u00e4hdekoodi", -"B": "B", +"Insert\/Edit code sample": "Lis\u00e4\u00e4\/muokkaa koodiesimerkki", +"Language": "Kieli", +"Code sample": "Koodiesimerkki", +"Color": "V\u00e4ri", "R": "R", "G": "G", -"Color": "V\u00e4ri", -"Right to left": "Oikealta vasemmalle", +"B": "B", "Left to right": "Vasemmalta oikealle", +"Right to left": "Oikealta vasemmalle", "Emoticons": "Hymi\u00f6t", -"Robots": "Robotit", "Document properties": "Dokumentin ominaisuudet", "Title": "Otsikko", "Keywords": "Avainsanat", -"Encoding": "Merkist\u00f6", "Description": "Kuvaus", +"Robots": "Robotit", "Author": "Tekij\u00e4", +"Encoding": "Merkist\u00f6", "Fullscreen": "Koko ruutu", +"Action": "Toiminto", +"Shortcut": "Oikotie", +"Help": "Ohje", +"Address": "Osoite", +"Focus to menubar": "Kohdistus valikkoon", +"Focus to toolbar": "Kohdistus ty\u00f6kalupalkkiin", +"Focus to element path": "Kohdistus elementtiin", +"Focus to contextual toolbar": "Kohdistus kontekstuaaliseen ty\u00f6kalupalkkiin", +"Insert link (if link plugin activated)": "Lis\u00e4\u00e4 linkki (jos linkki-liit\u00e4nn\u00e4inen aktiivinen)", +"Save (if save plugin activated)": "Tallenna (jos tallenna-liit\u00e4nn\u00e4inen aktiivinen)", +"Find (if searchreplace plugin activated)": "Etsi (jos etsikorvaa-liit\u00e4nn\u00e4inen aktiivinen)", +"Plugins installed ({0}):": "Asennetut liit\u00e4nn\u00e4iset ({0}):", +"Premium plugins:": "Premium liit\u00e4nn\u00e4iset:", +"Learn more...": "Lis\u00e4tietoja...", +"You are using {0}": "K\u00e4yt\u00e4t {0}", +"Plugins": "Liit\u00e4nn\u00e4iset", +"Handy Shortcuts": "K\u00e4tev\u00e4t pikan\u00e4pp\u00e4imet", "Horizontal line": "Vaakasuora viiva", -"Horizontal space": "Horisontaalinen tila", "Insert\/edit image": "Lis\u00e4\u00e4\/muokkaa kuva", +"Image description": "Kuvaus", +"Source": "L\u00e4hde", +"Dimensions": "Mittasuhteet", +"Constrain proportions": "S\u00e4ilyt\u00e4 mittasuhteet", "General": "Yleiset", "Advanced": "Lis\u00e4asetukset", -"Source": "L\u00e4hde", -"Border": "Reunus", -"Constrain proportions": "S\u00e4ilyt\u00e4 mittasuhteet", -"Vertical space": "Vertikaalinen tila", -"Image description": "Kuvaus", "Style": "Tyyli", -"Dimensions": "Mittasuhteet", +"Vertical space": "Vertikaalinen tila", +"Horizontal space": "Horisontaalinen tila", +"Border": "Reunus", "Insert image": "Lis\u00e4\u00e4 kuva", -"Zoom in": "L\u00e4henn\u00e4", -"Contrast": "Kontrasti", -"Back": "Takaisin", -"Gamma": "Gamma", -"Flip horizontally": "K\u00e4\u00e4nn\u00e4 vaakasuunnassa", -"Resize": "Kuvan koon muutos", -"Sharpen": "Ter\u00e4vyys", -"Zoom out": "Loitonna", -"Image options": "Kuvan asetukset", -"Apply": "Aseta", -"Brightness": "Kirkkaus", -"Rotate clockwise": "Kierr\u00e4 my\u00f6t\u00e4p\u00e4iv\u00e4\u00e4n", +"Image": "Kuva", +"Image list": "Kuvalista", "Rotate counterclockwise": "Kierr\u00e4 vastap\u00e4iv\u00e4\u00e4n", -"Edit image": "Muokkaa kuvaa", -"Color levels": "V\u00e4ritasot", -"Crop": "Rajaa valintaan", -"Orientation": "Suunta", +"Rotate clockwise": "Kierr\u00e4 my\u00f6t\u00e4p\u00e4iv\u00e4\u00e4n", "Flip vertically": "K\u00e4\u00e4nn\u00e4 pystysuunnassa", +"Flip horizontally": "K\u00e4\u00e4nn\u00e4 vaakasuunnassa", +"Edit image": "Muokkaa kuvaa", +"Image options": "Kuvan asetukset", +"Zoom in": "L\u00e4henn\u00e4", +"Zoom out": "Loitonna", +"Crop": "Rajaa valintaan", +"Resize": "Kuvan koon muutos", +"Orientation": "Suunta", +"Brightness": "Kirkkaus", +"Sharpen": "Ter\u00e4vyys", +"Contrast": "Kontrasti", +"Color levels": "V\u00e4ritasot", +"Gamma": "Gamma", "Invert": "K\u00e4\u00e4nteinen", +"Apply": "Aseta", +"Back": "Takaisin", "Insert date\/time": "Lis\u00e4\u00e4 p\u00e4iv\u00e4m\u00e4\u00e4r\u00e4 tai aika", -"Remove link": "Poista linkki", -"Url": "Osoite", -"Text to display": "N\u00e4ytett\u00e4v\u00e4 teksti", -"Anchors": "Ankkurit", +"Date\/time": "P\u00e4iv\u00e4m\u00e4\u00e4r\u00e4\/aika", "Insert link": "Lis\u00e4\u00e4 linkki", -"New window": "Uusi ikkuna", -"None": "Ei mit\u00e4\u00e4n", -"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "Antamasi osoite n\u00e4ytt\u00e4\u00e4 olevan ulkoinen linkki. Haluatko lis\u00e4t\u00e4 osoitteeseen vaaditun http:\/\/ -etuliitteen?", -"Target": "Kohde", -"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "Antamasi osoite n\u00e4ytt\u00e4\u00e4 olevan s\u00e4hk\u00f6postiosoite. Haluatko lis\u00e4t\u00e4 osoitteeseen vaaditun mailto: -etuliitteen?", "Insert\/edit link": "Lis\u00e4\u00e4\/muokkaa linkki", -"Insert\/edit video": "Lis\u00e4\u00e4\/muokkaa video", -"Poster": "L\u00e4hett\u00e4j\u00e4", -"Alternative source": "Vaihtoehtoinen l\u00e4hde", -"Paste your embed code below:": "Liit\u00e4 upotuskoodisi alapuolelle:", +"Text to display": "N\u00e4ytett\u00e4v\u00e4 teksti", +"Url": "Osoite", +"Target": "Kohde", +"None": "Ei mit\u00e4\u00e4n", +"New window": "Uusi ikkuna", +"Remove link": "Poista linkki", +"Anchors": "Ankkurit", +"Link": "Linkki", +"Paste or type a link": "Sijoita tai kirjoita linkki", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "Antamasi osoite n\u00e4ytt\u00e4\u00e4 olevan s\u00e4hk\u00f6postiosoite. Haluatko lis\u00e4t\u00e4 osoitteeseen vaaditun mailto: -etuliitteen?", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "Antamasi osoite n\u00e4ytt\u00e4\u00e4 olevan ulkoinen linkki. Haluatko lis\u00e4t\u00e4 osoitteeseen vaaditun http:\/\/ -etuliitteen?", +"Link list": "Linkkilista", "Insert video": "Lis\u00e4\u00e4 video", +"Insert\/edit video": "Lis\u00e4\u00e4\/muokkaa video", +"Insert\/edit media": "Lis\u00e4\u00e4\/muokkaa media", +"Alternative source": "Vaihtoehtoinen l\u00e4hde", +"Poster": "L\u00e4hett\u00e4j\u00e4", +"Paste your embed code below:": "Liit\u00e4 upotuskoodisi alapuolelle:", "Embed": "Upota", +"Media": "Media", "Nonbreaking space": "Sitova v\u00e4lily\u00f6nti", "Page break": "Sivunvaihto", "Paste as text": "Liit\u00e4 tekstin\u00e4", "Preview": "Esikatselu", "Print": "Tulosta", "Save": "Tallenna", -"Could not find the specified string.": "Haettua merkkijonoa ei l\u00f6ytynyt.", -"Replace": "Korvaa", -"Next": "Seur.", -"Whole words": "Koko sanat", -"Find and replace": "Etsi ja korvaa", -"Replace with": "Korvaa", "Find": "Etsi", +"Replace with": "Korvaa", +"Replace": "Korvaa", "Replace all": "Korvaa kaikki", -"Match case": "Erota isot ja pienet kirjaimet", "Prev": "Edel.", +"Next": "Seur.", +"Find and replace": "Etsi ja korvaa", +"Could not find the specified string.": "Haettua merkkijonoa ei l\u00f6ytynyt.", +"Match case": "Erota isot ja pienet kirjaimet", +"Whole words": "Koko sanat", "Spellcheck": "Oikolue", -"Finish": "Lopeta", -"Ignore all": "\u00c4l\u00e4 huomioi mit\u00e4\u00e4n", "Ignore": "\u00c4l\u00e4 huomioi", +"Ignore all": "\u00c4l\u00e4 huomioi mit\u00e4\u00e4n", +"Finish": "Lopeta", "Add to Dictionary": "Lis\u00e4\u00e4 sanakirjaan", -"Insert row before": "Lis\u00e4\u00e4 rivi ennen", -"Rows": "Rivit", -"Height": "Korkeus", -"Paste row after": "Liit\u00e4 rivi j\u00e4lkeen", -"Alignment": "Tasaus", -"Border color": "Reunuksen v\u00e4ri", -"Column group": "Sarakeryhm\u00e4", -"Row": "Rivi", -"Insert column before": "Lis\u00e4\u00e4 rivi ennen", -"Split cell": "Jaa solu", -"Cell padding": "Solun tyhj\u00e4 tila", -"Cell spacing": "Solun v\u00e4li", -"Row type": "Rivityyppi", "Insert table": "Lis\u00e4\u00e4 taulukko", -"Body": "Runko", -"Caption": "Seloste", -"Footer": "Alaosa", -"Delete row": "Poista rivi", -"Paste row before": "Liit\u00e4 rivi ennen", -"Scope": "Laajuus", -"Delete table": "Poista taulukko", -"H Align": "H tasaus", -"Top": "Yl\u00e4reuna", -"Header cell": "Otsikkosolu", -"Column": "Sarake", -"Row group": "Riviryhm\u00e4", -"Cell": "Solu", -"Middle": "Keskikohta", -"Cell type": "Solun tyyppi", -"Copy row": "Kopioi rivi", -"Row properties": "Rivin ominaisuudet", "Table properties": "Taulukon ominaisuudet", -"Bottom": "Alareuna", -"V Align": "V tasaus", -"Header": "Otsikko", -"Right": "Oikea", -"Insert column after": "Lis\u00e4\u00e4 rivi j\u00e4lkeen", -"Cols": "Sarakkeet", -"Insert row after": "Lis\u00e4\u00e4 rivi j\u00e4lkeen", -"Width": "Leveys", +"Delete table": "Poista taulukko", +"Cell": "Solu", +"Row": "Rivi", +"Column": "Sarake", "Cell properties": "Solun ominaisuudet", -"Left": "Vasen", -"Cut row": "Leikkaa rivi", -"Delete column": "Poista sarake", -"Center": "Keskell\u00e4", "Merge cells": "Yhdist\u00e4 solut", +"Split cell": "Jaa solu", +"Insert row before": "Lis\u00e4\u00e4 rivi ennen", +"Insert row after": "Lis\u00e4\u00e4 rivi j\u00e4lkeen", +"Delete row": "Poista rivi", +"Row properties": "Rivin ominaisuudet", +"Cut row": "Leikkaa rivi", +"Copy row": "Kopioi rivi", +"Paste row before": "Liit\u00e4 rivi ennen", +"Paste row after": "Liit\u00e4 rivi j\u00e4lkeen", +"Insert column before": "Lis\u00e4\u00e4 rivi ennen", +"Insert column after": "Lis\u00e4\u00e4 rivi j\u00e4lkeen", +"Delete column": "Poista sarake", +"Cols": "Sarakkeet", +"Rows": "Rivit", +"Width": "Leveys", +"Height": "Korkeus", +"Cell spacing": "Solun v\u00e4li", +"Cell padding": "Solun tyhj\u00e4 tila", +"Caption": "Seloste", +"Left": "Vasen", +"Center": "Keskell\u00e4", +"Right": "Oikea", +"Cell type": "Solun tyyppi", +"Scope": "Laajuus", +"Alignment": "Tasaus", +"H Align": "H tasaus", +"V Align": "V tasaus", +"Top": "Yl\u00e4reuna", +"Middle": "Keskikohta", +"Bottom": "Alareuna", +"Header cell": "Otsikkosolu", +"Row group": "Riviryhm\u00e4", +"Column group": "Sarakeryhm\u00e4", +"Row type": "Rivityyppi", +"Header": "Otsikko", +"Body": "Runko", +"Footer": "Alaosa", +"Border color": "Reunuksen v\u00e4ri", "Insert template": "Lis\u00e4\u00e4 pohja", "Templates": "Pohjat", +"Template": "Pohja", +"Text color": "Tekstin v\u00e4ri", "Background color": "Taustan v\u00e4ri", "Custom...": "Mukauta...", "Custom color": "Mukautettu v\u00e4ri", "No color": "Ei v\u00e4ri\u00e4", -"Text color": "Tekstin v\u00e4ri", +"Table of Contents": "Sis\u00e4llysluettelo", "Show blocks": "N\u00e4yt\u00e4 lohkot", "Show invisible characters": "N\u00e4yt\u00e4 n\u00e4kym\u00e4tt\u00f6m\u00e4t merkit", "Words: {0}": "Sanat: {0}", -"Insert": "Lis\u00e4\u00e4", +"{0} words": "{0} sanaa", "File": "Tiedosto", "Edit": "Muokkaa", -"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Rikastetun tekstin alue. Paina ALT-F9 valikkoon. Paina ALT-F10 ty\u00f6kaluriviin. Paina ALT-0 ohjeeseen.", -"Tools": "Ty\u00f6kalut", +"Insert": "Lis\u00e4\u00e4", "View": "N\u00e4yt\u00e4", +"Format": "Muotoilu", "Table": "Taulukko", -"Format": "Muotoilu" +"Tools": "Ty\u00f6kalut", +"Powered by {0}": "Tehty {0}:ll\u00e4", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Rikastetun tekstin alue. Paina ALT-F9 valikkoon. Paina ALT-F10 ty\u00f6kaluriviin. Paina ALT-0 ohjeeseen." }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/fr.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/fr.js index b9cfd8b515..2d074f8c6e 100644 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/fr.js +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/fr.js @@ -1 +1,389 @@ -tinyMCE.addI18n({fr:{common:{"more_colors":"Plus de couleurs","invalid_data":"Erreur : saisie de valeurs incorrectes. Elles sont mises en \u00e9vidence en rouge.","popup_blocked":"D\u00e9sol\u00e9, nous avons d\u00e9tect\u00e9 que votre bloqueur de popup a bloqu\u00e9 une fen\u00eatre dont l\'application a besoin. Vous devez d\u00e9sactiver votre bloqueur de popup pour pouvoir utiliser cet outil.","clipboard_no_support":"Actuellement non support\u00e9 par votre navigateur.\n Veuillez utiliser les raccourcis clavier \u00e0 la place.","clipboard_msg":"Les fonctions Copier/Couper/Coller ne sont pas valables sur Mozilla et Firefox.\nSouhaitez-vous avoir plus d\'informations sur ce sujet ?","not_set":"-- non d\u00e9fini --","class_name":"Classe",browse:"parcourir",close:"Fermer",cancel:"Annuler",update:"Mettre \u00e0 jour",insert:"Ins\u00e9rer",apply:"Appliquer","edit_confirm":"Souhaitez-vous utiliser le mode WYSIWYG pour cette zone de texte ?","invalid_data_number":"{#field} doit \u00eatre un nombre","invalid_data_min":"{#field} doit \u00eatre un nombre plus grand que {#min}","invalid_data_size":"{#field} doit \u00eatre un nombre ou un pourcentage",value:"(valeur)"},contextmenu:{full:"Justifi\u00e9",right:"Droite",center:"Centr\u00e9",left:"Gauche",align:"Alignement"},insertdatetime:{"day_short":"Dim,Lun,Mar,Mer,Jeu,Ven,Sam,Dim","day_long":"Dimanche,Lundi,Mardi,Mercredi,Jeudi,Vendredi,Samedi,Dimanche","months_short":"Jan,F\u00e9v,Mar,Avr,Mai,Juin,Juil,Ao\u00fbt,Sep,Oct,Nov,D\u00e9c","months_long":"Janvier,F\u00e9vrier,Mars,Avril,Mai,Juin,Juillet,Ao\u00fbt,Septembre,Octobre,Novembre,D\u00e9cembre","inserttime_desc":"Ins\u00e9rer l\'heure","insertdate_desc":"Ins\u00e9rer la date","time_fmt":"%H:%M:%S","date_fmt":"%d-%m-%Y"},print:{"print_desc":"Imprimer"},preview:{"preview_desc":"Pr\u00e9visualiser"},directionality:{"rtl_desc":"\u00c9criture de droite \u00e0 gauche","ltr_desc":"\u00c9criture de gauche \u00e0 droite"},layer:{content:"Nouvelle couche\u2026","absolute_desc":"Activer le positionnement absolu","backward_desc":"D\u00e9placer vers l\'arri\u00e8re","forward_desc":"D\u00e9placer vers l\'avant","insertlayer_desc":"Ins\u00e9rer une nouvelle couche"},save:{"save_desc":"Enregistrer","cancel_desc":"Annuler toutes les modifications"},nonbreaking:{"nonbreaking_desc":"Ins\u00e9rer une espace ins\u00e9cable"},iespell:{download:"ieSpell n\'est pas install\u00e9. Souhaitez-vous l\'installer maintenant ?","iespell_desc":"Lancer le v\u00e9rificateur d\'orthographe"},advhr:{"delta_height":"Ecart de hauteur","delta_width":"Ecart de largeur","advhr_desc":"Ins\u00e9rer un trait horizontal"},emotions:{"delta_height":"delta_height","delta_width":"delta_width","emotions_desc":"\u00c9motic\u00f4nes"},searchreplace:{"replace_desc":"Rechercher / remplacer","search_desc":"Rechercher","delta_width":"","delta_height":""},advimage:{"image_desc":"Ins\u00e9rer / \u00e9diter une image","delta_width":"","delta_height":""},advlink:{"link_desc":"Ins\u00e9rer / \u00e9diter un lien","delta_height":"","delta_width":""},xhtmlxtras:{"attribs_desc":"Ins\u00e9rer / \u00e9diter les attributs","ins_desc":"Ins\u00e9r\u00e9","del_desc":"Barr\u00e9","acronym_desc":"Acronyme","abbr_desc":"Abr\u00e9viation","cite_desc":"Citation","attribs_delta_height":"","attribs_delta_width":"","ins_delta_height":"","ins_delta_width":"","del_delta_height":"","del_delta_width":"","acronym_delta_height":"","acronym_delta_width":"","abbr_delta_height":"","abbr_delta_width":"","cite_delta_height":"","cite_delta_width":""},style:{desc:"\u00c9diter la feuille de style (CSS)","delta_height":"","delta_width":""},paste:{"plaintext_mode":"Le collage est actuellement en mode texte non format\u00e9. Cliquez \u00e0 nouveau pour revenir en mode de collage ordinaire.","plaintext_mode_sticky":"Le collage est actuellement en mode texte non format\u00e9. Cliquez \u00e0 nouveau pour revenir en mode de collage ordinaire. Apr\u00e8s avoir coll\u00e9 quelque chose, vous retournerez en mode de collage ordinaire.","selectall_desc":"Tout s\u00e9lectionner","paste_word_desc":"Coller un texte cr\u00e9\u00e9 sous Word","paste_text_desc":"Coller comme texte brut"},"paste_dlg":{"word_title":"Utilisez CTRL+V sur votre clavier pour coller le texte dans la fen\u00eatre.","text_linebreaks":"Conserver les retours \u00e0 la ligne","text_title":"Utilisez CTRL+V sur votre clavier pour coller le texte dans la fen\u00eatre."},table:{cell:"Cellule",col:"Colonne",row:"Ligne",del:"Effacer le tableau","copy_row_desc":"Copier la ligne","cut_row_desc":"Couper la ligne","paste_row_after_desc":"Coller la ligne apr\u00e8s","paste_row_before_desc":"Coller la ligne avant","props_desc":"Propri\u00e9t\u00e9s du tableau","cell_desc":"Propri\u00e9t\u00e9s de la cellule","row_desc":"Propri\u00e9t\u00e9s de la ligne","merge_cells_desc":"Fusionner les cellules","split_cells_desc":"Scinder les cellules fusionn\u00e9es","delete_col_desc":"Effacer la colonne","col_after_desc":"Ins\u00e9rer une colonne apr\u00e8s","col_before_desc":"Ins\u00e9rer une colonne avant","delete_row_desc":"Effacer la ligne","row_after_desc":"Ins\u00e9rer une ligne apr\u00e8s","row_before_desc":"Ins\u00e9rer une ligne avant",desc:"Ins\u00e9rer un nouveau tableau","merge_cells_delta_height":"","merge_cells_delta_width":"","table_delta_height":"","table_delta_width":"","cellprops_delta_height":"","cellprops_delta_width":"","rowprops_delta_height":"","rowprops_delta_width":""},autosave:{"warning_message":"Si vous restaurez le contenu sauv\u00e9, vous perdrez le contenu qui est actuellement dans l\'\u00e9diteur.\n\n\u00cates-vous s\u00fbr de vouloir restaurer le contenu sauv\u00e9 ?","restore_content":"Restaurer le contenu auto-sauvegard\u00e9.","unload_msg":"Les modifications apport\u00e9es seront perdues si vous quittez cette page."},fullscreen:{desc:"Passer en mode plein \u00e9cran"},media:{edit:"\u00c9diter un m\u00e9dia incorpor\u00e9",desc:"Ins\u00e9rer / \u00e9diter un m\u00e9dia incorpor\u00e9","delta_height":"","delta_width":""},fullpage:{desc:"Propri\u00e9t\u00e9s du document","delta_width":"","delta_height":""},template:{desc:"Ins\u00e9rer un mod\u00e8le pr\u00e9d\u00e9fini."},visualchars:{desc:"Activer les caract\u00e8res de mise en page."},spellchecker:{desc:"Activer le v\u00e9rificateur d\'orthographe",menu:"Param\u00e8tres du v\u00e9rificateur d\'orthographe","ignore_word":"Ignorer le mot","ignore_words":"Tout ignorer",langs:"Langues",wait:"Veuillez patienter\u2026",sug:"Suggestions","no_sug":"Aucune suggestion","no_mpell":"Aucune erreur trouv\u00e9e.","learn_word":"Apprendre le mot"},pagebreak:{desc:"Ins\u00e9rer un saut de page."},advlist:{types:"Types",def:"D\u00e9faut","lower_alpha":"Alpha minuscule","lower_greek":"Grec minuscule","lower_roman":"Romain minuscule","upper_alpha":"Alpha majuscule","upper_roman":"Romain majuscule",circle:"Cercle",disc:"Disque",square:"Carr\u00e9"},colors:{"333300":"Olive fonc\u00e9","993300":"Orange br\u00fbl\u00e9","000000":"Noir","003300":"Vert fonc\u00e9","003366":"Azur fonc\u00e9","000080":"Bleu marine","333399":"Indigo","333333":"Gris tr\u00e8s fonc\u00e9","800000":"Bordeaux",FF6600:"Orange","808000":"Olive","008000":"Vert","008080":"Sarcelle","0000FF":"Bleu","666699":"Bleu gris\u00e2tre","808080":"Gris",FF0000:"Rouge",FF9900:"Ambre","99CC00":"Jaune vert","339966":"Mer verte","33CCCC":"Turquoise","3366FF":"Bleu royal","800080":"Violet","999999":"Gris moyen",FF00FF:"Magenta",FFCC00:"Or",FFFF00:"Jaune","00FF00":"Lime","00FFFF":"Bleu vert","00CCFF":"Bleu ciel","993366":"Brun",C0C0C0:"Argent",FF99CC:"Rose",FFCC99:"P\u00eache",FFFF99:"Jaune clair",CCFFCC:"Vert p\u00e2le",CCFFFF:"Cyan p\u00e2le","99CCFF":"Bleu ciel clair",CC99FF:"Prune",FFFFFF:"Blanc"},aria:{"rich_text_area":"Texte riche"},wordcount:{words:"Mots:"}}}); \ No newline at end of file +tinymce.addI18n('fr_FR',{ +"Redo": "R\u00e9tablir", +"Undo": "Annuler", +"Cut": "Couper", +"Copy": "Copier", +"Paste": "Coller", +"Select all": "S\u00e9lectionner tout", +"New document": "Nouveau document", +"Ok": "OK", +"Cancel": "Annuler", +"Visual aids": "Aides visuelles", +"Bold": "Gras", +"Italic": "Italique", +"Underline": "Soulign\u00e9", +"Strikethrough": "Barr\u00e9", +"Superscript": "Exposant", +"Subscript": "Indice", +"Clear formatting": "Effacer la mise en forme", +"Align left": "Aligner \u00e0 gauche", +"Align center": "Centrer", +"Align right": "Aligner \u00e0 droite", +"Justify": "Justifier", +"Bullet list": "Liste \u00e0 puces", +"Numbered list": "Liste num\u00e9rot\u00e9e", +"Decrease indent": "R\u00e9duire le retrait", +"Increase indent": "Augmenter le retrait", +"Close": "Fermer", +"Formats": "Formats", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "Votre navigateur ne supporte pas l\u2019acc\u00e8s direct au presse-papiers. Merci d'utiliser les raccourcis clavier Ctrl+X\/C\/V.", +"Headers": "En-t\u00eates", +"Header 1": "En-t\u00eate 1", +"Header 2": "En-t\u00eate 2", +"Header 3": "En-t\u00eate 3", +"Header 4": "En-t\u00eate 4", +"Header 5": "En-t\u00eate 5", +"Header 6": "En-t\u00eate 6", +"Headings": "Titres", +"Heading 1": "Titre\u00a01", +"Heading 2": "Titre\u00a02", +"Heading 3": "Titre\u00a03", +"Heading 4": "Titre\u00a04", +"Heading 5": "Titre\u00a05", +"Heading 6": "Titre\u00a06", +"Preformatted": "Pr\u00e9format\u00e9", +"Div": "Div", +"Pre": "Pre", +"Code": "Code", +"Paragraph": "Paragraphe", +"Blockquote": "Blockquote", +"Inline": "En ligne", +"Blocks": "Blocs", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "Le presse-papiers est maintenant en mode \"texte plein\". Les contenus seront coll\u00e9s sans retenir les formatages jusqu'\u00e0 ce que vous d\u00e9sactiviez cette option.", +"Fonts": "Polices", +"Font Sizes": "Tailles de police", +"Class": "Classe", +"Browse for an image": "Rechercher une image", +"OR": "OU", +"Drop an image here": "D\u00e9poser une image ici", +"Upload": "T\u00e9l\u00e9charger", +"Block": "Bloc", +"Align": "Aligner", +"Default": "Par d\u00e9faut", +"Circle": "Cercle", +"Disc": "Disque", +"Square": "Carr\u00e9", +"Lower Alpha": "Alpha minuscule", +"Lower Greek": "Grec minuscule", +"Lower Roman": "Romain minuscule", +"Upper Alpha": "Alpha majuscule", +"Upper Roman": "Romain majuscule", +"Anchor...": "Ancre...", +"Name": "Nom", +"Id": "Id", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "L'Id doit commencer par une lettre suivi par des lettres, nombres, tirets, points, deux-points ou underscores", +"You have unsaved changes are you sure you want to navigate away?": "Vous avez des modifications non enregistr\u00e9es, \u00eates-vous s\u00fbr de quitter la page?", +"Restore last draft": "Restaurer le dernier brouillon", +"Special characters...": "Caract\u00e8res sp\u00e9ciaux...", +"Source code": "Code source", +"Insert\/Edit code sample": "Ins\u00e9rer \/ modifier une exemple de code", +"Language": "Langue", +"Code sample...": "Exemple de code...", +"Color Picker": "S\u00e9lecteur de couleurs", +"R": "R", +"G": "V", +"B": "B", +"Left to right": "Gauche \u00e0 droite", +"Right to left": "Droite \u00e0 gauche", +"Emoticons...": "\u00c9motic\u00f4nes...", +"Metadata and Document Properties": "M\u00e9tadonn\u00e9es et propri\u00e9t\u00e9s du document", +"Title": "Titre", +"Keywords": "Mots-cl\u00e9s", +"Description": "Description", +"Robots": "Robots", +"Author": "Auteur", +"Encoding": "Encodage", +"Fullscreen": "Plein \u00e9cran", +"Action": "Action", +"Shortcut": "Raccourci", +"Help": "Aide", +"Address": "Adresse", +"Focus to menubar": "Cibler la barre de menu", +"Focus to toolbar": "Cibler la barre d'outils", +"Focus to element path": "Cibler le chemin vers l'\u00e9l\u00e9ment", +"Focus to contextual toolbar": "Cibler la barre d'outils contextuelle", +"Insert link (if link plugin activated)": "Ins\u00e9rer un lien (si le module link est activ\u00e9)", +"Save (if save plugin activated)": "Enregistrer (si le module save est activ\u00e9)", +"Find (if searchreplace plugin activated)": "Rechercher (si le module searchreplace est activ\u00e9)", +"Plugins installed ({0}):": "Modules install\u00e9s ({0}) : ", +"Premium plugins:": "Modules premium :", +"Learn more...": "En savoir plus...", +"You are using {0}": "Vous utilisez {0}", +"Plugins": "Plugins", +"Handy Shortcuts": "Raccourcis utiles", +"Horizontal line": "Ligne horizontale", +"Insert\/edit image": "Ins\u00e9rer\/modifier une image", +"Image description": "Description de l'image", +"Source": "Source", +"Dimensions": "Dimensions", +"Constrain proportions": "Conserver les proportions", +"General": "G\u00e9n\u00e9ral", +"Advanced": "Avanc\u00e9", +"Style": "Style", +"Vertical space": "Espacement vertical", +"Horizontal space": "Espacement horizontal", +"Border": "Bordure", +"Insert image": "Ins\u00e9rer une image", +"Image...": "Image...", +"Image list": "Liste d'images", +"Rotate counterclockwise": "Rotation anti-horaire", +"Rotate clockwise": "Rotation horaire", +"Flip vertically": "Retournement vertical", +"Flip horizontally": "Retournement horizontal", +"Edit image": "Modifier l'image", +"Image options": "Options de l'image", +"Zoom in": "Zoomer", +"Zoom out": "D\u00e9zoomer", +"Crop": "Rogner", +"Resize": "Redimensionner", +"Orientation": "Orientation", +"Brightness": "Luminosit\u00e9", +"Sharpen": "Affiner", +"Contrast": "Contraste", +"Color levels": "Niveaux de couleur", +"Gamma": "Gamma", +"Invert": "Inverser", +"Apply": "Appliquer", +"Back": "Retour", +"Insert date\/time": "Ins\u00e9rer date\/heure", +"Date\/time": "Date\/heure", +"Insert\/Edit Link": "Ins\u00e9rer\/Modifier lien", +"Insert\/edit link": "Ins\u00e9rer\/modifier un lien", +"Text to display": "Texte \u00e0 afficher", +"Url": "Url", +"Open link in...": "Ouvrir le lien dans...", +"Current window": "Fen\u00eatre active", +"None": "n\/a", +"New window": "Nouvelle fen\u00eatre", +"Remove link": "Enlever le lien", +"Anchors": "Ancres", +"Link...": "Lien...", +"Paste or type a link": "Coller ou taper un lien", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "L'URL que vous avez entr\u00e9e semble \u00eatre une adresse e-mail. Voulez-vous ajouter le pr\u00e9fixe mailto: n\u00e9cessaire?", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "L'URL que vous avez entr\u00e9e semble \u00eatre un lien externe. Voulez-vous ajouter le pr\u00e9fixe http:\/\/ n\u00e9cessaire?", +"Link list": "Liste de liens", +"Insert video": "Ins\u00e9rer une vid\u00e9o", +"Insert\/edit video": "Ins\u00e9rer\/modifier une vid\u00e9o", +"Insert\/edit media": "Ins\u00e9rer\/modifier un m\u00e9dia", +"Alternative source": "Source alternative", +"Alternative source URL": "URL de la source alternative", +"Media poster (Image URL)": "Affiche de m\u00e9dia (URL de l'image)", +"Paste your embed code below:": "Collez votre code d'int\u00e9gration ci-dessous :", +"Embed": "Int\u00e9grer", +"Media...": "M\u00e9dia...", +"Nonbreaking space": "Espace ins\u00e9cable", +"Page break": "Saut de page", +"Paste as text": "Coller comme texte", +"Preview": "Pr\u00e9visualiser", +"Print...": "Imprimer...", +"Save": "Enregistrer", +"Find": "Chercher", +"Replace with": "Remplacer par", +"Replace": "Remplacer", +"Replace all": "Tout remplacer", +"Previous": "Pr\u00e9c\u00e9dente", +"Next": "Suiv", +"Find and replace...": "Trouver et remplacer...", +"Could not find the specified string.": "Impossible de trouver la cha\u00eene sp\u00e9cifi\u00e9e.", +"Match case": "Respecter la casse", +"Find whole words only": "Mot entier", +"Spell check": "V\u00e9rification de l'orthographe", +"Ignore": "Ignorer", +"Ignore all": "Tout ignorer", +"Finish": "Finie", +"Add to Dictionary": "Ajouter au dictionnaire", +"Insert table": "Ins\u00e9rer un tableau", +"Table properties": "Propri\u00e9t\u00e9s du tableau", +"Delete table": "Supprimer le tableau", +"Cell": "Cellule", +"Row": "Ligne", +"Column": "Colonne", +"Cell properties": "Propri\u00e9t\u00e9s de la cellule", +"Merge cells": "Fusionner les cellules", +"Split cell": "Diviser la cellule", +"Insert row before": "Ins\u00e9rer une ligne avant", +"Insert row after": "Ins\u00e9rer une ligne apr\u00e8s", +"Delete row": "Effacer la ligne", +"Row properties": "Propri\u00e9t\u00e9s de la ligne", +"Cut row": "Couper la ligne", +"Copy row": "Copier la ligne", +"Paste row before": "Coller la ligne avant", +"Paste row after": "Coller la ligne apr\u00e8s", +"Insert column before": "Ins\u00e9rer une colonne avant", +"Insert column after": "Ins\u00e9rer une colonne apr\u00e8s", +"Delete column": "Effacer la colonne", +"Cols": "Colonnes", +"Rows": "Lignes", +"Width": "Largeur", +"Height": "Hauteur", +"Cell spacing": "Espacement inter-cellulles", +"Cell padding": "Espacement interne cellule", +"Show caption": "Afficher le sous-titrage", +"Left": "Gauche", +"Center": "Centr\u00e9", +"Right": "Droite", +"Cell type": "Type de cellule", +"Scope": "Etendue", +"Alignment": "Alignement", +"H Align": "Alignement H", +"V Align": "Alignement V", +"Top": "Haut", +"Middle": "Milieu", +"Bottom": "Bas", +"Header cell": "Cellule d'en-t\u00eate", +"Row group": "Groupe de lignes", +"Column group": "Groupe de colonnes", +"Row type": "Type de ligne", +"Header": "En-t\u00eate", +"Body": "Corps", +"Footer": "Pied", +"Border color": "Couleur de la bordure", +"Insert template...": "Ins\u00e9rer un mod\u00e8le...", +"Templates": "Th\u00e8mes", +"Template": "Mod\u00e8le", +"Text color": "Couleur du texte", +"Background color": "Couleur d'arri\u00e8re-plan", +"Custom...": "Personnalis\u00e9...", +"Custom color": "Couleur personnalis\u00e9e", +"No color": "Aucune couleur", +"Remove color": "Supprimer la couleur", +"Table of Contents": "Table des mati\u00e8res", +"Show blocks": "Afficher les blocs", +"Show invisible characters": "Afficher les caract\u00e8res invisibles", +"Word count": "Nombre de mots", +"Words: {0}": "Mots : {0}", +"{0} words": "{0} mots", +"File": "Fichier", +"Edit": "Editer", +"Insert": "Ins\u00e9rer", +"View": "Voir", +"Format": "Format", +"Table": "Tableau", +"Tools": "Outils", +"Powered by {0}": "Propuls\u00e9 par {0}", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Zone Texte Riche. Appuyer sur ALT-F9 pour le menu. Appuyer sur ALT-F10 pour la barre d'outils. Appuyer sur ALT-0 pour de l'aide.", +"Image title": "Titre d'image", +"Border width": "\u00c9paisseur de la bordure", +"Border style": "Style de la bordure", +"Error": "Erreur", +"Warn": "Avertir", +"Valid": "Valide", +"To open the popup, press Shift+Enter": "Pour ouvrir la popup, appuyez sur Maj+Entr\u00e9e", +"Rich Text Area. Press ALT-0 for help.": "Zone de texte riche. Appuyez sur ALT-0 pour l'aide.", +"System Font": "Police syst\u00e8me", +"Failed to upload image: {0}": "\u00c9chec d'envoi de l'image\u00a0: {0}", +"Failed to load plugin: {0} from url {1}": "\u00c9chec de chargement du plug-in\u00a0: {0} \u00e0 partir de l\u2019URL {1} ", +"Failed to load plugin url: {0}": "\u00c9chec de chargement de l'URL du plug-in\u00a0: {0}", +"Failed to initialize plugin: {0}": "\u00c9chec d'initialisation du plug-in\u00a0: {0}", +"example": "exemple", +"Search": "Rechercher", +"All": "Tout", +"Currency": "Devise", +"Text": "Texte", +"Quotations": "Citations", +"Mathematical": "Op\u00e9rateurs math\u00e9matiques", +"Extended Latin": "Latin \u00e9tendu", +"Symbols": "Symboles", +"Arrows": "Fl\u00e8ches", +"User Defined": "D\u00e9fini par l'utilisateur", +"dollar sign": "Symbole dollar", +"currency sign": "Symbole devise", +"euro-currency sign": "Symbole euro", +"colon sign": "Symbole col\u00f3n", +"cruzeiro sign": "Symbole cruzeiro", +"french franc sign": "Symbole franc fran\u00e7ais", +"lira sign": "Symbole lire", +"mill sign": "Symbole milli\u00e8me", +"naira sign": "Symbole naira", +"peseta sign": "Symbole peseta", +"rupee sign": "Symbole roupie", +"won sign": "Symbole won", +"new sheqel sign": "Symbole nouveau ch\u00e9kel", +"dong sign": "Symbole dong", +"kip sign": "Symbole kip", +"tugrik sign": "Symbole tougrik", +"drachma sign": "Symbole drachme", +"german penny symbol": "Symbole pfennig", +"peso sign": "Symbole peso", +"guarani sign": "Symbole guarani", +"austral sign": "Symbole austral", +"hryvnia sign": "Symbole hryvnia", +"cedi sign": "Symbole cedi", +"livre tournois sign": "Symbole livre tournois", +"spesmilo sign": "Symbole spesmilo", +"tenge sign": "Symbole tenge", +"indian rupee sign": "Symbole roupie indienne", +"turkish lira sign": "Symbole lire turque", +"nordic mark sign": "Symbole du mark nordique", +"manat sign": "Symbole manat", +"ruble sign": "Symbole rouble", +"yen character": "Sinogramme Yen", +"yuan character": "Sinogramme Yuan", +"yuan character, in hong kong and taiwan": "Sinogramme Yuan, Hong Kong et Taiwan", +"yen\/yuan character variant one": "Sinogramme Yen\/Yuan, premi\u00e8re variante", +"Loading emoticons...": "Chargement des \u00e9motic\u00f4nes en cours...", +"Could not load emoticons": "\u00c9chec de chargement des \u00e9motic\u00f4nes", +"People": "Personnes", +"Animals and Nature": "Animaux & nature", +"Food and Drink": "Nourriture & boissons", +"Activity": "Activit\u00e9", +"Travel and Places": "Voyages & lieux", +"Objects": "Objets", +"Flags": "Drapeaux", +"Characters": "Caract\u00e8res", +"Characters (no spaces)": "Caract\u00e8res (espaces non compris)", +"Error: Form submit field collision.": "Erreur : conflit de champs lors de la soumission du formulaire", +"Error: No form element found.": "Erreur : aucun \u00e9l\u00e9ment de formulaire trouv\u00e9.", +"Update": "Mettre \u00e0 jour", +"Color swatch": "\u00c9chantillon de couleurs", +"Turquoise": "Turquoise", +"Green": "Vert", +"Blue": "Bleu", +"Purple": "Violet", +"Navy Blue": "Bleu marine", +"Dark Turquoise": "Turquoise fonc\u00e9", +"Dark Green": "Vert fonc\u00e9", +"Medium Blue": "Bleu moyen", +"Medium Purple": "Violet moyen", +"Midnight Blue": "Bleu de minuit", +"Yellow": "Jaune", +"Orange": "Orange", +"Red": "Rouge", +"Light Gray": "Gris clair", +"Gray": "Gris", +"Dark Yellow": "Jaune fonc\u00e9", +"Dark Orange": "Orange fonc\u00e9", +"Dark Red": "Rouge fonc\u00e9", +"Medium Gray": "Gris moyen", +"Dark Gray": "Gris fonc\u00e9", +"Black": "Noir", +"White": "Blanc", +"Switch to or from fullscreen mode": "Passer en ou quitter le mode plein \u00e9cran", +"Open help dialog": "Ouvrir la bo\u00eete de dialogue d'aide", +"history": "historique", +"styles": "styles", +"formatting": "mise en forme", +"alignment": "alignement", +"indentation": "retrait", +"permanent pen": "feutre ind\u00e9l\u00e9bile", +"comments": "commentaires", +"Anchor": "Ancre", +"Special character": "Caract\u00e8res sp\u00e9ciaux", +"Code sample": "Extrait de code", +"Color": "Couleur", +"Emoticons": "Emotic\u00f4nes", +"Document properties": "Propri\u00e9t\u00e9 du document", +"Image": "Image", +"Insert link": "Ins\u00e9rer un lien", +"Target": "Cible", +"Link": "Lien", +"Poster": "Publier", +"Media": "M\u00e9dia", +"Print": "Imprimer", +"Prev": "Pr\u00e9c ", +"Find and replace": "Trouver et remplacer", +"Whole words": "Mots entiers", +"Spellcheck": "V\u00e9rification orthographique", +"Caption": "Titre", +"Insert template": "Ajouter un th\u00e8me" +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/fr_FR.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/fr_FR.js new file mode 100644 index 0000000000..5c37164b2c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/fr_FR.js @@ -0,0 +1,261 @@ +tinymce.addI18n('fr_FR',{ +"Redo": "R\u00e9tablir", +"Undo": "Annuler", +"Cut": "Couper", +"Copy": "Copier", +"Paste": "Coller", +"Select all": "Tout s\u00e9lectionner", +"New document": "Nouveau document", +"Ok": "Ok", +"Cancel": "Annuler", +"Visual aids": "Aides visuelle", +"Bold": "Gras", +"Italic": "Italique", +"Underline": "Soulign\u00e9", +"Strikethrough": "Barr\u00e9", +"Superscript": "Exposant", +"Subscript": "Indice", +"Clear formatting": "Effacer la mise en forme", +"Align left": "Aligner \u00e0 gauche", +"Align center": "Centrer", +"Align right": "Aligner \u00e0 droite", +"Justify": "Justifier", +"Bullet list": "Puces", +"Numbered list": "Num\u00e9rotation", +"Decrease indent": "Diminuer le retrait", +"Increase indent": "Augmenter le retrait", +"Close": "Fermer", +"Formats": "Formats", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "Votre navigateur ne supporte pas la copie directe. Merci d'utiliser les touches Ctrl+X\/C\/V.", +"Headers": "Titres", +"Header 1": "Titre 1", +"Header 2": "Titre 2", +"Header 3": "Titre 3", +"Header 4": "Titre 4", +"Header 5": "Titre 5", +"Header 6": "Titre 6", +"Headings": "En-t\u00eates", +"Heading 1": "En-t\u00eate 1", +"Heading 2": "En-t\u00eate 2", +"Heading 3": "En-t\u00eate 3", +"Heading 4": "En-t\u00eate 4", +"Heading 5": "En-t\u00eate 5", +"Heading 6": "En-t\u00eate 6", +"Preformatted": "Pr\u00e9-formatt\u00e9", +"Div": "Div", +"Pre": "Pre", +"Code": "Code", +"Paragraph": "Paragraphe", +"Blockquote": "Citation", +"Inline": "En ligne", +"Blocks": "Blocs", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "Le presse-papiers est maintenant en mode \"texte plein\". Les contenus seront coll\u00e9s sans retenir les formatages jusqu'\u00e0 ce que vous d\u00e9sactiviez cette option.", +"Font Family": "Police", +"Font Sizes": "Taille de police", +"Class": "Classe", +"Browse for an image": "Parcourir pour s\u00e9lectionner une image", +"OR": "OU", +"Drop an image here": "Glisser une image ici", +"Upload": "D\u00e9poser", +"Block": "Bloquer", +"Align": "Aligner", +"Default": "Par d\u00e9faut", +"Circle": "Cercle", +"Disc": "Disque", +"Square": "Carr\u00e9", +"Lower Alpha": "Alpha minuscule", +"Lower Greek": "Grec minuscule", +"Lower Roman": "Romain minuscule", +"Upper Alpha": "Alpha majuscule", +"Upper Roman": "Romain majuscule", +"Anchor": "Ancre", +"Name": "Nom", +"Id": "Id", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "L'Id doit commencer par une lettre suivi par des lettres, nombres, tirets, points, deux-points ou underscores", +"You have unsaved changes are you sure you want to navigate away?": "Vous avez des modifications non enregistr\u00e9es, \u00eates-vous s\u00fbr de quitter la page?", +"Restore last draft": "Restaurer le dernier brouillon", +"Special character": "Caract\u00e8res sp\u00e9ciaux", +"Source code": "Code source", +"Insert\/Edit code sample": "Ins\u00e9rer \/ modifier une exemple de code", +"Language": "Langue", +"Code sample": "Extrait de code", +"Color": "Couleur", +"R": "R", +"G": "V", +"B": "B", +"Left to right": "Gauche \u00e0 droite", +"Right to left": "Droite \u00e0 gauche", +"Emoticons": "Emotic\u00f4nes", +"Document properties": "Propri\u00e9t\u00e9 du document", +"Title": "Titre", +"Keywords": "Mots-cl\u00e9s", +"Description": "Description", +"Robots": "Robots", +"Author": "Auteur", +"Encoding": "Encodage", +"Fullscreen": "Plein \u00e9cran", +"Action": "Action", +"Shortcut": "Raccourci", +"Help": "Aide", +"Address": "Adresse", +"Focus to menubar": "Cibler la barre de menu", +"Focus to toolbar": "Cibler la barre d'outils", +"Focus to element path": "Cibler le chemin vers l'\u00e9l\u00e9ment", +"Focus to contextual toolbar": "Cibler la barre d'outils contextuelle", +"Insert link (if link plugin activated)": "Ins\u00e9rer un lien (si le module link est activ\u00e9)", +"Save (if save plugin activated)": "Enregistrer (si le module save est activ\u00e9)", +"Find (if searchreplace plugin activated)": "Rechercher (si le module searchreplace est activ\u00e9)", +"Plugins installed ({0}):": "Modules install\u00e9s ({0}) : ", +"Premium plugins:": "Modules premium :", +"Learn more...": "En savoir plus...", +"You are using {0}": "Vous utilisez {0}", +"Plugins": "Plugins", +"Handy Shortcuts": "Raccourcis utiles", +"Horizontal line": "Ligne horizontale", +"Insert\/edit image": "Ins\u00e9rer\/modifier une image", +"Image description": "Description de l'image", +"Source": "Source", +"Dimensions": "Dimensions", +"Constrain proportions": "Conserver les proportions", +"General": "G\u00e9n\u00e9ral", +"Advanced": "Avanc\u00e9", +"Style": "Style", +"Vertical space": "Espacement vertical", +"Horizontal space": "Espacement horizontal", +"Border": "Bordure", +"Insert image": "Ins\u00e9rer une image", +"Image": "Image", +"Image list": "Liste d'images", +"Rotate counterclockwise": "Rotation anti-horaire", +"Rotate clockwise": "Rotation horaire", +"Flip vertically": "Retournement vertical", +"Flip horizontally": "Retournement horizontal", +"Edit image": "Modifier l'image", +"Image options": "Options de l'image", +"Zoom in": "Zoomer", +"Zoom out": "D\u00e9zoomer", +"Crop": "Rogner", +"Resize": "Redimensionner", +"Orientation": "Orientation", +"Brightness": "Luminosit\u00e9", +"Sharpen": "Affiner", +"Contrast": "Contraste", +"Color levels": "Niveaux de couleur", +"Gamma": "Gamma", +"Invert": "Inverser", +"Apply": "Appliquer", +"Back": "Retour", +"Insert date\/time": "Ins\u00e9rer date\/heure", +"Date\/time": "Date\/heure", +"Insert link": "Ins\u00e9rer un lien", +"Insert\/edit link": "Ins\u00e9rer\/modifier un lien", +"Text to display": "Texte \u00e0 afficher", +"Url": "Url", +"Target": "Cible", +"None": "n\/a", +"New window": "Nouvelle fen\u00eatre", +"Remove link": "Enlever le lien", +"Anchors": "Ancres", +"Link": "Lien", +"Paste or type a link": "Coller ou taper un lien", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "L'URL que vous avez entr\u00e9e semble \u00eatre une adresse e-mail. Voulez-vous ajouter le pr\u00e9fixe mailto: n\u00e9cessaire?", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "L'URL que vous avez entr\u00e9e semble \u00eatre un lien externe. Voulez-vous ajouter le pr\u00e9fixe http:\/\/ n\u00e9cessaire?", +"Link list": "Liste de liens", +"Insert video": "Ins\u00e9rer une vid\u00e9o", +"Insert\/edit video": "Ins\u00e9rer\/modifier une vid\u00e9o", +"Insert\/edit media": "Ins\u00e9rer\/modifier un m\u00e9dia", +"Alternative source": "Source alternative", +"Poster": "Publier", +"Paste your embed code below:": "Collez votre code d'int\u00e9gration ci-dessous :", +"Embed": "Int\u00e9grer", +"Media": "M\u00e9dia", +"Nonbreaking space": "Espace ins\u00e9cable", +"Page break": "Saut de page", +"Paste as text": "Coller comme texte", +"Preview": "Pr\u00e9visualiser", +"Print": "Imprimer", +"Save": "Enregistrer", +"Find": "Chercher", +"Replace with": "Remplacer par", +"Replace": "Remplacer", +"Replace all": "Tout remplacer", +"Prev": "Pr\u00e9c ", +"Next": "Suiv", +"Find and replace": "Trouver et remplacer", +"Could not find the specified string.": "Impossible de trouver la cha\u00eene sp\u00e9cifi\u00e9e.", +"Match case": "Respecter la casse", +"Whole words": "Mots entiers", +"Spellcheck": "V\u00e9rification orthographique", +"Ignore": "Ignorer", +"Ignore all": "Tout ignorer", +"Finish": "Finie", +"Add to Dictionary": "Ajouter au dictionnaire", +"Insert table": "Ins\u00e9rer un tableau", +"Table properties": "Propri\u00e9t\u00e9s du tableau", +"Delete table": "Supprimer le tableau", +"Cell": "Cellule", +"Row": "Ligne", +"Column": "Colonne", +"Cell properties": "Propri\u00e9t\u00e9s de la cellule", +"Merge cells": "Fusionner les cellules", +"Split cell": "Diviser la cellule", +"Insert row before": "Ins\u00e9rer une ligne avant", +"Insert row after": "Ins\u00e9rer une ligne apr\u00e8s", +"Delete row": "Effacer la ligne", +"Row properties": "Propri\u00e9t\u00e9s de la ligne", +"Cut row": "Couper la ligne", +"Copy row": "Copier la ligne", +"Paste row before": "Coller la ligne avant", +"Paste row after": "Coller la ligne apr\u00e8s", +"Insert column before": "Ins\u00e9rer une colonne avant", +"Insert column after": "Ins\u00e9rer une colonne apr\u00e8s", +"Delete column": "Effacer la colonne", +"Cols": "Colonnes", +"Rows": "Lignes", +"Width": "Largeur", +"Height": "Hauteur", +"Cell spacing": "Espacement inter-cellulles", +"Cell padding": "Espacement interne cellule", +"Caption": "Titre", +"Left": "Gauche", +"Center": "Centr\u00e9", +"Right": "Droite", +"Cell type": "Type de cellule", +"Scope": "Etendue", +"Alignment": "Alignement", +"H Align": "Alignement H", +"V Align": "Alignement V", +"Top": "Haut", +"Middle": "Milieu", +"Bottom": "Bas", +"Header cell": "Cellule d'en-t\u00eate", +"Row group": "Groupe de lignes", +"Column group": "Groupe de colonnes", +"Row type": "Type de ligne", +"Header": "En-t\u00eate", +"Body": "Corps", +"Footer": "Pied", +"Border color": "Couleur de la bordure", +"Insert template": "Ajouter un th\u00e8me", +"Templates": "Th\u00e8mes", +"Template": "Mod\u00e8le", +"Text color": "Couleur du texte", +"Background color": "Couleur d'arri\u00e8re-plan", +"Custom...": "Personnalis\u00e9...", +"Custom color": "Couleur personnalis\u00e9e", +"No color": "Aucune couleur", +"Table of Contents": "Table des mati\u00e8res", +"Show blocks": "Afficher les blocs", +"Show invisible characters": "Afficher les caract\u00e8res invisibles", +"Words: {0}": "Mots : {0}", +"{0} words": "{0} mots", +"File": "Fichier", +"Edit": "Editer", +"Insert": "Ins\u00e9rer", +"View": "Voir", +"Format": "Format", +"Table": "Tableau", +"Tools": "Outils", +"Powered by {0}": "Propuls\u00e9 par {0}", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Zone Texte Riche. Appuyer sur ALT-F9 pour le menu. Appuyer sur ALT-F10 pour la barre d'outils. Appuyer sur ALT-0 pour de l'aide." +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/ga.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/ga.js new file mode 100644 index 0000000000..c2a942c121 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/ga.js @@ -0,0 +1,261 @@ +tinymce.addI18n('ga',{ +"Redo": "Athdh\u00e9an", +"Undo": "Cealaigh", +"Cut": "Gearr", +"Copy": "C\u00f3ipe\u00e1il", +"Paste": "Greamaigh", +"Select all": "Roghnaigh uile", +"New document": "C\u00e1ip\u00e9is nua", +"Ok": "OK", +"Cancel": "Cealaigh", +"Visual aids": "\u00c1iseanna amhairc", +"Bold": "Trom", +"Italic": "Iod\u00e1lach", +"Underline": "Fol\u00edne", +"Strikethrough": "L\u00edne tr\u00edd", +"Superscript": "Forscript", +"Subscript": "Foscript", +"Clear formatting": "Glan form\u00e1idi\u00fa", +"Align left": "Ail\u00ednigh ar chl\u00e9", +"Align center": "Ail\u00ednigh sa l\u00e1r", +"Align right": "Ail\u00ednigh ar dheis", +"Justify": "Comhfhadaigh", +"Bullet list": "Liosta Urchar", +"Numbered list": "Liosta Uimhrithe", +"Decrease indent": "Laghdaigh eang", +"Increase indent": "M\u00e9adaigh eang", +"Close": "D\u00fan", +"Formats": "Form\u00e1id\u00ed", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "N\u00ed f\u00e9idir le do bhrabhs\u00e1la\u00ed teacht go d\u00edreach ar an ngearrthaisce. Bain \u00fas\u00e1id as na haicearra\u00ed Ctrl+X\/C\/V. ", +"Headers": "Ceannt\u00e1sca", +"Header 1": "Ceannt\u00e1sc 1", +"Header 2": "Ceannt\u00e1sc 2", +"Header 3": "Ceannt\u00e1sc 3", +"Header 4": "Ceannt\u00e1sc 4", +"Header 5": "Ceannt\u00e1sc 5", +"Header 6": "Ceannt\u00e1sc 6", +"Headings": "Ceannteidil", +"Heading 1": "Ceannteideal 1", +"Heading 2": "Ceannteideal 2", +"Heading 3": "Ceannteideal 3", +"Heading 4": "Ceannteideal 4", +"Heading 5": "Ceannteideal 5", +"Heading 6": "Ceannteideal 6", +"Preformatted": "R\u00e9amhfhorm\u00e1idithe", +"Div": "Deighilt", +"Pre": "R\u00e9amh", +"Code": "C\u00f3d", +"Paragraph": "Alt", +"Blockquote": "Athfhriotal", +"Inline": "Inl\u00edne", +"Blocks": "Blocanna", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "Sa m\u00f3d gn\u00e1th-th\u00e9acs anois. Gream\u00f3far \u00e1bhar mar ghn\u00e1th-th\u00e9acs go dt\u00ed go m\u00fachfaidh t\u00fa an rogha seo.", +"Font Family": "Cl\u00f3fhoireann", +"Font Sizes": "Cl\u00f3mh\u00e9ideanna", +"Class": "Aicme", +"Browse for an image": "Brabhs\u00e1il le haghaidh \u00edomh\u00e1", +"OR": "N\u00d3", +"Drop an image here": "Scaoil \u00edomh\u00e1 anseo", +"Upload": "Uasl\u00f3d\u00e1il", +"Block": "Bloc", +"Align": "Ail\u00ednigh", +"Default": "R\u00e9amhshocr\u00fa", +"Circle": "Ciorcal", +"Disc": "Diosca", +"Square": "Cearn\u00f3g", +"Lower Alpha": "Alfa Beag", +"Lower Greek": "Litir Bheag Ghr\u00e9agach", +"Lower Roman": "Litir Bheag R\u00f3mh\u00e1nach", +"Upper Alpha": "Alfa M\u00f3r", +"Upper Roman": "Litir Mh\u00f3r R\u00f3mh\u00e1nach", +"Anchor": "Ancaire", +"Name": "Ainm", +"Id": "Aitheantas", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "N\u00ed m\u00f3r don aitheantas tos\u00fa le litir, agus gan ach litreacha, uimhreacha, daiseanna, poncanna, idirstadanna, n\u00f3 fostr\u00edoca ina dhiaidh sin.", +"You have unsaved changes are you sure you want to navigate away?": "T\u00e1 athruithe gan s\u00e1bh\u00e1il ann. An bhfuil t\u00fa cinnte gur mhaith leat imeacht amach as seo?", +"Restore last draft": "Oscail an dr\u00e9acht is d\u00e9ana\u00ed", +"Special character": "Carachtar speisialta", +"Source code": "C\u00f3d foinseach", +"Insert\/Edit code sample": "Cuir sampla c\u00f3id isteach\/in eagar", +"Language": "Teanga", +"Code sample": "Sampla c\u00f3id", +"Color": "Dath", +"R": "D", +"G": "U", +"B": "G", +"Left to right": "Cl\u00e9-go-deas", +"Right to left": "Deas-go-cl\u00e9", +"Emoticons": "Straoiseoga", +"Document properties": "Air\u00edonna na C\u00e1ip\u00e9ise", +"Title": "Teideal", +"Keywords": "Lorgfhocail", +"Description": "Cur S\u00edos", +"Robots": "R\u00f3bait", +"Author": "\u00dadar", +"Encoding": "Ionch\u00f3d\u00fa", +"Fullscreen": "L\u00e1nsc\u00e1ile\u00e1n", +"Action": "Gn\u00edomh", +"Shortcut": "Aicearra", +"Help": "Cabhair", +"Address": "Seoladh", +"Focus to menubar": "F\u00f3cas sa bharra roghchl\u00e1ir", +"Focus to toolbar": "F\u00f3cas sa bharra uirlis\u00ed", +"Focus to element path": "F\u00f3cas sa chonair eiliminte", +"Focus to contextual toolbar": "F\u00f3cas sa bharra uirlis\u00ed comhth\u00e9acs\u00fail", +"Insert link (if link plugin activated)": "Cuir nasc isteach (m\u00e1 t\u00e1 an breise\u00e1n naisc ar si\u00fal)", +"Save (if save plugin activated)": "S\u00e1bh\u00e1il (m\u00e1 t\u00e1 an breise\u00e1n s\u00e1bh\u00e1la ar si\u00fal)", +"Find (if searchreplace plugin activated)": "Aimsigh (m\u00e1 t\u00e1 an breise\u00e1n cuardaigh ar si\u00fal)", +"Plugins installed ({0}):": "Breise\u00e1in shuite\u00e1ilte ({0}):", +"Premium plugins:": "Scothbhreise\u00e1in:", +"Learn more...": "Tuilleadh eolais...", +"You are using {0}": "T\u00e1 t\u00fa ag \u00fas\u00e1id {0}", +"Plugins": "Breise\u00e1in", +"Handy Shortcuts": "Aicearra\u00ed \u00das\u00e1ideacha", +"Horizontal line": "L\u00edne chothrom\u00e1nach", +"Insert\/edit image": "Cuir \u00edomh\u00e1 isteach\/in eagar", +"Image description": "Cur s\u00edos ar an \u00edomh\u00e1", +"Source": "Foinse", +"Dimensions": "Tois\u00ed", +"Constrain proportions": "Comhr\u00e9ir faoi ghlas", +"General": "Ginear\u00e1lta", +"Advanced": "Casta", +"Style": "St\u00edl", +"Vertical space": "Sp\u00e1s ingearach", +"Horizontal space": "Sp\u00e1s cothrom\u00e1nach", +"Border": "Iml\u00edne", +"Insert image": "Cuir \u00edomh\u00e1 isteach", +"Image": "\u00cdomh\u00e1", +"Image list": "Liosta \u00edomh\u00e1nna", +"Rotate counterclockwise": "Rothlaigh ar tuathal", +"Rotate clockwise": "Rothlaigh ar deiseal", +"Flip vertically": "Cas go hingearach", +"Flip horizontally": "Cas go cothrom\u00e1nach", +"Edit image": "Cuir an \u00edomh\u00e1 in eagar", +"Image options": "Roghanna \u00edomh\u00e1", +"Zoom in": "Z\u00fam\u00e1il isteach", +"Zoom out": "Z\u00fam\u00e1il amach", +"Crop": "Bear", +"Resize": "Athraigh m\u00e9id", +"Orientation": "Treoshu\u00edomh", +"Brightness": "Gile", +"Sharpen": "G\u00e9araigh", +"Contrast": "Codarsnacht", +"Color levels": "Leibh\u00e9il datha", +"Gamma": "G\u00e1ma", +"Invert": "Inbh\u00e9artaigh", +"Apply": "Cuir i bhfeidhm", +"Back": "Siar", +"Insert date\/time": "Cuir d\u00e1ta\/am isteach", +"Date\/time": "D\u00e1ta\/am", +"Insert link": "Cuir nasc isteach", +"Insert\/edit link": "Cuir nasc isteach\/in eagar", +"Text to display": "T\u00e9acs le taispe\u00e1int", +"Url": "URL", +"Target": "Sprioc", +"None": "Dada", +"New window": "Fuinneog nua", +"Remove link": "Bain an nasc", +"Anchors": "Ancair\u00ed", +"Link": "Nasc", +"Paste or type a link": "Greamaigh n\u00f3 cl\u00f3scr\u00edobh nasc", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "Is seoladh r\u00edomhphoist \u00e9 an URL a chuir t\u00fa isteach. An bhfuil fonn ort an r\u00e9im\u00edr riachtanach mailto: a chur leis?", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "Is nasc seachtrach \u00e9 an URL a chuir t\u00fa isteach. An bhfuil fonn ort an r\u00e9im\u00edr riachtanach http:\/\/ a chur leis?", +"Link list": "Liosta nascanna", +"Insert video": "Cuir f\u00edse\u00e1n isteach", +"Insert\/edit video": "Cuir f\u00edse\u00e1n isteach\/in eagar", +"Insert\/edit media": "Cuir me\u00e1n isteach\/in eagar", +"Alternative source": "Foinse mhalartach", +"Poster": "P\u00f3staer", +"Paste your embed code below:": "Greamaigh do ch\u00f3d leabaithe th\u00edos:", +"Embed": "Leabaigh", +"Media": "Me\u00e1in", +"Nonbreaking space": "Sp\u00e1s neamhbhristeach", +"Page break": "Briseadh leathanaigh", +"Paste as text": "Greamaigh mar th\u00e9acs", +"Preview": "R\u00e9amhamharc", +"Print": "Priont\u00e1il", +"Save": "S\u00e1bh\u00e1il", +"Find": "Aimsigh", +"Replace with": "Ionadaigh le", +"Replace": "Ionadaigh", +"Replace all": "Ionadaigh uile", +"Prev": "Siar", +"Next": "Ar aghaidh", +"Find and replace": "Aimsigh agus ionadaigh", +"Could not find the specified string.": "N\u00edor aims\u00edodh an teaghr\u00e1n.", +"Match case": "C\u00e1s-\u00edogair", +"Whole words": "Focail ioml\u00e1na", +"Spellcheck": "Seice\u00e1il an litri\u00fa", +"Ignore": "D\u00e9an neamhaird air", +"Ignore all": "D\u00e9an neamhaird orthu go l\u00e9ir", +"Finish": "Cr\u00edochnaigh", +"Add to Dictionary": "Cuir leis an bhFocl\u00f3ir \u00e9", +"Insert table": "Ions\u00e1igh t\u00e1bla", +"Table properties": "Air\u00edonna an t\u00e1bla", +"Delete table": "Scrios an t\u00e1bla", +"Cell": "Cill", +"Row": "R\u00f3", +"Column": "Col\u00fan", +"Cell properties": "Air\u00edonna na cille", +"Merge cells": "Cumaisc cealla", +"Split cell": "Roinn cill", +"Insert row before": "Ions\u00e1igh r\u00f3 os a chionn", +"Insert row after": "Ions\u00e1igh r\u00f3 faoi", +"Delete row": "Scrios an r\u00f3", +"Row properties": "Air\u00edonna an r\u00f3", +"Cut row": "Gearr an r\u00f3", +"Copy row": "C\u00f3ipe\u00e1il an r\u00f3", +"Paste row before": "Greamaigh r\u00f3 os a chionn", +"Paste row after": "Greamaigh r\u00f3 faoi", +"Insert column before": "Ions\u00e1igh col\u00fan ar chl\u00e9", +"Insert column after": "Ions\u00e1igh col\u00fan ar dheis", +"Delete column": "Scrios an col\u00fan", +"Cols": "Col\u00fain", +"Rows": "R\u00f3nna", +"Width": "Leithead", +"Height": "Airde", +"Cell spacing": "Sp\u00e1s\u00e1il ceall", +"Cell padding": "Stu\u00e1il ceall", +"Caption": "Fotheideal", +"Left": "Ar Chl\u00e9", +"Center": "Sa L\u00e1r", +"Right": "Ar Dheis", +"Cell type": "Cine\u00e1l na cille", +"Scope": "Sc\u00f3ip", +"Alignment": "Ail\u00edni\u00fa", +"H Align": "Ail\u00edni\u00fa C.", +"V Align": "Ail\u00edni\u00fa I.", +"Top": "Barr", +"Middle": "L\u00e1r", +"Bottom": "Bun", +"Header cell": "Cill cheannt\u00e1isc", +"Row group": "Gr\u00fapa r\u00f3nna", +"Column group": "Gr\u00fapa col\u00fan", +"Row type": "Cine\u00e1l an r\u00f3", +"Header": "Ceannt\u00e1sc", +"Body": "Corp", +"Footer": "Bunt\u00e1sc", +"Border color": "Dath na himl\u00edne", +"Insert template": "Ions\u00e1igh teimpl\u00e9ad", +"Templates": "Teimpl\u00e9id", +"Template": "Teimpl\u00e9ad", +"Text color": "Dath an t\u00e9acs", +"Background color": "Dath an ch\u00falra", +"Custom...": "Saincheap...", +"Custom color": "Dath saincheaptha", +"No color": "Gan dath", +"Table of Contents": "Cl\u00e1r na n\u00c1bhar", +"Show blocks": "Taispe\u00e1in blocanna", +"Show invisible characters": "Taispe\u00e1in carachtair dhofheicthe", +"Words: {0}": "Focail: {0}", +"{0} words": "{0} focal", +"File": "Comhad", +"Edit": "Eagar", +"Insert": "Ions\u00e1ig", +"View": "Amharc", +"Format": "Form\u00e1id", +"Table": "T\u00e1bla", +"Tools": "Uirlis\u00ed", +"Powered by {0}": "\u00c1 chumhacht\u00fa ag {0}", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Limist\u00e9ar M\u00e9ith-Th\u00e9acs. Br\u00faigh ALT-F9 le haghaidh roghchl\u00e1ir, ALT-F10 le haghaidh barra uirlis\u00ed, agus ALT-0 le c\u00fanamh a fh\u00e1il" +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/gl.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/gl.js new file mode 100644 index 0000000000..43c1900da7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/gl.js @@ -0,0 +1,253 @@ +tinymce.addI18n('gl',{ +"Redo": "Refacer", +"Undo": "Desfacer", +"Cut": "Cortar", +"Copy": "Copiar", +"Paste": "Pegar", +"Select all": "Seleccionar todo", +"New document": "Novo documento", +"Ok": "Aceptar", +"Cancel": "Cancelar", +"Visual aids": "Axudas visuais", +"Bold": "Negra", +"Italic": "Cursiva", +"Underline": "Subli\u00f1ado", +"Strikethrough": "Riscado", +"Superscript": "Super\u00edndice", +"Subscript": "Sub\u00edndice", +"Clear formatting": "Limpar o formato", +"Align left": "Ali\u00f1ar \u00e1 esquerda", +"Align center": "Ali\u00f1ar ao centro", +"Align right": "Ali\u00f1ar \u00e1 dereita", +"Justify": "Xustificar", +"Bullet list": "Lista de vi\u00f1etas", +"Numbered list": "Lista numerada", +"Decrease indent": "Reducir a sangr\u00eda", +"Increase indent": "Aumentar a sangr\u00eda", +"Close": "Pechar", +"Formats": "Formatos", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "O seu navegador non admite o acceso directo ao portapapeis. Empregue os atallos de teclado Ctrl+X\/C\/V no seu canto.", +"Headers": "Cabeceiras", +"Header 1": "Cabeceira 1", +"Header 2": "Cabeceira 2", +"Header 3": "Cabeceira 3", +"Header 4": "Cabeceira 4", +"Header 5": "Cabeceira 5", +"Header 6": "Cabeceira 6", +"Headings": "T\u00edtulo", +"Heading 1": "T\u00edtulo 1", +"Heading 2": "T\u00edtulo 2", +"Heading 3": "T\u00edtulo 3", +"Heading 4": "T\u00edtulo 4", +"Heading 5": "T\u00edtulo 5", +"Heading 6": "T\u00edtulo 6", +"Div": "Div", +"Pre": "Pre", +"Code": "C\u00f3digo", +"Paragraph": "Par\u00e1grafo", +"Blockquote": "Bloque entre comi\u00f1as", +"Inline": "En li\u00f1a", +"Blocks": "Bloques", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "Neste momento o pegado est\u00e1 definido en modo de texto simple. Os contidos p\u00e9garanse como texto sen formato ata que se active esta opci\u00f3n.", +"Font Family": "Tipo de letra", +"Font Sizes": "Tama\u00f1o da letra", +"Class": "Clase", +"Browse for an image": "Buscar unha imaxe", +"OR": "OU", +"Drop an image here": "Soltar unha imaxe", +"Upload": "Cargar", +"Default": "Predeterminada", +"Circle": "Circulo", +"Disc": "Disco", +"Square": "Cadrado", +"Lower Alpha": "Alfa min\u00fascula", +"Lower Greek": "Grega min\u00fascula", +"Lower Roman": "Romana min\u00fascula", +"Upper Alpha": "Alfa mai\u00fascula", +"Upper Roman": "Romana mai\u00fascula", +"Anchor": "Ancoraxe", +"Name": "Nome", +"Id": "ID", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "O ID debe comezar cunha letra, seguida s\u00f3 por letras, n\u00fameros, gui\u00f3ns, puntos, dos puntos ou gui\u00f3ns baixos.", +"You have unsaved changes are you sure you want to navigate away?": "Ten cambios sen gardar. Confirma que quere sa\u00edr?", +"Restore last draft": "Restaurar o \u00faltimo borrador", +"Special character": "Car\u00e1cter especial", +"Source code": "C\u00f3digo fonte", +"Insert\/Edit code sample": "Inserir\/editar mostra de c\u00f3digo", +"Language": "Idioma", +"Color": "Cor", +"R": "R", +"G": "G", +"B": "B", +"Left to right": "De esquerda a dereita", +"Right to left": "De dereita a esquerda", +"Emoticons": "Emoticonas", +"Document properties": "Propiedades do documento", +"Title": "T\u00edtulo", +"Keywords": "Palabras clave", +"Description": "Descrici\u00f3n", +"Robots": "Robots", +"Author": "Autor", +"Encoding": "Codificaci\u00f3n", +"Fullscreen": "Pantalla completa", +"Action": "Action", +"Shortcut": "Shortcut", +"Help": "Help", +"Address": "Address", +"Focus to menubar": "Focus to menubar", +"Focus to toolbar": "Focus to toolbar", +"Focus to element path": "Focus to element path", +"Focus to contextual toolbar": "Focus to contextual toolbar", +"Insert link (if link plugin activated)": "Insert link (if link plugin activated)", +"Save (if save plugin activated)": "Save (if save plugin activated)", +"Find (if searchreplace plugin activated)": "Find (if searchreplace plugin activated)", +"Plugins installed ({0}):": "Plugins installed ({0}):", +"Premium plugins:": "Premium plugins:", +"Learn more...": "Learn more...", +"You are using {0}": "You are using {0}", +"Horizontal line": "Li\u00f1a horizontal", +"Insert\/edit image": "Inserir\/editar imaxe", +"Image description": "Descrici\u00f3n da imaxe", +"Source": "Orixe", +"Dimensions": "Dimensi\u00f3ns", +"Constrain proportions": "Restrinxir as proporci\u00f3ns", +"General": "Xeral", +"Advanced": "Avanzado", +"Style": "Estilo", +"Vertical space": "Espazo vertical", +"Horizontal space": "Espazo horizontal", +"Border": "Bordo", +"Insert image": "Inserir imaxe", +"Image": "Imaxe", +"Image list": "Lista de imaxes", +"Rotate counterclockwise": "Rotate counterclockwise", +"Rotate clockwise": "Rotate clockwise", +"Flip vertically": "Flip vertically", +"Flip horizontally": "Flip horizontally", +"Edit image": "Edit image", +"Image options": "Image options", +"Zoom in": "Zoom in", +"Zoom out": "Zoom out", +"Crop": "Crop", +"Resize": "Resize", +"Orientation": "Orientation", +"Brightness": "Brightness", +"Sharpen": "Sharpen", +"Contrast": "Contrast", +"Color levels": "Color levels", +"Gamma": "Gamma", +"Invert": "Invert", +"Apply": "Apply", +"Back": "Back", +"Insert date\/time": "Inserir data\/hora", +"Date\/time": "Data\/hora", +"Insert link": "Inserir ligaz\u00f3n", +"Insert\/edit link": "Inserir\/editar ligaz\u00f3n", +"Text to display": "Texto que amosar", +"Url": "URL", +"Target": "Destino", +"None": "Ning\u00fan", +"New window": "Nova xanela", +"Remove link": "Retirar a ligaz\u00f3n", +"Anchors": "Ancoraxes", +"Link": "Ligaz\u00f3n", +"Paste or type a link": "Pegue ou escriba unha ligaz\u00f3n", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "O URL que introduciu semella seren un enderezo de correo. Quere engadirlle o prefixo mailto: requirido?", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "O URL que introduciu semella seren unha ligaz\u00f3n externa. Quere engadirlle o prefixo http:\/\/ requirido?", +"Link list": "Lista de ligaz\u00f3ns", +"Insert video": "Inserir v\u00eddeo", +"Insert\/edit video": "Inserir\/editar v\u00eddeo", +"Insert\/edit media": "Inserir\/editar medios", +"Alternative source": "Orixe alternativa", +"Poster": "Cartel", +"Paste your embed code below:": "Pegue embaixo o c\u00f3digo integrado:", +"Embed": "Integrado", +"Media": "Medios", +"Nonbreaking space": "Espazo irromp\u00edbel", +"Page break": "Quebra de p\u00e1xina", +"Paste as text": "Pegar como texto", +"Preview": "Vista previa", +"Print": "Imprimir", +"Save": "Gardar", +"Find": "Buscar", +"Replace with": "Substitu\u00edr con", +"Replace": "Substitu\u00edr", +"Replace all": "Substitu\u00edr todo", +"Prev": "Anterior", +"Next": "Seguinte", +"Find and replace": "Buscar e substitu\u00edr", +"Could not find the specified string.": "Non foi pos\u00edbel atopar a cadea de texto especificada.", +"Match case": "Distinguir mai\u00fasculas", +"Whole words": "Palabras completas", +"Spellcheck": "Corrector ortogr\u00e1fico", +"Ignore": "Ignorar", +"Ignore all": "Ignorar todo", +"Finish": "Rematar", +"Add to Dictionary": "Engadir ao dicionario", +"Insert table": "Inserir t\u00e1boa", +"Table properties": "Propiedades da t\u00e1boa", +"Delete table": "Eliminar t\u00e1boa", +"Cell": "Cela", +"Row": "Fila", +"Column": "Columna", +"Cell properties": "Propiedades da cela", +"Merge cells": "Combinar celas", +"Split cell": "Dividir celas", +"Insert row before": "Inserir unha fila enriba", +"Insert row after": "Inserir unha fila embaixo", +"Delete row": "Eliminar fila", +"Row properties": "Propiedades das filas", +"Cut row": "Cortar fila", +"Copy row": "Copiar fila", +"Paste row before": "Pegar fila embaixo", +"Paste row after": "Pegar fila enriba", +"Insert column before": "Inserir columna \u00e1 esquerda", +"Insert column after": "Inserir columna \u00e1 dereita", +"Delete column": "Eliminar columna", +"Cols": "Cols.", +"Rows": "Filas", +"Width": "Largo", +"Height": "Alto", +"Cell spacing": "Marxe entre celas", +"Cell padding": "Marxe interior da cela", +"Caption": "Subt\u00edtulo", +"Left": "Esquerda", +"Center": "Centro", +"Right": "Dereita", +"Cell type": "Tipo de cela", +"Scope": "\u00c1mbito", +"Alignment": "Ali\u00f1amento", +"H Align": "Ali\u00f1amento H", +"V Align": "Ali\u00f1amento V", +"Top": "Arriba", +"Middle": "Medio", +"Bottom": "Abaixo", +"Header cell": "Cela de cabeceira", +"Row group": "Grupo de filas", +"Column group": "Grupo de columnas", +"Row type": "Tipo de fila", +"Header": "Cabeceira", +"Body": "Corpo", +"Footer": "Rodap\u00e9", +"Border color": "Cor do bordo", +"Insert template": "Inserir modelo", +"Templates": "Modelos", +"Template": "Modelo", +"Text color": "Cor do texto", +"Background color": "Cor do fondo", +"Custom...": "Personalizado...", +"Custom color": "Cor personalizado", +"No color": "Sen cor", +"Table of Contents": "\u00cdndice de contidos", +"Show blocks": "Amosar os bloques", +"Show invisible characters": "Amosar caracteres invis\u00edbeis", +"Words: {0}": "Palabras: {0}", +"File": "Ficheiro", +"Edit": "Editar", +"Insert": "Inserir", +"View": "Ver", +"Format": "Formato", +"Table": "T\u00e1boa", +"Tools": "Ferramentas", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "\u00c1rea de texto mellorado. Prema ALT-F9 para o men\u00fa. Prema ALT-F10 para a barra de ferramentas. Prema ALT-0 para a axuda" +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/he.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/he.js deleted file mode 100644 index 0f4e12d7b5..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/he.js +++ /dev/null @@ -1 +0,0 @@ -tinyMCE.addI18n({he:{common:{"more_colors":"\u05e2\u05d5\u05d3 \u05e6\u05d1\u05e2\u05d9\u05dd","invalid_data":"\u05e9\u05d2\u05d9\u05d0\u05d4: \u05d4\u05d5\u05e7\u05dc\u05d3 \u05de\u05d9\u05d3\u05e2 \u05dc\u05d0 \u05ea\u05e7\u05e0\u05d9. \u05d4\u05de\u05d9\u05d3\u05e2 \u05e1\u05d5\u05de\u05df \u05d1\u05d0\u05d3\u05d5\u05dd.","popup_blocked":"\u05d7\u05d5\u05e1\u05dd \u05e4\u05e8\u05d9\u05d8\u05d9\u05dd \u05de\u05d5\u05e7\u05e4\u05e6\u05d9\u05dd \u05de\u05e0\u05e2 \u05de\u05d7\u05dc\u05d5\u05df \u05d7\u05e9\u05d5\u05d1 \u05de\u05dc\u05d4\u05e4\u05ea\u05d7,\u05d0\u05dd \u05d1\u05e8\u05e6\u05d5\u05e0\u05da \u05dc\u05d4\u05e9\u05ea\u05de\u05e9 \u05d1\u05db\u05dc\u05d9 \u05d6\u05d4 \u05e2\u05dc\u05d9\u05da \u05dc\u05d1\u05d8\u05dc \u05d0\u05ea \u05d7\u05d5\u05e1\u05dd \u05d4\u05e4\u05e8\u05d9\u05d8\u05d9\u05dd","clipboard_no_support":"\u05db\u05e8\u05d2\u05e2 \u05dc\u05d0 \u05e0\u05ea\u05de\u05da \u05e2\u05dc \u05d9\u05d3\u05d9 \u05d4\u05d3\u05e4\u05d3\u05e4\u05df \u05e9\u05dc\u05da. \u05d4\u05e9\u05ea\u05de\u05e9 \u05d1\u05e7\u05d9\u05e6\u05d5\u05e8\u05d9 \u05d4\u05de\u05e7\u05dc\u05d3\u05ea.","clipboard_msg":"\n \u05d4\u05e2\u05ea\u05e7\u05d4/\u05d2\u05d6\u05d9\u05e8\u05d4 \u05d5\u05d4\u05d3\u05d1\u05e7\u05d4 \u05d0\u05d9\u05e0\u05dd \u05d6\u05de\u05d9\u05e0\u05d9\u05dd \u05d1 Mozilla \u05d5\u05d1-Firefox.\n \u05d4\u05d0\u05dd \u05d1\u05e8\u05e6\u05d5\u05e0\u05da \u05dc\u05e7\u05d1\u05dc \u05de\u05d9\u05d3\u05e2 \u05e0\u05d5\u05e1\u05e3 \u05e2\u05dc \u05d4\u05e0\u05d5\u05e9\u05d0?\n ","not_set":"-- \u05dc\u05d0 \u05d4\u05d5\u05d2\u05d3\u05e8 --","class_name":"\u05de\u05d7\u05dc\u05e7\u05d4",browse:"\u05e2\u05d9\u05d5\u05df",close:"\u05e1\u05d2\u05d9\u05e8\u05d4",cancel:"\u05d1\u05d9\u05d8\u05d5\u05dc",update:"\u05e2\u05d3\u05db\u05d5\u05df",insert:"\u05d4\u05d5\u05e1\u05e4\u05d4",apply:"\u05d0\u05d9\u05e9\u05d5\u05e8","edit_confirm":"\u05d1\u05e8\u05e6\u05d5\u05e0\u05da \u05dc\u05d4\u05e9\u05ea\u05de\u05e9 \u05d1\u05e2\u05d5\u05e8\u05da \u05d4\u05de\u05ea\u05e7\u05d3\u05dd?","invalid_data_number":"{#field} \u05d7\u05d9\u05d9\u05d1 \u05dc\u05d4\u05d9\u05d5\u05ea \u05de\u05e1\u05e4\u05e8","invalid_data_min":"{#field} \u05d4\u05de\u05e1\u05e4\u05e8 \u05d7\u05d9\u05d9\u05d1 \u05dc\u05d4\u05d9\u05d5\u05ea \u05d2\u05d3\u05d5\u05dc \u05de-{#min}","invalid_data_size":"{#field} \u05d4\u05e2\u05e8\u05da \u05d7\u05d9\u05d9\u05d1 \u05dc\u05d4\u05d9\u05d5\u05ea \u05de\u05e1\u05e4\u05e8 \u05d0\u05d5 \u05d0\u05d7\u05d5\u05d6",value:"(\u05e2\u05e8\u05da)"},contextmenu:{full:"\u05e9\u05e0\u05d9 \u05d4\u05e6\u05d3\u05d3\u05d9\u05dd",right:"\u05d9\u05de\u05d9\u05df",center:"\u05d0\u05de\u05e6\u05e2",left:"\u05e9\u05de\u05d0\u05dc",align:"\u05d9\u05d9\u05e9\u05d5\u05e8"},insertdatetime:{"day_short":"\u05d9\u05d5\u05dd \u05d0\',\u05d9\u05d5\u05dd \u05d1\',\u05d9\u05d5\u05dd \u05d2\',\u05d9\u05d5\u05dd \u05d3\',\u05d9\u05d5\u05dd \u05d4\',\u05d9\u05d5\u05dd \u05d5\',\u05e9\u05d1\u05ea,\u05d9\u05d5\u05dd \u05d0\'","day_long":"\u05d9\u05d5\u05dd \u05e8\u05d0\u05e9\u05d5\u05df,\u05d9\u05d5\u05dd \u05e9\u05e0\u05d9,\u05d9\u05d5\u05dd \u05e9\u05dc\u05d9\u05e9\u05d9,\u05d9\u05d5\u05dd \u05e8\u05d1\u05d9\u05e2\u05d9,\u05d9\u05d5\u05dd \u05d7\u05de\u05d9\u05e9\u05d9,\u05d9\u05d5\u05dd \u05e9\u05d9\u05e9,\u05d9\u05d5\u05dd \u05e9\u05d1\u05ea,\u05d9\u05d5\u05dd \u05e8\u05d0\u05e9\u05d5\u05df","months_short":"\u05d9\u05e0\u05d5\u05d0\u05e8,\u05e4\u05d1\u05e8\u05d5\u05d0\u05e8,\u05de\u05e8\u05e5,\u05d0\u05e4\u05e8\u05d9\u05dc,\u05de\u05d0\u05d9,\u05d9\u05d5\u05e0\u05e2,\u05d9\u05d5\u05dc\u05d9,\u05d0\u05d5\u05d2\u05d5\u05e1\u05d8,\u05e1\u05e4\u05d8\u05de\u05d1\u05e8,\u05d0\u05d5\u05e7\u05d8\u05d5\u05d1\u05e8,\u05e0\u05d5\u05d1\u05de\u05d1\u05e8,\u05d3\u05e6\u05de\u05d1\u05e8","months_long":"\u05d9\u05e0\u05d5\u05d0\u05e8,\u05e4\u05d1\u05e8\u05d5\u05d0\u05e8,\u05de\u05e8\u05e5,\u05d0\u05e4\u05e8\u05d9\u05dc,\u05de\u05d0\u05d9,\u05d9\u05d5\u05e0\u05e2,\u05d9\u05d5\u05dc\u05d9,\u05d0\u05d5\u05d2\u05d5\u05e1\u05d8,\u05e1\u05e4\u05d8\u05de\u05d1\u05e8,\u05d0\u05d5\u05e7\u05d8\u05d5\u05d1\u05e8,\u05e0\u05d5\u05d1\u05de\u05d1\u05e8,\u05d3\u05e6\u05de\u05d1\u05e8","inserttime_desc":"\u05d4\u05d5\u05e1\u05e4\u05ea \u05d6\u05de\u05df","insertdate_desc":"\u05d4\u05d5\u05e1\u05e4\u05ea \u05ea\u05d0\u05e8\u05d9\u05da","time_fmt":"%H:%M:%S","date_fmt":"%d-%m-%Y"},print:{"print_desc":"\u05d4\u05d3\u05e4\u05e1\u05d4"},preview:{"preview_desc":"\u05ea\u05e6\u05d5\u05d2\u05d4 \u05de\u05e7\u05d3\u05d9\u05de\u05d4"},directionality:{"rtl_desc":"\u05db\u05d9\u05d5\u05d5\u05df \u05d8\u05e7\u05e1\u05d8 \u05de\u05d9\u05de\u05d9\u05df \u05dc\u05e9\u05de\u05d0\u05dc","ltr_desc":"\u05db\u05d9\u05d5\u05d5\u05df \u05d8\u05e7\u05e1\u05d8 \u05de\u05e9\u05de\u05d0\u05dc \u05dc\u05d9\u05de\u05d9\u05df"},layer:{content:"\u05e9\u05db\u05d1\u05d4 \u05d7\u05d3\u05e9\u05d4...","absolute_desc":"\u05d1\u05d7\u05d9\u05e8\u05ea \u05de\u05d9\u05e7\u05d5\u05dd \u05de\u05d5\u05d7\u05dc\u05d8","backward_desc":"\u05d4\u05e2\u05d1\u05e8\u05d4 \u05d0\u05d7\u05d5\u05e8\u05d4","forward_desc":"\u05d4\u05e2\u05d1\u05e8\u05d4 \u05e7\u05d3\u05d9\u05de\u05d4","insertlayer_desc":"\u05d4\u05d5\u05e1\u05e4\u05ea \u05e9\u05db\u05d1\u05d4 \u05d7\u05d3\u05e9\u05d4"},save:{"save_desc":"\u05e9\u05de\u05d9\u05e8\u05d4","cancel_desc":"\u05d1\u05d9\u05d8\u05d5\u05dc \u05db\u05dc \u05d4\u05e9\u05d9\u05e0\u05d5\u05d9\u05dd"},nonbreaking:{"nonbreaking_desc":"\u05d4\u05d5\u05e1\u05e4\u05ea \u05e8\u05d5\u05d5\u05d7"},iespell:{download:" \u05dc\u05d0 \u05e0\u05de\u05e6\u05d0 ieSpell. \u05d4\u05d0\u05dd \u05d1\u05e8\u05e6\u05d5\u05e0\u05da \u05dc\u05d4\u05ea\u05e7\u05d9\u05df?","iespell_desc":"\u05d1\u05d3\u05d9\u05e7\u05ea \u05d0\u05d9\u05d5\u05ea \u05d1\u05d0\u05e0\u05d2\u05dc\u05d9\u05ea"},advhr:{"advhr_desc":"\u05e7\u05d5 \u05d0\u05d5\u05e4\u05e7\u05d9","delta_height":"","delta_width":""},emotions:{"emotions_desc":"\u05e1\u05de\u05d9\u05d9\u05dc\u05d9\u05dd","delta_height":"","delta_width":""},searchreplace:{"replace_desc":"\u05d4\u05d7\u05dc\u05e4\u05d4","search_desc":"\u05d7\u05d9\u05e4\u05d5\u05e9","delta_width":"","delta_height":""},advimage:{"image_desc":"\u05d4\u05d5\u05e1\u05e4\u05d4/\u05e2\u05e8\u05d9\u05db\u05ea \u05ea\u05de\u05d5\u05e0\u05d4","delta_width":"","delta_height":""},advlink:{"link_desc":"\u05d4\u05d5\u05e1\u05e4\u05ea/\u05e2\u05e8\u05d9\u05db\u05ea \u05e7\u05d9\u05e9\u05d5\u05e8","delta_height":"","delta_width":""},xhtmlxtras:{"attribs_desc":"\u05d4\u05db\u05e0\u05e1/\u05e2\u05e8\u05d5\u05da \u05ea\u05db\u05d5\u05e0\u05d5\u05ea","ins_desc":"\u05d4\u05db\u05e0\u05e1\u05d4","del_desc":"\u05de\u05d7\u05d9\u05e7\u05d4","acronym_desc":"\u05e8\u05d0\u05e9\u05d9 \u05ea\u05d9\u05d1\u05d5\u05ea","abbr_desc":"\u05e7\u05d9\u05e6\u05d5\u05e8","cite_desc":"\u05e6\u05d9\u05d8\u05d5\u05d8","attribs_delta_height":"","attribs_delta_width":"","ins_delta_height":"","ins_delta_width":"","del_delta_height":"","del_delta_width":"","acronym_delta_height":"","acronym_delta_width":"","abbr_delta_height":"","abbr_delta_width":"","cite_delta_height":"","cite_delta_width":""},style:{desc:"\u05e2\u05d3\u05db\u05d5\u05df \u05d4\u05d2\u05d3\u05e8\u05d5\u05ea CSS","delta_height":"","delta_width":""},paste:{"plaintext_mode":"Paste is now in plain text mode. Click again to toggle back to regular paste mode.","plaintext_mode_sticky":"Paste is now in plain text mode. Click again to toggle back to regular paste mode. After you paste something you will be returned to regular paste mode.","selectall_desc":"\u05d1\u05d7\u05e8 \u05d4\u05db\u05dc","paste_word_desc":"\u05d4\u05d3\u05d1\u05e7\u05d4 \u05de-WORD","paste_text_desc":"\u05d4\u05d3\u05d1\u05e7\u05d4 \u05db\u05d8\u05e7\u05e1\u05d8 \u05d1\u05dc\u05d1\u05d3"},"paste_dlg":{"word_title":"\u05d4\u05d3\u05d1\u05d9\u05e7\u05d5 \u05d1\u05d7\u05dc\u05d5\u05df \u05d6\u05d4 \u05d0\u05ea \u05d4\u05d8\u05e7\u05e1\u05d8 \u05d1\u05d0\u05de\u05e6\u05e2\u05d5\u05ea \u05d4\u05de\u05e7\u05e9\u05d9\u05dd CTRL+V.","text_linebreaks":"\u05d4\u05e9\u05d0\u05e8 \u05d0\u05ea \u05e9\u05d5\u05e8\u05d5\u05ea \u05d4\u05e8\u05d5\u05d5\u05d7","text_title":"\u05d4\u05d3\u05d1\u05d9\u05e7\u05d5 \u05d1\u05d7\u05dc\u05d5\u05df \u05d6\u05d4 \u05d0\u05ea \u05d4\u05d8\u05e7\u05e1\u05d8 \u05d1\u05d0\u05de\u05e6\u05e2\u05d5\u05ea \u05d4\u05de\u05e7\u05e9\u05d9\u05dd CTRL+V."},table:{cell:"\u05ea\u05d0",col:"\u05e2\u05de\u05d5\u05d3\u05d4",row:"\u05e9\u05d5\u05e8\u05d4",del:"\u05de\u05d7\u05d9\u05e7\u05ea \u05d8\u05d1\u05dc\u05d4","copy_row_desc":"\u05d4\u05e2\u05ea\u05e7\u05ea \u05e9\u05d5\u05e8\u05d4 \u05d1\u05d8\u05d1\u05dc\u05d4","cut_row_desc":"\u05d2\u05d6\u05d9\u05e8\u05ea \u05e9\u05d5\u05e8\u05d4 \u05d1\u05d8\u05d1\u05dc\u05d4","paste_row_after_desc":"\u05d4\u05d3\u05d1\u05e7\u05ea \u05e9\u05d5\u05e8\u05d4 \u05d1\u05d8\u05d1\u05dc\u05d4 \u05d0\u05d7\u05e8\u05d9","paste_row_before_desc":"\u05d4\u05d3\u05d1\u05e7\u05ea \u05e9\u05d5\u05e8\u05d4 \u05d1\u05d8\u05d1\u05dc\u05d4 \u05dc\u05e4\u05e0\u05d9","props_desc":"\u05ea\u05db\u05d5\u05e0\u05d5\u05ea \u05d4\u05d8\u05d1\u05dc\u05d4","cell_desc":"\u05ea\u05db\u05d5\u05e0\u05d5\u05ea \u05ea\u05d0 \u05d1\u05d8\u05d1\u05dc\u05d4","row_desc":"\u05ea\u05db\u05d5\u05e0\u05d5\u05ea \u05e9\u05d5\u05e8\u05d4 \u05d1\u05d8\u05d1\u05dc\u05d4","merge_cells_desc":"\u05d0\u05d9\u05d7\u05d5\u05d3 \u05ea\u05d0\u05d9\u05dd \u05d1\u05d8\u05d1\u05dc\u05d4","split_cells_desc":"\u05e4\u05d9\u05e6\u05d5\u05dc \u05ea\u05d0\u05d9\u05dd \u05d1\u05d8\u05d1\u05dc\u05d4","delete_col_desc":"\u05d4\u05e1\u05e8\u05ea \u05e2\u05de\u05d5\u05d3\u05d4","col_after_desc":"\u05d4\u05db\u05e0\u05e1\u05ea \u05e2\u05de\u05d5\u05d3\u05d4 \u05de\u05e9\u05de\u05d0\u05dc","col_before_desc":"\u05d4\u05db\u05e0\u05e1\u05ea \u05e2\u05de\u05d5\u05d3\u05d4 \u05de\u05d9\u05de\u05d9\u05df","delete_row_desc":"\u05de\u05d7\u05d9\u05e7\u05ea \u05e9\u05d5\u05e8\u05d4","row_after_desc":"\u05d4\u05db\u05e0\u05e1\u05ea \u05e9\u05d5\u05e8\u05d4 \u05de\u05ea\u05d7\u05ea","row_before_desc":"\u05d4\u05db\u05e0\u05e1\u05ea \u05e9\u05d5\u05e8\u05d4 \u05de\u05e2\u05dc",desc:"\u05d4\u05db\u05e0\u05e1\u05ea \u05d0\u05d5 \u05e2\u05e8\u05d9\u05db\u05ea \u05d8\u05d1\u05dc\u05d4","merge_cells_delta_height":"","merge_cells_delta_width":"","table_delta_height":"","table_delta_width":"","cellprops_delta_height":"","cellprops_delta_width":"","rowprops_delta_height":"","rowprops_delta_width":""},autosave:{"warning_message":"\u05d0\u05dd \u05ea\u05e9\u05d7\u05d6\u05e8 \u05d0\u05ea \u05d4\u05ea\u05d5\u05db\u05df \u05dc\u05d2\u05e8\u05d9\u05e1\u05d0 \u05d4\u05e9\u05de\u05d5\u05e8\u05d4, \u05ea\u05d0\u05d1\u05d3 \u05d0\u05ea \u05db\u05dc \u05d4\u05ea\u05d5\u05db\u05df \u05e9\u05e0\u05de\u05e6\u05d0 \u05db\u05e2\u05ea \u05d1\u05e2\u05d5\u05e8\u05da. \u05d4\u05d0\u05dd \u05d0\u05ea\u05d4 \u05d1\u05d8\u05d5\u05d7 \u05e9\u05d0\u05ea\u05d4 \u05e8\u05d5\u05e6\u05d4 \u05dc\u05e9\u05d7\u05d6\u05e8 \u05d0\u05ea \u05d4\u05ea\u05d5\u05db\u05df \u05dc\u05d2\u05d9\u05e8\u05e1\u05d0 \u05d4\u05e9\u05de\u05d5\u05e8\u05d4?.","restore_content":"\u05e9\u05d7\u05d6\u05d5\u05e8 \u05dc\u05d2\u05d9\u05e8\u05e1\u05d0 \u05e9\u05de\u05d5\u05e8\u05d4 \u05d0\u05d5\u05d8\u05d5\u05de\u05d8\u05d9\u05ea","unload_msg":"\u05d4\u05e9\u05d9\u05e0\u05d5\u05d9\u05d9\u05dd \u05e9\u05d1\u05d9\u05e6\u05e2\u05ea \u05dc\u05d0 \u05d9\u05e9\u05de\u05e8\u05d5 \u05d0\u05dd \u05ea\u05e2\u05d1\u05d5\u05e8 \u05de\u05d3\u05e3 \u05d6\u05d4"},fullscreen:{desc:"\u05de\u05e2\u05d1\u05e8 \u05dc\u05de\u05e1\u05da \u05de\u05dc\u05d0/\u05d7\u05dc\u05e7\u05d9"},media:{edit:"\u05e2\u05e8\u05d9\u05db\u05ea \u05e1\u05e8\u05d8\u05d5\u05df",desc:"\u05d4\u05d5\u05e1\u05e4\u05ea/\u05e2\u05e8\u05d9\u05db\u05ea \u05e1\u05e8\u05d8\u05d5\u05df","delta_height":"","delta_width":""},fullpage:{desc:"\u05de\u05d0\u05e4\u05d9\u05d9\u05e0\u05d9 \u05e2\u05de\u05d5\u05d3","delta_width":"","delta_height":""},template:{desc:"Insert predefined template content"},visualchars:{desc:"\u05d4\u05e6\u05d2/\u05d4\u05e1\u05ea\u05e8 \u05ea\u05d5\u05d5\u05d9 \u05d1\u05e7\u05e8\u05d4"},spellchecker:{desc:"\u05d4\u05e4\u05e2\u05dc\u05ea \u05d1\u05d5\u05d3\u05e7 \u05d0\u05d9\u05d5\u05ea",menu:"\u05d4\u05d2\u05d3\u05e8\u05d5\u05ea \u05d1\u05d5\u05d3\u05e7 \u05d0\u05d9\u05d5\u05ea","ignore_word":"\u05dc\u05d4\u05ea\u05e2\u05dc\u05dd \u05de\u05d4\u05de\u05d9\u05dc\u05d4","ignore_words":"\u05dc\u05d4\u05ea\u05e2\u05dc\u05dd \u05de\u05d4\u05db\u05dc",langs:"\u05e9\u05e4\u05d5\u05ea",wait:"\u05e0\u05d0 \u05dc\u05d4\u05de\u05ea\u05d9\u05df..",sug:"\u05d4\u05e6\u05e2\u05d5\u05ea","no_sug":"\u05d0\u05d9\u05df \u05d4\u05e6\u05e2\u05d5\u05ea","no_mpell":"\u05dc\u05d0 \u05e0\u05de\u05e6\u05d0\u05d5 \u05e9\u05d2\u05d9\u05d0\u05d5\u05ea \u05d0\u05d9\u05d5\u05ea","learn_word":"\u05dc\u05de\u05d3 \u05de\u05d9\u05dc\u05d9\u05dd"},pagebreak:{desc:"\u05d4\u05d5\u05e1\u05e4\u05ea \u05de\u05e2\u05d1\u05e8 \u05d3\u05e3"},advlist:{types:"\u05e1\u05d5\u05d2\u05d9\u05dd",def:"\u05d1\u05e8\u05d9\u05e8\u05ea \u05de\u05d7\u05d3\u05dc","lower_alpha":"Lower alpha","lower_greek":"Lower greek","lower_roman":"Lower roman","upper_alpha":"Upper alpha","upper_roman":"Upper roman",circle:"\u05e2\u05d2\u05d5\u05dc",disc:"\u05d3\u05d9\u05e1\u05e7",square:"\u05de\u05e8\u05d5\u05d1\u05e2"},colors:{"333300":"\u05d6\u05d9\u05ea \u05db\u05d4\u05d4","993300":"\u05db\u05ea\u05d5\u05dd \u05db\u05d4\u05d4","000000":"\u05e9\u05d7\u05d5\u05e8","003300":"\u05d9\u05e8\u05d5\u05e7 \u05db\u05d4\u05d4","003366":"\u05d8\u05d5\u05e8\u05e7\u05d9\u05d6 \u05db\u05d4\u05d4","000080":"\u05db\u05d7\u05d5\u05dc \u05e6\u05d9","333399":"\u05d0\u05d9\u05e0\u05d3\u05d9\u05d2\u05d5","333333":"\u05d0\u05e4\u05d5\u05e8 \u05db\u05d4\u05d4 \u05de\u05d0\u05d5\u05d3","800000":"\u05e2\u05e8\u05de\u05d5\u05e0\u05d9",FF6600:"\u05db\u05ea\u05d5\u05dd","808000":"\u05d6\u05d9\u05ea","008000":"\u05d9\u05e8\u05d5\u05e7","008080":"\u05d9\u05e8\u05d5\u05e7-\u05db\u05d7\u05d5\u05dc \u05e2\u05de\u05d5\u05e7","0000FF":"\u05db\u05d7\u05d5\u05dc","666699":"\u05db\u05d7\u05d5\u05dc \u05d0\u05e4\u05e8\u05e4\u05e8","808080":"\u05d0\u05e4\u05d5\u05e8",FF0000:"\u05d0\u05d3\u05d5\u05dd",FF9900:"\u05e2\u05e0\u05d1\u05e8","99CC00":"\u05d9\u05e8\u05d5\u05e7 \u05e6\u05d4\u05d1\u05d4\u05d1","339966":"\u05d9\u05e8\u05d5\u05e7 \u05d9\u05dd","33CCCC":"\u05d8\u05d5\u05e8\u05e7\u05d9\u05d6","3366FF":"\u05db\u05d7\u05d5\u05dc \u05e8\u05d5\u05d9\u05d0\u05dc","800080":"\u05e1\u05d2\u05d5\u05dc","999999":"\u05d0\u05e4\u05d5\u05e8 \u05d1\u05d9\u05e0\u05d9\u05d9\u05dd",FF00FF:"\u05e1\u05d2\u05d5\u05dc-\u05d5\u05e8\u05d5\u05d3 (\u05de\u05d2\u05f3\u05e0\u05d8\u05d4)",FFCC00:"\u05d6\u05d4\u05d1",FFFF00:"\u05e6\u05d4\u05d5\u05d1","00FF00":"\u05dc\u05d9\u05d9\u05dd","00FFFF":"\u05d8\u05d5\u05e8\u05e7\u05d9\u05d6 \u05de\u05d9\u05dd","00CCFF":"\u05ea\u05db\u05dc\u05ea","993366":"\u05d7\u05d5\u05dd",C0C0C0:"\u05db\u05e1\u05e3",FF99CC:"\u05d5\u05e8\u05d5\u05d3",FFCC99:"\u05d0\u05e4\u05e8\u05e1\u05e7",FFFF99:"\u05e6\u05d4\u05d5\u05d1 \u05d1\u05d4\u05d9\u05e8",CCFFCC:"\u05d9\u05e8\u05d5\u05e7 \u05d7\u05d9\u05d5\u05d5\u05e8",CCFFFF:"\u05d8\u05d5\u05e8\u05e7\u05d9\u05d6 \u05d1\u05d4\u05d9\u05e8","99CCFF":"\u05ea\u05db\u05dc\u05ea \u05d1\u05d4\u05d9\u05e8",CC99FF:"\u05d5\u05e8\u05d5\u05d3 \u05e2\u05de\u05d5\u05e7",FFFFFF:"\u05dc\u05d1\u05df"},aria:{"rich_text_area":"\u05d0\u05d6\u05d5\u05e8 \u05e2\u05d5\u05e8\u05da \u05d8\u05e7\u05e1\u05d8 \u05e2\u05e9\u05d9\u05e8"},wordcount:{words:"\u05de\u05d9\u05dc\u05d9\u05dd:"}}}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/he_IL.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/he_IL.js new file mode 100644 index 0000000000..e1af3e3866 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/he_IL.js @@ -0,0 +1,262 @@ +tinymce.addI18n('he_IL',{ +"Redo": "\u05d1\u05e6\u05e2 \u05e9\u05d5\u05d1", +"Undo": "\u05d1\u05d8\u05dc \u05e4\u05e2\u05d5\u05dc\u05d4", +"Cut": "\u05d2\u05d6\u05d5\u05e8", +"Copy": "\u05d4\u05e2\u05ea\u05e7", +"Paste": "\u05d4\u05d3\u05d1\u05e7", +"Select all": "\u05d1\u05d7\u05e8 \u05d4\u05db\u05dc", +"New document": "\u05de\u05e1\u05de\u05da \u05d7\u05d3\u05e9", +"Ok": "\u05d0\u05d9\u05e9\u05d5\u05e8", +"Cancel": "\u05d1\u05d8\u05dc", +"Visual aids": "\u05e2\u05d6\u05e8\u05d9\u05dd \u05d7\u05d6\u05d5\u05ea\u05d9\u05d9\u05dd", +"Bold": "\u05de\u05d5\u05d3\u05d2\u05e9", +"Italic": "\u05e0\u05d8\u05d5\u05d9", +"Underline": "\u05e7\u05d5 \u05ea\u05d7\u05ea\u05d9", +"Strikethrough": "\u05e7\u05d5 \u05d7\u05d5\u05e6\u05d4", +"Superscript": "\u05db\u05ea\u05d1 \u05e2\u05d9\u05dc\u05d9", +"Subscript": "\u05db\u05ea\u05d1 \u05ea\u05d7\u05ea\u05d9", +"Clear formatting": "\u05e0\u05e7\u05d4 \u05e2\u05d9\u05e6\u05d5\u05d1", +"Align left": "\u05d9\u05d9\u05e9\u05e8 \u05dc\u05e9\u05de\u05d0\u05dc", +"Align center": "\u05de\u05e8\u05db\u05d6", +"Align right": "\u05d9\u05d9\u05e9\u05e8 \u05dc\u05d9\u05de\u05d9\u05df", +"Justify": "\u05de\u05ea\u05d7 \u05dc\u05e6\u05d3\u05d3\u05d9\u05dd", +"Bullet list": "\u05e8\u05e9\u05d9\u05de\u05ea \u05ea\u05d1\u05dc\u05d9\u05d8\u05d9\u05dd", +"Numbered list": "\u05e8\u05e9\u05d9\u05de\u05d4 \u05de\u05de\u05d5\u05e1\u05e4\u05e8\u05ea", +"Decrease indent": "\u05d4\u05e7\u05d8\u05df \u05d4\u05d6\u05d7\u05d4", +"Increase indent": "\u05d4\u05d2\u05d3\u05dc \u05d4\u05d6\u05d7\u05d4", +"Close": "\u05e1\u05d2\u05d5\u05e8", +"Formats": "\u05e2\u05d9\u05e6\u05d5\u05d1\u05d9\u05dd", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "\u05d4\u05d3\u05e4\u05d3\u05e4\u05df \u05e9\u05dc\u05da \u05d0\u05d9\u05e0\u05d5 \u05de\u05d0\u05e4\u05e9\u05e8 \u05d2\u05d9\u05e9\u05d4 \u05d9\u05e9\u05d9\u05e8\u05d4 \u05dc\u05dc\u05d5\u05d7. \u05d0\u05e0\u05d0 \u05d4\u05e9\u05ea\u05de\u05e9 \u05d1\u05e7\u05d9\u05e6\u05d5\u05e8\u05d9 \u05d4\u05de\u05e7\u05dc\u05d3\u05ea Ctrl+X\/C\/V \u05d1\u05de\u05e7\u05d5\u05dd.", +"Headers": "\u05db\u05d5\u05ea\u05e8\u05d5\u05ea", +"Header 1": "\u05db\u05d5\u05ea\u05e8\u05ea 1", +"Header 2": "\u05db\u05d5\u05ea\u05e8\u05ea 2", +"Header 3": "\u05db\u05d5\u05ea\u05e8\u05ea 3", +"Header 4": "\u05db\u05d5\u05ea\u05e8\u05ea 4", +"Header 5": "\u05db\u05d5\u05ea\u05e8\u05ea 5", +"Header 6": "\u05db\u05d5\u05ea\u05e8\u05ea 6", +"Headings": "\u05db\u05d5\u05ea\u05e8\u05d5\u05ea", +"Heading 1": "\u05db\u05d5\u05ea\u05e8\u05d5\u05ea 1", +"Heading 2": "\u05db\u05d5\u05ea\u05e8\u05d5\u05ea 2", +"Heading 3": "\u05db\u05d5\u05ea\u05e8\u05d5\u05ea 3", +"Heading 4": "\u05db\u05d5\u05ea\u05e8\u05d5\u05ea 4", +"Heading 5": "\u05db\u05d5\u05ea\u05e8\u05d5\u05ea 5", +"Heading 6": "\u05db\u05d5\u05ea\u05e8\u05d5\u05ea 6", +"Preformatted": "\u05e2\u05e6\u05d1 \u05de\u05d7\u05d3\u05e9", +"Div": "\u05de\u05e7\u05d8\u05e2 \u05e7\u05d5\u05d3 Div", +"Pre": "\u05e7\u05d8\u05e2 \u05de\u05e7\u05d3\u05d9\u05dd Pre", +"Code": "\u05e7\u05d5\u05d3", +"Paragraph": "\u05e4\u05d9\u05e1\u05e7\u05d4", +"Blockquote": "\u05de\u05e7\u05d8\u05e2 \u05e6\u05d9\u05d8\u05d5\u05d8", +"Inline": "\u05d1\u05d2\u05d5\u05e3 \u05d4\u05d8\u05e7\u05e1\u05d8", +"Blocks": "\u05de\u05d1\u05e0\u05d9\u05dd", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "\u05d4\u05d3\u05d1\u05e7\u05d4 \u05d1\u05de\u05e6\u05d1 \u05d8\u05e7\u05e1\u05d8 \u05e8\u05d2\u05d9\u05dc. \u05ea\u05db\u05e0\u05d9\u05dd \u05d9\u05d5\u05d3\u05d1\u05e7\u05d5 \u05de\u05e2\u05ea\u05d4 \u05db\u05d8\u05e7\u05e1\u05d8 \u05e8\u05d2\u05d9\u05dc \u05e2\u05d3 \u05e9\u05ea\u05db\u05d1\u05d4 \u05d0\u05e4\u05e9\u05e8\u05d5\u05ea \u05d6\u05d5.", +"Font Family": "\u05e1\u05d5\u05d2 \u05d2\u05d5\u05e4\u05df", +"Font Sizes": "\u05d2\u05d5\u05d3\u05dc \u05d2\u05d5\u05e4\u05df", +"Class": "\u05de\u05d7\u05dc\u05e7\u05d4", +"Browse for an image": "\u05d1\u05d7\u05e8 \u05ea\u05de\u05d5\u05e0\u05d4 \u05dc\u05d4\u05e2\u05dc\u05d5\u05ea", +"OR": "\u05d0\u05d5", +"Drop an image here": "\u05e9\u05d7\u05e8\u05e8 \u05ea\u05de\u05d5\u05e0\u05d4 \u05db\u05d0\u05df", +"Upload": "\u05d4\u05e2\u05dc\u05d4", +"Block": "\u05d1\u05dc\u05d5\u05e7", +"Align": "\u05d9\u05d9\u05e9\u05e8", +"Default": "\u05d1\u05e8\u05d9\u05e8\u05ea \u05de\u05d7\u05d3\u05dc", +"Circle": "\u05e2\u05d9\u05d2\u05d5\u05dc", +"Disc": "\u05d7\u05d9\u05e9\u05d5\u05e7", +"Square": "\u05e8\u05d9\u05d1\u05d5\u05e2", +"Lower Alpha": "\u05d0\u05d5\u05ea\u05d9\u05d5\u05ea \u05d0\u05e0\u05d2\u05dc\u05d9\u05d5\u05ea \u05e7\u05d8\u05e0\u05d5\u05ea", +"Lower Greek": "\u05d0\u05d5\u05ea\u05d9\u05d5\u05ea \u05d9\u05d5\u05d5\u05e0\u05d9\u05d5\u05ea \u05e7\u05d8\u05e0\u05d5\u05ea", +"Lower Roman": "\u05e1\u05e4\u05e8\u05d5\u05ea \u05e8\u05d5\u05de\u05d9\u05d5\u05ea \u05e7\u05d8\u05e0\u05d5\u05ea", +"Upper Alpha": "\u05d0\u05d5\u05ea\u05d9\u05d5\u05ea \u05d0\u05e0\u05d2\u05dc\u05d9\u05d5\u05ea \u05d2\u05d3\u05d5\u05dc\u05d5\u05ea", +"Upper Roman": "\u05e1\u05e4\u05e8\u05d5\u05ea \u05e8\u05d5\u05de\u05d9\u05d5\u05ea \u05d2\u05d3\u05d5\u05dc\u05d5\u05ea", +"Anchor": "\u05de\u05e7\u05d5\u05dd \u05e2\u05d9\u05d2\u05d5\u05df", +"Name": "\u05e9\u05dd", +"Id": "\u05de\u05d6\u05d4\u05d4", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "\u05d4\u05de\u05d6\u05d4\u05d4 \u05d7\u05d9\u05d9\u05d1 \u05dc\u05d4\u05ea\u05d7\u05d9\u05dc \u05d1\u05d0\u05d5\u05ea \u05d5\u05dc\u05d0\u05d7\u05e8\u05d9\u05d4 \u05e8\u05e7 \u05d0\u05d5\u05ea\u05d9\u05d5\u05ea, \u05de\u05e1\u05e4\u05e8\u05d9\u05dd, \u05de\u05e7\u05e4\u05d9\u05dd, \u05e0\u05e7\u05d5\u05d3\u05d5\u05ea, \u05e0\u05e7\u05d5\u05d3\u05ea\u05d9\u05d9\u05dd \u05d0\u05d5 \u05e7\u05d5\u05d5\u05d9\u05dd \u05ea\u05d7\u05ea\u05d9\u05d9\u05dd.", +"You have unsaved changes are you sure you want to navigate away?": "\u05d4\u05e9\u05d9\u05e0\u05d5\u05d9\u05d9\u05dd \u05dc\u05d0 \u05e0\u05e9\u05de\u05e8\u05d5. \u05d1\u05d8\u05d5\u05d7 \u05e9\u05d1\u05e8\u05e6\u05d5\u05e0\u05da \u05dc\u05e6\u05d0\u05ea \u05de\u05d4\u05d3\u05e3?", +"Restore last draft": "\u05e9\u05d7\u05d6\u05e8 \u05d8\u05d9\u05d5\u05d8\u05d4 \u05d0\u05d7\u05e8\u05d5\u05e0\u05d4", +"Special character": "\u05ea\u05d5\u05d5\u05d9\u05dd \u05de\u05d9\u05d5\u05d7\u05d3\u05d9\u05dd", +"Source code": "\u05e7\u05d5\u05d3 \u05de\u05e7\u05d5\u05e8", +"Insert\/Edit code sample": "\u05d4\u05db\u05e0\u05e1\/\u05e2\u05e8\u05d5\u05da \u05d3\u05d5\u05d2\u05de\u05ea \u05e7\u05d5\u05d3", +"Language": "\u05e9\u05e4\u05d4", +"Code sample": "\u05d3\u05d5\u05d2\u05de\u05ea \u05e7\u05d5\u05d3", +"Color": "\u05e6\u05d1\u05e2", +"R": "\u05d0'", +"G": "\u05d9'", +"B": "\u05db'", +"Left to right": "\u05de\u05e9\u05de\u05d0\u05dc \u05dc\u05d9\u05de\u05d9\u05df", +"Right to left": "\u05de\u05d9\u05de\u05d9\u05df \u05dc\u05e9\u05de\u05d0\u05dc", +"Emoticons": "\u05de\u05d7\u05d5\u05d5\u05ea", +"Document properties": "\u05de\u05d0\u05e4\u05d9\u05d9\u05e0\u05d9 \u05de\u05e1\u05de\u05da", +"Title": "\u05db\u05d5\u05ea\u05e8\u05ea", +"Keywords": "\u05de\u05d9\u05dc\u05d5\u05ea \u05de\u05e4\u05ea\u05d7", +"Description": "\u05ea\u05d9\u05d0\u05d5\u05e8", +"Robots": "\u05e8\u05d5\u05d1\u05d5\u05d8\u05d9\u05dd", +"Author": "\u05de\u05d7\u05d1\u05e8", +"Encoding": "\u05e7\u05d9\u05d3\u05d5\u05d3", +"Fullscreen": "\u05de\u05e1\u05da \u05de\u05dc\u05d0", +"Action": "\u05e4\u05e2\u05d5\u05dc\u05d4", +"Shortcut": "\u05e7\u05d9\u05e6\u05d5\u05e8", +"Help": "\u05e2\u05d6\u05e8\u05d4", +"Address": "\u05db\u05ea\u05d5\u05d1\u05ea", +"Focus to menubar": "\u05d4\u05e2\u05d1\u05e8 \u05de\u05d9\u05e7\u05d5\u05d3 \u05dc\u05e1\u05e8\u05d2\u05dc \u05d4\u05ea\u05e4\u05e8\u05d8\u05d9\u05dd", +"Focus to toolbar": "\u05d4\u05e2\u05d1\u05e8 \u05de\u05d9\u05e7\u05d5\u05d3 \u05dc\u05e1\u05e8\u05d2\u05dc \u05d4\u05db\u05dc\u05d9\u05dd", +"Focus to element path": "\u05e2\u05d1\u05e8 \u05de\u05d9\u05e7\u05d5\u05d3 \u05dc\u05db\u05ea\u05d5\u05d1\u05ea \u05d4\u05e4\u05e8\u05d9\u05d8", +"Focus to contextual toolbar": "\u05d4\u05e2\u05d1\u05e8 \u05de\u05d9\u05e7\u05d5\u05d3 \u05dc\u05e1\u05e8\u05d2\u05dc \u05ea\u05d5\u05db\u05df", +"Insert link (if link plugin activated)": "\u05d4\u05db\u05e0\u05e1 \u05e7\u05d9\u05e9\u05d5\u05e8 (\u05d0\u05dd \u05ea\u05d5\u05e1\u05e3 \"\u05e7\u05d9\u05e9\u05d5\u05e8\u05d9\u05dd\" \u05e4\u05e2\u05d9\u05dc)", +"Save (if save plugin activated)": "\u05e9\u05de\u05d5\u05e8 (\u05d0\u05dd \u05ea\u05d5\u05e1\u05e3 \"\u05e9\u05de\u05d9\u05e8\u05d4\" \u05e4\u05e2\u05d9\u05dc)", +"Find (if searchreplace plugin activated)": "\u05d7\u05e4\u05e9 (\u05d0\u05dd \u05ea\u05d5\u05e1\u05e3 \"\u05d7\u05e4\u05e9 \u05d5\u05d4\u05d7\u05dc\u05e3\" \u05e4\u05e2\u05d9\u05dc)", +"Plugins installed ({0}):": "\u05ea\u05d5\u05e1\u05e4\u05d9\u05dd \u05de\u05d5\u05ea\u05e7\u05e0\u05d9\u05dd ({0}):", +"Premium plugins:": "\u05ea\u05d5\u05e1\u05e4\u05d9\u05dd \u05d1\u05ea\u05e9\u05dc\u05d5\u05dd:", +"Learn more...": "\u05dc\u05de\u05d3 \u05e2\u05d5\u05d3...", +"You are using {0}": "\u05d0\u05ea\\\u05d4 \u05de\u05e9\u05ea\u05de\u05e9\\\u05ea {0}", +"Plugins": "\u05ea\u05d5\u05e1\u05e4\u05d9\u05dd", +"Handy Shortcuts": "\u05e7\u05d9\u05e6\u05d5\u05e8\u05d9\u05dd \u05e9\u05d9\u05de\u05d5\u05e9\u05d9\u05d9\u05dd", +"Horizontal line": "\u05e7\u05d5 \u05d0\u05d5\u05e4\u05e7\u05d9", +"Insert\/edit image": "\u05d4\u05db\u05e0\u05e1\/\u05e2\u05e8\u05d5\u05da \u05ea\u05de\u05d5\u05e0\u05d4", +"Image description": "\u05ea\u05d9\u05d0\u05d5\u05e8 \u05d4\u05ea\u05de\u05d5\u05e0\u05d4", +"Source": "\u05de\u05e7\u05d5\u05e8", +"Dimensions": "\u05de\u05d9\u05de\u05d3\u05d9\u05dd", +"Constrain proportions": "\u05d4\u05d2\u05d1\u05dc\u05ea \u05e4\u05e8\u05d5\u05e4\u05d5\u05e8\u05e6\u05d9\u05d5\u05ea", +"General": "\u05db\u05dc\u05dc\u05d9", +"Advanced": "\u05de\u05ea\u05e7\u05d3\u05dd", +"Style": "\u05e1\u05d2\u05e0\u05d5\u05df", +"Vertical space": "\u05de\u05e8\u05d5\u05d5\u05d7 \u05d0\u05e0\u05db\u05d9", +"Horizontal space": "\u05de\u05e8\u05d5\u05d5\u05d7 \u05d0\u05d5\u05e4\u05e7\u05d9", +"Border": "\u05de\u05e1\u05d2\u05e8\u05ea", +"Insert image": "\u05d4\u05db\u05e0\u05e1 \u05ea\u05de\u05d5\u05e0\u05d4", +"Image": "\u05ea\u05de\u05d5\u05e0\u05d4", +"Image list": "\u05e8\u05e9\u05d9\u05de\u05ea \u05ea\u05de\u05d5\u05e0\u05d5\u05ea", +"Rotate counterclockwise": "\u05e1\u05d5\u05d1\u05d1 \u05d1\u05db\u05d9\u05d5\u05d5\u05df \u05d4\u05e4\u05d5\u05da \u05dc\u05e9\u05e2\u05d5\u05df", +"Rotate clockwise": "\u05e1\u05d5\u05d1\u05d1 \u05d1\u05db\u05d9\u05d5\u05d5\u05df \u05d4\u05e9\u05e2\u05d5\u05df", +"Flip vertically": "\u05d4\u05e4\u05d5\u05da \u05d0\u05e0\u05db\u05d9\u05ea", +"Flip horizontally": "\u05d4\u05e4\u05d5\u05da \u05d0\u05d5\u05e4\u05e7\u05d9\u05ea", +"Edit image": "\u05e2\u05e8\u05d5\u05da \u05ea\u05de\u05d5\u05e0\u05d4", +"Image options": "\u05d0\u05e4\u05e9\u05e8\u05d5\u05d9\u05d5\u05ea \u05ea\u05de\u05d5\u05e0\u05d4", +"Zoom in": "\u05d4\u05d2\u05d3\u05dc \u05ea\u05e6\u05d5\u05d2\u05d4", +"Zoom out": "\u05d4\u05e7\u05d8\u05df \u05ea\u05e6\u05d5\u05d2\u05d4", +"Crop": "\u05e7\u05e6\u05e5", +"Resize": "\u05e9\u05e0\u05d4 \u05d2\u05d5\u05d3\u05dc", +"Orientation": "\u05db\u05d9\u05d5\u05d5\u05df \u05dc\u05d0\u05d5\u05e8\u05da \/ \u05dc\u05e8\u05d5\u05d7\u05d1", +"Brightness": "\u05d1\u05d4\u05d9\u05e8\u05d5\u05ea", +"Sharpen": "\u05d7\u05d3\u05d3", +"Contrast": "\u05e0\u05d9\u05d2\u05d5\u05d3\u05d9\u05d5\u05ea", +"Color levels": "\u05e8\u05de\u05d5\u05ea \u05e6\u05d1\u05e2\u05d9\u05dd", +"Gamma": "\u05d2\u05d0\u05de\u05d4", +"Invert": "\u05d4\u05d9\u05e4\u05d5\u05da \u05e6\u05d1\u05e2\u05d9\u05dd", +"Apply": "\u05d9\u05d9\u05e9\u05dd", +"Back": "\u05d7\u05d6\u05d5\u05e8", +"Insert date\/time": "\u05d4\u05db\u05e0\u05e1 \u05ea\u05d0\u05e8\u05d9\u05da\/\u05e9\u05e2\u05d4", +"Date\/time": "\u05ea\u05d0\u05e8\u05d9\u05da\/\u05e9\u05e2\u05d4", +"Insert link": "\u05d4\u05db\u05e0\u05e1 \u05e7\u05d9\u05e9\u05d5\u05e8", +"Insert\/edit link": "\u05d4\u05db\u05e0\u05e1\/\u05e2\u05e8\u05d5\u05da \u05e7\u05d9\u05e9\u05d5\u05e8", +"Text to display": "\u05d8\u05e7\u05e1\u05d8 \u05dc\u05d4\u05e6\u05d2\u05d4", +"Url": "\u05db\u05ea\u05d5\u05d1\u05ea \u05e7\u05d9\u05e9\u05d5\u05e8", +"Target": "\u05de\u05d8\u05e8\u05d4", +"None": "\u05dc\u05dc\u05d0", +"New window": "\u05d7\u05dc\u05d5\u05df \u05d7\u05d3\u05e9", +"Remove link": "\u05de\u05d7\u05e7 \u05e7\u05d9\u05e9\u05d5\u05e8", +"Anchors": "\u05e2\u05d5\u05d2\u05e0\u05d9\u05dd", +"Link": "\u05e7\u05d9\u05e9\u05d5\u05e8", +"Paste or type a link": "\u05d4\u05d3\u05d1\u05e7 \u05d0\u05d5 \u05d4\u05e7\u05dc\u05d3 \u05e7\u05d9\u05e9\u05d5\u05e8", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "\u05e0\u05e8\u05d0\u05d4 \u05e9\u05d4\u05db\u05ea\u05d5\u05d1\u05ea \u05e9\u05d4\u05db\u05e0\u05e1\u05ea \u05d4\u05d9\u05d0 \u05db\u05ea\u05d5\u05d1\u05ea \u05d0\u05d9\u05de\u05d9\u05d9\u05dc. \u05d4\u05d0\u05dd \u05d1\u05e8\u05e6\u05d5\u05e0\u05da \u05dc\u05d4\u05d5\u05e1\u05d9\u05e3 \u05d0\u05ea \u05d4\u05e7\u05d9\u05d3\u05d5\u05de\u05ea :mailto?", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "\u05e0\u05e8\u05d0\u05d4 \u05e9\u05d4\u05db\u05ea\u05d5\u05d1\u05ea \u05e9\u05d4\u05db\u05e0\u05e1\u05ea \u05d4\u05d9\u05d0 \u05e7\u05d9\u05e9\u05d5\u05e8 \u05d7\u05d9\u05e6\u05d5\u05e0\u05d9 \u05d4\u05d0\u05dd \u05d1\u05e8\u05e6\u05d5\u05e0\u05da \u05dc\u05d4\u05d5\u05e1\u05d9\u05e3 \u05e7\u05d9\u05d3\u05d5\u05de\u05ea http:\/\/?", +"Link list": "\u05e8\u05e9\u05d9\u05de\u05ea \u05e7\u05d9\u05e9\u05d5\u05e8\u05d9\u05dd", +"Insert video": "\u05d4\u05db\u05e0\u05e1 \u05e1\u05e8\u05d8\u05d5\u05df", +"Insert\/edit video": "\u05d4\u05db\u05e0\u05e1\/\u05e2\u05e8\u05d5\u05da \u05e1\u05e8\u05d8\u05d5\u05df", +"Insert\/edit media": "\u05d4\u05db\u05e0\u05e1\/\u05e2\u05e8\u05d5\u05da \u05de\u05d3\u05d9\u05d4", +"Alternative source": "\u05de\u05e7\u05d5\u05e8 \u05de\u05e9\u05e0\u05d9", +"Poster": "\u05e4\u05d5\u05e1\u05d8\u05e8", +"Paste your embed code below:": "\u05d4\u05d3\u05d1\u05e7 \u05e7\u05d5\u05d3 \u05d4\u05d8\u05de\u05e2\u05d4 \u05de\u05ea\u05d7\u05ea:", +"Embed": "\u05d4\u05d8\u05de\u05e2", +"Media": "\u05de\u05d3\u05d9\u05d4", +"Nonbreaking space": "\u05e8\u05d5\u05d5\u05d7 (\u05dc\u05dc\u05d0 \u05e9\u05d1\u05d9\u05e8\u05ea \u05e9\u05d5\u05e8\u05d4)", +"Page break": "\u05d3\u05e3 \u05d7\u05d3\u05e9", +"Paste as text": "\u05d4\u05d3\u05d1\u05e7 \u05db\u05d8\u05e7\u05e1\u05d8", +"Preview": "\u05ea\u05e6\u05d5\u05d2\u05d4 \u05de\u05e7\u05d3\u05d9\u05de\u05d4", +"Print": "\u05d4\u05d3\u05e4\u05e1", +"Save": "\u05e9\u05de\u05d9\u05e8\u05d4", +"Find": "\u05d7\u05e4\u05e9", +"Replace with": "\u05d4\u05d7\u05dc\u05e3 \u05d1", +"Replace": "\u05d4\u05d7\u05dc\u05e3", +"Replace all": "\u05d4\u05d7\u05dc\u05e3 \u05d4\u05db\u05dc", +"Prev": "\u05e7\u05d5\u05d3\u05dd", +"Next": "\u05d4\u05d1\u05d0", +"Find and replace": "\u05d7\u05e4\u05e9 \u05d5\u05d4\u05d7\u05dc\u05e3", +"Could not find the specified string.": "\u05de\u05d7\u05e8\u05d5\u05d6\u05ea \u05dc\u05d0 \u05e0\u05de\u05e6\u05d0\u05d4", +"Match case": "\u05d4\u05d1\u05d7\u05df \u05d1\u05d9\u05df \u05d0\u05d5\u05ea\u05d9\u05d5\u05ea \u05e7\u05d8\u05e0\u05d5\u05ea \u05dc\u05d2\u05d3\u05d5\u05dc\u05d5\u05ea", +"Whole words": "\u05de\u05d9\u05dc\u05d4 \u05e9\u05dc\u05de\u05d4", +"Spellcheck": "\u05d1\u05d5\u05d3\u05e7 \u05d0\u05d9\u05d5\u05ea", +"Ignore": "\u05d4\u05ea\u05e2\u05dc\u05dd", +"Ignore all": "\u05d4\u05ea\u05e2\u05dc\u05dd \u05de\u05d4\u05db\u05dc", +"Finish": "\u05e1\u05d9\u05d9\u05dd", +"Add to Dictionary": "\u05d4\u05d5\u05e1\u05e3 \u05dc\u05de\u05d9\u05dc\u05d5\u05df", +"Insert table": "\u05d4\u05db\u05e0\u05e1 \u05d8\u05d1\u05dc\u05d4", +"Table properties": "\u05de\u05d0\u05e4\u05d9\u05d9\u05e0\u05d9 \u05d8\u05d1\u05dc\u05d4", +"Delete table": "\u05de\u05d7\u05e7 \u05d8\u05d1\u05dc\u05d4", +"Cell": "\u05ea\u05d0", +"Row": "\u05e9\u05d5\u05e8\u05d4", +"Column": "\u05e2\u05de\u05d5\u05d3\u05d4", +"Cell properties": "\u05de\u05d0\u05e4\u05d9\u05d9\u05e0\u05d9 \u05ea\u05d0", +"Merge cells": "\u05de\u05d6\u05d2 \u05ea\u05d0\u05d9\u05dd", +"Split cell": "\u05e4\u05e6\u05dc \u05ea\u05d0", +"Insert row before": "\u05d4\u05d5\u05e1\u05e3 \u05e9\u05d5\u05e8\u05d4 \u05dc\u05e4\u05e0\u05d9", +"Insert row after": "\u05d4\u05d5\u05e1\u05e3 \u05e9\u05d5\u05e8\u05d4 \u05d0\u05d7\u05e8\u05d9", +"Delete row": "\u05de\u05d7\u05e7 \u05e9\u05d5\u05e8\u05d4", +"Row properties": "\u05de\u05d0\u05e4\u05d9\u05d9\u05e0\u05d9 \u05e9\u05d5\u05e8\u05d4", +"Cut row": "\u05d2\u05d6\u05d5\u05e8 \u05e9\u05d5\u05e8\u05d4", +"Copy row": "\u05d4\u05e2\u05ea\u05e7 \u05e9\u05d5\u05e8\u05d4", +"Paste row before": "\u05d4\u05d3\u05d1\u05e7 \u05e9\u05d5\u05e8\u05d4 \u05dc\u05e4\u05e0\u05d9", +"Paste row after": "\u05d4\u05e2\u05ea\u05e7 \u05e9\u05d5\u05e8\u05d4 \u05d0\u05d7\u05e8\u05d9", +"Insert column before": "\u05d4\u05e2\u05ea\u05e7 \u05e2\u05de\u05d5\u05d3\u05d4 \u05dc\u05e4\u05e0\u05d9", +"Insert column after": "\u05d4\u05e2\u05ea\u05e7 \u05e2\u05de\u05d5\u05d3\u05d4 \u05d0\u05d7\u05e8\u05d9", +"Delete column": "\u05de\u05d7\u05e7 \u05e2\u05de\u05d5\u05d3\u05d4", +"Cols": "\u05e2\u05de\u05d5\u05d3\u05d5\u05ea", +"Rows": "\u05e9\u05d5\u05e8\u05d5\u05ea", +"Width": "\u05e8\u05d5\u05d7\u05d1", +"Height": "\u05d2\u05d5\u05d1\u05d4", +"Cell spacing": "\u05e9\u05d5\u05dc\u05d9\u05d9\u05dd \u05d7\u05d9\u05e6\u05d5\u05e0\u05d9\u05dd \u05dc\u05ea\u05d0", +"Cell padding": "\u05e9\u05d5\u05dc\u05d9\u05d9\u05dd \u05e4\u05e0\u05d9\u05de\u05d9\u05d9\u05dd \u05dc\u05ea\u05d0", +"Caption": "\u05db\u05d9\u05ea\u05d5\u05d1", +"Left": "\u05e9\u05de\u05d0\u05dc", +"Center": "\u05de\u05e8\u05db\u05d6", +"Right": "\u05d9\u05de\u05d9\u05df", +"Cell type": "\u05e1\u05d5\u05d2 \u05ea\u05d0", +"Scope": "\u05d4\u05d9\u05e7\u05e3", +"Alignment": "\u05d9\u05d9\u05e9\u05d5\u05e8", +"H Align": "\u05d9\u05d9\u05e9\u05d5\u05e8 \u05d0\u05d5\u05e4\u05e7\u05d9", +"V Align": "\u05d9\u05d9\u05e9\u05d5\u05e8 \u05d0\u05e0\u05db\u05d9", +"Top": "\u05e2\u05dc\u05d9\u05d5\u05df", +"Middle": "\u05d0\u05de\u05e6\u05e2", +"Bottom": "\u05ea\u05d7\u05ea\u05d9\u05ea", +"Header cell": "\u05db\u05d5\u05ea\u05e8\u05ea \u05dc\u05ea\u05d0", +"Row group": "\u05e7\u05d9\u05d1\u05d5\u05e5 \u05e9\u05d5\u05e8\u05d5\u05ea", +"Column group": "\u05e7\u05d9\u05d1\u05d5\u05e5 \u05e2\u05de\u05d5\u05d3\u05d5\u05ea", +"Row type": "\u05e1\u05d5\u05d2 \u05e9\u05d5\u05e8\u05d4", +"Header": "\u05db\u05d5\u05ea\u05e8\u05ea", +"Body": "\u05d2\u05d5\u05e3 \u05d4\u05d8\u05d1\u05dc\u05d0", +"Footer": "\u05db\u05d5\u05ea\u05e8\u05ea \u05ea\u05d7\u05ea\u05d5\u05e0\u05d4", +"Border color": "\u05e6\u05d1\u05e2 \u05d2\u05d1\u05d5\u05dc", +"Insert template": "\u05d4\u05db\u05e0\u05e1 \u05ea\u05d1\u05e0\u05d9\u05ea", +"Templates": "\u05ea\u05d1\u05e0\u05d9\u05d5\u05ea", +"Template": "\u05ea\u05d1\u05e0\u05d9\u05ea", +"Text color": "\u05e6\u05d1\u05e2 \u05d4\u05db\u05ea\u05d1", +"Background color": "\u05e6\u05d1\u05e2 \u05e8\u05e7\u05e2", +"Custom...": "\u05de\u05d5\u05ea\u05d0\u05dd \u05d0\u05d9\u05e9\u05d9\u05ea...", +"Custom color": "\u05e6\u05d1\u05e2 \u05de\u05d5\u05ea\u05d0\u05dd \u05d0\u05d9\u05e9\u05d9\u05ea", +"No color": "\u05dc\u05dc\u05d0 \u05e6\u05d1\u05e2", +"Table of Contents": "\u05ea\u05d5\u05db\u05df \u05e2\u05e0\u05d9\u05d9\u05e0\u05d9\u05dd", +"Show blocks": "\u05d4\u05e6\u05d2 \u05ea\u05d9\u05d1\u05d5\u05ea", +"Show invisible characters": "\u05d4\u05e6\u05d2 \u05ea\u05d5\u05d5\u05d9\u05dd \u05dc\u05d0 \u05e0\u05e8\u05d0\u05d9\u05dd", +"Words: {0}": "\u05de\u05d9\u05dc\u05d9\u05dd: {0}", +"{0} words": "{0} \u05de\u05d9\u05dc\u05d9\u05dd", +"File": "\u05e7\u05d5\u05d1\u05e5", +"Edit": "\u05e2\u05e8\u05d9\u05db\u05d4", +"Insert": "\u05d4\u05d5\u05e1\u05e4\u05d4", +"View": "\u05ea\u05e6\u05d5\u05d2\u05d4", +"Format": "\u05e4\u05d5\u05e8\u05de\u05d8", +"Table": "\u05d8\u05d1\u05dc\u05d4", +"Tools": "\u05db\u05dc\u05d9\u05dd", +"Powered by {0}": "\u05de\u05d5\u05e4\u05e2\u05dc \u05e2\"\u05d9 {0}", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "\u05ea\u05d9\u05d1\u05ea \u05e2\u05e8\u05d9\u05db\u05d4 \u05d7\u05db\u05de\u05d4. \u05dc\u05d7\u05e5 Alt-F9 \u05dc\u05ea\u05e4\u05e8\u05d9\u05d8. Alt-F10 \u05dc\u05ea\u05e6\u05d5\u05d2\u05ea \u05db\u05e4\u05ea\u05d5\u05e8\u05d9\u05dd, Alt-0 \u05dc\u05e2\u05d6\u05e8\u05d4", +"_dir": "rtl" +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/hr.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/hr.js new file mode 100644 index 0000000000..d52f861ce9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/hr.js @@ -0,0 +1,253 @@ +tinymce.addI18n('hr',{ +"Redo": "Vrati", +"Undo": "Poni\u0161ti", +"Cut": "Izre\u017ei", +"Copy": "Kopiraj", +"Paste": "Zalijepi", +"Select all": "Ozna\u010di sve", +"New document": "Novi dokument", +"Ok": "U redu", +"Cancel": "Odustani", +"Visual aids": "Vizualna pomo\u0107", +"Bold": "Podebljano", +"Italic": "Kurziv", +"Underline": "Crta ispod", +"Strikethrough": "Crta kroz sredinu", +"Superscript": "Eksponent", +"Subscript": "Indeks", +"Clear formatting": "Ukloni oblikovanje", +"Align left": "Poravnaj lijevo", +"Align center": "Poravnaj po sredini", +"Align right": "Poravnaj desno", +"Justify": "Obostrano poravnanje", +"Bullet list": "Lista", +"Numbered list": "Numerirana lista", +"Decrease indent": "Smanji uvla\u010denje", +"Increase indent": "Pove\u0107aj uvla\u010denje", +"Close": "Zatvori", +"Formats": "Formati", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "Va\u0161 preglednik ne podr\u017eava direktan pristup me\u0111uspremniku. Molimo Vas da umjesto toga koristite tipkovni\u010dke kratice Ctrl+X\/C\/V.", +"Headers": "Zaglavlja", +"Header 1": "Zaglavlje 1", +"Header 2": "Zaglavlje 2", +"Header 3": "Zaglavlje 3", +"Header 4": "Zaglavlje 4", +"Header 5": "Zaglavlje 5", +"Header 6": "Zaglavlje 6", +"Headings": "Naslovi", +"Heading 1": "Naslov 1", +"Heading 2": "Naslov 2", +"Heading 3": "Naslov 3", +"Heading 4": "Naslov 4", +"Heading 5": "Naslov 5", +"Heading 6": "Naslov 6", +"Div": "DIV", +"Pre": "PRE", +"Code": "CODE oznaka", +"Paragraph": "Paragraf", +"Blockquote": "BLOCKQUOTE", +"Inline": "Unutarnje", +"Blocks": "Blokovi", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "Akcija zalijepi od sada lijepi \u010disti tekst. Sadr\u017eaj \u0107e biti zaljepljen kao \u010disti tekst sve dok ne isklju\u010dite ovu opciju.", +"Font Family": "Obitelj fonta", +"Font Sizes": "Veli\u010dine fonta", +"Class": "Class", +"Browse for an image": "Browse for an image", +"OR": "OR", +"Drop an image here": "Drop an image here", +"Upload": "Upload", +"Default": "Zadano", +"Circle": "Krug", +"Disc": "To\u010dka", +"Square": "Kvadrat", +"Lower Alpha": "Mala slova", +"Lower Greek": "Mala gr\u010dka slova", +"Lower Roman": "Mala rimska slova", +"Upper Alpha": "Velika slova", +"Upper Roman": "Velika rimska slova", +"Anchor": "Sidro", +"Name": "Ime", +"Id": "Id", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "Id treba po\u010dinjati slovom, a nakon toga slijede samo slova, brojevi, crtice, to\u010dke, dvoto\u010dke i podvlake.", +"You have unsaved changes are you sure you want to navigate away?": "Postoje ne pohranjene izmjene, jeste li sigurni da \u017eelite oti\u0107i?", +"Restore last draft": "Vrati posljednju skicu", +"Special character": "Poseban znak", +"Source code": "Izvorni kod", +"Insert\/Edit code sample": "Umetni\/Uredi primjer k\u00f4da", +"Language": "Jezik", +"Color": "Boja", +"R": "R", +"G": "G", +"B": "B", +"Left to right": "S lijeva na desno", +"Right to left": "S desna na lijevo", +"Emoticons": "Emotikoni", +"Document properties": "Svojstva dokumenta", +"Title": "Naslov", +"Keywords": "Klju\u010dne rije\u010di", +"Description": "Opis", +"Robots": "Roboti pretra\u017eiva\u010da", +"Author": "Autor", +"Encoding": "Kodna stranica", +"Fullscreen": "Cijeli ekran", +"Action": "Action", +"Shortcut": "Shortcut", +"Help": "Help", +"Address": "Address", +"Focus to menubar": "Focus to menubar", +"Focus to toolbar": "Focus to toolbar", +"Focus to element path": "Focus to element path", +"Focus to contextual toolbar": "Focus to contextual toolbar", +"Insert link (if link plugin activated)": "Insert link (if link plugin activated)", +"Save (if save plugin activated)": "Save (if save plugin activated)", +"Find (if searchreplace plugin activated)": "Find (if searchreplace plugin activated)", +"Plugins installed ({0}):": "Plugins installed ({0}):", +"Premium plugins:": "Premium plugins:", +"Learn more...": "Learn more...", +"You are using {0}": "You are using {0}", +"Horizontal line": "Horizontalna linija", +"Insert\/edit image": "Umetni\/izmijeni sliku", +"Image description": "Opis slike", +"Source": "Izvor", +"Dimensions": "Dimenzije", +"Constrain proportions": "Zadr\u017ei proporcije", +"General": "Op\u0107enito", +"Advanced": "Napredno", +"Style": "Stil", +"Vertical space": "Okomit razmak", +"Horizontal space": "Horizontalan razmak", +"Border": "Rub", +"Insert image": "Umetni sliku", +"Image": "Slika", +"Image list": "Image list", +"Rotate counterclockwise": "Rotiraj lijevo", +"Rotate clockwise": "Rotiraj desno", +"Flip vertically": "Obrni vertikalno", +"Flip horizontally": "Obrni horizontalno", +"Edit image": "Uredi sliku", +"Image options": "Opcije slike", +"Zoom in": "Pove\u0107aj", +"Zoom out": "Smanji", +"Crop": "Obre\u017ei", +"Resize": "Promjeni veli\u010dinu", +"Orientation": "Orijentacija", +"Brightness": "Svjetlina", +"Sharpen": "Izo\u0161travanje", +"Contrast": "Kontrast", +"Color levels": "Razine boje", +"Gamma": "Gamma", +"Invert": "Invertiraj", +"Apply": "Primijeni", +"Back": "Natrag", +"Insert date\/time": "Umetni datum\/vrijeme", +"Date\/time": "Datum\/vrijeme", +"Insert link": "Umetni poveznicu", +"Insert\/edit link": "Umetni\/izmijeni poveznicu", +"Text to display": "Tekst za prikaz", +"Url": "Url", +"Target": "Meta", +"None": "Ni\u0161ta", +"New window": "Novi prozor", +"Remove link": "Ukloni poveznicu", +"Anchors": "Kra\u0107e poveznice", +"Link": "Link", +"Paste or type a link": "Zalijepi ili upi\u0161i link", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "Izgleda da je URL koji ste upisali e-mail adresa. \u017delite li dodati obavezan mailto: prefiks?", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "Izgleda da je URL koji ste upisali vanjski link. \u017delite li dodati obavezan http:\/\/ prefiks?", +"Link list": "Link list", +"Insert video": "Umetni video", +"Insert\/edit video": "Umetni\/izmijeni video", +"Insert\/edit media": "Umetni\/uredi mediju", +"Alternative source": "Alternativni izvor", +"Poster": "Poster", +"Paste your embed code below:": "Umetnite va\u0161 kod za ugradnju ispod:", +"Embed": "Ugradi", +"Media": "Media", +"Nonbreaking space": "Neprekidaju\u0107i razmak", +"Page break": "Prijelom stranice", +"Paste as text": "Zalijepi kao tekst", +"Preview": "Pregled", +"Print": "Ispis", +"Save": "Spremi", +"Find": "Tra\u017ei", +"Replace with": "Zamijeni s", +"Replace": "Zamijeni", +"Replace all": "Zamijeni sve", +"Prev": "Prethodni", +"Next": "Slijede\u0107i", +"Find and replace": "Prona\u0111i i zamijeni", +"Could not find the specified string.": "Tra\u017eeni tekst nije prona\u0111en", +"Match case": "Pazi na mala i velika slova", +"Whole words": "Cijele rije\u010di", +"Spellcheck": "Provjeri pravopis", +"Ignore": "Zanemari", +"Ignore all": "Zanemari sve", +"Finish": "Zavr\u0161i", +"Add to Dictionary": "Dodaj u rje\u010dnik", +"Insert table": "Umetni tablicu", +"Table properties": "Svojstva tablice", +"Delete table": "Izbri\u0161i tablicu", +"Cell": "Polje", +"Row": "Redak", +"Column": "Stupac", +"Cell properties": "Svojstva polja", +"Merge cells": "Spoji polja", +"Split cell": "Razdvoji polja", +"Insert row before": "Umetni redak prije", +"Insert row after": "Umetni redak nakon", +"Delete row": "Izbri\u0161i redak", +"Row properties": "Svojstva redka", +"Cut row": "Izre\u017ei redak", +"Copy row": "Kopiraj redak", +"Paste row before": "Zalijepi redak prije", +"Paste row after": "Zalijepi redak nakon", +"Insert column before": "Umetni stupac prije", +"Insert column after": "Umetni stupac nakon", +"Delete column": "Izbri\u0161i stupac", +"Cols": "Stupci", +"Rows": "Redci", +"Width": "\u0160irina", +"Height": "Visina", +"Cell spacing": "Razmak izme\u0111u polja", +"Cell padding": "Razmak unutar polja", +"Caption": "Natpis", +"Left": "Lijevo", +"Center": "Sredina", +"Right": "Desno", +"Cell type": "Vrsta polja", +"Scope": "Doseg", +"Alignment": "Poravnanje", +"H Align": "H Poravnavanje", +"V Align": "V Poravnavanje", +"Top": "Vrh", +"Middle": "Sredina", +"Bottom": "Dno", +"Header cell": "Polje zaglavlja", +"Row group": "Grupirani redci", +"Column group": "Grupirani stupci", +"Row type": "Vrsta redka", +"Header": "Zaglavlje", +"Body": "Sadr\u017eaj", +"Footer": "Podno\u017eje", +"Border color": "Boja ruba", +"Insert template": "Umetni predlo\u017eak", +"Templates": "Predlo\u0161ci", +"Template": "Predlo\u017eak", +"Text color": "Boja teksta", +"Background color": "Boja pozadine", +"Custom...": "Prilago\u0111eno...", +"Custom color": "Prilago\u0111ena boja", +"No color": "Bez boje", +"Table of Contents": "Sadr\u017eaj", +"Show blocks": "Prika\u017ei blokove", +"Show invisible characters": "Prika\u017ei nevidljive znakove", +"Words: {0}": "Rije\u010di: {0}", +"File": "Datoteka", +"Edit": "Izmijeni", +"Insert": "Umetni", +"View": "Pogled", +"Format": "Oblikuj", +"Table": "Tablica", +"Tools": "Alati", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Pritisni ALT-F9 za izbornik. Pritisni ALT-F10 za alatnu traku. Pritisni ALT-0 za pomo\u0107" +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/hu_HU.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/hu_HU.js new file mode 100644 index 0000000000..3972dc2b3c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/hu_HU.js @@ -0,0 +1,261 @@ +tinymce.addI18n('hu_HU',{ +"Redo": "Ism\u00e9t", +"Undo": "Visszavon\u00e1s", +"Cut": "Kiv\u00e1g\u00e1s", +"Copy": "M\u00e1sol\u00e1s", +"Paste": "Beilleszt\u00e9s", +"Select all": "Minden kijel\u00f6l\u00e9se", +"New document": "\u00daj dokumentum", +"Ok": "Rendben", +"Cancel": "M\u00e9gse", +"Visual aids": "Vizu\u00e1lis seg\u00e9deszk\u00f6z\u00f6k", +"Bold": "F\u00e9lk\u00f6v\u00e9r", +"Italic": "D\u0151lt", +"Underline": "Al\u00e1h\u00fazott", +"Strikethrough": "\u00c1th\u00fazott", +"Superscript": "Fels\u0151 index", +"Subscript": "Als\u00f3 index", +"Clear formatting": "Form\u00e1z\u00e1s t\u00f6rl\u00e9se", +"Align left": "Balra igaz\u00edt", +"Align center": "K\u00f6z\u00e9pre z\u00e1r", +"Align right": "Jobbra igaz\u00edt", +"Justify": "Sorkiz\u00e1r\u00e1s", +"Bullet list": "Felsorol\u00e1s", +"Numbered list": "Sz\u00e1moz\u00e1s", +"Decrease indent": "Beh\u00faz\u00e1s cs\u00f6kkent\u00e9se", +"Increase indent": "Beh\u00faz\u00e1s n\u00f6vel\u00e9se", +"Close": "Bez\u00e1r", +"Formats": "Form\u00e1tumok", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "A b\u00f6ng\u00e9sz\u0151d nem t\u00e1mogatja a k\u00f6zvetlen hozz\u00e1f\u00e9r\u00e9st a v\u00e1g\u00f3laphoz. K\u00e9rlek haszn\u00e1ld a Ctrl+X\/C\/V billenty\u0171ket.", +"Headers": "C\u00edmsorok", +"Header 1": "C\u00edmsor 1", +"Header 2": "C\u00edmsor 2", +"Header 3": "C\u00edmsor 3", +"Header 4": "C\u00edmsor 4", +"Header 5": "C\u00edmsor 5", +"Header 6": "C\u00edmsor 6", +"Headings": "Fejl\u00e9cek", +"Heading 1": "Fejl\u00e9c 1", +"Heading 2": "Fejl\u00e9c 2", +"Heading 3": "Fejl\u00e9c 3", +"Heading 4": "Fejl\u00e9c 4", +"Heading 5": "Fejl\u00e9c 5", +"Heading 6": "Fejl\u00e9c 6", +"Preformatted": "El\u0151form\u00e1zott", +"Div": "Div", +"Pre": "El\u0151form\u00e1zott", +"Code": "K\u00f3d", +"Paragraph": "Bekezd\u00e9s", +"Blockquote": "Id\u00e9zetblokk", +"Inline": "Sz\u00f6vegk\u00f6zi", +"Blocks": "Blokkok", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "Beilleszt\u00e9s mostant\u00f3l egyszer\u0171 sz\u00f6veg m\u00f3dban. A tartalmak mostant\u00f3l egyszer\u0171 sz\u00f6vegk\u00e9nt lesznek beillesztve, am\u00edg nem kapcsolod ki ezt az opci\u00f3t.", +"Font Family": "Bet\u0171t\u00edpus", +"Font Sizes": "Bet\u0171m\u00e9retek", +"Class": "Oszt\u00e1ly", +"Browse for an image": "K\u00e9p tall\u00f3z\u00e1sa", +"OR": "vagy", +"Drop an image here": "Dobj ide egy k\u00e9pet", +"Upload": "Felt\u00f6lt\u00e9s", +"Block": "Blokk", +"Align": "Igaz\u00edt\u00e1s", +"Default": "Alap\u00e9rtelmezett", +"Circle": "K\u00f6r", +"Disc": "Pont", +"Square": "N\u00e9gyzet", +"Lower Alpha": "Kisbet\u0171", +"Lower Greek": "Kis g\u00f6r\u00f6g sz\u00e1m", +"Lower Roman": "Kis r\u00f3mai sz\u00e1m", +"Upper Alpha": "Nagybet\u0171", +"Upper Roman": "Nagy r\u00f3mai sz\u00e1m", +"Anchor": "Horgony", +"Name": "N\u00e9v", +"Id": "Azonos\u00edt\u00f3", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "Az azonos\u00edt\u00f3nak bet\u0171vel kell kezd\u0151dnie, azut\u00e1n csak bet\u0171ket, sz\u00e1mokat, gondolatjeleket, pontokat, kett\u0151spontokat vagy al\u00e1h\u00faz\u00e1st tartalmazhat.", +"You have unsaved changes are you sure you want to navigate away?": "Nem mentett m\u00f3dos\u00edt\u00e1said vannak, biztos hogy el akarsz navig\u00e1lni?", +"Restore last draft": "Utols\u00f3 piszkozat vissza\u00e1ll\u00edt\u00e1sa", +"Special character": "Speci\u00e1lis karakter", +"Source code": "Forr\u00e1sk\u00f3d", +"Insert\/Edit code sample": "K\u00f3dminta besz\u00far\u00e1sa\/szerkeszt\u00e9se", +"Language": "Nyelv", +"Code sample": "K\u00f3d p\u00e9lda", +"Color": "Sz\u00edn", +"R": "R", +"G": "G", +"B": "B", +"Left to right": "Balr\u00f3l jobbra", +"Right to left": "Jobbr\u00f3l balra", +"Emoticons": "Vigyorok", +"Document properties": "Dokumentum tulajdons\u00e1gai", +"Title": "C\u00edm", +"Keywords": "Kulcsszavak", +"Description": "Le\u00edr\u00e1s", +"Robots": "Robotok", +"Author": "Szerz\u0151", +"Encoding": "K\u00f3dol\u00e1s", +"Fullscreen": "Teljes k\u00e9perny\u0151", +"Action": "M\u0171velet", +"Shortcut": "Parancsikon", +"Help": "S\u00fag\u00f3", +"Address": "C\u00edm", +"Focus to menubar": "F\u00f3kusz a men\u00fcre", +"Focus to toolbar": "F\u00f3kusz az eszk\u00f6zt\u00e1rra", +"Focus to element path": "F\u00f3kusz az elemek \u00fatvonal\u00e1ra", +"Focus to contextual toolbar": "F\u00f3kusz a k\u00f6rnyezetf\u00fcgg\u0151 eszk\u00f6zt\u00e1rra", +"Insert link (if link plugin activated)": "Hivatkoz\u00e1s besz\u00far\u00e1sa (ha a hivatkoz\u00e1s b\u0151v\u00edtm\u00e9ny enged\u00e9lyezett)", +"Save (if save plugin activated)": "Ment\u00e9s (ha a ment\u00e9s b\u0151v\u00edtm\u00e9ny enged\u00e9lyezett)", +"Find (if searchreplace plugin activated)": "Keres\u00e9s (ha a keres\u00e9s \u00e9s csere b\u0151v\u00edtm\u00e9ny enged\u00e9lyezett)", +"Plugins installed ({0}):": "Telep\u00edtett b\u0151v\u00edtm\u00e9nyek ({0}):", +"Premium plugins:": "Pr\u00e9mium b\u0151v\u00edtm\u00e9nyek:", +"Learn more...": "Tudj meg t\u00f6bbet...", +"You are using {0}": "Haszn\u00e1latban: {0}", +"Plugins": "Pluginek", +"Handy Shortcuts": "Hasznos linkek", +"Horizontal line": "V\u00edzszintes vonal", +"Insert\/edit image": "K\u00e9p beilleszt\u00e9se\/szerkeszt\u00e9se", +"Image description": "K\u00e9p le\u00edr\u00e1sa", +"Source": "Forr\u00e1s", +"Dimensions": "M\u00e9retek", +"Constrain proportions": "M\u00e9retar\u00e1ny", +"General": "\u00c1ltal\u00e1nos", +"Advanced": "Halad\u00f3", +"Style": "St\u00edlus", +"Vertical space": "Vertik\u00e1lis hely", +"Horizontal space": "Horizont\u00e1lis hely", +"Border": "Szeg\u00e9ly", +"Insert image": "K\u00e9p beilleszt\u00e9se", +"Image": "K\u00e9p", +"Image list": "K\u00e9p lista", +"Rotate counterclockwise": "Forgat\u00e1s az \u00f3ramutat\u00f3 j\u00e1r\u00e1s\u00e1val ellent\u00e9tesen", +"Rotate clockwise": "Forgat\u00e1s az \u00f3ramutat\u00f3 j\u00e1r\u00e1s\u00e1val megegyez\u0151en", +"Flip vertically": "F\u00fcgg\u0151leges t\u00fckr\u00f6z\u00e9s", +"Flip horizontally": "V\u00edzszintes t\u00fckr\u00f6z\u00e9s", +"Edit image": "K\u00e9p szerkeszt\u00e9se", +"Image options": "K\u00e9p be\u00e1ll\u00edt\u00e1sok", +"Zoom in": "Nagy\u00edt\u00e1s", +"Zoom out": "Kicsiny\u00edt\u00e9s", +"Crop": "K\u00e9p v\u00e1g\u00e1s", +"Resize": "\u00c1tm\u00e9retez\u00e9s", +"Orientation": "K\u00e9p t\u00e1jol\u00e1s", +"Brightness": "F\u00e9nyer\u0151", +"Sharpen": "\u00c9less\u00e9g", +"Contrast": "Kontraszt", +"Color levels": "Sz\u00ednszint", +"Gamma": "Gamma", +"Invert": "Inverz k\u00e9p", +"Apply": "Ment\u00e9s", +"Back": "Vissza", +"Insert date\/time": "D\u00e1tum\/id\u0151 beilleszt\u00e9se", +"Date\/time": "D\u00e1tum\/id\u0151", +"Insert link": "Hivatkoz\u00e1s beilleszt\u00e9se", +"Insert\/edit link": "Hivatkoz\u00e1s beilleszt\u00e9se\/szerkeszt\u00e9se", +"Text to display": "Megjelen\u0151 sz\u00f6veg", +"Url": "Url", +"Target": "C\u00e9l", +"None": "Nincs", +"New window": "\u00daj ablak", +"Remove link": "Hivatkoz\u00e1s t\u00f6rl\u00e9se", +"Anchors": "Horgonyok", +"Link": "Hivatkoz\u00e1s", +"Paste or type a link": "Hivatkoz\u00e1s be\u00edr\u00e1sa vagy beilleszt\u00e9se", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "A megadott URL email c\u00edmnek t\u0171nik. Szeretn\u00e9d hozz\u00e1adni a sz\u00fcks\u00e9ges mailto: el\u0151tagot?", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "A megadott URL k\u00fcls\u0151 c\u00edmnek t\u0171nik. Szeretn\u00e9d hozz\u00e1adni a sz\u00fcks\u00e9ges http:\/\/ el\u0151tagot?", +"Link list": "Hivatkoz\u00e1slista", +"Insert video": "Vide\u00f3 beilleszt\u00e9se", +"Insert\/edit video": "Vide\u00f3 beilleszt\u00e9se\/szerkeszt\u00e9se", +"Insert\/edit media": "M\u00e9dia besz\u00far\u00e1sa\/beilleszt\u00e9se", +"Alternative source": "Alternat\u00edv forr\u00e1s", +"Poster": "El\u0151n\u00e9zeti k\u00e9p", +"Paste your embed code below:": "Illeszd be a be\u00e1gyaz\u00f3 k\u00f3dot alulra:", +"Embed": "Be\u00e1gyaz\u00e1s", +"Media": "M\u00e9dia", +"Nonbreaking space": "Nem t\u00f6rhet\u0151 sz\u00f3k\u00f6z", +"Page break": "Oldalt\u00f6r\u00e9s", +"Paste as text": "Beilleszt\u00e9s sz\u00f6vegk\u00e9nt", +"Preview": "El\u0151n\u00e9zet", +"Print": "Nyomtat\u00e1s", +"Save": "Ment\u00e9s", +"Find": "Keres\u00e9s", +"Replace with": "Csere erre", +"Replace": "Csere", +"Replace all": "Az \u00f6sszes cser\u00e9je", +"Prev": "El\u0151z\u0151", +"Next": "K\u00f6vetkez\u0151", +"Find and replace": "Keres\u00e9s \u00e9s csere", +"Could not find the specified string.": "A be\u00edrt kifejez\u00e9s nem tal\u00e1lhat\u00f3.", +"Match case": "Kis \u00e9s nagybet\u0171k megk\u00fcl\u00f6nb\u00f6ztet\u00e9se", +"Whole words": "Csak ha ez a teljes sz\u00f3", +"Spellcheck": "Helyes\u00edr\u00e1s ellen\u0151rz\u00e9s", +"Ignore": "Figyelmen k\u00edv\u00fcl hagy", +"Ignore all": "Mindent figyelmen k\u00edv\u00fcl hagy", +"Finish": "Befejez\u00e9s", +"Add to Dictionary": "Sz\u00f3t\u00e1rhoz ad", +"Insert table": "T\u00e1bl\u00e1zat beilleszt\u00e9se", +"Table properties": "T\u00e1bl\u00e1zat tulajdons\u00e1gok", +"Delete table": "T\u00e1bl\u00e1zat t\u00f6rl\u00e9se", +"Cell": "Cella", +"Row": "Sor", +"Column": "Oszlop", +"Cell properties": "Cella tulajdons\u00e1gok", +"Merge cells": "Cell\u00e1k egyes\u00edt\u00e9se", +"Split cell": "Cell\u00e1k sz\u00e9tv\u00e1laszt\u00e1sa", +"Insert row before": "Sor besz\u00far\u00e1sa el\u00e9", +"Insert row after": "Sor besz\u00far\u00e1sa m\u00f6g\u00e9", +"Delete row": "Sor t\u00f6rl\u00e9se", +"Row properties": "Sor tulajdons\u00e1gai", +"Cut row": "Sor kiv\u00e1g\u00e1sa", +"Copy row": "Sor m\u00e1sol\u00e1sa", +"Paste row before": "Sor beilleszt\u00e9se el\u00e9", +"Paste row after": "Sor beilleszt\u00e9se m\u00f6g\u00e9", +"Insert column before": "Oszlop besz\u00far\u00e1sa el\u00e9", +"Insert column after": "Oszlop besz\u00far\u00e1sa m\u00f6g\u00e9", +"Delete column": "Oszlop t\u00f6rl\u00e9se", +"Cols": "Oszlopok", +"Rows": "Sorok", +"Width": "Sz\u00e9less\u00e9g", +"Height": "Magass\u00e1g", +"Cell spacing": "Cell\u00e1k t\u00e1vols\u00e1ga", +"Cell padding": "Cella m\u00e9rete", +"Caption": "Felirat", +"Left": "Bal", +"Center": "K\u00f6z\u00e9p", +"Right": "Jobb", +"Cell type": "Cella t\u00edpusa", +"Scope": "Hat\u00f3k\u00f6r", +"Alignment": "Igaz\u00edt\u00e1s", +"H Align": "V\u00edzszintes igaz\u00edt\u00e1s", +"V Align": "F\u00fcgg\u0151leges igaz\u00edt\u00e1s", +"Top": "Fel\u00fcl", +"Middle": "K\u00f6z\u00e9pen", +"Bottom": "Alul", +"Header cell": "Fejl\u00e9c cella", +"Row group": "Sor csoport", +"Column group": "Oszlop csoport", +"Row type": "Sor t\u00edpus", +"Header": "Fejl\u00e9c", +"Body": "Sz\u00f6vegt\u00f6rzs", +"Footer": "L\u00e1bl\u00e9c", +"Border color": "Szeg\u00e9ly sz\u00edne", +"Insert template": "Sablon beilleszt\u00e9se", +"Templates": "Sablonok", +"Template": "Sablon", +"Text color": "Sz\u00f6veg sz\u00edne", +"Background color": "H\u00e1tt\u00e9r sz\u00edn", +"Custom...": "Egy\u00e9ni...", +"Custom color": "Egy\u00e9ni sz\u00edn", +"No color": "Nincs sz\u00edn", +"Table of Contents": "Tartalomjegyz\u00e9k", +"Show blocks": "Blokkok mutat\u00e1sa", +"Show invisible characters": "L\u00e1thatatlan karakterek mutat\u00e1sa", +"Words: {0}": "Szavak: {0}", +"{0} words": "{0} sz\u00f3", +"File": "F\u00e1jl", +"Edit": "Szerkeszt\u00e9s", +"Insert": "Beilleszt\u00e9s", +"View": "N\u00e9zet", +"Format": "Form\u00e1tum", +"Table": "T\u00e1bl\u00e1zat", +"Tools": "Eszk\u00f6z\u00f6k", +"Powered by {0}": "\u00dczemelteti: {0}", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Rich Text ter\u00fclet. Nyomj ALT-F9-et a men\u00fch\u00f6z. Nyomj ALT-F10-et az eszk\u00f6zt\u00e1rhoz. Nyomj ALT-0-t a s\u00fag\u00f3hoz" +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/id.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/id.js new file mode 100644 index 0000000000..af2d4078d3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/id.js @@ -0,0 +1,261 @@ +tinymce.addI18n('id',{ +"Redo": "Ulang", +"Undo": "Batal", +"Cut": "Penggal", +"Copy": "Salin", +"Paste": "Tempel", +"Select all": "Pilih semua", +"New document": "Dokumen baru", +"Ok": "Ok", +"Cancel": "Batal", +"Visual aids": "Alat bantu visual", +"Bold": "Tebal", +"Italic": "Miring", +"Underline": "Garis bawah", +"Strikethrough": "Coret", +"Superscript": "Superskrip", +"Subscript": "Subskrip", +"Clear formatting": "Hapus format", +"Align left": "Rata kiri", +"Align center": "Rata tengah", +"Align right": "Rata kanan", +"Justify": "Penuh", +"Bullet list": "Daftar bersimbol", +"Numbered list": "Daftar bernomor", +"Decrease indent": "Turunkan inden", +"Increase indent": "Tambah inden", +"Close": "Tutup", +"Formats": "Format", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "Browser anda tidak mendukung akses langsung ke clipboard. Silahkan gunakan Ctrl+X\/C\/V dari keyboard.", +"Headers": "Judul", +"Header 1": "Judul 1", +"Header 2": "Judul 2", +"Header 3": "Judul 3", +"Header 4": "Judul 4", +"Header 5": "Judul 5", +"Header 6": "Judul 6", +"Headings": "Judul", +"Heading 1": "Judul 1", +"Heading 2": "Judul 2", +"Heading 3": "Judul 3", +"Heading 4": "Judul 4", +"Heading 5": "Judul 5", +"Heading 6": "Judul 6", +"Preformatted": "Monokode", +"Div": "Div", +"Pre": "Pre", +"Code": "Kode", +"Paragraph": "Paragraf", +"Blockquote": "Kutipan", +"Inline": "Baris", +"Blocks": "Blok", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "Penempelan sekarang dalam modus teks biasa. Konten sekarang akan disisipkan sebagai teks biasa sampai Anda memadamkan pilihan ini.", +"Font Family": "Jenis Huruf", +"Font Sizes": "Ukuran Huruf", +"Class": "Klas", +"Browse for an image": "Cari gambar", +"OR": "Atau", +"Drop an image here": "Letakan gambar di sini", +"Upload": "Unggah", +"Block": "Blok", +"Align": "Menyelaraskan", +"Default": "Bawaan", +"Circle": "Lingkaran", +"Disc": "Cakram", +"Square": "Kotak", +"Lower Alpha": "Huruf Kecil", +"Lower Greek": "Huruf Kecil Yunani", +"Lower Roman": "Huruf Kecil Romawi", +"Upper Alpha": "Huruf Besar", +"Upper Roman": "Huruf Besar Romawi", +"Anchor": "Jangkar", +"Name": "Nama", +"Id": "Id", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "Id harus dimulai dengan huruf, dan hanya diikuti oleh huruf, angka, koma, titik, titik koma atau garis bawah.", +"You have unsaved changes are you sure you want to navigate away?": "Anda memiliki perubahan yang belum disimpan, yakin ingin beralih ?", +"Restore last draft": "Muat kembali draft sebelumnya", +"Special character": "Spesial karakter", +"Source code": "Kode sumber", +"Insert\/Edit code sample": "Tambah\/Edit contoh kode", +"Language": "Bahasa", +"Code sample": "Contoh kode", +"Color": "Warna", +"R": "M", +"G": "H", +"B": "B", +"Left to right": "Kiri ke kanan", +"Right to left": "Kanan ke kiri", +"Emoticons": "Emotikon", +"Document properties": "Properti dokumwn", +"Title": "Judul", +"Keywords": "Kata kunci", +"Description": "Deskripsi", +"Robots": "Robot", +"Author": "Penulis", +"Encoding": "Enkoding", +"Fullscreen": "Layar penuh", +"Action": "Tindakan", +"Shortcut": "Pintasan", +"Help": "Bantuan", +"Address": "Alamat", +"Focus to menubar": "Fokus ke menubar", +"Focus to toolbar": "Fokus ke toolbar", +"Focus to element path": "Fokus ke jalur elemen", +"Focus to contextual toolbar": "Fokus ke toolbar kontekstual", +"Insert link (if link plugin activated)": "Masukan link (jika plugin diaktifkan)", +"Save (if save plugin activated)": "Simpan (jika plugin simpan diaktifkan)", +"Find (if searchreplace plugin activated)": "Cari (jika plugin searchplace diaktifkan)", +"Plugins installed ({0}):": "Plugin terpasang ({0})", +"Premium plugins:": "Plugin premium:", +"Learn more...": "Pelajari selengkapnya...", +"You are using {0}": "Anda menggunakan {0}", +"Plugins": "Plugin", +"Handy Shortcuts": "Pintasan Praktis", +"Horizontal line": "Garis horisontal", +"Insert\/edit image": "Sisip\/sunting gambar", +"Image description": "Deskripsi gambar", +"Source": "Sumber", +"Dimensions": "Dimensi", +"Constrain proportions": "Samakan proporsi", +"General": "Umum", +"Advanced": "Lanjutan", +"Style": "Gaya", +"Vertical space": "Spasi vertikal", +"Horizontal space": "Spasi horisontal", +"Border": "Batas", +"Insert image": "Sisipkan gambar", +"Image": "Gambar", +"Image list": "Daftar gambar", +"Rotate counterclockwise": "Putar berlawananjarumjam", +"Rotate clockwise": "Putar searahjarumjam", +"Flip vertically": "Balik vertikal", +"Flip horizontally": "Balik horisontal", +"Edit image": "Sunting gambar", +"Image options": "Opsi gambar", +"Zoom in": "Perbesar", +"Zoom out": "Perkecil", +"Crop": "Krop", +"Resize": "Ubah ukuran", +"Orientation": "Orientasi", +"Brightness": "Kecerahan", +"Sharpen": "Ketajaman", +"Contrast": "Kontras", +"Color levels": "Tingakt warna", +"Gamma": "Gamma", +"Invert": "Kebalikan", +"Apply": "Terapkan", +"Back": "Kembali", +"Insert date\/time": "Sisipkan tanggal\/waktu", +"Date\/time": "Tanggal\/waktu", +"Insert link": "Sisipkan tautan", +"Insert\/edit link": "Sisip\/sunting tautan", +"Text to display": "Teks yang ditampilkan", +"Url": "Tautan", +"Target": "Jendela tujuan", +"None": "Tidak ada", +"New window": "Jendela baru", +"Remove link": "Buang tautan", +"Anchors": "Jangkar", +"Link": "Tautan", +"Paste or type a link": "Tempel atau ketik sebuah tautan", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "Tautan yang anda masukkan sepertinya adalah alamat email. Apakah Anda ingin menambahkan prefiks mailto: yang dibutuhkan?", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "Tautan yang anda masukkan sepertinya adalah tautan eksternal. Apakah Anda ingin menambahkan prefiks http:\/\/ yang dibutuhkan?", +"Link list": "Daftar tautan", +"Insert video": "Sisipkan video", +"Insert\/edit video": "Sisip\/sunting video", +"Insert\/edit media": "Sisip\/sunting media", +"Alternative source": "Sumber alternatif", +"Poster": "Penulis", +"Paste your embed code below:": "Tempel kode yang diembed dibawah ini:", +"Embed": "Embed", +"Media": "Media", +"Nonbreaking space": "Spasi", +"Page break": "Baris baru", +"Paste as text": "Tempel sebagai teks biasa", +"Preview": "Pratinjau", +"Print": "Cetak", +"Save": "Simpan", +"Find": "Cari", +"Replace with": "Ganti dengan", +"Replace": "Ganti", +"Replace all": "Ganti semua", +"Prev": "Sebelumnya", +"Next": "Berikutnya", +"Find and replace": "Cari dan ganti", +"Could not find the specified string.": "Tidak dapat menemukan string yang dimaksud.", +"Match case": "Samakan besar kecil huruf", +"Whole words": "Semua kata", +"Spellcheck": "Periksa ejaan", +"Ignore": "Abaikan", +"Ignore all": "Abaikan semua", +"Finish": "Selesai", +"Add to Dictionary": "Tambahkan ke kamus", +"Insert table": "Sisipkan tabel", +"Table properties": "Properti tabel", +"Delete table": "Hapus tabel", +"Cell": "Sel", +"Row": "Baris", +"Column": "Kolom", +"Cell properties": "Properti sel", +"Merge cells": "Gabung sel", +"Split cell": "Bagi sel", +"Insert row before": "Sisipkan baris sebelum", +"Insert row after": "Sisipkan baris setelah", +"Delete row": "Hapus baris", +"Row properties": "Properti baris", +"Cut row": "Penggal baris", +"Copy row": "Salin baris", +"Paste row before": "Tempel baris sebelum", +"Paste row after": "Tempel baris setelah", +"Insert column before": "Sisipkan kolom sebelum", +"Insert column after": "Sisipkan kolom setelah", +"Delete column": "Hapus kolom", +"Cols": "Kolom", +"Rows": "Baris", +"Width": "Lebar", +"Height": "Tinggi", +"Cell spacing": "Spasi sel ", +"Cell padding": "Lapisan sel", +"Caption": "Caption", +"Left": "Kiri", +"Center": "Tengah", +"Right": "Kanan", +"Cell type": "Tipe sel", +"Scope": "Skup", +"Alignment": "Penjajaran", +"H Align": "Rata Samping", +"V Align": "Rata Atas", +"Top": "Atas", +"Middle": "Tengah", +"Bottom": "Bawah", +"Header cell": "Judul sel", +"Row group": "Kelompok baris", +"Column group": "Kelompok kolom", +"Row type": "Tipe baris", +"Header": "Judul", +"Body": "Body", +"Footer": "Footer", +"Border color": "Warna batas", +"Insert template": "Sisipkan templat", +"Templates": "Templat", +"Template": "Templat", +"Text color": "Warna teks", +"Background color": "Warna latar", +"Custom...": "Atur sendiri...", +"Custom color": "Warna sendiri", +"No color": "Tidak berwarna", +"Table of Contents": "Daftar Isi", +"Show blocks": "Tampilkan blok", +"Show invisible characters": "Tampilkan karakter tak tampak", +"Words: {0}": "Kata: {0}", +"{0} words": "{0} kata", +"File": "Berkas", +"Edit": "Sunting", +"Insert": "Sisip", +"View": "Tampilan", +"Format": "Format", +"Table": "Tabel", +"Tools": "Alat", +"Powered by {0}": "Didukung oleh {0}", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Area teks kaya. Tekan ALT-F9 untuk menu. Tekan ALT-F10 untuk toolbar. Tekan ALT-0 untuk bantuan" +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/it.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/it.js index d97803d2b3..5ffc0c0f80 100644 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/it.js +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/it.js @@ -1,219 +1,261 @@ tinymce.addI18n('it',{ -"Cut": "Taglia", -"Heading 5": "Intestazione 5", -"Header 2": "Header 2", -"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "Il tuo browser non supporta l'accesso diretto negli Appunti. Per favore usa i tasti di scelta rapida Ctrl+X\/C\/V.", -"Heading 4": "Intestazione 4", -"Div": "Div", -"Heading 2": "Intestazione 2", -"Paste": "Incolla", -"Close": "Chiudi", -"Font Family": "Famiglia font", -"Pre": "Pre", -"Align right": "Allinea a Destra", -"New document": "Nuovo Documento", -"Blockquote": "Blockquote", -"Numbered list": "Elenchi Numerati", -"Heading 1": "Intestazione 1", -"Headings": "Intestazioni", -"Increase indent": "Aumenta Rientro", -"Formats": "Formattazioni", -"Headers": "Intestazioni", -"Select all": "Seleziona Tutto", -"Header 3": "Intestazione 3", -"Blocks": "Blocchi", -"Undo": "Indietro", -"Strikethrough": "Barrato", -"Bullet list": "Elenchi Puntati", -"Header 1": "Intestazione 1", -"Superscript": "Apice", -"Clear formatting": "Cancella Formattazione", -"Font Sizes": "Dimensioni font", -"Subscript": "Pedice", -"Header 6": "Intestazione 6", "Redo": "Ripeti", -"Paragraph": "Paragrafo", -"Ok": "Ok", -"Bold": "Grassetto", -"Code": "Codice", -"Italic": "Corsivo", -"Align center": "Allinea al Cento", -"Header 5": "Intestazione 5", -"Heading 6": "Intestazione 6", -"Heading 3": "Intestazione 3", -"Decrease indent": "Riduci Rientro", -"Header 4": "Intestazione 4", -"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "Incolla \u00e8 in modalit\u00e0 testo normale. I contenuti sono incollati come testo normale se non disattivi l'opzione.", -"Underline": "Sottolineato", -"Cancel": "Annulla", -"Justify": "Giustifica", -"Inline": "Inlinea", +"Undo": "Indietro", +"Cut": "Taglia", "Copy": "Copia", -"Align left": "Allinea a Sinistra", +"Paste": "Incolla", +"Select all": "Seleziona Tutto", +"New document": "Nuovo Documento", +"Ok": "Ok", +"Cancel": "Annulla", "Visual aids": "Elementi Visivi", -"Lower Greek": "Greek Minore", -"Square": "Quadrato", +"Bold": "Grassetto", +"Italic": "Corsivo", +"Underline": "Sottolineato", +"Strikethrough": "Barrato", +"Superscript": "Apice", +"Subscript": "Pedice", +"Clear formatting": "Cancella Formattazione", +"Align left": "Allinea a Sinistra", +"Align center": "Allinea al Cento", +"Align right": "Allinea a Destra", +"Justify": "Giustifica", +"Bullet list": "Elenchi Puntati", +"Numbered list": "Elenchi Numerati", +"Decrease indent": "Riduci Rientro", +"Increase indent": "Aumenta Rientro", +"Close": "Chiudi", +"Formats": "Formattazioni", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "Il tuo browser non supporta l'accesso diretto negli Appunti. Per favore usa i tasti di scelta rapida Ctrl+X\/C\/V.", +"Headers": "Intestazioni", +"Header 1": "Intestazione 1", +"Header 2": "Header 2", +"Header 3": "Intestazione 3", +"Header 4": "Intestazione 4", +"Header 5": "Intestazione 5", +"Header 6": "Intestazione 6", +"Headings": "Intestazioni", +"Heading 1": "Intestazione 1", +"Heading 2": "Intestazione 2", +"Heading 3": "Intestazione 3", +"Heading 4": "Intestazione 4", +"Heading 5": "Intestazione 5", +"Heading 6": "Intestazione 6", +"Preformatted": "Preformattato", +"Div": "Div", +"Pre": "Pre", +"Code": "Codice", +"Paragraph": "Paragrafo", +"Blockquote": "Blockquote", +"Inline": "Inlinea", +"Blocks": "Blocchi", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "Incolla \u00e8 in modalit\u00e0 testo normale. I contenuti sono incollati come testo normale se non disattivi l'opzione.", +"Font Family": "Famiglia font", +"Font Sizes": "Dimensioni font", +"Class": "Classe", +"Browse for an image": "Scegli un'immagine", +"OR": "o", +"Drop an image here": "Incolla un'immagine qui", +"Upload": "Carica", +"Block": "Blocco", +"Align": "Allinea", "Default": "Default", -"Lower Alpha": "Alpha Minore", "Circle": "Cerchio", "Disc": "Disco", +"Square": "Quadrato", +"Lower Alpha": "Alpha Minore", +"Lower Greek": "Greek Minore", +"Lower Roman": "Roman Minore", "Upper Alpha": "Alpha Superiore", "Upper Roman": "Roman Superiore", -"Lower Roman": "Roman Minore", -"Name": "Nome", "Anchor": "Fissa", +"Name": "Nome", +"Id": "Id", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "L'id dovrebbe cominciare con una lettera, seguito solo da lettere, numeri, linee, punti, virgole.", "You have unsaved changes are you sure you want to navigate away?": "Non hai salvato delle modifiche, sei sicuro di andartene?", "Restore last draft": "Ripristina l'ultima bozza.", "Special character": "Carattere Speciale", "Source code": "Codice Sorgente", -"B": "B", +"Insert\/Edit code sample": "Inserisci\/Modifica esempio di codice", +"Language": "Lingua", +"Code sample": "Esempio di codice", +"Color": "Colore", "R": "R", "G": "G", -"Color": "Colore", -"Right to left": "Da Destra a Sinistra", +"B": "B", "Left to right": "Da Sinistra a Destra", +"Right to left": "Da Destra a Sinistra", "Emoticons": "Emoction", -"Robots": "Robot", "Document properties": "Propriet\u00e0 Documento", "Title": "Titolo", "Keywords": "Parola Chiave", -"Encoding": "Codifica", "Description": "Descrizione", +"Robots": "Robot", "Author": "Autore", +"Encoding": "Codifica", "Fullscreen": "Schermo Intero", +"Action": "Azione", +"Shortcut": "Scorciatoia", +"Help": "Aiuto", +"Address": "Indirizzo", +"Focus to menubar": "Focus sulla barra del menu", +"Focus to toolbar": "Focus sulla barra degli strumenti", +"Focus to element path": "Focus sul percorso dell'elemento", +"Focus to contextual toolbar": "Focus sulla barra degli strumenti contestuale", +"Insert link (if link plugin activated)": "Inserisci link (se il plugin link \u00e8 attivato)", +"Save (if save plugin activated)": "Salva (se il plugin save \u00e8 attivato)", +"Find (if searchreplace plugin activated)": "Trova (se il plugin searchreplace \u00e8 attivato)", +"Plugins installed ({0}):": "Plugin installati ({0}):", +"Premium plugins:": "Plugin Premium:", +"Learn more...": "Per saperne di pi\u00f9...", +"You are using {0}": "Stai usando {0}", +"Plugins": "Plugin", +"Handy Shortcuts": "Scorciatoia pratica", "Horizontal line": "Linea Orizzontale", -"Horizontal space": "Spazio Orizzontale", "Insert\/edit image": "Aggiungi\/Modifica Immagine", +"Image description": "Descrizione Immagine", +"Source": "Fonte", +"Dimensions": "Dimenzioni", +"Constrain proportions": "Mantieni Proporzioni", "General": "Generale", "Advanced": "Avanzato", -"Source": "Fonte", -"Border": "Bordo", -"Constrain proportions": "Mantieni Proporzioni", -"Vertical space": "Spazio Verticale", -"Image description": "Descrizione Immagine", "Style": "Stile", -"Dimensions": "Dimenzioni", +"Vertical space": "Spazio Verticale", +"Horizontal space": "Spazio Orizzontale", +"Border": "Bordo", "Insert image": "Inserisci immagine", -"Zoom in": "Ingrandisci", -"Contrast": "Contrasto", -"Back": "Indietro", -"Gamma": "Gamma", -"Flip horizontally": "Rifletti orizzontalmente", -"Resize": "Ridimensiona", -"Sharpen": "Contrasta", -"Zoom out": "Rimpicciolisci", -"Image options": "Opzioni immagine", -"Apply": "Applica", -"Brightness": "Luminosit\u00e0", -"Rotate clockwise": "Ruota in senso orario", +"Image": "Immagine", +"Image list": "Elenco immagini", "Rotate counterclockwise": "Ruota in senso antiorario", -"Edit image": "Modifica immagine", -"Color levels": "Livelli colore", -"Crop": "Taglia", -"Orientation": "Orientamento", +"Rotate clockwise": "Ruota in senso orario", "Flip vertically": "Rifletti verticalmente", +"Flip horizontally": "Rifletti orizzontalmente", +"Edit image": "Modifica immagine", +"Image options": "Opzioni immagine", +"Zoom in": "Ingrandisci", +"Zoom out": "Rimpicciolisci", +"Crop": "Taglia", +"Resize": "Ridimensiona", +"Orientation": "Orientamento", +"Brightness": "Luminosit\u00e0", +"Sharpen": "Contrasta", +"Contrast": "Contrasto", +"Color levels": "Livelli colore", +"Gamma": "Gamma", "Invert": "Inverti", +"Apply": "Applica", +"Back": "Indietro", "Insert date\/time": "Inserisci Data\/Ora", -"Remove link": "Rimuovi link", -"Url": "Url", -"Text to display": "Testo da Visualizzare", -"Anchors": "Anchors", +"Date\/time": "Data\/Ora", "Insert link": "Inserisci il Link", -"New window": "Nuova Finestra", -"None": "No", -"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "L'URL inserito sembra essere un collegamento esterno. Vuoi aggiungere il prefisso necessario http:\/\/?", -"Target": "Target", -"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "L'URL inserito sembra essere un indirizzo email. Vuoi aggiungere il prefisso necessario mailto:?", "Insert\/edit link": "Inserisci\/Modifica Link", -"Insert\/edit video": "Inserisci\/Modifica Video", -"Poster": "Anteprima", -"Alternative source": "Alternativo", -"Paste your embed code below:": "Incolla il codice d'incorporamento qui:", +"Text to display": "Testo da Visualizzare", +"Url": "Url", +"Target": "Target", +"None": "No", +"New window": "Nuova Finestra", +"Remove link": "Rimuovi link", +"Anchors": "Anchors", +"Link": "Collegamento", +"Paste or type a link": "Incolla o digita un collegamento", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "L'URL inserito sembra essere un indirizzo email. Vuoi aggiungere il prefisso necessario mailto:?", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "L'URL inserito sembra essere un collegamento esterno. Vuoi aggiungere il prefisso necessario http:\/\/?", +"Link list": "Elenco link", "Insert video": "Inserisci Video", +"Insert\/edit video": "Inserisci\/Modifica Video", +"Insert\/edit media": "Inserisci\/Modifica Media", +"Alternative source": "Alternativo", +"Poster": "Anteprima", +"Paste your embed code below:": "Incolla il codice d'incorporamento qui:", "Embed": "Incorporare", +"Media": "Media", "Nonbreaking space": "Spazio unificatore", "Page break": "Interruzione di pagina", "Paste as text": "incolla come testo", "Preview": "Anteprima", "Print": "Stampa", "Save": "Salva", -"Could not find the specified string.": "Impossibile trovare la parola specifica.", -"Replace": "Sostituisci", -"Next": "Successivo", -"Whole words": "Parole Sbagliate", -"Find and replace": "Trova e Sostituisci", -"Replace with": "Sostituisci Con", "Find": "Trova", +"Replace with": "Sostituisci Con", +"Replace": "Sostituisci", "Replace all": "Sostituisci Tutto", -"Match case": "Maiuscole\/Minuscole ", "Prev": "Precedente", +"Next": "Successivo", +"Find and replace": "Trova e Sostituisci", +"Could not find the specified string.": "Impossibile trovare la parola specifica.", +"Match case": "Maiuscole\/Minuscole ", +"Whole words": "Parole Sbagliate", "Spellcheck": "Controllo ortografico", -"Finish": "Termina", -"Ignore all": "Ignora Tutto", "Ignore": "Ignora", +"Ignore all": "Ignora Tutto", +"Finish": "Termina", "Add to Dictionary": "Aggiungi al Dizionario", -"Insert row before": "Inserisci una Riga Prima", -"Rows": "Righe", -"Height": "Altezza", -"Paste row after": "Incolla una Riga Dopo", -"Alignment": "Allineamento", -"Border color": "Colore bordo", -"Column group": "Gruppo di Colonne", -"Row": "Riga", -"Insert column before": "Inserisci una Colonna Prima", -"Split cell": "Dividi Cella", -"Cell padding": "Padding della Cella", -"Cell spacing": "Spaziatura della Cella", -"Row type": "Tipo di Riga", "Insert table": "Inserisci Tabella", -"Body": "Body", -"Caption": "Didascalia", -"Footer": "Footer", -"Delete row": "Cancella Riga", -"Paste row before": "Incolla una Riga Prima", -"Scope": "Campo", -"Delete table": "Cancella Tabella", -"H Align": "Allineamento H", -"Top": "In alto", -"Header cell": "cella d'intestazione", -"Column": "Colonna", -"Row group": "Gruppo di Righe", -"Cell": "Cella", -"Middle": "In mezzo", -"Cell type": "Tipo di Cella", -"Copy row": "Copia Riga", -"Row properties": "Propriet\u00e0 della Riga", "Table properties": "Propiet\u00e0 della Tabella", -"Bottom": "In fondo", -"V Align": "Allineamento V", -"Header": "Header", -"Right": "Destra", -"Insert column after": "Inserisci una Colonna Dopo", -"Cols": "Colonne", -"Insert row after": "Inserisci una Riga Dopo", -"Width": "Larghezza", +"Delete table": "Cancella Tabella", +"Cell": "Cella", +"Row": "Riga", +"Column": "Colonna", "Cell properties": "Propiet\u00e0 della Cella", -"Left": "Sinistra", -"Cut row": "Taglia Riga", -"Delete column": "Cancella Colonna", -"Center": "Centro", "Merge cells": "Unisci Cella", +"Split cell": "Dividi Cella", +"Insert row before": "Inserisci una Riga Prima", +"Insert row after": "Inserisci una Riga Dopo", +"Delete row": "Cancella Riga", +"Row properties": "Propriet\u00e0 della Riga", +"Cut row": "Taglia Riga", +"Copy row": "Copia Riga", +"Paste row before": "Incolla una Riga Prima", +"Paste row after": "Incolla una Riga Dopo", +"Insert column before": "Inserisci una Colonna Prima", +"Insert column after": "Inserisci una Colonna Dopo", +"Delete column": "Cancella Colonna", +"Cols": "Colonne", +"Rows": "Righe", +"Width": "Larghezza", +"Height": "Altezza", +"Cell spacing": "Spaziatura della Cella", +"Cell padding": "Padding della Cella", +"Caption": "Didascalia", +"Left": "Sinistra", +"Center": "Centro", +"Right": "Destra", +"Cell type": "Tipo di Cella", +"Scope": "Campo", +"Alignment": "Allineamento", +"H Align": "Allineamento H", +"V Align": "Allineamento V", +"Top": "In alto", +"Middle": "In mezzo", +"Bottom": "In fondo", +"Header cell": "cella d'intestazione", +"Row group": "Gruppo di Righe", +"Column group": "Gruppo di Colonne", +"Row type": "Tipo di Riga", +"Header": "Header", +"Body": "Body", +"Footer": "Footer", +"Border color": "Colore bordo", "Insert template": "Inserisci Template", "Templates": "Template", +"Template": "Modello", +"Text color": "Colore Testo", "Background color": "Colore Background", "Custom...": "Personalizzato...", "Custom color": "Colore personalizzato", "No color": "Nessun colore", -"Text color": "Colore Testo", +"Table of Contents": "Tabella dei contenuti", "Show blocks": "Mostra Blocchi", "Show invisible characters": "Mostra Caratteri Invisibili", "Words: {0}": "Parole: {0}", -"Insert": "Inserisci", +"{0} words": "{0} parole", "File": "File", "Edit": "Modifica", -"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Rich Text Area. Premi ALT-F9 per il men\u00f9. Premi ALT-F10 per la barra degli strumenti. Premi ALT-0 per l'aiuto.", -"Tools": "Strumenti", +"Insert": "Inserisci", "View": "Visualiza", +"Format": "Formato", "Table": "Tabella", -"Format": "Formato" +"Tools": "Strumenti", +"Powered by {0}": "Fornito da {0}", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Rich Text Area. Premi ALT-F9 per il men\u00f9. Premi ALT-F10 per la barra degli strumenti. Premi ALT-0 per l'aiuto." }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/ja.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/ja.js index 848cbd36b9..61f0ba61a1 100644 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/ja.js +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/ja.js @@ -1,219 +1,261 @@ tinymce.addI18n('ja',{ -"Cut": "\u5207\u308a\u53d6\u308a", -"Heading 5": "\u898b\u51fa\u3057 5", -"Header 2": "\u30d8\u30c3\u30c0\u30fc 2", -"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "\u304a\u4f7f\u3044\u306e\u30d6\u30e9\u30a6\u30b6\u3067\u306f\u30af\u30ea\u30c3\u30d7\u30dc\u30fc\u30c9\u6a5f\u80fd\u3092\u5229\u7528\u3059\u308b\u3053\u3068\u304c\u3067\u304d\u307e\u305b\u3093\u3002\u30ad\u30fc\u30dc\u30fc\u30c9\u306e\u30b7\u30e7\u30fc\u30c8\u30ab\u30c3\u30c8\uff08Ctrl+X, Ctrl+C, Ctrl+V\uff09\u3092\u304a\u4f7f\u3044\u4e0b\u3055\u3044\u3002", -"Heading 4": "\u898b\u51fa\u3057 4", -"Div": "Div", -"Heading 2": "\u898b\u51fa\u3057 2", -"Paste": "\u8cbc\u308a\u4ed8\u3051", -"Close": "\u9589\u3058\u308b", -"Font Family": "\u30d5\u30a9\u30f3\u30c8\u30d5\u30a1\u30df\u30ea\u30fc", -"Pre": "Pre", -"Align right": "\u53f3\u5bc4\u305b", -"New document": "\u65b0\u898f\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8", -"Blockquote": "\u5f15\u7528", -"Numbered list": "\u756a\u53f7\u4ed8\u304d\u7b87\u6761\u66f8\u304d", -"Heading 1": "\u898b\u51fa\u3057 1", -"Headings": "\u898b\u51fa\u3057", -"Increase indent": "\u30a4\u30f3\u30c7\u30f3\u30c8\u3092\u5897\u3084\u3059", -"Formats": "\u66f8\u5f0f", -"Headers": "\u30d8\u30c3\u30c0\u30fc", -"Select all": "\u5168\u3066\u3092\u9078\u629e", -"Header 3": "\u30d8\u30c3\u30c0\u30fc 3", -"Blocks": "\u30d6\u30ed\u30c3\u30af", -"Undo": "\u5143\u306b\u623b\u3059", -"Strikethrough": "\u53d6\u308a\u6d88\u3057\u7dda", -"Bullet list": "\u7b87\u6761\u66f8\u304d", -"Header 1": "\u30d8\u30c3\u30c0\u30fc 1", -"Superscript": "\u4e0a\u4ed8\u304d\u6587\u5b57", -"Clear formatting": "\u66f8\u5f0f\u3092\u30af\u30ea\u30a2", -"Font Sizes": "\u30d5\u30a9\u30f3\u30c8\u30b5\u30a4\u30ba", -"Subscript": "\u4e0b\u4ed8\u304d\u6587\u5b57", -"Header 6": "\u30d8\u30c3\u30c0\u30fc 6", "Redo": "\u3084\u308a\u76f4\u3059", -"Paragraph": "\u6bb5\u843d", -"Ok": "OK", -"Bold": "\u592a\u5b57", -"Code": "\u30b3\u30fc\u30c9", -"Italic": "\u659c\u4f53", -"Align center": "\u4e2d\u592e\u63c3\u3048", -"Header 5": "\u30d8\u30c3\u30c0\u30fc 5", -"Heading 6": "\u898b\u51fa\u3057 6", -"Heading 3": "\u898b\u51fa\u3057 3", -"Decrease indent": "\u30a4\u30f3\u30c7\u30f3\u30c8\u3092\u6e1b\u3089\u3059", -"Header 4": "\u30d8\u30c3\u30c0\u30fc 4", -"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "\u8cbc\u308a\u4ed8\u3051\u306f\u73fe\u5728\u30d7\u30ec\u30fc\u30f3\u30c6\u30ad\u30b9\u30c8\u30e2\u30fc\u30c9\u3067\u3059\u3002\u3053\u306e\u30aa\u30d7\u30b7\u30e7\u30f3\u3092\u30aa\u30d5\u306b\u3057\u306a\u3044\u9650\u308a\u5185\u5bb9\u306f\u30d7\u30ec\u30fc\u30f3\u30c6\u30ad\u30b9\u30c8\u3068\u3057\u3066\u8cbc\u308a\u4ed8\u3051\u3089\u308c\u307e\u3059\u3002", -"Underline": "\u4e0b\u7dda", -"Cancel": "\u30ad\u30e3\u30f3\u30bb\u30eb", -"Justify": "\u4e21\u7aef\u63c3\u3048", -"Inline": "\u30a4\u30f3\u30e9\u30a4\u30f3", +"Undo": "\u5143\u306b\u623b\u3059", +"Cut": "\u5207\u308a\u53d6\u308a", "Copy": "\u30b3\u30d4\u30fc", -"Align left": "\u5de6\u5bc4\u305b", +"Paste": "\u8cbc\u308a\u4ed8\u3051", +"Select all": "\u5168\u3066\u3092\u9078\u629e", +"New document": "\u65b0\u898f\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8", +"Ok": "OK", +"Cancel": "\u30ad\u30e3\u30f3\u30bb\u30eb", "Visual aids": "\u8868\u306e\u67a0\u7dda\u3092\u70b9\u7dda\u3067\u8868\u793a", -"Lower Greek": "\u5c0f\u6587\u5b57\u306e\u30ae\u30ea\u30b7\u30e3\u6587\u5b57", -"Square": "\u56db\u89d2", +"Bold": "\u592a\u5b57", +"Italic": "\u659c\u4f53", +"Underline": "\u4e0b\u7dda", +"Strikethrough": "\u53d6\u308a\u6d88\u3057\u7dda", +"Superscript": "\u4e0a\u4ed8\u304d\u6587\u5b57", +"Subscript": "\u4e0b\u4ed8\u304d\u6587\u5b57", +"Clear formatting": "\u66f8\u5f0f\u3092\u30af\u30ea\u30a2", +"Align left": "\u5de6\u5bc4\u305b", +"Align center": "\u4e2d\u592e\u63c3\u3048", +"Align right": "\u53f3\u5bc4\u305b", +"Justify": "\u4e21\u7aef\u63c3\u3048", +"Bullet list": "\u7b87\u6761\u66f8\u304d", +"Numbered list": "\u756a\u53f7\u4ed8\u304d\u7b87\u6761\u66f8\u304d", +"Decrease indent": "\u30a4\u30f3\u30c7\u30f3\u30c8\u3092\u6e1b\u3089\u3059", +"Increase indent": "\u30a4\u30f3\u30c7\u30f3\u30c8\u3092\u5897\u3084\u3059", +"Close": "\u9589\u3058\u308b", +"Formats": "\u66f8\u5f0f", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "\u304a\u4f7f\u3044\u306e\u30d6\u30e9\u30a6\u30b6\u3067\u306f\u30af\u30ea\u30c3\u30d7\u30dc\u30fc\u30c9\u6a5f\u80fd\u3092\u5229\u7528\u3059\u308b\u3053\u3068\u304c\u3067\u304d\u307e\u305b\u3093\u3002\u30ad\u30fc\u30dc\u30fc\u30c9\u306e\u30b7\u30e7\u30fc\u30c8\u30ab\u30c3\u30c8\uff08Ctrl+X, Ctrl+C, Ctrl+V\uff09\u3092\u304a\u4f7f\u3044\u4e0b\u3055\u3044\u3002", +"Headers": "\u30d8\u30c3\u30c0\u30fc", +"Header 1": "\u30d8\u30c3\u30c0\u30fc 1", +"Header 2": "\u30d8\u30c3\u30c0\u30fc 2", +"Header 3": "\u30d8\u30c3\u30c0\u30fc 3", +"Header 4": "\u30d8\u30c3\u30c0\u30fc 4", +"Header 5": "\u30d8\u30c3\u30c0\u30fc 5", +"Header 6": "\u30d8\u30c3\u30c0\u30fc 6", +"Headings": "\u898b\u51fa\u3057", +"Heading 1": "\u898b\u51fa\u3057 1", +"Heading 2": "\u898b\u51fa\u3057 2", +"Heading 3": "\u898b\u51fa\u3057 3", +"Heading 4": "\u898b\u51fa\u3057 4", +"Heading 5": "\u898b\u51fa\u3057 5", +"Heading 6": "\u898b\u51fa\u3057 6", +"Preformatted": "Preformatted", +"Div": "Div", +"Pre": "Pre", +"Code": "\u30b3\u30fc\u30c9", +"Paragraph": "\u6bb5\u843d", +"Blockquote": "\u5f15\u7528", +"Inline": "\u30a4\u30f3\u30e9\u30a4\u30f3", +"Blocks": "\u30d6\u30ed\u30c3\u30af", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "\u8cbc\u308a\u4ed8\u3051\u306f\u73fe\u5728\u30d7\u30ec\u30fc\u30f3\u30c6\u30ad\u30b9\u30c8\u30e2\u30fc\u30c9\u3067\u3059\u3002\u3053\u306e\u30aa\u30d7\u30b7\u30e7\u30f3\u3092\u30aa\u30d5\u306b\u3057\u306a\u3044\u9650\u308a\u5185\u5bb9\u306f\u30d7\u30ec\u30fc\u30f3\u30c6\u30ad\u30b9\u30c8\u3068\u3057\u3066\u8cbc\u308a\u4ed8\u3051\u3089\u308c\u307e\u3059\u3002", +"Font Family": "\u30d5\u30a9\u30f3\u30c8\u30d5\u30a1\u30df\u30ea\u30fc", +"Font Sizes": "\u30d5\u30a9\u30f3\u30c8\u30b5\u30a4\u30ba", +"Class": "\u30af\u30e9\u30b9", +"Browse for an image": "\u30a4\u30e1\u30fc\u30b8\u3092\u53c2\u7167", +"OR": "\u307e\u305f\u306f", +"Drop an image here": "\u3053\u3053\u306b\u753b\u50cf\u3092\u30c9\u30ed\u30c3\u30d7", +"Upload": "\u30a2\u30c3\u30d7\u30ed\u30fc\u30c9", +"Block": "\u30d6\u30ed\u30c3\u30af", +"Align": "\u914d\u7f6e", "Default": "\u30c7\u30d5\u30a9\u30eb\u30c8", -"Lower Alpha": "\u5c0f\u6587\u5b57\u306e\u30a2\u30eb\u30d5\u30a1\u30d9\u30c3\u30c8", "Circle": "\u5186", "Disc": "\u70b9", +"Square": "\u56db\u89d2", +"Lower Alpha": "\u5c0f\u6587\u5b57\u306e\u30a2\u30eb\u30d5\u30a1\u30d9\u30c3\u30c8", +"Lower Greek": "\u5c0f\u6587\u5b57\u306e\u30ae\u30ea\u30b7\u30e3\u6587\u5b57", +"Lower Roman": "\u5c0f\u6587\u5b57\u306e\u30ed\u30fc\u30de\u6570\u5b57", "Upper Alpha": "\u5927\u6587\u5b57\u306e\u30a2\u30eb\u30d5\u30a1\u30d9\u30c3\u30c8", "Upper Roman": "\u5927\u6587\u5b57\u306e\u30ed\u30fc\u30de\u6570\u5b57", -"Lower Roman": "\u5c0f\u6587\u5b57\u306e\u30ed\u30fc\u30de\u6570\u5b57", -"Name": "\u30a2\u30f3\u30ab\u30fc\u540d", "Anchor": "\u30a2\u30f3\u30ab\u30fc\uff08\u30ea\u30f3\u30af\u306e\u5230\u9054\u70b9\uff09", +"Name": "\u30a2\u30f3\u30ab\u30fc\u540d", +"Id": "Id", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "ID\u306f\u6587\u5b57\u3067\u59cb\u307e\u308a\u3001\u6587\u5b57\u3001\u6570\u5b57\u3001\u30c0\u30c3\u30b7\u30e5\u3001\u30c9\u30c3\u30c8\u3001\u30b3\u30ed\u30f3\u307e\u305f\u306f\u30a2\u30f3\u30c0\u30fc\u30b9\u30b3\u30a2\u3067\u59cb\u307e\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002", "You have unsaved changes are you sure you want to navigate away?": "\u307e\u3060\u4fdd\u5b58\u3057\u3066\u3044\u306a\u3044\u5909\u66f4\u304c\u3042\u308a\u307e\u3059\u304c\u3001\u672c\u5f53\u306b\u3053\u306e\u30da\u30fc\u30b8\u3092\u96e2\u308c\u307e\u3059\u304b\uff1f", "Restore last draft": "\u524d\u56de\u306e\u4e0b\u66f8\u304d\u3092\u5fa9\u6d3b\u3055\u305b\u308b", "Special character": "\u7279\u6b8a\u6587\u5b57", "Source code": "\u30bd\u30fc\u30b9\u30b3\u30fc\u30c9", -"B": "B", +"Insert\/Edit code sample": "\u30b3\u30fc\u30c9\u30b5\u30f3\u30d7\u30eb\u306e\u633f\u5165\u30fb\u7de8\u96c6", +"Language": "\u8a00\u8a9e", +"Code sample": "\u30b3\u30fc\u30c9\u30b5\u30f3\u30d7\u30eb", +"Color": "\u30ab\u30e9\u30fc", "R": "R", "G": "G", -"Color": "\u30ab\u30e9\u30fc", -"Right to left": "\u53f3\u304b\u3089\u5de6", +"B": "B", "Left to right": "\u5de6\u304b\u3089\u53f3", +"Right to left": "\u53f3\u304b\u3089\u5de6", "Emoticons": "\u7d75\u6587\u5b57", -"Robots": "\u30ed\u30dc\u30c3\u30c4", "Document properties": "\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8\u306e\u30d7\u30ed\u30d1\u30c6\u30a3", "Title": "\u30bf\u30a4\u30c8\u30eb", "Keywords": "\u30ad\u30fc\u30ef\u30fc\u30c9", -"Encoding": "\u30a8\u30f3\u30b3\u30fc\u30c7\u30a3\u30f3\u30b0", "Description": "\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8\u306e\u5185\u5bb9", +"Robots": "\u30ed\u30dc\u30c3\u30c4", "Author": "\u8457\u8005", +"Encoding": "\u30a8\u30f3\u30b3\u30fc\u30c7\u30a3\u30f3\u30b0", "Fullscreen": "\u5168\u753b\u9762\u8868\u793a", +"Action": "\u30a2\u30af\u30b7\u30e7\u30f3", +"Shortcut": "\u30b7\u30e7\u30fc\u30c8\u30ab\u30c3\u30c8", +"Help": "\u30d8\u30eb\u30d7", +"Address": "\u30a2\u30c9\u30ec\u30b9", +"Focus to menubar": "\u30e1\u30cb\u30e5\u30fc\u30d0\u30fc\u306b\u30d5\u30a9\u30fc\u30ab\u30b9", +"Focus to toolbar": "\u30c4\u30fc\u30eb\u30d0\u30fc\u306b\u30d5\u30a9\u30fc\u30ab\u30b9", +"Focus to element path": "\u8981\u7d20\u30d1\u30b9\u306b\u30d5\u30a9\u30fc\u30ab\u30b9", +"Focus to contextual toolbar": "\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u30c4\u30fc\u30eb\u30d0\u30fc\u306b\u30d5\u30a9\u30fc\u30ab\u30b9", +"Insert link (if link plugin activated)": "\u30ea\u30f3\u30af\u3092\u633f\u5165 (\u30ea\u30f3\u30af\u30d7\u30e9\u30b0\u30a4\u30f3\u6709\u52b9\u6642)", +"Save (if save plugin activated)": "\u4fdd\u5b58 (\u4fdd\u5b58\u30d7\u30e9\u30b0\u30a4\u30f3\u6709\u52b9\u6642)", +"Find (if searchreplace plugin activated)": "\u691c\u7d22(\u7f6e\u63db\u30d7\u30e9\u30b0\u30a4\u30f3\u6709\u52b9\u6642)", +"Plugins installed ({0}):": "\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u6e08\u30d7\u30e9\u30b0\u30a4\u30f3 ({0}):", +"Premium plugins:": "\u30d7\u30ec\u30df\u30a2\u30e0\u30d7\u30e9\u30b0\u30a4\u30f3:", +"Learn more...": "\u8a73\u7d30...", +"You are using {0}": "\u3042\u306a\u305f\u306f {0} \u4f7f\u7528\u4e2d", +"Plugins": "\u30d7\u30e9\u30b0\u30a4\u30f3", +"Handy Shortcuts": "\u4fbf\u5229\u306a\u30b7\u30e7\u30fc\u30c8\u30ab\u30c3\u30c8", "Horizontal line": "\u6c34\u5e73\u7f6b\u7dda", -"Horizontal space": "\u6a2a\u65b9\u5411\u306e\u4f59\u767d", "Insert\/edit image": "\u753b\u50cf\u306e\u633f\u5165\u30fb\u7de8\u96c6", +"Image description": "\u753b\u50cf\u306e\u8aac\u660e\u6587", +"Source": "\u753b\u50cf\u306e\u30bd\u30fc\u30b9", +"Dimensions": "\u753b\u50cf\u30b5\u30a4\u30ba\uff08\u6a2a\u30fb\u7e26\uff09", +"Constrain proportions": "\u7e26\u6a2a\u6bd4\u3092\u4fdd\u6301\u3059\u308b", "General": "\u4e00\u822c", "Advanced": "\u8a73\u7d30\u8a2d\u5b9a", -"Source": "\u753b\u50cf\u306e\u30bd\u30fc\u30b9", -"Border": "\u67a0\u7dda", -"Constrain proportions": "\u7e26\u6a2a\u6bd4\u3092\u4fdd\u6301\u3059\u308b", -"Vertical space": "\u7e26\u65b9\u5411\u306e\u4f59\u767d", -"Image description": "\u753b\u50cf\u306e\u8aac\u660e\u6587", "Style": "\u30b9\u30bf\u30a4\u30eb", -"Dimensions": "\u753b\u50cf\u30b5\u30a4\u30ba\uff08\u6a2a\u30fb\u7e26\uff09", +"Vertical space": "\u7e26\u65b9\u5411\u306e\u4f59\u767d", +"Horizontal space": "\u6a2a\u65b9\u5411\u306e\u4f59\u767d", +"Border": "\u67a0\u7dda", "Insert image": "\u753b\u50cf\u306e\u633f\u5165", -"Zoom in": "\u30ba\u30fc\u30e0\u30a4\u30f3", -"Contrast": "\u30b3\u30f3\u30c8\u30e9\u30b9\u30c8", -"Back": "\u623b\u308b", -"Gamma": "\u30ac\u30f3\u30de", -"Flip horizontally": "\u6c34\u5e73\u306b\u53cd\u8ee2", -"Resize": "\u30ea\u30b5\u30a4\u30ba", -"Sharpen": "\u30b7\u30e3\u30fc\u30d7\u5316", -"Zoom out": "\u30ba\u30fc\u30e0\u30a2\u30a6\u30c8", -"Image options": "\u753b\u50cf\u30aa\u30d7\u30b7\u30e7\u30f3", -"Apply": "\u9069\u7528", -"Brightness": "\u660e\u308b\u3055", -"Rotate clockwise": "\u6642\u8a08\u56de\u308a\u306b\u56de\u8ee2", +"Image": "\u753b\u50cf", +"Image list": "\u753b\u50cf\u4e00\u89a7", "Rotate counterclockwise": "\u53cd\u6642\u8a08\u56de\u308a\u306b\u56de\u8ee2", -"Edit image": "\u753b\u50cf\u306e\u7de8\u96c6", -"Color levels": "\u30ab\u30e9\u30fc\u30ec\u30d9\u30eb", -"Crop": "\u30af\u30ed\u30c3\u30d7", -"Orientation": "\u5411\u304d", +"Rotate clockwise": "\u6642\u8a08\u56de\u308a\u306b\u56de\u8ee2", "Flip vertically": "\u4e0a\u4e0b\u306b\u53cd\u8ee2", +"Flip horizontally": "\u6c34\u5e73\u306b\u53cd\u8ee2", +"Edit image": "\u753b\u50cf\u306e\u7de8\u96c6", +"Image options": "\u753b\u50cf\u30aa\u30d7\u30b7\u30e7\u30f3", +"Zoom in": "\u30ba\u30fc\u30e0\u30a4\u30f3", +"Zoom out": "\u30ba\u30fc\u30e0\u30a2\u30a6\u30c8", +"Crop": "\u30af\u30ed\u30c3\u30d7", +"Resize": "\u30ea\u30b5\u30a4\u30ba", +"Orientation": "\u5411\u304d", +"Brightness": "\u660e\u308b\u3055", +"Sharpen": "\u30b7\u30e3\u30fc\u30d7\u5316", +"Contrast": "\u30b3\u30f3\u30c8\u30e9\u30b9\u30c8", +"Color levels": "\u30ab\u30e9\u30fc\u30ec\u30d9\u30eb", +"Gamma": "\u30ac\u30f3\u30de", "Invert": "\u53cd\u8ee2", +"Apply": "\u9069\u7528", +"Back": "\u623b\u308b", "Insert date\/time": "\u65e5\u4ed8\u30fb\u6642\u523b", -"Remove link": "\u30ea\u30f3\u30af\u306e\u524a\u9664", -"Url": "\u30ea\u30f3\u30af\u5148URL", -"Text to display": "\u30ea\u30f3\u30af\u5143\u30c6\u30ad\u30b9\u30c8", -"Anchors": "\u30a2\u30f3\u30ab\u30fc\uff08\u30ea\u30f3\u30af\u306e\u5230\u9054\u70b9\uff09", +"Date\/time": "\u65e5\u4ed8\u30fb\u6642\u523b", "Insert link": "\u30ea\u30f3\u30af", -"New window": "\u65b0\u898f\u30a6\u30a3\u30f3\u30c9\u30a6", -"None": "\u306a\u3057", -"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "\u5165\u529b\u3055\u308c\u305fURL\u306f\u5916\u90e8\u30ea\u30f3\u30af\u306e\u3088\u3046\u3067\u3059\u3002\u300chttp:\/\/\u300d\u30d7\u30ec\u30d5\u30a3\u30c3\u30af\u30b9\u3092\u8ffd\u52a0\u3057\u307e\u3059\u304b\uff1f", -"Target": "\u30bf\u30fc\u30b2\u30c3\u30c8\u5c5e\u6027", -"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "\u5165\u529b\u3055\u308c\u305fURL\u306f\u30e1\u30fc\u30eb\u30a2\u30c9\u30ec\u30b9\u306e\u3088\u3046\u3067\u3059\u3002\u300cmailto:\u300d\u30d7\u30ec\u30d5\u30a3\u30c3\u30af\u30b9\u3092\u8ffd\u52a0\u3057\u307e\u3059\u304b\uff1f", "Insert\/edit link": "\u30ea\u30f3\u30af\u306e\u633f\u5165\u30fb\u7de8\u96c6", -"Insert\/edit video": "\u52d5\u753b\u306e\u633f\u5165\u30fb\u7de8\u96c6", -"Poster": "\u4ee3\u66ff\u753b\u50cf\u306e\u5834\u6240", -"Alternative source": "\u4ee3\u66ff\u52d5\u753b\u306e\u5834\u6240", -"Paste your embed code below:": "\u57cb\u3081\u8fbc\u307f\u7528\u30b3\u30fc\u30c9\u3092\u4e0b\u8a18\u306b\u8cbc\u308a\u4ed8\u3051\u3066\u304f\u3060\u3055\u3044\u3002", +"Text to display": "\u30ea\u30f3\u30af\u5143\u30c6\u30ad\u30b9\u30c8", +"Url": "\u30ea\u30f3\u30af\u5148URL", +"Target": "\u30bf\u30fc\u30b2\u30c3\u30c8\u5c5e\u6027", +"None": "\u306a\u3057", +"New window": "\u65b0\u898f\u30a6\u30a3\u30f3\u30c9\u30a6", +"Remove link": "\u30ea\u30f3\u30af\u306e\u524a\u9664", +"Anchors": "\u30a2\u30f3\u30ab\u30fc\uff08\u30ea\u30f3\u30af\u306e\u5230\u9054\u70b9\uff09", +"Link": "\u30ea\u30f3\u30af", +"Paste or type a link": "\u30ea\u30f3\u30af\u3092\u30da\u30fc\u30b9\u30c8\u307e\u305f\u306f\u5165\u529b", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "\u5165\u529b\u3055\u308c\u305fURL\u306f\u30e1\u30fc\u30eb\u30a2\u30c9\u30ec\u30b9\u306e\u3088\u3046\u3067\u3059\u3002\u300cmailto:\u300d\u30d7\u30ec\u30d5\u30a3\u30c3\u30af\u30b9\u3092\u8ffd\u52a0\u3057\u307e\u3059\u304b\uff1f", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "\u5165\u529b\u3055\u308c\u305fURL\u306f\u5916\u90e8\u30ea\u30f3\u30af\u306e\u3088\u3046\u3067\u3059\u3002\u300chttp:\/\/\u300d\u30d7\u30ec\u30d5\u30a3\u30c3\u30af\u30b9\u3092\u8ffd\u52a0\u3057\u307e\u3059\u304b\uff1f", +"Link list": "\u30ea\u30f3\u30af\u4e00\u89a7", "Insert video": "\u52d5\u753b", +"Insert\/edit video": "\u52d5\u753b\u306e\u633f\u5165\u30fb\u7de8\u96c6", +"Insert\/edit media": "\u30e1\u30c7\u30a3\u30a2\u306e\u633f\u5165\u30fb\u7de8\u96c6", +"Alternative source": "\u4ee3\u66ff\u52d5\u753b\u306e\u5834\u6240", +"Poster": "\u4ee3\u66ff\u753b\u50cf\u306e\u5834\u6240", +"Paste your embed code below:": "\u57cb\u3081\u8fbc\u307f\u7528\u30b3\u30fc\u30c9\u3092\u4e0b\u8a18\u306b\u8cbc\u308a\u4ed8\u3051\u3066\u304f\u3060\u3055\u3044\u3002", "Embed": "\u57cb\u3081\u8fbc\u307f", +"Media": "\u30e1\u30c7\u30a3\u30a2", "Nonbreaking space": "\u56fa\u5b9a\u30b9\u30da\u30fc\u30b9\uff08 \uff09", "Page break": "\u30da\u30fc\u30b8\u533a\u5207\u308a", "Paste as text": "\u30c6\u30ad\u30b9\u30c8\u3068\u3057\u3066\u8cbc\u308a\u4ed8\u3051", "Preview": "\u30d7\u30ec\u30d3\u30e5\u30fc", "Print": "\u5370\u5237", "Save": "\u4fdd\u5b58", -"Could not find the specified string.": "\u304a\u63a2\u3057\u306e\u6587\u5b57\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3067\u3057\u305f\u3002", -"Replace": "\u7f6e\u304d\u63db\u3048", -"Next": "\u6b21", -"Whole words": "\u5358\u8a9e\u5358\u4f4d\u3067\u691c\u7d22\u3059\u308b", -"Find and replace": "\u691c\u7d22\u3068\u7f6e\u304d\u63db\u3048", -"Replace with": "\u7f6e\u304d\u63db\u3048\u308b\u6587\u5b57", "Find": "\u691c\u7d22", +"Replace with": "\u7f6e\u304d\u63db\u3048\u308b\u6587\u5b57", +"Replace": "\u7f6e\u304d\u63db\u3048", "Replace all": "\u5168\u3066\u3092\u7f6e\u304d\u63db\u3048\u308b", -"Match case": "\u5927\u6587\u5b57\u30fb\u5c0f\u6587\u5b57\u3092\u533a\u5225\u3059\u308b", "Prev": "\u524d", +"Next": "\u6b21", +"Find and replace": "\u691c\u7d22\u3068\u7f6e\u304d\u63db\u3048", +"Could not find the specified string.": "\u304a\u63a2\u3057\u306e\u6587\u5b57\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3067\u3057\u305f\u3002", +"Match case": "\u5927\u6587\u5b57\u30fb\u5c0f\u6587\u5b57\u3092\u533a\u5225\u3059\u308b", +"Whole words": "\u5358\u8a9e\u5358\u4f4d\u3067\u691c\u7d22\u3059\u308b", "Spellcheck": "\u30b9\u30da\u30eb\u30c1\u30a7\u30c3\u30af", -"Finish": "\u7d42\u4e86", -"Ignore all": "\u5168\u3066\u3092\u7121\u8996", "Ignore": "\u7121\u8996", +"Ignore all": "\u5168\u3066\u3092\u7121\u8996", +"Finish": "\u7d42\u4e86", "Add to Dictionary": "\u8f9e\u66f8\u306b\u8ffd\u52a0", -"Insert row before": "\u4e0a\u5074\u306b\u884c\u3092\u633f\u5165", -"Rows": "\u884c\u6570", -"Height": "\u9ad8\u3055", -"Paste row after": "\u4e0b\u5074\u306b\u884c\u3092\u8cbc\u308a\u4ed8\u3051", -"Alignment": "\u914d\u7f6e", -"Border color": "\u67a0\u7dda\u306e\u8272", -"Column group": "\u5217\u30b0\u30eb\u30fc\u30d7", -"Row": "\u884c", -"Insert column before": "\u5de6\u5074\u306b\u5217\u3092\u633f\u5165", -"Split cell": "\u30bb\u30eb\u306e\u5206\u5272", -"Cell padding": "\u30bb\u30eb\u5185\u4f59\u767d\uff08\u30d1\u30c7\u30a3\u30f3\u30b0\uff09", -"Cell spacing": "\u30bb\u30eb\u306e\u9593\u9694", -"Row type": "\u884c\u30bf\u30a4\u30d7", "Insert table": "\u8868\u306e\u633f\u5165", -"Body": "\u30dc\u30c7\u30a3\u30fc", -"Caption": "\u8868\u984c", -"Footer": "\u30d5\u30c3\u30bf\u30fc", -"Delete row": "\u884c\u306e\u524a\u9664", -"Paste row before": "\u4e0a\u5074\u306b\u884c\u3092\u8cbc\u308a\u4ed8\u3051", -"Scope": "\u30b9\u30b3\u30fc\u30d7", -"Delete table": "\u8868\u306e\u524a\u9664", -"H Align": "\u6c34\u5e73\u65b9\u5411\u306e\u914d\u7f6e", -"Top": "\u4e0a", -"Header cell": "\u30d8\u30c3\u30c0\u30fc\u30bb\u30eb", -"Column": "\u5217", -"Row group": "\u884c\u30b0\u30eb\u30fc\u30d7", -"Cell": "\u30bb\u30eb", -"Middle": "\u4e2d\u592e", -"Cell type": "\u30bb\u30eb\u30bf\u30a4\u30d7", -"Copy row": "\u884c\u306e\u30b3\u30d4\u30fc", -"Row properties": "\u884c\u306e\u8a73\u7d30\u8a2d\u5b9a", "Table properties": "\u8868\u306e\u8a73\u7d30\u8a2d\u5b9a", -"Bottom": "\u4e0b", -"V Align": "\u5782\u76f4\u65b9\u5411\u306e\u914d\u7f6e", -"Header": "\u30d8\u30c3\u30c0\u30fc", -"Right": "\u53f3\u5bc4\u305b", -"Insert column after": "\u53f3\u5074\u306b\u5217\u3092\u633f\u5165", -"Cols": "\u5217\u6570", -"Insert row after": "\u4e0b\u5074\u306b\u884c\u3092\u633f\u5165", -"Width": "\u5e45", +"Delete table": "\u8868\u306e\u524a\u9664", +"Cell": "\u30bb\u30eb", +"Row": "\u884c", +"Column": "\u5217", "Cell properties": "\u30bb\u30eb\u306e\u8a73\u7d30\u8a2d\u5b9a", -"Left": "\u5de6\u5bc4\u305b", -"Cut row": "\u884c\u306e\u5207\u308a\u53d6\u308a", -"Delete column": "\u5217\u306e\u524a\u9664", -"Center": "\u4e2d\u592e\u63c3\u3048", "Merge cells": "\u30bb\u30eb\u306e\u7d50\u5408", +"Split cell": "\u30bb\u30eb\u306e\u5206\u5272", +"Insert row before": "\u4e0a\u5074\u306b\u884c\u3092\u633f\u5165", +"Insert row after": "\u4e0b\u5074\u306b\u884c\u3092\u633f\u5165", +"Delete row": "\u884c\u306e\u524a\u9664", +"Row properties": "\u884c\u306e\u8a73\u7d30\u8a2d\u5b9a", +"Cut row": "\u884c\u306e\u5207\u308a\u53d6\u308a", +"Copy row": "\u884c\u306e\u30b3\u30d4\u30fc", +"Paste row before": "\u4e0a\u5074\u306b\u884c\u3092\u8cbc\u308a\u4ed8\u3051", +"Paste row after": "\u4e0b\u5074\u306b\u884c\u3092\u8cbc\u308a\u4ed8\u3051", +"Insert column before": "\u5de6\u5074\u306b\u5217\u3092\u633f\u5165", +"Insert column after": "\u53f3\u5074\u306b\u5217\u3092\u633f\u5165", +"Delete column": "\u5217\u306e\u524a\u9664", +"Cols": "\u5217\u6570", +"Rows": "\u884c\u6570", +"Width": "\u5e45", +"Height": "\u9ad8\u3055", +"Cell spacing": "\u30bb\u30eb\u306e\u9593\u9694", +"Cell padding": "\u30bb\u30eb\u5185\u4f59\u767d\uff08\u30d1\u30c7\u30a3\u30f3\u30b0\uff09", +"Caption": "\u8868\u984c", +"Left": "\u5de6\u5bc4\u305b", +"Center": "\u4e2d\u592e\u63c3\u3048", +"Right": "\u53f3\u5bc4\u305b", +"Cell type": "\u30bb\u30eb\u30bf\u30a4\u30d7", +"Scope": "\u30b9\u30b3\u30fc\u30d7", +"Alignment": "\u914d\u7f6e", +"H Align": "\u6c34\u5e73\u65b9\u5411\u306e\u914d\u7f6e", +"V Align": "\u5782\u76f4\u65b9\u5411\u306e\u914d\u7f6e", +"Top": "\u4e0a", +"Middle": "\u4e2d\u592e", +"Bottom": "\u4e0b", +"Header cell": "\u30d8\u30c3\u30c0\u30fc\u30bb\u30eb", +"Row group": "\u884c\u30b0\u30eb\u30fc\u30d7", +"Column group": "\u5217\u30b0\u30eb\u30fc\u30d7", +"Row type": "\u884c\u30bf\u30a4\u30d7", +"Header": "\u30d8\u30c3\u30c0\u30fc", +"Body": "\u30dc\u30c7\u30a3\u30fc", +"Footer": "\u30d5\u30c3\u30bf\u30fc", +"Border color": "\u67a0\u7dda\u306e\u8272", "Insert template": "\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8\u306e\u633f\u5165", "Templates": "\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8\u540d", +"Template": "\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8", +"Text color": "\u30c6\u30ad\u30b9\u30c8\u306e\u8272", "Background color": "\u80cc\u666f\u8272", "Custom...": "\u30ab\u30b9\u30bf\u30e0...", "Custom color": "\u30ab\u30b9\u30bf\u30e0\u30ab\u30e9\u30fc", "No color": "\u30ab\u30e9\u30fc\u306a\u3057", -"Text color": "\u30c6\u30ad\u30b9\u30c8\u306e\u8272", +"Table of Contents": "\u76ee\u6b21", "Show blocks": "\u6587\u7ae0\u306e\u533a\u5207\u308a\u3092\u70b9\u7dda\u3067\u8868\u793a", "Show invisible characters": "\u4e0d\u53ef\u8996\u6587\u5b57\u3092\u8868\u793a", "Words: {0}": "\u5358\u8a9e\u6570: {0}", -"Insert": "\u633f\u5165", +"{0} words": "{0} \u30ef\u30fc\u30c9", "File": "\u30d5\u30a1\u30a4\u30eb", "Edit": "\u7de8\u96c6", -"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "\u66f8\u5f0f\u4ed8\u304d\u30c6\u30ad\u30b9\u30c8\u306e\u7de8\u96c6\u753b\u9762\u3002ALT-F9\u3067\u30e1\u30cb\u30e5\u30fc\u3001ALT-F10\u3067\u30c4\u30fc\u30eb\u30d0\u30fc\u3001ALT-0\u3067\u30d8\u30eb\u30d7\u304c\u8868\u793a\u3055\u308c\u307e\u3059\u3002", -"Tools": "\u30c4\u30fc\u30eb", +"Insert": "\u633f\u5165", "View": "\u8868\u793a", +"Format": "\u66f8\u5f0f", "Table": "\u8868", -"Format": "\u66f8\u5f0f" +"Tools": "\u30c4\u30fc\u30eb", +"Powered by {0}": "Powered by {0}", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "\u66f8\u5f0f\u4ed8\u304d\u30c6\u30ad\u30b9\u30c8\u306e\u7de8\u96c6\u753b\u9762\u3002ALT-F9\u3067\u30e1\u30cb\u30e5\u30fc\u3001ALT-F10\u3067\u30c4\u30fc\u30eb\u30d0\u30fc\u3001ALT-0\u3067\u30d8\u30eb\u30d7\u304c\u8868\u793a\u3055\u308c\u307e\u3059\u3002" }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/ka_GE.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/ka_GE.js new file mode 100644 index 0000000000..9bffb8040d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/ka_GE.js @@ -0,0 +1,230 @@ +tinymce.addI18n('ka_GE',{ +"Cut": "\u10d0\u10db\u10dd\u10ed\u10e0\u10d0", +"Heading 5": "\u10e1\u10d0\u10d7\u10d0\u10e3\u10e0\u10d8 5", +"Header 2": "\u10e1\u10d0\u10d7\u10d0\u10e3\u10e0\u10d8 2", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "\u10d7\u10e5\u10d5\u10d4\u10dc \u10d1\u10e0\u10d0\u10e3\u10d6\u10d4\u10e0\u10e1 \u10d0\u10e0 \u10d0\u10e5\u10d5\u10e1 \u10d1\u10e3\u10e4\u10e0\u10e2\u10e8\u10d8 \u10e8\u10d4\u10ee\u10ec\u10d4\u10d5\u10d8\u10e1 \u10db\u10ee\u10d0\u10e0\u10d3\u10d0\u10ed\u10d4\u10e0\u10d0. \u10d2\u10d7\u10ee\u10dd\u10d5\u10d7 \u10e1\u10d0\u10dc\u10d0\u10ea\u10d5\u10da\u10dd\u10d3 \u10d8\u10e1\u10d0\u10e0\u10d2\u10d4\u10d1\u10da\u10dd\u10d7 Ctrl+X\/C\/V \u10db\u10d0\u10da\u10e1\u10d0\u10ee\u10db\u10dd\u10d1\u10d8 \u10d9\u10dd\u10db\u10d1\u10d8\u10dc\u10d0\u10ea\u10d8\u10d4\u10d1\u10d8\u10d7.", +"Heading 4": "\u10e1\u10d0\u10d7\u10d0\u10e3\u10e0\u10d8 4", +"Div": "\u10d2\u10d0\u10dc\u10d0\u10ec\u10d8\u10da\u10d4\u10d1\u10d0", +"Heading 2": "\u10e1\u10d0\u10d7\u10d0\u10e3\u10e0\u10d8 2", +"Paste": "\u10e9\u10d0\u10e1\u10db\u10d0", +"Close": "\u10d3\u10d0\u10ee\u10e3\u10e0\u10d5\u10d0", +"Font Family": "\u10e4\u10dd\u10dc\u10e2\u10d8", +"Pre": "\u10de\u10e0\u10d4\u10e4\u10dd\u10e0\u10db\u10d0\u10e2\u10d8", +"Align right": "\u10d2\u10d0\u10d0\u10e1\u10ec\u10dd\u10e0\u10d4 \u10db\u10d0\u10e0\u10ef\u10d5\u10dc\u10d8\u10d5", +"New document": "\u10d0\u10ee\u10d0\u10da\u10d8 \u10d3\u10dd\u10d9\u10e3\u10db\u10d4\u10dc\u10e2\u10d8", +"Blockquote": "\u10d1\u10da\u10dd\u10d9\u10d8\u10e0\u10d4\u10d1\u10e3\u10da\u10d8 \u10ea\u10d8\u10e2\u10d0\u10e2\u10d0", +"Numbered list": "\u10d3\u10d0\u10dc\u10dd\u10db\u10e0\u10d8\u10da\u10d8 \u10e1\u10d8\u10d0", +"Heading 1": "\u10e1\u10d0\u10d7\u10d0\u10e3\u10e0\u10d8 1", +"Headings": "\u10e1\u10d0\u10d7\u10d0\u10e3\u10e0\u10d8", +"Increase indent": "\u10d0\u10d1\u10d6\u10d0\u10ea\u10d8\u10e1 \u10d2\u10d0\u10d6\u10e0\u10d3\u10d0", +"Formats": "\u10e4\u10dd\u10e0\u10db\u10d0\u10e2\u10d4\u10d1\u10d8", +"Headers": "\u10e1\u10d0\u10d7\u10d0\u10e3\u10e0\u10d4\u10d1\u10d8", +"Select all": "\u10e7\u10d5\u10d4\u10da\u10d0\u10e1 \u10db\u10dd\u10e6\u10dc\u10d8\u10e8\u10d5\u10dc\u10d0", +"Header 3": "\u10e1\u10d0\u10d7\u10d0\u10e3\u10e0\u10d8 3", +"Blocks": "\u10d1\u10da\u10dd\u10d9\u10d4\u10d1\u10d8", +"Undo": "\u10d3\u10d0\u10d1\u10e0\u10e3\u10dc\u10d4\u10d1\u10d0", +"Strikethrough": "\u10e8\u10e3\u10d0 \u10ee\u10d0\u10d6\u10d8", +"Bullet list": "\u10d1\u10e3\u10da\u10d4\u10e2 \u10e1\u10d8\u10d0", +"Header 1": "\u10e1\u10d0\u10d7\u10d0\u10e3\u10e0\u10d8 1", +"Superscript": "\u10d6\u10d4\u10d3\u10d0 \u10d8\u10dc\u10d3\u10d4\u10e5\u10e1\u10d8", +"Clear formatting": "\u10e4\u10dd\u10e0\u10db\u10d0\u10e2\u10d8\u10e0\u10d4\u10d1\u10d8\u10e1 \u10d2\u10d0\u10e1\u10e3\u10e4\u10d7\u10d0\u10d5\u10d4\u10d1\u10d0", +"Font Sizes": "\u10e4\u10dd\u10dc\u10e2\u10d8\u10e1 \u10d6\u10dd\u10db\u10d0", +"Subscript": "\u10e5\u10d5\u10d4\u10d3\u10d0 \u10d8\u10dc\u10d3\u10d4\u10e5\u10e1\u10d8", +"Header 6": "\u10e1\u10d0\u10d7\u10d0\u10e3\u10e0\u10d8 6", +"Redo": "\u10d2\u10d0\u10db\u10d4\u10dd\u10e0\u10d4\u10d1\u10d0", +"Paragraph": "\u10de\u10d0\u10e0\u10d0\u10d2\u10e0\u10d0\u10e4\u10d8", +"Ok": "\u10d9\u10d0\u10e0\u10d2\u10d8", +"Bold": "\u10db\u10d9\u10d5\u10d4\u10d7\u10e0\u10d8", +"Code": "\u10d9\u10dd\u10d3\u10d8", +"Italic": "\u10d3\u10d0\u10ee\u10e0\u10d8\u10da\u10d8", +"Align center": "\u10d2\u10d0\u10d0\u10e1\u10ec\u10dd\u10e0\u10d4 \u10ea\u10d4\u10dc\u10e2\u10e0\u10e8\u10d8", +"Header 5": "\u10e1\u10d0\u10d7\u10d0\u10e3\u10e0\u10d8 5", +"Heading 6": "\u10e1\u10d0\u10d7\u10d0\u10e3\u10e0\u10d8 6", +"Heading 3": "\u10e1\u10d0\u10d7\u10d0\u10e3\u10e0\u10d8 3", +"Decrease indent": "\u10d0\u10d1\u10d6\u10d0\u10ea\u10d8\u10e1 \u10e8\u10d4\u10db\u10ea\u10d8\u10e0\u10d4\u10d1\u10d0", +"Header 4": "\u10e1\u10d0\u10d7\u10d0\u10e3\u10e0\u10d8 4", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "\u10e2\u10d4\u10e5\u10e1\u10e2\u10d8\u10e1 \u10e9\u10d0\u10e1\u10db\u10d0 \u10e9\u10d5\u10d4\u10e3\u10da\u10d4\u10d1\u10e0\u10d8\u10d5 \u10e0\u10d4\u10df\u10d8\u10db\u10e8\u10d8\u10d0. \u10e2\u10d4\u10e5\u10e1\u10e2\u10d8 \u10e9\u10d0\u10d8\u10e1\u10db\u10d4\u10d5\u10d0 \u10e3\u10e4\u10dd\u10e0\u10db\u10d0\u10e2\u10dd\u10d7 \u10e1\u10d0\u10dc\u10d0\u10db \u10d0\u10db \u10d7\u10d5\u10d8\u10e1\u10d4\u10d1\u10d0\u10e1 \u10d0\u10e0 \u10d2\u10d0\u10d7\u10d8\u10e8\u10d0\u10d5\u10d7.", +"Underline": "\u10e5\u10d5\u10d4\u10d3\u10d0 \u10ee\u10d0\u10d6\u10d8", +"Cancel": "\u10d2\u10d0\u10e3\u10e5\u10db\u10d4\u10d1\u10d0", +"Justify": "\u10d2\u10d0\u10db\u10d0\u10e0\u10d7\u10e3\u10da\u10d8", +"Inline": "\u10ee\u10d0\u10d6\u10e8\u10d8\u10d3\u10d0", +"Copy": "\u10d9\u10dd\u10de\u10d8\u10e0\u10d4\u10d1\u10d0", +"Align left": "\u10d2\u10d0\u10d0\u10e1\u10ec\u10dd\u10e0\u10d4 \u10db\u10d0\u10e0\u10ea\u10ee\u10dc\u10d8\u10d5", +"Visual aids": "\u10d5\u10d8\u10d6\u10e3\u10d0\u10da\u10d8\u10d6\u10d0\u10ea\u10d8\u10d0", +"Lower Greek": "\u10d3\u10d0\u10d1\u10d0\u10da\u10d8 \u10d1\u10d4\u10e0\u10eb\u10dc\u10e3\u10da\u10d8", +"Square": "\u10d9\u10d5\u10d0\u10d3\u10e0\u10d0\u10e2\u10d8", +"Default": "\u10e1\u10e2\u10d0\u10dc\u10d3\u10d0\u10e0\u10e2\u10e3\u10da\u10d8", +"Lower Alpha": "\u10d3\u10d0\u10d1\u10d0\u10da\u10d8 \u10d0\u10da\u10e4\u10d0", +"Circle": "\u10ec\u10e0\u10d4", +"Disc": "\u10d3\u10d8\u10e1\u10d9\u10d8", +"Upper Alpha": "\u10db\u10d0\u10e6\u10d0\u10da\u10d8 \u10d0\u10da\u10e4\u10d0", +"Upper Roman": "\u10db\u10d0\u10e6\u10d0\u10da\u10d8 \u10e0\u10dd\u10db\u10d0\u10e3\u10da\u10d8", +"Lower Roman": "\u10d3\u10d0\u10d1\u10d0\u10da\u10d8 \u10e0\u10dd\u10db\u10d0\u10e3\u10da\u10d8", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "id \u10e3\u10dc\u10d3\u10d0 \u10d8\u10ec\u10e7\u10d4\u10d1\u10dd\u10d3\u10d4\u10e1 \u10d0\u10e1\u10dd\u10d7\u10d8, \u10e0\u10dd\u10db\u10d4\u10da\u10e1\u10d0\u10ea \u10db\u10dd\u10e7\u10d5\u10d4\u10d1\u10d0 \u10db\u10ee\u10dd\u10da\u10dd\u10d3 \u10d0\u10e1\u10dd\u10d4\u10d1\u10d8, \u10ea\u10d8\u10e4\u10e0\u10d4\u10d1\u10d8, \u10e2\u10d8\u10e0\u10d4, \u10ec\u10d4\u10e0\u10e2\u10d8\u10da\u10d4\u10d1\u10d8, \u10dd\u10e0\u10d8 \u10ec\u10d4\u10e0\u10e2\u10d8\u10da\u10d8 \u10d0\u10dc \u10e5\u10d5\u10d4\u10d3\u10d0 \u10e2\u10d8\u10e0\u10d4. ", +"Name": "\u10e1\u10d0\u10ee\u10d4\u10da\u10d8", +"Anchor": "\u10e6\u10e3\u10d6\u10d0", +"Id": "id", +"You have unsaved changes are you sure you want to navigate away?": "\u10d7\u10e5\u10d5\u10d4\u10dc \u10d2\u10d0\u10e5\u10d5\u10d7 \u10e8\u10d4\u10e3\u10dc\u10d0\u10ee\u10d0\u10d5\u10d8 \u10e8\u10d4\u10e1\u10ec\u10dd\u10e0\u10d4\u10d1\u10d4\u10d1\u10d8, \u10d3\u10d0\u10e0\u10ec\u10db\u10e3\u10dc\u10d4\u10d1\u10e3\u10da\u10d8 \u10ee\u10d0\u10d7 \u10e0\u10dd\u10db \u10e1\u10ee\u10d5\u10d0\u10d2\u10d0\u10dc \u10d2\u10d0\u10d3\u10d0\u10e1\u10d5\u10da\u10d0 \u10d2\u10e1\u10e3\u10e0\u10d7?", +"Restore last draft": "\u10d1\u10dd\u10da\u10dd\u10e1 \u10e8\u10d4\u10dc\u10d0\u10ee\u10e3\u10da\u10d8\u10e1 \u10d0\u10e6\u10d3\u10d2\u10d4\u10dc\u10d0", +"Special character": "\u10e1\u10de\u10d4\u10ea\u10d8\u10d0\u10da\u10e3\u10e0\u10d8 \u10e1\u10d8\u10db\u10d1\u10dd\u10da\u10dd", +"Source code": "\u10ec\u10e7\u10d0\u10e0\u10dd\u10e1 \u10d9\u10dd\u10d3\u10d8", +"Language": "\u10d4\u10dc\u10d0", +"Insert\/Edit code sample": "\u10e9\u10d0\u10e1\u10d5\u10d8\/\u10e8\u10d4\u10d0\u10e1\u10ec\u10dd\u10e0\u10d4 \u10d9\u10dd\u10d3\u10d8\u10e1 \u10db\u10d0\u10d2\u10d0\u10da\u10d8\u10d7\u10d8", +"B": "\u10da", +"R": "\u10ec", +"G": "\u10db", +"Color": "\u10e4\u10d4\u10e0\u10d8", +"Right to left": "\u10db\u10d0\u10e0\u10ef\u10d5\u10dc\u10d8\u10d3\u10d0\u10dc \u10db\u10d0\u10e0\u10ea\u10ee\u10dc\u10d8\u10d5", +"Left to right": "\u10db\u10d0\u10e0\u10ea\u10ee\u10dc\u10d8\u10d3\u10d0\u10dc \u10db\u10d0\u10e0\u10ef\u10d5\u10dc\u10d8\u10d5", +"Emoticons": "\u10e1\u10db\u10d0\u10d8\u10da\u10d8\u10d9\u10d4\u10d1\u10d8", +"Robots": "\u10e0\u10dd\u10d1\u10dd\u10d4\u10d1\u10d8", +"Document properties": "\u10d3\u10dd\u10d9\u10e3\u10db\u10d4\u10dc\u10e2\u10d8\u10e1 \u10d7\u10d5\u10d8\u10e1\u10d4\u10d1\u10d4\u10d1\u10d8", +"Title": "\u10e1\u10d0\u10d7\u10d0\u10e3\u10e0\u10d8", +"Keywords": "\u10e1\u10d0\u10d9\u10d5\u10d0\u10dc\u10eb\u10dd \u10e1\u10d8\u10e2\u10e7\u10d5\u10d4\u10d1\u10d8", +"Encoding": "\u10d9\u10dd\u10d3\u10d8\u10e0\u10d4\u10d1\u10d0", +"Description": "\u10d0\u10ee\u10ec\u10d4\u10e0\u10d0", +"Author": "\u10d0\u10d5\u10e2\u10dd\u10e0\u10d8", +"Fullscreen": "\u10e1\u10d0\u10d5\u10e1\u10d4 \u10d4\u10d9\u10e0\u10d0\u10dc\u10d8", +"Horizontal line": "\u10f0\u10dd\u10e0\u10d8\u10d6\u10dd\u10dc\u10e2\u10d0\u10da\u10e3\u10e0\u10d8 \u10ee\u10d0\u10d6\u10d8", +"Horizontal space": "\u10f0\u10dd\u10e0\u10d8\u10d6\u10dd\u10dc\u10e2\u10d0\u10da\u10e3\u10e0\u10d8 \u10e1\u10d8\u10d5\u10e0\u10ea\u10d4", +"Insert\/edit image": "\u10e9\u10d0\u10e1\u10d5\u10d8\/\u10e8\u10d4\u10d0\u10e1\u10ec\u10dd\u10e0\u10d4 \u10e1\u10e3\u10e0\u10d0\u10d7\u10d8", +"General": "\u10db\u10d7\u10d0\u10d5\u10d0\u10e0\u10d8", +"Advanced": "\u10d3\u10d0\u10db\u10d0\u10e2\u10d4\u10d1\u10d8\u10d7\u10d8", +"Source": "\u10d1\u10db\u10e3\u10da\u10d8", +"Border": "\u10e1\u10d0\u10d6\u10e6\u10d5\u10d0\u10e0\u10d8", +"Constrain proportions": "\u10de\u10e0\u10dd\u10de\u10dd\u10e0\u10ea\u10d8\u10d8\u10e1 \u10d3\u10d0\u10ea\u10d5\u10d0", +"Vertical space": "\u10d5\u10d4\u10e0\u10e2\u10d8\u10d9\u10d0\u10da\u10e3\u10e0\u10d8 \u10e1\u10d8\u10d5\u10e0\u10ea\u10d4", +"Image description": "\u10e1\u10e3\u10e0\u10d0\u10d7\u10d8\u10e1 \u10d3\u10d0\u10ee\u10d0\u10e1\u10d8\u10d0\u10d7\u10d4\u10d1\u10d0", +"Style": "\u10e1\u10e2\u10d8\u10da\u10d8", +"Dimensions": "\u10d2\u10d0\u10dc\u10d6\u10dd\u10db\u10d8\u10da\u10d4\u10d1\u10d0", +"Insert image": "\u10e1\u10e3\u10e0\u10d0\u10d7\u10d8\u10e1 \u10e9\u10d0\u10e1\u10db\u10d0", +"Image": "\u10d2\u10d0\u10db\u10dd\u10e1\u10d0\u10ee\u10e3\u10da\u10d4\u10d1\u10d0", +"Zoom in": "\u10d2\u10d0\u10d3\u10d8\u10d3\u10d8\u10d4\u10d1\u10d0", +"Contrast": "\u10d9\u10dd\u10dc\u10e2\u10e0\u10d0\u10e1\u10e2\u10d8", +"Back": "\u10e3\u10d9\u10d0\u10dc", +"Gamma": "\u10d2\u10d0\u10db\u10d0", +"Flip horizontally": "\u10f0\u10dd\u10e0\u10d8\u10d6\u10dd\u10dc\u10e2\u10d0\u10da\u10e3\u10e0\u10d0\u10d3 \u10e8\u10d4\u10e2\u10e0\u10d8\u10d0\u10da\u10d4\u10d1\u10d0", +"Resize": "\u10d6\u10dd\u10db\u10d8\u10e1 \u10e8\u10d4\u10ea\u10d5\u10da\u10d0", +"Sharpen": "\u10d2\u10d0\u10da\u10d4\u10e1\u10d5\u10d0", +"Zoom out": "\u10d3\u10d0\u10de\u10d0\u10e2\u10d0\u10e0\u10d0\u10d5\u10d4\u10d1\u10d0", +"Image options": "\u10e1\u10e3\u10e0\u10d0\u10d7\u10d8\u10e1 \u10de\u10d0\u10e0\u10d0\u10db\u10d4\u10e2\u10e0\u10d4\u10d1\u10d8", +"Apply": "\u10db\u10d8\u10e6\u10d4\u10d1\u10d0", +"Brightness": "\u10e1\u10d8\u10d9\u10d0\u10e8\u10d9\u10d0\u10e8\u10d4", +"Rotate clockwise": "\u10e1\u10d0\u10d0\u10d7\u10d8\u10e1 \u10d8\u10e1\u10e0\u10d8\u10e1 \u10db\u10d8\u10db\u10d0\u10e0\u10d7\u10e3\u10da\u10d4\u10d1\u10d8\u10d7 \u10db\u10dd\u10d1\u10e0\u10e3\u10dc\u10d4\u10d1\u10d0", +"Rotate counterclockwise": "\u10e1\u10d0\u10d0\u10d7\u10d8\u10e1 \u10d8\u10e1\u10e0\u10d8\u10e1 \u10db\u10d8\u10db\u10d0\u10e0\u10d7\u10e3\u10da\u10d4\u10d1\u10d8\u10e1 \u10e1\u10d0\u10ec\u10d8\u10dc\u10d0\u10d0\u10e6\u10db\u10d3\u10d4\u10d2\u10dd\u10d2 \u10db\u10dd\u10d1\u10e0\u10e3\u10dc\u10d4\u10d1\u10d0", +"Edit image": "\u10e1\u10e3\u10e0\u10d0\u10d7\u10d8\u10e1 \u10e0\u10d4\u10d3\u10d0\u10e5\u10e2\u10d8\u10e0\u10d4\u10d1\u10d0", +"Color levels": "\u10e4\u10d4\u10e0\u10d8\u10e1 \u10d3\u10dd\u10dc\u10d4", +"Crop": "\u10db\u10dd\u10ed\u10e0\u10d0", +"Orientation": "\u10dd\u10e0\u10d8\u10d4\u10dc\u10e2\u10d0\u10ea\u10d8\u10d0", +"Flip vertically": "\u10d5\u10d4\u10e0\u10e2\u10d8\u10d9\u10d0\u10da\u10e3\u10e0\u10d0\u10d3 \u10d0\u10e2\u10e0\u10d8\u10d0\u10da\u10d4\u10d1\u10d0", +"Invert": "\u10e8\u10d4\u10d1\u10e0\u10e3\u10dc\u10d4\u10d1\u10d0", +"Date\/time": "\u10d7\u10d0\u10e0\u10d8\u10e6\u10d8\/\u10d3\u10e0\u10dd", +"Insert date\/time": "\u10d7\u10d0\u10e0\u10d8\u10e6\u10d8\/\u10d3\u10e0\u10dd\u10d8\u10e1 \u10e9\u10d0\u10e1\u10db\u10d0", +"Remove link": "\u10d1\u10db\u10e3\u10da\u10d8\u10e1 \u10ec\u10d0\u10e8\u10da\u10d0", +"Url": "Url", +"Text to display": "\u10e2\u10d4\u10e5\u10e1\u10e2\u10d8", +"Anchors": "\u10e6\u10e3\u10d6\u10d0", +"Insert link": "\u10d1\u10db\u10e3\u10da\u10d8\u10e1 \u10e9\u10d0\u10e1\u10db\u10d0", +"Link": "\u10d1\u10db\u10e3\u10da\u10d8", +"New window": "\u10d0\u10ee\u10d0\u10da \u10e4\u10d0\u10dc\u10ef\u10d0\u10e0\u10d0\u10e8\u10d8", +"None": "\u10d0\u10e0\u10ea\u10d4\u10e0\u10d7\u10d8", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "\u10d7\u10e5\u10d5\u10d4\u10dc\u10e1 \u10db\u10d8\u10d4\u10e0 \u10db\u10d8\u10d7\u10d8\u10d7\u10d4\u10d1\u10e3\u10da\u10d8 \u10db\u10d8\u10e1\u10d0\u10db\u10d0\u10e0\u10d7\u10d8 \u10ec\u10d0\u10e0\u10db\u10dd\u10d0\u10d3\u10d2\u10d4\u10dc\u10e1 \u10d2\u10d0\u10e0\u10d4 \u10d1\u10db\u10e3\u10da\u10e1. \u10d2\u10e1\u10e3\u10e0\u10d7, \u10e0\u10dd\u10db \u10db\u10d8\u10d5\u10d0\u10dc\u10d8\u10ed\u10dd http:\/\/ \u10e4\u10e0\u10d4\u10e4\u10d8\u10e5\u10e1\u10d8?", +"Paste or type a link": "\u10e9\u10d0\u10e1\u10d5\u10d8\u10d7 \u10d0\u10dc \u10e8\u10d4\u10d8\u10e7\u10d5\u10d0\u10dc\u10d4\u10d7 \u10d1\u10db\u10e3\u10da\u10d8", +"Target": "\u10d2\u10d0\u10ee\u10e1\u10dc\u10d0", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "\u10d7\u10e5\u10d5\u10d4\u10dc \u10db\u10d8\u10e3\u10d7\u10d8\u10d7\u10d4\u10d7 \u10d4\u10da-\u10e4\u10dd\u10e1\u10e2\u10d8\u10e1 \u10db\u10d8\u10e1\u10d0\u10db\u10d0\u10e0\u10d7\u10d8 \u10dc\u10d0\u10ea\u10d5\u10da\u10d0\u10d3 \u10d5\u10d4\u10d1-\u10d2\u10d5\u10d4\u10e0\u10d3\u10d8\u10e1\u10d0. \u10d2\u10e1\u10e3\u10e0\u10d7, \u10e0\u10dd\u10db \u10db\u10d8\u10d5\u10d0\u10dc\u10d8\u10ed\u10dd mailto: \u10e4\u10e0\u10d4\u10e4\u10d8\u10e5\u10e1\u10d8?", +"Insert\/edit link": "\u10d1\u10db\u10e3\u10da\u10d8\u10e1 \u10e9\u10d0\u10e1\u10db\u10d0\/\u10e0\u10d4\u10d3\u10d0\u10e5\u10e2\u10d8\u10e0\u10d4\u10d0", +"Insert\/edit video": "\u10d5\u10d8\u10d3\u10d4\u10dd\u10e1 \u10e9\u10d0\u10e1\u10db\u10d0\/\u10e0\u10d4\u10d3\u10d0\u10e5\u10e2\u10d8\u10e0\u10d4\u10d1\u10d0", +"Media": "\u10db\u10d4\u10d3\u10d8\u10d0", +"Alternative source": "\u10d0\u10da\u10e2\u10d4\u10e0\u10dc\u10d0\u10e2\u10d8\u10e3\u10da\u10d8 \u10ec\u10e7\u10d0\u10e0\u10dd", +"Paste your embed code below:": "\u10d0\u10e5 \u10e9\u10d0\u10e1\u10d5\u10d8\u10d7 \u10d7\u10e5\u10d5\u10d4\u10dc\u10d8 \u10d9\u10dd\u10d3\u10d8:", +"Insert video": "\u10d5\u10d8\u10d3\u10d4\u10dd\u10e1 \u10e9\u10d0\u10e1\u10db\u10d0", +"Poster": "\u10de\u10da\u10d0\u10d9\u10d0\u10e2\u10d8", +"Insert\/edit media": "\u10db\u10d4\u10d3\u10d8\u10d0\u10e1 \u10e9\u10d0\u10e1\u10db\u10d0\/\u10e0\u10d4\u10d3\u10d0\u10e5\u10e2\u10d8\u10e0\u10d4\u10d1\u10d0", +"Embed": "\u10e9\u10d0\u10e8\u10d4\u10dc\u10d4\u10d1\u10d0", +"Nonbreaking space": "\u10e3\u10ec\u10e7\u10d5\u10d4\u10e2\u10d8 \u10e1\u10d8\u10d5\u10e0\u10ea\u10d4", +"Page break": "\u10d2\u10d5\u10d4\u10e0\u10d3\u10d8\u10e1 \u10d2\u10d0\u10ec\u10e7\u10d5\u10d4\u10e2\u10d0", +"Paste as text": "\u10e9\u10d0\u10e1\u10d5\u10d8\u10d7 \u10e0\u10dd\u10d2\u10dd\u10e0\u10ea \u10e2\u10d4\u10e5\u10e1\u10e2\u10d8", +"Preview": "\u10ec\u10d8\u10dc\u10d0\u10e1\u10ec\u10d0\u10e0 \u10dc\u10d0\u10ee\u10d5\u10d0", +"Print": "\u10d0\u10db\u10dd\u10d1\u10d4\u10ed\u10d5\u10d3\u10d0", +"Save": "\u10e8\u10d4\u10dc\u10d0\u10ee\u10d5\u10d0", +"Could not find the specified string.": "\u10db\u10dd\u10ea\u10d4\u10db\u10e3\u10da\u10d8 \u10e9\u10d0\u10dc\u10d0\u10ec\u10d4\u10e0\u10d8 \u10d5\u10d4\u10e0 \u10db\u10dd\u10d8\u10eb\u10d4\u10d1\u10dc\u10d0.", +"Replace": "\u10e8\u10d4\u10e1\u10ec\u10dd\u10e0\u10d4\u10d1\u10d0", +"Next": "\u10e8\u10d4\u10db\u10d3\u10d4\u10d2\u10d8", +"Whole words": "\u10e1\u10e0\u10e3\u10da\u10d8 \u10e1\u10d8\u10e2\u10e7\u10d5\u10d4\u10d1\u10d8", +"Find and replace": "\u10db\u10dd\u10eb\u10d4\u10d1\u10dc\u10d4 \u10d3\u10d0 \u10e8\u10d4\u10d0\u10e1\u10ec\u10dd\u10e0\u10d4", +"Replace with": "\u10e8\u10d4\u10e1\u10d0\u10e1\u10ec\u10dd\u10e0\u10d4\u10d1\u10d4\u10da\u10d8 \u10e1\u10d8\u10e2\u10e7\u10d5\u10d0", +"Find": "\u10eb\u10d4\u10d1\u10dc\u10d0", +"Replace all": "\u10e7\u10d5\u10d4\u10da\u10d0\u10e1 \u10e8\u10d4\u10e1\u10ec\u10dd\u10e0\u10d4\u10d1\u10d0", +"Match case": "\u10d3\u10d0\u10d0\u10db\u10d7\u10ee\u10d5\u10d8\u10d4 \u10d0\u10e1\u10dd\u10d4\u10d1\u10d8\u10e1 \u10d6\u10dd\u10db\u10d0", +"Prev": "\u10ec\u10d8\u10dc\u10d0", +"Spellcheck": "\u10db\u10d0\u10e0\u10d7\u10da\u10ec\u10d4\u10e0\u10d8\u10e1 \u10e8\u10d4\u10db\u10dd\u10ec\u10db\u10d4\u10d1\u10d0", +"Finish": "\u10d3\u10d0\u10e1\u10d0\u10e1\u10e0\u10e3\u10da\u10d8", +"Ignore all": "\u10e7\u10d5\u10d4\u10da\u10d0\u10e1 \u10d8\u10d2\u10dc\u10dd\u10e0\u10d8\u10e0\u10d4\u10d1\u10d0", +"Ignore": "\u10d8\u10d2\u10dc\u10dd\u10e0\u10d8\u10e0\u10d4\u10d1\u10d0", +"Add to Dictionary": "\u10da\u10d4\u10e5\u10e1\u10d8\u10d9\u10dd\u10dc\u10e8\u10d8 \u10d3\u10d0\u10db\u10d0\u10e2\u10d4\u10d1\u10d0", +"Insert row before": "\u10e1\u10e2\u10e0\u10d8\u10e5\u10dd\u10dc\u10d8\u10e1 \u10d7\u10d0\u10d5\u10e8\u10d8 \u10d3\u10d0\u10db\u10d0\u10e2\u10d4\u10d1\u10d0", +"Rows": "\u10e1\u10e2\u10e0\u10d8\u10e5\u10dd\u10dc\u10d4\u10d1\u10d8", +"Height": "\u10e1\u10d8\u10db\u10d0\u10e6\u10da\u10d4", +"Paste row after": "\u10e1\u10e2\u10e0\u10d8\u10e5\u10dd\u10dc\u10d8\u10e1 \u10d1\u10dd\u10da\u10dd\u10e8\u10d8 \u10e9\u10d0\u10e1\u10db\u10d0", +"Alignment": "\u10e1\u10ec\u10dd\u10e0\u10d4\u10d1\u10d0", +"Border color": "\u10e1\u10d0\u10d6\u10d0\u10e0\u10d8\u10e1 \u10e4\u10d4\u10e0\u10d8", +"Column group": "\u10e1\u10d5\u10d4\u10e2\u10d8\u10e1 \u10ef\u10d2\u10e3\u10e4\u10d8", +"Row": "\u10e1\u10e2\u10e0\u10d8\u10e5\u10dd\u10dc\u10d8", +"Insert column before": "\u10e1\u10d5\u10d4\u10e2\u10d8\u10e1 \u10d7\u10d0\u10d5\u10e8\u10d8 \u10d3\u10d0\u10db\u10d0\u10e2\u10d4\u10d1\u10d0", +"Split cell": "\u10e3\u10ef\u10e0\u10d8\u10e1 \u10d2\u10d0\u10e7\u10dd\u10e4\u10d0", +"Cell padding": "\u10e3\u10ef\u10e0\u10d8\u10e1 \u10e4\u10d0\u10e0\u10d7\u10dd\u10d1\u10d8", +"Cell spacing": "\u10e3\u10ef\u10e0\u10d8\u10e1 \u10d3\u10d0\u10e8\u10dd\u10e0\u10d4\u10d1\u10d0", +"Row type": "\u10e1\u10e2\u10e0\u10d8\u10e5\u10dd\u10dc\u10d8\u10e1 \u10e2\u10d8\u10de\u10d8", +"Insert table": "\u10ea\u10ee\u10e0\u10d8\u10da\u10d8\u10e1 \u10e9\u10d0\u10e1\u10db\u10d0", +"Body": "\u10e2\u10d0\u10dc\u10d8", +"Caption": "\u10ec\u10d0\u10e0\u10ec\u10d4\u10e0\u10d0", +"Footer": "\u10eb\u10d8\u10e0\u10d8", +"Delete row": "\u10e1\u10e2\u10e0\u10d8\u10e5\u10dd\u10dc\u10d8\u10e1 \u10ec\u10d0\u10e8\u10da\u10d0", +"Paste row before": "\u10e1\u10e2\u10e0\u10d8\u10e5\u10dd\u10dc\u10d8\u10e1 \u10d7\u10d0\u10d5\u10e8\u10d8 \u10e9\u10d0\u10e1\u10db\u10d0", +"Scope": "\u10e9\u10d0\u10e0\u10e9\u10dd", +"Delete table": "\u10ea\u10ee\u10e0\u10d8\u10da\u10d8\u10e1 \u10ec\u10d0\u10e8\u10da\u10d0", +"H Align": "H \u10e9\u10d0\u10db\u10ec\u10d9\u10e0\u10d8\u10d5\u10d4\u10d1\u10d0", +"Top": "\u10db\u10d0\u10e6\u10da\u10d0", +"Header cell": "\u10d7\u10d0\u10d5\u10d8\u10e1 \u10e3\u10ef\u10e0\u10d0", +"Column": "\u10e1\u10d5\u10d4\u10e2\u10d8", +"Row group": "\u10e1\u10e2\u10e0\u10d8\u10e5\u10dd\u10dc\u10d8\u10e1 \u10ef\u10d2\u10e3\u10e4\u10d8", +"Cell": "\u10e3\u10ef\u10e0\u10d0", +"Middle": "\u10e8\u10e3\u10d0", +"Cell type": "\u10e3\u10ef\u10e0\u10d8\u10e1 \u10e2\u10d8\u10de\u10d8", +"Copy row": "\u10e1\u10e2\u10e0\u10d8\u10e5\u10dd\u10dc\u10d8\u10e1 \u10d9\u10dd\u10de\u10d8\u10e0\u10d4\u10d1\u10d0", +"Row properties": "\u10e1\u10e2\u10e0\u10d8\u10e5\u10dd\u10dc\u10d8\u10e1 \u10d7\u10d5\u10d8\u10e1\u10d4\u10d1\u10d4\u10d1\u10d8", +"Table properties": "\u10ea\u10ee\u10e0\u10d8\u10da\u10d8\u10e1 \u10d7\u10d5\u10d8\u10e1\u10d4\u10d1\u10d4\u10d1\u10d8", +"Bottom": "\u10e5\u10d5\u10d4\u10d3\u10d0", +"V Align": "V \u10e9\u10d0\u10db\u10ec\u10d9\u10e0\u10d8\u10d5\u10d4\u10d1\u10d0", +"Header": "\u10d7\u10d0\u10d5\u10d8", +"Right": "\u10db\u10d0\u10e0\u10ef\u10d5\u10dc\u10d8\u10d5", +"Insert column after": "\u10e1\u10d5\u10d4\u10e2\u10d8\u10e1 \u10d1\u10dd\u10da\u10dd\u10e8\u10d8 \u10d3\u10d0\u10db\u10d0\u10e2\u10d4\u10d1\u10d0", +"Cols": "\u10e1\u10d5\u10d4\u10e2\u10d4\u10d1\u10d8", +"Insert row after": "\u10e1\u10e2\u10e0\u10d8\u10e5\u10dd\u10dc\u10d8\u10e1 \u10d1\u10dd\u10da\u10dd\u10e8\u10d8 \u10d3\u10d0\u10db\u10d0\u10e2\u10d4\u10d1\u10d0", +"Width": "\u10e1\u10d8\u10d2\u10d0\u10dc\u10d4", +"Cell properties": "\u10e3\u10ef\u10e0\u10d8\u10e1 \u10d7\u10d5\u10d8\u10e1\u10d4\u10d1\u10d4\u10d1\u10d8", +"Left": "\u10db\u10d0\u10e0\u10ea\u10ee\u10dc\u10d8\u10d5", +"Cut row": "\u10e1\u10e2\u10e0\u10d8\u10e5\u10dd\u10dc\u10d8\u10e1 \u10d0\u10db\u10dd\u10ed\u10e0\u10d0", +"Delete column": "\u10e1\u10d5\u10d4\u10e2\u10d8\u10e1 \u10ec\u10d0\u10e8\u10da\u10d0", +"Center": "\u10ea\u10d4\u10dc\u10e2\u10e0\u10e8\u10d8", +"Merge cells": "\u10e3\u10ef\u10e0\u10d4\u10d1\u10d8\u10e1 \u10d2\u10d0\u10d4\u10e0\u10d7\u10d8\u10d0\u10dc\u10d4\u10d1\u10d0", +"Insert template": "\u10e8\u10d0\u10d1\u10da\u10dd\u10dc\u10d8\u10e1 \u10e9\u10d0\u10e1\u10db\u10d0", +"Templates": "\u10e8\u10d0\u10d1\u10da\u10dd\u10dc\u10d4\u10d1\u10d8", +"Background color": "\u10e3\u10d9\u10d0\u10dc\u10d0 \u10e4\u10d4\u10e0\u10d8", +"Custom...": "\u10db\u10dd\u10e0\u10d2\u10d4\u10d1\u10e3\u10da\u10d8", +"Custom color": "\u10db\u10dd\u10e0\u10d2\u10d4\u10d1\u10e3\u10da\u10d8 \u10e4\u10d4\u10e0\u10d8", +"No color": "\u10e4\u10d4\u10e0\u10d8\u10e1 \u10d2\u10d0\u10e0\u10d4\u10e8\u10d4", +"Text color": "\u10e2\u10d4\u10e5\u10e1\u10e2\u10d8\u10e1 \u10e4\u10d4\u10e0\u10d8", +"Table of Contents": "\u10e1\u10d0\u10e0\u10e9\u10d4\u10d5\u10d8", +"Show blocks": "\u10d1\u10da\u10dd\u10d9\u10d4\u10d1\u10d8\u10e1 \u10e9\u10d5\u10d4\u10dc\u10d4\u10d1\u10d0", +"Show invisible characters": "\u10e3\u10ee\u10d8\u10da\u10d0\u10d5\u10d8 \u10e1\u10d8\u10db\u10d1\u10dd\u10da\u10dd\u10d4\u10d1\u10d8\u10e1 \u10e9\u10d5\u10d4\u10dc\u10d4\u10d1\u10d0", +"Words: {0}": "\u10e1\u10d8\u10e2\u10e7\u10d5\u10d4\u10d1\u10d8: {0}", +"Insert": "\u10e9\u10d0\u10e1\u10db\u10d0", +"File": "\u10e4\u10d0\u10d8\u10da\u10d8", +"Edit": "\u10e8\u10d4\u10e1\u10ec\u10dd\u10e0\u10d4\u10d1\u10d0", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "\u10e2\u10d4\u10e5\u10e1\u10e2\u10d8\u10e1 \u10e4\u10d0\u10e0\u10d7\u10d8. \u10d3\u10d0\u10d0\u10ed\u10d8\u10e0\u10d4\u10d7 ALT-F9\u10e1 \u10db\u10d4\u10dc\u10d8\u10e3\u10e1 \u10d2\u10d0\u10db\u10dd\u10e1\u10d0\u10eb\u10d0\u10ee\u10d4\u10d1\u10da\u10d0\u10d3. \u10d3\u10d0\u10d0\u10ed\u10d8\u10e0\u10d4\u10d7 ALT-F10\u10e1 \u10de\u10d0\u10dc\u10d4\u10da\u10d8\u10e1\u10d7\u10d5\u10d8\u10e1. \u10d3\u10d0\u10d0\u10ed\u10d8\u10e0\u10d4\u10d7 ALT-0\u10e1 \u10d3\u10d0\u10ee\u10db\u10d0\u10e0\u10d4\u10d1\u10d8\u10e1\u10d7\u10d5\u10d8\u10e1", +"Tools": "\u10d8\u10d0\u10e0\u10d0\u10e6\u10d4\u10d1\u10d8", +"View": "\u10dc\u10d0\u10ee\u10d5\u10d0", +"Table": "\u10ea\u10ee\u10e0\u10d8\u10da\u10d8", +"Format": "\u10e4\u10dd\u10e0\u10db\u10d0\u10e2\u10d8" +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/kab.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/kab.js new file mode 100644 index 0000000000..b9f9bccf40 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/kab.js @@ -0,0 +1,261 @@ +tinymce.addI18n('kab',{ +"Redo": "Err-d", +"Undo": "Semmet", +"Cut": "Gzem", +"Copy": "N\u0263el", +"Paste": "Sente\u1e0d", +"Select all": "Fren kulec", +"New document": "Attaftar amaynut", +"Ok": "Ih", +"Cancel": "Semmet", +"Visual aids": "Visual aids", +"Bold": "Tira tazurant", +"Italic": "Tira yeknan", +"Underline": "Aderrer", +"Strikethrough": "Strikethrough", +"Superscript": "Superscript", +"Subscript": "Subscript", +"Clear formatting": "Clear formatting", +"Align left": "Tarigla \u0263er zelma\u1e0d", +"Align center": "Di tlemast", +"Align right": "tarigla \u0263er zelma\u1e0d", +"Justify": "Justify", +"Bullet list": "Tabdart s tlillac", +"Numbered list": "Tabdart s wu\u1e6d\u1e6dunen", +"Decrease indent": "Simc\u1e6du\u1e25 asi\u1e93i", +"Increase indent": "Sim\u0263ur asi\u1e93i", +"Close": "Mdel", +"Formats": "Imasalen", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.", +"Headers": "Izwal", +"Header 1": "Azwel 1", +"Header 2": "Azwel 2", +"Header 3": "Azwel 3", +"Header 4": "Azwel 4", +"Header 5": "Header 5", +"Header 6": "Azwel 6", +"Headings": "Izewlen", +"Heading 1": "Inixf 1", +"Heading 2": "Inixf 2", +"Heading 3": "Inixf 3", +"Heading 4": "Inixf 4", +"Heading 5": "Inixf 5", +"Heading 6": "Inixf 6", +"Preformatted": "Yettwamsel si tazwara", +"Div": "Div", +"Pre": "Pre", +"Code": "Tangalt", +"Paragraph": "taseddart", +"Blockquote": "Tanebdurt", +"Inline": "Inline", +"Blocks": "I\u1e25edran", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.", +"Font Family": "Tasefsit", +"Font Sizes": "Tiddi n tsefsit", +"Class": "Asmil", +"Browse for an image": "Snirem iwakken ad tferne\u1e0d tugna", +"OR": "Ih", +"Drop an image here": "Ssers tugna dagi", +"Upload": "Sili", +"Block": "Sew\u1e25el", +"Align": "Settef", +"Default": "Lex\u1e63as", +"Circle": "Tawinest", +"Disc": "A\u1e0debsi", +"Square": "Amku\u1e93", +"Lower Alpha": "Alpha ame\u1e93yan", +"Lower Greek": "Grik ame\u1e93yan", +"Lower Roman": "Ruman amectu\u1e25", +"Upper Alpha": "Alfa ameqran", +"Upper Roman": "Ruman ameqran", +"Anchor": "Tamdeyt", +"Name": "Isem", +"Id": "Id", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "id ilaq ad ibdu s usekkil, ad yettwa\u1e0dfer kan s isekkilen, im\u1e0danen, ijerri\u1e0den, tinqi\u1e0din, snat n tenqi\u1e0din ne\u0263 ijerri\u1e0den n wadda.", +"You have unsaved changes are you sure you want to navigate away?": "Ibeddilen ur twaskelsen ara teb\u0263i\u1e0d ad teff\u0263e\u1e0d ?", +"Restore last draft": "Restore last draft", +"Special character": "Askil uslig", +"Source code": "Tangalt ta\u0263balut", +"Insert\/Edit code sample": "Ger\/\u1e92reg tangalt n umedya", +"Language": "Tutlayt", +"Code sample": "Tikkest n tengalt", +"Color": "Ini", +"R": "R", +"G": "G", +"B": "B", +"Left to right": "Seg zelma\u1e0d \u0263er yefus", +"Right to left": "Seg yefus \u0263er zelma\u1e0d", +"Emoticons": "Emoticons", +"Document properties": "Iraten n warat", +"Title": "Azwel", +"Keywords": "Awalen yufraren", +"Description": "Aglam", +"Robots": "Robots", +"Author": "Ameskar", +"Encoding": "Asettengel", +"Fullscreen": "Agdil a\u010duran", +"Action": "Tigawt", +"Shortcut": "Anegzum", +"Help": "Tallalt", +"Address": "Tansa", +"Focus to menubar": "Asa\u1e0des \u0263ef tfeggagt n wumu\u0263", +"Focus to toolbar": "Asa\u1e0des \u0263ef tfeggagt n ifecka", +"Focus to element path": "Asa\u1e0des \u0263ef ubrid n uferdis", +"Focus to contextual toolbar": "Asa\u1e0des \u0263ef tfeggagt n ifecka tanattalt", +"Insert link (if link plugin activated)": "Ger ase\u0263wen (ma yermed uzegrir n use\u0263wen)", +"Save (if save plugin activated)": "Sekles (ma yermed uzegrir save)", +"Find (if searchreplace plugin activated)": "Nadi (ma yermed uzegrir searchreplace)", +"Plugins installed ({0}):": "Izegriren yettwasbedden ({0}):", +"Premium plugins:": "Izegriren premium :", +"Learn more...": "\u1e92er ugar...", +"You are using {0}": "Tsseqdace\u1e0d {0}", +"Plugins": "Isi\u0263zifen", +"Handy Shortcuts": "Inegzumen", +"Horizontal line": "Ajerri\u1e0d aglawan", +"Insert\/edit image": "Ger\/\u1e92reg tugna", +"Image description": "Aglam n tugna", +"Source": "A\u0263balu", +"Dimensions": "Tisekta", +"Constrain proportions": "Constrain proportions", +"General": "Amatu", +"Advanced": "Ana\u1e93i", +"Style": "A\u0263anib", +"Vertical space": "Talunt taratakt", +"Horizontal space": "Talunt taglawant", +"Border": "Iri", +"Insert image": "Ger tugna", +"Image": "Tugna", +"Image list": "Tabdart n tugniwin", +"Rotate counterclockwise": "Tuzya mgal tamrilt", +"Rotate clockwise": "Tuzya yugdan tamrilt", +"Flip vertically": "Tuzya taratakt", +"Flip horizontally": "Tuzttya tagrawant", +"Edit image": "\u1e92reg tugna", +"Image options": "Tixti\u1e5biyin n tugna", +"Zoom in": "Zoom in", +"Zoom out": "Zoom out", +"Crop": "Rogner", +"Resize": "Beddel tiddi", +"Orientation": "Ta\u0263da", +"Brightness": "Tafat", +"Sharpen": "Affiner", +"Contrast": "Contrast", +"Color levels": "Iswiren n yini", +"Gamma": "Gamma", +"Invert": "Tti", +"Apply": "Snes", +"Back": "Tu\u0263alin", +"Insert date\/time": "Ger azemz\/asrag", +"Date\/time": "Azemz\/Asrag", +"Insert link": "Ger azday", +"Insert\/edit link": "Ger\/\u1e93reg azday", +"Text to display": "A\u1e0dris ara yettwabeqq\u1e0den", +"Url": "Url", +"Target": "Target", +"None": "Ulac", +"New window": "Asfaylu amaynut", +"Remove link": "Kkes azday", +"Anchors": "Timdyin", +"Link": "Ase\u0263wen", +"Paste or type a link": "Sente\u1e0d ne\u0263 sekcem ase\u0263wen", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "URL i teskecme\u1e0d tettban-d d tansa email. teb\u0263i\u1e0d ad s-ternu\u1e0d azwir mailto : ?", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "URL i teskecme\u1e0d tettban-d d azday uffi\u0263. Teb\u0263i\u1e0d ad s-ternu\u1e0d azwir http:\/\/ ?", +"Link list": "Tabdart n is\u0263ewnen", +"Insert video": "Ger avidyu", +"Insert\/edit video": "Ger\/\u1e93reg avidyu", +"Insert\/edit media": "Ger\/\u1e92reg amiya", +"Alternative source": "A\u0263balu amlellay", +"Poster": "Poster", +"Paste your embed code below:": "Paste your embed code below:", +"Embed": "Embed", +"Media": "Amidya", +"Nonbreaking space": "Talunt ur nettwagzam ara", +"Page break": "Angaz n usebter", +"Paste as text": "Sente\u1e0d d a\u1e0dris", +"Preview": "Sken", +"Print": "Siggez", +"Save": "Sekles", +"Find": "Nadi", +"Replace with": "Semselsi s", +"Replace": "Semselsi", +"Replace all": "Semselsi kulec", +"Prev": "Win yezrin", +"Next": "Win \u0263ers", +"Find and replace": "Nadi semselsi", +"Could not find the specified string.": "Ur d-nufi ara azrar i d-yettunefken.", +"Match case": "Match case", +"Whole words": "Awal ummid", +"Spellcheck": "Ase\u0263ti n tira", +"Ignore": "Zgel", +"Ignore all": "Zgel kulec", +"Finish": "Fak", +"Add to Dictionary": "Rnu-t s amawal", +"Insert table": "Ger tafelwit", +"Table properties": "Iraten n tfelwit", +"Delete table": "Kkes tafelwit", +"Cell": "Taxxamt", +"Row": "Adur", +"Column": "Tagejdit", +"Cell properties": "Iraten n texxamt", +"Merge cells": "Seddukel tixxamin", +"Split cell": "B\u1e0du tixxamin", +"Insert row before": "Ger adur deffir", +"Insert row after": "Ger adur sdat", +"Delete row": "Kkes tagejdit", +"Row properties": "Iraten n udur", +"Cut row": "Gzem adur", +"Copy row": "N\u0263el adur", +"Paste row before": "Sente\u1e0d adur sdat", +"Paste row after": "Sente\u1e0d adur deffir", +"Insert column before": "Sente\u1e0d tagejdit sdat", +"Insert column after": "Sente\u1e0d tagejdit deffir", +"Delete column": "Kkes tagejdit", +"Cols": "Tigejda", +"Rows": "Aduren", +"Width": "Tehri", +"Height": "Te\u0263zi", +"Cell spacing": "Tlunt ger texxamin", +"Cell padding": "Tama n texxamt", +"Caption": "Caption", +"Left": "\u0194er zelma\u1e0d", +"Center": "Di tlemmast", +"Right": "\u0194er yefus", +"Cell type": "Anaw n texxamt", +"Scope": "Scope", +"Alignment": "Tarigla", +"H Align": "Tarigla taglawant", +"V Align": "Tarigla taratakt", +"Top": "Uksawen", +"Middle": "Di tlemmast", +"Bottom": "Uksar", +"Header cell": "Tasen\u1e6di\u1e0dt n texxamt", +"Row group": "Agraw n waduren", +"Column group": "Agraw n tgejda", +"Row type": "Anaw n wadur", +"Header": "Tasenti\u1e0dt", +"Body": "Tafka", +"Footer": "A\u1e0dar", +"Border color": "Ini n yiri", +"Insert template": "Ger tamuddimt", +"Templates": "Timudimin", +"Template": "Tine\u0263rufin", +"Text color": "Ini n u\u1e0dris", +"Background color": "Ini n ugilal", +"Custom...": "Custom...", +"Custom color": "Custom color", +"No color": "Ulac ini", +"Table of Contents": "Tafelwit n ugbur", +"Show blocks": "Beqqe\u1e0d i\u1e25edran", +"Show invisible characters": "Beqqe\u1e0d isekkilen uffiren", +"Words: {0}": "Words: {0}", +"{0} words": "{0} n wawalen", +"File": "Afaylu", +"Edit": "\u1e92reg", +"Insert": "Ger", +"View": "Tamu\u0263li", +"Format": "Amasal", +"Table": "Tafelwit", +"Tools": "Ifecka", +"Powered by {0}": "Iteddu s {0} ", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help" +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/kk.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/kk.js new file mode 100644 index 0000000000..7cec8ab1d3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/kk.js @@ -0,0 +1,230 @@ +tinymce.addI18n('kk',{ +"Cut": "\u049a\u0438\u044b\u043f \u0430\u043b\u0443", +"Heading 5": "\u0422\u0430\u049b\u044b\u0440\u044b\u043f 5", +"Header 2": "\u0422\u0430\u049b\u044b\u0440\u044b\u043f\u0448\u0430 2", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "\u0411\u0440\u0430\u0443\u0437\u0435\u0440\u0456\u04a3\u0456\u0437 \u0430\u043b\u043c\u0430\u0441\u0443 \u0431\u0443\u0444\u0435\u0440\u0456\u043d\u0435 \u0442\u0456\u043a\u0435\u043b\u0435\u0439 \u049b\u0430\u0442\u044b\u043d\u0430\u0439 \u0430\u043b\u043c\u0430\u0439\u0434\u044b. Ctrl+X\/C\/V \u043f\u0435\u0440\u043d\u0435\u043b\u0435\u0440 \u0442\u0456\u0440\u043a\u0435\u0441\u0456\u043c\u0456\u043d \u043f\u0430\u0439\u0434\u0430\u043b\u0430\u043d\u044b\u04a3\u044b\u0437.", +"Heading 4": "\u0422\u0430\u049b\u044b\u0440\u044b\u043f 4", +"Div": "Div", +"Heading 2": "\u0422\u0430\u049b\u044b\u0440\u044b\u043f 2", +"Paste": "\u049a\u043e\u044e", +"Close": "\u0416\u0430\u0431\u0443", +"Font Family": "\u049a\u0430\u0440\u0456\u043f\u0442\u0435\u0440 \u0442\u043e\u0431\u044b", +"Pre": "Pre", +"Align right": "\u041e\u04a3\u0493\u0430 \u043e\u0440\u043d\u0430\u043b\u0430\u0441\u0442\u044b\u0440\u0443", +"New document": "\u0416\u0430\u04a3\u0430 \u049b\u04b1\u0436\u0430\u0442", +"Blockquote": "\u0414\u04d9\u0439\u0435\u043a\u0441\u04e9\u0437", +"Numbered list": "\u041d\u04e9\u043c\u0456\u0440\u043b\u0435\u043d\u0433\u0435\u043d \u0442\u0456\u0437\u0456\u043c", +"Heading 1": "\u0422\u0430\u049b\u044b\u0440\u044b\u043f 1", +"Headings": "\u0422\u0430\u049b\u044b\u0440\u044b\u043f", +"Increase indent": "\u0428\u0435\u0433\u0456\u043d\u0456\u0441\u0442\u0456 \u0430\u0440\u0442\u0442\u044b\u0440\u0443", +"Formats": "\u0424\u043e\u0440\u043c\u0430\u0442\u0442\u0430\u0440", +"Headers": "\u0422\u0430\u049b\u044b\u0440\u044b\u043f\u0448\u0430", +"Select all": "\u0411\u0430\u0440\u043b\u044b\u0493\u044b\u043d \u0442\u0430\u04a3\u0434\u0430\u0443", +"Header 3": "\u0422\u0430\u049b\u044b\u0440\u044b\u043f\u0448\u0430 3", +"Blocks": "\u0411\u043b\u043e\u043a\u0442\u0435\u043a\u0442\u0435\u0441 (Block)", +"Undo": "\u0411\u043e\u043b\u0434\u044b\u0440\u043c\u0430\u0443", +"Strikethrough": "\u0411\u0435\u043b\u0456\u043d\u0435\u043d \u0441\u044b\u0437\u044b\u043b\u0493\u0430\u043d", +"Bullet list": "\u0422\u0430\u04a3\u0431\u0430\u043b\u0430\u043d\u0493\u0430\u043d \u0442\u0456\u0437\u0456\u043c", +"Header 1": "\u0422\u0430\u049b\u044b\u0440\u044b\u043f\u0448\u0430 1", +"Superscript": "\u04ae\u0441\u0442\u0456\u04a3\u0433\u0456 \u0438\u043d\u0434\u0435\u043a\u0441", +"Clear formatting": "\u0424\u043e\u0440\u043c\u0430\u0442\u0442\u0430\u0443\u0434\u0430\u043d \u0442\u0430\u0437\u0430\u0440\u0442\u0443", +"Font Sizes": "\u049a\u0430\u0440\u0456\u043f\u0442\u0435\u0440 \u04e9\u043b\u0448\u0435\u043c\u0456", +"Subscript": "\u0410\u0441\u0442\u044b\u04a3\u0493\u044b \u0438\u043d\u0434\u0435\u043a\u0441", +"Header 6": "\u0422\u0430\u049b\u044b\u0440\u044b\u043f\u0448\u0430 6", +"Redo": "\u049a\u0430\u0439\u0442\u0430\u0440\u0443", +"Paragraph": "\u0410\u0431\u0437\u0430\u0446", +"Ok": "\u041e\u041a", +"Bold": "\u0416\u0443\u0430\u043d", +"Code": "\u041a\u043e\u0434", +"Italic": "\u041a\u04e9\u043b\u0431\u0435\u0443", +"Align center": "\u041e\u0440\u0442\u0430\u0441\u044b\u043d\u0430 \u043e\u0440\u043d\u0430\u043b\u0430\u0441\u0442\u044b\u0440\u0443", +"Header 5": "\u0422\u0430\u049b\u044b\u0440\u044b\u043f\u0448\u0430 5", +"Heading 6": "\u0422\u0430\u049b\u044b\u0440\u044b\u043f 6", +"Heading 3": "\u0422\u0430\u049b\u044b\u0440\u044b\u043f 3", +"Decrease indent": "\u0428\u0435\u0433\u0456\u043d\u0456\u0441\u0442\u0456 \u043a\u0435\u043c\u0456\u0442\u0443", +"Header 4": "\u0422\u0430\u049b\u044b\u0440\u044b\u043f\u0448\u0430 4", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "\u041e\u0441\u044b \u043e\u043f\u0446\u0438\u044f \u04e9\u0448\u0456\u0440\u0456\u043b\u043c\u0435\u0433\u0435\u043d\u0448\u0435, \u0431\u0443\u0444\u0435\u0440\u0434\u0435\u0433\u0456 \u043c\u04d9\u0442\u0456\u043d \u043a\u04d9\u0434\u0456\u043c\u0433\u0456 \u043c\u04d9\u0442\u0456\u043d \u0440\u0435\u0442\u0456\u043d\u0434\u0435 \u049b\u043e\u0439\u044b\u043b\u0430\u0434\u044b.", +"Underline": "\u0410\u0441\u0442\u044b \u0441\u044b\u0437\u044b\u043b\u0493\u0430\u043d", +"Cancel": "\u0411\u0430\u0441 \u0442\u0430\u0440\u0442\u0443", +"Justify": "\u0422\u043e\u043b\u0442\u044b\u0440\u0443", +"Inline": "\u041a\u0456\u0440\u0456\u0441\u0442\u0456\u0440\u0456\u043b\u0433\u0435\u043d (Inline)", +"Copy": "\u041a\u04e9\u0448\u0456\u0440\u0443", +"Align left": "\u0421\u043e\u043b\u0493\u0430 \u043e\u0440\u043d\u0430\u043b\u0430\u0441\u0442\u044b\u0440\u0443", +"Visual aids": "\u041a\u04e9\u043c\u0435\u043a\u0448\u0456 \u0431\u0435\u043b\u0433\u0456\u043b\u0435\u0440", +"Lower Greek": "\u041a\u0456\u0448\u0456 \u0433\u0440\u0435\u043a \u04d9\u0440\u0456\u043f\u0442\u0435\u0440\u0456", +"Square": "\u0428\u0430\u0440\u0448\u044b", +"Default": "\u04d8\u0434\u0435\u043f\u043a\u0456", +"Lower Alpha": "\u041a\u0456\u0448\u0456 \u04d9\u0440\u0456\u043f\u0442\u0435\u0440", +"Circle": "\u0428\u0435\u04a3\u0431\u0435\u0440", +"Disc": "\u0414\u0438\u0441\u043a", +"Upper Alpha": "\u0411\u0430\u0441 \u04d9\u0440\u0456\u043f\u0442\u0435\u0440", +"Upper Roman": "\u0411\u0430\u0441 \u0440\u0438\u043c \u0446\u0438\u0444\u0440\u043b\u0430\u0440\u044b", +"Lower Roman": "\u041a\u0456\u0448\u0456 \u0440\u0438\u043c \u0446\u0438\u0444\u0440\u043b\u0430\u0440\u044b", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "Id \u0442\u0435\u043a \u049b\u0430\u043d\u0430 \u04d9\u0440\u0456\u043f\u0442\u0435\u043d \u0431\u0430\u0441\u0442\u0430\u043b\u044b\u043f, \u04d9\u0440\u0456\u043f\u0442\u0435\u0440, \u0441\u0430\u043d\u0434\u0430\u0440, \u0441\u044b\u0437\u044b\u049b\u0448\u0430\u043b\u0430\u0440, \u043d\u04af\u043a\u0442\u0435\u043b\u0435\u0440 \u0436\u04d9\u043d\u0435 \u0442.\u0431 \u0436\u0430\u043b\u0493\u0430\u0441\u0443\u044b \u0442\u0438\u0456\u0441.", +"Name": "\u0410\u0442\u044b", +"Anchor": "\u0411\u0435\u0442\u0431\u0435\u043b\u0433\u0456", +"Id": "Id", +"You have unsaved changes are you sure you want to navigate away?": "\u0421\u0430\u049b\u0442\u0430\u043b\u043c\u0430\u0493\u0430\u043d \u04e9\u0437\u0433\u0435\u0440\u0456\u0441\u0442\u0435\u0440 \u0431\u0430\u0440. \u0421\u0456\u0437 \u0448\u044b\u043d\u044b\u043c\u0435\u043d \u0431\u0430\u0441\u049b\u0430 \u0436\u0435\u0440\u0433\u0435 \u043a\u0435\u0442\u0443\u0434\u0456 \u049b\u0430\u043b\u0430\u0439\u0441\u044b\u0437 \u0431\u0430?", +"Restore last draft": "\u0421\u043e\u04a3\u0493\u044b \u0441\u0430\u049b\u0442\u0430\u043b\u0493\u0430\u043d\u0434\u044b \u049b\u0430\u043b\u043f\u044b\u043d\u0430 \u043a\u0435\u043b\u0442\u0456\u0440\u0443", +"Special character": "\u0410\u0440\u043d\u0430\u0439\u044b \u0442\u0430\u04a3\u0431\u0430", +"Source code": "\u0411\u0430\u0441\u0442\u0430\u043f\u049b\u044b \u043a\u043e\u0434", +"Language": "\u0422\u0456\u043b", +"Insert\/Edit code sample": "\u041a\u043e\u0434 \u04af\u043b\u0433\u0456\u0441\u0456\u043d \u043a\u0456\u0440\u0456\u0441\u0442\u0456\u0440\u0443\/\u0442\u04af\u0437\u0435\u0442\u0443", +"B": "B", +"R": "R", +"G": "G", +"Color": "\u0422\u04af\u0441", +"Right to left": "\u041e\u04a3\u043d\u0430\u043d \u0441\u043e\u043b\u0493\u0430", +"Left to right": "\u0421\u043e\u043b\u0434\u0430\u043d \u043e\u04a3\u0493\u0430", +"Emoticons": "\u0421\u043c\u0430\u0439\u043b\u0438\u043a\u0442\u0430\u0440", +"Robots": "Meta-robots", +"Document properties": "\u049a\u04b1\u0436\u0430\u0442 \u0441\u0438\u043f\u0430\u0442\u0442\u0430\u0440\u044b", +"Title": "\u0410\u0442\u0430\u0443\u044b", +"Keywords": "Meta-keywords", +"Encoding": "Meta-charset", +"Description": "\u0421\u0438\u043f\u0430\u0442\u0442\u0430\u043c\u0430\u0441\u044b", +"Author": "Meta-author", +"Fullscreen": "\u0422\u043e\u043b\u044b\u049b \u044d\u043a\u0440\u0430\u043d", +"Horizontal line": "\u041a\u04e9\u043b\u0434\u0435\u043d\u0435\u04a3 \u0441\u044b\u0437\u044b\u049b", +"Horizontal space": "\u041a\u04e9\u043b\u0434\u0435\u043d\u0435\u04a3\u0456\u043d\u0435\u043d \u049b\u0430\u043b\u0430\u0442\u044b\u043d \u043e\u0440\u044b\u043d", +"Insert\/edit image": "\u0421\u0443\u0440\u0435\u0442 \u043a\u0456\u0440\u0456\u0441\u0442\u0456\u0440\u0443\/\u0442\u04af\u0437\u0435\u0442\u0443", +"General": "\u0416\u0430\u043b\u043f\u044b", +"Advanced": "\u049a\u043e\u0441\u044b\u043c\u0448\u0430", +"Source": "\u0410\u0434\u0440\u0435\u0441\u0456", +"Border": "\u0416\u0438\u0435\u0433\u0456", +"Constrain proportions": "\u041f\u0440\u043e\u043f\u043e\u0440\u0446\u0438\u044f\u043b\u0430\u0440\u0434\u044b \u0441\u0430\u049b\u0442\u0430\u0443", +"Vertical space": "\u0422\u0456\u043a \u043a\u0435\u04a3\u0434\u0456\u0433\u0456", +"Image description": "\u0421\u0443\u0440\u0435\u0442 \u0441\u0438\u043f\u0430\u0442\u0442\u0430\u043c\u0430\u0441\u044b", +"Style": "\u0421\u0442\u0438\u043b\u0456", +"Dimensions": "\u04e8\u043b\u0448\u0435\u043c\u0434\u0435\u0440\u0456", +"Insert image": "\u0421\u0443\u0440\u0435\u0442 \u043a\u0456\u0440\u0456\u0441\u0442\u0456\u0440\u0443", +"Image": "\u0421\u0443\u0440\u0435\u0442", +"Zoom in": "\u0416\u0430\u049b\u044b\u043d\u0434\u0430\u0442\u0443", +"Contrast": "\u049a\u043e\u044e\u043b\u0430\u0442\u0443", +"Back": "\u0410\u0440\u0442\u049b\u0430", +"Gamma": "\u0413\u0430\u043c\u043c\u0430", +"Flip horizontally": "\u041a\u04e9\u043b\u0434\u0435\u043d\u0435\u04a3\u043d\u0435\u043d \u0430\u0443\u0434\u0430\u0440\u0443", +"Resize": "\u04e8\u043b\u0448\u0435\u043c\u0456\u043d \u04e9\u0437\u0433\u0435\u0440\u0442\u0443", +"Sharpen": "\u041d\u0430\u049b\u0442\u044b\u043b\u0430\u0443", +"Zoom out": "\u0410\u043b\u044b\u0441\u0442\u0430\u0442\u0443", +"Image options": "\u0421\u0443\u0440\u0435\u0442 \u0431\u0430\u043f\u0442\u0430\u0443\u043b\u0430\u0440\u044b", +"Apply": "\u0421\u0430\u049b\u0442\u0430\u0443", +"Brightness": "\u0410\u0448\u044b\u049b\u0442\u0430\u0443", +"Rotate clockwise": "\u0421\u0430\u0493\u0430\u0442 \u0442\u0456\u043b\u0456\u043d\u0456\u04a3 \u0431\u0430\u0493\u044b\u0442\u044b\u043c\u0435\u043d \u0431\u04b1\u0440\u0443", +"Rotate counterclockwise": "\u0421\u0430\u0493\u0430\u0442 \u0442\u0456\u043b\u0456\u043d\u0456\u04a3 \u0431\u0430\u0493\u044b\u0442\u044b\u043d\u0430 \u049b\u0430\u0440\u0441\u044b \u0431\u04b1\u0440\u0443", +"Edit image": "\u0421\u0443\u0440\u0435\u0442\u0442\u0456 \u04e9\u0437\u0433\u0435\u0440\u0442\u0443", +"Color levels": "\u0422\u04af\u0441 \u0434\u0435\u04a3\u0433\u0435\u0439\u043b\u0435\u0440\u0456", +"Crop": "\u041a\u0435\u0441\u0443", +"Orientation": "\u0411\u0430\u0493\u0434\u0430\u0440", +"Flip vertically": "\u0422\u0456\u0433\u0456\u043d\u0435\u043d \u0430\u0443\u0434\u0430\u0440\u0443", +"Invert": "\u041a\u0456\u0440\u0456\u0441\u0442\u0456\u0440\u0443", +"Date\/time": "\u041a\u04af\u043d\/\u0443\u0430\u049b\u044b\u0442", +"Insert date\/time": "\u041a\u04af\u043d\/\u0443\u0430\u049b\u044b\u0442 \u043a\u0456\u0440\u0456\u0441\u0442\u0456\u0440\u0443", +"Remove link": "\u0421\u0456\u043b\u0442\u0435\u043c\u0435\u043d\u0456 \u0430\u043b\u044b\u043f \u0442\u0430\u0441\u0442\u0430\u0443", +"Url": "URL-\u0430\u0434\u0440\u0435\u0441\u0456", +"Text to display": "\u041a\u04e9\u0440\u0441\u0435\u0442\u0456\u043b\u0435\u0442\u0456\u043d \u043c\u04d9\u0442\u0456\u043d", +"Anchors": "\u0421\u0456\u043b\u0442\u0435\u043c\u0435\u043b\u0435\u0440", +"Insert link": "\u0421\u0456\u043b\u0442\u0435\u043c\u0435 \u043a\u0456\u0440\u0456\u0441\u0442\u0456\u0440\u0443", +"Link": "\u0421\u0456\u043b\u0442\u0435\u043c\u0435", +"New window": "\u0416\u0430\u04a3\u0430 \u0442\u0435\u0440\u0435\u0437\u0435", +"None": "\u0416\u043e\u049b", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "\u0421\u0456\u0437 \u0435\u04a3\u0433\u0456\u0437\u0456\u043f \u0442\u04b1\u0440\u0493\u0430\u043d URL \u0441\u044b\u0440\u0442\u049b\u044b \u0441\u0456\u043b\u0442\u0435\u043c\u0435 \u0431\u043e\u043b\u044b\u043f \u0442\u0430\u0431\u044b\u043b\u0430\u0434\u044b. \u0410\u043b\u0434\u044b\u043d\u0430 http:\/\/ \u043f\u0440\u0435\u0444\u0438\u043a\u0441\u0456\u043d \u049b\u043e\u0441\u0443\u0434\u044b \u049b\u0430\u043b\u0430\u0439\u0441\u044b\u0437 \u0431\u0430?", +"Paste or type a link": "\u0421\u0456\u043b\u0442\u0435\u043c\u0435\u043d\u0456 \u049b\u043e\u0439\u044b\u04a3\u044b\u0437 \u043d\u0435\u043c\u0435\u0441\u0435 \u0442\u0435\u0440\u0456\u04a3\u0456\u0437", +"Target": "\u0410\u0448\u044b\u043b\u0430\u0442\u044b\u043d \u0436\u0435\u0440\u0456", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "\u0421\u0456\u0437 \u0435\u04a3\u0433\u0456\u0437\u0456\u043f \u0442\u04b1\u0440\u0493\u0430\u043d URL e-mail \u0430\u0434\u0440\u0435\u0441\u0456 \u0431\u043e\u043b\u044b\u043f \u0442\u0430\u0431\u044b\u043b\u0430\u0434\u044b. \u0410\u043b\u0434\u044b\u043d\u0430 mailto: \u043f\u0440\u0435\u0444\u0438\u043a\u0441\u0456\u043d \u049b\u043e\u0441\u0443\u0434\u044b \u049b\u0430\u043b\u0430\u0439\u0441\u044b\u0437 \u0431\u0430?", +"Insert\/edit link": "\u0421\u0456\u043b\u0442\u0435\u043c\u0435 \u043a\u0456\u0440\u0456\u0441\u0442\u0456\u0440\u0443\/\u0442\u04af\u0437\u0435\u0442\u0443", +"Insert\/edit video": "\u0412\u0438\u0434\u0435\u043e \u043a\u0456\u0440\u0456\u0441\u0442\u0456\u0440\u0443\/\u0442\u04af\u0437\u0435\u0442\u0443", +"Media": "\u041c\u0435\u0434\u0438\u0430", +"Alternative source": "\u049a\u043e\u0441\u044b\u043c\u0448\u0430 \u0430\u0434\u0440\u0435\u0441\u0456", +"Paste your embed code below:": "\u0422\u04e9\u043c\u0435\u043d\u0434\u0435\u0433\u0456 \u043a\u043e\u0434\u0442\u044b \u043a\u04e9\u0448\u0456\u0440\u0456\u043f \u0430\u043b\u044b\u043f, \u049b\u043e\u0439\u044b\u04a3\u044b\u0437:", +"Insert video": "\u0412\u0438\u0434\u0435\u043e \u043a\u0456\u0440\u0456\u0441\u0442\u0456\u0440\u0443", +"Poster": "\u041f\u043e\u0441\u0442\u0435\u0440\u0456", +"Insert\/edit media": "\u041c\u0435\u0434\u0438\u0430 \u043a\u0456\u0440\u0456\u0441\u0442\u0456\u0440\u0443\/\u0442\u04af\u0437\u0435\u0442\u0443", +"Embed": "\u0415\u043d\u0434\u0456\u0440\u0443", +"Nonbreaking space": "\u04ae\u0437\u0434\u0456\u043a\u0441\u0456\u0437 \u0431\u043e\u0441 \u043e\u0440\u044b\u043d", +"Page break": "\u0411\u0435\u0442 \u04af\u0437\u0456\u043b\u0456\u043c\u0456", +"Paste as text": "\u041c\u04d9\u0442\u0456\u043d \u0440\u0435\u0442\u0456\u043d\u0434\u0435 \u049b\u043e\u044e", +"Preview": "\u0410\u043b\u0434\u044b\u043d-\u0430\u043b\u0430 \u049b\u0430\u0440\u0430\u0443", +"Print": "\u0411\u0430\u0441\u044b\u043f \u0448\u044b\u0493\u0430\u0440\u0443", +"Save": "\u0421\u0430\u049b\u0442\u0430\u0443", +"Could not find the specified string.": "\u041a\u04e9\u0440\u0441\u0435\u0442\u0456\u043b\u0433\u0435\u043d \u0436\u043e\u043b \u0442\u0430\u0431\u044b\u043b\u043c\u0430\u0434\u044b.", +"Replace": "\u0410\u0443\u044b\u0441\u0442\u044b\u0440\u0443", +"Next": "\u041a\u0435\u043b\u0435\u0441\u0456", +"Whole words": "\u0422\u04b1\u0442\u0430\u0441 \u0441\u04e9\u0437\u0434\u0435\u0440", +"Find and replace": "\u0422\u0430\u0431\u0443 \u0436\u04d9\u043d\u0435 \u0430\u0443\u044b\u0441\u0442\u044b\u0440\u0443", +"Replace with": "\u0410\u0443\u044b\u0441\u0442\u044b\u0440\u0430\u0442\u044b\u043d \u043c\u04d9\u0442\u0456\u043d", +"Find": "\u0422\u0430\u0431\u044b\u043b\u0430\u0442\u044b\u043d \u043c\u04d9\u0442\u0456\u043d", +"Replace all": "\u0411\u0430\u0440\u043b\u044b\u0493\u044b\u043d \u0430\u0443\u044b\u0441\u0442\u044b\u0440\u0443", +"Match case": "\u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0434\u0456 \u0435\u0441\u043a\u0435\u0440\u0443", +"Prev": "\u0410\u043b\u0434\u044b\u04a3\u0493\u044b", +"Spellcheck": "\u0415\u043c\u043b\u0435 \u0442\u0435\u043a\u0441\u0435\u0440\u0443", +"Finish": "\u0410\u044f\u049b\u0442\u0430\u0443", +"Ignore all": "\u0415\u0448\u049b\u0430\u0439\u0441\u044b\u0441\u044b\u043d \u0435\u043b\u0435\u043c\u0435\u0443", +"Ignore": "\u0415\u043b\u0435\u043c\u0435\u0443", +"Add to Dictionary": "\u0421\u04e9\u0437\u0434\u0456\u043a\u043a\u0435 \u043a\u0456\u0440\u0456\u0441\u0442\u0456\u0440\u0443", +"Insert row before": "\u04ae\u0441\u0442\u0456\u043d\u0435 \u0436\u043e\u043b \u049b\u043e\u0441\u0443", +"Rows": "\u0416\u043e\u043b\u044b", +"Height": "\u0411\u0438\u0456\u043a\u0442\u0456\u0433\u0456", +"Paste row after": "\u0416\u043e\u043b\u0434\u044b\u04a3 \u0430\u0441\u0442\u044b\u043d\u0430 \u049b\u043e\u044e", +"Alignment": "\u041e\u0440\u043d\u0430\u043b\u0430\u0441\u0443\u044b", +"Border color": "\u0416\u0438\u0435\u043a \u0442\u04af\u0441\u0456", +"Column group": "\u0411\u0430\u0493\u0430\u043d \u0442\u043e\u0431\u044b", +"Row": "\u0416\u043e\u043b", +"Insert column before": "\u0410\u043b\u0434\u044b\u043d\u0430 \u0431\u0430\u0493\u0430\u043d \u049b\u043e\u0441\u0443", +"Split cell": "\u04b0\u044f\u0448\u044b\u049b\u0442\u044b \u0431\u04e9\u043b\u0443", +"Cell padding": "\u04b0\u044f\u0448\u044b\u049b \u043a\u0435\u04a3\u0434\u0456\u0433\u0456", +"Cell spacing": "\u04b0\u044f\u0448\u044b\u049b \u0430\u0440\u0430\u043b\u044b\u0493\u044b", +"Row type": "\u0416\u043e\u043b \u0442\u0438\u043f\u0456", +"Insert table": "\u041a\u0435\u0441\u0442\u0435 \u043a\u0456\u0440\u0456\u0441\u0442\u0456\u0440\u0443", +"Body": "\u041d\u0435\u0433\u0456\u0437\u0433\u0456 \u0431\u04e9\u043b\u0456\u0433\u0456", +"Caption": "\u0410\u0442\u0430\u0443\u044b", +"Footer": "\u0410\u044f\u049b \u0436\u0430\u0493\u044b", +"Delete row": "\u0416\u043e\u043b\u0434\u044b \u0436\u043e\u044e", +"Paste row before": "\u0416\u043e\u043b\u0434\u044b\u04a3 \u04af\u0441\u0442\u0456\u043d\u0435 \u049b\u043e\u044e", +"Scope": "\u0410\u0443\u043c\u0430\u0493\u044b", +"Delete table": "\u041a\u0435\u0441\u0442\u0435\u043d\u0456 \u0436\u043e\u044e", +"H Align": "\u041a\u04e9\u043b\u0434\u0435\u043d\u0435\u04a3\u043d\u0435\u043d \u0442\u0443\u0440\u0430\u043b\u0430\u0443", +"Top": "\u04ae\u0441\u0442\u0456", +"Header cell": "\u0422\u0430\u049b\u044b\u0440\u044b\u043f\u0448\u0430 \u04b1\u044f\u0448\u044b\u049b", +"Column": "\u0411\u0430\u0493\u0430\u043d", +"Row group": "\u0416\u043e\u043b \u0442\u043e\u0431\u044b", +"Cell": "\u04b0\u044f\u0448\u044b\u049b", +"Middle": "\u041e\u0440\u0442\u0430\u0441\u044b", +"Cell type": "\u04b0\u044f\u0448\u044b\u049b \u0442\u0438\u043f\u0456", +"Copy row": "\u0416\u043e\u043b\u0434\u044b \u043a\u04e9\u0448\u0456\u0440\u0443", +"Row properties": "\u0416\u043e\u043b \u0441\u0438\u043f\u0430\u0442\u0442\u0430\u0440\u044b", +"Table properties": "\u041a\u0435\u0441\u0442\u0435 \u0441\u0438\u043f\u0430\u0442\u0442\u0430\u0440\u044b", +"Bottom": "\u0410\u0441\u0442\u044b", +"V Align": "\u0422\u0456\u0433\u0456\u043d\u0435\u043d \u0442\u0443\u0440\u0430\u043b\u0430\u0443", +"Header": "\u0411\u0430\u0441 \u0436\u0430\u0493\u044b", +"Right": "\u041e\u04a3\u0493\u0430", +"Insert column after": "\u0410\u0440\u0442\u044b\u043d\u0430 \u0431\u0430\u0493\u0430\u043d \u049b\u043e\u0441\u0443", +"Cols": "\u0411\u0430\u0493\u0430\u043d\u044b", +"Insert row after": "\u0410\u0441\u0442\u044b\u043d\u0430 \u0436\u043e\u043b \u049b\u043e\u0441\u0443", +"Width": "\u04b0\u0437\u044b\u043d\u0434\u044b\u0493\u044b", +"Cell properties": "\u04b0\u044f\u0448\u044b\u049b \u0441\u0438\u043f\u0430\u0442\u0442\u0430\u0440\u044b", +"Left": "\u0421\u043e\u043b\u0493\u0430", +"Cut row": "\u0416\u043e\u043b\u0434\u044b \u049b\u0438\u044b\u043f \u0430\u043b\u0443", +"Delete column": "\u0411\u0430\u0493\u0430\u043d\u0434\u044b \u0436\u043e\u044e", +"Center": "\u041e\u0440\u0442\u0430\u0441\u044b\u043d\u0430", +"Merge cells": "\u04b0\u044f\u0448\u044b\u049b\u0442\u0430\u0440\u0434\u044b \u0431\u0456\u0440\u0456\u043a\u0442\u0456\u0440\u0443", +"Insert template": "\u04ae\u043b\u0433\u0456 \u043a\u0456\u0440\u0456\u0441\u0442\u0456\u0440\u0443", +"Templates": "\u04ae\u043b\u0433\u0456\u043b\u0435\u0440", +"Background color": "\u04e8\u04a3\u0456\u043d\u0456\u04a3 \u0442\u04af\u0441\u0456", +"Custom...": "\u04e8\u0437\u0433\u0435\u0440\u0442\u0443", +"Custom color": "\u0422\u04af\u0441 \u04e9\u0437\u0433\u0435\u0440\u0442\u0443", +"No color": "\u0422\u04af\u0441\u0441\u0456\u0437", +"Text color": "\u041c\u04d9\u0442\u0456\u043d \u0442\u04af\u0441\u0456", +"Table of Contents": "\u041c\u0430\u0437\u043c\u04b1\u043d\u0434\u0430\u0440 \u043a\u0435\u0441\u0442\u0435\u0441\u0456", +"Show blocks": "\u0411\u043b\u043e\u043a\u0442\u0430\u0440\u0434\u044b \u043a\u04e9\u0440\u0441\u0435\u0442\u0443", +"Show invisible characters": "\u041a\u04e9\u0440\u0456\u043d\u0431\u0435\u0439\u0442\u0456\u043d \u0442\u0430\u04a3\u0431\u0430\u043b\u0430\u0440\u0434\u044b \u043a\u04e9\u0440\u0441\u0435\u0442\u0443", +"Words: {0}": "\u0421\u04e9\u0437 \u0441\u0430\u043d\u044b: {0}", +"Insert": "\u041a\u0456\u0440\u0456\u0441\u0442\u0456\u0440\u0443", +"File": "\u0424\u0430\u0439\u043b", +"Edit": "\u0422\u04af\u0437\u0435\u0442\u0443", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "\u0424\u043e\u0440\u043c\u0430\u0442\u0442\u0430\u043b\u0493\u0430\u043d \u043c\u04d9\u0442\u0456\u043d \u0430\u0443\u043c\u0430\u0493\u044b. \u041c\u0435\u043d\u044e \u043a\u04e9\u0440\u0441\u0435\u0442\u0443 \u04af\u0448\u0456\u043d ALT-F9 \u0431\u0430\u0441\u044b\u04a3\u044b\u0437. \u049a\u04b1\u0440\u0430\u043b\u0434\u0430\u0440 \u043f\u0430\u043d\u0435\u043b\u0456\u043d \u043a\u04e9\u0440\u0441\u0435\u0442\u0443 \u04af\u0448\u0456\u043d ALT-F10 \u0431\u0430\u0441\u044b\u04a3\u044b\u0437. \u041a\u04e9\u043c\u0435\u043a \u0430\u043b\u0443 \u04af\u0448\u0456\u043d ALT-0 \u0431\u0430\u0441\u044b\u04a3\u044b\u0437.", +"Tools": "\u049a\u04b1\u0440\u0430\u043b\u0434\u0430\u0440", +"View": "\u041a\u04e9\u0440\u0456\u043d\u0456\u0441", +"Table": "\u041a\u0435\u0441\u0442\u0435", +"Format": "\u0424\u043e\u0440\u043c\u0430\u0442" +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/km_KH.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/km_KH.js new file mode 100644 index 0000000000..5c4b055126 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/km_KH.js @@ -0,0 +1,253 @@ +tinymce.addI18n('km_KH',{ +"Redo": "\u1792\u17d2\u179c\u17be\u200b\u179c\u17b7\u1789", +"Undo": "\u1798\u17b7\u1793\u200b\u1792\u17d2\u179c\u17be\u200b\u179c\u17b7\u1789", +"Cut": "\u1780\u17b6\u178f\u17cb", +"Copy": "\u1785\u1798\u17d2\u179b\u1784", +"Paste": "\u1794\u17b7\u1791\u200b\u1797\u17d2\u1787\u17b6\u1794\u17cb", +"Select all": "\u1787\u17d2\u179a\u17be\u179f\u200b\u1791\u17b6\u17c6\u1784\u200b\u17a2\u179f\u17cb", +"New document": "\u17af\u1780\u179f\u17b6\u179a\u200b\u17a2\u178f\u17d2\u1790\u1794\u1791\u200b\u1790\u17d2\u1798\u17b8", +"Ok": "\u1796\u17d2\u179a\u1798", +"Cancel": "\u1794\u17c4\u17c7\u200b\u1794\u1784\u17cb", +"Visual aids": "\u1791\u17b7\u178a\u17d2\u178b\u1797\u17b6\u1796\u200b\u1787\u17c6\u1793\u17bd\u1799", +"Bold": "\u178a\u17b7\u178f", +"Italic": "\u1791\u17d2\u179a\u17c1\u178f", +"Underline": "\u1782\u17bc\u179f\u200b\u1794\u1793\u17d2\u1791\u17b6\u178f\u17cb\u200b\u1796\u17b8\u200b\u1780\u17d2\u179a\u17c4\u1798", +"Strikethrough": "\u1782\u17bc\u179f\u200b\u1794\u1793\u17d2\u1791\u17b6\u178f\u17cb\u200b\u1786\u17bc\u178f", +"Superscript": "\u17a2\u1780\u17d2\u179f\u179a\u200b\u178f\u17bc\u1785\u200b\u179b\u17be", +"Subscript": "\u17a2\u1780\u17d2\u179f\u179a\u200b\u178f\u17bc\u1785\u200b\u1780\u17d2\u179a\u17c4\u1798", +"Clear formatting": "\u179f\u1798\u17d2\u17a2\u17b6\u178f\u200b\u1791\u1798\u17d2\u179a\u1784\u17cb", +"Align left": "\u178f\u1798\u17d2\u179a\u17b9\u1798\u200b\u1791\u17c5\u200b\u1786\u17d2\u179c\u17c1\u1784", +"Align center": "\u178f\u1798\u17d2\u179a\u17b9\u1798\u200b\u1791\u17c5\u200b\u1780\u178e\u17d2\u178a\u17b6\u179b", +"Align right": "\u178f\u1798\u17d2\u179a\u17b9\u1798\u200b\u1791\u17c5\u200b\u179f\u17d2\u178a\u17b6\u17c6", +"Justify": "\u178f\u1798\u17d2\u179a\u17b9\u1798\u200b\u1796\u17c1\u1789", +"Bullet list": "\u1794\u1789\u17d2\u1787\u17b8\u200b\u1787\u17b6\u200b\u1785\u17c6\u178e\u17bb\u1785", +"Numbered list": "\u1794\u1789\u17d2\u1787\u17b8\u200b\u1787\u17b6\u200b\u179b\u17c1\u1781", +"Decrease indent": "\u1781\u17b7\u178f\u200b\u1794\u1793\u17d2\u1791\u17b6\u178f\u17cb\u200b\u1785\u17c1\u1789", +"Increase indent": "\u1781\u17b7\u178f\u200b\u1794\u1793\u17d2\u1791\u17b6\u178f\u17cb\u200b\u1785\u17bc\u179b", +"Close": "\u1794\u17b7\u1791", +"Formats": "\u1791\u17d2\u179a\u1784\u17cb\u1791\u17d2\u179a\u17b6\u1799", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "\u1780\u1798\u17d2\u1798\u179c\u17b7\u1792\u17b8\u200b\u17a2\u17ca\u17b8\u1793\u1792\u17ba\u178e\u17b7\u178f\u200b\u179a\u1794\u179f\u17cb\u200b\u17a2\u17d2\u1793\u1780\u200b\u1798\u17b7\u1793\u200b\u17a2\u17b6\u1785\u200b\u1785\u17bc\u179b\u200b\u1795\u17d2\u1791\u17b6\u179b\u17cb\u200b\u1791\u17c5\u200b\u1780\u17b6\u1793\u17cb\u200b\u1783\u17d2\u179b\u17b8\u1794\u1794\u178f\u200b\u1791\u17c1\u17d4 \u179f\u17bc\u1798\u200b\u1794\u17d2\u179a\u17be Ctrl+X\/C\/V \u179b\u17be\u200b\u1780\u17d2\u178a\u17b6\u179a\u200b\u1785\u17bb\u1785\u200b\u1787\u17c6\u1793\u17bd\u179f\u200b\u179c\u17b7\u1789\u17d4", +"Headers": "\u1780\u17d2\u1794\u17b6\u179b", +"Header 1": "\u1780\u17d2\u1794\u17b6\u179b 1", +"Header 2": "\u1780\u17d2\u1794\u17b6\u179b 2", +"Header 3": "\u1780\u17d2\u1794\u17b6\u179b 3", +"Header 4": "\u1780\u17d2\u1794\u17b6\u179b 4", +"Header 5": "\u1780\u17d2\u1794\u17b6\u179b 5", +"Header 6": "\u1780\u17d2\u1794\u17b6\u179b 6", +"Headings": "\u1780\u17d2\u1794\u17b6\u179b", +"Heading 1": "\u1780\u17d2\u1794\u17b6\u179b 1", +"Heading 2": "\u1780\u17d2\u1794\u17b6\u179b 2", +"Heading 3": "\u1780\u17d2\u1794\u17b6\u179b 3", +"Heading 4": "\u1780\u17d2\u1794\u17b6\u179b 4", +"Heading 5": "\u1780\u17d2\u1794\u17b6\u179b 5", +"Heading 6": "\u1780\u17d2\u1794\u17b6\u179b 6", +"Div": "Div", +"Pre": "Pre", +"Code": "\u1780\u17bc\u178a", +"Paragraph": "\u1780\u1790\u17b6\u1781\u178e\u17d2\u178c", +"Blockquote": "\u1794\u17d2\u179b\u17bb\u1780\u200b\u1796\u17b6\u1780\u17d2\u1799\u200b\u179f\u1798\u17d2\u179a\u1784\u17cb", +"Inline": "\u1780\u17d2\u1793\u17bb\u1784\u200b\u1794\u1793\u17d2\u1791\u17b6\u178f\u17cb", +"Blocks": "\u1794\u17d2\u179b\u17bb\u1780", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "\u1780\u17b6\u179a\u200b\u1794\u17b7\u1791\u200b\u1797\u17d2\u1787\u17b6\u1794\u17cb\u200b\u1796\u17c1\u179b\u200b\u1793\u17c1\u17c7 \u179f\u17d2\u1790\u17b7\u178f\u200b\u1780\u17d2\u1793\u17bb\u1784\u200b\u1794\u17c2\u1794\u200b\u1795\u17c2\u1793\u200b\u17a2\u1780\u17d2\u179f\u179a\u200b\u1792\u1798\u17d2\u1798\u178f\u17b6\u17d4 \u1794\u1785\u17d2\u1785\u17bb\u1794\u17d2\u1794\u1793\u17d2\u1793\u200b\u1793\u17c1\u17c7 \u1798\u17b6\u178f\u17b7\u1780\u17b6\u200b\u1791\u17b6\u17c6\u1784\u200b\u17a1\u17b6\u1799\u200b\u1793\u17b9\u1784\u200b\u178f\u17d2\u179a\u17bc\u179c\u200b\u1794\u17b6\u1793\u200b\u1794\u17b7\u1791\u200b\u1797\u17d2\u1787\u17b6\u1794\u17cb\u200b\u1787\u17b6\u200b\u17a2\u1780\u17d2\u179f\u179a\u200b\u1792\u1798\u17d2\u1798\u178f\u17b6 \u179b\u17bb\u17c7\u178f\u17d2\u179a\u17b6\u200b\u178f\u17c2\u200b\u17a2\u17d2\u1793\u1780\u200b\u1794\u17b7\u1791\u200b\u1787\u1798\u17d2\u179a\u17be\u179f\u200b\u1793\u17c1\u17c7\u17d4", +"Font Family": "\u1782\u17d2\u179a\u17bd\u179f\u17b6\u179a\u200b\u1796\u17bb\u1798\u17d2\u1796\u200b\u17a2\u1780\u17d2\u179f\u179a", +"Font Sizes": "\u1791\u17c6\u17a0\u17c6\u200b\u17a2\u1780\u17d2\u179f\u179a", +"Class": "Class", +"Browse for an image": "\u179a\u1780\u1798\u17be\u179b\u200b\u179a\u17bc\u1794\u1797\u17b6\u1796", +"OR": "\u17ac", +"Drop an image here": "\u1791\u1798\u17d2\u179b\u17b6\u1794\u17cb\u200b\u179a\u17bc\u1794\u1797\u17b6\u1796\u200b\u1793\u17c5\u200b\u178f\u17d2\u179a\u1784\u17cb\u200b\u1793\u17c1\u17c7", +"Upload": "\u1795\u17d2\u1791\u17bb\u1780\u17a1\u17be\u1784", +"Default": "\u179b\u17c6\u1793\u17b6\u17c6\u200b\u178a\u17be\u1798", +"Circle": "\u1798\u17bc\u179b", +"Disc": "\u1790\u17b6\u179f", +"Square": "\u1787\u17d2\u179a\u17bb\u1784", +"Lower Alpha": "\u17a2\u1780\u17d2\u179f\u179a\u200b\u178f\u17bc\u1785", +"Lower Greek": "\u179b\u17c1\u1781\u200b\u1780\u17d2\u179a\u17b7\u1780\u200b\u178f\u17bc\u1785", +"Lower Roman": "\u179b\u17c1\u1781\u200b\u179a\u17c9\u17bc\u1798\u17c9\u17b6\u17c6\u1784\u200b\u178f\u17bc\u1785", +"Upper Alpha": "\u17a2\u1780\u17d2\u179f\u179a\u200b\u1792\u17c6", +"Upper Roman": "\u179b\u17c1\u1781\u200b\u179a\u17c9\u17bc\u1798\u17c9\u17b6\u17c6\u1784\u200b\u1792\u17c6", +"Anchor": "\u1799\u17bb\u1790\u17d2\u1780\u17b6", +"Name": "\u1788\u17d2\u1798\u17c4\u17c7", +"Id": "Id", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "Id \u1782\u17bd\u179a\u178f\u17c2\u200b\u1795\u17d2\u178a\u17be\u1798\u200b\u1787\u17b6\u1798\u17bd\u1799\u200b\u178f\u17bd\u17a2\u1780\u17d2\u179f\u179a \u17a0\u17be\u1799\u200b\u1794\u1793\u17d2\u178f\u200b\u1787\u17b6\u1798\u17bd\u1799\u200b\u178f\u17c2\u200b\u178f\u17bd\u17a2\u1780\u17d2\u179f\u179a \u179b\u17c1\u1781 \u179f\u1789\u17d2\u1789\u17b6\u200b\u178a\u1780 \u179f\u1789\u17d2\u1789\u17b6\u200b\u1785\u17bb\u1785 \u179f\u1789\u17d2\u1789\u17b6\u200b\u1785\u17bb\u1785\u1796\u17b8\u179a \u17ac\u200b\u1794\u1793\u17d2\u1791\u17b6\u178f\u17cb\u200b\u1780\u17d2\u179a\u17c4\u1798\u17d4", +"You have unsaved changes are you sure you want to navigate away?": "\u1798\u17b6\u1793\u200b\u1794\u1793\u17d2\u179b\u17b6\u179f\u17cb\u200b\u1794\u17d2\u178a\u17bc\u179a\u200b\u1798\u17b7\u1793\u200b\u1791\u17b6\u1793\u17cb\u200b\u1794\u17b6\u1793\u200b\u179a\u1780\u17d2\u179f\u17b6\u200b\u1791\u17bb\u1780\u17d4 \u178f\u17be\u200b\u17a2\u17d2\u1793\u1780\u200b\u1796\u17b7\u178f\u200b\u1787\u17b6\u200b\u1785\u1784\u17cb\u200b\u1785\u17b6\u1780\u200b\u1785\u17c1\u1789\u200b\u1796\u17b8\u1791\u17b8\u1793\u17c1\u17c7\u200b\u1798\u17c2\u1793\u1791\u17c1?", +"Restore last draft": "\u179f\u17d2\u178a\u17b6\u179a\u200b\u179f\u17c1\u1785\u1780\u17d2\u178a\u17b8\u200b\u1796\u17d2\u179a\u17b6\u1784\u200b\u1796\u17b8\u200b\u1798\u17bb\u1793", +"Special character": "\u178f\u17bd\u200b\u17a2\u1780\u17d2\u179f\u179a\u200b\u1796\u17b7\u179f\u17c1\u179f", +"Source code": "\u17a2\u1780\u17d2\u179f\u179a\u200b\u1780\u17bc\u178a", +"Insert\/Edit code sample": "\u1794\u1789\u17d2\u1785\u17bc\u179b\/\u1780\u17c2\u179f\u1798\u17d2\u179a\u17bd\u179b \u1780\u17bc\u178a\u200b\u1782\u17c6\u179a\u17bc", +"Language": "\u1797\u17b6\u179f\u17b6", +"Color": "\u1796\u178e\u17cc", +"R": "R", +"G": "G", +"B": "B", +"Left to right": "\u1786\u17d2\u179c\u17c1\u1784\u200b\u1791\u17c5\u200b\u179f\u17d2\u178a\u17b6\u17c6", +"Right to left": "\u179f\u17d2\u178a\u17b6\u17c6\u200b\u1791\u17c5\u200b\u1786\u17d2\u179c\u17c1\u1784", +"Emoticons": "\u179a\u17bc\u1794\u200b\u179f\u1789\u17d2\u1789\u17b6\u178e\u200b\u17a2\u17b6\u179a\u1798\u17d2\u1798\u178e\u17cd", +"Document properties": "\u179b\u1780\u17d2\u1781\u178e\u17c8\u200b\u179f\u1798\u17d2\u1794\u178f\u17d2\u178f\u17b7\u200b\u17af\u1780\u179f\u17b6\u179a", +"Title": "\u1785\u17c6\u178e\u1784\u200b\u1787\u17be\u1784", +"Keywords": "\u1796\u17b6\u1780\u17d2\u1799\u200b\u1782\u1793\u17d2\u179b\u17b9\u17c7", +"Description": "\u179f\u17c1\u1785\u1780\u17d2\u178a\u17b8\u200b\u17a2\u1792\u17b7\u1794\u17d2\u1794\u17b6\u1799", +"Robots": "\u179a\u17bc\u1794\u1799\u1793\u17d2\u178f", +"Author": "\u17a2\u17d2\u1793\u1780\u200b\u1793\u17b7\u1796\u1793\u17d2\u1792", +"Encoding": "\u1780\u17b6\u179a\u200b\u17a2\u17ca\u17b8\u1793\u1780\u17bc\u178a", +"Fullscreen": "\u1796\u17c1\u1789\u200b\u17a2\u17c1\u1780\u17d2\u179a\u1784\u17cb", +"Action": "\u179f\u1780\u1798\u17d2\u1798\u1797\u17b6\u1796", +"Shortcut": "\u1795\u17d2\u179b\u17bc\u179c\u1780\u17b6\u178f\u17cb", +"Help": "\u1787\u17c6\u1793\u17bd\u1799", +"Address": "\u17a2\u17b6\u179f\u1799\u178a\u17d2\u178b\u17b6\u1793", +"Focus to menubar": "\u1795\u17d2\u178a\u17c4\u178f\u200b\u1791\u17c5\u179b\u17be\u200b\u179a\u1794\u17b6\u179a\u200b\u1798\u17c9\u17ba\u1793\u17bb\u1799", +"Focus to toolbar": "\u1795\u17d2\u178a\u17c4\u178f\u200b\u1791\u17c5\u179b\u17be\u200b\u179a\u1794\u17b6\u179a\u200b\u17a7\u1794\u1780\u179a\u178e\u17cd", +"Focus to element path": "\u1795\u17d2\u178a\u17c4\u178f\u200b\u1791\u17c5\u179b\u17be\u200b\u1791\u17b8\u178f\u17b6\u17c6\u1784\u200b\u179a\u1794\u179f\u17cb\u200b\u1792\u17b6\u178f\u17bb", +"Focus to contextual toolbar": "\u1795\u17d2\u178a\u17c4\u178f\u200b\u1791\u17c5\u200b\u179b\u17be\u200b\u179a\u1794\u17b6\u179a\u17a7\u1794\u1780\u179a\u178e\u17cd\u200b\u178f\u17b6\u1798\u200b\u1794\u179a\u17b7\u1794\u1791", +"Insert link (if link plugin activated)": "\u1794\u1789\u17d2\u1785\u17bc\u179b\u200b\u178f\u17c6\u178e (\u1794\u17d2\u179a\u179f\u17b7\u1793\u1794\u17be\u200b\u1780\u1798\u17d2\u1798\u179c\u17b7\u1792\u17b8 plugin \u1794\u17b6\u1793\u1794\u17be\u1780)", +"Save (if save plugin activated)": "\u179a\u1780\u17d2\u179f\u17b6\u1791\u17bb\u1780 (\u1794\u17d2\u179a\u179f\u17b7\u1793\u1794\u17be\u200b\u1780\u1798\u17d2\u1798\u179c\u17b7\u1792\u17b8 save \u1794\u17b6\u1793\u1794\u17be\u1780)", +"Find (if searchreplace plugin activated)": "\u179f\u17d2\u179c\u17c2\u1784\u179a\u1780 (\u1794\u17d2\u179a\u179f\u17b7\u1793\u200b\u1794\u17be\u200b\u1780\u1798\u17d2\u1798\u179c\u17b7\u1792\u17b8 searchreplace \u1794\u17b6\u1793\u200b\u1794\u17be\u1780)", +"Plugins installed ({0}):": "\u1780\u1798\u17d2\u1798\u179c\u17b7\u1792\u17b8\u200b\u1794\u1793\u17d2\u1790\u17c2\u1798\u200b\u178a\u17c2\u179b\u1794\u17b6\u1793\u200b\u178a\u17c6\u17a1\u17be\u1784 ({0})\u17d6", +"Premium plugins:": "\u1780\u1798\u17d2\u1798\u179c\u17b7\u1792\u17b8\u200b\u1782\u17b7\u178f\u200b\u1794\u17d2\u179a\u17b6\u1780\u17cb\u17d6", +"Learn more...": "\u179f\u17b7\u1780\u17d2\u179f\u17b6\u200b\u1794\u1793\u17d2\u1790\u17c2\u1798...", +"You are using {0}": "\u17a2\u17d2\u1793\u1780\u200b\u1780\u17c6\u1796\u17bb\u1784\u200b\u1794\u17d2\u179a\u17be {0}", +"Horizontal line": "\u1794\u1793\u17d2\u1791\u17b6\u178f\u17cb\u200b\u178a\u17c1\u1780", +"Insert\/edit image": "\u1794\u1789\u17d2\u1785\u17bc\u179b\/\u1780\u17c2 \u179a\u17bc\u1794\u200b\u1797\u17b6\u1796", +"Image description": "\u179f\u17c1\u1785\u1780\u17d2\u178a\u17b8\u200b\u17a2\u1792\u17b7\u1794\u17d2\u1794\u17b6\u1799\u200b\u1796\u17b8\u200b\u179a\u17bc\u1794", +"Source": "\u1794\u17d2\u179a\u1797\u1796", +"Dimensions": "\u179c\u17b7\u1798\u17b6\u178f\u17d2\u179a", +"Constrain proportions": " \u1794\u1784\u17d2\u1781\u17c6\u200b\u17b2\u17d2\u1799\u200b\u1798\u17b6\u1793\u200b\u179f\u1798\u17b6\u1798\u17b6\u178f\u17d2\u179a", +"General": "\u1791\u17bc\u1791\u17c5", +"Advanced": "\u1780\u1798\u17d2\u179a\u17b7\u178f\u200b\u1781\u17d2\u1796\u179f\u17cb", +"Style": "\u179a\u1785\u1793\u17b6\u1794\u1790", +"Vertical space": "\u179b\u17c6\u17a0\u200b\u1794\u1789\u17d2\u1788\u179a", +"Horizontal space": "\u179b\u17c6\u17a0\u200b\u1795\u17d2\u178a\u17c1\u1780", +"Border": "\u179f\u17ca\u17bb\u1798", +"Insert image": "\u1794\u1789\u17d2\u1785\u17bc\u179b\u200b\u179a\u17bc\u1794\u200b\u1797\u17b6\u1796", +"Image": "\u179a\u17bc\u1794\u1797\u17b6\u1796", +"Image list": "\u1794\u1789\u17d2\u1787\u17b8\u179a\u17bc\u1794\u1797\u17b6\u1796", +"Rotate counterclockwise": "\u1794\u1784\u17d2\u179c\u17b7\u179b\u200b\u1785\u17d2\u179a\u17b6\u179f\u200b\u1791\u17d2\u179a\u1793\u17b7\u1785\u200b\u1793\u17b6\u17a1\u17b7\u1780\u17b6", +"Rotate clockwise": "\u1794\u1784\u17d2\u179c\u17b7\u179b\u200b\u179f\u17d2\u179a\u1794\u200b\u1791\u17d2\u179a\u1793\u17b7\u1785\u200b\u1793\u17b6\u17a1\u17b7\u1780\u17b6", +"Flip vertically": "\u178f\u17d2\u179a\u17a1\u1794\u17cb\u200b\u1794\u1789\u17d2\u1788\u179a", +"Flip horizontally": "\u178f\u17d2\u179a\u17a1\u1794\u17cb\u200b\u1795\u17d2\u178a\u17c1\u1780", +"Edit image": "\u1780\u17c2\u179f\u1798\u17d2\u179a\u17bd\u179b\u200b\u179a\u17bc\u1794\u1797\u17b6\u1796", +"Image options": "\u1787\u1798\u17d2\u179a\u17be\u179f\u200b\u179a\u17bc\u1794\u1797\u17b6\u1796", +"Zoom in": "\u1796\u1784\u17d2\u179a\u17b8\u1780", +"Zoom out": "\u1794\u1784\u17d2\u179a\u17bd\u1798", +"Crop": "\u1785\u17d2\u179a\u17b9\u1794", +"Resize": "\u1794\u17d2\u178a\u17bc\u179a\u200b\u1791\u17c6\u17a0\u17c6", +"Orientation": "\u1791\u17b7\u179f", +"Brightness": "\u1796\u1793\u17d2\u179b\u17ba", +"Sharpen": "\u1785\u17d2\u1794\u17b6\u179f\u17cb", +"Contrast": "\u1780\u1798\u17d2\u179a\u17b7\u178f\u200b\u1796\u178e\u17cc", +"Color levels": "\u1780\u1798\u17d2\u179a\u17b7\u178f\u200b\u1796\u178e\u17cc", +"Gamma": "\u17a0\u17d2\u1782\u17b6\u1798\u17c9\u17b6", +"Invert": "\u178a\u17b6\u1780\u17cb\u200b\u1794\u1789\u17d2\u1785\u17d2\u179a\u17b6\u179f", +"Apply": "\u17a2\u1793\u17bb\u179c\u178f\u17d2\u178f", +"Back": "\u1790\u1799\u1780\u17d2\u179a\u17c4\u1799", +"Insert date\/time": "\u1794\u1789\u17d2\u1785\u17bc\u179b\u200b\u1780\u17b6\u179b\u200b\u1794\u179a\u17b7\u1785\u17d2\u1786\u17c1\u1791\/\u1798\u17c9\u17c4\u1784", +"Date\/time": "\u1780\u17b6\u179b\u1794\u179a\u17b7\u1785\u17d2\u1786\u17c1\u1791\/\u1798\u17c9\u17c4\u1784", +"Insert link": "\u1794\u1789\u17d2\u1785\u17bc\u179b\u200b\u178f\u17c6\u178e", +"Insert\/edit link": "\u1794\u1789\u17d2\u1785\u17bc\u179b\/\u1780\u17c2 \u178f\u17c6\u178e", +"Text to display": "\u17a2\u1780\u17d2\u179f\u179a\u200b\u178f\u17d2\u179a\u17bc\u179c\u200b\u1794\u1784\u17d2\u17a0\u17b6\u1789", +"Url": "Url", +"Target": "\u1791\u17b7\u179f\u178a\u17c5", +"None": "\u1798\u17b7\u1793\u200b\u1798\u17b6\u1793", +"New window": "\u1795\u17d2\u1791\u17b6\u17c6\u1784\u200b\u179c\u17b8\u1793\u178a\u17bc\u200b\u1790\u17d2\u1798\u17b8", +"Remove link": "\u178a\u1780\u200b\u178f\u17c6\u178e\u200b\u1785\u17c1\u1789", +"Anchors": "\u1799\u17bb\u1790\u17d2\u1780\u17b6", +"Link": "\u178f\u17c6\u178e", +"Paste or type a link": "\u1794\u17b7\u1791\u1797\u17d2\u1787\u17b6\u1794\u17cb\u200b\u17ac\u200b\u179c\u17b6\u1799\u1794\u1789\u17d2\u1785\u17bc\u179b\u200b\u178f\u17c6\u178e", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "\u17a2\u17d2\u1793\u1780\u200b\u1794\u17b6\u1793\u200b\u1794\u1789\u17d2\u1785\u17bc\u179b URL \u178a\u17c2\u179b\u200b\u1798\u17b6\u1793\u200b\u179f\u178e\u17d2\u178b\u17b6\u1793\u200b\u178a\u17bc\u1785\u200b\u17a2\u17b6\u179f\u1799\u178a\u17d2\u178b\u17b6\u1793\u200b\u17a2\u17ca\u17b8\u1798\u17c2\u179b\u17d4 \u178f\u17be\u200b\u17a2\u17d2\u1793\u1780\u200b\u1785\u1784\u17cb\u200b\u1794\u1793\u17d2\u1790\u17c2\u1798\u200b\u1794\u17bb\u1796\u17d2\u179c\u1794\u200b\u1791 mailto: \u178a\u17c2\u179a\u200b\u17ac\u1791\u17c1?", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "\u17a2\u17d2\u1793\u1780\u200b\u1794\u17b6\u1793\u200b\u1794\u1789\u17d2\u1785\u17bc\u179b URL \u178a\u17c2\u179b\u200b\u1787\u17b6\u200b\u178f\u17c6\u178e\u200b\u1791\u17c5\u200b\u1781\u17b6\u1784\u200b\u1780\u17d2\u179a\u17c5\u17d4 \u178f\u17be\u200b\u17a2\u17d2\u1793\u1780\u200b\u1785\u1784\u17cb\u200b\u1794\u1793\u17d2\u1790\u17c2\u1798\u200b\u1794\u17bb\u1796\u17d2\u179c\u1794\u200b\u1791 http:\/\/ \u178a\u17c2\u179a\u200b\u17ac\u1791\u17c1?", +"Link list": "\u1794\u1789\u17d2\u1787\u17b8\u178f\u17c6\u178e", +"Insert video": "\u1794\u1789\u17d2\u1785\u17bc\u179b\u200b\u179c\u17b8\u178a\u17c1\u17a2\u17bc", +"Insert\/edit video": "\u1794\u1789\u17d2\u1785\u17bc\u179b\/\u1780\u17c2 \u179c\u17b8\u178a\u17c1\u17a2\u17bc", +"Insert\/edit media": "\u1794\u1789\u17d2\u1787\u17bc\u179b\u200b\/\u1780\u17c2\u179f\u1798\u17d2\u179a\u17bd\u179b \u1798\u17c1\u178c\u17b6", +"Alternative source": "\u1794\u17d2\u179a\u1797\u1796\u200b\u178a\u1791\u17c3\u200b\u1791\u17c0\u178f", +"Poster": "\u17a2\u17d2\u1793\u1780\u200b\u1795\u17d2\u179f\u17b6\u1799", +"Paste your embed code below:": "\u1794\u17b7\u1791\u200b\u1797\u17d2\u1787\u17b6\u1794\u17cb\u200b\u1780\u17bc\u178a\u200b\u1794\u1784\u17d2\u1780\u1794\u17cb\u200b\u1793\u17c5\u200b\u1781\u17b6\u1784\u200b\u1780\u17d2\u179a\u17c4\u1798:", +"Embed": "\u1794\u1784\u17d2\u1780\u1794\u17cb", +"Media": "\u1798\u17c1\u178c\u17b6", +"Nonbreaking space": "\u178a\u17c6\u178e\u1780\u200b\u1783\u17d2\u179b\u17b6\u200b\u1798\u17b7\u1793\u200b\u1794\u17c6\u1794\u17c2\u1780", +"Page break": "\u1794\u17c6\u1794\u17c2\u1780\u200b\u1791\u17c6\u1796\u17d0\u179a", +"Paste as text": "\u1794\u17b7\u1791\u200b\u1797\u17d2\u1787\u17b6\u1794\u17cb\u200b\u1787\u17b6\u200b\u17a2\u1780\u17d2\u179f\u179a", +"Preview": "\u1798\u17be\u179b\u200b\u1787\u17b6\u200b\u1798\u17bb\u1793", +"Print": "\u1794\u17c4\u17c7\u200b\u1796\u17bb\u1798\u17d2\u1796", +"Save": "\u179a\u1780\u17d2\u179f\u17b6\u200b\u1791\u17bb\u1780", +"Find": "\u179f\u17d2\u179c\u17c2\u1784\u200b\u179a\u1780", +"Replace with": "\u1787\u17c6\u1793\u17bd\u179f\u200b\u178a\u17c4\u1799", +"Replace": "\u1787\u17c6\u1793\u17bd\u179f", +"Replace all": "\u1787\u17c6\u1793\u17bd\u179f\u200b\u1791\u17b6\u17c6\u1784\u200b\u17a2\u179f\u17cb", +"Prev": "\u1780\u17d2\u179a\u17c4\u1799", +"Next": "\u1798\u17bb\u1781", +"Find and replace": "\u179f\u17d2\u179c\u17c2\u1784\u200b\u179a\u1780\u200b\u1793\u17b7\u1784\u200b\u1787\u17c6\u1793\u17bd\u179f", +"Could not find the specified string.": "\u1798\u17b7\u1793\u200b\u17a2\u17b6\u1785\u200b\u179a\u1780\u200b\u1783\u17be\u1789\u200b\u1781\u17d2\u179f\u17c2\u200b\u17a2\u1780\u17d2\u179f\u179a\u200b\u178a\u17c2\u179b\u200b\u1794\u17b6\u1793\u200b\u1780\u17c6\u178e\u178f\u17cb\u17d4", +"Match case": "\u1780\u179a\u178e\u17b8\u200b\u178a\u17c6\u178e\u17bc\u1785", +"Whole words": "\u1796\u17b6\u1780\u17d2\u1799\u200b\u1791\u17b6\u17c6\u1784\u200b\u1798\u17bc\u179b", +"Spellcheck": "\u1796\u17b7\u1793\u17b7\u178f\u17d2\u1799\u200b\u17a2\u1780\u17d2\u1781\u179a\u17b6\u179c\u17b7\u179a\u17bb\u1791\u17d2\u1792", +"Ignore": "\u1798\u17b7\u1793\u200b\u17a2\u17be\u200b\u1796\u17be", +"Ignore all": "\u1798\u17b7\u1793\u200b\u17a2\u17be\u1796\u17be\u200b\u1791\u17b6\u17c6\u1784\u200b\u17a2\u179f\u17cb", +"Finish": "\u1794\u1789\u17d2\u1785\u1794\u17cb", +"Add to Dictionary": "\u1794\u1793\u17d2\u1790\u17c2\u1798\u200b\u1791\u17c5\u200b\u179c\u1785\u1793\u17b6\u1793\u17bb\u1780\u17d2\u179a\u1798", +"Insert table": "\u1794\u1789\u17d2\u1785\u17bc\u179b\u200b\u178f\u17b6\u179a\u17b6\u1784", +"Table properties": "\u179b\u1780\u17d2\u1781\u178e\u17c8\u200b\u178f\u17b6\u179a\u17b6\u1784", +"Delete table": "\u179b\u17bb\u1794\u200b\u178f\u17b6\u179a\u17b6\u1784", +"Cell": "\u1780\u17d2\u179a\u17a1\u17b6", +"Row": "\u1787\u17bd\u179a\u200b\u178a\u17c1\u1780", +"Column": "\u1787\u17bd\u179a\u200b\u1788\u179a", +"Cell properties": "\u179b\u1780\u17d2\u1781\u178e\u17c8\u200b\u1780\u17d2\u179a\u17a1\u17b6", +"Merge cells": "\u1794\u1789\u17d2\u1785\u17bc\u179b\u200b\u1780\u17d2\u179a\u17a1\u17b6\u200b\u1785\u17bc\u179b\u200b\u1782\u17d2\u1793\u17b6", +"Split cell": "\u1789\u17c2\u1780\u200b\u1780\u17d2\u179a\u17a1\u17b6", +"Insert row before": "\u1794\u1789\u17d2\u1785\u17bc\u179b\u200b\u1788\u17bd\u179a\u200b\u178a\u17c1\u1780\u200b\u1796\u17b8\u200b\u1798\u17bb\u1781", +"Insert row after": "\u1794\u1789\u17d2\u1785\u17bc\u179b\u200b\u1787\u17bd\u179a\u200b\u178a\u17c1\u1780\u200b\u1796\u17b8\u200b\u1780\u17d2\u179a\u17c4\u1799", +"Delete row": "\u179b\u17bb\u1794\u200b\u1787\u17bd\u179a\u200b\u178a\u17c1\u1780", +"Row properties": "\u179b\u1780\u17d2\u1781\u178e\u17c8\u200b\u1787\u17bd\u179a\u200b\u178a\u17c1\u1780", +"Cut row": "\u1780\u17b6\u178f\u17cb\u200b\u1787\u17bd\u179a\u200b\u178a\u17c1\u1780", +"Copy row": "\u1785\u1798\u17d2\u179b\u1784\u200b\u1787\u17bd\u179a\u200b\u178a\u17c1\u1780", +"Paste row before": "\u1794\u17b7\u1791\u200b\u1797\u17d2\u1787\u17b6\u1794\u17cb\u200b\u1787\u17bd\u179a\u200b\u178a\u17c1\u1780\u200b\u1796\u17b8\u200b\u1798\u17bb\u1781", +"Paste row after": "\u1794\u17b7\u1791\u200b\u1797\u17d2\u1787\u17b6\u1794\u17cb\u200b\u1787\u17bd\u179a\u200b\u178a\u17c1\u1780\u200b\u1796\u17b8\u200b\u1780\u17d2\u179a\u17c4\u1799", +"Insert column before": "\u1794\u1789\u17d2\u1785\u17bc\u179b\u200b\u1787\u17bd\u179a\u200b\u1788\u179a\u200b\u1796\u17b8\u200b\u1798\u17bb\u1781", +"Insert column after": "\u1794\u1789\u17d2\u1787\u17bc\u179b\u200b\u1787\u17bd\u179a\u200b\u178a\u17c1\u1780\u200b\u1796\u17b8\u200b\u1780\u17d2\u179a\u17c4\u1799", +"Delete column": "\u179b\u17bb\u1794\u200b\u1787\u17bd\u179a\u200b\u1788\u179a", +"Cols": "\u1787\u17bd\u179a\u200b\u1788\u179a", +"Rows": "\u1787\u17bd\u179a\u200b\u178a\u17c1\u1780", +"Width": "\u1791\u1791\u17b9\u1784", +"Height": "\u1780\u1798\u17d2\u1796\u179f\u17cb", +"Cell spacing": "\u1782\u1798\u17d2\u179b\u17b6\u178f\u200b\u1780\u17d2\u179a\u17a1\u17b6", +"Cell padding": "\u1785\u1793\u17d2\u179b\u17c4\u17c7\u200b\u1780\u17d2\u179a\u17a1\u17b6", +"Caption": "\u1785\u17c6\u178e\u1784\u200b\u1787\u17be\u1784", +"Left": "\u1786\u17d2\u179c\u17c1\u1784", +"Center": "\u1780\u178e\u17d2\u178a\u17b6\u179b", +"Right": "\u179f\u17d2\u178a\u17b6\u17c6", +"Cell type": "\u1794\u17d2\u179a\u1797\u17c1\u1791\u200b\u1780\u17d2\u179a\u17a1\u17b6", +"Scope": "\u179c\u17b7\u179f\u17b6\u179b\u200b\u1797\u17b6\u1796", +"Alignment": "\u1780\u17b6\u179a\u200b\u178f\u1798\u17d2\u179a\u17b9\u1798", +"H Align": "\u1780\u17b6\u179a\u200b\u178f\u1798\u17d2\u179a\u17b9\u1798\u200b\u1795\u17d2\u178a\u17c1\u1780", +"V Align": "\u1780\u17b6\u179a\u200b\u178f\u1798\u17d2\u179a\u17b9\u1798\u200b\u1794\u1789\u17d2\u1788\u179a", +"Top": "\u179b\u17be", +"Middle": "\u1780\u178e\u17d2\u178a\u17b6\u179b", +"Bottom": "\u1780\u17d2\u179a\u17c4\u1798", +"Header cell": "\u1780\u17d2\u179a\u17a1\u17b6\u200b\u1785\u17c6\u178e\u1784\u200b\u1787\u17be\u1784", +"Row group": "\u1780\u17d2\u179a\u17bb\u1798\u200b\u1787\u17bd\u179a\u200b\u178a\u17c1\u1780", +"Column group": "\u1780\u17d2\u179a\u17bb\u1798\u200b\u1787\u17bd\u179a\u200b\u1788\u179a", +"Row type": "\u1794\u17d2\u179a\u1797\u17c1\u1791\u200b\u1787\u17bd\u179a\u200b\u178a\u17c1\u1780", +"Header": "\u1785\u17c6\u178e\u1784\u200b\u1787\u17be\u1784", +"Body": "\u178f\u17bd\u200b\u179f\u17c1\u1785\u1780\u17d2\u178a\u17b8", +"Footer": "\u1794\u178b\u1798\u200b\u1780\u1790\u17b6", +"Border color": "\u1796\u178e\u17cc\u200b\u179f\u17ca\u17bb\u1798", +"Insert template": "\u1794\u1789\u17d2\u1785\u17bc\u179b\u200b\u1796\u17bb\u1798\u17d2\u1796\u200b\u1782\u1798\u17d2\u179a\u17bc", +"Templates": "\u1796\u17bb\u1798\u17d2\u1796\u200b\u1782\u17c6\u179a\u17bc", +"Template": "\u1796\u17bb\u1798\u17d2\u1796\u1782\u17c6\u179a\u17bc", +"Text color": "\u1796\u178e\u17cc\u200b\u17a2\u1780\u17d2\u179f\u179a", +"Background color": "\u1796\u178e\u17cc\u200b\u1795\u17d2\u1791\u17c3\u200b\u1780\u17d2\u179a\u17c4\u1799", +"Custom...": "\u1795\u17d2\u1791\u17b6\u179b\u17cb\u200b\u1781\u17d2\u179b\u17bd\u1793...", +"Custom color": "\u1796\u178e\u17cc\u200b\u1795\u17d2\u1791\u17b6\u179b\u17cb\u200b\u1781\u17d2\u179b\u17bd\u1793", +"No color": "\u1782\u17d2\u1798\u17b6\u1793\u200b\u1796\u178e\u17cc", +"Table of Contents": "\u178f\u17b6\u179a\u17b6\u1784\u200b\u1793\u17c3\u200b\u1798\u17b6\u178f\u17b7\u1780\u17b6", +"Show blocks": "\u1794\u1784\u17d2\u17a0\u17b6\u1789\u200b\u1794\u17d2\u179b\u17bb\u1780", +"Show invisible characters": "\u1794\u1784\u17d2\u17a0\u17b6\u1789\u200b\u178f\u17bd\u200b\u17a2\u1780\u17d2\u179f\u179a\u200b\u1780\u17c6\u1794\u17b6\u17c6\u1784", +"Words: {0}": "\u1796\u17b6\u1780\u17d2\u1799: {0}", +"File": "\u17af\u1780\u179f\u17b6\u179a", +"Edit": "\u1780\u17c2\u1794\u17d2\u179a\u17c2", +"Insert": "\u1794\u1789\u17d2\u1785\u17bc\u179b", +"View": "\u1791\u17b7\u178a\u17d2\u178b\u1797\u17b6\u1796", +"Format": "\u1791\u17d2\u179a\u1784\u17cb\u1791\u17d2\u179a\u17b6\u1799", +"Table": "\u178f\u17b6\u179a\u17b6\u1784", +"Tools": "\u17a7\u1794\u1780\u179a\u178e\u17cd", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "\u1791\u17b8\u178f\u17b6\u17c6\u1784\u200b\u17a2\u1780\u17d2\u179f\u179a\u200b\u179f\u17c6\u1794\u17bc\u179a\u1794\u17c2\u1794\u17d4 \u1785\u17bb\u1785 ALT-F9 \u179f\u1798\u17d2\u179a\u17b6\u1794\u17cb\u200b\u1798\u17c9\u17ba\u1793\u17bb\u1799\u17d4 \u1785\u17bb\u1785 ALT-F10 \u179f\u1798\u17d2\u179a\u17b6\u1794\u17cb\u200b\u179a\u1794\u17b6\u179a\u200b\u17a7\u1794\u1780\u179a\u178e\u17cd\u17d4 \u1785\u17bb\u1785 ALT-0 \u179f\u1798\u17d2\u179a\u17b6\u1794\u17cb\u200b\u1787\u17c6\u1793\u17bd\u1799\u17d4" +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/ko_KR.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/ko_KR.js new file mode 100644 index 0000000000..ce0e42c7cf --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/ko_KR.js @@ -0,0 +1,261 @@ +tinymce.addI18n('ko_KR',{ +"Redo": "\ub2e4\uc2dc\uc2e4\ud589", +"Undo": "\uc2e4\ud589\ucde8\uc18c", +"Cut": "\uc798\ub77c\ub0b4\uae30", +"Copy": "\ubcf5\uc0ac\ud558\uae30", +"Paste": "\ubd99\uc5ec\ub123\uae30", +"Select all": "\uc804\uccb4\uc120\ud0dd", +"New document": "\uc0c8 \ubb38\uc11c", +"Ok": "\ud655\uc778", +"Cancel": "\ucde8\uc18c", +"Visual aids": "\uc2dc\uac01\uad50\uc7ac", +"Bold": "\uad75\uac8c", +"Italic": "\uae30\uc6b8\uc784\uaf34", +"Underline": "\ubc11\uc904", +"Strikethrough": "\ucde8\uc18c\uc120", +"Superscript": "\uc717\ucca8\uc790", +"Subscript": "\uc544\ub798\ucca8\uc790", +"Clear formatting": "\ud3ec\ub9f7\ucd08\uae30\ud654", +"Align left": "\uc67c\ucabd\uc815\ub82c", +"Align center": "\uac00\uc6b4\ub370\uc815\ub82c", +"Align right": "\uc624\ub978\ucabd\uc815\ub82c", +"Justify": "\uc591\ucabd\uc815\ub82c", +"Bullet list": "\uc810\ub9ac\uc2a4\ud2b8", +"Numbered list": "\uc22b\uc790\ub9ac\uc2a4\ud2b8", +"Decrease indent": "\ub0b4\uc5b4\uc4f0\uae30", +"Increase indent": "\ub4e4\uc5ec\uc4f0\uae30", +"Close": "\ub2eb\uae30", +"Formats": "\ud3ec\ub9f7", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "\ube0c\ub77c\uc6b0\uc838\uac00 \ud074\ub9bd\ubcf4\ub4dc \uc811\uadfc\uc744 \ud5c8\uc6a9\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. Ctrl+X\/C\/V \ud0a4\ub97c \uc774\uc6a9\ud574 \uc8fc\uc138\uc694.", +"Headers": "\uc2a4\ud0c0\uc77c", +"Header 1": "\uc81c\ubaa9 1", +"Header 2": "\uc81c\ubaa9 2", +"Header 3": "\uc81c\ubaa9 3", +"Header 4": "\uc81c\ubaa9 4", +"Header 5": "\uc81c\ubaa9 5", +"Header 6": "\uc81c\ubaa9 6", +"Headings": "\uc81c\ubaa9", +"Heading 1": "\uc81c\ubaa9 1", +"Heading 2": "\uc81c\ubaa9 2", +"Heading 3": "\uc81c\ubaa9 3", +"Heading 4": "\uc81c\ubaa9 4", +"Heading 5": "\uc81c\ubaa9 5", +"Heading 6": "\uc81c\ubaa9 6", +"Preformatted": "Preformatted", +"Div": "\uad6c\ubd84", +"Pre": "Pre", +"Code": "\ucf54\ub4dc", +"Paragraph": "\ub2e8\ub77d", +"Blockquote": "\uad6c\ud68d", +"Inline": "\ub77c\uc778 \uc124\uc815", +"Blocks": "\ube14\ub85d \uc124\uc815", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "\uc2a4\ud0c0\uc77c\ubcf5\uc0ac \ub044\uae30. \uc774 \uc635\uc158\uc744 \ub044\uae30 \uc804\uc5d0\ub294 \ubcf5\uc0ac \uc2dc, \uc2a4\ud0c0\uc77c\uc774 \ubcf5\uc0ac\ub418\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4.", +"Font Family": "\uae00\uaf34", +"Font Sizes": "\ud3f0\ud2b8 \uc0ac\uc774\uc988", +"Class": "\ud074\ub798\uc2a4", +"Browse for an image": "\uc774\ubbf8\uc9c0 \ucc3e\uae30", +"OR": "\ud639\uc740", +"Drop an image here": "\uc774\ubbf8\uc9c0 \ub4dc\ub86d", +"Upload": "\uc5c5\ub85c\ub4dc", +"Block": "\ube14\ub85d", +"Align": "\uc815\ub82c", +"Default": "\uae30\ubcf8", +"Circle": "\uc6d0", +"Disc": "\uc6d0\ubc18", +"Square": "\uc0ac\uac01", +"Lower Alpha": "\uc54c\ud30c\ubcb3 \uc18c\ubb38\uc790", +"Lower Greek": "\uadf8\ub9ac\uc2a4\uc5b4 \uc18c\ubb38\uc790", +"Lower Roman": "\ub85c\ub9c8\uc790 \uc18c\ubb38\uc790", +"Upper Alpha": "\uc54c\ud30c\ubcb3 \uc18c\ubb38\uc790", +"Upper Roman": "\ub85c\ub9c8\uc790 \ub300\ubb38\uc790", +"Anchor": "\uc575\ucee4", +"Name": "\uc774\ub984", +"Id": "\uc544\uc774\ub514", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "\uc544\uc774\ub514\ub294 \ubb38\uc790, \uc22b\uc790, \ub300\uc2dc, \uc810, \ucf5c\ub860 \ub610\ub294 \ubc11\uc904\ub85c \uc2dc\uc791\ud574\uc57c\ud569\ub2c8\ub2e4.", +"You have unsaved changes are you sure you want to navigate away?": "\uc800\uc7a5\ud558\uc9c0 \uc54a\uc740 \uc815\ubcf4\uac00 \uc788\uc2b5\ub2c8\ub2e4. \uc774 \ud398\uc774\uc9c0\ub97c \ubc97\uc5b4\ub098\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", +"Restore last draft": "\ub9c8\uc9c0\ub9c9 \ucd08\uc548 \ubcf5\uc6d0", +"Special character": "\ud2b9\uc218\ubb38\uc790", +"Source code": "\uc18c\uc2a4\ucf54\ub4dc", +"Insert\/Edit code sample": "\ucf54\ub4dc\uc0d8\ud50c \uc0bd\uc785\/\ud3b8\uc9d1", +"Language": "\uc5b8\uc5b4", +"Code sample": "\ucf54\ub4dc\uc0d8\ud50c", +"Color": "\uc0c9\uc0c1", +"R": "R", +"G": "G", +"B": "B", +"Left to right": "\uc67c\ucabd\uc5d0\uc11c \uc624\ub978\ucabd", +"Right to left": "\uc624\ub978\ucabd\uc5d0\uc11c \uc67c\ucabd", +"Emoticons": "\uc774\ubaa8\ud2f0\ucf58", +"Document properties": "\ubb38\uc11c \uc18d\uc131", +"Title": "\uc81c\ubaa9", +"Keywords": "\ud0a4\uc6cc\ub4dc", +"Description": "\uc124\uba85", +"Robots": "\ub85c\ubd07", +"Author": "\uc800\uc790", +"Encoding": "\uc778\ucf54\ub529", +"Fullscreen": "\uc804\uccb4\ud654\uba74", +"Action": "\ub3d9\uc791", +"Shortcut": "\ub2e8\ucd95\ud0a4", +"Help": "\ub3c4\uc6c0\ub9d0", +"Address": "\uc8fc\uc18c", +"Focus to menubar": "\uba54\ub274\uc5d0 \ud3ec\ucee4\uc2a4", +"Focus to toolbar": "\ud234\ubc14\uc5d0 \ud3ec\ucee4\uc2a4", +"Focus to element path": "element path\uc5d0 \ud3ec\ucee4\uc2a4", +"Focus to contextual toolbar": "\ucf04\ud14d\uc2a4\ud2b8 \ud234\ubc14\uc5d0 \ud3ec\ucee4\uc2a4", +"Insert link (if link plugin activated)": "\ub9c1\ud06c \uc0bd\uc785 (link \ud50c\ub7ec\uadf8\uc778\uc774 \ud65c\uc131\ud654\ub41c \uc0c1\ud0dc\uc5d0\uc11c)", +"Save (if save plugin activated)": "\uc800\uc7a5 (save \ud50c\ub7ec\uadf8\uc778\uc774 \ud65c\uc131\ud654\ub41c \uc0c1\ud0dc\uc5d0\uc11c)", +"Find (if searchreplace plugin activated)": "\ucc3e\uae30(searchreplace \ud50c\ub7ec\uadf8\uc778\uc774 \ud65c\uc131\ud654\ub41c \uc0c1\ud0dc\uc5d0\uc11c)", +"Plugins installed ({0}):": "\uc124\uce58\ub41c \ud50c\ub7ec\uadf8\uc778 ({0}):", +"Premium plugins:": "\uace0\uae09 \ud50c\ub7ec\uadf8\uc778", +"Learn more...": "\uc880 \ub354 \uc0b4\ud3b4\ubcf4\uae30", +"You are using {0}": "{0}\ub97c \uc0ac\uc6a9\uc911", +"Plugins": "\ud50c\ub7ec\uadf8\uc778", +"Handy Shortcuts": "\ub2e8\ucd95\ud0a4", +"Horizontal line": "\uac00\ub85c", +"Insert\/edit image": "\uc774\ubbf8\uc9c0 \uc0bd\uc785\/\uc218\uc815", +"Image description": "\uc774\ubbf8\uc9c0 \uc124\uba85", +"Source": "\uc18c\uc2a4", +"Dimensions": "\ud06c\uae30", +"Constrain proportions": "\uc791\uc5c5 \uc81c\ud55c", +"General": "\uc77c\ubc18", +"Advanced": "\uace0\uae09", +"Style": "\uc2a4\ud0c0\uc77c", +"Vertical space": "\uc218\uc9c1 \uacf5\ubc31", +"Horizontal space": "\uc218\ud3c9 \uacf5\ubc31", +"Border": "\ud14c\ub450\ub9ac", +"Insert image": "\uc774\ubbf8\uc9c0 \uc0bd\uc785", +"Image": "\uc774\ubbf8\uc9c0", +"Image list": "\uc774\ubbf8\uc9c0 \ubaa9\ub85d", +"Rotate counterclockwise": "\uc2dc\uacc4\ubc18\ub300\ubc29\ud5a5\uc73c\ub85c \ud68c\uc804", +"Rotate clockwise": "\uc2dc\uacc4\ubc29\ud5a5\uc73c\ub85c \ud68c\uc804", +"Flip vertically": "\uc218\uc9c1 \ub4a4\uc9d1\uae30", +"Flip horizontally": "\uc218\ud3c9 \ub4a4\uc9d1\uae30", +"Edit image": "\uc774\ubbf8\uc9c0 \ud3b8\uc9d1", +"Image options": "\uc774\ubbf8\uc9c0 \uc635\uc158", +"Zoom in": "\ud655\ub300", +"Zoom out": "\ucd95\uc18c", +"Crop": "\uc790\ub974\uae30", +"Resize": "\ud06c\uae30 \uc870\uc808", +"Orientation": "\ubc29\ud5a5", +"Brightness": "\ubc1d\uae30", +"Sharpen": "\uc120\uba85\ud558\uac8c", +"Contrast": "\ub300\ube44", +"Color levels": "\uc0c9\uc0c1\ub808\ubca8", +"Gamma": "\uac10\ub9c8", +"Invert": "\ubc18\uc804", +"Apply": "\uc801\uc6a9", +"Back": "\ub4a4\ub85c", +"Insert date\/time": "\ub0a0\uc9dc\/\uc2dc\uac04\uc0bd\uc785", +"Date\/time": "\ub0a0\uc9dc\/\uc2dc\uac04", +"Insert link": "\ub9c1\ud06c \uc0bd\uc785 ", +"Insert\/edit link": "\ub9c1\ud06c \uc0bd\uc785\/\uc218\uc815", +"Text to display": "\ubcf8\ubb38", +"Url": "\uc8fc\uc18c", +"Target": "\ub300\uc0c1", +"None": "\uc5c6\uc74c", +"New window": "\uc0c8\ucc3d", +"Remove link": "\ub9c1\ud06c\uc0ad\uc81c", +"Anchors": "\ucc45\uac08\ud53c", +"Link": "\ub9c1\ud06c", +"Paste or type a link": "\ub9c1\ud06c\ub97c \ubd99\uc5ec\ub123\uac70\ub098 \uc785\ub825\ud558\uc138\uc694", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "\ud604\uc7ac E-mail\uc8fc\uc18c\ub97c \uc785\ub825\ud558\uc168\uc2b5\ub2c8\ub2e4. E-mail \uc8fc\uc18c\uc5d0 \ub9c1\ud06c\ub97c \uac78\uae4c\uc694?", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "\ud604\uc7ac \uc6f9\uc0ac\uc774\ud2b8 \uc8fc\uc18c\ub97c \uc785\ub825\ud558\uc168\uc2b5\ub2c8\ub2e4. \ud574\ub2f9 \uc8fc\uc18c\uc5d0 \ub9c1\ud06c\ub97c \uac78\uae4c\uc694?", +"Link list": "\ub9c1\ud06c \ub9ac\uc2a4\ud2b8", +"Insert video": "\ube44\ub514\uc624 \uc0bd\uc785", +"Insert\/edit video": "\ube44\ub514\uc624 \uc0bd\uc785\/\uc218\uc815", +"Insert\/edit media": "\ubbf8\ub514\uc5b4 \uc0bd\uc785\/\uc218\uc815", +"Alternative source": "\ub300\uccb4 \uc18c\uc2a4", +"Poster": "\ud3ec\uc2a4\ud130", +"Paste your embed code below:": "\uc544\ub798\uc5d0 \ucf54\ub4dc\ub97c \ubd99\uc5ec\ub123\uc73c\uc138\uc694:", +"Embed": "\uc0bd\uc785", +"Media": "\ubbf8\ub514\uc5b4", +"Nonbreaking space": "\ub744\uc5b4\uc4f0\uae30", +"Page break": "\ud398\uc774\uc9c0 \uad6c\ubd84\uc790", +"Paste as text": "\ud14d\uc2a4\ud2b8\ub85c \ubd99\uc5ec\ub123\uae30", +"Preview": "\ubbf8\ub9ac\ubcf4\uae30", +"Print": "\ucd9c\ub825", +"Save": "\uc800\uc7a5", +"Find": "\ucc3e\uae30", +"Replace with": "\uad50\uccb4", +"Replace": "\uad50\uccb4", +"Replace all": "\uc804\uccb4 \uad50\uccb4", +"Prev": "\uc774\uc804", +"Next": "\ub2e4\uc74c", +"Find and replace": "\ucc3e\uc544\uc11c \uad50\uccb4", +"Could not find the specified string.": "\ubb38\uc790\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.", +"Match case": "\ub300\uc18c\ubb38\uc790 \uc77c\uce58", +"Whole words": "\uc804\uccb4 \ub2e8\uc5b4", +"Spellcheck": "\ubb38\ubc95\uccb4\ud06c", +"Ignore": "\ubb34\uc2dc", +"Ignore all": "\uc804\uccb4\ubb34\uc2dc", +"Finish": "\uc644\ub8cc", +"Add to Dictionary": "\uc0ac\uc804\uc5d0 \ucd94\uac00", +"Insert table": "\ud14c\uc774\ube14 \uc0bd\uc785", +"Table properties": "\ud14c\uc774\ube14 \uc18d\uc131", +"Delete table": "\ud14c\uc774\ube14 \uc0ad\uc81c", +"Cell": "\uc140", +"Row": "\uc5f4", +"Column": "\ud589", +"Cell properties": "\uc140 \uc18d", +"Merge cells": "\uc140 \ud569\uce58\uae30", +"Split cell": "\uc140 \ub098\ub204\uae30", +"Insert row before": "\uc774\uc804\uc5d0 \ud589 \uc0bd\uc785", +"Insert row after": "\ub2e4\uc74c\uc5d0 \ud589 \uc0bd\uc785", +"Delete row": "\ud589 \uc9c0\uc6b0\uae30", +"Row properties": "\ud589 \uc18d\uc131", +"Cut row": "\ud589 \uc798\ub77c\ub0b4\uae30", +"Copy row": "\ud589 \ubcf5\uc0ac", +"Paste row before": "\uc774\uc804\uc5d0 \ud589 \ubd99\uc5ec\ub123\uae30", +"Paste row after": "\ub2e4\uc74c\uc5d0 \ud589 \ubd99\uc5ec\ub123\uae30", +"Insert column before": "\uc774\uc804\uc5d0 \ud589 \uc0bd\uc785", +"Insert column after": "\ub2e4\uc74c\uc5d0 \uc5f4 \uc0bd\uc785", +"Delete column": "\uc5f4 \uc9c0\uc6b0\uae30", +"Cols": "\uc5f4", +"Rows": "\ud589", +"Width": "\ub113\uc774", +"Height": "\ub192\uc774", +"Cell spacing": "\uc140 \uac04\uaca9", +"Cell padding": "\uc140 \uc548\ucabd \uc5ec\ubc31", +"Caption": "\ucea1\uc158", +"Left": "\uc67c\ucabd", +"Center": "\uac00\uc6b4\ub370", +"Right": "\uc624\ub978\ucabd", +"Cell type": "\uc140 \ud0c0\uc785", +"Scope": "\ubc94\uc704", +"Alignment": "\uc815\ub82c", +"H Align": "\uac00\ub85c \uc815\ub82c", +"V Align": "\uc138\ub85c \uc815\ub82c", +"Top": "\uc0c1\ub2e8", +"Middle": "\uc911\uac04", +"Bottom": "\ud558\ub2e8", +"Header cell": "\ud5e4\ub354 \uc140", +"Row group": "\ud589 \uadf8\ub8f9", +"Column group": "\uc5f4 \uadf8\ub8f9", +"Row type": "\ud589 \ud0c0\uc785", +"Header": "\ud5e4\ub354", +"Body": "\ubc14\ub514", +"Footer": "\ud478\ud130", +"Border color": "\ud14c\ub450\ub9ac \uc0c9", +"Insert template": "\ud15c\ud50c\ub9bf \uc0bd\uc785", +"Templates": "\ud15c\ud50c\ub9bf", +"Template": "\ud15c\ud50c\ub9bf", +"Text color": "\ubb38\uc790 \uc0c9\uae54", +"Background color": "\ubc30\uacbd\uc0c9", +"Custom...": "\uc9c1\uc811 \uc0c9\uae54 \uc9c0\uc815\ud558\uae30", +"Custom color": "\uc9c1\uc811 \uc9c0\uc815\ud55c \uc0c9\uae54", +"No color": "\uc0c9\uc0c1 \uc5c6\uc74c", +"Table of Contents": "\ubaa9\ucc28", +"Show blocks": "\ube14\ub7ed \ubcf4\uc5ec\uc8fc\uae30", +"Show invisible characters": "\uc548\ubcf4\uc774\ub294 \ubb38\uc790 \ubcf4\uc774\uae30", +"Words: {0}": "\ub2e8\uc5b4: {0}", +"{0} words": "{0} \ub2e8\uc5b4", +"File": "\ud30c\uc77c", +"Edit": "\uc218\uc815", +"Insert": "\uc0bd\uc785", +"View": "\ubcf4\uae30", +"Format": "\ud3ec\ub9f7", +"Table": "\ud14c\uc774\ube14", +"Tools": "\ub3c4\uad6c", +"Powered by {0}": "Powered by {0}", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "\uc11c\uc2dd \uc788\ub294 \ud14d\uc2a4\ud2b8 \ud3b8\uc9d1\uae30 \uc785\ub2c8\ub2e4. ALT-F9\ub97c \ub204\ub974\uba74 \uba54\ub274, ALT-F10\ub97c \ub204\ub974\uba74 \ud234\ubc14, ALT-0\uc744 \ub204\ub974\uba74 \ub3c4\uc6c0\ub9d0\uc744 \ubcfc \uc218 \uc788\uc2b5\ub2c8\ub2e4." +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/lt.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/lt.js new file mode 100644 index 0000000000..2a279686be --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/lt.js @@ -0,0 +1,261 @@ +tinymce.addI18n('lt',{ +"Redo": "Gr\u0105\u017einti", +"Undo": "Atstatyti", +"Cut": "I\u0161kirpti", +"Copy": "Kopijuoti", +"Paste": "\u012ed\u0117ti", +"Select all": "Pa\u017eym\u0117ti visk\u0105", +"New document": "Naujas dokumentas", +"Ok": "Gerai", +"Cancel": "Atsisakyti", +"Visual aids": "Vaizdin\u0117s priemon\u0117s", +"Bold": "Pary\u0161kintas", +"Italic": "Kursyvinis", +"Underline": "Pabrauktas", +"Strikethrough": "Perbrauktas", +"Superscript": "Vir\u0161utinis indeksas", +"Subscript": "Apatinis indeksas", +"Clear formatting": "Naikinti formatavim\u0105", +"Align left": "Lygiuoti kair\u0117je", +"Align center": "Centruoti", +"Align right": "Lygiuoti de\u0161in\u0117je", +"Justify": "I\u0161d\u0117styti per vis\u0105 plot\u012f", +"Bullet list": "\u017denklinimo s\u0105ra\u0161as", +"Numbered list": "Skaitmeninis s\u0105ra\u0161as", +"Decrease indent": "Ma\u017einti \u012ftrauk\u0105", +"Increase indent": "Didinti \u012ftrauk\u0105", +"Close": "U\u017edaryti", +"Formats": "Formatai", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "Nar\u0161ykl\u0117s nustatymai neleid\u017eia redaktoriui tiesiogiai pasiekti laikinosios atminties. Pra\u0161ome naudoti klaviat\u016bros klavi\u0161us Ctrl+X\/C\/V.", +"Headers": "Antra\u0161t\u0117s", +"Header 1": "Antra\u0161t\u0117 1", +"Header 2": "Antra\u0161t\u0117 2", +"Header 3": "Antra\u0161t\u0117 3", +"Header 4": "Antra\u0161t\u0117 4", +"Header 5": "Antra\u0161t\u0117 5", +"Header 6": "Antra\u0161t\u0117 6", +"Headings": "Antra\u0161t\u0117s", +"Heading 1": "Antra\u0161t\u0117 1", +"Heading 2": "Antra\u0161t\u0117 2", +"Heading 3": "Antra\u0161t\u0117 3", +"Heading 4": "Antra\u0161t\u0117 4", +"Heading 5": "Antra\u0161t\u0117 5", +"Heading 6": "Antra\u0161t\u0117 6", +"Preformatted": "Suformuotas i\u0161 anksto", +"Div": "Div", +"Pre": "Pre", +"Code": "Kodas", +"Paragraph": "Paragrafas", +"Blockquote": "Citata", +"Inline": "Inline", +"Blocks": "Blokai", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "Dabar \u012fterpiama paprastojo teksto re\u017eimu. Kol \u0161i parinktis \u012fjungta, turinys bus \u012fterptas kaip paprastas tekstas.", +"Font Family": "\u0160riftas", +"Font Sizes": "\u0160rifto dyd\u017eiai", +"Class": "Klas\u0117", +"Browse for an image": "Ie\u0161koti paveiksl\u0117lio", +"OR": "ARBA", +"Drop an image here": "Tempkite paveiksl\u0117l\u012f \u010dia", +"Upload": "\u012ekelti", +"Block": "Blokas", +"Align": "Lygiavimas", +"Default": "Pagrindinis", +"Circle": "Apskritimas", +"Disc": "Diskas", +"Square": "Kvadratas", +"Lower Alpha": "Ma\u017eosios raid\u0117s", +"Lower Greek": "Ma\u017eosios graik\u0173", +"Lower Roman": "Ma\u017eosios rom\u0117n\u0173", +"Upper Alpha": "Did\u017eiosios raid\u0117s", +"Upper Roman": "Did\u017eiosios rom\u0117n\u0173", +"Anchor": "\u017dym\u0117", +"Name": "Pavadinimas", +"Id": "ID", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "ID turi prasid\u0117ti raide, po kurios gali b\u016bti raid\u0117s, skai\u010diai, br\u016bk\u0161niai, ta\u0161kai, kabliata\u0161kiai ar apatiniai pabraukimai.", +"You have unsaved changes are you sure you want to navigate away?": "Turite nei\u0161saugot\u0173 pakeitim\u0173! Ar tikrai norite i\u0161eiti?", +"Restore last draft": "Atstatyti paskutin\u012f projekt\u0105", +"Special character": "Specialus simbolis", +"Source code": "Pirminis \u0161altinis", +"Insert\/Edit code sample": "Prid\u0117ti \/ keisti kodo pavyzd\u012f", +"Language": "Kalba", +"Code sample": "Kodo pavyzdys", +"Color": "Spalva", +"R": "R", +"G": "G", +"B": "B", +"Left to right": "I\u0161 kair\u0117s \u012f de\u0161in\u0119", +"Right to left": "I\u0161 de\u0161in\u0117s \u012f kair\u0119", +"Emoticons": "Jaustukai", +"Document properties": "Dokumento savyb\u0117s", +"Title": "Pavadinimas", +"Keywords": "\u017dymos", +"Description": "Apra\u0161as", +"Robots": "Robotai", +"Author": "Autorius", +"Encoding": "Kodavimas", +"Fullscreen": "Visas ekranas", +"Action": "Veiksmas", +"Shortcut": "Nuoroda", +"Help": "Pagalba", +"Address": "Adresas", +"Focus to menubar": "Fokusuoti \u012f meniu", +"Focus to toolbar": "Fokusuoti \u012f \u012franki\u0173 juost\u0105", +"Focus to element path": "Fokusuoti \u012f elemento keli\u0105", +"Focus to contextual toolbar": "Fokusuoti \u012f kontekstin\u012f \u012franki\u0173 juost\u0105", +"Insert link (if link plugin activated)": "Prid\u0117ti nuorod\u0105 (jei link priedas aktyvuotas)", +"Save (if save plugin activated)": "I\u0161saugoti (jei save priedas aktyvuotas)", +"Find (if searchreplace plugin activated)": "Ie\u0161koti (jei searchreplace priedas aktyvuotas)", +"Plugins installed ({0}):": "\u012ediegti priedai ({0}):", +"Premium plugins:": "Mokami priedai:", +"Learn more...": "Su\u017einoti daugiau...", +"You are using {0}": "Naudojate {0}", +"Plugins": "Priedai", +"Handy Shortcuts": "Patogios nuorodos", +"Horizontal line": "Horizontali linija", +"Insert\/edit image": "\u012eterpti|Tvarkyti paveiksl\u0117l\u012f", +"Image description": "Paveiksl\u0117lio apra\u0161as", +"Source": "Pirmin\u0117 nuoroda", +"Dimensions": "Matmenys", +"Constrain proportions": "Laikytis proporcij\u0173", +"General": "Bendra", +"Advanced": "I\u0161pl\u0117stas", +"Style": "Stilius", +"Vertical space": "Vertikalus tarpas", +"Horizontal space": "Horizontalus tarpas", +"Border": "R\u0117melis", +"Insert image": "\u012eterpti paveiksl\u0117l\u012f", +"Image": "Paveiksl\u0117lis", +"Image list": "Paveiksl\u0117li\u0173 s\u0105ra\u0161as", +"Rotate counterclockwise": "Pasukti prie\u0161 laikrod\u017eio rodykl\u0119", +"Rotate clockwise": "Pasukti pagal laikrod\u017eio rodykl\u0119", +"Flip vertically": "Apversti vertikaliai", +"Flip horizontally": "Apversti horizontaliai", +"Edit image": "Redaguoti paveiksl\u0117l\u012f", +"Image options": "Paveiksl\u0117lio nustatymai", +"Zoom in": "Priartinti", +"Zoom out": "Atitolinti", +"Crop": "Atkarpyti", +"Resize": "Keisti dyd\u012f", +"Orientation": "Pasukimas", +"Brightness": "\u0160viesumas", +"Sharpen": "Ry\u0161kumas", +"Contrast": "Kontrastas", +"Color levels": "Spalv\u0173 lygiai", +"Gamma": "Gama", +"Invert": "Prie\u0161ingos spalvos", +"Apply": "Taikyti", +"Back": "Atgal", +"Insert date\/time": "\u012eterpti dat\u0105\/laik\u0105", +"Date\/time": "Data \/ laikas", +"Insert link": "\u012eterpti nuorod\u0105", +"Insert\/edit link": "\u012eterpti\/taisyti nuorod\u0105", +"Text to display": "Rodomas tekstas", +"Url": "Nuoroda", +"Target": "Tikslin\u0117 nuoroda", +"None": "Nieko", +"New window": "Naujas langas", +"Remove link": "\u0160alinti nuorod\u0105", +"Anchors": "\u017dym\u0117", +"Link": "Nuoroda", +"Paste or type a link": "\u012eklijuokite arba \u012fra\u0161ykite nuorod\u0105", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "Atrodo, kad \u012fvesta nuoroda yra elektroninio pa\u0161to adresas. Ar norite prie\u0161 j\u012f \u012fvesti reikalaujam\u0105 \u201emailto:\u201c?", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "Atrodo, kad \u012fved\u0117te nuotolin\u0119 nuorod\u0105. Ar norite prie\u0161 j\u0105 \u012fvesti reikalaujam\u0105 \u201ehttp:\/\/\u201c?", +"Link list": "Nuorod\u0173 s\u0105ra\u0161as", +"Insert video": "\u012eterpti video", +"Insert\/edit video": "\u012eterpti\/tvarkyti video", +"Insert\/edit media": "Prid\u0117ti \/ keisti medij\u0105", +"Alternative source": "Alternatyvus \u0161altinis", +"Poster": "Plakatas", +"Paste your embed code below:": "\u012eterpkite kod\u0105 \u017eemiau:", +"Embed": "\u012eterpti", +"Media": "Medija", +"Nonbreaking space": "Nepertraukiamos vietos", +"Page break": "Puslapio skirtukas", +"Paste as text": "\u012eklijuoti kaip tekst\u0105", +"Preview": "Per\u017ei\u016bra", +"Print": "Spausdinti", +"Save": "I\u0161saugoti", +"Find": "Ie\u0161koti", +"Replace with": "Kuo pakeisti", +"Replace": "Pakeisti", +"Replace all": "Pakeisti visk\u0105", +"Prev": "Ankstesnis", +"Next": "Sekantis", +"Find and replace": "Surasti ir pakeisti", +"Could not find the specified string.": "Nepavyko rasti nurodytos eilut\u0117s.", +"Match case": "Atitinkamus", +"Whole words": "Visus \u017eod\u017eius", +"Spellcheck": "Ra\u0161ybos tikrinimas", +"Ignore": "Ignoruoti", +"Ignore all": "Ignoruoti visk\u0105", +"Finish": "Baigti", +"Add to Dictionary": "Prid\u0117ti \u012f \u017dodyn\u0105", +"Insert table": "\u012eterpti lentel\u0119", +"Table properties": "Lentel\u0117s savyb\u0117s", +"Delete table": "\u0160alinti lentel\u0119", +"Cell": "Langeliai", +"Row": "Eilut\u0117s", +"Column": "Stulpelis", +"Cell properties": "Langelio savyb\u0117s", +"Merge cells": "Sujungti langelius", +"Split cell": "Skaidyti langelius", +"Insert row before": "\u012eterpti eilut\u0119 prie\u0161", +"Insert row after": "\u012eterpti eilut\u0119 po", +"Delete row": "Naikinti eilut\u0119", +"Row properties": "Eilut\u0117s savyb\u0117s", +"Cut row": "I\u0161kirpti eilut\u0119", +"Copy row": "Kopijuoti eilut\u0119", +"Paste row before": "\u012ed\u0117ti eilut\u0119 prie\u0161", +"Paste row after": "\u012ed\u0117ti eilut\u0119 po", +"Insert column before": "\u012eterpti stulpel\u012f prie\u0161", +"Insert column after": "\u012eterpti stulpel\u012f po", +"Delete column": "Naikinti stulpel\u012f", +"Cols": "Stulpeliai", +"Rows": "Eilut\u0117s", +"Width": "Plotis", +"Height": "Auk\u0161tis", +"Cell spacing": "Tarpas tarp langeli\u0173", +"Cell padding": "Tarpas nuo langelio iki teksto", +"Caption": "Antra\u0161t\u0117", +"Left": "Kair\u0117", +"Center": "Centras", +"Right": "De\u0161in\u0117", +"Cell type": "Langelio tipas", +"Scope": "Strukt\u016bra", +"Alignment": "Lygiavimas", +"H Align": "H Lygiavimas", +"V Align": "V Lygiavimas", +"Top": "Vir\u0161uje", +"Middle": "Viduryje", +"Bottom": "Apa\u010dioje", +"Header cell": "Antra\u0161t\u0117s langelis", +"Row group": "Eilu\u010di\u0173 grup\u0117", +"Column group": "Stulpeli\u0173 grup\u0117", +"Row type": "Eilu\u010di\u0173 tipas", +"Header": "Antra\u0161t\u0117", +"Body": "Turinys", +"Footer": "Apa\u010dia", +"Border color": "R\u0117melio spalva", +"Insert template": "\u012eterpti \u0161ablon\u0105", +"Templates": "\u0160ablonai", +"Template": "\u0160ablonas", +"Text color": "Teksto spalva", +"Background color": "Fono spalva", +"Custom...": "Pasirinktinas...", +"Custom color": "Pasirinktina spalva", +"No color": "Jokios spalvos", +"Table of Contents": "Turinys", +"Show blocks": "Rodyti blokus", +"Show invisible characters": "Rodyti nematomus simbolius", +"Words: {0}": "\u017dod\u017eiai: {0}", +"{0} words": "{0} \u017eod\u017eiai", +"File": "Failas", +"Edit": "Redaguoti", +"Insert": "\u012eterpti", +"View": "Per\u017ei\u016bra", +"Format": "Formatas", +"Table": "Lentel\u0117", +"Tools": "\u012erankiai", +"Powered by {0}": "Sukurta {0}", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Suformatuoto teksto laukas. D\u0117l meniu spauskite ALT-F9. U\u017eduo\u010di\u0173 juostos \u012fjungimui spauskite ALT-F10. Pagalbai - spauskite ALT-0." +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/lv.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/lv.js new file mode 100644 index 0000000000..9f88e6f5c3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/lv.js @@ -0,0 +1,260 @@ +tinymce.addI18n('lv',{ +"Redo": "Solis uz priek\u0161u", +"Undo": "Solis atpaka\u013c", +"Cut": "Izgriezt", +"Copy": "Kop\u0113t", +"Paste": "Iel\u012bm\u0113t", +"Select all": "Iez\u012bm\u0113t visu", +"New document": "Jauns dokuments", +"Ok": "Ok", +"Cancel": "Atcelt", +"Visual aids": "Vizu\u0101l\u0101 pal\u012bdz\u012bba", +"Bold": "Treknraksts", +"Italic": "Sl\u012bpraksts", +"Underline": "Pasv\u012btrot", +"Strikethrough": "Nosv\u012btrot", +"Superscript": "Aug\u0161raksts", +"Subscript": "Apak\u0161raksts", +"Clear formatting": "No\u0146emt format\u0113jumu", +"Align left": "Pa kreisi", +"Align center": "Centr\u0113t", +"Align right": "Pa labi", +"Justify": "Gar ab\u0101m mal\u0101m", +"Bullet list": "Nenumur\u0113ts saraksts", +"Numbered list": "Numur\u0113ts saraksts", +"Decrease indent": "Samazin\u0101t atk\u0101pi", +"Increase indent": "Palielin\u0101t atk\u0101pi", +"Close": "Aizv\u0113rt", +"Formats": "Format\u0113jumi", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "J\u016bsu p\u0101rl\u016bkprogramma neatbalsta piek\u013cuvi starpliktuvei. L\u016bdzu, lietojiet Ctrl+X\/C\/V klaviat\u016bras sa\u012bsnes.", +"Headers": "Virsraksti", +"Header 1": "1. l\u012bme\u0146a virsraksts", +"Header 2": "2. l\u012bme\u0146a virsraksts", +"Header 3": "3. l\u012bme\u0146a virsraksts", +"Header 4": "4. l\u012bme\u0146a virsraksts", +"Header 5": "5. l\u012bme\u0146a virsraksts", +"Header 6": "6. l\u012bme\u0146a virsraksts", +"Headings": "Virsraksti", +"Heading 1": "1. l\u012bme\u0146a virsraksts", +"Heading 2": "2. l\u012bme\u0146a virsraksts", +"Heading 3": "3. l\u012bme\u0146a virsraksts", +"Heading 4": "4. l\u012bme\u0146a virsraksts", +"Heading 5": "5. l\u012bme\u0146a virsraksts", +"Heading 6": "6. l\u012bme\u0146a virsraksts", +"Div": "Div", +"Pre": "Pre", +"Code": "Kods", +"Paragraph": "Rindkopa", +"Blockquote": "Cit\u0101ts", +"Inline": "Inline elementi", +"Blocks": "Bloka elementi", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "Iel\u012bm\u0113\u0161ana vienk\u0101r\u0161\u0101 teksta re\u017e\u012bm\u0101. Saturs tiks iel\u012bm\u0113ts bez format\u0113juma l\u012bdz \u0161\u012b opcija tiks atsl\u0113gta.", +"Font Family": "Fontu saime", +"Font Sizes": "Fontu izm\u0113ri", +"Class": "Klase", +"Browse for an image": "Izv\u0113l\u0113ties att\u0113lu", +"OR": "VAI", +"Drop an image here": "Ievelciet att\u0113lu \u0161eit", +"Upload": "Aug\u0161upiel\u0101d\u0113t", +"Block": "Bloks", +"Align": "L\u012bdzin\u0101t", +"Default": "Parastais", +"Circle": "Aplis", +"Disc": "Disks", +"Square": "Kvadr\u0101ts", +"Lower Alpha": "Lat\u012b\u0146u mazie burti", +"Lower Greek": "Grie\u0137u mazie burti", +"Lower Roman": "Romie\u0161u mazie burti", +"Upper Alpha": "Lat\u012b\u0146u lielie burti", +"Upper Roman": "Romie\u0161u lielie burti", +"Anchor": "Iek\u0161\u0113j\u0101 saite", +"Name": "Nosaukums", +"Id": "Identifikators", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "Identifikatoram j\u0101s\u0101kas ar burtu, p\u0113c tam var satur\u0113t: burtus, ciparus, domuz\u012bmes, punktus, kolus vai pasv\u012btrojumz\u012bmes. ", +"You have unsaved changes are you sure you want to navigate away?": "Saturs ir labots un nav saglab\u0101ts. Vai tie\u0161\u0101m v\u0113laties atst\u0101t \u0161o lapu?", +"Restore last draft": "Atjaunot p\u0113d\u0113jo melnrakstu", +"Special character": "Speci\u0101l\u0101 rakstz\u012bme", +"Source code": "Pirmkods", +"Insert\/Edit code sample": "Ievad\u012bt\/Labot koda paraugu", +"Language": "Valoda", +"Code sample": "Koda paraugs", +"Color": "Kr\u0101sa", +"R": "R", +"G": "G", +"B": "B", +"Left to right": "No kreis\u0101s uz labo", +"Right to left": "No lab\u0101s uz kreiso", +"Emoticons": "Emocijas", +"Document properties": "Dokumenta parametri", +"Title": "Nosaukums", +"Keywords": "Atsl\u0113gv\u0101rdi", +"Description": "Apraksts", +"Robots": "Programmas", +"Author": "Autors", +"Encoding": "Kod\u0113\u0161ana", +"Fullscreen": "Pilnekr\u0101na re\u017e\u012bms", +"Action": "Darb\u012bba", +"Shortcut": "Sa\u012bsne", +"Help": "Pal\u012bdz\u012bba", +"Address": "Adrese", +"Focus to menubar": "Fokuss uz izv\u0113lni", +"Focus to toolbar": "Fokuss uz r\u012bkjoslu", +"Focus to element path": "Fokuss uz elementa ce\u013cu", +"Focus to contextual toolbar": "Fokuss uz papildizv\u0113lni", +"Insert link (if link plugin activated)": "Ievietot saiti (Ja sai\u0161u spraudnis ir akt\u012bvs)", +"Save (if save plugin activated)": "Saglab\u0101t (Ja saglab\u0101\u0161anas spraudnis ir akt\u012bvs)", +"Find (if searchreplace plugin activated)": "Atrast (Ja \"searchreplace\" spraudnis ir akt\u012bvs)", +"Plugins installed ({0}):": "Spraud\u0146i instal\u0113ti ({0}):", +"Premium plugins:": "\u012apa\u0161ie spraud\u0146i:", +"Learn more...": "Uzzin\u0101t vair\u0101k...", +"You are using {0}": "J\u016bs lietojiet {0}", +"Plugins": "Spraud\u0146i", +"Handy Shortcuts": "Paroc\u012bgi \u012bsce\u013ci", +"Horizontal line": "Horizont\u0101l\u0101 l\u012bnija", +"Insert\/edit image": "Ievietot\/labot att\u0113lu", +"Image description": "Apraksts", +"Source": "Avots", +"Dimensions": "Izm\u0113ri", +"Constrain proportions": "Saglab\u0101t malu attiec\u012bbu", +"General": "Pamata info", +"Advanced": "Papildus", +"Style": "Stils", +"Vertical space": "Vertik\u0101l\u0101 atstarpe", +"Horizontal space": "Horizont\u0101l\u0101 atstarpe", +"Border": "Apmale", +"Insert image": "Ievietot att\u0113lu", +"Image": "Att\u0113ls", +"Image list": "Att\u0113lu saraksts", +"Rotate counterclockwise": "Pagriezt pret\u0113ji pulkste\u0146a r\u0101d\u012bt\u0101ja virzienam", +"Rotate clockwise": "Pagriezt pulkste\u0146a r\u0101d\u012bt\u0101ja virzien\u0101", +"Flip vertically": "Apmest vertik\u0101li", +"Flip horizontally": "Apmest horizont\u0101li", +"Edit image": "Redi\u0123\u0113t att\u0113lu", +"Image options": "Att\u0113la parametri", +"Zoom in": "Pietuvin\u0101t", +"Zoom out": "Att\u0101lin\u0101t", +"Crop": "Apgriezt", +"Resize": "Main\u012bt izm\u0113ru", +"Orientation": "Orient\u0101cija", +"Brightness": "Gai\u0161ums", +"Sharpen": "Asums", +"Contrast": "Kontrasts", +"Color levels": "Kr\u0101su l\u012bme\u0146i", +"Gamma": "Gamma", +"Invert": "Pret\u0113j\u0101s kr\u0101sas", +"Apply": "Pielietot", +"Back": "Atgriezties", +"Insert date\/time": "Ievietot datumu\/laiku", +"Date\/time": "Datums\/laiks", +"Insert link": "Ievietot saiti", +"Insert\/edit link": "Ievietot\/labot saiti", +"Text to display": "Nosaukums", +"Url": "Adrese", +"Target": "Kur atv\u0113rt", +"None": "\u2014", +"New window": "Jaun\u0101 \u0161\u0137irkl\u012b", +"Remove link": "No\u0146emt saiti", +"Anchors": "Saites", +"Link": "Saite", +"Paste or type a link": "Iekop\u0113jiet vai ierakstiet saiti", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "J\u016bs ievad\u012bj\u0101t e-pasta adresi. Lai t\u0101 korekti darbotos, ir nepiecie\u0161ams to papildin\u0101t ar \"mailto:\" priek\u0161\u0101. Vai v\u0113laties to izdar\u012bt?", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "J\u016bs ievad\u012bj\u0101t \u0101r\u0113jo saiti. Lai t\u0101 korekti darbotos, ir nepiecie\u0161ams to papildin\u0101t ar \"http:\/\/\" priek\u0161\u0101. Vai v\u0113laties to izdar\u012bt?", +"Link list": "Sai\u0161u saraksts", +"Insert video": "Ievietot video", +"Insert\/edit video": "Ievietot\/redi\u0123\u0113t video", +"Insert\/edit media": "Ievietot\/labot att\u0113lu", +"Alternative source": "Alternat\u012bvs avots", +"Poster": "Att\u0113ls", +"Paste your embed code below:": "Iekop\u0113jiet Embed kodu \u0161eit:", +"Embed": "Embed kods", +"Media": "Att\u0113ls vai video", +"Nonbreaking space": "Nedal\u0101m\u0101 atstarpe", +"Page break": "P\u0101reja uz jauno lapu", +"Paste as text": "Iel\u012bm\u0113t bez format\u0113juma", +"Preview": "Priek\u0161skat\u012bt", +"Print": "Druk\u0101t", +"Save": "Saglab\u0101t", +"Find": "Mekl\u0113t", +"Replace with": "Aizvietot ar", +"Replace": "Aizvietot", +"Replace all": "Aizvietot visu", +"Prev": "Iepriek\u0161\u0113jais", +"Next": "N\u0101kamais", +"Find and replace": "Mekl\u0113t un aizvietot", +"Could not find the specified string.": "Mekl\u0113tais teksts netika atrasts", +"Match case": "At\u0161\u0137irt lielos un mazos burtus", +"Whole words": "Tikai pilnos v\u0101rdus", +"Spellcheck": "Pareizrakst\u012bbas p\u0101rbaude", +"Ignore": "Ignor\u0113t", +"Ignore all": "Ignor\u0113t visu", +"Finish": "Pabeigt", +"Add to Dictionary": "Pievienot v\u0101rdn\u012bcai", +"Insert table": "Ievietot tabulu", +"Table properties": "Tabulas parametri", +"Delete table": "Dz\u0113st tabulu", +"Cell": "\u0160\u016bna", +"Row": "Rinda", +"Column": "Kolonna", +"Cell properties": "\u0160\u016bnas parametri", +"Merge cells": "Apvienot \u0161\u016bnas", +"Split cell": "Sadal\u012bt \u0161\u016bnas", +"Insert row before": "Jauna rinda augst\u0101k", +"Insert row after": "Jauna rinda zem\u0101k", +"Delete row": "Dz\u0113st rindu", +"Row properties": "Rindas parametri", +"Cut row": "Izgriezt rindu", +"Copy row": "Kop\u0113t rindu", +"Paste row before": "Iel\u012bm\u0113t rindu augst\u0101k", +"Paste row after": "Iel\u012bm\u0113t rindu zem\u0101k", +"Insert column before": "Jauna kolonna pa kreisi", +"Insert column after": "Jauna kolonna pa labi", +"Delete column": "Dz\u0113st kolonu", +"Cols": "Kolonnas", +"Rows": "Rindas", +"Width": "Platums", +"Height": "Augstums", +"Cell spacing": "\u0160\u016bnu atstarpe", +"Cell padding": "Iek\u0161\u0113j\u0101 atstarpe", +"Caption": "Ar virsrakstu", +"Left": "Pa kreisi", +"Center": "Centr\u0113t", +"Right": "Pa labi", +"Cell type": "\u0160\u016bnas veids", +"Scope": "Attiecin\u0101t uz", +"Alignment": "Izl\u012bdzin\u0101\u0161ana", +"H Align": "Horizont\u0101lais novietojums", +"V Align": "Vertik\u0101lais novietojums", +"Top": "Aug\u0161\u0101", +"Middle": "Pa vidu", +"Bottom": "Apak\u0161\u0101", +"Header cell": "Galvenes \u0161\u016bna", +"Row group": "Rindu grupa", +"Column group": "Kolonnu grupa", +"Row type": "Rindas veids", +"Header": "Galvene", +"Body": "Saturs", +"Footer": "K\u0101jene", +"Border color": "Apmales kr\u0101sa", +"Insert template": "Ievietot veidni", +"Templates": "Veidnes", +"Template": "Veidne", +"Text color": "Teksta kr\u0101sa", +"Background color": "Fona kr\u0101sa", +"Custom...": "Izv\u0113l\u0113ties citu...", +"Custom color": "Specifisk\u0101 kr\u0101sa", +"No color": "Nenor\u0101d\u012bt kr\u0101su", +"Table of Contents": "Saturs", +"Show blocks": "R\u0101d\u012bt blokus", +"Show invisible characters": "R\u0101d\u012bt neredzam\u0101s rakstz\u012bmes", +"Words: {0}": "V\u0101rdi: {0}", +"{0} words": "{0} v\u0101rdi", +"File": "Datne", +"Edit": "Labot", +"Insert": "Ievietot", +"View": "Skat\u012bt", +"Format": "Format\u0113t", +"Table": "Tabula", +"Tools": "R\u012bki", +"Powered by {0}": "Darb\u012bbu nodro\u0161ina {0}", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Satura redaktors. Nospiediet ALT-F9 lai par\u0101d\u012btu izv\u0113lni, ALT-F10 - r\u012bkjoslu vai ALT-0 - pal\u012bdz\u012bbu." +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/nb_NO.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/nb_NO.js new file mode 100644 index 0000000000..59233450b0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/nb_NO.js @@ -0,0 +1,261 @@ +tinymce.addI18n('nb_NO',{ +"Redo": "Utf\u00f8r likevel", +"Undo": "Angre", +"Cut": "Klipp ut", +"Copy": "Kopier", +"Paste": "Lim inn", +"Select all": "Marker alt", +"New document": "Nytt dokument", +"Ok": "OK", +"Cancel": "Avbryt", +"Visual aids": "Visuelle hjelpemidler", +"Bold": "Halvfet", +"Italic": "Kursiv", +"Underline": "Understreket", +"Strikethrough": "Gjennomstreket", +"Superscript": "Hevet skrift", +"Subscript": "Senket skrift", +"Clear formatting": "Fjern formateringer", +"Align left": "Venstrejustert", +"Align center": "Midtstilt", +"Align right": "H\u00f8yrejustert", +"Justify": "Juster alle linjer", +"Bullet list": "Punktliste", +"Numbered list": "Nummerliste", +"Decrease indent": "Reduser innrykk", +"Increase indent": "\u00d8k innrykk", +"Close": "Lukk", +"Formats": "Stiler", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "Nettleseren din st\u00f8tter ikke direkte tilgang til utklippsboken. Bruk istedet tastatur-snarveiene Ctrl+X\/C\/V, eller Cmd+X\/C\/V p\u00e5 Mac.", +"Headers": "Overskrifter", +"Header 1": "Overskrift 1", +"Header 2": "Overskrift 2", +"Header 3": "Overskrift 3", +"Header 4": "Overskrift 4", +"Header 5": "Overskrift 5", +"Header 6": "Overskrift 6", +"Headings": "Overskrifter", +"Heading 1": "Overskrift 1", +"Heading 2": "Overskrift 2", +"Heading 3": "Overskrift 3", +"Heading 4": "Overskrift 4", +"Heading 5": "Overskrift 5", +"Heading 6": "Overskrift 6", +"Preformatted": "Forh\u00e5ndsformatert", +"Div": "Delblokk
", +"Pre": "Definert
",
+"Code": "Kode ",
+"Paragraph": "Avsnitt 

", +"Blockquote": "Sitatblokk

", +"Inline": "Innkapslet ", +"Blocks": "Blokker", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "Lim inn er n\u00e5 i ren-tekst modus. Kopiert innhold vil bli limt inn som ren tekst inntil du sl\u00e5r av dette valget.", +"Font Family": "Skriftsnitt", +"Font Sizes": "St\u00f8rrelse", +"Class": "Klasse", +"Browse for an image": "S\u00f8k etter bilde", +"OR": "ELLER", +"Drop an image here": "Slipp et bilde her", +"Upload": "Last opp", +"Block": "Blokk", +"Align": "Juster", +"Default": "Normal", +"Circle": "\u00c5pen sirkel", +"Disc": "Fylt sirkel", +"Square": "Fylt firkant", +"Lower Alpha": "Minuskler", +"Lower Greek": "Greske minuskler", +"Lower Roman": "Romerske minuskler", +"Upper Alpha": "Versaler", +"Upper Roman": "Romerske versaler", +"Anchor": "Anker", +"Name": "Navn", +"Id": "Id", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "Id burde starte med en bokstav, bare fulgt av bokstaver, nummer, streker, punktum, koloner eller understreker.", +"You have unsaved changes are you sure you want to navigate away?": "Du har ikke arkivert endringene. Vil du fortsette uten \u00e5 arkivere?", +"Restore last draft": "Gjenopprett siste utkast", +"Special character": "Spesialtegn", +"Source code": "Kildekode", +"Insert\/Edit code sample": "Sett inn\/endre kodeeksempel", +"Language": "Spr\u00e5k", +"Code sample": "Kodeeksempel", +"Color": "Farge", +"R": "R", +"G": "G", +"B": "B", +"Left to right": "Venstre til h\u00f8yre", +"Right to left": "H\u00f8yre til venstre", +"Emoticons": "Hum\u00f8rfjes", +"Document properties": "Dokumentegenskaper", +"Title": "Tittel", +"Keywords": "N\u00f8kkelord", +"Description": "Beskrivelse", +"Robots": "Roboter", +"Author": "Forfatter", +"Encoding": "Tegnkoding", +"Fullscreen": "Fullskjerm", +"Action": "Handling", +"Shortcut": "Snarvei", +"Help": "Hjelp", +"Address": "Adresse", +"Focus to menubar": "Fokus p\u00e5 menylinje", +"Focus to toolbar": "Fokus p\u00e5 verkt\u00f8ylinje", +"Focus to element path": "Fokus p\u00e5 elementsti", +"Focus to contextual toolbar": "Fokus p\u00e5 kontekstuell verkt\u00f8ylinje", +"Insert link (if link plugin activated)": "Sett inn lenke (dersom lenketillegg er aktivert)", +"Save (if save plugin activated)": "Lagre (dersom lagretillegg er aktivert)", +"Find (if searchreplace plugin activated)": "Finn (dersom tillegg for s\u00f8k og erstatt er aktivert)", +"Plugins installed ({0}):": "Installerte tillegg ({0}):", +"Premium plugins:": "Premiumtillegg:", +"Learn more...": "Les mer ...", +"You are using {0}": "Du bruker {0}", +"Plugins": "Tillegg", +"Handy Shortcuts": "Nyttige snarveier", +"Horizontal line": "Horisontal linje", +"Insert\/edit image": "Sett inn\/endre bilde", +"Image description": "Bildebeskrivelse", +"Source": "Bildelenke", +"Dimensions": "Dimensjoner", +"Constrain proportions": "Behold proporsjoner", +"General": "Generelt", +"Advanced": "Avansert", +"Style": "Stil", +"Vertical space": "Vertikal marg", +"Horizontal space": "Horisontal marg", +"Border": "Ramme", +"Insert image": "Sett inn bilde", +"Image": "Bilde", +"Image list": "Bildeliste", +"Rotate counterclockwise": "Roter mot venstre", +"Rotate clockwise": "Roter mot h\u00f8yre", +"Flip vertically": "Speilvend vertikalt", +"Flip horizontally": "Speilvend horisontalt", +"Edit image": "Rediger bilde", +"Image options": "Bilde innstillinger", +"Zoom in": "Zoom inn", +"Zoom out": "Zoom ut", +"Crop": "Beskj\u00e6r", +"Resize": "Skaler", +"Orientation": "Orientering", +"Brightness": "Lysstyrke", +"Sharpen": "Skarphet", +"Contrast": "Kontrast", +"Color levels": "Fargeniv\u00e5", +"Gamma": "Gamma", +"Invert": "Inverter", +"Apply": "Utf\u00f8r", +"Back": "Tilbake", +"Insert date\/time": "Sett inn dato\/tid", +"Date\/time": "Dato\/tid", +"Insert link": "Sett inn lenke", +"Insert\/edit link": "Sett inn\/endre lenke", +"Text to display": "Tekst som skal vises", +"Url": "Url", +"Target": "M\u00e5l", +"None": "Ingen", +"New window": "Nytt vindu", +"Remove link": "Fjern lenke", +"Anchors": "Anker", +"Link": "Lenke", +"Paste or type a link": "Lim inn eller skriv en lenke", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "Oppgitte URL ser ut til \u00e5 v\u00e6re en epost-adresse. \u00d8nsker du \u00e5 sette inn p\u00e5krevet mailto: prefiks forran epost-adressen?", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "Oppgitt URL ser ut til \u00e5 v\u00e6re en e-postadresse. \u00d8nsker du \u00e5 sette inn p\u00e5krevd mailto:-prefiks foran e-postadressen?", +"Link list": "Lenkeliste", +"Insert video": "Sett inn video", +"Insert\/edit video": "Sett inn\/rediger video", +"Insert\/edit media": "Sett inn\/endre media", +"Alternative source": "Alternativ kilde", +"Poster": "Plakatbilde", +"Paste your embed code below:": "Lim inn inkluderings-koden nedenfor", +"Embed": "Inkluder", +"Media": "Media", +"Nonbreaking space": "Hardt mellomrom", +"Page break": "Sideskifte", +"Paste as text": "Lim inn som tekst", +"Preview": "Forh\u00e5ndsvisning", +"Print": "Skriv ut", +"Save": "Arkiver", +"Find": "Finn", +"Replace with": "Erstatt med", +"Replace": "Erstatt", +"Replace all": "Erstatt alle", +"Prev": "Forrige", +"Next": "Neste", +"Find and replace": "Finn og erstatt", +"Could not find the specified string.": "Kunne ikke finne den spesifiserte teksten", +"Match case": "Match store og sm\u00e5 bokstaver", +"Whole words": "Hele ord", +"Spellcheck": "Stavekontroll", +"Ignore": "Ignorer", +"Ignore all": "Ignorer alle", +"Finish": "Avslutt", +"Add to Dictionary": "Legg til i ordliste", +"Insert table": "Sett inn tabell", +"Table properties": "Tabell egenskaper", +"Delete table": "Slett tabell", +"Cell": "Celle", +"Row": "Rad", +"Column": "Kolonne", +"Cell properties": "Celle egenskaper", +"Merge cells": "Sl\u00e5 sammen celler", +"Split cell": "Splitt celle", +"Insert row before": "Sett inn rad f\u00f8r", +"Insert row after": "Sett in rad etter", +"Delete row": "Slett rad", +"Row properties": "Rad egenskaper", +"Cut row": "Klipp ut rad", +"Copy row": "Kopier rad", +"Paste row before": "Lim inn rad f\u00f8r", +"Paste row after": "Lim inn rad etter", +"Insert column before": "Sett inn kolonne f\u00f8r", +"Insert column after": "Sett inn kolonne etter", +"Delete column": "Slett kolonne", +"Cols": "Kolonner", +"Rows": "Rader", +"Width": "Bredde", +"Height": "H\u00f8yde", +"Cell spacing": "Celleavstand", +"Cell padding": "Cellemarg", +"Caption": "Tittel", +"Left": "Venstre", +"Center": "Midtstilt", +"Right": "H\u00f8yre", +"Cell type": "Celletype", +"Scope": "Omfang", +"Alignment": "Justering", +"H Align": "H Justering", +"V Align": "V Justering", +"Top": "Topp", +"Middle": "Midten", +"Bottom": "Bunn", +"Header cell": "Topptekst-celle", +"Row group": "Radgruppe", +"Column group": "Kolonnegruppe", +"Row type": "Rad-type", +"Header": "Topptekst", +"Body": "Br\u00f8dtekst", +"Footer": "Bunntekst", +"Border color": "Rammefarge", +"Insert template": "Sett inn mal", +"Templates": "Maler", +"Template": "Mal", +"Text color": "Tekstfarge", +"Background color": "Bakgrunnsfarge", +"Custom...": "Tilpass...", +"Custom color": "Tilpasset farge", +"No color": "Ingen farge", +"Table of Contents": "Innholdsfortegnelse", +"Show blocks": "Vis blokker", +"Show invisible characters": "Vis skjulte tegn", +"Words: {0}": "Antall ord: {0}", +"{0} words": "{0} ord", +"File": "Arkiv", +"Edit": "Rediger", +"Insert": "Sett inn", +"View": "Vis", +"Format": "Format", +"Table": "Tabell", +"Tools": "Verkt\u00f8y", +"Powered by {0}": "Redigert med {0}", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Tekstredigering. Tast ALT-F9 for meny. Tast ALT-F10 for verkt\u00f8ys-rader. Tast ALT-0 for hjelp." +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/nl.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/nl.js index ee1f335624..c80590b297 100644 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/nl.js +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/nl.js @@ -1,219 +1,261 @@ tinymce.addI18n('nl',{ -"Cut": "Knippen", -"Heading 5": "Kop 5", -"Header 2": "Kop 2", -"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "Uw browser ondersteunt geen toegang tot het clipboard. Gelieve ctrl+X\/C\/V sneltoetsen te gebruiken.", -"Heading 4": "Kop 4", -"Div": "Div", -"Heading 2": "Kop 2", -"Paste": "Plakken", -"Close": "Sluiten", -"Font Family": "Lettertype", -"Pre": "Pre", -"Align right": "Rechts uitlijnen", -"New document": "Nieuw document", -"Blockquote": "Quote", -"Numbered list": "Nummering", -"Heading 1": "Kop 1", -"Headings": "Koppen", -"Increase indent": "Inspringen vergroten", -"Formats": "Opmaak", -"Headers": "Kopteksten", -"Select all": "Alles selecteren", -"Header 3": "Kop 3", -"Blocks": "Blok", -"Undo": "Ongedaan maken", -"Strikethrough": "Doorhalen", -"Bullet list": "Opsommingsteken", -"Header 1": "Kop 1", -"Superscript": "Superscript", -"Clear formatting": "Opmaak verwijderen", -"Font Sizes": "Tekengrootte", -"Subscript": "Subscript", -"Header 6": "Kop 6", "Redo": "Opnieuw", -"Paragraph": "Paragraaf", -"Ok": "Ok\u00e9", -"Bold": "Vet", -"Code": "Code", -"Italic": "Cursief", -"Align center": "Centreren", -"Header 5": "Kop 5", -"Heading 6": "Kop 6", -"Heading 3": "Kop 3", -"Decrease indent": "Inspringen verkleinen", -"Header 4": "Kop 4", -"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "Plakken gebeurt nu als platte tekst. Tekst wordt nu ingevoegd zonder opmaak tot deze optie uitgeschakeld wordt.", -"Underline": "Onderstreept", -"Cancel": "Annuleren", -"Justify": "Uitlijnen", -"Inline": "Inlijn", +"Undo": "Ongedaan maken", +"Cut": "Knippen", "Copy": "Kopi\u00ebren", -"Align left": "Links uitlijnen", +"Paste": "Plakken", +"Select all": "Alles selecteren", +"New document": "Nieuw document", +"Ok": "Ok\u00e9", +"Cancel": "Annuleren", "Visual aids": "Hulpmiddelen", -"Lower Greek": "Griekse letters", -"Square": "Vierkant", +"Bold": "Vet", +"Italic": "Cursief", +"Underline": "Onderstreept", +"Strikethrough": "Doorhalen", +"Superscript": "Superscript", +"Subscript": "Subscript", +"Clear formatting": "Opmaak verwijderen", +"Align left": "Links uitlijnen", +"Align center": "Centreren", +"Align right": "Rechts uitlijnen", +"Justify": "Uitlijnen", +"Bullet list": "Opsommingsteken", +"Numbered list": "Nummering", +"Decrease indent": "Inspringen verkleinen", +"Increase indent": "Inspringen vergroten", +"Close": "Sluiten", +"Formats": "Opmaak", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "Uw browser ondersteunt geen toegang tot het clipboard. Gelieve ctrl+X\/C\/V sneltoetsen te gebruiken.", +"Headers": "Kopteksten", +"Header 1": "Kop 1", +"Header 2": "Kop 2", +"Header 3": "Kop 3", +"Header 4": "Kop 4", +"Header 5": "Kop 5", +"Header 6": "Kop 6", +"Headings": "Koppen", +"Heading 1": "Kop 1", +"Heading 2": "Kop 2", +"Heading 3": "Kop 3", +"Heading 4": "Kop 4", +"Heading 5": "Kop 5", +"Heading 6": "Kop 6", +"Preformatted": "Voor-opgemaakt", +"Div": "Div", +"Pre": "Pre", +"Code": "Code", +"Paragraph": "Paragraaf", +"Blockquote": "Quote", +"Inline": "Inlijn", +"Blocks": "Blok", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "Plakken gebeurt nu als platte tekst. Tekst wordt nu ingevoegd zonder opmaak tot deze optie uitgeschakeld wordt.", +"Font Family": "Lettertype", +"Font Sizes": "Tekengrootte", +"Class": "Class", +"Browse for an image": "Zoek naar een afbeelding", +"OR": "OF", +"Drop an image here": "Plaats hier een afbeelding", +"Upload": "Uploaden", +"Block": "Blok", +"Align": "Uitlijnen", "Default": "Standaard", -"Lower Alpha": "Kleine letters", "Circle": "Cirkel", "Disc": "Bolletje", +"Square": "Vierkant", +"Lower Alpha": "Kleine letters", +"Lower Greek": "Griekse letters", +"Lower Roman": "Romeinse cijfers klein", "Upper Alpha": "Hoofdletters", "Upper Roman": "Romeinse cijfers groot", -"Lower Roman": "Romeinse cijfers klein", -"Name": "Naam", "Anchor": "Anker", +"Name": "Naam", +"Id": "ID", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "ID moet beginnen met een letter, gevolgd door letters, nummers, streepjes, punten, dubbele punten of underscores.", "You have unsaved changes are you sure you want to navigate away?": "U hebt niet alles opgeslagen bent u zeker dat u de pagina wenst te verlaten?", "Restore last draft": "Herstel het laatste concept", "Special character": "Speciale karakters", "Source code": "Broncode", -"B": "Blauw", +"Insert\/Edit code sample": "Broncode invoegen\/bewerken", +"Language": "Programmeertaal", +"Code sample": "Broncode voorbeeld", +"Color": "Kleur", "R": "Rood", "G": "Groen", -"Color": "Kleur", -"Right to left": "Rechts naar links", +"B": "Blauw", "Left to right": "Links naar rechts", +"Right to left": "Rechts naar links", "Emoticons": "Emoticons", -"Robots": "Robots", "Document properties": "Document eigenschappen", "Title": "Titel", "Keywords": "Sleutelwoorden", -"Encoding": "Codering", "Description": "Omschrijving", +"Robots": "Robots", "Author": "Auteur", +"Encoding": "Codering", "Fullscreen": "Volledig scherm", +"Action": "Actie", +"Shortcut": "Snelkoppeling", +"Help": "Help", +"Address": "Adres", +"Focus to menubar": "Menubalk selecteren", +"Focus to toolbar": "Werkbalk selecteren", +"Focus to element path": "Element pad selecteren", +"Focus to contextual toolbar": "Contextuele werkbalk selecteren", +"Insert link (if link plugin activated)": "Link invoegen (als link plug-in geactiveerd is)", +"Save (if save plugin activated)": "Opslaan (als opslaan plug-in ingeschakeld is)", +"Find (if searchreplace plugin activated)": "Zoeken (als zoeken\/vervangen plug-in ingeschakeld is)", +"Plugins installed ({0}):": "Plug-ins ge\u00efnstalleerd ({0}):", +"Premium plugins:": "Premium plug-ins:", +"Learn more...": "Leer meer...", +"You are using {0}": "Je gebruikt {0}", +"Plugins": "Plug-ins", +"Handy Shortcuts": "Handige snelkoppelingen", "Horizontal line": "Horizontale lijn", -"Horizontal space": "Horizontale ruimte", "Insert\/edit image": "Afbeelding invoegen\/bewerken", +"Image description": "Afbeelding omschrijving", +"Source": "Bron", +"Dimensions": "Afmetingen", +"Constrain proportions": "Verhoudingen behouden", "General": "Algemeen", "Advanced": "Geavanceerd", -"Source": "Bron", -"Border": "Rand", -"Constrain proportions": "Verhoudingen behouden", -"Vertical space": "Verticale ruimte", -"Image description": "Afbeelding omschrijving", "Style": "Stijl", -"Dimensions": "Afmetingen", +"Vertical space": "Verticale ruimte", +"Horizontal space": "Horizontale ruimte", +"Border": "Rand", "Insert image": "Afbeelding invoegen", -"Zoom in": "Inzoomen", -"Contrast": "Contrast", -"Back": "Terug", -"Gamma": "Gamma", -"Flip horizontally": "Horizontaal spiegelen", -"Resize": "Formaat aanpassen", -"Sharpen": "Scherpte", -"Zoom out": "Uitzoomen", -"Image options": "Afbeelding opties", -"Apply": "Toepassen", -"Brightness": "Helderheid", -"Rotate clockwise": "Rechtsom draaien", +"Image": "Afbeelding", +"Image list": "Afbeeldingenlijst", "Rotate counterclockwise": "Linksom draaien", -"Edit image": "Bewerk afbeelding", -"Color levels": "Kleurniveau's", -"Crop": "Uitsnijden", -"Orientation": "Orientatie", +"Rotate clockwise": "Rechtsom draaien", "Flip vertically": "Verticaal spiegelen", +"Flip horizontally": "Horizontaal spiegelen", +"Edit image": "Bewerk afbeelding", +"Image options": "Afbeelding opties", +"Zoom in": "Inzoomen", +"Zoom out": "Uitzoomen", +"Crop": "Uitsnijden", +"Resize": "Formaat aanpassen", +"Orientation": "Orientatie", +"Brightness": "Helderheid", +"Sharpen": "Scherpte", +"Contrast": "Contrast", +"Color levels": "Kleurniveau's", +"Gamma": "Gamma", "Invert": "Omkeren", +"Apply": "Toepassen", +"Back": "Terug", "Insert date\/time": "Voeg datum\/tijd in", -"Remove link": "Link verwijderen", -"Url": "Url", -"Text to display": "Linktekst", -"Anchors": "Anker", +"Date\/time": "Datum\/tijd", "Insert link": "Hyperlink invoegen", -"New window": "Nieuw venster", -"None": "Geen", -"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "De ingegeven URL verwijst naar een extern adres. Wil je er \"http:\/\/\" aan toevoegen?", -"Target": "Doel", -"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "De ingegeven URL lijkt op een e-mailadres. Wil je er \"mailto:\" aan toevoegen?", "Insert\/edit link": "Hyperlink invoegen\/bewerken", -"Insert\/edit video": "Video invoegen\/bewerken", -"Poster": "Poster", -"Alternative source": "Alternatieve bron", -"Paste your embed code below:": "Plak u in te sluiten code hieronder:", +"Text to display": "Linktekst", +"Url": "Url", +"Target": "Doel", +"None": "Geen", +"New window": "Nieuw venster", +"Remove link": "Link verwijderen", +"Anchors": "Anker", +"Link": "Link", +"Paste or type a link": "Plak of typ een link", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "De ingegeven URL lijkt op een e-mailadres. Wil je er \"mailto:\" aan toevoegen?", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "De ingegeven URL verwijst naar een extern adres. Wil je er \"http:\/\/\" aan toevoegen?", +"Link list": "Linklijst", "Insert video": "Video invoegen", +"Insert\/edit video": "Video invoegen\/bewerken", +"Insert\/edit media": "Media invoegen\/bewerken", +"Alternative source": "Alternatieve bron", +"Poster": "Poster", +"Paste your embed code below:": "Plak u in te sluiten code hieronder:", "Embed": "Insluiten", +"Media": "Media", "Nonbreaking space": "Vaste spatie invoegen", "Page break": "Pagina einde", "Paste as text": "Plakken als tekst", "Preview": "Voorbeeld", "Print": "Print", "Save": "Opslaan", -"Could not find the specified string.": "Geen resultaten gevonden", -"Replace": "Vervangen", -"Next": "Volgende", -"Whole words": "Alleen hele woorden", -"Find and replace": "Zoek en vervang", -"Replace with": "Vervangen door", "Find": "Zoeken", +"Replace with": "Vervangen door", +"Replace": "Vervangen", "Replace all": "Alles vervangen", -"Match case": "Identieke hoofd\/kleine letters", "Prev": "Vorige", +"Next": "Volgende", +"Find and replace": "Zoek en vervang", +"Could not find the specified string.": "Geen resultaten gevonden", +"Match case": "Identieke hoofd\/kleine letters", +"Whole words": "Alleen hele woorden", "Spellcheck": "Spellingscontrole", -"Finish": "Einde", -"Ignore all": "Alles negeren", "Ignore": "Negeren", +"Ignore all": "Alles negeren", +"Finish": "Einde", "Add to Dictionary": "Toevoegen aan woordenlijst", -"Insert row before": "Voeg rij boven toe", -"Rows": "Rijen", -"Height": "Hoogte", -"Paste row after": "Plak rij onder", -"Alignment": "Uitlijning", -"Border color": "Randkleur", -"Column group": "Kolomgroep", -"Row": "Rij", -"Insert column before": "Voeg kolom in voor", -"Split cell": "Cel splitsen", -"Cell padding": "Ruimte binnen cel", -"Cell spacing": "Celruimte", -"Row type": "Rijtype", "Insert table": "Tabel invoegen", -"Body": "Body", -"Caption": "Onderschrift", -"Footer": "Voettekst", -"Delete row": "Verwijder rij", -"Paste row before": "Plak rij boven", -"Scope": "Bereik", -"Delete table": "Verwijder tabel", -"H Align": "Links uitlijnen", -"Top": "Bovenaan", -"Header cell": "Kopcel", -"Column": "Kolom", -"Row group": "Rijgroep", -"Cell": "Cel", -"Middle": "Centreren", -"Cell type": "Celtype", -"Copy row": "Kopieer rij", -"Row properties": "Rij eigenschappen", "Table properties": "Tabel eigenschappen", -"Bottom": "Onderaan", -"V Align": "Boven uitlijnen", -"Header": "Koptekst", -"Right": "Rechts", -"Insert column after": "Voeg kolom in na", -"Cols": "Kolommen", -"Insert row after": "Voeg rij onder toe", -"Width": "Breedte", +"Delete table": "Verwijder tabel", +"Cell": "Cel", +"Row": "Rij", +"Column": "Kolom", "Cell properties": "Cel eigenschappen", -"Left": "Links", -"Cut row": "Knip rij", -"Delete column": "Verwijder kolom", -"Center": "Midden", "Merge cells": "Cellen samenvoegen", +"Split cell": "Cel splitsen", +"Insert row before": "Voeg rij boven toe", +"Insert row after": "Voeg rij onder toe", +"Delete row": "Verwijder rij", +"Row properties": "Rij eigenschappen", +"Cut row": "Knip rij", +"Copy row": "Kopieer rij", +"Paste row before": "Plak rij boven", +"Paste row after": "Plak rij onder", +"Insert column before": "Voeg kolom in voor", +"Insert column after": "Voeg kolom in na", +"Delete column": "Verwijder kolom", +"Cols": "Kolommen", +"Rows": "Rijen", +"Width": "Breedte", +"Height": "Hoogte", +"Cell spacing": "Celruimte", +"Cell padding": "Ruimte binnen cel", +"Caption": "Onderschrift", +"Left": "Links", +"Center": "Midden", +"Right": "Rechts", +"Cell type": "Celtype", +"Scope": "Bereik", +"Alignment": "Uitlijning", +"H Align": "Links uitlijnen", +"V Align": "Boven uitlijnen", +"Top": "Bovenaan", +"Middle": "Centreren", +"Bottom": "Onderaan", +"Header cell": "Kopcel", +"Row group": "Rijgroep", +"Column group": "Kolomgroep", +"Row type": "Rijtype", +"Header": "Koptekst", +"Body": "Body", +"Footer": "Voettekst", +"Border color": "Randkleur", "Insert template": "Sjabloon invoegen", "Templates": "Sjablonen", +"Template": "Sjabloon", +"Text color": "Tekstkleur", "Background color": "Achtergrondkleur", "Custom...": "Eigen...", "Custom color": "Eigen kleur", "No color": "Geen kleur", -"Text color": "Tekstkleur", +"Table of Contents": "Inhoudsopgave", "Show blocks": "Blokken tonen", "Show invisible characters": "Onzichtbare karakters tonen", "Words: {0}": "Woorden: {0}", -"Insert": "Invoegen", +"{0} words": "{0} woorden", "File": "Bestand", "Edit": "Bewerken", -"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Rich Text Area. Druk ALT-F9 voor het menu. Druk ALT-F10 voor de toolbar. Druk ALT-0 voor help.", -"Tools": "Gereedschap", +"Insert": "Invoegen", "View": "Beeld", +"Format": "Opmaak", "Table": "Tabel", -"Format": "Opmaak" +"Tools": "Gereedschap", +"Powered by {0}": "Gemaakt door {0}", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Rich Text Area. Druk ALT-F9 voor het menu. Druk ALT-F10 voor de toolbar. Druk ALT-0 voor help." }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/no.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/no.js deleted file mode 100644 index 69ded56dab..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/no.js +++ /dev/null @@ -1 +0,0 @@ -tinyMCE.addI18n({no:{common:{"more_colors":"Flere farger","invalid_data":"Feil: Ugyldig verdi er skrevet inn, disse er merket med r\u00f8dt","popup_blocked":"Beklager, men vi har registrert at din popup-sperrer har blokkert et vindu i nettleseren. Du m\u00e5 oppheve popup-sperren for at nettstedet skal f\u00e5 tilgang til dette verkt\u00f8yet","clipboard_no_support":"For tiden ikke st\u00f8ttet av din nettleser, bruk tastatursnarveier i stedet.","clipboard_msg":"Klipp ut / Kopier /Lim inn fungerer ikke i Mozilla og Firefox. Vil du vite mer om dette?","not_set":"--Ikke satt--","class_name":"Klasse",browse:"Bla gjennom",close:"Lukk",cancel:"Avbryt",update:"Oppdater",insert:"Sett inn",apply:"Bruk","edit_confirm":"Vil du bruke WYSIWYG-editoren for dette tekstfeltet?","invalid_data_number":"{#field} m\u00e5 v\u00e6re et nummer","invalid_data_min":"{#field} m\u00e5 v\u00e6re et nummber st\u00f8rre en {#min}","invalid_data_size":"{#field} m\u00e5 v\u00e6re et nummer eller prosent av",value:"(verdi)"},contextmenu:{full:"Full",right:"H\u00f8yre",center:"Midtstilt",left:"Venstre",align:"Justering"},insertdatetime:{"day_short":"S\u00f8n,Man,Tir,Ons,Tor,Fre,L\u00f8r,S\u00f8n","day_long":"s\u00f8ndag,mandag,tirsdag,onsdag,torsdag,fredag,l\u00f8rdag,s\u00f8ndag","months_short":"jan,feb,mar,apr,mai,jun,jul,aug,sep,okt,nov,des","months_long":"januar,februar,mars,april,mai,juni,juli,august,september,oktober,november,desember","inserttime_desc":"Sett inn tid","insertdate_desc":"Sett inn dato","time_fmt":"%H:%M:%S","date_fmt":"%d-%m-%Y"},print:{"print_desc":"Skriv ut"},preview:{"preview_desc":"Forh\u00e5ndsvisning"},directionality:{"rtl_desc":"Retning h\u00f8yre mot venstre","ltr_desc":"Retning venstre mot h\u00f8yre"},layer:{content:"Nytt lag ...","absolute_desc":"Sl\u00e5 p\u00e5/av absolutt plassering","backward_desc":"Flytt bakover","forward_desc":"Flytt fremover","insertlayer_desc":"Sett inn nytt lag"},save:{"save_desc":"Lagre","cancel_desc":"Kanseller alle endringer"},nonbreaking:{"nonbreaking_desc":"Sett inn tegn for hardt mellomrom"},iespell:{download:"ieSpell ikke funnet. \u00d8nsker du \u00e5 installere ieSpell?","iespell_desc":"Stavekontroll"},advhr:{"advhr_desc":"Horisontal linje","delta_height":"","delta_width":""},emotions:{"emotions_desc":"Hum\u00f8rfjes","delta_height":"","delta_width":""},searchreplace:{"replace_desc":"S\u00f8k/Erstatt","search_desc":"S\u00f8k","delta_width":"","delta_height":""},advimage:{"image_desc":"Sett inn / rediger bilde","delta_width":"","delta_height":""},advlink:{"link_desc":"Sett inn / rediger lenke","delta_height":"","delta_width":""},xhtmlxtras:{"attribs_desc":"Sett inn / rediger attributter","ins_desc":"Innsetting","del_desc":"Sletting","acronym_desc":"Akronym","abbr_desc":"Forkortelse","cite_desc":"Sitat","attribs_delta_height":"","attribs_delta_width":"","ins_delta_height":"","ins_delta_width":"","del_delta_height":"","del_delta_width":"","acronym_delta_height":"","acronym_delta_width":"","abbr_delta_height":"","abbr_delta_width":"","cite_delta_height":"","cite_delta_width":""},style:{desc:"Rediger CSS-stil","delta_height":"","delta_width":""},paste:{"plaintext_mode":"Lim inn er n\u00e5 i vanlig tekst modus. Klikk igjen for \u00e5 bytte til vanlig innlimings modus.","plaintext_mode_sticky":"Lim inn er n\u00e5 i vanlig tekst modus. Klikk igjen for \u00e5 bytte til vanlig innlimings modus. Etter at du limer inn noe vil du g\u00e5 tilbake til ordin\u00e6r innliming.","selectall_desc":"Merk alt","paste_word_desc":"Lim inn fra Word","paste_text_desc":"Lim inn som ren tekst"},"paste_dlg":{"word_title":"Bruk CTRL+V p\u00e5 tastaturet for \u00e5 lime inn teksten i dette vinduet.","text_linebreaks":"Behold tekstbryting","text_title":"Bruk CTRL+V p\u00e5 tastaturet for \u00e5 lime inn teksten i dette vinduet."},table:{cell:"Celle",col:"Kolonne",row:"Rad",del:"Slett tabell","copy_row_desc":"Kopier rad","cut_row_desc":"Slett rad","paste_row_after_desc":"Lim inn rad etter","paste_row_before_desc":"Lim inn rad foran","props_desc":"Tabellegenskaper","cell_desc":"Celleegenskaper","row_desc":"Radegenskaper","merge_cells_desc":"Sl\u00e5 sammen celler","split_cells_desc":"Splitt sammensl\u00e5tte celler","delete_col_desc":"Slett kolonne","col_after_desc":"Sett inn kolonne etter","col_before_desc":"Sett inn kolonne foran","delete_row_desc":"Slett rad","row_after_desc":"Sett inn rad etter","row_before_desc":"Sett inn rad foran",desc:"Sett inn ny tabell","merge_cells_delta_height":"","merge_cells_delta_width":"","table_delta_height":"","table_delta_width":"","cellprops_delta_height":"","cellprops_delta_width":"","rowprops_delta_height":"","rowprops_delta_width":""},autosave:{"warning_message":"Hvis du gjenoppretter tidligere lagret innhold s\u00e5 vil du miste alt n\u00e5v\u00e6rende innhold i editoren.\n\nEr du sikker p\u00e5 at du vil gjenopprette tidligere lagret innhold?.","restore_content":"Gjenopprett autolagret innhold.","unload_msg":"Utf\u00f8rte endringer g\u00e5r tapt hvis du navigerer vekk fra denne siden!"},fullscreen:{desc:"Sl\u00e5 fullskjermsmodus av/p\u00e5"},media:{edit:"Rediger innebygd objekt",desc:"Sett inn / rediger innebygd objekt","delta_height":"","delta_width":""},fullpage:{desc:"Dokumentegenskaper","delta_width":"","delta_height":""},template:{desc:"Sett inn forh\u00e5ndsdefinert malinnhold"},visualchars:{desc:"Visuelle konktrolltegn p\u00e5/av"},spellchecker:{desc:"Stavekontroll p\u00e5/av",menu:"Oppsett stavekontroll","ignore_word":"Ignorer ord","ignore_words":"Ignorer alt",langs:"Spr\u00e5k",wait:"Vennligst vent ...",sug:"Forslag","no_sug":"Ingen forslag","no_mpell":"Ingen stavefeil funnet.","learn_word":"L\u00e6r ordet"},pagebreak:{desc:"Sett inn sideskift"},advlist:{types:"Types",def:"Standard","lower_alpha":"Sm\u00e5 alfanumerisk","lower_greek":"Sm\u00e5 gresk","lower_roman":"Sm\u00e5 roman","upper_alpha":"Store alfanumerisk","upper_roman":"Store roman",circle:"Sirkel",disc:"Plate",square:"Firkant"},colors:{"333300":"M\u00f8rk olivengr\u00f8nn","993300":"Brent oransje","000000":"Svart","003300":"M\u00f8rkegr\u00f8nn","003366":"M\u00f8rk asurbl\u00e5","000080":"Marinebl\u00e5","333399":"Indigobl\u00e5","333333":"M\u00f8rk m\u00f8rkegr\u00e5","800000":"R\u00f8dbrun",FF6600:"Oransje","808000":"Olivengr\u00f8nn","008000":"Gr\u00f8nn","008080":"M\u00f8rk gr\u00f8nnbl\u00e5","0000FF":"Bl\u00e5","666699":"Gr\u00e5bl\u00e5","808080":"Gr\u00e5",FF0000:"R\u00f8d",FF9900:"Amber","99CC00":"Gulgr\u00f8nn","339966":"Havgr\u00f8nn","33CCCC":"Turkis","3366FF":"Kongebl\u00e5","800080":"Purpur","999999":"Medium gr\u00e5",FF00FF:"Magentar\u00f8d",FFCC00:"Gull",FFFF00:"Gul","00FF00":"Limegr\u00f8nn","00FFFF":"Cyanbl\u00e5","00CCFF":"Himmelbl\u00e5","993366":"Brun",C0C0C0:"S\u00f8lv",FF99CC:"Rosa",FFCC99:"Fersken",FFFF99:"Lysgul",CCFFCC:"Lysegr\u00f8nn",CCFFFF:"Lys cyanbl\u00e5","99CCFF":"Lys himmelbl\u00e5",CC99FF:"Plomme",FFFFFF:"Hvit"},aria:{"rich_text_area":"Rikt tekstfelt"},wordcount:{words:"Ord:"}}}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/pl.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/pl.js index b80cb7bc95..92dc74dfa6 100644 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/pl.js +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/pl.js @@ -1,219 +1,261 @@ tinymce.addI18n('pl',{ -"Cut": "Wytnij", -"Heading 5": "Nag\u0142\u00f3wek 5", -"Header 2": "Nag\u0142\u00f3wek 2", -"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "Twoja przegl\u0105darka nie obs\u0142uguje bezpo\u015bredniego dost\u0119pu do schowka. U\u017cyj zamiast tego kombinacji klawiszy Ctrl+X\/C\/V.", -"Heading 4": "Nag\u0142\u00f3wek 4", -"Div": "Div", -"Heading 2": "Nag\u0142\u00f3wek 2", -"Paste": "Wklej", -"Close": "Zamknij", -"Font Family": "Kr\u00f3j fontu", -"Pre": "Sformatowany tekst", -"Align right": "Wyr\u00f3wnaj do prawej", -"New document": "Nowy dokument", -"Blockquote": "Blok cytatu", -"Numbered list": "Lista numerowana", -"Heading 1": "Nag\u0142\u00f3wek 1", -"Headings": "Nag\u0142\u00f3wki", -"Increase indent": "Zwi\u0119ksz wci\u0119cie", -"Formats": "Formaty", -"Headers": "Nag\u0142\u00f3wki", -"Select all": "Zaznacz wszystko", -"Header 3": "Nag\u0142\u00f3wek 3", -"Blocks": "Bloki", -"Undo": "Cofnij", -"Strikethrough": "Przekre\u015blenie", -"Bullet list": "Lista wypunktowana", -"Header 1": "Nag\u0142\u00f3wek 1", -"Superscript": "Indeks g\u00f3rny", -"Clear formatting": "Wyczy\u015b\u0107 formatowanie", -"Font Sizes": "Rozmiar fontu", -"Subscript": "Indeks dolny", -"Header 6": "Nag\u0142\u00f3wek 6", "Redo": "Pon\u00f3w", -"Paragraph": "Akapit", -"Ok": "Ok", -"Bold": "Pogrubienie", -"Code": "Kod \u017ar\u00f3d\u0142owy", -"Italic": "Kursywa", -"Align center": "Wyr\u00f3wnaj do \u015brodka", -"Header 5": "Nag\u0142\u00f3wek 5", -"Heading 6": "Nag\u0142\u00f3wek 6", -"Heading 3": "Nag\u0142\u00f3wek 3", -"Decrease indent": "Zmniejsz wci\u0119cie", -"Header 4": "Nag\u0142\u00f3wek 4", -"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "Wklejanie jest w trybie tekstowym. Zawarto\u015b\u0107 zostanie wklejona jako zwyk\u0142y tekst dop\u00f3ki nie wy\u0142\u0105czysz tej opcji.", -"Underline": "Podkre\u015blenie", -"Cancel": "Anuluj", -"Justify": "Do lewej i prawej", -"Inline": "W tek\u015bcie", +"Undo": "Cofnij", +"Cut": "Wytnij", "Copy": "Kopiuj", -"Align left": "Wyr\u00f3wnaj do lewej", +"Paste": "Wklej", +"Select all": "Zaznacz wszystko", +"New document": "Nowy dokument", +"Ok": "Ok", +"Cancel": "Anuluj", "Visual aids": "Pomoce wizualne", -"Lower Greek": "Ma\u0142e greckie", -"Square": "Kwadrat", +"Bold": "Pogrubienie", +"Italic": "Kursywa", +"Underline": "Podkre\u015blenie", +"Strikethrough": "Przekre\u015blenie", +"Superscript": "Indeks g\u00f3rny", +"Subscript": "Indeks dolny", +"Clear formatting": "Wyczy\u015b\u0107 formatowanie", +"Align left": "Wyr\u00f3wnaj do lewej", +"Align center": "Wyr\u00f3wnaj do \u015brodka", +"Align right": "Wyr\u00f3wnaj do prawej", +"Justify": "Do lewej i prawej", +"Bullet list": "Lista wypunktowana", +"Numbered list": "Lista numerowana", +"Decrease indent": "Zmniejsz wci\u0119cie", +"Increase indent": "Zwi\u0119ksz wci\u0119cie", +"Close": "Zamknij", +"Formats": "Formaty", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "Twoja przegl\u0105darka nie obs\u0142uguje bezpo\u015bredniego dost\u0119pu do schowka. U\u017cyj zamiast tego kombinacji klawiszy Ctrl+X\/C\/V.", +"Headers": "Nag\u0142\u00f3wki", +"Header 1": "Nag\u0142\u00f3wek 1", +"Header 2": "Nag\u0142\u00f3wek 2", +"Header 3": "Nag\u0142\u00f3wek 3", +"Header 4": "Nag\u0142\u00f3wek 4", +"Header 5": "Nag\u0142\u00f3wek 5", +"Header 6": "Nag\u0142\u00f3wek 6", +"Headings": "Nag\u0142\u00f3wki", +"Heading 1": "Nag\u0142\u00f3wek 1", +"Heading 2": "Nag\u0142\u00f3wek 2", +"Heading 3": "Nag\u0142\u00f3wek 3", +"Heading 4": "Nag\u0142\u00f3wek 4", +"Heading 5": "Nag\u0142\u00f3wek 5", +"Heading 6": "Nag\u0142\u00f3wek 6", +"Preformatted": "Sformatowany tekst", +"Div": "Div", +"Pre": "Sformatowany tekst", +"Code": "Kod \u017ar\u00f3d\u0142owy", +"Paragraph": "Akapit", +"Blockquote": "Blok cytatu", +"Inline": "W tek\u015bcie", +"Blocks": "Bloki", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "Wklejanie jest w trybie tekstowym. Zawarto\u015b\u0107 zostanie wklejona jako zwyk\u0142y tekst dop\u00f3ki nie wy\u0142\u0105czysz tej opcji.", +"Font Family": "Kr\u00f3j fontu", +"Font Sizes": "Rozmiar fontu", +"Class": "Klasa", +"Browse for an image": "Przegl\u0105daj za zdj\u0119ciem", +"OR": "LUB", +"Drop an image here": "Upu\u015b\u0107 obraz tutaj", +"Upload": "Prze\u015blij", +"Block": "Zablokuj", +"Align": "Wyr\u00f3wnaj", "Default": "Domy\u015blne", -"Lower Alpha": "Ma\u0142e litery", "Circle": "K\u00f3\u0142ko", "Disc": "Dysk", +"Square": "Kwadrat", +"Lower Alpha": "Ma\u0142e litery", +"Lower Greek": "Ma\u0142e greckie", +"Lower Roman": "Ma\u0142e rzymskie", "Upper Alpha": "Wielkie litery", "Upper Roman": "Wielkie rzymskie", -"Lower Roman": "Ma\u0142e rzymskie", -"Name": "Nazwa", "Anchor": "Kotwica", +"Name": "Nazwa", +"Id": "Identyfikator", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "Identyfikator powinien zaczyna\u0107 si\u0119 liter\u0105, dozwolone s\u0105 tylko litery, numery, uko\u015bniki, kropki, dwukropki i podkre\u015blniki - tzw. pod\u0142ogi", "You have unsaved changes are you sure you want to navigate away?": "Masz niezapisane zmiany. Czy na pewno chcesz opu\u015bci\u0107 stron\u0119?", "Restore last draft": "Przywr\u00f3\u0107 ostatni szkic", "Special character": "Znak specjalny", "Source code": "Kod \u017ar\u00f3d\u0142owy", -"B": "B", +"Insert\/Edit code sample": "Dodaj\/Edytuj przyk\u0142adowy kod", +"Language": "J\u0119zyk", +"Code sample": "Przyk\u0142ad kodu \u017ar\u00f3d\u0142owego", +"Color": "Kolor", "R": "R", "G": "G", -"Color": "Kolor", -"Right to left": "Od prawej do lewej", +"B": "B", "Left to right": "Od lewej do prawej", +"Right to left": "Od prawej do lewej", "Emoticons": "Ikony emocji", -"Robots": "Roboty", "Document properties": "W\u0142a\u015bciwo\u015bci dokumentu", "Title": "Tytu\u0142", "Keywords": "S\u0142owa kluczowe", -"Encoding": "Kodowanie", "Description": "Opis", +"Robots": "Roboty", "Author": "Autor", +"Encoding": "Kodowanie", "Fullscreen": "Pe\u0142ny ekran", +"Action": "Akcja", +"Shortcut": "Skr\u00f3t", +"Help": "Pomoc", +"Address": "Adres", +"Focus to menubar": "Skup si\u0119 na pasku menu", +"Focus to toolbar": "Skupi\u0107 si\u0119 na pasku", +"Focus to element path": "Skup si\u0119 na \u015bcie\u017cce elementu", +"Focus to contextual toolbar": "Skupi\u0107 si\u0119 na pasku narz\u0119dzi kontekstowych", +"Insert link (if link plugin activated)": "Wstaw \u0142\u0105cze (je\u015bli w\u0142\u0105czysz wtyczk\u0119 link\u00f3w)", +"Save (if save plugin activated)": "Zapisz (je\u015bli aktywowana jest wtyczka do zapisu)", +"Find (if searchreplace plugin activated)": "Znajd\u017a (je\u015bli w\u0142\u0105czysz wtyczk\u0119 do wyszukiwania)", +"Plugins installed ({0}):": "Zainstalowane wtyczki ({0}):", +"Premium plugins:": "Wtyczki Premium:", +"Learn more...": "Dowiedz si\u0119 wi\u0119cej...", +"You are using {0}": "U\u017cywasz {0}", +"Plugins": "Pluginy", +"Handy Shortcuts": "Przydatne skr\u00f3ty", "Horizontal line": "Pozioma linia", -"Horizontal space": "Odst\u0119p poziomy", "Insert\/edit image": "Wstaw\/edytuj obrazek", +"Image description": "Opis obrazka", +"Source": "\u0179r\u00f3d\u0142o", +"Dimensions": "Wymiary", +"Constrain proportions": "Zachowaj proporcje", "General": "Og\u00f3lne", "Advanced": "Zaawansowane", -"Source": "\u0179r\u00f3d\u0142o", -"Border": "Ramka", -"Constrain proportions": "Zachowaj proporcje", -"Vertical space": "Odst\u0119p pionowy", -"Image description": "Opis obrazka", "Style": "Styl", -"Dimensions": "Wymiary", +"Vertical space": "Odst\u0119p pionowy", +"Horizontal space": "Odst\u0119p poziomy", +"Border": "Ramka", "Insert image": "Wstaw obrazek", -"Zoom in": "Powi\u0119ksz", -"Contrast": "Kontrast", -"Back": "Cofnij", -"Gamma": "Gamma", -"Flip horizontally": "Przerzu\u0107 w poziomie", -"Resize": "Zmiana rozmiaru", -"Sharpen": "Wyostrz", -"Zoom out": "Pomniejsz", -"Image options": "Opcje obrazu", -"Apply": "Zaakceptuj", -"Brightness": "Jasno\u015b\u0107", -"Rotate clockwise": "Obr\u00f3\u0107 w prawo", +"Image": "Obraz", +"Image list": "Lista obrazk\u00f3w", "Rotate counterclockwise": "Obr\u00f3\u0107 w lewo", -"Edit image": "Edytuj obrazek", -"Color levels": "Poziom koloru", -"Crop": "Przytnij", -"Orientation": "Orientacja", +"Rotate clockwise": "Obr\u00f3\u0107 w prawo", "Flip vertically": "Przerzu\u0107 w pionie", +"Flip horizontally": "Przerzu\u0107 w poziomie", +"Edit image": "Edytuj obrazek", +"Image options": "Opcje obrazu", +"Zoom in": "Powi\u0119ksz", +"Zoom out": "Pomniejsz", +"Crop": "Przytnij", +"Resize": "Zmiana rozmiaru", +"Orientation": "Orientacja", +"Brightness": "Jasno\u015b\u0107", +"Sharpen": "Wyostrz", +"Contrast": "Kontrast", +"Color levels": "Poziom koloru", +"Gamma": "Gamma", "Invert": "Odwr\u00f3\u0107", +"Apply": "Zaakceptuj", +"Back": "Cofnij", "Insert date\/time": "Wstaw dat\u0119\/czas", -"Remove link": "Usu\u0144 \u0142\u0105cze", -"Url": "URL", -"Text to display": "Tekst do wy\u015bwietlenia", -"Anchors": "Kotwice", +"Date\/time": "Data\/Czas", "Insert link": "Wstaw \u0142\u0105cze", -"New window": "Nowe okno", -"None": "\u017baden", -"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "URL, kt\u00f3ry wprowadzi\u0142e\u015b wygl\u0105da na link zewn\u0119trzny. Czy chcesz doda\u0107 http:\/\/ jako prefiks?", -"Target": "Cel", -"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "URL, kt\u00f3ry wprowadzi\u0142e\u015b wygl\u0105da na adres e-mail. Czy chcesz doda\u0107 mailto: jako prefiks?", "Insert\/edit link": "Wstaw\/edytuj \u0142\u0105cze", -"Insert\/edit video": "Wstaw\/edytuj wideo", -"Poster": "Plakat", -"Alternative source": "Alternatywne \u017ar\u00f3d\u0142o", -"Paste your embed code below:": "Wklej tutaj kod do osadzenia:", +"Text to display": "Tekst do wy\u015bwietlenia", +"Url": "URL", +"Target": "Cel", +"None": "\u017baden", +"New window": "Nowe okno", +"Remove link": "Usu\u0144 \u0142\u0105cze", +"Anchors": "Kotwice", +"Link": "Adres \u0142\u0105cza", +"Paste or type a link": "Wklej lub wpisz adres \u0142\u0105cza", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "URL, kt\u00f3ry wprowadzi\u0142e\u015b wygl\u0105da na adres e-mail. Czy chcesz doda\u0107 mailto: jako prefiks?", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "URL, kt\u00f3ry wprowadzi\u0142e\u015b wygl\u0105da na link zewn\u0119trzny. Czy chcesz doda\u0107 http:\/\/ jako prefiks?", +"Link list": "Lista link\u00f3w", "Insert video": "Wstaw wideo", +"Insert\/edit video": "Wstaw\/edytuj wideo", +"Insert\/edit media": "Wstaw\/Edytuj media", +"Alternative source": "Alternatywne \u017ar\u00f3d\u0142o", +"Poster": "Plakat", +"Paste your embed code below:": "Wklej tutaj kod do osadzenia:", "Embed": "Osad\u017a", +"Media": "Media", "Nonbreaking space": "Nie\u0142amliwa spacja", "Page break": "Podzia\u0142 strony", "Paste as text": "Wklej jako zwyk\u0142y tekst", "Preview": "Podgl\u0105d", "Print": "Drukuj", "Save": "Zapisz", -"Could not find the specified string.": "Nie znaleziono szukanego tekstu.", -"Replace": "Zamie\u0144", -"Next": "Nast.", -"Whole words": "Ca\u0142e s\u0142owa", -"Find and replace": "Znajd\u017a i zamie\u0144", -"Replace with": "Zamie\u0144 na", "Find": "Znajd\u017a", +"Replace with": "Zamie\u0144 na", +"Replace": "Zamie\u0144", "Replace all": "Zamie\u0144 wszystko", -"Match case": "Dopasuj wielko\u015b\u0107 liter", "Prev": "Poprz.", +"Next": "Nast.", +"Find and replace": "Znajd\u017a i zamie\u0144", +"Could not find the specified string.": "Nie znaleziono szukanego tekstu.", +"Match case": "Dopasuj wielko\u015b\u0107 liter", +"Whole words": "Ca\u0142e s\u0142owa", "Spellcheck": "Sprawdzanie pisowni", -"Finish": "Zako\u0144cz", -"Ignore all": "Ignoruj wszystko", "Ignore": "Ignoruj", +"Ignore all": "Ignoruj wszystko", +"Finish": "Zako\u0144cz", "Add to Dictionary": "Dodaj do s\u0142ownika", -"Insert row before": "Wstaw wiersz przed", -"Rows": "Wiersz.", -"Height": "Wysoko\u015b\u0107", -"Paste row after": "Wklej wiersz po", -"Alignment": "Wyr\u00f3wnanie", -"Border color": "Kolor ramki", -"Column group": "Grupa kolumn", -"Row": "Wiersz", -"Insert column before": "Wstaw kolumn\u0119 przed", -"Split cell": "Podziel kom\u00f3rk\u0119", -"Cell padding": "Dope\u0142nienie kom\u00f3rki", -"Cell spacing": "Odst\u0119py kom\u00f3rek", -"Row type": "Typ wiersza", "Insert table": "Wstaw tabel\u0119", -"Body": "Tre\u015b\u0107", -"Caption": "Tytu\u0142", -"Footer": "Stopka", -"Delete row": "Usu\u0144 wiersz", -"Paste row before": "Wklej wiersz przed", -"Scope": "Kontekst", -"Delete table": "Usu\u0144 tabel\u0119", -"H Align": "Wyr\u00f3wnanie w pionie", -"Top": "G\u00f3ra", -"Header cell": "Kom\u00f3rka nag\u0142\u00f3wka", -"Column": "Kolumna", -"Row group": "Grupa wierszy", -"Cell": "Kom\u00f3rka", -"Middle": "\u015arodek", -"Cell type": "Typ kom\u00f3rki", -"Copy row": "Kopiuj wiersz", -"Row properties": "W\u0142a\u015bciwo\u015bci wiersza", "Table properties": "W\u0142a\u015bciwo\u015bci tabeli", -"Bottom": "D\u00f3\u0142", -"V Align": "Wyr\u00f3wnanie w poziomie", -"Header": "Nag\u0142\u00f3wek", -"Right": "Prawo", -"Insert column after": "Wstaw kolumn\u0119 po", -"Cols": "Kol.", -"Insert row after": "Wstaw wiersz po", -"Width": "Szeroko\u015b\u0107", +"Delete table": "Usu\u0144 tabel\u0119", +"Cell": "Kom\u00f3rka", +"Row": "Wiersz", +"Column": "Kolumna", "Cell properties": "W\u0142a\u015bciwo\u015bci kom\u00f3rki", -"Left": "Lewo", -"Cut row": "Wytnij wiersz", -"Delete column": "Usu\u0144 kolumn\u0119", -"Center": "\u015arodek", "Merge cells": "\u0141\u0105cz kom\u00f3rki", +"Split cell": "Podziel kom\u00f3rk\u0119", +"Insert row before": "Wstaw wiersz przed", +"Insert row after": "Wstaw wiersz po", +"Delete row": "Usu\u0144 wiersz", +"Row properties": "W\u0142a\u015bciwo\u015bci wiersza", +"Cut row": "Wytnij wiersz", +"Copy row": "Kopiuj wiersz", +"Paste row before": "Wklej wiersz przed", +"Paste row after": "Wklej wiersz po", +"Insert column before": "Wstaw kolumn\u0119 przed", +"Insert column after": "Wstaw kolumn\u0119 po", +"Delete column": "Usu\u0144 kolumn\u0119", +"Cols": "Kol.", +"Rows": "Wiersz.", +"Width": "Szeroko\u015b\u0107", +"Height": "Wysoko\u015b\u0107", +"Cell spacing": "Odst\u0119py kom\u00f3rek", +"Cell padding": "Dope\u0142nienie kom\u00f3rki", +"Caption": "Tytu\u0142", +"Left": "Lewo", +"Center": "\u015arodek", +"Right": "Prawo", +"Cell type": "Typ kom\u00f3rki", +"Scope": "Kontekst", +"Alignment": "Wyr\u00f3wnanie", +"H Align": "Wyr\u00f3wnanie w pionie", +"V Align": "Wyr\u00f3wnanie w poziomie", +"Top": "G\u00f3ra", +"Middle": "\u015arodek", +"Bottom": "D\u00f3\u0142", +"Header cell": "Kom\u00f3rka nag\u0142\u00f3wka", +"Row group": "Grupa wierszy", +"Column group": "Grupa kolumn", +"Row type": "Typ wiersza", +"Header": "Nag\u0142\u00f3wek", +"Body": "Tre\u015b\u0107", +"Footer": "Stopka", +"Border color": "Kolor ramki", "Insert template": "Wstaw szablon", "Templates": "Szablony", +"Template": "Szablon", +"Text color": "Kolor tekstu", "Background color": "Kolor t\u0142a", "Custom...": "Niestandardowy...", "Custom color": "Kolor niestandardowy", "No color": "Bez koloru", -"Text color": "Kolor tekstu", +"Table of Contents": "Spis tre\u015bci", "Show blocks": "Poka\u017c bloki", "Show invisible characters": "Poka\u017c niewidoczne znaki", "Words: {0}": "S\u0142\u00f3w: {0}", -"Insert": "Wstaw", +"{0} words": "{0} s\u0142\u00f3w", "File": "Plik", "Edit": "Edycja", -"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Obszar Edycji. ALT-F9 - menu. ALT-F10 - pasek narz\u0119dzi. ALT-0 - pomoc", -"Tools": "Narz\u0119dzia", +"Insert": "Wstaw", "View": "Widok", +"Format": "Format", "Table": "Tabela", -"Format": "Format" +"Tools": "Narz\u0119dzia", +"Powered by {0}": "Powered by {0}", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Obszar Edycji. ALT-F9 - menu. ALT-F10 - pasek narz\u0119dzi. ALT-0 - pomoc" }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/pt.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/pt.js deleted file mode 100644 index 809f1c2d9b..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/pt.js +++ /dev/null @@ -1 +0,0 @@ -tinyMCE.addI18n({pt:{common:{"more_colors":"Mais Cores","invalid_data":"Erro: Valores inv\u00e1lidos marcados em vermelho.","popup_blocked":"Detectamos que o seu bloqueador de popups bloqueou uma janela que \u00e9 essencial para a aplica\u00e7\u00e3o. Voc\u00ea precisa desativar o bloqueador de janelas de popups para utilizar esta ferramenta.","clipboard_no_support":"O seu browser n\u00e3o suporta esta fun\u00e7\u00e3o, use os atalhos do teclado.","clipboard_msg":"Copiar/recortar/colar n\u00e3o est\u00e1 dispon\u00edvel no Mozilla e Firefox. Deseja mais informa\u00e7\u00f5es sobre este problema?","not_set":"-- N/A --","class_name":"Classe",browse:"Procurar",close:"Fechar",cancel:"Cancelar",update:"Atualizar",insert:"Inserir",apply:"Aplicar","edit_confirm":"Deseja usar o modo de edi\u00e7\u00e3o avan\u00e7ado neste campo de texto?","invalid_data_number":"{#field} deve ser um n\u00famero","invalid_data_min":"{#field} deve ser um n\u00famero maior que {#min}","invalid_data_size":"{#field} deve ser um n\u00famero ou uma percentagem",value:"(valor)"},contextmenu:{full:"Justificado",right:"Direita",center:"Centro",left:"Esquerda",align:"Alinhamento"},insertdatetime:{"day_short":"Dom,Seg,Ter,Qua,Qui,Sex,Sab,Dom","day_long":"Domingo,Segunda-feira,Ter\u00e7a-feira,Quarta-feira,Quinta-feira,Sexta-feira,S\u00e1bado,Domingo","months_short":"Jan,Fev,Mar,Abr,Mai,Jun,Jul,Ago,Set,Out,Nov,Dez","months_long":"Janeiro,Fevereiro,Mar\u00e7o,Abril,Maio,Junho,Julho,Agosto,Setembro,Outubro,Novembro,Dezembro","inserttime_desc":"Inserir hora","insertdate_desc":"Inserir data","time_fmt":"%H:%M:%S","date_fmt":"%d-%m-%Y"},print:{"print_desc":"Imprimir"},preview:{"preview_desc":"Pr\u00e9-visualizar"},directionality:{"rtl_desc":"Da direita para esquerda","ltr_desc":"Da esquerda para direita"},layer:{content:"Nova camada...","absolute_desc":"Alternar o posicionamento absoluto","backward_desc":"Mover para tr\u00e1s","forward_desc":"Mover para frente","insertlayer_desc":"Inserir nova camada"},save:{"save_desc":"Salvar","cancel_desc":"Cancelar todas as altera\u00e7\u00f5es"},nonbreaking:{"nonbreaking_desc":"Inserir um espa\u00e7o \"sem quebra\""},iespell:{download:"Plugin de ortografia n\u00e3o-detectado. Deseja instalar agora?","iespell_desc":"Verificar ortografia"},advhr:{"advhr_desc":"Separador horizontal","delta_height":"","delta_width":""},emotions:{"emotions_desc":"Emoticons","delta_height":"","delta_width":""},searchreplace:{"replace_desc":"Localizar/substituir","search_desc":"Localizar","delta_width":"","delta_height":""},advimage:{"image_desc":"Inserir/editar imagem","delta_width":"","delta_height":""},advlink:{"delta_width":"50","link_desc":"Inserir/editar hyperlink","delta_height":""},xhtmlxtras:{"attribs_desc":"Inserir/Editar atributos","ins_desc":"Inserir","del_desc":"Apagar","acronym_desc":"Acr\u00f4nimo","abbr_desc":"Abrevia\u00e7\u00e3o","cite_desc":"Cita\u00e7\u00e3o","attribs_delta_height":"","attribs_delta_width":"","ins_delta_height":"","ins_delta_width":"","del_delta_height":"","del_delta_width":"","acronym_delta_height":"","acronym_delta_width":"","abbr_delta_height":"","abbr_delta_width":"","cite_delta_height":"","cite_delta_width":""},style:{desc:"Editar CSS","delta_height":"","delta_width":""},paste:{"plaintext_mode":"Comando colar est\u00e1 em modo texto simples. Clique novamente para voltar para o modo normal.","plaintext_mode_sticky":"Comando colar est\u00e1 em modo texto simples. Clique novamente para voltar para o modo normal. Depois de colar alguma coisa retornar\u00e1 para o modo normal.","selectall_desc":"Selecionar tudo","paste_word_desc":"Colar (copiado do WORD)","paste_text_desc":"Colar como texto simples"},"paste_dlg":{"word_title":"Use CTRL+V para colar o texto na janela.","text_linebreaks":"Manter quebras de linha","text_title":"Use CTRL+V para colar o texto na janela."},table:{cell:"C\u00e9lula",col:"Coluna",row:"Linha",del:"Apagar tabela","copy_row_desc":"Copiar linha","cut_row_desc":"Recortar linha","paste_row_after_desc":"Colar linha depois","paste_row_before_desc":"Colar linha antes","props_desc":"Propriedades da tabela","cell_desc":"Propriedades das c\u00e9lulas","row_desc":"Propriedades das linhas","merge_cells_desc":"Unir c\u00e9lulas","split_cells_desc":"Dividir c\u00e9lulas","delete_col_desc":"Remover coluna","col_after_desc":"Inserir coluna depois","col_before_desc":"Inserir coluna antes","delete_row_desc":"Apagar linha","row_after_desc":"Inserir linha depois","row_before_desc":"Inserir linha antes",desc:"Inserir nova tabela","merge_cells_delta_height":"","merge_cells_delta_width":"","table_delta_height":"","table_delta_width":"","cellprops_delta_height":"","cellprops_delta_width":"","rowprops_delta_height":"","rowprops_delta_width":""},autosave:{"warning_message":"Se restaurar o conte\u00fado, voc\u00ea ir\u00e1 perder tudo que est\u00e1 atualmente no editor.\n\nTem certeza que quer restaurar o conte\u00fado salvo?","restore_content":"Restaura conte\u00fado salvo automaticamente.","unload_msg":"As mudan\u00e7as efetuadas ser\u00e3o perdidas se sair desta p\u00e1gina."},fullscreen:{desc:"Tela Inteira"},media:{edit:"Editar m\u00eddia embutida",desc:"Inserir/Editar m\u00eddia embutida","delta_height":"","delta_width":""},fullpage:{desc:"Propriedades do Documento","delta_width":"","delta_height":""},template:{desc:"Inserir template"},visualchars:{desc:"Caracteres de controle visual ligado/desligado"},spellchecker:{desc:"Alternar verifica\u00e7\u00e3o ortogr\u00e1fica",menu:"Configura\u00e7\u00f5es de ortografia","ignore_word":"Ignorar palavra","ignore_words":"Ignorar tudo",langs:"Linguagens",wait:"Aguarde...",sug:"Sugest\u00f5es","no_sug":"Sem sugest\u00f5es","no_mpell":"N\u00e3o foram detectados erros de ortografia.","learn_word":"Aprender palavra"},pagebreak:{desc:"Inserir quebra de p\u00e1gina."},advlist:{types:"Tipos",def:"Padr\u00e3o","lower_alpha":"Alfabeto min\u00fasculo","lower_greek":"Alfabeto grego","lower_roman":"Num. romanos min\u00fasculos","upper_alpha":"Alfabeto mai\u00fasculos","upper_roman":"Num. romanos mai\u00fasculos",circle:"C\u00edrculo",disc:"Disco",square:"Quadrado"},colors:{"333300":"Oliva escuro","993300":"Laranja queimado","000000":"Preto","003300":"Verde escuro","003366":"Azul escuro","000080":"Azul marinho","333399":"\u00cdndigo","333333":"Cinza muito escuro","800000":"Marrom 1",FF6600:"Laranja","808000":"Oliva","008000":"Verde","008080":"Verde azulado","0000FF":"Azul","666699":"Azul acinzentado","808080":"Cinza",FF0000:"Vermelho",FF9900:"\u00c2mbar","99CC00":"Amarelo esverdeado","339966":"Verde mar","33CCCC":"Turquesa","3366FF":"Azul real","800080":"Roxo","999999":"Cinza m\u00e9dio",FF00FF:"Magenta",FFCC00:"Ouro",FFFF00:"Amarelo","00FF00":"Lima","00FFFF":"\u00c1gua","00CCFF":"Azul celeste","993366":"Marrom 2",C0C0C0:"Prata",FF99CC:"Rosa",FFCC99:"P\u00eassego",FFFF99:"Amarelo claro",CCFFCC:"Verde p\u00e1lido",CCFFFF:"Ciano p\u00e1lido","99CCFF":"Azul celeste claro",CC99FF:"Ameixa",FFFFFF:"Branco"},aria:{"rich_text_area":"\u00c1rea de texto rico"},wordcount:{words:"Palavras:"}}}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/pt_BR.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/pt_BR.js new file mode 100644 index 0000000000..497043a9d4 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/pt_BR.js @@ -0,0 +1,261 @@ +tinymce.addI18n('pt_BR',{ +"Redo": "Refazer", +"Undo": "Desfazer", +"Cut": "Recortar", +"Copy": "Copiar", +"Paste": "Colar", +"Select all": "Selecionar tudo", +"New document": "Novo documento", +"Ok": "Ok", +"Cancel": "Cancelar", +"Visual aids": "Ajuda visual", +"Bold": "Negrito", +"Italic": "It\u00e1lico", +"Underline": "Sublinhar", +"Strikethrough": "Riscar", +"Superscript": "Sobrescrito", +"Subscript": "Subscrever", +"Clear formatting": "Limpar formata\u00e7\u00e3o", +"Align left": "Alinhar \u00e0 esquerda", +"Align center": "Centralizar", +"Align right": "Alinhar \u00e0 direita", +"Justify": "Justificar", +"Bullet list": "Lista n\u00e3o ordenada", +"Numbered list": "Lista ordenada", +"Decrease indent": "Diminuir recuo", +"Increase indent": "Aumentar recuo", +"Close": "Fechar", +"Formats": "Formatos", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "Seu navegador n\u00e3o suporta acesso direto \u00e0 \u00e1rea de transfer\u00eancia. Por favor use os atalhos Ctrl+X - C - V do teclado", +"Headers": "Cabe\u00e7alhos", +"Header 1": "Cabe\u00e7alho 1", +"Header 2": "Cabe\u00e7alho 2", +"Header 3": "Cabe\u00e7alho 3", +"Header 4": "Cabe\u00e7alho 4", +"Header 5": "Cabe\u00e7alho 5", +"Header 6": "Cabe\u00e7alho 6", +"Headings": "Cabe\u00e7alhos", +"Heading 1": "Cabe\u00e7alho 1", +"Heading 2": "Cabe\u00e7alho 2", +"Heading 3": "Cabe\u00e7alho 3", +"Heading 4": "Cabe\u00e7alho 4", +"Heading 5": "Cabe\u00e7alho 5", +"Heading 6": "Cabe\u00e7alho 6", +"Preformatted": "Preformatado", +"Div": "Div", +"Pre": "Pre", +"Code": "C\u00f3digo", +"Paragraph": "Par\u00e1grafo", +"Blockquote": "Aspas", +"Inline": "Em linha", +"Blocks": "Blocos", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "O comando colar est\u00e1 agora em modo texto plano. O conte\u00fado ser\u00e1 colado como texto plano at\u00e9 voc\u00ea desligar esta op\u00e7\u00e3o.", +"Font Family": "Fonte", +"Font Sizes": "Tamanho", +"Class": "Classe", +"Browse for an image": "Procure uma imagem", +"OR": "OU", +"Drop an image here": "Arraste uma imagem aqui", +"Upload": "Carregar", +"Block": "Bloco", +"Align": "Alinhamento", +"Default": "Padr\u00e3o", +"Circle": "C\u00edrculo", +"Disc": "Disco", +"Square": "Quadrado", +"Lower Alpha": "a. b. c. ...", +"Lower Greek": "\u03b1. \u03b2. \u03b3. ...", +"Lower Roman": "i. ii. iii. ...", +"Upper Alpha": "A. B. C. ...", +"Upper Roman": "I. II. III. ...", +"Anchor": "\u00c2ncora", +"Name": "Nome", +"Id": "Id", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "Id deve come\u00e7ar com uma letra, seguido apenas por letras, n\u00fameros, tra\u00e7os, pontos, dois pontos ou sublinhados.", +"You have unsaved changes are you sure you want to navigate away?": "Voc\u00ea tem mudan\u00e7as n\u00e3o salvas. Voc\u00ea tem certeza que deseja sair?", +"Restore last draft": "Restaurar \u00faltimo rascunho", +"Special character": "Caracteres especiais", +"Source code": "C\u00f3digo fonte", +"Insert\/Edit code sample": "Inserir\/Editar c\u00f3digo de exemplo", +"Language": "Idioma", +"Code sample": "Exemplo de c\u00f3digo", +"Color": "Cor", +"R": "R", +"G": "G", +"B": "B", +"Left to right": "Da esquerda para a direita", +"Right to left": "Da direita para a esquerda", +"Emoticons": "Emoticons", +"Document properties": "Propriedades do documento", +"Title": "T\u00edtulo", +"Keywords": "Palavras-chave", +"Description": "Descri\u00e7\u00e3o", +"Robots": "Rob\u00f4s", +"Author": "Autor", +"Encoding": "Codifica\u00e7\u00e3o", +"Fullscreen": "Tela cheia", +"Action": "A\u00e7\u00e3o", +"Shortcut": "Atalho", +"Help": "Ajuda", +"Address": "Endere\u00e7o", +"Focus to menubar": "Foco no menu", +"Focus to toolbar": "Foco na barra de ferramentas", +"Focus to element path": "Foco no caminho do elemento", +"Focus to contextual toolbar": "Foco na barra de ferramentas contextual", +"Insert link (if link plugin activated)": "Inserir link (se o plugin de link estiver ativado)", +"Save (if save plugin activated)": "Salvar (se o plugin de salvar estiver ativado)", +"Find (if searchreplace plugin activated)": "Procurar (se o plugin de procurar e substituir estiver ativado)", +"Plugins installed ({0}):": "Plugins instalados ({0}):", +"Premium plugins:": "Plugins premium:", +"Learn more...": "Saiba mais...", +"You are using {0}": "Voc\u00ea est\u00e1 usando {0}", +"Plugins": "Plugins", +"Handy Shortcuts": "Atalhos \u00fateis", +"Horizontal line": "Linha horizontal", +"Insert\/edit image": "Inserir\/editar imagem", +"Image description": "Inserir descri\u00e7\u00e3o", +"Source": "Endere\u00e7o da imagem", +"Dimensions": "Dimens\u00f5es", +"Constrain proportions": "Manter propor\u00e7\u00f5es", +"General": "Geral", +"Advanced": "Avan\u00e7ado", +"Style": "Estilo", +"Vertical space": "Espa\u00e7amento vertical", +"Horizontal space": "Espa\u00e7amento horizontal", +"Border": "Borda", +"Insert image": "Inserir imagem", +"Image": "Imagem", +"Image list": "Lista de Imagens", +"Rotate counterclockwise": "Girar em sentido hor\u00e1rio", +"Rotate clockwise": "Girar em sentido anti-hor\u00e1rio", +"Flip vertically": "Virar verticalmente", +"Flip horizontally": "Virar horizontalmente", +"Edit image": "Editar imagem", +"Image options": "Op\u00e7\u00f5es de Imagem", +"Zoom in": "Aumentar zoom", +"Zoom out": "Diminuir zoom", +"Crop": "Cortar", +"Resize": "Redimensionar", +"Orientation": "Orienta\u00e7\u00e3o", +"Brightness": "Brilho", +"Sharpen": "Aumentar nitidez", +"Contrast": "Contraste", +"Color levels": "N\u00edveis de cor", +"Gamma": "Gama", +"Invert": "Inverter", +"Apply": "Aplicar", +"Back": "Voltar", +"Insert date\/time": "Inserir data\/hora", +"Date\/time": "data\/hora", +"Insert link": "Inserir link", +"Insert\/edit link": "Inserir\/editar link", +"Text to display": "Texto para mostrar", +"Url": "Url", +"Target": "Alvo", +"None": "Nenhum", +"New window": "Nova janela", +"Remove link": "Remover link", +"Anchors": "\u00c2ncoras", +"Link": "Link", +"Paste or type a link": "Cole ou digite um Link", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "A URL que voc\u00ea informou parece ser um link externo. Deseja incluir o prefixo http:\/\/?", +"Link list": "Lista de Links", +"Insert video": "Inserir v\u00eddeo", +"Insert\/edit video": "Inserir\/editar v\u00eddeo", +"Insert\/edit media": "Inserir\/editar imagem", +"Alternative source": "Fonte alternativa", +"Poster": "Autor", +"Paste your embed code below:": "Insira o c\u00f3digo de incorpora\u00e7\u00e3o abaixo:", +"Embed": "Incorporar", +"Media": "imagem", +"Nonbreaking space": "Espa\u00e7o n\u00e3o separ\u00e1vel", +"Page break": "Quebra de p\u00e1gina", +"Paste as text": "Colar como texto", +"Preview": "Pr\u00e9-visualizar", +"Print": "Imprimir", +"Save": "Salvar", +"Find": "Localizar", +"Replace with": "Substituir por", +"Replace": "Substituir", +"Replace all": "Substituir tudo", +"Prev": "Anterior", +"Next": "Pr\u00f3ximo", +"Find and replace": "Localizar e substituir", +"Could not find the specified string.": "N\u00e3o foi poss\u00edvel encontrar o termo especificado", +"Match case": "Diferenciar mai\u00fasculas e min\u00fasculas", +"Whole words": "Palavras inteiras", +"Spellcheck": "Corretor ortogr\u00e1fico", +"Ignore": "Ignorar", +"Ignore all": "Ignorar tudo", +"Finish": "Finalizar", +"Add to Dictionary": "Adicionar ao Dicion\u00e1rio", +"Insert table": "Inserir tabela", +"Table properties": "Propriedades da tabela", +"Delete table": "Excluir tabela", +"Cell": "C\u00e9lula", +"Row": "Linha", +"Column": "Coluna", +"Cell properties": "Propriedades da c\u00e9lula", +"Merge cells": "Agrupar c\u00e9lulas", +"Split cell": "Dividir c\u00e9lula", +"Insert row before": "Inserir linha antes", +"Insert row after": "Inserir linha depois", +"Delete row": "Excluir linha", +"Row properties": "Propriedades da linha", +"Cut row": "Recortar linha", +"Copy row": "Copiar linha", +"Paste row before": "Colar linha antes", +"Paste row after": "Colar linha depois", +"Insert column before": "Inserir coluna antes", +"Insert column after": "Inserir coluna depois", +"Delete column": "Excluir coluna", +"Cols": "Colunas", +"Rows": "Linhas", +"Width": "Largura", +"Height": "Altura", +"Cell spacing": "Espa\u00e7amento da c\u00e9lula", +"Cell padding": "Espa\u00e7amento interno da c\u00e9lula", +"Caption": "Legenda", +"Left": "Esquerdo", +"Center": "Centro", +"Right": "Direita", +"Cell type": "Tipo de c\u00e9lula", +"Scope": "Escopo", +"Alignment": "Alinhamento", +"H Align": "Alinhamento H", +"V Align": "Alinhamento V", +"Top": "Superior", +"Middle": "Meio", +"Bottom": "Inferior", +"Header cell": "C\u00e9lula cabe\u00e7alho", +"Row group": "Agrupar linha", +"Column group": "Agrupar coluna", +"Row type": "Tipo de linha", +"Header": "Cabe\u00e7alho", +"Body": "Corpo", +"Footer": "Rodap\u00e9", +"Border color": "Cor da borda", +"Insert template": "Inserir modelo", +"Templates": "Modelos", +"Template": "Modelo", +"Text color": "Cor do texto", +"Background color": "Cor do fundo", +"Custom...": "Personalizado...", +"Custom color": "Cor personalizada", +"No color": "Nenhuma cor", +"Table of Contents": "\u00edndice de Conte\u00fado", +"Show blocks": "Mostrar blocos", +"Show invisible characters": "Exibir caracteres invis\u00edveis", +"Words: {0}": "Palavras: {0}", +"{0} words": "{0} palavras", +"File": "Arquivo", +"Edit": "Editar", +"Insert": "Inserir", +"View": "Visualizar", +"Format": "Formatar", +"Table": "Tabela", +"Tools": "Ferramentas", +"Powered by {0}": "Distribu\u00eddo por {0}", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "\u00c1rea de texto formatado. Pressione ALT-F9 para exibir o menu, ALT-F10 para exibir a barra de ferramentas ou ALT-0 para exibir a ajuda" +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/pt_PT.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/pt_PT.js new file mode 100644 index 0000000000..0376e82429 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/pt_PT.js @@ -0,0 +1,261 @@ +tinymce.addI18n('pt_PT',{ +"Redo": "Refazer", +"Undo": "Desfazer", +"Cut": "Cortar", +"Copy": "Copiar", +"Paste": "Colar", +"Select all": "Selecionar tudo", +"New document": "Novo documento", +"Ok": "Ok", +"Cancel": "Cancelar", +"Visual aids": "Ajuda visual", +"Bold": "Negrito", +"Italic": "It\u00e1lico", +"Underline": "Sublinhado", +"Strikethrough": "Rasurado", +"Superscript": "Superior \u00e0 linha", +"Subscript": "Inferior \u00e0 linha", +"Clear formatting": "Limpar formata\u00e7\u00e3o", +"Align left": "Alinhar \u00e0 esquerda", +"Align center": "Alinhar ao centro", +"Align right": "Alinhar \u00e0 direita", +"Justify": "Justificado", +"Bullet list": "Lista com marcadores", +"Numbered list": "Lista numerada", +"Decrease indent": "Diminuir avan\u00e7o", +"Increase indent": "Aumentar avan\u00e7o", +"Close": "Fechar", +"Formats": "Formatos", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "O seu navegador n\u00e3o suporta acesso direto \u00e0 \u00e1rea de transfer\u00eancia. Por favor use os atalhos Ctrl+X\/C\/V do seu teclado.", +"Headers": "Cabe\u00e7alhos", +"Header 1": "Cabe\u00e7alho 1", +"Header 2": "Cabe\u00e7alho 2", +"Header 3": "Cabe\u00e7alho 3", +"Header 4": "Cabe\u00e7alho 4", +"Header 5": "Cabe\u00e7alho 5", +"Header 6": "Cabe\u00e7alho 6", +"Headings": "T\u00edtulos", +"Heading 1": "T\u00edtulo 1", +"Heading 2": "T\u00edtulo 2", +"Heading 3": "T\u00edtulo 3", +"Heading 4": "T\u00edtulo 4", +"Heading 5": "T\u00edtulo 5", +"Heading 6": "T\u00edtulo 6", +"Preformatted": "Pr\u00e9-formatado", +"Div": "Div", +"Pre": "Pre", +"Code": "C\u00f3digo", +"Paragraph": "Par\u00e1grafo", +"Blockquote": "Cita\u00e7\u00e3o em bloco", +"Inline": "Na linha", +"Blocks": "Blocos", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "O comando colar est\u00e1 em modo de texto simples. O conte\u00fado ser\u00e1 colado como texto simples at\u00e9 desativar esta op\u00e7\u00e3o.", +"Font Family": "Fonte", +"Font Sizes": "Tamanhos", +"Class": "Classe", +"Browse for an image": "Procurar por uma imagem", +"OR": "Ou", +"Drop an image here": "Solte uma imagem aqui", +"Upload": "Carregar", +"Block": "Bloco", +"Align": "Alinhar", +"Default": "Padr\u00e3o", +"Circle": "C\u00edrculo", +"Disc": "Disco", +"Square": "Quadrado", +"Lower Alpha": "a. b. c. ...", +"Lower Greek": "\\u03b1. \\u03b2. \\u03b3. ...", +"Lower Roman": "i. ii. iii. ...", +"Upper Alpha": "A. B. C. ...", +"Upper Roman": "I. II. III. ...", +"Anchor": "\u00c2ncora", +"Name": "Nome", +"Id": "ID", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "O ID deve come\u00e7ar com uma letra, seguido apenas por letras, n\u00fameros, pontos, dois pontos, tra\u00e7os ou sobtra\u00e7os.", +"You have unsaved changes are you sure you want to navigate away?": "Existem altera\u00e7\u00f5es que ainda n\u00e3o foram guardadas. Tem a certeza que pretende sair?", +"Restore last draft": "Restaurar o \u00faltimo rascunho", +"Special character": "Car\u00e1cter especial", +"Source code": "C\u00f3digo fonte", +"Insert\/Edit code sample": "Inserir\/editar amostra de c\u00f3digo", +"Language": "Idioma", +"Code sample": "Amostra de c\u00f3digo", +"Color": "Cor", +"R": "R", +"G": "G", +"B": "B", +"Left to right": "Da esquerda para a direita", +"Right to left": "Da direita para a esquerda", +"Emoticons": "Emo\u00e7\u00f5es", +"Document properties": "Propriedades do documento", +"Title": "T\u00edtulo", +"Keywords": "Palavras-chave", +"Description": "Descri\u00e7\u00e3o", +"Robots": "Rob\u00f4s", +"Author": "Autor", +"Encoding": "Codifica\u00e7\u00e3o", +"Fullscreen": "Ecr\u00e3 completo", +"Action": "A\u00e7\u00e3o", +"Shortcut": "Atalho", +"Help": "Ajuda", +"Address": "Endere\u00e7o", +"Focus to menubar": "Foco na barra de menu", +"Focus to toolbar": "Foco na barra de ferramentas", +"Focus to element path": "Foco no caminho do elemento", +"Focus to contextual toolbar": "Foco na barra de contexto", +"Insert link (if link plugin activated)": "Inserir hiperliga\u00e7\u00e3o (se o plugin de liga\u00e7\u00f5es estiver ativado)", +"Save (if save plugin activated)": "Guardar (se o plugin de guardar estiver ativado)", +"Find (if searchreplace plugin activated)": "Pesquisar (se o plugin pesquisar e substituir estiver ativado)", +"Plugins installed ({0}):": "Plugins instalados ({0}):", +"Premium plugins:": "Plugins comerciais:", +"Learn more...": "Saiba mais...", +"You are using {0}": "Est\u00e1 a usar {0}", +"Plugins": "Plugins", +"Handy Shortcuts": "Atalhos \u00fateis", +"Horizontal line": "Linha horizontal", +"Insert\/edit image": "Inserir\/editar imagem", +"Image description": "Descri\u00e7\u00e3o da imagem", +"Source": "Localiza\u00e7\u00e3o", +"Dimensions": "Dimens\u00f5es", +"Constrain proportions": "Manter propor\u00e7\u00f5es", +"General": "Geral", +"Advanced": "Avan\u00e7ado", +"Style": "Estilo", +"Vertical space": "Espa\u00e7amento vertical", +"Horizontal space": "Espa\u00e7amento horizontal", +"Border": "Contorno", +"Insert image": "Inserir imagem", +"Image": "Imagem", +"Image list": "Lista de imagens", +"Rotate counterclockwise": "Rota\u00e7\u00e3o anti-hor\u00e1ria", +"Rotate clockwise": "Rota\u00e7\u00e3o hor\u00e1ria", +"Flip vertically": "Inverter verticalmente", +"Flip horizontally": "Inverter horizontalmente", +"Edit image": "Editar imagem", +"Image options": "Op\u00e7\u00f5es de imagem", +"Zoom in": "Mais zoom", +"Zoom out": "Menos zoom", +"Crop": "Recortar", +"Resize": "Redimensionar", +"Orientation": "Orienta\u00e7\u00e3o", +"Brightness": "Brilho", +"Sharpen": "Mais nitidez", +"Contrast": "Contraste", +"Color levels": "N\u00edveis de cor", +"Gamma": "Gama", +"Invert": "Inverter", +"Apply": "Aplicar", +"Back": "Voltar", +"Insert date\/time": "Inserir data\/hora", +"Date\/time": "Data\/hora", +"Insert link": "Inserir liga\u00e7\u00e3o", +"Insert\/edit link": "Inserir\/editar liga\u00e7\u00e3o", +"Text to display": "Texto a exibir", +"Url": "URL", +"Target": "Alvo", +"None": "Nenhum", +"New window": "Nova janela", +"Remove link": "Remover liga\u00e7\u00e3o", +"Anchors": "\u00c2ncora", +"Link": "Liga\u00e7\u00e3o", +"Paste or type a link": "Copiar ou escrever uma hiperliga\u00e7\u00e3o", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "O URL que indicou parece ser um endere\u00e7o de email. Quer adicionar o prefixo mailto: tal como necess\u00e1rio?", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "O URL que indicou parece ser um endere\u00e7o web. Quer adicionar o prefixo http:\/\/ tal como necess\u00e1rio?", +"Link list": "Lista de liga\u00e7\u00f5es", +"Insert video": "Inserir v\u00eddeo", +"Insert\/edit video": "Inserir\/editar v\u00eddeo", +"Insert\/edit media": "Inserir\/editar media", +"Alternative source": "Localiza\u00e7\u00e3o alternativa", +"Poster": "Autor", +"Paste your embed code below:": "Colar c\u00f3digo para embeber:", +"Embed": "Embeber", +"Media": "Media", +"Nonbreaking space": "Espa\u00e7o n\u00e3o quebr\u00e1vel", +"Page break": "Quebra de p\u00e1gina", +"Paste as text": "Colar como texto", +"Preview": "Pr\u00e9-visualizar", +"Print": "Imprimir", +"Save": "Guardar", +"Find": "Pesquisar", +"Replace with": "Substituir por", +"Replace": "Substituir", +"Replace all": "Substituir tudo", +"Prev": "Anterior", +"Next": "Pr\u00f3ximo", +"Find and replace": "Pesquisar e substituir", +"Could not find the specified string.": "N\u00e3o foi poss\u00edvel localizar o termo especificado.", +"Match case": "Diferenciar mai\u00fasculas e min\u00fasculas", +"Whole words": "Palavras completas", +"Spellcheck": "Corretor ortogr\u00e1fico", +"Ignore": "Ignorar", +"Ignore all": "Ignorar tudo", +"Finish": "Concluir", +"Add to Dictionary": "Adicionar ao dicion\u00e1rio", +"Insert table": "Inserir tabela", +"Table properties": "Propriedades da tabela", +"Delete table": "Eliminar tabela", +"Cell": "C\u00e9lula", +"Row": "Linha", +"Column": "Coluna", +"Cell properties": "Propriedades da c\u00e9lula", +"Merge cells": "Unir c\u00e9lulas", +"Split cell": "Dividir c\u00e9lula", +"Insert row before": "Inserir linha antes", +"Insert row after": "Inserir linha depois", +"Delete row": "Eliminar linha", +"Row properties": "Propriedades da linha", +"Cut row": "Cortar linha", +"Copy row": "Copiar linha", +"Paste row before": "Colar linha antes", +"Paste row after": "Colar linha depois", +"Insert column before": "Inserir coluna antes", +"Insert column after": "Inserir coluna depois", +"Delete column": "Eliminar coluna", +"Cols": "Colunas", +"Rows": "Linhas", +"Width": "Largura", +"Height": "Altura", +"Cell spacing": "Espa\u00e7amento entre c\u00e9lulas", +"Cell padding": "Espa\u00e7amento interno da c\u00e9lula", +"Caption": "Legenda", +"Left": "Esquerda", +"Center": "Centro", +"Right": "Direita", +"Cell type": "Tipo de c\u00e9lula", +"Scope": "Escopo", +"Alignment": "Alinhamento", +"H Align": "Alinhamento H", +"V Align": "Alinhamento V", +"Top": "Superior", +"Middle": "Meio", +"Bottom": "Inferior", +"Header cell": "C\u00e9lula de cabe\u00e7alho", +"Row group": "Agrupar linha", +"Column group": "Agrupar coluna", +"Row type": "Tipo de linha", +"Header": "Cabe\u00e7alho", +"Body": "Corpo", +"Footer": "Rodap\u00e9", +"Border color": "Cor de contorno", +"Insert template": "Inserir modelo", +"Templates": "Modelos", +"Template": "Tema", +"Text color": "Cor do texto", +"Background color": "Cor de fundo", +"Custom...": "Personalizada...", +"Custom color": "Cor personalizada", +"No color": "Sem cor", +"Table of Contents": "\u00cdndice", +"Show blocks": "Mostrar blocos", +"Show invisible characters": "Mostrar caracteres invis\u00edveis", +"Words: {0}": "Palavras: {0}", +"{0} words": "{0} palavras", +"File": "Ficheiro", +"Edit": "Editar", +"Insert": "Inserir", +"View": "Ver", +"Format": "Formatar", +"Table": "Tabela", +"Tools": "Ferramentas", +"Powered by {0}": "Criado em {0}", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Caixa de texto formatado. Pressione ALT-F9 para exibir o menu. Pressione ALT-F10 para exibir a barra de ferramentas. Pressione ALT-0 para exibir a ajuda" +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/ro.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/ro.js new file mode 100644 index 0000000000..0a6a2eadf0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/ro.js @@ -0,0 +1,230 @@ +tinymce.addI18n('ro',{ +"Cut": "Taie", +"Heading 5": "Titlu 5", +"Header 2": "Antet 2", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "Browser-ul dumneavoastr\u0103 nu support\u0103 acces direct la clipboard. Folosi\u0163i combina\u0163ile de tastatur\u0103 Ctrl+X\/C\/V.", +"Heading 4": "Titlu 4", +"Div": "Div", +"Heading 2": "Titlu 2", +"Paste": "Insereaz\u0103", +"Close": "\u00cenchide", +"Font Family": "Familie font", +"Pre": "Pre", +"Align right": "Aliniere la dreapta", +"New document": "Document nou", +"Blockquote": "Citat", +"Numbered list": "List\u0103 ordonat\u0103", +"Heading 1": "Titlu 1", +"Headings": "Titluri", +"Increase indent": "Indenteaz\u0103", +"Formats": "Formate", +"Headers": "Antete", +"Select all": "Selecteaz\u0103 tot", +"Header 3": "Antet 3", +"Blocks": "Blocuri", +"Undo": "Ref\u0103 la loc", +"Strikethrough": "T\u0103iat", +"Bullet list": "List\u0103 neordonat\u0103", +"Header 1": "Antet 1", +"Superscript": "Superscript", +"Clear formatting": "\u015eterge format\u0103rile", +"Font Sizes": "Dimensiuni font", +"Subscript": "Subscript", +"Header 6": "Antet 6", +"Redo": "Execut\u0103 din nou", +"Paragraph": "Paragraf", +"Ok": "Ok", +"Bold": "\u00cengro\u015fat", +"Code": "Cod", +"Italic": "Italic", +"Align center": "Centrare", +"Header 5": "Antet 5", +"Heading 6": "Titlu 6", +"Heading 3": "Titlu 3", +"Decrease indent": "De-indenteaz\u0103", +"Header 4": "Antet 4", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "Functia \"lipe\u015fte\" este acum \u00een modul text simplu. Continutul va fi acum inserat ca text simplu p\u00e2n\u0103 c\u00e2nd aceast\u0103 op\u021biune va fi dezactivat.", +"Underline": "Subliniat", +"Cancel": "Anuleaz\u0103", +"Justify": "Aliniere pe toat\u0103 l\u0103\u021bimea", +"Inline": "Inline", +"Copy": "Copiaz\u0103", +"Align left": "Aliniere la st\u00e2nga", +"Visual aids": "Ajutor vizual", +"Lower Greek": "Minuscule Grecesti", +"Square": "P\u0103trat", +"Default": "Implicit", +"Lower Alpha": "Minuscule Alfanumerice", +"Circle": "Cerc", +"Disc": "Disc", +"Upper Alpha": "Majuscule Alfanumerice", +"Upper Roman": "Majuscule Romane", +"Lower Roman": "Minuscule Romane", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "Id-ul trebuie s\u0103 inceap\u0103 cu o liter\u0103, urmat\u0103 exclusiv de litere, numere, cratime, puncte, punct \u0219i virgul\u0103 sau underscore-uri.", +"Name": "Nume", +"Anchor": "Ancor\u0103", +"Id": "Id", +"You have unsaved changes are you sure you want to navigate away?": "Ave\u021bi modific\u0103ri nesalvate! Sunte\u0163i sigur c\u0103 dori\u0163i s\u0103 ie\u015fiti?", +"Restore last draft": "Restaurare la ultima salvare", +"Special character": "Caractere speciale", +"Source code": "Codul surs\u0103", +"Language": "Limba", +"Insert\/Edit code sample": "Inserare\/Editare mostr\u0103 cod", +"B": "B", +"R": "R", +"G": "G", +"Color": "Culoare", +"Right to left": "Dreapta la st\u00e2nga", +"Left to right": "St\u00e2nga la dreapta", +"Emoticons": "Emoticoane", +"Robots": "Robo\u021bi", +"Document properties": "Propriet\u0103\u021bi document", +"Title": "Titlu", +"Keywords": "Cuvinte cheie", +"Encoding": "Codare", +"Description": "Descriere", +"Author": "Autor", +"Fullscreen": "Pe tot ecranul", +"Horizontal line": "Linie orizontal\u0103", +"Horizontal space": "Spa\u021biul orizontal", +"Insert\/edit image": "Inserare\/editarea imaginilor", +"General": "General", +"Advanced": "Avansat", +"Source": "Surs\u0103", +"Border": "Bordur\u0103", +"Constrain proportions": "Constr\u00e2nge propor\u021biile", +"Vertical space": "Spa\u021biul vertical", +"Image description": "Descrierea imaginii", +"Style": "Stil", +"Dimensions": "Dimensiuni", +"Insert image": "Inserare imagine", +"Image": "Imagine", +"Zoom in": "M\u0103rire", +"Contrast": "Contrast", +"Back": "\u00cenapoi", +"Gamma": "Gamma", +"Flip horizontally": "R\u0103sturn\u0103 orizontal", +"Resize": "Redimensionare", +"Sharpen": "Accentuare", +"Zoom out": "Mic\u015forare", +"Image options": "Op\u021biuni imagine", +"Apply": "Salveaz\u0103", +"Brightness": "Str\u0103lucire", +"Rotate clockwise": "Rotire \u00een sensul orar", +"Rotate counterclockwise": "Rotire \u00een sensul antiorar", +"Edit image": "Editare imagine", +"Color levels": "Niveluri de culoare", +"Crop": "Decupare", +"Orientation": "Orientare", +"Flip vertically": "R\u0103sturn\u0103 vertical", +"Invert": "Invers\u0103", +"Date\/time": "Data\/ora", +"Insert date\/time": "Insereaz\u0103 data\/ora", +"Remove link": "\u0218terge link-ul", +"Url": "Url", +"Text to display": "Text de afi\u0219at", +"Anchors": "Ancor\u0103", +"Insert link": "Inserare link", +"Link": "Link", +"New window": "Fereastr\u0103 nou\u0103", +"None": "Nici unul", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "URL-ul introdus pare s\u0103 fie o adres\u0103 web. Dori\u021bi s\u0103 ad\u0103uga\u021bi prefixul http:\/\/ ?", +"Paste or type a link": "Introduce\u021bi un link", +"Target": "\u021aint\u0103", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "URL-ul introdus pare s\u0103 fie o adres\u0103 de e-mail. Dori\u021bi s\u0103 ad\u0103uga\u021bi prefixul mailto: ?", +"Insert\/edit link": "Inserare\/editare link", +"Insert\/edit video": "Inserare\/editare video", +"Media": "Media", +"Alternative source": "Surs\u0103 alternativ\u0103", +"Paste your embed code below:": "Insera\u021bi codul:", +"Insert video": "Inserare video", +"Poster": "Poster", +"Insert\/edit media": "Inserare\/editare media", +"Embed": "Embed", +"Nonbreaking space": "Spa\u021biu neseparator", +"Page break": "\u00centrerupere de pagin\u0103", +"Paste as text": "Lipe\u015fte ca text", +"Preview": "Previzualizare", +"Print": "Tip\u0103re\u0219te", +"Save": "Salveaz\u0103", +"Could not find the specified string.": "Nu am putut g\u0103si \u0219irul specificat.", +"Replace": "\u00cenlocuie\u015fte", +"Next": "Precedent", +"Whole words": "Doar cuv\u00eentul \u00eentreg", +"Find and replace": "Caut\u0103 \u015fi \u00eenlocuie\u015fte", +"Replace with": "\u00cenlocuie\u015fte cu", +"Find": "Caut\u0103", +"Replace all": "\u00cenlocuie\u015fte toate", +"Match case": "Distinge majuscule\/minuscule", +"Prev": "Anterior", +"Spellcheck": "Verificarea ortografic\u0103", +"Finish": "Finalizeaz\u0103", +"Ignore all": "Ignor\u0103 toate", +"Ignore": "Ignor\u0103", +"Add to Dictionary": "Adaug\u0103 \u00een Dic\u021bionar", +"Insert row before": "Insereaz\u0103 \u00eenainte de linie", +"Rows": "Linii", +"Height": "\u00cen\u0103l\u0163ime", +"Paste row after": "Lipe\u015fte linie dup\u0103", +"Alignment": "Aliniament", +"Border color": "Culoare bordur\u0103", +"Column group": "Grup de coloane", +"Row": "Linie", +"Insert column before": "Insereaza \u00eenainte de coloan\u0103", +"Split cell": "\u00cemp\u0103r\u021birea celulelor", +"Cell padding": "Spa\u021biere", +"Cell spacing": "Spa\u021biere celule", +"Row type": "Tip de linie", +"Insert table": "Insereaz\u0103 tabel\u0103", +"Body": "Corp", +"Caption": "Titlu", +"Footer": "Subsol", +"Delete row": "\u0218terge linia", +"Paste row before": "Lipe\u015fte \u00eenainte de linie", +"Scope": "Domeniu", +"Delete table": "\u0218terge tabel\u0103", +"H Align": "Aliniere H", +"Top": "Sus", +"Header cell": "Antet celul\u0103", +"Column": "Coloan\u0103", +"Row group": "Grup de linii", +"Cell": "Celul\u0103", +"Middle": "Mijloc", +"Cell type": "Tip celul\u0103", +"Copy row": "Copiaz\u0103 linie", +"Row properties": "Propriet\u0103\u021bi linie", +"Table properties": "Propriet\u0103\u021bi tabel\u0103", +"Bottom": "Jos", +"V Align": "Aliniere V", +"Header": "Antet", +"Right": "Dreapta", +"Insert column after": "Insereaza dup\u0103 coloan\u0103", +"Cols": "Coloane", +"Insert row after": "Insereaz\u0103 dup\u0103 linie", +"Width": "L\u0103\u0163ime", +"Cell properties": "Propriet\u0103\u021bi celul\u0103", +"Left": "St\u00e2nga", +"Cut row": "Taie linie", +"Delete column": "\u0218terge coloana", +"Center": "Centru", +"Merge cells": "\u00cembinarea celulelor", +"Insert template": "Insereaz\u0103 \u0219ablon", +"Templates": "\u015eabloane", +"Background color": "Culoare fundal", +"Custom...": "Personalizat...", +"Custom color": "Culoare personalizat\u0103", +"No color": "F\u0103r\u0103 culoare", +"Text color": "Culoare text", +"Table of Contents": "Cuprins", +"Show blocks": "Afi\u0219are blocuri", +"Show invisible characters": "Afi\u0219are caractere invizibile", +"Words: {0}": "Cuvinte: {0}", +"Insert": "Insereaz\u0103", +"File": "Fil\u0103", +"Edit": "Editeaz\u0103", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Zon\u0103 cu Rich Text. Apas\u0103 ALT-F9 pentru meniu. Apas\u0103 ALT-F10 pentru bara de unelte. Apas\u0103 ALT-0 pentru ajutor", +"Tools": "Unelte", +"View": "Vezi", +"Table": "Tabel\u0103", +"Format": "Formateaz\u0103" +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/ru.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/ru.js index 110a978002..dfae77d955 100644 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/ru.js +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/ru.js @@ -1,219 +1,261 @@ tinymce.addI18n('ru',{ -"Cut": "\u0412\u044b\u0440\u0435\u0437\u0430\u0442\u044c", -"Heading 5": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 5", -"Header 2": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 2", -"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "\u0412\u0430\u0448 \u0431\u0440\u0430\u0443\u0437\u0435\u0440 \u043d\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u043f\u0440\u044f\u043c\u043e\u0439 \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u0431\u0443\u0444\u0435\u0440\u0443 \u043e\u0431\u043c\u0435\u043d\u0430. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u0441\u043e\u0447\u0435\u0442\u0430\u043d\u0438\u044f \u043a\u043b\u0430\u0432\u0438\u0448: Ctrl+X\/C\/V.", -"Heading 4": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 4", -"Div": "\u0411\u043b\u043e\u043a", -"Heading 2": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 2", -"Paste": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u044c", -"Close": "\u0417\u0430\u043a\u0440\u044b\u0442\u044c", -"Font Family": "\u0428\u0440\u0438\u0444\u0442", -"Pre": "\u041f\u0440\u0435\u0434\u0432\u0430\u0440\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u0444\u043e\u0440\u043c\u0430\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435", -"Align right": "\u041f\u043e \u043f\u0440\u0430\u0432\u043e\u043c\u0443 \u043a\u0440\u0430\u044e", -"New document": "\u041d\u043e\u0432\u044b\u0439 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442", -"Blockquote": "\u0426\u0438\u0442\u0430\u0442\u0430", -"Numbered list": "\u041d\u0443\u043c\u0435\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u0441\u043f\u0438\u0441\u043e\u043a", -"Heading 1": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 1", -"Headings": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0438", -"Increase indent": "\u0423\u0432\u0435\u043b\u0438\u0447\u0438\u0442\u044c \u043e\u0442\u0441\u0442\u0443\u043f", -"Formats": "\u0424\u043e\u0440\u043c\u0430\u0442", -"Headers": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0438", -"Select all": "\u0412\u044b\u0434\u0435\u043b\u0438\u0442\u044c \u0432\u0441\u0435", -"Header 3": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 3", -"Blocks": "\u0411\u043b\u043e\u043a\u0438", -"Undo": "\u0412\u0435\u0440\u043d\u0443\u0442\u044c", -"Strikethrough": "\u0417\u0430\u0447\u0435\u0440\u043a\u043d\u0443\u0442\u044b\u0439", -"Bullet list": "\u041c\u0430\u0440\u043a\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u0441\u043f\u0438\u0441\u043e\u043a", -"Header 1": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 1", -"Superscript": "\u0412\u0435\u0440\u0445\u043d\u0438\u0439 \u0438\u043d\u0434\u0435\u043a\u0441", -"Clear formatting": "\u041e\u0447\u0438\u0441\u0442\u0438\u0442\u044c \u0444\u043e\u0440\u043c\u0430\u0442", -"Font Sizes": "\u0420\u0430\u0437\u043c\u0435\u0440 \u0448\u0440\u0438\u0444\u0442\u0430", -"Subscript": "\u041d\u0438\u0436\u043d\u0438\u0439 \u0438\u043d\u0434\u0435\u043a\u0441", -"Header 6": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 6", "Redo": "\u041e\u0442\u043c\u0435\u043d\u0438\u0442\u044c", -"Paragraph": "\u041f\u0430\u0440\u0430\u0433\u0440\u0430\u0444", -"Ok": "\u041e\u043a", -"Bold": "\u041f\u043e\u043b\u0443\u0436\u0438\u0440\u043d\u044b\u0439", -"Code": "\u041a\u043e\u0434", -"Italic": "\u041a\u0443\u0440\u0441\u0438\u0432", -"Align center": "\u041f\u043e \u0446\u0435\u043d\u0442\u0440\u0443", -"Header 5": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 5", -"Heading 6": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 6", -"Heading 3": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 3", -"Decrease indent": "\u0423\u043c\u0435\u043d\u044c\u0448\u0438\u0442\u044c \u043e\u0442\u0441\u0442\u0443\u043f", -"Header 4": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 4", -"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "\u0412\u0441\u0442\u0430\u0432\u043a\u0430 \u043e\u0441\u0443\u0449\u0435\u0441\u0442\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0432 \u0432\u0438\u0434\u0435 \u043f\u0440\u043e\u0441\u0442\u043e\u0433\u043e \u0442\u0435\u043a\u0441\u0442\u0430, \u043f\u043e\u043a\u0430 \u043d\u0435 \u043e\u0442\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0434\u0430\u043d\u043d\u0443\u044e \u043e\u043f\u0446\u0438\u044e.", -"Underline": "\u041f\u043e\u0434\u0447\u0435\u0440\u043a\u043d\u0443\u0442\u044b\u0439", -"Cancel": "\u041e\u0442\u043c\u0435\u043d\u0438\u0442\u044c", -"Justify": "\u041f\u043e \u0448\u0438\u0440\u0438\u043d\u0435", -"Inline": "\u0421\u0442\u0440\u043e\u0447\u043d\u044b\u0435", +"Undo": "\u0412\u0435\u0440\u043d\u0443\u0442\u044c", +"Cut": "\u0412\u044b\u0440\u0435\u0437\u0430\u0442\u044c", "Copy": "\u041a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u0442\u044c", -"Align left": "\u041f\u043e \u043b\u0435\u0432\u043e\u043c\u0443 \u043a\u0440\u0430\u044e", +"Paste": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u044c", +"Select all": "\u0412\u044b\u0434\u0435\u043b\u0438\u0442\u044c \u0432\u0441\u0435", +"New document": "\u041d\u043e\u0432\u044b\u0439 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442", +"Ok": "\u041e\u043a", +"Cancel": "\u041e\u0442\u043c\u0435\u043d\u0438\u0442\u044c", "Visual aids": "\u041f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u043a\u043e\u043d\u0442\u0443\u0440\u044b", -"Lower Greek": "\u0421\u0442\u0440\u043e\u0447\u043d\u044b\u0435 \u0433\u0440\u0435\u0447\u0435\u0441\u043a\u0438\u0435 \u0431\u0443\u043a\u0432\u044b", -"Square": "\u041a\u0432\u0430\u0434\u0440\u0430\u0442\u044b", +"Bold": "\u041f\u043e\u043b\u0443\u0436\u0438\u0440\u043d\u044b\u0439", +"Italic": "\u041a\u0443\u0440\u0441\u0438\u0432", +"Underline": "\u041f\u043e\u0434\u0447\u0435\u0440\u043a\u043d\u0443\u0442\u044b\u0439", +"Strikethrough": "\u0417\u0430\u0447\u0435\u0440\u043a\u043d\u0443\u0442\u044b\u0439", +"Superscript": "\u0412\u0435\u0440\u0445\u043d\u0438\u0439 \u0438\u043d\u0434\u0435\u043a\u0441", +"Subscript": "\u041d\u0438\u0436\u043d\u0438\u0439 \u0438\u043d\u0434\u0435\u043a\u0441", +"Clear formatting": "\u041e\u0447\u0438\u0441\u0442\u0438\u0442\u044c \u0444\u043e\u0440\u043c\u0430\u0442", +"Align left": "\u041f\u043e \u043b\u0435\u0432\u043e\u043c\u0443 \u043a\u0440\u0430\u044e", +"Align center": "\u041f\u043e \u0446\u0435\u043d\u0442\u0440\u0443", +"Align right": "\u041f\u043e \u043f\u0440\u0430\u0432\u043e\u043c\u0443 \u043a\u0440\u0430\u044e", +"Justify": "\u041f\u043e \u0448\u0438\u0440\u0438\u043d\u0435", +"Bullet list": "\u041c\u0430\u0440\u043a\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u0441\u043f\u0438\u0441\u043e\u043a", +"Numbered list": "\u041d\u0443\u043c\u0435\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u0441\u043f\u0438\u0441\u043e\u043a", +"Decrease indent": "\u0423\u043c\u0435\u043d\u044c\u0448\u0438\u0442\u044c \u043e\u0442\u0441\u0442\u0443\u043f", +"Increase indent": "\u0423\u0432\u0435\u043b\u0438\u0447\u0438\u0442\u044c \u043e\u0442\u0441\u0442\u0443\u043f", +"Close": "\u0417\u0430\u043a\u0440\u044b\u0442\u044c", +"Formats": "\u0424\u043e\u0440\u043c\u0430\u0442", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "\u0412\u0430\u0448 \u0431\u0440\u0430\u0443\u0437\u0435\u0440 \u043d\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u043f\u0440\u044f\u043c\u043e\u0439 \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u0431\u0443\u0444\u0435\u0440\u0443 \u043e\u0431\u043c\u0435\u043d\u0430. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u0441\u043e\u0447\u0435\u0442\u0430\u043d\u0438\u044f \u043a\u043b\u0430\u0432\u0438\u0448: Ctrl+X\/C\/V.", +"Headers": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0438", +"Header 1": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 1", +"Header 2": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 2", +"Header 3": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 3", +"Header 4": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 4", +"Header 5": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 5", +"Header 6": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 6", +"Headings": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0438", +"Heading 1": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 1", +"Heading 2": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 2", +"Heading 3": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 3", +"Heading 4": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 4", +"Heading 5": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 5", +"Heading 6": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 6", +"Preformatted": "\u041f\u0440\u0435\u0434\u0432\u0430\u0440\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u0444\u043e\u0440\u043c\u0430\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435", +"Div": "\u0411\u043b\u043e\u043a", +"Pre": "\u041f\u0440\u0435\u0434\u0432\u0430\u0440\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u0444\u043e\u0440\u043c\u0430\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435", +"Code": "\u041a\u043e\u0434", +"Paragraph": "\u041f\u0430\u0440\u0430\u0433\u0440\u0430\u0444", +"Blockquote": "\u0426\u0438\u0442\u0430\u0442\u0430", +"Inline": "\u0421\u0442\u0440\u043e\u0447\u043d\u044b\u0435", +"Blocks": "\u0411\u043b\u043e\u043a\u0438", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "\u0412\u0441\u0442\u0430\u0432\u043a\u0430 \u043e\u0441\u0443\u0449\u0435\u0441\u0442\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0432 \u0432\u0438\u0434\u0435 \u043f\u0440\u043e\u0441\u0442\u043e\u0433\u043e \u0442\u0435\u043a\u0441\u0442\u0430, \u043f\u043e\u043a\u0430 \u043d\u0435 \u043e\u0442\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0434\u0430\u043d\u043d\u0443\u044e \u043e\u043f\u0446\u0438\u044e.", +"Font Family": "\u0428\u0440\u0438\u0444\u0442", +"Font Sizes": "\u0420\u0430\u0437\u043c\u0435\u0440 \u0448\u0440\u0438\u0444\u0442\u0430", +"Class": "\u041a\u043b\u0430\u0441\u0441", +"Browse for an image": "\u0412\u044b\u0431\u043e\u0440 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f", +"OR": "\u0418\u041b\u0418", +"Drop an image here": "\u041f\u0435\u0440\u0435\u0442\u0430\u0449\u0438\u0442\u0435 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u0441\u044e\u0434\u0430", +"Upload": "\u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c", +"Block": "\u0411\u043b\u043e\u043a", +"Align": "\u0412\u044b\u0440\u0430\u0432\u043d\u0438\u0432\u0430\u043d\u0438\u0435", "Default": "\u0421\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0439", -"Lower Alpha": "\u0421\u0442\u0440\u043e\u0447\u043d\u044b\u0435 \u043b\u0430\u0442\u0438\u043d\u0441\u043a\u0438\u0435 \u0431\u0443\u043a\u0432\u044b", "Circle": "\u041e\u043a\u0440\u0443\u0436\u043d\u043e\u0441\u0442\u0438", "Disc": "\u041a\u0440\u0443\u0433\u0438", +"Square": "\u041a\u0432\u0430\u0434\u0440\u0430\u0442\u044b", +"Lower Alpha": "\u0421\u0442\u0440\u043e\u0447\u043d\u044b\u0435 \u043b\u0430\u0442\u0438\u043d\u0441\u043a\u0438\u0435 \u0431\u0443\u043a\u0432\u044b", +"Lower Greek": "\u0421\u0442\u0440\u043e\u0447\u043d\u044b\u0435 \u0433\u0440\u0435\u0447\u0435\u0441\u043a\u0438\u0435 \u0431\u0443\u043a\u0432\u044b", +"Lower Roman": "\u0421\u0442\u0440\u043e\u0447\u043d\u044b\u0435 \u0440\u0438\u043c\u0441\u043a\u0438\u0435 \u0446\u0438\u0444\u0440\u044b", "Upper Alpha": "\u0417\u0430\u0433\u043b\u0430\u0432\u043d\u044b\u0435 \u043b\u0430\u0442\u0438\u043d\u0441\u043a\u0438\u0435 \u0431\u0443\u043a\u0432\u044b", "Upper Roman": "\u0417\u0430\u0433\u043b\u0430\u0432\u043d\u044b\u0435 \u0440\u0438\u043c\u0441\u043a\u0438\u0435 \u0446\u0438\u0444\u0440\u044b", -"Lower Roman": "\u0421\u0442\u0440\u043e\u0447\u043d\u044b\u0435 \u0440\u0438\u043c\u0441\u043a\u0438\u0435 \u0446\u0438\u0444\u0440\u044b", -"Name": "\u0418\u043c\u044f", "Anchor": "\u042f\u043a\u043e\u0440\u044c", +"Name": "\u0418\u043c\u044f", +"Id": "Id", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "Id \u0434\u043e\u043b\u0436\u0435\u043d \u043d\u0430\u0447\u0438\u043d\u0430\u0442\u044c\u0441\u044f \u0441 \u0431\u0443\u043a\u0432\u044b, \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0430\u0442\u044c\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u0441 \u0431\u0443\u043a\u0432\u044b, \u0446\u0438\u0444\u0440\u044b, \u0442\u0438\u0440\u0435, \u0442\u043e\u0447\u043a\u0438, \u0434\u0432\u043e\u0435\u0442\u043e\u0447\u0438\u044f \u0438\u043b\u0438 \u043f\u043e\u0434\u0447\u0435\u0440\u043a\u0438\u0432\u0430\u043d\u0438\u044f.", "You have unsaved changes are you sure you want to navigate away?": "\u0423 \u0432\u0430\u0441 \u0435\u0441\u0442\u044c \u043d\u0435 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u043d\u044b\u0435 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f. \u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u0443\u0439\u0442\u0438?", "Restore last draft": "\u0412\u043e\u0441\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u0433\u043e \u043f\u0440\u043e\u0435\u043a\u0442\u0430", "Special character": "\u0421\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u044b\u0435 \u0441\u0438\u043c\u0432\u043e\u043b\u044b", "Source code": "\u0418\u0441\u0445\u043e\u0434\u043d\u044b\u0439 \u043a\u043e\u0434", -"B": "B", +"Insert\/Edit code sample": "\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c\/\u0418\u0437\u043c\u0435\u043d\u0438\u0442\u044c \u043f\u0440\u0438\u043c\u0435\u0440 \u043a\u043e\u0434\u0430", +"Language": "\u042f\u0437\u044b\u043a", +"Code sample": "\u041f\u0440\u0438\u043c\u0435\u0440 \u043a\u043e\u0434\u0430", +"Color": "\u0426\u0432\u0435\u0442", "R": "R", "G": "G", -"Color": "\u0426\u0432\u0435\u0442", -"Right to left": "\u041d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0441\u043f\u0440\u0430\u0432\u0430 \u043d\u0430\u043b\u0435\u0432\u043e", +"B": "B", "Left to right": "\u041d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0441\u043b\u0435\u0432\u0430 \u043d\u0430\u043f\u0440\u0430\u0432\u043e", +"Right to left": "\u041d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0441\u043f\u0440\u0430\u0432\u0430 \u043d\u0430\u043b\u0435\u0432\u043e", "Emoticons": "\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0441\u043c\u0430\u0439\u043b", -"Robots": "\u0420\u043e\u0431\u043e\u0442\u044b", "Document properties": "\u0421\u0432\u043e\u0439\u0441\u0442\u0432\u0430 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430", "Title": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a", "Keywords": "\u041a\u043b\u044e\u0447\u0438\u0432\u044b\u0435 \u0441\u043b\u043e\u0432\u0430", -"Encoding": "\u041a\u043e\u0434\u0438\u0440\u043e\u0432\u043a\u0430", "Description": "\u041e\u043f\u0438\u0441\u0430\u043d\u0438\u0435", +"Robots": "\u0420\u043e\u0431\u043e\u0442\u044b", "Author": "\u0410\u0432\u0442\u043e\u0440", +"Encoding": "\u041a\u043e\u0434\u0438\u0440\u043e\u0432\u043a\u0430", "Fullscreen": "\u041f\u043e\u043b\u043d\u043e\u044d\u043a\u0440\u0430\u043d\u043d\u044b\u0439 \u0440\u0435\u0436\u0438\u043c", +"Action": "\u0414\u0435\u0439\u0441\u0442\u0432\u0438\u0435", +"Shortcut": "\u042f\u0440\u043b\u044b\u043a", +"Help": "\u041f\u043e\u043c\u043e\u0449\u044c", +"Address": "\u0410\u0434\u0440\u0435\u0441", +"Focus to menubar": "\u0424\u043e\u043a\u0443\u0441 \u043d\u0430 \u043f\u0430\u043d\u0435\u043b\u0438 \u043c\u0435\u043d\u044e", +"Focus to toolbar": "\u0424\u043e\u043a\u0443\u0441 \u043d\u0430 \u043f\u0430\u043d\u0435\u043b\u0438 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u043e\u0432", +"Focus to element path": "\u0424\u043e\u043a\u0443\u0441 \u043d\u0430 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0435 \u043f\u0443\u0442\u0438", +"Focus to contextual toolbar": "\u0424\u043e\u043a\u0443\u0441 \u043d\u0430 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u043d\u043e\u0439 \u043f\u0430\u043d\u0435\u043b\u0438 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u043e\u0432", +"Insert link (if link plugin activated)": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0441\u0441\u044b\u043b\u043a\u0443 (\u0435\u0441\u043b\u0438 \u043f\u043b\u0430\u0433\u0438\u043d link \u0430\u043a\u0442\u0438\u0432\u0438\u0440\u043e\u0432\u0430\u043d)", +"Save (if save plugin activated)": "\u0421\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c (\u0435\u0441\u043b\u0438 \u043f\u043b\u0430\u0433\u0438\u043d save \u0430\u043a\u0442\u0438\u0432\u0438\u0440\u043e\u0432\u0430\u043d)", +"Find (if searchreplace plugin activated)": "\u041d\u0430\u0439\u0442\u0438 (\u0435\u0441\u043b\u0438 \u043f\u043b\u0430\u0433\u0438\u043d searchreplace \u0430\u043a\u0442\u0438\u0432\u0438\u0440\u043e\u0432\u0430\u043d)", +"Plugins installed ({0}):": "\u0423\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044b\u0435 \u043f\u043b\u0430\u0433\u0438\u043d\u044b ({0}):", +"Premium plugins:": "\u041f\u0440\u0435\u043c\u0438\u0443\u043c \u043f\u043b\u0430\u0433\u0438\u043d\u044b:", +"Learn more...": "\u0423\u0437\u043d\u0430\u0442\u044c \u0431\u043e\u043b\u044c\u0448\u0435...", +"You are using {0}": "\u0412\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0435 {0}", +"Plugins": "\u041f\u043b\u0430\u0433\u0438\u043d\u044b", +"Handy Shortcuts": "\u0413\u043e\u0440\u044f\u0447\u0438\u0435 \u043a\u043b\u0430\u0432\u0438\u0448\u0438", "Horizontal line": "\u0413\u043e\u0440\u0438\u0437\u043e\u043d\u0442\u0430\u043b\u044c\u043d\u0430\u044f \u043b\u0438\u043d\u0438\u044f", -"Horizontal space": "\u0413\u043e\u0440\u0438\u0437\u043e\u043d\u0442\u0430\u043b\u044c\u043d\u044b\u0439 \u0438\u043d\u0442\u0435\u0440\u0432\u0430\u043b", "Insert\/edit image": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u044c\/\u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435", +"Image description": "\u041e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f", +"Source": "\u0418\u0441\u0442\u043e\u0447\u043d\u0438\u043a", +"Dimensions": "\u0420\u0430\u0437\u043c\u0435\u0440", +"Constrain proportions": "\u0421\u043e\u0445\u0440\u0430\u043d\u044f\u0442\u044c \u043f\u0440\u043e\u043f\u043e\u0440\u0446\u0438\u0438", "General": "\u041e\u0431\u0449\u0435\u0435", "Advanced": "\u0420\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u043d\u044b\u0435", -"Source": "\u0418\u0441\u0442\u043e\u0447\u043d\u0438\u043a", -"Border": "\u0420\u0430\u043c\u043a\u0430", -"Constrain proportions": "\u0421\u043e\u0445\u0440\u0430\u043d\u044f\u0442\u044c \u043f\u0440\u043e\u043f\u043e\u0440\u0446\u0438\u0438", -"Vertical space": "\u0412\u0435\u0440\u0442\u0438\u043a\u0430\u043b\u044c\u043d\u044b\u0439 \u0438\u043d\u0442\u0435\u0440\u0432\u0430\u043b", -"Image description": "\u041e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f", "Style": "\u0421\u0442\u0438\u043b\u044c", -"Dimensions": "\u0420\u0430\u0437\u043c\u0435\u0440", +"Vertical space": "\u0412\u0435\u0440\u0442\u0438\u043a\u0430\u043b\u044c\u043d\u044b\u0439 \u0438\u043d\u0442\u0435\u0440\u0432\u0430\u043b", +"Horizontal space": "\u0413\u043e\u0440\u0438\u0437\u043e\u043d\u0442\u0430\u043b\u044c\u043d\u044b\u0439 \u0438\u043d\u0442\u0435\u0440\u0432\u0430\u043b", +"Border": "\u0420\u0430\u043c\u043a\u0430", "Insert image": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435", -"Zoom in": "\u041f\u0440\u0438\u0431\u043b\u0438\u0437\u0438\u0442\u044c", -"Contrast": "\u041a\u043e\u043d\u0442\u0440\u0430\u0441\u0442", -"Back": "\u041d\u0430\u0437\u0430\u0434", -"Gamma": "\u0413\u0430\u043c\u043c\u0430", -"Flip horizontally": "\u041e\u0442\u0440\u0430\u0437\u0438\u0442\u044c \u043f\u043e \u0433\u043e\u0440\u0438\u0437\u043e\u043d\u0442\u0430\u043b\u0438", -"Resize": "\u0418\u0437\u043c\u0435\u043d\u0438\u0442\u044c \u0440\u0430\u0437\u043c\u0435\u0440", -"Sharpen": "\u0427\u0435\u0442\u043a\u043e\u0441\u0442\u044c", -"Zoom out": "\u041e\u0442\u0434\u0430\u043b\u0438\u0442\u044c", -"Image options": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f", -"Apply": "\u041f\u0440\u0438\u043c\u0435\u043d\u0438\u0442\u044c", -"Brightness": "\u042f\u0440\u043a\u043e\u0441\u0442\u044c", -"Rotate clockwise": "\u041f\u043e\u0432\u0435\u0440\u043d\u0443\u0442\u044c \u043f\u043e \u0447\u0430\u0441\u043e\u0432\u043e\u0439 \u0441\u0442\u0440\u0435\u043b\u043a\u0435", +"Image": "\u0418\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f", +"Image list": "\u0421\u043f\u0438\u0441\u043e\u043a \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0439", "Rotate counterclockwise": "\u041f\u043e\u0432\u0435\u0440\u043d\u0443\u0442\u044c \u043f\u0440\u043e\u0442\u0438\u0432 \u0447\u0430\u0441\u043e\u0432\u043e\u0439 \u0441\u0442\u0440\u0435\u043b\u043a\u0438", -"Edit image": "\u0420\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435", -"Color levels": "\u0426\u0432\u0435\u0442\u043e\u0432\u044b\u0435 \u0443\u0440\u043e\u0432\u043d\u0438", -"Crop": "\u041e\u0431\u0440\u0435\u0437\u0430\u0442\u044c", -"Orientation": "\u041e\u0440\u0438\u0435\u043d\u0442\u0430\u0446\u0438\u044f", +"Rotate clockwise": "\u041f\u043e\u0432\u0435\u0440\u043d\u0443\u0442\u044c \u043f\u043e \u0447\u0430\u0441\u043e\u0432\u043e\u0439 \u0441\u0442\u0440\u0435\u043b\u043a\u0435", "Flip vertically": "\u041e\u0442\u0440\u0430\u0437\u0438\u0442\u044c \u043f\u043e \u0432\u0435\u0440\u0442\u0438\u043a\u0430\u043b\u0438", +"Flip horizontally": "\u041e\u0442\u0440\u0430\u0437\u0438\u0442\u044c \u043f\u043e \u0433\u043e\u0440\u0438\u0437\u043e\u043d\u0442\u0430\u043b\u0438", +"Edit image": "\u0420\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435", +"Image options": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f", +"Zoom in": "\u041f\u0440\u0438\u0431\u043b\u0438\u0437\u0438\u0442\u044c", +"Zoom out": "\u041e\u0442\u0434\u0430\u043b\u0438\u0442\u044c", +"Crop": "\u041e\u0431\u0440\u0435\u0437\u0430\u0442\u044c", +"Resize": "\u0418\u0437\u043c\u0435\u043d\u0438\u0442\u044c \u0440\u0430\u0437\u043c\u0435\u0440", +"Orientation": "\u041e\u0440\u0438\u0435\u043d\u0442\u0430\u0446\u0438\u044f", +"Brightness": "\u042f\u0440\u043a\u043e\u0441\u0442\u044c", +"Sharpen": "\u0427\u0435\u0442\u043a\u043e\u0441\u0442\u044c", +"Contrast": "\u041a\u043e\u043d\u0442\u0440\u0430\u0441\u0442", +"Color levels": "\u0426\u0432\u0435\u0442\u043e\u0432\u044b\u0435 \u0443\u0440\u043e\u0432\u043d\u0438", +"Gamma": "\u0413\u0430\u043c\u043c\u0430", "Invert": "\u0418\u043d\u0432\u0435\u0440\u0441\u0438\u044f", +"Apply": "\u041f\u0440\u0438\u043c\u0435\u043d\u0438\u0442\u044c", +"Back": "\u041d\u0430\u0437\u0430\u0434", "Insert date\/time": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0434\u0430\u0442\u0443\/\u0432\u0440\u0435\u043c\u044f", -"Remove link": "\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u0441\u0441\u044b\u043b\u043a\u0443", -"Url": "\u0410\u0434\u0440\u0435\u0441 \u0441\u0441\u044b\u043b\u043a\u0438", -"Text to display": "\u041e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u043c\u044b\u0439 \u0442\u0435\u043a\u0441\u0442", -"Anchors": "\u042f\u043a\u043e\u0440\u044f", +"Date\/time": "\u0414\u0430\u0442\u0430\/\u0432\u0440\u0435\u043c\u044f", "Insert link": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0441\u0441\u044b\u043b\u043a\u0443", -"New window": "\u0412 \u043d\u043e\u0432\u043e\u043c \u043e\u043a\u043d\u0435", -"None": "\u041d\u0435\u0442", -"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "\u0412\u0432\u0435\u0434\u0451\u043d\u043d\u044b\u0439 URL \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0432\u043d\u0435\u0448\u043d\u0435\u0439 \u0441\u0441\u044b\u043b\u043a\u043e\u0439. \u0412\u044b \u0436\u0435\u043b\u0430\u0435\u0442\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043f\u0440\u0435\u0444\u0438\u043a\u0441 \u00abhttp:\/\/\u00bb?", -"Target": "\u041e\u0442\u043a\u0440\u044b\u0432\u0430\u0442\u044c \u0441\u0441\u044b\u043b\u043a\u0443", -"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "\u0412\u0432\u0435\u0434\u0451\u043d\u043d\u044b\u0439 URL \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u044b\u043c \u0430\u0434\u0440\u0435\u0441\u043e\u043c \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0439 \u043f\u043e\u0447\u0442\u044b. \u0412\u044b \u0436\u0435\u043b\u0430\u0435\u0442\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043f\u0440\u0435\u0444\u0438\u043a\u0441 \u00abmailto:\u00bb?", "Insert\/edit link": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u044c\/\u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u0441\u044b\u043b\u043a\u0443", -"Insert\/edit video": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u044c\/\u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0432\u0438\u0434\u0435\u043e", -"Poster": "\u0418\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435", -"Alternative source": "\u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u043d\u044b\u0439 \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a", -"Paste your embed code below:": "\u0412\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u0432\u0430\u0448 \u043a\u043e\u0434 \u043d\u0438\u0436\u0435:", +"Text to display": "\u041e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u043c\u044b\u0439 \u0442\u0435\u043a\u0441\u0442", +"Url": "\u0410\u0434\u0440\u0435\u0441 \u0441\u0441\u044b\u043b\u043a\u0438", +"Target": "\u041e\u0442\u043a\u0440\u044b\u0432\u0430\u0442\u044c \u0441\u0441\u044b\u043b\u043a\u0443", +"None": "\u041d\u0435\u0442", +"New window": "\u0412 \u043d\u043e\u0432\u043e\u043c \u043e\u043a\u043d\u0435", +"Remove link": "\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u0441\u0441\u044b\u043b\u043a\u0443", +"Anchors": "\u042f\u043a\u043e\u0440\u044f", +"Link": "\u0421\u0441\u044b\u043b\u043a\u0430", +"Paste or type a link": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0438\u043b\u0438 \u0432\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u0441\u0441\u044b\u043b\u043a\u0443", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "\u0412\u0432\u0435\u0434\u0451\u043d\u043d\u044b\u0439 URL \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u044b\u043c \u0430\u0434\u0440\u0435\u0441\u043e\u043c \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0439 \u043f\u043e\u0447\u0442\u044b. \u0412\u044b \u0436\u0435\u043b\u0430\u0435\u0442\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043f\u0440\u0435\u0444\u0438\u043a\u0441 \u00abmailto:\u00bb?", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "\u0412\u0432\u0435\u0434\u0451\u043d\u043d\u044b\u0439 URL \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0432\u043d\u0435\u0448\u043d\u0435\u0439 \u0441\u0441\u044b\u043b\u043a\u043e\u0439. \u0412\u044b \u0436\u0435\u043b\u0430\u0435\u0442\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043f\u0440\u0435\u0444\u0438\u043a\u0441 \u00abhttp:\/\/\u00bb?", +"Link list": "\u0421\u043f\u0438\u0441\u043e\u043a \u0441\u0441\u044b\u043b\u043e\u043a", "Insert video": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0432\u0438\u0434\u0435\u043e", +"Insert\/edit video": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u044c\/\u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0432\u0438\u0434\u0435\u043e", +"Insert\/edit media": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u044c\/\u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0432\u0438\u0434\u0435\u043e", +"Alternative source": "\u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u043d\u044b\u0439 \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a", +"Poster": "\u0418\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435", +"Paste your embed code below:": "\u0412\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u0432\u0430\u0448 \u043a\u043e\u0434 \u043d\u0438\u0436\u0435:", "Embed": "\u041a\u043e\u0434 \u0434\u043b\u044f \u0432\u0441\u0442\u0430\u0432\u043a\u0438", +"Media": "\u0412\u0438\u0434\u0435\u043e", "Nonbreaking space": "\u041d\u0435\u0440\u0430\u0437\u0440\u044b\u0432\u043d\u044b\u0439 \u043f\u0440\u043e\u0431\u0435\u043b", "Page break": "\u0420\u0430\u0437\u0440\u044b\u0432 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b", "Paste as text": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u043a\u0430\u043a \u0442\u0435\u043a\u0441\u0442", "Preview": "\u041f\u0440\u0435\u0434\u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440", "Print": "\u041f\u0435\u0447\u0430\u0442\u044c", "Save": "\u0421\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c", -"Could not find the specified string.": "\u0417\u0430\u0434\u0430\u043d\u043d\u0430\u044f \u0441\u0442\u0440\u043e\u043a\u0430 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u0430", -"Replace": "\u0417\u0430\u043c\u0435\u043d\u0438\u0442\u044c", -"Next": "\u0412\u043d\u0438\u0437", -"Whole words": "\u0421\u043b\u043e\u0432\u043e \u0446\u0435\u043b\u0438\u043a\u043e\u043c", -"Find and replace": "\u041f\u043e\u0438\u0441\u043a \u0438 \u0437\u0430\u043c\u0435\u043d\u0430", -"Replace with": "\u0417\u0430\u043c\u0435\u043d\u0438\u0442\u044c \u043d\u0430", "Find": "\u041d\u0430\u0439\u0442\u0438", +"Replace with": "\u0417\u0430\u043c\u0435\u043d\u0438\u0442\u044c \u043d\u0430", +"Replace": "\u0417\u0430\u043c\u0435\u043d\u0438\u0442\u044c", "Replace all": "\u0417\u0430\u043c\u0435\u043d\u0438\u0442\u044c \u0432\u0441\u0435", -"Match case": "\u0423\u0447\u0438\u0442\u044b\u0432\u0430\u0442\u044c \u0440\u0435\u0433\u0438\u0441\u0442\u0440", "Prev": "\u0412\u0432\u0435\u0440\u0445", +"Next": "\u0412\u043d\u0438\u0437", +"Find and replace": "\u041f\u043e\u0438\u0441\u043a \u0438 \u0437\u0430\u043c\u0435\u043d\u0430", +"Could not find the specified string.": "\u0417\u0430\u0434\u0430\u043d\u043d\u0430\u044f \u0441\u0442\u0440\u043e\u043a\u0430 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u0430", +"Match case": "\u0423\u0447\u0438\u0442\u044b\u0432\u0430\u0442\u044c \u0440\u0435\u0433\u0438\u0441\u0442\u0440", +"Whole words": "\u0421\u043b\u043e\u0432\u043e \u0446\u0435\u043b\u0438\u043a\u043e\u043c", "Spellcheck": "\u041f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c \u043f\u0440\u0430\u0432\u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435", -"Finish": "\u0417\u0430\u043a\u043e\u043d\u0447\u0438\u0442\u044c", -"Ignore all": "\u0418\u0433\u043d\u043e\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0432\u0441\u0435", "Ignore": "\u0418\u0433\u043d\u043e\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c", +"Ignore all": "\u0418\u0433\u043d\u043e\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0432\u0441\u0435", +"Finish": "\u0417\u0430\u043a\u043e\u043d\u0447\u0438\u0442\u044c", "Add to Dictionary": "\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0432 \u0441\u043b\u043e\u0432\u0430\u0440\u044c", -"Insert row before": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u043f\u0443\u0441\u0442\u0443\u044e \u0441\u0442\u0440\u043e\u043a\u0443 \u0441\u0432\u0435\u0440\u0445\u0443", -"Rows": "\u0421\u0442\u0440\u043e\u043a\u0438", -"Height": "\u0412\u044b\u0441\u043e\u0442\u0430", -"Paste row after": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0441\u0442\u0440\u043e\u043a\u0443 \u0441\u043d\u0438\u0437\u0443", -"Alignment": "\u0412\u044b\u0440\u0430\u0432\u043d\u0438\u0432\u0430\u043d\u0438\u0435", -"Border color": "\u0426\u0432\u0435\u0442 \u0440\u0430\u043c\u043a\u0438", -"Column group": "\u0413\u0440\u0443\u043f\u043f\u0430 \u043a\u043e\u043b\u043e\u043d\u043e\u043a", -"Row": "\u0421\u0442\u0440\u043e\u043a\u0430", -"Insert column before": "\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0441\u0442\u043e\u043b\u0431\u0435\u0446 \u0441\u043b\u0435\u0432\u0430", -"Split cell": "\u0420\u0430\u0437\u0431\u0438\u0442\u044c \u044f\u0447\u0435\u0439\u043a\u0443", -"Cell padding": "\u0412\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u0439 \u043e\u0442\u0441\u0442\u0443\u043f", -"Cell spacing": "\u0412\u043d\u0435\u0448\u043d\u0438\u0439 \u043e\u0442\u0441\u0442\u0443\u043f", -"Row type": "\u0422\u0438\u043f \u0441\u0442\u0440\u043e\u043a\u0438", "Insert table": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0442\u0430\u0431\u043b\u0438\u0446\u0443", -"Body": "\u0422\u0435\u043b\u043e", -"Caption": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a", -"Footer": "\u041d\u0438\u0437", -"Delete row": "\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u0441\u0442\u0440\u043e\u043a\u0443", -"Paste row before": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0441\u0442\u0440\u043e\u043a\u0443 \u0441\u0432\u0435\u0440\u0445\u0443", -"Scope": "Scope", -"Delete table": "\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u0442\u0430\u0431\u043b\u0438\u0446\u0443", -"H Align": "\u0413\u043e\u0440\u0438\u0437\u043e\u043d\u0442\u0430\u043b\u044c\u043d\u043e\u0435 \u0432\u044b\u0440\u0430\u0432\u043d\u0438\u0432\u0430\u043d\u0438\u0435", -"Top": "\u041f\u043e \u0432\u0435\u0440\u0445\u0443", -"Header cell": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a", -"Column": "\u0421\u0442\u043e\u043b\u0431\u0435\u0446", -"Row group": "\u0413\u0440\u0443\u043f\u043f\u0430 \u0441\u0442\u0440\u043e\u043a", -"Cell": "\u042f\u0447\u0435\u0439\u043a\u0430", -"Middle": "\u041f\u043e \u0441\u0435\u0440\u0435\u0434\u0438\u043d\u0435", -"Cell type": "\u0422\u0438\u043f \u044f\u0447\u0435\u0439\u043a\u0438", -"Copy row": "\u041a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u0442\u0440\u043e\u043a\u0443", -"Row properties": "\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0441\u0442\u0440\u043e\u043a\u0438", "Table properties": "\u0421\u0432\u043e\u0439\u0441\u0442\u0432\u0430 \u0442\u0430\u0431\u043b\u0438\u0446\u044b", -"Bottom": "\u041f\u043e \u043d\u0438\u0437\u0443", -"V Align": "\u0412\u0435\u0440\u0442\u0438\u043a\u0430\u043b\u044c\u043d\u043e\u0435 \u0432\u044b\u0440\u0430\u0432\u043d\u0438\u0432\u0430\u043d\u0438\u0435", -"Header": "\u0428\u0430\u043f\u043a\u0430", -"Right": "\u041f\u043e \u043f\u0440\u0430\u0432\u043e\u043c\u0443 \u043a\u0440\u0430\u044e", -"Insert column after": "\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0441\u0442\u043e\u043b\u0431\u0435\u0446 \u0441\u043f\u0440\u0430\u0432\u0430", -"Cols": "\u0421\u0442\u043e\u043b\u0431\u0446\u044b", -"Insert row after": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u043f\u0443\u0441\u0442\u0443\u044e \u0441\u0442\u0440\u043e\u043a\u0443 \u0441\u043d\u0438\u0437\u0443", -"Width": "\u0428\u0438\u0440\u0438\u043d\u0430", +"Delete table": "\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u0442\u0430\u0431\u043b\u0438\u0446\u0443", +"Cell": "\u042f\u0447\u0435\u0439\u043a\u0430", +"Row": "\u0421\u0442\u0440\u043e\u043a\u0430", +"Column": "\u0421\u0442\u043e\u043b\u0431\u0435\u0446", "Cell properties": "\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u044f\u0447\u0435\u0439\u043a\u0438", -"Left": "\u041f\u043e \u043b\u0435\u0432\u043e\u043c\u0443 \u043a\u0440\u0430\u044e", -"Cut row": "\u0412\u044b\u0440\u0435\u0437\u0430\u0442\u044c \u0441\u0442\u0440\u043e\u043a\u0443", -"Delete column": "\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u0441\u0442\u043e\u043b\u0431\u0435\u0446", -"Center": "\u041f\u043e \u0446\u0435\u043d\u0442\u0440\u0443", "Merge cells": "\u041e\u0431\u044a\u0435\u0434\u0438\u043d\u0438\u0442\u044c \u044f\u0447\u0435\u0439\u043a\u0438", +"Split cell": "\u0420\u0430\u0437\u0431\u0438\u0442\u044c \u044f\u0447\u0435\u0439\u043a\u0443", +"Insert row before": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u043f\u0443\u0441\u0442\u0443\u044e \u0441\u0442\u0440\u043e\u043a\u0443 \u0441\u0432\u0435\u0440\u0445\u0443", +"Insert row after": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u043f\u0443\u0441\u0442\u0443\u044e \u0441\u0442\u0440\u043e\u043a\u0443 \u0441\u043d\u0438\u0437\u0443", +"Delete row": "\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u0441\u0442\u0440\u043e\u043a\u0443", +"Row properties": "\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0441\u0442\u0440\u043e\u043a\u0438", +"Cut row": "\u0412\u044b\u0440\u0435\u0437\u0430\u0442\u044c \u0441\u0442\u0440\u043e\u043a\u0443", +"Copy row": "\u041a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u0442\u0440\u043e\u043a\u0443", +"Paste row before": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0441\u0442\u0440\u043e\u043a\u0443 \u0441\u0432\u0435\u0440\u0445\u0443", +"Paste row after": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0441\u0442\u0440\u043e\u043a\u0443 \u0441\u043d\u0438\u0437\u0443", +"Insert column before": "\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0441\u0442\u043e\u043b\u0431\u0435\u0446 \u0441\u043b\u0435\u0432\u0430", +"Insert column after": "\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0441\u0442\u043e\u043b\u0431\u0435\u0446 \u0441\u043f\u0440\u0430\u0432\u0430", +"Delete column": "\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u0441\u0442\u043e\u043b\u0431\u0435\u0446", +"Cols": "\u0421\u0442\u043e\u043b\u0431\u0446\u044b", +"Rows": "\u0421\u0442\u0440\u043e\u043a\u0438", +"Width": "\u0428\u0438\u0440\u0438\u043d\u0430", +"Height": "\u0412\u044b\u0441\u043e\u0442\u0430", +"Cell spacing": "\u0412\u043d\u0435\u0448\u043d\u0438\u0439 \u043e\u0442\u0441\u0442\u0443\u043f", +"Cell padding": "\u0412\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u0439 \u043e\u0442\u0441\u0442\u0443\u043f", +"Caption": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a", +"Left": "\u041f\u043e \u043b\u0435\u0432\u043e\u043c\u0443 \u043a\u0440\u0430\u044e", +"Center": "\u041f\u043e \u0446\u0435\u043d\u0442\u0440\u0443", +"Right": "\u041f\u043e \u043f\u0440\u0430\u0432\u043e\u043c\u0443 \u043a\u0440\u0430\u044e", +"Cell type": "\u0422\u0438\u043f \u044f\u0447\u0435\u0439\u043a\u0438", +"Scope": "Scope", +"Alignment": "\u0412\u044b\u0440\u0430\u0432\u043d\u0438\u0432\u0430\u043d\u0438\u0435", +"H Align": "\u0413\u043e\u0440\u0438\u0437\u043e\u043d\u0442\u0430\u043b\u044c\u043d\u043e\u0435 \u0432\u044b\u0440\u0430\u0432\u043d\u0438\u0432\u0430\u043d\u0438\u0435", +"V Align": "\u0412\u0435\u0440\u0442\u0438\u043a\u0430\u043b\u044c\u043d\u043e\u0435 \u0432\u044b\u0440\u0430\u0432\u043d\u0438\u0432\u0430\u043d\u0438\u0435", +"Top": "\u041f\u043e \u0432\u0435\u0440\u0445\u0443", +"Middle": "\u041f\u043e \u0441\u0435\u0440\u0435\u0434\u0438\u043d\u0435", +"Bottom": "\u041f\u043e \u043d\u0438\u0437\u0443", +"Header cell": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a", +"Row group": "\u0413\u0440\u0443\u043f\u043f\u0430 \u0441\u0442\u0440\u043e\u043a", +"Column group": "\u0413\u0440\u0443\u043f\u043f\u0430 \u043a\u043e\u043b\u043e\u043d\u043e\u043a", +"Row type": "\u0422\u0438\u043f \u0441\u0442\u0440\u043e\u043a\u0438", +"Header": "\u0428\u0430\u043f\u043a\u0430", +"Body": "\u0422\u0435\u043b\u043e", +"Footer": "\u041d\u0438\u0437", +"Border color": "\u0426\u0432\u0435\u0442 \u0440\u0430\u043c\u043a\u0438", "Insert template": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0448\u0430\u0431\u043b\u043e\u043d", "Templates": "\u0428\u0430\u0431\u043b\u043e\u043d\u044b", +"Template": "\u0428\u0430\u0431\u043b\u043e\u043d", +"Text color": "\u0426\u0432\u0435\u0442 \u0442\u0435\u043a\u0441\u0442\u0430", "Background color": "\u0426\u0432\u0435\u0442 \u0444\u043e\u043d\u0430", "Custom...": "\u0412\u044b\u0431\u0440\u0430\u0442\u044c\u2026", "Custom color": "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0439 \u0446\u0432\u0435\u0442", "No color": "\u0411\u0435\u0437 \u0446\u0432\u0435\u0442\u0430", -"Text color": "\u0426\u0432\u0435\u0442 \u0442\u0435\u043a\u0441\u0442\u0430", +"Table of Contents": "\u0421\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435", "Show blocks": "\u041f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u0431\u043b\u043e\u043a\u0438", "Show invisible characters": "\u041f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u043d\u0435\u0432\u0438\u0434\u0438\u043c\u044b\u0435 \u0441\u0438\u043c\u0432\u043e\u043b\u044b", "Words: {0}": "\u041a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0441\u043b\u043e\u0432: {0}", -"Insert": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u044c", +"{0} words": "\u0441\u043b\u043e\u0432: {0}", "File": "\u0424\u0430\u0439\u043b", "Edit": "\u0418\u0437\u043c\u0435\u043d\u0438\u0442\u044c", -"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "\u0422\u0435\u043a\u0441\u0442\u043e\u0432\u043e\u0435 \u043f\u043e\u043b\u0435. \u041d\u0430\u0436\u043c\u0438\u0442\u0435 ALT-F9 \u0447\u0442\u043e\u0431\u044b \u0432\u044b\u0437\u0432\u0430\u0442\u044c \u043c\u0435\u043d\u044e, ALT-F10 \u043f\u0430\u043d\u0435\u043b\u044c \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u043e\u0432, ALT-0 \u0434\u043b\u044f \u0432\u044b\u0437\u043e\u0432\u0430 \u043f\u043e\u043c\u043e\u0449\u0438.", -"Tools": "\u0418\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u044b", +"Insert": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u044c", "View": "\u0412\u0438\u0434", +"Format": "\u0424\u043e\u0440\u043c\u0430\u0442", "Table": "\u0422\u0430\u0431\u043b\u0438\u0446\u0430", -"Format": "\u0424\u043e\u0440\u043c\u0430\u0442" +"Tools": "\u0418\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u044b", +"Powered by {0}": "\u041f\u0440\u0438 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0435 {0}", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "\u0422\u0435\u043a\u0441\u0442\u043e\u0432\u043e\u0435 \u043f\u043e\u043b\u0435. \u041d\u0430\u0436\u043c\u0438\u0442\u0435 ALT-F9 \u0447\u0442\u043e\u0431\u044b \u0432\u044b\u0437\u0432\u0430\u0442\u044c \u043c\u0435\u043d\u044e, ALT-F10 \u043f\u0430\u043d\u0435\u043b\u044c \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u043e\u0432, ALT-0 \u0434\u043b\u044f \u0432\u044b\u0437\u043e\u0432\u0430 \u043f\u043e\u043c\u043e\u0449\u0438." }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/sk.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/sk.js new file mode 100644 index 0000000000..2362a6dbc6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/sk.js @@ -0,0 +1,253 @@ +tinymce.addI18n('sk',{ +"Redo": "Znova", +"Undo": "Vr\u00e1ti\u0165", +"Cut": "Vystrihn\u00fa\u0165", +"Copy": "Kop\u00edrova\u0165", +"Paste": "Vlo\u017ei\u0165", +"Select all": "Ozna\u010di\u0165 v\u0161etko", +"New document": "Nov\u00fd dokument", +"Ok": "Ok", +"Cancel": "Zru\u0161i\u0165", +"Visual aids": "Vizu\u00e1lne pom\u00f4cky", +"Bold": "Tu\u010dn\u00e9", +"Italic": "Kurz\u00edva", +"Underline": "Pod\u010diarknut\u00e9", +"Strikethrough": "Pre\u010diarknut\u00e9", +"Superscript": "Horn\u00fd index", +"Subscript": "Spodn\u00fd index", +"Clear formatting": "Vymaza\u0165 form\u00e1tovanie", +"Align left": "Zarovna\u0165 v\u013eavo", +"Align center": "Zarovna\u0165 na stred", +"Align right": "Zarovna\u0165 vpravo", +"Justify": "Zarovna\u0165", +"Bullet list": "Odr\u00e1\u017eky", +"Numbered list": "\u010c\u00edslovan\u00fd zoznam", +"Decrease indent": "Zmen\u0161i\u0165 odsadenie", +"Increase indent": "Zv\u00e4\u010d\u0161i\u0165 odsadenie", +"Close": "Zatvori\u0165", +"Formats": "Form\u00e1ty", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "V\u00e1\u0161 prehliada\u010d nepodporuje priamy pr\u00edstup do schr\u00e1nky. Pou\u017eite kl\u00e1vesov\u00e9 skratky Ctrl+X\/C\/V.", +"Headers": "Nadpisy", +"Header 1": "Nadpis 1", +"Header 2": "Nadpis 2", +"Header 3": "Nadpis 3", +"Header 4": "Nadpis 4", +"Header 5": "Nadpis 5", +"Header 6": "Nadpis 6", +"Headings": "Nadpisy", +"Heading 1": "Nadpis 1", +"Heading 2": "Nadpis 2", +"Heading 3": "Nadpis 3", +"Heading 4": "Nadpis 4", +"Heading 5": "Nadpis 5", +"Heading 6": "Nadpis 6", +"Div": "Blok", +"Pre": "Preform\u00e1tovan\u00fd", +"Code": "K\u00f3d", +"Paragraph": "Odsek", +"Blockquote": "Cit\u00e1cia", +"Inline": "\u0160t\u00fdly", +"Blocks": "Bloky", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "Vkladanie je v m\u00f3de neform\u00e1tovan\u00e9ho textu. Vkladan\u00fd obsah bude vlo\u017een\u00fd ako neform\u00e1tovan\u00fd, a\u017e pok\u00fdm t\u00fato mo\u017enos\u0165 nevypnete.", +"Font Family": "P\u00edsmo", +"Font Sizes": "Ve\u013ekos\u0165 p\u00edsma", +"Class": "Trieda", +"Browse for an image": "N\u00e1js\u0165 obr\u00e1zok", +"OR": "ALEBO", +"Drop an image here": "Pretiahnite obr\u00e1zok sem", +"Upload": "Nahra\u0165", +"Default": "V\u00fdchodzie", +"Circle": "Kruh", +"Disc": "Disk", +"Square": "\u0160tvorec", +"Lower Alpha": "Mal\u00e9 p\u00edsmen\u00e1", +"Lower Greek": "Mal\u00e9 gr\u00e9cke p\u00edsmen\u00e1", +"Lower Roman": "Mal\u00e9 r\u00edmske \u010d\u00edslice", +"Upper Alpha": "Ve\u013ek\u00e9 p\u00edsmen\u00e1", +"Upper Roman": "Ve\u013ek\u00e9 r\u00edmske \u010d\u00edslice", +"Anchor": "Odkaz", +"Name": "N\u00e1zov", +"Id": "Id", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "Id by malo za\u010d\u00edna\u0165 p\u00edsmenom, nasledovan\u00e9 p\u00edsmenami, \u010d\u00edslami, pom\u013a\u010dkami, bodkami, dvojbodkami alebo podtr\u017en\u00edkmi.", +"You have unsaved changes are you sure you want to navigate away?": "M\u00e1te neulo\u017een\u00e9 zmeny, naozaj chcete opusti\u0165 str\u00e1nku?", +"Restore last draft": "Obnovi\u0165 posledn\u00fd koncept", +"Special character": "\u0160peci\u00e1lny znak", +"Source code": "Zdrojov\u00fd k\u00f3d", +"Insert\/Edit code sample": "Vlo\u017ei\u0165\/upravi\u0165 vzorku k\u00f3du", +"Language": "Jazyk", +"Color": "Farba", +"R": "R", +"G": "G", +"B": "B", +"Left to right": "Z\u013eava doprava", +"Right to left": "Sprava do\u013eava", +"Emoticons": "Smajl\u00edci", +"Document properties": "Vlastnosti dokumentu", +"Title": "Nadpis", +"Keywords": "K\u013e\u00fa\u010dov\u00e9 slov\u00e1", +"Description": "Popis", +"Robots": "Preh\u013ead\u00e1vacie roboty", +"Author": "Autor", +"Encoding": "K\u00f3dovanie", +"Fullscreen": "Na cel\u00fa obrazovku", +"Action": "Action", +"Shortcut": "Shortcut", +"Help": "Help", +"Address": "Address", +"Focus to menubar": "Focus to menubar", +"Focus to toolbar": "Focus to toolbar", +"Focus to element path": "Focus to element path", +"Focus to contextual toolbar": "Focus to contextual toolbar", +"Insert link (if link plugin activated)": "Insert link (if link plugin activated)", +"Save (if save plugin activated)": "Save (if save plugin activated)", +"Find (if searchreplace plugin activated)": "Find (if searchreplace plugin activated)", +"Plugins installed ({0}):": "Plugins installed ({0}):", +"Premium plugins:": "Premium plugins:", +"Learn more...": "Learn more...", +"You are using {0}": "You are using {0}", +"Horizontal line": "Horizont\u00e1lna \u010diara", +"Insert\/edit image": "Vlo\u017ei\u0165\/upravi\u0165 obr\u00e1zok", +"Image description": "Popis obr\u00e1zku", +"Source": "Zdroj", +"Dimensions": "Rozmery", +"Constrain proportions": "Vymedzen\u00e9 proporcie", +"General": "Hlavn\u00e9", +"Advanced": "Pokro\u010dil\u00e9", +"Style": "\u0160t\u00fdl", +"Vertical space": "Vertik\u00e1lny priestor", +"Horizontal space": "Horizont\u00e1lny priestor", +"Border": "Or\u00e1movanie", +"Insert image": "Vlo\u017ei\u0165 obr\u00e1zok", +"Image": "Obr\u00e1zok", +"Image list": "Zoznam obr\u00e1zkov", +"Rotate counterclockwise": "Oto\u010di\u0165 proti smeru hodinov\u00fdch ru\u010di\u010diek", +"Rotate clockwise": "Oto\u010di\u0165 v smere hodinov\u00fdch ru\u010di\u010diek", +"Flip vertically": "Preklopi\u0165 vertik\u00e1lne", +"Flip horizontally": "Preklopi\u0165 horizont\u00e1lne", +"Edit image": "Upravi\u0165 obr\u00e1zok", +"Image options": "Mo\u017enosti obr\u00e1zku", +"Zoom in": "Pribl\u00ed\u017ei\u0165", +"Zoom out": "Oddiali\u0165", +"Crop": "Vyreza\u0165", +"Resize": "Zmeni\u0165 ve\u013ekos\u0165", +"Orientation": "Orient\u00e1cia", +"Brightness": "Jas", +"Sharpen": "Zaostri\u0165", +"Contrast": "Kontrast", +"Color levels": "\u00darovne farieb", +"Gamma": "Gama", +"Invert": "Invertova\u0165", +"Apply": "Pou\u017ei\u0165", +"Back": "Sp\u00e4\u0165", +"Insert date\/time": "Vlo\u017ei\u0165 d\u00e1tum\/\u010das", +"Date\/time": "D\u00e1tum\/\u010das", +"Insert link": "Vlo\u017ei\u0165 odkaz", +"Insert\/edit link": "Vlo\u017ei\u0165\/upravi\u0165 odkaz", +"Text to display": "Zobrazen\u00fd text", +"Url": "Url", +"Target": "Cie\u013e", +"None": "\u017diadne", +"New window": "Nov\u00e9 okno", +"Remove link": "Odstr\u00e1ni\u0165 odkaz", +"Anchors": "Kotvy", +"Link": "Odkaz", +"Paste or type a link": "Prilepte alebo nap\u00ed\u0161te odkaz", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "URL, ktor\u00fa ste vlo\u017eili je pravdepodobne emailov\u00e1 adresa. \u017del\u00e1te si prida\u0165 vy\u017eadovan\u00fa mailto: predponu?", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "URL adresa ktor\u00fa ste zadali vyzer\u00e1 ako extern\u00fd odkaz. Chcete prida\u0165 vy\u017eadovan\u00fa http:\/\/ predponu?", +"Link list": "Zoznam odkazov", +"Insert video": "Vlo\u017ei\u0165 video", +"Insert\/edit video": "Vlo\u017ei\u0165\/upravi\u0165 video", +"Insert\/edit media": "Vlo\u017ei\u0165\/upravi\u0165 m\u00e9di\u00e1", +"Alternative source": "Alternat\u00edvny zdroj", +"Poster": "Uk\u00e1\u017eka", +"Paste your embed code below:": "Vlo\u017ete k\u00f3d pre vlo\u017eenie na str\u00e1nku:", +"Embed": "Vlo\u017een\u00e9", +"Media": "M\u00e9di\u00e1", +"Nonbreaking space": "Nedelite\u013en\u00e1 medzera", +"Page break": "Zalomenie str\u00e1nky", +"Paste as text": "Vlo\u017ei\u0165 ako text", +"Preview": "N\u00e1h\u013ead", +"Print": "Tla\u010di\u0165", +"Save": "Ulo\u017ei\u0165", +"Find": "H\u013eada\u0165", +"Replace with": "Nahradi\u0165 za", +"Replace": "Nahradi\u0165", +"Replace all": "Nahradi\u0165 v\u0161etko", +"Prev": "Predch\u00e1dzaj\u00face", +"Next": "Nasleduj\u00face", +"Find and replace": "Vyh\u013eada\u0165 a nahradi\u0165", +"Could not find the specified string.": "Zadan\u00fd re\u0165azec sa nena\u0161iel.", +"Match case": "Rozli\u0161ova\u0165 ve\u013ek\u00e9\/mal\u00e9", +"Whole words": "Cel\u00e9 slov\u00e1", +"Spellcheck": "Kontrola pravopisu", +"Ignore": "Ignorova\u0165", +"Ignore all": "Ignorova\u0165 v\u0161etko", +"Finish": "Dokon\u010di\u0165", +"Add to Dictionary": "Prida\u0165 do slovn\u00edka", +"Insert table": "Vlo\u017ei\u0165 tabu\u013eku", +"Table properties": "Nastavenia tabu\u013eky", +"Delete table": "Zmaza\u0165 tabu\u013eku", +"Cell": "Bunka", +"Row": "Riadok", +"Column": "St\u013apec", +"Cell properties": "Vlastnosti bunky", +"Merge cells": "Spoji\u0165 bunky", +"Split cell": "Rozdeli\u0165 bunku", +"Insert row before": "Vlo\u017ei\u0165 nov\u00fd riadok pred", +"Insert row after": "Vlo\u017ei\u0165 nov\u00fd riadok za", +"Delete row": "Zmaza\u0165 riadok", +"Row properties": "Vlastnosti riadku", +"Cut row": "Vystrihn\u00fa\u0165 riadok", +"Copy row": "Kop\u00edrova\u0165 riadok", +"Paste row before": "Vlo\u017ei\u0165 riadok pred", +"Paste row after": "Vlo\u017ei\u0165 riadok za", +"Insert column before": "Prida\u0165 nov\u00fd st\u013apec pred", +"Insert column after": "Prida\u0165 nov\u00fd st\u013apec za", +"Delete column": "Vymaza\u0165 st\u013apec", +"Cols": "St\u013apce", +"Rows": "Riadky", +"Width": "\u0160\u00edrka", +"Height": "V\u00fd\u0161ka", +"Cell spacing": "Priestor medzi bunkami", +"Cell padding": "Odsadenie v bunk\u00e1ch", +"Caption": "Popisok", +"Left": "V\u013eavo", +"Center": "Na stred", +"Right": "Vpravo", +"Cell type": "Typ bunky", +"Scope": "Oblas\u0165", +"Alignment": "Zarovnanie", +"H Align": "Horizont\u00e1lne zarovnanie", +"V Align": "Vertik\u00e1lne zarovnanie", +"Top": "Vrch", +"Middle": "Stred", +"Bottom": "Spodok", +"Header cell": "Bunka z\u00e1hlavia", +"Row group": "Skupina riadkov", +"Column group": "Skupina st\u013apcov", +"Row type": "Typ riadku", +"Header": "Z\u00e1hlavie", +"Body": "Telo", +"Footer": "P\u00e4ti\u010dka", +"Border color": "Farba or\u00e1movania", +"Insert template": "Vlo\u017ei\u0165 \u0161abl\u00f3nu", +"Templates": "\u0160abl\u00f3ny", +"Template": "\u0160abl\u00f3na", +"Text color": "Farba textu", +"Background color": "Farba pozadia", +"Custom...": "Vlastn\u00e1...", +"Custom color": "Vlastn\u00e1 farba", +"No color": "Bez farby", +"Table of Contents": "Obsah", +"Show blocks": "Zobrazi\u0165 bloky", +"Show invisible characters": "Zobrazi\u0165 skryt\u00e9 znaky", +"Words: {0}": "Slov: {0}", +"File": "S\u00fabor", +"Edit": "Upravi\u0165", +"Insert": "Vlo\u017ei\u0165", +"View": "Zobrazi\u0165", +"Format": "Form\u00e1t", +"Table": "Tabu\u013eka", +"Tools": "N\u00e1stroje", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Textov\u00e9 pole. Stla\u010dte ALT-F9 pre zobrazenie menu, ALT-F10 pre zobrazenie panela n\u00e1strojov, ALT-0 pre n\u00e1povedu." +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/sl_SI.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/sl_SI.js new file mode 100644 index 0000000000..ba6c89a584 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/sl_SI.js @@ -0,0 +1,230 @@ +tinymce.addI18n('sl_SI',{ +"Cut": "Izre\u017ei", +"Heading 5": "Podnaslov 5", +"Header 2": "Naslov 2", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "Varnostne nastavitve brskalnika ne dopu\u0161\u010dajo direktnega dostopa do odlo\u017ei\u0161\u010da. Uporabite kombinacijo tipk Ctrl+X\/C\/V na tipkovnici.", +"Heading 4": "Podnaslov 4", +"Div": "Div", +"Heading 2": "Podnaslov 2", +"Paste": "Prilepi", +"Close": "Zapri", +"Font Family": "Dru\u017eina pisav", +"Pre": "Predformat", +"Align right": "Desna poravnava", +"New document": "Nov dokument", +"Blockquote": "Zamik besedila", +"Numbered list": "O\u0161tevil\u010den seznam", +"Heading 1": "Podnaslov 1", +"Headings": "Podnaslovi", +"Increase indent": "Pove\u010daj zamik", +"Formats": "Oblika", +"Headers": "Naslovi", +"Select all": "Izberi vse", +"Header 3": "Naslov 3", +"Blocks": "Grupe", +"Undo": "Razveljavi", +"Strikethrough": "Pre\u010drtano", +"Bullet list": "Ozna\u010den seznam", +"Header 1": "Naslov 1", +"Superscript": "Nadpisano", +"Clear formatting": "Odstrani oblikovanje", +"Font Sizes": "Velikosti pisave", +"Subscript": "Podpisano", +"Header 6": "Naslov 6", +"Redo": "Ponovi", +"Paragraph": "Odstavek", +"Ok": "V redu", +"Bold": "Krepko", +"Code": "Koda", +"Italic": "Le\u017ee\u010de", +"Align center": "Sredinska poravnava", +"Header 5": "Naslov 5", +"Heading 6": "Podnaslov 6", +"Heading 3": "Podnaslov 3", +"Decrease indent": "Zmanj\u0161aj zamik", +"Header 4": "Naslov 4", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "Odlagali\u0161\u010de je zdaj v tekstovnem na\u010dinu. Vsebina bo preslikana kot golo besedilo brez oblike, dokler te mo\u017enosti ne izklju\u010dite.", +"Underline": "Pod\u010drtano", +"Cancel": "Prekli\u010di", +"Justify": "Obojestranska poravnava", +"Inline": "Med besedilom", +"Copy": "Kopiraj", +"Align left": "Leva poravnava", +"Visual aids": "Vizualni pripomo\u010dki", +"Lower Greek": "Male gr\u0161ke \u010drke", +"Square": "Kvadratek", +"Default": "Privzeto", +"Lower Alpha": "Male tiskane \u010drke", +"Circle": "Pikica", +"Disc": "Kroglica", +"Upper Alpha": "Velike tiskane \u010drke", +"Upper Roman": "Velike rimske \u0161tevilke", +"Lower Roman": "Male rimske \u0161tevilke", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "Id se mora za\u010deti s \u010drko, sledijo samo \u010drke, \u0161tevilke, pomi\u0161ljaj, pike, dvopi\u010dje ali pod\u010drtaj.", +"Name": "Naziv zaznamka", +"Anchor": "Zaznamek", +"Id": "Id", +"You have unsaved changes are you sure you want to navigate away?": "Imate neshranjene spremembe. Ste prepri\u010dati, da \u017eelite zapustiti stran?", +"Restore last draft": "Obnovi zadnji osnutek", +"Special character": "Posebni znaki", +"Source code": "Izvorna koda", +"Language": "Jezik", +"Insert\/Edit code sample": "Vstavi\/Uredi vzor\u010dno kodo", +"B": "B", +"R": "R", +"G": "G", +"Color": "Barva", +"Right to left": "Od desne proti levi", +"Left to right": "Od leve proti desni", +"Emoticons": "Sme\u0161ki", +"Robots": "Robotki", +"Document properties": "Lastnosti dokumenta", +"Title": "Naslov", +"Keywords": "Klju\u010dne besede", +"Encoding": "Kodiranje", +"Description": "Opis", +"Author": "Avtor", +"Fullscreen": "\u010cez cel zaslon", +"Horizontal line": "Vodoravna \u010drta", +"Horizontal space": "Vodoravni prostor", +"Insert\/edit image": "Vstavi\/uredi sliko", +"General": "Splo\u0161no", +"Advanced": "Napredno", +"Source": "Pot", +"Border": "Obroba", +"Constrain proportions": "Obdr\u017ei razmerje", +"Vertical space": "Navpi\u010dni prostor", +"Image description": "Opis slike", +"Style": "Slog", +"Dimensions": "Dimenzije", +"Insert image": "Vnesi sliko", +"Image": "Slika", +"Zoom in": "Pove\u010daj", +"Contrast": "Kontrast", +"Back": "Nazaj", +"Gamma": "Gama", +"Flip horizontally": "Obrni vodoravno", +"Resize": "Spremeni velikost", +"Sharpen": "Izostri", +"Zoom out": "Pomanj\u0161aj", +"Image options": "Mo\u017enosti slike", +"Apply": "Uporabi", +"Brightness": "Svetlost", +"Rotate clockwise": "Zavrti v smeri urinega kazalca", +"Rotate counterclockwise": "Zavrti v nasprotni smeri urnega kazalca", +"Edit image": "Uredi sliko", +"Color levels": "Barvni nivo", +"Crop": "Obre\u017ei", +"Orientation": "Usmerjenost", +"Flip vertically": "Obrni navpi\u010dno", +"Invert": "Obrni", +"Date\/time": "Datum\/\u010das", +"Insert date\/time": "Vstavi datum\/\u010das", +"Remove link": "Odstrani povezavo", +"Url": "Povezava", +"Text to display": "Prikazno besedilo", +"Anchors": "Sidra", +"Insert link": "Vstavi povezavo", +"Link": "Povezava", +"New window": "Novo okno", +"None": "Brez", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "Vne\u0161eni URL predstavlja zunanjo povezavo. Ali \u017eelite dodati \"http:\/\/\" predpono?", +"Paste or type a link": "Prilepite ali vnesite povezavo", +"Target": "Cilj", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "Vne\u0161eni URL predstavlja e-po\u0161tni naslov. Ali \u017eelite dodati potrebno \"mailto:\" predpono?", +"Insert\/edit link": "Vstavi\/uredi povezavo", +"Insert\/edit video": "Vstavi\/uredi video", +"Media": "Medij", +"Alternative source": "Nadomestni vir", +"Paste your embed code below:": "Prilepite kodo za vdelavo:", +"Insert video": "Vstavi video", +"Poster": "Poster", +"Insert\/edit media": "Vstavi\/uredi medij", +"Embed": "Vdelaj", +"Nonbreaking space": "Nedeljivi presledek", +"Page break": "Prelom strani", +"Paste as text": "Vnesi kot besedilo", +"Preview": "Predogled", +"Print": "Natisni", +"Save": "Shrani", +"Could not find the specified string.": "Iskanje ni vrnilo rezultatov.", +"Replace": "Zamenjaj", +"Next": "Naprej", +"Whole words": "Cele besede", +"Find and replace": "Poi\u0161\u010di in zamenjaj", +"Replace with": "Zamenjaj z", +"Find": "I\u0161\u010di", +"Replace all": "Zamenjaj vse", +"Match case": "Ujemanje malih in velikih \u010drk", +"Prev": "Nazaj", +"Spellcheck": "Preverjanje \u010drkovanja", +"Finish": "Zaklju\u010di", +"Ignore all": "Prezri vse", +"Ignore": "Prezri", +"Add to Dictionary": "Dodaj v slovar", +"Insert row before": "Vstavi vrstico pred", +"Rows": "Vrstice", +"Height": "Vi\u0161ina", +"Paste row after": "Prilepi vrstico za", +"Alignment": "Poravnava", +"Border color": "Barva obrobe", +"Column group": "Grupiranje stolpcev", +"Row": "Vrstica", +"Insert column before": "Vstavi stolpec pred", +"Split cell": "Razdeli celico", +"Cell padding": "Polnilo med celicami", +"Cell spacing": "Razmik med celicami", +"Row type": "Tip vrstice", +"Insert table": "Vstavi tabelo", +"Body": "Vsebina", +"Caption": "Naslov", +"Footer": "Noga", +"Delete row": "Izbri\u0161i vrstico", +"Paste row before": "Prilepi vrstico pred", +"Scope": "Obseg", +"Delete table": "Izbri\u0161i tabelo", +"H Align": "Horizontalna poravnava", +"Top": "Vrh", +"Header cell": "Celica glave", +"Column": "Stolpec", +"Row group": "Grupiranje vrstic", +"Cell": "Celica", +"Middle": "Sredina", +"Cell type": "Tip celice", +"Copy row": "Kopiraj vrstico", +"Row properties": "Lastnosti vrstice", +"Table properties": "Lastnosti tabele", +"Bottom": "Dno", +"V Align": "Vertikalna poravnava", +"Header": "Glava", +"Right": "Desno", +"Insert column after": "Vstavi stolpec za", +"Cols": "Stolpci", +"Insert row after": "Vstavi vrstico za", +"Width": "\u0160irina", +"Cell properties": "Lastnosti celice", +"Left": "Levo", +"Cut row": "Izre\u017ei vrstico", +"Delete column": "Izbri\u0161i stolpec", +"Center": "Sredinsko", +"Merge cells": "Zdru\u017ei celice", +"Insert template": "Vstavi predlogo", +"Templates": "Predloge", +"Background color": "Barva ozadja", +"Custom...": "Po meri ...", +"Custom color": "Barva po meri", +"No color": "Brezbarvno", +"Text color": "Barva besedila", +"Table of Contents": "Kazalo", +"Show blocks": "Prika\u017ei bloke", +"Show invisible characters": "Prika\u017ei skrite znake", +"Words: {0}": "Besed: {0}", +"Insert": "Vstavi", +"File": "Datoteka", +"Edit": "Uredi", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Bogato besedilo. Pritisnite ALT-F9 za meni. Pritisnite ALT-F10 za orodno vrstico. Pritisnite ALT-0 za pomo\u010d", +"Tools": "Orodja", +"View": "Pogled", +"Table": "Tabela", +"Format": "Oblika" +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/sr.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/sr.js new file mode 100644 index 0000000000..9150c2ed82 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/sr.js @@ -0,0 +1,261 @@ +tinymce.addI18n('sr',{ +"Redo": "Napred", +"Undo": "Nazad", +"Cut": "Iseci", +"Copy": "Kopiraj", +"Paste": "Nalepi", +"Select all": "Obele\u017ei sve", +"New document": "Novi dokument", +"Ok": "Ok", +"Cancel": "Opozovi", +"Visual aids": "Vizuelna pomagala", +"Bold": "Podebljan", +"Italic": "isko\u0161en", +"Underline": "Podvu\u010den", +"Strikethrough": "Precrtan", +"Superscript": "Natpis", +"Subscript": "Potpisan", +"Clear formatting": "Brisanje formatiranja", +"Align left": "Poravnano levo", +"Align center": "Poravnano centar", +"Align right": "Poravnano desno", +"Justify": "Poravnanje", +"Bullet list": "Lista nabrajanja", +"Numbered list": "Numerisana lista", +"Decrease indent": "Smanji uvla\u010denje", +"Increase indent": "Pove\u0107aj uvla\u010denje", +"Close": "Zatvori", +"Formats": "Formatiraj", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "Va\u0161 pretra\u017eiva\u010d nepodr\u017eava direktan pristup prenosu.Koristite Ctrl+X\/C\/V pre\u010dice na tastaturi", +"Headers": "Zaglavlje", +"Header 1": "Zaglavlje 1", +"Header 2": "Zaglavlje 2", +"Header 3": "Zaglavlje 3", +"Header 4": "Zaglavlje 4", +"Header 5": "Zaglavlje 5", +"Header 6": "Zaglavlje 6", +"Headings": "Naslovi", +"Heading 1": "Naslov 1", +"Heading 2": "Naslov 2", +"Heading 3": "Naslov 3", +"Heading 4": "Naslov 4", +"Heading 5": "Naslov 5", +"Heading 6": "Naslov 6", +"Preformatted": "Formatirano", +"Div": "Div", +"Pre": "Pre", +"Code": "Kod", +"Paragraph": "Paragraf", +"Blockquote": "Navodnici", +"Inline": "U liniji", +"Blocks": "Blokovi", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "Nalepiti je sada u obi\u010dnom text modu.Sadr\u017eaj \u0107e biti nalepljen kao obi\u010dan tekst dok ne ugasite ovu opciju.", +"Font Family": "Vrsta fonta", +"Font Sizes": "Veli\u010dine fontova", +"Class": "Klasa", +"Browse for an image": "Prona\u0111i sliku", +"OR": "ili", +"Drop an image here": "Prevuci sliku ovde", +"Upload": "Po\u0161alji", +"Block": "Blok", +"Align": "Poravnaj", +"Default": "Podrazumevano", +"Circle": "Krug", +"Disc": "Disk", +"Square": "Kvadrat", +"Lower Alpha": "Donja Alpha", +"Lower Greek": "Ni\u017ei gr\u010dki", +"Lower Roman": "Donji Roman", +"Upper Alpha": "Gornji Alpha", +"Upper Roman": "Gornji Roman", +"Anchor": "Sidro", +"Name": "Ime", +"Id": "id", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "Id treba da po\u010dinje slovom, pra\u0107eno samo slovima, brojevima, crticama, ta\u010dkama, dvota\u010dkom ili donjim crtama", +"You have unsaved changes are you sure you want to navigate away?": "Imate nesa\u010duvane promene dali ste sigurni da \u017eelite da iza\u0111ete?", +"Restore last draft": "Vrati poslednji nacrt", +"Special character": "Specijalni karakter", +"Source code": "Izvorni kod", +"Insert\/Edit code sample": "Dodaj\/Izmeni primer koda", +"Language": "Jezik", +"Code sample": "Primer koda", +"Color": "Boja", +"R": "R", +"G": "G", +"B": "B", +"Left to right": "Sa leve na desnu", +"Right to left": "Sa desne na levu", +"Emoticons": "Smajliji", +"Document properties": "Postavke dokumenta", +"Title": "Naslov", +"Keywords": "Klju\u010dne re\u010di", +"Description": "Opis", +"Robots": "Roboti", +"Author": "Autor", +"Encoding": "Kodiranje", +"Fullscreen": "Pun ekran", +"Action": "Akcija", +"Shortcut": "Pre\u010dica", +"Help": "Pomo\u0107", +"Address": "Adresa", +"Focus to menubar": "Fokus na meni", +"Focus to toolbar": "Fokus na traku sa alatima", +"Focus to element path": "Fokus na putanju elementa", +"Focus to contextual toolbar": "Fokus na kontekstualnu traku alata", +"Insert link (if link plugin activated)": "Dodaj link (ukoliko je link dodatak aktiviran)", +"Save (if save plugin activated)": "Sa\u010duvaj (ukoliko je sa\u010duvaj dodatak aktiviran)", +"Find (if searchreplace plugin activated)": "Prona\u0111i (ako je dodatak pretrage i zamene aktiviran)", +"Plugins installed ({0}):": "Dodataka instalirano ({0}):", +"Premium plugins:": "Premium dodaci", +"Learn more...": "Saznaj vi\u0161e", +"You are using {0}": "Koristite {0}", +"Plugins": "Dadaci", +"Handy Shortcuts": "Prakti\u010dne pre\u010dice", +"Horizontal line": "Horizontalna linija", +"Insert\/edit image": "Ubaci\/Promeni sliku", +"Image description": "Opis slike", +"Source": "Izvor", +"Dimensions": "Dimenzije", +"Constrain proportions": "Ograni\u010dene proporcije", +"General": "Op\u0161te", +"Advanced": "Napredno", +"Style": "Stil", +"Vertical space": "Vertikalni razmak", +"Horizontal space": "Horizontalni razmak", +"Border": "Okvir", +"Insert image": "Ubaci sliku", +"Image": "Slika", +"Image list": "Lista slika", +"Rotate counterclockwise": "Rotiraj levo", +"Rotate clockwise": "Rotiraj desno", +"Flip vertically": "Okreni vertikalno", +"Flip horizontally": "Okreni horizontalno", +"Edit image": "Izmeni sliku", +"Image options": "Opcije slike", +"Zoom in": "Uve\u0107aj", +"Zoom out": "Umanji", +"Crop": "Izeci", +"Resize": "Promeni veli\u010dinu", +"Orientation": "Orijentacija", +"Brightness": "Osvetljenje", +"Sharpen": "Izo\u0161travanje", +"Contrast": "Kontrast", +"Color levels": "Nivo boja", +"Gamma": "Gama", +"Invert": "Izokreni", +"Apply": "Primeni", +"Back": "Nazad", +"Insert date\/time": "Ubaci datum\/vreme", +"Date\/time": "Datum\/vreme", +"Insert link": "Ubaci vezu", +"Insert\/edit link": "Ubaci\/promeni vezu", +"Text to display": "Tekst za prikaz", +"Url": "Url", +"Target": "Meta", +"None": "Ni\u0161ta", +"New window": "Novi prozor", +"Remove link": "Ukloni link", +"Anchors": "sidro", +"Link": "Link", +"Paste or type a link": "Nalepi ili ukucaj link", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "Izgleda da je URL koji ste uneli email adresa. Da li \u017eelite da dodate zahtevani mailto: prefiks?", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "Izgleda da je URL koji ste uneli spolja\u0161nja veza. Da li \u017eelite da dodate zahtevani http:\/\/ prefiks?", +"Link list": "Lista veza", +"Insert video": "Ubaci video", +"Insert\/edit video": "Ubaci\/promeni video", +"Insert\/edit media": "Ubaci\/izmeni mediju", +"Alternative source": "Alternativni izvor", +"Poster": "Poster", +"Paste your embed code below:": "Nalepite ugra\u0111eni kod ispod:", +"Embed": "Ugra\u0111eno", +"Media": "Medija", +"Nonbreaking space": "bez ramaka", +"Page break": "Lomljenje stranice", +"Paste as text": "Nalepi kao tekst", +"Preview": "Pregled", +"Print": "\u0160tampanje", +"Save": "Sa\u010duvati", +"Find": "Na\u0111i", +"Replace with": "Zameni sa", +"Replace": "Zameni", +"Replace all": "Zameni sve", +"Prev": "Prethodni", +"Next": "Slede\u0107i", +"Find and replace": "Na\u0111i i zameni", +"Could not find the specified string.": "Nije mogu\u0107e prona\u0107i navedeni niz.", +"Match case": "Predmet za upore\u0111ivanje", +"Whole words": "Cele re\u010di", +"Spellcheck": "Provera pravopisa", +"Ignore": "ignori\u0161i", +"Ignore all": "Ignori\u0161i sve", +"Finish": "Kraj", +"Add to Dictionary": "Dodaj u re\u010dnik", +"Insert table": "ubaci tabelu", +"Table properties": "Postavke tabele", +"Delete table": "Obri\u0161i tabelu", +"Cell": "\u0106elija", +"Row": "Red", +"Column": "Kolona", +"Cell properties": "Postavke \u0107elije", +"Merge cells": "Spoji \u0107elije", +"Split cell": "Razdvoji \u0107elije", +"Insert row before": "Ubaci red pre", +"Insert row after": "Ubaci red posle", +"Delete row": "Obri\u0161i red", +"Row properties": "Postavke reda", +"Cut row": "Iseci red", +"Copy row": "Kopiraj red", +"Paste row before": "Nalepi red pre", +"Paste row after": "Nalepi red posle", +"Insert column before": "Ubaci kolonu pre", +"Insert column after": "Ubaci kolonu posle", +"Delete column": "Obri\u0161i kolonu", +"Cols": "Kolone", +"Rows": "Redovi", +"Width": "\u0160irina", +"Height": "Visina", +"Cell spacing": "Prostor \u0107elije", +"Cell padding": "Razmak \u0107elije", +"Caption": "Natpis", +"Left": "Levo", +"Center": "Centar", +"Right": "Desno", +"Cell type": "Tip \u0107elije", +"Scope": "Obim", +"Alignment": "Svrstavanje", +"H Align": "Horizontalno poravnanje", +"V Align": "Vertikalno poravnanje", +"Top": "Vrh", +"Middle": "Sredina", +"Bottom": "Podno\u017eje", +"Header cell": "Visina \u0107elije", +"Row group": "Grupa reda", +"Column group": "Grupa kolone", +"Row type": "Tip reda", +"Header": "Zaglavlje", +"Body": "Telo", +"Footer": "Podno\u017eje", +"Border color": "Boja ivice", +"Insert template": "Ubaci \u0161ablon", +"Templates": "\u0160abloni", +"Template": "\u0160ablon", +"Text color": "Boja tekst", +"Background color": "Boja pozadine", +"Custom...": "Posebno...", +"Custom color": "Posebna boja", +"No color": "Bez boje", +"Table of Contents": "Tabela sadr\u017eaja", +"Show blocks": "Prikaz blokova", +"Show invisible characters": "Prika\u017ei nevidljive karaktere", +"Words: {0}": "Re\u010di:{0}", +"{0} words": "{0} re\u010di", +"File": "Datoteka", +"Edit": "Ure\u0111ivanje", +"Insert": "Umetni", +"View": "Prikaz", +"Format": "Format", +"Table": "Tabela", +"Tools": "Alatke", +"Powered by {0}": "Pokre\u0107e ga {0}", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Oboga\u0107en tekst. Pritisni te ALT-F9 za meni.Pritisnite ALT-F10 za traku sa alatkama.Pritisnite ALT-0 za pomo\u0107" +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/sv.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/sv.js deleted file mode 100644 index a2a3d77ffd..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/sv.js +++ /dev/null @@ -1 +0,0 @@ -tinyMCE.addI18n({sv:{common:{"more_colors":"Fler f\u00e4rger","invalid_data":"Fel: Inkorrekta v\u00e4rden har matats in, dessa \u00e4r markerade i r\u00f6tt.","popup_blocked":"Popup blockerare detekterad. St\u00e4ng av den s\u00e5 att dialogerna kan \u00f6ppnas.","clipboard_no_support":"Funktionen \u00e4r inte tillg\u00e4nglig i din webbl\u00e4sare, anv\u00e4nd tangentbordsgenv\u00e4garna i st\u00e4llet.","clipboard_msg":"Kopiera/klipp ut/klistra in \u00e4r inte tillg\u00e4ngligt i din webbl\u00e4sare.\nVill du veta mer?","not_set":"-- Inte satt --","class_name":"Klass",browse:"Bl\u00e4ddra",close:"St\u00e4ng",cancel:"Avbryt",update:"Uppdatera",insert:"Infoga",apply:"Applicera","edit_confirm":"Vill du anv\u00e4nda WYSIWYG f\u00f6r denna textarea?","invalid_data_number":"{#field} m\u00e5ste vara ett nummer","invalid_data_min":"{#field} m\u00e5ste vara ett nummer st\u00f6rren \u00e4n {#min}","invalid_data_size":"{#field} m\u00e5ste vara ett nummer eller i procent",value:"(V\u00e4rde)"},contextmenu:{full:"Utfyllnad",right:"H\u00f6ger",center:"Centrerad",left:"V\u00e4nster",align:"Justering"},insertdatetime:{"day_short":"S\u00f6n,M\u00e5n,Tis,Ons,Tors,Fre,L\u00f6r,S\u00f6n","day_long":"S\u00f6ndag,M\u00e5ndag,Tisdag,Onsdag,Torsdag,Fredag,L\u00f6rdag,S\u00f6ndag","months_short":"Jan,Feb,Mar,Apr,Maj,Jun,Jul,Aug,Sep,Okt,Nov,Dec","months_long":"Januari,Februari,Mars,April,Maj,Juni,Juli,Augusti,September,Oktober,November,December","inserttime_desc":"Infoga tid","insertdate_desc":"Infoga datum","time_fmt":"%H:%M:%S","date_fmt":"%Y-%m-%d "},print:{"print_desc":"Skriv ut"},preview:{"preview_desc":"F\u00f6rhandsgranska"},directionality:{"rtl_desc":"Skriftl\u00e4ge - h\u00f6ger till v\u00e4nster","ltr_desc":"Skriftl\u00e4ge - v\u00e4nster till h\u00f6ger"},layer:{content:"Nytt lager...","absolute_desc":"Sl\u00e5 av/p\u00e5 absolut positionering","backward_desc":"Flytta bak\u00e5t","forward_desc":"Flytta fram\u00e5t","insertlayer_desc":"Infoga nytt lager"},save:{"save_desc":"Spara","cancel_desc":"Hoppa \u00f6ver alla f\u00f6r\u00e4ndringar"},nonbreaking:{"nonbreaking_desc":"Infoga icke radbrytande mellanslag"},iespell:{download:"ieSpell kunde inte hittas, vill du installera denna nu?","iespell_desc":"R\u00e4ttstava"},advhr:{"advhr_desc":"Horisontell skiljelinje","delta_height":"","delta_width":""},emotions:{"emotions_desc":"Smileys","delta_height":"","delta_width":""},searchreplace:{"replace_desc":"S\u00f6k/ers\u00e4tt","search_desc":"S\u00f6k","delta_width":"","delta_height":""},advimage:{"image_desc":"Infoga/redigera bild","delta_width":"","delta_height":""},advlink:{"link_desc":"Infoga/redigera l\u00e4nk","delta_height":"","delta_width":""},xhtmlxtras:{"attribs_desc":"Redigera attribut","ins_desc":"Markera som tillagt","del_desc":"Markera som struket","acronym_desc":"Akronym","abbr_desc":"F\u00f6rkortning","cite_desc":"citat","attribs_delta_height":"","attribs_delta_width":"","ins_delta_height":"","ins_delta_width":"","del_delta_height":"","del_delta_width":"","acronym_delta_height":"","acronym_delta_width":"","abbr_delta_height":"","abbr_delta_width":"","cite_delta_height":"","cite_delta_width":""},style:{desc:"Redigera inline CSS","delta_height":"","delta_width":""},paste:{"plaintext_mode":"Inklistring \u00e4r nu i textl\u00e4ge.","plaintext_mode_sticky":"Inklistring \u00e4r nu i textl\u00e4ge. Efter att du klistrat in kommer den att \u00e5terg\u00e5 till normall\u00e4ge.","selectall_desc":"Markera allt","paste_word_desc":"Klistra in fr\u00e5n Word","paste_text_desc":"Klistra in som text"},"paste_dlg":{"word_title":"Anv\u00e4nd ctrl-v p\u00e5 ditt tangentbord f\u00f6r att klistra in i detta f\u00f6nster.","text_linebreaks":"Spara radbrytningar","text_title":"Anv\u00e4nd ctrl-v p\u00e5 ditt tangentbord f\u00f6r att klistra in i detta f\u00f6nster."},table:{cell:"Cell",col:"Kolumn",row:"Rad",del:"Radera tabell","copy_row_desc":"Klistra in rad","cut_row_desc":"Klipp ut rad","paste_row_after_desc":"Klistra in rad efter","paste_row_before_desc":"Klistra in rad ovanf\u00f6r","props_desc":"Tabellinst\u00e4llningar","cell_desc":"Tabellcellsinst\u00e4llningar","row_desc":"Tabellradsinst\u00e4llningar","merge_cells_desc":"Sammanfoga celler","split_cells_desc":"Separera sammansatta celler","delete_col_desc":"Radera kolumn","col_after_desc":"Infoga kolumn efter","col_before_desc":"Infoga kolumn f\u00f6re","delete_row_desc":"Radera rad","row_after_desc":"Infoga ny rad efter","row_before_desc":"Infoga ny rad f\u00f6re",desc:"Infoga/redigera ny tabell","merge_cells_delta_height":"","merge_cells_delta_width":"","table_delta_height":"","table_delta_width":"","cellprops_delta_height":"","cellprops_delta_width":"","rowprops_delta_height":"","rowprops_delta_width":""},autosave:{"warning_message":"Om du \u00e5terskapar inneh\u00e5ll s\u00e5 kommer det nuvarande inneh\u00e5llet i f\u00e4ltet att raderas.\n\n\u00c4r du s\u00e4ker p\u00e5 att du vill g\u00f6ra detta?","restore_content":"\u00c5terskapa automatiskt sparat inneh\u00e5ll.","unload_msg":"De f\u00f6r\u00e4ndringar som du gjort kommer att g\u00e5 f\u00f6rlorade om du l\u00e4mnar sidan."},fullscreen:{desc:"Sl\u00e5 av/p\u00e5 fullsk\u00e4rmsl\u00e4ge"},media:{edit:"Redigera inb\u00e4ddad media",desc:"Infoga/redigera inb\u00e4ddad media","delta_height":"","delta_width":""},fullpage:{desc:"Dokumentinst\u00e4llningar","delta_width":"","delta_height":""},template:{desc:"Infoga en f\u00e4rdig mall"},visualchars:{desc:"Visa osynliga tecken"},spellchecker:{desc:"Sl\u00e5 av/p\u00e5 r\u00e4ttstavningskontroll",menu:"R\u00e4ttstavningsinst\u00e4llningar","ignore_word":"Ignorera ord","ignore_words":"Ignorera alla",langs:"Spr\u00e5k",wait:"Var god v\u00e4nta...",sug:"F\u00f6rslag","no_sug":"Inga f\u00f6rslag","no_mpell":"Inga felstavningar funna.","learn_word":"L\u00e4r ord"},pagebreak:{desc:"Infoga sidbrytning"},advlist:{types:"Typer",def:"Standard","lower_alpha":"Lower alpha","lower_greek":"Lower greek","lower_roman":"Lower roman","upper_alpha":"Upper alpha","upper_roman":"Upper roman",circle:"Cirkel",disc:"Disc",square:"Fyrkant"},colors:{"333300":"M\u00f6rkoliv","993300":"Br\u00e4ndorange","000000":"Svart","003300":"M\u00f6rkgr\u00f6n","003366":"M\u00f6rkazur","000080":"Marinbl\u00e5","333399":"Indigo","333333":"Mycket m\u00f6rkgr\u00e5","800000":"R\u00f6dbrun",FF6600:"Orange","808000":"Oliv","008000":"Gr\u00f6n","008080":"Kricka","0000FF":"Bl\u00e5","666699":"Gr\u00e5bl\u00e5","808080":"Gr\u00e5",FF0000:"R\u00f6d",FF9900:"B\u00e4rnsten","99CC00":"Gulgr\u00f6n","339966":"Havsbl\u00e5","33CCCC":"Turkos","3366FF":"Kungligtbl\u00e5tt","800080":"Lila","999999":"Medelgr\u00e5",FF00FF:"Magenta",FFCC00:"Guld",FFFF00:"Gul","00FF00":"Lime","00FFFF":"Vatten","00CCFF":"Himmelsbl\u00e5","993366":"Brun",C0C0C0:"Silver",FF99CC:"Rosa",FFCC99:"Periska",FFFF99:"Ljusgul",CCFFCC:"Blekgr\u00f6n",CCFFFF:"Blekcyan","99CCFF":"Ljus himmel",CC99FF:"Plommon",FFFFFF:"Vitt"},aria:{"rich_text_area":"Redigeringsarea"},wordcount:{words:"Ord:"}}}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/sv_SE.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/sv_SE.js new file mode 100644 index 0000000000..83aaaef59d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/sv_SE.js @@ -0,0 +1,261 @@ +tinymce.addI18n('sv_SE',{ +"Redo": "G\u00f6r om", +"Undo": "\u00c5ngra", +"Cut": "Klipp ut", +"Copy": "Kopiera", +"Paste": "Klistra in", +"Select all": "Markera allt", +"New document": "Nytt dokument", +"Ok": "Ok", +"Cancel": "Avbryt", +"Visual aids": "Visuella hj\u00e4lpmedel", +"Bold": "Fetstil", +"Italic": "Kursiv stil", +"Underline": "Understruken", +"Strikethrough": "Genomstruken", +"Superscript": "Upph\u00f6jd text", +"Subscript": "Neds\u00e4nkt text", +"Clear formatting": "Avformatera", +"Align left": "V\u00e4nsterst\u00e4ll", +"Align center": "Centrera", +"Align right": "H\u00f6gerst\u00e4ll", +"Justify": "Justera", +"Bullet list": "Punktlista", +"Numbered list": "Nummerlista", +"Decrease indent": "Minska indrag", +"Increase indent": "\u00d6ka indrag", +"Close": "St\u00e4ng", +"Formats": "Format", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "Din browser st\u00f6djer inte direkt \u00e5tkomst till klippboken. V\u00e4nligen anv\u00e4nd kortkommandona Ctrl+X\/C\/V i st\u00e4llet.", +"Headers": "Rubriker", +"Header 1": "Rubrik 1", +"Header 2": "Rubrik 2", +"Header 3": "Rubrik 3", +"Header 4": "Rubrik 4", +"Header 5": "Rubrik 5", +"Header 6": "Rubrik 6", +"Headings": "Rubriker", +"Heading 1": "Rubrik 1", +"Heading 2": "Rubrik 2", +"Heading 3": "Rubrik 3", +"Heading 4": "Rubrik 4", +"Heading 5": "Rubrik 5", +"Heading 6": "Rubrik 6", +"Preformatted": "Preformaterad", +"Div": "Div", +"Pre": "F\u00f6rformaterad", +"Code": "Kod", +"Paragraph": "Br\u00f6dtext", +"Blockquote": "Blockcitat", +"Inline": "Inline", +"Blocks": "Block", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "Klistra in \u00e4r nu i textl\u00e4ge. Inneh\u00e5ll kommer att konverteras till text tills du sl\u00e5r av detta l\u00e4ge.", +"Font Family": "Teckensnitt", +"Font Sizes": "Storlek", +"Class": "Klass", +"Browse for an image": "Bl\u00e4ddra efter en bild", +"OR": "ELLER", +"Drop an image here": "Sl\u00e4pp en bild h\u00e4r", +"Upload": "Ladda upp", +"Block": "Block", +"Align": "Justera", +"Default": "Original", +"Circle": "Cirkel", +"Disc": "Disk", +"Square": "Fyrkant", +"Lower Alpha": "Gemener", +"Lower Greek": "Grekiska gemener", +"Lower Roman": "Romerska gemener", +"Upper Alpha": "Versaler", +"Upper Roman": "Romerska versaler", +"Anchor": "Ankare", +"Name": "Namn", +"Id": "Id", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "Id skall b\u00f6rja med en bokstav och f\u00f6ljande tecken ska vara bokst\u00e4ver, nummer, punkter, understr\u00e4ck eller kolon.", +"You have unsaved changes are you sure you want to navigate away?": "Du har f\u00f6r\u00e4ndringar som du inte har sparat. \u00c4r du s\u00e4ker p\u00e5 att du vill navigera vidare?", +"Restore last draft": "\u00c5terst\u00e4ll senaste utkast", +"Special character": "Specialtecken", +"Source code": "K\u00e4llkod", +"Insert\/Edit code sample": "Infoga\/Redigera k\u00e5d exempel", +"Language": "Spr\u00e5k", +"Code sample": "K\u00e5dexempel", +"Color": "F\u00e4rg", +"R": "R", +"G": "G", +"B": "B", +"Left to right": "V\u00e4nster till h\u00f6ger", +"Right to left": "H\u00f6ger till v\u00e4nster", +"Emoticons": "Emoticons", +"Document properties": "Dokumentegenskaper", +"Title": "Titel", +"Keywords": "Nyckelord", +"Description": "Beskrivning", +"Robots": "Robotar", +"Author": "F\u00f6rfattare", +"Encoding": "Encoding", +"Fullscreen": "Fullsk\u00e4rm", +"Action": "H\u00e4ndelse", +"Shortcut": "Kortkommando", +"Help": "Hj\u00e4lp", +"Address": "Adress", +"Focus to menubar": "Fokusera p\u00e5 menyrad", +"Focus to toolbar": "Fokusera p\u00e5 verktygsrad", +"Focus to element path": "Fokusera p\u00e5 elements\u00f6kv\u00e4gsrad", +"Focus to contextual toolbar": "Fokusera p\u00e5 den kontextuella verktygsraden", +"Insert link (if link plugin activated)": "Infoga l\u00e4nk (om link-pluginet \u00e4r aktiverat)", +"Save (if save plugin activated)": "Spara (om save-pluginet \u00e4r aktiverat)", +"Find (if searchreplace plugin activated)": "S\u00f6k (om searchreplace-pluginet \u00e4r aktiverat)", +"Plugins installed ({0}):": "Installerade plugins ({0}):", +"Premium plugins:": "Premiumplugins:", +"Learn more...": "L\u00e4s mer...", +"You are using {0}": "Du anv\u00e4nder {0}", +"Plugins": "Plugins", +"Handy Shortcuts": "Kortkommandon", +"Horizontal line": "Horisontell linje", +"Insert\/edit image": "Infoga\/redigera bild", +"Image description": "Bildbeskrivning", +"Source": "K\u00e4lla", +"Dimensions": "Dimensioner", +"Constrain proportions": "Begr\u00e4nsa proportioner", +"General": "Generella", +"Advanced": "Avancerat", +"Style": "Stil", +"Vertical space": "Vertikaltutrymme", +"Horizontal space": "Horisontellt utrymme", +"Border": "Ram", +"Insert image": "Infoga bild", +"Image": "Bild", +"Image list": "Bildlista", +"Rotate counterclockwise": "Rotera moturs", +"Rotate clockwise": "Rotera medurs", +"Flip vertically": "Spegelv\u00e4nd vertikalt", +"Flip horizontally": "Spegelv\u00e4nd horisontellt", +"Edit image": "Redigera bild", +"Image options": "Bild inst\u00e4llningar", +"Zoom in": "Zooma in", +"Zoom out": "Zooma ut", +"Crop": "Besk\u00e4r", +"Resize": "Skala om", +"Orientation": "Orientera", +"Brightness": "Ljusstyrka", +"Sharpen": "Sk\u00e4rpa", +"Contrast": "Kontrast", +"Color levels": "F\u00e4rgniv\u00e5er", +"Gamma": "Gamma", +"Invert": "Invertera", +"Apply": "Applicera", +"Back": "Tillbaka", +"Insert date\/time": "Infoga datum\/tid", +"Date\/time": "Datum\/tid", +"Insert link": "Infoga l\u00e4nk", +"Insert\/edit link": "Infoga\/redigera l\u00e4nk", +"Text to display": "Text att visa", +"Url": "Url", +"Target": "M\u00e5l", +"None": "Ingen", +"New window": "Nytt f\u00f6nster", +"Remove link": "Ta bort l\u00e4nk", +"Anchors": "Bokm\u00e4rken", +"Link": "L\u00e4nk", +"Paste or type a link": "Klistra in eller skriv en l\u00e4nk", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "Urlen du angav verkar vara en epost adress. Vill du l\u00e4gga till ett mailto: prefix?", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "Urlen du angav verkar vara en extern l\u00e4nk. Vill du l\u00e4gga till http:\/\/ prefixet?", +"Link list": "L\u00e4nklista", +"Insert video": "Infoga video", +"Insert\/edit video": "Infoga\/redigera video", +"Insert\/edit media": "Infoga\/redigera media", +"Alternative source": "Alternativ k\u00e4lla", +"Poster": "Affish", +"Paste your embed code below:": "Klistra in din inb\u00e4ddningskod nedan:", +"Embed": "Inb\u00e4ddning", +"Media": "Media", +"Nonbreaking space": "Avbrottsfritt mellanrum", +"Page break": "Sidbrytning", +"Paste as text": "Klistra in som text", +"Preview": "F\u00f6rhandsgranska", +"Print": "Skriv ut", +"Save": "Spara", +"Find": "S\u00f6k", +"Replace with": "Ers\u00e4tt med", +"Replace": "Ers\u00e4tt", +"Replace all": "Ers\u00e4tt alla", +"Prev": "F\u00f6reg\u00e5ende", +"Next": "N\u00e4sta", +"Find and replace": "S\u00f6k och ers\u00e4tt", +"Could not find the specified string.": "Kunde inte hitta den specifierade st\u00e4ngen.", +"Match case": "Matcha gemener\/versaler", +"Whole words": "Hela ord", +"Spellcheck": "R\u00e4ttstava", +"Ignore": "Ignorera", +"Ignore all": "Ignorera alla", +"Finish": "Avsluta", +"Add to Dictionary": "L\u00e4gg till i ordlista", +"Insert table": "Infoga tabell", +"Table properties": "Tabellegenskaper", +"Delete table": "Radera tabell", +"Cell": "Cell", +"Row": "Rad", +"Column": "Kolumn", +"Cell properties": "Cellegenskaper", +"Merge cells": "Sammanfoga celler", +"Split cell": "Bryt is\u00e4r celler", +"Insert row before": "Infoga rad f\u00f6re", +"Insert row after": "Infoga rad efter", +"Delete row": "Radera rad", +"Row properties": "Radegenskaper", +"Cut row": "Klipp ut rad", +"Copy row": "Kopiera rad", +"Paste row before": "Klista in rad f\u00f6re", +"Paste row after": "Klistra in rad efter", +"Insert column before": "Infoga kolumn f\u00f6re", +"Insert column after": "Infoga kolumn efter", +"Delete column": "Radera kolumn", +"Cols": "Kolumner", +"Rows": "Rader", +"Width": "Bredd", +"Height": "H\u00f6jd", +"Cell spacing": "Cellmellanrum", +"Cell padding": "Cellpaddning", +"Caption": "Rubrik", +"Left": "V\u00e4nster", +"Center": "Centrum", +"Right": "H\u00f6ger", +"Cell type": "Celltyp", +"Scope": "Omf\u00e5ng", +"Alignment": "Justering", +"H Align": "H-justering", +"V Align": "V-justering", +"Top": "Toppen", +"Middle": "Mitten", +"Bottom": "Botten", +"Header cell": "Huvudcell", +"Row group": "Radgrupp", +"Column group": "Kolumngrupp", +"Row type": "Radtyp", +"Header": "Huvud", +"Body": "Kropp", +"Footer": "Fot", +"Border color": "Ramf\u00e4rg", +"Insert template": "Infoga mall", +"Templates": "Mallar", +"Template": "Mall", +"Text color": "Textf\u00e4rg", +"Background color": "Bakgrundsf\u00e4rg", +"Custom...": "Anpassad...", +"Custom color": "Anpassad f\u00e4rg", +"No color": "Ingen f\u00e4rg", +"Table of Contents": "Inneh\u00e5llsf\u00f6rteckning", +"Show blocks": "Visa block", +"Show invisible characters": "Visa onsynliga tecken", +"Words: {0}": "Ord: {0}", +"{0} words": "{0} ord", +"File": "Fil", +"Edit": "Redigera", +"Insert": "Infoga", +"View": "Visa", +"Format": "Format", +"Table": "Tabell", +"Tools": "Verktyg", +"Powered by {0}": "Powered by {0}", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Textredigerare. Tryck ALT-F9 f\u00f6r menyn. Tryck ALT-F10 f\u00f6r verktygsrader. Tryck ALT-0 f\u00f6r hj\u00e4lp." +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/ta.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/ta.js new file mode 100644 index 0000000000..6d0736cf72 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/ta.js @@ -0,0 +1,261 @@ +tinymce.addI18n('ta',{ +"Redo": "\u0bae\u0bc0\u0ba3\u0bcd\u0b9f\u0bc1\u0bae\u0bcd \u0b9a\u0bc6\u0baf\u0bcd\u0b95", +"Undo": "\u0b9a\u0bc6\u0baf\u0bb2\u0bcd\u0ba4\u0bb5\u0bbf\u0bb0\u0bcd\u0b95\u0bcd\u0b95", +"Cut": "\u0bb5\u0bc6\u0b9f\u0bcd\u0b9f\u0bc1\u0b95", +"Copy": "\u0ba8\u0b95\u0bb2\u0bc6\u0b9f\u0bc1\u0b95\u0bcd\u0b95", +"Paste": "\u0b92\u0b9f\u0bcd\u0b9f\u0bc1\u0b95", +"Select all": "\u0b85\u0ba9\u0bc8\u0ba4\u0bcd\u0ba4\u0bc8\u0baf\u0bc1\u0bae\u0bcd \u0ba4\u0bc7\u0bb0\u0bcd\u0bb5\u0bc1 \u0b9a\u0bc6\u0baf\u0bcd\u0b95", +"New document": "\u0baa\u0bc1\u0ba4\u0bbf\u0baf \u0b86\u0bb5\u0ba3\u0bae\u0bcd", +"Ok": "\u0b9a\u0bb0\u0bbf", +"Cancel": "\u0bb0\u0ba4\u0bcd\u0ba4\u0bc1 \u0b9a\u0bc6\u0baf\u0bcd\u0b95", +"Visual aids": "\u0b95\u0bbe\u0b9f\u0bcd\u0b9a\u0bbf\u0ba4\u0bcd \u0ba4\u0bc1\u0ba3\u0bc8\u0baf\u0ba9\u0bcd\u0b95\u0bb3\u0bcd", +"Bold": "\u0ba4\u0b9f\u0bbf\u0baa\u0bcd\u0baa\u0bc1", +"Italic": "\u0b9a\u0bbe\u0baf\u0bcd\u0bb5\u0bc1", +"Underline": "\u0b85\u0b9f\u0bbf\u0b95\u0bcd\u0b95\u0bcb\u0b9f\u0bc1", +"Strikethrough": "\u0ba8\u0b9f\u0bc1\u0b95\u0bcd\u0b95\u0bcb\u0b9f\u0bc1", +"Superscript": "\u0bae\u0bc7\u0bb2\u0bcd\u0b92\u0b9f\u0bcd\u0b9f\u0bc1", +"Subscript": "\u0b95\u0bc0\u0bb4\u0bcd\u0b92\u0b9f\u0bcd\u0b9f\u0bc1", +"Clear formatting": "\u0bb5\u0b9f\u0bbf\u0bb5\u0bae\u0bc8\u0baa\u0bcd\u0baa\u0bc1 \u0b85\u0bb4\u0bbf\u0b95\u0bcd\u0b95", +"Align left": "\u0b87\u0b9f\u0ba4\u0bc1 \u0b9a\u0bc0\u0bb0\u0bae\u0bc8", +"Align center": "\u0bae\u0bc8\u0baf \u0b9a\u0bc0\u0bb0\u0bae\u0bc8", +"Align right": "\u0bb5\u0bb2\u0ba4\u0bc1 \u0b9a\u0bc0\u0bb0\u0bae\u0bc8", +"Justify": "\u0ba8\u0bc7\u0bb0\u0bcd\u0ba4\u0bcd\u0ba4\u0bbf \u0b9a\u0bc6\u0baf\u0bcd\u0b95", +"Bullet list": "\u0baa\u0bca\u0b9f\u0bcd\u0b9f\u0bbf\u0b9f\u0baa\u0bcd\u0baa\u0b9f\u0bcd\u0b9f \u0baa\u0b9f\u0bcd\u0b9f\u0bbf\u0baf\u0bb2\u0bcd", +"Numbered list": "\u0b8e\u0ba3\u0bcd\u0ba3\u0bbf\u0b9f\u0baa\u0bcd\u0baa\u0b9f\u0bcd\u0b9f \u0baa\u0b9f\u0bcd\u0b9f\u0bbf\u0baf\u0bb2\u0bcd", +"Decrease indent": "\u0b89\u0bb3\u0bcd\u0ba4\u0bb3\u0bcd\u0bb3\u0bc1\u0ba4\u0bb2\u0bc8 \u0b95\u0bc1\u0bb1\u0bc8\u0b95\u0bcd\u0b95", +"Increase indent": "\u0b89\u0bb3\u0bcd\u0ba4\u0bb3\u0bcd\u0bb3\u0bc1\u0ba4\u0bb2\u0bc8 \u0b85\u0ba4\u0bbf\u0b95\u0bb0\u0bbf\u0b95\u0bcd\u0b95", +"Close": "\u0bae\u0bc2\u0b9f\u0bc1\u0b95", +"Formats": "\u0bb5\u0b9f\u0bbf\u0bb5\u0bae\u0bc8\u0baa\u0bcd\u0baa\u0bc1\u0b95\u0bb3\u0bcd", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "\u0ba8\u0b95\u0bb2\u0b95\u0ba4\u0bcd\u0ba4\u0bbf\u0bb1\u0bcd\u0b95\u0bc1 \u0ba8\u0bc7\u0bb0\u0b9f\u0bbf \u0b85\u0ba3\u0bc1\u0b95\u0bb2\u0bc8 \u0ba4\u0b99\u0bcd\u0b95\u0bb3\u0bcd \u0b89\u0bb2\u0bbe\u0bb5\u0bbf \u0b86\u0ba4\u0bb0\u0bbf\u0b95\u0bcd\u0b95\u0bb5\u0bbf\u0bb2\u0bcd\u0bb2\u0bc8. \u0b86\u0b95\u0bb5\u0bc7 \u0bb5\u0bbf\u0b9a\u0bc8\u0baa\u0bcd\u0baa\u0bb2\u0b95\u0bc8 \u0b95\u0bc1\u0bb1\u0bc1\u0b95\u0bcd\u0b95\u0bc1\u0bb5\u0bb4\u0bbf\u0b95\u0bb3\u0bbe\u0ba9 Ctrl+X\/C\/V \u0b87\u0bb5\u0bb1\u0bcd\u0bb1\u0bc8 \u0ba4\u0baf\u0bb5\u0bc1 \u0b9a\u0bc6\u0baf\u0bcd\u0ba4\u0bc1 \u0baa\u0baf\u0ba9\u0bcd\u0baa\u0b9f\u0bc1\u0ba4\u0bcd\u0ba4\u0bc1\u0b95.", +"Headers": "\u0ba4\u0bb2\u0bc8\u0baa\u0bcd\u0baa\u0bc1\u0b95\u0bb3\u0bcd", +"Header 1": "\u0ba4\u0bb2\u0bc8\u0baa\u0bcd\u0baa\u0bc1 1", +"Header 2": "\u0ba4\u0bb2\u0bc8\u0baa\u0bcd\u0baa\u0bc1 2", +"Header 3": "\u0ba4\u0bb2\u0bc8\u0baa\u0bcd\u0baa\u0bc1 3", +"Header 4": "\u0ba4\u0bb2\u0bc8\u0baa\u0bcd\u0baa\u0bc1 4", +"Header 5": "\u0ba4\u0bb2\u0bc8\u0baa\u0bcd\u0baa\u0bc1 5", +"Header 6": "\u0ba4\u0bb2\u0bc8\u0baa\u0bcd\u0baa\u0bc1 6", +"Headings": "\u0ba4\u0bb2\u0bc8\u0baa\u0bcd\u0baa\u0bc1\u0b95\u0bb3\u0bcd", +"Heading 1": "\u0ba4\u0bb2\u0bc8\u0baa\u0bcd\u0baa\u0bc1 1", +"Heading 2": "\u0ba4\u0bb2\u0bc8\u0baa\u0bcd\u0baa\u0bc1 2", +"Heading 3": "\u0ba4\u0bb2\u0bc8\u0baa\u0bcd\u0baa\u0bc1 3", +"Heading 4": "\u0ba4\u0bb2\u0bc8\u0baa\u0bcd\u0baa\u0bc1 4", +"Heading 5": "\u0ba4\u0bb2\u0bc8\u0baa\u0bcd\u0baa\u0bc1 5", +"Heading 6": "\u0ba4\u0bb2\u0bc8\u0baa\u0bcd\u0baa\u0bc1 6", +"Preformatted": "\u0bae\u0bc1\u0ba9\u0bcd\u0bb5\u0b9f\u0bbf\u0bb5\u0bae\u0bc8\u0b95\u0bcd\u0b95\u0baa\u0bcd\u0baa\u0b9f\u0bcd\u0b9f\u0ba4\u0bc1", +"Div": "\u0baa\u0bbf\u0bb0\u0bbf\u0bb5\u0bc1 (Div)", +"Pre": "\u0bae\u0bc1\u0ba9\u0bcd \u0bb5\u0b9f\u0bbf\u0bb5\u0bae\u0bc8\u0b95\u0bcd\u0b95\u0baa\u0bcd\u0baa\u0b9f\u0bcd\u0b9f\u0ba4\u0bc1 (Pre)", +"Code": "\u0b95\u0bc1\u0bb1\u0bbf\u0baf\u0bc0\u0b9f\u0bc1", +"Paragraph": "\u0baa\u0ba4\u0bcd\u0ba4\u0bbf", +"Blockquote": "\u0ba4\u0bca\u0b95\u0bc1\u0ba4\u0bbf \u0bae\u0bc7\u0bb1\u0bcd\u0b95\u0bcb\u0bb3\u0bcd", +"Inline": "\u0b89\u0bb3\u0bcd\u0bb5\u0bb0\u0bbf\u0b9a\u0bc8", +"Blocks": "\u0ba4\u0bca\u0b95\u0bc1\u0ba4\u0bbf\u0b95\u0bb3\u0bcd", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "\u0b87\u0baf\u0bb2\u0bcd\u0baa\u0bc1 \u0b89\u0bb0\u0bc8 \u0bae\u0bc1\u0bb1\u0bc8\u0bae\u0bc8\u0baf\u0bbf\u0bb2\u0bcd \u0ba4\u0bb1\u0bcd\u0baa\u0bcb\u0ba4\u0bc1 \u0b92\u0b9f\u0bcd\u0b9f\u0bc1\u0ba4\u0bb2\u0bcd \u0b89\u0bb3\u0bcd\u0bb3\u0ba4\u0bc1. \u0ba4\u0bbe\u0b99\u0bcd\u0b95\u0bb3\u0bcd \u0b87\u0ba8\u0bcd\u0ba4 \u0bb5\u0bbf\u0bb0\u0bc1\u0baa\u0bcd\u0baa\u0bc8 \u0bae\u0bbe\u0bb1\u0bcd\u0bb1\u0bc1\u0bae\u0bcd \u0bb5\u0bb0\u0bc8 \u0b89\u0bb3\u0bcd\u0bb3\u0b9f\u0b95\u0bcd\u0b95\u0b99\u0bcd\u0b95\u0bb3\u0bcd \u0b87\u0baf\u0bb2\u0bcd\u0baa\u0bc1 \u0b89\u0bb0\u0bc8\u0baf\u0bbe\u0b95 \u0b92\u0b9f\u0bcd\u0b9f\u0baa\u0bcd\u0baa\u0b9f\u0bc1\u0bae\u0bcd.", +"Font Family": "\u0b8e\u0bb4\u0bc1\u0ba4\u0bcd\u0ba4\u0bc1\u0bb0\u0bc1 \u0b95\u0bc1\u0b9f\u0bc1\u0bae\u0bcd\u0baa\u0bae\u0bcd", +"Font Sizes": "\u0b8e\u0bb4\u0bc1\u0ba4\u0bcd\u0ba4\u0bc1\u0bb0\u0bc1 \u0b85\u0bb3\u0bb5\u0bc1\u0b95\u0bb3\u0bcd", +"Class": "Class", +"Browse for an image": "\u0b92\u0bb0\u0bc1 \u0baa\u0b9f\u0ba4\u0bcd\u0ba4\u0bc1\u0b95\u0bcd\u0b95\u0bc1 \u0b89\u0bb2\u0bbe\u0bb5\u0bc1\u0b95", +"OR": "\u0b85\u0bb2\u0bcd\u0bb2\u0ba4\u0bc1", +"Drop an image here": "\u0b92\u0bb0\u0bc1 \u0baa\u0b9f\u0ba4\u0bcd\u0ba4\u0bc8 \u0b87\u0b99\u0bcd\u0b95\u0bc1 \u0b87\u0bb4\u0bc1\u0ba4\u0bcd\u0ba4\u0bc1\u0baa\u0bcd \u0baa\u0bcb\u0b9f\u0bb5\u0bc1\u0bae\u0bcd", +"Upload": "\u0baa\u0ba4\u0bbf\u0bb5\u0bc7\u0bb1\u0bcd\u0bb1\u0bc1\u0b95", +"Block": "\u0ba4\u0bca\u0b95\u0bc1\u0ba4\u0bbf", +"Align": "\u0b9a\u0bc0\u0bb0\u0bae\u0bc8", +"Default": "\u0b89\u0bb3\u0bcd\u0bb3\u0bbf\u0bb0\u0bc1\u0baa\u0bcd\u0baa\u0bc1", +"Circle": "\u0bb5\u0b9f\u0bcd\u0b9f\u0bae\u0bcd", +"Disc": "\u0bb5\u0b9f\u0bcd\u0b9f\u0bc1", +"Square": "\u0b9a\u0ba4\u0bc1\u0bb0\u0bae\u0bcd", +"Lower Alpha": "\u0b95\u0bc0\u0bb4\u0bcd \u0b8e\u0bb4\u0bc1\u0ba4\u0bcd\u0ba4\u0bc1", +"Lower Greek": "\u0b95\u0bc0\u0bb4\u0bcd \u0b95\u0bbf\u0bb0\u0bc7\u0b95\u0bcd\u0b95\u0bae\u0bcd", +"Lower Roman": "\u0b95\u0bc0\u0bb4\u0bcd \u0bb0\u0bcb\u0bae\u0bbe\u0ba9\u0bbf\u0baf\u0bae\u0bcd", +"Upper Alpha": "\u0bae\u0bc7\u0bb2\u0bcd \u0b8e\u0bb4\u0bc1\u0ba4\u0bcd\u0ba4\u0bc1", +"Upper Roman": "\u0bae\u0bc7\u0bb2\u0bcd \u0bb0\u0bcb\u0bae\u0bbe\u0ba9\u0bbf\u0baf\u0bae\u0bcd", +"Anchor": "\u0ba8\u0b99\u0bcd\u0b95\u0bc2\u0bb0\u0bae\u0bcd", +"Name": "\u0baa\u0bc6\u0baf\u0bb0\u0bcd", +"Id": "Id", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "Id \u0b86\u0ba9\u0ba4\u0bc1 \u0b92\u0bb0\u0bc1 \u0b8e\u0bb4\u0bc1\u0ba4\u0bcd\u0ba4\u0bbf\u0bb2\u0bcd \u0ba4\u0bca\u0b9f\u0b99\u0bcd\u0b95 \u0bb5\u0bc7\u0ba3\u0bcd\u0b9f\u0bc1\u0bae\u0bcd; \u0b87\u0ba4\u0ba9\u0bc8\u0ba4\u0bcd \u0ba4\u0bca\u0b9f\u0bb0\u0bcd\u0ba8\u0bcd\u0ba4\u0bc1 \u0b8e\u0bb4\u0bc1\u0ba4\u0bcd\u0ba4\u0bc1\u0b95\u0bcd\u0b95\u0bb3\u0bcd, \u0b8e\u0ba3\u0bcd\u0b95\u0bb3\u0bcd, \u0b87\u0b9f\u0bc8\u0b95\u0bcd\u0b95\u0bc7\u0bbe\u0b9f\u0bc1\u0b95\u0bb3\u0bcd (-), \u0baa\u0bc1\u0bb3\u0bcd\u0bb3\u0bbf\u0b95\u0bb3\u0bcd (.), \u0bae\u0bc1\u0b95\u0bcd\u0b95\u0bbe\u0bb1\u0bcd\u0baa\u0bc1\u0bb3\u0bcd\u0bb3\u0bbf\u0b95\u0bb3\u0bcd (:) \u0bae\u0bb1\u0bcd\u0bb1\u0bc1\u0bae\u0bcd \u0b85\u0b9f\u0bbf\u0b95\u0bcd\u0b95\u0bc7\u0bbe\u0b9f\u0bc1\u0b95\u0bb3\u0bcd (_) \u0bae\u0b9f\u0bcd\u0b9f\u0bc1\u0bae\u0bc7 \u0b87\u0bb0\u0bc1\u0ba4\u0bcd\u0ba4\u0bb2\u0bcd \u0bb5\u0bc7\u0ba3\u0bcd\u0b9f\u0bc1\u0bae\u0bcd.", +"You have unsaved changes are you sure you want to navigate away?": "\u0b9a\u0bc7\u0bae\u0bbf\u0b95\u0bcd\u0b95\u0baa\u0bcd\u0baa\u0b9f\u0bbe\u0ba4 \u0bae\u0bbe\u0bb1\u0bcd\u0bb1\u0b99\u0bcd\u0b95\u0bb3\u0bcd \u0b89\u0bb3\u0bcd\u0bb3\u0ba9; \u0ba4\u0bbe\u0b99\u0bcd\u0b95\u0bb3\u0bcd \u0b89\u0bb1\u0bc1\u0ba4\u0bbf\u0baf\u0bbe\u0b95 \u0bb5\u0bc6\u0bb3\u0bbf\u0baf\u0bc7\u0bb1 \u0bb5\u0bbf\u0bb0\u0bc1\u0bae\u0bcd\u0baa\u0bc1\u0b95\u0bbf\u0bb1\u0bc0\u0bb0\u0bcd\u0b95\u0bbe\u0bb3\u0bbe?", +"Restore last draft": "\u0b95\u0b9f\u0ba8\u0bcd\u0ba4 \u0bb5\u0bb0\u0bc8\u0bb5\u0bc8 \u0bae\u0bc0\u0b9f\u0bcd\u0b9f\u0bc6\u0b9f\u0bc1\u0b95\u0bcd\u0b95", +"Special character": "\u0b9a\u0bbf\u0bb1\u0baa\u0bcd\u0baa\u0bc1 \u0b8e\u0bb4\u0bc1\u0ba4\u0bcd\u0ba4\u0bc1\u0bb0\u0bc1", +"Source code": "\u0bae\u0bc2\u0bb2 \u0b95\u0bc1\u0bb1\u0bbf\u0baf\u0bc0\u0b9f\u0bc1", +"Insert\/Edit code sample": "\u0b95\u0bc1\u0bb1\u0bbf\u0baf\u0bc0\u0b9f\u0bc1 \u0bae\u0bbe\u0ba4\u0bbf\u0bb0\u0bbf \u0b9a\u0bc6\u0bb0\u0bc1\u0b95\u0bc1\u0b95\/\u0ba4\u0bca\u0b95\u0bc1\u0b95\u0bcd\u0b95", +"Language": "\u0bae\u0bca\u0bb4\u0bbf", +"Code sample": "\u0b95\u0bc1\u0bb1\u0bbf\u0baf\u0bc0\u0b9f\u0bc1 \u0bae\u0bbe\u0ba4\u0bbf\u0bb0\u0bbf", +"Color": "\u0ba8\u0bbf\u0bb1\u0bae\u0bcd", +"R": "R", +"G": "G", +"B": "B", +"Left to right": "\u0b87\u0b9f\u0bae\u0bbf\u0bb0\u0bc1\u0ba8\u0bcd\u0ba4\u0bc1 \u0bb5\u0bb2\u0bae\u0bcd", +"Right to left": "\u0bb5\u0bb2\u0bae\u0bbf\u0bb0\u0bc1\u0ba8\u0bcd\u0ba4\u0bc1 \u0b87\u0b9f\u0bae\u0bcd", +"Emoticons": "\u0b89\u0ba3\u0bb0\u0bcd\u0b9a\u0bcd\u0b9a\u0bbf\u0baa\u0bcd\u0baa\u0b9f\u0bbf\u0bae\u0b99\u0bcd\u0b95\u0bb3\u0bcd", +"Document properties": "\u0b86\u0bb5\u0ba3\u0ba4\u0bcd\u0ba4\u0bbf\u0ba9\u0bcd \u0baa\u0ba3\u0bcd\u0baa\u0bc1\u0b95\u0bb3\u0bcd", +"Title": "\u0ba4\u0bb2\u0bc8\u0baa\u0bcd\u0baa\u0bc1", +"Keywords": "\u0bae\u0bc1\u0ba4\u0ba9\u0bcd\u0bae\u0bc8\u0b9a\u0bcd\u0b9a\u0bca\u0bb1\u0bcd\u0b95\u0bb3\u0bcd", +"Description": "\u0bb5\u0bbf\u0bb5\u0bb0\u0bae\u0bcd", +"Robots": "\u0baa\u0bca\u0bb1\u0bbf\u0baf\u0ba9\u0bcd\u0b95\u0bb3\u0bcd (Robots)", +"Author": "\u0b8e\u0bb4\u0bc1\u0ba4\u0bcd\u0ba4\u0bbe\u0bb3\u0bb0\u0bcd", +"Encoding": "\u0b95\u0bc1\u0bb1\u0bbf\u0baf\u0bc0\u0b9f\u0bbe\u0b95\u0bcd\u0b95\u0bae\u0bcd", +"Fullscreen": "\u0bae\u0bc1\u0bb4\u0bc1\u0ba4\u0bcd\u0ba4\u0bbf\u0bb0\u0bc8", +"Action": "\u0b9a\u0bc6\u0baf\u0bb2\u0bcd", +"Shortcut": "\u0b95\u0bc1\u0bb1\u0bc1\u0b95\u0bcd\u0b95\u0bc1\u0bb5\u0bb4\u0bbf", +"Help": "\u0b89\u0ba4\u0bb5\u0bbf", +"Address": "\u0bae\u0bc1\u0b95\u0bb5\u0bb0\u0bbf", +"Focus to menubar": "\u0baa\u0b9f\u0bcd\u0b9f\u0bbf\u0baa\u0bcd\u0baa\u0b9f\u0bcd\u0b9f\u0bc8\u0baf\u0bbf\u0bb2\u0bcd \u0b95\u0bb5\u0ba9\u0bae\u0bcd \u0b9a\u0bc6\u0bb2\u0bc1\u0ba4\u0bcd\u0ba4\u0bc1\u0b95", +"Focus to toolbar": "\u0b95\u0bb0\u0bc1\u0bb5\u0bbf\u0baa\u0bcd\u0baa\u0b9f\u0bcd\u0b9f\u0bc8\u0baf\u0bbf\u0bb2\u0bcd \u0b95\u0bb5\u0ba9\u0bae\u0bcd \u0b9a\u0bc6\u0bb2\u0bc1\u0ba4\u0bcd\u0ba4\u0bc1\u0b95", +"Focus to element path": "\u0bae\u0bc2\u0bb2\u0b95\u0baa\u0bcd \u0baa\u0bbe\u0ba4\u0bc8\u0baf\u0bbf\u0bb2\u0bcd \u0b95\u0bb5\u0ba9\u0bae\u0bcd \u0b9a\u0bc6\u0bb2\u0bc1\u0ba4\u0bcd\u0ba4\u0bc1\u0b95", +"Focus to contextual toolbar": "\u0b9a\u0bc2\u0bb4\u0bcd\u0ba8\u0bbf\u0bb2\u0bc8 \u0b95\u0bb0\u0bc1\u0bb5\u0bbf\u0baa\u0bcd\u0baa\u0b9f\u0bcd\u0b9f\u0bc8\u0baf\u0bbf\u0bb2\u0bcd \u0b95\u0bb5\u0ba9\u0bae\u0bcd \u0b9a\u0bc6\u0bb2\u0bc1\u0ba4\u0bcd\u0ba4\u0bc1\u0b95", +"Insert link (if link plugin activated)": "\u0b87\u0ba3\u0bc8\u0baa\u0bcd\u0baa\u0bc1 \u0b9a\u0bc6\u0bb0\u0bc1\u0b95\u0bc1\u0b95 (\u0b87\u0ba3\u0bc8\u0baa\u0bcd\u0baa\u0bc1 \u0b9a\u0bca\u0bb0\u0bc1\u0b95\u0bbf \u0b9a\u0bc6\u0baf\u0bb2\u0bbe\u0b95\u0bcd\u0b95\u0baa\u0bcd\u0baa\u0b9f\u0bc1\u0ba4\u0bcd\u0ba4\u0bbf\u0baf\u0bbf\u0bb0\u0bc1\u0ba8\u0bcd\u0ba4\u0bbe\u0bb2\u0bcd)", +"Save (if save plugin activated)": "\u0b9a\u0bc7\u0bae\u0bbf\u0b95\u0bcd\u0b95 (\u0b9a\u0bc7\u0bae\u0bbf\u0baa\u0bcd\u0baa\u0bc1 \u0b9a\u0bca\u0bb0\u0bc1\u0b95\u0bbf \u0b9a\u0bc6\u0baf\u0bb2\u0bbe\u0b95\u0bcd\u0b95\u0baa\u0bcd\u0baa\u0b9f\u0bc1\u0ba4\u0bcd\u0ba4\u0bbf\u0baf\u0bbf\u0bb0\u0bc1\u0ba8\u0bcd\u0ba4\u0bbe\u0bb2\u0bcd)", +"Find (if searchreplace plugin activated)": "\u0b95\u0ba3\u0bcd\u0b9f\u0bc1\u0baa\u0bbf\u0b9f\u0bbf\u0b95\u0bcd\u0b95 (\u0ba4\u0bc7\u0b9f\u0bbf\u0bae\u0bbe\u0bb1\u0bcd\u0bb1\u0bb2\u0bcd \u0b9a\u0bca\u0bb0\u0bc1\u0b95\u0bbf \u0b9a\u0bc6\u0baf\u0bb2\u0bbe\u0b95\u0bcd\u0b95\u0baa\u0bcd\u0baa\u0b9f\u0bc1\u0ba4\u0bcd\u0ba4\u0bbf\u0baf\u0bbf\u0bb0\u0bc1\u0ba8\u0bcd\u0ba4\u0bbe\u0bb2\u0bcd)", +"Plugins installed ({0}):": "\u0ba8\u0bbf\u0bb1\u0bc1\u0bb5\u0baa\u0bcd\u0baa\u0b9f\u0bcd\u0b9f\u0bc1\u0bb3\u0bcd\u0bb3 \u0b9a\u0bca\u0bb0\u0bc1\u0b95\u0bbf\u0b95\u0bb3\u0bcd ({0}):", +"Premium plugins:": "\u0b89\u0baf\u0bb0\u0bcd\u0bae\u0ba4\u0bbf\u0baa\u0bcd\u0baa\u0bc1 \u0b9a\u0bca\u0bb0\u0bc1\u0b95\u0bbf\u0b95\u0bb3\u0bcd:", +"Learn more...": "\u0bae\u0bc7\u0bb2\u0bc1\u0bae\u0bcd \u0b85\u0bb1\u0bbf\u0b95...", +"You are using {0}": "\u0ba4\u0bbe\u0b99\u0bcd\u0b95\u0bb3\u0bcd \u0baa\u0baf\u0ba9\u0bcd\u0baa\u0b9f\u0bc1\u0ba4\u0bcd\u0ba4\u0bc1\u0bb5\u0ba4\u0bc1 {0}", +"Plugins": "\u0b9a\u0bca\u0bb0\u0bc1\u0b95\u0bbf\u0b95\u0bb3\u0bcd", +"Handy Shortcuts": "\u0b8e\u0bb3\u0bbf\u0ba4\u0bbf\u0bb2\u0bcd \u0b95\u0bc8\u0baf\u0bbe\u0bb3\u0b95\u0bcd\u0b95\u0bc2\u0b9f\u0bbf\u0baf \u0b95\u0bc1\u0bb1\u0bc1\u0b95\u0bcd\u0b95\u0bc1\u0bb5\u0bb4\u0bbf\u0b95\u0bb3\u0bcd", +"Horizontal line": "\u0b95\u0bbf\u0b9f\u0bc8 \u0b95\u0bcb\u0b9f\u0bc1", +"Insert\/edit image": "\u0baa\u0b9f\u0bae\u0bcd \u0b9a\u0bc6\u0bb0\u0bc1\u0b95\u0bc1\u0b95\/\u0ba4\u0bca\u0b95\u0bc1\u0b95\u0bcd\u0b95", +"Image description": "\u0baa\u0b9f \u0bb5\u0bbf\u0bb5\u0bb0\u0bae\u0bcd", +"Source": "\u0bae\u0bc2\u0bb2\u0bae\u0bcd", +"Dimensions": "\u0baa\u0bb0\u0bbf\u0bae\u0bbe\u0ba3\u0b99\u0bcd\u0b95\u0bb3\u0bcd", +"Constrain proportions": "\u0bb5\u0bbf\u0b95\u0bbf\u0ba4\u0bbe\u0b9a\u0bcd\u0b9a\u0bbe\u0bb0\u0ba4\u0bcd\u0ba4\u0bbf\u0bb2\u0bcd \u0b95\u0b9f\u0bcd\u0b9f\u0bc1\u0baa\u0bcd\u0baa\u0b9f\u0bc1\u0ba4\u0bcd\u0ba4\u0bc1\u0b95", +"General": "\u0baa\u0bca\u0ba4\u0bc1", +"Advanced": "\u0bae\u0bc7\u0bae\u0bcd\u0baa\u0b9f\u0bcd\u0b9f\u0ba4\u0bc1", +"Style": "\u0baa\u0bbe\u0ba3\u0bbf", +"Vertical space": "\u0ba8\u0bc6\u0b9f\u0bc1\u0ba4\u0bb3 \u0b87\u0b9f\u0bc8\u0bb5\u0bc6\u0bb3\u0bbf", +"Horizontal space": "\u0b95\u0bbf\u0b9f\u0bc8\u0bae\u0b9f\u0bcd\u0b9f \u0b87\u0b9f\u0bc8\u0bb5\u0bc6\u0bb3\u0bbf", +"Border": "\u0b95\u0bb0\u0bc8", +"Insert image": "\u0baa\u0b9f\u0bae\u0bcd \u0b9a\u0bc6\u0bb0\u0bc1\u0b95\u0bc1\u0b95", +"Image": "\u0baa\u0b9f\u0bae\u0bcd", +"Image list": "\u0baa\u0b9f\u0baa\u0bcd \u0baa\u0b9f\u0bcd\u0b9f\u0bbf\u0baf\u0bb2\u0bcd", +"Rotate counterclockwise": "\u0b95\u0b9f\u0bbf\u0b95\u0bbe\u0bb0 \u0b8e\u0ba4\u0bbf\u0bb0\u0bcd\u0ba4\u0bbf\u0b9a\u0bc8\u0baf\u0bbf\u0bb2\u0bcd \u0b9a\u0bc1\u0bb4\u0bb1\u0bcd\u0bb1\u0bc1", +"Rotate clockwise": "\u0b95\u0b9f\u0bbf\u0b95\u0bbe\u0bb0\u0ba4\u0bcd\u0ba4\u0bbf\u0b9a\u0bc8\u0baf\u0bbf\u0bb2\u0bcd \u0b9a\u0bc1\u0bb4\u0bb1\u0bcd\u0bb1\u0bc1", +"Flip vertically": "\u0b9a\u0bc6\u0b99\u0bcd\u0b95\u0bc1\u0ba4\u0bcd\u0ba4\u0bbe\u0b95 \u0baa\u0bc1\u0bb0\u0b9f\u0bcd\u0b9f\u0bc1", +"Flip horizontally": "\u0b95\u0bbf\u0b9f\u0bc8\u0bae\u0b9f\u0bcd\u0b9f\u0bae\u0bbe\u0b95 \u0baa\u0bc1\u0bb0\u0b9f\u0bcd\u0b9f\u0bc1", +"Edit image": "\u0baa\u0b9f\u0ba4\u0bcd\u0ba4\u0bc8 \u0ba4\u0bca\u0b95\u0bc1", +"Image options": "\u0baa\u0b9f \u0bb5\u0bbf\u0bb0\u0bc1\u0baa\u0bcd\u0baa\u0bc1\u0b95\u0bb3\u0bcd", +"Zoom in": "\u0baa\u0bc6\u0bb0\u0bbf\u0ba4\u0bbe\u0b95\u0bcd\u0b95\u0bc1", +"Zoom out": "\u0b9a\u0bbf\u0bb1\u0bbf\u0ba4\u0bbe\u0b95\u0bcd\u0b95\u0bc1", +"Crop": "\u0b9a\u0bc6\u0ba4\u0bc1\u0b95\u0bcd\u0b95\u0bc1", +"Resize": "\u0bae\u0bb1\u0bc1\u0b85\u0bb3\u0bb5\u0bbf\u0b9f\u0bc1", +"Orientation": "\u0ba4\u0bbf\u0b9a\u0bc8\u0baf\u0bae\u0bc8\u0bb5\u0bc1", +"Brightness": "\u0b92\u0bb3\u0bbf\u0bb0\u0bcd\u0bb5\u0bc1", +"Sharpen": "\u0b95\u0bc2\u0bb0\u0bcd\u0bae\u0bc8\u0baf\u0bbe\u0b95\u0bcd\u0b95\u0bc1", +"Contrast": "\u0ba8\u0bbf\u0bb1\u0bae\u0bbe\u0bb1\u0bc1\u0baa\u0bbe\u0b9f\u0bc1", +"Color levels": "\u0bb5\u0ba3\u0bcd\u0ba3 \u0ba8\u0bbf\u0bb2\u0bc8\u0b95\u0bb3\u0bcd", +"Gamma": "Gamma", +"Invert": "\u0ba8\u0bc7\u0bb0\u0bcd\u0bae\u0bbe\u0bb1\u0bcd\u0bb1\u0bc1", +"Apply": "\u0baa\u0baf\u0ba9\u0bcd\u0baa\u0b9f\u0bc1\u0ba4\u0bcd\u0ba4\u0bc1", +"Back": "\u0baa\u0bbf\u0ba9\u0bcd", +"Insert date\/time": "\u0ba4\u0bc7\u0ba4\u0bbf\/\u0ba8\u0bc7\u0bb0\u0bae\u0bcd \u0b9a\u0bc6\u0bb0\u0bc1\u0b95\u0bc1\u0b95", +"Date\/time": "\u0ba4\u0bc7\u0ba4\u0bbf\/\u0ba8\u0bc7\u0bb0\u0bae\u0bcd", +"Insert link": "\u0b87\u0ba3\u0bc8\u0baa\u0bcd\u0baa\u0bc1 \u0b9a\u0bc6\u0bb0\u0bc1\u0b95\u0bc1\u0b95", +"Insert\/edit link": "\u0b87\u0ba3\u0bc8\u0baa\u0bcd\u0baa\u0bc1 \u0b9a\u0bc6\u0bb0\u0bc1\u0b95\u0bc1\u0b95\/\u0ba4\u0bca\u0b95\u0bc1\u0b95\u0bcd\u0b95", +"Text to display": "\u0b95\u0bbe\u0b9f\u0bcd\u0b9a\u0bbf\u0baa\u0bcd\u0baa\u0b9f\u0bc1\u0ba4\u0bcd\u0ba4 \u0bb5\u0bc7\u0ba3\u0bcd\u0b9f\u0bbf\u0baf \u0b89\u0bb0\u0bc8", +"Url": "\u0b87\u0ba3\u0bc8\u0baf\u0bae\u0bc1\u0b95\u0bb5\u0bb0\u0bbf", +"Target": "\u0b87\u0bb2\u0b95\u0bcd\u0b95\u0bc1", +"None": "\u0b8f\u0ba4\u0bc1\u0bae\u0bbf\u0bb2\u0bcd\u0bb2\u0bc8", +"New window": "\u0baa\u0bc1\u0ba4\u0bbf\u0baf \u0b9a\u0bbe\u0bb3\u0bb0\u0bae\u0bcd", +"Remove link": "\u0b87\u0ba3\u0bc8\u0baa\u0bcd\u0baa\u0bc8 \u0b85\u0b95\u0bb1\u0bcd\u0bb1\u0bc1\u0b95", +"Anchors": "\u0ba8\u0b99\u0bcd\u0b95\u0bc2\u0bb0\u0b99\u0bcd\u0b95\u0bb3\u0bcd", +"Link": "\u0b87\u0ba3\u0bc8\u0baa\u0bcd\u0baa\u0bc1", +"Paste or type a link": "\u0b92\u0bb0\u0bc1 \u0b87\u0ba3\u0bc8\u0baa\u0bcd\u0baa\u0bc1 \u0b92\u0b9f\u0bcd\u0b9f\u0bc1\u0b95 \u0b85\u0bb2\u0bcd\u0bb2\u0ba4\u0bc1 \u0ba4\u0b9f\u0bcd\u0b9f\u0b9a\u0bcd\u0b9a\u0bbf\u0b9f\u0bc1\u0b95", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "\u0ba4\u0bbe\u0b99\u0bcd\u0b95\u0bb3\u0bcd \u0b89\u0bb3\u0bcd\u0bb3\u0bbf\u0b9f\u0bcd\u0b9f \u0b87\u0ba3\u0bc8\u0baf\u0bae\u0bc1\u0b95\u0bb5\u0bb0\u0bbf (URL) \u0b92\u0bb0\u0bc1 \u0bae\u0bbf\u0ba9\u0bcd-\u0b85\u0b9e\u0bcd\u0b9a\u0bb2\u0bcd \u0bae\u0bc1\u0b95\u0bb5\u0bb0\u0bbf \u0baa\u0bcb\u0bb2\u0bcd \u0ba4\u0bcb\u0ba9\u0bcd\u0bb1\u0bc1\u0b95\u0bbf\u0bb1\u0ba4\u0bc1. \u0ba4\u0bc7\u0bb5\u0bc8\u0baf\u0bbe\u0ba9 mailto: \u0bae\u0bc1\u0ba9\u0bcd-\u0b92\u0b9f\u0bcd\u0b9f\u0bc8\u0ba4\u0bcd (prefix) \u0ba4\u0bbe\u0b99\u0bcd\u0b95\u0bb3\u0bcd \u0b9a\u0bc7\u0bb0\u0bcd\u0b95\u0bcd\u0b95 \u0bb5\u0bc7\u0ba3\u0bcd\u0b9f\u0bc1\u0bae\u0bbe?", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "\u0ba4\u0bbe\u0b99\u0bcd\u0b95\u0bb3\u0bcd \u0b89\u0bb3\u0bcd\u0bb3\u0bbf\u0b9f\u0bcd\u0b9f \u0b87\u0ba3\u0bc8\u0baf\u0bae\u0bc1\u0b95\u0bb5\u0bb0\u0bbf (URL) \u0b92\u0bb0\u0bc1 \u0bb5\u0bc6\u0bb3\u0bbf\u0baa\u0bcd\u0baa\u0bc1\u0bb1 \u0b87\u0ba3\u0bc8\u0baa\u0bcd\u0baa\u0bc1 (external link) \u0baa\u0bcb\u0bb2\u0bcd \u0ba4\u0bcb\u0ba9\u0bcd\u0bb1\u0bc1\u0b95\u0bbf\u0bb1\u0ba4\u0bc1. \u0ba4\u0bc7\u0bb5\u0bc8\u0baf\u0bbe\u0ba9 http:\/\/ \u0bae\u0bc1\u0ba9\u0bcd-\u0b92\u0b9f\u0bcd\u0b9f\u0bc8\u0ba4\u0bcd (prefix) \u0ba4\u0bbe\u0b99\u0bcd\u0b95\u0bb3\u0bcd \u0b9a\u0bc7\u0bb0\u0bcd\u0b95\u0bcd\u0b95 \u0bb5\u0bc7\u0ba3\u0bcd\u0b9f\u0bc1\u0bae\u0bbe?", +"Link list": "\u0b87\u0ba3\u0bc8\u0baa\u0bcd\u0baa\u0bc1\u0baa\u0bcd \u0baa\u0b9f\u0bcd\u0b9f\u0bbf\u0baf\u0bb2\u0bcd", +"Insert video": "\u0b95\u0bbe\u0ba3\u0bca\u0bb3\u0bbf \u0b9a\u0bc6\u0bb0\u0bc1\u0b95\u0bc1\u0b95", +"Insert\/edit video": "\u0b95\u0bbe\u0ba3\u0bca\u0bb3\u0bbf \u0b9a\u0bc6\u0bb0\u0bc1\u0b95\u0bc1\u0b95\/\u0ba4\u0bca\u0b95\u0bc1\u0b95\u0bcd\u0b95", +"Insert\/edit media": "\u0b8a\u0b9f\u0b95\u0bae\u0bcd \u0b9a\u0bc6\u0bb0\u0bc1\u0b95\u0bc1\u0b95\/\u0ba4\u0bca\u0b95\u0bc1\u0b95\u0bcd\u0b95", +"Alternative source": "\u0bae\u0bbe\u0bb1\u0bcd\u0bb1\u0bc1 \u0bae\u0bc2\u0bb2\u0bae\u0bcd", +"Poster": "\u0b9a\u0bc1\u0bb5\u0bb0\u0bca\u0b9f\u0bcd\u0b9f\u0bbf", +"Paste your embed code below:": "\u0ba4\u0b99\u0bcd\u0b95\u0bb3\u0bcd \u0b89\u0b9f\u0bcd\u0baa\u0bc6\u0bbe\u0ba4\u0bbf \u0b95\u0bc1\u0bb1\u0bbf\u0baf\u0bc0\u0b9f\u0bcd\u0b9f\u0bc8 \u0b95\u0bc0\u0bb4\u0bc7 \u0b92\u0b9f\u0bcd\u0b9f\u0bb5\u0bc1\u0bae\u0bcd:", +"Embed": "\u0b89\u0b9f\u0bcd\u0baa\u0bca\u0ba4\u0bbf", +"Media": "\u0b8a\u0b9f\u0b95\u0bae\u0bcd", +"Nonbreaking space": "\u0baa\u0bbf\u0bb0\u0bbf\u0baf\u0bbe\u0ba4 \u0b87\u0b9f\u0bc8\u0bb5\u0bc6\u0bb3\u0bbf", +"Page break": "\u0baa\u0b95\u0bcd\u0b95 \u0baa\u0bbf\u0bb0\u0bbf\u0baa\u0bcd\u0baa\u0bc1", +"Paste as text": "\u0b89\u0bb0\u0bc8\u0baf\u0bbe\u0b95 \u0b92\u0b9f\u0bcd\u0b9f\u0bc1\u0b95", +"Preview": "\u0bae\u0bc1\u0ba9\u0bcd\u0ba8\u0bcb\u0b95\u0bcd\u0b95\u0bc1", +"Print": "\u0b85\u0b9a\u0bcd\u0b9a\u0bbf\u0b9f\u0bc1\u0b95", +"Save": "\u0b9a\u0bc7\u0bae\u0bbf\u0b95\u0bcd\u0b95", +"Find": "\u0b95\u0ba3\u0bcd\u0b9f\u0bc1\u0baa\u0bbf\u0b9f\u0bbf\u0b95\u0bcd\u0b95", +"Replace with": "\u0b87\u0ba4\u0ba9\u0bc1\u0b9f\u0ba9\u0bcd \u0bae\u0bbe\u0bb1\u0bcd\u0bb1\u0bc1\u0b95", +"Replace": "\u0bae\u0bbe\u0bb1\u0bcd\u0bb1\u0bc1\u0b95", +"Replace all": "\u0b85\u0ba9\u0bc8\u0ba4\u0bcd\u0ba4\u0bc8\u0baf\u0bc1\u0bae\u0bcd \u0bae\u0bbe\u0bb1\u0bcd\u0bb1\u0bc1\u0b95", +"Prev": "\u0bae\u0bc1\u0ba8\u0bcd\u0ba4\u0bc8\u0baf", +"Next": "\u0b85\u0b9f\u0bc1\u0ba4\u0bcd\u0ba4", +"Find and replace": "\u0b95\u0ba3\u0bcd\u0b9f\u0bc1\u0baa\u0bbf\u0b9f\u0bbf\u0ba4\u0bcd\u0ba4\u0bc1 \u0bae\u0bbe\u0bb1\u0bcd\u0bb1\u0bc1\u0b95", +"Could not find the specified string.": "\u0b95\u0bc1\u0bb1\u0bbf\u0baa\u0bcd\u0baa\u0bbf\u0b9f\u0bcd\u0b9f \u0b9a\u0bb0\u0bae\u0bcd \u0b95\u0ba3\u0bcd\u0b9f\u0bc1\u0baa\u0bbf\u0b9f\u0bbf\u0b95\u0bcd\u0b95 \u0bae\u0bc1\u0b9f\u0bbf\u0baf\u0bb5\u0bbf\u0bb2\u0bcd\u0bb2\u0bc8", +"Match case": "\u0bb5\u0b9f\u0bbf\u0bb5\u0ba4\u0bcd\u0ba4\u0bc8 \u0baa\u0bca\u0bb0\u0bc1\u0ba4\u0bcd\u0ba4\u0bc1\u0b95", +"Whole words": "\u0bae\u0bc1\u0bb4\u0bc1 \u0b9a\u0bca\u0bb1\u0bcd\u0b95\u0bb3\u0bcd", +"Spellcheck": "\u0b8e\u0bb4\u0bc1\u0ba4\u0bcd\u0ba4\u0bc1\u0baa\u0bcd\u0baa\u0bbf\u0bb4\u0bc8\u0baf\u0bc8 \u0b9a\u0bb0\u0bbf\u0baa\u0bbe\u0bb0\u0bcd\u0b95\u0bcd\u0b95", +"Ignore": "\u0baa\u0bc1\u0bb1\u0b95\u0bcd\u0b95\u0ba3\u0bbf\u0b95\u0bcd\u0b95", +"Ignore all": "\u0b85\u0ba9\u0bc8\u0ba4\u0bcd\u0ba4\u0bc8\u0baf\u0bc1\u0bae\u0bcd \u0baa\u0bc1\u0bb1\u0b95\u0bcd\u0b95\u0ba3\u0bbf\u0b95\u0bcd\u0b95", +"Finish": "\u0bae\u0bc1\u0b9f\u0bbf\u0b95\u0bcd\u0b95", +"Add to Dictionary": "\u0b85\u0b95\u0bb0\u0bbe\u0ba4\u0bbf\u0baf\u0bbf\u0bb2\u0bcd \u0b9a\u0bc7\u0bb0\u0bcd\u0b95\u0bcd\u0b95", +"Insert table": "\u0b85\u0b9f\u0bcd\u0b9f\u0bb5\u0ba3\u0bc8 \u0b9a\u0bc6\u0bb0\u0bc1\u0b95\u0bc1\u0b95", +"Table properties": "\u0b85\u0b9f\u0bcd\u0b9f\u0bb5\u0ba3\u0bc8 \u0baa\u0ba3\u0bcd\u0baa\u0bc1\u0b95\u0bb3\u0bcd", +"Delete table": "\u0b85\u0b9f\u0bcd\u0b9f\u0bb5\u0ba3\u0bc8 \u0ba8\u0bc0\u0b95\u0bcd\u0b95\u0bc1\u0b95", +"Cell": "\u0b9a\u0bbf\u0bb1\u0bcd\u0bb1\u0bb1\u0bc8", +"Row": "\u0bb5\u0bb0\u0bbf\u0b9a\u0bc8", +"Column": "\u0ba8\u0bc6\u0b9f\u0bc1\u0bb5\u0bb0\u0bbf\u0b9a\u0bc8", +"Cell properties": "\u0b9a\u0bbf\u0bb1\u0bcd\u0bb1\u0bb1\u0bc8 \u0baa\u0ba3\u0bcd\u0baa\u0bc1\u0b95\u0bb3\u0bcd", +"Merge cells": "\u0b9a\u0bbf\u0bb1\u0bcd\u0bb1\u0bb1\u0bc8\u0b95\u0bb3\u0bcd \u0b9a\u0bc7\u0bb0\u0bcd\u0b95\u0bcd\u0b95", +"Split cell": "\u0b9a\u0bbf\u0bb1\u0bcd\u0bb1\u0bb1\u0bc8 \u0baa\u0bbf\u0bb0\u0bbf\u0b95\u0bcd\u0b95", +"Insert row before": "\u0b87\u0ba4\u0bb1\u0bcd\u0b95\u0bc1 \u0bae\u0bc1\u0ba9\u0bcd \u0bb5\u0bb0\u0bbf\u0b9a\u0bc8 \u0b9a\u0bc6\u0bb0\u0bc1\u0b95\u0bc1\u0b95", +"Insert row after": "\u0b87\u0ba4\u0bb1\u0bcd\u0b95\u0bc1 \u0baa\u0bbf\u0ba9\u0bcd \u0bb5\u0bb0\u0bbf\u0b9a\u0bc8 \u0b9a\u0bc6\u0bb0\u0bc1\u0b95\u0bc1\u0b95", +"Delete row": "\u0bb5\u0bb0\u0bbf\u0b9a\u0bc8 \u0ba8\u0bc0\u0b95\u0bcd\u0b95\u0bc1\u0b95", +"Row properties": "\u0bb5\u0bb0\u0bbf\u0b9a\u0bc8 \u0baa\u0ba3\u0bcd\u0baa\u0bc1\u0b95\u0bb3\u0bcd", +"Cut row": "\u0bb5\u0bb0\u0bbf\u0b9a\u0bc8 \u0bb5\u0bc6\u0b9f\u0bcd\u0b9f\u0bc1\u0b95", +"Copy row": "\u0bb5\u0bb0\u0bbf\u0b9a\u0bc8 \u0ba8\u0b95\u0bb2\u0bc6\u0b9f\u0bc1\u0b95\u0bcd\u0b95", +"Paste row before": "\u0b87\u0ba4\u0bb1\u0bcd\u0b95\u0bc1 \u0bae\u0bc1\u0ba9\u0bcd \u0bb5\u0bb0\u0bbf\u0b9a\u0bc8 \u0b92\u0b9f\u0bcd\u0b9f\u0bc1\u0b95", +"Paste row after": "\u0b87\u0ba4\u0bb1\u0bcd\u0b95\u0bc1 \u0baa\u0bbf\u0ba9\u0bcd \u0bb5\u0bb0\u0bbf\u0b9a\u0bc8 \u0b92\u0b9f\u0bcd\u0b9f\u0bc1\u0b95", +"Insert column before": "\u0b87\u0ba4\u0bb1\u0bcd\u0b95\u0bc1 \u0bae\u0bc1\u0ba9\u0bcd \u0ba8\u0bc6\u0b9f\u0bc1\u0bb5\u0bb0\u0bbf\u0b9a\u0bc8 \u0b9a\u0bc6\u0bb0\u0bc1\u0b95\u0bc1\u0b95", +"Insert column after": "\u0b87\u0ba4\u0bb1\u0bcd\u0b95\u0bc1 \u0baa\u0bbf\u0ba9\u0bcd \u0ba8\u0bc6\u0b9f\u0bc1\u0bb5\u0bb0\u0bbf\u0b9a\u0bc8 \u0b9a\u0bc6\u0bb0\u0bc1\u0b95\u0bc1\u0b95", +"Delete column": "\u0ba8\u0bc6\u0b9f\u0bc1\u0bb5\u0bb0\u0bbf\u0b9a\u0bc8 \u0ba8\u0bc0\u0b95\u0bcd\u0b95\u0bc1\u0b95", +"Cols": "\u0ba8\u0bc6\u0b9f\u0bc1\u0bb5\u0bb0\u0bbf\u0b9a\u0bc8\u0b95\u0bb3\u0bcd", +"Rows": "\u0bb5\u0bb0\u0bbf\u0b9a\u0bc8\u0b95\u0bb3\u0bcd", +"Width": "\u0b85\u0b95\u0bb2\u0bae\u0bcd", +"Height": "\u0b89\u0baf\u0bb0\u0bae\u0bcd", +"Cell spacing": "\u0b9a\u0bbf\u0bb1\u0bcd\u0bb1\u0bb1\u0bc8 \u0b87\u0b9f\u0bc8\u0bb5\u0bc6\u0bb3\u0bbf", +"Cell padding": "\u0b9a\u0bbf\u0bb1\u0bcd\u0bb1\u0bb1\u0bc8 \u0ba8\u0bbf\u0bb0\u0baa\u0bcd\u0baa\u0bb2\u0bcd", +"Caption": "\u0ba4\u0bb2\u0bc8\u0baa\u0bcd\u0baa\u0bc1", +"Left": "\u0b87\u0b9f\u0bae\u0bcd", +"Center": "\u0bae\u0bc8\u0baf\u0bae\u0bcd", +"Right": "\u0bb5\u0bb2\u0bae\u0bcd", +"Cell type": "\u0b9a\u0bbf\u0bb1\u0bcd\u0bb1\u0bb1\u0bc8 \u0bb5\u0b95\u0bc8", +"Scope": "\u0bb5\u0bb0\u0bc8\u0baf\u0bc6\u0bb2\u0bcd\u0bb2\u0bc8", +"Alignment": "\u0b9a\u0bc0\u0bb0\u0bae\u0bc8\u0bb5\u0bc1", +"H Align": "\u0b95\u0bbf (H) \u0b9a\u0bc0\u0bb0\u0bae\u0bc8", +"V Align": "\u0b9a\u0bc6 (V) \u0b9a\u0bc0\u0bb0\u0bae\u0bc8", +"Top": "\u0bae\u0bc7\u0bb2\u0bcd", +"Middle": "\u0ba8\u0b9f\u0bc1", +"Bottom": "\u0b95\u0bc0\u0bb4\u0bcd", +"Header cell": "\u0ba4\u0bb2\u0bc8\u0baa\u0bcd\u0baa\u0bc1 \u0b9a\u0bbf\u0bb1\u0bcd\u0bb1\u0bb1\u0bc8", +"Row group": "\u0bb5\u0bb0\u0bbf\u0b9a\u0bc8 \u0b95\u0bc1\u0bb4\u0bc1", +"Column group": "\u0ba8\u0bc6\u0b9f\u0bc1\u0bb5\u0bb0\u0bbf\u0b9a\u0bc8 \u0b95\u0bc1\u0bb4\u0bc1", +"Row type": "\u0bb5\u0bb0\u0bbf\u0b9a\u0bc8 \u0bb5\u0b95\u0bc8", +"Header": "\u0ba4\u0bb2\u0bc8\u0baa\u0bcd\u0baa\u0bc1", +"Body": "\u0b89\u0b9f\u0bb2\u0bcd", +"Footer": "\u0b85\u0b9f\u0bbf\u0b95\u0bcd\u0b95\u0bc1\u0bb1\u0bbf\u0baa\u0bcd\u0baa\u0bc1", +"Border color": "\u0b95\u0bb0\u0bc8\u0baf\u0bbf\u0ba9\u0bcd \u0ba8\u0bbf\u0bb1\u0bae\u0bcd", +"Insert template": "\u0bb5\u0bbe\u0bb0\u0bcd\u0baa\u0bcd\u0baa\u0bc1\u0bb0\u0bc1 \u0b9a\u0bc6\u0bb0\u0bc1\u0b95\u0bc1\u0b95", +"Templates": "\u0bb5\u0bbe\u0bb0\u0bcd\u0baa\u0bcd\u0baa\u0bc1\u0bb0\u0bc1\u0b95\u0bcd\u0b95\u0bb3\u0bcd", +"Template": "\u0bb5\u0bbe\u0bb0\u0bcd\u0baa\u0bcd\u0baa\u0bc1\u0bb0\u0bc1", +"Text color": "\u0b89\u0bb0\u0bc8\u0baf\u0bbf\u0ba9\u0bcd \u0ba8\u0bbf\u0bb1\u0bae\u0bcd", +"Background color": "\u0baa\u0bbf\u0ba9\u0bcd\u0ba9\u0ba3\u0bbf \u0ba8\u0bbf\u0bb1\u0bae\u0bcd", +"Custom...": "\u0ba4\u0ba9\u0bbf\u0baa\u0bcd\u0baa\u0baf\u0ba9\u0bcd...", +"Custom color": "\u0ba4\u0ba9\u0bbf\u0baa\u0bcd\u0baa\u0baf\u0ba9\u0bcd \u0ba8\u0bbf\u0bb1\u0bae\u0bcd", +"No color": "\u0ba8\u0bbf\u0bb1\u0bae\u0bcd \u0b87\u0bb2\u0bcd\u0bb2\u0bc8", +"Table of Contents": "\u0baa\u0bca\u0bb0\u0bc1\u0bb3\u0b9f\u0b95\u0bcd\u0b95\u0bae\u0bcd", +"Show blocks": "\u0ba4\u0bca\u0b95\u0bc1\u0ba4\u0bbf\u0b95\u0bb3\u0bc8 \u0b95\u0bbe\u0b9f\u0bcd\u0b9f\u0bc1\u0b95", +"Show invisible characters": "\u0b95\u0ba3\u0bcd\u0ba3\u0bc1\u0b95\u0bcd\u0b95\u0bc1\u0ba4\u0bcd \u0ba4\u0bc6\u0bb0\u0bbf\u0baf\u0bbe\u0ba4 \u0b89\u0bb0\u0bc1\u0b95\u0bcd\u0b95\u0bb3\u0bc8 \u0b95\u0bbe\u0b9f\u0bcd\u0b9f\u0bc1\u0b95", +"Words: {0}": "\u0b9a\u0bca\u0bb1\u0bcd\u0b95\u0bb3\u0bcd: {0}", +"{0} words": "{0} \u0b9a\u0bca\u0bb1\u0bcd\u0b95\u0bb3\u0bcd", +"File": "\u0b95\u0bcb\u0baa\u0bcd\u0baa\u0bc1", +"Edit": "\u0ba4\u0bca\u0b95\u0bc1\u0b95\u0bcd\u0b95", +"Insert": "\u0b9a\u0bc6\u0bb0\u0bc1\u0b95\u0bc1\u0b95", +"View": "\u0ba8\u0bcb\u0b95\u0bcd\u0b95\u0bc1\u0b95", +"Format": "\u0bb5\u0b9f\u0bbf\u0bb5\u0bae\u0bc8\u0baa\u0bcd\u0baa\u0bc1", +"Table": "\u0b85\u0b9f\u0bcd\u0b9f\u0bb5\u0ba3\u0bc8", +"Tools": "\u0b95\u0bb0\u0bc1\u0bb5\u0bbf\u0b95\u0bb3\u0bcd", +"Powered by {0}": "\u0bb5\u0bb2\u0bc1\u0bb5\u0bb3\u0bbf\u0baa\u0bcd\u0baa\u0ba4\u0bc1 {0}", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "\u0b89\u0baf\u0bb0\u0bcd \u0b89\u0bb0\u0bc8 \u0baa\u0b95\u0bc1\u0ba4\u0bbf. \u0baa\u0b9f\u0bcd\u0b9f\u0bbf\u0b95\u0bcd\u0b95\u0bc1 ALT-F9 , \u0b95\u0bb0\u0bc1\u0bb5\u0bbf\u0baa\u0bcd\u0baa\u0b9f\u0bcd\u0b9f\u0bc8\u0b95\u0bcd\u0b95\u0bc1 ALT-F10 , \u0b89\u0ba4\u0bb5\u0bbf\u0b95\u0bcd\u0b95\u0bc1 ALT-0" +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/ta_IN.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/ta_IN.js new file mode 100644 index 0000000000..faa20ef916 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/ta_IN.js @@ -0,0 +1,261 @@ +tinymce.addI18n('ta_IN',{ +"Redo": "\u0bae\u0bc0\u0ba3\u0bcd\u0b9f\u0bc1\u0bae\u0bcd \u0b9a\u0bc6\u0baf\u0bcd\u0b95", +"Undo": "\u0b9a\u0bc6\u0baf\u0bb2\u0bcd\u0ba4\u0bb5\u0bbf\u0bb0\u0bcd\u0b95\u0bcd\u0b95", +"Cut": "\u0bb5\u0bc6\u0b9f\u0bcd\u0b9f\u0bc1\u0b95", +"Copy": "\u0ba8\u0b95\u0bb2\u0bc6\u0b9f\u0bc1\u0b95\u0bcd\u0b95", +"Paste": "\u0b92\u0b9f\u0bcd\u0b9f\u0bc1\u0b95", +"Select all": "\u0b85\u0ba9\u0bc8\u0ba4\u0bcd\u0ba4\u0bc8\u0baf\u0bc1\u0bae\u0bcd \u0ba4\u0bc7\u0bb0\u0bcd\u0bb5\u0bc1 \u0b9a\u0bc6\u0baf\u0bcd\u0b95", +"New document": "\u0baa\u0bc1\u0ba4\u0bbf\u0baf \u0b86\u0bb5\u0ba3\u0bae\u0bcd", +"Ok": "\u0b9a\u0bb0\u0bbf", +"Cancel": "\u0bb0\u0ba4\u0bcd\u0ba4\u0bc1 \u0b9a\u0bc6\u0baf\u0bcd\u0b95", +"Visual aids": "\u0b95\u0bbe\u0b9f\u0bcd\u0b9a\u0bbf\u0ba4\u0bcd \u0ba4\u0bc1\u0ba3\u0bc8\u0baf\u0ba9\u0bcd\u0b95\u0bb3\u0bcd", +"Bold": "\u0ba4\u0b9f\u0bbf\u0baa\u0bcd\u0baa\u0bc1", +"Italic": "\u0b9a\u0bbe\u0baf\u0bcd\u0bb5\u0bc1", +"Underline": "\u0b85\u0b9f\u0bbf\u0b95\u0bcd\u0b95\u0bcb\u0b9f\u0bc1", +"Strikethrough": "\u0ba8\u0b9f\u0bc1\u0b95\u0bcd\u0b95\u0bcb\u0b9f\u0bc1", +"Superscript": "\u0bae\u0bc7\u0bb2\u0bcd\u0b92\u0b9f\u0bcd\u0b9f\u0bc1", +"Subscript": "\u0b95\u0bc0\u0bb4\u0bcd\u0b92\u0b9f\u0bcd\u0b9f\u0bc1", +"Clear formatting": "\u0bb5\u0b9f\u0bbf\u0bb5\u0bae\u0bc8\u0baa\u0bcd\u0baa\u0bc1 \u0b85\u0bb4\u0bbf\u0b95\u0bcd\u0b95", +"Align left": "\u0b87\u0b9f\u0ba4\u0bc1 \u0b9a\u0bc0\u0bb0\u0bae\u0bc8", +"Align center": "\u0bae\u0bc8\u0baf \u0b9a\u0bc0\u0bb0\u0bae\u0bc8", +"Align right": "\u0bb5\u0bb2\u0ba4\u0bc1 \u0b9a\u0bc0\u0bb0\u0bae\u0bc8", +"Justify": "\u0ba8\u0bc7\u0bb0\u0bcd\u0ba4\u0bcd\u0ba4\u0bbf \u0b9a\u0bc6\u0baf\u0bcd\u0b95", +"Bullet list": "\u0baa\u0bca\u0b9f\u0bcd\u0b9f\u0bbf\u0b9f\u0baa\u0bcd\u0baa\u0b9f\u0bcd\u0b9f \u0baa\u0b9f\u0bcd\u0b9f\u0bbf\u0baf\u0bb2\u0bcd", +"Numbered list": "\u0b8e\u0ba3\u0bcd\u0ba3\u0bbf\u0b9f\u0baa\u0bcd\u0baa\u0b9f\u0bcd\u0b9f \u0baa\u0b9f\u0bcd\u0b9f\u0bbf\u0baf\u0bb2\u0bcd", +"Decrease indent": "\u0b89\u0bb3\u0bcd\u0ba4\u0bb3\u0bcd\u0bb3\u0bc1\u0ba4\u0bb2\u0bc8 \u0b95\u0bc1\u0bb1\u0bc8\u0b95\u0bcd\u0b95", +"Increase indent": "\u0b89\u0bb3\u0bcd\u0ba4\u0bb3\u0bcd\u0bb3\u0bc1\u0ba4\u0bb2\u0bc8 \u0b85\u0ba4\u0bbf\u0b95\u0bb0\u0bbf\u0b95\u0bcd\u0b95", +"Close": "\u0bae\u0bc2\u0b9f\u0bc1\u0b95", +"Formats": "\u0bb5\u0b9f\u0bbf\u0bb5\u0bae\u0bc8\u0baa\u0bcd\u0baa\u0bc1\u0b95\u0bb3\u0bcd", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "\u0ba8\u0b95\u0bb2\u0b95\u0ba4\u0bcd\u0ba4\u0bbf\u0bb1\u0bcd\u0b95\u0bc1 \u0ba8\u0bc7\u0bb0\u0b9f\u0bbf \u0b85\u0ba3\u0bc1\u0b95\u0bb2\u0bc8 \u0ba4\u0b99\u0bcd\u0b95\u0bb3\u0bcd \u0b89\u0bb2\u0bbe\u0bb5\u0bbf \u0b86\u0ba4\u0bb0\u0bbf\u0b95\u0bcd\u0b95\u0bb5\u0bbf\u0bb2\u0bcd\u0bb2\u0bc8. \u0b86\u0b95\u0bb5\u0bc7 \u0bb5\u0bbf\u0b9a\u0bc8\u0baa\u0bcd\u0baa\u0bb2\u0b95\u0bc8 \u0b95\u0bc1\u0bb1\u0bc1\u0b95\u0bcd\u0b95\u0bc1\u0bb5\u0bb4\u0bbf\u0b95\u0bb3\u0bbe\u0ba9 Ctrl+X\/C\/V \u0b87\u0bb5\u0bb1\u0bcd\u0bb1\u0bc8 \u0ba4\u0baf\u0bb5\u0bc1 \u0b9a\u0bc6\u0baf\u0bcd\u0ba4\u0bc1 \u0baa\u0baf\u0ba9\u0bcd\u0baa\u0b9f\u0bc1\u0ba4\u0bcd\u0ba4\u0bc1\u0b95.", +"Headers": "\u0ba4\u0bb2\u0bc8\u0baa\u0bcd\u0baa\u0bc1\u0b95\u0bb3\u0bcd", +"Header 1": "\u0ba4\u0bb2\u0bc8\u0baa\u0bcd\u0baa\u0bc1 1", +"Header 2": "\u0ba4\u0bb2\u0bc8\u0baa\u0bcd\u0baa\u0bc1 2", +"Header 3": "\u0ba4\u0bb2\u0bc8\u0baa\u0bcd\u0baa\u0bc1 3", +"Header 4": "\u0ba4\u0bb2\u0bc8\u0baa\u0bcd\u0baa\u0bc1 4", +"Header 5": "\u0ba4\u0bb2\u0bc8\u0baa\u0bcd\u0baa\u0bc1 5", +"Header 6": "\u0ba4\u0bb2\u0bc8\u0baa\u0bcd\u0baa\u0bc1 6", +"Headings": "\u0ba4\u0bb2\u0bc8\u0baa\u0bcd\u0baa\u0bc1\u0b95\u0bb3\u0bcd", +"Heading 1": "\u0ba4\u0bb2\u0bc8\u0baa\u0bcd\u0baa\u0bc1 1", +"Heading 2": "\u0ba4\u0bb2\u0bc8\u0baa\u0bcd\u0baa\u0bc1 2", +"Heading 3": "\u0ba4\u0bb2\u0bc8\u0baa\u0bcd\u0baa\u0bc1 3", +"Heading 4": "\u0ba4\u0bb2\u0bc8\u0baa\u0bcd\u0baa\u0bc1 4", +"Heading 5": "\u0ba4\u0bb2\u0bc8\u0baa\u0bcd\u0baa\u0bc1 5", +"Heading 6": "\u0ba4\u0bb2\u0bc8\u0baa\u0bcd\u0baa\u0bc1 6", +"Preformatted": "\u0bae\u0bc1\u0ba9\u0bcd\u0bb5\u0b9f\u0bbf\u0bb5\u0bae\u0bc8\u0b95\u0bcd\u0b95\u0baa\u0bcd\u0baa\u0b9f\u0bcd\u0b9f\u0ba4\u0bc1", +"Div": "\u0baa\u0bbf\u0bb0\u0bbf\u0bb5\u0bc1 (Div)", +"Pre": "\u0bae\u0bc1\u0ba9\u0bcd \u0bb5\u0b9f\u0bbf\u0bb5\u0bae\u0bc8\u0b95\u0bcd\u0b95\u0baa\u0bcd\u0baa\u0b9f\u0bcd\u0b9f\u0ba4\u0bc1 (Pre)", +"Code": "\u0b95\u0bc1\u0bb1\u0bbf\u0baf\u0bc0\u0b9f\u0bc1", +"Paragraph": "\u0baa\u0ba4\u0bcd\u0ba4\u0bbf", +"Blockquote": "\u0ba4\u0bca\u0b95\u0bc1\u0ba4\u0bbf \u0bae\u0bc7\u0bb1\u0bcd\u0b95\u0bcb\u0bb3\u0bcd", +"Inline": "\u0b89\u0bb3\u0bcd\u0bb5\u0bb0\u0bbf\u0b9a\u0bc8", +"Blocks": "\u0ba4\u0bca\u0b95\u0bc1\u0ba4\u0bbf\u0b95\u0bb3\u0bcd", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "\u0b87\u0baf\u0bb2\u0bcd\u0baa\u0bc1 \u0b89\u0bb0\u0bc8 \u0bae\u0bc1\u0bb1\u0bc8\u0bae\u0bc8\u0baf\u0bbf\u0bb2\u0bcd \u0ba4\u0bb1\u0bcd\u0baa\u0bcb\u0ba4\u0bc1 \u0b92\u0b9f\u0bcd\u0b9f\u0bc1\u0ba4\u0bb2\u0bcd \u0b89\u0bb3\u0bcd\u0bb3\u0ba4\u0bc1. \u0ba4\u0bbe\u0b99\u0bcd\u0b95\u0bb3\u0bcd \u0b87\u0ba8\u0bcd\u0ba4 \u0bb5\u0bbf\u0bb0\u0bc1\u0baa\u0bcd\u0baa\u0bc8 \u0bae\u0bbe\u0bb1\u0bcd\u0bb1\u0bc1\u0bae\u0bcd \u0bb5\u0bb0\u0bc8 \u0b89\u0bb3\u0bcd\u0bb3\u0b9f\u0b95\u0bcd\u0b95\u0b99\u0bcd\u0b95\u0bb3\u0bcd \u0b87\u0baf\u0bb2\u0bcd\u0baa\u0bc1 \u0b89\u0bb0\u0bc8\u0baf\u0bbe\u0b95 \u0b92\u0b9f\u0bcd\u0b9f\u0baa\u0bcd\u0baa\u0b9f\u0bc1\u0bae\u0bcd.", +"Font Family": "\u0b8e\u0bb4\u0bc1\u0ba4\u0bcd\u0ba4\u0bc1\u0bb0\u0bc1 \u0b95\u0bc1\u0b9f\u0bc1\u0bae\u0bcd\u0baa\u0bae\u0bcd", +"Font Sizes": "\u0b8e\u0bb4\u0bc1\u0ba4\u0bcd\u0ba4\u0bc1\u0bb0\u0bc1 \u0b85\u0bb3\u0bb5\u0bc1\u0b95\u0bb3\u0bcd", +"Class": "Class", +"Browse for an image": "\u0b92\u0bb0\u0bc1 \u0baa\u0b9f\u0ba4\u0bcd\u0ba4\u0bc1\u0b95\u0bcd\u0b95\u0bc1 \u0b89\u0bb2\u0bbe\u0bb5\u0bc1\u0b95", +"OR": "\u0b85\u0bb2\u0bcd\u0bb2\u0ba4\u0bc1", +"Drop an image here": "\u0b92\u0bb0\u0bc1 \u0baa\u0b9f\u0ba4\u0bcd\u0ba4\u0bc8 \u0b87\u0b99\u0bcd\u0b95\u0bc1 \u0b87\u0bb4\u0bc1\u0ba4\u0bcd\u0ba4\u0bc1\u0baa\u0bcd \u0baa\u0bcb\u0b9f\u0bb5\u0bc1\u0bae\u0bcd", +"Upload": "\u0baa\u0ba4\u0bbf\u0bb5\u0bc7\u0bb1\u0bcd\u0bb1\u0bc1\u0b95", +"Block": "\u0ba4\u0bca\u0b95\u0bc1\u0ba4\u0bbf", +"Align": "\u0b9a\u0bc0\u0bb0\u0bae\u0bc8", +"Default": "\u0b89\u0bb3\u0bcd\u0bb3\u0bbf\u0bb0\u0bc1\u0baa\u0bcd\u0baa\u0bc1", +"Circle": "\u0bb5\u0b9f\u0bcd\u0b9f\u0bae\u0bcd", +"Disc": "\u0bb5\u0b9f\u0bcd\u0b9f\u0bc1", +"Square": "\u0b9a\u0ba4\u0bc1\u0bb0\u0bae\u0bcd", +"Lower Alpha": "\u0b95\u0bc0\u0bb4\u0bcd \u0b8e\u0bb4\u0bc1\u0ba4\u0bcd\u0ba4\u0bc1", +"Lower Greek": "\u0b95\u0bc0\u0bb4\u0bcd \u0b95\u0bbf\u0bb0\u0bc7\u0b95\u0bcd\u0b95\u0bae\u0bcd", +"Lower Roman": "\u0b95\u0bc0\u0bb4\u0bcd \u0bb0\u0bcb\u0bae\u0bbe\u0ba9\u0bbf\u0baf\u0bae\u0bcd", +"Upper Alpha": "\u0bae\u0bc7\u0bb2\u0bcd \u0b8e\u0bb4\u0bc1\u0ba4\u0bcd\u0ba4\u0bc1", +"Upper Roman": "\u0bae\u0bc7\u0bb2\u0bcd \u0bb0\u0bcb\u0bae\u0bbe\u0ba9\u0bbf\u0baf\u0bae\u0bcd", +"Anchor": "\u0ba8\u0b99\u0bcd\u0b95\u0bc2\u0bb0\u0bae\u0bcd", +"Name": "\u0baa\u0bc6\u0baf\u0bb0\u0bcd", +"Id": "Id", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "Id \u0b86\u0ba9\u0ba4\u0bc1 \u0b92\u0bb0\u0bc1 \u0b8e\u0bb4\u0bc1\u0ba4\u0bcd\u0ba4\u0bbf\u0bb2\u0bcd \u0ba4\u0bca\u0b9f\u0b99\u0bcd\u0b95 \u0bb5\u0bc7\u0ba3\u0bcd\u0b9f\u0bc1\u0bae\u0bcd; \u0b87\u0ba4\u0ba9\u0bc8\u0ba4\u0bcd \u0ba4\u0bca\u0b9f\u0bb0\u0bcd\u0ba8\u0bcd\u0ba4\u0bc1 \u0b8e\u0bb4\u0bc1\u0ba4\u0bcd\u0ba4\u0bc1\u0b95\u0bcd\u0b95\u0bb3\u0bcd, \u0b8e\u0ba3\u0bcd\u0b95\u0bb3\u0bcd, \u0b87\u0b9f\u0bc8\u0b95\u0bcd\u0b95\u0bc7\u0bbe\u0b9f\u0bc1\u0b95\u0bb3\u0bcd (-), \u0baa\u0bc1\u0bb3\u0bcd\u0bb3\u0bbf\u0b95\u0bb3\u0bcd (.), \u0bae\u0bc1\u0b95\u0bcd\u0b95\u0bbe\u0bb1\u0bcd\u0baa\u0bc1\u0bb3\u0bcd\u0bb3\u0bbf\u0b95\u0bb3\u0bcd (:) \u0bae\u0bb1\u0bcd\u0bb1\u0bc1\u0bae\u0bcd \u0b85\u0b9f\u0bbf\u0b95\u0bcd\u0b95\u0bc7\u0bbe\u0b9f\u0bc1\u0b95\u0bb3\u0bcd (_) \u0bae\u0b9f\u0bcd\u0b9f\u0bc1\u0bae\u0bc7 \u0b87\u0bb0\u0bc1\u0ba4\u0bcd\u0ba4\u0bb2\u0bcd \u0bb5\u0bc7\u0ba3\u0bcd\u0b9f\u0bc1\u0bae\u0bcd.", +"You have unsaved changes are you sure you want to navigate away?": "\u0b9a\u0bc7\u0bae\u0bbf\u0b95\u0bcd\u0b95\u0baa\u0bcd\u0baa\u0b9f\u0bbe\u0ba4 \u0bae\u0bbe\u0bb1\u0bcd\u0bb1\u0b99\u0bcd\u0b95\u0bb3\u0bcd \u0b89\u0bb3\u0bcd\u0bb3\u0ba9; \u0ba4\u0bbe\u0b99\u0bcd\u0b95\u0bb3\u0bcd \u0b89\u0bb1\u0bc1\u0ba4\u0bbf\u0baf\u0bbe\u0b95 \u0bb5\u0bc6\u0bb3\u0bbf\u0baf\u0bc7\u0bb1 \u0bb5\u0bbf\u0bb0\u0bc1\u0bae\u0bcd\u0baa\u0bc1\u0b95\u0bbf\u0bb1\u0bc0\u0bb0\u0bcd\u0b95\u0bbe\u0bb3\u0bbe?", +"Restore last draft": "\u0b95\u0b9f\u0ba8\u0bcd\u0ba4 \u0bb5\u0bb0\u0bc8\u0bb5\u0bc8 \u0bae\u0bc0\u0b9f\u0bcd\u0b9f\u0bc6\u0b9f\u0bc1\u0b95\u0bcd\u0b95", +"Special character": "\u0b9a\u0bbf\u0bb1\u0baa\u0bcd\u0baa\u0bc1 \u0b8e\u0bb4\u0bc1\u0ba4\u0bcd\u0ba4\u0bc1\u0bb0\u0bc1", +"Source code": "\u0bae\u0bc2\u0bb2 \u0b95\u0bc1\u0bb1\u0bbf\u0baf\u0bc0\u0b9f\u0bc1", +"Insert\/Edit code sample": "\u0b95\u0bc1\u0bb1\u0bbf\u0baf\u0bc0\u0b9f\u0bc1 \u0bae\u0bbe\u0ba4\u0bbf\u0bb0\u0bbf \u0b9a\u0bca\u0bb0\u0bc1\u0b95\u0bc1\u0b95\/\u0ba4\u0bca\u0b95\u0bc1\u0b95\u0bcd\u0b95", +"Language": "\u0bae\u0bca\u0bb4\u0bbf", +"Code sample": "\u0b95\u0bc1\u0bb1\u0bbf\u0baf\u0bc0\u0b9f\u0bc1 \u0bae\u0bbe\u0ba4\u0bbf\u0bb0\u0bbf", +"Color": "\u0ba8\u0bbf\u0bb1\u0bae\u0bcd", +"R": "R", +"G": "G", +"B": "B", +"Left to right": "\u0b87\u0b9f\u0bae\u0bbf\u0bb0\u0bc1\u0ba8\u0bcd\u0ba4\u0bc1 \u0bb5\u0bb2\u0bae\u0bcd", +"Right to left": "\u0bb5\u0bb2\u0bae\u0bbf\u0bb0\u0bc1\u0ba8\u0bcd\u0ba4\u0bc1 \u0b87\u0b9f\u0bae\u0bcd", +"Emoticons": "\u0b89\u0ba3\u0bb0\u0bcd\u0b9a\u0bcd\u0b9a\u0bbf\u0baa\u0bcd\u0baa\u0b9f\u0bbf\u0bae\u0b99\u0bcd\u0b95\u0bb3\u0bcd", +"Document properties": "\u0b86\u0bb5\u0ba3\u0ba4\u0bcd\u0ba4\u0bbf\u0ba9\u0bcd \u0baa\u0ba3\u0bcd\u0baa\u0bc1\u0b95\u0bb3\u0bcd", +"Title": "\u0ba4\u0bb2\u0bc8\u0baa\u0bcd\u0baa\u0bc1", +"Keywords": "\u0bae\u0bc1\u0ba4\u0ba9\u0bcd\u0bae\u0bc8\u0b9a\u0bcd\u0b9a\u0bca\u0bb1\u0bcd\u0b95\u0bb3\u0bcd", +"Description": "\u0bb5\u0bbf\u0bb5\u0bb0\u0bae\u0bcd", +"Robots": "\u0baa\u0bca\u0bb1\u0bbf\u0baf\u0ba9\u0bcd\u0b95\u0bb3\u0bcd (Robots)", +"Author": "\u0b8e\u0bb4\u0bc1\u0ba4\u0bcd\u0ba4\u0bbe\u0bb3\u0bb0\u0bcd", +"Encoding": "\u0b95\u0bc1\u0bb1\u0bbf\u0baf\u0bc0\u0b9f\u0bbe\u0b95\u0bcd\u0b95\u0bae\u0bcd", +"Fullscreen": "\u0bae\u0bc1\u0bb4\u0bc1\u0ba4\u0bcd\u0ba4\u0bbf\u0bb0\u0bc8", +"Action": "\u0b9a\u0bc6\u0baf\u0bb2\u0bcd", +"Shortcut": "\u0b95\u0bc1\u0bb1\u0bc1\u0b95\u0bcd\u0b95\u0bc1\u0bb5\u0bb4\u0bbf", +"Help": "\u0b89\u0ba4\u0bb5\u0bbf", +"Address": "\u0bae\u0bc1\u0b95\u0bb5\u0bb0\u0bbf", +"Focus to menubar": "\u0baa\u0b9f\u0bcd\u0b9f\u0bbf\u0baa\u0bcd\u0baa\u0b9f\u0bcd\u0b9f\u0bc8\u0baf\u0bbf\u0bb2\u0bcd \u0b95\u0bb5\u0ba9\u0bae\u0bcd \u0b9a\u0bc6\u0bb2\u0bc1\u0ba4\u0bcd\u0ba4\u0bc1\u0b95", +"Focus to toolbar": "\u0b95\u0bb0\u0bc1\u0bb5\u0bbf\u0baa\u0bcd\u0baa\u0b9f\u0bcd\u0b9f\u0bc8\u0baf\u0bbf\u0bb2\u0bcd \u0b95\u0bb5\u0ba9\u0bae\u0bcd \u0b9a\u0bc6\u0bb2\u0bc1\u0ba4\u0bcd\u0ba4\u0bc1\u0b95", +"Focus to element path": "\u0bae\u0bc2\u0bb2\u0b95\u0baa\u0bcd \u0baa\u0bbe\u0ba4\u0bc8\u0baf\u0bbf\u0bb2\u0bcd \u0b95\u0bb5\u0ba9\u0bae\u0bcd \u0b9a\u0bc6\u0bb2\u0bc1\u0ba4\u0bcd\u0ba4\u0bc1\u0b95", +"Focus to contextual toolbar": "\u0b9a\u0bc2\u0bb4\u0bcd\u0ba8\u0bbf\u0bb2\u0bc8 \u0b95\u0bb0\u0bc1\u0bb5\u0bbf\u0baa\u0bcd\u0baa\u0b9f\u0bcd\u0b9f\u0bc8\u0baf\u0bbf\u0bb2\u0bcd \u0b95\u0bb5\u0ba9\u0bae\u0bcd \u0b9a\u0bc6\u0bb2\u0bc1\u0ba4\u0bcd\u0ba4\u0bc1\u0b95", +"Insert link (if link plugin activated)": "\u0b87\u0ba3\u0bc8\u0baa\u0bcd\u0baa\u0bc1 \u0b9a\u0bc6\u0bb0\u0bc1\u0b95\u0bc1\u0b95 (\u0b87\u0ba3\u0bc8\u0baa\u0bcd\u0baa\u0bc1 \u0b9a\u0bca\u0bb0\u0bc1\u0b95\u0bbf \u0b9a\u0bc6\u0baf\u0bb2\u0bbe\u0b95\u0bcd\u0b95\u0baa\u0bcd\u0baa\u0b9f\u0bc1\u0ba4\u0bcd\u0ba4\u0bbf\u0baf\u0bbf\u0bb0\u0bc1\u0ba8\u0bcd\u0ba4\u0bbe\u0bb2\u0bcd)", +"Save (if save plugin activated)": "\u0b9a\u0bc7\u0bae\u0bbf\u0b95\u0bcd\u0b95 (\u0b9a\u0bc7\u0bae\u0bbf\u0baa\u0bcd\u0baa\u0bc1 \u0b9a\u0bca\u0bb0\u0bc1\u0b95\u0bbf \u0b9a\u0bc6\u0baf\u0bb2\u0bbe\u0b95\u0bcd\u0b95\u0baa\u0bcd\u0baa\u0b9f\u0bc1\u0ba4\u0bcd\u0ba4\u0bbf\u0baf\u0bbf\u0bb0\u0bc1\u0ba8\u0bcd\u0ba4\u0bbe\u0bb2\u0bcd)", +"Find (if searchreplace plugin activated)": "\u0b95\u0ba3\u0bcd\u0b9f\u0bc1\u0baa\u0bbf\u0b9f\u0bbf\u0b95\u0bcd\u0b95 (\u0ba4\u0bc7\u0b9f\u0bbf\u0bae\u0bbe\u0bb1\u0bcd\u0bb1\u0bb2\u0bcd \u0b9a\u0bca\u0bb0\u0bc1\u0b95\u0bbf \u0b9a\u0bc6\u0baf\u0bb2\u0bbe\u0b95\u0bcd\u0b95\u0baa\u0bcd\u0baa\u0b9f\u0bc1\u0ba4\u0bcd\u0ba4\u0bbf\u0baf\u0bbf\u0bb0\u0bc1\u0ba8\u0bcd\u0ba4\u0bbe\u0bb2\u0bcd)", +"Plugins installed ({0}):": "\u0ba8\u0bbf\u0bb1\u0bc1\u0bb5\u0baa\u0bcd\u0baa\u0b9f\u0bcd\u0b9f\u0bc1\u0bb3\u0bcd\u0bb3 \u0b9a\u0bca\u0bb0\u0bc1\u0b95\u0bbf\u0b95\u0bb3\u0bcd ({0}):", +"Premium plugins:": "\u0b89\u0baf\u0bb0\u0bcd\u0bae\u0ba4\u0bbf\u0baa\u0bcd\u0baa\u0bc1 \u0b9a\u0bca\u0bb0\u0bc1\u0b95\u0bbf\u0b95\u0bb3\u0bcd:", +"Learn more...": "\u0bae\u0bc7\u0bb2\u0bc1\u0bae\u0bcd \u0b85\u0bb1\u0bbf\u0b95...", +"You are using {0}": "\u0ba4\u0bbe\u0b99\u0bcd\u0b95\u0bb3\u0bcd \u0baa\u0baf\u0ba9\u0bcd\u0baa\u0b9f\u0bc1\u0ba4\u0bcd\u0ba4\u0bc1\u0bb5\u0ba4\u0bc1 {0}", +"Plugins": "\u0b9a\u0bca\u0bb0\u0bc1\u0b95\u0bbf\u0b95\u0bb3\u0bcd", +"Handy Shortcuts": "\u0b8e\u0bb3\u0bbf\u0ba4\u0bbf\u0bb2\u0bcd \u0b95\u0bc8\u0baf\u0bbe\u0bb3\u0b95\u0bcd\u0b95\u0bc2\u0b9f\u0bbf\u0baf \u0b95\u0bc1\u0bb1\u0bc1\u0b95\u0bcd\u0b95\u0bc1\u0bb5\u0bb4\u0bbf\u0b95\u0bb3\u0bcd", +"Horizontal line": "\u0b95\u0bbf\u0b9f\u0bc8 \u0b95\u0bcb\u0b9f\u0bc1", +"Insert\/edit image": "\u0baa\u0b9f\u0bae\u0bcd \u0b9a\u0bca\u0bb0\u0bc1\u0b95\u0bc1\u0b95\/\u0ba4\u0bca\u0b95\u0bc1\u0b95\u0bcd\u0b95", +"Image description": "\u0baa\u0b9f \u0bb5\u0bbf\u0bb5\u0bb0\u0bae\u0bcd", +"Source": "\u0bae\u0bc2\u0bb2\u0bae\u0bcd", +"Dimensions": "\u0baa\u0bb0\u0bbf\u0bae\u0bbe\u0ba3\u0b99\u0bcd\u0b95\u0bb3\u0bcd", +"Constrain proportions": "\u0bb5\u0bbf\u0b95\u0bbf\u0ba4\u0bbe\u0b9a\u0bcd\u0b9a\u0bbe\u0bb0\u0ba4\u0bcd\u0ba4\u0bbf\u0bb2\u0bcd \u0b95\u0b9f\u0bcd\u0b9f\u0bc1\u0baa\u0bcd\u0baa\u0b9f\u0bc1\u0ba4\u0bcd\u0ba4\u0bc1\u0b95", +"General": "\u0baa\u0bca\u0ba4\u0bc1", +"Advanced": "\u0bae\u0bc7\u0bae\u0bcd\u0baa\u0b9f\u0bcd\u0b9f\u0ba4\u0bc1", +"Style": "\u0baa\u0bbe\u0ba3\u0bbf", +"Vertical space": "\u0ba8\u0bc6\u0b9f\u0bc1\u0ba4\u0bb3 \u0b87\u0b9f\u0bc8\u0bb5\u0bc6\u0bb3\u0bbf", +"Horizontal space": "\u0b95\u0bbf\u0b9f\u0bc8\u0bae\u0b9f\u0bcd\u0b9f \u0b87\u0b9f\u0bc8\u0bb5\u0bc6\u0bb3\u0bbf", +"Border": "\u0b95\u0bb0\u0bc8", +"Insert image": "\u0baa\u0b9f\u0bae\u0bcd \u0b9a\u0bca\u0bb0\u0bc1\u0b95\u0bc1\u0b95", +"Image": "\u0baa\u0b9f\u0bae\u0bcd", +"Image list": "\u0baa\u0b9f\u0baa\u0bcd \u0baa\u0b9f\u0bcd\u0b9f\u0bbf\u0baf\u0bb2\u0bcd", +"Rotate counterclockwise": "\u0b95\u0b9f\u0bbf\u0b95\u0bbe\u0bb0 \u0b8e\u0ba4\u0bbf\u0bb0\u0bcd\u0ba4\u0bbf\u0b9a\u0bc8\u0baf\u0bbf\u0bb2\u0bcd \u0b9a\u0bc1\u0bb4\u0bb1\u0bcd\u0bb1\u0bc1", +"Rotate clockwise": "\u0b95\u0b9f\u0bbf\u0b95\u0bbe\u0bb0\u0ba4\u0bcd\u0ba4\u0bbf\u0b9a\u0bc8\u0baf\u0bbf\u0bb2\u0bcd \u0b9a\u0bc1\u0bb4\u0bb1\u0bcd\u0bb1\u0bc1", +"Flip vertically": "\u0b9a\u0bc6\u0b99\u0bcd\u0b95\u0bc1\u0ba4\u0bcd\u0ba4\u0bbe\u0b95 \u0baa\u0bc1\u0bb0\u0b9f\u0bcd\u0b9f\u0bc1", +"Flip horizontally": "\u0b95\u0bbf\u0b9f\u0bc8\u0bae\u0b9f\u0bcd\u0b9f\u0bae\u0bbe\u0b95 \u0baa\u0bc1\u0bb0\u0b9f\u0bcd\u0b9f\u0bc1", +"Edit image": "\u0baa\u0b9f\u0ba4\u0bcd\u0ba4\u0bc8 \u0ba4\u0bca\u0b95\u0bc1", +"Image options": "\u0baa\u0b9f \u0bb5\u0bbf\u0bb0\u0bc1\u0baa\u0bcd\u0baa\u0bc1\u0b95\u0bb3\u0bcd", +"Zoom in": "\u0baa\u0bc6\u0bb0\u0bbf\u0ba4\u0bbe\u0b95\u0bcd\u0b95\u0bc1", +"Zoom out": "\u0b9a\u0bbf\u0bb1\u0bbf\u0ba4\u0bbe\u0b95\u0bcd\u0b95\u0bc1", +"Crop": "\u0b9a\u0bc6\u0ba4\u0bc1\u0b95\u0bcd\u0b95\u0bc1", +"Resize": "\u0bae\u0bb1\u0bc1\u0b85\u0bb3\u0bb5\u0bbf\u0b9f\u0bc1", +"Orientation": "\u0ba4\u0bbf\u0b9a\u0bc8\u0baf\u0bae\u0bc8\u0bb5\u0bc1", +"Brightness": "\u0b92\u0bb3\u0bbf\u0bb0\u0bcd\u0bb5\u0bc1", +"Sharpen": "\u0b95\u0bc2\u0bb0\u0bcd\u0bae\u0bc8\u0baf\u0bbe\u0b95\u0bcd\u0b95\u0bc1", +"Contrast": "\u0ba8\u0bbf\u0bb1\u0bae\u0bbe\u0bb1\u0bc1\u0baa\u0bbe\u0b9f\u0bc1", +"Color levels": "\u0bb5\u0ba3\u0bcd\u0ba3 \u0ba8\u0bbf\u0bb2\u0bc8\u0b95\u0bb3\u0bcd", +"Gamma": "Gamma", +"Invert": "\u0ba8\u0bc7\u0bb0\u0bcd\u0bae\u0bbe\u0bb1\u0bcd\u0bb1\u0bc1", +"Apply": "\u0baa\u0baf\u0ba9\u0bcd\u0baa\u0b9f\u0bc1\u0ba4\u0bcd\u0ba4\u0bc1", +"Back": "\u0baa\u0bbf\u0ba9\u0bcd", +"Insert date\/time": "\u0ba4\u0bc7\u0ba4\u0bbf\/\u0ba8\u0bc7\u0bb0\u0bae\u0bcd \u0b9a\u0bca\u0bb0\u0bc1\u0b95\u0bc1\u0b95", +"Date\/time": "\u0ba4\u0bc7\u0ba4\u0bbf\/\u0ba8\u0bc7\u0bb0\u0bae\u0bcd", +"Insert link": "\u0b87\u0ba3\u0bc8\u0baa\u0bcd\u0baa\u0bc1 \u0b9a\u0bca\u0bb0\u0bc1\u0b95\u0bc1\u0b95", +"Insert\/edit link": "\u0b87\u0ba3\u0bc8\u0baa\u0bcd\u0baa\u0bc1 \u0b9a\u0bca\u0bb0\u0bc1\u0b95\u0bc1\u0b95\/\u0ba4\u0bca\u0b95\u0bc1\u0b95\u0bcd\u0b95", +"Text to display": "\u0b95\u0bbe\u0b9f\u0bcd\u0b9a\u0bbf\u0baa\u0bcd\u0baa\u0b9f\u0bc1\u0ba4\u0bcd\u0ba4 \u0bb5\u0bc7\u0ba3\u0bcd\u0b9f\u0bbf\u0baf \u0b89\u0bb0\u0bc8", +"Url": "\u0b87\u0ba3\u0bc8\u0baf\u0bae\u0bc1\u0b95\u0bb5\u0bb0\u0bbf", +"Target": "\u0b87\u0bb2\u0b95\u0bcd\u0b95\u0bc1", +"None": "\u0b8f\u0ba4\u0bc1\u0bae\u0bbf\u0bb2\u0bcd\u0bb2\u0bc8", +"New window": "\u0baa\u0bc1\u0ba4\u0bbf\u0baf \u0b9a\u0bbe\u0bb3\u0bb0\u0bae\u0bcd", +"Remove link": "\u0b87\u0ba3\u0bc8\u0baa\u0bcd\u0baa\u0bc8 \u0b85\u0b95\u0bb1\u0bcd\u0bb1\u0bc1\u0b95", +"Anchors": "\u0ba8\u0b99\u0bcd\u0b95\u0bc2\u0bb0\u0b99\u0bcd\u0b95\u0bb3\u0bcd", +"Link": "\u0b87\u0ba3\u0bc8\u0baa\u0bcd\u0baa\u0bc1", +"Paste or type a link": "\u0b92\u0bb0\u0bc1 \u0b87\u0ba3\u0bc8\u0baa\u0bcd\u0baa\u0bc1 \u0b92\u0b9f\u0bcd\u0b9f\u0bc1\u0b95 \u0b85\u0bb2\u0bcd\u0bb2\u0ba4\u0bc1 \u0ba4\u0b9f\u0bcd\u0b9f\u0b9a\u0bcd\u0b9a\u0bbf\u0b9f\u0bc1\u0b95", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "\u0ba4\u0bbe\u0b99\u0bcd\u0b95\u0bb3\u0bcd \u0b89\u0bb3\u0bcd\u0bb3\u0bbf\u0b9f\u0bcd\u0b9f \u0b87\u0ba3\u0bc8\u0baf\u0bae\u0bc1\u0b95\u0bb5\u0bb0\u0bbf (URL) \u0b92\u0bb0\u0bc1 \u0bae\u0bbf\u0ba9\u0bcd-\u0b85\u0b9e\u0bcd\u0b9a\u0bb2\u0bcd \u0bae\u0bc1\u0b95\u0bb5\u0bb0\u0bbf \u0baa\u0bcb\u0bb2\u0bcd \u0ba4\u0bcb\u0ba9\u0bcd\u0bb1\u0bc1\u0b95\u0bbf\u0bb1\u0ba4\u0bc1. \u0ba4\u0bc7\u0bb5\u0bc8\u0baf\u0bbe\u0ba9 mailto: \u0bae\u0bc1\u0ba9\u0bcd-\u0b92\u0b9f\u0bcd\u0b9f\u0bc8\u0ba4\u0bcd (prefix) \u0ba4\u0bbe\u0b99\u0bcd\u0b95\u0bb3\u0bcd \u0b9a\u0bc7\u0bb0\u0bcd\u0b95\u0bcd\u0b95 \u0bb5\u0bc7\u0ba3\u0bcd\u0b9f\u0bc1\u0bae\u0bbe?", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "\u0ba4\u0bbe\u0b99\u0bcd\u0b95\u0bb3\u0bcd \u0b89\u0bb3\u0bcd\u0bb3\u0bbf\u0b9f\u0bcd\u0b9f \u0b87\u0ba3\u0bc8\u0baf\u0bae\u0bc1\u0b95\u0bb5\u0bb0\u0bbf (URL) \u0b92\u0bb0\u0bc1 \u0bb5\u0bc6\u0bb3\u0bbf\u0baa\u0bcd\u0baa\u0bc1\u0bb1 \u0b87\u0ba3\u0bc8\u0baa\u0bcd\u0baa\u0bc1 (external link) \u0baa\u0bcb\u0bb2\u0bcd \u0ba4\u0bcb\u0ba9\u0bcd\u0bb1\u0bc1\u0b95\u0bbf\u0bb1\u0ba4\u0bc1. \u0ba4\u0bc7\u0bb5\u0bc8\u0baf\u0bbe\u0ba9 http:\/\/ \u0bae\u0bc1\u0ba9\u0bcd-\u0b92\u0b9f\u0bcd\u0b9f\u0bc8\u0ba4\u0bcd (prefix) \u0ba4\u0bbe\u0b99\u0bcd\u0b95\u0bb3\u0bcd \u0b9a\u0bc7\u0bb0\u0bcd\u0b95\u0bcd\u0b95 \u0bb5\u0bc7\u0ba3\u0bcd\u0b9f\u0bc1\u0bae\u0bbe?", +"Link list": "\u0b87\u0ba3\u0bc8\u0baa\u0bcd\u0baa\u0bc1\u0baa\u0bcd \u0baa\u0b9f\u0bcd\u0b9f\u0bbf\u0baf\u0bb2\u0bcd", +"Insert video": "\u0b95\u0bbe\u0ba3\u0bca\u0bb3\u0bbf \u0b9a\u0bca\u0bb0\u0bc1\u0b95\u0bc1\u0b95", +"Insert\/edit video": "\u0b95\u0bbe\u0ba3\u0bca\u0bb3\u0bbf \u0b9a\u0bca\u0bb0\u0bc1\u0b95\u0bc1\u0b95\/\u0ba4\u0bca\u0b95\u0bc1\u0b95\u0bcd\u0b95", +"Insert\/edit media": "\u0b8a\u0b9f\u0b95\u0bae\u0bcd \u0b9a\u0bca\u0bb0\u0bc1\u0b95\u0bc1\u0b95\/\u0ba4\u0bca\u0b95\u0bc1\u0b95\u0bcd\u0b95", +"Alternative source": "\u0bae\u0bbe\u0bb1\u0bcd\u0bb1\u0bc1 \u0bae\u0bc2\u0bb2\u0bae\u0bcd", +"Poster": "\u0b9a\u0bc1\u0bb5\u0bb0\u0bca\u0b9f\u0bcd\u0b9f\u0bbf", +"Paste your embed code below:": "\u0ba4\u0b99\u0bcd\u0b95\u0bb3\u0bcd \u0b89\u0b9f\u0bcd\u0baa\u0bc6\u0bbe\u0ba4\u0bbf \u0b95\u0bc1\u0bb1\u0bbf\u0baf\u0bc0\u0b9f\u0bcd\u0b9f\u0bc8 \u0b95\u0bc0\u0bb4\u0bc7 \u0b92\u0b9f\u0bcd\u0b9f\u0bb5\u0bc1\u0bae\u0bcd:", +"Embed": "\u0b89\u0b9f\u0bcd\u0baa\u0bca\u0ba4\u0bbf", +"Media": "\u0b8a\u0b9f\u0b95\u0bae\u0bcd", +"Nonbreaking space": "\u0baa\u0bbf\u0bb0\u0bbf\u0baf\u0bbe\u0ba4 \u0b87\u0b9f\u0bc8\u0bb5\u0bc6\u0bb3\u0bbf", +"Page break": "\u0baa\u0b95\u0bcd\u0b95 \u0baa\u0bbf\u0bb0\u0bbf\u0baa\u0bcd\u0baa\u0bc1", +"Paste as text": "\u0b89\u0bb0\u0bc8\u0baf\u0bbe\u0b95 \u0b92\u0b9f\u0bcd\u0b9f\u0bc1\u0b95", +"Preview": "\u0bae\u0bc1\u0ba9\u0bcd\u0ba8\u0bcb\u0b95\u0bcd\u0b95\u0bc1", +"Print": "\u0b85\u0b9a\u0bcd\u0b9a\u0bbf\u0b9f\u0bc1\u0b95", +"Save": "\u0b9a\u0bc7\u0bae\u0bbf\u0b95\u0bcd\u0b95", +"Find": "\u0b95\u0ba3\u0bcd\u0b9f\u0bc1\u0baa\u0bbf\u0b9f\u0bbf\u0b95\u0bcd\u0b95", +"Replace with": "\u0b87\u0ba4\u0ba9\u0bc1\u0b9f\u0ba9\u0bcd \u0bae\u0bbe\u0bb1\u0bcd\u0bb1\u0bc1\u0b95", +"Replace": "\u0bae\u0bbe\u0bb1\u0bcd\u0bb1\u0bc1\u0b95", +"Replace all": "\u0b85\u0ba9\u0bc8\u0ba4\u0bcd\u0ba4\u0bc8\u0baf\u0bc1\u0bae\u0bcd \u0bae\u0bbe\u0bb1\u0bcd\u0bb1\u0bc1\u0b95", +"Prev": "\u0bae\u0bc1\u0ba8\u0bcd\u0ba4\u0bc8\u0baf", +"Next": "\u0b85\u0b9f\u0bc1\u0ba4\u0bcd\u0ba4", +"Find and replace": "\u0b95\u0ba3\u0bcd\u0b9f\u0bc1\u0baa\u0bbf\u0b9f\u0bbf\u0ba4\u0bcd\u0ba4\u0bc1 \u0bae\u0bbe\u0bb1\u0bcd\u0bb1\u0bc1\u0b95", +"Could not find the specified string.": "\u0b95\u0bc1\u0bb1\u0bbf\u0baa\u0bcd\u0baa\u0bbf\u0b9f\u0bcd\u0b9f \u0b9a\u0bb0\u0bae\u0bcd \u0b95\u0ba3\u0bcd\u0b9f\u0bc1\u0baa\u0bbf\u0b9f\u0bbf\u0b95\u0bcd\u0b95 \u0bae\u0bc1\u0b9f\u0bbf\u0baf\u0bb5\u0bbf\u0bb2\u0bcd\u0bb2\u0bc8", +"Match case": "\u0bb5\u0b9f\u0bbf\u0bb5\u0ba4\u0bcd\u0ba4\u0bc8 \u0baa\u0bca\u0bb0\u0bc1\u0ba4\u0bcd\u0ba4\u0bc1\u0b95", +"Whole words": "\u0bae\u0bc1\u0bb4\u0bc1 \u0b9a\u0bca\u0bb1\u0bcd\u0b95\u0bb3\u0bcd", +"Spellcheck": "\u0b8e\u0bb4\u0bc1\u0ba4\u0bcd\u0ba4\u0bc1\u0baa\u0bcd\u0baa\u0bbf\u0bb4\u0bc8\u0baf\u0bc8 \u0b9a\u0bb0\u0bbf\u0baa\u0bbe\u0bb0\u0bcd\u0b95\u0bcd\u0b95", +"Ignore": "\u0baa\u0bc1\u0bb1\u0b95\u0bcd\u0b95\u0ba3\u0bbf\u0b95\u0bcd\u0b95", +"Ignore all": "\u0b85\u0ba9\u0bc8\u0ba4\u0bcd\u0ba4\u0bc8\u0baf\u0bc1\u0bae\u0bcd \u0baa\u0bc1\u0bb1\u0b95\u0bcd\u0b95\u0ba3\u0bbf\u0b95\u0bcd\u0b95", +"Finish": "\u0bae\u0bc1\u0b9f\u0bbf\u0b95\u0bcd\u0b95", +"Add to Dictionary": "\u0b85\u0b95\u0bb0\u0bbe\u0ba4\u0bbf\u0baf\u0bbf\u0bb2\u0bcd \u0b9a\u0bc7\u0bb0\u0bcd\u0b95\u0bcd\u0b95", +"Insert table": "\u0b85\u0b9f\u0bcd\u0b9f\u0bb5\u0ba3\u0bc8 \u0b9a\u0bca\u0bb0\u0bc1\u0b95\u0bc1\u0b95", +"Table properties": "\u0b85\u0b9f\u0bcd\u0b9f\u0bb5\u0ba3\u0bc8 \u0baa\u0ba3\u0bcd\u0baa\u0bc1\u0b95\u0bb3\u0bcd", +"Delete table": "\u0b85\u0b9f\u0bcd\u0b9f\u0bb5\u0ba3\u0bc8 \u0ba8\u0bc0\u0b95\u0bcd\u0b95\u0bc1\u0b95", +"Cell": "\u0b9a\u0bbf\u0bb1\u0bcd\u0bb1\u0bb1\u0bc8", +"Row": "\u0bb5\u0bb0\u0bbf\u0b9a\u0bc8", +"Column": "\u0ba8\u0bc6\u0b9f\u0bc1\u0bb5\u0bb0\u0bbf\u0b9a\u0bc8", +"Cell properties": "\u0b9a\u0bbf\u0bb1\u0bcd\u0bb1\u0bb1\u0bc8 \u0baa\u0ba3\u0bcd\u0baa\u0bc1\u0b95\u0bb3\u0bcd", +"Merge cells": "\u0b9a\u0bbf\u0bb1\u0bcd\u0bb1\u0bb1\u0bc8\u0b95\u0bb3\u0bcd \u0b9a\u0bc7\u0bb0\u0bcd\u0b95\u0bcd\u0b95", +"Split cell": "\u0b9a\u0bbf\u0bb1\u0bcd\u0bb1\u0bb1\u0bc8 \u0baa\u0bbf\u0bb0\u0bbf\u0b95\u0bcd\u0b95", +"Insert row before": "\u0b87\u0ba4\u0bb1\u0bcd\u0b95\u0bc1 \u0bae\u0bc1\u0ba9\u0bcd \u0bb5\u0bb0\u0bbf\u0b9a\u0bc8 \u0b9a\u0bca\u0bb0\u0bc1\u0b95\u0bc1\u0b95", +"Insert row after": "\u0b87\u0ba4\u0bb1\u0bcd\u0b95\u0bc1 \u0baa\u0bbf\u0ba9\u0bcd \u0bb5\u0bb0\u0bbf\u0b9a\u0bc8 \u0b9a\u0bca\u0bb0\u0bc1\u0b95\u0bc1\u0b95", +"Delete row": "\u0bb5\u0bb0\u0bbf\u0b9a\u0bc8 \u0ba8\u0bc0\u0b95\u0bcd\u0b95\u0bc1\u0b95", +"Row properties": "\u0bb5\u0bb0\u0bbf\u0b9a\u0bc8 \u0baa\u0ba3\u0bcd\u0baa\u0bc1\u0b95\u0bb3\u0bcd", +"Cut row": "\u0bb5\u0bb0\u0bbf\u0b9a\u0bc8 \u0bb5\u0bc6\u0b9f\u0bcd\u0b9f\u0bc1\u0b95", +"Copy row": "\u0bb5\u0bb0\u0bbf\u0b9a\u0bc8 \u0ba8\u0b95\u0bb2\u0bc6\u0b9f\u0bc1\u0b95\u0bcd\u0b95", +"Paste row before": "\u0b87\u0ba4\u0bb1\u0bcd\u0b95\u0bc1 \u0bae\u0bc1\u0ba9\u0bcd \u0bb5\u0bb0\u0bbf\u0b9a\u0bc8 \u0b92\u0b9f\u0bcd\u0b9f\u0bc1\u0b95", +"Paste row after": "\u0b87\u0ba4\u0bb1\u0bcd\u0b95\u0bc1 \u0baa\u0bbf\u0ba9\u0bcd \u0bb5\u0bb0\u0bbf\u0b9a\u0bc8 \u0b92\u0b9f\u0bcd\u0b9f\u0bc1\u0b95", +"Insert column before": "\u0b87\u0ba4\u0bb1\u0bcd\u0b95\u0bc1 \u0bae\u0bc1\u0ba9\u0bcd \u0ba8\u0bc6\u0b9f\u0bc1\u0bb5\u0bb0\u0bbf\u0b9a\u0bc8 \u0b9a\u0bca\u0bb0\u0bc1\u0b95\u0bc1\u0b95", +"Insert column after": "\u0b87\u0ba4\u0bb1\u0bcd\u0b95\u0bc1 \u0baa\u0bbf\u0ba9\u0bcd \u0ba8\u0bc6\u0b9f\u0bc1\u0bb5\u0bb0\u0bbf\u0b9a\u0bc8 \u0b9a\u0bca\u0bb0\u0bc1\u0b95\u0bc1\u0b95", +"Delete column": "\u0ba8\u0bc6\u0b9f\u0bc1\u0bb5\u0bb0\u0bbf\u0b9a\u0bc8 \u0ba8\u0bc0\u0b95\u0bcd\u0b95\u0bc1\u0b95", +"Cols": "\u0ba8\u0bc6\u0b9f\u0bc1\u0bb5\u0bb0\u0bbf\u0b9a\u0bc8\u0b95\u0bb3\u0bcd", +"Rows": "\u0bb5\u0bb0\u0bbf\u0b9a\u0bc8\u0b95\u0bb3\u0bcd", +"Width": "\u0b85\u0b95\u0bb2\u0bae\u0bcd", +"Height": "\u0b89\u0baf\u0bb0\u0bae\u0bcd", +"Cell spacing": "\u0b9a\u0bbf\u0bb1\u0bcd\u0bb1\u0bb1\u0bc8 \u0b87\u0b9f\u0bc8\u0bb5\u0bc6\u0bb3\u0bbf", +"Cell padding": "\u0b9a\u0bbf\u0bb1\u0bcd\u0bb1\u0bb1\u0bc8 \u0ba8\u0bbf\u0bb0\u0baa\u0bcd\u0baa\u0bb2\u0bcd", +"Caption": "\u0ba4\u0bb2\u0bc8\u0baa\u0bcd\u0baa\u0bc1", +"Left": "\u0b87\u0b9f\u0bae\u0bcd", +"Center": "\u0bae\u0bc8\u0baf\u0bae\u0bcd", +"Right": "\u0bb5\u0bb2\u0bae\u0bcd", +"Cell type": "\u0b9a\u0bbf\u0bb1\u0bcd\u0bb1\u0bb1\u0bc8 \u0bb5\u0b95\u0bc8", +"Scope": "\u0bb5\u0bb0\u0bc8\u0baf\u0bc6\u0bb2\u0bcd\u0bb2\u0bc8", +"Alignment": "\u0b9a\u0bc0\u0bb0\u0bae\u0bc8\u0bb5\u0bc1", +"H Align": "\u0b95\u0bbf (H) \u0b87\u0b9a\u0bc8\u0bb5\u0bc1", +"V Align": "\u0b9a\u0bc6 (V) \u0b87\u0b9a\u0bc8\u0bb5\u0bc1", +"Top": "\u0bae\u0bc7\u0bb2\u0bcd", +"Middle": "\u0ba8\u0b9f\u0bc1", +"Bottom": "\u0bae\u0bc7\u0bb2\u0bcd", +"Header cell": "\u0ba4\u0bb2\u0bc8\u0baa\u0bcd\u0baa\u0bc1 \u0b9a\u0bbf\u0bb1\u0bcd\u0bb1\u0bb1\u0bc8", +"Row group": "\u0bb5\u0bb0\u0bbf\u0b9a\u0bc8 \u0b95\u0bc1\u0bb4\u0bc1", +"Column group": "\u0ba8\u0bc6\u0b9f\u0bc1\u0bb5\u0bb0\u0bbf\u0b9a\u0bc8 \u0b95\u0bc1\u0bb4\u0bc1", +"Row type": "\u0bb5\u0bb0\u0bbf\u0b9a\u0bc8 \u0bb5\u0b95\u0bc8", +"Header": "\u0ba4\u0bb2\u0bc8\u0baa\u0bcd\u0baa\u0bc1", +"Body": "\u0b89\u0b9f\u0bb2\u0bcd", +"Footer": "\u0b85\u0b9f\u0bbf\u0b95\u0bcd\u0b95\u0bc1\u0bb1\u0bbf\u0baa\u0bcd\u0baa\u0bc1", +"Border color": "\u0b95\u0bb0\u0bc8\u0baf\u0bbf\u0ba9\u0bcd \u0ba8\u0bbf\u0bb1\u0bae\u0bcd", +"Insert template": "\u0bb5\u0bbe\u0bb0\u0bcd\u0baa\u0bcd\u0baa\u0bc1\u0bb0\u0bc1 \u0b9a\u0bca\u0bb0\u0bc1\u0b95\u0bc1\u0b95", +"Templates": "\u0bb5\u0bbe\u0bb0\u0bcd\u0baa\u0bcd\u0baa\u0bc1\u0bb0\u0bc1\u0b95\u0bcd\u0b95\u0bb3\u0bcd", +"Template": "\u0bb5\u0bbe\u0bb0\u0bcd\u0baa\u0bcd\u0baa\u0bc1\u0bb0\u0bc1", +"Text color": "\u0b89\u0bb0\u0bc8\u0baf\u0bbf\u0ba9\u0bcd \u0ba8\u0bbf\u0bb1\u0bae\u0bcd", +"Background color": "\u0baa\u0bbf\u0ba9\u0bcd\u0ba9\u0ba3\u0bbf \u0ba8\u0bbf\u0bb1\u0bae\u0bcd", +"Custom...": "\u0ba4\u0ba9\u0bbf\u0baa\u0bcd\u0baa\u0baf\u0ba9\u0bcd...", +"Custom color": "\u0ba4\u0ba9\u0bbf\u0baa\u0bcd\u0baa\u0baf\u0ba9\u0bcd \u0ba8\u0bbf\u0bb1\u0bae\u0bcd", +"No color": "\u0ba8\u0bbf\u0bb1\u0bae\u0bcd \u0b87\u0bb2\u0bcd\u0bb2\u0bc8", +"Table of Contents": "\u0baa\u0bca\u0bb0\u0bc1\u0bb3\u0b9f\u0b95\u0bcd\u0b95\u0bae\u0bcd", +"Show blocks": "\u0ba4\u0bca\u0b95\u0bc1\u0ba4\u0bbf\u0b95\u0bb3\u0bc8 \u0b95\u0bbe\u0b9f\u0bcd\u0b9f\u0bc1\u0b95", +"Show invisible characters": "\u0b95\u0ba3\u0bcd\u0ba3\u0bc1\u0b95\u0bcd\u0b95\u0bc1\u0ba4\u0bcd \u0ba4\u0bc6\u0bb0\u0bbf\u0baf\u0bbe\u0ba4 \u0b89\u0bb0\u0bc1\u0b95\u0bcd\u0b95\u0bb3\u0bc8 \u0b95\u0bbe\u0b9f\u0bcd\u0b9f\u0bc1\u0b95", +"Words: {0}": "\u0b9a\u0bca\u0bb1\u0bcd\u0b95\u0bb3\u0bcd: {0}", +"{0} words": "{0} \u0b9a\u0bca\u0bb1\u0bcd\u0b95\u0bb3\u0bcd", +"File": "\u0b95\u0bcb\u0baa\u0bcd\u0baa\u0bc1", +"Edit": "\u0ba4\u0bca\u0b95\u0bc1\u0b95\u0bcd\u0b95", +"Insert": "\u0b9a\u0bca\u0bb0\u0bc1\u0b95\u0bc1\u0b95", +"View": "\u0ba8\u0bcb\u0b95\u0bcd\u0b95\u0bc1\u0b95", +"Format": "\u0bb5\u0b9f\u0bbf\u0bb5\u0bae\u0bc8\u0baa\u0bcd\u0baa\u0bc1", +"Table": "\u0b85\u0b9f\u0bcd\u0b9f\u0bb5\u0ba3\u0bc8", +"Tools": "\u0b95\u0bb0\u0bc1\u0bb5\u0bbf\u0b95\u0bb3\u0bcd", +"Powered by {0}": "\u0bb5\u0bb2\u0bc1\u0bb5\u0bb3\u0bbf\u0baa\u0bcd\u0baa\u0ba4\u0bc1 {0}", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "\u0b89\u0baf\u0bb0\u0bcd \u0b89\u0bb0\u0bc8 \u0baa\u0b95\u0bc1\u0ba4\u0bbf. \u0baa\u0b9f\u0bcd\u0b9f\u0bbf\u0b95\u0bcd\u0b95\u0bc1 ALT-F9 , \u0b95\u0bb0\u0bc1\u0bb5\u0bbf\u0baa\u0bcd\u0baa\u0b9f\u0bcd\u0b9f\u0bc8\u0b95\u0bcd\u0b95\u0bc1 ALT-F10 , \u0b89\u0ba4\u0bb5\u0bbf\u0b95\u0bcd\u0b95\u0bc1 ALT-0" +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/th_TH.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/th_TH.js new file mode 100644 index 0000000000..9e42a9243a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/th_TH.js @@ -0,0 +1,261 @@ +tinymce.addI18n('th_TH',{ +"Redo": "\u0e17\u0e4d\u0e32\u0e0b\u0e49\u0e33", +"Undo": "\u0e40\u0e25\u0e34\u0e01\u0e17\u0e33", +"Cut": "\u0e15\u0e31\u0e14", +"Copy": "\u0e04\u0e31\u0e14\u0e25\u0e2d\u0e01", +"Paste": "\u0e27\u0e32\u0e07", +"Select all": "\u0e40\u0e25\u0e37\u0e2d\u0e01\u0e17\u0e31\u0e49\u0e07\u0e2b\u0e21\u0e14", +"New document": "\u0e40\u0e2d\u0e01\u0e2a\u0e32\u0e23\u0e43\u0e2b\u0e21\u0e48", +"Ok": "\u0e15\u0e01\u0e25\u0e07", +"Cancel": "\u0e22\u0e01\u0e40\u0e25\u0e34\u0e01", +"Visual aids": "\u0e17\u0e31\u0e28\u0e19\u0e39\u0e1b\u0e01\u0e23\u0e13\u0e4c", +"Bold": "\u0e15\u0e31\u0e27\u0e2b\u0e19\u0e32", +"Italic": "\u0e15\u0e31\u0e27\u0e40\u0e2d\u0e35\u0e22\u0e07", +"Underline": "\u0e02\u0e35\u0e14\u0e40\u0e2a\u0e49\u0e19\u0e43\u0e15\u0e49", +"Strikethrough": "\u0e02\u0e35\u0e14\u0e17\u0e31\u0e1a", +"Superscript": "\u0e15\u0e31\u0e27\u0e22\u0e01", +"Subscript": "\u0e15\u0e31\u0e27\u0e2b\u0e49\u0e2d\u0e22", +"Clear formatting": "\u0e25\u0e49\u0e32\u0e07\u0e01\u0e32\u0e23\u0e08\u0e31\u0e14\u0e23\u0e39\u0e1b\u0e41\u0e1a\u0e1a", +"Align left": "\u0e08\u0e31\u0e14\u0e0a\u0e34\u0e14\u0e0b\u0e49\u0e32\u0e22", +"Align center": "\u0e08\u0e31\u0e14\u0e01\u0e36\u0e48\u0e07\u0e01\u0e25\u0e32\u0e07", +"Align right": "\u0e08\u0e31\u0e14\u0e0a\u0e34\u0e14\u0e02\u0e27\u0e32", +"Justify": "\u0e40\u0e15\u0e47\u0e21\u0e41\u0e19\u0e27", +"Bullet list": "\u0e23\u0e32\u0e22\u0e01\u0e32\u0e23\u0e2a\u0e31\u0e0d\u0e25\u0e31\u0e01\u0e29\u0e13\u0e4c\u0e2b\u0e31\u0e27\u0e02\u0e49\u0e2d\u0e22\u0e48\u0e2d\u0e22", +"Numbered list": "\u0e23\u0e32\u0e22\u0e01\u0e32\u0e23\u0e25\u0e33\u0e14\u0e31\u0e1a\u0e40\u0e25\u0e02", +"Decrease indent": "\u0e25\u0e14\u0e01\u0e32\u0e23\u0e40\u0e22\u0e37\u0e49\u0e2d\u0e07", +"Increase indent": "\u0e40\u0e1e\u0e34\u0e48\u0e21\u0e01\u0e32\u0e23\u0e40\u0e22\u0e37\u0e49\u0e2d\u0e07", +"Close": "\u0e1b\u0e34\u0e14", +"Formats": "\u0e23\u0e39\u0e1b\u0e41\u0e1a\u0e1a", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "\u0e40\u0e1a\u0e23\u0e32\u0e27\u0e4c\u0e40\u0e0b\u0e2d\u0e23\u0e4c\u0e02\u0e2d\u0e07\u0e04\u0e38\u0e13\u0e44\u0e21\u0e48\u0e2a\u0e19\u0e31\u0e1a\u0e2a\u0e19\u0e38\u0e19\u0e01\u0e32\u0e23\u0e40\u0e02\u0e49\u0e32\u0e16\u0e36\u0e07\u0e42\u0e14\u0e22\u0e15\u0e23\u0e07\u0e44\u0e1b\u0e22\u0e31\u0e07\u0e04\u0e25\u0e34\u0e1b\u0e1a\u0e2d\u0e23\u0e4c\u0e14 \u0e01\u0e23\u0e38\u0e13\u0e32\u0e43\u0e0a\u0e49\u0e41\u0e1b\u0e49\u0e19\u0e1e\u0e34\u0e21\u0e1e\u0e4c\u0e25\u0e31\u0e14 Ctrl+X\/C\/V \u0e41\u0e17\u0e19", +"Headers": "\u0e2a\u0e48\u0e27\u0e19\u0e2b\u0e31\u0e27", +"Header 1": "\u0e2a\u0e48\u0e27\u0e19\u0e2b\u0e31\u0e27 1", +"Header 2": "\u0e2a\u0e48\u0e27\u0e19\u0e2b\u0e31\u0e27 2", +"Header 3": "\u0e2a\u0e48\u0e27\u0e19\u0e2b\u0e31\u0e27 3", +"Header 4": "\u0e2a\u0e48\u0e27\u0e19\u0e2b\u0e31\u0e27 4", +"Header 5": "\u0e2a\u0e48\u0e27\u0e19\u0e2b\u0e31\u0e27 5", +"Header 6": "\u0e2a\u0e48\u0e27\u0e19\u0e2b\u0e31\u0e27 6", +"Headings": "\u0e2b\u0e31\u0e27\u0e40\u0e23\u0e37\u0e48\u0e2d\u0e07", +"Heading 1": "\u0e2b\u0e31\u0e27\u0e40\u0e23\u0e37\u0e48\u0e2d\u0e07 1", +"Heading 2": "\u0e2b\u0e31\u0e27\u0e40\u0e23\u0e37\u0e48\u0e2d\u0e07 2", +"Heading 3": "\u0e2b\u0e31\u0e27\u0e40\u0e23\u0e37\u0e48\u0e2d\u0e07 3", +"Heading 4": "\u0e2b\u0e31\u0e27\u0e40\u0e23\u0e37\u0e48\u0e2d\u0e07 4", +"Heading 5": "\u0e2b\u0e31\u0e27\u0e40\u0e23\u0e37\u0e48\u0e2d\u0e07 5", +"Heading 6": "\u0e2b\u0e31\u0e27\u0e40\u0e23\u0e37\u0e48\u0e2d\u0e07 6", +"Preformatted": "\u0e08\u0e31\u0e14\u0e23\u0e39\u0e1b\u0e41\u0e1a\u0e1a", +"Div": "Div", +"Pre": "\u0e01\u0e48\u0e2d\u0e19", +"Code": "\u0e42\u0e04\u0e49\u0e14", +"Paragraph": "\u0e22\u0e48\u0e2d\u0e2b\u0e19\u0e49\u0e32", +"Blockquote": "\u0e22\u0e01\u0e02\u0e49\u0e2d\u0e04\u0e27\u0e32\u0e21\u0e17\u0e31\u0e49\u0e07\u0e22\u0e48\u0e2d\u0e2b\u0e19\u0e49\u0e32", +"Inline": "\u0e41\u0e1a\u0e1a\u0e2d\u0e34\u0e19\u0e44\u0e25\u0e19\u0e4c", +"Blocks": "\u0e1a\u0e25\u0e47\u0e2d\u0e01", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "\u0e01\u0e32\u0e23\u0e27\u0e32\u0e07\u0e15\u0e2d\u0e19\u0e19\u0e35\u0e49\u0e2d\u0e22\u0e39\u0e48\u0e43\u0e19\u0e42\u0e2b\u0e21\u0e14\u0e02\u0e49\u0e2d\u0e04\u0e27\u0e32\u0e21\u0e18\u0e23\u0e23\u0e21\u0e14\u0e32 \u0e40\u0e19\u0e37\u0e49\u0e2d\u0e2b\u0e32\u0e08\u0e30\u0e16\u0e39\u0e01\u0e27\u0e32\u0e07\u0e40\u0e1b\u0e47\u0e19\u0e02\u0e49\u0e2d\u0e04\u0e27\u0e32\u0e21\u0e18\u0e23\u0e23\u0e21\u0e14\u0e32\u0e08\u0e19\u0e01\u0e27\u0e48\u0e32\u0e04\u0e38\u0e13\u0e08\u0e30\u0e1b\u0e34\u0e14\u0e15\u0e31\u0e27\u0e40\u0e25\u0e37\u0e2d\u0e01\u0e19\u0e35\u0e49", +"Font Family": "\u0e15\u0e23\u0e30\u0e01\u0e39\u0e25\u0e41\u0e1a\u0e1a\u0e2d\u0e31\u0e01\u0e29\u0e23", +"Font Sizes": "\u0e02\u0e19\u0e32\u0e14\u0e41\u0e1a\u0e1a\u0e2d\u0e31\u0e01\u0e29\u0e23", +"Class": "\u0e04\u0e25\u0e32\u0e2a", +"Browse for an image": "\u0e40\u0e23\u0e35\u0e22\u0e01\u0e14\u0e39\u0e23\u0e39\u0e1b\u0e20\u0e32\u0e1e", +"OR": "\u0e2b\u0e23\u0e37\u0e2d", +"Drop an image here": "\u0e27\u0e32\u0e07\u0e23\u0e39\u0e1b\u0e20\u0e32\u0e1e\u0e17\u0e35\u0e48\u0e19\u0e35\u0e48", +"Upload": "\u0e2d\u0e31\u0e1b\u0e42\u0e2b\u0e25\u0e14", +"Block": "\u0e1a\u0e25\u0e47\u0e2d\u0e01", +"Align": "\u0e01\u0e32\u0e23\u0e08\u0e31\u0e14\u0e41\u0e19\u0e27", +"Default": "\u0e04\u0e48\u0e32\u0e40\u0e23\u0e34\u0e48\u0e21\u0e15\u0e49\u0e19", +"Circle": "\u0e27\u0e07\u0e01\u0e25\u0e21", +"Disc": "\u0e14\u0e34\u0e2a\u0e01\u0e4c", +"Square": "\u0e08\u0e31\u0e15\u0e38\u0e23\u0e31\u0e2a", +"Lower Alpha": "\u0e2d\u0e31\u0e25\u0e1f\u0e32\u0e17\u0e35\u0e48\u0e15\u0e48\u0e33\u0e01\u0e27\u0e48\u0e32", +"Lower Greek": "\u0e01\u0e23\u0e35\u0e01\u0e17\u0e35\u0e48\u0e15\u0e48\u0e33\u0e01\u0e27\u0e48\u0e32", +"Lower Roman": "\u0e42\u0e23\u0e21\u0e31\u0e19\u0e17\u0e35\u0e48\u0e15\u0e48\u0e33\u0e01\u0e27\u0e48\u0e32", +"Upper Alpha": "\u0e2d\u0e31\u0e25\u0e1f\u0e32\u0e17\u0e35\u0e48\u0e2a\u0e39\u0e07\u0e01\u0e27\u0e48\u0e32", +"Upper Roman": "\u0e42\u0e23\u0e21\u0e31\u0e19\u0e17\u0e35\u0e48\u0e2a\u0e39\u0e07\u0e01\u0e27\u0e48\u0e32", +"Anchor": "\u0e08\u0e38\u0e14\u0e22\u0e36\u0e14", +"Name": "\u0e0a\u0e37\u0e48\u0e2d", +"Id": "Id", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "Id \u0e04\u0e27\u0e23\u0e08\u0e30\u0e02\u0e36\u0e49\u0e19\u0e15\u0e49\u0e19\u0e14\u0e49\u0e27\u0e22\u0e15\u0e31\u0e27\u0e2d\u0e31\u0e01\u0e29\u0e23 \u0e15\u0e32\u0e21\u0e14\u0e49\u0e27\u0e22\u0e15\u0e31\u0e27\u0e2d\u0e31\u0e01\u0e29\u0e23 \u0e15\u0e31\u0e27\u0e40\u0e25\u0e02 \u0e02\u0e35\u0e14\u0e01\u0e25\u0e32\u0e07 \u0e08\u0e38\u0e14 \u0e2d\u0e31\u0e12\u0e20\u0e32\u0e04 \u0e2b\u0e23\u0e37\u0e2d \u0e02\u0e35\u0e14\u0e25\u0e48\u0e32\u0e07", +"You have unsaved changes are you sure you want to navigate away?": "\u0e04\u0e38\u0e13\u0e21\u0e35\u0e01\u0e32\u0e23\u0e40\u0e1b\u0e25\u0e35\u0e48\u0e22\u0e19\u0e41\u0e1b\u0e25\u0e07\u0e17\u0e35\u0e48\u0e44\u0e21\u0e48\u0e44\u0e14\u0e49\u0e1a\u0e31\u0e19\u0e17\u0e36\u0e01 \u0e04\u0e38\u0e13\u0e15\u0e49\u0e2d\u0e07\u0e01\u0e32\u0e23\u0e17\u0e35\u0e48\u0e08\u0e30\u0e2d\u0e2d\u0e01\u0e2b\u0e23\u0e37\u0e2d\u0e44\u0e21\u0e48?", +"Restore last draft": "\u0e04\u0e37\u0e19\u0e04\u0e48\u0e32\u0e41\u0e1a\u0e1a\u0e23\u0e48\u0e32\u0e07\u0e25\u0e48\u0e32\u0e2a\u0e38\u0e14", +"Special character": "\u0e2d\u0e31\u0e01\u0e02\u0e23\u0e30\u0e1e\u0e34\u0e40\u0e28\u0e29", +"Source code": "\u0e42\u0e04\u0e49\u0e14\u0e15\u0e49\u0e19\u0e09\u0e1a\u0e31\u0e1a", +"Insert\/Edit code sample": "\u0e41\u0e17\u0e23\u0e01\/\u0e41\u0e01\u0e49\u0e44\u0e02\u0e15\u0e31\u0e27\u0e2d\u0e22\u0e48\u0e32\u0e07\u0e42\u0e04\u0e49\u0e14", +"Language": "\u0e20\u0e32\u0e29\u0e32", +"Code sample": "\u0e15\u0e31\u0e27\u0e2d\u0e22\u0e48\u0e32\u0e07\u0e42\u0e04\u0e49\u0e14", +"Color": "\u0e2a\u0e35", +"R": "\u0e41\u0e14\u0e07", +"G": "\u0e40\u0e02\u0e35\u0e22\u0e27", +"B": "\u0e19\u0e49\u0e33\u0e40\u0e07\u0e34\u0e19", +"Left to right": "\u0e0b\u0e49\u0e32\u0e22\u0e44\u0e1b\u0e02\u0e27\u0e32", +"Right to left": "\u0e02\u0e27\u0e32\u0e44\u0e1b\u0e0b\u0e49\u0e32\u0e22", +"Emoticons": "\u0e2d\u0e34\u0e42\u0e21\u0e15\u0e34\u0e04\u0e2d\u0e19", +"Document properties": "\u0e04\u0e38\u0e13\u0e2a\u0e21\u0e1a\u0e31\u0e15\u0e34\u0e02\u0e2d\u0e07\u0e40\u0e2d\u0e01\u0e2a\u0e32\u0e23", +"Title": "\u0e0a\u0e37\u0e48\u0e2d\u0e40\u0e23\u0e37\u0e48\u0e2d\u0e07", +"Keywords": "\u0e04\u0e33\u0e2a\u0e33\u0e04\u0e31\u0e0d", +"Description": "\u0e04\u0e33\u0e2d\u0e18\u0e34\u0e1a\u0e32\u0e22", +"Robots": "\u0e2b\u0e38\u0e48\u0e19\u0e22\u0e19\u0e15\u0e4c", +"Author": "\u0e1c\u0e39\u0e49\u0e40\u0e02\u0e35\u0e22\u0e19", +"Encoding": "\u0e01\u0e32\u0e23\u0e40\u0e02\u0e49\u0e32\u0e23\u0e2b\u0e31\u0e2a", +"Fullscreen": "\u0e40\u0e15\u0e47\u0e21\u0e08\u0e2d", +"Action": "\u0e01\u0e32\u0e23\u0e01\u0e23\u0e30\u0e17\u0e33", +"Shortcut": "\u0e17\u0e32\u0e07\u0e25\u0e31\u0e14", +"Help": "\u0e0a\u0e48\u0e27\u0e22\u0e40\u0e2b\u0e25\u0e37\u0e2d", +"Address": "\u0e17\u0e35\u0e48\u0e2d\u0e22\u0e39\u0e48", +"Focus to menubar": "\u0e42\u0e1f\u0e01\u0e31\u0e2a\u0e44\u0e1b\u0e17\u0e35\u0e48\u0e40\u0e21\u0e19\u0e39\u0e1a\u0e32\u0e23\u0e4c", +"Focus to toolbar": "\u0e42\u0e1f\u0e01\u0e31\u0e2a\u0e44\u0e1b\u0e17\u0e35\u0e48\u0e41\u0e16\u0e1a\u0e40\u0e04\u0e23\u0e37\u0e48\u0e2d\u0e07\u0e21\u0e37\u0e2d", +"Focus to element path": "\u0e42\u0e1f\u0e01\u0e31\u0e2a\u0e44\u0e1b\u0e17\u0e35\u0e48\u0e40\u0e2a\u0e49\u0e19\u0e17\u0e32\u0e07\u0e02\u0e2d\u0e07\u0e2d\u0e07\u0e04\u0e4c\u0e1b\u0e23\u0e30\u0e01\u0e2d\u0e1a", +"Focus to contextual toolbar": "\u0e42\u0e1f\u0e01\u0e31\u0e2a\u0e44\u0e1b\u0e17\u0e35\u0e48\u0e41\u0e16\u0e1a\u0e40\u0e04\u0e23\u0e37\u0e48\u0e2d\u0e07\u0e21\u0e37\u0e2d\u0e15\u0e32\u0e21\u0e1a\u0e23\u0e34\u0e1a\u0e17", +"Insert link (if link plugin activated)": "\u0e41\u0e17\u0e23\u0e01\u0e25\u0e34\u0e07\u0e01\u0e4c (\u0e2b\u0e32\u0e01\u0e40\u0e1b\u0e34\u0e14\u0e43\u0e0a\u0e49\u0e07\u0e32\u0e19\u0e1b\u0e25\u0e31\u0e4a\u0e01\u0e2d\u0e34\u0e19\u0e25\u0e34\u0e07\u0e01\u0e4c)", +"Save (if save plugin activated)": "\u0e1a\u0e31\u0e19\u0e17\u0e36\u0e01 (\u0e2b\u0e32\u0e01\u0e40\u0e1b\u0e34\u0e14\u0e43\u0e0a\u0e49\u0e07\u0e32\u0e19\u0e1b\u0e25\u0e31\u0e4a\u0e01\u0e2d\u0e34\u0e19\u0e1a\u0e31\u0e19\u0e17\u0e36\u0e01)", +"Find (if searchreplace plugin activated)": "\u0e04\u0e49\u0e19\u0e2b\u0e32 (\u0e2b\u0e32\u0e01\u0e40\u0e1b\u0e34\u0e14\u0e43\u0e0a\u0e49\u0e07\u0e32\u0e19\u0e1b\u0e25\u0e31\u0e4a\u0e01\u0e2d\u0e34\u0e19 searchreplace)", +"Plugins installed ({0}):": "\u0e1b\u0e25\u0e31\u0e4a\u0e01\u0e2d\u0e34\u0e19\u0e17\u0e35\u0e48\u0e15\u0e34\u0e14\u0e15\u0e31\u0e49\u0e07\u0e41\u0e25\u0e49\u0e27 ({0}):", +"Premium plugins:": "\u0e1b\u0e25\u0e31\u0e4a\u0e01\u0e2d\u0e34\u0e19\u0e1e\u0e23\u0e35\u0e40\u0e21\u0e35\u0e22\u0e21:", +"Learn more...": "\u0e40\u0e23\u0e35\u0e22\u0e19\u0e23\u0e39\u0e49\u0e40\u0e1e\u0e34\u0e48\u0e21\u0e40\u0e15\u0e34\u0e21...", +"You are using {0}": "\u0e04\u0e38\u0e13\u0e01\u0e33\u0e25\u0e31\u0e07\u0e43\u0e0a\u0e49 {0}", +"Plugins": "\u0e1b\u0e25\u0e31\u0e4a\u0e01\u0e2d\u0e34\u0e19", +"Handy Shortcuts": "\u0e17\u0e32\u0e07\u0e25\u0e31\u0e14\u0e14\u0e49\u0e27\u0e22\u0e21\u0e37\u0e2d", +"Horizontal line": "\u0e40\u0e2a\u0e49\u0e19\u0e41\u0e19\u0e27\u0e19\u0e2d\u0e19", +"Insert\/edit image": "\u0e41\u0e17\u0e23\u0e01\/\u0e41\u0e01\u0e49\u0e44\u0e02\u0e23\u0e39\u0e1b", +"Image description": "\u0e04\u0e33\u0e2d\u0e18\u0e34\u0e1a\u0e32\u0e22\u0e23\u0e39\u0e1b", +"Source": "\u0e41\u0e2b\u0e25\u0e48\u0e07\u0e17\u0e35\u0e48\u0e21\u0e32", +"Dimensions": "\u0e02\u0e19\u0e32\u0e14", +"Constrain proportions": "\u0e08\u0e33\u0e01\u0e31\u0e14\u0e2a\u0e31\u0e14\u0e2a\u0e48\u0e27\u0e19", +"General": "\u0e17\u0e31\u0e48\u0e27\u0e44\u0e1b", +"Advanced": "\u0e02\u0e31\u0e49\u0e19\u0e2a\u0e39\u0e07", +"Style": "\u0e23\u0e39\u0e1b\u0e41\u0e1a\u0e1a", +"Vertical space": "\u0e0a\u0e48\u0e2d\u0e07\u0e27\u0e48\u0e32\u0e07\u0e41\u0e19\u0e27\u0e15\u0e31\u0e49\u0e07", +"Horizontal space": "\u0e0a\u0e48\u0e2d\u0e07\u0e27\u0e48\u0e32\u0e07\u0e41\u0e19\u0e27\u0e19\u0e2d\u0e19", +"Border": "\u0e40\u0e2a\u0e49\u0e19\u0e02\u0e2d\u0e1a", +"Insert image": "\u0e41\u0e17\u0e23\u0e01\u0e23\u0e39\u0e1b\u0e20\u0e32\u0e1e", +"Image": "\u0e23\u0e39\u0e1b\u0e20\u0e32\u0e1e", +"Image list": "\u0e23\u0e32\u0e22\u0e01\u0e32\u0e23\u0e23\u0e39\u0e1b\u0e20\u0e32\u0e1e", +"Rotate counterclockwise": "\u0e2b\u0e21\u0e38\u0e19\u0e17\u0e27\u0e19\u0e40\u0e02\u0e47\u0e21\u0e19\u0e32\u0e2c\u0e34\u0e01\u0e32", +"Rotate clockwise": "\u0e2b\u0e21\u0e38\u0e19\u0e15\u0e32\u0e21\u0e40\u0e02\u0e47\u0e21\u0e19\u0e32\u0e2c\u0e34\u0e01\u0e32", +"Flip vertically": "\u0e1e\u0e25\u0e34\u0e01\u0e15\u0e32\u0e21\u0e41\u0e19\u0e27\u0e15\u0e31\u0e49\u0e07", +"Flip horizontally": "\u0e1e\u0e25\u0e34\u0e01\u0e15\u0e32\u0e21\u0e41\u0e19\u0e27\u0e19\u0e2d\u0e19", +"Edit image": "\u0e41\u0e01\u0e49\u0e44\u0e02\u0e23\u0e39\u0e1b", +"Image options": "\u0e15\u0e31\u0e27\u0e40\u0e25\u0e37\u0e2d\u0e01\u0e23\u0e39\u0e1b\u0e20\u0e32\u0e1e", +"Zoom in": "\u0e02\u0e22\u0e32\u0e22\u0e40\u0e02\u0e49\u0e32", +"Zoom out": "\u0e22\u0e48\u0e2d\u0e2d\u0e2d\u0e01", +"Crop": "\u0e04\u0e23\u0e2d\u0e1b\u0e15\u0e31\u0e14", +"Resize": "\u0e1b\u0e23\u0e31\u0e1a\u0e02\u0e19\u0e32\u0e14", +"Orientation": "\u0e01\u0e32\u0e23\u0e08\u0e31\u0e14\u0e27\u0e32\u0e07", +"Brightness": "\u0e04\u0e27\u0e32\u0e21\u0e2a\u0e27\u0e48\u0e32\u0e07", +"Sharpen": "\u0e04\u0e27\u0e32\u0e21\u0e04\u0e21", +"Contrast": "\u0e04\u0e27\u0e32\u0e21\u0e40\u0e1b\u0e23\u0e35\u0e22\u0e1a\u0e15\u0e48\u0e32\u0e07", +"Color levels": "\u0e23\u0e30\u0e14\u0e31\u0e1a\u0e2a\u0e35", +"Gamma": "\u0e41\u0e01\u0e21\u0e21\u0e32", +"Invert": "\u0e22\u0e49\u0e2d\u0e19\u0e01\u0e25\u0e31\u0e1a", +"Apply": "\u0e19\u0e33\u0e44\u0e1b\u0e43\u0e0a\u0e49", +"Back": "\u0e01\u0e25\u0e31\u0e1a", +"Insert date\/time": "\u0e41\u0e17\u0e23\u0e01\u0e27\u0e31\u0e19\u0e17\u0e35\u0e48\/\u0e40\u0e27\u0e25\u0e32", +"Date\/time": "\u0e27\u0e31\u0e19\u0e17\u0e35\u0e48\/\u0e40\u0e27\u0e25\u0e32", +"Insert link": "\u0e41\u0e17\u0e23\u0e01\u0e25\u0e34\u0e07\u0e01\u0e4c", +"Insert\/edit link": "\u0e41\u0e17\u0e23\u0e01\/\u0e41\u0e01\u0e49\u0e44\u0e02\u0e25\u0e34\u0e07\u0e01\u0e4c", +"Text to display": "\u0e02\u0e49\u0e2d\u0e04\u0e27\u0e32\u0e21\u0e17\u0e35\u0e48\u0e08\u0e30\u0e41\u0e2a\u0e14\u0e07", +"Url": "URL", +"Target": "\u0e40\u0e1b\u0e49\u0e32\u0e2b\u0e21\u0e32\u0e22", +"None": "\u0e44\u0e21\u0e48\u0e21\u0e35", +"New window": "\u0e40\u0e1b\u0e34\u0e14\u0e2b\u0e19\u0e49\u0e32\u0e15\u0e48\u0e32\u0e07\u0e43\u0e2b\u0e21\u0e48", +"Remove link": "\u0e40\u0e2d\u0e32\u0e25\u0e34\u0e07\u0e01\u0e4c\u0e2d\u0e2d\u0e01", +"Anchors": "\u0e08\u0e38\u0e14\u0e22\u0e36\u0e14", +"Link": "\u0e25\u0e34\u0e07\u0e01\u0e4c", +"Paste or type a link": "\u0e27\u0e32\u0e07\u0e2b\u0e23\u0e37\u0e2d\u0e1b\u0e49\u0e2d\u0e19\u0e25\u0e34\u0e07\u0e01\u0e4c", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "URL \u0e17\u0e35\u0e48\u0e04\u0e38\u0e13\u0e23\u0e30\u0e1a\u0e38\u0e14\u0e39\u0e40\u0e2b\u0e21\u0e37\u0e2d\u0e19\u0e27\u0e48\u0e32\u0e40\u0e1b\u0e47\u0e19\u0e2d\u0e35\u0e40\u0e21\u0e25\u0e41\u0e2d\u0e14\u0e40\u0e14\u0e23\u0e2a \u0e04\u0e38\u0e13\u0e15\u0e49\u0e2d\u0e07\u0e01\u0e32\u0e23\u0e43\u0e2a\u0e48 mailto: \u0e19\u0e33\u0e2b\u0e19\u0e49\u0e32\u0e2b\u0e23\u0e37\u0e2d\u0e44\u0e21\u0e48", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "URL \u0e17\u0e35\u0e48\u0e04\u0e38\u0e13\u0e23\u0e30\u0e1a\u0e38\u0e14\u0e39\u0e40\u0e2b\u0e21\u0e37\u0e2d\u0e19\u0e27\u0e48\u0e32\u0e40\u0e1b\u0e47\u0e19\u0e25\u0e34\u0e07\u0e01\u0e4c\u0e20\u0e32\u0e22\u0e19\u0e2d\u0e01 \u0e04\u0e38\u0e13\u0e15\u0e49\u0e2d\u0e07\u0e01\u0e32\u0e23\u0e43\u0e2a\u0e48 http:\/\/ \u0e19\u0e33\u0e2b\u0e19\u0e49\u0e32\u0e2b\u0e23\u0e37\u0e2d\u0e44\u0e21\u0e48", +"Link list": "\u0e23\u0e32\u0e22\u0e01\u0e32\u0e23\u0e25\u0e34\u0e07\u0e01\u0e4c", +"Insert video": "\u0e41\u0e17\u0e23\u0e01\u0e27\u0e34\u0e14\u0e35\u0e42\u0e2d", +"Insert\/edit video": "\u0e41\u0e17\u0e23\u0e01\/\u0e41\u0e01\u0e49\u0e44\u0e02\u0e27\u0e34\u0e14\u0e35\u0e42\u0e2d", +"Insert\/edit media": "\u0e41\u0e17\u0e23\u0e01\/\u0e41\u0e01\u0e49\u0e44\u0e02\u0e2a\u0e37\u0e48\u0e2d", +"Alternative source": "\u0e41\u0e2b\u0e25\u0e48\u0e07\u0e17\u0e35\u0e48\u0e21\u0e32\u0e2a\u0e33\u0e23\u0e2d\u0e07", +"Poster": "\u0e42\u0e1b\u0e2a\u0e40\u0e15\u0e2d\u0e23\u0e4c", +"Paste your embed code below:": "\u0e27\u0e32\u0e07\u0e42\u0e04\u0e49\u0e14\u0e1d\u0e31\u0e07\u0e15\u0e31\u0e27\u0e02\u0e2d\u0e07\u0e04\u0e38\u0e13\u0e14\u0e49\u0e32\u0e19\u0e25\u0e48\u0e32\u0e07:", +"Embed": "\u0e1d\u0e31\u0e07", +"Media": "\u0e2a\u0e37\u0e48\u0e2d", +"Nonbreaking space": "\u0e0a\u0e48\u0e2d\u0e07\u0e27\u0e48\u0e32\u0e07\u0e44\u0e21\u0e48\u0e41\u0e22\u0e01", +"Page break": "\u0e15\u0e31\u0e27\u0e41\u0e1a\u0e48\u0e07\u0e2b\u0e19\u0e49\u0e32", +"Paste as text": "\u0e27\u0e32\u0e07\u0e40\u0e1b\u0e47\u0e19\u0e02\u0e49\u0e2d\u0e04\u0e27\u0e32\u0e21", +"Preview": "\u0e41\u0e2a\u0e14\u0e07\u0e15\u0e31\u0e27\u0e2d\u0e22\u0e48\u0e32\u0e07", +"Print": "\u0e1e\u0e34\u0e21\u0e1e\u0e4c", +"Save": "\u0e1a\u0e31\u0e19\u0e17\u0e36\u0e01", +"Find": "\u0e04\u0e49\u0e19\u0e2b\u0e32", +"Replace with": "\u0e41\u0e17\u0e19\u0e17\u0e35\u0e48\u0e14\u0e49\u0e27\u0e22", +"Replace": "\u0e41\u0e17\u0e19\u0e17\u0e35\u0e48", +"Replace all": "\u0e41\u0e17\u0e19\u0e17\u0e35\u0e48\u0e17\u0e31\u0e49\u0e07\u0e2b\u0e21\u0e14", +"Prev": "\u0e01\u0e48\u0e2d\u0e19\u0e2b\u0e19\u0e49\u0e32", +"Next": "\u0e16\u0e31\u0e14\u0e44\u0e1b", +"Find and replace": "\u0e04\u0e49\u0e19\u0e2b\u0e32\u0e41\u0e25\u0e30\u0e41\u0e17\u0e19\u0e17\u0e35\u0e48", +"Could not find the specified string.": "\u0e44\u0e21\u0e48\u0e1e\u0e1a\u0e2a\u0e15\u0e23\u0e34\u0e07\u0e17\u0e35\u0e48\u0e23\u0e30\u0e1a\u0e38", +"Match case": "\u0e15\u0e23\u0e07\u0e15\u0e32\u0e21\u0e15\u0e31\u0e27\u0e1e\u0e34\u0e21\u0e1e\u0e4c\u0e43\u0e2b\u0e0d\u0e48-\u0e40\u0e25\u0e47\u0e01", +"Whole words": "\u0e17\u0e31\u0e49\u0e07\u0e04\u0e33", +"Spellcheck": "\u0e15\u0e23\u0e27\u0e08\u0e01\u0e32\u0e23\u0e2a\u0e30\u0e01\u0e14", +"Ignore": "\u0e25\u0e30\u0e40\u0e27\u0e49\u0e19", +"Ignore all": "\u0e25\u0e30\u0e40\u0e27\u0e49\u0e19\u0e17\u0e31\u0e49\u0e07\u0e2b\u0e21\u0e14", +"Finish": "\u0e40\u0e2a\u0e23\u0e47\u0e08\u0e2a\u0e34\u0e49\u0e19", +"Add to Dictionary": "\u0e40\u0e1e\u0e34\u0e48\u0e21\u0e43\u0e19\u0e1e\u0e08\u0e19\u0e32\u0e19\u0e38\u0e01\u0e23\u0e21", +"Insert table": "\u0e41\u0e17\u0e23\u0e01\u0e15\u0e32\u0e23\u0e32\u0e07", +"Table properties": "\u0e04\u0e38\u0e13\u0e2a\u0e21\u0e1a\u0e31\u0e15\u0e34\u0e02\u0e2d\u0e07\u0e15\u0e32\u0e23\u0e32\u0e07", +"Delete table": "\u0e25\u0e1a\u0e15\u0e32\u0e23\u0e32\u0e07", +"Cell": "\u0e40\u0e0b\u0e25\u0e25\u0e4c", +"Row": "\u0e41\u0e16\u0e27", +"Column": "\u0e04\u0e2d\u0e25\u0e31\u0e21\u0e19\u0e4c", +"Cell properties": "\u0e04\u0e38\u0e13\u0e2a\u0e21\u0e1a\u0e31\u0e15\u0e34\u0e02\u0e2d\u0e07\u0e40\u0e0b\u0e25\u0e25\u0e4c", +"Merge cells": "\u0e1c\u0e2a\u0e32\u0e19\u0e40\u0e0b\u0e25\u0e25\u0e4c", +"Split cell": "\u0e41\u0e22\u0e01\u0e40\u0e0b\u0e25\u0e25\u0e4c", +"Insert row before": "\u0e41\u0e17\u0e23\u0e01\u0e41\u0e16\u0e27\u0e14\u0e49\u0e32\u0e19\u0e1a\u0e19", +"Insert row after": "\u0e41\u0e17\u0e23\u0e01\u0e41\u0e16\u0e27\u0e14\u0e49\u0e32\u0e19\u0e25\u0e48\u0e32\u0e07", +"Delete row": "\u0e25\u0e1a\u0e41\u0e16\u0e27", +"Row properties": "\u0e04\u0e38\u0e13\u0e2a\u0e21\u0e1a\u0e31\u0e15\u0e34\u0e02\u0e2d\u0e07\u0e41\u0e16\u0e27", +"Cut row": "\u0e15\u0e31\u0e14\u0e41\u0e16\u0e27", +"Copy row": "\u0e04\u0e31\u0e14\u0e25\u0e2d\u0e01\u0e41\u0e16\u0e27", +"Paste row before": "\u0e27\u0e32\u0e07\u0e41\u0e16\u0e27\u0e14\u0e49\u0e32\u0e19\u0e1a\u0e19", +"Paste row after": "\u0e27\u0e32\u0e07\u0e41\u0e16\u0e27\u0e14\u0e49\u0e32\u0e19\u0e25\u0e48\u0e32\u0e07", +"Insert column before": "\u0e41\u0e17\u0e23\u0e01\u0e04\u0e2d\u0e25\u0e31\u0e21\u0e19\u0e4c\u0e02\u0e49\u0e32\u0e07\u0e2b\u0e19\u0e49\u0e32", +"Insert column after": "\u0e41\u0e17\u0e23\u0e01\u0e04\u0e2d\u0e25\u0e31\u0e21\u0e19\u0e4c\u0e02\u0e49\u0e32\u0e07\u0e2b\u0e25\u0e31\u0e07", +"Delete column": "\u0e25\u0e1a\u0e04\u0e2d\u0e25\u0e31\u0e21\u0e19\u0e4c", +"Cols": "\u0e04\u0e2d\u0e25\u0e31\u0e21\u0e19\u0e4c", +"Rows": "\u0e41\u0e16\u0e27", +"Width": "\u0e04\u0e27\u0e32\u0e21\u0e01\u0e27\u0e49\u0e32\u0e07", +"Height": "\u0e04\u0e27\u0e32\u0e21\u0e2a\u0e39\u0e07", +"Cell spacing": "\u0e0a\u0e48\u0e2d\u0e07\u0e27\u0e48\u0e32\u0e07\u0e23\u0e30\u0e2b\u0e27\u0e48\u0e32\u0e07\u0e40\u0e0b\u0e25\u0e25\u0e4c", +"Cell padding": "\u0e0a\u0e48\u0e2d\u0e07\u0e27\u0e48\u0e32\u0e07\u0e20\u0e32\u0e22\u0e43\u0e19\u0e40\u0e0b\u0e25\u0e25\u0e4c", +"Caption": "\u0e1b\u0e49\u0e32\u0e22\u0e04\u0e33\u0e2d\u0e18\u0e34\u0e1a\u0e32\u0e22", +"Left": "\u0e0b\u0e49\u0e32\u0e22", +"Center": "\u0e01\u0e36\u0e48\u0e07\u0e01\u0e25\u0e32\u0e07", +"Right": "\u0e02\u0e27\u0e32", +"Cell type": "\u0e0a\u0e19\u0e34\u0e14\u0e02\u0e2d\u0e07\u0e40\u0e0b\u0e25\u0e25\u0e4c", +"Scope": "\u0e02\u0e2d\u0e1a\u0e40\u0e02\u0e15", +"Alignment": "\u0e01\u0e32\u0e23\u0e08\u0e31\u0e14\u0e41\u0e19\u0e27", +"H Align": "\u0e01\u0e32\u0e23\u0e40\u0e23\u0e35\u0e22\u0e07\u0e43\u0e19\u0e41\u0e19\u0e27\u0e19\u0e2d\u0e19", +"V Align": "\u0e01\u0e32\u0e23\u0e40\u0e23\u0e35\u0e22\u0e07\u0e43\u0e19\u0e41\u0e19\u0e27\u0e15\u0e31\u0e49\u0e07", +"Top": "\u0e1a\u0e19", +"Middle": "\u0e01\u0e25\u0e32\u0e07", +"Bottom": "\u0e25\u0e48\u0e32\u0e07", +"Header cell": "\u0e40\u0e0b\u0e25\u0e25\u0e4c\u0e2a\u0e48\u0e27\u0e19\u0e2b\u0e31\u0e27", +"Row group": "\u0e01\u0e25\u0e38\u0e48\u0e21\u0e41\u0e16\u0e27", +"Column group": "\u0e01\u0e25\u0e38\u0e48\u0e21\u0e04\u0e2d\u0e25\u0e31\u0e21\u0e19\u0e4c", +"Row type": "\u0e0a\u0e19\u0e34\u0e14\u0e02\u0e2d\u0e07\u0e41\u0e16\u0e27", +"Header": "\u0e2a\u0e48\u0e27\u0e19\u0e2b\u0e31\u0e27", +"Body": "\u0e40\u0e19\u0e37\u0e49\u0e2d\u0e04\u0e27\u0e32\u0e21", +"Footer": "\u0e2a\u0e48\u0e27\u0e19\u0e17\u0e49\u0e32\u0e22", +"Border color": "\u0e2a\u0e35\u0e02\u0e2d\u0e1a", +"Insert template": "\u0e41\u0e17\u0e23\u0e01\u0e41\u0e21\u0e48\u0e41\u0e1a\u0e1a", +"Templates": "\u0e41\u0e21\u0e48\u0e41\u0e1a\u0e1a", +"Template": "\u0e41\u0e21\u0e48\u0e41\u0e1a\u0e1a", +"Text color": "\u0e2a\u0e35\u0e02\u0e49\u0e2d\u0e04\u0e27\u0e32\u0e21", +"Background color": "\u0e2a\u0e35\u0e1e\u0e37\u0e49\u0e19\u0e2b\u0e25\u0e31\u0e07", +"Custom...": "\u0e01\u0e33\u0e2b\u0e19\u0e14\u0e40\u0e2d\u0e07", +"Custom color": "\u0e2a\u0e35\u0e17\u0e35\u0e48\u0e01\u0e33\u0e2b\u0e19\u0e14\u0e40\u0e2d\u0e07", +"No color": "\u0e44\u0e21\u0e48\u0e21\u0e35\u0e2a\u0e35", +"Table of Contents": "\u0e2a\u0e32\u0e23\u0e1a\u0e31\u0e0d", +"Show blocks": "\u0e41\u0e2a\u0e14\u0e07\u0e1a\u0e25\u0e47\u0e2d\u0e01", +"Show invisible characters": "\u0e41\u0e2a\u0e14\u0e07\u0e15\u0e31\u0e27\u0e2d\u0e31\u0e01\u0e29\u0e23\u0e17\u0e35\u0e48\u0e21\u0e2d\u0e07\u0e44\u0e21\u0e48\u0e40\u0e2b\u0e47\u0e19", +"Words: {0}": "\u0e04\u0e33: {0}", +"{0} words": "{0} \u0e04\u0e33", +"File": "\u0e44\u0e1f\u0e25\u0e4c", +"Edit": "\u0e41\u0e01\u0e49\u0e44\u0e02", +"Insert": "\u0e41\u0e17\u0e23\u0e01", +"View": "\u0e21\u0e38\u0e21\u0e21\u0e2d\u0e07", +"Format": "\u0e23\u0e39\u0e1b\u0e41\u0e1a\u0e1a", +"Table": "\u0e15\u0e32\u0e23\u0e32\u0e07", +"Tools": "\u0e40\u0e04\u0e23\u0e37\u0e48\u0e2d\u0e07\u0e21\u0e37\u0e2d", +"Powered by {0}": "\u0e02\u0e31\u0e1a\u0e40\u0e04\u0e25\u0e37\u0e48\u0e2d\u0e19\u0e42\u0e14\u0e22 {0}", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "\u0e1e\u0e37\u0e49\u0e19\u0e17\u0e35\u0e48 Rich Text \u0e01\u0e14 ALT-F9 \u0e2a\u0e33\u0e2b\u0e23\u0e31\u0e1a\u0e40\u0e21\u0e19\u0e39 \u0e01\u0e14 ALT-F10 \u0e2a\u0e33\u0e2b\u0e23\u0e31\u0e1a\u0e41\u0e16\u0e1a\u0e40\u0e04\u0e23\u0e37\u0e48\u0e2d\u0e07\u0e21\u0e37\u0e2d \u0e01\u0e14 ALT-0 \u0e2a\u0e33\u0e2b\u0e23\u0e31\u0e1a\u0e04\u0e27\u0e32\u0e21\u0e0a\u0e48\u0e27\u0e22\u0e40\u0e2b\u0e25\u0e37\u0e2d" +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/tr.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/tr.js new file mode 100644 index 0000000000..7b69596402 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/tr.js @@ -0,0 +1,261 @@ +tinymce.addI18n('tr',{ +"Redo": "Yinele", +"Undo": "Geri al", +"Cut": "Kes", +"Copy": "Kopyala", +"Paste": "Yap\u0131\u015ft\u0131r", +"Select all": "T\u00fcm\u00fcn\u00fc se\u00e7", +"New document": "Yeni dok\u00fcman", +"Ok": "Tamam", +"Cancel": "\u0130ptal", +"Visual aids": "G\u00f6rsel ara\u00e7lar", +"Bold": "Kal\u0131n", +"Italic": "\u0130talik", +"Underline": "Alt\u0131 \u00e7izili", +"Strikethrough": "\u00dcst\u00fc \u00e7izili", +"Superscript": "\u00dcst simge", +"Subscript": "Alt simge", +"Clear formatting": "Bi\u00e7imi temizle", +"Align left": "Sola hizala", +"Align center": "Ortala", +"Align right": "Sa\u011fa hizala", +"Justify": "\u0130ki yana yasla", +"Bullet list": "\u0130\u015faretli liste", +"Numbered list": "Numaral\u0131 liste ", +"Decrease indent": "Girintiyi azalt", +"Increase indent": "Girintiyi art\u0131r", +"Close": "Kapat", +"Formats": "Bi\u00e7imler", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "Taray\u0131c\u0131n\u0131z panoya do\u011frudan eri\u015fimi desteklemiyor. L\u00fctfen Ctrl+X\\\/C\\\/V klavye k\u0131sayollar\u0131n\u0131 kullan\u0131n\u0131z.", +"Headers": "Ba\u015fl\u0131klar", +"Header 1": "Ba\u015fl\u0131k 1", +"Header 2": "Ba\u015fl\u0131k 2", +"Header 3": "Ba\u015fl\u0131k 3", +"Header 4": "Ba\u015fl\u0131k 4", +"Header 5": "Ba\u015fl\u0131k 5", +"Header 6": "Ba\u015fl\u0131k 6", +"Headings": "Ba\u015fl\u0131klar", +"Heading 1": "Ba\u015fl\u0131k 1", +"Heading 2": "Ba\u015fl\u0131k 2", +"Heading 3": "Ba\u015fl\u0131k 3", +"Heading 4": "Ba\u015fl\u0131k 4", +"Heading 5": "Ba\u015fl\u0131k 5", +"Heading 6": "Ba\u015fl\u0131k 6", +"Preformatted": "\u00d6nceden bi\u00e7imlendirilmi\u015f", +"Div": "Div", +"Pre": "Pre", +"Code": "Kod", +"Paragraph": "Paragraf", +"Blockquote": "Al\u0131nt\u0131", +"Inline": "Sat\u0131r i\u00e7i", +"Blocks": "Bloklar", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "D\u00fcz metin modunda yap\u0131\u015ft\u0131r. Bu se\u00e7ene\u011fi kapatana kadar i\u00e7erikler d\u00fcz metin olarak yap\u0131\u015ft\u0131r\u0131l\u0131r.", +"Font Family": "Yaz\u0131 Tipleri", +"Font Sizes": "Yaz\u0131 Boyutlar\u0131", +"Class": "Class", +"Browse for an image": "G\u00f6rsel se\u00e7", +"OR": "ya da", +"Drop an image here": "G\u00f6rseli buraya s\u00fcr\u00fckleyin", +"Upload": "Y\u00fckle", +"Block": "Blok", +"Align": "Hizala", +"Default": "Varsay\u0131lan", +"Circle": "Daire", +"Disc": "Disk", +"Square": "Kare", +"Lower Alpha": "K\u00fc\u00e7\u00fck Harf", +"Lower Greek": "K\u00fc\u00e7\u00fck Yunan Harfleri", +"Lower Roman": "K\u00fc\u00e7\u00fck Roman Harfleri ", +"Upper Alpha": "B\u00fcy\u00fck Harf", +"Upper Roman": "B\u00fcy\u00fck Roman Harfleri ", +"Anchor": "\u00c7apa", +"Name": "\u0130sim", +"Id": "Kimlik", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "Id bir harf ile ba\u015flamal\u0131d\u0131r ve harf, rakam, \u00e7izgi, nokta, iki nokta \u00fcst\u00fcste veya alt \u00e7izgi kullan\u0131labilir.", +"You have unsaved changes are you sure you want to navigate away?": "Kaydedilmemi\u015f de\u011fi\u015fiklikler var, sayfadan ayr\u0131lmak istedi\u011finize emin misiniz?", +"Restore last draft": "Son tasla\u011f\u0131 geri y\u00fckle", +"Special character": "\u00d6zel karakter", +"Source code": "Kaynak kodu", +"Insert\/Edit code sample": "\u00d6rnek kod ekle\/d\u00fczenle", +"Language": "Dil", +"Code sample": "Code sample", +"Color": "Renk", +"R": "R", +"G": "G", +"B": "B", +"Left to right": "Soldan sa\u011fa", +"Right to left": "Sa\u011fdan sola", +"Emoticons": "\u0130fadeler", +"Document properties": "Dok\u00fcman \u00f6zellikleri", +"Title": "Ba\u015fl\u0131k", +"Keywords": "Anahtar kelimeler", +"Description": "A\u00e7\u0131klama", +"Robots": "Robotlar", +"Author": "Yazar", +"Encoding": "Kodlama", +"Fullscreen": "Tam ekran", +"Action": "Eylem", +"Shortcut": "K\u0131sayol", +"Help": "Yard\u0131m", +"Address": "Adres", +"Focus to menubar": "Men\u00fcye odaklan", +"Focus to toolbar": "Ara\u00e7 tak\u0131m\u0131na odaklan", +"Focus to element path": "\u00d6\u011fe yoluna odaklan", +"Focus to contextual toolbar": "Ba\u011flamsal ara\u00e7 tak\u0131m\u0131na odaklan", +"Insert link (if link plugin activated)": "Ba\u011flant\u0131 ekle (Ba\u011flant\u0131 eklentisi aktif ise)", +"Save (if save plugin activated)": "Kaydet (Kay\u0131t eklentisi aktif ise)", +"Find (if searchreplace plugin activated)": "Bul (Bul\/De\u011fi\u015ftir eklentisi aktif ise)", +"Plugins installed ({0}):": "Eklentiler y\u00fcklendi ({0}):", +"Premium plugins:": "Premium eklentiler:", +"Learn more...": "Detayl\u0131 bilgi...", +"You are using {0}": "\u015eu an {0} kullan\u0131yorsunuz", +"Plugins": "Plugins", +"Handy Shortcuts": "Handy Shortcuts", +"Horizontal line": "Yatay \u00e7izgi", +"Insert\/edit image": "Resim ekle\/d\u00fczenle", +"Image description": "Resim a\u00e7\u0131klamas\u0131", +"Source": "Kaynak", +"Dimensions": "Boyutlar", +"Constrain proportions": "Oranlar\u0131 koru", +"General": "Genel", +"Advanced": "Geli\u015fmi\u015f", +"Style": "Stil", +"Vertical space": "Dikey bo\u015fluk", +"Horizontal space": "Yatay bo\u015fluk", +"Border": "Kenarl\u0131k", +"Insert image": "Resim ekle", +"Image": "Resim", +"Image list": "G\u00f6rsel listesi", +"Rotate counterclockwise": "Saatin tersi y\u00f6n\u00fcnde d\u00f6nd\u00fcr", +"Rotate clockwise": "Saat y\u00f6n\u00fcnde d\u00f6nd\u00fcr", +"Flip vertically": "Dikine \u00e7evir", +"Flip horizontally": "Enine \u00e7evir", +"Edit image": "Resmi d\u00fczenle", +"Image options": "Resim ayarlar\u0131", +"Zoom in": "Yak\u0131nla\u015ft\u0131r", +"Zoom out": "Uzakla\u015ft\u0131r", +"Crop": "K\u0131rp", +"Resize": "Yeniden Boyutland\u0131r", +"Orientation": "Oryantasyon", +"Brightness": "Parlakl\u0131k", +"Sharpen": "Keskinle\u015ftir", +"Contrast": "Kontrast", +"Color levels": "Renk d\u00fczeyleri", +"Gamma": "Gama", +"Invert": "Ters \u00c7evir", +"Apply": "Uygula", +"Back": "Geri", +"Insert date\/time": "Tarih\/saat ekle", +"Date\/time": "Tarih\/saat", +"Insert link": "Ba\u011flant\u0131 ekle", +"Insert\/edit link": "Ba\u011flant\u0131 ekle\/d\u00fczenle", +"Text to display": "Yaz\u0131y\u0131 g\u00f6r\u00fcnt\u00fcle", +"Url": "Url", +"Target": "Hedef", +"None": "Hi\u00e7biri", +"New window": "Yeni pencere", +"Remove link": "Ba\u011flant\u0131y\u0131 kald\u0131r", +"Anchors": "\u00c7apalar", +"Link": "Ba\u011flant\u0131", +"Paste or type a link": "Bir ba\u011flant\u0131 yaz\u0131n yada yap\u0131\u015ft\u0131r\u0131n", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "Girdi\u011finiz URL bir e-posta adresi gibi g\u00f6r\u00fcn\u00fcyor. Gerekli olan mailto: \u00f6nekini eklemek ister misiniz?", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "Girdi\u011finiz URL bir d\u0131\u015f ba\u011flant\u0131 gibi g\u00f6r\u00fcn\u00fcyor. Gerekli olan http:\/\/ \u00f6nekini eklemek ister misiniz?", +"Link list": "Ba\u011flant\u0131 listesi", +"Insert video": "Video ekle", +"Insert\/edit video": "Video ekle\/d\u00fczenle", +"Insert\/edit media": "Medya ekle\/d\u00fczenle", +"Alternative source": "Alternatif kaynak", +"Poster": "Poster", +"Paste your embed code below:": "Video g\u00f6mme kodunu a\u015fa\u011f\u0131ya yap\u0131\u015ft\u0131r\u0131n\u0131z:", +"Embed": "G\u00f6mme", +"Media": "Medya", +"Nonbreaking space": "B\u00f6l\u00fcnemez bo\u015fluk", +"Page break": "Sayfa sonu", +"Paste as text": "Metin olarak yap\u0131\u015ft\u0131r", +"Preview": "\u00d6nizleme", +"Print": "Yazd\u0131r", +"Save": "Kaydet", +"Find": "Bul", +"Replace with": "Bununla de\u011fi\u015ftir", +"Replace": "De\u011fi\u015ftir", +"Replace all": "T\u00fcm\u00fcn\u00fc de\u011fi\u015ftir", +"Prev": "\u00d6nceki", +"Next": "Sonraki", +"Find and replace": "Bul ve de\u011fi\u015ftir", +"Could not find the specified string.": "Herhangi bir sonu\u00e7 bulunamad\u0131.", +"Match case": "B\u00fcy\u00fck\/k\u00fc\u00e7\u00fck harf duyarl\u0131", +"Whole words": "Tam kelimeler", +"Spellcheck": "Yaz\u0131m denetimi", +"Ignore": "Yoksay", +"Ignore all": "T\u00fcm\u00fcn\u00fc yoksay", +"Finish": "Bitir", +"Add to Dictionary": "S\u00f6zl\u00fc\u011fe Ekle", +"Insert table": "Tablo ekle", +"Table properties": "Tablo \u00f6zellikleri", +"Delete table": "Tablo sil", +"Cell": "H\u00fccre", +"Row": "Sat\u0131r", +"Column": "S\u00fctun", +"Cell properties": "H\u00fccre \u00f6zellikleri", +"Merge cells": "H\u00fccreleri birle\u015ftir", +"Split cell": "H\u00fccre b\u00f6l", +"Insert row before": "\u00dcste sat\u0131r ekle", +"Insert row after": "Alta sat\u0131r ekle ", +"Delete row": "Sat\u0131r sil", +"Row properties": "Sat\u0131r \u00f6zellikleri", +"Cut row": "Sat\u0131r\u0131 kes", +"Copy row": "Sat\u0131r\u0131 kopyala", +"Paste row before": "\u00dcste sat\u0131r yap\u0131\u015ft\u0131r", +"Paste row after": "Alta sat\u0131r yap\u0131\u015ft\u0131r", +"Insert column before": "Sola s\u00fctun ekle", +"Insert column after": "Sa\u011fa s\u00fctun ekle", +"Delete column": "S\u00fctun sil", +"Cols": "S\u00fctunlar", +"Rows": "Sat\u0131rlar", +"Width": "Geni\u015flik", +"Height": "Y\u00fckseklik", +"Cell spacing": "H\u00fccre aral\u0131\u011f\u0131", +"Cell padding": "H\u00fccre dolgusu", +"Caption": "Ba\u015fl\u0131k", +"Left": "Sol", +"Center": "Orta", +"Right": "Sa\u011f", +"Cell type": "H\u00fccre tipi", +"Scope": "Kapsam", +"Alignment": "Hizalama", +"H Align": "Yatay Hizalama", +"V Align": "Dikey Hizalama", +"Top": "\u00dcst", +"Middle": "Orta", +"Bottom": "Alt", +"Header cell": "Ba\u015fl\u0131k h\u00fccresi", +"Row group": "Sat\u0131r grubu", +"Column group": "S\u00fctun grubu", +"Row type": "Sat\u0131r tipi", +"Header": "Ba\u015fl\u0131k", +"Body": "G\u00f6vde", +"Footer": "Alt", +"Border color": "Kenarl\u0131k rengi", +"Insert template": "\u015eablon ekle", +"Templates": "\u015eablonlar", +"Template": "Taslak", +"Text color": "Yaz\u0131 rengi", +"Background color": "Arka plan rengi", +"Custom...": "\u00d6zel...", +"Custom color": "\u00d6zel renk", +"No color": "Renk yok", +"Table of Contents": "\u0130\u00e7erik tablosu", +"Show blocks": "Bloklar\u0131 g\u00f6ster", +"Show invisible characters": "G\u00f6r\u00fcnmez karakterleri g\u00f6ster", +"Words: {0}": "Kelime: {0}", +"{0} words": "{0} words", +"File": "Dosya", +"Edit": "D\u00fczenle", +"Insert": "Ekle", +"View": "G\u00f6r\u00fcn\u00fcm", +"Format": "Bi\u00e7im", +"Table": "Tablo", +"Tools": "Ara\u00e7lar", +"Powered by {0}": "Powered by {0}", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Zengin Metin Alan\u0131. Men\u00fc i\u00e7in ALT-F9 tu\u015funa bas\u0131n\u0131z. Ara\u00e7 \u00e7ubu\u011fu i\u00e7in ALT-F10 tu\u015funa bas\u0131n\u0131z. Yard\u0131m i\u00e7in ALT-0 tu\u015funa bas\u0131n\u0131z." +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/tr_TR.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/tr_TR.js new file mode 100644 index 0000000000..ea89bc44c3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/tr_TR.js @@ -0,0 +1,261 @@ +tinymce.addI18n('tr_TR',{ +"Redo": "Yinele", +"Undo": "Geri Al", +"Cut": "Kes", +"Copy": "Kopyala", +"Paste": "Yap\u0131\u015ft\u0131r", +"Select all": "T\u00fcm\u00fcn\u00fc se\u00e7", +"New document": "Yeni dok\u00fcman", +"Ok": "Tamam", +"Cancel": "\u0130ptal", +"Visual aids": "G\u00f6rsel ara\u00e7lar", +"Bold": "Kal\u0131n", +"Italic": "\u0130talik", +"Underline": "Alt\u0131 \u00e7izili", +"Strikethrough": "\u00dcst\u00fc \u00e7izili", +"Superscript": "\u00dcst simge", +"Subscript": "Alt simge", +"Clear formatting": "Bi\u00e7imi temizle", +"Align left": "Sola hizala", +"Align center": "Ortala", +"Align right": "Sa\u011fa hizala", +"Justify": "\u0130ki yana yasla", +"Bullet list": "S\u0131ras\u0131z liste", +"Numbered list": "S\u0131ral\u0131 liste", +"Decrease indent": "Girintiyi azalt", +"Increase indent": "Girintiyi art\u0131r", +"Close": "Kapat", +"Formats": "Bi\u00e7imler", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "Taray\u0131c\u0131n\u0131z panoya direk eri\u015fimi desteklemiyor. L\u00fctfen Ctrl+X\/C\/V klavye k\u0131sayollar\u0131n\u0131 kullan\u0131n.", +"Headers": "Ba\u015fl\u0131klar", +"Header 1": "Ba\u015fl\u0131k 1", +"Header 2": "Ba\u015fl\u0131k 2", +"Header 3": "Ba\u015fl\u0131k 3", +"Header 4": "Ba\u015fl\u0131k 4", +"Header 5": "Ba\u015fl\u0131k 5", +"Header 6": "Ba\u015fl\u0131k 6", +"Headings": "Ba\u015fl\u0131klar", +"Heading 1": "Ba\u015fl\u0131k 1", +"Heading 2": "Ba\u015fl\u0131k 2", +"Heading 3": "Ba\u015fl\u0131k 3", +"Heading 4": "Ba\u015fl\u0131k 4", +"Heading 5": "Ba\u015fl\u0131k 5", +"Heading 6": "Ba\u015fl\u0131k 6", +"Preformatted": "\u00d6nceden bi\u00e7imlendirilmi\u015f", +"Div": "Div", +"Pre": "\u00d6n", +"Code": "Kod", +"Paragraph": "Paragraf", +"Blockquote": "Al\u0131nt\u0131", +"Inline": "Sat\u0131r i\u00e7i", +"Blocks": "Bloklar", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "D\u00fcz metin modunda yap\u0131\u015ft\u0131r. Bu se\u00e7ene\u011fi kapatana kadar i\u00e7erikler d\u00fcz metin olarak yap\u0131\u015ft\u0131r\u0131l\u0131r.", +"Font Family": "Yaz\u0131tipi Ailesi", +"Font Sizes": "Yaz\u0131tipi B\u00fcy\u00fckl\u00fc\u011f\u00fc", +"Class": "S\u0131n\u0131f", +"Browse for an image": "Bir resim aray\u0131n", +"OR": "ya da", +"Drop an image here": "Buraya bir resim koy", +"Upload": "Y\u00fckle", +"Block": "Blok", +"Align": "Hizala", +"Default": "Varsay\u0131lan", +"Circle": "Daire", +"Disc": "Disk", +"Square": "Kare", +"Lower Alpha": "K\u00fc\u00e7\u00fck ABC", +"Lower Greek": "K\u00fc\u00e7\u00fck Yunan alfabesi", +"Lower Roman": "K\u00fc\u00e7\u00fck Roman alfabesi", +"Upper Alpha": "B\u00fcy\u00fck ABC", +"Upper Roman": "B\u00fcy\u00fck Roman alfabesi", +"Anchor": "\u00c7apa", +"Name": "\u0130sim", +"Id": "Id", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "Id bir harf ile ba\u015flamal\u0131d\u0131r ve sadece harfleri, rakamlar\u0131, \u00e7izgileri, noktalar\u0131, virg\u00fclleri veya alt \u00e7izgileri i\u00e7ermelidir.", +"You have unsaved changes are you sure you want to navigate away?": "Kaydedilmemi\u015f de\u011fi\u015fiklikler var, sayfadan ayr\u0131lmak istedi\u011finize emin misiniz?", +"Restore last draft": "Son tasla\u011f\u0131 kurtar", +"Special character": "\u00d6zel karakter", +"Source code": "Kaynak kodu", +"Insert\/Edit code sample": "Kod \u00f6rne\u011fini Kaydet\/D\u00fczenle", +"Language": "Dil", +"Code sample": "Kod \u00f6rne\u011fi", +"Color": "Renk", +"R": "R", +"G": "G", +"B": "B", +"Left to right": "Soldan sa\u011fa", +"Right to left": "Sa\u011fdan sola", +"Emoticons": "G\u00fcl\u00fcc\u00fckler", +"Document properties": "Dok\u00fcman \u00f6zellikleri", +"Title": "Ba\u015fl\u0131k", +"Keywords": "Anahtar kelimeler", +"Description": "A\u00e7\u0131klama", +"Robots": "Robotlar", +"Author": "Yazar", +"Encoding": "Kodlama", +"Fullscreen": "Tam ekran", +"Action": "Eylem", +"Shortcut": "K\u0131sayol", +"Help": "Yard\u0131m", +"Address": "Adres", +"Focus to menubar": "Men\u00fc \u00e7ubu\u011funa odaklan.", +"Focus to toolbar": "Ara\u00e7 \u00e7ubu\u011funa odaklan.", +"Focus to element path": "Eleman yoluna odaklan", +"Focus to contextual toolbar": "Ba\u011flamsal ara\u00e7 \u00e7ubu\u011funa odaklan", +"Insert link (if link plugin activated)": "Link ekle (Link eklentisi aktif ise)", +"Save (if save plugin activated)": "Kaydet (Kay\u0131t eklentisi aktif ise)", +"Find (if searchreplace plugin activated)": "Bul (SearchReplace eklentisi aktif ise)", +"Plugins installed ({0}):": "Y\u00fckl\u00fc eklenti say\u0131s\u0131 : ({0}):", +"Premium plugins:": "Premium eklentileri", +"Learn more...": "Daha fazla bilgi edinin.", +"You are using {0}": "{0} kullan\u0131yorsun.", +"Plugins": "Eklentiler", +"Handy Shortcuts": "Kullan\u0131\u015fl\u0131 K\u0131sayollar", +"Horizontal line": "Yatay \u00e7izgi", +"Insert\/edit image": "Resim ekle\/d\u00fczenle", +"Image description": "Resim a\u00e7\u0131klamas\u0131", +"Source": "Kaynak", +"Dimensions": "Boyutlar", +"Constrain proportions": "En - Boy oran\u0131n\u0131 koru", +"General": "Genel", +"Advanced": "Geli\u015fmi\u015f", +"Style": "Stil", +"Vertical space": "Dikey bo\u015fluk", +"Horizontal space": "Yatay bo\u015fluk", +"Border": "\u00c7er\u00e7eve", +"Insert image": "Resim ekle", +"Image": "Resim", +"Image list": "Resim listesi", +"Rotate counterclockwise": "Saat y\u00f6n\u00fcn\u00fcn tersine d\u00f6nd\u00fcr", +"Rotate clockwise": "Saat y\u00f6n\u00fcnde d\u00f6nd\u00fcr", +"Flip vertically": "Dikey \u00e7evir", +"Flip horizontally": "Yatay \u00e7evir", +"Edit image": "G\u00f6r\u00fcnt\u00fcy\u00fc d\u00fczenle", +"Image options": "G\u00f6r\u00fcnt\u00fc se\u00e7enekleri", +"Zoom in": "Yak\u0131nla\u015ft\u0131r", +"Zoom out": "Uzakla\u015ft\u0131r", +"Crop": "Kes", +"Resize": "Yeniden Boyutland\u0131r", +"Orientation": "Y\u00f6n\u00fcn\u00fc Belirle", +"Brightness": "Parlakl\u0131k", +"Sharpen": "Keskinle\u015ftir", +"Contrast": "Kontrast", +"Color levels": "Renk seviyesi", +"Gamma": "Gama", +"Invert": "Tersine \u00e7evir", +"Apply": "Uygula", +"Back": "Geri", +"Insert date\/time": "Tarih \/ Zaman ekle", +"Date\/time": "Tarih\/zaman", +"Insert link": "Ba\u011flant\u0131 ekle", +"Insert\/edit link": "Ba\u011flant\u0131 ekle\/d\u00fczenle", +"Text to display": "G\u00f6r\u00fcnen yaz\u0131", +"Url": "Url", +"Target": "Hedef", +"None": "Hi\u00e7biri", +"New window": "Yeni pencere", +"Remove link": "Ba\u011flant\u0131y\u0131 kald\u0131r", +"Anchors": "\u00c7apalar", +"Link": "Ba\u011flant\u0131", +"Paste or type a link": "Bir ba\u011flant\u0131 yap\u0131\u015ft\u0131r\u0131n yada yaz\u0131n.", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "Girdi\u011finiz URL bir eposta adresi gibi g\u00f6z\u00fck\u00fcyor. Gerekli olan mailto: \u00f6nekini eklemek ister misiniz?", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "Girdi\u011finiz URL bir d\u0131\u015f ba\u011flant\u0131 gibi g\u00f6z\u00fck\u00fcyor. Gerekli olan http:\/\/ \u00f6nekini eklemek ister misiniz?", +"Link list": "Link listesi", +"Insert video": "Video ekle", +"Insert\/edit video": "Video ekle\/d\u00fczenle", +"Insert\/edit media": "Medya ekle\/d\u00fczenle", +"Alternative source": "Alternatif kaynak", +"Poster": "Poster", +"Paste your embed code below:": "Medya g\u00f6mme kodunu buraya yap\u0131\u015ft\u0131r:", +"Embed": "G\u00f6mme", +"Media": "Medya", +"Nonbreaking space": "B\u00f6l\u00fcnemez bo\u015fluk", +"Page break": "Sayfa sonu", +"Paste as text": "Metin olarak yap\u0131\u015ft\u0131r", +"Preview": "\u00d6nizleme", +"Print": "Yazd\u0131r", +"Save": "Kaydet", +"Find": "Bul", +"Replace with": "Bununla de\u011fi\u015ftir", +"Replace": "De\u011fi\u015ftir", +"Replace all": "T\u00fcm\u00fcn\u00fc de\u011fi\u015ftir", +"Prev": "\u00d6nceki", +"Next": "Sonraki", +"Find and replace": "Bul ve de\u011fi\u015ftir", +"Could not find the specified string.": "Herhangi bir sonu\u00e7 bulunamad\u0131.", +"Match case": "B\u00fcy\u00fck \/ K\u00fc\u00e7\u00fck harfe duyarl\u0131", +"Whole words": "Tam s\u00f6zc\u00fckler", +"Spellcheck": "Yaz\u0131m denetimi", +"Ignore": "Yoksay", +"Ignore all": "T\u00fcm\u00fcn\u00fc yoksay", +"Finish": "Bitir", +"Add to Dictionary": "S\u00f6zl\u00fc\u011fe ekle", +"Insert table": "Tablo ekle", +"Table properties": "Tablo \u00f6zellikleri", +"Delete table": "Tabloyu sil", +"Cell": "H\u00fccre", +"Row": "Sat\u0131r", +"Column": "S\u00fctun", +"Cell properties": "H\u00fccre \u00f6zellikleri", +"Merge cells": "H\u00fccreleri birle\u015ftir", +"Split cell": "H\u00fccreleri ay\u0131r", +"Insert row before": "\u00d6ncesine yeni sat\u0131r ekle", +"Insert row after": "Sonras\u0131na yeni sat\u0131r ekle", +"Delete row": "Sat\u0131r\u0131 sil", +"Row properties": "Sat\u0131r \u00f6zellikleri", +"Cut row": "Sat\u0131r\u0131 kes", +"Copy row": "Sat\u0131r\u0131 kopyala", +"Paste row before": "\u00d6ncesine sat\u0131r yap\u0131\u015ft\u0131r", +"Paste row after": "Sonras\u0131na sat\u0131r yap\u0131\u015ft\u0131r", +"Insert column before": "\u00d6ncesine yeni s\u00fctun ekle", +"Insert column after": "Sonras\u0131na yeni s\u00fctun ekle", +"Delete column": "S\u00fctunu sil", +"Cols": "S\u00fctunlar", +"Rows": "Sat\u0131rlar", +"Width": "Geni\u015flik", +"Height": "Y\u00fckseklik", +"Cell spacing": "H\u00fccre aral\u0131\u011f\u0131", +"Cell padding": "H\u00fccre i\u00e7 bo\u015flu\u011fu", +"Caption": "Ba\u015fl\u0131k", +"Left": "Sol", +"Center": "Orta", +"Right": "Sa\u011f", +"Cell type": "H\u00fccre tipi", +"Scope": "Kapsam", +"Alignment": "Hizalama", +"H Align": "Yatay Hizalama", +"V Align": "Dikey Hizalama", +"Top": "\u00dcst", +"Middle": "Orta", +"Bottom": "Alt", +"Header cell": "Ba\u015fl\u0131k h\u00fccresi", +"Row group": "Sat\u0131r grubu", +"Column group": "S\u00fctun grubu", +"Row type": "Sat\u0131r tipi", +"Header": "Ba\u015fl\u0131k", +"Body": "G\u00f6vde", +"Footer": "Alt", +"Border color": "Kenarl\u0131k Rengi", +"Insert template": "\u015eablon ekle", +"Templates": "\u015eablonlar", +"Template": "Tema", +"Text color": "Yaz\u0131 rengi", +"Background color": "Arkaplan rengi", +"Custom...": "\u00d6zel", +"Custom color": "\u00d6zel Renk", +"No color": "Renk Yok", +"Table of Contents": "\u0130\u00e7indekiler", +"Show blocks": "Bloklar\u0131 g\u00f6r\u00fcnt\u00fcle", +"Show invisible characters": "G\u00f6r\u00fcnmez karakterleri g\u00f6ster", +"Words: {0}": "Kelime: {0}", +"{0} words": "{0} kelime", +"File": "Dosya", +"Edit": "D\u00fczenle", +"Insert": "Ekle", +"View": "G\u00f6r\u00fcnt\u00fcle", +"Format": "Bi\u00e7im", +"Table": "Tablo", +"Tools": "Ara\u00e7lar", +"Powered by {0}": "{0} taraf\u0131ndan yap\u0131lm\u0131\u015ft\u0131r ", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Zengin Metin Alan\u0131. Men\u00fc i\u00e7in ALT-F9 k\u0131sayolunu kullan\u0131n. Ara\u00e7 \u00e7ubu\u011fu i\u00e7in ALT-F10 k\u0131sayolunu kullan\u0131n. Yard\u0131m i\u00e7in ALT-0 k\u0131sayolunu kullan\u0131n." +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/ug.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/ug.js new file mode 100644 index 0000000000..55fe840b13 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/ug.js @@ -0,0 +1,260 @@ +tinymce.addI18n('ug',{ +"Redo": "\u0642\u0627\u064a\u062a\u0627 \u0642\u0649\u0644\u0649\u0634", +"Undo": "\u0626\u0627\u0631\u0642\u0649\u063a\u0627 \u064a\u06d0\u0646\u0649\u0634", +"Cut": "\u0643\u06d0\u0633\u0649\u0634", +"Copy": "\u0643\u06c6\u0686\u06c8\u0631\u06c8\u0634", +"Paste": "\u0686\u0627\u067e\u0644\u0627\u0634", +"Select all": "\u06be\u06d5\u0645\u0645\u0649\u0646\u0649 \u062a\u0627\u0644\u0644\u0627\u0634", +"New document": "\u064a\u06d0\u06ad\u0649 \u067e\u06c8\u062a\u06c8\u0643", +"Ok": "\u062c\u06d5\u0632\u0649\u0645\u0644\u06d5\u0634", +"Cancel": "\u0642\u0627\u0644\u062f\u06c7\u0631\u06c7\u0634", +"Visual aids": "\u0626\u06d5\u0633\u0643\u06d5\u0631\u062a\u0649\u0634", +"Bold": "\u062a\u0648\u0645", +"Italic": "\u064a\u0627\u0646\u062a\u06c7", +"Underline": "\u0626\u0627\u0633\u062a\u0649 \u0633\u0649\u0632\u0649\u0642", +"Strikethrough": "\u0626\u06c6\u0686\u06c8\u0631\u06c8\u0634 \u0633\u0649\u0632\u0649\u0642\u0649", +"Superscript": "\u0626\u06c8\u0633\u062a\u06c8\u0646\u0643\u0649 \u0628\u06d5\u0644\u06af\u06d5", +"Subscript": "\u0626\u0627\u0633\u062a\u0649\u0646\u0642\u0649 \u0628\u06d5\u0644\u06af\u06d5", +"Clear formatting": "\u0641\u0648\u0631\u0645\u0627\u062a\u0646\u0649 \u062a\u0627\u0632\u0644\u0627\u0634", +"Align left": "\u0633\u0648\u0644\u063a\u0627 \u062a\u0648\u063a\u0631\u0649\u0644\u0627\u0634", +"Align center": "\u0645\u06d5\u0631\u0643\u06d5\u0632\u06af\u06d5 \u062a\u0648\u063a\u06c7\u0631\u0644\u0627\u0634", +"Align right": "\u0626\u0648\u06ad\u063a\u0627 \u062a\u0648\u063a\u06c7\u0631\u0644\u0627\u0634", +"Justify": "\u0626\u0649\u0643\u0643\u0649 \u064a\u0627\u0646\u063a\u0627 \u062a\u0648\u063a\u06c7\u0631\u0644\u0627\u0634", +"Bullet list": "\u0628\u06d5\u0644\u06af\u06d5 \u062a\u0649\u0632\u0649\u0645\u0644\u0649\u0643", +"Numbered list": "\u0633\u0627\u0646\u0644\u0649\u0642 \u062a\u0649\u0632\u0649\u0645\u0644\u0649\u0643", +"Decrease indent": "\u0626\u0627\u0644\u062f\u0649\u063a\u0627 \u0633\u06c8\u0631\u06c8\u0634", +"Increase indent": "\u0643\u06d5\u064a\u0646\u0649\u06af\u06d5 \u0633\u06c8\u0631\u06c8\u0634", +"Close": "\u062a\u0627\u0642\u0627\u0634", +"Formats": "\u0641\u0648\u0631\u0645\u0627\u062a", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "\u0633\u0649\u0632\u0646\u0649\u06ad \u062a\u0648\u0631 \u0643\u06c6\u0631\u06af\u06c8\u0686\u0649\u06ad\u0649\u0632 \u0642\u0649\u064a\u0649\u067e \u0686\u0627\u067e\u0644\u0627\u0634 \u062a\u0627\u062e\u062a\u0649\u0633\u0649 \u0632\u0649\u064a\u0627\u0631\u06d5\u062a \u0642\u0649\u0644\u0649\u0634\u0646\u0649 \u0642\u0648\u0644\u0644\u0649\u0645\u0627\u064a\u062f\u06c7. Ctrl+X\/C\/V \u062a\u06d0\u0632\u0644\u06d5\u062a\u0645\u06d5 \u0643\u06c7\u0646\u06c7\u067e\u0643\u0649\u0633\u0649 \u0626\u0627\u0631\u0642\u0649\u0644\u0649\u0642 \u0643\u06d0\u0633\u0649\u067e \u0686\u0627\u067e\u0644\u0627\u0634 \u0645\u06d5\u0634\u063a\u06c7\u0644\u0627\u062a\u0649 \u0642\u0649\u0644\u0649\u06ad.", +"Headers": "\u0628\u06d0\u0634\u0649", +"Header 1": "\u062a\u06d0\u0645\u0627 1", +"Header 2": "\u062a\u06d0\u0645\u0627 2", +"Header 3": "\u062a\u06d0\u0645\u0627 3", +"Header 4": "\u062a\u06d0\u0645\u0627 4", +"Header 5": "\u062a\u06d0\u0645\u0627 5", +"Header 6": "\u062a\u06d0\u0645\u0627 6", +"Headings": "\u0645\u0627\u06cb\u0632\u06c7", +"Heading 1": "1 \u062f\u06d5\u0631\u0649\u062c\u0649\u0644\u0649\u0643 \u0645\u0627\u06cb\u0632\u06c7", +"Heading 2": "2 \u062f\u06d5\u0631\u0649\u062c\u0649\u0644\u0649\u0643 \u0645\u0627\u06cb\u0632\u06c7", +"Heading 3": "3 \u062f\u06d5\u0631\u0649\u062c\u0649\u0644\u0649\u0643 \u0645\u0627\u06cb\u0632\u06c7", +"Heading 4": "4 \u062f\u06d5\u0631\u0649\u062c\u0649\u0644\u0649\u0643 \u0645\u0627\u06cb\u0632\u06c7", +"Heading 5": "5 \u062f\u06d5\u0631\u0649\u062c\u0649\u0644\u0649\u0643 \u0645\u0627\u06cb\u0632\u06c7", +"Heading 6": "6 \u062f\u06d5\u0631\u0649\u062c\u0649\u0644\u0649\u0643 \u0645\u0627\u06cb\u0632\u06c7", +"Div": "Div", +"Pre": "Pre", +"Code": "\u0643\u0648\u062f", +"Paragraph": "\u067e\u0627\u0631\u0627\u06af\u0649\u0631\u0627 \u0641", +"Blockquote": "\u0626\u06d5\u0633\u0643\u06d5\u0631\u062a\u0649\u0634", +"Inline": "\u0626\u0649\u0686\u0643\u0649", +"Blocks": "\u0631\u0627\u064a\u0648\u0646", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "\u06be\u0627\u0632\u0649\u0631 \u0686\u0627\u067e\u0644\u0649\u0633\u0649\u06ad\u0649\u0632 \u0633\u0627\u067e \u062a\u06d0\u0643\u0649\u0634 \u0645\u06d5\u0632\u0645\u06c7\u0646\u0649 \u0686\u0627\u067e\u0644\u0649\u0646\u0649\u062f\u06c7. \u062a\u06d0\u0643\u0649\u0634 \u0634\u06d5\u0643\u0644\u0649\u062f\u06d5 \u0686\u0627\u067e\u0644\u0627\u0634 \u062a\u06d5\u06ad\u0634\u0649\u0643\u0649\u0646\u0649 \u062a\u0627\u0642\u0649\u06cb\u06d5\u062a\u0643\u06d5\u0646\u06af\u06d5 \u0642\u06d5\u062f\u06d5\u0631.", +"Font Family": "\u062e\u06d5\u062a \u0646\u06c7\u0633\u062e\u0649\u0633\u0649", +"Font Sizes": "\u062e\u06d5\u062a \u0686\u0648\u06ad\u0644\u06c7\u0642\u0649", +"Class": "\u062a\u06c8\u0631", +"Browse for an image": "\u0631\u06d5\u0633\u0649\u0645\u0646\u0649 \u0643\u06c6\u0631\u0633\u0649\u062a\u0649\u0634", +"OR": "\u064a\u0627\u0643\u0649", +"Drop an image here": "\u0628\u06c7 \u064a\u06d5\u0631\u062f\u0649\u0643\u0649 \u0631\u06d5\u0633\u0649\u0645\u0646\u0649 \u0626\u06c6\u0686\u06c8\u0631\u06c8\u0634", +"Upload": "\u0686\u0649\u0642\u0649\u0631\u0649\u0634", +"Block": "\u067e\u0627\u0631\u0686\u06d5", +"Align": "\u062a\u0648\u063a\u0631\u0649\u0644\u0649\u0646\u0649\u0634\u0649", +"Default": "\u0633\u06c8\u0643\u06c8\u062a", +"Circle": "\u0686\u06d5\u0645\u0628\u06d5\u0631", +"Disc": "\u062f\u06d0\u0633\u0643\u0627", +"Square": "\u0643\u06cb\u0627\u062f\u0631\u0627\u062a", +"Lower Alpha": "\u0626\u0649\u0646\u06af\u0649\u0644\u0649\u0632\u0686\u06d5 \u0643\u0649\u0686\u0649\u0643 \u064a\u06d0\u0632\u0649\u0644\u0649\u0634\u0649", +"Lower Greek": "\u06af\u0631\u06d0\u062a\u0633\u0649\u064a\u0649\u0686\u06d5 \u0643\u0649\u0686\u0649\u0643 \u064a\u06d0\u0632\u0649\u0644\u0649\u0634\u0649", +"Lower Roman": "\u0631\u0649\u0645\u0686\u06d5 \u0643\u0649\u0686\u0649\u0643 \u064a\u06d0\u0632\u0649\u0644\u0649\u0634\u0649", +"Upper Alpha": "\u0626\u0649\u0646\u06af\u0649\u0644\u0649\u0632\u0686\u06d5 \u0686\u0648\u06ad \u064a\u06d0\u0632\u0649\u0644\u0649\u0634\u0649", +"Upper Roman": "\u0631\u0649\u0645\u0686\u06d5 \u0686\u0648\u06ad \u064a\u06d0\u0632\u0649\u0644\u0649\u0634\u0649", +"Anchor": "\u0626\u06c7\u0644\u0627\u0646\u0645\u0627", +"Name": "\u0646\u0627\u0645\u0649", +"Id": "Id", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "ID \u0686\u0648\u0642\u06c7\u0645 \u06be\u06d5\u0631\u0649\u067e \u0628\u0649\u0644\u06d5\u0646 \u0628\u0627\u0634\u0644\u0649\u0646\u0649\u0634\u0649 \u0643\u06d0\u0631\u06d5\u0643 \u060c \u0626\u0627\u0631\u0642\u0649\u0633\u0649 \u067e\u06d5\u0642\u06d5\u062a \u06be\u06d5\u0631\u0649\u067e \u060c \u0633\u0627\u0646 \u060c \u0626\u0627\u064a\u0631\u0649\u0634 \u0628\u06d5\u0644\u06af\u0649\u0633\u0649 \u060c \u0686\u0649\u0643\u0649\u062a \u06cb\u06d5 \u0626\u0627\u0633\u062a\u0649 \u0633\u0649\u0632\u0649\u0642\u0649 \u062f\u0649\u0646 \u0626\u0649\u0628\u0627\u0631\u06d5\u062a .", +"You have unsaved changes are you sure you want to navigate away?": "\u0633\u0649\u0632 \u062a\u06d0\u062e\u0649 \u0645\u06d5\u0632\u0645\u06c7\u0646\u0646\u0649 \u0633\u0627\u0642\u0644\u0649\u0645\u0649\u062f\u0649\u06ad\u0649\u0632\u060c \u0626\u0627\u064a\u0631\u0649\u0644\u0627\u0645\u0633\u0649\u0632\u061f", +"Restore last draft": "\u0626\u0627\u062e\u0649\u0631\u0642\u0649 \u0643\u06c7\u067e\u0649\u064a\u0649\u06af\u06d5 \u0642\u0627\u064a\u062a\u0649\u0634", +"Special character": "\u0626\u0627\u0644\u0627\u06be\u0649\u062f\u06d5 \u0628\u06d5\u0644\u06af\u0649\u0644\u06d5\u0631", +"Source code": "\u0626\u06d5\u0633\u0644\u0649 \u0643\u0648\u062f\u0649", +"Insert\/Edit code sample": "\u0643\u0648\u062f \u0645\u0649\u0633\u0627\u0644\u0649\\\u0642\u0649\u0633\u062a\u06c7\u0631\u06c7\u0634", +"Language": "\u062a\u0649\u0644", +"Code sample": "\u0643\u0648\u062f \u0645\u0649\u0633\u0627\u0644\u0649", +"Color": "\u0631\u06d5\u06ad", +"R": "R", +"G": "G", +"B": "B", +"Left to right": "\u0633\u0648\u0644\u062f\u0649\u0646 \u0626\u0648\u06ad\u063a\u0627 ", +"Right to left": "\u0626\u0648\u06ad\u062f\u0649\u0646 \u0633\u0648\u0644\u063a\u0627", +"Emoticons": "\u0686\u0649\u0631\u0627\u064a \u0626\u0649\u067e\u0627\u062f\u06d5", +"Document properties": "\u06be\u06c6\u062c\u062c\u06d5\u062a \u062e\u0627\u0633\u0644\u0649\u0642\u0649", +"Title": "\u062a\u06d0\u0645\u0627", +"Keywords": "\u06be\u0627\u0644\u0642\u0649\u0644\u0649\u0642 \u0633\u06c6\u0632", +"Description": "\u062a\u06d5\u0633\u0649\u06cb\u0649\u0631", +"Robots": "\u0645\u0627\u0634\u0649\u0646\u0627 \u0626\u0627\u062f\u06d5\u0645", +"Author": "\u0626\u06c7\u0644\u0627\u0646\u0645\u0627", +"Encoding": "\u0643\u0648\u062f\u0644\u0627\u0634", +"Fullscreen": "\u067e\u06c8\u062a\u06c8\u0646 \u0626\u06d0\u0643\u0631\u0627\u0646", +"Action": "\u06be\u06d5\u0631\u0649\u0643\u06d5\u062a", +"Shortcut": "\u0642\u0649\u0633\u0642\u0627 \u064a\u0648\u0644", +"Help": "\u064a\u0627\u0631\u062f\u06d5\u0645", +"Address": "\u0626\u0627\u062f\u0649\u0631\u0649\u0633", +"Focus to menubar": "\u062a\u0649\u0632\u0649\u0645\u0644\u0649\u0643 \u0633\u0649\u062a\u0648\u0646\u0649\u063a\u0627 \u062f\u0649\u0642\u06d5\u062a", +"Focus to toolbar": "\u0642\u06c7\u0631\u0627\u0644 \u0633\u0649\u062a\u0648\u0646\u0649\u063a\u0627 \u062f\u0649\u0642\u06d5\u062a", +"Focus to element path": "\u0626\u06d0\u0644\u0649\u0645\u0649\u0646\u062a\u0644\u0627\u0631 \u064a\u0648\u0644\u0649\u063a\u0627 \u062f\u0649\u0642\u06d5\u062a", +"Focus to contextual toolbar": "\u0643\u0648\u0646\u062a\u06d0\u0643\u0649\u0633\u062a \u0642\u0648\u0631\u0627\u0644 \u0626\u0649\u0633\u062a\u0648\u0646\u0649\u063a\u0627 \u062f\u06d0\u0642\u06d5\u062a", +"Insert link (if link plugin activated)": "\u0626\u06c7\u0644\u0627\u0646\u0645\u0627 \u0642\u0649\u0633\u062a\u06c7\u0631\u06c7\u06ad (\u0626\u06c7\u0644\u0627\u0646\u0645\u0627 \u0642\u0649\u0633\u062a\u06c7\u0631\u06c7\u0634 \u0642\u0649\u0633\u062a\u06c7\u0631\u0645\u0649\u0633\u0649\u0646\u0649 \u0642\u0648\u0632\u063a\u0627\u062a\u0642\u0627\u0646 \u0626\u06d5\u06be\u06cb\u0627\u0644\u062f\u0627)", +"Save (if save plugin activated)": "\u0633\u0627\u0642\u0644\u0627\u0634 (\u0633\u0627\u0642\u0644\u0627\u0634 \u0642\u0649\u0633\u062a\u06c7\u0631\u0645\u0649\u0633\u0649\u0646\u0649 \u0642\u0648\u0632\u063a\u0627\u062a\u0642\u0627\u0646 \u0626\u06d5\u06be\u06cb\u0627\u0644\u062f\u0627)", +"Find (if searchreplace plugin activated)": "\u0626\u0649\u0632\u062f\u06d5\u0634 (\u0626\u0649\u0632\u062f\u06d5\u0634 \u0642\u0649\u0633\u062a\u06c7\u0631\u0645\u0649\u0633\u0649 \u0642\u0648\u0632\u063a\u0649\u062a\u0649\u0644\u063a\u0627\u0646 \u0626\u06d5\u06be\u06cb\u0627\u0644\u062f\u0627)", +"Plugins installed ({0}):": "\u0642\u0627\u0686\u0649\u0644\u0627\u0646\u063a\u0627\u0646 \u0642\u0649\u0633\u062a\u06c7\u0631\u06c7\u0644\u0645\u0627 ({0}):", +"Premium plugins:": "\u064a\u06c7\u0642\u0649\u0631\u0649 \u062f\u06d5\u0631\u0649\u062c\u0649\u0644\u0649\u0643 \u0642\u0649\u0633\u062a\u06c7\u0631\u06c7\u0644\u0645\u0627 :", +"Learn more...": "\u062a\u06d0\u062e\u0649\u0645\u06c7 \u0686\u06c8\u0634\u0649\u0646\u0649\u0634 ...", +"You are using {0}": "\u0626\u0649\u0634\u0644\u0649\u062a\u0649\u06cb\u0627\u062a\u0642\u0649\u0646\u0649\u06ad\u0649\u0632 {0}", +"Plugins": "\u0642\u0649\u0633\u062a\u06c7\u0631\u06c7\u0644\u0645\u0627", +"Handy Shortcuts": "\u0642\u0648\u0644\u0627\u064a\u0644\u0649\u0642 \u0642\u0649\u0633\u0642\u0627 \u064a\u0648\u0644", +"Horizontal line": "\u06af\u0648\u0631\u0632\u0649\u0646\u062a\u0627\u0644 \u0642\u06c7\u0631", +"Insert\/edit image": "\u0631\u06d5\u0633\u0649\u0645 \u0642\u0649\u0633\u062a\u06c7\u0631\u06c7\u0634 \u064a\u0627\u0643\u0649 \u062a\u06d5\u06be\u0631\u0649\u0631\u0644\u06d5\u0634", +"Image description": "\u0631\u06d5\u0633\u0649\u0645 \u062a\u06d5\u0633\u06cb\u0649\u0631\u0649", +"Source": "\u0645\u06d5\u0646\u0628\u06d5", +"Dimensions": "\u0686\u0648\u06ad-\u0643\u0649\u0686\u0649\u0643", +"Constrain proportions": "\u0626\u06d0\u06af\u0649\u0632\u0644\u0649\u0643-\u0643\u06d5\u06ad\u0644\u0649\u0643 \u0646\u0649\u0633\u067e\u0649\u062a\u0649\u0646\u0649 \u0633\u0627\u0642\u0644\u0627\u0634", +"General": "\u0626\u0627\u062f\u06d5\u062a\u062a\u0649\u0643\u0649", +"Advanced": "\u0626\u0627\u0644\u0627\u06be\u0649\u062f\u06d5", +"Style": "\u0626\u06c7\u0633\u0644\u06c7\u067e", +"Vertical space": "\u06cb\u06d0\u0631\u062a\u0649\u0643\u0627\u0644 \u0628\u0648\u0634\u0644\u06c7\u0642", +"Horizontal space": "\u06af\u0648\u0631\u0632\u0649\u0646\u062a\u0627\u0644 \u0628\u0648\u0634\u0644\u06c7\u0642", +"Border": "\u064a\u0627\u0642\u0627", +"Insert image": "\u0631\u06d5\u0633\u0649\u0645 \u0642\u0649\u0633\u062a\u06c7\u0631\u06c7\u0634", +"Image": "\u0631\u06d5\u0633\u0649\u0645", +"Image list": "\u0631\u06d5\u0633\u0649\u0645 \u062a\u0649\u0632\u0649\u0645\u0644\u0649\u0643\u0649", +"Rotate counterclockwise": "\u200f\u200f\u0633\u0627\u0626\u06d5\u062a\u0643\u06d5 \u0642\u0627\u0631\u0634\u0649 \u0686\u06c6\u0631\u06c8\u0634", +"Rotate clockwise": "\u200f\u200f\u0633\u0627\u0626\u06d5\u062a \u064a\u06c6\u0646\u0649\u0644\u0649\u0634\u0649\u062f\u06d5 \u0686\u06c6\u0631\u06c8\u0634", +"Flip vertically": "\u06cb\u06d0\u0631\u062a\u0649\u0643\u0627\u0644 \u0626\u06c6\u0631\u06c8\u0634", +"Flip horizontally": "\u06af\u0648\u0631\u0649\u0632\u0648\u0646\u062a\u0627\u0644 \u0626\u06c6\u0631\u06c8\u0634", +"Edit image": "\u0631\u06d5\u0633\u0649\u0645 \u062a\u06d5\u06be\u0631\u0649\u0631\u0644\u06d5\u0634", +"Image options": "\u0631\u06d5\u0633\u0649\u0645 \u062a\u0627\u0644\u0644\u0627\u0646\u0645\u0649\u0644\u0649\u0631\u0649", +"Zoom in": "\u064a\u06d0\u0642\u0649\u0646\u0644\u0627\u062a\u0645\u0627\u0642", +"Zoom out": "\u064a\u0649\u0631\u0627\u0642\u0644\u0627\u062a\u0645\u0627\u0642", +"Crop": "\u0642\u0649\u064a\u0649\u0634", +"Resize": "\u0686\u0648\u06ad\u0644\u06c7\u0642\u0649\u0646\u0649 \u0626\u06c6\u0632\u06af\u06d5\u0631\u062a\u0649\u0634", +"Orientation": "\u064a\u06c6\u0646\u0649\u0644\u0649\u0634", +"Brightness": "\u064a\u0648\u0631\u06c7\u0642\u0644\u06c7\u0642\u0649", +"Sharpen": "\u0626\u06c6\u062a\u0643\u06c8\u0631\u0644\u06d5\u0634\u062a\u06c8\u0631\u06c8\u0634", +"Contrast": "\u0633\u06d0\u0644\u0649\u0634\u062a\u06c7\u0631\u0645\u0627", +"Color levels": "\u0631\u06d5\u06ad \u062f\u06d5\u0631\u0649\u062c\u0649\u0644\u0649\u0631\u0649", +"Gamma": "\u06af\u0627\u0645\u0645\u0627", +"Invert": "\u062a\u06d5\u062a\u06c8\u0631", +"Apply": "\u0642\u0648\u0644\u0644\u0649\u0646\u0649\u0634", +"Back": "\u0642\u0627\u064a\u062a\u0649\u0634", +"Insert date\/time": "\u0686\u0649\u0633\u0644\u0627\/\u06cb\u0627\u0642\u0649\u062a \u0643\u0649\u0631\u06af\u06c8\u0632\u06c8\u0634", +"Date\/time": "\u0686\u06d0\u0633\u0644\u0627\\\u06cb\u0627\u0642\u0649\u062a", +"Insert link": "\u0626\u06c7\u0644\u0649\u0646\u0649\u0634 \u0642\u0649\u0633\u062a\u06c7\u0631\u06c7\u0634", +"Insert\/edit link": "\u0626\u06c7\u0644\u0649\u0646\u0649\u0634 \u0642\u06c7\u0633\u062a\u06c7\u0631\u06c7\u0634\/\u062a\u06d5\u06be\u0631\u0649\u0631\u0644\u06d5\u0634", +"Text to display": "\u0643\u06c6\u0631\u06c8\u0646\u0649\u062f\u0649\u063a\u0627\u0646 \u0645\u06d5\u0632\u0645\u06c7\u0646", +"Url": "\u0626\u0627\u062f\u0631\u0649\u0633", +"Target": "\u0646\u0649\u0634\u0627\u0646", +"None": "\u064a\u0648\u0642", +"New window": "\u064a\u06d0\u06ad\u0649 \u0643\u06c6\u0632\u0646\u06d5\u0643", +"Remove link": "\u0626\u06c7\u0644\u0649\u0646\u0649\u0634 \u0626\u06c6\u0686\u06c8\u0631\u06c8\u0634", +"Anchors": "\u0626\u06c7\u0644\u0649\u0646\u0649\u0634", +"Link": "\u0626\u06c7\u0644\u0649\u0646\u0649\u0634", +"Paste or type a link": "\u0626\u06c7\u0644\u0649\u0646\u0649\u0634 \u0686\u0627\u067e\u0644\u0627\u06ad \u064a\u0627\u0643\u0649 \u0643\u0649\u0631\u06af\u06c8\u0632\u06c8\u06ad", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "\u0633\u0649\u0632 \u0643\u0649\u0631\u06af\u06c8\u0632\u06af\u06d5\u0646 URL \u0628\u0649\u0631 \u0626\u06d0\u0644\u062e\u06d5\u062a \u0626\u0627\u062f\u0631\u06d0\u0633\u0649\u062f\u06d5\u0643 \u0642\u0649\u0644\u0649\u067e \u062a\u06c7\u0631\u0649\u062f\u06c7\u060c\u062a\u06d5\u0644\u06d5\u067e \u0642\u0649\u0644\u0649\u0646\u063a\u0627\u0646 mailto \u0646\u0649 \u0642\u06c7\u0634\u0627\u0645\u0633\u0649\u0632\u061f", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "\u0633\u0649\u0632 \u0643\u0649\u0631\u06af\u06c8\u0632\u06af\u06d5\u0646 \u062a\u0648\u0631 \u0626\u0627\u062f\u0631\u06d0\u0633\u0649 \u0633\u0649\u0631\u062a\u0642\u0649 \u0626\u06c7\u0644\u0627\u0646\u0645\u0649\u062f\u06d5\u0643 \u0642\u0649\u0644\u0649\u067e \u062a\u06c7\u0631\u0649\u062f\u06c7 \u060c\u062a\u06d5\u0644\u06d5\u067e \u0642\u0649\u0644\u0649\u0646\u063a\u0627\u0646 http:\/\/ \u0646\u0649 \u0642\u0648\u0634\u0627\u0645\u0633\u0649\u0632\u061f", +"Link list": "\u0626\u06c7\u0644\u0649\u0646\u0649\u0634 \u062a\u06c8\u0631\u0649", +"Insert video": "\u0633\u0649\u0646 \u0642\u0649\u0633\u062a\u06c7\u0631\u06c7\u0634", +"Insert\/edit video": "\u0633\u0649\u0646 \u0642\u0649\u0633\u062a\u06c7\u0631\u06c7\u0634\/\u062a\u06d5\u06be\u0631\u0649\u0631\u0644\u06d5\u0634", +"Insert\/edit media": "\u0645\u06d0\u062f\u0649\u064a\u0627 \u0642\u0649\u0633\u062a\u06c7\u0631\u06c7\u0634\/\u062a\u06d5\u06be\u0631\u0649\u0631\u0644\u06d5\u0634", +"Alternative source": "\u062a\u06d5\u0633\u06cb\u0649\u0631\u0649", +"Poster": "\u064a\u0648\u0644\u0644\u0649\u063a\u06c7\u0686\u0649", +"Paste your embed code below:": "\u0642\u0649\u0633\u062a\u06c7\u0631\u0645\u0627\u0642\u0686\u0649 \u0628\u0648\u0644\u063a\u0627\u0646 \u0643\u0648\u062f\u0646\u0649 \u0686\u0627\u067e\u0644\u0627\u06ad", +"Embed": "\u0642\u0649\u0633\u062a\u06c7\u0631\u06c7\u0634", +"Media": "\u0645\u06d0\u062f\u0649\u064a\u0627", +"Nonbreaking space": "\u0628\u0648\u0634\u0644\u06c7\u0642", +"Page break": "\u0628\u06d5\u062a \u0626\u0627\u062e\u0649\u0631\u0644\u0627\u0634\u062a\u06c7\u0631\u06c7\u0634", +"Paste as text": "\u062a\u06d0\u0643\u0649\u0634 \u0634\u06d5\u0643\u0644\u0649\u062f\u06d5 \u0686\u0627\u067e\u0644\u0627\u0634", +"Preview": "\u0643\u06c6\u0631\u06c8\u0634", +"Print": "\u0628\u06d0\u0633\u0649\u0634", +"Save": "\u0633\u0627\u0642\u0644\u0627\u0634", +"Find": "\u0626\u0649\u0632\u062f\u06d5\u0634", +"Replace with": "\u0626\u0627\u0644\u0645\u0627\u0634\u062a\u06c7\u0631\u06c7\u0634", +"Replace": "\u0626\u0627\u0644\u0645\u0627\u0634\u062a\u06c7\u0631\u06c7\u0634", +"Replace all": "\u06be\u06d5\u0645\u0645\u0649\u0646\u0649 \u0626\u0627\u0644\u0645\u0627\u0634\u062a\u06c7\u0631\u06c7\u0634", +"Prev": "\u0626\u0627\u0644\u062f\u0649\u0646\u0642\u0649\u0633\u0649", +"Next": "\u0643\u06d0\u064a\u0649\u0646\u0643\u0649\u0633\u0649", +"Find and replace": "\u0626\u0649\u0632\u062f\u06d5\u0634 \u06cb\u06d5 \u0626\u0627\u0644\u0645\u0627\u0634\u062a\u06c7\u0631\u06c7\u0634", +"Could not find the specified string.": "\u0626\u0649\u0632\u062f\u0649\u0645\u06d5\u0643\u0686\u0649 \u0628\u0648\u0644\u063a\u0627\u0646 \u0645\u06d5\u0632\u0645\u06c7\u0646\u0646\u0649 \u062a\u0627\u067e\u0627\u0644\u0645\u0649\u062f\u0649.", +"Match case": "\u0686\u0648\u06ad \u0643\u0649\u0686\u0649\u0643 \u06be\u06d5\u0631\u0649\u067e\u0646\u0649 \u067e\u06d5\u0631\u0649\u0642\u0644\u06d5\u0646\u062f\u06c8\u0631\u06c8\u0634", +"Whole words": "\u062a\u0648\u0644\u06c7\u0642 \u0645\u0627\u0633\u0644\u0627\u0634\u062a\u06c7\u0631\u06c7\u0634", +"Spellcheck": "\u0626\u0649\u0645\u0644\u0627 \u062a\u06d5\u0643\u0634\u06c8\u0631\u06c8\u0634", +"Ignore": "\u0626\u06c6\u062a\u0643\u06c8\u0632\u06c8\u0634", +"Ignore all": "\u06be\u06d5\u0645\u0645\u0649\u0646\u0649 \u0626\u06c6\u062a\u0643\u06c8\u0632\u06c8\u0634", +"Finish": "\u0626\u0627\u062e\u0649\u0631\u0644\u0627\u0634\u062a\u06c7\u0631\u06c7\u0634", +"Add to Dictionary": "\u0644\u06c7\u063a\u06d5\u062a \u0642\u0648\u0634\u06c7\u0634", +"Insert table": "\u062c\u06d5\u062f\u06cb\u06d5\u0644 \u0642\u0649\u0633\u062a\u06c7\u0631\u06c7\u0634", +"Table properties": "\u062c\u06d5\u062f\u06cb\u06d5\u0644 \u062e\u0627\u0633\u0644\u0649\u0642\u0649", +"Delete table": "\u062c\u06d5\u062f\u06cb\u06d5\u0644 \u0626\u06c6\u0686\u06c8\u0631\u0634", +"Cell": "\u0643\u0627\u062a\u06d5\u0643", +"Row": "\u0642\u06c7\u0631", +"Column": "\u0631\u06d5\u062a", +"Cell properties": "\u0643\u0627\u062a\u06d5\u0643 \u062e\u0627\u0633\u0644\u0649\u0642\u0649", +"Merge cells": "\u0643\u0627\u062a\u06d5\u0643 \u0628\u0649\u0631\u0644\u06d5\u0634\u062a\u06c8\u0631\u06c8\u0634", +"Split cell": "\u0643\u0627\u062a\u06d5\u0643 \u067e\u0627\u0631\u0686\u0649\u0644\u0627\u0634", +"Insert row before": "\u0626\u0627\u0644\u062f\u0649\u063a\u0627 \u0642\u06c7\u0631 \u0642\u0649\u0633\u062a\u06c7\u0631\u06c7\u0634", +"Insert row after": "\u0626\u0627\u0631\u0642\u0649\u063a\u0627 \u0642\u06c7\u0631 \u0642\u0649\u0633\u062a\u06c7\u0631\u06c7\u0634", +"Delete row": "\u0642\u06c7\u0631 \u0626\u06c6\u0686\u06c8\u0631\u06c8\u0634", +"Row properties": "\u0642\u06c7\u0631 \u062e\u0627\u0633\u0644\u0649\u0642\u0649", +"Cut row": "\u0642\u06c7\u0631 \u0643\u06d0\u0633\u0649\u0634", +"Copy row": "\u0642\u06c7\u0631 \u0643\u06c6\u0686\u06c8\u0631\u06c8\u0634", +"Paste row before": "\u0642\u06c7\u0631 \u0626\u0627\u0644\u062f\u0649\u063a\u0627 \u0686\u0627\u067e\u0644\u0627\u0634", +"Paste row after": "\u0642\u06c7\u0631 \u0643\u06d5\u064a\u0646\u0649\u06af\u06d5 \u0686\u0627\u067e\u0644\u0627\u0634", +"Insert column before": "\u0631\u06d5\u062a \u0626\u0627\u0644\u062f\u0649\u063a\u0627 \u0642\u0649\u0633\u062a\u06c7\u0631\u06c7\u0634", +"Insert column after": "\u0631\u06d5\u062a \u0643\u06d5\u064a\u0646\u0649\u06af\u06d5 \u0642\u0649\u0633\u062a\u06c7\u0631\u06c7\u0634", +"Delete column": "\u0631\u06d5\u062a \u0626\u06c6\u0686\u06c8\u0631\u06c8\u0634", +"Cols": "\u0631\u06d5\u062a", +"Rows": "\u0642\u06c7\u0631", +"Width": "\u0643\u06d5\u06ad\u0644\u0649\u0643\u0649", +"Height": "\u0626\u06d0\u06af\u0649\u0632\u0644\u0649\u0643\u0649", +"Cell spacing": "\u0643\u0627\u062a\u06d5\u0643 \u0633\u0649\u0631\u062a\u0642\u0649 \u0626\u0627\u0631\u0649\u0644\u0649\u0642\u0649", +"Cell padding": "\u0643\u0627\u062a\u06d5\u0643 \u0626\u0649\u0686\u0643\u0649 \u0626\u0627\u0631\u0649\u0644\u0649\u0642\u0649", +"Caption": "\u0686\u06c8\u0634\u06d5\u0646\u062f\u06c8\u0631\u06c8\u0634", +"Left": "\u0633\u0648\u0644", +"Center": "\u0645\u06d5\u0631\u0643\u06d5\u0632", +"Right": "\u0626\u0648\u06ad", +"Cell type": "\u0643\u0627\u062a\u06d5\u0643 \u062a\u0649\u067e\u0649", +"Scope": "\u062f\u0627\u0626\u0649\u0631\u06d5", +"Alignment": "\u064a\u06c6\u0644\u0649\u0646\u0649\u0634\u0649", +"H Align": "\u06af\u0648\u0631\u0632\u0649\u0646\u062a\u0627\u0644 \u062a\u0648\u063a\u0631\u0649\u0644\u0627\u0634", +"V Align": "\u06cb\u06d0\u0631\u062a\u0649\u0643\u0627\u0644 \u062a\u0648\u063a\u0631\u0649\u0644\u0627\u0634", +"Top": "\u0626\u06c8\u0633\u062a\u0649", +"Middle": "\u0626\u0648\u062a\u062a\u06c7\u0631\u0633\u0649", +"Bottom": "\u0626\u0627\u0633\u062a\u0649", +"Header cell": "\u0628\u0627\u0634 \u0643\u0627\u062a\u06d5\u0643", +"Row group": "\u0642\u06c7\u0631 \u06af\u06c7\u0631\u06c7\u067e\u067e\u0649\u0633\u0649", +"Column group": "\u0631\u06d5\u062a \u06af\u06c7\u0631\u06c7\u067e\u067e\u0649\u0633\u0649", +"Row type": "\u0642\u06c7\u0631 \u062a\u0649\u067e\u0649", +"Header": "\u0628\u06d0\u0634\u0649", +"Body": "\u0628\u06d5\u062f\u0649\u0646\u0649", +"Footer": "\u067e\u06c7\u062a\u0649", +"Border color": "\u0631\u0627\u0645\u0643\u0627 \u0631\u06d5\u06ad\u06af\u0649", +"Insert template": "\u0626\u06c8\u0644\u06af\u06d5 \u0642\u0649\u0633\u062a\u06c7\u0631\u06c7\u0634", +"Templates": "\u0626\u06c8\u0644\u06af\u0649\u0644\u06d5\u0631", +"Template": "\u0626\u06c8\u0644\u06af\u0649\u0644\u06d5\u0631", +"Text color": "\u062e\u06d5\u062a \u0631\u06d5\u06ad\u06af\u0649", +"Background color": "\u0626\u0627\u0631\u0642\u0627 \u0631\u06d5\u06ad\u06af\u0649", +"Custom...": "\u0626\u0649\u062e\u062a\u0649\u064a\u0627\u0631\u0649", +"Custom color": "\u0626\u0649\u062e\u062a\u0649\u064a\u0627\u0631\u0649 \u0631\u06d5\u06ad", +"No color": "\u0631\u06d5\u06ad \u064a\u0648\u0642", +"Table of Contents": "\u062c\u06d5\u062f\u06d5\u0644\u0646\u0649\u06ad \u0645\u06d5\u0632\u0645\u06c7\u0646\u0649", +"Show blocks": "\u0631\u0627\u064a\u0648\u0646 \u0643\u06c6\u0631\u0633\u0649\u062a\u0649\u0634", +"Show invisible characters": "\u0643\u06c6\u0631\u06c8\u0646\u0645\u06d5\u064a\u062f\u0649\u063a\u0627\u0646 \u06be\u06d5\u0631\u0649\u067e\u0644\u06d5\u0631\u0646\u0649 \u0643\u06c6\u0631\u0633\u0649\u062a\u0649\u0634", +"Words: {0}": "\u0633\u06c6\u0632: {0}", +"{0} words": "{0} \u0633\u06c6\u0632", +"File": "\u06be\u06c6\u062c\u062c\u06d5\u062a", +"Edit": "\u062a\u06d5\u06be\u0631\u0649\u0631\u0644\u06d5\u0634", +"Insert": "\u0642\u0649\u0633\u062a\u06c7\u0631\u06c7\u0634", +"View": "\u0643\u06c6\u0631\u06c8\u0634", +"Format": "\u0641\u0648\u0631\u0645\u0627\u062a", +"Table": "\u062c\u06d5\u062f\u06cb\u06d5\u0644", +"Tools": "\u0642\u06c7\u0631\u0627\u0644", +"Powered by {0}": "\u062a\u06d0\u062e\u0646\u0649\u0643\u0649\u062f\u0627 {0}", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "\u0645\u0648\u0644 \u0645\u06d5\u0632\u0645\u06c7\u0646\u0644\u06c7\u0642 \u062a\u06d0\u0643\u06d0\u0633\u0649\u062a \u0631\u0627\u0645\u0643\u0649\u0633\u0649 \u0631\u0627\u064a\u0648\u0646\u0649\u062f\u0627 \u062a\u0649\u0632\u0649\u0645\u0644\u0649\u0643 \u0626\u06c8\u0686\u06c8\u0646 ALT-F9 \u0646\u0649\u060c \u0642\u0648\u0631\u0627\u0644 \u0628\u0627\u0644\u062f\u0649\u0642\u0649 \u0626\u06c8\u0686\u06c8\u0646 ALT-F10 \u0646\u0649\u060c \u064a\u0627\u0631\u062f\u06d5\u0645 \u0626\u06c8\u0686\u06c8\u0646 ALT-0 \u0646\u0649 \u0628\u06d0\u0633\u0649\u06ad" +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/uk.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/uk.js new file mode 100644 index 0000000000..976ca53ace --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/uk.js @@ -0,0 +1,261 @@ +tinymce.addI18n('uk',{ +"Redo": "\u041f\u043e\u0432\u0435\u0440\u043d\u0443\u0442\u0438", +"Undo": "\u0421\u043a\u0430\u0441\u0443\u0432\u0430\u0442\u0438", +"Cut": "\u0412\u0438\u0440\u0456\u0437\u0430\u0442\u0438", +"Copy": "\u041a\u043e\u043f\u0456\u044e\u0432\u0430\u0442\u0438", +"Paste": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u0438", +"Select all": "\u0412\u0438\u0434\u0456\u043b\u0438\u0442\u0438 \u0432\u0441\u0435", +"New document": "\u041d\u043e\u0432\u0438\u0439 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442", +"Ok": "\u0413\u0430\u0440\u0430\u0437\u0434", +"Cancel": "\u0421\u043a\u0430\u0441\u0443\u0432\u0430\u0442\u0438", +"Visual aids": "\u041d\u0430\u043e\u0447\u043d\u0456 \u043f\u0440\u0438\u043b\u0430\u0434\u0434\u044f", +"Bold": "\u0416\u0438\u0440\u043d\u0438\u0439", +"Italic": "\u041a\u0443\u0440\u0441\u0438\u0432", +"Underline": "\u041f\u0456\u0434\u043a\u0440\u0435\u0441\u043b\u0435\u043d\u0438\u0439", +"Strikethrough": "\u0417\u0430\u043a\u0440\u0435\u0441\u043b\u0435\u043d\u0438\u0439", +"Superscript": "\u0412\u0435\u0440\u0445\u043d\u0456\u0439 \u0456\u043d\u0434\u0435\u043a\u0441", +"Subscript": "\u041d\u0438\u0436\u043d\u0456\u0439 \u0456\u043d\u0434\u0435\u043a\u0441", +"Clear formatting": "\u041e\u0447\u0438\u0441\u0442\u0438\u0442\u0438 \u0444\u043e\u0440\u043c\u0430\u0442\u0443\u0432\u0430\u043d\u043d\u044f", +"Align left": "\u041f\u043e \u043b\u0456\u0432\u043e\u043c\u0443 \u043a\u0440\u0430\u044e", +"Align center": "\u041f\u043e \u0446\u0435\u043d\u0442\u0440\u0443", +"Align right": "\u041f\u043e \u043f\u0440\u0430\u0432\u043e\u043c\u0443 \u043a\u0440\u0430\u044e", +"Justify": "\u041f\u043e \u0448\u0438\u0440\u0438\u043d\u0456", +"Bullet list": "\u041d\u0435\u043d\u0443\u043c\u0435\u0440\u043e\u0432\u0430\u043d\u0438\u0439 \u0441\u043f\u0438\u0441\u043e\u043a", +"Numbered list": "\u041d\u0443\u043c\u0435\u0440\u043e\u0432\u0430\u043d\u0438\u0439 \u0441\u043f\u0438\u0441\u043e\u043a", +"Decrease indent": "\u0417\u043c\u0435\u043d\u0448\u0438\u0442\u0438\u0442\u0438 \u0432\u0456\u0434\u0441\u0442\u0443\u043f", +"Increase indent": "\u0417\u0431\u0456\u043b\u044c\u0448\u0438\u0442\u0438 \u0432\u0456\u0434\u0441\u0442\u0443\u043f", +"Close": "\u0417\u0430\u043a\u0440\u0438\u0442\u0438", +"Formats": "\u0424\u043e\u0440\u043c\u0430\u0442\u0438", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "\u0412\u0430\u0448 \u0431\u0440\u0430\u0443\u0437\u0435\u0440 \u043d\u0435 \u043f\u0456\u0434\u0442\u0440\u0438\u043c\u0443\u0454 \u043f\u0440\u044f\u043c\u0438\u0439 \u0434\u043e\u0441\u0442\u0443\u043f \u0434\u043e \u0431\u0443\u0444\u0435\u0440\u0443 \u043e\u0431\u043c\u0456\u043d\u0443. \u0411\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0439\u0442\u0435 \u0441\u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044f \u043a\u043b\u0430\u0432\u0456\u0448 Ctrl+C\/V\/X.", +"Headers": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0438", +"Header 1": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 1", +"Header 2": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 2", +"Header 3": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 3", +"Header 4": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 4", +"Header 5": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 5", +"Header 6": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 6", +"Headings": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a", +"Heading 1": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 1", +"Heading 2": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 2", +"Heading 3": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 3", +"Heading 4": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 4", +"Heading 5": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 5", +"Heading 6": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 6", +"Preformatted": "\u041f\u043e\u043f\u0435\u0440\u0435\u0434\u043d\u044c\u043e \u0432\u0456\u0434\u0444\u043e\u0440\u043c\u0430\u0442\u043e\u0432\u0430\u043d\u0438\u0439", +"Div": "\u0411\u043b\u043e\u043a", +"Pre": "\u041f\u043e\u043f\u0435\u0440\u0435\u0434\u043d\u0454 \u0444\u043e\u0440\u043c\u0430\u0442\u0443\u0432\u0430\u043d\u043d\u044f", +"Code": "\u041a\u043e\u0434", +"Paragraph": "\u041f\u0430\u0440\u0430\u0433\u0440\u0430\u0444", +"Blockquote": "\u0426\u0438\u0442\u0430\u0442\u0430", +"Inline": "\u0412\u0431\u0443\u0434\u043e\u0432\u0430\u043d\u0456", +"Blocks": "\u0411\u043b\u043e\u043a\u0438", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "\u0412\u0441\u0442\u0430\u0432\u043a\u0430 \u0437\u0434\u0456\u0439\u0441\u043d\u044e\u0454\u0442\u044c\u0441\u044f \u0443 \u0432\u0438\u0433\u043b\u044f\u0434\u0456 \u043f\u0440\u043e\u0441\u0442\u043e\u0433\u043e \u0442\u0435\u043a\u0441\u0442\u0443, \u043f\u043e\u043a\u0438 \u043d\u0435 \u0432\u0456\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u0438 \u0434\u0430\u043d\u0443 \u043e\u043f\u0446\u0456\u044e.", +"Font Family": "\u0422\u0438\u043f \u0448\u0440\u0438\u0444\u0442\u0443", +"Font Sizes": "\u0420\u043e\u0437\u043c\u0456\u0440 \u0448\u0440\u0438\u0444\u0442\u0443", +"Class": "\u041a\u043b\u0430\u0441", +"Browse for an image": "\u0412\u0438\u0431\u0456\u0440 \u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u043d\u044f", +"OR": "\u0410\u0411\u041e", +"Drop an image here": "\u041f\u0435\u0440\u0435\u043c\u0456\u0441\u0442\u0456\u0442\u044c \u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u043d\u044f \u0441\u044e\u0434\u0438", +"Upload": "\u0417\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0438\u0442\u0438", +"Block": "\u0411\u043b\u043e\u043a", +"Align": "\u0412\u0438\u0440\u0456\u0432\u043d\u044e\u0432\u0430\u043d\u043d\u044f", +"Default": "\u0421\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u0438\u0439", +"Circle": "\u041e\u043a\u0440\u0443\u0436\u043d\u043e\u0441\u0442\u0456", +"Disc": "\u041a\u0440\u0443\u0433\u0438", +"Square": "\u041a\u0432\u0430\u0434\u0440\u0430\u0442\u0438", +"Lower Alpha": "\u041c\u0430\u043b\u0456 \u043b\u0430\u0442\u0438\u043d\u0441\u044c\u043a\u0456 \u0431\u0443\u043a\u0432\u0438", +"Lower Greek": "\u041c\u0430\u043b\u0456 \u0433\u0440\u0435\u0446\u044c\u043a\u0456 \u0431\u0443\u043a\u0432\u0438", +"Lower Roman": "\u041c\u0430\u043b\u0456 \u0440\u0438\u043c\u0441\u044c\u043a\u0456 \u0446\u0438\u0444\u0440\u0438", +"Upper Alpha": "\u0412\u0435\u043b\u0438\u043a\u0456 \u043b\u0430\u0442\u0438\u043d\u0441\u044c\u043a\u0456 \u0431\u0443\u043a\u0432\u0438", +"Upper Roman": "\u0420\u0438\u043c\u0441\u044c\u043a\u0456 \u0446\u0438\u0444\u0440\u0438", +"Anchor": "\u042f\u043a\u0456\u0440", +"Name": "\u041d\u0430\u0437\u0432\u0430", +"Id": "\u041a\u043e\u0434", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "\u041a\u043e\u0434 \u043c\u0430\u0454 \u043f\u043e\u0447\u0438\u043d\u0430\u0442\u0438\u0441\u044f \u0437 \u043b\u0456\u0442\u0435\u0440\u0438 \u0456 \u043c\u043e\u0436\u0435 \u043c\u0456\u0441\u0442\u0438\u0442\u0438 \u043b\u0438\u0448\u0435 \u0441\u0438\u043c\u0432\u043e\u043b\u0438 \u043b\u0456\u0442\u0435\u0440, \u0446\u0438\u0444\u0440, \u0434\u0435\u0444\u0456\u0441\u0443, \u043a\u0440\u0430\u043f\u043a\u0438, \u043a\u043e\u043c\u0438 \u0430\u0431\u043e \u043d\u0438\u0436\u043d\u044c\u043e\u0433\u043e \u043f\u0456\u0434\u043a\u0440\u0435\u0441\u043b\u0435\u043d\u043d\u044f.", +"You have unsaved changes are you sure you want to navigate away?": "\u0423 \u0412\u0430\u0441 \u0454 \u043d\u0435\u0437\u0431\u0435\u0440\u0435\u0436\u0435\u043d\u0456 \u0437\u043c\u0456\u043d\u0438. \u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043f\u0456\u0442\u0438?", +"Restore last draft": "\u0412\u0456\u0434\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044f \u043e\u0441\u0442\u0430\u043d\u043d\u044c\u043e\u0433\u043e \u043f\u0440\u043e\u0435\u043a\u0442\u0443", +"Special character": "\u0421\u043f\u0435\u0446\u0456\u0430\u043b\u044c\u043d\u0456 \u0441\u0438\u043c\u0432\u043e\u043b\u0438", +"Source code": "\u0412\u0438\u0445\u0456\u0434\u043d\u0438\u0439 \u043a\u043e\u0434", +"Insert\/Edit code sample": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u0438\/\u0437\u043c\u0456\u043d\u0438\u0442\u0438 \u043f\u0440\u0438\u043a\u043b\u0430\u0434 \u043a\u043e\u0434\u0443", +"Language": "\u041c\u043e\u0432\u0430", +"Code sample": "\u041f\u0440\u0438\u043a\u043b\u0430\u0434 \u043a\u043e\u0434\u0443", +"Color": "\u043a\u043e\u043b\u0456\u0440", +"R": "\u0427", +"G": "\u0417", +"B": "\u0411", +"Left to right": "\u0417\u043b\u0456\u0432\u0430 \u043d\u0430\u043f\u0440\u0430\u0432\u043e", +"Right to left": "\u0421\u043f\u0440\u0430\u0432\u0430 \u043d\u0430\u043b\u0456\u0432\u043e", +"Emoticons": "\u0415\u043c\u043e\u0446\u0456\u0457", +"Document properties": "\u0412\u043b\u0430\u0441\u0442\u0438\u0432\u043e\u0441\u0442\u0456 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430", +"Title": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a", +"Keywords": "\u041a\u043b\u044e\u0447\u043e\u0432\u0456 \u0441\u043b\u043e\u0432\u0430", +"Description": "\u041e\u043f\u0438\u0441", +"Robots": "\u0420\u043e\u0431\u043e\u0442\u0438", +"Author": "\u0410\u0432\u0442\u043e\u0440", +"Encoding": "\u041a\u043e\u0434\u0443\u0432\u0430\u043d\u043d\u044f", +"Fullscreen": "\u041f\u043e\u0432\u043d\u043e\u0435\u043a\u0440\u0430\u043d\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c", +"Action": "\u0414\u0456\u044f", +"Shortcut": "\u042f\u0440\u043b\u0438\u043a", +"Help": "\u0414\u043e\u043f\u043e\u043c\u043e\u0433\u0430", +"Address": "\u0410\u0434\u0440\u0435\u0441\u0430", +"Focus to menubar": "\u0424\u043e\u043a\u0443\u0441 \u043d\u0430 \u043c\u0435\u043d\u044e", +"Focus to toolbar": "\u0424\u043e\u043a\u0443\u0441 \u043d\u0430 \u0456\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u0430\u0445", +"Focus to element path": "\u0424\u043e\u043a\u0443\u0441 \u043d\u0430 \u0448\u043b\u044f\u0445\u0443", +"Focus to contextual toolbar": "\u0424\u043e\u043a\u0443\u0441 \u043d\u0430 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0456", +"Insert link (if link plugin activated)": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u0438 \u043f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f (\u044f\u043a\u0449\u043e \u043f\u043b\u0430\u0433\u0456\u043d \u043f\u043e\u0441\u0438\u043b\u0430\u043d\u044c \u0430\u043a\u0442\u0438\u0432\u043e\u0432\u0430\u043d\u0438\u0439)", +"Save (if save plugin activated)": "\u0417\u0431\u0435\u0440\u0435\u0433\u0442\u0438 (\u044f\u043a\u0449\u043e \u043f\u043b\u0430\u0433\u0456\u043d \u0437\u0431\u0435\u0440\u0435\u0436\u0435\u043d\u043d\u044f \u0430\u043a\u0442\u0438\u0432\u043e\u0432\u0430\u043d\u043e)", +"Find (if searchreplace plugin activated)": "\u0417\u043d\u0430\u0439\u0442\u0438 (\u044f\u043a\u0449\u043e \u043f\u043b\u0430\u0433\u0456\u043d \u043f\u043e\u0448\u0443\u043a\u0443 \u0430\u043a\u0442\u0438\u0432\u043e\u0432\u0430\u043d\u043e)", +"Plugins installed ({0}):": "\u0412\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u0456 \u043f\u043b\u0430\u0433\u0456\u043d\u0438 ({0}):", +"Premium plugins:": "\u041f\u0440\u0435\u043c\u0456\u0443\u043c \u043f\u043b\u0430\u0433\u0456\u043d\u0438:", +"Learn more...": "\u0414\u043e\u0434\u0430\u0442\u043a\u043e\u0432\u043e...", +"You are using {0}": "\u0423 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u0430\u043d\u043d\u0456 {0}", +"Plugins": "\u041f\u043b\u0430\u0433\u0456\u043d\u0438", +"Handy Shortcuts": "\u041a\u043b\u0430\u0432\u0456\u0430\u0442\u0443\u0440\u043d\u0456 \u0441\u043a\u043e\u0440\u043e\u0447\u0435\u043d\u043d\u044f", +"Horizontal line": "\u0413\u043e\u0440\u0438\u0437\u043e\u043d\u0442\u0430\u043b\u044c\u043d\u0430 \u043b\u0456\u043d\u0456\u044f", +"Insert\/edit image": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u0438\/\u0437\u043c\u0456\u043d\u0438\u0442\u0438 \u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u043d\u044f", +"Image description": "\u041e\u043f\u0438\u0441 \u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u043d\u044f", +"Source": "\u0414\u0436\u0435\u0440\u0435\u043b\u043e", +"Dimensions": "\u0420\u043e\u0437\u043c\u0456\u0440", +"Constrain proportions": "\u0417\u0431\u0435\u0440\u0456\u0433\u0430\u0442\u0438 \u043f\u0440\u043e\u043f\u043e\u0440\u0446\u0456\u0457", +"General": "\u0417\u0430\u0433\u0430\u043b\u044c\u043d\u0456", +"Advanced": "\u0420\u043e\u0437\u0448\u0438\u0440\u0435\u043d\u0456", +"Style": "\u0421\u0442\u0438\u043b\u044c", +"Vertical space": "\u0412\u0435\u0440\u0442\u0438\u043a\u0430\u043b\u044c\u043d\u0438\u0439 \u0456\u043d\u0442\u0435\u0440\u0432\u0430\u043b", +"Horizontal space": "\u0413\u043e\u0440\u0438\u0437\u043e\u043d\u0442\u0430\u043b\u044c\u043d\u0438\u0439 \u0456\u043d\u0442\u0435\u0440\u0432\u0430\u043b", +"Border": "\u041c\u0435\u0436\u0430", +"Insert image": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u0438 \u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u043d\u044f", +"Image": "\u0417\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u043d\u044f", +"Image list": "\u041f\u0435\u0440\u0435\u043b\u0456\u043a \u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u044c", +"Rotate counterclockwise": "\u041f\u043e\u0432\u0435\u0440\u043d\u0443\u0442\u0438 \u043f\u0440\u043e\u0442\u0438 \u0433\u043e\u0434\u0438\u043d\u043d\u0438\u043a\u043e\u0432\u043e\u0457 \u0441\u0442\u0440\u0456\u043b\u043a\u0438", +"Rotate clockwise": "\u041f\u043e\u0432\u0435\u0440\u043d\u0443\u0442\u0438 \u0437\u0430 \u0433\u043e\u0434\u0438\u043d\u043d\u0438\u043a\u043e\u0432\u043e\u044e \u0441\u0442\u0440\u0456\u043b\u043a\u043e\u044e", +"Flip vertically": "\u0412\u0456\u0434\u043e\u0431\u0440\u0430\u0437\u0438\u0442\u0438 \u043f\u043e \u0432\u0435\u0440\u0442\u0438\u043a\u0430\u043b\u0456", +"Flip horizontally": "\u0412\u0456\u0434\u043e\u0431\u0440\u0430\u0437\u0438\u0442\u0438 \u043f\u043e \u0433\u043e\u0440\u0438\u0437\u043e\u043d\u0442\u0430\u043b\u0456", +"Edit image": "\u0420\u0435\u0434\u0430\u0433\u0443\u0432\u0430\u0442\u0438 \u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u043d\u044f", +"Image options": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u043d\u044f", +"Zoom in": "\u041d\u0430\u0431\u043b\u0438\u0437\u0438\u0442\u0438", +"Zoom out": "\u0412\u0456\u0434\u0434\u0430\u043b\u0438\u0442\u0438", +"Crop": "\u041e\u0431\u0440\u0456\u0437\u0430\u0442\u0438", +"Resize": "\u0417\u043c\u0456\u043d\u0438\u0442\u0438 \u0440\u043e\u0437\u043c\u0456\u0440", +"Orientation": "\u041e\u0440\u0456\u0454\u043d\u0442\u0430\u0446\u0456\u044f", +"Brightness": "\u042f\u0441\u043a\u0440\u0430\u0432\u0456\u0441\u0442\u044c", +"Sharpen": "\u0427\u0456\u0442\u043a\u0456\u0441\u0442\u044c", +"Contrast": "\u041a\u043e\u043d\u0442\u0440\u0430\u0441\u0442", +"Color levels": "\u0420\u0456\u0432\u043d\u0456 \u043a\u043e\u043b\u044c\u043e\u0440\u0456\u0432", +"Gamma": "\u0413\u0430\u043c\u043c\u0430", +"Invert": "\u0406\u043d\u0432\u0435\u0440\u0441\u0456\u044f", +"Apply": "\u0417\u0430\u0441\u0442\u043e\u0441\u0443\u0432\u0430\u0442\u0438", +"Back": "\u041f\u043e\u0432\u0435\u0440\u043d\u0443\u0442\u0438\u0441\u044f", +"Insert date\/time": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u0438 \u0434\u0430\u0442\u0443\/\u0447\u0430\u0441", +"Date\/time": "\u0414\u0430\u0442\u0430\/\u0447\u0430\u0441", +"Insert link": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u0438 \u043f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f", +"Insert\/edit link": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u0438\/\u0440\u0435\u0434\u0430\u0433\u0443\u0432\u0430\u0442\u0438 \u043f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f", +"Text to display": "\u0422\u0435\u043a\u0441\u0442 \u0434\u043b\u044f \u0432\u0456\u0434\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u043d\u044f", +"Url": "\u0410\u0434\u0440\u0435\u0441\u0430 \u043f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f", +"Target": "\u0412\u0456\u0434\u043a\u0440\u0438\u0432\u0430\u0442\u0438 \u043f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f", +"None": "\u041d\u0456", +"New window": "\u0423 \u043d\u043e\u0432\u043e\u043c\u0443 \u0432\u0456\u043a\u043d\u0456", +"Remove link": "\u0412\u0438\u0434\u0430\u043b\u0438\u0442\u0438 \u043f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f", +"Anchors": "\u042f\u043a\u043e\u0440\u0456", +"Link": "\u041f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f", +"Paste or type a link": "\u041d\u0430\u043f\u0438\u0441\u0430\u0442\u0438 \u0430\u0431\u043e \u0432\u0441\u0442\u0430\u0432\u0438\u0442\u0438 \u043f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "\u0421\u0445\u043e\u0436\u0435, \u0449\u043e \u0432\u0438 \u0432\u0432\u0435\u043b\u0438 \u0430\u0434\u0440\u0435\u0441\u0443 \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0457 \u043f\u043e\u0448\u0442\u0438. \u0412\u0438 \u0431\u0430\u0436\u0430\u0454\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u0438 mailto: \u043f\u0440\u0435\u0444\u0456\u043a\u0441?", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "\u0421\u0445\u043e\u0436\u0435, \u0449\u043e \u0432\u0438 \u0432\u0432\u0435\u043b\u0438 \u0437\u043e\u0432\u043d\u0456\u0448\u043d\u0454 \u043f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f. \u0412\u0438 \u0431\u0430\u0436\u0430\u0454\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u0438 http:\/\/ \u043f\u0440\u0435\u0444\u0456\u043a\u0441?", +"Link list": "\u041f\u0435\u0440\u0435\u043b\u0456\u043a \u043f\u043e\u0441\u0438\u043b\u0430\u043d\u044c", +"Insert video": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u0438 \u0432\u0456\u0434\u0435\u043e", +"Insert\/edit video": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u0438\/\u0440\u0435\u0434\u0430\u0433\u0443\u0432\u0430\u0442\u0438 \u0432\u0456\u0434\u0435\u043e", +"Insert\/edit media": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u0438\/\u0440\u0435\u0434\u0430\u0433\u0443\u0432\u0430\u0442\u0438 \u0430\u0443\u0434\u0456\u043e", +"Alternative source": "\u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u043d\u0435 \u0434\u0436\u0435\u0440\u0435\u043b\u043e", +"Poster": "\u0417\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u043d\u044f", +"Paste your embed code below:": "\u0412\u0441\u0442\u0430\u0432\u0442\u0435 \u0432\u0430\u0448 \u043a\u043e\u0434 \u043d\u0438\u0436\u0447\u0435:", +"Embed": "\u041a\u043e\u0434 \u0434\u043b\u044f \u0432\u0441\u0442\u0430\u0432\u043a\u0438", +"Media": "\u041c\u0435\u0434\u0456\u0430\u0434\u0430\u043d\u0456", +"Nonbreaking space": "\u041d\u0435\u0440\u043e\u0437\u0440\u0438\u0432\u043d\u0438\u0439 \u043f\u0440\u043e\u0431\u0456\u043b", +"Page break": "\u0420\u043e\u0437\u0440\u0438\u0432 \u0441\u0442\u043e\u0440\u0456\u043d\u043a\u0438", +"Paste as text": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u0438 \u044f\u043a \u0442\u0435\u043a\u0441\u0442", +"Preview": "\u041f\u043e\u043f\u0435\u0440\u0435\u0434\u043d\u0456\u0439 \u043f\u0435\u0440\u0435\u0433\u043b\u044f\u0434", +"Print": "\u0414\u0440\u0443\u043a\u0443\u0432\u0430\u0442\u0438", +"Save": "\u0417\u0431\u0435\u0440\u0435\u0433\u0442\u0438", +"Find": "\u0417\u043d\u0430\u0439\u0442\u0438", +"Replace with": "\u0417\u0430\u043c\u0456\u043d\u0438\u0442\u0438 \u043d\u0430", +"Replace": "\u0417\u0430\u043c\u0456\u043d\u0438\u0442\u0438", +"Replace all": "\u0417\u0430\u043c\u0456\u043d\u0438\u0442\u0438 \u0432\u0441\u0435", +"Prev": "\u0412\u0433\u043e\u0440\u0443", +"Next": "\u0412\u043d\u0438\u0437", +"Find and replace": "\u041f\u043e\u0448\u0443\u043a \u0456 \u0437\u0430\u043c\u0456\u043d\u0430", +"Could not find the specified string.": "\u0412\u043a\u0430\u0437\u0430\u043d\u0438\u0439 \u0440\u044f\u0434\u043e\u043a \u043d\u0435 \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u043e", +"Match case": "\u0412\u0440\u0430\u0445\u043e\u0432\u0443\u0432\u0430\u0442\u0438 \u0440\u0435\u0433\u0456\u0441\u0442\u0440", +"Whole words": "\u0426\u0456\u043b\u0456 \u0441\u043b\u043e\u0432\u0430", +"Spellcheck": "\u041f\u0435\u0440\u0435\u0432\u0456\u0440\u043a\u0430 \u043e\u0440\u0444\u043e\u0433\u0440\u0430\u0444\u0456\u0457", +"Ignore": "\u0406\u0433\u043d\u043e\u0440\u0443\u0432\u0430\u0442\u0438", +"Ignore all": "\u0406\u0433\u043d\u043e\u0440\u0443\u0432\u0430\u0442\u0438 \u0432\u0441\u0435", +"Finish": "\u0417\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u0438", +"Add to Dictionary": "\u0414\u043e\u0434\u0430\u0442\u0438 \u0434\u043e \u0421\u043b\u043e\u0432\u043d\u0438\u043a\u0430", +"Insert table": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u0438 \u0442\u0430\u0431\u043b\u0438\u0446\u044e", +"Table properties": "\u0412\u043b\u0430\u0441\u0442\u0438\u0432\u043e\u0441\u0442\u0456 \u0442\u0430\u0431\u043b\u0438\u0446\u0456", +"Delete table": "\u0412\u0438\u0434\u0430\u043b\u0438\u0442\u0438 \u0442\u0430\u0431\u043b\u0438\u0446\u044e", +"Cell": "\u041a\u043e\u043c\u0456\u0440\u043a\u0430", +"Row": "\u0420\u044f\u0434\u043e\u043a", +"Column": "\u0421\u0442\u043e\u0432\u043f\u0435\u0446\u044c", +"Cell properties": "\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438 \u043a\u043e\u043c\u0456\u0440\u043a\u0438", +"Merge cells": "\u041e\u0431'\u0454\u0434\u043d\u0430\u0442\u0438 \u043a\u043e\u043c\u0456\u0440\u043a\u0438", +"Split cell": "\u0420\u043e\u0437\u0431\u0438\u0442\u0438 \u043a\u043e\u043c\u0456\u0440\u043a\u0443", +"Insert row before": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u0438 \u043f\u043e\u0440\u043e\u0436\u043d\u0456\u0439 \u0440\u044f\u0434\u043e\u043a \u0437\u0432\u0435\u0440\u0445\u0443", +"Insert row after": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u0438 \u043f\u043e\u0440\u043e\u0436\u043d\u0456\u0439 \u0440\u044f\u0434\u043e\u043a \u0437\u043d\u0438\u0437\u0443", +"Delete row": "\u0412\u0438\u0434\u0430\u043b\u0438\u0442\u0438 \u0440\u044f\u0434\u043e\u043a", +"Row properties": "\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438 \u0440\u044f\u0434\u043a\u0430", +"Cut row": "\u0412\u0438\u0440\u0456\u0437\u0430\u0442\u0438 \u0440\u044f\u0434\u043e\u043a", +"Copy row": "\u041a\u043e\u043f\u0456\u044e\u0432\u0430\u0442\u0438 \u0440\u044f\u0434\u043e\u043a", +"Paste row before": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u0438 \u0440\u044f\u0434\u043e\u043a \u0437\u0432\u0435\u0440\u0445\u0443", +"Paste row after": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u0438 \u0440\u044f\u0434\u043e\u043a \u0437\u043d\u0438\u0437\u0443", +"Insert column before": "\u0414\u043e\u0434\u0430\u0442\u0438 \u0441\u0442\u043e\u0432\u043f\u0435\u0446\u044c \u043b\u0456\u0432\u043e\u0440\u0443\u0447", +"Insert column after": "\u0414\u043e\u0434\u0430\u0442\u0438 \u0441\u0442\u043e\u0432\u043f\u0435\u0446\u044c \u043f\u0440\u0430\u0432\u043e\u0440\u0443\u0447", +"Delete column": "\u0412\u0438\u0434\u0430\u043b\u0438\u0442\u0438 \u0441\u0442\u043e\u0432\u043f\u0435\u0446\u044c", +"Cols": "\u0421\u0442\u043e\u0432\u043f\u0446\u0456", +"Rows": "\u0420\u044f\u0434\u043a\u0438", +"Width": "\u0428\u0438\u0440\u0438\u043d\u0430", +"Height": "\u0412\u0438\u0441\u043e\u0442\u0430", +"Cell spacing": "\u0412\u0456\u0434\u0441\u0442\u0430\u043d\u044c \u043c\u0456\u0436 \u043a\u043e\u043c\u0456\u0440\u043a\u0430\u043c\u0438", +"Cell padding": "\u041f\u043e\u043b\u044f \u043a\u043e\u043c\u0456\u0440\u043e\u043a", +"Caption": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a", +"Left": "\u041f\u043e \u043b\u0456\u0432\u043e\u043c\u0443 \u043a\u0440\u0430\u044e", +"Center": "\u041f\u043e \u0446\u0435\u043d\u0442\u0440\u0443", +"Right": "\u041f\u043e \u043f\u0440\u0430\u0432\u043e\u043c\u0443 \u043a\u0440\u0430\u044e", +"Cell type": "\u0422\u0438\u043f \u043a\u043e\u043c\u0456\u0440\u043a\u0438", +"Scope": "\u0421\u0444\u0435\u0440\u0430", +"Alignment": "\u0412\u0438\u0440\u0456\u0432\u043d\u044e\u0432\u0430\u043d\u043d\u044f", +"H Align": "\u0413\u043e\u0440\u0438\u0437\u043e\u043d\u0442\u0430\u043b\u044c\u043d\u0435 \u0432\u0438\u0440\u0456\u0432\u043d\u044e\u0432\u0430\u043d\u043d\u044f", +"V Align": "\u0412\u0435\u0440\u0442\u0438\u043a\u0430\u043b\u044c\u043d\u0435 \u0432\u0438\u0440\u0456\u0432\u043d\u044e\u0432\u0430\u043d\u043d\u044f", +"Top": "\u041f\u043e \u0432\u0435\u0440\u0445\u043d\u044c\u043e\u043c\u0443 \u043a\u0440\u0430\u044e", +"Middle": "\u041f\u043e \u0446\u0435\u043d\u0442\u0440\u0443", +"Bottom": "\u041f\u043e \u043d\u0438\u0436\u043d\u044c\u043e\u043c\u0443 \u043a\u0440\u0430\u044e", +"Header cell": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a", +"Row group": "\u0413\u0440\u0443\u043f\u0430 \u0440\u044f\u0434\u043a\u0456\u0432", +"Column group": "\u0413\u0440\u0443\u043f\u0430 \u0441\u0442\u043e\u0432\u043f\u0446\u0456\u0432", +"Row type": "\u0422\u0438\u043f \u0440\u044f\u0434\u043a\u0430", +"Header": "\u0412\u0435\u0440\u0445\u043d\u0456\u0439 \u043a\u043e\u043b\u043e\u043d\u0442\u0438\u0442\u0443\u043b", +"Body": "\u0422\u0456\u043b\u043e", +"Footer": "\u041d\u0438\u0436\u043d\u0456\u0439 \u043a\u043e\u043b\u043e\u043d\u0442\u0438\u0442\u0443\u043b", +"Border color": "\u043a\u043e\u043b\u0456\u0440 \u0440\u0430\u043c\u043a\u0438", +"Insert template": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u0438 \u0448\u0430\u0431\u043b\u043e\u043d", +"Templates": "\u0428\u0430\u0431\u043b\u043e\u043d\u0438", +"Template": "\u0428\u0430\u0431\u043b\u043e\u043d", +"Text color": "\u041a\u043e\u043b\u0456\u0440 \u0442\u0435\u043a\u0441\u0442\u0443", +"Background color": "\u041a\u043e\u043b\u0456\u0440 \u0444\u043e\u043d\u0443", +"Custom...": "\u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0446\u044c\u043a\u0438\u0439", +"Custom color": "\u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0446\u044c\u043a\u0438\u0439 \u043a\u043e\u043b\u0456\u0440", +"No color": "\u0431\u0435\u0437 \u043a\u043e\u043b\u044c\u043e\u0440\u0443", +"Table of Contents": "\u0417\u043c\u0456\u0441\u0442", +"Show blocks": "\u041f\u043e\u043a\u0430\u0437\u0443\u0432\u0430\u0442\u0438 \u0431\u043b\u043e\u043a\u0438", +"Show invisible characters": "\u041f\u043e\u043a\u0430\u0437\u0443\u0432\u0430\u0442\u0438 \u043d\u0435\u0432\u0438\u0434\u0438\u043c\u0456 \u0441\u0438\u043c\u0432\u043e\u043b\u0438", +"Words: {0}": "\u041a\u0456\u043b\u044c\u043a\u0456\u0441\u0442\u044c \u0441\u043b\u0456\u0432: {0}", +"{0} words": "{0} \u0441\u043b\u0456\u0432", +"File": "\u0424\u0430\u0439\u043b", +"Edit": "\u0417\u043c\u0456\u043d\u0438\u0442\u0438", +"Insert": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u0438", +"View": "\u0412\u0438\u0433\u043b\u044f\u0434", +"Format": "\u0424\u043e\u0440\u043c\u0430\u0442", +"Table": "\u0422\u0430\u0431\u043b\u0438\u0446\u044f", +"Tools": "\u0406\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u0438", +"Powered by {0}": "\u041f\u0440\u0430\u0446\u044e\u0454 \u043d\u0430 {0}", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "\u0422\u0435\u043a\u0441\u0442\u043e\u0432\u0435 \u043f\u043e\u043b\u0435. \u041d\u0430\u0442\u0438\u0441\u043d\u0456\u0442\u044c ALT-F9 \u0449\u043e\u0431 \u0432\u0438\u043a\u043b\u0438\u043a\u0430\u0442\u0438 \u043c\u0435\u043d\u044e, ALT-F10 \u043f\u0430\u043d\u0435\u043b\u044c \u0456\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u0456\u0432, ALT-0 \u0434\u043b\u044f \u0432\u0438\u043a\u043b\u0438\u043a\u0443 \u0434\u043e\u043f\u043e\u043c\u043e\u0433\u0438." +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/uk_UA.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/uk_UA.js new file mode 100644 index 0000000000..b2d3bae286 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/uk_UA.js @@ -0,0 +1,261 @@ +tinymce.addI18n('uk_UA',{ +"Redo": "\u0412\u0456\u0434\u043d\u043e\u0432\u0438\u0442\u0438", +"Undo": "\u0412\u0456\u0434\u043c\u0456\u043d\u0438\u0442\u0438", +"Cut": "\u0412\u0438\u0440\u0456\u0437\u0430\u0442\u0438", +"Copy": "\u041a\u043e\u043f\u0456\u044e\u0432\u0430\u0442\u0438", +"Paste": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u0438", +"Select all": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0443\u0441\u0435", +"New document": "\u041d\u043e\u0432\u0438\u0439 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442", +"Ok": "Ok", +"Cancel": "\u0412\u0456\u0434\u043c\u0456\u043d\u0438\u0442\u0438", +"Visual aids": "\u0412\u0456\u0437\u0443\u0430\u043b\u044c\u043d\u0456 \u0437\u0430\u0441\u043e\u0431\u0438", +"Bold": "\u0416\u0438\u0440\u043d\u0438\u0439", +"Italic": "\u041a\u0443\u0440\u0441\u0438\u0432", +"Underline": "\u041f\u0456\u0434\u043a\u0440\u0435\u0441\u043b\u0435\u043d\u0438\u0439", +"Strikethrough": "\u041f\u0435\u0440\u0435\u043a\u0440\u0435\u0441\u043b\u0435\u043d\u0438\u0439", +"Superscript": "\u0412\u0435\u0440\u0445\u043d\u0456\u0439 \u0456\u043d\u0434\u0435\u043a\u0441", +"Subscript": "\u0406\u043d\u0434\u0435\u043a\u0441", +"Clear formatting": "\u041e\u0447\u0438\u0441\u0442\u0438\u0442\u0438 \u0444\u043e\u0440\u043c\u0430\u0442\u0443\u0432\u0430\u043d\u043d\u044f", +"Align left": "\u041b\u0456\u0432\u043e\u0440\u0443\u0447", +"Align center": "\u041f\u043e \u0446\u0435\u043d\u0442\u0440\u0443", +"Align right": "\u041f\u0440\u0430\u0432\u043e\u0440\u0443\u0447", +"Justify": "\u0412\u0438\u0440\u0456\u0432\u043d\u044f\u0442\u0438", +"Bullet list": "\u041c\u0430\u0440\u043a\u0456\u0440\u043e\u0432\u0430\u043d\u0438\u0439 \u0441\u043f\u0438\u0441\u043e\u043a", +"Numbered list": "\u041f\u0440\u043e\u043d\u0443\u043c\u0435\u0440\u043e\u0432\u0430\u043d\u0438\u0439 \u0441\u043f\u0438\u0441\u043e\u043a", +"Decrease indent": "\u0417\u043c\u0435\u043d\u0448\u0438\u0442\u0438 \u0432\u0456\u0434\u0441\u0442\u0443\u043f", +"Increase indent": "\u0417\u0431\u0456\u043b\u044c\u0448\u0438\u0442\u0438 \u0432\u0456\u0434\u0441\u0442\u0443\u043f", +"Close": "\u0417\u0430\u043a\u0440\u0438\u0442\u0438", +"Formats": "\u0424\u043e\u0440\u043c\u0430\u0442\u0438", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "\u0412\u0430\u0448 \u0431\u0440\u0430\u0443\u0437\u0435\u0440 \u043d\u0435 \u043f\u0456\u0434\u0442\u0440\u0438\u043c\u0443\u0454 \u043f\u0440\u044f\u043c\u0438\u0439 \u0434\u043e\u0441\u0442\u0443\u043f \u0434\u043e \u0431\u0443\u0444\u0435\u0440\u0430 \u043e\u0431\u043c\u0456\u043d\u0443. \u0417\u0430\u043c\u0456\u0441\u0442\u044c \u0446\u044c\u043e\u0433\u043e \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0439\u0442\u0435 \u043f\u043e\u0454\u0434\u043d\u0430\u043d\u043d\u044f \u043a\u043b\u0430\u0432\u0456\u0448 Ctrl + X\/C\/V.", +"Headers": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0438", +"Header 1": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 1", +"Header 2": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 2", +"Header 3": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 3", +"Header 4": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 4", +"Header 5": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 5", +"Header 6": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 6", +"Headings": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0438", +"Heading 1": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 1", +"Heading 2": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 2", +"Heading 3": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 3", +"Heading 4": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 4", +"Heading 5": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 5", +"Heading 6": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 6", +"Preformatted": "\u041f\u043e\u043f\u0435\u0440\u0435\u0434\u043d\u044c\u043e \u0432\u0456\u0434\u0444\u043e\u0440\u043c\u0430\u0442\u043e\u0432\u0430\u043d\u0438\u0439", +"Div": "Div", +"Pre": "Pre", +"Code": "\u041a\u043e\u0434", +"Paragraph": "\u0410\u0431\u0437\u0430\u0446", +"Blockquote": "\u0426\u0438\u0442\u0430\u0442\u0430", +"Inline": "\u0412\u0431\u0443\u0434\u043e\u0432\u0430\u043d\u0438\u0439", +"Blocks": "\u0411\u043b\u043e\u043a\u0438", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "\u0412\u0441\u0442\u0430\u0432\u043a\u0430 \u0437\u0430\u0440\u0430\u0437 \u0432 \u0440\u0435\u0436\u0438\u043c\u0456 \u0437\u0432\u0438\u0447\u0430\u0439\u043d\u043e\u0433\u043e \u0442\u0435\u043a\u0441\u0442\u0443. \u0417\u043c\u0456\u0441\u0442 \u0431\u0443\u0434\u0435 \u0432\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0439 \u044f\u043a \u043f\u0440\u043e\u0441\u0442\u0438\u0439 \u0442\u0435\u043a\u0441\u0442, \u043f\u043e\u043a\u0438 \u0412\u0438 \u043d\u0435 \u0432\u0438\u043c\u043a\u043d\u0435\u0442\u0435 \u0446\u044e \u043e\u043f\u0446\u0456\u044e.", +"Font Family": "\u0428\u0440\u0438\u0444\u0442", +"Font Sizes": "\u0420\u043e\u0437\u043c\u0456\u0440 \u0448\u0440\u0438\u0444\u0442\u0430", +"Class": "\u041a\u043b\u0430\u0441", +"Browse for an image": "\u0412\u0438\u0431\u0456\u0440 \u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u043d\u044f", +"OR": "\u0410\u0411\u041e", +"Drop an image here": "\u041f\u0435\u0440\u0435\u043c\u0456\u0441\u0442\u0456\u0442\u044c \u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u043d\u044f \u0441\u044e\u0434\u0438", +"Upload": "\u0417\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0438\u0442\u0438", +"Block": "\u0411\u043b\u043e\u043a", +"Align": "\u0412\u0438\u0440\u0456\u0432\u043d\u044e\u0432\u0430\u043d\u043d\u044f", +"Default": "\u0423\u043c\u043e\u0432\u0447\u0430\u043d\u043d\u044f", +"Circle": "\u041a\u043e\u043b\u043e", +"Disc": "\u0414\u0438\u0441\u043a", +"Square": "\u041a\u0432\u0430\u0434\u0440\u0430\u0442", +"Lower Alpha": "\u041d\u0438\u0436\u043d\u0456\u0439 \u0440\u0435\u0433\u0456\u0441\u0442\u0440", +"Lower Greek": "\u041c\u0430\u043b\u0456 \u0433\u0440\u0435\u0446\u044c\u043a\u0456 \u043b\u0456\u0442\u0435\u0440\u0438", +"Lower Roman": "\u0420\u0438\u043c\u0441\u044c\u043a\u0456 \u0446\u0438\u0444\u0440\u0438 \u0443 \u043d\u0438\u0436\u043d\u044c\u043e\u043c\u0443 \u0440\u0435\u0433\u0456\u0441\u0442\u0440\u0456", +"Upper Alpha": "\u0412\u0435\u0440\u0445\u043d\u0456\u0439 \u0440\u0435\u0433\u0456\u0441\u0442\u0440", +"Upper Roman": "\u0420\u0438\u043c\u0441\u044c\u043a\u0456 \u0446\u0438\u0444\u0440\u0438 \u0443 \u0432\u0435\u0440\u0445\u043d\u044c\u043e\u043c\u0443 \u0440\u0435\u0433\u0456\u0441\u0442\u0440\u0456", +"Anchor": "\u041f\u0440\u0438\u0432'\u044f\u0437\u043a\u0430", +"Name": "\u0406\u043c'\u044f", +"Id": "\u041a\u043e\u0434", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "\u041a\u043e\u0434 \u043c\u0430\u0454 \u043f\u043e\u0447\u0438\u043d\u0430\u0442\u0438\u0441\u044f \u0437 \u043b\u0456\u0442\u0435\u0440\u0438 \u0456 \u043c\u043e\u0436\u0435 \u043c\u0456\u0441\u0442\u0438\u0442\u0438 \u043b\u0438\u0448\u0435 \u0441\u0438\u043c\u0432\u043e\u043b\u0438 \u043b\u0456\u0442\u0435\u0440, \u0446\u0438\u0444\u0440, \u0434\u0435\u0444\u0456\u0441\u0443, \u043a\u0440\u0430\u043f\u043a\u0438, \u043a\u043e\u043c\u0438 \u0430\u0431\u043e \u043d\u0438\u0436\u043d\u044c\u043e\u0433\u043e \u043f\u0456\u0434\u043a\u0440\u0435\u0441\u043b\u0435\u043d\u043d\u044f.", +"You have unsaved changes are you sure you want to navigate away?": "\u0423 \u0432\u0430\u0441 \u0454 \u043d\u0435\u0437\u0431\u0435\u0440\u0435\u0436\u0435\u043d\u0456 \u0437\u043c\u0456\u043d\u0438. \u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043f\u0456\u0442\u0438 ?", +"Restore last draft": "\u0412\u0456\u0434\u043d\u043e\u0432\u0438\u0442\u0438 \u043e\u0441\u0442\u0430\u043d\u043d\u0456\u0439 \u043f\u0440\u043e\u0435\u043a\u0442", +"Special character": "\u0421\u043f\u0435\u0446\u0456\u0430\u043b\u044c\u043d\u0438\u0439 \u0441\u0438\u043c\u0432\u043e\u043b", +"Source code": "\u0414\u0436\u0435\u0440\u0435\u043b\u043e", +"Insert\/Edit code sample": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u0438\/\u041d\u0430\u043f\u0438\u0441\u0430\u0442\u0438 \u043f\u0440\u0438\u043a\u043b\u0430\u0434 \u043a\u043e\u0434\u0443", +"Language": "\u041c\u043e\u0432\u0430", +"Code sample": "\u041f\u0440\u0438\u043a\u043b\u0430\u0434 \u043a\u043e\u0434\u0443", +"Color": "\u041a\u043e\u043b\u0456\u0440", +"R": "R", +"G": "G", +"B": "B", +"Left to right": "\u0417\u043b\u0456\u0432\u0430 \u043d\u0430\u043f\u0440\u0430\u0432\u043e", +"Right to left": "\u0421\u043f\u0440\u0430\u0432\u0430 \u043d\u0430\u043b\u0456\u0432\u043e", +"Emoticons": "\u0421\u043c\u0430\u0439\u043b\u0438", +"Document properties": "\u0412\u043b\u0430\u0441\u0442\u0438\u0432\u043e\u0441\u0442\u0456 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0443", +"Title": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a", +"Keywords": "\u041a\u043b\u044e\u0447\u043e\u0432\u0456 \u0441\u043b\u043e\u0432\u0430", +"Description": "\u041e\u043f\u0438\u0441", +"Robots": "\u0420\u043e\u0431\u043e\u0442\u0438", +"Author": "\u0410\u0432\u0442\u043e\u0440", +"Encoding": "\u041a\u043e\u0434\u0443\u0432\u0430\u043d\u043d\u044f", +"Fullscreen": "\u041d\u0430 \u0432\u0435\u0441\u044c \u0435\u043a\u0440\u0430\u043d", +"Action": "\u0414\u0456\u044f", +"Shortcut": "\u042f\u0440\u043b\u0438\u043a", +"Help": "\u0414\u043e\u043f\u043e\u043c\u043e\u0433\u0430", +"Address": "\u0410\u0434\u0440\u0435\u0441\u0430", +"Focus to menubar": "\u0424\u043e\u043a\u0443\u0441 \u043d\u0430 \u043c\u0435\u043d\u044e", +"Focus to toolbar": "\u0424\u043e\u043a\u0443\u0441 \u043d\u0430 \u0456\u043d\u0441\u0442\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u0430\u0445", +"Focus to element path": "\u0424\u043e\u043a\u0443\u0441 \u043d\u0430 \u0448\u043b\u044f\u0445\u0443", +"Focus to contextual toolbar": "\u0424\u043e\u043a\u0443\u0441 \u043d\u0430 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442", +"Insert link (if link plugin activated)": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u0438 \u043f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f (\u044f\u043a\u0449\u043e \u0434\u043e\u0437\u0432\u043e\u043b\u0435\u043d\u043e)", +"Save (if save plugin activated)": "\u0417\u0431\u0435\u0440\u0435\u0433\u0442\u0438 (\u044f\u043a\u0449\u043e \u0434\u043e\u0437\u0432\u043e\u043b\u0435\u043d\u043e)", +"Find (if searchreplace plugin activated)": "\u0417\u043d\u0430\u0439\u0442\u0438 (\u044f\u043a\u0449\u043e \u0434\u043e\u0437\u0432\u043e\u043b\u0435\u043d\u043e)", +"Plugins installed ({0}):": "\u041d\u0430\u044f\u0432\u043d\u0456 \u0434\u043e\u0434\u0430\u0442\u043a\u0438 ({0}):", +"Premium plugins:": "\u041f\u0440\u0435\u043c\u0456\u0430\u043b\u044c\u043d\u0456 \u0434\u043e\u0434\u0430\u0442\u043a\u0438:", +"Learn more...": "\u0414\u043e\u0434\u0430\u0442\u043a\u043e\u0432\u043e...", +"You are using {0}": "\u0423 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u0430\u043d\u043d\u0456 {0}", +"Plugins": "\u041f\u043b\u0430\u0433\u0456\u043d\u0438", +"Handy Shortcuts": "\u041a\u043e\u0440\u0438\u0441\u043d\u0456 \u044f\u0440\u043b\u0438\u043a\u0438", +"Horizontal line": "\u0413\u043e\u0440\u0438\u0437\u043e\u043d\u0442\u0430\u043b\u044c\u043d\u0430 \u043b\u0456\u043d\u0456\u044f", +"Insert\/edit image": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u0438\/\u0440\u0435\u0434\u0430\u0433\u0443\u0432\u0430\u0442\u0438 \u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u043d\u044f", +"Image description": "\u041e\u043f\u0438\u0441 \u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u043d\u044f", +"Source": "\u0414\u0436\u0435\u0440\u0435\u043b\u043e", +"Dimensions": "\u0420\u043e\u0437\u043c\u0456\u0440", +"Constrain proportions": "\u0417\u0431\u0435\u0440\u0456\u0433\u0430\u0442\u0438 \u043f\u0440\u043e\u043f\u043e\u0440\u0446\u0456\u0457", +"General": "\u0417\u0430\u0433\u0430\u043b\u044c\u043d\u0435", +"Advanced": "\u0414\u043e\u0434\u0430\u0442\u043a\u043e\u0432\u043e", +"Style": "\u0421\u0442\u0438\u043b\u044c", +"Vertical space": "\u0412\u0435\u0440\u0442\u0438\u043a\u0430\u043b\u044c\u043d\u0438\u0439 \u043f\u0440\u043e\u043f\u0443\u0441\u043a", +"Horizontal space": "\u0413\u043e\u0440\u0438\u0437\u043e\u043d\u0442\u0430\u043b\u044c\u043d\u0438\u0439 \u043f\u0440\u043e\u043f\u0443\u0441\u043a", +"Border": "\u041c\u0435\u0436\u0430", +"Insert image": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u0438 \u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u043d\u044f", +"Image": "\u0417\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u043d\u044f", +"Image list": "\u0421\u043f\u0438\u0441\u043e\u043a \u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u044c", +"Rotate counterclockwise": "\u041f\u043e\u0432\u0435\u0440\u043d\u0443\u0442\u0438 \u043f\u0440\u043e\u0442\u0438 \u0433\u043e\u0434\u0438\u043d\u043d\u0438\u043a\u043e\u0432\u043e\u0457 \u0441\u0442\u0440\u0456\u043b\u043a\u0438", +"Rotate clockwise": "\u041f\u043e\u0432\u0435\u0440\u043d\u0443\u0442\u0438 \u0437\u0430 \u0433\u043e\u0434\u0438\u043d\u043d\u0438\u043a\u043e\u0432\u043e\u044e \u0441\u0442\u0440\u0456\u043b\u043a\u043e\u044e", +"Flip vertically": "\u0412\u0456\u0434\u043e\u0431\u0440\u0430\u0437\u0438\u0442\u0438 \u043f\u043e \u0432\u0435\u0440\u0442\u0438\u043a\u0430\u043b\u0456", +"Flip horizontally": "\u0412\u0456\u0434\u043e\u0431\u0440\u0430\u0437\u0438\u0442\u0438 \u043f\u043e \u0433\u043e\u0440\u0438\u0437\u043e\u043d\u0442\u0430\u043b\u0456", +"Edit image": "\u0420\u0435\u0434\u0430\u0433\u0443\u0432\u0430\u0442\u0438 \u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u043d\u044f", +"Image options": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u043d\u044f", +"Zoom in": "\u0417\u0431\u0456\u043b\u044c\u0448\u0438\u0442\u0438", +"Zoom out": "\u0417\u043c\u0435\u043d\u0448\u0438\u0442\u0438", +"Crop": "\u041e\u0431\u0440\u0456\u0437\u0430\u0442\u0438", +"Resize": "\u0417\u043c\u0456\u043d\u0438\u0442\u0438 \u0440\u043e\u0437\u043c\u0456\u0440", +"Orientation": "\u041e\u0440\u0456\u0454\u043d\u0442\u0430\u0446\u0456\u044f", +"Brightness": "\u042f\u0441\u043a\u0440\u0430\u0432\u0456\u0441\u0442\u044c", +"Sharpen": "\u0427\u0456\u0442\u043a\u0456\u0441\u0442\u044c", +"Contrast": "\u041a\u043e\u043d\u0442\u0440\u0430\u0441\u0442", +"Color levels": "\u0420\u0456\u0432\u043d\u0456 \u043a\u043e\u043b\u044c\u043e\u0440\u0456\u0432", +"Gamma": "\u0413\u0430\u043c\u043c\u0430", +"Invert": "\u0406\u043d\u0432\u0435\u0440\u0441\u0456\u044f", +"Apply": "\u0417\u0430\u0441\u0442\u043e\u0441\u0443\u0432\u0430\u0442\u0438", +"Back": "\u041f\u043e\u0432\u0435\u0440\u043d\u0443\u0442\u0438\u0441\u044f", +"Insert date\/time": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u0438 \u0434\u0430\u0442\u0443\/\u0447\u0430\u0441", +"Date\/time": "\u0414\u0430\u0442\u0430\/\u0447\u0430\u0441", +"Insert link": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u0438 \u043f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f", +"Insert\/edit link": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u0438\/\u0440\u0435\u0434\u0430\u0433\u0443\u0432\u0430\u0442\u0438 \u043f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f", +"Text to display": "\u0422\u0435\u043a\u0441\u0442 \u0434\u043b\u044f \u0432\u0456\u0434\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u043d\u044f", +"Url": "URL", +"Target": "\u041c\u0435\u0442\u0430", +"None": "\u041d\u0456", +"New window": "\u041d\u043e\u0432\u0435 \u0432\u0456\u043a\u043d\u043e", +"Remove link": "\u0412\u0438\u0434\u0430\u043b\u0438\u0442\u0438 \u043f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f", +"Anchors": "\u042f\u043a\u043e\u0440\u044f", +"Link": "\u041f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f", +"Paste or type a link": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u0438 \u0430\u0431\u043e \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u0438 \u043f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "\u0421\u0445\u043e\u0436\u0435, \u0449\u043e \u0432\u0438 \u0432\u0432\u0435\u043b\u0438 \u0430\u0434\u0440\u0435\u0441\u0443 \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0457 \u043f\u043e\u0448\u0442\u0438. \u0412\u0438 \u0431\u0430\u0436\u0430\u0454\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u0438 \u043f\u0440\u0435\u0444\u0456\u043a\u0441 mailto:?", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "\u0421\u0445\u043e\u0436\u0435, \u0449\u043e \u0432\u0438 \u0432\u0432\u0435\u043b\u0438 \u0437\u043e\u0432\u043d\u0456\u0448\u043d\u0454 \u043f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f. \u0412\u0438 \u0431\u0430\u0436\u0430\u0454\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u0438 \u043f\u0440\u0435\u0444\u0456\u043a\u0441 http:\/\/?", +"Link list": "\u0421\u043f\u0438\u0441\u043e\u043a \u043f\u043e\u0441\u0438\u043b\u0430\u043d\u044c", +"Insert video": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u0438 \u0432\u0456\u0434\u0435\u043e", +"Insert\/edit video": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u0438\/\u0440\u0435\u0434\u0430\u0433\u0443\u0432\u0430\u0442\u0438 \u0432\u0456\u0434\u0435\u043e", +"Insert\/edit media": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u0438\/\u0440\u0435\u0434\u0430\u0433\u0443\u0432\u0430\u0442\u0438 \u043c\u0435\u0434\u0456\u0430\u0434\u0430\u043d\u0456", +"Alternative source": "\u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u043d\u0435 \u0434\u0436\u0435\u0440\u0435\u043b\u043e", +"Poster": "\u041f\u043b\u0430\u043a\u0430\u0442", +"Paste your embed code below:": "\u0412\u0441\u0442\u0430\u0432\u0442\u0435 \u0432\u0430\u0448 \u043a\u043e\u0434 \u043d\u0438\u0436\u0447\u0435:", +"Embed": "\u0412\u043f\u0440\u043e\u0432\u0430\u0434\u0438\u0442\u0438", +"Media": "\u041c\u0435\u0434\u0456\u0430\u0434\u0430\u043d\u0456", +"Nonbreaking space": "\u041d\u0435\u0440\u043e\u0437\u0440\u0438\u0432\u043d\u0438\u0439 \u043f\u0440\u043e\u043f\u0443\u0441\u043a", +"Page break": "\u0420\u043e\u0437\u0440\u0438\u0432 \u0441\u0442\u043e\u0440\u0456\u043d\u043a\u0438", +"Paste as text": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u0438 \u044f\u043a \u0442\u0435\u043a\u0441\u0442", +"Preview": "\u041f\u043e\u043f\u0435\u0440\u0435\u0434\u043d\u0456\u0439 \u043f\u0435\u0440\u0435\u0433\u043b\u044f\u0434", +"Print": "\u0414\u0440\u0443\u043a", +"Save": "\u0417\u0431\u0435\u0440\u0435\u0433\u0442\u0438", +"Find": "\u0417\u043d\u0430\u0439\u0442\u0438", +"Replace with": "\u0417\u0430\u043c\u0456\u043d\u0438\u0442\u0438 \u043d\u0430", +"Replace": "\u0417\u0430\u043c\u0456\u043d\u0438\u0442\u0438", +"Replace all": "\u0417\u0430\u043c\u0456\u043d\u0438\u0442\u0438 \u0432\u0441\u0435", +"Prev": "\u041f\u043e\u043f\u0435\u0440\u0435\u0434\u043d\u0456\u0439", +"Next": "\u041d\u0430\u0441\u0442\u0443\u043f\u043d\u0438\u0439", +"Find and replace": "\u0417\u043d\u0430\u0439\u0442\u0438 \u0456 \u0437\u0430\u043c\u0456\u043d\u0438\u0442\u0438", +"Could not find the specified string.": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u0437\u043d\u0430\u0439\u0442\u0438 \u0437\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439 \u0440\u044f\u0434\u043e\u043a.", +"Match case": "\u0417 \u0443\u0440\u0430\u0445\u0443\u0432\u0430\u043d\u043d\u044f\u043c \u0440\u0435\u0433\u0456\u0441\u0442\u0440\u0443", +"Whole words": "\u0426\u0456\u043b\u0456 \u0441\u043b\u043e\u0432\u0430", +"Spellcheck": "\u041f\u0435\u0440\u0435\u0432\u0456\u0440\u043a\u0430 \u043e\u0440\u0444\u043e\u0433\u0440\u0430\u0444\u0456\u0457", +"Ignore": "\u0406\u0433\u043d\u043e\u0440\u0443\u0432\u0430\u0442\u0438", +"Ignore all": "\u0406\u0433\u043d\u043e\u0440\u0443\u0432\u0430\u0442\u0438 \u0432\u0441\u0435", +"Finish": "\u0417\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u0438", +"Add to Dictionary": "\u0414\u043e\u0434\u0430\u0442\u0438 \u0432 \u0441\u043b\u043e\u0432\u043d\u0438\u043a", +"Insert table": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u0438 \u0442\u0430\u0431\u043b\u0438\u0446\u044e", +"Table properties": "\u0412\u043b\u0430\u0441\u0442\u0438\u0432\u043e\u0441\u0442\u0456 \u0442\u0430\u0431\u043b\u0438\u0446\u0456", +"Delete table": "\u0412\u0438\u0434\u0430\u043b\u0438\u0442\u0438 \u0442\u0430\u0431\u043b\u0438\u0446\u044e", +"Cell": "\u041a\u043e\u043c\u0456\u0440\u043a\u0430", +"Row": "\u0420\u044f\u0434\u043e\u043a", +"Column": "\u0421\u0442\u043e\u0432\u043f\u0435\u0446\u044c", +"Cell properties": "\u0412\u043b\u0430\u0441\u0442\u0438\u0432\u043e\u0441\u0442\u0456 \u043a\u043e\u043c\u0456\u0440\u043a\u0438", +"Merge cells": "\u041e\u0431'\u0454\u0434\u043d\u0430\u0442\u0438 \u043a\u043e\u043c\u0456\u0440\u043a\u0438", +"Split cell": "\u0420\u043e\u0437\u0431\u0438\u0442\u0438 \u043a\u043e\u043c\u0456\u0440\u043a\u0443", +"Insert row before": "\u0412\u0441\u0442\u0430\u0432\u0442\u0435 \u0440\u044f\u0434\u043e\u043a \u043f\u0435\u0440\u0435\u0434", +"Insert row after": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u0438 \u0440\u044f\u0434\u043e\u043a \u043f\u0456\u0441\u043b\u044f", +"Delete row": "\u0412\u0438\u0434\u0430\u043b\u0438\u0442\u0438 \u0440\u044f\u0434\u043e\u043a", +"Row properties": "\u0412\u043b\u0430\u0441\u0442\u0438\u0432\u043e\u0441\u0442\u0456 \u0440\u044f\u0434\u043a\u0430", +"Cut row": "\u0412\u0438\u0440\u0456\u0437\u0430\u0442\u0438 \u0440\u044f\u0434\u043e\u043a", +"Copy row": "\u041a\u043e\u043f\u0456\u044e\u0432\u0430\u0442\u0438 \u0440\u044f\u0434\u043e\u043a", +"Paste row before": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u0438 \u0440\u044f\u0434\u043e\u043a \u043f\u0435\u0440\u0435\u0434", +"Paste row after": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u0438 \u0440\u044f\u0434\u043e\u043a \u043f\u0456\u0441\u043b\u044f", +"Insert column before": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u0438 \u0441\u0442\u043e\u0432\u043f\u0435\u0446\u044c \u043f\u0435\u0440\u0435\u0434", +"Insert column after": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u0438 \u0441\u0442\u043e\u0432\u043f\u0435\u0446\u044c \u043f\u0456\u0441\u043b\u044f", +"Delete column": "\u0412\u0438\u0434\u0430\u043b\u0438\u0442\u0438 \u0441\u0442\u043e\u0432\u043f\u0435\u0446\u044c", +"Cols": "\u0421\u0442\u043e\u0432\u043f\u0446\u0456", +"Rows": "\u0420\u044f\u0434\u043a\u0438", +"Width": "\u0428\u0438\u0440\u0438\u043d\u0430", +"Height": "\u0412\u0438\u0441\u043e\u0442\u0430", +"Cell spacing": "\u0406\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043c\u0456\u0436 \u043a\u043e\u043c\u0456\u0440\u043a\u0430\u043c\u0438", +"Cell padding": "\u0417\u0430\u043f\u043e\u0432\u043d\u0435\u043d\u043d\u044f \u043a\u043e\u043c\u0456\u0440\u043e\u043a", +"Caption": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a", +"Left": "\u041b\u0456\u0432\u043e\u0440\u0443\u0447", +"Center": "\u0426\u0435\u043d\u0442\u0440", +"Right": "\u041f\u0440\u0430\u0432\u043e\u0440\u0443\u0447", +"Cell type": "\u0422\u0438\u043f \u043a\u043e\u043c\u0456\u0440\u043a\u0438", +"Scope": "\u0423 \u043c\u0435\u0436\u0430\u0445", +"Alignment": "\u0412\u0438\u0440\u0456\u0432\u043d\u044e\u0432\u0430\u043d\u043d\u044f", +"H Align": "\u0413\u043e\u0440\u0438\u0437\u043e\u043d\u0442\u0430\u043b\u044c\u043d\u0435 \u0432\u0438\u0440\u0456\u0432\u043d\u044e\u0432\u0430\u043d\u043d\u044f", +"V Align": "\u0412\u0435\u0440\u0442\u0438\u043a\u0430\u043b\u044c\u043d\u0435 \u0432\u0438\u0440\u0456\u0432\u043d\u044e\u0432\u0430\u043d\u043d\u044f", +"Top": "\u041f\u043e \u0432\u0435\u0440\u0445\u043d\u044c\u043e\u043c\u0443 \u043a\u0440\u0430\u044e", +"Middle": "\u041f\u043e \u0446\u0435\u043d\u0442\u0440\u0443", +"Bottom": "\u041f\u043e \u043d\u0438\u0436\u043d\u044c\u043e\u043c\u0443 \u043a\u0440\u0430\u044e", +"Header cell": "\u041a\u043e\u043c\u0456\u0440\u043a\u0430 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0443", +"Row group": "\u0413\u0440\u0443\u043f\u0430 \u0440\u044f\u0434\u043a\u0456\u0432", +"Column group": "\u0413\u0440\u0443\u043f\u0430 \u0441\u0442\u043e\u0432\u043f\u0446\u0456\u0432", +"Row type": "\u0422\u0438\u043f \u0440\u044f\u0434\u043a\u0430", +"Header": "\u0412\u0435\u0440\u0445\u043d\u0456\u0439 \u043a\u043e\u043b\u043e\u043d\u0442\u0438\u0442\u0443\u043b", +"Body": "\u0422\u0456\u043b\u043e", +"Footer": "\u041d\u0438\u0436\u043d\u0456\u0439 \u043a\u043e\u043b\u043e\u043d\u0442\u0438\u0442\u0443\u043b", +"Border color": "\u041a\u043e\u043b\u0456\u0440 \u043c\u0435\u0436\u0456", +"Insert template": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u0438 \u0448\u0430\u0431\u043b\u043e\u043d", +"Templates": "\u0428\u0430\u0431\u043b\u043e\u043d\u0438", +"Template": "\u0428\u0430\u0431\u043b\u043e\u043d", +"Text color": "\u041a\u043e\u043b\u0456\u0440 \u0442\u0435\u043a\u0441\u0442\u0443", +"Background color": "\u041a\u043e\u043b\u0456\u0440 \u0444\u043e\u043d\u0443", +"Custom...": "\u0406\u043d\u0448\u0438\u0439...", +"Custom color": "\u041a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0446\u044c\u043a\u0438\u0439 \u043a\u043e\u043b\u0456\u0440", +"No color": "\u0411\u0435\u0437 \u043a\u043e\u043b\u044c\u043e\u0440\u0443", +"Table of Contents": "\u0417\u043c\u0456\u0441\u0442", +"Show blocks": "\u041f\u043e\u043a\u0430\u0437\u0430\u0442\u0438 \u0431\u043b\u043e\u043a\u0438", +"Show invisible characters": "\u041f\u043e\u043a\u0430\u0437\u0430\u0442\u0438 \u043d\u0435\u0432\u0438\u0434\u0438\u043c\u0456 \u0441\u0438\u043c\u0432\u043e\u043b\u0438", +"Words: {0}": "\u0421\u043b\u043e\u0432\u0430: {0}", +"{0} words": "{0} \u0441\u043b\u0456\u0432", +"File": "\u0424\u0430\u0439\u043b", +"Edit": "\u041f\u0440\u0430\u0432\u043a\u0430", +"Insert": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u0438", +"View": "\u0412\u0438\u0434", +"Format": "\u0424\u043e\u0440\u043c\u0430\u0442", +"Table": "\u0422\u0430\u0431\u043b\u0438\u0446\u044f", +"Tools": "\u0406\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u0438", +"Powered by {0}": "\u0417\u0430 \u043f\u0456\u0434\u0442\u0440\u0438\u043c\u0438\u043a\u0438 {0}", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "\u041e\u0431\u043b\u0430\u0441\u0442\u044c Rich \u0442\u0435\u043a\u0441\u0442\u0443. \u041d\u0430\u0442\u0438\u0441\u043d\u0456\u0442\u044c ALT-F9 - \u043c\u0435\u043d\u044e. \u041d\u0430\u0442\u0438\u0441\u043d\u0456\u0442\u044c ALT-F10 - \u043f\u0430\u043d\u0435\u043b\u044c \u0456\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u0456\u0432. \u041d\u0430\u0442\u0438\u0441\u043d\u0456\u0442\u044c ALT-0 - \u0434\u043e\u0432\u0456\u0434\u043a\u0430" +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/uz.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/uz.js new file mode 100644 index 0000000000..5e977a0825 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/uz.js @@ -0,0 +1,260 @@ +tinymce.addI18n('uz',{ +"Redo": "Bekor qilish", +"Undo": "Orqaga qaytarish", +"Cut": "Kesib olish", +"Copy": "Nusxa olish", +"Paste": "Qo\u2018yish", +"Select all": "Barchasini belgilash", +"New document": "Yangi hujjat", +"Ok": "Bajarish", +"Cancel": "Bekor qilish", +"Visual aids": "Ko\u2018rgazmali o\u2018quv qurollar", +"Bold": "Yo'g'on", +"Italic": "Yotiq", +"Underline": "Tagi chizilgan", +"Strikethrough": "O'chirilgan yozuv", +"Superscript": "Yuqori yozuv", +"Subscript": "Quyi yozuv", +"Clear formatting": "Formatlashni tozalash", +"Align left": "Chapga tekislash", +"Align center": "Markazga tekislash", +"Align right": "O'ngga tekislash", +"Justify": "Ikki tomondan tekislash", +"Bullet list": "Nuqtali ro\u2018yxat", +"Numbered list": "Raqamli ro\u2018yxat", +"Decrease indent": "Satr boshini kamaytirish", +"Increase indent": "Satr boshini oshirish", +"Close": "Yopish", +"Formats": "Formatlar", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "Sizning brauzeringiz buferga to\u2018g\u2018ridan-to\u2018g\u2018ri kirish qo\u2018llab-quvvatlamaydi. O\u2018rniga klaviaturaning Ctrl+X\/C\/V qisqartirishlarni foydalaning.", +"Headers": "Sarlavhalar", +"Header 1": "Sarlavha 1", +"Header 2": "Sarlavha 2", +"Header 3": "Sarlavha 3", +"Header 4": "Sarlavha 4", +"Header 5": "Sarlavha 5", +"Header 6": "Sarlavha 6", +"Headings": "Sarlavhalar", +"Heading 1": "Sarlavha 1", +"Heading 2": "Sarlavha 2", +"Heading 3": "Sarlavha 3", +"Heading 4": "Sarlavha 4", +"Heading 5": "Sarlavha 5", +"Heading 6": "Sarlavha 6", +"Div": "Div", +"Pre": "Pre", +"Code": "Kod", +"Paragraph": "Paragraf", +"Blockquote": "Matn blok parchasi", +"Inline": "Bir qator ketma-ketlikda", +"Blocks": "Bloklar", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "Qo'shish oddiy matn rejimida amalga oshiriladi. Ushbu hususiyatni o'chirmaguningizcha, kontent oddiy matn sifatida qo'shiladi.", +"Font Family": "Srift turi", +"Font Sizes": "Shrift kattaligi", +"Class": "Klass", +"Browse for an image": "Rasmni yuklash", +"OR": "YOKI", +"Drop an image here": "Bu erga rasmni olib o'ting", +"Upload": "Yuklash", +"Block": "Blok", +"Align": "Saflamoq", +"Default": "Standart", +"Circle": "Doira", +"Disc": "Disk", +"Square": "Kvadrat", +"Lower Alpha": "Kichik lotincha", +"Lower Greek": "Pastki yunon", +"Lower Roman": "Kichik kirilcha", +"Upper Alpha": "Katta lotincha", +"Upper Roman": "Katta kirilcha", +"Anchor": "Langar", +"Name": "Nomi", +"Id": "Id", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "Id faqat harf bilan boshlanishi lozim, o'z ichiga faqat harflar, sonlar, tire, nuqtalar, pastgi chiziqlardan iborat bo'lishi mumkin", +"You have unsaved changes are you sure you want to navigate away?": "Sizda saqlanmagan o'zgartirishlar bor. Boshqa yerga chiqib ketish uchun ishonchingiz komilmi?", +"Restore last draft": "Oxirgi ", +"Special character": "Maxsus belgilar", +"Source code": "Manba kodi", +"Insert\/Edit code sample": "Kod namunasini qo'shish \/ tahrirlash", +"Language": "Til", +"Code sample": "Kod namunasi", +"Color": "Rang", +"R": "R", +"G": "G", +"B": "B", +"Left to right": "Chapdan o'ngga", +"Right to left": "O'ngdan chapga", +"Emoticons": "Hissiyot ikonkalari", +"Document properties": "Hujjatning xususiyatlari", +"Title": "Nomi", +"Keywords": "Kalit so'zlar", +"Description": "Tavsif", +"Robots": "Robotlar", +"Author": "Muallif", +"Encoding": "Kodlash", +"Fullscreen": "Butun ekran rejimi", +"Action": "Harakat", +"Shortcut": "Yorliq", +"Help": "Yordam", +"Address": "Manzil", +"Focus to menubar": "Menubarga e'tibor qaratish", +"Focus to toolbar": "Vositalar paneliga e'tibor qaratish", +"Focus to element path": "Elementlar manziliga e'tibor qaratish", +"Focus to contextual toolbar": "Kontekstli vositalar paneliga e'tibor qaratish", +"Insert link (if link plugin activated)": "Havolani qo'shish (havola plagini o'rnatilgan bo'lsa)", +"Save (if save plugin activated)": "Saqlash (saqlash plagini o'rnatilgan bo'lsa)", +"Find (if searchreplace plugin activated)": "Qidirish (qidirish plagini o'rnatilgan bo'lsa)", +"Plugins installed ({0}):": "O'rnatilgan plaginlar ({0})", +"Premium plugins:": "Premium plaginlar:", +"Learn more...": "Batafsil ma'lumot...", +"You are using {0}": "Siz {0} ishlatmoqdasiz", +"Plugins": "Plaginlar", +"Handy Shortcuts": "Foydalanadigan yorliqlar", +"Horizontal line": "Gorizontal chiziq", +"Insert\/edit image": "Rasmni qo'shish \/ tahrirlash", +"Image description": "Rasm tavsifi", +"Source": "Manba", +"Dimensions": "O'lchamlari", +"Constrain proportions": "Nisbatlarni cheklash", +"General": "Umumiy", +"Advanced": "Ilg'or", +"Style": "Uslub", +"Vertical space": "Vertikal o'lchov", +"Horizontal space": "Gorizontal o'lchov", +"Border": "Chegara", +"Insert image": "Rasm qo'shish", +"Image": "Rasm", +"Image list": "Rasmlar ro'yhati", +"Rotate counterclockwise": "Soatga qarshi yo'nalishda aylantirish", +"Rotate clockwise": "Soat yo'nalishda aylantirish", +"Flip vertically": "Vertikal o'girish", +"Flip horizontally": "Gorizontal o'girish", +"Edit image": "Rasmni tahrirlash", +"Image options": "Rasm imkoniyatlari", +"Zoom in": "Yaqinlashtirish", +"Zoom out": "Uzoqlashtirish", +"Crop": "Kesib olish", +"Resize": "O'lchamini o'zgartirish", +"Orientation": "Orientatsiya", +"Brightness": "Yorqinligi", +"Sharpen": "Keskinligi", +"Contrast": "Ravshanligi", +"Color levels": "Rang sathi", +"Gamma": "Gamma", +"Invert": "Ranglarni ag'darish", +"Apply": "Qo'llash", +"Back": "Ortga qaytish", +"Insert date\/time": "Kun \/ vaqtni qo'shish", +"Date\/time": "Kun\/vaqt", +"Insert link": "Havola qo'shish", +"Insert\/edit link": "Havola qo'shish \/ tahrirlash", +"Text to display": "Ko'rsatiladigan matn", +"Url": "URL", +"Target": "Nishon", +"None": "Hech bir", +"New window": "Yangi oyna", +"Remove link": "Havolani olib tashlash", +"Anchors": "Langarlar", +"Link": "Havola", +"Paste or type a link": "Havolani joylashtirish yoki kiritish", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "Siz kiritgan URL elektron pochta manziliga oxshayapti. \"mailto:\" prefiksi qo'shilsinmi?", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "Siz kiritgan URL tashqi havolaga oxshayapti. \"http:\/\/\" prefiksi qo'shilsinmi?", +"Link list": "Havolalar ro'yhati", +"Insert video": "Video qo'shish", +"Insert\/edit video": "Videoni qo'shish \/ tahrirlash", +"Insert\/edit media": "Media qo'shish \/ tahrirlash", +"Alternative source": "Muqobil manba", +"Poster": "Poster", +"Paste your embed code below:": "Kodni quyiga joylashtiring:", +"Embed": "Ichiga olgan", +"Media": "Media", +"Nonbreaking space": "Buzilmas bo'sh joy", +"Page break": "Yangi bet", +"Paste as text": "Tekst qo'shish", +"Preview": "Tahrirni avvaldan ko'rish", +"Print": "Chop etish", +"Save": "Saqlash", +"Find": "Qidirish", +"Replace with": "bilan almashtirish", +"Replace": "Almashtirish", +"Replace all": "Barchasini almashtirish", +"Prev": "Avvalgisi", +"Next": "Keyingisi", +"Find and replace": "Topib almashtirish", +"Could not find the specified string.": "Belgilangan satr topilmadi.", +"Match case": "O'xshashliklar", +"Whole words": "Butun so'z", +"Spellcheck": "Imloni tekshirish", +"Ignore": "E'tiborsiz qoldirish", +"Ignore all": "Barchasini e'tiborsiz qoldirish", +"Finish": "Tugatish", +"Add to Dictionary": "Lug'atga qo'shish", +"Insert table": "Jadvalni qo'shish", +"Table properties": "Jadval xususiyatlari", +"Delete table": "Jadvalni o'chirib tashlash", +"Cell": "Katak", +"Row": "Satr", +"Column": "Ustun", +"Cell properties": "Katak hususiyatlari", +"Merge cells": "Kataklarni birlashtirish", +"Split cell": "Kataklarni bo'lish", +"Insert row before": "Yuqorisiga satr qo'shish", +"Insert row after": "Ketidan satr qo'shish", +"Delete row": "Satrni olib tashlash", +"Row properties": "Satr hususiyatlari", +"Cut row": "Satrni kesib olish", +"Copy row": "Satrdan nusxa ko'chirish", +"Paste row before": "Yuqorisiga satrni joylashtirish", +"Paste row after": "Ketidan satrni joylashtirish", +"Insert column before": "Ustunni oldi tomoniga qo'shish", +"Insert column after": "Ustunni ketidan qo'shish", +"Delete column": "Ustunni olib tashlash", +"Cols": "Ustunlar", +"Rows": "Satrlar", +"Width": "Kengligi", +"Height": "Balandligi", +"Cell spacing": "Kataklar orasi", +"Cell padding": "Kataklar chegarasidan bo'sh joy", +"Caption": "Taglavha", +"Left": "Chapga", +"Center": "Markazga", +"Right": "O'ngga", +"Cell type": "Katak turi", +"Scope": "Muhit", +"Alignment": "Tekislash", +"H Align": "Gorizontal tekislash", +"V Align": "Vertikal tekislash", +"Top": "Yuqoriga", +"Middle": "Markaziga", +"Bottom": "Tagiga", +"Header cell": "Sarlavha katagi", +"Row group": "Satrlar guruhi", +"Column group": "Ustunlar guruhi", +"Row type": "Satr turi", +"Header": "Sarlavha", +"Body": "Tanasi", +"Footer": "Tag qismi", +"Border color": "Chegara rangi", +"Insert template": "Andozani qo'shish", +"Templates": "Andozalar", +"Template": "Andoza", +"Text color": "Matn rangi", +"Background color": "Orqa fon rangi", +"Custom...": "O'zgacha...", +"Custom color": "O'zgacha rang", +"No color": "Rangsiz", +"Table of Contents": "Mundarija", +"Show blocks": "Bloklarni ko'rsatish", +"Show invisible characters": "Ko'rinmas belgilarni ko'rsatish", +"Words: {0}": "So'zlar soni: {0}", +"{0} words": "{0} so`z", +"File": "Fayl", +"Edit": "Tahrirlash", +"Insert": "Qo'shish", +"View": "Ko'rish", +"Format": "Shakllar", +"Table": "Jadval", +"Tools": "Vositalar", +"Powered by {0}": "{0} bilan ishlaydi", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Murakkab matn maydoni. Menyu uchun ALT-F9 tugmalarini bosing. Vositalar paneli uchun ALT-F10 tugmasini bosing. Yordamni chaqirish uchun ALT-0-ni bosing" +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/vi_VN.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/vi_VN.js new file mode 100644 index 0000000000..3c0a859d63 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/vi_VN.js @@ -0,0 +1,260 @@ +tinymce.addI18n('vi_VN',{ +"Redo": "Ho\u00e0n t\u00e1t", +"Undo": "Hu\u1ef7 thao t\u00e1c", +"Cut": "C\u1eaft", +"Copy": "Ch\u00e9p", +"Paste": "D\u00e1n", +"Select all": "Ch\u1ecdn t\u1ea5t c\u1ea3", +"New document": "T\u1ea1o t\u00e0i li\u1ec7u m\u1edbi", +"Ok": "OK", +"Cancel": "Hu\u1ef7", +"Visual aids": "Hi\u1ec7n khung so\u1ea1n th\u1ea3o", +"Bold": "T\u00f4 \u0111\u1eadm", +"Italic": "In nghi\u00eang", +"Underline": "G\u1ea1ch d\u01b0\u1edbi", +"Strikethrough": "G\u1ea1ch ngang", +"Superscript": "Tr\u00ean d\u00f2ng", +"Subscript": "D\u01b0\u1edbi d\u00f2ng", +"Clear formatting": "Xo\u00e1 \u0111\u1ecbnh d\u1ea1ng", +"Align left": "Canh tr\u00e1i", +"Align center": "Canh gi\u1eefa", +"Align right": "Canh ph\u1ea3i", +"Justify": "Canh \u0111\u1ec1u hai b\u00ean", +"Bullet list": "D\u1ea5u \u0111\u1ea7u d\u00f2ng", +"Numbered list": "Danh s\u00e1ch s\u1ed1", +"Decrease indent": "L\u00f9i ra", +"Increase indent": "L\u00f9i v\u00e0o", +"Close": "\u0110\u00f3ng", +"Formats": "\u0110\u1ecbnh d\u1ea1ng", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "Tr\u00ecnh duy\u1ec7t c\u1ee7a b\u1ea1n kh\u00f4ng h\u1ed7 tr\u1ee3 truy c\u1eadp clipboard, vui l\u00f2ng s\u1eed d\u1ee5ng c\u00e1c t\u1ed5 h\u1ee3p Ctrl + X, C, V.", +"Headers": "\u0110\u1ea7u trang", +"Header 1": "Ti\u00eau \u0111\u1ec1 1", +"Header 2": "Ti\u00eau \u0111\u1ec1 2", +"Header 3": "Ti\u00eau \u0111\u1ec1 3", +"Header 4": "Ti\u00eau \u0111\u1ec1 4", +"Header 5": "Ti\u00eau \u0111\u1ec1 5", +"Header 6": "Ti\u00eau \u0111\u1ec1 6", +"Headings": "Ti\u00eau \u0111\u1ec1", +"Heading 1": "Ti\u00eau \u0111\u1ec1 1", +"Heading 2": "Ti\u00eau \u0111\u1ec1 2", +"Heading 3": "Ti\u00eau \u0111\u1ec1 3", +"Heading 4": "Ti\u00eau \u0111\u1ec1 4", +"Heading 5": "Ti\u00eau \u0111\u1ec1 5", +"Heading 6": "Ti\u00eau \u0111\u1ec1 6", +"Div": "Khung", +"Pre": "\u0110\u1ecbnh d\u1ea1ng", +"Code": "M\u00e3", +"Paragraph": "\u0110o\u1ea1n v\u0103n", +"Blockquote": "Tr\u00edch", +"Inline": "C\u00f9ng d\u00f2ng", +"Blocks": "Bao", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "D\u00e1n b\u00e2y gi\u1edd l\u00e0 \u1edf ch\u1ebf \u0111\u1ed9 v\u0103n b\u1ea3n \u0111\u01a1n gi\u1ea3n. N\u1ed9i dung s\u1ebd \u0111\u01b0\u1ee3c d\u00e1n nh\u01b0 \u0111\u1ed3ng b\u1eb1ng v\u0103n b\u1ea3n cho \u0111\u1ebfn khi b\u1ea1n chuy\u1ec3n \u0111\u1ed5i t\u00f9y ch\u1ecdn n\u00e0y.", +"Font Family": "Ph\u00f4ng", +"Font Sizes": "K\u00edch th\u01b0\u1edbc ph\u00f4ng", +"Class": "L\u1edbp", +"Browse for an image": "Duy\u1ec7t \u1ea3nh", +"OR": "HO\u1eb6C", +"Drop an image here": "Th\u1ea3 h\u00ecnh \u1ea3nh \u1edf \u0111\u00e2y", +"Upload": "T\u1ea3i l\u00ean", +"Block": "Kh\u1ed1i", +"Align": "C\u0103n ch\u1ec9nh", +"Default": "Ng\u1ea7m \u0111\u1ecbnh", +"Circle": "H\u00ecnh tr\u00f2n", +"Disc": "H\u00ecnh tr\u00f2n m\u1ecfng", +"Square": "\u00d4 vu\u00f4ng", +"Lower Alpha": "K\u00fd t\u1ef1 th\u01b0\u1eddng", +"Lower Greek": "S\u1ed1 hy l\u1ea1p th\u01b0\u1eddng", +"Lower Roman": "S\u1ed1 la m\u00e3 th\u01b0\u1eddng", +"Upper Alpha": "K\u00fd t\u1ef1 hoa", +"Upper Roman": "S\u1ed1 la m\u00e3 hoa", +"Anchor": "Neo", +"Name": "T\u00ean", +"Id": "Id", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "Id ph\u1ea3i b\u1eaft \u0111\u1ea7u b\u1eb1ng m\u1ed9t ch\u1eef c\u00e1i, ch\u1ec9 theo sau b\u1edfi c\u00e1c ch\u1eef c\u00e1i, s\u1ed1, d\u1ea5u g\u1ea1ch ngang, d\u1ea5u ch\u1ea5m, d\u1ea5u hai ch\u1ea5m ho\u1eb7c d\u1ea5u g\u1ea1ch d\u01b0\u1edbi.", +"You have unsaved changes are you sure you want to navigate away?": "B\u1ea1n ch\u01b0a l\u01b0u c\u00e1c thay \u0111\u1ed5i, b\u1ea1n c\u00f3 th\u1eadt s\u1ef1 mu\u1ed1n \u0111\u00f3ng ?", +"Restore last draft": "Ph\u1ee5c h\u1ed3i b\u1ea3n l\u01b0u g\u1ea7n nh\u1ea5t", +"Special character": "K\u00fd t\u1ef1 \u0111\u1eb7c bi\u1ec7t", +"Source code": "M\u00e3 ngu\u1ed3n", +"Insert\/Edit code sample": "Ch\u00e8n\/Ch\u1ec9nh s\u1eeda m\u1eabu", +"Language": "Ng\u00f4n ng\u1eef", +"Code sample": "\u0110o\u1ea1n m\u00e3 m\u1eabu", +"Color": "M\u00e0u", +"R": "R", +"G": "G", +"B": "B", +"Left to right": "Tr\u00e1i sang ph\u1ea3i", +"Right to left": "Ph\u1ea3i sang tr\u00e1i", +"Emoticons": "Bi\u1ec3u t\u01b0\u1ee3ng c\u1ea3m x\u00fac", +"Document properties": "Thu\u1ed9c t\u00ednh t\u00e0i li\u1ec7u", +"Title": "Ti\u00eau \u0111\u1ec1", +"Keywords": "T\u1eeb kho\u00e1", +"Description": "Mi\u00eau t\u1ea3", +"Robots": "Robots", +"Author": "Neo", +"Encoding": "M\u00e3 ho\u00e1", +"Fullscreen": "\u0110\u1ea7y m\u00e0n h\u00ecnh", +"Action": "H\u00e0nh \u0111\u1ed9ng", +"Shortcut": "L\u1ed1i t\u1eaft", +"Help": "Tr\u1ee3 gi\u00fap", +"Address": "\u0110\u1ecba ch\u1ec9", +"Focus to menubar": "G\u1eafn l\u00ean thanh tr\u00ecnh \u0111\u01a1n", +"Focus to toolbar": "G\u1eafn l\u00ean thanh c\u00f4ng c\u1ee5", +"Focus to element path": "G\u1eafn v\u00e0o \u0111\u01b0\u1eddng d\u1eabn", +"Focus to contextual toolbar": "G\u1eafn v\u00e0o thanh c\u00f4ng c\u1ee5 ng\u1eef c\u1ea3nh", +"Insert link (if link plugin activated)": "Ch\u00e8n li\u00ean k\u1ebft (n\u1ebfu plugin li\u00ean k\u1ebft \u0111\u1ea3 k\u00edch ho\u1ea1t)", +"Save (if save plugin activated)": "L\u01b0u (n\u1ebfu plugin l\u01b0u \u0111\u1ea3 k\u00edch ho\u1ea1t)", +"Find (if searchreplace plugin activated)": "T\u00ecm (n\u1ebfu plugin t\u00ecm v\u00e0 thay th\u1ebf \u0111\u1ea3 k\u00edch ho\u1ea1t)", +"Plugins installed ({0}):": "Plugin \u0111\u00e3 c\u00e0i \u0111\u1eb7t ({0}):", +"Premium plugins:": "C\u00e1c Plugin tr\u1ea3 ph\u00ed:", +"Learn more...": "T\u00ecm hi\u1ec3u th\u00eam...", +"You are using {0}": "B\u1ea1n \u0111ang s\u1eed d\u1ee5ng {0}", +"Plugins": "Plugins", +"Handy Shortcuts": "Ph\u00edm t\u1eaft ti\u1ec7n d\u1ee5ng", +"Horizontal line": "G\u1ea1ch ngang", +"Insert\/edit image": "Th\u00eam \/ s\u1eeda h\u00ecnh \u1ea3nh", +"Image description": "Mi\u00eau t\u1ea3 h\u00ecnh \u1ea3nh", +"Source": "Ngu\u1ed3n", +"Dimensions": "K\u00edch th\u01b0\u1edbc", +"Constrain proportions": "H\u1ea1n ch\u1ebf t\u1ef7 l\u1ec7", +"General": "T\u1ed5ng h\u1ee3p", +"Advanced": "N\u00e2ng cao", +"Style": "Ki\u1ec3u", +"Vertical space": "Kho\u1ea3ng c\u00e1ch d\u1ecdc", +"Horizontal space": "Kho\u1ea3ng c\u00e1ch ngang", +"Border": "\u0110\u01b0\u1eddng vi\u1ec1n", +"Insert image": "Ch\u00e8n \u1ea3nh", +"Image": "H\u00ecnh \u1ea3nh", +"Image list": "Danh s\u00e1ch \u1ea3nh", +"Rotate counterclockwise": "Xoay ng\u01b0\u1ee3c chi\u1ec1u kim \u0111\u1ed3ng", +"Rotate clockwise": "Xoay theo chi\u1ec1u kim \u0111\u1ed3ng h\u1ed3", +"Flip vertically": "L\u1eadt d\u1ecdc", +"Flip horizontally": "L\u1eadt ngang", +"Edit image": "S\u1eeda \u1ea3nh", +"Image options": "T\u00f9y ch\u1ecdn h\u00ecnh \u1ea3nh", +"Zoom in": "Ph\u00f3ng to", +"Zoom out": "Thu nh\u1ecf", +"Crop": "X\u00e9n", +"Resize": "Thay \u0111\u1ed5i k\u00edch th\u01b0\u1edbc", +"Orientation": "\u0110\u1ecbnh h\u01b0\u1edbng", +"Brightness": "\u0110\u1ed9 s\u00e1ng", +"Sharpen": "\u0110\u1ed9 s\u1eafc n\u00e9t", +"Contrast": "\u0110\u1ed9 t\u01b0\u01a1ng ph\u1ea3n", +"Color levels": "M\u1ee9c \u0111\u1ed9 m\u00e0u s\u1eafc", +"Gamma": "M\u00e0u Gamma", +"Invert": "\u0110\u1ea3o ng\u01b0\u1ee3c", +"Apply": "\u00c1p d\u1ee5ng", +"Back": "Tr\u1edf l\u1ea1i", +"Insert date\/time": "Th\u00eam ng\u00e0y \/ gi\u1edd", +"Date\/time": "Ng\u00e0y\/gi\u1edd", +"Insert link": "Th\u00eam li\u00ean k\u1ebft", +"Insert\/edit link": "Th\u00eam \/ s\u1eeda li\u00ean k\u1ebft", +"Text to display": "Ch\u1eef hi\u1ec3n th\u1ecb", +"Url": "Li\u00ean k\u1ebft", +"Target": "M\u1ee5c ti\u00eau", +"None": "Kh\u00f4ng", +"New window": "C\u1eeda s\u1ed5 m\u1edbi", +"Remove link": "Xo\u00e1 li\u00ean k\u1ebft", +"Anchors": "Ghim", +"Link": "Li\u00ean k\u1ebft", +"Paste or type a link": "D\u00e1n ho\u1eb7c nh\u1eadp li\u00ean k\u1ebft", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "URL b\u1ea1n nh\u1eadp v\u00e0o c\u00f3 v\u1ebb l\u00e0 m\u1ed9t \u0111\u1ecba ch\u1ec9 email. B\u1ea1n c\u00f3 mu\u1ed1n th\u00eam c\u00e1c y\u00eau c\u1ea7u mailto: ti\u1ec1n t\u1ed1?", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "URL b\u1ea1n nh\u1eadp v\u00e0o c\u00f3 v\u1ebb l\u00e0 m\u1ed9t li\u00ean k\u1ebft b\u00ean ngo\u00e0i. B\u1ea1n c\u00f3 mu\u1ed1n th\u00eam ti\u1ec1n t\u1ed1 http:\/\/ c\u1ea7n thi\u1ebft?", +"Link list": "Danh s\u00e1ch li\u00ean k\u1ebft", +"Insert video": "Th\u00eam video", +"Insert\/edit video": "Th\u00eam \/ s\u1eeda video", +"Insert\/edit media": "Ch\u00e8n\/ch\u1ec9nh s\u1eeda ph\u01b0\u01a1ng ti\u1ec7n truy\u1ec1n th\u00f4ng", +"Alternative source": "Ngu\u1ed3n thay th\u1ebf", +"Poster": "Ng\u01b0\u1eddi \u0111\u0103ng", +"Paste your embed code below:": "D\u00e1n m\u00e3 embed v\u00e0o:", +"Embed": "Embed", +"Media": "Ph\u01b0\u01a1ng ti\u1ec7n truy\u1ec1n th\u00f4ng", +"Nonbreaking space": "Kh\u00f4ng ng\u1eaft kho\u1ea3ng", +"Page break": "Ng\u1eaft trang", +"Paste as text": "D\u00e1n nh\u01b0 v\u0103n b\u1ea3n", +"Preview": "Xem tr\u01b0\u1edbc", +"Print": "In", +"Save": "L\u01b0u", +"Find": "T\u00ecm", +"Replace with": "Thay th\u1ebf b\u1eb1ng", +"Replace": "Thay th\u1ebf", +"Replace all": "Thay th\u1ebf t\u1ea5t c\u1ea3", +"Prev": "Tr\u01b0\u1edbc", +"Next": "Sau", +"Find and replace": "T\u00ecm v\u00e0 thay th\u1ebf", +"Could not find the specified string.": "Kh\u00f4ng t\u00ecm th\u1ea5y chu\u1ed7i y\u00eau c\u1ea7u", +"Match case": "Ph\u00e2n bi\u1ec7t hoa th\u01b0\u1eddng", +"Whole words": "T\u1ea5t c\u1ea3 \u0111o\u1ea1n", +"Spellcheck": "Ki\u1ec3m tra ch\u00ednh t\u1ea3", +"Ignore": "L\u1edd qua", +"Ignore all": "L\u1edd t\u1ea5t c\u1ea3", +"Finish": "Ho\u00e0n t\u1ea5t", +"Add to Dictionary": "Th\u00eam v\u00e0o t\u1eeb \u0111i\u1ec3n", +"Insert table": "Th\u00eam b\u1ea3ng", +"Table properties": "Thu\u1ed9c t\u00ednh b\u1ea3ng", +"Delete table": "Xo\u00e1 b\u1ea3ng", +"Cell": "\u00d4", +"Row": "D\u00f2ng", +"Column": "C\u1ed9t", +"Cell properties": "Thu\u1ed9c t\u00ednh \u00f4", +"Merge cells": "N\u1ed1i \u00f4", +"Split cell": "Chia \u00f4", +"Insert row before": "Th\u00eam d\u00f2ng ph\u00eda tr\u00ean", +"Insert row after": "Th\u00eam d\u00f2ng ph\u00eda d\u01b0\u1edbi", +"Delete row": "Xo\u00e1 d\u00f2ng", +"Row properties": "Thu\u1ed9c t\u00ednh d\u00f2ng", +"Cut row": "C\u1eaft d\u00f2ng", +"Copy row": "Ch\u00e9p d\u00f2ng", +"Paste row before": "D\u00e1n v\u00e0o ph\u00eda tr\u01b0\u1edbc, tr\u00ean", +"Paste row after": "D\u00e1n v\u00e0o ph\u00eda sau, d\u01b0\u1edbi", +"Insert column before": "Th\u00eam c\u1ed9t b\u00ean tr\u00e1i", +"Insert column after": "Th\u00eam c\u1ed9t b\u00ean ph\u1ea3i", +"Delete column": "Xo\u00e1 c\u1ed9t", +"Cols": "C\u1ed9t", +"Rows": "D\u00f2ng", +"Width": "R\u1ed9ng", +"Height": "Cao", +"Cell spacing": "Kho\u1ea3ng c\u00e1ch \u00f4", +"Cell padding": "Kho\u1ea3ng c\u00e1ch trong \u00f4", +"Caption": "Ti\u00eau \u0111\u1ec1", +"Left": "Tr\u00e1i", +"Center": "Gi\u1eefa", +"Right": "Ph\u1ea3i", +"Cell type": "Lo\u1ea1i \u00f4", +"Scope": "Quy\u1ec1n", +"Alignment": "Canh ch\u1ec9nh", +"H Align": "X\u1ebfp ngang", +"V Align": "X\u1ebfp d\u1ecdc", +"Top": "\u0110\u1ec9nh", +"Middle": "Gi\u1eefa", +"Bottom": "\u0110\u00e1y", +"Header cell": "Ti\u00eau \u0111\u1ec1 \u00f4", +"Row group": "Nh\u00f3m d\u00f2ng", +"Column group": "Nh\u00f3m c\u1ed9t", +"Row type": "Lo\u1ea1i d\u00f2ng", +"Header": "Ti\u00eau \u0111\u1ec1", +"Body": "N\u1ed9i dung", +"Footer": "Ch\u00e2n", +"Border color": "M\u00e0u vi\u1ec1n", +"Insert template": "Th\u00eam m\u1eabu", +"Templates": "M\u1eabu", +"Template": "B\u1ea3n m\u1eabu", +"Text color": "M\u00e0u ch\u1eef", +"Background color": "M\u00e0u n\u1ec1n", +"Custom...": "T\u00f9y ch\u1ecdn...", +"Custom color": "M\u00e0u t\u00f9y ch\u1ecdn", +"No color": "Kh\u00f4ng m\u00e0u", +"Table of Contents": "M\u1ee5c l\u1ee5c", +"Show blocks": "Hi\u1ec3n th\u1ecb kh\u1ed1i", +"Show invisible characters": "Hi\u1ec3n th\u1ecb c\u00e1c k\u00fd t\u1ef1 \u1ea9n", +"Words: {0}": "T\u1eeb: {0}", +"{0} words": "{0} t\u1eeb", +"File": "T\u1eadp tin", +"Edit": "S\u1eeda", +"Insert": "Th\u00eam", +"View": "Xem", +"Format": "\u0110\u1ecbnh d\u1ea1ng", +"Table": "B\u1ea3ng", +"Tools": "C\u00f4ng c\u1ee5", +"Powered by {0}": "\u0110\u01b0\u1ee3c cung c\u1ea5p b\u1edfi {0}", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Khu v\u1ef1c so\u1ea1n th\u1ea3o. Nh\u1ea5n ALT-F9 \u0111\u1ec3 hi\u1ec7n menu, ALT-F10 \u0111\u1ec3 hi\u1ec7n thanh c\u00f4ng c\u1ee5. C\u1ea7n tr\u1ee3 gi\u00fap nh\u1ea5n ALT-0" +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/zh.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/zh.js deleted file mode 100644 index 42f7abbc96..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/zh.js +++ /dev/null @@ -1 +0,0 @@ -tinyMCE.addI18n({"zh-cn":{common:{"more_colors":"\u66f4\u591a\u989c\u8272","invalid_data":"\u9519\u8bef\uff1a\u6807\u8bb0\u4e3a\u7ea2\u8272\u7684\u90e8\u5206\u6709\u8bef\u3002","popup_blocked":"\u62b1\u6b49\uff0c\u60a8\u7981\u7528\u4e86\u5f39\u51fa\u7a97\u53e3\u529f\u80fd\u3002\u4e3a\u4e86\u4f7f\u7528\u8be5\u5de5\u5177\u7684\u5168\u90e8\u529f\u80fd\uff0c\u60a8\u9700\u8981\u5141\u8bb8\u5f39\u51fa\u7a97\u53e3\u3002","clipboard_no_support":"\u60a8\u7684\u6d4f\u89c8\u5668\u4e0d\u652f\u6301\uff0c\u4f7f\u7528\u5feb\u6377\u952e\u4ee3\u66ff\u3002","clipboard_msg":"\u5728Mozilla\u548cFirefox\u4e2d\u4e0d\u80fd\u4f7f\u7528\u590d\u5236/\u7c98\u8d34/\u526a\u5207\u3002\u60a8\u8981\u67e5\u770b\u8be5\u95ee\u9898\u66f4\u591a\u7684\u4fe1\u606f\u5417\uff1f","not_set":"-- \u672a\u8bbe\u7f6e --","class_name":"\u7c7b\u522b",browse:"\u6d4f\u89c8",close:"\u5173\u95ed",cancel:"\u53d6\u6d88",update:"\u66f4\u65b0",insert:"\u63d2\u5165",apply:"\u5e94\u7528","edit_confirm":"\u8be5\u6587\u672c\u57df\u662f\u5426\u9700\u8981\u4f7f\u7528\u6240\u89c1\u5373\u6240\u5f97\u6a21\u5f0f\uff1f","invalid_data_number":"{#field} \u5fc5\u987b\u4e3a\u6570\u5b57","invalid_data_min":"{#field} \u5fc5\u987b\u4e3a\u5927\u4e8e {#min} \u7684\u6570\u5b57","invalid_data_size":"{#field} \u5fc5\u987b\u4e3a\u6570\u5b57\u6216\u767e\u5206\u6570",value:"(value)"},contextmenu:{full:"\u4e24\u7aef\u5bf9\u9f50",right:"\u53f3\u5bf9\u9f50",center:"\u5c45\u4e2d",left:"\u5de6\u5bf9\u9f50",align:"\u5bf9\u9f50"},insertdatetime:{"day_short":"\u5468\u65e5,\u5468\u4e00,\u5468\u4e8c,\u5468\u4e09,\u5468\u56db,\u5468\u4e94,\u5468\u516d,\u5468\u65e5","day_long":"\u661f\u671f\u65e5,\u661f\u671f\u4e00,\u661f\u671f\u4e8c,\u661f\u671f\u4e09,\u661f\u671f\u56db,\u661f\u671f\u4e94,\u661f\u671f\u516d,\u661f\u671f\u65e5","months_short":"1\u6708,2\u6708,3\u6708,4\u6708,5\u6708,6\u6708,7\u6708,8\u6708,9\u6708,10\u6708,11\u6708,12\u6708","months_long":"\u4e00\u6708,\u4e8c\u6708,\u4e09\u6708,\u56db\u6708,\u4e94\u6708,\u516d\u6708,\u4e03\u6708,\u516b\u6708,\u4e5d\u6708,\u5341\u6708,\u5341\u4e00\u6708,\u5341\u4e8c\u6708","inserttime_desc":"\u63d2\u5165\u65f6\u95f4","insertdate_desc":"\u63d2\u5165\u65e5\u671f","time_fmt":"%H:%M:%S","date_fmt":"%Y-%m-%d"},print:{"print_desc":"\u6253\u5370"},preview:{"preview_desc":"\u9884\u89c8"},directionality:{"rtl_desc":"\u6587\u5b57\u65b9\u5411\u4e3a\u4ece\u53f3\u5230\u5de6","ltr_desc":"\u6587\u5b57\u65b9\u5411\u4e3a\u4ece\u5de6\u5230\u53f3"},layer:{content:"\u65b0\u5efa\u5c42...","absolute_desc":"\u5207\u6362\u5230\u7edd\u5bf9\u4f4d\u7f6e","backward_desc":"\u7f6e\u540e","forward_desc":"\u7f6e\u524d","insertlayer_desc":"\u63d2\u5165\u65b0\u5c42"},save:{"save_desc":"\u4fdd\u5b58","cancel_desc":"\u53d6\u6d88\u66f4\u6539"},nonbreaking:{"nonbreaking_desc":"\u63d2\u5165\u4e0d\u95f4\u65ad\u7a7a\u683c\u7b26"},iespell:{download:"\u62fc\u5199\u68c0\u67e5\u672a\u5b89\u88c5\uff0c\u662f\u5426\u9a6c\u4e0a\u5b89\u88c5\uff1f","iespell_desc":"\u62fc\u5199\u68c0\u67e5"},advhr:{"delta_height":"\u9ad8\u5ea6","delta_width":"\u5bbd\u5ea6","advhr_desc":"\u6c34\u5e73\u7ebf"},emotions:{"emotions_desc":"\u8868\u60c5","delta_height":"","delta_width":""},searchreplace:{"replace_desc":"\u67e5\u627e/\u66ff\u6362","search_desc":"\u67e5\u627e","delta_width":"","delta_height":""},advimage:{"image_desc":"\u63d2\u5165/\u7f16\u8f91 \u56fe\u7247","delta_width":"","delta_height":""},advlink:{"link_desc":"\u63d2\u5165/\u7f16\u8f91 \u8d85\u94fe\u63a5","delta_height":"","delta_width":""},xhtmlxtras:{"attribs_desc":"\u63d2\u5165/\u7f16\u8f91\u5c5e\u6027","ins_desc":"\u63d2\u5165","del_desc":"\u5220\u9664","acronym_desc":"\u9996\u5b57\u6bcd\u7f29\u5199","abbr_desc":"\u7f29\u5199","cite_desc":"\u5f15\u7528","attribs_delta_height":"","attribs_delta_width":"","ins_delta_height":"","ins_delta_width":"","del_delta_height":"","del_delta_width":"","acronym_delta_height":"","acronym_delta_width":"","abbr_delta_height":"","abbr_delta_width":"","cite_delta_height":"","cite_delta_width":""},style:{desc:"\u7f16\u8f91CSS\u6837\u5f0f","delta_height":"","delta_width":""},paste:{"plaintext_mode":"\u5f53\u524d\u4e3a\u7eaf\u6587\u672c\u6a21\u5f0f\u7c98\u8d34\uff0c\u518d\u6b21\u70b9\u51fb\u8fd4\u56de\u666e\u901a\u7c98\u8d34\u6a21\u5f0f\u3002","plaintext_mode_sticky":"\u5f53\u524d\u4e3a\u7eaf\u6587\u672c\u6a21\u5f0f\u7c98\u8d34\u3002\u518d\u6b21\u70b9\u51fb\u8fd4\u56de\u666e\u901a\u7c98\u8d34\u6a21\u5f0f\uff0c\u5728\u60a8\u7c98\u8d34\u5185\u5bb9\u540e\u5c06\u8fd4\u56de\u666e\u901a\u7c98\u8d34\u6a21\u5f0f\u3002","selectall_desc":"\u5168\u9009","paste_word_desc":"\u4eceWord\u7c98\u8d34","paste_text_desc":"\u4ee5\u7eaf\u6587\u672c\u7c98\u8d34"},"paste_dlg":{"word_title":"\u4f7f\u7528CTRL V\u7c98\u8d34\u6587\u672c\u5230\u7a97\u53e3\u4e2d\u3002","text_linebreaks":"\u4fdd\u7559\u65ad\u884c","text_title":"\u4f7f\u7528CTRL V\u7c98\u8d34\u6587\u672c\u5230\u7a97\u53e3\u4e2d\u3002"},table:{cell:"\u5355\u5143\u683c",col:"\u5217",row:"\u884c",del:"\u5220\u9664\u8868\u683c","copy_row_desc":"\u590d\u5236\u884c","cut_row_desc":"\u526a\u5207\u884c","paste_row_after_desc":"\u5728\u4e0b\u65b9\u7c98\u8d34\u884c","paste_row_before_desc":"\u5728\u4e0a\u65b9\u7c98\u8d34\u884c","props_desc":"\u8868\u683c\u5c5e\u6027","cell_desc":"\u5355\u5143\u683c\u5c5e\u6027","row_desc":"\u884c\u5c5e\u6027","merge_cells_desc":"\u5408\u5e76\u5355\u5143\u683c","split_cells_desc":"\u5206\u5272\u5355\u5143\u683c","delete_col_desc":"\u5220\u9664\u5217","col_after_desc":"\u5728\u53f3\u4fa7\u63d2\u5165\u5217","col_before_desc":"\u5728\u5de6\u4fa7\u63d2\u5165\u5217","delete_row_desc":"\u5220\u9664\u884c","row_after_desc":"\u5728\u4e0b\u65b9\u63d2\u5165\u884c","row_before_desc":"\u5728\u4e0a\u65b9\u63d2\u5165\u884c",desc:"\u63d2\u5165\u65b0\u8868\u683c","merge_cells_delta_height":"","merge_cells_delta_width":"","table_delta_height":"","table_delta_width":"","cellprops_delta_height":"","cellprops_delta_width":"","rowprops_delta_height":"","rowprops_delta_width":""},autosave:{"warning_message":"\u5982\u679c\u6062\u590d\u4fdd\u5b58\u7684\u5185\u5bb9\uff0c\u60a8\u5f53\u524d\u7f16\u8f91\u7684\u6240\u6709\u7684\u5185\u5bb9\u5c06\u4e22\u5931\u3002nn\u60a8\u786e\u5b9a\u8981\u6062\u590d\u4fdd\u5b58\u7684\u5185\u5bb9\u5417\uff1f","restore_content":"\u6062\u590d\u81ea\u52a8\u4fdd\u5b58\u7684\u5185\u5bb9\u3002","unload_msg":"\u5982\u679c\u9000\u51fa\u8be5\u9875\uff0c\u60a8\u6240\u505a\u7684\u66f4\u6539\u5c06\u4e22\u5931\u3002"},fullscreen:{desc:"\u5207\u6362\u5168\u5c4f\u6a21\u5f0f"},media:{edit:"\u7f16\u8f91\u5d4c\u5165\u5f0f\u5a92\u4f53",desc:"\u63d2\u5165/\u7f16\u8f91 \u5d4c\u5165\u5f0f\u5a92\u4f53","delta_height":"","delta_width":""},fullpage:{desc:"\u6587\u4ef6\u5c5e\u6027","delta_width":"\u5bbd\u5ea6","delta_height":"\u9ad8\u5ea6"},template:{desc:"\u63d2\u5165\u9884\u8bbe\u7684\u6a21\u677f\u5185\u5bb9"},visualchars:{desc:"\u663e\u793a/\u9690\u85cf \u975e\u53ef\u89c1\u5b57\u7b26"},spellchecker:{desc:"\u62fc\u5199\u68c0\u67e5",menu:"\u62fc\u5199\u68c0\u67e5\u8bbe\u7f6e","ignore_word":"\u5ffd\u7565","ignore_words":"\u5168\u90e8\u5ffd\u7565",langs:"\u8bed\u8a00",wait:"\u8bf7\u7a0d\u5019...",sug:"\u5efa\u8bae","no_sug":"\u65e0\u5efa\u8bae","no_mpell":"\u65e0\u62fc\u5199\u9519\u8bef","learn_word":"\u5b66\u4e60\u8bcd\u7ec4"},pagebreak:{desc:"\u63d2\u5165\u5206\u9875\u7b26"},advlist:{types:"\u6837\u5f0f",def:"\u9ed8\u8ba4","lower_alpha":"\u5c0f\u5199\u5b57\u6bcd","lower_greek":"\u5c0f\u5199\u5e0c\u814a\u5b57\u6bcd","lower_roman":"\u5c0f\u5199\u7f57\u9a6c\u6570\u5b57","upper_alpha":"\u5927\u5199\u5b57\u6bcd","upper_roman":"\u5927\u5199\u7f57\u9a6c\u6570\u5b57",circle:"\u5706\u5708",disc:"\u5706\u70b9",square:"\u65b9\u5757"},colors:{"333300":"Dark olive","993300":"Burnt orange","000000":"Black","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"},aria:{"rich_text_area":"\u5bcc\u6587\u672c\u57df"},wordcount:{words:"\u5b57\u6570:"}}}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/zh_CN.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/zh_CN.js new file mode 100644 index 0000000000..0f3cf923ef --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/zh_CN.js @@ -0,0 +1,261 @@ +tinymce.addI18n('zh_CN',{ +"Redo": "\u91cd\u590d", +"Undo": "\u64a4\u6d88", +"Cut": "\u526a\u5207", +"Copy": "\u590d\u5236", +"Paste": "\u7c98\u8d34", +"Select all": "\u5168\u9009", +"New document": "\u65b0\u6587\u6863", +"Ok": "\u786e\u5b9a", +"Cancel": "\u53d6\u6d88", +"Visual aids": "\u7f51\u683c\u7ebf", +"Bold": "\u7c97\u4f53", +"Italic": "\u659c\u4f53", +"Underline": "\u4e0b\u5212\u7ebf", +"Strikethrough": "\u5220\u9664\u7ebf", +"Superscript": "\u4e0a\u6807", +"Subscript": "\u4e0b\u6807", +"Clear formatting": "\u6e05\u9664\u683c\u5f0f", +"Align left": "\u5de6\u5bf9\u9f50", +"Align center": "\u5c45\u4e2d", +"Align right": "\u53f3\u5bf9\u9f50", +"Justify": "\u4e24\u7aef\u5bf9\u9f50", +"Bullet list": "\u9879\u76ee\u7b26\u53f7", +"Numbered list": "\u7f16\u53f7\u5217\u8868", +"Decrease indent": "\u51cf\u5c11\u7f29\u8fdb", +"Increase indent": "\u589e\u52a0\u7f29\u8fdb", +"Close": "\u5173\u95ed", +"Formats": "\u683c\u5f0f", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "\u4f60\u7684\u6d4f\u89c8\u5668\u4e0d\u652f\u6301\u5bf9\u526a\u8d34\u677f\u7684\u8bbf\u95ee\uff0c\u8bf7\u4f7f\u7528Ctrl+X\/C\/V\u952e\u8fdb\u884c\u590d\u5236\u7c98\u8d34\u3002", +"Headers": "\u6807\u9898", +"Header 1": "\u6807\u98981", +"Header 2": "\u6807\u98982", +"Header 3": "\u6807\u98983", +"Header 4": "\u6807\u98984", +"Header 5": "\u6807\u98985", +"Header 6": "\u6807\u98986", +"Headings": "\u6807\u9898", +"Heading 1": "\u6807\u98981", +"Heading 2": "\u6807\u98982", +"Heading 3": "\u6807\u98983", +"Heading 4": "\u6807\u98984", +"Heading 5": "\u6807\u98985", +"Heading 6": "\u6807\u98986", +"Preformatted": "\u9884\u683c\u5f0f\u5316", +"Div": "Div\u533a\u5757", +"Pre": "\u9884\u683c\u5f0f\u6587\u672c", +"Code": "\u4ee3\u7801", +"Paragraph": "\u6bb5\u843d", +"Blockquote": "\u5f15\u7528", +"Inline": "\u6587\u672c", +"Blocks": "\u533a\u5757", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "\u5f53\u524d\u4e3a\u7eaf\u6587\u672c\u7c98\u8d34\u6a21\u5f0f\uff0c\u518d\u6b21\u70b9\u51fb\u53ef\u4ee5\u56de\u5230\u666e\u901a\u7c98\u8d34\u6a21\u5f0f\u3002", +"Font Family": "\u5b57\u4f53", +"Font Sizes": "\u5b57\u53f7", +"Class": "Class", +"Browse for an image": "\u6d4f\u89c8\u56fe\u50cf", +"OR": "\u6216", +"Drop an image here": "\u62d6\u653e\u4e00\u5f20\u56fe\u50cf\u81f3\u6b64", +"Upload": "\u4e0a\u4f20", +"Block": "\u5757", +"Align": "\u5bf9\u9f50", +"Default": "\u9ed8\u8ba4", +"Circle": "\u7a7a\u5fc3\u5706", +"Disc": "\u5b9e\u5fc3\u5706", +"Square": "\u65b9\u5757", +"Lower Alpha": "\u5c0f\u5199\u82f1\u6587\u5b57\u6bcd", +"Lower Greek": "\u5c0f\u5199\u5e0c\u814a\u5b57\u6bcd", +"Lower Roman": "\u5c0f\u5199\u7f57\u9a6c\u5b57\u6bcd", +"Upper Alpha": "\u5927\u5199\u82f1\u6587\u5b57\u6bcd", +"Upper Roman": "\u5927\u5199\u7f57\u9a6c\u5b57\u6bcd", +"Anchor": "\u951a\u70b9", +"Name": "\u540d\u79f0", +"Id": "\u6807\u8bc6\u7b26", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "\u6807\u8bc6\u7b26\u5e94\u8be5\u4ee5\u5b57\u6bcd\u5f00\u5934\uff0c\u540e\u8ddf\u5b57\u6bcd\u3001\u6570\u5b57\u3001\u7834\u6298\u53f7\u3001\u70b9\u3001\u5192\u53f7\u6216\u4e0b\u5212\u7ebf\u3002", +"You have unsaved changes are you sure you want to navigate away?": "\u4f60\u8fd8\u6709\u6587\u6863\u5c1a\u672a\u4fdd\u5b58\uff0c\u786e\u5b9a\u8981\u79bb\u5f00\uff1f", +"Restore last draft": "\u6062\u590d\u4e0a\u6b21\u7684\u8349\u7a3f", +"Special character": "\u7279\u6b8a\u7b26\u53f7", +"Source code": "\u6e90\u4ee3\u7801", +"Insert\/Edit code sample": "\u63d2\u5165\/\u7f16\u8f91\u4ee3\u7801\u793a\u4f8b", +"Language": "\u8bed\u8a00", +"Code sample": "\u4ee3\u7801\u793a\u4f8b", +"Color": "\u989c\u8272", +"R": "R", +"G": "G", +"B": "B", +"Left to right": "\u4ece\u5de6\u5230\u53f3", +"Right to left": "\u4ece\u53f3\u5230\u5de6", +"Emoticons": "\u8868\u60c5", +"Document properties": "\u6587\u6863\u5c5e\u6027", +"Title": "\u6807\u9898", +"Keywords": "\u5173\u952e\u8bcd", +"Description": "\u63cf\u8ff0", +"Robots": "\u673a\u5668\u4eba", +"Author": "\u4f5c\u8005", +"Encoding": "\u7f16\u7801", +"Fullscreen": "\u5168\u5c4f", +"Action": "\u64cd\u4f5c", +"Shortcut": "\u5feb\u6377\u952e", +"Help": "\u5e2e\u52a9", +"Address": "\u5730\u5740", +"Focus to menubar": "\u79fb\u52a8\u7126\u70b9\u5230\u83dc\u5355\u680f", +"Focus to toolbar": "\u79fb\u52a8\u7126\u70b9\u5230\u5de5\u5177\u680f", +"Focus to element path": "\u79fb\u52a8\u7126\u70b9\u5230\u5143\u7d20\u8def\u5f84", +"Focus to contextual toolbar": "\u79fb\u52a8\u7126\u70b9\u5230\u4e0a\u4e0b\u6587\u83dc\u5355", +"Insert link (if link plugin activated)": "\u63d2\u5165\u94fe\u63a5 (\u5982\u679c\u94fe\u63a5\u63d2\u4ef6\u5df2\u6fc0\u6d3b)", +"Save (if save plugin activated)": "\u4fdd\u5b58(\u5982\u679c\u4fdd\u5b58\u63d2\u4ef6\u5df2\u6fc0\u6d3b)", +"Find (if searchreplace plugin activated)": "\u67e5\u627e(\u5982\u679c\u67e5\u627e\u66ff\u6362\u63d2\u4ef6\u5df2\u6fc0\u6d3b)", +"Plugins installed ({0}):": "\u5df2\u5b89\u88c5\u63d2\u4ef6 ({0}):", +"Premium plugins:": "\u4f18\u79c0\u63d2\u4ef6\uff1a", +"Learn more...": "\u4e86\u89e3\u66f4\u591a...", +"You are using {0}": "\u4f60\u6b63\u5728\u4f7f\u7528 {0}", +"Plugins": "\u63d2\u4ef6", +"Handy Shortcuts": "\u5feb\u6377\u952e", +"Horizontal line": "\u6c34\u5e73\u5206\u5272\u7ebf", +"Insert\/edit image": "\u63d2\u5165\/\u7f16\u8f91\u56fe\u7247", +"Image description": "\u56fe\u7247\u63cf\u8ff0", +"Source": "\u5730\u5740", +"Dimensions": "\u5927\u5c0f", +"Constrain proportions": "\u4fdd\u6301\u7eb5\u6a2a\u6bd4", +"General": "\u666e\u901a", +"Advanced": "\u9ad8\u7ea7", +"Style": "\u6837\u5f0f", +"Vertical space": "\u5782\u76f4\u8fb9\u8ddd", +"Horizontal space": "\u6c34\u5e73\u8fb9\u8ddd", +"Border": "\u8fb9\u6846", +"Insert image": "\u63d2\u5165\u56fe\u7247", +"Image": "\u56fe\u7247", +"Image list": "\u56fe\u7247\u5217\u8868", +"Rotate counterclockwise": "\u9006\u65f6\u9488\u65cb\u8f6c", +"Rotate clockwise": "\u987a\u65f6\u9488\u65cb\u8f6c", +"Flip vertically": "\u5782\u76f4\u7ffb\u8f6c", +"Flip horizontally": "\u6c34\u5e73\u7ffb\u8f6c", +"Edit image": "\u7f16\u8f91\u56fe\u7247", +"Image options": "\u56fe\u7247\u9009\u9879", +"Zoom in": "\u653e\u5927", +"Zoom out": "\u7f29\u5c0f", +"Crop": "\u88c1\u526a", +"Resize": "\u8c03\u6574\u5927\u5c0f", +"Orientation": "\u65b9\u5411", +"Brightness": "\u4eae\u5ea6", +"Sharpen": "\u9510\u5316", +"Contrast": "\u5bf9\u6bd4\u5ea6", +"Color levels": "\u989c\u8272\u5c42\u6b21", +"Gamma": "\u4f3d\u9a6c\u503c", +"Invert": "\u53cd\u8f6c", +"Apply": "\u5e94\u7528", +"Back": "\u540e\u9000", +"Insert date\/time": "\u63d2\u5165\u65e5\u671f\/\u65f6\u95f4", +"Date\/time": "\u65e5\u671f\/\u65f6\u95f4", +"Insert link": "\u63d2\u5165\u94fe\u63a5", +"Insert\/edit link": "\u63d2\u5165\/\u7f16\u8f91\u94fe\u63a5", +"Text to display": "\u663e\u793a\u6587\u5b57", +"Url": "\u5730\u5740", +"Target": "\u6253\u5f00\u65b9\u5f0f", +"None": "\u65e0", +"New window": "\u5728\u65b0\u7a97\u53e3\u6253\u5f00", +"Remove link": "\u5220\u9664\u94fe\u63a5", +"Anchors": "\u951a\u70b9", +"Link": "\u94fe\u63a5", +"Paste or type a link": "\u7c98\u8d34\u6216\u8f93\u5165\u94fe\u63a5", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "\u4f60\u6240\u586b\u5199\u7684URL\u5730\u5740\u4e3a\u90ae\u4ef6\u5730\u5740\uff0c\u9700\u8981\u52a0\u4e0amailto:\u524d\u7f00\u5417\uff1f", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "\u4f60\u6240\u586b\u5199\u7684URL\u5730\u5740\u5c5e\u4e8e\u5916\u90e8\u94fe\u63a5\uff0c\u9700\u8981\u52a0\u4e0ahttp:\/\/:\u524d\u7f00\u5417\uff1f", +"Link list": "\u94fe\u63a5\u5217\u8868", +"Insert video": "\u63d2\u5165\u89c6\u9891", +"Insert\/edit video": "\u63d2\u5165\/\u7f16\u8f91\u89c6\u9891", +"Insert\/edit media": "\u63d2\u5165\/\u7f16\u8f91\u5a92\u4f53", +"Alternative source": "\u955c\u50cf", +"Poster": "\u5c01\u9762", +"Paste your embed code below:": "\u5c06\u5185\u5d4c\u4ee3\u7801\u7c98\u8d34\u5728\u4e0b\u9762:", +"Embed": "\u5185\u5d4c", +"Media": "\u5a92\u4f53", +"Nonbreaking space": "\u4e0d\u95f4\u65ad\u7a7a\u683c", +"Page break": "\u5206\u9875\u7b26", +"Paste as text": "\u7c98\u8d34\u4e3a\u6587\u672c", +"Preview": "\u9884\u89c8", +"Print": "\u6253\u5370", +"Save": "\u4fdd\u5b58", +"Find": "\u67e5\u627e", +"Replace with": "\u66ff\u6362\u4e3a", +"Replace": "\u66ff\u6362", +"Replace all": "\u5168\u90e8\u66ff\u6362", +"Prev": "\u4e0a\u4e00\u4e2a", +"Next": "\u4e0b\u4e00\u4e2a", +"Find and replace": "\u67e5\u627e\u548c\u66ff\u6362", +"Could not find the specified string.": "\u672a\u627e\u5230\u641c\u7d22\u5185\u5bb9.", +"Match case": "\u533a\u5206\u5927\u5c0f\u5199", +"Whole words": "\u5168\u5b57\u5339\u914d", +"Spellcheck": "\u62fc\u5199\u68c0\u67e5", +"Ignore": "\u5ffd\u7565", +"Ignore all": "\u5168\u90e8\u5ffd\u7565", +"Finish": "\u5b8c\u6210", +"Add to Dictionary": "\u6dfb\u52a0\u5230\u5b57\u5178", +"Insert table": "\u63d2\u5165\u8868\u683c", +"Table properties": "\u8868\u683c\u5c5e\u6027", +"Delete table": "\u5220\u9664\u8868\u683c", +"Cell": "\u5355\u5143\u683c", +"Row": "\u884c", +"Column": "\u5217", +"Cell properties": "\u5355\u5143\u683c\u5c5e\u6027", +"Merge cells": "\u5408\u5e76\u5355\u5143\u683c", +"Split cell": "\u62c6\u5206\u5355\u5143\u683c", +"Insert row before": "\u5728\u4e0a\u65b9\u63d2\u5165", +"Insert row after": "\u5728\u4e0b\u65b9\u63d2\u5165", +"Delete row": "\u5220\u9664\u884c", +"Row properties": "\u884c\u5c5e\u6027", +"Cut row": "\u526a\u5207\u884c", +"Copy row": "\u590d\u5236\u884c", +"Paste row before": "\u7c98\u8d34\u5230\u4e0a\u65b9", +"Paste row after": "\u7c98\u8d34\u5230\u4e0b\u65b9", +"Insert column before": "\u5728\u5de6\u4fa7\u63d2\u5165", +"Insert column after": "\u5728\u53f3\u4fa7\u63d2\u5165", +"Delete column": "\u5220\u9664\u5217", +"Cols": "\u5217", +"Rows": "\u884c", +"Width": "\u5bbd", +"Height": "\u9ad8", +"Cell spacing": "\u5355\u5143\u683c\u5916\u95f4\u8ddd", +"Cell padding": "\u5355\u5143\u683c\u5185\u8fb9\u8ddd", +"Caption": "\u6807\u9898", +"Left": "\u5de6\u5bf9\u9f50", +"Center": "\u5c45\u4e2d", +"Right": "\u53f3\u5bf9\u9f50", +"Cell type": "\u5355\u5143\u683c\u7c7b\u578b", +"Scope": "\u8303\u56f4", +"Alignment": "\u5bf9\u9f50\u65b9\u5f0f", +"H Align": "\u6c34\u5e73\u5bf9\u9f50", +"V Align": "\u5782\u76f4\u5bf9\u9f50", +"Top": "\u9876\u90e8\u5bf9\u9f50", +"Middle": "\u5782\u76f4\u5c45\u4e2d", +"Bottom": "\u5e95\u90e8\u5bf9\u9f50", +"Header cell": "\u8868\u5934\u5355\u5143\u683c", +"Row group": "\u884c\u7ec4", +"Column group": "\u5217\u7ec4", +"Row type": "\u884c\u7c7b\u578b", +"Header": "\u8868\u5934", +"Body": "\u8868\u4f53", +"Footer": "\u8868\u5c3e", +"Border color": "\u8fb9\u6846\u989c\u8272", +"Insert template": "\u63d2\u5165\u6a21\u677f", +"Templates": "\u6a21\u677f", +"Template": "\u6a21\u677f", +"Text color": "\u6587\u5b57\u989c\u8272", +"Background color": "\u80cc\u666f\u8272", +"Custom...": "\u81ea\u5b9a\u4e49...", +"Custom color": "\u81ea\u5b9a\u4e49\u989c\u8272", +"No color": "\u65e0", +"Table of Contents": "\u5185\u5bb9\u5217\u8868", +"Show blocks": "\u663e\u793a\u533a\u5757\u8fb9\u6846", +"Show invisible characters": "\u663e\u793a\u4e0d\u53ef\u89c1\u5b57\u7b26", +"Words: {0}": "\u5b57\u6570\uff1a{0}", +"{0} words": "{0} \u5b57", +"File": "\u6587\u4ef6", +"Edit": "\u7f16\u8f91", +"Insert": "\u63d2\u5165", +"View": "\u89c6\u56fe", +"Format": "\u683c\u5f0f", +"Table": "\u8868\u683c", +"Tools": "\u5de5\u5177", +"Powered by {0}": "\u7531{0}\u9a71\u52a8", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "\u5728\u7f16\u8f91\u533a\u6309ALT-F9\u6253\u5f00\u83dc\u5355\uff0c\u6309ALT-F10\u6253\u5f00\u5de5\u5177\u680f\uff0c\u6309ALT-0\u67e5\u770b\u5e2e\u52a9" +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/zh_TW.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/zh_TW.js new file mode 100644 index 0000000000..cc5f157949 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/zh_TW.js @@ -0,0 +1,261 @@ +tinymce.addI18n('zh_TW',{ +"Redo": "\u53d6\u6d88\u5fa9\u539f", +"Undo": "\u5fa9\u539f", +"Cut": "\u526a\u4e0b", +"Copy": "\u8907\u88fd", +"Paste": "\u8cbc\u4e0a", +"Select all": "\u5168\u9078", +"New document": "\u65b0\u6587\u4ef6", +"Ok": "\u78ba\u5b9a", +"Cancel": "\u53d6\u6d88", +"Visual aids": "\u5c0f\u5e6b\u624b", +"Bold": "\u7c97\u9ad4", +"Italic": "\u659c\u9ad4", +"Underline": "\u5e95\u7dda", +"Strikethrough": "\u522a\u9664\u7dda", +"Superscript": "\u4e0a\u6a19", +"Subscript": "\u4e0b\u6a19", +"Clear formatting": "\u6e05\u9664\u683c\u5f0f", +"Align left": "\u7f6e\u5de6\u5c0d\u9f4a", +"Align center": "\u7f6e\u4e2d\u5c0d\u9f4a", +"Align right": "\u7f6e\u53f3\u5c0d\u9f4a", +"Justify": "\u5de6\u53f3\u5c0d\u9f4a", +"Bullet list": "\u9805\u76ee\u6e05\u55ae", +"Numbered list": "\u6578\u5b57\u6e05\u55ae", +"Decrease indent": "\u6e1b\u5c11\u7e2e\u6392", +"Increase indent": "\u589e\u52a0\u7e2e\u6392", +"Close": "\u95dc\u9589", +"Formats": "\u683c\u5f0f", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "\u60a8\u7684\u700f\u89bd\u5668\u4e0d\u652f\u63f4\u5b58\u53d6\u526a\u8cbc\u7c3f\uff0c\u53ef\u4ee5\u4f7f\u7528\u5feb\u901f\u9375 Ctrl + X\/C\/V \u4ee3\u66ff\u526a\u4e0b\u3001\u8907\u88fd\u8207\u8cbc\u4e0a\u3002", +"Headers": "\u6a19\u984c", +"Header 1": "\u6a19\u984c 1", +"Header 2": "\u6a19\u984c 2", +"Header 3": "\u6a19\u984c 3", +"Header 4": "\u6a19\u984c 4", +"Header 5": "\u6a19\u984c 5", +"Header 6": "\u6a19\u984c 6", +"Headings": "\u6a19\u984c", +"Heading 1": "\u6a19\u984c 1", +"Heading 2": "\u6a19\u984c 2", +"Heading 3": "\u6a19\u984c 3", +"Heading 4": "\u6a19\u984c 4", +"Heading 5": "\u6a19\u984c 5", +"Heading 6": "\u6a19\u984c 6", +"Preformatted": "\u9810\u5148\u6392\u7248", +"Div": "Div", +"Pre": "Pre", +"Code": "\u7a0b\u5f0f\u78bc", +"Paragraph": "\u6bb5\u843d", +"Blockquote": "\u5f15\u7528", +"Inline": "Inline", +"Blocks": "\u5340\u584a", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "\u76ee\u524d\u5c07\u4ee5\u7d14\u6587\u5b57\u7684\u6a21\u5f0f\u8cbc\u4e0a\uff0c\u60a8\u53ef\u4ee5\u518d\u9ede\u9078\u4e00\u6b21\u53d6\u6d88\u3002", +"Font Family": "\u5b57\u9ad4", +"Font Sizes": "\u5b57\u578b\u5927\u5c0f", +"Class": "\u985e\u5225", +"Browse for an image": "\u5f9e\u5716\u7247\u4e2d\u700f\u89bd", +"OR": "\u6216", +"Drop an image here": "\u62d6\u66f3\u5716\u7247\u81f3\u6b64", +"Upload": "\u4e0a\u50b3", +"Block": "\u5340\u584a", +"Align": "\u5c0d\u9f4a", +"Default": "\u9810\u8a2d", +"Circle": "\u7a7a\u5fc3\u5713", +"Disc": "\u5be6\u5fc3\u5713", +"Square": "\u6b63\u65b9\u5f62", +"Lower Alpha": "\u5c0f\u5beb\u82f1\u6587\u5b57\u6bcd", +"Lower Greek": "\u5e0c\u81d8\u5b57\u6bcd", +"Lower Roman": "\u5c0f\u5beb\u7f85\u99ac\u6578\u5b57", +"Upper Alpha": "\u5927\u5beb\u82f1\u6587\u5b57\u6bcd", +"Upper Roman": "\u5927\u5beb\u7f85\u99ac\u6578\u5b57", +"Anchor": "\u52a0\u5165\u9328\u9ede", +"Name": "\u540d\u7a31", +"Id": "Id", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "Id\u61c9\u4ee5\u5b57\u6bcd\u958b\u982d\uff0c\u5f8c\u9762\u63a5\u8457\u5b57\u6bcd\uff0c\u6578\u5b57\uff0c\u7834\u6298\u865f\uff0c\u9ede\u6578\uff0c\u5192\u865f\u6216\u4e0b\u5283\u7dda\u3002", +"You have unsaved changes are you sure you want to navigate away?": "\u7de8\u8f2f\u5c1a\u672a\u88ab\u5132\u5b58\uff0c\u4f60\u78ba\u5b9a\u8981\u96e2\u958b\uff1f", +"Restore last draft": "\u8f09\u5165\u4e0a\u4e00\u6b21\u7de8\u8f2f\u7684\u8349\u7a3f", +"Special character": "\u7279\u6b8a\u5b57\u5143", +"Source code": "\u539f\u59cb\u78bc", +"Insert\/Edit code sample": "\u63d2\u5165\/\u7de8\u8f2f \u7a0b\u5f0f\u78bc\u7bc4\u4f8b", +"Language": "\u8a9e\u8a00", +"Code sample": "\u7a0b\u5f0f\u78bc\u7bc4\u4f8b", +"Color": "\u984f\u8272", +"R": "\u7d05", +"G": "\u7da0", +"B": "\u85cd", +"Left to right": "\u5f9e\u5de6\u5230\u53f3", +"Right to left": "\u5f9e\u53f3\u5230\u5de6", +"Emoticons": "\u8868\u60c5", +"Document properties": "\u6587\u4ef6\u7684\u5c6c\u6027", +"Title": "\u6a19\u984c", +"Keywords": "\u95dc\u9375\u5b57", +"Description": "\u63cf\u8ff0", +"Robots": "\u6a5f\u5668\u4eba", +"Author": "\u4f5c\u8005", +"Encoding": "\u7de8\u78bc", +"Fullscreen": "\u5168\u87a2\u5e55", +"Action": "\u52d5\u4f5c", +"Shortcut": "\u5feb\u901f\u9375", +"Help": "\u5e6b\u52a9", +"Address": "\u5730\u5740", +"Focus to menubar": "\u8df3\u81f3\u9078\u55ae\u5217", +"Focus to toolbar": "\u8df3\u81f3\u5de5\u5177\u5217", +"Focus to element path": "\u8df3\u81f3HTML\u5143\u7d20\u5217", +"Focus to contextual toolbar": "\u8df3\u81f3\u5feb\u6377\u9078\u55ae", +"Insert link (if link plugin activated)": "\u65b0\u589e\u6377\u5f91 (\u6377\u5f91\u5916\u639b\u555f\u7528\u6642)", +"Save (if save plugin activated)": "\u5132\u5b58 (\u5132\u5b58\u5916\u639b\u555f\u7528\u6642)", +"Find (if searchreplace plugin activated)": "\u5c0b\u627e (\u5c0b\u627e\u53d6\u4ee3\u5916\u639b\u555f\u7528\u6642)", +"Plugins installed ({0}):": "({0}) \u500b\u5916\u639b\u5df2\u5b89\u88dd\uff1a", +"Premium plugins:": "\u52a0\u503c\u5916\u639b\uff1a", +"Learn more...": "\u4e86\u89e3\u66f4\u591a...", +"You are using {0}": "\u60a8\u6b63\u5728\u4f7f\u7528 {0}", +"Plugins": "\u5916\u639b", +"Handy Shortcuts": "\u5feb\u901f\u9375", +"Horizontal line": "\u6c34\u5e73\u7dda", +"Insert\/edit image": "\u63d2\u5165\/\u7de8\u8f2f \u5716\u7247", +"Image description": "\u5716\u7247\u63cf\u8ff0", +"Source": "\u5716\u7247\u7db2\u5740", +"Dimensions": "\u5c3a\u5bf8", +"Constrain proportions": "\u7b49\u6bd4\u4f8b\u7e2e\u653e", +"General": "\u4e00\u822c", +"Advanced": "\u9032\u968e", +"Style": "\u6a23\u5f0f", +"Vertical space": "\u9ad8\u5ea6", +"Horizontal space": "\u5bec\u5ea6", +"Border": "\u908a\u6846", +"Insert image": "\u63d2\u5165\u5716\u7247", +"Image": "\u5716\u7247", +"Image list": "\u5716\u7247\u6e05\u55ae", +"Rotate counterclockwise": "\u9006\u6642\u91dd\u65cb\u8f49", +"Rotate clockwise": "\u9806\u6642\u91dd\u65cb\u8f49", +"Flip vertically": "\u5782\u76f4\u7ffb\u8f49", +"Flip horizontally": "\u6c34\u5e73\u7ffb\u8f49", +"Edit image": "\u7de8\u8f2f\u5716\u7247", +"Image options": "\u5716\u7247\u9078\u9805", +"Zoom in": "\u653e\u5927", +"Zoom out": "\u7e2e\u5c0f", +"Crop": "\u88c1\u526a", +"Resize": "\u8abf\u6574\u5927\u5c0f", +"Orientation": "\u65b9\u5411", +"Brightness": "\u4eae\u5ea6", +"Sharpen": "\u92b3\u5316", +"Contrast": "\u5c0d\u6bd4", +"Color levels": "\u984f\u8272\u5c64\u6b21", +"Gamma": "\u4f3d\u99ac\u503c", +"Invert": "\u53cd\u8f49", +"Apply": "\u61c9\u7528", +"Back": "\u5f8c\u9000", +"Insert date\/time": "\u63d2\u5165 \u65e5\u671f\/\u6642\u9593", +"Date\/time": "\u65e5\u671f\/\u6642\u9593", +"Insert link": "\u63d2\u5165\u9023\u7d50", +"Insert\/edit link": "\u63d2\u5165\/\u7de8\u8f2f\u9023\u7d50", +"Text to display": "\u986f\u793a\u6587\u5b57", +"Url": "\u7db2\u5740", +"Target": "\u958b\u555f\u65b9\u5f0f", +"None": "\u7121", +"New window": "\u53e6\u958b\u8996\u7a97", +"Remove link": "\u79fb\u9664\u9023\u7d50", +"Anchors": "\u52a0\u5165\u9328\u9ede", +"Link": "\u9023\u7d50", +"Paste or type a link": "\u8cbc\u4e0a\u6216\u8f38\u5165\u9023\u7d50", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "\u4f60\u6240\u586b\u5beb\u7684URL\u70ba\u96fb\u5b50\u90f5\u4ef6\uff0c\u9700\u8981\u52a0\u4e0amailto:\u524d\u7db4\u55ce\uff1f", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "\u4f60\u6240\u586b\u5beb\u7684URL\u5c6c\u65bc\u5916\u90e8\u93c8\u63a5\uff0c\u9700\u8981\u52a0\u4e0ahttp:\/\/:\u524d\u7db4\u55ce\uff1f", +"Link list": "\u9023\u7d50\u6e05\u55ae", +"Insert video": "\u63d2\u5165\u5f71\u97f3", +"Insert\/edit video": "\u63d2\u4ef6\/\u7de8\u8f2f \u5f71\u97f3", +"Insert\/edit media": "\u63d2\u5165\/\u7de8\u8f2f \u5a92\u9ad4", +"Alternative source": "\u66ff\u4ee3\u5f71\u97f3", +"Poster": "\u9810\u89bd\u5716\u7247", +"Paste your embed code below:": "\u8acb\u5c07\u60a8\u7684\u5d4c\u5165\u5f0f\u7a0b\u5f0f\u78bc\u8cbc\u5728\u4e0b\u9762:", +"Embed": "\u5d4c\u5165\u78bc", +"Media": "\u5a92\u9ad4", +"Nonbreaking space": "\u4e0d\u5206\u884c\u7684\u7a7a\u683c", +"Page break": "\u5206\u9801", +"Paste as text": "\u4ee5\u7d14\u6587\u5b57\u8cbc\u4e0a", +"Preview": "\u9810\u89bd", +"Print": "\u5217\u5370", +"Save": "\u5132\u5b58", +"Find": "\u641c\u5c0b", +"Replace with": "\u66f4\u63db", +"Replace": "\u66ff\u63db", +"Replace all": "\u66ff\u63db\u5168\u90e8", +"Prev": "\u4e0a\u4e00\u500b", +"Next": "\u4e0b\u4e00\u500b", +"Find and replace": "\u5c0b\u627e\u53ca\u53d6\u4ee3", +"Could not find the specified string.": "\u7121\u6cd5\u67e5\u8a62\u5230\u6b64\u7279\u5b9a\u5b57\u4e32", +"Match case": "\u76f8\u5339\u914d\u6848\u4ef6", +"Whole words": "\u6574\u500b\u55ae\u5b57", +"Spellcheck": "\u62fc\u5b57\u6aa2\u67e5", +"Ignore": "\u5ffd\u7565", +"Ignore all": "\u5ffd\u7565\u6240\u6709", +"Finish": "\u5b8c\u6210", +"Add to Dictionary": "\u52a0\u5165\u5b57\u5178\u4e2d", +"Insert table": "\u63d2\u5165\u8868\u683c", +"Table properties": "\u8868\u683c\u5c6c\u6027", +"Delete table": "\u522a\u9664\u8868\u683c", +"Cell": "\u5132\u5b58\u683c", +"Row": "\u5217", +"Column": "\u884c", +"Cell properties": "\u5132\u5b58\u683c\u5c6c\u6027", +"Merge cells": "\u5408\u4f75\u5132\u5b58\u683c", +"Split cell": "\u5206\u5272\u5132\u5b58\u683c", +"Insert row before": "\u63d2\u5165\u5217\u5728...\u4e4b\u524d", +"Insert row after": "\u63d2\u5165\u5217\u5728...\u4e4b\u5f8c", +"Delete row": "\u522a\u9664\u5217", +"Row properties": "\u5217\u5c6c\u6027", +"Cut row": "\u526a\u4e0b\u5217", +"Copy row": "\u8907\u88fd\u5217", +"Paste row before": "\u8cbc\u4e0a\u5217\u5728...\u4e4b\u524d", +"Paste row after": "\u8cbc\u4e0a\u5217\u5728...\u4e4b\u5f8c", +"Insert column before": "\u63d2\u5165\u6b04\u4f4d\u5728...\u4e4b\u524d", +"Insert column after": "\u63d2\u5165\u6b04\u4f4d\u5728...\u4e4b\u5f8c", +"Delete column": "\u522a\u9664\u884c", +"Cols": "\u6b04\u4f4d\u6bb5", +"Rows": "\u5217", +"Width": "\u5bec\u5ea6", +"Height": "\u9ad8\u5ea6", +"Cell spacing": "\u5132\u5b58\u683c\u5f97\u9593\u8ddd", +"Cell padding": "\u5132\u5b58\u683c\u7684\u908a\u8ddd", +"Caption": "\u8868\u683c\u6a19\u984c", +"Left": "\u5de6\u908a", +"Center": "\u4e2d\u9593", +"Right": "\u53f3\u908a", +"Cell type": "\u5132\u5b58\u683c\u7684\u985e\u578b", +"Scope": "\u7bc4\u570d", +"Alignment": "\u5c0d\u9f4a", +"H Align": "\u6c34\u5e73\u4f4d\u7f6e", +"V Align": "\u5782\u76f4\u4f4d\u7f6e", +"Top": "\u7f6e\u9802", +"Middle": "\u7f6e\u4e2d", +"Bottom": "\u7f6e\u5e95", +"Header cell": "\u6a19\u982d\u5132\u5b58\u683c", +"Row group": "\u5217\u7fa4\u7d44", +"Column group": "\u6b04\u4f4d\u7fa4\u7d44", +"Row type": "\u884c\u7684\u985e\u578b", +"Header": "\u6a19\u982d", +"Body": "\u4e3b\u9ad4", +"Footer": "\u9801\u5c3e", +"Border color": "\u908a\u6846\u984f\u8272", +"Insert template": "\u63d2\u5165\u6a23\u7248", +"Templates": "\u6a23\u7248", +"Template": "\u6a23\u677f", +"Text color": "\u6587\u5b57\u984f\u8272", +"Background color": "\u80cc\u666f\u984f\u8272", +"Custom...": "\u81ea\u8a02", +"Custom color": "\u81ea\u8a02\u984f\u8272", +"No color": "No color", +"Table of Contents": "\u76ee\u9304", +"Show blocks": "\u986f\u793a\u5340\u584a\u8cc7\u8a0a", +"Show invisible characters": "\u986f\u793a\u96b1\u85cf\u5b57\u5143", +"Words: {0}": "\u5b57\u6578\uff1a{0}", +"{0} words": "{0} \u5b57\u5143", +"File": "\u6a94\u6848", +"Edit": "\u7de8\u8f2f", +"Insert": "\u63d2\u5165", +"View": "\u6aa2\u8996", +"Format": "\u683c\u5f0f", +"Table": "\u8868\u683c", +"Tools": "\u5de5\u5177", +"Powered by {0}": "\u7531 {0} \u63d0\u4f9b", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "\u8c50\u5bcc\u7684\u6587\u672c\u5340\u57df\u3002\u6309ALT-F9\u524d\u5f80\u4e3b\u9078\u55ae\u3002\u6309ALT-F10\u547c\u53eb\u5de5\u5177\u6b04\u3002\u6309ALT-0\u5c0b\u6c42\u5e6b\u52a9" +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index d69ef62f16..b8087066c9 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -876,9 +876,9 @@ }, "dependencies": { "acorn": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", - "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", + "version": "5.7.4", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", + "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", "dev": true }, "source-map": { @@ -1035,9 +1035,9 @@ "integrity": "sha512-M1JtZctO2Zg+1qeGUFZXtYKsyaRptqQtqpVzlj80I0NzGW9MF3um0DBuizIvQlrPYUlTdm+wcOPZpZoerkxQdA==" }, "acorn": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.0.tgz", - "integrity": "sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz", + "integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==", "dev": true }, "acorn-jsx": { @@ -1099,9 +1099,9 @@ "dev": true }, "angular": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/angular/-/angular-1.7.5.tgz", - "integrity": "sha512-760183yxtGzni740IBTieNuWLtPNAoMqvmC0Z62UoU0I3nqk+VJuO3JbQAXOyvo3Oy/ZsdNQwrSTh/B0OQZjNw==" + "version": "1.7.9", + "resolved": "https://registry.npmjs.org/angular/-/angular-1.7.9.tgz", + "integrity": "sha512-5se7ZpcOtu0MBFlzGv5dsM1quQDoDeUTwZrWjGtTNA7O88cD8TEk5IEKCTDa3uECV9XnvKREVUr7du1ACiWGFQ==" }, "angular-animate": { "version": "1.7.5", @@ -1109,9 +1109,9 @@ "integrity": "sha512-kU/fHIGf2a4a3bH7E1tzALTHk+QfoUSCK9fEcMFisd6ZWvNDwPzXWAilItqOC3EDiAXPmGHaNc9/aXiD9xrAxQ==" }, "angular-aria": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/angular-aria/-/angular-aria-1.7.5.tgz", - "integrity": "sha512-X2dGRw+PK7hrV7/X1Ns4e5P3KC/OBFi1l7z//D/v7zbZObsAx48qBoX7unsck+s4+mnO+ikNNkHG5N49VfAyRw==" + "version": "1.7.9", + "resolved": "https://registry.npmjs.org/angular-aria/-/angular-aria-1.7.9.tgz", + "integrity": "sha512-luI3Jemd1AbOQW0krdzfEG3fM0IFtLY0bSSqIDEx3POE0XjKIC1MkrO8Csyq9PPgueLphyAPofzUwZ8YeZ88SA==" }, "angular-chart.js": { "version": "1.1.1", @@ -1198,7 +1198,7 @@ }, "ansi-colors": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", + "resolved": "http://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", "dev": true, "requires": { @@ -1279,7 +1279,7 @@ "anymatch": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", - "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==", + "integrity": "sha1-VT3Lj5HjyImEXf26NMd3IbkLnXo=", "dev": true, "requires": { "micromatch": "^2.1.5", @@ -1427,7 +1427,7 @@ "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "integrity": "sha1-vNZ5HqWuCXJeF+WtmIE0zUCz2RE=", "dev": true, "requires": { "sprintf-js": "~1.0.2" @@ -1451,7 +1451,7 @@ "arr-flatten": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "integrity": "sha1-NgSLv/TntH4TZkQxbJlmnqWukfE=", "dev": true }, "arr-map": { @@ -1526,7 +1526,7 @@ "array-slice": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", - "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", + "integrity": "sha1-42jqFfibxwaff/uJrsOmx9SsItQ=", "dev": true }, "array-sort": { @@ -1569,7 +1569,7 @@ "arraybuffer.slice": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", - "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==", + "integrity": "sha1-O7xCdd1YTMGxCAm4nU6LY6aednU=", "dev": true }, "asap": { @@ -1744,7 +1744,7 @@ "base": { "version": "0.11.2", "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "integrity": "sha1-e95c7RRbbVUakNuH+DxVi060io8=", "dev": true, "requires": { "cache-base": "^1.0.1", @@ -1768,7 +1768,7 @@ "is-accessor-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -1777,7 +1777,7 @@ "is-data-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -1786,7 +1786,7 @@ "is-descriptor": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=", "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", @@ -2093,7 +2093,7 @@ }, "bl": { "version": "1.2.2", - "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", + "resolved": "http://registry.npmjs.org/bl/-/bl-1.2.2.tgz", "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", "dev": true, "optional": true, @@ -2213,7 +2213,7 @@ "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "integrity": "sha1-PH/L9SnYcibz0vUrlm/1Jx60Qd0=", "dev": true, "requires": { "balanced-match": "^1.0.0", @@ -2223,7 +2223,7 @@ "braces": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "integrity": "sha1-WXn9PxTNUxVl5fot8av/8d+u5yk=", "dev": true, "requires": { "arr-flatten": "^1.1.0", @@ -2274,7 +2274,7 @@ "buffer-alloc": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", - "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "integrity": "sha1-iQ3ZDZI6hz4I4Q5f1RpX5bfM4Ow=", "dev": true, "requires": { "buffer-alloc-unsafe": "^1.1.0", @@ -2284,7 +2284,7 @@ "buffer-alloc-unsafe": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", - "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", + "integrity": "sha1-vX3CauKXLQ7aJTvgYdupkjScGfA=", "dev": true }, "buffer-crc32": { @@ -2330,7 +2330,7 @@ "cache-base": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "integrity": "sha1-Cn9GQWgxyLZi7jb+TnxZ129marI=", "dev": true, "requires": { "collection-visit": "^1.0.0", @@ -2467,9 +2467,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001002", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001002.tgz", - "integrity": "sha512-pRuxPE8wdrWmVPKcDmJJiGBxr6lFJq4ivdSeo9FTmGj5Rb8NX3Mby2pARG57MXF15hYAhZ0nHV5XxT2ig4bz3g==", + "version": "1.0.30001040", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001040.tgz", + "integrity": "sha512-Ep0tEPeI5wCvmJNrXjE3etgfI+lkl1fTDU6Y3ZH1mhrjkPlVI9W4pcKbMo+BQLpEWKVYYp2EmYaRsqpPC3k7lQ==", "dev": true }, "caseless": { @@ -2605,7 +2605,7 @@ "class-utils": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "integrity": "sha1-+TNprouafOAv1B+q0MqDAzGQxGM=", "dev": true, "requires": { "arr-union": "^3.1.0", @@ -2851,7 +2851,7 @@ "color-support": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "integrity": "sha1-k4NDeaHMmgxh+C9S8NBDIiUb1aI=", "dev": true }, "colornames": { @@ -2993,7 +2993,7 @@ "concat-with-sourcemaps": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz", - "integrity": "sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg==", + "integrity": "sha1-1OqT8FriV5CVG5nns7CeOQikCC4=", "dev": true, "requires": { "source-map": "^0.6.1" @@ -3002,7 +3002,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", "dev": true } } @@ -3076,7 +3076,7 @@ "content-type": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "integrity": "sha1-4TjMdeBAxyexlm/l5fjJruJW/js=", "dev": true }, "convert-source-map": { @@ -3661,7 +3661,7 @@ "define-property": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "integrity": "sha1-1Flono1lS6d+AqgX+HENcCyxbp0=", "dev": true, "requires": { "is-descriptor": "^1.0.2", @@ -3671,7 +3671,7 @@ "is-accessor-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -3680,7 +3680,7 @@ "is-data-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -3689,7 +3689,7 @@ "is-descriptor": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=", "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", @@ -4042,7 +4042,7 @@ "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", "dev": true, "requires": { "ms": "2.0.0" @@ -4058,7 +4058,7 @@ }, "engine.io-client": { "version": "3.2.1", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.2.1.tgz", + "resolved": "http://registry.npmjs.org/engine.io-client/-/engine.io-client-3.2.1.tgz", "integrity": "sha512-y5AbkytWeM4jQr7m/koQLc5AxpRKC1hEVUb/s1FUAWEJq5AzJJ4NLvzuKPuxtDi5Mq755WuDvZ6Iv2rXj4PTzw==", "dev": true, "requires": { @@ -4084,7 +4084,7 @@ "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", "dev": true, "requires": { "ms": "2.0.0" @@ -4132,7 +4132,7 @@ "errno": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", - "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", + "integrity": "sha1-RoTXF3mtOa8Xfj8AeZb3xnyFJhg=", "dev": true, "optional": true, "requires": { @@ -4414,7 +4414,7 @@ "esquery": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", - "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", + "integrity": "sha1-QGxRZYsfWZGl+bYrHcJbAOPlxwg=", "dev": true, "requires": { "estraverse": "^4.0.0" @@ -4423,7 +4423,7 @@ "esrecurse": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "integrity": "sha1-AHo7n9vCs7uH5IeeoZyS/b05Qs8=", "dev": true, "requires": { "estraverse": "^4.1.0" @@ -4478,7 +4478,7 @@ "exec-buffer": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/exec-buffer/-/exec-buffer-3.2.0.tgz", - "integrity": "sha512-wsiD+2Tp6BWHoVv3B+5Dcx6E7u5zky+hUwOHjuH2hKSLR3dvRmX8fk8UD8uqQixHs4Wk6eDmiegVrMPjKj7wpA==", + "integrity": "sha1-sWhtvZBMfPmC5lLB9aebHlVzCCs=", "dev": true, "optional": true, "requires": { @@ -4694,7 +4694,7 @@ "is-extendable": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "integrity": "sha1-p0cPnkJnM9gb2B4RVSZOOjUHyrQ=", "dev": true, "requires": { "is-plain-object": "^2.0.4" @@ -4716,7 +4716,7 @@ "extglob": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "integrity": "sha1-rQD+TcYSqSMuhxhxHcXLWrAoVUM=", "dev": true, "requires": { "array-unique": "^0.3.2", @@ -4750,7 +4750,7 @@ "is-accessor-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -4759,7 +4759,7 @@ "is-data-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -4768,7 +4768,7 @@ "is-descriptor": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=", "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", @@ -5347,7 +5347,7 @@ "fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "integrity": "sha1-a+Dem+mYzhavivwkSXue6bfM2a0=", "dev": true, "optional": true }, @@ -5946,7 +5946,7 @@ "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "integrity": "sha1-pWiZ0+o8m6uHS7l3O3xe3pL0iV0=", "dev": true }, "functional-red-black-tree": { @@ -6191,7 +6191,7 @@ "global-modules": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", - "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "integrity": "sha1-bXcPDrUjrHgWTXK15xqIdyZcw+o=", "dev": true, "requires": { "global-prefix": "^1.0.1", @@ -6771,9 +6771,9 @@ }, "dependencies": { "acorn": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", - "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", + "version": "5.7.4", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", + "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", "dev": true }, "source-map": { @@ -7079,7 +7079,7 @@ "has-binary2": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", - "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", + "integrity": "sha1-d3asYn8+p3JQz8My2rfd9eT10R0=", "dev": true, "requires": { "isarray": "2.0.1" @@ -7506,7 +7506,7 @@ "ini": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "integrity": "sha1-7uJfVtscnsYIXgwid4CD9Zar+Sc=", "dev": true }, "inquirer": { @@ -7629,7 +7629,7 @@ "is-absolute": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", - "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", + "integrity": "sha1-OV4a6EsR8mrReV5zwXN45IowFXY=", "dev": true, "requires": { "is-relative": "^1.0.0", @@ -7680,13 +7680,13 @@ "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "integrity": "sha1-76ouqdqg16suoTqXsritUf776L4=", "dev": true }, "is-callable": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", - "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", + "integrity": "sha1-HhrfIZ4e62hNaR+dagX/DTCiTXU=", "dev": true }, "is-color-stop": { @@ -7732,7 +7732,7 @@ "is-descriptor": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "integrity": "sha1-Nm2CQN3kh8pRgjsaufB6EKeCUco=", "dev": true, "requires": { "is-accessor-descriptor": "^0.1.6", @@ -7743,7 +7743,7 @@ "kind-of": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "integrity": "sha1-cpyR4thXt6QZofmqZWhcTDP1hF0=", "dev": true } } @@ -7891,7 +7891,7 @@ "is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "integrity": "sha1-LBY7P6+xtgbZ0Xko8FwqHDjgdnc=", "dev": true, "requires": { "isobject": "^3.0.1" @@ -7934,7 +7934,7 @@ "is-relative": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", - "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", + "integrity": "sha1-obtpNc6MXboei5dUubLcwCDiJg0=", "dev": true, "requires": { "is-unc-path": "^1.0.0" @@ -7943,7 +7943,7 @@ "is-resolvable": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", - "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", + "integrity": "sha1-+xj4fOH+uSUWnJpAfBkxijIG7Yg=", "dev": true }, "is-retry-allowed": { @@ -7986,7 +7986,7 @@ "is-unc-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", - "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", + "integrity": "sha1-1zHoiY7QkKEsNSrS6u1Qla0yLJ0=", "dev": true, "requires": { "unc-path-regex": "^0.1.2" @@ -8007,7 +8007,7 @@ "is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "integrity": "sha1-0YUOuXkezRjmGCzhKjDzlmNLsZ0=", "dev": true }, "is-wsl": { @@ -8149,7 +8149,7 @@ "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "integrity": "sha1-afaofZUTq4u4/mO9sJecRI5oRmA=", "dev": true }, "json-stable-stringify-without-jsonify": { @@ -8434,7 +8434,7 @@ "kind-of": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "integrity": "sha1-ARRrNqYhjmTljzqNZt5df8b20FE=", "dev": true }, "klaw": { @@ -8871,7 +8871,7 @@ "lowercase-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "integrity": "sha1-b54wtHCE2XGnyCD/FabFFnt0wm8=", "dev": true, "optional": true }, @@ -8927,7 +8927,7 @@ "make-iterator": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", - "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", + "integrity": "sha1-KbM/MSqo9UfEpeSQ9Wr87JkTOtY=", "dev": true, "requires": { "kind-of": "^6.0.2" @@ -9050,7 +9050,7 @@ "micromatch": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "integrity": "sha1-cIWbyVyYQJUvNZoGij/En57PrCM=", "dev": true, "requires": { "arr-diff": "^4.0.0", @@ -9093,7 +9093,7 @@ "mimic-fn": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "integrity": "sha1-ggyGo5M0ZA6ZUWkovQP8qIBX0CI=", "dev": true }, "mimic-response": { @@ -9114,7 +9114,7 @@ }, "minimist": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true }, @@ -9165,7 +9165,7 @@ "dependencies": { "minimist": { "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true } @@ -9243,7 +9243,7 @@ }, "next-tick": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "resolved": "http://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", "dev": true }, @@ -9324,9 +9324,9 @@ "dev": true }, "nouislider": { - "version": "14.0.2", - "resolved": "https://registry.npmjs.org/nouislider/-/nouislider-14.0.2.tgz", - "integrity": "sha512-N4AQStV4frh+XcLUwMI/hZpBP6tRboDE/4LZ7gzfxMVXFi/2J9URphnm40Ff4KEyrAVGSGaWApvljoMzTNWBlA==" + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/nouislider/-/nouislider-14.1.1.tgz", + "integrity": "sha512-3/+Z/pTBoWoJf2YXSEWRmS27LW2XxOBmGEzkPyRzB/J6QvL+0mS3QwcQp0SmWhgO5CMzbSxPmb1lDDD4HP12bg==" }, "now-and-later": { "version": "2.0.1", @@ -9338,9 +9338,9 @@ } }, "npm": { - "version": "6.12.0", - "resolved": "https://registry.npmjs.org/npm/-/npm-6.12.0.tgz", - "integrity": "sha512-juj5VkB3/k+PWbJUnXD7A/8oc8zLusDnK/sV9PybSalsbOVOTIp5vSE0rz5rQ7BsmUgQS47f/L2GYQnWXaKgnQ==", + "version": "6.13.6", + "resolved": "https://registry.npmjs.org/npm/-/npm-6.13.6.tgz", + "integrity": "sha512-NomC08kv7HIl1FOyLOe9Hp89kYsOsvx52huVIJ7i8hFW8Xp65lDwe/8wTIrh9q9SaQhA8hTrfXPh3BEL3TmMpw==", "requires": { "JSONStream": "^1.3.5", "abbrev": "~1.1.1", @@ -9348,12 +9348,12 @@ "ansistyles": "~0.1.3", "aproba": "^2.0.0", "archy": "~1.0.0", - "bin-links": "^1.1.3", + "bin-links": "^1.1.6", "bluebird": "^3.5.5", "byte-size": "^5.0.1", "cacache": "^12.0.3", "call-limit": "^1.1.1", - "chownr": "^1.1.2", + "chownr": "^1.1.3", "ci-info": "^2.0.0", "cli-columns": "^3.1.2", "cli-table3": "^0.5.1", @@ -9369,9 +9369,9 @@ "find-npm-prefix": "^1.0.2", "fs-vacuum": "~1.2.10", "fs-write-stream-atomic": "~1.0.10", - "gentle-fs": "^2.2.1", + "gentle-fs": "^2.3.0", "glob": "^7.1.4", - "graceful-fs": "^4.2.2", + "graceful-fs": "^4.2.3", "has-unicode": "~2.0.1", "hosted-git-info": "^2.8.5", "iferr": "^1.0.2", @@ -9384,7 +9384,7 @@ "is-cidr": "^3.0.0", "json-parse-better-errors": "^1.0.2", "lazy-property": "~1.0.0", - "libcipm": "^4.0.4", + "libcipm": "^4.0.7", "libnpm": "^3.0.1", "libnpmaccess": "^3.0.2", "libnpmhook": "^5.0.3", @@ -9418,25 +9418,25 @@ "npm-install-checks": "^3.0.2", "npm-lifecycle": "^3.1.4", "npm-package-arg": "^6.1.1", - "npm-packlist": "^1.4.4", + "npm-packlist": "^1.4.7", "npm-pick-manifest": "^3.0.2", "npm-profile": "^4.0.2", - "npm-registry-fetch": "^4.0.0", + "npm-registry-fetch": "^4.0.2", "npm-user-validate": "~1.0.0", "npmlog": "~4.1.2", "once": "~1.4.0", "opener": "^1.5.1", "osenv": "^0.1.5", - "pacote": "^9.5.8", + "pacote": "^9.5.12", "path-is-inside": "~1.0.2", "promise-inflight": "~1.0.1", "qrcode-terminal": "^0.12.0", "query-string": "^6.8.2", "qw": "~1.0.1", "read": "~1.0.7", - "read-cmd-shim": "^1.0.4", + "read-cmd-shim": "^1.0.5", "read-installed": "~4.0.3", - "read-package-json": "^2.1.0", + "read-package-json": "^2.1.1", "read-package-tree": "^5.3.1", "readable-stream": "^3.4.0", "readdir-scoped-modules": "^1.1.0", @@ -9451,7 +9451,7 @@ "sorted-union-stream": "~2.1.3", "ssri": "^6.0.1", "stringify-package": "^1.0.1", - "tar": "^4.4.12", + "tar": "^4.4.13", "text-table": "~0.2.0", "tiny-relative-date": "^1.3.0", "uid-number": "0.0.6", @@ -9459,7 +9459,7 @@ "unique-filename": "^1.1.1", "unpipe": "~1.0.0", "update-notifier": "^2.5.0", - "uuid": "^3.3.2", + "uuid": "^3.3.3", "validate-npm-package-license": "^3.0.4", "validate-npm-package-name": "~3.0.0", "which": "^1.3.1", @@ -9607,13 +9607,14 @@ } }, "bin-links": { - "version": "1.1.3", + "version": "1.1.6", "bundled": true, "requires": { "bluebird": "^3.5.3", "cmd-shim": "^3.0.0", - "gentle-fs": "^2.0.1", + "gentle-fs": "^2.3.0", "graceful-fs": "^4.1.15", + "npm-normalize-package-bin": "^1.0.0", "write-file-atomic": "^2.3.0" } }, @@ -9705,7 +9706,7 @@ } }, "chownr": { - "version": "1.1.2", + "version": "1.1.3", "bundled": true }, "ci-info": { @@ -10266,7 +10267,7 @@ }, "dependencies": { "minipass": { - "version": "2.8.6", + "version": "2.9.0", "bundled": true, "requires": { "safe-buffer": "^5.1.2", @@ -10362,11 +10363,12 @@ "bundled": true }, "gentle-fs": { - "version": "2.2.1", + "version": "2.3.0", "bundled": true, "requires": { "aproba": "^1.1.2", "chownr": "^1.1.2", + "cmd-shim": "^3.0.3", "fs-vacuum": "^1.2.10", "graceful-fs": "^4.1.11", "iferr": "^0.1.5", @@ -10448,7 +10450,7 @@ } }, "graceful-fs": { - "version": "4.2.2", + "version": "4.2.3", "bundled": true }, "har-schema": { @@ -10508,7 +10510,7 @@ } }, "https-proxy-agent": { - "version": "2.2.2", + "version": "2.2.4", "bundled": true, "requires": { "agent-base": "^4.3.0", @@ -10534,7 +10536,7 @@ "bundled": true }, "ignore-walk": { - "version": "3.0.1", + "version": "3.0.3", "bundled": true, "requires": { "minimatch": "^3.0.4" @@ -10748,7 +10750,7 @@ } }, "libcipm": { - "version": "4.0.4", + "version": "4.0.7", "bundled": true, "requires": { "bin-links": "^1.1.2", @@ -11017,14 +11019,14 @@ } }, "make-fetch-happen": { - "version": "5.0.0", + "version": "5.0.2", "bundled": true, "requires": { "agentkeepalive": "^3.4.1", "cacache": "^12.0.0", "http-cache-semantics": "^3.8.1", "http-proxy-agent": "^2.1.0", - "https-proxy-agent": "^2.2.1", + "https-proxy-agent": "^2.2.3", "lru-cache": "^5.1.1", "mississippi": "^3.0.0", "node-fetch-npm": "^2.0.2", @@ -11070,27 +11072,23 @@ "version": "0.0.8", "bundled": true }, - "minipass": { - "version": "2.3.3", + "minizlib": { + "version": "1.3.3", "bundled": true, "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" + "minipass": "^2.9.0" }, "dependencies": { - "yallist": { - "version": "3.0.2", - "bundled": true + "minipass": { + "version": "2.9.0", + "bundled": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } } } }, - "minizlib": { - "version": "1.2.2", - "bundled": true, - "requires": { - "minipass": "^2.2.1" - } - }, "mississippi": { "version": "3.0.0", "bundled": true, @@ -11215,8 +11213,11 @@ } }, "npm-bundled": { - "version": "1.0.6", - "bundled": true + "version": "1.1.1", + "bundled": true, + "requires": { + "npm-normalize-package-bin": "^1.0.1" + } }, "npm-cache-filename": { "version": "1.0.2", @@ -11247,6 +11248,10 @@ "version": "1.2.1", "bundled": true }, + "npm-normalize-package-bin": { + "version": "1.0.1", + "bundled": true + }, "npm-package-arg": { "version": "6.1.1", "bundled": true, @@ -11258,7 +11263,7 @@ } }, "npm-packlist": { - "version": "1.4.4", + "version": "1.4.7", "bundled": true, "requires": { "ignore-walk": "^3.0.1", @@ -11284,7 +11289,7 @@ } }, "npm-registry-fetch": { - "version": "4.0.0", + "version": "4.0.2", "bundled": true, "requires": { "JSONStream": "^1.3.4", @@ -11292,7 +11297,14 @@ "figgy-pudding": "^3.4.1", "lru-cache": "^5.1.1", "make-fetch-happen": "^5.0.0", - "npm-package-arg": "^6.1.0" + "npm-package-arg": "^6.1.0", + "safe-buffer": "^5.2.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.0", + "bundled": true + } } }, "npm-run-path": { @@ -11409,7 +11421,7 @@ } }, "pacote": { - "version": "9.5.8", + "version": "9.5.12", "bundled": true, "requires": { "bluebird": "^3.5.3", @@ -11426,6 +11438,7 @@ "mississippi": "^3.0.0", "mkdirp": "^0.5.1", "normalize-package-data": "^2.4.0", + "npm-normalize-package-bin": "^1.0.0", "npm-package-arg": "^6.1.0", "npm-packlist": "^1.1.12", "npm-pick-manifest": "^3.0.0", @@ -11444,7 +11457,7 @@ }, "dependencies": { "minipass": { - "version": "2.3.5", + "version": "2.9.0", "bundled": true, "requires": { "safe-buffer": "^5.1.2", @@ -11644,7 +11657,7 @@ } }, "read-cmd-shim": { - "version": "1.0.4", + "version": "1.0.5", "bundled": true, "requires": { "graceful-fs": "^4.1.2" @@ -11664,14 +11677,14 @@ } }, "read-package-json": { - "version": "2.1.0", + "version": "2.1.1", "bundled": true, "requires": { "glob": "^7.1.1", "graceful-fs": "^4.1.2", "json-parse-better-errors": "^1.0.1", "normalize-package-data": "^2.0.0", - "slash": "^1.0.0" + "npm-normalize-package-bin": "^1.0.0" } }, "read-package-tree": { @@ -11824,24 +11837,20 @@ "version": "3.0.2", "bundled": true }, - "slash": { - "version": "1.0.0", - "bundled": true - }, "slide": { "version": "1.1.6", "bundled": true }, "smart-buffer": { - "version": "4.0.2", + "version": "4.1.0", "bundled": true }, "socks": { - "version": "2.3.2", + "version": "2.3.3", "bundled": true, "requires": { - "ip": "^1.1.5", - "smart-buffer": "4.0.2" + "ip": "1.1.5", + "smart-buffer": "^4.1.0" } }, "socks-proxy-agent": { @@ -12056,7 +12065,7 @@ } }, "tar": { - "version": "4.4.12", + "version": "4.4.13", "bundled": true, "requires": { "chownr": "^1.1.1", @@ -12069,7 +12078,7 @@ }, "dependencies": { "minipass": { - "version": "2.8.6", + "version": "2.9.0", "bundled": true, "requires": { "safe-buffer": "^5.1.2", @@ -12231,7 +12240,7 @@ } }, "uuid": { - "version": "3.3.2", + "version": "3.3.3", "bundled": true }, "validate-npm-package-license": { @@ -12645,7 +12654,7 @@ "dependencies": { "minimist": { "version": "0.0.10", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", "dev": true }, @@ -12736,7 +12745,7 @@ }, "os-locale": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "resolved": "http://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", "dev": true, "requires": { @@ -13496,7 +13505,7 @@ }, "pretty-hrtime": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", + "resolved": "http://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", "dev": true }, @@ -13521,7 +13530,7 @@ "promise": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", - "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "integrity": "sha1-BktyYCsY+Q8pGSuLG8QY/9Hr078=", "dev": true, "optional": true, "requires": { @@ -13590,7 +13599,7 @@ "qjobs": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", - "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", + "integrity": "sha1-xF6cYYAL0IfviNfiVkI73Unl0HE=", "dev": true }, "qs": { @@ -13786,7 +13795,7 @@ "regex-cache": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", - "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", + "integrity": "sha1-db3FiioUls7EihKDW8VMjVYjNt0=", "dev": true, "requires": { "is-equal-shallow": "^0.1.3" @@ -13795,7 +13804,7 @@ "regex-not": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "integrity": "sha1-H07OJ+ALC2XgJHpoEOaoXYOldSw=", "dev": true, "requires": { "extend-shallow": "^3.0.2", @@ -14040,7 +14049,7 @@ "ret": { "version": "0.1.15", "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "integrity": "sha1-uKSCXVvbH8P29Twrwz+BOIaBx7w=", "dev": true }, "reusify": { @@ -14103,7 +14112,7 @@ "run-sequence": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/run-sequence/-/run-sequence-2.2.1.tgz", - "integrity": "sha512-qkzZnQWMZjcKbh3CNly2srtrkaO/2H/SI5f2eliMCapdRD3UhMrwjfOAZJAnZ2H8Ju4aBzFZkBGXUqFs9V0yxw==", + "integrity": "sha1-HOZD2jb9jH6n4akynaM/wriJhJU=", "dev": true, "requires": { "chalk": "^1.1.3", @@ -14200,7 +14209,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "integrity": "sha1-mR7GnSluAxN0fVm9/St0XDX4go0=", "dev": true }, "safe-regex": { @@ -14215,13 +14224,13 @@ "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "integrity": "sha1-RPoWGwGHuVSd2Eu5GAL5vYOFzWo=", "dev": true }, "sax": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "integrity": "sha1-KBYjTiN4vdxOU1T6tcqold9xANk=", "dev": true }, "seek-bzip": { @@ -14386,7 +14395,7 @@ "snapdragon": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "integrity": "sha1-ZJIufFZbDhQgS6GqfWlkJ40lGC0=", "dev": true, "requires": { "base": "^0.11.1", @@ -14437,7 +14446,7 @@ "snapdragon-node": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "integrity": "sha1-bBdfhv8UvbByRWPo88GwIaKGhTs=", "dev": true, "requires": { "define-property": "^1.0.0", @@ -14457,7 +14466,7 @@ "is-accessor-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -14466,7 +14475,7 @@ "is-data-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -14475,7 +14484,7 @@ "is-descriptor": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=", "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", @@ -14488,7 +14497,7 @@ "snapdragon-util": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "integrity": "sha1-+VZHlIbyrNeXAGk/b3uAXkWrVuI=", "dev": true, "requires": { "kind-of": "^3.2.0" @@ -14589,7 +14598,7 @@ }, "socket.io-parser": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.2.0.tgz", + "resolved": "http://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.2.0.tgz", "integrity": "sha512-FYiBx7rc/KORMJlgsXysflWx/RIvtqZbyGLlHZvjfmPTPeuD/I8MaW7cfFrj5tRltICJdgwflhfZ3NVVbVLFQA==", "dev": true, "requires": { @@ -14607,7 +14616,7 @@ "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", "dev": true, "requires": { "ms": "2.0.0" @@ -14697,7 +14706,7 @@ "spdx-expression-parse": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", - "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "integrity": "sha1-meEZt6XaAOBUkcn6M4t5BII7QdA=", "dev": true, "requires": { "spdx-exceptions": "^2.1.0", @@ -14718,7 +14727,7 @@ "split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "integrity": "sha1-fLCd2jqGWFcFxks5pkZgOGguj+I=", "dev": true, "requires": { "extend-shallow": "^3.0.0" @@ -14792,7 +14801,7 @@ "stable": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", - "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", + "integrity": "sha1-g26zyDgv4pNv6vVEYxAXzn1Ho88=", "dev": true }, "stack-trace": { @@ -14998,7 +15007,7 @@ "strip-outer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", - "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", + "integrity": "sha1-sv0qv2YEudHmATBXGV34Nrip1jE=", "dev": true, "optional": true, "requires": { @@ -15304,14 +15313,14 @@ "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" }, "tinymce": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/tinymce/-/tinymce-4.9.2.tgz", - "integrity": "sha512-ZRoTGG4GAsOI73QPSNkabO7nkoYw9H6cglRB44W2mMkxSiqxYi8WJlgkUphk0fDqo6ZD6r3E+NSP4UHxF2lySg==" + "version": "4.9.9", + "resolved": "https://registry.npmjs.org/tinymce/-/tinymce-4.9.9.tgz", + "integrity": "sha512-7Wqh4PGSAWm6FyNwyI1uFAaZyzeQeiwd9Gg2R89SpFIqoMrSzNHIYBqnZnlDm4Bd2DJ0wcC6uJhwFrabIE8puw==" }, "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "integrity": "sha1-bTQzWIl2jSGyvNoKonfO07G/rfk=", "dev": true, "requires": { "os-tmpdir": "~1.0.2" @@ -15336,7 +15345,7 @@ "to-buffer": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", - "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==", + "integrity": "sha1-STvUj2LXxD/N7TE6A9ytsuEhOoA=", "dev": true, "optional": true }, @@ -15369,7 +15378,7 @@ "to-regex": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "integrity": "sha1-E8/dmzNlUvMLUfM6iuG0Knp1mc4=", "dev": true, "requires": { "define-property": "^2.0.2", @@ -15563,7 +15572,7 @@ "ultron": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", - "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==", + "integrity": "sha1-n+FTahCmZKZSZqHjzPhf02MCvJw=", "dev": true }, "unbzip2-stream": { @@ -15746,7 +15755,7 @@ "uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "integrity": "sha1-lMVA4f93KVbiKZUHwBCupsiDjrA=", "dev": true, "requires": { "punycode": "^2.1.0" @@ -15800,7 +15809,7 @@ "util.promisify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", - "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", + "integrity": "sha1-RA9xZaRZyaFtwUXrjnLzVocJcDA=", "dev": true, "requires": { "define-properties": "^1.1.2", @@ -16093,7 +16102,7 @@ }, "wrap-ansi": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "dev": true, "requires": { @@ -16119,7 +16128,7 @@ "ws": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", - "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "integrity": "sha1-8c+E/i1ekB686U767OeF8YeiKPI=", "dev": true, "requires": { "async-limiter": "~1.0.0", diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index 0f02aba5e2..8ab5980107 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -11,9 +11,9 @@ }, "dependencies": { "ace-builds": "1.4.2", - "angular": "1.7.5", + "angular": "1.7.9", "angular-animate": "1.7.5", - "angular-aria": "1.7.5", + "angular-aria": "1.7.9", "angular-chart.js": "^1.1.1", "angular-cookies": "1.7.5", "angular-dynamic-locale": "0.1.37", @@ -39,10 +39,10 @@ "moment": "2.22.2", "ng-file-upload": "12.2.13", "nouislider": "14.1.1", - "npm": "6.12.0", + "npm": "6.13.6", "signalr": "2.4.0", "spectrum-colorpicker": "1.8.0", - "tinymce": "4.9.2", + "tinymce": "4.9.9", "typeahead.js": "0.11.1", "underscore": "1.9.1" }, @@ -50,7 +50,7 @@ "@babel/core": "7.6.4", "@babel/preset-env": "7.6.3", "autoprefixer": "9.6.5", - "caniuse-lite": "^1.0.30001002", + "caniuse-lite": "^1.0.30001037", "cssnano": "4.1.10", "fs": "0.0.2", "gulp": "4.0.2", 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 6c0515906f..75bf0d52af 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/common/directives/components/application/umbappheader.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbappheader.directive.js index ead54b3fc3..8efaf0c024 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbappheader.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbappheader.directive.js @@ -1,7 +1,7 @@ (function () { "use strict"; - function AppHeaderDirective(eventsService, appState, userService, focusService) { + function AppHeaderDirective(eventsService, appState, userService, focusService, backdropService) { function link(scope, el, attr, ctrl) { @@ -18,20 +18,20 @@ ]; // when a user logs out or timesout - evts.push(eventsService.on("app.notAuthenticated", function() { + evts.push(eventsService.on("app.notAuthenticated", function () { scope.authenticated = false; scope.user = null; })); // when the application is ready and the user is authorized setup the data - evts.push(eventsService.on("app.ready", function(evt, data) { - + evts.push(eventsService.on("app.ready", function (evt, data) { + scope.authenticated = true; scope.user = data.user; if (scope.user.avatars) { scope.avatar = []; - if (angular.isArray(scope.user.avatars)) { + if (Utilities.isArray(scope.user.avatars)) { for (var i = 0; i < scope.user.avatars.length; i++) { scope.avatar.push({ value: scope.user.avatars[i] }); } @@ -40,13 +40,13 @@ })); - evts.push(eventsService.on("app.userRefresh", function(evt) { - userService.refreshCurrentUser().then(function(data) { + evts.push(eventsService.on("app.userRefresh", function (evt) { + userService.refreshCurrentUser().then(function (data) { scope.user = data; - + if (scope.user.avatars) { scope.avatar = []; - if (angular.isArray(scope.user.avatars)) { + if (Utilities.isArray(scope.user.avatars)) { for (var i = 0; i < scope.user.avatars.length; i++) { scope.avatar.push({ value: scope.user.avatars[i] }); } @@ -54,10 +54,10 @@ } }); })); - + scope.rememberFocus = focusService.rememberFocus; - - scope.searchClick = function() { + + scope.searchClick = function () { var showSearch = appState.getSearchState("show"); appState.setSearchState("show", !showSearch); }; @@ -71,13 +71,15 @@ }; scope.avatarClick = function () { - if(!scope.userDialog) { + if (!scope.userDialog) { + backdropService.open(); scope.userDialog = { view: "user", show: true, close: function (oldModel) { scope.userDialog.show = false; scope.userDialog = null; + backdropService.close(); } }; } else { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbbackdrop.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbbackdrop.directive.js index 39e4f10666..4b24075748 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbbackdrop.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbbackdrop.directive.js @@ -7,8 +7,8 @@ var events = []; - scope.clickBackdrop = function(event) { - if(scope.disableEventsOnClick === true) { + scope.clickBackdrop = function (event) { + if (scope.disableEventsOnClick === true) { event.preventDefault(); event.stopPropagation(); } @@ -22,16 +22,16 @@ } - function setHighlight () { + function setHighlight() { scope.loading = true; $timeout(function () { // The element to highlight - var highlightElement = angular.element(scope.highlightElement); + var highlightElement = $(scope.highlightElement); - if(highlightElement && highlightElement.length > 0) { + if (highlightElement && highlightElement.length > 0) { var offset = highlightElement.offset(); var width = highlightElement.outerWidth(); @@ -48,7 +48,7 @@ var rectRight = el.find(".umb-backdrop__rect--right"); var rectBottom = el.find(".umb-backdrop__rect--bottom"); var rectLeft = el.find(".umb-backdrop__rect--left"); - + // Add the css scope.rectTopCss = { "height": topDistance, "left": leftDistance + "px", opacity: scope.backdropOpacity }; scope.rectRightCss = { "left": leftAndWidth + "px", "top": topDistance + "px", "height": height, opacity: scope.backdropOpacity }; @@ -56,14 +56,14 @@ scope.rectLeftCss = { "width": leftDistance, opacity: scope.backdropOpacity }; // Prevent interaction in the highlighted area - if(scope.highlightPreventClick) { + if (scope.highlightPreventClick) { var preventClickElement = el.find(".umb-backdrop__highlight-prevent-click"); preventClickElement.css({ "width": width, "height": height, "left": offset.left, "top": offset.top }); } } - scope.loading = false; + scope.loading = false; }); @@ -74,8 +74,8 @@ } events.push(scope.$watch("highlightElement", function (newValue, oldValue) { - if(!newValue) {return;} - if(newValue === oldValue) {return;} + if (!newValue) { return; } + if (newValue === oldValue) { return; } setHighlight(); })); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsearch.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsearch.directive.js index 8434a96ba5..e03e63b68f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsearch.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsearch.directive.js @@ -12,7 +12,7 @@ onClose: "&" } }; - + function umbSearchController($timeout, backdropService, searchService, focusService) { var vm = this; @@ -25,7 +25,7 @@ vm.handleKeyDown = handleKeyDown; vm.closeSearch = closeSearch; vm.focusSearch = focusSearch; - + //we need to capture the focus before this element is initialized. vm.focusBeforeOpening = focusService.getLastKnownFocus(); @@ -66,8 +66,8 @@ */ function focusSearch() { vm.searchHasFocus = false; - $timeout(function(){ - vm.searchHasFocus = true; + $timeout(function () { + vm.searchHasFocus = true; }); } @@ -76,14 +76,14 @@ * @param {object} event */ function handleKeyDown(event) { - + // esc - if(event.keyCode === 27) { + if (event.keyCode === 27) { event.stopPropagation(); event.preventDefault(); - + closeSearch(); - return; + return; } // up/down (navigate search results) @@ -132,7 +132,7 @@ } $timeout(function () { - var resultElementLink = angular.element(".umb-search-item[active-result='true'] .umb-search-result__link"); + var resultElementLink = $(".umb-search-item[active-result='true'] .umb-search-result__link"); resultElementLink[0].focus(); }); } @@ -142,10 +142,10 @@ * Used to proxy a callback */ function closeSearch() { - if(vm.focusBeforeOpening) { + if (vm.focusBeforeOpening) { vm.focusBeforeOpening.focus(); } - if(vm.onClose) { + if (vm.onClose) { vm.onClose(); } } @@ -155,9 +155,9 @@ * @param {string} searchQuery */ function search(searchQuery) { - if(searchQuery.length > 0) { - var search = {"term": searchQuery}; - searchService.searchAll(search).then(function(result){ + if (searchQuery.length > 0) { + var search = { "term": searchQuery }; + searchService.searchAll(search).then(function (result) { //result is a dictionary of group Title and it's results var filtered = {}; _.each(result, function (value, key) { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsections.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsections.directive.js index b8ee797c82..190da7da0d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsections.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsections.directive.js @@ -12,26 +12,25 @@ function sectionsDirective($timeout, $window, navigationService, treeService, se var sectionItemsWidth = []; var evts = []; - var maxSections = 8; //setup scope vars - scope.maxSections = maxSections; - scope.overflowingSections = 0; scope.sections = []; + scope.visibleSections = 0; scope.currentSection = appState.getSectionState("currentSection"); - scope.showTray = false; //appState.getGlobalState("showTray"); + scope.showTray = false; scope.stickyNavigation = appState.getGlobalState("stickyNavigation"); - scope.needTray = false; function loadSections() { sectionService.getSectionsForUser() .then(function (result) { scope.sections = result; + scope.visibleSections = scope.sections.length; + // store the width of each section so we can hide/show them based on browser width // we store them because the sections get removed from the dom and then we // can't tell when to show them gain $timeout(function () { - $("#applications .sections li").each(function (index) { + $("#applications .sections li:not(:last)").each(function (index) { sectionItemsWidth.push($(this).outerWidth()); }); }); @@ -42,25 +41,22 @@ function sectionsDirective($timeout, $window, navigationService, treeService, se function calculateWidth() { $timeout(function () { //total width minus room for avatar, search, and help icon - var windowWidth = $(window).width() - 150; + var containerWidth = $(".umb-app-header").outerWidth() - $(".umb-app-header__actions").outerWidth(); + var trayToggleWidth = $("#applications .sections li.expand").outerWidth(); var sectionsWidth = 0; - scope.totalSections = scope.sections.length; - scope.maxSections = maxSections; - scope.overflowingSections = scope.maxSections - scope.totalSections; - scope.needTray = scope.sections.length > scope.maxSections; - + // detect how many sections we can show on the screen for (var i = 0; i < sectionItemsWidth.length; i++) { var sectionItemWidth = sectionItemsWidth[i]; sectionsWidth += sectionItemWidth; - if (sectionsWidth > windowWidth) { - scope.needTray = true; - scope.maxSections = i - 1; - scope.overflowingSections = scope.maxSections - scope.totalSections; - break; + if (sectionsWidth + trayToggleWidth > containerWidth) { + scope.visibleSections = i; + return; } } + + scope.visibleSections = scope.sections.length; }); } @@ -133,6 +129,12 @@ function sectionsDirective($timeout, $window, navigationService, treeService, se } }; + scope.currentSectionInOverflow = function () { + var currentSection = scope.sections.filter(s => s.alias === scope.currentSection); + + return currentSection.length > 0 && scope.sections.indexOf(currentSection[0]) > scope.visibleSections - 1; + }; + loadSections(); } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour.directive.js index 287962b6d3..6f98dbca6e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour.directive.js @@ -198,27 +198,27 @@ In the following example you see how to run some custom logic before a step goes scope.loadingStep = false; scope.elementNotFound = false; - scope.model.nextStep = function() { + scope.model.nextStep = function () { nextStep(); }; - scope.model.endTour = function() { + scope.model.endTour = function () { unbindEvent(); tourService.endTour(scope.model); backdropService.close(); }; - scope.model.completeTour = function() { + scope.model.completeTour = function () { unbindEvent(); - tourService.completeTour(scope.model).then(function() { - backdropService.close(); + tourService.completeTour(scope.model).then(function () { + backdropService.close(); }); }; - scope.model.disableTour = function() { + scope.model.disableTour = function () { unbindEvent(); - tourService.disableTour(scope.model).then(function() { - backdropService.close(); + tourService.disableTour(scope.model).then(function () { + backdropService.close(); }); } @@ -227,7 +227,7 @@ In the following example you see how to run some custom logic before a step goes pulseElement = el.find(".umb-tour__pulse"); popover.hide(); scope.model.currentStepIndex = 0; - backdropService.open({disableEventsOnClick: true}); + backdropService.open({ disableEventsOnClick: true }); startStep(); } @@ -249,20 +249,20 @@ In the following example you see how to run some custom logic before a step goes } function nextStep() { - + popover.hide(); pulseElement.hide(); $timeout.cancel(pulseTimer); scope.model.currentStepIndex++; // make sure we don't go too far - if(scope.model.currentStepIndex !== scope.model.steps.length) { + if (scope.model.currentStepIndex !== scope.model.steps.length) { startStep(); - // tour completed - final step + // tour completed - final step } else { scope.loadingStep = true; - waitForPendingRerequests().then(function(){ + waitForPendingRerequests().then(function () { scope.loadingStep = false; // clear current step scope.model.currentStep = {}; @@ -280,17 +280,17 @@ In the following example you see how to run some custom logic before a step goes backdropService.setOpacity(scope.model.steps[scope.model.currentStepIndex].backdropOpacity); backdropService.setHighlight(null); - waitForPendingRerequests().then(function() { + waitForPendingRerequests().then(function () { scope.model.currentStep = scope.model.steps[scope.model.currentStepIndex]; setView(); - + // if highlight element is set - find it findHighlightElement(); // if a custom event needs to be bound we do it now - if(scope.model.currentStep.event) { + if (scope.model.currentStep.event) { bindEvent(); } @@ -301,7 +301,7 @@ In the following example you see how to run some custom logic before a step goes function findHighlightElement() { - scope.elementNotFound = false; + scope.elementNotFound = false; $timeout(function () { // clear element when step as marked as intro, so it always displays in the center @@ -312,15 +312,15 @@ In the following example you see how to run some custom logic before a step goes } // if an element isn't set - show the popover in the center - if(scope.model.currentStep && !scope.model.currentStep.element) { + if (scope.model.currentStep && !scope.model.currentStep.element) { setPopoverPosition(null); return; } - var element = angular.element(scope.model.currentStep.element); + var element = $(scope.model.currentStep.element); // we couldn't find the element in the dom - abort and show error - if(element.length === 0) { + if (element.length === 0) { scope.elementNotFound = true; setPopoverPosition(null); return; @@ -337,7 +337,7 @@ In the following example you see how to run some custom logic before a step goes el = el.offsetParent(); } } - + var scrollToCenterOfContainer = offsetTop - (scrollParent[0].clientHeight / 2); if (element[0].clientHeight < scrollParent[0].clientHeight) { scrollToCenterOfContainer += (element[0].clientHeight / 2); @@ -366,7 +366,7 @@ In the following example you see how to run some custom logic before a step goes function setPopoverPosition(element) { $timeout(function () { - + var position = "center"; var margin = 20; var css = {}; @@ -374,10 +374,10 @@ In the following example you see how to run some custom logic before a step goes var popoverWidth = popover.outerWidth(); var popoverHeight = popover.outerHeight(); var popoverOffset = popover.offset(); - var documentWidth = angular.element(document).width(); - var documentHeight = angular.element(document).height(); + var documentWidth = $(document).width(); + var documentHeight = $(document).height(); - if(element) { + if (element) { var offset = element.offset(); var width = element.outerWidth(); @@ -436,29 +436,29 @@ In the following example you see how to run some custom logic before a step goes } else { // if there is no dom element center the popover - css.top = "calc(50% - " + popoverHeight/2 + "px)"; - css.left = "calc(50% - " + popoverWidth/2 + "px)"; + css.top = "calc(50% - " + popoverHeight / 2 + "px)"; + css.left = "calc(50% - " + popoverWidth / 2 + "px)"; } popover.css(css).fadeIn("fast"); - + }); } function setPulsePosition() { - if(scope.model.currentStep.event) { + if (scope.model.currentStep.event) { + + pulseTimer = $timeout(function () { - pulseTimer = $timeout(function(){ - var clickElementSelector = scope.model.currentStep.eventElement ? scope.model.currentStep.eventElement : scope.model.currentStep.element; var clickElement = $(clickElementSelector); - + var offset = clickElement.offset(); var width = clickElement.outerWidth(); var height = clickElement.outerHeight(); - + pulseElement.css({ "width": width, "height": height, "left": offset.left, "top": offset.top }); pulseElement.fadeIn(); @@ -468,24 +468,24 @@ In the following example you see how to run some custom logic before a step goes function waitForPendingRerequests() { var deferred = $q.defer(); - var timer = window.setInterval(function(){ - + var timer = window.setInterval(function () { + var requestsReady = false; var animationsDone = false; // check for pending requests both in angular and on the document - if($http.pendingRequests.length === 0 && document.readyState === "complete") { + if ($http.pendingRequests.length === 0 && document.readyState === "complete") { requestsReady = true; } // check for animations. ng-enter and ng-leave are default angular animations. // Also check for infinite editors animating - if(document.querySelectorAll(".ng-enter, .ng-leave, .umb-editor--animating").length === 0) { + if (document.querySelectorAll(".ng-enter, .ng-leave, .umb-editor--animating").length === 0) { animationsDone = true; } - if(requestsReady && animationsDone) { - $timeout(function(){ + if (requestsReady && animationsDone) { + $timeout(function () { deferred.resolve(); clearInterval(timer); }); @@ -512,14 +512,14 @@ In the following example you see how to run some custom logic before a step goes var bindToElement = scope.model.currentStep.element; var eventName = scope.model.currentStep.event + ".step-" + scope.model.currentStepIndex; var removeEventName = "remove.step-" + scope.model.currentStepIndex; - var handled = false; + var handled = false; - if(scope.model.currentStep.eventElement) { + if (scope.model.currentStep.eventElement) { bindToElement = scope.model.currentStep.eventElement; } - $(bindToElement).on(eventName, function(){ - if(!handled) { + $(bindToElement).on(eventName, function () { + if (!handled) { unbindEvent(); nextStep(); handled = true; @@ -530,7 +530,7 @@ In the following example you see how to run some custom logic before a step goes // for some reason it seems the elements gets removed before the event is raised. This is a temp solution which assumes: // "if you ask me to click on an element, and it suddenly gets removed from the dom, let's go on to the next step". $(bindToElement).on(removeEventName, function () { - if(!handled) { + if (!handled) { unbindEvent(); nextStep(); handled = true; @@ -542,13 +542,13 @@ In the following example you see how to run some custom logic before a step goes function unbindEvent() { var eventName = scope.model.currentStep.event + ".step-" + scope.model.currentStepIndex; var removeEventName = "remove.step-" + scope.model.currentStepIndex; - - if(scope.model.currentStep.eventElement) { - angular.element(scope.model.currentStep.eventElement).off(eventName); - angular.element(scope.model.currentStep.eventElement).off(removeEventName); + + if (scope.model.currentStep.eventElement) { + $(scope.model.currentStep.eventElement).off(eventName); + $(scope.model.currentStep.eventElement).off(removeEventName); } else { - angular.element(scope.model.currentStep.element).off(eventName); - angular.element(scope.model.currentStep.element).off(removeEventName); + $(scope.model.currentStep.element).off(eventName); + $(scope.model.currentStep.element).off(removeEventName); } } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour/umbtourstep.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour/umbtourstep.directive.js index 2dc0ebdf93..381ecc76c3 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour/umbtourstep.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour/umbtourstep.directive.js @@ -18,9 +18,9 @@ function link(scope, element, attrs, ctrl) { - scope.close = function() { - if(scope.onClose) { - scope.onClose(); + scope.close = function () { + if (scope.onClose) { + scope.onClose(); } } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js index cab71842b1..0b137a5fbe 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js @@ -31,18 +31,18 @@ $scope.page.hideActionsMenu = infiniteMode ? true : false; $scope.page.hideChangeVariant = false; $scope.allowOpen = true; - $scope.app = null; + $scope.activeApp = null; //initializes any watches function startWatches(content) { //watch for changes to isNew, set the page.isNew accordingly and load the breadcrumb if we can $scope.$watch('isNew', function (newVal, oldVal) { - + $scope.page.isNew = Object.toBoolean(newVal); //We fetch all ancestors of the node to generate the footer breadcrumb navigation - if (content.parentId && content.parentId !== -1) { + if (content.parentId && content.parentId !== -1 && content.parentId !== -20) { loadBreadcrumb(); if (!watchingCulture) { $scope.$watch('culture', @@ -74,31 +74,23 @@ var isAppPresent = false; // on first init, we dont have any apps. but if we are re-initializing, we do, but ... - if ($scope.app) { + if ($scope.activeApp) { - // lets check if it still exists as part of our apps array. (if not we have made a change to our docType, even just a re-save of the docType it will turn into new Apps.) _.forEach(content.apps, function (app) { - if (app === $scope.app) { + if (app.alias === $scope.activeApp.alias) { isAppPresent = true; + $scope.appChanged(app); } }); - // if we did reload our DocType, but still have the same app we will try to find it by the alias. if (isAppPresent === false) { - _.forEach(content.apps, function (app) { - if (app.alias === $scope.app.alias) { - isAppPresent = true; - app.active = true; - $scope.appChanged(app); - } - }); + // active app does not exist anymore. + $scope.activeApp = null; } - } // if we still dont have a app, lets show the first one: - if (isAppPresent === false && content.apps.length) { - content.apps[0].active = true; + if ($scope.activeApp === null && content.apps.length) { $scope.appChanged(content.apps[0]); } // otherwise make sure the save options are up to date with the current content state @@ -151,8 +143,8 @@ } /** Returns true if the content item varies by culture */ - function isContentCultureVariant() { - return $scope.content.variants.length > 1; + function hasVariants(content) { + return content.variants.length > 1; } function reload() { @@ -182,32 +174,32 @@ } })); - evts.push(eventsService.on("editors.content.reload", function (name, args) { + evts.push(eventsService.on("editors.content.reload", function (name, args) { if (args && args.node && $scope.content.id === args.node.id) { reload(); loadBreadcrumb(); syncTreeNode($scope.content, $scope.content.path); } })); - - evts.push(eventsService.on("rte.file.uploading", function(){ + + evts.push(eventsService.on("rte.file.uploading", function () { $scope.page.saveButtonState = "busy"; $scope.page.buttonGroupState = "busy"; })); - evts.push(eventsService.on("rte.file.uploaded", function(){ + evts.push(eventsService.on("rte.file.uploaded", function () { $scope.page.saveButtonState = "success"; $scope.page.buttonGroupState = "success"; })); - evts.push(eventsService.on("rte.shortcut.save", function(){ + evts.push(eventsService.on("rte.shortcut.save", function () { if ($scope.page.showSaveButton) { $scope.save(); } })); - evts.push(eventsService.on("content.saved", function(){ + evts.push(eventsService.on("content.saved", function () { // Clear out localstorage keys that start with tinymce__ // When we save/perist a content node // NOTE: clearAll supports a RegEx pattern of items to remove @@ -215,6 +207,13 @@ })); } + function appendRuntimeData() { + $scope.content.variants.forEach((variant) => { + variant.compositeId = contentEditingHelper.buildCompositeVariantId(variant); + variant.htmlId = "_content_variant_" + variant.compositeId; + }); + } + /** * This does the content loading and initializes everything, called on first load */ @@ -226,6 +225,7 @@ $scope.content = data; + appendRuntimeData(); init(); syncTreeNode($scope.content, $scope.content.path, true); @@ -251,6 +251,7 @@ $scope.content = data; + appendRuntimeData(); init(); startWatches($scope.content); @@ -274,7 +275,7 @@ $scope.page.saveButtonStyle = content.trashed || content.isElement || content.isBlueprint ? "primary" : "info"; // only create the save/publish/preview buttons if the // content app is "Conent" - if ($scope.app && $scope.app.alias !== "umbContent" && $scope.app.alias !== "umbInfo" && $scope.app.alias !== "umbListView") { + if ($scope.activeApp && $scope.activeApp.alias !== "umbContent" && $scope.activeApp.alias !== "umbInfo" && $scope.activeApp.alias !== "umbListView") { $scope.defaultButton = null; $scope.subButtons = null; $scope.page.showSaveButton = false; @@ -287,6 +288,8 @@ $scope.page.showSaveButton = true; // add ellipsis to the save button if it opens the variant overlay $scope.page.saveButtonEllipsis = content.variants && content.variants.length > 1 ? "true" : "false"; + } else { + $scope.page.showSaveButton = false; } // create the pubish combo button @@ -321,7 +324,7 @@ .then(function (syncArgs) { $scope.page.menu.currentNode = syncArgs.node; if (reloadChildren && syncArgs.node.expanded) { - treeService.loadNodeChildren({node: syncArgs.node}); + treeService.loadNodeChildren({ node: syncArgs.node }); } }, function () { //handle the rejection @@ -587,7 +590,7 @@ $scope.sendToPublish = function () { clearNotifications($scope.content); - if (isContentCultureVariant()) { + if (hasVariants($scope.content)) { //before we launch the dialog we want to execute all client side validations first if (formHelper.submitForm({ scope: $scope, action: "publish" })) { @@ -647,7 +650,7 @@ $scope.saveAndPublish = function () { clearNotifications($scope.content); - if (isContentCultureVariant()) { + if (hasVariants($scope.content)) { //before we launch the dialog we want to execute all client side validations first if (formHelper.submitForm({ scope: $scope, action: "publish" })) { var dialog = { @@ -709,7 +712,7 @@ $scope.save = function () { clearNotifications($scope.content); // TODO: Add "..." to save button label if there are more than one variant to publish - currently it just adds the elipses if there's more than 1 variant - if (isContentCultureVariant()) { + if (hasVariants($scope.content)) { //before we launch the dialog we want to execute all client side validations first if (formHelper.submitForm({ scope: $scope, action: "openSaveDialog" })) { @@ -774,7 +777,7 @@ clearNotifications($scope.content); //before we launch the dialog we want to execute all client side validations first if (formHelper.submitForm({ scope: $scope, action: "schedule" })) { - if (!isContentCultureVariant()) { + if (!hasVariants($scope.content)) { //ensure the flags are set $scope.content.variants[0].save = true; } @@ -782,7 +785,7 @@ var dialog = { parentScope: $scope, view: "views/content/overlays/schedule.html", - variants: angular.copy($scope.content.variants), //set a model property for the dialog + variants: Utilities.copy($scope.content.variants), //set a model property for the dialog skipFormValidation: true, //when submitting the overlay form, skip any client side validation submitButtonLabelKey: "buttons_schedulePublish", submit: function (model) { @@ -791,6 +794,7 @@ $scope.content.variants[i].expireDate = model.variants[i].expireDate; $scope.content.variants[i].releaseDateFormatted = model.variants[i].releaseDateFormatted; $scope.content.variants[i].expireDateFormatted = model.variants[i].expireDateFormatted; + $scope.content.variants[i].save = model.variants[i].save; } model.submitButtonState = "busy"; @@ -810,12 +814,12 @@ }, function (err) { clearDirtyState($scope.content.variants); //if this is invariant, show the notification errors, else they'll be shown inline with the variant - if (!isContentCultureVariant()) { + if (!hasVariants($scope.content)) { formHelper.showNotifications(err.data); } model.submitButtonState = "error"; //re-map the dialog model since we've re-bound the properties - dialog.variants = angular.copy($scope.content.variants); + dialog.variants = Utilities.copy($scope.content.variants); //don't reject, we've handled the error return $q.when(err); }); @@ -837,7 +841,7 @@ //before we launch the dialog we want to execute all client side validations first if (formHelper.submitForm({ scope: $scope, action: "publishDescendants" })) { - if (!isContentCultureVariant()) { + if (!hasVariants($scope.content)) { //ensure the flags are set $scope.content.variants[0].save = true; $scope.content.variants[0].publish = true; @@ -870,7 +874,7 @@ }, function (err) { clearDirtyState($scope.content.variants); //if this is invariant, show the notification errors, else they'll be shown inline with the variant - if (!isContentCultureVariant()) { + if (!hasVariants($scope.content)) { formHelper.showNotifications(err.data); } model.submitButtonState = "error"; @@ -960,11 +964,18 @@ * Call back when a content app changes * @param {any} app */ - $scope.appChanged = function (app) { + $scope.appChanged = function (activeApp) { - $scope.app = app; + $scope.activeApp = activeApp; + + _.forEach($scope.content.apps, function (app) { + app.active = false; + if (app.alias === $scope.activeApp.alias) { + app.active = true; + } + }); - $scope.$broadcast("editors.apps.appChanged", { app: app }); + $scope.$broadcast("editors.apps.appChanged", { app: activeApp }); createButtons($scope.content); @@ -1026,6 +1037,7 @@ getMethod: "&", getScaffoldMethod: "&?", culture: "=?", + segment: "=?", infiniteModel: "=?" } }; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js index 78a2111fc5..1e929af6e9 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js @@ -130,6 +130,7 @@ view: "default", content: labels.doctypeChangeWarning, submitButtonLabelKey: "general_continue", + submitButtonStyle: "warning", closeButtonLabelKey: "general_cancel", submit: function () { openDocTypeEditor(documentType); @@ -322,11 +323,14 @@ // find the urls for the currently selected language if (scope.node.variants.length > 1) { // nodes with variants - scope.currentUrls = _.filter(scope.node.urls, (url) => scope.currentVariant.language.culture === url.culture); + scope.currentUrls = _.filter(scope.node.urls, (url) => (scope.currentVariant.language && scope.currentVariant.language.culture === url.culture)); } else { // invariant nodes scope.currentUrls = scope.node.urls; } + + // figure out if multiple cultures apply across the content urls + scope.currentUrlsHaveMultipleCultures = _.keys(_.groupBy(scope.currentUrls, url => url.culture)).length > 1; } // load audit trail and redirects when on the info tab diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbtabbedcontent.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbtabbedcontent.directive.js index 06f426889f..3aa0470262 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbtabbedcontent.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbtabbedcontent.directive.js @@ -4,7 +4,7 @@ /** This directive is used to render out the current variant tabs and properties and exposes an API for other directives to consume */ function tabbedContentDirective($timeout) { - function link($scope, $element, $attrs) { + function link($scope, $element) { var appRootNode = $element[0]; @@ -115,21 +115,18 @@ } - function controller($scope, $element, $attrs) { - + function controller($scope) { //expose the property/methods for other directives to use this.content = $scope.content; - this.activeVariant = _.find(this.content.variants, variant => { - return variant.active; - }); - - $scope.activeVariant = this.activeVariant; - - $scope.defaultVariant = _.find(this.content.variants, variant => { - return variant.language.isDefault; - }); - + + if($scope.contentNodeModel) { + $scope.defaultVariant = _.find($scope.contentNodeModel.variants, variant => { + // defaultVariant will never have segment. Wether it has a language or not depends on the setup. + return !variant.segment && ((variant.language && variant.language.isDefault) || (!variant.language)); + }); + } + $scope.unlockInvariantValue = function(property) { property.unlockInvariantValue = !property.unlockInvariantValue; }; @@ -141,6 +138,24 @@ } } ); + + $scope.propertyEditorDisabled = function (property) { + if (property.unlockInvariantValue) { + return false; + } + + var contentLanguage = $scope.content.language; + + var canEditCulture = !contentLanguage || + // If the property culture equals the content culture it can be edited + property.culture === contentLanguage.culture || + // A culture-invariant property can only be edited by the default language variant + (property.culture == null && contentLanguage.isDefault); + + var canEditSegment = property.segment === $scope.content.segment; + + return !canEditCulture || !canEditSegment; + } } var directive = { @@ -150,7 +165,8 @@ controller: controller, link: link, scope: { - content: "=" + content: "=", // in this context the content is the variant model. + contentNodeModel: "=?" //contentNodeModel is the content model for the node, } }; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontent.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontent.directive.js index 5dc8903e65..2d3a8e2238 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontent.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontent.directive.js @@ -12,7 +12,6 @@ editor: "<", editorIndex: "<", editorCount: "<", - openVariants: "<", onCloseSplitView: "&", onSelectVariant: "&", onOpenSplitView: "&", @@ -25,7 +24,7 @@ controller: umbVariantContentController }; - function umbVariantContentController($scope, $element, $location) { + function umbVariantContentController($scope) { var unsubscribe = []; @@ -42,13 +41,14 @@ vm.showBackButton = showBackButton; function onInit() { - // disable the name field if the active content app is not "Content" - vm.nameDisabled = false; - angular.forEach(vm.editor.content.apps, function(app){ - if(app.active && app.alias !== "umbContent" && app.alias !== "umbInfo" && app.alias !== "umbListView") { - vm.nameDisabled = true; - } - }); + + // Make copy of apps, so we can have a variant specific model for the App. (needed for validation etc.) + vm.editor.variantApps = Utilities.copy(vm.content.apps); + + var activeApp = vm.content.apps.find((app) => app.active); + + onAppChanged(activeApp); + } function showBackButton() { @@ -58,9 +58,9 @@ /** Called when the component has linked all elements, this is when the form controller is available */ function postLink() { //set the content to dirty if the header changes - unsubscribe.push($scope.$watch("contentHeaderForm.$dirty", - function(newValue, oldValue) { - if (newValue === true) { + unsubscribe.push($scope.$watch("vm.editor.content.name", + function (newValue, oldValue) { + if (newValue !== oldValue) { vm.editor.content.isDirty = true; } })); @@ -94,14 +94,23 @@ } $scope.$on("editors.apps.appChanged", function($event, $args) { - var app = $args.app; - // disable the name field if the active content app is not "Content" or "Info" - vm.nameDisabled = false; - if(app && app.alias !== "umbContent" && app.alias !== "umbInfo" && app.alias !== "umbListView") { - vm.nameDisabled = true; - } + var activeApp = $args.app; + + // sync varaintApps active with new active. + _.forEach(vm.editor.variantApps, function (app) { + app.active = (app.alias === activeApp.alias); + }); + + onAppChanged(activeApp); }); + function onAppChanged(activeApp) { + + // disable the name field if the active content app is not "Content" or "Info" + vm.nameDisabled = (activeApp && activeApp.alias !== "umbContent" && activeApp.alias !== "umbInfo" && activeApp.alias !== "umbListView"); + + } + /** * Used to proxy a callback * @param {any} item diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontenteditors.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontenteditors.directive.js index a4dac046e5..a188a83d83 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontenteditors.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontenteditors.directive.js @@ -8,8 +8,9 @@ templateUrl: 'views/components/content/umb-variant-content-editors.html', bindings: { page: "<", - content: "<", // TODO: Not sure if this should be = since we are changing the 'active' property of a variant + content: "<", culture: "<", + segment: "<", onSelectApp: "&?", onSelectAppAnchor: "&?", onBack: "&?", @@ -19,12 +20,11 @@ controller: umbVariantContentEditorsController }; - function umbVariantContentEditorsController($scope, $location, $timeout) { + function umbVariantContentEditorsController($scope, $location, contentEditingHelper) { var prevContentDateUpdated = null; var vm = this; - var activeAppAlias = null; vm.$onInit = onInit; vm.$onChanges = onChanges; @@ -39,13 +39,11 @@ //Used to track how many content views there are (for split view there will be 2, it could support more in theory) vm.editors = []; - //Used to track the open variants across the split views - vm.openVariants = []; /** Called when the component initializes */ function onInit() { - prevContentDateUpdated = angular.copy(vm.content.updateDate); - setActiveCulture(); + prevContentDateUpdated = Utilities.copy(vm.content.updateDate); + setActiveVariant(); } /** Called when the component has linked all elements, this is when the form controller is available */ @@ -60,15 +58,17 @@ function onChanges(changes) { if (changes.culture && !changes.culture.isFirstChange() && changes.culture.currentValue !== changes.culture.previousValue) { - setActiveCulture(); + setActiveVariant(); + } else if (changes.segment && !changes.segment.isFirstChange() && changes.segment.currentValue !== changes.segment.previousValue) { + setActiveVariant(); } } /** Allows us to deep watch whatever we want - executes on every digest cycle */ function doCheck() { if (!angular.equals(vm.content.updateDate, prevContentDateUpdated)) { - setActiveCulture(); - prevContentDateUpdated = angular.copy(vm.content.updateDate); + setActiveVariant(); + prevContentDateUpdated = Utilities.copy(vm.content.updateDate); } } @@ -79,37 +79,32 @@ } /** - * Set the active variant based on the current culture (query string) + * Set the active variant based on the current culture or segment (query string) */ - function setActiveCulture() { + function setActiveVariant() { // set the active variant var activeVariant = null; _.each(vm.content.variants, function (v) { - if (v.language && v.language.culture === vm.culture) { - v.active = true; + if ((vm.culture === "invariant" || v.language && v.language.culture === vm.culture) && v.segment === vm.segment) { activeVariant = v; } - else { - v.active = false; - } }); if (!activeVariant) { // Set the first variant to active if we can't find it. // If the content item is invariant, then only one item exists in the array. - vm.content.variants[0].active = true; activeVariant = vm.content.variants[0]; } - insertVariantEditor(0, initVariant(activeVariant, 0)); + insertVariantEditor(0, activeVariant); if (vm.editors.length > 1) { //now re-sync any other editor content (i.e. if split view is open) for (var s = 1; s < vm.editors.length; s++) { //get the variant from the scope model var variant = _.find(vm.content.variants, function (v) { - return v.language.culture === vm.editors[s].content.language.culture; + return (!v.language || v.language.culture === vm.editors[s].content.language.culture) && v.segment === vm.editors[s].content.segment; }); - vm.editors[s].content = initVariant(variant, s); + vm.editors[s].content = variant; } } @@ -122,157 +117,84 @@ */ function insertVariantEditor(index, variant) { + if (vm.editors[index]) { + if (vm.editors[index].content === variant) { + // This variant is already the content of the editor in this index. + return; + } + vm.editors[index].content.active = false; + } + variant.active = true; + var variantCulture = variant.language ? variant.language.culture : "invariant"; + var variantSegment = variant.segment; - //check if the culture at the index is the same, if it's null an editor will be added - var currentCulture = vm.editors.length === 0 || vm.editors.length <= index ? null : vm.editors[index].culture; + var currentCulture = index < vm.editors.length ? vm.editors[index].culture : null; + var currentSegment = index < vm.editors.length ? vm.editors[index].segment : null; + + // if index not already exists or if the culture or segment isnt identical then we do a replacement. + if (index >= vm.editors.length || currentCulture !== variantCulture || currentSegment !== variantSegment) { - if (currentCulture !== variantCulture) { - //Not the current culture which means we need to modify the array. + //Not the current culture or segment which means we need to modify the array. //NOTE: It is not good enough to just replace the `content` object at a given index in the array // since that would mean that directives are not re-initialized. vm.editors.splice(index, 1, { + compositeId: variant.compositeId, content: variant, - //used for "track-by" ng-repeat - culture: variantCulture + culture: variantCulture, + segment: variantSegment }); } else { - //replace the editor for the same culture + //replace the content of the editor, since the culture and segment is the same. vm.editors[index].content = variant; } + } - - function initVariant(variant, editorIndex) { - //The model that is assigned to the editor contains the current content variant along - //with a copy of the contentApps. This is required because each editor renders it's own - //header and content apps section and the content apps contains the view for editing content itself - //and we need to assign a view model to the subView so that it is scoped to the current - //editor so that split views work. - - //copy the apps from the main model if not assigned yet to the variant - if (!variant.apps) { - variant.apps = angular.copy(vm.content.apps); - } - - //if this is a variant has a culture/language than we need to assign the language drop down info - if (variant.language) { - //if the variant list that defines the header drop down isn't assigned to the variant then assign it now - if (!variant.variants) { - variant.variants = _.map(vm.content.variants, - function (v) { - return _.pick(v, "active", "language", "state"); - }); - } - else { - //merge the scope variants on top of the header variants collection (handy when needing to refresh) - angular.extend(variant.variants, - _.map(vm.content.variants, - function (v) { - return _.pick(v, "active", "language", "state"); - })); - } - - //ensure the current culture is set as the active one - for (var i = 0; i < variant.variants.length; i++) { - if (variant.variants[i].language.culture === variant.language.culture) { - variant.variants[i].active = true; - } - else { - variant.variants[i].active = false; - } - } - - // keep track of the open variants across the different split views - // push the first variant then update the variant index based on the editor index - if(vm.openVariants && vm.openVariants.length === 0) { - vm.openVariants.push(variant.language.culture); - } else { - vm.openVariants[editorIndex] = variant.language.culture; - } - - } - - //then assign the variant to a view model to the content app - var contentApp = _.find(variant.apps, function (a) { - return a.alias === "umbContent"; - }); - - if (contentApp) { - //The view model for the content app is simply the index of the variant being edited - var variantIndex = vm.content.variants.indexOf(variant); - contentApp.viewModel = variantIndex; - } - - // make sure the same app it set to active in the new variant - if(activeAppAlias) { - angular.forEach(variant.apps, function(app) { - app.active = false; - if(app.alias === activeAppAlias) { - app.active = true; - } - }); - } - - return variant; - } + /** * Adds a new editor to the editors array to show content in a split view * @param {any} selectedVariant */ function openSplitView(selectedVariant) { - var selectedCulture = selectedVariant.language.culture; + // enforce content contentApp in splitview. + var contentApp = vm.content.apps.find((app) => app.alias === "umbContent"); + if(contentApp) { + selectApp(contentApp); + } + + insertVariantEditor(vm.editors.length, selectedVariant); + + splitViewChanged(); + + } + + $scope.$on("editors.content.splitViewRequest", function(event, args) {requestSplitView(args);}); + vm.requestSplitView = requestSplitView; + function requestSplitView(args) { + var culture = args.culture; + var segment = args.segment; - //Find the whole variant model based on the culture that was chosen var variant = _.find(vm.content.variants, function (v) { - return v.language.culture === selectedCulture; + return (!v.language || v.language.culture === culture) && v.segment === segment; }); - insertVariantEditor(vm.editors.length, initVariant(variant, vm.editors.length)); - - //only the content app can be selected since no other apps are shown, and because we copy all of these apps - //to the "editors" we need to update this across all editors - for (var e = 0; e < vm.editors.length; e++) { - var editor = vm.editors[e]; - for (var i = 0; i < editor.content.apps.length; i++) { - var app = editor.content.apps[i]; - if (app.alias === "umbContent") { - app.active = true; - // tell the world that the app has changed (but do it only once) - if (e === 0) { - selectApp(app); - } - } - else { - app.active = false; - } - } + if (variant != null) { + openSplitView(variant); } - - // TODO: hacking animation states - these should hopefully be easier to do when we upgrade angular - editor.collapsed = true; - editor.loading = true; - $timeout(function () { - editor.collapsed = false; - editor.loading = false; - splitViewChanged(); - }, 100); } /** Closes the split view */ function closeSplitView(editorIndex) { // TODO: hacking animation states - these should hopefully be easier to do when we upgrade angular var editor = vm.editors[editorIndex]; - editor.loading = true; - editor.collapsed = true; - $timeout(function () { - vm.editors.splice(editorIndex, 1); - //remove variant from open variants - vm.openVariants.splice(editorIndex, 1); - //update the current culture to reflect the last open variant (closing the split view corresponds to selecting the other variant) - $location.search("cculture", vm.openVariants[0]); - splitViewChanged(); - }, 400); + vm.editors.splice(editorIndex, 1); + editor.content.active = false; + + //update the current culture to reflect the last open variant (closing the split view corresponds to selecting the other variant) + + $location.search({"cculture": vm.editors[0].content.language ? vm.editors[0].content.language.culture : null, "csegment": vm.editors[0].content.segment}); + splitViewChanged(); } /** @@ -282,8 +204,11 @@ */ function selectVariant(variant, editorIndex) { - // prevent variants already open in a split view to be opened - if(vm.openVariants.indexOf(variant.language.culture) !== -1) { + var variantCulture = variant.language ? variant.language.culture : "invariant"; + var variantSegment = variant.segment || null; + + // Check if we already have this editor open, if so, do nothing. + if (vm.editors.find((editor) => (!editor.content.language || editor.content.language.culture === variantCulture) && editor.content.segment === variantSegment)) { return; } @@ -292,27 +217,12 @@ if (editorIndex === 0) { //If we've made it this far, then update the query string. //The editor will respond to this query string changing. - $location.search("cculture", variant.language.culture); + $location.search("cculture", variantCulture).search("csegment", variantSegment); } else { - //Update the 'active' variant for this editor - var editor = vm.editors[editorIndex]; - //set all variant drop down items as inactive for this editor and then set the selected one as active - for (var i = 0; i < editor.content.variants.length; i++) { - editor.content.variants[i].active = false; - } - variant.active = true; - - //get the variant content model and initialize the editor with that - var contentVariant = _.find(vm.content.variants, - function (v) { - return v.language.culture === variant.language.culture; - }); - editor.content = initVariant(contentVariant, editorIndex); - //update the editors collection - insertVariantEditor(editorIndex, contentVariant); + insertVariantEditor(editorIndex, variant); } } @@ -332,14 +242,6 @@ vm.onSelectAppAnchor({"app": app, "anchor": anchor}); } } - - - $scope.$on("editors.apps.appChanged", function($event, $args) { - var app = $args.app; - if(app && app.alias) { - activeAppAlias = app.alias; - } - }); } 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 index 7755d9d63b..f80b3ceb3e 100644 --- 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 @@ -73,7 +73,7 @@ Use this directive to generate a list of breadcrumbs. var path = scope.pathTo(ancestor); $location.path(path); - navigationService.clearSearch(["cculture"]); + navigationService.clearSearch(["cculture", "csegment"]); } scope.pathTo = function (ancestor) { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorcontentheader.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorcontentheader.directive.js index b3948bd7c4..7bd812e321 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorcontentheader.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorcontentheader.directive.js @@ -2,11 +2,10 @@ 'use strict'; function EditorContentHeader(serverValidationManager, localizationService, editorState) { + function link(scope) { - - function link(scope, el, attr, ctrl) { var unsubscribe = []; - + if (!scope.serverValidationNameField) { scope.serverValidationNameField = "Name"; } @@ -14,19 +13,20 @@ scope.serverValidationAliasField = "Alias"; } - scope.isNew = scope.content.state == "NotCreated"; - - localizationService.localizeMany([ - scope.isNew ? "placeholders_a11yCreateItem" : "placeholders_a11yEdit", - "placeholders_a11yName", - scope.isNew ? "general_new" : "general_edit"] - ).then(function (data) { + scope.isNew = scope.editor.content.state == "NotCreated"; + localizationService.localizeMany( + [ + scope.isNew ? "placeholders_a11yCreateItem" : "placeholders_a11yEdit", + "placeholders_a11yName", + scope.isNew ? "general_new" : "general_edit" + ] + ).then(function (data) { scope.a11yMessage = data[0]; scope.a11yName = data[1]; var title = data[2] + ": "; if (!scope.isNew) { - scope.a11yMessage += " " + scope.content.name; + scope.a11yMessage += " " + scope.editor.content.name; title += scope.content.name; } else { var name = editorState.current.contentTypeName; @@ -34,199 +34,212 @@ scope.a11yName = name + " " + scope.a11yName; title += name; } - + scope.$emit("$changeTitle", title); }); scope.vm = {}; + scope.vm.hasVariants = false; + scope.vm.hasSubVariants = false; + scope.vm.hasCulture = false; + scope.vm.hasSegments = false; scope.vm.dropdownOpen = false; - scope.vm.currentVariant = ""; scope.vm.variantsWithError = []; scope.vm.defaultVariant = null; - scope.vm.errorsOnOtherVariants = false;// indicating wether to show that other variants, than the current, have errors. + + function updateVaraintErrors() { + scope.content.variants.forEach( function (variant) { + variant.hasError = scope.variantHasError(variant); + + }); + checkErrorsOnOtherVariants(); + } function checkErrorsOnOtherVariants() { var check = false; - angular.forEach(scope.content.variants, function (variant) { - if (scope.openVariants.indexOf(variant.language.culture) === -1 && scope.variantHasError(variant.language.culture)) { + scope.content.variants.forEach( function (variant) { + if (variant.active !== true && variant.hasError) { check = true; } }); scope.vm.errorsOnOtherVariants = check; } + + function onVariantValidation(valid, errors, allErrors, culture, segment) { - function onCultureValidation(valid, errors, allErrors, culture) { - var index = scope.vm.variantsWithError.indexOf(culture); - if (valid === true) { + // only want to react to property errors: + if(errors.findIndex(error => {return error.propertyAlias !== null;}) === -1) { + // we dont have any errors for properties, meaning we will back out. + return; + } + + // If error coming back is invariant, we will assign the error to the default variant by picking the defaultVariant language. + if(culture === "invariant") { + culture = scope.vm.defaultVariant.language.culture; + } + + var index = scope.vm.variantsWithError.findIndex((item) => item.culture === culture && item.segment === segment) + if(valid === true) { if (index !== -1) { scope.vm.variantsWithError.splice(index, 1); } } else { if (index === -1) { - scope.vm.variantsWithError.push(culture); + scope.vm.variantsWithError.push({"culture": culture, "segment": segment}); } } - checkErrorsOnOtherVariants(); + scope.$evalAsync(updateVaraintErrors); } - + function onInit() { - - // find default. - angular.forEach(scope.content.variants, function (variant) { - if (variant.language.isDefault) { + + // find default + check if we have variants. + scope.content.variants.forEach( function (variant) { + if (variant.language !== null && variant.language.isDefault) { scope.vm.defaultVariant = variant; } - }); - - setCurrentVariant(); - - angular.forEach(scope.content.apps, (app) => { - if (app.alias === "umbContent") { - app.anchors = scope.content.tabs; + if (variant.language !== null) { + scope.vm.hasCulture = true; + } + if (variant.segment !== null) { + scope.vm.hasSegments = true; } }); + scope.vm.hasVariants = (scope.vm.hasCulture || scope.vm.hasSegments); + scope.vm.hasSubVariants = (scope.vm.hasCulture && scope.vm.hasSegments); - angular.forEach(scope.content.variants, function (variant) { - unsubscribe.push(serverValidationManager.subscribe(null, variant.language.culture, null, onCultureValidation)); - }); + updateVaraintErrors(); - unsubscribe.push(serverValidationManager.subscribe(null, null, null, onCultureValidation)); - - - - } - - function setCurrentVariant() { - angular.forEach(scope.content.variants, function (variant) { - if (variant.active) { - scope.vm.currentVariant = variant; - checkErrorsOnOtherVariants(); + scope.vm.variantMenu = []; + if (scope.vm.hasCulture) { + scope.content.variants.forEach( (v) => { + if (v.language !== null && v.segment === null) { + var variantMenuEntry = { + key: String.CreateGuid(), + open: v.language && v.language.culture === scope.editor.culture, + variant: v, + subVariants: scope.content.variants.filter( (subVariant) => subVariant.language.culture === v.language.culture && subVariant.segment !== null) + }; + scope.vm.variantMenu.push(variantMenuEntry); + } + }); + } else { + scope.content.variants.forEach( (v) => { + scope.vm.variantMenu.push({ + key: String.CreateGuid(), + variant: v + }); + }); } - }); - } + + scope.editor.variantApps.forEach( (app) => { + if (app.alias === "umbContent") { + app.anchors = scope.editor.content.tabs; + } + }); - scope.goBack = function () { - if (scope.onBack) { - scope.onBack(); + scope.content.variants.forEach( function (variant) { + + // if we are looking for the variant with default language then we also want to check for invariant variant. + if (variant.language && variant.language.culture === scope.vm.defaultVariant.language.culture && variant.segment === null) { + unsubscribe.push(serverValidationManager.subscribe(null, "invariant", null, onVariantValidation, null)); + } + unsubscribe.push(serverValidationManager.subscribe(null, variant.language !== null ? variant.language.culture : null, null, onVariantValidation, variant.segment)); + }); + } - }; - scope.selectVariant = function (event, variant) { + scope.goBack = function () { + if (scope.onBack) { + scope.onBack(); + } + }; - if (scope.onSelectVariant) { - scope.vm.dropdownOpen = false; - scope.onSelectVariant({ "variant": variant }); + scope.selectVariant = function (event, variant) { + + if (scope.onSelectVariant) { + scope.vm.dropdownOpen = false; + scope.onSelectVariant({ "variant": variant }); + } + }; + + scope.selectNavigationItem = function(item) { + if(scope.onSelectNavigationItem) { + scope.onSelectNavigationItem({"item": item}); + } } - }; - scope.selectNavigationItem = function (item) { - if (scope.onSelectNavigationItem) { - scope.onSelectNavigationItem({ "item": item }); + scope.selectAnchorItem = function(item, anchor) { + if(scope.onSelectAnchorItem) { + scope.onSelectAnchorItem({"item": item, "anchor": anchor}); + } } - } - scope.selectAnchorItem = function (item, anchor) { - if (scope.onSelectAnchorItem) { - scope.onSelectAnchorItem({ "item": item, "anchor": anchor }); - } - } + scope.closeSplitView = function () { + if (scope.onCloseSplitView) { + scope.onCloseSplitView(); + } + }; - scope.closeSplitView = function () { - if (scope.onCloseSplitView) { - scope.onCloseSplitView(); - } - }; - - scope.openInSplitView = function (event, variant) { - if (scope.onOpenInSplitView) { - scope.vm.dropdownOpen = false; - scope.onOpenInSplitView({ "variant": variant }); - } - }; - - /** - * keep track of open variants - this is used to prevent the same variant to be open in more than one split view - * @param {any} culture - */ - scope.variantIsOpen = function (culture) { - return (scope.openVariants.indexOf(culture) !== -1); - } - - /** - * Check whether a variant has a error, used to display errors in variant switcher. - * @param {any} culture - */ - scope.variantHasError = function (culture) { - // if we are looking for the default language we also want to check for invariant. - if (culture === scope.vm.defaultVariant.language.culture) { - if (scope.vm.variantsWithError.indexOf("invariant") !== -1) { + scope.openInSplitView = function (event, variant) { + if (scope.onOpenInSplitView) { + scope.vm.dropdownOpen = false; + scope.onOpenInSplitView({ "variant": variant }); + } + }; + + /** + * Check whether a variant has a error, used to display errors in variant switcher. + * @param {any} culture + */ + scope.variantHasError = function(variant) { + if(scope.vm.variantsWithError.find((item) => (!variant.language || item.culture === variant.language.culture) && item.segment === variant.segment) !== undefined) { return true; } + return false; } - if (scope.vm.variantsWithError.indexOf(culture) !== -1) { - return true; - } - return false; - } - onInit(); - - //watch for the active culture changing, if it changes, update the current variant - if (scope.content.variants) { - scope.$watch(function () { - for (var i = 0; i < scope.content.variants.length; i++) { - var v = scope.content.variants[i]; - if (v.active) { - return v.language.culture; - } - } - return scope.vm.currentVariant.language.culture; //should never get here - }, function (newValue, oldValue) { - if (newValue !== scope.vm.currentVariant.language.culture) { - setCurrentVariant(); + onInit(); + + scope.$on('$destroy', function () { + for (var u in unsubscribe) { + unsubscribe[u](); } }); } - scope.$on('$destroy', function () { - for (var u in unsubscribe) { - unsubscribe[u](); - } - }); + + var directive = { + transclude: true, + restrict: 'E', + replace: true, + templateUrl: 'views/components/editor/umb-editor-content-header.html', + scope: { + name: "=", + nameDisabled: "'); + function resizeInput() { - 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) { - $timeout( - function() { - update(true); + if (domEl.scrollWidth !== domEl.clientWidth) { + if (ngModelController.$modelValue) { + element.width(domEl.scrollWidth); + } } - ); - }); - scope.$on('$destroy', function() { - element.off('keyup keydown keypress change', update); - element.off('blur', update(true)); - unbindModelWatcher(); + if (!ngModelController.$modelValue && attr.placeholder) { + attr.$set('size', attr.placeholder.length); + element.width('auto'); + } - // clean up IE dom element - if (isIEFlag === true && domElType === "text") { - mirror.remove(); - } + } - }); - } - }; - }); + 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 (domElType === "textarea") { + + resizeTextarea(); + + } else if (domElType === "text") { + + resizeInput(); + + } + + }; + + //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) { + $timeout( + function () { + update(true); + } + ); + }); + + scope.$on('$destroy', function () { + element.off('keyup keydown keypress change', update); + element.off('blur', update(true)); + unbindModelWatcher(); + }); + } + }; + }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbcheckbox.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbcheckbox.directive.js index d562b21d52..9a9d6d4a76 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbcheckbox.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbcheckbox.directive.js @@ -71,6 +71,7 @@ templateUrl: 'views/components/forms/umb-checkbox.html', controller: UmbCheckboxController, controllerAs: 'vm', + transclude: true, bindings: { model: "=", inputId: "@", diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbradiobutton.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbradiobutton.directive.js index 7ed84547f1..d79140f947 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbradiobutton.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbradiobutton.directive.js @@ -69,6 +69,7 @@ templateUrl: 'views/components/forms/umb-radiobutton.html', controller: UmbRadiobuttonController, controllerAs: 'vm', + transclude: true, bindings: { model: "=", inputId: "@", diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbrawmodel.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbrawmodel.directive.js index 9b479b60ae..271a499b09 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbrawmodel.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbrawmodel.directive.js @@ -9,91 +9,91 @@ will override element type to textarea and add own attribute ngModel tied to jso */ angular.module("umbraco.directives") - .directive('umbRawModel', function () { - return { - restrict: 'A', - require: 'ngModel', - template: '', - replace : true, - scope: { - model: '=umbRawModel', - validateOn:'=' - }, - link: function (scope, element, attrs, ngModelCtrl) { + .directive('umbRawModel', function () { + return { + restrict: 'A', + require: 'ngModel', + template: '', + replace: true, + scope: { + model: '=umbRawModel', + validateOn: '=' + }, + link: function (scope, element, attrs, ngModelCtrl) { - function setEditing (value) { - scope.jsonEditing = angular.copy( jsonToString(value)); - } + function setEditing(value) { + scope.jsonEditing = Utilities.copy(jsonToString(value)); + } - function updateModel (value) { - scope.model = stringToJson(value); - } + function updateModel(value) { + scope.model = stringToJson(value); + } - function setValid() { - ngModelCtrl.$setValidity('json', true); - } + function setValid() { + ngModelCtrl.$setValidity('json', true); + } - function setInvalid () { - ngModelCtrl.$setValidity('json', false); - } + function setInvalid() { + ngModelCtrl.$setValidity('json', false); + } - function stringToJson(text) { - try { - return angular.fromJson(text); - } catch (err) { - setInvalid(); - return text; - } - } + function stringToJson(text) { + try { + return JSON.parse(text); + } catch (err) { + setInvalid(); + return text; + } + } - function jsonToString(object) { - // better than JSON.stringify(), because it formats + filters $$hashKey etc. - // NOTE that this will remove all $-prefixed values - return angular.toJson(object, true); - } + function jsonToString(object) { + // better than JSON.stringify(), because it formats + filters $$hashKey etc. + // NOTE that this will remove all $-prefixed values + return Utilities.toJson(object, true); + } - function isValidJson(model) { - var flag = true; - try { - angular.fromJson(model); - } catch (err) { - flag = false; - } - return flag; - } + function isValidJson(model) { + var flag = true; + try { + JSON.parse(model) + } catch (err) { + flag = false; + } + return flag; + } - //init - setEditing(scope.model); + //init + setEditing(scope.model); - var onInputChange = function(newval,oldval){ - if (newval !== oldval) { - if (isValidJson(newval)) { - setValid(); - updateModel(newval); - } else { - setInvalid(); - } - } - }; + var onInputChange = function (newval, oldval) { + if (newval !== oldval) { + if (isValidJson(newval)) { + setValid(); + updateModel(newval); + } else { + setInvalid(); + } + } + }; - if(scope.validateOn){ - element.on(scope.validateOn, function(){ - scope.$apply(function(){ - onInputChange(scope.jsonEditing); - }); - }); - }else{ - //check for changes going out - scope.$watch('jsonEditing', onInputChange, true); - } + if (scope.validateOn) { + element.on(scope.validateOn, function () { + scope.$apply(function () { + onInputChange(scope.jsonEditing); + }); + }); + } else { + //check for changes going out + scope.$watch('jsonEditing', onInputChange, true); + } - //check for changes coming in - scope.$watch('model', function (newval, oldval) { - if (newval !== oldval) { - setEditing(newval); - } - }, true); + //check for changes coming in + scope.$watch('model', function (newval, oldval) { + if (newval !== oldval) { + setEditing(newval); + } + }, true); - } - }; - }); + } + }; + }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js index f606c0539a..3578624b50 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js @@ -29,7 +29,7 @@ angular.module("umbraco.directives") } var editorConfig = scope.configuration ? scope.configuration : null; - if (!editorConfig || angular.isString(editorConfig)) { + if (!editorConfig || Utilities.isString(editorConfig)) { editorConfig = tinyMceService.defaultPrevalues(); //for the grid by default, we don't want to include the macro toolbar editorConfig.toolbar = _.without(editorConfig, "umbmacro"); 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 index df3770056e..34bfb01019 100644 --- 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 @@ -30,7 +30,7 @@ angular.module("umbraco.directives") .directive('localize', function ($log, localizationService) { return { restrict: 'E', - scope:{ + scope: { key: '@', tokens: '=', watchTokens: '@' @@ -40,13 +40,13 @@ angular.module("umbraco.directives") link: function (scope, element, attrs) { var key = scope.key; scope.text = ""; - + // A render function to be able to update tokens as values update. function render() { element.html(localizationService.tokenReplace(scope.text, scope.tokens || null)); } - - localizationService.localize(key).then(function(value){ + + localizationService.localize(key).then(function (value) { scope.text = value; render(); }); @@ -64,19 +64,19 @@ angular.module("umbraco.directives") //Support one or more attribute properties to update var keys = attrs.localize.split(','); - angular.forEach(keys, function(value, key){ + keys.forEach((value, key) => { var attr = element.attr(value); - if(attr){ - if(attr[0] === '@'){ + if (attr) { + if (attr[0] === '@') { //If the translation key starts with @ then remove it attr = attr.substring(1); } var t = localizationService.tokenize(attr, scope); - - localizationService.localize(t.key, t.tokens).then(function(val){ - element.attr(value, val); + + localizationService.localize(t.key, t.tokens).then(function (val) { + element.attr(value, val); }); } }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/media/umbmedianodeinfo.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/media/umbmedianodeinfo.directive.js index 4993b013c7..dfa1afc247 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/media/umbmedianodeinfo.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/media/umbmedianodeinfo.directive.js @@ -1,13 +1,30 @@ (function () { 'use strict'; - function MediaNodeInfoDirective($timeout, $location, eventsService, userService, dateHelper, editorService, mediaHelper) { + function MediaNodeInfoDirective($timeout, $location, eventsService, userService, dateHelper, editorService, mediaHelper, mediaResource, $q) { function link(scope, element, attrs, ctrl) { var evts = []; scope.allowChangeMediaType = false; + scope.loading = true; + + scope.changeContentPageNumber = changeContentPageNumber; + scope.contentOptions = {}; + scope.contentOptions.entityType = "DOCUMENT"; + scope.hasContentReferences = false; + + scope.changeMediaPageNumber = changeMediaPageNumber; + scope.mediaOptions = {}; + scope.mediaOptions.entityType = "MEDIA"; + scope.hasMediaReferences = false; + + scope.changeMemberPageNumber = changeMemberPageNumber; + scope.memberOptions = {}; + scope.memberOptions.entityType = "MEMBER"; + scope.hasMemberReferences = false; + function onInit() { @@ -94,6 +111,45 @@ setMediaExtension(); }); + function changeContentPageNumber(pageNumber) { + scope.contentOptions.pageNumber = pageNumber; + loadContentRelations(); + } + + function changeMediaPageNumber(pageNumber) { + scope.mediaOptions.pageNumber = pageNumber; + loadMediaRelations(); + } + + function changeMemberPageNumber(pageNumber) { + scope.memberOptions.pageNumber = pageNumber; + loadMemberRelations(); + } + + function loadContentRelations() { + return mediaResource.getPagedReferences(scope.node.id, scope.contentOptions) + .then(function (data) { + scope.contentReferences = data; + scope.hasContentReferences = data.items.length > 0; + }); + } + + function loadMediaRelations() { + return mediaResource.getPagedReferences(scope.node.id, scope.mediaOptions) + .then(function (data) { + scope.mediaReferences = data; + scope.hasMediaReferences = data.items.length > 0; + }); + } + + function loadMemberRelations() { + return mediaResource.getPagedReferences(scope.node.id, scope.memberOptions) + .then(function (data) { + scope.memberReferences = data; + scope.hasMemberReferences = data.items.length > 0; + }); + } + //ensure to unregister from all events! scope.$on('$destroy', function () { for (var e in evts) { @@ -102,6 +158,18 @@ }); onInit(); + + // load media type references when the 'info' tab is first activated/switched to + evts.push(eventsService.on("app.tabChange", function (event, args) { + $timeout(function () { + if (args.alias === "umbInfo") { + + $q.all([loadContentRelations(), loadMediaRelations(), loadMemberRelations()]).then(function () { + scope.loading = false; + }); + } + }); + })); } var directive = { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/overlays/umboverlay.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/overlays/umboverlay.directive.js index bc3993458e..ad396e7a9a 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/overlays/umboverlay.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/overlays/umboverlay.directive.js @@ -193,343 +193,351 @@ Opens an overlay to show a custom YSOD.
@param {string} position The overlay position ("left", "right", "center": "target"). **/ -(function() { - 'use strict'; +(function () { + 'use strict'; function OverlayDirective($timeout, formHelper, overlayHelper, localizationService, $q, $templateCache, $http, $compile) { - function link(scope, el, attr, ctrl) { + function link(scope, el, attr, ctrl) { - scope.directive = { - enableConfirmButton: false - }; - - var overlayNumber = 0; - var numberOfOverlays = 0; - var isRegistered = false; - - - var modelCopy = {}; - var unsubscribe = []; - - function activate() { - - setView(); - - setButtonText(); - - modelCopy = makeModelCopy(scope.model); - - $timeout(function() { - - if (scope.position === "target" && scope.model.event) { - setTargetPosition(); - - // update the position of the overlay on content changes - // as these affect the layout/size of the overlay - if ('ResizeObserver' in window) - { - var resizeObserver = new ResizeObserver(setTargetPosition); - var contentArea = document.getElementById("contentwrapper"); - resizeObserver.observe(el[0]); - if (contentArea) { - resizeObserver.observe(contentArea); - } - unsubscribe.push(function () { - resizeObserver.disconnect(); - }); - } - } - - // this has to be done inside a timeout to ensure the destroy - // event on other overlays is run before registering a new one - registerOverlay(); - - 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"; - } - - //if a custom parent scope is defined then we need to manually compile the view - if (scope.parentScope) { - var element = el.find(".scoped-view"); - $http.get(scope.view, { cache: $templateCache }) - .then(function (response) { - var templateScope = scope.parentScope.$new(); - unsubscribe.push(function() { - templateScope.$destroy(); - }); - templateScope.model = scope.model; - element.html(response.data); - element.show(); - $compile(element.contents())(templateScope); - }); - } - } - - } - - function setButtonText() { - - var labelKeys = [ - "general_close", - "general_submit" - ]; - - localizationService.localizeMany(labelKeys).then(function (values) { - if (!scope.model.closeButtonLabelKey && !scope.model.closeButtonLabel) { - scope.model.closeButtonLabel = values[0]; - } - if (!scope.model.submitButtonLabelKey && !scope.model.submitButtonLabel) { - scope.model.submitButtonLabel = values[1]; - } - }); - } - - function registerOverlay() { - - overlayNumber = overlayHelper.registerOverlay(); - - $(document).on("keydown.overlay-" + overlayNumber, function(event) { - - if (event.which === 27) { - - numberOfOverlays = overlayHelper.getNumberOfOverlays(); - - if (numberOfOverlays === overlayNumber && !scope.model.disableEscKey) { - scope.$apply(function () { - scope.closeOverLay(); - }); - } - - event.stopPropagation(); - 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"); - var submitOnEnterValue = submitOnEnter ? document.activeElement.getAttribute("overlay-submit-on-enter") : ""; - - if(clickableElements.indexOf(activeElementType) >= 0) { - // don't do anything, let the browser Enter key handle this - } else if(activeElementType === "TEXTAREA" && !submitOnEnter) { - - - } else if (submitOnEnter && submitOnEnterValue === "false") { - // don't do anything - }else { - scope.$apply(function () { - scope.submitForm(scope.model); - }); - event.preventDefault(); - } - - } - - } - - }); - - isRegistered = true; - - } - - function unregisterOverlay() { - - if(isRegistered) { - - overlayHelper.unregisterOverlay(); - - $(document).off("keydown.overlay-" + overlayNumber); - - isRegistered = false; - } - - } - - function makeModelCopy(object) { - - var newObject = {}; - - for (var key in object) { - if (key !== "event" && key !== "parentScope") { - newObject[key] = angular.copy(object[key]); - } - } - - return newObject; - - } - - function setOverlayIndent() { - - var overlayIndex = overlayNumber - 1; - var indentSize = overlayIndex * 20; - var overlayWidth = el[0].clientWidth; - - el.css('width', overlayWidth - indentSize); - - if(scope.position === "center" && overlayIndex > 0 || scope.position === "target" && overlayIndex > 0) { - var overlayTopPosition = el[0].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" + scope.directive = { + enableConfirmButton: false }; - // click position - mousePositionClickX = scope.model.event.pageX; - mousePositionClickY = scope.model.event.pageY; + var overlayNumber = 0; + var numberOfOverlays = 0; + var isRegistered = false; - // element size - elementHeight = el[0].clientHeight; - elementWidth = el[0].clientWidth; - // move element to this position - position.left = mousePositionClickX - (elementWidth / 2); - position.top = mousePositionClickY - (elementHeight / 2); + var modelCopy = {}; + var unsubscribe = []; + + function activate() { + setView(); + + setButtonText(); + + modelCopy = makeModelCopy(scope.model); + + $timeout(function () { + + if (scope.position === "target" && scope.model.event) { + setTargetPosition(); + + // update the position of the overlay on content changes + // as these affect the layout/size of the overlay + if ('ResizeObserver' in window) { + var resizeObserver = new ResizeObserver(setTargetPosition); + var contentArea = document.getElementById("contentwrapper"); + resizeObserver.observe(el[0]); + if (contentArea) { + resizeObserver.observe(contentArea); + } + unsubscribe.push(function () { + resizeObserver.disconnect(); + }); + } + } + + // this has to be done inside a timeout to ensure the destroy + // event on other overlays is run before registering a new one + registerOverlay(); + + setOverlayIndent(); + + focusOnOverlayHeading() + }); - // 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"; + // Ideally this would focus on the first natively focusable element in the overlay, but as the content can be dynamic, it is focusing on the heading. + function focusOnOverlayHeading() { + var heading = el.find(".umb-overlay__title"); + + if (heading) { + heading.focus(); + } } - // outside left - if (position.left < containerLeft) { - position.left = containerLeft + 10; - position.right = "inherit"; + function setView() { + + if (scope.view) { + + if (scope.view.indexOf(".html") === -1) { + var viewAlias = scope.view.toLowerCase(); + scope.view = "views/common/overlays/" + viewAlias + "/" + viewAlias + ".html"; + } + + //if a custom parent scope is defined then we need to manually compile the view + if (scope.parentScope) { + var element = el.find(".scoped-view"); + $http.get(scope.view, { cache: $templateCache }) + .then(function (response) { + var templateScope = scope.parentScope.$new(); + unsubscribe.push(function () { + templateScope.$destroy(); + }); + templateScope.model = scope.model; + element.html(response.data); + element.show(); + $compile(element.contents())(templateScope); + }); + } + } + } - // outside top - if (position.top < containerTop) { - position.top = 10; - position.bottom = "inherit"; + function setButtonText() { + + var labelKeys = [ + "general_close", + "general_submit" + ]; + + localizationService.localizeMany(labelKeys).then(function (values) { + if (!scope.model.closeButtonLabelKey && !scope.model.closeButtonLabel) { + scope.model.closeButtonLabel = values[0]; + } + if (!scope.model.submitButtonLabelKey && !scope.model.submitButtonLabel) { + scope.model.submitButtonLabel = values[1]; + } + }); } - el.css(position); - } + function registerOverlay() { - scope.submitForm = function(model) { - if(scope.model.submit) { - if (formHelper.submitForm({ scope: scope, skipValidation: scope.model.skipFormValidation})) { - - if (scope.model.confirmSubmit && scope.model.confirmSubmit.enable && !scope.directive.enableConfirmButton) { - //wrap in a when since we don't know if this is a promise or not - $q.when(scope.model.submit(model, modelCopy, scope.directive.enableConfirmButton)).then( - function() { - formHelper.resetForm({ scope: scope }); - }); - } else { - unregisterOverlay(); - //wrap in a when since we don't know if this is a promise or not - $q.when(scope.model.submit(model, modelCopy, scope.directive.enableConfirmButton)).then( - function() { - formHelper.resetForm({ scope: scope }); - }); - } + overlayNumber = overlayHelper.registerOverlay(); - } - } - }; + $(document).on("keydown.overlay-" + overlayNumber, function (event) { - scope.cancelConfirmSubmit = function() { - scope.model.confirmSubmit.show = false; - }; + if (event.which === 27) { - scope.closeOverLay = function() { + numberOfOverlays = overlayHelper.getNumberOfOverlays(); - unregisterOverlay(); + if (numberOfOverlays === overlayNumber && !scope.model.disableEscKey) { + scope.$apply(function () { + scope.closeOverLay(); + }); + } + + event.stopPropagation(); + 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"); + var submitOnEnterValue = submitOnEnter ? document.activeElement.getAttribute("overlay-submit-on-enter") : ""; + + if (clickableElements.indexOf(activeElementType) >= 0) { + // don't do anything, let the browser Enter key handle this + } else if (activeElementType === "TEXTAREA" && !submitOnEnter) { + + + } else if (submitOnEnter && submitOnEnterValue === "false") { + // don't do anything + } else { + scope.$apply(function () { + scope.submitForm(scope.model); + }); + event.preventDefault(); + } + + } + + } + + }); + + isRegistered = true; - if (scope.model && scope.model.close) { - scope.model = modelCopy; - scope.model.close(scope.model); - } else { - scope.model.show = false; - scope.model = null; } - }; + function unregisterOverlay() { + + if (isRegistered) { + + overlayHelper.unregisterOverlay(); + + $(document).off("keydown.overlay-" + overlayNumber); + + isRegistered = false; + } - scope.outSideClick = function() { - if(!scope.model.disableBackdropClick) { - scope.closeOverLay(); } + + function makeModelCopy(object) { + + var newObject = {}; + + for (var key in object) { + if (key !== "event" && key !== "parentScope") { + newObject[key] = Utilities.copy(object[key]); + } + } + + return newObject; + + } + + function setOverlayIndent() { + + var overlayIndex = overlayNumber - 1; + var indentSize = overlayIndex * 20; + var overlayWidth = el[0].clientWidth; + + el.css('width', overlayWidth - indentSize); + + if (scope.position === "center" && overlayIndex > 0 || scope.position === "target" && overlayIndex > 0) { + var overlayTopPosition = el[0].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" + }; + + // click position + mousePositionClickX = scope.model.event.pageX; + mousePositionClickY = scope.model.event.pageY; + + // element size + elementHeight = el[0].clientHeight; + elementWidth = el[0].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, skipValidation: scope.model.skipFormValidation })) { + + if (scope.model.confirmSubmit && scope.model.confirmSubmit.enable && !scope.directive.enableConfirmButton) { + //wrap in a when since we don't know if this is a promise or not + $q.when(scope.model.submit(model, modelCopy, scope.directive.enableConfirmButton)).then( + function () { + formHelper.resetForm({ scope: scope }); + }); + } else { + unregisterOverlay(); + //wrap in a when since we don't know if this is a promise or not + $q.when(scope.model.submit(model, modelCopy, scope.directive.enableConfirmButton)).then( + function () { + formHelper.resetForm({ scope: scope }); + }); + } + + } + } + }; + + scope.cancelConfirmSubmit = function () { + scope.model.confirmSubmit.show = false; + }; + + scope.closeOverLay = function () { + + unregisterOverlay(); + + if (scope.model && scope.model.close) { + scope.model = modelCopy; + scope.model.close(scope.model); + } else { + scope.model.show = false; + scope.model = null; + } + + }; + + scope.outSideClick = function () { + if (!scope.model.disableBackdropClick) { + scope.closeOverLay(); + } + }; + + unsubscribe.push(unregisterOverlay); + scope.$on('$destroy', function () { + for (var i = 0; i < unsubscribe.length; i++) { + unsubscribe[i](); + } + }); + + activate(); + + } + + var directive = { + transclude: true, + restrict: 'E', + replace: true, + templateUrl: 'views/components/overlays/umb-overlay.html', + scope: { + ngShow: "=", + model: "=", + view: "=", + position: "@", + size: "=?", + parentScope: "=?" + }, + link: link }; - unsubscribe.push(unregisterOverlay); - scope.$on('$destroy', function () { - for (var i = 0; i < unsubscribe.length; i++) { - unsubscribe[i](); - } - }); + return directive; + } - activate(); - - } - - var directive = { - transclude: true, - restrict: 'E', - replace: true, - templateUrl: 'views/components/overlays/umb-overlay.html', - scope: { - ngShow: "=", - model: "=", - view: "=", - position: "@", - size: "=?", - parentScope: "=?" - }, - link: link - }; - - return directive; - } - - angular.module('umbraco.directives').directive('umbOverlay', OverlayDirective); + angular.module('umbraco.directives').directive('umbOverlay', OverlayDirective); })(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tags/umbtagseditor.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tags/umbtagseditor.directive.js index 7ff7f7fa66..1308515e8e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tags/umbtagseditor.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tags/umbtagseditor.directive.js @@ -159,7 +159,7 @@ function configureViewModel(isInitLoad) { if (vm.value) { - if (angular.isString(vm.value) && vm.value.length > 0) { + if (Utilities.isString(vm.value) && vm.value.length > 0) { if (vm.config.storageType === "Json") { //json storage vm.viewModel = JSON.parse(vm.value); @@ -191,7 +191,7 @@ } } } - else if (angular.isArray(vm.value)) { + else if (Utilities.isArray(vm.value)) { vm.viewModel = vm.value; } } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js index 3d743c7e9a..5a7da80eff 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js @@ -87,7 +87,7 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use /** Helper function to emit tree events */ function emitEvent(eventName, args) { - if (registeredCallbacks[eventName] && angular.isArray(registeredCallbacks[eventName])) { + if (registeredCallbacks[eventName] && Utilities.isArray(registeredCallbacks[eventName])) { _.each(registeredCallbacks[eventName], function (c) { c(args);//call it }); @@ -100,7 +100,7 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use * @param {any} args either a string representing the 'section' or an object containing: 'section', 'treeAlias', 'customTreeParams', 'cacheKey' */ function load(args) { - if (angular.isString(args)) { + if (Utilities.isString(args)) { $scope.section = args; } else if (args) { @@ -147,7 +147,7 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use throw "args.path cannot be null"; } - if (angular.isString(args.path)) { + if (Utilities.isString(args.path)) { args.path = args.path.replace('"', '').split(','); } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js index 6f195dcc52..3e2e7e362e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js @@ -14,7 +14,8 @@ function treeSearchBox(localizationService, searchService, $q) { section: "@", datatypeKey: "@", hideSearchCallback: "=", - searchCallback: "=" + searchCallback: "=", + autoFocus: "=" }, restrict: "E", // restrict to an element replace: true, // replace the html element with the template diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbaceeditor.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbaceeditor.directive.js index cd1b1d8181..10fc44c2c6 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbaceeditor.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbaceeditor.directive.js @@ -24,40 +24,40 @@ // sets the ace worker path, if running from concatenated // or minified source - if (angular.isDefined(opts.workerPath)) { + if (Utilities.isDefined(opts.workerPath)) { var config = window.ace.require('ace/config'); config.set('workerPath', opts.workerPath); } // ace requires loading - if (angular.isDefined(opts.require)) { + if (Utilities.isDefined(opts.require)) { opts.require.forEach(function(n) { window.ace.require(n); }); } // Boolean options - if (angular.isDefined(opts.showGutter)) { + if (Utilities.isDefined(opts.showGutter)) { acee.renderer.setShowGutter(opts.showGutter); } - if (angular.isDefined(opts.useWrapMode)) { + if (Utilities.isDefined(opts.useWrapMode)) { session.setUseWrapMode(opts.useWrapMode); } - if (angular.isDefined(opts.showInvisibles)) { + if (Utilities.isDefined(opts.showInvisibles)) { acee.renderer.setShowInvisibles(opts.showInvisibles); } - if (angular.isDefined(opts.showIndentGuides)) { + if (Utilities.isDefined(opts.showIndentGuides)) { acee.renderer.setDisplayIndentGuides(opts.showIndentGuides); } - if (angular.isDefined(opts.useSoftTabs)) { + if (Utilities.isDefined(opts.useSoftTabs)) { session.setUseSoftTabs(opts.useSoftTabs); } - if (angular.isDefined(opts.showPrintMargin)) { + if (Utilities.isDefined(opts.showPrintMargin)) { acee.setShowPrintMargin(opts.showPrintMargin); } // commands - if (angular.isDefined(opts.disableSearch) && opts.disableSearch) { + if (Utilities.isDefined(opts.disableSearch) && opts.disableSearch) { acee.commands.addCommands([{ name: 'unfind', bindKey: { @@ -72,15 +72,15 @@ } // Basic options - if (angular.isString(opts.theme)) { + if (Utilities.isString(opts.theme)) { acee.setTheme('ace/theme/' + opts.theme); } - if (angular.isString(opts.mode)) { + if (Utilities.isString(opts.mode)) { session.setMode('ace/mode/' + opts.mode); } // Advanced options - if (angular.isDefined(opts.firstLineNumber)) { - if (angular.isNumber(opts.firstLineNumber)) { + if (Utilities.isDefined(opts.firstLineNumber)) { + if (Utilities.isNumber(opts.firstLineNumber)) { session.setOption('firstLineNumber', opts.firstLineNumber); } else if (angular.isFunction(opts.firstLineNumber)) { session.setOption('firstLineNumber', opts.firstLineNumber()); @@ -89,7 +89,7 @@ // advanced options var key, obj; - if (angular.isDefined(opts.advanced)) { + if (Utilities.isDefined(opts.advanced)) { for (key in opts.advanced) { // create a javascript object with the key and value obj = { @@ -102,7 +102,7 @@ } // advanced options for the renderer - if (angular.isDefined(opts.rendererOptions)) { + if (Utilities.isDefined(opts.rendererOptions)) { for (key in opts.rendererOptions) { // create a javascript object with the key and value obj = { @@ -206,7 +206,7 @@ */ var args = Array.prototype.slice.call(arguments, 1); - if (angular.isDefined(callback)) { + if (Utilities.isDefined(callback)) { scope.$evalAsync(function() { if (angular.isFunction(callback)) { callback(args); 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 index 4fc22c4b74..96ce8735eb 100644 --- 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 @@ -99,13 +99,18 @@ Use this directive to render a ui component for selecting child items to a paren @param {string} parentName (binding): The parent name. @param {string} parentIcon (binding): The parent icon. @param {number} parentId (binding): The parent id. -@param {callback} onRemove (binding): Callback when the remove button is clicked on an item. +@param {callback} onRemove (binding): Callback when removing an item.

The callback returns:

  • child: The selected item.
  • $index: The selected item index.
-@param {callback} onAdd (binding): Callback when the add button is clicked. +@param {callback} onAdd (binding): Callback when adding an item. +

The callback returns:

+
    +
  • $event: The select event.
  • +
+@param {callback} onSort (binding): Callback when sorting an item.

The callback returns:

  • $event: The select event.
  • @@ -174,20 +179,36 @@ Use this directive to render a ui component for selecting child items to a paren eventBindings.push(scope.$watch('parentName', function(newValue, oldValue){ if (newValue === oldValue) { return; } - if ( oldValue === undefined || newValue === undefined) { 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; } + if (oldValue === undefined || newValue === undefined) { return; } syncParentIcon(); })); + // sortable options for allowed child content types + scope.sortableOptions = { + axis: "y", + cancel: ".unsortable", + containment: "parent", + distance: 10, + opacity: 0.7, + tolerance: "pointer", + scroll: true, + zIndex: 6000, + update: function (e, ui) { + if(scope.onSort) { + scope.onSort(); + } + } + }; + // clean up scope.$on('$destroy', function(){ // unbind watchers @@ -209,7 +230,8 @@ Use this directive to render a ui component for selecting child items to a paren parentIcon: "=", parentId: "=", onRemove: "=", - onAdd: "=" + onAdd: "=", + onSort: "=" }, link: link }; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbdatetimepicker.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbdatetimepicker.directive.js index 5433f73fa6..0498b81963 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbdatetimepicker.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbdatetimepicker.directive.js @@ -67,37 +67,37 @@ Use this directive to render a date time picker @param {callback} onDayCreate (callback): Take full control of every date cell with theonDayCreate()hook. **/ -(function() { - 'use strict'; +(function () { + 'use strict'; - var umbDateTimePicker = { - template: '' + - '' + - '
    ' + - '
    ', - controller: umbDateTimePickerCtrl, - transclude: true, - bindings: { - ngModel: '<', - options: '<', - onSetup: '&?', - onChange: '&?', - onOpen: '&?', - onClose: '&?', - onMonthChange: '&?', - onYearChange: '&?', - onReady: '&?', - onValueUpdate: '&?', - onDayCreate: '&?' - } + var umbDateTimePicker = { + template: '' + + '' + + '
    ' + + '
    ', + controller: umbDateTimePickerCtrl, + transclude: true, + bindings: { + ngModel: '<', + options: '<', + onSetup: '&?', + onChange: '&?', + onOpen: '&?', + onClose: '&?', + onMonthChange: '&?', + onYearChange: '&?', + onReady: '&?', + onValueUpdate: '&?', + onDayCreate: '&?' + } }; - + function umbDateTimePickerCtrl($element, $timeout, $scope, assetsService, userService) { var ctrl = this; var userLocale = null; - ctrl.$onInit = function() { + ctrl.$onInit = function () { // load css file for the date picker assetsService.loadCss('lib/flatpickr/flatpickr.css', $scope).then(function () { @@ -113,27 +113,27 @@ Use this directive to render a date time picker }); }); - }; + }; - function grabElementAndRunFlatpickr() { - $timeout(function() { - var transcludeEl = $element.find('ng-transclude')[0]; - var element = transcludeEl.children[0]; + function grabElementAndRunFlatpickr() { + $timeout(function () { + var transcludeEl = $element.find('ng-transclude')[0]; + var element = transcludeEl.children[0]; - setDatepicker(element); - }, 0, true); - } + setDatepicker(element); + }, 0, true); + } - function setDatepicker(element) { - var fpLib = flatpickr ? flatpickr : FlatpickrInstance; + function setDatepicker(element) { + var fpLib = flatpickr ? flatpickr : FlatpickrInstance; - if (!fpLib) { - return console.warn('Unable to find any flatpickr installation'); - } + if (!fpLib) { + return console.warn('Unable to find any flatpickr installation'); + } var fpInstance; - setUpCallbacks(); + setUpCallbacks(); if (!ctrl.options.locale) { ctrl.options.locale = userLocale; @@ -149,101 +149,101 @@ Use this directive to render a date time picker }; fpInstance = new fpLib(element, ctrl.options); - - if (ctrl.onSetup) { - ctrl.onSetup({ - fpItem: fpInstance - }); - } - // If has ngModel set the date - if (ctrl.ngModel) { - fpInstance.setDate(ctrl.ngModel); - } + if (ctrl.onSetup) { + ctrl.onSetup({ + fpItem: fpInstance + }); + } - // destroy the flatpickr instance when the dom element is removed - angular.element(element).on('$destroy', function() { - fpInstance.destroy(); - }); + // If has ngModel set the date + if (ctrl.ngModel) { + fpInstance.setDate(ctrl.ngModel); + } - // Refresh the scope - $scope.$applyAsync(); - } + // destroy the flatpickr instance when the dom element is removed + $(element).on('$destroy', function () { + fpInstance.destroy(); + }); - function setUpCallbacks() { - // bind hook for onChange - if(ctrl.options && ctrl.onChange) { - ctrl.options.onChange = function(selectedDates, dateStr, instance) { - $timeout(function() { - ctrl.onChange({selectedDates: selectedDates, dateStr: dateStr, instance: instance}); - }); - }; - } + // Refresh the scope + $scope.$applyAsync(); + } - // bind hook for onOpen - if(ctrl.options && ctrl.onOpen) { - ctrl.options.onOpen = function(selectedDates, dateStr, instance) { - $timeout(function() { - ctrl.onOpen({selectedDates: selectedDates, dateStr: dateStr, instance: instance}); - }); - }; - } + function setUpCallbacks() { + // bind hook for onChange + if (ctrl.options && ctrl.onChange) { + ctrl.options.onChange = function (selectedDates, dateStr, instance) { + $timeout(function () { + ctrl.onChange({ selectedDates: selectedDates, dateStr: dateStr, instance: instance }); + }); + }; + } - // bind hook for onOpen - if(ctrl.options && ctrl.onClose) { - ctrl.options.onClose = function(selectedDates, dateStr, instance) { - $timeout(function() { - ctrl.onClose({selectedDates: selectedDates, dateStr: dateStr, instance: instance}); - }); - }; - } + // bind hook for onOpen + if (ctrl.options && ctrl.onOpen) { + ctrl.options.onOpen = function (selectedDates, dateStr, instance) { + $timeout(function () { + ctrl.onOpen({ selectedDates: selectedDates, dateStr: dateStr, instance: instance }); + }); + }; + } - // bind hook for onMonthChange - if(ctrl.options && ctrl.onMonthChange) { - ctrl.options.onMonthChange = function(selectedDates, dateStr, instance) { - $timeout(function() { - ctrl.onMonthChange({selectedDates: selectedDates, dateStr: dateStr, instance: instance}); - }); - }; - } + // bind hook for onOpen + if (ctrl.options && ctrl.onClose) { + ctrl.options.onClose = function (selectedDates, dateStr, instance) { + $timeout(function () { + ctrl.onClose({ selectedDates: selectedDates, dateStr: dateStr, instance: instance }); + }); + }; + } - // bind hook for onYearChange - if(ctrl.options && ctrl.onYearChange) { - ctrl.options.onYearChange = function(selectedDates, dateStr, instance) { - $timeout(function() { - ctrl.onYearChange({selectedDates: selectedDates, dateStr: dateStr, instance: instance}); - }); - }; - } + // bind hook for onMonthChange + if (ctrl.options && ctrl.onMonthChange) { + ctrl.options.onMonthChange = function (selectedDates, dateStr, instance) { + $timeout(function () { + ctrl.onMonthChange({ selectedDates: selectedDates, dateStr: dateStr, instance: instance }); + }); + }; + } - // bind hook for onReady - if(ctrl.options && ctrl.onReady) { - ctrl.options.onReady = function(selectedDates, dateStr, instance) { - $timeout(function() { - ctrl.onReady({selectedDates: selectedDates, dateStr: dateStr, instance: instance}); - }); - }; - } + // bind hook for onYearChange + if (ctrl.options && ctrl.onYearChange) { + ctrl.options.onYearChange = function (selectedDates, dateStr, instance) { + $timeout(function () { + ctrl.onYearChange({ selectedDates: selectedDates, dateStr: dateStr, instance: instance }); + }); + }; + } - // bind hook for onValueUpdate - if(ctrl.onValueUpdate) { - ctrl.options.onValueUpdate = function(selectedDates, dateStr, instance) { - $timeout(function() { - ctrl.onValueUpdate({selectedDates: selectedDates, dateStr: dateStr, instance: instance}); - }); - }; - } + // bind hook for onReady + if (ctrl.options && ctrl.onReady) { + ctrl.options.onReady = function (selectedDates, dateStr, instance) { + $timeout(function () { + ctrl.onReady({ selectedDates: selectedDates, dateStr: dateStr, instance: instance }); + }); + }; + } - // bind hook for onDayCreate - if(ctrl.onDayCreate) { - ctrl.options.onDayCreate = function(selectedDates, dateStr, instance) { - $timeout(function() { - ctrl.onDayCreate({selectedDates: selectedDates, dateStr: dateStr, instance: instance}); - }); - }; - } + // bind hook for onValueUpdate + if (ctrl.onValueUpdate) { + ctrl.options.onValueUpdate = function (selectedDates, dateStr, instance) { + $timeout(function () { + ctrl.onValueUpdate({ selectedDates: selectedDates, dateStr: dateStr, instance: instance }); + }); + }; + } - } + // bind hook for onDayCreate + if (ctrl.onDayCreate) { + ctrl.options.onDayCreate = function (selectedDates, dateStr, instance) { + $timeout(function () { + ctrl.onDayCreate({ selectedDates: selectedDates, dateStr: dateStr, instance: instance }); + }); + }; + } + + } } // umbFlatpickr (umb-flatpickr) is deprecated, but we keep it for backwards compatibility diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js index a9b9cc52b1..3f53a1e18c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js @@ -1,738 +1,740 @@ -(function() { - 'use strict'; +(function () { + 'use strict'; - function GroupsBuilderDirective(contentTypeHelper, contentTypeResource, mediaTypeResource, - dataTypeHelper, dataTypeResource, $filter, iconHelper, $q, $timeout, notificationsService, - localizationService, editorService, eventsService, overlayService) { - - function link(scope, el, attr, ctrl) { - - var eventBindings = []; - var validationTranslated = ""; - var tabNoSortOrderTranslated = ""; + function GroupsBuilderDirective(contentTypeHelper, contentTypeResource, mediaTypeResource, + dataTypeHelper, dataTypeResource, $filter, iconHelper, $q, $timeout, notificationsService, + localizationService, editorService, eventsService, overlayService) { - scope.dataTypeHasChanged = false; - scope.sortingMode = false; - scope.toolbar = []; - scope.sortableOptionsGroup = {}; - scope.sortableOptionsProperty = {}; - scope.sortingButtonKey = "general_reorder"; - scope.compositionsButtonState = "init"; + function link(scope, el, attr, ctrl) { - function activate() { + var eventBindings = []; + var validationTranslated = ""; + var tabNoSortOrderTranslated = ""; - setSortingOptions(); + scope.dataTypeHasChanged = false; + scope.sortingMode = false; + scope.toolbar = []; + scope.sortableOptionsGroup = {}; + scope.sortableOptionsProperty = {}; + scope.sortingButtonKey = "general_reorder"; + scope.compositionsButtonState = "init"; - // set placeholder property on each group - if (scope.model.groups.length !== 0) { - angular.forEach(scope.model.groups, function(group) { - addInitProperty(group); - }); - } + function activate() { - // add init tab - addInitGroup(scope.model.groups); + setSortingOptions(); - activateFirstGroup(scope.model.groups); - - // localize texts - localizationService.localize("validation_validation").then(function(value) { - validationTranslated = value; - }); - - localizationService.localize("contentTypeEditor_tabHasNoSortOrder").then(function(value) { - tabNoSortOrderTranslated = value; - }); - } - - function setSortingOptions() { - - scope.sortableOptionsGroup = { - axis: 'y', - 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 = { - axis: 'y', - 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; + // 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); + + activateFirstGroup(scope.model.groups); + + // localize texts + localizationService.localize("validation_validation").then(function (value) { + validationTranslated = value; + }); + + localizationService.localize("contentTypeEditor_tabHasNoSortOrder").then(function (value) { + tabNoSortOrderTranslated = value; + }); + } + + function setSortingOptions() { + + scope.sortableOptionsGroup = { + axis: 'y', + 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 = { + axis: 'y', + 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(); + } + }; + } - // store this tabs sort order as reference for the next - prevSortOrder = group.sortOrder; + function updateTabsSortOrder() { - } + var first = true; + var prevSortOrder = 0; - }); + scope.model.groups.map(function (group) { - } + var index = scope.model.groups.indexOf(group); - function filterAvailableCompositions(selectedContentType, selecting) { + if (group.tabState !== "init") { - //selecting = true if the user has check the item, false if the user has unchecked the item + // 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; + + } - var selectedContentTypeAliases = selecting ? - //the user has selected the item so add to the current list - _.union(scope.compositionsDialogModel.compositeContentTypes, [selectedContentType.alias]) : - //the user has unselected the item so remove from the current list - _.reject(scope.compositionsDialogModel.compositeContentTypes, function(i) { - return i === selectedContentType.alias; }); - //get the currently assigned property type aliases - ensure we pass these to the server side filer - var propAliasesExisting = _.filter(_.flatten(_.map(scope.model.groups, function(g) { - return _.map(g.properties, function(p) { - return p.alias; - }); - })), function (f) { - return f !== null && f !== undefined; - }); + } - //use a different resource lookup depending on the content type type - var resourceLookup = scope.contentType === "documentType" ? contentTypeResource.getAvailableCompositeContentTypes : mediaTypeResource.getAvailableCompositeContentTypes; + function filterAvailableCompositions(selectedContentType, selecting) { - return resourceLookup(scope.model.id, selectedContentTypeAliases, propAliasesExisting).then(function (filteredAvailableCompositeTypes) { - _.each(scope.compositionsDialogModel.availableCompositeContentTypes, function (current) { - //reset first - current.allowed = true; - //see if this list item is found in the response (allowed) list - var found = _.find(filteredAvailableCompositeTypes, function (f) { - return current.contentType.alias === f.contentType.alias; + //selecting = true if the user has check the item, false if the user has unchecked the item + + var selectedContentTypeAliases = selecting ? + //the user has selected the item so add to the current list + _.union(scope.compositionsDialogModel.compositeContentTypes, [selectedContentType.alias]) : + //the user has unselected the item so remove from the current list + _.reject(scope.compositionsDialogModel.compositeContentTypes, function (i) { + return i === selectedContentType.alias; }); - //allow if the item was found in the response (allowed) list - - // and ensure its set to allowed if it is currently checked, - // DO not allow if it's a locked content type. - current.allowed = scope.model.lockedCompositeContentTypes.indexOf(current.contentType.alias) === -1 && - (selectedContentTypeAliases.indexOf(current.contentType.alias) !== -1) || ((found !== null && found !== undefined) ? found.allowed : false); - + //get the currently assigned property type aliases - ensure we pass these to the server side filer + var propAliasesExisting = _.filter(_.flatten(_.map(scope.model.groups, function (g) { + return _.map(g.properties, function (p) { + return p.alias; + }); + })), function (f) { + return f !== null && f !== undefined; }); - }); - } - function updatePropertiesSortOrder() { + //use a different resource lookup depending on the content type type + var resourceLookup = scope.contentType === "documentType" ? contentTypeResource.getAvailableCompositeContentTypes : mediaTypeResource.getAvailableCompositeContentTypes; - angular.forEach(scope.model.groups, function(group){ - if( group.tabState !== "init" ) { - group.properties = contentTypeHelper.updatePropertiesSortOrder(group.properties); - } - }); + return resourceLookup(scope.model.id, selectedContentTypeAliases, propAliasesExisting).then(function (filteredAvailableCompositeTypes) { + _.each(scope.compositionsDialogModel.availableCompositeContentTypes, function (current) { + //reset first + current.allowed = true; + //see if this list item is found in the response (allowed) list + var found = _.find(filteredAvailableCompositeTypes, function (f) { + return current.contentType.alias === f.contentType.alias; + }); - } + //allow if the item was found in the response (allowed) list - + // and ensure its set to allowed if it is currently checked, + // DO not allow if it's a locked content type. + current.allowed = scope.model.lockedCompositeContentTypes.indexOf(current.contentType.alias) === -1 && + (selectedContentTypeAliases.indexOf(current.contentType.alias) !== -1) || ((found !== null && found !== undefined) ? found.allowed : false); + + }); + }); + } + + function updatePropertiesSortOrder() { + + angular.forEach(scope.model.groups, function (group) { + if (group.tabState !== "init") { + group.properties = contentTypeHelper.updatePropertiesSortOrder(group.properties); + } + }); + + } + + function setupAvailableContentTypesModel(result) { + scope.compositionsDialogModel.availableCompositeContentTypes = result; + //iterate each one and set it up + _.each(scope.compositionsDialogModel.availableCompositeContentTypes, function (c) { + //enable it if it's part of the selected model + if (scope.compositionsDialogModel.compositeContentTypes.indexOf(c.contentType.alias) !== -1) { + c.allowed = true; + } + + //set the inherited flags + c.inherited = false; + if (scope.model.lockedCompositeContentTypes.indexOf(c.contentType.alias) > -1) { + c.inherited = true; + } + // convert icons for composite content types + iconHelper.formatContentTypeIcons([c.contentType]); + }); + } + + /* ---------- DELETE PROMT ---------- */ + + scope.togglePrompt = function (object) { + object.deletePrompt = !object.deletePrompt; + }; + + scope.hidePrompt = function (object) { + object.deletePrompt = false; + }; + + /* ---------- TOOLBAR ---------- */ + + scope.toggleSortingMode = function (tool) { + + if (scope.sortingMode === true) { + + var sortOrderMissing = false; + + for (var i = 0; i < scope.model.groups.length; i++) { + var group = scope.model.groups[i]; + if (group.tabState !== "init" && group.sortOrder === undefined) { + sortOrderMissing = true; + group.showSortOrderMissing = true; + notificationsService.error(validationTranslated + ": " + group.name + " " + tabNoSortOrderTranslated); + } + } + + if (!sortOrderMissing) { + scope.sortingMode = false; + scope.sortingButtonKey = "general_reorder"; + } + + } else { + + scope.sortingMode = true; + scope.sortingButtonKey = "general_reorderDone"; - function setupAvailableContentTypesModel(result) { - scope.compositionsDialogModel.availableCompositeContentTypes = result; - //iterate each one and set it up - _.each(scope.compositionsDialogModel.availableCompositeContentTypes, function (c) { - //enable it if it's part of the selected model - if (scope.compositionsDialogModel.compositeContentTypes.indexOf(c.contentType.alias) !== -1) { - c.allowed = true; } - //set the inherited flags - c.inherited = false; - if (scope.model.lockedCompositeContentTypes.indexOf(c.contentType.alias) > -1) { - c.inherited = true; - } - // convert icons for composite content types - iconHelper.formatContentTypeIcons([c.contentType]); - }); - } + }; - /* ---------- DELETE PROMT ---------- */ + scope.openCompositionsDialog = function () { - scope.togglePrompt = function (object) { - object.deletePrompt = !object.deletePrompt; - }; + scope.compositionsDialogModel = { + contentType: scope.model, + compositeContentTypes: scope.model.compositeContentTypes, + view: "views/common/infiniteeditors/compositions/compositions.html", + size: "small", + submit: function () { - scope.hidePrompt = function (object) { - object.deletePrompt = false; - }; + // make sure that all tabs has an init property + if (scope.model.groups.length !== 0) { + angular.forEach(scope.model.groups, function (group) { + addInitProperty(group); + }); + } - /* ---------- TOOLBAR ---------- */ + // remove overlay + editorService.close(); - scope.toggleSortingMode = function(tool) { + }, + close: function (oldModel) { - if (scope.sortingMode === true) { + // reset composition changes + scope.model.groups = oldModel.contentType.groups; + scope.model.compositeContentTypes = oldModel.contentType.compositeContentTypes; - var sortOrderMissing = false; + // remove overlay + editorService.close(); - for (var i = 0; i < scope.model.groups.length; i++) { - var group = scope.model.groups[i]; - if (group.tabState !== "init" && group.sortOrder === undefined) { - sortOrderMissing = true; - group.showSortOrderMissing = true; - notificationsService.error(validationTranslated + ": " + group.name + " " + tabNoSortOrderTranslated); - } - } + }, + selectCompositeContentType: function (selectedContentType) { - if (!sortOrderMissing) { - scope.sortingMode = false; - scope.sortingButtonKey = "general_reorder"; - } + //first check if this is a new selection - we need to store this value here before any further digests/async + // because after that the scope.model.compositeContentTypes will be populated with the selected value. + var newSelection = scope.model.compositeContentTypes.indexOf(selectedContentType.alias) === -1; - } else { + if (newSelection) { + //merge composition with content type - scope.sortingMode = true; - scope.sortingButtonKey = "general_reorderDone"; + //use a different resource lookup depending on the content type type + var resourceLookup = scope.contentType === "documentType" ? contentTypeResource.getById : mediaTypeResource.getById; - } + resourceLookup(selectedContentType.id).then(function (composition) { + //based on the above filtering we shouldn't be able to select an invalid one, but let's be safe and + // double check here. + var overlappingAliases = contentTypeHelper.validateAddingComposition(scope.model, composition); + if (overlappingAliases.length > 0) { + //this will create an invalid composition, need to uncheck it + scope.compositionsDialogModel.compositeContentTypes.splice( + scope.compositionsDialogModel.compositeContentTypes.indexOf(composition.alias), 1); + //dissallow this until something else is unchecked + selectedContentType.allowed = false; + } + else { + contentTypeHelper.mergeCompositeContentType(scope.model, composition); + } - }; - - scope.openCompositionsDialog = function() { - - scope.compositionsDialogModel = { - contentType: scope.model, - compositeContentTypes: scope.model.compositeContentTypes, - view: "views/common/infiniteeditors/compositions/compositions.html", - size: "small", - submit: function() { - - // 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 - editorService.close(); - - }, - close: function(oldModel) { - - // reset composition changes - scope.model.groups = oldModel.contentType.groups; - scope.model.compositeContentTypes = oldModel.contentType.compositeContentTypes; - - // remove overlay - editorService.close(); - - }, - selectCompositeContentType: function (selectedContentType) { - - //first check if this is a new selection - we need to store this value here before any further digests/async - // because after that the scope.model.compositeContentTypes will be populated with the selected value. - var newSelection = scope.model.compositeContentTypes.indexOf(selectedContentType.alias) === -1; - - if (newSelection) { - //merge composition with content type - - //use a different resource lookup depending on the content type type - var resourceLookup = scope.contentType === "documentType" ? contentTypeResource.getById : mediaTypeResource.getById; - - resourceLookup(selectedContentType.id).then(function (composition) { - //based on the above filtering we shouldn't be able to select an invalid one, but let's be safe and - // double check here. - var overlappingAliases = contentTypeHelper.validateAddingComposition(scope.model, composition); - if (overlappingAliases.length > 0) { - //this will create an invalid composition, need to uncheck it - scope.compositionsDialogModel.compositeContentTypes.splice( - scope.compositionsDialogModel.compositeContentTypes.indexOf(composition.alias), 1); - //dissallow this until something else is unchecked - selectedContentType.allowed = false; + //based on the selection, we need to filter the available composite types list + filterAvailableCompositions(selectedContentType, newSelection).then(function () { + // TODO: Here we could probably re-enable selection if we previously showed a throbber or something + }); + }); } else { - contentTypeHelper.mergeCompositeContentType(scope.model, composition); + // split composition from content type + contentTypeHelper.splitCompositeContentType(scope.model, selectedContentType); + + //based on the selection, we need to filter the available composite types list + filterAvailableCompositions(selectedContentType, newSelection).then(function () { + // TODO: Here we could probably re-enable selection if we previously showed a throbber or something + }); } - //based on the selection, we need to filter the available composite types list - filterAvailableCompositions(selectedContentType, newSelection).then(function () { - // TODO: Here we could probably re-enable selection if we previously showed a throbber or something - }); - }); - } - else { - // split composition from content type - contentTypeHelper.splitCompositeContentType(scope.model, selectedContentType); + } + }; - //based on the selection, we need to filter the available composite types list - filterAvailableCompositions(selectedContentType, newSelection).then(function () { - // TODO: Here we could probably re-enable selection if we previously showed a throbber or something + //select which resource methods to use, eg document Type or Media Type versions + var availableContentTypeResource = scope.contentType === "documentType" ? contentTypeResource.getAvailableCompositeContentTypes : mediaTypeResource.getAvailableCompositeContentTypes; + var whereUsedContentTypeResource = scope.contentType === "documentType" ? contentTypeResource.getWhereCompositionIsUsedInContentTypes : mediaTypeResource.getWhereCompositionIsUsedInContentTypes; + var countContentTypeResource = scope.contentType === "documentType" ? contentTypeResource.getCount : mediaTypeResource.getCount; + + //get the currently assigned property type aliases - ensure we pass these to the server side filer + var propAliasesExisting = _.filter(_.flatten(_.map(scope.model.groups, function (g) { + return _.map(g.properties, function (p) { + return p.alias; }); + })), function (f) { + return f !== null && f !== undefined; + }); + scope.compositionsButtonState = "busy"; + $q.all([ + //get available composite types + availableContentTypeResource(scope.model.id, [], propAliasesExisting, scope.model.isElement).then(function (result) { + setupAvailableContentTypesModel(result); + }), + //get where used document types + whereUsedContentTypeResource(scope.model.id).then(function (whereUsed) { + //pass to the dialog model the content type eg documentType or mediaType + scope.compositionsDialogModel.section = scope.contentType; + //pass the list of 'where used' document types + scope.compositionsDialogModel.whereCompositionUsed = whereUsed; + }), + //get content type count + countContentTypeResource().then(function (result) { + scope.compositionsDialogModel.totalContentTypes = parseInt(result, 10); + }) + ]).then(function () { + //resolves when both other promises are done, now show it + editorService.open(scope.compositionsDialogModel); + scope.compositionsButtonState = "init"; + }); + + }; + + + scope.openDocumentType = function (documentTypeId) { + const editor = { + id: documentTypeId, + submit: function (model) { + const args = { node: scope.model }; + eventsService.emit("editors.documentType.reload", args); + editorService.close(); + }, + close: function () { + editorService.close(); + } + }; + editorService.documentTypeEditor(editor); + + }; + + /* ---------- 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); + + // push new init tab to the scope + addInitGroup(scope.model.groups); + }; + + 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.canRemoveGroup = function (group) { + return group.inherited !== true && _.find(group.properties, function (property) { return property.locked === true; }) == null; } - }; - //select which resource methods to use, eg document Type or Media Type versions - var availableContentTypeResource = scope.contentType === "documentType" ? contentTypeResource.getAvailableCompositeContentTypes : mediaTypeResource.getAvailableCompositeContentTypes; - var whereUsedContentTypeResource = scope.contentType === "documentType" ? contentTypeResource.getWhereCompositionIsUsedInContentTypes : mediaTypeResource.getWhereCompositionIsUsedInContentTypes; - var countContentTypeResource = scope.contentType === "documentType" ? contentTypeResource.getCount : mediaTypeResource.getCount; + scope.removeGroup = function (groupIndex) { + scope.model.groups.splice(groupIndex, 1); + }; - //get the currently assigned property type aliases - ensure we pass these to the server side filer - var propAliasesExisting = _.filter(_.flatten(_.map(scope.model.groups, function(g) { - return _.map(g.properties, function(p) { - return p.alias; - }); - })), function(f) { - return f !== null && f !== undefined; - }); - scope.compositionsButtonState = "busy"; - $q.all([ - //get available composite types - availableContentTypeResource(scope.model.id, [], propAliasesExisting, scope.model.isElement).then(function (result) { - setupAvailableContentTypesModel(result); - }), - //get where used document types - whereUsedContentTypeResource(scope.model.id).then(function (whereUsed) { - //pass to the dialog model the content type eg documentType or mediaType - scope.compositionsDialogModel.section = scope.contentType; - //pass the list of 'where used' document types - scope.compositionsDialogModel.whereCompositionUsed = whereUsed; - }), - //get content type count - countContentTypeResource().then(function(result) { - scope.compositionsDialogModel.totalContentTypes = parseInt(result, 10); - }) - ]).then(function() { - //resolves when both other promises are done, now show it - editorService.open(scope.compositionsDialogModel); - scope.compositionsButtonState = "init"; - }); + scope.updateGroupTitle = function (group) { + if (group.properties.length === 0) { + addInitProperty(group); + } + }; - }; + scope.changeSortOrderValue = function (group) { + + if (group.sortOrder !== undefined) { + group.showSortOrderMissing = false; + } + 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; + } + + function activateFirstGroup(groups) { + if (groups && groups.length > 0) { + var firstGroup = groups[0]; + if (!firstGroup.tabState || firstGroup.tabState === "inActive") { + firstGroup.tabState = "active"; + } + } + } + + /* ---------- PROPERTIES ---------- */ + + scope.addPropertyToActiveGroup = function () { + var group = _.find(scope.model.groups, group => group.tabState === "active"); + if (!group && scope.model.groups.length) { + group = scope.model.groups[0]; + } + + if (!group || !group.name) { + return; + } + + var property = _.find(group.properties, property => property.propertyState === "init"); + if (!property) { + return; + } + scope.addProperty(property, group); + } + + 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) { + + var oldPropertyModel = Utilities.copy(property); + if (oldPropertyModel.allowCultureVariant === undefined) { + // this is necessary for comparison when detecting changes to the property + oldPropertyModel.allowCultureVariant = scope.model.allowCultureVariant; + oldPropertyModel.alias = ""; + } + var propertyModel = Utilities.copy(property); + + var propertySettings = { + title: "Property settings", + property: propertyModel, + contentType: scope.contentType, + contentTypeName: scope.model.name, + contentTypeAllowCultureVariant: scope.model.allowCultureVariant, + contentTypeAllowSegmentVariant: scope.model.allowSegmentVariant, + view: "views/common/infiniteeditors/propertysettings/propertysettings.html", + size: "small", + submit: function (model) { + + property.inherited = false; + property.dialogIsOpen = false; + property.propertyState = "active"; + + // apply all property changes + property.label = propertyModel.label; + property.alias = propertyModel.alias; + property.description = propertyModel.description; + property.config = propertyModel.config; + property.editor = propertyModel.editor; + property.view = propertyModel.view; + property.dataTypeId = propertyModel.dataTypeId; + property.dataTypeIcon = propertyModel.dataTypeIcon; + property.dataTypeName = propertyModel.dataTypeName; + property.validation.mandatory = propertyModel.validation.mandatory; + property.validation.mandatoryMessage = propertyModel.validation.mandatoryMessage; + property.validation.pattern = propertyModel.validation.pattern; + property.validation.patternMessage = propertyModel.validation.patternMessage; + property.showOnMemberProfile = propertyModel.showOnMemberProfile; + property.memberCanEdit = propertyModel.memberCanEdit; + property.isSensitiveData = propertyModel.isSensitiveData; + property.isSensitiveValue = propertyModel.isSensitiveValue; + property.allowCultureVariant = propertyModel.allowCultureVariant; + property.allowSegmentVariant = propertyModel.allowSegmentVariant; + + // update existing data types + if (model.updateSameDataTypes) { + updateSameDataTypes(property); + } + + // close the editor + editorService.close(); + + // push new init property to group + addInitProperty(group); + + // set focus on init property + var numberOfProperties = group.properties.length; + group.properties[numberOfProperties - 1].focus = true; + + notifyChanged(); + }, + close: function () { + if (_.isEqual(oldPropertyModel, propertyModel) === false) { + localizationService.localizeMany(["general_confirm", "contentTypeEditor_propertyHasChanges", "general_cancel", "general_ok"]).then(function (data) { + const overlay = { + title: data[0], + content: data[1], + closeButtonLabel: data[2], + submitButtonLabel: data[3], + submitButtonStyle: "danger", + close: function () { + overlayService.close(); + }, + submit: function () { + // close the confirmation + overlayService.close(); + // close the editor + editorService.close(); + } + }; + + overlayService.open(overlay); + }); + } + else { + // remove the editor + editorService.close(); + } + } + }; + + // open property settings editor + editorService.open(propertySettings); + + // set property states + property.dialogIsOpen = true; + + } + }; + + scope.deleteProperty = function (tab, propertyIndex) { + + // remove property + tab.properties.splice(propertyIndex, 1); + + notifyChanged(); + }; + + function notifyChanged() { + eventsService.emit("editors.groupsBuilder.changed"); + } + + function addInitProperty(group) { + + var addInitPropertyBool = true; + var initProperty = { + label: null, + alias: null, + propertyState: "init", + validation: { + mandatory: false, + mandatoryMessage: null, + pattern: null, + patternMessage: 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; + + } + + }); + }); + } + + function hasPropertyOfDataTypeId(dataTypeId) { + + // look at each property + var result = _.filter(scope.model.groups, function (group) { + return _.filter(group.properties, function (property) { + return (property.dataTypeId === dataTypeId); + }); + }); + + return (result.length > 0); + } - scope.openDocumentType = function (documentTypeId) { - const editor = { - id: documentTypeId, - submit: function (model) { - const args = { node: scope.model }; - eventsService.emit("editors.documentType.reload", args); - editorService.close(); - }, - close: function () { - editorService.close(); - } - }; - editorService.documentTypeEditor(editor); + eventBindings.push(scope.$watch('model', function (newValue, oldValue) { + if (newValue !== undefined && newValue.groups !== undefined) { + activate(); + } + })); - }; + // clean up + eventBindings.push(eventsService.on("editors.dataTypeSettings.saved", function (name, args) { + if (hasPropertyOfDataTypeId(args.dataType.id)) { + scope.dataTypeHasChanged = true; + } + })); - /* ---------- GROUPS ---------- */ + // clean up + eventBindings.push(scope.$on('$destroy', function () { + for (var e in eventBindings) { + eventBindings[e](); + } + // if a dataType has changed, we want to notify which properties that are affected by this dataTypeSettings change + if (scope.dataTypeHasChanged === true) { + var args = { documentType: scope.model }; + eventsService.emit("editors.documentType.saved", args); + } + })); - 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); - - // push new init tab to the scope - addInitGroup(scope.model.groups); - }; - - 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.canRemoveGroup = function(group){ - return group.inherited !== true && _.find(group.properties, function(property) { return property.locked === true; }) == null; - } - - scope.removeGroup = function(groupIndex) { - scope.model.groups.splice(groupIndex, 1); - }; - - scope.updateGroupTitle = function(group) { - if (group.properties.length === 0) { - addInitProperty(group); - } - }; - - scope.changeSortOrderValue = function(group) { - - if (group.sortOrder !== undefined) { - group.showSortOrderMissing = false; - } - 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; - } - - function activateFirstGroup(groups) { - if (groups && groups.length > 0) { - var firstGroup = groups[0]; - if(!firstGroup.tabState || firstGroup.tabState === "inActive") { - firstGroup.tabState = "active"; - } - } - } - - /* ---------- PROPERTIES ---------- */ - - scope.addPropertyToActiveGroup = function () { - var group = _.find(scope.model.groups, group => group.tabState === "active"); - if (!group && scope.model.groups.length) { - group = scope.model.groups[0]; - } - - if (!group || !group.name) { - return; - } - - var property = _.find(group.properties, property => property.propertyState === "init"); - if (!property) { - return; - } - scope.addProperty(property, group); - } - - 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) { - - var oldPropertyModel = angular.copy(property); - if (oldPropertyModel.allowCultureVariant === undefined) { - // this is necessary for comparison when detecting changes to the property - oldPropertyModel.allowCultureVariant = scope.model.allowCultureVariant; - oldPropertyModel.alias = ""; - } - var propertyModel = angular.copy(property); - - var propertySettings = { - title: "Property settings", - property: propertyModel, - contentType: scope.contentType, - contentTypeName: scope.model.name, - contentTypeAllowCultureVariant: scope.model.allowCultureVariant, - view: "views/common/infiniteeditors/propertysettings/propertysettings.html", - size: "small", - submit: function(model) { - - property.inherited = false; - property.dialogIsOpen = false; - property.propertyState = "active"; - - // apply all property changes - property.label = propertyModel.label; - property.alias = propertyModel.alias; - property.description = propertyModel.description; - property.config = propertyModel.config; - property.editor = propertyModel.editor; - property.view = propertyModel.view; - property.dataTypeId = propertyModel.dataTypeId; - property.dataTypeIcon = propertyModel.dataTypeIcon; - property.dataTypeName = propertyModel.dataTypeName; - property.validation.mandatory = propertyModel.validation.mandatory; - property.validation.mandatoryMessage = propertyModel.validation.mandatoryMessage; - property.validation.pattern = propertyModel.validation.pattern; - property.validation.patternMessage = propertyModel.validation.patternMessage; - property.showOnMemberProfile = propertyModel.showOnMemberProfile; - property.memberCanEdit = propertyModel.memberCanEdit; - property.isSensitiveData = propertyModel.isSensitiveData; - property.isSensitiveValue = propertyModel.isSensitiveValue; - property.allowCultureVariant = propertyModel.allowCultureVariant; - - // update existing data types - if(model.updateSameDataTypes) { - updateSameDataTypes(property); - } - - // close the editor - editorService.close(); - - // push new init property to group - addInitProperty(group); - - // set focus on init property - var numberOfProperties = group.properties.length; - group.properties[numberOfProperties - 1].focus = true; - - notifyChanged(); + var directive = { + restrict: "E", + replace: true, + templateUrl: "views/components/umb-groups-builder.html", + scope: { + model: "=", + compositions: "=", + sorting: "=", + contentType: "@" }, - close: function() { - if(_.isEqual(oldPropertyModel, propertyModel) === false) { - localizationService.localizeMany(["general_confirm", "contentTypeEditor_propertyHasChanges", "general_cancel", "general_ok"]).then(function (data) { - const overlay = { - title: data[0], - content: data[1], - closeButtonLabel: data[2], - submitButtonLabel: data[3], - submitButtonStyle: "danger", - close: function () { - overlayService.close(); - }, - submit: function () { - // close the confirmation - overlayService.close(); - // close the editor - editorService.close(); - } - }; - - overlayService.open(overlay); - }); - } - else { - // remove the editor - editorService.close(); - } - } - }; - - // open property settings editor - editorService.open(propertySettings); - - // set property states - property.dialogIsOpen = true; - - } - }; - - scope.deleteProperty = function(tab, propertyIndex) { - - // remove property - tab.properties.splice(propertyIndex, 1); - - notifyChanged(); - }; - - function notifyChanged() { - eventsService.emit("editors.groupsBuilder.changed"); - } - - function addInitProperty(group) { - - var addInitPropertyBool = true; - var initProperty = { - label: null, - alias: null, - propertyState: "init", - validation: { - mandatory: false, - mandatoryMessage: null, - pattern: null, - patternMessage: null - } + link: link }; - // 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; - - } - - }); - }); - } - - function hasPropertyOfDataTypeId(dataTypeId) { - - // look at each property - var result = _.filter(scope.model.groups, function(group) { - return _.filter(group.properties, function(property) { - return (property.dataTypeId === dataTypeId); - }); - }); - - return (result.length > 0); - } - - - eventBindings.push(scope.$watch('model', function(newValue, oldValue) { - if (newValue !== undefined && newValue.groups !== undefined) { - activate(); - } - })); - - // clean up - eventBindings.push(eventsService.on("editors.dataTypeSettings.saved", function (name, args) { - if(hasPropertyOfDataTypeId(args.dataType.id)) { - scope.dataTypeHasChanged = true; - } - })); - - // clean up - eventBindings.push(scope.$on('$destroy', function() { - for(var e in eventBindings) { - eventBindings[e](); - } - // if a dataType has changed, we want to notify which properties that are affected by this dataTypeSettings change - if(scope.dataTypeHasChanged === true) { - var args = {documentType: scope.model}; - eventsService.emit("editors.documentType.saved", args); - } - })); - + return directive; } - 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); + angular.module('umbraco.directives').directive('umbGroupsBuilder', GroupsBuilderDirective); })(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbimagelazyload.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbimagelazyload.directive.js index 9d76993fd3..988f8fab24 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbimagelazyload.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbimagelazyload.directive.js @@ -31,7 +31,7 @@ Use this directive to lazy-load an image only when it is scrolled into view. **/ -(function() { +(function () { 'use strict'; function ImageLazyLoadDirective() { @@ -41,7 +41,7 @@ Use this directive to lazy-load an image only when it is scrolled into view. function link(scope, element, attrs) { const observer = new IntersectionObserver(loadImg); - const img = angular.element(element)[0]; + const img = element[0]; img.src = placeholder; observer.observe(img); 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 index 953a28bd99..2d3db7c162 100644 --- 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 @@ -120,26 +120,26 @@ When this combination is hit an overview is opened with shortcuts based on the m scope.toggleShortcutsOverlay = function () { - if(overlay) { + if (overlay) { scope.close(); } else { scope.open(); } - if(scope.onToggle) { + if (scope.onToggle) { scope.onToggle(); } }; - scope.open = function() { - if(!overlay) { + scope.open = function () { + if (!overlay) { overlay = { title: "Keyboard shortcuts", view: "keyboardshortcuts", hideSubmitButton: true, shortcuts: scope.model, - close: function() { + close: function () { scope.close(); } }; @@ -147,20 +147,20 @@ When this combination is hit an overview is opened with shortcuts based on the m } }; - scope.close = function() { - if(overlay) { + scope.close = function () { + if (overlay) { overlayService.close(); overlay = null; - if(scope.onClose) { + if (scope.onClose) { scope.onClose(); } } }; function onInit() { - angular.forEach(scope.model, function (shortcutGroup) { - angular.forEach(shortcutGroup.shortcuts, function (shortcut) { + scope.model.forEach(shortcutGroup => { + shortcutGroup.shortcuts.forEach(shortcut => { shortcut.platformKeys = []; // get shortcut keys for mac @@ -173,30 +173,29 @@ When this combination is hit an overview is opened with shortcuts based on the m } else if (shortcut.keys && shortcut && shortcut.keys.length > 0) { shortcut.platformKeys = shortcut.keys; } - - }); + }) }); } onInit(); - eventBindings.push(scope.$watch('model', function(newValue, oldValue){ + eventBindings.push(scope.$watch('model', function (newValue, oldValue) { if (newValue !== oldValue) { onInit(); } })); - eventBindings.push(scope.$watch('showOverlay', function(newValue, oldValue){ + eventBindings.push(scope.$watch('showOverlay', function (newValue, oldValue) { - if(newValue === oldValue) { + if (newValue === oldValue) { return; } - if(newValue === true) { + if (newValue === true) { scope.open(); } - if(newValue === false) { + if (newValue === false) { scope.close(); } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js index 1c4bf4d583..aa28d49c4a 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js @@ -314,7 +314,7 @@ Use this directive to generate a thumbnail grid of media items. }; var unbindItemsWatcher = scope.$watch('items', function(newValue, oldValue) { - if (angular.isArray(newValue)) { + if (Utilities.isArray(newValue)) { activate(); } }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js index 1142863bd0..66e03a7302 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js @@ -58,7 +58,9 @@ entityResource.getPagedChildren(miniListView.node.id, scope.entityType, miniListView.pagination) .then(function (data) { - + if (scope.onItemsLoaded) { + scope.onItemsLoaded({items: data.items}); + } // update children miniListView.children = data.items; _.each(miniListView.children, function(c) { @@ -208,6 +210,7 @@ startNodeId: "=", onSelect: "&", onClose: "&", + onItemsLoaded: "&", entityTypeFilter: "=" }, link: link diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbnestedcontent.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbnestedcontent.directive.js index 471714d30b..72dba3ca2f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbnestedcontent.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbnestedcontent.directive.js @@ -3,12 +3,12 @@ function () { var link = function ($scope) { - + // Clone the model because some property editors // do weird things like updating and config values // so we want to ensure we start from a fresh every // time, we'll just sync the value back when we need to - $scope.model = angular.copy($scope.ngModel); + $scope.model = Utilities.copy($scope.ngModel); $scope.nodeContext = $scope.model; // Find the selected tab @@ -31,7 +31,7 @@ // Tell inner controls we are submitting $scope.$broadcast("formSubmitting", { scope: $scope }); - + // Sync the values back angular.forEach($scope.ngModel.variants[0].tabs, function (tab) { if (tab.alias.toLowerCase() === selectedTab.alias.toLowerCase()) { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbpasswordtoggle.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbpasswordtoggle.directive.js deleted file mode 100644 index aac1b8dac1..0000000000 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbpasswordtoggle.directive.js +++ /dev/null @@ -1,37 +0,0 @@ -/** -@ngdoc directive -@name umbraco.directives.directive:umbPasswordToggle -@restrict E -@scope - -@description -Added in Umbraco v. 7.7.4: Use this directive to render a password toggle. - -**/ - -(function () { - 'use strict'; - - // comes from https://codepen.io/jakob-e/pen/eNBQaP - // works fine with Angular 1.6.5 - alas not with 1.1.5 - binding issue - - function PasswordToggleDirective($compile) { - - var directive = { - restrict: 'A', - scope: {}, - link: function(scope, elem, attrs) { - scope.tgl = function () { elem.attr("type", (elem.attr("type") === "text" ? "password" : "text")); } - var lnk = angular.element("Toggle"); - $compile(lnk)(scope); - elem.wrap("
    ").after(lnk); - } - }; - - return directive; - - } - - angular.module('umbraco.directives').directive('umbPasswordToggle', PasswordToggleDirective); - -})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbrangeslider.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbrangeslider.directive.js index 0003658600..21a1f181a6 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbrangeslider.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbrangeslider.directive.js @@ -57,13 +57,13 @@ For extra details about options and events take a look here: https://refreshless **/ -(function() { - 'use strict'; +(function () { + 'use strict'; - var umbRangeSlider = { + var umbRangeSlider = { template: '
    ', - controller: UmbRangeSliderController, - bindings: { + controller: UmbRangeSliderController, + bindings: { ngModel: '<', options: '<', onSetup: '&?', @@ -73,15 +73,15 @@ For extra details about options and events take a look here: https://refreshless onChange: '&?', onStart: '&?', onEnd: '&?' - } + } }; - - function UmbRangeSliderController($element, $timeout, $scope, assetsService) { - + + function UmbRangeSliderController($element, $timeout, $scope, assetsService) { + const ctrl = this; let sliderInstance = null; - ctrl.$onInit = function() { + ctrl.$onInit = function () { // load css file for the date picker assetsService.loadCss('lib/nouislider/nouislider.min.css', $scope); @@ -94,13 +94,13 @@ For extra details about options and events take a look here: https://refreshless }; - function grabElementAndRun() { - $timeout(function() { + function grabElementAndRun() { + $timeout(function () { const element = $element.find('.umb-range-slider')[0]; - setSlider(element); - }, 0, true); + setSlider(element); + }, 0, true); } - + function setSlider(element) { sliderInstance = element; @@ -117,82 +117,82 @@ For extra details about options and events take a look here: https://refreshless // create new slider noUiSlider.create(sliderInstance, options); - - if (ctrl.onSetup) { - ctrl.onSetup({ - slider: sliderInstance - }); + + if (ctrl.onSetup) { + ctrl.onSetup({ + slider: sliderInstance + }); } // If has ngModel set the date - if (ctrl.ngModel) { + if (ctrl.ngModel) { sliderInstance.noUiSlider.set(ctrl.ngModel); } // destroy the slider instance when the dom element is removed - angular.element(element).on('$destroy', function() { + $(element).on('$destroy', function () { sliderInstance.noUiSlider.off(); }); setUpCallbacks(); - // Refresh the scope - $scope.$applyAsync(); + // Refresh the scope + $scope.$applyAsync(); } - + function setUpCallbacks() { - if(sliderInstance) { + if (sliderInstance) { // bind hook for update - if(ctrl.onUpdate) { - sliderInstance.noUiSlider.on('update', function (values, handle, unencoded, tap, positions) { - $timeout(function() { - ctrl.onUpdate({values: values, handle: handle, unencoded: unencoded, tap: tap, positions: positions}); + if (ctrl.onUpdate) { + sliderInstance.noUiSlider.on('update', function (values, handle, unencoded, tap, positions) { + $timeout(function () { + ctrl.onUpdate({ values: values, handle: handle, unencoded: unencoded, tap: tap, positions: positions }); }); }); } // bind hook for slide - if(ctrl.onSlide) { - sliderInstance.noUiSlider.on('slide', function (values, handle, unencoded, tap, positions) { - $timeout(function() { - ctrl.onSlide({values: values, handle: handle, unencoded: unencoded, tap: tap, positions: positions}); + if (ctrl.onSlide) { + sliderInstance.noUiSlider.on('slide', function (values, handle, unencoded, tap, positions) { + $timeout(function () { + ctrl.onSlide({ values: values, handle: handle, unencoded: unencoded, tap: tap, positions: positions }); }); }); } // bind hook for set - if(ctrl.onSet) { - sliderInstance.noUiSlider.on('set', function (values, handle, unencoded, tap, positions) { - $timeout(function() { - ctrl.onSet({values: values, handle: handle, unencoded: unencoded, tap: tap, positions: positions}); + if (ctrl.onSet) { + sliderInstance.noUiSlider.on('set', function (values, handle, unencoded, tap, positions) { + $timeout(function () { + ctrl.onSet({ values: values, handle: handle, unencoded: unencoded, tap: tap, positions: positions }); }); }); } // bind hook for change - if(ctrl.onChange) { - sliderInstance.noUiSlider.on('change', function (values, handle, unencoded, tap, positions) { - $timeout(function() { - ctrl.onChange({values: values, handle: handle, unencoded: unencoded, tap: tap, positions: positions}); + if (ctrl.onChange) { + sliderInstance.noUiSlider.on('change', function (values, handle, unencoded, tap, positions) { + $timeout(function () { + ctrl.onChange({ values: values, handle: handle, unencoded: unencoded, tap: tap, positions: positions }); }); }); } // bind hook for start - if(ctrl.onStart) { - sliderInstance.noUiSlider.on('start', function (values, handle, unencoded, tap, positions) { - $timeout(function() { - ctrl.onStart({values: values, handle: handle, unencoded: unencoded, tap: tap, positions: positions}); + if (ctrl.onStart) { + sliderInstance.noUiSlider.on('start', function (values, handle, unencoded, tap, positions) { + $timeout(function () { + ctrl.onStart({ values: values, handle: handle, unencoded: unencoded, tap: tap, positions: positions }); }); }); } // bind hook for end - if(ctrl.onEnd) { - sliderInstance.noUiSlider.on('end', function (values, handle, unencoded, tap, positions) { - $timeout(function() { - ctrl.onEnd({values: values, handle: handle, unencoded: unencoded, tap: tap, positions: positions}); + if (ctrl.onEnd) { + sliderInstance.noUiSlider.on('end', function (values, handle, unencoded, tap, positions) { + $timeout(function () { + ctrl.onEnd({ values: values, handle: handle, unencoded: unencoded, tap: tap, positions: positions }); }); }); } @@ -201,7 +201,7 @@ For extra details about options and events take a look here: https://refreshless } } - + angular.module('umbraco.directives').component('umbRangeSlider', umbRangeSlider); - + })(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/users/changepassword.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/users/changepassword.directive.js index 97eb2bf708..8cbdabbf75 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/users/changepassword.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/users/changepassword.directive.js @@ -56,7 +56,7 @@ } //set the model defaults - if (!angular.isObject(vm.passwordValues)) { + if (!Utilities.isObject(vm.passwordValues)) { //if it's not an object then just create a new one vm.passwordValues = { newPassword: null, diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/util/autoscale.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/util/autoscale.directive.js index 029a4e420f..023692be86 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/util/autoscale.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/util/autoscale.directive.js @@ -21,7 +21,7 @@ angular.module("umbraco.directives") var totalOffset = 0; var offsety = parseInt(attrs.autoScale, 10); - var window = angular.element($window); + var window = $($window); if (offsety !== undefined) { totalOffset += offsety; } @@ -34,7 +34,7 @@ angular.module("umbraco.directives") el.height(window.height() - (el.offset().top + totalOffset)); } - var resizeCallback = function() { + var resizeCallback = function () { setElementSize(); }; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/util/disabletabindex.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/util/disabletabindex.directive.js index 759d05df71..d43282715e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/util/disabletabindex.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/util/disabletabindex.directive.js @@ -1,46 +1,46 @@ angular.module("umbraco.directives") .directive('disableTabindex', function (tabbableService) { - return { - restrict: 'A', //Can only be used as an attribute, - scope: { - "disableTabindex": "<" - }, - link: function (scope, element, attrs) { + return { + restrict: 'A', //Can only be used as an attribute, + scope: { + "disableTabindex": "<" + }, + link: function (scope, element, attrs) { - if(scope.disableTabindex) { - //Select the node that will be observed for mutations (native DOM element not jQLite version) - var targetNode = element[0]; + if (scope.disableTabindex) { + //Select the node that will be observed for mutations (native DOM element not jQLite version) + var targetNode = element[0]; - //Watch for DOM changes - so when the property editor subview loads in - //We can be notified its updated the child elements inside the DIV we are watching - var observer = new MutationObserver(domChange); + //Watch for DOM changes - so when the property editor subview loads in + //We can be notified its updated the child elements inside the DIV we are watching + var observer = new MutationObserver(domChange); - // Options for the observer (which mutations to observe) - var config = { attributes: true, childList: true, subtree: true }; + // Options for the observer (which mutations to observe) + var config = { attributes: true, childList: true, subtree: true }; - function domChange(mutationsList, observer) { - for(var mutation of mutationsList) { + function domChange(mutationsList, observer) { + for (var mutation of mutationsList) { - //DOM items have been added or removed - if (mutation.type == 'childList') { + //DOM items have been added or removed + if (mutation.type == 'childList') { - //Check if any child items in mutation.target contain an input - var childInputs = tabbableService.tabbable(mutation.target); + //Check if any child items in mutation.target contain an input + var childInputs = tabbableService.tabbable(mutation.target); - //For each item in childInputs - override or set HTML attribute tabindex="-1" - angular.forEach(childInputs, function(element){ - angular.element(element).attr('tabindex', '-1'); - }); + //For each item in childInputs - override or set HTML attribute tabindex="-1" + angular.forEach(childInputs, function (element) { + $(element).attr('tabindex', '-1'); + }); + } } } + + // Start observing the target node for configured mutations + //GO GO GO + observer.observe(targetNode, config); } - // Start observing the target node for configured mutations - //GO GO GO - observer.observe(targetNode, config); } - - } - }; -}); + }; + }); 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 deleted file mode 100644 index 7914dfc3f0..0000000000 --- a/src/Umbraco.Web.UI.Client/src/common/directives/util/konami.directive.js +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Konami Code directive for AngularJS - * @version v0.0.1 - * @license MIT License, https://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); - } - }; - }]); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertymsg.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertymsg.directive.js index 0369b4bd2e..30d3530efb 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertymsg.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertymsg.directive.js @@ -24,8 +24,7 @@ function valPropertyMsg(serverValidationManager, localizationService) { var hasError = false; //create properties on our custom scope so we can use it in our template - scope.errorMsg = ""; - + scope.errorMsg = ""; //the property form controller api var formCtrl = ctrl[0]; @@ -34,11 +33,16 @@ function valPropertyMsg(serverValidationManager, localizationService) { //the property controller api var umbPropCtrl = ctrl[2]; //the variants controller api - var umbVariantCtrl = ctrl[3]; + var umbVariantCtrl = ctrl[3]; var currentProperty = umbPropCtrl.property; scope.currentProperty = currentProperty; - var currentCulture = currentProperty.culture; + + var currentCulture = currentProperty.culture; + var currentSegment = currentProperty.segment; + + // validation object won't exist when editor loads outside the content form (ie in settings section when modifying a content type) + var isMandatory = currentProperty.validation ? currentProperty.validation.mandatory : undefined; var labels = {}; localizationService.localize("errors_propertyHasErrors").then(function (data) { @@ -51,7 +55,7 @@ function valPropertyMsg(serverValidationManager, localizationService) { var currentVariant = umbVariantCtrl.editor.content; // Lets check if we have variants and we are on the default language then ... - if (umbVariantCtrl.content.variants.length > 1 && !currentVariant.language.isDefault && !currentCulture && !currentProperty.unlockInvariantValue) { + if (umbVariantCtrl.content.variants.length > 1 && (!currentVariant.language || !currentVariant.language.isDefault) && !currentCulture && !currentSegment && !currentProperty.unlockInvariantValue) { //This property is locked cause its a invariant property shown on a non-default language. //Therefor do not validate this field. return; @@ -67,7 +71,7 @@ function valPropertyMsg(serverValidationManager, localizationService) { //this can be null if no property was assigned if (scope.currentProperty) { //first try to get the error msg from the server collection - var err = serverValidationManager.getPropertyError(scope.currentProperty.alias, null, ""); + var err = serverValidationManager.getPropertyError(scope.currentProperty.alias, null, "", null); //if there's an error message use it if (err && err.errorMsg) { return err.errorMsg; @@ -91,23 +95,25 @@ function valPropertyMsg(serverValidationManager, localizationService) { if (!watcher) { watcher = scope.$watch("currentProperty.value", function (newValue, oldValue) { - if (angular.equals(newValue, oldValue)) { return; } var errCount = 0; + for (var e in formCtrl.$error) { - if (angular.isArray(formCtrl.$error[e])) { + if (Utilities.isArray(formCtrl.$error[e])) { errCount++; } - } + } //we are explicitly checking for valServer errors here, since we shouldn't auto clear // based on other errors. We'll also check if there's no other validation errors apart from valPropertyMsg, if valPropertyMsg // is the only one, then we'll clear. - if (errCount === 0 || (errCount === 1 && angular.isArray(formCtrl.$error.valPropertyMsg)) || (formCtrl.$invalid && angular.isArray(formCtrl.$error.valServer))) { + if (errCount === 0 + || (errCount === 1 && Utilities.isArray(formCtrl.$error.valPropertyMsg)) + || (formCtrl.$invalid && Utilities.isArray(formCtrl.$error.valServer))) { scope.errorMsg = ""; formCtrl.$setValidity('valPropertyMsg', true); } else if (showValidation && scope.errorMsg === "") { @@ -136,6 +142,21 @@ function valPropertyMsg(serverValidationManager, localizationService) { } //if there are any errors in the current property form that are not valPropertyMsg else if (_.without(_.keys(formCtrl.$error), "valPropertyMsg").length > 0) { + + // errors exist, but if the property is NOT mandatory and has no value, the errors should be cleared + if (isMandatory !== undefined && isMandatory === false && !currentProperty.value) { + hasError = false; + showValidation = false; + scope.errorMsg = ""; + + // if there's no value, the controls can be reset, which clears the error state on formCtrl + for (let control of formCtrl.$getControls()) { + control.$setValidity(); + } + + return; + } + hasError = true; //update the validation message if we don't already have one assigned. if (showValidation && scope.errorMsg === "") { @@ -201,25 +222,31 @@ function valPropertyMsg(serverValidationManager, localizationService) { // the correct field validation in their property editors. if (scope.currentProperty) { //this can be null if no property was assigned + + function serverValidationManagerCallback(isValid, propertyErrors, allErrors) { + hasError = !isValid; + if (hasError) { + //set the error message to the server message + scope.errorMsg = propertyErrors[0].errorMsg; + //flag that the current validator is invalid + formCtrl.$setValidity('valPropertyMsg', false); + startWatch(); + } + else { + scope.errorMsg = ""; + //flag that the current validator is valid + formCtrl.$setValidity('valPropertyMsg', true); + stopWatch(); + } + } + unsubscribe.push(serverValidationManager.subscribe(scope.currentProperty.alias, currentCulture, "", - function(isValid, propertyErrors, allErrors) { - hasError = !isValid; - if (hasError) { - //set the error message to the server message - scope.errorMsg = propertyErrors[0].errorMsg; - //flag that the current validator is invalid - formCtrl.$setValidity('valPropertyMsg', false); - startWatch(); - } - else { - scope.errorMsg = ""; - //flag that the current validator is valid - formCtrl.$setValidity('valPropertyMsg', true); - stopWatch(); - } - })); + serverValidationManagerCallback, + currentSegment + ) + ); } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valserver.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valserver.directive.js index a0cc7e3033..3fa9220f7b 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valserver.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valserver.directive.js @@ -24,6 +24,7 @@ function valServer(serverValidationManager) { var currentProperty = umbPropCtrl.property; var currentCulture = currentProperty.culture; + var currentSegment = currentProperty.segment; if (umbVariantCtrl) { //if we are inside of an umbVariantContent directive @@ -31,7 +32,7 @@ function valServer(serverValidationManager) { var currentVariant = umbVariantCtrl.editor.content; // Lets check if we have variants and we are on the default language then ... - if (umbVariantCtrl.content.variants.length > 1 && !currentVariant.language.isDefault && !currentCulture && !currentProperty.unlockInvariantValue) { + if (umbVariantCtrl.content.variants.length > 1 && (!currentVariant.language || !currentVariant.language.isDefault) && !currentCulture && !currentSegment && !currentProperty.unlockInvariantValue) { //This property is locked cause its a invariant property shown on a non-default language. //Therefor do not validate this field. return; @@ -75,7 +76,7 @@ function valServer(serverValidationManager) { if (modelCtrl.$invalid) { modelCtrl.$setValidity('valServer', true); //clear the server validation entry - serverValidationManager.removePropertyError(currentProperty.alias, currentCulture, fieldName); + serverValidationManager.removePropertyError(currentProperty.alias, currentCulture, fieldName, currentSegment); stopWatch(); } }, true); @@ -90,23 +91,26 @@ function valServer(serverValidationManager) { } //subscribe to the server validation changes + function serverValidationManagerCallback(isValid, propertyErrors, allErrors) { + if (!isValid) { + modelCtrl.$setValidity('valServer', false); + //assign an error msg property to the current validator + modelCtrl.errorMsg = propertyErrors[0].errorMsg; + startWatch(); + } + else { + modelCtrl.$setValidity('valServer', true); + //reset the error message + modelCtrl.errorMsg = ""; + stopWatch(); + } + } unsubscribe.push(serverValidationManager.subscribe(currentProperty.alias, currentCulture, fieldName, - function(isValid, propertyErrors, allErrors) { - if (!isValid) { - modelCtrl.$setValidity('valServer', false); - //assign an error msg property to the current validator - modelCtrl.errorMsg = propertyErrors[0].errorMsg; - startWatch(); - } - else { - modelCtrl.$setValidity('valServer', true); - //reset the error message - modelCtrl.errorMsg = ""; - stopWatch(); - } - })); + serverValidationManagerCallback, + currentSegment) + ); scope.$on('$destroy', function () { stopWatch(); 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 index 524b5f7efe..1f5aaaa1c2 100644 --- 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 @@ -40,7 +40,7 @@ function link(scope, el, attr, ctrl) { //if there are no containing form or valFormManager controllers, then we do nothing - if (!ctrl || !angular.isArray(ctrl) || ctrl.length !== 2 || !ctrl[0] || !ctrl[1]) { + if (!ctrl || !Utilities.isArray(ctrl) || ctrl.length !== 2 || !ctrl[0] || !ctrl[1]) { return; } diff --git a/src/Umbraco.Web.UI.Client/src/common/filters/umbwordlimit.filter.js b/src/Umbraco.Web.UI.Client/src/common/filters/umbwordlimit.filter.js index c02624409f..93f0bddc96 100644 --- a/src/Umbraco.Web.UI.Client/src/common/filters/umbwordlimit.filter.js +++ b/src/Umbraco.Web.UI.Client/src/common/filters/umbwordlimit.filter.js @@ -13,7 +13,7 @@ function umbWordLimitFilter() { return function (collection, property) { - if (!angular.isString(collection)) { + if (!Utilities.isString(collection)) { return collection; } @@ -35,4 +35,4 @@ angular.module('umbraco.filters').filter('umbWordLimit', umbWordLimitFilter); -})(); \ No newline at end of file +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/interceptors/culturerequest.interceptor.js b/src/Umbraco.Web.UI.Client/src/common/interceptors/culturerequest.interceptor.js index 186f3accf0..283a2a7ae9 100644 --- a/src/Umbraco.Web.UI.Client/src/common/interceptors/culturerequest.interceptor.js +++ b/src/Umbraco.Web.UI.Client/src/common/interceptors/culturerequest.interceptor.js @@ -24,6 +24,7 @@ if ($routeParams) { // it's an API request, add the current client culture as a header value config.headers["X-UMB-CULTURE"] = $routeParams.cculture ? $routeParams.cculture : $routeParams.mculture; + config.headers["X-UMB-SEGMENT"] = $routeParams.csegment ? $routeParams.csegment : null; } return config; diff --git a/src/Umbraco.Web.UI.Client/src/common/interceptors/donotpostdollarvariablesrequest.interceptor.js b/src/Umbraco.Web.UI.Client/src/common/interceptors/donotpostdollarvariablesrequest.interceptor.js index 03373089d7..f3dd60bdce 100644 --- a/src/Umbraco.Web.UI.Client/src/common/interceptors/donotpostdollarvariablesrequest.interceptor.js +++ b/src/Umbraco.Web.UI.Client/src/common/interceptors/donotpostdollarvariablesrequest.interceptor.js @@ -1,40 +1,40 @@ -(function() { - 'use strict'; - +(function () { + 'use strict'; + function removeProperty(obj, propertyPrefix) { for (var property in obj) { if (obj.hasOwnProperty(property)) { - + if (property.startsWith(propertyPrefix) && obj[property] !== undefined) { obj[property] = undefined; } - + if (typeof obj[property] === "object") { removeProperty(obj[property], propertyPrefix); } } } - + } - - function transform(data){ + + function transform(data) { removeProperty(data, "$"); } - + function doNotPostDollarVariablesRequestInterceptor($q, urlHelper) { return { //dealing with requests: - 'request': function(config) { - if(config.method === "POST"){ - var clone = angular.copy(config); + 'request': function (config) { + if (config.method === "POST") { + var clone = Utilities.copy(config); transform(clone.data); return clone; } - + return config; } }; - } + } angular.module('umbraco.interceptors').factory('doNotPostDollarVariablesOnPostRequestInterceptor', doNotPostDollarVariablesRequestInterceptor); })(); diff --git a/src/Umbraco.Web.UI.Client/src/common/interceptors/security.interceptor.js b/src/Umbraco.Web.UI.Client/src/common/interceptors/security.interceptor.js index 30daaf5837..bf748975a1 100644 --- a/src/Umbraco.Web.UI.Client/src/common/interceptors/security.interceptor.js +++ b/src/Umbraco.Web.UI.Client/src/common/interceptors/security.interceptor.js @@ -44,7 +44,7 @@ var headers = config.headers ? config.headers : {}; //Here we'll check if we should ignore the error (either based on the original header set or the request configuration) - if (headers["x-umb-ignore-error"] === "ignore" || config.umbIgnoreErrors === true || (angular.isArray(config.umbIgnoreStatus) && config.umbIgnoreStatus.indexOf(rejection.status) !== -1)) { + if (headers["x-umb-ignore-error"] === "ignore" || config.umbIgnoreErrors === true || (Utilities.isArray(config.umbIgnoreStatus) && config.umbIgnoreStatus.indexOf(rejection.status) !== -1)) { //exit/ignore return $q.reject(rejection); } diff --git a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/datatype.mocks.js b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/datatype.mocks.js index 054f0aa66d..e70f483bf4 100644 --- a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/datatype.mocks.js +++ b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/datatype.mocks.js @@ -1,113 +1,113 @@ angular.module('umbraco.mocks'). - factory('dataTypeMocks', ['$httpBackend', 'mocksUtils', function ($httpBackend, mocksUtils) { - 'use strict'; - - function returnById(status, data, headers) { + factory('dataTypeMocks', ['$httpBackend', 'mocksUtils', function ($httpBackend, mocksUtils) { + 'use strict'; - if (!mocksUtils.checkAuth()) { - return [401, null, null]; - } + function returnById(status, data, headers) { - var id = mocksUtils.getParameterByName(data, "id") || 1234; + if (!mocksUtils.checkAuth()) { + return [401, null, null]; + } - var selectedId = String.CreateGuid(); + var id = mocksUtils.getParameterByName(data, "id") || 1234; - var dataType = mocksUtils.getMockDataType(id, selectedId); - - return [200, dataType, null]; - } - - function returnEmpty(status, data, headers) { + var selectedId = String.CreateGuid(); - if (!mocksUtils.checkAuth()) { - return [401, null, null]; - } + var dataType = mocksUtils.getMockDataType(id, selectedId); - var response = returnById(200, "", null); - var node = response[1]; + return [200, dataType, null]; + } - node.name = ""; - node.selectedEditor = ""; - node.id = 0; - node.preValues = []; + function returnEmpty(status, data, headers) { - return response; - } - - function returnPreValues(status, data, headers) { + if (!mocksUtils.checkAuth()) { + return [401, null, null]; + } - if (!mocksUtils.checkAuth()) { - return [401, null, null]; - } + var response = returnById(200, "", null); + var node = response[1]; - var editorId = mocksUtils.getParameterByName(data, "editorId") || "83E9AD36-51A7-4440-8C07-8A5623AC6979"; + node.name = ""; + node.selectedEditor = ""; + node.id = 0; + node.preValues = []; - var preValues = [ - { - label: "Custom pre value 1 for editor " + editorId, - description: "Enter a value for this pre-value", - key: "myPreVal", - view: "requiredfield", - validation: [ - { - type: "Required" - } - ] - }, - { - label: "Custom pre value 2 for editor " + editorId, - description: "Enter a value for this pre-value", - key: "myPreVal", - view: "requiredfield", - validation: [ - { - type: "Required" - } - ] - } - ]; - return [200, preValues, null]; - } - - function returnSave(status, data, headers) { - if (!mocksUtils.checkAuth()) { - return [401, null, null]; - } + return response; + } - var postedData = angular.fromJson(headers); + function returnPreValues(status, data, headers) { - var dataType = mocksUtils.getMockDataType(postedData.id, postedData.selectedEditor); - dataType.notifications = [{ - header: "Saved", - message: "Data type saved", - type: 0 - }]; + if (!mocksUtils.checkAuth()) { + return [401, null, null]; + } - return [200, dataType, null]; - } + var editorId = mocksUtils.getParameterByName(data, "editorId") || "83E9AD36-51A7-4440-8C07-8A5623AC6979"; - return { - register: function() { - - $httpBackend - .whenPOST(mocksUtils.urlRegex('/umbraco/UmbracoApi/DataType/PostSave')) - .respond(returnSave); - - $httpBackend - .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/DataType/GetById')) - .respond(returnById); - - $httpBackend - .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/DataType/GetEmpty')) - .respond(returnEmpty); - - $httpBackend - .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/DataType/GetPreValues')) - .respond(returnPreValues); - }, - expectGetById: function() { - $httpBackend - .expectGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/DataType/GetById')); - } - }; - }]); + var preValues = [ + { + label: "Custom pre value 1 for editor " + editorId, + description: "Enter a value for this pre-value", + key: "myPreVal", + view: "requiredfield", + validation: [ + { + type: "Required" + } + ] + }, + { + label: "Custom pre value 2 for editor " + editorId, + description: "Enter a value for this pre-value", + key: "myPreVal", + view: "requiredfield", + validation: [ + { + type: "Required" + } + ] + } + ]; + return [200, preValues, null]; + } + + function returnSave(status, data, headers) { + if (!mocksUtils.checkAuth()) { + return [401, null, null]; + } + + var postedData = JSON.parse(headers); + + var dataType = mocksUtils.getMockDataType(postedData.id, postedData.selectedEditor); + dataType.notifications = [{ + header: "Saved", + message: "Data type saved", + type: 0 + }]; + + return [200, dataType, null]; + } + + return { + register: function () { + + $httpBackend + .whenPOST(mocksUtils.urlRegex('/umbraco/UmbracoApi/DataType/PostSave')) + .respond(returnSave); + + $httpBackend + .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/DataType/GetById')) + .respond(returnById); + + $httpBackend + .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/DataType/GetEmpty')) + .respond(returnEmpty); + + $httpBackend + .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/DataType/GetPreValues')) + .respond(returnPreValues); + }, + expectGetById: function () { + $httpBackend + .expectGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/DataType/GetById')); + } + }; + }]); diff --git a/src/Umbraco.Web.UI.Client/src/common/mocks/services/localization.mocks.js b/src/Umbraco.Web.UI.Client/src/common/mocks/services/localization.mocks.js index 3874ff9bf6..ea7f3a6d4c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/mocks/services/localization.mocks.js +++ b/src/Umbraco.Web.UI.Client/src/common/mocks/services/localization.mocks.js @@ -84,7 +84,7 @@ angular.module('umbraco.mocks'). "buttons_save": "Save", "buttons_saveAndPublish": "Save and publish", "buttons_saveToPublish": "Save and send for approval", - "buttons_showPage": "Preview", + "buttons_saveAndPreview": "Save and preview", "buttons_showPageDisabled": "Preview is disabled because there's no template assigned", "buttons_styleChoose": "Choose style", "buttons_styleShow": "Show styles", diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/auth.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/auth.resource.js index 678cffe42e..936f69e738 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/auth.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/auth.resource.js @@ -30,7 +30,7 @@ function authResource($q, $http, umbRequestHelper, angularHelper) { umbRequestHelper.getApiUrl( "authenticationApiBaseUrl", "PostSend2FACode"), - angular.toJson(provider)), + Utilities.toJson(provider)), 'Could not send code'); }, 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 d714ea4938..7bc2a9d2c8 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 @@ -610,10 +610,10 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { //converts the value to a js bool function toBool(v) { - if (angular.isNumber(v)) { + if (Utilities.isNumber(v)) { return v > 0; } - if (angular.isString(v)) { + if (Utilities.isString(v)) { return v === "true"; } if (typeof v === "boolean") { @@ -1003,10 +1003,10 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { loginPageId: loginPageId, errorPageId: errorPageId }; - if (angular.isArray(groups) && groups.length) { + if (Utilities.isArray(groups) && groups.length) { publicAccess.groups = groups; } - else if (angular.isArray(usernames) && usernames.length) { + else if (Utilities.isArray(usernames) && usernames.length) { publicAccess.usernames = usernames; } else { 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 64accc18c1..97bebef062 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 @@ -351,6 +351,16 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca return umbRequestHelper.resourcePromise( $http.post(umbRequestHelper.getApiUrl("contentTypeApiBaseUrl", "PostCreateDefaultTemplate", { id: id })), 'Failed to create default template for content type with id ' + id); + }, + + hasContentNodes: function (id) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentTypeApiBaseUrl", + "HasContentNodes", + [{ id: id }])), + 'Failed to retrieve indication for whether content type with id ' + id + ' has associated content nodes'); } }; } diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/currentuser.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/currentuser.resource.js index e10837ceca..60b87e919f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/currentuser.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/currentuser.resource.js @@ -87,7 +87,7 @@ function currentUserResource($q, $http, umbRequestHelper, umbDataFormatter) { umbRequestHelper.getApiUrl( "currentUserApiBaseUrl", "PostSetInvitedUserPassword"), - angular.toJson(newPassword)), + Utilities.toJson(newPassword)), 'Failed to change password'); }, diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/emailmarketing.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/emailmarketing.resource.js new file mode 100644 index 0000000000..4ac56ad13b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/resources/emailmarketing.resource.js @@ -0,0 +1,34 @@ +/** + * @ngdoc service + * @name umbraco.resources.emailMarketingResource + * @description Used to add a backoffice user to Umbraco's email marketing system, if user opts in + * + * + **/ +function emailMarketingResource($http, umbRequestHelper) { + + // LOCAL + // http://localhost:7071/api/EmailProxy + + // LIVE + // https://emailcollector.umbraco.io/api/EmailProxy + + const emailApiUrl = 'https://emailcollector.umbraco.io/api/EmailProxy'; + + //the factory object returned + return { + + postAddUserToEmailMarketing: (user) => { + return umbRequestHelper.resourcePromise( + $http.post(emailApiUrl, + { + name: user.name, + email: user.email, + usergroup: user.userGroups // [ "admin", "sensitiveData" ] + }), + 'Failed to add user to email marketing list'); + } + }; +} + +angular.module('umbraco.resources').factory('emailMarketingResource', emailMarketingResource); 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 9cf1181cfa..61d646afc0 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 @@ -127,6 +127,25 @@ function entityResource($q, $http, umbRequestHelper) { 'Failed to retrieve url for id:' + id); }, + getUrlByUdi: function (udi, culture) { + + if (!udi) { + return ""; + } + + if (!culture) { + culture = ""; + } + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "GetUrl", + [{ udi: udi }, {culture: culture }])), + 'Failed to retrieve url for UDI:' + udi); + }, + /** * @ngdoc method * @name umbraco.resources.entityResource#getById @@ -166,18 +185,22 @@ function entityResource($q, $http, umbRequestHelper) { }, - getUrlAndAnchors: function (id) { + getUrlAndAnchors: function (id, culture) { if (id === -1 || id === "-1") { return null; } + if (!culture) { + culture = ""; + } + return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( "entityApiBaseUrl", "GetUrlAndAnchors", - [{ id: id }])), + [{ id: id }, {culture: culture }])), 'Failed to retrieve url and anchors data for id ' + id); }, diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/imageurlgenerator.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/imageurlgenerator.resource.js new file mode 100644 index 0000000000..a937cd2675 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/resources/imageurlgenerator.resource.js @@ -0,0 +1,36 @@ +/** + * @ngdoc service + * @name umbraco.resources.imageUrlGeneratorResource + * @function + * + * @description + * Used by the various controllers to get an image URL formatted correctly for the current image URL generator + */ +(function () { + 'use strict'; + + function imageUrlGeneratorResource($http, umbRequestHelper) { + + function getCropUrl(mediaPath, width, height, imageCropMode, animationProcessMode) { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "imageUrlGeneratorApiBaseUrl", + "GetCropUrl", + { mediaPath, width, height, imageCropMode, animationProcessMode })), + 'Failed to get crop URL'); + } + + + var resource = { + getCropUrl: getCropUrl + }; + + return resource; + + } + + angular.module('umbraco.resources').factory('imageUrlGeneratorResource', imageUrlGeneratorResource); + +})(); 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 ca7700c188..e24f4786eb 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 @@ -348,10 +348,10 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { //converts the value to a js bool function toBool(v) { - if (angular.isNumber(v)) { + if (Utilities.isNumber(v)) { return v > 0; } - if (angular.isString(v)) { + if (Utilities.isString(v)) { return v === "true"; } if (typeof v === "boolean") { @@ -552,6 +552,36 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { "Search", args)), 'Failed to retrieve media items for search: ' + query); + }, + + getPagedReferences: function (id, options) { + + var defaults = { + pageSize: 25, + pageNumber: 1, + entityType: "DOCUMENT" + }; + if (options === undefined) { + options = {}; + } + //overwrite the defaults if there are any specified + angular.extend(defaults, options); + //now copy back to the options we will use + options = defaults; + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetPagedReferences", + { + id: id, + entityType: options.entityType, + pageNumber: options.pageNumber, + pageSize: options.pageSize + } + )), + "Failed to retrieve usages for media of id " + id); } }; 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 d21edbbab8..c45e173a98 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 @@ -53,10 +53,10 @@ function memberResource($q, $http, umbDataFormatter, umbRequestHelper) { //converts the value to a js bool function toBool(v) { - if (angular.isNumber(v)) { + if (Utilities.isNumber(v)) { return v > 0; } - if (angular.isString(v)) { + if (Utilities.isString(v)) { return v === "true"; } if (typeof v === "boolean") { diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/relationtype.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/relationtype.resource.js index 7f13a46d2f..7c542c5e7b 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/relationtype.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/relationtype.resource.js @@ -114,6 +114,34 @@ function relationTypeResource($q, $http, umbRequestHelper, umbDataFormatter) { $http.post(umbRequestHelper.getApiUrl("relationTypeApiBaseUrl", "DeleteById", [{ id: id }])), "Failed to delete item " + id ); + }, + + getPagedResults: function (id, options) { + + var defaults = { + pageSize: 25, + pageNumber: 1 + }; + if (options === undefined) { + options = {}; + } + //overwrite the defaults if there are any specified + angular.extend(defaults, options); + //now copy back to the options we will use + options = defaults; + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "relationTypeApiBaseUrl", + "GetPagedResults", + { + id: id, + pageNumber: options.pageNumber, + pageSize: options.pageSize + } + )), + 'Failed to get paged relations for id ' + id); } }; diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/tour.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/tour.resource.js index 40baf0f389..485b0d299a 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/tour.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/tour.resource.js @@ -20,10 +20,21 @@ "GetTours")), 'Failed to get tours'); } + + function getToursForDoctype(doctypeAlias) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "tourApiBaseUrl", + "GetToursForDoctype", + [{ doctypeAlias: doctypeAlias }])), + 'Failed to get tours'); + } var resource = { - getTours: getTours + getTours: getTours, + getToursForDoctype: getToursForDoctype }; return resource; diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js index 0fd308ffd0..2b9e0c0fd5 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js @@ -405,6 +405,43 @@ formattedSaveData), "Failed to save user"); } + + /** + * @ngdoc method + * @name umbraco.resources.usersResource#changePassword + * @methodOf umbraco.resources.usersResource + * + * @description + * Changes a user's password + * + * ##usage + *
    +          * usersResource.changePassword(changePasswordModel)
    +          *    .then(function() {
    +          *        // password changed
    +          *    });
    +          * 
    + * + * @param {Object} model object to save + * @returns {Promise} resourcePromise object containing the updated user. + * + */ + function changePassword(changePasswordModel) { + if (!changePasswordModel) { + throw "password model not specified"; + } + + //need to convert the password data into the correctly formatted save data - it is *not* the same and we don't want to over-post + var formattedPasswordData = umbDataFormatter.formatChangePasswordModel(changePasswordModel); + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "userApiBaseUrl", + "PostChangePassword"), + formattedPasswordData), + "Failed to save user"); + } /** * @ngdoc method @@ -447,6 +484,7 @@ createUser: createUser, inviteUser: inviteUser, saveUser: saveUser, + changePassword: changePassword, deleteNonLoggedInUser: deleteNonLoggedInUser, clearAvatar: clearAvatar }; diff --git a/src/Umbraco.Web.UI.Client/src/common/services/angularhelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/angularhelper.service.js index 455857c1e1..f2ff711ac9 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/angularhelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/angularhelper.service.js @@ -33,7 +33,7 @@ function angularHelper($q) { //this is sequential promise chaining, it's not pretty but we need to do it this way. //$q.all doesn't execute promises in sequence but that's what we want to do here. - if (!angular.isArray(promises)) { + if (!Utilities.isArray(promises)) { throw "promises must be an array"; } @@ -178,11 +178,11 @@ function angularHelper($q) { $valid: true, $submitted: false, $pending: undefined, - $addControl: angular.noop, - $removeControl: angular.noop, - $setValidity: angular.noop, - $setDirty: angular.noop, - $setPristine: angular.noop, + $addControl: Utilities.noop, + $removeControl: Utilities.noop, + $setValidity: Utilities.noop, + $setDirty: Utilities.noop, + $setPristine: Utilities.noop, $name: formName }; } 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 6673002981..30e59e9a88 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 @@ -256,7 +256,7 @@ angular.module('umbraco.services') load: function (pathArray, scope, defaultAssetType) { var promise; - if (!angular.isArray(pathArray)) { + if (!Utilities.isArray(pathArray)) { throw "pathArray must be an array"; } 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 20a47ae32c..8f2aa1d22b 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 @@ -10,7 +10,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt function isValidIdentifier(id) { //empty id <= 0 - if (angular.isNumber(id)) { + if (Utilities.isNumber(id)) { if (id === 0) { return false; } @@ -39,7 +39,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt /** Used by the content editor and mini content editor to perform saving operations */ contentEditorPerformSave: function (args) { - if (!angular.isObject(args)) { + if (!Utilities.isObject(args)) { throw "args must be an object"; } if (!args.scope) { @@ -152,7 +152,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt here we'll build the buttons according to the chars of the user. */ configureContentEditorButtons: function (args) { - if (!angular.isObject(args)) { + if (!Utilities.isObject(args)) { throw "args must be an object"; } if (!args.content) { @@ -698,7 +698,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt // /belle/#/content/edit/9876 (where 9876 is the new id) //clear the query strings - navigationService.clearSearch(["cculture"]); + navigationService.clearSearch(["cculture", "csegment"]); if (softRedirect) { navigationService.setSoftRedirect(); } 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 index 305e4a694d..1be66cc68f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/contenttypehelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/contenttypehelper.service.js @@ -7,21 +7,21 @@ function contentTypeHelper(contentTypeResource, dataTypeResource, $filter, $inje var contentTypeHelperService = { - createIdArray: function(array) { + createIdArray: function (array) { - var newArray = []; + var newArray = []; - angular.forEach(array, function(arrayItem){ + angular.forEach(array, function (arrayItem) { - if(angular.isObject(arrayItem)) { - newArray.push(arrayItem.id); - } else { - newArray.push(arrayItem); - } + if (Utilities.isObject(arrayItem)) { + newArray.push(arrayItem.id); + } else { + newArray.push(arrayItem); + } - }); + }); - return newArray; + return newArray; }, @@ -30,18 +30,18 @@ function contentTypeHelper(contentTypeResource, dataTypeResource, $filter, $inje var modelsResource = $injector.has("modelsBuilderManagementResource") ? $injector.get("modelsBuilderManagementResource") : null; var modelsBuilderEnabled = Umbraco.Sys.ServerVariables.umbracoPlugins.modelsBuilder.enabled; if (modelsBuilderEnabled && modelsResource) { - modelsResource.buildModels().then(function(result) { + modelsResource.buildModels().then(function (result) { deferred.resolve(result); //just calling this to get the servar back to life modelsResource.getModelsOutOfDateStatus(); - }, function(e) { + }, function (e) { deferred.reject(e); }); } - else { - deferred.resolve(false); + else { + deferred.resolve(false); } return deferred.promise; }, @@ -49,10 +49,10 @@ function contentTypeHelper(contentTypeResource, dataTypeResource, $filter, $inje checkModelsBuilderStatus: function () { var deferred = $q.defer(); var modelsResource = $injector.has("modelsBuilderManagementResource") ? $injector.get("modelsBuilderManagementResource") : null; - var modelsBuilderEnabled = (Umbraco && Umbraco.Sys && Umbraco.Sys.ServerVariables && Umbraco.Sys.ServerVariables.umbracoPlugins && Umbraco.Sys.ServerVariables.umbracoPlugins.modelsBuilder && Umbraco.Sys.ServerVariables.umbracoPlugins.modelsBuilder.enabled === true); - + var modelsBuilderEnabled = (Umbraco && Umbraco.Sys && Umbraco.Sys.ServerVariables && Umbraco.Sys.ServerVariables.umbracoPlugins && Umbraco.Sys.ServerVariables.umbracoPlugins.modelsBuilder && Umbraco.Sys.ServerVariables.umbracoPlugins.modelsBuilder.enabled === true); + if (modelsBuilderEnabled && modelsResource) { - modelsResource.getModelsOutOfDateStatus().then(function(result) { + modelsResource.getModelsOutOfDateStatus().then(function (result) { //Generate models buttons should be enabled if it is 0 deferred.resolve(result.status === 0); }); @@ -64,37 +64,37 @@ function contentTypeHelper(contentTypeResource, dataTypeResource, $filter, $inje }, makeObjectArrayFromId: function (idArray, objectArray) { - var newArray = []; + var newArray = []; - for (var idIndex = 0; idArray.length > idIndex; idIndex++) { - var id = idArray[idIndex]; + 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); - } - } + for (var objectIndex = 0; objectArray.length > objectIndex; objectIndex++) { + var object = objectArray[objectIndex]; + if (id === object.id) { + newArray.push(object); + } + } - } + } - return newArray; + return newArray; }, - validateAddingComposition: function(contentType, compositeContentType) { + validateAddingComposition: function (contentType, compositeContentType) { //Validate that by adding this group that we are not adding duplicate property type aliases - var propertiesAdding = _.flatten(_.map(compositeContentType.groups, function(g) { - return _.map(g.properties, function(p) { + var propertiesAdding = _.flatten(_.map(compositeContentType.groups, function (g) { + return _.map(g.properties, function (p) { return p.alias; }); })); - var propAliasesExisting = _.filter(_.flatten(_.map(contentType.groups, function(g) { - return _.map(g.properties, function(p) { + var propAliasesExisting = _.filter(_.flatten(_.map(contentType.groups, function (g) { + return _.map(g.properties, function (p) { return p.alias; }); - })), function(f) { + })), function (f) { return f !== null && f !== undefined; }); @@ -108,7 +108,7 @@ function contentTypeHelper(contentTypeResource, dataTypeResource, $filter, $inje return []; }, - mergeCompositeContentType: function(contentType, compositeContentType) { + mergeCompositeContentType: function (contentType, compositeContentType) { //Validate that there are no overlapping aliases var overlappingAliases = this.validateAddingComposition(contentType, compositeContentType); @@ -116,107 +116,107 @@ function contentTypeHelper(contentTypeResource, dataTypeResource, $filter, $inje throw new Error("Cannot add this composition, these properties already exist on the content type: " + overlappingAliases.join()); } - angular.forEach(compositeContentType.groups, function(compositionGroup) { + angular.forEach(compositeContentType.groups, function (compositionGroup) { - // order composition groups based on sort order - compositionGroup.properties = $filter('orderBy')(compositionGroup.properties, 'sortOrder'); + // 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; - }); - }); + // 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 tab + compositionGroup.inherited = true; - // set inherited state on properties - angular.forEach(compositionGroup.properties, function(compositionProperty) { - compositionProperty.inherited = true; - }); + // set inherited state on properties + angular.forEach(compositionGroup.properties, function (compositionProperty) { + compositionProperty.inherited = true; + }); - // set tab state - compositionGroup.tabState = "inActive"; + // set tab state + compositionGroup.tabState = "inActive"; - // if groups are named the same - merge the groups - angular.forEach(contentType.groups, function(contentTypeGroup) { + // if groups are named the same - merge the groups + angular.forEach(contentType.groups, function (contentTypeGroup) { - if (contentTypeGroup.name === compositionGroup.name) { + if (contentTypeGroup.name === compositionGroup.name) { - // set flag to show if properties has been merged into a tab - compositionGroup.groupIsMerged = true; + // set flag to show if properties has been merged into a tab + compositionGroup.groupIsMerged = true; - // make group inherited - contentTypeGroup.inherited = true; + // make group inherited + contentTypeGroup.inherited = true; - // add properties to the top of the array - contentTypeGroup.properties = compositionGroup.properties.concat(contentTypeGroup.properties); + // 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); + // 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 = Utilities.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 (contentTypeGroup.parentTabContentTypeNames === null || contentTypeGroup.parentTabContentTypeNames === undefined) { - contentTypeGroup.parentTabContentTypeNames = []; + if (compositionGroup.parentTabContentTypeNames === null || compositionGroup.parentTabContentTypeNames === undefined) { + compositionGroup.parentTabContentTypeNames = []; } // push name to array of merged composite content types - contentTypeGroup.parentTabContentTypeNames.push(compositeContentType.name); + compositionGroup.parentTabContentTypeNames.push(compositeContentType.name); // make parentTabContentTypes to an array so we can push values - if (contentTypeGroup.parentTabContentTypes === null || contentTypeGroup.parentTabContentTypes === undefined) { - contentTypeGroup.parentTabContentTypes = []; + if (compositionGroup.parentTabContentTypes === null || compositionGroup.parentTabContentTypes === undefined) { + compositionGroup.parentTabContentTypes = []; } // push id to array of merged composite content types - contentTypeGroup.parentTabContentTypes.push(compositeContentType.id); + compositionGroup.parentTabContentTypes.push(compositeContentType.id); - // get sort order from composition - contentTypeGroup.sortOrder = compositionGroup.sortOrder; + // push group before placeholder tab + contentType.groups.unshift(compositionGroup); - // 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); + } - } + }); - }); + // sort all groups by sortOrder property + contentType.groups = $filter('orderBy')(contentType.groups, 'sortOrder'); - // 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; + return contentType; }, @@ -224,22 +224,22 @@ function contentTypeHelper(contentTypeResource, dataTypeResource, $filter, $inje var groups = []; - angular.forEach(contentType.groups, function(contentTypeGroup){ + angular.forEach(contentType.groups, function (contentTypeGroup) { - if( contentTypeGroup.tabState !== "init" ) { + 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 ) { + if (idIndex !== -1) { var properties = []; // remove all properties from composite content type - angular.forEach(contentTypeGroup.properties, function(property){ - if(property.contentTypeId !== compositeContentType.id) { + angular.forEach(contentTypeGroup.properties, function (property) { + if (property.contentTypeId !== compositeContentType.id) { properties.push(property); } }); @@ -252,22 +252,22 @@ function contentTypeHelper(contentTypeResource, dataTypeResource, $filter, $inje contentTypeGroup.parentTabContentTypeNames.splice(nameIndex, 1); // remove inherited state if there are no inherited properties - if(contentTypeGroup.parentTabContentTypes.length === 0) { + if (contentTypeGroup.parentTabContentTypes.length === 0) { contentTypeGroup.inherited = false; } // remove group if there are no properties left - if(contentTypeGroup.properties.length > 1) { + if (contentTypeGroup.properties.length > 1) { //contentType.groups.splice(groupIndex, 1); groups.push(contentTypeGroup); } } else { - groups.push(contentTypeGroup); + groups.push(contentTypeGroup); } } else { - groups.push(contentTypeGroup); + groups.push(contentTypeGroup); } // update sort order on properties @@ -281,67 +281,67 @@ function contentTypeHelper(contentTypeResource, dataTypeResource, $filter, $inje updatePropertiesSortOrder: function (properties) { - var sortOrder = 0; + var sortOrder = 0; - angular.forEach(properties, function(property) { - if( !property.inherited && property.propertyState !== "init") { - property.sortOrder = sortOrder; - } - sortOrder++; - }); + angular.forEach(properties, function (property) { + if (!property.inherited && property.propertyState !== "init") { + property.sortOrder = sortOrder; + } + sortOrder++; + }); - return properties; + return properties; }, - getTemplatePlaceholder: function() { + getTemplatePlaceholder: function () { - var templatePlaceholder = { - "name": "", - "icon": "icon-layout", - "alias": "templatePlaceholder", - "placeholder": true - }; + var templatePlaceholder = { + "name": "", + "icon": "icon-layout", + "alias": "templatePlaceholder", + "placeholder": true + }; - return templatePlaceholder; + return templatePlaceholder; }, - insertDefaultTemplatePlaceholder: function(defaultTemplate) { + insertDefaultTemplatePlaceholder: function (defaultTemplate) { - // get template placeholder - var templatePlaceholder = contentTypeHelperService.getTemplatePlaceholder(); + // get template placeholder + var templatePlaceholder = contentTypeHelperService.getTemplatePlaceholder(); - // add as default template - defaultTemplate = templatePlaceholder; + // add as default template + defaultTemplate = templatePlaceholder; - return defaultTemplate; + return defaultTemplate; }, - insertTemplatePlaceholder: function(array) { + insertTemplatePlaceholder: function (array) { - // get template placeholder - var templatePlaceholder = contentTypeHelperService.getTemplatePlaceholder(); + // get template placeholder + var templatePlaceholder = contentTypeHelperService.getTemplatePlaceholder(); - // add as selected item - array.push(templatePlaceholder); + // add as selected item + array.push(templatePlaceholder); - return array; + return array; - }, + }, - insertChildNodePlaceholder: function (array, name, icon, id) { + insertChildNodePlaceholder: function (array, name, icon, id) { - var placeholder = { - "name": name, - "icon": icon, - "id": id - }; + var placeholder = { + "name": name, + "icon": icon, + "id": id + }; - array.push(placeholder); + array.push(placeholder); - } + } }; diff --git a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js index 1d80d3a3ed..538bd41ce0 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js @@ -164,13 +164,14 @@ When building a custom infinite editor view you can use the same components as a "use strict"; function editorService(eventsService, keyboardService, $timeout) { - - + + let editorsKeyboardShorcuts = []; var editors = []; var isEnabled = true; - - + var lastElementInFocus = null; + + // events for backdrop eventsService.on("appState.backdrop", function (name, args) { if (args.show === true) { @@ -179,7 +180,7 @@ When building a custom infinite editor view you can use the same components as a focus(); } }); - + /** * @ngdoc method @@ -204,7 +205,7 @@ When building a custom infinite editor view you can use the same components as a function getNumberOfEditors() { return editors.length; }; - + /** * @ngdoc method * @name umbraco.services.editorService#blur @@ -231,7 +232,7 @@ When building a custom infinite editor view you can use the same components as a * Method to tell editors that they are gaining focus again. */ function focus() { - if(isEnabled === false) { + if (isEnabled === false) { /* keyboard shortcuts will be overwritten by the new infinite editor so we need to store the shortcuts for the current editor so they can be rebound when the infinite editor closes @@ -240,7 +241,7 @@ When building a custom infinite editor view you can use the same components as a isEnabled = true; } } - + /** * @ngdoc method * @name umbraco.services.editorService#open @@ -261,6 +262,12 @@ When building a custom infinite editor view you can use the same components as a */ unbindKeyboardShortcuts(); + // if this is the first editor layer, save the currently focused element + // so we can re-apply focus to it once all the editor layers are closed + if (editors.length === 0) { + lastElementInFocus = document.activeElement; + } + // set flag so we know when the editor is open in "infinite mode" editor.infiniteMode = true; @@ -298,9 +305,13 @@ When building a custom infinite editor view you can use the same components as a // delay required to map the properties to the correct editor due // to another delay in the closing animation of the editor - $timeout(function() { + $timeout(function () { // rebind keyboard shortcuts for the new editor in focus rebindKeyboardShortcuts(); + + if (editors.length === 0 && lastElementInFocus) { + lastElementInFocus.focus(); + } }, 0); } @@ -514,7 +525,7 @@ When building a custom infinite editor view you can use the same components as a function rollback(editor) { editor.view = "views/common/infiniteeditors/rollback/rollback.html"; - if (!editor.size) editor.size = "small"; + if (!editor.size) editor.size = "medium"; open(editor); } @@ -577,7 +588,7 @@ When building a custom infinite editor view you can use the same components as a */ function mediaPicker(editor) { editor.view = "views/common/infiniteeditors/mediapicker/mediapicker.html"; - if (!editor.size) editor.size = "small"; + if (!editor.size) editor.size = "medium"; editor.updatedMediaNodes = []; open(editor); } @@ -658,6 +669,23 @@ When building a custom infinite editor view you can use the same components as a open(editor); } + /** + * @ngdoc method + * @name umbraco.services.editorService#memberTypeEditor + * @methodOf umbraco.services.editorService + * + * @description + * Opens the member type editor in infinite editing, the submit callback returns the saved member type + * @param {Object} editor rendering options + * @param {Callback} editor.submit Submits the editor + * @param {Callback} editor.close Closes the editor + * @returns {Object} editor object + */ + function memberTypeEditor(editor) { + editor.view = "views/membertypes/edit.html"; + open(editor); + } + /** * @ngdoc method * @name umbraco.services.editorService#queryBuilder @@ -756,7 +784,7 @@ When building a custom infinite editor view you can use the same components as a * @methodOf umbraco.services.editorService * * @description - * Opens the user group picker in infinite editing, the submit callback returns the saved template + * Opens the template editor in infinite editing, the submit callback returns the saved template * @param {Object} editor rendering options * @param {String} editor.id The template id * @param {Callback} editor.submit Submits the editor @@ -900,21 +928,21 @@ When building a custom infinite editor view you can use the same components as a open(editor); } - /** - * @ngdoc method - * @name umbraco.services.editorService#memberPicker - * @methodOf umbraco.services.editorService - * - * @description - * Opens a member picker in infinite editing, the submit callback returns an array of selected items - * - * @param {Object} editor rendering options - * @param {Boolean} editor.multiPicker Pick one or multiple items - * @param {Function} editor.submit Callback function when the submit button is clicked. Returns the editor model object - * @param {Function} editor.close Callback function when the close button is clicked. - * - * @returns {Object} editor object - */ + /** + * @ngdoc method + * @name umbraco.services.editorService#memberPicker + * @methodOf umbraco.services.editorService + * + * @description + * Opens a member picker in infinite editing, the submit callback returns an array of selected items + * + * @param {Object} editor rendering options + * @param {Boolean} editor.multiPicker Pick one or multiple items + * @param {Function} editor.submit Callback function when the submit button is clicked. Returns the editor model object + * @param {Function} editor.close Callback function when the close button is clicked. + * + * @returns {Object} editor object + */ function memberPicker(editor) { editor.view = "views/common/infiniteeditors/treepicker/treepicker.html"; if (!editor.size) editor.size = "small"; @@ -957,7 +985,7 @@ When building a custom infinite editor view you can use the same components as a * */ function unbindKeyboardShortcuts() { - const shortcuts = angular.copy(keyboardService.keyboardEvent); + const shortcuts = Utilities.copy(keyboardService.keyboardEvent); editorsKeyboardShorcuts.push(shortcuts); // unbind the current shortcuts because we only want to @@ -1011,6 +1039,7 @@ When building a custom infinite editor view you can use the same components as a iconPicker: iconPicker, documentTypeEditor: documentTypeEditor, mediaTypeEditor: mediaTypeEditor, + memberTypeEditor: memberTypeEditor, queryBuilder: queryBuilder, treePicker: treePicker, nodePermissions: nodePermissions, diff --git a/src/Umbraco.Web.UI.Client/src/common/services/editorstate.service.js b/src/Umbraco.Web.UI.Client/src/common/services/editorstate.service.js index 97a9ac5c4b..28daa3f245 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/editorstate.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/editorstate.service.js @@ -10,7 +10,7 @@ * * it is possible to modify this object, so should be used with care */ -angular.module('umbraco.services').factory("editorState", function ($rootScope) { +angular.module('umbraco.services').factory("editorState", function ($rootScope, eventsService) { var current = null; @@ -30,6 +30,7 @@ angular.module('umbraco.services').factory("editorState", function ($rootScope) */ set: function (entity) { current = entity; + eventsService.emit("editorState.changed", { entity: entity }); }, /** diff --git a/src/Umbraco.Web.UI.Client/src/common/services/events.service.js b/src/Umbraco.Web.UI.Client/src/common/services/events.service.js index 51f63e6787..965ac3d635 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/events.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/events.service.js @@ -1,7 +1,7 @@ /** Used to broadcast and listen for global events and allow the ability to add async listeners to the callbacks */ /* - Core app events: + Core app events: app.ready app.authenticated @@ -12,9 +12,9 @@ */ function eventsService($q, $rootScope) { - + return { - + /** raise an event with a given name */ emit: function (name, args) { diff --git a/src/Umbraco.Web.UI.Client/src/common/services/filemanager.service.js b/src/Umbraco.Web.UI.Client/src/common/services/filemanager.service.js index 41614a3bee..9e0285d58d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/filemanager.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/filemanager.service.js @@ -27,10 +27,10 @@ function fileManager($rootScope) { setFiles: function (args) { //propertyAlias, files - if (!angular.isString(args.propertyAlias)) { + if (!Utilities.isString(args.propertyAlias)) { throw "args.propertyAlias must be a non empty string"; } - if (!angular.isObject(args.files)) { + if (!Utilities.isObject(args.files)) { throw "args.files must be an object"; } @@ -40,7 +40,7 @@ function fileManager($rootScope) { } var metaData = []; - if (angular.isArray(args.metaData)) { + if (Utilities.isArray(args.metaData)) { metaData = args.metaData; } 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 0555318bae..90fbd76ec9 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 @@ -83,7 +83,7 @@ function formHelper(angularHelper, serverValidationManager, notificationsService if (!args || !args.notifications) { return false; } - if (angular.isArray(args.notifications)) { + if (Utilities.isArray(args.notifications)) { for (var i = 0; i < args.notifications.length; i++) { notificationsService.showNotification(args.notifications[i]); } @@ -159,9 +159,16 @@ function formHelper(angularHelper, serverValidationManager, notificationsService //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 - //There will always be at least 3 parts for content properties since all model errors for properties are prefixed with "_Properties" + //There will always be at least 4 parts for content properties since all model errors for properties are prefixed with "_Properties" //If it is not prefixed with "_Properties" that means the error is for a field of the object directly. + // Example: "_Properties.headerImage.en-US.mySegment.myField" + // * it's for a property since it has a _Properties prefix + // * it's for the headerImage property type + // * it's for the en-US culture + // * it's for the mySegment segment + // * it's for the myField html field (optional) + var parts = e.split("."); //Check if this is for content properties - specific to content/media/member editors because those are special @@ -179,16 +186,23 @@ function formHelper(angularHelper, serverValidationManager, notificationsService } } - //if it contains 3 '.' then we will wire it up to a property's html field + var segment = null; if (parts.length > 3) { - //add an error with a reference to the field for which the validation belongs too - serverValidationManager.addPropertyError(propertyAlias, culture, parts[3], modelState[e][0]); + segment = parts[3]; + //special check in case the string is formatted this way + if (segment === "null") { + segment = null; + } } - else { - //add a generic error for the property, no reference to a specific html field - serverValidationManager.addPropertyError(propertyAlias, culture, "", modelState[e][0]); + + var htmlFieldReference = ""; + if (parts.length > 4) { + htmlFieldReference = parts[4] || ""; } + // add a generic error for the property + serverValidationManager.addPropertyError(propertyAlias, culture, htmlFieldReference, modelState[e][0], segment); + } else { //Everthing else is just a 'Field'... the field name could contain any level of 'parts' though, for example: diff --git a/src/Umbraco.Web.UI.Client/src/common/services/listviewhelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/listviewhelper.service.js index 20d014ab0f..28156e70c3 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/listviewhelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/listviewhelper.service.js @@ -332,14 +332,14 @@ selection.length = 0; - if (angular.isArray(items)) { + if (Utilities.isArray(items)) { for (i = 0; items.length > i; i++) { var item = items[i]; item.selected = false; } } - if(angular.isArray(folders)) { + if(Utilities.isArray(folders)) { for (i = 0; folders.length > i; i++) { var folder = folders[i]; folder.selected = false; @@ -366,7 +366,7 @@ var checkbox = $event.target; var clearSelection = false; - if (!angular.isArray(items)) { + if (!Utilities.isArray(items)) { return; } @@ -413,7 +413,7 @@ function selectAllItemsToggle(items, selection) { - if (!angular.isArray(items)) { + if (!Utilities.isArray(items)) { return; } 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 a91f9d51e4..5b79b9c327 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 @@ -62,12 +62,12 @@ function macroService() { val = val ? val : ""; //need to detect if the val is a string or an object var keyVal; - if (angular.isString(val)) { + if (Utilities.isString(val)) { keyVal = key + "=\"" + (val ? val : "") + "\" "; } else { //if it's not a string we'll send it through the json serializer - var json = angular.toJson(val); + var json = Utilities.toJson(val); //then we need to url encode it so that it's safe var encoded = encodeURIComponent(json); keyVal = key + "=\"" + encoded + "\" "; @@ -142,7 +142,7 @@ function macroService() { if (item.value !== null && item.value !== undefined && !_.isString(item.value)) { try { - val = angular.toJson(val); + val = Utilities.toJson(val); } catch (e) { // not json 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 2271f891ce..ce2a18c3c0 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 @@ -45,7 +45,7 @@ function mediaHelper(umbRequestHelper, $log) { //this performs a simple check to see if we have a media file as value //it doesnt catch everything, but better then nothing - if (angular.isString(item.value) && item.value.indexOf(mediaRoot) === 0) { + if (Utilities.isString(item.value) && item.value.indexOf(mediaRoot) === 0) { return true; } @@ -143,7 +143,7 @@ function mediaHelper(umbRequestHelper, $log) { */ resolveFileFromEntity: function (mediaEntity, thumbnail) { - var mediaPath = angular.isObject(mediaEntity.metaData) ? mediaEntity.metaData.MediaPath : null; + var mediaPath = Utilities.isObject(mediaEntity.metaData) ? mediaEntity.metaData.MediaPath : null; if (!mediaPath) { //don't throw since this image legitimately might not contain a media path, but output a warning 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 8d1caab850..da784a1f9e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js @@ -26,60 +26,60 @@ function navigationService($routeParams, $location, $q, $injector, eventsService navReadyPromise.resolve(mainTreeApi); }); - + //A list of query strings defined that when changed will not cause a reload of the route - var nonRoutingQueryStrings = ["mculture", "cculture", "lq", "sr"]; + var nonRoutingQueryStrings = ["mculture", "cculture", "csegment", "lq", "sr"]; var retainedQueryStrings = ["mculture"]; - + function setMode(mode) { switch (mode) { - case 'tree': - appState.setGlobalState("navMode", "tree"); - appState.setGlobalState("showNavigation", true); - appState.setMenuState("showMenu", false); - appState.setMenuState("showMenuDialog", false); - appState.setGlobalState("stickyNavigation", false); - appState.setGlobalState("showTray", false); - break; - case 'menu': - appState.setGlobalState("navMode", "menu"); - appState.setGlobalState("showNavigation", true); - appState.setMenuState("showMenu", true); - appState.setMenuState("showMenuDialog", false); - appState.setGlobalState("stickyNavigation", true); - break; - case 'dialog': - appState.setGlobalState("navMode", "dialog"); - appState.setGlobalState("stickyNavigation", true); - appState.setGlobalState("showNavigation", true); - appState.setMenuState("showMenu", false); - appState.setMenuState("showMenuDialog", true); - appState.setMenuState("allowHideMenuDialog", true); - break; - case 'search': - appState.setGlobalState("navMode", "search"); - appState.setGlobalState("stickyNavigation", false); - appState.setGlobalState("showNavigation", true); - appState.setMenuState("showMenu", false); - appState.setSectionState("showSearchResults", true); - appState.setMenuState("showMenuDialog", false); - break; - default: - appState.setGlobalState("navMode", "default"); - appState.setMenuState("showMenu", false); - appState.setMenuState("showMenuDialog", false); - appState.setMenuState("allowHideMenuDialog", true); - appState.setSectionState("showSearchResults", false); - appState.setGlobalState("stickyNavigation", false); - appState.setGlobalState("showTray", false); - appState.setMenuState("currentNode", null); + case 'tree': + appState.setGlobalState("navMode", "tree"); + appState.setGlobalState("showNavigation", true); + appState.setMenuState("showMenu", false); + appState.setMenuState("showMenuDialog", false); + appState.setGlobalState("stickyNavigation", false); + appState.setGlobalState("showTray", false); + break; + case 'menu': + appState.setGlobalState("navMode", "menu"); + appState.setGlobalState("showNavigation", true); + appState.setMenuState("showMenu", true); + appState.setMenuState("showMenuDialog", false); + appState.setGlobalState("stickyNavigation", true); + break; + case 'dialog': + appState.setGlobalState("navMode", "dialog"); + appState.setGlobalState("stickyNavigation", true); + appState.setGlobalState("showNavigation", true); + appState.setMenuState("showMenu", false); + appState.setMenuState("showMenuDialog", true); + appState.setMenuState("allowHideMenuDialog", true); + break; + case 'search': + appState.setGlobalState("navMode", "search"); + appState.setGlobalState("stickyNavigation", false); + appState.setGlobalState("showNavigation", true); + appState.setMenuState("showMenu", false); + appState.setSectionState("showSearchResults", true); + appState.setMenuState("showMenuDialog", false); + break; + default: + appState.setGlobalState("navMode", "default"); + appState.setMenuState("showMenu", false); + appState.setMenuState("showMenuDialog", false); + appState.setMenuState("allowHideMenuDialog", true); + appState.setSectionState("showSearchResults", false); + appState.setGlobalState("stickyNavigation", false); + appState.setGlobalState("showTray", false); + appState.setMenuState("currentNode", null); - if (appState.getGlobalState("isTablet") === true) { - appState.setGlobalState("showNavigation", false); - } + if (appState.getGlobalState("isTablet") === true) { + appState.setGlobalState("showNavigation", false); + } - break; + break; } } @@ -88,7 +88,7 @@ function navigationService($routeParams, $location, $q, $injector, eventsService * @param {any} requestPath */ function pathToRouteParts(requestPath) { - if (!angular.isString(requestPath)) { + if (!Utilities.isString(requestPath)) { throw "The value for requestPath is not a string"; } var pathAndQuery = requestPath.split("#")[1]; @@ -114,7 +114,7 @@ function navigationService($routeParams, $location, $q, $injector, eventsService } var service = { - + /** * @ngdoc method * @name umbraco.services.navigationService#isRouteChangingNavigation @@ -130,11 +130,11 @@ function navigationService($routeParams, $location, $q, $injector, eventsService */ isRouteChangingNavigation: function (currUrlParams, nextUrlParams) { - if (angular.isString(currUrlParams)) { + if (Utilities.isString(currUrlParams)) { currUrlParams = pathToRouteParts(currUrlParams); } - if (angular.isString(nextUrlParams)) { + if (Utilities.isString(nextUrlParams)) { nextUrlParams = pathToRouteParts(nextUrlParams); } @@ -151,7 +151,7 @@ function navigationService($routeParams, $location, $q, $injector, eventsService var nextRoutingKeys = _.difference(_.keys(nextUrlParams), nonRoutingQueryStrings); var diff1 = _.difference(currRoutingKeys, nextRoutingKeys); var diff2 = _.difference(nextRoutingKeys, currRoutingKeys); - + //if the routing parameter keys are the same, we'll compare their values to see if any have changed and if so then the routing will be allowed. if (diff1.length === 0 && diff2.length === 0) { var partsChanged = 0; @@ -223,7 +223,7 @@ function navigationService($routeParams, $location, $q, $injector, eventsService * @param {Object} nextRouteParams The next route parameters */ retainQueryStrings: function (currRouteParams, nextRouteParams) { - var toRetain = angular.copy(nextRouteParams); + var toRetain = Utilities.copy(nextRouteParams); var updated = false; _.each(retainedQueryStrings, function (r) { @@ -260,7 +260,7 @@ function navigationService($routeParams, $location, $q, $injector, eventsService * and load the dashboard related to the section * @param {string} sectionAlias The alias of the section */ - changeSection: function(sectionAlias, force) { + changeSection: function (sectionAlias, force) { setMode("default-opensection"); if (force && appState.getSectionState("currentSection") === sectionAlias) { @@ -360,19 +360,19 @@ function navigationService($routeParams, $location, $q, $injector, eventsService TODO: Delete this if not required */ - _syncPath: function(path, forceReload) { + _syncPath: function (path, forceReload) { return navReadyPromise.promise.then(function () { return mainTreeApi.syncTree({ path: path, forceReload: forceReload }); }); }, - - reloadNode: function(node) { + + reloadNode: function (node) { return navReadyPromise.promise.then(function () { return mainTreeApi.reloadNode(node); }); }, - - reloadSection: function(sectionAlias) { + + reloadSection: function (sectionAlias) { return navReadyPromise.promise.then(function () { treeService.clearCache({ section: sectionAlias }); return mainTreeApi.load(sectionAlias); @@ -387,11 +387,11 @@ function navigationService($routeParams, $location, $q, $injector, eventsService * @description * Hides the tree by hiding the containing dom element */ - hideTree: function() { + hideTree: function () { if (appState.getGlobalState("isTablet") === true && !appState.getGlobalState("stickyNavigation")) { //reset it to whatever is in the url - appState.setSectionState("currentSection", $routeParams.section); + appState.setSectionState("currentSection", $routeParams.section); setMode("default-hidesectiontree"); } @@ -409,19 +409,19 @@ function navigationService($routeParams, $location, $q, $injector, eventsService * * @param {Event} event the click event triggering the method, passed from the DOM element */ - showMenu: function(args) { - + showMenu: function (args) { + var self = this; return treeService.getMenu({ treeNode: args.node }) - .then(function(data) { + .then(function (data) { //check for a default //NOTE: event will be undefined when a call to hideDialog is made so it won't re-load the default again. // but perhaps there's a better way to deal with with an additional parameter in the args ? it works though. if (data.defaultAlias && !args.skipDefault) { - var found = _.find(data.menuItems, function(item) { + var found = _.find(data.menuItems, function (item) { return item.alias = data.defaultAlias; }); @@ -450,7 +450,7 @@ function navigationService($routeParams, $location, $q, $injector, eventsService return $q.resolve(); }); - + }, /** @@ -461,7 +461,7 @@ function navigationService($routeParams, $location, $q, $injector, eventsService * @description * Hides the menu by hiding the containing dom element */ - hideMenu: function() { + hideMenu: function () { //SD: Would we ever want to access the last action'd node instead of clearing it here? appState.setMenuState("currentNode", null); appState.setMenuState("menuActions", []); @@ -483,14 +483,14 @@ function navigationService($routeParams, $location, $q, $injector, eventsService appState.setMenuState("currentNode", node); - if (action.metaData && action.metaData["actionRoute"] && angular.isString(action.metaData["actionRoute"])) { + if (action.metaData && action.metaData["actionRoute"] && Utilities.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] : ""); this.hideNavigation(); return; } - else if (action.metaData && action.metaData["jsAction"] && angular.isString(action.metaData["jsAction"])) { + else if (action.metaData && action.metaData["jsAction"] && Utilities.isString(action.metaData["jsAction"])) { //we'll try to get the jsAction from the injector var menuAction = action.metaData["jsAction"].split('.'); @@ -532,7 +532,7 @@ function navigationService($routeParams, $location, $q, $injector, eventsService }); } }, - + /** * @ngdoc method @@ -553,7 +553,7 @@ function navigationService($routeParams, $location, $q, $injector, eventsService * @param {Scope} args.scope current scope passed to the dialog * @param {Object} args.action the clicked action containing `name` and `alias` */ - showDialog: function(args) { + showDialog: function (args) { if (!args) { throw "showDialog is missing the args parameter"; @@ -579,37 +579,44 @@ function navigationService($routeParams, $location, $q, $injector, eventsService templateUrl = args.action.metaData["actionView"]; } else { - - //by convention we will look into the /views/{treetype}/{action}.html - // for example: /views/content/create.html - - //we will also check for a 'packageName' for the current tree, if it exists then the convention will be: - // for example: /App_Plugins/{mypackage}/backoffice/{treetype}/create.html - var treeAlias = treeService.getTreeAlias(args.node); - var packageTreeFolder = treeService.getTreePackageFolder(treeAlias); - if (!treeAlias) { throw "Could not get tree alias for node " + args.node.id; } - - if (packageTreeFolder) { - templateUrl = Umbraco.Sys.ServerVariables.umbracoSettings.appPluginsPath + - "/" + packageTreeFolder + - "/backoffice/" + treeAlias + "/" + args.action.alias + ".html"; - } - else { - templateUrl = "views/" + treeAlias + "/" + args.action.alias + ".html"; - } - + templateUrl = this.getTreeTemplateUrl(treeAlias, args.action.alias); } setMode("dialog"); - if(templateUrl) { + if (templateUrl) { appState.setMenuState("dialogTemplateUrl", templateUrl); } - + + }, + /** + * @ngdoc method + * @name umbraco.services.navigationService#getTreeTemplateUrl + * @methodOf umbraco.services.navigationService + * + * @param {string} treeAlias the alias of the tree to look up + * @param {string} action the view file name + * @description + * creates the templateUrl based on treeAlias and action + * by convention we will look into the /views/{treetype}/{action}.html + * for example: /views/content/create.html + * we will also check for a 'packageName' for the current tree, if it exists then the convention will be: + * for example: /App_Plugins/{mypackage}/backoffice/{treetype}/create.html + */ + getTreeTemplateUrl: function (treeAlias, action) { + var packageTreeFolder = treeService.getTreePackageFolder(treeAlias); + if (packageTreeFolder) { + return Umbraco.Sys.ServerVariables.umbracoSettings.appPluginsPath + + "/" + packageTreeFolder + + "/backoffice/" + treeAlias + "/" + action + ".html"; + } + else { + return "views/" + treeAlias + "/" + action + ".html"; + } }, /** @@ -654,7 +661,7 @@ function navigationService($routeParams, $location, $q, $injector, eventsService * @description * shows the search pane */ - showSearch: function() { + showSearch: function () { setMode("search"); }, /** @@ -665,7 +672,7 @@ function navigationService($routeParams, $location, $q, $injector, eventsService * @description * hides the search pane */ - hideSearch: function() { + hideSearch: function () { setMode("default-hidesearch"); }, /** @@ -676,7 +683,7 @@ function navigationService($routeParams, $location, $q, $injector, eventsService * @description * hides any open navigation panes and resets the tree, actions and the currently selected node */ - hideNavigation: function() { + hideNavigation: function () { appState.setMenuState("menuActions", []); setMode("default"); } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/notifications.service.js b/src/Umbraco.Web.UI.Client/src/common/services/notifications.service.js index e5701b9de0..196e0e3baa 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/notifications.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/notifications.service.js @@ -245,7 +245,7 @@ angular.module('umbraco.services') * @param {Int} index index where the notication should be removed from */ remove: function (index) { - if(angular.isObject(index)){ + if (Utilities.isObject(index)){ var i = nArray.indexOf(index); angularHelper.safeApply($rootScope, function() { nArray.splice(i, 1); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/servervalidationmgr.service.js b/src/Umbraco.Web.UI.Client/src/common/services/servervalidationmgr.service.js index b9bfa51122..718e44d66e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/servervalidationmgr.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/servervalidationmgr.service.js @@ -13,19 +13,20 @@ function serverValidationManager($timeout) { var callbacks = []; /** calls the callback specified with the errors specified, used internally */ - function executeCallback(self, errorsForCallback, callback, culture) { + function executeCallback(self, errorsForCallback, callback, culture, segment) { callback.apply(self, [ false, // pass in a value indicating it is invalid errorsForCallback, // pass in the errors for this item self.items, // pass in all errors in total - culture // pass the culture that we are listing for. + culture, // pass the culture that we are listing for. + segment // pass the segment that we are listing for. ] ); } function getFieldErrors(self, fieldName) { - if (!angular.isString(fieldName)) { + if (!Utilities.isString(fieldName)) { throw "fieldName must be a string"; } @@ -35,33 +36,40 @@ function serverValidationManager($timeout) { }); } - function getPropertyErrors(self, propertyAlias, culture, fieldName) { - if (!angular.isString(propertyAlias)) { + + function getPropertyErrors(self, propertyAlias, culture, segment, fieldName) { + if (!Utilities.isString(propertyAlias)) { throw "propertyAlias must be a string"; } - if (fieldName && !angular.isString(fieldName)) { + if (fieldName && !Utilities.isString(fieldName)) { throw "fieldName must be a string"; } if (!culture) { culture = "invariant"; } + if (!segment) { + segment = null; + } //find all errors for this property return _.filter(self.items, function (item) { - return (item.propertyAlias === propertyAlias && item.culture === culture && (item.fieldName === fieldName || (fieldName === undefined || fieldName === ""))); + return (item.propertyAlias === propertyAlias && item.culture === culture && item.segment === segment && (item.fieldName === fieldName || (fieldName === undefined || fieldName === ""))); }); } - function getCultureErrors(self, culture) { + function getVariantErrors(self, culture, segment) { if (!culture) { culture = "invariant"; } + if (!segment) { + segment = null; + } //find all errors for this property return _.filter(self.items, function (item) { - return (item.culture === culture); + return (item.culture === culture && item.segment === segment); }); } @@ -71,21 +79,21 @@ function serverValidationManager($timeout) { //its a field error callback var fieldErrors = getFieldErrors(self, callbacks[cb].fieldName); if (fieldErrors.length > 0) { - executeCallback(self, fieldErrors, callbacks[cb].callback, callbacks[cb].culture); + executeCallback(self, fieldErrors, callbacks[cb].callback, callbacks[cb].culture, callbacks[cb].segment); } } else if (callbacks[cb].propertyAlias != null) { //its a property error - var propErrors = getPropertyErrors(self, callbacks[cb].propertyAlias, callbacks[cb].culture, callbacks[cb].fieldName); + var propErrors = getPropertyErrors(self, callbacks[cb].propertyAlias, callbacks[cb].culture, callbacks[cb].segment, callbacks[cb].fieldName); if (propErrors.length > 0) { - executeCallback(self, propErrors, callbacks[cb].callback, callbacks[cb].culture); + executeCallback(self, propErrors, callbacks[cb].callback, callbacks[cb].culture, callbacks[cb].segment); } } else { - //its a culture error - var cultureErrors = getCultureErrors(self, callbacks[cb].culture); - if (cultureErrors.length > 0) { - executeCallback(self, cultureErrors, callbacks[cb].callback, callbacks[cb].culture); + //its a variant error + var variantErrors = getVariantErrors(self, callbacks[cb].culture, callbacks[cb].segment); + if (variantErrors.length > 0) { + executeCallback(self, variantErrors, callbacks[cb].callback, callbacks[cb].culture, callbacks[cb].segment); } } } @@ -150,20 +158,27 @@ function serverValidationManager($timeout) { * field alias to listen for. * If propertyAlias is null, then this subscription is for a field property (not a user defined property). */ - subscribe: function (propertyAlias, culture, fieldName, callback) { + subscribe: function (propertyAlias, culture, fieldName, callback, segment) { if (!callback) { return; } var id = String.CreateGuid(); + + //normalize culture to "invariant" if (!culture) { culture = "invariant"; } + //normalize segment to null + if (!segment) { + segment = null; + } if (propertyAlias === null) { callbacks.push({ propertyAlias: null, culture: culture, + segment: segment, fieldName: fieldName, callback: callback, id: id @@ -175,6 +190,7 @@ function serverValidationManager($timeout) { callbacks.push({ propertyAlias: propertyAlias, culture: culture, + segment: segment, fieldName: fieldName, callback: callback, id: id @@ -199,25 +215,29 @@ function serverValidationManager($timeout) { * @param {} fieldName * @returns {} */ - unsubscribe: function (propertyAlias, culture, fieldName) { + unsubscribe: function (propertyAlias, culture, fieldName, segment) { - //normalize culture to null + //normalize culture to "invariant" if (!culture) { culture = "invariant"; } + //normalize segment to null + if (!segment) { + segment = null; + } if (propertyAlias === null) { //remove all callbacks for the content field callbacks = _.reject(callbacks, function (item) { - return item.propertyAlias === null && item.culture === culture && item.fieldName === fieldName; + return item.propertyAlias === null && item.culture === culture && item.segment === segment && item.fieldName === fieldName; }); } else if (propertyAlias !== undefined) { //remove all callbacks for the content property callbacks = _.reject(callbacks, function (item) { - return item.propertyAlias === propertyAlias && item.culture === culture && + return item.propertyAlias === propertyAlias && item.culture === culture && item.segment === segment && (item.fieldName === fieldName || ((item.fieldName === undefined || item.fieldName === "") && (fieldName === undefined || fieldName === ""))); }); @@ -236,16 +256,20 @@ function serverValidationManager($timeout) { * This will always return any callbacks registered for just the property (i.e. field name is empty) and for ones with an * explicit field name set. */ - getPropertyCallbacks: function (propertyAlias, culture, fieldName) { + getPropertyCallbacks: function (propertyAlias, culture, fieldName, segment) { - //normalize culture to null + //normalize culture to "invariant" if (!culture) { culture = "invariant"; } + //normalize segment to null + if (!segment) { + segment = null; + } var found = _.filter(callbacks, function (item) { //returns any callback that have been registered directly against the field and for only the property - return (item.propertyAlias === propertyAlias && item.culture === culture && (item.fieldName === fieldName || (item.fieldName === undefined || item.fieldName === ""))); + return (item.propertyAlias === propertyAlias && item.culture === culture && item.segment === segment && (item.fieldName === fieldName || (item.fieldName === undefined || item.fieldName === ""))); }); return found; }, @@ -262,7 +286,7 @@ function serverValidationManager($timeout) { getFieldCallbacks: function (fieldName) { var found = _.filter(callbacks, function (item) { //returns any callback that have been registered directly against the field - return (item.propertyAlias === null && item.culture === "invariant" && item.fieldName === fieldName); + return (item.propertyAlias === null && item.culture === "invariant" && item.segment === null && item.fieldName === fieldName); }); return found; }, @@ -274,12 +298,29 @@ function serverValidationManager($timeout) { * @function * * @description - * Gets all callbacks that has been registered using the subscribe method for the culture. + * Gets all callbacks that has been registered using the subscribe method for the culture. Not including segments. */ getCultureCallbacks: function (culture) { var found = _.filter(callbacks, function (item) { //returns any callback that have been registered directly/ONLY against the culture - return (item.culture === culture && item.propertyAlias === null && item.fieldName === null); + return (item.culture === culture && item.segment === null && item.propertyAlias === null && item.fieldName === null); + }); + return found; + }, + + /** + * @ngdoc function + * @name getVariantCallbacks + * @methodOf umbraco.services.serverValidationManager + * @function + * + * @description + * Gets all callbacks that has been registered using the subscribe method for the culture and segment. + */ + getVariantCallbacks: function (culture, segment) { + var found = _.filter(callbacks, function (item) { + //returns any callback that have been registered directly against the given culture and given segment. + return (item.culture === culture && item.segment === segment && item.propertyAlias === null && item.fieldName === null); }); return found; }, @@ -303,6 +344,7 @@ function serverValidationManager($timeout) { this.items.push({ propertyAlias: null, culture: "invariant", + segment: null, fieldName: fieldName, errorMsg: errorMsg }); @@ -314,7 +356,7 @@ function serverValidationManager($timeout) { var cbs = this.getFieldCallbacks(fieldName); //call each callback for this error for (var cb in cbs) { - executeCallback(this, errorsForCallback, cbs[cb].callback, null); + executeCallback(this, errorsForCallback, cbs[cb].callback, null, null); } }, @@ -327,7 +369,7 @@ function serverValidationManager($timeout) { * @description * Adds an error message for the content property */ - addPropertyError: function (propertyAlias, culture, fieldName, errorMsg) { + addPropertyError: function (propertyAlias, culture, fieldName, errorMsg, segment) { if (!propertyAlias) { return; } @@ -336,31 +378,36 @@ function serverValidationManager($timeout) { if (!culture) { culture = "invariant"; } + //normalize segment to null + if (!segment) { + segment = null; + } //only add the item if it doesn't exist - if (!this.hasPropertyError(propertyAlias, culture, fieldName)) { + if (!this.hasPropertyError(propertyAlias, culture, fieldName, segment)) { this.items.push({ propertyAlias: propertyAlias, culture: culture, + segment: segment, fieldName: fieldName, errorMsg: errorMsg }); } //find all errors for this item - var errorsForCallback = getPropertyErrors(this, propertyAlias, culture, fieldName); + var errorsForCallback = getPropertyErrors(this, propertyAlias, culture, segment, fieldName); //we should now call all of the call backs registered for this error - var cbs = this.getPropertyCallbacks(propertyAlias, culture, fieldName); + var cbs = this.getPropertyCallbacks(propertyAlias, culture, fieldName, segment); //call each callback for this error for (var cb in cbs) { - executeCallback(this, errorsForCallback, cbs[cb].callback, culture); + executeCallback(this, errorsForCallback, cbs[cb].callback, culture, segment); } - //execute culture specific callbacks here too when a propery error is added - var cultureCbs = this.getCultureCallbacks(culture); + //execute variant specific callbacks here too when a propery error is added + var variantCbs = this.getVariantCallbacks(culture, segment); //call each callback for this error - for (var cb in cultureCbs) { - executeCallback(this, errorsForCallback, cultureCbs[cb].callback, culture); + for (var cb in variantCbs) { + executeCallback(this, errorsForCallback, variantCbs[cb].callback, culture, segment); } }, @@ -373,7 +420,7 @@ function serverValidationManager($timeout) { * @description * Removes an error message for the content property */ - removePropertyError: function (propertyAlias, culture, fieldName) { + removePropertyError: function (propertyAlias, culture, fieldName, segment) { if (!propertyAlias) { return; @@ -383,10 +430,14 @@ function serverValidationManager($timeout) { if (!culture) { culture = "invariant"; } + //normalize segment to null + if (!segment) { + segment = null; + } //remove the item this.items = _.reject(this.items, function (item) { - return (item.propertyAlias === propertyAlias && item.culture === culture && (item.fieldName === fieldName || (fieldName === undefined || fieldName === ""))); + return (item.propertyAlias === propertyAlias && item.culture === culture && item.segment === segment && (item.fieldName === fieldName || (fieldName === undefined || fieldName === ""))); }); }, @@ -405,7 +456,10 @@ function serverValidationManager($timeout) { callbacks[cb].callback.apply(this, [ true, //pass in a value indicating it is VALID [], //pass in empty collection - []]); //pass in empty collection + [], + null, + null] + ); } }, @@ -431,16 +485,20 @@ function serverValidationManager($timeout) { * @description * Gets the error message for the content property */ - getPropertyError: function (propertyAlias, culture, fieldName) { + getPropertyError: function (propertyAlias, culture, fieldName, segment) { - //normalize culture to null + //normalize culture to "invariant" if (!culture) { culture = "invariant"; } + //normalize segment to null + if (!segment) { + segment = null; + } var err = _.find(this.items, function (item) { //return true if the property alias matches and if an empty field name is specified or the field name matches - return (item.propertyAlias === propertyAlias && item.culture === culture && (item.fieldName === fieldName || (fieldName === undefined || fieldName === ""))); + return (item.propertyAlias === propertyAlias && item.culture === culture && item.segment === segment && (item.fieldName === fieldName || (fieldName === undefined || fieldName === ""))); }); return err; }, @@ -457,7 +515,7 @@ function serverValidationManager($timeout) { getFieldError: function (fieldName) { var err = _.find(this.items, function (item) { //return true if the property alias matches and if an empty field name is specified or the field name matches - return (item.propertyAlias === null && item.culture === "invariant" && item.fieldName === fieldName); + return (item.propertyAlias === null && item.culture === "invariant" && item.segment === null && item.fieldName === fieldName); }); return err; }, @@ -471,16 +529,20 @@ function serverValidationManager($timeout) { * @description * Checks if the content property + culture + field name combo has an error */ - hasPropertyError: function (propertyAlias, culture, fieldName) { + hasPropertyError: function (propertyAlias, culture, fieldName, segment) { //normalize culture to null if (!culture) { culture = "invariant"; } + //normalize segment to null + if (!segment) { + segment = null; + } var err = _.find(this.items, function (item) { //return true if the property alias matches and if an empty field name is specified or the field name matches - return (item.propertyAlias === propertyAlias && item.culture === culture && (item.fieldName === fieldName || (fieldName === undefined || fieldName === ""))); + return (item.propertyAlias === propertyAlias && item.culture === culture && item.segment === segment && (item.fieldName === fieldName || (fieldName === undefined || fieldName === ""))); }); return err ? true : false; }, @@ -497,12 +559,11 @@ function serverValidationManager($timeout) { hasFieldError: function (fieldName) { var err = _.find(this.items, function (item) { //return true if the property alias matches and if an empty field name is specified or the field name matches - return (item.propertyAlias === null && item.culture === "invariant" && item.fieldName === fieldName); + return (item.propertyAlias === null && item.culture === "invariant" && item.segment === null && item.fieldName === fieldName); }); return err ? true : false; }, - /** * @ngdoc function * @name hasCultureError @@ -513,14 +574,40 @@ function serverValidationManager($timeout) { * Checks if the given culture has an error */ hasCultureError: function (culture) { - - //normalize culture to null + + //normalize culture to "invariant" if (!culture) { culture = "invariant"; } + + var err = _.find(this.items, function (item) { + return (item.culture === culture && item.segment === null); + }); + return err ? true : false; + }, + + /** + * @ngdoc function + * @name hasVariantError + * @methodOf umbraco.services.serverValidationManager + * @function + * + * @description + * Checks if the given culture has an error + */ + hasVariantError: function (culture, segment) { + + //normalize culture to "invariant" + if (!culture) { + culture = "invariant"; + } + //normalize segment to null + if (!segment) { + segment = null; + } var err = _.find(this.items, function (item) { - return item.culture === culture; + return (item.culture === culture && item.segment === segment); }); return err ? true : false; }, 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 61e3ae90ec..66f0b110bf 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 @@ -15,25 +15,85 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s var fallbackStyles = [{ title: "Page header", block: "h2" }, { title: "Section header", block: "h3" }, { title: "Paragraph header", block: "h4" }, { title: "Normal", block: "p" }, { title: "Quote", block: "blockquote" }, { title: "Code", block: "code" }]; // these languages are available for localization var availableLanguages = [ + 'ar', + 'ar_SA', + 'hy', + 'az', + 'eu', + 'be', + 'bn_BD', + 'bs', + 'bg_BG', + 'ca', + 'zh_CN', + 'zh_TW', + 'hr', + 'cs', 'da', - 'de', - 'en', - 'en_us', + 'dv', + 'nl', + 'en_CA', + 'en_GB', + 'et', + 'fo', 'fi', - 'fr', - 'he', + 'fr_FR', + 'gd', + 'gl', + 'ka_GE', + 'de', + 'de_AT', + 'el', + 'he_IL', + 'hi_IN', + 'hu_HU', + 'is_IS', + 'id', 'it', 'ja', - 'nl', - 'no', + 'kab', + 'kk', + 'km_KH', + 'ko_KR', + 'ku', + 'ku_IQ', + 'lv', + 'lt', + 'lb', + 'ml', + 'ml_IN', + 'mn_MN', + 'nb_NO', + 'fa', + 'fa_IR', 'pl', - 'pt', + 'pt_BR', + 'pt_PT', + 'ro', 'ru', - 'sv', - 'zh' + 'sr', + 'si_LK', + 'sk', + 'sl_SI', + 'es', + 'es_MX', + 'sv_SE', + 'tg', + 'ta', + 'ta_IN', + 'tt', + 'th_TH', + 'tr', + 'tr_TR', + 'ug', + 'uk', + 'uk_UA', + 'vi', + 'vi_VN', + 'cy' ]; //define fallback language - var defaultLanguage = 'en_us'; + var defaultLanguage = 'en_US'; /** * Returns a promise of an object containing the stylesheets and styleFormats collections @@ -109,7 +169,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s //wheras tinymce is in the format of ru, de, en, en_us, etc. var localeId = $locale.id.replace('-', '_'); //try matching the language using full locale format - var languageMatch = _.find(availableLanguages, function (o) { return o === localeId; }); + var languageMatch = _.find(availableLanguages, function (o) { return o.toLowerCase() === localeId; }); //if no matches, try matching using only the language if (languageMatch === undefined) { var localeParts = localeId.split('_'); @@ -248,6 +308,8 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s var src = imgUrl + "?width=" + newSize.width + "&height=" + newSize.height; editor.dom.setAttrib(imageDomElement, 'data-mce-src', src); } + + editor.execCommand("mceAutoResize", false, null, null); } } @@ -418,7 +480,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s //now we need to check if this custom config key is defined in our baseline, if it is we don't want to //overwrite the baseline config item if it is an array, we want to concat the items in the array, otherwise //if it's an object it will overwrite the baseline - if (angular.isArray(config[i]) && angular.isArray(tinyMceConfig.customConfig[i])) { + if (Utilities.isArray(config[i]) && Utilities.isArray(tinyMceConfig.customConfig[i])) { //concat it and below this concat'd array will overwrite the baseline in angular.extend tinyMceConfig.customConfig[i] = config[i].concat(tinyMceConfig.customConfig[i]); } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tour.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tour.service.js index e102da5d34..a11a9d6c82 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tour.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tour.service.js @@ -9,7 +9,7 @@ 'use strict'; function tourService(eventsService, currentUserResource, $q, tourResource) { - + var tours = []; var currentTour = null; @@ -18,14 +18,16 @@ */ function registerAllTours() { tours = []; - return tourResource.getTours().then(function(tourFiles) { - angular.forEach(tourFiles, function (tourFile) { - angular.forEach(tourFile.tours, function(newTour) { + return tourResource.getTours().then(function (tourFiles) { + tourFiles.forEach(tourFile => { + + tourFile.tours.forEach(newTour => { validateTour(newTour); validateTourRegistration(newTour); - tours.push(newTour); + tours.push(newTour); }); }); + eventsService.emit("appState.tour.updatedTours", tours); }); } @@ -74,7 +76,7 @@ tour.disabled = true; currentUserResource .saveTourStatus({ alias: tour.alias, disabled: tour.disabled, completed: tour.completed }).then( - function() { + function () { eventsService.emit("appState.tour.end", tour); currentTour = null; deferred.resolve(tour); @@ -96,7 +98,7 @@ tour.completed = true; currentUserResource .saveTourStatus({ alias: tour.alias, disabled: tour.disabled, completed: tour.completed }).then( - function() { + function () { eventsService.emit("appState.tour.complete", tour); currentTour = null; deferred.resolve(tour); @@ -130,35 +132,43 @@ function getGroupedTours() { var deferred = $q.defer(); var tours = getTours(); - setTourStatuses(tours).then(function() { + setTourStatuses(tours).then(function () { var groupedTours = []; tours.forEach(function (item) { - - var groupExists = false; - var newGroup = { - "group": "", - "tours": [] - }; - groupedTours.forEach(function(group){ - // extend existing group if it is already added - if(group.group === item.group) { - if(item.groupOrder) { - group.groupOrder = item.groupOrder + if (item.contentType === null || item.contentType === '') { + var groupExists = false; + var newGroup = { + "group": "", + "tours": [] + }; + + groupedTours.forEach(function (group) { + // extend existing group if it is already added + if (group.group === item.group) { + if (item.groupOrder) { + group.groupOrder = item.groupOrder; + } + groupExists = true; + + if (item.hidden === false) { + group.tours.push(item); + } } - groupExists = true; - group.tours.push(item) - } - }); + }); - // push new group to array if it doesn't exist - if(!groupExists) { - newGroup.group = item.group; - if(item.groupOrder) { - newGroup.groupOrder = item.groupOrder + // push new group to array if it doesn't exist + if (!groupExists) { + newGroup.group = item.group; + if (item.groupOrder) { + newGroup.groupOrder = item.groupOrder; + } + + if (item.hidden === false) { + newGroup.tours.push(item); + groupedTours.push(newGroup); + } } - newGroup.tours.push(item); - groupedTours.push(newGroup); } }); @@ -188,6 +198,24 @@ return deferred.promise; } + /** + * @ngdoc method + * @name umbraco.services.tourService#getToursForDoctype + * @methodOf umbraco.services.tourService + * + * @description + * Returns a promise of the tours found by documenttype alias. + * @param {Object} doctypeAlias The doctype alias for which the tours which should be returned + * @returns {Array} An array of tour objects for the doctype + */ + function getToursForDoctype(doctypeAlias) { + var deferred = $q.defer(); + tourResource.getToursForDoctype(doctypeAlias).then(function (tours) { + deferred.resolve(tours); + }); + return deferred.promise; + } + /////////// /** @@ -216,14 +244,14 @@ throw "Tour " + tour.alias + " is missing the required sections"; } } - + /** * Validates a tour before it gets registered in the service * @param {any} tour */ function validateTourRegistration(tour) { // check for existing tours with the same alias - angular.forEach(tours, function (existingTour) { + tours.forEach(existingTour => { if (existingTour.alias === tour.alias) { throw "A tour with the alias " + tour.alias + " is already registered"; } @@ -239,16 +267,17 @@ var deferred = $q.defer(); currentUserResource.getTours().then(function (storedTours) { - angular.forEach(storedTours, function (storedTour) { + storedTours.forEach(storedTour => { + if (storedTour.completed === true) { - angular.forEach(tours, function (tour) { + tours.forEach(tour => { if (storedTour.alias === tour.alias) { tour.completed = true; } }); } if (storedTour.disabled === true) { - angular.forEach(tours, function (tour) { + tours.forEach(tour => { if (storedTour.alias === tour.alias) { tour.disabled = true; } @@ -269,7 +298,8 @@ completeTour: completeTour, getCurrentTour: getCurrentTour, getGroupedTours: getGroupedTours, - getTourByAlias: getTourByAlias + getTourByAlias: getTourByAlias, + getToursForDoctype: getToursForDoctype }; return service; diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js index 3c9846fc43..0d6216f7cc 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js @@ -47,7 +47,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS /** Internal method to track expanded paths on a tree */ _trackExpandedPaths: function (node, expandedPaths) { - if (!node.children || !angular.isArray(node.children) || node.children.length == 0) { + if (!node.children || !Utilities.isArray(node.children) || node.children.length == 0) { return; } @@ -174,7 +174,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS //we determine this based on the server variables if (Umbraco.Sys.ServerVariables.umbracoPlugins && Umbraco.Sys.ServerVariables.umbracoPlugins.trees && - angular.isArray(Umbraco.Sys.ServerVariables.umbracoPlugins.trees)) { + Utilities.isArray(Umbraco.Sys.ServerVariables.umbracoPlugins.trees)) { var found = _.find(Umbraco.Sys.ServerVariables.umbracoPlugins.trees, function (item) { return invariantEquals(item.alias, treeAlias); @@ -473,7 +473,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS for (var i = 0; i < treeNode.children.length; i++) { var child = treeNode.children[i]; - if (child.children && angular.isArray(child.children) && child.children.length > 0) { + if (child.children && Utilities.isArray(child.children) && child.children.length > 0) { //recurse found = this.getDescendantNode(child, id); if (found) { @@ -773,7 +773,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS if (!args.path) { throw "No path defined on args object for syncTree"; } - if (!angular.isArray(args.path)) { + if (!Utilities.isArray(args.path)) { throw "Path must be an array"; } if (args.path.length < 1) { diff --git a/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js b/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js index a03a71febe..109fff0919 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js @@ -64,7 +64,7 @@ var saveModel = _.pick(displayModel, 'compositeContentTypes', 'isContainer', 'allowAsRoot', 'allowedTemplates', 'allowedContentTypes', 'alias', 'description', 'thumbnail', 'name', 'id', 'icon', 'trashed', - 'key', 'parentId', 'alias', 'path', 'allowCultureVariant', 'isElement'); + 'key', 'parentId', 'alias', 'path', 'allowCultureVariant', 'allowSegmentVariant', 'isElement'); // TODO: Map these saveModel.allowedTemplates = _.map(displayModel.allowedTemplates, function (t) { return t.alias; }); @@ -83,7 +83,7 @@ }); var saveProperties = _.map(realProperties, function (p) { - var saveProperty = _.pick(p, 'id', 'alias', 'description', 'validation', 'label', 'sortOrder', 'dataTypeId', 'groupId', 'memberCanEdit', 'showOnMemberProfile', 'isSensitiveData', 'allowCultureVariant'); + var saveProperty = _.pick(p, 'id', 'alias', 'description', 'validation', 'label', 'sortOrder', 'dataTypeId', 'groupId', 'memberCanEdit', 'showOnMemberProfile', 'isSensitiveData', 'allowCultureVariant', 'allowSegmentVariant'); return saveProperty; }); @@ -152,14 +152,13 @@ formatUserPostData: function (displayModel) { //create the save model from the display model - var saveModel = _.pick(displayModel, 'id', 'parentId', 'name', 'username', 'culture', 'email', 'startContentIds', 'startMediaIds', 'userGroups', 'message', 'changePassword'); - saveModel.changePassword = this.formatChangePasswordModel(saveModel.changePassword); + var saveModel = _.pick(displayModel, 'id', 'parentId', 'name', 'username', 'culture', 'email', 'startContentIds', 'startMediaIds', 'userGroups', 'message'); //make sure the userGroups are just a string array var currGroups = saveModel.userGroups; var formattedGroups = []; for (var i = 0; i < currGroups.length; i++) { - if (!angular.isString(currGroups[i])) { + if (!Utilities.isString(currGroups[i])) { formattedGroups.push(currGroups[i].alias); } else { @@ -230,7 +229,7 @@ var currSections = saveModel.sections; var formattedSections = []; for (var i = 0; i < currSections.length; i++) { - if (!angular.isString(currSections[i])) { + if (!Utilities.isString(currSections[i])) { formattedSections.push(currSections[i].alias); } else { @@ -243,7 +242,7 @@ var currUsers = saveModel.users; var formattedUsers = []; for (var j = 0; j < currUsers.length; j++) { - if (!angular.isNumber(currUsers[j])) { + if (!Utilities.isNumber(currUsers[j])) { formattedUsers.push(currUsers[j].id); } else { @@ -368,6 +367,7 @@ name: v.name || "", //if its null/empty,we must pass up an empty string else we get json converter errors properties: getContentProperties(v.tabs), culture: v.language ? v.language.culture : null, + segment: v.segment, publish: v.publish, save: v.save, releaseDate: v.releaseDate, @@ -394,38 +394,59 @@ */ formatContentGetData: function(displayModel) { - //We need to check for invariant properties among the variant variants. - //When we detect this, we want to make sure that the property object instance is the - //same reference object between all variants instead of a copy (which it will be when - //return from the JSON structure). + // We need to check for invariant properties among the variant variants, + // as the value of an invariant property is shared between different variants. + // A property can be culture invariant, segment invariant, or both. + // When we detect this, we want to make sure that the property object instance is the + // same reference object between all variants instead of a copy (which it will be when + // return from the JSON structure). if (displayModel.variants && displayModel.variants.length > 1) { + // Collect all invariant properties from the variants that are either the + // default language variant or the default segment variant. + var defaultVariants = _.filter(displayModel.variants, function (variant) { + var isDefaultLanguage = variant.language && variant.language.isDefault; + var isDefaultSegment = variant.segment == null; - var invariantProperties = []; - - //collect all invariant properties on the first first variant - var firstVariant = displayModel.variants[0]; - _.each(firstVariant.tabs, function(tab, tabIndex) { - _.each(tab.properties, function (property, propIndex) { - //in theory if there's more than 1 variant, that means they would all have a language - //but we'll do our safety checks anyways here - if (firstVariant.language && !property.culture) { - invariantProperties.push({ - tabIndex: tabIndex, - propIndex: propIndex, - property: property - }); - } - }); + return isDefaultLanguage || isDefaultSegment; }); + if (defaultVariants.length > 0) { + _.each(defaultVariants, function (defaultVariant) { + var invariantProps = []; - //now assign this same invariant property instance to the same index of the other variants property array - for (var j = 1; j < displayModel.variants.length; j++) { - var variant = displayModel.variants[j]; + _.each(defaultVariant.tabs, function (tab, tabIndex) { + _.each(tab.properties, function (property, propIndex) { + // culture == null -> property is culture invariant + // segment == null -> property is *possibly* segment invariant + if (!property.culture || !property.segment) { + invariantProps.push({ + tabIndex: tabIndex, + propIndex: propIndex, + property: property + }); + } + }); + }); - _.each(invariantProperties, function (invProp) { - variant.tabs[invProp.tabIndex].properties[invProp.propIndex] = invProp.property; + var otherVariants = _.filter(displayModel.variants, function (variant) { + return variant !== defaultVariant; + }); + + // now assign this same invariant property instance to the same index of the other variants property array + _.each(otherVariants, function (variant) { + _.each(invariantProps, function (invProp) { + var tab = variant.tabs[invProp.tabIndex]; + var prop = tab.properties[invProp.propIndex]; + + var inheritsCulture = prop.culture === invProp.property.culture && prop.segment == null && invProp.property.segment == null; + var inheritsSegment = prop.segment === invProp.property.segment && !prop.culture; + + if (inheritsCulture || inheritsSegment) { + tab.properties[invProp.propIndex] = invProp.property; + } + }); + }); }); } } 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 6c765ee135..edf698c8a7 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 @@ -44,7 +44,7 @@ function umbRequestHelper($http, $q, notificationsService, eventsService, formHe */ dictionaryToQueryString: function (queryStrings) { - if (angular.isArray(queryStrings)) { + if (Utilities.isArray(queryStrings)) { return _.map(queryStrings, function (item) { var key = null; var val = null; @@ -59,7 +59,7 @@ function umbRequestHelper($http, $q, notificationsService, eventsService, formHe return encodeURIComponent(key) + "=" + encodeURIComponent(val); }).join("&"); } - else if (angular.isObject(queryStrings)) { + else if (Utilities.isObject(queryStrings)) { //this allows for a normal object to be passed in (ie. a dictionary) return decodeURIComponent($.param(queryStrings)); @@ -91,7 +91,7 @@ function umbRequestHelper($http, $q, notificationsService, eventsService, formHe } return Umbraco.Sys.ServerVariables["umbracoUrls"][apiName] + actionName + - (!queryStrings ? "" : "?" + (angular.isString(queryStrings) ? queryStrings : this.dictionaryToQueryString(queryStrings))); + (!queryStrings ? "" : "?" + (Utilities.isString(queryStrings) ? queryStrings : this.dictionaryToQueryString(queryStrings))); }, @@ -129,7 +129,7 @@ function umbRequestHelper($http, $q, notificationsService, eventsService, formHe var err = { //NOTE: the default error message here should never be used based on the above docs! - errorMsg: (angular.isString(opts) ? opts : 'An error occurred!'), + errorMsg: (Utilities.isString(opts) ? opts : 'An error occurred!'), data: data, status: status }; @@ -254,7 +254,7 @@ function umbRequestHelper($http, $q, notificationsService, eventsService, formHe // so we know which property it belongs to on the server side var fileKey = "file_" + args.files[f].alias + "_" + (args.files[f].culture ? args.files[f].culture : ""); - if (angular.isArray(args.files[f].metaData) && args.files[f].metaData.length > 0) { + if (Utilities.isArray(args.files[f].metaData) && args.files[f].metaData.length > 0) { fileKey += ("_" + args.files[f].metaData.join("_")); } formData.append(fileKey, args.files[f].file); @@ -322,7 +322,7 @@ function umbRequestHelper($http, $q, notificationsService, eventsService, formHe //validate input, jsonData can be an array of key/value pairs or just one key/value pair. if (!jsonData) { throw "jsonData cannot be null"; } - if (angular.isArray(jsonData)) { + if (Utilities.isArray(jsonData)) { _.each(jsonData, function (item) { if (!item.key || !item.value) { throw "jsonData array item must have both a key and a value property"; } }); @@ -340,13 +340,13 @@ function umbRequestHelper($http, $q, notificationsService, eventsService, formHe transformRequest: function(data) { var formData = new FormData(); //add the json data - if (angular.isArray(data)) { + if (Utilities.isArray(data)) { _.each(data, function(item) { - formData.append(item.key, !angular.isString(item.value) ? angular.toJson(item.value) : item.value); + formData.append(item.key, !Utilities.isString(item.value) ? Utilities.toJson(item.value) : item.value); }); } else { - formData.append(data.key, !angular.isString(data.value) ? angular.toJson(data.value) : data.value); + formData.append(data.key, !Utilities.isString(data.value) ? Utilities.toJson(data.value) : data.value); } //call the callback @@ -365,12 +365,23 @@ function umbRequestHelper($http, $q, notificationsService, eventsService, formHe }, /** + * @ngdoc method + * @name umbraco.resources.contentResource#downloadFile + * @methodOf umbraco.resources.contentResource + * + * @description * Downloads a file to the client using AJAX/XHR - * Based on an implementation here: web.student.tuwien.ac.at/~e0427417/jsdownload.html - * See https://stackoverflow.com/a/24129082/694494 + * + * @param {string} httpPath the path (url) to the resource being downloaded + * @returns {Promise} http promise object. */ downloadFile : function (httpPath) { + /** + * Based on an implementation here: web.student.tuwien.ac.at/~e0427417/jsdownload.html + * See https://stackoverflow.com/a/24129082/694494 + */ + // Use an arraybuffer return $http.get(httpPath, { responseType: 'arraybuffer' }) .then(function (response) { 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 7723c8f4bb..de6fbaf782 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, requestRetryQueue, authResource, $timeout, angularHelper) { + .factory('userService', function ($rootScope, eventsService, $q, $location, requestRetryQueue, authResource, emailMarketingResource, $timeout, angularHelper) { var currentUser = null; var lastUserId = null; @@ -128,7 +128,7 @@ angular.module('umbraco.services') function setUserTimeoutInternal(newTimeout) { var asNumber = parseFloat(newTimeout); - if (!isNaN(asNumber) && currentUser && angular.isNumber(asNumber)) { + if (!isNaN(asNumber) && currentUser && Utilities.isNumber(asNumber)) { currentUser.remainingAuthSeconds = newTimeout; lastServerTimeoutSet = new Date(); } @@ -185,7 +185,19 @@ angular.module('umbraco.services') authenticate: function (login, password) { return authResource.performLogin(login, password) - .then(this.setAuthenticationSuccessful); + .then(function(data) { + + // Check if user has a start node set. + if(data.startContentIds.length === 0 && data.startMediaIds.length === 0){ + var errorMsg = "User has no start-nodes"; + var result = { errorMsg: errorMsg, user: data, authenticated: false, lastUserId: lastUserId, loginType: "credentials" }; + eventsService.emit("app.notAuthenticated", result); + throw result; + } + + return data; + + }).then(this.setAuthenticationSuccessful); }, setAuthenticationSuccessful: function (data) { @@ -262,6 +274,11 @@ angular.module('umbraco.services') /** Called whenever a server request is made that contains a x-umb-user-seconds response header for which we can update the user's remaining timeout seconds */ setUserTimeout: function (newTimeout) { setUserTimeoutInternal(newTimeout); + }, + + /** Calls out to a Remote Azure Function to deal with email marketing service */ + addUserToEmailMarketing: (user) => { + return emailMarketingResource.postAddUserToEmailMarketing(user); } }; 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 a7ff9def21..58ef342f44 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 @@ -206,11 +206,11 @@ function umbSessionStorage($window) { return { get: function (key) { - return angular.fromJson(storage["umb_" + key]); + return JSON.parse(storage["umb_" + key]); }, set: function (key, value) { - storage["umb_" + key] = angular.toJson(value); + storage["umb_" + key] = Utilities.toJson(value); } }; diff --git a/src/Umbraco.Web.UI.Client/src/init.js b/src/Umbraco.Web.UI.Client/src/init.js index 7d199c5c4f..ce824f63c0 100644 --- a/src/Umbraco.Web.UI.Client/src/init.js +++ b/src/Umbraco.Web.UI.Client/src/init.js @@ -1,6 +1,6 @@ /** Executed when the application starts, binds to events and set global state */ -app.run(['$rootScope', '$route', '$location', 'urlHelper', 'navigationService', 'appState', 'assetsService', 'eventsService', '$cookies', 'tourService', - function ($rootScope, $route, $location, urlHelper, navigationService, appState, assetsService, eventsService, $cookies, tourService) { +app.run(['$rootScope', '$route', '$location', 'urlHelper', 'navigationService', 'appState', 'assetsService', 'eventsService', '$cookies', 'tourService', 'localStorageService', + function ($rootScope, $route, $location, urlHelper, navigationService, appState, assetsService, eventsService, $cookies, tourService, localStorageService) { //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 @@ -23,11 +23,35 @@ app.run(['$rootScope', '$route', '$location', 'urlHelper', 'navigationService', appReady(data); tourService.registerAllTours().then(function () { - // Auto start intro tour + + // Start intro tour tourService.getTourByAlias("umbIntroIntroduction").then(function (introTour) { // start intro tour if it hasn't been completed or disabled if (introTour && introTour.disabled !== true && introTour.completed !== true) { tourService.startTour(introTour); + localStorageService.set("introTourShown", true); + } + else { + + const introTourShown = localStorageService.get("introTourShown"); + if (!introTourShown) { + // Go & show email marketing tour (ONLY when intro tour is completed or been dismissed) + tourService.getTourByAlias("umbEmailMarketing").then(function (emailMarketingTour) { + // Only show the email marketing tour one time - dismissing it or saying no will make sure it never appears again + // Unless invoked from tourService JS Client code explicitly. + // Accepted mails = Completed and Declicned mails = Disabled + if (emailMarketingTour && emailMarketingTour.disabled !== true && emailMarketingTour.completed !== true) { + + // Only show the email tour once per logged in session + // The localstorage key is removed on logout or user session timeout + const emailMarketingTourShown = localStorageService.get("emailMarketingTourShown"); + if (!emailMarketingTourShown) { + tourService.startTour(emailMarketingTour); + localStorageService.set("emailMarketingTourShown", true); + } + } + }); + } } }); }); @@ -65,7 +89,7 @@ app.run(['$rootScope', '$route', '$location', 'urlHelper', 'navigationService', currentRouteParams = toRetain; } else { - currentRouteParams = angular.copy(current.params); + currentRouteParams = Utilities.copy(current.params); } @@ -159,7 +183,7 @@ app.run(['$rootScope', '$route', '$location', 'urlHelper', 'navigationService', currentRouteParams = toRetain; } else { - currentRouteParams = angular.copy(next.params); + currentRouteParams = Utilities.copy(next.params); } //always clear the 'sr' query string (soft redirect) if it exists @@ -167,7 +191,7 @@ app.run(['$rootScope', '$route', '$location', 'urlHelper', 'navigationService', currentRouteParams.sr = null; $route.updateParams(currentRouteParams); } - + } } }); 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 f6f162f04f..05c391c3e7 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/installer.service.js +++ b/src/Umbraco.Web.UI.Client/src/installer/installer.service.js @@ -2,17 +2,16 @@ angular.module("umbraco.install").factory('installerService', function ($rootSco var _status = { index: 0, - current: undefined, - steps: undefined, + current: null, + steps: null, loading: true, progress: "100%" }; - var factTimer = undefined; + var factTimer; var _installerModel = { - installId: undefined, - instructions: { - } + installId: null, + instructions: {} }; //add to umbraco installer facts here @@ -304,7 +303,7 @@ angular.module("umbraco.install").factory('installerService', function ($rootSco }, switchToFeedback : function(){ - service.status.current = undefined; + service.status.current = null; service.status.loading = true; service.status.configuring = false; @@ -320,11 +319,11 @@ angular.module("umbraco.install").factory('installerService', function ($rootSco switchToConfiguration : function(){ service.status.loading = false; service.status.configuring = true; - service.status.feedback = undefined; - service.status.fact = undefined; + service.status.feedback = null; + service.status.fact = null; - if(factTimer){ - clearInterval(factTimer); + if (factTimer) { + clearInterval(factTimer); } }, @@ -335,8 +334,8 @@ angular.module("umbraco.install").factory('installerService', function ($rootSco service.status.feedback = "Redirecting you to Umbraco, please wait"; service.status.loading = false; - if(factTimer){ - clearInterval(factTimer); + if (factTimer) { + clearInterval(factTimer); } $timeout(function(){ diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/database.controller.js b/src/Umbraco.Web.UI.Client/src/installer/steps/database.controller.js index e83c5114fc..687fce95ae 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/steps/database.controller.js +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/database.controller.js @@ -4,18 +4,19 @@ angular.module("umbraco.install").controller("Umbraco.Installer.DataBaseControll $scope.invalidDbDns = false; $scope.dbs = [ - { name: 'Microsoft SQL Server Compact (SQL CE)', id: 0}, - { name: 'Microsoft SQL Server', id: 1}, + { name: 'Microsoft SQL Server Compact (SQL CE)', id: 0 }, + { name: 'Microsoft SQL Server', id: 1 }, { name: 'Microsoft SQL Azure', id: 3 }, - { name: 'Custom connection string', id: -1} + { name: 'Custom connection string', id: -1 } ]; - if ( installerService.status.current.model.dbType === undefined ) { + if (angular.isUndefined(installerService.status.current.model.dbType) || installerService.status.current.model.dbType === null) { installerService.status.current.model.dbType = 0; } - $scope.validateAndForward = function(){ - if ( !$scope.checking && this.myForm.$valid ) { + $scope.validateAndForward = function() { + if (!$scope.checking && this.myForm.$valid) + { $scope.checking = true; $scope.invalidDbDns = false; @@ -23,9 +24,9 @@ angular.module("umbraco.install").controller("Umbraco.Installer.DataBaseControll $http.post( Umbraco.Sys.ServerVariables.installApiBaseUrl + "PostValidateDatabaseConnection", - model ).then( function( response ) { + model).then(function(response) { - if ( response.data === true ) { + if (response.data === true) { installerService.forward(); } else { diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/machinekey.controller.js b/src/Umbraco.Web.UI.Client/src/installer/steps/machinekey.controller.js index bdcef63d95..fdd2c65c1c 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/steps/machinekey.controller.js +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/machinekey.controller.js @@ -1,5 +1,4 @@ angular.module("umbraco.install").controller("Umbraco.Installer.MachineKeyController", function ($scope, installerService) { - $scope.continue = function () { installerService.status.current.model = true; @@ -11,4 +10,4 @@ installerService.forward(); }; -}); \ No newline at end of file +}); diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/starterkit.html b/src/Umbraco.Web.UI.Client/src/installer/steps/starterkit.html index cfb08744b2..92f5cc8d9d 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/steps/starterkit.html +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/starterkit.html @@ -20,8 +20,9 @@
- +
diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/user.controller.js b/src/Umbraco.Web.UI.Client/src/installer/steps/user.controller.js index 10c0d596eb..9f0108853f 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/steps/user.controller.js +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/user.controller.js @@ -13,14 +13,14 @@ angular.module("umbraco.install").controller("Umbraco.Install.UserController", f $scope.passwordPattern = new RegExp(exp); } - $scope.validateAndInstall = function(){ - installerService.install(); + $scope.validateAndInstall = function() { + installerService.install(); }; $scope.validateAndForward = function(){ - if(this.myForm.$valid){ - installerService.forward(); - } + if (this.myForm.$valid) { + installerService.forward(); + } }; -}); \ 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 b5e032f9fb..174f9f41d7 100644 --- a/src/Umbraco.Web.UI.Client/src/less/belle.less +++ b/src/Umbraco.Web.UI.Client/src/less/belle.less @@ -107,6 +107,7 @@ @import "components/overlays.less"; @import "components/card.less"; @import "components/editor/umb-editor.less"; +@import "components/editor/umb-variant-switcher.less"; @import "components/umb-sub-views.less"; @import "components/umb-editor-navigation.less"; @import "components/umb-editor-navigation-item.less"; @@ -137,6 +138,7 @@ @import "components/tooltip/umb-tooltip-list.less"; @import "components/overlays/umb-overlay-backdrop.less"; @import "components/overlays/umb-itempicker.less"; +@import "components/overlays/umb-variant-selector-overlay"; @import "components/umb-grid.less"; @import "components/umb-empty-state.less"; @import "components/umb-property-editor.less"; @@ -194,6 +196,8 @@ @import "components/contextdialogs/umb-dialog-datatype-delete.less"; +@import "components/umbemailmarketing.less"; + // Utilities @import "utilities/layout/_display.less"; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-dashboard.less b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-dashboard.less index 52ff2c2b01..03153973ff 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-dashboard.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-dashboard.less @@ -31,6 +31,6 @@ border: none; } -.umb-dashboard__header .umb-tabs-nav .umb-tab > a { +.umb-dashboard__header .umb-tabs-nav .umb-tab > .umb-tab-button { padding-bottom: 25px; } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-search.less b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-search.less index 70e4f3d372..2f9430ef41 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-search.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-search.less @@ -16,6 +16,10 @@ box-shadow: 0 10px 20px rgba(0,0,0,.12),0 6px 6px rgba(0,0,0,.14); } +.umb-search__label{ + margin: 0; +} + /* Search field */ @@ -107,4 +111,4 @@ .umb-search-result__description { color: @gray-5; font-size: 13px; -} \ No newline at end of file +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-tour.less b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-tour.less index 42403c65b1..bf2f030cea 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-tour.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-tour.less @@ -119,3 +119,17 @@ border: none; padding: 0; } + +.umb-tour__popover--promotion { + width: 800px; + min-height: 400px; + padding: 40px; + border-radius: @baseBorderRadius * 2; + .umb-tour-step__close { + top: 40px; + right: 40px; + } + a { + text-decoration: underline; + } +} 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 index 0465881387..02b67460f6 100644 --- 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 @@ -17,15 +17,17 @@ .umb-button-group { .umb-button__button { - border-radius: @baseBorderRadius; + border-radius: @baseBorderRadius 0 0 @baseBorderRadius; + + &:hover { + z-index: 2; + } } .umb-button-group__toggle { border-radius: 0 @baseBorderRadius @baseBorderRadius 0; - border-left: 1px solid rgba(0,0,0,0.09); - margin-left: -2px; + margin-left: -1px; padding-left: 10px; padding-right: 10px; } - } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-toggle.less b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-toggle.less index 456601a7bd..ff4122b258 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-toggle.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-toggle.less @@ -103,14 +103,21 @@ - +.umb-toggle.umb-toggle--disabled.umb-toggle--checked, .umb-toggle.umb-toggle--disabled { .umb-toggle__toggle { cursor: not-allowed; - border-color: @gray-5; + background-color: @gray-9; + border-color: @gray-9; + } + .umb-toggle__icon--left { + color: @gray-6; + } + .umb-toggle__icon--right { + color: @gray-6; } .umb-toggle__handler { - background-color: @gray-5; + background-color: @gray-10; } } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/editor.less b/src/Umbraco.Web.UI.Client/src/less/components/editor.less index bc84b0d35e..ac55c6ffb1 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/editor.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/editor.less @@ -162,161 +162,6 @@ a.umb-editor-header__close-split-view:hover { } } -/* variant switcher */ -.umb-variant-switcher__toggle { - position: relative; - display: flex; - align-items: center; - padding: 0 10px; - margin: 1px 1px; - right: 0; - height: 30px; - text-decoration: none !important; - font-size: 13px; - color: @ui-action-discreet-type; - background: transparent; - border: none; - - max-width: 50%; - white-space: nowrap; - - user-select: none; - - span { - text-overflow: ellipsis; - overflow: hidden; - } -} - -button.umb-variant-switcher__toggle { - transition: color 0.2s ease-in-out; - &:hover { - //background-color: @gray-10; - color: @ui-action-discreet-type-hover; - .umb-variant-switcher__expand { - color: @ui-action-discreet-type-hover; - } - } - - &.--error { - &::before { - content: '!'; - position: absolute; - top: -8px; - right: -10px; - display: inline-flex; - align-items: center; - justify-content: center; - width: 20px; - height: 20px; - border-radius: 10px; - text-align: center; - font-weight: bold; - background-color: @errorBackground; - color: @errorText; - } - } -} - -.umb-variant-switcher__expand { - color: @ui-action-discreet-type; - margin-top: 3px; - margin-left: 5px; - margin-right: -5px; - transition: color 0.2s ease-in-out; -} - -.umb-variant-switcher__item { - display: flex; - justify-content: space-between; - align-items: center; - border-bottom: 1px solid @gray-9; - position: relative; -} - -.umb-variant-switcher__item:last-child { - border-bottom: none; -} - -.umb-variant-switcher__item.--current { - color: @ui-light-active-type; -} -.umb-variant-switcher__item.--current .umb-variant-switcher__name-wrapper { - border-left: 4px solid @ui-active; -} - -.umb-variant-switcher__item:hover { - outline: none; -} - -.umb-variant-switcher__item.--not-allowed:not(.--current) .umb-variant-switcher__name-wrapper:hover { - //background-color: @white !important; - cursor: default; -} - -.umb-variant-switcher__item:hover .umb-variant-switcher__split-view { - display: block; - cursor: pointer; -} - -.umb-variant-switcher__item.--error { - .umb-variant-switcher__name { - color: @red; - &::after { - content: '!'; - position: relative; - display: inline-flex; - align-items: center; - justify-content: center; - margin-left: 5px; - top: -3px; - width: 14px; - height: 14px; - border-radius: 7px; - font-size: 8px; - text-align: center; - font-weight: bold; - background-color: @errorBackground; - color: @errorText; - } - } -} - -.umb-variant-switcher__name-wrapper { - font-size: 14px; - flex: 1; - cursor: pointer; - padding-top: 6px !important; - padding-bottom: 6px !important; - background-color: transparent; - border: none; - border-left: 2px solid transparent; -} - -.umb-variant-switcher__name { - display: block; -} - -.umb-variant-switcher__state { - font-size: 13px; - color: @gray-4; -} - -.umb-variant-switcher__split-view { - font-size: 13px; - display: none; - padding: 16px 20px; - position: absolute; - right: 0; - top: 0; - bottom: 0; - background-color: @white; - - &:hover { - background-color: @ui-option-hover; - color: @ui-option-type-hover; - } -} // container 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 index 4ebfa94b6f..3c4a037b0b 100644 --- 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 @@ -5,7 +5,7 @@ border-right: 5px solid @brownGrayLight; display: flex; justify-content: space-between; - margin: -10px -5px 10px; + margin: -10px -1px 10px; position: relative; top: 0; box-sizing: border-box; @@ -34,6 +34,7 @@ transition: box-shadow 240ms; position:sticky; z-index: 30; + width: calc(100% + 2px); &.umb-sticky-bar--active { box-shadow: 0 6px 3px -3px rgba(0,0,0,.16); diff --git a/src/Umbraco.Web.UI.Client/src/less/components/editor/umb-editor.less b/src/Umbraco.Web.UI.Client/src/less/components/editor/umb-editor.less index d2a3bdedb1..e81df77772 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/editor/umb-editor.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/editor/umb-editor.less @@ -1,6 +1,7 @@ .umb-editors { .absolute(); overflow: hidden; + z-index: 7500; .umb-editor { box-shadow: 0px 0 30px 0 rgba(0,0,0,.3); @@ -104,4 +105,4 @@ i { margin-right:5px; } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/editor/umb-variant-switcher.less b/src/Umbraco.Web.UI.Client/src/less/components/editor/umb-variant-switcher.less new file mode 100644 index 0000000000..8dbc070856 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/editor/umb-variant-switcher.less @@ -0,0 +1,332 @@ +/* variant switcher */ +.umb-variant-switcher__toggle { + position: relative; + display: flex; + align-items: center; + padding: 0 10px; + margin: 1px 1px; + right: 0; + height: 30px; + text-decoration: none !important; + font-size: 13px; + color: @ui-action-discreet-type; + background: transparent; + border: none; + + max-width: 50%; + white-space: nowrap; + + user-select: none; + + span { + text-overflow: ellipsis; + overflow: hidden; + } +} + +button.umb-variant-switcher__toggle { + transition: color 0.2s ease-in-out; + &:hover { + //background-color: @gray-10; + color: @ui-action-discreet-type-hover; + .umb-variant-switcher__expand { + color: @ui-action-discreet-type-hover; + } + } + + &.--error { + &::before { + content: '!'; + position: absolute; + top: -8px; + right: -10px; + display: inline-flex; + align-items: center; + justify-content: center; + width: 20px; + height: 20px; + border-radius: 10px; + text-align: center; + font-weight: bold; + background-color: @errorBackground; + color: @errorText; + } + } +} + +.umb-variant-switcher__expand { + color: @ui-action-discreet-type; + margin-top: 3px; + margin-left: 5px; + margin-right: -5px; + transition: color 0.2s ease-in-out; +} + + +.umb-variant-switcher { + min-width: 100%; + max-height: 80vh; + overflow-y: auto; + margin-top: 5px; + user-select: none; +} + +.umb-variant-switcher__item { + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid @gray-9; + position: relative; + .umb-variant-switcher__name-wrapper:hover { + .umb-variant-switcher__name { + color: @blueMid; + } + .umb-variant-switcher__state { + color: @blueMid; + } + } +} +.umb-variant-switcher__item.--state-notCreated:not(.--active) { + .umb-variant-switcher__name-wrapper::before { + content: "+"; + display: block; + float: left; + font-size: 15px; + font-weight: 900; + padding: 8px 16px 8px 6px; + color: @gray-5; + } + .umb-variant-switcher__item-expand-button + .umb-variant-switcher__name-wrapper::before { + padding: 8px 16px 8px 20px; + } + .umb-variant-switcher__name { + color: @gray-5; + } + .umb-variant-switcher__state { + color: @gray-6; + } + .umb-variant-switcher__name-wrapper::after { + content: ""; + position: absolute; + z-index: 1; + border: 1px dashed @gray-9; + top: 7px; + bottom: 7px; + left: 7px; + right: 7px; + border-radius: 3px; + pointer-events: none; + } + + .umb-variant-switcher__name-wrapper:hover { + &::before { + color: @blueMid; + } + .umb-variant-switcher__name { + color: @blueMid; + } + .umb-variant-switcher__state { + color: @blueMid; + } + } +} +/* +.umb-variant-switcher__item.--state-draft { + .umb-variant-switcher__name { + color: @gray-5; + } + &:hover { + .umb-variant-switcher__name { + color: @blueMid; + } + } +} +*/ + +.umb-variant-switcher.--has-sub-variants { + .umb-variant-switcher__item { + + } +} + +.umb-variant-switcher__item-expand-button { + text-decoration: none; + display: inline-block; + flex: 0; + align-self: stretch; + + padding-left: 22px !important; + padding-right: 14px !important; + + font-size: 12px; + + * { + pointer-events: none; + } +} + +.umb-variant-switcher__item:last-child { + border-bottom: none; +} + +.umb-variant-switcher__item.--current { + //color: @ui-light-active-type; + //background-color: @pinkExtraLight; + .umb-variant-switcher__name { + //color: @ui-light-active-type; + font-weight: 700; + } + &::before { + content: ''; + position: absolute; + border-radius: 0 4px 4px 0; + background-color: @ui-active-border; + width: 4px; + top:8px; + bottom: 8px; + left:0; + z-index:1; + pointer-events: none; + } +} + +.umb-variant-switcher__item:hover { + outline: none; +} + +.umb-variant-switcher__item.--active:not(.--current) .umb-variant-switcher__name-wrapper:hover { + //background-color: @white !important; + cursor: default; +} + +.umb-variant-switcher__item:focus .umb-variant-switcher__split-view, +.umb-variant-switcher__item:focus-within .umb-variant-switcher__split-view, +.umb-variant-switcher__item:hover .umb-variant-switcher__split-view, +.umb-variant-switcher__split-view:focus { + display: block; + cursor: pointer; +} + +.umb-variant-switcher__item.--error { + .umb-variant-switcher__name { + color: @red; + &::after { + content: '!'; + position: relative; + display: inline-flex; + align-items: center; + justify-content: center; + margin-left: 5px; + top: -3px; + width: 14px; + height: 14px; + border-radius: 7px; + font-size: 8px; + text-align: center; + font-weight: bold; + background-color: @errorBackground; + color: @errorText; + } + } +} + +.umb-variant-switcher__name-wrapper { + font-size: 14px; + text-align: left; + flex: 1; + cursor: pointer; + background-color: transparent; + border: none; +} +.dropdown-menu>li { + > .umb-variant-switcher__name-wrapper { + padding-top: 10px; + padding-bottom: 10px; + } + + > .umb-variant-switcher__item-expand-button + .umb-variant-switcher__name-wrapper { + padding-left: 5px; + } +} + + +.umb-variant-switcher__name { + display: block; + font-weight: 600; + margin-bottom: -2px; +} + +.umb-variant-switcher__state { + font-size: 12px; + color: @gray-4; +} + +.umb-variant-switcher__split-view { + font-size: 12px; + display: none; + padding: 20px 20px; + position: absolute; + right: 0; + top: 0; + bottom: 0; + background-color: @white; + + &:hover { + background-color: @ui-option-hover; + color: @ui-option-type-hover; + } +} + + +.umb-variant-switcher__sub-variants { + + position: relative; + border-bottom: 1px solid @gray-9; + background-color: @gray-13; + /* + &::before { + content: ""; + position: absolute; + z-index: 1; + top: 0px; + left: 20px; + width: 4px; + bottom: 14px; + border-bottom-left-radius: 2px; + border-bottom-right-radius: 2px; + background-color: @gray-8; + } + */ + .umb-variant-switcher__item { + border-bottom-color: @gray-10; + } + + .umb-variant-switcher__item.--state-notCreated:not(.--active) { + .umb-variant-switcher__name-wrapper::after { + left: 55px;// overwrite left to achieve same indentation on the dashed border as language. + } + } + + .umb-variant-switcher__name-wrapper { + + margin-left: 48px; + padding-left: 20px; + + padding-top: 10px; + padding-bottom: 10px; + + &:hover { + color: @ui-option-type-hover; + background-color: @ui-option-hover; + } + + .umb-variant-switcher__name { + //margin-right: 20px; + } + .umb-variant-switcher__state { + //flex: 0 0 200px; + } + + + } +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/overlays.less b/src/Umbraco.Web.UI.Client/src/less/components/overlays.less index 853e142110..381157c8bc 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/overlays.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/overlays.less @@ -2,7 +2,7 @@ position: fixed; overflow: hidden; background: @white; - z-index: @zindexUmbOverlay; + z-index: 7501; animation: fadeIn 0.2s; box-shadow: 0 10px 50px rgba(0,0,0,0.1), 0 6px 20px rgba(0,0,0,0.16); text-align: left; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/overlays/umb-variant-selector-overlay.less b/src/Umbraco.Web.UI.Client/src/less/components/overlays/umb-variant-selector-overlay.less new file mode 100644 index 0000000000..b50a622f98 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/overlays/umb-variant-selector-overlay.less @@ -0,0 +1,26 @@ +.umb-variant-selector-overlay { + + + .umb-variant-selector-entry { + .umb-form-check { + .umb-form-check__symbol { + margin-top: 2px; + } + } + } + .umb-variant-selector-entry__title { + font-weight: 600; + font-size: 14px; + .__secondarytitle { + font-weight: normal; + color: @gray-5; + } + } + .umb-variant-selector-entry__description { + display: block; + font-size: 12px; + color: @gray-4; + } + + +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less index d06c15cd30..839e61c5f9 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less @@ -256,7 +256,7 @@ body.touch .umb-tree { } &.current > .umb-tree-item__inner > .umb-tree-item__annotation { - background-color: @pinkLight; + background-color: @ui-active; } } 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 index b6cdc0e8d9..937f746c56 100644 --- 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 @@ -16,20 +16,24 @@ .umb-child-selector__child.-placeholder { border: 1px dashed @gray-8; background: none; - cursor: pointer; text-align: center; justify-content: center; - - color:@ui-action-type; + width: 100%; + color: @ui-action-type; + &:hover { - color:@ui-action-type-hover; - border-color:@ui-action-type-hover; - text-decoration:none; + color: @ui-action-type-hover; + border-color: @ui-action-type-hover; + text-decoration: none; } } .umb-child-selector__children-container { - margin-left: 30px; + margin-left: 30px; + + .umb-child-selector__child.ui-sortable-handle { + cursor: move; + } } .umb-child-selector__child-description { @@ -62,5 +66,6 @@ } .umb-child-selector__child-remove { - cursor: pointer; + background: none; + border: none; } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-content-grid.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-content-grid.less index 622dcb8b0a..47fc8a10b9 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-content-grid.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-content-grid.less @@ -11,7 +11,7 @@ cursor: pointer; position: relative; user-select: none; - box-shadow: 0 1px 1px 0 rgba(0,0,0,0.16); + box-shadow: 0 1px 2px 0 rgba(0,0,0,0.16); border-radius: 3px; } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-navigation-item.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-navigation-item.less index 6a6a8f9f5b..f3c41dbc33 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-navigation-item.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-navigation-item.less @@ -115,7 +115,7 @@ li { &.is-active a { - border-left-color: @ui-active; + border-left-color: @ui-active-border; } a { @@ -178,11 +178,11 @@ } // Validation -.umb-sub-views-nav-item__action.-has-error, +.show-validation .umb-sub-views-nav-item__action.-has-error, .show-validation .umb-sub-views-nav-item > a.-has-error { color: @red; - &::after { + &::before { background-color: @red; } } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-form-check.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-form-check.less index 76a4df0056..a52f81b92a 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-form-check.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-form-check.less @@ -1,17 +1,26 @@ -@checkboxWidth: 16px; -@checkboxHeight: 16px; +@checkboxWidth: 18px; +@checkboxHeight: 18px; +label.umb-form-check--checkbox{ + margin:3px 0; +} .umb-form-check { display: flex; - flex-wrap: wrap; - align-items: center; position: relative; - padding: 0 0 0 26px !important; + padding-left: 0px; margin: 0; min-height: 22px; - line-height: 22px; cursor: pointer !important; + .umb-form-check__symbol { + margin-top: 4px; + margin-right: 10px; + } + .umb-form-check__info { + margin-left:20px; + } + + &.-small-text{ font-size: 13px; } @@ -22,7 +31,6 @@ &__text { position: relative; - top: 1px; user-select: none; } @@ -90,10 +98,10 @@ &__state { display: flex; height: 18px; - margin-top: 2px; position: absolute; + margin-top: 2px; top: 0; - left: 0; + left: -1px; } &__check { @@ -101,6 +109,7 @@ position: relative; background: @white; border: 1px solid @inputBorder; + border-radius: @baseBorderRadius; width: @checkboxWidth; height: @checkboxHeight; @@ -160,5 +169,6 @@ &.umb-form-check--disabled { cursor: not-allowed !important; opacity: 0.5; + pointer-events: none; } } 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 index 479074fee9..26d61412ae 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-grid.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-grid.less @@ -162,6 +162,8 @@ } .umb-grid .umb-row .umb-cell-placeholder { + display: block; + width: 100%; min-height: 88px; border-width: 1px; border-style: dashed; @@ -226,6 +228,7 @@ .umb-grid .cell-tools-add.-bar { display: block; + width: calc(100% - 20px); text-align: center; padding: 5px; border: 1px dashed @ui-action-discreet-border; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-iconpicker.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-iconpicker.less index e8a62f739d..98b2b1d72d 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-iconpicker.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-iconpicker.less @@ -15,7 +15,9 @@ overflow: hidden; } -.umb-iconpicker-item a { +.umb-iconpicker-item button { + background: transparent; + border: 0 none; display: flex; justify-content: center; align-items: center; @@ -26,8 +28,8 @@ border-radius: 3px; } -.umb-iconpicker-item a:hover, -.umb-iconpicker-item a:focus { +.umb-iconpicker-item button:hover, +.umb-iconpicker-item button:focus { background: @gray-10; outline: none; } @@ -39,7 +41,7 @@ box-sizing: border-box; } -.umb-iconpicker-item a:active { +.umb-iconpicker-item button:active { background: @gray-10; } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-list.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-list.less index 94cfa6f62c..44955e8f8e 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-list.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-list.less @@ -1,15 +1,18 @@ .umb-list--condensed { .umb-list-item { - padding-top: 5px; - padding-bottom: 5px; + padding-top: 7px; + padding-bottom: 7px; } } .umb-list-item { - border-bottom: 1px solid @gray-9; + border-bottom: 1px solid @gray-11; padding-top: 15px; padding-bottom: 15px; display: flex; + &:last-of-type { + border-bottom: none; + } } a.umb-list-item:hover, diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-mini-search.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-mini-search.less index ac15b3dcf8..ec598c17eb 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-mini-search.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-mini-search.less @@ -4,20 +4,26 @@ .icon { position: absolute; - padding: 5px 8px; + width: 30px; + height: 30px; + display: flex; + justify-content: center; + align-items: center; + margin: 1px; + padding: 0; pointer-events: none; - top: 2px; color: @ui-action-discreet-type; transition: color .1s linear; } input { width: 0px; - padding-left:24px; + padding-left: 24px; margin-bottom: 0px; background-color: transparent; border-color: @ui-action-discreet-border; transition: background-color .1s linear, border-color .1s linear, color .1s linear, width .1s ease-in-out, padding-left .1s ease-in-out; + cursor: pointer; } &:focus-within, &:hover { @@ -34,6 +40,7 @@ background-color: white; color: @ui-action-discreet-border-hover; border-color: @ui-action-discreet-border-hover; + cursor: unset; } input:focus, &:focus-within input, &.--has-value input { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less index 4168ab3c39..716693c778 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less @@ -135,6 +135,8 @@ .umb-nested-content__header-bar:hover .umb-nested-content__icons, +.umb-nested-content__header-bar:focus .umb-nested-content__icons, +.umb-nested-content__header-bar:focus-within .umb-nested-content__icons, .umb-nested-content__item--active > .umb-nested-content__header-bar .umb-nested-content__icons { opacity: 1; } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-node-preview.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-node-preview.less index f754a09368..939fd79826 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-node-preview.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-node-preview.less @@ -29,7 +29,8 @@ .umb-node-preview__icon { display: flex; width: 25px; - height: 25px; + min-height: 25px; + height: 100%; justify-content: center; align-items: center; font-size: 20px; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-property-actions.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-property-actions.less index 3f0b981ac6..17c4bf1a55 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-property-actions.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-property-actions.less @@ -61,6 +61,7 @@ opacity: 0; transition: opacity 120ms; } +.umb-property:focus-within .umb-property-actions__toggle, .umb-property:hover .umb-property-actions__toggle, .umb-property .umb-property-actions__toggle:focus { opacity: 1; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-property-file-upload.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-property-file-upload.less index 08b1a1b5e1..9550acfb1b 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-property-file-upload.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-property-file-upload.less @@ -1,6 +1,7 @@ .umb-property-file-upload { .umb-upload-button-big { + max-width: (@propertyEditorLimitedWidth - 40); display: block; padding: 20px; opacity: 1; 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 index ee784787fa..15b317aa45 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-tabs.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-tabs.less @@ -13,25 +13,31 @@ top: 1px; } -.umb-tab > a { - +.umb-tab-button { display: flex; justify-content: center; align-items: center; position: relative; - + cursor: pointer; //border-bottom: 4px solid transparent; color: @ui-light-type; padding: 5px 20px 15px 20px; transition: color 150ms ease-in-out; + &:focus { color: @ui-light-type-hover; + + body:not(.tabbing-active) &{ + outline: none; + } } + &:hover { color: @ui-light-type-hover; text-decoration: none; } + &::after { content: ""; height: 0px; @@ -42,12 +48,21 @@ bottom: 0; border-radius: 3px 3px 0 0; opacity: 0; - transition: all .2s linear; + transition: all 0.2s linear; + } + + &--expand > i { + height: 5px; + width: 5px; + border-radius: 50%; + background: @black; + display: inline-block; + margin: 0 5px 0 0; + opacity: 0.6; } } - -.umb-tab--active > a { +.umb-tab--active > .umb-tab-button { color: @ui-light-active-type; //border-bottom-color: @ui-active; /* @@ -64,19 +79,19 @@ } } -.show-validation .umb-tab--error > a, -.show-validation .umb-tab--error > a:hover, -.show-validation .umb-tab--error > a:focus { - color: @white !important; - background-color: @red !important; - border-color: @errorBorder; +.show-validation .umb-tab--error > .umb-tab-button, +.show-validation .umb-tab--error > .umb-tab-button:hover, +.show-validation .umb-tab--error > .umb-tab-button:focus { + color: @white !important; + background-color: @red !important; + border-color: @errorBorder; } -.show-validation .umb-tab--error a:before { - content: "\e25d"; - font-family: 'icomoon'; - margin-right: 5px; - vertical-align: top; +.show-validation .umb-tab--error .umb-tab-button:before { + content: "\e25d"; + font-family: "icomoon"; + margin-right: 5px; + vertical-align: top; } // tabs tray @@ -86,20 +101,10 @@ left: auto; } -.umb-tabs-tray > a { +.umb-tabs-tray > .umb-tab-button { cursor: pointer; } .umb-tabs-tray-item--active { border-left: 2px solid @ui-active; } - -.umb-tab--expand > a > i { - height: 5px; - width: 5px; - border-radius: 50%; - background: @black; - display: inline-block; - margin: 0 5px 0 0; - opacity: .6; -} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umbemailmarketing.less b/src/Umbraco.Web.UI.Client/src/less/components/umbemailmarketing.less new file mode 100644 index 0000000000..f4b3183045 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umbemailmarketing.less @@ -0,0 +1,44 @@ +.umb-email-marketing { + + h2 { + font-weight: 800; + max-width: 26ex; + margin-top: 20px; + } + + .layout { + display: flex; + align-items: center; + align-content: stretch; + + .primary { + flex-basis: 50%; + padding-right: 40px; + padding-top: 20px; + padding-bottom: 20px; + .notice { + color: @gray-5; + font-style: italic; + a { + color: @gray-5; + &:hover { + color: @ui-action-type-hover; + } + } + } + } + + .secondary { + flex-basis: 50%; + svg { + height: 200px; + width: 100%; + margin-top: -60px; + } + } + } + + .cta { + text-align: right; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/less/forms.less b/src/Umbraco.Web.UI.Client/src/less/forms.less index 0600c9aab6..ffc0626981 100644 --- a/src/Umbraco.Web.UI.Client/src/less/forms.less +++ b/src/Umbraco.Web.UI.Client/src/less/forms.less @@ -297,7 +297,6 @@ select[size] { } // Focus for select, file, radio, and checkbox -select, input[type="file"], input[type="radio"], input[type="checkbox"] { diff --git a/src/Umbraco.Web.UI.Client/src/less/gridview.less b/src/Umbraco.Web.UI.Client/src/less/gridview.less index 238feead90..11ba7b2795 100644 --- a/src/Umbraco.Web.UI.Client/src/less/gridview.less +++ b/src/Umbraco.Web.UI.Client/src/less/gridview.less @@ -101,6 +101,9 @@ position:relative; } + .usky-grid .grid-layout { + max-width: 600px; +} // ROW // ------------------------- @@ -517,6 +520,25 @@ position:relative; } + .usky-grid .uSky-templates .layout { + margin-top: 5px; + margin-bottom: 20px; + float: left; +} + + +.usky-grid .uSky-templates .columns { + margin-top: 5px; + margin-bottom: 25px; + float: left; +} + + +.usky-grid .uSky-templates .columns .preview-cell p { + font-size: 6px; + line-height: 8px; + text-align: center; +} /**************************************************************************************************/ diff --git a/src/Umbraco.Web.UI.Client/src/less/mixins.less b/src/Umbraco.Web.UI.Client/src/less/mixins.less index efc0178ca2..f5c499cbfc 100644 --- a/src/Umbraco.Web.UI.Client/src/less/mixins.less +++ b/src/Umbraco.Web.UI.Client/src/less/mixins.less @@ -418,7 +418,7 @@ // -------------------------------------------------- // Limit width of specific property editors .umb-property-editor--limit-width { - max-width: 800px; + max-width: @propertyEditorLimitedWidth; } // Horizontal dividers diff --git a/src/Umbraco.Web.UI.Client/src/less/modals.less b/src/Umbraco.Web.UI.Client/src/less/modals.less index 925f845c4c..4ce907d06f 100644 --- a/src/Umbraco.Web.UI.Client/src/less/modals.less +++ b/src/Umbraco.Web.UI.Client/src/less/modals.less @@ -88,9 +88,13 @@ background: @white; } -.umb-dialog .umb-btn-toolbar .umb-control-group{ - border: none; - padding: none; +.umb-dialog .abstract{ + margin-bottom:20px; +} + +.umb-dialog .umb-btn-toolbar .umb-control-group { + border: none; + padding: none; } .umb-dialog-body{ diff --git a/src/Umbraco.Web.UI.Client/src/less/properties.less b/src/Umbraco.Web.UI.Client/src/less/properties.less index 8523fe9300..9e951feb1a 100644 --- a/src/Umbraco.Web.UI.Client/src/less/properties.less +++ b/src/Umbraco.Web.UI.Client/src/less/properties.less @@ -49,7 +49,7 @@ } .date-wrapper-mini--checkbox{ - margin: 0 0 0 26px; + margin: 0 0 0 28px; } .date-wrapper-mini__date { @@ -62,6 +62,10 @@ &:first-of-type { margin-left: 0; } + .flatpickr-input > button:first-of-type { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } } .date-wrapper-mini__date .flatpickr-input > a { 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 112f94572d..764b73c593 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -672,7 +672,8 @@ } .imagecropper .umb-sortable-thumbnails li .crop-name, - .imagecropper .umb-sortable-thumbnails li .crop-size { + .imagecropper .umb-sortable-thumbnails li .crop-size, + .imagecropper .umb-sortable-thumbnails li .crop-annotation { display: block; text-align: left; font-size: 13px; @@ -684,17 +685,28 @@ margin: 10px 0 5px; } - .imagecropper .umb-sortable-thumbnails li .crop-size { + .imagecropper .umb-sortable-thumbnails li .crop-size, + .imagecropper .umb-sortable-thumbnails li .crop-annotation { font-size: 10px; font-style: italic; margin: 0 0 5px; } + .imagecropper .umb-sortable-thumbnails li .crop-annotation { + color: @gray-6; + } + .btn-crop-delete { display: block; text-align: left; } + .imagecropper .cropList-container { + h5 { + margin-left: 10px; + margin-top: 0; + } + } // // folder-browser // -------------------------------------------------- @@ -710,6 +722,10 @@ // // File upload // -------------------------------------------------- +.umb-fileupload { + display: flex; +} + .umb-fileupload .preview { border-radius: 5px; border: 1px solid @gray-6; diff --git a/src/Umbraco.Web.UI.Client/src/less/rte.less b/src/Umbraco.Web.UI.Client/src/less/rte.less index d6d38f540a..0e43f428ae 100644 --- a/src/Umbraco.Web.UI.Client/src/less/rte.less +++ b/src/Umbraco.Web.UI.Client/src/less/rte.less @@ -164,4 +164,13 @@ .mce-fullscreen { position: absolute; + + .mce-in { + position: fixed; + top: 35px !important; + } + + umb-editor__overlay, .umb-editor { + position: fixed; + } } diff --git a/src/Umbraco.Web.UI.Client/src/less/sections.less b/src/Umbraco.Web.UI.Client/src/less/sections.less index 40921c5b76..d4b5cfc998 100644 --- a/src/Umbraco.Web.UI.Client/src/less/sections.less +++ b/src/Umbraco.Web.UI.Client/src/less/sections.less @@ -31,7 +31,7 @@ ul.sections { height: 4px; bottom: 0; transform: translateY(4px); - background-color: @ui-active; + background-color: @pinkLight; position: absolute; border-radius: 3px 3px 0 0; opacity: 0; @@ -41,13 +41,13 @@ ul.sections { &:focus .section__name { .tabbing-active & { - border: 1px solid; - border-color: @gray-9; + border: 1px solid @gray-9; } } } .section__name { + border: 1px solid transparent; border-radius: 3px; margin-top: 1px; padding: 3px 10px 4px 10px; @@ -55,8 +55,8 @@ ul.sections { transition: opacity .1s linear, box-shadow .1s; } - &.current a { - color: @ui-active; + &.current > a { + color: @pinkLight; &::after { opacity: 1; @@ -75,9 +75,16 @@ ul.sections { opacity: 0.6; transition: opacity .1s linear; } - + + &.current { + i { + opacity: 1; + background: @pinkLight; + } + } + &:hover i { - opacity:1; + opacity: 1; } } @@ -104,7 +111,7 @@ ul.sections-tray { li { &.current a { - color: @ui-active; + color: @ui-active-border; opacity: 1; &::after { @@ -124,7 +131,7 @@ ul.sections-tray { content: ""; width: 4px; height: 100%; - background-color: @ui-active; + background-color: @ui-active-border; position: absolute; border-radius: 0 3px 3px 0; opacity: 0; diff --git a/src/Umbraco.Web.UI.Client/src/less/variables.less b/src/Umbraco.Web.UI.Client/src/less/variables.less index ad7c4ff95d..d94e4e1fb1 100644 --- a/src/Umbraco.Web.UI.Client/src/less/variables.less +++ b/src/Umbraco.Web.UI.Client/src/less/variables.less @@ -75,6 +75,8 @@ @gray-9: #E9E9EB; @gray-10: #F3F3F5; @gray-11: #F6F6F7; +@gray-12: #F9F9FA; +@gray-13: #FBFBFD; @sand-1: #DED4CF;// added 2019 @sand-2: #EBDED6;// added 2019 @@ -110,7 +112,7 @@ //@blueLight: #4f89de; @blue: #2E8AEA; @blueMid: #2152A3;// updated 2019 -@blueMidLight: rgb(99, 174, 236); +@blueMidLight: #6ab4f0; @blueDark: #3544b1;// updated 2019 @blueExtraDark: #1b264f;// added 2019 @blueLight: #ADD8E6; @@ -118,6 +120,7 @@ //@orange: #f79c37;// updated 2019 @pink: #D93F4C;// #C3325F;// update 2019 @pinkLight: #f5c1bc;// added 2019 +@pinkExtraLight: #fee4e1;// added 2020 @pinkRedLight: #ff8a89;// added 2019 @brown: #9d8057;// added 2019 @brownLight: #e4e0dd;// added 2019 @@ -136,17 +139,18 @@ @ui-option-type: @blueExtraDark; @ui-option-type-hover: @blueMid; @ui-option: @white; -@ui-option-hover: @sand-8; +@ui-option-hover: @gray-12; @ui-option-disabled-type: @gray-6; @ui-option-disabled-type-hover: @gray-5; -@ui-option-disabled-hover: @sand-8; +@ui-option-disabled-hover: @gray-12; @ui-disabled-type: @gray-6; @ui-disabled-border: @gray-6; //@ui-active: #346ab3; -@ui-active: @pinkLight; +@ui-active: @pinkExtraLight; +@ui-active-border: @pinkLight; @ui-active-blur: @brownLight; @ui-active-type: @blueExtraDark; @ui-active-type-hover: @blueMid; @@ -163,19 +167,19 @@ @ui-light-type-hover: @blueMid; @ui-light-active-border: @pinkLight; -@ui-light-active-type: @blueExtraDark; -@ui-light-active-type-hover: @blueMid; +@ui-light-active-type: @blueMid; +@ui-light-active-type-hover: @blueMidLight; @ui-action: @white; -@ui-action-hover: @sand-8; +@ui-action-hover: @gray-12; @ui-action-type: @blueExtraDark; @ui-action-type-hover: @blueMid; @ui-action-border: @blueExtraDark; @ui-action-border-hover: @blueMid; @ui-action-discreet: @white; -@ui-action-discreet-hover: @sand-8; +@ui-action-discreet-hover: @gray-12; @ui-action-discreet-type: @blueExtraDark; @ui-action-discreet-type-hover: @blueMid; @ui-action-discreet-border: @gray-7; @@ -246,6 +250,7 @@ @paddingSmall: 2px 10px; // 26px @paddingMini: 0 6px; // 22px +@propertyEditorLimitedWidth: 800px; // Disabled this to keep consistency throughout the backoffice UI. Untill a better solution is thought up, this will do. @baseBorderRadius: 3px; // 2px; diff --git a/src/Umbraco.Web.UI.Client/src/main.controller.js b/src/Umbraco.Web.UI.Client/src/main.controller.js index 93870f8a56..81eadf150f 100644 --- a/src/Umbraco.Web.UI.Client/src/main.controller.js +++ b/src/Umbraco.Web.UI.Client/src/main.controller.js @@ -1,19 +1,18 @@ - -/** +/** * @ngdoc controller - * @name Umbraco.MainController + * @name Umbraco.MainController * @function * - * @description + * @description * The main application controller * */ function MainController($scope, $location, appState, treeService, notificationsService, userService, historyService, updateChecker, navigationService, eventsService, tmhDynamicLocale, localStorageService, editorService, overlayService, assetsService, tinyMceAssets) { - + //the null is important because we do an explicit bool check on this in the view - $scope.authenticated = null; + $scope.authenticated = null; $scope.touchDevice = appState.getGlobalState("touchDevice"); $scope.infiniteMode = false; $scope.overlay = {}; @@ -67,13 +66,19 @@ function MainController($scope, $location, appState, treeService, notificationsS }; var evts = []; - + //when a user logs out or timesout evts.push(eventsService.on("app.notAuthenticated", function (evt, data) { $scope.authenticated = null; $scope.user = null; const isTimedOut = data && data.isTimedOut ? true : false; + $scope.showLoginScreen(isTimedOut); + + // Remove the localstorage items for tours shown + // Means that when next logged in they can be re-shown if not already dismissed etc + localStorageService.remove("emailMarketingTourShown"); + localStorageService.remove("introTourShown"); })); evts.push(eventsService.on("app.userRefresh", function(evt) { diff --git a/src/Umbraco.Web.UI.Client/src/navigation.controller.js b/src/Umbraco.Web.UI.Client/src/navigation.controller.js index 194c45afe6..ee8d13e320 100644 --- a/src/Umbraco.Web.UI.Client/src/navigation.controller.js +++ b/src/Umbraco.Web.UI.Client/src/navigation.controller.js @@ -68,7 +68,7 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar args.event.stopPropagation(); args.event.preventDefault(); - if (n.metaData && n.metaData["jsClickCallback"] && angular.isString(n.metaData["jsClickCallback"]) && n.metaData["jsClickCallback"] !== "") { + if (n.metaData && n.metaData["jsClickCallback"] && Utilities.isString(n.metaData["jsClickCallback"]) && n.metaData["jsClickCallback"] !== "") { //this is a legacy tree node! var jsPrefix = "javascript:"; var js; @@ -142,7 +142,7 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar var isInit = false; var evts = []; - + //Listen for global state changes evts.push(eventsService.on("appState.globalState.changed", function (e, args) { if (args.key === "showNavigation") { @@ -200,7 +200,7 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar $scope.treeApi.load({ section: $scope.currentSection, customTreeParams: $scope.customTreeParams, cacheKey: $scope.treeCacheKey }); }); } - + //show/hide search results if (args.key === "showSearchResults") { $scope.showSearchResults = args.value; @@ -222,7 +222,7 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar } else { $location.search("mculture", null); } - + var currentEditorState = editorState.getCurrent(); if (currentEditorState && currentEditorState.path) { $scope.treeApi.syncTree({ path: currentEditorState.path, activate: true }); @@ -233,13 +233,13 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar //Emitted when a language is created or an existing one saved/edited evts.push(eventsService.on("editors.languages.languageSaved", function (e, args) { - if(args.isNew){ + if (args.isNew) { //A new language has been created - reload languages for tree loadLanguages().then(function (languages) { $scope.languages = languages; }); } - else if(args.language.isDefault){ + else if (args.language.isDefault) { //A language was saved and was set to be the new default (refresh the tree, so its at the top) loadLanguages().then(function (languages) { $scope.languages = languages; @@ -257,6 +257,7 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar evts.push(eventsService.on("app.ready", function (evt, data) { $scope.authenticated = true; ensureInit(); + ensureMainCulture(); })); // event for infinite editors @@ -279,8 +280,22 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar } })); - - + /** + * For multi language sites, this ensures that mculture is set to either the last selected language or the default one + */ + function ensureMainCulture() { + if ($location.search().mculture) { + return; + } + var language = lastLanguageOrDefault(); + if (!language) { + return; + } + // trigger a language selection in the next digest cycle + $timeout(function () { + $scope.selectLanguage(language); + }); + } /** * Based on the current state of the application, this configures the scope variables that control the main tree and language drop down @@ -385,28 +400,19 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar if ($scope.languages.length > 1) { //if there's already one set, check if it exists - var currCulture = null; + var language = null; var mainCulture = $location.search().mculture; if (mainCulture) { - currCulture = _.find($scope.languages, function (l) { + language = _.find($scope.languages, function (l) { return l.culture.toLowerCase() === mainCulture.toLowerCase(); }); } - if (!currCulture) { - // no culture in the request, let's look for one in the cookie that's set when changing language - var defaultCulture = $cookies.get("UMB_MCULTURE"); - if (!defaultCulture || !_.find($scope.languages, function (l) { - return l.culture.toLowerCase() === defaultCulture.toLowerCase(); - })) { - // no luck either, look for the default language - var defaultLang = _.find($scope.languages, function (l) { - return l.isDefault; - }); - if (defaultLang) { - defaultCulture = defaultLang.culture; - } + if (!language) { + language = lastLanguageOrDefault(); + + if (language) { + $location.search("mculture", language.culture); } - $location.search("mculture", defaultCulture ? defaultCulture : null); } } @@ -426,11 +432,30 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar //the nav is ready, let the app know eventsService.emit("app.navigationReady", { treeApi: $scope.treeApi }); - + } }); }); } + + function lastLanguageOrDefault() { + if (!$scope.languages || $scope.languages.length <= 1) { + return null; + } + // see if we can find a culture in the cookie set when changing language + var lastCulture = $cookies.get("UMB_MCULTURE"); + var language = lastCulture ? _.find($scope.languages, function (l) { + return l.culture.toLowerCase() === lastCulture.toLowerCase(); + }) : null; + if (!language) { + // no luck, look for the default language + language = _.find($scope.languages, function (l) { + return l.isDefault; + }); + } + return language; + } + function nodeExpandedHandler(args) { //store the reference to the expanded node path if (args.node) { @@ -444,7 +469,7 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar // add the selected culture to a cookie so the user will log back into the same culture later on (cookie lifetime = one year) var expireDate = new Date(); expireDate.setDate(expireDate.getDate() + 365); - $cookies.put("UMB_MCULTURE", language.culture, {path: "/", expires: expireDate}); + $cookies.put("UMB_MCULTURE", language.culture, { path: "/", expires: expireDate }); // close the language selector $scope.page.languageSelectorIsOpen = false; @@ -470,9 +495,10 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar //execute them sequentially // set selected language to active - angular.forEach($scope.languages, function(language){ + $scope.languages.forEach(language => { language.active = false; }); + language.active = true; angularHelper.executeSequentialPromises(promises); @@ -513,7 +539,7 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar closeTree(); }; - $scope.onOutsideClick = function() { + $scope.onOutsideClick = function () { closeTree(); }; diff --git a/src/Umbraco.Web.UI.Client/src/preview/preview.controller.js b/src/Umbraco.Web.UI.Client/src/preview/preview.controller.js index dc40338d01..5ff8dd3633 100644 --- a/src/Umbraco.Web.UI.Client/src/preview/preview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/preview/preview.controller.js @@ -6,8 +6,8 @@ var app = angular.module("umbraco.preview", ['umbraco.resources', 'umbraco.services']) .controller("previewController", function ($scope, $window, $location) { - - $scope.currentCulture = {iso:'', title:'...', icon:'icon-loading'} + + $scope.currentCulture = { iso: '', title: '...', icon: 'icon-loading' } var cultures = []; $scope.tabbingActive = false; @@ -21,7 +21,7 @@ var app = angular.module("umbraco.preview", ['umbraco.resources', 'umbraco.servi window.addEventListener('mousedown', disableTabbingActive); } } - + function disableTabbingActive(evt) { $scope.tabbingActive = false; $scope.$digest(); @@ -113,10 +113,10 @@ var app = angular.module("umbraco.preview", ['umbraco.resources', 'umbraco.servi $scope.sizeOpen = false; $scope.cultureOpen = false; - $scope.toggleSizeOpen = function() { + $scope.toggleSizeOpen = function () { $scope.sizeOpen = toggleMenu($scope.sizeOpen); } - $scope.toggleCultureOpen = function() { + $scope.toggleCultureOpen = function () { $scope.cultureOpen = toggleMenu($scope.cultureOpen); } @@ -132,8 +132,8 @@ var app = angular.module("umbraco.preview", ['umbraco.resources', 'umbraco.servi $scope.sizeOpen = false; $scope.cultureOpen = false; } - - $scope.windowClickHandler = function() { + + $scope.windowClickHandler = function () { closeOthers(); } function windowBlurHandler() { @@ -141,16 +141,16 @@ var app = angular.module("umbraco.preview", ['umbraco.resources', 'umbraco.servi $scope.$digest(); } - var win = angular.element($window); + var win = $($window); win.on("blur", windowBlurHandler); - + $scope.$on("$destroy", function () { - win.off("blur", handleBlwindowBlurHandlerur ); + win.off("blur", handleBlwindowBlurHandlerur); }); - - function setPageUrl(){ + + function setPageUrl() { $scope.pageId = $location.search().id || getParameterByName("id"); var culture = $location.search().culture || getParameterByName("culture"); @@ -204,27 +204,27 @@ var app = angular.module("umbraco.preview", ['umbraco.resources', 'umbraco.servi /* Change culture */ /*****************************************************************************/ $scope.changeCulture = function (iso) { - if($location.search().culture !== iso) { + if ($location.search().culture !== iso) { $scope.frameLoaded = false; $scope.currentCultureIso = iso; $location.search("culture", iso); setPageUrl(); } }; - $scope.registerCulture = function(iso, title, isDefault) { - var cultureObject = {iso: iso, title: title, isDefault: isDefault}; + $scope.registerCulture = function (iso, title, isDefault) { + var cultureObject = { iso: iso, title: title, isDefault: isDefault }; cultures.push(cultureObject); } - $scope.$watch("currentCultureIso", function(oldIso, newIso) { + $scope.$watch("currentCultureIso", function (oldIso, newIso) { // if no culture is selected, we will pick the default one: if ($scope.currentCultureIso === null) { - $scope.currentCulture = cultures.find(function(culture) { + $scope.currentCulture = cultures.find(function (culture) { return culture.isDefault === true; }) return; } - $scope.currentCulture = cultures.find(function(culture) { + $scope.currentCulture = cultures.find(function (culture) { return culture.iso === $scope.currentCultureIso; }) }); @@ -252,7 +252,7 @@ var app = angular.module("umbraco.preview", ['umbraco.resources', 'umbraco.servi }); } - function hideUmbracoPreviewBadge (iframe) { + function hideUmbracoPreviewBadge(iframe) { if (iframe && iframe.contentDocument && iframe.contentDocument.getElementById("umbracoPreviewBadge")) { iframe.contentDocument.getElementById("umbracoPreviewBadge").style.display = "none"; } diff --git a/src/Umbraco.Web.UI.Client/src/routes.js b/src/Umbraco.Web.UI.Client/src/routes.js index 556a4d6aef..93122792d3 100644 --- a/src/Umbraco.Web.UI.Client/src/routes.js +++ b/src/Umbraco.Web.UI.Client/src/routes.js @@ -154,7 +154,7 @@ app.config(function ($routeProvider) { //This allows us to dynamically change the template for this route since you cannot inject services into the templateUrl method. template: "
", //This controller will execute for this route, then we replace the template dynamically based on the current tree. - controller: function ($scope, $routeParams, treeService) { + controller: function ($scope, $routeParams, navigationService) { if (!$routeParams.method) { $scope.templateUrl = "views/common/dashboard.html"; @@ -176,24 +176,7 @@ app.config(function ($routeProvider) { $scope.templateUrl = "views/users/overview.html"; return; } - - // Here we need to figure out if this route is for a user's 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 + - "/" + packageTreeFolder + - "/backoffice/" + $routeParams.tree + "/" + $routeParams.method + ".html"); - } - else { - $scope.templateUrl = ('views/' + $routeParams.tree + '/' + $routeParams.method + '.html'); - } + $scope.templateUrl = navigationService.getTreeTemplateUrl($routeParams.tree, $routeParams.method); }, reloadOnSearch: false, resolve: canRoute(true) @@ -202,30 +185,13 @@ app.config(function ($routeProvider) { //This allows us to dynamically change the template for this route since you cannot inject services into the templateUrl method. template: "
", //This controller will execute for this route, then we replace the template dynamically based on the current tree. - controller: function ($scope, $route, $routeParams, treeService) { + controller: function ($scope, $routeParams, navigationService) { if (!$routeParams.tree || !$routeParams.method) { $scope.templateUrl = "views/common/dashboard.html"; + return; } - - // 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 + - "/" + packageTreeFolder + - "/backoffice/" + $routeParams.tree + "/" + $routeParams.method + ".html"); - } - else { - $scope.templateUrl = ('views/' + $routeParams.tree + '/' + $routeParams.method + '.html'); - } - + $scope.templateUrl = navigationService.getTreeTemplateUrl($routeParams.tree, $routeParams.method); }, reloadOnSearch: false, reloadOnUrl: false, diff --git a/src/Umbraco.Web.UI.Client/src/utilities.js b/src/Umbraco.Web.UI.Client/src/utilities.js new file mode 100644 index 0000000000..71d904427b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/utilities.js @@ -0,0 +1,115 @@ +/** + * A friendly utility collection to replace AngularJs' ng-functions + * If it doesn't exist here, it's probably available as vanilla JS + * + * Still carries a dependency on underscore, but if usages of underscore from + * elsewhere in the codebase can instead use these methods, the underscore + * dependency will be nicely abstracted and can be removed/swapped later + * + * This collection is open to extension... + */ +(function (window) { + + /** + * Equivalent to angular.noop + */ + const noop = () => { }; + + /** + * Facade to angular.copy + */ + const copy = val => angular.copy(val); + + /** + * Equivalent to angular.isArray + */ + const isArray = val => Array.isArray(val) || val instanceof Array; + + /** + * Facade to angular.equals + */ + const equals = (a, b) => angular.equals(a, b); + + /** + * Facade to angular.extend + * Use this with Angular objects, for vanilla JS objects, use Object.assign() + */ + const extend = (dst, src) => angular.extend(dst, src); + + /** + * Equivalent to angular.isFunction + */ + const isFunction = val => typeof val === 'function'; + + /** + * Equivalent to angular.isUndefined + */ + const isUndefined = val => typeof val === 'undefined'; + + /** + * Equivalent to angular.isDefined. Inverts result of const isUndefined + */ + const isDefined = val => !isUndefined(val); + + /** + * Equivalent to angular.isString + */ + const isString = val => typeof val === 'string'; + + /** + * Equivalent to angular.isNumber + */ + const isNumber = val => typeof val === 'number'; + + /** + * Equivalent to angular.isObject + */ + const isObject = val => val !== null && typeof val === 'object'; + + const isWindow = obj => obj && obj.window === obj; + + const isScope = obj => obj && obj.$evalAsync && obj.$watch; + + const toJsonReplacer = (key, value) => { + var val = value; + if (typeof key === 'string' && key.charAt(0) === '$' && key.charAt(1) === '$') { + val = undefined; + } else if (isWindow(value)) { + val = '$WINDOW'; + } else if (value && window.document === value) { + val = '$DOCUMENT'; + } else if (isScope(value)) { + val = '$SCOPE'; + } + return val; + } + /** + * Equivalent to angular.toJson + */ + const toJson = (obj, pretty) => { + if (isUndefined(obj)) return undefined; + if (!isNumber(pretty)) { + pretty = pretty ? 2 : null; + } + return JSON.stringify(obj, toJsonReplacer, pretty); + } + + let _utilities = { + noop: noop, + copy: copy, + isArray: isArray, + equals: equals, + extend: extend, + isFunction: isFunction, + isUndefined: isUndefined, + isDefined: isDefined, + isString: isString, + isNumber: isNumber, + isObject: isObject, + toJson: toJson + }; + + if (typeof (window.Utilities) === 'undefined') { + window.Utilities = _utilities; + } +})(window); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.controller.js index 3323f2bfb3..268bfb3a8c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.controller.js @@ -1,7 +1,7 @@ (function () { "use strict"; - function HelpDrawerController($scope, $routeParams, $timeout, dashboardResource, localizationService, userService, eventsService, helpService, appState, tourService, $filter) { + function HelpDrawerController($scope, $routeParams, $timeout, dashboardResource, localizationService, userService, eventsService, helpService, appState, tourService, $filter, editorState) { var vm = this; var evts = []; @@ -18,6 +18,10 @@ vm.startTour = startTour; vm.getTourGroupCompletedPercentage = getTourGroupCompletedPercentage; vm.showTourButton = showTourButton; + + vm.showDocTypeTour = false; + vm.docTypeTours = []; + vm.nodeName = ''; function startTour(tour) { tourService.startTour(tour); @@ -58,9 +62,16 @@ handleSectionChange(); })); + evts.push(eventsService.on("editorState.changed", + function (e, args) { + setDocTypeTour(args.entity); + })); + findHelp(vm.section, vm.tree, vm.userType, vm.userLang); }); + + setDocTypeTour(editorState.getCurrent()); // check if a tour is running - if it is open the matching group var currentTour = tourService.getCurrentTour(); @@ -84,7 +95,7 @@ setSectionName(); findHelp(vm.section, vm.tree, vm.userType, vm.userLang); - + setDocTypeTour(); } }); } @@ -168,6 +179,26 @@ }); } + function setDocTypeTour(node) { + vm.showDocTypeTour = false; + vm.docTypeTours = []; + vm.nodeName = ''; + + if (vm.section === 'content' && vm.tree === 'content') { + + if (node) { + tourService.getToursForDoctype(node.contentTypeAlias).then(function (data) { + if (data && data.length > 0) { + vm.docTypeTours = data; + var currentVariant = _.find(node.variants, (x) => x.active); + vm.nodeName = currentVariant.name; + vm.showDocTypeTour = true; + } + }); + } + } + } + evts.push(eventsService.on("appState.tour.complete", function (event, tour) { tourService.getGroupedTours().then(function(groupedTours) { vm.tours = groupedTours; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.html b/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.html index 96f4a404bc..aa6126e73e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.html @@ -8,12 +8,36 @@ - -
+ +
+
Need help editing current item '{{vm.nodeName}}' ?
-
Tours
+
-
+ +
+
+
+
+ {{ tour.name }} +
+
+ +
+
+
+
+
+
+ + +
+ +
+ Tours +
+ +
@@ -25,7 +49,9 @@ {{tourGroup.group}} - Other + + Other +
-
+
- -
-
-
{{dashboard.label}}
-
-
-
+ +
+
+
{{dashboard.label}}
+
+
+
+
-
- -
-
Articles
-
    -
  • - - - - {{topic.name}} - - - {{topic.description}} - + +
    +
    + Articles +
    +
    @@ -85,7 +113,9 @@
    -
    Videos
    +
    + Videos +
    • @@ -101,7 +131,9 @@
      -
      Visit umbraco.tv
      +
      + Visit umbraco.tv +
      The best Umbraco video tutorials @@ -110,7 +142,9 @@
      -
      Visit our.umbraco.com
      +
      + Visit our.umbraco.com +
      The friendliest community diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/compositions/compositions.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/compositions/compositions.controller.js index 7cfa02f95a..aa0cd54dff 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/compositions/compositions.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/compositions/compositions.controller.js @@ -17,7 +17,7 @@ /* make a copy of the init model so it is possible to roll back the changes on cancel */ - oldModel = angular.copy($scope.model); + oldModel = Utilities.copy($scope.model); if (!$scope.model.title) { $scope.model.title = "Compositions"; @@ -39,7 +39,7 @@ }); } - + function isSelected(alias) { if ($scope.model.contentType.compositeContentTypes.indexOf(alias) !== -1) { @@ -68,7 +68,7 @@ or the confirm checkbox has been checked */ if (compositionRemoved) { vm.allowSubmit = false; - localizationService.localize("general_remove").then(function(value) { + localizationService.localize("general_remove").then(function (value) { const dialog = { view: "views/common/infiniteeditors/compositions/overlays/confirmremove.html", title: value, diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/copy/copy.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/copy/copy.controller.js index 3b405333bf..8d9acd4230 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/copy/copy.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/copy/copy.controller.js @@ -16,8 +16,9 @@ var dialogOptions = $scope.model; var node = dialogOptions.currentNode; - $scope.model.relateToOriginal = true; - $scope.dialogTreeApi = {}; + $scope.model.relateToOriginal = true; + $scope.model.includeDescendants = true; + $scope.dialogTreeApi = {}; vm.searchInfo = { searchFromId: null, @@ -33,14 +34,12 @@ function onInit() { var labelKeys = [ - "general_copy", - "defaultdialogs_relateToOriginalLabel" + "general_copy" ]; localizationService.localizeMany(labelKeys).then(function (data) { vm.labels.title = data[0]; - vm.labels.relateToOriginal = data[1]; setTitle(vm.labels.title); }); @@ -132,6 +131,10 @@ if (type === "relate") { $scope.model.relateToOriginal = !$scope.model.relateToOriginal; } + // If the includeDescendants toggle is clicked + if (type === "descendants") { + $scope.model.includeDescendants = !$scope.model.includeDescendants; + } } onInit(); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/copy/copy.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/copy/copy.html index 86c0186374..c06c292a06 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/copy/copy.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/copy/copy.html @@ -15,6 +15,10 @@ +

      + Choose where to copy the selected item(s) +

      +
      + + + + + @@ -82,7 +91,8 @@ button-style="success" label-key="general_submit" state="vm.saveButtonState" - action="vm.submit(model)"> + action="vm.submit(model)" + disabled="!model.target"> diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.controller.js index 471d23ae84..5bfee22c4e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.controller.js @@ -16,7 +16,7 @@ function IconPickerController($scope, iconHelper, localizationService) { vm.close = close; vm.colors = [ - { name: "Black", value: "color-black" }, + { name: "Black", value: "color-black", default: true }, { name: "Blue Grey", value: "color-blue-grey" }, { name: "Grey", value: "color-grey" }, { name: "Brown", value: "color-brown" }, @@ -49,7 +49,7 @@ function IconPickerController($scope, iconHelper, localizationService) { }); // set a default color if nothing is passed in - vm.color = $scope.model.color ? findColor($scope.model.color) : vm.colors[0]; + vm.color = $scope.model.color ? findColor($scope.model.color) : vm.colors.find(x => x.default); // if an icon is passed in - preselect it vm.icon = $scope.model.icon ? $scope.model.icon : undefined; @@ -71,12 +71,13 @@ function IconPickerController($scope, iconHelper, localizationService) { } function findColor(value) { - return _.findWhere(vm.colors, {value: value}); + return vm.colors.find(x => x.value === value); } - function selectColor(color, $index, $event) { - $scope.model.color = color.value; - vm.color = color; + function selectColor(color) { + let newColor = (color || vm.colors.find(x => x.default)); + $scope.model.color = newColor.value; + vm.color = newColor; } function close() { diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.html index 3caa6ae03d..c2b0d0e458 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.html @@ -46,9 +46,10 @@
      diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js index b5043293e5..a9803b70f9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js @@ -1,7 +1,7 @@ //used for the media picker dialog angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController", function ($scope, eventsService, entityResource, mediaResource, mediaHelper, udiParser, userService, localizationService, editorService) { - + var vm = this; var dialogOptions = $scope.model; @@ -16,7 +16,7 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController", if (!$scope.model.title) { localizationService.localize("defaultdialogs_selectLink") - .then(function(value) { + .then(function (value) { $scope.model.title = value; }); } @@ -33,6 +33,7 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController", }; $scope.showTarget = $scope.model.hideTarget !== true; + $scope.showAnchor = $scope.model.hideAnchor !== true; // this ensures that we only sync the tree once and only when it's ready var oneTimeTreeSync = { @@ -58,7 +59,7 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController", if (dialogOptions.currentTarget) { // clone the current target so we don't accidentally update the caller's model while manipulating $scope.model.target - $scope.model.target = angular.copy(dialogOptions.currentTarget); + $scope.model.target = Utilities.copy(dialogOptions.currentTarget); // if we have a node ID, we fetch the current node to build the form data if ($scope.model.target.id || $scope.model.target.udi) { @@ -193,7 +194,7 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController", tree: "content" }); }, - close: function() { + close: function () { editorService.close(); } }; @@ -250,13 +251,13 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController", } function close() { - if($scope.model && $scope.model.close) { + if ($scope.model && $scope.model.close) { $scope.model.close(); } } function submit() { - if($scope.model && $scope.model.submit) { + if ($scope.model && $scope.model.submit) { $scope.model.submit($scope.model); } } diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.html index a7d2dbbee2..ad0aaab57c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.html @@ -14,7 +14,7 @@ -
      +
      - + 0) { - $scope.macros = _.filter(data, function(d) { + if (Utilities.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); }); } diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js index 01f61a16ae..7a4db7cbc5 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js @@ -1,13 +1,13 @@ //used for the media picker dialog angular.module("umbraco") .controller("Umbraco.Editors.MediaPickerController", - function ($scope, $timeout, mediaResource, entityResource, userService, mediaHelper, mediaTypeHelper, eventsService, treeService, localStorageService, localizationService) { + function ($scope, $timeout, mediaResource, entityResource, userService, mediaHelper, mediaTypeHelper, eventsService, treeService, localStorageService, localizationService, editorService, umbSessionStorage) { var vm = this; - + vm.submit = submit; vm.close = close; - + vm.toggle = toggle; vm.upload = upload; vm.dragLeave = dragLeave; @@ -23,9 +23,10 @@ angular.module("umbraco") vm.clickHandler = clickHandler; vm.clickItemName = clickItemName; vm.gotoFolder = gotoFolder; + vm.shouldShowUrl = shouldShowUrl; var dialogOptions = $scope.model; - + $scope.disableFolderSelect = (dialogOptions.disableFolderSelect && dialogOptions.disableFolderSelect !== "0") ? true : false; $scope.disableFocalPoint = (dialogOptions.disableFocalPoint && dialogOptions.disableFocalPoint !== "0") ? true : false; $scope.onlyImages = (dialogOptions.onlyImages && dialogOptions.onlyImages !== "0") ? true : false; @@ -36,6 +37,11 @@ angular.module("umbraco") $scope.cropSize = dialogOptions.cropSize; $scope.lastOpenedNode = localStorageService.get("umbLastOpenedMediaNodeId"); $scope.lockedFolder = true; + $scope.allowMediaEdit = dialogOptions.allowMediaEdit ? dialogOptions.allowMediaEdit : false; + + $scope.filterOptions = { + excludeSubFolders: umbSessionStorage.get("mediaPickerExcludeSubFolders") || false + }; var userStartNodes = []; @@ -127,21 +133,21 @@ angular.module("umbraco") // media object so we need to look it up var id = $scope.target.udi ? $scope.target.udi : $scope.target.id; var altText = $scope.target.altText; - + // ID of a UDI or legacy int ID still could be null/undefinied here // As user may dragged in an image that has not been saved to media section yet if (id) { entityResource.getById(id, "Media") - .then(function (node) { - $scope.target = node; - if (ensureWithinStartNode(node)) { - selectMedia(node); - $scope.target.url = mediaHelper.resolveFileFromEntity(node); - $scope.target.thumbnail = mediaHelper.resolveFileFromEntity(node, true); - $scope.target.altText = altText; - openDetailsDialog(); - } - }, gotoStartNode); + .then(function (node) { + $scope.target = node; + if (ensureWithinStartNode(node)) { + selectMedia(node); + $scope.target.url = mediaHelper.resolveFileFromEntity(node); + $scope.target.thumbnail = mediaHelper.resolveFileFromEntity(node, true); + $scope.target.altText = altText; + openDetailsDialog(); + } + }, gotoStartNode); } else { // No ID set - then this is going to be a tmpimg that has not been uploaded // User editing this will want to be changing the ALT text @@ -150,15 +156,15 @@ angular.module("umbraco") } } - function upload(v) { - angular.element(".umb-file-dropzone .file-select").trigger("click"); + function upload() { + $(".umb-file-dropzone .file-select").trigger("click"); } - function dragLeave(el, event) { + function dragLeave() { $scope.activeDrag = false; } - function dragEnter(el, event) { + function dragEnter() { $scope.activeDrag = true; } @@ -234,16 +240,16 @@ angular.module("umbraco") } } else { if ($scope.showDetails) { - + $scope.target = media; - + // handle both entity and full media object if (media.image) { $scope.target.url = media.image; } else { $scope.target.url = mediaHelper.resolveFile(media); } - + openDetailsDialog(); } else { selectMedia(media); @@ -295,14 +301,12 @@ angular.module("umbraco") $timeout(function () { if ($scope.multiPicker) { var images = _.rest($scope.images, $scope.images.length - files.length); - _.each(images, function(image) { + _.each(images, function (image) { selectMedia(image); }); } else { var image = $scope.images[$scope.images.length - 1]; - $scope.target = image; - $scope.target.url = mediaHelper.resolveFile(image); - selectMedia(image); + clickHandler(image); } }); }); @@ -318,7 +322,7 @@ angular.module("umbraco") // also make sure the node is not trashed if (nodePath.indexOf($scope.startNodeId.toString()) !== -1 && node.trashed === false) { - gotoFolder({ id: $scope.lastOpenedNode, name: "Media", icon: "icon-folder", path: node.path }); + gotoFolder({ id: $scope.lastOpenedNode || $scope.startNodeId, name: "Media", icon: "icon-folder", path: node.path }); return true; } else { gotoFolder({ id: $scope.startNodeId, name: "Media", icon: "icon-folder" }); @@ -337,7 +341,7 @@ angular.module("umbraco") return false; } - function gotoStartNode(err) { + function gotoStartNode() { gotoFolder({ id: $scope.startNodeId, name: "Media", icon: "icon-folder" }); } @@ -369,7 +373,7 @@ angular.module("umbraco") if (vm.searchOptions.filter) { searchMedia(); } else { - + // reset pagination vm.searchOptions = { pageNumber: 1, @@ -379,7 +383,7 @@ angular.module("umbraco") filter: '', dataTypeKey: dataTypeKey }; - + getChildren($scope.currentFolder.id); } }); @@ -391,6 +395,7 @@ angular.module("umbraco") } function toggle() { + umbSessionStorage.set("mediaPickerExcludeSubFolders", $scope.filterOptions.excludeSubFolders); // Make sure to activate the changeSearch function everytime the toggle is clicked changeSearch(); } @@ -403,12 +408,12 @@ angular.module("umbraco") function searchMedia() { vm.loading = true; - entityResource.getPagedDescendants($scope.currentFolder.id, "Media", vm.searchOptions) + entityResource.getPagedDescendants($scope.filterOptions.excludeSubFolders ? $scope.currentFolder.id : $scope.startNodeId, "Media", vm.searchOptions) .then(function (data) { // update image data to work with image grid - angular.forEach(data.items, function (mediaItem) { - setMediaMetaData(mediaItem); - }); + if (data.items) { + data.items.forEach(mediaItem => setMediaMetaData(mediaItem)); + } // update images $scope.images = data.items ? data.items : []; @@ -492,7 +497,7 @@ angular.module("umbraco") var folderImage = $scope.images[folderIndex]; var imageIsSelected = false; - if ($scope.model && angular.isArray($scope.model.selection)) { + if ($scope.model && Utilities.isArray($scope.model.selection)) { for (var selectedIndex = 0; selectedIndex < $scope.model.selection.length; selectedIndex++) { @@ -531,6 +536,19 @@ angular.module("umbraco") } } + function shouldShowUrl() { + if (!$scope.target) { + return false; + } + if ($scope.target.id) { + return false; + } + if ($scope.target.url && $scope.target.url.toLower().indexOf("blob:") === 0) { + return false; + } + return true; + } + function submit() { if ($scope.model && $scope.model.submit) { $scope.model.submit($scope.model); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.html index 917010dbb6..e1a89061b6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.html @@ -32,10 +32,10 @@
      + label-key="general_excludeFromSubFolders">
      @@ -112,9 +112,8 @@ disable-folder-select={{disableFolderSelect}} only-images={{onlyImages}} only-folders={{onlyFolders}} - include-sub-folders={{showChilds}} - current-folder-id="{{currentFolder.id}}" - allow-open-folder="!disableFolderSelect"> + include-sub-folders={{!filterOptions.excludeSubFolders}} + current-folder-id="{{currentFolder.id}}"> @@ -138,8 +137,8 @@
      - -
      + +
      diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/move/move.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/move/move.html index 8424bcefbe..1c9fd5b822 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/move/move.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/move/move.html @@ -13,6 +13,9 @@ +

      + Choose where to move the selected item(s) +

      @@ -75,7 +78,8 @@ button-style="success" label-key="general_submit" state="vm.saveButtonState" - action="vm.submit(model)"> + action="vm.submit(model)" + disabled="!model.target"> diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.controller.js index eb279bddef..a6d1383640 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.controller.js @@ -30,6 +30,7 @@ vm.close = close; vm.toggleAllowCultureVariants = toggleAllowCultureVariants; + vm.toggleAllowSegmentVariants = toggleAllowSegmentVariants; vm.toggleValidation = toggleValidation; vm.toggleShowOnMemberProfile = toggleShowOnMemberProfile; vm.toggleMemberCanEdit = toggleMemberCanEdit; @@ -56,7 +57,8 @@ "validation_validateAsEmail", "validation_validateAsNumber", "validation_validateAsUrl", - "validation_enterCustomValidation" + "validation_enterCustomValidation", + "validation_fieldIsMandatory" ]; localizationService.localizeMany(labels) @@ -66,6 +68,7 @@ vm.labels.validateAsNumber = data[1]; vm.labels.validateAsUrl = data[2]; vm.labels.customValidation = data[3]; + vm.labels.fieldIsMandatory = data[4]; vm.validationTypes = [ { @@ -246,6 +249,10 @@ $scope.model.property.allowCultureVariant = toggleValue($scope.model.property.allowCultureVariant); } + function toggleAllowSegmentVariants() { + $scope.model.property.allowSegmentVariant = toggleValue($scope.model.property.allowSegmentVariant); + } + function toggleValidation() { $scope.model.property.validation.mandatory = toggleValue($scope.model.property.validation.mandatory); } diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html index 3353be6f06..1671925a91 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html @@ -83,7 +83,6 @@ > -
      @@ -92,14 +91,15 @@
      - - + on-click="vm.toggleValidation()" + label-on="{{vm.labels.fieldIsMandatory}}" + label-off="{{vm.labels.fieldIsMandatory}}" + show-labels="true" + label-position="right" focus-when="{{vm.focusOnMandatoryField}}" + class="mb1">
      -
      +
      + + - - +
      +
      + +
      + + +
      diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js index f71eb2c51e..24ed971de0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js @@ -130,8 +130,8 @@ } // diff requires a string - property.value = property.value ? property.value : ""; - oldProperty.value = oldProperty.value ? oldProperty.value : ""; + property.value = property.value ? property.value + "" : ""; + oldProperty.value = oldProperty.value ? oldProperty.value + "" : ""; var diffProperty = { "alias": property.alias, diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html index 9b230410b0..e292a94606 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html @@ -9,9 +9,9 @@ hide-icon="true" hide-description="true"> - + - + @@ -21,21 +21,21 @@
      -
      - +
      - +

      {{vm.currentVersion.name}} (Created: {{vm.currentVersion.createDate}})

      - +
      - - - {{vm.currentVariant.language.name}} + + - - - -
      Open in split view
      + +
      Open in split view
      +
      + + +
      Open in split view
      +
      +
      @@ -70,10 +96,10 @@
      -
      +
      diff --git a/src/Umbraco.Web.UI.Client/src/views/components/forms/umb-checkbox.html b/src/Umbraco.Web.UI.Client/src/views/components/forms/umb-checkbox.html index 82b21e4c3b..ba5adf199a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/forms/umb-checkbox.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/forms/umb-checkbox.html @@ -1,22 +1,28 @@ diff --git a/src/Umbraco.Web.UI.Client/src/views/components/forms/umb-radiobutton.html b/src/Umbraco.Web.UI.Client/src/views/components/forms/umb-radiobutton.html index 9ee50bcae1..a34d548ea6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/forms/umb-radiobutton.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/forms/umb-radiobutton.html @@ -1,16 +1,20 @@ diff --git a/src/Umbraco.Web.UI.Client/src/views/components/grid/grid-rte.html b/src/Umbraco.Web.UI.Client/src/views/components/grid/grid-rte.html index b5531f477a..cbd1ac3f30 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/grid/grid-rte.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/grid/grid-rte.html @@ -1,4 +1,4 @@ 
      -
      +
      diff --git a/src/Umbraco.Web.UI.Client/src/views/components/media/umb-media-node-info.html b/src/Umbraco.Web.UI.Client/src/views/components/media/umb-media-node-info.html index 4f7141559c..a606aa5588 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/media/umb-media-node-info.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/media/umb-media-node-info.html @@ -1,20 +1,25 @@
      -
      - + + + + +
      + + + - +
      -
      + +
      + @@ -39,12 +167,11 @@ - + 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 index bc0f2cb001..36a9271818 100644 --- 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 @@ -1,13 +1,16 @@
        -
      • +
      • -
      • - +
      • + - - {{ tab.label }} + +
      • diff --git a/src/Umbraco.Web.UI.Client/src/views/components/tree/umb-tree-search-box.html b/src/Umbraco.Web.UI.Client/src/views/components/tree/umb-tree-search-box.html index 6bb2d120c8..217489f14c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/tree/umb-tree-search-box.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/tree/umb-tree-search-box.html @@ -5,9 +5,9 @@ ng-model="term" class="umb-search-field search-query -full-width-input" placeholder="{{searchPlaceholderText}}" - focus-when="{{showSearch}}"> + umb-auto-focus="{{autoFocus ? 'true' : 'false'}}">

        Search {{searchFromName}}

        -
      \ No newline at end of file +
      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 index 686937e8c9..1d88c0eb96 100644 --- 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 @@ -14,23 +14,25 @@
      -
      +
      - +
      - {{ selectedChild.name }} + {{selectedChild.name}}
      - +
      - +
      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 index f4e4dff3af..b45a3bb843 100644 --- 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 @@ -6,11 +6,11 @@ '-left': direction === 'left'}" on-outside-click="clickCancel()"> - + - +
      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 index 836e8fc3f1..820963b11b 100644 --- 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 @@ -1,5 +1,5 @@
      - {{ alias }} + {{ alias }}
      - + +
      + +
      + +
      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 index 1fa917a07f..c9bf3b3083 100644 --- 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 @@ -21,7 +21,7 @@ {{layout.name}} -
      +
      diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-list-view.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-list-view.html index da1e5c3aa7..d66e7f7f90 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-list-view.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-list-view.html @@ -43,6 +43,7 @@ ng-model="search" ng-change="searchMiniListView(search, miniListView)" prevent-enter-submit + umb-auto-focus no-dirty-check>
      diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-search/umb-mini-search.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-search/umb-mini-search.html index 20ce87f0eb..d4e75908bd 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-search/umb-mini-search.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-search/umb-mini-search.html @@ -1,5 +1,5 @@ - + diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-search/umbminisearch.component.js b/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-search/umbminisearch.component.js index 994129708f..d7aee744e4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-search/umbminisearch.component.js +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-search/umbminisearch.component.js @@ -10,7 +10,8 @@ bindings: { model: "=", onStartTyping: "&?", - onSearch: "&?" + onSearch: "&?", + onBlur: "&?" } }); diff --git a/src/Umbraco.Web.UI.Client/src/views/components/users/umb-user-preview.html b/src/Umbraco.Web.UI.Client/src/views/components/users/umb-user-preview.html index 32aa5ed1a2..014297ae51 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/users/umb-user-preview.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/users/umb-user-preview.html @@ -1,12 +1,11 @@
      - +
      @@ -15,7 +14,6 @@
      - Remove -
      - -
      \ No newline at end of file + +
      +
      diff --git a/src/Umbraco.Web.UI.Client/src/views/content/apps/content/content.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/apps/content/content.controller.js index 2a3f67a7e3..66747a8697 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/apps/content/content.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/apps/content/content.controller.js @@ -7,35 +7,62 @@ //if we make the viewModel the variant itself, we end up with a circular reference in the models which isn't ideal // (i.e. variant.apps[contentApp].viewModel = variant) //so instead since we already have access to the content, we can just get the variant directly by the index. + + var unbindLanguageWatcher = function() {}; + var unbindSegmentWatcher = function() {}; + var timeout = null; var vm = this; vm.loading = true; function onInit() { - //get the variant by index (see notes above) - vm.content = $scope.content.variants[$scope.model.viewModel]; + serverValidationManager.notify(); vm.loading = false; + timeout = null;// ensure timeout is set to null, so we know that its not running anymore. //if this variant has a culture/language assigned, then we need to watch it since it will change //if the language drop down changes and we need to re-init - if (vm.content.language) { - $scope.$watch(function () { - return vm.content.language.culture; + if ($scope.variantContent) { + if ($scope.variantContent.language) { + unbindLanguageWatcher = $scope.$watch(function () { + return $scope.variantContent.language.culture; + }, function (newVal, oldVal) { + if (newVal !== oldVal) { + requestUpdate(); + } + }); + } + + unbindSegmentWatcher = $scope.$watch(function () { + return $scope.variantContent.segment; }, function (newVal, oldVal) { if (newVal !== oldVal) { - vm.loading = true; - - // TODO: Can we minimize the flicker? - $timeout(function () { - onInit(); - }, 100); + requestUpdate(); } }); } + + } + + function requestUpdate() { + if (timeout === null) { + vm.loading = true; + + // TODO: Can we minimize the flicker? + timeout = $timeout(function () { + onInit(); + }, 100); + } } onInit(); + + $scope.$on("$destroy", function() { + unbindLanguageWatcher(); + unbindSegmentWatcher(); + $timeout.cancel(timeout); + }); } angular.module("umbraco").controller("Umbraco.Editors.Content.Apps.ContentController", ContentAppContentController); diff --git a/src/Umbraco.Web.UI.Client/src/views/content/apps/content/content.html b/src/Umbraco.Web.UI.Client/src/views/content/apps/content/content.html index d391f2cd95..7023a4187a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/apps/content/content.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/apps/content/content.html @@ -2,7 +2,8 @@ + content-node-model="content" + content="variantContent"> - +
      diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.create.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.create.controller.js index 64f601f8b7..fc7d38fd37 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.create.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.create.controller.js @@ -22,10 +22,14 @@ function contentCreateController($scope, function initialize() { $scope.loading = true; $scope.allowedTypes = null; - $scope.countTypes = contentTypeResource.getCount; var getAllowedTypes = contentTypeResource.getAllowedTypes($scope.currentNode.id).then(function (data) { $scope.allowedTypes = iconHelper.formatContentTypeIcons(data); + if ($scope.allowedTypes.length === 0) { + contentTypeResource.getCount().then(function(count) { + $scope.countTypes = count; + }); + } }); var getCurrentUser = authResource.getCurrentUser().then(function (currentUser) { if (currentUser.allowedSections.indexOf("settings") > -1) { @@ -60,6 +64,9 @@ function contentCreateController($scope, language as what is selected in the tree */ .search("cculture", mainCulture) /* when we create a new node we must make sure that any previously + opened segments is reset */ + .search("csegment", null) + /* when we create a new node we must make sure that any previously used blueprint is reset */ .search("blueprintId", null); close(); 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 ad79bf01d1..2c7c53e31f 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 @@ -28,6 +28,7 @@ function ContentEditController($scope, $routeParams, contentResource) { $scope.isNew = infiniteMode ? $scope.model.create : $routeParams.create; //load the default culture selected in the main tree if any $scope.culture = $routeParams.cculture ? $routeParams.cculture : $routeParams.mculture; + $scope.segment = $routeParams.csegment ? $routeParams.csegment : null; //Bind to $routeUpdate which will execute anytime a location changes but the route is not triggered. //This is so we can listen to changes on the cculture parameter since that will not cause a route change @@ -36,6 +37,7 @@ function ContentEditController($scope, $routeParams, contentResource) { //will not cause a route change and so we can update the isNew and contentId flags accordingly. $scope.$on('$routeUpdate', function (event, next) { $scope.culture = next.params.cculture ? next.params.cculture : $routeParams.mculture; + $scope.segment = next.params.csegment ? next.params.csegment : null; $scope.isNew = next.params.create === "true"; $scope.contentId = infiniteMode ? $scope.model.id : $routeParams.id; }); 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 cfb4b2c573..1d4b646e05 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/create.html @@ -36,7 +36,7 @@
        -
      • +
      diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/publish.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publish.controller.js index 8caa38ea17..decf05be5f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/publish.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publish.controller.js @@ -5,193 +5,171 @@ var vm = this; vm.loading = true; - vm.hasPristineVariants = false; vm.isNew = true; vm.changeSelection = changeSelection; - vm.dirtyVariantFilter = dirtyVariantFilter; - vm.pristineVariantFilter = pristineVariantFilter; - /** Returns true if publishing is possible based on if there are un-published mandatory languages */ + /** Returns true if publish meets the requirements of mandatory languages */ function canPublish() { - var possible = false; + var hasSomethingToPublish = false; + for (var i = 0; i < vm.variants.length; i++) { var variant = vm.variants[i]; - var state = canVariantPublish(variant); - if (state === true) { - possible = true; - } - if (state === false) { + + // if varaint is mandatory and not already published: + if (variant.publish === false && notPublishedMandatoryFilter(variant)) { return false; } + if (variant.publish === true) { + hasSomethingToPublish = true; + } + } - return possible; - } - - /** Returns true if publishing is possible based on if the variant is a un-published mandatory language */ - function canVariantPublish(variant) { - - //if this variant will show up in the publish-able list - var publishable = dirtyVariantFilter(variant); - var published = !(variant.state === "NotCreated" || variant.state === "Draft"); - - // is this variant mandatory: - if (variant.language.isMandatory && !published && !variant.publish) { - //if a mandatory variant isn't published or set to be published - //then we cannot continue - - return false; - } - - // is this variant selected for publish: - if (variant.publish === true) { - return publishable; - } - - return null; + return hasSomethingToPublish; } function changeSelection(variant) { - + // update submit button state: $scope.model.disableSubmitButton = !canPublish(); - //need to set the Save state to true if publish is true + //need to set the Save state to same as publish. variant.save = variant.publish; - - variant.willPublish = canVariantPublish(variant); } - function dirtyVariantFilter(variant) { - //determine a variant is 'dirty' (meaning it will show up as publish-able) if it's - // * the active one - // * it's editor is in a $dirty state - // * it has pending saves - // * it is unpublished - // * it is in NotCreated state - return (variant.active || variant.isDirty || variant.state === "Draft" || variant.state === "PublishedPendingChanges" || variant.state === "NotCreated"); - } - function hasAnyData(variant) { + function hasAnyDataFilter(variant) { if (variant.name == null || variant.name.length === 0) { return false; } - var result = variant.isDirty != null; - - if(result) return true; + if(variant.isDirty === true) { + return true; + } for (var t=0; t < variant.tabs.length; t++){ for (var p=0; p < variant.tabs[t].properties.length; p++){ - var property = variant.tabs[t].properties[p]; - - if(property.culture == null) continue; - - result = result || (property.value != null && property.value.length > 0); - - if(result) return true; + if (property.value != null && property.value.length > 0) { + return true; + } } } - return result; + return false; } - function pristineVariantFilter(variant) { - return !(dirtyVariantFilter(variant)); + function dirtyVariantFilter(variant) { + //determine a variant is 'dirty' (meaning it will show up as publish-able) if it's + // * it's editor is in a $dirty state + // * it has pending saves + // * it is unpublished + return (variant.isDirty || variant.state === "Draft" || variant.state === "PublishedPendingChanges"); + } + + function publishableVariantFilter(variant) { + //determine a variant is 'dirty' (meaning it will show up as publish-able) if it's + // * variant is active + // * it's editor is in a $dirty state + // * it has pending saves + // * it is unpublished + return (variant.active || variant.isDirty || variant.state === "Draft" || variant.state === "PublishedPendingChanges"); + } + + function notPublishedMandatoryFilter(variant) { + return variant.state !== "Published" && isMandatoryFilter(variant); + } + function isMandatoryFilter(variant) { + //determine a variant is 'dirty' (meaning it will show up as publish-able) if it's + // * has a mandatory language + // * without having a segment, segments cant be mandatory at current state of code. + return (variant.language && variant.language.isMandatory === true && variant.segment == null); + } + function notPublishableButMandatoryFilter(variant) { + //determine a variant is needed, but not already a choice. + // * publishable — aka. displayed as a publish option. + // * published — its already published and everything is then fine. + // * mandatory — this is needed, and thats why we highlight it. + return !publishableVariantFilter(variant) && variant.state !== "Published" && variant.isMandatory === true; } function onInit() { - - vm.variants = $scope.model.variants; - if (!$scope.model.title) { - localizationService.localize("content_readyToPublish").then(function (value) { - $scope.model.title = value; - }); - } + _.each(vm.variants, (variant) => { + + // reset to not be published + variant.publish = false; + variant.save = false; + + variant.isMandatory = isMandatoryFilter(variant); - vm.hasPristineVariants = false; - - _.each(vm.variants, - function (variant) { - if(variant.state === "NotCreated") { - vm.isNew = true; - } + // If we have a variant thats not in the state of NotCreated, then we know we have adata and its not a new content node. + if(variant.state !== "NotCreated") { + vm.isNew = false; } - ); - - _.each(vm.variants, - function (variant) { - variant.compositeId = contentEditingHelper.buildCompositeVariantId(variant); - variant.htmlId = "_content_variant_" + variant.compositeId; - - // reset to not be published - variant.publish = false; - variant.save = false; - - //check for pristine variants - if (!vm.hasPristineVariants) { - vm.hasPristineVariants = pristineVariantFilter(variant); - } - - // If the variant havent been created jet. - if(variant.state === "NotCreated") { - // If the variant is mandatory, then set the variant to be published. - if (variant.language.isMandatory === true) { - variant.publish = true; - variant.save = true; - } - } - - variant.canPublish = dirtyVariantFilter(variant); - - // if we have data on this variant. - if(variant.canPublish && hasAnyData(variant)) { - // and if some varaints havent been saved before, or they dont have a publishing date set, then we set it for publishing. - if(vm.isNew || variant.publishDate == null){ - variant.publish = true; - variant.save = true; - } - } - - variant.willPublish = canVariantPublish(variant); - } - ); - - if (vm.variants.length !== 0) { - //now sort it so that the current one is at the top - vm.variants = _.sortBy(vm.variants, function (v) { - return v.active ? 0 : 1; - }); - - var active = _.find(vm.variants, function (v) { - return v.active; - }); - - if (active) { - //ensure that the current one is selected - active.publish = true; - active.save = true; - } - - $scope.model.disableSubmitButton = !canPublish(); - - } else { - //disable Publish button if we have nothing to publish, should not happen - $scope.model.disableSubmitButton = true; - } - - var labelKey = vm.isNew ? "content_languagesToPublishForFirstTime" : "content_languagesToPublish"; - - localizationService.localize(labelKey).then(function (value) { - vm.headline = value; - vm.loading = false; }); + _.each(vm.variants, (variant) => { + + // if this is a new node and we have data on this variant. + if(vm.isNew === true && hasAnyDataFilter(variant)) { + variant.save = true; + } + + }); + vm.availableVariants = vm.variants.filter(publishableVariantFilter); + vm.missingMandatoryVariants = vm.variants.filter(notPublishableButMandatoryFilter); + + // if any active varaiant that is available for publish, we set it to be published: + _.each(vm.availableVariants, (v) => { + if(v.active) { + v.save = v.publish = true; + } + }); + + if (vm.availableVariants.length !== 0) { + vm.availableVariants.sort(function (a, b) { + if (a.language && b.language) { + if (a.language.name > b.language.name) { + return -1; + } + if (a.language.name < b.language.name) { + return 1; + } + } + if (a.segment && b.segment) { + if (a.segment > b.segment) { + return -1; + } + if (a.segment < b.segment) { + return 1; + } + } + return 0; + }); + } + + + $scope.model.disableSubmitButton = !canPublish(); + + if (vm.missingMandatoryVariants.length > 0) { + localizationService.localize("content_notReadyToPublish").then(function (value) { + $scope.model.title = value; + vm.loading = false; + }); + } else { + if (!$scope.model.title) { + localizationService.localize("content_readyToPublish").then(function (value) { + $scope.model.title = value; + vm.loading = false; + }); + } else { + vm.loading = false; + } + } } diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/publish.html b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publish.html index 80c8cfd485..4cf4444cfe 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/publish.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publish.html @@ -1,67 +1,83 @@ -
      - -
      -

      {{vm.headline}}

      -
      +
      -
      +
      + +
      +

      +
      + ng-repeat="variant in vm.availableVariants track by variant.compositeId"> -
      +
      -
      - - - - - - + server-validation-field="{{variant.htmlId}}" + > + + + * + + + + — {{variant.language.name}} + * + + + + - + + + + {{publishVariantSelectorForm.publishVariantSelector.errorMsg}} + + - - {{publishVariantSelectorForm.publishVariantSelector.errorMsg}} - + - -
      -
      +
      +
      +

      +
      -
      -
      -

      +
      +
      +

      -
      -
      -
      - {{variant.language.name}} - * -
      +
      +
      + + + * + + + + — {{variant.language.name}} + * + + + + - + + + + {{publishVariantSelectorForm.publishVariantSelector.errorMsg}} + -
      - -
      - -
      -
      {{notification.message}}
      -
      +
      diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.controller.js index 0ca2fe65c9..095c4f3fe1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.controller.js @@ -5,12 +5,16 @@ var vm = this; + vm.includeUnpublished = false; + vm.changeSelection = changeSelection; + vm.toggleIncludeUnpublished = toggleIncludeUnpublished; + function onInit() { - vm.includeUnpublished = false; vm.variants = $scope.model.variants; + vm.displayVariants = vm.variants.slice(0);// shallow copy, we dont want to share the array-object(because we will be performing a sort method) but each entry should be shared (because we need validation and notifications). vm.labels = {}; if (!$scope.model.title) { @@ -19,17 +23,30 @@ }); } - _.each(vm.variants, - function (variant) { - variant.compositeId = (variant.language ? variant.language.culture : "inv") + "_" + (variant.segment ? variant.segment : ""); - variant.htmlId = "_content_variant_" + variant.compositeId; - }); + _.each(vm.variants, function (variant) { + variant.isMandatory = isMandatoryFilter(variant); + }); if (vm.variants.length > 1) { - //now sort it so that the current one is at the top - vm.variants = _.sortBy(vm.variants, function (v) { - return v.active ? 0 : 1; + vm.displayVariants.sort(function (a, b) { + if (a.language && b.language) { + if (a.language.name > b.language.name) { + return -1; + } + if (a.language.name < b.language.name) { + return 1; + } + } + if (a.segment && b.segment) { + if (a.segment > b.segment) { + return -1; + } + if (a.segment < b.segment) { + return 1; + } + } + return 0; }); var active = _.find(vm.variants, function (v) { @@ -48,14 +65,17 @@ // localize help text for invariant content vm.labels.help = { "key": "content_publishDescendantsHelp", - "tokens": [] + "tokens": [vm.variants[0].name] }; - // add the node name as a token so it will show up in the translated text - vm.labels.help.tokens.push(vm.variants[0].name); } } + function toggleIncludeUnpublished() { + console.log("toggleIncludeUnpublished") + vm.includeUnpublished = !vm.includeUnpublished; + } + /** Returns true if publishing is possible based on if there are un-published mandatory languages */ function canPublish() { var selected = []; @@ -64,7 +84,7 @@ var published = !(variant.state === "NotCreated" || variant.state === "Draft"); - if (variant.language.isMandatory && !published && !variant.publish) { + if (variant.segment == null && variant.language && variant.language.isMandatory && !published && !variant.publish) { //if a mandatory variant isn't published //and not flagged for saving //then we cannot continue @@ -86,6 +106,14 @@ variant.save = variant.publish; } + + function isMandatoryFilter(variant) { + //determine a variant is 'dirty' (meaning it will show up as publish-able) if it's + // * has a mandatory language + // * without having a segment, segments cant be mandatory at current state of code. + return (variant.language && variant.language.isMandatory === true && variant.segment == null); + } + //when this dialog is closed, reset all 'publish' flags $scope.$on('$destroy', function () { for (var i = 0; i < vm.variants.length; i++) { diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.html b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.html index 0bf434f9a4..92373d00ff 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.html @@ -1,67 +1,69 @@ -
      +
      -
      +

      - +
      -
      +

      - -
      - -
      - +
      -
      +
      -
      +
      - - -
      - - - - - - - - - {{publishVariantSelectorForm.publishVariantSelector.errorMsg}} - - - + > + + + * -
      -
      + + + — {{variant.language.name}} + * + + + + - + + + + {{publishVariantSelectorForm.publishVariantSelector.errorMsg}} + + + +
      diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/save.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/overlays/save.controller.js index aacea9fe0e..2d40005618 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/save.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/save.controller.js @@ -9,8 +9,6 @@ vm.isNew = true; vm.changeSelection = changeSelection; - vm.dirtyVariantFilter = dirtyVariantFilter; - vm.pristineVariantFilter = pristineVariantFilter; function changeSelection(variant) { var firstSelected = _.find(vm.variants, function (v) { @@ -19,16 +17,18 @@ $scope.model.disableSubmitButton = !firstSelected; //disable submit button if there is none selected } - function dirtyVariantFilter(variant) { + function saveableVariantFilter(variant) { //determine a variant is 'dirty' (meaning it will show up as save-able) if it's // * the active one // * it's editor is in a $dirty state - // * it is in NotCreated state return (variant.active || variant.isDirty); } - function pristineVariantFilter(variant) { - return !(dirtyVariantFilter(variant)); + function isMandatoryFilter(variant) { + //determine a variant is 'dirty' (meaning it will show up as publish-able) if it's + // * has a mandatory language + // * without having a segment, segments cant be mandatory at current state of code. + return (variant.language && variant.language.isMandatory === true && variant.segment == null); } function hasAnyData(variant) { @@ -57,6 +57,7 @@ function onInit() { vm.variants = $scope.model.variants; + vm.availableVariants = vm.variants.filter(saveableVariantFilter); if(!$scope.model.title) { localizationService.localize("content_readyToSave").then(function(value){ @@ -64,10 +65,15 @@ }); } - vm.hasPristineVariants = false; - _.each(vm.variants, function (variant) { + + //reset state: + variant.save = false; + variant.publish = false; + + variant.isMandatory = isMandatoryFilter(variant); + if(variant.state !== "NotCreated"){ vm.isNew = false; } @@ -75,34 +81,40 @@ _.each(vm.variants, function (variant) { - variant.compositeId = contentEditingHelper.buildCompositeVariantId(variant); - variant.htmlId = "_content_variant_" + variant.compositeId; - - //check for pristine variants - if (!vm.hasPristineVariants) { - vm.hasPristineVariants = pristineVariantFilter(variant); - } - if(vm.isNew && hasAnyData(variant)){ variant.save = true; } }); if (vm.variants.length !== 0) { - //now sort it so that the current one is at the top - vm.variants = _.sortBy(vm.variants, function (v) { - return v.active ? 0 : 1; + + _.find(vm.variants, function (v) { + if(v.active) { + //ensure that the current one is selected + v.save = true; + } }); - var active = _.find(vm.variants, function (v) { - return v.active; + vm.availableVariants.sort(function (a, b) { + if (a.language && b.language) { + if (a.language.name > b.language.name) { + return -1; + } + if (a.language.name < b.language.name) { + return 1; + } + } + if (a.segment && b.segment) { + if (a.segment > b.segment) { + return -1; + } + if (a.segment < b.segment) { + return 1; + } + } + return 0; }); - if (active) { - //ensure that the current one is selected - active.save = true; - } - } else { //disable save button if we have nothing to save $scope.model.disableSubmitButton = true; diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/save.html b/src/Umbraco.Web.UI.Client/src/views/content/overlays/save.html index 552f6003b0..dd6e96df95 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/save.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/save.html @@ -1,4 +1,4 @@ -
      +
      @@ -7,17 +7,16 @@

      - - +

      + ng-repeat="variant in vm.availableVariants track by variant.compositeId"> -
      +
      + > + + + * + + + + — {{variant.language.name}} + * + + + + - + + + + {{saveVariantSelectorForm.saveVariantSelector.errorMsg}} + + -
      - - - - - + - - {{saveVariantSelectorForm.saveVariantSelector.errorMsg}} - - - -

      - -
      -
      -

      - - -

      -
      - -
      -
      -
      - {{ variant.language.name }} - * -
      - -
      - -
      - -
      -
      {{notification.message}}
      -
      -
      -
      -
      diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/schedule.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/overlays/schedule.controller.js index 5aa7eff1ef..e7f31ac707 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/schedule.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/schedule.controller.js @@ -12,7 +12,6 @@ vm.clearPublishDate = clearPublishDate; vm.clearUnpublishDate = clearUnpublishDate; vm.dirtyVariantFilter = dirtyVariantFilter; - vm.pristineVariantFilter = pristineVariantFilter; vm.changeSelection = changeSelection; vm.firstSelectedDates = {}; @@ -24,7 +23,7 @@ function onInit() { vm.variants = $scope.model.variants; - vm.hasPristineVariants = false; + vm.displayVariants = vm.variants.slice(0);// shallow copy, we dont want to share the array-object(because we will be performing a sort method) but each entry should be shared (because we need validation and notifications). for (let i = 0; i < vm.variants.length; i++) { origDates.push({ @@ -38,36 +37,42 @@ $scope.model.title = value; }); } + + _.each(vm.variants, function (variant) { + variant.isMandatory = isMandatoryFilter(variant); + + }); // Check for variants: if a node is invariant it will still have the default language in variants // so we have to check for length > 1 if (vm.variants.length > 1) { - _.each(vm.variants, - function (variant) { - variant.compositeId = contentEditingHelper.buildCompositeVariantId(variant); - variant.htmlId = "_content_variant_" + variant.compositeId; - - //check for pristine variants - if (!vm.hasPristineVariants) { - vm.hasPristineVariants = pristineVariantFilter(variant); + vm.displayVariants.sort(function (a, b) { + if (a.language && b.language) { + if (a.language.name > b.language.name) { + return -1; } - }); - - //now sort it so that the current one is at the top - vm.variants = _.sortBy(vm.variants, function (v) { - return v.active ? 0 : 1; + if (a.language.name < b.language.name) { + return 1; + } + } + if (a.segment && b.segment) { + if (a.segment > b.segment) { + return -1; + } + if (a.segment < b.segment) { + return 1; + } + } + return 0; }); - var active = _.find(vm.variants, function (v) { - return v.active; + _.each(vm.variants, function (v) { + if (v.active) { + v.save = true; + } }); - - if (active) { - //ensure that the current one is selected - active.save = true; - } - + $scope.model.disableSubmitButton = !canSchedule(); } @@ -127,6 +132,7 @@ * @param {any} type publish or unpublish */ function datePickerChange(variant, dateStr, type) { + console.log("datePickerChange", variant, dateStr, type) if (type === 'publish') { setPublishDate(variant, dateStr); } else if (type === 'unpublish') { @@ -241,6 +247,7 @@ * @param {any} variant */ function clearPublishDate(variant) { + console.log("clearPublishDate", variant, variant.releaseDate) if(variant && variant.releaseDate) { variant.releaseDate = null; // we don't have a publish date anymore so we can clear the min date for unpublish @@ -256,6 +263,7 @@ * @param {any} variant */ function clearUnpublishDate(variant) { + console.log("clearUnpublishDate", variant) if(variant && variant.expireDate) { variant.expireDate = null; // we don't have a unpublish date anymore so we can clear the max date for publish @@ -297,8 +305,11 @@ return (variant.active || variant.isDirty || variant.state === "Draft" || variant.state === "PublishedPendingChanges" || variant.state === "NotCreated"); } - function pristineVariantFilter(variant) { - return !(dirtyVariantFilter(variant)); + function isMandatoryFilter(variant) { + //determine a variant is 'dirty' (meaning it will show up as publish-able) if it's + // * has a mandatory language + // * without having a segment, segments cant be mandatory at current state of code. + return (variant.language && variant.language.isMandatory === true && variant.segment == null); } /** Returns true if publishing is possible based on if there are un-published mandatory languages */ @@ -321,7 +332,7 @@ return true; } - var isMandatory = variant.language && variant.language.isMandatory; + var isMandatory = variant.segment == null && variant.language && variant.language.isMandatory; //if this variant will show up in the publish-able list var publishable = dirtyVariantFilter(variant); diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/schedule.html b/src/Umbraco.Web.UI.Client/src/views/content/overlays/schedule.html index 4f4e3c2874..8f515a10e4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/schedule.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/schedule.html @@ -1,4 +1,4 @@ -
      +
      @@ -84,28 +84,39 @@
      -
      +
      -
      +
      + server-validation-field="{{variant.htmlId}}" + > + + + * + + + + — {{variant.language.name}} + * + + + + - + +
      - - - - - +
      -
      +
      Publish:  {{variant.releaseDateFormatted}}
      @@ -117,7 +128,7 @@ on-open="vm.datePickerShow(variant, 'publish')" on-close="vm.datePickerClose(variant, 'publish')">
      - @@ -126,9 +137,9 @@
      - +
      @@ -143,7 +154,7 @@ on-open="vm.datePickerShow(variant, 'unpublish')" on-close="vm.datePickerClose(variant, 'unpublish')">
      - @@ -152,7 +163,7 @@
      - +
      diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.controller.js index c85e8d7013..f425dce4a3 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.controller.js @@ -6,8 +6,6 @@ var vm = this; vm.loading = true; - vm.modifiedVariantFilter = modifiedVariantFilter; - vm.unmodifiedVariantFilter = unmodifiedVariantFilter; vm.changeSelection = changeSelection; function onInit() { @@ -21,28 +19,40 @@ }); } + _.each(vm.variants, function (variant) { + variant.isMandatory = isMandatoryFilter(variant); + }); + + vm.availableVariants = vm.variants.filter(publishableVariantFilter); + + if (vm.availableVariants.length !== 0) { - if (vm.variants.length !== 0) { - _.each(vm.variants, - function (variant) { - variant.compositeId = contentEditingHelper.buildCompositeVariantId(variant); - variant.htmlId = "_content_variant_" + variant.compositeId; - }); - - //now sort it so that the current one is at the top - vm.variants = _.sortBy(vm.variants, function (v) { - return v.active ? 0 : 1; + vm.availableVariants = vm.availableVariants.sort(function (a, b) { + if (a.language && b.language) { + if (a.language.name > b.language.name) { + return -1; + } + if (a.language.name < b.language.name) { + return 1; + } + } + if (a.segment && b.segment) { + if (a.segment > b.segment) { + return -1; + } + if (a.segment < b.segment) { + return 1; + } + } + return 0; }); - var active = _.find(vm.variants, function (v) { - return v.active; + _.each(vm.availableVariants, function (v) { + if(v.active) { + v.save = true; + } }); - if (active) { - //ensure that the current one is selected - active.save = true; - } - } else { //disable save button if we have nothing to save $scope.model.disableSubmitButton = true; @@ -59,20 +69,20 @@ $scope.model.disableSubmitButton = !firstSelected; //disable submit button if there is none selected } - function modifiedVariantFilter(variant) { - //determine a variant is 'modified' (meaning it will show up as able to send for approval) - // * it's editor is in a $dirty state - // * it is in Draft state - // * it is published with pending changes - return (variant.active || variant.isDirty || variant.state === "Draft" || variant.state === "PublishedPendingChanges"); + function isMandatoryFilter(variant) { + //determine a variant is 'dirty' (meaning it will show up as publish-able) if it's + // * has a mandatory language + // * without having a segment, segments cant be mandatory at current state of code. + return (variant.language && variant.language.isMandatory === true && variant.segment == null); } - function unmodifiedVariantFilter(variant) { - //determine a variant is 'unmodified' (meaning it will NOT show up as able to send for approval) + function publishableVariantFilter(variant) { + //determine a variant is 'dirty' (meaning it will show up as publish-able) if it's + // * variant is active // * it's editor is in a $dirty state - // * it has been published - // * it is not created for that specific language - return (variant.state === "Published" && !variant.isDirty && !variant.active || variant.state === "NotCreated" && !variant.isDirty && !variant.active); + // * it has pending saves + // * it is unpublished + return (variant.active || variant.isDirty || variant.state === "Draft" || variant.state === "PublishedPendingChanges"); } //when this dialog is closed, reset all 'save' flags diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.html b/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.html index ae8cd87484..91fa1799fc 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.html @@ -1,4 +1,4 @@ -
      +

      @@ -10,31 +10,36 @@
      -
      +
      -
      +
      - - -
      - + > + + + * + + + + — {{variant.language.name}} + * + + - - + - + - - - {{publishVariantSelectorForm.publishVariantSelector.errorMsg}} + + {{publishVariantSelectorForm.publishVariantSelector.errorMsg}} + - -
      +
      @@ -42,20 +47,4 @@
      -
      -
      -

      -
      - -
      -
      -
      - {{ variant.language.name }} - * -
      - -
      -
      -
      -
      diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/unpublish.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/overlays/unpublish.controller.js index 3011bf49ee..3db76ee49f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/unpublish.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/unpublish.controller.js @@ -7,12 +7,11 @@ var autoSelectedVariants = []; vm.changeSelection = changeSelection; - vm.publishedVariantFilter = publishedVariantFilter; - vm.unpublishedVariantFilter = unpublishedVariantFilter; function onInit() { vm.variants = $scope.model.variants; + vm.unpublishableVariants = vm.variants.filter(publishedVariantFilter) // set dialog title if (!$scope.model.title) { @@ -21,24 +20,38 @@ }); } - _.each(vm.variants, - function (variant) { - variant.compositeId = contentEditingHelper.buildCompositeVariantId(variant); - variant.htmlId = "_content_variant_" + variant.compositeId; - }); + _.each(vm.variants, function (variant) { + variant.isMandatory = isMandatoryFilter(variant); + }); // node has variants if (vm.variants.length !== 1) { - //now sort it so that the current one is at the top - vm.variants = _.sortBy(vm.variants, function (v) { - return v.active ? 0 : 1; + + vm.unpublishableVariants.sort(function (a, b) { + if (a.language && b.language) { + if (a.language.name > b.language.name) { + return -1; + } + if (a.language.name < b.language.name) { + return 1; + } + } + if (a.segment && b.segment) { + if (a.segment > b.segment) { + return -1; + } + if (a.segment < b.segment) { + return 1; + } + } + return 0; }); var active = _.find(vm.variants, function (v) { return v.active; }); - if (active) { + if (active && publishedVariantFilter(active)) { //ensure that the current one is selected active.save = true; } @@ -51,21 +64,15 @@ function changeSelection(selectedVariant) { - // disable submit button if nothing is selected - var firstSelected = _.find(vm.variants, function (v) { - return v.save; - }); - $scope.model.disableSubmitButton = !firstSelected; //disable submit button if there is none selected - - // if a mandatory variant is selected we want to select all other variants + // if a mandatory variant is selected we want to select all other variants, we cant have anything published if a mandatory variants gets unpublished. // and disable selection for the others - if(selectedVariant.save && selectedVariant.language.isMandatory) { + if(selectedVariant.save && selectedVariant.segment == null && selectedVariant.language && selectedVariant.language.isMandatory) { - angular.forEach(vm.variants, function(variant){ - if(!variant.save && publishedVariantFilter(variant)) { + vm.variants.forEach(function(variant) { + if(!variant.save) { // keep track of the variants we automaically select // so we can remove the selection again - autoSelectedVariants.push(variant.language.culture); + autoSelectedVariants.push(variant); variant.save = true; } variant.disabled = true; @@ -79,12 +86,12 @@ // if a mandatory variant is deselected we want to deselet all the variants // that was automatically selected so it goes back to the state before the mandatory language was selected. // We also want to enable all checkboxes again - if(!selectedVariant.save && selectedVariant.language.isMandatory) { + if(!selectedVariant.save && selectedVariant.segment == null && selectedVariant.language && selectedVariant.language.isMandatory) { - angular.forEach(vm.variants, function(variant){ + vm.variants.forEach( function(variant){ // check if variant was auto selected, then deselect - if(_.contains(autoSelectedVariants, variant.language.culture)) { + if(_.contains(autoSelectedVariants, variant)) { variant.save = false; }; @@ -93,6 +100,19 @@ autoSelectedVariants = []; } + // disable submit button if nothing is selected + var firstSelected = _.find(vm.variants, function (v) { + return v.save; + }); + $scope.model.disableSubmitButton = !firstSelected; //disable submit button if there is none selected + + } + + function isMandatoryFilter(variant) { + //determine a variant is 'dirty' (meaning it will show up as publish-able) if it's + // * has a mandatory language + // * without having a segment, segments cant be mandatory at current state of code. + return (variant.language && variant.language.isMandatory === true && variant.segment == null); } function publishedVariantFilter(variant) { @@ -102,13 +122,6 @@ return (variant.state === "Published" || variant.state === "PublishedPendingChanges"); } - function unpublishedVariantFilter(variant) { - //determine a variant is 'modified' (meaning it will NOT show up as able to unpublish) - // * it's editor is in a $dirty state - // * it is published with pending changes - return (variant.state !== "Published" && variant.state !== "PublishedPendingChanges"); - } - //when this dialog is closed, remove all unpublish and disabled flags $scope.$on('$destroy', function () { for (var i = 0; i < vm.variants.length; i++) { diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/unpublish.html b/src/Umbraco.Web.UI.Client/src/views/content/overlays/unpublish.html index e384823cc3..94dd9fdac5 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/unpublish.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/unpublish.html @@ -1,4 +1,4 @@ -
      +
      @@ -10,12 +10,12 @@

      - +
      - -
      + +
      -
      +
      - -
      - - - - + > + + + * -
      + + + — {{variant.language.name}} + * + + + + - + + + + {{publishVariantSelectorForm.publishVariantSelector.errorMsg}} + +
      + + +
      - +

      - -
      -
      -

      -
      - -
      -
      -
      - {{ variant.language.name }} - * -
      - -
      - -
      -
      -
      -
      - +
      diff --git a/src/Umbraco.Web.UI.Client/src/views/content/rights.html b/src/Umbraco.Web.UI.Client/src/views/content/rights.html index 6ceec1db51..3395468cb0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/rights.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/rights.html @@ -6,8 +6,8 @@ -
      - + -
      Set permissions for {{ currentNode.name }}
      -

      +
      Set permissions for {{ currentNode.name }}
      +

      @@ -104,7 +104,7 @@
      {{::reference.name}}
      {{::reference.alias}}
      {{::reference.properties | umbCmsJoinArray:', ':'name'}}
      - +
      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 index e1334aa816..732aa898a7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.controller.js @@ -97,7 +97,7 @@ function DocumentTypesCreateController($scope, $location, navigationService, con $scope.error = err; //show any notifications - if (angular.isArray(err.data.notifications)) { + if (Utilities.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/documenttypes/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js index 0ed76ed99b..42dba5f580 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js @@ -325,7 +325,7 @@ /* ---------- SAVE ---------- */ function save() { - saveInternal().then(angular.noop, angular.noop); + saveInternal().then(Utilities.noop, Utilities.noop); } /** This internal save method performs the actual saving and returns a promise, not to be bound to any buttons but used by other bound methods */ 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 index a3b422e4f3..da8dee8e03 100644 --- 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 @@ -9,7 +9,7 @@ (function() { 'use strict'; - function PermissionsController($scope, contentTypeResource, iconHelper, contentTypeHelper, localizationService, overlayService) { + function PermissionsController($scope, $timeout, contentTypeResource, iconHelper, contentTypeHelper, localizationService, overlayService) { /* ----------- SCOPE VARIABLES ----------- */ @@ -20,11 +20,15 @@ vm.selectedChildren = []; vm.overlayTitle = ""; + vm.showAllowSegmentationOption = Umbraco.Sys.ServerVariables.umbracoSettings.showAllowSegmentationForDocumentTypes || false; vm.addChild = addChild; vm.removeChild = removeChild; + vm.sortChildren = sortChildren; vm.toggleAllowAsRoot = toggleAllowAsRoot; vm.toggleAllowCultureVariants = toggleAllowCultureVariants; + vm.toggleAllowSegmentVariants = toggleAllowSegmentVariants; + vm.canToggleIsElement = false; vm.toggleIsElement = toggleIsElement; /* ---------- INIT ---------- */ @@ -48,9 +52,16 @@ if($scope.model.id === 0) { contentTypeHelper.insertChildNodePlaceholder(vm.contentTypes, $scope.model.name, $scope.model.icon, $scope.model.id); } - }); + // Can only switch to an element type if there are no content nodes already created from the type. + if ($scope.model.id > 0 && !$scope.model.isElement ) { + contentTypeResource.hasContentNodes($scope.model.id).then(function (result) { + vm.canToggleIsElement = !result; + }); + } else { + vm.canToggleIsElement = true; + } } function addChild($event) { @@ -84,6 +95,13 @@ $scope.model.allowedContentTypes.splice(selectedChildIndex, 1); } + function sortChildren() { + // we need to wait until the next digest cycle for vm.selectedChildren to be updated + $timeout(function () { + $scope.model.allowedContentTypes = _.pluck(vm.selectedChildren, "id"); + }); + } + // note: "safe toggling" here ie handling cases where the value is undefined, etc function toggleAllowAsRoot() { @@ -94,6 +112,10 @@ $scope.model.allowCultureVariant = $scope.model.allowCultureVariant ? false : true; } + function toggleAllowSegmentVariants() { + $scope.model.allowSegmentVariant = $scope.model.allowSegmentVariant ? false : true; + } + function toggleIsElement() { $scope.model.isElement = $scope.model.isElement ? false : true; } 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 index e890921ad4..b44ce0ff7e 100644 --- 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 @@ -33,7 +33,8 @@ parent-icon="model.icon" parent-id="model.id" on-add="vm.addChild" - on-remove="vm.removeChild"> + on-remove="vm.removeChild" + on-sort="vm.sortChildren">
      @@ -42,8 +43,8 @@
      -
      - +
      +
      @@ -56,16 +57,35 @@
      + +
      + +
      +
      + +
      + +
      + + +
      + +
      +
      +
      diff --git a/src/Umbraco.Web.UI.Client/src/views/languages/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/languages/edit.controller.js index 5f1c46de4c..96441e6101 100644 --- a/src/Umbraco.Web.UI.Client/src/views/languages/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/languages/edit.controller.js @@ -57,7 +57,7 @@ }; if ($routeParams.create) { - vm.page.name = vm.labels.addLanguage; + vm.page.name = vm.labels.addLanguage; } }); @@ -87,14 +87,14 @@ if (!$routeParams.create) { - promises.push(languageResource.getById($routeParams.id).then(function(lang) { + promises.push(languageResource.getById($routeParams.id).then(function (lang) { vm.language = lang; vm.page.name = vm.language.name; /* we need to store the initial default state so we can disable the toggle if it is the default. we need to prevent from not having a default language. */ - vm.initIsDefault = angular.copy(vm.language.isDefault); + vm.initIsDefault = Utilities.copy(vm.language.isDefault); makeBreadcrumbs(); @@ -182,12 +182,12 @@ function toggleDefault() { // it shouldn't be possible to uncheck the default language - if(vm.initIsDefault) { + if (vm.initIsDefault) { return; } vm.language.isDefault = !vm.language.isDefault; - if(vm.language.isDefault) { + if (vm.language.isDefault) { vm.showDefaultLanguageInfo = true; } else { vm.showDefaultLanguageInfo = false; 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 a5884c2355..a7ce67fc0b 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 @@ -73,14 +73,14 @@ function mediaEditController($scope, $routeParams, $q, appState, mediaResource, var content = $scope.content; - // we need to check wether an app is present in the current data, if not we will present the default app. + // we need to check whether an app is present in the current data, if not we will present the default app. var isAppPresent = false; // on first init, we dont have any apps. but if we are re-initializing, we do, but ... if ($scope.app) { // lets check if it still exists as part of our apps array. (if not we have made a change to our docType, even just a re-save of the docType it will turn into new Apps.) - _.forEach(content.apps, function(app) { + content.apps.forEach(app => { if (app === $scope.app) { isAppPresent = true; } @@ -88,7 +88,7 @@ function mediaEditController($scope, $routeParams, $q, appState, mediaResource, // if we did reload our DocType, but still have the same app we will try to find it by the alias. if (isAppPresent === false) { - _.forEach(content.apps, function(app) { + content.apps.forEach(app => { if (app.alias === $scope.app.alias) { isAppPresent = true; app.active = true; @@ -182,24 +182,26 @@ function mediaEditController($scope, $routeParams, $q, appState, mediaResource, formHelper.resetForm({ scope: $scope }); - contentEditingHelper.handleSuccessfulSave({ - scope: $scope, - savedContent: data, - rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data) - }); - - editorState.set($scope.content); - - syncTreeNode($scope.content, data.path); - - init(); - - $scope.page.saveButtonState = "success"; - // close the editor if it's infinite mode + // submit function manages rebinding changes if(infiniteMode && $scope.model.submit) { $scope.model.mediaNode = $scope.content; $scope.model.submit($scope.model); + } else { + // if not infinite mode, rebind changed props etc + contentEditingHelper.handleSuccessfulSave({ + scope: $scope, + savedContent: data, + rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data) + }); + + editorState.set($scope.content); + + syncTreeNode($scope.content, data.path); + + $scope.page.saveButtonState = "success"; + + init(); } }, function(err) { @@ -245,7 +247,7 @@ function mediaEditController($scope, $routeParams, $q, appState, mediaResource, syncTreeNode($scope.content, data.path, true); } - if ($scope.content.parentId && $scope.content.parentId != -1) { + if ($scope.content.parentId && $scope.content.parentId !== -1 && $scope.content.parentId !== -21) { //We fetch all ancestors of the node to generate the footer breadcrump navigation entityResource.getAncestors(nodeId, "media") .then(function (anc) { diff --git a/src/Umbraco.Web.UI.Client/src/views/mediatypes/delete.html b/src/Umbraco.Web.UI.Client/src/views/mediatypes/delete.html index c059710ebb..fb0c4c3a52 100644 --- a/src/Umbraco.Web.UI.Client/src/views/mediatypes/delete.html +++ b/src/Umbraco.Web.UI.Client/src/views/mediatypes/delete.html @@ -9,7 +9,7 @@
      + on-cancel="cancel" confirm-button-style="danger">
      diff --git a/src/Umbraco.Web.UI.Client/src/views/mediatypes/edit.html b/src/Umbraco.Web.UI.Client/src/views/mediatypes/edit.html index 814ef7282d..153a0a4505 100644 --- a/src/Umbraco.Web.UI.Client/src/views/mediatypes/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/mediatypes/edit.html @@ -9,6 +9,7 @@ + on-remove="vm.removeChild" + on-sort="vm.sortChildren">
      diff --git a/src/Umbraco.Web.UI.Client/src/views/membertypes/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/membertypes/edit.controller.js index ab54a453b5..b2e515e187 100644 --- a/src/Umbraco.Web.UI.Client/src/views/membertypes/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/membertypes/edit.controller.js @@ -13,8 +13,13 @@ var evts = []; var vm = this; + var infiniteMode = $scope.model && $scope.model.infiniteMode; + var memberTypeId = infiniteMode ? $scope.model.id : $routeParams.id; + var create = infiniteMode ? $scope.model.create : $routeParams.create; vm.save = save; + vm.close = close; + vm.editorfor = "visuallyHiddenTexts_newMember"; vm.header = {}; vm.header.editorfor = "content_membergroup"; @@ -25,6 +30,7 @@ vm.page.loading = false; vm.page.saveButtonState = "init"; vm.labels = {}; + vm.saveButtonKey = infiniteMode ? "buttons_saveAndClose" : "buttons_save"; var labelKeys = [ "general_design", @@ -86,7 +92,7 @@ vm.page.defaultButton = { hotKey: "ctrl+s", hotKeyWhenHidden: true, - labelKey: "buttons_save", + labelKey: vm.saveButtonKey, letter: "S", type: "submit", handler: function () { vm.save(); } @@ -94,7 +100,7 @@ vm.page.subButtons = [{ hotKey: "ctrl+g", hotKeyWhenHidden: true, - labelKey: "buttons_saveAndGenerateModels", + labelKey: infiniteMode ? "buttons_generateModelsAndClose" : "buttons_saveAndGenerateModels", letter: "G", handler: function () { @@ -147,12 +153,12 @@ } }); - if ($routeParams.create) { + if (create) { vm.page.loading = true; //we are creating so get an empty data type item - memberTypeResource.getScaffold($routeParams.id) + memberTypeResource.getScaffold(memberTypeId) .then(function (dt) { init(dt); @@ -163,10 +169,12 @@ vm.page.loading = true; - memberTypeResource.getById($routeParams.id).then(function (dt) { + memberTypeResource.getById(memberTypeId).then(function (dt) { init(dt); - syncTreeNode(vm.contentType, dt.path, true); + if(!infiniteMode) { + syncTreeNode(vm.contentType, dt.path, true); + } vm.page.loading = false; }); @@ -219,10 +227,16 @@ } }).then(function (data) { //success - syncTreeNode(vm.contentType, data.path); + if(!infiniteMode) { + syncTreeNode(vm.contentType, data.path); + } vm.page.saveButtonState = "success"; + if(infiniteMode && $scope.model.submit) { + $scope.model.submit(); + } + deferred.resolve(data); }, function (err) { //error @@ -307,6 +321,12 @@ }); } + + function close() { + if(infiniteMode && $scope.model.close) { + $scope.model.close(); + } + } evts.push(eventsService.on("editors.groupsBuilder.changed", function(name, args) { angularHelper.getCurrentForm($scope).$setDirty(); diff --git a/src/Umbraco.Web.UI.Client/src/views/membertypes/edit.html b/src/Umbraco.Web.UI.Client/src/views/membertypes/edit.html index 07824bc7ec..c4c521c857 100644 --- a/src/Umbraco.Web.UI.Client/src/views/membertypes/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/membertypes/edit.html @@ -40,6 +40,14 @@ + + + + label-key="{{vm.saveButtonKey}}"> { @@ -78,7 +78,7 @@ }); - + localizationService.localizeMany(["buttons_save", "packager_includeAllChildNodes"]).then(function (values) { vm.labels.button = values[0]; vm.labels.includeAllChildNodes = values[1]; @@ -232,7 +232,7 @@ function openFilePicker() { - let selection = angular.copy(vm.package.files); + let selection = Utilities.copy(vm.package.files); const filePicker = { title: "Select files", diff --git a/src/Umbraco.Web.UI.Client/src/views/packages/views/created.controller.js b/src/Umbraco.Web.UI.Client/src/views/packages/views/created.controller.js index 295057e609..503d85fe8d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/packages/views/created.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/packages/views/created.controller.js @@ -15,7 +15,7 @@ packageResource.getAllCreated().then(createdPackages => { vm.createdPackages = createdPackages; - }, angular.noop); + }, Utilities.noop); } diff --git a/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/edit.controller.js index aa37a91919..2f7d879607 100644 --- a/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/edit.controller.js @@ -80,7 +80,7 @@ activate: false }); completeSave(saved); - }, angular.noop); + }, Utilities.noop); } else { diff --git a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/colorpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/colorpicker.controller.js index b4381b699b..9ad2c87ab4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/colorpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/colorpicker.controller.js @@ -19,7 +19,7 @@ angular.module("umbraco").controller("Umbraco.PrevalueEditors.ColorPickerControl // Make an array from the dictionary var items = []; - if (angular.isArray($scope.model.prevalues)) { + if (Utilities.isArray($scope.model.prevalues)) { for (var i in $scope.model.prevalues) { var oldValue = $scope.model.prevalues[i]; diff --git a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/multivalues.controller.js b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/multivalues.controller.js index 3da57943f9..c37c382dac 100644 --- a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/multivalues.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/multivalues.controller.js @@ -7,7 +7,7 @@ angular.module("umbraco").controller("Umbraco.PrevalueEditors.MultiValuesControl $scope.hasError = false; $scope.focusOnNew = false; - if (!angular.isArray($scope.model.value)) { + if (!Utilities.isArray($scope.model.value)) { //make an array from the dictionary var items = []; 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 951b76193f..0359043da4 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 @@ -118,7 +118,7 @@ angular.module('umbraco') } function populate(data) { - if (angular.isArray(data)) { + if (Utilities.isArray(data)) { _.each(data, function (item, i) { $scope.add(item); }); 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 49058dfe29..ab7f5c66e0 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 @@ -38,7 +38,7 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.ChangePasswordCont } //set the model defaults - if (!angular.isObject($scope.model.value)) { + if (!Utilities.isObject($scope.model.value)) { //if it's not an object then just create a new one $scope.model.value = { newPassword: null, diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/checkboxlist/checkboxlist.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/checkboxlist/checkboxlist.controller.js index 297bf23cef..10668808a5 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/checkboxlist/checkboxlist.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/checkboxlist/checkboxlist.controller.js @@ -10,7 +10,7 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.CheckboxListContro function init() { // currently the property editor will onyl work if our input is an object. - if (angular.isObject($scope.model.config.items)) { + if (Utilities.isObject($scope.model.config.items)) { // formatting the items in the dictionary into an array var sortedItems = []; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.controller.js index ba2ad72191..e2f502e463 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.controller.js @@ -25,7 +25,7 @@ function ColorPickerController($scope, $timeout) { initActiveColor(); } - if (!angular.isArray($scope.model.config.items)) { + if (!Utilities.isArray($scope.model.config.items)) { //make an array from the dictionary var items = []; for (var i in $scope.model.config.items) { 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 91c6e673b9..2cbad88a43 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 @@ -55,7 +55,7 @@ }); }); - if (!angular.isArray($scope.model.value)) { + if (!Utilities.isArray($scope.model.value)) { //make an array from the dictionary var items = []; for (var i in $scope.model.value) { 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 5df324c60f..238872db40 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 @@ -152,7 +152,7 @@ function contentPickerController($scope, entityResource, editorState, iconHelper dataTypeKey: $scope.model.dataTypeKey, currentNode: editorState ? editorState.current : null, callback: function (data) { - if (angular.isArray(data)) { + if (Utilities.isArray(data)) { _.each(data, function (item, i) { $scope.add(item); }); @@ -233,7 +233,7 @@ function contentPickerController($scope, entityResource, editorState, iconHelper $scope.currentPicker = dialogOptions; $scope.currentPicker.submit = function (model) { - if (angular.isArray(model.selection)) { + if (Utilities.isArray(model.selection)) { _.each(model.selection, function (item, i) { $scope.add(item); }); 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 ab9b078433..ba22ca9d80 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 @@ -31,7 +31,7 @@ ... -
      +
      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 0f19bf3b1a..24affc6ac1 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 @@ -85,13 +85,27 @@ function dateTimePickerController($scope, angularHelper, dateHelper, validationM }; $scope.datePickerChange = function(date) { - setDate(date); + const momentDate = moment(date); + setDate(momentDate); setDatePickerVal(); }; - $scope.inputChanged = function() { - setDate($scope.model.datetimePickerValue); - setDatePickerVal(); + $scope.inputChanged = function () { + if ($scope.model.datetimePickerValue === "" && $scope.hasDatetimePickerValue) { + // $scope.hasDatetimePickerValue indicates that we had a value before the input was changed, + // but now the input is empty. + $scope.clearDate(); + } else if ($scope.model.datetimePickerValue) { + var momentDate = moment($scope.model.datetimePickerValue, $scope.model.config.format, true); + if (!momentDate || !momentDate.isValid()) { + momentDate = moment(new Date($scope.model.datetimePickerValue)); + } + if (momentDate && momentDate.isValid()) { + setDate(momentDate); + } + setDatePickerVal(); + flatPickr.setDate($scope.model.value, false); + } } //here we declare a special method which will be called whenever the value has changed from the server @@ -103,15 +117,14 @@ function dateTimePickerController($scope, angularHelper, dateHelper, validationM var newDate = moment(newVal); if (newDate.isAfter(minDate)) { - setDate(newVal); + setDate(newDate); } else { $scope.clearDate(); } } }; - function setDate(date) { - const momentDate = moment(date); + function setDate(momentDate) { angularHelper.safeApply($scope, function() { // when a date is changed, update the model if (momentDate && momentDate.isValid()) { @@ -123,12 +136,11 @@ function dateTimePickerController($scope, angularHelper, dateHelper, validationM $scope.hasDatetimePickerValue = false; $scope.model.datetimePickerValue = null; } - updateModelValue(date); + updateModelValue(momentDate); }); } - function updateModelValue(date) { - const momentDate = moment(date); + function updateModelValue(momentDate) { if ($scope.hasDatetimePickerValue) { if ($scope.model.config.pickTime) { //check if we are supposed to offset the time diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/dropdownFlexible/dropdownFlexible.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/dropdownFlexible/dropdownFlexible.controller.js index a6d615cdd1..a8979c949b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/dropdownFlexible/dropdownFlexible.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/dropdownFlexible/dropdownFlexible.controller.js @@ -45,14 +45,14 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.DropdownFlexibleCo $scope.model.value = [$scope.model.singleDropdownValue]; } - if (angular.isArray($scope.model.config.items)) { + if (Utilities.isArray($scope.model.config.items)) { //PP: I dont think this will happen, but we have tests that expect it to happen.. //if array is simple values, convert to array of objects - if(!angular.isObject($scope.model.config.items[0])){ + if (!Utilities.isObject($scope.model.config.items[0])){ $scope.model.config.items = convertArrayToDictionaryArray($scope.model.config.items); } } - else if (angular.isObject($scope.model.config.items)) { + else if (Utilities.isObject($scope.model.config.items)) { $scope.model.config.items = convertObjectToDictionaryArray($scope.model.config.items); } else { 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 0f8a8df895..1370550631 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 @@ -4,7 +4,7 @@ angular.module("umbraco") function onInit() { - var embedPreview = angular.isObject($scope.control.value) && $scope.control.value.preview ? $scope.control.value.preview : $scope.control.value; + var embedPreview = Utilities.isObject($scope.control.value) && $scope.control.value.preview ? $scope.control.value.preview : $scope.control.value; $scope.trustedValue = embedPreview ? $sce.trustAsHtml(embedPreview) : null; @@ -19,7 +19,7 @@ angular.module("umbraco") $scope.setEmbed = function () { - var original = angular.isObject($scope.control.value) ? $scope.control.value : null; + var original = Utilities.isObject($scope.control.value) ? $scope.control.value : null; var embed = { original: original, 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 3db1221f5e..2568f62cf4 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 @@ -9,6 +9,7 @@ angular.module("umbraco") $element, eventsService, editorService, + overlayService, $interpolate ) { @@ -184,7 +185,7 @@ angular.module("umbraco") var rteId = value.id; if ($.inArray(rteId, notIncludedRte) < 0) { - + // remember this RTEs settings, cause we need to update it later. var editor = _.findWhere(tinyMCE.editors, { id: rteId }) if (editor) { @@ -196,17 +197,17 @@ angular.module("umbraco") } else { $(event.target).find(".umb-rte").each(function () { - + var rteId = $(this).attr("id"); - + if ($.inArray(rteId, notIncludedRte) < 0) { - + // remember this RTEs settings, cause we need to update it later. var editor = _.findWhere(tinyMCE.editors, { id: rteId }) if (editor) { draggedRteSettings[rteId] = editor.settings; } - + notIncludedRte.splice(0, 0, $(this).attr("id")); } }); @@ -226,12 +227,12 @@ angular.module("umbraco") // reset dragged RTE settings in case a RTE isn't dragged draggedRteSettings = {}; notIncludedRte = []; - + ui.item[0].style.display = "block"; ui.item.find(".umb-rte").each(function (key, value) { - + var rteId = value.id; - + // remember this RTEs settings, cause we need to update it later. var editor = _.findWhere(tinyMCE.editors, { id: rteId }); @@ -254,17 +255,17 @@ angular.module("umbraco") ui.item.offsetParent().find(".umb-rte").each(function (key, value) { var rteId = value.id; if ($.inArray(rteId, notIncludedRte) < 0) { - + var editor = _.findWhere(tinyMCE.editors, { id: rteId }); if (editor) { draggedRteSettings[rteId] = editor.settings; } - + // add all dragged's neighbouring RTEs in the new cell notIncludedRte.splice(0, 0, rteId); } }); - + // reconstruct the dragged RTE (could be undefined when dragging something else than RTE) if (draggedRteSettings !== undefined) { tinyMCE.init(draggedRteSettings); @@ -320,21 +321,22 @@ angular.module("umbraco") var title = ""; localizationService.localize("grid_insertControl").then(function (value) { title = value; - $scope.editorOverlay = { - view: "itempicker", + overlayService.open({ + view: "itempicker", filter: area.$allowedEditors.length > 15, title: title, availableItems: area.$allowedEditors, event: event, - show: true, submit: function (model) { if (model.selectedItem) { $scope.addControl(model.selectedItem, area, index); - $scope.editorOverlay.show = false; - $scope.editorOverlay = null; + overlayService.close(); } + }, + close: function () { + overlayService.close(); } - }; + }); }); }; @@ -343,7 +345,7 @@ angular.module("umbraco") // ********************************************* $scope.addTemplate = function (template) { - $scope.model.value = angular.copy(template); + $scope.model.value = Utilities.copy(template); //default row data _.forEach($scope.model.value.sections, function (section) { @@ -385,7 +387,7 @@ angular.module("umbraco") $scope.addRow = function (section, layout, isInit) { //copy the selected layout into the rows collection - var row = angular.copy(layout); + var row = Utilities.copy(layout); // Init row value row = $scope.initRow(row); @@ -402,6 +404,15 @@ angular.module("umbraco") eventsService.emit("grid.rowAdded", { scope: $scope, element: $element, row: row }); + // TODO: find a nicer way to do this without relying on setTimeout + setTimeout(function () { + var newRowEl = $element.find("[data-rowid='" + row.$uniqueId + "']"); + + if (newRowEl !== null) { + newRowEl.focus(); + } + }, 0); + }; $scope.removeRow = function (section, $index) { @@ -456,13 +467,13 @@ angular.module("umbraco") var styles, config; if (itemType === 'control') { styles = null; - config = angular.copy(gridItem.editor.config.settings); + config = Utilities.copy(gridItem.editor.config.settings); } else { - styles = _.filter(angular.copy($scope.model.config.items.styles), function (item) { return shouldApply(item, itemType, gridItem); }); - config = _.filter(angular.copy($scope.model.config.items.config), function (item) { return shouldApply(item, itemType, gridItem); }); + styles = _.filter(Utilities.copy($scope.model.config.items.styles), function (item) { return shouldApply(item, itemType, gridItem); }); + config = _.filter(Utilities.copy($scope.model.config.items.config), function (item) { return shouldApply(item, itemType, gridItem); }); } - if (angular.isObject(gridItem.config)) { + if (Utilities.isObject(gridItem.config)) { _.each(config, function (cfg) { var val = gridItem.config[cfg.key]; if (val) { @@ -471,7 +482,7 @@ angular.module("umbraco") }); } - if (angular.isObject(gridItem.styles)) { + if (Utilities.isObject(gridItem.styles)) { _.each(styles, function (style) { var val = gridItem.styles[style.key]; if (val) { @@ -728,13 +739,13 @@ angular.module("umbraco") //if nothing is found, set it to 12 if (!$scope.model.config.items.columns) { $scope.model.config.items.columns = 12; - } else if (angular.isString($scope.model.config.items.columns)) { + } else if (Utilities.isString($scope.model.config.items.columns)) { $scope.model.config.items.columns = parseInt($scope.model.config.items.columns); } 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)) { + if ($scope.model.value.name && Utilities.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 @@ -745,14 +756,14 @@ angular.module("umbraco") return t.name === $scope.model.value.name; }); - if (found && angular.isArray(found.sections) && found.sections.length === $scope.model.value.sections.length) { + if (found && Utilities.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)); + angular.extend($scope.model.value.sections[index], Utilities.copy(templateSection)); }); } @@ -824,7 +835,7 @@ angular.module("umbraco") return null; } else { //make a copy to not touch the original config - original = angular.copy(original); + original = Utilities.copy(original); original.styles = row.styles; original.config = row.config; original.hasConfig = gridItemHasConfig(row.styles, row.config); 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 e889067321..5048c479bb 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 @@ -95,7 +95,7 @@
      - + -
      +
      +
      - +
      - +
      -
      +
      @@ -249,14 +249,16 @@
      - - - - + + + Add new row + +
      @@ -264,10 +266,11 @@

      -
      + ng-click="addRow(section, layout)" + type="button">
      @@ -285,7 +288,7 @@ {{layout.label || layout.name}} -
      +
      @@ -296,11 +299,4 @@
      - - -
      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 27ea819884..f273ea63e6 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 @@ -1,296 +1,296 @@ angular.module("umbraco") .controller("Umbraco.PropertyEditors.GridPrevalueEditorController", - function ($scope, gridService, editorService) { + function ($scope, gridService, editorService) { - var emptyModel = { - styles:[ - { - label: "Set a background image", - description: "Set a row background", - key: "background-image", - view: "imagepicker", - modifier: "url({0})" - } - ], - - config:[ - { - label: "Class", - description: "Set a css class", - key: "class", - view: "textstring" - } - ], - - columns: 12, - templates:[ - { - name: "1 column layout", - sections: [ - { - grid: 12 - } - ] - }, - { - name: "2 column layout", - sections: [ - { - grid: 4 - }, - { - grid: 8 - } - ] - } - ], - - - layouts:[ - { - label: "Headline", - name: "Headline", - areas: [ - { - grid: 12, - editors: ["headline"] - } - ] - }, - { - label: "Article", - name: "Article", - areas: [ - { - grid: 4 - }, - { - grid: 8 - } - ] - } - ] - }; - - /**************** - template - *****************/ - - $scope.configureTemplate = function(template) { - - var index = $scope.model.value.templates.indexOf(template); - - if (template === undefined) { - template = { - name: "", - sections: [ - - ] - }; - } - - var layoutConfigOverlay = { - currentLayout: angular.copy(template), - rows: $scope.model.value.layouts, - columns: $scope.model.value.columns, - view: "views/propertyEditors/grid/dialogs/layoutconfig.html", - size: "small", - submit: function (model) { - if (index === -1) { - $scope.model.value.templates.push(model); - } else { - $scope.model.value.templates[index] = model; + var emptyModel = { + styles: [ + { + label: "Set a background image", + description: "Set a row background", + key: "background-image", + view: "imagepicker", + modifier: "url({0})" } - editorService.close(); - }, - close: function(model) { - editorService.close(); - } + ], + + config: [ + { + label: "Class", + description: "Set a css class", + key: "class", + view: "textstring" + } + ], + + columns: 12, + templates: [ + { + name: "1 column layout", + sections: [ + { + grid: 12 + } + ] + }, + { + name: "2 column layout", + sections: [ + { + grid: 4 + }, + { + grid: 8 + } + ] + } + ], + + + layouts: [ + { + label: "Headline", + name: "Headline", + areas: [ + { + grid: 12, + editors: ["headline"] + } + ] + }, + { + label: "Article", + name: "Article", + areas: [ + { + grid: 4 + }, + { + grid: 8 + } + ] + } + ] }; - editorService.open(layoutConfigOverlay); - - }; + /**************** + template + *****************/ - $scope.deleteTemplate = function(index){ - $scope.model.value.templates.splice(index, 1); - }; - + $scope.configureTemplate = function (template) { - /**************** - Row - *****************/ + var index = $scope.model.value.templates.indexOf(template); - $scope.configureLayout = function(layout) { + if (template === undefined) { + template = { + name: "", + sections: [ - var index = $scope.model.value.layouts.indexOf(layout); - - if(layout === undefined){ - layout = { - name: "", - areas:[ + ] + }; + } - ] + var layoutConfigOverlay = { + currentLayout: Utilities.copy(template), + rows: $scope.model.value.layouts, + columns: $scope.model.value.columns, + view: "views/propertyEditors/grid/dialogs/layoutconfig.html", + size: "small", + submit: function (model) { + if (index === -1) { + $scope.model.value.templates.push(model); + } else { + $scope.model.value.templates[index] = model; + } + editorService.close(); + }, + close: function (model) { + editorService.close(); + } }; - } - - var rowConfigOverlay = { - currentRow: angular.copy(layout), - editors: $scope.editors, - columns: $scope.model.value.columns, - view: "views/propertyEditors/grid/dialogs/rowconfig.html", - size: "small", - submit: function (model) { - if (index === -1) { - $scope.model.value.layouts.push(model); - } else { - $scope.model.value.layouts[index] = model; - } - editorService.close(); - }, - close: function(model) { - editorService.close(); - } - }; - editorService.open(rowConfigOverlay); - - }; + editorService.open(layoutConfigOverlay); - //var rowDeletesPending = false; - $scope.deleteLayout = function(index) { - - var rowDeleteOverlay = { - dialogData: { - rowName: $scope.model.value.layouts[index].name - }, - view: "views/propertyEditors/grid/dialogs/rowdeleteconfirm.html", - size: "small", - submit: function(model) { - $scope.model.value.layouts.splice(index, 1); - editorService.close(); - }, - close: function(model) { - editorService.close(); + }; + + $scope.deleteTemplate = function (index) { + $scope.model.value.templates.splice(index, 1); + }; + + + /**************** + Row + *****************/ + + $scope.configureLayout = function (layout) { + + var index = $scope.model.value.layouts.indexOf(layout); + + if (layout === undefined) { + layout = { + name: "", + areas: [ + + ] + }; + } + + var rowConfigOverlay = { + currentRow: Utilities.copy(layout), + editors: $scope.editors, + columns: $scope.model.value.columns, + view: "views/propertyEditors/grid/dialogs/rowconfig.html", + size: "small", + submit: function (model) { + if (index === -1) { + $scope.model.value.layouts.push(model); + } else { + $scope.model.value.layouts[index] = model; + } + editorService.close(); + }, + close: function (model) { + editorService.close(); + } + }; + + editorService.open(rowConfigOverlay); + + }; + + //var rowDeletesPending = false; + $scope.deleteLayout = function (index) { + + var rowDeleteOverlay = { + dialogData: { + rowName: $scope.model.value.layouts[index].name + }, + view: "views/propertyEditors/grid/dialogs/rowdeleteconfirm.html", + size: "small", + submit: function (model) { + $scope.model.value.layouts.splice(index, 1); + editorService.close(); + }, + close: function (model) { + editorService.close(); + } + }; + + editorService.open(rowDeleteOverlay); + }; + + + /**************** + utillities + *****************/ + $scope.toggleCollection = function (collection, toggle) { + if (toggle) { + collection = []; + } else { + collection = null; } }; - editorService.open(rowDeleteOverlay); - }; + $scope.percentage = function (spans) { + return ((spans / $scope.model.value.columns) * 100).toFixed(8); + }; - - /**************** - utillities - *****************/ - $scope.toggleCollection = function(collection, toggle){ - if(toggle){ - collection = []; - }else{ - collection = null; - } - }; - - $scope.percentage = function(spans){ - return ((spans / $scope.model.value.columns) * 100).toFixed(8); - }; - - $scope.zeroWidthFilter = function (cell) { + $scope.zeroWidthFilter = function (cell) { return cell.grid > 0; - }; - - /**************** - Config - *****************/ - - $scope.removeConfigValue = function(collection, index){ - collection.splice(index, 1); - }; - - var editConfigCollection = function(configValues, title, callback) { - - var editConfigCollectionOverlay = { - config: configValues, - title: title, - view: "views/propertyeditors/grid/dialogs/editconfig.html", - size: "small", - submit: function(model) { - callback(model.config); - editorService.close(); - }, - close: function(model) { - editorService.close(); - } }; - editorService.open(editConfigCollectionOverlay); - }; + /**************** + Config + *****************/ - $scope.editConfig = function() { - editConfigCollection($scope.model.value.config, "Settings", function(data) { - $scope.model.value.config = data; - }); - }; + $scope.removeConfigValue = function (collection, index) { + collection.splice(index, 1); + }; - $scope.editStyles = function() { - editConfigCollection($scope.model.value.styles, "Styling", function(data){ - $scope.model.value.styles = data; - }); - }; + var editConfigCollection = function (configValues, title, callback) { - /**************** - editors - *****************/ - gridService.getGridEditors().then(function(response){ - $scope.editors = response.data; - }); + var editConfigCollectionOverlay = { + config: configValues, + title: title, + view: "views/propertyeditors/grid/dialogs/editconfig.html", + size: "small", + submit: function (model) { + callback(model.config); + editorService.close(); + }, + close: function (model) { + editorService.close(); + } + }; + editorService.open(editConfigCollectionOverlay); + }; - /* init grid data */ - if (!$scope.model.value || $scope.model.value === "" || !$scope.model.value.templates) { - $scope.model.value = emptyModel; - } else { + $scope.editConfig = function () { + editConfigCollection($scope.model.value.config, "Settings", function (data) { + $scope.model.value.config = data; + }); + }; - if (!$scope.model.value.columns) { - $scope.model.value.columns = emptyModel.columns; - } + $scope.editStyles = function () { + editConfigCollection($scope.model.value.styles, "Styling", function (data) { + $scope.model.value.styles = data; + }); + }; - - if (!$scope.model.value.config) { - $scope.model.value.config = []; - } - - if (!$scope.model.value.styles) { - $scope.model.value.styles = []; - } - } - - /**************** - Clean up - *****************/ - var unsubscribe = $scope.$on("formSubmitting", function (ev, args) { - var ts = $scope.model.value.templates; - var ls = $scope.model.value.layouts; - - _.each(ts, function(t){ - _.each(t.sections, function(section, index){ - if(section.grid === 0){ - t.sections.splice(index, 1); - } - }); + /**************** + editors + *****************/ + gridService.getGridEditors().then(function (response) { + $scope.editors = response.data; }); - _.each(ls, function(l){ - _.each(l.areas, function(area, index){ - if(area.grid === 0){ - l.areas.splice(index, 1); - } - }); + + /* init grid data */ + if (!$scope.model.value || $scope.model.value === "" || !$scope.model.value.templates) { + $scope.model.value = emptyModel; + } else { + + if (!$scope.model.value.columns) { + $scope.model.value.columns = emptyModel.columns; + } + + + if (!$scope.model.value.config) { + $scope.model.value.config = []; + } + + if (!$scope.model.value.styles) { + $scope.model.value.styles = []; + } + } + + /**************** + Clean up + *****************/ + var unsubscribe = $scope.$on("formSubmitting", function (ev, args) { + var ts = $scope.model.value.templates; + var ls = $scope.model.value.layouts; + + _.each(ts, function (t) { + _.each(t.sections, function (section, index) { + if (section.grid === 0) { + t.sections.splice(index, 1); + } + }); + }); + + _.each(ls, function (l) { + _.each(l.areas, function (area, index) { + if (area.grid === 0) { + l.areas.splice(index, 1); + } + }); + }); }); - }); - //when the scope is destroyed we need to unsubscribe - $scope.$on('$destroy', function () { - unsubscribe(); - }); + //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.prevalues.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.prevalues.html index 92d1a9ef26..34a59cabf1 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 @@ -1,9 +1,15 @@
      -
      +
      -

      -

      +

      + + Grid Layouts + +

      +

      + Layouts are the overall work area for the grid editor, usually you only need one or two different layouts +

        + class="preview-rows layout">
        {{template.name}}
        - - +
      -
      -
      +
      -

      -

      +

      + Row Configurations +

      +

      + Rows are predefined cells arranged horizontally +

        + class="preview-rows columns">
        -

        {{area.maxItems}}

        +

        {{area.maxItems}}

        @@ -74,17 +84,18 @@
        {{layout.label || layout.name}}
        - - - - +
      -
      @@ -93,7 +104,7 @@
      -
      +
      @@ -108,22 +119,21 @@ ng-model="model.value.config">
    • - + - - +
      -
    • - - - - - +
    • +
    @@ -137,22 +147,22 @@ ng-model="model.value.styles">
  • - + - - +
    -
  • - - - - +
  • +
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.controller.js index b5ae731c94..30715dfd5e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.controller.js @@ -1,218 +1,223 @@ angular.module('umbraco') .controller("Umbraco.PropertyEditors.ImageCropperController", - function ($scope, fileManager, $timeout) { + function ($scope, fileManager, $timeout) { - var config = angular.copy($scope.model.config); + var config = Utilities.copy($scope.model.config); - $scope.filesSelected = onFileSelected; - $scope.filesChanged = onFilesChanged; - $scope.fileUploaderInit = onFileUploaderInit; - $scope.imageLoaded = imageLoaded; - $scope.crop = crop; - $scope.done = done; - $scope.clear = clear; - $scope.reset = reset; - $scope.close = close; - $scope.focalPointChanged = focalPointChanged; - //declare a special method which will be called whenever the value has changed from the server - $scope.model.onValueChanged = onValueChanged; + $scope.filesSelected = onFileSelected; + $scope.filesChanged = onFilesChanged; + $scope.fileUploaderInit = onFileUploaderInit; + $scope.imageLoaded = imageLoaded; + $scope.crop = crop; + $scope.done = done; + $scope.clear = clear; + $scope.reset = reset; + $scope.close = close; + $scope.isCustomCrop = isCustomCrop; + $scope.focalPointChanged = focalPointChanged; + //declare a special method which will be called whenever the value has changed from the server + $scope.model.onValueChanged = onValueChanged; - /** - * Called when the umgImageGravity component updates the focal point value - * @param {any} left - * @param {any} top - */ - function focalPointChanged(left, top) { - //update the model focalpoint value - $scope.model.value.focalPoint = { - left: left, - top: top + /** + * Called when the umgImageGravity component updates the focal point value + * @param {any} left + * @param {any} top + */ + function focalPointChanged(left, top) { + //update the model focalpoint value + $scope.model.value.focalPoint = { + left: left, + top: top + }; + + //set form to dirty to track changes + $scope.imageCropperForm.$setDirty(); + } + + /** + * Used to assign a new model value + * @param {any} src + */ + function setModelValueWithSrc(src) { + if (!$scope.model.value || !$scope.model.value.src) { + //we are copying to not overwrite the original config + $scope.model.value = angular.extend(Utilities.copy($scope.model.config), { src: src }); + } + } + + /** + * called whenever the value has changed from the server + * @param {any} newVal + * @param {any} oldVal + */ + function onValueChanged(newVal, oldVal) { + //clear current uploaded files + fileManager.setFiles({ + propertyAlias: $scope.model.alias, + culture: $scope.model.culture, + files: [] + }); + } + + /** + * Called when the a new file is selected + * @param {any} value + */ + function onFileSelected(value, files) { + setModelValueWithSrc(value); + //set form to dirty to track changes + $scope.imageCropperForm.$setDirty(); + } + + function imageLoaded(isCroppable, hasDimensions) { + $scope.isCroppable = isCroppable; + $scope.hasDimensions = hasDimensions; }; - //set form to dirty to track changes - $scope.imageCropperForm.$setDirty(); - } - - /** - * Used to assign a new model value - * @param {any} src - */ - function setModelValueWithSrc(src) { - if (!$scope.model.value || !$scope.model.value.src) { - //we are copying to not overwrite the original config - $scope.model.value = angular.extend(angular.copy($scope.model.config), { src: src }); - } - } - - /** - * called whenever the value has changed from the server - * @param {any} newVal - * @param {any} oldVal - */ - function onValueChanged(newVal, oldVal) { - //clear current uploaded files - fileManager.setFiles({ - propertyAlias: $scope.model.alias, - culture: $scope.model.culture, - files: [] - }); - } - - /** - * Called when the a new file is selected - * @param {any} value - */ - function onFileSelected(value, files) { - setModelValueWithSrc(value); - //set form to dirty to track changes - $scope.imageCropperForm.$setDirty(); - } - - function imageLoaded (isCroppable, hasDimensions) { - $scope.isCroppable = isCroppable; - $scope.hasDimensions = hasDimensions; - }; - - /** - * Called when the file collection changes - * @param {any} value - * @param {any} files - */ - function onFilesChanged(files) { - if (files && files[0]) { - $scope.imageSrc = files[0].fileSrc; - //set form to dirty to track changes - $scope.imageCropperForm.$setDirty(); - } - } - - /** - * Called when the file uploader initializes - * @param {any} value - */ - function onFileUploaderInit(value, files) { - //move previously saved value to the editor - if ($scope.model.value) { - //backwards compat with the old file upload (incase some-one swaps them..) - if (angular.isString($scope.model.value)) { - setModelValueWithSrc($scope.model.value); - } - else { - //sync any config changes with the editor and drop outdated crops - _.each($scope.model.value.crops, function (saved) { - var configured = _.find(config.crops, function (item) { return item.alias === saved.alias }); - - if (configured && configured.height === saved.height && configured.width === saved.width) { - configured.coordinates = saved.coordinates; - } - }); - $scope.model.value.crops = config.crops; - - //restore focalpoint if missing - if (!$scope.model.value.focalPoint) { - $scope.model.value.focalPoint = { left: 0.5, top: 0.5 }; - } - } - - //if there are already files in the client assigned then set the src + /** + * Called when the file collection changes + * @param {any} value + * @param {any} files + */ + function onFilesChanged(files) { if (files && files[0]) { $scope.imageSrc = files[0].fileSrc; + //set form to dirty to track changes + $scope.imageCropperForm.$setDirty(); + } + } + + /** + * Called when the file uploader initializes + * @param {any} value + */ + function onFileUploaderInit(value, files) { + //move previously saved value to the editor + if ($scope.model.value) { + //backwards compat with the old file upload (incase some-one swaps them..) + if (Utilities.isString($scope.model.value)) { + setModelValueWithSrc($scope.model.value); + } + else { + //sync any config changes with the editor and drop outdated crops + _.each($scope.model.value.crops, function (saved) { + var configured = _.find(config.crops, function (item) { return item.alias === saved.alias }); + + if (configured && configured.height === saved.height && configured.width === saved.width) { + configured.coordinates = saved.coordinates; + } + }); + $scope.model.value.crops = config.crops; + + //restore focalpoint if missing + if (!$scope.model.value.focalPoint) { + $scope.model.value.focalPoint = { left: 0.5, top: 0.5 }; + } + } + + //if there are already files in the client assigned then set the src + if (files && files[0]) { + $scope.imageSrc = files[0].fileSrc; + } + else { + $scope.imageSrc = $scope.model.value.src; + } + + } + } + + /** + * crop a specific crop + * @param {any} targetCrop + */ + function crop(targetCrop) { + if (!$scope.currentCrop) { + // clone the crop so we can discard the changes + $scope.currentCrop = Utilities.copy(targetCrop); + $scope.currentPoint = null; + + //set form to dirty to track changes + $scope.imageCropperForm.$setDirty(); } else { - $scope.imageSrc = $scope.model.value.src; - } - - } - } + // we have a crop open already - close the crop (this will discard any changes made) + close(); - /** - * crop a specific crop - * @param {any} targetCrop - */ - function crop(targetCrop) { - if (!$scope.currentCrop) { - // clone the crop so we can discard the changes - $scope.currentCrop = angular.copy(targetCrop); - $scope.currentPoint = null; + // the crop editor needs a digest cycle to close down properly, otherwise its state + // is reused for the new crop... and that's really bad + $timeout(function () { + crop(targetCrop); + $scope.pendingCrop = false; + }); + + // this is necessary to keep the screen from flickering too badly while we wait for the new crop to open + // - check the view for its usage (basically it makes sure we keep the space reserved for the new crop) + $scope.pendingCrop = true; + } + }; + + /** done cropping */ + function done() { + if (!$scope.currentCrop) { + return; + } + // find the original crop by crop alias and update its coordinates + var editedCrop = _.find($scope.model.value.crops, crop => crop.alias === $scope.currentCrop.alias); + editedCrop.coordinates = $scope.currentCrop.coordinates; + $scope.close(); //set form to dirty to track changes $scope.imageCropperForm.$setDirty(); - } - else { - // we have a crop open already - close the crop (this will discard any changes made) - close(); + }; - // the crop editor needs a digest cycle to close down properly, otherwise its state - // is reused for the new crop... and that's really bad - $timeout(function () { - crop(targetCrop); - $scope.pendingCrop = false; + function reset() { + $scope.currentCrop.coordinates = undefined; + $scope.done(); + } + + function close() { + $scope.currentCrop = undefined; + $scope.currentPoint = undefined; + } + + /** + * crop a specific crop + * @param {any} crop + */ + function clear(crop) { + //clear current uploaded files + fileManager.setFiles({ + propertyAlias: $scope.model.alias, + culture: $scope.model.culture, + files: [] }); - // this is necessary to keep the screen from flickering too badly while we wait for the new crop to open - // - check the view for its usage (basically it makes sure we keep the space reserved for the new crop) - $scope.pendingCrop = true; + //clear the ui + $scope.imageSrc = null; + if ($scope.model.value) { + $scope.model.value = null; + } + + //set form to dirty to track changes + $scope.imageCropperForm.$setDirty(); + }; + + function isCustomCrop(crop) { + return !!crop.coordinates; } - }; - /** done cropping */ - function done() { - if (!$scope.currentCrop) { - return; - } - // find the original crop by crop alias and update its coordinates - var editedCrop = _.find($scope.model.value.crops, crop => crop.alias === $scope.currentCrop.alias); - editedCrop.coordinates = $scope.currentCrop.coordinates; - $scope.close(); - - //set form to dirty to track changes - $scope.imageCropperForm.$setDirty(); - }; - - function reset() { - $scope.currentCrop.coordinates = undefined; - $scope.done(); - } - - function close() { - $scope.currentCrop = undefined; - $scope.currentPoint = undefined; - } - - /** - * crop a specific crop - * @param {any} crop - */ - function clear(crop) { - //clear current uploaded files - fileManager.setFiles({ - propertyAlias: $scope.model.alias, - culture: $scope.model.culture, - files: [] + var unsubscribe = $scope.$on("formSubmitting", function () { + $scope.currentCrop = null; + $scope.currentPoint = null; }); - //clear the ui - $scope.imageSrc = null; - if ($scope.model.value) { - $scope.model.value = null; - } - - //set form to dirty to track changes - $scope.imageCropperForm.$setDirty(); - }; - - var unsubscribe = $scope.$on("formSubmitting", function () { - $scope.currentCrop = null; - $scope.currentPoint = null; - }); - - $scope.$on('$destroy', function () { - unsubscribe(); - }); - }) + $scope.$on('$destroy', function () { + unsubscribe(); + }); + }) .run(function (mediaHelper, umbRequestHelper) { if (mediaHelper && mediaHelper.registerFileResolver) { - + //NOTE: The 'entity' can be either a normal media entity or an "entity" returned from the entityResource // they contain different data structures so if we need to query against it we need to be aware of this. mediaHelper.registerFileResolver("Umbraco.ImageCropper", function (property, entity, thumbnail) { @@ -227,7 +232,7 @@ angular.module('umbraco') //this is a fallback in case the cropper has been asssigned a upload field } - else if (angular.isString(property.value)) { + else if (Utilities.isString(property.value)) { if (thumbnail) { if (mediaHelper.detectIfImageByExtension(property.value)) { 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 3164a6964c..84c696ab2e 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 @@ -60,6 +60,7 @@
{{value.alias}} {{value.width}}px x {{value.height}}px + User defined 
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.prevalues.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.prevalues.controller.js index de56442239..79ed11aef8 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.prevalues.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.prevalues.controller.js @@ -47,7 +47,7 @@ angular.module("umbraco").controller("Umbraco.PrevalueEditors.CropSizesControlle $scope.setFocus = true; if ($scope.newItem && $scope.newItem.alias && - angular.isNumber($scope.newItem.width) && angular.isNumber($scope.newItem.height) && + Utilities.isNumber($scope.newItem.width) && Utilities.isNumber($scope.newItem.height) && $scope.newItem.width > 0 && $scope.newItem.height > 0) { var exists = _.find($scope.model.value, function (item) { return $scope.newItem.alias === item.alias; }); 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 fe83a4d451..16c1be98a0 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 @@ -276,6 +276,9 @@ function listViewController($scope, $interpolate, $routeParams, $injector, $time } $scope.reloadView = function (id, reloadActiveNode) { + if (!id) { + return; + } $scope.viewLoaded = false; $scope.folders = []; @@ -379,7 +382,7 @@ function listViewController($scope, $interpolate, $routeParams, $injector, $time return 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))) + if (!(result.data && Utilities.isArray(result.data.notifications))) showNotificationsAndReset(result, true, getSuccessMsg(selected.length)); }); } @@ -620,7 +623,7 @@ function listViewController($scope, $interpolate, $routeParams, $injector, $time currentNode: $scope.contentId, submit: function (model) { if (model.target) { - performCopy(model.target, model.relateToOriginal); + performCopy(model.target, model.relateToOriginal, model.includeDescendants); } editorService.close(); }, @@ -631,9 +634,9 @@ function listViewController($scope, $interpolate, $routeParams, $injector, $time editorService.copy(copyEditor); }; - function performCopy(target, relateToOriginal) { + function performCopy(target, relateToOriginal, includeDescendants) { applySelected( - function (selected, index) { return contentResource.copy({ parentId: target.id, id: getIdCallback(selected[index]), relateToOriginal: relateToOriginal }); }, + function (selected, index) { return contentResource.copy({ parentId: target.id, id: getIdCallback(selected[index]), relateToOriginal: relateToOriginal, recursive: includeDescendants }); }, function (count, total) { var key = (total === 1 ? "bulk_copiedItemOfItem" : "bulk_copiedItemOfItems"); return localizationService.localize(key, [count, total]); @@ -706,52 +709,19 @@ function listViewController($scope, $interpolate, $routeParams, $injector, $time } function isDate(val) { - if (angular.isString(val)) { + if (Utilities.isString(val)) { return val.match(/^(\d{4})\-(\d{2})\-(\d{2})\ (\d{2})\:(\d{2})\:(\d{2})$/); } return false; } function initView() { - //default to root id if the id is undefined var id = $routeParams.id; if (id === undefined) { - id = -1; + // no ID found in route params - don't list anything as we don't know for sure where we are + return; } - getContentTypesCallback(id).then(function (listViewAllowedTypes) { - $scope.listViewAllowedTypes = listViewAllowedTypes; - - var blueprints = false; - _.each(listViewAllowedTypes, function (allowedType) { - if (_.isEmpty(allowedType.blueprints)) { - // this helps the view understand that there are no blueprints available - allowedType.blueprints = null; - } - else { - blueprints = true; - // turn the content type blueprints object into an array of sortable objects for the view - allowedType.blueprints = _.map(_.pairs(allowedType.blueprints || {}), function (pair) { - return { - id: pair[0], - name: pair[1] - }; - }); - } - }); - - if (listViewAllowedTypes.length === 1 && blueprints === false) { - $scope.createAllowedButtonSingle = true; - } - if (listViewAllowedTypes.length === 1 && blueprints === true) { - $scope.createAllowedButtonSingleWithBlueprints = true; - } - if (listViewAllowedTypes.length > 1) { - $scope.createAllowedButtonMultiWithBlueprints = true; - } - }); - - $scope.contentId = id; $scope.isTrashed = editorState.current ? editorState.current.trashed : id === "-20" || id === "-21"; @@ -765,6 +735,40 @@ function listViewController($scope, $interpolate, $routeParams, $injector, $time $scope.options.allowBulkMove || $scope.options.allowBulkDelete; + if ($scope.isTrashed === false) { + getContentTypesCallback(id).then(function (listViewAllowedTypes) { + $scope.listViewAllowedTypes = listViewAllowedTypes; + + var blueprints = false; + _.each(listViewAllowedTypes, function (allowedType) { + if (_.isEmpty(allowedType.blueprints)) { + // this helps the view understand that there are no blueprints available + allowedType.blueprints = null; + } + else { + blueprints = true; + // turn the content type blueprints object into an array of sortable objects for the view + allowedType.blueprints = _.map(_.pairs(allowedType.blueprints || {}), function (pair) { + return { + id: pair[0], + name: pair[1] + }; + }); + } + }); + + if (listViewAllowedTypes.length === 1 && blueprints === false) { + $scope.createAllowedButtonSingle = true; + } + if (listViewAllowedTypes.length === 1 && blueprints === true) { + $scope.createAllowedButtonSingleWithBlueprints = true; + } + if (listViewAllowedTypes.length > 1) { + $scope.createAllowedButtonMultiWithBlueprints = true; + } + }); + } + $scope.reloadView($scope.contentId); } 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 6549c08b17..ee1847b430 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 @@ -21,11 +21,11 @@
- @@ -46,7 +46,7 @@
- @@ -70,21 +70,21 @@ diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/orderDirection.prevalues.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/orderDirection.prevalues.html index 83a905ccf7..7ec0895936 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/orderDirection.prevalues.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/orderDirection.prevalues.html @@ -1,10 +1,16 @@ 
- - +
    +
  • + +
  • +
  • + +
  • +
- Required + Required
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 f05b1e31d8..bb87f0463d 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 @@ -27,6 +27,20 @@ function MarkdownEditorController($scope, $element, assetsService, editorService editorService.mediaPicker(mediaPicker); } + function openLinkPicker(callback) { + var linkPicker = { + hideTarget: true, + submit: function(model) { + callback(model.target.url, model.target.name); + editorService.close(); + }, + close: function() { + editorService.close(); + } + }; + editorService.linkPicker(linkPicker); + } + assetsService .load([ "lib/markdown/markdown.converter.js", @@ -53,6 +67,12 @@ function MarkdownEditorController($scope, $element, assetsService, editorService return true; // tell the editor that we'll take care of getting the image url }); + //subscribe to the link dialog clicks + editor2.hooks.set("insertLinkDialog", function (callback) { + openLinkPicker(callback); + return true; // tell the editor that we'll take care of getting the link url + }); + editor2.hooks.set("onPreviewRefresh", function () { // We must manually update the model as there is no way to hook into the markdown editor events without exstensive edits to the library. if ($scope.model.value !== $("textarea", $element).val()) { 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 8c6194d638..340e6865ef 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 @@ -48,49 +48,43 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl // This is done by remapping the int/guid ids into a new array of items, where we create "Deleted item" placeholders // when there is no match for a selected id. This will ensure that the values being set on save, are the same as before. - medias = _.map(ids, - function (id) { - var found = _.find(medias, - function (m) { - // We could use coercion (two ='s) here .. but not sure if this works equally well in all browsers and - // it's prone to someone "fixing" it at some point without knowing the effects. Rather use toString() - // compares and be completely sure it works. - return m.udi.toString() === id.toString() || m.id.toString() === id.toString(); - }); - if (found) { - return found; - } else { - return { - name: vm.labels.deletedItem, - id: $scope.model.config.idType !== "udi" ? id : null, - udi: $scope.model.config.idType === "udi" ? id : null, - icon: "icon-picture", - thumbnail: null, - trashed: true - }; - } - }); + medias = ids.map(id => { + // We could use coercion (two ='s) here .. but not sure if this works equally well in all browsers and + // it's prone to someone "fixing" it at some point without knowing the effects. Rather use toString() + // compares and be completely sure it works. + var found = medias.find(m => m.udi.toString() === id.toString() || m.id.toString() === id.toString()); + if (found) { + return found; + } else { + return { + name: vm.labels.deletedItem, + id: $scope.model.config.idType !== "udi" ? id : null, + udi: $scope.model.config.idType === "udi" ? id : null, + icon: "icon-picture", + thumbnail: null, + trashed: true + }; + } + }); - _.each(medias, - function (media, i) { + medias.forEach(media => { + if (!media.extension && media.id && media.metaData) { + media.extension = mediaHelper.getFileExtension(media.metaData.MediaPath); + } - if (!media.extension && media.id && media.metaData) { - media.extension = mediaHelper.getFileExtension(media.metaData.MediaPath); - } + // if there is no thumbnail, try getting one if the media is not a placeholder item + if (!media.thumbnail && media.id && media.metaData) { + media.thumbnail = mediaHelper.resolveFileFromEntity(media, true); + } - // if there is no thumbnail, try getting one if the media is not a placeholder item - if (!media.thumbnail && media.id && media.metaData) { - media.thumbnail = mediaHelper.resolveFileFromEntity(media, true); - } + $scope.mediaItems.push(media); - $scope.mediaItems.push(media); - - if ($scope.model.config.idType === "udi") { - $scope.ids.push(media.udi); - } else { - $scope.ids.push(media.id); - } - }); + if ($scope.model.config.idType === "udi") { + $scope.ids.push(media.udi); + } else { + $scope.ids.push(media.id); + } + }); sync(); }); @@ -100,7 +94,7 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl function sync() { $scope.model.value = $scope.ids.join(); removeAllEntriesAction.isDisabled = $scope.ids.length === 0; - }; + } function setDirty() { angularHelper.getCurrentForm($scope).$setDirty(); @@ -111,18 +105,17 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl // reload. We only reload the images that is already picked but has been updated. // We have to get the entities from the server because the media // can be edited without being selected - _.each($scope.images, - function (image, i) { - if (updatedMediaNodes.indexOf(image.udi) !== -1) { - image.loading = true; - entityResource.getById(image.udi, "media") - .then(function (mediaEntity) { - angular.extend(image, mediaEntity); - image.thumbnail = mediaHelper.resolveFileFromEntity(image, true); - image.loading = false; - }); - } - }); + $scope.mediaItems.forEach(media => { + if (updatedMediaNodes.indexOf(media.udi) !== -1) { + media.loading = true; + entityResource.getById(media.udi, "Media") + .then(function (mediaEntity) { + angular.extend(media, mediaEntity); + media.thumbnail = mediaHelper.resolveFileFromEntity(media, true); + media.loading = false; + }); + } + }); } function init() { @@ -177,20 +170,20 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl // the media picker is using media entities so we get the // entity so we easily can format it for use in the media grid if (model && model.mediaNode) { - entityResource.getById(model.mediaNode.id, "media") + entityResource.getById(model.mediaNode.id, "Media") .then(function (mediaEntity) { // if an image is selecting more than once // we need to update all the media items - angular.forEach($scope.images, function (image) { - if (image.id === model.mediaNode.id) { - angular.extend(image, mediaEntity); - image.thumbnail = mediaHelper.resolveFileFromEntity(image, true); + $scope.mediaItems.forEach(media => { + if (media.id === model.mediaNode.id) { + angular.extend(media, mediaEntity); + media.thumbnail = mediaHelper.resolveFileFromEntity(media, true); } }); }); } }, - close: function (model) { + close: function () { editorService.close(); } }; @@ -210,7 +203,7 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl editorService.close(); - _.each(model.selection, function (media, i) { + model.selection.forEach(media => { // if there is no thumbnail, try getting one if the media is not a placeholder item if (!media.thumbnail && media.id && media.metaData) { media.thumbnail = mediaHelper.resolveFileFromEntity(media, true); @@ -280,16 +273,14 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl disabled: !multiPicker, items: "li:not(.add-wrapper)", cancel: ".unsortable", - update: function (e, ui) { + update: function () { setDirty(); $timeout(function() { // TODO: Instead of doing this with a timeout would be better to use a watch like we do in the // content picker. Then we don't have to worry about setting ids, render models, models, we just set one and let the // watch do all the rest. - $scope.ids = _.map($scope.mediaItems, - function (item) { - return $scope.model.config.idType === "udi" ? item.udi : item.id; - }); + $scope.ids = $scope.mediaItems.map(media => $scope.model.config.idType === "udi" ? media.udi : media.id); + sync(); }); } 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 b17906272d..c4dba4d373 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 @@ -36,16 +36,16 @@
- -
  • -
  • 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 c3acf020b8..95e595a97a 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 @@ -20,7 +20,7 @@ function memberPickerController($scope, entityResource, iconHelper, angularHelpe }, filterCssClass: "not-allowed", callback: function(data) { - if (angular.isArray(data)) { + if (Utilities.isArray(data)) { _.each(data, function (item, i) { $scope.add(item); }); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js index 6b7d9dd7ae..172f9b2249 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js @@ -82,6 +82,7 @@ function multiUrlPickerController($scope, angularHelper, localizationService, en currentTarget: target, dataTypeKey: $scope.model.dataTypeKey, ignoreUserStartNodes : ($scope.model.config && $scope.model.config.ignoreUserStartNodes) ? $scope.model.config.ignoreUserStartNodes : "0", + hideAnchor: $scope.model.config && $scope.model.config.hideAnchor ? true : false, submit: function (model) { if (model.target.url || model.target.anchor) { // if an anchor exists, check that it is appropriately prefixed @@ -143,6 +144,16 @@ function multiUrlPickerController($scope, angularHelper, localizationService, en if ($scope.model.validation && $scope.model.validation.mandatory && !$scope.model.config.minNumber) { $scope.model.config.minNumber = 1; } + + _.each($scope.model.value, function (item){ + // we must reload the "document" link URLs to match the current editor culture + if (item.udi && item.udi.indexOf("/document/") > 0) { + item.url = null; + entityResource.getUrlByUdi(item.udi).then(function (data) { + item.url = data; + }); + } + }); } init(); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js index 84069acfcf..7cad5a5f05 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js @@ -14,7 +14,7 @@ }); function NestedContentController($scope, $interpolate, $filter, $timeout, contentResource, localizationService, iconHelper, clipboardService, eventsService, overlayService, $routeParams, editorState) { - + var vm = this; var model = $scope.$parent.$parent.model; @@ -41,7 +41,7 @@ if (vm.maxItems === 0) vm.maxItems = 1000; - vm.singleMode = vm.minItems === 1 && vm.maxItems === 1; + vm.singleMode = vm.minItems === 1 && vm.maxItems === 1 && model.config.contentTypes.length === 1;; vm.showIcons = Object.toBoolean(model.config.showIcons); vm.wideMode = Object.toBoolean(model.config.hideLabel); vm.hasContentTypes = model.config.contentTypes.length > 0; @@ -58,9 +58,9 @@ updateModel(); vm.currentNode = node; } - - var copyAllEntries = function() { - + + var copyAllEntries = function () { + syncCurrentNode(); // list aliases @@ -68,14 +68,14 @@ // remove dublicates aliases = aliases.filter((item, index) => aliases.indexOf(item) === index); - + var nodeName = ""; - if(vm.umbVariantContent) { + if (vm.umbVariantContent) { nodeName = vm.umbVariantContent.editor.content.name; } - localizationService.localize("clipboard_labelForArrayOfItemsFrom", [model.label, nodeName]).then(function(data) { + localizationService.localize("clipboard_labelForArrayOfItemsFrom", [model.label, nodeName]).then(function (data) { clipboardService.copyArray("elementTypeArray", aliases, vm.nodes, data, "icon-thumbnail-list", model.id); }); } @@ -131,6 +131,7 @@ setCurrentNode(newNode); setDirty(); + validate(); }; vm.openNodeTypePicker = function ($event) { @@ -145,7 +146,7 @@ orderBy: "$index", view: "itempicker", event: $event, - clickPasteItem: function(item) { + clickPasteItem: function (item) { if (item.type === "elementTypeArray") { _.each(item.data, function (entry) { pasteFromClipboard(entry); @@ -182,9 +183,9 @@ if (vm.overlayMenu.availableItems.length === 0) { return; } - + vm.overlayMenu.size = vm.overlayMenu.availableItems.length > 6 ? "medium" : "small"; - + vm.overlayMenu.pasteItems = []; var singleEntriesForPaste = clipboardService.retriveEntriesOfType("elementType", contentTypeAliases); @@ -196,7 +197,7 @@ icon: entry.icon }); }); - + var arrayEntriesForPaste = clipboardService.retriveEntriesOfType("elementTypeArray", contentTypeAliases); _.each(arrayEntriesForPaste, function (entry) { vm.overlayMenu.pasteItems.push({ @@ -222,6 +223,7 @@ if (vm.overlayMenu.availableItems.length === 1 && vm.overlayMenu.pasteItems.length === 0) { // only one scaffold type - no need to display the picker addNode(vm.scaffolds[0].contentTypeAlias); + vm.overlayMenu = null; return; } @@ -236,12 +238,22 @@ } }; + vm.canDeleteNode = function (idx) { + return (vm.nodes.length > vm.minItems) + ? true + : model.config.contentTypes.length > 1; + } + function deleteNode(idx) { vm.nodes.splice(idx, 1); setDirty(); updateModel(); + validate(); }; vm.requestDeleteNode = function (idx) { + if (!vm.canDeleteNode(idx)) { + return; + } if (model.config.confirmDeletes === true) { localizationService.localizeMany(["content_nestedContentDeleteItem", "general_delete", "general_cancel", "contentTypeEditor_yesDelete"]).then(function (data) { const overlay = { @@ -267,6 +279,9 @@ }; vm.getName = function (idx) { + if (!model.value || !model.value.length) { + return ""; + } var name = ""; @@ -316,6 +331,10 @@ }; vm.getIcon = function (idx) { + if (!model.value || !model.value.length) { + return ""; + } + var scaffold = getScaffold(model.value[idx].ncContentTypeAlias); return scaffold && scaffold.icon ? iconHelper.convertFromLegacyIcon(scaffold.icon) : "icon-folder"; } @@ -376,10 +395,10 @@ clipboardService.copy("elementType", node.contentTypeAlias, node); $event.stopPropagation(); } - - + + function pasteFromClipboard(newNode) { - + if (newNode === undefined) { return; } @@ -390,7 +409,7 @@ vm.nodes.push(newNode); setDirty(); //updateModel();// done by setting current node... - + setCurrentNode(newNode); } @@ -470,11 +489,13 @@ } } - // Auto-fill with elementTypes, but only if we have one type to choose from, and if this property is empty. - if (vm.singleMode === true && vm.nodes.length === 0 && model.config.minItems > 0) { + // Enforce min items if we only have one scaffold type + var modelWasChanged = false; + if (vm.nodes.length < vm.minItems && vm.scaffolds.length === 1) { for (var i = vm.nodes.length; i < model.config.minItems; i++) { addNode(vm.scaffolds[0].contentTypeAlias); } + modelWasChanged = true; } // If there is only one item, set it as current node @@ -482,15 +503,21 @@ setCurrentNode(vm.nodes[0]); } + validate(); + vm.inited = true; + if (modelWasChanged) { + updateModel(); + } + updatePropertyActionStates(); checkAbilityToPasteContent(); } } function createNode(scaffold, fromNcEntry) { - var node = angular.copy(scaffold); + var node = Utilities.copy(scaffold); node.key = fromNcEntry && fromNcEntry.key ? fromNcEntry.key : String.CreateGuid(); @@ -566,17 +593,17 @@ } function updatePropertyActionStates() { - copyAllEntriesAction.isDisabled = !model.value || model.value.length === 0; - removeAllEntriesAction.isDisabled = !model.value || model.value.length === 0; + copyAllEntriesAction.isDisabled = !model.value || !model.value.length; + removeAllEntriesAction.isDisabled = copyAllEntriesAction.isDisabled; } - + var propertyActions = [ copyAllEntriesAction, removeAllEntriesAction ]; - + this.$onInit = function () { if (this.umbProperty) { this.umbProperty.setPropertyActions(propertyActions); @@ -587,25 +614,28 @@ updateModel(); }); + var validate = function () { + if (vm.nodes.length < vm.minItems) { + $scope.nestedContentForm.minCount.$setValidity("minCount", false); + } + else { + $scope.nestedContentForm.minCount.$setValidity("minCount", true); + } + + if (vm.nodes.length > vm.maxItems) { + $scope.nestedContentForm.maxCount.$setValidity("maxCount", false); + } + else { + $scope.nestedContentForm.maxCount.$setValidity("maxCount", true); + } + } + var watcher = $scope.$watch( function () { return vm.nodes.length; }, function () { - //Validate! - if (vm.nodes.length < vm.minItems) { - $scope.nestedContentForm.minCount.$setValidity("minCount", false); - } - else { - $scope.nestedContentForm.minCount.$setValidity("minCount", true); - } - - if (vm.nodes.length > vm.maxItems) { - $scope.nestedContentForm.maxCount.$setValidity("maxCount", false); - } - else { - $scope.nestedContentForm.maxCount.$setValidity("maxCount", true); - } + validate(); } ); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.doctypepicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.doctypepicker.html index c6860140a5..f62894e043 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.doctypepicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.doctypepicker.html @@ -56,11 +56,13 @@

    Group:
    - Select the group whose properties should be displayed. If left blank, the first group on the element type will be used. + Select the group whose properties should be displayed. If left blank, the first group on the element type will be used.

    Template:
    - Enter an angular expression to evaluate against each item for its name. Use {{$index}} to display the item index + Enter an angular expression to evaluate against each item for its name. Use + {{$index}} + to display the item index

    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.propertyeditor.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.propertyeditor.html index b3821cff3d..da6e466b50 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.propertyeditor.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.propertyeditor.html @@ -2,13 +2,13 @@ - +
    -
    +
    @@ -17,7 +17,7 @@ {{vm.labels.copy_icon_title}} -
    - diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/radiobuttons/radiobuttons.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/radiobuttons/radiobuttons.controller.js index 2db7eaf562..6bfde10e9c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/radiobuttons/radiobuttons.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/radiobuttons/radiobuttons.controller.js @@ -8,7 +8,7 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.RadioButtonsContro function init() { //we can't really do anything if the config isn't an object - if (angular.isObject($scope.model.config.items)) { + if (Utilities.isObject($scope.model.config.items)) { // formatting the items in the dictionary into an array var sortedItems = []; 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 6a5a76b800..e642051733 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 @@ -80,7 +80,7 @@ }; $scope.add = function ($event) { - if (!angular.isArray($scope.model.value)) { + if (!Utilities.isArray($scope.model.value)) { $scope.model.value = []; } 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 748d8da1a4..74a70118eb 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 @@ -12,7 +12,7 @@ angular.module("umbraco") $scope.textAreaHtmlId = $scope.model.alias + "_" + String.CreateGuid(); var editorConfig = $scope.model.config ? $scope.model.config.editor : null; - if (!editorConfig || angular.isString(editorConfig)) { + if (!editorConfig || Utilities.isString(editorConfig)) { editorConfig = tinyMceService.defaultPrevalues(); } //make sure there's a max image size diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.controller.js index 41097f9e9a..33e2b834f3 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.controller.js @@ -3,7 +3,7 @@ angular.module("umbraco").controller("Umbraco.PrevalueEditors.RteController", var cfg = tinyMceService.defaultPrevalues(); if($scope.model.value){ - if(angular.isString($scope.model.value)){ + if(Utilities.isString($scope.model.value)){ $scope.model.value = cfg; } }else{ @@ -39,8 +39,14 @@ angular.module("umbraco").controller("Umbraco.PrevalueEditors.RteController", stylesheetResource.getAll().then(function(stylesheets){ $scope.stylesheets = stylesheets; - - _.each($scope.stylesheets, function (stylesheet) { + + // if the CSS directory changes, previously assigned stylesheets are retained, but will not be visible + // and will throw a 404 when loading the RTE. Remove them here. Still needs to be saved... + let cssPath = Umbraco.Sys.ServerVariables.umbracoSettings.cssPath; + $scope.model.value.stylesheets = $scope.model.value.stylesheets + .filter(sheet => sheet.startsWith(cssPath)); + + $scope.stylesheets.forEach(stylesheet => { // support both current format (full stylesheet path) and legacy format (stylesheet name only) stylesheet.selected = $scope.model.value.stylesheets.indexOf(stylesheet.path) >= 0 ||$scope.model.value.stylesheets.indexOf(stylesheet.name) >= 0; }); 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 a20289d076..f5f0f7f2a2 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 @@ -56,7 +56,7 @@ return value.toFixed(stepDecimalPlaces); }, from: function (value) { - return value; + return Number(value); } }, "range": { 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 b147c4620b..1b913d7014 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 @@ -2,7 +2,7 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.UrlListController" function($rootScope, $scope, $filter) { function formatDisplayValue() { - if (angular.isArray($scope.model.value)) { + if (Utilities.isArray($scope.model.value)) { //it's the json value $scope.renderModel = _.map($scope.model.value, function (item) { return { @@ -42,4 +42,4 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.UrlListController" formatDisplayValue(); }; - }); \ No newline at end of file + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/relationtypes/create.html b/src/Umbraco.Web.UI.Client/src/views/relationtypes/create.html index ab65241c95..beb1962b4e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/relationtypes/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/relationtypes/create.html @@ -31,8 +31,7 @@ @@ -41,8 +40,7 @@ diff --git a/src/Umbraco.Web.UI.Client/src/views/relationtypes/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/relationtypes/edit.controller.js index ef77086343..74b3e31b87 100644 --- a/src/Umbraco.Web.UI.Client/src/views/relationtypes/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/relationtypes/edit.controller.js @@ -6,7 +6,7 @@ * @description * The controller for editing relation types. */ -function RelationTypeEditController($scope, $routeParams, relationTypeResource, editorState, navigationService, dateHelper, userService, entityResource, formHelper, contentEditingHelper, localizationService) { +function RelationTypeEditController($scope, $routeParams, relationTypeResource, editorState, navigationService, dateHelper, userService, entityResource, formHelper, contentEditingHelper, localizationService, eventsService) { var vm = this; @@ -25,6 +25,10 @@ function RelationTypeEditController($scope, $routeParams, relationTypeResource, function init() { vm.page.loading = true; + vm.relationsLoading = true; + + vm.changePageNumber = changePageNumber; + vm.options = {}; var labelKeys = [ "relationType_tabRelationType", @@ -49,17 +53,39 @@ function RelationTypeEditController($scope, $routeParams, relationTypeResource, ]; }); + // load references when the 'relations' tab is first activated/switched to + eventsService.on("app.tabChange", function (event, args) { + if (args.alias === "relations") { + loadRelations(); + } + }); + + // Inital page/overview API call of relation type relationTypeResource.getById($routeParams.id) - .then(function(data) { + .then(function (data) { bindRelationType(data); vm.page.loading = false; }); } - function bindRelationType(relationType) { - formatDates(relationType.relations); - getRelationNames(relationType); + function changePageNumber(pageNumber) { + vm.options.pageNumber = pageNumber; + loadRelations(); + } + + /** Loads in the references one time when content app changed */ + function loadRelations() { + relationTypeResource.getPagedResults($routeParams.id, vm.options) + .then(function (data) { + formatDates(data.items); + vm.relationsLoading = false; + vm.relations = data; + }); + } + + + function bindRelationType(relationType) { // Convert property value to string, since the umb-radiobutton component at the moment only handle string values. // Sometime later the umb-radiobutton might be able to handle value as boolean. relationType.isBidirectional = (relationType.isBidirectional || false).toString(); @@ -74,7 +100,7 @@ function RelationTypeEditController($scope, $routeParams, relationTypeResource, } function formatDates(relations) { - if(relations) { + if (relations) { userService.getCurrentUser().then(function (currentUser) { angular.forEach(relations, function (relation) { relation.timestampFormatted = dateHelper.getLocalDate(relation.createDate, currentUser.locale, 'LLL'); @@ -83,41 +109,6 @@ function RelationTypeEditController($scope, $routeParams, relationTypeResource, } } - function getRelationNames(relationType) { - if (relationType.relations) { - // can we grab app entity types in one go? - if (relationType.parentObjectType === relationType.childObjectType) { - // yep, grab the distinct list of parent and child entities - var entityIds = _.uniq(_.union(_.pluck(relationType.relations, "parentId"), _.pluck(relationType.relations, "childId"))); - entityResource.getByIds(entityIds, relationType.parentObjectTypeName).then(function (entities) { - updateRelationNames(relationType, entities); - }); - } else { - // nope, grab the parent and child entities individually - var parentEntityIds = _.uniq(_.pluck(relationType.relations, "parentId")); - var childEntityIds = _.uniq(_.pluck(relationType.relations, "childId")); - entityResource.getByIds(parentEntityIds, relationType.parentObjectTypeName).then(function (entities) { - updateRelationNames(relationType, entities); - }); - entityResource.getByIds(childEntityIds, relationType.childObjectTypeName).then(function (entities) { - updateRelationNames(relationType, entities); - }); - } - } - } - - function updateRelationNames(relationType, entities) { - var entitiesById = _.indexBy(entities, "id"); - _.each(relationType.relations, function(relation) { - if (entitiesById[relation.parentId]) { - relation.parentName = entitiesById[relation.parentId].name; - } - if (entitiesById[relation.childId]) { - relation.childName = entitiesById[relation.childId].name; - } - }); - } - function saveRelationType() { if (formHelper.submitForm({ scope: $scope, statusMessage: "Saving..." })) { diff --git a/src/Umbraco.Web.UI.Client/src/views/relationtypes/views/relations.html b/src/Umbraco.Web.UI.Client/src/views/relationtypes/views/relations.html index 96e86c2303..b525fbcba3 100644 --- a/src/Umbraco.Web.UI.Client/src/views/relationtypes/views/relations.html +++ b/src/Umbraco.Web.UI.Client/src/views/relationtypes/views/relations.html @@ -1,28 +1,39 @@ - + + + - + No relations for this relation type. - -
    - - - - - - - - - - - - - -
    ParentChildCreatedComment
    {{relation.parentName}}{{relation.childName}}{{relation.timestampFormatted}}{{relation.comment}}
    -
    -
    + +
    + + + + + + + + + + + + + +
    ParentChildCreatedComment
    {{relation.parentName}}{{relation.childName}}{{relation.timestampFormatted}}{{relation.comment}}
    +
    + + +
    + + +
    +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js index 30aa677b8b..2e3bc6eb80 100644 --- a/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js @@ -8,7 +8,7 @@ var infiniteMode = $scope.model && $scope.model.infiniteMode; var id = infiniteMode ? $scope.model.id : $routeParams.id; var create = infiniteMode ? $scope.model.create : $routeParams.create; - + vm.header = {}; vm.header.editorfor = "template_template"; vm.header.setPageTitle = true; @@ -26,7 +26,7 @@ vm.page.insertDefaultButton = { labelKey: "general_insert", addEllipsis: "true", - handler: function() { + handler: function () { vm.openInsertOverlay(); } }; @@ -68,16 +68,16 @@ //Keyboard shortcuts for help dialog vm.page.keyboardShortcutsOverview = []; - templateHelper.getGeneralShortcuts().then(function(data){ + templateHelper.getGeneralShortcuts().then(function (data) { vm.page.keyboardShortcutsOverview.push(data); }); - templateHelper.getEditorShortcuts().then(function(data){ + templateHelper.getEditorShortcuts().then(function (data) { vm.page.keyboardShortcutsOverview.push(data); }); - templateHelper.getTemplateEditorShortcuts().then(function(data){ + templateHelper.getTemplateEditorShortcuts().then(function (data) { vm.page.keyboardShortcutsOverview.push(data); }); - + vm.save = function (suppressNotification) { vm.page.saveButtonState = "busy"; @@ -87,36 +87,36 @@ saveMethod: templateResource.save, scope: $scope, content: vm.template, - rebindCallback: function (orignal, saved) {} + rebindCallback: function (orignal, saved) { } }).then(function (saved) { - if (!suppressNotification) { - localizationService.localizeMany(["speechBubbles_templateSavedHeader", "speechBubbles_templateSavedText"]).then(function(data){ + if (!suppressNotification) { + localizationService.localizeMany(["speechBubbles_templateSavedHeader", "speechBubbles_templateSavedText"]).then(function (data) { var header = data[0]; var message = data[1]; notificationsService.success(header, message); }); - } + } vm.page.saveButtonState = "success"; vm.template = saved; //sync state - if(!infiniteMode) { + if (!infiniteMode) { editorState.set(vm.template); } - + // sync tree // if master template alias has changed move the node to it's new location - if(!infiniteMode && oldMasterTemplateAlias !== vm.template.masterTemplateAlias) { - + if (!infiniteMode && oldMasterTemplateAlias !== vm.template.masterTemplateAlias) { + // When creating a new template the id is -1. Make sure We don't remove the root node. if (vm.page.menu.currentNode.id !== "-1") { // move node to new location in tree //first we need to remove the node that we're working on treeService.removeNode(vm.page.menu.currentNode); } - + // update stored alias to the new one so the node won't move again unless the alias is changed again oldMasterTemplateAlias = vm.template.masterTemplateAlias; @@ -127,7 +127,7 @@ } else { // normal tree sync - if(!infiniteMode) { + if (!infiniteMode) { navigationService.syncTree({ tree: "templates", path: vm.template.path, forceReload: true }).then(function (syncArgs) { vm.page.menu.currentNode = syncArgs.node; }); @@ -138,7 +138,7 @@ // clear $dirty state on form setFormState("pristine"); - if(infiniteMode) { + if (infiniteMode) { submit(); } @@ -164,16 +164,16 @@ // load templates - used in the master template picker templateResource.getAll() - .then(function(templates) { + .then(function (templates) { vm.templates = templates; }); - if(create) { + if (create) { templateResource.getScaffold((id)).then(function (template) { - vm.ready(template); - }); + vm.ready(template); + }); } else { - templateResource.getById(id).then(function(template){ + templateResource.getById(id).then(function (template) { vm.ready(template); }); } @@ -181,26 +181,26 @@ }; - vm.ready = function(template){ - vm.page.loading = false; + vm.ready = function (template) { + vm.page.loading = false; vm.template = template; - // if this is a new template, bind to the blur event on the name - if (create) { - $timeout(function() { - var nameField = angular.element(document.querySelector('[data-element="editor-name-field"]')); - if (nameField) { - nameField.on('blur', function(event) { - if (event.target.value) { - vm.save(true); - } - }); - } - }); - } - + // if this is a new template, bind to the blur event on the name + if (create) { + $timeout(function () { + var nameField = $('[data-element="editor-name-field"]'); + if (nameField) { + nameField.on('blur', function (event) { + if (event.target.value) { + vm.save(true); + } + }); + } + }); + } + // sync state - if(!infiniteMode) { + if (!infiniteMode) { editorState.set(vm.template); navigationService.syncTree({ tree: "templates", path: vm.template.path, forceReload: true }).then(function (syncArgs) { vm.page.menu.currentNode = syncArgs.node; @@ -208,7 +208,7 @@ } // save state of master template to use for comparison when syncing the tree on save - oldMasterTemplateAlias = angular.copy(template.masterTemplateAlias); + oldMasterTemplateAlias = Utilities.copy(template.masterTemplateAlias); // ace configuration vm.aceOption = { @@ -221,12 +221,12 @@ enableBasicAutocompletion: true, enableLiveAutocompletion: false }, - onLoad: function(_editor) { + onLoad: function (_editor) { vm.editor = _editor; - + //Update the auto-complete method to use ctrl+alt+space _editor.commands.bindKey("ctrl-alt-space", "startAutocomplete"); - + // Unassigns the keybinding (That was previously auto-complete) // As conflicts with our own tree search shortcut _editor.commands.bindKey("ctrl-space", null); @@ -238,20 +238,20 @@ { name: 'unSelectOrFindPrevious', bindKey: 'Alt-Shift-K', - exec: function() { + exec: function () { // Toggle the show keyboard shortcuts overlay - $scope.$apply(function(){ + $scope.$apply(function () { vm.showKeyboardShortcut = !vm.showKeyboardShortcut; }); - + }, readOnly: true }, { name: 'insertUmbracoValue', bindKey: 'Alt-Shift-V', - exec: function() { - $scope.$apply(function(){ + exec: function () { + $scope.$apply(function () { openPageFieldOverlay(); }); }, @@ -260,18 +260,18 @@ { name: 'insertPartialView', bindKey: 'Alt-Shift-P', - exec: function() { - $scope.$apply(function(){ + exec: function () { + $scope.$apply(function () { openPartialOverlay(); }); }, readOnly: true }, - { + { name: 'insertDictionary', bindKey: 'Alt-Shift-D', - exec: function() { - $scope.$apply(function(){ + exec: function () { + $scope.$apply(function () { openDictionaryItemOverlay(); }); }, @@ -280,8 +280,8 @@ { name: 'insertUmbracoMacro', bindKey: 'Alt-Shift-M', - exec: function() { - $scope.$apply(function(){ + exec: function () { + $scope.$apply(function () { openMacroOverlay(); }); }, @@ -290,8 +290,8 @@ { name: 'insertQuery', bindKey: 'Alt-Shift-Q', - exec: function() { - $scope.$apply(function(){ + exec: function () { + $scope.$apply(function () { openQueryBuilderOverlay(); }); }, @@ -300,8 +300,8 @@ { name: 'insertSection', bindKey: 'Alt-Shift-S', - exec: function() { - $scope.$apply(function(){ + exec: function () { + $scope.$apply(function () { openSectionsOverlay(); }); }, @@ -310,21 +310,21 @@ { name: 'chooseMasterTemplate', bindKey: 'Alt-Shift-T', - exec: function() { - $scope.$apply(function(){ + exec: function () { + $scope.$apply(function () { openMasterTemplateOverlay(); }); }, readOnly: true } - + ]); - + // initial cursor placement // Keep cursor in name field if we are create a new template // else set the cursor at the bottom of the code editor - if(!create) { - $timeout(function(){ + if (!create) { + $timeout(function () { vm.editor.navigateFileEnd(); vm.editor.focus(); persistCurrentLocation(); @@ -335,9 +335,9 @@ vm.editor.on("blur", persistCurrentLocation); vm.editor.on("focus", persistCurrentLocation); vm.editor.on("change", changeAceEditor); - } + } } - + }; vm.openPageFieldOverlay = openPageFieldOverlay; @@ -363,15 +363,15 @@ partial: true, umbracoField: true }, - submit: function(model) { - switch(model.insert.type) { + submit: function (model) { + switch (model.insert.type) { case "macro": var macroObject = macroService.collectValueData(model.insert.selectedMacro, model.insert.macroParams, "Mvc"); insert(macroObject.syntax); break; case "dictionary": - var code = templateHelper.getInsertDictionarySnippet(model.insert.node.name); - insert(code); + var code = templateHelper.getInsertDictionarySnippet(model.insert.node.name); + insert(code); break; case "partial": var code = templateHelper.getInsertPartialSnippet(model.insert.node.parentId, model.insert.node.name); @@ -383,7 +383,7 @@ } editorService.close(); }, - close: function(oldModel) { + close: function (oldModel) { // close the dialog editorService.close(); // focus editor @@ -401,7 +401,7 @@ insert(macroObject.syntax); editorService.close(); }, - close: function() { + close: function () { editorService.close(); vm.editor.focus(); } @@ -431,7 +431,7 @@ "emptyStates_emptyDictionaryTree" ]; - localizationService.localizeMany(labelKeys).then(function(values){ + localizationService.localizeMany(labelKeys).then(function (values) { var title = values[0]; var emptyStateMessage = values[1]; @@ -442,7 +442,7 @@ multiPicker: false, title: title, emptyStateMessage: emptyStateMessage, - select: function(node){ + select: function (node) { var code = templateHelper.getInsertDictionarySnippet(node.name); insert(code); editorService.close(); @@ -463,22 +463,22 @@ function openPartialOverlay() { - localizationService.localize("template_insertPartialView").then(function(value){ + localizationService.localize("template_insertPartialView").then(function (value) { var title = value; var partialItem = { - section: "settings", + section: "settings", treeAlias: "partialViews", entityType: "partialView", multiPicker: false, title: title, - filter: function(i) { - if(i.name.indexOf(".cshtml") === -1 && i.name.indexOf(".vbhtml") === -1) { + filter: function (i) { + if (i.name.indexOf(".cshtml") === -1 && i.name.indexOf(".vbhtml") === -1) { return true; } }, filterCssClass: "not-allowed", - select: function(node){ + select: function (node) { var code = templateHelper.getInsertPartialSnippet(node.parentId, node.name); insert(code); editorService.close(); @@ -505,7 +505,7 @@ close: function () { editorService.close(); // focus editor - vm.editor.focus(); + vm.editor.focus(); } }; editorService.queryBuilder(queryBuilder); @@ -515,7 +515,7 @@ function openSectionsOverlay() { var templateSections = { isMaster: vm.template.isMasterTemplate, - submit: function(model) { + submit: function (model) { if (model.insertType === 'renderBody') { var code = templateHelper.getRenderBodySnippet(); @@ -535,7 +535,7 @@ editorService.close(); }, - close: function(model) { + close: function (model) { editorService.close(); vm.editor.focus(); } @@ -559,12 +559,12 @@ } }); - localizationService.localize("template_mastertemplate").then(function(value){ + localizationService.localize("template_mastertemplate").then(function (value) { var title = value; var masterTemplate = { title: title, availableItems: availableMasterTemplates, - submit: function(model) { + submit: function (model) { var template = model.selectedItem; if (template && template.alias) { vm.template.masterTemplateAlias = template.alias; @@ -575,7 +575,7 @@ } editorService.close(); }, - close: function(oldModel) { + close: function (oldModel) { // close dialog editorService.close(); // focus editor @@ -596,14 +596,14 @@ vm.template.masterTemplateAlias = null; setLayout(null); } - + } function getMasterTemplateName(masterTemplateAlias, templates) { - if(masterTemplateAlias) { + if (masterTemplateAlias) { var templateName = ""; - angular.forEach(templates, function(template){ - if(template.alias === masterTemplateAlias) { + angular.forEach(templates, function (template) { + if (template.alias === masterTemplateAlias) { templateName = template.name; } }); @@ -620,8 +620,8 @@ } - function setLayout(templatePath){ - + function setLayout(templatePath) { + var templateCode = vm.editor.getValue(); var newValue = templatePath; var layoutDefRegex = new RegExp("(@{[\\s\\S]*?Layout\\s*?=\\s*?)(\"[^\"]*?\"|null)(;[\\s\\S]*?})", "gi"); @@ -645,7 +645,7 @@ vm.editor.setValue(templateCode); vm.editor.clearSelection(); vm.editor.navigateFileStart(); - + vm.editor.focus(); // set form state to $dirty setFormState("dirty"); @@ -668,7 +668,7 @@ str = str.replace("{0}", selectedContent); vm.editor.insert(str); vm.editor.focus(); - + // set form state to $dirty setFormState("dirty"); } @@ -682,14 +682,14 @@ } function setFormState(state) { - + // get the current form var currentForm = angularHelper.getCurrentForm($scope); // set state - if(state === "dirty") { + if (state === "dirty") { currentForm.$setDirty(); - } else if(state === "pristine") { + } else if (state === "pristine") { currentForm.$setPristine(); } } @@ -699,18 +699,18 @@ } function submit() { - if($scope.model.submit) { + if ($scope.model.submit) { $scope.model.template = vm.template; $scope.model.submit($scope.model); } } function close() { - if($scope.model.close) { + if ($scope.model.close) { $scope.model.close(); } } - + vm.init(); } diff --git a/src/Umbraco.Web.UI.Client/src/views/users/group.controller.js b/src/Umbraco.Web.UI.Client/src/views/users/group.controller.js index 43339adf04..f996e944db 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/group.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/users/group.controller.js @@ -6,7 +6,7 @@ var vm = this; var contentPickerOpen = false; - vm.page = {}; + vm.page = {}; vm.page.rootIcon = "icon-folder"; vm.userGroup = {}; vm.labels = {}; @@ -63,9 +63,9 @@ }); } else { // get user group - userGroupsResource.getUserGroup($routeParams.id).then(function (userGroup) { - vm.userGroup = userGroup; - formatGranularPermissionSelection(); + userGroupsResource.getUserGroup($routeParams.id).then(function (userGroup) { + vm.userGroup = userGroup; + formatGranularPermissionSelection(); setSectionIcon(vm.userGroup.sections); makeBreadcrumbs(); vm.loading = false; @@ -101,7 +101,7 @@ function openSectionPicker() { var currentSelection = []; - angular.copy(vm.userGroup.sections, currentSelection); + Utilities.copy(vm.userGroup.sections, currentSelection); var sectionPicker = { selection: currentSelection, submit: function (model) { @@ -166,7 +166,7 @@ function openUserPicker() { var currentSelection = []; - angular.copy(vm.userGroup.users, currentSelection); + Utilities.copy(vm.userGroup.users, currentSelection); var userPicker = { selection: currentSelection, submit: function (model) { @@ -212,8 +212,8 @@ if (model.selection) { var node = model.selection[0]; //check if this is already in our selection - var found = _.find(vm.userGroup.assignedPermissions, function(i) { - return i.id === node.id; + var found = _.find(vm.userGroup.assignedPermissions, function (i) { + return i.id === node.id; }); node = found ? found : node; setPermissionsForNode(node); @@ -231,7 +231,7 @@ //clone the current defaults to pass to the model if (!node.permissions) { - node.permissions = angular.copy(vm.userGroup.defaultPermissions); + node.permissions = Utilities.copy(vm.userGroup.defaultPermissions); } vm.nodePermissions = { @@ -257,7 +257,7 @@ editorService.close(); - if(contentPickerOpen) { + if (contentPickerOpen) { editorService.close(); contentPickerOpen = false; } diff --git a/src/Umbraco.Web.UI.Client/src/views/users/group.html b/src/Umbraco.Web.UI.Client/src/views/users/group.html index 0244819655..eae6dbd75c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/group.html +++ b/src/Umbraco.Web.UI.Client/src/views/users/group.html @@ -40,13 +40,12 @@ on-remove="vm.removeSelectedItem($index, vm.userGroup.sections)"> - + @@ -61,14 +60,14 @@ on-remove="vm.clearStartNode('content')"> - + + @@ -85,14 +84,14 @@ on-remove="vm.clearStartNode('media')"> - + + @@ -127,13 +126,12 @@ on-edit="vm.setPermissionsForNode(node)"> - + @@ -155,13 +153,11 @@ on-remove="vm.removeSelectedItem($index, vm.userGroup.users)"> - +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js b/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js index c935852bf8..ecea3b1dba 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js @@ -8,7 +8,7 @@ vm.page = {}; vm.page.rootIcon = "icon-folder"; vm.user = { - changePassword: null + changePassword: null }; vm.breadcrumbs = []; vm.showBackButton = true; @@ -17,11 +17,12 @@ vm.maxFileSize = Umbraco.Sys.ServerVariables.umbracoSettings.maxFileSize + "KB"; vm.acceptedFileTypes = mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes); vm.usernameIsEmail = Umbraco.Sys.ServerVariables.umbracoSettings.usernameIsEmail; - + //create the initial model for change password vm.changePasswordModel = { - config: {}, - isChanging: false + config: {}, + isChanging: false, + value: {} }; vm.goToPage = goToPage; @@ -38,6 +39,7 @@ vm.clearAvatar = clearAvatar; vm.save = save; + vm.changePassword = changePassword; vm.toggleChangePassword = toggleChangePassword; function init() { @@ -81,40 +83,39 @@ //go get the config for the membership provider and add it to the model authResource.getMembershipProviderConfig().then(function (data) { - vm.changePasswordModel.config = data; + vm.changePasswordModel.config = data; - //the user has a password if they are not states: Invited, NoCredentials - vm.changePasswordModel.config.hasPassword = vm.user.userState !== 3 && vm.user.userState !== 4; + //the user has a password if they are not states: Invited, NoCredentials + vm.changePasswordModel.config.hasPassword = vm.user.userState !== 3 && vm.user.userState !== 4; - vm.changePasswordModel.config.disableToggle = true; + vm.changePasswordModel.config.disableToggle = true; - //this is only relavent for membership providers now (it's basically obsolete) - vm.changePasswordModel.config.enableReset = false; + //this is only relavent for membership providers now (it's basically obsolete) + vm.changePasswordModel.config.enableReset = false; - //in the ASP.NET Identity world, this config option will allow an admin user to change another user's password - //if the user has access to the user section. So if this editor is being access, the user of course has access to this section. - //the authorization check is also done on the server side when submitted. - - // only update the setting if not the current logged in user, otherwise leave the value as it is - // currently set in the web.config - if (!vm.user.isCurrentUser) - { - vm.changePasswordModel.config.allowManuallyChangingPassword = true; - } - - vm.loading = false; + //in the ASP.NET Identity world, this config option will allow an admin user to change another user's password + //if the user has access to the user section. So if this editor is being access, the user of course has access to this section. + //the authorization check is also done on the server side when submitted. + + // only update the setting if not the current logged in user, otherwise leave the value as it is + // currently set in the web.config + if (!vm.user.isCurrentUser) { + vm.changePasswordModel.config.allowManuallyChangingPassword = true; + } + + vm.loading = false; }); }); } - + function getLocalDate(date, culture, format) { - if(date) { + if (date) { var dateVal; var serverOffset = Umbraco.Sys.ServerVariables.application.serverTimeOffset; var localOffset = new Date().getTimezoneOffset(); var serverTimeNeedsOffsetting = (-serverOffset !== localOffset); - if(serverTimeNeedsOffsetting) { + if (serverTimeNeedsOffsetting) { dateVal = dateHelper.convertToLocalMomentTime(date, serverOffset); } else { dateVal = moment(date, "YYYY-MM-DD HH:mm:ss"); @@ -125,23 +126,34 @@ } function toggleChangePassword() { - vm.changePasswordModel.isChanging = !vm.changePasswordModel.isChanging; - //reset it - vm.user.changePassword = null; + //reset it + vm.user.changePassword = null; + + localizationService.localizeMany(["general_cancel", "general_confirm", "general_changePassword"]) + .then(function (data) { + const overlay = { + view: "changepassword", + title: data[2], + changePassword: vm.user.changePassword, + config: vm.changePasswordModel.config, + closeButtonLabel: data[0], + submitButtonLabel: data[1], + submitButtonStyle: 'success', + close: () => overlayService.close(), + submit: model => { + overlayService.close(); + vm.changePasswordModel.value = model.changePassword; + changePassword(); + } + }; + overlayService.open(overlay); + }); } function save() { if (formHelper.submitForm({ scope: $scope })) { - //anytime a user is changing another user's password, we are in effect resetting it so we need to set that flag here - if (vm.user.changePassword) { - //NOTE: the check for allowManuallyChangingPassword is due to this legacy user membership provider setting, if that is true, then the current user - //can change their own password without entering their current one (this is a legacy setting since that is a security issue but we need to maintain compat). - //if allowManuallyChangingPassword=false, then we are using default settings and the user will need to enter their old password to change their own password. - vm.user.changePassword.reset = (!vm.user.changePassword.oldPassword && !vm.user.isCurrentUser) || vm.changePasswordModel.config.allowManuallyChangingPassword; - } - vm.page.saveButtonState = "busy"; vm.user.resetPasswordValue = null; @@ -152,21 +164,17 @@ .then(function (saved) { //if the user saved, then try to execute all extended save options - extendedSave(saved).then(function(result) { + extendedSave(saved).then(function (result) { //if all is good, then reset the form formHelper.resetForm({ scope: $scope }); - }, angular.noop); - + }, Utilities.noop); + vm.user = _.omit(saved, "navigation"); //restore vm.user.navigation = currentNav; setUserDisplayState(); formatDatesToLocal(vm.user); - vm.changePasswordModel.isChanging = false; - //the user has a password if they are not states: Invited, NoCredentials - vm.changePasswordModel.config.hasPassword = vm.user.userState !== 3 && vm.user.userState !== 4; - vm.page.saveButtonState = "success"; }, function (err) { @@ -175,12 +183,42 @@ err: err, showNotifications: true }); - + vm.page.saveButtonState = "error"; }); } } + /** + * + */ + function changePassword() { + //anytime a user is changing another user's password, we are in effect resetting it so we need to set that flag here + if (vm.changePasswordModel.value) { + //NOTE: the check for allowManuallyChangingPassword is due to this legacy user membership provider setting, if that is true, then the current user + //can change their own password without entering their current one (this is a legacy setting since that is a security issue but we need to maintain compat). + //if allowManuallyChangingPassword=false, then we are using default settings and the user will need to enter their old password to change their own password. + vm.changePasswordModel.value.reset = (!vm.changePasswordModel.value.oldPassword && !vm.user.isCurrentUser) || vm.changePasswordModel.config.allowManuallyChangingPassword; + } + + // since we don't send the entire user model, the id is required + vm.changePasswordModel.value.id = vm.user.id; + + usersResource.changePassword(vm.changePasswordModel.value) + .then(() => { + vm.changePasswordModel.isChanging = false; + vm.changePasswordModel.value = {}; + + //the user has a password if they are not states: Invited, NoCredentials + vm.changePasswordModel.config.hasPassword = vm.user.userState !== 3 && vm.user.userState !== 4; + }, err => { + contentEditingHelper.handleSaveError({ + err: err, + showNotifications: true + }); + }); + } + /** * Used to emit the save event and await any async operations being performed by editor extensions * @param {any} savedUser @@ -189,7 +227,7 @@ //used to track any promises added by the event handlers to be awaited var promises = []; - + var args = { //getPromise: getPromise, user: savedUser, @@ -201,10 +239,10 @@ //emit the event eventsService.emit("editors.user.editController.save", args); - + //await all promises to complete var resultPromise = $q.all(promises); - + return resultPromise; } @@ -214,7 +252,7 @@ function openUserGroupPicker() { var currentSelection = []; - angular.copy(vm.user.userGroups, currentSelection); + Utilities.copy(vm.user.userGroups, currentSelection); var userGroupPicker = { selection: currentSelection, submit: function (model) { @@ -224,7 +262,7 @@ } editorService.close(); }, - close: function () { + close: function () { editorService.close(); } }; @@ -316,10 +354,10 @@ vm.user.userState = 1; setUserDisplayState(); vm.disableUserButtonState = "success"; - + }, function (error) { vm.disableUserButtonState = "error"; - + }); } @@ -341,7 +379,7 @@ vm.user.failedPasswordAttempts = 0; setUserDisplayState(); vm.unlockUserButtonState = "success"; - + }, function (error) { vm.unlockUserButtonState = "error"; }); @@ -409,7 +447,7 @@ function clearAvatar() { // get user usersResource.clearAvatar(vm.user.id).then(function (data) { - vm.user.avatars = data; + vm.user.avatars = data; }); } @@ -430,15 +468,15 @@ }).progress(function (evt) { if (vm.avatarFile.uploadStatus !== "done" && vm.avatarFile.uploadStatus !== "error") { - // set uploading status on file - vm.avatarFile.uploadStatus = "uploading"; + // set uploading status on file + vm.avatarFile.uploadStatus = "uploading"; - // calculate progress in percentage - var progressPercentage = parseInt(100.0 * evt.loaded / evt.total, 10); + // calculate progress in percentage + var progressPercentage = parseInt(100.0 * evt.loaded / evt.total, 10); - // set percentage property on file - vm.avatarFile.uploadProgress = progressPercentage; - } + // set percentage property on file + vm.avatarFile.uploadProgress = progressPercentage; + } }).success(function (data, status, headers, config) { diff --git a/src/Umbraco.Web.UI.Client/src/views/users/views/groups/groups.controller.js b/src/Umbraco.Web.UI.Client/src/views/users/views/groups/groups.controller.js index 984866dca1..31c52a344b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/views/groups/groups.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/users/views/groups/groups.controller.js @@ -112,7 +112,7 @@ userGroupsResource.deleteUserGroups(_.pluck(vm.selection, "id")).then(function (data) { clearSelection(); onInit(); - }, angular.noop); + }, Utilities.noop); overlayService.close(); } }; diff --git a/src/Umbraco.Web.UI.Client/src/views/users/views/user/details.html b/src/Umbraco.Web.UI.Client/src/views/users/views/user/details.html index a1dcafd421..bb3efaede2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/views/user/details.html +++ b/src/Umbraco.Web.UI.Client/src/views/users/views/user/details.html @@ -75,13 +75,11 @@ on-remove="model.removeSelectedItem($index, model.user.userGroups)"> - + @@ -100,13 +98,12 @@ name="model.labels.noStartNodes"> - + @@ -125,13 +122,12 @@ name="model.labels.noStartNodes"> - + @@ -258,7 +254,7 @@
    -
    - - - - - - - - - -


    Password reset to value: {{model.user.resetPasswordValue}}

    diff --git a/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js b/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js index 4ee31806a3..102efae702 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js @@ -14,7 +14,7 @@ vm.userStates = []; vm.selection = []; vm.newUser = {}; - vm.usersOptions = {filter:null}; + vm.usersOptions = {}; vm.userSortData = [ { label: "Name (A-Z)", key: "Name", direction: "Ascending" }, { label: "Name (Z-A)", key: "Name", direction: "Descending" }, @@ -67,7 +67,7 @@ ]; // Get last selected layout for "users" (defaults to first layout = card layout) - vm.activeLayout = listViewHelper.getLayout("users", vm.layouts); + vm.activeLayout = listViewHelper.getLayout("users", vm.layouts); // Don't show the invite button if no email is configured if (Umbraco.Sys.ServerVariables.umbracoSettings.showUserInvite) { @@ -112,6 +112,7 @@ vm.selectAll = selectAll; vm.areAllSelected = areAllSelected; vm.searchUsers = searchUsers; + vm.onBlurSearch = onBlurSearch; vm.getFilterName = getFilterName; vm.setUserStatesFilter = setUserStatesFilter; vm.setUserGroupFilter = setUserGroupFilter; @@ -150,10 +151,12 @@ function initViewOptions() { // Start with default view options. + vm.usersOptions.filter = ""; vm.usersOptions.orderBy = "Name"; vm.usersOptions.orderDirection = "Ascending"; // Update from querystring if available. + initViewOptionFromQueryString("filter"); initViewOptionFromQueryString("orderBy"); initViewOptionFromQueryString("orderDirection"); initViewOptionFromQueryString("pageNumber"); @@ -244,19 +247,19 @@ function selectLayout(selectedLayout) { // save the selected layout for "users" so it's applied next time the user visits this section - vm.activeLayout = listViewHelper.setLayout("users", selectedLayout, vm.layouts); + vm.activeLayout = listViewHelper.setLayout("users", selectedLayout, vm.layouts); } - + function isSelectable(user) { return !user.isCurrentUser; } - + function selectUser(user) { - + if (!isSelectable(user)) { return; } - + if (user.selected) { var index = vm.selection.indexOf(user.id); vm.selection.splice(index, 1); @@ -265,9 +268,9 @@ user.selected = true; vm.selection.push(user.id); } - + setBulkActions(vm.users); - + } function clearSelection() { @@ -276,14 +279,14 @@ }); vm.selection = []; } - + function clickUser(user, $event) { - + $event.stopPropagation(); - + if ($event) { // targeting a new tab/window? - if ($event.ctrlKey || + if ($event.ctrlKey || $event.shiftKey || $event.metaKey || // apple ($event.button && $event.button === 1) // middle click, >IE9 + everyone else @@ -292,7 +295,7 @@ return; } } - + goToUser(user); $event.preventDefault(); @@ -383,7 +386,7 @@ vm.selectedBulkUserGroups = []; editorService.close(); clearSelection(); - }, angular.noop); + }, Utilities.noop); }, close: function () { vm.selectedBulkUserGroups = []; @@ -395,7 +398,7 @@ function openUserGroupPicker() { var currentSelection = []; - angular.copy(vm.newUser.userGroups, currentSelection); + Utilities.copy(vm.newUser.userGroups, currentSelection); var userGroupPicker = { selection: currentSelection, submit: function (model) { @@ -451,6 +454,7 @@ var search = _.debounce(function () { $scope.$apply(function () { + vm.usersOptions.pageNumber = 1; getUsers(); }); }, 500); @@ -459,6 +463,10 @@ search(); } + function onBlurSearch() { + updateLocation("filter", vm.usersOptions.filter); + } + function getFilterName(array) { var name = vm.labels.all; var found = false; @@ -512,7 +520,7 @@ } updateLocation("userStates", vm.usersOptions.userStates.join(",")); - getUsers(); + changePageNumber(1); } function setUserGroupFilter(userGroup) { @@ -529,7 +537,7 @@ } updateLocation("userGroups", vm.usersOptions.userGroups.join(",")); - getUsers(); + changePageNumber(1); } function setOrderByFilter(value, direction) { @@ -547,6 +555,7 @@ } function updateLocation(key, value) { + $location.search("filter", vm.usersOptions.filter);// update filter, but first when something else requests a url update. $location.search(key, value); } @@ -602,7 +611,7 @@ // copy to clip board success function copySuccess() { if (vm.page.copyPasswordButtonState !== "success") { - $timeout(function(){ + $timeout(function () { vm.page.copyPasswordButtonState = "success"; }); $timeout(function () { @@ -614,7 +623,7 @@ // copy to clip board error function copyError() { if (vm.page.copyPasswordButtonState !== "error") { - $timeout(function() { + $timeout(function () { vm.page.copyPasswordButtonState = "error"; }); $timeout(function () { @@ -645,7 +654,7 @@ return null; } - + function getEditPath(user) { return pathToUser(user) + usersOptionsAsQueryString(); } @@ -657,7 +666,8 @@ function usersOptionsAsQueryString() { var qs = "?orderBy=" + vm.usersOptions.orderBy + "&orderDirection=" + vm.usersOptions.orderDirection + - "&pageNumber=" + vm.usersOptions.pageNumber; + "&pageNumber=" + vm.usersOptions.pageNumber + + "&filter=" + vm.usersOptions.filter; qs += addUsersOptionsFilterCollectionToQueryString("userStates", vm.usersOptions.userStates); qs += addUsersOptionsFilterCollectionToQueryString("userGroups", vm.usersOptions.userGroups); @@ -689,7 +699,7 @@ vm.usersOptions.pageSize = data.pageSize; vm.usersOptions.totalItems = data.totalItems; vm.usersOptions.totalPages = data.totalPages; - + formatDates(vm.users); setUserDisplayState(vm.users); vm.userStatesFilter = usersHelper.getUserStatesFilter(data.userStates); @@ -743,19 +753,19 @@ var firstSelectedUserGroups; angular.forEach(users, function (user) { - + if (!user.selected) { return; } - - + + // if the current user is selected prevent any bulk actions with the user included if (user.isCurrentUser) { vm.allowDisableUser = false; vm.allowEnableUser = false; vm.allowUnlockUser = false; vm.allowSetUserGroup = false; - + return false; } diff --git a/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.html b/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.html index 638e6376c3..a0098d411c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.html +++ b/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.html @@ -28,7 +28,7 @@ - + @@ -119,7 +119,7 @@ - + diff --git a/src/Umbraco.Web.UI.Client/test/lib/angular/angular-mocks.js b/src/Umbraco.Web.UI.Client/test/lib/angular/angular-mocks.js index b4a3fb9ed6..31310e1c46 100644 --- a/src/Umbraco.Web.UI.Client/test/lib/angular/angular-mocks.js +++ b/src/Umbraco.Web.UI.Client/test/lib/angular/angular-mocks.js @@ -51,12 +51,12 @@ angular.mock.$Browser = function () { self.onUrlChange = function (listener) { self.pollFns.push( - function () { - if (self.$$lastUrl != self.$$url) { - self.$$lastUrl = self.$$url; - listener(self.$$url); - } - } + function () { + if (self.$$lastUrl != self.$$url) { + self.$$lastUrl = self.$$url; + listener(self.$$url); + } + } ); return listener; @@ -104,7 +104,7 @@ angular.mock.$Browser = function () { * @param {number=} number of milliseconds to flush. See {@link #defer.now} */ self.defer.flush = function (delay) { - if (angular.isDefined(delay)) { + if (Utilities.isDefined(delay)) { self.defer.now += delay; } else { if (self.deferredFns.length) { @@ -165,15 +165,15 @@ angular.mock.$Browser.prototype = { if (value == undefined) { delete this.cookieHash[name]; } else { - if (angular.isString(value) && //strings only + if (Utilities.isString(value) && //strings only value.length <= 4096) { //strict cookie storage limits this.cookieHash[name] = value; } } } else { if (!angular.equals(this.cookieHash, this.lastCookieHash)) { - this.lastCookieHash = angular.copy(this.cookieHash); - this.cookieHash = angular.copy(this.cookieHash); + this.lastCookieHash = Utilities.copy(this.cookieHash); + this.cookieHash = Utilities.copy(this.cookieHash); } return this.cookieHash; } @@ -397,7 +397,7 @@ angular.mock.$LogProvider = function () { }); if (errors.length) { errors.unshift("Expected $log to be empty! Either a message was logged unexpectedly, or an expected " + - "log message was not checked and removed:"); + "log message was not checked and removed:"); errors.push(''); throw new Error(errors.join('\n---------\n')); } @@ -486,7 +486,7 @@ angular.mock.$LogProvider = function () { */ angular.mock.TzDate = function (offset, timestamp) { var self = new Date(0); - if (angular.isString(timestamp)) { + if (Utilities.isString(timestamp)) { var tsStr = timestamp; self.origDate = jsonStringToDate(timestamp); @@ -581,12 +581,12 @@ angular.mock.$LogProvider = function () { if (self.toISOString) { self.toISOString = function () { return padNumber(self.origDate.getUTCFullYear(), 4) + '-' + - padNumber(self.origDate.getUTCMonth() + 1, 2) + '-' + - padNumber(self.origDate.getUTCDate(), 2) + 'T' + - padNumber(self.origDate.getUTCHours(), 2) + ':' + - padNumber(self.origDate.getUTCMinutes(), 2) + ':' + - padNumber(self.origDate.getUTCSeconds(), 2) + '.' + - padNumber(self.origDate.getUTCMilliseconds(), 3) + 'Z' + padNumber(self.origDate.getUTCMonth() + 1, 2) + '-' + + padNumber(self.origDate.getUTCDate(), 2) + 'T' + + padNumber(self.origDate.getUTCHours(), 2) + ':' + + padNumber(self.origDate.getUTCMinutes(), 2) + ':' + + padNumber(self.origDate.getUTCSeconds(), 2) + '.' + + padNumber(self.origDate.getUTCMilliseconds(), 3) + 'Z' } } @@ -686,19 +686,19 @@ angular.mock.dump = function (object) { var out; if (angular.isElement(object)) { - object = angular.element(object); - out = angular.element('
    '); + object = $(object); + out = $('
    '); angular.forEach(object, function (element) { - out.append(angular.element(element).clone()); + out.append($(element).clone()); }); out = out.html(); - } else if (angular.isArray(object)) { + } else if (Utilities.isArray(object)) { out = []; angular.forEach(object, function (o) { out.push(serialize(o)); }); out = '[ ' + out.join(', ') + ' ]'; - } else if (angular.isObject(object)) { + } else if (Utilities.isObject(object)) { if (angular.isFunction(object.$eval) && angular.isFunction(object.$apply)) { out = serializeScope(object); } else if (object instanceof Error) { @@ -943,7 +943,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) { wasExpected = false; function prettyPrint(data) { - return (angular.isString(data) || angular.isFunction(data) || data instanceof RegExp) + return (Utilities.isString(data) || angular.isFunction(data) || data instanceof RegExp) ? data : angular.toJson(data); } @@ -1004,7 +1004,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) { throw wasExpected ? Error('No response defined !') : Error('Unexpected request: ' + method + ' ' + url + '\n' + - (expectation ? 'Expected ' + expectation : 'No more request expected')); + (expectation ? 'Expected ' + expectation : 'No more request expected')); } /** @@ -1267,7 +1267,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) { $rootScope.$digest(); if (!responses.length) throw Error('No pending request to flush !'); - if (angular.isDefined(count)) { + if (Utilities.isDefined(count)) { while (count--) { if (!responses.length) throw Error('No more pending request to flush !'); responses.shift()(); @@ -1365,8 +1365,8 @@ function MockHttpExpectation(method, url, data, headers) { this.match = function (m, u, d, h) { if (method != m) return false; if (!this.matchUrl(u)) return false; - if (angular.isDefined(d) && !this.matchData(d)) return false; - if (angular.isDefined(h) && !this.matchHeaders(h)) return false; + if (Utilities.isDefined(d) && !this.matchData(d)) return false; + if (Utilities.isDefined(h) && !this.matchHeaders(h)) return false; return true; }; @@ -1385,7 +1385,7 @@ function MockHttpExpectation(method, url, data, headers) { this.matchData = function (d) { if (angular.isUndefined(data)) return true; if (data && angular.isFunction(data.test)) return data.test(d); - if (data && !angular.isString(data)) return angular.toJson(data) == d; + if (data && !Utilities.isString(data)) return angular.toJson(data) == d; return data == d; }; @@ -1499,7 +1499,7 @@ angular.mock.$TimeoutDecorator = function ($delegate, $browser) { */ angular.mock.$RootElementProvider = function () { this.$get = function () { - return angular.element('
    '); + return $('
    '); } }; @@ -1572,7 +1572,7 @@ angular.module('ngMockE2E', ['ng']).config(function ($provide) { * * // adds a new phone to the phones array * $httpBackend.whenPOST('/phones').respond(function(method, url, data) { - * phones.push(angular.fromJSON(data)); + * phones.push(JSON.parse(data)); * }); * $httpBackend.whenGET(/^\/templates\//).passThrough(); * //... @@ -1710,7 +1710,7 @@ angular.mock.clearDataCache = function () { if (cache.hasOwnProperty(key)) { var handle = cache[key].handle; - handle && angular.element(handle.elem).unbind(); + handle && $(handle.elem).unbind(); delete cache[key]; } } 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 2ccf9e886a..227a359d1d 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 @@ -121,13 +121,15 @@ describe('contentEditingHelper tests', function () { //act //note the null, that's because culture is null - formHelper.handleServerValidation({ "_Properties.bodyText.null.value": ["Required"] }); + formHelper.handleServerValidation({ "_Properties.bodyText.null.null.value": ["Required"] }); //assert expect(serverValidationManager.items.length).toBe(1); expect(serverValidationManager.items[0].fieldName).toBe("value"); expect(serverValidationManager.items[0].errorMsg).toBe("Required"); expect(serverValidationManager.items[0].propertyAlias).toBe("bodyText"); + expect(serverValidationManager.items[0].culture).toBe("invariant"); + expect(serverValidationManager.items[0].segment).toBeNull(); }); it('adds a multiple property and field level server validation errors when they are invalid', function () { @@ -142,7 +144,7 @@ describe('contentEditingHelper tests', function () { "Name": ["Required"], "UpdateDate": ["Invalid date"], //note the null, that's because culture is null - "_Properties.bodyText.null.value": ["Required field"], + "_Properties.bodyText.en-US.mySegment.value": ["Required field"], "_Properties.textarea": ["Invalid format"] }); @@ -157,6 +159,8 @@ describe('contentEditingHelper tests', function () { expect(serverValidationManager.items[2].fieldName).toBe("value"); expect(serverValidationManager.items[2].errorMsg).toBe("Required field"); expect(serverValidationManager.items[2].propertyAlias).toBe("bodyText"); + expect(serverValidationManager.items[2].culture).toBe("en-US"); + expect(serverValidationManager.items[2].segment).toBe("mySegment"); expect(serverValidationManager.items[3].fieldName).toBe(""); expect(serverValidationManager.items[3].errorMsg).toBe("Invalid format"); expect(serverValidationManager.items[3].propertyAlias).toBe("textarea"); diff --git a/src/Umbraco.Web.UI.Client/test/unit/common/services/server-validation-manager.spec.js b/src/Umbraco.Web.UI.Client/test/unit/common/services/server-validation-manager.spec.js index 966731f0f7..5d2a618f46 100644 --- a/src/Umbraco.Web.UI.Client/test/unit/common/services/server-validation-manager.spec.js +++ b/src/Umbraco.Web.UI.Client/test/unit/common/services/server-validation-manager.spec.js @@ -61,12 +61,12 @@ it('can retrieve property validation errors for a sub field', function () { //arrange - serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1"); + serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1", null); serverValidationManager.addPropertyError("myProperty", null, "value2", "Another value 2"); //act - var err1 = serverValidationManager.getPropertyError("myProperty", null, "value1"); - var err2 = serverValidationManager.getPropertyError("myProperty", null, "value2"); + var err1 = serverValidationManager.getPropertyError("myProperty", null, "value1", null); + var err2 = serverValidationManager.getPropertyError("myProperty", null, "value2", null); //assert expect(err1).not.toBeUndefined(); @@ -85,14 +85,14 @@ it('can retrieve property validation errors for a sub field for culture', function () { //arrange - serverValidationManager.addPropertyError("myProperty", "en-US", "value1", "Some value 1"); - serverValidationManager.addPropertyError("myProperty", "fr-FR", "value2", "Another value 2"); + serverValidationManager.addPropertyError("myProperty", "en-US", "value1", "Some value 1", null); + serverValidationManager.addPropertyError("myProperty", "fr-FR", "value2", "Another value 2", null); //act - var err1 = serverValidationManager.getPropertyError("myProperty", "en-US", "value1"); - var err1NotFound = serverValidationManager.getPropertyError("myProperty", null, "value2"); - var err2 = serverValidationManager.getPropertyError("myProperty", "fr-FR", "value2"); - var err2NotFound = serverValidationManager.getPropertyError("myProperty", null, "value2"); + var err1 = serverValidationManager.getPropertyError("myProperty", "en-US", "value1", null); + var err1NotFound = serverValidationManager.getPropertyError("myProperty", null, "value1", null); + var err2 = serverValidationManager.getPropertyError("myProperty", "fr-FR", "value2", null); + var err2NotFound = serverValidationManager.getPropertyError("myProperty", null, "value2", null); //assert @@ -111,12 +111,77 @@ expect(err2.errorMsg).toEqual("Another value 2"); expect(err2.culture).toEqual("fr-FR"); }); + + it('can retrieve property validation errors for a sub field for segments', function () { + + //arrange + serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1", "segment1"); + serverValidationManager.addPropertyError("myProperty", null, "value2", "Another value 2", "segment2"); + + //act + var err1 = serverValidationManager.getPropertyError("myProperty", null, "value1", "segment1"); + var err1NotFound = serverValidationManager.getPropertyError("myProperty", null, "value1", null); + var err2 = serverValidationManager.getPropertyError("myProperty", null, "value2", "segment2"); + var err2NotFound = serverValidationManager.getPropertyError("myProperty", null, "value2", null); + + + //assert + expect(err1NotFound).toBeUndefined(); + expect(err2NotFound).toBeUndefined(); + + expect(err1).not.toBeUndefined(); + expect(err1.propertyAlias).toEqual("myProperty"); + expect(err1.fieldName).toEqual("value1"); + expect(err1.errorMsg).toEqual("Some value 1"); + expect(err1.segment).toEqual("segment1"); + + expect(err2).not.toBeUndefined(); + expect(err2.propertyAlias).toEqual("myProperty"); + expect(err2.fieldName).toEqual("value2"); + expect(err2.errorMsg).toEqual("Another value 2"); + expect(err2.segment).toEqual("segment2"); + }); + + + it('can retrieve property validation errors for a sub field for culture with segments', function () { + + //arrange + serverValidationManager.addPropertyError("myProperty", "en-US", "value1", "Some value 1", "segment1"); + serverValidationManager.addPropertyError("myProperty", "fr-FR", "value2", "Another value 2", "segment2"); + + //act + var err1 = serverValidationManager.getPropertyError("myProperty", "en-US", "value1", "segment1"); + expect(serverValidationManager.getPropertyError("myProperty", null, "value1", null)).toBeUndefined(); + expect(serverValidationManager.getPropertyError("myProperty", "en-US", "value1", null)).toBeUndefined(); + expect(serverValidationManager.getPropertyError("myProperty", null, "value1", "segment1")).toBeUndefined(); + var err2 = serverValidationManager.getPropertyError("myProperty", "fr-FR", "value2", "segment2"); + expect(serverValidationManager.getPropertyError("myProperty", null, "value2", null)).toBeUndefined(); + expect(serverValidationManager.getPropertyError("myProperty", "fr-FR", "value2", null)).toBeUndefined(); + expect(serverValidationManager.getPropertyError("myProperty", null, "value2", "segment2")).toBeUndefined(); + + + //assert + + expect(err1).not.toBeUndefined(); + expect(err1.propertyAlias).toEqual("myProperty"); + expect(err1.fieldName).toEqual("value1"); + expect(err1.errorMsg).toEqual("Some value 1"); + expect(err1.culture).toEqual("en-US"); + expect(err1.segment).toEqual("segment1"); + + expect(err2).not.toBeUndefined(); + expect(err2.propertyAlias).toEqual("myProperty"); + expect(err2.fieldName).toEqual("value2"); + expect(err2.errorMsg).toEqual("Another value 2"); + expect(err2.culture).toEqual("fr-FR"); + expect(err2.segment).toEqual("segment2"); + }); it('can add a property errors with multiple sub fields and it the first will be retreived with only the property alias', function () { //arrange - serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1"); - serverValidationManager.addPropertyError("myProperty", null, "value2", "Another value 2"); + serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1", null); + serverValidationManager.addPropertyError("myProperty", null, "value2", "Another value 2", null); //act var err = serverValidationManager.getPropertyError("myProperty"); @@ -132,10 +197,10 @@ it('will return null for a non-existing property error', function () { //arrage - serverValidationManager.addPropertyError("myProperty", null, "value", "Required"); + serverValidationManager.addPropertyError("myProperty", null, "value", "Required", null); //act - var err = serverValidationManager.getPropertyError("DoesntExist", null, "value"); + var err = serverValidationManager.getPropertyError("DoesntExist", null, "value", null); //assert expect(err).toBeUndefined(); @@ -145,15 +210,15 @@ it('detects if a property error exists', function () { //arrange - serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1"); - serverValidationManager.addPropertyError("myProperty", null, "value2", "Another value 2"); + serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1", null); + serverValidationManager.addPropertyError("myProperty", null, "value2", "Another value 2", null); //act var err1 = serverValidationManager.hasPropertyError("myProperty"); - var err2 = serverValidationManager.hasPropertyError("myProperty", null, "value1"); - var err3 = serverValidationManager.hasPropertyError("myProperty", null, "value2"); + var err2 = serverValidationManager.hasPropertyError("myProperty", null, "value1", null); + var err3 = serverValidationManager.hasPropertyError("myProperty", null, "value2", null); var err4 = serverValidationManager.hasPropertyError("notFound"); - var err5 = serverValidationManager.hasPropertyError("myProperty", null, "notFound"); + var err5 = serverValidationManager.hasPropertyError("myProperty", null, "notFound", null); //assert expect(err1).toBe(true); @@ -167,15 +232,15 @@ it('can remove a property error with a sub field specified', function () { //arrage - serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1"); - serverValidationManager.addPropertyError("myProperty", null, "value2", "Another value 2"); + serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1", null); + serverValidationManager.addPropertyError("myProperty", null, "value2", "Another value 2", null); //act - serverValidationManager.removePropertyError("myProperty", null, "value1"); + serverValidationManager.removePropertyError("myProperty", null, "value1", null); //assert - expect(serverValidationManager.hasPropertyError("myProperty", null, "value1")).toBe(false); - expect(serverValidationManager.hasPropertyError("myProperty", null, "value2")).toBe(true); + expect(serverValidationManager.hasPropertyError("myProperty", null, "value1", null)).toBe(false); + expect(serverValidationManager.hasPropertyError("myProperty", null, "value2", null)).toBe(true); }); @@ -189,8 +254,8 @@ serverValidationManager.removePropertyError("myProperty"); //assert - expect(serverValidationManager.hasPropertyError("myProperty", null, "value1")).toBe(false); - expect(serverValidationManager.hasPropertyError("myProperty", null, "value2")).toBe(false); + expect(serverValidationManager.hasPropertyError("myProperty", null, "value1", null)).toBe(false); + expect(serverValidationManager.hasPropertyError("myProperty", null, "value2", null)).toBe(false); }); @@ -201,10 +266,10 @@ it('can retrieve culture validation errors', function () { //arrange - serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1"); - serverValidationManager.addPropertyError("myProperty", "en-US", "value1", "Some value 2"); - serverValidationManager.addPropertyError("myProperty", null, "value2", "Another value 2"); - serverValidationManager.addPropertyError("myProperty", "fr-FR", "value2", "Another value 3"); + serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1", null); + serverValidationManager.addPropertyError("myProperty", "en-US", "value1", "Some value 2", null); + serverValidationManager.addPropertyError("myProperty", null, "value2", "Another value 2", null); + serverValidationManager.addPropertyError("myProperty", "fr-FR", "value2", "Another value 3", null); //assert expect(serverValidationManager.hasCultureError(null)).toBe(true); @@ -216,6 +281,39 @@ }); + describe('managing variant validation errors', function () { + + it('can retrieve variant validation errors', function () { + + //arrange + serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1", null); + serverValidationManager.addPropertyError("myProperty", "en-US", "value1", "Some value 2", null); + serverValidationManager.addPropertyError("myProperty", null, "value2", "Another value 2", null); + serverValidationManager.addPropertyError("myProperty", "fr-FR", "value2", "Another value 3", null); + + serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1", "MySegment"); + serverValidationManager.addPropertyError("myProperty", "en-US", "value1", "Some value 2", "MySegment"); + serverValidationManager.addPropertyError("myProperty", null, "value2", "Another value 2", "MySegment"); + serverValidationManager.addPropertyError("myProperty", "fr-FR", "value2", "Another value 3", "MySegment"); + + //assert + expect(serverValidationManager.hasVariantError(null, null)).toBe(true); + expect(serverValidationManager.hasVariantError("en-US", null)).toBe(true); + expect(serverValidationManager.hasVariantError("fr-FR", null)).toBe(true); + + expect(serverValidationManager.hasVariantError(null, "MySegment")).toBe(true); + expect(serverValidationManager.hasVariantError("en-US", "MySegment")).toBe(true); + expect(serverValidationManager.hasVariantError("fr-FR", "MySegment")).toBe(true); + + expect(serverValidationManager.hasVariantError("es-ES", null)).toBe(false); + expect(serverValidationManager.hasVariantError("es-ES", "MySegment")).toBe(false); + expect(serverValidationManager.hasVariantError("fr-FR", "MySegmentNotRight")).toBe(false); + expect(serverValidationManager.hasVariantError(null, "MySegmentNotRight")).toBe(false); + + }); + + }); + describe('validation error subscriptions', function() { it('can subscribe to a field error', function() { @@ -228,11 +326,11 @@ propertyErrors: propertyErrors, allErrors: allErrors }; - }); + }, null); //act serverValidationManager.addFieldError("Name", "Required"); - serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1"); + serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1", null); //assert expect(args).not.toBeUndefined(); @@ -249,8 +347,8 @@ }; var cb2 = function () { }; - serverValidationManager.subscribe(null, null, "Name", cb1); - serverValidationManager.subscribe(null, null, "Title", cb2); + serverValidationManager.subscribe(null, null, "Name", cb1, null); + serverValidationManager.subscribe(null, null, "Title", cb2, null); //act serverValidationManager.addFieldError("Name", "Required"); @@ -284,7 +382,7 @@ propertyErrors: propertyErrors, allErrors: allErrors }; - }); + }, null); serverValidationManager.subscribe("myProperty", null, "", function (isValid, propertyErrors, allErrors) { numCalled++; @@ -293,12 +391,12 @@ propertyErrors: propertyErrors, allErrors: allErrors }; - }); + }, null); //act - serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1"); - serverValidationManager.addPropertyError("myProperty", null, "value2", "Some value 2"); - serverValidationManager.addPropertyError("myProperty", null, "", "Some value 3"); + serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1", null); + serverValidationManager.addPropertyError("myProperty", null, "value2", "Some value 2", null); + serverValidationManager.addPropertyError("myProperty", null, "", "Some value 3", null); //assert expect(args1).not.toBeUndefined(); @@ -335,7 +433,7 @@ propertyErrors: propertyErrors, allErrors: allErrors }; - }); + }, null); serverValidationManager.subscribe(null, "es-ES", null, function (isValid, propertyErrors, allErrors) { numCalled++; @@ -344,13 +442,13 @@ propertyErrors: propertyErrors, allErrors: allErrors }; - }); + }, null); //act - serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1"); - serverValidationManager.addPropertyError("myProperty", "en-US", "value1", "Some value 1"); - serverValidationManager.addPropertyError("myProperty", "en-US", "value2", "Some value 2"); - serverValidationManager.addPropertyError("myProperty", "fr-FR", "", "Some value 3"); + serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1", null); + serverValidationManager.addPropertyError("myProperty", "en-US", "value1", "Some value 1", null); + serverValidationManager.addPropertyError("myProperty", "en-US", "value2", "Some value 2", null); + serverValidationManager.addPropertyError("myProperty", "fr-FR", "", "Some value 3", null); //assert expect(args1).not.toBeUndefined(); diff --git a/src/Umbraco.Web.UI.Client/test/unit/utilities.spec.js b/src/Umbraco.Web.UI.Client/test/unit/utilities.spec.js new file mode 100644 index 0000000000..825ede8ea5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/test/unit/utilities.spec.js @@ -0,0 +1,55 @@ +(function () { + describe("Utilities", function () { + describe("toJson", function () { + it("should delegate to JSON.stringify", function () { + var spy = spyOn(JSON, "stringify").and.callThrough(); + + expect(Utilities.toJson({})).toEqual("{}"); + expect(spy).toHaveBeenCalled(); + }); + + it("should format objects pretty", function () { + expect(Utilities.toJson({ a: 1, b: 2 }, true)).toBe( + '{\n "a": 1,\n "b": 2\n}' + ); + expect(Utilities.toJson({ a: { b: 2 } }, true)).toBe( + '{\n "a": {\n "b": 2\n }\n}' + ); + expect(Utilities.toJson({ a: 1, b: 2 }, false)).toBe('{"a":1,"b":2}'); + expect(Utilities.toJson({ a: 1, b: 2 }, 0)).toBe('{"a":1,"b":2}'); + expect(Utilities.toJson({ a: 1, b: 2 }, 1)).toBe( + '{\n "a": 1,\n "b": 2\n}' + ); + expect(Utilities.toJson({ a: 1, b: 2 }, {})).toBe( + '{\n "a": 1,\n "b": 2\n}' + ); + }); + + it("should not serialize properties starting with $$", function () { + expect(Utilities.toJson({ $$some: "value" }, false)).toEqual("{}"); + }); + + it("should serialize properties starting with $", function () { + expect(Utilities.toJson({ $few: "v" }, false)).toEqual('{"$few":"v"}'); + }); + + it("should not serialize $window object", function () { + expect(Utilities.toJson(window)).toEqual('"$WINDOW"'); + }); + + it("should not serialize $document object", function () { + expect(Utilities.toJson(document)).toEqual('"$DOCUMENT"'); + }); + + it("should not serialize scope instances", inject(function ( + $rootScope + ) { + expect(Utilities.toJson({ key: $rootScope })).toEqual('{"key":"$SCOPE"}'); + })); + + it("should serialize undefined as undefined", function () { + expect(Utilities.toJson(undefined)).toEqual(undefined); + }); + }); + }); +})(); diff --git a/src/Umbraco.Web.UI.Docs/umb-docs.css b/src/Umbraco.Web.UI.Docs/umb-docs.css index 931c22b255..0f2e3e7f74 100644 --- a/src/Umbraco.Web.UI.Docs/umb-docs.css +++ b/src/Umbraco.Web.UI.Docs/umb-docs.css @@ -7,6 +7,21 @@ body { } +.container, .navbar-static-top .container, .navbar-fixed-top .container, .navbar-fixed-bottom .container { + max-width: 1500px; + width: 95%; +} + +.span3 { + width: 220px; + width: calc(90% / 12 * 3); +} + +.span9 { + width: 700px; + width: calc(90% / 12 * 9); +} + .h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6 { font-family: inherit; font-weight: 400; diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index a2dfa0ffd2..1d77505fc6 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -86,7 +86,7 @@ - + @@ -345,9 +345,9 @@ False True - 8600 + 8700 / - http://localhost:8600 + http://localhost:8700 False False diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/EditProfile.cshtml b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/EditProfile.cshtml index 7034404ca3..74ec033f25 100644 --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/EditProfile.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/EditProfile.cshtml @@ -23,7 +23,7 @@ { if (success) { - @* This message will show if RedirectOnSucces is set to false (default) *@ + @* This message will show if profileModel.RedirectUrl is not defined (default) *@

    Profile updated

    } diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Gallery.cshtml b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Gallery.cshtml index 5aa09a3cb3..64be8eb60c 100755 --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Gallery.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Gallery.cshtml @@ -29,13 +29,13 @@ @* a single image *@ if (media.IsDocumentType("Image")) { - @Render(media); + @Render(media) } @* a folder with images under it *@ foreach (var image in media.Children()) { - @Render(image); + @Render(image) } }
    diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/RegisterMember.cshtml b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/RegisterMember.cshtml index 17ed95ea31..804e2307f0 100644 --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/RegisterMember.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/RegisterMember.cshtml @@ -45,7 +45,7 @@ @if (success) { - @* This message will show if RedirectOnSucces is set to false (default) *@ + @* This message will show if registerModel.RedirectUrl is not defined (default) *@

    Registration succeeded.

    } else diff --git a/src/Umbraco.Web.UI/Umbraco/Views/Default.cshtml b/src/Umbraco.Web.UI/Umbraco/Views/Default.cshtml index 056d2b6f51..bd8d23a851 100644 --- a/src/Umbraco.Web.UI/Umbraco/Views/Default.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/Views/Default.cshtml @@ -66,11 +66,7 @@
    -
    -
    - -
    @@ -93,7 +89,7 @@
    - + + diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml index 9f233a1f8c..fe7e8ac638 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml @@ -81,7 +81,7 @@ Uložit Uložit a publikovat Uložit a odeslat ke schválení - Náhled + Náhled Náhled je deaktivován, protože není přiřazena žádná šablona Vybrat styl Zobrazit styly diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml index 5b4d1c9afa..7cead86114 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml @@ -36,7 +36,9 @@ Sæt rettigheder for siden %0% Vælg hvor du vil kopiere Vælg hvortil du vil flytte + Vælg hvor du vil flytte de valgte elementer hen til i træstrukturen nedenfor + Vælg hvor du vil kopiere de valgte elementer til blev flyttet til blev kopieret til blev slettet @@ -138,7 +140,7 @@ Gem og send til udgivelse Gem listevisning Planlæg - Forhåndsvisning + Forhåndsvisning Forhåndsvisning er deaktiveret fordi der ikke er nogen skabelon tildelt Vælg formattering Vis koder @@ -447,6 +449,7 @@ Se cache element Relatér til original Inkludér undersider + Det venligste community Link til side Åben linket i et nyt vindue eller fane Link til medie @@ -541,9 +544,6 @@ #value eller ?key=value Indtast alias... Genererer alias... - Opret element - Rediger - Navn Opret brugerdefineret listevisning @@ -663,7 +663,7 @@ Ikon Id Importer - Inkludér undermapper i søgning + Søg kun i denne mappe Info Indre margen Indsæt @@ -753,6 +753,9 @@ nuværende Indlejring valgt + Andet + Artikler + Videoer Blå @@ -779,6 +782,7 @@ Generelt Editor Skift tillad sprogvarianter + Skift tillad segmentering Baggrundsfarve @@ -1085,6 +1089,7 @@ Mange hilsner fra Umbraco robotten Tilføj ny beskæring Acceptér Fortryd + Brugerdefineret Vælg en version at sammenligne med den nuværende version @@ -1117,7 +1122,10 @@ Mange hilsner fra Umbraco robotten Formularer + Tours De bedste Umbraco video tutorials + Besøg our.umbraco.com + Besøg umbraco.tv Standardskabelon @@ -1403,9 +1411,16 @@ Mange hilsner fra Umbraco robotten fane har ingen sorteringsrækkefølge Hvor er denne komposition brugt? Denne komposition brugt i kompositionen af de følgende indholdstyper: - Tillad sprogvariation + Tillad variationer + Tillad sprogvariation + Tillad segmentering + Tillader sprogvariationer + Tillader segmentering Tillad at redaktører kan oprette indhold af denne type på flere sprog. + Tillad at redaktører kan oprette dette indhold på flere sprog. + Tillad at redaktører kan oprette flere udgaver af denne type indhold. Tillad sprogvariation + Tillad segmentering Element-type Er en Element-type En Element-type er tiltænkt brug i f.eks. Nested Content, ikke i indholdstræet. @@ -1737,6 +1752,9 @@ Mange hilsner fra Umbraco robotten Aktivt sprog Skift sprog til Opret ny mappe + Opret element + Rediger + Navn Referencer diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml index e45d97e7bd..529074f24d 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml @@ -140,7 +140,7 @@ Speichern und zur Abnahme übergeben Listenansicht sichern Veröffentlichung planen - Vorschau + Vorschau Die Vorschaufunktion ist deaktiviert, da keine Vorlage zugewiesen ist Stil auswählen Stil anzeigen @@ -1173,7 +1173,7 @@ Installiert Installierte Pakete Lokale Installation - Benenden + Abschließen Diese Paket hat keine Einstellungen Es wurden noche keine Pakete angelegt Sie haben keine Pakete installiert diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index f61cc767a0..d7ccb13193 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -1,2369 +1,2416 @@ - - - - The Umbraco community - https://our.umbraco.com/documentation/Extending-Umbraco/Language-Files - - - Culture and Hostnames - Audit Trail - Browse Node - Change Document Type - Change Data Type - Copy - Create - Export - Create Package - Create group - Delete - Disable - Empty recycle bin - Enable - Export Document Type - Import Document Type - Import Package - Edit in Canvas - Exit - Move - Notifications - Public access - Publish - Unpublish - Reload - Republish entire site - Rename - Restore - Set permissions for the page %0% - Choose where to copy - Choose where to move - to in the tree structure below - was moved to - was copied to - was deleted - Permissions - Rollback - Send To Publish - Send To Translation - Set group - Sort - Translate - Update - Set permissions - Unlock - Create Content Template - Resend Invitation - - - Content - Administration - Structure - Other - - - Allow access to assign culture and hostnames - Allow access to view a node's history log - Allow access to view a node - Allow access to change document type for a node - Allow access to copy a node - Allow access to create nodes - Allow access to delete nodes - Allow access to move a node - Allow access to set and change public access for a node - Allow access to publish a node - Allow access to unpublish a node - Allow access to change permissions for a node - Allow access to roll back a node to a previous state - Allow access to send a node for approval before publishing - Allow access to send a node for translation - Allow access to change the sort order for nodes - Allow access to translate a node - Allow access to save a node - Allow access to create a Content Template - - - Content - Info - - - Permission denied. - Add new Domain - remove - Invalid node. - One or more domains have an invalid format. - Domain has already been assigned. - Language - Domain - New domain '%0%' has been created - Domain '%0%' is deleted - Domain '%0%' has already been assigned - Domain '%0%' has been updated - Edit Current Domains - - - Inherit - Culture - - or inherit culture from parent nodes. Will also apply
    - to the current node, unless a domain below applies too.]]> -
    - Domains - - - Clear selection - Select - Do something else - Bold - Cancel Paragraph Indent - Insert form field - Insert graphic headline - Edit Html - Indent Paragraph - Italic - Center - Justify Left - Justify Right - Insert Link - Insert local link (anchor) - Bullet List - Numeric List - Insert macro - Insert picture - Publish and close - Publish with descendants - Edit relations - Return to list - Save - Save and close - Save and publish - Save and schedule - Save and send for approval - Save list view - Schedule - Preview - Preview is disabled because there's no template assigned - Choose style - Show styles - Insert table - Save and generate models - Undo - Redo - Delete tag - Cancel - Confirm - More publishing options - - - Viewing for - Content deleted - Content unpublished - Content saved and Published - Content saved and published for languages: %0% - Content saved - Content saved for languages: %0% - Content moved - Content copied - Content rolled back - Content sent for publishing - Content sent for publishing for languages: %0% - Sort child items performed by user - Copy - Publish - Publish - Move - Save - Save - Delete - Unpublish - Rollback - Send To Publish - Send To Publish - Sort - History (all variants) - - - To change the document type for the selected content, first select from the list of valid types for this location. - Then confirm and/or amend the mapping of properties from the current type to the new, and click Save. - The content has been re-published. - Current Property - Current type - The document type cannot be changed, as there are no alternatives valid for this location. An alternative will be valid if it is allowed under the parent of the selected content item and that all existing child content items are allowed to be created under it. - Document Type Changed - Map Properties - Map to Property - New Template - New Type - none - Content - Select New Document Type - The document type of the selected content has been successfully changed to [new type] and the following properties mapped: - to - Could not complete property mapping as one or more properties have more than one mapping defined. - Only alternate types valid for the current location are displayed. - - - Failed to create a folder under parent with ID %0% - Failed to create a folder under parent with name %0% - The folder name cannot contain illegal characters. - Failed to delete item: %0% - - - Is Published - About this page - Alias - (how would you describe the picture over the phone) - Alternative Links - Click to edit this item - Created by - Original author - Updated by - Created - Date/time this document was created - Document Type - Editing - Remove at - This item has been changed after publication - This item is not published - Last published - There are no items to show - There are no items to show in the list. - No content has been added - No members have been added - Media Type - Link to media item(s) - Member Group - Role - Member Type - No changes have been made - No date chosen - Page title - This media item has no link - Properties - This document is published but is not visible because the parent '%0%' is unpublished - This culture is published but is not visible because it is unpublished on parent '%0%' - This document is published but is not in the cache - Could not get the url - This document is published but its url would collide with content %0% - This document is published but its url cannot be routed - Publish - Published - Published (pending changes) - Publication Status - Publish with descendants to publish %0% and all content items underneath and thereby making their content publicly available.]]> - Publish with descendants to publish the selected languages and the same languages of content items underneath and thereby making their content publicly available.]]> - Publish at - Unpublish at - Clear Date - Set date - Sortorder is updated - To sort the nodes, simply drag the nodes or click one of the column headers. You can select multiple nodes by holding the "shift" or "control" key while selecting - Statistics - Title (optional) - Alternative text (optional) - Type - Unpublish - Unpublished - Last edited - Date/time this document was edited - Remove file(s) - Click here to remove the image from the media item - Click here to remove the file from the media item - Link to document - Member of group(s) - Not a member of group(s) - Child items - Target - This translates to the following time on the server: - What does this mean?]]> - Are you sure you want to delete this item? - Property %0% uses editor %1% which is not supported by Nested Content. - Are you sure you want to delete all items? - No content types are configured for this property. - Add element type - Select element type - Add another text box - Remove this text box - Content root - Include drafts: also publish unpublished content items. - This value is hidden. If you need access to view this value please contact your website administrator. - This value is hidden. - What languages would you like to publish? All languages with content are saved! - What languages would you like to publish? - What languages would you like to save? - All languages with content are saved on creation! - What languages would you like to send for approval? - What languages would you like to schedule? - Select the languages to unpublish. Unpublishing a mandatory language will unpublish all languages. - Published Languages - Unpublished Languages - Unmodified Languages - These languages haven't been created - Ready to Publish? - Ready to Save? - Send for approval - Select the date and time to publish and/or unpublish the content item. - Create new - Paste from clipboard - This item is in the Recycle Bin - - - Create a new Content Template from '%0%' - Blank - Select a Content Template - Content Template created - A Content Template was created from '%0%' - Another Content Template with the same name already exists - A Content Template is predefined content that an editor can select to use as the basis for creating new content - - - Click to upload - or click here to choose files - You can drag files here to upload - Cannot upload this file, it does not have an approved file type - Max file size is - Media root - Failed to move media - Failed to copy media - Failed to create a folder under parent id %0% - Failed to rename the folder with id %0% - Drag and drop your file(s) into the area - - - Create a new member - All Members - Member groups have no additional properties for editing. - - - Where do you want to create the new %0% - Create an item under - Select the document type you want to make a content template for - Enter a folder name - Choose a type and a title - Document Types within the Settings section, by editing the Allowed child node types under Permissions.]]> - Document Types within the Settings section.]]> - The selected page in the content tree doesn't allow for any pages to be created below it. - Edit permissions for this document type - Create a new document type - Document Types within the Settings section, by changing the Allow as root option under Permissions.]]> - Media Types Types within the Settings section, by editing the Allowed child node types under Permissions.]]> - The selected media in the tree doesn't allow for any other media to be created below it. - Edit permissions for this media type - Document Type without a template - New folder - New data type - New JavaScript file - New empty partial view - New partial view macro - New partial view from snippet - New partial view macro from snippet - New partial view macro (without macro) - New style sheet file - New Rich Text Editor style sheet file - - - Browse your website - - Hide - If Umbraco isn't opening, you might need to allow popups from this site - has opened in a new window - Restart - Visit - Welcome - - - Stay - Discard changes - You have unsaved changes - Are you sure you want to navigate away from this page? - you have unsaved changes - Publishing will make the selected items visible on the site. - Unpublishing will remove the selected items and all their descendants from the site. - Unpublishing will remove this page and all its descendants from the site. - You have unsaved changes. Making changes to the Document Type will discard the changes. - - - Done - Deleted %0% item - Deleted %0% items - Deleted %0% out of %1% item - Deleted %0% out of %1% items - Published %0% item - Published %0% items - Published %0% out of %1% item - Published %0% out of %1% items - Unpublished %0% item - Unpublished %0% items - Unpublished %0% out of %1% item - Unpublished %0% out of %1% items - Moved %0% item - Moved %0% items - Moved %0% out of %1% item - Moved %0% out of %1% items - Copied %0% item - Copied %0% items - Copied %0% out of %1% item - Copied %0% out of %1% items - - - Link title - Link - Anchor / querystring - Name - Manage hostnames - Close this window - Are you sure you want to delete - Are you sure you want to disable - Are you sure? - Are you sure? - Cut - Edit Dictionary Item - Edit Language - Edit selected media - Insert local link - Insert character - Insert graphic headline - Insert picture - Insert link - Click to add a Macro - Insert table - This will delete the language - Changing the culture for a language may be an expensive operation and will result in the content cache and indexes being rebuilt - Last Edited - Link - Internal link: - When using local links, insert "#" in front of link - Open in new window? - Macro Settings - This macro does not contain any properties you can edit - Paste - Edit permissions for - Set permissions for - Set permissions for %0% for user group %1% - Select the users groups you want to set permissions for - The items in the recycle bin are now being deleted. Please do not close this window while this operation takes place - The recycle bin is now empty - When items are deleted from the recycle bin, they will be gone forever - regexlib.com's webservice is currently experiencing some problems, which we have no control over. We are very sorry for this inconvenience.]]> - Search for a regular expression to add validation to a form field. Example: 'email, 'zip-code' 'url' - Remove Macro - Required Field - Site is reindexed - The website cache has been refreshed. All publish content is now up to date. While all unpublished content is still unpublished - The website cache will be refreshed. All published content will be updated, while unpublished content will stay unpublished. - Number of columns - Number of rows - Click on the image to see full size - Pick item - View Cache Item - Relate to original - Include descendants - The friendliest community - Link to page - Opens the linked document in a new window or tab - Link to media - Select content start node - Select media - Select media type - Select icon - Select item - Select link - Select macro - Select content - Select content type - Select media start node - Select member - Select member group - Select member type - Select node - Select sections - Select users - No icons were found - There are no parameters for this macro - There are no macros available to insert - External login providers - Exception Details - Stacktrace - Inner Exception - Link your - Un-link your - account - Select editor - Select snippet - This will delete the node and all its languages. If you only want to delete one language, you should unpublish the node in that language instead. - - - There are no dictionary items. - - - %0%' below - ]]> - Culture Name - - Dictionary overview - - - Configured Searchers - Shows properties and tools for any configured Searcher (i.e. such as a multi-index searcher) - Field values - Health status - The health status of the index and if it can be read - Indexers - Index info - Lists the properties of the index - Manage Examine's indexes - Allows you to view the details of each index and provides some tools for managing the indexes - Rebuild index - - 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. - ]]> -
    - Searchers - Search the index and view the results - Tools - Tools to manage the index - fields - The index cannot be read and will need to be rebuilt - The process is taking longer than expected, check the umbraco log to see if there have been any errors during this operation - This index cannot be rebuilt because it has no assigned - IIndexPopulator - - - Enter your username - Enter your password - Confirm your password - Name the %0%... - Enter a name... - Enter an email... - Enter a username... - Label... - Enter a description... - Type to search... - Type to filter... - Type to add tags (press enter after each tag)... - Enter your email - Enter a message... - Your username is usually your email - #value or ?key=value - Enter alias... - Generating alias... - Create item - Create - Edit - Name - - - Create custom list view - Remove custom list view - A content type, media type or member type with this alias already exists - - - Renamed - Enter a new folder name here - %0% was renamed to %1% - - - Add prevalue - Database datatype - Property editor GUID - Property editor - Buttons - Enable advanced settings for - Enable context menu - Maximum default size of inserted images - Related stylesheets - Show label - Width and height - All property types & property data - using this data type will be deleted permanently, please confirm you want to delete these as well - Yes, delete - and all property types & property data using this data type - Select the folder to move - to in the tree structure below - was moved underneath - - - Your data has been saved, but before you can publish this page there are some errors you need to fix first: - The current membership provider does not support changing password (EnablePasswordRetrieval need to be true) - %0% already exists - There were errors: - There were errors: - The password should be a minimum of %0% characters long and contain at least %1% non-alpha numeric character(s) - %0% must be an integer - The %0% field in the %1% tab is mandatory - %0% is a mandatory field - %0% at %1% is not in a correct format - %0% is not in a correct format - - - Received an error from the server - The specified file type has been disallowed by the administrator - 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%) - 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? - Startnode deleted, please contact your administrator - Please mark content before changing style - No active styles available - Please place cursor at the left of the two cells you wish to merge - You cannot split a cell that hasn't been merged. - This property is invalid - - - About - Action - Actions - Add - Alias - All - Are you sure? - Back - Back to overview - Border - by - Cancel - Cell margin - Choose - Clear - Close - Close Window - Comment - Confirm - Constrain - Constrain proportions - Content - Continue - Copy - Create - Database - Date - Default - Delete - Deleted - Deleting... - Design - Dictionary - Dimensions - Down - Download - Edit - Edited - Elements - Email - Error - Field - Find - First - Focal point - General - Groups - Group - Height - Help - Hide - History - Icon - Id - Import - Include subfolders in search - Info - Inner margin - Insert - Install - Invalid - Justify - Label - Language - Last - Layout - Links - Loading - Locked - Login - Log off - Logout - Macro - Mandatory - Message - Move - Name - New - Next - No - of - Off - OK - Open - Options - On - or - Order by - Password - Path - One moment please... - Previous - Properties - Rebuild - Email to receive form data - Recycle Bin - Your recycle bin is empty - Reload - Remaining - Remove - Rename - Renew - Required - Retrieve - Retry - Permissions - Scheduled Publishing - Search - Sorry, we can not find what you are looking for. - No items have been added - Server - Settings - Show - Show page on Send - Size - Sort - Status - Submit - Type - Type to search... - under - Up - Update - Upgrade - Upload - Url - User - Username - Value - View - Welcome... - Width - Yes - Folder - Search results - Reorder - I am done reordering - Preview - Change password - to - List view - Saving... - current - Embed - selected - - - Blue - - - Add group - Add property - Add editor - Add template - Add child node - Add child - Edit data type - Navigate sections - Shortcuts - show shortcuts - Toggle list view - Toggle allow as root - Comment/Uncomment lines - Remove line - Copy Lines Up - Copy Lines Down - Move Lines Up - Move Lines Down - General - Editor - Toggle allow culture variants - - - Background colour - Bold - Text colour - Font - Text - - - Page - - - The installer cannot connect to the database. - Could not save the web.config file. Please modify the connection string manually. - Your database has been found and is identified as - Database configuration - - install button to install the Umbraco %0% database - ]]> - - Next to proceed.]]> - Database not found! Please check that the information in the "connection string" of the "web.config" file is correct.

    -

    To proceed, please edit the "web.config" file (using Visual Studio or your favourite text editor), scroll to the bottom, add the connection string for your database in the key named "UmbracoDbDSN" and save the file.

    -

    - Click the retry button when - done.
    - More information on editing web.config here.

    ]]>
    - - Please contact your ISP if necessary. - If you're installing on a local machine or server you might need information from your system administrator.]]> - - Press the upgrade button to upgrade your database to Umbraco %0%

    -

    - Don't worry - no content will be deleted and everything will continue working afterwards! -

    - ]]>
    - Press Next to - proceed. ]]> - next to continue the configuration wizard]]> - The Default users' password needs to be changed!]]> - The Default user has been disabled or has no access to Umbraco!

    No further actions needs to be taken. Click Next to proceed.]]> - The Default user's password has been successfully changed since the installation!

    No further actions needs to be taken. Click Next to proceed.]]> - The password is changed! - Get a great start, watch our introduction videos - By clicking the next button (or modifying the umbracoConfigurationStatus in web.config), you accept the license for this software as specified in the box below. Notice that this Umbraco distribution consists of two different licenses, the open source MIT license for the framework and the Umbraco freeware license that covers the UI. - Not installed yet. - Affected files and folders - More information on setting up permissions for Umbraco here - You need to grant ASP.NET modify permissions to the following files/folders - Your permission settings are almost perfect!

    - You can run Umbraco without problems, but you will not be able to install packages which are recommended to take full advantage of Umbraco.]]>
    - How to Resolve - Click here to read the text version - video tutorial on setting up folder permissions for Umbraco or read the text version.]]> - Your permission settings might be an issue! -

    - You can run Umbraco without problems, but you will not be able to create folders or install packages which are recommended to take full advantage of Umbraco.]]>
    - Your permission settings are not ready for Umbraco! -

    - In order to run Umbraco, you'll need to update your permission settings.]]>
    - Your permission settings are perfect!

    - You are ready to run Umbraco and install packages!]]>
    - Resolving folder issue - Follow this link for more information on problems with ASP.NET and creating folders - Setting up folder permissions - - I want to start from scratch - learn how) - You can still choose to install Runway later on. Please go to the Developer section and choose Packages. - ]]> - You've just set up a clean Umbraco platform. What do you want to do next? - Runway is installed - - This is our list of recommended modules, check off the ones you would like to install, or view the full list of modules - ]]> - Only recommended for experienced users - I want to start with a simple website - - "Runway" is a simple website providing some basic document types and templates. The installer can set up Runway for you automatically, - but you can easily edit, extend or remove it. It's not necessary and you can perfectly use Umbraco without it. However, - Runway offers an easy foundation based on best practices to get you started faster than ever. - If you choose to install Runway, you can optionally select basic building blocks called Runway Modules to enhance your Runway pages. -

    - - Included with Runway: Home page, Getting Started page, Installing Modules page.
    - Optional Modules: Top Navigation, Sitemap, Contact, Gallery. -
    - ]]>
    - What is Runway - Step 1/5 Accept license - Step 2/5: Database configuration - Step 3/5: Validating File Permissions - Step 4/5: Check Umbraco security - Step 5/5: Umbraco is ready to get you started - Thank you for choosing Umbraco - Browse your new site -You installed Runway, so why not see how your new website looks.]]> - Further help and information -Get help from our award winning community, browse the documentation or watch some free videos on how to build a simple site, how to use packages and a quick guide to the Umbraco terminology]]> - Umbraco %0% is installed and ready for use - /web.config file and update the AppSetting key UmbracoConfigurationStatus in the bottom to the value of '%0%'.]]> - started instantly by clicking the "Launch Umbraco" button below.
    If you are new to Umbraco, -you can find plenty of resources on our getting started pages.]]>
    - Launch Umbraco -To manage your website, simply open the Umbraco back office and start adding content, updating the templates and stylesheets or add new functionality]]> - Connection to database failed. - Umbraco Version 3 - Umbraco Version 4 - Watch - Umbraco %0% for a fresh install or upgrading from version 3.0. -

    - Press "next" to start the wizard.]]>
    - - - Culture Code - Culture Name - - - You've been idle and logout will automatically occur in - Renew now to save your work - - - Happy super Sunday - Happy manic Monday - Happy tubular Tuesday - Happy wonderful Wednesday - Happy thunderous Thursday - Happy funky Friday - Happy Caturday - Log in below - Sign in with - Session timed out - © 2001 - %0%
    Umbraco.com

    ]]>
    - Forgotten password? - An email will be sent to the address specified with a link to reset your password - An email with password reset instructions will be sent to the specified address if it matched our records - Show password - Hide password - Return to login form - Please provide a new password - Your Password has been updated - The link you have clicked on is invalid or has expired - Umbraco: Reset Password - - - - - - - - - - - -
    - - - - - -
    - -
    - -
    -
    - - - - - - -
    -
    -
    - - - - -
    - - - - -
    -

    - Password reset requested -

    -

    - Your username to login to the Umbraco back-office is: %0% -

    -

    - - - - - - -
    - - Click this link to reset your password - -
    -

    -

    If you cannot click on the link, copy and paste this URL into your browser window:

    - - - - -
    - - %1% - -
    -

    -
    -
    -


    -
    -
    - - - ]]>
    - - - Dashboard - Sections - Content - - - Choose page above... - %0% has been copied to %1% - Select where the document %0% should be copied to below - %0% has been moved to %1% - Select where the document %0% should be moved to below - has been selected as the root of your new content, click 'ok' below. - No node selected yet, please select a node in the list above before clicking 'ok' - The current node is not allowed under the chosen node because of its type - The current node cannot be moved to one of its subpages - The current node cannot exist at the root - The action isn't allowed since you have insufficient permissions on 1 or more child documents. - Relate copied items to original - - - %0%]]> - Notification settings saved for - - The following languages have been modified %0% - - - - - - - - - - - -
    - - - - - -
    - -
    - -
    -
    - - - - - - -
    -
    -
    - - - - -
    - - - - -
    -

    - Hi %0%, -

    -

    - This is an automated mail to inform you that the task '%1%' has been performed on the page '%2%' by the user '%3%' -

    - - - - - - -
    - -
    - EDIT
    -
    -

    -

    Update summary:

    - %6% -

    -

    - Have a nice day!

    - Cheers from the Umbraco robot -

    -
    -
    -


    -
    -
    - - - ]]>
    - The following languages have been modified:

    - %0% - ]]>
    - [%0%] Notification about %1% performed on %2% - Notifications - - - Actions - Created - Create package - - button and locating the package. Umbraco packages usually have a ".umb" or ".zip" extension. - ]]> - This will delete the package - Drop to upload - Include all child nodes - or click here to choose package file - Upload package - Install a local package by selecting it from your machine. Only install packages from sources you know and trust - Upload another package - Cancel and upload another package - I accept - terms of use - - Path to file - Absolute path to file (ie: /bin/umbraco.bin) - Installed - Installed packages - Install local - Finish - This package has no configuration view - No packages have been created yet - You don’t have any packages installed - 'Packages' icon in the top right of your screen]]> - Package Actions - Author URL - Package Content - Package Files - Icon URL - Install package - License - License URL - Package Properties - Search for packages - Results for - We couldn’t find anything for - Please try searching for another package or browse through the categories - Popular - New releases - has - karma points - Information - Owner - Contributors - Created - Current version - .NET version - Downloads - Likes - Compatibility - This package is compatible with the following versions of Umbraco, as reported by community members. Full compatability cannot be guaranteed for versions reported below 100% - External sources - Author - Documentation - Package meta data - Package name - Package doesn't contain any items -
    - You can safely remove this from the system by clicking "uninstall package" below.]]>
    - Package options - Package readme - Package repository - Confirm package uninstall - Package was uninstalled - The package was successfully uninstalled - Uninstall package - - Notice: any documents, media etc depending on the items you remove, will stop working, and could lead to system instability, - so uninstall with caution. If in doubt, contact the package author.]]> - Package version - Package already installed - This package cannot be installed, it requires a minimum Umbraco version of - Uninstalling... - Downloading... - Importing... - Installing... - Restarting, please wait... - All done, your browser will now refresh, please wait... - Please click 'Finish' to complete installation and reload the page. - Uploading package... - - - Paste with full formatting (Not recommended) - The text you're trying to paste contains special characters or formatting. This could be caused by copying text from Microsoft Word. Umbraco can remove special characters or formatting automatically, so the pasted content will be more suitable for the web. - Paste as raw text without any formatting at all - Paste, but remove formatting (Recommended) - - - Group based protection - If you want to grant access to all members of specific member groups - You need to create a member group before you can use group based authentication - Error Page - Used when people are logged on, but do not have access - %0%]]> - %0% is now protected]]> - %0%]]> - Login Page - Choose the page that contains the login form - Remove protection... - %0%?]]> - Select the pages that contain login form and error messages - %0%]]> - %0%]]> - Specific members protection - If you wish to grant access to specific members - - - - - - - - - Include unpublished subpages - Publishing in progress - please wait... - %0% out of %1% pages have been published... - %0% has been published - %0% and subpages have been published - Publish %0% and all its subpages - Publish to publish %0% and thereby making its content publicly available.

    - You can publish this page and all its subpages by checking Include unpublished subpages below. - ]]>
    - - - You have not configured any approved colours - - - You can only select items of type(s): %0% - You have picked a content item currently deleted or in the recycle bin - You have picked content items currently deleted or in the recycle bin - - - Deleted item - You have picked a media item currently deleted or in the recycle bin - You have picked media items currently deleted or in the recycle bin - Trashed - - - enter external link - choose internal page - Caption - Link - Open in new window - enter the display caption - Enter the link - - - Reset crop - Save crop - Add new crop - Done - Undo edits - - - Select a version to compare with the current version - Current version - Red text will not be shown in the selected version. , green means added]]> - Document has been rolled back - This displays the selected version as HTML, if you wish to see the difference between 2 versions at the same time, use the diff view - Rollback to - Select version - View - - - Edit script file - - - Concierge - Content - Courier - Developer - Forms - Help - Umbraco Configuration Wizard - Media - Members - Newsletters - Packages - Settings - Statistics - Translation - Users - - - The best Umbraco video tutorials - - - Default template - To import a document type, find the ".udt" file on your computer by clicking the "Browse" button and click "Import" (you'll be asked for confirmation on the next screen) - New Tab Title - Node type - Type - Stylesheet - Script - Tab - Tab Title - Tabs - Master Content Type enabled - This Content Type uses - as a Master Content Type. Tabs from Master Content Types are not shown and can only be edited on the Master Content Type itself - No properties defined on this tab. Click on the "add a new property" link at the top to create a new property. - Create matching template - Add icon - - - Sort order - Creation date - Sorting complete. - Drag the different items up or down below to set how they should be arranged. Or click the column headers to sort the entire collection of items - - - - Validation - Validation errors must be fixed before the item can be saved - Failed - Saved - 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 - DataType: %1%]]> - Propertytype deleted - Document Type saved - Tab created - Tab deleted - Tab with id: %0% deleted - Stylesheet not saved - Stylesheet saved - Stylesheet saved without any errors - Datatype saved - Dictionary item saved - Publishing failed because the parent page isn't published - Content published - and visible on the website - Content saved - Remember to publish to make changes visible - Sent For Approval - Changes have been sent for approval - Media saved - Member group saved - Media saved without any errors - Member saved - Stylesheet Property Saved - Stylesheet saved - Template saved - Error saving user (check log) - User Saved - User type saved - User group saved - Cultures and hostnames saved - Error saving cultures and hostnames - File not saved - file could not be saved. Please check file permissions - File saved - File saved without any errors - Language saved - Media Type saved - Member Type saved - Member Group saved - Template not saved - Please make sure that you do not have 2 templates with the same alias - Template saved - Template saved without any errors! - Content unpublished - Partial view saved - Partial view saved without any errors! - Partial view not saved - An error occurred saving the file. - Permissions saved for - Deleted %0% user groups - %0% was deleted - Enabled %0% users - Disabled %0% users - %0% is now enabled - %0% is now disabled - User groups have been set - Unlocked %0% users - %0% is now unlocked - Member was exported to file - An error occurred while exporting the member - User %0% was deleted - Invite user - Invitation has been re-sent to %0% - Document type was exported to file - An error occurred while exporting the document type - - - Add style - Edit style - Rich text editor styles - Define the styles that should be available in the rich text editor for this stylesheet - Edit stylesheet - Edit stylesheet property - The name displayed in the editor style selector - Preview - How the text will look like in the rich text editor. - Selector - Uses CSS syntax, e.g. "h1" or ".redHeader" - Styles - The CSS that should be applied in the rich text editor, e.g. "color:red;" - Code - Editor - - - Failed to delete template with ID %0% - Edit template - Sections - Insert content area - Insert content area placeholder - Insert - Choose what to insert into your template - Dictionary item - A dictionary item is a placeholder for a translatable piece of text, which makes it easy to create designs for multilingual websites. - Macro - - A Macro is a configurable component which is great for - reusable parts of your design, where you need the option to provide parameters, - such as galleries, forms and lists. - - Value - Displays the value of a named field from the current page, with options to modify the value or fallback to alternative values. - Partial view - - A partial view is a separate template file which can be rendered inside another - template, it's great for reusing markup or for separating complex templates into separate files. - - Master template - No master - Render child template - @RenderBody()
    placeholder. - ]]> - Define a named section - @section { ... }. This can be rendered in a - specific area of the parent of this template, by using @RenderSection. - ]]> - Render a named section - @RenderSection(name) placeholder. - This renders an area of a child template which is wrapped in a corresponding @section [name]{ ... } definition. - ]]> - Section Name - Section is mandatory - - If mandatory, the child template must contain a @section definition, otherwise an error is shown. - - Query builder - items returned, in - copy to clipboard - I want - all content - content of type "%0%" - from - my website - where - and - is - is not - before - before (including selected date) - after - after (including selected date) - equals - does not equal - contains - does not contain - greater than - greater than or equal to - less than - less than or equal to - Id - Name - Created Date - Last Updated Date - order by - ascending - descending - Template - - - Image - Macro - 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 - Allow all editors - Allow all row configurations - Maximum items - Leave blank or set to 0 for unlimited - Set as default - Choose extra - Choose default - are added - Warning - You are deleting the row configuration - - Deleting a row configuration name will result in loss of data for any existing content that is based on this configuration. - - - - Compositions - Group - You have not added any groups - Add group - Inherited from - Add property - Required label - Enable list view - Configures the content item to show a sortable and searchable list of its children, the children will not be shown in the tree - Allowed Templates - Choose which templates editors are allowed to use on content of this type - Allow as root - Allow editors to create content of this type in the root of the content tree. - Allowed child node types - Allow content of the specified types to be created underneath content of this type. - Choose child node - 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. - There are no content types available to use as a composition. - Removing a composition will delete all the associated property data. Once you save the document type there's no way back. - Create new - Use existing - Editor settings - Configuration - Yes, delete - was moved underneath - was copied underneath - Select the folder to move - Select the folder to copy - to in the tree structure below - All Document types - All Documents - All media items - using this document type will be deleted permanently, please confirm you want to delete these as well. - using this media type will be deleted permanently, please confirm you want to delete these as well. - using this member type will be deleted permanently, please confirm you want to delete these as well - and all documents using this type - and all media items using this type - and all members using this type - Member can edit - Allow this property value to be edited by the member on their profile page - Is sensitive data - Hide this property value from content editors that don't have access to view sensitive information - Show on member profile - Allow this property value to be displayed on the member profile page - tab has no sort order - Where is this composition used? - This composition is currently used in the composition of the following content types: - Allow varying by culture - Allow editors to create content of this type in different languages. - Allow varying by culture - Element type - Is an Element type - An Element type is meant to be used for instance in Nested Content, and not in the tree. - This is not applicable for an Element type - You have made changes to this property. Are you sure you want to discard them? - - - Add language - Mandatory language - Properties on this language have to be filled out before the node can be published. - Default language - An Umbraco site can only have one default language set. - Switching default language may result in default content missing. - Falls back to - No fall back language - To allow multi-lingual content to fall back to another language if not present in the requested language, select it here. - Fall back language - none - - - - Add parameter - Edit parameter - Enter macro name - Parameters - Define the parameters that should be available when using this macro. - Select partial view macro file - - - Building models - this can take a bit of time, don't worry - Models generated - Models could not be generated - Models generation has failed, see exception in U log - - - Add fallback field - Fallback field - Add default value - Default value - Fallback field - Default value - Casing - Encoding - Choose field - Convert line breaks - Yes, convert line breaks - Replaces line breaks with 'br' html tag - Custom Fields - Date only - Format and encoding - Format as date - Format the value as a date, or a date with time, according to the active culture - HTML encode - Will replace special characters by their HTML equivalent. - Will be inserted after the field value - Will be inserted before the field value - Lowercase - Modify output - None - Output sample - Insert after field - Insert before field - Recursive - Yes, make it recursive - Separator - Standard Fields - Uppercase - URL encode - Will format special characters in URLs - Will only be used when the field values above are empty - This field will only be used if the primary field is empty - Date and time - - - Translation details - Download XML DTD - Fields - Include subpages - - No translator users found. Please create a translator user before you start sending content to translation - The page '%0%' has been send to translation - Send the page '%0%' to translation - Total words - Translate to - Translation completed. - You can preview the pages, you've just translated, by clicking below. If the original page is found, you will get a comparison of the 2 pages. - Translation failed, the XML file might be corrupt - Translation options - Translator - Upload translation XML - - - Content - Content Templates - Media - Cache Browser - Recycle Bin - Created packages - Data Types - Dictionary - Installed packages - Install skin - Install starter kit - Languages - Install local package - Macros - Media Types - Members - Member Groups - Member Roles - Member Types - Document Types - Relation Types - Packages - Packages - Partial Views - Partial View Macro Files - Install from repository - Install Runway - Runway modules - Scripting Files - Scripts - Stylesheets - Templates - Log Viewer - Users - Settings - Templating - Third Party - - - New update ready - %0% is ready, click here for download - No connection to server - Error checking for update. Please review trace-stack for further information - - - Access - Based on the assigned groups and start nodes, the user has access to the following nodes - Assign access - Administrator - Category field - User created - Change Your Password - Change photo - New password - hasn't been locked out - The password hasn't been changed - Confirm new password - You can change your password for accessing the Umbraco Back Office by filling out the form below and click the 'Change Password' button - Content Channel - Create another user - Create new users to give them access to Umbraco. When a new user is created a password will be generated that you can share with the user. - Description field - Disable User - Document Type - Editor - Excerpt field - Failed login attempts - Go to user profile - Add groups to assign access and permissions - Invite another user - Invite new users to give them access to Umbraco. An invite email will be sent to the user with information on how to log in to Umbraco. Invites last for 72 hours. - Language - Set the language you will see in menus and dialogs - Last lockout date - Last login - Password last changed - Username - Media start node - Limit the media library to a specific start node - Media start nodes - Limit the media library to specific start nodes - Sections - Disable Umbraco Access - has not logged in yet - Old password - Password - Reset password - Your password has been changed! - Please confirm the new password - Enter your new password - Your new password cannot be blank! - Current password - Invalid current password - There was a difference between the new password and the confirmed password. Please try again! - The confirmed password doesn't match the new password! - Replace child node permissions - You are currently modifying permissions for the pages: - Select pages to modify their permissions - Remove photo - Default permissions - Granular permissions - Set permissions for specific nodes - Profile - Search all children - Add sections to give users access - Select user groups - No start node selected - No start nodes selected - Content start node - Limit the content tree to a specific start node - Content start nodes - Limit the content tree to specific start nodes - User last updated - has been created - The new user has successfully been created. To log in to Umbraco use the password below. - User management - Name - User permissions - User group - has been invited - An invitation has been sent to the new user with details about how to log in to Umbraco. - Hello there and welcome to Umbraco! In just 1 minute you’ll be good to go, we just need you to setup a password and add a picture for your avatar. - Welcome to Umbraco! Unfortunately your invite has expired. Please contact your administrator and ask them to resend it. - Uploading a photo of yourself will make it easy for other users to recognize you. Click the circle above to upload your photo. - Writer - Change - Your profile - Your recent history - Session expires in - Invite user - Create user - Send invite - Back to users - Umbraco: Invitation - - - - - - - - - - - -
    - - - - - -
    - -
    - -
    -
    - - - - - - -
    -
    -
    - - - - -
    - - - - -
    -

    - Hi %0%, -

    -

    - You have been invited by %1% to the Umbraco Back Office. -

    -

    - Message from %1%: -
    - %2% -

    - - - - - - -
    - - - - - - -
    - - Click this link to accept the invite - -
    -
    -

    If you cannot click on the link, copy and paste this URL into your browser window:

    - - - - -
    - - %3% - -
    -

    -
    -
    -


    -
    -
    - - ]]>
    - Invite - Resending invitation... - Delete User - Are you sure you wish to delete this user account? - All - Active - Disabled - Locked out - Invited - Inactive - Name (A-Z) - Name (Z-A) - Newest - Oldest - Last login - No user groups have been added - - - Validation - No validation - Validate as an email address - Validate as a number - Validate as a URL - ...or enter a custom validation - Field is mandatory - Enter a custom validation error message (optional) - Enter a regular expression - Enter a custom validation error message (optional) - You need to add at least - You can only have - items - items selected - Invalid date - Not a number - Invalid email - Custom validation - %1% more.]]> - %1% too many.]]> - - - - Value is set to the recommended value: '%0%'. - Value was set to '%1%' for XPath '%2%' in configuration file '%3%'. - Expected value '%1%' for '%2%' in configuration file '%3%', but found '%0%'. - Found unexpected value '%0%' for '%2%' in configuration file '%3%'. - - Custom errors are set to '%0%'. - Custom errors are currently set to '%0%'. It is recommended to set this to '%1%' before go live. - Custom errors successfully set to '%0%'. - MacroErrors are set to '%0%'. - MacroErrors are set to '%0%' which will prevent some or all pages in your site from loading completely if there are any errors in macros. Rectifying this will set the value to '%1%'. - MacroErrors are now set to '%0%'. - - Try Skip IIS Custom Errors is set to '%0%' and you're using IIS version '%1%'. - Try Skip IIS Custom Errors is currently '%0%'. It is recommended to set this to '%1%' for your IIS version (%2%). - Try Skip IIS Custom Errors successfully set to '%0%'. - - File does not exist: '%0%'. - '%0%' in config file '%1%'.]]> - There was an error, check log for full error: %0%. - Database - The database schema is correct for this version of Umbraco - %0% problems were detected with your database schema (Check the log for details) - Some errors were detected while validating the database schema against the current version of Umbraco. - Your website's certificate is valid. - Certificate validation error: '%0%' - Your website's SSL certificate has expired. - Your website's SSL certificate is expiring in %0% days. - Error pinging the URL %0% - '%1%' - You are currently %0% viewing the site using the HTTPS scheme. - The appSetting 'Umbraco.Core.UseHttps' is set to 'false' in your web.config file. Once you access this site using the HTTPS scheme, that should be set to 'true'. - The appSetting 'Umbraco.Core.UseHttps' is set to '%0%' in your web.config file, your cookies are %1% marked as secure. - Could not update the 'Umbraco.Core.UseHttps' setting in your web.config file. Error: %0% - - Enable HTTPS - Sets umbracoSSL setting to true in the appSettings of the web.config file. - The appSetting 'Umbraco.Core.UseHttps' is now set to 'true' in your web.config file, your cookies will be marked as secure. - Fix - Cannot fix a check with a value comparison type of 'ShouldNotEqual'. - Cannot fix a check with a value comparison type of 'ShouldEqual' with a provided value. - Value to fix check not provided. - Debug compilation mode is disabled. - Debug compilation mode is currently enabled. It is recommended to disable this setting before go live. - Debug compilation mode successfully disabled. - Trace mode is disabled. - Trace mode is currently enabled. It is recommended to disable this setting before go live. - Trace mode successfully disabled. - All folders have the correct permissions set. - - %0%.]]> - %0%. If they aren't being written to no action need be taken.]]> - All files have the correct permissions set. - - %0%.]]> - %0%. If they aren't being written to no action need be taken.]]> - X-Frame-Options used to control whether a site can be IFRAMEd by another was found.]]> - X-Frame-Options used to control whether a site can be IFRAMEd by another was not found.]]> - Set Header in Config - Adds a value to the httpProtocol/customHeaders section of web.config to prevent the site being IFRAMEd by other websites. - A setting to create a header preventing IFRAMEing of the site by other websites has been added to your web.config file. - Could not update web.config file. Error: %0% - X-Content-Type-Options used to protect against MIME sniffing vulnerabilities was found.]]> - X-Content-Type-Options used to protect against MIME sniffing vulnerabilities was not found.]]> - Adds a value to the httpProtocol/customHeaders section of web.config to protect against MIME sniffing vulnerabilities. - A setting to create a header protecting against MIME sniffing vulnerabilities has been added to your web.config file. - Strict-Transport-Security, also known as the HSTS-header, was found.]]> - Strict-Transport-Security was not found.]]> - Adds the header 'Strict-Transport-Security' with the value 'max-age=10886400' to the httpProtocol/customHeaders section of web.config. Use this fix only if you will have your domains running with https for the next 18 weeks (minimum). - The HSTS header has been added to your web.config file. - X-XSS-Protection was found.]]> - X-XSS-Protection was not found.]]> - Adds the header 'X-XSS-Protection' with the value '1; mode=block' to the httpProtocol/customHeaders section of web.config. - The X-XSS-Protection header has been added to your web.config file. - - %0%.]]> - No headers revealing information about the website technology were found. - In the Web.config file, system.net/mailsettings could not be found. - In the Web.config file system.net/mailsettings section, the host is not configured. - SMTP settings are configured correctly and the service is operating as expected. - The SMTP server configured with host '%0%' and port '%1%' could not be reached. Please check to ensure the SMTP settings in the Web.config file system.net/mailsettings are correct. - %0%.]]> - %0%.]]> -

    Results of the scheduled Umbraco Health Checks run on %0% at %1% are as follows:

    %2%]]>
    - Umbraco Health Check Status: %0% - Check All Groups - Check group - - The health checker evaluates various areas of your site for best practice settings, configuration, potential problems, etc. You can easily fix problems by pressing a button. - You can add your own health checks, have a look at the documentation for more information about custom health checks.

    - ]]> -
    - - - Disable URL tracker - Enable URL tracker - Original URL - Redirected To - Redirect Url Management - The following URLs redirect to this content item: - No redirects have been made - When a published page gets renamed or moved a redirect will automatically be made to the new page. - Are you sure you want to remove the redirect from '%0%' to '%1%'? - Redirect URL removed. - Error removing redirect URL. - This will remove the redirect - Are you sure you want to disable the URL tracker? - URL tracker has now been disabled. - Error disabling the URL tracker, more information can be found in your log file. - URL tracker has now been enabled. - Error enabling the URL tracker, more information can be found in your log file. - - - No Dictionary items to choose from - - - %0% characters left.]]> - %1% too many.]]> - - - Trashed content with Id: {0} related to original parent content with Id: {1} - Trashed media with Id: {0} related to original parent media item with Id: {1} - Cannot automatically restore this item - There is no location where this item can be automatically restored. You can move the item manually using the tree below. - was restored under - - - Direction - Parent to child - Bidirectional - Parent - Child - Count - Relations - Created - Comment - Name - No relations for this relation type. - Relation Type - Relations - - - Getting Started - Redirect URL Management - Content - Welcome - Examine Management - Published Status - Models Builder - Health Check - Profiling - Getting Started - Install Umbraco Forms - - - Go back - Active layout: - Jump to - group - passed - warning - failed - suggestion - Check passed - Check failed - Open backoffice search - Open/Close backoffice help - Open/Close your profile options - Open context menu for - Current language - Switch language to - Create new folder - Partial View - Partial View Macro - Member - Data type - Search the redirect dashboard - Search the user group section - Search the users section - - - References - This Data Type has no references. - Used in Document Types - No references to Document Types. - Used in Media Types - No references to Media Types. - Used in Member Types - No references to Member Types. - Used by - - - Log Levels - Saved Searches - Total Items - Timestamp - Level - Machine - Message - Exception - Properties - Search With Google - Search this message with Google - Search With Bing - Search this message with Bing - Search Our Umbraco - Search this message on Our Umbraco forums and docs - Search Our Umbraco with Google - Search Our Umbraco forums using Google - Search Umbraco Source - Search within Umbraco source code on Github - Search Umbraco Issues - Search Umbraco Issues on Github - Delete this search - Find Logs with Request ID - Find Logs with Namespace - Find Logs with Machine Name - Open - - - Copy %0% - %0% from %1% - Remove all items - - - Open Property Actions - - - Wait - Refresh status - Memory Cache - - - - Reload - Database Cache - - Rebuilding can be expensive. - Use it when reloading is not enough, and you think that the database cache has not been - properly generated—which would indicate some critical Umbraco issue. - ]]> - - Rebuild - Internals - - not need to use it. - ]]> - - Collect - Published Cache Status - Caches - - - Performance profiling - - - Umbraco currently runs in debug mode. This means you can use the built-in performance profiler to assess the performance when rendering pages. -

    -

    - If you want to activate the profiler for a specific page rendering, simply add umbDebug=true to the querystring when requesting the page. -

    -

    - If you want the profiler to be activated by default for all page renderings, you can use the toggle below. - It will set a cookie in your browser, which then activates the profiler automatically. - In other words, the profiler will only be active by default in your browser - not everyone else's. -

    - ]]> -
    - Activate the profiler by default - Friendly reminder - - - You should never let a production site run in debug mode. Debug mode is turned off by setting debug="false" on the <compilation /> element in web.config. -

    - ]]> -
    - - - Umbraco currently does not run in debug mode, so you can't use the built-in profiler. This is how it should be for a production site. -

    -

    - Debug mode is turned on by setting debug="true" on the <compilation /> element in web.config. -

    - ]]> -
    - - - Hours of Umbraco training videos are only a click away - - 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 - - - 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 Settings section - Find out more - - in the Documentation section of Our Umbraco - ]]> - - - Community Forum - ]]> - - - tutorial videos (some are free, some require a subscription) - ]]> - - - productivity boosting tools and commercial support - ]]> - - - training and certification opportunities - ]]> - - - - Welcome to The Friendly CMS - 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. - - + + + + The Umbraco community + https://our.umbraco.com/documentation/Extending-Umbraco/Language-Files + + + Culture and Hostnames + Audit Trail + Browse Node + Change Document Type + Copy + Create + Export + Create Package + Create group + Delete + Disable + Empty recycle bin + Enable + Export Document Type + Import Document Type + Import Package + Edit in Canvas + Exit + Move + Notifications + Public access + Publish + Unpublish + Reload + Republish entire site + Rename + Restore + Set permissions for the page %0% + Choose where to copy + Choose where to move + to in the tree structure below + Choose where to copy the selected item(s) + Choose where to move the selected item(s) + was moved to + was copied to + was deleted + Permissions + Rollback + Send To Publish + Send To Translation + Set group + Sort + Translate + Update + Set permissions + Unlock + Create Content Template + Resend Invitation + + + Content + Administration + Structure + Other + + + Allow access to assign culture and hostnames + Allow access to view a node's history log + Allow access to view a node + Allow access to change document type for a node + Allow access to copy a node + Allow access to create nodes + Allow access to delete nodes + Allow access to move a node + Allow access to set and change public access for a node + Allow access to publish a node + Allow access to unpublish a node + Allow access to change permissions for a node + Allow access to roll back a node to a previous state + Allow access to send a node for approval before publishing + Allow access to send a node for translation + Allow access to change the sort order for nodes + Allow access to translate a node + Allow access to save a node + Allow access to create a Content Template + + + Content + Info + + + Permission denied. + Add new Domain + remove + Invalid node. + One or more domains have an invalid format. + Domain has already been assigned. + Language + Domain + New domain '%0%' has been created + Domain '%0%' is deleted + Domain '%0%' has already been assigned + Domain '%0%' has been updated + Edit Current Domains + + + Inherit + Culture + + or inherit culture from parent nodes. Will also apply
    + to the current node, unless a domain below applies too.]]> +
    + Domains + + + Clear selection + Select + Do something else + Bold + Cancel Paragraph Indent + Insert form field + Insert graphic headline + Edit Html + Indent Paragraph + Italic + Center + Justify Left + Justify Right + Insert Link + Insert local link (anchor) + Bullet List + Numeric List + Insert macro + Insert picture + Publish and close + Publish with descendants + Edit relations + Return to list + Save + Save and close + Save and publish + Save and schedule + Save and send for approval + Save list view + Schedule + Save and preview + Preview is disabled because there's no template assigned + Choose style + Show styles + Insert table + Save and generate models + Undo + Redo + Delete tag + Cancel + Confirm + More publishing options + + + Viewing for + Content deleted + Content unpublished + Content saved and Published + Content saved and published for languages: %0% + Content saved + Content saved for languages: %0% + Content moved + Content copied + Content rolled back + Content sent for publishing + Content sent for publishing for languages: %0% + Sort child items performed by user + Copy + Publish + Publish + Move + Save + Save + Delete + Unpublish + Rollback + Send To Publish + Send To Publish + Sort + History (all variants) + + + To change the document type for the selected content, first select from the list of valid types for this location. + Then confirm and/or amend the mapping of properties from the current type to the new, and click Save. + The content has been re-published. + Current Property + Current type + The document type cannot be changed, as there are no alternatives valid for this location. An alternative will be valid if it is allowed under the parent of the selected content item and that all existing child content items are allowed to be created under it. + Document Type Changed + Map Properties + Map to Property + New Template + New Type + none + Content + Select New Document Type + The document type of the selected content has been successfully changed to [new type] and the following properties mapped: + to + Could not complete property mapping as one or more properties have more than one mapping defined. + Only alternate types valid for the current location are displayed. + + + Failed to create a folder under parent with ID %0% + Failed to create a folder under parent with name %0% + The folder name cannot contain illegal characters. + Failed to delete item: %0% + + + Is Published + About this page + Alias + (how would you describe the picture over the phone) + Alternative Links + Click to edit this item + Created by + Original author + Updated by + Created + Date/time this document was created + Document Type + Editing + Remove at + This item has been changed after publication + This item is not published + Last published + There are no items to show + There are no items to show in the list. + No content has been added + No members have been added + Media Type + Link to media item(s) + Member Group + Role + Member Type + No changes have been made + No date chosen + Page title + This media item has no link + Properties + This document is published but is not visible because the parent '%0%' is unpublished + This culture is published but is not visible because it is unpublished on parent '%0%' + This document is published but is not in the cache + Could not get the url + This document is published but its url would collide with content %0% + This document is published but its url cannot be routed + Publish + Published + Published (pending changes) + Publication Status + %0% and all content items underneath and thereby making their content publicly available.]]> + + Publish at + Unpublish at + Clear Date + Set date + Sortorder is updated + To sort the nodes, simply drag the nodes or click one of the column headers. You can select multiple nodes by holding the "shift" or "control" key while selecting + Statistics + Title (optional) + Alternative text (optional) + Type + Unpublish + Unpublished + Last edited + Date/time this document was edited + Remove file(s) + Click here to remove the image from the media item + Click here to remove the file from the media item + Link to document + Member of group(s) + Not a member of group(s) + Child items + Target + This translates to the following time on the server: + What does this mean?]]> + Are you sure you want to delete this item? + Property %0% uses editor %1% which is not supported by Nested Content. + Are you sure you want to delete all items? + No content types are configured for this property. + Add element type + Select element type + Select the group whose properties should be displayed. If left blank, the first group on the element type will be used. + Enter an angular expression to evaluate against each item for its name. Use + to display the item index + Add another text box + Remove this text box + Content root + Include drafts and unpublished content items. + This value is hidden. If you need access to view this value please contact your website administrator. + This value is hidden. + What languages would you like to publish? All languages with content are saved! + What languages would you like to publish? + What languages would you like to save? + All languages with content are saved on creation! + What languages would you like to send for approval? + What languages would you like to schedule? + Select the languages to unpublish. Unpublishing a mandatory language will unpublish all languages. + Published Languages + Unpublished Languages + Unmodified Languages + These languages haven't been created + + All new variants will be saved. + Which variants you would like to publish? + Choose which variants to be saved. + Pick variants to send for approval. + Set scheduled publishing... + Select the variants to unpublish. Unpublishing a mandatory language will unpublish all variants. + The following variants is required for publishing to take place: + + We are not ready to Publish + Ready to publish? + Ready to Save? + Send for approval + Select the date and time to publish and/or unpublish the content item. + Create new + Paste from clipboard + This item is in the Recycle Bin + + + Create a new Content Template from '%0%' + Blank + Select a Content Template + Content Template created + A Content Template was created from '%0%' + Another Content Template with the same name already exists + A Content Template is predefined content that an editor can select to use as the basis for creating new content + + + Click to upload + or click here to choose files + You can drag files here to upload + Cannot upload this file, it does not have an approved file type + Max file size is + Media root + Failed to move media + Failed to copy media + Failed to create a folder under parent id %0% + Failed to rename the folder with id %0% + Drag and drop your file(s) into the area + + + Create a new member + All Members + Member groups have no additional properties for editing. + + + Where do you want to create the new %0% + Create an item under + Select the document type you want to make a content template for + Enter a folder name + Choose a type and a title + Document Types within the Settings section, by editing the Allowed child node types under Permissions.]]> + Document Types within the Settings section.]]> + The selected page in the content tree doesn't allow for any pages to be created below it. + Edit permissions for this document type + Create a new document type + Document Types within the Settings section, by changing the Allow as root option under Permissions.]]> + Media Types Types within the Settings section, by editing the Allowed child node types under Permissions.]]> + The selected media in the tree doesn't allow for any other media to be created below it. + Edit permissions for this media type + Document Type without a template + New folder + New data type + New JavaScript file + New empty partial view + New partial view macro + New partial view from snippet + New partial view macro from snippet + New partial view macro (without macro) + New style sheet file + New Rich Text Editor style sheet file + + + Browse your website + - Hide + If Umbraco isn't opening, you might need to allow popups from this site + has opened in a new window + Restart + Visit + Welcome + + + Stay + Discard changes + You have unsaved changes + Are you sure you want to navigate away from this page? - you have unsaved changes + Publishing will make the selected items visible on the site. + Unpublishing will remove the selected items and all their descendants from the site. + Unpublishing will remove this page and all its descendants from the site. + You have unsaved changes. Making changes to the Document Type will discard the changes. + + + Done + Deleted %0% item + Deleted %0% items + Deleted %0% out of %1% item + Deleted %0% out of %1% items + Published %0% item + Published %0% items + Published %0% out of %1% item + Published %0% out of %1% items + Unpublished %0% item + Unpublished %0% items + Unpublished %0% out of %1% item + Unpublished %0% out of %1% items + Moved %0% item + Moved %0% items + Moved %0% out of %1% item + Moved %0% out of %1% items + Copied %0% item + Copied %0% items + Copied %0% out of %1% item + Copied %0% out of %1% items + + + Link title + Link + Anchor / querystring + Name + Manage hostnames + Close this window + Are you sure you want to delete + Are you sure you want to disable + Are you sure? + Are you sure? + Cut + Edit Dictionary Item + Edit Language + Edit selected media + Insert local link + Insert character + Insert graphic headline + Insert picture + Insert link + Click to add a Macro + Insert table + This will delete the language + Changing the culture for a language may be an expensive operation and will result in the content cache and indexes being rebuilt + Last Edited + Link + Internal link: + When using local links, insert "#" in front of link + Open in new window? + Macro Settings + This macro does not contain any properties you can edit + Paste + Edit permissions for + Set permissions for + Set permissions for %0% for user group %1% + Select the users groups you want to set permissions for + The items in the recycle bin are now being deleted. Please do not close this window while this operation takes place + The recycle bin is now empty + When items are deleted from the recycle bin, they will be gone forever + regexlib.com's webservice is currently experiencing some problems, which we have no control over. We are very sorry for this inconvenience.]]> + Search for a regular expression to add validation to a form field. Example: 'email, 'zip-code' 'url' + Remove Macro + Required Field + Site is reindexed + The website cache has been refreshed. All publish content is now up to date. While all unpublished content is still unpublished + The website cache will be refreshed. All published content will be updated, while unpublished content will stay unpublished. + Number of columns + Number of rows + Click on the image to see full size + Pick item + View Cache Item + Relate to original + Include descendants + The friendliest community + Link to page + Opens the linked document in a new window or tab + Link to media + Select content start node + Select media + Select media type + Select icon + Select item + Select link + Select macro + Select content + Select content type + Select media start node + Select member + Select member group + Select member type + Select node + Select sections + Select users + No icons were found + There are no parameters for this macro + There are no macros available to insert + External login providers + Exception Details + Stacktrace + Inner Exception + Link your + Un-link your + account + Select editor + Select snippet + This will delete the node and all its languages. If you only want to delete one language, you should unpublish the node in that language instead. + + + There are no dictionary items. + + + %0%' below + ]]> + Culture Name + + Dictionary overview + + + Configured Searchers + Shows properties and tools for any configured Searcher (i.e. such as a multi-index searcher) + Field values + Health status + The health status of the index and if it can be read + Indexers + Index info + Lists the properties of the index + Manage Examine's indexes + Allows you to view the details of each index and provides some tools for managing the indexes + Rebuild index + + 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. + ]]> +
    + Searchers + Search the index and view the results + Tools + Tools to manage the index + fields + The index cannot be read and will need to be rebuilt + The process is taking longer than expected, check the umbraco log to see if there have been any errors during this operation + This index cannot be rebuilt because it has no assigned + IIndexPopulator + + + Enter your username + Enter your password + Confirm your password + Name the %0%... + Enter a name... + Enter an email... + Enter a username... + Label... + Enter a description... + Type to search... + Type to filter... + Type to add tags (press enter after each tag)... + Enter your email + Enter a message... + Your username is usually your email + #value or ?key=value + Enter alias... + Generating alias... + Create item + Create + Edit + Name + + + Create custom list view + Remove custom list view + A content type, media type or member type with this alias already exists + + + Renamed + Enter a new folder name here + %0% was renamed to %1% + + + Add prevalue + Database datatype + Property editor GUID + Property editor + Buttons + Enable advanced settings for + Enable context menu + Maximum default size of inserted images + Related stylesheets + Show label + Width and height + All property types & property data + using this data type will be deleted permanently, please confirm you want to delete these as well + Yes, delete + and all property types & property data using this data type + Select the folder to move + to in the tree structure below + was moved underneath + + + Your data has been saved, but before you can publish this page there are some errors you need to fix first: + The current membership provider does not support changing password (EnablePasswordRetrieval need to be true) + %0% already exists + There were errors: + There were errors: + The password should be a minimum of %0% characters long and contain at least %1% non-alpha numeric character(s) + %0% must be an integer + The %0% field in the %1% tab is mandatory + %0% is a mandatory field + %0% at %1% is not in a correct format + %0% is not in a correct format + + + Received an error from the server + The specified file type has been disallowed by the administrator + 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%) + 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? + Startnode deleted, please contact your administrator + Please mark content before changing style + No active styles available + Please place cursor at the left of the two cells you wish to merge + You cannot split a cell that hasn't been merged. + This property is invalid + + + About + Action + Actions + Add + Alias + All + Are you sure? + Back + Back to overview + Border + by + Cancel + Cell margin + Choose + Clear + Close + Close Window + Comment + Confirm + Constrain + Constrain proportions + Content + Continue + Copy + Create + Database + Date + Default + Delete + Deleted + Deleting... + Design + Dictionary + Dimensions + Down + Download + Edit + Edited + Elements + Email + Error + Field + Find + First + Focal point + General + Groups + Group + Height + Help + Hide + History + Icon + Id + Import + Search only this folder + Info + Inner margin + Insert + Install + Invalid + Justify + Label + Language + Last + Layout + Links + Loading + Locked + Login + Log off + Logout + Macro + Mandatory + Message + Move + Name + New + Next + No + of + Off + OK + Open + Options + On + or + Order by + Password + Path + One moment please... + Previous + Properties + Rebuild + Email to receive form data + Recycle Bin + Your recycle bin is empty + Reload + Remaining + Remove + Rename + Renew + Required + Retrieve + Retry + Permissions + Scheduled Publishing + Search + Sorry, we can not find what you are looking for. + No items have been added + Server + Settings + Show + Show page on Send + Size + Sort + Status + Submit + Success + Type + Type to search... + under + Up + Update + Upgrade + Upload + Url + User + Username + Value + View + Welcome... + Width + Yes + Folder + Search results + Reorder + I am done reordering + Preview + Change password + to + List view + Saving... + current + Embed + selected + Other + Articles + Videos + Clear + Installing + + + Blue + + + Add group + Add property + Add editor + Add template + Add child node + Add child + Edit data type + Navigate sections + Shortcuts + show shortcuts + Toggle list view + Toggle allow as root + Comment/Uncomment lines + Remove line + Copy Lines Up + Copy Lines Down + Move Lines Up + Move Lines Down + General + Editor + Toggle allow culture variants + Toggle allow segmentation + + + Background colour + Bold + Text colour + Font + Text + + + Page + + + The installer cannot connect to the database. + Could not save the web.config file. Please modify the connection string manually. + Your database has been found and is identified as + Database configuration + + install button to install the Umbraco %0% database + ]]> + + Next to proceed.]]> + Database not found! Please check that the information in the "connection string" of the "web.config" file is correct.

    +

    To proceed, please edit the "web.config" file (using Visual Studio or your favourite text editor), scroll to the bottom, add the connection string for your database in the key named "UmbracoDbDSN" and save the file.

    +

    + Click the retry button when + done.
    + More information on editing web.config here.

    ]]>
    + + Please contact your ISP if necessary. + If you're installing on a local machine or server you might need information from your system administrator.]]> + + Press the upgrade button to upgrade your database to Umbraco %0%

    +

    + Don't worry - no content will be deleted and everything will continue working afterwards! +

    + ]]>
    + Press Next to + proceed. ]]> + next to continue the configuration wizard]]> + The Default users' password needs to be changed!]]> + The Default user has been disabled or has no access to Umbraco!

    No further actions needs to be taken. Click Next to proceed.]]> + The Default user's password has been successfully changed since the installation!

    No further actions needs to be taken. Click Next to proceed.]]> + The password is changed! + Get a great start, watch our introduction videos + By clicking the next button (or modifying the umbracoConfigurationStatus in web.config), you accept the license for this software as specified in the box below. Notice that this Umbraco distribution consists of two different licenses, the open source MIT license for the framework and the Umbraco freeware license that covers the UI. + Not installed yet. + Affected files and folders + More information on setting up permissions for Umbraco here + You need to grant ASP.NET modify permissions to the following files/folders + Your permission settings are almost perfect!

    + You can run Umbraco without problems, but you will not be able to install packages which are recommended to take full advantage of Umbraco.]]>
    + How to Resolve + Click here to read the text version + video tutorial on setting up folder permissions for Umbraco or read the text version.]]> + Your permission settings might be an issue! +

    + You can run Umbraco without problems, but you will not be able to create folders or install packages which are recommended to take full advantage of Umbraco.]]>
    + Your permission settings are not ready for Umbraco! +

    + In order to run Umbraco, you'll need to update your permission settings.]]>
    + Your permission settings are perfect!

    + You are ready to run Umbraco and install packages!]]>
    + Resolving folder issue + Follow this link for more information on problems with ASP.NET and creating folders + Setting up folder permissions + + I want to start from scratch + learn how) + You can still choose to install Runway later on. Please go to the Developer section and choose Packages. + ]]> + You've just set up a clean Umbraco platform. What do you want to do next? + Runway is installed + + This is our list of recommended modules, check off the ones you would like to install, or view the full list of modules + ]]> + Only recommended for experienced users + I want to start with a simple website + + "Runway" is a simple website providing some basic document types and templates. The installer can set up Runway for you automatically, + but you can easily edit, extend or remove it. It's not necessary and you can perfectly use Umbraco without it. However, + Runway offers an easy foundation based on best practices to get you started faster than ever. + If you choose to install Runway, you can optionally select basic building blocks called Runway Modules to enhance your Runway pages. +

    + + Included with Runway: Home page, Getting Started page, Installing Modules page.
    + Optional Modules: Top Navigation, Sitemap, Contact, Gallery. +
    + ]]>
    + What is Runway + Step 1/5 Accept license + Step 2/5: Database configuration + Step 3/5: Validating File Permissions + Step 4/5: Check Umbraco security + Step 5/5: Umbraco is ready to get you started + Thank you for choosing Umbraco + Browse your new site +You installed Runway, so why not see how your new website looks.]]> + Further help and information +Get help from our award winning community, browse the documentation or watch some free videos on how to build a simple site, how to use packages and a quick guide to the Umbraco terminology]]> + Umbraco %0% is installed and ready for use + /web.config file and update the AppSetting key UmbracoConfigurationStatus in the bottom to the value of '%0%'.]]> + started instantly by clicking the "Launch Umbraco" button below.
    If you are new to Umbraco, +you can find plenty of resources on our getting started pages.]]>
    + Launch Umbraco +To manage your website, simply open the Umbraco back office and start adding content, updating the templates and stylesheets or add new functionality]]> + Connection to database failed. + Umbraco Version 3 + Umbraco Version 4 + Watch + Umbraco %0% for a fresh install or upgrading from version 3.0. +

    + Press "next" to start the wizard.]]>
    + + + Culture Code + Culture Name + + + You've been idle and logout will automatically occur in + Renew now to save your work + + + Happy super Sunday + Happy manic Monday + Happy tubular Tuesday + Happy wonderful Wednesday + Happy thunderous Thursday + Happy funky Friday + Happy Caturday + Log in below + Sign in with + Session timed out + © 2001 - %0%
    Umbraco.com

    ]]>
    + Forgotten password? + An email will be sent to the address specified with a link to reset your password + An email with password reset instructions will be sent to the specified address if it matched our records + Show password + Hide password + Return to login form + Please provide a new password + Your Password has been updated + The link you have clicked on is invalid or has expired + Umbraco: Reset Password + + + + + + + + + + + +
    + + + + + +
    + +
    + +
    +
    + + + + + + +
    +
    +
    + + + + +
    + + + + +
    +

    + Password reset requested +

    +

    + Your username to login to the Umbraco back-office is: %0% +

    +

    + + + + + + +
    + + Click this link to reset your password + +
    +

    +

    If you cannot click on the link, copy and paste this URL into your browser window:

    + + + + +
    + + %1% + +
    +

    +
    +
    +


    +
    +
    + + + ]]>
    + + + Dashboard + Sections + Content + + + Choose page above... + %0% has been copied to %1% + Select where the document %0% should be copied to below + %0% has been moved to %1% + Select where the document %0% should be moved to below + has been selected as the root of your new content, click 'ok' below. + No node selected yet, please select a node in the list above before clicking 'ok' + The current node is not allowed under the chosen node because of its type + The current node cannot be moved to one of its subpages + The current node cannot exist at the root + The action isn't allowed since you have insufficient permissions on 1 or more child documents. + Relate copied items to original + + + %0%]]> + Notification settings saved for + + The following languages have been modified %0% + + + + + + + + + + + +
    + + + + + +
    + +
    + +
    +
    + + + + + + +
    +
    +
    + + + + +
    + + + + +
    +

    + Hi %0%, +

    +

    + This is an automated mail to inform you that the task '%1%' has been performed on the page '%2%' by the user '%3%' +

    + + + + + + +
    + +
    + EDIT
    +
    +

    +

    Update summary:

    + %6% +

    +

    + Have a nice day!

    + Cheers from the Umbraco robot +

    +
    +
    +


    +
    +
    + + + ]]>
    + The following languages have been modified:

    + %0% + ]]>
    + [%0%] Notification about %1% performed on %2% + Notifications + + + Actions + Created + Create package + + button and locating the package. Umbraco packages usually have a ".umb" or ".zip" extension. + ]]> + This will delete the package + Drop to upload + Include all child nodes + or click here to choose package file + Upload package + Install a local package by selecting it from your machine. Only install packages from sources you know and trust + Upload another package + Cancel and upload another package + I accept + terms of use + + Path to file + Absolute path to file (ie: /bin/umbraco.bin) + Installed + Installed packages + Install local + Finish + This package has no configuration view + No packages have been created yet + You don’t have any packages installed + 'Packages' icon in the top right of your screen]]> + Package Actions + Author URL + Package Content + Package Files + Icon URL + Install package + License + License URL + Package Properties + Search for packages + Results for + We couldn’t find anything for + Please try searching for another package or browse through the categories + Popular + New releases + has + karma points + Information + Owner + Contributors + Created + Current version + .NET version + Downloads + Likes + Compatibility + This package is compatible with the following versions of Umbraco, as reported by community members. Full compatability cannot be guaranteed for versions reported below 100% + External sources + Author + Documentation + Package meta data + Package name + Package doesn't contain any items +
    + You can safely remove this from the system by clicking "uninstall package" below.]]>
    + Package options + Package readme + Package repository + Confirm package uninstall + Package was uninstalled + The package was successfully uninstalled + Uninstall package + + Notice: any documents, media etc depending on the items you remove, will stop working, and could lead to system instability, + so uninstall with caution. If in doubt, contact the package author.]]> + Package version + Package already installed + This package cannot be installed, it requires a minimum Umbraco version of + Uninstalling... + Downloading... + Importing... + Installing... + Restarting, please wait... + All done, your browser will now refresh, please wait... + Please click 'Finish' to complete installation and reload the page. + Uploading package... + + + Paste with full formatting (Not recommended) + The text you're trying to paste contains special characters or formatting. This could be caused by copying text from Microsoft Word. Umbraco can remove special characters or formatting automatically, so the pasted content will be more suitable for the web. + Paste as raw text without any formatting at all + Paste, but remove formatting (Recommended) + + + Group based protection + If you want to grant access to all members of specific member groups + You need to create a member group before you can use group based authentication + Error Page + Used when people are logged on, but do not have access + %0%]]> + %0% is now protected]]> + %0%]]> + Login Page + Choose the page that contains the login form + Remove protection... + %0%?]]> + Select the pages that contain login form and error messages + %0%]]> + %0%]]> + Specific members protection + If you wish to grant access to specific members + + + + + + + + + Include unpublished subpages + Publishing in progress - please wait... + %0% out of %1% pages have been published... + %0% has been published + %0% and subpages have been published + Publish %0% and all its subpages + Publish to publish %0% and thereby making its content publicly available.

    + You can publish this page and all its subpages by checking Include unpublished subpages below. + ]]>
    + + + You have not configured any approved colours + + + You can only select items of type(s): %0% + You have picked a content item currently deleted or in the recycle bin + You have picked content items currently deleted or in the recycle bin + + + Deleted item + You have picked a media item currently deleted or in the recycle bin + You have picked media items currently deleted or in the recycle bin + Trashed + + + enter external link + choose internal page + Caption + Link + Open in new window + enter the display caption + Enter the link + + + Reset crop + Save crop + Add new crop + Done + Undo edits + User defined + + + Select a version to compare with the current version + Current version + Red text will not be shown in the selected version. , green means added]]> + Document has been rolled back + This displays the selected version as HTML, if you wish to see the difference between 2 versions at the same time, use the diff view + Rollback to + Select version + View + + + Edit script file + + + Concierge + Content + Courier + Developer + Forms + Help + Umbraco Configuration Wizard + Media + Members + Newsletters + Packages + Settings + Statistics + Translation + Users + + + Tours + The best Umbraco video tutorials + Visit our.umbraco.com + Visit umbraco.tv + + + Default template + To import a document type, find the ".udt" file on your computer by clicking the "Browse" button and click "Import" (you'll be asked for confirmation on the next screen) + New Tab Title + Node type + Type + Stylesheet + Script + Tab + Tab Title + Tabs + Master Content Type enabled + This Content Type uses + as a Master Content Type. Tabs from Master Content Types are not shown and can only be edited on the Master Content Type itself + No properties defined on this tab. Click on the "add a new property" link at the top to create a new property. + Create matching template + Add icon + + + Sort order + Creation date + Sorting complete. + Drag the different items up or down below to set how they should be arranged. Or click the column headers to sort the entire collection of items + + + + Validation + Validation errors must be fixed before the item can be saved + Failed + Saved + 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 + DataType: %1%]]> + Propertytype deleted + Document Type saved + Tab created + Tab deleted + Tab with id: %0% deleted + Stylesheet not saved + Stylesheet saved + Stylesheet saved without any errors + Datatype saved + Dictionary item saved + Publishing failed because the parent page isn't published + Content published + and visible on the website + Content saved + Remember to publish to make changes visible + Sent For Approval + Changes have been sent for approval + Media saved + Member group saved + Media saved without any errors + Member saved + Stylesheet Property Saved + Stylesheet saved + Template saved + Error saving user (check log) + User Saved + User type saved + User group saved + Cultures and hostnames saved + Error saving cultures and hostnames + File not saved + file could not be saved. Please check file permissions + File saved + File saved without any errors + Language saved + Media Type saved + Member Type saved + Member Group saved + Template not saved + Please make sure that you do not have 2 templates with the same alias + Template saved + Template saved without any errors! + Content unpublished + Partial view saved + Partial view saved without any errors! + Partial view not saved + An error occurred saving the file. + Permissions saved for + Deleted %0% user groups + %0% was deleted + Enabled %0% users + Disabled %0% users + %0% is now enabled + %0% is now disabled + User groups have been set + Unlocked %0% users + %0% is now unlocked + Member was exported to file + An error occurred while exporting the member + User %0% was deleted + Invite user + Invitation has been re-sent to %0% + Document type was exported to file + An error occurred while exporting the document type + + + Add style + Edit style + Rich text editor styles + Define the styles that should be available in the rich text editor for this stylesheet + Edit stylesheet + Edit stylesheet property + The name displayed in the editor style selector + Preview + How the text will look like in the rich text editor. + Selector + Uses CSS syntax, e.g. "h1" or ".redHeader" + Styles + The CSS that should be applied in the rich text editor, e.g. "color:red;" + Code + Editor + + + Failed to delete template with ID %0% + Edit template + Sections + Insert content area + Insert content area placeholder + Insert + Choose what to insert into your template + Dictionary item + A dictionary item is a placeholder for a translatable piece of text, which makes it easy to create designs for multilingual websites. + Macro + + A Macro is a configurable component which is great for + reusable parts of your design, where you need the option to provide parameters, + such as galleries, forms and lists. + + Value + Displays the value of a named field from the current page, with options to modify the value or fallback to alternative values. + Partial view + + A partial view is a separate template file which can be rendered inside another + template, it's great for reusing markup or for separating complex templates into separate files. + + Master template + No master + Render child template + @RenderBody() placeholder. + ]]> + Define a named section + @section { ... }. This can be rendered in a + specific area of the parent of this template, by using @RenderSection. + ]]> + Render a named section + @RenderSection(name) placeholder. + This renders an area of a child template which is wrapped in a corresponding @section [name]{ ... } definition. + ]]> + Section Name + Section is mandatory + + If mandatory, the child template must contain a @section definition, otherwise an error is shown. + + Query builder + items returned, in + copy to clipboard + I want + all content + content of type "%0%" + from + my website + where + and + is + is not + before + before (including selected date) + after + after (including selected date) + equals + does not equal + contains + does not contain + greater than + greater than or equal to + less than + less than or equal to + Id + Name + Created Date + Last Updated Date + order by + ascending + descending + Template + + + Image + Macro + 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 + Allow all editors + Allow all row configurations + Maximum items + Leave blank or set to 0 for unlimited + Set as default + Choose extra + Choose default + are added + Warning + You are deleting the row configuration + + Deleting a row configuration name will result in loss of data for any existing content that is based on this configuration. + + + + Compositions + Group + You have not added any groups + Add group + Inherited from + Add property + Required label + Enable list view + Configures the content item to show a sortable and searchable list of its children, the children will not be shown in the tree + Allowed Templates + Choose which templates editors are allowed to use on content of this type + Allow as root + Allow editors to create content of this type in the root of the content tree. + Allowed child node types + Allow content of the specified types to be created underneath content of this type. + Choose child node + 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. + There are no content types available to use as a composition. + Removing a composition will delete all the associated property data. Once you save the document type there's no way back. + Create new + Use existing + Editor settings + Configuration + Yes, delete + was moved underneath + was copied underneath + Select the folder to move + Select the folder to copy + to in the tree structure below + All Document types + All Documents + All media items + using this document type will be deleted permanently, please confirm you want to delete these as well. + using this media type will be deleted permanently, please confirm you want to delete these as well. + using this member type will be deleted permanently, please confirm you want to delete these as well + and all documents using this type + and all media items using this type + and all members using this type + Member can edit + Allow this property value to be edited by the member on their profile page + Is sensitive data + Hide this property value from content editors that don't have access to view sensitive information + Show on member profile + Allow this property value to be displayed on the member profile page + tab has no sort order + Where is this composition used? + This composition is currently used in the composition of the following content types: + Allow variations + Allow vary by culture + Allow segmentation + Vary by culture + Vary by segments + Allow editors to create content of this type in different languages. + Allow editors to create content of different languages. + Allow editors to create segments of this content. + Allow varying by culture + Allow segmentation + Element type + Is an Element type + An Element type is meant to be used for instance in Nested Content, and not in the tree. + A document type cannot be changed to an Element type once it has been used to create one or more content items. + This is not applicable for an Element type + You have made changes to this property. Are you sure you want to discard them? + + + Add language + Mandatory language + Properties on this language have to be filled out before the node can be published. + Default language + An Umbraco site can only have one default language set. + Switching default language may result in default content missing. + Falls back to + No fall back language + To allow multi-lingual content to fall back to another language if not present in the requested language, select it here. + Fall back language + none + + + + Add parameter + Edit parameter + Enter macro name + Parameters + Define the parameters that should be available when using this macro. + Select partial view macro file + + + Building models + this can take a bit of time, don't worry + Models generated + Models could not be generated + Models generation has failed, see exception in U log + + + Add fallback field + Fallback field + Add default value + Default value + Fallback field + Default value + Casing + Encoding + Choose field + Convert line breaks + Yes, convert line breaks + Replaces line breaks with 'br' html tag + Custom Fields + Date only + Format and encoding + Format as date + Format the value as a date, or a date with time, according to the active culture + HTML encode + Will replace special characters by their HTML equivalent. + Will be inserted after the field value + Will be inserted before the field value + Lowercase + Modify output + None + Output sample + Insert after field + Insert before field + Recursive + Yes, make it recursive + Separator + Standard Fields + Uppercase + URL encode + Will format special characters in URLs + Will only be used when the field values above are empty + This field will only be used if the primary field is empty + Date and time + + + Translation details + Download XML DTD + Fields + Include subpages + + No translator users found. Please create a translator user before you start sending content to translation + The page '%0%' has been send to translation + Send the page '%0%' to translation + Total words + Translate to + Translation completed. + You can preview the pages, you've just translated, by clicking below. If the original page is found, you will get a comparison of the 2 pages. + Translation failed, the XML file might be corrupt + Translation options + Translator + Upload translation XML + + + Content + Content Templates + Media + Cache Browser + Recycle Bin + Created packages + Data Types + Dictionary + Installed packages + Install skin + Install starter kit + Languages + Install local package + Macros + Media Types + Members + Member Groups + Member Roles + Member Types + Document Types + Relation Types + Packages + Packages + Partial Views + Partial View Macro Files + Install from repository + Install Runway + Runway modules + Scripting Files + Scripts + Stylesheets + Templates + Log Viewer + Users + Settings + Templating + Third Party + + + New update ready + %0% is ready, click here for download + No connection to server + Error checking for update. Please review trace-stack for further information + + + Access + Based on the assigned groups and start nodes, the user has access to the following nodes + Assign access + Administrator + Category field + User created + Change Your Password + Change photo + New password + hasn't been locked out + The password hasn't been changed + Confirm new password + You can change your password for accessing the Umbraco Back Office by filling out the form below and click the 'Change Password' button + Content Channel + Create another user + Create new users to give them access to Umbraco. When a new user is created a password will be generated that you can share with the user. + Description field + Disable User + Document Type + Editor + Excerpt field + Failed login attempts + Go to user profile + Add groups to assign access and permissions + Invite another user + Invite new users to give them access to Umbraco. An invite email will be sent to the user with information on how to log in to Umbraco. Invites last for 72 hours. + Language + Set the language you will see in menus and dialogs + Last lockout date + Last login + Password last changed + Username + Media start node + Limit the media library to a specific start node + Media start nodes + Limit the media library to specific start nodes + Sections + Disable Umbraco Access + has not logged in yet + Old password + Password + Reset password + Your password has been changed! + Password changed + Please confirm the new password + Enter your new password + Your new password cannot be blank! + Current password + Invalid current password + There was a difference between the new password and the confirmed password. Please try again! + The confirmed password doesn't match the new password! + Replace child node permissions + You are currently modifying permissions for the pages: + Select pages to modify their permissions + Remove photo + Default permissions + Granular permissions + Set permissions for specific nodes + Profile + Search all children + Add sections to give users access + Select user groups + No start node selected + No start nodes selected + Content start node + Limit the content tree to a specific start node + Content start nodes + Limit the content tree to specific start nodes + User last updated + has been created + The new user has successfully been created. To log in to Umbraco use the password below. + User management + Name + User permissions + User group + has been invited + An invitation has been sent to the new user with details about how to log in to Umbraco. + Hello there and welcome to Umbraco! In just 1 minute you’ll be good to go, we just need you to setup a password and add a picture for your avatar. + Welcome to Umbraco! Unfortunately your invite has expired. Please contact your administrator and ask them to resend it. + Uploading a photo of yourself will make it easy for other users to recognize you. Click the circle above to upload your photo. + Writer + Change + Your profile + Your recent history + Session expires in + Invite user + Create user + Send invite + Back to users + Umbraco: Invitation + + + + + + + + + + + +
    + + + + + +
    + +
    + +
    +
    + + + + + + +
    +
    +
    + + + + +
    + + + + +
    +

    + Hi %0%, +

    +

    + You have been invited by %1% to the Umbraco Back Office. +

    +

    + Message from %1%: +
    + %2% +

    + + + + + + +
    + + + + + + +
    + + Click this link to accept the invite + +
    +
    +

    If you cannot click on the link, copy and paste this URL into your browser window:

    + + + + +
    + + %3% + +
    +

    +
    +
    +


    +
    +
    + + ]]>
    + Invite + Resending invitation... + Delete User + Are you sure you wish to delete this user account? + All + Active + Disabled + Locked out + Invited + Inactive + Name (A-Z) + Name (Z-A) + Newest + Oldest + Last login + No user groups have been added + + + Validation + No validation + Validate as an email address + Validate as a number + Validate as a URL + ...or enter a custom validation + Field is mandatory + Enter a custom validation error message (optional) + Enter a regular expression + Enter a custom validation error message (optional) + You need to add at least + You can only have + items + items selected + Invalid date + Not a number + Invalid email + Custom validation + %1% more.]]> + %1% too many.]]> + + + + Value is set to the recommended value: '%0%'. + Value was set to '%1%' for XPath '%2%' in configuration file '%3%'. + Expected value '%1%' for '%2%' in configuration file '%3%', but found '%0%'. + Found unexpected value '%0%' for '%2%' in configuration file '%3%'. + + Custom errors are set to '%0%'. + Custom errors are currently set to '%0%'. It is recommended to set this to '%1%' before go live. + Custom errors successfully set to '%0%'. + MacroErrors are set to '%0%'. + MacroErrors are set to '%0%' which will prevent some or all pages in your site from loading completely if there are any errors in macros. Rectifying this will set the value to '%1%'. + MacroErrors are now set to '%0%'. + + Try Skip IIS Custom Errors is set to '%0%' and you're using IIS version '%1%'. + Try Skip IIS Custom Errors is currently '%0%'. It is recommended to set this to '%1%' for your IIS version (%2%). + Try Skip IIS Custom Errors successfully set to '%0%'. + + File does not exist: '%0%'. + '%0%' in config file '%1%'.]]> + There was an error, check log for full error: %0%. + Database - The database schema is correct for this version of Umbraco + %0% problems were detected with your database schema (Check the log for details) + Some errors were detected while validating the database schema against the current version of Umbraco. + Your website's certificate is valid. + Certificate validation error: '%0%' + Your website's SSL certificate has expired. + Your website's SSL certificate is expiring in %0% days. + Error pinging the URL %0% - '%1%' + You are currently %0% viewing the site using the HTTPS scheme. + The appSetting 'Umbraco.Core.UseHttps' is set to 'false' in your web.config file. Once you access this site using the HTTPS scheme, that should be set to 'true'. + The appSetting 'Umbraco.Core.UseHttps' is set to '%0%' in your web.config file, your cookies are %1% marked as secure. + Could not update the 'Umbraco.Core.UseHttps' setting in your web.config file. Error: %0% + + Enable HTTPS + Sets umbracoSSL setting to true in the appSettings of the web.config file. + The appSetting 'Umbraco.Core.UseHttps' is now set to 'true' in your web.config file, your cookies will be marked as secure. + Fix + Cannot fix a check with a value comparison type of 'ShouldNotEqual'. + Cannot fix a check with a value comparison type of 'ShouldEqual' with a provided value. + Value to fix check not provided. + Debug compilation mode is disabled. + Debug compilation mode is currently enabled. It is recommended to disable this setting before go live. + Debug compilation mode successfully disabled. + Trace mode is disabled. + Trace mode is currently enabled. It is recommended to disable this setting before go live. + Trace mode successfully disabled. + All folders have the correct permissions set. + + %0%.]]> + %0%. If they aren't being written to no action need be taken.]]> + All files have the correct permissions set. + + %0%.]]> + %0%. If they aren't being written to no action need be taken.]]> + X-Frame-Options used to control whether a site can be IFRAMEd by another was found.]]> + X-Frame-Options used to control whether a site can be IFRAMEd by another was not found.]]> + Set Header in Config + Adds a value to the httpProtocol/customHeaders section of web.config to prevent the site being IFRAMEd by other websites. + A setting to create a header preventing IFRAMEing of the site by other websites has been added to your web.config file. + Could not update web.config file. Error: %0% + X-Content-Type-Options used to protect against MIME sniffing vulnerabilities was found.]]> + X-Content-Type-Options used to protect against MIME sniffing vulnerabilities was not found.]]> + Adds a value to the httpProtocol/customHeaders section of web.config to protect against MIME sniffing vulnerabilities. + A setting to create a header protecting against MIME sniffing vulnerabilities has been added to your web.config file. + Strict-Transport-Security, also known as the HSTS-header, was found.]]> + Strict-Transport-Security was not found.]]> + Adds the header 'Strict-Transport-Security' with the value 'max-age=10886400' to the httpProtocol/customHeaders section of web.config. Use this fix only if you will have your domains running with https for the next 18 weeks (minimum). + The HSTS header has been added to your web.config file. + X-XSS-Protection was found.]]> + X-XSS-Protection was not found.]]> + Adds the header 'X-XSS-Protection' with the value '1; mode=block' to the httpProtocol/customHeaders section of web.config. + The X-XSS-Protection header has been added to your web.config file. + + %0%.]]> + No headers revealing information about the website technology were found. + In the Web.config file, system.net/mailsettings could not be found. + In the Web.config file system.net/mailsettings section, the host is not configured. + SMTP settings are configured correctly and the service is operating as expected. + The SMTP server configured with host '%0%' and port '%1%' could not be reached. Please check to ensure the SMTP settings in the Web.config file system.net/mailsettings are correct. + %0%.]]> + %0%.]]> +

    Results of the scheduled Umbraco Health Checks run on %0% at %1% are as follows:

    %2%]]>
    + Umbraco Health Check Status: %0% + Check All Groups + Check group + + The health checker evaluates various areas of your site for best practice settings, configuration, potential problems, etc. You can easily fix problems by pressing a button. + You can add your own health checks, have a look at the documentation for more information about custom health checks.

    + ]]> +
    + + + Disable URL tracker + Enable URL tracker + Original URL + Redirected To + Redirect Url Management + The following URLs redirect to this content item: + No redirects have been made + When a published page gets renamed or moved a redirect will automatically be made to the new page. + Are you sure you want to remove the redirect from '%0%' to '%1%'? + Redirect URL removed. + Error removing redirect URL. + This will remove the redirect + Are you sure you want to disable the URL tracker? + URL tracker has now been disabled. + Error disabling the URL tracker, more information can be found in your log file. + URL tracker has now been enabled. + Error enabling the URL tracker, more information can be found in your log file. + + + No Dictionary items to choose from + + + %0% characters left.]]> + %1% too many.]]> + + + Trashed content with Id: {0} related to original parent content with Id: {1} + Trashed media with Id: {0} related to original parent media item with Id: {1} + Cannot automatically restore this item + There is no location where this item can be automatically restored. You can move the item manually using the tree below. + was restored under + + + Direction + Parent to child + Bidirectional + Parent + Child + Count + Relations + Created + Comment + Name + No relations for this relation type. + Relation Type + Relations + + + Getting Started + Redirect URL Management + Content + Welcome + Examine Management + Published Status + Models Builder + Health Check + Profiling + Getting Started + Install Umbraco Forms + + + Go back + Active layout: + Jump to + group + passed + warning + failed + suggestion + Check passed + Check failed + Open backoffice search + Open/Close backoffice help + Open/Close your profile options + Open context menu for + Current language + Switch language to + Create new folder + Partial View + Partial View Macro + Member + Data type + Search the redirect dashboard + Search the user group section + Search the users section + Create item + Create + Edit + Name + Add new row + View more options + + + References + This Data Type has no references. + Used in Document Types + No references to Document Types. + Used in Media Types + No references to Media Types. + Used in Member Types + No references to Member Types. + Used by + Used in Documents + Used in Members + Used in Media + + + Log Levels + Saved Searches + Total Items + Timestamp + Level + Machine + Message + Exception + Properties + Search With Google + Search this message with Google + Search With Bing + Search this message with Bing + Search Our Umbraco + Search this message on Our Umbraco forums and docs + Search Our Umbraco with Google + Search Our Umbraco forums using Google + Search Umbraco Source + Search within Umbraco source code on Github + Search Umbraco Issues + Search Umbraco Issues on Github + Delete this search + Find Logs with Request ID + Find Logs with Namespace + Find Logs with Machine Name + Open + + + Copy %0% + %0% from %1% + Remove all items + + + Open Property Actions + + + Wait + Refresh status + Memory Cache + + + + Reload + Database Cache + + Rebuilding can be expensive. + Use it when reloading is not enough, and you think that the database cache has not been + properly generated—which would indicate some critical Umbraco issue. + ]]> + + Rebuild + Internals + + not need to use it. + ]]> + + Collect + Published Cache Status + Caches + + + Performance profiling + + + Umbraco currently runs in debug mode. This means you can use the built-in performance profiler to assess the performance when rendering pages. +

    +

    + If you want to activate the profiler for a specific page rendering, simply add umbDebug=true to the querystring when requesting the page. +

    +

    + If you want the profiler to be activated by default for all page renderings, you can use the toggle below. + It will set a cookie in your browser, which then activates the profiler automatically. + In other words, the profiler will only be active by default in your browser - not everyone else's. +

    + ]]> +
    + Activate the profiler by default + Friendly reminder + + + You should never let a production site run in debug mode. Debug mode is turned off by setting debug="false" on the <compilation /> element in web.config. +

    + ]]> +
    + + + Umbraco currently does not run in debug mode, so you can't use the built-in profiler. This is how it should be for a production site. +

    +

    + Debug mode is turned on by setting debug="true" on the <compilation /> element in web.config. +

    + ]]> +
    + + + Hours of Umbraco training videos are only a click away + + 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 + + + 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 Settings section + Find out more + + in the Documentation section of Our Umbraco + ]]> + + + Community Forum + ]]> + + + tutorial videos (some are free, some require a subscription) + ]]> + + + productivity boosting tools and commercial support + ]]> + + + training and certification opportunities + ]]> + + + + Welcome to The Friendly CMS + 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. + + + Umbraco Forms + Create forms using an intuitive drag and drop interface. From simple contact forms that sends e-mails to advanced questionaires that integrate with CRM systems. Your clients will love it! + +
    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 83d896d489..e4c764e112 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -37,6 +37,8 @@ Choose where to copy Choose where to move to in the tree structure below + Choose where to copy the selected item(s) + Choose where to move the selected item(s) was moved to was copied to was deleted @@ -138,7 +140,7 @@ Send for approval Save list view Schedule - Preview + Save and preview Preview is disabled because there's no template assigned Choose style Show styles @@ -252,8 +254,8 @@ Published Published (pending changes)> Publication Status - Publish with descendants to publish %0% and all content items underneath and thereby making their content publicly available.]]> - Publish with descendants to publish the selected languages and the same languages of content items underneath and thereby making their content publicly available.]]> + %0% and all content items underneath and thereby making their content publicly available.]]> + Publish at Unpublish at Clear Date @@ -285,10 +287,13 @@ No content types are configured for this property. Add element type Select element type + Select the group whose properties should be displayed. If left blank, the first group on the element type will be used. + Enter an angular expression to evaluate against each item for its name. Use + to display the item index Add another text box Remove this text box Content root - Include drafts: also publish unpublished content items. + Include drafts and unpublished content items. This value is hidden. If you need access to view this value please contact your website administrator. This value is hidden. What languages would you like to publish? All languages with content are saved! @@ -302,7 +307,17 @@ Unpublished Languages Unmodified Languages These languages haven't been created - Ready to Publish? + + All new variants will be saved. + Which variants you would like to publish? + Choose which variants to be saved. + Pick variants to send for approval. + Set scheduled publishing... + Select the variants to unpublish. Unpublishing a mandatory language will unpublish all variants. + The following variants is required for publishing to take place: + + We are not ready to Publish + Ready to publish? Ready to Save? Send for approval Select the date and time to publish and/or unpublish the content item. @@ -553,10 +568,6 @@ #value or ?key=value Enter alias... Generating alias... - Create item - Create - Edit - Name Create custom list view @@ -677,7 +688,7 @@ Icon Id Import - Include subfolders in search + Search only this folder Info Inner margin Insert @@ -739,6 +750,7 @@ Sort Status Submit + Success Type Type to search... under @@ -766,6 +778,11 @@ current Embed selected + Other + Articles + Videos + Clear + Installing Blue @@ -792,6 +809,7 @@ General Editor Toggle allow culture variants + Toggle allow segmentation Background color @@ -1311,11 +1329,12 @@ To manage your website, simply open the Umbraco back office and start adding con Enter the link - Reset crop + Reset crop Save crop Add new crop - Done - Undo edits + Done + Undo edits + User defined Select a version to compare with the current version @@ -1341,7 +1360,10 @@ To manage your website, simply open the Umbraco back office and start adding con Users + Tours The best Umbraco video tutorials + Visit our.umbraco.com + Visit umbraco.tv Default template @@ -1649,13 +1671,21 @@ To manage your website, simply open the Umbraco back office and start adding con tab has no sort order Where is this composition used? This composition is currently used in the composition of the following content types: - Allow varying by culture + Allow variations + Allow vary by culture + Allow segmentation + Vary by culture + Vary by segments Allow editors to create content of this type in different languages. + Allow editors to create content of different languages. + Allow editors to create segments of this content. Allow varying by culture + Allow segmentation Element type - Is an Element type - An Element type is meant to be used for instance in Nested Content, and not in the tree. - This is not applicable for an Element type + Is an element type + An element type is meant to be used for instance in Nested Content, and not in the tree. + A document type cannot be changed to an element type once it has been used to create one or more content items. + This is not applicable for an element type You have made changes to this property. Are you sure you want to discard them? @@ -1846,6 +1876,7 @@ To manage your website, simply open the Umbraco back office and start adding con Password Reset password Your password has been changed! + Password changed Please confirm the new password Enter your new password Your new password cannot be blank! @@ -2222,6 +2253,12 @@ To manage your website, simply open the Umbraco back office and start adding con Search the redirect dashboard Search the user group section Search the users section + Create item + Create + Edit + Name + Add new row + View more options References @@ -2233,6 +2270,9 @@ To manage your website, simply open the Umbraco back office and start adding con Used in Member Types No references to Member Types. Used by + Used in Documents + Used in Members + Used in Media Log Levels @@ -2384,5 +2424,9 @@ To manage your website, simply open the Umbraco back office and start adding con Welcome to The Friendly CMS 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. - + + + Umbraco Forms + Create forms using an intuitive drag and drop interface. From simple contact forms that sends e-mails to advanced questionaires that integrate with CRM systems. Your clients will love it! + diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/es.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/es.xml index 371e6de7a2..c35c84ebdc 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/es.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/es.xml @@ -124,7 +124,7 @@ Guardar y publicar Guardar y enviar para aprobación Guardar vista de lista - Previsualizar + Previsualizar La previsualización está deshabilitada porque no hay ninguna plantilla asignada Elegir estilo Mostrar estilos diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml index 18a5fe9c5a..b5d1c8feb2 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml @@ -124,7 +124,7 @@ Sauver et planifier Sauver et envoyer pour approbation Sauver la mise en page de la liste - Prévisualiser + Prévisualiser La prévisualisation est désactivée car aucun modèle n'a été assigné. Choisir un style Afficher les styles @@ -1180,7 +1180,7 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Type de propriété créé Type de données : %1%]]> Type de propriété supprimé - Type de documet sauvegardé + Type de document sauvegardé Onglet créé Onglet supprimé Onglet avec l'ID : %0% supprimé diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/he.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/he.xml index 9b816b4682..e100cb4301 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/he.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/he.xml @@ -68,7 +68,7 @@ שמור שמור ופרסם שמור ושלח לאישור - תצוגה מקדימה + תצוגה מקדימה בחר עיצוב הצג עיצוב הוספת טבלה diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/it.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/it.xml index d2a1d75ee7..4866fff843 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/it.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/it.xml @@ -69,7 +69,7 @@ Salva Salva e pubblica Salva e invia per approvazione - Anteprima + Anteprima Scegli lo stile Mostra gli stili diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/ja.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/ja.xml index dd68ed45e5..21559f915a 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/ja.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/ja.xml @@ -85,7 +85,7 @@ 保存及び公開 保存して承認に送る リスト ビューの保存 - プレビュー + プレビュー テンプレートが指定されていないのでプレビューは無効になっています スタイルの選択 スタイルの表示 diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/ko.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/ko.xml index 12f7c9ed50..a87f6f1410 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/ko.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/ko.xml @@ -67,7 +67,7 @@ 저장 저장 후 발행 저장 후 승인을 위해 전송 - 미리보기 + 미리보기 스타일 선택 스타일 보기 테이블 삽입 diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml index 78b0ebfb5a..731aea4a20 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml @@ -83,7 +83,7 @@ Lagre og publiser Lagre og planlegge Lagre og send til publisering - Forhåndsvis + Forhåndsvis Forhåndsvisning er deaktivert siden det ikke er angitt noen mal Velg formattering Vis stiler diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml index 90f06fe7a6..601626d896 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml @@ -90,7 +90,7 @@ Opslaan en publiceren Opslaan en verzenden voor goedkeuring Sla list view op - voorbeeld bekijken + voorbeeld bekijken Voorbeeld bekijken is uitgeschakeld omdat er geen template is geselecteerd Stijl kiezen Stijlen tonen diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml index de3e988118..fd806041c5 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml @@ -121,7 +121,7 @@ Zapisz i publikuj Zapisz i wyślij do zaakceptowania Zapisz widok listy - Podgląd + Podgląd Podgląd jest wyłączony, ponieważ żaden szablon nie został przydzielony Wybierz styl Pokaż style diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/pt.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/pt.xml index e7afd04acd..9fd4696c28 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/pt.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/pt.xml @@ -67,7 +67,7 @@ Salvar Salvar e publicar Salvar e mandar para aprovação - Prévia + Prévia Escolha estilo Mostrar estilos Inserir tabela diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml index c52e17e829..7a3e099262 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml @@ -155,7 +155,7 @@ Направить на публикацию Сохранить список Выбрать - Предварительный просмотр + Предварительный просмотр Предварительный просмотр запрещен, так как документу не сопоставлен шаблон Другие действия Выбрать стиль diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml index 152a40b965..e7e7abe2cd 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml @@ -134,7 +134,7 @@ Spara och skicka för godkännande Schemaläggning Välj - Förhandsgranska + Förhandsgranska Förhandsgranskning är avstängt på grund av att det inte finns någon mall tilldelad Ångra Välj stil diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/tr.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/tr.xml index 02069a53dc..f998080b06 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/tr.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/tr.xml @@ -85,7 +85,7 @@ Kaydet Kaydet ve Yayınla Kaydet ve Onay için gönder - Önizle + Önizle Önizleme kapalı, Atanmış şablon yok Stili seçin Stilleri Göster diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/zh.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/zh.xml index a8dd8a8bef..5210b46bcc 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/zh.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/zh.xml @@ -90,7 +90,7 @@ 保存并发布 保存并提交审核 保存列表视图 - 预览 + 预览 因未设置模板无法预览 选择样式 显示样式 diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/zh_tw.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/zh_tw.xml index bac817ad20..320c3f63d8 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/zh_tw.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/zh_tw.xml @@ -87,7 +87,7 @@ 保存並發佈 保存並提交審核 保存清單檢視 - 預覽 + 預覽 因未設置範本無法預覽 選擇樣式 顯示樣式 diff --git a/src/Umbraco.Web.UI/Umbraco/js/main.controller.js b/src/Umbraco.Web.UI/Umbraco/js/main.controller.js deleted file mode 100644 index 93870f8a56..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/js/main.controller.js +++ /dev/null @@ -1,215 +0,0 @@ - -/** - * @ngdoc controller - * @name Umbraco.MainController - * @function - * - * @description - * The main application controller - * - */ -function MainController($scope, $location, appState, treeService, notificationsService, - userService, historyService, updateChecker, navigationService, eventsService, - tmhDynamicLocale, localStorageService, editorService, overlayService, assetsService, tinyMceAssets) { - - //the null is important because we do an explicit bool check on this in the view - $scope.authenticated = null; - $scope.touchDevice = appState.getGlobalState("touchDevice"); - $scope.infiniteMode = false; - $scope.overlay = {}; - $scope.drawer = {}; - $scope.search = {}; - $scope.login = {}; - $scope.tabbingActive = false; - - // Load TinyMCE assets ahead of time in the background for the user - // To help with first load of the RTE - tinyMceAssets.forEach(function (tinyJsAsset) { - assetsService.loadJs(tinyJsAsset, $scope); - }); - - // There are a number of ways to detect when a focus state should be shown when using the tab key and this seems to be the simplest solution. - // For more information about this approach, see https://hackernoon.com/removing-that-ugly-focus-ring-and-keeping-it-too-6c8727fefcd2 - function handleFirstTab(evt) { - if (evt.keyCode === 9) { - $scope.tabbingActive = true; - $scope.$digest(); - window.removeEventListener('keydown', handleFirstTab); - window.addEventListener('mousedown', disableTabbingActive); - } - } - - function disableTabbingActive(evt) { - $scope.tabbingActive = false; - $scope.$digest(); - window.removeEventListener('mousedown', disableTabbingActive); - window.addEventListener("keydown", handleFirstTab); - } - - window.addEventListener("keydown", handleFirstTab); - - - $scope.removeNotification = function (index) { - notificationsService.remove(index); - }; - - $scope.closeSearch = function() { - appState.setSearchState("show", false); - }; - - $scope.showLoginScreen = function(isTimedOut) { - $scope.login.isTimedOut = isTimedOut; - $scope.login.show = true; - }; - - $scope.hideLoginScreen = function() { - $scope.login.show = false; - }; - - var evts = []; - - //when a user logs out or timesout - evts.push(eventsService.on("app.notAuthenticated", function (evt, data) { - $scope.authenticated = null; - $scope.user = null; - const isTimedOut = data && data.isTimedOut ? true : false; - $scope.showLoginScreen(isTimedOut); - })); - - evts.push(eventsService.on("app.userRefresh", function(evt) { - userService.refreshCurrentUser().then(function(data) { - $scope.user = data; - - //Load locale file - if ($scope.user.locale) { - tmhDynamicLocale.set($scope.user.locale); - } - }); - })); - - //when the app is ready/user is logged in, setup the data - evts.push(eventsService.on("app.ready", function (evt, data) { - - $scope.authenticated = data.authenticated; - $scope.user = data.user; - - updateChecker.check().then(function (update) { - if (update && update !== "null") { - if (update.type !== "None") { - var notification = { - headline: "Update available", - message: "Click to download", - sticky: true, - type: "info", - url: update.url - }; - notificationsService.add(notification); - } - } - }); - - //if the user has changed we need to redirect to the root so they don't try to continue editing the - //last item in the URL (NOTE: the user id can equal zero, so we cannot just do !data.lastUserId since that will resolve to true) - if (data.lastUserId !== undefined && data.lastUserId !== null && data.lastUserId !== data.user.id) { - - var section = appState.getSectionState("currentSection"); - if (section) { - //if there's a section already assigned, reload it so the tree is cleared - navigationService.reloadSection(section); - } - - $location.path("/").search(""); - historyService.removeAll(); - treeService.clearCache(); - editorService.closeAll(); - overlayService.close(); - - //if the user changed, clearout local storage too - could contain sensitive data - localStorageService.clearAll(); - } - - //if this is a new login (i.e. the user entered credentials), then clear out local storage - could contain sensitive data - if (data.loginType === "credentials") { - localStorageService.clearAll(); - } - - //Load locale file - if ($scope.user.locale) { - tmhDynamicLocale.set($scope.user.locale); - } - - })); - - // events for search - evts.push(eventsService.on("appState.searchState.changed", function (e, args) { - if (args.key === "show") { - $scope.search.show = args.value; - } - })); - - // events for drawer - // manage the help dialog by subscribing to the showHelp appState - evts.push(eventsService.on("appState.drawerState.changed", function (e, args) { - // set view - if (args.key === "view") { - $scope.drawer.view = args.value; - } - // set custom model - if (args.key === "model") { - $scope.drawer.model = args.value; - } - // show / hide drawer - if (args.key === "showDrawer") { - $scope.drawer.show = args.value; - } - })); - - // events for overlays - evts.push(eventsService.on("appState.overlay", function (name, args) { - $scope.overlay = args; - })); - - // events for tours - evts.push(eventsService.on("appState.tour.start", function (name, args) { - $scope.tour = args; - $scope.tour.show = true; - })); - - evts.push(eventsService.on("appState.tour.end", function () { - $scope.tour = null; - })); - - evts.push(eventsService.on("appState.tour.complete", function () { - $scope.tour = null; - })); - - // events for backdrop - evts.push(eventsService.on("appState.backdrop", function (name, args) { - $scope.backdrop = args; - })); - - // event for infinite editors - evts.push(eventsService.on("appState.editors.open", function (name, args) { - $scope.infiniteMode = args && args.editors.length > 0 ? true : false; - })); - - evts.push(eventsService.on("appState.editors.close", function (name, args) { - $scope.infiniteMode = args && args.editors.length > 0 ? true : false; - })); - - //ensure to unregister from all events! - $scope.$on('$destroy', function () { - for (var e in evts) { - eventsService.unsubscribe(evts[e]); - } - }); - -} - - -//register it -angular.module('umbraco').controller("Umbraco.MainController", MainController). - config(function (tmhDynamicLocaleProvider) { - //Set url for locale files - tmhDynamicLocaleProvider.localeLocationPattern('lib/angular-i18n/angular-locale_{{locale}}.js'); - }); diff --git a/src/Umbraco.Web.UI/Umbraco/js/navigation.controller.js b/src/Umbraco.Web.UI/Umbraco/js/navigation.controller.js deleted file mode 100644 index 194c45afe6..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/js/navigation.controller.js +++ /dev/null @@ -1,544 +0,0 @@ - -/** - * @ngdoc controller - * @name Umbraco.NavigationController - * @function - * - * @description - * Handles the section area of the app - * - * @param {navigationService} navigationService A reference to the navigationService - */ -function NavigationController($scope, $rootScope, $location, $log, $q, $routeParams, $timeout, $cookies, treeService, appState, navigationService, keyboardService, historyService, eventsService, angularHelper, languageResource, contentResource, editorState) { - - //this is used to trigger the tree to start loading once everything is ready - var treeInitPromise = $q.defer(); - - $scope.treeApi = {}; - - //Bind to the main tree events - $scope.onTreeInit = function () { - - $scope.treeApi.callbacks.treeNodeExpanded(nodeExpandedHandler); - - //when a tree is loaded into a section, we need to put it into appState - $scope.treeApi.callbacks.treeLoaded(function (args) { - appState.setTreeState("currentRootNode", args.tree); - }); - - //when a tree node is synced this event will fire, this allows us to set the currentNode - $scope.treeApi.callbacks.treeSynced(function (args) { - - if (args.activate === undefined || args.activate === true) { - //set the current selected node - appState.setTreeState("selectedNode", args.node); - //when a node is activated, this is the same as clicking it and we need to set the - //current menu item to be this node as well. - //appState.setMenuState("currentNode", args.node);// Niels: No, we are setting it from the dialog. - } - }); - - //this reacts to the options item in the tree - $scope.treeApi.callbacks.treeOptionsClick(function (args) { - args.event.stopPropagation(); - args.event.preventDefault(); - - //Set the current action node (this is not the same as the current selected node!) - //appState.setMenuState("currentNode", args.node);// Niels: No, we are setting it from the dialog. - - if (args.event && args.event.altKey) { - args.skipDefault = true; - } - - navigationService.showMenu(args); - }); - - $scope.treeApi.callbacks.treeNodeAltSelect(function (args) { - args.event.stopPropagation(); - args.event.preventDefault(); - - args.skipDefault = true; - navigationService.showMenu(args); - }); - - //this reacts to tree items themselves being clicked - //the tree directive should not contain any handling, simply just bubble events - $scope.treeApi.callbacks.treeNodeSelect(function (args) { - var n = args.node; - args.event.stopPropagation(); - args.event.preventDefault(); - - if (n.metaData && n.metaData["jsClickCallback"] && angular.isString(n.metaData["jsClickCallback"]) && n.metaData["jsClickCallback"] !== "") { - //this is a legacy tree node! - var jsPrefix = "javascript:"; - var js; - if (n.metaData["jsClickCallback"].startsWith(jsPrefix)) { - js = n.metaData["jsClickCallback"].substr(jsPrefix.length); - } - else { - js = n.metaData["jsClickCallback"]; - } - try { - var func = eval(js); - //this is normally not necessary since the eval above should execute the method and will return nothing. - if (func != null && (typeof func === "function")) { - func.call(); - } - } - catch (ex) { - $log.error("Error evaluating js callback from legacy tree node: " + ex); - } - } - 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); - navigationService.clearSearch(); - } - else if (n.section) { - $location.path(n.section); - navigationService.clearSearch(); - } - - navigationService.hideNavigation(); - }); - - return treeInitPromise.promise; - } - - //set up our scope vars - $scope.showContextMenuDialog = false; - $scope.showContextMenu = false; - $scope.showSearchResults = false; - $scope.menuDialogTitle = null; - $scope.menuActions = []; - $scope.menuNode = null; - $scope.languages = []; - $scope.selectedLanguage = {}; - $scope.page = {}; - $scope.page.languageSelectorIsOpen = false; - - $scope.currentSection = null; - $scope.customTreeParams = null; - $scope.treeCacheKey = "_"; - $scope.showNavigation = appState.getGlobalState("showNavigation"); - // tracks all expanded paths so when the language is switched we can resync it with the already loaded paths - var expandedPaths = []; - - //trigger search with a hotkey: - keyboardService.bind("ctrl+shift+s", function () { - navigationService.showSearch(); - }); - - //// TODO: remove this it's not a thing - //$scope.selectedId = navigationService.currentId; - - var isInit = false; - var evts = []; - - //Listen for global state changes - evts.push(eventsService.on("appState.globalState.changed", function (e, args) { - if (args.key === "showNavigation") { - $scope.showNavigation = args.value; - } - })); - - //Listen for menu state changes - evts.push(eventsService.on("appState.menuState.changed", function (e, args) { - if (args.key === "showMenuDialog") { - $scope.showContextMenuDialog = args.value; - } - if (args.key === "dialogTemplateUrl") { - $scope.dialogTemplateUrl = args.value; - } - if (args.key === "showMenu") { - $scope.showContextMenu = args.value; - } - if (args.key === "dialogTitle") { - $scope.menuDialogTitle = args.value; - } - if (args.key === "menuActions") { - $scope.menuActions = args.value; - } - if (args.key === "currentNode") { - $scope.menuNode = args.value; - } - })); - - //Listen for tree state changes - evts.push(eventsService.on("appState.treeState.changed", function (e, args) { - if (args.key === "currentRootNode") { - - //if the changed state is the currentRootNode, determine if this is a full screen app - if (args.value.root && args.value.root.containsTrees === false) { - $rootScope.emptySection = true; - } - else { - $rootScope.emptySection = false; - } - } - - })); - - //Listen for section state changes - evts.push(eventsService.on("appState.sectionState.changed", function (e, args) { - - //section changed - if (args.key === "currentSection" && $scope.currentSection != args.value) { - //before loading the main tree we need to ensure that the nav is ready - navigationService.waitForNavReady().then(() => { - $scope.currentSection = args.value; - //load the tree - configureTreeAndLanguages(); - $scope.treeApi.load({ section: $scope.currentSection, customTreeParams: $scope.customTreeParams, cacheKey: $scope.treeCacheKey }); - }); - } - - //show/hide search results - if (args.key === "showSearchResults") { - $scope.showSearchResults = args.value; - } - - })); - - // Listen for language updates - evts.push(eventsService.on("editors.languages.languageDeleted", function (e, args) { - loadLanguages().then(function (languages) { - $scope.languages = languages; - const defaultCulture = $scope.languages[0].culture; - - if (args.language.culture === $scope.selectedLanguage.culture) { - $scope.selectedLanguage = defaultCulture; - - if ($scope.languages.length > 1) { - $location.search("mculture", defaultCulture); - } else { - $location.search("mculture", null); - } - - var currentEditorState = editorState.getCurrent(); - if (currentEditorState && currentEditorState.path) { - $scope.treeApi.syncTree({ path: currentEditorState.path, activate: true }); - } - } - }); - })); - - //Emitted when a language is created or an existing one saved/edited - evts.push(eventsService.on("editors.languages.languageSaved", function (e, args) { - if(args.isNew){ - //A new language has been created - reload languages for tree - loadLanguages().then(function (languages) { - $scope.languages = languages; - }); - } - else if(args.language.isDefault){ - //A language was saved and was set to be the new default (refresh the tree, so its at the top) - loadLanguages().then(function (languages) { - $scope.languages = languages; - }); - } - })); - - //when a user logs out or timesout - evts.push(eventsService.on("app.notAuthenticated", function () { - $scope.authenticated = false; - })); - - //when the application is ready and the user is authorized, setup the data - //this will occur anytime a new user logs in! - evts.push(eventsService.on("app.ready", function (evt, data) { - $scope.authenticated = true; - ensureInit(); - })); - - // event for infinite editors - evts.push(eventsService.on("appState.editors.open", function (name, args) { - $scope.infiniteMode = args && args.editors.length > 0 ? true : false; - })); - - evts.push(eventsService.on("appState.editors.close", function (name, args) { - $scope.infiniteMode = args && args.editors.length > 0 ? true : false; - })); - - evts.push(eventsService.on("treeService.removeNode", function (e, args) { - //check to see if the current page has been removed - - var currentEditorState = editorState.getCurrent(); - if (currentEditorState && currentEditorState.id.toString() === args.node.id.toString()) { - //current page is loaded, so navigate to root - var section = appState.getSectionState("currentSection"); - $location.path("/" + section); - } - })); - - - - - /** - * Based on the current state of the application, this configures the scope variables that control the main tree and language drop down - */ - function configureTreeAndLanguages() { - - //create the custom query string param for this tree, this is currently only relevant for content - if ($scope.currentSection === "content") { - - //must use $location here because $routeParams isn't available until after the route change - var mainCulture = $location.search().mculture; - //select the current language if set in the query string - if (mainCulture && $scope.languages && $scope.languages.length > 1) { - var found = _.find($scope.languages, function (l) { - if (mainCulture === true) { - return false; - } - return l.culture.toLowerCase() === mainCulture.toLowerCase(); - }); - if (found) { - //set the route param - found.active = true; - $scope.selectedLanguage = found; - } - } - - var queryParams = {}; - if ($scope.selectedLanguage && $scope.selectedLanguage.culture) { - queryParams["culture"] = $scope.selectedLanguage.culture; - } - var queryString = $.param(queryParams); //create the query string from the params object - } - - if (queryString) { - $scope.customTreeParams = queryString; - $scope.treeCacheKey = queryString; // this tree uses caching but we need to change it's cache key per lang - } - else { - $scope.treeCacheKey = "_"; // this tree uses caching, there's no lang selected so use the default - } - - } - - /** - * Called when the app is ready and sets up the navigation (should only be called once) - */ - function ensureInit() { - - //only run once ever! - if (isInit) { - return; - } - - isInit = true; - - var navInit = false; - - //$routeParams will be populated after $routeChangeSuccess since this controller is used outside ng-view, - //* we listen for the first route change with a section to setup the navigation. - //* we listen for all route changes to track the current section. - $rootScope.$on('$routeChangeSuccess', function () { - - //only continue if there's a section available - if ($routeParams.section) { - - if (!navInit) { - navInit = true; - initNav(); - } - - //keep track of the current section when it changes - if ($scope.currentSection != $routeParams.section) { - appState.setSectionState("currentSection", $routeParams.section); - } - - } - }); - } - - /** - * This loads the language data, if the are no variant content types configured this will return no languages - */ - function loadLanguages() { - - return contentResource.allowsCultureVariation().then(function (b) { - if (b === true) { - return languageResource.getAll(); - } else { - return $q.when([]); //resolve an empty collection - } - }); - } - - /** - * Called once during init to initialize the navigation/tree/languages - */ - function initNav() { - // load languages - loadLanguages().then(function (languages) { - - $scope.languages = languages; - - if ($scope.languages.length > 1) { - //if there's already one set, check if it exists - var currCulture = null; - var mainCulture = $location.search().mculture; - if (mainCulture) { - currCulture = _.find($scope.languages, function (l) { - return l.culture.toLowerCase() === mainCulture.toLowerCase(); - }); - } - if (!currCulture) { - // no culture in the request, let's look for one in the cookie that's set when changing language - var defaultCulture = $cookies.get("UMB_MCULTURE"); - if (!defaultCulture || !_.find($scope.languages, function (l) { - return l.culture.toLowerCase() === defaultCulture.toLowerCase(); - })) { - // no luck either, look for the default language - var defaultLang = _.find($scope.languages, function (l) { - return l.isDefault; - }); - if (defaultLang) { - defaultCulture = defaultLang.culture; - } - } - $location.search("mculture", defaultCulture ? defaultCulture : null); - } - } - - $scope.currentSection = $routeParams.section; - - configureTreeAndLanguages(); - - //resolve the tree promise, set it's property values for loading the tree which will make the tree load - treeInitPromise.resolve({ - section: $scope.currentSection, - customTreeParams: $scope.customTreeParams, - cacheKey: $scope.treeCacheKey, - - //because angular doesn't return a promise for the resolve method, we need to resort to some hackery, else - //like normal JS promises we could do resolve(...).then() - onLoaded: function () { - - //the nav is ready, let the app know - eventsService.emit("app.navigationReady", { treeApi: $scope.treeApi }); - - } - }); - }); - } - function nodeExpandedHandler(args) { - //store the reference to the expanded node path - if (args.node) { - treeService._trackExpandedPaths(args.node, expandedPaths); - } - } - - $scope.selectLanguage = function (language) { - - $location.search("mculture", language.culture); - // add the selected culture to a cookie so the user will log back into the same culture later on (cookie lifetime = one year) - var expireDate = new Date(); - expireDate.setDate(expireDate.getDate() + 365); - $cookies.put("UMB_MCULTURE", language.culture, {path: "/", expires: expireDate}); - - // close the language selector - $scope.page.languageSelectorIsOpen = false; - - configureTreeAndLanguages(); //re-bind language to the query string and update the tree params - - //reload the tree with it's updated querystring args - $scope.treeApi.load({ section: $scope.currentSection, customTreeParams: $scope.customTreeParams, cacheKey: $scope.treeCacheKey }).then(function () { - - //re-sync to currently edited node - var currNode = appState.getTreeState("selectedNode"); - //create the list of promises - var promises = []; - //starting with syncing to the currently selected node if there is one - if (currNode) { - var path = treeService.getPath(currNode); - promises.push($scope.treeApi.syncTree({ path: path, activate: true })); - } - // TODO: If we want to keep all paths expanded ... but we need more testing since we need to deal with unexpanding - //for (var i = 0; i < expandedPaths.length; i++) { - // promises.push($scope.treeApi.syncTree({ path: expandedPaths[i], activate: false, forceReload: true })); - //} - //execute them sequentially - - // set selected language to active - angular.forEach($scope.languages, function(language){ - language.active = false; - }); - language.active = true; - - angularHelper.executeSequentialPromises(promises); - }); - - }; - - //this reacts to the options item in the tree - // TODO: migrate to nav service - // TODO: is this used? - $scope.searchShowMenu = function (ev, args) { - //always skip default - args.skipDefault = true; - navigationService.showMenu(args); - }; - - // TODO: migrate to nav service - // TODO: is this used? - $scope.searchHide = function () { - navigationService.hideSearch(); - }; - - //the below assists with hiding/showing the tree - var treeActive = false; - - //Sets a service variable as soon as the user hovers the navigation with the mouse - //used by the leaveTree method to delay hiding - $scope.enterTree = function (event) { - treeActive = true; - }; - - // Hides navigation tree, with a short delay, is cancelled if the user moves the mouse over the tree again - $scope.leaveTree = function (event) { - //this is a hack to handle IE touch events which freaks out due to no mouse events so the tree instantly shuts down - if (!event) { - return; - } - closeTree(); - }; - - $scope.onOutsideClick = function() { - closeTree(); - }; - - function closeTree() { - if (!appState.getGlobalState("touchDevice")) { - treeActive = false; - $timeout(function () { - if (!treeActive) { - navigationService.hideTree(); - } - }, 300); - } - } - - $scope.toggleLanguageSelector = function () { - $scope.page.languageSelectorIsOpen = !$scope.page.languageSelectorIsOpen; - }; - - //ensure to unregister from all events! - $scope.$on('$destroy', function () { - for (var e in evts) { - eventsService.unsubscribe(evts[e]); - } - }); -} - -//register it -angular.module('umbraco').controller("Umbraco.NavigationController", NavigationController); diff --git a/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap3-Fluid.cshtml b/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap3-Fluid.cshtml index defe59d808..131b0515ae 100644 --- a/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap3-Fluid.cshtml +++ b/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap3-Fluid.cshtml @@ -2,31 +2,31 @@ @using Umbraco.Web.Templates @using Newtonsoft.Json.Linq -@* +@* Razor helpers located at the bottom of this file *@ @if (Model != null && Model.sections != null) { var oneColumn = ((System.Collections.ICollection)Model.sections).Count == 1; - +
    @if (oneColumn) { foreach (var section in Model.sections) {
    @foreach (var row in section.rows) { - @renderRow(row); + @renderRow(row) }
    - } - }else { + } + }else {
    @foreach (var s in Model.sections) {
    @foreach (var row in s.rows) { - @renderRow(row); + @renderRow(row) }
    @@ -85,4 +85,4 @@ return new MvcHtmlString(string.Join(" ", attrs)); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap3.cshtml b/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap3.cshtml index 9333628ed6..23fee33043 100644 --- a/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap3.cshtml +++ b/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap3.cshtml @@ -12,7 +12,7 @@ foreach (var section in Model.sections) {
    @foreach (var row in section.rows) { - @renderRow(row, true); + @renderRow(row, true) }
    } @@ -23,7 +23,7 @@
    @foreach (var row in s.rows) { - @renderRow(row, false); + @renderRow(row, false) }
    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 ea79ce41ad..e20b717ed5 100644 --- a/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Media.cshtml +++ b/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Media.cshtml @@ -2,20 +2,24 @@ @using Umbraco.Web.Templates @if (Model.value != null) -{ +{ var url = Model.value.image; if(Model.editor.config != null && Model.editor.config.size != null){ - url += "?width=" + Model.editor.config.size.width; - url += "&height=" + Model.editor.config.size.height; - - if(Model.value.focalPoint != null){ - url += "¢er=" + Model.value.focalPoint.top +"," + Model.value.focalPoint.left; - url += "&mode=crop"; - } + url = ImageCropperTemplateExtensions.GetCropUrl(url, + width: Model.editor.config.size.width, + height: Model.editor.config.size.height, + cropDataSet: Model.value.focalPoint == null ? null : new Umbraco.Core.PropertyEditors.ValueConverters.ImageCropperValue + { + FocalPoint = new Umbraco.Core.PropertyEditors.ValueConverters.ImageCropperValue.ImageCropperFocalPoint + { + Top = Model.value.focalPoint.top, + Left = Model.value.focalPoint.left + } + }); } var altText = Model.value.altText ?? Model.value.caption ?? string.Empty; - + @altText if (Model.value.caption != null) diff --git a/src/Umbraco.Web.UI/config/BackOfficeTours/getting-started.json b/src/Umbraco.Web.UI/config/BackOfficeTours/getting-started.json index e300e6562e..7b3f2a2184 100644 --- a/src/Umbraco.Web.UI/config/BackOfficeTours/getting-started.json +++ b/src/Umbraco.Web.UI/config/BackOfficeTours/getting-started.json @@ -1,4 +1,22 @@ [ + { + "name": "Email Marketing", + "alias": "umbEmailMarketing", + "group": "Email Marketing", + "groupOrder": 10, + "hidden": true, + "requiredSections": [ + "content" + ], + "steps": [ + { + "title": "Do you want to stay updated on everything Umbraco?", + "content": "

    Thank you for using Umbraco! Would you like to stay up-to-date with Umbraco product updates, security advisories, community news and special offers? Sign up for our newsletter and never miss out on the latest Umbraco news.

    By signing up, you agree that we can use your info according to our privacy policy.

    ", + "view": "emails", + "type": "promotion" + } + ] + }, { "name": "Introduction", "alias": "umbIntroIntroduction", diff --git a/src/Umbraco.Web.UI/config/umbracoSettings.config b/src/Umbraco.Web.UI/config/umbracoSettings.config index 9e60a9499c..c3df6cfb41 100644 --- a/src/Umbraco.Web.UI/config/umbracoSettings.config +++ b/src/Umbraco.Web.UI/config/umbracoSettings.config @@ -231,6 +231,14 @@ 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 + @disableRedirectUrlTracking + When the URL changes for content, redirects are automatically created for redirect handling within the + request pipeline. Setting this setting to true stops the automatic creation of redirects. Note that this + does not stop the request pipeline from handling any previously created redirects. + @urlProviderMode + By default Umbraco automatically figures out if internal URLs should be rendered as relative or absolute, + depending on the current request and the configured domains. By setting this setting to "Relative" or + "Absolute" you can force Umbraco to always render URLs as either relative or absolute. @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 diff --git a/src/Umbraco.Web.UI/web.Template.Debug.config b/src/Umbraco.Web.UI/web.Template.Debug.config index ff42f098f7..0026f23514 100644 --- a/src/Umbraco.Web.UI/web.Template.Debug.config +++ b/src/Umbraco.Web.UI/web.Template.Debug.config @@ -21,6 +21,11 @@ + + + + + diff --git a/src/Umbraco.Web/Cache/MacroCacheRefresher.cs b/src/Umbraco.Web/Cache/MacroCacheRefresher.cs index 24479b8415..0cecba7b7b 100644 --- a/src/Umbraco.Web/Cache/MacroCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/MacroCacheRefresher.cs @@ -53,7 +53,7 @@ namespace Umbraco.Web.Cache { macroRepoCache.Result.Clear(RepositoryCacheKeys.GetKey(payload.Id)); } - }; + } base.Refresh(json); } diff --git a/src/Umbraco.Web/Composing/Current.cs b/src/Umbraco.Web/Composing/Current.cs index 5be5e45ecd..2419eaa6d4 100644 --- a/src/Umbraco.Web/Composing/Current.cs +++ b/src/Umbraco.Web/Composing/Current.cs @@ -88,6 +88,12 @@ namespace Umbraco.Web.Composing public static UmbracoHelper UmbracoHelper => Factory.GetInstance(); + public static IUmbracoComponentRenderer UmbracoComponentRenderer + => Factory.GetInstance(); + public static ITagQuery TagQuery + => Factory.GetInstance(); + public static IPublishedContentQuery PublishedContentQuery + => Factory.GetInstance(); public static DistributedCache DistributedCache => Factory.GetInstance(); @@ -182,6 +188,8 @@ namespace Umbraco.Web.Composing public static DataEditorCollection DataEditors => CoreCurrent.DataEditors; + public static DataValueReferenceFactoryCollection DataValueReferenceFactories => CoreCurrent.DataValueReferenceFactories; + public static PropertyEditorCollection PropertyEditors => CoreCurrent.PropertyEditors; public static ParameterEditorCollection ParameterEditors => CoreCurrent.ParameterEditors; diff --git a/src/Umbraco.Web/Controllers/UmbLoginController.cs b/src/Umbraco.Web/Controllers/UmbLoginController.cs index 2ff80e2668..88bc17abff 100644 --- a/src/Umbraco.Web/Controllers/UmbLoginController.cs +++ b/src/Umbraco.Web/Controllers/UmbLoginController.cs @@ -46,7 +46,7 @@ namespace Umbraco.Web.Controllers // if it's not a local url we'll redirect to the root of the current site return Redirect(Url.IsLocalUrl(model.RedirectUrl) ? model.RedirectUrl - : CurrentPage.AncestorOrSelf(1).Url); + : CurrentPage.AncestorOrSelf(1).Url()); } //redirect to current page by default diff --git a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs index dc46a97df4..53865830d8 100644 --- a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs +++ b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs @@ -314,6 +314,10 @@ namespace Umbraco.Web.Editors "tinyMceApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( controller => controller.UploadImage()) }, + { + "imageUrlGeneratorApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + controller => controller.GetCropUrl(null, null, null, null, null)) + }, } }, { @@ -345,6 +349,7 @@ namespace Umbraco.Web.Editors {"loginBackgroundImage", Current.Configs.Settings().Content.LoginBackgroundImage}, {"showUserInvite", EmailSender.CanSendRequiredEmail}, {"canSendRequiredEmail", EmailSender.CanSendRequiredEmail}, + {"showAllowSegmentationForDocumentTypes", false}, } }, { diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index f5d72894ca..691d6afa3a 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -869,7 +869,7 @@ namespace Umbraco.Web.Editors return true; } - + /// /// Helper method to perform the saving of the content and add the notifications to the result @@ -897,7 +897,13 @@ namespace Umbraco.Web.Editors if (variantCount > 1) { var cultureErrors = ModelState.GetCulturesWithErrors(Services.LocalizationService, cultureForInvariantErrors); - foreach (var c in contentItem.Variants.Where(x => x.Save && !cultureErrors.Contains(x.Culture)).Select(x => x.Culture).ToArray()) + + var savedWithoutErrors = contentItem.Variants + .Where(x => x.Save && !cultureErrors.Contains(x.Culture) && x.Culture != null) + .Select(x => x.Culture) + .ToArray(); + + foreach (var c in savedWithoutErrors) { AddSuccessNotification(notifications, c, Services.TextService.Localize("speechBubbles/editContentSavedHeader"), @@ -1161,14 +1167,14 @@ namespace Umbraco.Web.Editors //validate if we can publish based on the mandatory language requirements var canPublish = ValidatePublishingMandatoryLanguages( cultureErrors, - contentItem, cultureVariants, mandatoryCultures, + contentItem, cultureVariants, mandatoryCultures, mandatoryVariant => mandatoryVariant.Publish); //Now check if there are validation errors on each variant. //If validation errors are detected on a variant and it's state is set to 'publish', then we //need to change it to 'save'. //It is a requirement that this is performed AFTER ValidatePublishingMandatoryLanguages. - + foreach (var variant in contentItem.Variants) { if (cultureErrors.Contains(variant.Culture)) @@ -1656,14 +1662,14 @@ namespace Umbraco.Web.Editors [HttpPost] public DomainSave PostSaveLanguageAndDomains(DomainSave model) { - foreach(var domain in model.Domains) + foreach (var domain in model.Domains) { try { var uri = DomainUtilities.ParseUriFromDomainName(domain.Name, Request.RequestUri); } catch (UriFormatException) - { + { var response = Request.CreateValidationErrorResponse(Services.TextService.Localize("assignDomain/invalidDomain")); throw new HttpResponseException(response); } @@ -1829,7 +1835,7 @@ namespace Umbraco.Web.Editors base.HandleInvalidModelState(display); } - + /// /// Maps the dto property values and names to the persisted model /// @@ -1842,7 +1848,7 @@ namespace Umbraco.Web.Editors var culture = property.PropertyType.VariesByCulture() ? variant.Culture : null; var segment = property.PropertyType.VariesBySegment() ? variant.Segment : null; return (culture, segment); - } + } var variantIndex = 0; @@ -1874,7 +1880,8 @@ namespace Umbraco.Web.Editors ? variant.PropertyCollectionDto : new ContentPropertyCollectionDto { - Properties = variant.PropertyCollectionDto.Properties.Where(x => !x.Culture.IsNullOrWhiteSpace()) + Properties = variant.PropertyCollectionDto.Properties.Where( + x => !x.Culture.IsNullOrWhiteSpace() || !x.Segment.IsNullOrWhiteSpace()) }; //for each variant, map the property values @@ -1884,15 +1891,15 @@ namespace Umbraco.Web.Editors (save, property) => { // Get property value - (var culture, var segment) = PropertyCultureAndSegment(property, variant); - return property.GetValue(culture, segment); + (var culture, var segment) = PropertyCultureAndSegment(property, variant); + return property.GetValue(culture, segment); }, (save, property, v) => { // Set property value (var culture, var segment) = PropertyCultureAndSegment(property, variant); - property.SetValue(v, culture, segment); - }, + property.SetValue(v, culture, segment); + }, variant.Culture); variantIndex++; @@ -2172,7 +2179,10 @@ namespace Umbraco.Web.Editors /// private ContentItemDisplay MapToDisplay(IContent content) { - var display = Mapper.Map(content); + var display = Mapper.Map(content, context => + { + context.Items["CurrentUser"] = Security.CurrentUser; + }); display.AllowPreview = display.AllowPreview && content.Trashed == false && content.ContentType.IsElement == false; return display; } diff --git a/src/Umbraco.Web/Editors/ContentTypeController.cs b/src/Umbraco.Web/Editors/ContentTypeController.cs index 0f51c35a14..9c248f186b 100644 --- a/src/Umbraco.Web/Editors/ContentTypeController.cs +++ b/src/Umbraco.Web/Editors/ContentTypeController.cs @@ -70,6 +70,13 @@ namespace Umbraco.Web.Editors return Services.ContentTypeService.Count(); } + [HttpGet] + [UmbracoTreeAuthorize(Constants.Trees.DocumentTypes)] + public bool HasContentNodes(int id) + { + return Services.ContentTypeService.HasContentNodes(id); + } + public DocumentTypeDisplay GetById(int id) { var ct = Services.ContentTypeService.Get(id); @@ -425,11 +432,11 @@ namespace Umbraco.Web.Editors } var contentType = Services.ContentTypeBaseServices.GetContentTypeOf(contentItem); - var ids = contentType.AllowedContentTypes.Select(x => x.Id.Value).ToArray(); + var ids = contentType.AllowedContentTypes.OrderBy(c => c.SortOrder).Select(x => x.Id.Value).ToArray(); if (ids.Any() == false) return Enumerable.Empty(); - types = Services.ContentTypeService.GetAll(ids).ToList(); + types = Services.ContentTypeService.GetAll(ids).OrderBy(c => ids.IndexOf(c.Id)).ToList(); } var basics = types.Where(type => type.IsElement == false).Select(Mapper.Map).ToList(); @@ -452,7 +459,7 @@ namespace Umbraco.Web.Editors } } - return basics; + return basics.OrderBy(c => contentId == Constants.System.Root ? c.Name : string.Empty); } /// diff --git a/src/Umbraco.Web/Editors/DataTypeController.cs b/src/Umbraco.Web/Editors/DataTypeController.cs index 5f5f5104cb..ad92d40ecf 100644 --- a/src/Umbraco.Web/Editors/DataTypeController.cs +++ b/src/Umbraco.Web/Editors/DataTypeController.cs @@ -427,7 +427,7 @@ namespace Umbraco.Web.Editors { var propertyEditor = propertyEditors.SingleOrDefault(x => x.Alias == dataType.Alias); if (propertyEditor != null) - dataType.HasPrevalues = propertyEditor.GetConfigurationEditor().Fields.Any(); ; + dataType.HasPrevalues = propertyEditor.GetConfigurationEditor().Fields.Any(); } var grouped = dataTypes diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index 0513017b70..3938ae5ab8 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -206,6 +206,35 @@ namespace Umbraco.Web.Editors throw new HttpResponseException(HttpStatusCode.NotFound); } + /// + /// Gets the url of an entity + /// + /// UDI of the entity to fetch URL for + /// The culture to fetch the URL for + /// The URL or path to the item + public HttpResponseMessage GetUrl(Udi udi, string culture = "*") + { + var intId = Services.EntityService.GetId(udi); + if (!intId.Success) + throw new HttpResponseException(HttpStatusCode.NotFound); + UmbracoEntityTypes entityType; + switch(udi.EntityType) + { + case Constants.UdiEntityType.Document: + entityType = UmbracoEntityTypes.Document; + break; + case Constants.UdiEntityType.Media: + entityType = UmbracoEntityTypes.Media; + break; + case Constants.UdiEntityType.Member: + entityType = UmbracoEntityTypes.Member; + break; + default: + throw new HttpResponseException(HttpStatusCode.NotFound); + } + return GetUrl(intId.Result, entityType, culture); + } + /// /// Gets the url of an entity /// @@ -303,7 +332,9 @@ namespace Umbraco.Web.Editors [HttpGet] public UrlAndAnchors GetUrlAndAnchors(int id, string culture = "*") { - var url = UmbracoContext.UrlProvider.GetUrl(id); + culture = culture ?? ClientCulture(); + + var url = UmbracoContext.UrlProvider.GetUrl(id, culture: culture); var anchorValues = Services.ContentService.GetAnchorValuesFromRTEs(id, culture); return new UrlAndAnchors(url, anchorValues); } @@ -662,6 +693,9 @@ namespace Umbraco.Web.Editors if (pageSize <= 0) throw new HttpResponseException(HttpStatusCode.NotFound); + // re-normalize since NULL can be passed in + filter = filter ?? string.Empty; + var objectType = ConvertToObjectType(type); if (objectType.HasValue) { @@ -734,7 +768,8 @@ namespace Umbraco.Web.Editors /// private IEnumerable ExamineSearch(string query, UmbracoEntityTypes entityType, string searchFrom = null, bool ignoreUserStartNodes = false) { - return _treeSearcher.ExamineSearch(query, entityType, 200, 0, out _, searchFrom, ignoreUserStartNodes); + var culture = ClientCulture(); + return _treeSearcher.ExamineSearch(query, entityType, 200, 0, culture, out _, searchFrom, ignoreUserStartNodes); } private IEnumerable GetResultForChildren(int id, UmbracoEntityTypes entityType) diff --git a/src/Umbraco.Web/Editors/ExamineManagementController.cs b/src/Umbraco.Web/Editors/ExamineManagementController.cs index 0953b41cac..cf1dfd5d5d 100644 --- a/src/Umbraco.Web/Editors/ExamineManagementController.cs +++ b/src/Umbraco.Web/Editors/ExamineManagementController.cs @@ -141,9 +141,6 @@ namespace Umbraco.Web.Editors try { - //clear and replace - index.CreateIndex(); - var cacheKey = "temp_indexing_op_" + index.Name; //put temp val in cache which is used as a rudimentary way to know when the indexing is done AppCaches.RuntimeCache.Insert(cacheKey, () => "tempValue", TimeSpan.FromMinutes(5)); diff --git a/src/Umbraco.Web/Editors/Filters/ContentModelValidator.cs b/src/Umbraco.Web/Editors/Filters/ContentModelValidator.cs index 0d77b35528..810c2d1bea 100644 --- a/src/Umbraco.Web/Editors/Filters/ContentModelValidator.cs +++ b/src/Umbraco.Web/Editors/Filters/ContentModelValidator.cs @@ -198,7 +198,7 @@ namespace Umbraco.Web.Editors.Filters r.ErrorMessage = property.ValidationRegExpMessage; } - modelState.AddPropertyError(r, property.Alias, property.Culture); + modelState.AddPropertyError(r, property.Alias, property.Culture, property.Segment); } } } diff --git a/src/Umbraco.Web/Editors/ImageUrlGeneratorController.cs b/src/Umbraco.Web/Editors/ImageUrlGeneratorController.cs new file mode 100644 index 0000000000..87d7e29619 --- /dev/null +++ b/src/Umbraco.Web/Editors/ImageUrlGeneratorController.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Umbraco.Core.Models; +using Umbraco.Web.Models; +using Umbraco.Web.Mvc; + +namespace Umbraco.Web.Editors +{ + /// + /// The API controller used for getting URLs for images with parameters + /// + /// + /// + /// This controller allows for retrieving URLs for processed images, such as resized, cropped, + /// or otherwise altered. These can be different based on the IImageUrlGenerator + /// implementation in use, and so the BackOffice could should not rely on hard-coded string + /// building to generate correct URLs + /// + /// + public class ImageUrlGeneratorController : UmbracoAuthorizedJsonController + { + private readonly IImageUrlGenerator _imageUrlGenerator; + + public ImageUrlGeneratorController(IImageUrlGenerator imageUrlGenerator) + { + _imageUrlGenerator = imageUrlGenerator; + } + + public string GetCropUrl(string mediaPath, int? width = null, int? height = null, ImageCropMode? imageCropMode = null, string animationProcessMode = null) + { + return mediaPath.GetCropUrl(_imageUrlGenerator, null, width: width, height: height, imageCropMode: imageCropMode, animationProcessMode: animationProcessMode); + } + } +} diff --git a/src/Umbraco.Web/Editors/ImagesController.cs b/src/Umbraco.Web/Editors/ImagesController.cs index b29c166765..f3682a5b43 100644 --- a/src/Umbraco.Web/Editors/ImagesController.cs +++ b/src/Umbraco.Web/Editors/ImagesController.cs @@ -2,8 +2,10 @@ using System.IO; using System.Net; using System.Net.Http; +using Umbraco.Core.Composing; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; +using Umbraco.Core.Models; using Umbraco.Web.Media; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi; @@ -19,11 +21,17 @@ namespace Umbraco.Web.Editors { private readonly IMediaFileSystem _mediaFileSystem; private readonly IContentSection _contentSection; + private readonly IImageUrlGenerator _imageUrlGenerator; - public ImagesController(IMediaFileSystem mediaFileSystem, IContentSection contentSection) + [Obsolete("This constructor will be removed in a future release. Please use the constructor with the IImageUrlGenerator overload")] + public ImagesController(IMediaFileSystem mediaFileSystem, IContentSection contentSection) : this (mediaFileSystem, contentSection, Current.ImageUrlGenerator) + { + } + public ImagesController(IMediaFileSystem mediaFileSystem, IContentSection contentSection, IImageUrlGenerator imageUrlGenerator) { _mediaFileSystem = mediaFileSystem; _contentSection = contentSection; + _imageUrlGenerator = imageUrlGenerator; } /// @@ -60,8 +68,25 @@ namespace Umbraco.Web.Editors //redirect to ImageProcessor thumbnail with rnd generated from last modified time of original media file var response = Request.CreateResponse(HttpStatusCode.Found); - var imageLastModified = _mediaFileSystem.GetLastModified(imagePath); - response.Headers.Location = new Uri($"{imagePath}?rnd={imageLastModified:yyyyMMddHHmmss}&upscale=false&width={width}&animationprocessmode=first&mode=max", UriKind.Relative); + + DateTimeOffset? imageLastModified = null; + try + { + imageLastModified = _mediaFileSystem.GetLastModified(imagePath); + + } + catch (Exception) + { + // if we get an exception here it's probably because the image path being requested is an image that doesn't exist + // in the local media file system. This can happen if someone is storing an absolute path to an image online, which + // is perfectly legal but in that case the media file system isn't going to resolve it. + // so ignore and we won't set a last modified date. + } + + var rnd = imageLastModified.HasValue ? $"&rnd={imageLastModified:yyyyMMddHHmmss}" : null; + var imageUrl = _imageUrlGenerator.GetImageUrl(new ImageUrlGenerationOptions(imagePath) { UpScale = false, Width = width, AnimationProcessMode = "first", ImageCropMode = "max", CacheBusterValue = rnd }); + + response.Headers.Location = new Uri(imageUrl, UriKind.RelativeOrAbsolute); return response; } diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index 81d5704b5a..c2561e334e 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -36,6 +36,7 @@ using Umbraco.Core.PropertyEditors; using Umbraco.Web.ContentApps; using Umbraco.Web.Editors.Binders; using Umbraco.Web.Editors.Filters; +using Umbraco.Core.Models.Entities; namespace Umbraco.Web.Editors { @@ -943,5 +944,31 @@ namespace Umbraco.Web.Editors return hasPathAccess; } + + public PagedResult GetPagedReferences(int id, string entityType, int pageNumber = 1, int pageSize = 100) + { + if (pageNumber <= 0 || pageSize <= 0) + { + throw new NotSupportedException("Both pageNumber and pageSize must be greater than zero"); + } + + var objectType = ObjectTypes.GetUmbracoObjectType(entityType); + var udiType = ObjectTypes.GetUdiType(objectType); + + var relations = Services.RelationService.GetPagedParentEntitiesByChildId(id, pageNumber - 1, pageSize, out var totalRecords, objectType); + + return new PagedResult(totalRecords, pageNumber, pageSize) + { + Items = relations.Cast().Select(rel => new EntityBasic + { + Id = rel.Id, + Key = rel.Key, + Udi = Udi.Create(udiType, rel.Key), + Icon = rel.ContentTypeIcon, + Name = rel.Name, + Alias = rel.ContentTypeAlias + }) + }; + } } } diff --git a/src/Umbraco.Web/Editors/MediaTypeController.cs b/src/Umbraco.Web/Editors/MediaTypeController.cs index 43569c77e2..3a4026423a 100644 --- a/src/Umbraco.Web/Editors/MediaTypeController.cs +++ b/src/Umbraco.Web/Editors/MediaTypeController.cs @@ -240,11 +240,11 @@ namespace Umbraco.Web.Editors } var contentType = Services.MediaTypeService.Get(contentItem.ContentTypeId); - var ids = contentType.AllowedContentTypes.Select(x => x.Id.Value).ToArray(); + var ids = contentType.AllowedContentTypes.OrderBy(c => c.SortOrder).Select(x => x.Id.Value).ToArray(); if (ids.Any() == false) return Enumerable.Empty(); - types = Services.MediaTypeService.GetAll(ids).ToList(); + types = Services.MediaTypeService.GetAll(ids).OrderBy(c => ids.IndexOf(c.Id)).ToList(); } var basics = types.Select(Mapper.Map).ToList(); @@ -255,7 +255,7 @@ namespace Umbraco.Web.Editors basic.Description = TranslateItem(basic.Description); } - return basics.OrderBy(x => x.Name); + return basics.OrderBy(c => contentId == Constants.System.Root ? c.Name : string.Empty); } /// diff --git a/src/Umbraco.Web/Editors/PackageInstallController.cs b/src/Umbraco.Web/Editors/PackageInstallController.cs index 6b2bd07fbd..1030498734 100644 --- a/src/Umbraco.Web/Editors/PackageInstallController.cs +++ b/src/Umbraco.Web/Editors/PackageInstallController.cs @@ -102,8 +102,8 @@ namespace Umbraco.Web.Editors model.LicenseUrl = ins.LicenseUrl; model.Readme = ins.Readme; model.ConflictingMacroAliases = ins.Warnings.ConflictingMacros.ToDictionary(x => x.Name, x => x.Alias); - model.ConflictingStyleSheetNames = ins.Warnings.ConflictingStylesheets.ToDictionary(x => x.Name, x => x.Alias); ; - model.ConflictingTemplateAliases = ins.Warnings.ConflictingTemplates.ToDictionary(x => x.Name, x => x.Alias); ; + model.ConflictingStyleSheetNames = ins.Warnings.ConflictingStylesheets.ToDictionary(x => x.Name, x => x.Alias); + model.ConflictingTemplateAliases = ins.Warnings.ConflictingTemplates.ToDictionary(x => x.Name, x => x.Alias); model.ContainsUnsecureFiles = ins.Warnings.UnsecureFiles.Any(); model.Url = ins.Url; model.Version = ins.Version; diff --git a/src/Umbraco.Web/Editors/RelationTypeController.cs b/src/Umbraco.Web/Editors/RelationTypeController.cs index faafbb79f1..f12faf77cc 100644 --- a/src/Umbraco.Web/Editors/RelationTypeController.cs +++ b/src/Umbraco.Web/Editors/RelationTypeController.cs @@ -3,12 +3,14 @@ using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Web.Http; +using System.Linq; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Mvc; @@ -45,14 +47,28 @@ namespace Umbraco.Web.Editors throw new HttpResponseException(HttpStatusCode.NotFound); } - var relations = Services.RelationService.GetByRelationTypeId(relationType.Id); - var display = Mapper.Map(relationType); - display.Relations = Mapper.MapEnumerable(relations); - + return display; } + public PagedResult GetPagedResults(int id, int pageNumber = 1, int pageSize = 100) + { + + if (pageNumber <= 0 || pageSize <= 0) + { + throw new NotSupportedException("Both pageNumber and pageSize must be greater than zero"); + } + + // Ordering do we need to pass through? + var relations = Services.RelationService.GetPagedByRelationTypeId(id, pageNumber -1, pageSize, out long totalRecords); + + return new PagedResult(totalRecords, pageNumber, pageSize) + { + Items = relations.Select(x => Mapper.Map(x)) + }; + } + /// /// Gets a list of object types which can be associated via relations. /// @@ -84,11 +100,7 @@ namespace Umbraco.Web.Editors /// A containing the persisted relation type's ID. public HttpResponseMessage PostCreate(RelationTypeSave relationType) { - var relationTypePersisted = new RelationType(relationType.ChildObjectType, relationType.ParentObjectType, relationType.Name.ToSafeAlias(true)) - { - Name = relationType.Name, - IsBidirectional = relationType.IsBidirectional - }; + var relationTypePersisted = new RelationType(relationType.Name, relationType.Name.ToSafeAlias(true), relationType.IsBidirectional, relationType.ChildObjectType, relationType.ParentObjectType); try { diff --git a/src/Umbraco.Web/Editors/TemplateController.cs b/src/Umbraco.Web/Editors/TemplateController.cs index 8da5e80f2e..903446d23c 100644 --- a/src/Umbraco.Web/Editors/TemplateController.cs +++ b/src/Umbraco.Web/Editors/TemplateController.cs @@ -194,7 +194,9 @@ namespace Umbraco.Web.Editors throw new HttpResponseException(HttpStatusCode.NotFound); } - var template = Services.FileService.CreateTemplateWithIdentity(display.Name, display.Alias, display.Content, master); + // we need to pass the template name as alias to keep the template file casing consistent with templates created with content + // - see comment in FileService.CreateTemplateForContentType for additional details + var template = Services.FileService.CreateTemplateWithIdentity(display.Name, display.Name, display.Content, master); Mapper.Map(template, display); } diff --git a/src/Umbraco.Web/Editors/TourController.cs b/src/Umbraco.Web/Editors/TourController.cs index 25b4f7e9fc..8991bcdd6a 100644 --- a/src/Umbraco.Web/Editors/TourController.cs +++ b/src/Umbraco.Web/Editors/TourController.cs @@ -110,6 +110,39 @@ namespace Umbraco.Web.Editors return result.Except(toursToBeRemoved).OrderBy(x => x.FileName, StringComparer.InvariantCultureIgnoreCase); } + /// + /// Gets a tours for a specific doctype + /// + /// The documenttype alias + /// A + public IEnumerable GetToursForDoctype(string doctypeAlias) + { + var tourFiles = this.GetTours(); + + var doctypeAliasWithCompositions = new List + { + doctypeAlias + }; + + var contentType = this.Services.ContentTypeService.Get(doctypeAlias); + + if (contentType != null) + { + doctypeAliasWithCompositions.AddRange(contentType.CompositionAliases()); + } + + return tourFiles.SelectMany(x => x.Tours) + .Where(x => + { + if (string.IsNullOrEmpty(x.ContentType)) + { + return false; + } + var contentTypes = x.ContentType.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(ct => ct.Trim()); + return contentTypes.Intersect(doctypeAliasWithCompositions).Any(); + }); + } + private void TryParseTourFile(string tourFile, ICollection result, List filters, diff --git a/src/Umbraco.Web/Editors/UpdateCheckController.cs b/src/Umbraco.Web/Editors/UpdateCheckController.cs index 132526576b..480298fea7 100644 --- a/src/Umbraco.Web/Editors/UpdateCheckController.cs +++ b/src/Umbraco.Web/Editors/UpdateCheckController.cs @@ -2,11 +2,14 @@ using System.Linq; using System.Net.Http; using System.Net.Http.Headers; +using System.Threading.Tasks; using System.Web.Http.Filters; +using Semver; using Umbraco.Core; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Models; +using Umbraco.Core.Services; using Umbraco.Web.Models; using Umbraco.Web.Mvc; @@ -15,8 +18,17 @@ namespace Umbraco.Web.Editors [PluginController("UmbracoApi")] public class UpdateCheckController : UmbracoAuthorizedJsonController { + private readonly IUpgradeService _upgradeService; + + public UpdateCheckController() { } + + public UpdateCheckController(IUpgradeService upgradeService) + { + _upgradeService = upgradeService; + } + [UpdateCheckResponseFilter] - public UpgradeCheckResponse GetCheck() + public async Task GetCheck() { var updChkCookie = Request.Headers.GetCookies("UMB_UPDCHK").FirstOrDefault(); var updateCheckCookie = updChkCookie != null ? updChkCookie["UMB_UPDCHK"].Value : ""; @@ -24,14 +36,11 @@ namespace Umbraco.Web.Editors { try { - var check = new org.umbraco.update.CheckForUpgrade { Timeout = 2000 }; + var version = new SemVersion(UmbracoVersion.Current.Major, UmbracoVersion.Current.Minor, + UmbracoVersion.Current.Build, UmbracoVersion.Comment); + var result = await _upgradeService.CheckUpgrade(version); - var result = check.CheckUpgrade(UmbracoVersion.Current.Major, - UmbracoVersion.Current.Minor, - UmbracoVersion.Current.Build, - UmbracoVersion.Comment); - - return new UpgradeCheckResponse(result.UpgradeType.ToString(), result.Comment, result.UpgradeUrl); + return new UpgradeCheckResponse(result.UpgradeType, result.Comment, result.UpgradeUrl); } catch (System.Net.WebException) { diff --git a/src/Umbraco.Web/Editors/UsersController.cs b/src/Umbraco.Web/Editors/UsersController.cs index 15e5b9404f..c27d7c9f48 100644 --- a/src/Umbraco.Web/Editors/UsersController.cs +++ b/src/Umbraco.Web/Editors/UsersController.cs @@ -26,6 +26,7 @@ using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Security; using Umbraco.Core.Services; using Umbraco.Web.Editors.Filters; +using Umbraco.Web.Models; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi; @@ -549,29 +550,7 @@ namespace Umbraco.Web.Editors if (Current.Configs.Settings().Security.UsernameIsEmail && found.Username == found.Email && userSave.Username != userSave.Email) { userSave.Username = userSave.Email; - } - - if (userSave.ChangePassword != null) - { - var passwordChanger = new PasswordChanger(Logger, Services.UserService, UmbracoContext.HttpContext); - - //this will change the password and raise appropriate events - var passwordChangeResult = await passwordChanger.ChangePasswordWithIdentityAsync(Security.CurrentUser, found, userSave.ChangePassword, UserManager); - if (passwordChangeResult.Success) - { - //need to re-get the user - found = Services.UserService.GetUserById(intId.Result); - } - else - { - hasErrors = true; - - foreach (var memberName in passwordChangeResult.Result.ChangeError.MemberNames) - { - ModelState.AddModelError(memberName, passwordChangeResult.Result.ChangeError.ErrorMessage); - } - } - } + } if (hasErrors) throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState)); @@ -587,6 +566,51 @@ namespace Umbraco.Web.Editors return display; } + /// + /// + /// + /// + /// + public async Task> PostChangePassword(ChangingPasswordModel changingPasswordModel) + { + changingPasswordModel = changingPasswordModel ?? throw new ArgumentNullException(nameof(changingPasswordModel)); + + if (ModelState.IsValid == false) + { + throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState)); + } + + var intId = changingPasswordModel.Id.TryConvertTo(); + if (intId.Success == false) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + var found = Services.UserService.GetUserById(intId.Result); + if (found == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + var passwordChanger = new PasswordChanger(Logger, Services.UserService, UmbracoContext.HttpContext); + var passwordChangeResult = await passwordChanger.ChangePasswordWithIdentityAsync(Security.CurrentUser, found, changingPasswordModel, UserManager); + + if (passwordChangeResult.Success) + { + var result = new ModelWithNotifications(passwordChangeResult.Result.ResetPassword); + result.AddSuccessNotification(Services.TextService.Localize("general/success"), Services.TextService.Localize("user/passwordChangedGeneric")); + return result; + } + + foreach (var memberName in passwordChangeResult.Result.ChangeError.MemberNames) + { + ModelState.AddModelError(memberName, passwordChangeResult.Result.ChangeError.ErrorMessage); + } + + throw new HttpResponseException(Request.CreateValidationErrorResponse(ModelState)); + } + + /// /// Disables the users with the given user ids /// diff --git a/src/Umbraco.Web/GridTemplateExtensions.cs b/src/Umbraco.Web/GridTemplateExtensions.cs index afa929cfbb..81dc33d2c6 100644 --- a/src/Umbraco.Web/GridTemplateExtensions.cs +++ b/src/Umbraco.Web/GridTemplateExtensions.cs @@ -45,6 +45,34 @@ namespace Umbraco.Web return html.Partial(view, model); } + public static MvcHtmlString GetGridHtml(this HtmlHelper html, IPublishedElement contentItem) + { + return html.GetGridHtml(contentItem, "bodyText", "bootstrap3"); + } + + public static MvcHtmlString GetGridHtml(this HtmlHelper html, IPublishedElement contentItem, string propertyAlias) + { + if (propertyAlias == null) throw new ArgumentNullException(nameof(propertyAlias)); + if (string.IsNullOrWhiteSpace(propertyAlias)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(propertyAlias)); + + return html.GetGridHtml(contentItem, propertyAlias, "bootstrap3"); + } + + public static MvcHtmlString GetGridHtml(this HtmlHelper html, IPublishedElement contentItem, string propertyAlias, string framework) + { + if (propertyAlias == null) throw new ArgumentNullException(nameof(propertyAlias)); + if (string.IsNullOrWhiteSpace(propertyAlias)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(propertyAlias)); + + var view = "Grid/" + framework; + var prop = contentItem.GetProperty(propertyAlias); + if (prop == null) throw new InvalidOperationException("No property type found with alias " + propertyAlias); + var model = prop.GetValue(); + + var asString = model as string; + if (asString != null && string.IsNullOrEmpty(asString)) return new MvcHtmlString(string.Empty); + + return html.Partial(view, model); + } public static MvcHtmlString GetGridHtml(this IPublishedProperty property, HtmlHelper html, string framework = "bootstrap3") { var asString = property.GetValue() as string; diff --git a/src/Umbraco.Web/HealthCheck/Checks/Data/DatabaseIntegrityCheck.cs b/src/Umbraco.Web/HealthCheck/Checks/Data/DatabaseIntegrityCheck.cs new file mode 100644 index 0000000000..0c3e2f3d91 --- /dev/null +++ b/src/Umbraco.Web/HealthCheck/Checks/Data/DatabaseIntegrityCheck.cs @@ -0,0 +1,124 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Serilog.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Services; + +namespace Umbraco.Web.HealthCheck.Checks.Data +{ + [HealthCheck( + "73DD0C1C-E0CA-4C31-9564-1DCA509788AF", + "Database data integrity check", + Description = "Checks for various data integrity issues in the Umbraco database.", + Group = "Data Integrity")] + public class DatabaseIntegrityCheck : HealthCheck + { + private readonly IContentService _contentService; + private readonly IMediaService _mediaService; + private const string _fixMediaPaths = "fixMediaPaths"; + private const string _fixContentPaths = "fixContentPaths"; + private const string _fixMediaPathsTitle = "Fix media paths"; + private const string _fixContentPathsTitle = "Fix content paths"; + + public DatabaseIntegrityCheck(IContentService contentService, IMediaService mediaService) + { + _contentService = contentService; + _mediaService = mediaService; + } + + /// + /// Get the status for this health check + /// + /// + public override IEnumerable GetStatus() + { + //return the statuses + return new[] + { + CheckDocuments(false), + CheckMedia(false) + }; + } + + private HealthCheckStatus CheckMedia(bool fix) + { + return CheckPaths(_fixMediaPaths, _fixMediaPathsTitle, Core.Constants.UdiEntityType.Media, fix, + () => _mediaService.CheckDataIntegrity(new ContentDataIntegrityReportOptions {FixIssues = fix})); + } + + private HealthCheckStatus CheckDocuments(bool fix) + { + return CheckPaths(_fixContentPaths, _fixContentPathsTitle, Core.Constants.UdiEntityType.Document, fix, + () => _contentService.CheckDataIntegrity(new ContentDataIntegrityReportOptions {FixIssues = fix})); + } + + private HealthCheckStatus CheckPaths(string actionAlias, string actionName, string entityType, bool detailedReport, Func doCheck) + { + var report = doCheck(); + + var actions = new List(); + if (!report.Ok) + { + actions.Add(new HealthCheckAction(actionAlias, Id) + { + Name = actionName + }); + } + + return new HealthCheckStatus(GetReport(report, entityType, detailedReport)) + { + ResultType = report.Ok ? StatusResultType.Success : StatusResultType.Error, + Actions = actions + }; + } + + private static string GetReport(ContentDataIntegrityReport report, string entityType, bool detailed) + { + var sb = new StringBuilder(); + + if (report.Ok) + { + sb.AppendLine($"

    All {entityType} paths are valid

    "); + + if (!detailed) + return sb.ToString(); + } + else + { + sb.AppendLine($"

    {report.DetectedIssues.Count} invalid {entityType} paths detected.

    "); + } + + if (detailed && report.DetectedIssues.Count > 0) + { + sb.AppendLine("
      "); + foreach (var issueGroup in report.DetectedIssues.GroupBy(x => x.Value.IssueType)) + { + var countByGroup = issueGroup.Count(); + var fixedByGroup = issueGroup.Count(x => x.Value.Fixed); + sb.AppendLine("
    • "); + sb.AppendLine($"{countByGroup} issues of type {issueGroup.Key} ... {fixedByGroup} fixed"); + sb.AppendLine("
    • "); + } + sb.AppendLine("
    "); + } + + return sb.ToString(); + } + + public override HealthCheckStatus ExecuteAction(HealthCheckAction action) + { + switch (action.Alias) + { + case _fixContentPaths: + return CheckDocuments(true); + case _fixMediaPaths: + return CheckMedia(true); + default: + throw new InvalidOperationException("Action not supported"); + } + } + } +} diff --git a/src/Umbraco.Web/HealthCheck/NotificationMethods/EmailNotificationMethod.cs b/src/Umbraco.Web/HealthCheck/NotificationMethods/EmailNotificationMethod.cs index 87c0e4f46d..873b356214 100644 --- a/src/Umbraco.Web/HealthCheck/NotificationMethods/EmailNotificationMethod.cs +++ b/src/Umbraco.Web/HealthCheck/NotificationMethods/EmailNotificationMethod.cs @@ -20,7 +20,7 @@ namespace Umbraco.Web.HealthCheck.NotificationMethods public EmailNotificationMethod(ILocalizedTextService textService, IRuntimeState runtimeState, ILogger logger) { - var recipientEmail = Settings["recipientEmail"]?.Value; + var recipientEmail = Settings?["recipientEmail"]?.Value; if (string.IsNullOrWhiteSpace(recipientEmail)) { Enabled = false; diff --git a/src/Umbraco.Web/HtmlStringUtilities.cs b/src/Umbraco.Web/HtmlStringUtilities.cs index 4606a58a3a..a8cbb70019 100644 --- a/src/Umbraco.Web/HtmlStringUtilities.cs +++ b/src/Umbraco.Web/HtmlStringUtilities.cs @@ -243,21 +243,27 @@ namespace Umbraco.Web } } - if (!lengthReached && currentTextLength >= length) + if (!lengthReached) { - // if the last character added was the first of a two character unicode pair, add the second character - if (Char.IsHighSurrogate((char)ic)) + if (currentTextLength == length) { - var lowSurrogate = tr.Read(); - outputtw.Write((char)lowSurrogate); - } + // if the last character added was the first of a two character unicode pair, add the second character + if (char.IsHighSurrogate((char)ic)) + { + var lowSurrogate = tr.Read(); + outputtw.Write((char)lowSurrogate); + } - // Reached truncate limit. - if (addElipsis) - { - outputtw.Write(hellip); } - lengthReached = true; + // only add elipsis if current length greater than original length + if (currentTextLength > length) + { + if (addElipsis) + { + outputtw.Write(hellip); + } + lengthReached = true; + } } } diff --git a/src/Umbraco.Web/ImageCropperTemplateCoreExtensions.cs b/src/Umbraco.Web/ImageCropperTemplateCoreExtensions.cs new file mode 100644 index 0000000000..7ac5578d75 --- /dev/null +++ b/src/Umbraco.Web/ImageCropperTemplateCoreExtensions.cs @@ -0,0 +1,379 @@ +using System; +using Newtonsoft.Json.Linq; +using System.Globalization; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.PropertyEditors.ValueConverters; +using Umbraco.Web.Models; + +namespace Umbraco.Web +{ + public static class ImageCropperTemplateCoreExtensions + { + /// + /// Gets the ImageProcessor Url by the crop alias (from the "umbracoFile" property alias) on the IPublishedContent item + /// + /// + /// The IPublishedContent item. + /// + /// + /// The crop alias e.g. thumbnail + /// + /// + /// The ImageProcessor.Web Url. + /// + public static string GetCropUrl(this IPublishedContent mediaItem, string cropAlias, IImageUrlGenerator imageUrlGenerator) + { + return mediaItem.GetCropUrl(imageUrlGenerator, cropAlias: cropAlias, useCropDimensions: true); + } + + /// + /// Gets the ImageProcessor Url by the crop alias using the specified property containing the image cropper Json data on the IPublishedContent item. + /// + /// + /// The IPublishedContent item. + /// + /// + /// The property alias of the property containing the Json data e.g. umbracoFile + /// + /// + /// The crop alias e.g. thumbnail + /// + /// + /// The ImageProcessor.Web Url. + /// + public static string GetCropUrl(this IPublishedContent mediaItem, string propertyAlias, string cropAlias, IImageUrlGenerator imageUrlGenerator) + { + return mediaItem.GetCropUrl(imageUrlGenerator, propertyAlias: propertyAlias, cropAlias: cropAlias, useCropDimensions: true); + } + + /// + /// Gets the ImageProcessor Url from the IPublishedContent item. + /// + /// + /// The IPublishedContent item. + /// + /// + /// The width of the output image. + /// + /// + /// The height of the output image. + /// + /// + /// Property alias of the property containing the Json data. + /// + /// + /// The crop alias. + /// + /// + /// Quality percentage of the output image. + /// + /// + /// The image crop mode. + /// + /// + /// The image crop anchor. + /// + /// + /// Use focal point, to generate an output image using the focal point instead of the predefined crop + /// + /// + /// 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 serialized date of the last edit of the item to ensure client cache refresh when updated + /// + /// + /// These are any query string parameters (formatted as query strings) that ImageProcessor supports. For example: + /// + /// + /// + /// + /// + /// Use a dimension as a ratio + /// + /// + /// If the image should be upscaled to requested dimensions + /// + /// + /// The . + /// + public static string GetCropUrl( + this IPublishedContent mediaItem, + IImageUrlGenerator imageUrlGenerator, + 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) + { + if (mediaItem == null) throw new ArgumentNullException("mediaItem"); + + var cacheBusterValue = cacheBuster ? mediaItem.UpdateDate.ToFileTimeUtc().ToString(CultureInfo.InvariantCulture) : null; + + if (mediaItem.HasProperty(propertyAlias) == false || mediaItem.HasValue(propertyAlias) == false) + return string.Empty; + + var mediaItemUrl = mediaItem.MediaUrl(propertyAlias: propertyAlias); + + //get the default obj from the value converter + var cropperValue = mediaItem.Value(propertyAlias); + + //is it strongly typed? + var stronglyTyped = cropperValue as ImageCropperValue; + if (stronglyTyped != null) + { + return GetCropUrl( + mediaItemUrl, imageUrlGenerator, stronglyTyped, width, height, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions, + cacheBusterValue, furtherOptions, ratioMode, upScale); + } + + //this shouldn't be the case but we'll check + var jobj = cropperValue as JObject; + if (jobj != null) + { + stronglyTyped = jobj.ToObject(); + return GetCropUrl( + mediaItemUrl, imageUrlGenerator, stronglyTyped, width, height, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions, + cacheBusterValue, furtherOptions, ratioMode, upScale); + } + + //it's a single string + return GetCropUrl( + mediaItemUrl, imageUrlGenerator, width, height, mediaItemUrl, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions, + cacheBusterValue, furtherOptions, ratioMode, upScale); + } + + /// + /// Gets the ImageProcessor Url from the image path. + /// + /// + /// The image url. + /// + /// + /// The width of the output image. + /// + /// + /// 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. + /// + /// + /// The image crop mode. + /// + /// + /// The image crop anchor. + /// + /// + /// 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 + /// + /// + /// Add a serialized date of the last edit of the item to ensure client cache refresh when updated + /// + /// + /// These are any query string parameters (formatted as query strings) that ImageProcessor supports. For example: + /// + /// + /// + /// + /// + /// Use a dimension as a ratio + /// + /// + /// If the image should be upscaled to requested dimensions + /// + /// + /// The . + /// + public static string GetCropUrl( + this string imageUrl, + IImageUrlGenerator imageUrlGenerator, + 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) + { + if (string.IsNullOrEmpty(imageUrl)) return string.Empty; + + ImageCropperValue cropDataSet = null; + if (string.IsNullOrEmpty(imageCropperValue) == false && imageCropperValue.DetectIsJson() && (imageCropMode == ImageCropMode.Crop || imageCropMode == null)) + { + cropDataSet = imageCropperValue.DeserializeImageCropperValue(); + } + return GetCropUrl( + imageUrl, imageUrlGenerator, cropDataSet, width, height, cropAlias, quality, imageCropMode, + imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBusterValue, furtherOptions, ratioMode, upScale); + } + + /// + /// Gets the ImageProcessor Url from the image path. + /// + /// + /// The image url. + /// + /// + /// The generator that will process all the options and the image URL to return a full image urls with all processing options appended + /// + /// + /// + /// The width of the output image. + /// + /// + /// The height of the output image. + /// + /// + /// The crop alias. + /// + /// + /// Quality percentage of the output image. + /// + /// + /// The image crop mode. + /// + /// + /// The image crop anchor. + /// + /// + /// 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 + /// + /// + /// Add a serialized date of the last edit of the item to ensure client cache refresh when updated + /// + /// + /// These are any query string parameters (formatted as query strings) that ImageProcessor supports. For example: + /// + /// + /// + /// + /// + /// Use a dimension as a ratio + /// + /// + /// If the image should be upscaled to requested dimensions + /// + /// + /// The . + /// + public static string GetCropUrl( + this string imageUrl, + IImageUrlGenerator imageUrlGenerator, + ImageCropperValue cropDataSet, + int? width = null, + int? height = 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, + string animationProcessMode = null) + { + if (string.IsNullOrEmpty(imageUrl)) return string.Empty; + + ImageUrlGenerationOptions options; + + if (cropDataSet != null && (imageCropMode == ImageCropMode.Crop || imageCropMode == null)) + { + var crop = cropDataSet.GetCrop(cropAlias); + + // if a crop was specified, but not found, return null + if (crop == null && !string.IsNullOrWhiteSpace(cropAlias)) + return null; + + options = cropDataSet.GetCropBaseOptions(imageUrl, crop, string.IsNullOrWhiteSpace(cropAlias), preferFocalPoint); + + 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) + { + options.HeightRatio = (decimal)crop.Height / crop.Width; + } + + // 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) + { + options.WidthRatio = (decimal)crop.Width / crop.Height; + } + } + else + { + options = new ImageUrlGenerationOptions (imageUrl) + { + ImageCropMode = (imageCropMode ?? ImageCropMode.Pad).ToString().ToLowerInvariant(), + ImageCropAnchor = imageCropAnchor?.ToString().ToLowerInvariant() + }; + } + + options.Quality = quality; + options.Width = ratioMode != null && ratioMode.Value == ImageCropRatioMode.Width ? null : width; + options.Height = ratioMode != null && ratioMode.Value == ImageCropRatioMode.Height ? null : height; + options.AnimationProcessMode = animationProcessMode; + + if (ratioMode == ImageCropRatioMode.Width && height != null) + { + // if only height specified then assume a square + if (width == null) width = height; + options.WidthRatio = (decimal)width / (decimal)height; + } + + if (ratioMode == ImageCropRatioMode.Height && width != null) + { + // if only width specified then assume a square + if (height == null) height = width; + options.HeightRatio = (decimal)height / (decimal)width; + } + + options.UpScale = upScale; + options.FurtherOptions = furtherOptions; + options.CacheBusterValue = cacheBusterValue; + + return imageUrlGenerator.GetImageUrl(options); + } + } +} diff --git a/src/Umbraco.Web/ImageCropperTemplateExtensions.cs b/src/Umbraco.Web/ImageCropperTemplateExtensions.cs index 656f1e05a2..f3833ba42a 100644 --- a/src/Umbraco.Web/ImageCropperTemplateExtensions.cs +++ b/src/Umbraco.Web/ImageCropperTemplateExtensions.cs @@ -29,10 +29,7 @@ namespace Umbraco.Web /// /// The ImageProcessor.Web Url. /// - public static string GetCropUrl(this IPublishedContent mediaItem, string cropAlias) - { - return mediaItem.GetCropUrl(cropAlias: cropAlias, useCropDimensions: true); - } + public static string GetCropUrl(this IPublishedContent mediaItem, string cropAlias) => ImageCropperTemplateCoreExtensions.GetCropUrl(mediaItem, cropAlias, Current.ImageUrlGenerator); /// /// Gets the ImageProcessor Url by the crop alias using the specified property containing the image cropper Json data on the IPublishedContent item. @@ -49,10 +46,7 @@ namespace Umbraco.Web /// /// The ImageProcessor.Web Url. /// - public static string GetCropUrl(this IPublishedContent mediaItem, string propertyAlias, string cropAlias) - { - return mediaItem.GetCropUrl(propertyAlias: propertyAlias, cropAlias: cropAlias, useCropDimensions: true); - } + public static string GetCropUrl(this IPublishedContent mediaItem, string propertyAlias, string cropAlias) => ImageCropperTemplateCoreExtensions.GetCropUrl(mediaItem, propertyAlias, cropAlias, Current.ImageUrlGenerator); /// /// Gets the ImageProcessor Url from the IPublishedContent item. @@ -121,44 +115,7 @@ namespace Umbraco.Web bool cacheBuster = true, string furtherOptions = null, ImageCropRatioMode? ratioMode = null, - bool upScale = true) - { - if (mediaItem == null) throw new ArgumentNullException("mediaItem"); - - var cacheBusterValue = cacheBuster ? mediaItem.UpdateDate.ToFileTimeUtc().ToString(CultureInfo.InvariantCulture) : null; - - if (mediaItem.HasProperty(propertyAlias) == false || mediaItem.HasValue(propertyAlias) == false) - return string.Empty; - - var mediaItemUrl = mediaItem.MediaUrl(propertyAlias: propertyAlias); - - //get the default obj from the value converter - var cropperValue = mediaItem.Value(propertyAlias); - - //is it strongly typed? - var stronglyTyped = cropperValue as ImageCropperValue; - if (stronglyTyped != null) - { - return GetCropUrl( - mediaItemUrl, stronglyTyped, width, height, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions, - cacheBusterValue, furtherOptions, ratioMode, upScale); - } - - //this shouldn't be the case but we'll check - var jobj = cropperValue as JObject; - if (jobj != null) - { - stronglyTyped = jobj.ToObject(); - return GetCropUrl( - mediaItemUrl, stronglyTyped, width, height, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions, - cacheBusterValue, furtherOptions, ratioMode, upScale); - } - - //it's a single string - return GetCropUrl( - mediaItemUrl, width, height, mediaItemUrl, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions, - cacheBusterValue, furtherOptions, ratioMode, upScale); - } + bool upScale = true) => ImageCropperTemplateCoreExtensions.GetCropUrl(mediaItem, Current.ImageUrlGenerator, width, height, propertyAlias, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBuster, furtherOptions, ratioMode, upScale); /// /// Gets the ImageProcessor Url from the image path. @@ -227,19 +184,7 @@ namespace Umbraco.Web string cacheBusterValue = null, string furtherOptions = null, ImageCropRatioMode? ratioMode = null, - bool upScale = true) - { - if (string.IsNullOrEmpty(imageUrl)) return string.Empty; - - ImageCropperValue cropDataSet = null; - if (string.IsNullOrEmpty(imageCropperValue) == false && imageCropperValue.DetectIsJson() && (imageCropMode == ImageCropMode.Crop || imageCropMode == null)) - { - cropDataSet = imageCropperValue.DeserializeImageCropperValue(); - } - return GetCropUrl( - imageUrl, cropDataSet, width, height, cropAlias, quality, imageCropMode, - imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBusterValue, furtherOptions, ratioMode, upScale); - } + bool upScale = true) => ImageCropperTemplateCoreExtensions.GetCropUrl(imageUrl, Current.ImageUrlGenerator, width, height, imageCropperValue, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBusterValue, furtherOptions, ratioMode, upScale); /// /// Gets the ImageProcessor Url from the image path. @@ -306,129 +251,8 @@ namespace Umbraco.Web string cacheBusterValue = null, string furtherOptions = null, ImageCropRatioMode? ratioMode = null, - bool upScale = true) - { - if (string.IsNullOrEmpty(imageUrl) == false) - { - var imageProcessorUrl = new StringBuilder(); + bool upScale = true) => ImageCropperTemplateCoreExtensions.GetCropUrl(imageUrl, Current.ImageUrlGenerator, cropDataSet, width, height, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBusterValue, furtherOptions, ratioMode, upScale); - if (cropDataSet != null && (imageCropMode == ImageCropMode.Crop || imageCropMode == null)) - { - var crop = cropDataSet.GetCrop(cropAlias); - - // if a crop was specified, but not found, return null - if (crop == null && !string.IsNullOrWhiteSpace(cropAlias)) - return null; - - imageProcessorUrl.Append(imageUrl); - cropDataSet.AppendCropBaseUrl(imageProcessorUrl, crop, string.IsNullOrWhiteSpace(cropAlias), preferFocalPoint); - - 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 - { - imageProcessorUrl.Append(imageUrl); - - if (imageCropMode == null) - { - imageCropMode = ImageCropMode.Pad; - } - - imageProcessorUrl.Append("?mode=" + imageCropMode.ToString().ToLower()); - - if (imageCropAnchor != null) - { - imageProcessorUrl.Append("&anchor=" + imageCropAnchor.ToString().ToLower()); - } - } - - var hasFormat = furtherOptions != null && furtherOptions.InvariantContains("&format="); - - //Only put quality here, if we don't have a format specified. - //Otherwise we need to put quality at the end to avoid it being overridden by the format. - if (quality != null && hasFormat == false) - { - imageProcessorUrl.Append("&quality=" + quality); - } - - if (width != null && ratioMode != ImageCropRatioMode.Width) - { - imageProcessorUrl.Append("&width=" + width); - } - - if (height != null && ratioMode != ImageCropRatioMode.Height) - { - imageProcessorUrl.Append("&height=" + height); - } - - if (ratioMode == ImageCropRatioMode.Width && height != null) - { - // if only height specified then assume a square - if (width == null) - { - width = height; - } - - 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 square - if (height == null) - { - height = width; - } - - var heightRatio = (decimal)height / (decimal)width; - imageProcessorUrl.Append("&heightratio=" + heightRatio.ToString(CultureInfo.InvariantCulture)); - } - - if (upScale == false) - { - imageProcessorUrl.Append("&upscale=false"); - } - - if (furtherOptions != null) - { - imageProcessorUrl.Append(furtherOptions); - } - - //If furtherOptions contains a format, we need to put the quality after the format. - if (quality != null && hasFormat) - { - imageProcessorUrl.Append("&quality=" + quality); - } - - if (cacheBusterValue != null) - { - imageProcessorUrl.Append("&rnd=").Append(cacheBusterValue); - } - - return imageProcessorUrl.ToString(); - } - - return string.Empty; - } internal static ImageCropperValue DeserializeImageCropperValue(this string json) { diff --git a/src/Umbraco.Web/Install/InstallHelper.cs b/src/Umbraco.Web/Install/InstallHelper.cs index effb46c9b7..b74a20c27c 100644 --- a/src/Umbraco.Web/Install/InstallHelper.cs +++ b/src/Umbraco.Web/Install/InstallHelper.cs @@ -4,14 +4,17 @@ using System.Configuration; using System.IO; using System.Linq; using System.Net.Http; +using System.Threading.Tasks; using System.Web; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Migrations.Install; +using Umbraco.Core.Models; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.SqlSyntax; +using Umbraco.Core.Services; using Umbraco.Web.Composing; using Umbraco.Web.Install.Models; @@ -24,16 +27,18 @@ namespace Umbraco.Web.Install private readonly HttpContextBase _httpContext; private readonly ILogger _logger; private readonly IGlobalSettings _globalSettings; + private readonly IInstallationService _installationService; private InstallationType? _installationType; public InstallHelper(IUmbracoContextAccessor umbracoContextAccessor, DatabaseBuilder databaseBuilder, - ILogger logger, IGlobalSettings globalSettings) + ILogger logger, IGlobalSettings globalSettings, IInstallationService installationService) { _httpContext = umbracoContextAccessor.UmbracoContext.HttpContext; _logger = logger; _globalSettings = globalSettings; _databaseBuilder = databaseBuilder; + _installationService = installationService; } public InstallationType GetInstallationType() @@ -41,7 +46,7 @@ namespace Umbraco.Web.Install return _installationType ?? (_installationType = IsBrandNewInstall ? InstallationType.NewInstall : InstallationType.Upgrade).Value; } - internal void InstallStatus(bool isCompleted, string errorMsg) + internal async Task InstallStatus(bool isCompleted, string errorMsg) { try { @@ -59,8 +64,12 @@ namespace Umbraco.Web.Install if (installId == Guid.Empty) installId = Guid.NewGuid(); } + else + { + installId = Guid.NewGuid(); // Guid.TryParse will have reset installId to Guid.Empty + } } - _httpContext.Response.Cookies.Set(new HttpCookie(Constants.Web.InstallerCookieName, "1")); + _httpContext.Response.Cookies.Set(new HttpCookie(Constants.Web.InstallerCookieName, installId.ToString())); var dbProvider = string.Empty; if (IsBrandNewInstall == false) @@ -70,18 +79,13 @@ namespace Umbraco.Web.Install dbProvider = GetDbProviderString(Current.SqlContext); } - var check = new org.umbraco.update.CheckForUpgrade(); - check.Install(installId, - IsBrandNewInstall == false, - isCompleted, - DateTime.Now, - UmbracoVersion.Current.Major, - UmbracoVersion.Current.Minor, - UmbracoVersion.Current.Build, - UmbracoVersion.Comment, - errorMsg, - userAgent, - dbProvider); + var installLog = new InstallLog(installId: installId, isUpgrade: IsBrandNewInstall == false, + installCompleted: isCompleted, timestamp: DateTime.Now, versionMajor: UmbracoVersion.Current.Major, + versionMinor: UmbracoVersion.Current.Minor, versionPatch: UmbracoVersion.Current.Build, + versionComment: UmbracoVersion.Comment, error: errorMsg, userAgent: userAgent, + dbProvider: dbProvider); + + await _installationService.LogInstall(installLog); } catch (Exception ex) { diff --git a/src/Umbraco.Web/Install/InstallSteps/SetUmbracoVersionStep.cs b/src/Umbraco.Web/Install/InstallSteps/SetUmbracoVersionStep.cs index b5fdea32b7..17191d2521 100644 --- a/src/Umbraco.Web/Install/InstallSteps/SetUmbracoVersionStep.cs +++ b/src/Umbraco.Web/Install/InstallSteps/SetUmbracoVersionStep.cs @@ -13,7 +13,7 @@ using Umbraco.Web.Security; namespace Umbraco.Web.Install.InstallSteps { [InstallSetupStep(InstallationType.NewInstall | InstallationType.Upgrade, - "UmbracoVersion", 50, "Installation is complete!, get ready to be redirected to your new CMS.", + "UmbracoVersion", 50, "Installation is complete! Get ready to be redirected to your new CMS.", PerformsAppRestart = true)] internal class SetUmbracoVersionStep : InstallSetupStep { diff --git a/src/Umbraco.Web/JavaScript/AssetInitialization.cs b/src/Umbraco.Web/JavaScript/AssetInitialization.cs index 00605ece1f..42c750ffd3 100644 --- a/src/Umbraco.Web/JavaScript/AssetInitialization.cs +++ b/src/Umbraco.Web/JavaScript/AssetInitialization.cs @@ -5,6 +5,7 @@ using System.Reflection; using System.Web; using ClientDependency.Core; using ClientDependency.Core.Config; +using Umbraco.Core; using Umbraco.Web.Composing; using Umbraco.Web.PropertyEditors; @@ -35,7 +36,7 @@ namespace Umbraco.Web.JavaScript var requestUrl = httpContext.Request.Url; if (requestUrl == null) throw new ArgumentException("HttpContext.Request.Url is null.", nameof(httpContext)); - var dependencies = assets.Select(x => + var dependencies = assets.Where(x => x.IsNullOrWhiteSpace() == false).Select(x => { // most declarations with be made relative to the /umbraco folder, so things // like lib/blah/blah.js so we need to turn them into absolutes here diff --git a/src/Umbraco.Web/Macros/MacroRenderer.cs b/src/Umbraco.Web/Macros/MacroRenderer.cs index 8d7d3bc013..b4fd8c0d86 100755 --- a/src/Umbraco.Web/Macros/MacroRenderer.cs +++ b/src/Umbraco.Web/Macros/MacroRenderer.cs @@ -40,12 +40,18 @@ namespace Umbraco.Web.Macros #region MacroContent cache // gets this macro content cache identifier - private string GetContentCacheIdentifier(MacroModel model, int pageId) + private string GetContentCacheIdentifier(MacroModel model, int pageId, string cultureName) { var id = new StringBuilder(); var alias = model.Alias; id.AppendFormat("{0}-", alias); + //always add current culture to the key to allow variants to have different cache results + if (!string.IsNullOrEmpty(cultureName)) + { + // are there any unusual culture formats we'd need to handle? + id.AppendFormat("{0}-", cultureName); + } if (model.CacheByPage) id.AppendFormat("{0}-", pageId); @@ -190,7 +196,7 @@ namespace Umbraco.Web.Macros ? macroParams[key]?.ToString() ?? string.Empty : string.Empty; } - } + } #endregion #region Render/Execute @@ -221,7 +227,8 @@ namespace Umbraco.Web.Macros foreach (var prop in macro.Properties) prop.Value = ParseAttribute(pageElements, prop.Value); - macro.CacheIdentifier = GetContentCacheIdentifier(macro, content.Id); + var cultureName = System.Threading.Thread.CurrentThread.CurrentUICulture.Name; + macro.CacheIdentifier = GetContentCacheIdentifier(macro, content.Id, cultureName); // get the macro from cache if it is there var macroContent = GetMacroContentFromCache(macro); @@ -317,7 +324,7 @@ namespace Umbraco.Web.Macros private Attempt ExecuteMacroOfType(MacroModel model, IPublishedContent content) { if (model == null) throw new ArgumentNullException(nameof(model)); - + // ensure that we are running against a published node (ie available in XML) // that may not be the case if the macro is embedded in a RTE of an unpublished document @@ -453,9 +460,9 @@ namespace Umbraco.Web.Macros return value; } - + #endregion - + } } diff --git a/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs b/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs index 41f0e2fb65..3bbc4a793b 100644 --- a/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs +++ b/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs @@ -248,8 +248,10 @@ namespace Umbraco.Web.Macros public string UrlSegment => throw new NotImplementedException(); + [Obsolete("Use WriterName(IUserService) extension instead")] public string WriterName { get; } + [Obsolete("Use CreatorName(IUserService) extension instead")] public string CreatorName { get; } public int WriterId => _inner.WriterId; diff --git a/src/Umbraco.Web/Media/Exif/ExifBitConverter.cs b/src/Umbraco.Web/Media/Exif/ExifBitConverter.cs index 86bd12c15e..1dcae62acd 100644 --- a/src/Umbraco.Web/Media/Exif/ExifBitConverter.cs +++ b/src/Umbraco.Web/Media/Exif/ExifBitConverter.cs @@ -12,7 +12,7 @@ namespace Umbraco.Web.Media.Exif public ExifBitConverter(ByteOrder from, ByteOrder to) : base(from, to) { - ; + } #endregion diff --git a/src/Umbraco.Web/Media/Exif/ExifExtendedProperty.cs b/src/Umbraco.Web/Media/Exif/ExifExtendedProperty.cs index 63c1ce3365..8889a13e1d 100644 --- a/src/Umbraco.Web/Media/Exif/ExifExtendedProperty.cs +++ b/src/Umbraco.Web/Media/Exif/ExifExtendedProperty.cs @@ -28,7 +28,7 @@ namespace Umbraco.Web.Media.Exif public ExifEnumProperty(ExifTag tag, T value) : this(tag, value, false) { - ; + } public override ExifInterOperability Interoperability @@ -210,13 +210,13 @@ namespace Umbraco.Web.Media.Exif public ExifPointSubjectArea(ExifTag tag, ushort[] value) : base(tag, value) { - ; + } public ExifPointSubjectArea(ExifTag tag, ushort x, ushort y) - : base(tag, new ushort[] { x, y }) + : base(tag, new ushort[] {x, y}) { - ; + } } @@ -239,13 +239,13 @@ namespace Umbraco.Web.Media.Exif public ExifCircularSubjectArea(ExifTag tag, ushort[] value) : base(tag, value) { - ; + } public ExifCircularSubjectArea(ExifTag tag, ushort x, ushort y, ushort d) : base(tag, new ushort[] { x, y, d }) { - ; + } } @@ -269,13 +269,13 @@ namespace Umbraco.Web.Media.Exif public ExifRectangularSubjectArea(ExifTag tag, ushort[] value) : base(tag, value) { - ; + } public ExifRectangularSubjectArea(ExifTag tag, ushort x, ushort y, ushort w, ushort h) : base(tag, new ushort[] { x, y, w, h }) { - ; + } } @@ -303,13 +303,13 @@ namespace Umbraco.Web.Media.Exif public GPSLatitudeLongitude(ExifTag tag, MathEx.UFraction32[] value) : base(tag, value) { - ; + } public GPSLatitudeLongitude(ExifTag tag, float d, float m, float s) : base(tag, new MathEx.UFraction32[] { new MathEx.UFraction32(d), new MathEx.UFraction32(m), new MathEx.UFraction32(s) }) { - ; + } } @@ -331,13 +331,13 @@ namespace Umbraco.Web.Media.Exif public GPSTimeStamp(ExifTag tag, MathEx.UFraction32[] value) : base(tag, value) { - ; + } public GPSTimeStamp(ExifTag tag, float h, float m, float s) : base(tag, new MathEx.UFraction32[] { new MathEx.UFraction32(h), new MathEx.UFraction32(m), new MathEx.UFraction32(s) }) { - ; + } } diff --git a/src/Umbraco.Web/Media/Exif/JFIFExtendedProperty.cs b/src/Umbraco.Web/Media/Exif/JFIFExtendedProperty.cs index 94b255f4d1..24b3ac74be 100644 --- a/src/Umbraco.Web/Media/Exif/JFIFExtendedProperty.cs +++ b/src/Umbraco.Web/Media/Exif/JFIFExtendedProperty.cs @@ -19,7 +19,7 @@ namespace Umbraco.Web.Media.Exif public JFIFVersion(ExifTag tag, ushort value) : base(tag, value) { - ; + } public override string ToString() diff --git a/src/Umbraco.Web/Media/Exif/JPEGSection.cs b/src/Umbraco.Web/Media/Exif/JPEGSection.cs index a1bc420fe4..78565d2bfa 100644 --- a/src/Umbraco.Web/Media/Exif/JPEGSection.cs +++ b/src/Umbraco.Web/Media/Exif/JPEGSection.cs @@ -45,7 +45,7 @@ public JPEGSection(JPEGMarker marker) : this(marker, new byte[0], new byte[0]) { - ; + } #endregion diff --git a/src/Umbraco.Web/Media/Exif/MathEx.cs b/src/Umbraco.Web/Media/Exif/MathEx.cs index 735358c40a..94cbccfbda 100644 --- a/src/Umbraco.Web/Media/Exif/MathEx.cs +++ b/src/Umbraco.Web/Media/Exif/MathEx.cs @@ -403,37 +403,37 @@ namespace Umbraco.Web.Media.Exif public Fraction32(int numerator, int denominator) : this(numerator, denominator, 0) { - ; + } public Fraction32(int numerator) : this(numerator, (int)1) { - ; + } public Fraction32(Fraction32 f) : this(f.Numerator, f.Denominator, f.Error) { - ; + } public Fraction32(float value) : this((double)value) { - ; + } public Fraction32(double value) : this(FromDouble(value)) { - ; + } public Fraction32(string s) : this(FromString(s)) { - ; + } #endregion @@ -1033,37 +1033,37 @@ namespace Umbraco.Web.Media.Exif public UFraction32(uint numerator, uint denominator) : this(numerator, denominator, 0) { - ; + } public UFraction32(uint numerator) : this(numerator, (uint)1) { - ; + } public UFraction32(UFraction32 f) : this(f.Numerator, f.Denominator, f.Error) { - ; + } public UFraction32(float value) : this((double)value) { - ; + } public UFraction32(double value) : this(FromDouble(value)) { - ; + } public UFraction32(string s) : this(FromString(s)) { - ; + } #endregion diff --git a/src/Umbraco.Web/ModelStateExtensions.cs b/src/Umbraco.Web/ModelStateExtensions.cs index 00b17781e0..6144d6f9c9 100644 --- a/src/Umbraco.Web/ModelStateExtensions.cs +++ b/src/Umbraco.Web/ModelStateExtensions.cs @@ -49,13 +49,15 @@ namespace Umbraco.Web /// /// The culture for the property, if the property is invariant than this is empty internal static void AddPropertyError(this System.Web.Http.ModelBinding.ModelStateDictionary modelState, - ValidationResult result, string propertyAlias, string culture = "") + ValidationResult result, string propertyAlias, string culture = "", string segment = "") { if (culture == null) culture = ""; modelState.AddValidationError(result, "_Properties", propertyAlias, //if the culture is null, we'll add the term 'invariant' as part of the key - culture.IsNullOrWhiteSpace() ? "invariant" : culture); + culture.IsNullOrWhiteSpace() ? "invariant" : culture, + // if the segment is null, we'll add the term 'null' as part of the key + segment.IsNullOrWhiteSpace() ? "null" : segment); } /// diff --git a/src/Umbraco.Web/Models/BackOfficeTour.cs b/src/Umbraco.Web/Models/BackOfficeTour.cs index d5987ec5bc..7396d3d00d 100644 --- a/src/Umbraco.Web/Models/BackOfficeTour.cs +++ b/src/Umbraco.Web/Models/BackOfficeTour.cs @@ -16,20 +16,32 @@ namespace Umbraco.Web.Models [DataMember(Name = "name")] public string Name { get; set; } + [DataMember(Name = "alias")] public string Alias { get; set; } + [DataMember(Name = "group")] public string Group { get; set; } + [DataMember(Name = "groupOrder")] public int GroupOrder { get; set; } + + [DataMember(Name = "hidden")] + public bool Hidden { get; set; } + [DataMember(Name = "allowDisable")] public bool AllowDisable { get; set; } + [DataMember(Name = "requiredSections")] public List RequiredSections { get; set; } + [DataMember(Name = "steps")] public BackOfficeTourStep[] Steps { get; set; } [DataMember(Name = "culture")] public string Culture { get; set; } + + [DataMember(Name = "contentType")] + public string ContentType { get; set; } } } diff --git a/src/Umbraco.Web/Models/ChangingPasswordModel.cs b/src/Umbraco.Web/Models/ChangingPasswordModel.cs index 355b358049..633c42a056 100644 --- a/src/Umbraco.Web/Models/ChangingPasswordModel.cs +++ b/src/Umbraco.Web/Models/ChangingPasswordModel.cs @@ -49,5 +49,11 @@ namespace Umbraco.Web.Models /// [DataMember(Name = "generatedPassword")] public string GeneratedPassword { get; set; } + + /// + /// The id of the user - required to allow changing password without the entire UserSave model + /// + [DataMember(Name = "id")] + public int Id { get; set; } } } diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentTypeSave.cs b/src/Umbraco.Web/Models/ContentEditing/ContentTypeSave.cs index b1d24c5fd2..a93be56846 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentTypeSave.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentTypeSave.cs @@ -69,6 +69,9 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "allowCultureVariant")] public bool AllowCultureVariant { get; set; } + [DataMember(Name = "allowSegmentVariant")] + public bool AllowSegmentVariant { get; set; } + //Tabs [DataMember(Name = "groups")] public IEnumerable> Groups { get; set; } diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentVariationDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/ContentVariationDisplay.cs index aff79d7b9d..2a2ec49002 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentVariationDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentVariationDisplay.cs @@ -24,6 +24,9 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "name", IsRequired = true)] public string Name { get; set; } + [DataMember(Name = "displayName")] + public string DisplayName { get; set; } + /// /// Defines the tabs containing display properties /// diff --git a/src/Umbraco.Web/Models/ContentEditing/DocumentTypeDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/DocumentTypeDisplay.cs index 6b82f74ca7..9467033aec 100644 --- a/src/Umbraco.Web/Models/ContentEditing/DocumentTypeDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/DocumentTypeDisplay.cs @@ -20,9 +20,12 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "defaultTemplate")] public EntityBasic DefaultTemplate { get; set; } - + [DataMember(Name = "allowCultureVariant")] public bool AllowCultureVariant { get; set; } + [DataMember(Name = "allowSegmentVariant")] + public bool AllowSegmentVariant { get; set; } + } } diff --git a/src/Umbraco.Web/Models/ContentEditing/MediaTypeDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/MediaTypeDisplay.cs index 40227184db..ea0393336c 100644 --- a/src/Umbraco.Web/Models/ContentEditing/MediaTypeDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/MediaTypeDisplay.cs @@ -5,6 +5,7 @@ namespace Umbraco.Web.Models.ContentEditing [DataContract(Name = "contentType", Namespace = "")] public class MediaTypeDisplay : ContentTypeCompositionDisplay { - + [DataMember(Name = "isSystemMediaType")] + public bool IsSystemMediaType { get; set; } } } diff --git a/src/Umbraco.Web/Models/ContentEditing/PropertyTypeBasic.cs b/src/Umbraco.Web/Models/ContentEditing/PropertyTypeBasic.cs index d180a68a2c..5ff1744ea2 100644 --- a/src/Umbraco.Web/Models/ContentEditing/PropertyTypeBasic.cs +++ b/src/Umbraco.Web/Models/ContentEditing/PropertyTypeBasic.cs @@ -54,5 +54,8 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "allowCultureVariant")] public bool AllowCultureVariant { get; set; } + + [DataMember(Name = "allowSegmentVariant")] + public bool AllowSegmentVariant { get; set; } } } diff --git a/src/Umbraco.Web/Models/ContentEditing/RelationTypeDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/RelationTypeDisplay.cs index 55e140d6f3..b7f209cd20 100644 --- a/src/Umbraco.Web/Models/ContentEditing/RelationTypeDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/RelationTypeDisplay.cs @@ -24,7 +24,7 @@ namespace Umbraco.Web.Models.ContentEditing /// /// Corresponds to the NodeObjectType in the umbracoNode table [DataMember(Name = "parentObjectType", IsRequired = true)] - public Guid ParentObjectType { get; set; } + public Guid? ParentObjectType { get; set; } /// /// Gets or sets the Parent's object type name. @@ -38,7 +38,7 @@ namespace Umbraco.Web.Models.ContentEditing /// /// Corresponds to the NodeObjectType in the umbracoNode table [DataMember(Name = "childObjectType", IsRequired = true)] - public Guid ChildObjectType { get; set; } + public Guid? ChildObjectType { get; set; } /// /// Gets or sets the Child's object type name. @@ -47,13 +47,6 @@ namespace Umbraco.Web.Models.ContentEditing [ReadOnly(true)] public string ChildObjectTypeName { get; set; } - /// - /// Gets or sets the relations associated with this relation type. - /// - [DataMember(Name = "relations")] - [ReadOnly(true)] - public IEnumerable Relations { get; set; } - /// /// This is used to add custom localized messages/strings to the response for the app to use for localized UI purposes. /// diff --git a/src/Umbraco.Web/Models/ContentEditing/RelationTypeSave.cs b/src/Umbraco.Web/Models/ContentEditing/RelationTypeSave.cs index e7e8d6d2ba..434cf1de89 100644 --- a/src/Umbraco.Web/Models/ContentEditing/RelationTypeSave.cs +++ b/src/Umbraco.Web/Models/ContentEditing/RelationTypeSave.cs @@ -16,12 +16,12 @@ namespace Umbraco.Web.Models.ContentEditing /// Gets or sets the parent object type ID. /// [DataMember(Name = "parentObjectType", IsRequired = false)] - public Guid ParentObjectType { get; set; } + public Guid? ParentObjectType { get; set; } /// /// Gets or sets the child object type ID. /// [DataMember(Name = "childObjectType", IsRequired = false)] - public Guid ChildObjectType { get; set; } + public Guid? ChildObjectType { get; set; } } } diff --git a/src/Umbraco.Web/Models/ImageProcessorImageUrlGenerator.cs b/src/Umbraco.Web/Models/ImageProcessorImageUrlGenerator.cs new file mode 100644 index 0000000000..e471e9aa4a --- /dev/null +++ b/src/Umbraco.Web/Models/ImageProcessorImageUrlGenerator.cs @@ -0,0 +1,64 @@ +using System.Globalization; +using System.Text; +using Umbraco.Core; +using Umbraco.Core.Models; + +namespace Umbraco.Web.Models +{ + internal class ImageProcessorImageUrlGenerator : IImageUrlGenerator + { + public string GetImageUrl(ImageUrlGenerationOptions options) + { + if (options == null) return null; + + var imageProcessorUrl = new StringBuilder(options.ImageUrl ?? string.Empty); + + if (options.FocalPoint != null) AppendFocalPoint(imageProcessorUrl, options); + else if (options.Crop != null) AppendCrop(imageProcessorUrl, options); + else if (options.DefaultCrop) imageProcessorUrl.Append("?anchor=center&mode=crop"); + else + { + imageProcessorUrl.Append("?mode=").Append((options.ImageCropMode ?? "crop").ToLower()); + + if (options.ImageCropAnchor != null) imageProcessorUrl.Append("&anchor=").Append(options.ImageCropAnchor.ToLower()); + } + + var hasFormat = options.FurtherOptions != null && options.FurtherOptions.InvariantContains("&format="); + + //Only put quality here, if we don't have a format specified. + //Otherwise we need to put quality at the end to avoid it being overridden by the format. + if (options.Quality != null && hasFormat == false) imageProcessorUrl.Append("&quality=").Append(options.Quality); + if (options.HeightRatio != null) imageProcessorUrl.Append("&heightratio=").Append(options.HeightRatio.Value.ToString(CultureInfo.InvariantCulture)); + if (options.WidthRatio != null) imageProcessorUrl.Append("&widthratio=").Append(options.WidthRatio.Value.ToString(CultureInfo.InvariantCulture)); + if (options.Width != null) imageProcessorUrl.Append("&width=").Append(options.Width); + if (options.Height != null) imageProcessorUrl.Append("&height=").Append(options.Height); + if (options.UpScale == false) imageProcessorUrl.Append("&upscale=false"); + if (options.AnimationProcessMode != null) imageProcessorUrl.Append("&animationprocessmode=").Append(options.AnimationProcessMode); + if (options.FurtherOptions != null) imageProcessorUrl.Append(options.FurtherOptions); + + //If furtherOptions contains a format, we need to put the quality after the format. + if (options.Quality != null && hasFormat) imageProcessorUrl.Append("&quality=").Append(options.Quality); + if (options.CacheBusterValue != null) imageProcessorUrl.Append("&rnd=").Append(options.CacheBusterValue); + + return imageProcessorUrl.ToString(); + } + + private void AppendFocalPoint(StringBuilder imageProcessorUrl, ImageUrlGenerationOptions options) + { + imageProcessorUrl.Append("?center="); + imageProcessorUrl.Append(options.FocalPoint.Top.ToString(CultureInfo.InvariantCulture)).Append(","); + imageProcessorUrl.Append(options.FocalPoint.Left.ToString(CultureInfo.InvariantCulture)); + imageProcessorUrl.Append("&mode=crop"); + } + + private void AppendCrop(StringBuilder imageProcessorUrl, ImageUrlGenerationOptions options) + { + imageProcessorUrl.Append("?crop="); + imageProcessorUrl.Append(options.Crop.X1.ToString(CultureInfo.InvariantCulture)).Append(","); + imageProcessorUrl.Append(options.Crop.Y1.ToString(CultureInfo.InvariantCulture)).Append(","); + imageProcessorUrl.Append(options.Crop.X2.ToString(CultureInfo.InvariantCulture)).Append(","); + imageProcessorUrl.Append(options.Crop.Y2.ToString(CultureInfo.InvariantCulture)); + imageProcessorUrl.Append("&cropmode=percentage"); + } + } +} diff --git a/src/Umbraco.Web/Models/Mapping/ContentMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/ContentMapDefinition.cs index dc0df4ca96..865057ba24 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentMapDefinition.cs @@ -5,6 +5,7 @@ using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Mapping; using Umbraco.Core.Models; +using Umbraco.Core.Models.Membership; using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Routing; @@ -27,6 +28,7 @@ namespace Umbraco.Web.Models.Mapping private readonly ILocalizationService _localizationService; private readonly ILogger _logger; private readonly IUserService _userService; + private readonly IEntityService _entityService; private readonly TabsAndPropertiesMapper _tabsAndPropertiesMapper; private readonly ContentSavedStateMapper _stateMapper; private readonly ContentBasicSavedStateMapper _basicStateMapper; @@ -34,7 +36,7 @@ namespace Umbraco.Web.Models.Mapping public ContentMapDefinition(CommonMapper commonMapper, ILocalizedTextService localizedTextService, IContentService contentService, IContentTypeService contentTypeService, IFileService fileService, IUmbracoContextAccessor umbracoContextAccessor, IPublishedRouter publishedRouter, ILocalizationService localizationService, ILogger logger, - IUserService userService) + IUserService userService, IEntityService entityService) { _commonMapper = commonMapper; _localizedTextService = localizedTextService; @@ -46,11 +48,11 @@ namespace Umbraco.Web.Models.Mapping _localizationService = localizationService; _logger = logger; _userService = userService; - + _entityService = entityService; _tabsAndPropertiesMapper = new TabsAndPropertiesMapper(localizedTextService); _stateMapper = new ContentSavedStateMapper(); _basicStateMapper = new ContentBasicSavedStateMapper(); - _contentVariantMapper = new ContentVariantMapper(_localizationService); + _contentVariantMapper = new ContentVariantMapper(_localizationService, localizedTextService); } public void DefineMaps(UmbracoMapper mapper) @@ -80,7 +82,7 @@ namespace Umbraco.Web.Models.Mapping target.Icon = source.ContentType.Icon; target.Id = source.Id; target.IsBlueprint = source.Blueprint; - target.IsChildOfListView = DermineIsChildOfListView(source); + target.IsChildOfListView = DetermineIsChildOfListView(source, context); target.IsContainer = source.ContentType.IsContainer; target.IsElement = source.ContentType.IsElement; target.Key = source.Key; @@ -102,7 +104,7 @@ namespace Umbraco.Web.Models.Mapping target.ContentDto.Properties = context.MapEnumerable(source.Properties); } - // Umbraco.Code.MapAll -Segment -Language + // Umbraco.Code.MapAll -Segment -Language -DisplayName private void Map(IContent source, ContentVariantDisplay target, MapperContext context) { target.CreateDate = source.CreateDate; @@ -211,13 +213,66 @@ namespace Umbraco.Web.Models.Mapping return source.CultureInfos.TryGetValue(culture, out var name) && !name.Name.IsNullOrWhiteSpace() ? name.Name : $"({source.Name})"; } - private bool DermineIsChildOfListView(IContent source) + /// + /// Checks if the content item is a descendant of a list view + /// + /// + /// + /// + /// Returns true if the content item is a descendant of a list view and where the content is + /// not a current user's start node. + /// + /// + /// We must check if it's the current user's start node because in that case we will actually be + /// rendering the tree node underneath the list view to visually show context. In this case we return + /// false because the item is technically not being rendered as part of a list view but instead as a + /// real tree node. If we didn't perform this check then tree syncing wouldn't work correctly. + /// + private bool DetermineIsChildOfListView(IContent source, MapperContext context) { - // map the IsChildOfListView (this is actually if it is a descendant of a list view!) + var userStartNodes = Array.Empty(); + + // In cases where a user's start node is below a list view, we will actually render + // out the tree to that start node and in that case for that start node, we want to return + // false here. + if (context.HasItems && context.Items.TryGetValue("CurrentUser", out var usr) && usr is IUser currentUser) + { + userStartNodes = currentUser.CalculateContentStartNodeIds(_entityService); + if (!userStartNodes.Contains(Constants.System.Root)) + { + // return false if this is the user's actual start node, the node will be rendered in the tree + // regardless of if it's a list view or not + if (userStartNodes.Contains(source.Id)) + return false; + } + } + var parent = _contentService.GetParent(source); - return parent != null && (parent.ContentType.IsContainer || _contentTypeService.HasContainerInPath(parent.Path)); + + if (parent == null) + return false; + + var pathParts = parent.Path.Split(',').Select(x => int.TryParse(x, out var i) ? i : 0).ToList(); + + // reduce the path parts so we exclude top level content items that + // are higher up than a user's start nodes + foreach (var n in userStartNodes) + { + var index = pathParts.IndexOf(n); + if (index != -1) + { + // now trim all top level start nodes to the found index + for (var i = 0; i < index; i++) + { + pathParts.RemoveAt(0); + } + } + } + + return parent.ContentType.IsContainer || _contentTypeService.HasContainerInPath(pathParts.ToArray()); } + private DateTime? GetScheduledDate(IContent source, ContentScheduleAction action, MapperContext context) { var culture = context.GetCulture() ?? string.Empty; diff --git a/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs index 95101da4e3..c767e9aaf3 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs @@ -121,6 +121,7 @@ namespace Umbraco.Web.Models.Mapping MapTypeToDisplayBase(source, target); target.AllowCultureVariant = source.VariesByCulture(); + target.AllowSegmentVariant = source.VariesBySegment(); //sync templates target.AllowedTemplates = context.MapEnumerable(source.AllowedTemplates); @@ -145,6 +146,7 @@ namespace Umbraco.Web.Models.Mapping //default listview target.ListViewEditorName = Constants.Conventions.DataTypes.ListViewPrefix + "Media"; + target.IsSystemMediaType = source.IsSystemMediaType(); if (string.IsNullOrEmpty(source.Name)) return; @@ -215,7 +217,7 @@ namespace Umbraco.Web.Models.Mapping } // Umbraco.Code.MapAll -CreateDate -DeleteDate -UpdateDate - // Umbraco.Code.MapAll -SupportsPublishing -Key -PropertyEditorAlias -ValueStorageType + // Umbraco.Code.MapAll -SupportsPublishing -Key -PropertyEditorAlias -ValueStorageType -Variations private static void Map(PropertyTypeBasic source, PropertyType target, MapperContext context) { target.Name = source.Label; @@ -225,10 +227,9 @@ namespace Umbraco.Web.Models.Mapping target.MandatoryMessage = source.Validation.MandatoryMessage; target.ValidationRegExp = source.Validation.Pattern; target.ValidationRegExpMessage = source.Validation.PatternMessage; - target.Variations = source.AllowCultureVariant - ? target.Variations.SetFlag(ContentVariation.Culture) - : target.Variations.UnsetFlag(ContentVariation.Culture); - + target.SetVariesBy(ContentVariation.Culture, source.AllowCultureVariant); + target.SetVariesBy(ContentVariation.Segment, source.AllowSegmentVariant); + if (source.Id > 0) target.Id = source.Id; @@ -339,6 +340,7 @@ namespace Umbraco.Web.Models.Mapping { target.Alias = source.Alias; target.AllowCultureVariant = source.AllowCultureVariant; + target.AllowSegmentVariant = source.AllowSegmentVariant; target.DataTypeId = source.DataTypeId; target.DataTypeKey = source.DataTypeKey; target.Description = source.Description; @@ -355,6 +357,7 @@ namespace Umbraco.Web.Models.Mapping { target.Alias = source.Alias; target.AllowCultureVariant = source.AllowCultureVariant; + target.AllowSegmentVariant = source.AllowSegmentVariant; target.DataTypeId = source.DataTypeId; target.DataTypeKey = source.DataTypeKey; target.Description = source.Description; @@ -369,7 +372,7 @@ namespace Umbraco.Web.Models.Mapping target.Validation = source.Validation; } - // Umbraco.Code.MapAll -CreatorId -Level -SortOrder + // Umbraco.Code.MapAll -CreatorId -Level -SortOrder -Variations // Umbraco.Code.MapAll -CreateDate -UpdateDate -DeleteDate // Umbraco.Code.MapAll -ContentTypeComposition (done by AfterMapSaveToType) private static void MapSaveToTypeBase(TSource source, IContentTypeComposition target, MapperContext context) @@ -399,9 +402,8 @@ namespace Umbraco.Web.Models.Mapping if (!(target is IMemberType)) { - target.Variations = source.AllowCultureVariant - ? target.Variations.SetFlag(ContentVariation.Culture) - : target.Variations.UnsetFlag(ContentVariation.Culture); + target.SetVariesBy(ContentVariation.Culture, source.AllowCultureVariant); + target.SetVariesBy(ContentVariation.Segment, source.AllowSegmentVariant); } // handle property groups and property types @@ -492,7 +494,7 @@ namespace Umbraco.Web.Models.Mapping target.Udi = MapContentTypeUdi(source); target.UpdateDate = source.UpdateDate; - target.AllowedContentTypes = source.AllowedContentTypes.Select(x => x.Id.Value); + target.AllowedContentTypes = source.AllowedContentTypes.OrderBy(c => c.SortOrder).Select(x => x.Id.Value); target.CompositeContentTypes = source.ContentTypeComposition.Select(x => x.Alias); target.LockedCompositeContentTypes = MapLockedCompositions(source); } diff --git a/src/Umbraco.Web/Models/Mapping/ContentVariantMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentVariantMapper.cs index 5d076812f3..0b6be53045 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentVariantMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentVariantMapper.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using Umbraco.Core; using Umbraco.Core.Mapping; @@ -13,10 +14,12 @@ namespace Umbraco.Web.Models.Mapping internal class ContentVariantMapper { private readonly ILocalizationService _localizationService; + private readonly ILocalizedTextService _localizedTextService; - public ContentVariantMapper(ILocalizationService localizationService) + public ContentVariantMapper(ILocalizationService localizationService, ILocalizedTextService localizedTextService) { _localizationService = localizationService ?? throw new ArgumentNullException(nameof(localizationService)); + _localizedTextService = localizedTextService ?? throw new ArgumentNullException(nameof(localizedTextService)); } public IEnumerable Map(IContent source, MapperContext context) @@ -113,11 +116,19 @@ namespace Umbraco.Web.Models.Mapping /// /// /// - /// Returns all segments assigned to the content including 'null' values + /// Returns all segments assigned to the content including the default `null` segment. /// private IEnumerable GetSegments(IContent content) { - return content.Properties.SelectMany(p => p.Values.Select(v => v.Segment)).Distinct(); + // The default segment (null) is always there, + // even when there is no property data at all yet + var segments = new List { null }; + + // Add actual segments based on the property values + segments.AddRange(content.Properties.SelectMany(p => p.Values.Select(v => v.Segment))); + + // Do not return a segment more than once + return segments.Distinct(); } private ContentVariantDisplay CreateVariantDisplay(MapperContext context, IContent content, Language language, string segment) @@ -130,8 +141,31 @@ namespace Umbraco.Web.Models.Mapping variantDisplay.Segment = segment; variantDisplay.Language = language; variantDisplay.Name = content.GetCultureName(language?.IsoCode); + variantDisplay.DisplayName = GetDisplayName(language, segment); return variantDisplay; } + + private string GetDisplayName(Language language, string segment) + { + var isCultureVariant = language != null; + var isSegmentVariant = !segment.IsNullOrWhiteSpace(); + + if(!isCultureVariant && !isSegmentVariant) + { + return _localizedTextService.Localize("general/default"); + } + + var parts = new List(); + + if (isSegmentVariant) + parts.Add(segment); + + if (isCultureVariant) + parts.Add(language.Name); + + return string.Join(" — ", parts); + + } } } diff --git a/src/Umbraco.Web/Models/Mapping/EntityMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/EntityMapDefinition.cs index f2099f2554..ddff093aab 100644 --- a/src/Umbraco.Web/Models/Mapping/EntityMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/EntityMapDefinition.cs @@ -177,6 +177,12 @@ namespace Umbraco.Web.Models.Mapping target.Name = source.Values.ContainsKey("nodeName") ? source.Values["nodeName"] : "[no name]"; + var culture = context.GetCulture(); + if(culture.IsNullOrWhiteSpace() == false) + { + target.Name = source.Values.ContainsKey($"nodeName_{culture}") ? source.Values[$"nodeName_{culture}"] : target.Name; + } + if (source.Values.TryGetValue(UmbracoExamineIndex.UmbracoFileFieldName, out var umbracoFile)) { if (umbracoFile != null) @@ -234,11 +240,11 @@ namespace Umbraco.Web.Models.Mapping { switch (entity) { - case ContentEntitySlim contentEntity: - // NOTE: this case covers both content and media entities - return contentEntity.ContentTypeIcon; - case MemberEntitySlim memberEntity: + case IMemberEntitySlim memberEntity: return memberEntity.ContentTypeIcon.IfNullOrWhiteSpace(Constants.Icons.Member); + case IContentEntitySlim contentEntity: + // NOTE: this case covers both content and media entities + return contentEntity.ContentTypeIcon; } return null; diff --git a/src/Umbraco.Web/Models/Mapping/MediaMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/MediaMapDefinition.cs index 05c006ec41..fa89496392 100644 --- a/src/Umbraco.Web/Models/Mapping/MediaMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/MediaMapDefinition.cs @@ -56,7 +56,7 @@ namespace Umbraco.Web.Models.Mapping target.CreateDate = source.CreateDate; target.Icon = source.ContentType.Icon; target.Id = source.Id; - target.IsChildOfListView = DermineIsChildOfListView(source); + target.IsChildOfListView = DetermineIsChildOfListView(source); target.Key = source.Key; target.MediaLink = string.Join(",", source.GetUrls(Current.Configs.Settings().Content, _logger)); target.Name = source.Name; @@ -95,7 +95,7 @@ namespace Umbraco.Web.Models.Mapping target.VariesByCulture = source.ContentType.VariesByCulture(); } - private bool DermineIsChildOfListView(IMedia source) + private bool DetermineIsChildOfListView(IMedia source) { // map the IsChildOfListView (this is actually if it is a descendant of a list view!) var parent = _mediaService.GetParent(source); diff --git a/src/Umbraco.Web/Models/Mapping/PropertyTypeGroupMapper.cs b/src/Umbraco.Web/Models/Mapping/PropertyTypeGroupMapper.cs index 2f5822d1e3..370a459279 100644 --- a/src/Umbraco.Web/Models/Mapping/PropertyTypeGroupMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/PropertyTypeGroupMapper.cs @@ -241,7 +241,8 @@ namespace Umbraco.Web.Models.Mapping SortOrder = p.SortOrder, ContentTypeId = contentType.Id, ContentTypeName = contentType.Name, - AllowCultureVariant = p.VariesByCulture() + AllowCultureVariant = p.VariesByCulture(), + AllowSegmentVariant = p.VariesBySegment() }); } diff --git a/src/Umbraco.Web/Models/Mapping/RelationMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/RelationMapDefinition.cs index 7787750e54..d26a867858 100644 --- a/src/Umbraco.Web/Models/Mapping/RelationMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/RelationMapDefinition.cs @@ -1,12 +1,23 @@ -using Umbraco.Core; +using System.Linq; +using Umbraco.Core; using Umbraco.Core.Mapping; using Umbraco.Core.Models; +using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Web.Models.Mapping { internal class RelationMapDefinition : IMapDefinition { + private readonly IEntityService _entityService; + private readonly IRelationService _relationService; + + public RelationMapDefinition(IEntityService entityService, IRelationService relationService) + { + _entityService = entityService; + _relationService = relationService; + } + public void DefineMaps(UmbracoMapper mapper) { mapper.Define((source, context) => new RelationTypeDisplay(), Map); @@ -15,8 +26,8 @@ namespace Umbraco.Web.Models.Mapping } // Umbraco.Code.MapAll -Icon -Trashed -AdditionalData - // Umbraco.Code.MapAll -Relations -ParentId -Notifications - private static void Map(IRelationType source, RelationTypeDisplay target, MapperContext context) + // Umbraco.Code.MapAll -ParentId -Notifications + private void Map(IRelationType source, RelationTypeDisplay target, MapperContext context) { target.ChildObjectType = source.ChildObjectType; target.Id = source.Id; @@ -28,18 +39,32 @@ namespace Umbraco.Web.Models.Mapping target.Udi = Udi.Create(Constants.UdiEntityType.RelationType, source.Key); target.Path = "-1," + source.Id; - // Set the "friendly" names for the parent and child object types - target.ParentObjectTypeName = ObjectTypes.GetUmbracoObjectType(source.ParentObjectType).GetFriendlyName(); - target.ChildObjectTypeName = ObjectTypes.GetUmbracoObjectType(source.ChildObjectType).GetFriendlyName(); + // Set the "friendly" and entity names for the parent and child object types + if (source.ParentObjectType.HasValue) + { + var objType = ObjectTypes.GetUmbracoObjectType(source.ParentObjectType.Value); + target.ParentObjectTypeName = objType.GetFriendlyName(); + } + + if (source.ChildObjectType.HasValue) + { + var objType = ObjectTypes.GetUmbracoObjectType(source.ChildObjectType.Value); + target.ChildObjectTypeName = objType.GetFriendlyName(); + } } // Umbraco.Code.MapAll -ParentName -ChildName - private static void Map(IRelation source, RelationDisplay target, MapperContext context) + private void Map(IRelation source, RelationDisplay target, MapperContext context) { target.ChildId = source.ChildId; target.Comment = source.Comment; target.CreateDate = source.CreateDate; target.ParentId = source.ParentId; + + var entities = _relationService.GetEntitiesFromRelation(source); + + target.ParentName = entities.Item1.Name; + target.ChildName = entities.Item2.Name; } // Umbraco.Code.MapAll -CreateDate -UpdateDate -DeleteDate diff --git a/src/Umbraco.Web/Models/Mapping/UserMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/UserMapDefinition.cs index 88960fb189..aa158799cb 100644 --- a/src/Umbraco.Web/Models/Mapping/UserMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/UserMapDefinition.cs @@ -380,8 +380,8 @@ namespace Umbraco.Web.Models.Mapping .ToDictionary(x => x.Key, x => (IEnumerable)x.ToArray()); } - private static string MapContentTypeIcon(EntitySlim entity) - => entity is ContentEntitySlim contentEntity ? contentEntity.ContentTypeIcon : null; + private static string MapContentTypeIcon(IEntitySlim entity) + => entity is IContentEntitySlim contentEntity ? contentEntity.ContentTypeIcon : null; private IEnumerable GetStartNodes(int[] startNodeIds, UmbracoObjectTypes objectType, string localizedKey, MapperContext context) { diff --git a/src/Umbraco.Web/Models/PartialViewMacroModel.cs b/src/Umbraco.Web/Models/PartialViewMacroModel.cs index 9964877cba..f3272644f4 100644 --- a/src/Umbraco.Web/Models/PartialViewMacroModel.cs +++ b/src/Umbraco.Web/Models/PartialViewMacroModel.cs @@ -6,7 +6,7 @@ namespace Umbraco.Web.Models /// /// The model used when rendering Partial View Macros /// - public class PartialViewMacroModel + public class PartialViewMacroModel : IContentModel { public PartialViewMacroModel(IPublishedContent page, diff --git a/src/Umbraco.Web/Models/PublishedContent/PublishedValueFallback.cs b/src/Umbraco.Web/Models/PublishedContent/PublishedValueFallback.cs index 7b467b6d15..57c3094eaf 100644 --- a/src/Umbraco.Web/Models/PublishedContent/PublishedValueFallback.cs +++ b/src/Umbraco.Web/Models/PublishedContent/PublishedValueFallback.cs @@ -111,7 +111,7 @@ namespace Umbraco.Web.Models.PublishedContent var propertyType = content.ContentType.GetPropertyType(alias); if (propertyType != null) { - _variationContextAccessor.ContextualizeVariation(propertyType.Variations, ref culture, ref segment); + _variationContextAccessor.ContextualizeVariation(propertyType.Variations, content.Id, ref culture, ref segment); noValueProperty = content.GetProperty(alias); } @@ -168,7 +168,7 @@ namespace Umbraco.Web.Models.PublishedContent { culture = null; segment = null; - _variationContextAccessor.ContextualizeVariation(propertyType.Variations, ref culture, ref segment); + _variationContextAccessor.ContextualizeVariation(propertyType.Variations, content.Id, ref culture, ref segment); } property = content?.GetProperty(alias); diff --git a/src/Umbraco.Web/Models/PublishedContentBase.cs b/src/Umbraco.Web/Models/PublishedContentBase.cs index 148bab11c0..d6a57a66d7 100644 --- a/src/Umbraco.Web/Models/PublishedContentBase.cs +++ b/src/Umbraco.Web/Models/PublishedContentBase.cs @@ -54,6 +54,7 @@ namespace Umbraco.Web.Models public abstract int CreatorId { get; } /// + [Obsolete("Use CreatorName(IUserService) extension instead")] public abstract string CreatorName { get; } /// @@ -63,12 +64,14 @@ namespace Umbraco.Web.Models public abstract int WriterId { get; } /// + [Obsolete("Use WriterName(IUserService) extension instead")] public abstract string WriterName { get; } /// public abstract DateTime UpdateDate { get; } /// + [Obsolete("Use the Url() extension instead")] public virtual string Url => this.Url(); /// diff --git a/src/Umbraco.Web/Models/RegisterModel.cs b/src/Umbraco.Web/Models/RegisterModel.cs index 86a5459a74..fd6cadc04e 100644 --- a/src/Umbraco.Web/Models/RegisterModel.cs +++ b/src/Umbraco.Web/Models/RegisterModel.cs @@ -5,7 +5,6 @@ using System.ComponentModel.DataAnnotations; using System.Web; using System.Web.Mvc; using Umbraco.Core; -using Umbraco.Core.Composing; using Umbraco.Web.Security; using Current = Umbraco.Web.Composing.Current; @@ -65,7 +64,7 @@ namespace Umbraco.Web.Models /// [Required] public string Password { get; set; } - + /// /// The username of the model, if UsernameIsEmail is true then this is ignored. /// diff --git a/src/Umbraco.Web/PropertyEditors/ContentPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/ContentPickerPropertyEditor.cs index c6de91f560..683f1a05c3 100644 --- a/src/Umbraco.Web/PropertyEditors/ContentPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ContentPickerPropertyEditor.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using Umbraco.Core; using Umbraco.Core.Logging; +using Umbraco.Core.Models.Editors; using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors @@ -25,5 +26,24 @@ namespace Umbraco.Web.PropertyEditors { return new ContentPickerConfigurationEditor(); } + + protected override IDataValueEditor CreateValueEditor() => new ContentPickerPropertyValueEditor(Attribute); + + internal class ContentPickerPropertyValueEditor : DataValueEditor, IDataValueReference + { + public ContentPickerPropertyValueEditor(DataEditorAttribute attribute) : base(attribute) + { + } + + public IEnumerable GetReferences(object value) + { + var asString = value is string str ? str : value?.ToString(); + + if (string.IsNullOrEmpty(asString)) yield break; + + if (Udi.TryParse(asString, out var udi)) + yield return new UmbracoEntityReference(udi); + } + } } } diff --git a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs index 792552c5d7..862837381a 100644 --- a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; using Umbraco.Core; +using Umbraco.Core.Composing; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Editors; @@ -12,6 +13,7 @@ using Umbraco.Web.Templates; namespace Umbraco.Web.PropertyEditors { + /// /// Represents a grid property and parameter editor. /// @@ -25,18 +27,35 @@ namespace Umbraco.Web.PropertyEditors Group = Constants.PropertyEditors.Groups.RichContent)] public class GridPropertyEditor : DataEditor { - private IMediaService _mediaService; - private IContentTypeBaseServiceProvider _contentTypeBaseServiceProvider; private IUmbracoContextAccessor _umbracoContextAccessor; - private ILogger _logger; + private readonly HtmlImageSourceParser _imageSourceParser; + private readonly RichTextEditorPastedImages _pastedImages; + private readonly HtmlLocalLinkParser _localLinkParser; + private readonly IImageUrlGenerator _imageUrlGenerator; - public GridPropertyEditor(ILogger logger, IMediaService mediaService, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, IUmbracoContextAccessor umbracoContextAccessor) + [Obsolete("Use the constructor which takes an IImageUrlGenerator")] + public GridPropertyEditor(ILogger logger, + IUmbracoContextAccessor umbracoContextAccessor, + HtmlImageSourceParser imageSourceParser, + RichTextEditorPastedImages pastedImages, + HtmlLocalLinkParser localLinkParser) + : this(logger, umbracoContextAccessor, imageSourceParser, pastedImages, localLinkParser, Current.ImageUrlGenerator) + { + } + + public GridPropertyEditor(ILogger logger, + IUmbracoContextAccessor umbracoContextAccessor, + HtmlImageSourceParser imageSourceParser, + RichTextEditorPastedImages pastedImages, + HtmlLocalLinkParser localLinkParser, + IImageUrlGenerator imageUrlGenerator) : base(logger) { - _mediaService = mediaService; - _contentTypeBaseServiceProvider = contentTypeBaseServiceProvider; _umbracoContextAccessor = umbracoContextAccessor; - _logger = logger; + _imageSourceParser = imageSourceParser; + _pastedImages = pastedImages; + _localLinkParser = localLinkParser; + _imageUrlGenerator = imageUrlGenerator; } public override IPropertyIndexValueFactory PropertyIndexValueFactory => new GridPropertyIndexValueFactory(); @@ -45,24 +64,43 @@ namespace Umbraco.Web.PropertyEditors /// Overridden to ensure that the value is validated /// /// - protected override IDataValueEditor CreateValueEditor() => new GridPropertyValueEditor(Attribute, _mediaService, _contentTypeBaseServiceProvider, _umbracoContextAccessor, _logger); + protected override IDataValueEditor CreateValueEditor() => new GridPropertyValueEditor(Attribute, _umbracoContextAccessor, _imageSourceParser, _pastedImages, _localLinkParser, _imageUrlGenerator); protected override IConfigurationEditor CreateConfigurationEditor() => new GridConfigurationEditor(); - internal class GridPropertyValueEditor : DataValueEditor + internal class GridPropertyValueEditor : DataValueEditor, IDataValueReference { - private IMediaService _mediaService; - private IContentTypeBaseServiceProvider _contentTypeBaseServiceProvider; - private IUmbracoContextAccessor _umbracoContextAccessor; - private ILogger _logger; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private readonly HtmlImageSourceParser _imageSourceParser; + private readonly RichTextEditorPastedImages _pastedImages; + private readonly RichTextPropertyEditor.RichTextPropertyValueEditor _richTextPropertyValueEditor; + private readonly MediaPickerPropertyEditor.MediaPickerPropertyValueEditor _mediaPickerPropertyValueEditor; + private readonly IImageUrlGenerator _imageUrlGenerator; - public GridPropertyValueEditor(DataEditorAttribute attribute, IMediaService mediaService, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, IUmbracoContextAccessor umbracoContextAccessor, ILogger logger) + [Obsolete("Use the constructor which takes an IImageUrlGenerator")] + public GridPropertyValueEditor(DataEditorAttribute attribute, + IUmbracoContextAccessor umbracoContextAccessor, + HtmlImageSourceParser imageSourceParser, + RichTextEditorPastedImages pastedImages, + HtmlLocalLinkParser localLinkParser) + : this(attribute, umbracoContextAccessor, imageSourceParser, pastedImages, localLinkParser, Current.ImageUrlGenerator) + { + } + + public GridPropertyValueEditor(DataEditorAttribute attribute, + IUmbracoContextAccessor umbracoContextAccessor, + HtmlImageSourceParser imageSourceParser, + RichTextEditorPastedImages pastedImages, + HtmlLocalLinkParser localLinkParser, + IImageUrlGenerator imageUrlGenerator) : base(attribute) { - _mediaService = mediaService; - _contentTypeBaseServiceProvider = contentTypeBaseServiceProvider; _umbracoContextAccessor = umbracoContextAccessor; - _logger = logger; + _imageSourceParser = imageSourceParser; + _pastedImages = pastedImages; + _richTextPropertyValueEditor = new RichTextPropertyEditor.RichTextPropertyValueEditor(attribute, umbracoContextAccessor, imageSourceParser, localLinkParser, pastedImages, _imageUrlGenerator); + _mediaPickerPropertyValueEditor = new MediaPickerPropertyEditor.MediaPickerPropertyValueEditor(attribute); + _imageUrlGenerator = imageUrlGenerator; } /// @@ -87,7 +125,7 @@ namespace Umbraco.Web.PropertyEditors var mediaParent = config?.MediaParentId; var mediaParentId = mediaParent == null ? Guid.Empty : mediaParent.Guid; - var grid = DeserializeGridValue(rawJson, out var rtes); + var grid = DeserializeGridValue(rawJson, out var rtes, out _); var userId = _umbracoContextAccessor.UmbracoContext?.Security?.CurrentUser?.Id ?? Constants.Security.SuperUserId; @@ -97,8 +135,8 @@ namespace Umbraco.Web.PropertyEditors // Parse the HTML var html = rte.Value?.ToString(); - var parseAndSavedTempImages = TemplateUtilities.FindAndPersistPastedTempImages(html, mediaParentId, userId, _mediaService, _contentTypeBaseServiceProvider, _logger); - var editorValueWithMediaUrlsRemoved = TemplateUtilities.RemoveMediaUrlsFromTextString(parseAndSavedTempImages); + var parseAndSavedTempImages = _pastedImages.FindAndPersistPastedTempImages(html, mediaParentId, userId, _imageUrlGenerator); + var editorValueWithMediaUrlsRemoved = _imageSourceParser.RemoveImageSources(parseAndSavedTempImages); rte.Value = editorValueWithMediaUrlsRemoved; } @@ -120,30 +158,54 @@ namespace Umbraco.Web.PropertyEditors var val = property.GetValue(culture, segment)?.ToString(); if (val.IsNullOrWhiteSpace()) return string.Empty; - var grid = DeserializeGridValue(val, out var rtes); + var grid = DeserializeGridValue(val, out var rtes, out _); //process the rte values foreach (var rte in rtes.ToList()) { var html = rte.Value?.ToString(); - var propertyValueWithMediaResolved = TemplateUtilities.ResolveMediaFromTextString(html); + var propertyValueWithMediaResolved = _imageSourceParser.EnsureImageSources(html); rte.Value = propertyValueWithMediaResolved; } return grid; } - private GridValue DeserializeGridValue(string rawJson, out IEnumerable richTextValues) + private GridValue DeserializeGridValue(string rawJson, out IEnumerable richTextValues, out IEnumerable mediaValues) { var grid = JsonConvert.DeserializeObject(rawJson); // Find all controls that use the RTE editor - var controls = grid.Sections.SelectMany(x => x.Rows.SelectMany(r => r.Areas).SelectMany(a => a.Controls)); + var controls = grid.Sections.SelectMany(x => x.Rows.SelectMany(r => r.Areas).SelectMany(a => a.Controls)).ToArray(); richTextValues = controls.Where(x => x.Editor.Alias.ToLowerInvariant() == "rte"); + mediaValues = controls.Where(x => x.Editor.Alias.ToLowerInvariant() == "media"); return grid; } + + /// + /// Resolve references from values + /// + /// + /// + public IEnumerable GetReferences(object value) + { + var rawJson = value == null ? string.Empty : value is string str ? str : value.ToString(); + + if (rawJson.IsNullOrWhiteSpace()) + yield break; + + DeserializeGridValue(rawJson, out var richTextEditorValues, out var mediaValues); + + foreach (var umbracoEntityReference in richTextEditorValues.SelectMany(x => + _richTextPropertyValueEditor.GetReferences(x.Value))) + yield return umbracoEntityReference; + + foreach (var umbracoEntityReference in mediaValues.SelectMany(x => + _mediaPickerPropertyValueEditor.GetReferences(x.Value["udi"]))) + yield return umbracoEntityReference; + } } } } diff --git a/src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs index dd755ee0ba..fa8060bd15 100644 --- a/src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs @@ -1,5 +1,7 @@ -using Umbraco.Core; +using System.Collections.Generic; +using Umbraco.Core; using Umbraco.Core.Logging; +using Umbraco.Core.Models.Editors; using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors @@ -17,14 +19,40 @@ namespace Umbraco.Web.PropertyEditors Icon = Constants.Icons.MediaImage)] public class MediaPickerPropertyEditor : DataEditor { + /// /// Initializes a new instance of the class. /// public MediaPickerPropertyEditor(ILogger logger) : base(logger) - { } + { + } /// protected override IConfigurationEditor CreateConfigurationEditor() => new MediaPickerConfigurationEditor(); + + protected override IDataValueEditor CreateValueEditor() => new MediaPickerPropertyValueEditor(Attribute); + + internal class MediaPickerPropertyValueEditor : DataValueEditor, IDataValueReference + { + public MediaPickerPropertyValueEditor(DataEditorAttribute attribute) : base(attribute) + { + } + + public IEnumerable GetReferences(object value) + { + var asString = value is string str ? str : value?.ToString(); + + if (string.IsNullOrEmpty(asString)) yield break; + + foreach (var udiStr in asString.Split(',')) + { + if (Udi.TryParse(udiStr, out var udi)) + yield return new UmbracoEntityReference(udi); + } + } + } } + + } diff --git a/src/Umbraco.Web/PropertyEditors/MultiNodeTreePickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MultiNodeTreePickerPropertyEditor.cs index 742acbeca2..fd7f735e68 100644 --- a/src/Umbraco.Web/PropertyEditors/MultiNodeTreePickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MultiNodeTreePickerPropertyEditor.cs @@ -1,5 +1,7 @@ -using Umbraco.Core; +using System.Collections.Generic; +using Umbraco.Core; using Umbraco.Core.Logging; +using Umbraco.Core.Models.Editors; using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors @@ -18,5 +20,27 @@ namespace Umbraco.Web.PropertyEditors { } protected override IConfigurationEditor CreateConfigurationEditor() => new MultiNodePickerConfigurationEditor(); + + protected override IDataValueEditor CreateValueEditor() => new MultiNodeTreePickerPropertyValueEditor(Attribute); + + public class MultiNodeTreePickerPropertyValueEditor : DataValueEditor, IDataValueReference + { + public MultiNodeTreePickerPropertyValueEditor(DataEditorAttribute attribute): base(attribute) + { + + } + + public IEnumerable GetReferences(object value) + { + var asString = value == null ? string.Empty : value is string str ? str : value.ToString(); + + var udiPaths = asString.Split(','); + foreach (var udiPath in udiPaths) + if (Udi.TryParse(udiPath, out var udi)) + yield return new UmbracoEntityReference(udi); + } + } } + + } diff --git a/src/Umbraco.Web/PropertyEditors/MultiUrlPickerConfiguration.cs b/src/Umbraco.Web/PropertyEditors/MultiUrlPickerConfiguration.cs index 16aff6e0bf..239569478f 100644 --- a/src/Umbraco.Web/PropertyEditors/MultiUrlPickerConfiguration.cs +++ b/src/Umbraco.Web/PropertyEditors/MultiUrlPickerConfiguration.cs @@ -16,6 +16,9 @@ namespace Umbraco.Web.PropertyEditors Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")] public bool IgnoreUserStartNodes { get; set; } - + [ConfigurationField("hideAnchor", + "Hide anchor/query string input", "boolean", + Description = "Selecting this hides the anchor/query string input field in the linkpicker overlay.")] + public bool HideAnchor { get; set; } } } diff --git a/src/Umbraco.Web/PropertyEditors/MultiUrlPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MultiUrlPickerPropertyEditor.cs index 95ac809576..8af2d98018 100644 --- a/src/Umbraco.Web/PropertyEditors/MultiUrlPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MultiUrlPickerPropertyEditor.cs @@ -4,6 +4,9 @@ using Umbraco.Core.PropertyEditors; using Umbraco.Core.Logging; using Umbraco.Core.Services; using Umbraco.Web.PublishedCache; +using System.Collections.Generic; +using Umbraco.Core.Models.Editors; +using Newtonsoft.Json; namespace Umbraco.Web.PropertyEditors { @@ -25,7 +28,7 @@ namespace Umbraco.Web.PropertyEditors _entityService = entityService ?? throw new ArgumentNullException(nameof(entityService)); _publishedSnapshotAccessor = publishedSnapshotAccessor ?? throw new ArgumentNullException(nameof(publishedSnapshotAccessor)); } - + protected override IConfigurationEditor CreateConfigurationEditor() => new MultiUrlPickerConfigurationEditor(); protected override IDataValueEditor CreateValueEditor() => new MultiUrlPickerValueEditor(_entityService, _publishedSnapshotAccessor, Logger, Attribute); diff --git a/src/Umbraco.Web/PropertyEditors/MultiUrlPickerValueEditor.cs b/src/Umbraco.Web/PropertyEditors/MultiUrlPickerValueEditor.cs index aa8fa73c7a..853e995ed8 100644 --- a/src/Umbraco.Web/PropertyEditors/MultiUrlPickerValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MultiUrlPickerValueEditor.cs @@ -15,7 +15,7 @@ using Umbraco.Web.PublishedCache; namespace Umbraco.Web.PropertyEditors { - public class MultiUrlPickerValueEditor : DataValueEditor + public class MultiUrlPickerValueEditor : DataValueEditor, IDataValueReference { private readonly IEntityService _entityService; private readonly ILogger _logger; @@ -174,5 +174,22 @@ namespace Umbraco.Web.PropertyEditors [DataMember(Name = "queryString")] public string QueryString { get; set; } } + + public IEnumerable GetReferences(object value) + { + var asString = value == null ? string.Empty : value is string str ? str : value.ToString(); + + if (string.IsNullOrEmpty(asString)) yield break; + + var links = JsonConvert.DeserializeObject>(asString); + foreach (var link in links) + { + if (link.Udi != null) // Links can be absolute links without a Udi + { + yield return new UmbracoEntityReference(link.Udi); + } + + } + } } } diff --git a/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs index 4bf8ccb2c9..564630c574 100644 --- a/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; @@ -28,13 +29,16 @@ namespace Umbraco.Web.PropertyEditors public class NestedContentPropertyEditor : DataEditor { private readonly Lazy _propertyEditors; - + private readonly IDataTypeService _dataTypeService; + private readonly IContentTypeService _contentTypeService; internal const string ContentTypeAliasPropertyKey = "ncContentTypeAlias"; - public NestedContentPropertyEditor(ILogger logger, Lazy propertyEditors) + public NestedContentPropertyEditor(ILogger logger, Lazy propertyEditors, IDataTypeService dataTypeService, IContentTypeService contentTypeService) : base (logger) { _propertyEditors = propertyEditors; + _dataTypeService = dataTypeService; + _contentTypeService = contentTypeService; } // has to be lazy else circular dep in ctor @@ -48,32 +52,23 @@ namespace Umbraco.Web.PropertyEditors #region Value Editor - protected override IDataValueEditor CreateValueEditor() => new NestedContentPropertyValueEditor(Attribute, PropertyEditors); + protected override IDataValueEditor CreateValueEditor() => new NestedContentPropertyValueEditor(Attribute, PropertyEditors, _dataTypeService, _contentTypeService); - internal class NestedContentPropertyValueEditor : DataValueEditor + internal class NestedContentPropertyValueEditor : DataValueEditor, IDataValueReference { private readonly PropertyEditorCollection _propertyEditors; - - private readonly Lazy> _contentTypes = new Lazy>(() => - Current.Services.ContentTypeService.GetAll().ToDictionary(c => c.Alias) - ); - - public NestedContentPropertyValueEditor(DataEditorAttribute attribute, PropertyEditorCollection propertyEditors) + private readonly IDataTypeService _dataTypeService; + private readonly NestedContentValues _nestedContentValues; + + public NestedContentPropertyValueEditor(DataEditorAttribute attribute, PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, IContentTypeService contentTypeService) : base(attribute) { _propertyEditors = propertyEditors; - Validators.Add(new NestedContentValidator(propertyEditors, GetElementType)); + _dataTypeService = dataTypeService; + _nestedContentValues = new NestedContentValues(contentTypeService); + Validators.Add(new NestedContentValidator(propertyEditors, dataTypeService, _nestedContentValues)); } - - private IContentType GetElementType(JObject item) - { - var contentTypeAlias = item[ContentTypeAliasPropertyKey]?.ToObject() ?? string.Empty; - return _contentTypes.Value.ContainsKey(contentTypeAlias) ? _contentTypes.Value[contentTypeAlias] : null; - } - - internal ServiceContext Services => Current.Services; - /// public override object Configuration { @@ -94,60 +89,47 @@ namespace Umbraco.Web.PropertyEditors public override string ConvertDbToString(PropertyType propertyType, object propertyValue, IDataTypeService dataTypeService) { - if (propertyValue == null || string.IsNullOrWhiteSpace(propertyValue.ToString())) + var vals = _nestedContentValues.GetPropertyValues(propertyValue, out var deserialized).ToList(); + + if (vals.Count == 0) return string.Empty; - var value = JsonConvert.DeserializeObject>(propertyValue.ToString()); - if (value == null) - return string.Empty; - - foreach (var o in value) + foreach (var row in vals) { - var propValues = (JObject) o; - - var contentType = GetElementType(propValues); - if (contentType == null) - continue; - - var propAliases = propValues.Properties().Select(x => x.Name).ToArray(); - foreach (var propAlias in propAliases) + if (row.PropType == null) { - var propType = contentType.CompositionPropertyTypes.FirstOrDefault(x => x.Alias == propAlias); - if (propType == null) + // type not found, and property is not system: just delete the value + if (IsSystemPropertyKey(row.PropKey) == false) + row.JsonRowValue[row.PropKey] = null; + } + else + { + try { - // type not found, and property is not system: just delete the value - if (IsSystemPropertyKey(propAlias) == false) - propValues[propAlias] = null; + // convert the value, and store the converted value + var propEditor = _propertyEditors[row.PropType.PropertyEditorAlias]; + if (propEditor == null) continue; + + var tempConfig = dataTypeService.GetDataType(row.PropType.DataTypeId).Configuration; + var valEditor = propEditor.GetValueEditor(tempConfig); + var convValue = valEditor.ConvertDbToString(row.PropType, row.JsonRowValue[row.PropKey]?.ToString(), dataTypeService); + row.JsonRowValue[row.PropKey] = convValue; } - else + catch (InvalidOperationException) { - try - { - // convert the value, and store the converted value - var propEditor = _propertyEditors[propType.PropertyEditorAlias]; - if (propEditor == null) - { - continue; - } - var tempConfig = dataTypeService.GetDataType(propType.DataTypeId).Configuration; - var valEditor = propEditor.GetValueEditor(tempConfig); - var convValue = valEditor.ConvertDbToString(propType, propValues[propAlias]?.ToString(), dataTypeService); - propValues[propAlias] = convValue; - } - catch (InvalidOperationException) - { - // deal with weird situations by ignoring them (no comment) - propValues[propAlias] = null; - } + // deal with weird situations by ignoring them (no comment) + row.JsonRowValue[row.PropKey] = null; } } } - return JsonConvert.SerializeObject(value).ToXmlString(); + return JsonConvert.SerializeObject(deserialized).ToXmlString(); } #endregion + + #region Convert database // editor // note: there is NO variant support here @@ -155,65 +137,53 @@ namespace Umbraco.Web.PropertyEditors public override object ToEditor(Property property, IDataTypeService dataTypeService, string culture = null, string segment = null) { var val = property.GetValue(culture, segment); - if (val == null || string.IsNullOrWhiteSpace(val.ToString())) + + var vals = _nestedContentValues.GetPropertyValues(val, out var deserialized).ToList(); + + if (vals.Count == 0) return string.Empty; - var value = JsonConvert.DeserializeObject>(val.ToString()); - if (value == null) - return string.Empty; - - foreach (var o in value) + foreach (var row in vals) { - var propValues = (JObject) o; - - var contentType = GetElementType(propValues); - if (contentType == null) - continue; - - var propAliases = propValues.Properties().Select(x => x.Name).ToArray(); - foreach (var propAlias in propAliases) + if (row.PropType == null) { - var propType = contentType.CompositionPropertyTypes.FirstOrDefault(x => x.Alias == propAlias); - if (propType == null) + // type not found, and property is not system: just delete the value + if (IsSystemPropertyKey(row.PropKey) == false) + row.JsonRowValue[row.PropKey] = null; + } + else + { + try { - // type not found, and property is not system: just delete the value - if (IsSystemPropertyKey(propAlias) == false) - propValues[propAlias] = null; + // create a temp property with the value + // - force it to be culture invariant as NC can't handle culture variant element properties + row.PropType.Variations = ContentVariation.Nothing; + var tempProp = new Property(row.PropType); + tempProp.SetValue(row.JsonRowValue[row.PropKey] == null ? null : row.JsonRowValue[row.PropKey].ToString()); + + // convert that temp property, and store the converted value + var propEditor = _propertyEditors[row.PropType.PropertyEditorAlias]; + if (propEditor == null) + { + row.JsonRowValue[row.PropKey] = tempProp.GetValue()?.ToString(); + continue; + } + + var tempConfig = dataTypeService.GetDataType(row.PropType.DataTypeId).Configuration; + var valEditor = propEditor.GetValueEditor(tempConfig); + var convValue = valEditor.ToEditor(tempProp, dataTypeService); + row.JsonRowValue[row.PropKey] = convValue == null ? null : JToken.FromObject(convValue); } - else + catch (InvalidOperationException) { - try - { - // create a temp property with the value - // - force it to be culture invariant as NC can't handle culture variant element properties - propType.Variations = ContentVariation.Nothing; - var tempProp = new Property(propType); - tempProp.SetValue(propValues[propAlias] == null ? null : propValues[propAlias].ToString()); - - // convert that temp property, and store the converted value - var propEditor = _propertyEditors[propType.PropertyEditorAlias]; - if(propEditor == null) - { - propValues[propAlias] = tempProp.GetValue()?.ToString(); - continue; - } - var tempConfig = dataTypeService.GetDataType(propType.DataTypeId).Configuration; - var valEditor = propEditor.GetValueEditor(tempConfig); - var convValue = valEditor.ToEditor(tempProp, dataTypeService); - propValues[propAlias] = convValue == null ? null : JToken.FromObject(convValue); - } - catch (InvalidOperationException) - { - // deal with weird situations by ignoring them (no comment) - propValues[propAlias] = null; - } + // deal with weird situations by ignoring them (no comment) + row.JsonRowValue[row.PropKey] = null; } - } } // return json - return value; + return deserialized; } public override object FromEditor(ContentPropertyData editorValue, object currentValue) @@ -221,158 +191,229 @@ namespace Umbraco.Web.PropertyEditors if (editorValue.Value == null || string.IsNullOrWhiteSpace(editorValue.Value.ToString())) return null; - var value = JsonConvert.DeserializeObject>(editorValue.Value.ToString()); - if (value == null) - return null; + var vals = _nestedContentValues.GetPropertyValues(editorValue.Value, out var deserialized).ToList(); - // Issue #38 - Keep recursive property lookups working - if (!value.Any()) - return null; + if (vals.Count == 0) + return string.Empty; - // Process value - for (var i = 0; i < value.Count; i++) + foreach (var row in vals) { - var o = value[i]; - var propValues = ((JObject)o); - - var contentType = GetElementType(propValues); - if (contentType == null) + if (row.PropType == null) { - continue; + // type not found, and property is not system: just delete the value + if (IsSystemPropertyKey(row.PropKey) == false) + row.JsonRowValue[row.PropKey] = null; } - - var propValueKeys = propValues.Properties().Select(x => x.Name).ToArray(); - - foreach (var propKey in propValueKeys) + else { - var propType = contentType.CompositionPropertyTypes.FirstOrDefault(x => x.Alias == propKey); - if (propType == null) - { - if (IsSystemPropertyKey(propKey) == false) - { - // Property missing so just delete the value - propValues[propKey] = null; - } - } - else - { - // Fetch the property types prevalue - var propConfiguration = Services.DataTypeService.GetDataType(propType.DataTypeId).Configuration; + // Fetch the property types prevalue + var propConfiguration = _dataTypeService.GetDataType(row.PropType.DataTypeId).Configuration; - // Lookup the property editor - var propEditor = _propertyEditors[propType.PropertyEditorAlias]; - if (propEditor == null) - { - continue; - } + // Lookup the property editor + var propEditor = _propertyEditors[row.PropType.PropertyEditorAlias]; + if (propEditor == null) continue; - // Create a fake content property data object - var contentPropData = new ContentPropertyData(propValues[propKey], propConfiguration); + // Create a fake content property data object + var contentPropData = new ContentPropertyData(row.JsonRowValue[row.PropKey], propConfiguration); - // Get the property editor to do it's conversion - var newValue = propEditor.GetValueEditor().FromEditor(contentPropData, propValues[propKey]); - - // Store the value back - propValues[propKey] = (newValue == null) ? null : JToken.FromObject(newValue); - } + // Get the property editor to do it's conversion + var newValue = propEditor.GetValueEditor().FromEditor(contentPropData, row.JsonRowValue[row.PropKey]); + // Store the value back + row.JsonRowValue[row.PropKey] = (newValue == null) ? null : JToken.FromObject(newValue); } } - return JsonConvert.SerializeObject(value); + // return json + return JsonConvert.SerializeObject(deserialized); } - #endregion + + public IEnumerable GetReferences(object value) + { + var rawJson = value == null ? string.Empty : value is string str ? str : value.ToString(); + + var result = new List(); + + foreach (var row in _nestedContentValues.GetPropertyValues(rawJson, out _)) + { + if (row.PropType == null) continue; + + var propEditor = _propertyEditors[row.PropType.PropertyEditorAlias]; + + var valueEditor = propEditor?.GetValueEditor(); + if (!(valueEditor is IDataValueReference reference)) continue; + + var val = row.JsonRowValue[row.PropKey]?.ToString(); + + var refs = reference.GetReferences(val); + + result.AddRange(refs); + } + + return result; + } } internal class NestedContentValidator : IValueValidator { private readonly PropertyEditorCollection _propertyEditors; - private readonly Func _getElementType; + private readonly IDataTypeService _dataTypeService; + private readonly NestedContentValues _nestedContentValues; - public NestedContentValidator(PropertyEditorCollection propertyEditors, Func getElementType) + public NestedContentValidator(PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, NestedContentValues nestedContentValues) { _propertyEditors = propertyEditors; - _getElementType = getElementType; + _dataTypeService = dataTypeService; + _nestedContentValues = nestedContentValues; } public IEnumerable Validate(object rawValue, string valueType, object dataTypeConfiguration) { - if (rawValue == null) - yield break; + var validationResults = new List(); - var value = JsonConvert.DeserializeObject>(rawValue.ToString()); - if (value == null) - yield break; - - var dataTypeService = Current.Services.DataTypeService; - for (var i = 0; i < value.Count; i++) + foreach(var row in _nestedContentValues.GetPropertyValues(rawValue, out _)) { - var o = value[i]; - var propValues = (JObject) o; + if (row.PropType == null) continue; - var contentType = _getElementType(propValues); - if (contentType == null) continue; + var config = _dataTypeService.GetDataType(row.PropType.DataTypeId).Configuration; + var propertyEditor = _propertyEditors[row.PropType.PropertyEditorAlias]; + if (propertyEditor == null) continue; - var propValueKeys = propValues.Properties().Select(x => x.Name).ToArray(); - - foreach (var propKey in propValueKeys) + foreach (var validator in propertyEditor.GetValueEditor().Validators) { - var propType = contentType.CompositionPropertyTypes.FirstOrDefault(x => x.Alias == propKey); - if (propType != null) + foreach (var result in validator.Validate(row.JsonRowValue[row.PropKey], propertyEditor.GetValueEditor().ValueType, config)) { - var config = dataTypeService.GetDataType(propType.DataTypeId).Configuration; - var propertyEditor = _propertyEditors[propType.PropertyEditorAlias]; + result.ErrorMessage = "Item " + (row.RowIndex + 1) + " '" + row.PropType.Name + "' " + result.ErrorMessage; + validationResults.Add(result); + } + } - if (propertyEditor == null) - { - continue; - } + // Check mandatory + if (row.PropType.Mandatory) + { + if (row.JsonRowValue[row.PropKey] == null) + { + var message = string.IsNullOrWhiteSpace(row.PropType.MandatoryMessage) + ? $"'{row.PropType.Name}' cannot be null" + : row.PropType.MandatoryMessage; + validationResults.Add(new ValidationResult($"Item {(row.RowIndex + 1)}: {message}", new[] { row.PropKey })); + } + else if (row.JsonRowValue[row.PropKey].ToString().IsNullOrWhiteSpace() || (row.JsonRowValue[row.PropKey].Type == JTokenType.Array && !row.JsonRowValue[row.PropKey].HasValues)) + { + var message = string.IsNullOrWhiteSpace(row.PropType.MandatoryMessage) + ? $"'{row.PropType.Name}' cannot be empty" + : row.PropType.MandatoryMessage; + validationResults.Add(new ValidationResult($"Item {(row.RowIndex + 1)}: {message}", new[] { row.PropKey })); + } + } - foreach (var validator in propertyEditor.GetValueEditor().Validators) - { - foreach (var result in validator.Validate(propValues[propKey], propertyEditor.GetValueEditor().ValueType, config)) - { - result.ErrorMessage = "Item " + (i + 1) + " '" + propType.Name + "' " + result.ErrorMessage; - yield return result; - } - } - - // Check mandatory - if (propType.Mandatory) - { - if (propValues[propKey] == null) - { - var message = string.IsNullOrWhiteSpace(propType.MandatoryMessage) - ? $"'{propType.Name}' cannot be null" - : propType.MandatoryMessage; - yield return new ValidationResult($"Item {(i + 1)}: {message}", new[] { propKey }); - } - else if (propValues[propKey].ToString().IsNullOrWhiteSpace() || (propValues[propKey].Type == JTokenType.Array && !propValues[propKey].HasValues)) - { - var message = string.IsNullOrWhiteSpace(propType.MandatoryMessage) - ? $"'{propType.Name}' cannot be empty" - : propType.MandatoryMessage; - yield return new ValidationResult($"Item {(i + 1)}: {message}", new[] { propKey }); - } - } - - // Check regex - if (!propType.ValidationRegExp.IsNullOrWhiteSpace() - && propValues[propKey] != null && !propValues[propKey].ToString().IsNullOrWhiteSpace()) - { - var regex = new Regex(propType.ValidationRegExp); - if (!regex.IsMatch(propValues[propKey].ToString())) - { - var message = string.IsNullOrWhiteSpace(propType.ValidationRegExpMessage) - ? $"'{propType.Name}' is invalid, it does not match the correct pattern" - : propType.ValidationRegExpMessage; - yield return new ValidationResult($"Item {(i + 1)}: {message}", new[] { propKey }); - } - } + // Check regex + if (!row.PropType.ValidationRegExp.IsNullOrWhiteSpace() + && row.JsonRowValue[row.PropKey] != null && !row.JsonRowValue[row.PropKey].ToString().IsNullOrWhiteSpace()) + { + var regex = new Regex(row.PropType.ValidationRegExp); + if (!regex.IsMatch(row.JsonRowValue[row.PropKey].ToString())) + { + var message = string.IsNullOrWhiteSpace(row.PropType.ValidationRegExpMessage) + ? $"'{row.PropType.Name}' is invalid, it does not match the correct pattern" + : row.PropType.ValidationRegExpMessage; + validationResults.Add(new ValidationResult($"Item {(row.RowIndex + 1)}: {message}", new[] { row.PropKey })); } } } + + return validationResults; + } + } + + internal class NestedContentValues + { + private readonly Lazy> _contentTypes; + + public NestedContentValues(IContentTypeService contentTypeService) + { + _contentTypes = new Lazy>(() => contentTypeService.GetAll().ToDictionary(c => c.Alias)); + } + + private IContentType GetElementType(JObject item) + { + var contentTypeAlias = item[ContentTypeAliasPropertyKey]?.ToObject() ?? string.Empty; + _contentTypes.Value.TryGetValue(contentTypeAlias, out var contentType); + return contentType; + } + + public IEnumerable GetPropertyValues(object propertyValue, out List deserialized) + { + var rowValues = new List(); + + deserialized = null; + + if (propertyValue == null || string.IsNullOrWhiteSpace(propertyValue.ToString())) + return Enumerable.Empty(); + + deserialized = JsonConvert.DeserializeObject>(propertyValue.ToString()); + + // There was a note here about checking if the result had zero items and if so it would return null, so we'll continue to do that + // The original note was: "Issue #38 - Keep recursive property lookups working" + // Which is from the original NC tracker: https://github.com/umco/umbraco-nested-content/issues/38 + // This check should be used everywhere when iterating NC prop values, instead of just the one previous place so that + // empty values don't get persisted when there is nothing, it should actually be null. + if (deserialized == null || deserialized.Count == 0) + return Enumerable.Empty(); + + var index = 0; + + foreach (var o in deserialized) + { + var propValues = o; + + var contentType = GetElementType(propValues); + if (contentType == null) + continue; + + var propertyTypes = contentType.CompositionPropertyTypes.ToDictionary(x => x.Alias, x => x); + var propAliases = propValues.Properties().Select(x => x.Name); + foreach (var propAlias in propAliases) + { + propertyTypes.TryGetValue(propAlias, out var propType); + rowValues.Add(new RowValue(propAlias, propType, propValues, index)); + } + index++; + } + + return rowValues; + } + + internal class RowValue + { + public RowValue(string propKey, PropertyType propType, JObject propValues, int index) + { + PropKey = propKey ?? throw new ArgumentNullException(nameof(propKey)); + PropType = propType; + JsonRowValue = propValues ?? throw new ArgumentNullException(nameof(propValues)); + RowIndex = index; + } + + /// + /// The current property key being iterated for the row value + /// + public string PropKey { get; } + + /// + /// The of the value (if any), this may be null + /// + public PropertyType PropType { get; } + + /// + /// The json values for the current row + /// + public JObject JsonRowValue { get; } + + /// + /// The Nested Content row index + /// + public int RowIndex { get; } } } diff --git a/src/Umbraco.Web/PropertyEditors/RichTextEditorPastedImages.cs b/src/Umbraco.Web/PropertyEditors/RichTextEditorPastedImages.cs new file mode 100644 index 0000000000..67ff82a3e1 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/RichTextEditorPastedImages.cs @@ -0,0 +1,143 @@ +using HtmlAgilityPack; +using System; +using System.Collections.Generic; +using System.IO; +using Umbraco.Core; +using Umbraco.Core.Exceptions; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Services; +using Umbraco.Web.Templates; + +namespace Umbraco.Web.PropertyEditors +{ + public sealed class RichTextEditorPastedImages + { + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private readonly ILogger _logger; + private readonly IMediaService _mediaService; + private readonly IContentTypeBaseServiceProvider _contentTypeBaseServiceProvider; + + const string TemporaryImageDataAttribute = "data-tmpimg"; + + public RichTextEditorPastedImages(IUmbracoContextAccessor umbracoContextAccessor, ILogger logger, IMediaService mediaService, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider) + { + _umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _mediaService = mediaService ?? throw new ArgumentNullException(nameof(mediaService)); + _contentTypeBaseServiceProvider = contentTypeBaseServiceProvider ?? throw new ArgumentNullException(nameof(contentTypeBaseServiceProvider)); + } + + /// + /// Used by the RTE (and grid RTE) for drag/drop/persisting images + /// + /// + /// + /// + /// + internal string FindAndPersistPastedTempImages(string html, Guid mediaParentFolder, int userId, IImageUrlGenerator imageUrlGenerator) + { + // Find all img's that has data-tmpimg attribute + // Use HTML Agility Pack - https://html-agility-pack.net + var htmlDoc = new HtmlDocument(); + htmlDoc.LoadHtml(html); + + var tmpImages = htmlDoc.DocumentNode.SelectNodes($"//img[@{TemporaryImageDataAttribute}]"); + if (tmpImages == null || tmpImages.Count == 0) + return html; + + // An array to contain a list of URLs that + // we have already processed to avoid dupes + var uploadedImages = new Dictionary(); + + foreach (var img in tmpImages) + { + // The data attribute contains the path to the tmp img to persist as a media item + var tmpImgPath = img.GetAttributeValue(TemporaryImageDataAttribute, string.Empty); + + if (string.IsNullOrEmpty(tmpImgPath)) + continue; + + var absoluteTempImagePath = IOHelper.MapPath(tmpImgPath); + var fileName = Path.GetFileName(absoluteTempImagePath); + var safeFileName = fileName.ToSafeFileName(); + + var mediaItemName = safeFileName.ToFriendlyName(); + IMedia mediaFile; + GuidUdi udi; + + if (uploadedImages.ContainsKey(tmpImgPath) == false) + { + if (mediaParentFolder == Guid.Empty) + mediaFile = _mediaService.CreateMedia(mediaItemName, Constants.System.Root, Constants.Conventions.MediaTypes.Image, userId); + else + mediaFile = _mediaService.CreateMedia(mediaItemName, mediaParentFolder, Constants.Conventions.MediaTypes.Image, userId); + + var fileInfo = new FileInfo(absoluteTempImagePath); + + var fileStream = fileInfo.OpenReadWithRetry(); + if (fileStream == null) throw new InvalidOperationException("Could not acquire file stream"); + using (fileStream) + { + mediaFile.SetValue(_contentTypeBaseServiceProvider, Constants.Conventions.Media.File, safeFileName, fileStream); + } + + _mediaService.Save(mediaFile, userId); + + udi = mediaFile.GetUdi(); + } + else + { + // Already been uploaded & we have it's UDI + udi = uploadedImages[tmpImgPath]; + } + + // Add the UDI to the img element as new data attribute + img.SetAttributeValue("data-udi", udi.ToString()); + + // Get the new persisted image url + var mediaTyped = _umbracoContextAccessor?.UmbracoContext?.Media.GetById(udi.Guid); + if (mediaTyped == null) + throw new PanicException($"Could not find media by id {udi.Guid} or there was no UmbracoContext available."); + + var location = mediaTyped.Url(); + + // Find the width & height attributes as we need to set the imageprocessor QueryString + var width = img.GetAttributeValue("width", int.MinValue); + var height = img.GetAttributeValue("height", int.MinValue); + + if (width != int.MinValue && height != int.MinValue) + { + location = imageUrlGenerator.GetImageUrl(new ImageUrlGenerationOptions(location) { ImageCropMode = "max", Width = width, Height = height }); + } + + img.SetAttributeValue("src", location); + + // Remove the data attribute (so we do not re-process this) + img.Attributes.Remove(TemporaryImageDataAttribute); + + // Add to the dictionary to avoid dupes + if (uploadedImages.ContainsKey(tmpImgPath) == false) + { + uploadedImages.Add(tmpImgPath, udi); + + // Delete folder & image now its saved in media + // The folder should contain one image - as a unique guid folder created + // for each image uploaded from TinyMceController + var folderName = Path.GetDirectoryName(absoluteTempImagePath); + try + { + Directory.Delete(folderName, true); + } + catch (Exception ex) + { + _logger.Error(typeof(HtmlImageSourceParser), ex, "Could not delete temp file or folder {FileName}", absoluteTempImagePath); + } + } + } + + return htmlDoc.DocumentNode.OuterHtml; + } + } +} diff --git a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs index 5743e9c1d5..42777f11ad 100644 --- a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs @@ -1,8 +1,11 @@ using System; using System.Collections.Generic; +using System.Linq; using Umbraco.Core; +using Umbraco.Core.Composing; using Umbraco.Core.Logging; using Umbraco.Core.Models; +using Umbraco.Core.Models.Editors; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; using Umbraco.Examine; @@ -24,27 +27,40 @@ namespace Umbraco.Web.PropertyEditors Icon = "icon-browser-window")] public class RichTextPropertyEditor : DataEditor { - private IMediaService _mediaService; - private IContentTypeBaseServiceProvider _contentTypeBaseServiceProvider; private IUmbracoContextAccessor _umbracoContextAccessor; - private ILogger _logger; + private readonly HtmlImageSourceParser _imageSourceParser; + private readonly HtmlLocalLinkParser _localLinkParser; + private readonly RichTextEditorPastedImages _pastedImages; + private readonly IImageUrlGenerator _imageUrlGenerator; + /// /// The constructor will setup the property editor based on the attribute if one is found /// - public RichTextPropertyEditor(ILogger logger, IMediaService mediaService, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, IUmbracoContextAccessor umbracoContextAccessor) : base(logger) + [Obsolete("Use the constructor which takes an IImageUrlGenerator")] + public RichTextPropertyEditor(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, HtmlImageSourceParser imageSourceParser, HtmlLocalLinkParser localLinkParser, RichTextEditorPastedImages pastedImages) + : this(logger, umbracoContextAccessor, imageSourceParser, localLinkParser, pastedImages, Current.ImageUrlGenerator) + { + } + + /// + /// The constructor will setup the property editor based on the attribute if one is found + /// + public RichTextPropertyEditor(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, HtmlImageSourceParser imageSourceParser, HtmlLocalLinkParser localLinkParser, RichTextEditorPastedImages pastedImages, IImageUrlGenerator imageUrlGenerator) + : base(logger) { - _mediaService = mediaService; - _contentTypeBaseServiceProvider = contentTypeBaseServiceProvider; _umbracoContextAccessor = umbracoContextAccessor; - _logger = logger; + _imageSourceParser = imageSourceParser; + _localLinkParser = localLinkParser; + _pastedImages = pastedImages; + _imageUrlGenerator = imageUrlGenerator; } /// /// Create a custom value editor /// /// - protected override IDataValueEditor CreateValueEditor() => new RichTextPropertyValueEditor(Attribute, _mediaService, _contentTypeBaseServiceProvider, _umbracoContextAccessor, _logger); + protected override IDataValueEditor CreateValueEditor() => new RichTextPropertyValueEditor(Attribute, _umbracoContextAccessor, _imageSourceParser, _localLinkParser, _pastedImages, _imageUrlGenerator); protected override IConfigurationEditor CreateConfigurationEditor() => new RichTextConfigurationEditor(); @@ -53,20 +69,22 @@ namespace Umbraco.Web.PropertyEditors /// /// A custom value editor to ensure that macro syntax is parsed when being persisted and formatted correctly for display in the editor /// - internal class RichTextPropertyValueEditor : DataValueEditor + internal class RichTextPropertyValueEditor : DataValueEditor, IDataValueReference { - private IMediaService _mediaService; - private IContentTypeBaseServiceProvider _contentTypeBaseServiceProvider; private IUmbracoContextAccessor _umbracoContextAccessor; - private ILogger _logger; + private readonly HtmlImageSourceParser _imageSourceParser; + private readonly HtmlLocalLinkParser _localLinkParser; + private readonly RichTextEditorPastedImages _pastedImages; + private readonly IImageUrlGenerator _imageUrlGenerator; - public RichTextPropertyValueEditor(DataEditorAttribute attribute, IMediaService mediaService, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, IUmbracoContextAccessor umbracoContextAccessor, ILogger logger) + public RichTextPropertyValueEditor(DataEditorAttribute attribute, IUmbracoContextAccessor umbracoContextAccessor, HtmlImageSourceParser imageSourceParser, HtmlLocalLinkParser localLinkParser, RichTextEditorPastedImages pastedImages, IImageUrlGenerator imageUrlGenerator) : base(attribute) { - _mediaService = mediaService; - _contentTypeBaseServiceProvider = contentTypeBaseServiceProvider; _umbracoContextAccessor = umbracoContextAccessor; - _logger = logger; + _imageSourceParser = imageSourceParser; + _localLinkParser = localLinkParser; + _pastedImages = pastedImages; + _imageUrlGenerator = imageUrlGenerator; } /// @@ -98,7 +116,7 @@ namespace Umbraco.Web.PropertyEditors if (val == null) return null; - var propertyValueWithMediaResolved = TemplateUtilities.ResolveMediaFromTextString(val.ToString()); + var propertyValueWithMediaResolved = _imageSourceParser.EnsureImageSources(val.ToString()); var parsed = MacroTagParser.FormatRichTextPersistedDataForEditor(propertyValueWithMediaResolved, new Dictionary()); return parsed; } @@ -120,12 +138,30 @@ namespace Umbraco.Web.PropertyEditors var mediaParent = config?.MediaParentId; var mediaParentId = mediaParent == null ? Guid.Empty : mediaParent.Guid; - var parseAndSavedTempImages = TemplateUtilities.FindAndPersistPastedTempImages(editorValue.Value.ToString(), mediaParentId, userId, _mediaService, _contentTypeBaseServiceProvider, _logger); - var editorValueWithMediaUrlsRemoved = TemplateUtilities.RemoveMediaUrlsFromTextString(parseAndSavedTempImages); + var parseAndSavedTempImages = _pastedImages.FindAndPersistPastedTempImages(editorValue.Value.ToString(), mediaParentId, userId, _imageUrlGenerator); + var editorValueWithMediaUrlsRemoved = _imageSourceParser.RemoveImageSources(parseAndSavedTempImages); var parsed = MacroTagParser.FormatRichTextContentForPersistence(editorValueWithMediaUrlsRemoved); return parsed; } + + /// + /// Resolve references from values + /// + /// + /// + public IEnumerable GetReferences(object value) + { + var asString = value == null ? string.Empty : value is string str ? str : value.ToString(); + + foreach (var udi in _imageSourceParser.FindUdisFromDataAttributes(asString)) + yield return new UmbracoEntityReference(udi); + + foreach (var udi in _localLinkParser.FindUdisFromLocalLinks(asString)) + yield return new UmbracoEntityReference(udi); + + //TODO: Detect Macros too ... but we can save that for a later date, right now need to do media refs + } } internal class RichTextPropertyIndexValueFactory : IPropertyIndexValueFactory diff --git a/src/Umbraco.Web/PropertyEditors/UploadFileTypeValidator.cs b/src/Umbraco.Web/PropertyEditors/UploadFileTypeValidator.cs index 6855ab3bb8..8d7bd29889 100644 --- a/src/Umbraco.Web/PropertyEditors/UploadFileTypeValidator.cs +++ b/src/Umbraco.Web/PropertyEditors/UploadFileTypeValidator.cs @@ -48,7 +48,7 @@ namespace Umbraco.Web.PropertyEditors internal static bool IsValidFileExtension(string fileName) { if (fileName.IndexOf('.') <= 0) return false; - var extension = new FileInfo(fileName).Extension.TrimStart("."); + var extension = fileName.GetFileExtension().TrimStart("."); return Current.Configs.Settings().Content.IsFileAllowedForUpload(extension); } } diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs index e11f3e0d3a..5c027ed9b6 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs @@ -12,11 +12,26 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters [DefaultPropertyValueConverter] public class MarkdownEditorValueConverter : PropertyValueConverterBase { + private readonly HtmlLocalLinkParser _localLinkParser; + private readonly HtmlUrlParser _urlParser; + + [Obsolete("Use ctor defining all dependencies instead")] + public MarkdownEditorValueConverter() + : this(Current.Factory.GetInstance(), Current.Factory.GetInstance()) + { + } + + public MarkdownEditorValueConverter(HtmlLocalLinkParser localLinkParser, HtmlUrlParser urlParser) + { + _localLinkParser = localLinkParser; + _urlParser = urlParser; + } + public override bool IsConverter(IPublishedPropertyType propertyType) => Constants.PropertyEditors.Aliases.MarkdownEditor == propertyType.EditorAlias; public override Type GetPropertyValueType(IPublishedPropertyType propertyType) - => typeof (IHtmlString); + => typeof(IHtmlString); public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Snapshot; @@ -27,8 +42,8 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters var sourceString = source.ToString(); // ensures string is parsed for {localLink} and urls are resolved correctly - sourceString = TemplateUtilities.ParseInternalLinks(sourceString, preview, Current.UmbracoContext); - sourceString = TemplateUtilities.ResolveUrlsFromTextString(sourceString); + sourceString = _localLinkParser.EnsureInternalLinks(sourceString, preview); + sourceString = _urlParser.EnsureUrls(sourceString); return sourceString; } diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentManyValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentManyValueConverter.cs index 559777786f..4a25049695 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentManyValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentManyValueConverter.cs @@ -6,6 +6,7 @@ using Newtonsoft.Json.Linq; using Umbraco.Core.Logging; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.PropertyEditors.ValueConverters; using Umbraco.Web.PublishedCache; namespace Umbraco.Web.PropertyEditors.ValueConverters @@ -14,6 +15,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters /// /// Provides an implementation for for nested content. /// + [DefaultPropertyValueConverter(typeof(JsonValueConverter))] public class NestedContentManyValueConverter : NestedContentValueConverterBase { private readonly IProfilingLogger _proflog; diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentSingleValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentSingleValueConverter.cs index 06aa0b42fb..c9c99615f6 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentSingleValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentSingleValueConverter.cs @@ -5,6 +5,7 @@ using Newtonsoft.Json.Linq; using Umbraco.Core.Logging; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.PropertyEditors.ValueConverters; using Umbraco.Web.PublishedCache; namespace Umbraco.Web.PropertyEditors.ValueConverters @@ -13,6 +14,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters /// /// Provides an implementation for for nested content. /// + [DefaultPropertyValueConverter(typeof(JsonValueConverter))] public class NestedContentSingleValueConverter : NestedContentValueConverterBase { private readonly IProfilingLogger _proflog; diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs index d5e1f841ea..3ab502742c 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs @@ -24,6 +24,9 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters { private readonly IUmbracoContextAccessor _umbracoContextAccessor; private readonly IMacroRenderer _macroRenderer; + private readonly HtmlLocalLinkParser _linkParser; + private readonly HtmlUrlParser _urlParser; + private readonly HtmlImageSourceParser _imageSourceParser; public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) { @@ -32,10 +35,14 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters return PropertyCacheLevel.Snapshot; } - public RteMacroRenderingValueConverter(IUmbracoContextAccessor umbracoContextAccessor, IMacroRenderer macroRenderer) + public RteMacroRenderingValueConverter(IUmbracoContextAccessor umbracoContextAccessor, IMacroRenderer macroRenderer, + HtmlLocalLinkParser linkParser, HtmlUrlParser urlParser, HtmlImageSourceParser imageSourceParser) { _umbracoContextAccessor = umbracoContextAccessor; _macroRenderer = macroRenderer; + _linkParser = linkParser; + _urlParser = urlParser; + _imageSourceParser = imageSourceParser; } // NOT thread-safe over a request because it modifies the @@ -81,9 +88,9 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters var sourceString = source.ToString(); // ensures string is parsed for {localLink} and urls and media are resolved correctly - sourceString = TemplateUtilities.ParseInternalLinks(sourceString, preview, Current.UmbracoContext); - sourceString = TemplateUtilities.ResolveUrlsFromTextString(sourceString); - sourceString = TemplateUtilities.ResolveMediaFromTextString(sourceString); + sourceString = _linkParser.EnsureInternalLinks(sourceString, preview); + sourceString = _urlParser.EnsureUrls(sourceString); + sourceString = _imageSourceParser.EnsureImageSources(sourceString); // ensure string is parsed for macros and macros are executed correctly sourceString = RenderRteMacros(sourceString, preview); diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/TextStringValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/TextStringValueConverter.cs index b8ad1477b4..939a658407 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/TextStringValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/TextStringValueConverter.cs @@ -11,11 +11,19 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters [DefaultPropertyValueConverter] public class TextStringValueConverter : PropertyValueConverterBase { + public TextStringValueConverter(HtmlLocalLinkParser linkParser, HtmlUrlParser urlParser) + { + _linkParser = linkParser; + _urlParser = urlParser; + } + private static readonly string[] PropertyTypeAliases = { Constants.PropertyEditors.Aliases.TextBox, Constants.PropertyEditors.Aliases.TextArea }; + private readonly HtmlLocalLinkParser _linkParser; + private readonly HtmlUrlParser _urlParser; public override bool IsConverter(IPublishedPropertyType propertyType) => PropertyTypeAliases.Contains(propertyType.EditorAlias); @@ -32,8 +40,8 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters var sourceString = source.ToString(); // ensures string is parsed for {localLink} and urls are resolved correctly - sourceString = TemplateUtilities.ParseInternalLinks(sourceString, preview, Current.UmbracoContext); - sourceString = TemplateUtilities.ResolveUrlsFromTextString(sourceString); + sourceString = _linkParser.EnsureInternalLinks(sourceString, preview); + sourceString = _urlParser.EnsureUrls(sourceString); return sourceString; } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs index 84edb9113c..24c6a7018b 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs @@ -355,6 +355,7 @@ namespace Umbraco.Web.PublishedCache.NuCache private static IEnumerable GetByXPath(XPathNodeIterator iterator) { + iterator = iterator.Clone(); while (iterator.MoveNext()) { var xnav = iterator.Current as NavigableNavigator; diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentNode.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentNode.cs index 3b4859432d..a724e78b72 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentNode.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentNode.cs @@ -109,6 +109,8 @@ namespace Umbraco.Web.PublishedCache.NuCache // everything that is common to both draft and published versions // keep this as small as possible + + public readonly int Id; public readonly Guid Uid; public IPublishedContentType ContentType; @@ -116,10 +118,14 @@ namespace Umbraco.Web.PublishedCache.NuCache public readonly string Path; public readonly int SortOrder; public readonly int ParentContentId; + + // TODO: Can we make everything readonly?? This would make it easier to debug and be less error prone especially for new developers. + // Once a Node is created and exists in the cache it is readonly so we should be able to make that happen at the API level too. public int FirstChildContentId; public int LastChildContentId; public int NextSiblingContentId; public int PreviousSiblingContentId; + public readonly DateTime CreateDate; public readonly int CreatorId; diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs index 550fd507d5..a3f918c92c 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs @@ -14,12 +14,24 @@ using Umbraco.Web.PublishedCache.NuCache.Snap; namespace Umbraco.Web.PublishedCache.NuCache { - // stores content + /// + /// Stores content in memory and persists it back to disk + /// + /// + /// + /// Methods in this class suffixed with the term "Locked" means that those methods can only be called within a WriteLock. A WriteLock + /// is acquired by the GetScopedWriteLock method. Locks are not allowed to be recursive. + /// + /// + /// This class's logic is based on the class but has been slightly modified to suit these purposes. + /// + /// internal class ContentStore { // this class is an extended version of SnapDictionary // most of the snapshots management code, etc is an exact copy // SnapDictionary has unit tests to ensure it all works correctly + // For locking information, see SnapDictionary private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; private readonly IVariationContextAccessor _variationContextAccessor; @@ -38,7 +50,6 @@ namespace Umbraco.Web.PublishedCache.NuCache private long _liveGen, _floorGen; private bool _nextGen, _collectAuto; private Task _collectTask; - private volatile int _wlocked; private List> _wchanges; // TODO: collection trigger (ok for now) @@ -79,15 +90,9 @@ namespace Umbraco.Web.PublishedCache.NuCache private readonly string _instanceId = Guid.NewGuid().ToString("N"); - private class ReadLockInfo - { - public bool Taken; - } - private class WriteLockInfo { public bool Taken; - public bool Count; } // a scope contextual that represents a locked writer to the dictionary @@ -118,22 +123,26 @@ namespace Umbraco.Web.PublishedCache.NuCache return ScopeContextualBase.Get(scopeProvider, _instanceId, scoped => new ScopedWriteLock(this, scoped)); } + private void EnsureLocked() + { + if (!Monitor.IsEntered(_wlocko)) + throw new InvalidOperationException("Write lock must be acquried."); + } + private void Lock(WriteLockInfo lockInfo, bool forceGen = false) { + if (Monitor.IsEntered(_wlocko)) + throw new InvalidOperationException("Recursive locks not allowed"); + Monitor.Enter(_wlocko, ref lockInfo.Taken); - var rtaken = false; - try + lock(_rlocko) { - Monitor.Enter(_rlocko, ref rtaken); - // see SnapDictionary try { } finally { - _wlocked++; - lockInfo.Count = true; - if (_nextGen == false || (forceGen && _wlocked == 1)) + if (_nextGen == false || (forceGen)) { // because we are changing things, a new generation // is created, which will trigger a new snapshot @@ -143,63 +152,49 @@ namespace Umbraco.Web.PublishedCache.NuCache _nextGen = true; } } - } - finally - { - if (rtaken) Monitor.Exit(_rlocko); - } - } - - private void Lock(ReadLockInfo lockInfo) - { - Monitor.Enter(_rlocko, ref lockInfo.Taken); + } } private void Release(WriteLockInfo lockInfo, bool commit = true) { - if (commit == false) + try { - var rtaken = false; - try + if (commit == false) { - Monitor.Enter(_rlocko, ref rtaken); - try { } - finally + lock (_rlocko) { - _nextGen = false; - _liveGen -= 1; + // see SnapDictionary + try { } + finally + { + _nextGen = false; + _liveGen -= 1; + } } - } - finally - { - if (rtaken) Monitor.Exit(_rlocko); - } - Rollback(_contentNodes); - RollbackRoot(); - Rollback(_contentTypesById); - Rollback(_contentTypesByAlias); + Rollback(_contentNodes); + RollbackRoot(); + Rollback(_contentTypesById); + Rollback(_contentTypesByAlias); + } + else if (_localDb != null && _wchanges != null) + { + foreach (var change in _wchanges) + { + if (change.Value.IsNull) + _localDb.TryRemove(change.Key, out ContentNodeKit unused); + else + _localDb[change.Key] = change.Value; + } + _wchanges = null; + _localDb.Commit(); + } } - else if (_localDb != null && _wchanges != null) + finally { - foreach (var change in _wchanges) - { - if (change.Value.IsNull) - _localDb.TryRemove(change.Key, out ContentNodeKit unused); - else - _localDb[change.Key] = change.Value; - } - _wchanges = null; - _localDb.Commit(); + if (lockInfo.Taken) + Monitor.Exit(_wlocko); } - - if (lockInfo.Count) _wlocked--; - if (lockInfo.Taken) Monitor.Exit(_wlocko); - } - - private void Release(ReadLockInfo lockInfo) - { - if (lockInfo.Taken) Monitor.Exit(_rlocko); } private void RollbackRoot() @@ -237,7 +232,7 @@ namespace Umbraco.Web.PublishedCache.NuCache { try { - // Trying to lock could throw exceptions so always make sure to clean up. + // Trying to lock could throw exceptions so always make sure to clean up. Lock(lockInfo); } finally @@ -246,7 +241,11 @@ namespace Umbraco.Web.PublishedCache.NuCache { _localDb?.Dispose(); } - catch { /* TBD: May already be throwing so don't throw again */} + catch (Exception ex) + { + /* TBD: May already be throwing so don't throw again */ + _logger.Error(ex, "Error trying to release DB"); + } finally { _localDb = null; @@ -254,6 +253,11 @@ namespace Umbraco.Web.PublishedCache.NuCache } } + catch (Exception ex) + { + _logger.Error(ex, "Error trying to lock"); + throw; + } finally { Release(lockInfo); @@ -270,87 +274,111 @@ namespace Umbraco.Web.PublishedCache.NuCache #region Content types - public void NewContentTypes(IEnumerable types) + /// + /// Sets data for new content types + /// + /// + /// + /// This methods MUST be called from within a write lock, normally wrapped within GetScopedWriteLock + /// otherwise an exception will occur. + /// + /// + /// Thrown if this method is not called within a write lock + /// + public void NewContentTypesLocked(IEnumerable types) { - var lockInfo = new WriteLockInfo(); - try - { - Lock(lockInfo); + EnsureLocked(); - foreach (var type in types) - { - SetValueLocked(_contentTypesById, type.Id, type); - SetValueLocked(_contentTypesByAlias, type.Alias, type); - } - } - finally + foreach (var type in types) { - Release(lockInfo); + SetValueLocked(_contentTypesById, type.Id, type); + SetValueLocked(_contentTypesByAlias, type.Alias, type); } } - public void UpdateContentTypes(IEnumerable types) + /// + /// Sets data for updated content types + /// + /// + /// + /// This methods MUST be called from within a write lock, normally wrapped within GetScopedWriteLock + /// otherwise an exception will occur. + /// + /// + /// Thrown if this method is not called within a write lock + /// + public void UpdateContentTypesLocked(IEnumerable types) { //nothing to do if this is empty, no need to lock/allocate/iterate/etc... if (!types.Any()) return; - var lockInfo = new WriteLockInfo(); - try + EnsureLocked(); + + var index = types.ToDictionary(x => x.Id, x => x); + + foreach (var type in index.Values) { - Lock(lockInfo); - - var index = types.ToDictionary(x => x.Id, x => x); - - foreach (var type in index.Values) - { - SetValueLocked(_contentTypesById, type.Id, type); - SetValueLocked(_contentTypesByAlias, type.Alias, type); - } - - foreach (var link in _contentNodes.Values) - { - var node = link.Value; - if (node == null) continue; - var contentTypeId = node.ContentType.Id; - if (index.TryGetValue(contentTypeId, out var contentType) == false) continue; - SetValueLocked(_contentNodes, node.Id, new ContentNode(node, contentType)); - } + SetValueLocked(_contentTypesById, type.Id, type); + SetValueLocked(_contentTypesByAlias, type.Alias, type); } - finally + + foreach (var link in _contentNodes.Values) { - Release(lockInfo); + var node = link.Value; + if (node == null) continue; + var contentTypeId = node.ContentType.Id; + if (index.TryGetValue(contentTypeId, out var contentType) == false) continue; + SetValueLocked(_contentNodes, node.Id, new ContentNode(node, contentType)); } } - public void SetAllContentTypes(IEnumerable types) + /// + /// Updates/sets data for all content types + /// + /// + /// + /// This methods MUST be called from within a write lock, normally wrapped within GetScopedWriteLock + /// otherwise an exception will occur. + /// + /// + /// Thrown if this method is not called within a write lock + /// + public void SetAllContentTypesLocked(IEnumerable types) { - var lockInfo = new WriteLockInfo(); - try + EnsureLocked(); + + // clear all existing content types + ClearLocked(_contentTypesById); + ClearLocked(_contentTypesByAlias); + + // set all new content types + foreach (var type in types) { - Lock(lockInfo); - - // clear all existing content types - ClearLocked(_contentTypesById); - ClearLocked(_contentTypesByAlias); - - // set all new content types - foreach (var type in types) - { - SetValueLocked(_contentTypesById, type.Id, type); - SetValueLocked(_contentTypesByAlias, type.Alias, type); - } - - // beware! at that point the cache is inconsistent, - // assuming we are going to SetAll content items! - } - finally - { - Release(lockInfo); + SetValueLocked(_contentTypesById, type.Id, type); + SetValueLocked(_contentTypesByAlias, type.Alias, type); } + + // beware! at that point the cache is inconsistent, + // assuming we are going to SetAll content items! } - public void UpdateContentTypes(IReadOnlyCollection removedIds, IReadOnlyCollection refreshedTypes, IReadOnlyCollection kits) + /// + /// Updates/sets/removes data for content types + /// + /// + /// + /// + /// + /// This methods MUST be called from within a write lock, normally wrapped within GetScopedWriteLock + /// otherwise an exception will occur. + /// + /// + /// Thrown if this method is not called within a write lock + /// + public void UpdateContentTypesLocked(IReadOnlyCollection removedIds, IReadOnlyCollection refreshedTypes, IReadOnlyCollection kits) { + EnsureLocked(); + var removedIdsA = removedIds ?? Array.Empty(); var refreshedTypesA = refreshedTypes ?? Array.Empty(); var refreshedIdsA = refreshedTypesA.Select(x => x.Id).ToList(); @@ -359,85 +387,84 @@ namespace Umbraco.Web.PublishedCache.NuCache if (kits.Count == 0 && refreshedIdsA.Count == 0 && removedIdsA.Count == 0) return; //exit - there is nothing to do here - var lockInfo = new WriteLockInfo(); - try + var removedContentTypeNodes = new List(); + var refreshedContentTypeNodes = new List(); + + // find all the nodes that are either refreshed or removed, + // because of their content type being either refreshed or removed + foreach (var link in _contentNodes.Values) { - Lock(lockInfo); - - var removedContentTypeNodes = new List(); - var refreshedContentTypeNodes = new List(); - - // find all the nodes that are either refreshed or removed, - // because of their content type being either refreshed or removed - foreach (var link in _contentNodes.Values) - { - var node = link.Value; - if (node == null) continue; - var contentTypeId = node.ContentType.Id; - if (removedIdsA.Contains(contentTypeId)) removedContentTypeNodes.Add(node.Id); - if (refreshedIdsA.Contains(contentTypeId)) refreshedContentTypeNodes.Add(node.Id); - } - - // perform deletion of content with removed content type - // removing content types should have removed their content already - // but just to be 100% sure, clear again here - foreach (var node in removedContentTypeNodes) - ClearBranchLocked(node); - - // perform deletion of removed content types - foreach (var id in removedIdsA) - { - if (_contentTypesById.TryGetValue(id, out var link) == false || link.Value == null) - continue; - SetValueLocked(_contentTypesById, id, null); - SetValueLocked(_contentTypesByAlias, link.Value.Alias, null); - } - - // perform update of refreshed content types - foreach (var type in refreshedTypesA) - { - SetValueLocked(_contentTypesById, type.Id, type); - SetValueLocked(_contentTypesByAlias, type.Alias, type); - } - - // perform update of content with refreshed content type - from the kits - // skip missing type, skip missing parents & un-buildable kits - what else could we do? - // kits are ordered by level, so ParentExists is ok here - var visited = new List(); - foreach (var kit in kits.Where(x => - refreshedIdsA.Contains(x.ContentTypeId) && - BuildKit(x, out _))) - { - // replacing the node: must preserve the parents - var node = GetHead(_contentNodes, kit.Node.Id)?.Value; - if (node != null) - kit.Node.FirstChildContentId = node.FirstChildContentId; - - SetValueLocked(_contentNodes, kit.Node.Id, kit.Node); - - visited.Add(kit.Node.Id); - if (_localDb != null) RegisterChange(kit.Node.Id, kit); - } - - // all content should have been refreshed - but... - var orphans = refreshedContentTypeNodes.Except(visited); - foreach (var id in orphans) - ClearBranchLocked(id); + var node = link.Value; + if (node == null) continue; + var contentTypeId = node.ContentType.Id; + if (removedIdsA.Contains(contentTypeId)) removedContentTypeNodes.Add(node.Id); + if (refreshedIdsA.Contains(contentTypeId)) refreshedContentTypeNodes.Add(node.Id); } - finally + + // perform deletion of content with removed content type + // removing content types should have removed their content already + // but just to be 100% sure, clear again here + foreach (var node in removedContentTypeNodes) + ClearBranchLocked(node); + + // perform deletion of removed content types + foreach (var id in removedIdsA) { - Release(lockInfo); + if (_contentTypesById.TryGetValue(id, out var link) == false || link.Value == null) + continue; + SetValueLocked(_contentTypesById, id, null); + SetValueLocked(_contentTypesByAlias, link.Value.Alias, null); } + + // perform update of refreshed content types + foreach (var type in refreshedTypesA) + { + SetValueLocked(_contentTypesById, type.Id, type); + SetValueLocked(_contentTypesByAlias, type.Alias, type); + } + + // perform update of content with refreshed content type - from the kits + // skip missing type, skip missing parents & un-buildable kits - what else could we do? + // kits are ordered by level, so ParentExists is ok here + var visited = new List(); + foreach (var kit in kits.Where(x => + refreshedIdsA.Contains(x.ContentTypeId) && + BuildKit(x, out _))) + { + // replacing the node: must preserve the parents + var node = GetHead(_contentNodes, kit.Node.Id)?.Value; + if (node != null) + kit.Node.FirstChildContentId = node.FirstChildContentId; + + SetValueLocked(_contentNodes, kit.Node.Id, kit.Node); + + visited.Add(kit.Node.Id); + if (_localDb != null) RegisterChange(kit.Node.Id, kit); + } + + // all content should have been refreshed - but... + var orphans = refreshedContentTypeNodes.Except(visited); + foreach (var id in orphans) + ClearBranchLocked(id); } - public void UpdateDataTypes(IEnumerable dataTypeIds, Func getContentType) + /// + /// Updates data types + /// + /// + /// + /// + /// This methods MUST be called from within a write lock, normally wrapped within GetScopedWriteLock + /// otherwise an exception will occur. + /// + /// + /// Thrown if this method is not called within a write lock + /// + public void UpdateDataTypesLocked(IEnumerable dataTypeIds, Func getContentType) { - var lockInfo = new WriteLockInfo(); - try - { - Lock(lockInfo); + EnsureLocked(); - var contentTypes = _contentTypesById + var contentTypes = _contentTypesById .Where(kvp => kvp.Value.Value != null && kvp.Value.Value.PropertyTypes.Any(p => dataTypeIds.Contains(p.DataType.Id))) @@ -446,40 +473,43 @@ namespace Umbraco.Web.PublishedCache.NuCache .Where(x => x != null) // poof, gone, very unlikely and probably an anomaly .ToArray(); - var contentTypeIdsA = contentTypes.Select(x => x.Id).ToArray(); - var contentTypeNodes = new Dictionary>(); - foreach (var id in contentTypeIdsA) - contentTypeNodes[id] = new List(); - foreach (var link in _contentNodes.Values) - { - var node = link.Value; - if (node != null && contentTypeIdsA.Contains(node.ContentType.Id)) - contentTypeNodes[node.ContentType.Id].Add(node.Id); - } - - foreach (var contentType in contentTypes) - { - // again, weird situation - if (contentTypeNodes.ContainsKey(contentType.Id) == false) - continue; - - foreach (var id in contentTypeNodes[contentType.Id]) - { - _contentNodes.TryGetValue(id, out var link); - if (link?.Value == null) - continue; - var node = new ContentNode(link.Value, contentType); - SetValueLocked(_contentNodes, id, node); - if (_localDb != null) RegisterChange(id, node.ToKit()); - } - } - } - finally + var contentTypeIdsA = contentTypes.Select(x => x.Id).ToArray(); + var contentTypeNodes = new Dictionary>(); + foreach (var id in contentTypeIdsA) + contentTypeNodes[id] = new List(); + foreach (var link in _contentNodes.Values) { - Release(lockInfo); + var node = link.Value; + if (node != null && contentTypeIdsA.Contains(node.ContentType.Id)) + contentTypeNodes[node.ContentType.Id].Add(node.Id); + } + + foreach (var contentType in contentTypes) + { + // again, weird situation + if (contentTypeNodes.ContainsKey(contentType.Id) == false) + continue; + + foreach (var id in contentTypeNodes[contentType.Id]) + { + _contentNodes.TryGetValue(id, out var link); + if (link?.Value == null) + continue; + var node = new ContentNode(link.Value, contentType); + SetValueLocked(_contentNodes, id, node); + if (_localDb != null) RegisterChange(id, node.ToKit()); + } } } + /// + /// Validate the and try to create a parent + /// + /// + /// + /// + /// Returns false if the parent was not found or if the kit validation failed + /// private bool BuildKit(ContentNodeKit kit, out LinkedNode parent) { // make sure parent exists @@ -490,6 +520,15 @@ namespace Umbraco.Web.PublishedCache.NuCache return false; } + // We cannot continue if there's no value. This shouldn't happen but it can happen if the database umbracoNode.path + // data is invalid/corrupt. If that is the case, the parentId might be ok but not the Path which can result in null + // because the data sort operation is by path. + if (parent.Value == null) + { + _logger.Warn($"Skip item id={kit.Node.Id}, no Data assigned for linked node with path {kit.Node.Path} and parent id {kit.Node.ParentContentId}. This can indicate data corruption for the Path value for node {kit.Node.Id}. See the Health Check dashboard in Settings to resolve data integrity issues."); + return false; + } + // make sure the kit is valid if (kit.DraftData == null && kit.PublishedData == null) { @@ -534,8 +573,22 @@ namespace Umbraco.Web.PublishedCache.NuCache return link; } - public bool Set(ContentNodeKit kit) + /// + /// Sets the data for a + /// + /// + /// + /// + /// This methods MUST be called from within a write lock, normally wrapped within GetScopedWriteLock + /// otherwise an exception will occur. + /// + /// + /// Thrown if this method is not called within a write lock + /// + public bool SetLocked(ContentNodeKit kit) { + EnsureLocked(); + // ReSharper disable LocalizableElement if (kit.IsEmpty) throw new ArgumentException("Kit is empty.", nameof(kit)); @@ -545,64 +598,54 @@ namespace Umbraco.Web.PublishedCache.NuCache _logger.Debug("Set content ID: {KitNodeId}", kit.Node.Id); - var lockInfo = new WriteLockInfo(); - try + // get existing + _contentNodes.TryGetValue(kit.Node.Id, out var link); + var existing = link?.Value; + + if (!BuildKit(kit, out var parent)) + return false; + + // moving? + var moving = existing != null && existing.ParentContentId != kit.Node.ParentContentId; + + // manage children + if (existing != null) { - Lock(lockInfo); - - // get existing - _contentNodes.TryGetValue(kit.Node.Id, out var link); - var existing = link?.Value; - - if (!BuildKit(kit, out var parent)) - return false; - - // moving? - var moving = existing != null && existing.ParentContentId != kit.Node.ParentContentId; - - // manage children - if (existing != null) - { - kit.Node.FirstChildContentId = existing.FirstChildContentId; - kit.Node.LastChildContentId = existing.LastChildContentId; - } - - // set - SetValueLocked(_contentNodes, kit.Node.Id, kit.Node); - if (_localDb != null) RegisterChange(kit.Node.Id, kit); - - // manage the tree - if (existing == null) - { - // new, add to parent - AddTreeNodeLocked(kit.Node, parent); - } - else if (moving || existing.SortOrder != kit.Node.SortOrder) - { - // moved, remove existing from its parent, add content to its parent - RemoveTreeNodeLocked(existing); - AddTreeNodeLocked(kit.Node); - } - else - { - // replacing existing, handle siblings - kit.Node.NextSiblingContentId = existing.NextSiblingContentId; - kit.Node.PreviousSiblingContentId = existing.PreviousSiblingContentId; - } - - _xmap[kit.Node.Uid] = kit.Node.Id; + kit.Node.FirstChildContentId = existing.FirstChildContentId; + kit.Node.LastChildContentId = existing.LastChildContentId; } - finally + + // set + SetValueLocked(_contentNodes, kit.Node.Id, kit.Node); + if (_localDb != null) RegisterChange(kit.Node.Id, kit); + + // manage the tree + if (existing == null) { - Release(lockInfo); + // new, add to parent + AddTreeNodeLocked(kit.Node, parent); } + else if (moving || existing.SortOrder != kit.Node.SortOrder) + { + // moved, remove existing from its parent, add content to its parent + RemoveTreeNodeLocked(existing); + AddTreeNodeLocked(kit.Node); + } + else + { + // replacing existing, handle siblings + kit.Node.NextSiblingContentId = existing.NextSiblingContentId; + kit.Node.PreviousSiblingContentId = existing.PreviousSiblingContentId; + } + + _xmap[kit.Node.Uid] = kit.Node.Id; return true; } private void ClearRootLocked() { - if (_root.Gen < _liveGen) + if (_root.Gen != _liveGen) _root = new LinkedNode(new ContentNode(), _liveGen, _root); else _root.Value.FirstChildContentId = -1; @@ -617,195 +660,216 @@ namespace Umbraco.Web.PublishedCache.NuCache /// True if the data is coming from the database (not the local cache db) /// /// + /// /// This requires that the collection is sorted by Level + ParentId + Sort Order. /// This should be used only on a site startup as the first generations. /// This CANNOT be used after startup since it bypasses all checks for Generations. + /// + /// + /// This methods MUST be called from within a write lock, normally wrapped within GetScopedWriteLock + /// otherwise an exception will occur. + /// /// - internal bool SetAllFastSorted(IEnumerable kits, bool fromDb) + /// + /// Thrown if this method is not called within a write lock + /// + public bool SetAllFastSortedLocked(IEnumerable kits, bool fromDb) { - var lockInfo = new WriteLockInfo(); + EnsureLocked(); + var ok = true; - try + + ClearLocked(_contentNodes); + ClearRootLocked(); + + // The name of the game here is to populate each kit's + // FirstChildContentId + // LastChildContentId + // NextSiblingContentId + // PreviousSiblingContentId + + ContentNode previousNode = null; + ContentNode parent = null; + + foreach (var kit in kits) { - Lock(lockInfo); - - ClearLocked(_contentNodes); - ClearRootLocked(); - - // The name of the game here is to populate each kit's - // FirstChildContentId - // LastChildContentId - // NextSiblingContentId - // PreviousSiblingContentId - - ContentNode previousNode = null; - ContentNode parent = null; - - foreach (var kit in kits) + if (!BuildKit(kit, out var parentLink)) { - if (!BuildKit(kit, out var parentLink)) - { - ok = false; - continue; // skip that one - } - - var thisNode = kit.Node; - - if (parent == null) - { - // first parent - parent = parentLink.Value; - parent.FirstChildContentId = thisNode.Id; // this node is the first node - } - else if (parent.Id != parentLink.Value.Id) - { - // new parent - parent = parentLink.Value; - parent.FirstChildContentId = thisNode.Id; // this node is the first node - previousNode = null; // there is no previous sibling - } - - _logger.Debug($"Set {thisNode.Id} with parent {thisNode.ParentContentId}"); - SetValueLocked(_contentNodes, thisNode.Id, thisNode); - - // if we are initializing from the database source ensure the local db is updated - if (fromDb && _localDb != null) RegisterChange(thisNode.Id, kit); - - // this node is always the last child - parent.LastChildContentId = thisNode.Id; - - // wire previous node as previous sibling - if (previousNode != null) - { - previousNode.NextSiblingContentId = thisNode.Id; - thisNode.PreviousSiblingContentId = previousNode.Id; - } - - // this node becomes the previous node - previousNode = thisNode; - - _xmap[kit.Node.Uid] = kit.Node.Id; + ok = false; + continue; // skip that one } - } - finally - { - Release(lockInfo); + + var thisNode = kit.Node; + + if (parent == null) + { + // first parent + parent = parentLink.Value; + parent.FirstChildContentId = thisNode.Id; // this node is the first node + } + else if (parent.Id != parentLink.Value.Id) + { + // new parent + parent = parentLink.Value; + parent.FirstChildContentId = thisNode.Id; // this node is the first node + previousNode = null; // there is no previous sibling + } + + _logger.Debug($"Set {thisNode.Id} with parent {thisNode.ParentContentId}"); + SetValueLocked(_contentNodes, thisNode.Id, thisNode); + + // if we are initializing from the database source ensure the local db is updated + if (fromDb && _localDb != null) RegisterChange(thisNode.Id, kit); + + // this node is always the last child + parent.LastChildContentId = thisNode.Id; + + // wire previous node as previous sibling + if (previousNode != null) + { + previousNode.NextSiblingContentId = thisNode.Id; + thisNode.PreviousSiblingContentId = previousNode.Id; + } + + // this node becomes the previous node + previousNode = thisNode; + + _xmap[kit.Node.Uid] = kit.Node.Id; } return ok; } - public bool SetAll(IEnumerable kits) + /// + /// Set all data for a collection of + /// + /// + /// + /// + /// This methods MUST be called from within a write lock, normally wrapped within GetScopedWriteLock + /// otherwise an exception will occur. + /// + /// + /// Thrown if this method is not called within a write lock + /// + public bool SetAllLocked(IEnumerable kits) { - var lockInfo = new WriteLockInfo(); + EnsureLocked(); + var ok = true; - try + + ClearLocked(_contentNodes); + ClearRootLocked(); + + // do NOT clear types else they are gone! + //ClearLocked(_contentTypesById); + //ClearLocked(_contentTypesByAlias); + + foreach (var kit in kits) { - Lock(lockInfo); - - ClearLocked(_contentNodes); - ClearRootLocked(); - - // do NOT clear types else they are gone! - //ClearLocked(_contentTypesById); - //ClearLocked(_contentTypesByAlias); - - foreach (var kit in kits) + if (!BuildKit(kit, out var parent)) { - if (!BuildKit(kit, out var parent)) - { - ok = false; - continue; // skip that one - } - _logger.Debug($"Set {kit.Node.Id} with parent {kit.Node.ParentContentId}"); - SetValueLocked(_contentNodes, kit.Node.Id, kit.Node); - - if (_localDb != null) RegisterChange(kit.Node.Id, kit); - AddTreeNodeLocked(kit.Node, parent); - - _xmap[kit.Node.Uid] = kit.Node.Id; + ok = false; + continue; // skip that one } - } - finally - { - Release(lockInfo); + _logger.Debug($"Set {kit.Node.Id} with parent {kit.Node.ParentContentId}"); + SetValueLocked(_contentNodes, kit.Node.Id, kit.Node); + + if (_localDb != null) RegisterChange(kit.Node.Id, kit); + AddTreeNodeLocked(kit.Node, parent); + + _xmap[kit.Node.Uid] = kit.Node.Id; } return ok; } - // IMPORTANT kits must be sorted out by LEVEL and by SORT ORDER - public bool SetBranch(int rootContentId, IEnumerable kits) + /// + /// Sets data for a branch of + /// + /// + /// + /// + /// + /// + /// IMPORTANT kits must be sorted out by LEVEL and by SORT ORDER + /// + /// + /// This methods MUST be called from within a write lock, normally wrapped within GetScopedWriteLock + /// otherwise an exception will occur. + /// + /// + /// + /// Thrown if this method is not called within a write lock + /// + public bool SetBranchLocked(int rootContentId, IEnumerable kits) { - var lockInfo = new WriteLockInfo(); + EnsureLocked(); + var ok = true; - try + + // get existing + _contentNodes.TryGetValue(rootContentId, out var link); + var existing = link?.Value; + + // clear + if (existing != null) { - Lock(lockInfo); - - // get existing - _contentNodes.TryGetValue(rootContentId, out var link); - var existing = link?.Value; - - // clear - if (existing != null) - { - //this zero's out the branch (recursively), if we're in a new gen this will add a NULL placeholder for the gen - ClearBranchLocked(existing); - //TODO: This removes the current GEN from the tree - do we really want to do that? - RemoveTreeNodeLocked(existing); - } - - // now add them all back - foreach (var kit in kits) - { - if (!BuildKit(kit, out var parent)) - { - ok = false; - continue; // skip that one - } - SetValueLocked(_contentNodes, kit.Node.Id, kit.Node); - if (_localDb != null) RegisterChange(kit.Node.Id, kit); - AddTreeNodeLocked(kit.Node, parent); - - _xmap[kit.Node.Uid] = kit.Node.Id; - } + //this zero's out the branch (recursively), if we're in a new gen this will add a NULL placeholder for the gen + ClearBranchLocked(existing); + //TODO: This removes the current GEN from the tree - do we really want to do that? (not sure if this is still an issue....) + RemoveTreeNodeLocked(existing); } - finally + + // now add them all back + foreach (var kit in kits) { - Release(lockInfo); + if (!BuildKit(kit, out var parent)) + { + ok = false; + continue; // skip that one + } + SetValueLocked(_contentNodes, kit.Node.Id, kit.Node); + if (_localDb != null) RegisterChange(kit.Node.Id, kit); + AddTreeNodeLocked(kit.Node, parent); + + _xmap[kit.Node.Uid] = kit.Node.Id; } return ok; } - public bool Clear(int id) + /// + /// Clears data for a given node id + /// + /// + /// + /// + /// This methods MUST be called from within a write lock, normally wrapped within GetScopedWriteLock + /// otherwise an exception will occur. + /// + /// + /// Thrown if this method is not called within a write lock + /// + public bool ClearLocked(int id) { - var lockInfo = new WriteLockInfo(); - try - { - Lock(lockInfo); + EnsureLocked(); - // try to find the content - // if it is not there, nothing to do - _contentNodes.TryGetValue(id, out var link); // else null - if (link?.Value == null) return false; + // try to find the content + // if it is not there, nothing to do + _contentNodes.TryGetValue(id, out var link); // else null + if (link?.Value == null) return false; - var content = link.Value; - _logger.Debug("Clear content ID: {ContentId}", content.Id); + var content = link.Value; + _logger.Debug("Clear content ID: {ContentId}", content.Id); - // clear the entire branch - ClearBranchLocked(content); + // clear the entire branch + ClearBranchLocked(content); - // manage the tree - RemoveTreeNodeLocked(content); + // manage the tree + RemoveTreeNodeLocked(content); - return true; - } - finally - { - Release(lockInfo); - } + return true; } private void ClearBranchLocked(int id) @@ -818,6 +882,10 @@ namespace Umbraco.Web.PublishedCache.NuCache private void ClearBranchLocked(ContentNode content) { + // This should never be null, all code that calls this method is null checking but we've seen + // issues of null ref exceptions in issue reports so we'll double check here + if (content == null) throw new ArgumentNullException(nameof(content)); + SetValueLocked(_contentNodes, content.Id, null); if (_localDb != null) RegisterChange(content.Id, ContentNodeKit.Null); @@ -826,9 +894,11 @@ namespace Umbraco.Web.PublishedCache.NuCache var id = content.FirstChildContentId; while (id > 0) { + // get the required link node, this ensures that both `link` and `link.Value` are not null var link = GetRequiredLinkedNode(id, "child", null); - ClearBranchLocked(link.Value); - id = link.Value.NextSiblingContentId; + var linkValue = link.Value; // capture local since clearing in recurse can clear it + ClearBranchLocked(linkValue); // recurse + id = linkValue.NextSiblingContentId; } } @@ -899,8 +969,18 @@ namespace Umbraco.Web.PublishedCache.NuCache return link; } + /// + /// This removes this current node from the tree hiearchy by removing it from it's parent's linked list + /// + /// + /// + /// This is called within a lock which means a new Gen is being created therefore this will not modify any existing content in a Gen. + /// private void RemoveTreeNodeLocked(ContentNode content) { + // NOTE: DO NOT modify `content` here, this would modify data for an existing Gen, all modifications are done to clones + // which would be targeting the new Gen. + var parentLink = content.ParentContentId < 0 ? _root : GetRequiredLinkedNode(content.ParentContentId, "parent", null); @@ -937,9 +1017,6 @@ namespace Umbraco.Web.PublishedCache.NuCache var prev = GenCloneLocked(prevLink); prev.NextSiblingContentId = content.NextSiblingContentId; } - - content.NextSiblingContentId = -1; - content.PreviousSiblingContentId = -1; } private bool ParentPublishedLocked(ContentNodeKit kit) @@ -955,7 +1032,7 @@ namespace Umbraco.Web.PublishedCache.NuCache { var node = link.Value; - if (node != null && link.Gen < _liveGen) + if (node != null && link.Gen != _liveGen) { node = new ContentNode(link.Value); if (link == _root) @@ -976,6 +1053,12 @@ namespace Umbraco.Web.PublishedCache.NuCache var parent = parentLink.Value; + // We are doing a null check here but this should no longer be possible because we have a null check in BuildKit + // for the parent.Value property and we'll output a warning. However I'll leave this additional null check in place. + // see https://github.com/umbraco/Umbraco-CMS/issues/7868 + if (parent == null) + throw new PanicException($"A null Value was returned on the {nameof(parentLink)} LinkedNode with id={content.ParentContentId}, potentially your database paths are corrupted."); + // if parent has no children, clone parent + add as first child if (parent.FirstChildContentId < 0) { @@ -1109,7 +1192,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // this is safe only because we're write-locked foreach (var kvp in dict.Where(x => x.Value != null)) { - if (kvp.Value.Gen < _liveGen) + if (kvp.Value.Gen != _liveGen) { var link = new LinkedNode(null, _liveGen, kvp.Value); dict.TryUpdate(kvp.Key, link, kvp.Value); @@ -1197,11 +1280,8 @@ namespace Umbraco.Web.PublishedCache.NuCache public Snapshot CreateSnapshot() { - var lockInfo = new ReadLockInfo(); - try + lock(_rlocko) { - Lock(lockInfo); - // if no next generation is required, and we already have one, // use it and create a new snapshot if (_nextGen == false && _genObj != null) @@ -1214,7 +1294,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // else we need to try to create a new gen ref // whether we are wlocked or not, noone can rlock while we do, // so _liveGen and _nextGen are safe - if (_wlocked > 0) // volatile, cannot ++ but could -- + if (Monitor.IsEntered(_wlocko)) { // write-locked, cannot use latest gen (at least 1) so use previous var snapGen = _nextGen ? _liveGen - 1 : _liveGen; @@ -1253,10 +1333,6 @@ namespace Umbraco.Web.PublishedCache.NuCache return snapshot; } - finally - { - Release(lockInfo); - } } public Snapshot LiveSnapshot => new Snapshot(this, _liveGen @@ -1374,16 +1450,17 @@ namespace Umbraco.Web.PublishedCache.NuCache } } - public async Task WaitForPendingCollect() - { - Task task; - lock (_rlocko) - { - task = _collectTask; - } - if (task != null) - await task; - } + // TODO: This is never used? Should it be? Maybe move to TestHelper below? + //public async Task WaitForPendingCollect() + //{ + // Task task; + // lock (_rlocko) + // { + // task = _collectTask; + // } + // if (task != null) + // await task; + //} public long GenCount => _genObjs.Count; diff --git a/src/Umbraco.Web/PublishedCache/NuCache/DataSource/DatabaseDataSource.cs b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/DatabaseDataSource.cs index 19aab7ea65..694dac04df 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/DataSource/DatabaseDataSource.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/DatabaseDataSource.cs @@ -20,6 +20,8 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource // provides efficient database access for NuCache internal class DatabaseDataSource : IDataSource { + private const int PageSize = 500; + // we want arrays, we want them all loaded, not an enumerable private Sql ContentSourcesSelect(IScope scope, Func, Sql> joins = null) @@ -79,33 +81,43 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource .Where(x => x.NodeObjectType == Constants.ObjectTypes.Document && !x.Trashed) .OrderBy(x => x.Level, x => x.ParentId, x => x.SortOrder); - return scope.Database.Query(sql).Select(CreateContentNodeKit); + // We need to page here. We don't want to iterate over every single row in one connection cuz this can cause an SQL Timeout. + // We also want to read with a db reader and not load everything into memory, QueryPaged lets us do that. + + foreach (var row in scope.Database.QueryPaged(PageSize, sql)) + yield return CreateContentNodeKit(row); } public IEnumerable GetBranchContentSources(IScope scope, int id) { var syntax = scope.SqlContext.SqlSyntax; - var sql = ContentSourcesSelect(scope, s => s + var sql = ContentSourcesSelect(scope, + s => s.InnerJoin("x").On((left, right) => left.NodeId == right.NodeId || SqlText(left.Path, right.Path, (lp, rp) => $"({lp} LIKE {syntax.GetConcat(rp, "',%'")})"), aliasRight: "x")) + .Where(x => x.NodeObjectType == Constants.ObjectTypes.Document && !x.Trashed) + .Where(x => x.NodeId == id, "x") + .OrderBy(x => x.Level, x => x.ParentId, x => x.SortOrder); - .InnerJoin("x").On((left, right) => left.NodeId == right.NodeId || SqlText(left.Path, right.Path, (lp, rp) => $"({lp} LIKE {syntax.GetConcat(rp, "',%'")})"), aliasRight: "x")) + // We need to page here. We don't want to iterate over every single row in one connection cuz this can cause an SQL Timeout. + // We also want to read with a db reader and not load everything into memory, QueryPaged lets us do that. - .Where(x => x.NodeObjectType == Constants.ObjectTypes.Document && !x.Trashed) - .Where(x => x.NodeId == id, "x") - .OrderBy(x => x.Level, x => x.ParentId, x => x.SortOrder); - - return scope.Database.Query(sql).Select(CreateContentNodeKit); + foreach (var row in scope.Database.QueryPaged(PageSize, sql)) + yield return CreateContentNodeKit(row); } public IEnumerable GetTypeContentSources(IScope scope, IEnumerable ids) { - if (!ids.Any()) return Enumerable.Empty(); + if (!ids.Any()) yield break; var sql = ContentSourcesSelect(scope) .Where(x => x.NodeObjectType == Constants.ObjectTypes.Document && !x.Trashed) .WhereIn(x => x.ContentTypeId, ids) .OrderBy(x => x.Level, x => x.ParentId, x => x.SortOrder); - return scope.Database.Query(sql).Select(CreateContentNodeKit); + // We need to page here. We don't want to iterate over every single row in one connection cuz this can cause an SQL Timeout. + // We also want to read with a db reader and not load everything into memory, QueryPaged lets us do that. + + foreach (var row in scope.Database.QueryPaged(PageSize, sql)) + yield return CreateContentNodeKit(row); } private Sql MediaSourcesSelect(IScope scope, Func, Sql> joins = null) @@ -116,11 +128,8 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource x => Alias(x.Level, "Level"), x => Alias(x.Path, "Path"), x => Alias(x.SortOrder, "SortOrder"), x => Alias(x.ParentId, "ParentId"), x => Alias(x.CreateDate, "CreateDate"), x => Alias(x.UserId, "CreatorId")) .AndSelect(x => Alias(x.ContentTypeId, "ContentTypeId")) - .AndSelect(x => Alias(x.Id, "VersionId"), x => Alias(x.Text, "EditName"), x => Alias(x.VersionDate, "EditVersionDate"), x => Alias(x.UserId, "EditWriterId")) - .AndSelect("nuEdit", x => Alias(x.Data, "EditData")) - .From(); if (joins != null) @@ -128,9 +137,7 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource sql = sql .InnerJoin().On((left, right) => left.NodeId == right.NodeId) - .InnerJoin().On((left, right) => left.NodeId == right.NodeId && right.Current) - .LeftJoin("nuEdit").On((left, right) => left.NodeId == right.NodeId && !right.Published, aliasRight: "nuEdit"); return sql; @@ -152,33 +159,43 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource .Where(x => x.NodeObjectType == Constants.ObjectTypes.Media && !x.Trashed) .OrderBy(x => x.Level, x => x.ParentId, x => x.SortOrder); - return scope.Database.Query(sql).Select(CreateMediaNodeKit); + // We need to page here. We don't want to iterate over every single row in one connection cuz this can cause an SQL Timeout. + // We also want to read with a db reader and not load everything into memory, QueryPaged lets us do that. + + foreach (var row in scope.Database.QueryPaged(PageSize, sql)) + yield return CreateMediaNodeKit(row); } public IEnumerable GetBranchMediaSources(IScope scope, int id) { var syntax = scope.SqlContext.SqlSyntax; - var sql = MediaSourcesSelect(scope, s => s - - .InnerJoin("x").On((left, right) => left.NodeId == right.NodeId || SqlText(left.Path, right.Path, (lp, rp) => $"({lp} LIKE {syntax.GetConcat(rp, "',%'")})"), aliasRight: "x")) - + var sql = MediaSourcesSelect(scope, + s => s.InnerJoin("x").On((left, right) => left.NodeId == right.NodeId || SqlText(left.Path, right.Path, (lp, rp) => $"({lp} LIKE {syntax.GetConcat(rp, "',%'")})"), aliasRight: "x")) .Where(x => x.NodeObjectType == Constants.ObjectTypes.Media && !x.Trashed) .Where(x => x.NodeId == id, "x") .OrderBy(x => x.Level, x => x.ParentId, x => x.SortOrder); - return scope.Database.Query(sql).Select(CreateMediaNodeKit); + // We need to page here. We don't want to iterate over every single row in one connection cuz this can cause an SQL Timeout. + // We also want to read with a db reader and not load everything into memory, QueryPaged lets us do that. + + foreach (var row in scope.Database.QueryPaged(PageSize, sql)) + yield return CreateMediaNodeKit(row); } public IEnumerable GetTypeMediaSources(IScope scope, IEnumerable ids) { - if (!ids.Any()) return Enumerable.Empty(); + if (!ids.Any()) yield break; var sql = MediaSourcesSelect(scope) .Where(x => x.NodeObjectType == Constants.ObjectTypes.Media && !x.Trashed) .WhereIn(x => x.ContentTypeId, ids) .OrderBy(x => x.Level, x => x.ParentId, x => x.SortOrder); - return scope.Database.Query(sql).Select(CreateMediaNodeKit); + // We need to page here. We don't want to iterate over every single row in one connection cuz this can cause an SQL Timeout. + // We also want to read with a db reader and not load everything into memory, QueryPaged lets us do that. + + foreach (var row in scope.Database.QueryPaged(PageSize, sql)) + yield return CreateMediaNodeKit(row); } private static ContentNodeKit CreateContentNodeKit(ContentSourceDto dto) diff --git a/src/Umbraco.Web/PublishedCache/NuCache/Property.cs b/src/Umbraco.Web/PublishedCache/NuCache/Property.cs index 0254b815c1..86023bb302 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/Property.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/Property.cs @@ -90,7 +90,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // determines whether a property has value public override bool HasValue(string culture = null, string segment = null) { - _content.VariationContextAccessor.ContextualizeVariation(_variations, ref culture, ref segment); + _content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, ref culture, ref segment); var value = GetSourceValue(culture, segment); var hasValue = PropertyType.IsValue(value, PropertyValueLevel.Source); @@ -194,7 +194,7 @@ namespace Umbraco.Web.PublishedCache.NuCache public override object GetSourceValue(string culture = null, string segment = null) { - _content.VariationContextAccessor.ContextualizeVariation(_variations, ref culture, ref segment); + _content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, ref culture, ref segment); if (culture == "" && segment == "") return _sourceValue; @@ -208,7 +208,7 @@ namespace Umbraco.Web.PublishedCache.NuCache public override object GetValue(string culture = null, string segment = null) { - _content.VariationContextAccessor.ContextualizeVariation(_variations, ref culture, ref segment); + _content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, ref culture, ref segment); object value; lock (_locko) @@ -229,7 +229,7 @@ namespace Umbraco.Web.PublishedCache.NuCache public override object GetXPathValue(string culture = null, string segment = null) { - _content.VariationContextAccessor.ContextualizeVariation(_variations, ref culture, ref segment); + _content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, ref culture, ref segment); lock (_locko) { diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs index bf4975714d..d80affbfa9 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs @@ -163,6 +163,7 @@ namespace Umbraco.Web.PublishedCache.NuCache public override int CreatorId => _contentNode.CreatorId; /// + [Obsolete("Use CreatorName(IUserService) extension instead")] public override string CreatorName => GetProfileNameById(_contentNode.CreatorId); /// @@ -172,6 +173,7 @@ namespace Umbraco.Web.PublishedCache.NuCache public override int WriterId => ContentData.WriterId; /// + [Obsolete("Use WriterName(IUserService) extension instead")] public override string WriterName => GetProfileNameById(ContentData.WriterId); /// @@ -326,7 +328,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // beware what you use that one for - you don't want to cache its result private IAppCache GetAppropriateCache() { - var publishedSnapshot = (PublishedSnapshot)_publishedSnapshotAccessor.PublishedSnapshot; + var publishedSnapshot = _publishedSnapshotAccessor.PublishedSnapshot; var cache = publishedSnapshot == null ? null : ((IsPreviewing == false || PublishedSnapshotService.FullCacheWhenPreviewing) && (ContentType.ItemType != PublishedItemType.Member) @@ -337,7 +339,7 @@ namespace Umbraco.Web.PublishedCache.NuCache private IAppCache GetCurrentSnapshotCache() { - var publishedSnapshot = (PublishedSnapshot)_publishedSnapshotAccessor.PublishedSnapshot; + var publishedSnapshot = _publishedSnapshotAccessor.PublishedSnapshot; return publishedSnapshot?.SnapshotCache; } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs index e0b95e8481..7e78b2e96f 100755 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs @@ -4,6 +4,8 @@ using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; +using System.Threading; +using System.Threading.Tasks; using CSharpTest.Net.Collections; using Newtonsoft.Json; using Umbraco.Core; @@ -144,7 +146,7 @@ namespace Umbraco.Web.PublishedCache.NuCache _domainStore = new SnapDictionary(); - LoadCachesOnStartup(); + LoadCachesOnStartup(); } Guid GetUid(ContentStore store, int id) => store.LiveSnapshot.Get(id)?.Uid ?? default; @@ -165,14 +167,14 @@ namespace Umbraco.Web.PublishedCache.NuCache /// to not run if MainDom wasn't acquired. /// If MainDom was not acquired, then _localContentDb and _localMediaDb will remain null which means this appdomain /// will load in published content via the DB and in that case this appdomain will probably not exist long enough to - /// serve more than a page of content. + /// serve more than a page of content. /// private void MainDomRegister() { var path = GetLocalFilesPath(); var localContentDbPath = Path.Combine(path, "NuCache.Content.db"); var localMediaDbPath = Path.Combine(path, "NuCache.Media.db"); - + _localContentDbExists = File.Exists(localContentDbPath); _localMediaDbExists = File.Exists(localMediaDbPath); @@ -191,10 +193,15 @@ namespace Umbraco.Web.PublishedCache.NuCache /// private void MainDomRelease() { + _logger.Debug("Releasing from MainDom..."); + lock (_storesLock) { + _logger.Debug("Releasing content store..."); _contentStore?.ReleaseLocalDb(); //null check because we could shut down before being assigned _localContentDb = null; + + _logger.Debug("Releasing media store..."); _mediaStore?.ReleaseLocalDb(); //null check because we could shut down before being assigned _localMediaDb = null; @@ -217,7 +224,7 @@ namespace Umbraco.Web.PublishedCache.NuCache { okContent = LockAndLoadContent(scope => LoadContentFromLocalDbLocked(true)); if (!okContent) - _logger.Warn("Loading content from local db raised warnings, will reload from database."); + _logger.Warn("Loading content from local db raised warnings, will reload from database."); } if (_localMediaDbExists) @@ -378,7 +385,7 @@ namespace Umbraco.Web.PublishedCache.NuCache var contentTypes = _serviceContext.ContentTypeService.GetAll() .Select(x => _publishedContentTypeFactory.CreateContentType(x)); - _contentStore.SetAllContentTypes(contentTypes); + _contentStore.SetAllContentTypesLocked(contentTypes); using (_logger.TraceDuration("Loading content from database")) { @@ -389,7 +396,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // IMPORTANT GetAllContentSources sorts kits by level + parentId + sortOrder var kits = _dataSource.GetAllContentSources(scope); - return onStartup ? _contentStore.SetAllFastSorted(kits, true) : _contentStore.SetAll(kits); + return onStartup ? _contentStore.SetAllFastSortedLocked(kits, true) : _contentStore.SetAllLocked(kits); } } @@ -397,7 +404,7 @@ namespace Umbraco.Web.PublishedCache.NuCache { var contentTypes = _serviceContext.ContentTypeService.GetAll() .Select(x => _publishedContentTypeFactory.CreateContentType(x)); - _contentStore.SetAllContentTypes(contentTypes); + _contentStore.SetAllContentTypesLocked(contentTypes); using (_logger.TraceDuration("Loading content from local cache file")) { @@ -449,7 +456,7 @@ namespace Umbraco.Web.PublishedCache.NuCache var mediaTypes = _serviceContext.MediaTypeService.GetAll() .Select(x => _publishedContentTypeFactory.CreateContentType(x)); - _mediaStore.SetAllContentTypes(mediaTypes); + _mediaStore.SetAllContentTypesLocked(mediaTypes); using (_logger.TraceDuration("Loading media from database")) { @@ -461,7 +468,7 @@ namespace Umbraco.Web.PublishedCache.NuCache _logger.Debug("Loading media from database..."); // IMPORTANT GetAllMediaSources sorts kits by level + parentId + sortOrder var kits = _dataSource.GetAllMediaSources(scope); - return onStartup ? _mediaStore.SetAllFastSorted(kits, true) : _mediaStore.SetAll(kits); + return onStartup ? _mediaStore.SetAllFastSortedLocked(kits, true) : _mediaStore.SetAllLocked(kits); } } @@ -469,7 +476,7 @@ namespace Umbraco.Web.PublishedCache.NuCache { var mediaTypes = _serviceContext.MediaTypeService.GetAll() .Select(x => _publishedContentTypeFactory.CreateContentType(x)); - _mediaStore.SetAllContentTypes(mediaTypes); + _mediaStore.SetAllContentTypesLocked(mediaTypes); using (_logger.TraceDuration("Loading media from local cache file")) { @@ -510,7 +517,7 @@ namespace Umbraco.Web.PublishedCache.NuCache return false; } - return onStartup ? store.SetAllFastSorted(kits, false) : store.SetAll(kits); + return onStartup ? store.SetAllFastSortedLocked(kits, false) : store.SetAllLocked(kits); } // keep these around - might be useful @@ -616,7 +623,7 @@ namespace Umbraco.Web.PublishedCache.NuCache .Where(x => x.RootContentId.HasValue && x.LanguageIsoCode.IsNullOrWhiteSpace() == false) .Select(x => new Domain(x.Id, x.DomainName, x.RootContentId.Value, CultureInfo.GetCultureInfo(x.LanguageIsoCode), x.IsWildcard))) { - _domainStore.Set(domain.Id, domain); + _domainStore.SetLocked(domain.Id, domain); } } @@ -664,10 +671,12 @@ namespace Umbraco.Web.PublishedCache.NuCache publishedChanged = publishedChanged2; } + if (draftChanged || publishedChanged) ((PublishedSnapshot)CurrentPublishedSnapshot)?.Resync(); } + // Calling this method means we have a lock on the contentStore (i.e. GetScopedWriteLock) private void NotifyLocked(IEnumerable payloads, out bool draftChanged, out bool publishedChanged) { publishedChanged = false; @@ -677,7 +686,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // content (and content types) are read-locked while reading content // contentStore is wlocked (so readable, only no new views) // and it can be wlocked by 1 thread only at a time - // contentStore is write-locked during changes + // contentStore is write-locked during changes - see note above, calls to this method are wrapped in contentStore.GetScopedWriteLock foreach (var payload in payloads) { @@ -697,7 +706,7 @@ namespace Umbraco.Web.PublishedCache.NuCache if (payload.ChangeTypes.HasType(TreeChangeTypes.Remove)) { - if (_contentStore.Clear(payload.Id)) + if (_contentStore.ClearLocked(payload.Id)) draftChanged = publishedChanged = true; continue; } @@ -720,7 +729,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // ?? should we do some RV check here? // IMPORTANT GetbranchContentSources sorts kits by level and by sort order var kits = _dataSource.GetBranchContentSources(scope, capture.Id); - _contentStore.SetBranch(capture.Id, kits); + _contentStore.SetBranchLocked(capture.Id, kits); } else { @@ -728,11 +737,11 @@ namespace Umbraco.Web.PublishedCache.NuCache var kit = _dataSource.GetContentSource(scope, capture.Id); if (kit.IsEmpty) { - _contentStore.Clear(capture.Id); + _contentStore.ClearLocked(capture.Id); } else { - _contentStore.Set(kit); + _contentStore.SetLocked(kit); } } @@ -790,7 +799,7 @@ namespace Umbraco.Web.PublishedCache.NuCache if (payload.ChangeTypes.HasType(TreeChangeTypes.Remove)) { - if (_mediaStore.Clear(payload.Id)) + if (_mediaStore.ClearLocked(payload.Id)) anythingChanged = true; continue; } @@ -813,7 +822,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // ?? should we do some RV check here? // IMPORTANT GetbranchContentSources sorts kits by level and by sort order var kits = _dataSource.GetBranchMediaSources(scope, capture.Id); - _mediaStore.SetBranch(capture.Id, kits); + _mediaStore.SetBranchLocked(capture.Id, kits); } else { @@ -821,11 +830,11 @@ namespace Umbraco.Web.PublishedCache.NuCache var kit = _dataSource.GetMediaSource(scope, capture.Id); if (kit.IsEmpty) { - _mediaStore.Clear(capture.Id); + _mediaStore.ClearLocked(capture.Id); } else { - _mediaStore.Set(kit); + _mediaStore.SetLocked(kit); } } @@ -850,7 +859,7 @@ namespace Umbraco.Web.PublishedCache.NuCache Notify(_contentStore, payloads, RefreshContentTypesLocked); Notify(_mediaStore, payloads, RefreshMediaTypesLocked); - if (_publishedModelFactory.IsLiveFactory()) + if (_publishedModelFactory.IsLiveFactoryEnabled()) { //In the case of Pure Live - we actually need to refresh all of the content and the media //see https://github.com/umbraco/Umbraco-CMS/issues/5671 @@ -858,12 +867,37 @@ namespace Umbraco.Web.PublishedCache.NuCache //into a new DLL for the application which includes both content types and media types. //Since the models in the cache are based on these actual classes, all of the objects in the cache need to be updated //to use the newest version of the class. - using (_contentStore.GetScopedWriteLock(_scopeProvider)) - using (_mediaStore.GetScopedWriteLock(_scopeProvider)) - { - NotifyLocked(new[] { new ContentCacheRefresher.JsonPayload(0, null, TreeChangeTypes.RefreshAll) }, out var draftChanged, out var publishedChanged); - NotifyLocked(new[] { new MediaCacheRefresher.JsonPayload(0, null, TreeChangeTypes.RefreshAll) }, out var anythingChanged); - } + + // NOTE: Ideally this can be run on background threads here which would prevent blocking the UI + // as is the case when saving a content type. Intially one would think that it won't be any different + // between running this here or in another background thread immediately after with regards to how the + // UI will respond because we already know between calling `WithSafeLiveFactoryReset` to reset the PureLive models + // and this code here, that many front-end requests could be attempted to be processed. If that is the case, those pages are going to get a + // model binding error and our ModelBindingExceptionFilter is going to to its magic to reload those pages so the end user is none the wiser. + // So whether or not this executes 'here' or on a background thread immediately wouldn't seem to make any difference except that we can return + // execution to the UI sooner. + // BUT!... there is a difference IIRC. There is still execution logic that continues after this call on this thread with the cache refreshers + // and those cache refreshers need to have the up-to-date data since other user cache refreshers will be expecting the data to be 'live'. If + // we ran this on a background thread then those cache refreshers are going to not get 'live' data when they query the content cache which + // they require. + + // These can be run side by side in parallel. + + Parallel.Invoke( + () => + { + using (_contentStore.GetScopedWriteLock(_scopeProvider)) + { + NotifyLocked(new[] { new ContentCacheRefresher.JsonPayload(0, null, TreeChangeTypes.RefreshAll) }, out _, out _); + } + }, + () => + { + using (_mediaStore.GetScopedWriteLock(_scopeProvider)) + { + NotifyLocked(new[] { new MediaCacheRefresher.JsonPayload(0, null, TreeChangeTypes.RefreshAll) }, out _); + } + }); } ((PublishedSnapshot)CurrentPublishedSnapshot)?.Resync(); @@ -927,14 +961,14 @@ namespace Umbraco.Web.PublishedCache.NuCache using (var scope = _scopeProvider.CreateScope()) { scope.ReadLock(Constants.Locks.ContentTree); - _contentStore.UpdateDataTypes(idsA, id => CreateContentType(PublishedItemType.Content, id)); + _contentStore.UpdateDataTypesLocked(idsA, id => CreateContentType(PublishedItemType.Content, id)); scope.Complete(); } using (var scope = _scopeProvider.CreateScope()) { scope.ReadLock(Constants.Locks.MediaTree); - _mediaStore.UpdateDataTypes(idsA, id => CreateContentType(PublishedItemType.Media, id)); + _mediaStore.UpdateDataTypesLocked(idsA, id => CreateContentType(PublishedItemType.Media, id)); scope.Complete(); } } @@ -964,7 +998,7 @@ namespace Umbraco.Web.PublishedCache.NuCache } break; case DomainChangeTypes.Remove: - _domainStore.Clear(payload.Id); + _domainStore.ClearLocked(payload.Id); break; case DomainChangeTypes.Refresh: var domain = _serviceContext.DomainService.GetById(payload.Id); @@ -972,14 +1006,14 @@ namespace Umbraco.Web.PublishedCache.NuCache if (domain.RootContentId.HasValue == false) continue; // anomaly if (domain.LanguageIsoCode.IsNullOrWhiteSpace()) continue; // anomaly var culture = CultureInfo.GetCultureInfo(domain.LanguageIsoCode); - _domainStore.Set(domain.Id, new Domain(domain.Id, domain.DomainName, domain.RootContentId.Value, culture, domain.IsWildcard)); + _domainStore.SetLocked(domain.Id, new Domain(domain.Id, domain.DomainName, domain.RootContentId.Value, culture, domain.IsWildcard)); break; } } } } - //Methods used to prevent allocations of lists + //Methods used to prevent allocations of lists private void AddToList(ref List list, int val) => GetOrCreateList(ref list).Add(val); private List GetOrCreateList(ref List list) => list ?? (list = new List()); @@ -1057,11 +1091,11 @@ namespace Umbraco.Web.PublishedCache.NuCache ? Array.Empty() : _dataSource.GetTypeContentSources(scope, refreshedIds).ToArray(); - _contentStore.UpdateContentTypes(removedIds, typesA, kits); + _contentStore.UpdateContentTypesLocked(removedIds, typesA, kits); if (!otherIds.IsCollectionEmpty()) - _contentStore.UpdateContentTypes(CreateContentTypes(PublishedItemType.Content, otherIds.ToArray())); + _contentStore.UpdateContentTypesLocked(CreateContentTypes(PublishedItemType.Content, otherIds.ToArray())); if (!newIds.IsCollectionEmpty()) - _contentStore.NewContentTypes(CreateContentTypes(PublishedItemType.Content, newIds.ToArray())); + _contentStore.NewContentTypesLocked(CreateContentTypes(PublishedItemType.Content, newIds.ToArray())); scope.Complete(); } } @@ -1088,11 +1122,11 @@ namespace Umbraco.Web.PublishedCache.NuCache ? Array.Empty() : _dataSource.GetTypeMediaSources(scope, refreshedIds).ToArray(); - _mediaStore.UpdateContentTypes(removedIds, typesA, kits); + _mediaStore.UpdateContentTypesLocked(removedIds, typesA, kits); if (!otherIds.IsCollectionEmpty()) - _mediaStore.UpdateContentTypes(CreateContentTypes(PublishedItemType.Media, otherIds.ToArray()).ToArray()); + _mediaStore.UpdateContentTypesLocked(CreateContentTypes(PublishedItemType.Media, otherIds.ToArray()).ToArray()); if (!newIds.IsCollectionEmpty()) - _mediaStore.NewContentTypes(CreateContentTypes(PublishedItemType.Media, newIds.ToArray()).ToArray()); + _mediaStore.NewContentTypesLocked(CreateContentTypes(PublishedItemType.Media, newIds.ToArray()).ToArray()); scope.Complete(); } } @@ -1163,12 +1197,14 @@ namespace Umbraco.Web.PublishedCache.NuCache // elements // just need to make sure nothing gets elements in another enlisted action... so using // a MaxValue to make sure this one runs last, and it should be ok + scopeContext.Enlist("Umbraco.Web.PublishedCache.NuCache.PublishedSnapshotService.Resync", () => this, (completed, svc) => { ((PublishedSnapshot)svc.CurrentPublishedSnapshot)?.Resync(); }, int.MaxValue); } + // create a new snapshot cache if snapshots are different gens if (contentSnap.Gen != _contentGen || mediaSnap.Gen != _mediaGen || domainSnap.Gen != _domainGen || _elementsCache == null) { @@ -1280,7 +1316,7 @@ namespace Umbraco.Web.PublishedCache.NuCache var member = args.Entity; // refresh the edited data - OnRepositoryRefreshed(db, member, true); + OnRepositoryRefreshed(db, member, false); } private void OnRepositoryRefreshed(IUmbracoDatabase db, IContentBase content, bool published) @@ -1335,7 +1371,7 @@ namespace Umbraco.Web.PublishedCache.NuCache { //culture changed on an existing language var cultureChanged = e.SavedEntities.Any(x => !x.WasPropertyDirty(nameof(ILanguage.Id)) && x.WasPropertyDirty(nameof(ILanguage.IsoCode))); - if(cultureChanged) + if (cultureChanged) { RebuildContentDbCache(); } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/Snap/LinkedNode.cs b/src/Umbraco.Web/PublishedCache/NuCache/Snap/LinkedNode.cs index d187996df8..94f83ac4e5 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/Snap/LinkedNode.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/Snap/LinkedNode.cs @@ -11,7 +11,7 @@ { public LinkedNode(TValue value, long gen, LinkedNode next = null) { - Value = value; + Value = value; // This is allowed to be null, we actually explicitly set this to null in ClearLocked Gen = gen; Next = next; } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/SnapDictionary.cs b/src/Umbraco.Web/PublishedCache/NuCache/SnapDictionary.cs index 9671949ff0..c38940da25 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/SnapDictionary.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/SnapDictionary.cs @@ -22,6 +22,11 @@ namespace Umbraco.Web.PublishedCache.NuCache // This class is optimized for many readers, few writers // Readers are lock-free + // NOTE - we used to lock _rlocko the long hand way with Monitor.Enter(_rlocko, ref lockTaken) but this has + // been replaced with a normal c# lock because that's exactly how the normal c# lock works, + // see https://blogs.msdn.microsoft.com/ericlippert/2009/03/06/locks-and-exceptions-do-not-mix/ + // for the readlock, there's no reason here to use the long hand way. + private readonly ConcurrentDictionary> _items; private readonly ConcurrentQueue _genObjs; private GenObj _genObj; @@ -30,7 +35,6 @@ namespace Umbraco.Web.PublishedCache.NuCache private long _liveGen, _floorGen; private bool _nextGen, _collectAuto; private Task _collectTask; - private volatile int _wlocked; // minGenDelta to be adjusted // we may want to throttle collects even if delta is reached @@ -71,20 +75,14 @@ namespace Umbraco.Web.PublishedCache.NuCache // are all ignored - Release is private and meant to be invoked with 'commit' being false only // only on the outermost lock (by SnapDictionaryWriter) - // using (...) {} for locking is prone to nasty leaks in case of weird exceptions - // such as thread-abort or out-of-memory, but let's not worry about it now + // side note - using (...) {} for locking is prone to nasty leaks in case of weird exceptions + // such as thread-abort or out-of-memory, which is why we've moved away from the old using wrapper we had on locking. private readonly string _instanceId = Guid.NewGuid().ToString("N"); - private class ReadLockInfo - { - public bool Taken; - } - private class WriteLockInfo { public bool Taken; - public bool Count; } // a scope contextual that represents a locked writer to the dictionary @@ -92,8 +90,7 @@ namespace Umbraco.Web.PublishedCache.NuCache { private readonly WriteLockInfo _lockinfo = new WriteLockInfo(); private readonly SnapDictionary _dictionary; - private int _released; - + public ScopedWriteLock(SnapDictionary dictionary, bool scoped) { _dictionary = dictionary; @@ -102,8 +99,6 @@ namespace Umbraco.Web.PublishedCache.NuCache public override void Release(bool completed) { - if (Interlocked.CompareExchange(ref _released, 1, 0) != 0) - return; _dictionary.Release(_lockinfo, completed); } } @@ -117,28 +112,31 @@ namespace Umbraco.Web.PublishedCache.NuCache return ScopeContextualBase.Get(scopeProvider, _instanceId, scoped => new ScopedWriteLock(this, scoped)); } + private void EnsureLocked() + { + if (!Monitor.IsEntered(_wlocko)) + throw new InvalidOperationException("Write lock must be acquried."); + } + private void Lock(WriteLockInfo lockInfo, bool forceGen = false) { + if (Monitor.IsEntered(_wlocko)) + throw new InvalidOperationException("Recursive locks not allowed"); + Monitor.Enter(_wlocko, ref lockInfo.Taken); - var rtaken = false; - try + lock(_rlocko) { - Monitor.Enter(_rlocko, ref rtaken); - // assume everything in finally runs atomically // http://stackoverflow.com/questions/18501678/can-this-unexpected-behavior-of-prepareconstrainedregions-and-thread-abort-be-ex // http://joeduffyblog.com/2005/03/18/atomicity-and-asynchronous-exception-failures/ // http://joeduffyblog.com/2007/02/07/introducing-the-new-readerwriterlockslim-in-orcas/ // http://chabster.blogspot.fr/2013/12/readerwriterlockslim-fails-on-dual.html //RuntimeHelpers.PrepareConstrainedRegions(); - try { } finally + try { } + finally { - // increment the lock count, and register that this lock is counting - _wlocked++; - lockInfo.Count = true; - - if (_nextGen == false || (forceGen && _wlocked == 1)) + if (_nextGen == false || (forceGen)) { // because we are changing things, a new generation // is created, which will trigger a new snapshot @@ -149,15 +147,6 @@ namespace Umbraco.Web.PublishedCache.NuCache } } } - finally - { - if (rtaken) Monitor.Exit(_rlocko); - } - } - - private void Lock(ReadLockInfo lockInfo) - { - Monitor.Enter(_rlocko, ref lockInfo.Taken); } private void Release(WriteLockInfo lockInfo, bool commit = true) @@ -168,22 +157,17 @@ namespace Umbraco.Web.PublishedCache.NuCache if (commit == false) { - var rtaken = false; - try + lock(_rlocko) { - Monitor.Enter(_rlocko, ref rtaken); - try { } finally + try { } + finally { // forget about the temp. liveGen _nextGen = false; _liveGen -= 1; } } - finally - { - if (rtaken) Monitor.Exit(_rlocko); - } - + foreach (var item in _items) { var link = item.Value; @@ -197,16 +181,10 @@ namespace Umbraco.Web.PublishedCache.NuCache } } - // decrement the lock count, if counting, then exit the lock - if (lockInfo.Count) _wlocked--; + // TODO: Shouldn't this be in a finally block? Monitor.Exit(_wlocko); } - private void Release(ReadLockInfo lockInfo) - { - if (lockInfo.Taken) Monitor.Exit(_rlocko); - } - #endregion #region Set, Clear, Get, Has @@ -219,75 +197,59 @@ namespace Umbraco.Web.PublishedCache.NuCache return link; } - public void Set(TKey key, TValue value) + public void SetLocked(TKey key, TValue value) { - var lockInfo = new WriteLockInfo(); - try - { - Lock(lockInfo); + EnsureLocked(); - // this is safe only because we're write-locked - var link = GetHead(key); - if (link != null) + // this is safe only because we're write-locked + var link = GetHead(key); + if (link != null) + { + // already in the dict + if (link.Gen != _liveGen) { - // already in the dict - if (link.Gen != _liveGen) - { - // for an older gen - if value is different then insert a new - // link for the new gen, with the new value - if (link.Value != value) - _items.TryUpdate(key, new LinkedNode(value, _liveGen, link), link); - } - else - { - // for the live gen - we can fix the live gen - and remove it - // if value is null and there's no next gen - if (value == null && link.Next == null) - _items.TryRemove(key, out link); - else - link.Value = value; - } + // for an older gen - if value is different then insert a new + // link for the new gen, with the new value + if (link.Value != value) + _items.TryUpdate(key, new LinkedNode(value, _liveGen, link), link); } else { - _items.TryAdd(key, new LinkedNode(value, _liveGen)); - } - } - finally - { - Release(lockInfo); - } - } - - public void Clear(TKey key) - { - Set(key, null); - } - - public void Clear() - { - var lockInfo = new WriteLockInfo(); - try - { - Lock(lockInfo); - - // this is safe only because we're write-locked - foreach (var kvp in _items.Where(x => x.Value != null)) - { - if (kvp.Value.Gen < _liveGen) - { - var link = new LinkedNode(null, _liveGen, kvp.Value); - _items.TryUpdate(kvp.Key, link, kvp.Value); - } + // for the live gen - we can fix the live gen - and remove it + // if value is null and there's no next gen + if (value == null && link.Next == null) + _items.TryRemove(key, out link); else - { - kvp.Value.Value = null; - } + link.Value = value; } } - finally + else { - Release(lockInfo); + _items.TryAdd(key, new LinkedNode(value, _liveGen)); + } + } + + public void ClearLocked(TKey key) + { + SetLocked(key, null); + } + + public void ClearLocked() + { + EnsureLocked(); + + // this is safe only because we're write-locked + foreach (var kvp in _items.Where(x => x.Value != null)) + { + if (kvp.Value.Gen < _liveGen) + { + var link = new LinkedNode(null, _liveGen, kvp.Value); + _items.TryUpdate(kvp.Key, link, kvp.Value); + } + else + { + kvp.Value.Value = null; + } } } @@ -347,11 +309,8 @@ namespace Umbraco.Web.PublishedCache.NuCache public Snapshot CreateSnapshot() { - var lockInfo = new ReadLockInfo(); - try + lock(_rlocko) { - Lock(lockInfo); - // if no next generation is required, and we already have a gen object, // use it to create a new snapshot if (_nextGen == false && _genObj != null) @@ -360,7 +319,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // else we need to try to create a new gen object // whether we are wlocked or not, noone can rlock while we do, // so _liveGen and _nextGen are safe - if (_wlocked > 0) // volatile, cannot ++ but could -- + if (Monitor.IsEntered(_wlocko)) { // write-locked, cannot use latest gen (at least 1) so use previous var snapGen = _nextGen ? _liveGen - 1 : _liveGen; @@ -398,10 +357,6 @@ namespace Umbraco.Web.PublishedCache.NuCache return snapshot; } - finally - { - Release(lockInfo); - } } public Task CollectAsync() @@ -496,17 +451,18 @@ namespace Umbraco.Web.PublishedCache.NuCache } } - public /*async*/ Task PendingCollect() - { - Task task; - lock (_rlocko) - { - task = _collectTask; - } - return task ?? Task.CompletedTask; - //if (task != null) - // await task; - } + // TODO: This is never used? Should it be? Maybe move to TestHelper below? + //public /*async*/ Task PendingCollect() + //{ + // Task task; + // lock (_rlocko) + // { + // task = _collectTask; + // } + // return task ?? Task.CompletedTask; + // //if (task != null) + // // await task; + //} public long GenCount => _genObjs.Count; @@ -531,7 +487,7 @@ namespace Umbraco.Web.PublishedCache.NuCache public long LiveGen => _dict._liveGen; public long FloorGen => _dict._floorGen; public bool NextGen => _dict._nextGen; - public int WLocked => _dict._wlocked; + public bool IsLocked => Monitor.IsEntered(_dict._wlocko); public bool CollectAuto { diff --git a/src/Umbraco.Web/PublishedCache/NuCache/notes.txt b/src/Umbraco.Web/PublishedCache/NuCache/readme.md similarity index 70% rename from src/Umbraco.Web/PublishedCache/NuCache/notes.txt rename to src/Umbraco.Web/PublishedCache/NuCache/readme.md index ff2d8dd48b..c8e8a363e9 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/notes.txt +++ b/src/Umbraco.Web/PublishedCache/NuCache/readme.md @@ -1,10 +1,6 @@ NuCache Documentation ====================== -NOTE - RENAMING -Facade = PublishedSnapshot -and everything needs to be renamed accordingly - HOW IT WORKS ------------- @@ -22,12 +18,12 @@ When reading the cache, we read views up the chain until we find a value (which null) for the given id, and finally we read the store itself. -The FacadeService manages a ContentStore for content, and another for media. -When a Facade is created, the FacadeService gets ContentView objects from the stores. +The PublishedSnapshotService manages a ContentStore for content, and another for media. +When a PublishedSnapshot is created, the PublishedSnapshotService gets ContentView objects from the stores. Views provide an immutable snapshot of the content and media. -When the FacadeService is notified of changes, it notifies the stores. -Then it resync the current Facade, so that it requires new views, etc. +When the PublishedSnapshotService is notified of changes, it notifies the stores. +Then it resync the current PublishedSnapshot, so that it requires new views, etc. Whenever a content, media or member is modified or removed, the cmsContentNu table is updated with a json dictionary of alias => property value, so that a content, @@ -50,32 +46,32 @@ Each ContentStore has a _freezeLock object used to protect the 'Frozen' state of the store. It's a disposable object that releases the lock when disposed, so usage would be: using (store.Frozen) { ... }. -The FacadeService has a _storesLock object used to guarantee atomic access to the +The PublishedSnapshotService has a _storesLock object used to guarantee atomic access to the set of content, media stores. CACHE ------ -For each set of views, the FacadeService creates a SnapshotCache. So a SnapshotCache +For each set of views, the PublishedSnapshotService creates a SnapshotCache. So a SnapshotCache is valid until anything changes in the content or media trees. In other words, things that go in the SnapshotCache stay until a content or media is modified. -For each Facade, the FacadeService creates a FacadeCache. So a FacadeCache is valid -for the duration of the Facade (usually, the request). In other words, things that go -in the FacadeCache stay (and are visible to) for the duration of the request only. +For each PublishedSnapshot, the PublishedSnapshotService creates a PublishedSnapshotCache. So a PublishedSnapshotCache is valid +for the duration of the PublishedSnapshot (usually, the request). In other words, things that go +in the PublishedSnapshotCache stay (and are visible to) for the duration of the request only. -The FacadeService defines a static constant FullCacheWhenPreviewing, that defines +The PublishedSnapshotService defines a static constant FullCacheWhenPreviewing, that defines how caches operate when previewing: - when true, the caches in preview mode work normally. -- when false, everything that would go to the SnapshotCache goes to the FacadeCache. +- when false, everything that would go to the SnapshotCache goes to the PublishedSnapshotCache. At the moment it is true in the code, which means that eg converted values for previewed content will go in the SnapshotCache. Makes for faster preview, but uses more memory on the long term... would need some benchmarking to figure out what is best. -Members only live for the duration of the Facade. So, for members SnapshotCache is -never used, and everything goes to the FacadeCache. +Members only live for the duration of the PublishedSnapshot. So, for members SnapshotCache is +never used, and everything goes to the PublishedSnapshotCache. All cache keys are computed in the CacheKeys static class. @@ -85,15 +81,15 @@ TESTS For testing purposes the following mechanisms exist: -The Facade type has a static Current property that is used to obtain the 'current' -facade in many places, going through the PublishedCachesServiceResolver to get the -current service, and asking the current service for the current facade, which by +The PublishedSnapshot type has a static Current property that is used to obtain the 'current' +PublishedSnapshot in many places, going through the PublishedCachesServiceResolver to get the +current service, and asking the current service for the current PublishedSnapshot, which by default relies on UmbracoContext. For test purposes, it is possible to override the -entire mechanism by defining Facade.GetCurrentFacadeFunc which should return a facade. +entire mechanism by defining PublishedSnapshot.GetCurrentPublishedSnapshotFunc which should return a PublishedSnapshot. A PublishedContent keeps only id-references to its parent and children, and needs a way to retrieve the actual objects from the cache - which depends on the current -facade. It is possible to override the entire mechanism by defining PublishedContent. +PublishedSnapshot. It is possible to override the entire mechanism by defining PublishedContent. GetContentByIdFunc or .GetMediaByIdFunc. Setting these functions must be done before Resolution is frozen. @@ -110,7 +106,7 @@ possible to support detached contents & properties, even those that do not have int id, but again this should be refactored entirely anyway. Not doing any row-version checks (see XmlStore) when reloading from database, though it -is maintained in the database. Two FIXME in FacadeService. Should we do it? +is maintained in the database. Two FIXME in PublishedSnapshotService. Should we do it? There is no on-disk cache at all so everything is reloaded from the cmsContentNu table when the site restarts. This is pretty fast, but we should experiment with solutions to @@ -121,4 +117,4 @@ PublishedMember exposes properties that IPublishedContent does not, and that are to be lost soon as the member is wrapped (content set, model...) - so we probably need some sort of IPublishedMember. -/ \ No newline at end of file +/ diff --git a/src/Umbraco.Web/PublishedCache/PublishedMember.cs b/src/Umbraco.Web/PublishedCache/PublishedMember.cs index 6e9ec61c62..e7cdb65f2e 100644 --- a/src/Umbraco.Web/PublishedCache/PublishedMember.cs +++ b/src/Umbraco.Web/PublishedCache/PublishedMember.cs @@ -140,9 +140,11 @@ namespace Umbraco.Web.PublishedCache public override string UrlSegment => throw new NotSupportedException(); // TODO: ARGH! need to fix this - this is not good because it uses ApplicationContext.Current + [Obsolete("Use WriterName(IUserService) extension instead")] public override string WriterName => _member.GetCreatorProfile().Name; // TODO: ARGH! need to fix this - this is not good because it uses ApplicationContext.Current + [Obsolete("Use CreatorName(IUserService) extension instead")] public override string CreatorName => _member.GetCreatorProfile().Name; public override int WriterId => _member.CreatorId; diff --git a/src/Umbraco.Web/PublishedCache/PublishedSnapshotServiceBase.cs b/src/Umbraco.Web/PublishedCache/PublishedSnapshotServiceBase.cs index 20d3e6d8e3..1b56f54569 100644 --- a/src/Umbraco.Web/PublishedCache/PublishedSnapshotServiceBase.cs +++ b/src/Umbraco.Web/PublishedCache/PublishedSnapshotServiceBase.cs @@ -1,11 +1,12 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Umbraco.Core.Models.Membership; using Umbraco.Core.Models.PublishedContent; using Umbraco.Web.Cache; namespace Umbraco.Web.PublishedCache { - abstract class PublishedSnapshotServiceBase : IPublishedSnapshotService + internal abstract class PublishedSnapshotServiceBase : IPublishedSnapshotService { protected PublishedSnapshotServiceBase(IPublishedSnapshotAccessor publishedSnapshotAccessor, IVariationContextAccessor variationContextAccessor) { diff --git a/src/Umbraco.Web/PublishedContentExtensions.cs b/src/Umbraco.Web/PublishedContentExtensions.cs index 75eb6adbcb..750ffa4be6 100644 --- a/src/Umbraco.Web/PublishedContentExtensions.cs +++ b/src/Umbraco.Web/PublishedContentExtensions.cs @@ -28,6 +28,20 @@ namespace Umbraco.Web private static UmbracoContext UmbracoContext => Current.UmbracoContext; private static ISiteDomainHelper SiteDomainHelper => Current.Factory.GetInstance(); + #region Creator/Writer Names + + public static string CreatorName(this IPublishedContent content, IUserService userService) + { + return userService.GetProfileById(content.CreatorId)?.Name; + } + + public static string WriterName(this IPublishedContent content, IUserService userService) + { + return userService.GetProfileById(content.WriterId)?.Name; + } + + #endregion + #region IsComposedOf /// @@ -673,7 +687,7 @@ namespace Umbraco.Web /// The specific culture to filter for. If null is used the current culture is used. (Default is null) /// /// - /// This can be useful in order to return all nodes in an entire site by a type when combined with TypedContentAtRoot + /// This can be useful in order to return all nodes in an entire site by a type when combined with or similar. /// public static IEnumerable DescendantsOrSelfOfType(this IEnumerable parentNodes, string docTypeAlias, string culture = null) { @@ -687,7 +701,7 @@ namespace Umbraco.Web /// The specific culture to filter for. If null is used the current culture is used. (Default is null) /// /// - /// This can be useful in order to return all nodes in an entire site by a type when combined with TypedContentAtRoot + /// This can be useful in order to return all nodes in an entire site by a type when combined with or similar. /// public static IEnumerable DescendantsOrSelf(this IEnumerable parentNodes, string culture = null) where T : class, IPublishedContent @@ -1193,7 +1207,7 @@ namespace Umbraco.Web /// if any. In addition, when the content type is multi-lingual, this is the url for the /// specified culture. Otherwise, it is the invariant url. /// - public static string Url(this IPublishedContent content, string culture = null, UrlMode mode = UrlMode.Auto) + public static string Url(this IPublishedContent content, string culture = null, UrlMode mode = UrlMode.Default) { var umbracoContext = Composing.Current.UmbracoContext; diff --git a/src/Umbraco.Web/Routing/PublishedContentNotFoundHandler.cs b/src/Umbraco.Web/Routing/PublishedContentNotFoundHandler.cs index ae8da1fc6c..e1f9022f27 100644 --- a/src/Umbraco.Web/Routing/PublishedContentNotFoundHandler.cs +++ b/src/Umbraco.Web/Routing/PublishedContentNotFoundHandler.cs @@ -37,9 +37,9 @@ namespace Umbraco.Web.Routing reason = "No template exists to render the document at url '{0}'."; response.Write("

    Page not found

    "); - response.Write("

    "); + response.Write("

    "); response.Write(string.Format(reason, HttpUtility.HtmlEncode(Current.UmbracoContext.OriginalRequestUrl.PathAndQuery))); - response.Write("

    "); + response.Write(""); if (string.IsNullOrWhiteSpace(_message) == false) response.Write("

    " + _message + "

    "); response.Write("

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

    "); diff --git a/src/Umbraco.Web/Routing/UrlProvider.cs b/src/Umbraco.Web/Routing/UrlProvider.cs index 59e39fa80a..d42639b781 100644 --- a/src/Umbraco.Web/Routing/UrlProvider.cs +++ b/src/Umbraco.Web/Routing/UrlProvider.cs @@ -75,6 +75,7 @@ namespace Umbraco.Web.Routing private UrlMode GetMode(bool absolute) => absolute ? UrlMode.Absolute : Mode; private IPublishedContent GetDocument(int id) => _umbracoContext.Content.GetById(id); private IPublishedContent GetDocument(Guid id) => _umbracoContext.Content.GetById(id); + private IPublishedContent GetMedia(Guid id) => _umbracoContext.Media.GetById(id); /// /// Gets the url of a published content. @@ -184,6 +185,18 @@ namespace Umbraco.Web.Routing #region GetMediaUrl + /// + /// Gets the url of a media item. + /// + /// + /// + /// + /// + /// + /// + public string GetMediaUrl(Guid id, UrlMode mode = UrlMode.Default, string culture = null, string propertyAlias = Constants.Conventions.Media.File, Uri current = null) + => GetMediaUrl(GetMedia(id), mode, culture, propertyAlias, current); + /// /// Gets the url of a media item. /// diff --git a/src/Umbraco.Web/Runtime/WebInitialComposer.cs b/src/Umbraco.Web/Runtime/WebInitialComposer.cs index 87c0f46fba..203bae4854 100644 --- a/src/Umbraco.Web/Runtime/WebInitialComposer.cs +++ b/src/Umbraco.Web/Runtime/WebInitialComposer.cs @@ -9,9 +9,7 @@ using Umbraco.Core.Dashboards; using Umbraco.Core.Dictionary; using Umbraco.Core.Events; using Umbraco.Core.Migrations.PostMigrations; -using Umbraco.Web.Migrations.PostMigrations; using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.PropertyEditors; using Umbraco.Core.PropertyEditors.ValueConverters; using Umbraco.Core.Runtime; using Umbraco.Core.Services; @@ -19,7 +17,6 @@ using Umbraco.Web.Actions; using Umbraco.Web.Cache; using Umbraco.Web.Composing.CompositionExtensions; using Umbraco.Web.ContentApps; -using Umbraco.Web.Dashboards; using Umbraco.Web.Dictionary; using Umbraco.Web.Editors; using Umbraco.Web.Features; @@ -37,10 +34,12 @@ using Umbraco.Web.Security.Providers; using Umbraco.Web.Services; using Umbraco.Web.SignalR; using Umbraco.Web.Templates; -using Umbraco.Web.Tour; using Umbraco.Web.Trees; using Umbraco.Web.WebApi; using Current = Umbraco.Web.Composing.Current; +using Umbraco.Web.PropertyEditors; +using Umbraco.Core.Models; +using Umbraco.Web.Models; namespace Umbraco.Web.Runtime { @@ -73,7 +72,7 @@ namespace Umbraco.Web.Runtime // register accessors for cultures composition.RegisterUnique(); composition.RegisterUnique(); - + // register the http context and umbraco context accessors // we *should* use the HttpContextUmbracoContextAccessor, however there are cases when // we have no http context, eg when booting Umbraco or in background threads, so instead @@ -95,7 +94,7 @@ namespace Umbraco.Web.Runtime // a way to inject the UmbracoContext - DO NOT register this as Lifetime.Request since LI will dispose the context // in it's own way but we don't want that to happen, we manage its lifetime ourselves. composition.Register(factory => factory.GetInstance().UmbracoContext); - + composition.RegisterUnique(); composition.Register(factory => { var umbCtx = factory.GetInstance(); @@ -107,6 +106,11 @@ namespace Umbraco.Web.Runtime composition.RegisterUnique(); composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + // register the umbraco helper - this is Transient! very important! // also, if not level.Run, we cannot really use the helper (during upgrade...) // so inject a "void" helper (not exactly pretty but...) @@ -144,19 +148,19 @@ namespace Umbraco.Web.Runtime .ComposeUmbracoControllers(GetType().Assembly) .SetDefaultRenderMvcController(); // default controller for template views - composition.WithCollectionBuilder() + composition.SearchableTrees() .Add(() => composition.TypeLoader.GetTypes()); composition.Register(Lifetime.Request); - composition.WithCollectionBuilder() + composition.EditorValidators() .Add(() => composition.TypeLoader.GetTypes()); - composition.WithCollectionBuilder(); + composition.TourFilters(); composition.RegisterUnique(); - composition.WithCollectionBuilder() + composition.Actions() .Add(() => composition.TypeLoader.GetTypes()); //we need to eagerly scan controller types since they will need to be routed @@ -171,26 +175,28 @@ namespace Umbraco.Web.Runtime // here because there cannot be two converters for one property editor - and we want the full // RteMacroRenderingValueConverter that converts macros, etc. So remove TinyMceValueConverter. // (the limited one, defined in Core, is there for tests) - same for others - composition.WithCollectionBuilder() + composition.PropertyValueConverters() .Remove() .Remove() .Remove(); - + // add all known factories, devs can then modify this list on application // startup either by binding to events or in their own global.asax - composition.WithCollectionBuilder() + composition.FilteredControllerFactory() .Append(); - composition.WithCollectionBuilder() + composition.UrlProviders() .Append() .Append(); - composition.WithCollectionBuilder() + composition.MediaUrlProviders() .Append(); + composition.RegisterUnique(); + composition.RegisterUnique(); - composition.WithCollectionBuilder() + composition.ContentFinders() // all built-in finders in the correct order, // devs can then modify this list on application startup .Append() @@ -205,7 +211,7 @@ namespace Umbraco.Web.Runtime composition.RegisterUnique(); // register *all* checks, except those marked [HideFromTypeFinder] of course - composition.WithCollectionBuilder() + composition.HealthChecks() .Add(() => composition.TypeLoader.GetTypes()); composition.WithCollectionBuilder() @@ -225,13 +231,13 @@ namespace Umbraco.Web.Runtime composition.RegisterUnique(); // register known content apps - composition.WithCollectionBuilder() + composition.ContentApps() .Append() .Append() .Append(); // register back office sections in the order we want them rendered - composition.WithCollectionBuilder() + composition.Sections() .Append() .Append() .Append() @@ -242,18 +248,18 @@ namespace Umbraco.Web.Runtime .Append(); // register core CMS dashboards and 3rd party types - will be ordered by weight attribute & merged with package.manifest dashboards - composition.WithCollectionBuilder() + composition.Dashboards() .Add(composition.TypeLoader.GetTypes()); // register back office trees // the collection builder only accepts types inheriting from TreeControllerBase // and will filter out those that are not attributed with TreeAttribute - composition.WithCollectionBuilder() + composition.Trees() .AddTreeControllers(umbracoApiControllerTypes.Where(x => typeof(TreeControllerBase).IsAssignableFrom(x))); // register OEmbed providers - no type scanning - all explicit opt-in of adding types // note: IEmbedProvider is not IDiscoverable - think about it if going for type scanning - composition.WithCollectionBuilder() + composition.OEmbedProviders() .Append() .Append() .Append() @@ -268,7 +274,7 @@ namespace Umbraco.Web.Runtime .Append() .Append() .Append(); - + // replace with web implementation composition.RegisterUnique(); diff --git a/src/Umbraco.Web/Search/ExamineComposer.cs b/src/Umbraco.Web/Search/ExamineComposer.cs index b30f0cbe03..64eeb6978a 100644 --- a/src/Umbraco.Web/Search/ExamineComposer.cs +++ b/src/Umbraco.Web/Search/ExamineComposer.cs @@ -4,6 +4,7 @@ using Umbraco.Core; using Umbraco.Core.Composing; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Scoping; using Umbraco.Core.Services; using Umbraco.Core.Strings; using Umbraco.Examine; @@ -36,12 +37,14 @@ namespace Umbraco.Web.Search factory.GetInstance(), factory.GetInstance(), factory.GetInstance(), + factory.GetInstance(), true)); composition.RegisterUnique(factory => new ContentValueSetBuilder( factory.GetInstance(), factory.GetInstance(), factory.GetInstance(), + factory.GetInstance(), false)); composition.RegisterUnique, MediaValueSetBuilder>(); composition.RegisterUnique, MemberValueSetBuilder>(); diff --git a/src/Umbraco.Web/Search/IUmbracoTreeSearcherFields.cs b/src/Umbraco.Web/Search/IUmbracoTreeSearcherFields.cs new file mode 100644 index 0000000000..c5a6c53d19 --- /dev/null +++ b/src/Umbraco.Web/Search/IUmbracoTreeSearcherFields.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; + +namespace Umbraco.Web.Search +{ + /// + /// Used to propagate hardcoded internal Field lists + /// + public interface IUmbracoTreeSearcherFields + { + /// + /// Propagate list of searchable fields for all node types + /// + IEnumerable GetBackOfficeFields(); + /// + /// Propagate list of searchable fields for Members + /// + IEnumerable GetBackOfficeMembersFields(); + /// + /// Propagate list of searchable fields for Media + /// + IEnumerable GetBackOfficeMediaFields(); + /// + /// Propagate list of searchable fields for Documents + /// + IEnumerable GetBackOfficeDocumentFields(); + } +} diff --git a/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs b/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs index 463f4b09df..dcc156e356 100644 --- a/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs +++ b/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs @@ -12,6 +12,7 @@ using Umbraco.Core.Persistence; using Umbraco.Core.Services; using Umbraco.Examine; using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web.Models.Mapping; using Umbraco.Web.Trees; namespace Umbraco.Web.Search @@ -28,13 +29,15 @@ namespace Umbraco.Web.Search private readonly IEntityService _entityService; private readonly UmbracoMapper _mapper; private readonly ISqlContext _sqlContext; + private readonly IUmbracoTreeSearcherFields _umbracoTreeSearcherFields; + public UmbracoTreeSearcher(IExamineManager examineManager, UmbracoContext umbracoContext, ILocalizationService languageService, IEntityService entityService, UmbracoMapper mapper, - ISqlContext sqlContext) + ISqlContext sqlContext,IUmbracoTreeSearcherFields umbracoTreeSearcherFields) { _examineManager = examineManager ?? throw new ArgumentNullException(nameof(examineManager)); _umbracoContext = umbracoContext; @@ -42,6 +45,7 @@ namespace Umbraco.Web.Search _entityService = entityService; _mapper = mapper; _sqlContext = sqlContext; + _umbracoTreeSearcherFields = umbracoTreeSearcherFields; } /// @@ -62,12 +66,22 @@ namespace Umbraco.Web.Search UmbracoEntityTypes entityType, int pageSize, long pageIndex, out long totalFound, string searchFrom = null, bool ignoreUserStartNodes = false) + { + return ExamineSearch(query, entityType, pageSize, pageIndex, culture: null, out totalFound, searchFrom, ignoreUserStartNodes); + } + + public IEnumerable ExamineSearch( + string query, + UmbracoEntityTypes entityType, + int pageSize, + long pageIndex, string culture, + out long totalFound, string searchFrom = null, bool ignoreUserStartNodes = false) { var sb = new StringBuilder(); string type; var indexName = Constants.UmbracoIndexes.InternalIndexName; - var fields = new List { "id", "__NodeId", "__Key" }; + var fields = _umbracoTreeSearcherFields.GetBackOfficeFields().ToList(); // TODO: WE should try to allow passing in a lucene raw query, however we will still need to do some manual string // manipulation for things like start paths, member types, etc... @@ -87,7 +101,7 @@ namespace Umbraco.Web.Search case UmbracoEntityTypes.Member: indexName = Constants.UmbracoIndexes.MembersIndexName; type = "member"; - fields.AddRange(new[]{ "email", "loginName"}); + fields.AddRange(_umbracoTreeSearcherFields.GetBackOfficeMembersFields()); if (searchFrom != null && searchFrom != Constants.Conventions.MemberTypes.AllMembersListId && searchFrom.Trim() != "-1") { sb.Append("+__NodeTypeAlias:"); @@ -97,12 +111,13 @@ namespace Umbraco.Web.Search break; case UmbracoEntityTypes.Media: type = "media"; - fields.AddRange(new[] { UmbracoExamineIndex.UmbracoFileFieldName }); + fields.AddRange(_umbracoTreeSearcherFields.GetBackOfficeMediaFields()); var allMediaStartNodes = _umbracoContext.Security.CurrentUser.CalculateMediaStartNodeIds(_entityService); AppendPath(sb, UmbracoObjectTypes.Media, allMediaStartNodes, searchFrom, ignoreUserStartNodes, _entityService); break; case UmbracoEntityTypes.Document: type = "content"; + fields.AddRange(_umbracoTreeSearcherFields.GetBackOfficeDocumentFields()); var allContentStartNodes = _umbracoContext.Security.CurrentUser.CalculateContentStartNodeIds(_entityService); AppendPath(sb, UmbracoObjectTypes.Document, allContentStartNodes, searchFrom, ignoreUserStartNodes, _entityService); break; @@ -136,7 +151,7 @@ namespace Umbraco.Web.Search case UmbracoEntityTypes.Media: return MediaFromSearchResults(pagedResult); case UmbracoEntityTypes.Document: - return ContentFromSearchResults(pagedResult); + return ContentFromSearchResults(pagedResult, culture); default: throw new NotSupportedException("The " + typeof(UmbracoTreeSearcher) + " currently does not support searching against object type " + entityType); } @@ -439,13 +454,17 @@ namespace Umbraco.Web.Search /// /// /// - private IEnumerable ContentFromSearchResults(IEnumerable results) + private IEnumerable ContentFromSearchResults(IEnumerable results, string culture = null) { var defaultLang = _languageService.GetDefaultLanguageIsoCode(); - foreach (var result in results) { - var entity = _mapper.Map(result); + var entity = _mapper.Map(result, context => { + if(culture != null) { + context.SetCulture(culture); + } + } + ); var intId = entity.Id.TryConvertTo(); if (intId.Success) @@ -453,7 +472,7 @@ namespace Umbraco.Web.Search //if it varies by culture, return the default language URL if (result.Values.TryGetValue(UmbracoContentIndex.VariesByCultureFieldName, out var varies) && varies == "y") { - entity.AdditionalData["Url"] = _umbracoContext.Url(intId.Result, defaultLang); + entity.AdditionalData["Url"] = _umbracoContext.Url(intId.Result, culture ?? defaultLang); } else { diff --git a/src/Umbraco.Web/Search/UmbracoTreeSearcherFields.cs b/src/Umbraco.Web/Search/UmbracoTreeSearcherFields.cs new file mode 100644 index 0000000000..f90d7bc6b6 --- /dev/null +++ b/src/Umbraco.Web/Search/UmbracoTreeSearcherFields.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using System.Linq; +using Umbraco.Examine; + +namespace Umbraco.Web.Search +{ + public class UmbracoTreeSearcherFields : IUmbracoTreeSearcherFields + { + private IReadOnlyList _backOfficeFields = new List {"id", "__NodeId", "__Key"}; + public IEnumerable GetBackOfficeFields() + { + return _backOfficeFields; + } + + + private IReadOnlyList _backOfficeMembersFields = new List {"email", "loginName"}; + public IEnumerable GetBackOfficeMembersFields() + { + return _backOfficeMembersFields; + } + private IReadOnlyList _backOfficeMediaFields = new List {UmbracoExamineIndex.UmbracoFileFieldName }; + public IEnumerable GetBackOfficeMediaFields() + { + return _backOfficeMediaFields; + } + public IEnumerable GetBackOfficeDocumentFields() + { + return Enumerable.Empty(); + } + } +} diff --git a/src/Umbraco.Web/Security/BackOfficeSignInManager.cs b/src/Umbraco.Web/Security/BackOfficeSignInManager.cs index b33487bc8d..5f1c1012f3 100644 --- a/src/Umbraco.Web/Security/BackOfficeSignInManager.cs +++ b/src/Umbraco.Web/Security/BackOfficeSignInManager.cs @@ -112,6 +112,21 @@ namespace Umbraco.Web.Security return SignInStatus.LockedOut; } + // We need to verify that the user belongs to one or more groups that define content and media start nodes. + // To do so we have to create the user claims identity and validate the calculated start nodes. + var userIdentity = await CreateUserIdentityAsync(user); + if (userIdentity is UmbracoBackOfficeIdentity backOfficeIdentity) + { + if (backOfficeIdentity.StartContentNodes.Length == 0 || backOfficeIdentity.StartMediaNodes.Length == 0) + { + _logger.WriteCore(TraceEventType.Information, 0, + $"Login attempt failed for username {userName} from IP address {_request.RemoteIpAddress}, no content and/or media start nodes could be found for any of the user's groups", null, null); + + // We will say its a sucessful login which it is, but they have no node access + return SignInStatus.Success; + } + } + await UserManager.ResetAccessFailedCountAsync(user.Id); return await SignInOrTwoFactor(user, isPersistent); } diff --git a/src/Umbraco.Web/Security/ExternalSignInAutoLinkOptions.cs b/src/Umbraco.Web/Security/ExternalSignInAutoLinkOptions.cs index 44fbdda6c7..2a9c3a5f07 100644 --- a/src/Umbraco.Web/Security/ExternalSignInAutoLinkOptions.cs +++ b/src/Umbraco.Web/Security/ExternalSignInAutoLinkOptions.cs @@ -23,7 +23,7 @@ namespace Umbraco.Web.Security string[] defaultUserGroups = null, string defaultCulture = null) { - _defaultUserGroups = defaultUserGroups ?? new[] { "editor" }; + _defaultUserGroups = defaultUserGroups ?? new[] { Constants.Security.EditorGroupAlias }; _autoLinkExternalAccount = autoLinkExternalAccount; _defaultCulture = defaultCulture ?? Current.Configs.Global().DefaultUILanguage; } diff --git a/src/Umbraco.Web/Security/Providers/UsersMembershipProvider.cs b/src/Umbraco.Web/Security/Providers/UsersMembershipProvider.cs index f3aa5dd838..10a293a3e7 100644 --- a/src/Umbraco.Web/Security/Providers/UsersMembershipProvider.cs +++ b/src/Umbraco.Web/Security/Providers/UsersMembershipProvider.cs @@ -32,7 +32,7 @@ namespace Umbraco.Web.Security.Providers } private readonly IMemberTypeService _memberTypeService; - private string _defaultMemberTypeAlias = "writer"; + private string _defaultMemberTypeAlias = Constants.Security.WriterGroupAlias; private volatile bool _hasDefaultMember = false; private static readonly object Locker = new object(); diff --git a/src/Umbraco.Web/Templates/HtmlImageSourceParser.cs b/src/Umbraco.Web/Templates/HtmlImageSourceParser.cs new file mode 100644 index 0000000000..539ce303fd --- /dev/null +++ b/src/Umbraco.Web/Templates/HtmlImageSourceParser.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using Umbraco.Core; + +namespace Umbraco.Web.Templates +{ + + public sealed class HtmlImageSourceParser + { + public HtmlImageSourceParser(Func getMediaUrl) + { + this._getMediaUrl = getMediaUrl; + } + + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + public HtmlImageSourceParser(IUmbracoContextAccessor umbracoContextAccessor) + { + _umbracoContextAccessor = umbracoContextAccessor; + } + + private static readonly Regex ResolveImgPattern = new Regex(@"(]*src="")([^""\?]*)((?:\?[^""]*)?""[^>]*data-udi="")([^""]*)(""[^>]*>)", + RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); + + private static readonly Regex DataUdiAttributeRegex = new Regex(@"data-udi=\\?(?:""|')(?umb://[A-z0-9\-]+/[A-z0-9]+)\\?(?:""|')", + RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase); + + private Func _getMediaUrl; + + /// + /// Parses out media UDIs from an html string based on 'data-udi' html attributes + /// + /// + /// + public IEnumerable FindUdisFromDataAttributes(string text) + { + var matches = DataUdiAttributeRegex.Matches(text); + if (matches.Count == 0) + yield break; + + foreach (Match match in matches) + { + if (match.Groups.Count == 2 && Udi.TryParse(match.Groups[1].Value, out var udi)) + yield return udi; + } + } + + /// + /// Parses the string looking for Umbraco image tags and updates them to their up-to-date image sources. + /// + /// + /// + /// Umbraco image tags are identified by their data-udi attributes + public string EnsureImageSources(string text) + { + if(_getMediaUrl == null) + _getMediaUrl = (guid) => _umbracoContextAccessor.UmbracoContext.UrlProvider.GetMediaUrl(guid); + + return ResolveImgPattern.Replace(text, match => + { + // match groups: + // - 1 = from the beginning of the image tag until src attribute value begins + // - 2 = the src attribute value excluding the querystring (if present) + // - 3 = anything after group 2 and before the data-udi attribute value begins + // - 4 = the data-udi attribute value + // - 5 = anything after group 4 until the image tag is closed + var udi = match.Groups[4].Value; + if (udi.IsNullOrWhiteSpace() || GuidUdi.TryParse(udi, out var guidUdi) == false) + { + return match.Value; + } + var mediaUrl = _getMediaUrl(guidUdi.Guid); + if (mediaUrl == null) + { + // image does not exist - we could choose to remove the image entirely here (return empty string), + // but that would leave the editors completely in the dark as to why the image doesn't show + return match.Value; + } + + return $"{match.Groups[1].Value}{mediaUrl}{match.Groups[3].Value}{udi}{match.Groups[5].Value}"; + }); + } + + /// + /// Removes media urls from <img> tags where a data-udi attribute is present + /// + /// + /// + public string RemoveImageSources(string text) + // see comment in ResolveMediaFromTextString for group reference + => ResolveImgPattern.Replace(text, "$1$3$4$5"); + } +} diff --git a/src/Umbraco.Web/Templates/HtmlLocalLinkParser.cs b/src/Umbraco.Web/Templates/HtmlLocalLinkParser.cs new file mode 100644 index 0000000000..f65a7183b7 --- /dev/null +++ b/src/Umbraco.Web/Templates/HtmlLocalLinkParser.cs @@ -0,0 +1,121 @@ +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Web.PublishedCache; +using Umbraco.Web.Routing; + +namespace Umbraco.Web.Templates +{ + /// + /// Utility class used to parse internal links + /// + public sealed class HtmlLocalLinkParser + { + + private static readonly Regex LocalLinkPattern = new Regex(@"href=""[/]?(?:\{|\%7B)localLink:([a-zA-Z0-9-://]+)(?:\}|\%7D)", + RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); + + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + + public HtmlLocalLinkParser(IUmbracoContextAccessor umbracoContextAccessor) + { + _umbracoContextAccessor = umbracoContextAccessor; + } + + internal IEnumerable FindUdisFromLocalLinks(string text) + { + foreach ((int? intId, GuidUdi udi, string tagValue) in FindLocalLinkIds(text)) + { + if (udi != null) + yield return udi; // In v8, we only care abuot UDIs + } + } + + /// + /// Parses the string looking for the {localLink} syntax and updates them to their correct links. + /// + /// + /// + /// + public string EnsureInternalLinks(string text, bool preview) + { + if (_umbracoContextAccessor.UmbracoContext == null) + throw new InvalidOperationException("Could not parse internal links, there is no current UmbracoContext"); + + if (!preview) + return EnsureInternalLinks(text); + + using (_umbracoContextAccessor.UmbracoContext.ForcedPreview(preview)) // force for url provider + { + return EnsureInternalLinks(text); + } + } + + /// + /// Parses the string looking for the {localLink} syntax and updates them to their correct links. + /// + /// + /// + /// + public string EnsureInternalLinks(string text) + { + if (_umbracoContextAccessor.UmbracoContext == null) + throw new InvalidOperationException("Could not parse internal links, there is no current UmbracoContext"); + + var urlProvider = _umbracoContextAccessor.UmbracoContext.UrlProvider; + + foreach((int? intId, GuidUdi udi, string tagValue) in FindLocalLinkIds(text)) + { + if (udi != null) + { + var newLink = "#"; + if (udi.EntityType == Constants.UdiEntityType.Document) + newLink = urlProvider.GetUrl(udi.Guid); + else if (udi.EntityType == Constants.UdiEntityType.Media) + newLink = urlProvider.GetMediaUrl(udi.Guid); + + if (newLink == null) + newLink = "#"; + + text = text.Replace(tagValue, "href=\"" + newLink); + } + else if (intId.HasValue) + { + var newLink = urlProvider.GetUrl(intId.Value); + text = text.Replace(tagValue, "href=\"" + newLink); + } + } + + return text; + } + + private IEnumerable<(int? intId, GuidUdi udi, string tagValue)> FindLocalLinkIds(string text) + { + // Parse internal links + var tags = LocalLinkPattern.Matches(text); + foreach (Match tag in tags) + { + if (tag.Groups.Count > 0) + { + var id = tag.Groups[1].Value; //.Remove(tag.Groups[1].Value.Length - 1, 1); + + //The id could be an int or a UDI + if (Udi.TryParse(id, out var udi)) + { + var guidUdi = udi as GuidUdi; + if (guidUdi != null) + yield return (null, guidUdi, tag.Value); + } + + if (int.TryParse(id, out var intId)) + { + yield return (intId, null, tag.Value); + } + } + } + + } + } +} diff --git a/src/Umbraco.Web/Templates/HtmlUrlParser.cs b/src/Umbraco.Web/Templates/HtmlUrlParser.cs new file mode 100644 index 0000000000..5b78477579 --- /dev/null +++ b/src/Umbraco.Web/Templates/HtmlUrlParser.cs @@ -0,0 +1,61 @@ +using System.Text.RegularExpressions; +using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; + +namespace Umbraco.Web.Templates +{ + public sealed class HtmlUrlParser + { + private readonly IContentSection _contentSection; + private readonly IProfilingLogger _logger; + + private static readonly Regex ResolveUrlPattern = new Regex("(=[\"\']?)(\\W?\\~(?:.(?![\"\']?\\s+(?:\\S+)=|[>\"\']))+.)[\"\']?", + RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); + + public HtmlUrlParser(IContentSection contentSection, IProfilingLogger logger) + { + _contentSection = contentSection; + _logger = logger; + } + + /// + /// The RegEx matches any HTML attribute values that start with a tilde (~), those that match are passed to ResolveUrl to replace the tilde with the application path. + /// + /// + /// + /// + /// When used with a Virtual-Directory set-up, this would resolve all URLs correctly. + /// The recommendation is that the "ResolveUrlsFromTextString" option (in umbracoSettings.config) is set to false for non-Virtual-Directory installs. + /// + public string EnsureUrls(string text) + { + if (_contentSection.ResolveUrlsFromTextString == false) return text; + + using (var timer = _logger.DebugDuration(typeof(IOHelper), "ResolveUrlsFromTextString starting", "ResolveUrlsFromTextString complete")) + { + // find all relative urls (ie. urls that contain ~) + var tags = ResolveUrlPattern.Matches(text); + _logger.Debug(typeof(IOHelper), "After regex: {Duration} matched: {TagsCount}", timer.Stopwatch.ElapsedMilliseconds, tags.Count); + foreach (Match tag in tags) + { + var url = ""; + if (tag.Groups[1].Success) + url = tag.Groups[1].Value; + + // The richtext editor inserts a slash in front of the url. That's why we need this little fix + // if (url.StartsWith("/")) + // text = text.Replace(url, ResolveUrl(url.Substring(1))); + // else + if (string.IsNullOrEmpty(url) == false) + { + var resolvedUrl = (url.Substring(0, 1) == "/") ? IOHelper.ResolveUrl(url.Substring(1)) : IOHelper.ResolveUrl(url); + text = text.Replace(url, resolvedUrl); + } + } + } + + return text; + } + } +} diff --git a/src/Umbraco.Web/Templates/TemplateUtilities.cs b/src/Umbraco.Web/Templates/TemplateUtilities.cs index 58d3ed341e..f08abfaf12 100644 --- a/src/Umbraco.Web/Templates/TemplateUtilities.cs +++ b/src/Umbraco.Web/Templates/TemplateUtilities.cs @@ -2,29 +2,24 @@ using System; using System.Collections.Generic; using System.IO; -using System.Text.RegularExpressions; using Umbraco.Core; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Services; using Umbraco.Web.Composing; +using Umbraco.Web.PropertyEditors; using Umbraco.Web.PublishedCache; using Umbraco.Web.Routing; using File = System.IO.File; namespace Umbraco.Web.Templates { - //NOTE: I realize there is only one class in this namespace but I'm pretty positive that there will be more classes in - //this namespace once we start migrating and cleaning up more code. - /// - /// Utility class used for templates - /// + [Obsolete("This class is obsolete, all methods have been moved to other classes: " + nameof(HtmlLocalLinkParser) + ", " + nameof(HtmlUrlParser) + " and " + nameof(HtmlImageSourceParser))] public static class TemplateUtilities { - const string TemporaryImageDataAttribute = "data-tmpimg"; - + [Obsolete("Inject and use an instance of " + nameof(HtmlLocalLinkParser) + " instead")] internal static string ParseInternalLinks(string text, bool preview, UmbracoContext umbracoContext) { using (umbracoContext.ForcedPreview(preview)) // force for url provider @@ -35,262 +30,28 @@ namespace Umbraco.Web.Templates return text; } - /// - /// Parses the string looking for the {localLink} syntax and updates them to their correct links. - /// - /// - /// - /// - public static string ParseInternalLinks(string text, UrlProvider urlProvider) => - ParseInternalLinks(text, urlProvider, Current.UmbracoContext.MediaCache); + [Obsolete("Inject and use an instance of " + nameof(HtmlLocalLinkParser) + " instead")] + public static string ParseInternalLinks(string text, UrlProvider urlProvider) + => Current.Factory.GetInstance().EnsureInternalLinks(text); - // TODO: Replace mediaCache with media url provider - internal static string ParseInternalLinks(string text, UrlProvider urlProvider, IPublishedMediaCache mediaCache) - { - if (urlProvider == null) throw new ArgumentNullException(nameof(urlProvider)); - if (mediaCache == null) throw new ArgumentNullException(nameof(mediaCache)); - - // Parse internal links - var tags = LocalLinkPattern.Matches(text); - foreach (Match tag in tags) - { - if (tag.Groups.Count > 0) - { - var id = tag.Groups[1].Value; //.Remove(tag.Groups[1].Value.Length - 1, 1); - - //The id could be an int or a UDI - if (Udi.TryParse(id, out var udi)) - { - var guidUdi = udi as GuidUdi; - if (guidUdi != null) - { - var newLink = "#"; - if (guidUdi.EntityType == Constants.UdiEntityType.Document) - newLink = urlProvider.GetUrl(guidUdi.Guid); - else if (guidUdi.EntityType == Constants.UdiEntityType.Media) - newLink = mediaCache.GetById(guidUdi.Guid)?.Url; - - if (newLink == null) - newLink = "#"; - - text = text.Replace(tag.Value, "href=\"" + newLink); - } - } - - if (int.TryParse(id, out var intId)) - { - var newLink = urlProvider.GetUrl(intId); - text = text.Replace(tag.Value, "href=\"" + newLink); - } - } - } - - return text; - } - - - // static compiled regex for faster performance - private static readonly Regex LocalLinkPattern = new Regex(@"href=""[/]?(?:\{|\%7B)localLink:([a-zA-Z0-9-://]+)(?:\}|\%7D)", - RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); - - private static readonly Regex ResolveUrlPattern = new Regex("(=[\"\']?)(\\W?\\~(?:.(?![\"\']?\\s+(?:\\S+)=|[>\"\']))+.)[\"\']?", - RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); - - private static readonly Regex ResolveImgPattern = new Regex(@"(]*src="")([^""\?]*)([^""]*""[^>]*data-udi="")([^""]*)(""[^>]*>)", - RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); - - /// - /// The RegEx matches any HTML attribute values that start with a tilde (~), those that match are passed to ResolveUrl to replace the tilde with the application path. - /// - /// - /// - /// - /// When used with a Virtual-Directory set-up, this would resolve all URLs correctly. - /// The recommendation is that the "ResolveUrlsFromTextString" option (in umbracoSettings.config) is set to false for non-Virtual-Directory installs. - /// + [Obsolete("Inject and use an instance of " + nameof(HtmlUrlParser))] public static string ResolveUrlsFromTextString(string text) - { - if (Current.Configs.Settings().Content.ResolveUrlsFromTextString == false) return text; - - using (var timer = Current.ProfilingLogger.DebugDuration(typeof(IOHelper), "ResolveUrlsFromTextString starting", "ResolveUrlsFromTextString complete")) - { - // find all relative urls (ie. urls that contain ~) - var tags = ResolveUrlPattern.Matches(text); - Current.Logger.Debug(typeof(IOHelper), "After regex: {Duration} matched: {TagsCount}", timer.Stopwatch.ElapsedMilliseconds, tags.Count); - foreach (Match tag in tags) - { - var url = ""; - if (tag.Groups[1].Success) - url = tag.Groups[1].Value; - - // The richtext editor inserts a slash in front of the url. That's why we need this little fix - // if (url.StartsWith("/")) - // text = text.Replace(url, ResolveUrl(url.Substring(1))); - // else - if (String.IsNullOrEmpty(url) == false) - { - var resolvedUrl = (url.Substring(0, 1) == "/") ? IOHelper.ResolveUrl(url.Substring(1)) : IOHelper.ResolveUrl(url); - text = text.Replace(url, resolvedUrl); - } - } - } - - return text; - } + => Current.Factory.GetInstance().EnsureUrls(text); + [Obsolete("Use " + nameof(StringExtensions) + "." + nameof(StringExtensions.CleanForXss) + " instead")] public static string CleanForXss(string text, params char[] ignoreFromClean) - { - return text.CleanForXss(ignoreFromClean); - } + => text.CleanForXss(ignoreFromClean); - /// - /// Parses the string looking for Umbraco image tags and updates them to their up-to-date image sources. - /// - /// - /// - /// Umbraco image tags are identified by their data-udi attributes + [Obsolete("Use " + nameof(HtmlImageSourceParser) + "." + nameof(HtmlImageSourceParser.EnsureImageSources) + " instead")] public static string ResolveMediaFromTextString(string text) - { - // don't attempt to proceed without a context - if (Current.UmbracoContext == null || Current.UmbracoContext.Media == null) - { - return text; - } - - return ResolveImgPattern.Replace(text, match => - { - // match groups: - // - 1 = from the beginning of the image tag until src attribute value begins - // - 2 = the src attribute value excluding the querystring (if present) - // - 3 = anything after group 2 and before the data-udi attribute value begins - // - 4 = the data-udi attribute value - // - 5 = anything after group 4 until the image tag is closed - var udi = match.Groups[4].Value; - if(udi.IsNullOrWhiteSpace() || GuidUdi.TryParse(udi, out var guidUdi) == false) - { - return match.Value; - } - var media = Current.UmbracoContext.Media.GetById(guidUdi.Guid); - if(media == null) - { - // image does not exist - we could choose to remove the image entirely here (return empty string), - // but that would leave the editors completely in the dark as to why the image doesn't show - return match.Value; - } - - var url = media.Url; - return $"{match.Groups[1].Value}{url}{match.Groups[3].Value}{udi}{match.Groups[5].Value}"; - }); - } - - /// - /// Removes media urls from <img> tags where a data-udi attribute is present - /// - /// - /// + => Current.Factory.GetInstance().EnsureImageSources(text); + + [Obsolete("Use " + nameof(HtmlImageSourceParser) + "." + nameof(HtmlImageSourceParser.RemoveImageSources) + " instead")] internal static string RemoveMediaUrlsFromTextString(string text) - // see comment in ResolveMediaFromTextString for group reference - => ResolveImgPattern.Replace(text, "$1$3$4$5"); + => Current.Factory.GetInstance().RemoveImageSources(text); + [Obsolete("Use " + nameof(HtmlImageSourceParser) + "." + nameof(RichTextEditorPastedImages.FindAndPersistPastedTempImages) + " instead")] internal static string FindAndPersistPastedTempImages(string html, Guid mediaParentFolder, int userId, IMediaService mediaService, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, ILogger logger) - { - // Find all img's that has data-tmpimg attribute - // Use HTML Agility Pack - https://html-agility-pack.net - var htmlDoc = new HtmlDocument(); - htmlDoc.LoadHtml(html); - - var tmpImages = htmlDoc.DocumentNode.SelectNodes($"//img[@{TemporaryImageDataAttribute}]"); - if (tmpImages == null || tmpImages.Count == 0) - return html; - - // An array to contain a list of URLs that - // we have already processed to avoid dupes - var uploadedImages = new Dictionary(); - - foreach (var img in tmpImages) - { - // The data attribute contains the path to the tmp img to persist as a media item - var tmpImgPath = img.GetAttributeValue(TemporaryImageDataAttribute, string.Empty); - - if (string.IsNullOrEmpty(tmpImgPath)) - continue; - - var absoluteTempImagePath = IOHelper.MapPath(tmpImgPath); - var fileName = Path.GetFileName(absoluteTempImagePath); - var safeFileName = fileName.ToSafeFileName(); - - var mediaItemName = safeFileName.ToFriendlyName(); - IMedia mediaFile; - GuidUdi udi; - - if (uploadedImages.ContainsKey(tmpImgPath) == false) - { - if (mediaParentFolder == Guid.Empty) - mediaFile = mediaService.CreateMedia(mediaItemName, Constants.System.Root, Constants.Conventions.MediaTypes.Image, userId); - else - mediaFile = mediaService.CreateMedia(mediaItemName, mediaParentFolder, Constants.Conventions.MediaTypes.Image, userId); - - var fileInfo = new FileInfo(absoluteTempImagePath); - - var fileStream = fileInfo.OpenReadWithRetry(); - if (fileStream == null) throw new InvalidOperationException("Could not acquire file stream"); - using (fileStream) - { - mediaFile.SetValue(contentTypeBaseServiceProvider, Constants.Conventions.Media.File, safeFileName, fileStream); - } - - mediaService.Save(mediaFile, userId); - - udi = mediaFile.GetUdi(); - } - else - { - // Already been uploaded & we have it's UDI - udi = uploadedImages[tmpImgPath]; - } - - // Add the UDI to the img element as new data attribute - img.SetAttributeValue("data-udi", udi.ToString()); - - // Get the new persisted image url - var mediaTyped = Current.UmbracoHelper.Media(udi.Guid); - var location = mediaTyped.Url; - - // Find the width & height attributes as we need to set the imageprocessor QueryString - var width = img.GetAttributeValue("width", int.MinValue); - var height = img.GetAttributeValue("height", int.MinValue); - - if(width != int.MinValue && height != int.MinValue) - { - location = $"{location}?width={width}&height={height}&mode=max"; - } - - img.SetAttributeValue("src", location); - - // Remove the data attribute (so we do not re-process this) - img.Attributes.Remove(TemporaryImageDataAttribute); - - // Add to the dictionary to avoid dupes - if(uploadedImages.ContainsKey(tmpImgPath) == false) - { - uploadedImages.Add(tmpImgPath, udi); - - // Delete folder & image now its saved in media - // The folder should contain one image - as a unique guid folder created - // for each image uploaded from TinyMceController - var folderName = Path.GetDirectoryName(absoluteTempImagePath); - try - { - Directory.Delete(folderName, true); - } - catch (Exception ex) - { - logger.Error(typeof(TemplateUtilities), ex, "Could not delete temp file or folder {FileName}", absoluteTempImagePath); - } - } - } - - return htmlDoc.DocumentNode.OuterHtml; - } + => Current.Factory.GetInstance().FindAndPersistPastedTempImages(html, mediaParentFolder, userId, Current.Factory.GetInstance()); } } diff --git a/src/Umbraco.Web/Trees/ContentBlueprintTreeController.cs b/src/Umbraco.Web/Trees/ContentBlueprintTreeController.cs index ac75fd831d..fd05f7cfbd 100644 --- a/src/Umbraco.Web/Trees/ContentBlueprintTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentBlueprintTreeController.cs @@ -47,7 +47,7 @@ namespace Umbraco.Web.Trees if (id == Constants.System.RootString) { //get all blueprint content types - var contentTypeAliases = entities.Select(x => ((ContentEntitySlim) x).ContentTypeAlias).Distinct(); + var contentTypeAliases = entities.Select(x => ((IContentEntitySlim) x).ContentTypeAlias).Distinct(); //get the ids var contentTypeIds = Services.ContentTypeService.GetAllContentTypeIds(contentTypeAliases.ToArray()).ToArray(); @@ -75,7 +75,7 @@ namespace Umbraco.Web.Trees var ct = Services.ContentTypeService.Get(intId.Result); if (ct == null) return nodes; - var blueprintsForDocType = entities.Where(x => ct.Alias == ((ContentEntitySlim) x).ContentTypeAlias); + var blueprintsForDocType = entities.Where(x => ct.Alias == ((IContentEntitySlim) x).ContentTypeAlias); nodes.AddRange(blueprintsForDocType .Select(entity => { diff --git a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs index 1069df0ec4..6d156e3fc8 100644 --- a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs @@ -364,7 +364,12 @@ namespace Umbraco.Web.Trees var startNodes = Services.EntityService.GetAll(UmbracoObjectType, UserStartNodes); //if any of these start nodes' parent is current, then we need to render children normally so we need to switch some logic and tell // the UI that this node does have children and that it isn't a container - if (startNodes.Any(x => x.ParentId == e.Id)) + + if (startNodes.Any(x => + { + var pathParts = x.Path.Split(','); + return pathParts.Contains(e.Id.ToInvariantString()); + })) { renderChildren = true; } diff --git a/src/Umbraco.Web/Trees/ContentTypeTreeController.cs b/src/Umbraco.Web/Trees/ContentTypeTreeController.cs index e8f8fb9f75..ece16229b2 100644 --- a/src/Umbraco.Web/Trees/ContentTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTypeTreeController.cs @@ -69,15 +69,18 @@ namespace Umbraco.Web.Trees .OrderBy(entity => entity.Name) .Select(dt => { + // get the content type here so we can get the icon from it to use when we create the tree node + // and we can enrich the result with content type data that's not available in the entity service output + var contentType = contentTypes[dt.Id]; + // since 7.4+ child type creation is enabled by a config option. It defaults to on, but can be disabled if we decide to. // need this check to keep supporting sites where children have already been created. var hasChildren = dt.HasChildren; - var node = CreateTreeNode(dt, Constants.ObjectTypes.DocumentType, id, queryStrings, Constants.Icons.ContentType, hasChildren); + var node = CreateTreeNode(dt, Constants.ObjectTypes.DocumentType, id, queryStrings, contentType?.Icon ?? Constants.Icons.ContentType, hasChildren); node.Path = dt.Path; - // enrich the result with content type data that's not available in the entity service output - var contentType = contentTypes[dt.Id]; + // now we can enrich the result with content type data that's not available in the entity service output node.Alias = contentType.Alias; node.AdditionalData["isElement"] = contentType.IsElement; diff --git a/src/Umbraco.Web/Trees/MediaTypeTreeController.cs b/src/Umbraco.Web/Trees/MediaTypeTreeController.cs index f85aefcace..7a9a80c8fc 100644 --- a/src/Umbraco.Web/Trees/MediaTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/MediaTypeTreeController.cs @@ -25,10 +25,12 @@ namespace Umbraco.Web.Trees public class MediaTypeTreeController : TreeController, ISearchableTree { private readonly UmbracoTreeSearcher _treeSearcher; + private readonly IMediaTypeService _mediaTypeService; - public MediaTypeTreeController(UmbracoTreeSearcher treeSearcher, IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper) : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper) + public MediaTypeTreeController(UmbracoTreeSearcher treeSearcher, IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper, IMediaTypeService mediaTypeService) : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper) { _treeSearcher = treeSearcher; + _mediaTypeService = mediaTypeService; } protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) @@ -54,6 +56,8 @@ namespace Umbraco.Web.Trees // if the request is for folders only then just return if (queryStrings["foldersonly"].IsNullOrWhiteSpace() == false && queryStrings["foldersonly"] == "1") return nodes; + var mediaTypes = _mediaTypeService.GetAll(); + nodes.AddRange( Services.EntityService.GetChildren(intId.Result, UmbracoObjectTypes.MediaType) .OrderBy(entity => entity.Name) @@ -62,7 +66,8 @@ namespace Umbraco.Web.Trees // since 7.4+ child type creation is enabled by a config option. It defaults to on, but can be disabled if we decide to. // need this check to keep supporting sites where children have already been created. var hasChildren = dt.HasChildren; - var node = CreateTreeNode(dt, Constants.ObjectTypes.MediaType, id, queryStrings, Constants.Icons.MediaType, hasChildren); + var mt = mediaTypes.FirstOrDefault(x => x.Id == dt.Id); + var node = CreateTreeNode(dt, Constants.ObjectTypes.MediaType, id, queryStrings, mt?.Icon ?? Constants.Icons.MediaType, hasChildren); node.Path = dt.Path; return node; @@ -120,7 +125,10 @@ namespace Umbraco.Web.Trees } menu.Items.Add(Services.TextService, opensDialog: true); - menu.Items.Add(Services.TextService, opensDialog: true); + if(ct.IsSystemMediaType() == false) + { + menu.Items.Add(Services.TextService, opensDialog: true); + } menu.Items.Add(new RefreshNode(Services.TextService, true)); } diff --git a/src/Umbraco.Web/Trees/MemberTypeTreeController.cs b/src/Umbraco.Web/Trees/MemberTypeTreeController.cs index 2046baf2d3..5db9088f20 100644 --- a/src/Umbraco.Web/Trees/MemberTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/MemberTypeTreeController.cs @@ -33,7 +33,7 @@ namespace Umbraco.Web.Trees { return Services.MemberTypeService.GetAll() .OrderBy(x => x.Name) - .Select(dt => CreateTreeNode(dt, Constants.ObjectTypes.MemberType, id, queryStrings, Constants.Icons.MemberType, false)); + .Select(dt => CreateTreeNode(dt, Constants.ObjectTypes.MemberType, id, queryStrings, dt?.Icon ?? Constants.Icons.MemberType, false)); } public IEnumerable Search(string query, int pageSize, long pageIndex, out long totalFound, string searchFrom = null) diff --git a/src/Umbraco.Web/Trees/RelationTypeTreeController.cs b/src/Umbraco.Web/Trees/RelationTypeTreeController.cs index ab6dd39820..aa3206b5e4 100644 --- a/src/Umbraco.Web/Trees/RelationTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/RelationTypeTreeController.cs @@ -16,6 +16,8 @@ namespace Umbraco.Web.Trees { protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) { + //TODO: Do not allow deleting built in types + var menu = new MenuItemCollection(); if (id == Constants.System.RootString) diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index caf91152fe..e39687bed8 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -61,7 +61,7 @@ - + @@ -150,11 +150,14 @@ + + + @@ -223,6 +226,7 @@ + @@ -234,6 +238,7 @@ + @@ -249,11 +254,16 @@ + + + + + @@ -1181,11 +1191,6 @@ - - True - True - Reference.map - @@ -1209,32 +1214,16 @@ - - - - + - - - - MSDiscoCodeGenerator - Reference.cs - Designer - Mvc\web.config - - Reference.map - - - Reference.map - SettingsSingleFileGenerator Settings.Designer.cs diff --git a/src/Umbraco.Web/UmbracoApplication.cs b/src/Umbraco.Web/UmbracoApplication.cs index 94403bc1be..f8ee238da7 100644 --- a/src/Umbraco.Web/UmbracoApplication.cs +++ b/src/Umbraco.Web/UmbracoApplication.cs @@ -1,7 +1,9 @@ -using System.Threading; +using System.Configuration; +using System.Threading; using System.Web; using Umbraco.Core; using Umbraco.Core.Logging.Serilog; +using Umbraco.Core.Runtime; using Umbraco.Web.Runtime; namespace Umbraco.Web @@ -14,7 +16,17 @@ namespace Umbraco.Web protected override IRuntime GetRuntime() { var logger = SerilogLogger.CreateWithDefaultConfiguration(); - return new WebRuntime(this, logger, new MainDom(logger)); + + // Determine if we should use the sql main dom or the default + var appSettingMainDomLock = ConfigurationManager.AppSettings[Constants.AppSettings.MainDomLock]; + + var mainDomLock = appSettingMainDomLock == "SqlMainDomLock" + ? (IMainDomLock)new SqlMainDomLock(logger) + : new MainDomSemaphoreLock(logger); + + var runtime = new WebRuntime(this, logger, new MainDom(logger, mainDomLock)); + + return runtime; } /// diff --git a/src/Umbraco.Web/UmbracoComponentRenderer.cs b/src/Umbraco.Web/UmbracoComponentRenderer.cs index a5890c9f97..0373c73724 100644 --- a/src/Umbraco.Web/UmbracoComponentRenderer.cs +++ b/src/Umbraco.Web/UmbracoComponentRenderer.cs @@ -27,12 +27,14 @@ namespace Umbraco.Web private readonly IUmbracoContextAccessor _umbracoContextAccessor; private readonly IMacroRenderer _macroRenderer; private readonly ITemplateRenderer _templateRenderer; + private readonly HtmlLocalLinkParser _linkParser; - public UmbracoComponentRenderer(IUmbracoContextAccessor umbracoContextAccessor, IMacroRenderer macroRenderer, ITemplateRenderer templateRenderer) + public UmbracoComponentRenderer(IUmbracoContextAccessor umbracoContextAccessor, IMacroRenderer macroRenderer, ITemplateRenderer templateRenderer, HtmlLocalLinkParser linkParser) { _umbracoContextAccessor = umbracoContextAccessor; _macroRenderer = macroRenderer; _templateRenderer = templateRenderer ?? throw new ArgumentNullException(nameof(templateRenderer)); + _linkParser = linkParser; } /// diff --git a/src/Umbraco.Web/UmbracoHelper.cs b/src/Umbraco.Web/UmbracoHelper.cs index 1bc8df5b24..6a2fba1152 100644 --- a/src/Umbraco.Web/UmbracoHelper.cs +++ b/src/Umbraco.Web/UmbracoHelper.cs @@ -70,24 +70,31 @@ namespace Umbraco.Web #endregion // ensures that we can return the specified value + [Obsolete("This method is only used in Obsolete properties")] T Ensure(T o) where T : class => o ?? throw new InvalidOperationException("This UmbracoHelper instance has not been initialized."); + [Obsolete("Inject and use an instance of " + nameof(IUmbracoComponentRenderer) + " in the constructor for using it in classes or get it from Current.UmbracoComponentRenderer in views.")] private IUmbracoComponentRenderer ComponentRenderer => Ensure(_componentRenderer); + + [Obsolete("Inject and use an instance of " + nameof(ICultureDictionaryFactory) + " in the constructor for using it in classes or get it from Current.CultureDictionaryFactory in views.")] private ICultureDictionaryFactory CultureDictionaryFactory => Ensure(_cultureDictionaryFactory); /// /// Gets the tag context. /// + [Obsolete("Inject and use an instance of " + nameof(ITagQuery) + " in the constructor for using it in classes or get it from Current.TagQuery in views.")] public ITagQuery TagQuery => Ensure(_tagQuery); /// /// Gets the query context. /// + [Obsolete("Inject and use an instance of " + nameof(IPublishedContentQuery) + " in the constructor for using it in classes or get it from Current.PublishedContentQuery in views")] public IPublishedContentQuery ContentQuery => Ensure(_publishedContentQuery); /// /// Gets the membership helper. /// + [Obsolete("Inject and use an instance of " + nameof(Security.MembershipHelper) + " in the constructor instead. In views you can use @Members.")] public MembershipHelper MembershipHelper => Ensure(_membershipHelper); /// diff --git a/src/Umbraco.Web/Web References/org.umbraco.update/Reference.cs b/src/Umbraco.Web/Web References/org.umbraco.update/Reference.cs deleted file mode 100644 index 230594fcbd..0000000000 --- a/src/Umbraco.Web/Web References/org.umbraco.update/Reference.cs +++ /dev/null @@ -1,286 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -// -// This source code was auto-generated by Microsoft.VSDesigner, Version 4.0.30319.42000. -// -#pragma warning disable 1591 - -namespace Umbraco.Web.org.umbraco.update { - using System; - using System.Web.Services; - using System.Diagnostics; - using System.Web.Services.Protocols; - using System.Xml.Serialization; - using System.ComponentModel; - - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.3062.0")] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - [System.Web.Services.WebServiceBindingAttribute(Name="CheckForUpgradeSoap", Namespace="http://update.umbraco.org/")] - public partial class CheckForUpgrade : System.Web.Services.Protocols.SoapHttpClientProtocol { - - private System.Threading.SendOrPostCallback InstallOperationCompleted; - - private System.Threading.SendOrPostCallback CheckUpgradeOperationCompleted; - - private bool useDefaultCredentialsSetExplicitly; - - /// - public CheckForUpgrade() { - this.Url = "http://update.umbraco.org/checkforupgrade.asmx"; - if ((this.IsLocalFileSystemWebService(this.Url) == true)) { - this.UseDefaultCredentials = true; - this.useDefaultCredentialsSetExplicitly = false; - } - else { - this.useDefaultCredentialsSetExplicitly = true; - } - } - - public new string Url { - get { - return base.Url; - } - set { - if ((((this.IsLocalFileSystemWebService(base.Url) == true) - && (this.useDefaultCredentialsSetExplicitly == false)) - && (this.IsLocalFileSystemWebService(value) == false))) { - base.UseDefaultCredentials = false; - } - base.Url = value; - } - } - - public new bool UseDefaultCredentials { - get { - return base.UseDefaultCredentials; - } - set { - base.UseDefaultCredentials = value; - this.useDefaultCredentialsSetExplicitly = true; - } - } - - /// - public event InstallCompletedEventHandler InstallCompleted; - - /// - public event CheckUpgradeCompletedEventHandler CheckUpgradeCompleted; - - /// - [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://update.umbraco.org/Install", RequestNamespace="http://update.umbraco.org/", ResponseNamespace="http://update.umbraco.org/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] - public void Install(System.Guid installId, bool isUpgrade, bool installCompleted, System.DateTime timestamp, int versionMajor, int versionMinor, int versionPatch, string versionComment, string error, string userAgent, string dbProvider) { - this.Invoke("Install", new object[] { - installId, - isUpgrade, - installCompleted, - timestamp, - versionMajor, - versionMinor, - versionPatch, - versionComment, - error, - userAgent, - dbProvider}); - } - - /// - public void InstallAsync(System.Guid installId, bool isUpgrade, bool installCompleted, System.DateTime timestamp, int versionMajor, int versionMinor, int versionPatch, string versionComment, string error, string userAgent, string dbProvider) { - this.InstallAsync(installId, isUpgrade, installCompleted, timestamp, versionMajor, versionMinor, versionPatch, versionComment, error, userAgent, dbProvider, null); - } - - /// - public void InstallAsync(System.Guid installId, bool isUpgrade, bool installCompleted, System.DateTime timestamp, int versionMajor, int versionMinor, int versionPatch, string versionComment, string error, string userAgent, string dbProvider, object userState) { - if ((this.InstallOperationCompleted == null)) { - this.InstallOperationCompleted = new System.Threading.SendOrPostCallback(this.OnInstallOperationCompleted); - } - this.InvokeAsync("Install", new object[] { - installId, - isUpgrade, - installCompleted, - timestamp, - versionMajor, - versionMinor, - versionPatch, - versionComment, - error, - userAgent, - dbProvider}, this.InstallOperationCompleted, userState); - } - - private void OnInstallOperationCompleted(object arg) { - if ((this.InstallCompleted != null)) { - System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); - this.InstallCompleted(this, new System.ComponentModel.AsyncCompletedEventArgs(invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); - } - } - - /// - [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://update.umbraco.org/CheckUpgrade", RequestNamespace="http://update.umbraco.org/", ResponseNamespace="http://update.umbraco.org/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] - public UpgradeResult CheckUpgrade(int versionMajor, int versionMinor, int versionPatch, string versionComment) { - object[] results = this.Invoke("CheckUpgrade", new object[] { - versionMajor, - versionMinor, - versionPatch, - versionComment}); - return ((UpgradeResult)(results[0])); - } - - /// - public void CheckUpgradeAsync(int versionMajor, int versionMinor, int versionPatch, string versionComment) { - this.CheckUpgradeAsync(versionMajor, versionMinor, versionPatch, versionComment, null); - } - - /// - public void CheckUpgradeAsync(int versionMajor, int versionMinor, int versionPatch, string versionComment, object userState) { - if ((this.CheckUpgradeOperationCompleted == null)) { - this.CheckUpgradeOperationCompleted = new System.Threading.SendOrPostCallback(this.OnCheckUpgradeOperationCompleted); - } - this.InvokeAsync("CheckUpgrade", new object[] { - versionMajor, - versionMinor, - versionPatch, - versionComment}, this.CheckUpgradeOperationCompleted, userState); - } - - private void OnCheckUpgradeOperationCompleted(object arg) { - if ((this.CheckUpgradeCompleted != null)) { - System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); - this.CheckUpgradeCompleted(this, new CheckUpgradeCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); - } - } - - /// - public new void CancelAsync(object userState) { - base.CancelAsync(userState); - } - - private bool IsLocalFileSystemWebService(string url) { - if (((url == null) - || (url == string.Empty))) { - return false; - } - System.Uri wsUri = new System.Uri(url); - if (((wsUri.Port >= 1024) - && (string.Compare(wsUri.Host, "localHost", System.StringComparison.OrdinalIgnoreCase) == 0))) { - return true; - } - return false; - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.7.3062.0")] - [System.SerializableAttribute()] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://update.umbraco.org/")] - public partial class UpgradeResult { - - private string commentField; - - private UpgradeType upgradeTypeField; - - private string upgradeUrlField; - - /// - public string Comment { - get { - return this.commentField; - } - set { - this.commentField = value; - } - } - - /// - public UpgradeType UpgradeType { - get { - return this.upgradeTypeField; - } - set { - this.upgradeTypeField = value; - } - } - - /// - public string UpgradeUrl { - get { - return this.upgradeUrlField; - } - set { - this.upgradeUrlField = value; - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.7.3062.0")] - [System.SerializableAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://update.umbraco.org/")] - public enum UpgradeType { - - /// - None, - - /// - Patch, - - /// - Minor, - - /// - Major, - - /// - Critical, - - /// - Error, - - /// - OutOfSync, - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.3062.0")] - public delegate void InstallCompletedEventHandler(object sender, System.ComponentModel.AsyncCompletedEventArgs e); - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.3062.0")] - public delegate void CheckUpgradeCompletedEventHandler(object sender, CheckUpgradeCompletedEventArgs e); - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.3062.0")] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - public partial class CheckUpgradeCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { - - private object[] results; - - internal CheckUpgradeCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : - base(exception, cancelled, userState) { - this.results = results; - } - - /// - public UpgradeResult Result { - get { - this.RaiseExceptionIfNecessary(); - return ((UpgradeResult)(this.results[0])); - } - } - } -} - -#pragma warning restore 1591 \ No newline at end of file diff --git a/src/Umbraco.Web/Web References/org.umbraco.update/Reference.map b/src/Umbraco.Web/Web References/org.umbraco.update/Reference.map deleted file mode 100644 index ded6650264..0000000000 --- a/src/Umbraco.Web/Web References/org.umbraco.update/Reference.map +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/src/Umbraco.Web/Web References/org.umbraco.update/UpgradeResult.datasource b/src/Umbraco.Web/Web References/org.umbraco.update/UpgradeResult.datasource deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/Umbraco.Web/Web References/org.umbraco.update/UpgradeResult1.datasource b/src/Umbraco.Web/Web References/org.umbraco.update/UpgradeResult1.datasource deleted file mode 100644 index c42c98b0b7..0000000000 --- a/src/Umbraco.Web/Web References/org.umbraco.update/UpgradeResult1.datasource +++ /dev/null @@ -1,10 +0,0 @@ - - - - umbraco.presentation.org.umbraco.update.UpgradeResult, Web References.org.umbraco.update.Reference.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null - \ No newline at end of file diff --git a/src/Umbraco.Web/Web References/org.umbraco.update/checkforupgrade.disco b/src/Umbraco.Web/Web References/org.umbraco.update/checkforupgrade.disco deleted file mode 100644 index 366f4fdd6e..0000000000 --- a/src/Umbraco.Web/Web References/org.umbraco.update/checkforupgrade.disco +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/Umbraco.Web/Web References/org.umbraco.update/checkforupgrade.wsdl b/src/Umbraco.Web/Web References/org.umbraco.update/checkforupgrade.wsdl deleted file mode 100644 index e2aba65c7c..0000000000 --- a/src/Umbraco.Web/Web References/org.umbraco.update/checkforupgrade.wsdl +++ /dev/null @@ -1,142 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/umbraco.sln b/src/umbraco.sln index ba9df633bb..a747f21d19 100644 --- a/src/umbraco.sln +++ b/src/umbraco.sln @@ -104,6 +104,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "IssueTemplates", "IssueTemp EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.ModelsBuilder.Embedded", "Umbraco.ModelsBuilder.Embedded\Umbraco.ModelsBuilder.Embedded.csproj", "{52AC0BA8-A60E-4E36-897B-E8B97A54ED1C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.TestData", "Umbraco.TestData\Umbraco.TestData.csproj", "{FB5676ED-7A69-492C-B802-E7B24144C0FC}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -140,6 +142,10 @@ Global {52AC0BA8-A60E-4E36-897B-E8B97A54ED1C}.Debug|Any CPU.Build.0 = Debug|Any CPU {52AC0BA8-A60E-4E36-897B-E8B97A54ED1C}.Release|Any CPU.ActiveCfg = Release|Any CPU {52AC0BA8-A60E-4E36-897B-E8B97A54ED1C}.Release|Any CPU.Build.0 = Release|Any CPU + {FB5676ED-7A69-492C-B802-E7B24144C0FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FB5676ED-7A69-492C-B802-E7B24144C0FC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FB5676ED-7A69-492C-B802-E7B24144C0FC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FB5676ED-7A69-492C-B802-E7B24144C0FC}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -152,6 +158,7 @@ Global {53594E5B-64A2-4545-8367-E3627D266AE8} = {FD962632-184C-4005-A5F3-E705D92FC645} {3A33ADC9-C6C0-4DB1-A613-A9AF0210DF3D} = {B5BD12C1-A454-435E-8A46-FF4A364C0382} {C7311C00-2184-409B-B506-52A5FAEA8736} = {FD962632-184C-4005-A5F3-E705D92FC645} + {FB5676ED-7A69-492C-B802-E7B24144C0FC} = {B5BD12C1-A454-435E-8A46-FF4A364C0382} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {7A0F2E34-D2AF-4DAB-86A0-7D7764B3D0EC}