From 83b4b6a5f4d84f5e57cdbb55f8d4970df1af28a4 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Sat, 8 Jun 2019 22:50:56 +0200 Subject: [PATCH 001/548] Annotate each crop with "Automatic" or "User defined" --- .../src/less/property-editors.less | 16 ++++++++++++++-- .../imagecropper/imagecropper.controller.js | 6 +++++- .../imagecropper/imagecropper.html | 2 ++ src/Umbraco.Web.UI/Umbraco/config/lang/da.xml | 2 ++ src/Umbraco.Web.UI/Umbraco/config/lang/en.xml | 2 ++ src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml | 8 +++++--- 6 files changed, 30 insertions(+), 6 deletions(-) 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 9e8dd37ab9..3ff8432f07 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -710,7 +710,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; @@ -722,17 +723,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 // -------------------------------------------------- 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..3a8e6db8d8 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 @@ -13,7 +13,7 @@ angular.module('umbraco') $scope.clear = clear; $scope.reset = reset; $scope.close = close; - $scope.focalPointChanged = focalPointChanged; + $scope.isCustomCrop = isCustomCrop; //declare a special method which will be called whenever the value has changed from the server $scope.model.onValueChanged = onValueChanged; @@ -201,6 +201,10 @@ angular.module('umbraco') $scope.imageCropperForm.$setDirty(); }; + function isCustomCrop(crop) { + return !!crop.coordinates; + } + var unsubscribe = $scope.$on("formSubmitting", function () { $scope.currentCrop = null; $scope.currentPoint = null; 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 7438fa04da..5604ac553d 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 @@ -58,6 +58,8 @@
{{value.alias}} {{value.width}}px x {{value.height}}px + User defined + Automatic
diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml index 9e6bdc5e57..b70f0f07af 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml @@ -1019,6 +1019,8 @@ Mange hilsner fra Umbraco robotten Nulstil Acceptér Fortryd + Automatisk + Brugerdefineret Vælg en version at sammenligne med den nuværende version diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index 189bd9f10b..4bf77a48d6 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -1273,6 +1273,8 @@ To manage your website, simply open the Umbraco back office and start adding con Add new crop Done Undo edits + Automatic + User defined Select a version to compare with the current version 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 6ce6f82ccc..010fe7fb4a 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -1274,11 +1274,13 @@ 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 + Automatic + User defined Select a version to compare with the current version From 6425592cc587b072129fe7bfbf520bb18c635a83 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Sun, 9 Jun 2019 12:11:45 +0200 Subject: [PATCH 002/548] Only show "custom crop" text ("automatic crop" is implicit) --- .../src/views/propertyeditors/imagecropper/imagecropper.html | 3 +-- src/Umbraco.Web.UI/Umbraco/config/lang/da.xml | 1 - src/Umbraco.Web.UI/Umbraco/config/lang/en.xml | 1 - src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml | 1 - 4 files changed, 1 insertion(+), 5 deletions(-) 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 5604ac553d..e2428cec05 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 @@ -58,8 +58,7 @@
{{value.alias}} {{value.width}}px x {{value.height}}px - User defined - Automatic + User defined 
diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml index b70f0f07af..15ee17ca3a 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml @@ -1019,7 +1019,6 @@ Mange hilsner fra Umbraco robotten Nulstil Acceptér Fortryd - Automatisk Brugerdefineret diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index 4bf77a48d6..1035848a1c 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -1273,7 +1273,6 @@ To manage your website, simply open the Umbraco back office and start adding con Add new crop Done Undo edits - Automatic User defined 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 010fe7fb4a..35d89d283f 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -1279,7 +1279,6 @@ To manage your website, simply open the Umbraco back office and start adding con Add new crop Done Undo edits - Automatic User defined From 201c91d3a8e90409a0b949249b8cc9b853835d50 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Sun, 9 Jun 2019 12:32:27 +0200 Subject: [PATCH 003/548] Whoops, accidental removal of focalPointChanged --- .../propertyeditors/imagecropper/imagecropper.controller.js | 1 + 1 file changed, 1 insertion(+) 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 3a8e6db8d8..e3576426a3 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 @@ -14,6 +14,7 @@ angular.module('umbraco') $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; From 355a5e0eb490173a10c72681bbbbe2cf565bdaa2 Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 24 Jun 2019 10:32:52 +0200 Subject: [PATCH 004/548] Remove ModelsBuilder NuGet dependency, add project --- .../Api/ApiBasicAuthFilter.cs | 86 --- src/Umbraco.ModelsBuilder/Api/ApiClient.cs | 159 ----- src/Umbraco.ModelsBuilder/Api/ApiHelper.cs | 27 - src/Umbraco.ModelsBuilder/Api/ApiVersion.cs | 88 --- .../Api/GetModelsData.cs | 17 - .../Api/ModelsBuilderApiController.cs | 85 --- src/Umbraco.ModelsBuilder/Api/TokenData.cs | 17 - .../Api/ValidateClientVersionData.cs | 57 -- src/Umbraco.ModelsBuilder/Building/Builder.cs | 322 ---------- .../Building/CodeDomBuilder.cs | 113 ---- .../Building/CodeParser.cs | 238 ------- .../Building/Compiler.cs | 171 ----- .../Building/CompilerException.cs | 25 - .../Building/ParseResult.cs | 275 -------- .../Building/PropertyModel.cs | 66 -- .../Building/TextBuilder.cs | 554 ---------------- .../Building/TextHeaderWriter.cs | 26 - .../Building/TypeModel.cs | 208 ------ .../Configuration/ClrNameSource.cs | 28 - .../Configuration/Config.cs | 357 ----------- .../Configuration/ModelsMode.cs | 52 -- .../Configuration/ModelsModeExtensions.cs | 51 -- .../Configuration/UmbracoConfigExtensions.cs | 34 - .../Dashboard/BuilderDashboardHelper.cs | 91 --- .../EnumerableExtensions.cs | 33 - .../IgnoreContentTypeAttribute.cs | 46 -- .../IgnorePropertyTypeAttribute.cs | 19 - .../ImplementContentTypeAttribute.cs | 20 - .../ImplementPropertyTypeAttribute.cs | 23 - .../ModelsBaseClassAttribute.cs | 16 - .../ModelsBuilderAssemblyAttribute.cs | 23 - .../ModelsNamespaceAttribute.cs | 16 - .../ModelsUsingAttribute.cs | 21 - .../Properties/AssemblyInfo.cs | 34 +- .../PublishedElementExtensions.cs | 48 -- .../PublishedPropertyTypeExtensions.cs | 20 - .../PureLiveAssemblyAttribute.cs | 15 - .../ReferencedAssemblies.cs | 137 ---- .../RenameContentTypeAttribute.cs | 18 - .../RenamePropertyTypeAttribute.cs | 18 - src/Umbraco.ModelsBuilder/TypeExtensions.cs | 22 - .../Umbraco.ModelsBuilder.csproj | 75 +-- .../Umbraco/HashCombiner.cs | 38 -- .../Umbraco/HashHelper.cs | 45 -- .../Umbraco/LiveModelsProvider.cs | 142 ----- .../ModelsBuilderBackOfficeController.cs | 194 ------ .../Umbraco/ModelsBuilderComponent.cs | 178 ------ .../Umbraco/ModelsGenerationError.cs | 60 -- .../Umbraco/OutOfDateModelsStatus.cs | 58 -- .../Umbraco/PublishedModelUtility.cs | 67 -- .../Umbraco/PureLiveModelFactory.cs | 602 ------------------ .../Umbraco/UmbracoServices.cs | 236 ------- .../Validation/ContentTypeModelValidator.cs | 95 --- src/umbraco.sln | 10 +- 54 files changed, 38 insertions(+), 5438 deletions(-) delete mode 100644 src/Umbraco.ModelsBuilder/Api/ApiBasicAuthFilter.cs delete mode 100644 src/Umbraco.ModelsBuilder/Api/ApiClient.cs delete mode 100644 src/Umbraco.ModelsBuilder/Api/ApiHelper.cs delete mode 100644 src/Umbraco.ModelsBuilder/Api/ApiVersion.cs delete mode 100644 src/Umbraco.ModelsBuilder/Api/GetModelsData.cs delete mode 100644 src/Umbraco.ModelsBuilder/Api/ModelsBuilderApiController.cs delete mode 100644 src/Umbraco.ModelsBuilder/Api/TokenData.cs delete mode 100644 src/Umbraco.ModelsBuilder/Api/ValidateClientVersionData.cs delete mode 100644 src/Umbraco.ModelsBuilder/Building/Builder.cs delete mode 100644 src/Umbraco.ModelsBuilder/Building/CodeDomBuilder.cs delete mode 100644 src/Umbraco.ModelsBuilder/Building/CodeParser.cs delete mode 100644 src/Umbraco.ModelsBuilder/Building/Compiler.cs delete mode 100644 src/Umbraco.ModelsBuilder/Building/CompilerException.cs delete mode 100644 src/Umbraco.ModelsBuilder/Building/ParseResult.cs delete mode 100644 src/Umbraco.ModelsBuilder/Building/PropertyModel.cs delete mode 100644 src/Umbraco.ModelsBuilder/Building/TextBuilder.cs delete mode 100644 src/Umbraco.ModelsBuilder/Building/TextHeaderWriter.cs delete mode 100644 src/Umbraco.ModelsBuilder/Building/TypeModel.cs delete mode 100644 src/Umbraco.ModelsBuilder/Configuration/ClrNameSource.cs delete mode 100644 src/Umbraco.ModelsBuilder/Configuration/Config.cs delete mode 100644 src/Umbraco.ModelsBuilder/Configuration/ModelsMode.cs delete mode 100644 src/Umbraco.ModelsBuilder/Configuration/ModelsModeExtensions.cs delete mode 100644 src/Umbraco.ModelsBuilder/Configuration/UmbracoConfigExtensions.cs delete mode 100644 src/Umbraco.ModelsBuilder/Dashboard/BuilderDashboardHelper.cs delete mode 100644 src/Umbraco.ModelsBuilder/EnumerableExtensions.cs delete mode 100644 src/Umbraco.ModelsBuilder/IgnoreContentTypeAttribute.cs delete mode 100644 src/Umbraco.ModelsBuilder/IgnorePropertyTypeAttribute.cs delete mode 100644 src/Umbraco.ModelsBuilder/ImplementContentTypeAttribute.cs delete mode 100644 src/Umbraco.ModelsBuilder/ImplementPropertyTypeAttribute.cs delete mode 100644 src/Umbraco.ModelsBuilder/ModelsBaseClassAttribute.cs delete mode 100644 src/Umbraco.ModelsBuilder/ModelsBuilderAssemblyAttribute.cs delete mode 100644 src/Umbraco.ModelsBuilder/ModelsNamespaceAttribute.cs delete mode 100644 src/Umbraco.ModelsBuilder/ModelsUsingAttribute.cs delete mode 100644 src/Umbraco.ModelsBuilder/PublishedElementExtensions.cs delete mode 100644 src/Umbraco.ModelsBuilder/PublishedPropertyTypeExtensions.cs delete mode 100644 src/Umbraco.ModelsBuilder/PureLiveAssemblyAttribute.cs delete mode 100644 src/Umbraco.ModelsBuilder/ReferencedAssemblies.cs delete mode 100644 src/Umbraco.ModelsBuilder/RenameContentTypeAttribute.cs delete mode 100644 src/Umbraco.ModelsBuilder/RenamePropertyTypeAttribute.cs delete mode 100644 src/Umbraco.ModelsBuilder/TypeExtensions.cs delete mode 100644 src/Umbraco.ModelsBuilder/Umbraco/HashCombiner.cs delete mode 100644 src/Umbraco.ModelsBuilder/Umbraco/HashHelper.cs delete mode 100644 src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProvider.cs delete mode 100644 src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderBackOfficeController.cs delete mode 100644 src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderComponent.cs delete mode 100644 src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerationError.cs delete mode 100644 src/Umbraco.ModelsBuilder/Umbraco/OutOfDateModelsStatus.cs delete mode 100644 src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs delete mode 100644 src/Umbraco.ModelsBuilder/Umbraco/PureLiveModelFactory.cs delete mode 100644 src/Umbraco.ModelsBuilder/Umbraco/UmbracoServices.cs delete mode 100644 src/Umbraco.ModelsBuilder/Validation/ContentTypeModelValidator.cs diff --git a/src/Umbraco.ModelsBuilder/Api/ApiBasicAuthFilter.cs b/src/Umbraco.ModelsBuilder/Api/ApiBasicAuthFilter.cs deleted file mode 100644 index cc862ff207..0000000000 --- a/src/Umbraco.ModelsBuilder/Api/ApiBasicAuthFilter.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Text; -using System.Web.Http.Controllers; -using System.Web.Security; -using Umbraco.Core; -using Umbraco.Core.Composing; -using Umbraco.Core.Configuration; -using Umbraco.Core.Models.Membership; - -namespace Umbraco.ModelsBuilder.Api -{ - - //TODO: This needs to be changed: - // * Authentication cannot happen in a filter, only Authorization - // * The filter must be an AuthorizationFilter, not an ActionFilter - // * Authorization must be done using the Umbraco logic - it is very specific for claim checking for ASP.Net Identity - // * Theoretically this shouldn't be required whatsoever because when we authenticate a request that has Basic Auth (i.e. for - // VS to work, it will add the correct Claims to the Identity and it will automatically be authorized. - // - // we *do* have POC supporting ASP.NET identity, however they require some config on the server - // we'll keep using this quick-and-dirty method for the time being - - public class ApiBasicAuthFilter : System.Web.Http.Filters.ActionFilterAttribute // use the http one, not mvc, with api controllers! - { - private static readonly char[] Separator = ":".ToCharArray(); - private readonly string _section; - - public ApiBasicAuthFilter(string section) - { - _section = section; - } - - public override void OnActionExecuting(HttpActionContext actionContext) - { - try - { - var user = Authenticate(actionContext.Request); - if (user == null || !user.AllowedSections.Contains(_section)) - { - actionContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized); - } - //else - //{ - // // note - would that be a proper way to pass data to the controller? - // // see http://stevescodingblog.co.uk/basic-authentication-with-asp-net-webapi/ - // actionContext.ControllerContext.RouteData.Values["umbraco-user"] = user; - //} - - base.OnActionExecuting(actionContext); - } - catch - { - actionContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized); - } - } - - private static IUser Authenticate(HttpRequestMessage request) - { - var ah = request.Headers.Authorization; - if (ah == null || ah.Scheme != "Basic") - return null; - - var token = ah.Parameter; - var credentials = Encoding.ASCII - .GetString(Convert.FromBase64String(token)) - .Split(Separator); - if (credentials.Length != 2) - return null; - - var username = ApiClient.DecodeTokenElement(credentials[0]); - var password = ApiClient.DecodeTokenElement(credentials[1]); - - var providerKey = UmbracoConfig.For.UmbracoSettings().Providers.DefaultBackOfficeUserProvider; - var provider = Membership.Providers[providerKey]; - if (provider == null || !provider.ValidateUser(username, password)) - return null; - var user = Current.Services.UserService.GetByUsername(username); - if (!user.IsApproved || user.IsLockedOut) - return null; - return user; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.ModelsBuilder/Api/ApiClient.cs b/src/Umbraco.ModelsBuilder/Api/ApiClient.cs deleted file mode 100644 index dde3641b97..0000000000 --- a/src/Umbraco.ModelsBuilder/Api/ApiClient.cs +++ /dev/null @@ -1,159 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net.Http; -using System.Net.Http.Formatting; -using System.Net.Http.Headers; -using System.Text; - -namespace Umbraco.ModelsBuilder.Api -{ - public class ApiClient - { - private readonly string _url; - private readonly string _user; - private readonly string _password; - - private readonly JsonMediaTypeFormatter _formatter; - private readonly MediaTypeFormatter[] _formatters; - - // fixme hardcoded? - // could be options - but we cannot "discover" them as the API client runs outside of the web app - // in addition, anything that references the controller forces API clients to reference Umbraco.Core - private const string ApiControllerUrl = "/Umbraco/BackOffice/ModelsBuilder/ModelsBuilderApi/"; - - public ApiClient(string url, string user, string password) - { - _url = url.TrimEnd('/'); - _user = user; - _password = password; - - _formatter = new JsonMediaTypeFormatter(); - _formatters = new MediaTypeFormatter[] { _formatter }; - } - - private void SetBaseAddress(HttpClient client, string url) - { - try - { - client.BaseAddress = new Uri(url); - } - catch - { - throw new UriFormatException($"Invalid URI: the format of the URI \"{url}\" could not be determined."); - } - } - - public void ValidateClientVersion() - { - // FIXME - add proxys support - - var hch = new HttpClientHandler(); - - using (var client = new HttpClient(hch)) - { - SetBaseAddress(client, _url); - Authorize(client); - - var data = new ValidateClientVersionData - { - ClientVersion = ApiVersion.Current.Version, - MinServerVersionSupportingClient = ApiVersion.Current.MinServerVersionSupportingClient, - }; - - var result = client.PostAsync(_url + ApiControllerUrl + nameof(ModelsBuilderApiController.ValidateClientVersion), - data, _formatter).Result; - - // this is not providing enough details in case of an error - do our own reporting - //result.EnsureSuccessStatusCode(); - EnsureSuccess(result); - } - } - - public IDictionary GetModels(Dictionary ourFiles, string modelsNamespace) - { - // FIXME - add proxys support - - var hch = new HttpClientHandler(); - - //hch.Proxy = new WebProxy("path.to.proxy", 8888); - //hch.UseProxy = true; - - using (var client = new HttpClient(hch)) - { - SetBaseAddress(client, _url); - Authorize(client); - - var data = new GetModelsData - { - Namespace = modelsNamespace, - ClientVersion = ApiVersion.Current.Version, - MinServerVersionSupportingClient = ApiVersion.Current.MinServerVersionSupportingClient, - Files = ourFiles - }; - - var result = client.PostAsync(_url + ApiControllerUrl + nameof(ModelsBuilderApiController.GetModels), - data, _formatter).Result; - - // this is not providing enough details in case of an error - do our own reporting - //result.EnsureSuccessStatusCode(); - EnsureSuccess(result); - - var genFiles = result.Content.ReadAsAsync>(_formatters).Result; - return genFiles; - } - } - - private static void EnsureSuccess(HttpResponseMessage result) - { - if (result.IsSuccessStatusCode) return; - - var text = result.Content.ReadAsStringAsync().Result; - throw new Exception($"Response status code does not indicate success ({result.StatusCode})\n{text}"); - } - - private void Authorize(HttpClient client) - { - AuthorizeBasic(client); - } - - // fixme - for the time being, we don't cache the token and we auth on each API call - // not used at the moment - /* - private void AuthorizeIdentity(HttpClient client) - { - var formData = new FormUrlEncodedContent(new[] - { - new KeyValuePair("grant_type", "password"), - new KeyValuePair("userName", _user), - new KeyValuePair("password", _password), - }); - - var result = client.PostAsync(_url + UmbracoOAuthTokenUrl, formData).Result; - - EnsureSuccess(result); - - var token = result.Content.ReadAsAsync(_formatters).Result; - if (token.TokenType != "bearer") - throw new Exception($"Received invalid token type \"{token.TokenType}\", expected \"bearer\"."); - - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.AccessToken); - } - */ - - private void AuthorizeBasic(HttpClient client) - { - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", - Convert.ToBase64String(Encoding.UTF8.GetBytes(EncodeTokenElement(_user) + ':' + EncodeTokenElement(_password)))); - } - - public static string EncodeTokenElement(string s) - { - return s.Replace("%", "%a").Replace(":", "%b"); - } - - public static string DecodeTokenElement(string s) - { - return s.Replace("%b", ":").Replace("%a", "%"); - } - } -} diff --git a/src/Umbraco.ModelsBuilder/Api/ApiHelper.cs b/src/Umbraco.ModelsBuilder/Api/ApiHelper.cs deleted file mode 100644 index fa6492fe3f..0000000000 --- a/src/Umbraco.ModelsBuilder/Api/ApiHelper.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Collections.Generic; -using System.Text; -using Umbraco.ModelsBuilder.Building; -using Umbraco.ModelsBuilder.Umbraco; - -namespace Umbraco.ModelsBuilder.Api -{ - internal static class ApiHelper - { - public static Dictionary GetModels(UmbracoServices umbracoServices, string modelsNamespace, IDictionary files) - { - var typeModels = umbracoServices.GetAllTypes(); - - var parseResult = new CodeParser().ParseWithReferencedAssemblies(files); - var builder = new TextBuilder(typeModels, parseResult, modelsNamespace); - - var models = new Dictionary(); - foreach (var typeModel in builder.GetModelsToGenerate()) - { - var sb = new StringBuilder(); - builder.Generate(sb, typeModel); - models[typeModel.ClrName] = sb.ToString(); - } - return models; - } - } -} diff --git a/src/Umbraco.ModelsBuilder/Api/ApiVersion.cs b/src/Umbraco.ModelsBuilder/Api/ApiVersion.cs deleted file mode 100644 index 2ee64b8c54..0000000000 --- a/src/Umbraco.ModelsBuilder/Api/ApiVersion.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System; -using System.Reflection; - -namespace Umbraco.ModelsBuilder.Api -{ - /// - /// Manages API version handshake between client and server. - /// - public class ApiVersion - { - #region Configure - - // indicate the minimum version of the client API that is supported by this server's API. - // eg our Version = 4.8 but we support connections from VSIX down to version 3.2 - // => as a server, we accept connections from client down to version ... - private static readonly Version MinClientVersionSupportedByServerConst = new Version(3, 0, 0, 0); - - // indicate the minimum version of the server that can support the client API - // eg our Version = 4.8 and we know we're compatible with website server down to version 3.2 - // => as a client, we tell the server down to version ... that it should accept us - private static readonly Version MinServerVersionSupportingClientConst = new Version(3, 0, 0, 0); - - #endregion - - /// - /// Initializes a new instance of the class. - /// - /// The currently executing version. - /// The min client version supported by the server. - /// An opt min server version supporting the client. - internal ApiVersion(Version executingVersion, Version minClientVersionSupportedByServer, Version minServerVersionSupportingClient = null) - { - if (executingVersion == null) throw new ArgumentNullException(nameof(executingVersion)); - if (minClientVersionSupportedByServer == null) throw new ArgumentNullException(nameof(minClientVersionSupportedByServer)); - - Version = executingVersion; - MinClientVersionSupportedByServer = minClientVersionSupportedByServer; - MinServerVersionSupportingClient = minServerVersionSupportingClient; - } - - /// - /// Gets the currently executing API version. - /// - public static ApiVersion Current { get; } - = new ApiVersion(Assembly.GetExecutingAssembly().GetName().Version, - MinClientVersionSupportedByServerConst, MinServerVersionSupportingClientConst); - - /// - /// Gets the executing version of the API. - /// - public Version Version { get; } - - /// - /// Gets the min client version supported by the server. - /// - public Version MinClientVersionSupportedByServer { get; } - - /// - /// Gets the min server version supporting the client. - /// - public Version MinServerVersionSupportingClient { get; } - - /// - /// Gets a value indicating whether the API server is compatible with a client. - /// - /// The client version. - /// An opt min server version supporting the client. - /// - /// A client is compatible with a server if the client version is greater-or-equal _minClientVersionSupportedByServer - /// (ie client can be older than server, up to a point) AND the client version is lower-or-equal the server version - /// (ie client cannot be more recent than server) UNLESS the server . - /// - public bool IsCompatibleWith(Version clientVersion, Version minServerVersionSupportingClient = null) - { - // client cannot be older than server's min supported version - if (clientVersion < MinClientVersionSupportedByServer) - return false; - - // if we know about this client (client is older than server), it is supported - if (clientVersion <= Version) // if we know about this client (client older than server) - return true; - - // if we don't know about this client (client is newer than server), - // give server a chance to tell client it is, indeed, ok to support it - return minServerVersionSupportingClient != null && minServerVersionSupportingClient <= Version; - } - } -} diff --git a/src/Umbraco.ModelsBuilder/Api/GetModelsData.cs b/src/Umbraco.ModelsBuilder/Api/GetModelsData.cs deleted file mode 100644 index 9a5c55afc2..0000000000 --- a/src/Umbraco.ModelsBuilder/Api/GetModelsData.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Collections.Generic; -using System.Runtime.Serialization; - -namespace Umbraco.ModelsBuilder.Api -{ - [DataContract] - public class GetModelsData : ValidateClientVersionData - { - [DataMember] - public string Namespace { get; set; } - - [DataMember] - public IDictionary Files { get; set; } - - public override bool IsValid => base.IsValid && Files != null; - } -} \ No newline at end of file diff --git a/src/Umbraco.ModelsBuilder/Api/ModelsBuilderApiController.cs b/src/Umbraco.ModelsBuilder/Api/ModelsBuilderApiController.cs deleted file mode 100644 index 444910b069..0000000000 --- a/src/Umbraco.ModelsBuilder/Api/ModelsBuilderApiController.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System; -using System.Net; -using System.Net.Http; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.ModelsBuilder.Configuration; -using Umbraco.ModelsBuilder.Umbraco; -using Umbraco.Web.Mvc; -using Umbraco.Web.WebApi; - -namespace Umbraco.ModelsBuilder.Api -{ - // read http://umbraco.com/follow-us/blog-archive/2014/1/17/heads-up,-breaking-change-coming-in-702-and-62.aspx - // read http://our.umbraco.org/forum/developers/api-questions/43025-Web-API-authentication - // UmbracoAuthorizedApiController :: /Umbraco/BackOffice/Zbu/ModelsBuilderApi/GetTypeModels - // UmbracoApiController :: /Umbraco/Zbu/ModelsBuilderApi/GetTypeModels ?? UNLESS marked with isbackoffice - // - // BEWARE! the controller url is hard-coded in ModelsBuilderApi and needs to be in sync! - - [PluginController(ControllerArea)] - [IsBackOffice] - //[UmbracoApplicationAuthorize(Constants.Applications.Developer)] // see ApiBasicAuthFilter - that one would be for ASP.NET identity - public class ModelsBuilderApiController : UmbracoApiController // UmbracoAuthorizedApiController - for ASP.NET identity - { - public const string ControllerArea = "ModelsBuilder"; - - private readonly UmbracoServices _umbracoServices; - - public ModelsBuilderApiController(UmbracoServices umbracoServices) - { - _umbracoServices = umbracoServices; - } - - // invoked by the API - [System.Web.Http.HttpPost] // use the http one, not mvc, with api controllers! - [ApiBasicAuthFilter("developer")] // have to use our own, non-cookie-based, auth - public HttpResponseMessage ValidateClientVersion(ValidateClientVersionData data) - { - if (!UmbracoConfig.For.ModelsBuilder().ApiServer) - return Request.CreateResponse(HttpStatusCode.Forbidden, "API server does not want to talk to you."); - - if (!ModelState.IsValid || data == null || !data.IsValid) - return Request.CreateResponse(HttpStatusCode.BadRequest, "Invalid data."); - - var checkResult = CheckVersion(data.ClientVersion, data.MinServerVersionSupportingClient); - return (checkResult.Success - ? Request.CreateResponse(HttpStatusCode.OK, "OK", Configuration.Formatters.JsonFormatter) - : checkResult.Result); - } - - // invoked by the API - [System.Web.Http.HttpPost] // use the http one, not mvc, with api controllers! - [ApiBasicAuthFilter("developer")] // have to use our own, non-cookie-based, auth - public HttpResponseMessage GetModels(GetModelsData data) - { - if (!UmbracoConfig.For.ModelsBuilder().ApiServer) - return Request.CreateResponse(HttpStatusCode.Forbidden, "API server does not want to talk to you."); - - if (!ModelState.IsValid || data == null || !data.IsValid) - return Request.CreateResponse(HttpStatusCode.BadRequest, "Invalid data."); - - var checkResult = CheckVersion(data.ClientVersion, data.MinServerVersionSupportingClient); - if (!checkResult.Success) - return checkResult.Result; - - var models = ApiHelper.GetModels(_umbracoServices, data.Namespace, data.Files); - - return Request.CreateResponse(HttpStatusCode.OK, models, Configuration.Formatters.JsonFormatter); - } - - private Attempt CheckVersion(Version clientVersion, Version minServerVersionSupportingClient) - { - if (clientVersion == null) - return Attempt.Fail(Request.CreateResponse(HttpStatusCode.Forbidden, - $"API version conflict: client version () is not compatible with server version({ApiVersion.Current.Version}).")); - - // minServerVersionSupportingClient can be null - var isOk = ApiVersion.Current.IsCompatibleWith(clientVersion, minServerVersionSupportingClient); - var response = isOk ? null : Request.CreateResponse(HttpStatusCode.Forbidden, - $"API version conflict: client version ({clientVersion}) is not compatible with server version({ApiVersion.Current.Version})."); - - return Attempt.If(isOk, response); - } - } -} diff --git a/src/Umbraco.ModelsBuilder/Api/TokenData.cs b/src/Umbraco.ModelsBuilder/Api/TokenData.cs deleted file mode 100644 index c34a6c75c5..0000000000 --- a/src/Umbraco.ModelsBuilder/Api/TokenData.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Runtime.Serialization; - -namespace Umbraco.ModelsBuilder.Api -{ - [DataContract] - class TokenData - { - [DataMember(Name = "access_token")] - public string AccessToken { get; set; } - - [DataMember(Name = "token_type")] - public string TokenType { get; set; } - - [DataMember(Name = "expires_in")] - public int ExpiresIn { get; set; } - } -} diff --git a/src/Umbraco.ModelsBuilder/Api/ValidateClientVersionData.cs b/src/Umbraco.ModelsBuilder/Api/ValidateClientVersionData.cs deleted file mode 100644 index 39ef08d816..0000000000 --- a/src/Umbraco.ModelsBuilder/Api/ValidateClientVersionData.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System; -using System.Runtime.Serialization; - -namespace Umbraco.ModelsBuilder.Api -{ - [DataContract] - public class ValidateClientVersionData - { - // issues 32, 34... problems when serializing versions - // - // make sure System.Version objects are transfered as strings - // depending on the JSON serializer version, it looks like versions are causing issues - // see - // http://stackoverflow.com/questions/13170386/why-system-version-in-json-string-does-not-deserialize-correctly - // - // if the class is marked with [DataContract] then only properties marked with [DataMember] - // are serialized and the rest is ignored, see - // http://www.asp.net/web-api/overview/formats-and-model-binding/json-and-xml-serialization - - [DataMember] - public string ClientVersionString - { - get { return VersionToString(ClientVersion); } - set { ClientVersion = ParseVersion(value, false, "client"); } - } - - [DataMember] - public string MinServerVersionSupportingClientString - { - get { return VersionToString(MinServerVersionSupportingClient); } - set { MinServerVersionSupportingClient = ParseVersion(value, true, "minServer"); } - } - - // not serialized - public Version ClientVersion { get; set; } - public Version MinServerVersionSupportingClient { get; set; } - - private static string VersionToString(Version version) - { - return version?.ToString() ?? "0.0.0.0"; - } - - private static Version ParseVersion(string value, bool canBeNull, string name) - { - if (string.IsNullOrWhiteSpace(value) && canBeNull) - return null; - - Version version; - if (Version.TryParse(value, out version)) - return version; - - throw new ArgumentException($"Failed to parse \"{value}\" as {name} version."); - } - - public virtual bool IsValid => ClientVersion != null; - } -} \ No newline at end of file diff --git a/src/Umbraco.ModelsBuilder/Building/Builder.cs b/src/Umbraco.ModelsBuilder/Building/Builder.cs deleted file mode 100644 index acfa402355..0000000000 --- a/src/Umbraco.ModelsBuilder/Building/Builder.cs +++ /dev/null @@ -1,322 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Umbraco.Core.Configuration; -using Umbraco.ModelsBuilder.Configuration; - -namespace Umbraco.ModelsBuilder.Building -{ - // NOTE - // The idea was to have different types of builder, because I wanted to experiment with - // building code with CodeDom. Turns out more complicated than I thought and maybe not - // worth it at the moment, to we're using TextBuilder and its Generate method is specific. - // - // Keeping the code as-is for the time being... - - /// - /// Provides a base class for all builders. - /// - internal abstract class Builder - { - private readonly IList _typeModels; - - protected Dictionary ModelsMap { get; } = new Dictionary(); - protected ParseResult ParseResult { get; } - - // the list of assemblies that will be 'using' by default - protected readonly IList TypesUsing = new List - { - "System", - "System.Collections.Generic", - "System.Linq.Expressions", - "System.Web", - "Umbraco.Core.Models", - "Umbraco.Core.Models.PublishedContent", - "Umbraco.Web", - "Umbraco.ModelsBuilder", - "Umbraco.ModelsBuilder.Umbraco", - }; - - /// - /// Gets or sets a value indicating the namespace to use for the models. - /// - /// May be overriden by code attributes. - public string ModelsNamespace { get; set; } - - /// - /// Gets the list of assemblies to add to the set of 'using' assemblies in each model file. - /// - public IList Using => TypesUsing; - - /// - /// Gets the list of models to generate. - /// - /// The models to generate, ie those that are not ignored. - public IEnumerable GetModelsToGenerate() - { - return _typeModels.Where(x => !x.IsContentIgnored); - } - - /// - /// Gets the list of all models. - /// - /// Includes those that are ignored. - internal IList TypeModels => _typeModels; - - /// - /// Initializes a new instance of the class with a list of models to generate - /// and the result of code parsing. - /// - /// The list of models to generate. - /// The result of code parsing. - protected Builder(IList typeModels, ParseResult parseResult) - { - _typeModels = typeModels ?? throw new ArgumentNullException(nameof(typeModels)); - ParseResult = parseResult ?? throw new ArgumentNullException(nameof(parseResult)); - - Prepare(); - } - - /// - /// Initializes a new instance of the class with a list of models to generate, - /// the result of code parsing, and a models namespace. - /// - /// The list of models to generate. - /// The result of code parsing. - /// The models namespace. - protected Builder(IList typeModels, ParseResult parseResult, string modelsNamespace) - : this(typeModels, parseResult) - { - // can be null or empty, we'll manage - ModelsNamespace = modelsNamespace; - } - - // for unit tests only - protected Builder() - { } - - /// - /// Prepares generation by processing the result of code parsing. - /// - /// - /// Preparation includes figuring out from the existing code which models or properties should - /// be ignored or renamed, etc. -- anything that comes from the attributes in the existing code. - /// - private void Prepare() - { - var pureLive = UmbracoConfig.For.ModelsBuilder().ModelsMode == ModelsMode.PureLive; - - // mark IsContentIgnored models that we discovered should be ignored - // then propagate / ignore children of ignored contents - // ignore content = don't generate a class for it, don't generate children - foreach (var typeModel in _typeModels.Where(x => ParseResult.IsIgnored(x.Alias))) - typeModel.IsContentIgnored = true; - foreach (var typeModel in _typeModels.Where(x => !x.IsContentIgnored && x.EnumerateBaseTypes().Any(xx => xx.IsContentIgnored))) - typeModel.IsContentIgnored = true; - - // handle model renames - foreach (var typeModel in _typeModels.Where(x => ParseResult.IsContentRenamed(x.Alias))) - { - typeModel.ClrName = ParseResult.ContentClrName(typeModel.Alias); - typeModel.IsRenamed = true; - ModelsMap[typeModel.Alias] = typeModel.ClrName; - } - - // handle implement - foreach (var typeModel in _typeModels.Where(x => ParseResult.HasContentImplement(x.Alias))) - { - typeModel.HasImplement = true; - } - - // mark OmitBase models that we discovered already have a base class - foreach (var typeModel in _typeModels.Where(x => ParseResult.HasContentBase(ParseResult.ContentClrName(x.Alias) ?? x.ClrName))) - typeModel.HasBase = true; - - foreach (var typeModel in _typeModels) - { - // mark IsRemoved properties that we discovered should be ignored - // ie is marked as ignored on type, or on any parent type - var tm = typeModel; - foreach (var property in typeModel.Properties - .Where(property => tm.EnumerateBaseTypes(true).Any(x => ParseResult.IsPropertyIgnored(ParseResult.ContentClrName(x.Alias) ?? x.ClrName, property.Alias)))) - { - property.IsIgnored = true; - } - - // handle property renames - foreach (var property in typeModel.Properties) - property.ClrName = ParseResult.PropertyClrName(ParseResult.ContentClrName(typeModel.Alias) ?? typeModel.ClrName, property.Alias) ?? property.ClrName; - } - - // for the first two of these two tests, - // always throw, even in purelive: cannot happen unless ppl start fidling with attributes to rename - // things, and then they should pay attention to the generation error log - there's no magic here - // for the last one, don't throw in purelive, see comment - - // ensure we have no duplicates type names - foreach (var xx in _typeModels.Where(x => !x.IsContentIgnored).GroupBy(x => x.ClrName).Where(x => x.Count() > 1)) - throw new InvalidOperationException($"Type name \"{xx.Key}\" is used" - + $" for types with alias {string.Join(", ", xx.Select(x => x.ItemType + ":\"" + x.Alias + "\""))}. Names have to be unique." - + " Consider using an attribute to assign different names to conflicting types."); - - // ensure we have no duplicates property names - foreach (var typeModel in _typeModels.Where(x => !x.IsContentIgnored)) - foreach (var xx in typeModel.Properties.Where(x => !x.IsIgnored).GroupBy(x => x.ClrName).Where(x => x.Count() > 1)) - throw new InvalidOperationException($"Property name \"{xx.Key}\" in type {typeModel.ItemType}:\"{typeModel.Alias}\"" - + $" is used for properties with alias {string.Join(", ", xx.Select(x => "\"" + x.Alias + "\""))}. Names have to be unique." - + " Consider using an attribute to assign different names to conflicting properties."); - - // ensure content & property type don't have identical name (csharp hates it) - foreach (var typeModel in _typeModels.Where(x => !x.IsContentIgnored)) - { - foreach (var xx in typeModel.Properties.Where(x => !x.IsIgnored && x.ClrName == typeModel.ClrName)) - { - if (!pureLive) - throw new InvalidOperationException($"The model class for content type with alias \"{typeModel.Alias}\" is named \"{xx.ClrName}\"." - + $" CSharp does not support using the same name for the property with alias \"{xx.Alias}\"." - + " Consider using an attribute to assign a different name to the property."); - - // for purelive, will we generate a commented out properties with an error message, - // instead of throwing, because then it kills the sites and ppl don't understand why - xx.AddError($"The class {typeModel.ClrName} cannot implement this property, because" - + $" CSharp does not support naming the property with alias \"{xx.Alias}\" with the same name as content type with alias \"{typeModel.Alias}\"." - + " Consider using an attribute to assign a different name to the property."); - - // will not be implemented on interface nor class - // note: we will still create the static getter, and implement the property on other classes... - } - } - - // ensure we have no collision between base types - // NO: we may want to define a base class in a partial, on a model that has a parent - // we are NOT checking that the defined base type does maintain the inheritance chain - //foreach (var xx in _typeModels.Where(x => !x.IsContentIgnored).Where(x => x.BaseType != null && x.HasBase)) - // throw new InvalidOperationException(string.Format("Type alias \"{0}\" has more than one parent class.", - // xx.Alias)); - - // discover interfaces that need to be declared / implemented - foreach (var typeModel in _typeModels) - { - // collect all the (non-removed) types implemented at parent level - // ie the parent content types and the mixins content types, recursively - var parentImplems = new List(); - if (typeModel.BaseType != null && !typeModel.BaseType.IsContentIgnored) - TypeModel.CollectImplems(parentImplems, typeModel.BaseType); - - // interfaces we must declare we implement (initially empty) - // ie this type's mixins, except those that have been removed, - // and except those that are already declared at the parent level - // in other words, DeclaringInterfaces is "local mixins" - var declaring = typeModel.MixinTypes - .Where(x => !x.IsContentIgnored) - .Except(parentImplems); - typeModel.DeclaringInterfaces.AddRange(declaring); - - // interfaces we must actually implement (initially empty) - // if we declare we implement a mixin interface, we must actually implement - // its properties, all recursively (ie if the mixin interface implements...) - // so, starting with local mixins, we collect all the (non-removed) types above them - var mixinImplems = new List(); - foreach (var i in typeModel.DeclaringInterfaces) - TypeModel.CollectImplems(mixinImplems, i); - // and then we remove from that list anything that is already declared at the parent level - typeModel.ImplementingInterfaces.AddRange(mixinImplems.Except(parentImplems)); - } - - // register using types - foreach (var usingNamespace in ParseResult.UsingNamespaces) - { - if (!TypesUsing.Contains(usingNamespace)) - TypesUsing.Add(usingNamespace); - } - - // discover static mixin methods - foreach (var typeModel in _typeModels) - typeModel.StaticMixinMethods.AddRange(ParseResult.StaticMixinMethods(typeModel.ClrName)); - - // handle ctor - foreach (var typeModel in _typeModels.Where(x => ParseResult.HasCtor(x.ClrName))) - typeModel.HasCtor = true; - } - - private SemanticModel _ambiguousSymbolsModel; - private int _ambiguousSymbolsPos; - - // internal for tests - internal void PrepareAmbiguousSymbols() - { - var codeBuilder = new StringBuilder(); - foreach (var t in TypesUsing) - codeBuilder.AppendFormat("using {0};\n", t); - - codeBuilder.AppendFormat("namespace {0}\n{{ }}\n", GetModelsNamespace()); - - var compiler = new Compiler(); - SyntaxTree[] trees; - var compilation = compiler.GetCompilation("MyCompilation", new Dictionary { { "code", codeBuilder.ToString() } }, out trees); - var tree = trees[0]; - _ambiguousSymbolsModel = compilation.GetSemanticModel(tree); - - var namespaceSyntax = tree.GetRoot().DescendantNodes().OfType().First(); - //var namespaceSymbol = model.GetDeclaredSymbol(namespaceSyntax); - _ambiguousSymbolsPos = namespaceSyntax.OpenBraceToken.SpanStart; - } - - // looking for a simple symbol eg 'Umbraco' or 'String' - // expecting to match eg 'Umbraco' or 'System.String' - // returns true if either - // - more than 1 symbol is found (explicitely ambiguous) - // - 1 symbol is found BUT not matching (implicitely ambiguous) - protected bool IsAmbiguousSymbol(string symbol, string match) - { - if (_ambiguousSymbolsModel == null) - PrepareAmbiguousSymbols(); - if (_ambiguousSymbolsModel == null) - throw new Exception("Could not prepare ambiguous symbols."); - var symbols = _ambiguousSymbolsModel.LookupNamespacesAndTypes(_ambiguousSymbolsPos, null, symbol); - - if (symbols.Length > 1) return true; - if (symbols.Length == 0) return false; // what else? - - // only 1 - ensure it matches - var found = symbols[0].ToDisplayString(); - var pos = found.IndexOf('<'); // generic? - if (pos > 0) found = found.Substring(0, pos); // strip - return found != match; // and compare - } - - internal string ModelsNamespaceForTests; - - public string GetModelsNamespace() - { - if (ModelsNamespaceForTests != null) - return ModelsNamespaceForTests; - - // code attribute overrides everything - if (ParseResult.HasModelsNamespace) - return ParseResult.ModelsNamespace; - - // if builder was initialized with a namespace, use that one - if (!string.IsNullOrWhiteSpace(ModelsNamespace)) - return ModelsNamespace; - - // default - // fixme - should NOT reference config here, should make ModelsNamespace mandatory - return UmbracoConfig.For.ModelsBuilder().ModelsNamespace; - } - - protected string GetModelsBaseClassName(TypeModel type) - { - // code attribute overrides everything - if (ParseResult.HasModelsBaseClassName) - return ParseResult.ModelsBaseClassName; - - // default - return type.IsElement ? "PublishedElementModel" : "PublishedContentModel"; - } - } -} diff --git a/src/Umbraco.ModelsBuilder/Building/CodeDomBuilder.cs b/src/Umbraco.ModelsBuilder/Building/CodeDomBuilder.cs deleted file mode 100644 index 925337bd1e..0000000000 --- a/src/Umbraco.ModelsBuilder/Building/CodeDomBuilder.cs +++ /dev/null @@ -1,113 +0,0 @@ -using System.CodeDom; -using System.Collections.Generic; - -namespace Umbraco.ModelsBuilder.Building -{ - // NOTE - // See nodes in Builder.cs class - that one does not work, is not complete, - // and was just some sort of experiment... - - /// - /// Implements a builder that works by using CodeDom - /// - internal class CodeDomBuilder : Builder - { - /// - /// Initializes a new instance of the class with a list of models to generate. - /// - /// The list of models to generate. - public CodeDomBuilder(IList typeModels) - : base(typeModels, null) - { } - - /// - /// Outputs a generated model to a code namespace. - /// - /// The code namespace. - /// The model to generate. - public void Generate(CodeNamespace ns, TypeModel typeModel) - { - // what about USING? - // what about references? - - if (typeModel.IsMixin) - { - var i = new CodeTypeDeclaration("I" + typeModel.ClrName) - { - IsInterface = true, - IsPartial = true, - Attributes = MemberAttributes.Public - }; - i.BaseTypes.Add(typeModel.BaseType == null ? "IPublishedContent" : "I" + typeModel.BaseType.ClrName); - - foreach (var mixinType in typeModel.DeclaringInterfaces) - i.BaseTypes.Add(mixinType.ClrName); - - i.Comments.Add(new CodeCommentStatement($"Mixin content Type {typeModel.Id} with alias \"{typeModel.Alias}\"")); - - foreach (var propertyModel in typeModel.Properties) - { - var p = new CodeMemberProperty - { - Name = propertyModel.ClrName, - Type = new CodeTypeReference(propertyModel.ModelClrType), - Attributes = MemberAttributes.Public, - HasGet = true, - HasSet = false - }; - i.Members.Add(p); - } - } - - var c = new CodeTypeDeclaration(typeModel.ClrName) - { - IsClass = true, - IsPartial = true, - Attributes = MemberAttributes.Public - }; - - c.BaseTypes.Add(typeModel.BaseType == null ? "PublishedContentModel" : typeModel.BaseType.ClrName); - - // if it's a missing it implements its own interface - if (typeModel.IsMixin) - c.BaseTypes.Add("I" + typeModel.ClrName); - - // write the mixins, if any, as interfaces - // only if not a mixin because otherwise the interface already has them - if (typeModel.IsMixin == false) - foreach (var mixinType in typeModel.DeclaringInterfaces) - c.BaseTypes.Add("I" + mixinType.ClrName); - - foreach (var mixin in typeModel.MixinTypes) - c.BaseTypes.Add("I" + mixin.ClrName); - - c.Comments.Add(new CodeCommentStatement($"Content Type {typeModel.Id} with alias \"{typeModel.Alias}\"")); - - foreach (var propertyModel in typeModel.Properties) - { - var p = new CodeMemberProperty - { - Name = propertyModel.ClrName, - Type = new CodeTypeReference(propertyModel.ModelClrType), - Attributes = MemberAttributes.Public, - HasGet = true, - HasSet = false - }; - p.GetStatements.Add(new CodeMethodReturnStatement( // return - new CodeMethodInvokeExpression( - new CodeMethodReferenceExpression( - new CodeThisReferenceExpression(), // this - "Value", // .Value - new[] // - { - new CodeTypeReference(propertyModel.ModelClrType) - }), - new CodeExpression[] // ("alias") - { - new CodePrimitiveExpression(propertyModel.Alias) - }))); - c.Members.Add(p); - } - } - } -} diff --git a/src/Umbraco.ModelsBuilder/Building/CodeParser.cs b/src/Umbraco.ModelsBuilder/Building/CodeParser.cs deleted file mode 100644 index 30fcbf1f91..0000000000 --- a/src/Umbraco.ModelsBuilder/Building/CodeParser.cs +++ /dev/null @@ -1,238 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Umbraco.Core.Models.PublishedContent; - -namespace Umbraco.ModelsBuilder.Building -{ - /// - /// Implements code parsing. - /// - /// Parses user's code and look for generator's instructions. - internal class CodeParser - { - /// - /// Parses a set of file. - /// - /// A set of (filename,content) representing content to parse. - /// The result of the code parsing. - /// The set of files is a dictionary of name, content. - public ParseResult Parse(IDictionary files) - { - return Parse(files, Enumerable.Empty()); - } - - /// - /// Parses a set of file. - /// - /// A set of (filename,content) representing content to parse. - /// Assemblies to reference in compilations. - /// The result of the code parsing. - /// The set of files is a dictionary of name, content. - public ParseResult Parse(IDictionary files, IEnumerable references) - { - SyntaxTree[] trees; - var compiler = new Compiler { References = references }; - var compilation = compiler.GetCompilation("Umbraco.ModelsBuilder.Generated", files, out trees); - - var disco = new ParseResult(); - foreach (var tree in trees) - Parse(disco, compilation, tree); - - return disco; - } - - public ParseResult ParseWithReferencedAssemblies(IDictionary files) - { - return Parse(files, ReferencedAssemblies.References); - } - - private static void Parse(ParseResult disco, CSharpCompilation compilation, SyntaxTree tree) - { - var model = compilation.GetSemanticModel(tree); - - //we quite probably have errors but that is normal - //var diags = model.GetDiagnostics(); - - var classDecls = tree.GetRoot().DescendantNodes().OfType(); - foreach (var classSymbol in classDecls.Select(x => model.GetDeclaredSymbol(x))) - { - ParseClassSymbols(disco, classSymbol); - - var baseClassSymbol = classSymbol.BaseType; - if (baseClassSymbol != null) - //disco.SetContentBaseClass(SymbolDisplay.ToDisplayString(classSymbol), SymbolDisplay.ToDisplayString(baseClassSymbol)); - disco.SetContentBaseClass(classSymbol.Name, baseClassSymbol.Name); - - var interfaceSymbols = classSymbol.Interfaces; - disco.SetContentInterfaces(classSymbol.Name, //SymbolDisplay.ToDisplayString(classSymbol), - interfaceSymbols.Select(x => x.Name)); //SymbolDisplay.ToDisplayString(x))); - - var hasCtor = classSymbol.Constructors - .Any(x => - { - if (x.IsStatic) return false; - if (x.Parameters.Length != 1) return false; - var type1 = x.Parameters[0].Type; - var type2 = typeof (IPublishedContent); - return type1.ToDisplayString() == type2.FullName; - }); - - if (hasCtor) - disco.SetHasCtor(classSymbol.Name); - - foreach (var propertySymbol in classSymbol.GetMembers().Where(x => x is IPropertySymbol)) - ParsePropertySymbols(disco, classSymbol, propertySymbol); - - foreach (var staticMethodSymbol in classSymbol.GetMembers().Where(x => x is IMethodSymbol)) - ParseMethodSymbol(disco, classSymbol, staticMethodSymbol); - } - - var interfaceDecls = tree.GetRoot().DescendantNodes().OfType(); - foreach (var interfaceSymbol in interfaceDecls.Select(x => model.GetDeclaredSymbol(x))) - { - ParseClassSymbols(disco, interfaceSymbol); - - var interfaceSymbols = interfaceSymbol.Interfaces; - disco.SetContentInterfaces(interfaceSymbol.Name, //SymbolDisplay.ToDisplayString(interfaceSymbol), - interfaceSymbols.Select(x => x.Name)); // SymbolDisplay.ToDisplayString(x))); - } - - ParseAssemblySymbols(disco, compilation.Assembly); - } - - private static void ParseClassSymbols(ParseResult disco, ISymbol symbol) - { - foreach (var attrData in symbol.GetAttributes()) - { - var attrClassSymbol = attrData.AttributeClass; - - // handle errors - if (attrClassSymbol is IErrorTypeSymbol) continue; - if (attrData.AttributeConstructor == null) continue; - - var attrClassName = SymbolDisplay.ToDisplayString(attrClassSymbol); - switch (attrClassName) - { - case "Umbraco.ModelsBuilder.IgnorePropertyTypeAttribute": - var propertyAliasToIgnore = (string)attrData.ConstructorArguments[0].Value; - disco.SetIgnoredProperty(symbol.Name /*SymbolDisplay.ToDisplayString(symbol)*/, propertyAliasToIgnore); - break; - case "Umbraco.ModelsBuilder.RenamePropertyTypeAttribute": - var propertyAliasToRename = (string)attrData.ConstructorArguments[0].Value; - var propertyRenamed = (string)attrData.ConstructorArguments[1].Value; - disco.SetRenamedProperty(symbol.Name /*SymbolDisplay.ToDisplayString(symbol)*/, propertyAliasToRename, propertyRenamed); - break; - // that one causes all sorts of issues with references to Umbraco.Core in Roslyn - //case "Umbraco.Core.Models.PublishedContent.PublishedContentModelAttribute": - // var contentAliasToRename = (string)attrData.ConstructorArguments[0].Value; - // disco.SetRenamedContent(contentAliasToRename, symbol.Name /*SymbolDisplay.ToDisplayString(symbol)*/); - // break; - case "Umbraco.ModelsBuilder.ImplementContentTypeAttribute": - var contentAliasToRename = (string)attrData.ConstructorArguments[0].Value; - disco.SetRenamedContent(contentAliasToRename, symbol.Name, true /*SymbolDisplay.ToDisplayString(symbol)*/); - break; - } - } - } - - private static void ParsePropertySymbols(ParseResult disco, ISymbol classSymbol, ISymbol symbol) - { - foreach (var attrData in symbol.GetAttributes()) - { - var attrClassSymbol = attrData.AttributeClass; - - // handle errors - if (attrClassSymbol is IErrorTypeSymbol) continue; - if (attrData.AttributeConstructor == null) continue; - - var attrClassName = SymbolDisplay.ToDisplayString(attrClassSymbol); - // ReSharper disable once SwitchStatementMissingSomeCases - switch (attrClassName) - { - case "Umbraco.ModelsBuilder.ImplementPropertyTypeAttribute": - var propertyAliasToIgnore = (string)attrData.ConstructorArguments[0].Value; - disco.SetIgnoredProperty(classSymbol.Name /*SymbolDisplay.ToDisplayString(classSymbol)*/, propertyAliasToIgnore); - break; - } - } - } - - private static void ParseAssemblySymbols(ParseResult disco, ISymbol symbol) - { - foreach (var attrData in symbol.GetAttributes()) - { - var attrClassSymbol = attrData.AttributeClass; - - // handle errors - if (attrClassSymbol is IErrorTypeSymbol) continue; - if (attrData.AttributeConstructor == null) continue; - - var attrClassName = SymbolDisplay.ToDisplayString(attrClassSymbol); - switch (attrClassName) - { - case "Umbraco.ModelsBuilder.IgnoreContentTypeAttribute": - var contentAliasToIgnore = (string)attrData.ConstructorArguments[0].Value; - // see notes in IgnoreContentTypeAttribute - //var ignoreContent = (bool)attrData.ConstructorArguments[1].Value; - //var ignoreMixin = (bool)attrData.ConstructorArguments[1].Value; - //var ignoreMixinProperties = (bool)attrData.ConstructorArguments[1].Value; - disco.SetIgnoredContent(contentAliasToIgnore /*, ignoreContent, ignoreMixin, ignoreMixinProperties*/); - break; - - case "Umbraco.ModelsBuilder.RenameContentTypeAttribute": - var contentAliasToRename = (string) attrData.ConstructorArguments[0].Value; - var contentRenamed = (string)attrData.ConstructorArguments[1].Value; - disco.SetRenamedContent(contentAliasToRename, contentRenamed, false); - break; - - case "Umbraco.ModelsBuilder.ModelsBaseClassAttribute": - var modelsBaseClass = (INamedTypeSymbol) attrData.ConstructorArguments[0].Value; - if (modelsBaseClass is IErrorTypeSymbol) - throw new Exception($"Invalid base class type \"{modelsBaseClass.Name}\"."); - disco.SetModelsBaseClassName(SymbolDisplay.ToDisplayString(modelsBaseClass)); - break; - - case "Umbraco.ModelsBuilder.ModelsNamespaceAttribute": - var modelsNamespace= (string) attrData.ConstructorArguments[0].Value; - disco.SetModelsNamespace(modelsNamespace); - break; - - case "Umbraco.ModelsBuilder.ModelsUsingAttribute": - var usingNamespace = (string)attrData.ConstructorArguments[0].Value; - disco.SetUsingNamespace(usingNamespace); - break; - } - } - } - - private static void ParseMethodSymbol(ParseResult disco, ISymbol classSymbol, ISymbol symbol) - { - var methodSymbol = symbol as IMethodSymbol; - - if (methodSymbol == null - || !methodSymbol.IsStatic - || methodSymbol.IsGenericMethod - || methodSymbol.ReturnsVoid - || methodSymbol.IsExtensionMethod - || methodSymbol.Parameters.Length != 1) - return; - - var returnType = methodSymbol.ReturnType; - var paramSymbol = methodSymbol.Parameters[0]; - var paramType = paramSymbol.Type; - - // cannot do this because maybe the param type is ISomething and we don't have - // that type yet - will be generated - so cannot put any condition on it really - //const string iPublishedContent = "Umbraco.Core.Models.IPublishedContent"; - //var implements = paramType.AllInterfaces.Any(x => x.ToDisplayString() == iPublishedContent); - //if (!implements) - // return; - - disco.SetStaticMixinMethod(classSymbol.Name, methodSymbol.Name, returnType.Name, paramType.Name); - } - } -} diff --git a/src/Umbraco.ModelsBuilder/Building/Compiler.cs b/src/Umbraco.ModelsBuilder/Building/Compiler.cs deleted file mode 100644 index 66064bef0b..0000000000 --- a/src/Umbraco.ModelsBuilder/Building/Compiler.cs +++ /dev/null @@ -1,171 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Web; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Umbraco.Core.Configuration; -using Umbraco.ModelsBuilder.Configuration; - -namespace Umbraco.ModelsBuilder.Building -{ - // main Roslyn compiler - internal class Compiler - { - private readonly LanguageVersion _languageVersion; - - public Compiler() - : this(UmbracoConfig.For.ModelsBuilder().LanguageVersion) - { } - - public Compiler(LanguageVersion languageVersion) - { - _languageVersion = languageVersion; - References = ReferencedAssemblies.References; - Debug = HttpContext.Current != null && HttpContext.Current.IsDebuggingEnabled; - } - - // gets or sets the references - public IEnumerable References { get; set; } - - public bool Debug { get; set; } - - // gets a compilation - public CSharpCompilation GetCompilation(string assemblyName, IDictionary files) - { - SyntaxTree[] trees; - return GetCompilation(assemblyName, files, out trees); - } - - // gets a compilation - // used by CodeParser to get a "compilation" of the existing files - public CSharpCompilation GetCompilation(string assemblyName, IDictionary files, out SyntaxTree[] trees) - { - var options = new CSharpParseOptions(_languageVersion); - trees = files.Select(x => - { - var text = x.Value; - var tree = CSharpSyntaxTree.ParseText(text, /*options:*/ options); - var diagnostic = tree.GetDiagnostics().FirstOrDefault(y => y.Severity == DiagnosticSeverity.Error); - if (diagnostic != null) - ThrowExceptionFromDiagnostic(x.Key, x.Value, diagnostic); - return tree; - }).ToArray(); - - var refs = References; - - var compilationOptions = new CSharpCompilationOptions( - OutputKind.DynamicallyLinkedLibrary, - assemblyIdentityComparer: DesktopAssemblyIdentityComparer.Default, - optimizationLevel: Debug ? OptimizationLevel.Debug : OptimizationLevel.Release - ); - var compilation = CSharpCompilation.Create( - assemblyName, - /*syntaxTrees:*/ trees, - /*references:*/ refs, - compilationOptions); - - return compilation; - } - - // compile files into a Dll - // used by ModelsBuilderBackOfficeController in [Live]Dll mode, to compile the models to disk - public void Compile(string assemblyName, IDictionary files, string binPath) - { - var assemblyPath = Path.Combine(binPath, assemblyName + ".dll"); - using (var stream = new FileStream(assemblyPath, FileMode.Create)) - { - Compile(assemblyName, files, stream); - } - - // this is how we'd create the pdb: - /* - var pdbPath = Path.Combine(binPath, assemblyName + ".pdb"); - - // create the compilation - var compilation = GetCompilation(assemblyName, files); - - // check diagnostics for errors (not warnings) - foreach (var diag in compilation.GetDiagnostics().Where(x => x.Severity == DiagnosticSeverity.Error)) - ThrowExceptionFromDiagnostic(files, diag); - - // emit - var result = compilation.Emit(assemblyPath, pdbPath); - if (result.Success) return; - - // deal with errors - var diagnostic = result.Diagnostics.First(x => x.Severity == DiagnosticSeverity.Error); - ThrowExceptionFromDiagnostic(files, diagnostic); - */ - } - - // compile files into an assembly - public Assembly Compile(string assemblyName, IDictionary files) - { - using (var stream = new MemoryStream()) - { - Compile(assemblyName, files, stream); - return Assembly.Load(stream.GetBuffer()); - } - } - - // compile one file into an assembly - public Assembly Compile(string assemblyName, string path, string code) - { - using (var stream = new MemoryStream()) - { - Compile(assemblyName, new Dictionary { { path, code } }, stream); - return Assembly.Load(stream.GetBuffer()); - } - } - - // compiles files into a stream - public void Compile(string assemblyName, IDictionary files, Stream stream) - { - // create the compilation - var compilation = GetCompilation(assemblyName, files); - - // check diagnostics for errors (not warnings) - foreach (var diag in compilation.GetDiagnostics().Where(x => x.Severity == DiagnosticSeverity.Error)) - ThrowExceptionFromDiagnostic(files, diag); - - // emit - var result = compilation.Emit(stream); - if (result.Success) return; - - // deal with errors - var diagnostic = result.Diagnostics.First(x => x.Severity == DiagnosticSeverity.Error); - ThrowExceptionFromDiagnostic(files, diagnostic); - } - - // compiles one file into a stream - public void Compile(string assemblyName, string path, string code, Stream stream) - { - Compile(assemblyName, new Dictionary { { path, code } }, stream); - } - - private static void ThrowExceptionFromDiagnostic(IDictionary files, Diagnostic diagnostic) - { - var message = diagnostic.GetMessage(); - if (diagnostic.Location == Location.None) - throw new CompilerException(message); - - var position = diagnostic.Location.GetLineSpan().StartLinePosition.Line + 1; - var path = diagnostic.Location.SourceTree.FilePath; - var code = files.ContainsKey(path) ? files[path] : string.Empty; - throw new CompilerException(message, path, code, position); - } - - private static void ThrowExceptionFromDiagnostic(string path, string code, Diagnostic diagnostic) - { - var message = diagnostic.GetMessage(); - if (diagnostic.Location == Location.None) - throw new CompilerException(message); - - var position = diagnostic.Location.GetLineSpan().StartLinePosition.Line + 1; - throw new CompilerException(message, path, code, position); - } - } -} diff --git a/src/Umbraco.ModelsBuilder/Building/CompilerException.cs b/src/Umbraco.ModelsBuilder/Building/CompilerException.cs deleted file mode 100644 index e978f67ae5..0000000000 --- a/src/Umbraco.ModelsBuilder/Building/CompilerException.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; - -namespace Umbraco.ModelsBuilder.Building -{ - public class CompilerException : Exception - { - public CompilerException(string message) - : base(message) - { } - - public CompilerException(string message, string path, string sourceCode, int line) - : base(message) - { - Path = path; - SourceCode = sourceCode; - Line = line; - } - - public string Path { get; } = string.Empty; - - public string SourceCode { get; } = string.Empty; - - public int Line { get; } = -1; - } -} diff --git a/src/Umbraco.ModelsBuilder/Building/ParseResult.cs b/src/Umbraco.ModelsBuilder/Building/ParseResult.cs deleted file mode 100644 index d1f61363ff..0000000000 --- a/src/Umbraco.ModelsBuilder/Building/ParseResult.cs +++ /dev/null @@ -1,275 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Umbraco.ModelsBuilder.Building -{ - /// - /// Contains the result of a code parsing. - /// - internal class ParseResult - { - // "alias" is the umbraco alias - // content "name" is the complete name eg Foo.Bar.Name - // property "name" is just the local name - - // see notes in IgnoreContentTypeAttribute - - private readonly HashSet _ignoredContent - = new HashSet(StringComparer.InvariantCultureIgnoreCase); - //private readonly HashSet _ignoredMixin - // = new HashSet(StringComparer.InvariantCultureIgnoreCase); - //private readonly HashSet _ignoredMixinProperties - // = new HashSet(StringComparer.InvariantCultureIgnoreCase); - private readonly Dictionary _renamedContent - = new Dictionary(StringComparer.InvariantCultureIgnoreCase); - private readonly HashSet _withImplementContent - = new HashSet(StringComparer.InvariantCultureIgnoreCase); - private readonly Dictionary> _ignoredProperty - = new Dictionary>(); - private readonly Dictionary> _renamedProperty - = new Dictionary>(); - private readonly Dictionary _contentBase - = new Dictionary(); - private readonly Dictionary _contentInterfaces - = new Dictionary(); - private readonly List _usingNamespaces - = new List(); - private readonly Dictionary> _staticMixins - = new Dictionary>(); - private readonly HashSet _withCtor - = new HashSet(StringComparer.InvariantCultureIgnoreCase); - - public static readonly ParseResult Empty = new ParseResult(); - - private class StaticMixinMethodInfo - { - public StaticMixinMethodInfo(string contentName, string methodName, string returnType, string paramType) - { - ContentName = contentName; - MethodName = methodName; - //ReturnType = returnType; - //ParamType = paramType; - } - - // short name eg Type1 - public string ContentName { get; private set; } - - // short name eg GetProp1 - public string MethodName { get; private set; } - - // those types cannot be FQ because when parsing, some of them - // might not exist since we're generating them... and so prob. - // that info is worthless - not using it anyway at the moment... - - //public string ReturnType { get; private set; } - //public string ParamType { get; private set; } - } - - #region Declare - - // content with that alias should not be generated - // alias can end with a * (wildcard) - public void SetIgnoredContent(string contentAlias /*, bool ignoreContent, bool ignoreMixin, bool ignoreMixinProperties*/) - { - //if (ignoreContent) - _ignoredContent.Add(contentAlias); - //if (ignoreMixin) - // _ignoredMixin.Add(contentAlias); - //if (ignoreMixinProperties) - // _ignoredMixinProperties.Add(contentAlias); - } - - // content with that alias should be generated with a different name - public void SetRenamedContent(string contentAlias, string contentName, bool withImplement) - { - _renamedContent[contentAlias] = contentName; - if (withImplement) - _withImplementContent.Add(contentAlias); - } - - // property with that alias should not be generated - // applies to content name and any content that implements it - // here, contentName may be an interface - // alias can end with a * (wildcard) - public void SetIgnoredProperty(string contentName, string propertyAlias) - { - HashSet ignores; - if (!_ignoredProperty.TryGetValue(contentName, out ignores)) - ignores = _ignoredProperty[contentName] = new HashSet(StringComparer.InvariantCultureIgnoreCase); - ignores.Add(propertyAlias); - } - - // property with that alias should be generated with a different name - // applies to content name and any content that implements it - // here, contentName may be an interface - public void SetRenamedProperty(string contentName, string propertyAlias, string propertyName) - { - Dictionary renames; - if (!_renamedProperty.TryGetValue(contentName, out renames)) - renames = _renamedProperty[contentName] = new Dictionary(StringComparer.InvariantCultureIgnoreCase); - renames[propertyAlias] = propertyName; - } - - // content with that name has a base class so no need to generate one - public void SetContentBaseClass(string contentName, string baseName) - { - if (baseName.ToLowerInvariant() != "object") - _contentBase[contentName] = baseName; - } - - // content with that name implements the interfaces - public void SetContentInterfaces(string contentName, IEnumerable interfaceNames) - { - _contentInterfaces[contentName] = interfaceNames.ToArray(); - } - - public void SetModelsBaseClassName(string modelsBaseClassName) - { - ModelsBaseClassName = modelsBaseClassName; - } - - public void SetModelsNamespace(string modelsNamespace) - { - ModelsNamespace = modelsNamespace; - } - - public void SetUsingNamespace(string usingNamespace) - { - _usingNamespaces.Add(usingNamespace); - } - - public void SetStaticMixinMethod(string contentName, string methodName, string returnType, string paramType) - { - if (!_staticMixins.ContainsKey(contentName)) - _staticMixins[contentName] = new List(); - - _staticMixins[contentName].Add(new StaticMixinMethodInfo(contentName, methodName, returnType, paramType)); - } - - public void SetHasCtor(string contentName) - { - _withCtor.Add(contentName); - } - - #endregion - - #region Query - - public bool IsIgnored(string contentAlias) - { - return IsContentOrMixinIgnored(contentAlias, _ignoredContent); - } - - //public bool IsMixinIgnored(string contentAlias) - //{ - // return IsContentOrMixinIgnored(contentAlias, _ignoredMixin); - //} - - //public bool IsMixinPropertiesIgnored(string contentAlias) - //{ - // return IsContentOrMixinIgnored(contentAlias, _ignoredMixinProperties); - //} - - private static bool IsContentOrMixinIgnored(string contentAlias, HashSet ignored) - { - if (ignored.Contains(contentAlias)) return true; - return ignored - .Where(x => x.EndsWith("*")) - .Select(x => x.Substring(0, x.Length - 1)) - .Any(x => contentAlias.StartsWith(x, StringComparison.InvariantCultureIgnoreCase)); - } - - public bool HasContentBase(string contentName) - { - return _contentBase.ContainsKey(contentName); - } - - public bool IsContentRenamed(string contentAlias) - { - return _renamedContent.ContainsKey(contentAlias); - } - - public bool HasContentImplement(string contentAlias) - { - return _withImplementContent.Contains(contentAlias); - } - - public string ContentClrName(string contentAlias) - { - string name; - return (_renamedContent.TryGetValue(contentAlias, out name)) ? name : null; - } - - public bool IsPropertyIgnored(string contentName, string propertyAlias) - { - HashSet ignores; - if (_ignoredProperty.TryGetValue(contentName, out ignores)) - { - if (ignores.Contains(propertyAlias)) return true; - if (ignores - .Where(x => x.EndsWith("*")) - .Select(x => x.Substring(0, x.Length - 1)) - .Any(x => propertyAlias.StartsWith(x, StringComparison.InvariantCultureIgnoreCase))) - return true; - } - string baseName; - if (_contentBase.TryGetValue(contentName, out baseName) - && IsPropertyIgnored(baseName, propertyAlias)) return true; - string[] interfaceNames; - if (_contentInterfaces.TryGetValue(contentName, out interfaceNames) - && interfaceNames.Any(interfaceName => IsPropertyIgnored(interfaceName, propertyAlias))) return true; - return false; - } - - public string PropertyClrName(string contentName, string propertyAlias) - { - Dictionary renames; - string name; - if (_renamedProperty.TryGetValue(contentName, out renames) - && renames.TryGetValue(propertyAlias, out name)) return name; - string baseName; - if (_contentBase.TryGetValue(contentName, out baseName) - && null != (name = PropertyClrName(baseName, propertyAlias))) return name; - string[] interfaceNames; - if (_contentInterfaces.TryGetValue(contentName, out interfaceNames) - && null != (name = interfaceNames - .Select(interfaceName => PropertyClrName(interfaceName, propertyAlias)) - .FirstOrDefault(x => x != null))) return name; - return null; - } - - public bool HasModelsBaseClassName - { - get { return !string.IsNullOrWhiteSpace(ModelsBaseClassName); } - } - - public string ModelsBaseClassName { get; private set; } - - public bool HasModelsNamespace - { - get { return !string.IsNullOrWhiteSpace(ModelsNamespace); } - } - - public string ModelsNamespace { get; private set; } - - public IEnumerable UsingNamespaces - { - get { return _usingNamespaces; } - } - - public IEnumerable StaticMixinMethods(string contentName) - { - return _staticMixins.ContainsKey(contentName) - ? _staticMixins[contentName].Select(x => x.MethodName) - : Enumerable.Empty() ; - } - - public bool HasCtor(string contentName) - { - return _withCtor.Contains(contentName); - } - - #endregion - } -} \ No newline at end of file diff --git a/src/Umbraco.ModelsBuilder/Building/PropertyModel.cs b/src/Umbraco.ModelsBuilder/Building/PropertyModel.cs deleted file mode 100644 index 1595b3f888..0000000000 --- a/src/Umbraco.ModelsBuilder/Building/PropertyModel.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Umbraco.ModelsBuilder.Building -{ - /// - /// Represents a model property. - /// - public class PropertyModel - { - /// - /// Gets the alias of the property. - /// - public string Alias; - - /// - /// Gets the name of the property. - /// - public string Name; - - /// - /// Gets the description of the property. - /// - public string Description; - - /// - /// Gets the clr name of the property. - /// - /// This is just the local name eg "Price". - public string ClrName; - - /// - /// Gets the Model Clr type of the property values. - /// - /// As indicated by the PublishedPropertyType, ie by the IPropertyValueConverter - /// if any, else object. May include some ModelType that will need to be mapped. - public Type ModelClrType; - - /// - /// Gets the CLR type name of the property values. - /// - public string ClrTypeName; - - /// - /// Gets a value indicating whether this property should be excluded from generation. - /// - public bool IsIgnored; - - /// - /// Gets the generation errors for the property. - /// - /// This should be null, unless something prevents the property from being - /// generated, and then the value should explain what. This can be used to generate - /// commented out code eg in PureLive. - public List Errors; - - /// - /// Adds an error. - /// - public void AddError(string error) - { - if (Errors == null) Errors = new List(); - Errors.Add(error); - } - } -} diff --git a/src/Umbraco.ModelsBuilder/Building/TextBuilder.cs b/src/Umbraco.ModelsBuilder/Building/TextBuilder.cs deleted file mode 100644 index 85ccd541b7..0000000000 --- a/src/Umbraco.ModelsBuilder/Building/TextBuilder.cs +++ /dev/null @@ -1,554 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using Umbraco.Core.Configuration; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.ModelsBuilder.Configuration; - -namespace Umbraco.ModelsBuilder.Building -{ - /// - /// Implements a builder that works by writing text. - /// - internal class TextBuilder : Builder - { - /// - /// Initializes a new instance of the class with a list of models to generate - /// and the result of code parsing. - /// - /// The list of models to generate. - /// The result of code parsing. - public TextBuilder(IList typeModels, ParseResult parseResult) - : base(typeModels, parseResult) - { } - - /// - /// Initializes a new instance of the class with a list of models to generate, - /// the result of code parsing, and a models namespace. - /// - /// The list of models to generate. - /// The result of code parsing. - /// The models namespace. - public TextBuilder(IList typeModels, ParseResult parseResult, string modelsNamespace) - : base(typeModels, parseResult, modelsNamespace) - { } - - // internal for unit tests only - internal TextBuilder() - { } - - /// - /// Outputs a generated model to a string builder. - /// - /// The string builder. - /// The model to generate. - public void Generate(StringBuilder sb, TypeModel typeModel) - { - WriteHeader(sb); - - foreach (var t in TypesUsing) - sb.AppendFormat("using {0};\n", t); - - sb.Append("\n"); - sb.AppendFormat("namespace {0}\n", GetModelsNamespace()); - sb.Append("{\n"); - - WriteContentType(sb, typeModel); - - sb.Append("}\n"); - } - - /// - /// Outputs generated models to a string builder. - /// - /// The string builder. - /// The models to generate. - public void Generate(StringBuilder sb, IEnumerable typeModels) - { - WriteHeader(sb); - - foreach (var t in TypesUsing) - sb.AppendFormat("using {0};\n", t); - - // assembly attributes marker - sb.Append("\n//ASSATTR\n"); - - sb.Append("\n"); - sb.AppendFormat("namespace {0}\n", GetModelsNamespace()); - sb.Append("{\n"); - - foreach (var typeModel in typeModels) - { - WriteContentType(sb, typeModel); - sb.Append("\n"); - } - - sb.Append("}\n"); - } - - /// - /// Outputs an "auto-generated" header to a string builder. - /// - /// The string builder. - public static void WriteHeader(StringBuilder sb) - { - TextHeaderWriter.WriteHeader(sb); - } - - private void WriteContentType(StringBuilder sb, TypeModel type) - { - string sep; - - if (type.IsMixin) - { - // write the interface declaration - sb.AppendFormat("\t// Mixin content Type {0} with alias \"{1}\"\n", type.Id, type.Alias); - if (!string.IsNullOrWhiteSpace(type.Name)) - sb.AppendFormat("\t/// {0}\n", XmlCommentString(type.Name)); - sb.AppendFormat("\tpublic partial interface I{0}", type.ClrName); - var implements = type.BaseType == null || type.BaseType.IsContentIgnored - ? (type.HasBase ? null : (type.IsElement ? "PublishedElement" : "PublishedContent")) - : type.BaseType.ClrName; - if (implements != null) - sb.AppendFormat(" : I{0}", implements); - - // write the mixins - sep = implements == null ? ":" : ","; - foreach (var mixinType in type.DeclaringInterfaces.OrderBy(x => x.ClrName)) - { - sb.AppendFormat("{0} I{1}", sep, mixinType.ClrName); - sep = ","; - } - - sb.Append("\n\t{\n"); - - // write the properties - only the local (non-ignored) ones, we're an interface - var more = false; - foreach (var prop in type.Properties.Where(x => !x.IsIgnored).OrderBy(x => x.ClrName)) - { - if (more) sb.Append("\n"); - more = true; - WriteInterfaceProperty(sb, prop); - } - - sb.Append("\t}\n\n"); - } - - // write the class declaration - if (type.IsRenamed) - sb.AppendFormat("\t// Content Type {0} with alias \"{1}\"\n", type.Id, type.Alias); - if (!string.IsNullOrWhiteSpace(type.Name)) - sb.AppendFormat("\t/// {0}\n", XmlCommentString(type.Name)); - // cannot do it now. see note in ImplementContentTypeAttribute - //if (!type.HasImplement) - // sb.AppendFormat("\t[ImplementContentType(\"{0}\")]\n", type.Alias); - sb.AppendFormat("\t[PublishedModel(\"{0}\")]\n", type.Alias); - sb.AppendFormat("\tpublic partial class {0}", type.ClrName); - var inherits = type.HasBase - ? null // has its own base already - : (type.BaseType == null || type.BaseType.IsContentIgnored - ? GetModelsBaseClassName(type) - : type.BaseType.ClrName); - if (inherits != null) - sb.AppendFormat(" : {0}", inherits); - - sep = inherits == null ? ":" : ","; - if (type.IsMixin) - { - // if it's a mixin it implements its own interface - sb.AppendFormat("{0} I{1}", sep, type.ClrName); - } - else - { - // write the mixins, if any, as interfaces - // only if not a mixin because otherwise the interface already has them already - foreach (var mixinType in type.DeclaringInterfaces.OrderBy(x => x.ClrName)) - { - sb.AppendFormat("{0} I{1}", sep, mixinType.ClrName); - sep = ","; - } - } - - // begin class body - sb.Append("\n\t{\n"); - - // write the constants & static methods - // as 'new' since parent has its own - or maybe not - disable warning - sb.Append("\t\t// helpers\n"); - sb.Append("#pragma warning disable 0109 // new is redundant\n"); - sb.AppendFormat("\t\tpublic new const string ModelTypeAlias = \"{0}\";\n", - type.Alias); - var itemType = type.IsElement ? TypeModel.ItemTypes.Content : type.ItemType; // fixme - sb.AppendFormat("\t\tpublic new const PublishedItemType ModelItemType = PublishedItemType.{0};\n", - itemType); - sb.Append("\t\tpublic new static PublishedContentType GetModelContentType()\n"); - sb.Append("\t\t\t=> PublishedModelUtility.GetModelContentType(ModelItemType, ModelTypeAlias);\n"); - sb.AppendFormat("\t\tpublic static PublishedPropertyType GetModelPropertyType(Expression> selector)\n", - type.ClrName); - sb.Append("\t\t\t=> PublishedModelUtility.GetModelPropertyType(GetModelContentType(), selector);\n"); - sb.Append("#pragma warning restore 0109\n\n"); - - // write the ctor - if (!type.HasCtor) - sb.AppendFormat("\t\t// ctor\n\t\tpublic {0}(IPublished{1} content)\n\t\t\t: base(content)\n\t\t{{ }}\n\n", - type.ClrName, type.IsElement ? "Element" : "Content"); - - // write the properties - sb.Append("\t\t// properties\n"); - WriteContentTypeProperties(sb, type); - - // close the class declaration - sb.Append("\t}\n"); - } - - private void WriteContentTypeProperties(StringBuilder sb, TypeModel type) - { - var staticMixinGetters = UmbracoConfig.For.ModelsBuilder().StaticMixinGetters; - - // write the properties - foreach (var prop in type.Properties.Where(x => !x.IsIgnored).OrderBy(x => x.ClrName)) - WriteProperty(sb, type, prop, staticMixinGetters && type.IsMixin ? type.ClrName : null); - - // no need to write the parent properties since we inherit from the parent - // and the parent defines its own properties. need to write the mixins properties - // since the mixins are only interfaces and we have to provide an implementation. - - // write the mixins properties - foreach (var mixinType in type.ImplementingInterfaces.OrderBy(x => x.ClrName)) - foreach (var prop in mixinType.Properties.Where(x => !x.IsIgnored).OrderBy(x => x.ClrName)) - if (staticMixinGetters) - WriteMixinProperty(sb, prop, mixinType.ClrName); - else - WriteProperty(sb, mixinType, prop); - } - - private void WriteMixinProperty(StringBuilder sb, PropertyModel property, string mixinClrName) - { - sb.Append("\n"); - - // Adds xml summary to each property containing - // property name and property description - if (!string.IsNullOrWhiteSpace(property.Name) || !string.IsNullOrWhiteSpace(property.Description)) - { - sb.Append("\t\t///\n"); - - if (!string.IsNullOrWhiteSpace(property.Description)) - sb.AppendFormat("\t\t/// {0}: {1}\n", XmlCommentString(property.Name), XmlCommentString(property.Description)); - else - sb.AppendFormat("\t\t/// {0}\n", XmlCommentString(property.Name)); - - sb.Append("\t\t///\n"); - } - - sb.AppendFormat("\t\t[ImplementPropertyType(\"{0}\")]\n", property.Alias); - - sb.Append("\t\tpublic "); - WriteClrType(sb, property.ClrTypeName); - - sb.AppendFormat(" {0} => ", - property.ClrName); - WriteNonGenericClrType(sb, GetModelsNamespace() + "." + mixinClrName); - sb.AppendFormat(".{0}(this);\n", - MixinStaticGetterName(property.ClrName)); - } - - private static string MixinStaticGetterName(string clrName) - { - return string.Format(UmbracoConfig.For.ModelsBuilder().StaticMixinGetterPattern, clrName); - } - - private void WriteProperty(StringBuilder sb, TypeModel type, PropertyModel property, string mixinClrName = null) - { - var mixinStatic = mixinClrName != null; - - sb.Append("\n"); - - if (property.Errors != null) - { - sb.Append("\t\t/*\n"); - sb.Append("\t\t * THIS PROPERTY CANNOT BE IMPLEMENTED, BECAUSE:\n"); - sb.Append("\t\t *\n"); - var first = true; - foreach (var error in property.Errors) - { - if (first) first = false; - else sb.Append("\t\t *\n"); - foreach (var s in SplitError(error)) - { - sb.Append("\t\t * "); - sb.Append(s); - sb.Append("\n"); - } - } - sb.Append("\t\t *\n"); - sb.Append("\n"); - } - - // Adds xml summary to each property containing - // property name and property description - if (!string.IsNullOrWhiteSpace(property.Name) || !string.IsNullOrWhiteSpace(property.Description)) - { - sb.Append("\t\t///\n"); - - if (!string.IsNullOrWhiteSpace(property.Description)) - sb.AppendFormat("\t\t/// {0}: {1}\n", XmlCommentString(property.Name), XmlCommentString(property.Description)); - else - sb.AppendFormat("\t\t/// {0}\n", XmlCommentString(property.Name)); - - sb.Append("\t\t///\n"); - } - - sb.AppendFormat("\t\t[ImplementPropertyType(\"{0}\")]\n", property.Alias); - - if (mixinStatic) - { - sb.Append("\t\tpublic "); - WriteClrType(sb, property.ClrTypeName); - sb.AppendFormat(" {0} => {1}(this);\n", - property.ClrName, MixinStaticGetterName(property.ClrName)); - } - else - { - sb.Append("\t\tpublic "); - WriteClrType(sb, property.ClrTypeName); - sb.AppendFormat(" {0} => this.Value", - property.ClrName); - if (property.ModelClrType != typeof(object)) - { - sb.Append("<"); - WriteClrType(sb, property.ClrTypeName); - sb.Append(">"); - } - sb.AppendFormat("(\"{0}\");\n", - property.Alias); - } - - if (property.Errors != null) - { - sb.Append("\n"); - sb.Append("\t\t *\n"); - sb.Append("\t\t */\n"); - } - - if (!mixinStatic) return; - - var mixinStaticGetterName = MixinStaticGetterName(property.ClrName); - - if (type.StaticMixinMethods.Contains(mixinStaticGetterName)) return; - - sb.Append("\n"); - - if (!string.IsNullOrWhiteSpace(property.Name)) - sb.AppendFormat("\t\t/// Static getter for {0}\n", XmlCommentString(property.Name)); - - sb.Append("\t\tpublic static "); - WriteClrType(sb, property.ClrTypeName); - sb.AppendFormat(" {0}(I{1} that) => that.Value", - mixinStaticGetterName, mixinClrName); - if (property.ModelClrType != typeof(object)) - { - sb.Append("<"); - WriteClrType(sb, property.ClrTypeName); - sb.Append(">"); - } - sb.AppendFormat("(\"{0}\");\n", - property.Alias); - } - - private static IEnumerable SplitError(string error) - { - var p = 0; - while (p < error.Length) - { - var n = p + 50; - while (n < error.Length && error[n] != ' ') n++; - if (n >= error.Length) break; - yield return error.Substring(p, n - p); - p = n + 1; - } - if (p < error.Length) - yield return error.Substring(p); - } - - private void WriteInterfaceProperty(StringBuilder sb, PropertyModel property) - { - if (property.Errors != null) - { - sb.Append("\t\t/*\n"); - sb.Append("\t\t * THIS PROPERTY CANNOT BE IMPLEMENTED, BECAUSE:\n"); - sb.Append("\t\t *\n"); - var first = true; - foreach (var error in property.Errors) - { - if (first) first = false; - else sb.Append("\t\t *\n"); - foreach (var s in SplitError(error)) - { - sb.Append("\t\t * "); - sb.Append(s); - sb.Append("\n"); - } - } - sb.Append("\t\t *\n"); - sb.Append("\n"); - } - - if (!string.IsNullOrWhiteSpace(property.Name)) - sb.AppendFormat("\t\t/// {0}\n", XmlCommentString(property.Name)); - sb.Append("\t\t"); - WriteClrType(sb, property.ClrTypeName); - sb.AppendFormat(" {0} {{ get; }}\n", - property.ClrName); - - if (property.Errors != null) - { - sb.Append("\n"); - sb.Append("\t\t *\n"); - sb.Append("\t\t */\n"); - } - } - - // internal for unit tests - internal void WriteClrType(StringBuilder sb, Type type) - { - var s = type.ToString(); - - if (type.IsGenericType) - { - var p = s.IndexOf('`'); - WriteNonGenericClrType(sb, s.Substring(0, p)); - sb.Append("<"); - var args = type.GetGenericArguments(); - for (var i = 0; i < args.Length; i++) - { - if (i > 0) sb.Append(", "); - WriteClrType(sb, args[i]); - } - sb.Append(">"); - } - else - { - WriteNonGenericClrType(sb, s); - } - } - - internal void WriteClrType(StringBuilder sb, string type) - { - var p = type.IndexOf('<'); - if (type.Contains('<')) - { - WriteNonGenericClrType(sb, type.Substring(0, p)); - sb.Append("<"); - var args = type.Substring(p + 1).TrimEnd('>').Split(','); // fixme will NOT work with nested generic types - for (var i = 0; i < args.Length; i++) - { - if (i > 0) sb.Append(", "); - WriteClrType(sb, args[i]); - } - sb.Append(">"); - } - else - { - WriteNonGenericClrType(sb, type); - } - } - - private void WriteNonGenericClrType(StringBuilder sb, string s) - { - // map model types - s = Regex.Replace(s, @"\{(.*)\}\[\*\]", m => ModelsMap[m.Groups[1].Value + "[]"]); - - // takes care eg of "System.Int32" vs. "int" - if (TypesMap.TryGetValue(s.ToLowerInvariant(), out string typeName)) - { - sb.Append(typeName); - return; - } - - // if full type name matches a using clause, strip - // so if we want Umbraco.Core.Models.IPublishedContent - // and using Umbraco.Core.Models, then we just need IPublishedContent - typeName = s; - string typeUsing = null; - var p = typeName.LastIndexOf('.'); - if (p > 0) - { - var x = typeName.Substring(0, p); - if (Using.Contains(x)) - { - typeName = typeName.Substring(p + 1); - typeUsing = x; - } - } - - // nested types *after* using - typeName = typeName.Replace("+", "."); - - // symbol to test is the first part of the name - // so if type name is Foo.Bar.Nil we want to ensure that Foo is not ambiguous - p = typeName.IndexOf('.'); - var symbol = p > 0 ? typeName.Substring(0, p) : typeName; - - // what we should find - WITHOUT any generic thing - just the type - // no 'using' = the exact symbol - // a 'using' = using.symbol - var match = typeUsing == null ? symbol : (typeUsing + "." + symbol); - - // if not ambiguous, be happy - if (!IsAmbiguousSymbol(symbol, match)) - { - sb.Append(typeName); - return; - } - - // symbol is ambiguous - // if no 'using', must prepend global:: - if (typeUsing == null) - { - sb.Append("global::"); - sb.Append(s.Replace("+", ".")); - return; - } - - // could fullname be non-ambiguous? - // note: all-or-nothing, not trying to segment the using clause - typeName = s.Replace("+", "."); - p = typeName.IndexOf('.'); - symbol = typeName.Substring(0, p); - match = symbol; - - // still ambiguous, must prepend global:: - if (IsAmbiguousSymbol(symbol, match)) - sb.Append("global::"); - - sb.Append(typeName); - } - - private static string XmlCommentString(string s) - { - return s.Replace('<', '{').Replace('>', '}').Replace('\r', ' ').Replace('\n', ' '); - } - - private static readonly IDictionary TypesMap = new Dictionary - { - { "system.int16", "short" }, - { "system.int32", "int" }, - { "system.int64", "long" }, - { "system.string", "string" }, - { "system.object", "object" }, - { "system.boolean", "bool" }, - { "system.void", "void" }, - { "system.char", "char" }, - { "system.byte", "byte" }, - { "system.uint16", "ushort" }, - { "system.uint32", "uint" }, - { "system.uint64", "ulong" }, - { "system.sbyte", "sbyte" }, - { "system.single", "float" }, - { "system.double", "double" }, - { "system.decimal", "decimal" } - }; - } -} diff --git a/src/Umbraco.ModelsBuilder/Building/TextHeaderWriter.cs b/src/Umbraco.ModelsBuilder/Building/TextHeaderWriter.cs deleted file mode 100644 index d165f03907..0000000000 --- a/src/Umbraco.ModelsBuilder/Building/TextHeaderWriter.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Text; -using Umbraco.ModelsBuilder.Api; - -namespace Umbraco.ModelsBuilder.Building -{ - public static class TextHeaderWriter - { - /// - /// Outputs an "auto-generated" header to a string builder. - /// - /// The string builder. - public static void WriteHeader(StringBuilder sb) - { - sb.Append("//------------------------------------------------------------------------------\n"); - 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.Append("//\n"); - sb.Append("// Changes to this file will be lost if the code is regenerated.\n"); - sb.Append("// \n"); - sb.Append("//------------------------------------------------------------------------------\n"); - sb.Append("\n"); - } - } -} diff --git a/src/Umbraco.ModelsBuilder/Building/TypeModel.cs b/src/Umbraco.ModelsBuilder/Building/TypeModel.cs deleted file mode 100644 index 5ada8e881c..0000000000 --- a/src/Umbraco.ModelsBuilder/Building/TypeModel.cs +++ /dev/null @@ -1,208 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Umbraco.ModelsBuilder.Building -{ - /// - /// Represents a model. - /// - public class TypeModel - { - /// - /// Gets the unique identifier of the corresponding content type. - /// - public int Id; - - /// - /// Gets the alias of the model. - /// - public string Alias; - - /// - /// Gets the name of the content type. - /// - public string Name; - - /// - /// Gets the description of the content type. - /// - public string Description; - - /// - /// Gets the clr name of the model. - /// - /// This is the complete name eg "Foo.Bar.MyContent". - public string ClrName; - - /// - /// Gets the unique identifier of the parent. - /// - /// The parent can either be a base content type, or a content types container. If the content - /// type does not have a base content type, then returns -1. - public int ParentId; - - /// - /// Gets the base model. - /// - /// - /// If the content type does not have a base content type, then returns null. - /// The current model inherits from its base model. - /// - public TypeModel BaseType; // the parent type in Umbraco (type inherits its properties) - - /// - /// Gets the list of properties that are defined by this model. - /// - /// These are only those property that are defined locally by this model, - /// and the list does not contain properties inherited from base models or from mixins. - public readonly List Properties = new List(); - - /// - /// Gets the mixin models. - /// - /// The current model implements mixins. - public readonly List MixinTypes = new List(); - - /// - /// Gets the list of interfaces that this model needs to declare it implements. - /// - /// Some of these interfaces may actually be implemented by a base model - /// that this model inherits from. - public readonly List DeclaringInterfaces = new List(); - - /// - /// Gets the list of interfaces that this model needs to actually implement. - /// - public readonly List ImplementingInterfaces = new List(); - - /// - /// Gets the list of existing static mixin method candidates. - /// - public readonly List StaticMixinMethods = new List(); - - /// - /// Gets a value indicating whether this model has a base class. - /// - /// Can be either because the content type has a base content type declared in Umbraco, - /// or because the existing user's code declares a base class for this model. - public bool HasBase; - - /// - /// Gets a value indicating whether this model has been renamed. - /// - public bool IsRenamed; - - /// - /// Gets a value indicating whether this model has [ImplementContentType] already. - /// - public bool HasImplement; - - /// - /// Gets a value indicating whether this model is used as a mixin by another model. - /// - public bool IsMixin; - - /// - /// Gets a value indicating whether this model is the base model of another model. - /// - public bool IsParent; - - /// - /// Gets a value indicating whether this model should be excluded from generation. - /// - public bool IsContentIgnored; - - /// - /// Gets a value indicating whether the ctor is already defined in a partial. - /// - public bool HasCtor; - - /// - /// Gets a value indicating whether the type is an element. - /// - public bool IsElement => ItemType == ItemTypes.Element; - - /// - /// Represents the different model item types. - /// - public enum ItemTypes - { - /// - /// Element. - /// - Element, - - /// - /// Content. - /// - Content, - - /// - /// Media. - /// - Media, - - /// - /// Member. - /// - Member - } - - private ItemTypes _itemType; - - /// - /// Gets or sets the model item type. - /// - public ItemTypes ItemType - { - get { return _itemType; } - set - { - switch (value) - { - case ItemTypes.Element: - case ItemTypes.Content: - case ItemTypes.Media: - case ItemTypes.Member: - _itemType = value; - break; - default: - throw new ArgumentException("value"); - } - } - } - - /// - /// Recursively collects all types inherited, or implemented as interfaces, by a specified type. - /// - /// The collection. - /// The type. - /// Includes the specified type. - internal static void CollectImplems(ICollection types, TypeModel type) - { - if (!type.IsContentIgnored && types.Contains(type) == false) - types.Add(type); - if (type.BaseType != null && !type.BaseType.IsContentIgnored) - CollectImplems(types, type.BaseType); - foreach (var mixin in type.MixinTypes.Where(x => !x.IsContentIgnored)) - CollectImplems(types, mixin); - } - - /// - /// Enumerates the base models starting from the current model up. - /// - /// Indicates whether the enumeration should start with the current model - /// or from its base model. - /// The base models. - public IEnumerable EnumerateBaseTypes(bool andSelf = false) - { - var typeModel = andSelf ? this : BaseType; - while (typeModel != null) - { - yield return typeModel; - typeModel = typeModel.BaseType; - } - } - } -} diff --git a/src/Umbraco.ModelsBuilder/Configuration/ClrNameSource.cs b/src/Umbraco.ModelsBuilder/Configuration/ClrNameSource.cs deleted file mode 100644 index d195846411..0000000000 --- a/src/Umbraco.ModelsBuilder/Configuration/ClrNameSource.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace Umbraco.ModelsBuilder.Configuration -{ - /// - /// Defines the CLR name sources. - /// - public enum ClrNameSource - { - /// - /// No source. - /// - Nothing = 0, - - /// - /// Use the name as source. - /// - Name, - - /// - /// Use the alias as source. - /// - Alias, - - /// - /// Use the alias directly. - /// - RawAlias - } -} diff --git a/src/Umbraco.ModelsBuilder/Configuration/Config.cs b/src/Umbraco.ModelsBuilder/Configuration/Config.cs deleted file mode 100644 index ebfe4be709..0000000000 --- a/src/Umbraco.ModelsBuilder/Configuration/Config.cs +++ /dev/null @@ -1,357 +0,0 @@ -using System; -using System.Configuration; -using System.IO; -using System.Reflection; -using System.Web.Configuration; -using System.Web.Hosting; -using Microsoft.CodeAnalysis.CSharp; -using Umbraco.Core; - -namespace Umbraco.ModelsBuilder.Configuration -{ - /// - /// Represents the models builder configuration. - /// - public class Config - { - private static Config _value; - - /// - /// Gets the configuration - internal so that the UmbracoConfig extension - /// can get the value to initialize its own value. Either a value has - /// been provided via the Setup method, or a new instance is created, which - /// will load settings from the config file. - /// - internal static Config Value => _value ?? new Config(); - - /// - /// Sets the configuration programmatically. - /// - /// The configuration. - /// - /// Once the configuration has been accessed via the UmbracoConfig extension, - /// it cannot be changed anymore, and using this method will achieve nothing. - /// For tests, see UmbracoConfigExtensions.ResetConfig(). - /// - public static void Setup(Config config) - { - _value = config; - } - - internal const string DefaultStaticMixinGetterPattern = "Get{0}"; - internal const LanguageVersion DefaultLanguageVersion = LanguageVersion.CSharp6; - internal const string DefaultModelsNamespace = "Umbraco.Web.PublishedModels"; - internal const ClrNameSource DefaultClrNameSource = ClrNameSource.Alias; // for legacy reasons - internal const string DefaultModelsDirectory = "~/App_Data/Models"; - - /// - /// Initializes a new instance of the class. - /// - private Config() - { - 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"; - - // ensure defaults are initialized for tests - StaticMixinGetterPattern = DefaultStaticMixinGetterPattern; - LanguageVersion = DefaultLanguageVersion; - ModelsNamespace = DefaultModelsNamespace; - ClrNameSource = DefaultClrNameSource; - ModelsDirectory = HostingEnvironment.IsHosted - ? HostingEnvironment.MapPath(DefaultModelsDirectory) - : DefaultModelsDirectory.TrimStart("~/"); - DebugLevel = 0; - - // 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.Dll): - ModelsMode = ModelsMode.Dll; - break; - case nameof(ModelsMode.LiveDll): - ModelsMode = ModelsMode.LiveDll; - 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."); - } - } - - // default: false - EnableApi = ConfigurationManager.AppSettings[prefix + "EnableApi"].InvariantEquals("true"); - AcceptUnsafeModelsDirectory = ConfigurationManager.AppSettings[prefix + "AcceptUnsafeModelsDirectory"].InvariantEquals("true"); - - // default: true - EnableFactory = !ConfigurationManager.AppSettings[prefix + "EnableFactory"].InvariantEquals("false"); - StaticMixinGetters = !ConfigurationManager.AppSettings[prefix + "StaticMixinGetters"].InvariantEquals("false"); - FlagOutOfDateModels = !ConfigurationManager.AppSettings[prefix + "FlagOutOfDateModels"].InvariantEquals("false"); - - // default: initialized above with DefaultModelsNamespace const - var value = ConfigurationManager.AppSettings[prefix + "ModelsNamespace"]; - if (!string.IsNullOrWhiteSpace(value)) - ModelsNamespace = value; - - // default: initialized above with DefaultStaticMixinGetterPattern const - value = ConfigurationManager.AppSettings[prefix + "StaticMixinGetterPattern"]; - if (!string.IsNullOrWhiteSpace(value)) - StaticMixinGetterPattern = value; - - // default: initialized above with DefaultLanguageVersion const - value = ConfigurationManager.AppSettings[prefix + "LanguageVersion"]; - if (!string.IsNullOrWhiteSpace(value)) - { - LanguageVersion lv; - if (!Enum.TryParse(value, true, out lv)) - throw new ConfigurationErrorsException($"Invalid language version \"{value}\"."); - LanguageVersion = lv; - } - - // default: initialized above with DefaultClrNameSource const - value = ConfigurationManager.AppSettings[prefix + "ClrNameSource"]; - if (!string.IsNullOrWhiteSpace(value)) - { - switch (value) - { - case nameof(ClrNameSource.Nothing): - ClrNameSource = ClrNameSource.Nothing; - break; - case nameof(ClrNameSource.Alias): - ClrNameSource = ClrNameSource.Alias; - break; - case nameof(ClrNameSource.RawAlias): - ClrNameSource = ClrNameSource.RawAlias; - break; - case nameof(ClrNameSource.Name): - ClrNameSource = ClrNameSource.Name; - break; - default: - throw new ConfigurationErrorsException($"ClrNameSource \"{value}\" is not a valid source." - + " Note that sources are case-sensitive."); - } - } - - // default: initialized above with DefaultModelsDirectory const - value = ConfigurationManager.AppSettings[prefix + "ModelsDirectory"]; - if (!string.IsNullOrWhiteSpace(value)) - { - var root = HostingEnvironment.IsHosted - ? HostingEnvironment.MapPath("~/") - : Directory.GetCurrentDirectory(); - if (root == null) - throw new ConfigurationErrorsException("Could not determine root directory."); - - // GetModelsDirectory will ensure that the path is safe - ModelsDirectory = GetModelsDirectory(root, value, AcceptUnsafeModelsDirectory); - } - - // default: 0 - value = ConfigurationManager.AppSettings[prefix + "DebugLevel"]; - if (!string.IsNullOrWhiteSpace(value)) - { - int debugLevel; - if (!int.TryParse(value, out 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; - } - - /// - /// Initializes a new instance of the class. - /// - public Config( - bool enable = false, - ModelsMode modelsMode = ModelsMode.Nothing, - bool enableApi = true, - string modelsNamespace = null, - bool enableFactory = true, - LanguageVersion languageVersion = DefaultLanguageVersion, - bool staticMixinGetters = true, - string staticMixinGetterPattern = null, - bool flagOutOfDateModels = true, - ClrNameSource clrNameSource = DefaultClrNameSource, - string modelsDirectory = null, - bool acceptUnsafeModelsDirectory = false, - int debugLevel = 0) - { - Enable = enable; - ModelsMode = modelsMode; - - EnableApi = enableApi; - ModelsNamespace = string.IsNullOrWhiteSpace(modelsNamespace) ? DefaultModelsNamespace : modelsNamespace; - EnableFactory = enableFactory; - LanguageVersion = languageVersion; - StaticMixinGetters = staticMixinGetters; - StaticMixinGetterPattern = string.IsNullOrWhiteSpace(staticMixinGetterPattern) ? DefaultStaticMixinGetterPattern : staticMixinGetterPattern; - FlagOutOfDateModels = flagOutOfDateModels; - ClrNameSource = clrNameSource; - ModelsDirectory = string.IsNullOrWhiteSpace(modelsDirectory) ? DefaultModelsDirectory : modelsDirectory; - AcceptUnsafeModelsDirectory = acceptUnsafeModelsDirectory; - DebugLevel = debugLevel; - } - - // internal for tests - internal static string GetModelsDirectory(string root, string config, bool acceptUnsafe) - { - // making sure it is safe, ie under the website root, - // unless AcceptUnsafeModelsDirectory and then everything is OK. - - if (!Path.IsPathRooted(root)) - throw new ConfigurationErrorsException($"Root is not rooted \"{root}\"."); - - if (config.StartsWith("~/")) - { - var dir = Path.Combine(root, config.TrimStart("~/")); - - // sanitize - GetFullPath will take care of any relative - // segments in path, eg '../../foo.tmp' - it may throw a SecurityException - // if the combined path reaches illegal parts of the filesystem - dir = Path.GetFullPath(dir); - root = Path.GetFullPath(root); - - if (!dir.StartsWith(root) && !acceptUnsafe) - throw new ConfigurationErrorsException($"Invalid models directory \"{config}\"."); - - return dir; - } - - if (acceptUnsafe) - return Path.GetFullPath(config); - - throw new ConfigurationErrorsException($"Invalid models directory \"{config}\"."); - } - - /// - /// Gets a value indicating whether the whole models experience is enabled. - /// - /// - /// If this is false then absolutely nothing happens. - /// Default value is false which means that unless we have this setting, nothing happens. - /// - public bool Enable { get; } - - /// - /// Gets the models mode. - /// - public ModelsMode ModelsMode { get; } - - /// - /// Gets a value indicating whether to serve the API. - /// - public bool ApiServer => EnableApi && ApiInstalled && IsDebug; - - /// - /// Gets a value indicating whether to enable the API. - /// - /// - /// Default value is true. - /// The API is used by the Visual Studio extension and the console tool to talk to Umbraco - /// and retrieve the content types. It needs to be enabled so the extension & tool can work. - /// - public bool EnableApi { get; } - - /// - /// Gets a value indicating whether the API is installed. - /// - // fixme - this is now always true as the API is part of Core - public bool ApiInstalled => true; - - /// - /// Gets a value indicating whether system.web/compilation/@debug is true. - /// - public bool IsDebug - { - get - { - var section = (CompilationSection) ConfigurationManager.GetSection("system.web/compilation"); - return section != null && section.Debug; - } - } - - /// - /// Gets the models namespace. - /// - /// That value could be overriden by other (attribute in user's code...). Return default if no value was supplied. - public string ModelsNamespace { get; } - - /// - /// Gets a value indicating whether we should enable the models factory. - /// - /// Default value is true because no factory is enabled by default in Umbraco. - public bool EnableFactory { get; } - - /// - /// Gets the Roslyn parser language version. - /// - /// Default value is CSharp6. - public LanguageVersion LanguageVersion { get; } - - /// - /// Gets a value indicating whether to generate static mixin getters. - /// - /// Default value is false for backward compat reaons. - public bool StaticMixinGetters { get; } - - /// - /// Gets the string pattern for mixin properties static getter name. - /// - /// Default value is "GetXxx". Standard string format. - public string StaticMixinGetterPattern { get; } - - /// - /// Gets a value indicating whether we should flag out-of-date models. - /// - /// 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; } - - /// - /// Gets the CLR name source. - /// - public ClrNameSource ClrNameSource { get; } - - /// - /// Gets the models directory. - /// - /// Default is ~/App_Data/Models but that can be changed. - public string ModelsDirectory { get; } - - /// - /// Gets a value indicating whether to accept an unsafe value for ModelsDirectory. - /// - /// An unsafe value is an absolute path, or a relative path pointing outside - /// of the website root. - public bool AcceptUnsafeModelsDirectory { get; } - - /// - /// Gets a value indicating the debug log level. - /// - /// 0 means minimal (safe on live site), anything else means more and more details (maybe not safe). - public int DebugLevel { get; } - } -} diff --git a/src/Umbraco.ModelsBuilder/Configuration/ModelsMode.cs b/src/Umbraco.ModelsBuilder/Configuration/ModelsMode.cs deleted file mode 100644 index e04c4dee90..0000000000 --- a/src/Umbraco.ModelsBuilder/Configuration/ModelsMode.cs +++ /dev/null @@ -1,52 +0,0 @@ -namespace Umbraco.ModelsBuilder.Configuration -{ - /// - /// Defines the models generation modes. - /// - public enum ModelsMode - { - /// - /// Do not generate models. - /// - Nothing = 0, // default value - - /// - /// Generate models in memory. - /// When: a content type change occurs. - /// - /// The app does not restart. Models are available in views exclusively. - PureLive, - - /// - /// Generate models in AppData. - /// When: generation is triggered. - /// - /// Generation can be triggered from the dashboard. The app does not restart. - /// Models are not compiled and thus are not available to the project. - AppData, - - /// - /// Generate models in AppData. - /// When: a content type change occurs, or generation is triggered. - /// - /// Generation can be triggered from the dashboard. The app does not restart. - /// Models are not compiled and thus are not available to the project. - LiveAppData, - - /// - /// Generates models in AppData and compiles them into a Dll into ~/bin (the app restarts). - /// When: generation is triggered. - /// - /// Generation can be triggered from the dashboard. The app does restart. Models - /// are available to the entire project. - Dll, - - /// - /// Generates models in AppData and compiles them into a Dll into ~/bin (the app restarts). - /// When: a content type change occurs, or generation is triggered. - /// - /// Generation can be triggered from the dashboard. The app does restart. Models - /// are available to the entire project. - LiveDll - } -} diff --git a/src/Umbraco.ModelsBuilder/Configuration/ModelsModeExtensions.cs b/src/Umbraco.ModelsBuilder/Configuration/ModelsModeExtensions.cs deleted file mode 100644 index be609c0548..0000000000 --- a/src/Umbraco.ModelsBuilder/Configuration/ModelsModeExtensions.cs +++ /dev/null @@ -1,51 +0,0 @@ -namespace Umbraco.ModelsBuilder.Configuration -{ - /// - /// Provides extensions for the enumeration. - /// - public static class ModelsModeExtensions - { - /// - /// Gets a value indicating whether the mode is LiveAnything or PureLive. - /// - public static bool IsLive(this ModelsMode modelsMode) - { - return - modelsMode == ModelsMode.PureLive - || modelsMode == ModelsMode.LiveDll - || modelsMode == ModelsMode.LiveAppData; - } - - /// - /// Gets a value indicating whether the mode is LiveAnything but not PureLive. - /// - public static bool IsLiveNotPure(this ModelsMode modelsMode) - { - return - modelsMode == ModelsMode.LiveDll - || modelsMode == ModelsMode.LiveAppData; - } - - /// - /// Gets a value indicating whether the mode is [Live]Dll. - /// - public static bool IsAnyDll(this ModelsMode modelsMode) - { - return - modelsMode == ModelsMode.Dll - || modelsMode == ModelsMode.LiveDll; - } - - /// - /// Gets a value indicating whether the mode supports explicit generation (as opposed to pure live). - /// - public static bool SupportsExplicitGeneration(this ModelsMode modelsMode) - { - return - modelsMode == ModelsMode.Dll - || modelsMode == ModelsMode.LiveDll - || modelsMode == ModelsMode.AppData - || modelsMode == ModelsMode.LiveAppData; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.ModelsBuilder/Configuration/UmbracoConfigExtensions.cs b/src/Umbraco.ModelsBuilder/Configuration/UmbracoConfigExtensions.cs deleted file mode 100644 index acc587e779..0000000000 --- a/src/Umbraco.ModelsBuilder/Configuration/UmbracoConfigExtensions.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Threading; -using Umbraco.Core.Configuration; - -namespace Umbraco.ModelsBuilder.Configuration -{ - /// - /// Provides extension methods for the class. - /// - public static class UmbracoConfigExtensions - { - private static Config _config; - - /// - /// Gets the models builder configuration. - /// - /// The umbraco configuration. - /// The models builder configuration. - /// Getting the models builder configuration freezes its state, - /// and any attempt at modifying the configuration using the Setup method - /// will be ignored. - public static Config ModelsBuilder(this UmbracoConfig umbracoConfig) - { - // capture the current Config2.Default value, cannot change anymore - LazyInitializer.EnsureInitialized(ref _config, () => Config.Value); - return _config; - } - - // internal for tests - internal static void ResetConfig() - { - _config = null; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.ModelsBuilder/Dashboard/BuilderDashboardHelper.cs b/src/Umbraco.ModelsBuilder/Dashboard/BuilderDashboardHelper.cs deleted file mode 100644 index 9e5741805e..0000000000 --- a/src/Umbraco.ModelsBuilder/Dashboard/BuilderDashboardHelper.cs +++ /dev/null @@ -1,91 +0,0 @@ -using System; -using System.Text; -using Umbraco.Core.Configuration; -using Umbraco.ModelsBuilder.Configuration; -using Umbraco.ModelsBuilder.Umbraco; - -namespace Umbraco.ModelsBuilder.Dashboard -{ - internal static class BuilderDashboardHelper - { - public static bool CanGenerate() - { - return UmbracoConfig.For.ModelsBuilder().ModelsMode.SupportsExplicitGeneration(); - } - - public static bool GenerateCausesRestart() - { - return UmbracoConfig.For.ModelsBuilder().ModelsMode.IsAnyDll(); - } - - public static bool AreModelsOutOfDate() - { - return OutOfDateModelsStatus.IsOutOfDate; - } - - public static string LastError() - { - return ModelsGenerationError.GetLastError(); - } - - public static string Text() - { - var config = UmbracoConfig.For.ModelsBuilder(); - if (!config.Enable) - return "ModelsBuilder is disabled
(the .Enable key is missing, or its value is not 'true')."; - - var sb = new StringBuilder(); - - sb.Append("ModelsBuilder is enabled, with the following configuration:"); - - sb.Append("
    "); - - sb.Append("
  • The models factory is "); - sb.Append(config.EnableFactory || config.ModelsMode == ModelsMode.PureLive - ? "enabled" - : "not enabled. Umbraco will not use models"); - sb.Append(".
  • "); - - sb.Append("
  • The API is "); - if (config.ApiInstalled && config.EnableApi) - { - sb.Append("installed and enabled"); - if (!config.IsDebug) sb.Append(".
    However, the API runs only with debug compilation mode"); - } - else if (config.ApiInstalled || config.EnableApi) - sb.Append(config.ApiInstalled ? "installed but not enabled" : "enabled but not installed"); - else sb.Append("neither installed nor enabled"); - sb.Append(".
    "); - if (!config.ApiServer) - sb.Append("External tools such as Visual Studio cannot use the API"); - else - sb.Append("The API endpoint is open on this server"); - sb.Append(".
  • "); - - sb.Append(config.ModelsMode != ModelsMode.Nothing - ? $"
  • {config.ModelsMode} models are enabled.
  • " - : "
  • No models mode is specified: models will not be generated.
  • "); - - sb.Append($"
  • Models namespace is {config.ModelsNamespace}.
  • "); - - sb.Append("
  • Static mixin getters are "); - sb.Append(config.StaticMixinGetters ? "enabled" : "disabled"); - if (config.StaticMixinGetters) - { - sb.Append(". The pattern for getters is "); - sb.Append(string.IsNullOrWhiteSpace(config.StaticMixinGetterPattern) - ? "not configured (will use default)" - : $"\"{config.StaticMixinGetterPattern}\""); - } - sb.Append(".
  • "); - - sb.Append("
  • Tracking of out-of-date models is "); - sb.Append(config.FlagOutOfDateModels ? "enabled" : "not enabled"); - sb.Append(".
  • "); - - sb.Append("
"); - - return sb.ToString(); - } - } -} diff --git a/src/Umbraco.ModelsBuilder/EnumerableExtensions.cs b/src/Umbraco.ModelsBuilder/EnumerableExtensions.cs deleted file mode 100644 index da77bfa958..0000000000 --- a/src/Umbraco.ModelsBuilder/EnumerableExtensions.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Umbraco.ModelsBuilder -{ - public static class EnumerableExtensions - { - public static void RemoveAll(this IList list, Func predicate) - { - for (var i = 0; i < list.Count; i++) - { - if (predicate(list[i])) - { - list.RemoveAt(i--); // i-- is important here! - } - } - } - - public static IEnumerable And(this IEnumerable enumerable, T item) - { - foreach (var x in enumerable) yield return x; - yield return item; - } - - public static IEnumerable AndIfNotNull(this IEnumerable enumerable, T item) - where T : class - { - foreach (var x in enumerable) yield return x; - if (item != null) - yield return item; - } - } -} diff --git a/src/Umbraco.ModelsBuilder/IgnoreContentTypeAttribute.cs b/src/Umbraco.ModelsBuilder/IgnoreContentTypeAttribute.cs deleted file mode 100644 index e5ab3a2e35..0000000000 --- a/src/Umbraco.ModelsBuilder/IgnoreContentTypeAttribute.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Security.Policy; -using System.Text; -using System.Threading.Tasks; -using Umbraco.ModelsBuilder; - -namespace Umbraco.ModelsBuilder -{ - // for the time being it's all-or-nothing - // when the content type is ignored then - // - no class is generated for that content type - // - no class is generated for any child of that class - // - no interface is generated for that content type as a mixin - // - and it is ignored as a mixin ie its properties are not generated - // in the future we may way to do - // [assembly:IgnoreContentType("foo", ContentTypeIgnorable.ContentType|ContentTypeIgnorable.Mixin|ContentTypeIgnorable.MixinProperties)] - // so that we can - // - generate a class for that content type, or not - // - if not generated, generate children or not - // - if generate children, include properties or not - // - generate an interface for that content type as a mixin - // - if not generated, still generate properties in content types implementing the mixin or not - // but... I'm not even sure it makes sense - // if we don't want it... we don't want it. - - // about ignoring - // - content (don't generate the content, use as mixin) - // - mixin (don't generate the interface, use the properties) - // - mixin properties (generate the interface, not the properties) - // - mixin: local only or children too... - - /// - /// Indicates that no model should be generated for a specified content type alias. - /// - /// When a content type is ignored, its descendants are also ignored. - /// Supports trailing wildcard eg "foo*". - [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true, Inherited = false)] - public sealed class IgnoreContentTypeAttribute : Attribute - { - public IgnoreContentTypeAttribute(string alias /*, bool ignoreContent = true, bool ignoreMixin = true, bool ignoreMixinProperties = true*/) - {} - } -} - diff --git a/src/Umbraco.ModelsBuilder/IgnorePropertyTypeAttribute.cs b/src/Umbraco.ModelsBuilder/IgnorePropertyTypeAttribute.cs deleted file mode 100644 index 4dce0f9b7f..0000000000 --- a/src/Umbraco.ModelsBuilder/IgnorePropertyTypeAttribute.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Umbraco.ModelsBuilder -{ - /// - /// Indicates that no model should be generated for a specified property type alias. - /// - /// Supports trailing wildcard eg "foo*". - [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] - public sealed class IgnorePropertyTypeAttribute : Attribute - { - public IgnorePropertyTypeAttribute(string alias) - {} - } -} diff --git a/src/Umbraco.ModelsBuilder/ImplementContentTypeAttribute.cs b/src/Umbraco.ModelsBuilder/ImplementContentTypeAttribute.cs deleted file mode 100644 index 142f115b07..0000000000 --- a/src/Umbraco.ModelsBuilder/ImplementContentTypeAttribute.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; - -namespace Umbraco.ModelsBuilder -{ - // NOTE - // that attribute should inherit from PublishedModelAttribute - // so we do not have different syntaxes - // but... is sealed at the moment. - - /// - /// Indicates that a (partial) class defines the model type for a specific alias. - /// - /// Though a model will be generated - so that is the way to register a rename. - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] - public sealed class ImplementContentTypeAttribute : Attribute - { - public ImplementContentTypeAttribute(string alias) - { } - } -} diff --git a/src/Umbraco.ModelsBuilder/ImplementPropertyTypeAttribute.cs b/src/Umbraco.ModelsBuilder/ImplementPropertyTypeAttribute.cs deleted file mode 100644 index c5d8f8cad4..0000000000 --- a/src/Umbraco.ModelsBuilder/ImplementPropertyTypeAttribute.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Umbraco.ModelsBuilder -{ - /// - /// Indicates that a property implements a given property alias. - /// - /// And therefore it should not be generated. - [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)] - public sealed class ImplementPropertyTypeAttribute : Attribute - { - public ImplementPropertyTypeAttribute(string alias) - { - Alias = alias; - } - - public string Alias { get; private set; } - } -} diff --git a/src/Umbraco.ModelsBuilder/ModelsBaseClassAttribute.cs b/src/Umbraco.ModelsBuilder/ModelsBaseClassAttribute.cs deleted file mode 100644 index 3c401b7fdb..0000000000 --- a/src/Umbraco.ModelsBuilder/ModelsBaseClassAttribute.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; - -namespace Umbraco.ModelsBuilder -{ - /// - /// Indicates the default base class for models. - /// - /// Otherwise it is PublishedContentModel. Would make sense to inherit from PublishedContentModel. - [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false, Inherited = false)] - public sealed class ModelsBaseClassAttribute : Attribute - { - public ModelsBaseClassAttribute(Type type) - {} - } -} - diff --git a/src/Umbraco.ModelsBuilder/ModelsBuilderAssemblyAttribute.cs b/src/Umbraco.ModelsBuilder/ModelsBuilderAssemblyAttribute.cs deleted file mode 100644 index ed956852f8..0000000000 --- a/src/Umbraco.ModelsBuilder/ModelsBuilderAssemblyAttribute.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; - -namespace Umbraco.ModelsBuilder -{ - /// - /// Indicates that an Assembly is a Models Builder assembly. - /// - [AttributeUsage(AttributeTargets.Assembly /*, AllowMultiple = false, Inherited = false*/)] - public sealed class ModelsBuilderAssemblyAttribute : Attribute - { - /// - /// Gets or sets a value indicating whether the assembly is a PureLive assembly. - /// - /// A Models Builder assembly can be either PureLive or normal Dll. - public bool PureLive { get; set; } - - /// - /// Gets or sets a hash value representing the state of the custom source code files - /// and the Umbraco content types that were used to generate and compile the assembly. - /// - public string SourceHash { get; set; } - } -} diff --git a/src/Umbraco.ModelsBuilder/ModelsNamespaceAttribute.cs b/src/Umbraco.ModelsBuilder/ModelsNamespaceAttribute.cs deleted file mode 100644 index 1b1d62d9bc..0000000000 --- a/src/Umbraco.ModelsBuilder/ModelsNamespaceAttribute.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; - -namespace Umbraco.ModelsBuilder -{ - /// - /// Indicates the models namespace. - /// - /// Will override anything else that might come from settings. - [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false, Inherited = false)] - public sealed class ModelsNamespaceAttribute : Attribute - { - public ModelsNamespaceAttribute(string modelsNamespace) - {} - } -} - diff --git a/src/Umbraco.ModelsBuilder/ModelsUsingAttribute.cs b/src/Umbraco.ModelsBuilder/ModelsUsingAttribute.cs deleted file mode 100644 index 8fe1335631..0000000000 --- a/src/Umbraco.ModelsBuilder/ModelsUsingAttribute.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Security.Policy; -using System.Text; -using System.Threading.Tasks; -using Umbraco.ModelsBuilder; - -namespace Umbraco.ModelsBuilder -{ - /// - /// Indicates namespaces that should be used in generated models (in using clauses). - /// - [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true, Inherited = false)] - public sealed class ModelsUsingAttribute : Attribute - { - public ModelsUsingAttribute(string usingNamespace) - {} - } -} - diff --git a/src/Umbraco.ModelsBuilder/Properties/AssemblyInfo.cs b/src/Umbraco.ModelsBuilder/Properties/AssemblyInfo.cs index a2f8d1ae1e..8ec221bffb 100644 --- a/src/Umbraco.ModelsBuilder/Properties/AssemblyInfo.cs +++ b/src/Umbraco.ModelsBuilder/Properties/AssemblyInfo.cs @@ -1,14 +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.ModelsBuilder")] -[assembly: AssemblyDescription("Umbraco ModelsBuilder")] +[assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] -[assembly: AssemblyProduct("Umbraco CMS")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Umbraco.ModelsBuilder")] +[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)] -[assembly: Guid("7020a059-c0d1-43a0-8efd-23591a0c9af6")] -// code analysis -// IDE1006 is broken, wants _value syntax for consts, etc - and it's even confusing ppl at MS, kill it -[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "~_~")] +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("52ac0ba8-a60e-4e36-897b-e8b97a54ed1c")] + +// 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.ModelsBuilder/PublishedElementExtensions.cs b/src/Umbraco.ModelsBuilder/PublishedElementExtensions.cs deleted file mode 100644 index f3320b5dfb..0000000000 --- a/src/Umbraco.ModelsBuilder/PublishedElementExtensions.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; -using System.Linq.Expressions; -using System.Reflection; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Web; - -namespace Umbraco.ModelsBuilder -{ - /// - /// Provides extension methods to models. - /// - public static class PublishedElementExtensions - { - /// - /// Gets the value of a property. - /// - public static TValue Value(this TModel model, Expression> property, string culture = ".", string segment = ".") - where TModel : IPublishedElement - { - var alias = GetAlias(model, property); - return model.Value(alias, culture, segment); - } - - private static string GetAlias(TModel model, Expression> property) - { - if (property.NodeType != ExpressionType.Lambda) - throw new ArgumentException("Not a proper lambda expression (lambda).", nameof(property)); - - var lambda = (LambdaExpression) property; - var lambdaBody = lambda.Body; - - if (lambdaBody.NodeType != ExpressionType.MemberAccess) - throw new ArgumentException("Not a proper lambda expression (body).", nameof(property)); - - var memberExpression = (MemberExpression) lambdaBody; - if (memberExpression.Expression.NodeType != ExpressionType.Parameter) - throw new ArgumentException("Not a proper lambda expression (member).", nameof(property)); - - var member = memberExpression.Member; - - 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/PublishedPropertyTypeExtensions.cs b/src/Umbraco.ModelsBuilder/PublishedPropertyTypeExtensions.cs deleted file mode 100644 index b67ba54432..0000000000 --- a/src/Umbraco.ModelsBuilder/PublishedPropertyTypeExtensions.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core; -using Umbraco.Core.Models.PublishedContent; - -namespace Umbraco.ModelsBuilder -{ - public static class PublishedPropertyTypeExtensions - { - // fixme - need to rewrite that one - we don't have prevalues anymore - //public static KeyValuePair[] PreValues(this PublishedPropertyType propertyType) - //{ - // return ApplicationContext.Current.Services.DataTypeService - // .GetPreValuesCollectionByDataTypeId(propertyType.DataType.Id) - // .PreValuesAsArray - // .Select(x => new KeyValuePair(x.Id, x.Value)) - // .ToArray(); - //} - } -} diff --git a/src/Umbraco.ModelsBuilder/PureLiveAssemblyAttribute.cs b/src/Umbraco.ModelsBuilder/PureLiveAssemblyAttribute.cs deleted file mode 100644 index dfe369dc21..0000000000 --- a/src/Umbraco.ModelsBuilder/PureLiveAssemblyAttribute.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace Umbraco.ModelsBuilder -{ - /// - /// Indicates that an Assembly is a PureLive models assembly. - /// - /// Though technically not required, ie models will work without it, the attribute - /// can be used by Umbraco view models binder to figure out whether the model type comes - /// from a PureLive Assembly. - [Obsolete("Should use ModelsBuilderAssemblyAttribute but that requires a change in Umbraco Core.")] - [AttributeUsage(AttributeTargets.Assembly /*, AllowMultiple = false, Inherited = false*/)] - public sealed class PureLiveAssemblyAttribute : Attribute - { } -} diff --git a/src/Umbraco.ModelsBuilder/ReferencedAssemblies.cs b/src/Umbraco.ModelsBuilder/ReferencedAssemblies.cs deleted file mode 100644 index 42e8b3b9c9..0000000000 --- a/src/Umbraco.ModelsBuilder/ReferencedAssemblies.cs +++ /dev/null @@ -1,137 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Web.Compilation; -using System.Web.Hosting; -using Microsoft.CodeAnalysis; -using Umbraco.Core; - -namespace Umbraco.ModelsBuilder -{ - internal static class ReferencedAssemblies - { - private static readonly Lazy> LazyLocations; - private static readonly Lazy> LazyReferences; - - static ReferencedAssemblies() - { - LazyLocations = new Lazy>(() => HostingEnvironment.IsHosted - ? GetAllReferencedAssembliesLocationFromBuildManager() - : GetAllReferencedAssembliesFromDomain()); - - LazyReferences = new Lazy>(() => Locations - .Select(x => MetadataReference.CreateFromFile(x)) - .ToArray()); - } - - /// - /// Gets the assembly locations of all the referenced assemblies, that - /// are not dynamic, and have a non-null nor empty location. - /// - public static IEnumerable Locations => LazyLocations.Value; - - /// - /// Gets the metadata reference of all the referenced assemblies. - /// - public static IEnumerable References => LazyReferences.Value; - - // hosted, get referenced assemblies from the BuildManader and filter - private static IEnumerable GetAllReferencedAssembliesLocationFromBuildManager() - { - return BuildManager.GetReferencedAssemblies() - .Cast() - .Where(x => !x.IsDynamic && !x.Location.IsNullOrWhiteSpace()) - .Select(x => x.Location) - .And(typeof(ReferencedAssemblies).Assembly.Location) // always include ourselves - .Distinct() - .ToList(); - } - - // non-hosted, do our best - private static IEnumerable GetAllReferencedAssembliesFromDomain() - { - //TODO: This method has bugs since I've been stuck in an infinite loop with it, though this shouldn't - // execute while in the web application anyways. - - var assemblies = new List(); - var tmp1 = new List(); - var failed = new List(); - foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies() - .Where(x => x.IsDynamic == false) - .Where(x => !string.IsNullOrWhiteSpace(x.Location))) // though... IsDynamic should be enough? - { - assemblies.Add(assembly); - tmp1.Add(assembly); - } - - // fixme - AssemblyUtility questions - // - should we also load everything that's in the same directory? - // - do we want to load in the current app domain? - // - if this runs within Umbraco then we have already loaded them all? - - while (tmp1.Count > 0) - { - var tmp2 = tmp1 - .SelectMany(x => x.GetReferencedAssemblies()) - .Distinct() - .Where(x => assemblies.All(xx => x.FullName != xx.FullName)) // we don't have it already - .Where(x => failed.All(xx => x.FullName != xx.FullName)) // it hasn't failed already - .ToArray(); - tmp1.Clear(); - foreach (var assemblyName in tmp2) - { - try - { - var assembly = AppDomain.CurrentDomain.Load(assemblyName); - assemblies.Add(assembly); - tmp1.Add(assembly); - } - catch - { - failed.Add(assemblyName); - } - } - } - return assemblies.Select(x => x.Location).Distinct(); - } - - - // ---- - - private static IEnumerable GetDeepReferencedAssemblies(Assembly assembly) - { - var visiting = new Stack(); - var visited = new HashSet(); - - visiting.Push(assembly); - visited.Add(assembly); - while (visiting.Count > 0) - { - var visAsm = visiting.Pop(); - foreach (var refAsm in visAsm.GetReferencedAssemblies() - .Select(TryLoad) - .Where(x => x != null && visited.Contains(x) == false)) - { - yield return refAsm; - visiting.Push(refAsm); - visited.Add(refAsm); - } - } - } - - private static Assembly TryLoad(AssemblyName name) - { - try - { - return AppDomain.CurrentDomain.Load(name); - } - catch (Exception) - { - //Console.WriteLine(name); - return null; - } - } - - } -} diff --git a/src/Umbraco.ModelsBuilder/RenameContentTypeAttribute.cs b/src/Umbraco.ModelsBuilder/RenameContentTypeAttribute.cs deleted file mode 100644 index 0f985e70b3..0000000000 --- a/src/Umbraco.ModelsBuilder/RenameContentTypeAttribute.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Umbraco.ModelsBuilder -{ - /// - /// Indicates a model name for a specified content alias. - /// - [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true, Inherited = false)] - public sealed class RenameContentTypeAttribute : Attribute - { - public RenameContentTypeAttribute(string alias, string name) - {} - } -} diff --git a/src/Umbraco.ModelsBuilder/RenamePropertyTypeAttribute.cs b/src/Umbraco.ModelsBuilder/RenamePropertyTypeAttribute.cs deleted file mode 100644 index 0d8fd31b63..0000000000 --- a/src/Umbraco.ModelsBuilder/RenamePropertyTypeAttribute.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Umbraco.ModelsBuilder -{ - /// - /// Indicates a model name for a specified property alias. - /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] - public sealed class RenamePropertyTypeAttribute : Attribute - { - public RenamePropertyTypeAttribute(string alias, string name) - {} - } -} diff --git a/src/Umbraco.ModelsBuilder/TypeExtensions.cs b/src/Umbraco.ModelsBuilder/TypeExtensions.cs deleted file mode 100644 index d3b3ff6b4e..0000000000 --- a/src/Umbraco.ModelsBuilder/TypeExtensions.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; - -namespace Umbraco.ModelsBuilder -{ - internal static class TypeExtensions - { - /// - /// Creates a generic instance of a generic type with the proper actual type of an object. - /// - /// A generic type such as Something{} - /// An object whose type is used as generic type param. - /// Arguments for the constructor. - /// A generic instance of the generic type with the proper type. - /// Usage... typeof (Something{}).CreateGenericInstance(object1, object2, object3) will return - /// a Something{Type1} if object1.GetType() is Type1. - public static object CreateGenericInstance(this Type genericType, object typeParmObj, params object[] ctorArgs) - { - var type = genericType.MakeGenericType(typeParmObj.GetType()); - return Activator.CreateInstance(type, ctorArgs); - } - } -} diff --git a/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj b/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj index b9a5890d57..d1d3ca205d 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj +++ b/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj @@ -4,13 +4,14 @@ Debug AnyCPU - {7020A059-C0D1-43A0-8EFD-23591A0C9AF6} + {52AC0BA8-A60E-4E36-897B-E8B97A54ED1C} Library Properties Umbraco.ModelsBuilder Umbraco.ModelsBuilder v4.7.2 512 + true true @@ -28,13 +29,10 @@ TRACE prompt 4 - bin\Release\Umbraco.ModelsBuilder.xml - - @@ -43,76 +41,7 @@ - - Properties\SolutionInfo.cs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 2.8.0 - - - - - {31785bc3-256c-4613-b2f5-a1b0bdded8c1} - Umbraco.Core - - - {651e1350-91b6-44b7-bd60-7207006d7003} - Umbraco.Web - \ No newline at end of file diff --git a/src/Umbraco.ModelsBuilder/Umbraco/HashCombiner.cs b/src/Umbraco.ModelsBuilder/Umbraco/HashCombiner.cs deleted file mode 100644 index e11662eb24..0000000000 --- a/src/Umbraco.ModelsBuilder/Umbraco/HashCombiner.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using System.Globalization; - -namespace Umbraco.ModelsBuilder.Umbraco -{ - // because, of course, it's internal in Umbraco - // see also System.Web.Util.HashCodeCombiner - class HashCombiner - { - private long _combinedHash = 5381L; - - public void Add(int i) - { - _combinedHash = ((_combinedHash << 5) + _combinedHash) ^ i; - } - - public void Add(object o) - { - Add(o.GetHashCode()); - } - - public void Add(DateTime d) - { - Add(d.GetHashCode()); - } - - public void Add(string s) - { - if (s == null) return; - Add((StringComparer.InvariantCulture).GetHashCode(s)); - } - - public string GetCombinedHashCode() - { - return _combinedHash.ToString("x", CultureInfo.InvariantCulture); - } - } -} diff --git a/src/Umbraco.ModelsBuilder/Umbraco/HashHelper.cs b/src/Umbraco.ModelsBuilder/Umbraco/HashHelper.cs deleted file mode 100644 index c530cbbd6b..0000000000 --- a/src/Umbraco.ModelsBuilder/Umbraco/HashHelper.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Umbraco.ModelsBuilder.Building; - -namespace Umbraco.ModelsBuilder.Umbraco -{ - class HashHelper - { - public static string Hash(IDictionary ourFiles, IEnumerable typeModels) - { - var hash = new HashCombiner(); - - foreach (var kvp in ourFiles) - hash.Add(kvp.Key + "::" + kvp.Value); - - // see Umbraco.ModelsBuilder.Umbraco.Application for what's important to hash - // ie what comes from Umbraco (not computed by ModelsBuilder) and makes a difference - - foreach (var typeModel in typeModels.OrderBy(x => x.Alias)) - { - hash.Add("--- CONTENT TYPE MODEL ---"); - hash.Add(typeModel.Id); - hash.Add(typeModel.Alias); - hash.Add(typeModel.ClrName); - hash.Add(typeModel.ParentId); - hash.Add(typeModel.Name); - hash.Add(typeModel.Description); - hash.Add(typeModel.ItemType.ToString()); - hash.Add("MIXINS:" + string.Join(",", typeModel.MixinTypes.OrderBy(x => x.Id).Select(x => x.Id))); - - foreach (var prop in typeModel.Properties.OrderBy(x => x.Alias)) - { - hash.Add("--- PROPERTY ---"); - hash.Add(prop.Alias); - hash.Add(prop.ClrName); - hash.Add(prop.Name); - hash.Add(prop.Description); - hash.Add(prop.ModelClrType.ToString()); // see ModelType tests, want ToString() not FullName - } - } - - return hash.GetCombinedHashCode(); - } - } -} diff --git a/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProvider.cs b/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProvider.cs deleted file mode 100644 index b6c37a3558..0000000000 --- a/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProvider.cs +++ /dev/null @@ -1,142 +0,0 @@ -using System; -using System.Threading; -using System.Web; -using System.Web.Hosting; -using Umbraco.Core.Composing; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.ModelsBuilder.Configuration; -using Umbraco.ModelsBuilder.Umbraco; -using Umbraco.Web.Cache; - -// will install only if configuration says it needs to be installed -[assembly: PreApplicationStartMethod(typeof(LiveModelsProviderModule), "Install")] - -namespace Umbraco.ModelsBuilder.Umbraco -{ - // supports LiveDll and LiveAppData - but not PureLive - public sealed class LiveModelsProvider - { - private static UmbracoServices _umbracoServices; - private static Mutex _mutex; - private static int _req; - - internal static bool IsEnabled - { - get - { - var config = UmbracoConfig.For.ModelsBuilder(); - return config.ModelsMode.IsLiveNotPure(); - // we do not manage pure live here - } - } - - internal static void Install(UmbracoServices umbracoServices) - { - // just be sure - if (!IsEnabled) - return; - - _umbracoServices = umbracoServices; - - // initialize mutex - // ApplicationId will look like "/LM/W3SVC/1/Root/AppName" - // name is system-wide and must be less than 260 chars - var name = HostingEnvironment.ApplicationID + "/UmbracoLiveModelsProvider"; - _mutex = new Mutex(false, name); - - // anything changes, and we want to re-generate models. - ContentTypeCacheRefresher.CacheUpdated += RequestModelsGeneration; - DataTypeCacheRefresher.CacheUpdated += RequestModelsGeneration; - - // at the end of a request since we're restarting the pool - // NOTE - this does NOT trigger - see module below - //umbracoApplication.EndRequest += GenerateModelsIfRequested; - } - - // NOTE - // Using HttpContext Items fails because CacheUpdated triggers within - // some asynchronous backend task where we seem to have no HttpContext. - - // So we use a static (non request-bound) var to register that models - // need to be generated. Could be by another request. Anyway. We could - // have collisions but... you know the risk. - - private static void RequestModelsGeneration(object sender, EventArgs args) - { - //HttpContext.Current.Items[this] = true; - Current.Logger.Debug("Requested to generate models."); - Interlocked.Exchange(ref _req, 1); - } - - public static void GenerateModelsIfRequested(object sender, EventArgs args) - { - //if (HttpContext.Current.Items[this] == null) return; - if (Interlocked.Exchange(ref _req, 0) == 0) return; - - // cannot use a simple lock here because we don't want another AppDomain - // to generate while we do... and there could be 2 AppDomains if the app restarts. - - try - { - Current.Logger.Debug("Generate models..."); - const int timeout = 2*60*1000; // 2 mins - _mutex.WaitOne(timeout); // wait until it is safe, and acquire - Current.Logger.Info("Generate models now."); - GenerateModels(); - ModelsGenerationError.Clear(); - Current.Logger.Info("Generated."); - } - catch (TimeoutException) - { - Current.Logger.Warn("Timeout, models were NOT generated."); - } - catch (Exception e) - { - ModelsGenerationError.Report("Failed to build Live models.", e); - Current.Logger.Error("Failed to generate models.", e); - } - finally - { - _mutex.ReleaseMutex(); // release - } - } - - private static void GenerateModels() - { - var modelsDirectory = UmbracoConfig.For.ModelsBuilder().ModelsDirectory; - - var bin = HostingEnvironment.MapPath("~/bin"); - if (bin == null) - throw new Exception("Panic: bin is null."); - - var config = UmbracoConfig.For.ModelsBuilder(); - - // EnableDllModels will recycle the app domain - but this request will end properly - ModelsBuilderBackOfficeController.GenerateModels(_umbracoServices, modelsDirectory, config.ModelsMode.IsAnyDll() ? bin : null); - } - } - - // have to do this because it's the only way to subscribe to EndRequest, - // module is installed by assembly attribute at the top of this file - public class LiveModelsProviderModule : IHttpModule - { - public void Init(HttpApplication app) - { - app.EndRequest += LiveModelsProvider.GenerateModelsIfRequested; - } - - public void Dispose() - { - // nothing - } - - public static void Install() - { - if (!LiveModelsProvider.IsEnabled) - return; - - HttpApplication.RegisterModule(typeof(LiveModelsProviderModule)); - } - } -} diff --git a/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderBackOfficeController.cs b/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderBackOfficeController.cs deleted file mode 100644 index 19c9bda5da..0000000000 --- a/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderBackOfficeController.cs +++ /dev/null @@ -1,194 +0,0 @@ -using System; -using System.IO; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Runtime.Serialization; -using System.Text; -using System.Web.Hosting; -using Umbraco.Core.Configuration; -using Umbraco.ModelsBuilder.Building; -using Umbraco.ModelsBuilder.Configuration; -using Umbraco.ModelsBuilder.Dashboard; -using Umbraco.Web.Editors; -using Umbraco.Web.WebApi.Filters; - -namespace Umbraco.ModelsBuilder.Umbraco -{ - /// - /// API controller for use in the Umbraco back office with Angular resources - /// - /// - /// We've created a different controller for the backoffice/angular specifically this is to ensure that the - /// correct CSRF security is adhered to for angular and it also ensures that this controller is not subseptipal to - /// global WebApi formatters being changed since this is always forced to only return Angular JSON Specific formats. - /// - [UmbracoApplicationAuthorize(Core.Constants.Applications.Developer)] - public class ModelsBuilderBackOfficeController : UmbracoAuthorizedJsonController - { - private readonly UmbracoServices _umbracoServices; - - public ModelsBuilderBackOfficeController(UmbracoServices umbracoServices) - { - _umbracoServices = umbracoServices; - } - - // invoked by the dashboard - // requires that the user is logged into the backoffice and has access to the developer section - // beware! the name of the method appears in modelsbuilder.controller.js - [System.Web.Http.HttpPost] // use the http one, not mvc, with api controllers! - public HttpResponseMessage BuildModels() - { - try - { - if (!UmbracoConfig.For.ModelsBuilder().ModelsMode.SupportsExplicitGeneration()) - { - var result2 = new BuildResult { Success = false, Message = "Models generation is not enabled." }; - return Request.CreateResponse(HttpStatusCode.OK, result2, Configuration.Formatters.JsonFormatter); - } - - var modelsDirectory = UmbracoConfig.For.ModelsBuilder().ModelsDirectory; - - var bin = HostingEnvironment.MapPath("~/bin"); - if (bin == null) - throw new Exception("Panic: bin is null."); - - // EnableDllModels will recycle the app domain - but this request will end properly - GenerateModels(modelsDirectory, UmbracoConfig.For.ModelsBuilder().ModelsMode.IsAnyDll() ? bin : null); - - ModelsGenerationError.Clear(); - } - catch (Exception e) - { - ModelsGenerationError.Report("Failed to build models.", e); - } - - return Request.CreateResponse(HttpStatusCode.OK, GetDashboardResult(), Configuration.Formatters.JsonFormatter); - } - - // invoked by the back-office - // requires that the user is logged into the backoffice and has access to the developer section - [System.Web.Http.HttpGet] // use the http one, not mvc, with api controllers! - public HttpResponseMessage GetModelsOutOfDateStatus() - { - var status = OutOfDateModelsStatus.IsEnabled - ? (OutOfDateModelsStatus.IsOutOfDate - ? new OutOfDateStatus { Status = OutOfDateType.OutOfDate } - : new OutOfDateStatus { Status = OutOfDateType.Current }) - : new OutOfDateStatus { Status = OutOfDateType.Unknown }; - - return Request.CreateResponse(HttpStatusCode.OK, status, Configuration.Formatters.JsonFormatter); - } - - // invoked by the back-office - // requires that the user is logged into the backoffice and has access to the developer section - // beware! the name of the method appears in modelsbuilder.controller.js - [System.Web.Http.HttpGet] // use the http one, not mvc, with api controllers! - public HttpResponseMessage GetDashboard() - { - return Request.CreateResponse(HttpStatusCode.OK, GetDashboardResult(), Configuration.Formatters.JsonFormatter); - } - - private Dashboard GetDashboardResult() - { - return new Dashboard - { - Enable = UmbracoConfig.For.ModelsBuilder().Enable, - Text = BuilderDashboardHelper.Text(), - CanGenerate = BuilderDashboardHelper.CanGenerate(), - GenerateCausesRestart = BuilderDashboardHelper.GenerateCausesRestart(), - OutOfDateModels = BuilderDashboardHelper.AreModelsOutOfDate(), - LastError = BuilderDashboardHelper.LastError(), - }; - } - - private void GenerateModels(string modelsDirectory, string bin) - { - GenerateModels(_umbracoServices, modelsDirectory, bin); - } - - internal static void GenerateModels(UmbracoServices umbracoServices, string modelsDirectory, string bin) - { - if (!Directory.Exists(modelsDirectory)) - Directory.CreateDirectory(modelsDirectory); - - foreach (var file in Directory.GetFiles(modelsDirectory, "*.generated.cs")) - File.Delete(file); - - var typeModels = umbracoServices.GetAllTypes(); - - var ourFiles = Directory.GetFiles(modelsDirectory, "*.cs").ToDictionary(x => x, File.ReadAllText); - var parseResult = new CodeParser().ParseWithReferencedAssemblies(ourFiles); - var builder = new TextBuilder(typeModels, parseResult, UmbracoConfig.For.ModelsBuilder().ModelsNamespace); - - foreach (var typeModel in builder.GetModelsToGenerate()) - { - var sb = new StringBuilder(); - builder.Generate(sb, typeModel); - var filename = Path.Combine(modelsDirectory, typeModel.ClrName + ".generated.cs"); - File.WriteAllText(filename, sb.ToString()); - } - - // the idea was to calculate the current hash and to add it as an extra file to the compilation, - // in order to be able to detect whether a DLL is consistent with an environment - however the - // environment *might not* contain the local partial files, and thus it could be impossible to - // calculate the hash. So... maybe that's not a good idea after all? - /* - var currentHash = HashHelper.Hash(ourFiles, typeModels); - ourFiles["models.hash.cs"] = $@"using Umbraco.ModelsBuilder; -[assembly:ModelsBuilderAssembly(SourceHash = ""{currentHash}"")] -"; - */ - - if (bin != null) - { - foreach (var file in Directory.GetFiles(modelsDirectory, "*.generated.cs")) - ourFiles[file] = File.ReadAllText(file); - var compiler = new Compiler(); - compiler.Compile(builder.GetModelsNamespace(), ourFiles, bin); - } - - OutOfDateModelsStatus.Clear(); - } - - [DataContract] - internal class BuildResult - { - [DataMember(Name = "success")] - public bool Success; - [DataMember(Name = "message")] - public string Message; - } - - [DataContract] - internal class Dashboard - { - [DataMember(Name = "enable")] - public bool Enable; - [DataMember(Name = "text")] - public string Text; - [DataMember(Name = "canGenerate")] - public bool CanGenerate; - [DataMember(Name = "generateCausesRestart")] - public bool GenerateCausesRestart; - [DataMember(Name = "outOfDateModels")] - public bool OutOfDateModels; - [DataMember(Name = "lastError")] - public string LastError; - } - - internal enum OutOfDateType - { - OutOfDate, - Current, - Unknown = 100 - } - - [DataContract] - internal class OutOfDateStatus - { - [DataMember(Name = "status")] - public OutOfDateType Status { get; set; } - } - } -} diff --git a/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderComponent.cs b/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderComponent.cs deleted file mode 100644 index a581319ba5..0000000000 --- a/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderComponent.cs +++ /dev/null @@ -1,178 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Web; -using System.Web.Mvc; -using System.Web.Routing; -using LightInject; -using Umbraco.Core; -using Umbraco.Core.Components; -using Umbraco.Core.Composing; -using Umbraco.Core.Configuration; -using Umbraco.Core.IO; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.Services; -using Umbraco.Core.Services.Implement; -using Umbraco.ModelsBuilder.Api; -using Umbraco.ModelsBuilder.Configuration; -using Umbraco.Web; -using Umbraco.Web.PublishedCache.NuCache; -using Umbraco.Web.UI.JavaScript; - -namespace Umbraco.ModelsBuilder.Umbraco -{ - [RequiredComponent(typeof(NuCacheComponent))] - [RuntimeLevel(MinLevel = RuntimeLevel.Run)] - public class ModelsBuilderComponent : UmbracoComponentBase, IUmbracoCoreComponent - { - public override void Compose(Composition composition) - { - base.Compose(composition); - - composition.Container.Register(new PerContainerLifetime()); - - var config = UmbracoConfig.For.ModelsBuilder(); - - if (config.ModelsMode == ModelsMode.PureLive) - ComposeForLiveModels(composition.Container); - else if (config.EnableFactory) - ComposeForDefaultModelsFactory(composition.Container); - - // always setup the dashboard - InstallServerVars(composition.Container.GetInstance().Level); - composition.Container.Register(typeof(ModelsBuilderBackOfficeController), new PerRequestLifeTime()); - - // setup the API if enabled (and in debug mode) - if (config.ApiServer) - composition.Container.Register(typeof(ModelsBuilderApiController), new PerRequestLifeTime()); - } - - public void Initialize(UmbracoServices umbracoServices) - { - var config = UmbracoConfig.For.ModelsBuilder(); - - if (config.Enable) - FileService.SavingTemplate += FileService_SavingTemplate; - - // fixme LiveModelsProvider should not be static - if (config.ModelsMode.IsLiveNotPure()) - LiveModelsProvider.Install(umbracoServices); - - // fixme OutOfDateModelsStatus should not be static - if (config.FlagOutOfDateModels) - OutOfDateModelsStatus.Install(); - } - - private void ComposeForDefaultModelsFactory(IServiceContainer container) - { - container.RegisterSingleton(factory - => new PublishedModelFactory(factory.GetInstance().GetTypes())); - } - - private void ComposeForLiveModels(IServiceContainer container) - { - container.RegisterSingleton(); - - // the following would add @using statement in every view so user's don't - // have to do it - however, then noone understands where the @using statement - // comes from, and it cannot be avoided / removed --- DISABLED - // - /* - // no need for @using in views - // note: - // we are NOT using the in-code attribute here, config is required - // because that would require parsing the code... and what if it changes? - // we can AddGlobalImport not sure we can remove one anyways - var modelsNamespace = Configuration.Config.ModelsNamespace; - if (string.IsNullOrWhiteSpace(modelsNamespace)) - modelsNamespace = Configuration.Config.DefaultModelsNamespace; - System.Web.WebPages.Razor.WebPageRazorHost.AddGlobalImport(modelsNamespace); - */ - } - - private void InstallServerVars(RuntimeLevel level) - { - // register our url - for the backoffice api - ServerVariablesParser.Parsing += (sender, serverVars) => - { - if (!serverVars.ContainsKey("umbracoUrls")) - throw new Exception("Missing umbracoUrls."); - var umbracoUrlsObject = serverVars["umbracoUrls"]; - if (umbracoUrlsObject == null) - throw new Exception("Null umbracoUrls"); - if (!(umbracoUrlsObject is Dictionary umbracoUrls)) - throw new Exception("Invalid umbracoUrls"); - - if (!serverVars.ContainsKey("umbracoPlugins")) - throw new Exception("Missing umbracoPlugins."); - if (!(serverVars["umbracoPlugins"] is Dictionary umbracoPlugins)) - throw new Exception("Invalid umbracoPlugins"); - - if (HttpContext.Current == null) throw new InvalidOperationException("HttpContext is null"); - var urlHelper = new UrlHelper(new RequestContext(new HttpContextWrapper(HttpContext.Current), new RouteData())); - - umbracoUrls["modelsBuilderBaseUrl"] = urlHelper.GetUmbracoApiServiceBaseUrl(controller => controller.BuildModels()); - umbracoPlugins["modelsBuilder"] = GetModelsBuilderSettings(level); - }; - } - - private Dictionary GetModelsBuilderSettings(RuntimeLevel level) - { - if (level != RuntimeLevel.Run) - return null; - - var settings = new Dictionary - { - {"enabled", UmbracoConfig.For.ModelsBuilder().Enable} - }; - - return settings; - } - - /// - /// Used to check if a template is being created based on a document type, in this case we need to - /// ensure the template markup is correct based on the model name of the document type - /// - /// - /// - private void FileService_SavingTemplate(IFileService sender, Core.Events.SaveEventArgs e) - { - // don't do anything if the factory is not enabled - // because, no factory = no models (even if generation is enabled) - if (!UmbracoConfig.For.ModelsBuilder().EnableFactory) return; - - // don't do anything if this special key is not found - if (!e.AdditionalData.ContainsKey("CreateTemplateForContentType")) return; - - // ensure we have the content type alias - if (!e.AdditionalData.ContainsKey("ContentTypeAlias")) - throw new InvalidOperationException("The additionalData key: ContentTypeAlias was not found"); - - foreach (var template in e.SavedEntities) - { - // if it is in fact a new entity (not been saved yet) and the "CreateTemplateForContentType" key - // is found, then it means a new template is being created based on the creation of a document type - if (!template.HasIdentity && string.IsNullOrWhiteSpace(template.Content)) - { - // ensure is safe and always pascal cased, per razor standard - // + this is how we get the default model name in Umbraco.ModelsBuilder.Umbraco.Application - var alias = e.AdditionalData["ContentTypeAlias"].ToString(); - var name = template.Name; // will be the name of the content type since we are creating - var className = UmbracoServices.GetClrName(name, alias); - - var modelNamespace = UmbracoConfig.For.ModelsBuilder().ModelsNamespace; - - // we do not support configuring this at the moment, so just let Umbraco use its default value - //var modelNamespaceAlias = ...; - - var markup = ViewHelper.GetDefaultFileContent( - modelClassName: className, - modelNamespace: modelNamespace/*, - modelNamespaceAlias: modelNamespaceAlias*/); - - //set the template content to the new markup - template.Content = markup; - } - } - } - } -} diff --git a/src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerationError.cs b/src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerationError.cs deleted file mode 100644 index 7102190b5e..0000000000 --- a/src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerationError.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using System.IO; -using System.Text; -using Umbraco.Core.Configuration; -using Umbraco.ModelsBuilder.Configuration; - -namespace Umbraco.ModelsBuilder.Umbraco -{ - internal static class ModelsGenerationError - { - public static void Clear() - { - var errFile = GetErrFile(); - if (errFile == null) return; - - // "If the file to be deleted does not exist, no exception is thrown." - File.Delete(errFile); - } - - public static void Report(string message, Exception e) - { - var errFile = GetErrFile(); - if (errFile == null) return; - - var sb = new StringBuilder(); - sb.Append(message); - sb.Append("\r\n"); - sb.Append(e.Message); - sb.Append("\r\n\r\n"); - sb.Append(e.StackTrace); - sb.Append("\r\n"); - - File.WriteAllText(errFile, sb.ToString()); - } - - public static string GetLastError() - { - var errFile = GetErrFile(); - if (errFile == null) return null; - - try - { - return File.ReadAllText(errFile); - } - catch // accepted - { - return null; - } - } - - private static string GetErrFile() - { - var modelsDirectory = UmbracoConfig.For.ModelsBuilder().ModelsDirectory; - if (!Directory.Exists(modelsDirectory)) - return null; - - return Path.Combine(modelsDirectory, "models.err"); - } - } -} diff --git a/src/Umbraco.ModelsBuilder/Umbraco/OutOfDateModelsStatus.cs b/src/Umbraco.ModelsBuilder/Umbraco/OutOfDateModelsStatus.cs deleted file mode 100644 index a047f21edb..0000000000 --- a/src/Umbraco.ModelsBuilder/Umbraco/OutOfDateModelsStatus.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using System.IO; -using System.Web.Hosting; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.ModelsBuilder.Configuration; -using Umbraco.Web.Cache; - -namespace Umbraco.ModelsBuilder.Umbraco -{ - public sealed class OutOfDateModelsStatus - { - internal static void Install() - { - // just be sure - if (UmbracoConfig.For.ModelsBuilder().FlagOutOfDateModels == false) - return; - - ContentTypeCacheRefresher.CacheUpdated += (sender, args) => Write(); - DataTypeCacheRefresher.CacheUpdated += (sender, args) => Write(); - } - - private static string GetFlagPath() - { - var modelsDirectory = UmbracoConfig.For.ModelsBuilder().ModelsDirectory; - if (!Directory.Exists(modelsDirectory)) - Directory.CreateDirectory(modelsDirectory); - return Path.Combine(modelsDirectory, "ood.flag"); - } - - private static void Write() - { - var path = GetFlagPath(); - if (path == null || File.Exists(path)) return; - File.WriteAllText(path, "THIS FILE INDICATES THAT MODELS ARE OUT-OF-DATE\n\n"); - } - - public static void Clear() - { - if (UmbracoConfig.For.ModelsBuilder().FlagOutOfDateModels == false) return; - var path = GetFlagPath(); - if (path == null || !File.Exists(path)) return; - File.Delete(path); - } - - public static bool IsEnabled => UmbracoConfig.For.ModelsBuilder().FlagOutOfDateModels; - - public static bool IsOutOfDate - { - get - { - if (UmbracoConfig.For.ModelsBuilder().FlagOutOfDateModels == false) return false; - var path = GetFlagPath(); - return path != null && File.Exists(path); - } - } - } -} diff --git a/src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs b/src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs deleted file mode 100644 index c70e8a3b65..0000000000 --- a/src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System; -using System.Linq; -using System.Linq.Expressions; -using Umbraco.Web.Composing; -using Umbraco.Core.Models.PublishedContent; - -namespace Umbraco.ModelsBuilder.Umbraco -{ - public static class PublishedModelUtility - { - // looks safer but probably useless... ppl should not call these methods directly - // and if they do... they have to take care about not doing stupid things - - //public static PublishedPropertyType GetModelPropertyType2(Expression> selector) - // where T : PublishedContentModel - //{ - // var type = typeof (T); - // var s1 = type.GetField("ModelTypeAlias", BindingFlags.Public | BindingFlags.Static); - // var alias = (s1.IsLiteral && s1.IsInitOnly && s1.FieldType == typeof(string)) ? (string)s1.GetValue(null) : null; - // var s2 = type.GetField("ModelItemType", BindingFlags.Public | BindingFlags.Static); - // var itemType = (s2.IsLiteral && s2.IsInitOnly && s2.FieldType == typeof(PublishedItemType)) ? (PublishedItemType)s2.GetValue(null) : 0; - - // var contentType = PublishedContentType.Get(itemType, alias); - // // etc... - //} - - public static PublishedContentType GetModelContentType(PublishedItemType itemType, string alias) - { - var facade = Current.UmbracoContext.PublishedSnapshot; // fixme inject! - switch (itemType) - { - case PublishedItemType.Content: - return facade.Content.GetContentType(alias); - case PublishedItemType.Media: - return facade.Media.GetContentType(alias); - case PublishedItemType.Member: - return facade.Members.GetContentType(alias); - default: - throw new ArgumentOutOfRangeException(nameof(itemType)); - } - } - - public static PublishedPropertyType GetModelPropertyType(PublishedContentType contentType, Expression> selector) - //where TModel : PublishedContentModel // fixme PublishedContentModel _or_ PublishedElementModel - { - // fixme therefore, missing a check on TModel here - - var expr = selector.Body as MemberExpression; - - if (expr == null) - throw new ArgumentException("Not a property expression.", nameof(selector)); - - // there _is_ a risk that contentType and T do not match - // see note above : accepted risk... - - var attr = expr.Member - .GetCustomAttributes(typeof (ImplementPropertyTypeAttribute), false) - .OfType() - .SingleOrDefault(); - - if (string.IsNullOrWhiteSpace(attr?.Alias)) - throw new InvalidOperationException($"Could not figure out property alias for property \"{expr.Member.Name}\"."); - - return contentType.GetPropertyType(attr.Alias); - } - } -} diff --git a/src/Umbraco.ModelsBuilder/Umbraco/PureLiveModelFactory.cs b/src/Umbraco.ModelsBuilder/Umbraco/PureLiveModelFactory.cs deleted file mode 100644 index 9558c0140e..0000000000 --- a/src/Umbraco.ModelsBuilder/Umbraco/PureLiveModelFactory.cs +++ /dev/null @@ -1,602 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Linq.Expressions; -using System.Reflection; -using System.Reflection.Emit; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading; -using System.Web.Compilation; -using System.Web.Hosting; -using System.Web.WebPages.Razor; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Web.Cache; -using Umbraco.ModelsBuilder.Building; -using Umbraco.ModelsBuilder.Configuration; -using File = System.IO.File; - -namespace Umbraco.ModelsBuilder.Umbraco -{ - internal class PureLiveModelFactory : IPublishedModelFactory, IRegisteredObject - { - private Assembly _modelsAssembly; - private Infos _infos = new Infos { ModelInfos = null, ModelTypeMap = new Dictionary() }; - private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(); - private volatile bool _hasModels; // volatile 'cos reading outside lock - private bool _pendingRebuild; - private readonly ProfilingLogger _logger; - private readonly FileSystemWatcher _watcher; - private int _ver, _skipver; - private readonly int _debugLevel; - private BuildManager _theBuildManager; - private readonly Lazy _umbracoServices; - private UmbracoServices UmbracoServices => _umbracoServices.Value; - - private static readonly Regex AssemblyVersionRegex = new Regex("AssemblyVersion\\(\"[0-9]+.[0-9]+.[0-9]+.[0-9]+\"\\)", RegexOptions.Compiled); - private const string ProjVirt = "~/App_Data/Models/all.generated.cs"; - private static readonly string[] OurFiles = { "models.hash", "models.generated.cs", "all.generated.cs", "all.dll.path", "models.err" }; - - public PureLiveModelFactory(Lazy umbracoServices, ProfilingLogger logger) - { - _umbracoServices = umbracoServices; - _logger = logger; - _ver = 1; // zero is for when we had no version - _skipver = -1; // nothing to skip - ContentTypeCacheRefresher.CacheUpdated += (sender, args) => ResetModels(); - DataTypeCacheRefresher.CacheUpdated += (sender, args) => ResetModels(); - RazorBuildProvider.CodeGenerationStarted += RazorBuildProvider_CodeGenerationStarted; - - if (!HostingEnvironment.IsHosted) return; - - var modelsDirectory = UmbracoConfig.For.ModelsBuilder().ModelsDirectory; - if (!Directory.Exists(modelsDirectory)) - Directory.CreateDirectory(modelsDirectory); - - // BEWARE! if the watcher is not properly released then for some reason the - // BuildManager will start confusing types - using a 'registered object' here - // though we should probably plug into Umbraco's MainDom - which is internal - HostingEnvironment.RegisterObject(this); - _watcher = new FileSystemWatcher(modelsDirectory); - _watcher.Changed += WatcherOnChanged; - _watcher.EnableRaisingEvents = true; - - // get it here, this need to be fast - _debugLevel = UmbracoConfig.For.ModelsBuilder().DebugLevel; - } - - #region IPublishedModelFactory - - public IPublishedElement CreateModel(IPublishedElement element) - { - // get models, rebuilding them if needed - var infos = EnsureModels()?.ModelInfos; - if (infos == null) - return element; - - // be case-insensitive - var contentTypeAlias = element.ContentType.Alias; - - // lookup model constructor (else null) - infos.TryGetValue(contentTypeAlias, out ModelInfo info); - - // create model - return info == null ? element : info.Ctor(element); - } - - // this runs only once the factory is ready - // NOT when building models - public Type MapModelType(Type type) - { - var infos = EnsureModels(); - return ModelType.Map(type, infos.ModelTypeMap); - } - - // this runs only once the factory is ready - // NOT when building models - public IList CreateModelList(string alias) - { - var infos = EnsureModels(); - - // fail fast - if (infos == null) - return new List(); - - if (!infos.ModelInfos.TryGetValue(alias, out var modelInfo)) - return new List(); - - var ctor = modelInfo.ListCtor; - if (ctor != null) return ctor(); - - var listType = typeof(List<>).MakeGenericType(modelInfo.ModelType); - ctor = modelInfo.ListCtor = ReflectionUtilities.EmitCtor>(declaring: listType); - return ctor(); - } - - #endregion - - #region Compilation - - // deadlock note - // - // when RazorBuildProvider_CodeGenerationStarted runs, the thread has Monitor.Enter-ed the BuildManager - // singleton instance, through a call to CompilationLock.GetLock in BuildManager.GetVPathBuildResultInternal, - // and now wants to lock _locker. - // when EnsureModels runs, the thread locks _locker and then wants BuildManager to compile, which in turns - // requires that the BuildManager can Monitor.Enter-ed itself. - // so: - // - // T1 - needs to ensure models, locks _locker - // T2 - needs to compile a view, locks BuildManager - // hits RazorBuildProvider_CodeGenerationStarted - // wants to lock _locker, wait - // T1 - needs to compile models, using BuildManager - // wants to lock itself, wait - // - // - // until ASP.NET kills the long-running request (thread abort) - // - // problem is, we *want* to suspend views compilation while the models assembly is being changed else we - // end up with views compiled and cached with the old assembly, while models come from the new assembly, - // which gives more YSOD. so we *have* to lock _locker in RazorBuildProvider_CodeGenerationStarted. - // - // one "easy" solution consists in locking the BuildManager *before* _locker in EnsureModels, thus ensuring - // we always lock in the same order, and getting rid of deadlocks - but that requires having access to the - // current BuildManager instance, which is BuildManager.TheBuildManager, which is an internal property. - // - // well, that's what we are doing in this class' TheBuildManager property, using reflection. - - private void RazorBuildProvider_CodeGenerationStarted(object sender, EventArgs e) - { - try - { - _locker.EnterReadLock(); - - // just be safe - can happen if the first view is not an Umbraco view, - // or if something went wrong and we don't have an assembly at all - if (_modelsAssembly == null) return; - - if (_debugLevel > 0) - _logger.Logger.Debug("RazorBuildProvider.CodeGenerationStarted"); - if (!(sender is RazorBuildProvider provider)) return; - - // add the assembly, and add a dependency to a text file that will change on each - // compilation as in some environments (could not figure which/why) the BuildManager - // would not re-compile the views when the models assembly is rebuilt. - provider.AssemblyBuilder.AddAssemblyReference(_modelsAssembly); - provider.AddVirtualPathDependency(ProjVirt); - } - finally - { - if (_locker.IsReadLockHeld) - _locker.ExitReadLock(); - } - } - - // tells the factory that it should build a new generation of models - private void ResetModels() - { - _logger.Logger.Debug("Resetting models."); - - try - { - _locker.EnterWriteLock(); - - _hasModels = false; - _pendingRebuild = true; - } - finally - { - if (_locker.IsWriteLockHeld) - _locker.ExitWriteLock(); - } - } - - // gets "the" build manager - private BuildManager TheBuildManager - { - get - { - if (_theBuildManager != null) return _theBuildManager; - var prop = typeof (BuildManager).GetProperty("TheBuildManager", BindingFlags.NonPublic | BindingFlags.Static); - if (prop == null) - throw new InvalidOperationException("Could not get BuildManager.TheBuildManager property."); - _theBuildManager = (BuildManager) prop.GetValue(null); - return _theBuildManager; - } - } - - // ensure that the factory is running with the lastest generation of models - internal Infos EnsureModels() - { - if (_debugLevel > 0) - _logger.Logger.Debug("Ensuring models."); - - // don't use an upgradeable lock here because only 1 thread at a time could enter it - try - { - _locker.EnterReadLock(); - if (_hasModels) - return _infos; - } - finally - { - if (_locker.IsReadLockHeld) - _locker.ExitReadLock(); - } - - var buildManagerLocked = false; - try - { - // always take the BuildManager lock *before* taking the _locker lock - // to avoid possible deadlock situations (see notes above) - Monitor.Enter(TheBuildManager, ref buildManagerLocked); - - _locker.EnterUpgradeableReadLock(); - - if (_hasModels) return _infos; - - _locker.EnterWriteLock(); - - // we don't have models, - // either they haven't been loaded from the cache yet - // or they have been reseted and are pending a rebuild - - using (_logger.DebugDuration("Get models.", "Got models.")) - { - try - { - var assembly = GetModelsAssembly(_pendingRebuild); - - // the one below can be used to simulate an issue with BuildManager, ie it will register - // the models with the factory but NOT with the BuildManager, which will not recompile views. - // this is for U4-8043 which is an obvious issue but I cannot replicate - //_modelsAssembly = _modelsAssembly ?? assembly; - - // the one below is the normal one - _modelsAssembly = assembly; - - var types = assembly.ExportedTypes.Where(x => x.Inherits() || x.Inherits()); - _infos = RegisterModels(types); - ModelsGenerationError.Clear(); - } - catch (Exception e) - { - try - { - _logger.Logger.Error("Failed to build models.", e); - _logger.Logger.Warn("Running without models."); // be explicit - ModelsGenerationError.Report("Failed to build PureLive models.", e); - } - finally - { - _modelsAssembly = null; - _infos = new Infos { ModelInfos = null, ModelTypeMap = new Dictionary() }; - } - } - - // don't even try again - _hasModels = true; - } - - return _infos; - } - finally - { - if (_locker.IsWriteLockHeld) - _locker.ExitWriteLock(); - if (_locker.IsUpgradeableReadLockHeld) - _locker.ExitUpgradeableReadLock(); - if (buildManagerLocked) - Monitor.Exit(TheBuildManager); - } - } - - private Assembly GetModelsAssembly(bool forceRebuild) - { - var modelsDirectory = UmbracoConfig.For.ModelsBuilder().ModelsDirectory; - if (!Directory.Exists(modelsDirectory)) - Directory.CreateDirectory(modelsDirectory); - - // must filter out *.generated.cs because we haven't deleted them yet! - var ourFiles = Directory.Exists(modelsDirectory) - ? Directory.GetFiles(modelsDirectory, "*.cs") - .Where(x => !x.EndsWith(".generated.cs")) - .ToDictionary(x => x, File.ReadAllText) - : new Dictionary(); - - var typeModels = UmbracoServices.GetAllTypes(); - var currentHash = HashHelper.Hash(ourFiles, typeModels); - var modelsHashFile = Path.Combine(modelsDirectory, "models.hash"); - var modelsSrcFile = Path.Combine(modelsDirectory, "models.generated.cs"); - var projFile = Path.Combine(modelsDirectory, "all.generated.cs"); - var dllPathFile = Path.Combine(modelsDirectory, "all.dll.path"); - - // caching the generated models speeds up booting - // currentHash hashes both the types & the user's partials - - if (!forceRebuild) - { - _logger.Logger.Debug("Looking for cached models."); - if (File.Exists(modelsHashFile) && File.Exists(projFile)) - { - var cachedHash = File.ReadAllText(modelsHashFile); - if (currentHash != cachedHash) - { - _logger.Logger.Debug("Found obsolete cached models."); - forceRebuild = true; - } - } - else - { - _logger.Logger.Debug("Could not find cached models."); - forceRebuild = true; - } - } - - Assembly assembly; - if (forceRebuild == false) - { - // try to load the dll directly (avoid rebuilding) - if (File.Exists(dllPathFile)) - { - var dllPath = File.ReadAllText(dllPathFile); - if (File.Exists(dllPath)) - { - assembly = Assembly.LoadFile(dllPath); - var attr = assembly.GetCustomAttribute(); - if (attr != null && attr.PureLive && attr.SourceHash == currentHash) - { - // if we were to resume at that revision, then _ver would keep increasing - // and that is probably a bad idea - so, we'll always rebuild starting at - // ver 1, but we remember we want to skip that one - so we never end up - // with the "same but different" version of the assembly in memory - _skipver = assembly.GetName().Version.Revision; - - _logger.Logger.Debug("Loading cached models (dll)."); - return assembly; - } - } - } - - // mmust reset the version in the file else it would keep growing - // loading cached modules only happens when the app restarts - var text = File.ReadAllText(projFile); - var match = AssemblyVersionRegex.Match(text); - if (match.Success) - { - text = text.Replace(match.Value, "AssemblyVersion(\"0.0.0." + _ver + "\")"); - File.WriteAllText(projFile, text); - } - - // generate a marker file that will be a dependency - // see note in RazorBuildProvider_CodeGenerationStarted - // NO: using all.generated.cs as a dependency - //File.WriteAllText(Path.Combine(modelsDirectory, "models.dep"), "VER:" + _ver); - - _ver++; - assembly = BuildManager.GetCompiledAssembly(ProjVirt); - File.WriteAllText(dllPathFile, assembly.Location); - - _logger.Logger.Debug("Loading cached models (source)."); - return assembly; - } - - // need to rebuild - _logger.Logger.Debug("Rebuilding models."); - - // generate code, save - var code = GenerateModelsCode(ourFiles, typeModels); - // add extra attributes, - // PureLiveAssembly helps identifying Assemblies that contain PureLive models - // AssemblyVersion is so that we have a different version for each rebuild - var ver = _ver == _skipver ? ++_ver : _ver; - _ver++; - code = code.Replace("//ASSATTR", $@"[assembly: PureLiveAssembly] -[assembly:ModelsBuilderAssembly(PureLive = true, SourceHash = ""{currentHash}"")] -[assembly:System.Reflection.AssemblyVersion(""0.0.0.{ver}"")]"); - File.WriteAllText(modelsSrcFile, code); - - // generate proj, save - ourFiles["models.generated.cs"] = code; - var proj = GenerateModelsProj(ourFiles); - File.WriteAllText(projFile, proj); - - // compile and register - assembly = BuildManager.GetCompiledAssembly(ProjVirt); - File.WriteAllText(dllPathFile, assembly.Location); - - // assuming we can write and it's not going to cause exceptions... - File.WriteAllText(modelsHashFile, currentHash); - - _logger.Logger.Debug("Done rebuilding."); - return assembly; - } - - private static Infos RegisterModels(IEnumerable types) - { - var ctorArgTypes = new[] { typeof (IPublishedElement) }; - var modelInfos = new Dictionary(StringComparer.InvariantCultureIgnoreCase); - var map = new Dictionary(); - - foreach (var type in types) - { - ConstructorInfo constructor = null; - Type parameterType = null; - - foreach (var ctor in type.GetConstructors()) - { - var parms = ctor.GetParameters(); - if (parms.Length == 1 && typeof (IPublishedElement).IsAssignableFrom(parms[0].ParameterType)) - { - if (constructor != null) - throw new InvalidOperationException($"Type {type.FullName} has more than one public constructor with one argument of type, or implementing, IPropertySet."); - constructor = ctor; - parameterType = parms[0].ParameterType; - } - } - - if (constructor == null) - throw new InvalidOperationException($"Type {type.FullName} is missing a public constructor with one argument of type, or implementing, IPropertySet."); - - var attribute = type.GetCustomAttribute(false); - var typeName = attribute == null ? type.Name : attribute.ContentTypeAlias; - - if (modelInfos.TryGetValue(typeName, out ModelInfo modelInfo)) - throw new InvalidOperationException($"Both types {type.FullName} and {modelInfo.ModelType.FullName} want to be a model type for content type with alias \"{typeName}\"."); - - // fixme use Core's ReflectionUtilities.EmitCtor !! - var meth = new DynamicMethod(string.Empty, typeof (IPublishedElement), ctorArgTypes, type.Module, true); - var gen = meth.GetILGenerator(); - gen.Emit(OpCodes.Ldarg_0); - gen.Emit(OpCodes.Newobj, constructor); - gen.Emit(OpCodes.Ret); - var func = (Func) meth.CreateDelegate(typeof (Func)); - - modelInfos[typeName] = new ModelInfo { ParameterType = parameterType, Ctor = func, ModelType = type }; - map[typeName] = type; - } - - return new Infos { ModelInfos = modelInfos.Count > 0 ? modelInfos : null, ModelTypeMap = map }; - } - - private static string GenerateModelsCode(IDictionary ourFiles, IList typeModels) - { - var modelsDirectory = UmbracoConfig.For.ModelsBuilder().ModelsDirectory; - if (!Directory.Exists(modelsDirectory)) - Directory.CreateDirectory(modelsDirectory); - - foreach (var file in Directory.GetFiles(modelsDirectory, "*.generated.cs")) - File.Delete(file); - - var map = typeModels.ToDictionary(x => x.Alias, x => x.ClrName); - foreach (var typeModel in typeModels) - { - foreach (var propertyModel in typeModel.Properties) - { - propertyModel.ClrTypeName = ModelType.MapToName(propertyModel.ModelClrType, map); - } - } - - var parseResult = new CodeParser().ParseWithReferencedAssemblies(ourFiles); - var builder = new TextBuilder(typeModels, parseResult, UmbracoConfig.For.ModelsBuilder().ModelsNamespace); - - var codeBuilder = new StringBuilder(); - builder.Generate(codeBuilder, builder.GetModelsToGenerate()); - var code = codeBuilder.ToString(); - - return code; - } - - private static readonly Regex UsingRegex = new Regex("^using(.*);", RegexOptions.Compiled | RegexOptions.Multiline); - private static readonly Regex AattrRegex = new Regex("^\\[assembly:(.*)\\]", RegexOptions.Compiled | RegexOptions.Multiline); - - private static string GenerateModelsProj(IDictionary files) - { - // ideally we would generate a CSPROJ file but then we'd need a BuildProvider for csproj - // trying to keep things simple for the time being, just write everything to one big file - - // group all 'using' at the top of the file (else fails) - var usings = new List(); - foreach (var k in files.Keys.ToList()) - files[k] = UsingRegex.Replace(files[k], m => - { - usings.Add(m.Groups[1].Value); - return string.Empty; - }); - - // group all '[assembly:...]' at the top of the file (else fails) - var aattrs = new List(); - foreach (var k in files.Keys.ToList()) - files[k] = AattrRegex.Replace(files[k], m => - { - aattrs.Add(m.Groups[1].Value); - return string.Empty; - }); - - var text = new StringBuilder(); - foreach (var u in usings.Distinct()) - { - text.Append("using "); - text.Append(u); - text.Append(";\r\n"); - } - foreach (var a in aattrs) - { - text.Append("[assembly:"); - text.Append(a); - text.Append("]\r\n"); - } - text.Append("\r\n\r\n"); - foreach (var f in files) - { - text.Append("// FILE: "); - text.Append(f.Key); - text.Append("\r\n\r\n"); - text.Append(f.Value); - text.Append("\r\n\r\n\r\n"); - } - text.Append("// EOF\r\n"); - - return text.ToString(); - } - - internal class Infos - { - public Dictionary ModelTypeMap { get; set; } - public Dictionary ModelInfos { get; set; } - } - - internal class ModelInfo - { - public Type ParameterType { get; set; } - public Func Ctor { get; set; } - public Type ModelType { get; set; } - public Func ListCtor { get; set; } - } - - #endregion - - #region Watching - - private void WatcherOnChanged(object sender, FileSystemEventArgs args) - { - var changed = args.Name; - - // don't reset when our files change because we are building! - // - // comment it out, and always ignore our files, because it seems that some - // race conditions can occur on slow Cloud filesystems and then we keep - // rebuilding - - //if (_building && OurFiles.Contains(changed)) - //{ - // //_logger.Logger.Info("Ignoring files self-changes."); - // return; - //} - - // always ignore our own file changes - if (OurFiles.Contains(changed)) - return; - - _logger.Logger.Info("Detected files changes."); - - ResetModels(); - } - - public void Stop(bool immediate) - { - _watcher.EnableRaisingEvents = false; - _watcher.Dispose(); - HostingEnvironment.UnregisterObject(this); - } - - #endregion - } -} diff --git a/src/Umbraco.ModelsBuilder/Umbraco/UmbracoServices.cs b/src/Umbraco.ModelsBuilder/Umbraco/UmbracoServices.cs deleted file mode 100644 index f0347d9194..0000000000 --- a/src/Umbraco.ModelsBuilder/Umbraco/UmbracoServices.cs +++ /dev/null @@ -1,236 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Models; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.Services; -using Umbraco.Core.Strings; -using Umbraco.ModelsBuilder.Building; -using Umbraco.ModelsBuilder.Configuration; - -namespace Umbraco.ModelsBuilder.Umbraco -{ - public class UmbracoServices - { - private readonly IContentTypeService _contentTypeService; - private readonly IMediaTypeService _mediaTypeService; - private readonly IMemberTypeService _memberTypeService; - private readonly IPublishedContentTypeFactory _publishedContentTypeFactory; - - public UmbracoServices(IContentTypeService contentTypeService, IMediaTypeService mediaTypeService, IMemberTypeService memberTypeService, IPublishedContentTypeFactory publishedContentTypeFactory) - { - _contentTypeService = contentTypeService; - _mediaTypeService = mediaTypeService; - _memberTypeService = memberTypeService; - _publishedContentTypeFactory = publishedContentTypeFactory; - } - - #region Services - - public IList GetAllTypes() - { - var types = new List(); - - types.AddRange(GetTypes(PublishedItemType.Content, _contentTypeService.GetAll().Cast().ToArray())); - types.AddRange(GetTypes(PublishedItemType.Media, _mediaTypeService.GetAll().Cast().ToArray())); - types.AddRange(GetTypes(PublishedItemType.Member, _memberTypeService.GetAll().Cast().ToArray())); - - return EnsureDistinctAliases(types); - } - - public IList GetContentTypes() - { - var contentTypes = _contentTypeService.GetAll().Cast().ToArray(); - return GetTypes(PublishedItemType.Content, contentTypes); // aliases have to be unique here - } - - public IList GetMediaTypes() - { - var contentTypes = _mediaTypeService.GetAll().Cast().ToArray(); - return GetTypes(PublishedItemType.Media, contentTypes); // aliases have to be unique here - } - - public IList GetMemberTypes() - { - var memberTypes = _memberTypeService.GetAll().Cast().ToArray(); - return GetTypes(PublishedItemType.Member, memberTypes); // aliases have to be unique here - } - - public static string GetClrName(string name, string alias) - { - // ideally we should just be able to re-use Umbraco's alias, - // just upper-casing the first letter, however in v7 for backward - // compatibility reasons aliases derive from names via ToSafeAlias which is - // PreFilter = ApplyUrlReplaceCharacters, - // IsTerm = (c, leading) => leading - // ? char.IsLetter(c) // only letters - // : (char.IsLetterOrDigit(c) || c == '_'), // letter, digit or underscore - // StringType = CleanStringType.Ascii | CleanStringType.UmbracoCase, - // BreakTermsOnUpper = false - // - // but that is not ideal with acronyms and casing - // however we CANNOT change Umbraco - // so, adding a way to "do it right" deriving from name, here - - switch (UmbracoConfig.For.ModelsBuilder().ClrNameSource) - { - case ClrNameSource.RawAlias: - // use Umbraco's alias - return alias; - - case ClrNameSource.Alias: - // ModelsBuilder's legacy - but not ideal - return alias.ToCleanString(CleanStringType.ConvertCase | CleanStringType.PascalCase); - - case ClrNameSource.Name: - // derive from name - var source = name.TrimStart('_'); // because CleanStringType.ConvertCase accepts them - return source.ToCleanString(CleanStringType.ConvertCase | CleanStringType.PascalCase | CleanStringType.Ascii); - - default: - throw new Exception("Invalid ClrNameSource."); - } - } - - private IList GetTypes(PublishedItemType itemType, IContentTypeComposition[] contentTypes) - { - var typeModels = new List(); - var uniqueTypes = new HashSet(); - - // get the types and the properties - foreach (var contentType in contentTypes) - { - var typeModel = new TypeModel - { - Id = contentType.Id, - Alias = contentType.Alias, - ClrName = GetClrName(contentType.Name, contentType.Alias), - ParentId = contentType.ParentId, - - Name = contentType.Name, - Description = contentType.Description - }; - - // of course this should never happen, but when it happens, better detect it - // else we end up with weird nullrefs everywhere - if (uniqueTypes.Contains(typeModel.ClrName)) - throw new Exception($"Panic: duplicate type ClrName \"{typeModel.ClrName}\"."); - uniqueTypes.Add(typeModel.ClrName); - - // fixme - we need a better way at figuring out what's an element type! - // and then we should not do the alias filtering below - bool IsElement(PublishedContentType x) - { - return x.Alias.InvariantEndsWith("Element"); - } - - var publishedContentType = _publishedContentTypeFactory.CreateContentType(contentType); - switch (itemType) - { - case PublishedItemType.Content: - if (IsElement(publishedContentType)) - { - typeModel.ItemType = TypeModel.ItemTypes.Element; - if (typeModel.ClrName.InvariantEndsWith("Element")) - typeModel.ClrName = typeModel.ClrName.Substring(0, typeModel.ClrName.Length - "Element".Length); - } - else - { - typeModel.ItemType = TypeModel.ItemTypes.Content; - } - break; - case PublishedItemType.Media: - typeModel.ItemType = TypeModel.ItemTypes.Media; - break; - case PublishedItemType.Member: - typeModel.ItemType = TypeModel.ItemTypes.Member; - break; - default: - throw new InvalidOperationException(string.Format("Unsupported PublishedItemType \"{0}\".", itemType)); - } - - typeModels.Add(typeModel); - - foreach (var propertyType in contentType.PropertyTypes) - { - var propertyModel = new PropertyModel - { - Alias = propertyType.Alias, - ClrName = GetClrName(propertyType.Name, propertyType.Alias), - - Name = propertyType.Name, - Description = propertyType.Description - }; - - var publishedPropertyType = publishedContentType.GetPropertyType(propertyType.Alias); - if (publishedPropertyType == null) - throw new Exception($"Panic: could not get published property type {contentType.Alias}.{propertyType.Alias}."); - - propertyModel.ModelClrType = publishedPropertyType.ModelClrType; - - typeModel.Properties.Add(propertyModel); - } - } - - // wire the base types - foreach (var typeModel in typeModels.Where(x => x.ParentId > 0)) - { - typeModel.BaseType = typeModels.SingleOrDefault(x => x.Id == typeModel.ParentId); - // Umbraco 7.4 introduces content types containers, so even though ParentId > 0, the parent might - // not be a content type - here we assume that BaseType being null while ParentId > 0 means that - // the parent is a container (and we don't check). - typeModel.IsParent = typeModel.BaseType != null; - } - - // discover mixins - foreach (var contentType in contentTypes) - { - var typeModel = typeModels.SingleOrDefault(x => x.Id == contentType.Id); - if (typeModel == null) throw new Exception("Panic: no type model matching content type."); - - IEnumerable compositionTypes; - var contentTypeAsMedia = contentType as IMediaType; - var contentTypeAsContent = contentType as IContentType; - var contentTypeAsMember = contentType as IMemberType; - if (contentTypeAsMedia != null) compositionTypes = contentTypeAsMedia.ContentTypeComposition; - else if (contentTypeAsContent != null) compositionTypes = contentTypeAsContent.ContentTypeComposition; - else if (contentTypeAsMember != null) compositionTypes = contentTypeAsMember.ContentTypeComposition; - else throw new Exception(string.Format("Panic: unsupported type \"{0}\".", contentType.GetType().FullName)); - - foreach (var compositionType in compositionTypes) - { - var compositionModel = typeModels.SingleOrDefault(x => x.Id == compositionType.Id); - if (compositionModel == null) throw new Exception("Panic: composition type does not exist."); - - if (compositionType.Id == contentType.ParentId) continue; - - // add to mixins - typeModel.MixinTypes.Add(compositionModel); - - // mark as mixin - as well as parents - compositionModel.IsMixin = true; - while ((compositionModel = compositionModel.BaseType) != null) - compositionModel.IsMixin = true; - } - } - - return typeModels; - } - - internal static IList EnsureDistinctAliases(IList typeModels) - { - var groups = typeModels.GroupBy(x => x.Alias.ToLowerInvariant()); - foreach (var group in groups.Where(x => x.Count() > 1)) - { - throw new NotSupportedException($"Alias \"{group.Key}\" is used by types" - + $" {string.Join(", ", group.Select(x => x.ItemType + ":\"" + x.Alias + "\""))}. Aliases have to be unique." - + " One of the aliases must be modified in order to use the ModelsBuilder."); - } - return typeModels; - } - - #endregion - } -} diff --git a/src/Umbraco.ModelsBuilder/Validation/ContentTypeModelValidator.cs b/src/Umbraco.ModelsBuilder/Validation/ContentTypeModelValidator.cs deleted file mode 100644 index 20f5e94b64..0000000000 --- a/src/Umbraco.ModelsBuilder/Validation/ContentTypeModelValidator.cs +++ /dev/null @@ -1,95 +0,0 @@ -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Linq; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Models; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.ModelsBuilder.Configuration; -using Umbraco.Web.Editors; -using Umbraco.Web.Models.ContentEditing; - -namespace Umbraco.ModelsBuilder.Validation -{ - /// - /// Used to validate the aliases for the content type when MB is enabled to ensure that - /// no illegal aliases are used - /// - internal class ContentTypeModelValidator : ContentTypeModelValidatorBase - { - } - - /// - /// Used to validate the aliases for the content type when MB is enabled to ensure that - /// no illegal aliases are used - /// - internal class MediaTypeModelValidator : ContentTypeModelValidatorBase - { - } - - /// - /// Used to validate the aliases for the content type when MB is enabled to ensure that - /// no illegal aliases are used - /// - internal class MemberTypeModelValidator : ContentTypeModelValidatorBase - { - } - - internal abstract class ContentTypeModelValidatorBase : EditorValidator - where TModel: ContentTypeSave - where TProperty: PropertyTypeBasic - { - protected override IEnumerable Validate(TModel model) - { - //don't do anything if we're not enabled - if (UmbracoConfig.For.ModelsBuilder().Enable) - { - var properties = model.Groups.SelectMany(x => x.Properties) - .Where(x => x.Inherited == false) - .ToArray(); - - foreach (var prop in properties) - { - var propertyGroup = model.Groups.Single(x => x.Properties.Contains(prop)); - - if (model.Alias.ToLowerInvariant() == prop.Alias.ToLowerInvariant()) - yield return new ValidationResult(string.Format("With Models Builder enabled, you can't have a property with a the alias \"{0}\" when the content type alias is also \"{0}\".", prop.Alias), new[] - { - string.Format("Groups[{0}].Properties[{1}].Alias", model.Groups.IndexOf(propertyGroup), propertyGroup.Properties.IndexOf(prop)) - }); - - //we need to return the field name with an index so it's wired up correctly - var groupIndex = model.Groups.IndexOf(propertyGroup); - var propertyIndex = propertyGroup.Properties.IndexOf(prop); - - var validationResult = ValidateProperty(prop, groupIndex, propertyIndex); - if (validationResult != null) - { - yield return validationResult; - } - } - } - } - - private ValidationResult ValidateProperty(PropertyTypeBasic property, int groupIndex, int propertyIndex) - { - //don't let them match any properties or methods in IPublishedContent - //TODO: There are probably more! - var reservedProperties = typeof(IPublishedContent).GetProperties().Select(x => x.Name).ToArray(); - var reservedMethods = typeof(IPublishedContent).GetMethods().Select(x => x.Name).ToArray(); - - var alias = property.Alias; - - if (reservedProperties.InvariantContains(alias) || reservedMethods.InvariantContains(alias)) - { - return new ValidationResult( - string.Format("The alias {0} is a reserved term and cannot be used", alias), new[] - { - string.Format("Groups[{0}].Properties[{1}].Alias", groupIndex, propertyIndex) - }); - } - - return null; - } - } -} diff --git a/src/umbraco.sln b/src/umbraco.sln index 7313e06e49..e603960782 100644 --- a/src/umbraco.sln +++ b/src/umbraco.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27004.2005 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29009.5 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.Web.UI", "Umbraco.Web.UI\Umbraco.Web.UI.csproj", "{4C4C194C-B5E4-4991-8F87-4373E24CC19F}" EndProject @@ -103,6 +103,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "IssueTemplates", "IssueTemp ..\.github\ISSUE_TEMPLATE\5_Security_issue.md = ..\.github\ISSUE_TEMPLATE\5_Security_issue.md EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.ModelsBuilder", "Umbraco.ModelsBuilder\Umbraco.ModelsBuilder.csproj", "{52AC0BA8-A60E-4E36-897B-E8B97A54ED1C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -135,6 +137,10 @@ Global {3A33ADC9-C6C0-4DB1-A613-A9AF0210DF3D}.Debug|Any CPU.Build.0 = Debug|Any CPU {3A33ADC9-C6C0-4DB1-A613-A9AF0210DF3D}.Release|Any CPU.ActiveCfg = Release|Any CPU {3A33ADC9-C6C0-4DB1-A613-A9AF0210DF3D}.Release|Any CPU.Build.0 = Release|Any CPU + {52AC0BA8-A60E-4E36-897B-E8B97A54ED1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {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 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From ee83c02424492d60079e0f364b303f829936c04c Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 24 Jun 2019 11:58:36 +0200 Subject: [PATCH 005/548] Import slimmed down ModelsBuilder --- src/Umbraco.ModelsBuilder/Api/ApiVersion.cs | 36 + src/Umbraco.ModelsBuilder/Building/Builder.cs | 236 ++++++ .../Building/PropertyModel.cs | 61 ++ .../Building/TextBuilder.cs | 579 +++++++++++++++ .../Building/TextHeaderWriter.cs | 26 + .../Building/TypeModel.cs | 205 ++++++ .../ConfigsExtensions.cs | 20 + .../Configuration/ClrNameSource.cs | 28 + .../Configuration/Config.cs | 224 ++++++ .../Configuration/ModelsMode.cs | 36 + .../Configuration/ModelsModeExtensions.cs | 37 + .../Dashboard/BuilderDashboardHelper.cs | 65 ++ .../EnumerableExtensions.cs | 33 + .../ImplementPropertyTypeAttribute.cs | 19 + .../ModelsBuilderAssemblyAttribute.cs | 23 + .../Properties/AssemblyInfo.cs | 30 +- .../PublishedElementExtensions.cs | 51 ++ .../PureLiveAssemblyAttribute.cs | 15 + .../ReferencedAssemblies.cs | 179 +++++ src/Umbraco.ModelsBuilder/TypeExtensions.cs | 22 + .../Umbraco.ModelsBuilder.csproj | 53 ++ .../Umbraco/HashCombiner.cs | 38 + .../Umbraco/HashHelper.cs | 42 ++ .../Umbraco/LiveModelsProvider.cs | 133 ++++ .../ModelsBuilderBackOfficeController.cs | 184 +++++ .../Umbraco/ModelsBuilderComponent.cs | 186 +++++ .../Umbraco/ModelsBuilderComposer.cs | 60 ++ .../Umbraco/ModelsBuilderInitializer.cs | 28 + .../Umbraco/ModelsGenerationError.cs | 62 ++ .../Umbraco/OutOfDateModelsStatus.cs | 57 ++ .../Umbraco/PublishedModelUtility.cs | 67 ++ .../Umbraco/PureLiveModelFactory.cs | 677 ++++++++++++++++++ .../Umbraco/UmbracoServices.cs | 198 +++++ .../Validation/ContentTypeModelValidator.cs | 93 +++ src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 4 + 35 files changed, 3779 insertions(+), 28 deletions(-) create mode 100644 src/Umbraco.ModelsBuilder/Api/ApiVersion.cs create mode 100644 src/Umbraco.ModelsBuilder/Building/Builder.cs create mode 100644 src/Umbraco.ModelsBuilder/Building/PropertyModel.cs create mode 100644 src/Umbraco.ModelsBuilder/Building/TextBuilder.cs create mode 100644 src/Umbraco.ModelsBuilder/Building/TextHeaderWriter.cs create mode 100644 src/Umbraco.ModelsBuilder/Building/TypeModel.cs create mode 100644 src/Umbraco.ModelsBuilder/ConfigsExtensions.cs create mode 100644 src/Umbraco.ModelsBuilder/Configuration/ClrNameSource.cs create mode 100644 src/Umbraco.ModelsBuilder/Configuration/Config.cs create mode 100644 src/Umbraco.ModelsBuilder/Configuration/ModelsMode.cs create mode 100644 src/Umbraco.ModelsBuilder/Configuration/ModelsModeExtensions.cs create mode 100644 src/Umbraco.ModelsBuilder/Dashboard/BuilderDashboardHelper.cs create mode 100644 src/Umbraco.ModelsBuilder/EnumerableExtensions.cs create mode 100644 src/Umbraco.ModelsBuilder/ImplementPropertyTypeAttribute.cs create mode 100644 src/Umbraco.ModelsBuilder/ModelsBuilderAssemblyAttribute.cs create mode 100644 src/Umbraco.ModelsBuilder/PublishedElementExtensions.cs create mode 100644 src/Umbraco.ModelsBuilder/PureLiveAssemblyAttribute.cs create mode 100644 src/Umbraco.ModelsBuilder/ReferencedAssemblies.cs create mode 100644 src/Umbraco.ModelsBuilder/TypeExtensions.cs create mode 100644 src/Umbraco.ModelsBuilder/Umbraco/HashCombiner.cs create mode 100644 src/Umbraco.ModelsBuilder/Umbraco/HashHelper.cs create mode 100644 src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProvider.cs create mode 100644 src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderBackOfficeController.cs create mode 100644 src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderComponent.cs create mode 100644 src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderComposer.cs create mode 100644 src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderInitializer.cs create mode 100644 src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerationError.cs create mode 100644 src/Umbraco.ModelsBuilder/Umbraco/OutOfDateModelsStatus.cs create mode 100644 src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs create mode 100644 src/Umbraco.ModelsBuilder/Umbraco/PureLiveModelFactory.cs create mode 100644 src/Umbraco.ModelsBuilder/Umbraco/UmbracoServices.cs create mode 100644 src/Umbraco.ModelsBuilder/Validation/ContentTypeModelValidator.cs diff --git a/src/Umbraco.ModelsBuilder/Api/ApiVersion.cs b/src/Umbraco.ModelsBuilder/Api/ApiVersion.cs new file mode 100644 index 0000000000..a7f2db397e --- /dev/null +++ b/src/Umbraco.ModelsBuilder/Api/ApiVersion.cs @@ -0,0 +1,36 @@ +using System; +using System.Reflection; +using Semver; + +namespace Umbraco.ModelsBuilder.Api +{ + /// + /// Manages API version handshake between client and server. + /// + public class ApiVersion + { + /// + /// Initializes a new instance of the class. + /// + /// The currently executing version. + /// + internal ApiVersion(SemVersion executingVersion) + { + Version = executingVersion ?? throw new ArgumentNullException(nameof(executingVersion)); + } + + private static SemVersion CurrentAssemblyVersion + => SemVersion.Parse(Assembly.GetExecutingAssembly().GetCustomAttribute().InformationalVersion); + + /// + /// Gets the currently executing API version. + /// + public static ApiVersion Current { get; } + = new ApiVersion(CurrentAssemblyVersion); + + /// + /// Gets the executing version of the API. + /// + public SemVersion Version { get; } + } +} diff --git a/src/Umbraco.ModelsBuilder/Building/Builder.cs b/src/Umbraco.ModelsBuilder/Building/Builder.cs new file mode 100644 index 0000000000..fd5eccde5b --- /dev/null +++ b/src/Umbraco.ModelsBuilder/Building/Builder.cs @@ -0,0 +1,236 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; +using Umbraco.ModelsBuilder.Configuration; + +namespace Umbraco.ModelsBuilder.Building +{ + // NOTE + // The idea was to have different types of builder, because I wanted to experiment with + // building code with CodeDom. Turns out more complicated than I thought and maybe not + // worth it at the moment, to we're using TextBuilder and its Generate method is specific. + // + // Keeping the code as-is for the time being... + + /// + /// Provides a base class for all builders. + /// + internal abstract class Builder + { + private readonly IList _typeModels; + + protected Dictionary ModelsMap { get; } = new Dictionary(); + + private static Config Config => Current.Configs.ModelsBuilder(); + + // the list of assemblies that will be 'using' by default + protected readonly IList TypesUsing = new List + { + "System", + "System.Collections.Generic", + "System.Linq.Expressions", + "System.Web", + "Umbraco.Core.Models", + "Umbraco.Core.Models.PublishedContent", + "Umbraco.Web", + "Umbraco.ModelsBuilder", + "Umbraco.ModelsBuilder.Umbraco", + }; + + /// + /// Gets or sets a value indicating the namespace to use for the models. + /// + /// May be overriden by code attributes. + public string ModelsNamespace { get; set; } + + /// + /// Gets the list of assemblies to add to the set of 'using' assemblies in each model file. + /// + public IList Using => TypesUsing; + + /// + /// Gets the list of models to generate. + /// + /// The models to generate, ie those that are not ignored. + public IEnumerable GetModelsToGenerate() + { + return _typeModels; + } + + /// + /// Gets the list of all models. + /// + /// Includes those that are ignored. + internal IList TypeModels => _typeModels; + + /// + /// Initializes a new instance of the class with a list of models to generate + /// and the result of code parsing. + /// + /// The list of models to generate. + protected Builder(IList typeModels) + : this(typeModels, null) + { } + + /// + /// Initializes a new instance of the class with a list of models to generate, + /// the result of code parsing, and a models namespace. + /// + /// The list of models to generate. + /// The models namespace. + protected Builder(IList typeModels, string modelsNamespace) + { + _typeModels = typeModels ?? throw new ArgumentNullException(nameof(typeModels)); + + // can be null or empty, we'll manage + ModelsNamespace = modelsNamespace; + + // but we want it to prepare + Prepare(); + } + + // for unit tests only + protected Builder() + { } + + /// + /// Prepares generation by processing the result of code parsing. + /// + /// + /// Preparation includes figuring out from the existing code which models or properties should + /// be ignored or renamed, etc. -- anything that comes from the attributes in the existing code. + /// + private void Prepare() + { + TypeModel.MapModelTypes(_typeModels, ModelsNamespace); + + var pureLive = Config.ModelsMode == ModelsMode.PureLive; + + // for the first two of these two tests, + // always throw, even in purelive: cannot happen unless ppl start fidling with attributes to rename + // things, and then they should pay attention to the generation error log - there's no magic here + // for the last one, don't throw in purelive, see comment + + // ensure we have no duplicates type names + foreach (var xx in _typeModels.GroupBy(x => x.ClrName).Where(x => x.Count() > 1)) + throw new InvalidOperationException($"Type name \"{xx.Key}\" is used" + + $" for types with alias {string.Join(", ", xx.Select(x => x.ItemType + ":\"" + x.Alias + "\""))}. Names have to be unique." + + " Consider using an attribute to assign different names to conflicting types."); + + // ensure we have no duplicates property names + foreach (var typeModel in _typeModels) + foreach (var xx in typeModel.Properties.GroupBy(x => x.ClrName).Where(x => x.Count() > 1)) + throw new InvalidOperationException($"Property name \"{xx.Key}\" in type {typeModel.ItemType}:\"{typeModel.Alias}\"" + + $" is used for properties with alias {string.Join(", ", xx.Select(x => "\"" + x.Alias + "\""))}. Names have to be unique." + + " Consider using an attribute to assign different names to conflicting properties."); + + // ensure content & property type don't have identical name (csharp hates it) + foreach (var typeModel in _typeModels) + { + foreach (var xx in typeModel.Properties.Where(x => x.ClrName == typeModel.ClrName)) + { + if (!pureLive) + throw new InvalidOperationException($"The model class for content type with alias \"{typeModel.Alias}\" is named \"{xx.ClrName}\"." + + $" CSharp does not support using the same name for the property with alias \"{xx.Alias}\"." + + " Consider using an attribute to assign a different name to the property."); + + // for purelive, will we generate a commented out properties with an error message, + // instead of throwing, because then it kills the sites and ppl don't understand why + xx.AddError($"The class {typeModel.ClrName} cannot implement this property, because" + + $" CSharp does not support naming the property with alias \"{xx.Alias}\" with the same name as content type with alias \"{typeModel.Alias}\"." + + " Consider using an attribute to assign a different name to the property."); + + // will not be implemented on interface nor class + // note: we will still create the static getter, and implement the property on other classes... + } + } + + // ensure we have no collision between base types + // NO: we may want to define a base class in a partial, on a model that has a parent + // we are NOT checking that the defined base type does maintain the inheritance chain + //foreach (var xx in _typeModels.Where(x => !x.IsContentIgnored).Where(x => x.BaseType != null && x.HasBase)) + // throw new InvalidOperationException(string.Format("Type alias \"{0}\" has more than one parent class.", + // xx.Alias)); + + // discover interfaces that need to be declared / implemented + foreach (var typeModel in _typeModels) + { + // collect all the (non-removed) types implemented at parent level + // ie the parent content types and the mixins content types, recursively + var parentImplems = new List(); + if (typeModel.BaseType != null) + TypeModel.CollectImplems(parentImplems, typeModel.BaseType); + + // interfaces we must declare we implement (initially empty) + // ie this type's mixins, except those that have been removed, + // and except those that are already declared at the parent level + // in other words, DeclaringInterfaces is "local mixins" + var declaring = typeModel.MixinTypes + .Except(parentImplems); + typeModel.DeclaringInterfaces.AddRange(declaring); + + // interfaces we must actually implement (initially empty) + // if we declare we implement a mixin interface, we must actually implement + // its properties, all recursively (ie if the mixin interface implements...) + // so, starting with local mixins, we collect all the (non-removed) types above them + var mixinImplems = new List(); + foreach (var i in typeModel.DeclaringInterfaces) + TypeModel.CollectImplems(mixinImplems, i); + // and then we remove from that list anything that is already declared at the parent level + typeModel.ImplementingInterfaces.AddRange(mixinImplems.Except(parentImplems)); + } + + // ensure elements don't inherit from non-elements + foreach (var typeModel in _typeModels.Where(x => x.IsElement)) + { + if (typeModel.BaseType != null && !typeModel.BaseType.IsElement) + throw new Exception($"Cannot generate model for type '{typeModel.Alias}' because it is an element type, but its parent type '{typeModel.BaseType.Alias}' is not."); + + var errs = typeModel.MixinTypes.Where(x => !x.IsElement).ToList(); + if (errs.Count > 0) + throw new Exception($"Cannot generate model for type '{typeModel.Alias}' because it is an element type, but it is composed of {string.Join(", ", errs.Select(x => "'" + x.Alias + "'"))} which {(errs.Count == 1 ? "is" : "are")} not."); + } + } + + // looking for a simple symbol eg 'Umbraco' or 'String' + // expecting to match eg 'Umbraco' or 'System.String' + // returns true if either + // - more than 1 symbol is found (explicitely ambiguous) + // - 1 symbol is found BUT not matching (implicitely ambiguous) + protected bool IsAmbiguousSymbol(string symbol, string match) + { + // cannot figure out is a symbol is ambiguous without Roslyn + // so... let's say everything is ambiguous - code won't be + // pretty but it'll work + return true; + } + + internal string ModelsNamespaceForTests; + + public string GetModelsNamespace() + { + if (ModelsNamespaceForTests != null) + return ModelsNamespaceForTests; + + // if builder was initialized with a namespace, use that one + if (!string.IsNullOrWhiteSpace(ModelsNamespace)) + return ModelsNamespace; + + // default + // fixme - should NOT reference config here, should make ModelsNamespace mandatory + return Config.ModelsNamespace; + } + + protected string GetModelsBaseClassName(TypeModel type) + { + // default + return type.IsElement ? "PublishedElementModel" : "PublishedContentModel"; + } + } +} diff --git a/src/Umbraco.ModelsBuilder/Building/PropertyModel.cs b/src/Umbraco.ModelsBuilder/Building/PropertyModel.cs new file mode 100644 index 0000000000..5f2545ec2a --- /dev/null +++ b/src/Umbraco.ModelsBuilder/Building/PropertyModel.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; + +namespace Umbraco.ModelsBuilder.Building +{ + /// + /// Represents a model property. + /// + public class PropertyModel + { + /// + /// Gets the alias of the property. + /// + public string Alias; + + /// + /// Gets the name of the property. + /// + public string Name; + + /// + /// Gets the description of the property. + /// + public string Description; + + /// + /// Gets the clr name of the property. + /// + /// This is just the local name eg "Price". + public string ClrName; + + /// + /// Gets the Model Clr type of the property values. + /// + /// As indicated by the PublishedPropertyType, ie by the IPropertyValueConverter + /// if any, else object. May include some ModelType that will need to be mapped. + public Type ModelClrType; + + /// + /// Gets the CLR type name of the property values. + /// + public string ClrTypeName; + + /// + /// Gets the generation errors for the property. + /// + /// This should be null, unless something prevents the property from being + /// generated, and then the value should explain what. This can be used to generate + /// commented out code eg in PureLive. + public List Errors; + + /// + /// Adds an error. + /// + public void AddError(string error) + { + if (Errors == null) Errors = new List(); + Errors.Add(error); + } + } +} diff --git a/src/Umbraco.ModelsBuilder/Building/TextBuilder.cs b/src/Umbraco.ModelsBuilder/Building/TextBuilder.cs new file mode 100644 index 0000000000..e15fdfca6e --- /dev/null +++ b/src/Umbraco.ModelsBuilder/Building/TextBuilder.cs @@ -0,0 +1,579 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; +using Umbraco.ModelsBuilder.Api; +using Umbraco.ModelsBuilder.Configuration; + +namespace Umbraco.ModelsBuilder.Building +{ + /// + /// Implements a builder that works by writing text. + /// + internal class TextBuilder : Builder + { + /// + /// Initializes a new instance of the class with a list of models to generate + /// and the result of code parsing. + /// + /// The list of models to generate. + public TextBuilder(IList typeModels) + : base(typeModels) + { } + + /// + /// Initializes a new instance of the class with a list of models to generate, + /// the result of code parsing, and a models namespace. + /// + /// The list of models to generate. + /// The models namespace. + public TextBuilder(IList typeModels, string modelsNamespace) + : base(typeModels, modelsNamespace) + { } + + // internal for unit tests only + internal TextBuilder() + { } + + private static Config Config => Current.Configs.ModelsBuilder(); + + /// + /// Outputs a generated model to a string builder. + /// + /// The string builder. + /// The model to generate. + public void Generate(StringBuilder sb, TypeModel typeModel) + { + WriteHeader(sb); + + foreach (var t in TypesUsing) + sb.AppendFormat("using {0};\n", t); + + sb.Append("\n"); + sb.AppendFormat("namespace {0}\n", GetModelsNamespace()); + sb.Append("{\n"); + + WriteContentType(sb, typeModel); + + sb.Append("}\n"); + } + + /// + /// Outputs generated models to a string builder. + /// + /// The string builder. + /// The models to generate. + public void Generate(StringBuilder sb, IEnumerable typeModels) + { + WriteHeader(sb); + + foreach (var t in TypesUsing) + sb.AppendFormat("using {0};\n", t); + + // assembly attributes marker + sb.Append("\n//ASSATTR\n"); + + sb.Append("\n"); + sb.AppendFormat("namespace {0}\n", GetModelsNamespace()); + sb.Append("{\n"); + + foreach (var typeModel in typeModels) + { + WriteContentType(sb, typeModel); + sb.Append("\n"); + } + + sb.Append("}\n"); + } + + /// + /// Outputs an "auto-generated" header to a string builder. + /// + /// The string builder. + public static void WriteHeader(StringBuilder sb) + { + TextHeaderWriter.WriteHeader(sb); + } + + // writes an attribute that identifies code generated by a tool + // (helps reduce warnings, tools such as FxCop use it) + // see https://github.com/zpqrtbnk/Zbu.ModelsBuilder/issues/107 + // see https://docs.microsoft.com/en-us/dotnet/api/system.codedom.compiler.generatedcodeattribute + // see https://blogs.msdn.microsoft.com/codeanalysis/2007/04/27/correct-usage-of-the-compilergeneratedattribute-and-the-generatedcodeattribute/ + // + // note that the blog post above clearly states that "Nor should it be applied at the type level if the type being generated is a partial class." + // and since our models are partial classes, we have to apply the attribute against the individual members, not the class itself. + // + private static void WriteGeneratedCodeAttribute(StringBuilder sb, string tabs) + { + sb.AppendFormat("{0}[global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"Umbraco.ModelsBuilder\", \"{1}\")]\n", tabs, ApiVersion.Current.Version); + } + + private void WriteContentType(StringBuilder sb, TypeModel type) + { + string sep; + + if (type.IsMixin) + { + // write the interface declaration + sb.AppendFormat("\t// Mixin Content Type with alias \"{0}\"\n", type.Alias); + if (!string.IsNullOrWhiteSpace(type.Name)) + sb.AppendFormat("\t/// {0}\n", XmlCommentString(type.Name)); + sb.AppendFormat("\tpublic partial interface I{0}", type.ClrName); + var implements = type.BaseType == null + ? (type.HasBase ? null : (type.IsElement ? "PublishedElement" : "PublishedContent")) + : type.BaseType.ClrName; + if (implements != null) + sb.AppendFormat(" : I{0}", implements); + + // write the mixins + sep = implements == null ? ":" : ","; + foreach (var mixinType in type.DeclaringInterfaces.OrderBy(x => x.ClrName)) + { + sb.AppendFormat("{0} I{1}", sep, mixinType.ClrName); + sep = ","; + } + + sb.Append("\n\t{\n"); + + // write the properties - only the local (non-ignored) ones, we're an interface + var more = false; + foreach (var prop in type.Properties.OrderBy(x => x.ClrName)) + { + if (more) sb.Append("\n"); + more = true; + WriteInterfaceProperty(sb, prop); + } + + sb.Append("\t}\n\n"); + } + + // write the class declaration + if (!string.IsNullOrWhiteSpace(type.Name)) + sb.AppendFormat("\t/// {0}\n", XmlCommentString(type.Name)); + // cannot do it now. see note in ImplementContentTypeAttribute + //if (!type.HasImplement) + // sb.AppendFormat("\t[ImplementContentType(\"{0}\")]\n", type.Alias); + sb.AppendFormat("\t[PublishedModel(\"{0}\")]\n", type.Alias); + sb.AppendFormat("\tpublic partial class {0}", type.ClrName); + var inherits = type.HasBase + ? null // has its own base already + : (type.BaseType == null + ? GetModelsBaseClassName(type) + : type.BaseType.ClrName); + if (inherits != null) + sb.AppendFormat(" : {0}", inherits); + + sep = inherits == null ? ":" : ","; + if (type.IsMixin) + { + // if it's a mixin it implements its own interface + sb.AppendFormat("{0} I{1}", sep, type.ClrName); + } + else + { + // write the mixins, if any, as interfaces + // only if not a mixin because otherwise the interface already has them already + foreach (var mixinType in type.DeclaringInterfaces.OrderBy(x => x.ClrName)) + { + sb.AppendFormat("{0} I{1}", sep, mixinType.ClrName); + sep = ","; + } + } + + // begin class body + sb.Append("\n\t{\n"); + + // write the constants & static methods + // as 'new' since parent has its own - or maybe not - disable warning + sb.Append("\t\t// helpers\n"); + sb.Append("#pragma warning disable 0109 // new is redundant\n"); + WriteGeneratedCodeAttribute(sb, "\t\t"); + sb.AppendFormat("\t\tpublic new const string ModelTypeAlias = \"{0}\";\n", + type.Alias); + var itemType = type.IsElement ? TypeModel.ItemTypes.Content : type.ItemType; // fixme + WriteGeneratedCodeAttribute(sb, "\t\t"); + sb.AppendFormat("\t\tpublic new const PublishedItemType ModelItemType = PublishedItemType.{0};\n", + itemType); + WriteGeneratedCodeAttribute(sb, "\t\t"); + sb.Append("\t\tpublic new static PublishedContentType GetModelContentType()\n"); + sb.Append("\t\t\t=> PublishedModelUtility.GetModelContentType(ModelItemType, ModelTypeAlias);\n"); + WriteGeneratedCodeAttribute(sb, "\t\t"); + sb.AppendFormat("\t\tpublic static PublishedPropertyType GetModelPropertyType(Expression> selector)\n", + type.ClrName); + sb.Append("\t\t\t=> PublishedModelUtility.GetModelPropertyType(GetModelContentType(), selector);\n"); + sb.Append("#pragma warning restore 0109\n\n"); + + // write the ctor + sb.AppendFormat("\t\t// ctor\n\t\tpublic {0}(IPublished{1} content)\n\t\t\t: base(content)\n\t\t{{ }}\n\n", + type.ClrName, type.IsElement ? "Element" : "Content"); + + // write the properties + sb.Append("\t\t// properties\n"); + WriteContentTypeProperties(sb, type); + + // close the class declaration + sb.Append("\t}\n"); + } + + private void WriteContentTypeProperties(StringBuilder sb, TypeModel type) + { + var staticMixinGetters = true; + + // write the properties + foreach (var prop in type.Properties.OrderBy(x => x.ClrName)) + WriteProperty(sb, type, prop, staticMixinGetters && type.IsMixin ? type.ClrName : null); + + // no need to write the parent properties since we inherit from the parent + // and the parent defines its own properties. need to write the mixins properties + // since the mixins are only interfaces and we have to provide an implementation. + + // write the mixins properties + foreach (var mixinType in type.ImplementingInterfaces.OrderBy(x => x.ClrName)) + foreach (var prop in mixinType.Properties.OrderBy(x => x.ClrName)) + if (staticMixinGetters) + WriteMixinProperty(sb, prop, mixinType.ClrName); + else + WriteProperty(sb, mixinType, prop); + } + + private void WriteMixinProperty(StringBuilder sb, PropertyModel property, string mixinClrName) + { + sb.Append("\n"); + + // Adds xml summary to each property containing + // property name and property description + if (!string.IsNullOrWhiteSpace(property.Name) || !string.IsNullOrWhiteSpace(property.Description)) + { + sb.Append("\t\t///\n"); + + if (!string.IsNullOrWhiteSpace(property.Description)) + sb.AppendFormat("\t\t/// {0}: {1}\n", XmlCommentString(property.Name), XmlCommentString(property.Description)); + else + sb.AppendFormat("\t\t/// {0}\n", XmlCommentString(property.Name)); + + sb.Append("\t\t///\n"); + } + + WriteGeneratedCodeAttribute(sb, "\t\t"); + sb.AppendFormat("\t\t[ImplementPropertyType(\"{0}\")]\n", property.Alias); + + sb.Append("\t\tpublic "); + WriteClrType(sb, property.ClrTypeName); + + sb.AppendFormat(" {0} => ", + property.ClrName); + WriteNonGenericClrType(sb, GetModelsNamespace() + "." + mixinClrName); + sb.AppendFormat(".{0}(this);\n", + MixinStaticGetterName(property.ClrName)); + } + + private static string MixinStaticGetterName(string clrName) + { + return string.Format("Get{0}", clrName); + } + + private void WriteProperty(StringBuilder sb, TypeModel type, PropertyModel property, string mixinClrName = null) + { + var mixinStatic = mixinClrName != null; + + sb.Append("\n"); + + if (property.Errors != null) + { + sb.Append("\t\t/*\n"); + sb.Append("\t\t * THIS PROPERTY CANNOT BE IMPLEMENTED, BECAUSE:\n"); + sb.Append("\t\t *\n"); + var first = true; + foreach (var error in property.Errors) + { + if (first) first = false; + else sb.Append("\t\t *\n"); + foreach (var s in SplitError(error)) + { + sb.Append("\t\t * "); + sb.Append(s); + sb.Append("\n"); + } + } + sb.Append("\t\t *\n"); + sb.Append("\n"); + } + + // Adds xml summary to each property containing + // property name and property description + if (!string.IsNullOrWhiteSpace(property.Name) || !string.IsNullOrWhiteSpace(property.Description)) + { + sb.Append("\t\t///\n"); + + if (!string.IsNullOrWhiteSpace(property.Description)) + sb.AppendFormat("\t\t/// {0}: {1}\n", XmlCommentString(property.Name), XmlCommentString(property.Description)); + else + sb.AppendFormat("\t\t/// {0}\n", XmlCommentString(property.Name)); + + sb.Append("\t\t///\n"); + } + + WriteGeneratedCodeAttribute(sb, "\t\t"); + sb.AppendFormat("\t\t[ImplementPropertyType(\"{0}\")]\n", property.Alias); + + if (mixinStatic) + { + sb.Append("\t\tpublic "); + WriteClrType(sb, property.ClrTypeName); + sb.AppendFormat(" {0} => {1}(this);\n", + property.ClrName, MixinStaticGetterName(property.ClrName)); + } + else + { + sb.Append("\t\tpublic "); + WriteClrType(sb, property.ClrTypeName); + sb.AppendFormat(" {0} => this.Value", + property.ClrName); + if (property.ModelClrType != typeof(object)) + { + sb.Append("<"); + WriteClrType(sb, property.ClrTypeName); + sb.Append(">"); + } + sb.AppendFormat("(\"{0}\");\n", + property.Alias); + } + + if (property.Errors != null) + { + sb.Append("\n"); + sb.Append("\t\t *\n"); + sb.Append("\t\t */\n"); + } + + if (!mixinStatic) return; + + var mixinStaticGetterName = MixinStaticGetterName(property.ClrName); + + if (type.StaticMixinMethods.Contains(mixinStaticGetterName)) return; + + sb.Append("\n"); + + if (!string.IsNullOrWhiteSpace(property.Name)) + sb.AppendFormat("\t\t/// Static getter for {0}\n", XmlCommentString(property.Name)); + + WriteGeneratedCodeAttribute(sb, "\t\t"); + sb.Append("\t\tpublic static "); + WriteClrType(sb, property.ClrTypeName); + sb.AppendFormat(" {0}(I{1} that) => that.Value", + mixinStaticGetterName, mixinClrName); + if (property.ModelClrType != typeof(object)) + { + sb.Append("<"); + WriteClrType(sb, property.ClrTypeName); + sb.Append(">"); + } + sb.AppendFormat("(\"{0}\");\n", + property.Alias); + } + + private static IEnumerable SplitError(string error) + { + var p = 0; + while (p < error.Length) + { + var n = p + 50; + while (n < error.Length && error[n] != ' ') n++; + if (n >= error.Length) break; + yield return error.Substring(p, n - p); + p = n + 1; + } + if (p < error.Length) + yield return error.Substring(p); + } + + private void WriteInterfaceProperty(StringBuilder sb, PropertyModel property) + { + if (property.Errors != null) + { + sb.Append("\t\t/*\n"); + sb.Append("\t\t * THIS PROPERTY CANNOT BE IMPLEMENTED, BECAUSE:\n"); + sb.Append("\t\t *\n"); + var first = true; + foreach (var error in property.Errors) + { + if (first) first = false; + else sb.Append("\t\t *\n"); + foreach (var s in SplitError(error)) + { + sb.Append("\t\t * "); + sb.Append(s); + sb.Append("\n"); + } + } + sb.Append("\t\t *\n"); + sb.Append("\n"); + } + + if (!string.IsNullOrWhiteSpace(property.Name)) + sb.AppendFormat("\t\t/// {0}\n", XmlCommentString(property.Name)); + WriteGeneratedCodeAttribute(sb, "\t\t"); + sb.Append("\t\t"); + WriteClrType(sb, property.ClrTypeName); + sb.AppendFormat(" {0} {{ get; }}\n", + property.ClrName); + + if (property.Errors != null) + { + sb.Append("\n"); + sb.Append("\t\t *\n"); + sb.Append("\t\t */\n"); + } + } + + // internal for unit tests + internal void WriteClrType(StringBuilder sb, Type type) + { + var s = type.ToString(); + + if (type.IsGenericType) + { + var p = s.IndexOf('`'); + WriteNonGenericClrType(sb, s.Substring(0, p)); + sb.Append("<"); + var args = type.GetGenericArguments(); + for (var i = 0; i < args.Length; i++) + { + if (i > 0) sb.Append(", "); + WriteClrType(sb, args[i]); + } + sb.Append(">"); + } + else + { + WriteNonGenericClrType(sb, s); + } + } + + internal void WriteClrType(StringBuilder sb, string type) + { + var p = type.IndexOf('<'); + if (type.Contains('<')) + { + WriteNonGenericClrType(sb, type.Substring(0, p)); + sb.Append("<"); + var args = type.Substring(p + 1).TrimEnd('>').Split(','); // fixme will NOT work with nested generic types + for (var i = 0; i < args.Length; i++) + { + if (i > 0) sb.Append(", "); + WriteClrType(sb, args[i]); + } + sb.Append(">"); + } + else + { + WriteNonGenericClrType(sb, type); + } + } + + private void WriteNonGenericClrType(StringBuilder sb, string s) + { + // map model types + s = Regex.Replace(s, @"\{(.*)\}\[\*\]", m => ModelsMap[m.Groups[1].Value + "[]"]); + + // takes care eg of "System.Int32" vs. "int" + if (TypesMap.TryGetValue(s, out string typeName)) + { + sb.Append(typeName); + return; + } + + // if full type name matches a using clause, strip + // so if we want Umbraco.Core.Models.IPublishedContent + // and using Umbraco.Core.Models, then we just need IPublishedContent + typeName = s; + string typeUsing = null; + var p = typeName.LastIndexOf('.'); + if (p > 0) + { + var x = typeName.Substring(0, p); + if (Using.Contains(x)) + { + typeName = typeName.Substring(p + 1); + typeUsing = x; + } + else if (x == ModelsNamespace) // that one is used by default + { + typeName = typeName.Substring(p + 1); + typeUsing = ModelsNamespace; + } + } + + // nested types *after* using + typeName = typeName.Replace("+", "."); + + // symbol to test is the first part of the name + // so if type name is Foo.Bar.Nil we want to ensure that Foo is not ambiguous + p = typeName.IndexOf('.'); + var symbol = p > 0 ? typeName.Substring(0, p) : typeName; + + // what we should find - WITHOUT any generic thing - just the type + // no 'using' = the exact symbol + // a 'using' = using.symbol + var match = typeUsing == null ? symbol : (typeUsing + "." + symbol); + + // if not ambiguous, be happy + if (!IsAmbiguousSymbol(symbol, match)) + { + sb.Append(typeName); + return; + } + + // symbol is ambiguous + // if no 'using', must prepend global:: + if (typeUsing == null) + { + sb.Append("global::"); + sb.Append(s.Replace("+", ".")); + return; + } + + // could fullname be non-ambiguous? + // note: all-or-nothing, not trying to segment the using clause + typeName = s.Replace("+", "."); + p = typeName.IndexOf('.'); + symbol = typeName.Substring(0, p); + match = symbol; + + // still ambiguous, must prepend global:: + if (IsAmbiguousSymbol(symbol, match)) + sb.Append("global::"); + + sb.Append(typeName); + } + + private static string XmlCommentString(string s) + { + return s.Replace('<', '{').Replace('>', '}').Replace('\r', ' ').Replace('\n', ' '); + } + + private static readonly IDictionary TypesMap = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "System.Int16", "short" }, + { "System.Int32", "int" }, + { "System.Int64", "long" }, + { "System.String", "string" }, + { "System.Object", "object" }, + { "System.Boolean", "bool" }, + { "System.Void", "void" }, + { "System.Char", "char" }, + { "System.Byte", "byte" }, + { "System.UInt16", "ushort" }, + { "System.UInt32", "uint" }, + { "System.UInt64", "ulong" }, + { "System.SByte", "sbyte" }, + { "System.Single", "float" }, + { "System.Double", "double" }, + { "System.Decimal", "decimal" } + }; + } +} diff --git a/src/Umbraco.ModelsBuilder/Building/TextHeaderWriter.cs b/src/Umbraco.ModelsBuilder/Building/TextHeaderWriter.cs new file mode 100644 index 0000000000..d165f03907 --- /dev/null +++ b/src/Umbraco.ModelsBuilder/Building/TextHeaderWriter.cs @@ -0,0 +1,26 @@ +using System.Text; +using Umbraco.ModelsBuilder.Api; + +namespace Umbraco.ModelsBuilder.Building +{ + public static class TextHeaderWriter + { + /// + /// Outputs an "auto-generated" header to a string builder. + /// + /// The string builder. + public static void WriteHeader(StringBuilder sb) + { + sb.Append("//------------------------------------------------------------------------------\n"); + 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.Append("//\n"); + sb.Append("// Changes to this file will be lost if the code is regenerated.\n"); + sb.Append("// \n"); + sb.Append("//------------------------------------------------------------------------------\n"); + sb.Append("\n"); + } + } +} diff --git a/src/Umbraco.ModelsBuilder/Building/TypeModel.cs b/src/Umbraco.ModelsBuilder/Building/TypeModel.cs new file mode 100644 index 0000000000..941894774e --- /dev/null +++ b/src/Umbraco.ModelsBuilder/Building/TypeModel.cs @@ -0,0 +1,205 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Models.PublishedContent; + +namespace Umbraco.ModelsBuilder.Building +{ + /// + /// Represents a model. + /// + public class TypeModel + { + /// + /// Gets the unique identifier of the corresponding content type. + /// + public int Id; + + /// + /// Gets the alias of the model. + /// + public string Alias; + + /// + /// Gets the name of the content type. + /// + public string Name; + + /// + /// Gets the description of the content type. + /// + public string Description; + + /// + /// Gets the clr name of the model. + /// + /// This is the complete name eg "Foo.Bar.MyContent". + public string ClrName; + + /// + /// Gets the unique identifier of the parent. + /// + /// The parent can either be a base content type, or a content types container. If the content + /// type does not have a base content type, then returns -1. + public int ParentId; + + /// + /// Gets the base model. + /// + /// + /// If the content type does not have a base content type, then returns null. + /// The current model inherits from its base model. + /// + public TypeModel BaseType; // the parent type in Umbraco (type inherits its properties) + + /// + /// Gets the list of properties that are defined by this model. + /// + /// These are only those property that are defined locally by this model, + /// and the list does not contain properties inherited from base models or from mixins. + public readonly List Properties = new List(); + + /// + /// Gets the mixin models. + /// + /// The current model implements mixins. + public readonly List MixinTypes = new List(); + + /// + /// Gets the list of interfaces that this model needs to declare it implements. + /// + /// Some of these interfaces may actually be implemented by a base model + /// that this model inherits from. + public readonly List DeclaringInterfaces = new List(); + + /// + /// Gets the list of interfaces that this model needs to actually implement. + /// + public readonly List ImplementingInterfaces = new List(); + + /// + /// Gets the list of existing static mixin method candidates. + /// + public readonly List StaticMixinMethods = new List(); + + /// + /// Gets a value indicating whether this model has a base class. + /// + /// Can be either because the content type has a base content type declared in Umbraco, + /// or because the existing user's code declares a base class for this model. + public bool HasBase; + + /// + /// Gets a value indicating whether this model is used as a mixin by another model. + /// + public bool IsMixin; + + /// + /// Gets a value indicating whether this model is the base model of another model. + /// + public bool IsParent; + + /// + /// Gets a value indicating whether the type is an element. + /// + public bool IsElement => ItemType == ItemTypes.Element; + + /// + /// Represents the different model item types. + /// + public enum ItemTypes + { + /// + /// Element. + /// + Element, + + /// + /// Content. + /// + Content, + + /// + /// Media. + /// + Media, + + /// + /// Member. + /// + Member + } + + private ItemTypes _itemType; + + /// + /// Gets or sets the model item type. + /// + public ItemTypes ItemType + { + get { return _itemType; } + set + { + switch (value) + { + case ItemTypes.Element: + case ItemTypes.Content: + case ItemTypes.Media: + case ItemTypes.Member: + _itemType = value; + break; + default: + throw new ArgumentException("value"); + } + } + } + + /// + /// Recursively collects all types inherited, or implemented as interfaces, by a specified type. + /// + /// The collection. + /// The type. + /// Includes the specified type. + internal static void CollectImplems(ICollection types, TypeModel type) + { + if (types.Contains(type) == false) + types.Add(type); + if (type.BaseType != null) + CollectImplems(types, type.BaseType); + foreach (var mixin in type.MixinTypes) + CollectImplems(types, mixin); + } + + /// + /// Enumerates the base models starting from the current model up. + /// + /// Indicates whether the enumeration should start with the current model + /// or from its base model. + /// The base models. + public IEnumerable EnumerateBaseTypes(bool andSelf = false) + { + var typeModel = andSelf ? this : BaseType; + while (typeModel != null) + { + yield return typeModel; + typeModel = typeModel.BaseType; + } + } + + /// + /// Maps ModelType. + /// + public static void MapModelTypes(IList typeModels, string ns) + { + var hasNs = !string.IsNullOrWhiteSpace(ns); + var map = typeModels.ToDictionary(x => x.Alias, x => hasNs ? (ns + "." + x.ClrName) : x.ClrName); + foreach (var typeModel in typeModels) + { + foreach (var propertyModel in typeModel.Properties) + { + propertyModel.ClrTypeName = ModelType.MapToName(propertyModel.ModelClrType, map); + } + } + } + } +} diff --git a/src/Umbraco.ModelsBuilder/ConfigsExtensions.cs b/src/Umbraco.ModelsBuilder/ConfigsExtensions.cs new file mode 100644 index 0000000000..dc0b136422 --- /dev/null +++ b/src/Umbraco.ModelsBuilder/ConfigsExtensions.cs @@ -0,0 +1,20 @@ +using Umbraco.Core.Configuration; +using Umbraco.ModelsBuilder.Configuration; + +namespace Umbraco.ModelsBuilder +{ + /// + /// Provides extension methods for the class. + /// + public static class ConfigsExtensions + { + /// + /// Gets the models builder configuration. + /// + /// Getting the models builder configuration freezes its state, + /// and any attempt at modifying the configuration using the Setup method + /// will be ignored. + public static Config ModelsBuilder(this Configs configs) + => configs.GetConfig(); + } +} \ No newline at end of file diff --git a/src/Umbraco.ModelsBuilder/Configuration/ClrNameSource.cs b/src/Umbraco.ModelsBuilder/Configuration/ClrNameSource.cs new file mode 100644 index 0000000000..d195846411 --- /dev/null +++ b/src/Umbraco.ModelsBuilder/Configuration/ClrNameSource.cs @@ -0,0 +1,28 @@ +namespace Umbraco.ModelsBuilder.Configuration +{ + /// + /// Defines the CLR name sources. + /// + public enum ClrNameSource + { + /// + /// No source. + /// + Nothing = 0, + + /// + /// Use the name as source. + /// + Name, + + /// + /// Use the alias as source. + /// + Alias, + + /// + /// Use the alias directly. + /// + RawAlias + } +} diff --git a/src/Umbraco.ModelsBuilder/Configuration/Config.cs b/src/Umbraco.ModelsBuilder/Configuration/Config.cs new file mode 100644 index 0000000000..8fdc10d5dc --- /dev/null +++ b/src/Umbraco.ModelsBuilder/Configuration/Config.cs @@ -0,0 +1,224 @@ +using System; +using System.Configuration; +using System.IO; +using System.Web.Configuration; +using System.Web.Hosting; +using Umbraco.Core; + +namespace Umbraco.ModelsBuilder.Configuration +{ + /// + /// Represents the models builder configuration. + /// + public class Config + { + internal const string DefaultModelsNamespace = "Umbraco.Web.PublishedModels"; + internal const string DefaultModelsDirectory = "~/App_Data/Models"; + + /// + /// Initializes a new instance of the class. + /// + public Config() + { + 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"; + + // ensure defaults are initialized for tests + ModelsNamespace = DefaultModelsNamespace; + ModelsDirectory = HostingEnvironment.IsHosted + ? HostingEnvironment.MapPath(DefaultModelsDirectory) + : DefaultModelsDirectory.TrimStart("~/"); + DebugLevel = 0; + + // 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"); + + // default: true + EnableFactory = !ConfigurationManager.AppSettings[prefix + "EnableFactory"].InvariantEquals("false"); + FlagOutOfDateModels = !ConfigurationManager.AppSettings[prefix + "FlagOutOfDateModels"].InvariantEquals("false"); + + // default: initialized above with DefaultModelsNamespace const + var value = ConfigurationManager.AppSettings[prefix + "ModelsNamespace"]; + if (!string.IsNullOrWhiteSpace(value)) + ModelsNamespace = value; + + // default: initialized above with DefaultModelsDirectory const + value = ConfigurationManager.AppSettings[prefix + "ModelsDirectory"]; + if (!string.IsNullOrWhiteSpace(value)) + { + var root = HostingEnvironment.IsHosted + ? HostingEnvironment.MapPath("~/") + : Directory.GetCurrentDirectory(); + if (root == null) + throw new ConfigurationErrorsException("Could not determine root directory."); + + // GetModelsDirectory will ensure that the path is safe + ModelsDirectory = GetModelsDirectory(root, value, AcceptUnsafeModelsDirectory); + } + + // default: 0 + value = ConfigurationManager.AppSettings[prefix + "DebugLevel"]; + if (!string.IsNullOrWhiteSpace(value)) + { + int debugLevel; + if (!int.TryParse(value, out 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; + } + + /// + /// Initializes a new instance of the class. + /// + public Config( + bool enable = false, + ModelsMode modelsMode = ModelsMode.Nothing, + string modelsNamespace = null, + bool enableFactory = true, + bool flagOutOfDateModels = true, + string modelsDirectory = null, + bool acceptUnsafeModelsDirectory = false, + int debugLevel = 0) + { + Enable = enable; + ModelsMode = modelsMode; + + ModelsNamespace = string.IsNullOrWhiteSpace(modelsNamespace) ? DefaultModelsNamespace : modelsNamespace; + EnableFactory = enableFactory; + FlagOutOfDateModels = flagOutOfDateModels; + ModelsDirectory = string.IsNullOrWhiteSpace(modelsDirectory) ? DefaultModelsDirectory : modelsDirectory; + AcceptUnsafeModelsDirectory = acceptUnsafeModelsDirectory; + DebugLevel = debugLevel; + } + + // internal for tests + internal static string GetModelsDirectory(string root, string config, bool acceptUnsafe) + { + // making sure it is safe, ie under the website root, + // unless AcceptUnsafeModelsDirectory and then everything is OK. + + if (!Path.IsPathRooted(root)) + throw new ConfigurationErrorsException($"Root is not rooted \"{root}\"."); + + if (config.StartsWith("~/")) + { + var dir = Path.Combine(root, config.TrimStart("~/")); + + // sanitize - GetFullPath will take care of any relative + // segments in path, eg '../../foo.tmp' - it may throw a SecurityException + // if the combined path reaches illegal parts of the filesystem + dir = Path.GetFullPath(dir); + root = Path.GetFullPath(root); + + if (!dir.StartsWith(root) && !acceptUnsafe) + throw new ConfigurationErrorsException($"Invalid models directory \"{config}\"."); + + return dir; + } + + if (acceptUnsafe) + return Path.GetFullPath(config); + + throw new ConfigurationErrorsException($"Invalid models directory \"{config}\"."); + } + + /// + /// Gets a value indicating whether the whole models experience is enabled. + /// + /// + /// If this is false then absolutely nothing happens. + /// Default value is false which means that unless we have this setting, nothing happens. + /// + public bool Enable { get; } + + /// + /// Gets the models mode. + /// + public ModelsMode ModelsMode { get; } + + /// + /// Gets a value indicating whether system.web/compilation/@debug is true. + /// + public bool IsDebug + { + get + { + var section = (CompilationSection) ConfigurationManager.GetSection("system.web/compilation"); + return section != null && section.Debug; + } + } + + /// + /// Gets the models namespace. + /// + /// That value could be overriden by other (attribute in user's code...). Return default if no value was supplied. + public string ModelsNamespace { get; } + + /// + /// Gets a value indicating whether we should enable the models factory. + /// + /// Default value is true because no factory is enabled by default in Umbraco. + public bool EnableFactory { get; } + + /// + /// Gets a value indicating whether we should flag out-of-date models. + /// + /// 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; } + + /// + /// Gets the models directory. + /// + /// Default is ~/App_Data/Models but that can be changed. + public string ModelsDirectory { get; } + + /// + /// Gets a value indicating whether to accept an unsafe value for ModelsDirectory. + /// + /// An unsafe value is an absolute path, or a relative path pointing outside + /// of the website root. + public bool AcceptUnsafeModelsDirectory { get; } + + /// + /// Gets a value indicating the debug log level. + /// + /// 0 means minimal (safe on live site), anything else means more and more details (maybe not safe). + public int DebugLevel { get; } + } +} diff --git a/src/Umbraco.ModelsBuilder/Configuration/ModelsMode.cs b/src/Umbraco.ModelsBuilder/Configuration/ModelsMode.cs new file mode 100644 index 0000000000..cc36099bc5 --- /dev/null +++ b/src/Umbraco.ModelsBuilder/Configuration/ModelsMode.cs @@ -0,0 +1,36 @@ +namespace Umbraco.ModelsBuilder.Configuration +{ + /// + /// Defines the models generation modes. + /// + public enum ModelsMode + { + /// + /// Do not generate models. + /// + Nothing = 0, // default value + + /// + /// Generate models in memory. + /// When: a content type change occurs. + /// + /// The app does not restart. Models are available in views exclusively. + PureLive, + + /// + /// Generate models in AppData. + /// When: generation is triggered. + /// + /// Generation can be triggered from the dashboard. The app does not restart. + /// Models are not compiled and thus are not available to the project. + AppData, + + /// + /// Generate models in AppData. + /// When: a content type change occurs, or generation is triggered. + /// + /// Generation can be triggered from the dashboard. The app does not restart. + /// Models are not compiled and thus are not available to the project. + LiveAppData + } +} diff --git a/src/Umbraco.ModelsBuilder/Configuration/ModelsModeExtensions.cs b/src/Umbraco.ModelsBuilder/Configuration/ModelsModeExtensions.cs new file mode 100644 index 0000000000..f40d7973b8 --- /dev/null +++ b/src/Umbraco.ModelsBuilder/Configuration/ModelsModeExtensions.cs @@ -0,0 +1,37 @@ +namespace Umbraco.ModelsBuilder.Configuration +{ + /// + /// Provides extensions for the enumeration. + /// + public static class ModelsModeExtensions + { + /// + /// Gets a value indicating whether the mode is LiveAnything or PureLive. + /// + public static bool IsLive(this ModelsMode modelsMode) + { + return + modelsMode == ModelsMode.PureLive + || modelsMode == ModelsMode.LiveAppData; + } + + /// + /// Gets a value indicating whether the mode is LiveAnything but not PureLive. + /// + public static bool IsLiveNotPure(this ModelsMode modelsMode) + { + return + modelsMode == ModelsMode.LiveAppData; + } + + /// + /// Gets a value indicating whether the mode supports explicit generation (as opposed to pure live). + /// + public static bool SupportsExplicitGeneration(this ModelsMode modelsMode) + { + return + modelsMode == ModelsMode.AppData + || modelsMode == ModelsMode.LiveAppData; + } + } +} diff --git a/src/Umbraco.ModelsBuilder/Dashboard/BuilderDashboardHelper.cs b/src/Umbraco.ModelsBuilder/Dashboard/BuilderDashboardHelper.cs new file mode 100644 index 0000000000..75c318faab --- /dev/null +++ b/src/Umbraco.ModelsBuilder/Dashboard/BuilderDashboardHelper.cs @@ -0,0 +1,65 @@ +using System.Text; +using Umbraco.Core.Composing; +using Umbraco.ModelsBuilder.Configuration; +using Umbraco.ModelsBuilder.Umbraco; + +namespace Umbraco.ModelsBuilder.Dashboard +{ + internal static class BuilderDashboardHelper + { + private static Config Config => Current.Configs.ModelsBuilder(); + + public static bool CanGenerate() + { + return Config.ModelsMode.SupportsExplicitGeneration(); + } + + public static bool AreModelsOutOfDate() + { + return OutOfDateModelsStatus.IsOutOfDate; + } + + public static string LastError() + { + return ModelsGenerationError.GetLastError(); + } + + public static string Text() + { + var config = Config; + + if (!config.Enable) + return "Version: " + Api.ApiVersion.Current.Version + "
 
ModelsBuilder is disabled
(the .Enable key is missing, or its value is not 'true')."; + + var sb = new StringBuilder(); + + sb.Append("Version: "); + sb.Append(Api.ApiVersion.Current.Version); + sb.Append("
 
"); + + sb.Append("ModelsBuilder is enabled, with the following configuration:"); + + sb.Append("
    "); + + sb.Append("
  • The models factory is "); + sb.Append(config.EnableFactory || config.ModelsMode == ModelsMode.PureLive + ? "enabled" + : "not enabled. Umbraco will not use models"); + sb.Append(".
  • "); + + sb.Append(config.ModelsMode != ModelsMode.Nothing + ? $"
  • {config.ModelsMode} models are enabled.
  • " + : "
  • No models mode is specified: models will not be generated.
  • "); + + sb.Append($"
  • Models namespace is {config.ModelsNamespace}.
  • "); + + sb.Append("
  • Tracking of out-of-date models is "); + sb.Append(config.FlagOutOfDateModels ? "enabled" : "not enabled"); + sb.Append(".
  • "); + + sb.Append("
"); + + return sb.ToString(); + } + } +} diff --git a/src/Umbraco.ModelsBuilder/EnumerableExtensions.cs b/src/Umbraco.ModelsBuilder/EnumerableExtensions.cs new file mode 100644 index 0000000000..da77bfa958 --- /dev/null +++ b/src/Umbraco.ModelsBuilder/EnumerableExtensions.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; + +namespace Umbraco.ModelsBuilder +{ + public static class EnumerableExtensions + { + public static void RemoveAll(this IList list, Func predicate) + { + for (var i = 0; i < list.Count; i++) + { + if (predicate(list[i])) + { + list.RemoveAt(i--); // i-- is important here! + } + } + } + + public static IEnumerable And(this IEnumerable enumerable, T item) + { + foreach (var x in enumerable) yield return x; + yield return item; + } + + public static IEnumerable AndIfNotNull(this IEnumerable enumerable, T item) + where T : class + { + foreach (var x in enumerable) yield return x; + if (item != null) + yield return item; + } + } +} diff --git a/src/Umbraco.ModelsBuilder/ImplementPropertyTypeAttribute.cs b/src/Umbraco.ModelsBuilder/ImplementPropertyTypeAttribute.cs new file mode 100644 index 0000000000..a3dba740c1 --- /dev/null +++ b/src/Umbraco.ModelsBuilder/ImplementPropertyTypeAttribute.cs @@ -0,0 +1,19 @@ +using System; + +namespace Umbraco.ModelsBuilder +{ + /// + /// Indicates that a property implements a given property alias. + /// + /// And therefore it should not be generated. + [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)] + public sealed class ImplementPropertyTypeAttribute : Attribute + { + public ImplementPropertyTypeAttribute(string alias) + { + Alias = alias; + } + + public string Alias { get; private set; } + } +} diff --git a/src/Umbraco.ModelsBuilder/ModelsBuilderAssemblyAttribute.cs b/src/Umbraco.ModelsBuilder/ModelsBuilderAssemblyAttribute.cs new file mode 100644 index 0000000000..ed956852f8 --- /dev/null +++ b/src/Umbraco.ModelsBuilder/ModelsBuilderAssemblyAttribute.cs @@ -0,0 +1,23 @@ +using System; + +namespace Umbraco.ModelsBuilder +{ + /// + /// Indicates that an Assembly is a Models Builder assembly. + /// + [AttributeUsage(AttributeTargets.Assembly /*, AllowMultiple = false, Inherited = false*/)] + public sealed class ModelsBuilderAssemblyAttribute : Attribute + { + /// + /// Gets or sets a value indicating whether the assembly is a PureLive assembly. + /// + /// A Models Builder assembly can be either PureLive or normal Dll. + public bool PureLive { get; set; } + + /// + /// Gets or sets a hash value representing the state of the custom source code files + /// and the Umbraco content types that were used to generate and compile the assembly. + /// + public string SourceHash { get; set; } + } +} diff --git a/src/Umbraco.ModelsBuilder/Properties/AssemblyInfo.cs b/src/Umbraco.ModelsBuilder/Properties/AssemblyInfo.cs index 8ec221bffb..b576807bb7 100644 --- a/src/Umbraco.ModelsBuilder/Properties/AssemblyInfo.cs +++ b/src/Umbraco.ModelsBuilder/Properties/AssemblyInfo.cs @@ -1,36 +1,10 @@ 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.ModelsBuilder")] -[assembly: AssemblyDescription("")] +[assembly: AssemblyDescription("Umbraco ModelsBuilder")] [assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Umbraco.ModelsBuilder")] -[assembly: AssemblyCopyright("Copyright © 2019")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] +[assembly: AssemblyProduct("Umbraco CMS")] -// 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("52ac0ba8-a60e-4e36-897b-e8b97a54ed1c")] - -// 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.ModelsBuilder/PublishedElementExtensions.cs b/src/Umbraco.ModelsBuilder/PublishedElementExtensions.cs new file mode 100644 index 0000000000..1211c4fdad --- /dev/null +++ b/src/Umbraco.ModelsBuilder/PublishedElementExtensions.cs @@ -0,0 +1,51 @@ +using System; +using System.Linq.Expressions; +using System.Reflection; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.ModelsBuilder; + +// same namespace as original Umbraco.Web PublishedElementExtensions +// ReSharper disable once CheckNamespace +namespace Umbraco.Web +{ + /// + /// Provides extension methods to models. + /// + public static class PublishedElementExtensions + { + /// + /// 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) + where TModel : IPublishedElement + { + var alias = GetAlias(model, property); + return model.Value(alias, culture, segment, fallback, defaultValue); + } + + // fixme that one should be public so ppl can use it + private static string GetAlias(TModel model, Expression> property) + { + if (property.NodeType != ExpressionType.Lambda) + throw new ArgumentException("Not a proper lambda expression (lambda).", nameof(property)); + + var lambda = (LambdaExpression) property; + var lambdaBody = lambda.Body; + + if (lambdaBody.NodeType != ExpressionType.MemberAccess) + throw new ArgumentException("Not a proper lambda expression (body).", nameof(property)); + + var memberExpression = (MemberExpression) lambdaBody; + if (memberExpression.Expression.NodeType != ExpressionType.Parameter) + throw new ArgumentException("Not a proper lambda expression (member).", nameof(property)); + + var member = memberExpression.Member; + + 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/PureLiveAssemblyAttribute.cs b/src/Umbraco.ModelsBuilder/PureLiveAssemblyAttribute.cs new file mode 100644 index 0000000000..dfe369dc21 --- /dev/null +++ b/src/Umbraco.ModelsBuilder/PureLiveAssemblyAttribute.cs @@ -0,0 +1,15 @@ +using System; + +namespace Umbraco.ModelsBuilder +{ + /// + /// Indicates that an Assembly is a PureLive models assembly. + /// + /// Though technically not required, ie models will work without it, the attribute + /// can be used by Umbraco view models binder to figure out whether the model type comes + /// from a PureLive Assembly. + [Obsolete("Should use ModelsBuilderAssemblyAttribute but that requires a change in Umbraco Core.")] + [AttributeUsage(AttributeTargets.Assembly /*, AllowMultiple = false, Inherited = false*/)] + public sealed class PureLiveAssemblyAttribute : Attribute + { } +} diff --git a/src/Umbraco.ModelsBuilder/ReferencedAssemblies.cs b/src/Umbraco.ModelsBuilder/ReferencedAssemblies.cs new file mode 100644 index 0000000000..e0a20eb9d6 --- /dev/null +++ b/src/Umbraco.ModelsBuilder/ReferencedAssemblies.cs @@ -0,0 +1,179 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Web.Compilation; +using System.Web.Hosting; +using Umbraco.Core; + +namespace Umbraco.ModelsBuilder +{ + internal static class ReferencedAssemblies + { + private static readonly Lazy> LazyLocations; + + static ReferencedAssemblies() + { + LazyLocations = new Lazy>(() => HostingEnvironment.IsHosted + ? GetAllReferencedAssembliesLocationFromBuildManager() + : GetAllReferencedAssembliesFromDomain()); + } + + /// + /// Gets the assembly locations of all the referenced assemblies, that + /// are not dynamic, and have a non-null nor empty location. + /// + public static IEnumerable Locations => LazyLocations.Value; + + public static Assembly GetNetStandardAssembly(List assemblies) + { + if (assemblies == null) + assemblies = BuildManager.GetReferencedAssemblies().Cast().ToList(); + + // for some reason, netstandard is also missing from BuildManager.ReferencedAssemblies and yet, is part of + // the references that CSharpCompiler (above) receives - where is it coming from? cannot figure it out + try + { + // so, resorting to an ugly trick + // we should have System.Reflection.Metadata around, and it should reference netstandard + var someAssembly = assemblies.First(x => x.FullName.StartsWith("System.Reflection.Metadata,")); + var netStandardAssemblyName = someAssembly.GetReferencedAssemblies().First(x => x.FullName.StartsWith("netstandard,")); + var netStandard = Assembly.Load(netStandardAssemblyName.FullName); + return netStandard; + } + catch { /* never mind */ } + + return null; + } + + public static Assembly GetNetStandardAssembly() + { + // in PreApplicationStartMethod we cannot get BuildManager.Referenced assemblies, do it differently + try + { + var someAssembly = Assembly.Load("System.Reflection.Metadata"); + var netStandardAssemblyName = someAssembly.GetReferencedAssemblies().First(x => x.FullName.StartsWith("netstandard,")); + var netStandard = Assembly.Load(netStandardAssemblyName.FullName); + return netStandard; + } + catch { /* never mind */ } + + return null; + } + + // hosted, get referenced assemblies from the BuildManager and filter + private static IEnumerable GetAllReferencedAssembliesLocationFromBuildManager() + { + var assemblies = BuildManager.GetReferencedAssemblies().Cast().ToList(); + + assemblies.Add(typeof(ReferencedAssemblies).Assembly); // always include ourselves + + // see https://github.com/aspnet/RoslynCodeDomProvider/blob/master/src/Microsoft.CodeDom.Providers.DotNetCompilerPlatform/CSharpCompiler.cs: + // mentions "Bug 913691: Explicitly add System.Runtime as a reference." + // and explicitly adds System.Runtime to references before invoking csc.exe + // so, doing the same here + try + { + var systemRuntime = Assembly.Load("System.Runtime, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"); + assemblies.Add(systemRuntime); + } + catch { /* never mind */ } + + // for some reason, netstandard is also missing from BuildManager.ReferencedAssemblies and yet, is part of + // the references that CSharpCompiler (above) receives - where is it coming from? cannot figure it out + var netStandard = GetNetStandardAssembly(assemblies); + if (netStandard != null) assemblies.Add(netStandard); + + return assemblies + .Where(x => !x.IsDynamic && !x.Location.IsNullOrWhiteSpace()) + .Select(x => x.Location) + .Distinct() + .ToList(); + } + + // non-hosted, do our best + private static IEnumerable GetAllReferencedAssembliesFromDomain() + { + //TODO: This method has bugs since I've been stuck in an infinite loop with it, though this shouldn't + // execute while in the web application anyways. + + var assemblies = new List(); + var tmp1 = new List(); + var failed = new List(); + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies() + .Where(x => x.IsDynamic == false) + .Where(x => !string.IsNullOrWhiteSpace(x.Location))) // though... IsDynamic should be enough? + { + assemblies.Add(assembly); + tmp1.Add(assembly); + } + + // fixme - AssemblyUtility questions + // - should we also load everything that's in the same directory? + // - do we want to load in the current app domain? + // - if this runs within Umbraco then we have already loaded them all? + + while (tmp1.Count > 0) + { + var tmp2 = tmp1 + .SelectMany(x => x.GetReferencedAssemblies()) + .Distinct() + .Where(x => assemblies.All(xx => x.FullName != xx.FullName)) // we don't have it already + .Where(x => failed.All(xx => x.FullName != xx.FullName)) // it hasn't failed already + .ToArray(); + tmp1.Clear(); + foreach (var assemblyName in tmp2) + { + try + { + var assembly = AppDomain.CurrentDomain.Load(assemblyName); + assemblies.Add(assembly); + tmp1.Add(assembly); + } + catch + { + failed.Add(assemblyName); + } + } + } + return assemblies.Select(x => x.Location).Distinct(); + } + + + // ---- + + private static IEnumerable GetDeepReferencedAssemblies(Assembly assembly) + { + var visiting = new Stack(); + var visited = new HashSet(); + + visiting.Push(assembly); + visited.Add(assembly); + while (visiting.Count > 0) + { + var visAsm = visiting.Pop(); + foreach (var refAsm in visAsm.GetReferencedAssemblies() + .Select(TryLoad) + .Where(x => x != null && visited.Contains(x) == false)) + { + yield return refAsm; + visiting.Push(refAsm); + visited.Add(refAsm); + } + } + } + + private static Assembly TryLoad(AssemblyName name) + { + try + { + return AppDomain.CurrentDomain.Load(name); + } + catch (Exception) + { + //Console.WriteLine(name); + return null; + } + } + } +} diff --git a/src/Umbraco.ModelsBuilder/TypeExtensions.cs b/src/Umbraco.ModelsBuilder/TypeExtensions.cs new file mode 100644 index 0000000000..d3b3ff6b4e --- /dev/null +++ b/src/Umbraco.ModelsBuilder/TypeExtensions.cs @@ -0,0 +1,22 @@ +using System; + +namespace Umbraco.ModelsBuilder +{ + internal static class TypeExtensions + { + /// + /// Creates a generic instance of a generic type with the proper actual type of an object. + /// + /// A generic type such as Something{} + /// An object whose type is used as generic type param. + /// Arguments for the constructor. + /// A generic instance of the generic type with the proper type. + /// Usage... typeof (Something{}).CreateGenericInstance(object1, object2, object3) will return + /// a Something{Type1} if object1.GetType() is Type1. + public static object CreateGenericInstance(this Type genericType, object typeParmObj, params object[] ctorArgs) + { + var type = genericType.MakeGenericType(typeParmObj.GetType()); + return Activator.CreateInstance(type, ctorArgs); + } + } +} diff --git a/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj b/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj index d1d3ca205d..5b0272a526 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj +++ b/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj @@ -32,7 +32,10 @@ + + + @@ -41,7 +44,57 @@ + + Properties\SolutionInfo.cs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {31785BC3-256C-4613-B2F5-A1B0BDDED8C1} + Umbraco.Core + + + {651e1350-91b6-44b7-bd60-7207006d7003} + Umbraco.Web + + + + + 5.2.7 + \ No newline at end of file diff --git a/src/Umbraco.ModelsBuilder/Umbraco/HashCombiner.cs b/src/Umbraco.ModelsBuilder/Umbraco/HashCombiner.cs new file mode 100644 index 0000000000..e11662eb24 --- /dev/null +++ b/src/Umbraco.ModelsBuilder/Umbraco/HashCombiner.cs @@ -0,0 +1,38 @@ +using System; +using System.Globalization; + +namespace Umbraco.ModelsBuilder.Umbraco +{ + // because, of course, it's internal in Umbraco + // see also System.Web.Util.HashCodeCombiner + class HashCombiner + { + private long _combinedHash = 5381L; + + public void Add(int i) + { + _combinedHash = ((_combinedHash << 5) + _combinedHash) ^ i; + } + + public void Add(object o) + { + Add(o.GetHashCode()); + } + + public void Add(DateTime d) + { + Add(d.GetHashCode()); + } + + public void Add(string s) + { + if (s == null) return; + Add((StringComparer.InvariantCulture).GetHashCode(s)); + } + + public string GetCombinedHashCode() + { + return _combinedHash.ToString("x", CultureInfo.InvariantCulture); + } + } +} diff --git a/src/Umbraco.ModelsBuilder/Umbraco/HashHelper.cs b/src/Umbraco.ModelsBuilder/Umbraco/HashHelper.cs new file mode 100644 index 0000000000..e4a0705ec0 --- /dev/null +++ b/src/Umbraco.ModelsBuilder/Umbraco/HashHelper.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; +using System.Linq; +using Umbraco.ModelsBuilder.Building; + +namespace Umbraco.ModelsBuilder.Umbraco +{ + class HashHelper + { + public static string Hash(IEnumerable typeModels) + { + var hash = new HashCombiner(); + + // see Umbraco.ModelsBuilder.Umbraco.Application for what's important to hash + // ie what comes from Umbraco (not computed by ModelsBuilder) and makes a difference + + foreach (var typeModel in typeModels.OrderBy(x => x.Alias)) + { + hash.Add("--- CONTENT TYPE MODEL ---"); + hash.Add(typeModel.Id); + hash.Add(typeModel.Alias); + hash.Add(typeModel.ClrName); + hash.Add(typeModel.ParentId); + hash.Add(typeModel.Name); + hash.Add(typeModel.Description); + hash.Add(typeModel.ItemType.ToString()); + hash.Add("MIXINS:" + string.Join(",", typeModel.MixinTypes.OrderBy(x => x.Id).Select(x => x.Id))); + + foreach (var prop in typeModel.Properties.OrderBy(x => x.Alias)) + { + hash.Add("--- PROPERTY ---"); + hash.Add(prop.Alias); + hash.Add(prop.ClrName); + hash.Add(prop.Name); + hash.Add(prop.Description); + hash.Add(prop.ModelClrType.ToString()); // see ModelType tests, want ToString() not FullName + } + } + + return hash.GetCombinedHashCode(); + } + } +} diff --git a/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProvider.cs b/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProvider.cs new file mode 100644 index 0000000000..194e7de695 --- /dev/null +++ b/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProvider.cs @@ -0,0 +1,133 @@ +using System; +using System.Threading; +using System.Web; +using System.Web.Hosting; +using Umbraco.Core.Composing; +using Umbraco.Core.Logging; +using Umbraco.ModelsBuilder.Configuration; +using Umbraco.ModelsBuilder.Umbraco; +using Umbraco.Web.Cache; + +// will install only if configuration says it needs to be installed +[assembly: PreApplicationStartMethod(typeof(LiveModelsProviderModule), "Install")] + +namespace Umbraco.ModelsBuilder.Umbraco +{ + // supports LiveDll and LiveAppData - but not PureLive + public sealed class LiveModelsProvider + { + private static UmbracoServices _umbracoServices; + private static Mutex _mutex; + private static int _req; + + private static Config Config => Current.Configs.ModelsBuilder(); + + // we do not manage pure live here + internal static bool IsEnabled => Config.ModelsMode.IsLiveNotPure(); + + internal static void Install(UmbracoServices umbracoServices) + { + // just be sure + if (!IsEnabled) + return; + + _umbracoServices = umbracoServices; + + // initialize mutex + // ApplicationId will look like "/LM/W3SVC/1/Root/AppName" + // name is system-wide and must be less than 260 chars + var name = HostingEnvironment.ApplicationID + "/UmbracoLiveModelsProvider"; + _mutex = new Mutex(false, name); + + // anything changes, and we want to re-generate models. + ContentTypeCacheRefresher.CacheUpdated += RequestModelsGeneration; + DataTypeCacheRefresher.CacheUpdated += RequestModelsGeneration; + + // at the end of a request since we're restarting the pool + // NOTE - this does NOT trigger - see module below + //umbracoApplication.EndRequest += GenerateModelsIfRequested; + } + + // NOTE + // Using HttpContext Items fails because CacheUpdated triggers within + // some asynchronous backend task where we seem to have no HttpContext. + + // So we use a static (non request-bound) var to register that models + // need to be generated. Could be by another request. Anyway. We could + // have collisions but... you know the risk. + + private static void RequestModelsGeneration(object sender, EventArgs args) + { + //HttpContext.Current.Items[this] = true; + Current.Logger.Debug("Requested to generate models."); + Interlocked.Exchange(ref _req, 1); + } + + public static void GenerateModelsIfRequested(object sender, EventArgs args) + { + //if (HttpContext.Current.Items[this] == null) return; + if (Interlocked.Exchange(ref _req, 0) == 0) return; + + // cannot use a simple lock here because we don't want another AppDomain + // to generate while we do... and there could be 2 AppDomains if the app restarts. + + try + { + Current.Logger.Debug("Generate models..."); + const int timeout = 2*60*1000; // 2 mins + _mutex.WaitOne(timeout); // wait until it is safe, and acquire + Current.Logger.Info("Generate models now."); + GenerateModels(); + ModelsGenerationError.Clear(); + Current.Logger.Info("Generated."); + } + catch (TimeoutException) + { + Current.Logger.Warn("Timeout, models were NOT generated."); + } + catch (Exception e) + { + ModelsGenerationError.Report("Failed to build Live models.", e); + Current.Logger.Error("Failed to generate models.", e); + } + finally + { + _mutex.ReleaseMutex(); // release + } + } + + private static void GenerateModels() + { + var modelsDirectory = Config.ModelsDirectory; + var modelsNamespace = Config.ModelsNamespace; + + var bin = HostingEnvironment.MapPath("~/bin"); + if (bin == null) + throw new Exception("Panic: bin is null."); + + // EnableDllModels will recycle the app domain - but this request will end properly + ModelsBuilderBackOfficeController.GenerateModels(_umbracoServices, modelsDirectory, modelsNamespace); + } + } + + // have to do this because it's the only way to subscribe to EndRequest, + // module is installed by assembly attribute at the top of this file + public class LiveModelsProviderModule : IHttpModule + { + public void Init(HttpApplication app) + { + app.EndRequest += LiveModelsProvider.GenerateModelsIfRequested; + } + + public void Dispose() + { + // nothing + } + + public static void Install() + { + // always - don't read config in PreApplicationStartMethod + HttpApplication.RegisterModule(typeof(LiveModelsProviderModule)); + } + } +} diff --git a/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderBackOfficeController.cs b/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderBackOfficeController.cs new file mode 100644 index 0000000000..b1e6fbb1fb --- /dev/null +++ b/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderBackOfficeController.cs @@ -0,0 +1,184 @@ +using System; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Runtime.Serialization; +using System.Text; +using System.Web.Hosting; +using Umbraco.ModelsBuilder.Building; +using Umbraco.ModelsBuilder.Configuration; +using Umbraco.ModelsBuilder.Dashboard; +using Umbraco.Web.Editors; +using Umbraco.Web.WebApi.Filters; + +namespace Umbraco.ModelsBuilder.Umbraco +{ + /// + /// API controller for use in the Umbraco back office with Angular resources + /// + /// + /// We've created a different controller for the backoffice/angular specifically this is to ensure that the + /// correct CSRF security is adhered to for angular and it also ensures that this controller is not subseptipal to + /// global WebApi formatters being changed since this is always forced to only return Angular JSON Specific formats. + /// + [UmbracoApplicationAuthorize(Core.Constants.Applications.Settings)] + public class ModelsBuilderBackOfficeController : UmbracoAuthorizedJsonController + { + private readonly UmbracoServices _umbracoServices; + private readonly Config _config; + + public ModelsBuilderBackOfficeController(UmbracoServices umbracoServices, Config config) + { + _umbracoServices = umbracoServices; + _config = config; + } + + // invoked by the dashboard + // requires that the user is logged into the backoffice and has access to the settings section + // beware! the name of the method appears in modelsbuilder.controller.js + [System.Web.Http.HttpPost] // use the http one, not mvc, with api controllers! + public HttpResponseMessage BuildModels() + { + try + { + var config = _config; + + if (!config.ModelsMode.SupportsExplicitGeneration()) + { + var result2 = new BuildResult { Success = false, Message = "Models generation is not enabled." }; + return Request.CreateResponse(HttpStatusCode.OK, result2, Configuration.Formatters.JsonFormatter); + } + + var modelsDirectory = config.ModelsDirectory; + + var bin = HostingEnvironment.MapPath("~/bin"); + if (bin == null) + throw new Exception("Panic: bin is null."); + + // EnableDllModels will recycle the app domain - but this request will end properly + GenerateModels(modelsDirectory); + + ModelsGenerationError.Clear(); + } + catch (Exception e) + { + ModelsGenerationError.Report("Failed to build models.", e); + } + + return Request.CreateResponse(HttpStatusCode.OK, GetDashboardResult(), Configuration.Formatters.JsonFormatter); + } + + // invoked by the back-office + // requires that the user is logged into the backoffice and has access to the settings section + [System.Web.Http.HttpGet] // use the http one, not mvc, with api controllers! + public HttpResponseMessage GetModelsOutOfDateStatus() + { + var status = OutOfDateModelsStatus.IsEnabled + ? (OutOfDateModelsStatus.IsOutOfDate + ? new OutOfDateStatus { Status = OutOfDateType.OutOfDate } + : new OutOfDateStatus { Status = OutOfDateType.Current }) + : new OutOfDateStatus { Status = OutOfDateType.Unknown }; + + return Request.CreateResponse(HttpStatusCode.OK, status, Configuration.Formatters.JsonFormatter); + } + + // invoked by the back-office + // requires that the user is logged into the backoffice and has access to the settings section + // beware! the name of the method appears in modelsbuilder.controller.js + [System.Web.Http.HttpGet] // use the http one, not mvc, with api controllers! + public HttpResponseMessage GetDashboard() + { + return Request.CreateResponse(HttpStatusCode.OK, GetDashboardResult(), Configuration.Formatters.JsonFormatter); + } + + private Dashboard GetDashboardResult() + { + return new Dashboard + { + Enable = _config.Enable, + Text = BuilderDashboardHelper.Text(), + CanGenerate = BuilderDashboardHelper.CanGenerate(), + OutOfDateModels = BuilderDashboardHelper.AreModelsOutOfDate(), + LastError = BuilderDashboardHelper.LastError(), + }; + } + + private void GenerateModels(string modelsDirectory) + { + GenerateModels(_umbracoServices, modelsDirectory, _config.ModelsNamespace); + } + + internal static void GenerateModels(UmbracoServices umbracoServices, string modelsDirectory, string modelsNamespace) + { + if (!Directory.Exists(modelsDirectory)) + Directory.CreateDirectory(modelsDirectory); + + foreach (var file in Directory.GetFiles(modelsDirectory, "*.generated.cs")) + File.Delete(file); + + var typeModels = umbracoServices.GetAllTypes(); + + var builder = new TextBuilder(typeModels, modelsNamespace); + + foreach (var typeModel in builder.GetModelsToGenerate()) + { + var sb = new StringBuilder(); + builder.Generate(sb, typeModel); + var filename = Path.Combine(modelsDirectory, typeModel.ClrName + ".generated.cs"); + File.WriteAllText(filename, sb.ToString()); + } + + // the idea was to calculate the current hash and to add it as an extra file to the compilation, + // in order to be able to detect whether a DLL is consistent with an environment - however the + // environment *might not* contain the local partial files, and thus it could be impossible to + // calculate the hash. So... maybe that's not a good idea after all? + /* + var currentHash = HashHelper.Hash(ourFiles, typeModels); + ourFiles["models.hash.cs"] = $@"using Umbraco.ModelsBuilder; +[assembly:ModelsBuilderAssembly(SourceHash = ""{currentHash}"")] +"; + */ + + OutOfDateModelsStatus.Clear(); + } + + [DataContract] + internal class BuildResult + { + [DataMember(Name = "success")] + public bool Success; + [DataMember(Name = "message")] + public string Message; + } + + [DataContract] + internal class Dashboard + { + [DataMember(Name = "enable")] + public bool Enable; + [DataMember(Name = "text")] + public string Text; + [DataMember(Name = "canGenerate")] + public bool CanGenerate; + [DataMember(Name = "outOfDateModels")] + public bool OutOfDateModels; + [DataMember(Name = "lastError")] + public string LastError; + } + + internal enum OutOfDateType + { + OutOfDate, + Current, + Unknown = 100 + } + + [DataContract] + internal class OutOfDateStatus + { + [DataMember(Name = "status")] + public OutOfDateType Status { get; set; } + } + } +} diff --git a/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderComponent.cs b/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderComponent.cs new file mode 100644 index 0000000000..70d55368ac --- /dev/null +++ b/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderComponent.cs @@ -0,0 +1,186 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Web; +using System.Web.Mvc; +using System.Web.Routing; +using Umbraco.Core.Composing; +using Umbraco.Core.IO; +using Umbraco.Core.Services; +using Umbraco.Core.Services.Implement; +using Umbraco.ModelsBuilder.Configuration; +using Umbraco.Web; +using Umbraco.Web.JavaScript; +using Umbraco.Web.Mvc; + +namespace Umbraco.ModelsBuilder.Umbraco +{ + public class ModelsBuilderComponent : IComponent + { + private readonly UmbracoServices _umbracoServices; + + private readonly Config _config; + + public ModelsBuilderComponent(UmbracoServices umbracoServices, Config config) + { + _umbracoServices = umbracoServices; + _config = config; + } + + public void Initialize() + { + // always setup the dashboard + // note: UmbracoApiController instances are automatically registered + InstallServerVars(); + + ContentModelBinder.ModelBindingException += ContentModelBinder_ModelBindingException; + + if (_config.Enable) + FileService.SavingTemplate += FileService_SavingTemplate; + + // fixme LiveModelsProvider should not be static + if (_config.ModelsMode.IsLiveNotPure()) + LiveModelsProvider.Install(_umbracoServices); + + // fixme OutOfDateModelsStatus should not be static + if (_config.FlagOutOfDateModels) + OutOfDateModelsStatus.Install(); + } + + public void Terminate() + { } + + private void InstallServerVars() + { + // register our url - for the backoffice api + ServerVariablesParser.Parsing += (sender, serverVars) => + { + if (!serverVars.ContainsKey("umbracoUrls")) + throw new Exception("Missing umbracoUrls."); + var umbracoUrlsObject = serverVars["umbracoUrls"]; + if (umbracoUrlsObject == null) + throw new Exception("Null umbracoUrls"); + if (!(umbracoUrlsObject is Dictionary umbracoUrls)) + throw new Exception("Invalid umbracoUrls"); + + if (!serverVars.ContainsKey("umbracoPlugins")) + throw new Exception("Missing umbracoPlugins."); + if (!(serverVars["umbracoPlugins"] is Dictionary umbracoPlugins)) + throw new Exception("Invalid umbracoPlugins"); + + if (HttpContext.Current == null) throw new InvalidOperationException("HttpContext is null"); + var urlHelper = new UrlHelper(new RequestContext(new HttpContextWrapper(HttpContext.Current), new RouteData())); + + umbracoUrls["modelsBuilderBaseUrl"] = urlHelper.GetUmbracoApiServiceBaseUrl(controller => controller.BuildModels()); + umbracoPlugins["modelsBuilder"] = GetModelsBuilderSettings(); + }; + } + + private Dictionary GetModelsBuilderSettings() + { + var settings = new Dictionary + { + {"enabled", _config.Enable} + }; + + return settings; + } + + /// + /// Used to check if a template is being created based on a document type, in this case we need to + /// ensure the template markup is correct based on the model name of the document type + /// + /// + /// + private void FileService_SavingTemplate(IFileService sender, Core.Events.SaveEventArgs e) + { + // don't do anything if the factory is not enabled + // because, no factory = no models (even if generation is enabled) + if (!_config.EnableFactory) return; + + // don't do anything if this special key is not found + if (!e.AdditionalData.ContainsKey("CreateTemplateForContentType")) return; + + // ensure we have the content type alias + if (!e.AdditionalData.ContainsKey("ContentTypeAlias")) + throw new InvalidOperationException("The additionalData key: ContentTypeAlias was not found"); + + foreach (var template in e.SavedEntities) + { + // if it is in fact a new entity (not been saved yet) and the "CreateTemplateForContentType" key + // is found, then it means a new template is being created based on the creation of a document type + if (!template.HasIdentity && string.IsNullOrWhiteSpace(template.Content)) + { + // ensure is safe and always pascal cased, per razor standard + // + this is how we get the default model name in Umbraco.ModelsBuilder.Umbraco.Application + var alias = e.AdditionalData["ContentTypeAlias"].ToString(); + var name = template.Name; // will be the name of the content type since we are creating + var className = UmbracoServices.GetClrName(name, alias); + + var modelNamespace = _config.ModelsNamespace; + + // we do not support configuring this at the moment, so just let Umbraco use its default value + //var modelNamespaceAlias = ...; + + var markup = ViewHelper.GetDefaultFileContent( + modelClassName: className, + modelNamespace: modelNamespace/*, + modelNamespaceAlias: modelNamespaceAlias*/); + + //set the template content to the new markup + template.Content = markup; + } + } + } + + private void ContentModelBinder_ModelBindingException(object sender, ContentModelBinder.ModelBindingArgs args) + { + var sourceAttr = args.SourceType.Assembly.GetCustomAttribute(); + var modelAttr = args.ModelType.Assembly.GetCustomAttribute(); + + // if source or model is not a ModelsBuider type... + if (sourceAttr == null || modelAttr == null) + { + // if neither are ModelsBuilder types, give up entirely + if (sourceAttr == null && modelAttr == null) + return; + + // else report, but better not restart (loops?) + args.Message.Append(" The "); + args.Message.Append(sourceAttr == null ? "view model" : "source"); + args.Message.Append(" is a ModelsBuilder type, but the "); + args.Message.Append(sourceAttr != null ? "view model" : "source"); + args.Message.Append(" is not. The application is in an unstable state and should be restarted."); + return; + } + + // both are ModelsBuilder types + var pureSource = sourceAttr.PureLive; + var pureModel = modelAttr.PureLive; + + if (sourceAttr.PureLive || modelAttr.PureLive) + { + if (pureSource == false || pureModel == false) + { + // only one is pure - report, but better not restart (loops?) + args.Message.Append(pureSource + ? " The content model is PureLive, but the view model is not." + : " The view model is PureLive, but the content model is not."); + args.Message.Append(" The application is in an unstable state and should be restarted."); + } + else + { + // both are pure - report, and if different versions, restart + // if same version... makes no sense... and better not restart (loops?) + var sourceVersion = args.SourceType.Assembly.GetName().Version; + var modelVersion = args.ModelType.Assembly.GetName().Version; + args.Message.Append(" Both view and content models are PureLive, with "); + args.Message.Append(sourceVersion == modelVersion + ? "same version. The application is in an unstable state and should be restarted." + : "different versions. The application is in an unstable state and is going to be restarted."); + args.Restart = sourceVersion != modelVersion; + } + } + } + } +} diff --git a/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderComposer.cs b/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderComposer.cs new file mode 100644 index 0000000000..3dae2f86ab --- /dev/null +++ b/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderComposer.cs @@ -0,0 +1,60 @@ +using System.Linq; +using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.ModelsBuilder.Configuration; +using Umbraco.Web.PublishedCache.NuCache; + +namespace Umbraco.ModelsBuilder.Umbraco +{ + [ComposeBefore(typeof(NuCacheComposer))] + [RuntimeLevel(MinLevel = RuntimeLevel.Run)] + public sealed class ModelsBuilderComposer : ComponentComposer, ICoreComposer + { + public override void Compose(Composition composition) + { + base.Compose(composition); + + composition.Register(Lifetime.Singleton); + composition.Configs.Add(() => new Config()); + + if (composition.Configs.ModelsBuilder().ModelsMode == ModelsMode.PureLive) + ComposeForLiveModels(composition); + else if (composition.Configs.ModelsBuilder().EnableFactory) + ComposeForDefaultModelsFactory(composition); + } + + private void ComposeForDefaultModelsFactory(Composition composition) + { + composition.RegisterUnique(factory => + { + var typeLoader = factory.GetInstance(); + var types = typeLoader + .GetTypes() // element models + .Concat(typeLoader.GetTypes()); // content models + return new PublishedModelFactory(types); + }); + } + + private void ComposeForLiveModels(Composition composition) + { + composition.RegisterUnique(); + + // the following would add @using statement in every view so user's don't + // have to do it - however, then noone understands where the @using statement + // comes from, and it cannot be avoided / removed --- DISABLED + // + /* + // no need for @using in views + // note: + // we are NOT using the in-code attribute here, config is required + // because that would require parsing the code... and what if it changes? + // we can AddGlobalImport not sure we can remove one anyways + var modelsNamespace = Configuration.Config.ModelsNamespace; + if (string.IsNullOrWhiteSpace(modelsNamespace)) + modelsNamespace = Configuration.Config.DefaultModelsNamespace; + System.Web.WebPages.Razor.WebPageRazorHost.AddGlobalImport(modelsNamespace); + */ + } + } +} diff --git a/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderInitializer.cs b/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderInitializer.cs new file mode 100644 index 0000000000..9bd662da37 --- /dev/null +++ b/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderInitializer.cs @@ -0,0 +1,28 @@ +using System.Web; +using System.Web.Compilation; +using Umbraco.ModelsBuilder.Umbraco; + +[assembly: PreApplicationStartMethod(typeof(ModelsBuilderInitializer), "Initialize")] + +namespace Umbraco.ModelsBuilder.Umbraco +{ + public static class ModelsBuilderInitializer + { + public static void Initialize() + { + // for some reason, netstandard is missing from BuildManager.ReferencedAssemblies and yet, is part of + // the references that CSharpCompiler receives - in some cases eg when building views - but not when + // using BuildManager to build the PureLive models - where is it coming from? cannot figure it out + + // so... cheating here + + // this is equivalent to adding + // + // to web.config system.web/compilation/assemblies + + var netStandard = ReferencedAssemblies.GetNetStandardAssembly(); + if (netStandard != null) + BuildManager.AddReferencedAssembly(netStandard); + } + } +} diff --git a/src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerationError.cs b/src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerationError.cs new file mode 100644 index 0000000000..a7b437df57 --- /dev/null +++ b/src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerationError.cs @@ -0,0 +1,62 @@ +using System; +using System.IO; +using System.Text; +using Umbraco.Core.Composing; +using Umbraco.ModelsBuilder.Configuration; + +namespace Umbraco.ModelsBuilder.Umbraco +{ + internal static class ModelsGenerationError + { + private static Config Config => Current.Configs.ModelsBuilder(); + + public static void Clear() + { + var errFile = GetErrFile(); + if (errFile == null) return; + + // "If the file to be deleted does not exist, no exception is thrown." + File.Delete(errFile); + } + + public static void Report(string message, Exception e) + { + var errFile = GetErrFile(); + if (errFile == null) return; + + var sb = new StringBuilder(); + sb.Append(message); + sb.Append("\r\n"); + sb.Append(e.Message); + sb.Append("\r\n\r\n"); + sb.Append(e.StackTrace); + sb.Append("\r\n"); + + File.WriteAllText(errFile, sb.ToString()); + } + + public static string GetLastError() + { + var errFile = GetErrFile(); + if (errFile == null) return null; + + try + { + return File.ReadAllText(errFile); + } + catch // accepted + { + return null; + } + } + + private static string GetErrFile() + { + var modelsDirectory = Config.ModelsDirectory; + if (!Directory.Exists(modelsDirectory)) + return null; + + return Path.Combine(modelsDirectory, "models.err"); + } + } +} diff --git a/src/Umbraco.ModelsBuilder/Umbraco/OutOfDateModelsStatus.cs b/src/Umbraco.ModelsBuilder/Umbraco/OutOfDateModelsStatus.cs new file mode 100644 index 0000000000..142cc7578a --- /dev/null +++ b/src/Umbraco.ModelsBuilder/Umbraco/OutOfDateModelsStatus.cs @@ -0,0 +1,57 @@ +using System.IO; +using Umbraco.Core.Composing; +using Umbraco.ModelsBuilder.Configuration; +using Umbraco.Web.Cache; + +namespace Umbraco.ModelsBuilder.Umbraco +{ + public sealed class OutOfDateModelsStatus + { + private static Config Config => Current.Configs.ModelsBuilder(); + + internal static void Install() + { + // just be sure + if (Config.FlagOutOfDateModels == false) + return; + + ContentTypeCacheRefresher.CacheUpdated += (sender, args) => Write(); + DataTypeCacheRefresher.CacheUpdated += (sender, args) => Write(); + } + + private static string GetFlagPath() + { + var modelsDirectory = Config.ModelsDirectory; + if (!Directory.Exists(modelsDirectory)) + Directory.CreateDirectory(modelsDirectory); + return Path.Combine(modelsDirectory, "ood.flag"); + } + + private static void Write() + { + var path = GetFlagPath(); + if (path == null || File.Exists(path)) return; + File.WriteAllText(path, "THIS FILE INDICATES THAT MODELS ARE OUT-OF-DATE\n\n"); + } + + public static void Clear() + { + if (Config.FlagOutOfDateModels == false) return; + var path = GetFlagPath(); + if (path == null || !File.Exists(path)) return; + File.Delete(path); + } + + public static bool IsEnabled => Config.FlagOutOfDateModels; + + public static bool IsOutOfDate + { + get + { + if (Config.FlagOutOfDateModels == false) return false; + var path = GetFlagPath(); + return path != null && File.Exists(path); + } + } + } +} diff --git a/src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs b/src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs new file mode 100644 index 0000000000..c70e8a3b65 --- /dev/null +++ b/src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs @@ -0,0 +1,67 @@ +using System; +using System.Linq; +using System.Linq.Expressions; +using Umbraco.Web.Composing; +using Umbraco.Core.Models.PublishedContent; + +namespace Umbraco.ModelsBuilder.Umbraco +{ + public static class PublishedModelUtility + { + // looks safer but probably useless... ppl should not call these methods directly + // and if they do... they have to take care about not doing stupid things + + //public static PublishedPropertyType GetModelPropertyType2(Expression> selector) + // where T : PublishedContentModel + //{ + // var type = typeof (T); + // var s1 = type.GetField("ModelTypeAlias", BindingFlags.Public | BindingFlags.Static); + // var alias = (s1.IsLiteral && s1.IsInitOnly && s1.FieldType == typeof(string)) ? (string)s1.GetValue(null) : null; + // var s2 = type.GetField("ModelItemType", BindingFlags.Public | BindingFlags.Static); + // var itemType = (s2.IsLiteral && s2.IsInitOnly && s2.FieldType == typeof(PublishedItemType)) ? (PublishedItemType)s2.GetValue(null) : 0; + + // var contentType = PublishedContentType.Get(itemType, alias); + // // etc... + //} + + public static PublishedContentType GetModelContentType(PublishedItemType itemType, string alias) + { + var facade = Current.UmbracoContext.PublishedSnapshot; // fixme inject! + switch (itemType) + { + case PublishedItemType.Content: + return facade.Content.GetContentType(alias); + case PublishedItemType.Media: + return facade.Media.GetContentType(alias); + case PublishedItemType.Member: + return facade.Members.GetContentType(alias); + default: + throw new ArgumentOutOfRangeException(nameof(itemType)); + } + } + + public static PublishedPropertyType GetModelPropertyType(PublishedContentType contentType, Expression> selector) + //where TModel : PublishedContentModel // fixme PublishedContentModel _or_ PublishedElementModel + { + // fixme therefore, missing a check on TModel here + + var expr = selector.Body as MemberExpression; + + if (expr == null) + throw new ArgumentException("Not a property expression.", nameof(selector)); + + // there _is_ a risk that contentType and T do not match + // see note above : accepted risk... + + var attr = expr.Member + .GetCustomAttributes(typeof (ImplementPropertyTypeAttribute), false) + .OfType() + .SingleOrDefault(); + + if (string.IsNullOrWhiteSpace(attr?.Alias)) + throw new InvalidOperationException($"Could not figure out property alias for property \"{expr.Member.Name}\"."); + + return contentType.GetPropertyType(attr.Alias); + } + } +} diff --git a/src/Umbraco.ModelsBuilder/Umbraco/PureLiveModelFactory.cs b/src/Umbraco.ModelsBuilder/Umbraco/PureLiveModelFactory.cs new file mode 100644 index 0000000000..05f748a588 --- /dev/null +++ b/src/Umbraco.ModelsBuilder/Umbraco/PureLiveModelFactory.cs @@ -0,0 +1,677 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Web; +using System.Web.Compilation; +using System.Web.Hosting; +using System.Web.WebPages.Razor; +using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.Core.Logging; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Web.Cache; +using Umbraco.ModelsBuilder.Building; +using Umbraco.ModelsBuilder.Configuration; +using File = System.IO.File; + +namespace Umbraco.ModelsBuilder.Umbraco +{ + internal class PureLiveModelFactory : ILivePublishedModelFactory, IRegisteredObject + { + private Assembly _modelsAssembly; + private Infos _infos = new Infos { ModelInfos = null, ModelTypeMap = new Dictionary() }; + private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(); + private volatile bool _hasModels; // volatile 'cos reading outside lock + private bool _pendingRebuild; + private readonly IProfilingLogger _logger; + private readonly FileSystemWatcher _watcher; + private int _ver, _skipver; + private readonly int _debugLevel; + private BuildManager _theBuildManager; + private readonly Lazy _umbracoServices; + private UmbracoServices UmbracoServices => _umbracoServices.Value; + + private static readonly Regex AssemblyVersionRegex = new Regex("AssemblyVersion\\(\"[0-9]+.[0-9]+.[0-9]+.[0-9]+\"\\)", RegexOptions.Compiled); + private const string ProjVirt = "~/App_Data/Models/all.generated.cs"; + private static readonly string[] OurFiles = { "models.hash", "models.generated.cs", "all.generated.cs", "all.dll.path", "models.err" }; + + private readonly Config _config; + + public PureLiveModelFactory(Lazy umbracoServices, IProfilingLogger logger, Config config) + { + _umbracoServices = umbracoServices; + _logger = logger; + _config = config; + _ver = 1; // zero is for when we had no version + _skipver = -1; // nothing to skip + + RazorBuildProvider.CodeGenerationStarted += RazorBuildProvider_CodeGenerationStarted; + + if (!HostingEnvironment.IsHosted) return; + + var modelsDirectory = _config.ModelsDirectory; + if (!Directory.Exists(modelsDirectory)) + Directory.CreateDirectory(modelsDirectory); + + // BEWARE! if the watcher is not properly released then for some reason the + // BuildManager will start confusing types - using a 'registered object' here + // though we should probably plug into Umbraco's MainDom - which is internal + HostingEnvironment.RegisterObject(this); + _watcher = new FileSystemWatcher(modelsDirectory); + _watcher.Changed += WatcherOnChanged; + _watcher.EnableRaisingEvents = true; + + // get it here, this need to be fast + _debugLevel = _config.DebugLevel; + } + + #region ILivePublishedModelFactory + + /// + public object SyncRoot { get; } = new object(); + + /// + public void Refresh() + { + ResetModels(); + EnsureModels(); + } + + #endregion + + #region IPublishedModelFactory + + public IPublishedElement CreateModel(IPublishedElement element) + { + // get models, rebuilding them if needed + var infos = EnsureModels()?.ModelInfos; + if (infos == null) + return element; + + // be case-insensitive + var contentTypeAlias = element.ContentType.Alias; + + // lookup model constructor (else null) + infos.TryGetValue(contentTypeAlias, out ModelInfo info); + + // create model + return info == null ? element : info.Ctor(element); + } + + // this runs only once the factory is ready + // NOT when building models + public Type MapModelType(Type type) + { + var infos = EnsureModels(); + return ModelType.Map(type, infos.ModelTypeMap); + } + + // this runs only once the factory is ready + // NOT when building models + public IList CreateModelList(string alias) + { + var infos = EnsureModels(); + + // fail fast + if (infos == null) + return new List(); + + if (!infos.ModelInfos.TryGetValue(alias, out var modelInfo)) + return new List(); + + var ctor = modelInfo.ListCtor; + if (ctor != null) return ctor(); + + var listType = typeof(List<>).MakeGenericType(modelInfo.ModelType); + ctor = modelInfo.ListCtor = ReflectionUtilities.EmitConstructor>(declaring: listType); + return ctor(); + } + + #endregion + + #region Compilation + + // deadlock note + // + // when RazorBuildProvider_CodeGenerationStarted runs, the thread has Monitor.Enter-ed the BuildManager + // singleton instance, through a call to CompilationLock.GetLock in BuildManager.GetVPathBuildResultInternal, + // and now wants to lock _locker. + // when EnsureModels runs, the thread locks _locker and then wants BuildManager to compile, which in turns + // requires that the BuildManager can Monitor.Enter-ed itself. + // so: + // + // T1 - needs to ensure models, locks _locker + // T2 - needs to compile a view, locks BuildManager + // hits RazorBuildProvider_CodeGenerationStarted + // wants to lock _locker, wait + // T1 - needs to compile models, using BuildManager + // wants to lock itself, wait + // + // + // until ASP.NET kills the long-running request (thread abort) + // + // problem is, we *want* to suspend views compilation while the models assembly is being changed else we + // end up with views compiled and cached with the old assembly, while models come from the new assembly, + // which gives more YSOD. so we *have* to lock _locker in RazorBuildProvider_CodeGenerationStarted. + // + // one "easy" solution consists in locking the BuildManager *before* _locker in EnsureModels, thus ensuring + // we always lock in the same order, and getting rid of deadlocks - but that requires having access to the + // current BuildManager instance, which is BuildManager.TheBuildManager, which is an internal property. + // + // well, that's what we are doing in this class' TheBuildManager property, using reflection. + + private void RazorBuildProvider_CodeGenerationStarted(object sender, EventArgs e) + { + try + { + _locker.EnterReadLock(); + + // just be safe - can happen if the first view is not an Umbraco view, + // or if something went wrong and we don't have an assembly at all + if (_modelsAssembly == null) return; + + if (_debugLevel > 0) + _logger.Debug("RazorBuildProvider.CodeGenerationStarted"); + if (!(sender is RazorBuildProvider provider)) return; + + // add the assembly, and add a dependency to a text file that will change on each + // compilation as in some environments (could not figure which/why) the BuildManager + // would not re-compile the views when the models assembly is rebuilt. + provider.AssemblyBuilder.AddAssemblyReference(_modelsAssembly); + provider.AddVirtualPathDependency(ProjVirt); + } + finally + { + if (_locker.IsReadLockHeld) + _locker.ExitReadLock(); + } + } + + // tells the factory that it should build a new generation of models + private void ResetModels() + { + _logger.Debug("Resetting models."); + + try + { + _locker.EnterWriteLock(); + + _hasModels = false; + _pendingRebuild = true; + + var modelsDirectory = _config.ModelsDirectory; + if (!Directory.Exists(modelsDirectory)) + Directory.CreateDirectory(modelsDirectory); + + // clear stuff + var modelsHashFile = Path.Combine(modelsDirectory, "models.hash"); + //var modelsSrcFile = Path.Combine(modelsDirectory, "models.generated.cs"); + //var projFile = Path.Combine(modelsDirectory, "all.generated.cs"); + var dllPathFile = Path.Combine(modelsDirectory, "all.dll.path"); + + if (File.Exists(dllPathFile)) File.Delete(dllPathFile); + if (File.Exists(modelsHashFile)) File.Delete(modelsHashFile); + } + finally + { + if (_locker.IsWriteLockHeld) + _locker.ExitWriteLock(); + } + } + + // gets "the" build manager + private BuildManager TheBuildManager + { + get + { + if (_theBuildManager != null) return _theBuildManager; + var prop = typeof (BuildManager).GetProperty("TheBuildManager", BindingFlags.NonPublic | BindingFlags.Static); + if (prop == null) + throw new InvalidOperationException("Could not get BuildManager.TheBuildManager property."); + _theBuildManager = (BuildManager) prop.GetValue(null); + return _theBuildManager; + } + } + + // ensure that the factory is running with the lastest generation of models + internal Infos EnsureModels() + { + if (_debugLevel > 0) + _logger.Debug("Ensuring models."); + + // don't use an upgradeable lock here because only 1 thread at a time could enter it + try + { + _locker.EnterReadLock(); + if (_hasModels) + return _infos; + } + finally + { + if (_locker.IsReadLockHeld) + _locker.ExitReadLock(); + } + + var buildManagerLocked = false; + try + { + // always take the BuildManager lock *before* taking the _locker lock + // to avoid possible deadlock situations (see notes above) + Monitor.Enter(TheBuildManager, ref buildManagerLocked); + + _locker.EnterUpgradeableReadLock(); + + if (_hasModels) return _infos; + + _locker.EnterWriteLock(); + + // we don't have models, + // either they haven't been loaded from the cache yet + // or they have been reseted and are pending a rebuild + + using (_logger.DebugDuration("Get models.", "Got models.")) + { + try + { + var assembly = GetModelsAssembly(_pendingRebuild); + + // the one below can be used to simulate an issue with BuildManager, ie it will register + // the models with the factory but NOT with the BuildManager, which will not recompile views. + // this is for U4-8043 which is an obvious issue but I cannot replicate + //_modelsAssembly = _modelsAssembly ?? assembly; + + // the one below is the normal one + _modelsAssembly = assembly; + + var types = assembly.ExportedTypes.Where(x => x.Inherits() || x.Inherits()); + _infos = RegisterModels(types); + ModelsGenerationError.Clear(); + } + catch (Exception e) + { + try + { + _logger.Error("Failed to build models.", e); + _logger.Warn("Running without models."); // be explicit + ModelsGenerationError.Report("Failed to build PureLive models.", e); + } + finally + { + _modelsAssembly = null; + _infos = new Infos { ModelInfos = null, ModelTypeMap = new Dictionary() }; + } + } + + // don't even try again + _hasModels = true; + } + + return _infos; + } + finally + { + if (_locker.IsWriteLockHeld) + _locker.ExitWriteLock(); + if (_locker.IsUpgradeableReadLockHeld) + _locker.ExitUpgradeableReadLock(); + if (buildManagerLocked) + Monitor.Exit(TheBuildManager); + } + } + + private Assembly GetModelsAssembly(bool forceRebuild) + { + var modelsDirectory = _config.ModelsDirectory; + if (!Directory.Exists(modelsDirectory)) + Directory.CreateDirectory(modelsDirectory); + + var typeModels = UmbracoServices.GetAllTypes(); + var currentHash = HashHelper.Hash(typeModels); + var modelsHashFile = Path.Combine(modelsDirectory, "models.hash"); + var modelsSrcFile = Path.Combine(modelsDirectory, "models.generated.cs"); + var projFile = Path.Combine(modelsDirectory, "all.generated.cs"); + var dllPathFile = Path.Combine(modelsDirectory, "all.dll.path"); + + // caching the generated models speeds up booting + // currentHash hashes both the types & the user's partials + + if (!forceRebuild) + { + _logger.Debug("Looking for cached models."); + if (File.Exists(modelsHashFile) && File.Exists(projFile)) + { + var cachedHash = File.ReadAllText(modelsHashFile); + if (currentHash != cachedHash) + { + _logger.Debug("Found obsolete cached models."); + forceRebuild = true; + } + + // else cachedHash matches currentHash, we can try to load an existing dll + } + else + { + _logger.Debug("Could not find cached models."); + forceRebuild = true; + } + } + + Assembly assembly; + if (!forceRebuild) + { + // try to load the dll directly (avoid rebuilding) + // + // ensure that the .dll file does not have a corresponding .dll.delete file + // as that would mean the the .dll file is going to be deleted and should not + // be re-used - that should not happen in theory, but better be safe + // + // ensure that the .dll file is in the current codegen directory - when IIS + // or Express does a full restart, it can switch to an entirely new codegen + // directory, and then we end up referencing a dll which is *not* in that + // directory, and BuildManager fails to instantiate views ("the view found + // at ... was not created"). + // + if (File.Exists(dllPathFile)) + { + var dllPath = File.ReadAllText(dllPathFile); + var codegen = HttpRuntime.CodegenDir; + + _logger.Debug($"Cached models dll at {dllPath}."); + + if (File.Exists(dllPath) && !File.Exists(dllPath + ".delete") && dllPath.StartsWith(codegen)) + { + assembly = Assembly.LoadFile(dllPath); + var attr = assembly.GetCustomAttribute(); + if (attr != null && attr.PureLive && attr.SourceHash == currentHash) + { + // if we were to resume at that revision, then _ver would keep increasing + // and that is probably a bad idea - so, we'll always rebuild starting at + // ver 1, but we remember we want to skip that one - so we never end up + // with the "same but different" version of the assembly in memory + _skipver = assembly.GetName().Version.Revision; + + _logger.Debug("Loading cached models (dll)."); + return assembly; + } + + _logger.Debug("Cached models dll cannot be loaded (invalid assembly)."); + } + else if (!File.Exists(dllPath)) + _logger.Debug("Cached models dll does not exist."); + else if (File.Exists(dllPath + ".delete")) + _logger.Debug("Cached models dll is marked for deletion."); + else if (!dllPath.StartsWith(codegen)) + _logger.Debug("Cached models dll is in a different codegen directory."); + else + _logger.Debug("Cached models dll cannot be loaded (why?)."); + } + + // must reset the version in the file else it would keep growing + // loading cached modules only happens when the app restarts + var text = File.ReadAllText(projFile); + var match = AssemblyVersionRegex.Match(text); + if (match.Success) + { + text = text.Replace(match.Value, "AssemblyVersion(\"0.0.0." + _ver + "\")"); + File.WriteAllText(projFile, text); + } + + // generate a marker file that will be a dependency + // see note in RazorBuildProvider_CodeGenerationStarted + // NO: using all.generated.cs as a dependency + //File.WriteAllText(Path.Combine(modelsDirectory, "models.dep"), "VER:" + _ver); + + _ver++; + try + { + assembly = BuildManager.GetCompiledAssembly(ProjVirt); + File.WriteAllText(dllPathFile, assembly.Location); + } + catch + { + ClearOnFailingToCompile(dllPathFile, modelsHashFile, projFile); + throw; + } + + _logger.Debug("Loading cached models (source)."); + return assembly; + } + + // need to rebuild + _logger.Debug("Rebuilding models."); + + // generate code, save + var code = GenerateModelsCode(typeModels); + // add extra attributes, + // PureLiveAssembly helps identifying Assemblies that contain PureLive models + // AssemblyVersion is so that we have a different version for each rebuild + var ver = _ver == _skipver ? ++_ver : _ver; + _ver++; + code = code.Replace("//ASSATTR", $@"[assembly: PureLiveAssembly] +[assembly:ModelsBuilderAssembly(PureLive = true, SourceHash = ""{currentHash}"")] +[assembly:System.Reflection.AssemblyVersion(""0.0.0.{ver}"")]"); + File.WriteAllText(modelsSrcFile, code); + + // generate proj, save + var projFiles = new Dictionary + { + { "models.generated.cs", code } + }; + var proj = GenerateModelsProj(projFiles); + File.WriteAllText(projFile, proj); + + // compile and register + try + { + assembly = BuildManager.GetCompiledAssembly(ProjVirt); + File.WriteAllText(dllPathFile, assembly.Location); + File.WriteAllText(modelsHashFile, currentHash); + } + catch + { + ClearOnFailingToCompile(dllPathFile, modelsHashFile, projFile); + throw; + } + + _logger.Debug("Done rebuilding."); + return assembly; + } + + private void ClearOnFailingToCompile(string dllPathFile, string modelsHashFile, string projFile) + { + _logger.Debug("Failed to compile."); + + // the dll file reference still points to the previous dll, which is obsolete + // now and will be deleted by ASP.NET eventually, so better clear that reference. + // also touch the proj file to force views to recompile - don't delete as it's + // useful to have the source around for debugging. + try + { + if (File.Exists(dllPathFile)) File.Delete(dllPathFile); + if (File.Exists(modelsHashFile)) File.Delete(modelsHashFile); + if (File.Exists(projFile)) File.SetLastWriteTime(projFile, DateTime.Now); + } + catch { /* enough */ } + } + + private static Infos RegisterModels(IEnumerable types) + { + var ctorArgTypes = new[] { typeof (IPublishedElement) }; + var modelInfos = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + var map = new Dictionary(); + + foreach (var type in types) + { + ConstructorInfo constructor = null; + Type parameterType = null; + + foreach (var ctor in type.GetConstructors()) + { + var parms = ctor.GetParameters(); + if (parms.Length == 1 && typeof (IPublishedElement).IsAssignableFrom(parms[0].ParameterType)) + { + if (constructor != null) + throw new InvalidOperationException($"Type {type.FullName} has more than one public constructor with one argument of type, or implementing, IPropertySet."); + constructor = ctor; + parameterType = parms[0].ParameterType; + } + } + + if (constructor == null) + throw new InvalidOperationException($"Type {type.FullName} is missing a public constructor with one argument of type, or implementing, IPropertySet."); + + var attribute = type.GetCustomAttribute(false); + var typeName = attribute == null ? type.Name : attribute.ContentTypeAlias; + + if (modelInfos.TryGetValue(typeName, out ModelInfo modelInfo)) + throw new InvalidOperationException($"Both types {type.FullName} and {modelInfo.ModelType.FullName} want to be a model type for content type with alias \"{typeName}\"."); + + // fixme use Core's ReflectionUtilities.EmitCtor !! + var meth = new DynamicMethod(string.Empty, typeof (IPublishedElement), ctorArgTypes, type.Module, true); + var gen = meth.GetILGenerator(); + gen.Emit(OpCodes.Ldarg_0); + gen.Emit(OpCodes.Newobj, constructor); + gen.Emit(OpCodes.Ret); + var func = (Func) meth.CreateDelegate(typeof (Func)); + + modelInfos[typeName] = new ModelInfo { ParameterType = parameterType, Ctor = func, ModelType = type }; + map[typeName] = type; + } + + return new Infos { ModelInfos = modelInfos.Count > 0 ? modelInfos : null, ModelTypeMap = map }; + } + + private string GenerateModelsCode(IList typeModels) + { + var modelsDirectory = _config.ModelsDirectory; + if (!Directory.Exists(modelsDirectory)) + Directory.CreateDirectory(modelsDirectory); + + foreach (var file in Directory.GetFiles(modelsDirectory, "*.generated.cs")) + File.Delete(file); + + var builder = new TextBuilder(typeModels, _config.ModelsNamespace); + + var codeBuilder = new StringBuilder(); + builder.Generate(codeBuilder, builder.GetModelsToGenerate()); + var code = codeBuilder.ToString(); + + return code; + } + + private static readonly Regex UsingRegex = new Regex("^using(.*);", RegexOptions.Compiled | RegexOptions.Multiline); + private static readonly Regex AattrRegex = new Regex("^\\[assembly:(.*)\\]", RegexOptions.Compiled | RegexOptions.Multiline); + + private static string GenerateModelsProj(IDictionary files) + { + // ideally we would generate a CSPROJ file but then we'd need a BuildProvider for csproj + // trying to keep things simple for the time being, just write everything to one big file + + // group all 'using' at the top of the file (else fails) + var usings = new List(); + foreach (var k in files.Keys.ToList()) + files[k] = UsingRegex.Replace(files[k], m => + { + usings.Add(m.Groups[1].Value); + return string.Empty; + }); + + // group all '[assembly:...]' at the top of the file (else fails) + var aattrs = new List(); + foreach (var k in files.Keys.ToList()) + files[k] = AattrRegex.Replace(files[k], m => + { + aattrs.Add(m.Groups[1].Value); + return string.Empty; + }); + + var text = new StringBuilder(); + foreach (var u in usings.Distinct()) + { + text.Append("using "); + text.Append(u); + text.Append(";\r\n"); + } + foreach (var a in aattrs) + { + text.Append("[assembly:"); + text.Append(a); + text.Append("]\r\n"); + } + text.Append("\r\n\r\n"); + foreach (var f in files) + { + text.Append("// FILE: "); + text.Append(f.Key); + text.Append("\r\n\r\n"); + text.Append(f.Value); + text.Append("\r\n\r\n\r\n"); + } + text.Append("// EOF\r\n"); + + return text.ToString(); + } + + internal class Infos + { + public Dictionary ModelTypeMap { get; set; } + public Dictionary ModelInfos { get; set; } + } + + internal class ModelInfo + { + public Type ParameterType { get; set; } + public Func Ctor { get; set; } + public Type ModelType { get; set; } + public Func ListCtor { get; set; } + } + + #endregion + + #region Watching + + private void WatcherOnChanged(object sender, FileSystemEventArgs args) + { + var changed = args.Name; + + // don't reset when our files change because we are building! + // + // comment it out, and always ignore our files, because it seems that some + // race conditions can occur on slow Cloud filesystems and then we keep + // rebuilding + + //if (_building && OurFiles.Contains(changed)) + //{ + // //_logger.Info("Ignoring files self-changes."); + // return; + //} + + // always ignore our own file changes + if (OurFiles.Contains(changed)) + return; + + _logger.Info("Detected files changes."); + + lock (SyncRoot) // don't reset while being locked + { + ResetModels(); + } + } + + public void Stop(bool immediate) + { + _watcher.EnableRaisingEvents = false; + _watcher.Dispose(); + HostingEnvironment.UnregisterObject(this); + } + + #endregion + } +} diff --git a/src/Umbraco.ModelsBuilder/Umbraco/UmbracoServices.cs b/src/Umbraco.ModelsBuilder/Umbraco/UmbracoServices.cs new file mode 100644 index 0000000000..67ef10be1a --- /dev/null +++ b/src/Umbraco.ModelsBuilder/Umbraco/UmbracoServices.cs @@ -0,0 +1,198 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Services; +using Umbraco.Core.Strings; +using Umbraco.ModelsBuilder.Building; +using Umbraco.ModelsBuilder.Configuration; + +namespace Umbraco.ModelsBuilder.Umbraco +{ + public class UmbracoServices + { + private readonly IContentTypeService _contentTypeService; + private readonly IMediaTypeService _mediaTypeService; + private readonly IMemberTypeService _memberTypeService; + private readonly IPublishedContentTypeFactory _publishedContentTypeFactory; + + public UmbracoServices(IContentTypeService contentTypeService, IMediaTypeService mediaTypeService, IMemberTypeService memberTypeService, IPublishedContentTypeFactory publishedContentTypeFactory) + { + _contentTypeService = contentTypeService; + _mediaTypeService = mediaTypeService; + _memberTypeService = memberTypeService; + _publishedContentTypeFactory = publishedContentTypeFactory; + } + + private static Config Config => Current.Configs.ModelsBuilder(); + + #region Services + + public IList GetAllTypes() + { + var types = new List(); + + types.AddRange(GetTypes(PublishedItemType.Content, _contentTypeService.GetAll().Cast().ToArray())); + types.AddRange(GetTypes(PublishedItemType.Media, _mediaTypeService.GetAll().Cast().ToArray())); + types.AddRange(GetTypes(PublishedItemType.Member, _memberTypeService.GetAll().Cast().ToArray())); + + return EnsureDistinctAliases(types); + } + + public IList GetContentTypes() + { + var contentTypes = _contentTypeService.GetAll().Cast().ToArray(); + return GetTypes(PublishedItemType.Content, contentTypes); // aliases have to be unique here + } + + public IList GetMediaTypes() + { + var contentTypes = _mediaTypeService.GetAll().Cast().ToArray(); + return GetTypes(PublishedItemType.Media, contentTypes); // aliases have to be unique here + } + + public IList GetMemberTypes() + { + var memberTypes = _memberTypeService.GetAll().Cast().ToArray(); + return GetTypes(PublishedItemType.Member, memberTypes); // aliases have to be unique here + } + + public static string GetClrName(string name, string alias) + { + // ModelsBuilder's legacy - but not ideal + return alias.ToCleanString(CleanStringType.ConvertCase | CleanStringType.PascalCase); + } + + private IList GetTypes(PublishedItemType itemType, IContentTypeComposition[] contentTypes) + { + var typeModels = new List(); + var uniqueTypes = new HashSet(); + + // get the types and the properties + foreach (var contentType in contentTypes) + { + var typeModel = new TypeModel + { + Id = contentType.Id, + Alias = contentType.Alias, + ClrName = GetClrName(contentType.Name, contentType.Alias), + ParentId = contentType.ParentId, + + Name = contentType.Name, + Description = contentType.Description + }; + + // of course this should never happen, but when it happens, better detect it + // else we end up with weird nullrefs everywhere + if (uniqueTypes.Contains(typeModel.ClrName)) + throw new Exception($"Panic: duplicate type ClrName \"{typeModel.ClrName}\"."); + uniqueTypes.Add(typeModel.ClrName); + + var publishedContentType = _publishedContentTypeFactory.CreateContentType(contentType); + switch (itemType) + { + case PublishedItemType.Content: + typeModel.ItemType = publishedContentType.ItemType == PublishedItemType.Element + ? TypeModel.ItemTypes.Element + : TypeModel.ItemTypes.Content; + break; + case PublishedItemType.Media: + typeModel.ItemType = publishedContentType.ItemType == PublishedItemType.Element + ? TypeModel.ItemTypes.Element + : TypeModel.ItemTypes.Media; + break; + case PublishedItemType.Member: + typeModel.ItemType = publishedContentType.ItemType == PublishedItemType.Element + ? TypeModel.ItemTypes.Element + : TypeModel.ItemTypes.Member; + break; + default: + throw new InvalidOperationException(string.Format("Unsupported PublishedItemType \"{0}\".", itemType)); + } + + typeModels.Add(typeModel); + + foreach (var propertyType in contentType.PropertyTypes) + { + var propertyModel = new PropertyModel + { + Alias = propertyType.Alias, + ClrName = GetClrName(propertyType.Name, propertyType.Alias), + + Name = propertyType.Name, + Description = propertyType.Description + }; + + var publishedPropertyType = publishedContentType.GetPropertyType(propertyType.Alias); + if (publishedPropertyType == null) + throw new Exception($"Panic: could not get published property type {contentType.Alias}.{propertyType.Alias}."); + + propertyModel.ModelClrType = publishedPropertyType.ModelClrType; + + typeModel.Properties.Add(propertyModel); + } + } + + // wire the base types + foreach (var typeModel in typeModels.Where(x => x.ParentId > 0)) + { + typeModel.BaseType = typeModels.SingleOrDefault(x => x.Id == typeModel.ParentId); + // Umbraco 7.4 introduces content types containers, so even though ParentId > 0, the parent might + // not be a content type - here we assume that BaseType being null while ParentId > 0 means that + // the parent is a container (and we don't check). + typeModel.IsParent = typeModel.BaseType != null; + } + + // discover mixins + foreach (var contentType in contentTypes) + { + var typeModel = typeModels.SingleOrDefault(x => x.Id == contentType.Id); + if (typeModel == null) throw new Exception("Panic: no type model matching content type."); + + IEnumerable compositionTypes; + var contentTypeAsMedia = contentType as IMediaType; + var contentTypeAsContent = contentType as IContentType; + var contentTypeAsMember = contentType as IMemberType; + if (contentTypeAsMedia != null) compositionTypes = contentTypeAsMedia.ContentTypeComposition; + else if (contentTypeAsContent != null) compositionTypes = contentTypeAsContent.ContentTypeComposition; + else if (contentTypeAsMember != null) compositionTypes = contentTypeAsMember.ContentTypeComposition; + else throw new Exception(string.Format("Panic: unsupported type \"{0}\".", contentType.GetType().FullName)); + + foreach (var compositionType in compositionTypes) + { + var compositionModel = typeModels.SingleOrDefault(x => x.Id == compositionType.Id); + if (compositionModel == null) throw new Exception("Panic: composition type does not exist."); + + if (compositionType.Id == contentType.ParentId) continue; + + // add to mixins + typeModel.MixinTypes.Add(compositionModel); + + // mark as mixin - as well as parents + compositionModel.IsMixin = true; + while ((compositionModel = compositionModel.BaseType) != null) + compositionModel.IsMixin = true; + } + } + + return typeModels; + } + + internal static IList EnsureDistinctAliases(IList typeModels) + { + var groups = typeModels.GroupBy(x => x.Alias.ToLowerInvariant()); + foreach (var group in groups.Where(x => x.Count() > 1)) + { + throw new NotSupportedException($"Alias \"{group.Key}\" is used by types" + + $" {string.Join(", ", group.Select(x => x.ItemType + ":\"" + x.Alias + "\""))}. Aliases have to be unique." + + " One of the aliases must be modified in order to use the ModelsBuilder."); + } + return typeModels; + } + + #endregion + } +} diff --git a/src/Umbraco.ModelsBuilder/Validation/ContentTypeModelValidator.cs b/src/Umbraco.ModelsBuilder/Validation/ContentTypeModelValidator.cs new file mode 100644 index 0000000000..3d2ce600c2 --- /dev/null +++ b/src/Umbraco.ModelsBuilder/Validation/ContentTypeModelValidator.cs @@ -0,0 +1,93 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.ModelsBuilder.Configuration; +using Umbraco.Web.Editors; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.ModelsBuilder.Validation +{ + /// + /// Used to validate the aliases for the content type when MB is enabled to ensure that + /// no illegal aliases are used + /// + internal class ContentTypeModelValidator : ContentTypeModelValidatorBase + { } + + /// + /// Used to validate the aliases for the content type when MB is enabled to ensure that + /// no illegal aliases are used + /// + internal class MediaTypeModelValidator : ContentTypeModelValidatorBase + { } + + /// + /// Used to validate the aliases for the content type when MB is enabled to ensure that + /// no illegal aliases are used + /// + internal class MemberTypeModelValidator : ContentTypeModelValidatorBase + { } + + internal abstract class ContentTypeModelValidatorBase : EditorValidator + where TModel: ContentTypeSave + where TProperty: PropertyTypeBasic + { + private static Config Config => Current.Configs.ModelsBuilder(); + + protected override IEnumerable Validate(TModel model) + { + //don't do anything if we're not enabled + if (Config.Enable) + { + var properties = model.Groups.SelectMany(x => x.Properties) + .Where(x => x.Inherited == false) + .ToArray(); + + foreach (var prop in properties) + { + var propertyGroup = model.Groups.Single(x => x.Properties.Contains(prop)); + + if (model.Alias.ToLowerInvariant() == prop.Alias.ToLowerInvariant()) + yield return new ValidationResult(string.Format("With Models Builder enabled, you can't have a property with a the alias \"{0}\" when the content type alias is also \"{0}\".", prop.Alias), new[] + { + string.Format("Groups[{0}].Properties[{1}].Alias", model.Groups.IndexOf(propertyGroup), propertyGroup.Properties.IndexOf(prop)) + }); + + //we need to return the field name with an index so it's wired up correctly + var groupIndex = model.Groups.IndexOf(propertyGroup); + var propertyIndex = propertyGroup.Properties.IndexOf(prop); + + var validationResult = ValidateProperty(prop, groupIndex, propertyIndex); + if (validationResult != null) + { + yield return validationResult; + } + } + } + } + + private ValidationResult ValidateProperty(PropertyTypeBasic property, int groupIndex, int propertyIndex) + { + //don't let them match any properties or methods in IPublishedContent + //TODO: There are probably more! + var reservedProperties = typeof(IPublishedContent).GetProperties().Select(x => x.Name).ToArray(); + var reservedMethods = typeof(IPublishedContent).GetMethods().Select(x => x.Name).ToArray(); + + var alias = property.Alias; + + if (reservedProperties.InvariantContains(alias) || reservedMethods.InvariantContains(alias)) + { + return new ValidationResult( + string.Format("The alias {0} is a reserved term and cannot be used", alias), new[] + { + string.Format("Groups[{0}].Properties[{1}].Alias", groupIndex, propertyIndex) + }); + } + + return null; + } + } +} diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 1a1946b01a..e52cb253cf 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -117,6 +117,10 @@ Umbraco.Examine {07FBC26B-2927-4A22-8D96-D644C667FECC} + + {52ac0ba8-a60e-4e36-897b-e8b97a54ed1c} + Umbraco.ModelsBuilder + {651e1350-91b6-44b7-bd60-7207006d7003} Umbraco.Web From f3643747b37335a7804cceb89a4407012a3a4915 Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 24 Jun 2019 14:33:15 +0200 Subject: [PATCH 006/548] Publicize IEditorValidator --- src/Umbraco.Web/Editors/EditorValidatorOfT.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web/Editors/EditorValidatorOfT.cs b/src/Umbraco.Web/Editors/EditorValidatorOfT.cs index 4ca008cf0d..715a49179e 100644 --- a/src/Umbraco.Web/Editors/EditorValidatorOfT.cs +++ b/src/Umbraco.Web/Editors/EditorValidatorOfT.cs @@ -8,7 +8,7 @@ namespace Umbraco.Web.Editors /// Provides a base class for implementations. /// /// The validated object type. - internal abstract class EditorValidator : IEditorValidator + public abstract class EditorValidator : IEditorValidator { public Type ModelType => typeof (T); @@ -16,4 +16,4 @@ namespace Umbraco.Web.Editors protected abstract IEnumerable Validate(T model); } -} \ No newline at end of file +} From 834aeee107ff48e7cdfcb5bb6edb778ab8711f4e Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 26 Jun 2019 22:38:32 +0200 Subject: [PATCH 007/548] Fix slimmed down ModelsBuilder --- build/NuSpecs/UmbracoCms.Web.nuspec | 3 +++ build/NuSpecs/UmbracoCms.nuspec | 1 - src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/build/NuSpecs/UmbracoCms.Web.nuspec b/build/NuSpecs/UmbracoCms.Web.nuspec index 614a816f3f..89ff42f853 100644 --- a/build/NuSpecs/UmbracoCms.Web.nuspec +++ b/build/NuSpecs/UmbracoCms.Web.nuspec @@ -51,14 +51,17 @@ + + + diff --git a/build/NuSpecs/UmbracoCms.nuspec b/build/NuSpecs/UmbracoCms.nuspec index 0d18ac59fa..62fd620faa 100644 --- a/build/NuSpecs/UmbracoCms.nuspec +++ b/build/NuSpecs/UmbracoCms.nuspec @@ -25,7 +25,6 @@ not want this to happen as the alpha of the next major is, really, the next major already. --> - diff --git a/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj b/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj index 5b0272a526..207d822fbb 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj +++ b/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj @@ -29,6 +29,7 @@ TRACE prompt 4 + bin\Release\Umbraco.ModelsBuilder.xml From b3867d9d550b07f350c02b202730c72ade1195a6 Mon Sep 17 00:00:00 2001 From: Stephan Date: Thu, 27 Jun 2019 09:16:21 +0200 Subject: [PATCH 008/548] Post-merge fixes --- src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs b/src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs index c70e8a3b65..7bbae927d5 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs +++ b/src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs @@ -24,7 +24,7 @@ namespace Umbraco.ModelsBuilder.Umbraco // // etc... //} - public static PublishedContentType GetModelContentType(PublishedItemType itemType, string alias) + public static IPublishedContentType GetModelContentType(PublishedItemType itemType, string alias) { var facade = Current.UmbracoContext.PublishedSnapshot; // fixme inject! switch (itemType) @@ -40,7 +40,7 @@ namespace Umbraco.ModelsBuilder.Umbraco } } - public static PublishedPropertyType GetModelPropertyType(PublishedContentType contentType, Expression> selector) + public static IPublishedPropertyType GetModelPropertyType(PublishedContentType contentType, Expression> selector) //where TModel : PublishedContentModel // fixme PublishedContentModel _or_ PublishedElementModel { // fixme therefore, missing a check on TModel here From 86cf3b275f2385dc398cc50bdb1e4e1ded0a38a2 Mon Sep 17 00:00:00 2001 From: Stephan Date: Thu, 27 Jun 2019 13:40:40 +0200 Subject: [PATCH 009/548] Some more things need to be public --- .../Validation/ContentTypeModelValidator.cs | 8 ++++---- src/Umbraco.Web/Editors/EditorValidatorCollection.cs | 2 +- .../Editors/EditorValidatorCollectionBuilder.cs | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.ModelsBuilder/Validation/ContentTypeModelValidator.cs b/src/Umbraco.ModelsBuilder/Validation/ContentTypeModelValidator.cs index 3d2ce600c2..dc4649f364 100644 --- a/src/Umbraco.ModelsBuilder/Validation/ContentTypeModelValidator.cs +++ b/src/Umbraco.ModelsBuilder/Validation/ContentTypeModelValidator.cs @@ -14,24 +14,24 @@ namespace Umbraco.ModelsBuilder.Validation /// Used to validate the aliases for the content type when MB is enabled to ensure that /// no illegal aliases are used /// - internal class ContentTypeModelValidator : ContentTypeModelValidatorBase + public class ContentTypeModelValidator : ContentTypeModelValidatorBase { } /// /// Used to validate the aliases for the content type when MB is enabled to ensure that /// no illegal aliases are used /// - internal class MediaTypeModelValidator : ContentTypeModelValidatorBase + public class MediaTypeModelValidator : ContentTypeModelValidatorBase { } /// /// Used to validate the aliases for the content type when MB is enabled to ensure that /// no illegal aliases are used /// - internal class MemberTypeModelValidator : ContentTypeModelValidatorBase + public class MemberTypeModelValidator : ContentTypeModelValidatorBase { } - internal abstract class ContentTypeModelValidatorBase : EditorValidator + public abstract class ContentTypeModelValidatorBase : EditorValidator where TModel: ContentTypeSave where TProperty: PropertyTypeBasic { diff --git a/src/Umbraco.Web/Editors/EditorValidatorCollection.cs b/src/Umbraco.Web/Editors/EditorValidatorCollection.cs index 6fc6bb5de2..0e42b0027c 100644 --- a/src/Umbraco.Web/Editors/EditorValidatorCollection.cs +++ b/src/Umbraco.Web/Editors/EditorValidatorCollection.cs @@ -6,7 +6,7 @@ using Umbraco.Core.Composing; namespace Umbraco.Web.Editors { - internal class EditorValidatorCollection : BuilderCollectionBase + public class EditorValidatorCollection : BuilderCollectionBase { public EditorValidatorCollection(IEnumerable items) : base(items) diff --git a/src/Umbraco.Web/Editors/EditorValidatorCollectionBuilder.cs b/src/Umbraco.Web/Editors/EditorValidatorCollectionBuilder.cs index b3b7bab1a5..994a813cf4 100644 --- a/src/Umbraco.Web/Editors/EditorValidatorCollectionBuilder.cs +++ b/src/Umbraco.Web/Editors/EditorValidatorCollectionBuilder.cs @@ -2,7 +2,7 @@ namespace Umbraco.Web.Editors { - internal class EditorValidatorCollectionBuilder : LazyCollectionBuilderBase + public class EditorValidatorCollectionBuilder : LazyCollectionBuilderBase { protected override EditorValidatorCollectionBuilder This => this; } From 3031459b8a43e33d501433e2e0e916ac67b31793 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Tue, 16 Jul 2019 19:44:11 +0200 Subject: [PATCH 010/548] Don't allow logins for users with no content and/or media start nodes --- src/Umbraco.Web/Security/BackOfficeSignInManager.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/Umbraco.Web/Security/BackOfficeSignInManager.cs b/src/Umbraco.Web/Security/BackOfficeSignInManager.cs index b33487bc8d..66b90a1396 100644 --- a/src/Umbraco.Web/Security/BackOfficeSignInManager.cs +++ b/src/Umbraco.Web/Security/BackOfficeSignInManager.cs @@ -112,6 +112,19 @@ 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); + return SignInStatus.Failure; + } + } + await UserManager.ResetAccessFailedCountAsync(user.Id); return await SignInOrTwoFactor(user, isPersistent); } From f4d8f58505bdb3d0c942ddfb165ffe095977aaf4 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Thu, 25 Jul 2019 07:40:38 +0200 Subject: [PATCH 011/548] Update src/Umbraco.Web/Security/BackOfficeSignInManager.cs Co-Authored-By: Ronald Barendse --- src/Umbraco.Web/Security/BackOfficeSignInManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Security/BackOfficeSignInManager.cs b/src/Umbraco.Web/Security/BackOfficeSignInManager.cs index 66b90a1396..fb8aff42e8 100644 --- a/src/Umbraco.Web/Security/BackOfficeSignInManager.cs +++ b/src/Umbraco.Web/Security/BackOfficeSignInManager.cs @@ -115,7 +115,7 @@ namespace Umbraco.Web.Security // 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 (userIdentity is UmbracoBackOfficeIdentity backOfficeIdentity) { if(backOfficeIdentity.StartContentNodes.Length == 0 || backOfficeIdentity.StartMediaNodes.Length == 0) { From f27b7549671e666928f422024f81bb82837b0c2d Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Thu, 25 Jul 2019 07:40:44 +0200 Subject: [PATCH 012/548] Update src/Umbraco.Web/Security/BackOfficeSignInManager.cs Co-Authored-By: Ronald Barendse --- src/Umbraco.Web/Security/BackOfficeSignInManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Security/BackOfficeSignInManager.cs b/src/Umbraco.Web/Security/BackOfficeSignInManager.cs index fb8aff42e8..8e5e532731 100644 --- a/src/Umbraco.Web/Security/BackOfficeSignInManager.cs +++ b/src/Umbraco.Web/Security/BackOfficeSignInManager.cs @@ -117,7 +117,7 @@ namespace Umbraco.Web.Security var userIdentity = await CreateUserIdentityAsync(user); if (userIdentity is UmbracoBackOfficeIdentity backOfficeIdentity) { - if(backOfficeIdentity.StartContentNodes.Length == 0 || backOfficeIdentity.StartMediaNodes.Length == 0) + 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); From 9a36d10d0c52227b77412a29c3acf8a8b986d670 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Wed, 9 Oct 2019 00:07:34 +0200 Subject: [PATCH 013/548] Combine exceptions thrown while terminating components into AggregateException --- .../Composing/ComponentCollection.cs | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Composing/ComponentCollection.cs b/src/Umbraco.Core/Composing/ComponentCollection.cs index 9b5319dc41..82c5ca78d3 100644 --- a/src/Umbraco.Core/Composing/ComponentCollection.cs +++ b/src/Umbraco.Core/Composing/ComponentCollection.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using Umbraco.Core.Logging; @@ -38,15 +39,29 @@ namespace Umbraco.Core.Composing { using (_logger.DebugDuration($"Terminating. (log components when >{LogThresholdMilliseconds}ms)", "Terminated.")) { + var exceptions = new List(); + foreach (var component in this.Reverse()) // terminate components in reverse order { var componentType = component.GetType(); using (_logger.DebugDuration($"Terminating {componentType.FullName}.", $"Terminated {componentType.FullName}.", thresholdMilliseconds: LogThresholdMilliseconds)) { - component.Terminate(); - component.DisposeIfDisposable(); + try + { + component.Terminate(); + component.DisposeIfDisposable(); + } + catch (Exception ex) + { + exceptions.Add(ex); + } } } + + if (exceptions.Count > 0) + { + throw new AggregateException("One or more errors occurred while terminating components.", exceptions); + } } } } From 2170a575f2f25a0da9614fcdc291d25e14a8f0fa Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Wed, 9 Oct 2019 00:11:46 +0200 Subject: [PATCH 014/548] Log exceptions thrown while terminating components --- src/Umbraco.Core/Composing/ComponentCollection.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/Umbraco.Core/Composing/ComponentCollection.cs b/src/Umbraco.Core/Composing/ComponentCollection.cs index 82c5ca78d3..a88f17bc07 100644 --- a/src/Umbraco.Core/Composing/ComponentCollection.cs +++ b/src/Umbraco.Core/Composing/ComponentCollection.cs @@ -39,8 +39,6 @@ namespace Umbraco.Core.Composing { using (_logger.DebugDuration($"Terminating. (log components when >{LogThresholdMilliseconds}ms)", "Terminated.")) { - var exceptions = new List(); - foreach (var component in this.Reverse()) // terminate components in reverse order { var componentType = component.GetType(); @@ -53,15 +51,10 @@ namespace Umbraco.Core.Composing } catch (Exception ex) { - exceptions.Add(ex); + _logger.Error(componentType, ex, "Error while terminating component."); } } } - - if (exceptions.Count > 0) - { - throw new AggregateException("One or more errors occurred while terminating components.", exceptions); - } } } } From ef03b0c6115cca168b3ad2787a6d8921ab3d28a1 Mon Sep 17 00:00:00 2001 From: Steve Megson Date: Wed, 9 Oct 2019 00:39:28 +0100 Subject: [PATCH 015/548] Stop caching of preview macro results --- .../Editors/MacroRenderingController.cs | 21 +++++--- src/Umbraco.Web/IUmbracoComponentRenderer.cs | 10 ++++ src/Umbraco.Web/UmbracoComponentRenderer.cs | 52 ++----------------- src/Umbraco.Web/UmbracoHelper.cs | 6 +-- 4 files changed, 30 insertions(+), 59 deletions(-) diff --git a/src/Umbraco.Web/Editors/MacroRenderingController.cs b/src/Umbraco.Web/Editors/MacroRenderingController.cs index efad07ce89..f8feae25eb 100644 --- a/src/Umbraco.Web/Editors/MacroRenderingController.cs +++ b/src/Umbraco.Web/Editors/MacroRenderingController.cs @@ -135,14 +135,19 @@ namespace Umbraco.Web.Editors // must have an active variation context! _variationContextAccessor.VariationContext = new VariationContext(culture); - var result = Request.CreateResponse(); - //need to create a specific content result formatted as HTML since this controller has been configured - //with only json formatters. - result.Content = new StringContent( - _componentRenderer.RenderMacro(pageId, m.Alias, macroParams).ToString(), - Encoding.UTF8, - "text/html"); - return result; + using (UmbracoContext.ForcedPreview(true)) + { + + var result = Request.CreateResponse(); + //need to create a specific content result formatted as HTML since this controller has been configured + //with only json formatters. + result.Content = new StringContent( + _componentRenderer.RenderMacro(publishedContent, m.Alias, macroParams).ToString(), + Encoding.UTF8, + "text/html"); + + return result; + } } [HttpPost] diff --git a/src/Umbraco.Web/IUmbracoComponentRenderer.cs b/src/Umbraco.Web/IUmbracoComponentRenderer.cs index 4dc9036e6b..dfe607fd4a 100644 --- a/src/Umbraco.Web/IUmbracoComponentRenderer.cs +++ b/src/Umbraco.Web/IUmbracoComponentRenderer.cs @@ -42,5 +42,15 @@ namespace Umbraco.Web /// The parameters. /// IHtmlString RenderMacro(int contentId, string alias, IDictionary parameters); + + /// + /// Renders the macro with the specified alias, passing in the specified parameters. + /// + /// + /// The alias. + /// The parameters. + /// + IHtmlString RenderMacro(IPublishedContent content, string alias, IDictionary parameters); + } } diff --git a/src/Umbraco.Web/UmbracoComponentRenderer.cs b/src/Umbraco.Web/UmbracoComponentRenderer.cs index f6c3d30da3..19e4223e2b 100644 --- a/src/Umbraco.Web/UmbracoComponentRenderer.cs +++ b/src/Umbraco.Web/UmbracoComponentRenderer.cs @@ -92,12 +92,12 @@ namespace Umbraco.Web if (contentId == default) throw new ArgumentException("Invalid content id " + contentId); - var content = _umbracoContextAccessor.UmbracoContext.Content?.GetById(true, contentId); + var content = _umbracoContextAccessor.UmbracoContext.Content?.GetById(contentId); if (content == null) throw new InvalidOperationException("Cannot render a macro, no content found by id " + contentId); - return RenderMacro(alias, parameters, content); + return RenderMacro(content, alias, parameters); } /// @@ -107,7 +107,7 @@ namespace Umbraco.Web /// The parameters. /// The content used for macro rendering /// - private IHtmlString RenderMacro(string alias, IDictionary parameters, IPublishedContent content) + public IHtmlString RenderMacro(IPublishedContent content, string alias, IDictionary parameters) { if (content == null) throw new ArgumentNullException(nameof(content)); @@ -117,51 +117,7 @@ namespace Umbraco.Web x => x.Key.ToLowerInvariant(), i => (i.Value is string) ? HttpUtility.HtmlDecode(i.Value.ToString()) : i.Value); - var macroControl = _macroRenderer.Render(alias, content, macroProps).GetAsControl(); - - string html; - if (macroControl is LiteralControl control) - { - // no need to execute, we already have text - html = control.Text; - } - else - { - using (var containerPage = new FormlessPage()) - { - containerPage.Controls.Add(macroControl); - - using (var output = new StringWriter()) - { - // .Execute() does a PushTraceContext/PopTraceContext and writes trace output straight into 'output' - // and I do not see how we could wire the trace context to the current context... so it creates dirty - // trace output right in the middle of the page. - // - // The only thing we can do is fully disable trace output while .Execute() runs and restore afterwards - // which means trace output is lost if the macro is a control (.ascx or user control) that is invoked - // from within Razor -- which makes sense anyway because the control can _not_ run correctly from - // within Razor since it will never be inserted into the page pipeline (which may even not exist at all - // if we're running MVC). - // - // I'm sure there's more things that will get lost with this context changing but I guess we'll figure - // those out as we go along. One thing we lose is the content type response output. - // http://issues.umbraco.org/issue/U4-1599 if it is setup during the macro execution. So - // here we'll save the content type response and reset it after execute is called. - - var contentType = _umbracoContextAccessor.UmbracoContext.HttpContext.Response.ContentType; - var traceIsEnabled = containerPage.Trace.IsEnabled; - containerPage.Trace.IsEnabled = false; - _umbracoContextAccessor.UmbracoContext.HttpContext.Server.Execute(containerPage, output, true); - containerPage.Trace.IsEnabled = traceIsEnabled; - //reset the content type - _umbracoContextAccessor.UmbracoContext.HttpContext.Response.ContentType = contentType; - - //Now, we need to ensure that local links are parsed - html = TemplateUtilities.ParseInternalLinks(output.ToString(), _umbracoContextAccessor.UmbracoContext.UrlProvider); - } - } - - } + string html = _macroRenderer.Render(alias, content, macroProps).GetAsText(); return new HtmlString(html); } diff --git a/src/Umbraco.Web/UmbracoHelper.cs b/src/Umbraco.Web/UmbracoHelper.cs index 367d90a504..01d64cdd3e 100644 --- a/src/Umbraco.Web/UmbracoHelper.cs +++ b/src/Umbraco.Web/UmbracoHelper.cs @@ -146,7 +146,7 @@ namespace Umbraco.Web /// public IHtmlString RenderMacro(string alias) { - return ComponentRenderer.RenderMacro(AssignedContentItem?.Id ?? 0, alias, new { }); + return ComponentRenderer.RenderMacro(AssignedContentItem, alias, new Dictionary()); } /// @@ -157,7 +157,7 @@ namespace Umbraco.Web /// public IHtmlString RenderMacro(string alias, object parameters) { - return ComponentRenderer.RenderMacro(AssignedContentItem?.Id ?? 0, alias, parameters.ToDictionary()); + return ComponentRenderer.RenderMacro(AssignedContentItem, alias, parameters.ToDictionary()); } /// @@ -168,7 +168,7 @@ namespace Umbraco.Web /// public IHtmlString RenderMacro(string alias, IDictionary parameters) { - return ComponentRenderer.RenderMacro(AssignedContentItem?.Id ?? 0, alias, parameters); + return ComponentRenderer.RenderMacro(AssignedContentItem, alias, parameters); } #endregion From 7f889e6d1b0c73a9f70382f10eb39645740a847b Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Wed, 9 Oct 2019 09:55:12 +0200 Subject: [PATCH 016/548] Get EnableComposer/DisableComposer attributes for all assemblies --- src/Umbraco.Core/Composing/Composers.cs | 21 +++-- src/Umbraco.Core/Runtime/CoreRuntime.cs | 2 +- .../Components/ComponentTests.cs | 79 +++++++++++-------- src/Umbraco.Tests/Runtimes/StandaloneTests.cs | 4 +- 4 files changed, 65 insertions(+), 41 deletions(-) diff --git a/src/Umbraco.Core/Composing/Composers.cs b/src/Umbraco.Core/Composing/Composers.cs index 0510740e42..a4eba294bc 100644 --- a/src/Umbraco.Core/Composing/Composers.cs +++ b/src/Umbraco.Core/Composing/Composers.cs @@ -18,19 +18,31 @@ namespace Umbraco.Core.Composing private readonly Composition _composition; private readonly IProfilingLogger _logger; private readonly IEnumerable _composerTypes; + private readonly IEnumerable _assemblies; private const int LogThresholdMilliseconds = 100; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The composition. /// The composer types. + /// The assemblies. /// A profiling logger. - public Composers(Composition composition, IEnumerable composerTypes, IProfilingLogger logger) + /// + /// composition + /// or + /// composerTypes + /// or + /// assemblies + /// or + /// logger + /// + public Composers(Composition composition, IEnumerable composerTypes, IEnumerable assemblies, IProfilingLogger logger) { _composition = composition ?? throw new ArgumentNullException(nameof(composition)); _composerTypes = composerTypes ?? throw new ArgumentNullException(nameof(composerTypes)); + _assemblies = assemblies ?? throw new ArgumentNullException(nameof(assemblies)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } @@ -103,7 +115,7 @@ namespace Umbraco.Core.Composing .ToList(); // enable or disable composers - EnableDisableComposers(composerTypeList); + EnableDisableComposers(composerTypeList, _assemblies); void GatherInterfaces(Type type, Func getTypeInAttribute, HashSet iset, List set2) where TAttribute : Attribute @@ -218,7 +230,7 @@ namespace Umbraco.Core.Composing return text.ToString(); } - private static void EnableDisableComposers(ICollection types) + private static void EnableDisableComposers(ICollection types, IEnumerable assemblies) { var enabled = new Dictionary(); @@ -240,7 +252,6 @@ namespace Umbraco.Core.Composing enableInfo.Weight = weight2; } - var assemblies = types.Select(x => x.Assembly).Distinct(); foreach (var assembly in assemblies) { foreach (var attr in assembly.GetCustomAttributes()) diff --git a/src/Umbraco.Core/Runtime/CoreRuntime.cs b/src/Umbraco.Core/Runtime/CoreRuntime.cs index 5b069641c4..abc2a924b4 100644 --- a/src/Umbraco.Core/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Core/Runtime/CoreRuntime.cs @@ -148,7 +148,7 @@ namespace Umbraco.Core.Runtime // get composers, and compose var composerTypes = ResolveComposerTypes(typeLoader); composition.WithCollectionBuilder(); - var composers = new Composers(composition, composerTypes, ProfilingLogger); + var composers = new Composers(composition, composerTypes, typeLoader.AssembliesToScan, ProfilingLogger); composers.Compose(); // create the factory diff --git a/src/Umbraco.Tests/Components/ComponentTests.cs b/src/Umbraco.Tests/Components/ComponentTests.cs index 2ba94d8c78..a82975e8c5 100644 --- a/src/Umbraco.Tests/Components/ComponentTests.cs +++ b/src/Umbraco.Tests/Components/ComponentTests.cs @@ -65,10 +65,11 @@ namespace Umbraco.Tests.Components public void Boot1A() { var register = MockRegister(); - var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); + var typeLoader = MockTypeLoader(); + var composition = new Composition(register, typeLoader, Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); var types = TypeArray(); - var composers = new Composers(composition, types, Mock.Of()); + var composers = new Composers(composition, types, typeLoader.AssembliesToScan, Mock.Of()); Composed.Clear(); // 2 is Core and requires 4 // 3 is User - goes away with RuntimeLevel.Unknown @@ -104,10 +105,11 @@ namespace Umbraco.Tests.Components public void Boot1B() { var register = MockRegister(); - var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Run)); + var typeLoader = MockTypeLoader(); + var composition = new Composition(register, typeLoader, Mock.Of(), MockRuntimeState(RuntimeLevel.Run)); var types = TypeArray(); - var composers = new Composers(composition, types, Mock.Of()); + var composers = new Composers(composition, types, typeLoader.AssembliesToScan, Mock.Of()); Composed.Clear(); // 2 is Core and requires 4 // 3 is User - stays with RuntimeLevel.Run @@ -120,10 +122,11 @@ namespace Umbraco.Tests.Components public void Boot2() { var register = MockRegister(); - var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); + var typeLoader = MockTypeLoader(); + var composition = new Composition(register, typeLoader, Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); var types = TypeArray(); - var composers = new Composers(composition, types, Mock.Of()); + var composers = new Composers(composition, types, typeLoader.AssembliesToScan, Mock.Of()); Composed.Clear(); // 21 is required by 20 // => reorder components accordingly @@ -135,10 +138,11 @@ namespace Umbraco.Tests.Components public void Boot3() { var register = MockRegister(); - var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); + var typeLoader = MockTypeLoader(); + var composition = new Composition(register, typeLoader, Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); var types = TypeArray(); - var composers = new Composers(composition, types, Mock.Of()); + var composers = new Composers(composition, types, typeLoader.AssembliesToScan, Mock.Of()); Composed.Clear(); // i23 requires 22 // 24, 25 implement i23 @@ -152,10 +156,11 @@ namespace Umbraco.Tests.Components public void BrokenRequire() { var register = MockRegister(); - var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); + var typeLoader = MockTypeLoader(); + var composition = new Composition(register, typeLoader, Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); var types = TypeArray(); - var composers = new Composers(composition, types, Mock.Of()); + var composers = new Composers(composition, types, typeLoader.AssembliesToScan, Mock.Of()); Composed.Clear(); try { @@ -175,10 +180,11 @@ namespace Umbraco.Tests.Components public void BrokenRequired() { var register = MockRegister(); - var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); + var typeLoader = MockTypeLoader(); + var composition = new Composition(register, typeLoader, Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); var types = TypeArray(); - var composers = new Composers(composition, types, Mock.Of()); + var composers = new Composers(composition, types, typeLoader.AssembliesToScan, Mock.Of()); Composed.Clear(); // 2 is Core and requires 4 // 13 is required by 1 @@ -196,6 +202,7 @@ namespace Umbraco.Tests.Components Terminated.Clear(); var register = MockRegister(); + var typeLoader = MockTypeLoader(); var factory = MockFactory(m => { m.Setup(x => x.TryGetInstance(It.Is(t => t == typeof (ISomeResource)))).Returns(() => new SomeResource()); @@ -210,10 +217,10 @@ namespace Umbraco.Tests.Components throw new NotSupportedException(type.FullName); }); }); - var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); + var composition = new Composition(register, typeLoader, Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); var types = new[] { typeof(Composer1), typeof(Composer5), typeof(Composer5a) }; - var composers = new Composers(composition, types, Mock.Of()); + var composers = new Composers(composition, types, typeLoader.AssembliesToScan, Mock.Of()); Assert.IsEmpty(Composed); composers.Compose(); @@ -236,10 +243,11 @@ namespace Umbraco.Tests.Components public void Requires1() { var register = MockRegister(); - var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); + var typeLoader = MockTypeLoader(); + var composition = new Composition(register, typeLoader, Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); var types = new[] { typeof(Composer6), typeof(Composer7), typeof(Composer8) }; - var composers = new Composers(composition, types, Mock.Of()); + var composers = new Composers(composition, types, typeLoader.AssembliesToScan, Mock.Of()); Composed.Clear(); composers.Compose(); Assert.AreEqual(2, Composed.Count); @@ -251,10 +259,11 @@ namespace Umbraco.Tests.Components public void Requires2A() { var register = MockRegister(); - var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); + var typeLoader = MockTypeLoader(); + var composition = new Composition(register, typeLoader, Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); var types = new[] { typeof(Composer9), typeof(Composer2), typeof(Composer4) }; - var composers = new Composers(composition, types, Mock.Of()); + var composers = new Composers(composition, types, typeLoader.AssembliesToScan, Mock.Of()); Composed.Clear(); composers.Compose(); Assert.AreEqual(2, Composed.Count); @@ -267,11 +276,12 @@ namespace Umbraco.Tests.Components public void Requires2B() { var register = MockRegister(); + var typeLoader = MockTypeLoader(); var factory = MockFactory(); - var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Run)); + var composition = new Composition(register, typeLoader, Mock.Of(), MockRuntimeState(RuntimeLevel.Run)); var types = new[] { typeof(Composer9), typeof(Composer2), typeof(Composer4) }; - var composers = new Composers(composition, types, Mock.Of()); + var composers = new Composers(composition, types, typeLoader.AssembliesToScan, Mock.Of()); Composed.Clear(); composers.Compose(); var builder = composition.WithCollectionBuilder(); @@ -287,35 +297,36 @@ namespace Umbraco.Tests.Components public void WeakDependencies() { var register = MockRegister(); - var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); + var typeLoader = MockTypeLoader(); + var composition = new Composition(register, typeLoader, Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); var types = new[] { typeof(Composer10) }; - var composers = new Composers(composition, types, Mock.Of()); + var composers = new Composers(composition, types, typeLoader.AssembliesToScan, Mock.Of()); Composed.Clear(); composers.Compose(); Assert.AreEqual(1, Composed.Count); Assert.AreEqual(typeof(Composer10), Composed[0]); types = new[] { typeof(Composer11) }; - composers = new Composers(composition, types, Mock.Of()); + composers = new Composers(composition, types, typeLoader.AssembliesToScan, Mock.Of()); Composed.Clear(); Assert.Throws(() => composers.Compose()); Console.WriteLine("throws:"); - composers = new Composers(composition, types, Mock.Of()); + composers = new Composers(composition, types, typeLoader.AssembliesToScan, Mock.Of()); var requirements = composers.GetRequirements(false); Console.WriteLine(Composers.GetComposersReport(requirements)); types = new[] { typeof(Composer2) }; - composers = new Composers(composition, types, Mock.Of()); + composers = new Composers(composition, types, typeLoader.AssembliesToScan, Mock.Of()); Composed.Clear(); Assert.Throws(() => composers.Compose()); Console.WriteLine("throws:"); - composers = new Composers(composition, types, Mock.Of()); + composers = new Composers(composition, types, typeLoader.AssembliesToScan, Mock.Of()); requirements = composers.GetRequirements(false); Console.WriteLine(Composers.GetComposersReport(requirements)); types = new[] { typeof(Composer12) }; - composers = new Composers(composition, types, Mock.Of()); + composers = new Composers(composition, types, typeLoader.AssembliesToScan, Mock.Of()); Composed.Clear(); composers.Compose(); Assert.AreEqual(1, Composed.Count); @@ -326,10 +337,11 @@ namespace Umbraco.Tests.Components public void DisableMissing() { var register = MockRegister(); - var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); + var typeLoader = MockTypeLoader(); + var composition = new Composition(register, typeLoader, Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); var types = new[] { typeof(Composer6), typeof(Composer8) }; // 8 disables 7 which is not in the list - var composers = new Composers(composition, types, Mock.Of()); + var composers = new Composers(composition, types, typeLoader.AssembliesToScan, Mock.Of()); Composed.Clear(); composers.Compose(); Assert.AreEqual(2, Composed.Count); @@ -341,16 +353,17 @@ namespace Umbraco.Tests.Components public void AttributesPriorities() { var register = MockRegister(); - var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); + var typeLoader = MockTypeLoader(); + var composition = new Composition(register, typeLoader, Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); var types = new[] { typeof(Composer26) }; // 26 disabled by assembly attribute - var composers = new Composers(composition, types, Mock.Of()); + var composers = new Composers(composition, types, typeLoader.AssembliesToScan, Mock.Of()); Composed.Clear(); composers.Compose(); Assert.AreEqual(0, Composed.Count); // 26 gone types = new[] { typeof(Composer26), typeof(Composer27) }; // 26 disabled by assembly attribute, enabled by 27 - composers = new Composers(composition, types, Mock.Of()); + composers = new Composers(composition, types, typeLoader.AssembliesToScan, Mock.Of()); Composed.Clear(); composers.Compose(); Assert.AreEqual(2, Composed.Count); // both @@ -367,7 +380,7 @@ namespace Umbraco.Tests.Components var composition = new Composition(register, typeLoader, Mock.Of(), MockRuntimeState(RuntimeLevel.Run)); var types = typeLoader.GetTypes().Where(x => x.FullName.StartsWith("Umbraco.Core.") || x.FullName.StartsWith("Umbraco.Web")); - var composers = new Composers(composition, types, Mock.Of()); + var composers = new Composers(composition, types, typeLoader.AssembliesToScan, Mock.Of()); var requirements = composers.GetRequirements(); var report = Composers.GetComposersReport(requirements); Console.WriteLine(report); diff --git a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs index d0258a100f..63422dd32c 100644 --- a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs +++ b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs @@ -83,7 +83,7 @@ namespace Umbraco.Tests.Runtimes var composerTypes = typeLoader.GetTypes() // all of them .Where(x => !x.FullName.StartsWith("Umbraco.Tests.")) // exclude test components .Where(x => x != typeof(WebInitialComposer) && x != typeof(WebFinalComposer)); // exclude web runtime - var composers = new Composers(composition, composerTypes, profilingLogger); + var composers = new Composers(composition, composerTypes, typeLoader.AssembliesToScan, profilingLogger); composers.Compose(); // must registers stuff that WebRuntimeComponent would register otherwise @@ -272,7 +272,7 @@ namespace Umbraco.Tests.Runtimes .Where(x => !x.FullName.StartsWith("Umbraco.Tests")); // single? //var componentTypes = new[] { typeof(CoreRuntimeComponent) }; - var composers = new Composers(composition, composerTypes, profilingLogger); + var composers = new Composers(composition, composerTypes, typeLoader.AssembliesToScan, profilingLogger); // get components to compose themselves composers.Compose(); From be21ddb38ef3f8a09a18b8614b803a1f79132435 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Wed, 9 Oct 2019 10:20:10 +0200 Subject: [PATCH 017/548] Added GetAssemblyAttributes to TypeLoader --- src/Umbraco.Core/Composing/TypeLoader.cs | 32 ++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/Umbraco.Core/Composing/TypeLoader.cs b/src/Umbraco.Core/Composing/TypeLoader.cs index fe7a561eca..1a5cd6751f 100644 --- a/src/Umbraco.Core/Composing/TypeLoader.cs +++ b/src/Umbraco.Core/Composing/TypeLoader.cs @@ -505,6 +505,38 @@ namespace Umbraco.Core.Composing #endregion + #region Get Assembly Attributes + + /// + /// Gets the assembly attributes. + /// + /// The attribute type. + /// + /// The assembly attributes of the specified type. + /// + public IEnumerable GetAssemblyAttributes() + where T : Attribute + { + return AssembliesToScan.SelectMany(a => a.GetCustomAttributes()); + } + + /// + /// Gets the assembly attributes. + /// + /// The attribute types. + /// + /// The assembly attributes of the specified types. + /// + /// attributeTypes + public IEnumerable GetAssemblyAttributes(params Type[] attributeTypes) + { + if (attributeTypes == null) throw new ArgumentNullException(nameof(attributeTypes)); + + return AssembliesToScan.SelectMany(a => attributeTypes.SelectMany(at => a.GetCustomAttributes(at))).ToList(); + } + + #endregion + #region Get Types /// From 03bbcec8946f74dc89d9a86be1b9827766cec8a3 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Wed, 9 Oct 2019 10:22:39 +0200 Subject: [PATCH 018/548] Enable/disable composers based on supplied attributes (instead of assemblies) --- src/Umbraco.Core/Composing/Composers.cs | 34 ++++++++-------- src/Umbraco.Core/Runtime/CoreRuntime.cs | 3 +- .../Components/ComponentTests.cs | 40 +++++++++---------- src/Umbraco.Tests/Runtimes/StandaloneTests.cs | 4 +- 4 files changed, 40 insertions(+), 41 deletions(-) diff --git a/src/Umbraco.Core/Composing/Composers.cs b/src/Umbraco.Core/Composing/Composers.cs index a4eba294bc..9b03c18baa 100644 --- a/src/Umbraco.Core/Composing/Composers.cs +++ b/src/Umbraco.Core/Composing/Composers.cs @@ -18,7 +18,7 @@ namespace Umbraco.Core.Composing private readonly Composition _composition; private readonly IProfilingLogger _logger; private readonly IEnumerable _composerTypes; - private readonly IEnumerable _assemblies; + private readonly IEnumerable _enableDisableAttributes; private const int LogThresholdMilliseconds = 100; @@ -27,22 +27,23 @@ namespace Umbraco.Core.Composing /// /// The composition. /// The composer types. - /// The assemblies. + /// The enable/disable attributes. /// A profiling logger. /// /// composition /// or /// composerTypes /// or - /// assemblies + /// enableDisableAttributes /// or /// logger /// - public Composers(Composition composition, IEnumerable composerTypes, IEnumerable assemblies, IProfilingLogger logger) + + public Composers(Composition composition, IEnumerable composerTypes, IEnumerable enableDisableAttributes, IProfilingLogger logger) { _composition = composition ?? throw new ArgumentNullException(nameof(composition)); _composerTypes = composerTypes ?? throw new ArgumentNullException(nameof(composerTypes)); - _assemblies = assemblies ?? throw new ArgumentNullException(nameof(assemblies)); + _enableDisableAttributes = enableDisableAttributes ?? throw new ArgumentNullException(nameof(enableDisableAttributes)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } @@ -115,7 +116,7 @@ namespace Umbraco.Core.Composing .ToList(); // enable or disable composers - EnableDisableComposers(composerTypeList, _assemblies); + EnableDisableComposers(_enableDisableAttributes, composerTypeList); void GatherInterfaces(Type type, Func getTypeInAttribute, HashSet iset, List set2) where TAttribute : Attribute @@ -230,7 +231,7 @@ namespace Umbraco.Core.Composing return text.ToString(); } - private static void EnableDisableComposers(ICollection types, IEnumerable assemblies) + private static void EnableDisableComposers(IEnumerable enableDisableAttributes, ICollection types) { var enabled = new Dictionary(); @@ -252,19 +253,16 @@ namespace Umbraco.Core.Composing enableInfo.Weight = weight2; } - foreach (var assembly in assemblies) + foreach (var attr in enableDisableAttributes.OfType()) { - foreach (var attr in assembly.GetCustomAttributes()) - { - var type = attr.EnabledType; - UpdateEnableInfo(type, 2, enabled, true); - } + var type = attr.EnabledType; + UpdateEnableInfo(type, 2, enabled, true); + } - foreach (var attr in assembly.GetCustomAttributes()) - { - var type = attr.DisabledType; - UpdateEnableInfo(type, 2, enabled, false); - } + foreach (var attr in enableDisableAttributes.OfType()) + { + var type = attr.DisabledType; + UpdateEnableInfo(type, 2, enabled, false); } foreach (var composerType in types) diff --git a/src/Umbraco.Core/Runtime/CoreRuntime.cs b/src/Umbraco.Core/Runtime/CoreRuntime.cs index abc2a924b4..6c462dc064 100644 --- a/src/Umbraco.Core/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Core/Runtime/CoreRuntime.cs @@ -147,8 +147,9 @@ namespace Umbraco.Core.Runtime // get composers, and compose var composerTypes = ResolveComposerTypes(typeLoader); + var enableDisableAttributes = typeLoader.GetAssemblyAttributes(typeof(EnableComposerAttribute), typeof(DisableComposerAttribute)); composition.WithCollectionBuilder(); - var composers = new Composers(composition, composerTypes, typeLoader.AssembliesToScan, ProfilingLogger); + var composers = new Composers(composition, composerTypes, enableDisableAttributes, ProfilingLogger); composers.Compose(); // create the factory diff --git a/src/Umbraco.Tests/Components/ComponentTests.cs b/src/Umbraco.Tests/Components/ComponentTests.cs index a82975e8c5..1a528f85f6 100644 --- a/src/Umbraco.Tests/Components/ComponentTests.cs +++ b/src/Umbraco.Tests/Components/ComponentTests.cs @@ -69,7 +69,7 @@ namespace Umbraco.Tests.Components var composition = new Composition(register, typeLoader, Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); var types = TypeArray(); - var composers = new Composers(composition, types, typeLoader.AssembliesToScan, Mock.Of()); + var composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of()); Composed.Clear(); // 2 is Core and requires 4 // 3 is User - goes away with RuntimeLevel.Unknown @@ -109,7 +109,7 @@ namespace Umbraco.Tests.Components var composition = new Composition(register, typeLoader, Mock.Of(), MockRuntimeState(RuntimeLevel.Run)); var types = TypeArray(); - var composers = new Composers(composition, types, typeLoader.AssembliesToScan, Mock.Of()); + var composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of()); Composed.Clear(); // 2 is Core and requires 4 // 3 is User - stays with RuntimeLevel.Run @@ -126,7 +126,7 @@ namespace Umbraco.Tests.Components var composition = new Composition(register, typeLoader, Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); var types = TypeArray(); - var composers = new Composers(composition, types, typeLoader.AssembliesToScan, Mock.Of()); + var composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of()); Composed.Clear(); // 21 is required by 20 // => reorder components accordingly @@ -142,7 +142,7 @@ namespace Umbraco.Tests.Components var composition = new Composition(register, typeLoader, Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); var types = TypeArray(); - var composers = new Composers(composition, types, typeLoader.AssembliesToScan, Mock.Of()); + var composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of()); Composed.Clear(); // i23 requires 22 // 24, 25 implement i23 @@ -160,7 +160,7 @@ namespace Umbraco.Tests.Components var composition = new Composition(register, typeLoader, Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); var types = TypeArray(); - var composers = new Composers(composition, types, typeLoader.AssembliesToScan, Mock.Of()); + var composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of()); Composed.Clear(); try { @@ -184,7 +184,7 @@ namespace Umbraco.Tests.Components var composition = new Composition(register, typeLoader, Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); var types = TypeArray(); - var composers = new Composers(composition, types, typeLoader.AssembliesToScan, Mock.Of()); + var composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of()); Composed.Clear(); // 2 is Core and requires 4 // 13 is required by 1 @@ -220,7 +220,7 @@ namespace Umbraco.Tests.Components var composition = new Composition(register, typeLoader, Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); var types = new[] { typeof(Composer1), typeof(Composer5), typeof(Composer5a) }; - var composers = new Composers(composition, types, typeLoader.AssembliesToScan, Mock.Of()); + var composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of()); Assert.IsEmpty(Composed); composers.Compose(); @@ -247,7 +247,7 @@ namespace Umbraco.Tests.Components var composition = new Composition(register, typeLoader, Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); var types = new[] { typeof(Composer6), typeof(Composer7), typeof(Composer8) }; - var composers = new Composers(composition, types, typeLoader.AssembliesToScan, Mock.Of()); + var composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of()); Composed.Clear(); composers.Compose(); Assert.AreEqual(2, Composed.Count); @@ -263,7 +263,7 @@ namespace Umbraco.Tests.Components var composition = new Composition(register, typeLoader, Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); var types = new[] { typeof(Composer9), typeof(Composer2), typeof(Composer4) }; - var composers = new Composers(composition, types, typeLoader.AssembliesToScan, Mock.Of()); + var composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of()); Composed.Clear(); composers.Compose(); Assert.AreEqual(2, Composed.Count); @@ -281,7 +281,7 @@ namespace Umbraco.Tests.Components var composition = new Composition(register, typeLoader, Mock.Of(), MockRuntimeState(RuntimeLevel.Run)); var types = new[] { typeof(Composer9), typeof(Composer2), typeof(Composer4) }; - var composers = new Composers(composition, types, typeLoader.AssembliesToScan, Mock.Of()); + var composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of()); Composed.Clear(); composers.Compose(); var builder = composition.WithCollectionBuilder(); @@ -301,32 +301,32 @@ namespace Umbraco.Tests.Components var composition = new Composition(register, typeLoader, Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); var types = new[] { typeof(Composer10) }; - var composers = new Composers(composition, types, typeLoader.AssembliesToScan, Mock.Of()); + var composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of()); Composed.Clear(); composers.Compose(); Assert.AreEqual(1, Composed.Count); Assert.AreEqual(typeof(Composer10), Composed[0]); types = new[] { typeof(Composer11) }; - composers = new Composers(composition, types, typeLoader.AssembliesToScan, Mock.Of()); + composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of()); Composed.Clear(); Assert.Throws(() => composers.Compose()); Console.WriteLine("throws:"); - composers = new Composers(composition, types, typeLoader.AssembliesToScan, Mock.Of()); + composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of()); var requirements = composers.GetRequirements(false); Console.WriteLine(Composers.GetComposersReport(requirements)); types = new[] { typeof(Composer2) }; - composers = new Composers(composition, types, typeLoader.AssembliesToScan, Mock.Of()); + composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of()); Composed.Clear(); Assert.Throws(() => composers.Compose()); Console.WriteLine("throws:"); - composers = new Composers(composition, types, typeLoader.AssembliesToScan, Mock.Of()); + composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of()); requirements = composers.GetRequirements(false); Console.WriteLine(Composers.GetComposersReport(requirements)); types = new[] { typeof(Composer12) }; - composers = new Composers(composition, types, typeLoader.AssembliesToScan, Mock.Of()); + composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of()); Composed.Clear(); composers.Compose(); Assert.AreEqual(1, Composed.Count); @@ -341,7 +341,7 @@ namespace Umbraco.Tests.Components var composition = new Composition(register, typeLoader, Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); var types = new[] { typeof(Composer6), typeof(Composer8) }; // 8 disables 7 which is not in the list - var composers = new Composers(composition, types, typeLoader.AssembliesToScan, Mock.Of()); + var composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of()); Composed.Clear(); composers.Compose(); Assert.AreEqual(2, Composed.Count); @@ -357,13 +357,13 @@ namespace Umbraco.Tests.Components var composition = new Composition(register, typeLoader, Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); var types = new[] { typeof(Composer26) }; // 26 disabled by assembly attribute - var composers = new Composers(composition, types, typeLoader.AssembliesToScan, Mock.Of()); + var composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of()); Composed.Clear(); composers.Compose(); Assert.AreEqual(0, Composed.Count); // 26 gone types = new[] { typeof(Composer26), typeof(Composer27) }; // 26 disabled by assembly attribute, enabled by 27 - composers = new Composers(composition, types, typeLoader.AssembliesToScan, Mock.Of()); + composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of()); Composed.Clear(); composers.Compose(); Assert.AreEqual(2, Composed.Count); // both @@ -380,7 +380,7 @@ namespace Umbraco.Tests.Components var composition = new Composition(register, typeLoader, Mock.Of(), MockRuntimeState(RuntimeLevel.Run)); var types = typeLoader.GetTypes().Where(x => x.FullName.StartsWith("Umbraco.Core.") || x.FullName.StartsWith("Umbraco.Web")); - var composers = new Composers(composition, types, typeLoader.AssembliesToScan, Mock.Of()); + var composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of()); var requirements = composers.GetRequirements(); var report = Composers.GetComposersReport(requirements); Console.WriteLine(report); diff --git a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs index 63422dd32c..7ab329b9a0 100644 --- a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs +++ b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs @@ -83,7 +83,7 @@ namespace Umbraco.Tests.Runtimes var composerTypes = typeLoader.GetTypes() // all of them .Where(x => !x.FullName.StartsWith("Umbraco.Tests.")) // exclude test components .Where(x => x != typeof(WebInitialComposer) && x != typeof(WebFinalComposer)); // exclude web runtime - var composers = new Composers(composition, composerTypes, typeLoader.AssembliesToScan, profilingLogger); + var composers = new Composers(composition, composerTypes, Enumerable.Empty(), profilingLogger); composers.Compose(); // must registers stuff that WebRuntimeComponent would register otherwise @@ -272,7 +272,7 @@ namespace Umbraco.Tests.Runtimes .Where(x => !x.FullName.StartsWith("Umbraco.Tests")); // single? //var componentTypes = new[] { typeof(CoreRuntimeComponent) }; - var composers = new Composers(composition, composerTypes, typeLoader.AssembliesToScan, profilingLogger); + var composers = new Composers(composition, composerTypes, Enumerable.Empty(), profilingLogger); // get components to compose themselves composers.Compose(); From fa2a4d867b06e5c0775fdc66f53beaea50c44b06 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Wed, 9 Oct 2019 15:37:13 +0200 Subject: [PATCH 019/548] Return assemblies as list and added documentation --- src/Umbraco.Core/Composing/Composers.cs | 12 +++++------- src/Umbraco.Core/Composing/TypeLoader.cs | 8 ++++---- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Core/Composing/Composers.cs b/src/Umbraco.Core/Composing/Composers.cs index 9b03c18baa..004c2527e6 100644 --- a/src/Umbraco.Core/Composing/Composers.cs +++ b/src/Umbraco.Core/Composing/Composers.cs @@ -26,18 +26,16 @@ namespace Umbraco.Core.Composing /// Initializes a new instance of the class. /// /// The composition. - /// The composer types. - /// The enable/disable attributes. - /// A profiling logger. - /// - /// composition + /// The types. + /// The and/or attributes. + /// The profiling logger. + /// composition /// or /// composerTypes /// or /// enableDisableAttributes /// or - /// logger - /// + /// logger public Composers(Composition composition, IEnumerable composerTypes, IEnumerable enableDisableAttributes, IProfilingLogger logger) { diff --git a/src/Umbraco.Core/Composing/TypeLoader.cs b/src/Umbraco.Core/Composing/TypeLoader.cs index 1a5cd6751f..7c70b4feba 100644 --- a/src/Umbraco.Core/Composing/TypeLoader.cs +++ b/src/Umbraco.Core/Composing/TypeLoader.cs @@ -508,20 +508,20 @@ namespace Umbraco.Core.Composing #region Get Assembly Attributes /// - /// Gets the assembly attributes. + /// Gets the assembly attributes of the specified type . /// /// The attribute type. /// - /// The assembly attributes of the specified type. + /// The assembly attributes of the specified type . /// public IEnumerable GetAssemblyAttributes() where T : Attribute { - return AssembliesToScan.SelectMany(a => a.GetCustomAttributes()); + return AssembliesToScan.SelectMany(a => a.GetCustomAttributes()).ToList(); } /// - /// Gets the assembly attributes. + /// Gets the assembly attributes of the specified . /// /// The attribute types. /// From bb26f07d01c12c95a6c194b0fb0c3962feb54f7e Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Wed, 9 Oct 2019 15:38:07 +0200 Subject: [PATCH 020/548] Added parameterless GetAssemblyAttributes overload returning all attributes --- src/Umbraco.Core/Composing/TypeLoader.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/Umbraco.Core/Composing/TypeLoader.cs b/src/Umbraco.Core/Composing/TypeLoader.cs index 7c70b4feba..b8c4db6183 100644 --- a/src/Umbraco.Core/Composing/TypeLoader.cs +++ b/src/Umbraco.Core/Composing/TypeLoader.cs @@ -520,6 +520,17 @@ namespace Umbraco.Core.Composing return AssembliesToScan.SelectMany(a => a.GetCustomAttributes()).ToList(); } + /// + /// Gets all the assembly attributes. + /// + /// + /// All assembly attributes. + /// + public IEnumerable GetAssemblyAttributes() + { + return AssembliesToScan.SelectMany(a => a.GetCustomAttributes()).ToList(); + } + /// /// Gets the assembly attributes of the specified . /// From 932f7297f3473957c074325f72f632728eb8426a Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Wed, 9 Oct 2019 15:50:06 +0200 Subject: [PATCH 021/548] Fixed failing test by passing in correct enable/disable attributes --- src/Umbraco.Tests/Components/ComponentTests.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Tests/Components/ComponentTests.cs b/src/Umbraco.Tests/Components/ComponentTests.cs index 1a528f85f6..042cac1281 100644 --- a/src/Umbraco.Tests/Components/ComponentTests.cs +++ b/src/Umbraco.Tests/Components/ComponentTests.cs @@ -5,7 +5,6 @@ using Moq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Cache; -using Umbraco.Core.Compose; using Umbraco.Core.Composing; using Umbraco.Core.IO; using Umbraco.Core.Logging; @@ -13,8 +12,6 @@ using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Scoping; -[assembly:DisableComposer(typeof(Umbraco.Tests.Components.ComponentTests.Composer26))] - namespace Umbraco.Tests.Components { [TestFixture] @@ -356,14 +353,15 @@ namespace Umbraco.Tests.Components var typeLoader = MockTypeLoader(); var composition = new Composition(register, typeLoader, Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); - var types = new[] { typeof(Composer26) }; // 26 disabled by assembly attribute - var composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of()); + var types = new[] { typeof(Composer26) }; + var enableDisableAttributes = new[] { new DisableComposerAttribute(typeof(Composer26)) }; + var composers = new Composers(composition, types, enableDisableAttributes, Mock.Of()); Composed.Clear(); composers.Compose(); Assert.AreEqual(0, Composed.Count); // 26 gone types = new[] { typeof(Composer26), typeof(Composer27) }; // 26 disabled by assembly attribute, enabled by 27 - composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of()); + composers = new Composers(composition, types, enableDisableAttributes, Mock.Of()); Composed.Clear(); composers.Compose(); Assert.AreEqual(2, Composed.Count); // both @@ -519,7 +517,6 @@ namespace Umbraco.Tests.Components public class Composer25 : TestComposerBase, IComposer23 { } - // disabled by assembly attribute public class Composer26 : TestComposerBase { } From 45db8fd7c41ff1c24070a0d1b458c0359cc7df15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20Knippers?= Date: Thu, 29 Aug 2019 11:07:00 +0200 Subject: [PATCH 022/548] Add ISimpleContentType.VariesBySegment() --- src/Umbraco.Core/ContentVariationExtensions.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Umbraco.Core/ContentVariationExtensions.cs b/src/Umbraco.Core/ContentVariationExtensions.cs index 5b157307ab..fe5a82047a 100644 --- a/src/Umbraco.Core/ContentVariationExtensions.cs +++ b/src/Umbraco.Core/ContentVariationExtensions.cs @@ -19,6 +19,11 @@ namespace Umbraco.Core /// 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. /// From 74727a179cddcc64277d6db8faf99bc68c52f795 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20Knippers?= Date: Thu, 29 Aug 2019 12:05:27 +0200 Subject: [PATCH 023/548] Adds Segment to ContentItemDisplay and update Variants to include segmented variants. --- .../ContentEditing/ContentPropertyBasic.cs | 12 +++- .../Mapping/ContentPropertyBasicMapper.cs | 8 ++- .../Models/Mapping/ContentVariantMapper.cs | 65 ++++++++++++++++--- .../Models/Mapping/MapperContextExtensions.cs | 19 +++++- 4 files changed, 92 insertions(+), 12 deletions(-) diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentPropertyBasic.cs b/src/Umbraco.Web/Models/ContentEditing/ContentPropertyBasic.cs index c5c22484ad..2b70a63035 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentPropertyBasic.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentPropertyBasic.cs @@ -53,11 +53,21 @@ namespace Umbraco.Web.Models.ContentEditing [ReadOnly(true)] public string Culture { get; set; } + /// + /// The segment of the property + /// + /// + /// The segment value of a property can always be null but can only have a non-null value + /// when the property can be varied by segment. + /// + [DataMember(Name = "segment")] + [ReadOnly(true)] + public string Segment { get; set; } + /// /// Used internally during model mapping /// [IgnoreDataMember] internal IDataEditor PropertyEditor { get; set; } - } } diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicMapper.cs index 36c1b360b2..aacafd2f2a 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicMapper.cs @@ -70,8 +70,14 @@ namespace Umbraco.Web.Models.Mapping dest.Culture = culture; + var segment = context.GetSegment(); + + // The current segment can always be null, even if the propertyType *can* be varied by segment + // so the nullcheck like with culture is not performed here. + dest.Segment = segment; + // if no 'IncludeProperties' were specified or this property is set to be included - we will map the value and return. - dest.Value = editor.GetValueEditor().ToEditor(property, DataTypeService, culture); + dest.Value = editor.GetValueEditor().ToEditor(property, DataTypeService, culture, segment); } } } diff --git a/src/Umbraco.Web/Models/Mapping/ContentVariantMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentVariantMapper.cs index c279ae2c70..848eaa0594 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentVariantMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentVariantMapper.cs @@ -21,27 +21,35 @@ namespace Umbraco.Web.Models.Mapping public IEnumerable Map(IContent source, MapperContext context) { - var result = new List(); - if (!source.ContentType.VariesByCulture()) + var variants = new List(); + + var variesByCulture = source.ContentType.VariesByCulture(); + var variesBySegment = source.ContentType.VariesBySegment(); + + if (!variesByCulture && !variesBySegment) { //this is invariant so just map the IContent instance to ContentVariationDisplay - result.Add(context.Map(source)); + variants.Add(context.Map(source)); + return variants; } - else + + if (variesByCulture && !variesBySegment) { + // Culture only + var allLanguages = _localizationService.GetAllLanguages().OrderBy(x => x.Id).ToList(); if (allLanguages.Count == 0) return Enumerable.Empty(); //this should never happen var langs = context.MapEnumerable(allLanguages).ToList(); //create a variant for each language, then we'll populate the values - var variants = langs.Select(x => + variants.AddRange(langs.Select(x => { //We need to set the culture in the mapping context since this is needed to ensure that the correct property values //are resolved during the mapping context.SetCulture(x.IsoCode); return context.Map(source); - }).ToList(); + })); for (int i = 0; i < langs.Count; i++) { @@ -63,10 +71,49 @@ namespace Umbraco.Web.Models.Mapping //Insert the default language as the first item variants.Insert(0, defaultLang); - - return variants; } - return result; + else if (variesBySegment && !variesByCulture) + { + // Segment only + throw new NotSupportedException("ContentVariantMapper not implemented for segment only!"); + } + else + { + // Culture and segment + + var allLanguages = _localizationService.GetAllLanguages().OrderBy(x => x.Id).ToList(); + if (allLanguages.Count == 0) return Enumerable.Empty(); //this should never happen + + var langs = context.MapEnumerable(allLanguages).ToList(); + + // All segments, including the unsegmented (= NULL) segment. + // TODO: The NULl segment might have to be changed to be empty string? + var segments = source.Properties + .SelectMany(p => p.Values.Select(v => v.Segment)) + .Distinct(); + + // Add all variants + foreach (var language in langs) + { + foreach (var segment in segments) + { + context.SetCulture(language.IsoCode); + context.SetSegment(segment); + + var variantDisplay = context.Map(source); + + variantDisplay.Language = language; + variantDisplay.Segment = segment; + variantDisplay.Name = source.GetCultureName(language.IsoCode); + + variants.Add(variantDisplay); + } + } + + // TODO: Sorting + } + + return variants; } } } diff --git a/src/Umbraco.Web/Models/Mapping/MapperContextExtensions.cs b/src/Umbraco.Web/Models/Mapping/MapperContextExtensions.cs index 1538f1a987..20a387c679 100644 --- a/src/Umbraco.Web/Models/Mapping/MapperContextExtensions.cs +++ b/src/Umbraco.Web/Models/Mapping/MapperContextExtensions.cs @@ -8,6 +8,7 @@ namespace Umbraco.Web.Models.Mapping internal static class MapperContextExtensions { private const string CultureKey = "Map.Culture"; + private const string SegmentKey = "Map.Segment"; private const string IncludedPropertiesKey = "Map.IncludedProperties"; /// @@ -18,6 +19,14 @@ namespace Umbraco.Web.Models.Mapping return context.HasItems && context.Items.TryGetValue(CultureKey, out var obj) && obj is string s ? s : null; } + /// + /// Gets the context segment. + /// + public static string GetSegment(this MapperContext context) + { + return context.HasItems && context.Items.TryGetValue(SegmentKey, out var obj) && obj is string s ? s : null; + } + /// /// Sets a context culture. /// @@ -26,6 +35,14 @@ namespace Umbraco.Web.Models.Mapping context.Items[CultureKey] = culture; } + /// + /// Sets a context segment. + /// + public static void SetSegment(this MapperContext context, string segment) + { + context.Items[SegmentKey] = segment; + } + /// /// Get included properties. /// @@ -42,4 +59,4 @@ namespace Umbraco.Web.Models.Mapping context.Items[IncludedPropertiesKey] = properties; } } -} \ No newline at end of file +} From 8f0ee6d71154bad2f389bc5aa6a04f19ad183045 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20Knippers?= Date: Thu, 29 Aug 2019 14:38:46 +0200 Subject: [PATCH 024/548] Added support for all 4 cases in ContentVariantMapper --- .../Models/Mapping/ContentVariantMapper.cs | 155 ++++++++++-------- 1 file changed, 83 insertions(+), 72 deletions(-) diff --git a/src/Umbraco.Web/Models/Mapping/ContentVariantMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentVariantMapper.cs index 848eaa0594..d16f244a0a 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentVariantMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentVariantMapper.cs @@ -21,99 +21,110 @@ namespace Umbraco.Web.Models.Mapping public IEnumerable Map(IContent source, MapperContext context) { - var variants = new List(); - var variesByCulture = source.ContentType.VariesByCulture(); var variesBySegment = source.ContentType.VariesBySegment(); + IList variants = new List(); + if (!variesByCulture && !variesBySegment) { - //this is invariant so just map the IContent instance to ContentVariationDisplay - variants.Add(context.Map(source)); - return variants; + // this is invariant so just map the IContent instance to ContentVariationDisplay + var variantDisplay = context.Map(source); + variants.Add(variantDisplay); } - - if (variesByCulture && !variesBySegment) + else if (variesByCulture && !variesBySegment) { - // Culture only - - var allLanguages = _localizationService.GetAllLanguages().OrderBy(x => x.Id).ToList(); - if (allLanguages.Count == 0) return Enumerable.Empty(); //this should never happen - - var langs = context.MapEnumerable(allLanguages).ToList(); - - //create a variant for each language, then we'll populate the values - variants.AddRange(langs.Select(x => - { - //We need to set the culture in the mapping context since this is needed to ensure that the correct property values - //are resolved during the mapping - context.SetCulture(x.IsoCode); - return context.Map(source); - })); - - for (int i = 0; i < langs.Count; i++) - { - var x = langs[i]; - var variant = variants[i]; - - variant.Language = x; - variant.Name = source.GetCultureName(x.IsoCode); - } - - //Put the default language first in the list & then sort rest by a-z - var defaultLang = variants.SingleOrDefault(x => x.Language.IsDefault); - - //Remove the default language from the list for now - variants.Remove(defaultLang); - - //Sort the remaining languages a-z - variants = variants.OrderBy(x => x.Language.Name).ToList(); - - //Insert the default language as the first item - variants.Insert(0, defaultLang); + var languages = GetLanguages(context); + variants = languages + .Select(language => CreateVariantDisplay(context, source, language, null)) + .ToList(); } else if (variesBySegment && !variesByCulture) { // Segment only - throw new NotSupportedException("ContentVariantMapper not implemented for segment only!"); + var segments = GetSegments(source); + variants = segments + .Select(segment => CreateVariantDisplay(context, source, null, segment)) + .ToList(); } else { // Culture and segment + var languages = GetLanguages(context).ToList(); + var segments = GetSegments(source).ToList(); - var allLanguages = _localizationService.GetAllLanguages().OrderBy(x => x.Id).ToList(); - if (allLanguages.Count == 0) return Enumerable.Empty(); //this should never happen - - var langs = context.MapEnumerable(allLanguages).ToList(); - - // All segments, including the unsegmented (= NULL) segment. - // TODO: The NULl segment might have to be changed to be empty string? - var segments = source.Properties - .SelectMany(p => p.Values.Select(v => v.Segment)) - .Distinct(); - - // Add all variants - foreach (var language in langs) + if (languages.Count == 0 || segments.Count == 0) { - foreach (var segment in segments) - { - context.SetCulture(language.IsoCode); - context.SetSegment(segment); - - var variantDisplay = context.Map(source); - - variantDisplay.Language = language; - variantDisplay.Segment = segment; - variantDisplay.Name = source.GetCultureName(language.IsoCode); - - variants.Add(variantDisplay); - } + // This should not happen + throw new ArgumentException("No languages or segments available"); } - // TODO: Sorting + variants = languages + .SelectMany(language => segments + .Select(segment => CreateVariantDisplay(context, source, language, segment))) + .ToList(); } - return variants; + return SortVariants(variants); + } + + private IList SortVariants(IList variants) + { + if (variants == null || variants.Count <= 1) + { + return variants; + } + + // Default variant first, then order by language, segment. + return variants + .OrderBy(v => IsDefaultLanguage(v) ? 0 : 1) + .ThenBy(v => IsDefaultSegment(v) ? 0 : 1) + .ThenBy(v => v?.Language?.Name) + .ThenBy(v => v.Segment) + .ToList(); + } + + private static bool IsDefaultSegment(ContentVariantDisplay variant) + { + return variant.Segment == null; + } + + private static bool IsDefaultLanguage(ContentVariantDisplay variant) + { + return variant.Language == null || variant.Language.IsDefault; + } + + private IEnumerable GetLanguages(MapperContext context) + { + var allLanguages = _localizationService.GetAllLanguages().OrderBy(x => x.Id).ToList(); + if (allLanguages.Count == 0) + { + // This should never happen + return Enumerable.Empty(); + } + else + { + return context.MapEnumerable(allLanguages).ToList(); + } + } + + private IEnumerable GetSegments(IContent content) + { + return content.Properties.SelectMany(p => p.Values.Select(v => v.Segment)).Distinct(); + } + + private ContentVariantDisplay CreateVariantDisplay(MapperContext context, IContent content, Language language, string segment) + { + context.SetCulture(language?.IsoCode); + context.SetSegment(segment); + + var variantDisplay = context.Map(content); + + variantDisplay.Segment = segment; + variantDisplay.Language = language; + variantDisplay.Name = content.GetCultureName(language?.IsoCode); + + return variantDisplay; } } } From 813998a614c89dbcd9d11e6019aad535cd18009e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20Knippers?= Date: Fri, 11 Oct 2019 14:13:49 +0200 Subject: [PATCH 025/548] Support for saving edited segment values --- .../Editors/Binders/ContentItemBinder.cs | 9 +++++--- src/Umbraco.Web/Editors/ContentController.cs | 23 +++++++++++++++---- .../ContentEditing/ContentVariantSave.cs | 6 +++++ 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Web/Editors/Binders/ContentItemBinder.cs b/src/Umbraco.Web/Editors/Binders/ContentItemBinder.cs index 11a876345d..d9d56c584f 100644 --- a/src/Umbraco.Web/Editors/Binders/ContentItemBinder.cs +++ b/src/Umbraco.Web/Editors/Binders/ContentItemBinder.cs @@ -49,7 +49,7 @@ namespace Umbraco.Web.Editors.Binders { foreach (var variant in model.Variants) { - if (variant.Culture.IsNullOrWhiteSpace()) + if (variant.Culture.IsNullOrWhiteSpace() && variant.Segment.IsNullOrWhiteSpace()) { //map the property dto collection (no culture is passed to the mapping context so it will be invariant) variant.PropertyCollectionDto = Current.Mapper.Map(model.PersistedContent); @@ -59,7 +59,11 @@ namespace Umbraco.Web.Editors.Binders //map the property dto collection with the culture of the current variant variant.PropertyCollectionDto = Current.Mapper.Map( model.PersistedContent, - context => context.SetCulture(variant.Culture)); + context => + { + context.SetCulture(variant.Culture); + context.SetSegment(variant.Segment); + }); } //now map all of the saved values to the dto @@ -87,6 +91,5 @@ namespace Umbraco.Web.Editors.Binders model.ParentId, contentType); } - } } diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 9d5af028e3..bc2b55833e 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -1836,8 +1836,13 @@ namespace Umbraco.Web.Editors /// private void MapValuesForPersistence(ContentItemSave contentSave) { - // inline method to determine if a property type varies - bool Varies(Property property) => property.PropertyType.VariesByCulture(); + // inline method to determine the culture and segment to persist the property + (string culture, string segment) PropertyCultureAndSegment(Property property, ContentVariantSave variant) + { + var culture = property.PropertyType.VariesByCulture() ? variant.Culture : null; + var segment = property.PropertyType.VariesBySegment() ? variant.Segment : null; + return (culture, segment); + } var variantIndex = 0; @@ -1876,8 +1881,18 @@ namespace Umbraco.Web.Editors MapPropertyValuesForPersistence( contentSave, propertyCollection, - (save, property) => Varies(property) ? property.GetValue(variant.Culture) : property.GetValue(), //get prop val - (save, property, v) => { if (Varies(property)) property.SetValue(v, variant.Culture); else property.SetValue(v); }, //set prop val + (save, property) => + { + // Get property value + (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); + }, variant.Culture); variantIndex++; diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentVariantSave.cs b/src/Umbraco.Web/Models/ContentEditing/ContentVariantSave.cs index deadac949a..9a7555ad92 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentVariantSave.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentVariantSave.cs @@ -29,6 +29,12 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "culture")] public string Culture { get; set; } + /// + /// The segment of this variant, if this is invariant than this is null or empty + /// + [DataMember(Name = "segment")] + public string Segment { get; set; } + /// /// Indicates if the variant should be updated /// From 1da29b29b90145c389e9b1569386d62e743ddcb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20Knippers?= Date: Fri, 30 Aug 2019 11:37:44 +0200 Subject: [PATCH 026/548] Only set segment on property when property can be varied by segment --- .../Models/Mapping/ContentPropertyBasicMapper.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicMapper.cs index aacafd2f2a..cf1bc3c253 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicMapper.cs @@ -70,10 +70,9 @@ namespace Umbraco.Web.Models.Mapping dest.Culture = culture; - var segment = context.GetSegment(); - - // The current segment can always be null, even if the propertyType *can* be varied by segment - // so the nullcheck like with culture is not performed here. + // Get the segment, which is always allowed to be null even if the propertyType *can* be varied by segment. + // There is therefore no need to perform the null check like with culture above. + var segment = !property.PropertyType.VariesBySegment() ? null : context.GetSegment(); dest.Segment = segment; // if no 'IncludeProperties' were specified or this property is set to be included - we will map the value and return. From f6e7740c931ca8d20c287f32871486926ca5f53b Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Fri, 11 Oct 2019 15:07:32 +0200 Subject: [PATCH 027/548] Use all assigned groups and explicit permission assignments when calculating available actions for a given node --- src/Umbraco.Core/Services/UserServiceExtensions.cs | 12 ++++++++++++ src/Umbraco.Web/Actions/ActionCollection.cs | 10 ---------- src/Umbraco.Web/Editors/ContentController.cs | 4 ++-- src/Umbraco.Web/Trees/ContentTreeController.cs | 4 ++-- src/Umbraco.Web/Trees/ContentTreeControllerBase.cs | 4 ++-- 5 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/Umbraco.Core/Services/UserServiceExtensions.cs b/src/Umbraco.Core/Services/UserServiceExtensions.cs index 31c446352e..82cab07b25 100644 --- a/src/Umbraco.Core/Services/UserServiceExtensions.cs +++ b/src/Umbraco.Core/Services/UserServiceExtensions.cs @@ -104,5 +104,17 @@ namespace Umbraco.Core.Services return found; } + + /// + /// Gets the concrete assigned permissions for the provided user and node + /// + /// + /// + /// + internal static string[] GetAssignedPermissions(this IUserService userService, IUser user, int nodeId) + { + var permissionCollection = userService.GetPermissions(user, nodeId); + return permissionCollection.SelectMany(c => c.AssignedPermissions).Distinct().ToArray(); + } } } diff --git a/src/Umbraco.Web/Actions/ActionCollection.cs b/src/Umbraco.Web/Actions/ActionCollection.cs index 89ac8a59f4..95d7b02e90 100644 --- a/src/Umbraco.Web/Actions/ActionCollection.cs +++ b/src/Umbraco.Web/Actions/ActionCollection.cs @@ -28,15 +28,5 @@ namespace Umbraco.Web.Actions .WhereNotNull() .ToList(); } - - internal IReadOnlyList FromEntityPermission(EntityPermission entityPermission) - { - var actions = this.ToArray(); // no worry: internally, it's already an array - return entityPermission.AssignedPermissions - .Where(x => x.Length == 1) - .SelectMany(x => actions.Where(y => y.Letter == x[0])) - .WhereNotNull() - .ToList(); - } } } diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 9d5af028e3..2aa0383a36 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -1679,9 +1679,9 @@ namespace Umbraco.Web.Editors throw new HttpResponseException(response); } - var permission = Services.UserService.GetPermissions(Security.CurrentUser, node.Path); + var assignedPermissions = Services.UserService.GetAssignedPermissions(Security.CurrentUser, node.Id); - if (permission.AssignedPermissions.Contains(ActionAssignDomain.ActionLetter.ToString(), StringComparer.Ordinal) == false) + if (assignedPermissions.Contains(ActionAssignDomain.ActionLetter.ToString(), StringComparer.Ordinal) == false) { var response = Request.CreateResponse(HttpStatusCode.BadRequest); response.Content = new StringContent("You do not have permission to assign domains on that node."); diff --git a/src/Umbraco.Web/Trees/ContentTreeController.cs b/src/Umbraco.Web/Trees/ContentTreeController.cs index 970191e510..9c5333ae2a 100644 --- a/src/Umbraco.Web/Trees/ContentTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTreeController.cs @@ -124,8 +124,8 @@ namespace Umbraco.Web.Trees menu.DefaultMenuAlias = ActionNew.ActionAlias; // we need to get the default permissions as you can't set permissions on the very root node - var permission = Services.UserService.GetPermissions(Security.CurrentUser, Constants.System.Root).First(); - var nodeActions = _actions.FromEntityPermission(permission) + var assignedPermissions = Services.UserService.GetAssignedPermissions(Security.CurrentUser, Constants.System.Root); + var nodeActions = _actions.GetByLetters(assignedPermissions) .Select(x => new MenuItem(x)); //these two are the standard items diff --git a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs index 015c91cb81..3e06eb975c 100644 --- a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs @@ -442,8 +442,8 @@ namespace Umbraco.Web.Trees internal IEnumerable GetAllowedUserMenuItemsForNode(IUmbracoEntity dd) { - var permission = Services.UserService.GetPermissions(Security.CurrentUser, dd.Path); - return Current.Actions.FromEntityPermission(permission).Select(x => new MenuItem(x)); + var assignedPermissions = Services.UserService.GetAssignedPermissions(Security.CurrentUser, dd.Id); + return Current.Actions.GetByLetters(assignedPermissions).Select(x => new MenuItem(x)); } /// From 1a92dbfd8fee30689822a09a57a2365d40c1bc4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20Knippers?= Date: Tue, 15 Oct 2019 09:28:25 +0200 Subject: [PATCH 028/548] Properly set / unset Culture flag instead of removing _all_ flags when "Vary by Culture" is false. --- .../Models/Mapping/ContentTypeMapDefinition.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs index 528d5f6de5..9b8936a43c 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs @@ -223,7 +223,11 @@ namespace Umbraco.Web.Models.Mapping target.DataTypeKey = source.DataTypeKey; target.Mandatory = source.Validation.Mandatory; target.ValidationRegExp = source.Validation.Pattern; - target.Variations = source.AllowCultureVariant ? ContentVariation.Culture : ContentVariation.Nothing; + target.Variations = source.AllowCultureVariant + // Set culture flag + ? target.Variations | ContentVariation.Culture + // Unset culture flag + : target.Variations & ~ContentVariation.Culture; if (source.Id > 0) target.Id = source.Id; @@ -395,9 +399,11 @@ namespace Umbraco.Web.Models.Mapping if (!(target is IMemberType)) { - target.Variations = ContentVariation.Nothing; - if (source.AllowCultureVariant) - target.Variations |= ContentVariation.Culture; + target.Variations = source.AllowCultureVariant + // Set culture flag + ? target.Variations | ContentVariation.Culture + // Unset culture flag + : target.Variations & ~ContentVariation.Culture; } // handle property groups and property types From 10a2303458519fff6162d667cd14927f3a9ef79a Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 18 Oct 2019 10:19:11 +1100 Subject: [PATCH 029/548] Renames AsyncLock -> SystemLock --- src/Umbraco.Core/MainDom.cs | 4 +-- .../{AsyncLock.cs => SystemLock.cs} | 35 ++----------------- .../SafeXmlReaderWriter.cs | 2 +- .../LegacyXmlPublishedCache/XmlStore.cs | 2 +- .../XmlStoreFilePersister.cs | 2 +- 5 files changed, 8 insertions(+), 37 deletions(-) rename src/Umbraco.Core/{AsyncLock.cs => SystemLock.cs} (80%) diff --git a/src/Umbraco.Core/MainDom.cs b/src/Umbraco.Core/MainDom.cs index d1012fb669..613abd6946 100644 --- a/src/Umbraco.Core/MainDom.cs +++ b/src/Umbraco.Core/MainDom.cs @@ -25,7 +25,7 @@ namespace Umbraco.Core private readonly object _locko = new object(); // async lock representing the main domain lock - private readonly AsyncLock _asyncLock; + private readonly SystemLock _asyncLock; private IDisposable _asyncLocker; // event wait handle used to notify current main domain that it should @@ -68,7 +68,7 @@ namespace Umbraco.Core var hash = (appId + ":::" + appPath).ToSHA1(); var lockName = "UMBRACO-" + hash + "-MAINDOM-LCK"; - _asyncLock = new AsyncLock(lockName); + _asyncLock = new SystemLock(lockName); var eventName = "UMBRACO-" + hash + "-MAINDOM-EVT"; _signal = new EventWaitHandle(false, EventResetMode.AutoReset, eventName); diff --git a/src/Umbraco.Core/AsyncLock.cs b/src/Umbraco.Core/SystemLock.cs similarity index 80% rename from src/Umbraco.Core/AsyncLock.cs rename to src/Umbraco.Core/SystemLock.cs index 6dd866705e..e637c7b08f 100644 --- a/src/Umbraco.Core/AsyncLock.cs +++ b/src/Umbraco.Core/SystemLock.cs @@ -21,18 +21,18 @@ namespace Umbraco.Core // been closed, the Semaphore system object is destroyed - so in any case // an iisreset should clean up everything // - internal class AsyncLock + internal class SystemLock { private readonly SemaphoreSlim _semaphore; private readonly Semaphore _semaphore2; private readonly IDisposable _releaser; private readonly Task _releaserTask; - public AsyncLock() + public SystemLock() : this (null) { } - public AsyncLock(string name) + public SystemLock(string name) { // WaitOne() waits until count > 0 then decrements count // Release() increments count @@ -67,35 +67,6 @@ namespace Umbraco.Core : new NamedSemaphoreReleaser(_semaphore2); } - //NOTE: We don't use the "Async" part of this lock at all - //TODO: Remove this and rename this class something like SystemWideLock, then we can re-instate this logic if we ever need an Async lock again - - //public Task LockAsync() - //{ - // var wait = _semaphore != null - // ? _semaphore.WaitAsync() - // : _semaphore2.WaitOneAsync(); - - // return wait.IsCompleted - // ? _releaserTask ?? Task.FromResult(CreateReleaser()) // anonymous vs named - // : wait.ContinueWith((_, state) => (((AsyncLock) state).CreateReleaser()), - // this, CancellationToken.None, - // TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); - //} - - //public Task LockAsync(int millisecondsTimeout) - //{ - // var wait = _semaphore != null - // ? _semaphore.WaitAsync(millisecondsTimeout) - // : _semaphore2.WaitOneAsync(millisecondsTimeout); - - // return wait.IsCompleted - // ? _releaserTask ?? Task.FromResult(CreateReleaser()) // anonymous vs named - // : wait.ContinueWith((_, state) => (((AsyncLock)state).CreateReleaser()), - // this, CancellationToken.None, - // TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); - //} - public IDisposable Lock() { if (_semaphore != null) diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/SafeXmlReaderWriter.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/SafeXmlReaderWriter.cs index c0b9383b57..aa88f28dc0 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/SafeXmlReaderWriter.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/SafeXmlReaderWriter.cs @@ -39,7 +39,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache return scopeProvider?.Context?.GetEnlisted(EnlistKey); } - public static SafeXmlReaderWriter Get(IScopeProvider scopeProvider, AsyncLock xmlLock, XmlDocument xml, Action refresh, Action apply, bool writer) + public static SafeXmlReaderWriter Get(IScopeProvider scopeProvider, SystemLock xmlLock, XmlDocument xml, Action refresh, Action apply, bool writer) { var scopeContext = scopeProvider.Context; diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlStore.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlStore.cs index 3b675c2f07..01f1ce264c 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlStore.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlStore.cs @@ -305,7 +305,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache private XmlDocument _xmlDocument; // supplied xml document (for tests) private volatile XmlDocument _xml; // master xml document - private readonly AsyncLock _xmlLock = new AsyncLock(); // protects _xml + private readonly SystemLock _xmlLock = new SystemLock(); // protects _xml // to be used by PublishedContentCache only // for non-preview content only diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlStoreFilePersister.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlStoreFilePersister.cs index 145a19872a..56c09b18ac 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlStoreFilePersister.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlStoreFilePersister.cs @@ -24,7 +24,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache private bool _released; private Timer _timer; private DateTime _initialTouch; - private readonly AsyncLock _runLock = new AsyncLock(); // ensure we run once at a time + private readonly SystemLock _runLock = new SystemLock(); // ensure we run once at a time // note: // as long as the runner controls the runs, we know that we run once at a time, but From 81ae83556493e8f4dc66fc08a40a3e62295b6582 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 18 Oct 2019 10:30:58 +1100 Subject: [PATCH 030/548] Implements Disposable pattern correctly for the NamedSemaphoreReleaser --- src/Umbraco.Core/SystemLock.cs | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Core/SystemLock.cs b/src/Umbraco.Core/SystemLock.cs index e637c7b08f..a04eb2f980 100644 --- a/src/Umbraco.Core/SystemLock.cs +++ b/src/Umbraco.Core/SystemLock.cs @@ -100,6 +100,12 @@ namespace Umbraco.Core _handle = GCHandle.Alloc(_semaphore); } + #region IDisposable Support + + // This code added to correctly implement the disposable pattern. + + private bool disposedValue = false; // To detect redundant calls + public void Dispose() { Dispose(true); @@ -108,12 +114,21 @@ namespace Umbraco.Core private void Dispose(bool disposing) { - // critical - _handle.Free(); - _semaphore.Release(); - _semaphore.Dispose(); - } + if (!disposedValue) + { + if (disposing) + { + _semaphore.Release(); + _semaphore.Dispose(); + } + // free unmanaged resources (unmanaged objects) and override a finalizer below. + _handle.Free(); + + disposedValue = true; + } + } + // we WANT to release the semaphore because it's a system object, ie a critical // non-managed resource - and if it is not released then noone else can acquire // the lock - so we inherit from CriticalFinalizerObject which means that the @@ -142,6 +157,9 @@ namespace Umbraco.Core // we do NOT want the finalizer to throw - never ever } } + + #endregion + } private class SemaphoreSlimReleaser : IDisposable From b5f29f2390fa346a8edc098d5e42b0bef4ce89fe Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 18 Oct 2019 11:08:58 +1100 Subject: [PATCH 031/548] Ensure the EventWaitHandle is cleaned up, MainDom and BackgroundTaskRunner was not using HostingEnvironment.Stop correctly, ensures no exception is thrown when setting task result on a background task, ensure multiple terminations don't occur, ensure terminations are done if shutdown fails. --- src/Umbraco.Core/MainDom.cs | 41 ++++++++++++++++--- src/Umbraco.Core/SystemLock.cs | 2 + src/Umbraco.Core/Umbraco.Core.csproj | 2 +- .../Scheduling/BackgroundTaskRunner.cs | 41 ++++++++++++++----- 4 files changed, 68 insertions(+), 18 deletions(-) diff --git a/src/Umbraco.Core/MainDom.cs b/src/Umbraco.Core/MainDom.cs index 613abd6946..41ed8eb242 100644 --- a/src/Umbraco.Core/MainDom.cs +++ b/src/Umbraco.Core/MainDom.cs @@ -15,7 +15,7 @@ namespace Umbraco.Core /// When an AppDomain starts, it tries to acquire the main domain status. /// When an AppDomain stops (eg the application is restarting) it should release the main domain status. /// - internal class MainDom : IMainDom, IRegisteredObject + internal class MainDom : IMainDom, IRegisteredObject, IDisposable { #region Vars @@ -204,14 +204,43 @@ namespace Umbraco.Core // IRegisteredObject void IRegisteredObject.Stop(bool immediate) { - try - { - OnSignal("environment"); // will run once - } - finally + OnSignal("environment"); // will run once + + if (immediate) { + //only unregister when it's the final call, else we won't be notified of the final call HostingEnvironment.UnregisterObject(this); + + // The web app is stopping immediately, dispose eagerly + Dispose(true); } } + + #region IDisposable Support + + // This code added to correctly implement the disposable pattern. + + private bool disposedValue = false; // To detect redundant calls + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + _signal?.Close(); + _signal?.Dispose(); + } + + disposedValue = true; + } + } + + public void Dispose() + { + Dispose(true); + } + + #endregion } } diff --git a/src/Umbraco.Core/SystemLock.cs b/src/Umbraco.Core/SystemLock.cs index a04eb2f980..04150d58c1 100644 --- a/src/Umbraco.Core/SystemLock.cs +++ b/src/Umbraco.Core/SystemLock.cs @@ -120,6 +120,8 @@ namespace Umbraco.Core { _semaphore.Release(); _semaphore.Dispose(); + + } // free unmanaged resources (unmanaged objects) and override a finalizer below. diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index c19722433f..2b9631c747 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -122,7 +122,7 @@ --> - + diff --git a/src/Umbraco.Web/Scheduling/BackgroundTaskRunner.cs b/src/Umbraco.Web/Scheduling/BackgroundTaskRunner.cs index 47e97593f0..dab6a8865e 100644 --- a/src/Umbraco.Web/Scheduling/BackgroundTaskRunner.cs +++ b/src/Umbraco.Web/Scheduling/BackgroundTaskRunner.cs @@ -700,16 +700,23 @@ namespace Umbraco.Web.Scheduling // processing asynchronously before calling the UnregisterObject method. _logger.Info("{LogPrefix} Waiting for tasks to complete", _logPrefix); - Shutdown(false, false); // do not accept any more tasks, flush the queue, do not wait - // raise the completed event only after the running threading task has completed - lock (_locker) + try { - if (_runningTask != null) - _runningTask.ContinueWith(_ => Terminate(false)); - else - Terminate(false); + Shutdown(false, false); // do not accept any more tasks, flush the queue, do not wait } + finally + { + // raise the completed event only after the running threading task has completed + lock (_locker) + { + if (_runningTask != null) + _runningTask.ContinueWith(_ => Terminate(false)); + else + Terminate(false); + } + } + } else { @@ -719,8 +726,14 @@ namespace Umbraco.Web.Scheduling // otherwise, its registration will be removed by the application manager. _logger.Info("{LogPrefix} Canceling tasks", _logPrefix); - Shutdown(true, true); // cancel all tasks, wait for the current one to end - Terminate(true); + try + { + Shutdown(true, true); // cancel all tasks, wait for the current one to end + } + finally + { + Terminate(true); + } } } @@ -732,7 +745,13 @@ namespace Umbraco.Web.Scheduling // raise the Terminated event // complete the awaitable completion source, if any - HostingEnvironment.UnregisterObject(this); + if (immediate) + { + //only unregister when it's the final call, else we won't be notified of the final call + HostingEnvironment.UnregisterObject(this); + } + + if (_terminated) return; // already taken care of TaskCompletionSource terminatedSource; lock (_locker) @@ -747,7 +766,7 @@ namespace Umbraco.Web.Scheduling OnEvent(Terminated, "Terminated"); - terminatedSource.SetResult(0); + terminatedSource.TrySetResult(0); } } } From 44ed611b7a3f5bc2542df9823a572cb8a9914391 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 18 Oct 2019 11:45:09 +1100 Subject: [PATCH 032/548] Ensure MainDom is always registered with HostingEnvironment, not just when it acquires maindom --- src/Umbraco.Core/MainDom.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Core/MainDom.cs b/src/Umbraco.Core/MainDom.cs index 41ed8eb242..10096567f3 100644 --- a/src/Umbraco.Core/MainDom.cs +++ b/src/Umbraco.Core/MainDom.cs @@ -25,8 +25,8 @@ namespace Umbraco.Core private readonly object _locko = new object(); // async lock representing the main domain lock - private readonly SystemLock _asyncLock; - private IDisposable _asyncLocker; + 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 @@ -48,6 +48,8 @@ namespace Umbraco.Core // initializes a new instance of MainDom public MainDom(ILogger logger) { + HostingEnvironment.RegisterObject(this); + _logger = logger; var appId = string.Empty; @@ -68,7 +70,7 @@ namespace Umbraco.Core var hash = (appId + ":::" + appPath).ToSHA1(); var lockName = "UMBRACO-" + hash + "-MAINDOM-LCK"; - _asyncLock = new SystemLock(lockName); + _systemLock = new SystemLock(lockName); var eventName = "UMBRACO-" + hash + "-MAINDOM-EVT"; _signal = new EventWaitHandle(false, EventResetMode.AutoReset, eventName); @@ -142,7 +144,7 @@ namespace Umbraco.Core { // in any case... _isMainDom = false; - _asyncLocker.Dispose(); + _systemLocker?.Dispose(); _logger.Info("Released ({SignalSource})", source); } } @@ -173,7 +175,7 @@ namespace Umbraco.Core // and the other one will timeout, which is accepted //TODO: This can throw a TimeoutException - in which case should this be in a try/finally to ensure the signal is always reset? - _asyncLocker = _asyncLock.Lock(LockTimeoutMilliseconds); + _systemLocker = _systemLock.Lock(LockTimeoutMilliseconds); _isMainDom = true; // we need to reset the event, because otherwise we would end up @@ -189,8 +191,6 @@ namespace Umbraco.Core _signal.WaitOneAsync() .ContinueWith(_ => OnSignal("signal")); - HostingEnvironment.RegisterObject(this); - _logger.Info("Acquired."); return true; } From beb16a2f79b847dcc2cb6de5752d7e56b2ede3ce Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 21 Oct 2019 15:47:37 +1100 Subject: [PATCH 033/548] Ensures MainDom.Register absolutely does not call install logic if it's not MainDom, adds logging --- src/Umbraco.Core/MainDom.cs | 6 ++++++ .../PublishedCache/NuCache/PublishedSnapshotService.cs | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/src/Umbraco.Core/MainDom.cs b/src/Umbraco.Core/MainDom.cs index 10096567f3..e4beafc110 100644 --- a/src/Umbraco.Core/MainDom.cs +++ b/src/Umbraco.Core/MainDom.cs @@ -101,6 +101,12 @@ namespace Umbraco.Core lock (_locko) { if (_signaled) return false; + if (_isMainDom == false) + { + _logger.Warn("Register called when MainDom has not been acquired"); + return false; + } + install?.Invoke(); if (release != null) _callbacks.Add(new KeyValuePair(weight, release)); diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs index 6e7916c77f..3fcf61a7bc 100755 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs @@ -132,6 +132,8 @@ namespace Umbraco.Web.PublishedCache.NuCache // if both local databases exist then GetTree will open them, else new databases will be created _localContentDb = BTree.GetTree(localContentDbPath, _localDbExists); _localMediaDb = BTree.GetTree(localMediaDbPath, _localDbExists); + + _logger.Info($"Registered with MainDom, local db exists? {_localDbExists}"); }, () => { @@ -144,6 +146,8 @@ namespace Umbraco.Web.PublishedCache.NuCache _mediaStore?.ReleaseLocalDb(); //null check because we could shut down before being assigned _localMediaDb = null; } + + _logger.Info("Released from MainDom"); }); // stores are created with a db so they can write to it, but they do not read from it, From eb670fd1ac91c8d1074a2897cf0c253783dfe0e8 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 21 Oct 2019 15:59:25 +1100 Subject: [PATCH 034/548] ensure OnSignal logic is all run within the lock, ensures all actions are run even if one throw on shutdown. --- src/Umbraco.Core/MainDom.cs | 46 ++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/Umbraco.Core/MainDom.cs b/src/Umbraco.Core/MainDom.cs index e4beafc110..f2505c3f78 100644 --- a/src/Umbraco.Core/MainDom.cs +++ b/src/Umbraco.Core/MainDom.cs @@ -126,32 +126,32 @@ namespace Umbraco.Core if (_signaled) return; if (_isMainDom == false) return; // probably not needed _signaled = true; - } - try - { - _logger.Info("Stopping ({SignalSource})", source); - foreach (var callback in _callbacks.OrderBy(x => x.Key).Select(x => x.Value)) + try { - try + _logger.Info("Stopping ({SignalSource})", source); + foreach (var callback in _callbacks.OrderBy(x => x.Key).Select(x => x.Value)) { - callback(); // no timeout on callbacks + try + { + callback(); // no timeout on callbacks + } + catch (Exception e) + { + _logger.Error(e, "Error while running callback"); + continue; + } } - catch (Exception e) - { - _logger.Error(e, "Error while running callback, remaining callbacks will not run."); - throw; - } - + _logger.Debug("Stopped ({SignalSource})", source); } - _logger.Debug("Stopped ({SignalSource})", source); - } - finally - { - // in any case... - _isMainDom = false; - _systemLocker?.Dispose(); - _logger.Info("Released ({SignalSource})", source); + finally + { + // in any case... + _isMainDom = false; + _systemLocker?.Dispose(); + _logger.Info("Released ({SignalSource})", source); + } + } } @@ -211,7 +211,7 @@ namespace Umbraco.Core void IRegisteredObject.Stop(bool immediate) { OnSignal("environment"); // will run once - + if (immediate) { //only unregister when it's the final call, else we won't be notified of the final call @@ -241,7 +241,7 @@ namespace Umbraco.Core disposedValue = true; } } - + public void Dispose() { Dispose(true); From 59ba68dfd0767635290cc1ed51bd1fe52a57d471 Mon Sep 17 00:00:00 2001 From: Steve Megson Date: Thu, 29 Aug 2019 20:49:32 +0100 Subject: [PATCH 035/548] TinyMCE language files --- .../lib/tinymce/langs/af_ZA.js | 230 +++++++++++ .../lib/tinymce/langs/ar.js | 262 ++++++++++++ .../lib/tinymce/langs/az.js | 261 ++++++++++++ .../lib/tinymce/langs/be.js | 261 ++++++++++++ .../lib/tinymce/langs/bg_BG.js | 261 ++++++++++++ .../lib/tinymce/langs/bn_BD.js | 261 ++++++++++++ .../lib/tinymce/langs/ca.js | 261 ++++++++++++ .../lib/tinymce/langs/cs.js | 261 ++++++++++++ .../lib/tinymce/langs/cs_CZ.js | 260 ++++++++++++ .../lib/tinymce/langs/cy.js | 230 +++++++++++ .../lib/tinymce/langs/da.js | 344 ++++++++------- .../lib/tinymce/langs/de.js | 346 +++++++++------- .../lib/tinymce/langs/de_AT.js | 261 ++++++++++++ .../lib/tinymce/langs/dv.js | 230 +++++++++++ .../lib/tinymce/langs/el.js | 261 ++++++++++++ .../lib/tinymce/langs/en.js | 1 - .../lib/tinymce/langs/en_CA.js | 261 ++++++++++++ .../lib/tinymce/langs/en_GB.js | 261 ++++++++++++ .../lib/tinymce/langs/en_us.js | 1 - .../lib/tinymce/langs/es.js | 261 ++++++++++++ .../lib/tinymce/langs/es_MX.js | 261 ++++++++++++ .../lib/tinymce/langs/et.js | 261 ++++++++++++ .../lib/tinymce/langs/eu.js | 261 ++++++++++++ .../lib/tinymce/langs/fa_IR.js | 262 ++++++++++++ .../lib/tinymce/langs/fi.js | 344 ++++++++------- .../lib/tinymce/langs/fr.js | 390 +++++++++++++++++- .../lib/tinymce/langs/fr_FR.js | 261 ++++++++++++ .../lib/tinymce/langs/ga.js | 261 ++++++++++++ .../lib/tinymce/langs/gl.js | 253 ++++++++++++ .../lib/tinymce/langs/he.js | 1 - .../lib/tinymce/langs/he_IL.js | 262 ++++++++++++ .../lib/tinymce/langs/hr.js | 253 ++++++++++++ .../lib/tinymce/langs/hu_HU.js | 261 ++++++++++++ .../lib/tinymce/langs/id.js | 261 ++++++++++++ .../lib/tinymce/langs/it.js | 344 ++++++++------- .../lib/tinymce/langs/ja.js | 344 ++++++++------- .../lib/tinymce/langs/ka_GE.js | 230 +++++++++++ .../lib/tinymce/langs/kab.js | 261 ++++++++++++ .../lib/tinymce/langs/kk.js | 230 +++++++++++ .../lib/tinymce/langs/km_KH.js | 253 ++++++++++++ .../lib/tinymce/langs/ko_KR.js | 261 ++++++++++++ .../lib/tinymce/langs/lt.js | 261 ++++++++++++ .../lib/tinymce/langs/lv.js | 260 ++++++++++++ .../lib/tinymce/langs/nb_NO.js | 261 ++++++++++++ .../lib/tinymce/langs/nl.js | 344 ++++++++------- .../lib/tinymce/langs/no.js | 1 - .../lib/tinymce/langs/pl.js | 344 ++++++++------- .../lib/tinymce/langs/pt.js | 1 - .../lib/tinymce/langs/pt_BR.js | 261 ++++++++++++ .../lib/tinymce/langs/pt_PT.js | 261 ++++++++++++ .../lib/tinymce/langs/ro.js | 230 +++++++++++ .../lib/tinymce/langs/ru.js | 344 ++++++++------- .../lib/tinymce/langs/sk.js | 253 ++++++++++++ .../lib/tinymce/langs/sl_SI.js | 230 +++++++++++ .../lib/tinymce/langs/sr.js | 261 ++++++++++++ .../lib/tinymce/langs/sv.js | 1 - .../lib/tinymce/langs/sv_SE.js | 261 ++++++++++++ .../lib/tinymce/langs/ta.js | 261 ++++++++++++ .../lib/tinymce/langs/ta_IN.js | 261 ++++++++++++ .../lib/tinymce/langs/th_TH.js | 261 ++++++++++++ .../lib/tinymce/langs/tr.js | 261 ++++++++++++ .../lib/tinymce/langs/tr_TR.js | 261 ++++++++++++ .../lib/tinymce/langs/ug.js | 260 ++++++++++++ .../lib/tinymce/langs/uk.js | 261 ++++++++++++ .../lib/tinymce/langs/uk_UA.js | 261 ++++++++++++ .../lib/tinymce/langs/uz.js | 260 ++++++++++++ .../lib/tinymce/langs/vi_VN.js | 260 ++++++++++++ .../lib/tinymce/langs/zh.js | 1 - .../lib/tinymce/langs/zh_CN.js | 261 ++++++++++++ .../lib/tinymce/langs/zh_TW.js | 261 ++++++++++++ .../src/common/services/tinymce.service.js | 84 +++- 71 files changed, 15849 insertions(+), 1229 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/af_ZA.js create mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/ar.js create mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/az.js create mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/be.js create mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/bg_BG.js create mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/bn_BD.js create mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/ca.js create mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/cs.js create mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/cs_CZ.js create mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/cy.js create mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/de_AT.js create mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/dv.js create mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/el.js delete mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/en.js create mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/en_CA.js create mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/en_GB.js delete mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/en_us.js create mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/es.js create mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/es_MX.js create mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/et.js create mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/eu.js create mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/fa_IR.js create mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/fr_FR.js create mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/ga.js create mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/gl.js delete mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/he.js create mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/he_IL.js create mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/hr.js create mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/hu_HU.js create mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/id.js create mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/ka_GE.js create mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/kab.js create mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/kk.js create mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/km_KH.js create mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/ko_KR.js create mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/lt.js create mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/lv.js create mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/nb_NO.js delete mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/no.js delete mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/pt.js create mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/pt_BR.js create mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/pt_PT.js create mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/ro.js create mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/sk.js create mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/sl_SI.js create mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/sr.js delete mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/sv.js create mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/sv_SE.js create mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/ta.js create mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/ta_IN.js create mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/th_TH.js create mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/tr.js create mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/tr_TR.js create mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/ug.js create mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/uk.js create mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/uk_UA.js create mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/uz.js create mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/vi_VN.js delete mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/zh.js create mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/zh_CN.js create mode 100644 src/Umbraco.Web.UI.Client/lib/tinymce/langs/zh_TW.js 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/src/common/services/tinymce.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js index 33a15d74d3..705eaa2b16 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('_'); From 13f7ee8a5fff4c3e584cb8d4405e8ac12060ed8d Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 21 Oct 2019 19:40:06 +1100 Subject: [PATCH 036/548] Adds IDataValueReference --- .../PropertyEditors/IDataValueEditor.cs | 19 +++++++++++++++++++ .../PropertyEditors/IDataValueReference.cs | 17 +++++++++++++++++ src/Umbraco.Core/Umbraco.Core.csproj | 1 + 3 files changed, 37 insertions(+) create mode 100644 src/Umbraco.Core/PropertyEditors/IDataValueReference.cs 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..d7d848f1bf --- /dev/null +++ b/src/Umbraco.Core/PropertyEditors/IDataValueReference.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; + +namespace Umbraco.Core.PropertyEditors +{ + /// + /// Used to resolve references from values + /// + public interface IDataValueReference + { + /// + /// Returns any references contained in the value + /// + /// + /// + IEnumerable GetReferences(object value); + } +} diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 43d168f442..be1686df4f 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -270,6 +270,7 @@ + From a7dfba58d10c6f91a5a59ac7e1969ac1ea1abbfc Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 21 Oct 2019 22:49:39 +1100 Subject: [PATCH 037/548] Removes singleton accessors from repositories --- .../Repositories/Implement/ContentRepositoryBase.cs | 5 +++-- .../Implement/DocumentBlueprintRepository.cs | 5 +++-- .../Repositories/Implement/DocumentRepository.cs | 5 +++-- .../Repositories/Implement/MediaRepository.cs | 5 +++-- .../Repositories/Implement/MemberRepository.cs | 5 +++-- .../Repositories/ContentTypeRepositoryTest.cs | 3 ++- .../Persistence/Repositories/DocumentRepositoryTest.cs | 2 +- .../Persistence/Repositories/DomainRepositoryTest.cs | 5 +++-- .../Persistence/Repositories/MediaRepositoryTest.cs | 5 +++-- .../Persistence/Repositories/MemberRepositoryTest.cs | 3 ++- .../Repositories/PublicAccessRepositoryTest.cs | 5 +++-- .../Persistence/Repositories/TagRepositoryTest.cs | 7 ++++--- .../Persistence/Repositories/TemplateRepositoryTest.cs | 3 ++- .../Persistence/Repositories/UserRepositoryTest.cs | 5 +++-- .../Services/ContentServicePerformanceTest.cs | 9 +++++---- src/Umbraco.Tests/Services/ContentServiceTests.cs | 2 +- 16 files changed, 44 insertions(+), 30 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs index 7ab73f3f2d..a6ae2fc7e0 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs @@ -33,17 +33,18 @@ namespace Umbraco.Core.Persistence.Repositories.Implement where TEntity : class, IUmbracoEntity where TRepository : class, IRepository { - protected ContentRepositoryBase(IScopeAccessor scopeAccessor, AppCaches cache, ILanguageRepository languageRepository, ILogger logger) + protected ContentRepositoryBase(IScopeAccessor scopeAccessor, AppCaches cache, ILanguageRepository languageRepository, ILogger logger, PropertyEditorCollection propertyEditors) : base(scopeAccessor, cache, logger) { LanguageRepository = languageRepository; + PropertyEditors = propertyEditors; } protected abstract TRepository This { get; } protected ILanguageRepository LanguageRepository { get; } - protected PropertyEditorCollection PropertyEditors => Current.PropertyEditors; // TODO: inject + protected PropertyEditorCollection PropertyEditors { get; } #region Versions diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentBlueprintRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentBlueprintRepository.cs index d137d7ac76..c654e1a6c2 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,8 @@ 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, PropertyEditorCollection propertyEditors) + : base(scopeAccessor, appCaches, logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, propertyEditors) { } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs index 2649b9993f..eb392e86cb 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs @@ -12,6 +12,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; @@ -30,8 +31,8 @@ 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) + public DocumentRepository(IScopeAccessor scopeAccessor, AppCaches appCaches, ILogger logger, IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository, ILanguageRepository languageRepository, PropertyEditorCollection propertyEditors) + : base(scopeAccessor, appCaches, languageRepository, logger, propertyEditors) { _contentTypeRepository = contentTypeRepository ?? throw new ArgumentNullException(nameof(contentTypeRepository)); _templateRepository = templateRepository ?? throw new ArgumentNullException(nameof(templateRepository)); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs index 25828b8126..ed3eb589bd 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,8 @@ 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, PropertyEditorCollection propertyEditors) + : base(scopeAccessor, cache, languageRepository, logger, propertyEditors) { _mediaTypeRepository = mediaTypeRepository ?? throw new ArgumentNullException(nameof(mediaTypeRepository)); _tagRepository = tagRepository ?? throw new ArgumentNullException(nameof(tagRepository)); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs index 892122dff9..4aaa064976 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,8 @@ 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, PropertyEditorCollection propertyEditors) + : base(scopeAccessor, cache, languageRepository, logger, propertyEditors) { _memberTypeRepository = memberTypeRepository ?? throw new ArgumentNullException(nameof(memberTypeRepository)); _tagRepository = tagRepository ?? throw new ArgumentNullException(nameof(tagRepository)); diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs index f953b9cce6..b8b1b03fc4 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,7 @@ 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 repository = new DocumentRepository(scopeAccessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, Factory.GetInstance()); return repository; } diff --git a/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs index 4d62ec8301..424ab145bc 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs @@ -69,7 +69,7 @@ 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 repository = new DocumentRepository(scopeAccessor, appCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, Factory.GetInstance()); return repository; } diff --git a/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs index 628f8d75a7..65b5e8365e 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs @@ -3,9 +3,10 @@ using System.Data; using System.Linq; using Moq; using NUnit.Framework; -using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core; 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 +26,7 @@ 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); + documentRepository = new DocumentRepository(accessor, Core.Cache.AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, Factory.GetInstance()); var domainRepository = new DomainRepository(accessor, Core.Cache.AppCaches.Disabled, Logger); return domainRepository; } diff --git a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs index e2123df9e3..462c44d325 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs @@ -4,7 +4,7 @@ using Moq; using NUnit.Framework; using Umbraco.Core.Cache; using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.IO; +using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence; @@ -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,7 @@ 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 repository = new MediaRepository(scopeAccessor, appCaches, Logger, mediaTypeRepository, tagRepository, Mock.Of(), Factory.GetInstance()); return repository; } diff --git a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs index 17b16ad7ab..0b23240615 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,7 @@ 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 repository = new MemberRepository(accessor, AppCaches.Disabled, Logger, memberTypeRepository, memberGroupRepository, tagRepo, Mock.Of(), Factory.GetInstance()); return repository; } diff --git a/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs index 56041c24aa..3398e7d24d 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs @@ -3,10 +3,11 @@ using System.Collections.Generic; using System.Linq; using Moq; using NUnit.Framework; -using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core; 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,7 @@ 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 repository = new DocumentRepository(accessor, AppCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, Factory.GetInstance()); return repository; } diff --git a/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs index e3de2c2892..9e7593f4ea 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs @@ -2,11 +2,12 @@ using Moq; using NUnit.Framework; using Umbraco.Core.Cache; -using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core; 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; @@ -958,7 +959,7 @@ 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 repository = new DocumentRepository(accessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, Factory.GetInstance()); return repository; } @@ -970,7 +971,7 @@ 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 repository = new MediaRepository(accessor, AppCaches.Disabled, Logger, mediaTypeRepository, tagRepository, Mock.Of(), Factory.GetInstance()); return repository; } } diff --git a/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs index b0f9a5335b..941ba84351 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; @@ -241,7 +242,7 @@ namespace Umbraco.Tests.Persistence.Repositories 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 contentRepo = new DocumentRepository((IScopeAccessor) ScopeProvider, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, Factory.GetInstance()); 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..c406a5c704 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs @@ -14,6 +14,7 @@ using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Entities; using Umbraco.Tests.Testing; using Umbraco.Core.Persistence; +using Umbraco.Core.PropertyEditors; namespace Umbraco.Tests.Persistence.Repositories { @@ -29,7 +30,7 @@ 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 repository = new MediaRepository(accessor, AppCaches, Mock.Of(), mediaTypeRepository, tagRepository, Mock.Of(), Factory.GetInstance()); return repository; } @@ -47,7 +48,7 @@ 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 repository = new DocumentRepository(accessor, AppCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, Factory.GetInstance()); return repository; } diff --git a/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs b/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs index ef80672baf..c77bb71494 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; @@ -168,7 +169,7 @@ namespace Umbraco.Tests.Services 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 = new DocumentRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository, Factory.GetInstance()); // Act Stopwatch watch = Stopwatch.StartNew(); @@ -202,7 +203,7 @@ namespace Umbraco.Tests.Services 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 = new DocumentRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository, Factory.GetInstance()); // Act Stopwatch watch = Stopwatch.StartNew(); @@ -234,7 +235,7 @@ namespace Umbraco.Tests.Services 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 = new DocumentRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository, Factory.GetInstance()); // Act var contents = repository.GetMany(); @@ -269,7 +270,7 @@ namespace Umbraco.Tests.Services 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 = new DocumentRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository, Factory.GetInstance()); // Act var contents = repository.GetMany(); diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index e26e764cd1..65c3b9f8a7 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -3166,7 +3166,7 @@ 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 repository = new DocumentRepository(accessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, Factory.GetInstance()); return repository; } From a78a4ff8075ace6b97d49b4b20c992c5c7647a33 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 21 Oct 2019 22:56:02 +1100 Subject: [PATCH 038/548] Less statics, new InternalLinkParserTests, better testing support on UDI, less singletons --- .../PropertyEditors/IDataValueReference.cs | 2 +- src/Umbraco.Core/Udi.cs | 2 +- src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + .../Web/InternalLinkParserTests.cs | 88 ++++++++++++++++++ .../Web/TemplateUtilitiesTests.cs | 71 +-------------- .../MarkdownEditorValueConverter.cs | 9 +- .../RteMacroRenderingValueConverter.cs | 6 +- .../TextStringValueConverter.cs | 8 +- src/Umbraco.Web/Routing/UrlProvider.cs | 13 +++ src/Umbraco.Web/Runtime/WebInitialComposer.cs | 2 + .../Templates/InternalLinkParser.cs | 91 +++++++++++++++++++ .../Templates/TemplateUtilities.cs | 73 ++------------- src/Umbraco.Web/Umbraco.Web.csproj | 1 + src/Umbraco.Web/UmbracoComponentRenderer.cs | 6 +- 14 files changed, 232 insertions(+), 141 deletions(-) create mode 100644 src/Umbraco.Tests/Web/InternalLinkParserTests.cs create mode 100644 src/Umbraco.Web/Templates/InternalLinkParser.cs diff --git a/src/Umbraco.Core/PropertyEditors/IDataValueReference.cs b/src/Umbraco.Core/PropertyEditors/IDataValueReference.cs index d7d848f1bf..e71642f8a3 100644 --- a/src/Umbraco.Core/PropertyEditors/IDataValueReference.cs +++ b/src/Umbraco.Core/PropertyEditors/IDataValueReference.cs @@ -3,7 +3,7 @@ namespace Umbraco.Core.PropertyEditors { /// - /// Used to resolve references from values + /// Resolve references from values /// public interface IDataValueReference { diff --git a/src/Umbraco.Core/Udi.cs b/src/Umbraco.Core/Udi.cs index c7297b8c09..e7d00fffa5 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) { diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 39826fcc38..e73caf4517 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -253,6 +253,7 @@ + diff --git a/src/Umbraco.Tests/Web/InternalLinkParserTests.cs b/src/Umbraco.Tests/Web/InternalLinkParserTests.cs new file mode 100644 index 0000000000..815ce408ee --- /dev/null +++ b/src/Umbraco.Tests/Web/InternalLinkParserTests.cs @@ -0,0 +1,88 @@ +using System; +using System.Linq; +using System.Web; +using Moq; +using NUnit.Framework; +using Umbraco.Core.Configuration.UmbracoSettings; +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.Templates; + +namespace Umbraco.Tests.Web +{ + [TestFixture] + public class InternalLinkParserTests + { + [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 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(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 = new Mock(); + publishedContent.Setup(x => x.Id).Returns(1234); + publishedContent.Setup(x => x.ContentType).Returns(contentType); + var contentCache = new Mock(); + contentCache.Setup(x => x.GetById(It.IsAny())).Returns(publishedContent.Object); + contentCache.Setup(x => x.GetById(It.IsAny())).Returns(publishedContent.Object); + var mediaType = new PublishedContentType(777, "image", PublishedItemType.Media, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing); + var media = new Mock(); + media.Setup(x => x.Url).Returns("/media/1001/my-image.jpg"); + media.Setup(x => x.ContentType).Returns(mediaType); + var mediaCache = new Mock(); + mediaCache.Setup(x => x.GetById(It.IsAny())).Returns(media.Object); + mediaCache.Setup(x => x.GetById(It.IsAny())).Returns(media.Object); + 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 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 = new UmbracoContextFactory( + umbracoContextAccessor, + snapshotService.Object, + new TestVariationContextAccessor(), + new TestDefaultCultureAccessor(), + Mock.Of(section => section.WebRouting == Mock.Of(routingSection => routingSection.UrlProviderMode == "Auto")), + globalSettings, + new UrlProviderCollection(new[] { testUrlProvider.Object }), + new MediaUrlProviderCollection(new[] { mediaUrlProvider.Object }), + Mock.Of()); + + using (var reference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of())) + { + var linkParser = new InternalLinkParser(umbracoContextAccessor); + + var output = linkParser.ParseInternalLinks(input); + + Assert.AreEqual(result, output); + } + } + } +} 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/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs index e11f3e0d3a..98413c7b70 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs @@ -12,6 +12,13 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters [DefaultPropertyValueConverter] public class MarkdownEditorValueConverter : PropertyValueConverterBase { + private readonly InternalLinkParser _localLinkParser; + + public MarkdownEditorValueConverter(InternalLinkParser localLinkParser) + { + _localLinkParser = localLinkParser; + } + public override bool IsConverter(IPublishedPropertyType propertyType) => Constants.PropertyEditors.Aliases.MarkdownEditor == propertyType.EditorAlias; @@ -27,7 +34,7 @@ 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 = _localLinkParser.ParseInternalLinks(sourceString, preview); sourceString = TemplateUtilities.ResolveUrlsFromTextString(sourceString); return sourceString; diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs index d5e1f841ea..95cf2cfc85 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs @@ -24,6 +24,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters { private readonly IUmbracoContextAccessor _umbracoContextAccessor; private readonly IMacroRenderer _macroRenderer; + private readonly InternalLinkParser _internalLinkParser; public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) { @@ -32,10 +33,11 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters return PropertyCacheLevel.Snapshot; } - public RteMacroRenderingValueConverter(IUmbracoContextAccessor umbracoContextAccessor, IMacroRenderer macroRenderer) + public RteMacroRenderingValueConverter(IUmbracoContextAccessor umbracoContextAccessor, IMacroRenderer macroRenderer, InternalLinkParser internalLinkParser) { _umbracoContextAccessor = umbracoContextAccessor; _macroRenderer = macroRenderer; + _internalLinkParser = internalLinkParser; } // NOT thread-safe over a request because it modifies the @@ -81,7 +83,7 @@ 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 = _internalLinkParser.ParseInternalLinks(sourceString, preview); sourceString = TemplateUtilities.ResolveUrlsFromTextString(sourceString); sourceString = TemplateUtilities.ResolveMediaFromTextString(sourceString); diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/TextStringValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/TextStringValueConverter.cs index b8ad1477b4..ee49536d9d 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/TextStringValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/TextStringValueConverter.cs @@ -11,11 +11,17 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters [DefaultPropertyValueConverter] public class TextStringValueConverter : PropertyValueConverterBase { + public TextStringValueConverter(InternalLinkParser internalLinkParser) + { + _internalLinkParser = internalLinkParser; + } + private static readonly string[] PropertyTypeAliases = { Constants.PropertyEditors.Aliases.TextBox, Constants.PropertyEditors.Aliases.TextArea }; + private readonly InternalLinkParser _internalLinkParser; public override bool IsConverter(IPublishedPropertyType propertyType) => PropertyTypeAliases.Contains(propertyType.EditorAlias); @@ -32,7 +38,7 @@ 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 = _internalLinkParser.ParseInternalLinks(sourceString, preview); sourceString = TemplateUtilities.ResolveUrlsFromTextString(sourceString); return sourceString; 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..82137bbd9d 100644 --- a/src/Umbraco.Web/Runtime/WebInitialComposer.cs +++ b/src/Umbraco.Web/Runtime/WebInitialComposer.cs @@ -107,6 +107,8 @@ namespace Umbraco.Web.Runtime 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...) diff --git a/src/Umbraco.Web/Templates/InternalLinkParser.cs b/src/Umbraco.Web/Templates/InternalLinkParser.cs new file mode 100644 index 0000000000..0d8a480a90 --- /dev/null +++ b/src/Umbraco.Web/Templates/InternalLinkParser.cs @@ -0,0 +1,91 @@ +using System; +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 InternalLinkParser + { + + private static readonly Regex LocalLinkPattern = new Regex(@"href=""[/]?(?:\{|\%7B)localLink:([a-zA-Z0-9-://]+)(?:\}|\%7D)", + RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); + + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + + public InternalLinkParser(IUmbracoContextAccessor umbracoContextAccessor) + { + _umbracoContextAccessor = umbracoContextAccessor; + } + + public string ParseInternalLinks(string text, bool preview) + { + if (_umbracoContextAccessor.UmbracoContext == null) + throw new InvalidOperationException("Could not parse internal links, there is no current UmbracoContext"); + + if (!preview) + return ParseInternalLinks(text); + + using (_umbracoContextAccessor.UmbracoContext.ForcedPreview(preview)) // force for url provider + { + return ParseInternalLinks(text); + } + } + + /// + /// Parses the string looking for the {localLink} syntax and updates them to their correct links. + /// + /// + /// + /// + public string ParseInternalLinks(string text) + { + if (_umbracoContextAccessor.UmbracoContext == null) + throw new InvalidOperationException("Could not parse internal links, there is no current UmbracoContext"); + + var urlProvider = _umbracoContextAccessor.UmbracoContext.UrlProvider; + + // 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 = urlProvider.GetMediaUrl(guidUdi.Guid); + + 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; + } + } +} diff --git a/src/Umbraco.Web/Templates/TemplateUtilities.cs b/src/Umbraco.Web/Templates/TemplateUtilities.cs index 58d3ed341e..1092be73e2 100644 --- a/src/Umbraco.Web/Templates/TemplateUtilities.cs +++ b/src/Umbraco.Web/Templates/TemplateUtilities.cs @@ -15,8 +15,6 @@ 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 @@ -24,7 +22,14 @@ namespace Umbraco.Web.Templates public static class TemplateUtilities { const string TemporaryImageDataAttribute = "data-tmpimg"; + + 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); + + [Obsolete("Inject and use an instance of InternalLinkParser instead")] internal static string ParseInternalLinks(string text, bool preview, UmbracoContext umbracoContext) { using (umbracoContext.ForcedPreview(preview)) // force for url provider @@ -35,69 +40,9 @@ namespace Umbraco.Web.Templates return text; } - /// - /// Parses the string looking for the {localLink} syntax and updates them to their correct links. - /// - /// - /// - /// + [Obsolete("Inject and use an instance of InternalLinkParser instead")] public static string ParseInternalLinks(string text, UrlProvider urlProvider) => - ParseInternalLinks(text, urlProvider, Current.UmbracoContext.MediaCache); - - // 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); + Current.Factory.GetInstance().ParseInternalLinks(text); /// /// 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. diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index cf14996888..616ed908e1 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -247,6 +247,7 @@ + diff --git a/src/Umbraco.Web/UmbracoComponentRenderer.cs b/src/Umbraco.Web/UmbracoComponentRenderer.cs index f6c3d30da3..805b9267f9 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 InternalLinkParser _internalLinkParser; - public UmbracoComponentRenderer(IUmbracoContextAccessor umbracoContextAccessor, IMacroRenderer macroRenderer, ITemplateRenderer templateRenderer) + public UmbracoComponentRenderer(IUmbracoContextAccessor umbracoContextAccessor, IMacroRenderer macroRenderer, ITemplateRenderer templateRenderer, InternalLinkParser internalLinkParser) { _umbracoContextAccessor = umbracoContextAccessor; _macroRenderer = macroRenderer; _templateRenderer = templateRenderer ?? throw new ArgumentNullException(nameof(templateRenderer)); + _internalLinkParser = internalLinkParser; } /// @@ -157,7 +159,7 @@ namespace Umbraco.Web _umbracoContextAccessor.UmbracoContext.HttpContext.Response.ContentType = contentType; //Now, we need to ensure that local links are parsed - html = TemplateUtilities.ParseInternalLinks(output.ToString(), _umbracoContextAccessor.UmbracoContext.UrlProvider); + html = _internalLinkParser.ParseInternalLinks(output.ToString()); } } From 8ccebd8006b8eba225fb496d67c3e1b4364e5675 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 21 Oct 2019 23:04:25 +1100 Subject: [PATCH 039/548] Revert "Removes singleton accessors from repositories" - this causes a circular ref?! --- .../Repositories/Implement/ContentRepositoryBase.cs | 5 ++--- .../Implement/DocumentBlueprintRepository.cs | 5 ++--- .../Repositories/Implement/DocumentRepository.cs | 5 ++--- .../Repositories/Implement/MediaRepository.cs | 5 ++--- .../Repositories/Implement/MemberRepository.cs | 5 ++--- .../Repositories/ContentTypeRepositoryTest.cs | 3 +-- .../Persistence/Repositories/DocumentRepositoryTest.cs | 2 +- .../Persistence/Repositories/DomainRepositoryTest.cs | 5 ++--- .../Persistence/Repositories/MediaRepositoryTest.cs | 5 ++--- .../Persistence/Repositories/MemberRepositoryTest.cs | 3 +-- .../Repositories/PublicAccessRepositoryTest.cs | 5 ++--- .../Persistence/Repositories/TagRepositoryTest.cs | 7 +++---- .../Persistence/Repositories/TemplateRepositoryTest.cs | 3 +-- .../Persistence/Repositories/UserRepositoryTest.cs | 5 ++--- .../Services/ContentServicePerformanceTest.cs | 9 ++++----- src/Umbraco.Tests/Services/ContentServiceTests.cs | 2 +- 16 files changed, 30 insertions(+), 44 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs index a6ae2fc7e0..7ab73f3f2d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs @@ -33,18 +33,17 @@ namespace Umbraco.Core.Persistence.Repositories.Implement where TEntity : class, IUmbracoEntity where TRepository : class, IRepository { - protected ContentRepositoryBase(IScopeAccessor scopeAccessor, AppCaches cache, ILanguageRepository languageRepository, ILogger logger, PropertyEditorCollection propertyEditors) + protected ContentRepositoryBase(IScopeAccessor scopeAccessor, AppCaches cache, ILanguageRepository languageRepository, ILogger logger) : base(scopeAccessor, cache, logger) { LanguageRepository = languageRepository; - PropertyEditors = propertyEditors; } protected abstract TRepository This { get; } protected ILanguageRepository LanguageRepository { get; } - protected PropertyEditorCollection PropertyEditors { get; } + protected PropertyEditorCollection PropertyEditors => Current.PropertyEditors; // TODO: inject #region Versions diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentBlueprintRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentBlueprintRepository.cs index c654e1a6c2..d137d7ac76 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentBlueprintRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentBlueprintRepository.cs @@ -2,7 +2,6 @@ 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 @@ -18,8 +17,8 @@ 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, PropertyEditorCollection propertyEditors) - : base(scopeAccessor, appCaches, logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, propertyEditors) + public DocumentBlueprintRepository(IScopeAccessor scopeAccessor, AppCaches appCaches, ILogger logger, IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository, ILanguageRepository languageRepository) + : base(scopeAccessor, appCaches, logger, contentTypeRepository, templateRepository, tagRepository, languageRepository) { } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs index eb392e86cb..2649b9993f 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs @@ -12,7 +12,6 @@ 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; @@ -31,8 +30,8 @@ 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, PropertyEditorCollection propertyEditors) - : base(scopeAccessor, appCaches, languageRepository, logger, propertyEditors) + public DocumentRepository(IScopeAccessor scopeAccessor, AppCaches appCaches, ILogger logger, IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository, ILanguageRepository languageRepository) + : base(scopeAccessor, appCaches, languageRepository, logger) { _contentTypeRepository = contentTypeRepository ?? throw new ArgumentNullException(nameof(contentTypeRepository)); _templateRepository = templateRepository ?? throw new ArgumentNullException(nameof(templateRepository)); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs index ed3eb589bd..25828b8126 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs @@ -12,7 +12,6 @@ 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; @@ -28,8 +27,8 @@ 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, PropertyEditorCollection propertyEditors) - : base(scopeAccessor, cache, languageRepository, logger, propertyEditors) + public MediaRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IMediaTypeRepository mediaTypeRepository, ITagRepository tagRepository, ILanguageRepository languageRepository) + : base(scopeAccessor, cache, languageRepository, logger) { _mediaTypeRepository = mediaTypeRepository ?? throw new ArgumentNullException(nameof(mediaTypeRepository)); _tagRepository = tagRepository ?? throw new ArgumentNullException(nameof(tagRepository)); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs index 4aaa064976..892122dff9 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs @@ -10,7 +10,6 @@ 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; @@ -26,8 +25,8 @@ 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, PropertyEditorCollection propertyEditors) - : base(scopeAccessor, cache, languageRepository, logger, propertyEditors) + public MemberRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IMemberTypeRepository memberTypeRepository, IMemberGroupRepository memberGroupRepository, ITagRepository tagRepository, ILanguageRepository languageRepository) + : base(scopeAccessor, cache, languageRepository, logger) { _memberTypeRepository = memberTypeRepository ?? throw new ArgumentNullException(nameof(memberTypeRepository)); _tagRepository = tagRepository ?? throw new ArgumentNullException(nameof(tagRepository)); diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs index b8b1b03fc4..f953b9cce6 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs @@ -8,7 +8,6 @@ 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; @@ -36,7 +35,7 @@ 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, Factory.GetInstance()); + var repository = new DocumentRepository(scopeAccessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository); return repository; } diff --git a/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs index 424ab145bc..4d62ec8301 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs @@ -69,7 +69,7 @@ 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, Factory.GetInstance()); + var repository = new DocumentRepository(scopeAccessor, appCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository); return repository; } diff --git a/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs index 65b5e8365e..628f8d75a7 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs @@ -3,10 +3,9 @@ using System.Data; using System.Linq; using Moq; using NUnit.Framework; -using Umbraco.Core; +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; @@ -26,7 +25,7 @@ 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, Factory.GetInstance()); + documentRepository = new DocumentRepository(accessor, Core.Cache.AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository); var domainRepository = new DomainRepository(accessor, Core.Cache.AppCaches.Disabled, Logger); return domainRepository; } diff --git a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs index 462c44d325..e2123df9e3 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs @@ -4,7 +4,7 @@ using Moq; using NUnit.Framework; using Umbraco.Core.Cache; using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core; +using Umbraco.Core.IO; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence; @@ -17,7 +17,6 @@ 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 { @@ -42,7 +41,7 @@ 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(), Factory.GetInstance()); + var repository = new MediaRepository(scopeAccessor, appCaches, Logger, mediaTypeRepository, tagRepository, Mock.Of()); return repository; } diff --git a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs index 0b23240615..17b16ad7ab 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs @@ -15,7 +15,6 @@ 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; @@ -36,7 +35,7 @@ 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(), Factory.GetInstance()); + var repository = new MemberRepository(accessor, AppCaches.Disabled, Logger, memberTypeRepository, memberGroupRepository, tagRepo, Mock.Of()); return repository; } diff --git a/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs index 3398e7d24d..56041c24aa 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs @@ -3,11 +3,10 @@ using System.Collections.Generic; using System.Linq; using Moq; using NUnit.Framework; -using Umbraco.Core; +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; @@ -311,7 +310,7 @@ 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, Factory.GetInstance()); + var repository = new DocumentRepository(accessor, AppCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository); return repository; } diff --git a/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs index 9e7593f4ea..e3de2c2892 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs @@ -2,12 +2,11 @@ using Moq; using NUnit.Framework; using Umbraco.Core.Cache; -using Umbraco.Core; +using Umbraco.Core.Configuration.UmbracoSettings; 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; @@ -959,7 +958,7 @@ 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, Factory.GetInstance()); + var repository = new DocumentRepository(accessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository); return repository; } @@ -971,7 +970,7 @@ 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(), Factory.GetInstance()); + var repository = new MediaRepository(accessor, AppCaches.Disabled, Logger, mediaTypeRepository, tagRepository, Mock.Of()); return repository; } } diff --git a/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs index 941ba84351..b0f9a5335b 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs @@ -12,7 +12,6 @@ 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; @@ -242,7 +241,7 @@ namespace Umbraco.Tests.Persistence.Repositories 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, Factory.GetInstance()); + var contentRepo = new DocumentRepository((IScopeAccessor) ScopeProvider, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository); 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 c406a5c704..3e5919d7f3 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs @@ -14,7 +14,6 @@ using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Entities; using Umbraco.Tests.Testing; using Umbraco.Core.Persistence; -using Umbraco.Core.PropertyEditors; namespace Umbraco.Tests.Persistence.Repositories { @@ -30,7 +29,7 @@ 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(), Factory.GetInstance()); + var repository = new MediaRepository(accessor, AppCaches, Mock.Of(), mediaTypeRepository, tagRepository, Mock.Of()); return repository; } @@ -48,7 +47,7 @@ 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, Factory.GetInstance()); + var repository = new DocumentRepository(accessor, AppCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository); return repository; } diff --git a/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs b/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs index c77bb71494..ef80672baf 100644 --- a/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs +++ b/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs @@ -14,7 +14,6 @@ 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; @@ -169,7 +168,7 @@ namespace Umbraco.Tests.Services 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, Factory.GetInstance()); + var repository = new DocumentRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository); // Act Stopwatch watch = Stopwatch.StartNew(); @@ -203,7 +202,7 @@ namespace Umbraco.Tests.Services 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, Factory.GetInstance()); + var repository = new DocumentRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository); // Act Stopwatch watch = Stopwatch.StartNew(); @@ -235,7 +234,7 @@ namespace Umbraco.Tests.Services 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, Factory.GetInstance()); + var repository = new DocumentRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository); // Act var contents = repository.GetMany(); @@ -270,7 +269,7 @@ namespace Umbraco.Tests.Services 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, Factory.GetInstance()); + var repository = new DocumentRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository); // Act var contents = repository.GetMany(); diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index 65c3b9f8a7..e26e764cd1 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -3166,7 +3166,7 @@ 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, Factory.GetInstance()); + var repository = new DocumentRepository(accessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository); return repository; } From 4f9e0fcb92e55757b7a0591fa7fa59e4ff257899 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 21 Oct 2019 23:53:14 +1100 Subject: [PATCH 040/548] No more using TemplateUtilities --- .../Implement/ContentRepositoryBase.cs | 3 +- .../PublishedContentTestBase.cs | 5 +- .../PublishedContent/PublishedContentTests.cs | 4 +- .../Web/InternalLinkParserTests.cs | 2 +- .../PropertyEditors/GridPropertyEditor.cs | 16 +- .../PropertyEditors/RichTextPropertyEditor.cs | 41 ++-- .../MarkdownEditorValueConverter.cs | 8 +- .../RteMacroRenderingValueConverter.cs | 13 +- .../TextStringValueConverter.cs | 8 +- src/Umbraco.Web/Runtime/WebInitialComposer.cs | 2 + .../Templates/InternalLinkParser.cs | 14 +- src/Umbraco.Web/Templates/MediaParser.cs | 186 +++++++++++++++ .../Templates/TemplateUtilities.cs | 213 ++---------------- src/Umbraco.Web/Templates/UrlParser.cs | 61 +++++ src/Umbraco.Web/Umbraco.Web.csproj | 2 + src/Umbraco.Web/UmbracoComponentRenderer.cs | 2 +- 16 files changed, 337 insertions(+), 243 deletions(-) create mode 100644 src/Umbraco.Web/Templates/MediaParser.cs create mode 100644 src/Umbraco.Web/Templates/UrlParser.cs diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs index 7ab73f3f2d..7ab9f10e1c 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs @@ -24,6 +24,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement internal sealed class ContentRepositoryBase { /// + /// /// This is used for unit tests ONLY /// public static bool ThrowOnWarning = false; @@ -43,7 +44,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected ILanguageRepository LanguageRepository { get; } - protected PropertyEditorCollection PropertyEditors => Current.PropertyEditors; // TODO: inject + protected PropertyEditorCollection PropertyEditors => Current.PropertyEditors; // TODO: inject ... this causes circular refs, not sure which refs they are though #region Versions diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs index f1e2bf20d6..1fa3384d08 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs @@ -11,6 +11,7 @@ using Umbraco.Core.Models; using Umbraco.Web.PropertyEditors; using Umbraco.Core.Services; using Umbraco.Web; +using Umbraco.Web.Templates; namespace Umbraco.Tests.PublishedContent { @@ -38,9 +39,11 @@ namespace Umbraco.Tests.PublishedContent base.Initialize(); var converters = Factory.GetInstance(); + var umbracoCtxAccessor = Mock.Of(); + var logger = Mock.Of(); var dataTypeService = new TestObjects.TestDataTypeService( - new DataType(new RichTextPropertyEditor(Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of())) { Id = 1 }); + new DataType(new RichTextPropertyEditor(logger, umbracoCtxAccessor, new MediaParser(umbracoCtxAccessor, logger, Mock.Of(), 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..d2f7283ee5 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs @@ -21,6 +21,7 @@ using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing; using Umbraco.Web.Models.PublishedContent; using Umbraco.Web.PropertyEditors; +using Umbraco.Web.Templates; namespace Umbraco.Tests.PublishedContent { @@ -45,11 +46,12 @@ namespace Umbraco.Tests.PublishedContent var mediaService = Mock.Of(); var contentTypeBaseServiceProvider = Mock.Of(); var umbracoContextAccessor = Mock.Of(); + var mediaParser = new MediaParser(umbracoContextAccessor, logger, mediaService, contentTypeBaseServiceProvider); 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, mediaParser)) { 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/Web/InternalLinkParserTests.cs b/src/Umbraco.Tests/Web/InternalLinkParserTests.cs index 815ce408ee..6cdff240b8 100644 --- a/src/Umbraco.Tests/Web/InternalLinkParserTests.cs +++ b/src/Umbraco.Tests/Web/InternalLinkParserTests.cs @@ -79,7 +79,7 @@ namespace Umbraco.Tests.Web { var linkParser = new InternalLinkParser(umbracoContextAccessor); - var output = linkParser.ParseInternalLinks(input); + var output = linkParser.EnsureInternalLinks(input); Assert.AreEqual(result, output); } diff --git a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs index f782f09289..bec28e33fd 100644 --- a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs @@ -28,14 +28,16 @@ namespace Umbraco.Web.PropertyEditors private IMediaService _mediaService; private IContentTypeBaseServiceProvider _contentTypeBaseServiceProvider; private IUmbracoContextAccessor _umbracoContextAccessor; + private readonly MediaParser _mediaParser; private ILogger _logger; - public GridPropertyEditor(ILogger logger, IMediaService mediaService, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, IUmbracoContextAccessor umbracoContextAccessor) + public GridPropertyEditor(ILogger logger, IMediaService mediaService, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, IUmbracoContextAccessor umbracoContextAccessor, MediaParser mediaParser) : base(logger) { _mediaService = mediaService; _contentTypeBaseServiceProvider = contentTypeBaseServiceProvider; _umbracoContextAccessor = umbracoContextAccessor; + _mediaParser = mediaParser; _logger = logger; } @@ -45,7 +47,7 @@ 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, _mediaService, _contentTypeBaseServiceProvider, _umbracoContextAccessor, _logger, _mediaParser); protected override IConfigurationEditor CreateConfigurationEditor() => new GridConfigurationEditor(); @@ -55,14 +57,16 @@ namespace Umbraco.Web.PropertyEditors private IContentTypeBaseServiceProvider _contentTypeBaseServiceProvider; private IUmbracoContextAccessor _umbracoContextAccessor; private ILogger _logger; + private readonly MediaParser _mediaParser; - public GridPropertyValueEditor(DataEditorAttribute attribute, IMediaService mediaService, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, IUmbracoContextAccessor umbracoContextAccessor, ILogger logger) + public GridPropertyValueEditor(DataEditorAttribute attribute, IMediaService mediaService, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, IUmbracoContextAccessor umbracoContextAccessor, ILogger logger, MediaParser _mediaParser) : base(attribute) { _mediaService = mediaService; _contentTypeBaseServiceProvider = contentTypeBaseServiceProvider; _umbracoContextAccessor = umbracoContextAccessor; _logger = logger; + this._mediaParser = _mediaParser; } /// @@ -97,8 +101,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 = _mediaParser.FindAndPersistPastedTempImages(html, mediaParentId, userId); + var editorValueWithMediaUrlsRemoved = _mediaParser.RemoveImageSources(parseAndSavedTempImages); rte.Value = editorValueWithMediaUrlsRemoved; } @@ -127,7 +131,7 @@ namespace Umbraco.Web.PropertyEditors { var html = rte.Value?.ToString(); - var propertyValueWithMediaResolved = TemplateUtilities.ResolveMediaFromTextString(html); + var propertyValueWithMediaResolved = _mediaParser.EnsureImageSources(html); rte.Value = propertyValueWithMediaResolved; } diff --git a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs index 3eed40c8bf..03dc7b6694 100644 --- a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs @@ -24,27 +24,24 @@ 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 MediaParser _mediaParser; /// /// 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) + public RichTextPropertyEditor(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, MediaParser mediaParser) + : base(logger) { - _mediaService = mediaService; - _contentTypeBaseServiceProvider = contentTypeBaseServiceProvider; _umbracoContextAccessor = umbracoContextAccessor; - _logger = logger; + _mediaParser = mediaParser; } /// /// Create a custom value editor /// /// - protected override IDataValueEditor CreateValueEditor() => new RichTextPropertyValueEditor(Attribute, _mediaService, _contentTypeBaseServiceProvider, _umbracoContextAccessor, _logger); + protected override IDataValueEditor CreateValueEditor() => new RichTextPropertyValueEditor(Attribute, _umbracoContextAccessor, _mediaParser); protected override IConfigurationEditor CreateConfigurationEditor() => new RichTextConfigurationEditor(); @@ -53,20 +50,16 @@ 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 MediaParser _mediaParser; - public RichTextPropertyValueEditor(DataEditorAttribute attribute, IMediaService mediaService, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, IUmbracoContextAccessor umbracoContextAccessor, ILogger logger) + public RichTextPropertyValueEditor(DataEditorAttribute attribute, IUmbracoContextAccessor umbracoContextAccessor, MediaParser _mediaParser) : base(attribute) { - _mediaService = mediaService; - _contentTypeBaseServiceProvider = contentTypeBaseServiceProvider; _umbracoContextAccessor = umbracoContextAccessor; - _logger = logger; + this._mediaParser = _mediaParser; } /// @@ -98,7 +91,7 @@ namespace Umbraco.Web.PropertyEditors if (val == null) return null; - var propertyValueWithMediaResolved = TemplateUtilities.ResolveMediaFromTextString(val.ToString()); + var propertyValueWithMediaResolved = _mediaParser.EnsureImageSources(val.ToString()); var parsed = MacroTagParser.FormatRichTextPersistedDataForEditor(propertyValueWithMediaResolved, new Dictionary()); return parsed; } @@ -120,12 +113,22 @@ 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 = _mediaParser.FindAndPersistPastedTempImages(editorValue.Value.ToString(), mediaParentId, userId); + var editorValueWithMediaUrlsRemoved = _mediaParser.RemoveImageSources(parseAndSavedTempImages); var parsed = MacroTagParser.FormatRichTextContentForPersistence(editorValueWithMediaUrlsRemoved); return parsed; } + + /// + /// Resolve references from values + /// + /// + /// + public IEnumerable GetReferences(object value) + { + throw new NotImplementedException(); + } } internal class RichTextPropertyIndexValueFactory : IPropertyIndexValueFactory diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs index 98413c7b70..578a4cad06 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs @@ -13,10 +13,12 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters public class MarkdownEditorValueConverter : PropertyValueConverterBase { private readonly InternalLinkParser _localLinkParser; + private readonly UrlParser _urlResolver; - public MarkdownEditorValueConverter(InternalLinkParser localLinkParser) + public MarkdownEditorValueConverter(InternalLinkParser localLinkParser, UrlParser urlResolver) { _localLinkParser = localLinkParser; + _urlResolver = urlResolver; } public override bool IsConverter(IPublishedPropertyType propertyType) @@ -34,8 +36,8 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters var sourceString = source.ToString(); // ensures string is parsed for {localLink} and urls are resolved correctly - sourceString = _localLinkParser.ParseInternalLinks(sourceString, preview); - sourceString = TemplateUtilities.ResolveUrlsFromTextString(sourceString); + sourceString = _localLinkParser.EnsureInternalLinks(sourceString, preview); + sourceString = _urlResolver.EnsureUrls(sourceString); return sourceString; } diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs index 95cf2cfc85..88c1429b16 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs @@ -25,6 +25,8 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters private readonly IUmbracoContextAccessor _umbracoContextAccessor; private readonly IMacroRenderer _macroRenderer; private readonly InternalLinkParser _internalLinkParser; + private readonly UrlParser _urlResolver; + private readonly MediaParser _mediaParser; public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) { @@ -33,11 +35,14 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters return PropertyCacheLevel.Snapshot; } - public RteMacroRenderingValueConverter(IUmbracoContextAccessor umbracoContextAccessor, IMacroRenderer macroRenderer, InternalLinkParser internalLinkParser) + public RteMacroRenderingValueConverter(IUmbracoContextAccessor umbracoContextAccessor, IMacroRenderer macroRenderer, + InternalLinkParser internalLinkParser, UrlParser urlResolver, MediaParser mediaParser) { _umbracoContextAccessor = umbracoContextAccessor; _macroRenderer = macroRenderer; _internalLinkParser = internalLinkParser; + _urlResolver = urlResolver; + _mediaParser = mediaParser; } // NOT thread-safe over a request because it modifies the @@ -83,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 = _internalLinkParser.ParseInternalLinks(sourceString, preview); - sourceString = TemplateUtilities.ResolveUrlsFromTextString(sourceString); - sourceString = TemplateUtilities.ResolveMediaFromTextString(sourceString); + sourceString = _internalLinkParser.EnsureInternalLinks(sourceString, preview); + sourceString = _urlResolver.EnsureUrls(sourceString); + sourceString = _mediaParser.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 ee49536d9d..1b85d6e608 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/TextStringValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/TextStringValueConverter.cs @@ -11,9 +11,10 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters [DefaultPropertyValueConverter] public class TextStringValueConverter : PropertyValueConverterBase { - public TextStringValueConverter(InternalLinkParser internalLinkParser) + public TextStringValueConverter(InternalLinkParser internalLinkParser, UrlParser urlParser) { _internalLinkParser = internalLinkParser; + _urlParser = urlParser; } private static readonly string[] PropertyTypeAliases = @@ -22,6 +23,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters Constants.PropertyEditors.Aliases.TextArea }; private readonly InternalLinkParser _internalLinkParser; + private readonly UrlParser _urlParser; public override bool IsConverter(IPublishedPropertyType propertyType) => PropertyTypeAliases.Contains(propertyType.EditorAlias); @@ -38,8 +40,8 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters var sourceString = source.ToString(); // ensures string is parsed for {localLink} and urls are resolved correctly - sourceString = _internalLinkParser.ParseInternalLinks(sourceString, preview); - sourceString = TemplateUtilities.ResolveUrlsFromTextString(sourceString); + sourceString = _internalLinkParser.EnsureInternalLinks(sourceString, preview); + sourceString = _urlParser.EnsureUrls(sourceString); return sourceString; } diff --git a/src/Umbraco.Web/Runtime/WebInitialComposer.cs b/src/Umbraco.Web/Runtime/WebInitialComposer.cs index 82137bbd9d..1b3128388d 100644 --- a/src/Umbraco.Web/Runtime/WebInitialComposer.cs +++ b/src/Umbraco.Web/Runtime/WebInitialComposer.cs @@ -108,6 +108,8 @@ namespace Umbraco.Web.Runtime 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...) diff --git a/src/Umbraco.Web/Templates/InternalLinkParser.cs b/src/Umbraco.Web/Templates/InternalLinkParser.cs index 0d8a480a90..32d7d42eac 100644 --- a/src/Umbraco.Web/Templates/InternalLinkParser.cs +++ b/src/Umbraco.Web/Templates/InternalLinkParser.cs @@ -23,17 +23,23 @@ namespace Umbraco.Web.Templates _umbracoContextAccessor = umbracoContextAccessor; } - public string ParseInternalLinks(string text, bool preview) + /// + /// 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 ParseInternalLinks(text); + return EnsureInternalLinks(text); using (_umbracoContextAccessor.UmbracoContext.ForcedPreview(preview)) // force for url provider { - return ParseInternalLinks(text); + return EnsureInternalLinks(text); } } @@ -43,7 +49,7 @@ namespace Umbraco.Web.Templates /// /// /// - public string ParseInternalLinks(string text) + public string EnsureInternalLinks(string text) { if (_umbracoContextAccessor.UmbracoContext == null) throw new InvalidOperationException("Could not parse internal links, there is no current UmbracoContext"); diff --git a/src/Umbraco.Web/Templates/MediaParser.cs b/src/Umbraco.Web/Templates/MediaParser.cs new file mode 100644 index 0000000000..9a3f8def3c --- /dev/null +++ b/src/Umbraco.Web/Templates/MediaParser.cs @@ -0,0 +1,186 @@ +using HtmlAgilityPack; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text.RegularExpressions; +using Umbraco.Core; +using Umbraco.Core.Exceptions; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Services; + +namespace Umbraco.Web.Templates +{ + public sealed class MediaParser + { + public MediaParser(IUmbracoContextAccessor umbracoContextAccessor, ILogger logger, IMediaService mediaService, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider) + { + _umbracoContextAccessor = umbracoContextAccessor; + _logger = logger; + _mediaService = mediaService; + _contentTypeBaseServiceProvider = contentTypeBaseServiceProvider; + } + + private static readonly Regex ResolveImgPattern = new Regex(@"(]*src="")([^""\?]*)([^""]*""[^>]*data-udi="")([^""]*)(""[^>]*>)", + RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private readonly ILogger _logger; + private readonly IMediaService _mediaService; + private readonly IContentTypeBaseServiceProvider _contentTypeBaseServiceProvider; + const string TemporaryImageDataAttribute = "data-tmpimg"; + + /// + /// 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) + { + // don't attempt to proceed without a context + if (_umbracoContextAccessor?.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 = _umbracoContextAccessor?.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 + /// + /// + /// + internal string RemoveImageSources(string text) + // see comment in ResolveMediaFromTextString for group reference + => ResolveImgPattern.Replace(text, "$1$3$4$5"); + + internal string FindAndPersistPastedTempImages(string html, Guid mediaParentFolder, int userId) + { + // 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 = $"{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(MediaParser), ex, "Could not delete temp file or folder {FileName}", absoluteTempImagePath); + } + } + } + + return htmlDoc.DocumentNode.OuterHtml; + } + } +} diff --git a/src/Umbraco.Web/Templates/TemplateUtilities.cs b/src/Umbraco.Web/Templates/TemplateUtilities.cs index 1092be73e2..d4bae38147 100644 --- a/src/Umbraco.Web/Templates/TemplateUtilities.cs +++ b/src/Umbraco.Web/Templates/TemplateUtilities.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Text.RegularExpressions; using Umbraco.Core; using Umbraco.Core.IO; using Umbraco.Core.Logging; @@ -16,19 +15,9 @@ using File = System.IO.File; namespace Umbraco.Web.Templates { - /// - /// Utility class used for templates - /// + [Obsolete("This class is obsolete, all methods have been moved to other classes such as InternalLinkHelper, UrlResolver and MediaParser")] public static class TemplateUtilities { - const string TemporaryImageDataAttribute = "data-tmpimg"; - - 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); - [Obsolete("Inject and use an instance of InternalLinkParser instead")] internal static string ParseInternalLinks(string text, bool preview, UmbracoContext umbracoContext) { @@ -41,201 +30,27 @@ namespace Umbraco.Web.Templates } [Obsolete("Inject and use an instance of InternalLinkParser instead")] - public static string ParseInternalLinks(string text, UrlProvider urlProvider) => - Current.Factory.GetInstance().ParseInternalLinks(text); + public static string ParseInternalLinks(string text, UrlProvider urlProvider) + => Current.Factory.GetInstance().EnsureInternalLinks(text); - /// - /// 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 UrlResolver")] 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 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 MediaParser.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 MediaParser.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 MediaParser.RemoveImageSources 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); } } diff --git a/src/Umbraco.Web/Templates/UrlParser.cs b/src/Umbraco.Web/Templates/UrlParser.cs new file mode 100644 index 0000000000..e5c40b7365 --- /dev/null +++ b/src/Umbraco.Web/Templates/UrlParser.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 UrlParser + { + 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 UrlParser(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/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 616ed908e1..74ac3f65f3 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -248,6 +248,8 @@ + + diff --git a/src/Umbraco.Web/UmbracoComponentRenderer.cs b/src/Umbraco.Web/UmbracoComponentRenderer.cs index 805b9267f9..c0f83fd1af 100644 --- a/src/Umbraco.Web/UmbracoComponentRenderer.cs +++ b/src/Umbraco.Web/UmbracoComponentRenderer.cs @@ -159,7 +159,7 @@ namespace Umbraco.Web _umbracoContextAccessor.UmbracoContext.HttpContext.Response.ContentType = contentType; //Now, we need to ensure that local links are parsed - html = _internalLinkParser.ParseInternalLinks(output.ToString()); + html = _internalLinkParser.EnsureInternalLinks(output.ToString()); } } From f8e04eb1d624fbefcb39b889a2babeb7bbe255bb Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 22 Oct 2019 00:53:52 +1100 Subject: [PATCH 041/548] Adds tests for MediaParser and simplifies UmbracoContext mocking --- .../PropertyEditors/ImageCropperTest.cs | 1 + .../Testing/Objects/TestDataSource.cs | 3 + .../Objects/TestUmbracoContextFactory.cs | 49 ++++++++++ src/Umbraco.Tests/Umbraco.Tests.csproj | 2 + .../Web/InternalLinkParserTests.cs | 46 ++++----- src/Umbraco.Tests/Web/MediaParserTests.cs | 97 +++++++++++++++++++ src/Umbraco.Web/Templates/MediaParser.cs | 12 ++- 7 files changed, 176 insertions(+), 34 deletions(-) create mode 100644 src/Umbraco.Tests/Testing/Objects/TestUmbracoContextFactory.cs create mode 100644 src/Umbraco.Tests/Web/MediaParserTests.cs diff --git a/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs b/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs index 8d2ab84d35..433ba64b38 100644 --- a/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs +++ b/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs @@ -24,6 +24,7 @@ using Umbraco.Web.PropertyEditors; namespace Umbraco.Tests.PropertyEditors { + [TestFixture] public class ImageCropperTest { 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/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index e73caf4517..20b29a3147 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -212,6 +212,7 @@ + @@ -254,6 +255,7 @@ + diff --git a/src/Umbraco.Tests/Web/InternalLinkParserTests.cs b/src/Umbraco.Tests/Web/InternalLinkParserTests.cs index 6cdff240b8..0a948a8617 100644 --- a/src/Umbraco.Tests/Web/InternalLinkParserTests.cs +++ b/src/Umbraco.Tests/Web/InternalLinkParserTests.cs @@ -8,6 +8,7 @@ using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Services; using Umbraco.Tests.TestHelpers; +using Umbraco.Tests.Testing.Objects; using Umbraco.Tests.Testing.Objects.Accessors; using Umbraco.Web; using Umbraco.Web.PublishedCache; @@ -16,6 +17,7 @@ using Umbraco.Web.Templates; namespace Umbraco.Tests.Web { + [TestFixture] public class InternalLinkParserTests { @@ -29,54 +31,40 @@ namespace Umbraco.Tests.Web [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 url provider which we'll use for testing - var testUrlProvider = new Mock(); - testUrlProvider + 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 globalSettings = SettingsForTests.GenerateMockGlobalSettings(); - 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 contentCache = new Mock(); - contentCache.Setup(x => x.GetById(It.IsAny())).Returns(publishedContent.Object); - contentCache.Setup(x => x.GetById(It.IsAny())).Returns(publishedContent.Object); + var mediaType = new PublishedContentType(777, "image", PublishedItemType.Media, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing); var media = new Mock(); - media.Setup(x => x.Url).Returns("/media/1001/my-image.jpg"); media.Setup(x => x.ContentType).Returns(mediaType); - var mediaCache = new Mock(); - mediaCache.Setup(x => x.GetById(It.IsAny())).Returns(media.Object); - mediaCache.Setup(x => x.GetById(It.IsAny())).Returns(media.Object); - 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 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 = new UmbracoContextFactory( - umbracoContextAccessor, - snapshotService.Object, - new TestVariationContextAccessor(), - new TestDefaultCultureAccessor(), - Mock.Of(section => section.WebRouting == Mock.Of(routingSection => routingSection.UrlProviderMode == "Auto")), - globalSettings, - new UrlProviderCollection(new[] { testUrlProvider.Object }), - new MediaUrlProviderCollection(new[] { mediaUrlProvider.Object }), - Mock.Of()); + 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 InternalLinkParser(umbracoContextAccessor); var output = linkParser.EnsureInternalLinks(input); diff --git a/src/Umbraco.Tests/Web/MediaParserTests.cs b/src/Umbraco.Tests/Web/MediaParserTests.cs new file mode 100644 index 0000000000..7c9e7576b5 --- /dev/null +++ b/src/Umbraco.Tests/Web/MediaParserTests.cs @@ -0,0 +1,97 @@ +using Umbraco.Core.Logging; +using Moq; +using NUnit.Framework; +using Umbraco.Core.Services; +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; + +namespace Umbraco.Tests.Web +{ + [TestFixture] + public class MediaParserTests + { + [Test] + public void Remove_Image_Sources() + { + var logger = Mock.Of(); + var umbracoContextAccessor = new TestUmbracoContextAccessor(); + var mediaParser = new MediaParser(umbracoContextAccessor, logger, Mock.Of(), Mock.Of()); + + var result = mediaParser.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 mediaParser = new MediaParser(umbracoContextAccessor, Mock.Of(), Mock.Of(), Mock.Of()); + + var result = mediaParser.EnsureImageSources(@"

+

+ +

+

+

+

+

+

+

"); + + Assert.AreEqual(@"

+

+ +

+

+

+

+

+

+

", result); + + } + + + } + } +} diff --git a/src/Umbraco.Web/Templates/MediaParser.cs b/src/Umbraco.Web/Templates/MediaParser.cs index 9a3f8def3c..4130b21da2 100644 --- a/src/Umbraco.Web/Templates/MediaParser.cs +++ b/src/Umbraco.Web/Templates/MediaParser.cs @@ -9,6 +9,7 @@ using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Services; +using Umbraco.Web.Routing; namespace Umbraco.Web.Templates { @@ -39,11 +40,13 @@ namespace Umbraco.Web.Templates public string EnsureImageSources(string text) { // don't attempt to proceed without a context - if (_umbracoContextAccessor?.UmbracoContext?.Media == null) + if (_umbracoContextAccessor?.UmbracoContext?.UrlProvider == null) { return text; } + var urlProvider = _umbracoContextAccessor.UmbracoContext.UrlProvider; + return ResolveImgPattern.Replace(text, match => { // match groups: @@ -57,16 +60,15 @@ namespace Umbraco.Web.Templates { return match.Value; } - var media = _umbracoContextAccessor?.UmbracoContext?.Media.GetById(guidUdi.Guid); - if (media == null) + var mediaUrl = urlProvider.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; } - var url = media.Url; - return $"{match.Groups[1].Value}{url}{match.Groups[3].Value}{udi}{match.Groups[5].Value}"; + return $"{match.Groups[1].Value}{mediaUrl}{match.Groups[3].Value}{udi}{match.Groups[5].Value}"; }); } From bb2a3a5e3dc365b472a2e9f4a896bfd21b7256a3 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 22 Oct 2019 11:09:21 +1100 Subject: [PATCH 042/548] stub class for udi parser --- src/Umbraco.Web/Templates/MediaParser.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/Umbraco.Web/Templates/MediaParser.cs b/src/Umbraco.Web/Templates/MediaParser.cs index 4130b21da2..2d20602e8e 100644 --- a/src/Umbraco.Web/Templates/MediaParser.cs +++ b/src/Umbraco.Web/Templates/MediaParser.cs @@ -13,6 +13,22 @@ using Umbraco.Web.Routing; namespace Umbraco.Web.Templates { + /// + /// Parses out UDIs in strings + /// + public sealed class UdiParser + { + /// + /// Parses out UDIs from an html string based on 'data-udi' html attributes + /// + /// + /// + public IEnumerable ParseUdisFromDataAttributes(string text) + { + + } + } + public sealed class MediaParser { public MediaParser(IUmbracoContextAccessor umbracoContextAccessor, ILogger logger, IMediaService mediaService, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider) From 7afdf9e9a94ffb81828c35ad75345ee86132ab4d Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 22 Oct 2019 11:40:51 +1100 Subject: [PATCH 043/548] ensure maindom is disposed as soon as the hosting environment is signaled, remove the GCHandle --- src/Umbraco.Core/MainDom.cs | 6 +++--- src/Umbraco.Core/SystemLock.cs | 23 +++++++++++------------ 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/Umbraco.Core/MainDom.cs b/src/Umbraco.Core/MainDom.cs index f2505c3f78..ccecb4aa82 100644 --- a/src/Umbraco.Core/MainDom.cs +++ b/src/Umbraco.Core/MainDom.cs @@ -212,13 +212,13 @@ namespace Umbraco.Core { OnSignal("environment"); // will run once + // The web app is stopping, dispose eagerly + Dispose(true); + if (immediate) { //only unregister when it's the final call, else we won't be notified of the final call HostingEnvironment.UnregisterObject(this); - - // The web app is stopping immediately, dispose eagerly - Dispose(true); } } diff --git a/src/Umbraco.Core/SystemLock.cs b/src/Umbraco.Core/SystemLock.cs index 04150d58c1..4eaae7082b 100644 --- a/src/Umbraco.Core/SystemLock.cs +++ b/src/Umbraco.Core/SystemLock.cs @@ -29,7 +29,7 @@ namespace Umbraco.Core private readonly Task _releaserTask; public SystemLock() - : this (null) + : this(null) { } public SystemLock(string name) @@ -92,12 +92,10 @@ namespace Umbraco.Core private class NamedSemaphoreReleaser : CriticalFinalizerObject, IDisposable { private readonly Semaphore _semaphore; - private GCHandle _handle; internal NamedSemaphoreReleaser(Semaphore semaphore) { _semaphore = semaphore; - _handle = GCHandle.Alloc(_semaphore); } #region IDisposable Support @@ -116,21 +114,22 @@ namespace Umbraco.Core { if (!disposedValue) { - if (disposing) + try { _semaphore.Release(); - _semaphore.Dispose(); - - } - - // free unmanaged resources (unmanaged objects) and override a finalizer below. - _handle.Free(); - + finally + { + try + { + _semaphore.Dispose(); + } + catch { } + } disposedValue = true; } } - + // we WANT to release the semaphore because it's a system object, ie a critical // non-managed resource - and if it is not released then noone else can acquire // the lock - so we inherit from CriticalFinalizerObject which means that the From 7ace5baf9b2439ced8d86251e65a5a5be8b308da Mon Sep 17 00:00:00 2001 From: JohnBlair Date: Thu, 17 Oct 2019 18:46:11 +0100 Subject: [PATCH 044/548] Allow nucache content and/or media db files to be reused if they already exist. --- src/Umbraco.Core/Runtime/CoreRuntime.cs | 28 ++++++++++--------- .../NuCache/PublishedSnapshotService.cs | 8 ++++-- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/Umbraco.Core/Runtime/CoreRuntime.cs b/src/Umbraco.Core/Runtime/CoreRuntime.cs index 5b069641c4..5cca74124d 100644 --- a/src/Umbraco.Core/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Core/Runtime/CoreRuntime.cs @@ -140,23 +140,25 @@ namespace Umbraco.Core.Runtime Compose(composition); // acquire the main domain - if this fails then anything that should be registered with MainDom will not operate - AcquireMainDom(mainDom); + if (AcquireMainDom(mainDom)) + { + // determine our runtime level + DetermineRuntimeLevel(databaseFactory, ProfilingLogger); - // determine our runtime level - DetermineRuntimeLevel(databaseFactory, ProfilingLogger); + // get composers, and compose + var composerTypes = ResolveComposerTypes(typeLoader); + composition.WithCollectionBuilder(); + var composers = new Composers(composition, composerTypes, ProfilingLogger); + composers.Compose(); - // get composers, and compose - var composerTypes = ResolveComposerTypes(typeLoader); - composition.WithCollectionBuilder(); - var composers = new Composers(composition, composerTypes, ProfilingLogger); - composers.Compose(); + // create the factory + _factory = Current.Factory = composition.CreateFactory(); - // create the factory - _factory = Current.Factory = composition.CreateFactory(); + // create & initialize the components + _components = _factory.GetInstance(); + _components.Initialize(); + } - // create & initialize the components - _components = _factory.GetInstance(); - _components.Initialize(); } catch (Exception e) { diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs index 3fcf61a7bc..81a3aa9e21 100755 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs @@ -128,10 +128,12 @@ namespace Umbraco.Web.PublishedCache.NuCache var path = GetLocalFilesPath(); var localContentDbPath = Path.Combine(path, "NuCache.Content.db"); var localMediaDbPath = Path.Combine(path, "NuCache.Media.db"); - _localDbExists = File.Exists(localContentDbPath) && File.Exists(localMediaDbPath); + var localContentDbExists = File.Exists(localContentDbPath); + var localMediaDbExists = File.Exists(localMediaDbPath); + _localDbExists = localContentDbExists && localMediaDbExists; // if both local databases exist then GetTree will open them, else new databases will be created - _localContentDb = BTree.GetTree(localContentDbPath, _localDbExists); - _localMediaDb = BTree.GetTree(localMediaDbPath, _localDbExists); + _localContentDb = BTree.GetTree(localContentDbPath, localContentDbExists); + _localMediaDb = BTree.GetTree(localMediaDbPath, localMediaDbExists); _logger.Info($"Registered with MainDom, local db exists? {_localDbExists}"); }, From e998fce5d1cecf1592f72c8bfb378af9060d3769 Mon Sep 17 00:00:00 2001 From: JohnBlair Date: Thu, 17 Oct 2019 19:00:00 +0100 Subject: [PATCH 045/548] Trying to lock could throw exceptions so always make sure to properly clean up the local DB. --- .../PublishedCache/NuCache/ContentStore.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs index 1bd58c3878..3fe4b8aecd 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs @@ -234,11 +234,17 @@ namespace Umbraco.Web.PublishedCache.NuCache var lockInfo = new WriteLockInfo(); try { - Lock(lockInfo); - - if (_localDb == null) return; - _localDb.Dispose(); - _localDb = null; + try{ + // Trying to lock could throw exceptions so always make sure to clean up. + Lock(lockInfo); + } + catch + { + if (_localDb == null) return; + _localDb.Dispose(); + _localDb = null; + } + } finally { From ee098e019451cc3da11465e18f6b464598ebd64d Mon Sep 17 00:00:00 2001 From: JohnBlair Date: Thu, 17 Oct 2019 19:06:28 +0100 Subject: [PATCH 046/548] Make sure the local dbs are disposed of if the content store was not created. --- .../PublishedCache/NuCache/PublishedSnapshotService.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs index 81a3aa9e21..e08a0e3c52 100755 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs @@ -144,8 +144,11 @@ namespace Umbraco.Web.PublishedCache.NuCache lock (_storesLock) { _contentStore?.ReleaseLocalDb(); //null check because we could shut down before being assigned + // Make sure the local dbs are disposed of if the content store was not created. + _localContentDb?.Dispose(); _localContentDb = null; _mediaStore?.ReleaseLocalDb(); //null check because we could shut down before being assigned + _localMediaDb?.Dispose(); _localMediaDb = null; } From 77f34e84665cced396a3924390bd3edc59eba62c Mon Sep 17 00:00:00 2001 From: JohnBlair Date: Thu, 17 Oct 2019 19:32:21 +0100 Subject: [PATCH 047/548] Always reset the signal on a timeout exception waiting on the lock. --- src/Umbraco.Core/MainDom.cs | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Core/MainDom.cs b/src/Umbraco.Core/MainDom.cs index ccecb4aa82..ae512241e1 100644 --- a/src/Umbraco.Core/MainDom.cs +++ b/src/Umbraco.Core/MainDom.cs @@ -180,17 +180,27 @@ namespace Umbraco.Core // if more than 1 instance reach that point, one will get the lock // and the other one will timeout, which is accepted - //TODO: This can throw a TimeoutException - in which case should this be in a try/finally to ensure the signal is always reset? - _systemLocker = _systemLock.Lock(LockTimeoutMilliseconds); + //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); + } + catch + { + throw; + } + 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(); + } _isMainDom = true; - - // 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(); + //WaitOneAsync (ext method) will wait for a signal without blocking the main thread, the waiting is done on a background thread From d774ff8bc15bd549caee73bc2b149b13df84d262 Mon Sep 17 00:00:00 2001 From: JohnBlair Date: Fri, 18 Oct 2019 07:17:56 +0100 Subject: [PATCH 048/548] Undoing a bad previous change where logic in a catch should have been in a finally. Also dispose could throw an error so catch it and complete the clean up. --- .../PublishedCache/NuCache/ContentStore.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs index 3fe4b8aecd..179b262568 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs @@ -238,11 +238,21 @@ namespace Umbraco.Web.PublishedCache.NuCache // Trying to lock could throw exceptions so always make sure to clean up. Lock(lockInfo); } - catch + catch { throw; } + finally { - if (_localDb == null) return; - _localDb.Dispose(); - _localDb = null; + if (_localDb != null) + { + try + { + _localDb.Dispose(); + } + catch { /* TBD: May already be throwing so don't throw again */} + finally + { + _localDb = null; + } + } } } From 478bc708b958a9efc6a59b6f1e55aec53e8b1eab Mon Sep 17 00:00:00 2001 From: JohnBlair Date: Fri, 18 Oct 2019 07:31:15 +0100 Subject: [PATCH 049/548] Defensive programming around disposal of local content and media dbs. --- .../NuCache/PublishedSnapshotService.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs index e08a0e3c52..d66bcd806b 100755 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs @@ -145,10 +145,18 @@ namespace Umbraco.Web.PublishedCache.NuCache { _contentStore?.ReleaseLocalDb(); //null check because we could shut down before being assigned // Make sure the local dbs are disposed of if the content store was not created. - _localContentDb?.Dispose(); + try + { + _localContentDb?.Dispose(); + } + catch { /* Carry on with cleanup */ } _localContentDb = null; _mediaStore?.ReleaseLocalDb(); //null check because we could shut down before being assigned - _localMediaDb?.Dispose(); + try + { + _localMediaDb?.Dispose(); + } + catch { } _localMediaDb = null; } From b04f9c17ae74458b4203f9a875be03363cad1824 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 22 Oct 2019 11:55:05 +1100 Subject: [PATCH 050/548] Makes some updates based on code reviews --- src/Umbraco.Core/MainDom.cs | 6 +--- src/Umbraco.Core/Runtime/CoreRuntime.cs | 30 +++++++++---------- .../PublishedCache/NuCache/ContentStore.cs | 30 +++++++++---------- .../NuCache/PublishedSnapshotService.cs | 22 ++++++++------ 4 files changed, 43 insertions(+), 45 deletions(-) diff --git a/src/Umbraco.Core/MainDom.cs b/src/Umbraco.Core/MainDom.cs index ae512241e1..79c6f08bd1 100644 --- a/src/Umbraco.Core/MainDom.cs +++ b/src/Umbraco.Core/MainDom.cs @@ -184,11 +184,7 @@ namespace Umbraco.Core try { _systemLocker = _systemLock.Lock(LockTimeoutMilliseconds); - } - catch - { - throw; - } + } finally { // we need to reset the event, because otherwise we would end up diff --git a/src/Umbraco.Core/Runtime/CoreRuntime.cs b/src/Umbraco.Core/Runtime/CoreRuntime.cs index 5cca74124d..5839ba6cfd 100644 --- a/src/Umbraco.Core/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Core/Runtime/CoreRuntime.cs @@ -140,24 +140,24 @@ namespace Umbraco.Core.Runtime Compose(composition); // acquire the main domain - if this fails then anything that should be registered with MainDom will not operate - if (AcquireMainDom(mainDom)) - { - // determine our runtime level - DetermineRuntimeLevel(databaseFactory, ProfilingLogger); + AcquireMainDom(mainDom); - // get composers, and compose - var composerTypes = ResolveComposerTypes(typeLoader); - composition.WithCollectionBuilder(); - var composers = new Composers(composition, composerTypes, ProfilingLogger); - composers.Compose(); + // determine our runtime level + DetermineRuntimeLevel(databaseFactory, ProfilingLogger); - // create the factory - _factory = Current.Factory = composition.CreateFactory(); + // get composers, and compose + var composerTypes = ResolveComposerTypes(typeLoader); + composition.WithCollectionBuilder(); + var composers = new Composers(composition, composerTypes, ProfilingLogger); + composers.Compose(); + + // create the factory + _factory = Current.Factory = composition.CreateFactory(); + + // create & initialize the components + _components = _factory.GetInstance(); + _components.Initialize(); - // create & initialize the components - _components = _factory.GetInstance(); - _components.Initialize(); - } } catch (Exception e) diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs index 179b262568..6cf34f68bb 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs @@ -128,7 +128,8 @@ namespace Umbraco.Web.PublishedCache.NuCache Monitor.Enter(_rlocko, ref rtaken); // see SnapDictionary - try { } finally + try { } + finally { _wlocked++; lockInfo.Count = true; @@ -234,27 +235,24 @@ namespace Umbraco.Web.PublishedCache.NuCache var lockInfo = new WriteLockInfo(); try { - try{ + try + { // Trying to lock could throw exceptions so always make sure to clean up. Lock(lockInfo); } - catch { throw; } - finally + finally { - if (_localDb != null) + try { - try - { - _localDb.Dispose(); - } - catch { /* TBD: May already be throwing so don't throw again */} - finally - { - _localDb = null; - } + _localDb?.Dispose(); + } + catch { /* TBD: May already be throwing so don't throw again */} + finally + { + _localDb = null; } } - + } finally { @@ -294,7 +292,7 @@ namespace Umbraco.Web.PublishedCache.NuCache public void UpdateContentTypes(IEnumerable types) { //nothing to do if this is empty, no need to lock/allocate/iterate/etc... - if (!types.Any()) return; + if (!types.Any()) return; var lockInfo = new WriteLockInfo(); try diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs index d66bcd806b..d89ff47313 100755 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs @@ -57,7 +57,8 @@ namespace Umbraco.Web.PublishedCache.NuCache private BPlusTree _localContentDb; private BPlusTree _localMediaDb; - private bool _localDbExists; + private bool _localContentDbExists; + private bool _localMediaDbExists; // define constant - determines whether to use cache when previewing // to store eg routes, property converted values, anything - caching @@ -128,14 +129,13 @@ namespace Umbraco.Web.PublishedCache.NuCache var path = GetLocalFilesPath(); var localContentDbPath = Path.Combine(path, "NuCache.Content.db"); var localMediaDbPath = Path.Combine(path, "NuCache.Media.db"); - var localContentDbExists = File.Exists(localContentDbPath); - var localMediaDbExists = File.Exists(localMediaDbPath); - _localDbExists = localContentDbExists && localMediaDbExists; + _localContentDbExists = File.Exists(localContentDbPath); + _localMediaDbExists = File.Exists(localMediaDbPath); // if both local databases exist then GetTree will open them, else new databases will be created - _localContentDb = BTree.GetTree(localContentDbPath, localContentDbExists); - _localMediaDb = BTree.GetTree(localMediaDbPath, localMediaDbExists); + _localContentDb = BTree.GetTree(localContentDbPath, _localContentDbExists); + _localMediaDb = BTree.GetTree(localMediaDbPath, _localMediaDbExists); - _logger.Info($"Registered with MainDom, local db exists? {_localDbExists}"); + _logger.Info($"Registered with MainDom, local content db exists? {_localContentDbExists}, local media db exists? {_localMediaDbExists}"); }, () => { @@ -200,11 +200,15 @@ namespace Umbraco.Web.PublishedCache.NuCache var okContent = false; var okMedia = false; - if (_localDbExists) + if (_localContentDbExists) { okContent = LockAndLoadContent(LoadContentFromLocalDbLocked); 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) + { okMedia = LockAndLoadMedia(LoadMediaFromLocalDbLocked); if (!okMedia) _logger.Warn("Loading media from local db raised warnings, will reload from database."); From 8a18a5b1cbe94180b7245cce5c145eeebdce347a Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 22 Oct 2019 12:13:40 +1100 Subject: [PATCH 051/548] ensure there is no casing issues with app physical path when generating a hash --- src/Umbraco.Core/MainDom.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/MainDom.cs b/src/Umbraco.Core/MainDom.cs index 79c6f08bd1..2fda0b2cb0 100644 --- a/src/Umbraco.Core/MainDom.cs +++ b/src/Umbraco.Core/MainDom.cs @@ -66,7 +66,7 @@ namespace Umbraco.Core // 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; + var appPath = HostingEnvironment.ApplicationPhysicalPath.ToLowerInvariant(); var hash = (appId + ":::" + appPath).ToSHA1(); var lockName = "UMBRACO-" + hash + "-MAINDOM-LCK"; From 6bb8a15a878346a9188577a2cca966df1e927bdf Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 22 Oct 2019 15:07:01 +1100 Subject: [PATCH 052/548] Adds UdiParserTests --- .../{Web => Templates}/MediaParserTests.cs | 7 ++-- src/Umbraco.Tests/Templates/UdiParserTests.cs | 28 +++++++++++++++ src/Umbraco.Tests/Umbraco.Tests.csproj | 3 +- src/Umbraco.Web/Templates/MediaParser.cs | 15 -------- src/Umbraco.Web/Templates/UdiParser.cs | 34 +++++++++++++++++++ src/Umbraco.Web/Umbraco.Web.csproj | 1 + 6 files changed, 69 insertions(+), 19 deletions(-) rename src/Umbraco.Tests/{Web => Templates}/MediaParserTests.cs (98%) create mode 100644 src/Umbraco.Tests/Templates/UdiParserTests.cs create mode 100644 src/Umbraco.Web/Templates/UdiParser.cs diff --git a/src/Umbraco.Tests/Web/MediaParserTests.cs b/src/Umbraco.Tests/Templates/MediaParserTests.cs similarity index 98% rename from src/Umbraco.Tests/Web/MediaParserTests.cs rename to src/Umbraco.Tests/Templates/MediaParserTests.cs index 7c9e7576b5..f7b5933a52 100644 --- a/src/Umbraco.Tests/Web/MediaParserTests.cs +++ b/src/Umbraco.Tests/Templates/MediaParserTests.cs @@ -13,8 +13,9 @@ using System; using System.Linq; using Umbraco.Core.Models; -namespace Umbraco.Tests.Web +namespace Umbraco.Tests.Templates { + [TestFixture] public class MediaParserTests { @@ -46,7 +47,7 @@ namespace Umbraco.Tests.Web 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); @@ -91,7 +92,7 @@ namespace Umbraco.Tests.Web } - + } } } diff --git a/src/Umbraco.Tests/Templates/UdiParserTests.cs b/src/Umbraco.Tests/Templates/UdiParserTests.cs new file mode 100644 index 0000000000..eca7f6c4f0 --- /dev/null +++ b/src/Umbraco.Tests/Templates/UdiParserTests.cs @@ -0,0 +1,28 @@ +using NUnit.Framework; +using Umbraco.Web.Templates; +using System.Linq; +using Umbraco.Core; + +namespace Umbraco.Tests.Templates +{ + [TestFixture] + public class UdiParserTests + { + [Test] + public void Returns_Udi_From_Data_Udi_Html_Attributes() + { + var input = @"

+

+ +
+

"; + + var parser = new UdiParser(); + var result = parser.ParseUdisFromDataAttributes(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]); + } + + } +} diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 20b29a3147..519f36012c 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -159,6 +159,7 @@ + @@ -255,7 +256,7 @@ - + diff --git a/src/Umbraco.Web/Templates/MediaParser.cs b/src/Umbraco.Web/Templates/MediaParser.cs index 2d20602e8e..071c6a5696 100644 --- a/src/Umbraco.Web/Templates/MediaParser.cs +++ b/src/Umbraco.Web/Templates/MediaParser.cs @@ -13,21 +13,6 @@ using Umbraco.Web.Routing; namespace Umbraco.Web.Templates { - /// - /// Parses out UDIs in strings - /// - public sealed class UdiParser - { - /// - /// Parses out UDIs from an html string based on 'data-udi' html attributes - /// - /// - /// - public IEnumerable ParseUdisFromDataAttributes(string text) - { - - } - } public sealed class MediaParser { diff --git a/src/Umbraco.Web/Templates/UdiParser.cs b/src/Umbraco.Web/Templates/UdiParser.cs new file mode 100644 index 0000000000..8bb5f8c579 --- /dev/null +++ b/src/Umbraco.Web/Templates/UdiParser.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using Umbraco.Core; + +namespace Umbraco.Web.Templates +{ + /// + /// Parses out UDIs in strings + /// + public sealed class UdiParser + { + private static readonly Regex DataUdiAttributeRegex = new Regex(@"data-udi=\\?(?:""|')(?umb://[A-z0-9\-]+/[A-z0-9]+)\\?(?:""|')", + RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase); + + /// + /// Parses out UDIs from an html string based on 'data-udi' html attributes + /// + /// + /// + public IEnumerable ParseUdisFromDataAttributes(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; + } + } + } +} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 74ac3f65f3..276cdf9ebc 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -249,6 +249,7 @@ + From 1adf5a30f361c4d9b779c28af35229c79db1e68c Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 22 Oct 2019 15:48:47 +1100 Subject: [PATCH 053/548] Renames some stuff, updates RTE editor to return media/content refs and adds tests for locallink parsing --- .../PublishedContentTestBase.cs | 2 +- .../PublishedContent/PublishedContentTests.cs | 2 +- ...rserTests.cs => ImageSourceParserTests.cs} | 27 ++++++++- .../Templates/LocalLinkParserTests.cs | 34 +++++++++++ src/Umbraco.Tests/Templates/UdiParserTests.cs | 28 --------- src/Umbraco.Tests/Umbraco.Tests.csproj | 4 +- .../Web/InternalLinkParserTests.cs | 2 +- .../PropertyEditors/GridPropertyEditor.cs | 8 +-- .../PropertyEditors/RichTextPropertyEditor.cs | 12 ++-- .../MarkdownEditorValueConverter.cs | 4 +- .../RteMacroRenderingValueConverter.cs | 6 +- .../TextStringValueConverter.cs | 4 +- src/Umbraco.Web/Runtime/WebInitialComposer.cs | 4 +- .../{MediaParser.cs => ImageSourceParser.cs} | 27 ++++++++- ...ternalLinkParser.cs => LocalLinkParser.cs} | 58 +++++++++++++------ .../Templates/TemplateUtilities.cs | 8 +-- src/Umbraco.Web/Templates/UdiParser.cs | 34 ----------- src/Umbraco.Web/Umbraco.Web.csproj | 5 +- src/Umbraco.Web/UmbracoComponentRenderer.cs | 4 +- 19 files changed, 156 insertions(+), 117 deletions(-) rename src/Umbraco.Tests/Templates/{MediaParserTests.cs => ImageSourceParserTests.cs} (69%) create mode 100644 src/Umbraco.Tests/Templates/LocalLinkParserTests.cs delete mode 100644 src/Umbraco.Tests/Templates/UdiParserTests.cs rename src/Umbraco.Web/Templates/{MediaParser.cs => ImageSourceParser.cs} (86%) rename src/Umbraco.Web/Templates/{InternalLinkParser.cs => LocalLinkParser.cs} (63%) delete mode 100644 src/Umbraco.Web/Templates/UdiParser.cs diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs index 1fa3384d08..fec852f97a 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs @@ -43,7 +43,7 @@ namespace Umbraco.Tests.PublishedContent var logger = Mock.Of(); var dataTypeService = new TestObjects.TestDataTypeService( - new DataType(new RichTextPropertyEditor(logger, umbracoCtxAccessor, new MediaParser(umbracoCtxAccessor, logger, Mock.Of(), Mock.Of()))) { Id = 1 }); + new DataType(new RichTextPropertyEditor(logger, umbracoCtxAccessor, new ImageSourceParser(umbracoCtxAccessor, logger, Mock.Of(), 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 d2f7283ee5..95d00998a1 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs @@ -46,7 +46,7 @@ namespace Umbraco.Tests.PublishedContent var mediaService = Mock.Of(); var contentTypeBaseServiceProvider = Mock.Of(); var umbracoContextAccessor = Mock.Of(); - var mediaParser = new MediaParser(umbracoContextAccessor, logger, mediaService, contentTypeBaseServiceProvider); + var mediaParser = new ImageSourceParser(umbracoContextAccessor, logger, mediaService, contentTypeBaseServiceProvider); var dataTypeService = new TestObjects.TestDataTypeService( new DataType(new VoidEditor(logger)) { Id = 1 }, diff --git a/src/Umbraco.Tests/Templates/MediaParserTests.cs b/src/Umbraco.Tests/Templates/ImageSourceParserTests.cs similarity index 69% rename from src/Umbraco.Tests/Templates/MediaParserTests.cs rename to src/Umbraco.Tests/Templates/ImageSourceParserTests.cs index f7b5933a52..d383ab02df 100644 --- a/src/Umbraco.Tests/Templates/MediaParserTests.cs +++ b/src/Umbraco.Tests/Templates/ImageSourceParserTests.cs @@ -12,19 +12,40 @@ using System.Web; using System; using System.Linq; using Umbraco.Core.Models; +using Umbraco.Core; namespace Umbraco.Tests.Templates { + [TestFixture] - public class MediaParserTests + public class ImageSourceParserTests { + [Test] + public void Returns_Udis_From_Data_Udi_Html_Attributes() + { + var input = @"

+

+ +
+

"; + + var logger = Mock.Of(); + var umbracoContextAccessor = new TestUmbracoContextAccessor(); + var mediaParser = new ImageSourceParser(umbracoContextAccessor, logger, Mock.Of(), Mock.Of()); + + var result = mediaParser.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 logger = Mock.Of(); var umbracoContextAccessor = new TestUmbracoContextAccessor(); - var mediaParser = new MediaParser(umbracoContextAccessor, logger, Mock.Of(), Mock.Of()); + var mediaParser = new ImageSourceParser(umbracoContextAccessor, logger, Mock.Of(), Mock.Of()); var result = mediaParser.RemoveImageSources(@"

@@ -66,7 +87,7 @@ namespace Umbraco.Tests.Templates var mediaCache = Mock.Get(reference.UmbracoContext.Media); mediaCache.Setup(x => x.GetById(It.IsAny())).Returns(media.Object); - var mediaParser = new MediaParser(umbracoContextAccessor, Mock.Of(), Mock.Of(), Mock.Of()); + var mediaParser = new ImageSourceParser(umbracoContextAccessor, Mock.Of(), Mock.Of(), Mock.Of()); var result = mediaParser.EnsureImageSources(@"

diff --git a/src/Umbraco.Tests/Templates/LocalLinkParserTests.cs b/src/Umbraco.Tests/Templates/LocalLinkParserTests.cs new file mode 100644 index 0000000000..30c79b3115 --- /dev/null +++ b/src/Umbraco.Tests/Templates/LocalLinkParserTests.cs @@ -0,0 +1,34 @@ +using NUnit.Framework; +using System.Linq; +using Umbraco.Core; +using Umbraco.Tests.Testing.Objects.Accessors; +using Umbraco.Web.Templates; + +namespace Umbraco.Tests.Templates +{ + [TestFixture] + public class LocalLinkParserTests + { + [Test] + public void Returns_Udis_From_LocalLinks() + { + var input = @"

+

+ + hello +
+

+hello +

"; + + var umbracoContextAccessor = new TestUmbracoContextAccessor(); + var parser = new LocalLinkParser(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]); + } + } +} diff --git a/src/Umbraco.Tests/Templates/UdiParserTests.cs b/src/Umbraco.Tests/Templates/UdiParserTests.cs deleted file mode 100644 index eca7f6c4f0..0000000000 --- a/src/Umbraco.Tests/Templates/UdiParserTests.cs +++ /dev/null @@ -1,28 +0,0 @@ -using NUnit.Framework; -using Umbraco.Web.Templates; -using System.Linq; -using Umbraco.Core; - -namespace Umbraco.Tests.Templates -{ - [TestFixture] - public class UdiParserTests - { - [Test] - public void Returns_Udi_From_Data_Udi_Html_Attributes() - { - var input = @"

-

- -
-

"; - - var parser = new UdiParser(); - var result = parser.ParseUdisFromDataAttributes(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]); - } - - } -} diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 519f36012c..d9583b9393 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -159,7 +159,7 @@ - + @@ -256,7 +256,7 @@ - + diff --git a/src/Umbraco.Tests/Web/InternalLinkParserTests.cs b/src/Umbraco.Tests/Web/InternalLinkParserTests.cs index 0a948a8617..49da8d6566 100644 --- a/src/Umbraco.Tests/Web/InternalLinkParserTests.cs +++ b/src/Umbraco.Tests/Web/InternalLinkParserTests.cs @@ -65,7 +65,7 @@ namespace Umbraco.Tests.Web mediaCache.Setup(x => x.GetById(It.IsAny())).Returns(media.Object); mediaCache.Setup(x => x.GetById(It.IsAny())).Returns(media.Object); - var linkParser = new InternalLinkParser(umbracoContextAccessor); + var linkParser = new LocalLinkParser(umbracoContextAccessor); var output = linkParser.EnsureInternalLinks(input); diff --git a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs index bec28e33fd..fa6346fdd8 100644 --- a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs @@ -28,10 +28,10 @@ namespace Umbraco.Web.PropertyEditors private IMediaService _mediaService; private IContentTypeBaseServiceProvider _contentTypeBaseServiceProvider; private IUmbracoContextAccessor _umbracoContextAccessor; - private readonly MediaParser _mediaParser; + private readonly ImageSourceParser _mediaParser; private ILogger _logger; - public GridPropertyEditor(ILogger logger, IMediaService mediaService, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, IUmbracoContextAccessor umbracoContextAccessor, MediaParser mediaParser) + public GridPropertyEditor(ILogger logger, IMediaService mediaService, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, IUmbracoContextAccessor umbracoContextAccessor, ImageSourceParser mediaParser) : base(logger) { _mediaService = mediaService; @@ -57,9 +57,9 @@ namespace Umbraco.Web.PropertyEditors private IContentTypeBaseServiceProvider _contentTypeBaseServiceProvider; private IUmbracoContextAccessor _umbracoContextAccessor; private ILogger _logger; - private readonly MediaParser _mediaParser; + private readonly ImageSourceParser _mediaParser; - public GridPropertyValueEditor(DataEditorAttribute attribute, IMediaService mediaService, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, IUmbracoContextAccessor umbracoContextAccessor, ILogger logger, MediaParser _mediaParser) + public GridPropertyValueEditor(DataEditorAttribute attribute, IMediaService mediaService, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, IUmbracoContextAccessor umbracoContextAccessor, ILogger logger, ImageSourceParser _mediaParser) : base(attribute) { _mediaService = mediaService; diff --git a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs index 03dc7b6694..4c7df4163c 100644 --- a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Models; @@ -25,12 +26,13 @@ namespace Umbraco.Web.PropertyEditors public class RichTextPropertyEditor : DataEditor { private IUmbracoContextAccessor _umbracoContextAccessor; - private readonly MediaParser _mediaParser; + private readonly ImageSourceParser _mediaParser; + /// /// The constructor will setup the property editor based on the attribute if one is found /// - public RichTextPropertyEditor(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, MediaParser mediaParser) + public RichTextPropertyEditor(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, ImageSourceParser mediaParser) : base(logger) { _umbracoContextAccessor = umbracoContextAccessor; @@ -53,9 +55,9 @@ namespace Umbraco.Web.PropertyEditors internal class RichTextPropertyValueEditor : DataValueEditor, IDataValueReference { private IUmbracoContextAccessor _umbracoContextAccessor; - private readonly MediaParser _mediaParser; + private readonly ImageSourceParser _mediaParser; - public RichTextPropertyValueEditor(DataEditorAttribute attribute, IUmbracoContextAccessor umbracoContextAccessor, MediaParser _mediaParser) + public RichTextPropertyValueEditor(DataEditorAttribute attribute, IUmbracoContextAccessor umbracoContextAccessor, ImageSourceParser _mediaParser) : base(attribute) { _umbracoContextAccessor = umbracoContextAccessor; @@ -127,7 +129,7 @@ namespace Umbraco.Web.PropertyEditors /// public IEnumerable GetReferences(object value) { - throw new NotImplementedException(); + return _mediaParser.FindUdisFromDataAttributes(value == null ? string.Empty : value is string str ? str : value.ToString()).ToList(); } } diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs index 578a4cad06..e8a2ac11a6 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs @@ -12,10 +12,10 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters [DefaultPropertyValueConverter] public class MarkdownEditorValueConverter : PropertyValueConverterBase { - private readonly InternalLinkParser _localLinkParser; + private readonly LocalLinkParser _localLinkParser; private readonly UrlParser _urlResolver; - public MarkdownEditorValueConverter(InternalLinkParser localLinkParser, UrlParser urlResolver) + public MarkdownEditorValueConverter(LocalLinkParser localLinkParser, UrlParser urlResolver) { _localLinkParser = localLinkParser; _urlResolver = urlResolver; diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs index 88c1429b16..2caac9e1f4 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs @@ -24,9 +24,9 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters { private readonly IUmbracoContextAccessor _umbracoContextAccessor; private readonly IMacroRenderer _macroRenderer; - private readonly InternalLinkParser _internalLinkParser; + private readonly LocalLinkParser _internalLinkParser; private readonly UrlParser _urlResolver; - private readonly MediaParser _mediaParser; + private readonly ImageSourceParser _mediaParser; public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) { @@ -36,7 +36,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters } public RteMacroRenderingValueConverter(IUmbracoContextAccessor umbracoContextAccessor, IMacroRenderer macroRenderer, - InternalLinkParser internalLinkParser, UrlParser urlResolver, MediaParser mediaParser) + LocalLinkParser internalLinkParser, UrlParser urlResolver, ImageSourceParser mediaParser) { _umbracoContextAccessor = umbracoContextAccessor; _macroRenderer = macroRenderer; diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/TextStringValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/TextStringValueConverter.cs index 1b85d6e608..5efc2ee2db 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/TextStringValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/TextStringValueConverter.cs @@ -11,7 +11,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters [DefaultPropertyValueConverter] public class TextStringValueConverter : PropertyValueConverterBase { - public TextStringValueConverter(InternalLinkParser internalLinkParser, UrlParser urlParser) + public TextStringValueConverter(LocalLinkParser internalLinkParser, UrlParser urlParser) { _internalLinkParser = internalLinkParser; _urlParser = urlParser; @@ -22,7 +22,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters Constants.PropertyEditors.Aliases.TextBox, Constants.PropertyEditors.Aliases.TextArea }; - private readonly InternalLinkParser _internalLinkParser; + private readonly LocalLinkParser _internalLinkParser; private readonly UrlParser _urlParser; public override bool IsConverter(IPublishedPropertyType propertyType) diff --git a/src/Umbraco.Web/Runtime/WebInitialComposer.cs b/src/Umbraco.Web/Runtime/WebInitialComposer.cs index 1b3128388d..2f78ac9732 100644 --- a/src/Umbraco.Web/Runtime/WebInitialComposer.cs +++ b/src/Umbraco.Web/Runtime/WebInitialComposer.cs @@ -107,9 +107,9 @@ namespace Umbraco.Web.Runtime composition.RegisterUnique(); 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...) diff --git a/src/Umbraco.Web/Templates/MediaParser.cs b/src/Umbraco.Web/Templates/ImageSourceParser.cs similarity index 86% rename from src/Umbraco.Web/Templates/MediaParser.cs rename to src/Umbraco.Web/Templates/ImageSourceParser.cs index 071c6a5696..6a0bba4998 100644 --- a/src/Umbraco.Web/Templates/MediaParser.cs +++ b/src/Umbraco.Web/Templates/ImageSourceParser.cs @@ -14,9 +14,9 @@ using Umbraco.Web.Routing; namespace Umbraco.Web.Templates { - public sealed class MediaParser + public sealed class ImageSourceParser { - public MediaParser(IUmbracoContextAccessor umbracoContextAccessor, ILogger logger, IMediaService mediaService, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider) + public ImageSourceParser(IUmbracoContextAccessor umbracoContextAccessor, ILogger logger, IMediaService mediaService, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider) { _umbracoContextAccessor = umbracoContextAccessor; _logger = logger; @@ -32,6 +32,27 @@ namespace Umbraco.Web.Templates private readonly IContentTypeBaseServiceProvider _contentTypeBaseServiceProvider; const string TemporaryImageDataAttribute = "data-tmpimg"; + private static readonly Regex DataUdiAttributeRegex = new Regex(@"data-udi=\\?(?:""|')(?umb://[A-z0-9\-]+/[A-z0-9]+)\\?(?:""|')", + RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase); + + /// + /// Parses out 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. /// @@ -178,7 +199,7 @@ namespace Umbraco.Web.Templates } catch (Exception ex) { - _logger.Error(typeof(MediaParser), ex, "Could not delete temp file or folder {FileName}", absoluteTempImagePath); + _logger.Error(typeof(ImageSourceParser), ex, "Could not delete temp file or folder {FileName}", absoluteTempImagePath); } } } diff --git a/src/Umbraco.Web/Templates/InternalLinkParser.cs b/src/Umbraco.Web/Templates/LocalLinkParser.cs similarity index 63% rename from src/Umbraco.Web/Templates/InternalLinkParser.cs rename to src/Umbraco.Web/Templates/LocalLinkParser.cs index 32d7d42eac..f394f56d85 100644 --- a/src/Umbraco.Web/Templates/InternalLinkParser.cs +++ b/src/Umbraco.Web/Templates/LocalLinkParser.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Text.RegularExpressions; using Umbraco.Core; using Umbraco.Core.Logging; @@ -10,7 +11,7 @@ namespace Umbraco.Web.Templates /// /// Utility class used to parse internal links /// - public sealed class InternalLinkParser + public sealed class LocalLinkParser { private static readonly Regex LocalLinkPattern = new Regex(@"href=""[/]?(?:\{|\%7B)localLink:([a-zA-Z0-9-://]+)(?:\}|\%7D)", @@ -18,11 +19,20 @@ namespace Umbraco.Web.Templates private readonly IUmbracoContextAccessor _umbracoContextAccessor; - public InternalLinkParser(IUmbracoContextAccessor umbracoContextAccessor) + public LocalLinkParser(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. /// @@ -56,6 +66,33 @@ namespace Umbraco.Web.Templates 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) @@ -69,29 +106,16 @@ namespace Umbraco.Web.Templates { 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 = urlProvider.GetMediaUrl(guidUdi.Guid); - - if (newLink == null) - newLink = "#"; - - text = text.Replace(tag.Value, "href=\"" + newLink); - } + yield return (null, guidUdi, tag.Value); } if (int.TryParse(id, out var intId)) { - var newLink = urlProvider.GetUrl(intId); - text = text.Replace(tag.Value, "href=\"" + newLink); + yield return (intId, null, tag.Value); } } } - return text; } } } diff --git a/src/Umbraco.Web/Templates/TemplateUtilities.cs b/src/Umbraco.Web/Templates/TemplateUtilities.cs index d4bae38147..db0366dbf3 100644 --- a/src/Umbraco.Web/Templates/TemplateUtilities.cs +++ b/src/Umbraco.Web/Templates/TemplateUtilities.cs @@ -31,7 +31,7 @@ namespace Umbraco.Web.Templates [Obsolete("Inject and use an instance of InternalLinkParser instead")] public static string ParseInternalLinks(string text, UrlProvider urlProvider) - => Current.Factory.GetInstance().EnsureInternalLinks(text); + => Current.Factory.GetInstance().EnsureInternalLinks(text); [Obsolete("Inject and use an instance of UrlResolver")] public static string ResolveUrlsFromTextString(string text) @@ -43,14 +43,14 @@ namespace Umbraco.Web.Templates [Obsolete("Use MediaParser.EnsureImageSources instead")] public static string ResolveMediaFromTextString(string text) - => Current.Factory.GetInstance().EnsureImageSources(text); + => Current.Factory.GetInstance().EnsureImageSources(text); [Obsolete("Use MediaParser.RemoveImageSources instead")] internal static string RemoveMediaUrlsFromTextString(string text) - => Current.Factory.GetInstance().RemoveImageSources(text); + => Current.Factory.GetInstance().RemoveImageSources(text); [Obsolete("Use MediaParser.RemoveImageSources instead")] internal static string FindAndPersistPastedTempImages(string html, Guid mediaParentFolder, int userId, IMediaService mediaService, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, ILogger logger) - => Current.Factory.GetInstance().FindAndPersistPastedTempImages(html, mediaParentFolder, userId); + => Current.Factory.GetInstance().FindAndPersistPastedTempImages(html, mediaParentFolder, userId); } } diff --git a/src/Umbraco.Web/Templates/UdiParser.cs b/src/Umbraco.Web/Templates/UdiParser.cs deleted file mode 100644 index 8bb5f8c579..0000000000 --- a/src/Umbraco.Web/Templates/UdiParser.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Text.RegularExpressions; -using Umbraco.Core; - -namespace Umbraco.Web.Templates -{ - /// - /// Parses out UDIs in strings - /// - public sealed class UdiParser - { - private static readonly Regex DataUdiAttributeRegex = new Regex(@"data-udi=\\?(?:""|')(?umb://[A-z0-9\-]+/[A-z0-9]+)\\?(?:""|')", - RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase); - - /// - /// Parses out UDIs from an html string based on 'data-udi' html attributes - /// - /// - /// - public IEnumerable ParseUdisFromDataAttributes(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; - } - } - } -} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 276cdf9ebc..8fc06c75c2 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -247,9 +247,8 @@ - - - + + diff --git a/src/Umbraco.Web/UmbracoComponentRenderer.cs b/src/Umbraco.Web/UmbracoComponentRenderer.cs index c0f83fd1af..83c8a7f0fa 100644 --- a/src/Umbraco.Web/UmbracoComponentRenderer.cs +++ b/src/Umbraco.Web/UmbracoComponentRenderer.cs @@ -27,9 +27,9 @@ namespace Umbraco.Web private readonly IUmbracoContextAccessor _umbracoContextAccessor; private readonly IMacroRenderer _macroRenderer; private readonly ITemplateRenderer _templateRenderer; - private readonly InternalLinkParser _internalLinkParser; + private readonly LocalLinkParser _internalLinkParser; - public UmbracoComponentRenderer(IUmbracoContextAccessor umbracoContextAccessor, IMacroRenderer macroRenderer, ITemplateRenderer templateRenderer, InternalLinkParser internalLinkParser) + public UmbracoComponentRenderer(IUmbracoContextAccessor umbracoContextAccessor, IMacroRenderer macroRenderer, ITemplateRenderer templateRenderer, LocalLinkParser internalLinkParser) { _umbracoContextAccessor = umbracoContextAccessor; _macroRenderer = macroRenderer; From aee3b9f9d28b810e893cfd73ad4c0c35504f5371 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 22 Oct 2019 15:52:09 +1100 Subject: [PATCH 054/548] updates rte editor to return local link udis --- .../PropertyEditors/RichTextPropertyEditor.cs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs index 4c7df4163c..8cb9536182 100644 --- a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs @@ -56,12 +56,14 @@ namespace Umbraco.Web.PropertyEditors { private IUmbracoContextAccessor _umbracoContextAccessor; private readonly ImageSourceParser _mediaParser; + private readonly LocalLinkParser _localLinkParser; - public RichTextPropertyValueEditor(DataEditorAttribute attribute, IUmbracoContextAccessor umbracoContextAccessor, ImageSourceParser _mediaParser) + public RichTextPropertyValueEditor(DataEditorAttribute attribute, IUmbracoContextAccessor umbracoContextAccessor, ImageSourceParser mediaParser, LocalLinkParser localLinkParser) : base(attribute) { _umbracoContextAccessor = umbracoContextAccessor; - this._mediaParser = _mediaParser; + _mediaParser = mediaParser; + _localLinkParser = localLinkParser; } /// @@ -129,7 +131,15 @@ namespace Umbraco.Web.PropertyEditors /// public IEnumerable GetReferences(object value) { - return _mediaParser.FindUdisFromDataAttributes(value == null ? string.Empty : value is string str ? str : value.ToString()).ToList(); + var asString = value == null ? string.Empty : value is string str ? str : value.ToString(); + + foreach (var udi in _mediaParser.FindUdisFromDataAttributes(asString)) + yield return udi; + + foreach (var udi in _localLinkParser.FindUdisFromLocalLinks(asString)) + yield return udi; + + //TODO: Detect Macros too ... but we can save that for a later date, right now need to do media refs } } From 611ba7de46e9aa4c113ae6e1c081a7a994b594f7 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 22 Oct 2019 16:12:31 +1100 Subject: [PATCH 055/548] fix build --- src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs index 8cb9536182..2b3e749953 100644 --- a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs @@ -27,23 +27,25 @@ namespace Umbraco.Web.PropertyEditors { private IUmbracoContextAccessor _umbracoContextAccessor; private readonly ImageSourceParser _mediaParser; - + private readonly LocalLinkParser _localLinkParser; + /// /// The constructor will setup the property editor based on the attribute if one is found /// - public RichTextPropertyEditor(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, ImageSourceParser mediaParser) + public RichTextPropertyEditor(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, ImageSourceParser mediaParser, LocalLinkParser localLinkParser) : base(logger) { _umbracoContextAccessor = umbracoContextAccessor; _mediaParser = mediaParser; + _localLinkParser = localLinkParser; } /// /// Create a custom value editor /// /// - protected override IDataValueEditor CreateValueEditor() => new RichTextPropertyValueEditor(Attribute, _umbracoContextAccessor, _mediaParser); + protected override IDataValueEditor CreateValueEditor() => new RichTextPropertyValueEditor(Attribute, _umbracoContextAccessor, _mediaParser, _localLinkParser); protected override IConfigurationEditor CreateConfigurationEditor() => new RichTextConfigurationEditor(); From dd909ebde43e28d83e3f7ea16abdf640d58a6e33 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 22 Oct 2019 09:44:51 +0200 Subject: [PATCH 056/548] AB3267 - Use interface instead of concrete class --- src/Umbraco.ModelsBuilder/Building/TextBuilder.cs | 4 ++-- .../Umbraco/PublishedModelUtility.cs | 11 ++++++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.ModelsBuilder/Building/TextBuilder.cs b/src/Umbraco.ModelsBuilder/Building/TextBuilder.cs index e15fdfca6e..7121dad1a9 100644 --- a/src/Umbraco.ModelsBuilder/Building/TextBuilder.cs +++ b/src/Umbraco.ModelsBuilder/Building/TextBuilder.cs @@ -199,10 +199,10 @@ namespace Umbraco.ModelsBuilder.Building sb.AppendFormat("\t\tpublic new const PublishedItemType ModelItemType = PublishedItemType.{0};\n", itemType); WriteGeneratedCodeAttribute(sb, "\t\t"); - sb.Append("\t\tpublic new static PublishedContentType GetModelContentType()\n"); + sb.Append("\t\tpublic new static IPublishedContentType GetModelContentType()\n"); sb.Append("\t\t\t=> PublishedModelUtility.GetModelContentType(ModelItemType, ModelTypeAlias);\n"); WriteGeneratedCodeAttribute(sb, "\t\t"); - sb.AppendFormat("\t\tpublic static PublishedPropertyType GetModelPropertyType(Expression> selector)\n", + sb.AppendFormat("\t\tpublic static IPublishedPropertyType GetModelPropertyType(Expression> selector)\n", type.ClrName); sb.Append("\t\t\t=> PublishedModelUtility.GetModelPropertyType(GetModelContentType(), selector);\n"); sb.Append("#pragma warning restore 0109\n\n"); diff --git a/src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs b/src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs index 7bbae927d5..44b7aae0c0 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs +++ b/src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel; using System.Linq; using System.Linq.Expressions; using Umbraco.Web.Composing; @@ -40,7 +41,15 @@ namespace Umbraco.ModelsBuilder.Umbraco } } - public static IPublishedPropertyType GetModelPropertyType(PublishedContentType contentType, Expression> selector) + [Obsolete("Use the overload taking IPublishedContentType instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + public static IPublishedPropertyType GetModelPropertyType(PublishedContentType contentType, + Expression> selector) + { + return GetModelPropertyType((IPublishedContentType)contentType, selector); + } + + public static IPublishedPropertyType GetModelPropertyType(IPublishedContentType contentType, Expression> selector) //where TModel : PublishedContentModel // fixme PublishedContentModel _or_ PublishedElementModel { // fixme therefore, missing a check on TModel here From 8c9557322566431f5d74002fbcd8b91c1b7c2a42 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 22 Oct 2019 14:22:43 +0200 Subject: [PATCH 057/548] AB3267 - Removed unused classes and set some internal --- .../Implement/ContentTypeRepositoryBase.cs | 7 ++-- .../Building/TextHeaderWriter.cs | 2 +- .../Configuration/ClrNameSource.cs | 28 ---------------- .../EnumerableExtensions.cs | 33 ------------------- .../Umbraco.ModelsBuilder.csproj | 6 ++-- 5 files changed, 8 insertions(+), 68 deletions(-) delete mode 100644 src/Umbraco.ModelsBuilder/Configuration/ClrNameSource.cs delete mode 100644 src/Umbraco.ModelsBuilder/EnumerableExtensions.cs diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs index 6385482686..e2c3d8c9b5 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs @@ -16,7 +16,6 @@ using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Scoping; using Umbraco.Core.Services; -using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics; namespace Umbraco.Core.Persistence.Repositories.Implement { @@ -506,7 +505,7 @@ AND umbracoNode.id <> @id", /// /// Corrects the property type variations for the given entity /// to make sure the property type variation is compatible with the - /// variation set on the entity itself. + /// variation set on the entity itself. /// /// Entity to correct properties for private void CorrectPropertyTypeVariations(IContentTypeComposition entity) @@ -754,7 +753,7 @@ AND umbracoNode.id <> @id", //we don't need to move the names! this is because we always keep the invariant names with the name of the default language. //however, if we were to move names, we could do this: BUT this doesn't work with SQLCE, for that we'd have to update row by row :( - // if we want these SQL statements back, look into GIT history + // if we want these SQL statements back, look into GIT history } } @@ -1033,7 +1032,7 @@ AND umbracoNode.id <> @id", //keep track of this node/lang to mark or unmark a culture as edited var editedLanguageVersions = new Dictionary<(int nodeId, int? langId), bool>(); - //keep track of which node to mark or unmark as edited + //keep track of which node to mark or unmark as edited var editedDocument = new Dictionary(); var nodeId = -1; var propertyTypeId = -1; diff --git a/src/Umbraco.ModelsBuilder/Building/TextHeaderWriter.cs b/src/Umbraco.ModelsBuilder/Building/TextHeaderWriter.cs index d165f03907..27fcb42b35 100644 --- a/src/Umbraco.ModelsBuilder/Building/TextHeaderWriter.cs +++ b/src/Umbraco.ModelsBuilder/Building/TextHeaderWriter.cs @@ -3,7 +3,7 @@ using Umbraco.ModelsBuilder.Api; namespace Umbraco.ModelsBuilder.Building { - public static class TextHeaderWriter + internal static class TextHeaderWriter { /// /// Outputs an "auto-generated" header to a string builder. diff --git a/src/Umbraco.ModelsBuilder/Configuration/ClrNameSource.cs b/src/Umbraco.ModelsBuilder/Configuration/ClrNameSource.cs deleted file mode 100644 index d195846411..0000000000 --- a/src/Umbraco.ModelsBuilder/Configuration/ClrNameSource.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace Umbraco.ModelsBuilder.Configuration -{ - /// - /// Defines the CLR name sources. - /// - public enum ClrNameSource - { - /// - /// No source. - /// - Nothing = 0, - - /// - /// Use the name as source. - /// - Name, - - /// - /// Use the alias as source. - /// - Alias, - - /// - /// Use the alias directly. - /// - RawAlias - } -} diff --git a/src/Umbraco.ModelsBuilder/EnumerableExtensions.cs b/src/Umbraco.ModelsBuilder/EnumerableExtensions.cs deleted file mode 100644 index da77bfa958..0000000000 --- a/src/Umbraco.ModelsBuilder/EnumerableExtensions.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Umbraco.ModelsBuilder -{ - public static class EnumerableExtensions - { - public static void RemoveAll(this IList list, Func predicate) - { - for (var i = 0; i < list.Count; i++) - { - if (predicate(list[i])) - { - list.RemoveAt(i--); // i-- is important here! - } - } - } - - public static IEnumerable And(this IEnumerable enumerable, T item) - { - foreach (var x in enumerable) yield return x; - yield return item; - } - - public static IEnumerable AndIfNotNull(this IEnumerable enumerable, T item) - where T : class - { - foreach (var x in enumerable) yield return x; - if (item != null) - yield return item; - } - } -} diff --git a/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj b/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj index 2791d420db..4dbe606a66 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj +++ b/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj @@ -55,12 +55,10 @@ - - @@ -93,6 +91,10 @@ + + {29aa69d9-b597-4395-8d42-43b1263c240a} + Umbraco.Abstractions + {31785BC3-256C-4613-B2F5-A1B0BDDED8C1} Umbraco.Core From fff646ad15c61c793fb42a16707ef25969ce4a6d Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 23 Oct 2019 14:12:23 +1100 Subject: [PATCH 058/548] MainDom will terminate on first (or only) call to Stop --- src/Umbraco.Core/MainDom.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Core/MainDom.cs b/src/Umbraco.Core/MainDom.cs index 2fda0b2cb0..e54bd0dcb3 100644 --- a/src/Umbraco.Core/MainDom.cs +++ b/src/Umbraco.Core/MainDom.cs @@ -218,14 +218,10 @@ namespace Umbraco.Core { OnSignal("environment"); // will run once - // The web app is stopping, dispose eagerly + // The web app is stopping, need to wind down Dispose(true); - if (immediate) - { - //only unregister when it's the final call, else we won't be notified of the final call - HostingEnvironment.UnregisterObject(this); - } + HostingEnvironment.UnregisterObject(this); } #region IDisposable Support From 31ddc1d935a02169e8f27ed1ede0814a20788d40 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 23 Oct 2019 14:17:30 +1100 Subject: [PATCH 059/548] Changes BackgroundTaskRunner to shutdown faster and to ensures that any latched tasks are canceled even with Stop(immediate == false) is executed. --- .../Scheduling/BackgroundTaskRunner.cs | 64 +++++++++++-------- 1 file changed, 37 insertions(+), 27 deletions(-) diff --git a/src/Umbraco.Web/Scheduling/BackgroundTaskRunner.cs b/src/Umbraco.Web/Scheduling/BackgroundTaskRunner.cs index dab6a8865e..e518f49ae0 100644 --- a/src/Umbraco.Web/Scheduling/BackgroundTaskRunner.cs +++ b/src/Umbraco.Web/Scheduling/BackgroundTaskRunner.cs @@ -338,14 +338,18 @@ namespace Umbraco.Web.Scheduling if (_isRunning == false) return; // done already } + var hasTasks = _tasks.Count > 0; + + if (!force && hasTasks) + _logger.Info("{LogPrefix} Waiting for tasks to complete", _logPrefix); + // complete the queue // will stop waiting on the queue or on a latch _tasks.Complete(); - if (force) + if (!hasTasks || force) { - // we must bring everything down, now - Thread.Sleep(100); // give time to Complete() + // we must bring everything down, now lock (_locker) { // was Complete() enough? @@ -354,13 +358,13 @@ namespace Umbraco.Web.Scheduling // try to cancel running async tasks (cannot do much about sync tasks) // break latched tasks // stop processing the queue - _shutdownTokenSource.Cancel(false); // false is the default - _shutdownTokenSource.Dispose(); + _shutdownTokenSource?.Cancel(false); // false is the default + _shutdownTokenSource?.Dispose(); _shutdownTokenSource = null; } // tasks in the queue will be executed... - if (wait == false) return; + if (!wait) return; _runningTask?.Wait(CancellationToken.None); // wait for whatever is running to end... } @@ -503,7 +507,7 @@ namespace Umbraco.Web.Scheduling // returns the task that completed // - latched.Latch completes when the latch releases // - _tasks.Completion completes when the runner completes - // - tokenTaskSource.Task completes when this task, or the whole runner, is cancelled + // - tokenTaskSource.Task completes when this task, or the whole runner is cancelled var task = await Task.WhenAny(latched.Latch, _tasks.Completion, tokenTaskSource.Task); // ok to run now @@ -693,13 +697,11 @@ namespace Umbraco.Web.Scheduling if (onTerminating) OnEvent(Terminating, "Terminating"); - if (immediate == false) + if (!immediate) { - // The Stop method is first called with the immediate parameter set to false. The object can either complete - // processing, call the UnregisterObject method, and then return or it can return immediately and complete - // processing asynchronously before calling the UnregisterObject method. - - _logger.Info("{LogPrefix} Waiting for tasks to complete", _logPrefix); + // immediate == false when the app is trying to wind down, immediate == true will be called either: + // after a call with immediate == false or if the app is not trying to wind down and needs to immediately stop. + // So Stop may be called twice or sometimes only once. try { @@ -716,24 +718,32 @@ namespace Umbraco.Web.Scheduling Terminate(false); } } - + + // If we are called with immediate == false, wind down above and then shutdown within 2 seconds, + // we want to shut down the app as quick as possible, if we wait until immediate == true, this can + // take a very long time since immediate will only be true when a new request is received on the new + // appdomain (or another iis timeout occurs ... which can take soeme time). + Task.Delay(2000, _shutdownToken).ContinueWith(_ => StopImmediate()); + } else { - // If the registered object does not complete processing before the application manager's time-out - // period expires, the Stop method is called again with the immediate parameter set to true. When the - // immediate parameter is true, the registered object must call the UnregisterObject method before returning; - // otherwise, its registration will be removed by the application manager. + // If we are called with immediate == true - cancel and shut down now. - _logger.Info("{LogPrefix} Canceling tasks", _logPrefix); - try - { - Shutdown(true, true); // cancel all tasks, wait for the current one to end - } - finally - { - Terminate(true); - } + StopImmediate(); + } + } + + private void StopImmediate() + { + _logger.Info("{LogPrefix} Canceling tasks", _logPrefix); + try + { + Shutdown(true, true); // cancel all tasks, wait for the current one to end + } + finally + { + Terminate(true); } } From 832803a9f64d7ab3a8f6dbb2b94f59f75f7f3318 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 23 Oct 2019 14:35:43 +1100 Subject: [PATCH 060/548] fix build --- .../PublishedContent/PublishedContentTestBase.cs | 6 ++++-- src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs index fec852f97a..6c68fecdd2 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs @@ -39,11 +39,13 @@ namespace Umbraco.Tests.PublishedContent base.Initialize(); var converters = Factory.GetInstance(); - var umbracoCtxAccessor = Mock.Of(); + var umbracoContextAccessor = Mock.Of(); var logger = Mock.Of(); + var imageSourceParser = new ImageSourceParser(umbracoContextAccessor, logger, Mock.Of(), Mock.Of()); + var localLinkParser = new LocalLinkParser(umbracoContextAccessor); var dataTypeService = new TestObjects.TestDataTypeService( - new DataType(new RichTextPropertyEditor(logger, umbracoCtxAccessor, new ImageSourceParser(umbracoCtxAccessor, logger, Mock.Of(), Mock.Of()))) { Id = 1 }); + new DataType(new RichTextPropertyEditor(logger, umbracoContextAccessor, imageSourceParser, localLinkParser)) { 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 95d00998a1..333f3ca7c0 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs @@ -47,11 +47,12 @@ namespace Umbraco.Tests.PublishedContent var contentTypeBaseServiceProvider = Mock.Of(); var umbracoContextAccessor = Mock.Of(); var mediaParser = new ImageSourceParser(umbracoContextAccessor, logger, mediaService, contentTypeBaseServiceProvider); + var localLinkParser = new LocalLinkParser(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, umbracoContextAccessor, mediaParser)) { Id = 1002 }, + new DataType(new RichTextPropertyEditor(logger, umbracoContextAccessor, mediaParser, localLinkParser)) { Id = 1002 }, new DataType(new IntegerPropertyEditor(logger)) { Id = 1003 }, new DataType(new TextboxPropertyEditor(logger)) { Id = 1004 }, new DataType(new MediaPickerPropertyEditor(logger)) { Id = 1005 }); From 17fd09fe3df0576dc6823cf3fa8d2d9d695a0912 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 23 Oct 2019 14:55:18 +1100 Subject: [PATCH 061/548] Naming --- .../PublishedContentTestBase.cs | 4 +-- .../PublishedContent/PublishedContentTests.cs | 6 ++-- .../Templates/ImageSourceParserTests.cs | 12 +++---- .../Templates/LocalLinkParserTests.cs | 2 +- src/Umbraco.Tests/Umbraco.Tests.csproj | 2 +- ...erTests.cs => HtmlLocalLinkParserTests.cs} | 4 +-- .../PropertyEditors/GridPropertyEditor.cs | 32 ++++++------------- .../PropertyEditors/RichTextPropertyEditor.cs | 26 +++++++-------- .../MarkdownEditorValueConverter.cs | 10 +++--- .../RteMacroRenderingValueConverter.cs | 20 ++++++------ .../TextStringValueConverter.cs | 10 +++--- src/Umbraco.Web/Runtime/WebInitialComposer.cs | 6 ++-- ...urceParser.cs => HtmlImageSourceParser.cs} | 15 ++++++--- ...alLinkParser.cs => HtmlLocalLinkParser.cs} | 4 +-- .../{UrlParser.cs => HtmlUrlParser.cs} | 4 +-- .../Templates/TemplateUtilities.cs | 24 +++++++------- src/Umbraco.Web/Umbraco.Web.csproj | 6 ++-- src/Umbraco.Web/UmbracoComponentRenderer.cs | 8 ++--- 18 files changed, 95 insertions(+), 100 deletions(-) rename src/Umbraco.Tests/Web/{InternalLinkParserTests.cs => HtmlLocalLinkParserTests.cs} (97%) rename src/Umbraco.Web/Templates/{ImageSourceParser.cs => HtmlImageSourceParser.cs} (92%) rename src/Umbraco.Web/Templates/{LocalLinkParser.cs => HtmlLocalLinkParser.cs} (97%) rename src/Umbraco.Web/Templates/{UrlParser.cs => HtmlUrlParser.cs} (95%) diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs index 6c68fecdd2..497c621963 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs @@ -42,8 +42,8 @@ namespace Umbraco.Tests.PublishedContent var umbracoContextAccessor = Mock.Of(); var logger = Mock.Of(); - var imageSourceParser = new ImageSourceParser(umbracoContextAccessor, logger, Mock.Of(), Mock.Of()); - var localLinkParser = new LocalLinkParser(umbracoContextAccessor); + var imageSourceParser = new HtmlImageSourceParser(umbracoContextAccessor, logger, Mock.Of(), Mock.Of()); + var localLinkParser = new HtmlLocalLinkParser(umbracoContextAccessor); var dataTypeService = new TestObjects.TestDataTypeService( new DataType(new RichTextPropertyEditor(logger, umbracoContextAccessor, imageSourceParser, localLinkParser)) { Id = 1 }); diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs index 333f3ca7c0..9f5dc81986 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs @@ -46,13 +46,13 @@ namespace Umbraco.Tests.PublishedContent var mediaService = Mock.Of(); var contentTypeBaseServiceProvider = Mock.Of(); var umbracoContextAccessor = Mock.Of(); - var mediaParser = new ImageSourceParser(umbracoContextAccessor, logger, mediaService, contentTypeBaseServiceProvider); - var localLinkParser = new LocalLinkParser(umbracoContextAccessor); + var imageSourceParser = new HtmlImageSourceParser(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, umbracoContextAccessor, mediaParser, localLinkParser)) { Id = 1002 }, + new DataType(new RichTextPropertyEditor(logger, umbracoContextAccessor, imageSourceParser, linkParser)) { 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/Templates/ImageSourceParserTests.cs b/src/Umbraco.Tests/Templates/ImageSourceParserTests.cs index d383ab02df..ca01caf344 100644 --- a/src/Umbraco.Tests/Templates/ImageSourceParserTests.cs +++ b/src/Umbraco.Tests/Templates/ImageSourceParserTests.cs @@ -32,9 +32,9 @@ namespace Umbraco.Tests.Templates var logger = Mock.Of(); var umbracoContextAccessor = new TestUmbracoContextAccessor(); - var mediaParser = new ImageSourceParser(umbracoContextAccessor, logger, Mock.Of(), Mock.Of()); + var imageSourceParser = new HtmlImageSourceParser(umbracoContextAccessor, logger, Mock.Of(), Mock.Of()); - var result = mediaParser.FindUdisFromDataAttributes(input).ToList(); + 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]); @@ -45,9 +45,9 @@ namespace Umbraco.Tests.Templates { var logger = Mock.Of(); var umbracoContextAccessor = new TestUmbracoContextAccessor(); - var mediaParser = new ImageSourceParser(umbracoContextAccessor, logger, Mock.Of(), Mock.Of()); + var imageSourceParser = new HtmlImageSourceParser(umbracoContextAccessor, logger, Mock.Of(), Mock.Of()); - var result = mediaParser.RemoveImageSources(@"

+ var result = imageSourceParser.RemoveImageSources(@"

@@ -87,9 +87,9 @@ namespace Umbraco.Tests.Templates var mediaCache = Mock.Get(reference.UmbracoContext.Media); mediaCache.Setup(x => x.GetById(It.IsAny())).Returns(media.Object); - var mediaParser = new ImageSourceParser(umbracoContextAccessor, Mock.Of(), Mock.Of(), Mock.Of()); + var imageSourceParser = new HtmlImageSourceParser(umbracoContextAccessor, Mock.Of(), Mock.Of(), Mock.Of()); - var result = mediaParser.EnsureImageSources(@"

+ var result = imageSourceParser.EnsureImageSources(@"

diff --git a/src/Umbraco.Tests/Templates/LocalLinkParserTests.cs b/src/Umbraco.Tests/Templates/LocalLinkParserTests.cs index 30c79b3115..e09d71196e 100644 --- a/src/Umbraco.Tests/Templates/LocalLinkParserTests.cs +++ b/src/Umbraco.Tests/Templates/LocalLinkParserTests.cs @@ -22,7 +22,7 @@ namespace Umbraco.Tests.Templates

"; var umbracoContextAccessor = new TestUmbracoContextAccessor(); - var parser = new LocalLinkParser(umbracoContextAccessor); + var parser = new HtmlLocalLinkParser(umbracoContextAccessor); var result = parser.FindUdisFromLocalLinks(input).ToList(); diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index d9583b9393..fa654ad4a4 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -255,7 +255,7 @@ - + diff --git a/src/Umbraco.Tests/Web/InternalLinkParserTests.cs b/src/Umbraco.Tests/Web/HtmlLocalLinkParserTests.cs similarity index 97% rename from src/Umbraco.Tests/Web/InternalLinkParserTests.cs rename to src/Umbraco.Tests/Web/HtmlLocalLinkParserTests.cs index 49da8d6566..e6a0abeb4c 100644 --- a/src/Umbraco.Tests/Web/InternalLinkParserTests.cs +++ b/src/Umbraco.Tests/Web/HtmlLocalLinkParserTests.cs @@ -19,7 +19,7 @@ namespace Umbraco.Tests.Web { [TestFixture] - public class InternalLinkParserTests + public class HtmlLocalLinkParserTests { [TestCase("", "")] [TestCase("hello href=\"{localLink:1234}\" world ", "hello href=\"/my-test-url\" world ")] @@ -65,7 +65,7 @@ namespace Umbraco.Tests.Web mediaCache.Setup(x => x.GetById(It.IsAny())).Returns(media.Object); mediaCache.Setup(x => x.GetById(It.IsAny())).Returns(media.Object); - var linkParser = new LocalLinkParser(umbracoContextAccessor); + var linkParser = new HtmlLocalLinkParser(umbracoContextAccessor); var output = linkParser.EnsureInternalLinks(input); diff --git a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs index fa6346fdd8..6481099f45 100644 --- a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs @@ -25,20 +25,14 @@ namespace Umbraco.Web.PropertyEditors Group = Constants.PropertyEditors.Groups.RichContent)] public class GridPropertyEditor : DataEditor { - private IMediaService _mediaService; - private IContentTypeBaseServiceProvider _contentTypeBaseServiceProvider; private IUmbracoContextAccessor _umbracoContextAccessor; - private readonly ImageSourceParser _mediaParser; - private ILogger _logger; + private readonly HtmlImageSourceParser _imageSourceParser; - public GridPropertyEditor(ILogger logger, IMediaService mediaService, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, IUmbracoContextAccessor umbracoContextAccessor, ImageSourceParser mediaParser) + public GridPropertyEditor(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, HtmlImageSourceParser imageSourceParser) : base(logger) { - _mediaService = mediaService; - _contentTypeBaseServiceProvider = contentTypeBaseServiceProvider; _umbracoContextAccessor = umbracoContextAccessor; - _mediaParser = mediaParser; - _logger = logger; + _imageSourceParser = imageSourceParser; } public override IPropertyIndexValueFactory PropertyIndexValueFactory => new GridPropertyIndexValueFactory(); @@ -47,26 +41,20 @@ namespace Umbraco.Web.PropertyEditors /// Overridden to ensure that the value is validated ///
/// - protected override IDataValueEditor CreateValueEditor() => new GridPropertyValueEditor(Attribute, _mediaService, _contentTypeBaseServiceProvider, _umbracoContextAccessor, _logger, _mediaParser); + protected override IDataValueEditor CreateValueEditor() => new GridPropertyValueEditor(Attribute, _umbracoContextAccessor, _imageSourceParser); protected override IConfigurationEditor CreateConfigurationEditor() => new GridConfigurationEditor(); internal class GridPropertyValueEditor : DataValueEditor { - private IMediaService _mediaService; - private IContentTypeBaseServiceProvider _contentTypeBaseServiceProvider; private IUmbracoContextAccessor _umbracoContextAccessor; - private ILogger _logger; - private readonly ImageSourceParser _mediaParser; + private readonly HtmlImageSourceParser _imageSourceParser; - public GridPropertyValueEditor(DataEditorAttribute attribute, IMediaService mediaService, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, IUmbracoContextAccessor umbracoContextAccessor, ILogger logger, ImageSourceParser _mediaParser) + public GridPropertyValueEditor(DataEditorAttribute attribute, IUmbracoContextAccessor umbracoContextAccessor, HtmlImageSourceParser imageSourceParser) : base(attribute) { - _mediaService = mediaService; - _contentTypeBaseServiceProvider = contentTypeBaseServiceProvider; _umbracoContextAccessor = umbracoContextAccessor; - _logger = logger; - this._mediaParser = _mediaParser; + _imageSourceParser = imageSourceParser; } /// @@ -101,8 +89,8 @@ namespace Umbraco.Web.PropertyEditors // Parse the HTML var html = rte.Value?.ToString(); - var parseAndSavedTempImages = _mediaParser.FindAndPersistPastedTempImages(html, mediaParentId, userId); - var editorValueWithMediaUrlsRemoved = _mediaParser.RemoveImageSources(parseAndSavedTempImages); + var parseAndSavedTempImages = _imageSourceParser.FindAndPersistPastedTempImages(html, mediaParentId, userId); + var editorValueWithMediaUrlsRemoved = _imageSourceParser.RemoveImageSources(parseAndSavedTempImages); rte.Value = editorValueWithMediaUrlsRemoved; } @@ -131,7 +119,7 @@ namespace Umbraco.Web.PropertyEditors { var html = rte.Value?.ToString(); - var propertyValueWithMediaResolved = _mediaParser.EnsureImageSources(html); + var propertyValueWithMediaResolved = _imageSourceParser.EnsureImageSources(html); rte.Value = propertyValueWithMediaResolved; } diff --git a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs index 2b3e749953..0dbe5426a2 100644 --- a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs @@ -26,18 +26,18 @@ namespace Umbraco.Web.PropertyEditors public class RichTextPropertyEditor : DataEditor { private IUmbracoContextAccessor _umbracoContextAccessor; - private readonly ImageSourceParser _mediaParser; - private readonly LocalLinkParser _localLinkParser; + private readonly HtmlImageSourceParser _imageSourceParser; + private readonly HtmlLocalLinkParser _localLinkParser; /// /// The constructor will setup the property editor based on the attribute if one is found /// - public RichTextPropertyEditor(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, ImageSourceParser mediaParser, LocalLinkParser localLinkParser) + public RichTextPropertyEditor(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, HtmlImageSourceParser imageSourceParser, HtmlLocalLinkParser localLinkParser) : base(logger) { _umbracoContextAccessor = umbracoContextAccessor; - _mediaParser = mediaParser; + _imageSourceParser = imageSourceParser; _localLinkParser = localLinkParser; } @@ -45,7 +45,7 @@ namespace Umbraco.Web.PropertyEditors /// Create a custom value editor /// /// - protected override IDataValueEditor CreateValueEditor() => new RichTextPropertyValueEditor(Attribute, _umbracoContextAccessor, _mediaParser, _localLinkParser); + protected override IDataValueEditor CreateValueEditor() => new RichTextPropertyValueEditor(Attribute, _umbracoContextAccessor, _imageSourceParser, _localLinkParser); protected override IConfigurationEditor CreateConfigurationEditor() => new RichTextConfigurationEditor(); @@ -57,14 +57,14 @@ namespace Umbraco.Web.PropertyEditors internal class RichTextPropertyValueEditor : DataValueEditor, IDataValueReference { private IUmbracoContextAccessor _umbracoContextAccessor; - private readonly ImageSourceParser _mediaParser; - private readonly LocalLinkParser _localLinkParser; + private readonly HtmlImageSourceParser _imageSourceParser; + private readonly HtmlLocalLinkParser _localLinkParser; - public RichTextPropertyValueEditor(DataEditorAttribute attribute, IUmbracoContextAccessor umbracoContextAccessor, ImageSourceParser mediaParser, LocalLinkParser localLinkParser) + public RichTextPropertyValueEditor(DataEditorAttribute attribute, IUmbracoContextAccessor umbracoContextAccessor, HtmlImageSourceParser imageSourceParser, HtmlLocalLinkParser localLinkParser) : base(attribute) { _umbracoContextAccessor = umbracoContextAccessor; - _mediaParser = mediaParser; + _imageSourceParser = imageSourceParser; _localLinkParser = localLinkParser; } @@ -97,7 +97,7 @@ namespace Umbraco.Web.PropertyEditors if (val == null) return null; - var propertyValueWithMediaResolved = _mediaParser.EnsureImageSources(val.ToString()); + var propertyValueWithMediaResolved = _imageSourceParser.EnsureImageSources(val.ToString()); var parsed = MacroTagParser.FormatRichTextPersistedDataForEditor(propertyValueWithMediaResolved, new Dictionary()); return parsed; } @@ -119,8 +119,8 @@ namespace Umbraco.Web.PropertyEditors var mediaParent = config?.MediaParentId; var mediaParentId = mediaParent == null ? Guid.Empty : mediaParent.Guid; - var parseAndSavedTempImages = _mediaParser.FindAndPersistPastedTempImages(editorValue.Value.ToString(), mediaParentId, userId); - var editorValueWithMediaUrlsRemoved = _mediaParser.RemoveImageSources(parseAndSavedTempImages); + var parseAndSavedTempImages = _imageSourceParser.FindAndPersistPastedTempImages(editorValue.Value.ToString(), mediaParentId, userId); + var editorValueWithMediaUrlsRemoved = _imageSourceParser.RemoveImageSources(parseAndSavedTempImages); var parsed = MacroTagParser.FormatRichTextContentForPersistence(editorValueWithMediaUrlsRemoved); return parsed; @@ -135,7 +135,7 @@ namespace Umbraco.Web.PropertyEditors { var asString = value == null ? string.Empty : value is string str ? str : value.ToString(); - foreach (var udi in _mediaParser.FindUdisFromDataAttributes(asString)) + foreach (var udi in _imageSourceParser.FindUdisFromDataAttributes(asString)) yield return udi; foreach (var udi in _localLinkParser.FindUdisFromLocalLinks(asString)) diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs index e8a2ac11a6..c62a79d283 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs @@ -12,13 +12,13 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters [DefaultPropertyValueConverter] public class MarkdownEditorValueConverter : PropertyValueConverterBase { - private readonly LocalLinkParser _localLinkParser; - private readonly UrlParser _urlResolver; + private readonly HtmlLocalLinkParser _localLinkParser; + private readonly HtmlUrlParser _urlParser; - public MarkdownEditorValueConverter(LocalLinkParser localLinkParser, UrlParser urlResolver) + public MarkdownEditorValueConverter(HtmlLocalLinkParser localLinkParser, HtmlUrlParser urlParser) { _localLinkParser = localLinkParser; - _urlResolver = urlResolver; + _urlParser = urlParser; } public override bool IsConverter(IPublishedPropertyType propertyType) @@ -37,7 +37,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters // ensures string is parsed for {localLink} and urls are resolved correctly sourceString = _localLinkParser.EnsureInternalLinks(sourceString, preview); - sourceString = _urlResolver.EnsureUrls(sourceString); + sourceString = _urlParser.EnsureUrls(sourceString); return sourceString; } diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs index 2caac9e1f4..3ab502742c 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs @@ -24,9 +24,9 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters { private readonly IUmbracoContextAccessor _umbracoContextAccessor; private readonly IMacroRenderer _macroRenderer; - private readonly LocalLinkParser _internalLinkParser; - private readonly UrlParser _urlResolver; - private readonly ImageSourceParser _mediaParser; + private readonly HtmlLocalLinkParser _linkParser; + private readonly HtmlUrlParser _urlParser; + private readonly HtmlImageSourceParser _imageSourceParser; public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) { @@ -36,13 +36,13 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters } public RteMacroRenderingValueConverter(IUmbracoContextAccessor umbracoContextAccessor, IMacroRenderer macroRenderer, - LocalLinkParser internalLinkParser, UrlParser urlResolver, ImageSourceParser mediaParser) + HtmlLocalLinkParser linkParser, HtmlUrlParser urlParser, HtmlImageSourceParser imageSourceParser) { _umbracoContextAccessor = umbracoContextAccessor; _macroRenderer = macroRenderer; - _internalLinkParser = internalLinkParser; - _urlResolver = urlResolver; - _mediaParser = mediaParser; + _linkParser = linkParser; + _urlParser = urlParser; + _imageSourceParser = imageSourceParser; } // NOT thread-safe over a request because it modifies the @@ -88,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 = _internalLinkParser.EnsureInternalLinks(sourceString, preview); - sourceString = _urlResolver.EnsureUrls(sourceString); - sourceString = _mediaParser.EnsureImageSources(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 5efc2ee2db..939a658407 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/TextStringValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/TextStringValueConverter.cs @@ -11,9 +11,9 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters [DefaultPropertyValueConverter] public class TextStringValueConverter : PropertyValueConverterBase { - public TextStringValueConverter(LocalLinkParser internalLinkParser, UrlParser urlParser) + public TextStringValueConverter(HtmlLocalLinkParser linkParser, HtmlUrlParser urlParser) { - _internalLinkParser = internalLinkParser; + _linkParser = linkParser; _urlParser = urlParser; } @@ -22,8 +22,8 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters Constants.PropertyEditors.Aliases.TextBox, Constants.PropertyEditors.Aliases.TextArea }; - private readonly LocalLinkParser _internalLinkParser; - private readonly UrlParser _urlParser; + private readonly HtmlLocalLinkParser _linkParser; + private readonly HtmlUrlParser _urlParser; public override bool IsConverter(IPublishedPropertyType propertyType) => PropertyTypeAliases.Contains(propertyType.EditorAlias); @@ -40,7 +40,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters var sourceString = source.ToString(); // ensures string is parsed for {localLink} and urls are resolved correctly - sourceString = _internalLinkParser.EnsureInternalLinks(sourceString, preview); + sourceString = _linkParser.EnsureInternalLinks(sourceString, preview); sourceString = _urlParser.EnsureUrls(sourceString); return sourceString; diff --git a/src/Umbraco.Web/Runtime/WebInitialComposer.cs b/src/Umbraco.Web/Runtime/WebInitialComposer.cs index 2f78ac9732..5ccb16a1a5 100644 --- a/src/Umbraco.Web/Runtime/WebInitialComposer.cs +++ b/src/Umbraco.Web/Runtime/WebInitialComposer.cs @@ -107,9 +107,9 @@ namespace Umbraco.Web.Runtime composition.RegisterUnique(); composition.RegisterUnique(); - 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...) diff --git a/src/Umbraco.Web/Templates/ImageSourceParser.cs b/src/Umbraco.Web/Templates/HtmlImageSourceParser.cs similarity index 92% rename from src/Umbraco.Web/Templates/ImageSourceParser.cs rename to src/Umbraco.Web/Templates/HtmlImageSourceParser.cs index 6a0bba4998..b36542167c 100644 --- a/src/Umbraco.Web/Templates/ImageSourceParser.cs +++ b/src/Umbraco.Web/Templates/HtmlImageSourceParser.cs @@ -14,9 +14,9 @@ using Umbraco.Web.Routing; namespace Umbraco.Web.Templates { - public sealed class ImageSourceParser + public sealed class HtmlImageSourceParser { - public ImageSourceParser(IUmbracoContextAccessor umbracoContextAccessor, ILogger logger, IMediaService mediaService, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider) + public HtmlImageSourceParser(IUmbracoContextAccessor umbracoContextAccessor, ILogger logger, IMediaService mediaService, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider) { _umbracoContextAccessor = umbracoContextAccessor; _logger = logger; @@ -99,10 +99,17 @@ namespace Umbraco.Web.Templates ///
/// /// - internal string RemoveImageSources(string text) + public string RemoveImageSources(string text) // see comment in ResolveMediaFromTextString for group reference => ResolveImgPattern.Replace(text, "$1$3$4$5"); + /// + /// Used by the RTE (and grid RTE) for drag/drop/persisting images + /// + /// + /// + /// + /// internal string FindAndPersistPastedTempImages(string html, Guid mediaParentFolder, int userId) { // Find all img's that has data-tmpimg attribute @@ -199,7 +206,7 @@ namespace Umbraco.Web.Templates } catch (Exception ex) { - _logger.Error(typeof(ImageSourceParser), ex, "Could not delete temp file or folder {FileName}", absoluteTempImagePath); + _logger.Error(typeof(HtmlImageSourceParser), ex, "Could not delete temp file or folder {FileName}", absoluteTempImagePath); } } } diff --git a/src/Umbraco.Web/Templates/LocalLinkParser.cs b/src/Umbraco.Web/Templates/HtmlLocalLinkParser.cs similarity index 97% rename from src/Umbraco.Web/Templates/LocalLinkParser.cs rename to src/Umbraco.Web/Templates/HtmlLocalLinkParser.cs index f394f56d85..f65a7183b7 100644 --- a/src/Umbraco.Web/Templates/LocalLinkParser.cs +++ b/src/Umbraco.Web/Templates/HtmlLocalLinkParser.cs @@ -11,7 +11,7 @@ namespace Umbraco.Web.Templates /// /// Utility class used to parse internal links /// - public sealed class LocalLinkParser + public sealed class HtmlLocalLinkParser { private static readonly Regex LocalLinkPattern = new Regex(@"href=""[/]?(?:\{|\%7B)localLink:([a-zA-Z0-9-://]+)(?:\}|\%7D)", @@ -19,7 +19,7 @@ namespace Umbraco.Web.Templates private readonly IUmbracoContextAccessor _umbracoContextAccessor; - public LocalLinkParser(IUmbracoContextAccessor umbracoContextAccessor) + public HtmlLocalLinkParser(IUmbracoContextAccessor umbracoContextAccessor) { _umbracoContextAccessor = umbracoContextAccessor; } diff --git a/src/Umbraco.Web/Templates/UrlParser.cs b/src/Umbraco.Web/Templates/HtmlUrlParser.cs similarity index 95% rename from src/Umbraco.Web/Templates/UrlParser.cs rename to src/Umbraco.Web/Templates/HtmlUrlParser.cs index e5c40b7365..5b78477579 100644 --- a/src/Umbraco.Web/Templates/UrlParser.cs +++ b/src/Umbraco.Web/Templates/HtmlUrlParser.cs @@ -5,7 +5,7 @@ using Umbraco.Core.Logging; namespace Umbraco.Web.Templates { - public sealed class UrlParser + public sealed class HtmlUrlParser { private readonly IContentSection _contentSection; private readonly IProfilingLogger _logger; @@ -13,7 +13,7 @@ namespace Umbraco.Web.Templates private static readonly Regex ResolveUrlPattern = new Regex("(=[\"\']?)(\\W?\\~(?:.(?![\"\']?\\s+(?:\\S+)=|[>\"\']))+.)[\"\']?", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); - public UrlParser(IContentSection contentSection, IProfilingLogger logger) + public HtmlUrlParser(IContentSection contentSection, IProfilingLogger logger) { _contentSection = contentSection; _logger = logger; diff --git a/src/Umbraco.Web/Templates/TemplateUtilities.cs b/src/Umbraco.Web/Templates/TemplateUtilities.cs index db0366dbf3..bcc6691864 100644 --- a/src/Umbraco.Web/Templates/TemplateUtilities.cs +++ b/src/Umbraco.Web/Templates/TemplateUtilities.cs @@ -15,10 +15,10 @@ using File = System.IO.File; namespace Umbraco.Web.Templates { - [Obsolete("This class is obsolete, all methods have been moved to other classes such as InternalLinkHelper, UrlResolver and MediaParser")] + [Obsolete("This class is obsolete, all methods have been moved to other classes: HtmlLocalLinkParser, HtmlUrlParser and HtmlImageSourceParser")] public static class TemplateUtilities { - [Obsolete("Inject and use an instance of InternalLinkParser instead")] + [Obsolete("Inject and use an instance of HtmlLocalLinkParser instead")] internal static string ParseInternalLinks(string text, bool preview, UmbracoContext umbracoContext) { using (umbracoContext.ForcedPreview(preview)) // force for url provider @@ -29,28 +29,28 @@ namespace Umbraco.Web.Templates return text; } - [Obsolete("Inject and use an instance of InternalLinkParser instead")] + [Obsolete("Inject and use an instance of HtmlLocalLinkParser instead")] public static string ParseInternalLinks(string text, UrlProvider urlProvider) - => Current.Factory.GetInstance().EnsureInternalLinks(text); + => Current.Factory.GetInstance().EnsureInternalLinks(text); - [Obsolete("Inject and use an instance of UrlResolver")] + [Obsolete("Inject and use an instance of HtmlUrlParser")] public static string ResolveUrlsFromTextString(string text) - => Current.Factory.GetInstance().EnsureUrls(text); + => Current.Factory.GetInstance().EnsureUrls(text); [Obsolete("Use StringExtensions.CleanForXss instead")] public static string CleanForXss(string text, params char[] ignoreFromClean) => text.CleanForXss(ignoreFromClean); - [Obsolete("Use MediaParser.EnsureImageSources instead")] + [Obsolete("Use HtmlImageSourceParser.EnsureImageSources instead")] public static string ResolveMediaFromTextString(string text) - => Current.Factory.GetInstance().EnsureImageSources(text); + => Current.Factory.GetInstance().EnsureImageSources(text); - [Obsolete("Use MediaParser.RemoveImageSources instead")] + [Obsolete("Use HtmlImageSourceParser.RemoveImageSources instead")] internal static string RemoveMediaUrlsFromTextString(string text) - => Current.Factory.GetInstance().RemoveImageSources(text); + => Current.Factory.GetInstance().RemoveImageSources(text); - [Obsolete("Use MediaParser.RemoveImageSources instead")] + [Obsolete("Use HtmlImageSourceParser.FindAndPersistPastedTempImages instead")] internal static string FindAndPersistPastedTempImages(string html, Guid mediaParentFolder, int userId, IMediaService mediaService, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, ILogger logger) - => Current.Factory.GetInstance().FindAndPersistPastedTempImages(html, mediaParentFolder, userId); + => Current.Factory.GetInstance().FindAndPersistPastedTempImages(html, mediaParentFolder, userId); } } diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 8fc06c75c2..84645f3b2d 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -247,9 +247,9 @@ - - - + + + diff --git a/src/Umbraco.Web/UmbracoComponentRenderer.cs b/src/Umbraco.Web/UmbracoComponentRenderer.cs index 83c8a7f0fa..01c696fd2d 100644 --- a/src/Umbraco.Web/UmbracoComponentRenderer.cs +++ b/src/Umbraco.Web/UmbracoComponentRenderer.cs @@ -27,14 +27,14 @@ namespace Umbraco.Web private readonly IUmbracoContextAccessor _umbracoContextAccessor; private readonly IMacroRenderer _macroRenderer; private readonly ITemplateRenderer _templateRenderer; - private readonly LocalLinkParser _internalLinkParser; + private readonly HtmlLocalLinkParser _linkParser; - public UmbracoComponentRenderer(IUmbracoContextAccessor umbracoContextAccessor, IMacroRenderer macroRenderer, ITemplateRenderer templateRenderer, LocalLinkParser internalLinkParser) + public UmbracoComponentRenderer(IUmbracoContextAccessor umbracoContextAccessor, IMacroRenderer macroRenderer, ITemplateRenderer templateRenderer, HtmlLocalLinkParser linkParser) { _umbracoContextAccessor = umbracoContextAccessor; _macroRenderer = macroRenderer; _templateRenderer = templateRenderer ?? throw new ArgumentNullException(nameof(templateRenderer)); - _internalLinkParser = internalLinkParser; + _linkParser = linkParser; } /// @@ -159,7 +159,7 @@ namespace Umbraco.Web _umbracoContextAccessor.UmbracoContext.HttpContext.Response.ContentType = contentType; //Now, we need to ensure that local links are parsed - html = _internalLinkParser.EnsureInternalLinks(output.ToString()); + html = _linkParser.EnsureInternalLinks(output.ToString()); } } From ba8c1df017d1595abfeee2dbb659b116406fdcd3 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 23 Oct 2019 15:38:14 +1100 Subject: [PATCH 062/548] fixing tests --- src/Umbraco.Tests/Testing/UmbracoTestBase.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs index 7e72a5aefb..ec265bd540 100644 --- a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs +++ b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs @@ -41,6 +41,7 @@ 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; namespace Umbraco.Tests.Testing { @@ -230,6 +231,10 @@ namespace Umbraco.Tests.Testing .Append(); Composition.RegisterUnique(); + Composition.RegisterUnique(); + Composition.RegisterUnique(); + Composition.RegisterUnique(); + } protected virtual void ComposeMisc() From c831c9de53b59a39734f3f38e06f89031cfb9b69 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 23 Oct 2019 16:30:22 +1100 Subject: [PATCH 063/548] uses nameof in attributes --- src/Umbraco.Web/Templates/TemplateUtilities.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web/Templates/TemplateUtilities.cs b/src/Umbraco.Web/Templates/TemplateUtilities.cs index bcc6691864..62d25fa794 100644 --- a/src/Umbraco.Web/Templates/TemplateUtilities.cs +++ b/src/Umbraco.Web/Templates/TemplateUtilities.cs @@ -15,10 +15,10 @@ using File = System.IO.File; namespace Umbraco.Web.Templates { - [Obsolete("This class is obsolete, all methods have been moved to other classes: HtmlLocalLinkParser, HtmlUrlParser and HtmlImageSourceParser")] + [Obsolete("This class is obsolete, all methods have been moved to other classes: " + nameof(HtmlLocalLinkParser) + ", " + nameof(HtmlUrlParser) + " and " + nameof(HtmlImageSourceParser))] public static class TemplateUtilities { - [Obsolete("Inject and use an instance of HtmlLocalLinkParser instead")] + [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 @@ -29,27 +29,27 @@ namespace Umbraco.Web.Templates return text; } - [Obsolete("Inject and use an instance of HtmlLocalLinkParser instead")] + [Obsolete("Inject and use an instance of " + nameof(HtmlLocalLinkParser) + " instead")] public static string ParseInternalLinks(string text, UrlProvider urlProvider) => Current.Factory.GetInstance().EnsureInternalLinks(text); - [Obsolete("Inject and use an instance of HtmlUrlParser")] + [Obsolete("Inject and use an instance of " + nameof(HtmlUrlParser))] public static string ResolveUrlsFromTextString(string text) => Current.Factory.GetInstance().EnsureUrls(text); - [Obsolete("Use StringExtensions.CleanForXss instead")] + [Obsolete("Use " + nameof(StringExtensions) + "." + nameof(StringExtensions.CleanForXss) + " instead")] public static string CleanForXss(string text, params char[] ignoreFromClean) => text.CleanForXss(ignoreFromClean); - [Obsolete("Use HtmlImageSourceParser.EnsureImageSources instead")] + [Obsolete("Use " + nameof(HtmlImageSourceParser) + "." + nameof(HtmlImageSourceParser.EnsureImageSources) + " instead")] public static string ResolveMediaFromTextString(string text) => Current.Factory.GetInstance().EnsureImageSources(text); - [Obsolete("Use HtmlImageSourceParser.RemoveImageSources instead")] + [Obsolete("Use " + nameof(HtmlImageSourceParser) + "." + nameof(HtmlImageSourceParser.RemoveImageSources) + " instead")] internal static string RemoveMediaUrlsFromTextString(string text) => Current.Factory.GetInstance().RemoveImageSources(text); - [Obsolete("Use HtmlImageSourceParser.FindAndPersistPastedTempImages instead")] + [Obsolete("Use " + nameof(HtmlImageSourceParser) + "." + nameof(HtmlImageSourceParser.FindAndPersistPastedTempImages) + " instead")] internal static string FindAndPersistPastedTempImages(string html, Guid mediaParentFolder, int userId, IMediaService mediaService, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, ILogger logger) => Current.Factory.GetInstance().FindAndPersistPastedTempImages(html, mediaParentFolder, userId); } From 407089c67add300bb34223d5787cbc5abd4c186a Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 23 Oct 2019 08:38:24 +0200 Subject: [PATCH 064/548] Updated Microsoft.CodeAnalysis.CSharp to match the rest of umbraco --- src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj b/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj index 4dbe606a66..e8fca43e04 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj +++ b/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj @@ -82,7 +82,7 @@ - 2.8.0 + 2.10.0 1.0.0-beta2-19324-01 From a9b88e4520db25557f7e5cfb9bb652f4b7d74e52 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 23 Oct 2019 09:41:12 +0200 Subject: [PATCH 065/548] AB3267 - Removed outdated comments --- src/Umbraco.ModelsBuilder/Building/Builder.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Umbraco.ModelsBuilder/Building/Builder.cs b/src/Umbraco.ModelsBuilder/Building/Builder.cs index fd5eccde5b..1d14155d62 100644 --- a/src/Umbraco.ModelsBuilder/Building/Builder.cs +++ b/src/Umbraco.ModelsBuilder/Building/Builder.cs @@ -57,7 +57,7 @@ namespace Umbraco.ModelsBuilder.Building /// /// Gets the list of models to generate. /// - /// The models to generate, ie those that are not ignored. + /// The models to generate public IEnumerable GetModelsToGenerate() { return _typeModels; @@ -102,10 +102,6 @@ namespace Umbraco.ModelsBuilder.Building /// /// Prepares generation by processing the result of code parsing. /// - /// - /// Preparation includes figuring out from the existing code which models or properties should - /// be ignored or renamed, etc. -- anything that comes from the attributes in the existing code. - /// private void Prepare() { TypeModel.MapModelTypes(_typeModels, ModelsNamespace); From b5ae96e8ef2e6d6d8e33ff698402177462fb9045 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 23 Oct 2019 09:41:35 +0200 Subject: [PATCH 066/548] AB3267 - Specify language version to make azure pipeline happy --- src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj b/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj index e8fca43e04..20b0f7fcf3 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj +++ b/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj @@ -12,6 +12,7 @@ v4.7.2 512 true + latast true From 3905265aba8086f925a03c2dae01c15afc177987 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 23 Oct 2019 09:52:41 +0200 Subject: [PATCH 067/548] AB3267 - Specify language version to make azure pipeline happy --- src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj b/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj index 20b0f7fcf3..a16579e60d 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj +++ b/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj @@ -12,7 +12,7 @@ v4.7.2 512 true - latast + 7.3 true From 9303a497328116a3b995eefaa6dcb7509c226799 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 23 Oct 2019 19:08:03 +1100 Subject: [PATCH 068/548] Moves the copy/paste rte stuff to a separate service, injects lazy property editors and relation service into the base content repositories --- .../Implement/ContentRepositoryBase.cs | 28 +++- .../Implement/DocumentBlueprintRepository.cs | 7 +- .../Implement/DocumentRepository.cs | 20 ++- .../Repositories/Implement/MediaRepository.cs | 6 +- .../Implement/MemberRepository.cs | 7 +- .../PropertyEditorCollection.cs | 4 +- .../Repositories/ContentTypeRepositoryTest.cs | 6 +- .../Repositories/DocumentRepositoryTest.cs | 5 +- .../Repositories/DomainRepositoryTest.cs | 9 +- .../Repositories/MediaRepositoryTest.cs | 6 +- .../Repositories/MemberRepositoryTest.cs | 6 +- .../PublicAccessRepositoryTest.cs | 6 +- .../Repositories/TagRepositoryTest.cs | 54 ++++--- .../Repositories/TemplateRepositoryTest.cs | 12 +- .../Repositories/UserRepositoryTest.cs | 12 +- .../PublishedContentTestBase.cs | 5 +- .../PublishedContent/PublishedContentTests.cs | 5 +- .../Services/ContentServicePerformanceTest.cs | 43 +++--- .../Services/ContentServiceTests.cs | 5 +- .../Templates/ImageSourceParserTests.cs | 9 +- src/Umbraco.Tests/Testing/UmbracoTestBase.cs | 2 + .../PropertyEditors/GridPropertyEditor.cs | 13 +- .../RichTextEditorPastedImages.cs | 143 ++++++++++++++++++ .../PropertyEditors/RichTextPropertyEditor.cs | 12 +- src/Umbraco.Web/Runtime/WebInitialComposer.cs | 2 + .../Templates/HtmlImageSourceParser.cs | 133 +--------------- .../Templates/TemplateUtilities.cs | 5 +- src/Umbraco.Web/Umbraco.Web.csproj | 1 + 28 files changed, 346 insertions(+), 220 deletions(-) create mode 100644 src/Umbraco.Web/PropertyEditors/RichTextEditorPastedImages.cs diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs index 7ab9f10e1c..eede78f4c7 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs @@ -31,20 +31,37 @@ namespace Umbraco.Core.Persistence.Repositories.Implement } 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; + + /// + /// + /// + /// + /// + /// + /// + /// + /// 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, + Lazy propertyEditors) : base(scopeAccessor, cache, logger) { LanguageRepository = languageRepository; + RelationRepository = relationRepository; + _propertyEditors = propertyEditors; } protected abstract TRepository This { get; } protected ILanguageRepository LanguageRepository { get; } + protected IRelationRepository RelationRepository { get; } - protected PropertyEditorCollection PropertyEditors => Current.PropertyEditors; // TODO: inject ... this causes circular refs, not sure which refs they are though + protected PropertyEditorCollection PropertyEditors => _propertyEditors.Value; #region Versions @@ -798,5 +815,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement } #endregion + + protected void PersistRelations(TEntity entity) + { + //foreach(var p in entity.) + } } } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentBlueprintRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentBlueprintRepository.cs index d137d7ac76..de766ee5aa 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, + Lazy propertyEditorCollection) + : base(scopeAccessor, appCaches, logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, propertyEditorCollection) { } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs index 2649b9993f..067086ea6b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs @@ -12,6 +12,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; @@ -30,8 +31,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, + Lazy propertyEditors) + : base(scopeAccessor, appCaches, logger, languageRepository, relationRepository, propertyEditors) { _contentTypeRepository = contentTypeRepository ?? throw new ArgumentNullException(nameof(contentTypeRepository)); _templateRepository = templateRepository ?? throw new ArgumentNullException(nameof(templateRepository)); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs index 25828b8126..0da35145cc 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, + Lazy propertyEditorCollection) + : base(scopeAccessor, cache, logger, languageRepository, relationRepository, propertyEditorCollection) { _mediaTypeRepository = mediaTypeRepository ?? throw new ArgumentNullException(nameof(mediaTypeRepository)); _tagRepository = tagRepository ?? throw new ArgumentNullException(nameof(tagRepository)); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs index 892122dff9..5274e99cc9 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, + Lazy propertyEditors) + : base(scopeAccessor, cache, logger, languageRepository, relationRepository, propertyEditors) { _memberTypeRepository = memberTypeRepository ?? throw new ArgumentNullException(nameof(memberTypeRepository)); _tagRepository = tagRepository ?? throw new ArgumentNullException(nameof(tagRepository)); 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.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs index f953b9cce6..4d2e8bc999 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,10 @@ 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 relationRepository = new RelationRepository(scopeAccessor, Logger, relationTypeRepository); + var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); + var repository = new DocumentRepository(scopeAccessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, propertyEditors); return repository; } diff --git a/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs index 4d62ec8301..c2f897df80 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs @@ -69,7 +69,10 @@ 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 relationRepository = new RelationRepository(scopeAccessor, Logger, relationTypeRepository); + var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); + var repository = new DocumentRepository(scopeAccessor, appCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, propertyEditors); return repository; } diff --git a/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs index 628f8d75a7..16a59d789f 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,10 @@ 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 relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository); + var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); + documentRepository = new DocumentRepository(accessor, Core.Cache.AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, propertyEditors); var domainRepository = new DomainRepository(accessor, Core.Cache.AppCaches.Disabled, Logger); return domainRepository; } diff --git a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs index e2123df9e3..c976912220 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,10 @@ 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 relationRepository = new RelationRepository(scopeAccessor, Logger, relationTypeRepository); + var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); + var repository = new MediaRepository(scopeAccessor, appCaches, Logger, mediaTypeRepository, tagRepository, Mock.Of(), relationRepository, propertyEditors); return repository; } diff --git a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs index 17b16ad7ab..9361bd5909 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,10 @@ 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 relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository); + var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); + var repository = new MemberRepository(accessor, AppCaches.Disabled, Logger, memberTypeRepository, memberGroupRepository, tagRepo, Mock.Of(), relationRepository, propertyEditors); return repository; } diff --git a/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs index 56041c24aa..8cabc18d2c 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,10 @@ 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 relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository); + var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); + var repository = new DocumentRepository(accessor, AppCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, propertyEditors); return repository; } diff --git a/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs index e3de2c2892..61aec9f74c 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,10 @@ 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 relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository); + var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); + var repository = new DocumentRepository(accessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, propertyEditors); return repository; } @@ -970,7 +975,10 @@ 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 relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository); + var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); + var repository = new MediaRepository(accessor, AppCaches.Disabled, Logger, mediaTypeRepository, tagRepository, Mock.Of(), relationRepository, propertyEditors); return repository; } } diff --git a/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs index b0f9a5335b..7bf113dfc3 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,14 @@ 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 relationRepository = new RelationRepository(ScopeProvider, Logger, relationTypeRepository); + var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); + var contentRepo = new DocumentRepository(ScopeProvider, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, propertyEditors); 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..01a5573119 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,10 @@ 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 relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository); + var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); + var repository = new MediaRepository(accessor, AppCaches, Mock.Of(), mediaTypeRepository, tagRepository, Mock.Of(), relationRepository, propertyEditors); return repository; } @@ -47,7 +52,10 @@ 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 relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository); + var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); + var repository = new DocumentRepository(accessor, AppCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, propertyEditors); return repository; } diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs index 497c621963..a96cad4076 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs @@ -42,10 +42,11 @@ namespace Umbraco.Tests.PublishedContent var umbracoContextAccessor = Mock.Of(); var logger = Mock.Of(); - var imageSourceParser = new HtmlImageSourceParser(umbracoContextAccessor, logger, Mock.Of(), 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(logger, umbracoContextAccessor, imageSourceParser, localLinkParser)) { Id = 1 }); + new DataType(new RichTextPropertyEditor(logger, umbracoContextAccessor, imageSourceParser, localLinkParser, pastedImages)) { 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 9f5dc81986..be63e3b5da 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs @@ -46,13 +46,14 @@ namespace Umbraco.Tests.PublishedContent var mediaService = Mock.Of(); var contentTypeBaseServiceProvider = Mock.Of(); var umbracoContextAccessor = Mock.Of(); - var imageSourceParser = new HtmlImageSourceParser(umbracoContextAccessor, logger, mediaService, contentTypeBaseServiceProvider); + 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, umbracoContextAccessor, imageSourceParser, linkParser)) { Id = 1002 }, + new DataType(new RichTextPropertyEditor(logger, umbracoContextAccessor, imageSourceParser, linkParser, pastedImages)) { 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/Services/ContentServicePerformanceTest.cs b/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs index ef80672baf..5de5d9bd16 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,20 @@ namespace Umbraco.Tests.Services Composition.Register(); } + private DocumentRepository CreateDocumentRepository(IScopeProvider provider) + { + 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 relationTypeRepository = new RelationTypeRepository((IScopeAccessor)provider, AppCaches.Disabled, Logger); + var relationRepository = new RelationRepository((IScopeAccessor)provider, Logger, relationTypeRepository); + var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); + var repository = new DocumentRepository((IScopeAccessor)provider, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository, relationRepository, propertyEditors); + return repository; + } + [Test] public void Profiler() { @@ -163,12 +178,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 +207,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 +234,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 +264,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..bd97772d12 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -3166,7 +3166,10 @@ 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 relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository); + var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); + var repository = new DocumentRepository(accessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, propertyEditors); return repository; } diff --git a/src/Umbraco.Tests/Templates/ImageSourceParserTests.cs b/src/Umbraco.Tests/Templates/ImageSourceParserTests.cs index ca01caf344..dbe5654117 100644 --- a/src/Umbraco.Tests/Templates/ImageSourceParserTests.cs +++ b/src/Umbraco.Tests/Templates/ImageSourceParserTests.cs @@ -13,6 +13,7 @@ using System; using System.Linq; using Umbraco.Core.Models; using Umbraco.Core; +using Umbraco.Web.PropertyEditors; namespace Umbraco.Tests.Templates { @@ -32,8 +33,8 @@ namespace Umbraco.Tests.Templates var logger = Mock.Of(); var umbracoContextAccessor = new TestUmbracoContextAccessor(); - var imageSourceParser = new HtmlImageSourceParser(umbracoContextAccessor, logger, Mock.Of(), Mock.Of()); - + 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]); @@ -45,7 +46,7 @@ namespace Umbraco.Tests.Templates { var logger = Mock.Of(); var umbracoContextAccessor = new TestUmbracoContextAccessor(); - var imageSourceParser = new HtmlImageSourceParser(umbracoContextAccessor, logger, Mock.Of(), Mock.Of()); + var imageSourceParser = new HtmlImageSourceParser(umbracoContextAccessor); var result = imageSourceParser.RemoveImageSources(@"

@@ -87,7 +88,7 @@ namespace Umbraco.Tests.Templates var mediaCache = Mock.Get(reference.UmbracoContext.Media); mediaCache.Setup(x => x.GetById(It.IsAny())).Returns(media.Object); - var imageSourceParser = new HtmlImageSourceParser(umbracoContextAccessor, Mock.Of(), Mock.Of(), Mock.Of()); + var imageSourceParser = new HtmlImageSourceParser(umbracoContextAccessor); var result = imageSourceParser.EnsureImageSources(@"

diff --git a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs index ec265bd540..ffc49b47ac 100644 --- a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs +++ b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs @@ -42,6 +42,7 @@ using Umbraco.Web.Sections; using Current = Umbraco.Core.Composing.Current; using FileSystems = Umbraco.Core.IO.FileSystems; using Umbraco.Web.Templates; +using Umbraco.Web.PropertyEditors; namespace Umbraco.Tests.Testing { @@ -234,6 +235,7 @@ namespace Umbraco.Tests.Testing Composition.RegisterUnique(); Composition.RegisterUnique(); Composition.RegisterUnique(); + Composition.RegisterUnique(); } diff --git a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs index 6481099f45..4eea36300b 100644 --- a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs @@ -12,6 +12,7 @@ using Umbraco.Web.Templates; namespace Umbraco.Web.PropertyEditors { + /// /// Represents a grid property and parameter editor. /// @@ -27,12 +28,14 @@ namespace Umbraco.Web.PropertyEditors { private IUmbracoContextAccessor _umbracoContextAccessor; private readonly HtmlImageSourceParser _imageSourceParser; + private readonly RichTextEditorPastedImages _pastedImages; - public GridPropertyEditor(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, HtmlImageSourceParser imageSourceParser) + public GridPropertyEditor(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, HtmlImageSourceParser imageSourceParser, RichTextEditorPastedImages pastedImages) : base(logger) { _umbracoContextAccessor = umbracoContextAccessor; _imageSourceParser = imageSourceParser; + _pastedImages = pastedImages; } public override IPropertyIndexValueFactory PropertyIndexValueFactory => new GridPropertyIndexValueFactory(); @@ -41,7 +44,7 @@ namespace Umbraco.Web.PropertyEditors /// Overridden to ensure that the value is validated ///
/// - protected override IDataValueEditor CreateValueEditor() => new GridPropertyValueEditor(Attribute, _umbracoContextAccessor, _imageSourceParser); + protected override IDataValueEditor CreateValueEditor() => new GridPropertyValueEditor(Attribute, _umbracoContextAccessor, _imageSourceParser, _pastedImages); protected override IConfigurationEditor CreateConfigurationEditor() => new GridConfigurationEditor(); @@ -49,12 +52,14 @@ namespace Umbraco.Web.PropertyEditors { private IUmbracoContextAccessor _umbracoContextAccessor; private readonly HtmlImageSourceParser _imageSourceParser; + private readonly RichTextEditorPastedImages _pastedImages; - public GridPropertyValueEditor(DataEditorAttribute attribute, IUmbracoContextAccessor umbracoContextAccessor, HtmlImageSourceParser imageSourceParser) + public GridPropertyValueEditor(DataEditorAttribute attribute, IUmbracoContextAccessor umbracoContextAccessor, HtmlImageSourceParser imageSourceParser, RichTextEditorPastedImages pastedImages) : base(attribute) { _umbracoContextAccessor = umbracoContextAccessor; _imageSourceParser = imageSourceParser; + _pastedImages = pastedImages; } /// @@ -89,7 +94,7 @@ namespace Umbraco.Web.PropertyEditors // Parse the HTML var html = rte.Value?.ToString(); - var parseAndSavedTempImages = _imageSourceParser.FindAndPersistPastedTempImages(html, mediaParentId, userId); + var parseAndSavedTempImages = _pastedImages.FindAndPersistPastedTempImages(html, mediaParentId, userId); var editorValueWithMediaUrlsRemoved = _imageSourceParser.RemoveImageSources(parseAndSavedTempImages); rte.Value = editorValueWithMediaUrlsRemoved; diff --git a/src/Umbraco.Web/PropertyEditors/RichTextEditorPastedImages.cs b/src/Umbraco.Web/PropertyEditors/RichTextEditorPastedImages.cs new file mode 100644 index 0000000000..0b2a607f8b --- /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) + { + // 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 = $"{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(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 0dbe5426a2..0287bdfefe 100644 --- a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs @@ -28,24 +28,26 @@ namespace Umbraco.Web.PropertyEditors private IUmbracoContextAccessor _umbracoContextAccessor; private readonly HtmlImageSourceParser _imageSourceParser; private readonly HtmlLocalLinkParser _localLinkParser; + private readonly RichTextEditorPastedImages _pastedImages; /// /// 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) + public RichTextPropertyEditor(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, HtmlImageSourceParser imageSourceParser, HtmlLocalLinkParser localLinkParser, RichTextEditorPastedImages pastedImages) : base(logger) { _umbracoContextAccessor = umbracoContextAccessor; _imageSourceParser = imageSourceParser; _localLinkParser = localLinkParser; + _pastedImages = pastedImages; } /// /// Create a custom value editor /// /// - protected override IDataValueEditor CreateValueEditor() => new RichTextPropertyValueEditor(Attribute, _umbracoContextAccessor, _imageSourceParser, _localLinkParser); + protected override IDataValueEditor CreateValueEditor() => new RichTextPropertyValueEditor(Attribute, _umbracoContextAccessor, _imageSourceParser, _localLinkParser, _pastedImages); protected override IConfigurationEditor CreateConfigurationEditor() => new RichTextConfigurationEditor(); @@ -59,13 +61,15 @@ namespace Umbraco.Web.PropertyEditors private IUmbracoContextAccessor _umbracoContextAccessor; private readonly HtmlImageSourceParser _imageSourceParser; private readonly HtmlLocalLinkParser _localLinkParser; + private readonly RichTextEditorPastedImages _pastedImages; - public RichTextPropertyValueEditor(DataEditorAttribute attribute, IUmbracoContextAccessor umbracoContextAccessor, HtmlImageSourceParser imageSourceParser, HtmlLocalLinkParser localLinkParser) + public RichTextPropertyValueEditor(DataEditorAttribute attribute, IUmbracoContextAccessor umbracoContextAccessor, HtmlImageSourceParser imageSourceParser, HtmlLocalLinkParser localLinkParser, RichTextEditorPastedImages pastedImages) : base(attribute) { _umbracoContextAccessor = umbracoContextAccessor; _imageSourceParser = imageSourceParser; _localLinkParser = localLinkParser; + _pastedImages = pastedImages; } /// @@ -119,7 +123,7 @@ namespace Umbraco.Web.PropertyEditors var mediaParent = config?.MediaParentId; var mediaParentId = mediaParent == null ? Guid.Empty : mediaParent.Guid; - var parseAndSavedTempImages = _imageSourceParser.FindAndPersistPastedTempImages(editorValue.Value.ToString(), mediaParentId, userId); + var parseAndSavedTempImages = _pastedImages.FindAndPersistPastedTempImages(editorValue.Value.ToString(), mediaParentId, userId); var editorValueWithMediaUrlsRemoved = _imageSourceParser.RemoveImageSources(parseAndSavedTempImages); var parsed = MacroTagParser.FormatRichTextContentForPersistence(editorValueWithMediaUrlsRemoved); diff --git a/src/Umbraco.Web/Runtime/WebInitialComposer.cs b/src/Umbraco.Web/Runtime/WebInitialComposer.cs index 5ccb16a1a5..4de5e8627a 100644 --- a/src/Umbraco.Web/Runtime/WebInitialComposer.cs +++ b/src/Umbraco.Web/Runtime/WebInitialComposer.cs @@ -41,6 +41,7 @@ using Umbraco.Web.Tour; using Umbraco.Web.Trees; using Umbraco.Web.WebApi; using Current = Umbraco.Web.Composing.Current; +using Umbraco.Web.PropertyEditors; namespace Umbraco.Web.Runtime { @@ -110,6 +111,7 @@ namespace Umbraco.Web.Runtime 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...) diff --git a/src/Umbraco.Web/Templates/HtmlImageSourceParser.cs b/src/Umbraco.Web/Templates/HtmlImageSourceParser.cs index b36542167c..66a1cee5bb 100644 --- a/src/Umbraco.Web/Templates/HtmlImageSourceParser.cs +++ b/src/Umbraco.Web/Templates/HtmlImageSourceParser.cs @@ -1,36 +1,20 @@ -using HtmlAgilityPack; -using System; -using System.Collections.Generic; -using System.IO; +using System.Collections.Generic; using System.Text.RegularExpressions; 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.Routing; namespace Umbraco.Web.Templates { public sealed class HtmlImageSourceParser { - public HtmlImageSourceParser(IUmbracoContextAccessor umbracoContextAccessor, ILogger logger, IMediaService mediaService, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider) + public HtmlImageSourceParser(IUmbracoContextAccessor umbracoContextAccessor) { _umbracoContextAccessor = umbracoContextAccessor; - _logger = logger; - _mediaService = mediaService; - _contentTypeBaseServiceProvider = contentTypeBaseServiceProvider; } private static readonly Regex ResolveImgPattern = new Regex(@"(]*src="")([^""\?]*)([^""]*""[^>]*data-udi="")([^""]*)(""[^>]*>)", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); - private readonly IUmbracoContextAccessor _umbracoContextAccessor; - private readonly ILogger _logger; - private readonly IMediaService _mediaService; - private readonly IContentTypeBaseServiceProvider _contentTypeBaseServiceProvider; - const string TemporaryImageDataAttribute = "data-tmpimg"; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; private static readonly Regex DataUdiAttributeRegex = new Regex(@"data-udi=\\?(?:""|')(?umb://[A-z0-9\-]+/[A-z0-9]+)\\?(?:""|')", RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase); @@ -103,115 +87,6 @@ namespace Umbraco.Web.Templates // see comment in ResolveMediaFromTextString for group reference => ResolveImgPattern.Replace(text, "$1$3$4$5"); - /// - /// Used by the RTE (and grid RTE) for drag/drop/persisting images - /// - /// - /// - /// - /// - internal string FindAndPersistPastedTempImages(string html, Guid mediaParentFolder, int userId) - { - // 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 = $"{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(HtmlImageSourceParser), ex, "Could not delete temp file or folder {FileName}", absoluteTempImagePath); - } - } - } - - return htmlDoc.DocumentNode.OuterHtml; - } + } } diff --git a/src/Umbraco.Web/Templates/TemplateUtilities.cs b/src/Umbraco.Web/Templates/TemplateUtilities.cs index 62d25fa794..f796985d39 100644 --- a/src/Umbraco.Web/Templates/TemplateUtilities.cs +++ b/src/Umbraco.Web/Templates/TemplateUtilities.cs @@ -8,6 +8,7 @@ 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; @@ -49,8 +50,8 @@ namespace Umbraco.Web.Templates internal static string RemoveMediaUrlsFromTextString(string text) => Current.Factory.GetInstance().RemoveImageSources(text); - [Obsolete("Use " + nameof(HtmlImageSourceParser) + "." + nameof(HtmlImageSourceParser.FindAndPersistPastedTempImages) + " instead")] + [Obsolete("Use " + nameof(HtmlImageSourceParser) + "." + nameof(RichTextEditorPastedImages.FindAndPersistPastedTempImages) + " instead")] internal static string FindAndPersistPastedTempImages(string html, Guid mediaParentFolder, int userId, IMediaService mediaService, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, ILogger logger) - => Current.Factory.GetInstance().FindAndPersistPastedTempImages(html, mediaParentFolder, userId); + => Current.Factory.GetInstance().FindAndPersistPastedTempImages(html, mediaParentFolder, userId); } } diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index cf3de1de71..18b8ad938d 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -228,6 +228,7 @@ + From 25d9b9efd1702fbcbc6d44abc7bf98724bdde9ef Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 23 Oct 2019 13:53:06 +0200 Subject: [PATCH 069/548] AB3267 - Made Umbraco core internals visible for Umbraco.ModelsBuilder and uses PanicException instead of just Exception --- src/Umbraco.Core/Properties/AssemblyInfo.cs | 1 + .../Umbraco/ModelsBuilderBackOfficeController.cs | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Properties/AssemblyInfo.cs b/src/Umbraco.Core/Properties/AssemblyInfo.cs index afd602cfc9..139af8725d 100644 --- a/src/Umbraco.Core/Properties/AssemblyInfo.cs +++ b/src/Umbraco.Core/Properties/AssemblyInfo.cs @@ -14,6 +14,7 @@ using System.Runtime.InteropServices; [assembly: InternalsVisibleTo("Umbraco.Web")] [assembly: InternalsVisibleTo("Umbraco.Web.UI")] [assembly: InternalsVisibleTo("Umbraco.Examine")] +[assembly: InternalsVisibleTo("Umbraco.ModelsBuilder")] [assembly: InternalsVisibleTo("Umbraco.Tests")] [assembly: InternalsVisibleTo("Umbraco.Tests.Benchmarks")] diff --git a/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderBackOfficeController.cs b/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderBackOfficeController.cs index b1e6fbb1fb..a3ff89595c 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderBackOfficeController.cs +++ b/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderBackOfficeController.cs @@ -6,6 +6,7 @@ using System.Net.Http; using System.Runtime.Serialization; using System.Text; using System.Web.Hosting; +using Umbraco.Core.Exceptions; using Umbraco.ModelsBuilder.Building; using Umbraco.ModelsBuilder.Configuration; using Umbraco.ModelsBuilder.Dashboard; @@ -30,7 +31,7 @@ namespace Umbraco.ModelsBuilder.Umbraco public ModelsBuilderBackOfficeController(UmbracoServices umbracoServices, Config config) { - _umbracoServices = umbracoServices; + //_umbracoServices = umbracoServices; _config = config; } @@ -54,7 +55,7 @@ namespace Umbraco.ModelsBuilder.Umbraco var bin = HostingEnvironment.MapPath("~/bin"); if (bin == null) - throw new Exception("Panic: bin is null."); + throw new PanicException("bin is null."); // EnableDllModels will recycle the app domain - but this request will end properly GenerateModels(modelsDirectory); From 43343166c554fa6160579ceb463cd9ff157ba96c Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 23 Oct 2019 14:11:24 +0200 Subject: [PATCH 070/548] AB3267 - refactored new Exception(..) to most explicit exceptions --- src/Umbraco.ModelsBuilder/Building/Builder.cs | 4 ++-- .../Umbraco/LiveModelsProvider.cs | 3 ++- .../Umbraco/ModelsBuilderComponent.cs | 10 +++++----- src/Umbraco.ModelsBuilder/Umbraco/UmbracoServices.cs | 11 ++++++----- 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/Umbraco.ModelsBuilder/Building/Builder.cs b/src/Umbraco.ModelsBuilder/Building/Builder.cs index 1d14155d62..f65d8e9e7c 100644 --- a/src/Umbraco.ModelsBuilder/Building/Builder.cs +++ b/src/Umbraco.ModelsBuilder/Building/Builder.cs @@ -186,11 +186,11 @@ namespace Umbraco.ModelsBuilder.Building foreach (var typeModel in _typeModels.Where(x => x.IsElement)) { if (typeModel.BaseType != null && !typeModel.BaseType.IsElement) - throw new Exception($"Cannot generate model for type '{typeModel.Alias}' because it is an element type, but its parent type '{typeModel.BaseType.Alias}' is not."); + throw new InvalidOperationException($"Cannot generate model for type '{typeModel.Alias}' because it is an element type, but its parent type '{typeModel.BaseType.Alias}' is not."); var errs = typeModel.MixinTypes.Where(x => !x.IsElement).ToList(); if (errs.Count > 0) - throw new Exception($"Cannot generate model for type '{typeModel.Alias}' because it is an element type, but it is composed of {string.Join(", ", errs.Select(x => "'" + x.Alias + "'"))} which {(errs.Count == 1 ? "is" : "are")} not."); + throw new InvalidOperationException($"Cannot generate model for type '{typeModel.Alias}' because it is an element type, but it is composed of {string.Join(", ", errs.Select(x => "'" + x.Alias + "'"))} which {(errs.Count == 1 ? "is" : "are")} not."); } } diff --git a/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProvider.cs b/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProvider.cs index 194e7de695..d8839a1297 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProvider.cs +++ b/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProvider.cs @@ -3,6 +3,7 @@ using System.Threading; using System.Web; using System.Web.Hosting; using Umbraco.Core.Composing; +using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; using Umbraco.ModelsBuilder.Configuration; using Umbraco.ModelsBuilder.Umbraco; @@ -103,7 +104,7 @@ namespace Umbraco.ModelsBuilder.Umbraco var bin = HostingEnvironment.MapPath("~/bin"); if (bin == null) - throw new Exception("Panic: bin is null."); + throw new PanicException("Panic: bin is null."); // EnableDllModels will recycle the app domain - but this request will end properly ModelsBuilderBackOfficeController.GenerateModels(_umbracoServices, modelsDirectory, modelsNamespace); diff --git a/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderComponent.cs b/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderComponent.cs index 70d55368ac..4a02ccf82f 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderComponent.cs +++ b/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderComponent.cs @@ -56,17 +56,17 @@ namespace Umbraco.ModelsBuilder.Umbraco ServerVariablesParser.Parsing += (sender, serverVars) => { if (!serverVars.ContainsKey("umbracoUrls")) - throw new Exception("Missing umbracoUrls."); + throw new ArgumentException("Missing umbracoUrls."); var umbracoUrlsObject = serverVars["umbracoUrls"]; if (umbracoUrlsObject == null) - throw new Exception("Null umbracoUrls"); + throw new ArgumentException("Null umbracoUrls"); if (!(umbracoUrlsObject is Dictionary umbracoUrls)) - throw new Exception("Invalid umbracoUrls"); + throw new ArgumentException("Invalid umbracoUrls"); if (!serverVars.ContainsKey("umbracoPlugins")) - throw new Exception("Missing umbracoPlugins."); + throw new ArgumentException("Missing umbracoPlugins."); if (!(serverVars["umbracoPlugins"] is Dictionary umbracoPlugins)) - throw new Exception("Invalid umbracoPlugins"); + throw new ArgumentException("Invalid umbracoPlugins"); if (HttpContext.Current == null) throw new InvalidOperationException("HttpContext is null"); var urlHelper = new UrlHelper(new RequestContext(new HttpContextWrapper(HttpContext.Current), new RouteData())); diff --git a/src/Umbraco.ModelsBuilder/Umbraco/UmbracoServices.cs b/src/Umbraco.ModelsBuilder/Umbraco/UmbracoServices.cs index 67ef10be1a..32f0703bac 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/UmbracoServices.cs +++ b/src/Umbraco.ModelsBuilder/Umbraco/UmbracoServices.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using Umbraco.Core; using Umbraco.Core.Composing; +using Umbraco.Core.Exceptions; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Services; @@ -88,7 +89,7 @@ namespace Umbraco.ModelsBuilder.Umbraco // of course this should never happen, but when it happens, better detect it // else we end up with weird nullrefs everywhere if (uniqueTypes.Contains(typeModel.ClrName)) - throw new Exception($"Panic: duplicate type ClrName \"{typeModel.ClrName}\"."); + throw new PanicException($"Panic: duplicate type ClrName \"{typeModel.ClrName}\"."); uniqueTypes.Add(typeModel.ClrName); var publishedContentType = _publishedContentTypeFactory.CreateContentType(contentType); @@ -128,7 +129,7 @@ namespace Umbraco.ModelsBuilder.Umbraco var publishedPropertyType = publishedContentType.GetPropertyType(propertyType.Alias); if (publishedPropertyType == null) - throw new Exception($"Panic: could not get published property type {contentType.Alias}.{propertyType.Alias}."); + throw new PanicException($"Panic: could not get published property type {contentType.Alias}.{propertyType.Alias}."); propertyModel.ModelClrType = publishedPropertyType.ModelClrType; @@ -150,7 +151,7 @@ namespace Umbraco.ModelsBuilder.Umbraco foreach (var contentType in contentTypes) { var typeModel = typeModels.SingleOrDefault(x => x.Id == contentType.Id); - if (typeModel == null) throw new Exception("Panic: no type model matching content type."); + if (typeModel == null) throw new PanicException("Panic: no type model matching content type."); IEnumerable compositionTypes; var contentTypeAsMedia = contentType as IMediaType; @@ -159,12 +160,12 @@ namespace Umbraco.ModelsBuilder.Umbraco if (contentTypeAsMedia != null) compositionTypes = contentTypeAsMedia.ContentTypeComposition; else if (contentTypeAsContent != null) compositionTypes = contentTypeAsContent.ContentTypeComposition; else if (contentTypeAsMember != null) compositionTypes = contentTypeAsMember.ContentTypeComposition; - else throw new Exception(string.Format("Panic: unsupported type \"{0}\".", contentType.GetType().FullName)); + else throw new PanicException(string.Format("Panic: unsupported type \"{0}\".", contentType.GetType().FullName)); foreach (var compositionType in compositionTypes) { var compositionModel = typeModels.SingleOrDefault(x => x.Id == compositionType.Id); - if (compositionModel == null) throw new Exception("Panic: composition type does not exist."); + if (compositionModel == null) throw new PanicException("Panic: composition type does not exist."); if (compositionType.Id == contentType.ParentId) continue; From c5bee7753744431236935806417ae7dad62533e7 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 23 Oct 2019 14:40:52 +0200 Subject: [PATCH 071/548] AB3267 - removed option to disable modelsbuilder - dont make sense for v8, and everything is broken when if you did it. --- build/NuSpecs/tools/Web.config.install.xdt | 1 - .../Configuration/Config.cs | 18 ---------- .../Dashboard/BuilderDashboardHelper.cs | 3 -- .../ModelsBuilderBackOfficeController.cs | 2 +- .../Umbraco/ModelsBuilderComponent.cs | 5 ++- .../Validation/ContentTypeModelValidator.cs | 34 ++++++++----------- src/Umbraco.Web.UI/web.Template.config | 1 - 7 files changed, 18 insertions(+), 46 deletions(-) diff --git a/build/NuSpecs/tools/Web.config.install.xdt b/build/NuSpecs/tools/Web.config.install.xdt index 2b79f95c70..6579717a60 100644 --- a/build/NuSpecs/tools/Web.config.install.xdt +++ b/build/NuSpecs/tools/Web.config.install.xdt @@ -14,7 +14,6 @@ - diff --git a/src/Umbraco.ModelsBuilder/Configuration/Config.cs b/src/Umbraco.ModelsBuilder/Configuration/Config.cs index 8fdc10d5dc..cf98e383eb 100644 --- a/src/Umbraco.ModelsBuilder/Configuration/Config.cs +++ b/src/Umbraco.ModelsBuilder/Configuration/Config.cs @@ -22,10 +22,6 @@ namespace Umbraco.ModelsBuilder.Configuration { 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"; - // ensure defaults are initialized for tests ModelsNamespace = DefaultModelsNamespace; ModelsDirectory = HostingEnvironment.IsHosted @@ -33,9 +29,6 @@ namespace Umbraco.ModelsBuilder.Configuration : DefaultModelsDirectory.TrimStart("~/"); DebugLevel = 0; - // stop here, everything is false - if (!Enable) return; - // mode var modelsMode = ConfigurationManager.AppSettings[prefix + "ModelsMode"]; if (!string.IsNullOrWhiteSpace(modelsMode)) @@ -105,7 +98,6 @@ namespace Umbraco.ModelsBuilder.Configuration /// Initializes a new instance of the class. /// public Config( - bool enable = false, ModelsMode modelsMode = ModelsMode.Nothing, string modelsNamespace = null, bool enableFactory = true, @@ -114,7 +106,6 @@ namespace Umbraco.ModelsBuilder.Configuration bool acceptUnsafeModelsDirectory = false, int debugLevel = 0) { - Enable = enable; ModelsMode = modelsMode; ModelsNamespace = string.IsNullOrWhiteSpace(modelsNamespace) ? DefaultModelsNamespace : modelsNamespace; @@ -156,15 +147,6 @@ namespace Umbraco.ModelsBuilder.Configuration throw new ConfigurationErrorsException($"Invalid models directory \"{config}\"."); } - /// - /// Gets a value indicating whether the whole models experience is enabled. - /// - /// - /// If this is false then absolutely nothing happens. - /// Default value is false which means that unless we have this setting, nothing happens. - /// - public bool Enable { get; } - /// /// Gets the models mode. /// diff --git a/src/Umbraco.ModelsBuilder/Dashboard/BuilderDashboardHelper.cs b/src/Umbraco.ModelsBuilder/Dashboard/BuilderDashboardHelper.cs index 75c318faab..ee9c384f9d 100644 --- a/src/Umbraco.ModelsBuilder/Dashboard/BuilderDashboardHelper.cs +++ b/src/Umbraco.ModelsBuilder/Dashboard/BuilderDashboardHelper.cs @@ -28,9 +28,6 @@ namespace Umbraco.ModelsBuilder.Dashboard { var config = Config; - if (!config.Enable) - return "Version: " + Api.ApiVersion.Current.Version + "
 
ModelsBuilder is disabled
(the .Enable key is missing, or its value is not 'true')."; - var sb = new StringBuilder(); sb.Append("Version: "); diff --git a/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderBackOfficeController.cs b/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderBackOfficeController.cs index a3ff89595c..88bfb7d7c2 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderBackOfficeController.cs +++ b/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderBackOfficeController.cs @@ -97,7 +97,7 @@ namespace Umbraco.ModelsBuilder.Umbraco { return new Dashboard { - Enable = _config.Enable, + Enable = true, Text = BuilderDashboardHelper.Text(), CanGenerate = BuilderDashboardHelper.CanGenerate(), OutOfDateModels = BuilderDashboardHelper.AreModelsOutOfDate(), diff --git a/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderComponent.cs b/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderComponent.cs index 4a02ccf82f..35f953d3f1 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderComponent.cs +++ b/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderComponent.cs @@ -35,8 +35,7 @@ namespace Umbraco.ModelsBuilder.Umbraco ContentModelBinder.ModelBindingException += ContentModelBinder_ModelBindingException; - if (_config.Enable) - FileService.SavingTemplate += FileService_SavingTemplate; + FileService.SavingTemplate += FileService_SavingTemplate; // fixme LiveModelsProvider should not be static if (_config.ModelsMode.IsLiveNotPure()) @@ -80,7 +79,7 @@ namespace Umbraco.ModelsBuilder.Umbraco { var settings = new Dictionary { - {"enabled", _config.Enable} + {"enabled", true} }; return settings; diff --git a/src/Umbraco.ModelsBuilder/Validation/ContentTypeModelValidator.cs b/src/Umbraco.ModelsBuilder/Validation/ContentTypeModelValidator.cs index dc4649f364..4b38de0168 100644 --- a/src/Umbraco.ModelsBuilder/Validation/ContentTypeModelValidator.cs +++ b/src/Umbraco.ModelsBuilder/Validation/ContentTypeModelValidator.cs @@ -39,32 +39,28 @@ namespace Umbraco.ModelsBuilder.Validation protected override IEnumerable Validate(TModel model) { - //don't do anything if we're not enabled - if (Config.Enable) + var properties = model.Groups.SelectMany(x => x.Properties) + .Where(x => x.Inherited == false) + .ToArray(); + + foreach (var prop in properties) { - var properties = model.Groups.SelectMany(x => x.Properties) - .Where(x => x.Inherited == false) - .ToArray(); + var propertyGroup = model.Groups.Single(x => x.Properties.Contains(prop)); - foreach (var prop in properties) - { - var propertyGroup = model.Groups.Single(x => x.Properties.Contains(prop)); - - if (model.Alias.ToLowerInvariant() == prop.Alias.ToLowerInvariant()) - yield return new ValidationResult(string.Format("With Models Builder enabled, you can't have a property with a the alias \"{0}\" when the content type alias is also \"{0}\".", prop.Alias), new[] + if (model.Alias.ToLowerInvariant() == prop.Alias.ToLowerInvariant()) + yield return new ValidationResult(string.Format("With Models Builder enabled, you can't have a property with a the alias \"{0}\" when the content type alias is also \"{0}\".", prop.Alias), new[] { string.Format("Groups[{0}].Properties[{1}].Alias", model.Groups.IndexOf(propertyGroup), propertyGroup.Properties.IndexOf(prop)) }); - //we need to return the field name with an index so it's wired up correctly - var groupIndex = model.Groups.IndexOf(propertyGroup); - var propertyIndex = propertyGroup.Properties.IndexOf(prop); + //we need to return the field name with an index so it's wired up correctly + var groupIndex = model.Groups.IndexOf(propertyGroup); + var propertyIndex = propertyGroup.Properties.IndexOf(prop); - var validationResult = ValidateProperty(prop, groupIndex, propertyIndex); - if (validationResult != null) - { - yield return validationResult; - } + var validationResult = ValidateProperty(prop, groupIndex, propertyIndex); + if (validationResult != null) + { + yield return validationResult; } } } diff --git a/src/Umbraco.Web.UI/web.Template.config b/src/Umbraco.Web.UI/web.Template.config index f0abbfde52..8759fc6459 100644 --- a/src/Umbraco.Web.UI/web.Template.config +++ b/src/Umbraco.Web.UI/web.Template.config @@ -47,7 +47,6 @@ - From 52b0edf1f50e1f2d4ef60c1a36b43d3edd15220e Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 24 Oct 2019 14:24:01 +1100 Subject: [PATCH 072/548] For backgroundtaskrunner, run shutdown on a threadpool thread to not block the shutdown of other IRegisteredObjects --- .../Scheduling/BackgroundTaskRunner.cs | 80 ++++++++++++------- 1 file changed, 53 insertions(+), 27 deletions(-) diff --git a/src/Umbraco.Web/Scheduling/BackgroundTaskRunner.cs b/src/Umbraco.Web/Scheduling/BackgroundTaskRunner.cs index e518f49ae0..403ffd47a8 100644 --- a/src/Umbraco.Web/Scheduling/BackgroundTaskRunner.cs +++ b/src/Umbraco.Web/Scheduling/BackgroundTaskRunner.cs @@ -697,43 +697,69 @@ namespace Umbraco.Web.Scheduling if (onTerminating) OnEvent(Terminating, "Terminating"); + // Run the Stop commands on another thread since IRegisteredObject.Stop calls are called sequentially + // with a single aspnet thread during shutdown and we don't want to delay other calls to IRegisteredObject.Stop. if (!immediate) { - // immediate == false when the app is trying to wind down, immediate == true will be called either: - // after a call with immediate == false or if the app is not trying to wind down and needs to immediately stop. - // So Stop may be called twice or sometimes only once. - - try - { - Shutdown(false, false); // do not accept any more tasks, flush the queue, do not wait - } - finally - { - // raise the completed event only after the running threading task has completed - lock (_locker) - { - if (_runningTask != null) - _runningTask.ContinueWith(_ => Terminate(false)); - else - Terminate(false); - } - } - - // If we are called with immediate == false, wind down above and then shutdown within 2 seconds, - // we want to shut down the app as quick as possible, if we wait until immediate == true, this can - // take a very long time since immediate will only be true when a new request is received on the new - // appdomain (or another iis timeout occurs ... which can take soeme time). - Task.Delay(2000, _shutdownToken).ContinueWith(_ => StopImmediate()); - + Task.Run(StopInitial, CancellationToken.None); } else { - // If we are called with immediate == true - cancel and shut down now. + lock(_locker) + { + if (_terminated) return; + Task.Run(StopImmediate, CancellationToken.None); + } + } + } + /// + /// Called when immediate == false for IRegisteredObject.Stop(bool immediate) + /// + /// + /// Called on a threadpool thread + /// + private void StopInitial() + { + // immediate == false when the app is trying to wind down, immediate == true will be called either: + // after a call with immediate == false or if the app is not trying to wind down and needs to immediately stop. + // So Stop may be called twice or sometimes only once. + + try + { + Shutdown(false, false); // do not accept any more tasks, flush the queue, do not wait + } + finally + { + // raise the completed event only after the running threading task has completed + lock (_locker) + { + if (_runningTask != null) + _runningTask.ContinueWith(_ => Terminate(false)); + else + Terminate(false); + } + } + + // If the shutdown token was not canceled in the Shutdown call above, it means there was still tasks + // being processed, in which case we'll give it a couple seconds + if (!_shutdownToken.IsCancellationRequested) + { + // If we are called with immediate == false, wind down above and then shutdown within 2 seconds, + // we want to shut down the app as quick as possible, if we wait until immediate == true, this can + // take a very long time since immediate will only be true when a new request is received on the new + // appdomain (or another iis timeout occurs ... which can take some time). + Thread.Sleep(2000); //we are already on a threadpool thread StopImmediate(); } } + /// + /// Called when immediate == true for IRegisteredObject.Stop(bool immediate) + /// + /// + /// Called on a threadpool thread + /// private void StopImmediate() { _logger.Info("{LogPrefix} Canceling tasks", _logPrefix); From f513ed547699ff3b2be931e547aaa515c4983aad Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 24 Oct 2019 14:35:30 +1100 Subject: [PATCH 073/548] Changes call transition from StopInitial to StopImmediate instead of terminate to ensure that all cancelation tokens are canceled and cleared --- src/Umbraco.Web/Scheduling/BackgroundTaskRunner.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web/Scheduling/BackgroundTaskRunner.cs b/src/Umbraco.Web/Scheduling/BackgroundTaskRunner.cs index 403ffd47a8..4158594109 100644 --- a/src/Umbraco.Web/Scheduling/BackgroundTaskRunner.cs +++ b/src/Umbraco.Web/Scheduling/BackgroundTaskRunner.cs @@ -735,9 +735,9 @@ namespace Umbraco.Web.Scheduling lock (_locker) { if (_runningTask != null) - _runningTask.ContinueWith(_ => Terminate(false)); + _runningTask.ContinueWith(_ => StopImmediate()); else - Terminate(false); + StopImmediate(); } } From 193892f0847bec49f4f7891e8dd296c10627a7a1 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 24 Oct 2019 16:48:21 +1100 Subject: [PATCH 074/548] Creates method to create the relations based on the property editor's returned reference but have discovered a gotcha for relations, so next step is to resolve that. --- src/Umbraco.Core/Constants-Conventions.cs | 32 ++++++++-- .../Migrations/Install/DatabaseDataCreator.cs | 10 +++ .../Models/Editors/ContentPropertyFile.cs | 1 + .../Models/Editors/UmbracoEntityReference.cs | 55 ++++++++++++++++ .../Repositories/IRelationRepository.cs | 9 ++- .../Implement/ContentRepositoryBase.cs | 62 ++++++++++++++++++- .../Implement/DocumentBlueprintRepository.cs | 4 +- .../Implement/DocumentRepository.cs | 4 +- .../Repositories/Implement/MediaRepository.cs | 4 +- .../Implement/MemberRepository.cs | 4 +- .../Implement/RelationRepository.cs | 16 +++++ .../SqlSyntax/SqlSyntaxProviderExtensions.cs | 2 + .../PropertyEditors/IDataValueReference.cs | 3 +- src/Umbraco.Core/Services/IRelationService.cs | 2 +- .../Services/Implement/RelationService.cs | 4 +- src/Umbraco.Core/Udi.cs | 2 +- src/Umbraco.Core/Umbraco.Core.csproj | 1 + .../Repositories/ContentTypeRepositoryTest.cs | 2 +- .../Repositories/DocumentRepositoryTest.cs | 2 +- .../Repositories/DomainRepositoryTest.cs | 2 +- .../Repositories/MediaRepositoryTest.cs | 2 +- .../Repositories/MemberRepositoryTest.cs | 2 +- .../PublicAccessRepositoryTest.cs | 2 +- .../Repositories/TagRepositoryTest.cs | 4 +- .../Repositories/TemplateRepositoryTest.cs | 2 +- .../Repositories/UserRepositoryTest.cs | 4 +- .../Services/ContentServicePerformanceTest.cs | 2 +- .../Services/ContentServiceTests.cs | 2 +- .../Services/RelationServiceTests.cs | 2 + .../PropertyEditors/RichTextPropertyEditor.cs | 7 ++- 30 files changed, 213 insertions(+), 37 deletions(-) create mode 100644 src/Umbraco.Core/Models/Editors/UmbracoEntityReference.cs diff --git a/src/Umbraco.Core/Constants-Conventions.cs b/src/Umbraco.Core/Constants-Conventions.cs index 6c9407667a..25d259b7d1 100644 --- a/src/Umbraco.Core/Constants-Conventions.cs +++ b/src/Umbraco.Core/Constants-Conventions.cs @@ -309,32 +309,52 @@ 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"; } diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs index 94d8cfbc62..1027840995 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs @@ -318,6 +318,16 @@ namespace Umbraco.Core.Migrations.Install 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(); _database.Insert(Constants.DatabaseSchema.Tables.RelationType, "id", false, relationType); + + //TODO: We need to decide if we are going to change the relations APIs since it's pretty crappy that we have to explicitly define all relation type object type combinations... + + relationType = new RelationTypeDto { Id = 4, Alias = Constants.Conventions.RelationTypes.RelatedMediaAlias, ChildObjectType = Constants.ObjectTypes.Media, ParentObjectType = Constants.ObjectTypes.Document, Dual = false, Name = Constants.Conventions.RelationTypes.RelatedMediaName }; + relationType.UniqueId = (relationType.Alias + "____" + relationType.Name).ToGuid(); + _database.Insert(Constants.DatabaseSchema.Tables.RelationType, "id", false, relationType); + + relationType = new RelationTypeDto { Id = 4, Alias = Constants.Conventions.RelationTypes.RelatedDocumentAlias, ChildObjectType = Constants.ObjectTypes.Document, ParentObjectType = Constants.ObjectTypes.Document, Dual = false, Name = Constants.Conventions.RelationTypes.RelatedDocumentName }; + relationType.UniqueId = (relationType.Alias + "____" + relationType.Name).ToGuid(); + _database.Insert(Constants.DatabaseSchema.Tables.RelationType, "id", false, relationType); } private void CreateKeyValueData() 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..f5121988f5 --- /dev/null +++ b/src/Umbraco.Core/Models/Editors/UmbracoEntityReference.cs @@ -0,0 +1,55 @@ +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 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/Persistence/Repositories/IRelationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IRelationRepository.cs index 51d7656d8a..00ab158d83 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IRelationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IRelationRepository.cs @@ -4,6 +4,13 @@ namespace Umbraco.Core.Persistence.Repositories { public interface IRelationRepository : IReadWriteQueryRepository { - + /// + /// 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); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs index eede78f4c7..3bb57dca9f 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; @@ -47,12 +48,13 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// 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, + ILanguageRepository languageRepository, IRelationRepository relationRepository, IRelationTypeRepository relationTypeRepository, Lazy propertyEditors) : base(scopeAccessor, cache, logger) { LanguageRepository = languageRepository; RelationRepository = relationRepository; + RelationTypeRepository = relationTypeRepository; _propertyEditors = propertyEditors; } @@ -60,6 +62,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected ILanguageRepository LanguageRepository { get; } protected IRelationRepository RelationRepository { get; } + protected IRelationTypeRepository RelationTypeRepository { get; } protected PropertyEditorCollection PropertyEditors => _propertyEditors.Value; @@ -818,7 +821,62 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected void PersistRelations(TEntity entity) { - //foreach(var p in entity.) + var trackedRelations = new List(); + + foreach (var p in entity.Properties) + { + if (!PropertyEditors.TryGet(p.PropertyType.PropertyEditorAlias, out var editor)) continue; + if (!(editor is IDataValueReference reference)) continue; + + //TODO: Support variants/segments! This is not required for this initial prototype which is why there is a check here + if (!p.PropertyType.VariesByNothing()) continue; + + var val = p.GetValue(); // get the invariant value + var refs = reference.GetReferences(val); + trackedRelations.AddRange(refs); + } + + if (trackedRelations.Count == 0) return; + + //First delete all relations for this entity + var relationTypes = trackedRelations.Select(x => x.RelationTypeAlias).ToArray(); + RelationRepository.DeleteByParent(entity.Id, relationTypes); + + 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); + + foreach(var rel in trackedRelations) + { + 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)) + continue; // This shouldn't happen! + + if (!keyToIds.TryGetValue(guid, out var id)) + continue; // This shouldn't happen! + + //Create new relation + //TODO: This is N+1, we could do this all in one operation, just need a new method on the relations repo + RelationRepository.Save(new Relation(entity.Id, id, relationType)); + } + + } + + 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/DocumentBlueprintRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentBlueprintRepository.cs index de766ee5aa..60d4026ad5 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentBlueprintRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentBlueprintRepository.cs @@ -19,9 +19,9 @@ 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, IRelationRepository relationRepository, + IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository, ILanguageRepository languageRepository, IRelationRepository relationRepository, IRelationTypeRepository relationTypeRepository, Lazy propertyEditorCollection) - : base(scopeAccessor, appCaches, logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, propertyEditorCollection) + : base(scopeAccessor, appCaches, logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditorCollection) { } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs index 067086ea6b..ce95875209 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs @@ -45,9 +45,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// 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, + IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository, ILanguageRepository languageRepository, IRelationRepository relationRepository, IRelationTypeRepository relationTypeRepository, Lazy propertyEditors) - : base(scopeAccessor, appCaches, logger, languageRepository, relationRepository, propertyEditors) + : base(scopeAccessor, appCaches, logger, languageRepository, relationRepository, relationTypeRepository, propertyEditors) { _contentTypeRepository = contentTypeRepository ?? throw new ArgumentNullException(nameof(contentTypeRepository)); _templateRepository = templateRepository ?? throw new ArgumentNullException(nameof(templateRepository)); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs index 0da35145cc..0423ac9125 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs @@ -28,9 +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, IRelationRepository relationRepository, + public MediaRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IMediaTypeRepository mediaTypeRepository, ITagRepository tagRepository, ILanguageRepository languageRepository, IRelationRepository relationRepository, IRelationTypeRepository relationTypeRepository, Lazy propertyEditorCollection) - : base(scopeAccessor, cache, logger, languageRepository, relationRepository, propertyEditorCollection) + : base(scopeAccessor, cache, logger, languageRepository, relationRepository, relationTypeRepository, propertyEditorCollection) { _mediaTypeRepository = mediaTypeRepository ?? throw new ArgumentNullException(nameof(mediaTypeRepository)); _tagRepository = tagRepository ?? throw new ArgumentNullException(nameof(tagRepository)); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs index 5274e99cc9..c36143d09a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs @@ -27,9 +27,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement private readonly IMemberGroupRepository _memberGroupRepository; public MemberRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, - IMemberTypeRepository memberTypeRepository, IMemberGroupRepository memberGroupRepository, ITagRepository tagRepository, ILanguageRepository languageRepository, IRelationRepository relationRepository, + IMemberTypeRepository memberTypeRepository, IMemberGroupRepository memberGroupRepository, ITagRepository tagRepository, ILanguageRepository languageRepository, IRelationRepository relationRepository, IRelationTypeRepository relationTypeRepository, Lazy propertyEditors) - : base(scopeAccessor, cache, logger, languageRepository, relationRepository, propertyEditors) + : base(scopeAccessor, cache, logger, languageRepository, relationRepository, relationTypeRepository, propertyEditors) { _memberTypeRepository = memberTypeRepository ?? throw new ArgumentNullException(nameof(memberTypeRepository)); _tagRepository = tagRepository ?? throw new ArgumentNullException(nameof(tagRepository)); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs index 4b4af505b8..88e28e6ab8 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs @@ -9,6 +9,7 @@ using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Scoping; namespace Umbraco.Core.Persistence.Repositories.Implement @@ -157,5 +158,20 @@ namespace Umbraco.Core.Persistence.Repositories.Implement } #endregion + + 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)); + } } } 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/PropertyEditors/IDataValueReference.cs b/src/Umbraco.Core/PropertyEditors/IDataValueReference.cs index e71642f8a3..8c0806a4a4 100644 --- a/src/Umbraco.Core/PropertyEditors/IDataValueReference.cs +++ b/src/Umbraco.Core/PropertyEditors/IDataValueReference.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Umbraco.Core.Models.Editors; namespace Umbraco.Core.PropertyEditors { @@ -12,6 +13,6 @@ namespace Umbraco.Core.PropertyEditors ///
/// /// - IEnumerable GetReferences(object value); + IEnumerable GetReferences(object value); } } diff --git a/src/Umbraco.Core/Services/IRelationService.cs b/src/Umbraco.Core/Services/IRelationService.cs index ef22632d6e..0f339688de 100644 --- a/src/Umbraco.Core/Services/IRelationService.cs +++ b/src/Umbraco.Core/Services/IRelationService.cs @@ -47,7 +47,7 @@ namespace Umbraco.Core.Services ///
/// to retrieve Relations for /// An enumerable list of objects - IEnumerable GetAllRelationsByRelationType(RelationType relationType); + IEnumerable GetAllRelationsByRelationType(IRelationType relationType); /// /// Gets all objects by their 's Id diff --git a/src/Umbraco.Core/Services/Implement/RelationService.cs b/src/Umbraco.Core/Services/Implement/RelationService.cs index 405c3a2800..9de3492e09 100644 --- a/src/Umbraco.Core/Services/Implement/RelationService.cs +++ b/src/Umbraco.Core/Services/Implement/RelationService.cs @@ -96,7 +96,7 @@ namespace Umbraco.Core.Services.Implement /// /// to retrieve Relations for /// An enumerable list of objects - public IEnumerable GetAllRelationsByRelationType(RelationType relationType) + public IEnumerable GetAllRelationsByRelationType(IRelationType relationType) { return GetAllRelationsByRelationType(relationType.Id); } @@ -642,6 +642,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); diff --git a/src/Umbraco.Core/Udi.cs b/src/Umbraco.Core/Udi.cs index e7d00fffa5..ea3ec0ed2d 100644 --- a/src/Umbraco.Core/Udi.cs +++ b/src/Umbraco.Core/Udi.cs @@ -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 2bd8bfc4fe..65118a877a 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -245,6 +245,7 @@ + diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs index 4d2e8bc999..07ca7d238d 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs @@ -39,7 +39,7 @@ namespace Umbraco.Tests.Persistence.Repositories var relationTypeRepository = new RelationTypeRepository(scopeAccessor, AppCaches.Disabled, Logger); var relationRepository = new RelationRepository(scopeAccessor, Logger, relationTypeRepository); var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); - var repository = new DocumentRepository(scopeAccessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, propertyEditors); + var repository = new DocumentRepository(scopeAccessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors); return repository; } diff --git a/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs index c2f897df80..45dc3de2e3 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs @@ -72,7 +72,7 @@ namespace Umbraco.Tests.Persistence.Repositories var relationTypeRepository = new RelationTypeRepository(scopeAccessor, AppCaches.Disabled, Logger); var relationRepository = new RelationRepository(scopeAccessor, Logger, relationTypeRepository); var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); - var repository = new DocumentRepository(scopeAccessor, appCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, propertyEditors); + var repository = new DocumentRepository(scopeAccessor, appCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors); return repository; } diff --git a/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs index 16a59d789f..27ea92fed6 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs @@ -30,7 +30,7 @@ namespace Umbraco.Tests.Persistence.Repositories var relationTypeRepository = new RelationTypeRepository(accessor, Core.Cache.AppCaches.Disabled, Logger); var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository); var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); - documentRepository = new DocumentRepository(accessor, Core.Cache.AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, propertyEditors); + documentRepository = new DocumentRepository(accessor, Core.Cache.AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors); var domainRepository = new DomainRepository(accessor, Core.Cache.AppCaches.Disabled, Logger); return domainRepository; } diff --git a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs index c976912220..93587506e8 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs @@ -45,7 +45,7 @@ namespace Umbraco.Tests.Persistence.Repositories var relationTypeRepository = new RelationTypeRepository(scopeAccessor, AppCaches.Disabled, Logger); var relationRepository = new RelationRepository(scopeAccessor, Logger, relationTypeRepository); var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); - var repository = new MediaRepository(scopeAccessor, appCaches, Logger, mediaTypeRepository, tagRepository, Mock.Of(), relationRepository, propertyEditors); + var repository = new MediaRepository(scopeAccessor, appCaches, Logger, mediaTypeRepository, tagRepository, Mock.Of(), relationRepository, relationTypeRepository, propertyEditors); return repository; } diff --git a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs index 9361bd5909..72b4691639 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs @@ -39,7 +39,7 @@ namespace Umbraco.Tests.Persistence.Repositories var relationTypeRepository = new RelationTypeRepository(accessor, AppCaches.Disabled, Logger); var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository); var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); - var repository = new MemberRepository(accessor, AppCaches.Disabled, Logger, memberTypeRepository, memberGroupRepository, tagRepo, Mock.Of(), relationRepository, propertyEditors); + var repository = new MemberRepository(accessor, AppCaches.Disabled, Logger, memberTypeRepository, memberGroupRepository, tagRepo, Mock.Of(), relationRepository, relationTypeRepository, propertyEditors); return repository; } diff --git a/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs index 8cabc18d2c..84a0f608f7 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs @@ -314,7 +314,7 @@ namespace Umbraco.Tests.Persistence.Repositories var relationTypeRepository = new RelationTypeRepository(accessor, AppCaches, Logger); var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository); var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); - var repository = new DocumentRepository(accessor, AppCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, propertyEditors); + var repository = new DocumentRepository(accessor, AppCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors); return repository; } diff --git a/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs index 61aec9f74c..a4155639be 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs @@ -963,7 +963,7 @@ namespace Umbraco.Tests.Persistence.Repositories var relationTypeRepository = new RelationTypeRepository(accessor, AppCaches.Disabled, Logger); var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository); var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); - var repository = new DocumentRepository(accessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, propertyEditors); + var repository = new DocumentRepository(accessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors); return repository; } @@ -978,7 +978,7 @@ namespace Umbraco.Tests.Persistence.Repositories var relationTypeRepository = new RelationTypeRepository(accessor, AppCaches.Disabled, Logger); var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository); var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); - var repository = new MediaRepository(accessor, AppCaches.Disabled, Logger, mediaTypeRepository, tagRepository, Mock.Of(), relationRepository, propertyEditors); + var repository = new MediaRepository(accessor, AppCaches.Disabled, Logger, mediaTypeRepository, tagRepository, Mock.Of(), relationRepository, relationTypeRepository, propertyEditors); return repository; } } diff --git a/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs index 7bf113dfc3..e996c4f6a1 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs @@ -245,7 +245,7 @@ namespace Umbraco.Tests.Persistence.Repositories var relationTypeRepository = new RelationTypeRepository(ScopeProvider, AppCaches.Disabled, Logger); var relationRepository = new RelationRepository(ScopeProvider, Logger, relationTypeRepository); var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); - var contentRepo = new DocumentRepository(ScopeProvider, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, propertyEditors); + var contentRepo = new DocumentRepository(ScopeProvider, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors); 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 01a5573119..0438e2193b 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs @@ -34,7 +34,7 @@ namespace Umbraco.Tests.Persistence.Repositories var relationTypeRepository = new RelationTypeRepository(accessor, AppCaches.Disabled, Logger); var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository); var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); - var repository = new MediaRepository(accessor, AppCaches, Mock.Of(), mediaTypeRepository, tagRepository, Mock.Of(), relationRepository, propertyEditors); + var repository = new MediaRepository(accessor, AppCaches, Mock.Of(), mediaTypeRepository, tagRepository, Mock.Of(), relationRepository, relationTypeRepository, propertyEditors); return repository; } @@ -55,7 +55,7 @@ namespace Umbraco.Tests.Persistence.Repositories var relationTypeRepository = new RelationTypeRepository(accessor, AppCaches.Disabled, Logger); var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository); var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); - var repository = new DocumentRepository(accessor, AppCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, propertyEditors); + var repository = new DocumentRepository(accessor, AppCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors); return repository; } diff --git a/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs b/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs index 5de5d9bd16..d7729c19c1 100644 --- a/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs +++ b/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs @@ -50,7 +50,7 @@ namespace Umbraco.Tests.Services var relationTypeRepository = new RelationTypeRepository((IScopeAccessor)provider, AppCaches.Disabled, Logger); var relationRepository = new RelationRepository((IScopeAccessor)provider, Logger, relationTypeRepository); var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); - var repository = new DocumentRepository((IScopeAccessor)provider, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository, relationRepository, propertyEditors); + var repository = new DocumentRepository((IScopeAccessor)provider, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository, relationRepository, relationTypeRepository, propertyEditors); return repository; } diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index bd97772d12..1f53767dbb 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -3169,7 +3169,7 @@ namespace Umbraco.Tests.Services var relationTypeRepository = new RelationTypeRepository(accessor, AppCaches.Disabled, Logger); var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository); var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); - var repository = new DocumentRepository(accessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, propertyEditors); + var repository = new DocumentRepository(accessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors); return repository; } diff --git a/src/Umbraco.Tests/Services/RelationServiceTests.cs b/src/Umbraco.Tests/Services/RelationServiceTests.cs index cfef50a330..ad24d2345a 100644 --- a/src/Umbraco.Tests/Services/RelationServiceTests.cs +++ b/src/Umbraco.Tests/Services/RelationServiceTests.cs @@ -23,5 +23,7 @@ namespace Umbraco.Tests.Services Assert.AreEqual(rt.Name, "repeatedEventOccurence"); } + + //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.Web/PropertyEditors/RichTextPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs index 0287bdfefe..ed3f484a4e 100644 --- a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs @@ -4,6 +4,7 @@ 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.Core.Services; using Umbraco.Examine; @@ -135,15 +136,15 @@ namespace Umbraco.Web.PropertyEditors ///
/// /// - public IEnumerable GetReferences(object value) + 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 udi; + yield return new UmbracoEntityReference(udi, Constants.Conventions.RelationTypes.RelatedMediaAlias); foreach (var udi in _localLinkParser.FindUdisFromLocalLinks(asString)) - yield return udi; + yield return new UmbracoEntityReference(udi, Constants.Conventions.RelationTypes.RelatedMediaAlias); //TODO: Detect Macros too ... but we can save that for a later date, right now need to do media refs } From ae64fe49be574ffd553c0b1026f64f356809ab7e Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 24 Oct 2019 21:32:00 +1100 Subject: [PATCH 075/548] Allows relation types to not have specific object types, updates IRelation to return the actual object types for the ID references, now relations can be more flexible without being strangely tied to specific object types based on the relation type. --- .../Compose/RelateOnCopyComponent.cs | 7 +- .../Compose/RelateOnTrashComponent.cs | 4 +- .../Migrations/Install/DatabaseDataCreator.cs | 2 +- .../Migrations/Upgrade/UmbracoPlan.cs | 4 + .../V_8_5_0/UpdateRelationTypeTable.cs | 21 ++++++ src/Umbraco.Core/Models/IRelation.cs | 9 ++- src/Umbraco.Core/Models/IRelationType.cs | 4 +- src/Umbraco.Core/Models/Relation.cs | 31 ++++++++ src/Umbraco.Core/Models/RelationType.cs | 32 ++++---- .../Persistence/Dtos/RelationDto.cs | 8 ++ .../Persistence/Dtos/RelationTypeDto.cs | 11 ++- .../Persistence/Factories/RelationFactory.cs | 18 +---- .../Factories/RelationTypeFactory.cs | 4 +- .../Repositories/IRelationRepository.cs | 3 +- .../Implement/RelationRepository.cs | 59 ++++++++------- .../Services/Implement/RelationService.cs | 18 +++-- src/Umbraco.Core/Umbraco.Core.csproj | 1 + src/Umbraco.Tests/Models/RelationTests.cs | 4 +- src/Umbraco.Tests/Models/RelationTypeTests.cs | 4 +- .../Repositories/RelationRepositoryTest.cs | 12 ++- .../RelationTypeRepositoryTest.cs | 8 +- .../Services/ContentServiceTests.cs | 2 +- .../Services/RelationServiceTests.cs | 73 ++++++++++++++++++- .../Editors/RelationTypeController.cs | 6 +- .../ContentEditing/RelationTypeDisplay.cs | 4 +- .../Models/Mapping/RelationMapDefinition.cs | 4 +- 26 files changed, 248 insertions(+), 105 deletions(-) create mode 100644 src/Umbraco.Core/Migrations/Upgrade/V_8_5_0/UpdateRelationTypeTable.cs 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/Migrations/Install/DatabaseDataCreator.cs b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs index 1027840995..78e22ef28e 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs @@ -325,7 +325,7 @@ namespace Umbraco.Core.Migrations.Install relationType.UniqueId = (relationType.Alias + "____" + relationType.Name).ToGuid(); _database.Insert(Constants.DatabaseSchema.Tables.RelationType, "id", false, relationType); - relationType = new RelationTypeDto { Id = 4, Alias = Constants.Conventions.RelationTypes.RelatedDocumentAlias, ChildObjectType = Constants.ObjectTypes.Document, ParentObjectType = Constants.ObjectTypes.Document, Dual = false, Name = Constants.Conventions.RelationTypes.RelatedDocumentName }; + relationType = new RelationTypeDto { Id = 5, Alias = Constants.Conventions.RelationTypes.RelatedDocumentAlias, ChildObjectType = Constants.ObjectTypes.Document, ParentObjectType = Constants.ObjectTypes.Document, Dual = false, Name = Constants.Conventions.RelationTypes.RelatedDocumentName }; relationType.UniqueId = (relationType.Alias + "____" + relationType.Name).ToGuid(); _database.Insert(Constants.DatabaseSchema.Tables.RelationType, "id", false, relationType); } diff --git a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs index e8fd3414ec..e5e335d309 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs @@ -6,6 +6,7 @@ using Umbraco.Core.Migrations.Upgrade.Common; using Umbraco.Core.Migrations.Upgrade.V_8_0_0; using Umbraco.Core.Migrations.Upgrade.V_8_0_1; using Umbraco.Core.Migrations.Upgrade.V_8_1_0; +using Umbraco.Core.Migrations.Upgrade.V_8_5_0; namespace Umbraco.Core.Migrations.Upgrade { @@ -182,6 +183,9 @@ namespace Umbraco.Core.Migrations.Upgrade To("{0372A42B-DECF-498D-B4D1-6379E907EB94}"); To("{5B1E0D93-F5A3-449B-84BA-65366B84E2D4}"); + // to 8.5.0... + To("{4759A294-9860-46BC-99F9-B4C975CAE580}"); + //FINAL } } diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_5_0/UpdateRelationTypeTable.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_5_0/UpdateRelationTypeTable.cs new file mode 100644 index 0000000000..ed21935488 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_5_0/UpdateRelationTypeTable.cs @@ -0,0 +1,21 @@ +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_5_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(); + Alter.Table(Constants.DatabaseSchema.Tables.RelationType).AlterColumn("childObjectType").AsGuid().Nullable(); + + //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 + Alter.Table(Constants.DatabaseSchema.Tables.RelationType).AlterColumn("alias").AsString(100).NotNullable(); + } + } +} 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/Relation.cs b/src/Umbraco.Core/Models/Relation.cs index f5d13c70c4..8e3a073a96 100644 --- a/src/Umbraco.Core/Models/Relation.cs +++ b/src/Umbraco.Core/Models/Relation.cs @@ -17,12 +17,37 @@ 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; + } /// @@ -35,6 +60,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 +73,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 259b7bc4ef..ea62cecab7 100644 --- a/src/Umbraco.Core/Models/RelationType.cs +++ b/src/Umbraco.Core/Models/RelationType.cs @@ -15,24 +15,26 @@ namespace Umbraco.Core.Models private string _name; private string _alias; private bool _isBidrectional; - private Guid _parentObjectType; - private Guid _childObjectType; + private Guid? _parentObjectType; + private Guid? _childObjectType; - public RelationType(Guid childObjectType, Guid parentObjectType, string alias) + //TODO: Should we put back the broken ctors with obsolete attributes? + + public RelationType(string alias, string name) + : this(name, alias, false, null, null) { - if (string.IsNullOrWhiteSpace(alias)) throw new ArgumentNullOrEmptyException(nameof(alias)); - _childObjectType = childObjectType; - _parentObjectType = parentObjectType; + } + + public RelationType(string name, string alias, bool isBidrectional, Guid? parentObjectType, Guid? childObjectType) + { + _name = name; _alias = alias; - Name = _alias; + _isBidrectional = isBidrectional; + _parentObjectType = parentObjectType; + _childObjectType = childObjectType; } - public RelationType(Guid childObjectType, Guid parentObjectType, string alias, string name) - : this(childObjectType, parentObjectType, alias) - { - if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullOrEmptyException(nameof(name)); - Name = name; - } + /// /// Gets or sets the Name of the RelationType @@ -69,7 +71,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)); @@ -80,7 +82,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/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/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..edd87fec68 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.ChildObjectType, dto.ParentObjectType); 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/Repositories/IRelationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IRelationRepository.cs index 00ab158d83..9a2e42568e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IRelationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IRelationRepository.cs @@ -1,4 +1,5 @@ -using Umbraco.Core.Models; +using System.Collections.Generic; +using Umbraco.Core.Models; namespace Umbraco.Core.Persistence.Repositories { diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs index 88e28e6ab8..ab6b02afb4 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs @@ -11,6 +11,7 @@ using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Scoping; +using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics; namespace Umbraco.Core.Persistence.Repositories.Implement { @@ -40,10 +41,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) @@ -68,26 +68,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; } @@ -98,14 +89,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; } @@ -137,11 +132,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(); } @@ -150,10 +146,11 @@ 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(); } @@ -173,5 +170,13 @@ namespace Umbraco.Core.Persistence.Repositories.Implement Database.Execute(Sql().Delete().WhereIn(x => x.Id, subQuery)); } + + private void PopulateObjectTypes(IRelation entity) + { + var nodes = Database.Fetch(Sql().Select().From().Where(x => x.NodeId == entity.ChildId || x.NodeId == entity.ParentId)) + .ToDictionary(x => x.NodeId, x => x.NodeObjectType); + entity.ParentObjectType = nodes[entity.ParentId].Value; + entity.ChildObjectType = nodes[entity.ChildId].Value; + } } } diff --git a/src/Umbraco.Core/Services/Implement/RelationService.cs b/src/Umbraco.Core/Services/Implement/RelationService.cs index 9de3492e09..cc0a3e23bc 100644 --- a/src/Umbraco.Core/Services/Implement/RelationService.cs +++ b/src/Umbraco.Core/Services/Implement/RelationService.cs @@ -288,7 +288,7 @@ namespace Umbraco.Core.Services.Implement /// An public IUmbracoEntity GetChildEntityFromRelation(IRelation relation) { - var objectType = ObjectTypes.GetUmbracoObjectType(relation.RelationType.ChildObjectType); + var objectType = ObjectTypes.GetUmbracoObjectType(relation.ChildObjectType); return _entityService.Get(relation.ChildId, objectType); } @@ -299,7 +299,7 @@ namespace Umbraco.Core.Services.Implement /// 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); } @@ -310,8 +310,8 @@ namespace Umbraco.Core.Services.Implement /// 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); @@ -328,7 +328,7 @@ namespace Umbraco.Core.Services.Implement { foreach (var relation in relations) { - var objectType = ObjectTypes.GetUmbracoObjectType(relation.RelationType.ChildObjectType); + var objectType = ObjectTypes.GetUmbracoObjectType(relation.ChildObjectType); yield return _entityService.Get(relation.ChildId, objectType); } } @@ -342,7 +342,7 @@ namespace Umbraco.Core.Services.Implement { foreach (var relation in relations) { - var objectType = ObjectTypes.GetUmbracoObjectType(relation.RelationType.ParentObjectType); + var objectType = ObjectTypes.GetUmbracoObjectType(relation.ParentObjectType); yield return _entityService.Get(relation.ParentId, objectType); } } @@ -356,8 +356,8 @@ namespace Umbraco.Core.Services.Implement { 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); @@ -379,6 +379,8 @@ namespace Umbraco.Core.Services.Implement 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()) diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 65118a877a..1f83bd2807 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -245,6 +245,7 @@ + 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/Persistence/Repositories/RelationRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/RelationRepositoryTest.cs index cea7f44b71..8d9f82a776 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/RelationRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/RelationRepositoryTest.cs @@ -260,8 +260,16 @@ 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()) diff --git a/src/Umbraco.Tests/Persistence/Repositories/RelationTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/RelationTypeRepositoryTest.cs index e52e2dfcdf..881ea23dc8 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); @@ -226,8 +224,8 @@ 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 = ScopeProvider.CreateScope()) diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index 1f53767dbb..7d65513cc0 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -1778,7 +1778,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 diff --git a/src/Umbraco.Tests/Services/RelationServiceTests.cs b/src/Umbraco.Tests/Services/RelationServiceTests.cs index ad24d2345a..a39a9c838b 100644 --- a/src/Umbraco.Tests/Services/RelationServiceTests.cs +++ b/src/Umbraco.Tests/Services/RelationServiceTests.cs @@ -4,24 +4,91 @@ 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 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.ChildObjectType.Value); + Assert.AreEqual(Constants.ObjectTypes.Media, rt.ParentObjectType.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 = CreateNewRelation("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 = CreateNewRelation("Test", "test"); + + // re-get + r = ServiceContext.RelationService.GetById(r.Id); + + Assert.AreEqual(Constants.ObjectTypes.Document, r.ParentObjectType); + Assert.AreEqual(Constants.ObjectTypes.Media, r.ChildObjectType); + } + + private IRelation CreateNewRelation(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; } //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.Web/Editors/RelationTypeController.cs b/src/Umbraco.Web/Editors/RelationTypeController.cs index faafbb79f1..6fb9108d74 100644 --- a/src/Umbraco.Web/Editors/RelationTypeController.cs +++ b/src/Umbraco.Web/Editors/RelationTypeController.cs @@ -84,11 +84,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/Models/ContentEditing/RelationTypeDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/RelationTypeDisplay.cs index 55e140d6f3..8a92d085eb 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. diff --git a/src/Umbraco.Web/Models/Mapping/RelationMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/RelationMapDefinition.cs index 7787750e54..7f89c78f8a 100644 --- a/src/Umbraco.Web/Models/Mapping/RelationMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/RelationMapDefinition.cs @@ -29,8 +29,8 @@ namespace Umbraco.Web.Models.Mapping 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(); + target.ParentObjectTypeName = source.ParentObjectType.HasValue ? ObjectTypes.GetUmbracoObjectType(source.ParentObjectType.Value).GetFriendlyName() : string.Empty; + target.ChildObjectTypeName = source.ChildObjectType.HasValue ? ObjectTypes.GetUmbracoObjectType(source.ChildObjectType.Value).GetFriendlyName() : string.Empty; } // Umbraco.Code.MapAll -ParentName -ChildName From f656f7d0a0cb33e0b6286ae2df15cc76b65df928 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 24 Oct 2019 22:38:11 +1100 Subject: [PATCH 076/548] Fixes relations editor and loading in relations, allows creating relations without object types, fixes migration --- .../V_8_5_0/UpdateRelationTypeTable.cs | 22 +++++++- .../Implement/RelationTypeRepository.cs | 16 +++++- .../Services/Implement/RelationService.cs | 6 ++ .../src/views/relationtypes/create.html | 6 +- .../views/relationtypes/edit.controller.js | 40 +------------ .../Editors/RelationTypeController.cs | 5 +- .../Models/ContentEditing/RelationTypeSave.cs | 4 +- .../Models/Mapping/RelationMapDefinition.cs | 56 ++++++++++++++++--- 8 files changed, 95 insertions(+), 60 deletions(-) diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_5_0/UpdateRelationTypeTable.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_5_0/UpdateRelationTypeTable.cs index ed21935488..30174f8d13 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_5_0/UpdateRelationTypeTable.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_5_0/UpdateRelationTypeTable.cs @@ -11,11 +11,27 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_5_0 public override void Migrate() { - Alter.Table(Constants.DatabaseSchema.Tables.RelationType).AlterColumn("parentObjectType").AsGuid().Nullable(); - Alter.Table(Constants.DatabaseSchema.Tables.RelationType).AlterColumn("childObjectType").AsGuid().Nullable(); + 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 - Alter.Table(Constants.DatabaseSchema.Tables.RelationType).AlterColumn("alias").AsString(100).NotNullable(); + + //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/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/Services/Implement/RelationService.cs b/src/Umbraco.Core/Services/Implement/RelationService.cs index cc0a3e23bc..bf1e7bf309 100644 --- a/src/Umbraco.Core/Services/Implement/RelationService.cs +++ b/src/Umbraco.Core/Services/Implement/RelationService.cs @@ -326,6 +326,8 @@ namespace Umbraco.Core.Services.Implement /// An enumerable list of public IEnumerable GetChildEntitiesFromRelations(IEnumerable relations) { + //TODO: Argh! N+1 + foreach (var relation in relations) { var objectType = ObjectTypes.GetUmbracoObjectType(relation.ChildObjectType); @@ -340,6 +342,8 @@ namespace Umbraco.Core.Services.Implement /// An enumerable list of public IEnumerable GetParentEntitiesFromRelations(IEnumerable relations) { + //TODO: Argh! N+1 + foreach (var relation in relations) { var objectType = ObjectTypes.GetUmbracoObjectType(relation.ParentObjectType); @@ -354,6 +358,8 @@ namespace Umbraco.Core.Services.Implement /// An enumerable list of with public IEnumerable> GetEntitiesFromRelations(IEnumerable relations) { + //TODO: Argh! N+1 + foreach (var relation in relations) { var childObjectType = ObjectTypes.GetUmbracoObjectType(relation.ChildObjectType); 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 67a48e77cd..c854580285 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 138e3e90e2..f83829dfba 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 @@ -46,7 +46,7 @@ function RelationTypeEditController($scope, $routeParams, relationTypeResource, }); relationTypeResource.getById($routeParams.id) - .then(function(data) { + .then(function (data) { bindRelationType(data); vm.page.loading = false; }); @@ -54,7 +54,6 @@ function RelationTypeEditController($scope, $routeParams, relationTypeResource, function bindRelationType(relationType) { formatDates(relationType.relations); - getRelationNames(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. @@ -70,7 +69,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'); @@ -79,41 +78,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/Editors/RelationTypeController.cs b/src/Umbraco.Web/Editors/RelationTypeController.cs index 6fb9108d74..2845a82aa1 100644 --- a/src/Umbraco.Web/Editors/RelationTypeController.cs +++ b/src/Umbraco.Web/Editors/RelationTypeController.cs @@ -45,11 +45,8 @@ 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; } 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/Mapping/RelationMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/RelationMapDefinition.cs index 7f89c78f8a..8407a7421c 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,13 +39,44 @@ 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 = source.ParentObjectType.HasValue ? ObjectTypes.GetUmbracoObjectType(source.ParentObjectType.Value).GetFriendlyName() : string.Empty; - target.ChildObjectTypeName = source.ChildObjectType.HasValue ? ObjectTypes.GetUmbracoObjectType(source.ChildObjectType.Value).GetFriendlyName() : string.Empty; + // 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(); + } + + // Load the relations + + var relations = _relationService.GetByRelationTypeId(source.Id); + var displayRelations = context.MapEnumerable(relations); + + // Load the entities + var entities = _relationService.GetEntitiesFromRelations(relations) + .ToDictionary(x => (x.Item1.Id, x.Item2.Id), x => x); + + foreach(var r in displayRelations) + { + var pair = entities[(r.ParentId, r.ChildId)]; + var parent = pair.Item1; + var child = pair.Item2; + + r.ChildName = child.Name; + r.ParentName = parent.Name; + } + + target.Relations = displayRelations; + } // 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; From f3f242b416104d339f2cc62fb5d579c3628db82b Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 24 Oct 2019 22:39:12 +1100 Subject: [PATCH 077/548] fixes test --- .../Persistence/Repositories/RelationTypeRepositoryTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Tests/Persistence/Repositories/RelationTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/RelationTypeRepositoryTest.cs index 881ea23dc8..962737e1dc 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/RelationTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/RelationTypeRepositoryTest.cs @@ -133,7 +133,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(7)); } } From 15865d566b0549acf651b63cdd97b474e8a362d4 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 25 Oct 2019 14:17:18 +1100 Subject: [PATCH 078/548] Implements the tracking for document/media/member repos --- src/Umbraco.Core/Constants-Conventions.cs | 2 ++ .../Migrations/Install/DatabaseDataCreator.cs | 6 ++-- .../Migrations/Upgrade/UmbracoPlan.cs | 1 + .../Upgrade/V_8_5_0/AddNewRelationTypes.cs | 31 +++++++++++++++++++ .../V_8_5_0/UpdateRelationTypeTable.cs | 1 + .../Implement/ContentRepositoryBase.cs | 3 +- .../Implement/DocumentRepository.cs | 4 +++ .../Repositories/Implement/MediaRepository.cs | 4 +++ .../Implement/MemberRepository.cs | 4 +++ .../PropertyEditors/DataEditor.cs | 3 +- src/Umbraco.Core/Umbraco.Core.csproj | 1 + .../Trees/RelationTypeTreeController.cs | 2 ++ 12 files changed, 56 insertions(+), 6 deletions(-) create mode 100644 src/Umbraco.Core/Migrations/Upgrade/V_8_5_0/AddNewRelationTypes.cs diff --git a/src/Umbraco.Core/Constants-Conventions.cs b/src/Umbraco.Core/Constants-Conventions.cs index 25d259b7d1..e01f0eb0f5 100644 --- a/src/Umbraco.Core/Constants-Conventions.cs +++ b/src/Umbraco.Core/Constants-Conventions.cs @@ -357,6 +357,8 @@ namespace Umbraco.Core /// Alias for default relation type "Relate Parent Media Folder On Delete". /// public const string RelateParentMediaFolderOnDeleteAlias = "relateParentMediaFolderOnDelete"; + + //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/Migrations/Install/DatabaseDataCreator.cs b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs index 78e22ef28e..9a96810f23 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs @@ -319,13 +319,11 @@ namespace Umbraco.Core.Migrations.Install relationType.UniqueId = (relationType.Alias + "____" + relationType.Name).ToGuid(); _database.Insert(Constants.DatabaseSchema.Tables.RelationType, "id", false, relationType); - //TODO: We need to decide if we are going to change the relations APIs since it's pretty crappy that we have to explicitly define all relation type object type combinations... - - relationType = new RelationTypeDto { Id = 4, Alias = Constants.Conventions.RelationTypes.RelatedMediaAlias, ChildObjectType = Constants.ObjectTypes.Media, ParentObjectType = Constants.ObjectTypes.Document, Dual = false, Name = Constants.Conventions.RelationTypes.RelatedMediaName }; + relationType = new RelationTypeDto { Id = 4, Alias = Constants.Conventions.RelationTypes.RelatedMediaAlias, ChildObjectType = null, ParentObjectType = null, Dual = false, Name = Constants.Conventions.RelationTypes.RelatedMediaName }; relationType.UniqueId = (relationType.Alias + "____" + relationType.Name).ToGuid(); _database.Insert(Constants.DatabaseSchema.Tables.RelationType, "id", false, relationType); - relationType = new RelationTypeDto { Id = 5, Alias = Constants.Conventions.RelationTypes.RelatedDocumentAlias, ChildObjectType = Constants.ObjectTypes.Document, ParentObjectType = Constants.ObjectTypes.Document, Dual = false, Name = Constants.Conventions.RelationTypes.RelatedDocumentName }; + relationType = new RelationTypeDto { Id = 5, Alias = Constants.Conventions.RelationTypes.RelatedDocumentAlias, ChildObjectType = null, ParentObjectType = null, Dual = false, Name = Constants.Conventions.RelationTypes.RelatedDocumentName }; relationType.UniqueId = (relationType.Alias + "____" + relationType.Name).ToGuid(); _database.Insert(Constants.DatabaseSchema.Tables.RelationType, "id", false, relationType); } diff --git a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs index e5e335d309..45182b17e3 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs @@ -185,6 +185,7 @@ namespace Umbraco.Core.Migrations.Upgrade // to 8.5.0... To("{4759A294-9860-46BC-99F9-B4C975CAE580}"); + To("{0BC866BC-0665-487A-9913-0290BD0169AD}"); //FINAL } diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_5_0/AddNewRelationTypes.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_5_0/AddNewRelationTypes.cs new file mode 100644 index 0000000000..88c6c43c46 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_5_0/AddNewRelationTypes.cs @@ -0,0 +1,31 @@ +namespace Umbraco.Core.Migrations.Upgrade.V_8_5_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 = (alias + "____" + name).ToGuid(); //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_5_0/UpdateRelationTypeTable.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_5_0/UpdateRelationTypeTable.cs index 30174f8d13..b76f5ba3a7 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_5_0/UpdateRelationTypeTable.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_5_0/UpdateRelationTypeTable.cs @@ -2,6 +2,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_5_0 { + public class UpdateRelationTypeTable : MigrationBase { public UpdateRelationTypeTable(IMigrationContext context) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs index 3bb57dca9f..8010d60267 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs @@ -826,7 +826,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement foreach (var p in entity.Properties) { if (!PropertyEditors.TryGet(p.PropertyType.PropertyEditorAlias, out var editor)) continue; - if (!(editor is IDataValueReference reference)) continue; + var valueEditor = editor.GetValueEditor(); + if (!(valueEditor is IDataValueReference reference)) continue; //TODO: Support variants/segments! This is not required for this initial prototype which is why there is a check here if (!p.PropertyType.VariesByNothing()) continue; diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs index ce95875209..5e3e7f05b9 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs @@ -484,6 +484,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement ClearEntityTags(entity, _tagRepository); } + PersistRelations(entity); + entity.ResetDirtyProperties(); // troubleshooting @@ -687,6 +689,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement 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? entity.ResetDirtyProperties(); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs index 0423ac9125..a3f9e45485 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs @@ -289,6 +289,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // set tags SetEntityTags(entity, _tagRepository); + PersistRelations(entity); + OnUowRefreshedEntity(new ScopedEntityEventArgs(AmbientScope, entity)); entity.ResetDirtyProperties(); @@ -345,6 +347,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/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs index c36143d09a..2871bf1dd3 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs @@ -324,6 +324,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement SetEntityTags(entity, _tagRepository); + PersistRelations(entity); + OnUowRefreshedEntity(new ScopedEntityEventArgs(AmbientScope, entity)); entity.ResetDirtyProperties(); @@ -389,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/PropertyEditors/DataEditor.cs b/src/Umbraco.Core/PropertyEditors/DataEditor.cs index dbb2fc467e..a82011edcc 100644 --- a/src/Umbraco.Core/PropertyEditors/DataEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/DataEditor.cs @@ -19,6 +19,7 @@ namespace Umbraco.Core.PropertyEditors public class DataEditor : IDataEditor { private IDictionary _defaultConfiguration; + private IDataValueEditor _nonConfigured; /// /// Initializes a new instance of the class. @@ -90,7 +91,7 @@ namespace Umbraco.Core.PropertyEditors /// simple enough for now. /// // TODO: point of that one? shouldn't we always configure? - public IDataValueEditor GetValueEditor() => ExplicitValueEditor ?? CreateValueEditor(); + public IDataValueEditor GetValueEditor() => ExplicitValueEditor ?? (_nonConfigured ?? (_nonConfigured= CreateValueEditor())); /// /// diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 1f83bd2807..ff3f8bf972 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -245,6 +245,7 @@ + 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) From 25c2eed8883813101da1b2371a7d899eae808b9c Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 25 Oct 2019 14:33:40 +1100 Subject: [PATCH 079/548] Ensures the right type is tracked for links ensures relations are removed. --- src/Umbraco.Core/Constants-Conventions.cs | 9 +++++++++ .../Repositories/Implement/ContentRepositoryBase.cs | 7 +++---- .../PropertyEditors/RichTextPropertyEditor.cs | 2 +- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Core/Constants-Conventions.cs b/src/Umbraco.Core/Constants-Conventions.cs index e01f0eb0f5..e2e0f30874 100644 --- a/src/Umbraco.Core/Constants-Conventions.cs +++ b/src/Umbraco.Core/Constants-Conventions.cs @@ -358,6 +358,15 @@ namespace Umbraco.Core /// 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/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs index 8010d60267..aaa3946494 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs @@ -837,11 +837,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement trackedRelations.AddRange(refs); } - if (trackedRelations.Count == 0) return; + //First delete all auto-relations for this entity + RelationRepository.DeleteByParent(entity.Id, Constants.Conventions.RelationTypes.AutomaticRelationTypes); - //First delete all relations for this entity - var relationTypes = trackedRelations.Select(x => x.RelationTypeAlias).ToArray(); - RelationRepository.DeleteByParent(entity.Id, relationTypes); + if (trackedRelations.Count == 0) return; var udiToGuids = trackedRelations.Select(x => x.Udi as GuidUdi) .ToDictionary(x => (Udi)x, x => x.Guid); diff --git a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs index ed3f484a4e..77617f7779 100644 --- a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs @@ -144,7 +144,7 @@ namespace Umbraco.Web.PropertyEditors yield return new UmbracoEntityReference(udi, Constants.Conventions.RelationTypes.RelatedMediaAlias); foreach (var udi in _localLinkParser.FindUdisFromLocalLinks(asString)) - yield return new UmbracoEntityReference(udi, Constants.Conventions.RelationTypes.RelatedMediaAlias); + yield return new UmbracoEntityReference(udi, Constants.Conventions.RelationTypes.RelatedDocumentAlias); //TODO: Detect Macros too ... but we can save that for a later date, right now need to do media refs } From 70ccee302d186542235e537ed1ea611ae22cc694 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 25 Oct 2019 15:08:56 +1100 Subject: [PATCH 080/548] Renames/moves some stuff and adds test to show auto relation tracking --- ...ntityType.cs => Contants-UdiEntityType.cs} | 0 src/Umbraco.Core/Umbraco.Core.csproj | 2 +- .../Services/ContentServiceTests.cs | 41 +++++++++++++++++++ ...Tests.cs => HtmlImageSourceParserTests.cs} | 2 +- .../HtmlLocalLinkParserTests.cs | 38 ++++++++++++----- .../Templates/LocalLinkParserTests.cs | 34 --------------- src/Umbraco.Tests/Umbraco.Tests.csproj | 5 +-- .../PropertyEditors/RichTextPropertyEditor.cs | 4 ++ .../Templates/HtmlImageSourceParser.cs | 2 +- 9 files changed, 78 insertions(+), 50 deletions(-) rename src/Umbraco.Core/{UdiEntityType.cs => Contants-UdiEntityType.cs} (100%) rename src/Umbraco.Tests/Templates/{ImageSourceParserTests.cs => HtmlImageSourceParserTests.cs} (98%) rename src/Umbraco.Tests/{Web => Templates}/HtmlLocalLinkParserTests.cs (78%) delete mode 100644 src/Umbraco.Tests/Templates/LocalLinkParserTests.cs 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/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index ff3f8bf972..2fd2c38bdd 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -1522,7 +1522,7 @@ - + diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index 7d65513cc0..89873b5880 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() { diff --git a/src/Umbraco.Tests/Templates/ImageSourceParserTests.cs b/src/Umbraco.Tests/Templates/HtmlImageSourceParserTests.cs similarity index 98% rename from src/Umbraco.Tests/Templates/ImageSourceParserTests.cs rename to src/Umbraco.Tests/Templates/HtmlImageSourceParserTests.cs index dbe5654117..3bef495507 100644 --- a/src/Umbraco.Tests/Templates/ImageSourceParserTests.cs +++ b/src/Umbraco.Tests/Templates/HtmlImageSourceParserTests.cs @@ -20,7 +20,7 @@ namespace Umbraco.Tests.Templates [TestFixture] - public class ImageSourceParserTests + public class HtmlImageSourceParserTests { [Test] public void Returns_Udis_From_Data_Udi_Html_Attributes() diff --git a/src/Umbraco.Tests/Web/HtmlLocalLinkParserTests.cs b/src/Umbraco.Tests/Templates/HtmlLocalLinkParserTests.cs similarity index 78% rename from src/Umbraco.Tests/Web/HtmlLocalLinkParserTests.cs rename to src/Umbraco.Tests/Templates/HtmlLocalLinkParserTests.cs index e6a0abeb4c..7cd96a32ed 100644 --- a/src/Umbraco.Tests/Web/HtmlLocalLinkParserTests.cs +++ b/src/Umbraco.Tests/Templates/HtmlLocalLinkParserTests.cs @@ -1,26 +1,44 @@ -using System; +using Moq; +using NUnit.Framework; +using System; using System.Linq; using System.Web; -using Moq; -using NUnit.Framework; -using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.Services; -using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing.Objects; using Umbraco.Tests.Testing.Objects.Accessors; using Umbraco.Web; -using Umbraco.Web.PublishedCache; using Umbraco.Web.Routing; using Umbraco.Web.Templates; -namespace Umbraco.Tests.Web +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 ")] @@ -40,7 +58,7 @@ namespace Umbraco.Tests.Web 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); diff --git a/src/Umbraco.Tests/Templates/LocalLinkParserTests.cs b/src/Umbraco.Tests/Templates/LocalLinkParserTests.cs deleted file mode 100644 index e09d71196e..0000000000 --- a/src/Umbraco.Tests/Templates/LocalLinkParserTests.cs +++ /dev/null @@ -1,34 +0,0 @@ -using NUnit.Framework; -using System.Linq; -using Umbraco.Core; -using Umbraco.Tests.Testing.Objects.Accessors; -using Umbraco.Web.Templates; - -namespace Umbraco.Tests.Templates -{ - [TestFixture] - public class LocalLinkParserTests - { - [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]); - } - } -} diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index fa654ad4a4..4b035f631e 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -159,7 +159,7 @@ - + @@ -255,8 +255,7 @@ - - + diff --git a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs index 77617f7779..1c97a2de7a 100644 --- a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs @@ -140,9 +140,13 @@ namespace Umbraco.Web.PropertyEditors { var asString = value == null ? string.Empty : value is string str ? str : value.ToString(); + //TODO: FindUdisFromDataAttributes will return UDIs of any type found, typically these will always be "media" but + // if the text is modified to be another type it will still be returned but we will always be relating these with the RelatedMediaAlias foreach (var udi in _imageSourceParser.FindUdisFromDataAttributes(asString)) yield return new UmbracoEntityReference(udi, Constants.Conventions.RelationTypes.RelatedMediaAlias); + //TODO: FindUdisFromLocalLinks will return UDIs of any type found, typically these will always be "document" but + // if the text is modified to be another type it will still be returned but we will always be relating these with the RelatedDocumentAlias foreach (var udi in _localLinkParser.FindUdisFromLocalLinks(asString)) yield return new UmbracoEntityReference(udi, Constants.Conventions.RelationTypes.RelatedDocumentAlias); diff --git a/src/Umbraco.Web/Templates/HtmlImageSourceParser.cs b/src/Umbraco.Web/Templates/HtmlImageSourceParser.cs index 66a1cee5bb..b0d6980ef3 100644 --- a/src/Umbraco.Web/Templates/HtmlImageSourceParser.cs +++ b/src/Umbraco.Web/Templates/HtmlImageSourceParser.cs @@ -20,7 +20,7 @@ namespace Umbraco.Web.Templates RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase); /// - /// Parses out UDIs from an html string based on 'data-udi' html attributes + /// Parses out media UDIs from an html string based on 'data-udi' html attributes /// /// /// From 1a75d99a6b3c6646842c1777502f7f9058162db8 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 25 Oct 2019 16:14:44 +1100 Subject: [PATCH 081/548] fixes test --- src/Umbraco.Core/PropertyEditors/DataEditor.cs | 8 ++++---- .../Packaging/PackageDataInstallationTests.cs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Core/PropertyEditors/DataEditor.cs b/src/Umbraco.Core/PropertyEditors/DataEditor.cs index a82011edcc..3f5f6afa4f 100644 --- a/src/Umbraco.Core/PropertyEditors/DataEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/DataEditor.cs @@ -91,7 +91,7 @@ namespace Umbraco.Core.PropertyEditors /// simple enough for now. /// // TODO: point of that one? shouldn't we always configure? - public IDataValueEditor GetValueEditor() => ExplicitValueEditor ?? (_nonConfigured ?? (_nonConfigured= CreateValueEditor())); + public IDataValueEditor GetValueEditor() => ExplicitValueEditor ?? (_nonConfigured ?? (_nonConfigured = CreateValueEditor())); /// /// @@ -114,7 +114,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; } @@ -164,7 +164,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); } @@ -176,7 +176,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.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"; } } From 1a703dcee8c19d89621d68daf46a4da9e1fa00a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Kottal?= Date: Sun, 27 Oct 2019 10:58:52 +0100 Subject: [PATCH 082/548] Aligns the padding of .umb-overlay-container with umb-overlay-header --- src/Umbraco.Web.UI.Client/src/less/components/overlays.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 d060f34a36..eb8740b385 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/overlays.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/overlays.less @@ -66,7 +66,7 @@ flex-shrink: 1; flex-basis: auto; position: relative; - padding: 20px; + padding: 30px; background: @white; max-height: calc(100vh - 170px); overflow-y: auto; From 813eb9fed1b343d7b5d13d234bd069f8ab0fce2b Mon Sep 17 00:00:00 2001 From: BatJan Date: Sun, 27 Oct 2019 21:12:38 +0100 Subject: [PATCH 083/548] Add table class to improve the look of the column overview --- .../propertyeditors/listview/includeproperties.prevalues.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/includeproperties.prevalues.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/includeproperties.prevalues.html index ba08aa2293..c6675ccec8 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/includeproperties.prevalues.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/includeproperties.prevalues.html @@ -12,7 +12,7 @@
- +
@@ -31,7 +31,7 @@
- (system field) + (system field)
From f6aee731fc7ee1f1cfe028d78cd91fdc7065bb31 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Sun, 27 Oct 2019 21:35:26 +0100 Subject: [PATCH 084/548] Make member types searchable --- .../Trees/MemberTypeTreeController.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Trees/MemberTypeTreeController.cs b/src/Umbraco.Web/Trees/MemberTypeTreeController.cs index bd80f63897..2046baf2d3 100644 --- a/src/Umbraco.Web/Trees/MemberTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/MemberTypeTreeController.cs @@ -2,7 +2,10 @@ using System.Linq; using System.Net.Http.Formatting; using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Models.Trees; +using Umbraco.Web.Search; using Umbraco.Web.WebApi.Filters; namespace Umbraco.Web.Trees @@ -10,8 +13,15 @@ namespace Umbraco.Web.Trees [CoreTree] [UmbracoTreeAuthorize(Constants.Trees.MemberTypes)] [Tree(Constants.Applications.Settings, Constants.Trees.MemberTypes, SortOrder = 2, TreeGroup = Constants.Trees.Groups.Settings)] - public class MemberTypeTreeController : MemberTypeAndGroupTreeControllerBase + public class MemberTypeTreeController : MemberTypeAndGroupTreeControllerBase, ISearchableTree { + private readonly UmbracoTreeSearcher _treeSearcher; + + public MemberTypeTreeController(UmbracoTreeSearcher treeSearcher) + { + _treeSearcher = treeSearcher; + } + protected override TreeNode CreateRootNode(FormDataCollection queryStrings) { var root = base.CreateRootNode(queryStrings); @@ -25,5 +35,9 @@ namespace Umbraco.Web.Trees .OrderBy(x => x.Name) .Select(dt => CreateTreeNode(dt, Constants.ObjectTypes.MemberType, id, queryStrings, Constants.Icons.MemberType, false)); } + + public IEnumerable Search(string query, int pageSize, long pageIndex, out long totalFound, string searchFrom = null) + => _treeSearcher.EntitySearch(UmbracoObjectTypes.MemberType, query, pageSize, pageIndex, out totalFound, searchFrom); + } } From a98188a88cfbb74cd7a6d19b75e47068f14e7cec Mon Sep 17 00:00:00 2001 From: BatJan Date: Sun, 27 Oct 2019 21:42:25 +0100 Subject: [PATCH 085/548] Add aria-hidden and change to + From ba1fc56c94c89630360a70f0011ed4b26b8ef6a5 Mon Sep 17 00:00:00 2001 From: BatJan Date: Sun, 27 Oct 2019 21:52:11 +0100 Subject: [PATCH 086/548] Change to From 43cd1268caed58c7a6cc2e312774411b681ef84b Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 28 Oct 2019 18:02:52 +1100 Subject: [PATCH 087/548] Moves classes/namespaces, reduces statics, reduces usages of "Current", imports some unit tests from orig MB. --- src/SolutionInfo.cs | 3 + .../BackOffice/DashboardReport.cs | 58 +++ .../ModelsBuilderBackOfficeController.cs | 73 +-- src/Umbraco.ModelsBuilder/Building/Builder.cs | 24 +- .../Building/TextBuilder.cs | 26 +- .../Building/TypeModel.cs | 8 +- .../ModelsBuilderComponent.cs | 56 ++- .../ModelsBuilderComposer.cs | 7 +- .../ModelsBuilderInitializer.cs | 6 +- .../ConfigsExtensions.cs | 4 +- .../Configuration/IModelsBuilderConfig.cs | 14 + .../{Config.cs => ModelsBuilderConfig.cs} | 25 +- .../Configuration/ModelsMode.cs | 2 +- .../Dashboard/BuilderDashboardHelper.cs | 62 --- .../{Umbraco => }/HashCombiner.cs | 8 +- .../Umbraco.ModelsBuilder.csproj | 26 +- .../Umbraco/LiveModelsProvider.cs | 77 ++- .../Umbraco/LiveModelsProviderModule.cs | 44 ++ .../{HashHelper.cs => ModelsBuilderHasher.cs} | 2 +- .../Umbraco/ModelsGenerationError.cs | 19 +- .../Umbraco/ModelsGenerator.cs | 63 +++ .../Umbraco/OutOfDateModelsStatus.cs | 7 +- .../Umbraco/PureLiveModelFactory.cs | 14 +- .../Umbraco/UmbracoServices.cs | 2 - .../Validation/ContentTypeModelValidator.cs | 2 +- .../ModelsBuilder/BuilderTests.cs | 443 ++++++++++++++++++ .../ModelsBuilder/ConfigTests.cs | 49 ++ .../ModelsBuilder/StringExtensions.cs | 16 + .../ModelsBuilder/UmbracoApplicationTests.cs | 63 +++ src/Umbraco.Tests/Umbraco.Tests.csproj | 8 + 30 files changed, 923 insertions(+), 288 deletions(-) create mode 100644 src/Umbraco.ModelsBuilder/BackOffice/DashboardReport.cs rename src/Umbraco.ModelsBuilder/{Umbraco => BackOffice}/ModelsBuilderBackOfficeController.cs (64%) rename src/Umbraco.ModelsBuilder/{Umbraco => Compose}/ModelsBuilderComponent.cs (81%) rename src/Umbraco.ModelsBuilder/{Umbraco => Compose}/ModelsBuilderComposer.cs (89%) rename src/Umbraco.ModelsBuilder/{Umbraco => Compose}/ModelsBuilderInitializer.cs (88%) create mode 100644 src/Umbraco.ModelsBuilder/Configuration/IModelsBuilderConfig.cs rename src/Umbraco.ModelsBuilder/Configuration/{Config.cs => ModelsBuilderConfig.cs} (90%) delete mode 100644 src/Umbraco.ModelsBuilder/Dashboard/BuilderDashboardHelper.cs rename src/Umbraco.ModelsBuilder/{Umbraco => }/HashCombiner.cs (76%) create mode 100644 src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProviderModule.cs rename src/Umbraco.ModelsBuilder/Umbraco/{HashHelper.cs => ModelsBuilderHasher.cs} (97%) create mode 100644 src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerator.cs create mode 100644 src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs create mode 100644 src/Umbraco.Tests/ModelsBuilder/ConfigTests.cs create mode 100644 src/Umbraco.Tests/ModelsBuilder/StringExtensions.cs create mode 100644 src/Umbraco.Tests/ModelsBuilder/UmbracoApplicationTests.cs diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index bf3a271d32..93921e07a2 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -1,5 +1,6 @@ using System.Reflection; using System.Resources; +using System.Runtime.CompilerServices; [assembly: AssemblyCompany("Umbraco")] [assembly: AssemblyCopyright("Copyright © Umbraco 2019")] @@ -20,3 +21,5 @@ using System.Resources; // these are FYI and changed automatically [assembly: AssemblyFileVersion("8.3.0")] [assembly: AssemblyInformationalVersion("8.3.0")] + +[assembly: InternalsVisibleTo("Umbraco.Tests")] diff --git a/src/Umbraco.ModelsBuilder/BackOffice/DashboardReport.cs b/src/Umbraco.ModelsBuilder/BackOffice/DashboardReport.cs new file mode 100644 index 0000000000..57afeaf069 --- /dev/null +++ b/src/Umbraco.ModelsBuilder/BackOffice/DashboardReport.cs @@ -0,0 +1,58 @@ +using System.Text; +using Umbraco.Core.Composing; +using Umbraco.ModelsBuilder.Configuration; +using Umbraco.ModelsBuilder.Umbraco; + +namespace Umbraco.ModelsBuilder.BackOffice +{ + internal class DashboardReport + { + private readonly IModelsBuilderConfig _config; + private readonly ModelsGenerator _modelsGenerator; + + public DashboardReport(IModelsBuilderConfig config, ModelsGenerator modelsGenerator) + { + _config = config; + _modelsGenerator = modelsGenerator; + } + + public bool CanGenerate() => _config.ModelsMode.SupportsExplicitGeneration(); + + public bool AreModelsOutOfDate() => OutOfDateModelsStatus.IsOutOfDate; + + public string LastError() => _modelsGenerator.GetLastError(); + + public string Text() + { + var sb = new StringBuilder(); + + sb.Append("Version: "); + sb.Append(Api.ApiVersion.Current.Version); + sb.Append("
 
"); + + sb.Append("ModelsBuilder is enabled, with the following configuration:"); + + sb.Append("
    "); + + sb.Append("
  • The models factory is "); + sb.Append(_config.EnableFactory || _config.ModelsMode == ModelsMode.PureLive + ? "enabled" + : "not enabled. Umbraco will not use models"); + sb.Append(".
  • "); + + sb.Append(_config.ModelsMode != ModelsMode.Nothing + ? $"
  • {_config.ModelsMode} models are enabled.
  • " + : "
  • No models mode is specified: models will not be generated.
  • "); + + sb.Append($"
  • Models namespace is {_config.ModelsNamespace}.
  • "); + + sb.Append("
  • Tracking of out-of-date models is "); + sb.Append(_config.FlagOutOfDateModels ? "enabled" : "not enabled"); + sb.Append(".
  • "); + + sb.Append("
"); + + return sb.ToString(); + } + } +} diff --git a/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderBackOfficeController.cs b/src/Umbraco.ModelsBuilder/BackOffice/ModelsBuilderBackOfficeController.cs similarity index 64% rename from src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderBackOfficeController.cs rename to src/Umbraco.ModelsBuilder/BackOffice/ModelsBuilderBackOfficeController.cs index 88bfb7d7c2..561da0a3d6 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderBackOfficeController.cs +++ b/src/Umbraco.ModelsBuilder/BackOffice/ModelsBuilderBackOfficeController.cs @@ -9,11 +9,11 @@ using System.Web.Hosting; using Umbraco.Core.Exceptions; using Umbraco.ModelsBuilder.Building; using Umbraco.ModelsBuilder.Configuration; -using Umbraco.ModelsBuilder.Dashboard; +using Umbraco.ModelsBuilder.Umbraco; using Umbraco.Web.Editors; using Umbraco.Web.WebApi.Filters; -namespace Umbraco.ModelsBuilder.Umbraco +namespace Umbraco.ModelsBuilder.BackOffice { /// /// API controller for use in the Umbraco back office with Angular resources @@ -26,13 +26,16 @@ namespace Umbraco.ModelsBuilder.Umbraco [UmbracoApplicationAuthorize(Core.Constants.Applications.Settings)] public class ModelsBuilderBackOfficeController : UmbracoAuthorizedJsonController { - private readonly UmbracoServices _umbracoServices; - private readonly Config _config; + private readonly IModelsBuilderConfig _config; + private readonly ModelsGenerator _modelGenerator; + private readonly DashboardReport _dashboardReport; - public ModelsBuilderBackOfficeController(UmbracoServices umbracoServices, Config config) + public ModelsBuilderBackOfficeController(IModelsBuilderConfig config, ModelsGenerator modelsGenerator) { //_umbracoServices = umbracoServices; _config = config; + _modelGenerator = modelsGenerator; + _dashboardReport = new DashboardReport(config, modelsGenerator); } // invoked by the dashboard @@ -51,20 +54,17 @@ namespace Umbraco.ModelsBuilder.Umbraco return Request.CreateResponse(HttpStatusCode.OK, result2, Configuration.Formatters.JsonFormatter); } - var modelsDirectory = config.ModelsDirectory; - var bin = HostingEnvironment.MapPath("~/bin"); if (bin == null) throw new PanicException("bin is null."); // EnableDllModels will recycle the app domain - but this request will end properly - GenerateModels(modelsDirectory); - - ModelsGenerationError.Clear(); + _modelGenerator.GenerateModels(); + _modelGenerator.ClearErrors(); } catch (Exception e) { - ModelsGenerationError.Report("Failed to build models.", e); + _modelGenerator.ReportError("Failed to build models.", e); } return Request.CreateResponse(HttpStatusCode.OK, GetDashboardResult(), Configuration.Formatters.JsonFormatter); @@ -76,9 +76,9 @@ namespace Umbraco.ModelsBuilder.Umbraco public HttpResponseMessage GetModelsOutOfDateStatus() { var status = OutOfDateModelsStatus.IsEnabled - ? (OutOfDateModelsStatus.IsOutOfDate + ? OutOfDateModelsStatus.IsOutOfDate ? new OutOfDateStatus { Status = OutOfDateType.OutOfDate } - : new OutOfDateStatus { Status = OutOfDateType.Current }) + : new OutOfDateStatus { Status = OutOfDateType.Current } : new OutOfDateStatus { Status = OutOfDateType.Unknown }; return Request.CreateResponse(HttpStatusCode.OK, status, Configuration.Formatters.JsonFormatter); @@ -98,52 +98,13 @@ namespace Umbraco.ModelsBuilder.Umbraco return new Dashboard { Enable = true, - Text = BuilderDashboardHelper.Text(), - CanGenerate = BuilderDashboardHelper.CanGenerate(), - OutOfDateModels = BuilderDashboardHelper.AreModelsOutOfDate(), - LastError = BuilderDashboardHelper.LastError(), + Text = _dashboardReport.Text(), + CanGenerate = _dashboardReport.CanGenerate(), + OutOfDateModels = _dashboardReport.AreModelsOutOfDate(), + LastError = _dashboardReport.LastError(), }; } - private void GenerateModels(string modelsDirectory) - { - GenerateModels(_umbracoServices, modelsDirectory, _config.ModelsNamespace); - } - - internal static void GenerateModels(UmbracoServices umbracoServices, string modelsDirectory, string modelsNamespace) - { - if (!Directory.Exists(modelsDirectory)) - Directory.CreateDirectory(modelsDirectory); - - foreach (var file in Directory.GetFiles(modelsDirectory, "*.generated.cs")) - File.Delete(file); - - var typeModels = umbracoServices.GetAllTypes(); - - var builder = new TextBuilder(typeModels, modelsNamespace); - - foreach (var typeModel in builder.GetModelsToGenerate()) - { - var sb = new StringBuilder(); - builder.Generate(sb, typeModel); - var filename = Path.Combine(modelsDirectory, typeModel.ClrName + ".generated.cs"); - File.WriteAllText(filename, sb.ToString()); - } - - // the idea was to calculate the current hash and to add it as an extra file to the compilation, - // in order to be able to detect whether a DLL is consistent with an environment - however the - // environment *might not* contain the local partial files, and thus it could be impossible to - // calculate the hash. So... maybe that's not a good idea after all? - /* - var currentHash = HashHelper.Hash(ourFiles, typeModels); - ourFiles["models.hash.cs"] = $@"using Umbraco.ModelsBuilder; -[assembly:ModelsBuilderAssembly(SourceHash = ""{currentHash}"")] -"; - */ - - OutOfDateModelsStatus.Clear(); - } - [DataContract] internal class BuildResult { diff --git a/src/Umbraco.ModelsBuilder/Building/Builder.cs b/src/Umbraco.ModelsBuilder/Building/Builder.cs index f65d8e9e7c..fa05b9d9a1 100644 --- a/src/Umbraco.ModelsBuilder/Building/Builder.cs +++ b/src/Umbraco.ModelsBuilder/Building/Builder.cs @@ -23,12 +23,11 @@ namespace Umbraco.ModelsBuilder.Building /// internal abstract class Builder { + private readonly IList _typeModels; protected Dictionary ModelsMap { get; } = new Dictionary(); - private static Config Config => Current.Configs.ModelsBuilder(); - // the list of assemblies that will be 'using' by default protected readonly IList TypesUsing = new List { @@ -69,27 +68,20 @@ namespace Umbraco.ModelsBuilder.Building /// Includes those that are ignored. internal IList TypeModels => _typeModels; - /// - /// Initializes a new instance of the class with a list of models to generate - /// and the result of code parsing. - /// - /// The list of models to generate. - protected Builder(IList typeModels) - : this(typeModels, null) - { } - /// /// Initializes a new instance of the class with a list of models to generate, /// the result of code parsing, and a models namespace. /// /// The list of models to generate. /// The models namespace. - protected Builder(IList typeModels, string modelsNamespace) + protected Builder(IModelsBuilderConfig config, IList typeModels) { _typeModels = typeModels ?? throw new ArgumentNullException(nameof(typeModels)); + Config = config ?? throw new ArgumentNullException(nameof(config)); + // can be null or empty, we'll manage - ModelsNamespace = modelsNamespace; + ModelsNamespace = Config.ModelsNamespace; // but we want it to prepare Prepare(); @@ -99,6 +91,8 @@ namespace Umbraco.ModelsBuilder.Building protected Builder() { } + protected IModelsBuilderConfig Config { get; } + /// /// Prepares generation by processing the result of code parsing. /// @@ -204,6 +198,8 @@ namespace Umbraco.ModelsBuilder.Building // cannot figure out is a symbol is ambiguous without Roslyn // so... let's say everything is ambiguous - code won't be // pretty but it'll work + + // Essentially this means that a `global::` syntax will be output for the generated models return true; } @@ -220,7 +216,7 @@ namespace Umbraco.ModelsBuilder.Building // default // fixme - should NOT reference config here, should make ModelsNamespace mandatory - return Config.ModelsNamespace; + return string.IsNullOrWhiteSpace(Config.ModelsNamespace) ? ModelsBuilderConfig.DefaultModelsNamespace : Config.ModelsNamespace; } protected string GetModelsBaseClassName(TypeModel type) diff --git a/src/Umbraco.ModelsBuilder/Building/TextBuilder.cs b/src/Umbraco.ModelsBuilder/Building/TextBuilder.cs index 7121dad1a9..79a64bd1ed 100644 --- a/src/Umbraco.ModelsBuilder/Building/TextBuilder.cs +++ b/src/Umbraco.ModelsBuilder/Building/TextBuilder.cs @@ -20,31 +20,19 @@ namespace Umbraco.ModelsBuilder.Building /// and the result of code parsing. /// /// The list of models to generate. - public TextBuilder(IList typeModels) - : base(typeModels) - { } - - /// - /// Initializes a new instance of the class with a list of models to generate, - /// the result of code parsing, and a models namespace. - /// - /// The list of models to generate. - /// The models namespace. - public TextBuilder(IList typeModels, string modelsNamespace) - : base(typeModels, modelsNamespace) + public TextBuilder(IModelsBuilderConfig config, IList typeModels) + : base(config, typeModels) { } // internal for unit tests only internal TextBuilder() { } - private static Config Config => Current.Configs.ModelsBuilder(); - /// - /// Outputs a generated model to a string builder. - /// - /// The string builder. - /// The model to generate. + /// Outputs a generated model to a string builder. + /// + /// The string builder. + /// The model to generate. public void Generate(StringBuilder sb, TypeModel typeModel) { WriteHeader(sb); @@ -354,7 +342,7 @@ namespace Umbraco.ModelsBuilder.Building var mixinStaticGetterName = MixinStaticGetterName(property.ClrName); - if (type.StaticMixinMethods.Contains(mixinStaticGetterName)) return; + //if (type.StaticMixinMethods.Contains(mixinStaticGetterName)) return; sb.Append("\n"); diff --git a/src/Umbraco.ModelsBuilder/Building/TypeModel.cs b/src/Umbraco.ModelsBuilder/Building/TypeModel.cs index 941894774e..06b5e7848a 100644 --- a/src/Umbraco.ModelsBuilder/Building/TypeModel.cs +++ b/src/Umbraco.ModelsBuilder/Building/TypeModel.cs @@ -77,10 +77,10 @@ namespace Umbraco.ModelsBuilder.Building /// public readonly List ImplementingInterfaces = new List(); - /// - /// Gets the list of existing static mixin method candidates. - /// - public readonly List StaticMixinMethods = new List(); + ///// + ///// Gets the list of existing static mixin method candidates. + ///// + //public readonly List StaticMixinMethods = new List(); //TODO: Do we need this? it isn't used /// /// Gets a value indicating whether this model has a base class. diff --git a/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderComponent.cs b/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComponent.cs similarity index 81% rename from src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderComponent.cs rename to src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComponent.cs index 35f953d3f1..6a9b8e1115 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderComponent.cs +++ b/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComponent.cs @@ -8,23 +8,25 @@ using Umbraco.Core.Composing; using Umbraco.Core.IO; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; +using Umbraco.ModelsBuilder.BackOffice; using Umbraco.ModelsBuilder.Configuration; +using Umbraco.ModelsBuilder.Umbraco; using Umbraco.Web; using Umbraco.Web.JavaScript; using Umbraco.Web.Mvc; -namespace Umbraco.ModelsBuilder.Umbraco +namespace Umbraco.ModelsBuilder.Compose { public class ModelsBuilderComponent : IComponent { - private readonly UmbracoServices _umbracoServices; - private readonly Config _config; + private readonly IModelsBuilderConfig _config; + private readonly LiveModelsProvider _liveModelsProvider; - public ModelsBuilderComponent(UmbracoServices umbracoServices, Config config) + public ModelsBuilderComponent(IModelsBuilderConfig config, LiveModelsProvider liveModelsProvider) { - _umbracoServices = umbracoServices; _config = config; + _liveModelsProvider = liveModelsProvider; } public void Initialize() @@ -39,7 +41,7 @@ namespace Umbraco.ModelsBuilder.Umbraco // fixme LiveModelsProvider should not be static if (_config.ModelsMode.IsLiveNotPure()) - LiveModelsProvider.Install(_umbracoServices); + _liveModelsProvider.Install(); // fixme OutOfDateModelsStatus should not be static if (_config.FlagOutOfDateModels) @@ -105,7 +107,6 @@ namespace Umbraco.ModelsBuilder.Umbraco throw new InvalidOperationException("The additionalData key: ContentTypeAlias was not found"); foreach (var template in e.SavedEntities) - { // if it is in fact a new entity (not been saved yet) and the "CreateTemplateForContentType" key // is found, then it means a new template is being created based on the creation of a document type if (!template.HasIdentity && string.IsNullOrWhiteSpace(template.Content)) @@ -129,7 +130,6 @@ namespace Umbraco.ModelsBuilder.Umbraco //set the template content to the new markup template.Content = markup; } - } } private void ContentModelBinder_ModelBindingException(object sender, ContentModelBinder.ModelBindingArgs args) @@ -154,32 +154,30 @@ namespace Umbraco.ModelsBuilder.Umbraco } // both are ModelsBuilder types - var pureSource = sourceAttr.PureLive; - var pureModel = modelAttr.PureLive; + var pureSource = sourceAttr.PureLive; + var pureModel = modelAttr.PureLive; - if (sourceAttr.PureLive || modelAttr.PureLive) - { - if (pureSource == false || pureModel == false) - { + if (sourceAttr.PureLive || modelAttr.PureLive) + if (pureSource == false || pureModel == false) + { // only one is pure - report, but better not restart (loops?) - args.Message.Append(pureSource - ? " The content model is PureLive, but the view model is not." - : " The view model is PureLive, but the content model is not."); - args.Message.Append(" The application is in an unstable state and should be restarted."); - } - else - { + args.Message.Append(pureSource + ? " The content model is PureLive, but the view model is not." + : " The view model is PureLive, but the content model is not."); + args.Message.Append(" The application is in an unstable state and should be restarted."); + } + else + { // both are pure - report, and if different versions, restart // if same version... makes no sense... and better not restart (loops?) - var sourceVersion = args.SourceType.Assembly.GetName().Version; + var sourceVersion = args.SourceType.Assembly.GetName().Version; var modelVersion = args.ModelType.Assembly.GetName().Version; - args.Message.Append(" Both view and content models are PureLive, with "); - args.Message.Append(sourceVersion == modelVersion - ? "same version. The application is in an unstable state and should be restarted." - : "different versions. The application is in an unstable state and is going to be restarted."); - args.Restart = sourceVersion != modelVersion; - } - } + args.Message.Append(" Both view and content models are PureLive, with "); + args.Message.Append(sourceVersion == modelVersion + ? "same version. The application is in an unstable state and should be restarted." + : "different versions. The application is in an unstable state and is going to be restarted."); + args.Restart = sourceVersion != modelVersion; + } } } } diff --git a/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderComposer.cs b/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComposer.cs similarity index 89% rename from src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderComposer.cs rename to src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComposer.cs index 3dae2f86ab..71250b2eb6 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderComposer.cs +++ b/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComposer.cs @@ -3,9 +3,10 @@ using Umbraco.Core; using Umbraco.Core.Composing; using Umbraco.Core.Models.PublishedContent; using Umbraco.ModelsBuilder.Configuration; +using Umbraco.ModelsBuilder.Umbraco; using Umbraco.Web.PublishedCache.NuCache; -namespace Umbraco.ModelsBuilder.Umbraco +namespace Umbraco.ModelsBuilder.Compose { [ComposeBefore(typeof(NuCacheComposer))] [RuntimeLevel(MinLevel = RuntimeLevel.Run)] @@ -16,7 +17,9 @@ namespace Umbraco.ModelsBuilder.Umbraco base.Compose(composition); composition.Register(Lifetime.Singleton); - composition.Configs.Add(() => new Config()); + composition.Configs.Add(() => new ModelsBuilderConfig()); + composition.RegisterUnique(); + composition.RegisterUnique(); if (composition.Configs.ModelsBuilder().ModelsMode == ModelsMode.PureLive) ComposeForLiveModels(composition); diff --git a/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderInitializer.cs b/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderInitializer.cs similarity index 88% rename from src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderInitializer.cs rename to src/Umbraco.ModelsBuilder/Compose/ModelsBuilderInitializer.cs index 9bd662da37..63f2336ebf 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderInitializer.cs +++ b/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderInitializer.cs @@ -1,12 +1,12 @@ using System.Web; using System.Web.Compilation; -using Umbraco.ModelsBuilder.Umbraco; +using Umbraco.ModelsBuilder.Compose; [assembly: PreApplicationStartMethod(typeof(ModelsBuilderInitializer), "Initialize")] -namespace Umbraco.ModelsBuilder.Umbraco +namespace Umbraco.ModelsBuilder.Compose { - public static class ModelsBuilderInitializer + internal static class ModelsBuilderInitializer { public static void Initialize() { diff --git a/src/Umbraco.ModelsBuilder/ConfigsExtensions.cs b/src/Umbraco.ModelsBuilder/ConfigsExtensions.cs index dc0b136422..c989be5aca 100644 --- a/src/Umbraco.ModelsBuilder/ConfigsExtensions.cs +++ b/src/Umbraco.ModelsBuilder/ConfigsExtensions.cs @@ -14,7 +14,7 @@ namespace Umbraco.ModelsBuilder /// Getting the models builder configuration freezes its state, /// and any attempt at modifying the configuration using the Setup method /// will be ignored. - public static Config ModelsBuilder(this Configs configs) - => configs.GetConfig(); + public static ModelsBuilderConfig ModelsBuilder(this Configs configs) + => configs.GetConfig(); } } \ No newline at end of file diff --git a/src/Umbraco.ModelsBuilder/Configuration/IModelsBuilderConfig.cs b/src/Umbraco.ModelsBuilder/Configuration/IModelsBuilderConfig.cs new file mode 100644 index 0000000000..3bca389f2f --- /dev/null +++ b/src/Umbraco.ModelsBuilder/Configuration/IModelsBuilderConfig.cs @@ -0,0 +1,14 @@ +namespace Umbraco.ModelsBuilder.Configuration +{ + public interface IModelsBuilderConfig + { + bool AcceptUnsafeModelsDirectory { get; } + int DebugLevel { get; } + bool EnableFactory { get; } + bool FlagOutOfDateModels { get; } + bool IsDebug { get; } + string ModelsDirectory { get; } + ModelsMode ModelsMode { get; } + string ModelsNamespace { get; } + } +} diff --git a/src/Umbraco.ModelsBuilder/Configuration/Config.cs b/src/Umbraco.ModelsBuilder/Configuration/ModelsBuilderConfig.cs similarity index 90% rename from src/Umbraco.ModelsBuilder/Configuration/Config.cs rename to src/Umbraco.ModelsBuilder/Configuration/ModelsBuilderConfig.cs index cf98e383eb..caff5001a4 100644 --- a/src/Umbraco.ModelsBuilder/Configuration/Config.cs +++ b/src/Umbraco.ModelsBuilder/Configuration/ModelsBuilderConfig.cs @@ -4,29 +4,28 @@ using System.IO; using System.Web.Configuration; using System.Web.Hosting; using Umbraco.Core; +using Umbraco.Core.IO; namespace Umbraco.ModelsBuilder.Configuration { /// /// Represents the models builder configuration. /// - public class Config + public class ModelsBuilderConfig : IModelsBuilderConfig { - internal const string DefaultModelsNamespace = "Umbraco.Web.PublishedModels"; - internal const string DefaultModelsDirectory = "~/App_Data/Models"; + public const string DefaultModelsNamespace = "Umbraco.Web.PublishedModels"; + public const string DefaultModelsDirectory = "~/App_Data/Models"; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public Config() + public ModelsBuilderConfig() { const string prefix = "Umbraco.ModelsBuilder."; // ensure defaults are initialized for tests ModelsNamespace = DefaultModelsNamespace; - ModelsDirectory = HostingEnvironment.IsHosted - ? HostingEnvironment.MapPath(DefaultModelsDirectory) - : DefaultModelsDirectory.TrimStart("~/"); + ModelsDirectory = IOHelper.MapPath("~/"); DebugLevel = 0; // mode @@ -69,9 +68,7 @@ namespace Umbraco.ModelsBuilder.Configuration value = ConfigurationManager.AppSettings[prefix + "ModelsDirectory"]; if (!string.IsNullOrWhiteSpace(value)) { - var root = HostingEnvironment.IsHosted - ? HostingEnvironment.MapPath("~/") - : Directory.GetCurrentDirectory(); + var root = IOHelper.MapPath("~/"); if (root == null) throw new ConfigurationErrorsException("Could not determine root directory."); @@ -95,9 +92,9 @@ namespace Umbraco.ModelsBuilder.Configuration } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public Config( + public ModelsBuilderConfig( ModelsMode modelsMode = ModelsMode.Nothing, string modelsNamespace = null, bool enableFactory = true, @@ -159,7 +156,7 @@ namespace Umbraco.ModelsBuilder.Configuration { get { - var section = (CompilationSection) ConfigurationManager.GetSection("system.web/compilation"); + var section = (CompilationSection)ConfigurationManager.GetSection("system.web/compilation"); return section != null && section.Debug; } } diff --git a/src/Umbraco.ModelsBuilder/Configuration/ModelsMode.cs b/src/Umbraco.ModelsBuilder/Configuration/ModelsMode.cs index cc36099bc5..1f1d65f4f1 100644 --- a/src/Umbraco.ModelsBuilder/Configuration/ModelsMode.cs +++ b/src/Umbraco.ModelsBuilder/Configuration/ModelsMode.cs @@ -8,7 +8,7 @@ /// /// Do not generate models. /// - Nothing = 0, // default value + Nothing = 0, // default value //TODO: This doesn't make sense since we cannot actualy disable MB since Umbraco would die /// /// Generate models in memory. diff --git a/src/Umbraco.ModelsBuilder/Dashboard/BuilderDashboardHelper.cs b/src/Umbraco.ModelsBuilder/Dashboard/BuilderDashboardHelper.cs deleted file mode 100644 index ee9c384f9d..0000000000 --- a/src/Umbraco.ModelsBuilder/Dashboard/BuilderDashboardHelper.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System.Text; -using Umbraco.Core.Composing; -using Umbraco.ModelsBuilder.Configuration; -using Umbraco.ModelsBuilder.Umbraco; - -namespace Umbraco.ModelsBuilder.Dashboard -{ - internal static class BuilderDashboardHelper - { - private static Config Config => Current.Configs.ModelsBuilder(); - - public static bool CanGenerate() - { - return Config.ModelsMode.SupportsExplicitGeneration(); - } - - public static bool AreModelsOutOfDate() - { - return OutOfDateModelsStatus.IsOutOfDate; - } - - public static string LastError() - { - return ModelsGenerationError.GetLastError(); - } - - public static string Text() - { - var config = Config; - - var sb = new StringBuilder(); - - sb.Append("Version: "); - sb.Append(Api.ApiVersion.Current.Version); - sb.Append("
 
"); - - sb.Append("ModelsBuilder is enabled, with the following configuration:"); - - sb.Append("
    "); - - sb.Append("
  • The models factory is "); - sb.Append(config.EnableFactory || config.ModelsMode == ModelsMode.PureLive - ? "enabled" - : "not enabled. Umbraco will not use models"); - sb.Append(".
  • "); - - sb.Append(config.ModelsMode != ModelsMode.Nothing - ? $"
  • {config.ModelsMode} models are enabled.
  • " - : "
  • No models mode is specified: models will not be generated.
  • "); - - sb.Append($"
  • Models namespace is {config.ModelsNamespace}.
  • "); - - sb.Append("
  • Tracking of out-of-date models is "); - sb.Append(config.FlagOutOfDateModels ? "enabled" : "not enabled"); - sb.Append(".
  • "); - - sb.Append("
"); - - return sb.ToString(); - } - } -} diff --git a/src/Umbraco.ModelsBuilder/Umbraco/HashCombiner.cs b/src/Umbraco.ModelsBuilder/HashCombiner.cs similarity index 76% rename from src/Umbraco.ModelsBuilder/Umbraco/HashCombiner.cs rename to src/Umbraco.ModelsBuilder/HashCombiner.cs index e11662eb24..51e02e93c1 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/HashCombiner.cs +++ b/src/Umbraco.ModelsBuilder/HashCombiner.cs @@ -1,17 +1,17 @@ using System; using System.Globalization; -namespace Umbraco.ModelsBuilder.Umbraco +namespace Umbraco.ModelsBuilder { // because, of course, it's internal in Umbraco // see also System.Web.Util.HashCodeCombiner - class HashCombiner + internal class HashCombiner { private long _combinedHash = 5381L; public void Add(int i) { - _combinedHash = ((_combinedHash << 5) + _combinedHash) ^ i; + _combinedHash = (_combinedHash << 5) + _combinedHash ^ i; } public void Add(object o) @@ -27,7 +27,7 @@ namespace Umbraco.ModelsBuilder.Umbraco public void Add(string s) { if (s == null) return; - Add((StringComparer.InvariantCulture).GetHashCode(s)); + Add(StringComparer.InvariantCulture.GetHashCode(s)); } public string GetCombinedHashCode() diff --git a/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj b/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj index a16579e60d..6945cc4b31 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj +++ b/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj @@ -56,10 +56,11 @@ - + + - + @@ -67,13 +68,15 @@ - - + - - - - + + + + + + + @@ -92,10 +95,6 @@ - - {29aa69d9-b597-4395-8d42-43b1263c240a} - Umbraco.Abstractions - {31785BC3-256C-4613-B2F5-A1B0BDDED8C1} Umbraco.Core @@ -110,5 +109,8 @@ 5.2.7 + + + \ No newline at end of file diff --git a/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProvider.cs b/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProvider.cs index d8839a1297..146e837dd9 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProvider.cs +++ b/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProvider.cs @@ -2,43 +2,46 @@ using System.Threading; using System.Web; using System.Web.Hosting; -using Umbraco.Core.Composing; using Umbraco.Core.Exceptions; +using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.ModelsBuilder.Configuration; using Umbraco.ModelsBuilder.Umbraco; using Umbraco.Web.Cache; -// will install only if configuration says it needs to be installed -[assembly: PreApplicationStartMethod(typeof(LiveModelsProviderModule), "Install")] - namespace Umbraco.ModelsBuilder.Umbraco { // supports LiveDll and LiveAppData - but not PureLive public sealed class LiveModelsProvider { - private static UmbracoServices _umbracoServices; private static Mutex _mutex; private static int _req; - - private static Config Config => Current.Configs.ModelsBuilder(); + private readonly ILogger _logger; + private readonly IModelsBuilderConfig _config; + private readonly ModelsGenerator _modelGenerator; // we do not manage pure live here - internal static bool IsEnabled => Config.ModelsMode.IsLiveNotPure(); + internal bool IsEnabled => _config.ModelsMode.IsLiveNotPure(); - internal static void Install(UmbracoServices umbracoServices) + public LiveModelsProvider(ILogger logger, IModelsBuilderConfig config, ModelsGenerator modelGenerator) + { + _logger = logger; + _config = config ?? throw new ArgumentNullException(nameof(config)); + _modelGenerator = modelGenerator; + } + + internal void Install() { // just be sure if (!IsEnabled) return; - _umbracoServices = umbracoServices; - // initialize mutex // ApplicationId will look like "/LM/W3SVC/1/Root/AppName" // name is system-wide and must be less than 260 chars var name = HostingEnvironment.ApplicationID + "/UmbracoLiveModelsProvider"; - _mutex = new Mutex(false, name); + + _mutex = new Mutex(false, name); //TODO: Replace this with MainDom? Seems we now have 2x implementations of almost the same thing // anything changes, and we want to re-generate models. ContentTypeCacheRefresher.CacheUpdated += RequestModelsGeneration; @@ -57,14 +60,14 @@ namespace Umbraco.ModelsBuilder.Umbraco // need to be generated. Could be by another request. Anyway. We could // have collisions but... you know the risk. - private static void RequestModelsGeneration(object sender, EventArgs args) + private void RequestModelsGeneration(object sender, EventArgs args) { //HttpContext.Current.Items[this] = true; - Current.Logger.Debug("Requested to generate models."); + _logger.Debug("Requested to generate models."); Interlocked.Exchange(ref _req, 1); } - public static void GenerateModelsIfRequested(object sender, EventArgs args) + public void GenerateModelsIfRequested(object sender, EventArgs args) { //if (HttpContext.Current.Items[this] == null) return; if (Interlocked.Exchange(ref _req, 0) == 0) return; @@ -74,22 +77,22 @@ namespace Umbraco.ModelsBuilder.Umbraco try { - Current.Logger.Debug("Generate models..."); + _logger.Debug("Generate models..."); const int timeout = 2*60*1000; // 2 mins _mutex.WaitOne(timeout); // wait until it is safe, and acquire - Current.Logger.Info("Generate models now."); + _logger.Info("Generate models now."); GenerateModels(); - ModelsGenerationError.Clear(); - Current.Logger.Info("Generated."); + _modelGenerator.ClearErrors(); + _logger.Info("Generated."); } catch (TimeoutException) { - Current.Logger.Warn("Timeout, models were NOT generated."); + _logger.Warn("Timeout, models were NOT generated."); } catch (Exception e) { - ModelsGenerationError.Report("Failed to build Live models.", e); - Current.Logger.Error("Failed to generate models.", e); + _modelGenerator.ReportError("Failed to build Live models.", e); + _logger.Error("Failed to generate models.", e); } finally { @@ -97,38 +100,16 @@ namespace Umbraco.ModelsBuilder.Umbraco } } - private static void GenerateModels() + private void GenerateModels() { - var modelsDirectory = Config.ModelsDirectory; - var modelsNamespace = Config.ModelsNamespace; - - var bin = HostingEnvironment.MapPath("~/bin"); + var bin = IOHelper.MapPath("~/bin"); if (bin == null) throw new PanicException("Panic: bin is null."); // EnableDllModels will recycle the app domain - but this request will end properly - ModelsBuilderBackOfficeController.GenerateModels(_umbracoServices, modelsDirectory, modelsNamespace); - } - } - - // have to do this because it's the only way to subscribe to EndRequest, - // module is installed by assembly attribute at the top of this file - public class LiveModelsProviderModule : IHttpModule - { - public void Init(HttpApplication app) - { - app.EndRequest += LiveModelsProvider.GenerateModelsIfRequested; + _modelGenerator.GenerateModels(); } - public void Dispose() - { - // nothing - } - - public static void Install() - { - // always - don't read config in PreApplicationStartMethod - HttpApplication.RegisterModule(typeof(LiveModelsProviderModule)); - } + } } diff --git a/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProviderModule.cs b/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProviderModule.cs new file mode 100644 index 0000000000..1dadbd41b6 --- /dev/null +++ b/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProviderModule.cs @@ -0,0 +1,44 @@ +using System; +using System.Web; +using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.ModelsBuilder.Umbraco; + +// will install only if configuration says it needs to be installed +[assembly: PreApplicationStartMethod(typeof(LiveModelsProviderModule), "Install")] + +namespace Umbraco.ModelsBuilder.Umbraco +{ + // have to do this because it's the only way to subscribe to EndRequest, + // module is installed by assembly attribute at the top of this file + public class LiveModelsProviderModule : IHttpModule + { + private static LiveModelsProvider _liveModelsProvider; + + public void Init(HttpApplication app) + { + app.EndRequest += App_EndRequest; + } + + private void App_EndRequest(object sender, EventArgs e) + { + // here we're using "Current." since we're in a module, it is possible in a round about way to inject into a module but for now we'll just use Current + if (_liveModelsProvider == null) + _liveModelsProvider = Current.Factory.GetInstance(); + + if (_liveModelsProvider.IsEnabled) + _liveModelsProvider.GenerateModelsIfRequested(sender, e); + } + + public void Dispose() + { + // nothing + } + + public static void Install() + { + // always - don't read config in PreApplicationStartMethod + HttpApplication.RegisterModule(typeof(LiveModelsProviderModule)); + } + } +} diff --git a/src/Umbraco.ModelsBuilder/Umbraco/HashHelper.cs b/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderHasher.cs similarity index 97% rename from src/Umbraco.ModelsBuilder/Umbraco/HashHelper.cs rename to src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderHasher.cs index e4a0705ec0..3354b4040d 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/HashHelper.cs +++ b/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderHasher.cs @@ -4,7 +4,7 @@ using Umbraco.ModelsBuilder.Building; namespace Umbraco.ModelsBuilder.Umbraco { - class HashHelper + internal class ModelsBuilderHasher { public static string Hash(IEnumerable typeModels) { diff --git a/src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerationError.cs b/src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerationError.cs index a7b437df57..a7cb1e11c3 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerationError.cs +++ b/src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerationError.cs @@ -6,11 +6,16 @@ using Umbraco.ModelsBuilder.Configuration; namespace Umbraco.ModelsBuilder.Umbraco { - internal static class ModelsGenerationError + internal class ModelsGenerationError { - private static Config Config => Current.Configs.ModelsBuilder(); + private readonly IModelsBuilderConfig _config; - public static void Clear() + public ModelsGenerationError(IModelsBuilderConfig config) + { + _config = config; + } + + public void Clear() { var errFile = GetErrFile(); if (errFile == null) return; @@ -19,7 +24,7 @@ namespace Umbraco.ModelsBuilder.Umbraco File.Delete(errFile); } - public static void Report(string message, Exception e) + public void Report(string message, Exception e) { var errFile = GetErrFile(); if (errFile == null) return; @@ -35,7 +40,7 @@ namespace Umbraco.ModelsBuilder.Umbraco File.WriteAllText(errFile, sb.ToString()); } - public static string GetLastError() + public string GetLastError() { var errFile = GetErrFile(); if (errFile == null) return null; @@ -50,9 +55,9 @@ namespace Umbraco.ModelsBuilder.Umbraco } } - private static string GetErrFile() + private string GetErrFile() { - var modelsDirectory = Config.ModelsDirectory; + var modelsDirectory = _config.ModelsDirectory; if (!Directory.Exists(modelsDirectory)) return null; diff --git a/src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerator.cs b/src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerator.cs new file mode 100644 index 0000000000..4e3607c156 --- /dev/null +++ b/src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerator.cs @@ -0,0 +1,63 @@ +using System; +using System.IO; +using System.Text; +using System.Web; +using Umbraco.ModelsBuilder.Building; +using Umbraco.ModelsBuilder.Configuration; +using Umbraco.ModelsBuilder.Umbraco; + +namespace Umbraco.ModelsBuilder.Umbraco +{ + public class ModelsGenerator + { + private readonly UmbracoServices _umbracoService; + private readonly IModelsBuilderConfig _config; + private readonly ModelsGenerationError _errors; + + public ModelsGenerator(UmbracoServices umbracoService, IModelsBuilderConfig config) + { + _umbracoService = umbracoService; + _config = config; + _errors = new ModelsGenerationError(config); + } + + internal void GenerateModels() + { + if (!Directory.Exists(_config.ModelsDirectory)) + Directory.CreateDirectory(_config.ModelsDirectory); + + foreach (var file in Directory.GetFiles(_config.ModelsDirectory, "*.generated.cs")) + File.Delete(file); + + var typeModels = _umbracoService.GetAllTypes(); + + var builder = new TextBuilder(_config, typeModels); + + foreach (var typeModel in builder.GetModelsToGenerate()) + { + var sb = new StringBuilder(); + builder.Generate(sb, typeModel); + var filename = Path.Combine(_config.ModelsDirectory, typeModel.ClrName + ".generated.cs"); + File.WriteAllText(filename, sb.ToString()); + } + + // the idea was to calculate the current hash and to add it as an extra file to the compilation, + // in order to be able to detect whether a DLL is consistent with an environment - however the + // environment *might not* contain the local partial files, and thus it could be impossible to + // calculate the hash. So... maybe that's not a good idea after all? + /* + var currentHash = HashHelper.Hash(ourFiles, typeModels); + ourFiles["models.hash.cs"] = $@"using Umbraco.ModelsBuilder; +[assembly:ModelsBuilderAssembly(SourceHash = ""{currentHash}"")] +"; + */ + + OutOfDateModelsStatus.Clear(); + } + + internal void ClearErrors() => _errors.Clear(); + internal void ReportError(string message, Exception e) => _errors.Report(message, e); + internal string GetLastError() => _errors.GetLastError(); + + } +} diff --git a/src/Umbraco.ModelsBuilder/Umbraco/OutOfDateModelsStatus.cs b/src/Umbraco.ModelsBuilder/Umbraco/OutOfDateModelsStatus.cs index 142cc7578a..3326379871 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/OutOfDateModelsStatus.cs +++ b/src/Umbraco.ModelsBuilder/Umbraco/OutOfDateModelsStatus.cs @@ -7,7 +7,12 @@ namespace Umbraco.ModelsBuilder.Umbraco { public sealed class OutOfDateModelsStatus { - private static Config Config => Current.Configs.ModelsBuilder(); + public OutOfDateModelsStatus() + { + + } + + private static ModelsBuilderConfig Config => Current.Configs.ModelsBuilder(); internal static void Install() { diff --git a/src/Umbraco.ModelsBuilder/Umbraco/PureLiveModelFactory.cs b/src/Umbraco.ModelsBuilder/Umbraco/PureLiveModelFactory.cs index 05f748a588..0f888cc428 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/PureLiveModelFactory.cs +++ b/src/Umbraco.ModelsBuilder/Umbraco/PureLiveModelFactory.cs @@ -42,13 +42,15 @@ namespace Umbraco.ModelsBuilder.Umbraco private const string ProjVirt = "~/App_Data/Models/all.generated.cs"; private static readonly string[] OurFiles = { "models.hash", "models.generated.cs", "all.generated.cs", "all.dll.path", "models.err" }; - private readonly Config _config; + private readonly IModelsBuilderConfig _config; + private readonly ModelsGenerator _modelGenerator; - public PureLiveModelFactory(Lazy umbracoServices, IProfilingLogger logger, Config config) + public PureLiveModelFactory(Lazy umbracoServices, IProfilingLogger logger, IModelsBuilderConfig config, ModelsGenerator modelGenerator) { _umbracoServices = umbracoServices; _logger = logger; _config = config; + _modelGenerator = modelGenerator; _ver = 1; // zero is for when we had no version _skipver = -1; // nothing to skip @@ -292,7 +294,7 @@ namespace Umbraco.ModelsBuilder.Umbraco var types = assembly.ExportedTypes.Where(x => x.Inherits() || x.Inherits()); _infos = RegisterModels(types); - ModelsGenerationError.Clear(); + _modelGenerator.ClearErrors(); } catch (Exception e) { @@ -300,7 +302,7 @@ namespace Umbraco.ModelsBuilder.Umbraco { _logger.Error("Failed to build models.", e); _logger.Warn("Running without models."); // be explicit - ModelsGenerationError.Report("Failed to build PureLive models.", e); + _modelGenerator.ReportError("Failed to build PureLive models.", e); } finally { @@ -333,7 +335,7 @@ namespace Umbraco.ModelsBuilder.Umbraco Directory.CreateDirectory(modelsDirectory); var typeModels = UmbracoServices.GetAllTypes(); - var currentHash = HashHelper.Hash(typeModels); + var currentHash = ModelsBuilderHasher.Hash(typeModels); var modelsHashFile = Path.Combine(modelsDirectory, "models.hash"); var modelsSrcFile = Path.Combine(modelsDirectory, "models.generated.cs"); var projFile = Path.Combine(modelsDirectory, "all.generated.cs"); @@ -557,7 +559,7 @@ namespace Umbraco.ModelsBuilder.Umbraco foreach (var file in Directory.GetFiles(modelsDirectory, "*.generated.cs")) File.Delete(file); - var builder = new TextBuilder(typeModels, _config.ModelsNamespace); + var builder = new TextBuilder(_config, typeModels); var codeBuilder = new StringBuilder(); builder.Generate(codeBuilder, builder.GetModelsToGenerate()); diff --git a/src/Umbraco.ModelsBuilder/Umbraco/UmbracoServices.cs b/src/Umbraco.ModelsBuilder/Umbraco/UmbracoServices.cs index 32f0703bac..410349096a 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/UmbracoServices.cs +++ b/src/Umbraco.ModelsBuilder/Umbraco/UmbracoServices.cs @@ -28,8 +28,6 @@ namespace Umbraco.ModelsBuilder.Umbraco _publishedContentTypeFactory = publishedContentTypeFactory; } - private static Config Config => Current.Configs.ModelsBuilder(); - #region Services public IList GetAllTypes() diff --git a/src/Umbraco.ModelsBuilder/Validation/ContentTypeModelValidator.cs b/src/Umbraco.ModelsBuilder/Validation/ContentTypeModelValidator.cs index 4b38de0168..fdae56be0d 100644 --- a/src/Umbraco.ModelsBuilder/Validation/ContentTypeModelValidator.cs +++ b/src/Umbraco.ModelsBuilder/Validation/ContentTypeModelValidator.cs @@ -35,7 +35,7 @@ namespace Umbraco.ModelsBuilder.Validation where TModel: ContentTypeSave where TProperty: PropertyTypeBasic { - private static Config Config => Current.Configs.ModelsBuilder(); + private static ModelsBuilderConfig Config => Current.Configs.ModelsBuilder(); protected override IEnumerable Validate(TModel model) { diff --git a/src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs b/src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs new file mode 100644 index 0000000000..18be0a37a3 --- /dev/null +++ b/src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs @@ -0,0 +1,443 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.CodeAnalysis; +using Moq; +using NUnit.Framework; +using Umbraco.Core.Composing; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.ModelsBuilder.Api; +using Umbraco.ModelsBuilder.Building; +using Umbraco.ModelsBuilder.Configuration; + +namespace Umbraco.ModelsBuilder.Tests +{ + [TestFixture] + public class BuilderTests + { + [SetUp] + public void Setup() + { + Current.Reset(); + Current.UnlockConfigs(); + Current.Configs.Add(() => new ModelsBuilderConfig()); + } + + [Test] + public void GenerateSimpleType() + { + // Umbraco returns nice, pascal-cased names + + var type1 = new TypeModel + { + Id = 1, + Alias = "type1", + ClrName = "Type1", + ParentId = 0, + BaseType = null, + ItemType = TypeModel.ItemTypes.Content, + }; + type1.Properties.Add(new PropertyModel + { + Alias = "prop1", + ClrName = "Prop1", + ModelClrType = typeof(string), + }); + + var types = new[] { type1 }; + + var code = new Dictionary + { + }; + + var builder = new TextBuilder(Mock.Of(), types); + var btypes = builder.TypeModels; + + var sb = new StringBuilder(); + builder.Generate(sb, builder.GetModelsToGenerate().First()); + var gen = sb.ToString(); + + var version = ApiVersion.Current.Version; + var expected = @"//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Umbraco.ModelsBuilder v" + version + @" +// +// Changes to this file will be lost if the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Web; +using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Web; +using Umbraco.ModelsBuilder; +using Umbraco.ModelsBuilder.Umbraco; + +namespace Umbraco.Web.PublishedModels +{ + [PublishedModel(""type1"")] + public partial class Type1 : PublishedContentModel + { + // helpers +#pragma warning disable 0109 // new is redundant + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder"", """ + version + @""")] + public new const string ModelTypeAlias = ""type1""; + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder"", """ + version + @""")] + public new const PublishedItemType ModelItemType = PublishedItemType.Content; + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder"", """ + version + @""")] + public new static IPublishedContentType GetModelContentType() + => PublishedModelUtility.GetModelContentType(ModelItemType, ModelTypeAlias); + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder"", """ + version + @""")] + public static IPublishedPropertyType GetModelPropertyType(Expression> selector) + => PublishedModelUtility.GetModelPropertyType(GetModelContentType(), selector); +#pragma warning restore 0109 + + // ctor + public Type1(IPublishedContent content) + : base(content) + { } + + // properties + + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder"", """ + version + @""")] + [ImplementPropertyType(""prop1"")] + public string Prop1 => this.Value(""prop1""); + } +} +"; + Console.WriteLine(gen); + Assert.AreEqual(expected.ClearLf(), gen); + } + + [Test] + public void GenerateSimpleType_Ambiguous_Issue() + { + // Umbraco returns nice, pascal-cased names + + var type1 = new TypeModel + { + Id = 1, + Alias = "type1", + ClrName = "Type1", + ParentId = 0, + BaseType = null, + ItemType = TypeModel.ItemTypes.Content, + }; + type1.Properties.Add(new PropertyModel + { + Alias = "foo", + ClrName = "Foo", + ModelClrType = typeof(IEnumerable<>).MakeGenericType(ModelType.For("foo")), + }); + + var type2 = new TypeModel + { + Id = 2, + Alias = "foo", + ClrName = "Foo", + ParentId = 0, + BaseType = null, + ItemType = TypeModel.ItemTypes.Element, + }; + + var types = new[] { type1, type2 }; + + var code = new Dictionary + { + { "code", @" +namespace Umbraco.Web.PublishedModels +{ + public partial class Foo + { + } +} +" } + }; + + var builder = new TextBuilder(Mock.Of(), types); + var btypes = builder.TypeModels; + + builder.ModelsNamespace = "Umbraco.Web.PublishedModels"; + + var sb1 = new StringBuilder(); + builder.Generate(sb1, builder.GetModelsToGenerate().Skip(1).First()); + var gen1 = sb1.ToString(); + Console.WriteLine(gen1); + + var sb = new StringBuilder(); + builder.Generate(sb, builder.GetModelsToGenerate().First()); + var gen = sb.ToString(); + + var version = ApiVersion.Current.Version; + var expected = @"//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Umbraco.ModelsBuilder v" + version + @" +// +// Changes to this file will be lost if the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Web; +using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Web; +using Umbraco.ModelsBuilder; +using Umbraco.ModelsBuilder.Umbraco; + +namespace Umbraco.Web.PublishedModels +{ + [PublishedModel(""type1"")] + public partial class Type1 : PublishedContentModel + { + // helpers +#pragma warning disable 0109 // new is redundant + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder"", """ + version + @""")] + public new const string ModelTypeAlias = ""type1""; + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder"", """ + version + @""")] + public new const PublishedItemType ModelItemType = PublishedItemType.Content; + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder"", """ + version + @""")] + public new static IPublishedContentType GetModelContentType() + => PublishedModelUtility.GetModelContentType(ModelItemType, ModelTypeAlias); + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder"", """ + version + @""")] + public static IPublishedPropertyType GetModelPropertyType(Expression> selector) + => PublishedModelUtility.GetModelPropertyType(GetModelContentType(), selector); +#pragma warning restore 0109 + + // ctor + public Type1(IPublishedContent content) + : base(content) + { } + + // properties + + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder"", """ + version + @""")] + [ImplementPropertyType(""foo"")] + public global::System.Collections.Generic.IEnumerable Foo => this.Value>(""foo""); + } +} +"; + Console.WriteLine(gen); + Assert.AreEqual(expected.ClearLf(), gen); + } + + [Test] + public void GenerateAmbiguous() + { + // NOTE: since + + var type1 = new TypeModel + { + Id = 1, + Alias = "type1", + ClrName = "Type1", + ParentId = 0, + BaseType = null, + ItemType = TypeModel.ItemTypes.Content, + IsMixin = true, + }; + type1.Properties.Add(new PropertyModel + { + Alias = "prop1", + ClrName = "Prop1", + ModelClrType = typeof(IPublishedContent), + }); + type1.Properties.Add(new PropertyModel + { + Alias = "prop2", + ClrName = "Prop2", + ModelClrType = typeof(System.Text.StringBuilder), + }); + type1.Properties.Add(new PropertyModel + { + Alias = "prop3", + ClrName = "Prop3", + ModelClrType = typeof(global::Umbraco.Core.IO.FileSecurityException), + }); + var types = new[] { type1 }; + + var code = new Dictionary + { + }; + + var builder = new TextBuilder(Mock.Of(), types); + builder.ModelsNamespace = "Umbraco.ModelsBuilder.Models"; // forces conflict with Umbraco.ModelsBuilder.Umbraco + var btypes = builder.TypeModels; + + var sb = new StringBuilder(); + foreach (var model in builder.GetModelsToGenerate()) + builder.Generate(sb, model); + var gen = sb.ToString(); + + Console.WriteLine(gen); + + Assert.IsTrue(gen.Contains(" global::Umbraco.Core.Models.PublishedContent.IPublishedContent Prop1")); + Assert.IsTrue(gen.Contains(" global::System.Text.StringBuilder Prop2")); + Assert.IsTrue(gen.Contains(" global::Umbraco.Core.IO.FileSecurityException Prop3")); + } + + [TestCase("int", typeof(int))] + [TestCase("global::System.Collections.Generic.IEnumerable", typeof(IEnumerable))] + [TestCase("global::Umbraco.ModelsBuilder.Tests.BuilderTestsClass1", typeof(BuilderTestsClass1))] + [TestCase("global::Umbraco.ModelsBuilder.Tests.BuilderTests.Class1", typeof(Class1))] + public void WriteClrType(string expected, Type input) + { + // note - these assertions differ from the original tests in MB because in the embedded version, the result of Builder.IsAmbiguousSymbol is always true + // which means global:: syntax will be applied to most things + + var builder = new TextBuilder(); + builder.ModelsNamespaceForTests = "ModelsNamespace"; + var sb = new StringBuilder(); + builder.WriteClrType(sb, input); + Assert.AreEqual(expected, sb.ToString()); + } + + [TestCase("int", typeof(int))] + [TestCase("global::System.Collections.Generic.IEnumerable", typeof(IEnumerable))] + [TestCase("global::Umbraco.ModelsBuilder.Tests.BuilderTestsClass1", typeof(BuilderTestsClass1))] + [TestCase("global::Umbraco.ModelsBuilder.Tests.BuilderTests.Class1", typeof(Class1))] + public void WriteClrTypeUsing(string expected, Type input) + { + // note - these assertions differ from the original tests in MB because in the embedded version, the result of Builder.IsAmbiguousSymbol is always true + // which means global:: syntax will be applied to most things + + var builder = new TextBuilder(); + builder.Using.Add("Umbraco.ModelsBuilder.Tests"); + builder.ModelsNamespaceForTests = "ModelsNamespace"; + var sb = new StringBuilder(); + builder.WriteClrType(sb, input); + Assert.AreEqual(expected, sb.ToString()); + } + + [Test] + public void WriteClrType_WithUsing() + { + var builder = new TextBuilder(); + builder.Using.Add("System.Text"); + builder.ModelsNamespaceForTests = "Umbraco.ModelsBuilder.Tests.Models"; + var sb = new StringBuilder(); + builder.WriteClrType(sb, typeof(StringBuilder)); + + // note - these assertions differ from the original tests in MB because in the embedded version, the result of Builder.IsAmbiguousSymbol is always true + // which means global:: syntax will be applied to most things + + Assert.AreEqual("global::System.Text.StringBuilder", sb.ToString()); + } + + [Test] + public void WriteClrTypeAnother_WithoutUsing() + { + var builder = new TextBuilder(); + builder.ModelsNamespaceForTests = "Umbraco.ModelsBuilder.Tests.Models"; + var sb = new StringBuilder(); + builder.WriteClrType(sb, typeof(StringBuilder)); + Assert.AreEqual("global::System.Text.StringBuilder", sb.ToString()); + } + + [Test] + public void WriteClrType_Ambiguous1() + { + var builder = new TextBuilder(); + builder.Using.Add("System.Text"); + builder.Using.Add("Umbraco.ModelsBuilder.Tests"); + builder.ModelsNamespaceForTests = "SomeRandomNamespace"; + var sb = new StringBuilder(); + builder.WriteClrType(sb, typeof(System.Text.ASCIIEncoding)); + + // note - these assertions differ from the original tests in MB because in the embedded version, the result of Builder.IsAmbiguousSymbol is always true + // which means global:: syntax will be applied to most things + + Assert.AreEqual("global::System.Text.ASCIIEncoding", sb.ToString()); + } + + [Test] + public void WriteClrType_Ambiguous() + { + var builder = new TextBuilder(); + builder.Using.Add("System.Text"); + builder.Using.Add("Umbraco.ModelsBuilder.Tests"); + builder.ModelsNamespaceForTests = "SomeBorkedNamespace"; + var sb = new StringBuilder(); + builder.WriteClrType(sb, typeof(System.Text.ASCIIEncoding)); + + // note - these assertions differ from the original tests in MB because in the embedded version, the result of Builder.IsAmbiguousSymbol is always true + // which means global:: syntax will be applied to most things + + Assert.AreEqual("global::System.Text.ASCIIEncoding", sb.ToString()); + } + + [Test] + public void WriteClrType_Ambiguous2() + { + var builder = new TextBuilder(); + builder.Using.Add("System.Text"); + builder.Using.Add("Umbraco.ModelsBuilder.Tests"); + builder.ModelsNamespaceForTests = "SomeRandomNamespace"; + var sb = new StringBuilder(); + builder.WriteClrType(sb, typeof(ASCIIEncoding)); + + // note - these assertions differ from the original tests in MB because in the embedded version, the result of Builder.IsAmbiguousSymbol is always true + // which means global:: syntax will be applied to most things + + Assert.AreEqual("global::Umbraco.ModelsBuilder.Tests.ASCIIEncoding", sb.ToString()); + } + + [Test] + public void WriteClrType_AmbiguousNot() + { + var builder = new TextBuilder(); + builder.Using.Add("System.Text"); + builder.Using.Add("Umbraco.ModelsBuilder.Tests"); + builder.ModelsNamespaceForTests = "Umbraco.ModelsBuilder.Tests.Models"; + var sb = new StringBuilder(); + builder.WriteClrType(sb, typeof(ASCIIEncoding)); + + // note - these assertions differ from the original tests in MB because in the embedded version, the result of Builder.IsAmbiguousSymbol is always true + // which means global:: syntax will be applied to most things + + Assert.AreEqual("global::Umbraco.ModelsBuilder.Tests.ASCIIEncoding", sb.ToString()); + } + + [Test] + public void WriteClrType_AmbiguousWithNested() + { + var builder = new TextBuilder(); + builder.Using.Add("System.Text"); + builder.Using.Add("Umbraco.ModelsBuilder.Tests"); + builder.ModelsNamespaceForTests = "SomeRandomNamespace"; + var sb = new StringBuilder(); + builder.WriteClrType(sb, typeof(ASCIIEncoding.Nested)); + + // note - these assertions differ from the original tests in MB because in the embedded version, the result of Builder.IsAmbiguousSymbol is always true + // which means global:: syntax will be applied to most things + + Assert.AreEqual("global::Umbraco.ModelsBuilder.Tests.ASCIIEncoding.Nested", sb.ToString()); + } + + public class Class1 { } + } + + // make it public to be ambiguous (see above) + public class ASCIIEncoding + { + // can we handle nested types? + public class Nested { } + } + + class BuilderTestsClass1 {} +} + +namespace SomeBorkedNamespace +{ + public class System { } +} diff --git a/src/Umbraco.Tests/ModelsBuilder/ConfigTests.cs b/src/Umbraco.Tests/ModelsBuilder/ConfigTests.cs new file mode 100644 index 0000000000..58215707f7 --- /dev/null +++ b/src/Umbraco.Tests/ModelsBuilder/ConfigTests.cs @@ -0,0 +1,49 @@ +using System.Configuration; +using NUnit.Framework; +using Umbraco.ModelsBuilder.Configuration; + +namespace Umbraco.ModelsBuilder.Tests +{ + [TestFixture] + public class ModelsBuilderConfigTests + { + [Test] + public void Test1() + { + var config = new ModelsBuilderConfig(modelsNamespace: "test1"); + Assert.AreEqual("test1", config.ModelsNamespace); + } + + [Test] + public void Test2() + { + var config = new ModelsBuilderConfig(modelsNamespace: "test2"); + Assert.AreEqual("test2", config.ModelsNamespace); + } + + [Test] + public void DefaultModelsNamespace() + { + var config = new ModelsBuilderConfig(); + Assert.AreEqual(ModelsBuilderConfig.DefaultModelsNamespace, config.ModelsNamespace); + } + + [TestCase("c:/path/to/root", "~/dir/models", false, "c:\\path\\to\\root\\dir\\models")] + [TestCase("c:/path/to/root", "~/../../dir/models", true, "c:\\path\\dir\\models")] + [TestCase("c:/path/to/root", "c:/another/path/to/elsewhere", true, "c:\\another\\path\\to\\elsewhere")] + public void GetModelsDirectoryTests(string root, string config, bool acceptUnsafe, string expected) + { + Assert.AreEqual(expected, ModelsBuilderConfig.GetModelsDirectory(root, config, acceptUnsafe)); + } + + [TestCase("c:/path/to/root", "~/../../dir/models", false)] + [TestCase("c:/path/to/root", "c:/another/path/to/elsewhere", false)] + public void GetModelsDirectoryThrowsTests(string root, string config, bool acceptUnsafe) + { + Assert.Throws(() => + { + var modelsDirectory = ModelsBuilderConfig.GetModelsDirectory(root, config, acceptUnsafe); + }); + } + } +} diff --git a/src/Umbraco.Tests/ModelsBuilder/StringExtensions.cs b/src/Umbraco.Tests/ModelsBuilder/StringExtensions.cs new file mode 100644 index 0000000000..13a256aa14 --- /dev/null +++ b/src/Umbraco.Tests/ModelsBuilder/StringExtensions.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Umbraco.ModelsBuilder.Tests +{ + public static class StringExtensions + { + public static string ClearLf(this string s) + { + return s.Replace("\r", ""); + } + } +} diff --git a/src/Umbraco.Tests/ModelsBuilder/UmbracoApplicationTests.cs b/src/Umbraco.Tests/ModelsBuilder/UmbracoApplicationTests.cs new file mode 100644 index 0000000000..12fa777e69 --- /dev/null +++ b/src/Umbraco.Tests/ModelsBuilder/UmbracoApplicationTests.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using NUnit.Framework; +using Umbraco.ModelsBuilder.Building; +using Umbraco.ModelsBuilder.Umbraco; + +namespace Umbraco.ModelsBuilder.Tests +{ + [TestFixture] + public class UmbracoApplicationTests + { + //[Test] + //public void Test() + //{ + // // start and terminate + // using (var app = Application.GetApplication(TestOptions.ConnectionString, TestOptions.DatabaseProvider)) + // { } + + // // start and terminate + // using (var app = Application.GetApplication(TestOptions.ConnectionString, TestOptions.DatabaseProvider)) + // { } + + // // start, use and terminate + // using (var app = Application.GetApplication(TestOptions.ConnectionString, TestOptions.DatabaseProvider)) + // { + // var types = app.GetContentTypes(); + // } + //} + + [Test] + public void ThrowsOnDuplicateAliases() + { + var typeModels = new List + { + new TypeModel { ItemType = TypeModel.ItemTypes.Content, Alias = "content1" }, + new TypeModel { ItemType = TypeModel.ItemTypes.Content, Alias = "content2" }, + new TypeModel { ItemType = TypeModel.ItemTypes.Media, Alias = "media1" }, + new TypeModel { ItemType = TypeModel.ItemTypes.Media, Alias = "media2" }, + new TypeModel { ItemType = TypeModel.ItemTypes.Member, Alias = "member1" }, + new TypeModel { ItemType = TypeModel.ItemTypes.Member, Alias = "member2" }, + }; + + Assert.AreEqual(6, UmbracoServices.EnsureDistinctAliases(typeModels).Count); + + typeModels.Add(new TypeModel { ItemType = TypeModel.ItemTypes.Media, Alias = "content1" }); + + try + { + UmbracoServices.EnsureDistinctAliases(typeModels); + } + catch (NotSupportedException e) + { + Console.WriteLine(e.Message); + return; + } + + Assert.Fail("Expected NotSupportedException."); + } + } +} diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 39826fcc38..ebf081160b 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -134,6 +134,10 @@ + + + + @@ -549,6 +553,10 @@ {31785BC3-256C-4613-B2F5-A1B0BDDED8C1} Umbraco.Core + + {52ac0ba8-a60e-4e36-897b-e8b97a54ed1c} + Umbraco.ModelsBuilder + {651E1350-91B6-44B7-BD60-7207006D7003} Umbraco.Web From 6368a0a8d61181bc589c1c8bee0c8bb470ba15d8 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 28 Oct 2019 19:08:42 +1100 Subject: [PATCH 088/548] Fixes circular ref, last static gone, slight perf improvement in module --- .../BackOffice/DashboardReport.cs | 12 ++++---- .../ModelsBuilderBackOfficeController.cs | 16 +++++++---- .../Compose/ModelsBuilderComponent.cs | 8 +++--- .../Compose/ModelsBuilderComposer.cs | 2 ++ .../Compose/ModelsBuilderInitializer.cs | 2 +- .../ConfigsExtensions.cs | 6 ++-- .../Configuration/ModelsBuilderConfig.cs | 2 +- .../Umbraco/LiveModelsProvider.cs | 8 ++++-- .../Umbraco/LiveModelsProviderModule.cs | 7 +++-- .../Umbraco/ModelsGenerationError.cs | 2 +- .../Umbraco/ModelsGenerator.cs | 13 +++------ .../Umbraco/OutOfDateModelsStatus.cs | 28 +++++++++---------- .../Umbraco/PublishedModelUtility.cs | 8 ------ .../Umbraco/PureLiveModelFactory.cs | 13 +++++---- .../Validation/ContentTypeModelValidator.cs | 2 -- 15 files changed, 64 insertions(+), 65 deletions(-) diff --git a/src/Umbraco.ModelsBuilder/BackOffice/DashboardReport.cs b/src/Umbraco.ModelsBuilder/BackOffice/DashboardReport.cs index 57afeaf069..85f7030eff 100644 --- a/src/Umbraco.ModelsBuilder/BackOffice/DashboardReport.cs +++ b/src/Umbraco.ModelsBuilder/BackOffice/DashboardReport.cs @@ -8,19 +8,21 @@ namespace Umbraco.ModelsBuilder.BackOffice internal class DashboardReport { private readonly IModelsBuilderConfig _config; - private readonly ModelsGenerator _modelsGenerator; + private readonly OutOfDateModelsStatus _outOfDateModels; + private readonly ModelsGenerationError _mbErrors; - public DashboardReport(IModelsBuilderConfig config, ModelsGenerator modelsGenerator) + public DashboardReport(IModelsBuilderConfig config, OutOfDateModelsStatus outOfDateModels, ModelsGenerationError mbErrors) { _config = config; - _modelsGenerator = modelsGenerator; + _outOfDateModels = outOfDateModels; + _mbErrors = mbErrors; } public bool CanGenerate() => _config.ModelsMode.SupportsExplicitGeneration(); - public bool AreModelsOutOfDate() => OutOfDateModelsStatus.IsOutOfDate; + public bool AreModelsOutOfDate() => _outOfDateModels.IsOutOfDate; - public string LastError() => _modelsGenerator.GetLastError(); + public string LastError() => _mbErrors.GetLastError(); public string Text() { diff --git a/src/Umbraco.ModelsBuilder/BackOffice/ModelsBuilderBackOfficeController.cs b/src/Umbraco.ModelsBuilder/BackOffice/ModelsBuilderBackOfficeController.cs index 561da0a3d6..6092c9678b 100644 --- a/src/Umbraco.ModelsBuilder/BackOffice/ModelsBuilderBackOfficeController.cs +++ b/src/Umbraco.ModelsBuilder/BackOffice/ModelsBuilderBackOfficeController.cs @@ -28,14 +28,18 @@ namespace Umbraco.ModelsBuilder.BackOffice { private readonly IModelsBuilderConfig _config; private readonly ModelsGenerator _modelGenerator; + private readonly OutOfDateModelsStatus _outOfDateModels; + private readonly ModelsGenerationError _mbErrors; private readonly DashboardReport _dashboardReport; - public ModelsBuilderBackOfficeController(IModelsBuilderConfig config, ModelsGenerator modelsGenerator) + public ModelsBuilderBackOfficeController(IModelsBuilderConfig config, ModelsGenerator modelsGenerator, OutOfDateModelsStatus outOfDateModels, ModelsGenerationError mbErrors) { //_umbracoServices = umbracoServices; _config = config; _modelGenerator = modelsGenerator; - _dashboardReport = new DashboardReport(config, modelsGenerator); + _outOfDateModels = outOfDateModels; + _mbErrors = mbErrors; + _dashboardReport = new DashboardReport(config, outOfDateModels, mbErrors); } // invoked by the dashboard @@ -60,11 +64,11 @@ namespace Umbraco.ModelsBuilder.BackOffice // EnableDllModels will recycle the app domain - but this request will end properly _modelGenerator.GenerateModels(); - _modelGenerator.ClearErrors(); + _mbErrors.Clear(); } catch (Exception e) { - _modelGenerator.ReportError("Failed to build models.", e); + _mbErrors.Report("Failed to build models.", e); } return Request.CreateResponse(HttpStatusCode.OK, GetDashboardResult(), Configuration.Formatters.JsonFormatter); @@ -75,8 +79,8 @@ namespace Umbraco.ModelsBuilder.BackOffice [System.Web.Http.HttpGet] // use the http one, not mvc, with api controllers! public HttpResponseMessage GetModelsOutOfDateStatus() { - var status = OutOfDateModelsStatus.IsEnabled - ? OutOfDateModelsStatus.IsOutOfDate + var status = _outOfDateModels.IsEnabled + ? _outOfDateModels.IsOutOfDate ? new OutOfDateStatus { Status = OutOfDateType.OutOfDate } : new OutOfDateStatus { Status = OutOfDateType.Current } : new OutOfDateStatus { Status = OutOfDateType.Unknown }; diff --git a/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComponent.cs b/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComponent.cs index 6a9b8e1115..6deeda40a6 100644 --- a/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComponent.cs +++ b/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComponent.cs @@ -22,11 +22,13 @@ namespace Umbraco.ModelsBuilder.Compose private readonly IModelsBuilderConfig _config; private readonly LiveModelsProvider _liveModelsProvider; + private readonly OutOfDateModelsStatus _outOfDateModels; - public ModelsBuilderComponent(IModelsBuilderConfig config, LiveModelsProvider liveModelsProvider) + public ModelsBuilderComponent(IModelsBuilderConfig config, LiveModelsProvider liveModelsProvider, OutOfDateModelsStatus outOfDateModels) { _config = config; _liveModelsProvider = liveModelsProvider; + _outOfDateModels = outOfDateModels; } public void Initialize() @@ -39,13 +41,11 @@ namespace Umbraco.ModelsBuilder.Compose FileService.SavingTemplate += FileService_SavingTemplate; - // fixme LiveModelsProvider should not be static if (_config.ModelsMode.IsLiveNotPure()) _liveModelsProvider.Install(); - // fixme OutOfDateModelsStatus should not be static if (_config.FlagOutOfDateModels) - OutOfDateModelsStatus.Install(); + _outOfDateModels.Install(); } public void Terminate() diff --git a/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComposer.cs b/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComposer.cs index 71250b2eb6..f2b09903f3 100644 --- a/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComposer.cs +++ b/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComposer.cs @@ -20,6 +20,8 @@ namespace Umbraco.ModelsBuilder.Compose composition.Configs.Add(() => new ModelsBuilderConfig()); composition.RegisterUnique(); composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); if (composition.Configs.ModelsBuilder().ModelsMode == ModelsMode.PureLive) ComposeForLiveModels(composition); diff --git a/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderInitializer.cs b/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderInitializer.cs index 63f2336ebf..6eb8bbb328 100644 --- a/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderInitializer.cs +++ b/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderInitializer.cs @@ -6,7 +6,7 @@ using Umbraco.ModelsBuilder.Compose; namespace Umbraco.ModelsBuilder.Compose { - internal static class ModelsBuilderInitializer + public static class ModelsBuilderInitializer { public static void Initialize() { diff --git a/src/Umbraco.ModelsBuilder/ConfigsExtensions.cs b/src/Umbraco.ModelsBuilder/ConfigsExtensions.cs index c989be5aca..09314bc3f3 100644 --- a/src/Umbraco.ModelsBuilder/ConfigsExtensions.cs +++ b/src/Umbraco.ModelsBuilder/ConfigsExtensions.cs @@ -14,7 +14,7 @@ namespace Umbraco.ModelsBuilder /// Getting the models builder configuration freezes its state, /// and any attempt at modifying the configuration using the Setup method /// will be ignored. - public static ModelsBuilderConfig ModelsBuilder(this Configs configs) - => configs.GetConfig(); + public static IModelsBuilderConfig ModelsBuilder(this Configs configs) + => configs.GetConfig(); } -} \ No newline at end of file +} diff --git a/src/Umbraco.ModelsBuilder/Configuration/ModelsBuilderConfig.cs b/src/Umbraco.ModelsBuilder/Configuration/ModelsBuilderConfig.cs index caff5001a4..48a4423054 100644 --- a/src/Umbraco.ModelsBuilder/Configuration/ModelsBuilderConfig.cs +++ b/src/Umbraco.ModelsBuilder/Configuration/ModelsBuilderConfig.cs @@ -25,7 +25,7 @@ namespace Umbraco.ModelsBuilder.Configuration // ensure defaults are initialized for tests ModelsNamespace = DefaultModelsNamespace; - ModelsDirectory = IOHelper.MapPath("~/"); + ModelsDirectory = IOHelper.MapPath(DefaultModelsDirectory); DebugLevel = 0; // mode diff --git a/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProvider.cs b/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProvider.cs index 146e837dd9..19a725f85a 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProvider.cs +++ b/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProvider.cs @@ -19,15 +19,17 @@ namespace Umbraco.ModelsBuilder.Umbraco private readonly ILogger _logger; private readonly IModelsBuilderConfig _config; private readonly ModelsGenerator _modelGenerator; + private readonly ModelsGenerationError _mbErrors; // we do not manage pure live here internal bool IsEnabled => _config.ModelsMode.IsLiveNotPure(); - public LiveModelsProvider(ILogger logger, IModelsBuilderConfig config, ModelsGenerator modelGenerator) + public LiveModelsProvider(ILogger logger, IModelsBuilderConfig config, ModelsGenerator modelGenerator, ModelsGenerationError mbErrors) { _logger = logger; _config = config ?? throw new ArgumentNullException(nameof(config)); _modelGenerator = modelGenerator; + _mbErrors = mbErrors; } internal void Install() @@ -82,7 +84,7 @@ namespace Umbraco.ModelsBuilder.Umbraco _mutex.WaitOne(timeout); // wait until it is safe, and acquire _logger.Info("Generate models now."); GenerateModels(); - _modelGenerator.ClearErrors(); + _mbErrors.Clear(); _logger.Info("Generated."); } catch (TimeoutException) @@ -91,7 +93,7 @@ namespace Umbraco.ModelsBuilder.Umbraco } catch (Exception e) { - _modelGenerator.ReportError("Failed to build Live models.", e); + _mbErrors.Report("Failed to build Live models.", e); _logger.Error("Failed to generate models.", e); } finally diff --git a/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProviderModule.cs b/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProviderModule.cs index 1dadbd41b6..aae01ea75c 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProviderModule.cs +++ b/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProviderModule.cs @@ -22,11 +22,14 @@ namespace Umbraco.ModelsBuilder.Umbraco private void App_EndRequest(object sender, EventArgs e) { + if (((HttpApplication)sender).Request.Url.IsClientSideRequest()) + return; + // here we're using "Current." since we're in a module, it is possible in a round about way to inject into a module but for now we'll just use Current if (_liveModelsProvider == null) - _liveModelsProvider = Current.Factory.GetInstance(); + _liveModelsProvider = Current.Factory.TryGetInstance(); // will be null in upgrade mode - if (_liveModelsProvider.IsEnabled) + if (_liveModelsProvider?.IsEnabled ?? false) _liveModelsProvider.GenerateModelsIfRequested(sender, e); } diff --git a/src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerationError.cs b/src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerationError.cs index a7cb1e11c3..3f96d2ccdf 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerationError.cs +++ b/src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerationError.cs @@ -6,7 +6,7 @@ using Umbraco.ModelsBuilder.Configuration; namespace Umbraco.ModelsBuilder.Umbraco { - internal class ModelsGenerationError + public sealed class ModelsGenerationError { private readonly IModelsBuilderConfig _config; diff --git a/src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerator.cs b/src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerator.cs index 4e3607c156..5ef86b50ca 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerator.cs +++ b/src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerator.cs @@ -12,13 +12,13 @@ namespace Umbraco.ModelsBuilder.Umbraco { private readonly UmbracoServices _umbracoService; private readonly IModelsBuilderConfig _config; - private readonly ModelsGenerationError _errors; + private readonly OutOfDateModelsStatus _outOfDateModels; - public ModelsGenerator(UmbracoServices umbracoService, IModelsBuilderConfig config) + public ModelsGenerator(UmbracoServices umbracoService, IModelsBuilderConfig config, OutOfDateModelsStatus outOfDateModels) { _umbracoService = umbracoService; _config = config; - _errors = new ModelsGenerationError(config); + _outOfDateModels = outOfDateModels; } internal void GenerateModels() @@ -52,12 +52,7 @@ namespace Umbraco.ModelsBuilder.Umbraco "; */ - OutOfDateModelsStatus.Clear(); + _outOfDateModels.Clear(); } - - internal void ClearErrors() => _errors.Clear(); - internal void ReportError(string message, Exception e) => _errors.Report(message, e); - internal string GetLastError() => _errors.GetLastError(); - } } diff --git a/src/Umbraco.ModelsBuilder/Umbraco/OutOfDateModelsStatus.cs b/src/Umbraco.ModelsBuilder/Umbraco/OutOfDateModelsStatus.cs index 3326379871..a72ccd2fa2 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/OutOfDateModelsStatus.cs +++ b/src/Umbraco.ModelsBuilder/Umbraco/OutOfDateModelsStatus.cs @@ -7,53 +7,53 @@ namespace Umbraco.ModelsBuilder.Umbraco { public sealed class OutOfDateModelsStatus { - public OutOfDateModelsStatus() - { + private readonly IModelsBuilderConfig _config; + public OutOfDateModelsStatus(IModelsBuilderConfig config) + { + _config = config; } - private static ModelsBuilderConfig Config => Current.Configs.ModelsBuilder(); - - internal static void Install() + internal void Install() { // just be sure - if (Config.FlagOutOfDateModels == false) + if (_config.FlagOutOfDateModels == false) return; ContentTypeCacheRefresher.CacheUpdated += (sender, args) => Write(); DataTypeCacheRefresher.CacheUpdated += (sender, args) => Write(); } - private static string GetFlagPath() + private string GetFlagPath() { - var modelsDirectory = Config.ModelsDirectory; + var modelsDirectory = _config.ModelsDirectory; if (!Directory.Exists(modelsDirectory)) Directory.CreateDirectory(modelsDirectory); return Path.Combine(modelsDirectory, "ood.flag"); } - private static void Write() + private void Write() { var path = GetFlagPath(); if (path == null || File.Exists(path)) return; File.WriteAllText(path, "THIS FILE INDICATES THAT MODELS ARE OUT-OF-DATE\n\n"); } - public static void Clear() + public void Clear() { - if (Config.FlagOutOfDateModels == false) return; + if (_config.FlagOutOfDateModels == false) return; var path = GetFlagPath(); if (path == null || !File.Exists(path)) return; File.Delete(path); } - public static bool IsEnabled => Config.FlagOutOfDateModels; + public bool IsEnabled => _config.FlagOutOfDateModels; - public static bool IsOutOfDate + public bool IsOutOfDate { get { - if (Config.FlagOutOfDateModels == false) return false; + if (_config.FlagOutOfDateModels == false) return false; var path = GetFlagPath(); return path != null && File.Exists(path); } diff --git a/src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs b/src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs index 44b7aae0c0..2a8022bef8 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs +++ b/src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs @@ -41,14 +41,6 @@ namespace Umbraco.ModelsBuilder.Umbraco } } - [Obsolete("Use the overload taking IPublishedContentType instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public static IPublishedPropertyType GetModelPropertyType(PublishedContentType contentType, - Expression> selector) - { - return GetModelPropertyType((IPublishedContentType)contentType, selector); - } - public static IPublishedPropertyType GetModelPropertyType(IPublishedContentType contentType, Expression> selector) //where TModel : PublishedContentModel // fixme PublishedContentModel _or_ PublishedElementModel { diff --git a/src/Umbraco.ModelsBuilder/Umbraco/PureLiveModelFactory.cs b/src/Umbraco.ModelsBuilder/Umbraco/PureLiveModelFactory.cs index 0f888cc428..464ce92ef5 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/PureLiveModelFactory.cs +++ b/src/Umbraco.ModelsBuilder/Umbraco/PureLiveModelFactory.cs @@ -35,7 +35,7 @@ namespace Umbraco.ModelsBuilder.Umbraco private int _ver, _skipver; private readonly int _debugLevel; private BuildManager _theBuildManager; - private readonly Lazy _umbracoServices; + private readonly Lazy _umbracoServices; // fixme: this is because of circular refs :( private UmbracoServices UmbracoServices => _umbracoServices.Value; private static readonly Regex AssemblyVersionRegex = new Regex("AssemblyVersion\\(\"[0-9]+.[0-9]+.[0-9]+.[0-9]+\"\\)", RegexOptions.Compiled); @@ -43,14 +43,14 @@ namespace Umbraco.ModelsBuilder.Umbraco private static readonly string[] OurFiles = { "models.hash", "models.generated.cs", "all.generated.cs", "all.dll.path", "models.err" }; private readonly IModelsBuilderConfig _config; - private readonly ModelsGenerator _modelGenerator; + private readonly ModelsGenerationError _errors; - public PureLiveModelFactory(Lazy umbracoServices, IProfilingLogger logger, IModelsBuilderConfig config, ModelsGenerator modelGenerator) + public PureLiveModelFactory(Lazy umbracoServices, IProfilingLogger logger, IModelsBuilderConfig config) { _umbracoServices = umbracoServices; _logger = logger; _config = config; - _modelGenerator = modelGenerator; + _errors = new ModelsGenerationError(config); _ver = 1; // zero is for when we had no version _skipver = -1; // nothing to skip @@ -294,7 +294,7 @@ namespace Umbraco.ModelsBuilder.Umbraco var types = assembly.ExportedTypes.Where(x => x.Inherits() || x.Inherits()); _infos = RegisterModels(types); - _modelGenerator.ClearErrors(); + _errors.Clear(); } catch (Exception e) { @@ -302,7 +302,7 @@ namespace Umbraco.ModelsBuilder.Umbraco { _logger.Error("Failed to build models.", e); _logger.Warn("Running without models."); // be explicit - _modelGenerator.ReportError("Failed to build PureLive models.", e); + _errors.Report("Failed to build PureLive models.", e); } finally { @@ -536,6 +536,7 @@ namespace Umbraco.ModelsBuilder.Umbraco throw new InvalidOperationException($"Both types {type.FullName} and {modelInfo.ModelType.FullName} want to be a model type for content type with alias \"{typeName}\"."); // fixme use Core's ReflectionUtilities.EmitCtor !! + // Yes .. DynamicMethod is uber slow var meth = new DynamicMethod(string.Empty, typeof (IPublishedElement), ctorArgTypes, type.Module, true); var gen = meth.GetILGenerator(); gen.Emit(OpCodes.Ldarg_0); diff --git a/src/Umbraco.ModelsBuilder/Validation/ContentTypeModelValidator.cs b/src/Umbraco.ModelsBuilder/Validation/ContentTypeModelValidator.cs index fdae56be0d..d22d146553 100644 --- a/src/Umbraco.ModelsBuilder/Validation/ContentTypeModelValidator.cs +++ b/src/Umbraco.ModelsBuilder/Validation/ContentTypeModelValidator.cs @@ -35,8 +35,6 @@ namespace Umbraco.ModelsBuilder.Validation where TModel: ContentTypeSave where TProperty: PropertyTypeBasic { - private static ModelsBuilderConfig Config => Current.Configs.ModelsBuilder(); - protected override IEnumerable Validate(TModel model) { var properties = model.Groups.SelectMany(x => x.Properties) From 51f89410cebf8f52a9dfcf4bde928b511bf4cb5d Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 28 Oct 2019 19:35:18 +1100 Subject: [PATCH 089/548] notes --- src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj | 4 +--- src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs | 6 ++++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj b/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj index 6945cc4b31..027ed6665c 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj +++ b/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj @@ -109,8 +109,6 @@ 5.2.7 - - - + \ No newline at end of file diff --git a/src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs b/src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs index 2a8022bef8..1db15fa9b1 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs +++ b/src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs @@ -7,6 +7,12 @@ using Umbraco.Core.Models.PublishedContent; namespace Umbraco.ModelsBuilder.Umbraco { + /// + /// This is called from within the generated model classes + /// + /// + /// DO NOT REMOVE - although there are not code references this is used directly by the generated models. + /// public static class PublishedModelUtility { // looks safer but probably useless... ppl should not call these methods directly From adbd2f0ccaa992abb41d6ceca7726eac9eb5f154 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 28 Oct 2019 21:53:32 +1100 Subject: [PATCH 090/548] Changes namespaces, removes legacy PureLiveAssemblyAttribute --- .../{Api => }/ApiVersion.cs | 2 +- .../ContentTypeModelValidator.cs | 16 +++++------- .../BackOffice/DashboardReport.cs | 3 +-- .../ModelsBuilderBackOfficeController.cs | 1 - src/Umbraco.ModelsBuilder/Building/Builder.cs | 3 +-- .../{Umbraco => Building}/ModelsGenerator.cs | 4 +-- .../Building/TextBuilder.cs | 1 - .../Building/TextHeaderWriter.cs | 1 - .../TypeModelHasher.cs} | 5 ++-- .../Compose/ModelsBuilderComponent.cs | 1 - .../Compose/ModelsBuilderComposer.cs | 2 +- .../{Umbraco => }/LiveModelsProvider.cs | 10 ++++---- .../{Umbraco => }/LiveModelsProviderModule.cs | 4 +-- .../{Umbraco => }/ModelsGenerationError.cs | 2 +- .../{Umbraco => }/OutOfDateModelsStatus.cs | 2 +- .../{Umbraco => }/PublishedModelUtility.cs | 6 ++--- .../PureLiveAssemblyAttribute.cs | 15 ----------- .../{Umbraco => }/PureLiveModelFactory.cs | 25 ++++++++----------- .../Umbraco.ModelsBuilder.csproj | 23 ++++++++--------- .../{Umbraco => }/UmbracoServices.cs | 6 ++--- .../ModelsBuilder/BuilderTests.cs | 3 --- .../ModelsBuilder/UmbracoApplicationTests.cs | 1 - 22 files changed, 49 insertions(+), 87 deletions(-) rename src/Umbraco.ModelsBuilder/{Api => }/ApiVersion.cs (97%) rename src/Umbraco.ModelsBuilder/{Validation => BackOffice}/ContentTypeModelValidator.cs (90%) rename src/Umbraco.ModelsBuilder/{Umbraco => Building}/ModelsGenerator.cs (94%) rename src/Umbraco.ModelsBuilder/{Umbraco/ModelsBuilderHasher.cs => Building/TypeModelHasher.cs} (92%) rename src/Umbraco.ModelsBuilder/{Umbraco => }/LiveModelsProvider.cs (95%) rename src/Umbraco.ModelsBuilder/{Umbraco => }/LiveModelsProviderModule.cs (95%) rename src/Umbraco.ModelsBuilder/{Umbraco => }/ModelsGenerationError.cs (97%) rename src/Umbraco.ModelsBuilder/{Umbraco => }/OutOfDateModelsStatus.cs (97%) rename src/Umbraco.ModelsBuilder/{Umbraco => }/PublishedModelUtility.cs (92%) delete mode 100644 src/Umbraco.ModelsBuilder/PureLiveAssemblyAttribute.cs rename src/Umbraco.ModelsBuilder/{Umbraco => }/PureLiveModelFactory.cs (96%) rename src/Umbraco.ModelsBuilder/{Umbraco => }/UmbracoServices.cs (98%) diff --git a/src/Umbraco.ModelsBuilder/Api/ApiVersion.cs b/src/Umbraco.ModelsBuilder/ApiVersion.cs similarity index 97% rename from src/Umbraco.ModelsBuilder/Api/ApiVersion.cs rename to src/Umbraco.ModelsBuilder/ApiVersion.cs index a7f2db397e..67d0ec5c4c 100644 --- a/src/Umbraco.ModelsBuilder/Api/ApiVersion.cs +++ b/src/Umbraco.ModelsBuilder/ApiVersion.cs @@ -2,7 +2,7 @@ using System.Reflection; using Semver; -namespace Umbraco.ModelsBuilder.Api +namespace Umbraco.ModelsBuilder { /// /// Manages API version handshake between client and server. diff --git a/src/Umbraco.ModelsBuilder/Validation/ContentTypeModelValidator.cs b/src/Umbraco.ModelsBuilder/BackOffice/ContentTypeModelValidator.cs similarity index 90% rename from src/Umbraco.ModelsBuilder/Validation/ContentTypeModelValidator.cs rename to src/Umbraco.ModelsBuilder/BackOffice/ContentTypeModelValidator.cs index d22d146553..1971c62c9f 100644 --- a/src/Umbraco.ModelsBuilder/Validation/ContentTypeModelValidator.cs +++ b/src/Umbraco.ModelsBuilder/BackOffice/ContentTypeModelValidator.cs @@ -8,12 +8,12 @@ using Umbraco.ModelsBuilder.Configuration; using Umbraco.Web.Editors; using Umbraco.Web.Models.ContentEditing; -namespace Umbraco.ModelsBuilder.Validation +namespace Umbraco.ModelsBuilder.BackOffice { /// - /// Used to validate the aliases for the content type when MB is enabled to ensure that - /// no illegal aliases are used - /// + /// Used to validate the aliases for the content type when MB is enabled to ensure that + /// no illegal aliases are used + /// public class ContentTypeModelValidator : ContentTypeModelValidatorBase { } @@ -32,8 +32,8 @@ namespace Umbraco.ModelsBuilder.Validation { } public abstract class ContentTypeModelValidatorBase : EditorValidator - where TModel: ContentTypeSave - where TProperty: PropertyTypeBasic + where TModel : ContentTypeSave + where TProperty : PropertyTypeBasic { protected override IEnumerable Validate(TModel model) { @@ -57,9 +57,7 @@ namespace Umbraco.ModelsBuilder.Validation var validationResult = ValidateProperty(prop, groupIndex, propertyIndex); if (validationResult != null) - { yield return validationResult; - } } } @@ -73,13 +71,11 @@ namespace Umbraco.ModelsBuilder.Validation var alias = property.Alias; if (reservedProperties.InvariantContains(alias) || reservedMethods.InvariantContains(alias)) - { return new ValidationResult( string.Format("The alias {0} is a reserved term and cannot be used", alias), new[] { string.Format("Groups[{0}].Properties[{1}].Alias", groupIndex, propertyIndex) }); - } return null; } diff --git a/src/Umbraco.ModelsBuilder/BackOffice/DashboardReport.cs b/src/Umbraco.ModelsBuilder/BackOffice/DashboardReport.cs index 85f7030eff..64fb217fce 100644 --- a/src/Umbraco.ModelsBuilder/BackOffice/DashboardReport.cs +++ b/src/Umbraco.ModelsBuilder/BackOffice/DashboardReport.cs @@ -1,7 +1,6 @@ using System.Text; using Umbraco.Core.Composing; using Umbraco.ModelsBuilder.Configuration; -using Umbraco.ModelsBuilder.Umbraco; namespace Umbraco.ModelsBuilder.BackOffice { @@ -29,7 +28,7 @@ namespace Umbraco.ModelsBuilder.BackOffice var sb = new StringBuilder(); sb.Append("Version: "); - sb.Append(Api.ApiVersion.Current.Version); + sb.Append(ApiVersion.Current.Version); sb.Append("
 
"); sb.Append("ModelsBuilder is enabled, with the following configuration:"); diff --git a/src/Umbraco.ModelsBuilder/BackOffice/ModelsBuilderBackOfficeController.cs b/src/Umbraco.ModelsBuilder/BackOffice/ModelsBuilderBackOfficeController.cs index 6092c9678b..e4efca70bd 100644 --- a/src/Umbraco.ModelsBuilder/BackOffice/ModelsBuilderBackOfficeController.cs +++ b/src/Umbraco.ModelsBuilder/BackOffice/ModelsBuilderBackOfficeController.cs @@ -9,7 +9,6 @@ using System.Web.Hosting; using Umbraco.Core.Exceptions; using Umbraco.ModelsBuilder.Building; using Umbraco.ModelsBuilder.Configuration; -using Umbraco.ModelsBuilder.Umbraco; using Umbraco.Web.Editors; using Umbraco.Web.WebApi.Filters; diff --git a/src/Umbraco.ModelsBuilder/Building/Builder.cs b/src/Umbraco.ModelsBuilder/Building/Builder.cs index fa05b9d9a1..885e51133b 100644 --- a/src/Umbraco.ModelsBuilder/Building/Builder.cs +++ b/src/Umbraco.ModelsBuilder/Building/Builder.cs @@ -38,8 +38,7 @@ namespace Umbraco.ModelsBuilder.Building "Umbraco.Core.Models", "Umbraco.Core.Models.PublishedContent", "Umbraco.Web", - "Umbraco.ModelsBuilder", - "Umbraco.ModelsBuilder.Umbraco", + "Umbraco.ModelsBuilder" }; /// diff --git a/src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerator.cs b/src/Umbraco.ModelsBuilder/Building/ModelsGenerator.cs similarity index 94% rename from src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerator.cs rename to src/Umbraco.ModelsBuilder/Building/ModelsGenerator.cs index 5ef86b50ca..6f6872e8ff 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerator.cs +++ b/src/Umbraco.ModelsBuilder/Building/ModelsGenerator.cs @@ -2,11 +2,9 @@ using System.IO; using System.Text; using System.Web; -using Umbraco.ModelsBuilder.Building; using Umbraco.ModelsBuilder.Configuration; -using Umbraco.ModelsBuilder.Umbraco; -namespace Umbraco.ModelsBuilder.Umbraco +namespace Umbraco.ModelsBuilder.Building { public class ModelsGenerator { diff --git a/src/Umbraco.ModelsBuilder/Building/TextBuilder.cs b/src/Umbraco.ModelsBuilder/Building/TextBuilder.cs index 79a64bd1ed..56349f16a8 100644 --- a/src/Umbraco.ModelsBuilder/Building/TextBuilder.cs +++ b/src/Umbraco.ModelsBuilder/Building/TextBuilder.cs @@ -5,7 +5,6 @@ using System.Text; using System.Text.RegularExpressions; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; -using Umbraco.ModelsBuilder.Api; using Umbraco.ModelsBuilder.Configuration; namespace Umbraco.ModelsBuilder.Building diff --git a/src/Umbraco.ModelsBuilder/Building/TextHeaderWriter.cs b/src/Umbraco.ModelsBuilder/Building/TextHeaderWriter.cs index 27fcb42b35..7244a2966d 100644 --- a/src/Umbraco.ModelsBuilder/Building/TextHeaderWriter.cs +++ b/src/Umbraco.ModelsBuilder/Building/TextHeaderWriter.cs @@ -1,5 +1,4 @@ using System.Text; -using Umbraco.ModelsBuilder.Api; namespace Umbraco.ModelsBuilder.Building { diff --git a/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderHasher.cs b/src/Umbraco.ModelsBuilder/Building/TypeModelHasher.cs similarity index 92% rename from src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderHasher.cs rename to src/Umbraco.ModelsBuilder/Building/TypeModelHasher.cs index 3354b4040d..2099c09415 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderHasher.cs +++ b/src/Umbraco.ModelsBuilder/Building/TypeModelHasher.cs @@ -1,10 +1,9 @@ using System.Collections.Generic; using System.Linq; -using Umbraco.ModelsBuilder.Building; -namespace Umbraco.ModelsBuilder.Umbraco +namespace Umbraco.ModelsBuilder.Building { - internal class ModelsBuilderHasher + internal class TypeModelHasher { public static string Hash(IEnumerable typeModels) { diff --git a/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComponent.cs b/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComponent.cs index 6deeda40a6..a68315606e 100644 --- a/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComponent.cs +++ b/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComponent.cs @@ -10,7 +10,6 @@ using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; using Umbraco.ModelsBuilder.BackOffice; using Umbraco.ModelsBuilder.Configuration; -using Umbraco.ModelsBuilder.Umbraco; using Umbraco.Web; using Umbraco.Web.JavaScript; using Umbraco.Web.Mvc; diff --git a/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComposer.cs b/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComposer.cs index f2b09903f3..fdc4253490 100644 --- a/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComposer.cs +++ b/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComposer.cs @@ -2,8 +2,8 @@ using Umbraco.Core; using Umbraco.Core.Composing; using Umbraco.Core.Models.PublishedContent; +using Umbraco.ModelsBuilder.Building; using Umbraco.ModelsBuilder.Configuration; -using Umbraco.ModelsBuilder.Umbraco; using Umbraco.Web.PublishedCache.NuCache; namespace Umbraco.ModelsBuilder.Compose diff --git a/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProvider.cs b/src/Umbraco.ModelsBuilder/LiveModelsProvider.cs similarity index 95% rename from src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProvider.cs rename to src/Umbraco.ModelsBuilder/LiveModelsProvider.cs index 19a725f85a..1e77ff3059 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProvider.cs +++ b/src/Umbraco.ModelsBuilder/LiveModelsProvider.cs @@ -5,13 +5,13 @@ using System.Web.Hosting; using Umbraco.Core.Exceptions; using Umbraco.Core.IO; using Umbraco.Core.Logging; +using Umbraco.ModelsBuilder.Building; using Umbraco.ModelsBuilder.Configuration; -using Umbraco.ModelsBuilder.Umbraco; using Umbraco.Web.Cache; -namespace Umbraco.ModelsBuilder.Umbraco +namespace Umbraco.ModelsBuilder { - // supports LiveDll and LiveAppData - but not PureLive + // supports LiveAppData - but not PureLive public sealed class LiveModelsProvider { private static Mutex _mutex; @@ -80,7 +80,7 @@ namespace Umbraco.ModelsBuilder.Umbraco try { _logger.Debug("Generate models..."); - const int timeout = 2*60*1000; // 2 mins + const int timeout = 2 * 60 * 1000; // 2 mins _mutex.WaitOne(timeout); // wait until it is safe, and acquire _logger.Info("Generate models now."); GenerateModels(); @@ -112,6 +112,6 @@ namespace Umbraco.ModelsBuilder.Umbraco _modelGenerator.GenerateModels(); } - + } } diff --git a/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProviderModule.cs b/src/Umbraco.ModelsBuilder/LiveModelsProviderModule.cs similarity index 95% rename from src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProviderModule.cs rename to src/Umbraco.ModelsBuilder/LiveModelsProviderModule.cs index aae01ea75c..9d50c4ab42 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProviderModule.cs +++ b/src/Umbraco.ModelsBuilder/LiveModelsProviderModule.cs @@ -2,12 +2,12 @@ using System.Web; using Umbraco.Core; using Umbraco.Core.Composing; -using Umbraco.ModelsBuilder.Umbraco; +using Umbraco.ModelsBuilder; // will install only if configuration says it needs to be installed [assembly: PreApplicationStartMethod(typeof(LiveModelsProviderModule), "Install")] -namespace Umbraco.ModelsBuilder.Umbraco +namespace Umbraco.ModelsBuilder { // have to do this because it's the only way to subscribe to EndRequest, // module is installed by assembly attribute at the top of this file diff --git a/src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerationError.cs b/src/Umbraco.ModelsBuilder/ModelsGenerationError.cs similarity index 97% rename from src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerationError.cs rename to src/Umbraco.ModelsBuilder/ModelsGenerationError.cs index 3f96d2ccdf..8264fecc87 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerationError.cs +++ b/src/Umbraco.ModelsBuilder/ModelsGenerationError.cs @@ -4,7 +4,7 @@ using System.Text; using Umbraco.Core.Composing; using Umbraco.ModelsBuilder.Configuration; -namespace Umbraco.ModelsBuilder.Umbraco +namespace Umbraco.ModelsBuilder { public sealed class ModelsGenerationError { diff --git a/src/Umbraco.ModelsBuilder/Umbraco/OutOfDateModelsStatus.cs b/src/Umbraco.ModelsBuilder/OutOfDateModelsStatus.cs similarity index 97% rename from src/Umbraco.ModelsBuilder/Umbraco/OutOfDateModelsStatus.cs rename to src/Umbraco.ModelsBuilder/OutOfDateModelsStatus.cs index a72ccd2fa2..3b43ed771e 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/OutOfDateModelsStatus.cs +++ b/src/Umbraco.ModelsBuilder/OutOfDateModelsStatus.cs @@ -3,7 +3,7 @@ using Umbraco.Core.Composing; using Umbraco.ModelsBuilder.Configuration; using Umbraco.Web.Cache; -namespace Umbraco.ModelsBuilder.Umbraco +namespace Umbraco.ModelsBuilder { public sealed class OutOfDateModelsStatus { diff --git a/src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs b/src/Umbraco.ModelsBuilder/PublishedModelUtility.cs similarity index 92% rename from src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs rename to src/Umbraco.ModelsBuilder/PublishedModelUtility.cs index 1db15fa9b1..44af532a0b 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs +++ b/src/Umbraco.ModelsBuilder/PublishedModelUtility.cs @@ -5,7 +5,7 @@ using System.Linq.Expressions; using Umbraco.Web.Composing; using Umbraco.Core.Models.PublishedContent; -namespace Umbraco.ModelsBuilder.Umbraco +namespace Umbraco.ModelsBuilder { /// /// This is called from within the generated model classes @@ -48,7 +48,7 @@ namespace Umbraco.ModelsBuilder.Umbraco } public static IPublishedPropertyType GetModelPropertyType(IPublishedContentType contentType, Expression> selector) - //where TModel : PublishedContentModel // fixme PublishedContentModel _or_ PublishedElementModel + //where TModel : PublishedContentModel // fixme PublishedContentModel _or_ PublishedElementModel { // fixme therefore, missing a check on TModel here @@ -61,7 +61,7 @@ namespace Umbraco.ModelsBuilder.Umbraco // see note above : accepted risk... var attr = expr.Member - .GetCustomAttributes(typeof (ImplementPropertyTypeAttribute), false) + .GetCustomAttributes(typeof(ImplementPropertyTypeAttribute), false) .OfType() .SingleOrDefault(); diff --git a/src/Umbraco.ModelsBuilder/PureLiveAssemblyAttribute.cs b/src/Umbraco.ModelsBuilder/PureLiveAssemblyAttribute.cs deleted file mode 100644 index dfe369dc21..0000000000 --- a/src/Umbraco.ModelsBuilder/PureLiveAssemblyAttribute.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace Umbraco.ModelsBuilder -{ - /// - /// Indicates that an Assembly is a PureLive models assembly. - /// - /// Though technically not required, ie models will work without it, the attribute - /// can be used by Umbraco view models binder to figure out whether the model type comes - /// from a PureLive Assembly. - [Obsolete("Should use ModelsBuilderAssemblyAttribute but that requires a change in Umbraco Core.")] - [AttributeUsage(AttributeTargets.Assembly /*, AllowMultiple = false, Inherited = false*/)] - public sealed class PureLiveAssemblyAttribute : Attribute - { } -} diff --git a/src/Umbraco.ModelsBuilder/Umbraco/PureLiveModelFactory.cs b/src/Umbraco.ModelsBuilder/PureLiveModelFactory.cs similarity index 96% rename from src/Umbraco.ModelsBuilder/Umbraco/PureLiveModelFactory.cs rename to src/Umbraco.ModelsBuilder/PureLiveModelFactory.cs index 464ce92ef5..eb82b4323f 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/PureLiveModelFactory.cs +++ b/src/Umbraco.ModelsBuilder/PureLiveModelFactory.cs @@ -21,7 +21,7 @@ using Umbraco.ModelsBuilder.Building; using Umbraco.ModelsBuilder.Configuration; using File = System.IO.File; -namespace Umbraco.ModelsBuilder.Umbraco +namespace Umbraco.ModelsBuilder { internal class PureLiveModelFactory : ILivePublishedModelFactory, IRegisteredObject { @@ -101,7 +101,7 @@ namespace Umbraco.ModelsBuilder.Umbraco var contentTypeAlias = element.ContentType.Alias; // lookup model constructor (else null) - infos.TryGetValue(contentTypeAlias, out ModelInfo info); + infos.TryGetValue(contentTypeAlias, out var info); // create model return info == null ? element : info.Ctor(element); @@ -234,10 +234,10 @@ namespace Umbraco.ModelsBuilder.Umbraco get { if (_theBuildManager != null) return _theBuildManager; - var prop = typeof (BuildManager).GetProperty("TheBuildManager", BindingFlags.NonPublic | BindingFlags.Static); + var prop = typeof(BuildManager).GetProperty("TheBuildManager", BindingFlags.NonPublic | BindingFlags.Static); if (prop == null) throw new InvalidOperationException("Could not get BuildManager.TheBuildManager property."); - _theBuildManager = (BuildManager) prop.GetValue(null); + _theBuildManager = (BuildManager)prop.GetValue(null); return _theBuildManager; } } @@ -335,7 +335,7 @@ namespace Umbraco.ModelsBuilder.Umbraco Directory.CreateDirectory(modelsDirectory); var typeModels = UmbracoServices.GetAllTypes(); - var currentHash = ModelsBuilderHasher.Hash(typeModels); + var currentHash = TypeModelHasher.Hash(typeModels); var modelsHashFile = Path.Combine(modelsDirectory, "models.hash"); var modelsSrcFile = Path.Combine(modelsDirectory, "models.generated.cs"); var projFile = Path.Combine(modelsDirectory, "all.generated.cs"); @@ -456,8 +456,7 @@ namespace Umbraco.ModelsBuilder.Umbraco // AssemblyVersion is so that we have a different version for each rebuild var ver = _ver == _skipver ? ++_ver : _ver; _ver++; - code = code.Replace("//ASSATTR", $@"[assembly: PureLiveAssembly] -[assembly:ModelsBuilderAssembly(PureLive = true, SourceHash = ""{currentHash}"")] + code = code.Replace("//ASSATTR", $@"[assembly:ModelsBuilderAssembly(PureLive = true, SourceHash = ""{currentHash}"")] [assembly:System.Reflection.AssemblyVersion(""0.0.0.{ver}"")]"); File.WriteAllText(modelsSrcFile, code); @@ -505,7 +504,7 @@ namespace Umbraco.ModelsBuilder.Umbraco private static Infos RegisterModels(IEnumerable types) { - var ctorArgTypes = new[] { typeof (IPublishedElement) }; + var ctorArgTypes = new[] { typeof(IPublishedElement) }; var modelInfos = new Dictionary(StringComparer.InvariantCultureIgnoreCase); var map = new Dictionary(); @@ -517,7 +516,7 @@ namespace Umbraco.ModelsBuilder.Umbraco foreach (var ctor in type.GetConstructors()) { var parms = ctor.GetParameters(); - if (parms.Length == 1 && typeof (IPublishedElement).IsAssignableFrom(parms[0].ParameterType)) + if (parms.Length == 1 && typeof(IPublishedElement).IsAssignableFrom(parms[0].ParameterType)) { if (constructor != null) throw new InvalidOperationException($"Type {type.FullName} has more than one public constructor with one argument of type, or implementing, IPropertySet."); @@ -532,17 +531,17 @@ namespace Umbraco.ModelsBuilder.Umbraco var attribute = type.GetCustomAttribute(false); var typeName = attribute == null ? type.Name : attribute.ContentTypeAlias; - if (modelInfos.TryGetValue(typeName, out ModelInfo modelInfo)) + if (modelInfos.TryGetValue(typeName, out var modelInfo)) throw new InvalidOperationException($"Both types {type.FullName} and {modelInfo.ModelType.FullName} want to be a model type for content type with alias \"{typeName}\"."); // fixme use Core's ReflectionUtilities.EmitCtor !! // Yes .. DynamicMethod is uber slow - var meth = new DynamicMethod(string.Empty, typeof (IPublishedElement), ctorArgTypes, type.Module, true); + var meth = new DynamicMethod(string.Empty, typeof(IPublishedElement), ctorArgTypes, type.Module, true); var gen = meth.GetILGenerator(); gen.Emit(OpCodes.Ldarg_0); gen.Emit(OpCodes.Newobj, constructor); gen.Emit(OpCodes.Ret); - var func = (Func) meth.CreateDelegate(typeof (Func)); + var func = (Func)meth.CreateDelegate(typeof(Func)); modelInfos[typeName] = new ModelInfo { ParameterType = parameterType, Ctor = func, ModelType = type }; map[typeName] = type; @@ -663,9 +662,7 @@ namespace Umbraco.ModelsBuilder.Umbraco _logger.Info("Detected files changes."); lock (SyncRoot) // don't reset while being locked - { ResetModels(); - } } public void Stop(bool immediate) diff --git a/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj b/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj index 027ed6665c..46ec0c7d2c 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj +++ b/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj @@ -49,7 +49,7 @@ Properties\SolutionInfo.cs - + @@ -65,24 +65,23 @@ - - - - + + + - + - - - - - - + + + + + + diff --git a/src/Umbraco.ModelsBuilder/Umbraco/UmbracoServices.cs b/src/Umbraco.ModelsBuilder/UmbracoServices.cs similarity index 98% rename from src/Umbraco.ModelsBuilder/Umbraco/UmbracoServices.cs rename to src/Umbraco.ModelsBuilder/UmbracoServices.cs index 410349096a..725e5b18c4 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/UmbracoServices.cs +++ b/src/Umbraco.ModelsBuilder/UmbracoServices.cs @@ -11,9 +11,9 @@ using Umbraco.Core.Strings; using Umbraco.ModelsBuilder.Building; using Umbraco.ModelsBuilder.Configuration; -namespace Umbraco.ModelsBuilder.Umbraco +namespace Umbraco.ModelsBuilder { - public class UmbracoServices + public sealed class UmbracoServices { private readonly IContentTypeService _contentTypeService; private readonly IMediaTypeService _mediaTypeService; @@ -184,11 +184,9 @@ namespace Umbraco.ModelsBuilder.Umbraco { var groups = typeModels.GroupBy(x => x.Alias.ToLowerInvariant()); foreach (var group in groups.Where(x => x.Count() > 1)) - { throw new NotSupportedException($"Alias \"{group.Key}\" is used by types" + $" {string.Join(", ", group.Select(x => x.ItemType + ":\"" + x.Alias + "\""))}. Aliases have to be unique." + " One of the aliases must be modified in order to use the ModelsBuilder."); - } return typeModels; } diff --git a/src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs b/src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs index 18be0a37a3..83d9a9f4b0 100644 --- a/src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs +++ b/src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs @@ -7,7 +7,6 @@ using Moq; using NUnit.Framework; using Umbraco.Core.Composing; using Umbraco.Core.Models.PublishedContent; -using Umbraco.ModelsBuilder.Api; using Umbraco.ModelsBuilder.Building; using Umbraco.ModelsBuilder.Configuration; @@ -77,7 +76,6 @@ using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Web; using Umbraco.ModelsBuilder; -using Umbraco.ModelsBuilder.Umbraco; namespace Umbraco.Web.PublishedModels { @@ -193,7 +191,6 @@ using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Web; using Umbraco.ModelsBuilder; -using Umbraco.ModelsBuilder.Umbraco; namespace Umbraco.Web.PublishedModels { diff --git a/src/Umbraco.Tests/ModelsBuilder/UmbracoApplicationTests.cs b/src/Umbraco.Tests/ModelsBuilder/UmbracoApplicationTests.cs index 12fa777e69..d6d44474c3 100644 --- a/src/Umbraco.Tests/ModelsBuilder/UmbracoApplicationTests.cs +++ b/src/Umbraco.Tests/ModelsBuilder/UmbracoApplicationTests.cs @@ -5,7 +5,6 @@ using System.Text; using System.Threading.Tasks; using NUnit.Framework; using Umbraco.ModelsBuilder.Building; -using Umbraco.ModelsBuilder.Umbraco; namespace Umbraco.ModelsBuilder.Tests { From d7ca1933cd85b9fdb5ae88d641e51b6b7eb3893d Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 28 Oct 2019 21:59:08 +1100 Subject: [PATCH 091/548] fixes tests --- src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs b/src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs index 83d9a9f4b0..e9824b9799 100644 --- a/src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs +++ b/src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs @@ -15,13 +15,13 @@ namespace Umbraco.ModelsBuilder.Tests [TestFixture] public class BuilderTests { - [SetUp] - public void Setup() - { - Current.Reset(); - Current.UnlockConfigs(); - Current.Configs.Add(() => new ModelsBuilderConfig()); - } + // [SetUp] + // public void Setup() + // { + // Current.Reset(); + // Current.UnlockConfigs(); + // Current.Configs.Add(() => new ModelsBuilderConfig()); + // } [Test] public void GenerateSimpleType() From 35192eefe53dfd096efb7c5791a00580df650b4e Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 28 Oct 2019 22:12:08 +1100 Subject: [PATCH 092/548] cleanup --- src/Umbraco.ModelsBuilder/Building/Builder.cs | 7 ++++--- src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs | 7 ------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.ModelsBuilder/Building/Builder.cs b/src/Umbraco.ModelsBuilder/Building/Builder.cs index 885e51133b..bba19c3b9f 100644 --- a/src/Umbraco.ModelsBuilder/Building/Builder.cs +++ b/src/Umbraco.ModelsBuilder/Building/Builder.cs @@ -213,9 +213,10 @@ namespace Umbraco.ModelsBuilder.Building if (!string.IsNullOrWhiteSpace(ModelsNamespace)) return ModelsNamespace; - // default - // fixme - should NOT reference config here, should make ModelsNamespace mandatory - return string.IsNullOrWhiteSpace(Config.ModelsNamespace) ? ModelsBuilderConfig.DefaultModelsNamespace : Config.ModelsNamespace; + // use configured else fallback to default + return string.IsNullOrWhiteSpace(Config.ModelsNamespace) + ? ModelsBuilderConfig.DefaultModelsNamespace + : Config.ModelsNamespace; } protected string GetModelsBaseClassName(TypeModel type) diff --git a/src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs b/src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs index e9824b9799..99012643aa 100644 --- a/src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs +++ b/src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs @@ -15,13 +15,6 @@ namespace Umbraco.ModelsBuilder.Tests [TestFixture] public class BuilderTests { - // [SetUp] - // public void Setup() - // { - // Current.Reset(); - // Current.UnlockConfigs(); - // Current.Configs.Add(() => new ModelsBuilderConfig()); - // } [Test] public void GenerateSimpleType() From c10ad86c43938cf8b76325f1b5164bec9eaf3420 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 28 Oct 2019 22:23:06 +1100 Subject: [PATCH 093/548] more cleanup --- src/Umbraco.ModelsBuilder/LiveModelsProvider.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Umbraco.ModelsBuilder/LiveModelsProvider.cs b/src/Umbraco.ModelsBuilder/LiveModelsProvider.cs index 1e77ff3059..0dfe503fce 100644 --- a/src/Umbraco.ModelsBuilder/LiveModelsProvider.cs +++ b/src/Umbraco.ModelsBuilder/LiveModelsProvider.cs @@ -104,10 +104,6 @@ namespace Umbraco.ModelsBuilder private void GenerateModels() { - var bin = IOHelper.MapPath("~/bin"); - if (bin == null) - throw new PanicException("Panic: bin is null."); - // EnableDllModels will recycle the app domain - but this request will end properly _modelGenerator.GenerateModels(); } From 21290fd13430e7dfbf8b75f96b8833beee805461 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Mon, 28 Oct 2019 12:53:08 +0100 Subject: [PATCH 094/548] AB2462 - Fixed issue with multiple references to the same item --- .../Repositories/Implement/ContentRepositoryBase.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs index aaa3946494..d93fd72bfb 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs @@ -25,7 +25,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement internal sealed class ContentRepositoryBase { /// - /// + /// /// This is used for unit tests ONLY /// public static bool ThrowOnWarning = false; @@ -38,7 +38,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement private readonly Lazy _propertyEditors; /// - /// + /// /// /// /// @@ -842,6 +842,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement 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); @@ -862,7 +863,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (!keyToIds.TryGetValue(guid, out var id)) continue; // This shouldn't happen! - + //Create new relation //TODO: This is N+1, we could do this all in one operation, just need a new method on the relations repo RelationRepository.Save(new Relation(entity.Id, id, relationType)); From 5699dc32b714564b9c274b51835abd449b3a4a53 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Mon, 28 Oct 2019 12:53:37 +0100 Subject: [PATCH 095/548] AB2462 - Find the relation type from the entity type on the udi --- .../Models/Editors/UmbracoEntityReference.cs | 15 +++++++++++++++ .../PropertyEditors/RichTextPropertyEditor.cs | 8 ++------ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Core/Models/Editors/UmbracoEntityReference.cs b/src/Umbraco.Core/Models/Editors/UmbracoEntityReference.cs index f5121988f5..31d48e60cf 100644 --- a/src/Umbraco.Core/Models/Editors/UmbracoEntityReference.cs +++ b/src/Umbraco.Core/Models/Editors/UmbracoEntityReference.cs @@ -16,6 +16,21 @@ namespace Umbraco.Core.Models.Editors 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(); diff --git a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs index 1c97a2de7a..678b7f2eb8 100644 --- a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs @@ -140,15 +140,11 @@ namespace Umbraco.Web.PropertyEditors { var asString = value == null ? string.Empty : value is string str ? str : value.ToString(); - //TODO: FindUdisFromDataAttributes will return UDIs of any type found, typically these will always be "media" but - // if the text is modified to be another type it will still be returned but we will always be relating these with the RelatedMediaAlias foreach (var udi in _imageSourceParser.FindUdisFromDataAttributes(asString)) - yield return new UmbracoEntityReference(udi, Constants.Conventions.RelationTypes.RelatedMediaAlias); + yield return new UmbracoEntityReference(udi); - //TODO: FindUdisFromLocalLinks will return UDIs of any type found, typically these will always be "document" but - // if the text is modified to be another type it will still be returned but we will always be relating these with the RelatedDocumentAlias foreach (var udi in _localLinkParser.FindUdisFromLocalLinks(asString)) - yield return new UmbracoEntityReference(udi, Constants.Conventions.RelationTypes.RelatedDocumentAlias); + 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 } From c52adf76f7fe5daedd1a51daf9c994b235c838ef Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Mon, 28 Oct 2019 12:56:54 +0100 Subject: [PATCH 096/548] AB3226 - Added MediaPicker references --- .../MediaPickerPropertyEditor.cs | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs index dd755ee0ba..0d0f1a8498 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,32 @@ 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); + } + + public class MediaPickerPropertyValueEditor : DataValueEditor, IDataValueReference + { + public MediaPickerPropertyValueEditor(DataEditorAttribute attribute) : base(attribute) + { + } + + public IEnumerable GetReferences(object value) + { + var asString = value == null ? string.Empty : value is string str ? str : value.ToString(); + + yield return new UmbracoEntityReference(Udi.Parse(asString)); + } } } From 80643ac6ad8b45c5133e13fcbe9aa4bb31c4a438 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Mon, 28 Oct 2019 13:01:39 +0100 Subject: [PATCH 097/548] AB3325 - Added Grid references --- .../PropertyEditors/GridPropertyEditor.cs | 57 ++++++++++++++++--- 1 file changed, 48 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs index 4eea36300b..e30e661ab1 100644 --- a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs @@ -29,13 +29,19 @@ namespace Umbraco.Web.PropertyEditors private IUmbracoContextAccessor _umbracoContextAccessor; private readonly HtmlImageSourceParser _imageSourceParser; private readonly RichTextEditorPastedImages _pastedImages; + private readonly HtmlLocalLinkParser _localLinkParser; - public GridPropertyEditor(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, HtmlImageSourceParser imageSourceParser, RichTextEditorPastedImages pastedImages) + public GridPropertyEditor(ILogger logger, + IUmbracoContextAccessor umbracoContextAccessor, + HtmlImageSourceParser imageSourceParser, + RichTextEditorPastedImages pastedImages, + HtmlLocalLinkParser localLinkParser) : base(logger) { _umbracoContextAccessor = umbracoContextAccessor; _imageSourceParser = imageSourceParser; _pastedImages = pastedImages; + _localLinkParser = localLinkParser; } public override IPropertyIndexValueFactory PropertyIndexValueFactory => new GridPropertyIndexValueFactory(); @@ -44,22 +50,30 @@ namespace Umbraco.Web.PropertyEditors /// Overridden to ensure that the value is validated /// /// - protected override IDataValueEditor CreateValueEditor() => new GridPropertyValueEditor(Attribute, _umbracoContextAccessor, _imageSourceParser, _pastedImages); + protected override IDataValueEditor CreateValueEditor() => new GridPropertyValueEditor(Attribute, _umbracoContextAccessor, _imageSourceParser, _pastedImages, _localLinkParser); protected override IConfigurationEditor CreateConfigurationEditor() => new GridConfigurationEditor(); - internal class GridPropertyValueEditor : DataValueEditor + internal class GridPropertyValueEditor : DataValueEditor, IDataValueReference { - private IUmbracoContextAccessor _umbracoContextAccessor; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; private readonly HtmlImageSourceParser _imageSourceParser; private readonly RichTextEditorPastedImages _pastedImages; + private readonly RichTextPropertyEditor.RichTextPropertyValueEditor _richTextPropertyValueEditor; + private readonly MediaPickerPropertyValueEditor _mediaPickerPropertyValueEditor; - public GridPropertyValueEditor(DataEditorAttribute attribute, IUmbracoContextAccessor umbracoContextAccessor, HtmlImageSourceParser imageSourceParser, RichTextEditorPastedImages pastedImages) + public GridPropertyValueEditor(DataEditorAttribute attribute, + IUmbracoContextAccessor umbracoContextAccessor, + HtmlImageSourceParser imageSourceParser, + RichTextEditorPastedImages pastedImages, + HtmlLocalLinkParser localLinkParser) : base(attribute) { _umbracoContextAccessor = umbracoContextAccessor; _imageSourceParser = imageSourceParser; _pastedImages = pastedImages; + _richTextPropertyValueEditor = new RichTextPropertyEditor.RichTextPropertyValueEditor(attribute, umbracoContextAccessor, imageSourceParser, localLinkParser, pastedImages); + _mediaPickerPropertyValueEditor = new MediaPickerPropertyValueEditor(attribute); } /// @@ -84,7 +98,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; @@ -117,7 +131,7 @@ namespace Umbraco.Web.PropertyEditors var val = property.GetValue(culture, segment); if (val == null) return string.Empty; - var grid = DeserializeGridValue(val.ToString(), out var rtes); + var grid = DeserializeGridValue(val.ToString(), out var rtes, out _); //process the rte values foreach (var rte in rtes.ToList()) @@ -131,16 +145,41 @@ namespace Umbraco.Web.PropertyEditors 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(); + + 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; + } + } } } } From 35ebc13774e07ba5fd62573348c61de291e5d433 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Mon, 28 Oct 2019 13:25:05 +0100 Subject: [PATCH 098/548] AB3331 - Added MNTP references --- .../MultiNodeTreePickerPropertyEditor.cs | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/PropertyEditors/MultiNodeTreePickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MultiNodeTreePickerPropertyEditor.cs index 742acbeca2..020ad38c75 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,29 @@ 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) + { + yield return new UmbracoEntityReference(Udi.Parse(udiPath)); + } + + } + } } + + } From f2e02ea05eff34f533bb60e6da049eb9f54c8c62 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Mon, 28 Oct 2019 13:57:55 +0100 Subject: [PATCH 099/548] AB3329 - Added Content picker references --- .../ContentPickerPropertyEditor.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/Umbraco.Web/PropertyEditors/ContentPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/ContentPickerPropertyEditor.cs index c6de91f560..0e6eaa9254 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,23 @@ 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) + { + if (value == null) yield break; + + var asString = value is string str ? str : value.ToString(); + + yield return new UmbracoEntityReference(Udi.Parse(asString)); + } + } } } From c8b189d53deb9d17061b3cb2ca84a4a1097f2a30 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Mon, 28 Oct 2019 13:58:23 +0100 Subject: [PATCH 100/548] AB3326 - Made MediaPickerPropertyValueEditor nested and internal and handle null values --- .../PropertyEditors/GridPropertyEditor.cs | 4 ++-- .../MediaPickerPropertyEditor.cs | 22 +++++++++++-------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs index e30e661ab1..0a6d1e2adf 100644 --- a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs @@ -60,7 +60,7 @@ namespace Umbraco.Web.PropertyEditors private readonly HtmlImageSourceParser _imageSourceParser; private readonly RichTextEditorPastedImages _pastedImages; private readonly RichTextPropertyEditor.RichTextPropertyValueEditor _richTextPropertyValueEditor; - private readonly MediaPickerPropertyValueEditor _mediaPickerPropertyValueEditor; + private readonly MediaPickerPropertyEditor.MediaPickerPropertyValueEditor _mediaPickerPropertyValueEditor; public GridPropertyValueEditor(DataEditorAttribute attribute, IUmbracoContextAccessor umbracoContextAccessor, @@ -73,7 +73,7 @@ namespace Umbraco.Web.PropertyEditors _imageSourceParser = imageSourceParser; _pastedImages = pastedImages; _richTextPropertyValueEditor = new RichTextPropertyEditor.RichTextPropertyValueEditor(attribute, umbracoContextAccessor, imageSourceParser, localLinkParser, pastedImages); - _mediaPickerPropertyValueEditor = new MediaPickerPropertyValueEditor(attribute); + _mediaPickerPropertyValueEditor = new MediaPickerPropertyEditor.MediaPickerPropertyValueEditor(attribute); } /// diff --git a/src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs index 0d0f1a8498..ffa3b8c074 100644 --- a/src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs @@ -32,19 +32,23 @@ namespace Umbraco.Web.PropertyEditors protected override IConfigurationEditor CreateConfigurationEditor() => new MediaPickerConfigurationEditor(); protected override IDataValueEditor CreateValueEditor() => new MediaPickerPropertyValueEditor(Attribute); - } - public class MediaPickerPropertyValueEditor : DataValueEditor, IDataValueReference - { - public MediaPickerPropertyValueEditor(DataEditorAttribute attribute) : base(attribute) + internal class MediaPickerPropertyValueEditor : DataValueEditor, IDataValueReference { - } + public MediaPickerPropertyValueEditor(DataEditorAttribute attribute) : base(attribute) + { + } - public IEnumerable GetReferences(object value) - { - var asString = value == null ? string.Empty : value is string str ? str : value.ToString(); + public IEnumerable GetReferences(object value) + { + if (value == null) yield break; - yield return new UmbracoEntityReference(Udi.Parse(asString)); + var asString = value is string str ? str : value.ToString(); + + yield return new UmbracoEntityReference(Udi.Parse(asString)); + } } } + + } From 0aaee0babab46caabb6fa3e1c545039287b5fe4b Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Mon, 28 Oct 2019 14:11:59 +0100 Subject: [PATCH 101/548] AB3328 - Multi Url Picker references --- .../PropertyEditors/MultiUrlPickerValueEditor.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/PropertyEditors/MultiUrlPickerValueEditor.cs b/src/Umbraco.Web/PropertyEditors/MultiUrlPickerValueEditor.cs index aa8fa73c7a..1f71f39d96 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,17 @@ 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(); + + var links = JsonConvert.DeserializeObject>(asString); + foreach (var link in links) + { + yield return new UmbracoEntityReference(link.Udi); + } + + } } } From 3b6abbb9368b0a93e6ef720e0a5d4bd0c8885a95 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 29 Oct 2019 00:25:03 +1100 Subject: [PATCH 102/548] Renames project: Umbraco.ModelsBuilder.Embedded and namespaces since we need a different assembly, updates nuspec, changes file path of MB app_plugins --- build/NuSpecs/UmbracoCms.Web.nuspec | 6 +++--- build/NuSpecs/UmbracoCms.nuspec | 1 + src/Umbraco.Core/Properties/AssemblyInfo.cs | 2 +- .../ApiVersion.cs | 2 +- .../BackOffice/ContentTypeModelValidator.cs | 4 +--- .../BackOffice/DashboardReport.cs | 5 ++--- .../ModelsBuilderBackOfficeController.cs | 9 +++------ .../Building/Builder.cs | 10 ++-------- .../Building/ModelsGenerator.cs | 8 +++----- .../Building/PropertyModel.cs | 2 +- .../Building/TextBuilder.cs | 6 ++---- .../Building/TextHeaderWriter.cs | 2 +- .../Building/TypeModel.cs | 2 +- .../Building/TypeModelHasher.cs | 2 +- .../Compose/ModelsBuilderComponent.cs | 6 +++--- .../Compose/ModelsBuilderComposer.cs | 8 +++++--- .../Compose/ModelsBuilderInitializer.cs | 4 ++-- .../ConfigsExtensions.cs | 4 ++-- .../Configuration/IModelsBuilderConfig.cs | 2 +- .../Configuration/ModelsBuilderConfig.cs | 3 +-- .../Configuration/ModelsMode.cs | 2 +- .../Configuration/ModelsModeExtensions.cs | 2 +- .../HashCombiner.cs | 2 +- .../ImplementPropertyTypeAttribute.cs | 2 +- .../LiveModelsProvider.cs | 9 +++------ .../LiveModelsProviderModule.cs | 4 ++-- .../ModelsBuilderAssemblyAttribute.cs | 2 +- .../ModelsGenerationError.cs | 5 ++--- .../OutOfDateModelsStatus.cs | 5 ++--- .../Properties/AssemblyInfo.cs | 0 .../PublishedElementExtensions.cs | 1 + .../PublishedModelUtility.cs | 5 ++--- .../PureLiveModelFactory.cs | 8 +++----- .../ReferencedAssemblies.cs | 2 +- .../TypeExtensions.cs | 2 +- .../Umbraco.ModelsBuilder.Embedded.csproj} | 6 +++--- .../UmbracoServices.cs | 6 ++---- .../ModelsBuilder/BuilderTests.cs | 20 ++++++++----------- .../ModelsBuilder/ConfigTests.cs | 4 ++-- .../ModelsBuilder/StringExtensions.cs | 8 +------- .../ModelsBuilder/UmbracoApplicationTests.cs | 8 +++----- src/Umbraco.Tests/Umbraco.Tests.csproj | 4 ++-- .../modelsbuilder.controller.js | 0 .../modelsbuilder.html | 0 .../modelsbuilder.resource.js | 0 .../package.manifest | 0 src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 14 +++++-------- src/umbraco.sln | 3 +-- 48 files changed, 86 insertions(+), 126 deletions(-) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/ApiVersion.cs (96%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/BackOffice/ContentTypeModelValidator.cs (96%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/BackOffice/DashboardReport.cs (94%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/BackOffice/ModelsBuilderBackOfficeController.cs (96%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/Building/Builder.cs (97%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/Building/ModelsGenerator.cs (93%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/Building/PropertyModel.cs (97%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/Building/TextBuilder.cs (99%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/Building/TextHeaderWriter.cs (95%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/Building/TypeModel.cs (99%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/Building/TypeModelHasher.cs (96%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/Compose/ModelsBuilderComponent.cs (98%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/Compose/ModelsBuilderComposer.cs (94%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/Compose/ModelsBuilderInitializer.cs (91%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/ConfigsExtensions.cs (87%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/Configuration/IModelsBuilderConfig.cs (86%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/Configuration/ModelsBuilderConfig.cs (99%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/Configuration/ModelsMode.cs (95%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/Configuration/ModelsModeExtensions.cs (95%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/HashCombiner.cs (95%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/ImplementPropertyTypeAttribute.cs (92%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/LiveModelsProvider.cs (95%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/LiveModelsProviderModule.cs (95%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/ModelsBuilderAssemblyAttribute.cs (95%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/ModelsGenerationError.cs (93%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/OutOfDateModelsStatus.cs (94%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/Properties/AssemblyInfo.cs (100%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/PublishedElementExtensions.cs (98%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/PublishedModelUtility.cs (98%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/PureLiveModelFactory.cs (99%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/ReferencedAssemblies.cs (99%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/TypeExtensions.cs (96%) rename src/{Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj => Umbraco.ModelsBuilder.Embedded/Umbraco.ModelsBuilder.Embedded.csproj} (95%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/UmbracoServices.cs (98%) rename src/Umbraco.Web.UI/App_Plugins/{ModelsBuilder => UmbModelsBuilder}/modelsbuilder.controller.js (100%) rename src/Umbraco.Web.UI/App_Plugins/{ModelsBuilder => UmbModelsBuilder}/modelsbuilder.html (100%) rename src/Umbraco.Web.UI/App_Plugins/{ModelsBuilder => UmbModelsBuilder}/modelsbuilder.resource.js (100%) rename src/Umbraco.Web.UI/App_Plugins/{ModelsBuilder => UmbModelsBuilder}/package.manifest (100%) diff --git a/build/NuSpecs/UmbracoCms.Web.nuspec b/build/NuSpecs/UmbracoCms.Web.nuspec index b6cbbbdb81..658d2f0672 100644 --- a/build/NuSpecs/UmbracoCms.Web.nuspec +++ b/build/NuSpecs/UmbracoCms.Web.nuspec @@ -52,17 +52,17 @@ - + - + - + diff --git a/build/NuSpecs/UmbracoCms.nuspec b/build/NuSpecs/UmbracoCms.nuspec index a6b06d9964..d0bd4a2688 100644 --- a/build/NuSpecs/UmbracoCms.nuspec +++ b/build/NuSpecs/UmbracoCms.nuspec @@ -42,6 +42,7 @@ + diff --git a/src/Umbraco.Core/Properties/AssemblyInfo.cs b/src/Umbraco.Core/Properties/AssemblyInfo.cs index 139af8725d..87e0732d47 100644 --- a/src/Umbraco.Core/Properties/AssemblyInfo.cs +++ b/src/Umbraco.Core/Properties/AssemblyInfo.cs @@ -14,7 +14,7 @@ using System.Runtime.InteropServices; [assembly: InternalsVisibleTo("Umbraco.Web")] [assembly: InternalsVisibleTo("Umbraco.Web.UI")] [assembly: InternalsVisibleTo("Umbraco.Examine")] -[assembly: InternalsVisibleTo("Umbraco.ModelsBuilder")] +[assembly: InternalsVisibleTo("Umbraco.ModelsBuilder.Embedded")] [assembly: InternalsVisibleTo("Umbraco.Tests")] [assembly: InternalsVisibleTo("Umbraco.Tests.Benchmarks")] diff --git a/src/Umbraco.ModelsBuilder/ApiVersion.cs b/src/Umbraco.ModelsBuilder.Embedded/ApiVersion.cs similarity index 96% rename from src/Umbraco.ModelsBuilder/ApiVersion.cs rename to src/Umbraco.ModelsBuilder.Embedded/ApiVersion.cs index 67d0ec5c4c..22347edd60 100644 --- a/src/Umbraco.ModelsBuilder/ApiVersion.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/ApiVersion.cs @@ -2,7 +2,7 @@ using System.Reflection; using Semver; -namespace Umbraco.ModelsBuilder +namespace Umbraco.ModelsBuilder.Embedded { /// /// Manages API version handshake between client and server. diff --git a/src/Umbraco.ModelsBuilder/BackOffice/ContentTypeModelValidator.cs b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ContentTypeModelValidator.cs similarity index 96% rename from src/Umbraco.ModelsBuilder/BackOffice/ContentTypeModelValidator.cs rename to src/Umbraco.ModelsBuilder.Embedded/BackOffice/ContentTypeModelValidator.cs index 1971c62c9f..f351721e56 100644 --- a/src/Umbraco.ModelsBuilder/BackOffice/ContentTypeModelValidator.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ContentTypeModelValidator.cs @@ -2,13 +2,11 @@ using System.ComponentModel.DataAnnotations; using System.Linq; using Umbraco.Core; -using Umbraco.Core.Composing; using Umbraco.Core.Models.PublishedContent; -using Umbraco.ModelsBuilder.Configuration; using Umbraco.Web.Editors; using Umbraco.Web.Models.ContentEditing; -namespace Umbraco.ModelsBuilder.BackOffice +namespace Umbraco.ModelsBuilder.Embedded.BackOffice { /// /// Used to validate the aliases for the content type when MB is enabled to ensure that diff --git a/src/Umbraco.ModelsBuilder/BackOffice/DashboardReport.cs b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/DashboardReport.cs similarity index 94% rename from src/Umbraco.ModelsBuilder/BackOffice/DashboardReport.cs rename to src/Umbraco.ModelsBuilder.Embedded/BackOffice/DashboardReport.cs index 64fb217fce..e4c45aaf3e 100644 --- a/src/Umbraco.ModelsBuilder/BackOffice/DashboardReport.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/DashboardReport.cs @@ -1,8 +1,7 @@ using System.Text; -using Umbraco.Core.Composing; -using Umbraco.ModelsBuilder.Configuration; +using Umbraco.ModelsBuilder.Embedded.Configuration; -namespace Umbraco.ModelsBuilder.BackOffice +namespace Umbraco.ModelsBuilder.Embedded.BackOffice { internal class DashboardReport { diff --git a/src/Umbraco.ModelsBuilder/BackOffice/ModelsBuilderBackOfficeController.cs b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ModelsBuilderBackOfficeController.cs similarity index 96% rename from src/Umbraco.ModelsBuilder/BackOffice/ModelsBuilderBackOfficeController.cs rename to src/Umbraco.ModelsBuilder.Embedded/BackOffice/ModelsBuilderBackOfficeController.cs index e4efca70bd..0842c3586a 100644 --- a/src/Umbraco.ModelsBuilder/BackOffice/ModelsBuilderBackOfficeController.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ModelsBuilderBackOfficeController.cs @@ -1,18 +1,15 @@ using System; -using System.IO; -using System.Linq; using System.Net; using System.Net.Http; using System.Runtime.Serialization; -using System.Text; using System.Web.Hosting; using Umbraco.Core.Exceptions; -using Umbraco.ModelsBuilder.Building; -using Umbraco.ModelsBuilder.Configuration; +using Umbraco.ModelsBuilder.Embedded.Building; +using Umbraco.ModelsBuilder.Embedded.Configuration; using Umbraco.Web.Editors; using Umbraco.Web.WebApi.Filters; -namespace Umbraco.ModelsBuilder.BackOffice +namespace Umbraco.ModelsBuilder.Embedded.BackOffice { /// /// API controller for use in the Umbraco back office with Angular resources diff --git a/src/Umbraco.ModelsBuilder/Building/Builder.cs b/src/Umbraco.ModelsBuilder.Embedded/Building/Builder.cs similarity index 97% rename from src/Umbraco.ModelsBuilder/Building/Builder.cs rename to src/Umbraco.ModelsBuilder.Embedded/Building/Builder.cs index bba19c3b9f..82b1b0adec 100644 --- a/src/Umbraco.ModelsBuilder/Building/Builder.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Building/Builder.cs @@ -1,15 +1,9 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Umbraco.Core.Composing; -using Umbraco.Core.Configuration; -using Umbraco.ModelsBuilder.Configuration; +using Umbraco.ModelsBuilder.Embedded.Configuration; -namespace Umbraco.ModelsBuilder.Building +namespace Umbraco.ModelsBuilder.Embedded.Building { // NOTE // The idea was to have different types of builder, because I wanted to experiment with diff --git a/src/Umbraco.ModelsBuilder/Building/ModelsGenerator.cs b/src/Umbraco.ModelsBuilder.Embedded/Building/ModelsGenerator.cs similarity index 93% rename from src/Umbraco.ModelsBuilder/Building/ModelsGenerator.cs rename to src/Umbraco.ModelsBuilder.Embedded/Building/ModelsGenerator.cs index 6f6872e8ff..8a3bc5a5b5 100644 --- a/src/Umbraco.ModelsBuilder/Building/ModelsGenerator.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Building/ModelsGenerator.cs @@ -1,10 +1,8 @@ -using System; -using System.IO; +using System.IO; using System.Text; -using System.Web; -using Umbraco.ModelsBuilder.Configuration; +using Umbraco.ModelsBuilder.Embedded.Configuration; -namespace Umbraco.ModelsBuilder.Building +namespace Umbraco.ModelsBuilder.Embedded.Building { public class ModelsGenerator { diff --git a/src/Umbraco.ModelsBuilder/Building/PropertyModel.cs b/src/Umbraco.ModelsBuilder.Embedded/Building/PropertyModel.cs similarity index 97% rename from src/Umbraco.ModelsBuilder/Building/PropertyModel.cs rename to src/Umbraco.ModelsBuilder.Embedded/Building/PropertyModel.cs index 5f2545ec2a..af5445b175 100644 --- a/src/Umbraco.ModelsBuilder/Building/PropertyModel.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Building/PropertyModel.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; -namespace Umbraco.ModelsBuilder.Building +namespace Umbraco.ModelsBuilder.Embedded.Building { /// /// Represents a model property. diff --git a/src/Umbraco.ModelsBuilder/Building/TextBuilder.cs b/src/Umbraco.ModelsBuilder.Embedded/Building/TextBuilder.cs similarity index 99% rename from src/Umbraco.ModelsBuilder/Building/TextBuilder.cs rename to src/Umbraco.ModelsBuilder.Embedded/Building/TextBuilder.cs index 56349f16a8..d1190a0374 100644 --- a/src/Umbraco.ModelsBuilder/Building/TextBuilder.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Building/TextBuilder.cs @@ -3,11 +3,9 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Text.RegularExpressions; -using Umbraco.Core.Composing; -using Umbraco.Core.Configuration; -using Umbraco.ModelsBuilder.Configuration; +using Umbraco.ModelsBuilder.Embedded.Configuration; -namespace Umbraco.ModelsBuilder.Building +namespace Umbraco.ModelsBuilder.Embedded.Building { /// /// Implements a builder that works by writing text. diff --git a/src/Umbraco.ModelsBuilder/Building/TextHeaderWriter.cs b/src/Umbraco.ModelsBuilder.Embedded/Building/TextHeaderWriter.cs similarity index 95% rename from src/Umbraco.ModelsBuilder/Building/TextHeaderWriter.cs rename to src/Umbraco.ModelsBuilder.Embedded/Building/TextHeaderWriter.cs index 7244a2966d..a93df97806 100644 --- a/src/Umbraco.ModelsBuilder/Building/TextHeaderWriter.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Building/TextHeaderWriter.cs @@ -1,6 +1,6 @@ using System.Text; -namespace Umbraco.ModelsBuilder.Building +namespace Umbraco.ModelsBuilder.Embedded.Building { internal static class TextHeaderWriter { diff --git a/src/Umbraco.ModelsBuilder/Building/TypeModel.cs b/src/Umbraco.ModelsBuilder.Embedded/Building/TypeModel.cs similarity index 99% rename from src/Umbraco.ModelsBuilder/Building/TypeModel.cs rename to src/Umbraco.ModelsBuilder.Embedded/Building/TypeModel.cs index 06b5e7848a..95356cf3ff 100644 --- a/src/Umbraco.ModelsBuilder/Building/TypeModel.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Building/TypeModel.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Linq; using Umbraco.Core.Models.PublishedContent; -namespace Umbraco.ModelsBuilder.Building +namespace Umbraco.ModelsBuilder.Embedded.Building { /// /// Represents a model. diff --git a/src/Umbraco.ModelsBuilder/Building/TypeModelHasher.cs b/src/Umbraco.ModelsBuilder.Embedded/Building/TypeModelHasher.cs similarity index 96% rename from src/Umbraco.ModelsBuilder/Building/TypeModelHasher.cs rename to src/Umbraco.ModelsBuilder.Embedded/Building/TypeModelHasher.cs index 2099c09415..ff2893d097 100644 --- a/src/Umbraco.ModelsBuilder/Building/TypeModelHasher.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Building/TypeModelHasher.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Linq; -namespace Umbraco.ModelsBuilder.Building +namespace Umbraco.ModelsBuilder.Embedded.Building { internal class TypeModelHasher { diff --git a/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComponent.cs b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComponent.cs similarity index 98% rename from src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComponent.cs rename to src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComponent.cs index a68315606e..401a5a8931 100644 --- a/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComponent.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComponent.cs @@ -8,13 +8,13 @@ using Umbraco.Core.Composing; using Umbraco.Core.IO; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; -using Umbraco.ModelsBuilder.BackOffice; -using Umbraco.ModelsBuilder.Configuration; +using Umbraco.ModelsBuilder.Embedded.BackOffice; +using Umbraco.ModelsBuilder.Embedded.Configuration; using Umbraco.Web; using Umbraco.Web.JavaScript; using Umbraco.Web.Mvc; -namespace Umbraco.ModelsBuilder.Compose +namespace Umbraco.ModelsBuilder.Embedded.Compose { public class ModelsBuilderComponent : IComponent { diff --git a/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComposer.cs b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComposer.cs similarity index 94% rename from src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComposer.cs rename to src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComposer.cs index fdc4253490..8b3f8db534 100644 --- a/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComposer.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComposer.cs @@ -2,11 +2,11 @@ using Umbraco.Core; using Umbraco.Core.Composing; using Umbraco.Core.Models.PublishedContent; -using Umbraco.ModelsBuilder.Building; -using Umbraco.ModelsBuilder.Configuration; +using Umbraco.ModelsBuilder.Embedded.Building; +using Umbraco.ModelsBuilder.Embedded.Configuration; using Umbraco.Web.PublishedCache.NuCache; -namespace Umbraco.ModelsBuilder.Compose +namespace Umbraco.ModelsBuilder.Embedded.Compose { [ComposeBefore(typeof(NuCacheComposer))] [RuntimeLevel(MinLevel = RuntimeLevel.Run)] @@ -16,6 +16,8 @@ namespace Umbraco.ModelsBuilder.Compose { base.Compose(composition); + + composition.Register(Lifetime.Singleton); composition.Configs.Add(() => new ModelsBuilderConfig()); composition.RegisterUnique(); diff --git a/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderInitializer.cs b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderInitializer.cs similarity index 91% rename from src/Umbraco.ModelsBuilder/Compose/ModelsBuilderInitializer.cs rename to src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderInitializer.cs index 6eb8bbb328..a86669b135 100644 --- a/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderInitializer.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderInitializer.cs @@ -1,10 +1,10 @@ using System.Web; using System.Web.Compilation; -using Umbraco.ModelsBuilder.Compose; +using Umbraco.ModelsBuilder.Embedded.Compose; [assembly: PreApplicationStartMethod(typeof(ModelsBuilderInitializer), "Initialize")] -namespace Umbraco.ModelsBuilder.Compose +namespace Umbraco.ModelsBuilder.Embedded.Compose { public static class ModelsBuilderInitializer { diff --git a/src/Umbraco.ModelsBuilder/ConfigsExtensions.cs b/src/Umbraco.ModelsBuilder.Embedded/ConfigsExtensions.cs similarity index 87% rename from src/Umbraco.ModelsBuilder/ConfigsExtensions.cs rename to src/Umbraco.ModelsBuilder.Embedded/ConfigsExtensions.cs index 09314bc3f3..e2b91a8974 100644 --- a/src/Umbraco.ModelsBuilder/ConfigsExtensions.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/ConfigsExtensions.cs @@ -1,7 +1,7 @@ using Umbraco.Core.Configuration; -using Umbraco.ModelsBuilder.Configuration; +using Umbraco.ModelsBuilder.Embedded.Configuration; -namespace Umbraco.ModelsBuilder +namespace Umbraco.ModelsBuilder.Embedded { /// /// Provides extension methods for the class. diff --git a/src/Umbraco.ModelsBuilder/Configuration/IModelsBuilderConfig.cs b/src/Umbraco.ModelsBuilder.Embedded/Configuration/IModelsBuilderConfig.cs similarity index 86% rename from src/Umbraco.ModelsBuilder/Configuration/IModelsBuilderConfig.cs rename to src/Umbraco.ModelsBuilder.Embedded/Configuration/IModelsBuilderConfig.cs index 3bca389f2f..d8a81f0458 100644 --- a/src/Umbraco.ModelsBuilder/Configuration/IModelsBuilderConfig.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Configuration/IModelsBuilderConfig.cs @@ -1,4 +1,4 @@ -namespace Umbraco.ModelsBuilder.Configuration +namespace Umbraco.ModelsBuilder.Embedded.Configuration { public interface IModelsBuilderConfig { diff --git a/src/Umbraco.ModelsBuilder/Configuration/ModelsBuilderConfig.cs b/src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsBuilderConfig.cs similarity index 99% rename from src/Umbraco.ModelsBuilder/Configuration/ModelsBuilderConfig.cs rename to src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsBuilderConfig.cs index 48a4423054..881c4f97b8 100644 --- a/src/Umbraco.ModelsBuilder/Configuration/ModelsBuilderConfig.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsBuilderConfig.cs @@ -2,11 +2,10 @@ using System.Configuration; using System.IO; using System.Web.Configuration; -using System.Web.Hosting; using Umbraco.Core; using Umbraco.Core.IO; -namespace Umbraco.ModelsBuilder.Configuration +namespace Umbraco.ModelsBuilder.Embedded.Configuration { /// /// Represents the models builder configuration. diff --git a/src/Umbraco.ModelsBuilder/Configuration/ModelsMode.cs b/src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsMode.cs similarity index 95% rename from src/Umbraco.ModelsBuilder/Configuration/ModelsMode.cs rename to src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsMode.cs index 1f1d65f4f1..145508991a 100644 --- a/src/Umbraco.ModelsBuilder/Configuration/ModelsMode.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsMode.cs @@ -1,4 +1,4 @@ -namespace Umbraco.ModelsBuilder.Configuration +namespace Umbraco.ModelsBuilder.Embedded.Configuration { /// /// Defines the models generation modes. diff --git a/src/Umbraco.ModelsBuilder/Configuration/ModelsModeExtensions.cs b/src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsModeExtensions.cs similarity index 95% rename from src/Umbraco.ModelsBuilder/Configuration/ModelsModeExtensions.cs rename to src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsModeExtensions.cs index f40d7973b8..be638729ea 100644 --- a/src/Umbraco.ModelsBuilder/Configuration/ModelsModeExtensions.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsModeExtensions.cs @@ -1,4 +1,4 @@ -namespace Umbraco.ModelsBuilder.Configuration +namespace Umbraco.ModelsBuilder.Embedded.Configuration { /// /// Provides extensions for the enumeration. diff --git a/src/Umbraco.ModelsBuilder/HashCombiner.cs b/src/Umbraco.ModelsBuilder.Embedded/HashCombiner.cs similarity index 95% rename from src/Umbraco.ModelsBuilder/HashCombiner.cs rename to src/Umbraco.ModelsBuilder.Embedded/HashCombiner.cs index 51e02e93c1..1c1fca6f73 100644 --- a/src/Umbraco.ModelsBuilder/HashCombiner.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/HashCombiner.cs @@ -1,7 +1,7 @@ using System; using System.Globalization; -namespace Umbraco.ModelsBuilder +namespace Umbraco.ModelsBuilder.Embedded { // because, of course, it's internal in Umbraco // see also System.Web.Util.HashCodeCombiner diff --git a/src/Umbraco.ModelsBuilder/ImplementPropertyTypeAttribute.cs b/src/Umbraco.ModelsBuilder.Embedded/ImplementPropertyTypeAttribute.cs similarity index 92% rename from src/Umbraco.ModelsBuilder/ImplementPropertyTypeAttribute.cs rename to src/Umbraco.ModelsBuilder.Embedded/ImplementPropertyTypeAttribute.cs index a3dba740c1..0359c49654 100644 --- a/src/Umbraco.ModelsBuilder/ImplementPropertyTypeAttribute.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/ImplementPropertyTypeAttribute.cs @@ -1,6 +1,6 @@ using System; -namespace Umbraco.ModelsBuilder +namespace Umbraco.ModelsBuilder.Embedded { /// /// Indicates that a property implements a given property alias. diff --git a/src/Umbraco.ModelsBuilder/LiveModelsProvider.cs b/src/Umbraco.ModelsBuilder.Embedded/LiveModelsProvider.cs similarity index 95% rename from src/Umbraco.ModelsBuilder/LiveModelsProvider.cs rename to src/Umbraco.ModelsBuilder.Embedded/LiveModelsProvider.cs index 0dfe503fce..333181f27c 100644 --- a/src/Umbraco.ModelsBuilder/LiveModelsProvider.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/LiveModelsProvider.cs @@ -1,15 +1,12 @@ using System; using System.Threading; -using System.Web; using System.Web.Hosting; -using Umbraco.Core.Exceptions; -using Umbraco.Core.IO; using Umbraco.Core.Logging; -using Umbraco.ModelsBuilder.Building; -using Umbraco.ModelsBuilder.Configuration; +using Umbraco.ModelsBuilder.Embedded.Building; +using Umbraco.ModelsBuilder.Embedded.Configuration; using Umbraco.Web.Cache; -namespace Umbraco.ModelsBuilder +namespace Umbraco.ModelsBuilder.Embedded { // supports LiveAppData - but not PureLive public sealed class LiveModelsProvider diff --git a/src/Umbraco.ModelsBuilder/LiveModelsProviderModule.cs b/src/Umbraco.ModelsBuilder.Embedded/LiveModelsProviderModule.cs similarity index 95% rename from src/Umbraco.ModelsBuilder/LiveModelsProviderModule.cs rename to src/Umbraco.ModelsBuilder.Embedded/LiveModelsProviderModule.cs index 9d50c4ab42..37b39124c3 100644 --- a/src/Umbraco.ModelsBuilder/LiveModelsProviderModule.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/LiveModelsProviderModule.cs @@ -2,12 +2,12 @@ using System.Web; using Umbraco.Core; using Umbraco.Core.Composing; -using Umbraco.ModelsBuilder; +using Umbraco.ModelsBuilder.Embedded; // will install only if configuration says it needs to be installed [assembly: PreApplicationStartMethod(typeof(LiveModelsProviderModule), "Install")] -namespace Umbraco.ModelsBuilder +namespace Umbraco.ModelsBuilder.Embedded { // have to do this because it's the only way to subscribe to EndRequest, // module is installed by assembly attribute at the top of this file diff --git a/src/Umbraco.ModelsBuilder/ModelsBuilderAssemblyAttribute.cs b/src/Umbraco.ModelsBuilder.Embedded/ModelsBuilderAssemblyAttribute.cs similarity index 95% rename from src/Umbraco.ModelsBuilder/ModelsBuilderAssemblyAttribute.cs rename to src/Umbraco.ModelsBuilder.Embedded/ModelsBuilderAssemblyAttribute.cs index ed956852f8..7570c0b5b2 100644 --- a/src/Umbraco.ModelsBuilder/ModelsBuilderAssemblyAttribute.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/ModelsBuilderAssemblyAttribute.cs @@ -1,6 +1,6 @@ using System; -namespace Umbraco.ModelsBuilder +namespace Umbraco.ModelsBuilder.Embedded { /// /// Indicates that an Assembly is a Models Builder assembly. diff --git a/src/Umbraco.ModelsBuilder/ModelsGenerationError.cs b/src/Umbraco.ModelsBuilder.Embedded/ModelsGenerationError.cs similarity index 93% rename from src/Umbraco.ModelsBuilder/ModelsGenerationError.cs rename to src/Umbraco.ModelsBuilder.Embedded/ModelsGenerationError.cs index 8264fecc87..a692f633a5 100644 --- a/src/Umbraco.ModelsBuilder/ModelsGenerationError.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/ModelsGenerationError.cs @@ -1,10 +1,9 @@ using System; using System.IO; using System.Text; -using Umbraco.Core.Composing; -using Umbraco.ModelsBuilder.Configuration; +using Umbraco.ModelsBuilder.Embedded.Configuration; -namespace Umbraco.ModelsBuilder +namespace Umbraco.ModelsBuilder.Embedded { public sealed class ModelsGenerationError { diff --git a/src/Umbraco.ModelsBuilder/OutOfDateModelsStatus.cs b/src/Umbraco.ModelsBuilder.Embedded/OutOfDateModelsStatus.cs similarity index 94% rename from src/Umbraco.ModelsBuilder/OutOfDateModelsStatus.cs rename to src/Umbraco.ModelsBuilder.Embedded/OutOfDateModelsStatus.cs index 3b43ed771e..5425c31c77 100644 --- a/src/Umbraco.ModelsBuilder/OutOfDateModelsStatus.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/OutOfDateModelsStatus.cs @@ -1,9 +1,8 @@ using System.IO; -using Umbraco.Core.Composing; -using Umbraco.ModelsBuilder.Configuration; +using Umbraco.ModelsBuilder.Embedded.Configuration; using Umbraco.Web.Cache; -namespace Umbraco.ModelsBuilder +namespace Umbraco.ModelsBuilder.Embedded { public sealed class OutOfDateModelsStatus { diff --git a/src/Umbraco.ModelsBuilder/Properties/AssemblyInfo.cs b/src/Umbraco.ModelsBuilder.Embedded/Properties/AssemblyInfo.cs similarity index 100% rename from src/Umbraco.ModelsBuilder/Properties/AssemblyInfo.cs rename to src/Umbraco.ModelsBuilder.Embedded/Properties/AssemblyInfo.cs diff --git a/src/Umbraco.ModelsBuilder/PublishedElementExtensions.cs b/src/Umbraco.ModelsBuilder.Embedded/PublishedElementExtensions.cs similarity index 98% rename from src/Umbraco.ModelsBuilder/PublishedElementExtensions.cs rename to src/Umbraco.ModelsBuilder.Embedded/PublishedElementExtensions.cs index 1211c4fdad..29429ba74f 100644 --- a/src/Umbraco.ModelsBuilder/PublishedElementExtensions.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/PublishedElementExtensions.cs @@ -3,6 +3,7 @@ using System.Linq.Expressions; using System.Reflection; using Umbraco.Core.Models.PublishedContent; using Umbraco.ModelsBuilder; +using Umbraco.ModelsBuilder.Embedded; // same namespace as original Umbraco.Web PublishedElementExtensions // ReSharper disable once CheckNamespace diff --git a/src/Umbraco.ModelsBuilder/PublishedModelUtility.cs b/src/Umbraco.ModelsBuilder.Embedded/PublishedModelUtility.cs similarity index 98% rename from src/Umbraco.ModelsBuilder/PublishedModelUtility.cs rename to src/Umbraco.ModelsBuilder.Embedded/PublishedModelUtility.cs index 44af532a0b..8a6ed83ce9 100644 --- a/src/Umbraco.ModelsBuilder/PublishedModelUtility.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/PublishedModelUtility.cs @@ -1,11 +1,10 @@ using System; -using System.ComponentModel; using System.Linq; using System.Linq.Expressions; -using Umbraco.Web.Composing; using Umbraco.Core.Models.PublishedContent; +using Umbraco.Web.Composing; -namespace Umbraco.ModelsBuilder +namespace Umbraco.ModelsBuilder.Embedded { /// /// This is called from within the generated model classes diff --git a/src/Umbraco.ModelsBuilder/PureLiveModelFactory.cs b/src/Umbraco.ModelsBuilder.Embedded/PureLiveModelFactory.cs similarity index 99% rename from src/Umbraco.ModelsBuilder/PureLiveModelFactory.cs rename to src/Umbraco.ModelsBuilder.Embedded/PureLiveModelFactory.cs index eb82b4323f..8e8a19c729 100644 --- a/src/Umbraco.ModelsBuilder/PureLiveModelFactory.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/PureLiveModelFactory.cs @@ -13,15 +13,13 @@ using System.Web.Compilation; using System.Web.Hosting; using System.Web.WebPages.Razor; using Umbraco.Core; -using Umbraco.Core.Composing; using Umbraco.Core.Logging; using Umbraco.Core.Models.PublishedContent; -using Umbraco.Web.Cache; -using Umbraco.ModelsBuilder.Building; -using Umbraco.ModelsBuilder.Configuration; +using Umbraco.ModelsBuilder.Embedded.Building; +using Umbraco.ModelsBuilder.Embedded.Configuration; using File = System.IO.File; -namespace Umbraco.ModelsBuilder +namespace Umbraco.ModelsBuilder.Embedded { internal class PureLiveModelFactory : ILivePublishedModelFactory, IRegisteredObject { diff --git a/src/Umbraco.ModelsBuilder/ReferencedAssemblies.cs b/src/Umbraco.ModelsBuilder.Embedded/ReferencedAssemblies.cs similarity index 99% rename from src/Umbraco.ModelsBuilder/ReferencedAssemblies.cs rename to src/Umbraco.ModelsBuilder.Embedded/ReferencedAssemblies.cs index e0a20eb9d6..4ccc1afc93 100644 --- a/src/Umbraco.ModelsBuilder/ReferencedAssemblies.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/ReferencedAssemblies.cs @@ -6,7 +6,7 @@ using System.Web.Compilation; using System.Web.Hosting; using Umbraco.Core; -namespace Umbraco.ModelsBuilder +namespace Umbraco.ModelsBuilder.Embedded { internal static class ReferencedAssemblies { diff --git a/src/Umbraco.ModelsBuilder/TypeExtensions.cs b/src/Umbraco.ModelsBuilder.Embedded/TypeExtensions.cs similarity index 96% rename from src/Umbraco.ModelsBuilder/TypeExtensions.cs rename to src/Umbraco.ModelsBuilder.Embedded/TypeExtensions.cs index d3b3ff6b4e..1f270a80a6 100644 --- a/src/Umbraco.ModelsBuilder/TypeExtensions.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/TypeExtensions.cs @@ -1,6 +1,6 @@ using System; -namespace Umbraco.ModelsBuilder +namespace Umbraco.ModelsBuilder.Embedded { internal static class TypeExtensions { diff --git a/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj b/src/Umbraco.ModelsBuilder.Embedded/Umbraco.ModelsBuilder.Embedded.csproj similarity index 95% rename from src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj rename to src/Umbraco.ModelsBuilder.Embedded/Umbraco.ModelsBuilder.Embedded.csproj index 46ec0c7d2c..a9a925d9cf 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj +++ b/src/Umbraco.ModelsBuilder.Embedded/Umbraco.ModelsBuilder.Embedded.csproj @@ -7,8 +7,8 @@ {52AC0BA8-A60E-4E36-897B-E8B97A54ED1C} Library Properties - Umbraco.ModelsBuilder - Umbraco.ModelsBuilder + Umbraco.ModelsBuilder.Embedded + Umbraco.ModelsBuilder.Embedded v4.7.2 512 true @@ -30,7 +30,7 @@ TRACE prompt 4 - bin\Release\Umbraco.ModelsBuilder.xml + bin\Release\Umbraco.ModelsBuilder.Embedded.xml diff --git a/src/Umbraco.ModelsBuilder/UmbracoServices.cs b/src/Umbraco.ModelsBuilder.Embedded/UmbracoServices.cs similarity index 98% rename from src/Umbraco.ModelsBuilder/UmbracoServices.cs rename to src/Umbraco.ModelsBuilder.Embedded/UmbracoServices.cs index 725e5b18c4..cd9f4802f2 100644 --- a/src/Umbraco.ModelsBuilder/UmbracoServices.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/UmbracoServices.cs @@ -2,16 +2,14 @@ using System.Collections.Generic; using System.Linq; using Umbraco.Core; -using Umbraco.Core.Composing; using Umbraco.Core.Exceptions; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Services; using Umbraco.Core.Strings; -using Umbraco.ModelsBuilder.Building; -using Umbraco.ModelsBuilder.Configuration; +using Umbraco.ModelsBuilder.Embedded.Building; -namespace Umbraco.ModelsBuilder +namespace Umbraco.ModelsBuilder.Embedded { public sealed class UmbracoServices { diff --git a/src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs b/src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs index 99012643aa..fd381178cc 100644 --- a/src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs +++ b/src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs @@ -2,15 +2,14 @@ using System.Collections.Generic; using System.Linq; using System.Text; -using Microsoft.CodeAnalysis; using Moq; using NUnit.Framework; -using Umbraco.Core.Composing; using Umbraco.Core.Models.PublishedContent; -using Umbraco.ModelsBuilder.Building; -using Umbraco.ModelsBuilder.Configuration; +using Umbraco.ModelsBuilder.Embedded; +using Umbraco.ModelsBuilder.Embedded.Building; +using Umbraco.ModelsBuilder.Embedded.Configuration; -namespace Umbraco.ModelsBuilder.Tests +namespace Umbraco.Tests.ModelsBuilder { [TestFixture] public class BuilderTests @@ -246,7 +245,7 @@ namespace Umbraco.Web.PublishedModels { Alias = "prop2", ClrName = "Prop2", - ModelClrType = typeof(System.Text.StringBuilder), + ModelClrType = typeof(global::System.Text.StringBuilder), }); type1.Properties.Add(new PropertyModel { @@ -342,7 +341,7 @@ namespace Umbraco.Web.PublishedModels builder.Using.Add("Umbraco.ModelsBuilder.Tests"); builder.ModelsNamespaceForTests = "SomeRandomNamespace"; var sb = new StringBuilder(); - builder.WriteClrType(sb, typeof(System.Text.ASCIIEncoding)); + builder.WriteClrType(sb, typeof(global::System.Text.ASCIIEncoding)); // note - these assertions differ from the original tests in MB because in the embedded version, the result of Builder.IsAmbiguousSymbol is always true // which means global:: syntax will be applied to most things @@ -358,7 +357,7 @@ namespace Umbraco.Web.PublishedModels builder.Using.Add("Umbraco.ModelsBuilder.Tests"); builder.ModelsNamespaceForTests = "SomeBorkedNamespace"; var sb = new StringBuilder(); - builder.WriteClrType(sb, typeof(System.Text.ASCIIEncoding)); + builder.WriteClrType(sb, typeof(global::System.Text.ASCIIEncoding)); // note - these assertions differ from the original tests in MB because in the embedded version, the result of Builder.IsAmbiguousSymbol is always true // which means global:: syntax will be applied to most things @@ -417,7 +416,7 @@ namespace Umbraco.Web.PublishedModels public class Class1 { } } - // make it public to be ambiguous (see above) +// make it public to be ambiguous (see above) public class ASCIIEncoding { // can we handle nested types? @@ -425,9 +424,6 @@ namespace Umbraco.Web.PublishedModels } class BuilderTestsClass1 {} -} -namespace SomeBorkedNamespace -{ public class System { } } diff --git a/src/Umbraco.Tests/ModelsBuilder/ConfigTests.cs b/src/Umbraco.Tests/ModelsBuilder/ConfigTests.cs index 58215707f7..5e122ad0fa 100644 --- a/src/Umbraco.Tests/ModelsBuilder/ConfigTests.cs +++ b/src/Umbraco.Tests/ModelsBuilder/ConfigTests.cs @@ -1,8 +1,8 @@ using System.Configuration; using NUnit.Framework; -using Umbraco.ModelsBuilder.Configuration; +using Umbraco.ModelsBuilder.Embedded.Configuration; -namespace Umbraco.ModelsBuilder.Tests +namespace Umbraco.Tests.ModelsBuilder { [TestFixture] public class ModelsBuilderConfigTests diff --git a/src/Umbraco.Tests/ModelsBuilder/StringExtensions.cs b/src/Umbraco.Tests/ModelsBuilder/StringExtensions.cs index 13a256aa14..361d104911 100644 --- a/src/Umbraco.Tests/ModelsBuilder/StringExtensions.cs +++ b/src/Umbraco.Tests/ModelsBuilder/StringExtensions.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Umbraco.ModelsBuilder.Tests +namespace Umbraco.Tests.ModelsBuilder { public static class StringExtensions { diff --git a/src/Umbraco.Tests/ModelsBuilder/UmbracoApplicationTests.cs b/src/Umbraco.Tests/ModelsBuilder/UmbracoApplicationTests.cs index d6d44474c3..4d2ae0e6c6 100644 --- a/src/Umbraco.Tests/ModelsBuilder/UmbracoApplicationTests.cs +++ b/src/Umbraco.Tests/ModelsBuilder/UmbracoApplicationTests.cs @@ -1,12 +1,10 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using NUnit.Framework; -using Umbraco.ModelsBuilder.Building; +using Umbraco.ModelsBuilder.Embedded; +using Umbraco.ModelsBuilder.Embedded.Building; -namespace Umbraco.ModelsBuilder.Tests +namespace Umbraco.Tests.ModelsBuilder { [TestFixture] public class UmbracoApplicationTests diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index ebf081160b..83fff814a4 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -553,9 +553,9 @@ {31785BC3-256C-4613-B2F5-A1B0BDDED8C1} Umbraco.Core - + {52ac0ba8-a60e-4e36-897b-e8b97a54ed1c} - Umbraco.ModelsBuilder + Umbraco.ModelsBuilder.Embedded {651E1350-91B6-44B7-BD60-7207006D7003} diff --git a/src/Umbraco.Web.UI/App_Plugins/ModelsBuilder/modelsbuilder.controller.js b/src/Umbraco.Web.UI/App_Plugins/UmbModelsBuilder/modelsbuilder.controller.js similarity index 100% rename from src/Umbraco.Web.UI/App_Plugins/ModelsBuilder/modelsbuilder.controller.js rename to src/Umbraco.Web.UI/App_Plugins/UmbModelsBuilder/modelsbuilder.controller.js diff --git a/src/Umbraco.Web.UI/App_Plugins/ModelsBuilder/modelsbuilder.html b/src/Umbraco.Web.UI/App_Plugins/UmbModelsBuilder/modelsbuilder.html similarity index 100% rename from src/Umbraco.Web.UI/App_Plugins/ModelsBuilder/modelsbuilder.html rename to src/Umbraco.Web.UI/App_Plugins/UmbModelsBuilder/modelsbuilder.html diff --git a/src/Umbraco.Web.UI/App_Plugins/ModelsBuilder/modelsbuilder.resource.js b/src/Umbraco.Web.UI/App_Plugins/UmbModelsBuilder/modelsbuilder.resource.js similarity index 100% rename from src/Umbraco.Web.UI/App_Plugins/ModelsBuilder/modelsbuilder.resource.js rename to src/Umbraco.Web.UI/App_Plugins/UmbModelsBuilder/modelsbuilder.resource.js diff --git a/src/Umbraco.Web.UI/App_Plugins/ModelsBuilder/package.manifest b/src/Umbraco.Web.UI/App_Plugins/UmbModelsBuilder/package.manifest similarity index 100% rename from src/Umbraco.Web.UI/App_Plugins/ModelsBuilder/package.manifest rename to src/Umbraco.Web.UI/App_Plugins/UmbModelsBuilder/package.manifest diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 1db4fabbe1..99c1c994d9 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -12,7 +12,7 @@ {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} OnBuildSuccess true - 44319 + 44331 enabled disabled false @@ -118,10 +118,6 @@ Umbraco.Examine {07FBC26B-2927-4A22-8D96-D644C667FECC} - - {52ac0ba8-a60e-4e36-897b-e8b97a54ed1c} - Umbraco.ModelsBuilder - {651e1350-91b6-44b7-bd60-7207006d7003} Umbraco.Web @@ -144,9 +140,9 @@ True Settings.settings - - - + + + @@ -172,7 +168,7 @@ - + ClientDependency.config Designer diff --git a/src/umbraco.sln b/src/umbraco.sln index 1d5aa9a60e..ba9df633bb 100644 --- a/src/umbraco.sln +++ b/src/umbraco.sln @@ -1,6 +1,5 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 -VisualStudioVersion = 16.0.29009.5 VisualStudioVersion = 16.0.29209.152 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.Web.UI", "Umbraco.Web.UI\Umbraco.Web.UI.csproj", "{4C4C194C-B5E4-4991-8F87-4373E24CC19F}" @@ -103,7 +102,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "IssueTemplates", "IssueTemp ..\.github\ISSUE_TEMPLATE\5_Security_issue.md = ..\.github\ISSUE_TEMPLATE\5_Security_issue.md EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.ModelsBuilder", "Umbraco.ModelsBuilder\Umbraco.ModelsBuilder.csproj", "{52AC0BA8-A60E-4E36-897B-E8B97A54ED1C}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.ModelsBuilder.Embedded", "Umbraco.ModelsBuilder.Embedded\Umbraco.ModelsBuilder.Embedded.csproj", "{52AC0BA8-A60E-4E36-897B-E8B97A54ED1C}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution From cd8930871118af37948f72135b02fd1ceb67598e Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 29 Oct 2019 01:14:10 +1100 Subject: [PATCH 103/548] Ensures embedded MB is enabled on the UI proj, disables embedded MB if the legacy MB is detected --- ...cs => ModelsBuilderDashboardController.cs} | 4 +- .../DisableModelsBuilderManifestFilter.cs | 17 ++++++++ .../Compose/DisabledModelsBuilderComponent.cs | 30 ++++++++++++++ .../Compose/ModelsBuilderComponent.cs | 5 ++- .../Compose/ModelsBuilderComposer.cs | 40 +++++++++++++++++-- .../Umbraco.ModelsBuilder.Embedded.csproj | 4 +- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 4 ++ src/Umbraco.Web/Properties/AssemblyInfo.cs | 2 +- 8 files changed, 96 insertions(+), 10 deletions(-) rename src/Umbraco.ModelsBuilder.Embedded/BackOffice/{ModelsBuilderBackOfficeController.cs => ModelsBuilderDashboardController.cs} (95%) create mode 100644 src/Umbraco.ModelsBuilder.Embedded/Compose/DisableModelsBuilderManifestFilter.cs create mode 100644 src/Umbraco.ModelsBuilder.Embedded/Compose/DisabledModelsBuilderComponent.cs diff --git a/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ModelsBuilderBackOfficeController.cs b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ModelsBuilderDashboardController.cs similarity index 95% rename from src/Umbraco.ModelsBuilder.Embedded/BackOffice/ModelsBuilderBackOfficeController.cs rename to src/Umbraco.ModelsBuilder.Embedded/BackOffice/ModelsBuilderDashboardController.cs index 0842c3586a..44260d6719 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ModelsBuilderBackOfficeController.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ModelsBuilderDashboardController.cs @@ -20,7 +20,7 @@ namespace Umbraco.ModelsBuilder.Embedded.BackOffice /// global WebApi formatters being changed since this is always forced to only return Angular JSON Specific formats. /// [UmbracoApplicationAuthorize(Core.Constants.Applications.Settings)] - public class ModelsBuilderBackOfficeController : UmbracoAuthorizedJsonController + public class ModelsBuilderDashboardController : UmbracoAuthorizedJsonController { private readonly IModelsBuilderConfig _config; private readonly ModelsGenerator _modelGenerator; @@ -28,7 +28,7 @@ namespace Umbraco.ModelsBuilder.Embedded.BackOffice private readonly ModelsGenerationError _mbErrors; private readonly DashboardReport _dashboardReport; - public ModelsBuilderBackOfficeController(IModelsBuilderConfig config, ModelsGenerator modelsGenerator, OutOfDateModelsStatus outOfDateModels, ModelsGenerationError mbErrors) + public ModelsBuilderDashboardController(IModelsBuilderConfig config, ModelsGenerator modelsGenerator, OutOfDateModelsStatus outOfDateModels, ModelsGenerationError mbErrors) { //_umbracoServices = umbracoServices; _config = config; diff --git a/src/Umbraco.ModelsBuilder.Embedded/Compose/DisableModelsBuilderManifestFilter.cs b/src/Umbraco.ModelsBuilder.Embedded/Compose/DisableModelsBuilderManifestFilter.cs new file mode 100644 index 0000000000..617c5ced9b --- /dev/null +++ b/src/Umbraco.ModelsBuilder.Embedded/Compose/DisableModelsBuilderManifestFilter.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core.Manifest; + +namespace Umbraco.ModelsBuilder.Embedded.Compose +{ + /// + /// Removes the built in embedded models builder manifest from being loaded + /// + internal class DisableModelsBuilderManifestFilter : IManifestFilter + { + public void Filter(List manifests) + { + manifests.RemoveAll(x => x.Source.EndsWith("App_Plugins\\UmbModelsBuilder\\package.manifest", StringComparison.InvariantCultureIgnoreCase)); + } + } +} diff --git a/src/Umbraco.ModelsBuilder.Embedded/Compose/DisabledModelsBuilderComponent.cs b/src/Umbraco.ModelsBuilder.Embedded/Compose/DisabledModelsBuilderComponent.cs new file mode 100644 index 0000000000..c599785711 --- /dev/null +++ b/src/Umbraco.ModelsBuilder.Embedded/Compose/DisabledModelsBuilderComponent.cs @@ -0,0 +1,30 @@ +using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.ModelsBuilder.Embedded.BackOffice; +using Umbraco.Web.Features; + +namespace Umbraco.ModelsBuilder.Embedded.Compose +{ + /// + /// Special component used for when MB is disabled with the legacy MB is detected + /// + internal class DisabledModelsBuilderComponent : IComponent + { + private readonly UmbracoFeatures _features; + + public DisabledModelsBuilderComponent(UmbracoFeatures features) + { + _features = features; + } + + public void Initialize() + { + //disable the embedded dashboard controller + _features.Disabled.Controllers.Add(); + } + + public void Terminate() + { + } + } +} diff --git a/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComponent.cs b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComponent.cs index 401a5a8931..be73393994 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComponent.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComponent.cs @@ -16,7 +16,8 @@ using Umbraco.Web.Mvc; namespace Umbraco.ModelsBuilder.Embedded.Compose { - public class ModelsBuilderComponent : IComponent + + internal class ModelsBuilderComponent : IComponent { private readonly IModelsBuilderConfig _config; @@ -71,7 +72,7 @@ namespace Umbraco.ModelsBuilder.Embedded.Compose if (HttpContext.Current == null) throw new InvalidOperationException("HttpContext is null"); var urlHelper = new UrlHelper(new RequestContext(new HttpContextWrapper(HttpContext.Current), new RouteData())); - umbracoUrls["modelsBuilderBaseUrl"] = urlHelper.GetUmbracoApiServiceBaseUrl(controller => controller.BuildModels()); + umbracoUrls["modelsBuilderBaseUrl"] = urlHelper.GetUmbracoApiServiceBaseUrl(controller => controller.BuildModels()); umbracoPlugins["modelsBuilder"] = GetModelsBuilderSettings(); }; } diff --git a/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComposer.cs b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComposer.cs index 8b3f8db534..cf70adb6fe 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComposer.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComposer.cs @@ -1,23 +1,33 @@ using System.Linq; +using System.Reflection; using Umbraco.Core; +using Umbraco.Core.Logging; using Umbraco.Core.Composing; using Umbraco.Core.Models.PublishedContent; using Umbraco.ModelsBuilder.Embedded.Building; using Umbraco.ModelsBuilder.Embedded.Configuration; using Umbraco.Web.PublishedCache.NuCache; +using Umbraco.Web.Features; namespace Umbraco.ModelsBuilder.Embedded.Compose { + + [ComposeBefore(typeof(NuCacheComposer))] [RuntimeLevel(MinLevel = RuntimeLevel.Run)] - public sealed class ModelsBuilderComposer : ComponentComposer, ICoreComposer + public sealed class ModelsBuilderComposer : ICoreComposer { - public override void Compose(Composition composition) + public void Compose(Composition composition) { - base.Compose(composition); + var isLegacyModelsBuilderInstalled = IsLegacyModelsBuilderInstalled(); - + if (isLegacyModelsBuilderInstalled) + { + ComposeForLegacyModelsBuilder(composition); + return; + } + composition.Components().Append(); composition.Register(Lifetime.Singleton); composition.Configs.Add(() => new ModelsBuilderConfig()); composition.RegisterUnique(); @@ -31,6 +41,28 @@ namespace Umbraco.ModelsBuilder.Embedded.Compose ComposeForDefaultModelsFactory(composition); } + private static bool IsLegacyModelsBuilderInstalled() + { + Assembly legacyMbAssembly = null; + try + { + legacyMbAssembly = Assembly.Load("Umbraco.ModelsBuilder"); + } + catch (System.Exception) + { + //swallow exception, DLL must not be there + } + + return legacyMbAssembly != null; + } + + private void ComposeForLegacyModelsBuilder(Composition composition) + { + composition.Logger.Info("ModelsBuilder.Embedded is disabled, the legacy ModelsBuilder was detected."); + composition.Components().Append(); + composition.ManifestFilters().Append(); + } + private void ComposeForDefaultModelsFactory(Composition composition) { composition.RegisterUnique(factory => diff --git a/src/Umbraco.ModelsBuilder.Embedded/Umbraco.ModelsBuilder.Embedded.csproj b/src/Umbraco.ModelsBuilder.Embedded/Umbraco.ModelsBuilder.Embedded.csproj index a9a925d9cf..4c22566c05 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Umbraco.ModelsBuilder.Embedded.csproj +++ b/src/Umbraco.ModelsBuilder.Embedded/Umbraco.ModelsBuilder.Embedded.csproj @@ -55,6 +55,8 @@ + + @@ -71,7 +73,7 @@ - + diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 99c1c994d9..ceec3ee927 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -118,6 +118,10 @@ Umbraco.Examine {07FBC26B-2927-4A22-8D96-D644C667FECC} + + {52ac0ba8-a60e-4e36-897b-e8b97a54ed1c} + Umbraco.ModelsBuilder.Embedded + {651e1350-91b6-44b7-bd60-7207006d7003} Umbraco.Web diff --git a/src/Umbraco.Web/Properties/AssemblyInfo.cs b/src/Umbraco.Web/Properties/AssemblyInfo.cs index 9f5abb99b5..ce2cbf0282 100644 --- a/src/Umbraco.Web/Properties/AssemblyInfo.cs +++ b/src/Umbraco.Web/Properties/AssemblyInfo.cs @@ -12,7 +12,7 @@ using System.Runtime.InteropServices; // Umbraco Cms [assembly: InternalsVisibleTo("Umbraco.Web.UI")] - +[assembly: InternalsVisibleTo("Umbraco.ModelsBuilder.Embedded")] [assembly: InternalsVisibleTo("Umbraco.Tests")] [assembly: InternalsVisibleTo("Umbraco.Tests.Benchmarks")] From a7873fc01c9b6d8803b16be0a222eb5463b5c850 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Mon, 28 Oct 2019 20:30:18 +0100 Subject: [PATCH 104/548] Trigger an RTE resize after inserting images --- .../src/common/services/tinymce.service.js | 2 ++ 1 file changed, 2 insertions(+) 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 1351da081a..e7f5a02936 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 @@ -248,6 +248,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); } } From c089ecf7b9584d11fbc093ab76174250e957c14d Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Mon, 28 Oct 2019 22:31:17 +0100 Subject: [PATCH 105/548] Retain picked item state when navigating the treepicker --- .../components/umbminilistview.directive.js | 5 ++++- .../treepicker/treepicker.controller.js | 16 +++++++++++++--- .../infiniteeditors/treepicker/treepicker.html | 1 + 3 files changed, 18 insertions(+), 4 deletions(-) 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/views/common/infiniteeditors/treepicker/treepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js index 0ff6403761..0faee7bf90 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js @@ -58,6 +58,7 @@ angular.module("umbraco").controller("Umbraco.Editors.TreePickerController", vm.hideSearch = hideSearch; vm.closeMiniListView = closeMiniListView; vm.selectListViewNode = selectListViewNode; + vm.listViewItemsLoaded = listViewItemsLoaded; vm.submit = submit; vm.close = close; @@ -415,7 +416,7 @@ angular.module("umbraco").controller("Umbraco.Editors.TreePickerController", if ($scope.model.selection.length > 0) { for (var i = 0; $scope.model.selection.length > i; i++) { var selectedItem = $scope.model.selection[i]; - if (selectedItem.id === item.id) { + if (selectedItem.id === parseInt(item.id)) { found = true; foundIndex = i; } @@ -630,8 +631,8 @@ angular.module("umbraco").controller("Umbraco.Editors.TreePickerController", _.each(vm.searchInfo.results, function (result) { var exists = _.find($scope.model.selection, - function (selectedId) { - return result.id == selectedId; + function (item) { + return result.id == item.id; }); if (exists) { result.selected = true; @@ -651,6 +652,15 @@ angular.module("umbraco").controller("Umbraco.Editors.TreePickerController", vm.miniListView = undefined; } + function listViewItemsLoaded(items) { + var selectedIds = _.pluck($scope.model.selection, "id"); + _.each(items, function (item) { + if (_.contains(selectedIds, item.id)) { + item.selected = true; + } + }); + } + function submit(model) { if($scope.model.submit) { $scope.model.submit(model); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.html index 78c75f6f8d..ef042205aa 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.html @@ -75,6 +75,7 @@ start-node-id="vm.startNodeId" on-select="vm.selectListViewNode(node)" on-close="vm.closeMiniListView()" + on-items-loaded="vm.listViewItemsLoaded(items)" entity-type-filter="vm.filter"> From 9c2360ef4362fc958f045e1f3c9d520a20d6f9b8 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Mon, 28 Oct 2019 22:51:30 +0100 Subject: [PATCH 106/548] Add autofocus to treepicker search box --- .../directives/components/tree/umbtreesearchbox.directive.js | 3 ++- .../views/common/infiniteeditors/treepicker/treepicker.html | 3 ++- .../src/views/components/tree/umb-tree-search-box.html | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) 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/views/common/infiniteeditors/treepicker/treepicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.html index 78c75f6f8d..756a08df34 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.html @@ -37,7 +37,8 @@ search-from-name="{{vm.searchInfo.searchFromName}}" show-search="{{vm.searchInfo.showSearch}}" datatype-key="{{vm.searchInfo.dataTypeKey}}" - section="{{vm.section}}"> + section="{{vm.section}}" + auto-focus="true"> 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 + From 2c9939e51699d0c32bcaad9e4fc9cd27da8e6da1 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 29 Oct 2019 11:02:18 +1100 Subject: [PATCH 107/548] restores the MB enable flag --- build/NuSpecs/tools/Web.config.install.xdt | 1 + .../BackOffice/ContentTypeModelValidator.cs | 70 +----------------- .../ContentTypeModelValidatorBase.cs | 71 +++++++++++++++++++ .../BackOffice/DashboardReport.cs | 3 + .../BackOffice/MediaTypeModelValidator.cs | 17 +++++ .../BackOffice/MemberTypeModelValidator.cs | 17 +++++ .../ModelsBuilderDashboardController.cs | 2 +- .../Compose/ModelsBuilderComponent.cs | 5 +- .../Configuration/IModelsBuilderConfig.cs | 1 + .../Configuration/ModelsBuilderConfig.cs | 18 +++++ .../Configuration/ModelsMode.cs | 2 +- .../Umbraco.ModelsBuilder.Embedded.csproj | 3 + src/Umbraco.Web.UI/web.Template.config | 1 + src/Umbraco.Web/Editors/IEditorValidator.cs | 4 +- 14 files changed, 141 insertions(+), 74 deletions(-) create mode 100644 src/Umbraco.ModelsBuilder.Embedded/BackOffice/ContentTypeModelValidatorBase.cs create mode 100644 src/Umbraco.ModelsBuilder.Embedded/BackOffice/MediaTypeModelValidator.cs create mode 100644 src/Umbraco.ModelsBuilder.Embedded/BackOffice/MemberTypeModelValidator.cs diff --git a/build/NuSpecs/tools/Web.config.install.xdt b/build/NuSpecs/tools/Web.config.install.xdt index 6579717a60..2b79f95c70 100644 --- a/build/NuSpecs/tools/Web.config.install.xdt +++ b/build/NuSpecs/tools/Web.config.install.xdt @@ -14,6 +14,7 @@ + diff --git a/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ContentTypeModelValidator.cs b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ContentTypeModelValidator.cs index f351721e56..1fdb64c62a 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ContentTypeModelValidator.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ContentTypeModelValidator.cs @@ -1,9 +1,4 @@ -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Linq; -using Umbraco.Core; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Web.Editors; +using Umbraco.ModelsBuilder.Embedded.Configuration; using Umbraco.Web.Models.ContentEditing; namespace Umbraco.ModelsBuilder.Embedded.BackOffice @@ -12,70 +7,11 @@ namespace Umbraco.ModelsBuilder.Embedded.BackOffice /// Used to validate the aliases for the content type when MB is enabled to ensure that /// no illegal aliases are used ///
+ // ReSharper disable once UnusedMember.Global - This is typed scanned public class ContentTypeModelValidator : ContentTypeModelValidatorBase - { } - - /// - /// Used to validate the aliases for the content type when MB is enabled to ensure that - /// no illegal aliases are used - /// - public class MediaTypeModelValidator : ContentTypeModelValidatorBase - { } - - /// - /// Used to validate the aliases for the content type when MB is enabled to ensure that - /// no illegal aliases are used - /// - public class MemberTypeModelValidator : ContentTypeModelValidatorBase - { } - - public abstract class ContentTypeModelValidatorBase : EditorValidator - where TModel : ContentTypeSave - where TProperty : PropertyTypeBasic { - protected override IEnumerable Validate(TModel model) + public ContentTypeModelValidator(IModelsBuilderConfig config) : base(config) { - var properties = model.Groups.SelectMany(x => x.Properties) - .Where(x => x.Inherited == false) - .ToArray(); - - foreach (var prop in properties) - { - var propertyGroup = model.Groups.Single(x => x.Properties.Contains(prop)); - - if (model.Alias.ToLowerInvariant() == prop.Alias.ToLowerInvariant()) - yield return new ValidationResult(string.Format("With Models Builder enabled, you can't have a property with a the alias \"{0}\" when the content type alias is also \"{0}\".", prop.Alias), new[] - { - string.Format("Groups[{0}].Properties[{1}].Alias", model.Groups.IndexOf(propertyGroup), propertyGroup.Properties.IndexOf(prop)) - }); - - //we need to return the field name with an index so it's wired up correctly - var groupIndex = model.Groups.IndexOf(propertyGroup); - var propertyIndex = propertyGroup.Properties.IndexOf(prop); - - var validationResult = ValidateProperty(prop, groupIndex, propertyIndex); - if (validationResult != null) - yield return validationResult; - } - } - - private ValidationResult ValidateProperty(PropertyTypeBasic property, int groupIndex, int propertyIndex) - { - //don't let them match any properties or methods in IPublishedContent - //TODO: There are probably more! - var reservedProperties = typeof(IPublishedContent).GetProperties().Select(x => x.Name).ToArray(); - var reservedMethods = typeof(IPublishedContent).GetMethods().Select(x => x.Name).ToArray(); - - var alias = property.Alias; - - if (reservedProperties.InvariantContains(alias) || reservedMethods.InvariantContains(alias)) - return new ValidationResult( - string.Format("The alias {0} is a reserved term and cannot be used", alias), new[] - { - string.Format("Groups[{0}].Properties[{1}].Alias", groupIndex, propertyIndex) - }); - - return null; } } } diff --git a/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ContentTypeModelValidatorBase.cs b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ContentTypeModelValidatorBase.cs new file mode 100644 index 0000000000..15ca2cca24 --- /dev/null +++ b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ContentTypeModelValidatorBase.cs @@ -0,0 +1,71 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using Umbraco.Core; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.ModelsBuilder.Embedded.Configuration; +using Umbraco.Web.Editors; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.ModelsBuilder.Embedded.BackOffice +{ + public abstract class ContentTypeModelValidatorBase : EditorValidator + where TModel : ContentTypeSave + where TProperty : PropertyTypeBasic + { + private readonly IModelsBuilderConfig _config; + + public ContentTypeModelValidatorBase(IModelsBuilderConfig config) + { + _config = config; + } + + protected override IEnumerable Validate(TModel model) + { + //don't do anything if we're not enabled + if (!_config.Enable) yield break; + + var properties = model.Groups.SelectMany(x => x.Properties) + .Where(x => x.Inherited == false) + .ToArray(); + + foreach (var prop in properties) + { + var propertyGroup = model.Groups.Single(x => x.Properties.Contains(prop)); + + if (model.Alias.ToLowerInvariant() == prop.Alias.ToLowerInvariant()) + yield return new ValidationResult(string.Format("With Models Builder enabled, you can't have a property with a the alias \"{0}\" when the content type alias is also \"{0}\".", prop.Alias), new[] + { + $"Groups[{model.Groups.IndexOf(propertyGroup)}].Properties[{propertyGroup.Properties.IndexOf(prop)}].Alias" + }); + + //we need to return the field name with an index so it's wired up correctly + var groupIndex = model.Groups.IndexOf(propertyGroup); + var propertyIndex = propertyGroup.Properties.IndexOf(prop); + + var validationResult = ValidateProperty(prop, groupIndex, propertyIndex); + if (validationResult != null) + yield return validationResult; + } + } + + private ValidationResult ValidateProperty(PropertyTypeBasic property, int groupIndex, int propertyIndex) + { + //don't let them match any properties or methods in IPublishedContent + //TODO: There are probably more! + var reservedProperties = typeof(IPublishedContent).GetProperties().Select(x => x.Name).ToArray(); + var reservedMethods = typeof(IPublishedContent).GetMethods().Select(x => x.Name).ToArray(); + + var alias = property.Alias; + + if (reservedProperties.InvariantContains(alias) || reservedMethods.InvariantContains(alias)) + return new ValidationResult( + $"The alias {alias} is a reserved term and cannot be used", new[] + { + $"Groups[{groupIndex}].Properties[{propertyIndex}].Alias" + }); + + return null; + } + } +} diff --git a/src/Umbraco.ModelsBuilder.Embedded/BackOffice/DashboardReport.cs b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/DashboardReport.cs index e4c45aaf3e..25ddc838e8 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/BackOffice/DashboardReport.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/DashboardReport.cs @@ -24,6 +24,9 @@ namespace Umbraco.ModelsBuilder.Embedded.BackOffice public string Text() { + if (!_config.Enable) + return "Version: " + ApiVersion.Current.Version + "
 
ModelsBuilder is disabled
(the .Enable key is missing, or its value is not 'true')."; + var sb = new StringBuilder(); sb.Append("Version: "); diff --git a/src/Umbraco.ModelsBuilder.Embedded/BackOffice/MediaTypeModelValidator.cs b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/MediaTypeModelValidator.cs new file mode 100644 index 0000000000..9dc1ea6c20 --- /dev/null +++ b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/MediaTypeModelValidator.cs @@ -0,0 +1,17 @@ +using Umbraco.ModelsBuilder.Embedded.Configuration; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.ModelsBuilder.Embedded.BackOffice +{ + /// + /// Used to validate the aliases for the content type when MB is enabled to ensure that + /// no illegal aliases are used + /// + // ReSharper disable once UnusedMember.Global - This is typed scanned + public class MediaTypeModelValidator : ContentTypeModelValidatorBase + { + public MediaTypeModelValidator(IModelsBuilderConfig config) : base(config) + { + } + } +} diff --git a/src/Umbraco.ModelsBuilder.Embedded/BackOffice/MemberTypeModelValidator.cs b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/MemberTypeModelValidator.cs new file mode 100644 index 0000000000..8d0a98eeab --- /dev/null +++ b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/MemberTypeModelValidator.cs @@ -0,0 +1,17 @@ +using Umbraco.ModelsBuilder.Embedded.Configuration; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.ModelsBuilder.Embedded.BackOffice +{ + /// + /// Used to validate the aliases for the content type when MB is enabled to ensure that + /// no illegal aliases are used + /// + // ReSharper disable once UnusedMember.Global - This is typed scanned + public class MemberTypeModelValidator : ContentTypeModelValidatorBase + { + public MemberTypeModelValidator(IModelsBuilderConfig config) : base(config) + { + } + } +} diff --git a/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ModelsBuilderDashboardController.cs b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ModelsBuilderDashboardController.cs index 44260d6719..1d9de265e9 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ModelsBuilderDashboardController.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ModelsBuilderDashboardController.cs @@ -97,7 +97,7 @@ namespace Umbraco.ModelsBuilder.Embedded.BackOffice { return new Dashboard { - Enable = true, + Enable = _config.Enable, Text = _dashboardReport.Text(), CanGenerate = _dashboardReport.CanGenerate(), OutOfDateModels = _dashboardReport.AreModelsOutOfDate(), diff --git a/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComponent.cs b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComponent.cs index be73393994..0e41c9ac62 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComponent.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComponent.cs @@ -39,7 +39,8 @@ namespace Umbraco.ModelsBuilder.Embedded.Compose ContentModelBinder.ModelBindingException += ContentModelBinder_ModelBindingException; - FileService.SavingTemplate += FileService_SavingTemplate; + if (_config.Enable) + FileService.SavingTemplate += FileService_SavingTemplate; if (_config.ModelsMode.IsLiveNotPure()) _liveModelsProvider.Install(); @@ -81,7 +82,7 @@ namespace Umbraco.ModelsBuilder.Embedded.Compose { var settings = new Dictionary { - {"enabled", true} + {"enabled", _config.Enable} }; return settings; diff --git a/src/Umbraco.ModelsBuilder.Embedded/Configuration/IModelsBuilderConfig.cs b/src/Umbraco.ModelsBuilder.Embedded/Configuration/IModelsBuilderConfig.cs index d8a81f0458..7e96aec60e 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Configuration/IModelsBuilderConfig.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Configuration/IModelsBuilderConfig.cs @@ -2,6 +2,7 @@ { public interface IModelsBuilderConfig { + bool Enable { get; } bool AcceptUnsafeModelsDirectory { get; } int DebugLevel { get; } bool EnableFactory { get; } diff --git a/src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsBuilderConfig.cs b/src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsBuilderConfig.cs index 881c4f97b8..c6bccdcf87 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsBuilderConfig.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsBuilderConfig.cs @@ -22,11 +22,18 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration { 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"; + // ensure defaults are initialized for tests ModelsNamespace = DefaultModelsNamespace; ModelsDirectory = IOHelper.MapPath(DefaultModelsDirectory); DebugLevel = 0; + // stop here, everything is false + if (!Enable) return; + // mode var modelsMode = ConfigurationManager.AppSettings[prefix + "ModelsMode"]; if (!string.IsNullOrWhiteSpace(modelsMode)) @@ -94,6 +101,7 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration /// Initializes a new instance of the class. ///
public ModelsBuilderConfig( + bool enable = false, ModelsMode modelsMode = ModelsMode.Nothing, string modelsNamespace = null, bool enableFactory = true, @@ -102,6 +110,7 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration bool acceptUnsafeModelsDirectory = false, int debugLevel = 0) { + Enable = enable; ModelsMode = modelsMode; ModelsNamespace = string.IsNullOrWhiteSpace(modelsNamespace) ? DefaultModelsNamespace : modelsNamespace; @@ -143,6 +152,15 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration throw new ConfigurationErrorsException($"Invalid models directory \"{config}\"."); } + /// + /// Gets a value indicating whether the whole models experience is enabled. + /// + /// + /// If this is false then absolutely nothing happens. + /// Default value is false which means that unless we have this setting, nothing happens. + /// + public bool Enable { get; } + /// /// Gets the models mode. /// diff --git a/src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsMode.cs b/src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsMode.cs index 145508991a..e0286fdab1 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsMode.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsMode.cs @@ -8,7 +8,7 @@ /// /// Do not generate models. /// - Nothing = 0, // default value //TODO: This doesn't make sense since we cannot actualy disable MB since Umbraco would die + Nothing = 0, // default value /// /// Generate models in memory. diff --git a/src/Umbraco.ModelsBuilder.Embedded/Umbraco.ModelsBuilder.Embedded.csproj b/src/Umbraco.ModelsBuilder.Embedded/Umbraco.ModelsBuilder.Embedded.csproj index 4c22566c05..c09703ecfb 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Umbraco.ModelsBuilder.Embedded.csproj +++ b/src/Umbraco.ModelsBuilder.Embedded/Umbraco.ModelsBuilder.Embedded.csproj @@ -50,6 +50,9 @@ Properties\SolutionInfo.cs + + + diff --git a/src/Umbraco.Web.UI/web.Template.config b/src/Umbraco.Web.UI/web.Template.config index 8759fc6459..f0abbfde52 100644 --- a/src/Umbraco.Web.UI/web.Template.config +++ b/src/Umbraco.Web.UI/web.Template.config @@ -47,6 +47,7 @@ + diff --git a/src/Umbraco.Web/Editors/IEditorValidator.cs b/src/Umbraco.Web/Editors/IEditorValidator.cs index d469d9d9eb..2d655e3506 100644 --- a/src/Umbraco.Web/Editors/IEditorValidator.cs +++ b/src/Umbraco.Web/Editors/IEditorValidator.cs @@ -14,9 +14,7 @@ namespace Umbraco.Web.Editors // initialized with all IEditorValidator instances // // validation is used exclusively in ContentTypeControllerBase - // the whole thing is internal at the moment, never released - // and, there are no IEditorValidator implementation in Core - // so... this all mechanism is basically useless + // currently the only implementations are for Models Builder. /// /// Provides a general object validator. From 0aa8711988d7040fb5ea73acb014323e3d53423d Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 29 Oct 2019 11:38:06 +1100 Subject: [PATCH 108/548] Ensures that the MB version is added to the models hash to ensure that if the MB version changes, the purelive models are regenerated --- src/Umbraco.ModelsBuilder.Embedded/Building/Builder.cs | 2 +- .../Building/TypeModelHasher.cs | 3 +++ src/Umbraco.ModelsBuilder.Embedded/UmbracoServices.cs | 4 ++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.ModelsBuilder.Embedded/Building/Builder.cs b/src/Umbraco.ModelsBuilder.Embedded/Building/Builder.cs index 82b1b0adec..ffd56d4312 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Building/Builder.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Building/Builder.cs @@ -32,7 +32,7 @@ namespace Umbraco.ModelsBuilder.Embedded.Building "Umbraco.Core.Models", "Umbraco.Core.Models.PublishedContent", "Umbraco.Web", - "Umbraco.ModelsBuilder" + "Umbraco.ModelsBuilder.Embedded" }; /// diff --git a/src/Umbraco.ModelsBuilder.Embedded/Building/TypeModelHasher.cs b/src/Umbraco.ModelsBuilder.Embedded/Building/TypeModelHasher.cs index ff2893d097..2f14bec875 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Building/TypeModelHasher.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Building/TypeModelHasher.cs @@ -35,6 +35,9 @@ namespace Umbraco.ModelsBuilder.Embedded.Building } } + // Include the MB version in the hash so that if the MB version changes, models are rebuilt + hash.Add(ApiVersion.Current.Version.ToString()); + return hash.GetCombinedHashCode(); } } diff --git a/src/Umbraco.ModelsBuilder.Embedded/UmbracoServices.cs b/src/Umbraco.ModelsBuilder.Embedded/UmbracoServices.cs index cd9f4802f2..5ede5f45e9 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/UmbracoServices.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/UmbracoServices.cs @@ -32,6 +32,10 @@ namespace Umbraco.ModelsBuilder.Embedded { var types = new List(); + // TODO: this will require 3 rather large SQL queries on startup in PureLive. I know that these will be cached after lookup but it will slow + // down startup time ... BUT these queries are also used in NuCache on startup so we can't really avoid them. Maybe one day we can + // load all of these in in one query and still have them cached per service, and/or somehow improve the perf of these since they are used on startup + // in more than one place. types.AddRange(GetTypes(PublishedItemType.Content, _contentTypeService.GetAll().Cast().ToArray())); types.AddRange(GetTypes(PublishedItemType.Media, _mediaTypeService.GetAll().Cast().ToArray())); types.AddRange(GetTypes(PublishedItemType.Member, _memberTypeService.GetAll().Cast().ToArray())); From 33cd7f6a982f4c76ab186d5fb6b4c43000d855ea Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 29 Oct 2019 11:55:16 +1100 Subject: [PATCH 109/548] oops, added the internal visible to attribute to the wrong file --- src/SolutionInfo.cs | 3 --- src/Umbraco.ModelsBuilder.Embedded/Properties/AssemblyInfo.cs | 3 +++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index 93921e07a2..bf3a271d32 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -1,6 +1,5 @@ using System.Reflection; using System.Resources; -using System.Runtime.CompilerServices; [assembly: AssemblyCompany("Umbraco")] [assembly: AssemblyCopyright("Copyright © Umbraco 2019")] @@ -21,5 +20,3 @@ using System.Runtime.CompilerServices; // these are FYI and changed automatically [assembly: AssemblyFileVersion("8.3.0")] [assembly: AssemblyInformationalVersion("8.3.0")] - -[assembly: InternalsVisibleTo("Umbraco.Tests")] diff --git a/src/Umbraco.ModelsBuilder.Embedded/Properties/AssemblyInfo.cs b/src/Umbraco.ModelsBuilder.Embedded/Properties/AssemblyInfo.cs index b576807bb7..68c149adde 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Properties/AssemblyInfo.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Properties/AssemblyInfo.cs @@ -1,4 +1,5 @@ using System.Reflection; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; [assembly: AssemblyTitle("Umbraco.ModelsBuilder")] @@ -8,3 +9,5 @@ using System.Runtime.InteropServices; [assembly: ComVisible(false)] [assembly: Guid("52ac0ba8-a60e-4e36-897b-e8b97a54ed1c")] + +[assembly: InternalsVisibleTo("Umbraco.Tests")] From df2a65424eb6e21ce34d5b200fa12bb0828f758b Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 29 Oct 2019 12:16:56 +1100 Subject: [PATCH 110/548] notes --- src/Umbraco.ModelsBuilder.Embedded/LiveModelsProviderModule.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.ModelsBuilder.Embedded/LiveModelsProviderModule.cs b/src/Umbraco.ModelsBuilder.Embedded/LiveModelsProviderModule.cs index 37b39124c3..678ff241b0 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/LiveModelsProviderModule.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/LiveModelsProviderModule.cs @@ -27,7 +27,7 @@ namespace Umbraco.ModelsBuilder.Embedded // here we're using "Current." since we're in a module, it is possible in a round about way to inject into a module but for now we'll just use Current if (_liveModelsProvider == null) - _liveModelsProvider = Current.Factory.TryGetInstance(); // will be null in upgrade mode + _liveModelsProvider = Current.Factory.TryGetInstance(); // will be null in upgrade mode or if embedded MB is disabled if (_liveModelsProvider?.IsEnabled ?? false) _liveModelsProvider.GenerateModelsIfRequested(sender, e); From d69356cc1293239709172b892b1541e47e1e0636 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 29 Oct 2019 13:29:22 +1100 Subject: [PATCH 111/548] Adds unit test and couple methods to relation service and improves the relation service lookups for entities. --- src/Umbraco.Core/Services/IRelationService.cs | 16 + .../Services/Implement/RelationService.cs | 370 +++++------------- .../Services/RelationServiceTests.cs | 33 ++ 3 files changed, 155 insertions(+), 264 deletions(-) diff --git a/src/Umbraco.Core/Services/IRelationService.cs b/src/Umbraco.Core/Services/IRelationService.cs index 0f339688de..49fa21af2b 100644 --- a/src/Umbraco.Core/Services/IRelationService.cs +++ b/src/Umbraco.Core/Services/IRelationService.cs @@ -70,6 +70,14 @@ namespace Umbraco.Core.Services /// An enumerable list of objects IEnumerable GetByParentId(int id); + /// + /// 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 /// @@ -92,6 +100,14 @@ namespace Umbraco.Core.Services /// An enumerable list of objects IEnumerable GetByChildId(int id); + /// + /// 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 /// diff --git a/src/Umbraco.Core/Services/Implement/RelationService.cs b/src/Umbraco.Core/Services/Implement/RelationService.cs index bf1e7bf309..490f36e7c7 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(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,33 +207,21 @@ namespace Umbraco.Core.Services.Implement } } - /// - /// Gets the Child object from a Relation as an - /// - /// Relation to retrieve child object from - /// An + /// public IUmbracoEntity GetChildEntityFromRelation(IRelation relation) { 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.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.ChildObjectType); @@ -319,43 +233,37 @@ 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) { - //TODO: Argh! N+1 + // 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 relation in relations) + foreach (var groupedRelations in relations.GroupBy(x => ObjectTypes.GetUmbracoObjectType(x.ChildObjectType))) { - var objectType = ObjectTypes.GetUmbracoObjectType(relation.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) { - //TODO: Argh! N+1 + // 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 relation in relations) + foreach (var groupedRelations in relations.GroupBy(x => ObjectTypes.GetUmbracoObjectType(x.ParentObjectType))) { - var objectType = ObjectTypes.GetUmbracoObjectType(relation.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> GetEntitiesFromRelations(IEnumerable relations) { //TODO: Argh! N+1 @@ -372,13 +280,7 @@ 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 @@ -406,25 +308,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); @@ -434,13 +324,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); @@ -450,11 +334,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)) @@ -464,11 +344,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)) @@ -478,12 +354,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)) @@ -493,13 +364,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); @@ -510,13 +375,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)) @@ -526,34 +385,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()) @@ -572,10 +417,7 @@ namespace Umbraco.Core.Services.Implement } } - /// - /// Saves a - /// - /// RelationType to Save + /// public void Save(IRelationType relationType) { using (var scope = ScopeProvider.CreateScope()) @@ -594,10 +436,7 @@ namespace Umbraco.Core.Services.Implement } } - /// - /// Deletes a - /// - /// Relation to Delete + /// public void Delete(IRelation relation) { using (var scope = ScopeProvider.CreateScope()) @@ -616,10 +455,7 @@ namespace Umbraco.Core.Services.Implement } } - /// - /// Deletes a - /// - /// RelationType to Delete + /// public void Delete(IRelationType relationType) { using (var scope = ScopeProvider.CreateScope()) @@ -638,10 +474,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(); @@ -663,6 +496,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.Tests/Services/RelationServiceTests.cs b/src/Umbraco.Tests/Services/RelationServiceTests.cs index a39a9c838b..0357c9e408 100644 --- a/src/Umbraco.Tests/Services/RelationServiceTests.cs +++ b/src/Umbraco.Tests/Services/RelationServiceTests.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Threading; using NUnit.Framework; using Umbraco.Core; @@ -14,6 +15,38 @@ namespace Umbraco.Tests.Services [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] public class RelationServiceTests : TestWithSomeContentBase { + [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() { From 5ddc961df3cb9a9327c6d4c503c4542ab2637f4f Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 29 Oct 2019 08:52:02 +0100 Subject: [PATCH 112/548] AB3327 - Nested Content references --- .../NestedContentPropertyEditor.cs | 41 ++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs index 7e91a3af79..ab26290812 100644 --- a/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs @@ -58,7 +58,7 @@ namespace Umbraco.Web.PropertyEditors protected override IDataValueEditor CreateValueEditor() => new NestedContentPropertyValueEditor(Attribute, PropertyEditors); - internal class NestedContentPropertyValueEditor : DataValueEditor + internal class NestedContentPropertyValueEditor : DataValueEditor, IDataValueReference { private readonly PropertyEditorCollection _propertyEditors; @@ -266,6 +266,45 @@ namespace Umbraco.Web.PropertyEditors return JsonConvert.SerializeObject(value); } + public IEnumerable GetReferences(object value) + { + var rawJson = value == null ? string.Empty : value is string str ? str : value.ToString(); + + var result = new List(); + + var list = JsonConvert.DeserializeObject>(rawJson); + if (list == null) + return result; + + foreach (var o in list) + { + 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) + { + var propType = contentType.CompositionPropertyTypes.FirstOrDefault(x => x.Alias == propAlias); + if (propType == null) continue; + + var propEditor = _propertyEditors[propType.PropertyEditorAlias]; + + var valueEditor = propEditor?.GetValueEditor(); + if (!(valueEditor is IDataValueReference reference)) continue; + + var val = propValues[propAlias]?.ToString(); + + var refs = reference.GetReferences(val); + + result.AddRange(refs); + } + } + return result; + } + #endregion } From 8d4de3de55756f8ff2c07c4980beb664009783ee Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 29 Oct 2019 08:52:47 +0100 Subject: [PATCH 113/548] AB3326 - Handle empty strings from nested content --- src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs index ffa3b8c074..be1a9b2691 100644 --- a/src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs @@ -41,9 +41,9 @@ namespace Umbraco.Web.PropertyEditors public IEnumerable GetReferences(object value) { - if (value == null) yield break; + var asString = value is string str ? str : value?.ToString(); - var asString = value is string str ? str : value.ToString(); + if (string.IsNullOrEmpty(asString)) yield break; yield return new UmbracoEntityReference(Udi.Parse(asString)); } From 34a0210d9da7a38bcfd7eaf93ba197056740dc21 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 29 Oct 2019 08:53:00 +0100 Subject: [PATCH 114/548] AB3329 - Handle empty strings from nested content --- .../PropertyEditors/ContentPickerPropertyEditor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web/PropertyEditors/ContentPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/ContentPickerPropertyEditor.cs index 0e6eaa9254..c09b3b6930 100644 --- a/src/Umbraco.Web/PropertyEditors/ContentPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ContentPickerPropertyEditor.cs @@ -37,9 +37,9 @@ namespace Umbraco.Web.PropertyEditors public IEnumerable GetReferences(object value) { - if (value == null) yield break; + var asString = value is string str ? str : value?.ToString(); - var asString = value is string str ? str : value.ToString(); + if (string.IsNullOrEmpty(asString)) yield break; yield return new UmbracoEntityReference(Udi.Parse(asString)); } From da698a9a853eebc109bbbb07f17b4aa3fce4e2c4 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 29 Oct 2019 09:57:32 +0100 Subject: [PATCH 115/548] AB2462 - Code review fixes --- .../Migrations/Install/DatabaseDataCreator.cs | 15 ++++++++++----- .../Upgrade/V_8_5_0/AddNewRelationTypes.cs | 6 ++++-- src/Umbraco.Core/Models/Relation.cs | 4 +--- .../Repositories/Implement/RelationRepository.cs | 12 ++++++++++-- src/Umbraco.Core/PropertyEditors/DataEditor.cs | 4 ++-- 5 files changed, 27 insertions(+), 14 deletions(-) diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs index 9a96810f23..888ff9a632 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs @@ -310,24 +310,29 @@ 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 = (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 = 5, Alias = Constants.Conventions.RelationTypes.RelatedDocumentAlias, ChildObjectType = null, ParentObjectType = null, Dual = false, Name = Constants.Conventions.RelationTypes.RelatedDocumentName }; - relationType.UniqueId = (relationType.Alias + "____" + relationType.Name).ToGuid(); + 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() { // on install, initialize the umbraco migration plan with the final state diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_5_0/AddNewRelationTypes.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_5_0/AddNewRelationTypes.cs index 88c6c43c46..40e541f04c 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_5_0/AddNewRelationTypes.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_5_0/AddNewRelationTypes.cs @@ -1,4 +1,6 @@ -namespace Umbraco.Core.Migrations.Upgrade.V_8_5_0 +using Umbraco.Core.Migrations.Install; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_5_0 { /// /// Ensures the new relation types are created @@ -22,7 +24,7 @@ private void CreateRelation(string alias, string name) { - var uniqueId = (alias + "____" + name).ToGuid(); //this is the same as how it installs so everything is consistent + 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/Models/Relation.cs b/src/Umbraco.Core/Models/Relation.cs index 8e3a073a96..7afa476226 100644 --- a/src/Umbraco.Core/Models/Relation.cs +++ b/src/Umbraco.Core/Models/Relation.cs @@ -22,8 +22,6 @@ namespace Umbraco.Core.Models /// /// /// - /// - /// /// public Relation(int parentId, int childId, IRelationType relationType) { @@ -48,7 +46,7 @@ namespace Umbraco.Core.Models ParentObjectType = parentObjectType; ChildObjectType = childObjectType; } - + /// /// Gets or sets the Parent Id of the Relation (Source) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs index ab6b02afb4..e6c4c6fd7e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs @@ -175,8 +175,16 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { var nodes = Database.Fetch(Sql().Select().From().Where(x => x.NodeId == entity.ChildId || x.NodeId == entity.ParentId)) .ToDictionary(x => x.NodeId, x => x.NodeObjectType); - entity.ParentObjectType = nodes[entity.ParentId].Value; - entity.ChildObjectType = nodes[entity.ChildId].Value; + + if(nodes.TryGetValue(entity.ParentId, out var parentObjectType)) + { + entity.ParentObjectType = parentObjectType.GetValueOrDefault(); + } + + if(nodes.TryGetValue(entity.ChildId, out var childObjectType)) + { + entity.ChildObjectType = childObjectType.GetValueOrDefault(); + } } } } diff --git a/src/Umbraco.Core/PropertyEditors/DataEditor.cs b/src/Umbraco.Core/PropertyEditors/DataEditor.cs index 3f5f6afa4f..7dc260e4c7 100644 --- a/src/Umbraco.Core/PropertyEditors/DataEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/DataEditor.cs @@ -19,7 +19,7 @@ namespace Umbraco.Core.PropertyEditors public class DataEditor : IDataEditor { private IDictionary _defaultConfiguration; - private IDataValueEditor _nonConfigured; + private IDataValueEditor _dataValueEditor; /// /// Initializes a new instance of the class. @@ -91,7 +91,7 @@ namespace Umbraco.Core.PropertyEditors /// simple enough for now. /// // TODO: point of that one? shouldn't we always configure? - public IDataValueEditor GetValueEditor() => ExplicitValueEditor ?? (_nonConfigured ?? (_nonConfigured = CreateValueEditor())); + public IDataValueEditor GetValueEditor() => ExplicitValueEditor ?? (_dataValueEditor ?? (_dataValueEditor = CreateValueEditor())); /// /// From 63cf79a758fe739d2e97af918262fd9bf29fb504 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 29 Oct 2019 11:01:44 +0100 Subject: [PATCH 116/548] AB3348 - Clean up unused usings, removed unused method... --- .../BackOffice/DashboardReport.cs | 1 - .../ModelsBuilderBackOfficeController.cs | 3 --- src/Umbraco.ModelsBuilder/Building/Builder.cs | 8 +------- .../Building/ModelsGenerator.cs | 4 +--- .../Building/TextBuilder.cs | 3 +-- .../ConfigsExtensions.cs | 2 +- .../Configuration/ModelsBuilderConfig.cs | 1 - .../LiveModelsProvider.cs | 3 --- .../ModelsGenerationError.cs | 1 - .../OutOfDateModelsStatus.cs | 1 - .../PublishedModelUtility.cs | 1 - .../PureLiveModelFactory.cs | 2 -- .../ReferencedAssemblies.cs | 20 ------------------- src/Umbraco.ModelsBuilder/UmbracoServices.cs | 2 -- 14 files changed, 4 insertions(+), 48 deletions(-) diff --git a/src/Umbraco.ModelsBuilder/BackOffice/DashboardReport.cs b/src/Umbraco.ModelsBuilder/BackOffice/DashboardReport.cs index 64fb217fce..a43ebdb927 100644 --- a/src/Umbraco.ModelsBuilder/BackOffice/DashboardReport.cs +++ b/src/Umbraco.ModelsBuilder/BackOffice/DashboardReport.cs @@ -1,5 +1,4 @@ using System.Text; -using Umbraco.Core.Composing; using Umbraco.ModelsBuilder.Configuration; namespace Umbraco.ModelsBuilder.BackOffice diff --git a/src/Umbraco.ModelsBuilder/BackOffice/ModelsBuilderBackOfficeController.cs b/src/Umbraco.ModelsBuilder/BackOffice/ModelsBuilderBackOfficeController.cs index e4efca70bd..3963ad09f2 100644 --- a/src/Umbraco.ModelsBuilder/BackOffice/ModelsBuilderBackOfficeController.cs +++ b/src/Umbraco.ModelsBuilder/BackOffice/ModelsBuilderBackOfficeController.cs @@ -1,10 +1,7 @@ using System; -using System.IO; -using System.Linq; using System.Net; using System.Net.Http; using System.Runtime.Serialization; -using System.Text; using System.Web.Hosting; using Umbraco.Core.Exceptions; using Umbraco.ModelsBuilder.Building; diff --git a/src/Umbraco.ModelsBuilder/Building/Builder.cs b/src/Umbraco.ModelsBuilder/Building/Builder.cs index bba19c3b9f..f7917cbf22 100644 --- a/src/Umbraco.ModelsBuilder/Building/Builder.cs +++ b/src/Umbraco.ModelsBuilder/Building/Builder.cs @@ -1,12 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Umbraco.Core.Composing; -using Umbraco.Core.Configuration; using Umbraco.ModelsBuilder.Configuration; namespace Umbraco.ModelsBuilder.Building @@ -71,8 +65,8 @@ namespace Umbraco.ModelsBuilder.Building /// Initializes a new instance of the class with a list of models to generate, /// the result of code parsing, and a models namespace. /// + /// /// The list of models to generate. - /// The models namespace. protected Builder(IModelsBuilderConfig config, IList typeModels) { _typeModels = typeModels ?? throw new ArgumentNullException(nameof(typeModels)); diff --git a/src/Umbraco.ModelsBuilder/Building/ModelsGenerator.cs b/src/Umbraco.ModelsBuilder/Building/ModelsGenerator.cs index 6f6872e8ff..9b1c36b42c 100644 --- a/src/Umbraco.ModelsBuilder/Building/ModelsGenerator.cs +++ b/src/Umbraco.ModelsBuilder/Building/ModelsGenerator.cs @@ -1,7 +1,5 @@ -using System; -using System.IO; +using System.IO; using System.Text; -using System.Web; using Umbraco.ModelsBuilder.Configuration; namespace Umbraco.ModelsBuilder.Building diff --git a/src/Umbraco.ModelsBuilder/Building/TextBuilder.cs b/src/Umbraco.ModelsBuilder/Building/TextBuilder.cs index 56349f16a8..317a9f27d7 100644 --- a/src/Umbraco.ModelsBuilder/Building/TextBuilder.cs +++ b/src/Umbraco.ModelsBuilder/Building/TextBuilder.cs @@ -3,8 +3,6 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Text.RegularExpressions; -using Umbraco.Core.Composing; -using Umbraco.Core.Configuration; using Umbraco.ModelsBuilder.Configuration; namespace Umbraco.ModelsBuilder.Building @@ -18,6 +16,7 @@ namespace Umbraco.ModelsBuilder.Building /// Initializes a new instance of the class with a list of models to generate /// and the result of code parsing. /// + /// /// The list of models to generate. public TextBuilder(IModelsBuilderConfig config, IList typeModels) : base(config, typeModels) diff --git a/src/Umbraco.ModelsBuilder/ConfigsExtensions.cs b/src/Umbraco.ModelsBuilder/ConfigsExtensions.cs index 09314bc3f3..b3605fc541 100644 --- a/src/Umbraco.ModelsBuilder/ConfigsExtensions.cs +++ b/src/Umbraco.ModelsBuilder/ConfigsExtensions.cs @@ -4,7 +4,7 @@ using Umbraco.ModelsBuilder.Configuration; namespace Umbraco.ModelsBuilder { /// - /// Provides extension methods for the class. + /// Provides extension methods for the class. /// public static class ConfigsExtensions { diff --git a/src/Umbraco.ModelsBuilder/Configuration/ModelsBuilderConfig.cs b/src/Umbraco.ModelsBuilder/Configuration/ModelsBuilderConfig.cs index 48a4423054..e67dcc9157 100644 --- a/src/Umbraco.ModelsBuilder/Configuration/ModelsBuilderConfig.cs +++ b/src/Umbraco.ModelsBuilder/Configuration/ModelsBuilderConfig.cs @@ -2,7 +2,6 @@ using System.Configuration; using System.IO; using System.Web.Configuration; -using System.Web.Hosting; using Umbraco.Core; using Umbraco.Core.IO; diff --git a/src/Umbraco.ModelsBuilder/LiveModelsProvider.cs b/src/Umbraco.ModelsBuilder/LiveModelsProvider.cs index 0dfe503fce..14a38a3291 100644 --- a/src/Umbraco.ModelsBuilder/LiveModelsProvider.cs +++ b/src/Umbraco.ModelsBuilder/LiveModelsProvider.cs @@ -1,9 +1,6 @@ using System; using System.Threading; -using System.Web; using System.Web.Hosting; -using Umbraco.Core.Exceptions; -using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.ModelsBuilder.Building; using Umbraco.ModelsBuilder.Configuration; diff --git a/src/Umbraco.ModelsBuilder/ModelsGenerationError.cs b/src/Umbraco.ModelsBuilder/ModelsGenerationError.cs index 8264fecc87..8585b2094a 100644 --- a/src/Umbraco.ModelsBuilder/ModelsGenerationError.cs +++ b/src/Umbraco.ModelsBuilder/ModelsGenerationError.cs @@ -1,7 +1,6 @@ using System; using System.IO; using System.Text; -using Umbraco.Core.Composing; using Umbraco.ModelsBuilder.Configuration; namespace Umbraco.ModelsBuilder diff --git a/src/Umbraco.ModelsBuilder/OutOfDateModelsStatus.cs b/src/Umbraco.ModelsBuilder/OutOfDateModelsStatus.cs index 3b43ed771e..a394b2611a 100644 --- a/src/Umbraco.ModelsBuilder/OutOfDateModelsStatus.cs +++ b/src/Umbraco.ModelsBuilder/OutOfDateModelsStatus.cs @@ -1,5 +1,4 @@ using System.IO; -using Umbraco.Core.Composing; using Umbraco.ModelsBuilder.Configuration; using Umbraco.Web.Cache; diff --git a/src/Umbraco.ModelsBuilder/PublishedModelUtility.cs b/src/Umbraco.ModelsBuilder/PublishedModelUtility.cs index 44af532a0b..3400b2dd7f 100644 --- a/src/Umbraco.ModelsBuilder/PublishedModelUtility.cs +++ b/src/Umbraco.ModelsBuilder/PublishedModelUtility.cs @@ -1,5 +1,4 @@ using System; -using System.ComponentModel; using System.Linq; using System.Linq.Expressions; using Umbraco.Web.Composing; diff --git a/src/Umbraco.ModelsBuilder/PureLiveModelFactory.cs b/src/Umbraco.ModelsBuilder/PureLiveModelFactory.cs index eb82b4323f..c050ae291a 100644 --- a/src/Umbraco.ModelsBuilder/PureLiveModelFactory.cs +++ b/src/Umbraco.ModelsBuilder/PureLiveModelFactory.cs @@ -13,10 +13,8 @@ using System.Web.Compilation; using System.Web.Hosting; using System.Web.WebPages.Razor; using Umbraco.Core; -using Umbraco.Core.Composing; using Umbraco.Core.Logging; using Umbraco.Core.Models.PublishedContent; -using Umbraco.Web.Cache; using Umbraco.ModelsBuilder.Building; using Umbraco.ModelsBuilder.Configuration; using File = System.IO.File; diff --git a/src/Umbraco.ModelsBuilder/ReferencedAssemblies.cs b/src/Umbraco.ModelsBuilder/ReferencedAssemblies.cs index e0a20eb9d6..e1e503d372 100644 --- a/src/Umbraco.ModelsBuilder/ReferencedAssemblies.cs +++ b/src/Umbraco.ModelsBuilder/ReferencedAssemblies.cs @@ -142,26 +142,6 @@ namespace Umbraco.ModelsBuilder // ---- - private static IEnumerable GetDeepReferencedAssemblies(Assembly assembly) - { - var visiting = new Stack(); - var visited = new HashSet(); - - visiting.Push(assembly); - visited.Add(assembly); - while (visiting.Count > 0) - { - var visAsm = visiting.Pop(); - foreach (var refAsm in visAsm.GetReferencedAssemblies() - .Select(TryLoad) - .Where(x => x != null && visited.Contains(x) == false)) - { - yield return refAsm; - visiting.Push(refAsm); - visited.Add(refAsm); - } - } - } private static Assembly TryLoad(AssemblyName name) { diff --git a/src/Umbraco.ModelsBuilder/UmbracoServices.cs b/src/Umbraco.ModelsBuilder/UmbracoServices.cs index 725e5b18c4..f21f75b5b1 100644 --- a/src/Umbraco.ModelsBuilder/UmbracoServices.cs +++ b/src/Umbraco.ModelsBuilder/UmbracoServices.cs @@ -2,14 +2,12 @@ using System.Collections.Generic; using System.Linq; using Umbraco.Core; -using Umbraco.Core.Composing; using Umbraco.Core.Exceptions; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Services; using Umbraco.Core.Strings; using Umbraco.ModelsBuilder.Building; -using Umbraco.ModelsBuilder.Configuration; namespace Umbraco.ModelsBuilder { From 4862ae03aea9758e9fade2b46e55e760336ff078 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 29 Oct 2019 11:19:32 +0100 Subject: [PATCH 117/548] AB3289 - Fixed broken tests --- .../ModelsBuilder/BuilderTests.cs | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs b/src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs index fd381178cc..e1c3ecc891 100644 --- a/src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs +++ b/src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs @@ -67,7 +67,7 @@ using System.Web; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Web; -using Umbraco.ModelsBuilder; +using Umbraco.ModelsBuilder.Embedded; namespace Umbraco.Web.PublishedModels { @@ -182,7 +182,7 @@ using System.Web; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Web; -using Umbraco.ModelsBuilder; +using Umbraco.ModelsBuilder.Embedded; namespace Umbraco.Web.PublishedModels { @@ -223,7 +223,7 @@ namespace Umbraco.Web.PublishedModels [Test] public void GenerateAmbiguous() { - // NOTE: since + // NOTE: since var type1 = new TypeModel { @@ -277,8 +277,8 @@ namespace Umbraco.Web.PublishedModels [TestCase("int", typeof(int))] [TestCase("global::System.Collections.Generic.IEnumerable", typeof(IEnumerable))] - [TestCase("global::Umbraco.ModelsBuilder.Tests.BuilderTestsClass1", typeof(BuilderTestsClass1))] - [TestCase("global::Umbraco.ModelsBuilder.Tests.BuilderTests.Class1", typeof(Class1))] + [TestCase("global::Umbraco.Tests.ModelsBuilder.BuilderTestsClass1", typeof(BuilderTestsClass1))] + [TestCase("global::Umbraco.Tests.ModelsBuilder.BuilderTests.Class1", typeof(Class1))] public void WriteClrType(string expected, Type input) { // note - these assertions differ from the original tests in MB because in the embedded version, the result of Builder.IsAmbiguousSymbol is always true @@ -293,15 +293,15 @@ namespace Umbraco.Web.PublishedModels [TestCase("int", typeof(int))] [TestCase("global::System.Collections.Generic.IEnumerable", typeof(IEnumerable))] - [TestCase("global::Umbraco.ModelsBuilder.Tests.BuilderTestsClass1", typeof(BuilderTestsClass1))] - [TestCase("global::Umbraco.ModelsBuilder.Tests.BuilderTests.Class1", typeof(Class1))] + [TestCase("global::Umbraco.Tests.ModelsBuilder.BuilderTestsClass1", typeof(BuilderTestsClass1))] + [TestCase("global::Umbraco.Tests.ModelsBuilder.BuilderTests.Class1", typeof(Class1))] public void WriteClrTypeUsing(string expected, Type input) { // note - these assertions differ from the original tests in MB because in the embedded version, the result of Builder.IsAmbiguousSymbol is always true // which means global:: syntax will be applied to most things var builder = new TextBuilder(); - builder.Using.Add("Umbraco.ModelsBuilder.Tests"); + builder.Using.Add("Umbraco.Tests.ModelsBuilder"); builder.ModelsNamespaceForTests = "ModelsNamespace"; var sb = new StringBuilder(); builder.WriteClrType(sb, input); @@ -313,7 +313,7 @@ namespace Umbraco.Web.PublishedModels { var builder = new TextBuilder(); builder.Using.Add("System.Text"); - builder.ModelsNamespaceForTests = "Umbraco.ModelsBuilder.Tests.Models"; + builder.ModelsNamespaceForTests = "Umbraco.Tests.ModelsBuilder.Models"; var sb = new StringBuilder(); builder.WriteClrType(sb, typeof(StringBuilder)); @@ -327,7 +327,7 @@ namespace Umbraco.Web.PublishedModels public void WriteClrTypeAnother_WithoutUsing() { var builder = new TextBuilder(); - builder.ModelsNamespaceForTests = "Umbraco.ModelsBuilder.Tests.Models"; + builder.ModelsNamespaceForTests = "Umbraco.Tests.ModelsBuilder.Models"; var sb = new StringBuilder(); builder.WriteClrType(sb, typeof(StringBuilder)); Assert.AreEqual("global::System.Text.StringBuilder", sb.ToString()); @@ -338,7 +338,7 @@ namespace Umbraco.Web.PublishedModels { var builder = new TextBuilder(); builder.Using.Add("System.Text"); - builder.Using.Add("Umbraco.ModelsBuilder.Tests"); + builder.Using.Add("Umbraco.Tests.ModelsBuilder"); builder.ModelsNamespaceForTests = "SomeRandomNamespace"; var sb = new StringBuilder(); builder.WriteClrType(sb, typeof(global::System.Text.ASCIIEncoding)); @@ -354,7 +354,7 @@ namespace Umbraco.Web.PublishedModels { var builder = new TextBuilder(); builder.Using.Add("System.Text"); - builder.Using.Add("Umbraco.ModelsBuilder.Tests"); + builder.Using.Add("Umbraco.Tests.ModelsBuilder"); builder.ModelsNamespaceForTests = "SomeBorkedNamespace"; var sb = new StringBuilder(); builder.WriteClrType(sb, typeof(global::System.Text.ASCIIEncoding)); @@ -370,7 +370,7 @@ namespace Umbraco.Web.PublishedModels { var builder = new TextBuilder(); builder.Using.Add("System.Text"); - builder.Using.Add("Umbraco.ModelsBuilder.Tests"); + builder.Using.Add("Umbraco.Tests.ModelsBuilder"); builder.ModelsNamespaceForTests = "SomeRandomNamespace"; var sb = new StringBuilder(); builder.WriteClrType(sb, typeof(ASCIIEncoding)); @@ -378,7 +378,7 @@ namespace Umbraco.Web.PublishedModels // note - these assertions differ from the original tests in MB because in the embedded version, the result of Builder.IsAmbiguousSymbol is always true // which means global:: syntax will be applied to most things - Assert.AreEqual("global::Umbraco.ModelsBuilder.Tests.ASCIIEncoding", sb.ToString()); + Assert.AreEqual("global::Umbraco.Tests.ModelsBuilder.ASCIIEncoding", sb.ToString()); } [Test] @@ -386,15 +386,15 @@ namespace Umbraco.Web.PublishedModels { var builder = new TextBuilder(); builder.Using.Add("System.Text"); - builder.Using.Add("Umbraco.ModelsBuilder.Tests"); - builder.ModelsNamespaceForTests = "Umbraco.ModelsBuilder.Tests.Models"; + builder.Using.Add("Umbraco.Tests.ModelsBuilder"); + builder.ModelsNamespaceForTests = "Umbraco.Tests.ModelsBuilder.Models"; var sb = new StringBuilder(); builder.WriteClrType(sb, typeof(ASCIIEncoding)); // note - these assertions differ from the original tests in MB because in the embedded version, the result of Builder.IsAmbiguousSymbol is always true // which means global:: syntax will be applied to most things - Assert.AreEqual("global::Umbraco.ModelsBuilder.Tests.ASCIIEncoding", sb.ToString()); + Assert.AreEqual("global::Umbraco.Tests.ModelsBuilder.ASCIIEncoding", sb.ToString()); } [Test] @@ -402,7 +402,7 @@ namespace Umbraco.Web.PublishedModels { var builder = new TextBuilder(); builder.Using.Add("System.Text"); - builder.Using.Add("Umbraco.ModelsBuilder.Tests"); + builder.Using.Add("Umbraco.Tests.ModelsBuilder"); builder.ModelsNamespaceForTests = "SomeRandomNamespace"; var sb = new StringBuilder(); builder.WriteClrType(sb, typeof(ASCIIEncoding.Nested)); @@ -410,7 +410,7 @@ namespace Umbraco.Web.PublishedModels // note - these assertions differ from the original tests in MB because in the embedded version, the result of Builder.IsAmbiguousSymbol is always true // which means global:: syntax will be applied to most things - Assert.AreEqual("global::Umbraco.ModelsBuilder.Tests.ASCIIEncoding.Nested", sb.ToString()); + Assert.AreEqual("global::Umbraco.Tests.ModelsBuilder.ASCIIEncoding.Nested", sb.ToString()); } public class Class1 { } From 3dd7e530ba28dd1c317fb3dcd6e0acb9142ef7fc Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Tue, 29 Oct 2019 11:38:32 +0100 Subject: [PATCH 118/548] Use "not allowed" cursor for locked type aliases --- .../src/views/components/umb-generate-alias.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 }}
Date: Tue, 29 Oct 2019 12:13:42 +0100 Subject: [PATCH 119/548] Protect "system media types" from alias changes and deletion --- src/Umbraco.Core/Models/MediaTypeExtensions.cs | 10 ++++++++++ src/Umbraco.Core/Umbraco.Core.csproj | 1 + .../src/views/mediatypes/edit.html | 1 + .../Models/ContentEditing/MediaTypeDisplay.cs | 3 ++- .../Models/Mapping/ContentTypeMapDefinition.cs | 1 + src/Umbraco.Web/Trees/MediaTypeTreeController.cs | 5 ++++- 6 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 src/Umbraco.Core/Models/MediaTypeExtensions.cs 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/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 309dc97b81..c0fcfe87f8 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -268,6 +268,7 @@ + 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 856089fa32..50975a6d60 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 @@ { - + [DataMember(Name = "isSystemMediaType")] + public bool IsSystemMediaType { get; set; } } } diff --git a/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs index 528d5f6de5..0e3af5d0da 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs @@ -145,6 +145,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; diff --git a/src/Umbraco.Web/Trees/MediaTypeTreeController.cs b/src/Umbraco.Web/Trees/MediaTypeTreeController.cs index f85aefcace..3d0046c319 100644 --- a/src/Umbraco.Web/Trees/MediaTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/MediaTypeTreeController.cs @@ -120,7 +120,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)); } From 17e28b6840813aff12e2347afc921dd78172c10f Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 29 Oct 2019 14:28:54 +0100 Subject: [PATCH 120/548] AB3389 - Move the stuff from App_Plugins/ModelsBuilder into Umbraco.Web.UI.Client and handle dashboard as code. --- build/NuSpecs/UmbracoCms.nuspec | 1 - .../DisableModelsBuilderManifestFilter.cs | 17 ----------------- .../Compose/ModelsBuilderComposer.cs | 5 +++-- .../ModelsBuilderDashboard.cs | 19 +++++++++++++++++++ .../Umbraco.ModelsBuilder.Embedded.csproj | 2 +- .../modelsbuildermanagement.resource.js} | 4 ++-- .../services/contenttypehelper.service.js | 4 ++-- .../modelsbuildermanagement.controller.js} | 12 ++++++------ .../settings/modelsbuildermanagement.html} | 2 +- .../UmbModelsBuilder/package.manifest | 18 ------------------ src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 8 +------- 11 files changed, 35 insertions(+), 57 deletions(-) delete mode 100644 src/Umbraco.ModelsBuilder.Embedded/Compose/DisableModelsBuilderManifestFilter.cs create mode 100644 src/Umbraco.ModelsBuilder.Embedded/ModelsBuilderDashboard.cs rename src/{Umbraco.Web.UI/App_Plugins/UmbModelsBuilder/modelsbuilder.resource.js => Umbraco.Web.UI.Client/src/common/resources/modelsbuildermanagement.resource.js} (80%) rename src/{Umbraco.Web.UI/App_Plugins/UmbModelsBuilder/modelsbuilder.controller.js => Umbraco.Web.UI.Client/src/views/dashboard/settings/modelsbuildermanagement.controller.js} (59%) rename src/{Umbraco.Web.UI/App_Plugins/UmbModelsBuilder/modelsbuilder.html => Umbraco.Web.UI.Client/src/views/dashboard/settings/modelsbuildermanagement.html} (94%) delete mode 100644 src/Umbraco.Web.UI/App_Plugins/UmbModelsBuilder/package.manifest diff --git a/build/NuSpecs/UmbracoCms.nuspec b/build/NuSpecs/UmbracoCms.nuspec index d0bd4a2688..a6b06d9964 100644 --- a/build/NuSpecs/UmbracoCms.nuspec +++ b/build/NuSpecs/UmbracoCms.nuspec @@ -42,7 +42,6 @@ - diff --git a/src/Umbraco.ModelsBuilder.Embedded/Compose/DisableModelsBuilderManifestFilter.cs b/src/Umbraco.ModelsBuilder.Embedded/Compose/DisableModelsBuilderManifestFilter.cs deleted file mode 100644 index 617c5ced9b..0000000000 --- a/src/Umbraco.ModelsBuilder.Embedded/Compose/DisableModelsBuilderManifestFilter.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Collections.Generic; -using Umbraco.Core.Manifest; - -namespace Umbraco.ModelsBuilder.Embedded.Compose -{ - /// - /// Removes the built in embedded models builder manifest from being loaded - /// - internal class DisableModelsBuilderManifestFilter : IManifestFilter - { - public void Filter(List manifests) - { - manifests.RemoveAll(x => x.Source.EndsWith("App_Plugins\\UmbModelsBuilder\\package.manifest", StringComparison.InvariantCultureIgnoreCase)); - } - } -} diff --git a/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComposer.cs b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComposer.cs index cf70adb6fe..c6924e3abe 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComposer.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComposer.cs @@ -6,6 +6,7 @@ using Umbraco.Core.Composing; using Umbraco.Core.Models.PublishedContent; using Umbraco.ModelsBuilder.Embedded.Building; using Umbraco.ModelsBuilder.Embedded.Configuration; +using Umbraco.Web; using Umbraco.Web.PublishedCache.NuCache; using Umbraco.Web.Features; @@ -58,9 +59,9 @@ namespace Umbraco.ModelsBuilder.Embedded.Compose private void ComposeForLegacyModelsBuilder(Composition composition) { - composition.Logger.Info("ModelsBuilder.Embedded is disabled, the legacy ModelsBuilder was detected."); + composition.Logger.Info("ModelsBuilder.Embedded is disabled, the external ModelsBuilder was detected."); composition.Components().Append(); - composition.ManifestFilters().Append(); + composition.Dashboards().Remove(); } private void ComposeForDefaultModelsFactory(Composition composition) diff --git a/src/Umbraco.ModelsBuilder.Embedded/ModelsBuilderDashboard.cs b/src/Umbraco.ModelsBuilder.Embedded/ModelsBuilderDashboard.cs new file mode 100644 index 0000000000..b8b1945f32 --- /dev/null +++ b/src/Umbraco.ModelsBuilder.Embedded/ModelsBuilderDashboard.cs @@ -0,0 +1,19 @@ +using System; +using Umbraco.Core.Composing; +using Umbraco.Core.Dashboards; + +namespace Umbraco.ModelsBuilder.Embedded +{ + [Weight(40)] + public class ModelsBuilderDashboard : IDashboard + { + public string Alias => "settingsModelsBuilder"; + + public string[] Sections => new [] { "settings" }; + + public string View => "views/dashboard/settings/modelsbuildermanagement.html"; + + public IAccessRule[] AccessRules => Array.Empty(); + } + +} diff --git a/src/Umbraco.ModelsBuilder.Embedded/Umbraco.ModelsBuilder.Embedded.csproj b/src/Umbraco.ModelsBuilder.Embedded/Umbraco.ModelsBuilder.Embedded.csproj index c09703ecfb..75121a635d 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Umbraco.ModelsBuilder.Embedded.csproj +++ b/src/Umbraco.ModelsBuilder.Embedded/Umbraco.ModelsBuilder.Embedded.csproj @@ -59,7 +59,6 @@ - @@ -68,6 +67,7 @@ + diff --git a/src/Umbraco.Web.UI/App_Plugins/UmbModelsBuilder/modelsbuilder.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/modelsbuildermanagement.resource.js similarity index 80% rename from src/Umbraco.Web.UI/App_Plugins/UmbModelsBuilder/modelsbuilder.resource.js rename to src/Umbraco.Web.UI.Client/src/common/resources/modelsbuildermanagement.resource.js index 58ca77cbdb..ee3cd80c71 100644 --- a/src/Umbraco.Web.UI/App_Plugins/UmbModelsBuilder/modelsbuilder.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/modelsbuildermanagement.resource.js @@ -1,4 +1,4 @@ -function modelsBuilderResource($q, $http, umbRequestHelper) { +function modelsBuilderManagementResource($q, $http, umbRequestHelper) { return { getModelsOutOfDateStatus: function () { @@ -20,4 +20,4 @@ } }; } -angular.module("umbraco.resources").factory("modelsBuilderResource", modelsBuilderResource); +angular.module("umbraco.resources").factory("modelsBuilderManagementResource", modelsBuilderManagementResource); 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 5c3e6eb4c8..305e4a694d 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 @@ -27,7 +27,7 @@ function contentTypeHelper(contentTypeResource, dataTypeResource, $filter, $inje generateModels: function () { var deferred = $q.defer(); - var modelsResource = $injector.has("modelsBuilderResource") ? $injector.get("modelsBuilderResource") : null; + 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) { @@ -48,7 +48,7 @@ function contentTypeHelper(contentTypeResource, dataTypeResource, $filter, $inje checkModelsBuilderStatus: function () { var deferred = $q.defer(); - var modelsResource = $injector.has("modelsBuilderResource") ? $injector.get("modelsBuilderResource") : null; + 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); if (modelsBuilderEnabled && modelsResource) { diff --git a/src/Umbraco.Web.UI/App_Plugins/UmbModelsBuilder/modelsbuilder.controller.js b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/modelsbuildermanagement.controller.js similarity index 59% rename from src/Umbraco.Web.UI/App_Plugins/UmbModelsBuilder/modelsbuilder.controller.js rename to src/Umbraco.Web.UI.Client/src/views/dashboard/settings/modelsbuildermanagement.controller.js index b0e0c303cf..423a20d864 100644 --- a/src/Umbraco.Web.UI/App_Plugins/UmbModelsBuilder/modelsbuilder.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/modelsbuildermanagement.controller.js @@ -1,4 +1,4 @@ -function modelsBuilderController($scope, $http, umbRequestHelper, modelsBuilderResource) { +function modelsBuilderManagementController($scope, $http, umbRequestHelper, modelsBuilderManagementResource) { var vm = this; @@ -9,8 +9,8 @@ function generate() { vm.generating = true; umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("modelsBuilderBaseUrl", "BuildModels")), - 'Failed to generate.') + $http.post(umbRequestHelper.getApiUrl("modelsBuilderBaseUrl", "BuildModels")), + 'Failed to generate.') .then(function (result) { vm.generating = false; vm.dashboard = result; @@ -19,7 +19,7 @@ function reload() { vm.loading = true; - modelsBuilderResource.getDashboard().then(function (result) { + modelsBuilderManagementResource.getDashboard().then(function (result) { vm.dashboard = result; vm.loading = false; }); @@ -27,7 +27,7 @@ function init() { vm.loading = true; - modelsBuilderResource.getDashboard().then(function (result) { + modelsBuilderManagementResource.getDashboard().then(function (result) { vm.dashboard = result; vm.loading = false; }); @@ -35,4 +35,4 @@ init(); } -angular.module("umbraco").controller("Umbraco.Dashboard.ModelsBuilderController", modelsBuilderController); \ No newline at end of file +angular.module("umbraco").controller("Umbraco.Dashboard.ModelsBuilderManagementController", modelsBuilderManagementController); diff --git a/src/Umbraco.Web.UI/App_Plugins/UmbModelsBuilder/modelsbuilder.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/modelsbuildermanagement.html similarity index 94% rename from src/Umbraco.Web.UI/App_Plugins/UmbModelsBuilder/modelsbuilder.html rename to src/Umbraco.Web.UI.Client/src/views/dashboard/settings/modelsbuildermanagement.html index 0c10c33e39..1ba86bf72b 100644 --- a/src/Umbraco.Web.UI/App_Plugins/UmbModelsBuilder/modelsbuilder.html +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/modelsbuildermanagement.html @@ -1,4 +1,4 @@ -
+
diff --git a/src/Umbraco.Web.UI/App_Plugins/UmbModelsBuilder/package.manifest b/src/Umbraco.Web.UI/App_Plugins/UmbModelsBuilder/package.manifest deleted file mode 100644 index 5e64177046..0000000000 --- a/src/Umbraco.Web.UI/App_Plugins/UmbModelsBuilder/package.manifest +++ /dev/null @@ -1,18 +0,0 @@ -{ - // array of files we want to inject into the application - "javascript": [ - "~/App_Plugins/ModelsBuilder/modelsbuilder.controller.js", - "~/App_Plugins/ModelsBuilder/modelsbuilder.resource.js" - ], - - // models builder dashboard - "dashboards": [ - { - "alias": "settingsModelsBuilder", - "name": "Models Builder", - "view": "/App_Plugins/ModelsBuilder/modelsbuilder.html", - "sections": [ "settings" ], - "weight": 40 - } - ] -} \ No newline at end of file diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index ceec3ee927..c556ffe773 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -80,6 +80,7 @@ + @@ -104,9 +105,6 @@ - - 8.1.0 - @@ -144,9 +142,6 @@ True Settings.settings - - - @@ -172,7 +167,6 @@ - ClientDependency.config Designer From 7d08040f34678c5662cdcc88bae9a3f467c37f3c Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 29 Oct 2019 14:29:58 +0100 Subject: [PATCH 121/548] AB3289 - Do not use the nuget package as default --- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index ceec3ee927..85300aceb2 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -104,9 +104,6 @@ - - 8.1.0 - From a00824484d46aba619d2e877fd8b031fba5de694 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 29 Oct 2019 14:43:43 +0100 Subject: [PATCH 122/548] Revert "AB3289 - Do not use the nuget package as default" This reverts commit 7d08040f --- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 85300aceb2..ceec3ee927 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -104,6 +104,9 @@ + + 8.1.0 + From 15395aaacaba28c483c530c671fe244525ef1894 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Tue, 29 Oct 2019 20:28:45 +0100 Subject: [PATCH 123/548] Re-apply input focus to the last focused element when closing dialogs --- .../src/common/services/editor.service.js | 11 +++++++++++ 1 file changed, 11 insertions(+) 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 02e85c278a..6bb44294ed 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 @@ -169,6 +169,7 @@ When building a custom infinite editor view you can use the same components as a let editorsKeyboardShorcuts = []; var editors = []; var isEnabled = true; + var lastElementInFocus = null; // events for backdrop @@ -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; @@ -301,6 +308,10 @@ When building a custom infinite editor view you can use the same components as a $timeout(function() { // rebind keyboard shortcuts for the new editor in focus rebindKeyboardShortcuts(); + + if (editors.length === 0 && lastElementInFocus) { + lastElementInFocus.focus(); + } }, 0); } From 4ee6fdd642628499272634e536470c569378a876 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Tue, 29 Oct 2019 20:39:38 +0100 Subject: [PATCH 124/548] Hide the content picker "max items" help text when it is configured in single picker mode --- .../src/views/propertyeditors/contentpicker/contentpicker.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 @@ ... -
+
From 12cb37057831908d4148b14306441753b02a51c0 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 30 Oct 2019 17:14:04 +1100 Subject: [PATCH 125/548] Reduces statics, reduces code duplication for iterating over properties in the NC prop editor --- .../Published/NestedContentTests.cs | 3 +- .../NestedContentPropertyEditor.cs | 370 ++++++++---------- 2 files changed, 170 insertions(+), 203 deletions(-) diff --git a/src/Umbraco.Tests/Published/NestedContentTests.cs b/src/Umbraco.Tests/Published/NestedContentTests.cs index 9385b8955a..2bba37c7d3 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()); editors = new PropertyEditorCollection(new DataEditorCollection(new DataEditor[] { editor })); var dataType1 = new DataType(editor) diff --git a/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs index ab26290812..7104df0775 100644 --- a/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs @@ -28,13 +28,14 @@ namespace Umbraco.Web.PropertyEditors public class NestedContentPropertyEditor : DataEditor { private readonly Lazy _propertyEditors; - + private readonly IDataTypeService _dataTypeService; internal const string ContentTypeAliasPropertyKey = "ncContentTypeAlias"; - public NestedContentPropertyEditor(ILogger logger, Lazy propertyEditors) + public NestedContentPropertyEditor(ILogger logger, Lazy propertyEditors, IDataTypeService dataTypeService) : base (logger) { _propertyEditors = propertyEditors; + _dataTypeService = dataTypeService; } // has to be lazy else circular dep in ctor @@ -56,21 +57,21 @@ namespace Umbraco.Web.PropertyEditors #region Value Editor - protected override IDataValueEditor CreateValueEditor() => new NestedContentPropertyValueEditor(Attribute, PropertyEditors); + protected override IDataValueEditor CreateValueEditor() => new NestedContentPropertyValueEditor(Attribute, PropertyEditors, _dataTypeService); internal class NestedContentPropertyValueEditor : DataValueEditor, IDataValueReference { private readonly PropertyEditorCollection _propertyEditors; + private readonly IDataTypeService _dataTypeService; - public NestedContentPropertyValueEditor(DataEditorAttribute attribute, PropertyEditorCollection propertyEditors) + public NestedContentPropertyValueEditor(DataEditorAttribute attribute, PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService) : base(attribute) { _propertyEditors = propertyEditors; - Validators.Add(new NestedContentValidator(propertyEditors)); + _dataTypeService = dataTypeService; + Validators.Add(new NestedContentValidator(propertyEditors, dataTypeService)); } - internal ServiceContext Services => Current.Services; - /// public override object Configuration { @@ -87,60 +88,92 @@ namespace Umbraco.Web.PropertyEditors } } - #region DB to String - - public override string ConvertDbToString(PropertyType propertyType, object propertyValue, IDataTypeService dataTypeService) + /// + /// Method used to iterate over the deserialized property values + /// + /// + /// + internal static List IteratePropertyValues(object propertyValue, Action onIteration) { if (propertyValue == null || string.IsNullOrWhiteSpace(propertyValue.ToString())) - return string.Empty; + return null; - var value = JsonConvert.DeserializeObject>(propertyValue.ToString()); - if (value == null) - return string.Empty; + var value = 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 (value == null || value.Count == 0) + return null; + + var index = 0; foreach (var o in value) { - var propValues = (JObject) o; + var propValues = o; + // TODO: This is N+1 (although we cache all doc types, it's still not pretty) var contentType = GetElementType(propValues); if (contentType == null) continue; - var propAliases = propValues.Properties().Select(x => x.Name).ToArray(); + var propertyTypes = contentType.CompositionPropertyTypes.ToDictionary(x => x.Alias, x => x); + var propAliases = propValues.Properties().Select(x => x.Name); foreach (var propAlias in propAliases) { - var propType = contentType.CompositionPropertyTypes.FirstOrDefault(x => x.Alias == propAlias); - if (propType == null) + propertyTypes.TryGetValue(propAlias, out var propType); + onIteration(propAlias, propType, propValues, index); + } + + index++; + } + + return value; + } + + #region DB to String + + public override string ConvertDbToString(PropertyType propertyType, object propertyValue, IDataTypeService dataTypeService) + { + var value = IteratePropertyValues(propertyValue, (string propAlias, PropertyType propType, JObject propValues, int index) => + { + if (propType == null) + { + // type not found, and property is not system: just delete the value + if (IsSystemPropertyKey(propAlias) == false) + propValues[propAlias] = 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[propType.PropertyEditorAlias]; + var tempConfig = dataTypeService.GetDataType(propType.DataTypeId).Configuration; + var valEditor = propEditor.GetValueEditor(tempConfig); + var convValue = valEditor.ConvertDbToString(propType, propValues[propAlias]?.ToString(), dataTypeService); + propValues[propAlias] = convValue; } - else + catch (InvalidOperationException) { - try - { - // convert the value, and store the converted value - var propEditor = _propertyEditors[propType.PropertyEditorAlias]; - 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) + propValues[propAlias] = null; } } - } + }); + + if (value == null) + return string.Empty; return JsonConvert.SerializeObject(value).ToXmlString(); } #endregion + + #region Convert database // editor // note: there is NO variant support here @@ -148,58 +181,43 @@ 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())) - return string.Empty; - var value = JsonConvert.DeserializeObject>(val.ToString()); + var value = IteratePropertyValues(val, (string propAlias, PropertyType propType, JObject propValues, int index) => + { + if (propType == null) + { + // type not found, and property is not system: just delete the value + if (IsSystemPropertyKey(propAlias) == false) + propValues[propAlias] = null; + } + else + { + 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]; + 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; + } + } + }); + if (value == null) return string.Empty; - foreach (var o in value) - { - 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) - { - 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(propAlias) == false) - propValues[propAlias] = null; - } - else - { - 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]; - 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; - } - } - - } - } - // return json return value; } @@ -209,60 +227,37 @@ 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; - - // Issue #38 - Keep recursive property lookups working - if (!value.Any()) - return null; - - // Process value - for (var i = 0; i < value.Count; i++) + var value = IteratePropertyValues(editorValue.Value, (string propAlias, PropertyType propType, JObject propValues, int index) => { - var o = value[i]; - var propValues = ((JObject)o); - - var contentType = GetElementType(propValues); - if (contentType == null) + if (propType == null) { - continue; + // type not found, and property is not system: just delete the value + if (IsSystemPropertyKey(propAlias) == false) + propValues[propAlias] = 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(propType.DataTypeId).Configuration; - // Lookup the property editor - var propEditor = _propertyEditors[propType.PropertyEditorAlias]; + // Lookup the property editor + var propEditor = _propertyEditors[propType.PropertyEditorAlias]; - // 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(propValues[propAlias], 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, propValues[propAlias]); + // Store the value back + propValues[propAlias] = (newValue == null) ? null : JToken.FromObject(newValue); } - } + }); + if (value == null) + return string.Empty; + + // return json return JsonConvert.SerializeObject(value); } @@ -272,36 +267,22 @@ namespace Umbraco.Web.PropertyEditors var result = new List(); - var list = JsonConvert.DeserializeObject>(rawJson); - if (list == null) - return result; - - foreach (var o in list) + var json = IteratePropertyValues(rawJson, (string propAlias, PropertyType propType, JObject propValues, int index) => { - var propValues = (JObject) o; + if (propType == null) return; - var contentType = GetElementType(propValues); - if (contentType == null) - continue; + var propEditor = _propertyEditors[propType.PropertyEditorAlias]; - var propAliases = propValues.Properties().Select(x => x.Name).ToArray(); - foreach (var propAlias in propAliases) - { - var propType = contentType.CompositionPropertyTypes.FirstOrDefault(x => x.Alias == propAlias); - if (propType == null) continue; + var valueEditor = propEditor?.GetValueEditor(); + if (!(valueEditor is IDataValueReference reference)) return; - var propEditor = _propertyEditors[propType.PropertyEditorAlias]; + var val = propValues[propAlias]?.ToString(); - var valueEditor = propEditor?.GetValueEditor(); - if (!(valueEditor is IDataValueReference reference)) continue; + var refs = reference.GetReferences(val); - var val = propValues[propAlias]?.ToString(); + result.AddRange(refs); + }); - var refs = reference.GetReferences(val); - - result.AddRange(refs); - } - } return result; } @@ -311,71 +292,56 @@ namespace Umbraco.Web.PropertyEditors internal class NestedContentValidator : IValueValidator { private readonly PropertyEditorCollection _propertyEditors; + private readonly IDataTypeService _dataTypeService; - public NestedContentValidator(PropertyEditorCollection propertyEditors) + public NestedContentValidator(PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService) { _propertyEditors = propertyEditors; + _dataTypeService = dataTypeService; } 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++) + NestedContentPropertyValueEditor.IteratePropertyValues(rawValue, (string propKey, PropertyType propType, JObject propValues, int i) => { - var o = value[i]; - var propValues = (JObject) o; + if (propType == null) return; - var contentType = GetElementType(propValues); - if (contentType == null) continue; + var config = _dataTypeService.GetDataType(propType.DataTypeId).Configuration; + var propertyEditor = _propertyEditors[propType.PropertyEditorAlias]; - 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(propValues[propKey], propertyEditor.GetValueEditor().ValueType, config)) { - var config = dataTypeService.GetDataType(propType.DataTypeId).Configuration; - var propertyEditor = _propertyEditors[propType.PropertyEditorAlias]; - - 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) - yield return new ValidationResult("Item " + (i + 1) + " '" + propType.Name + "' cannot be null", new[] { propKey }); - else if (propValues[propKey].ToString().IsNullOrWhiteSpace() || (propValues[propKey].Type == JTokenType.Array && !propValues[propKey].HasValues)) - yield return new ValidationResult("Item " + (i + 1) + " '" + propType.Name + "' cannot be empty", 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())) - { - yield return new ValidationResult("Item " + (i + 1) + " '" + propType.Name + "' is invalid, it does not match the correct pattern", new[] { propKey }); - } - } + result.ErrorMessage = "Item " + (i + 1) + " '" + propType.Name + "' " + result.ErrorMessage; + validationResults.Add(result); } } - } + + // Check mandatory + if (propType.Mandatory) + { + if (propValues[propKey] == null) + validationResults.Add(new ValidationResult("Item " + (i + 1) + " '" + propType.Name + "' cannot be null", new[] { propKey })); + else if (propValues[propKey].ToString().IsNullOrWhiteSpace() || (propValues[propKey].Type == JTokenType.Array && !propValues[propKey].HasValues)) + validationResults.Add(new ValidationResult("Item " + (i + 1) + " '" + propType.Name + "' cannot be empty", 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())) + { + validationResults.Add(new ValidationResult("Item " + (i + 1) + " '" + propType.Name + "' is invalid, it does not match the correct pattern", new[] { propKey })); + } + } + }); + + return validationResults; } } From f305dd45cbbf0c96855b86c2c3abed58da49f771 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 30 Oct 2019 08:50:15 +0100 Subject: [PATCH 126/548] AB3328 - Fixed two bugs - One if links was absolute and not references, and one if the value was empty string. --- .../PropertyEditors/MultiUrlPickerValueEditor.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/PropertyEditors/MultiUrlPickerValueEditor.cs b/src/Umbraco.Web/PropertyEditors/MultiUrlPickerValueEditor.cs index 1f71f39d96..98f41c50b9 100644 --- a/src/Umbraco.Web/PropertyEditors/MultiUrlPickerValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MultiUrlPickerValueEditor.cs @@ -179,12 +179,20 @@ namespace Umbraco.Web.PropertyEditors { 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) { - yield return new UmbracoEntityReference(link.Udi); + if (link.Udi != null) // Links can be absolute links without a Udi + { + yield return new UmbracoEntityReference(link.Udi); + } + } + + } } } From 06ca4294b19abdecdb5de433a41539dc5dffe0ef Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Wed, 30 Oct 2019 08:52:23 +0100 Subject: [PATCH 127/548] Styling updates to image cropper and upload property editors --- .../src/less/components/umb-property-file-upload.less | 1 + src/Umbraco.Web.UI.Client/src/less/mixins.less | 2 +- src/Umbraco.Web.UI.Client/src/less/property-editors.less | 4 ++++ src/Umbraco.Web.UI.Client/src/less/variables.less | 1 + 4 files changed, 7 insertions(+), 1 deletion(-) 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/mixins.less b/src/Umbraco.Web.UI.Client/src/less/mixins.less index e49755338b..52638c82dc 100644 --- a/src/Umbraco.Web.UI.Client/src/less/mixins.less +++ b/src/Umbraco.Web.UI.Client/src/less/mixins.less @@ -419,7 +419,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/property-editors.less b/src/Umbraco.Web.UI.Client/src/less/property-editors.less index dc2d402671..b7cf22be62 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -721,6 +721,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/variables.less b/src/Umbraco.Web.UI.Client/src/less/variables.less index 166640829b..a3f2183f83 100644 --- a/src/Umbraco.Web.UI.Client/src/less/variables.less +++ b/src/Umbraco.Web.UI.Client/src/less/variables.less @@ -322,6 +322,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; From abffe537c33a8ade581c1b6996ae0dc688d264a9 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Wed, 30 Oct 2019 11:00:19 +0100 Subject: [PATCH 128/548] Make multiple media property editor same width as other property editors --- src/Umbraco.Web.UI.Client/src/less/property-editors.less | 1 + 1 file changed, 1 insertion(+) 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 dc2d402671..d26fb8bbd9 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -232,6 +232,7 @@ } .umb-mediapicker-multi > div { width:100%; + .umb-property-editor--limit-width(); } From 965d6cbeb72ee426031488d6dad861e3fe3cc7e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20Knippers?= Date: Wed, 30 Oct 2019 10:47:08 +0100 Subject: [PATCH 129/548] Added enum extension methods + tests for SetFlag and UnsetFlag. --- src/Umbraco.Core/EnumExtensions.cs | 38 +++++++++++++++++ .../CoreThings/EnumExtensionsTests.cs | 42 +++++++++++++++++++ .../Mapping/ContentTypeMapDefinition.cs | 16 +++---- 3 files changed, 86 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Core/EnumExtensions.cs b/src/Umbraco.Core/EnumExtensions.cs index 1443a27876..b2e5f32c9a 100644 --- a/src/Umbraco.Core/EnumExtensions.cs +++ b/src/Umbraco.Core/EnumExtensions.cs @@ -41,5 +41,43 @@ namespace Umbraco.Core 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); + } } } diff --git a/src/Umbraco.Tests/CoreThings/EnumExtensionsTests.cs b/src/Umbraco.Tests/CoreThings/EnumExtensionsTests.cs index 72a55cee85..4a0c1d0f41 100644 --- a/src/Umbraco.Tests/CoreThings/EnumExtensionsTests.cs +++ b/src/Umbraco.Tests/CoreThings/EnumExtensionsTests.cs @@ -51,5 +51,47 @@ 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.Web/Models/Mapping/ContentTypeMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs index 9b8936a43c..1f1ea139fc 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs @@ -224,11 +224,9 @@ namespace Umbraco.Web.Models.Mapping target.Mandatory = source.Validation.Mandatory; target.ValidationRegExp = source.Validation.Pattern; target.Variations = source.AllowCultureVariant - // Set culture flag - ? target.Variations | ContentVariation.Culture - // Unset culture flag - : target.Variations & ~ContentVariation.Culture; - + ? target.Variations.SetFlag(ContentVariation.Culture) + : target.Variations.UnsetFlag(ContentVariation.Culture); + if (source.Id > 0) target.Id = source.Id; @@ -399,11 +397,9 @@ namespace Umbraco.Web.Models.Mapping if (!(target is IMemberType)) { - target.Variations = source.AllowCultureVariant - // Set culture flag - ? target.Variations | ContentVariation.Culture - // Unset culture flag - : target.Variations & ~ContentVariation.Culture; + target.Variations = source.AllowCultureVariant + ? target.Variations.SetFlag(ContentVariation.Culture) + : target.Variations.UnsetFlag(ContentVariation.Culture); } // handle property groups and property types From da3a971906f9c3b455ca912edf106893a31c7f79 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Wed, 30 Oct 2019 18:57:33 +0100 Subject: [PATCH 130/548] Do not show breadcrumb for immediate children in the recycle bin --- .../src/common/directives/components/content/edit.controller.js | 2 +- .../src/views/media/media.edit.controller.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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..d3f65d8f63 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 @@ -42,7 +42,7 @@ $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', 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..b8c38e923b 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 @@ -245,7 +245,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) { From 4de4656f9660ace1e1cf6d04570e820faeef2b33 Mon Sep 17 00:00:00 2001 From: Dennis Meinert Pedersen Date: Wed, 30 Oct 2019 20:47:03 +0100 Subject: [PATCH 131/548] Avoid breadcrumbs in mini list are misaligned --- .../src/less/components/umb-breadcrumbs.less | 2 +- .../src/less/components/umb-mini-list-view.less | 10 ++++++++-- .../src/views/components/umb-mini-list-view.html | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-breadcrumbs.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-breadcrumbs.less index 3c63d74a47..0afcfdd1f9 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-breadcrumbs.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-breadcrumbs.less @@ -8,7 +8,7 @@ .umb-breadcrumbs__ancestor { display: flex; - min-height: 25px; + align-items: center; } .umb-breadcrumbs__action { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-mini-list-view.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-mini-list-view.less index 7a2939bd30..8cd08f5045 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-mini-list-view.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-mini-list-view.less @@ -13,9 +13,15 @@ margin-right: 5px; } +.umb-mini-list-view__breadcrumb { + .flex; + margin-bottom: 10px; + min-height: 25px; +} + .umb-mini-list-view__back { - font-size: 12px; - margin-right: 5px; + font-size: 13px; + margin-right: 5px; color: @gray-4; display: flex; align-items: center; 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 f11df6042e..e14315f9f4 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 @@ -9,7 +9,7 @@

{{ miniListView.node.name }}

-
+
From 0e0f6e6678f1e3e9a7c093ed9df674a4a1670f37 Mon Sep 17 00:00:00 2001 From: Rasmus John Pedersen Date: Mon, 21 Oct 2019 22:21:23 +0200 Subject: [PATCH 132/548] Ensure media path stored is the same as stored in the property --- .../Factories/ContentBaseFactory.cs | 36 +++++++------------ .../Repositories/Implement/MediaRepository.cs | 6 ++-- .../IDataEditorWithMediaPath.cs | 9 +++++ src/Umbraco.Core/Umbraco.Core.csproj | 1 + .../FileUploadPropertyEditor.cs | 5 ++- .../ImageCropperPropertyEditor.cs | 5 ++- 6 files changed, 33 insertions(+), 29 deletions(-) create mode 100644 src/Umbraco.Core/PropertyEditors/IDataEditorWithMediaPath.cs diff --git a/src/Umbraco.Core/Persistence/Factories/ContentBaseFactory.cs b/src/Umbraco.Core/Persistence/Factories/ContentBaseFactory.cs index 434e0393cd..f9221ea576 100644 --- a/src/Umbraco.Core/Persistence/Factories/ContentBaseFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/ContentBaseFactory.cs @@ -1,17 +1,15 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text.RegularExpressions; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Repositories; +using Umbraco.Core.PropertyEditors; namespace Umbraco.Core.Persistence.Factories { internal class ContentBaseFactory { - private static readonly Regex MediaPathPattern = new Regex(@"(/media/.+?)(?:['""]|$)", RegexOptions.Compiled); - /// /// Builds an IContent item from a dto and content type. /// @@ -189,7 +187,7 @@ namespace Umbraco.Core.Persistence.Factories /// /// Builds a dto from an IMedia item. /// - public static MediaDto BuildDto(IMedia entity) + public static MediaDto BuildDto(PropertyEditorCollection propertyEditors, IMedia entity) { var contentDto = BuildContentDto(entity, Constants.ObjectTypes.Media); @@ -197,7 +195,7 @@ namespace Umbraco.Core.Persistence.Factories { NodeId = entity.Id, ContentDto = contentDto, - MediaVersionDto = BuildMediaVersionDto(entity, contentDto) + MediaVersionDto = BuildMediaVersionDto(propertyEditors, entity, contentDto) }; return dto; @@ -291,12 +289,19 @@ namespace Umbraco.Core.Persistence.Factories return dto; } - private static MediaVersionDto BuildMediaVersionDto(IMedia entity, ContentDto contentDto) + private static MediaVersionDto BuildMediaVersionDto(PropertyEditorCollection propertyEditors, IMedia entity, ContentDto contentDto) { // try to get a path from the string being stored for media // TODO: only considering umbracoFile - TryMatch(entity.GetValue("umbracoFile"), out var path); + string path = null; + + if (entity.Properties.TryGetValue(Constants.Conventions.Media.File, out var property) + && propertyEditors.TryGet(property.PropertyType.PropertyEditorAlias, out var editor) + && editor is IDataEditorWithMediaPath dataEditor) + { + path = dataEditor.GetMediaPath(property); + } var dto = new MediaVersionDto { @@ -308,22 +313,5 @@ namespace Umbraco.Core.Persistence.Factories return dto; } - - // TODO: this should NOT be here?! - // more dark magic ;-( - internal static bool TryMatch(string text, out string path) - { - // In v8 we should allow exposing this via the property editor in a much nicer way so that the property editor - // can tell us directly what any URL is for a given property if it contains an asset - - path = null; - if (string.IsNullOrWhiteSpace(text)) return false; - - var m = MediaPathPattern.Match(text); - if (!m.Success || m.Groups.Count != 2) return false; - - path = m.Groups[1].Value; - return true; - } } } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs index 25828b8126..8d7bda8943 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs @@ -228,7 +228,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement entity.SanitizeEntityPropertiesForXmlStorage(); // create the dto - var dto = ContentBaseFactory.BuildDto(entity); + var dto = ContentBaseFactory.BuildDto(PropertyEditors, entity); // derive path and level from parent var parent = GetParentNodeDto(entity.ParentId); @@ -317,7 +317,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement } // create the dto - var dto = ContentBaseFactory.BuildDto(entity); + var dto = ContentBaseFactory.BuildDto(PropertyEditors, entity); // update the node dto var nodeDto = dto.ContentDto.NodeDto; @@ -542,6 +542,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement media.ResetDirtyProperties(false); return media; } - + } } diff --git a/src/Umbraco.Core/PropertyEditors/IDataEditorWithMediaPath.cs b/src/Umbraco.Core/PropertyEditors/IDataEditorWithMediaPath.cs new file mode 100644 index 0000000000..af42ebf4d6 --- /dev/null +++ b/src/Umbraco.Core/PropertyEditors/IDataEditorWithMediaPath.cs @@ -0,0 +1,9 @@ +using Umbraco.Core.Models; + +namespace Umbraco.Core.PropertyEditors +{ + public interface IDataEditorWithMediaPath : IDataEditor + { + string GetMediaPath(Property property, string culture = null, string segment = null); + } +} diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index ffe20afdb3..c40f438960 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -278,6 +278,7 @@ + diff --git a/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs index 702408788a..3efeb01e6e 100644 --- a/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs @@ -18,7 +18,7 @@ namespace Umbraco.Web.PropertyEditors "fileupload", Group = Constants.PropertyEditors.Groups.Media, Icon = "icon-download-alt")] - public class FileUploadPropertyEditor : DataEditor + public class FileUploadPropertyEditor : DataEditor, IDataEditorWithMediaPath { private readonly IMediaFileSystem _mediaFileSystem; private readonly IContentSection _contentSection; @@ -43,6 +43,9 @@ namespace Umbraco.Web.PropertyEditors return editor; } + public string GetMediaPath(Property property, string culture = null, string segment = null) => + property.GetValue(culture)?.ToString(); + /// /// Gets a value indicating whether a property is an upload field. /// diff --git a/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs index 9a8fb7c40b..1a4845c74e 100644 --- a/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs @@ -27,7 +27,7 @@ namespace Umbraco.Web.PropertyEditors HideLabel = false, Group = Constants.PropertyEditors.Groups.Media, Icon = "icon-crop")] - public class ImageCropperPropertyEditor : DataEditor + public class ImageCropperPropertyEditor : DataEditor, IDataEditorWithMediaPath { private readonly IMediaFileSystem _mediaFileSystem; private readonly IContentSection _contentSettings; @@ -48,6 +48,9 @@ namespace Umbraco.Web.PropertyEditors _autoFillProperties = new UploadAutoFillProperties(_mediaFileSystem, logger, _contentSettings); } + public string GetMediaPath(Property property, string culture = null, string segment = null) => + GetFileSrcFromPropertyValue(property.GetValue(culture), out _); + /// /// Creates the corresponding property value editor. /// From 26f1e77813642f3c2badc9bf3ef2a1cd7e1a0383 Mon Sep 17 00:00:00 2001 From: Rasmus John Pedersen Date: Wed, 30 Oct 2019 21:57:50 +0100 Subject: [PATCH 133/548] Resolve media paths from data editors # Conflicts: # src/Umbraco.Core/Persistence/Factories/ContentBaseFactory.cs # src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs --- src/Umbraco.Core/ContentExtensions.cs | 15 ++++---- src/Umbraco.Core/Models/MediaExtensions.cs | 35 ++++-------------- .../Factories/ContentBaseFactory.cs | 3 +- .../IDataEditorWithMediaPath.cs | 6 +-- .../Routing/MediaUrlProviderTests.cs | 29 +++++++++++---- .../Services/MediaServiceTests.cs | 2 +- .../Entities/MockedContentTypes.cs | 37 +++++++++++++++++-- .../FileUploadPropertyEditor.cs | 5 +-- .../ImageCropperPropertyEditor.cs | 10 ++--- .../Routing/DefaultMediaUrlProvider.cs | 32 +++++++++------- 10 files changed, 102 insertions(+), 72 deletions(-) diff --git a/src/Umbraco.Core/ContentExtensions.cs b/src/Umbraco.Core/ContentExtensions.cs index a7d40b0b7d..3edad0c963 100644 --- a/src/Umbraco.Core/ContentExtensions.cs +++ b/src/Umbraco.Core/ContentExtensions.cs @@ -13,6 +13,7 @@ using Umbraco.Core.IO; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.Models.Membership; +using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; @@ -23,6 +24,8 @@ namespace Umbraco.Core // this ain't pretty private static IMediaFileSystem _mediaFileSystem; private static IMediaFileSystem MediaFileSystem => _mediaFileSystem ?? (_mediaFileSystem = Current.MediaFileSystem); + private static readonly PropertyEditorCollection _propertyEditors; + private static PropertyEditorCollection PropertyEditors = _propertyEditors ?? (_propertyEditors = Current.PropertyEditors); #region IContent @@ -162,14 +165,12 @@ namespace Umbraco.Core // Fixes https://github.com/umbraco/Umbraco-CMS/issues/3937 - Assigning a new file to an // existing IMedia with extension SetValue causes exception 'Illegal characters in path' string oldpath = null; - if (property.GetValue(culture, segment) is string svalue) + var value = property.GetValue(culture, segment); + + if (PropertyEditors.TryGet(propertyTypeAlias, out var editor) + && editor is IDataEditorWithMediaPath dataEditor) { - if (svalue.DetectIsJson()) - { - // the property value is a JSON serialized image crop data set - grab the "src" property as the file source - var jObject = JsonConvert.DeserializeObject(svalue); - svalue = jObject != null ? jObject.GetValueAsString("src") : svalue; - } + var svalue = dataEditor.GetMediaPath(value); oldpath = MediaFileSystem.GetRelativePath(svalue); } diff --git a/src/Umbraco.Core/Models/MediaExtensions.cs b/src/Umbraco.Core/Models/MediaExtensions.cs index 1166698adb..96f183b6e6 100644 --- a/src/Umbraco.Core/Models/MediaExtensions.cs +++ b/src/Umbraco.Core/Models/MediaExtensions.cs @@ -1,10 +1,8 @@ -using System; -using System.Linq; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; +using System.Linq; +using Umbraco.Core.Composing; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; -using Umbraco.Core.PropertyEditors.ValueConverters; +using Umbraco.Core.PropertyEditors; namespace Umbraco.Core.Models { @@ -18,29 +16,12 @@ namespace Umbraco.Core.Models if (!media.Properties.TryGetValue(propertyAlias, out var property)) return string.Empty; - // TODO: would need to be adjusted to variations, when media become variants - if (!(property.GetValue() is string jsonString)) - return string.Empty; - - if (property.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.UploadField) - return jsonString; - - if (property.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.ImageCropper) + if (Current.PropertyEditors.TryGet(property.PropertyType.PropertyEditorAlias, out var editor) + && editor is IDataEditorWithMediaPath dataEditor) { - if (jsonString.DetectIsJson() == false) - return jsonString; - - try - { - var json = JsonConvert.DeserializeObject(jsonString); - if (json["src"] != null) - return json["src"].Value(); - } - catch (Exception ex) - { - logger.Error(ex, "Could not parse the string '{JsonString}' to a json object", jsonString); - return string.Empty; - } + // TODO: would need to be adjusted to variations, when media become variants + var value = property.GetValue(); + return dataEditor.GetMediaPath(value); } // Without knowing what it is, just adding a string here might not be very nice diff --git a/src/Umbraco.Core/Persistence/Factories/ContentBaseFactory.cs b/src/Umbraco.Core/Persistence/Factories/ContentBaseFactory.cs index f9221ea576..2b2bed1d9e 100644 --- a/src/Umbraco.Core/Persistence/Factories/ContentBaseFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/ContentBaseFactory.cs @@ -300,7 +300,8 @@ namespace Umbraco.Core.Persistence.Factories && propertyEditors.TryGet(property.PropertyType.PropertyEditorAlias, out var editor) && editor is IDataEditorWithMediaPath dataEditor) { - path = dataEditor.GetMediaPath(property); + var value = property.GetValue(); + path = dataEditor.GetMediaPath(value); } var dto = new MediaVersionDto diff --git a/src/Umbraco.Core/PropertyEditors/IDataEditorWithMediaPath.cs b/src/Umbraco.Core/PropertyEditors/IDataEditorWithMediaPath.cs index af42ebf4d6..aea7d28758 100644 --- a/src/Umbraco.Core/PropertyEditors/IDataEditorWithMediaPath.cs +++ b/src/Umbraco.Core/PropertyEditors/IDataEditorWithMediaPath.cs @@ -1,9 +1,7 @@ -using Umbraco.Core.Models; - -namespace Umbraco.Core.PropertyEditors +namespace Umbraco.Core.PropertyEditors { public interface IDataEditorWithMediaPath : IDataEditor { - string GetMediaPath(Property property, string culture = null, string segment = null); + string GetMediaPath(object value); } } diff --git a/src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs b/src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs index 6489417dc7..2f960d498d 100644 --- a/src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs +++ b/src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs @@ -4,13 +4,18 @@ using Moq; using Newtonsoft.Json; using NUnit.Framework; using Umbraco.Core; +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.PublishedContent; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing; +using Umbraco.Web.PropertyEditors; using Umbraco.Web.Routing; namespace Umbraco.Tests.Routing @@ -25,7 +30,17 @@ namespace Umbraco.Tests.Routing { base.SetUp(); - _mediaUrlProvider = new DefaultMediaUrlProvider(); + var logger = Mock.Of(); + var mediaFileSystemMock = Mock.Of(); + var contentSection = Mock.Of(); + var dataTypeService = Mock.Of(); + + var propertyEditors = new PropertyEditorCollection(new DataEditorCollection(new IDataEditor[] + { + new FileUploadPropertyEditor(logger, mediaFileSystemMock, contentSection), + new ImageCropperPropertyEditor(logger, mediaFileSystemMock, contentSection, dataTypeService), + })); + _mediaUrlProvider = new DefaultMediaUrlProvider(propertyEditors); } public override void TearDown() @@ -54,10 +69,10 @@ namespace Umbraco.Tests.Routing const string expected = "/media/rfeiw584/test.jpg"; var configuration = new ImageCropperConfiguration(); - var imageCropperValue = new ImageCropperValue + var imageCropperValue = JsonConvert.SerializeObject(new ImageCropperValue { Src = expected - }; + }); var umbracoContext = GetUmbracoContext("/", mediaUrlProviders: new[] { _mediaUrlProvider }); var publishedContent = CreatePublishedContent(Constants.PropertyEditors.Aliases.ImageCropper, imageCropperValue, configuration); @@ -121,8 +136,8 @@ namespace Umbraco.Tests.Routing PropertyType = umbracoFilePropertyType, }; - property.SetValue("en", enMediaUrl, true); - property.SetValue("da", daMediaUrl); + property.SetSourceValue("en", enMediaUrl, true); + property.SetSourceValue("da", daMediaUrl); var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty(), new [] { umbracoFilePropertyType }, ContentVariation.Culture); var publishedContent = new SolidPublishedContent(contentType) {Properties = new[] {property}}; @@ -131,7 +146,7 @@ namespace Umbraco.Tests.Routing Assert.AreEqual(daMediaUrl, resolvedUrl); } - private static IPublishedContent CreatePublishedContent(string propertyEditorAlias, object propertyValue, object dataTypeConfiguration) + private static IPublishedContent CreatePublishedContent(string propertyEditorAlias, string propertyValue, object dataTypeConfiguration) { var umbracoFilePropertyType = CreatePropertyType(propertyEditorAlias, dataTypeConfiguration, ContentVariation.Nothing); @@ -147,7 +162,7 @@ namespace Umbraco.Tests.Routing new SolidPublishedProperty { Alias = "umbracoFile", - SolidValue = propertyValue, + SolidSourceValue = propertyValue, SolidHasValue = true, PropertyType = umbracoFilePropertyType } diff --git a/src/Umbraco.Tests/Services/MediaServiceTests.cs b/src/Umbraco.Tests/Services/MediaServiceTests.cs index 17711fbd31..b3dc274c5e 100644 --- a/src/Umbraco.Tests/Services/MediaServiceTests.cs +++ b/src/Umbraco.Tests/Services/MediaServiceTests.cs @@ -184,7 +184,7 @@ namespace Umbraco.Tests.Services public void Can_Get_Media_With_Crop_By_Path() { var mediaService = ServiceContext.MediaService; - var mediaType = MockedContentTypes.CreateImageMediaType("Image2"); + var mediaType = MockedContentTypes.CreateImageMediaTypeWithCrop("Image2"); ServiceContext.MediaTypeService.Save(mediaType); var media = MockedMedia.CreateMediaImageWithCrop(mediaType, -1); diff --git a/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs b/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs index e93e8e8740..41786a5fd7 100644 --- a/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs +++ b/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs @@ -420,10 +420,39 @@ namespace Umbraco.Tests.TestHelpers.Entities var contentCollection = new PropertyTypeCollection(false); contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.UploadField, ValueStorageType.Nvarchar) { Alias = Constants.Conventions.Media.File, Name = "File", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -90 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Width, Name = "Width", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -90 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Height, Name = "Height", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -90 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Bytes, Name = "Bytes", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -90 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Nvarchar) { Alias = Constants.Conventions.Media.Extension, Name = "File Extension", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -90 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Width, Name = "Width", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -92 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Height, Name = "Height", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -92 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Bytes, Name = "Bytes", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -92 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Nvarchar) { Alias = Constants.Conventions.Media.Extension, Name = "File Extension", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -92 }); + + mediaType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Media", SortOrder = 1 }); + + //ensure that nothing is marked as dirty + mediaType.ResetDirtyProperties(false); + + return mediaType; + } + + public static MediaType CreateImageMediaTypeWithCrop(string alias = Constants.Conventions.MediaTypes.Image) + { + var mediaType = new MediaType(-1) + { + Alias = alias, + Name = "Image", + Description = "ContentType used for images", + Icon = ".sprTreeDoc3", + Thumbnail = "doc.png", + SortOrder = 1, + CreatorId = 0, + Trashed = false + }; + + var contentCollection = new PropertyTypeCollection(false); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.ImageCropper, ValueStorageType.Ntext) { Alias = Constants.Conventions.Media.File, Name = "File", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = 1043 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Width, Name = "Width", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -92 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Height, Name = "Height", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -92 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Bytes, Name = "Bytes", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -92 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Nvarchar) { Alias = Constants.Conventions.Media.Extension, Name = "File Extension", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -92 }); mediaType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Media", SortOrder = 1 }); diff --git a/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs index 3efeb01e6e..052af18aa1 100644 --- a/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs @@ -43,8 +43,7 @@ namespace Umbraco.Web.PropertyEditors return editor; } - public string GetMediaPath(Property property, string culture = null, string segment = null) => - property.GetValue(culture)?.ToString(); + public string GetMediaPath(object value) => value?.ToString(); /// /// Gets a value indicating whether a property is an upload field. @@ -55,7 +54,7 @@ namespace Umbraco.Web.PropertyEditors { return property.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.UploadField; } - + /// /// Ensures any files associated are removed /// diff --git a/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs index 1a4845c74e..b0e5bf30bd 100644 --- a/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs @@ -48,8 +48,7 @@ namespace Umbraco.Web.PropertyEditors _autoFillProperties = new UploadAutoFillProperties(_mediaFileSystem, logger, _contentSettings); } - public string GetMediaPath(Property property, string culture = null, string segment = null) => - GetFileSrcFromPropertyValue(property.GetValue(culture), out _); + public string GetMediaPath(object value) => GetFileSrcFromPropertyValue(value, out _, false); /// /// Creates the corresponding property value editor. @@ -66,7 +65,7 @@ namespace Umbraco.Web.PropertyEditors /// /// Gets a value indicating whether a property is an image cropper field. /// - /// The property. + /// The property. /// A value indicating whether a property is an image cropper field, and (optionally) has a non-empty value. private static bool IsCropperField(Property property) { @@ -135,8 +134,9 @@ namespace Umbraco.Web.PropertyEditors /// /// /// The deserialized value + /// Should the path returned be the application relative path /// - private string GetFileSrcFromPropertyValue(object propVal, out JObject deserializedValue) + private string GetFileSrcFromPropertyValue(object propVal, out JObject deserializedValue, bool relative = true) { deserializedValue = null; if (propVal == null || !(propVal is string str)) return null; @@ -144,7 +144,7 @@ namespace Umbraco.Web.PropertyEditors deserializedValue = GetJObject(str, true); if (deserializedValue?["src"] == null) return null; var src = deserializedValue["src"].Value(); - return _mediaFileSystem.GetRelativePath(src); + return relative ? _mediaFileSystem.GetRelativePath(src) : src; } /// diff --git a/src/Umbraco.Web/Routing/DefaultMediaUrlProvider.cs b/src/Umbraco.Web/Routing/DefaultMediaUrlProvider.cs index 02dc4ebf29..dacd314f94 100644 --- a/src/Umbraco.Web/Routing/DefaultMediaUrlProvider.cs +++ b/src/Umbraco.Web/Routing/DefaultMediaUrlProvider.cs @@ -1,7 +1,7 @@ using System; -using Umbraco.Core; +using Umbraco.Core.Composing; using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.PropertyEditors.ValueConverters; +using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.Routing { @@ -10,13 +10,24 @@ namespace Umbraco.Web.Routing /// public class DefaultMediaUrlProvider : IMediaUrlProvider { + private readonly PropertyEditorCollection _propertyEditors; + + public DefaultMediaUrlProvider(PropertyEditorCollection propertyEditors) + { + _propertyEditors = propertyEditors ?? throw new ArgumentNullException(nameof(propertyEditors)); + } + + [Obsolete("Use the constructor with all parameters instead")] + public DefaultMediaUrlProvider() : this(Current.PropertyEditors) + { + } + /// public virtual UrlInfo GetMediaUrl(UmbracoContext umbracoContext, IPublishedContent content, - string propertyAlias, - UrlMode mode, string culture, Uri current) + string propertyAlias, UrlMode mode, string culture, Uri current) { var prop = content.GetProperty(propertyAlias); - var value = prop?.GetValue(culture); + var value = prop?.GetSourceValue(culture); if (value == null) { return null; @@ -25,15 +36,10 @@ namespace Umbraco.Web.Routing var propType = prop.PropertyType; string path = null; - switch (propType.EditorAlias) + if (_propertyEditors.TryGet(propType.EditorAlias, out var editor) + && editor is IDataEditorWithMediaPath dataEditor) { - case Constants.PropertyEditors.Aliases.UploadField: - path = value.ToString(); - break; - case Constants.PropertyEditors.Aliases.ImageCropper: - //get the url from the json format - path = value is ImageCropperValue stronglyTyped ? stronglyTyped.Src : value.ToString(); - break; + path = dataEditor.GetMediaPath(value); } var url = AssembleUrl(path, current, mode); From 3d66c201c2100be22a606432242fdf633bfe6db7 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 31 Oct 2019 14:08:38 +1100 Subject: [PATCH 134/548] Adds safety check of Udi.TryParse --- src/Umbraco.Web/PropertyEditors/ContentPickerPropertyEditor.cs | 3 ++- src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs | 3 ++- .../PropertyEditors/MultiNodeTreePickerPropertyEditor.cs | 3 ++- src/Umbraco.Web/PropertyEditors/MultiUrlPickerValueEditor.cs | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web/PropertyEditors/ContentPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/ContentPickerPropertyEditor.cs index c09b3b6930..683f1a05c3 100644 --- a/src/Umbraco.Web/PropertyEditors/ContentPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ContentPickerPropertyEditor.cs @@ -41,7 +41,8 @@ namespace Umbraco.Web.PropertyEditors if (string.IsNullOrEmpty(asString)) yield break; - yield return new UmbracoEntityReference(Udi.Parse(asString)); + if (Udi.TryParse(asString, out var udi)) + yield return new UmbracoEntityReference(udi); } } } diff --git a/src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs index be1a9b2691..ece210b9d1 100644 --- a/src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs @@ -45,7 +45,8 @@ namespace Umbraco.Web.PropertyEditors if (string.IsNullOrEmpty(asString)) yield break; - yield return new UmbracoEntityReference(Udi.Parse(asString)); + if (Udi.TryParse(asString, 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 020ad38c75..1da665a622 100644 --- a/src/Umbraco.Web/PropertyEditors/MultiNodeTreePickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MultiNodeTreePickerPropertyEditor.cs @@ -37,7 +37,8 @@ namespace Umbraco.Web.PropertyEditors var udiPaths = asString.Split(','); foreach (var udiPath in udiPaths) { - yield return new UmbracoEntityReference(Udi.Parse(udiPath)); + if (Udi.TryParse(udiPath, out var udi)) + yield return new UmbracoEntityReference(udi); } } diff --git a/src/Umbraco.Web/PropertyEditors/MultiUrlPickerValueEditor.cs b/src/Umbraco.Web/PropertyEditors/MultiUrlPickerValueEditor.cs index 98f41c50b9..9c42fe6cbe 100644 --- a/src/Umbraco.Web/PropertyEditors/MultiUrlPickerValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MultiUrlPickerValueEditor.cs @@ -181,7 +181,7 @@ namespace Umbraco.Web.PropertyEditors if (string.IsNullOrEmpty(asString)) yield break; - var links = JsonConvert.DeserializeObject>(asString); + var links = JsonConvert.DeserializeObject>(asString); foreach (var link in links) { if (link.Udi != null) // Links can be absolute links without a Udi From b22dba2062e99c8a9a404cac8d018c92f60912d3 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Thu, 31 Oct 2019 08:00:20 +0100 Subject: [PATCH 135/548] Use warning button style when opening doctype in infinite editing with unsaved changes --- .../components/content/umbcontentnodeinfo.directive.js | 1 + 1 file changed, 1 insertion(+) 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..0ed1b12000 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); From 0819f1a917f8ca6e390c16c93c22541a64117b9f Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Thu, 31 Oct 2019 08:08:08 +0100 Subject: [PATCH 136/548] Mark the current variant as dirty when its name changes --- .../components/content/umbvariantcontent.directive.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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..6fec20b256 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 @@ -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; } })); From 486dd0b8eaacbface476cac7aa7738aca0b95f61 Mon Sep 17 00:00:00 2001 From: Steve Megson Date: Thu, 31 Oct 2019 11:39:52 +0000 Subject: [PATCH 137/548] PublishedSnapshotService.OnMemberRefreshedEntity stores data as published rather than edited --- .../PublishedCache/NuCache/PublishedSnapshotService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs index e76b526492..742841aeb5 100755 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs @@ -1218,7 +1218,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) From f2d7a25e0f7b89e43df9a2999fcd868c196c6fd3 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Thu, 31 Oct 2019 15:52:48 +0100 Subject: [PATCH 138/548] Make macro partial view mandatory --- src/Umbraco.Web.UI.Client/src/views/macros/views/settings.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/macros/views/settings.html b/src/Umbraco.Web.UI.Client/src/views/macros/views/settings.html index d34cf1810d..7706734327 100644 --- a/src/Umbraco.Web.UI.Client/src/views/macros/views/settings.html +++ b/src/Umbraco.Web.UI.Client/src/views/macros/views/settings.html @@ -11,7 +11,7 @@ - + + Date: Thu, 31 Oct 2019 20:03:03 +0100 Subject: [PATCH 139/548] Evenly distribute link URL and anchor inputs in linkpicker --- .../src/less/property-editors.less | 15 +++++++++++++++ .../infiniteeditors/linkpicker/linkpicker.html | 6 +++--- 2 files changed, 18 insertions(+), 3 deletions(-) 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 dc2d402671..6371e51944 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -877,6 +877,21 @@ .umb-datepicker p {margin-top:10px;} .umb-datepicker p a{color: @gray-3;} +// +// Link picker +// -------------------------------------------------- +.umb-linkpicker { + .umb-linkpicker__url { + width: 50%; + padding-right: 5px; + } + + .umb-linkpicker__anchor { + width: 50%; + padding-left: 5px; + } +} + // // Code mirror - even though this isn't a proprety editor right now, it could be so I'm putting the styles here // -------------------------------------------------- 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 6ba2ec0270..a7d2dbbee2 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 @@ -1,4 +1,4 @@ -
+
@@ -16,7 +16,7 @@
- + - + Date: Thu, 31 Oct 2019 20:03:56 +0100 Subject: [PATCH 140/548] Angular API documentation: Adds styling for elements --- src/Umbraco.Web.UI.Docs/umb-docs.css | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Umbraco.Web.UI.Docs/umb-docs.css b/src/Umbraco.Web.UI.Docs/umb-docs.css index eef0fcee39..931c22b255 100644 --- a/src/Umbraco.Web.UI.Docs/umb-docs.css +++ b/src/Umbraco.Web.UI.Docs/umb-docs.css @@ -76,3 +76,12 @@ a:hover { width: 50px; margin-top: 5px; } + +.content .methods code { + border: 1px solid #e1e1e8; + background-color: #f7f7f9; + padding: 0px 3px; + font-size: 85%; + color: #d14; + white-space: nowrap; +} From 065e3c1ba3644fd1390853bec94eec4e2f11e43d Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 1 Nov 2019 11:33:15 +1100 Subject: [PATCH 141/548] removes the MB package --- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 555e11f4bc..0091d60545 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -81,7 +81,7 @@ - + @@ -111,9 +111,6 @@ runtime; build; native; contentfiles; analyzers all - - 8.1.0 - @@ -432,4 +429,4 @@ - + \ No newline at end of file From 717c185e45636c1898beb1eef8988f93707e000f Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 1 Nov 2019 14:22:49 +1100 Subject: [PATCH 142/548] Manually merges prev changes to nested content from 6768 and 6826 and changes how the prop values are iterated --- .../Published/NestedContentTests.cs | 2 +- .../NestedContentPropertyEditor.cs | 291 ++++++++++-------- 2 files changed, 166 insertions(+), 127 deletions(-) diff --git a/src/Umbraco.Tests/Published/NestedContentTests.cs b/src/Umbraco.Tests/Published/NestedContentTests.cs index 2bba37c7d3..a20053c295 100644 --- a/src/Umbraco.Tests/Published/NestedContentTests.cs +++ b/src/Umbraco.Tests/Published/NestedContentTests.cs @@ -34,7 +34,7 @@ namespace Umbraco.Tests.Published var proflog = new ProfilingLogger(logger, profiler); PropertyEditorCollection editors = null; - var editor = new NestedContentPropertyEditor(logger, new Lazy(() => editors), Mock.Of()); + 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.Web/PropertyEditors/NestedContentPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs index 7104df0775..1f89abdcdd 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; @@ -29,26 +30,20 @@ namespace Umbraco.Web.PropertyEditors { private readonly Lazy _propertyEditors; private readonly IDataTypeService _dataTypeService; + private readonly IContentTypeService _contentTypeService; internal const string ContentTypeAliasPropertyKey = "ncContentTypeAlias"; - public NestedContentPropertyEditor(ILogger logger, Lazy propertyEditors, IDataTypeService dataTypeService) + 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 private PropertyEditorCollection PropertyEditors => _propertyEditors.Value; - private static IContentType GetElementType(JObject item) - { - var contentTypeAlias = item[ContentTypeAliasPropertyKey]?.ToObject(); - return string.IsNullOrEmpty(contentTypeAlias) - ? null - : Current.Services.ContentTypeService.Get(contentTypeAlias); - } - #region Pre Value Editor protected override IConfigurationEditor CreateConfigurationEditor() => new NestedContentConfigurationEditor(); @@ -57,19 +52,21 @@ namespace Umbraco.Web.PropertyEditors #region Value Editor - protected override IDataValueEditor CreateValueEditor() => new NestedContentPropertyValueEditor(Attribute, PropertyEditors, _dataTypeService); + protected override IDataValueEditor CreateValueEditor() => new NestedContentPropertyValueEditor(Attribute, PropertyEditors, _dataTypeService, _contentTypeService); internal class NestedContentPropertyValueEditor : DataValueEditor, IDataValueReference { private readonly PropertyEditorCollection _propertyEditors; private readonly IDataTypeService _dataTypeService; - - public NestedContentPropertyValueEditor(DataEditorAttribute attribute, PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService) + private readonly NestedContentValues _nestedContentValues; + + public NestedContentPropertyValueEditor(DataEditorAttribute attribute, PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, IContentTypeService contentTypeService) : base(attribute) { _propertyEditors = propertyEditors; _dataTypeService = dataTypeService; - Validators.Add(new NestedContentValidator(propertyEditors, dataTypeService)); + _nestedContentValues = new NestedContentValues(contentTypeService); + Validators.Add(new NestedContentValidator(propertyEditors, dataTypeService, _nestedContentValues)); } /// @@ -88,86 +85,45 @@ namespace Umbraco.Web.PropertyEditors } } - /// - /// Method used to iterate over the deserialized property values - /// - /// - /// - internal static List IteratePropertyValues(object propertyValue, Action onIteration) - { - if (propertyValue == null || string.IsNullOrWhiteSpace(propertyValue.ToString())) - return null; - - var value = 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 (value == null || value.Count == 0) - return null; - - var index = 0; - - foreach (var o in value) - { - var propValues = o; - - // TODO: This is N+1 (although we cache all doc types, it's still not pretty) - 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); - onIteration(propAlias, propType, propValues, index); - } - - index++; - } - - return value; - } - #region DB to String public override string ConvertDbToString(PropertyType propertyType, object propertyValue, IDataTypeService dataTypeService) { - var value = IteratePropertyValues(propertyValue, (string propAlias, PropertyType propType, JObject propValues, int index) => + var vals = _nestedContentValues.GetPropertyValues(propertyValue).ToList(); + + if (vals.Count == 0) + return string.Empty; + + foreach (var row in vals) { - if (propType == null) + if (row.PropType == null) { // type not found, and property is not system: just delete the value - if (IsSystemPropertyKey(propAlias) == false) - propValues[propAlias] = null; + if (IsSystemPropertyKey(row.PropKey) == false) + row.PropValues[row.PropKey] = null; } else { try { // convert the value, and store the converted value - var propEditor = _propertyEditors[propType.PropertyEditorAlias]; - var tempConfig = dataTypeService.GetDataType(propType.DataTypeId).Configuration; + 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(propType, propValues[propAlias]?.ToString(), dataTypeService); - propValues[propAlias] = convValue; + var convValue = valEditor.ConvertDbToString(row.PropType, row.PropValues[row.PropKey]?.ToString(), dataTypeService); + row.PropValues[row.PropKey] = convValue; } catch (InvalidOperationException) { // deal with weird situations by ignoring them (no comment) - propValues[propAlias] = null; + row.PropValues[row.PropKey] = null; } } - }); + } - if (value == null) - return string.Empty; - - return JsonConvert.SerializeObject(value).ToXmlString(); + return JsonConvert.SerializeObject(vals).ToXmlString(); } #endregion @@ -182,13 +138,18 @@ namespace Umbraco.Web.PropertyEditors { var val = property.GetValue(culture, segment); - var value = IteratePropertyValues(val, (string propAlias, PropertyType propType, JObject propValues, int index) => + var vals = _nestedContentValues.GetPropertyValues(val).ToList(); + + if (vals.Count == 0) + return string.Empty; + + foreach (var row in vals) { - if (propType == null) + if (row.PropType == null) { // type not found, and property is not system: just delete the value - if (IsSystemPropertyKey(propAlias) == false) - propValues[propAlias] = null; + if (IsSystemPropertyKey(row.PropKey) == false) + row.PropValues[row.PropKey] = null; } else { @@ -196,30 +157,33 @@ namespace Umbraco.Web.PropertyEditors { // 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()); + row.PropType.Variations = ContentVariation.Nothing; + var tempProp = new Property(row.PropType); + tempProp.SetValue(row.PropValues[row.PropKey] == null ? null : row.PropValues[row.PropKey].ToString()); // convert that temp property, and store the converted value - var propEditor = _propertyEditors[propType.PropertyEditorAlias]; - var tempConfig = dataTypeService.GetDataType(propType.DataTypeId).Configuration; + var propEditor = _propertyEditors[row.PropType.PropertyEditorAlias]; + if (propEditor == null) + { + row.PropValues[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); - propValues[propAlias] = convValue == null ? null : JToken.FromObject(convValue); + row.PropValues[row.PropKey] = convValue == null ? null : JToken.FromObject(convValue); } catch (InvalidOperationException) { // deal with weird situations by ignoring them (no comment) - propValues[propAlias] = null; + row.PropValues[row.PropKey] = null; } } - }); - - if (value == null) - return string.Empty; + } // return json - return value; + return vals; } public override object FromEditor(ContentPropertyData editorValue, object currentValue) @@ -227,38 +191,41 @@ namespace Umbraco.Web.PropertyEditors if (editorValue.Value == null || string.IsNullOrWhiteSpace(editorValue.Value.ToString())) return null; - var value = IteratePropertyValues(editorValue.Value, (string propAlias, PropertyType propType, JObject propValues, int index) => + var vals = _nestedContentValues.GetPropertyValues(editorValue.Value).ToList(); + + if (vals.Count == 0) + return string.Empty; + + foreach (var row in vals) { - if (propType == null) + if (row.PropType == null) { // type not found, and property is not system: just delete the value - if (IsSystemPropertyKey(propAlias) == false) - propValues[propAlias] = null; + if (IsSystemPropertyKey(row.PropKey) == false) + row.PropValues[row.PropKey] = null; } else { // Fetch the property types prevalue - var propConfiguration = _dataTypeService.GetDataType(propType.DataTypeId).Configuration; + var propConfiguration = _dataTypeService.GetDataType(row.PropType.DataTypeId).Configuration; // Lookup the property editor - var propEditor = _propertyEditors[propType.PropertyEditorAlias]; + var propEditor = _propertyEditors[row.PropType.PropertyEditorAlias]; + if (propEditor == null) continue; // Create a fake content property data object - var contentPropData = new ContentPropertyData(propValues[propAlias], propConfiguration); + var contentPropData = new ContentPropertyData(row.PropValues[row.PropKey], propConfiguration); // Get the property editor to do it's conversion - var newValue = propEditor.GetValueEditor().FromEditor(contentPropData, propValues[propAlias]); + var newValue = propEditor.GetValueEditor().FromEditor(contentPropData, row.PropValues[row.PropKey]); // Store the value back - propValues[propAlias] = (newValue == null) ? null : JToken.FromObject(newValue); + row.PropValues[row.PropKey] = (newValue == null) ? null : JToken.FromObject(newValue); } - }); - - if (value == null) - return string.Empty; + } // return json - return JsonConvert.SerializeObject(value); + return JsonConvert.SerializeObject(vals); } public IEnumerable GetReferences(object value) @@ -267,21 +234,21 @@ namespace Umbraco.Web.PropertyEditors var result = new List(); - var json = IteratePropertyValues(rawJson, (string propAlias, PropertyType propType, JObject propValues, int index) => + foreach (var row in _nestedContentValues.GetPropertyValues(rawJson)) { - if (propType == null) return; + if (row.PropType == null) continue; - var propEditor = _propertyEditors[propType.PropertyEditorAlias]; + var propEditor = _propertyEditors[row.PropType.PropertyEditorAlias]; var valueEditor = propEditor?.GetValueEditor(); - if (!(valueEditor is IDataValueReference reference)) return; + if (!(valueEditor is IDataValueReference reference)) continue; - var val = propValues[propAlias]?.ToString(); + var val = row.PropValues[row.PropKey]?.ToString(); var refs = reference.GetReferences(val); result.AddRange(refs); - }); + } return result; } @@ -293,58 +260,130 @@ namespace Umbraco.Web.PropertyEditors { private readonly PropertyEditorCollection _propertyEditors; private readonly IDataTypeService _dataTypeService; + private readonly NestedContentValues _nestedContentValues; - public NestedContentValidator(PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService) + public NestedContentValidator(PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, NestedContentValues nestedContentValues) { _propertyEditors = propertyEditors; _dataTypeService = dataTypeService; + _nestedContentValues = nestedContentValues; } public IEnumerable Validate(object rawValue, string valueType, object dataTypeConfiguration) { var validationResults = new List(); - NestedContentPropertyValueEditor.IteratePropertyValues(rawValue, (string propKey, PropertyType propType, JObject propValues, int i) => + foreach(var row in _nestedContentValues.GetPropertyValues(rawValue)) { - if (propType == null) return; + if (row.PropType == null) continue; - var config = _dataTypeService.GetDataType(propType.DataTypeId).Configuration; - var propertyEditor = _propertyEditors[propType.PropertyEditorAlias]; + var config = _dataTypeService.GetDataType(row.PropType.DataTypeId).Configuration; + var propertyEditor = _propertyEditors[row.PropType.PropertyEditorAlias]; + if (propertyEditor == null) continue; foreach (var validator in propertyEditor.GetValueEditor().Validators) { - foreach (var result in validator.Validate(propValues[propKey], propertyEditor.GetValueEditor().ValueType, config)) + foreach (var result in validator.Validate(row.PropValues[row.PropKey], propertyEditor.GetValueEditor().ValueType, config)) { - result.ErrorMessage = "Item " + (i + 1) + " '" + propType.Name + "' " + result.ErrorMessage; + result.ErrorMessage = "Item " + (row.Index + 1) + " '" + row.PropType.Name + "' " + result.ErrorMessage; validationResults.Add(result); } } // Check mandatory - if (propType.Mandatory) + if (row.PropType.Mandatory) { - if (propValues[propKey] == null) - validationResults.Add(new ValidationResult("Item " + (i + 1) + " '" + propType.Name + "' cannot be null", new[] { propKey })); - else if (propValues[propKey].ToString().IsNullOrWhiteSpace() || (propValues[propKey].Type == JTokenType.Array && !propValues[propKey].HasValues)) - validationResults.Add(new ValidationResult("Item " + (i + 1) + " '" + propType.Name + "' cannot be empty", new[] { propKey })); + if (row.PropValues[row.PropKey] == null) + validationResults.Add(new ValidationResult("Item " + (row.Index + 1) + " '" + row.PropType.Name + "' cannot be null", new[] { row.PropKey })); + else if (row.PropValues[row.PropKey].ToString().IsNullOrWhiteSpace() || (row.PropValues[row.PropKey].Type == JTokenType.Array && !row.PropValues[row.PropKey].HasValues)) + validationResults.Add(new ValidationResult("Item " + (row.Index + 1) + " '" + row.PropType.Name + "' cannot be empty", new[] { row.PropKey })); } // Check regex - if (!propType.ValidationRegExp.IsNullOrWhiteSpace() - && propValues[propKey] != null && !propValues[propKey].ToString().IsNullOrWhiteSpace()) + if (!row.PropType.ValidationRegExp.IsNullOrWhiteSpace() + && row.PropValues[row.PropKey] != null && !row.PropValues[row.PropKey].ToString().IsNullOrWhiteSpace()) { - var regex = new Regex(propType.ValidationRegExp); - if (!regex.IsMatch(propValues[propKey].ToString())) + var regex = new Regex(row.PropType.ValidationRegExp); + if (!regex.IsMatch(row.PropValues[row.PropKey].ToString())) { - validationResults.Add(new ValidationResult("Item " + (i + 1) + " '" + propType.Name + "' is invalid, it does not match the correct pattern", new[] { propKey })); + validationResults.Add(new ValidationResult("Item " + (row.Index + 1) + " '" + row.PropType.Name + "' is invalid, it does not match the correct pattern", 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) + { + if (propertyValue == null || string.IsNullOrWhiteSpace(propertyValue.ToString())) + yield break; + + var value = 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 (value == null || value.Count == 0) + yield break; + + var index = 0; + + foreach (var o in value) + { + 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); + yield return new RowValue(propAlias, propType, propValues, index); + } + index++; + } + } + + internal class RowValue + { + public RowValue(string propKey, PropertyType propType, JObject propValues, int index) + { + PropKey = propKey ?? throw new ArgumentNullException(nameof(propKey)); + PropType = propType ?? throw new ArgumentNullException(nameof(propType)); + PropValues = propValues ?? throw new ArgumentNullException(nameof(propValues)); + Index = index; + } + + public string PropKey { get; } + public PropertyType PropType { get; } + public JObject PropValues { get; } + public int Index { get; } + } + } + #endregion private static bool IsSystemPropertyKey(string propKey) From d1ea46ff71d7c13025b172292e8817add685c6ab Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 1 Nov 2019 14:45:05 +1100 Subject: [PATCH 143/548] fixes null ref check and returning the correct deserialized val, adds comments --- .../NestedContentPropertyEditor.cs | 109 +++++++++++------- 1 file changed, 65 insertions(+), 44 deletions(-) diff --git a/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs index 1f89abdcdd..3d0605c4f9 100644 --- a/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs @@ -89,7 +89,7 @@ namespace Umbraco.Web.PropertyEditors public override string ConvertDbToString(PropertyType propertyType, object propertyValue, IDataTypeService dataTypeService) { - var vals = _nestedContentValues.GetPropertyValues(propertyValue).ToList(); + var vals = _nestedContentValues.GetPropertyValues(propertyValue, out var deserialized).ToList(); if (vals.Count == 0) return string.Empty; @@ -100,7 +100,7 @@ namespace Umbraco.Web.PropertyEditors { // type not found, and property is not system: just delete the value if (IsSystemPropertyKey(row.PropKey) == false) - row.PropValues[row.PropKey] = null; + row.JsonRowValue[row.PropKey] = null; } else { @@ -112,18 +112,18 @@ namespace Umbraco.Web.PropertyEditors var tempConfig = dataTypeService.GetDataType(row.PropType.DataTypeId).Configuration; var valEditor = propEditor.GetValueEditor(tempConfig); - var convValue = valEditor.ConvertDbToString(row.PropType, row.PropValues[row.PropKey]?.ToString(), dataTypeService); - row.PropValues[row.PropKey] = convValue; + var convValue = valEditor.ConvertDbToString(row.PropType, row.JsonRowValue[row.PropKey]?.ToString(), dataTypeService); + row.JsonRowValue[row.PropKey] = convValue; } catch (InvalidOperationException) { // deal with weird situations by ignoring them (no comment) - row.PropValues[row.PropKey] = null; + row.JsonRowValue[row.PropKey] = null; } } } - return JsonConvert.SerializeObject(vals).ToXmlString(); + return JsonConvert.SerializeObject(deserialized).ToXmlString(); } #endregion @@ -138,7 +138,7 @@ namespace Umbraco.Web.PropertyEditors { var val = property.GetValue(culture, segment); - var vals = _nestedContentValues.GetPropertyValues(val).ToList(); + var vals = _nestedContentValues.GetPropertyValues(val, out var deserialized).ToList(); if (vals.Count == 0) return string.Empty; @@ -149,7 +149,7 @@ namespace Umbraco.Web.PropertyEditors { // type not found, and property is not system: just delete the value if (IsSystemPropertyKey(row.PropKey) == false) - row.PropValues[row.PropKey] = null; + row.JsonRowValue[row.PropKey] = null; } else { @@ -159,31 +159,31 @@ namespace Umbraco.Web.PropertyEditors // - 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.PropValues[row.PropKey] == null ? null : row.PropValues[row.PropKey].ToString()); + 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.PropValues[row.PropKey] = tempProp.GetValue()?.ToString(); + 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.PropValues[row.PropKey] = convValue == null ? null : JToken.FromObject(convValue); + row.JsonRowValue[row.PropKey] = convValue == null ? null : JToken.FromObject(convValue); } catch (InvalidOperationException) { // deal with weird situations by ignoring them (no comment) - row.PropValues[row.PropKey] = null; + row.JsonRowValue[row.PropKey] = null; } } } // return json - return vals; + return deserialized; } public override object FromEditor(ContentPropertyData editorValue, object currentValue) @@ -191,7 +191,7 @@ namespace Umbraco.Web.PropertyEditors if (editorValue.Value == null || string.IsNullOrWhiteSpace(editorValue.Value.ToString())) return null; - var vals = _nestedContentValues.GetPropertyValues(editorValue.Value).ToList(); + var vals = _nestedContentValues.GetPropertyValues(editorValue.Value, out var deserialized).ToList(); if (vals.Count == 0) return string.Empty; @@ -202,7 +202,7 @@ namespace Umbraco.Web.PropertyEditors { // type not found, and property is not system: just delete the value if (IsSystemPropertyKey(row.PropKey) == false) - row.PropValues[row.PropKey] = null; + row.JsonRowValue[row.PropKey] = null; } else { @@ -214,18 +214,18 @@ namespace Umbraco.Web.PropertyEditors if (propEditor == null) continue; // Create a fake content property data object - var contentPropData = new ContentPropertyData(row.PropValues[row.PropKey], propConfiguration); + var contentPropData = new ContentPropertyData(row.JsonRowValue[row.PropKey], propConfiguration); // Get the property editor to do it's conversion - var newValue = propEditor.GetValueEditor().FromEditor(contentPropData, row.PropValues[row.PropKey]); + var newValue = propEditor.GetValueEditor().FromEditor(contentPropData, row.JsonRowValue[row.PropKey]); // Store the value back - row.PropValues[row.PropKey] = (newValue == null) ? null : JToken.FromObject(newValue); + row.JsonRowValue[row.PropKey] = (newValue == null) ? null : JToken.FromObject(newValue); } } // return json - return JsonConvert.SerializeObject(vals); + return JsonConvert.SerializeObject(deserialized); } public IEnumerable GetReferences(object value) @@ -234,7 +234,7 @@ namespace Umbraco.Web.PropertyEditors var result = new List(); - foreach (var row in _nestedContentValues.GetPropertyValues(rawJson)) + foreach (var row in _nestedContentValues.GetPropertyValues(rawJson, out _)) { if (row.PropType == null) continue; @@ -243,7 +243,7 @@ namespace Umbraco.Web.PropertyEditors var valueEditor = propEditor?.GetValueEditor(); if (!(valueEditor is IDataValueReference reference)) continue; - var val = row.PropValues[row.PropKey]?.ToString(); + var val = row.JsonRowValue[row.PropKey]?.ToString(); var refs = reference.GetReferences(val); @@ -273,7 +273,7 @@ namespace Umbraco.Web.PropertyEditors { var validationResults = new List(); - foreach(var row in _nestedContentValues.GetPropertyValues(rawValue)) + foreach(var row in _nestedContentValues.GetPropertyValues(rawValue, out _)) { if (row.PropType == null) continue; @@ -283,9 +283,9 @@ namespace Umbraco.Web.PropertyEditors foreach (var validator in propertyEditor.GetValueEditor().Validators) { - foreach (var result in validator.Validate(row.PropValues[row.PropKey], propertyEditor.GetValueEditor().ValueType, config)) + foreach (var result in validator.Validate(row.JsonRowValue[row.PropKey], propertyEditor.GetValueEditor().ValueType, config)) { - result.ErrorMessage = "Item " + (row.Index + 1) + " '" + row.PropType.Name + "' " + result.ErrorMessage; + result.ErrorMessage = "Item " + (row.RowIndex + 1) + " '" + row.PropType.Name + "' " + result.ErrorMessage; validationResults.Add(result); } } @@ -293,20 +293,20 @@ namespace Umbraco.Web.PropertyEditors // Check mandatory if (row.PropType.Mandatory) { - if (row.PropValues[row.PropKey] == null) - validationResults.Add(new ValidationResult("Item " + (row.Index + 1) + " '" + row.PropType.Name + "' cannot be null", new[] { row.PropKey })); - else if (row.PropValues[row.PropKey].ToString().IsNullOrWhiteSpace() || (row.PropValues[row.PropKey].Type == JTokenType.Array && !row.PropValues[row.PropKey].HasValues)) - validationResults.Add(new ValidationResult("Item " + (row.Index + 1) + " '" + row.PropType.Name + "' cannot be empty", new[] { row.PropKey })); + if (row.JsonRowValue[row.PropKey] == null) + validationResults.Add(new ValidationResult("Item " + (row.RowIndex + 1) + " '" + row.PropType.Name + "' cannot be null", new[] { row.PropKey })); + else if (row.JsonRowValue[row.PropKey].ToString().IsNullOrWhiteSpace() || (row.JsonRowValue[row.PropKey].Type == JTokenType.Array && !row.JsonRowValue[row.PropKey].HasValues)) + validationResults.Add(new ValidationResult("Item " + (row.RowIndex + 1) + " '" + row.PropType.Name + "' cannot be empty", new[] { row.PropKey })); } // Check regex if (!row.PropType.ValidationRegExp.IsNullOrWhiteSpace() - && row.PropValues[row.PropKey] != null && !row.PropValues[row.PropKey].ToString().IsNullOrWhiteSpace()) + && row.JsonRowValue[row.PropKey] != null && !row.JsonRowValue[row.PropKey].ToString().IsNullOrWhiteSpace()) { var regex = new Regex(row.PropType.ValidationRegExp); - if (!regex.IsMatch(row.PropValues[row.PropKey].ToString())) + if (!regex.IsMatch(row.JsonRowValue[row.PropKey].ToString())) { - validationResults.Add(new ValidationResult("Item " + (row.Index + 1) + " '" + row.PropType.Name + "' is invalid, it does not match the correct pattern", new[] { row.PropKey })); + validationResults.Add(new ValidationResult("Item " + (row.RowIndex + 1) + " '" + row.PropType.Name + "' is invalid, it does not match the correct pattern", new[] { row.PropKey })); } } } @@ -331,24 +331,28 @@ namespace Umbraco.Web.PropertyEditors return contentType; } - public IEnumerable GetPropertyValues(object propertyValue) + public IEnumerable GetPropertyValues(object propertyValue, out List deserialized) { - if (propertyValue == null || string.IsNullOrWhiteSpace(propertyValue.ToString())) - yield break; + var rowValues = new List(); - var value = JsonConvert.DeserializeObject>(propertyValue.ToString()); + 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 (value == null || value.Count == 0) - yield break; + if (deserialized == null || deserialized.Count == 0) + return Enumerable.Empty(); var index = 0; - foreach (var o in value) + foreach (var o in deserialized) { var propValues = o; @@ -361,10 +365,12 @@ namespace Umbraco.Web.PropertyEditors foreach (var propAlias in propAliases) { propertyTypes.TryGetValue(propAlias, out var propType); - yield return new RowValue(propAlias, propType, propValues, index); + rowValues.Add(new RowValue(propAlias, propType, propValues, index)); } index++; } + + return rowValues; } internal class RowValue @@ -372,15 +378,30 @@ namespace Umbraco.Web.PropertyEditors public RowValue(string propKey, PropertyType propType, JObject propValues, int index) { PropKey = propKey ?? throw new ArgumentNullException(nameof(propKey)); - PropType = propType ?? throw new ArgumentNullException(nameof(propType)); - PropValues = propValues ?? throw new ArgumentNullException(nameof(propValues)); - Index = index; + 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; } - public JObject PropValues { get; } - public int Index { get; } + + /// + /// The json values for the current row + /// + public JObject JsonRowValue { get; } + + /// + /// The Nested Content row index + /// + public int RowIndex { get; } } } From b0d834668d0631d14bd580ae615e08616ed33f07 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Fri, 1 Nov 2019 10:49:45 +0100 Subject: [PATCH 144/548] Fix search not skipping results --- src/Umbraco.Web/PublishedContentQuery.cs | 57 +++++++++++++----------- 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/src/Umbraco.Web/PublishedContentQuery.cs b/src/Umbraco.Web/PublishedContentQuery.cs index 2dbe4de4c5..72aec94e58 100644 --- a/src/Umbraco.Web/PublishedContentQuery.cs +++ b/src/Umbraco.Web/PublishedContentQuery.cs @@ -191,46 +191,52 @@ namespace Umbraco.Web /// public IEnumerable Search(string term, int skip, int take, out long totalRecords, string culture = "*", string indexName = null) { - indexName = string.IsNullOrEmpty(indexName) - ? Constants.UmbracoIndexes.ExternalIndexName - : indexName; + if (string.IsNullOrEmpty(indexName)) + { + indexName = Constants.UmbracoIndexes.ExternalIndexName; + } if (!_examineManager.TryGetIndex(indexName, out var index) || !(index is IUmbracoIndex umbIndex)) + { throw new InvalidOperationException($"No index found by name {indexName} or is not of type {typeof(IUmbracoIndex)}"); + } var searcher = umbIndex.GetSearcher(); - // default to max 500 results - var count = skip == 0 && take == 0 ? 500 : skip + take; - ISearchResults results; if (culture == "*") { - //search everything - - results = searcher.Search(term, count); - } - else if (culture.IsNullOrWhiteSpace()) - { - //only search invariant - - var qry = searcher.CreateQuery().Field(UmbracoContentIndex.VariesByCultureFieldName, "n"); //must not vary by culture - qry = qry.And().ManagedQuery(term); - results = qry.Execute(count); + // Search everything + results = skip == 0 && take == 0 + ? searcher.Search(term) + : searcher.Search(term, skip + take); } else { - //search only the specified culture + IBooleanOperation query; + if (string.IsNullOrWhiteSpace(culture)) + { + // Only search invariant + query = searcher.CreateQuery() + .Field(UmbracoContentIndex.VariesByCultureFieldName, "n") // Must not vary by culture + .And().ManagedQuery(term); + } + else + { + // Only search the specified culture + var fields = umbIndex.GetCultureAndInvariantFields(culture).ToArray(); // Get all index fields suffixed with the culture name supplied + query = searcher.CreateQuery() + .ManagedQuery(term, fields); + } - //get all index fields suffixed with the culture name supplied - var cultureFields = umbIndex.GetCultureAndInvariantFields(culture).ToArray(); - var qry = searcher.CreateQuery().ManagedQuery(term, cultureFields); - results = qry.Execute(count); + results = skip == 0 && take == 0 + ? query.Execute() + : query.Execute(skip + take); } totalRecords = results.TotalItemCount; - return new CultureContextualSearchResults(results.ToPublishedSearchResults(_publishedSnapshot.Content), _variationContextAccessor, culture); + return new CultureContextualSearchResults(results.Skip(skip).ToPublishedSearchResults(_publishedSnapshot.Content), _variationContextAccessor, culture); } /// @@ -244,10 +250,11 @@ namespace Umbraco.Web { var results = skip == 0 && take == 0 ? query.Execute() - : query.Execute(maxResults: skip + take); + : query.Execute(skip + take); totalRecords = results.TotalItemCount; - return results.ToPublishedSearchResults(_publishedSnapshot.Content); + + return results.Skip(skip).ToPublishedSearchResults(_publishedSnapshot.Content); } /// From d3d8b1bd68fdb804dbae259e79dfb9dce4e50fda Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Fri, 1 Nov 2019 15:06:00 +0100 Subject: [PATCH 145/548] Ensure that listview status messages are vertically centered --- src/Umbraco.Web.UI.Client/src/less/main.less | 6 ++++++ .../src/views/components/property/umb-property.html | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/main.less b/src/Umbraco.Web.UI.Client/src/less/main.less index 86a1acbeae..8578c22872 100644 --- a/src/Umbraco.Web.UI.Client/src/less/main.less +++ b/src/Umbraco.Web.UI.Client/src/less/main.less @@ -117,6 +117,12 @@ h5.-black { } .umb-control-group { position: relative; + + &.umb-control-group__listview { + // position: relative messes up the listview status messages (e.g. "no search results") + position: unset; + } + &::after { content: ''; display:block; diff --git a/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html b/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html index 927f677463..88a872e05b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html @@ -1,6 +1,6 @@
-
+
From 17c7f5c05f17c6db3d84010cb12daf7f3810c29f Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Fri, 1 Nov 2019 22:56:47 +0100 Subject: [PATCH 146/548] Mark Nested Content PVCs as default --- .../ValueConverters/NestedContentManyValueConverter.cs | 2 ++ .../ValueConverters/NestedContentSingleValueConverter.cs | 2 ++ 2 files changed, 4 insertions(+) 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; From dcba53033c4b387c2938127ea64cdf594c41d692 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 5 Nov 2019 10:30:38 +1100 Subject: [PATCH 147/548] Changes namespace for media tracking migrations to 8.6 --- .../Upgrade/{V_8_5_0 => V_8_6_0}/AddNewRelationTypes.cs | 2 +- .../Upgrade/{V_8_5_0 => V_8_6_0}/UpdateRelationTypeTable.cs | 2 +- src/Umbraco.Core/Umbraco.Core.csproj | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) rename src/Umbraco.Core/Migrations/Upgrade/{V_8_5_0 => V_8_6_0}/AddNewRelationTypes.cs (95%) rename src/Umbraco.Core/Migrations/Upgrade/{V_8_5_0 => V_8_6_0}/UpdateRelationTypeTable.cs (96%) diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_5_0/AddNewRelationTypes.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_6_0/AddNewRelationTypes.cs similarity index 95% rename from src/Umbraco.Core/Migrations/Upgrade/V_8_5_0/AddNewRelationTypes.cs rename to src/Umbraco.Core/Migrations/Upgrade/V_8_6_0/AddNewRelationTypes.cs index 40e541f04c..2e2e00a9bc 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_5_0/AddNewRelationTypes.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_6_0/AddNewRelationTypes.cs @@ -1,6 +1,6 @@ using Umbraco.Core.Migrations.Install; -namespace Umbraco.Core.Migrations.Upgrade.V_8_5_0 +namespace Umbraco.Core.Migrations.Upgrade.V_8_6_0 { /// /// Ensures the new relation types are created diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_5_0/UpdateRelationTypeTable.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_6_0/UpdateRelationTypeTable.cs similarity index 96% rename from src/Umbraco.Core/Migrations/Upgrade/V_8_5_0/UpdateRelationTypeTable.cs rename to src/Umbraco.Core/Migrations/Upgrade/V_8_6_0/UpdateRelationTypeTable.cs index b76f5ba3a7..c79f43d20f 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_5_0/UpdateRelationTypeTable.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_6_0/UpdateRelationTypeTable.cs @@ -1,6 +1,6 @@ using Umbraco.Core.Persistence.Dtos; -namespace Umbraco.Core.Migrations.Upgrade.V_8_5_0 +namespace Umbraco.Core.Migrations.Upgrade.V_8_6_0 { public class UpdateRelationTypeTable : MigrationBase diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 15eebe70ba..225317943b 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -251,8 +251,8 @@ - - + + @@ -1575,4 +1575,4 @@ - + \ No newline at end of file From 412eadd9a305a08eedeb679f7519500262e181e1 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 5 Nov 2019 10:48:23 +1100 Subject: [PATCH 148/548] Changes namespace for media tracking migrations to 8.6 --- src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs index 45182b17e3..77dcaa808e 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs @@ -6,7 +6,7 @@ using Umbraco.Core.Migrations.Upgrade.Common; using Umbraco.Core.Migrations.Upgrade.V_8_0_0; using Umbraco.Core.Migrations.Upgrade.V_8_0_1; using Umbraco.Core.Migrations.Upgrade.V_8_1_0; -using Umbraco.Core.Migrations.Upgrade.V_8_5_0; +using Umbraco.Core.Migrations.Upgrade.V_8_6_0; namespace Umbraco.Core.Migrations.Upgrade { From fff3d2648f71b375a6bec6624b140cce08a3e8e2 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 5 Nov 2019 15:05:51 +1100 Subject: [PATCH 149/548] Gets entity repository to be able to return a mix of object types --- src/Umbraco.Core/Models/ContentBase.cs | 3 + src/Umbraco.Core/Models/ContentTypeBase.cs | 3 + src/Umbraco.Core/Models/DataType.cs | 3 + .../Models/Entities/ITreeEntity.cs | 4 +- .../Models/Entities/IUmbracoEntity.cs | 7 +- src/Umbraco.Core/Models/EntityContainer.cs | 2 + src/Umbraco.Core/Models/SimpleContentType.cs | 3 + .../Factories/ContentBaseFactory.cs | 2 +- .../Factories/ContentTypeFactory.cs | 1 + .../Persistence/Factories/DataTypeFactory.cs | 1 + .../Mappers/UmbracoEntityMapper.cs | 1 + .../Repositories/IEntityRepository.cs | 43 ++++- .../Implement/EntityRepository.cs | 157 +++++++++++------- .../Repositories/EntityRepositoryTest.cs | 95 +++++++++++ src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + src/Umbraco.Web/Editors/EntityController.cs | 3 + .../Models/Mapping/EntityMapDefinition.cs | 8 +- .../Models/Mapping/UserMapDefinition.cs | 4 +- .../Trees/ContentBlueprintTreeController.cs | 4 +- 19 files changed, 273 insertions(+), 72 deletions(-) create mode 100644 src/Umbraco.Tests/Persistence/Repositories/EntityRepositoryTest.cs diff --git a/src/Umbraco.Core/Models/ContentBase.cs b/src/Umbraco.Core/Models/ContentBase.cs index fbb68194b7..2ea0fd855b 100644 --- a/src/Umbraco.Core/Models/ContentBase.cs +++ b/src/Umbraco.Core/Models/ContentBase.cs @@ -108,6 +108,9 @@ namespace Umbraco.Core.Models [IgnoreDataMember] public int VersionId { get; set; } + [DataMember] + public Guid NodeObjectType { get; internal set; } + /// /// Integer Id of the default ContentType /// diff --git a/src/Umbraco.Core/Models/ContentTypeBase.cs b/src/Umbraco.Core/Models/ContentTypeBase.cs index 04bcb7424a..1f3f1a5c13 100644 --- a/src/Umbraco.Core/Models/ContentTypeBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeBase.cs @@ -113,6 +113,9 @@ namespace Umbraco.Core.Models OnPropertyChanged(nameof(PropertyTypes)); } + [DataMember] + public Guid NodeObjectType { get; internal set; } + /// /// The Alias of the ContentType /// diff --git a/src/Umbraco.Core/Models/DataType.cs b/src/Umbraco.Core/Models/DataType.cs index c237f6381c..cba53bbd10 100644 --- a/src/Umbraco.Core/Models/DataType.cs +++ b/src/Umbraco.Core/Models/DataType.cs @@ -65,6 +65,9 @@ namespace Umbraco.Core.Models [DataMember] public string EditorAlias => _editor.Alias; + [DataMember] + public Guid NodeObjectType { get; internal set; } + /// [DataMember] public ValueStorageType DatabaseType 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/IUmbracoEntity.cs b/src/Umbraco.Core/Models/Entities/IUmbracoEntity.cs index e5f628b098..13087b5e95 100644 --- a/src/Umbraco.Core/Models/Entities/IUmbracoEntity.cs +++ b/src/Umbraco.Core/Models/Entities/IUmbracoEntity.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace Umbraco.Core.Models.Entities { @@ -11,5 +12,7 @@ namespace Umbraco.Core.Models.Entities /// An IUmbracoEntity can participate in notifications. /// public interface IUmbracoEntity : ITreeEntity, IRememberBeingDirty - { } + { + Guid NodeObjectType { get; } + } } diff --git a/src/Umbraco.Core/Models/EntityContainer.cs b/src/Umbraco.Core/Models/EntityContainer.cs index 70f6cbd878..71a477a9d6 100644 --- a/src/Umbraco.Core/Models/EntityContainer.cs +++ b/src/Umbraco.Core/Models/EntityContainer.cs @@ -62,6 +62,8 @@ namespace Umbraco.Core.Models ///
public Guid ContainerObjectType => ObjectTypeMap[_containedObjectType]; + Guid IUmbracoEntity.NodeObjectType => ContainerObjectType; + /// /// Gets the container object type corresponding to a contained object type. /// diff --git a/src/Umbraco.Core/Models/SimpleContentType.cs b/src/Umbraco.Core/Models/SimpleContentType.cs index 5c81017ec8..b4e7688eea 100644 --- a/src/Umbraco.Core/Models/SimpleContentType.cs +++ b/src/Umbraco.Core/Models/SimpleContentType.cs @@ -44,11 +44,14 @@ namespace Umbraco.Core.Models Name = contentType.Name; AllowedAsRoot = contentType.AllowedAsRoot; IsElement = contentType.IsElement; + NodeObjectType = contentType.NodeObjectType; } /// public string Alias { get; } + public Guid NodeObjectType { get; } + public int Id { get; } /// diff --git a/src/Umbraco.Core/Persistence/Factories/ContentBaseFactory.cs b/src/Umbraco.Core/Persistence/Factories/ContentBaseFactory.cs index 434e0393cd..25cc6bc358 100644 --- a/src/Umbraco.Core/Persistence/Factories/ContentBaseFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/ContentBaseFactory.cs @@ -34,7 +34,7 @@ namespace Umbraco.Core.Persistence.Factories content.VersionId = contentVersionDto.Id; content.Name = contentVersionDto.Text; - + content.NodeObjectType = nodeDto.NodeObjectType ?? Guid.Empty; content.Path = nodeDto.Path; content.Level = nodeDto.Level; content.ParentId = nodeDto.ParentId; diff --git a/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs b/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs index 54cfee0ffa..46489aa69e 100644 --- a/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs @@ -107,6 +107,7 @@ namespace Umbraco.Core.Persistence.Factories { entity.Id = dto.NodeDto.NodeId; entity.Key = dto.NodeDto.UniqueId; + entity.NodeObjectType = dto.NodeDto.NodeObjectType ?? Guid.Empty; entity.Alias = dto.Alias; entity.Name = dto.NodeDto.Text; entity.Icon = dto.Icon; diff --git a/src/Umbraco.Core/Persistence/Factories/DataTypeFactory.cs b/src/Umbraco.Core/Persistence/Factories/DataTypeFactory.cs index f189d38d05..5d92234ca8 100644 --- a/src/Umbraco.Core/Persistence/Factories/DataTypeFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/DataTypeFactory.cs @@ -28,6 +28,7 @@ namespace Umbraco.Core.Persistence.Factories dataType.DisableChangeTracking(); dataType.CreateDate = dto.NodeDto.CreateDate; + dataType.NodeObjectType = dto.NodeDto.NodeObjectType ?? Guid.Empty; dataType.DatabaseType = dto.DbType.EnumParse(true); dataType.Id = dto.NodeId; dataType.Key = dto.NodeDto.UniqueId; diff --git a/src/Umbraco.Core/Persistence/Mappers/UmbracoEntityMapper.cs b/src/Umbraco.Core/Persistence/Mappers/UmbracoEntityMapper.cs index 16e2e8bcd5..96f664cc25 100644 --- a/src/Umbraco.Core/Persistence/Mappers/UmbracoEntityMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/UmbracoEntityMapper.cs @@ -24,6 +24,7 @@ namespace Umbraco.Core.Persistence.Mappers DefineMap(nameof(IUmbracoEntity.Trashed), nameof(NodeDto.Trashed)); DefineMap(nameof(IUmbracoEntity.Key), nameof(NodeDto.UniqueId)); DefineMap(nameof(IUmbracoEntity.CreatorId), nameof(NodeDto.UserId)); + DefineMap(nameof(IUmbracoEntity.NodeObjectType), nameof(NodeDto.NodeObjectType)); } } } diff --git a/src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs index 69f6ef4c5f..a6a1691347 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs @@ -15,10 +15,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 +42,36 @@ 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 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); + + /// + /// 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/Implement/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs index 161db543ba..2b68c8fe19 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs @@ -36,26 +36,70 @@ namespace Umbraco.Core.Persistence.Repositories.Implement #region Repository - // get a page of entities + //public IEnumerable GetPagedResults(IDictionary> typesAndIds, long pageIndex, int pageSize, out long totalRecords, Ordering ordering) + //{ + // var isContent = typesAndIds.Keys.Any(objectType => objectType == Constants.ObjectTypes.Document || objectType == Constants.ObjectTypes.DocumentBlueprint); + // var isMedia = typesAndIds.Keys.Any(objectType => objectType == Constants.ObjectTypes.Media); + // var isMember = typesAndIds.Keys.Any(objectType => objectType == Constants.ObjectTypes.Member); + + // var sql = GetBase(isContent, isMedia, isMember, null, false) + // .WhereIn(x => x.NodeObjectType, typesAndIds.Keys); + + // ordering = ordering ?? Ordering.ByDefault(); + + // sql = AddGroupBy(isContent, isMedia, isMember, sql, ordering.IsEmpty); + + // if (!ordering.IsEmpty) + // { + // // apply ordering + // ApplyOrdering(ref sql, ordering); + // } + + // // TODO: we should be able to do sql = sql.OrderBy(x => Alias(x.NodeId, "NodeId")); but we can't because the OrderBy extension don't support Alias currently + // //no matter what we always must have node id ordered at the end + // sql = ordering.Direction == Direction.Ascending ? sql.OrderBy("NodeId") : sql.OrderByDescending("NodeId"); + + // // for content we must query for ContentEntityDto entities to produce the correct culture variant entity names + // var pageIndexToFetch = pageIndex + 1; + // IEnumerable dtos; + // var page = Database.Page(pageIndexToFetch, pageSize, sql); + // dtos = page.Items; + // totalRecords = page.TotalItems; + + // var entities = dtos.Select(BuildEntity).ToArray(); + + // BuildVariants(entities.OfType()); + + // return 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; + query = query.Where(x => x.NodeObjectType == objectType); + return GetPagedResultsByQuery(query, new[] { objectType }, pageIndex, pageSize, out totalRecords, filter, ordering); + } + + // get a page of entities + public IEnumerable GetPagedResultsByQuery(IQuery query, Guid[] objectTypes, long pageIndex, int pageSize, out long totalRecords, + IQuery filter, Ordering ordering) + { + 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, x => { if (filter == null) return; foreach (var filterClause in filter.GetWhereClauses()) x.Where(filterClause.Item1, filterClause.Item2); - }, objectType); + }, objectTypes); ordering = ordering ?? Ordering.ByDefault(); var translator = new SqlTranslator(sql, query); sql = translator.Translate(); - sql = AddGroupBy(isContent, isMedia, isMember, sql, ordering.IsEmpty); + sql = AddGroupBy(true, true, true, sql, ordering.IsEmpty); if (!ordering.IsEmpty) { @@ -70,35 +114,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 +129,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 +138,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 +149,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 +168,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 +200,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 +211,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 +255,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 +264,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 +378,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 +423,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 +455,10 @@ 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); + .WhereIn(x => x.NodeObjectType, objectTypes); } // gets the base SELECT + FROM + WHERE sql @@ -510,8 +532,13 @@ 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 + var orderBy = ordering.OrderBy.ToUpperInvariant() switch + { + "PATH" => SqlSyntax.GetQuotedColumn(NodeDto.TableName, "path"), + _ => ordering.OrderBy + }; if (ordering.Direction == Direction.Ascending) sql.OrderBy(orderBy); @@ -524,9 +551,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 +569,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 +630,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 @@ -650,7 +691,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.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/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 4b035f631e..c87e6501f9 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -140,6 +140,7 @@ + diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index 0513017b70..11b1260e71 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -662,6 +662,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) { diff --git a/src/Umbraco.Web/Models/Mapping/EntityMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/EntityMapDefinition.cs index 2e44f1327b..afd1a6b61c 100644 --- a/src/Umbraco.Web/Models/Mapping/EntityMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/EntityMapDefinition.cs @@ -226,11 +226,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/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/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 => { From 602acce8f49797b870548857df2895836d75f1bd Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 5 Nov 2019 15:05:51 +1100 Subject: [PATCH 150/548] Gets entity repository to be able to return a mix of object types --- src/Umbraco.Core/Models/ContentBase.cs | 3 + src/Umbraco.Core/Models/ContentTypeBase.cs | 3 + src/Umbraco.Core/Models/DataType.cs | 3 + .../Models/Entities/ITreeEntity.cs | 4 +- .../Models/Entities/IUmbracoEntity.cs | 7 +- src/Umbraco.Core/Models/EntityContainer.cs | 2 + src/Umbraco.Core/Models/SimpleContentType.cs | 3 + .../Factories/ContentBaseFactory.cs | 2 +- .../Factories/ContentTypeFactory.cs | 1 + .../Persistence/Factories/DataTypeFactory.cs | 1 + .../Mappers/UmbracoEntityMapper.cs | 1 + .../Repositories/IEntityRepository.cs | 43 ++++- .../Implement/EntityRepository.cs | 157 +++++++++++------- .../Repositories/EntityRepositoryTest.cs | 95 +++++++++++ src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + src/Umbraco.Web/Editors/EntityController.cs | 3 + .../Models/Mapping/EntityMapDefinition.cs | 8 +- .../Models/Mapping/UserMapDefinition.cs | 4 +- .../Trees/ContentBlueprintTreeController.cs | 4 +- 19 files changed, 273 insertions(+), 72 deletions(-) create mode 100644 src/Umbraco.Tests/Persistence/Repositories/EntityRepositoryTest.cs diff --git a/src/Umbraco.Core/Models/ContentBase.cs b/src/Umbraco.Core/Models/ContentBase.cs index fbb68194b7..2ea0fd855b 100644 --- a/src/Umbraco.Core/Models/ContentBase.cs +++ b/src/Umbraco.Core/Models/ContentBase.cs @@ -108,6 +108,9 @@ namespace Umbraco.Core.Models [IgnoreDataMember] public int VersionId { get; set; } + [DataMember] + public Guid NodeObjectType { get; internal set; } + /// /// Integer Id of the default ContentType /// diff --git a/src/Umbraco.Core/Models/ContentTypeBase.cs b/src/Umbraco.Core/Models/ContentTypeBase.cs index 04bcb7424a..1f3f1a5c13 100644 --- a/src/Umbraco.Core/Models/ContentTypeBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeBase.cs @@ -113,6 +113,9 @@ namespace Umbraco.Core.Models OnPropertyChanged(nameof(PropertyTypes)); } + [DataMember] + public Guid NodeObjectType { get; internal set; } + /// /// The Alias of the ContentType /// diff --git a/src/Umbraco.Core/Models/DataType.cs b/src/Umbraco.Core/Models/DataType.cs index c237f6381c..cba53bbd10 100644 --- a/src/Umbraco.Core/Models/DataType.cs +++ b/src/Umbraco.Core/Models/DataType.cs @@ -65,6 +65,9 @@ namespace Umbraco.Core.Models [DataMember] public string EditorAlias => _editor.Alias; + [DataMember] + public Guid NodeObjectType { get; internal set; } + /// [DataMember] public ValueStorageType DatabaseType 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/IUmbracoEntity.cs b/src/Umbraco.Core/Models/Entities/IUmbracoEntity.cs index e5f628b098..13087b5e95 100644 --- a/src/Umbraco.Core/Models/Entities/IUmbracoEntity.cs +++ b/src/Umbraco.Core/Models/Entities/IUmbracoEntity.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace Umbraco.Core.Models.Entities { @@ -11,5 +12,7 @@ namespace Umbraco.Core.Models.Entities /// An IUmbracoEntity can participate in notifications. /// public interface IUmbracoEntity : ITreeEntity, IRememberBeingDirty - { } + { + Guid NodeObjectType { get; } + } } diff --git a/src/Umbraco.Core/Models/EntityContainer.cs b/src/Umbraco.Core/Models/EntityContainer.cs index 70f6cbd878..71a477a9d6 100644 --- a/src/Umbraco.Core/Models/EntityContainer.cs +++ b/src/Umbraco.Core/Models/EntityContainer.cs @@ -62,6 +62,8 @@ namespace Umbraco.Core.Models ///
public Guid ContainerObjectType => ObjectTypeMap[_containedObjectType]; + Guid IUmbracoEntity.NodeObjectType => ContainerObjectType; + /// /// Gets the container object type corresponding to a contained object type. /// diff --git a/src/Umbraco.Core/Models/SimpleContentType.cs b/src/Umbraco.Core/Models/SimpleContentType.cs index 5c81017ec8..b4e7688eea 100644 --- a/src/Umbraco.Core/Models/SimpleContentType.cs +++ b/src/Umbraco.Core/Models/SimpleContentType.cs @@ -44,11 +44,14 @@ namespace Umbraco.Core.Models Name = contentType.Name; AllowedAsRoot = contentType.AllowedAsRoot; IsElement = contentType.IsElement; + NodeObjectType = contentType.NodeObjectType; } /// public string Alias { get; } + public Guid NodeObjectType { get; } + public int Id { get; } /// diff --git a/src/Umbraco.Core/Persistence/Factories/ContentBaseFactory.cs b/src/Umbraco.Core/Persistence/Factories/ContentBaseFactory.cs index 434e0393cd..25cc6bc358 100644 --- a/src/Umbraco.Core/Persistence/Factories/ContentBaseFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/ContentBaseFactory.cs @@ -34,7 +34,7 @@ namespace Umbraco.Core.Persistence.Factories content.VersionId = contentVersionDto.Id; content.Name = contentVersionDto.Text; - + content.NodeObjectType = nodeDto.NodeObjectType ?? Guid.Empty; content.Path = nodeDto.Path; content.Level = nodeDto.Level; content.ParentId = nodeDto.ParentId; diff --git a/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs b/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs index 54cfee0ffa..46489aa69e 100644 --- a/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs @@ -107,6 +107,7 @@ namespace Umbraco.Core.Persistence.Factories { entity.Id = dto.NodeDto.NodeId; entity.Key = dto.NodeDto.UniqueId; + entity.NodeObjectType = dto.NodeDto.NodeObjectType ?? Guid.Empty; entity.Alias = dto.Alias; entity.Name = dto.NodeDto.Text; entity.Icon = dto.Icon; diff --git a/src/Umbraco.Core/Persistence/Factories/DataTypeFactory.cs b/src/Umbraco.Core/Persistence/Factories/DataTypeFactory.cs index f189d38d05..5d92234ca8 100644 --- a/src/Umbraco.Core/Persistence/Factories/DataTypeFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/DataTypeFactory.cs @@ -28,6 +28,7 @@ namespace Umbraco.Core.Persistence.Factories dataType.DisableChangeTracking(); dataType.CreateDate = dto.NodeDto.CreateDate; + dataType.NodeObjectType = dto.NodeDto.NodeObjectType ?? Guid.Empty; dataType.DatabaseType = dto.DbType.EnumParse(true); dataType.Id = dto.NodeId; dataType.Key = dto.NodeDto.UniqueId; diff --git a/src/Umbraco.Core/Persistence/Mappers/UmbracoEntityMapper.cs b/src/Umbraco.Core/Persistence/Mappers/UmbracoEntityMapper.cs index 16e2e8bcd5..96f664cc25 100644 --- a/src/Umbraco.Core/Persistence/Mappers/UmbracoEntityMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/UmbracoEntityMapper.cs @@ -24,6 +24,7 @@ namespace Umbraco.Core.Persistence.Mappers DefineMap(nameof(IUmbracoEntity.Trashed), nameof(NodeDto.Trashed)); DefineMap(nameof(IUmbracoEntity.Key), nameof(NodeDto.UniqueId)); DefineMap(nameof(IUmbracoEntity.CreatorId), nameof(NodeDto.UserId)); + DefineMap(nameof(IUmbracoEntity.NodeObjectType), nameof(NodeDto.NodeObjectType)); } } } diff --git a/src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs index 69f6ef4c5f..a6a1691347 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs @@ -15,10 +15,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 +42,36 @@ 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 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); + + /// + /// 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/Implement/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs index 161db543ba..2b68c8fe19 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs @@ -36,26 +36,70 @@ namespace Umbraco.Core.Persistence.Repositories.Implement #region Repository - // get a page of entities + //public IEnumerable GetPagedResults(IDictionary> typesAndIds, long pageIndex, int pageSize, out long totalRecords, Ordering ordering) + //{ + // var isContent = typesAndIds.Keys.Any(objectType => objectType == Constants.ObjectTypes.Document || objectType == Constants.ObjectTypes.DocumentBlueprint); + // var isMedia = typesAndIds.Keys.Any(objectType => objectType == Constants.ObjectTypes.Media); + // var isMember = typesAndIds.Keys.Any(objectType => objectType == Constants.ObjectTypes.Member); + + // var sql = GetBase(isContent, isMedia, isMember, null, false) + // .WhereIn(x => x.NodeObjectType, typesAndIds.Keys); + + // ordering = ordering ?? Ordering.ByDefault(); + + // sql = AddGroupBy(isContent, isMedia, isMember, sql, ordering.IsEmpty); + + // if (!ordering.IsEmpty) + // { + // // apply ordering + // ApplyOrdering(ref sql, ordering); + // } + + // // TODO: we should be able to do sql = sql.OrderBy(x => Alias(x.NodeId, "NodeId")); but we can't because the OrderBy extension don't support Alias currently + // //no matter what we always must have node id ordered at the end + // sql = ordering.Direction == Direction.Ascending ? sql.OrderBy("NodeId") : sql.OrderByDescending("NodeId"); + + // // for content we must query for ContentEntityDto entities to produce the correct culture variant entity names + // var pageIndexToFetch = pageIndex + 1; + // IEnumerable dtos; + // var page = Database.Page(pageIndexToFetch, pageSize, sql); + // dtos = page.Items; + // totalRecords = page.TotalItems; + + // var entities = dtos.Select(BuildEntity).ToArray(); + + // BuildVariants(entities.OfType()); + + // return 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; + query = query.Where(x => x.NodeObjectType == objectType); + return GetPagedResultsByQuery(query, new[] { objectType }, pageIndex, pageSize, out totalRecords, filter, ordering); + } + + // get a page of entities + public IEnumerable GetPagedResultsByQuery(IQuery query, Guid[] objectTypes, long pageIndex, int pageSize, out long totalRecords, + IQuery filter, Ordering ordering) + { + 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, x => { if (filter == null) return; foreach (var filterClause in filter.GetWhereClauses()) x.Where(filterClause.Item1, filterClause.Item2); - }, objectType); + }, objectTypes); ordering = ordering ?? Ordering.ByDefault(); var translator = new SqlTranslator(sql, query); sql = translator.Translate(); - sql = AddGroupBy(isContent, isMedia, isMember, sql, ordering.IsEmpty); + sql = AddGroupBy(true, true, true, sql, ordering.IsEmpty); if (!ordering.IsEmpty) { @@ -70,35 +114,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 +129,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 +138,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 +149,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 +168,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 +200,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 +211,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 +255,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 +264,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 +378,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 +423,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 +455,10 @@ 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); + .WhereIn(x => x.NodeObjectType, objectTypes); } // gets the base SELECT + FROM + WHERE sql @@ -510,8 +532,13 @@ 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 + var orderBy = ordering.OrderBy.ToUpperInvariant() switch + { + "PATH" => SqlSyntax.GetQuotedColumn(NodeDto.TableName, "path"), + _ => ordering.OrderBy + }; if (ordering.Direction == Direction.Ascending) sql.OrderBy(orderBy); @@ -524,9 +551,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 +569,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 +630,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 @@ -650,7 +691,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.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/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 4b035f631e..c87e6501f9 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -140,6 +140,7 @@ + diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index 0513017b70..11b1260e71 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -662,6 +662,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) { diff --git a/src/Umbraco.Web/Models/Mapping/EntityMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/EntityMapDefinition.cs index 2e44f1327b..afd1a6b61c 100644 --- a/src/Umbraco.Web/Models/Mapping/EntityMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/EntityMapDefinition.cs @@ -226,11 +226,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/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/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 => { From f7e8a53922f074581b2106e108b3d10a20a0a30d Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 5 Nov 2019 15:53:50 +1100 Subject: [PATCH 151/548] Revert "Gets entity repository to be able to return a mix of object types" This reverts commit fff3d2648f71b375a6bec6624b140cce08a3e8e2. --- src/Umbraco.Core/Models/ContentBase.cs | 3 - src/Umbraco.Core/Models/ContentTypeBase.cs | 3 - src/Umbraco.Core/Models/DataType.cs | 3 - .../Models/Entities/ITreeEntity.cs | 4 +- .../Models/Entities/IUmbracoEntity.cs | 7 +- src/Umbraco.Core/Models/EntityContainer.cs | 2 - src/Umbraco.Core/Models/SimpleContentType.cs | 3 - .../Factories/ContentBaseFactory.cs | 2 +- .../Factories/ContentTypeFactory.cs | 1 - .../Persistence/Factories/DataTypeFactory.cs | 1 - .../Mappers/UmbracoEntityMapper.cs | 1 - .../Repositories/IEntityRepository.cs | 43 +---- .../Implement/EntityRepository.cs | 157 +++++++----------- .../Repositories/EntityRepositoryTest.cs | 95 ----------- src/Umbraco.Tests/Umbraco.Tests.csproj | 1 - src/Umbraco.Web/Editors/EntityController.cs | 3 - .../Models/Mapping/EntityMapDefinition.cs | 8 +- .../Models/Mapping/UserMapDefinition.cs | 4 +- .../Trees/ContentBlueprintTreeController.cs | 4 +- 19 files changed, 72 insertions(+), 273 deletions(-) delete mode 100644 src/Umbraco.Tests/Persistence/Repositories/EntityRepositoryTest.cs diff --git a/src/Umbraco.Core/Models/ContentBase.cs b/src/Umbraco.Core/Models/ContentBase.cs index 2ea0fd855b..fbb68194b7 100644 --- a/src/Umbraco.Core/Models/ContentBase.cs +++ b/src/Umbraco.Core/Models/ContentBase.cs @@ -108,9 +108,6 @@ namespace Umbraco.Core.Models [IgnoreDataMember] public int VersionId { get; set; } - [DataMember] - public Guid NodeObjectType { get; internal set; } - /// /// Integer Id of the default ContentType /// diff --git a/src/Umbraco.Core/Models/ContentTypeBase.cs b/src/Umbraco.Core/Models/ContentTypeBase.cs index 1f3f1a5c13..04bcb7424a 100644 --- a/src/Umbraco.Core/Models/ContentTypeBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeBase.cs @@ -113,9 +113,6 @@ namespace Umbraco.Core.Models OnPropertyChanged(nameof(PropertyTypes)); } - [DataMember] - public Guid NodeObjectType { get; internal set; } - /// /// The Alias of the ContentType /// diff --git a/src/Umbraco.Core/Models/DataType.cs b/src/Umbraco.Core/Models/DataType.cs index cba53bbd10..c237f6381c 100644 --- a/src/Umbraco.Core/Models/DataType.cs +++ b/src/Umbraco.Core/Models/DataType.cs @@ -65,9 +65,6 @@ namespace Umbraco.Core.Models [DataMember] public string EditorAlias => _editor.Alias; - [DataMember] - public Guid NodeObjectType { get; internal set; } - /// [DataMember] public ValueStorageType DatabaseType diff --git a/src/Umbraco.Core/Models/Entities/ITreeEntity.cs b/src/Umbraco.Core/Models/Entities/ITreeEntity.cs index ab63e1e1d8..afa3399202 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 be retrieved + /// been persistent and does not yet have an identity. The parent identifier will we 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/IUmbracoEntity.cs b/src/Umbraco.Core/Models/Entities/IUmbracoEntity.cs index 13087b5e95..e5f628b098 100644 --- a/src/Umbraco.Core/Models/Entities/IUmbracoEntity.cs +++ b/src/Umbraco.Core/Models/Entities/IUmbracoEntity.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; namespace Umbraco.Core.Models.Entities { @@ -12,7 +11,5 @@ namespace Umbraco.Core.Models.Entities /// An IUmbracoEntity can participate in notifications. /// public interface IUmbracoEntity : ITreeEntity, IRememberBeingDirty - { - Guid NodeObjectType { get; } - } + { } } diff --git a/src/Umbraco.Core/Models/EntityContainer.cs b/src/Umbraco.Core/Models/EntityContainer.cs index 71a477a9d6..70f6cbd878 100644 --- a/src/Umbraco.Core/Models/EntityContainer.cs +++ b/src/Umbraco.Core/Models/EntityContainer.cs @@ -62,8 +62,6 @@ namespace Umbraco.Core.Models ///
public Guid ContainerObjectType => ObjectTypeMap[_containedObjectType]; - Guid IUmbracoEntity.NodeObjectType => ContainerObjectType; - /// /// Gets the container object type corresponding to a contained object type. /// diff --git a/src/Umbraco.Core/Models/SimpleContentType.cs b/src/Umbraco.Core/Models/SimpleContentType.cs index b4e7688eea..5c81017ec8 100644 --- a/src/Umbraco.Core/Models/SimpleContentType.cs +++ b/src/Umbraco.Core/Models/SimpleContentType.cs @@ -44,14 +44,11 @@ namespace Umbraco.Core.Models Name = contentType.Name; AllowedAsRoot = contentType.AllowedAsRoot; IsElement = contentType.IsElement; - NodeObjectType = contentType.NodeObjectType; } /// public string Alias { get; } - public Guid NodeObjectType { get; } - public int Id { get; } /// diff --git a/src/Umbraco.Core/Persistence/Factories/ContentBaseFactory.cs b/src/Umbraco.Core/Persistence/Factories/ContentBaseFactory.cs index 25cc6bc358..434e0393cd 100644 --- a/src/Umbraco.Core/Persistence/Factories/ContentBaseFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/ContentBaseFactory.cs @@ -34,7 +34,7 @@ namespace Umbraco.Core.Persistence.Factories content.VersionId = contentVersionDto.Id; content.Name = contentVersionDto.Text; - content.NodeObjectType = nodeDto.NodeObjectType ?? Guid.Empty; + content.Path = nodeDto.Path; content.Level = nodeDto.Level; content.ParentId = nodeDto.ParentId; diff --git a/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs b/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs index 46489aa69e..54cfee0ffa 100644 --- a/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs @@ -107,7 +107,6 @@ namespace Umbraco.Core.Persistence.Factories { entity.Id = dto.NodeDto.NodeId; entity.Key = dto.NodeDto.UniqueId; - entity.NodeObjectType = dto.NodeDto.NodeObjectType ?? Guid.Empty; entity.Alias = dto.Alias; entity.Name = dto.NodeDto.Text; entity.Icon = dto.Icon; diff --git a/src/Umbraco.Core/Persistence/Factories/DataTypeFactory.cs b/src/Umbraco.Core/Persistence/Factories/DataTypeFactory.cs index 5d92234ca8..f189d38d05 100644 --- a/src/Umbraco.Core/Persistence/Factories/DataTypeFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/DataTypeFactory.cs @@ -28,7 +28,6 @@ namespace Umbraco.Core.Persistence.Factories dataType.DisableChangeTracking(); dataType.CreateDate = dto.NodeDto.CreateDate; - dataType.NodeObjectType = dto.NodeDto.NodeObjectType ?? Guid.Empty; dataType.DatabaseType = dto.DbType.EnumParse(true); dataType.Id = dto.NodeId; dataType.Key = dto.NodeDto.UniqueId; diff --git a/src/Umbraco.Core/Persistence/Mappers/UmbracoEntityMapper.cs b/src/Umbraco.Core/Persistence/Mappers/UmbracoEntityMapper.cs index 96f664cc25..16e2e8bcd5 100644 --- a/src/Umbraco.Core/Persistence/Mappers/UmbracoEntityMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/UmbracoEntityMapper.cs @@ -24,7 +24,6 @@ namespace Umbraco.Core.Persistence.Mappers DefineMap(nameof(IUmbracoEntity.Trashed), nameof(NodeDto.Trashed)); DefineMap(nameof(IUmbracoEntity.Key), nameof(NodeDto.UniqueId)); DefineMap(nameof(IUmbracoEntity.CreatorId), nameof(NodeDto.UserId)); - DefineMap(nameof(IUmbracoEntity.NodeObjectType), nameof(NodeDto.NodeObjectType)); } } } diff --git a/src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs index a6a1691347..69f6ef4c5f 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs @@ -15,22 +15,10 @@ 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); @@ -42,36 +30,7 @@ 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 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); - - /// - /// 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/Implement/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs index 2b68c8fe19..161db543ba 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs @@ -36,70 +36,26 @@ namespace Umbraco.Core.Persistence.Repositories.Implement #region Repository - //public IEnumerable GetPagedResults(IDictionary> typesAndIds, long pageIndex, int pageSize, out long totalRecords, Ordering ordering) - //{ - // var isContent = typesAndIds.Keys.Any(objectType => objectType == Constants.ObjectTypes.Document || objectType == Constants.ObjectTypes.DocumentBlueprint); - // var isMedia = typesAndIds.Keys.Any(objectType => objectType == Constants.ObjectTypes.Media); - // var isMember = typesAndIds.Keys.Any(objectType => objectType == Constants.ObjectTypes.Member); - - // var sql = GetBase(isContent, isMedia, isMember, null, false) - // .WhereIn(x => x.NodeObjectType, typesAndIds.Keys); - - // ordering = ordering ?? Ordering.ByDefault(); - - // sql = AddGroupBy(isContent, isMedia, isMember, sql, ordering.IsEmpty); - - // if (!ordering.IsEmpty) - // { - // // apply ordering - // ApplyOrdering(ref sql, ordering); - // } - - // // TODO: we should be able to do sql = sql.OrderBy(x => Alias(x.NodeId, "NodeId")); but we can't because the OrderBy extension don't support Alias currently - // //no matter what we always must have node id ordered at the end - // sql = ordering.Direction == Direction.Ascending ? sql.OrderBy("NodeId") : sql.OrderByDescending("NodeId"); - - // // for content we must query for ContentEntityDto entities to produce the correct culture variant entity names - // var pageIndexToFetch = pageIndex + 1; - // IEnumerable dtos; - // var page = Database.Page(pageIndexToFetch, pageSize, sql); - // dtos = page.Items; - // totalRecords = page.TotalItems; - - // var entities = dtos.Select(BuildEntity).ToArray(); - - // BuildVariants(entities.OfType()); - - // return entities; - //} - + // get a page of entities public IEnumerable GetPagedResultsByQuery(IQuery query, Guid objectType, long pageIndex, int pageSize, out long totalRecords, IQuery filter, Ordering ordering) { - query = query.Where(x => x.NodeObjectType == objectType); - return GetPagedResultsByQuery(query, new[] { objectType }, pageIndex, pageSize, out totalRecords, filter, ordering); - } - - // get a page of entities - public IEnumerable GetPagedResultsByQuery(IQuery query, Guid[] objectTypes, long pageIndex, int pageSize, out long totalRecords, - IQuery filter, Ordering ordering) - { - 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 isContent = objectType == Constants.ObjectTypes.Document || objectType == Constants.ObjectTypes.DocumentBlueprint; + var isMedia = objectType == Constants.ObjectTypes.Media; + var isMember = objectType == Constants.ObjectTypes.Member; var sql = GetBaseWhere(isContent, isMedia, isMember, false, x => { if (filter == null) return; foreach (var filterClause in filter.GetWhereClauses()) x.Where(filterClause.Item1, filterClause.Item2); - }, objectTypes); + }, objectType); ordering = ordering ?? Ordering.ByDefault(); var translator = new SqlTranslator(sql, query); sql = translator.Translate(); - sql = AddGroupBy(true, true, true, sql, ordering.IsEmpty); + sql = AddGroupBy(isContent, isMedia, isMember, sql, ordering.IsEmpty); if (!ordering.IsEmpty) { @@ -114,13 +70,35 @@ 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; - var page = Database.Page(pageIndexToFetch, pageSize, sql); - dtos = page.Items; - totalRecords = page.TotalItems; + 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 entities = dtos.Select(BuildEntity).ToArray(); + var entities = dtos.Select(x => BuildEntity(isContent, isMedia, isMember, x)).ToArray(); - BuildVariants(entities.OfType()); + if (isContent) + BuildVariants(entities.Cast()); return entities; } @@ -129,7 +107,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(dto); + return dto == null ? null : BuildEntity(false, false, false, dto); } @@ -138,7 +116,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])); } @@ -149,7 +127,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (dto == null) return null; - var entity = BuildEntity(dto); + var entity = BuildEntity(false, isMedia, isMember, dto); return entity; } @@ -168,7 +146,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(dto); + return dto == null ? null : BuildEntity(false, false, false, dto); } public IEntitySlim Get(int id, Guid objectTypeId) @@ -200,7 +178,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() @@ -211,7 +189,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement ? (IEnumerable)Database.Fetch(sql) : Database.Fetch(sql); - var entities = dtos.Select(BuildEntity).ToArray(); + var entities = dtos.Select(x => BuildEntity(false, isMedia, isMember, x)).ToArray(); return entities; } @@ -255,7 +233,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(BuildEntity).ToList(); + return dtos.Select(x => BuildEntity(false, false, false, x)).ToList(); } public IEnumerable GetByQuery(IQuery query, Guid objectType) @@ -264,7 +242,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, new[] { objectType }); + var sql = GetBaseWhere(isContent, isMedia, isMember, false, null, objectType); var translator = new SqlTranslator(sql, query); sql = translator.Translate(); @@ -378,14 +356,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, new[] { objectType }); + var sql = GetBaseWhere(isContent, isMedia, isMember, false, filter, 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) @@ -423,15 +401,15 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (isContent || isMedia || isMember) { sql - .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); + .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); } if (isContent) { sql - .LeftJoin().On((left, right) => left.NodeId == right.NodeId); + .InnerJoin().On((left, right) => left.NodeId == right.NodeId); } if (isMedia) @@ -455,10 +433,10 @@ 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[] objectTypes) + protected Sql GetBaseWhere(bool isContent, bool isMedia, bool isMember, bool isCount, Action> filter, Guid objectType) { return GetBase(isContent, isMedia, isMember, filter, isCount) - .WhereIn(x => x.NodeObjectType, objectTypes); + .Where(x => x.NodeObjectType == objectType); } // gets the base SELECT + FROM + WHERE sql @@ -532,13 +510,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (sql == null) throw new ArgumentNullException(nameof(sql)); if (ordering == null) throw new ArgumentNullException(nameof(ordering)); - // 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 - var orderBy = ordering.OrderBy.ToUpperInvariant() switch - { - "PATH" => SqlSyntax.GetQuotedColumn(NodeDto.TableName, "path"), - _ => ordering.OrderBy - }; + // 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); @@ -551,17 +524,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement #region Classes /// - /// The DTO used to fetch results for a generic content item which could be either a document, media or a member + /// The DTO used to fetch results for a content item with its variation info /// - 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 + private class ContentEntityDto : BaseDto { public ContentVariation Variations { get; set; } @@ -569,17 +534,11 @@ 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 { } @@ -630,13 +589,13 @@ namespace Umbraco.Core.Persistence.Repositories.Implement #region Factory - private EntitySlim BuildEntity(BaseDto dto) + private EntitySlim BuildEntity(bool isContent, bool isMedia, bool isMember, BaseDto dto) { - if (dto.NodeObjectType == Constants.ObjectTypes.Document) + if (isContent) return BuildDocumentEntity(dto); - if (dto.NodeObjectType == Constants.ObjectTypes.Media) + if (isMedia) return BuildMediaEntity(dto); - if (dto.NodeObjectType == Constants.ObjectTypes.Member) + if (isMember) return BuildMemberEntity(dto); // EntitySlim does not track changes @@ -691,7 +650,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var entity = new DocumentEntitySlim(); BuildContentEntity(entity, dto); - if (dto is DocumentEntityDto contentDto) + if (dto is ContentEntityDto contentDto) { // fill in the invariant info entity.Edited = contentDto.Edited; diff --git a/src/Umbraco.Tests/Persistence/Repositories/EntityRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/EntityRepositoryTest.cs deleted file mode 100644 index a4df5bcf78..0000000000 --- a/src/Umbraco.Tests/Persistence/Repositories/EntityRepositoryTest.cs +++ /dev/null @@ -1,95 +0,0 @@ -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/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index c87e6501f9..4b035f631e 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -140,7 +140,6 @@ - diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index 11b1260e71..0513017b70 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -662,9 +662,6 @@ 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) { diff --git a/src/Umbraco.Web/Models/Mapping/EntityMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/EntityMapDefinition.cs index afd1a6b61c..2e44f1327b 100644 --- a/src/Umbraco.Web/Models/Mapping/EntityMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/EntityMapDefinition.cs @@ -226,11 +226,11 @@ namespace Umbraco.Web.Models.Mapping { switch (entity) { - case IMemberEntitySlim memberEntity: - return memberEntity.ContentTypeIcon.IfNullOrWhiteSpace(Constants.Icons.Member); - case IContentEntitySlim contentEntity: + case ContentEntitySlim contentEntity: // NOTE: this case covers both content and media entities - return contentEntity.ContentTypeIcon; + return contentEntity.ContentTypeIcon; + case MemberEntitySlim memberEntity: + return memberEntity.ContentTypeIcon.IfNullOrWhiteSpace(Constants.Icons.Member); } return null; diff --git a/src/Umbraco.Web/Models/Mapping/UserMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/UserMapDefinition.cs index aa158799cb..88960fb189 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(IEntitySlim entity) - => entity is IContentEntitySlim contentEntity ? contentEntity.ContentTypeIcon : null; + private static string MapContentTypeIcon(EntitySlim entity) + => entity is ContentEntitySlim contentEntity ? contentEntity.ContentTypeIcon : null; private IEnumerable GetStartNodes(int[] startNodeIds, UmbracoObjectTypes objectType, string localizedKey, MapperContext context) { diff --git a/src/Umbraco.Web/Trees/ContentBlueprintTreeController.cs b/src/Umbraco.Web/Trees/ContentBlueprintTreeController.cs index fd05f7cfbd..ac75fd831d 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 => ((IContentEntitySlim) x).ContentTypeAlias).Distinct(); + var contentTypeAliases = entities.Select(x => ((ContentEntitySlim) 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 == ((IContentEntitySlim) x).ContentTypeAlias); + var blueprintsForDocType = entities.Where(x => ct.Alias == ((ContentEntitySlim) x).ContentTypeAlias); nodes.AddRange(blueprintsForDocType .Select(entity => { From 41486c7ed9734b722e101681a48ef9dd82a9abbc Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 5 Nov 2019 09:35:57 +0100 Subject: [PATCH 152/548] AB3613 - Only enumerate the folder if it exists --- src/Umbraco.Web/Editors/TourController.cs | 36 +++++++++++++---------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/src/Umbraco.Web/Editors/TourController.cs b/src/Umbraco.Web/Editors/TourController.cs index 166e5a894d..25b4f7e9fc 100644 --- a/src/Umbraco.Web/Editors/TourController.cs +++ b/src/Umbraco.Web/Editors/TourController.cs @@ -51,29 +51,35 @@ namespace Umbraco.Web.Editors } //collect all tour files in packages - foreach (var plugin in Directory.EnumerateDirectories(IOHelper.MapPath(SystemDirectories.AppPlugins))) + var appPlugins = IOHelper.MapPath(SystemDirectories.AppPlugins); + if (Directory.Exists(appPlugins)) { - var pluginName = Path.GetFileName(plugin.TrimEnd('\\')); - var pluginFilters = _filters.Where(x => x.PluginName != null && x.PluginName.IsMatch(pluginName)).ToList(); - - //If there is any filter applied to match the plugin only (no file or tour alias) then ignore the plugin entirely - var isPluginFiltered = pluginFilters.Any(x => x.TourFileName == null && x.TourAlias == null); - if (isPluginFiltered) continue; - - //combine matched package filters with filters not specific to a package - var combinedFilters = nonPluginFilters.Concat(pluginFilters).ToList(); - - foreach (var backofficeDir in Directory.EnumerateDirectories(plugin, "backoffice")) + foreach (var plugin in Directory.EnumerateDirectories(appPlugins)) { - foreach (var tourDir in Directory.EnumerateDirectories(backofficeDir, "tours")) + var pluginName = Path.GetFileName(plugin.TrimEnd('\\')); + var pluginFilters = _filters.Where(x => x.PluginName != null && x.PluginName.IsMatch(pluginName)) + .ToList(); + + //If there is any filter applied to match the plugin only (no file or tour alias) then ignore the plugin entirely + var isPluginFiltered = pluginFilters.Any(x => x.TourFileName == null && x.TourAlias == null); + if (isPluginFiltered) continue; + + //combine matched package filters with filters not specific to a package + var combinedFilters = nonPluginFilters.Concat(pluginFilters).ToList(); + + foreach (var backofficeDir in Directory.EnumerateDirectories(plugin, "backoffice")) { - foreach (var tourFile in Directory.EnumerateFiles(tourDir, "*.json")) + foreach (var tourDir in Directory.EnumerateDirectories(backofficeDir, "tours")) { - TryParseTourFile(tourFile, result, combinedFilters, aliasOnlyFilters, pluginName); + foreach (var tourFile in Directory.EnumerateFiles(tourDir, "*.json")) + { + TryParseTourFile(tourFile, result, combinedFilters, aliasOnlyFilters, pluginName); + } } } } } + //Get all allowed sections for the current user var allowedSections = user.AllowedSections.ToList(); From 6b7a48d00b83f681763b4a7bada9f897bb9bb781 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 6 Nov 2019 12:43:10 +1100 Subject: [PATCH 153/548] Gets entity repository working with relation queries and adds test --- .../Repositories/IEntityRepository.cs | 5 +- .../Repositories/IRelationRepository.cs | 4 + .../Implement/EntityRepository.cs | 36 +++++++-- .../Implement/RelationRepository.cs | 18 ++++- src/Umbraco.Core/Services/IRelationService.cs | 10 +++ .../Services/Implement/RelationService.cs | 11 +++ .../Repositories/ContentTypeRepositoryTest.cs | 3 +- .../Repositories/DocumentRepositoryTest.cs | 3 +- .../Repositories/DomainRepositoryTest.cs | 3 +- .../Repositories/MediaRepositoryTest.cs | 3 +- .../Repositories/MemberRepositoryTest.cs | 3 +- .../PublicAccessRepositoryTest.cs | 3 +- .../Repositories/RelationRepositoryTest.cs | 78 ++++++++++++++++++- .../Repositories/TagRepositoryTest.cs | 6 +- .../Repositories/TemplateRepositoryTest.cs | 3 +- .../Repositories/UserRepositoryTest.cs | 6 +- .../Services/ContentServicePerformanceTest.cs | 18 +++-- .../Services/ContentServiceTests.cs | 3 +- 18 files changed, 182 insertions(+), 34 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs index a6a1691347..102dc7c81a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs @@ -55,8 +55,9 @@ namespace Umbraco.Core.Persistence.Repositories /// 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); + IEnumerable GetPagedResultsByQuery( + IQuery query, Guid[] objectTypes, long pageIndex, int pageSize, out long totalRecords, + IQuery filter, Ordering ordering, IQuery relationQuery = null); /// /// Gets paged entities for a query and a specific object type diff --git a/src/Umbraco.Core/Persistence/Repositories/IRelationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IRelationRepository.cs index 9a2e42568e..9a99cb2a77 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IRelationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IRelationRepository.cs @@ -1,5 +1,7 @@ using System.Collections.Generic; using Umbraco.Core.Models; +using Umbraco.Core.Models.Entities; +using Umbraco.Core.Persistence.Querying; namespace Umbraco.Core.Persistence.Repositories { @@ -13,5 +15,7 @@ namespace Umbraco.Core.Persistence.Repositories /// 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); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs index 2b68c8fe19..9b05e95a17 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs @@ -82,24 +82,40 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // get a page of entities public IEnumerable GetPagedResultsByQuery(IQuery query, Guid[] objectTypes, long pageIndex, int pageSize, out long totalRecords, - IQuery filter, Ordering ordering) + IQuery filter, Ordering ordering, IQuery relationQuery = 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, x => + var sql = GetBaseWhere(isContent, isMedia, isMember, false, s => { - if (filter == null) return; - foreach (var filterClause in filter.GetWhereClauses()) - x.Where(filterClause.Item1, filterClause.Item2); + if (relationQuery != null) + { + // 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). + s.LeftJoin().On((left, right) => left.NodeId == right.ChildId || left.NodeId == right.ParentId); + s.LeftJoin().On((left, right) => left.RelationType == right.Id); + + // append the relations wheres + foreach (var relClause in relationQuery.GetWhereClauses()) + s.Where(relClause.Item1, relClause.Item2); + } + + if (filter != null) + { + foreach (var filterClause in filter.GetWhereClauses()) + s.Where(filterClause.Item1, filterClause.Item2); + } + + }, objectTypes); ordering = ordering ?? Ordering.ByDefault(); var translator = new SqlTranslator(sql, query); sql = translator.Translate(); - sql = AddGroupBy(true, true, true, sql, ordering.IsEmpty); + sql = AddGroupBy(isContent, isMedia, isMember, sql, ordering.IsEmpty); if (!ordering.IsEmpty) { @@ -457,8 +473,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // for a given object type, with a given filter protected Sql GetBaseWhere(bool isContent, bool isMedia, bool isMember, bool isCount, Action> filter, Guid[] objectTypes) { - return GetBase(isContent, isMedia, isMember, filter, isCount) - .WhereIn(x => x.NodeObjectType, objectTypes); + 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 diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs index e6c4c6fd7e..530872203c 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs @@ -11,6 +11,7 @@ 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 @@ -21,11 +22,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 @@ -156,6 +159,19 @@ namespace Umbraco.Core.Persistence.Repositories.Implement #endregion + public IEnumerable GetPagedParentEntitiesByChildId(int childId, long pageIndex, int pageSize, out long totalRecords) + { + // Create a query to match the child id + var relQuery = Query().Where(r => r.ChildId == childId); + + // Because of the way that the entity repository joins relations (on both child or parent) we need to add + // a clause to filter out the child entity from being returned from the results + var entityQuery = Query().Where(e => e.Id != childId); + + return _entityRepository.GetPagedResultsByQuery(entityQuery, Array.Empty(), pageIndex, pageSize, out totalRecords, null, null, relQuery); + + } + public void DeleteByParent(int parentId, params string[] relationTypeAliases) { var subQuery = Sql().Select(x => x.Id) diff --git a/src/Umbraco.Core/Services/IRelationService.cs b/src/Umbraco.Core/Services/IRelationService.cs index 49fa21af2b..95f04b6df2 100644 --- a/src/Umbraco.Core/Services/IRelationService.cs +++ b/src/Umbraco.Core/Services/IRelationService.cs @@ -189,6 +189,16 @@ 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); + /// /// Gets the Parent and Child objects from a list of Relations as a list of objects. /// diff --git a/src/Umbraco.Core/Services/Implement/RelationService.cs b/src/Umbraco.Core/Services/Implement/RelationService.cs index 490f36e7c7..56bf2bba06 100644 --- a/src/Umbraco.Core/Services/Implement/RelationService.cs +++ b/src/Umbraco.Core/Services/Implement/RelationService.cs @@ -263,6 +263,17 @@ namespace Umbraco.Core.Services.Implement } } + /// + public IEnumerable GetPagedParentEntitiesByChildId(int id, long pageIndex, int pageSize, out long totalChildren) + { + using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + { + //var query = Query().Where(x => x.ChildId == id); + return _relationRepository.GetPagedParentEntitiesByChildId(id, pageIndex, pageSize, out totalChildren); + } + throw new NotImplementedException(); + } + /// public IEnumerable> GetEntitiesFromRelations(IEnumerable relations) { diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs index 07ca7d238d..8ed935795d 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs @@ -37,7 +37,8 @@ namespace Umbraco.Tests.Persistence.Repositories contentTypeRepository = new ContentTypeRepository(scopeAccessor, AppCaches.Disabled, Logger, commonRepository, langRepository); var languageRepository = new LanguageRepository(scopeAccessor, AppCaches.Disabled, Logger); var relationTypeRepository = new RelationTypeRepository(scopeAccessor, AppCaches.Disabled, Logger); - var relationRepository = new RelationRepository(scopeAccessor, Logger, relationTypeRepository); + var entityRepository = new EntityRepository(scopeAccessor); + var relationRepository = new RelationRepository(scopeAccessor, Logger, relationTypeRepository, entityRepository); var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); var repository = new DocumentRepository(scopeAccessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors); return repository; diff --git a/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs index 45dc3de2e3..3a36a647f0 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs @@ -70,7 +70,8 @@ namespace Umbraco.Tests.Persistence.Repositories var languageRepository = new LanguageRepository(scopeAccessor, appCaches, Logger); contentTypeRepository = new ContentTypeRepository(scopeAccessor, appCaches, Logger, commonRepository, languageRepository); var relationTypeRepository = new RelationTypeRepository(scopeAccessor, AppCaches.Disabled, Logger); - var relationRepository = new RelationRepository(scopeAccessor, Logger, relationTypeRepository); + var entityRepository = new EntityRepository(scopeAccessor); + var relationRepository = new RelationRepository(scopeAccessor, Logger, relationTypeRepository, entityRepository); var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); var repository = new DocumentRepository(scopeAccessor, appCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors); return repository; diff --git a/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs index 27ea92fed6..ad27aed309 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs @@ -28,7 +28,8 @@ namespace Umbraco.Tests.Persistence.Repositories languageRepository = new LanguageRepository(accessor, Core.Cache.AppCaches.Disabled, Logger); contentTypeRepository = new ContentTypeRepository(accessor, Core.Cache.AppCaches.Disabled, Logger, commonRepository, languageRepository); var relationTypeRepository = new RelationTypeRepository(accessor, Core.Cache.AppCaches.Disabled, Logger); - var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository); + var entityRepository = new EntityRepository(accessor); + var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository, entityRepository); var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); documentRepository = new DocumentRepository(accessor, Core.Cache.AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors); var domainRepository = new DomainRepository(accessor, Core.Cache.AppCaches.Disabled, Logger); diff --git a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs index 93587506e8..ced0ee75e9 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs @@ -43,7 +43,8 @@ namespace Umbraco.Tests.Persistence.Repositories mediaTypeRepository = new MediaTypeRepository(scopeAccessor, appCaches, Logger, commonRepository, languageRepository); var tagRepository = new TagRepository(scopeAccessor, appCaches, Logger); var relationTypeRepository = new RelationTypeRepository(scopeAccessor, AppCaches.Disabled, Logger); - var relationRepository = new RelationRepository(scopeAccessor, Logger, relationTypeRepository); + var entityRepository = new EntityRepository(scopeAccessor); + var relationRepository = new RelationRepository(scopeAccessor, Logger, relationTypeRepository, entityRepository); var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); var repository = new MediaRepository(scopeAccessor, appCaches, Logger, mediaTypeRepository, tagRepository, Mock.Of(), relationRepository, relationTypeRepository, propertyEditors); return repository; diff --git a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs index 72b4691639..7531d92b49 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs @@ -37,7 +37,8 @@ namespace Umbraco.Tests.Persistence.Repositories memberGroupRepository = new MemberGroupRepository(accessor, AppCaches.Disabled, Logger); var tagRepo = new TagRepository(accessor, AppCaches.Disabled, Logger); var relationTypeRepository = new RelationTypeRepository(accessor, AppCaches.Disabled, Logger); - var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository); + var entityRepository = new EntityRepository(accessor); + var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository, entityRepository); var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); var repository = new MemberRepository(accessor, AppCaches.Disabled, Logger, memberTypeRepository, memberGroupRepository, tagRepo, Mock.Of(), relationRepository, relationTypeRepository, propertyEditors); return repository; diff --git a/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs index 84a0f608f7..9a65bde41f 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs @@ -312,7 +312,8 @@ namespace Umbraco.Tests.Persistence.Repositories var languageRepository = new LanguageRepository(accessor, AppCaches, Logger); contentTypeRepository = new ContentTypeRepository(accessor, AppCaches, Logger, commonRepository, languageRepository); var relationTypeRepository = new RelationTypeRepository(accessor, AppCaches, Logger); - var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository); + var entityRepository = new EntityRepository(accessor); + var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository, entityRepository); var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); var repository = new DocumentRepository(accessor, AppCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors); return repository; diff --git a/src/Umbraco.Tests/Persistence/Repositories/RelationRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/RelationRepositoryTest.cs index 8d9f82a776..b67a662123 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,73 @@ namespace Umbraco.Tests.Persistence.Repositories } } + [Test] + public void Get_Paged_Parent_Entities_By_Child_Id() + { + //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 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); + + 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); + } + } + [Test] public void Can_Perform_Exists_On_RelationRepository() { @@ -274,8 +344,10 @@ namespace Umbraco.Tests.Persistence.Repositories 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/TagRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs index a4155639be..fe0db4563a 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs @@ -961,7 +961,8 @@ namespace Umbraco.Tests.Persistence.Repositories var languageRepository = new LanguageRepository(accessor, AppCaches.Disabled, Logger); contentTypeRepository = new ContentTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository, languageRepository); var relationTypeRepository = new RelationTypeRepository(accessor, AppCaches.Disabled, Logger); - var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository); + var entityRepository = new EntityRepository(accessor); + var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository, entityRepository); var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); var repository = new DocumentRepository(accessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors); return repository; @@ -976,7 +977,8 @@ namespace Umbraco.Tests.Persistence.Repositories var languageRepository = new LanguageRepository(accessor, AppCaches.Disabled, Logger); mediaTypeRepository = new MediaTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository, languageRepository); var relationTypeRepository = new RelationTypeRepository(accessor, AppCaches.Disabled, Logger); - var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository); + var entityRepository = new EntityRepository(accessor); + var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository, entityRepository); var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); var repository = new MediaRepository(accessor, AppCaches.Disabled, Logger, mediaTypeRepository, tagRepository, Mock.Of(), relationRepository, relationTypeRepository, propertyEditors); return repository; diff --git a/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs index e996c4f6a1..4bf50c6ffd 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs @@ -243,7 +243,8 @@ namespace Umbraco.Tests.Persistence.Repositories 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 relationRepository = new RelationRepository(ScopeProvider, Logger, relationTypeRepository); + var entityRepository = new EntityRepository(ScopeProvider); + var relationRepository = new RelationRepository(ScopeProvider, Logger, relationTypeRepository, entityRepository); var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); var contentRepo = new DocumentRepository(ScopeProvider, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors); diff --git a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs index 0438e2193b..f4ab387683 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs @@ -32,7 +32,8 @@ namespace Umbraco.Tests.Persistence.Repositories mediaTypeRepository = new MediaTypeRepository(accessor, AppCaches, Mock.Of(), commonRepository, languageRepository); var tagRepository = new TagRepository(accessor, AppCaches, Mock.Of()); var relationTypeRepository = new RelationTypeRepository(accessor, AppCaches.Disabled, Logger); - var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository); + var entityRepository = new EntityRepository(accessor); + var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository, entityRepository); var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); var repository = new MediaRepository(accessor, AppCaches, Mock.Of(), mediaTypeRepository, tagRepository, Mock.Of(), relationRepository, relationTypeRepository, propertyEditors); return repository; @@ -53,7 +54,8 @@ namespace Umbraco.Tests.Persistence.Repositories var languageRepository = new LanguageRepository(accessor, AppCaches, Logger); contentTypeRepository = new ContentTypeRepository(accessor, AppCaches, Logger, commonRepository, languageRepository); var relationTypeRepository = new RelationTypeRepository(accessor, AppCaches.Disabled, Logger); - var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository); + var entityRepository = new EntityRepository(accessor); + var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository, entityRepository); var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); var repository = new DocumentRepository(accessor, AppCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors); return repository; diff --git a/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs b/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs index d7729c19c1..763635c393 100644 --- a/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs +++ b/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs @@ -42,15 +42,17 @@ namespace Umbraco.Tests.Services private DocumentRepository CreateDocumentRepository(IScopeProvider provider) { - 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 relationTypeRepository = new RelationTypeRepository((IScopeAccessor)provider, AppCaches.Disabled, Logger); - var relationRepository = new RelationRepository((IScopeAccessor)provider, Logger, relationTypeRepository); + 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 repository = new DocumentRepository((IScopeAccessor)provider, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository, relationRepository, relationTypeRepository, propertyEditors); + var repository = new DocumentRepository(accessor, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository, relationRepository, relationTypeRepository, propertyEditors); return repository; } diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index 89873b5880..88b5cb5c34 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -3208,7 +3208,8 @@ namespace Umbraco.Tests.Services var languageRepository = new LanguageRepository(accessor, AppCaches.Disabled, Logger); contentTypeRepository = new ContentTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository, languageRepository); var relationTypeRepository = new RelationTypeRepository(accessor, AppCaches.Disabled, Logger); - var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository); + var entityRepository = new EntityRepository(accessor); + var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository, entityRepository); var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); var repository = new DocumentRepository(accessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors); return repository; From d1948b1543590d670fbe5689d91a304a16d063d4 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 6 Nov 2019 13:08:03 +1100 Subject: [PATCH 154/548] Adds relation service tests for both paged children and parents and tests the repo --- .../Repositories/IRelationRepository.cs | 2 + .../Implement/RelationRepository.cs | 11 ++ src/Umbraco.Core/Services/IRelationService.cs | 10 ++ .../Services/Implement/RelationService.cs | 11 +- .../Repositories/RelationRepositoryTest.cs | 111 ++++++++++++------ 5 files changed, 105 insertions(+), 40 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/IRelationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IRelationRepository.cs index 9a99cb2a77..23271b34ee 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IRelationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IRelationRepository.cs @@ -17,5 +17,7 @@ namespace Umbraco.Core.Persistence.Repositories void DeleteByParent(int parentId, params string[] relationTypeAliases); IEnumerable GetPagedParentEntitiesByChildId(int childId, long pageIndex, int pageSize, out long totalRecords); + + IEnumerable GetPagedChildEntitiesByParentId(int parentId, long pageIndex, int pageSize, out long totalRecords); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs index 530872203c..71ca737fbb 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs @@ -169,7 +169,18 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var entityQuery = Query().Where(e => e.Id != childId); return _entityRepository.GetPagedResultsByQuery(entityQuery, Array.Empty(), pageIndex, pageSize, out totalRecords, null, null, relQuery); + } + public IEnumerable GetPagedChildEntitiesByParentId(int parentId, long pageIndex, int pageSize, out long totalRecords) + { + // Create a query to match the parent id + var relQuery = Query().Where(r => r.ParentId == parentId); + + // Because of the way that the entity repository joins relations (on both child or parent) we need to add + // a clause to filter out the child entity from being returned from the results + var entityQuery = Query().Where(e => e.Id != parentId); + + return _entityRepository.GetPagedResultsByQuery(entityQuery, Array.Empty(), pageIndex, pageSize, out totalRecords, null, null, relQuery); } public void DeleteByParent(int parentId, params string[] relationTypeAliases) diff --git a/src/Umbraco.Core/Services/IRelationService.cs b/src/Umbraco.Core/Services/IRelationService.cs index 95f04b6df2..6bfe1f0be3 100644 --- a/src/Umbraco.Core/Services/IRelationService.cs +++ b/src/Umbraco.Core/Services/IRelationService.cs @@ -199,6 +199,16 @@ namespace Umbraco.Core.Services /// IEnumerable GetPagedParentEntitiesByChildId(int id, long pageIndex, int pageSize, out long totalChildren); + /// + /// Returns paged child entities for a related parent id + /// + /// + /// + /// + /// + /// + IEnumerable GetPagedChildEntitiesByParentId(int id, long pageIndex, int pageSize, out long totalChildren); + /// /// Gets the Parent and Child objects from a list of Relations as a list of objects. /// diff --git a/src/Umbraco.Core/Services/Implement/RelationService.cs b/src/Umbraco.Core/Services/Implement/RelationService.cs index 56bf2bba06..c6d541c48b 100644 --- a/src/Umbraco.Core/Services/Implement/RelationService.cs +++ b/src/Umbraco.Core/Services/Implement/RelationService.cs @@ -268,10 +268,17 @@ namespace Umbraco.Core.Services.Implement { using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { - //var query = Query().Where(x => x.ChildId == id); return _relationRepository.GetPagedParentEntitiesByChildId(id, pageIndex, pageSize, out totalChildren); } - throw new NotImplementedException(); + } + + /// + public IEnumerable GetPagedChildEntitiesByParentId(int id, long pageIndex, int pageSize, out long totalChildren) + { + using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + { + return _relationRepository.GetPagedChildEntitiesByParentId(id, pageIndex, pageSize, out totalChildren); + } } /// diff --git a/src/Umbraco.Tests/Persistence/Repositories/RelationRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/RelationRepositoryTest.cs index b67a662123..4c8a7c1b16 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/RelationRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/RelationRepositoryTest.cs @@ -174,44 +174,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Get_Paged_Parent_Entities_By_Child_Id() { - //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 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); + CreateTestDataForPagingTests(out var createdContent, out var createdMembers, out var createdMedia); var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) @@ -238,6 +201,78 @@ namespace Umbraco.Tests.Persistence.Repositories } } + [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); + } + } + + 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() { From 9ef40fb4844c32f06a4fd9fbaebd192fa4e60499 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 6 Nov 2019 13:16:28 +1100 Subject: [PATCH 155/548] adds notes --- .../Repositories/Implement/RelationRepository.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs index 71ca737fbb..235bcd8484 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs @@ -168,6 +168,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // a clause to filter out the child entity from being returned from the results var entityQuery = Query().Where(e => e.Id != childId); + // 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(entityQuery, Array.Empty(), pageIndex, pageSize, out totalRecords, null, null, relQuery); } @@ -180,6 +186,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // a clause to filter out the child entity from being returned from the results var entityQuery = Query().Where(e => e.Id != parentId); + // 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(entityQuery, Array.Empty(), pageIndex, pageSize, out totalRecords, null, null, relQuery); } From 12541e7e8f41431ed05bfb02238862f337506d97 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 6 Nov 2019 13:22:12 +1100 Subject: [PATCH 156/548] reverts changes to IUmbracoEntity, we don't need to query on the object type property --- src/Umbraco.Core/Models/ContentBase.cs | 3 --- src/Umbraco.Core/Models/ContentTypeBase.cs | 3 --- src/Umbraco.Core/Models/DataType.cs | 3 --- src/Umbraco.Core/Models/Entities/IUmbracoEntity.cs | 7 ++----- src/Umbraco.Core/Models/EntityContainer.cs | 2 -- src/Umbraco.Core/Models/SimpleContentType.cs | 3 --- .../Persistence/Factories/ContentBaseFactory.cs | 2 +- .../Persistence/Factories/ContentTypeFactory.cs | 1 - src/Umbraco.Core/Persistence/Factories/DataTypeFactory.cs | 1 - .../Persistence/Mappers/UmbracoEntityMapper.cs | 1 - .../Persistence/Repositories/Implement/EntityRepository.cs | 1 - 11 files changed, 3 insertions(+), 24 deletions(-) diff --git a/src/Umbraco.Core/Models/ContentBase.cs b/src/Umbraco.Core/Models/ContentBase.cs index 2ea0fd855b..fbb68194b7 100644 --- a/src/Umbraco.Core/Models/ContentBase.cs +++ b/src/Umbraco.Core/Models/ContentBase.cs @@ -108,9 +108,6 @@ namespace Umbraco.Core.Models [IgnoreDataMember] public int VersionId { get; set; } - [DataMember] - public Guid NodeObjectType { get; internal set; } - /// /// Integer Id of the default ContentType /// diff --git a/src/Umbraco.Core/Models/ContentTypeBase.cs b/src/Umbraco.Core/Models/ContentTypeBase.cs index 1f3f1a5c13..04bcb7424a 100644 --- a/src/Umbraco.Core/Models/ContentTypeBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeBase.cs @@ -113,9 +113,6 @@ namespace Umbraco.Core.Models OnPropertyChanged(nameof(PropertyTypes)); } - [DataMember] - public Guid NodeObjectType { get; internal set; } - /// /// The Alias of the ContentType /// diff --git a/src/Umbraco.Core/Models/DataType.cs b/src/Umbraco.Core/Models/DataType.cs index cba53bbd10..c237f6381c 100644 --- a/src/Umbraco.Core/Models/DataType.cs +++ b/src/Umbraco.Core/Models/DataType.cs @@ -65,9 +65,6 @@ namespace Umbraco.Core.Models [DataMember] public string EditorAlias => _editor.Alias; - [DataMember] - public Guid NodeObjectType { get; internal set; } - /// [DataMember] public ValueStorageType DatabaseType diff --git a/src/Umbraco.Core/Models/Entities/IUmbracoEntity.cs b/src/Umbraco.Core/Models/Entities/IUmbracoEntity.cs index 13087b5e95..e5f628b098 100644 --- a/src/Umbraco.Core/Models/Entities/IUmbracoEntity.cs +++ b/src/Umbraco.Core/Models/Entities/IUmbracoEntity.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; namespace Umbraco.Core.Models.Entities { @@ -12,7 +11,5 @@ namespace Umbraco.Core.Models.Entities /// An IUmbracoEntity can participate in notifications. /// public interface IUmbracoEntity : ITreeEntity, IRememberBeingDirty - { - Guid NodeObjectType { get; } - } + { } } diff --git a/src/Umbraco.Core/Models/EntityContainer.cs b/src/Umbraco.Core/Models/EntityContainer.cs index 71a477a9d6..70f6cbd878 100644 --- a/src/Umbraco.Core/Models/EntityContainer.cs +++ b/src/Umbraco.Core/Models/EntityContainer.cs @@ -62,8 +62,6 @@ namespace Umbraco.Core.Models /// public Guid ContainerObjectType => ObjectTypeMap[_containedObjectType]; - Guid IUmbracoEntity.NodeObjectType => ContainerObjectType; - /// /// Gets the container object type corresponding to a contained object type. /// diff --git a/src/Umbraco.Core/Models/SimpleContentType.cs b/src/Umbraco.Core/Models/SimpleContentType.cs index b4e7688eea..5c81017ec8 100644 --- a/src/Umbraco.Core/Models/SimpleContentType.cs +++ b/src/Umbraco.Core/Models/SimpleContentType.cs @@ -44,14 +44,11 @@ namespace Umbraco.Core.Models Name = contentType.Name; AllowedAsRoot = contentType.AllowedAsRoot; IsElement = contentType.IsElement; - NodeObjectType = contentType.NodeObjectType; } /// public string Alias { get; } - public Guid NodeObjectType { get; } - public int Id { get; } /// diff --git a/src/Umbraco.Core/Persistence/Factories/ContentBaseFactory.cs b/src/Umbraco.Core/Persistence/Factories/ContentBaseFactory.cs index 25cc6bc358..434e0393cd 100644 --- a/src/Umbraco.Core/Persistence/Factories/ContentBaseFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/ContentBaseFactory.cs @@ -34,7 +34,7 @@ namespace Umbraco.Core.Persistence.Factories content.VersionId = contentVersionDto.Id; content.Name = contentVersionDto.Text; - content.NodeObjectType = nodeDto.NodeObjectType ?? Guid.Empty; + content.Path = nodeDto.Path; content.Level = nodeDto.Level; content.ParentId = nodeDto.ParentId; diff --git a/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs b/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs index 46489aa69e..54cfee0ffa 100644 --- a/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs @@ -107,7 +107,6 @@ namespace Umbraco.Core.Persistence.Factories { entity.Id = dto.NodeDto.NodeId; entity.Key = dto.NodeDto.UniqueId; - entity.NodeObjectType = dto.NodeDto.NodeObjectType ?? Guid.Empty; entity.Alias = dto.Alias; entity.Name = dto.NodeDto.Text; entity.Icon = dto.Icon; diff --git a/src/Umbraco.Core/Persistence/Factories/DataTypeFactory.cs b/src/Umbraco.Core/Persistence/Factories/DataTypeFactory.cs index 5d92234ca8..f189d38d05 100644 --- a/src/Umbraco.Core/Persistence/Factories/DataTypeFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/DataTypeFactory.cs @@ -28,7 +28,6 @@ namespace Umbraco.Core.Persistence.Factories dataType.DisableChangeTracking(); dataType.CreateDate = dto.NodeDto.CreateDate; - dataType.NodeObjectType = dto.NodeDto.NodeObjectType ?? Guid.Empty; dataType.DatabaseType = dto.DbType.EnumParse(true); dataType.Id = dto.NodeId; dataType.Key = dto.NodeDto.UniqueId; diff --git a/src/Umbraco.Core/Persistence/Mappers/UmbracoEntityMapper.cs b/src/Umbraco.Core/Persistence/Mappers/UmbracoEntityMapper.cs index 96f664cc25..16e2e8bcd5 100644 --- a/src/Umbraco.Core/Persistence/Mappers/UmbracoEntityMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/UmbracoEntityMapper.cs @@ -24,7 +24,6 @@ namespace Umbraco.Core.Persistence.Mappers DefineMap(nameof(IUmbracoEntity.Trashed), nameof(NodeDto.Trashed)); DefineMap(nameof(IUmbracoEntity.Key), nameof(NodeDto.UniqueId)); DefineMap(nameof(IUmbracoEntity.CreatorId), nameof(NodeDto.UserId)); - DefineMap(nameof(IUmbracoEntity.NodeObjectType), nameof(NodeDto.NodeObjectType)); } } } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs index 9b05e95a17..8aa912dc0d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs @@ -76,7 +76,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement public IEnumerable GetPagedResultsByQuery(IQuery query, Guid objectType, long pageIndex, int pageSize, out long totalRecords, IQuery filter, Ordering ordering) { - query = query.Where(x => x.NodeObjectType == objectType); return GetPagedResultsByQuery(query, new[] { objectType }, pageIndex, pageSize, out totalRecords, filter, ordering); } From b03bca409363fbcdd14fec140fadd1234b96b36f Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 6 Nov 2019 13:29:50 +1100 Subject: [PATCH 157/548] removes test code --- .../Implement/EntityRepository.cs | 39 +------------------ 1 file changed, 1 insertion(+), 38 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs index 8aa912dc0d..3ca28b6b44 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs @@ -35,44 +35,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected ISqlSyntaxProvider SqlSyntax => _scopeAccessor.AmbientScope.SqlContext.SqlSyntax; #region Repository - - //public IEnumerable GetPagedResults(IDictionary> typesAndIds, long pageIndex, int pageSize, out long totalRecords, Ordering ordering) - //{ - // var isContent = typesAndIds.Keys.Any(objectType => objectType == Constants.ObjectTypes.Document || objectType == Constants.ObjectTypes.DocumentBlueprint); - // var isMedia = typesAndIds.Keys.Any(objectType => objectType == Constants.ObjectTypes.Media); - // var isMember = typesAndIds.Keys.Any(objectType => objectType == Constants.ObjectTypes.Member); - - // var sql = GetBase(isContent, isMedia, isMember, null, false) - // .WhereIn(x => x.NodeObjectType, typesAndIds.Keys); - - // ordering = ordering ?? Ordering.ByDefault(); - - // sql = AddGroupBy(isContent, isMedia, isMember, sql, ordering.IsEmpty); - - // if (!ordering.IsEmpty) - // { - // // apply ordering - // ApplyOrdering(ref sql, ordering); - // } - - // // TODO: we should be able to do sql = sql.OrderBy(x => Alias(x.NodeId, "NodeId")); but we can't because the OrderBy extension don't support Alias currently - // //no matter what we always must have node id ordered at the end - // sql = ordering.Direction == Direction.Ascending ? sql.OrderBy("NodeId") : sql.OrderByDescending("NodeId"); - - // // for content we must query for ContentEntityDto entities to produce the correct culture variant entity names - // var pageIndexToFetch = pageIndex + 1; - // IEnumerable dtos; - // var page = Database.Page(pageIndexToFetch, pageSize, sql); - // dtos = page.Items; - // totalRecords = page.TotalItems; - - // var entities = dtos.Select(BuildEntity).ToArray(); - - // BuildVariants(entities.OfType()); - - // return entities; - //} - + public IEnumerable GetPagedResultsByQuery(IQuery query, Guid objectType, long pageIndex, int pageSize, out long totalRecords, IQuery filter, Ordering ordering) { From 90b6a09013b349ffc656b80737f9006ebe3313be Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 6 Nov 2019 14:35:15 +1100 Subject: [PATCH 158/548] Adds ability to get paged relations by type --- .../Repositories/IRelationRepository.cs | 4 + .../Implement/RelationRepository.cs | 47 ++++++ src/Umbraco.Core/Services/IRelationService.cs | 134 ++++++++++-------- .../Services/Implement/RelationService.cs | 10 ++ .../Services/RelationServiceTests.cs | 49 +++++++ 5 files changed, 182 insertions(+), 62 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/IRelationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IRelationRepository.cs index 9a2e42568e..3b67c25856 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IRelationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IRelationRepository.cs @@ -1,10 +1,14 @@ using System.Collections.Generic; using Umbraco.Core.Models; +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); + /// /// Deletes all relations for a parent for any specified relation type alias /// diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs index e6c4c6fd7e..7765086079 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs @@ -6,11 +6,13 @@ 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 @@ -156,6 +158,37 @@ namespace Umbraco.Core.Persistence.Repositories.Implement #endregion + 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) @@ -186,5 +219,19 @@ namespace Umbraco.Core.Persistence.Repositories.Implement entity.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/Services/IRelationService.cs b/src/Umbraco.Core/Services/IRelationService.cs index 49fa21af2b..1410b05531 100644 --- a/src/Umbraco.Core/Services/IRelationService.cs +++ b/src/Umbraco.Core/Services/IRelationService.cs @@ -8,152 +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 + /// 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 Id + /// 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 + /// An enumerable list of objects IEnumerable GetByParentId(int id, string relationTypeAlias); /// - /// Gets a list of objects by their parent entity + /// 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 Id + /// 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 + /// An enumerable list of objects IEnumerable GetByChildId(int id, string relationTypeAlias); /// - /// Gets a list of objects by their child Entity + /// 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 /// @@ -202,7 +212,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); /// @@ -211,7 +221,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); /// @@ -220,7 +230,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); /// @@ -229,14 +239,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); /// @@ -281,33 +291,33 @@ namespace Umbraco.Core.Services bool AreRelated(int parentId, int childId, string relationTypeAlias); /// - /// Saves a + /// Saves a /// /// Relation to save void Save(IRelation relation); /// - /// 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/Implement/RelationService.cs b/src/Umbraco.Core/Services/Implement/RelationService.cs index 490f36e7c7..a9d98383c7 100644 --- a/src/Umbraco.Core/Services/Implement/RelationService.cs +++ b/src/Umbraco.Core/Services/Implement/RelationService.cs @@ -207,6 +207,16 @@ namespace Umbraco.Core.Services.Implement } } + /// + 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) { diff --git a/src/Umbraco.Tests/Services/RelationServiceTests.cs b/src/Umbraco.Tests/Services/RelationServiceTests.cs index 0357c9e408..8406179d9a 100644 --- a/src/Umbraco.Tests/Services/RelationServiceTests.cs +++ b/src/Umbraco.Tests/Services/RelationServiceTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Threading; using NUnit.Framework; @@ -15,6 +16,54 @@ namespace Umbraco.Tests.Services [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() { From 049d51e466e9fec0767493dfd52626845c8e789a Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 6 Nov 2019 16:45:28 +1100 Subject: [PATCH 159/548] Adds ability to bulk insert relations and adds tests --- .../Repositories/IRelationRepository.cs | 6 ++ .../Implement/ContentRepositoryBase.cs | 25 +++--- .../Implement/RelationRepository.cs | 71 ++++++++++++++--- src/Umbraco.Core/Services/IRelationService.cs | 2 + .../Services/Implement/RelationService.cs | 18 +++++ .../Services/RelationServiceTests.cs | 78 ++++++++++++++++++- 6 files changed, 175 insertions(+), 25 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/IRelationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IRelationRepository.cs index 9a2e42568e..885d042d83 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IRelationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IRelationRepository.cs @@ -5,6 +5,12 @@ namespace Umbraco.Core.Persistence.Repositories { public interface IRelationRepository : IReadWriteQueryRepository { + /// + /// Persist multiple at once + /// + /// + void Save(IEnumerable relations); + /// /// Deletes all relations for a parent for any specified relation type alias /// diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs index d93fd72bfb..65f6dc0472 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs @@ -853,21 +853,22 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var allRelationTypes = RelationTypeRepository.GetMany(Array.Empty()) .ToDictionary(x => x.Alias, x => x); - foreach(var rel in trackedRelations) - { - if (!allRelationTypes.TryGetValue(rel.RelationTypeAlias, out var relationType)) - throw new InvalidOperationException($"The relation type {rel.RelationTypeAlias} does not exist"); + 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)) - continue; // This shouldn't happen! + if (!udiToGuids.TryGetValue(rel.Udi, out var guid)) + return null; // This shouldn't happen! - if (!keyToIds.TryGetValue(guid, out var id)) - continue; // This shouldn't happen! + if (!keyToIds.TryGetValue(guid, out var id)) + return null; // This shouldn't happen! - //Create new relation - //TODO: This is N+1, we could do this all in one operation, just need a new method on the relations repo - RelationRepository.Save(new Relation(entity.Id, id, relationType)); - } + return new Relation(entity.Id, id, relationType); + }).WhereNotNull(); + + // Save bulk relations + RelationRepository.Save(toSave); } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs index e6c4c6fd7e..b4fd0a349e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs @@ -156,6 +156,50 @@ namespace Umbraco.Core.Persistence.Repositories.Implement #endregion + 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 + + 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 void DeleteByParent(int parentId, params string[] relationTypeAliases) { var subQuery = Sql().Select(x => x.Id) @@ -171,19 +215,28 @@ namespace Umbraco.Core.Persistence.Repositories.Implement Database.Execute(Sql().Delete().WhereIn(x => x.Id, subQuery)); } - private void PopulateObjectTypes(IRelation entity) + /// + /// Used to populate the object types after insert/update + /// + /// + private void PopulateObjectTypes(params IRelation[] entities) { - var nodes = Database.Fetch(Sql().Select().From().Where(x => x.NodeId == entity.ChildId || x.NodeId == entity.ParentId)) + 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); - if(nodes.TryGetValue(entity.ParentId, out var parentObjectType)) + foreach (var e in entities) { - entity.ParentObjectType = parentObjectType.GetValueOrDefault(); - } - - if(nodes.TryGetValue(entity.ChildId, out var childObjectType)) - { - entity.ChildObjectType = childObjectType.GetValueOrDefault(); + if (nodes.TryGetValue(e.ParentId, out var parentObjectType)) + { + e.ParentObjectType = parentObjectType.GetValueOrDefault(); + } + if (nodes.TryGetValue(e.ChildId, out var childObjectType)) + { + e.ChildObjectType = childObjectType.GetValueOrDefault(); + } } } } diff --git a/src/Umbraco.Core/Services/IRelationService.cs b/src/Umbraco.Core/Services/IRelationService.cs index 49fa21af2b..6660ca5d1f 100644 --- a/src/Umbraco.Core/Services/IRelationService.cs +++ b/src/Umbraco.Core/Services/IRelationService.cs @@ -286,6 +286,8 @@ namespace Umbraco.Core.Services /// Relation to save void Save(IRelation relation); + void Save(IEnumerable relations); + /// /// Saves a /// diff --git a/src/Umbraco.Core/Services/Implement/RelationService.cs b/src/Umbraco.Core/Services/Implement/RelationService.cs index 490f36e7c7..4a5ca3d8d8 100644 --- a/src/Umbraco.Core/Services/Implement/RelationService.cs +++ b/src/Umbraco.Core/Services/Implement/RelationService.cs @@ -417,6 +417,24 @@ namespace Umbraco.Core.Services.Implement } } + 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) { diff --git a/src/Umbraco.Tests/Services/RelationServiceTests.cs b/src/Umbraco.Tests/Services/RelationServiceTests.cs index 0357c9e408..42b6f45047 100644 --- a/src/Umbraco.Tests/Services/RelationServiceTests.cs +++ b/src/Umbraco.Tests/Services/RelationServiceTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Threading; using NUnit.Framework; @@ -37,7 +38,7 @@ namespace Umbraco.Tests.Services ServiceContext.ContentService.Save(content); } - for (var i = 0; i < 6; i++) + 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(); @@ -83,7 +84,7 @@ namespace Umbraco.Tests.Services [Test] public void Relation_Returns_Parent_Child_Object_Types_When_Creating() { - var r = CreateNewRelation("Test", "test"); + var r = CreateAndSaveRelation("Test", "test"); Assert.AreEqual(Constants.ObjectTypes.Document, r.ParentObjectType); Assert.AreEqual(Constants.ObjectTypes.Media, r.ChildObjectType); @@ -92,7 +93,7 @@ namespace Umbraco.Tests.Services [Test] public void Relation_Returns_Parent_Child_Object_Types_When_Getting() { - var r = CreateNewRelation("Test", "test"); + var r = CreateAndSaveRelation("Test", "test"); // re-get r = ServiceContext.RelationService.GetById(r.Id); @@ -101,7 +102,47 @@ namespace Umbraco.Tests.Services Assert.AreEqual(Constants.ObjectTypes.Media, r.ChildObjectType); } - private IRelation CreateNewRelation(string name, string alias) + [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); @@ -124,6 +165,35 @@ namespace Umbraco.Tests.Services 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 } } From bf2727a1ebc4a1ab0aed5a2321f2ea501230ab6f Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Wed, 6 Nov 2019 13:40:00 +0000 Subject: [PATCH 160/548] New model to return from WebAPI --- .../Models/ContentEditing/MediaReferences.cs | 24 +++++++++++++++++++ src/Umbraco.Web/Umbraco.Web.csproj | 3 ++- 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 src/Umbraco.Web/Models/ContentEditing/MediaReferences.cs diff --git a/src/Umbraco.Web/Models/ContentEditing/MediaReferences.cs b/src/Umbraco.Web/Models/ContentEditing/MediaReferences.cs new file mode 100644 index 0000000000..b10022b105 --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/MediaReferences.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models.ContentEditing +{ + [DataContract(Name = "mediaReferences", Namespace = "")] + public class MediaReferences + { + [DataMember(Name = "content")] + public IEnumerable Content { get; set; } = Enumerable.Empty(); + + [DataMember(Name = "members")] + public IEnumerable Members { get; set; } = Enumerable.Empty(); + + [DataMember(Name = "media")] + public IEnumerable Media { get; set; } = Enumerable.Empty(); + + [DataContract(Name = "entityType", Namespace = "")] + public class EntityTypeReferences : EntityBasic + { + } + } +} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 60b70ed9a8..361a548123 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -222,6 +222,7 @@ + @@ -1283,4 +1284,4 @@ - + \ No newline at end of file From 1c7e2367f58cd2f8a32a594a48271218c897334a Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Wed, 6 Nov 2019 13:41:21 +0000 Subject: [PATCH 161/548] New WebAPI method to return references/relations for Media items --- src/Umbraco.Web/Editors/MediaController.cs | 67 ++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index a673f06e1d..36750d74ec 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,71 @@ namespace Umbraco.Web.Editors return hasPathAccess; } + + /// + /// Returns the references (usages) for the media item + /// + /// + /// + public MediaReferences GetReferences(int id) + { + var result = new MediaReferences(); + + var relations = Services.RelationService.GetByChildId(id, Constants.Conventions.RelationTypes.RelatedMediaAlias).ToList(); + var relationEntities = Services.RelationService.GetParentEntitiesFromRelations(relations).ToList(); + + var documents = new List(); + var members = new List(); + var media = new List(); + + foreach (var item in relationEntities) + { + switch (item) + { + case DocumentEntitySlim doc: + documents.Add(new MediaReferences.EntityTypeReferences { + Id = doc.Id, + Key = doc.Key, + Udi = Udi.Create(Constants.UdiEntityType.Document, doc.Key), + Icon = doc.ContentTypeIcon, + Name = doc.Name, + Alias = doc.ContentTypeAlias + }); + break; + + case MemberEntitySlim memb: + members.Add(new MediaReferences.EntityTypeReferences + { + Id = memb.Id, + Key = memb.Key, + Udi = Udi.Create(Constants.UdiEntityType.Member, memb.Key), + Icon = memb.ContentTypeIcon, + Name = memb.Name, + Alias = memb.ContentTypeAlias + }); + break; + + case MediaEntitySlim med: + media.Add(new MediaReferences.EntityTypeReferences + { + Id = med.Id, + Key = med.Key, + Udi = Udi.Create(Constants.UdiEntityType.Media, med.Key), + Icon = med.ContentTypeIcon, + Name = med.Name, + Alias = med.ContentTypeAlias + }); + break; + + default: + break; + } + } + + result.Content = documents; + result.Members = members; + result.Media = media; + return result; + } } } From 642247799a778c50d05e3399c06f192c28d8c55b Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Wed, 6 Nov 2019 13:42:00 +0000 Subject: [PATCH 162/548] Adds new AngularJS reference to call the new WebAPI endpoint --- .../src/common/resources/media.resource.js | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) 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..58c02b55df 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 @@ -552,8 +552,31 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { "Search", args)), 'Failed to retrieve media items for search: ' + query); - } + }, + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#getReferences + * @methodOf umbraco.resources.mediaResource + * + * @description + * Retrieves references of a given media item. + * + * @param {Int} id id of media node to retrieve references for + * @returns {Promise} resourcePromise object. + * + */ + getReferences: function (id) { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetReferences", + { id: id })), + "Failed to retrieve usages for media of id " + id); + + } }; } From 45392603bdf2d1e553f3a5eaca509dd005cde78b Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Wed, 6 Nov 2019 13:45:03 +0000 Subject: [PATCH 163/548] Adds in the new Media Relations tables to the directive view & JS to call the resource/WebAPI --- .../media/umbmedianodeinfo.directive.js | 26 +++- .../components/media/umb-media-node-info.html | 127 ++++++++++++++++-- 2 files changed, 139 insertions(+), 14 deletions(-) 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..18aa457862 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,15 @@ (function () { 'use strict'; - function MediaNodeInfoDirective($timeout, $location, eventsService, userService, dateHelper, editorService, mediaHelper) { + function MediaNodeInfoDirective($timeout, $location, eventsService, userService, dateHelper, editorService, mediaHelper, mediaResource, $routeParams) { function link(scope, element, attrs, ctrl) { var evts = []; + var referencesLoaded = false; scope.allowChangeMediaType = false; + scope.loading = true; function onInit() { @@ -94,6 +96,19 @@ setMediaExtension(); }); + /** Loads in the media references one time */ + function loadRelations() { + if (!referencesLoaded) { + referencesLoaded = true; + mediaResource.getReferences($routeParams.id) + .then(function (data) { + scope.loading = false; + scope.references = data; + scope.hasReferences = data.content.length > 0 || data.members.length > 0; + }); + } + } + //ensure to unregister from all events! scope.$on('$destroy', function () { for (var e in evts) { @@ -102,6 +117,15 @@ }); 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") { + loadRelations(); + } + }); + })); } var directive = { 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..a82e1897a1 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 +141,11 @@ - + From ab87bd200c68e09199b93ba6696a0605502abd52 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Wed, 6 Nov 2019 13:45:21 +0000 Subject: [PATCH 164/548] Adds in lang backoffice keys to EN & EN_US --- src/Umbraco.Web.UI/Umbraco/config/lang/en.xml | 3 +++ src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index 19c8642dc5..c95f059934 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -2181,6 +2181,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 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 0c572fba26..e4cd5765c1 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -2197,6 +2197,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 From ce969aad3b62ec12a257a4d9cd3566287e88a825 Mon Sep 17 00:00:00 2001 From: Jeffrey Schoemaker Date: Thu, 7 Nov 2019 13:34:26 +0100 Subject: [PATCH 165/548] Make it a bit less complex --- .github/BUILD.md | 2 ++ 1 file changed, 2 insertions(+) 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: From 787529cf0d3541e56048b8973c1bd9d2966101b6 Mon Sep 17 00:00:00 2001 From: Rasmus John Pedersen Date: Thu, 7 Nov 2019 19:41:51 +0100 Subject: [PATCH 166/548] Remove IDataEditor inheritance --- src/Umbraco.Core/PropertyEditors/IDataEditorWithMediaPath.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/PropertyEditors/IDataEditorWithMediaPath.cs b/src/Umbraco.Core/PropertyEditors/IDataEditorWithMediaPath.cs index aea7d28758..b16dbbaa25 100644 --- a/src/Umbraco.Core/PropertyEditors/IDataEditorWithMediaPath.cs +++ b/src/Umbraco.Core/PropertyEditors/IDataEditorWithMediaPath.cs @@ -1,6 +1,6 @@ namespace Umbraco.Core.PropertyEditors { - public interface IDataEditorWithMediaPath : IDataEditor + public interface IDataEditorWithMediaPath { string GetMediaPath(object value); } From 5db6ce2b8e7cc1a28c5951cc5f1371cbdd3dd6ff Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 8 Nov 2019 12:20:57 +1100 Subject: [PATCH 167/548] fixes some comments --- src/Umbraco.Web/Editors/MacrosController.cs | 1 - src/Umbraco.Web/Install/FilePermissionHelper.cs | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web/Editors/MacrosController.cs b/src/Umbraco.Web/Editors/MacrosController.cs index 3cb161e547..2e68925ce4 100644 --- a/src/Umbraco.Web/Editors/MacrosController.cs +++ b/src/Umbraco.Web/Editors/MacrosController.cs @@ -323,7 +323,6 @@ namespace Umbraco.Web.Editors /// Finds partial view files in app plugin folders. ///
/// - /// The . /// private IEnumerable FindPartialViewFilesInPluginFolders() { diff --git a/src/Umbraco.Web/Install/FilePermissionHelper.cs b/src/Umbraco.Web/Install/FilePermissionHelper.cs index ede9008514..af62b4fd9e 100644 --- a/src/Umbraco.Web/Install/FilePermissionHelper.cs +++ b/src/Umbraco.Web/Install/FilePermissionHelper.cs @@ -45,8 +45,8 @@ namespace Umbraco.Web.Install /// /// This will test the directories for write access /// - /// - /// + /// + /// /// /// If this is false, the easiest way to test for write access is to write a temp file, however some folder will cause /// an App Domain restart if a file is written to the folder, so in that case we need to use the ACL APIs which aren't as From eee33696e82e81a54ef35dbfea1ad2eb384380f3 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 11 Nov 2019 11:05:57 +0000 Subject: [PATCH 168/548] Adss in missing XML params from signature --- src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs index 102dc7c81a..be626b8d8b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs @@ -46,11 +46,13 @@ namespace Umbraco.Core.Persistence.Repositories /// Gets paged entities for a query and a subset of object types ///
/// + /// /// /// /// /// /// + /// /// /// A collection of mixed entity types which would be of type , , , /// From dd1c8ec6a3e699b66706b48821fc679bf92f8f63 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 11 Nov 2019 12:05:26 +0000 Subject: [PATCH 169/548] Revert a C#8 fancy new switch as AzureDevOps does not like that --- .../Repositories/Implement/EntityRepository.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs index 3ca28b6b44..a0d2baa907 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs @@ -516,11 +516,17 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // 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 - var orderBy = ordering.OrderBy.ToUpperInvariant() switch + string orderBy; + switch (ordering.OrderBy.ToUpperInvariant()) { - "PATH" => SqlSyntax.GetQuotedColumn(NodeDto.TableName, "path"), - _ => ordering.OrderBy - }; + case "PATH": + orderBy = SqlSyntax.GetQuotedColumn(NodeDto.TableName, "path"); + break; + + default: + orderBy = ordering.OrderBy; + break; + } if (ordering.Direction == Direction.Ascending) sql.OrderBy(orderBy); From 8dcec429d359e68554d5f653dc987b2ac14501a2 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 11 Nov 2019 15:16:19 +0000 Subject: [PATCH 170/548] Remove relations for the RelationType overview --- .../ContentEditing/RelationTypeDisplay.cs | 7 ------ .../Models/Mapping/RelationMapDefinition.cs | 22 ------------------- 2 files changed, 29 deletions(-) diff --git a/src/Umbraco.Web/Models/ContentEditing/RelationTypeDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/RelationTypeDisplay.cs index 8a92d085eb..b7f209cd20 100644 --- a/src/Umbraco.Web/Models/ContentEditing/RelationTypeDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/RelationTypeDisplay.cs @@ -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/Mapping/RelationMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/RelationMapDefinition.cs index 8407a7421c..2f111ffb5a 100644 --- a/src/Umbraco.Web/Models/Mapping/RelationMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/RelationMapDefinition.cs @@ -51,28 +51,6 @@ namespace Umbraco.Web.Models.Mapping var objType = ObjectTypes.GetUmbracoObjectType(source.ChildObjectType.Value); target.ChildObjectTypeName = objType.GetFriendlyName(); } - - // Load the relations - - var relations = _relationService.GetByRelationTypeId(source.Id); - var displayRelations = context.MapEnumerable(relations); - - // Load the entities - var entities = _relationService.GetEntitiesFromRelations(relations) - .ToDictionary(x => (x.Item1.Id, x.Item2.Id), x => x); - - foreach(var r in displayRelations) - { - var pair = entities[(r.ParentId, r.ChildId)]; - var parent = pair.Item1; - var child = pair.Item2; - - r.ChildName = child.Name; - r.ParentName = parent.Name; - } - - target.Relations = displayRelations; - } // Umbraco.Code.MapAll -ParentName -ChildName From bbcdcbdde0ea25f543d68fc9732212ad1c34af65 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Tue, 12 Nov 2019 14:09:36 +0000 Subject: [PATCH 171/548] Start adding in pagination to relations --- .../Implement/RelationRepository.cs | 2 +- .../common/resources/relationtype.resource.js | 43 ++++++++++++++++ .../views/relationtypes/edit.controller.js | 38 ++++++++++++-- .../views/relationtypes/views/relations.html | 51 +++++++++++-------- .../Editors/RelationTypeController.cs | 19 +++++++ 5 files changed, 129 insertions(+), 24 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs index 68575b2bab..9b04cd3d76 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs @@ -253,7 +253,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // apply ordering ApplyOrdering(ref sql, ordering); - var pageIndexToFetch = pageIndex + 1; + var pageIndexToFetch = pageIndex; var page = Database.Page(pageIndexToFetch, pageSize, sql); var dtos = page.Items; totalRecords = page.TotalItems; 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..5a7616dd1d 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,49 @@ function relationTypeResource($q, $http, umbRequestHelper, umbDataFormatter) { $http.post(umbRequestHelper.getApiUrl("relationTypeApiBaseUrl", "DeleteById", [{ id: id }])), "Failed to delete item " + id ); + }, + + getPagedResults: function (id, options) { + + console.log('options in', options); + + var defaults = { + pageSize: 100, + pageNumber: 1, + orderDirection: "Ascending", + orderBy: "SortOrder" + }; + 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; + //change asc/desct + if (options.orderDirection === "asc") { + options.orderDirection = "Ascending"; + } + else if (options.orderDirection === "desc") { + options.orderDirection = "Descending"; + } + + console.log('options before HTTP', options); + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "relationTypeApiBaseUrl", + "GetPagedResults", + { + id: id, + pageNumber: options.pageNumber, + pageSize: options.pageSize, + orderBy: options.orderBy, + orderDirection: options.orderDirection + } + )), + 'Failed to get paged relations for id ' + id); } }; 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 f83829dfba..d2bb3ef41d 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; @@ -15,12 +15,19 @@ function RelationTypeEditController($scope, $routeParams, relationTypeResource, vm.page.saveButtonState = "init"; vm.page.menu = {} + //var referencesLoaded = false; + vm.save = saveRelationType; init(); function init() { vm.page.loading = true; + vm.relationsLoading = true; + + vm.changePageNumber = changePageNumber; + vm.options = {}; + vm.options.pageSize = 3; var labelKeys = [ "relationType_tabRelationType", @@ -45,6 +52,13 @@ function RelationTypeEditController($scope, $routeParams, relationTypeResource, ]; }); + // load media type references when the 'info' tab is first activated/switched to + eventsService.on("app.tabChange", function (event, args) { + if (args.alias === "relations") { + loadRelations(); + } + }); + relationTypeResource.getById($routeParams.id) .then(function (data) { bindRelationType(data); @@ -52,9 +66,27 @@ function RelationTypeEditController($scope, $routeParams, relationTypeResource, }); } - function bindRelationType(relationType) { - formatDates(relationType.relations); + function changePageNumber(pageNumber) { + alert('pagenumber' + 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) { + console.log('paged data', 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(); 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/Editors/RelationTypeController.cs b/src/Umbraco.Web/Editors/RelationTypeController.cs index 2845a82aa1..14726b1074 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; @@ -50,6 +52,23 @@ namespace Umbraco.Web.Editors 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, 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. /// From 05c1f0f38903f364bf86fc2a9332b3012f705fce Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Tue, 12 Nov 2019 14:50:43 +0000 Subject: [PATCH 172/548] Fix up the missing nulls & ammend the mapping --- src/Umbraco.Web/Models/Mapping/RelationMapDefinition.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Umbraco.Web/Models/Mapping/RelationMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/RelationMapDefinition.cs index 2f111ffb5a..d26a867858 100644 --- a/src/Umbraco.Web/Models/Mapping/RelationMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/RelationMapDefinition.cs @@ -60,6 +60,11 @@ namespace Umbraco.Web.Models.Mapping 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 From 411bf067e5e2bf635f2a3345724272d82350f60b Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Tue, 12 Nov 2019 19:11:16 +0000 Subject: [PATCH 173/548] Bit of tidying up --- .../src/views/relationtypes/edit.controller.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) 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 d2bb3ef41d..65b581af3d 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 @@ -15,8 +15,6 @@ function RelationTypeEditController($scope, $routeParams, relationTypeResource, vm.page.saveButtonState = "init"; vm.page.menu = {} - //var referencesLoaded = false; - vm.save = saveRelationType; init(); @@ -52,13 +50,14 @@ function RelationTypeEditController($scope, $routeParams, relationTypeResource, ]; }); - // load media type references when the 'info' tab is first activated/switched to + // 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) { bindRelationType(data); @@ -67,7 +66,6 @@ function RelationTypeEditController($scope, $routeParams, relationTypeResource, } function changePageNumber(pageNumber) { - alert('pagenumber' + pageNumber); vm.options.pageNumber = pageNumber; loadRelations(); } @@ -77,9 +75,7 @@ function RelationTypeEditController($scope, $routeParams, relationTypeResource, function loadRelations() { relationTypeResource.getPagedResults($routeParams.id, vm.options) .then(function (data) { - console.log('paged data', data); formatDates(data.items); - vm.relationsLoading = false; vm.relations = data; }); From 9b87b65537d92ca954db116f9948561fd55ea411 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Wed, 13 Nov 2019 14:39:11 +0100 Subject: [PATCH 174/548] Create Examine query that only searches content --- src/Umbraco.Web/PublishedContentQuery.cs | 40 ++++++++++-------------- 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/src/Umbraco.Web/PublishedContentQuery.cs b/src/Umbraco.Web/PublishedContentQuery.cs index 72aec94e58..8f4242c180 100644 --- a/src/Umbraco.Web/PublishedContentQuery.cs +++ b/src/Umbraco.Web/PublishedContentQuery.cs @@ -201,39 +201,31 @@ namespace Umbraco.Web throw new InvalidOperationException($"No index found by name {indexName} or is not of type {typeof(IUmbracoIndex)}"); } - var searcher = umbIndex.GetSearcher(); + var query = umbIndex.GetSearcher().CreateQuery(IndexTypes.Content); - ISearchResults results; + IQueryExecutor queryExecutor; if (culture == "*") { // Search everything - results = skip == 0 && take == 0 - ? searcher.Search(term) - : searcher.Search(term, skip + take); + queryExecutor = query.ManagedQuery(term); + } + else if (string.IsNullOrWhiteSpace(culture)) + { + // Only search invariant + queryExecutor = query.Field(UmbracoContentIndex.VariesByCultureFieldName, "n") // Must not vary by culture + .And().ManagedQuery(term); } else { - IBooleanOperation query; - if (string.IsNullOrWhiteSpace(culture)) - { - // Only search invariant - query = searcher.CreateQuery() - .Field(UmbracoContentIndex.VariesByCultureFieldName, "n") // Must not vary by culture - .And().ManagedQuery(term); - } - else - { - // Only search the specified culture - var fields = umbIndex.GetCultureAndInvariantFields(culture).ToArray(); // Get all index fields suffixed with the culture name supplied - query = searcher.CreateQuery() - .ManagedQuery(term, fields); - } - - results = skip == 0 && take == 0 - ? query.Execute() - : query.Execute(skip + take); + // Only search the specified culture + var fields = umbIndex.GetCultureAndInvariantFields(culture).ToArray(); // Get all index fields suffixed with the culture name supplied + queryExecutor = query.ManagedQuery(term, fields); } + var results = skip == 0 && take == 0 + ? queryExecutor.Execute() + : queryExecutor.Execute(skip + take); + totalRecords = results.TotalItemCount; return new CultureContextualSearchResults(results.Skip(skip).ToPublishedSearchResults(_publishedSnapshot.Content), _variationContextAccessor, culture); From b64eb05e792abb9a3c44dda14f173541644deac1 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Wed, 13 Nov 2019 14:39:52 +0100 Subject: [PATCH 175/548] Update documentation of Search overloads --- src/Umbraco.Web/IPublishedContentQuery.cs | 48 +++++++++++++++-------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/src/Umbraco.Web/IPublishedContentQuery.cs b/src/Umbraco.Web/IPublishedContentQuery.cs index 8a8d678aba..351847da73 100644 --- a/src/Umbraco.Web/IPublishedContentQuery.cs +++ b/src/Umbraco.Web/IPublishedContentQuery.cs @@ -35,12 +35,15 @@ namespace Umbraco.Web /// /// Searches content. /// - /// Term to search. - /// Optional culture. - /// Optional index name. + /// The term to search. + /// The culture (defaults to a culture insensitive search). + /// The name of the index to search (defaults to ). + /// + /// The search results. + /// /// /// - /// When the is not specified or is *, all cultures are searched. + /// When the is not specified or is *, all cultures are searched. /// To search for only invariant documents and fields use null. /// When searching on a specific culture, all culture specific fields are searched for the provided culture and all invariant fields for all documents. /// @@ -51,15 +54,18 @@ namespace Umbraco.Web /// /// Searches content. /// - /// Term to search. - /// Numbers of items to skip. - /// Numbers of items to return. - /// Total number of matching items. - /// Optional culture. - /// Optional index name. + /// The term to search. + /// The amount of results to skip. + /// The amount of results to take/return. + /// The total amount of records. + /// The culture (defaults to a culture insensitive search). + /// The name of the index to search (defaults to ). + /// + /// The search results. + /// /// /// - /// When the is not specified or is *, all cultures are searched. + /// When the is not specified or is *, all cultures are searched. /// To search for only invariant documents and fields use null. /// When searching on a specific culture, all culture specific fields are searched for the provided culture and all invariant fields for all documents. /// @@ -68,18 +74,26 @@ namespace Umbraco.Web IEnumerable Search(string term, int skip, int take, out long totalRecords, string culture = "*", string indexName = null); /// - /// Executes the query and converts the results to PublishedSearchResult. + /// Executes the query and converts the results to using the . /// - /// - /// While enumerating results, the ambient culture is changed to be the searched culture. - /// + /// The query. + /// + /// The search results. + /// IEnumerable Search(IQueryExecutor query); /// - /// Executes the query and converts the results to PublishedSearchResult. + /// Executes the query and converts the results to using the . /// + /// The query. + /// The amount of results to skip. + /// The amount of results to take/return. + /// The total amount of records. + /// + /// The search results. + /// /// - /// While enumerating results, the ambient culture is changed to be the searched culture. + /// Make sure only content is searched (searcher.CreateQuery(IndexTypes.Content)), as this only returns content, but the could otherwise also include media records. /// IEnumerable Search(IQueryExecutor query, int skip, int take, out long totalRecords); } From 2314b4de5e25de35f0aef4c9fcb28b51281a87af Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Wed, 13 Nov 2019 14:35:13 +0000 Subject: [PATCH 176/548] Make fixes based on Shan's review notes --- .../Implement/RelationRepository.cs | 2 +- .../common/resources/relationtype.resource.js | 23 ++++--------------- .../views/relationtypes/edit.controller.js | 1 - .../Editors/RelationTypeController.cs | 2 +- 4 files changed, 6 insertions(+), 22 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs index 9b04cd3d76..68575b2bab 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs @@ -253,7 +253,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // apply ordering ApplyOrdering(ref sql, ordering); - var pageIndexToFetch = pageIndex; + var pageIndexToFetch = pageIndex + 1; var page = Database.Page(pageIndexToFetch, pageSize, sql); var dtos = page.Items; totalRecords = page.TotalItems; 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 5a7616dd1d..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 @@ -118,13 +118,9 @@ function relationTypeResource($q, $http, umbRequestHelper, umbDataFormatter) { getPagedResults: function (id, options) { - console.log('options in', options); - var defaults = { - pageSize: 100, - pageNumber: 1, - orderDirection: "Ascending", - orderBy: "SortOrder" + pageSize: 25, + pageNumber: 1 }; if (options === undefined) { options = {}; @@ -133,16 +129,7 @@ function relationTypeResource($q, $http, umbRequestHelper, umbDataFormatter) { angular.extend(defaults, options); //now copy back to the options we will use options = defaults; - //change asc/desct - if (options.orderDirection === "asc") { - options.orderDirection = "Ascending"; - } - else if (options.orderDirection === "desc") { - options.orderDirection = "Descending"; - } - - console.log('options before HTTP', options); - + return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( @@ -151,9 +138,7 @@ function relationTypeResource($q, $http, umbRequestHelper, umbDataFormatter) { { id: id, pageNumber: options.pageNumber, - pageSize: options.pageSize, - orderBy: options.orderBy, - orderDirection: options.orderDirection + pageSize: options.pageSize } )), 'Failed to get paged relations for id ' + id); 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 65b581af3d..44fbf6ffe9 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 @@ -25,7 +25,6 @@ function RelationTypeEditController($scope, $routeParams, relationTypeResource, vm.changePageNumber = changePageNumber; vm.options = {}; - vm.options.pageSize = 3; var labelKeys = [ "relationType_tabRelationType", diff --git a/src/Umbraco.Web/Editors/RelationTypeController.cs b/src/Umbraco.Web/Editors/RelationTypeController.cs index 14726b1074..f12faf77cc 100644 --- a/src/Umbraco.Web/Editors/RelationTypeController.cs +++ b/src/Umbraco.Web/Editors/RelationTypeController.cs @@ -61,7 +61,7 @@ namespace Umbraco.Web.Editors } // Ordering do we need to pass through? - var relations = Services.RelationService.GetPagedByRelationTypeId(id, pageNumber, pageSize, out long totalRecords); + var relations = Services.RelationService.GetPagedByRelationTypeId(id, pageNumber -1, pageSize, out long totalRecords); return new PagedResult(totalRecords, pageNumber, pageSize) { From 634f64851753f612b938475238aa687a0dc3c749 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Thu, 14 Nov 2019 09:20:18 +0100 Subject: [PATCH 177/548] Require Examine 1.0.2 --- build/NuSpecs/UmbracoCms.Web.nuspec | 2 +- src/Umbraco.Examine/Umbraco.Examine.csproj | 4 ++-- src/Umbraco.Tests/Umbraco.Tests.csproj | 2 +- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 4 ++-- src/Umbraco.Web/Umbraco.Web.csproj | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/build/NuSpecs/UmbracoCms.Web.nuspec b/build/NuSpecs/UmbracoCms.Web.nuspec index c8374bc2f7..72b4df2ecb 100644 --- a/build/NuSpecs/UmbracoCms.Web.nuspec +++ b/build/NuSpecs/UmbracoCms.Web.nuspec @@ -28,7 +28,7 @@ - + diff --git a/src/Umbraco.Examine/Umbraco.Examine.csproj b/src/Umbraco.Examine/Umbraco.Examine.csproj index db623ecddd..0e0ee62139 100644 --- a/src/Umbraco.Examine/Umbraco.Examine.csproj +++ b/src/Umbraco.Examine/Umbraco.Examine.csproj @@ -49,7 +49,7 @@ - + 1.0.0-beta2-19324-01 runtime; build; native; contentfiles; analyzers; buildtransitive @@ -112,4 +112,4 @@ - + \ No newline at end of file diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 39826fcc38..47542484d8 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -79,7 +79,7 @@ - + 1.8.14 diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 6e766f578e..70039488ed 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -87,7 +87,7 @@ - + @@ -431,4 +431,4 @@ - + \ No newline at end of file diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 56feaf52e7..9947a5e1de 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -63,7 +63,7 @@ - + 2.7.0.100 @@ -1279,4 +1279,4 @@ - + \ No newline at end of file From c206382fa105ebe4140fc724c5c987a4bf5bba22 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Thu, 14 Nov 2019 09:23:18 +0100 Subject: [PATCH 178/548] Use snapshot to convert search results into content, media or members --- src/Umbraco.Web/ExamineExtensions.cs | 83 ++++++++++++++++++++--- src/Umbraco.Web/IPublishedContentQuery.cs | 7 +- src/Umbraco.Web/PublishedContentQuery.cs | 2 +- 3 files changed, 76 insertions(+), 16 deletions(-) diff --git a/src/Umbraco.Web/ExamineExtensions.cs b/src/Umbraco.Web/ExamineExtensions.cs index 9a9fa98d95..8fead91194 100644 --- a/src/Umbraco.Web/ExamineExtensions.cs +++ b/src/Umbraco.Web/ExamineExtensions.cs @@ -2,32 +2,95 @@ using System.Collections.Generic; using System.Linq; using Examine; -using Umbraco.Core; +using Examine.LuceneEngine.Providers; using Umbraco.Core.Models.PublishedContent; +using Umbraco.Examine; using Umbraco.Web.PublishedCache; namespace Umbraco.Web { /// - /// Extension methods for Examine + /// Extension methods for Examine. /// public static class ExamineExtensions { + /// + /// Creates an containing all content from the . + /// + /// The search results. + /// The cache to fetch the content from. + /// + /// An containing all content. + /// + /// cache + /// + /// Search results are skipped if it can't be fetched from the by its integer id. + /// public static IEnumerable ToPublishedSearchResults(this IEnumerable results, IPublishedCache cache) { - var list = new List(); + if (cache == null) throw new ArgumentNullException(nameof(cache)); + + var publishedSearchResults = new List(); foreach (var result in results.OrderByDescending(x => x.Score)) { - if (!int.TryParse(result.Id, out var intId)) continue; //invalid - var content = cache.GetById(intId); - if (content == null) continue; // skip if this doesn't exist in the cache - - list.Add(new PublishedSearchResult(content, result.Score)); - + if (int.TryParse(result.Id, out var contentId) && + cache.GetById(contentId) is IPublishedContent content) + { + publishedSearchResults.Add(new PublishedSearchResult(content, result.Score)); + } } - return list; + return publishedSearchResults; + } + + /// + /// Creates an containing all content, media or members from the . + /// + /// The search results. + /// The snapshot. + /// + /// An containing all content, media or members. + /// + /// snapshot + /// + /// Search results are skipped if it can't be fetched from the respective cache by its integer id. + /// + public static IEnumerable ToPublishedSearchResults(this IEnumerable results, IPublishedSnapshot snapshot) + { + if (snapshot == null) throw new ArgumentNullException(nameof(snapshot)); + + var publishedSearchResults = new List(); + + foreach (var result in results.OrderByDescending(x => x.Score)) + { + if (int.TryParse(result.Id, out var contentId) && + result.Values.TryGetValue(LuceneIndex.CategoryFieldName, out var indexType)) + { + IPublishedContent content; + switch (indexType) + { + case IndexTypes.Content: + content = snapshot.Content.GetById(contentId); + break; + case IndexTypes.Media: + content = snapshot.Media.GetById(contentId); + break; + case IndexTypes.Member: + content = snapshot.Members.GetById(contentId); + break; + default: + continue; + } + + if (content != null) + { + publishedSearchResults.Add(new PublishedSearchResult(content, result.Score)); + } + } + } + + return publishedSearchResults; } } } diff --git a/src/Umbraco.Web/IPublishedContentQuery.cs b/src/Umbraco.Web/IPublishedContentQuery.cs index 351847da73..c4d829968f 100644 --- a/src/Umbraco.Web/IPublishedContentQuery.cs +++ b/src/Umbraco.Web/IPublishedContentQuery.cs @@ -74,7 +74,7 @@ namespace Umbraco.Web IEnumerable Search(string term, int skip, int take, out long totalRecords, string culture = "*", string indexName = null); /// - /// Executes the query and converts the results to using the . + /// Executes the query and converts the results to . /// /// The query. /// @@ -83,7 +83,7 @@ namespace Umbraco.Web IEnumerable Search(IQueryExecutor query); /// - /// Executes the query and converts the results to using the . + /// Executes the query and converts the results to . /// /// The query. /// The amount of results to skip. @@ -92,9 +92,6 @@ namespace Umbraco.Web /// /// The search results. /// - /// - /// Make sure only content is searched (searcher.CreateQuery(IndexTypes.Content)), as this only returns content, but the could otherwise also include media records. - /// IEnumerable Search(IQueryExecutor query, int skip, int take, out long totalRecords); } } diff --git a/src/Umbraco.Web/PublishedContentQuery.cs b/src/Umbraco.Web/PublishedContentQuery.cs index 8f4242c180..833df9971b 100644 --- a/src/Umbraco.Web/PublishedContentQuery.cs +++ b/src/Umbraco.Web/PublishedContentQuery.cs @@ -246,7 +246,7 @@ namespace Umbraco.Web totalRecords = results.TotalItemCount; - return results.Skip(skip).ToPublishedSearchResults(_publishedSnapshot.Content); + return results.Skip(skip).ToPublishedSearchResults(_publishedSnapshot); } /// From ca91bf0f94332555495281d78012d08618e2396c Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 15 Nov 2019 14:14:09 +1100 Subject: [PATCH 179/548] Fixes issue with getting paged entity relations when the relation is between the same entity and itself --- .../Repositories/IEntityRepository.cs | 9 ++-- .../Implement/EntityRepository.cs | 14 +------ .../Implement/RelationRepository.cs | 42 ++++++++++++------- .../Repositories/RelationRepositoryTest.cs | 28 +++++++++++++ 4 files changed, 62 insertions(+), 31 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs index be626b8d8b..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; @@ -52,14 +53,16 @@ namespace Umbraco.Core.Persistence.Repositories /// /// /// - /// + /// + /// 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, IQuery relationQuery = null); + IQuery filter, Ordering ordering, Action> sqlCustomization = null); /// /// Gets paged entities for a query and a specific object type diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs index a0d2baa907..0eafebbfde 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs @@ -44,7 +44,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // get a page of entities public IEnumerable GetPagedResultsByQuery(IQuery query, Guid[] objectTypes, long pageIndex, int pageSize, out long totalRecords, - IQuery filter, Ordering ordering, IQuery relationQuery = null) + 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); @@ -52,17 +52,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var sql = GetBaseWhere(isContent, isMedia, isMember, false, s => { - if (relationQuery != null) - { - // 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). - s.LeftJoin().On((left, right) => left.NodeId == right.ChildId || left.NodeId == right.ParentId); - s.LeftJoin().On((left, right) => left.RelationType == right.Id); - - // append the relations wheres - foreach (var relClause in relationQuery.GetWhereClauses()) - s.Where(relClause.Item1, relClause.Item2); - } + sqlCustomization?.Invoke(s); if (filter != null) { diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs index 68575b2bab..b03928996a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs @@ -160,40 +160,50 @@ namespace Umbraco.Core.Persistence.Repositories.Implement #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) { - // Create a query to match the child id - var relQuery = Query().Where(r => r.ChildId == childId); - - // Because of the way that the entity repository joins relations (on both child or parent) we need to add - // a clause to filter out the child entity from being returned from the results - var entityQuery = Query().Where(e => e.Id != childId); - // 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(entityQuery, Array.Empty(), pageIndex, pageSize, out totalRecords, null, null, relQuery); + return _entityRepository.GetPagedResultsByQuery(Query(), Array.Empty(), pageIndex, pageSize, out totalRecords, null, null, sql => + { + SqlJoinRelations(sql); + + sql.Where(r => r.ChildId == childId); + sql.Where((rel, node) => rel.ParentId == childId || node.NodeId != childId); + }); } public IEnumerable GetPagedChildEntitiesByParentId(int parentId, long pageIndex, int pageSize, out long totalRecords) { - // Create a query to match the parent id - var relQuery = Query().Where(r => r.ParentId == parentId); - - // Because of the way that the entity repository joins relations (on both child or parent) we need to add - // a clause to filter out the child entity from being returned from the results - var entityQuery = Query().Where(e => e.Id != parentId); - // 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(entityQuery, Array.Empty(), pageIndex, pageSize, out totalRecords, null, null, relQuery); + return _entityRepository.GetPagedResultsByQuery(Query(), Array.Empty(), pageIndex, pageSize, out totalRecords, null, null, sql => + { + SqlJoinRelations(sql); + + sql.Where(r => r.ParentId == parentId); + sql.Where((rel, node) => rel.ChildId == parentId || node.NodeId != parentId); + }); } public void Save(IEnumerable relations) diff --git a/src/Umbraco.Tests/Persistence/Repositories/RelationRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/RelationRepositoryTest.cs index 4c8a7c1b16..9622760792 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/RelationRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/RelationRepositoryTest.cs @@ -201,6 +201,34 @@ namespace Umbraco.Tests.Persistence.Repositories } } + [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() { From 85c22696451ad20655bb76828b05f8f1b80e288d Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 15 Nov 2019 14:49:46 +1100 Subject: [PATCH 180/548] Adds ability to filter out paged relations by entity type --- .../Repositories/IRelationRepository.cs | 7 ++++--- .../Implement/RelationRepository.cs | 12 +++++------ src/Umbraco.Core/Services/IRelationService.cs | 4 ++-- .../Services/Implement/RelationService.cs | 8 ++++---- .../Repositories/RelationRepositoryTest.cs | 20 +++++++++++++++++++ 5 files changed, 36 insertions(+), 15 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/IRelationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IRelationRepository.cs index 6c69e16edf..fc1be20e6f 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IRelationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IRelationRepository.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence.Querying; @@ -25,8 +26,8 @@ namespace Umbraco.Core.Persistence.Repositories /// void DeleteByParent(int parentId, params string[] relationTypeAliases); - IEnumerable GetPagedParentEntitiesByChildId(int childId, long pageIndex, int pageSize, out long totalRecords); + 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); + IEnumerable GetPagedChildEntitiesByParentId(int parentId, long pageIndex, int pageSize, out long totalRecords, params Guid[] entityTypes); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs index b03928996a..cff5e48854 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs @@ -172,7 +172,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement sql.LeftJoin().On((left, right) => left.RelationType == right.Id); } - public IEnumerable GetPagedParentEntitiesByChildId(int childId, long pageIndex, int pageSize, out long totalRecords) + 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 @@ -180,16 +180,16 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // 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(), Array.Empty(), pageIndex, pageSize, out totalRecords, null, null, sql => + return _entityRepository.GetPagedResultsByQuery(Query(), entityTypes, pageIndex, pageSize, out totalRecords, null, null, sql => { SqlJoinRelations(sql); - sql.Where(r => r.ChildId == childId); + 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) + 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 @@ -197,11 +197,11 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // 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(), Array.Empty(), pageIndex, pageSize, out totalRecords, null, null, sql => + return _entityRepository.GetPagedResultsByQuery(Query(), entityTypes, pageIndex, pageSize, out totalRecords, null, null, sql => { SqlJoinRelations(sql); - sql.Where(r => r.ParentId == parentId); + sql.Where(rel => rel.ParentId == parentId); sql.Where((rel, node) => rel.ChildId == parentId || node.NodeId != parentId); }); } diff --git a/src/Umbraco.Core/Services/IRelationService.cs b/src/Umbraco.Core/Services/IRelationService.cs index 4d94ddb48c..bf8bcd5b2a 100644 --- a/src/Umbraco.Core/Services/IRelationService.cs +++ b/src/Umbraco.Core/Services/IRelationService.cs @@ -207,7 +207,7 @@ namespace Umbraco.Core.Services /// /// /// - IEnumerable GetPagedParentEntitiesByChildId(int id, long pageIndex, int pageSize, out long totalChildren); + IEnumerable GetPagedParentEntitiesByChildId(int id, long pageIndex, int pageSize, out long totalChildren, params UmbracoObjectTypes[] entityTypes); /// /// Returns paged child entities for a related parent id @@ -217,7 +217,7 @@ namespace Umbraco.Core.Services /// /// /// - IEnumerable GetPagedChildEntitiesByParentId(int id, long pageIndex, int pageSize, out long totalChildren); + 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. diff --git a/src/Umbraco.Core/Services/Implement/RelationService.cs b/src/Umbraco.Core/Services/Implement/RelationService.cs index 11572f09ff..4b53709de9 100644 --- a/src/Umbraco.Core/Services/Implement/RelationService.cs +++ b/src/Umbraco.Core/Services/Implement/RelationService.cs @@ -274,20 +274,20 @@ namespace Umbraco.Core.Services.Implement } /// - public IEnumerable GetPagedParentEntitiesByChildId(int id, long pageIndex, int pageSize, out long totalChildren) + 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); + 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) + 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); + return _relationRepository.GetPagedChildEntitiesByParentId(id, pageIndex, pageSize, out totalChildren, entityTypes.Select(x => x.GetGuid()).ToArray()); } } diff --git a/src/Umbraco.Tests/Persistence/Repositories/RelationRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/RelationRepositoryTest.cs index 9622760792..364e4e2b3f 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/RelationRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/RelationRepositoryTest.cs @@ -198,6 +198,16 @@ namespace Umbraco.Tests.Persistence.Repositories 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); } } @@ -256,6 +266,16 @@ namespace Umbraco.Tests.Persistence.Repositories 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); } } From 8773d644aade2a8b488a10eccd081843e6eacaf6 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Fri, 15 Nov 2019 12:50:44 +0000 Subject: [PATCH 181/548] Implement paging for the three entity types that media can be related to --- .../media/umbmedianodeinfo.directive.js | 72 ++++++++-- .../src/common/resources/media.resource.js | 90 ++++++++++--- .../components/media/umb-media-node-info.html | 44 +++++-- src/Umbraco.Web/Editors/MediaController.cs | 123 +++++++++--------- .../Models/ContentEditing/MediaReferences.cs | 21 +-- 5 files changed, 234 insertions(+), 116 deletions(-) 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 18aa457862..6f1dde18a0 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,16 +1,31 @@ (function () { 'use strict'; - function MediaNodeInfoDirective($timeout, $location, eventsService, userService, dateHelper, editorService, mediaHelper, mediaResource, $routeParams) { + function MediaNodeInfoDirective($timeout, $location, eventsService, userService, dateHelper, editorService, mediaHelper, mediaResource, $routeParams, $q) { function link(scope, element, attrs, ctrl) { var evts = []; - var referencesLoaded = false; scope.allowChangeMediaType = false; scope.loading = true; + scope.changeContentPageNumber = changeContentPageNumber; + scope.contentOptions = {}; + scope.contentOptions.pageSize = 1; + scope.hasContentReferences = false; + + scope.changeMediaPageNumber = changeMediaPageNumber; + scope.mediaOptions = {}; + scope.mediaOptions.pageSize = 1; + scope.hasMediaReferences = false; + + scope.changeMemberPageNumber = changeMemberPageNumber; + scope.memberOptions = {}; + scope.memberOptions.pageSize = 1; + scope.hasMemberReferences = false; + + function onInit() { userService.getCurrentUser().then(function(user){ @@ -96,17 +111,43 @@ setMediaExtension(); }); - /** Loads in the media references one time */ - function loadRelations() { - if (!referencesLoaded) { - referencesLoaded = true; - mediaResource.getReferences($routeParams.id) - .then(function (data) { - scope.loading = false; - scope.references = data; - scope.hasReferences = data.content.length > 0 || data.members.length > 0; - }); - } + 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.getPagedContentReferences($routeParams.id, scope.contentOptions) + .then(function (data) { + scope.contentReferences = data; + scope.hasContentReferences = data.items.length > 0; + }); + } + + function loadMediaRelations() { + return mediaResource.getPagedMediaReferences($routeParams.id, scope.mediaOptions) + .then(function (data) { + scope.mediaReferences = data; + scope.hasMediaReferences = data.items.length > 0; + }); + } + + function loadMemberRelations() { + return mediaResource.getPagedMemberReferences($routeParams.id, scope.memberOptions) + .then(function (data) { + scope.memberReferences = data; + scope.hasMemberReferences = data.items.length > 0; + }); } //ensure to unregister from all events! @@ -122,7 +163,10 @@ evts.push(eventsService.on("app.tabChange", function (event, args) { $timeout(function () { if (args.alias === "umbInfo") { - loadRelations(); + + $q.all([loadContentRelations(), loadMediaRelations(), loadMemberRelations()]).then(function () { + scope.loading = false; + }); } }); })); 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 58c02b55df..cb8c883bb9 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 @@ -554,28 +554,88 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { 'Failed to retrieve media items for search: ' + query); }, - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#getReferences - * @methodOf umbraco.resources.mediaResource - * - * @description - * Retrieves references of a given media item. - * - * @param {Int} id id of media node to retrieve references for - * @returns {Promise} resourcePromise object. - * - */ - getReferences: function (id) { + getPagedContentReferences: 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( "mediaApiBaseUrl", - "GetReferences", - { id: id })), + "GetPagedContentReferences", + { + id: id, + pageNumber: options.pageNumber, + pageSize: options.pageSize + } + )), "Failed to retrieve usages for media of id " + id); + }, + getPagedMemberReferences: 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( + "mediaApiBaseUrl", + "GetPagedMemberReferences", + { + id: id, + pageNumber: options.pageNumber, + pageSize: options.pageSize + } + )), + "Failed to retrieve usages for media of id " + id); + }, + + getPagedMediaReferences: 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( + "mediaApiBaseUrl", + "GetPagedMediaReferences", + { + id: id, + pageNumber: options.pageNumber, + pageSize: options.pageSize + } + )), + "Failed to retrieve usages for media of id " + id); } }; } 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 a82e1897a1..21a28a905f 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 @@ -29,7 +29,7 @@ - + @@ -39,10 +39,9 @@ -
- +
-
+
Used in Documents @@ -58,7 +57,7 @@
-
+
{{::reference.name}}
{{::reference.alias}}
@@ -66,10 +65,19 @@
+ + +
+ + +
-
+
Used in Members @@ -85,7 +93,7 @@
-
+
{{::reference.name}}
{{::reference.alias}}
@@ -93,10 +101,19 @@
+ + +
+ + +
-
+
Used in Media @@ -112,7 +129,7 @@
-
+
{{::reference.name}}
{{::reference.alias}}
@@ -120,6 +137,15 @@
+ + +
+ + +
diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index 36750d74ec..22a546ad7d 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -945,70 +945,73 @@ namespace Umbraco.Web.Editors return hasPathAccess; } - /// - /// Returns the references (usages) for the media item - /// - /// - /// - public MediaReferences GetReferences(int id) + public PagedResult GetPagedContentReferences(int id, int pageNumber = 1, int pageSize = 100) { - var result = new MediaReferences(); - - var relations = Services.RelationService.GetByChildId(id, Constants.Conventions.RelationTypes.RelatedMediaAlias).ToList(); - var relationEntities = Services.RelationService.GetParentEntitiesFromRelations(relations).ToList(); - - var documents = new List(); - var members = new List(); - var media = new List(); - - foreach (var item in relationEntities) + if (pageNumber <= 0 || pageSize <= 0) { - switch (item) - { - case DocumentEntitySlim doc: - documents.Add(new MediaReferences.EntityTypeReferences { - Id = doc.Id, - Key = doc.Key, - Udi = Udi.Create(Constants.UdiEntityType.Document, doc.Key), - Icon = doc.ContentTypeIcon, - Name = doc.Name, - Alias = doc.ContentTypeAlias - }); - break; - - case MemberEntitySlim memb: - members.Add(new MediaReferences.EntityTypeReferences - { - Id = memb.Id, - Key = memb.Key, - Udi = Udi.Create(Constants.UdiEntityType.Member, memb.Key), - Icon = memb.ContentTypeIcon, - Name = memb.Name, - Alias = memb.ContentTypeAlias - }); - break; - - case MediaEntitySlim med: - media.Add(new MediaReferences.EntityTypeReferences - { - Id = med.Id, - Key = med.Key, - Udi = Udi.Create(Constants.UdiEntityType.Media, med.Key), - Icon = med.ContentTypeIcon, - Name = med.Name, - Alias = med.ContentTypeAlias - }); - break; - - default: - break; - } + throw new NotSupportedException("Both pageNumber and pageSize must be greater than zero"); } - result.Content = documents; - result.Members = members; - result.Media = media; - return result; + var relations = Services.RelationService.GetPagedParentEntitiesByChildId(id, pageNumber - 1, pageSize, out long totalRecords, UmbracoObjectTypes.Document); + + return new PagedResult(totalRecords, pageNumber, pageSize) + { + Items = relations.Cast().Select(doc => new EntityTypeReferences + { + Id = doc.Id, + Key = doc.Key, + Udi = Udi.Create(Constants.UdiEntityType.Document, doc.Key), + Icon = doc.ContentTypeIcon, + Name = doc.Name, + Alias = doc.ContentTypeAlias + }) + }; + } + + public PagedResult GetPagedMemberReferences(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"); + } + + var relations = Services.RelationService.GetPagedParentEntitiesByChildId(id, pageNumber - 1, pageSize, out long totalRecords, UmbracoObjectTypes.Member); + + return new PagedResult(totalRecords, pageNumber, pageSize) + { + Items = relations.Cast().Select(memb => new EntityTypeReferences + { + Id = memb.Id, + Key = memb.Key, + Udi = Udi.Create(Constants.UdiEntityType.Member, memb.Key), + Icon = memb.ContentTypeIcon, + Name = memb.Name, + Alias = memb.ContentTypeAlias + }) + }; + } + + public PagedResult GetPagedMediaReferences(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"); + } + + var relations = Services.RelationService.GetPagedParentEntitiesByChildId(id, pageNumber - 1, pageSize, out long totalRecords, UmbracoObjectTypes.Media); + + return new PagedResult(totalRecords, pageNumber, pageSize) + { + Items = relations.Cast().Select(med => new EntityTypeReferences + { + Id = med.Id, + Key = med.Key, + Udi = Udi.Create(Constants.UdiEntityType.Media, med.Key), + Icon = med.ContentTypeIcon, + Name = med.Name, + Alias = med.ContentTypeAlias + }) + }; } } } diff --git a/src/Umbraco.Web/Models/ContentEditing/MediaReferences.cs b/src/Umbraco.Web/Models/ContentEditing/MediaReferences.cs index b10022b105..a1fbdfa1e1 100644 --- a/src/Umbraco.Web/Models/ContentEditing/MediaReferences.cs +++ b/src/Umbraco.Web/Models/ContentEditing/MediaReferences.cs @@ -1,24 +1,9 @@ -using System.Collections.Generic; -using System.Linq; -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Web.Models.ContentEditing { - [DataContract(Name = "mediaReferences", Namespace = "")] - public class MediaReferences + [DataContract(Name = "entityType", Namespace = "")] + public class EntityTypeReferences : EntityBasic { - [DataMember(Name = "content")] - public IEnumerable Content { get; set; } = Enumerable.Empty(); - - [DataMember(Name = "members")] - public IEnumerable Members { get; set; } = Enumerable.Empty(); - - [DataMember(Name = "media")] - public IEnumerable Media { get; set; } = Enumerable.Empty(); - - [DataContract(Name = "entityType", Namespace = "")] - public class EntityTypeReferences : EntityBasic - { - } } } From 675b6e7b7c8c009d5b75adcabdd19e020d8eccb9 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Fri, 15 Nov 2019 13:53:56 +0000 Subject: [PATCH 182/548] Remove test page size of 1 to check if each pager was working fine --- .../directives/components/media/umbmedianodeinfo.directive.js | 3 --- 1 file changed, 3 deletions(-) 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 6f1dde18a0..d21a31e2a8 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 @@ -12,17 +12,14 @@ scope.changeContentPageNumber = changeContentPageNumber; scope.contentOptions = {}; - scope.contentOptions.pageSize = 1; scope.hasContentReferences = false; scope.changeMediaPageNumber = changeMediaPageNumber; scope.mediaOptions = {}; - scope.mediaOptions.pageSize = 1; scope.hasMediaReferences = false; scope.changeMemberPageNumber = changeMemberPageNumber; scope.memberOptions = {}; - scope.memberOptions.pageSize = 1; scope.hasMemberReferences = false; From 31b85a2cd6605b4478fe50a93406369b8381f045 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 18 Nov 2019 11:43:31 +0000 Subject: [PATCH 183/548] Remove code duplication & do some cleanup - need to work on passing the type variable to Cast --- .../media/umbmedianodeinfo.directive.js | 9 +- .../src/common/resources/media.resource.js | 65 ++------------ src/Umbraco.Web/Editors/MediaController.cs | 88 ++++++++----------- .../Models/ContentEditing/MediaReferences.cs | 9 -- src/Umbraco.Web/Umbraco.Web.csproj | 1 - 5 files changed, 49 insertions(+), 123 deletions(-) delete mode 100644 src/Umbraco.Web/Models/ContentEditing/MediaReferences.cs 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 d21a31e2a8..a4c0e29fe4 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 @@ -12,14 +12,17 @@ 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; @@ -124,7 +127,7 @@ } function loadContentRelations() { - return mediaResource.getPagedContentReferences($routeParams.id, scope.contentOptions) + return mediaResource.getPagedReferences($routeParams.id, scope.contentOptions) .then(function (data) { scope.contentReferences = data; scope.hasContentReferences = data.items.length > 0; @@ -132,7 +135,7 @@ } function loadMediaRelations() { - return mediaResource.getPagedMediaReferences($routeParams.id, scope.mediaOptions) + return mediaResource.getPagedReferences($routeParams.id, scope.mediaOptions) .then(function (data) { scope.mediaReferences = data; scope.hasMediaReferences = data.items.length > 0; @@ -140,7 +143,7 @@ } function loadMemberRelations() { - return mediaResource.getPagedMemberReferences($routeParams.id, scope.memberOptions) + return mediaResource.getPagedReferences($routeParams.id, scope.memberOptions) .then(function (data) { scope.memberReferences = data; scope.hasMemberReferences = data.items.length > 0; 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 cb8c883bb9..e4e3cc6f3f 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 @@ -554,11 +554,12 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { 'Failed to retrieve media items for search: ' + query); }, - getPagedContentReferences: function (id, options) { + getPagedReferences: function (id, options) { var defaults = { pageSize: 25, - pageNumber: 1 + pageNumber: 1, + entityType: "DOCUMENT" }; if (options === undefined) { options = {}; @@ -572,71 +573,17 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { $http.get( umbRequestHelper.getApiUrl( "mediaApiBaseUrl", - "GetPagedContentReferences", - { - id: id, - pageNumber: options.pageNumber, - pageSize: options.pageSize - } - )), - "Failed to retrieve usages for media of id " + id); - }, - - getPagedMemberReferences: 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( - "mediaApiBaseUrl", - "GetPagedMemberReferences", - { - id: id, - pageNumber: options.pageNumber, - pageSize: options.pageSize - } - )), - "Failed to retrieve usages for media of id " + id); - }, - - getPagedMediaReferences: 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( - "mediaApiBaseUrl", - "GetPagedMediaReferences", + "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/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index 22a546ad7d..2d64ae252a 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -945,71 +945,57 @@ namespace Umbraco.Web.Editors return hasPathAccess; } - public PagedResult GetPagedContentReferences(int id, int pageNumber = 1, int pageSize = 100) + 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 relations = Services.RelationService.GetPagedParentEntitiesByChildId(id, pageNumber - 1, pageSize, out long totalRecords, UmbracoObjectTypes.Document); + UmbracoObjectTypes entity; + string udiEntity; + Type castType; - return new PagedResult(totalRecords, pageNumber, pageSize) + switch (entityType.ToUpperInvariant()) { - Items = relations.Cast().Select(doc => new EntityTypeReferences - { - Id = doc.Id, - Key = doc.Key, - Udi = Udi.Create(Constants.UdiEntityType.Document, doc.Key), - Icon = doc.ContentTypeIcon, - Name = doc.Name, - Alias = doc.ContentTypeAlias - }) - }; - } + case "DOCUMENT": + entity = UmbracoObjectTypes.Document; + udiEntity = Constants.UdiEntityType.Document; + castType = typeof(DocumentEntitySlim); + break; - public PagedResult GetPagedMemberReferences(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"); + case "MEDIA": + entity = UmbracoObjectTypes.Media; + udiEntity = Constants.UdiEntityType.Media; + castType = typeof(MediaEntitySlim); + break; + + case "MEMBER": + entity = UmbracoObjectTypes.Member; + udiEntity = Constants.UdiEntityType.Member; + castType = typeof(MemberEntitySlim); + break; + + default: + entity = UmbracoObjectTypes.Document; + udiEntity = Constants.UdiEntityType.Document; + castType = typeof(DocumentEntitySlim); + break; } - var relations = Services.RelationService.GetPagedParentEntitiesByChildId(id, pageNumber - 1, pageSize, out long totalRecords, UmbracoObjectTypes.Member); + var relations = Services.RelationService.GetPagedParentEntitiesByChildId(id, pageNumber - 1, pageSize, out long totalRecords, entity); - return new PagedResult(totalRecords, pageNumber, pageSize) + + return new PagedResult(totalRecords, pageNumber, pageSize) { - Items = relations.Cast().Select(memb => new EntityTypeReferences + Items = relations.Cast().Select(rel => new EntityBasic { - Id = memb.Id, - Key = memb.Key, - Udi = Udi.Create(Constants.UdiEntityType.Member, memb.Key), - Icon = memb.ContentTypeIcon, - Name = memb.Name, - Alias = memb.ContentTypeAlias - }) - }; - } - - public PagedResult GetPagedMediaReferences(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"); - } - - var relations = Services.RelationService.GetPagedParentEntitiesByChildId(id, pageNumber - 1, pageSize, out long totalRecords, UmbracoObjectTypes.Media); - - return new PagedResult(totalRecords, pageNumber, pageSize) - { - Items = relations.Cast().Select(med => new EntityTypeReferences - { - Id = med.Id, - Key = med.Key, - Udi = Udi.Create(Constants.UdiEntityType.Media, med.Key), - Icon = med.ContentTypeIcon, - Name = med.Name, - Alias = med.ContentTypeAlias + Id = rel.Id, + Key = rel.Key, + Udi = Udi.Create(udiEntity, rel.Key), + Icon = rel.ContentTypeIcon, + Name = rel.Name, + Alias = rel.ContentTypeAlias }) }; } diff --git a/src/Umbraco.Web/Models/ContentEditing/MediaReferences.cs b/src/Umbraco.Web/Models/ContentEditing/MediaReferences.cs deleted file mode 100644 index a1fbdfa1e1..0000000000 --- a/src/Umbraco.Web/Models/ContentEditing/MediaReferences.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Runtime.Serialization; - -namespace Umbraco.Web.Models.ContentEditing -{ - [DataContract(Name = "entityType", Namespace = "")] - public class EntityTypeReferences : EntityBasic - { - } -} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 361a548123..5eca5ad7fe 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -222,7 +222,6 @@ - From dc494ff52587d3e29530fde75ac2b01ad58ae3da Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 18 Nov 2019 12:18:50 +0000 Subject: [PATCH 184/548] MemberEntitySlim has the same properties as ContentEntitySlim so lets inherit it --- src/Umbraco.Core/Models/Entities/MemberEntitySlim.cs | 11 ++--------- src/Umbraco.Web/Editors/MediaController.cs | 9 ++------- 2 files changed, 4 insertions(+), 16 deletions(-) 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.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index 2d64ae252a..751e386edb 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -954,32 +954,27 @@ namespace Umbraco.Web.Editors UmbracoObjectTypes entity; string udiEntity; - Type castType; switch (entityType.ToUpperInvariant()) { - case "DOCUMENT": + case "DOCUMENT": entity = UmbracoObjectTypes.Document; udiEntity = Constants.UdiEntityType.Document; - castType = typeof(DocumentEntitySlim); break; case "MEDIA": entity = UmbracoObjectTypes.Media; udiEntity = Constants.UdiEntityType.Media; - castType = typeof(MediaEntitySlim); break; case "MEMBER": entity = UmbracoObjectTypes.Member; udiEntity = Constants.UdiEntityType.Member; - castType = typeof(MemberEntitySlim); break; default: entity = UmbracoObjectTypes.Document; udiEntity = Constants.UdiEntityType.Document; - castType = typeof(DocumentEntitySlim); break; } @@ -988,7 +983,7 @@ namespace Umbraco.Web.Editors return new PagedResult(totalRecords, pageNumber, pageSize) { - Items = relations.Cast().Select(rel => new EntityBasic + Items = relations.Cast().Select(rel => new EntityBasic { Id = rel.Id, Key = rel.Key, From 895f68d9e2c6283bce0bd1dd3ef838f577467f2b Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 20 Nov 2019 12:15:27 +1100 Subject: [PATCH 185/548] Fixes bulk insert records, adjusts parsing of object types --- src/Umbraco.Core/Models/ObjectTypes.cs | 2 +- .../NPocoDatabaseExtensions-Bulk.cs | 18 +++++++++-- .../Implement/RelationRepository.cs | 2 ++ .../Persistence/UmbracoDatabase.cs | 4 +++ src/Umbraco.Web/Editors/MediaController.cs | 32 +++---------------- 5 files changed, 27 insertions(+), 31 deletions(-) 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/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/Repositories/Implement/RelationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs index cff5e48854..56a6336f75 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs @@ -236,6 +236,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement }, 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 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.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index 751e386edb..e9b879c48b 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -952,34 +952,10 @@ namespace Umbraco.Web.Editors throw new NotSupportedException("Both pageNumber and pageSize must be greater than zero"); } - UmbracoObjectTypes entity; - string udiEntity; - - switch (entityType.ToUpperInvariant()) - { - case "DOCUMENT": - entity = UmbracoObjectTypes.Document; - udiEntity = Constants.UdiEntityType.Document; - break; - - case "MEDIA": - entity = UmbracoObjectTypes.Media; - udiEntity = Constants.UdiEntityType.Media; - break; - - case "MEMBER": - entity = UmbracoObjectTypes.Member; - udiEntity = Constants.UdiEntityType.Member; - break; - - default: - entity = UmbracoObjectTypes.Document; - udiEntity = Constants.UdiEntityType.Document; - break; - } - - var relations = Services.RelationService.GetPagedParentEntitiesByChildId(id, pageNumber - 1, pageSize, out long totalRecords, entity); + 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) { @@ -987,7 +963,7 @@ namespace Umbraco.Web.Editors { Id = rel.Id, Key = rel.Key, - Udi = Udi.Create(udiEntity, rel.Key), + Udi = Udi.Create(udiType, rel.Key), Icon = rel.ContentTypeIcon, Name = rel.Name, Alias = rel.ContentTypeAlias From beebdce0a2f1b1eceade48d283369ed30def73c9 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 20 Nov 2019 12:18:14 +1100 Subject: [PATCH 186/548] Fixes $routeParams issue --- .../components/media/umbmedianodeinfo.directive.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 a4c0e29fe4..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,7 +1,7 @@ (function () { 'use strict'; - function MediaNodeInfoDirective($timeout, $location, eventsService, userService, dateHelper, editorService, mediaHelper, mediaResource, $routeParams, $q) { + function MediaNodeInfoDirective($timeout, $location, eventsService, userService, dateHelper, editorService, mediaHelper, mediaResource, $q) { function link(scope, element, attrs, ctrl) { @@ -127,7 +127,7 @@ } function loadContentRelations() { - return mediaResource.getPagedReferences($routeParams.id, scope.contentOptions) + return mediaResource.getPagedReferences(scope.node.id, scope.contentOptions) .then(function (data) { scope.contentReferences = data; scope.hasContentReferences = data.items.length > 0; @@ -135,7 +135,7 @@ } function loadMediaRelations() { - return mediaResource.getPagedReferences($routeParams.id, scope.mediaOptions) + return mediaResource.getPagedReferences(scope.node.id, scope.mediaOptions) .then(function (data) { scope.mediaReferences = data; scope.hasMediaReferences = data.items.length > 0; @@ -143,7 +143,7 @@ } function loadMemberRelations() { - return mediaResource.getPagedReferences($routeParams.id, scope.memberOptions) + return mediaResource.getPagedReferences(scope.node.id, scope.memberOptions) .then(function (data) { scope.memberReferences = data; scope.hasMemberReferences = data.items.length > 0; From b13120f0a11c25968d554f3064e485f8d651070e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Fri, 22 Nov 2019 11:32:11 +0100 Subject: [PATCH 187/548] minor style adjustment for media references --- .../src/views/components/media/umb-media-node-info.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 21a28a905f..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 @@ -6,7 +6,7 @@
- + @@ -43,7 +43,7 @@
-
+
Used in Documents
@@ -79,7 +79,7 @@
-
+
Used in Members
@@ -115,7 +115,7 @@
-
+
Used in Media
From 7ed6c0c1a2bd75c10e7d90e858ae7ac426f1ff66 Mon Sep 17 00:00:00 2001 From: Steve Megson Date: Sun, 24 Nov 2019 13:44:09 +0000 Subject: [PATCH 188/548] Thread safety in EnsureApplicationUrl --- src/Umbraco.Core/RuntimeState.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/RuntimeState.cs b/src/Umbraco.Core/RuntimeState.cs index 5d34fe70a1..65c93ece05 100644 --- a/src/Umbraco.Core/RuntimeState.cs +++ b/src/Umbraco.Core/RuntimeState.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading; using System.Web; @@ -22,7 +23,7 @@ namespace Umbraco.Core private readonly ILogger _logger; private readonly IUmbracoSettingsSection _settings; private readonly IGlobalSettings _globalSettings; - private readonly HashSet _applicationUrls = new HashSet(); + private readonly ConcurrentDictionary _applicationUrls = new ConcurrentDictionary(); private readonly Lazy _mainDom; private readonly Lazy _serverRegistrar; @@ -106,11 +107,11 @@ namespace Umbraco.Core // (this is a simplified version of what was in 7.x) // note: should this be optional? is it expensive? var url = request == null ? null : ApplicationUrlHelper.GetApplicationUrlFromCurrentRequest(request, _globalSettings); - var change = url != null && !_applicationUrls.Contains(url); + var change = url != null && !_applicationUrls.ContainsKey(url); if (change) { _logger.Info(typeof(ApplicationUrlHelper), "New url {Url} detected, re-discovering application url.", url); - _applicationUrls.Add(url); + _applicationUrls[url] = 0; } if (ApplicationUrl != null && !change) return; From 051b105feac2c704a62275551c1370d85ee92267 Mon Sep 17 00:00:00 2001 From: Steve Megson Date: Mon, 25 Nov 2019 09:01:14 +0000 Subject: [PATCH 189/548] Removed breaking change --- src/Umbraco.Web/Editors/MacroRenderingController.cs | 2 +- src/Umbraco.Web/IUmbracoComponentRenderer.cs | 10 ---------- src/Umbraco.Web/UmbracoComponentRenderer.cs | 2 +- src/Umbraco.Web/UmbracoHelper.cs | 6 +++--- 4 files changed, 5 insertions(+), 15 deletions(-) diff --git a/src/Umbraco.Web/Editors/MacroRenderingController.cs b/src/Umbraco.Web/Editors/MacroRenderingController.cs index f8feae25eb..7aabff6822 100644 --- a/src/Umbraco.Web/Editors/MacroRenderingController.cs +++ b/src/Umbraco.Web/Editors/MacroRenderingController.cs @@ -142,7 +142,7 @@ namespace Umbraco.Web.Editors //need to create a specific content result formatted as HTML since this controller has been configured //with only json formatters. result.Content = new StringContent( - _componentRenderer.RenderMacro(publishedContent, m.Alias, macroParams).ToString(), + _componentRenderer.RenderMacro(pageId, m.Alias, macroParams).ToString(), Encoding.UTF8, "text/html"); diff --git a/src/Umbraco.Web/IUmbracoComponentRenderer.cs b/src/Umbraco.Web/IUmbracoComponentRenderer.cs index dfe607fd4a..4dc9036e6b 100644 --- a/src/Umbraco.Web/IUmbracoComponentRenderer.cs +++ b/src/Umbraco.Web/IUmbracoComponentRenderer.cs @@ -42,15 +42,5 @@ namespace Umbraco.Web /// The parameters. /// IHtmlString RenderMacro(int contentId, string alias, IDictionary parameters); - - /// - /// Renders the macro with the specified alias, passing in the specified parameters. - /// - /// - /// The alias. - /// The parameters. - /// - IHtmlString RenderMacro(IPublishedContent content, string alias, IDictionary parameters); - } } diff --git a/src/Umbraco.Web/UmbracoComponentRenderer.cs b/src/Umbraco.Web/UmbracoComponentRenderer.cs index 19e4223e2b..9f56813364 100644 --- a/src/Umbraco.Web/UmbracoComponentRenderer.cs +++ b/src/Umbraco.Web/UmbracoComponentRenderer.cs @@ -107,7 +107,7 @@ namespace Umbraco.Web /// The parameters. /// The content used for macro rendering /// - public IHtmlString RenderMacro(IPublishedContent content, string alias, IDictionary parameters) + private IHtmlString RenderMacro(IPublishedContent content, string alias, IDictionary parameters) { if (content == null) throw new ArgumentNullException(nameof(content)); diff --git a/src/Umbraco.Web/UmbracoHelper.cs b/src/Umbraco.Web/UmbracoHelper.cs index 01d64cdd3e..e76c23a4c1 100644 --- a/src/Umbraco.Web/UmbracoHelper.cs +++ b/src/Umbraco.Web/UmbracoHelper.cs @@ -146,7 +146,7 @@ namespace Umbraco.Web /// public IHtmlString RenderMacro(string alias) { - return ComponentRenderer.RenderMacro(AssignedContentItem, alias, new Dictionary()); + return ComponentRenderer.RenderMacro(AssignedContentItem?.Id ?? 0, alias, new Dictionary()); } /// @@ -157,7 +157,7 @@ namespace Umbraco.Web /// public IHtmlString RenderMacro(string alias, object parameters) { - return ComponentRenderer.RenderMacro(AssignedContentItem, alias, parameters.ToDictionary()); + return ComponentRenderer.RenderMacro(AssignedContentItem?.Id ?? 0, alias, parameters.ToDictionary()); } /// @@ -168,7 +168,7 @@ namespace Umbraco.Web /// public IHtmlString RenderMacro(string alias, IDictionary parameters) { - return ComponentRenderer.RenderMacro(AssignedContentItem, alias, parameters); + return ComponentRenderer.RenderMacro(AssignedContentItem?.Id ?? 0, alias, parameters); } #endregion From 311e2afde08131b592ea8dbe5c15514ba969ce47 Mon Sep 17 00:00:00 2001 From: Steve Megson Date: Tue, 26 Nov 2019 08:51:48 +0000 Subject: [PATCH 190/548] Use ConcurrentHashSet --- .../Collections/ConcurrentHashSet.cs | 38 +++++++++++++++---- src/Umbraco.Core/RuntimeState.cs | 9 ++--- 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/src/Umbraco.Core/Collections/ConcurrentHashSet.cs b/src/Umbraco.Core/Collections/ConcurrentHashSet.cs index 54367ed588..c4dba51acd 100644 --- a/src/Umbraco.Core/Collections/ConcurrentHashSet.cs +++ b/src/Umbraco.Core/Collections/ConcurrentHashSet.cs @@ -70,7 +70,23 @@ namespace Umbraco.Core.Collections /// The number of elements contained in the . /// /// 2 - public int Count => GetThreadSafeClone().Count; + public int Count + { + get + { + try + { + _instanceLocker.EnterReadLock(); + return _innerSet.Count; + } + finally + { + if (_instanceLocker.IsReadLockHeld) + _instanceLocker.ExitReadLock(); + } + + } + } /// /// Gets a value indicating whether the is read-only. @@ -105,8 +121,7 @@ namespace Umbraco.Core.Collections /// public bool TryAdd(T item) { - var clone = GetThreadSafeClone(); - if (clone.Contains(item)) return false; + if (Contains(item)) return false; try { _instanceLocker.EnterWriteLock(); @@ -150,7 +165,16 @@ namespace Umbraco.Core.Collections /// The object to locate in the . public bool Contains(T item) { - return GetThreadSafeClone().Contains(item); + try + { + _instanceLocker.EnterReadLock(); + return _innerSet.Contains(item); + } + finally + { + if (_instanceLocker.IsReadLockHeld) + _instanceLocker.ExitReadLock(); + } } /// @@ -168,13 +192,13 @@ namespace Umbraco.Core.Collections HashSet clone = null; try { - _instanceLocker.EnterWriteLock(); + _instanceLocker.EnterReadLock(); clone = new HashSet(_innerSet, _innerSet.Comparer); } finally { - if (_instanceLocker.IsWriteLockHeld) - _instanceLocker.ExitWriteLock(); + if (_instanceLocker.IsReadLockHeld) + _instanceLocker.ExitReadLock(); } return clone; } diff --git a/src/Umbraco.Core/RuntimeState.cs b/src/Umbraco.Core/RuntimeState.cs index 65c93ece05..74ec70828b 100644 --- a/src/Umbraco.Core/RuntimeState.cs +++ b/src/Umbraco.Core/RuntimeState.cs @@ -1,9 +1,8 @@ using System; -using System.Collections.Concurrent; -using System.Collections.Generic; using System.Threading; using System.Web; using Semver; +using Umbraco.Core.Collections; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Exceptions; @@ -23,7 +22,7 @@ namespace Umbraco.Core private readonly ILogger _logger; private readonly IUmbracoSettingsSection _settings; private readonly IGlobalSettings _globalSettings; - private readonly ConcurrentDictionary _applicationUrls = new ConcurrentDictionary(); + private readonly ConcurrentHashSet _applicationUrls = new ConcurrentHashSet(); private readonly Lazy _mainDom; private readonly Lazy _serverRegistrar; @@ -107,11 +106,11 @@ namespace Umbraco.Core // (this is a simplified version of what was in 7.x) // note: should this be optional? is it expensive? var url = request == null ? null : ApplicationUrlHelper.GetApplicationUrlFromCurrentRequest(request, _globalSettings); - var change = url != null && !_applicationUrls.ContainsKey(url); + var change = url != null && !_applicationUrls.Contains(url); if (change) { _logger.Info(typeof(ApplicationUrlHelper), "New url {Url} detected, re-discovering application url.", url); - _applicationUrls[url] = 0; + _applicationUrls.Add(url); } if (ApplicationUrl != null && !change) return; From 2420b9c25362dc1bf55314edbe6a864c34f1bb64 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Tue, 26 Nov 2019 10:28:56 +0000 Subject: [PATCH 191/548] Update CoreComposer to use the easier to read extension method on composition as opposed to declaring WithCollectionBuilder --- src/Umbraco.Core/Runtime/CoreInitialComposer.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Core/Runtime/CoreInitialComposer.cs b/src/Umbraco.Core/Runtime/CoreInitialComposer.cs index 1f004846d0..86e61aeb90 100644 --- a/src/Umbraco.Core/Runtime/CoreInitialComposer.cs +++ b/src/Umbraco.Core/Runtime/CoreInitialComposer.cs @@ -43,7 +43,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,7 +70,7 @@ 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(); @@ -101,13 +101,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 +115,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)); From 3ead51ff64c13e8ff066181fcd0d830e8bde1048 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Tue, 26 Nov 2019 11:05:30 +0000 Subject: [PATCH 192/548] Update WebInitComposer to use the easier to read extension method on composition as opposed to declaring WithCollectionBuilder --- src/Umbraco.Web/Runtime/WebInitialComposer.cs | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Umbraco.Web/Runtime/WebInitialComposer.cs b/src/Umbraco.Web/Runtime/WebInitialComposer.cs index 4de5e8627a..27d0b5a1ce 100644 --- a/src/Umbraco.Web/Runtime/WebInitialComposer.cs +++ b/src/Umbraco.Web/Runtime/WebInitialComposer.cs @@ -150,19 +150,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 @@ -177,26 +177,26 @@ 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.WithCollectionBuilder() + composition.ContentFinders() // all built-in finders in the correct order, // devs can then modify this list on application startup .Append() @@ -211,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() @@ -231,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() @@ -248,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() From 7573d528074871e980341b171fcb61b42b02c54a Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Tue, 26 Nov 2019 11:09:33 +0000 Subject: [PATCH 193/548] Initial plumbing, extension methods etc for collection to store IDataValueReferences --- src/Umbraco.Core/Composing/Current.cs | 3 +++ src/Umbraco.Core/CompositionExtensions.cs | 7 +++++++ .../PropertyEditors/DataValueReferenceCollection.cs | 12 ++++++++++++ .../DataValueReferenceCollectionBuilder.cs | 9 +++++++++ src/Umbraco.Core/Runtime/CoreInitialComposer.cs | 4 ++++ src/Umbraco.Core/Umbraco.Core.csproj | 2 ++ src/Umbraco.Web/Composing/Current.cs | 2 ++ 7 files changed, 39 insertions(+) create mode 100644 src/Umbraco.Core/PropertyEditors/DataValueReferenceCollection.cs create mode 100644 src/Umbraco.Core/PropertyEditors/DataValueReferenceCollectionBuilder.cs diff --git a/src/Umbraco.Core/Composing/Current.cs b/src/Umbraco.Core/Composing/Current.cs index f12bf0dd63..1bd56ed727 100644 --- a/src/Umbraco.Core/Composing/Current.cs +++ b/src/Umbraco.Core/Composing/Current.cs @@ -154,6 +154,9 @@ namespace Umbraco.Core.Composing public static DataEditorCollection DataEditors => Factory.GetInstance(); + public static DataValueReferenceCollection DataValueReferences + => Factory.GetInstance(); + public static PropertyEditorCollection PropertyEditors => Factory.GetInstance(); diff --git a/src/Umbraco.Core/CompositionExtensions.cs b/src/Umbraco.Core/CompositionExtensions.cs index 5dd33c2a60..d29f251edc 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 collection builder. + /// + /// The composition. + public static DataValueReferenceCollectionBuilder DataValueReferences(this Composition composition) + => composition.WithCollectionBuilder(); + /// /// Gets the property value converters collection builder. /// diff --git a/src/Umbraco.Core/PropertyEditors/DataValueReferenceCollection.cs b/src/Umbraco.Core/PropertyEditors/DataValueReferenceCollection.cs new file mode 100644 index 0000000000..6d0e3e62df --- /dev/null +++ b/src/Umbraco.Core/PropertyEditors/DataValueReferenceCollection.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using Umbraco.Core.Composing; + +namespace Umbraco.Core.PropertyEditors +{ + public class DataValueReferenceCollection : BuilderCollectionBase + { + public DataValueReferenceCollection(IEnumerable items) + : base(items) + { } + } +} diff --git a/src/Umbraco.Core/PropertyEditors/DataValueReferenceCollectionBuilder.cs b/src/Umbraco.Core/PropertyEditors/DataValueReferenceCollectionBuilder.cs new file mode 100644 index 0000000000..0f8ee1bd8e --- /dev/null +++ b/src/Umbraco.Core/PropertyEditors/DataValueReferenceCollectionBuilder.cs @@ -0,0 +1,9 @@ +using Umbraco.Core.Composing; + +namespace Umbraco.Core.PropertyEditors +{ + public class DataValueReferenceCollectionBuilder : LazyCollectionBuilderBase + { + protected override DataValueReferenceCollectionBuilder This => this; + } +} diff --git a/src/Umbraco.Core/Runtime/CoreInitialComposer.cs b/src/Umbraco.Core/Runtime/CoreInitialComposer.cs index 86e61aeb90..db308d720c 100644 --- a/src/Umbraco.Core/Runtime/CoreInitialComposer.cs +++ b/src/Umbraco.Core/Runtime/CoreInitialComposer.cs @@ -75,6 +75,10 @@ namespace Umbraco.Core.Runtime composition.RegisterUnique(); composition.RegisterUnique(); + // TODO: WB Add our collection + // Manually register stuff in this collection + composition.DataValueReferences(); + // register a server registrar, by default it's the db registrar composition.RegisterUnique(f => { diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 225317943b..c6545a4f8b 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -281,6 +281,8 @@ + + diff --git a/src/Umbraco.Web/Composing/Current.cs b/src/Umbraco.Web/Composing/Current.cs index 5be5e45ecd..1363d60b8a 100644 --- a/src/Umbraco.Web/Composing/Current.cs +++ b/src/Umbraco.Web/Composing/Current.cs @@ -182,6 +182,8 @@ namespace Umbraco.Web.Composing public static DataEditorCollection DataEditors => CoreCurrent.DataEditors; + public static DataValueReferenceCollection DataValueReferences => CoreCurrent.DataValueReferences; + public static PropertyEditorCollection PropertyEditors => CoreCurrent.PropertyEditors; public static ParameterEditorCollection ParameterEditors => CoreCurrent.ParameterEditors; From 0680f104b5d0158a00665848ddf791b80ff009c5 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 26 Nov 2019 12:39:12 +0100 Subject: [PATCH 194/548] Added "rc" to AssemblyInformationalVersion --- src/SolutionInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index 5614cb8c41..7fa0b5a49d 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -19,4 +19,4 @@ using System.Resources; // these are FYI and changed automatically [assembly: AssemblyFileVersion("8.4.0")] -[assembly: AssemblyInformationalVersion("8.4.0")] +[assembly: AssemblyInformationalVersion("8.4.0-rc")] From 67946b4af31800f626842dfe53b5161a4d1ce1a9 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 27 Nov 2019 10:47:20 +1100 Subject: [PATCH 195/548] don't allocate empty dictionary when rendering macro and deal with null vals --- src/Umbraco.Web/Macros/MacroRenderer.cs | 2 +- src/Umbraco.Web/UmbracoComponentRenderer.cs | 4 ++-- src/Umbraco.Web/UmbracoHelper.cs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web/Macros/MacroRenderer.cs b/src/Umbraco.Web/Macros/MacroRenderer.cs index 3368def084..8d7d3bc013 100755 --- a/src/Umbraco.Web/Macros/MacroRenderer.cs +++ b/src/Umbraco.Web/Macros/MacroRenderer.cs @@ -186,7 +186,7 @@ namespace Umbraco.Web.Macros foreach (var prop in model.Properties) { var key = prop.Key.ToLowerInvariant(); - prop.Value = macroParams.ContainsKey(key) + prop.Value = macroParams != null && macroParams.ContainsKey(key) ? macroParams[key]?.ToString() ?? string.Empty : string.Empty; } diff --git a/src/Umbraco.Web/UmbracoComponentRenderer.cs b/src/Umbraco.Web/UmbracoComponentRenderer.cs index 9f56813364..a5890c9f97 100644 --- a/src/Umbraco.Web/UmbracoComponentRenderer.cs +++ b/src/Umbraco.Web/UmbracoComponentRenderer.cs @@ -77,7 +77,7 @@ namespace Umbraco.Web /// public IHtmlString RenderMacro(int contentId, string alias, object parameters) { - return RenderMacro(contentId, alias, parameters.ToDictionary()); + return RenderMacro(contentId, alias, parameters?.ToDictionary()); } /// @@ -113,7 +113,7 @@ namespace Umbraco.Web // TODO: We are doing at ToLower here because for some insane reason the UpdateMacroModel method looks for a lower case match. the whole macro concept needs to be rewritten. //NOTE: the value could have HTML encoded values, so we need to deal with that - var macroProps = parameters.ToDictionary( + var macroProps = parameters?.ToDictionary( x => x.Key.ToLowerInvariant(), i => (i.Value is string) ? HttpUtility.HtmlDecode(i.Value.ToString()) : i.Value); diff --git a/src/Umbraco.Web/UmbracoHelper.cs b/src/Umbraco.Web/UmbracoHelper.cs index e76c23a4c1..1bc8df5b24 100644 --- a/src/Umbraco.Web/UmbracoHelper.cs +++ b/src/Umbraco.Web/UmbracoHelper.cs @@ -146,7 +146,7 @@ namespace Umbraco.Web /// public IHtmlString RenderMacro(string alias) { - return ComponentRenderer.RenderMacro(AssignedContentItem?.Id ?? 0, alias, new Dictionary()); + return ComponentRenderer.RenderMacro(AssignedContentItem?.Id ?? 0, alias, null); } /// @@ -157,7 +157,7 @@ namespace Umbraco.Web /// public IHtmlString RenderMacro(string alias, object parameters) { - return ComponentRenderer.RenderMacro(AssignedContentItem?.Id ?? 0, alias, parameters.ToDictionary()); + return ComponentRenderer.RenderMacro(AssignedContentItem?.Id ?? 0, alias, parameters?.ToDictionary()); } /// From bd2c3bdcb3aedd474c2232f8509f9515158c3852 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 27 Nov 2019 14:28:53 +1100 Subject: [PATCH 196/548] Creates test to assert the problem --- .../PublishedContent/NuCacheChildrenTests.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs b/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs index 5d32606ee7..cc9ffdba3c 100644 --- a/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs +++ b/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs @@ -918,6 +918,14 @@ namespace Umbraco.Tests.PublishedContent 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 +942,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,6 +959,8 @@ namespace Umbraco.Tests.PublishedContent documents = snapshot.Content.GetById(2).Children().ToArray(); AssertDocuments(documents, "N9", "N8", "N7"); + + } [Test] From 880d65bb42128b7eb3ef15870cf544766f30bd09 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Wed, 27 Nov 2019 12:32:16 +0100 Subject: [PATCH 197/548] V8: Add indicators to required properties within nested content (#7217) --- .../src/less/components/umb-nested-content.less | 11 +++++++++++ .../nestedcontent/nestedcontent.controller.js | 1 + .../nestedcontent/nestedcontent.editor.html | 4 ++-- 3 files changed, 14 insertions(+), 2 deletions(-) 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 455a147395..9d8e886b7a 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 @@ -14,6 +14,17 @@ pointer-events: none; } +.umb-nested-content--mandatory { + /* + yeah so this is a pain, but we must be super specific in targeting the mandatory property labels, + otherwise all properties within a reqired, nested, nested content property will all appear mandatory + */ + > ng-form > .control-group > .umb-el-wrap > .control-header label:after { + content: '*'; + color: @red; + } +} + .umb-nested-content-overlay { position: absolute; top: 0; 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 37298fad3c..afdab78588 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 @@ -503,6 +503,7 @@ // Force validation to occur server side as this is the // only way we can have consistency between mandatory and // regex validation messages. Not ideal, but it works. + prop.ncMandatory = prop.validation.mandatory; prop.validation = { mandatory: false, pattern: "" diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.editor.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.editor.html index db0e9a016e..a2105c5675 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.editor.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.editor.html @@ -1,7 +1,7 @@ 
- + @@ -10,4 +10,4 @@

{{property.notSupportedMessage}}

-
\ No newline at end of file + From c20416a37492afb3d6a811f1080fcf895e2f0a46 Mon Sep 17 00:00:00 2001 From: Jan Skovgaard Date: Wed, 27 Nov 2019 13:38:02 +0100 Subject: [PATCH 198/548] Nested Content: Fix controls to be more accessible (#6998) * Change a to button * Added localization for copy * Correcting lost parts from merge. --- .../src/less/components/umb-nested-content.less | 2 ++ .../nestedcontent/nestedcontent.controller.js | 4 +++- .../nestedcontent.propertyeditor.html | 16 ++++++++++------ 3 files changed, 15 insertions(+), 7 deletions(-) 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 9d8e886b7a..b28925bec1 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 @@ -145,6 +145,8 @@ .umb-nested-content__icon { + background: transparent; + border: 0 none; display: inline-block; padding: 4px; margin: 2px; 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 afdab78588..c771b5645b 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 @@ -47,9 +47,11 @@ vm.hasContentTypes = model.config.contentTypes.length > 0; var labels = {}; - localizationService.localizeMany(["grid_addElement", "content_createEmpty"]).then(function (data) { + vm.labels = labels; + localizationService.localizeMany(["grid_addElement", "content_createEmpty", "actions_copy"]).then(function (data) { labels.grid_addElement = data[0]; labels.content_createEmpty = data[1]; + labels.copy_icon_title = data[2] }); function setCurrentNode(node) { 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 283f4406f5..d24d3796f3 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 @@ -13,12 +13,16 @@
- - - - - - + +
From 9ae05b5b152b1c85ebb4527cece7e43d7013ee71 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Wed, 27 Nov 2019 13:40:25 +0100 Subject: [PATCH 199/548] =?UTF-8?q?V8:=20Update=20the=20descriptions=20of?= =?UTF-8?q?=20the=20Nested=20Content=20configuration=E2=80=A6=20(#7224)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update the descriptions of the Nested Content configuration options * Reduced the amount of words for further readability --- .../PropertyEditors/NestedContentConfiguration.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web/PropertyEditors/NestedContentConfiguration.cs b/src/Umbraco.Web/PropertyEditors/NestedContentConfiguration.cs index b904a2250d..0f53207462 100644 --- a/src/Umbraco.Web/PropertyEditors/NestedContentConfiguration.cs +++ b/src/Umbraco.Web/PropertyEditors/NestedContentConfiguration.cs @@ -8,22 +8,22 @@ namespace Umbraco.Web.PropertyEditors ///
public class NestedContentConfiguration { - [ConfigurationField("contentTypes", "Document types", "views/propertyeditors/nestedcontent/nestedcontent.doctypepicker.html", Description = "Select the document types to use as the item blueprints. Only \"element\" types can be used.")] + [ConfigurationField("contentTypes", "Element Types", "views/propertyeditors/nestedcontent/nestedcontent.doctypepicker.html", Description = "Select the Element Types to use as models for the items.")] public ContentType[] ContentTypes { get; set; } - [ConfigurationField("minItems", "Min Items", "number", Description = "Set the minimum number of items allowed.")] + [ConfigurationField("minItems", "Min Items", "number", Description = "Minimum number of items allowed.")] public int? MinItems { get; set; } - [ConfigurationField("maxItems", "Max Items", "number", Description = "Set the maximum number of items allowed.")] + [ConfigurationField("maxItems", "Max Items", "number", Description = "Maximum number of items allowed.")] public int? MaxItems { get; set; } - [ConfigurationField("confirmDeletes", "Confirm Deletes", "boolean", Description = "Set whether item deletions should require confirming.")] + [ConfigurationField("confirmDeletes", "Confirm Deletes", "boolean", Description = "Requires editor confirmation for delete actions.")] public bool ConfirmDeletes { get; set; } = true; - [ConfigurationField("showIcons", "Show Icons", "boolean", Description = "Set whether to show the items doc type icon in the list.")] + [ConfigurationField("showIcons", "Show Icons", "boolean", Description = "Show the Element Type icons.")] public bool ShowIcons { get; set; } = true; - [ConfigurationField("hideLabel", "Hide Label", "boolean", Description = "Set whether to hide the editor label and have the list take up the full width of the editor window.")] + [ConfigurationField("hideLabel", "Hide Label", "boolean", Description = "Hide the property label and let the item list span the full width of the editor window.")] public bool HideLabel { get; set; } public class ContentType From 645a30a8aa0027fd49b85698bd26f4e1ee47a5e0 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Wed, 27 Nov 2019 20:24:16 +0000 Subject: [PATCH 200/548] Adds new ValueReference classes and update to Interface for IsForEditor method to check if we should get references for a specific dataeditor based on its alias --- .../Implement/ContentRepositoryBase.cs | 18 +++++-- .../Implement/DocumentBlueprintRepository.cs | 4 +- .../Implement/DocumentRepository.cs | 4 +- .../Repositories/Implement/MediaRepository.cs | 4 +- .../Implement/MemberRepository.cs | 4 +- .../DataValueReferenceCollectionBuilder.cs | 4 +- .../PropertyEditors/IDataValueReference.cs | 7 +++ .../Runtime/CoreInitialComposer.cs | 3 -- .../ContentPickerPropertyEditor.cs | 12 +---- .../PropertyEditors/GridPropertyEditor.cs | 26 +--------- .../MediaPickerPropertyEditor.cs | 12 +---- .../MultiNodeTreePickerPropertyEditor.cs | 15 +----- .../MultiUrlPickerValueEditor.cs | 5 +- .../NestedContentPropertyEditor.cs | 28 +---------- .../PropertyEditors/RichTextPropertyEditor.cs | 20 +------- .../ContentPickerPropertyValueReferences.cs | 22 +++++++++ .../GridPropertyValueReferences.cs | 37 ++++++++++++++ .../MediaPickerPropertyValueReferences.cs | 22 +++++++++ ...tiNodeTreePickerPropertyValueReferences.cs | 23 +++++++++ .../MultiUrlPickerValueReferences.cs | 26 ++++++++++ .../NestedContentPropertyValueReferences.cs | 49 +++++++++++++++++++ .../RichTextPropertyValueReferences.cs | 40 +++++++++++++++ src/Umbraco.Web/Runtime/WebInitialComposer.cs | 19 ++++++- src/Umbraco.Web/Umbraco.Web.csproj | 7 +++ 24 files changed, 281 insertions(+), 130 deletions(-) create mode 100644 src/Umbraco.Web/PropertyEditors/ValueReferences/ContentPickerPropertyValueReferences.cs create mode 100644 src/Umbraco.Web/PropertyEditors/ValueReferences/GridPropertyValueReferences.cs create mode 100644 src/Umbraco.Web/PropertyEditors/ValueReferences/MediaPickerPropertyValueReferences.cs create mode 100644 src/Umbraco.Web/PropertyEditors/ValueReferences/MultiNodeTreePickerPropertyValueReferences.cs create mode 100644 src/Umbraco.Web/PropertyEditors/ValueReferences/MultiUrlPickerValueReferences.cs create mode 100644 src/Umbraco.Web/PropertyEditors/ValueReferences/NestedContentPropertyValueReferences.cs create mode 100644 src/Umbraco.Web/PropertyEditors/ValueReferences/RichTextPropertyValueReferences.cs diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs index 65f6dc0472..c91a7e20e1 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs @@ -36,6 +36,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement where TRepository : class, IRepository { private readonly Lazy _propertyEditors; + private readonly DataValueReferenceCollection _dataValueReferences; /// /// @@ -49,13 +50,14 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// protected ContentRepositoryBase(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, ILanguageRepository languageRepository, IRelationRepository relationRepository, IRelationTypeRepository relationTypeRepository, - Lazy propertyEditors) + Lazy propertyEditors, DataValueReferenceCollection dataValueReferences) : base(scopeAccessor, cache, logger) { LanguageRepository = languageRepository; RelationRepository = relationRepository; RelationTypeRepository = relationTypeRepository; _propertyEditors = propertyEditors; + _dataValueReferences = dataValueReferences; } protected abstract TRepository This { get; } @@ -826,15 +828,21 @@ namespace Umbraco.Core.Persistence.Repositories.Implement foreach (var p in entity.Properties) { if (!PropertyEditors.TryGet(p.PropertyType.PropertyEditorAlias, out var editor)) continue; - var valueEditor = editor.GetValueEditor(); - if (!(valueEditor is IDataValueReference reference)) continue; //TODO: Support variants/segments! This is not required for this initial prototype which is why there is a check here if (!p.PropertyType.VariesByNothing()) continue; var val = p.GetValue(); // get the invariant value - var refs = reference.GetReferences(val); - trackedRelations.AddRange(refs); + + // WB: Loop over our collection of references registered and add references + foreach (var item in _dataValueReferences) + { + // 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 items + if (item.IsForEditor(editor)) + trackedRelations.AddRange(item.GetReferences(val)); + } } //First delete all auto-relations for this entity diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentBlueprintRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentBlueprintRepository.cs index 60d4026ad5..5c2e73de9d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentBlueprintRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentBlueprintRepository.cs @@ -20,8 +20,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { public DocumentBlueprintRepository(IScopeAccessor scopeAccessor, AppCaches appCaches, ILogger logger, IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository, ILanguageRepository languageRepository, IRelationRepository relationRepository, IRelationTypeRepository relationTypeRepository, - Lazy propertyEditorCollection) - : base(scopeAccessor, appCaches, logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditorCollection) + Lazy propertyEditorCollection, DataValueReferenceCollection dataValueReferences) + : base(scopeAccessor, appCaches, logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditorCollection, dataValueReferences) { } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs index 5e3e7f05b9..e1ea955972 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs @@ -46,8 +46,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// public DocumentRepository(IScopeAccessor scopeAccessor, AppCaches appCaches, ILogger logger, IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository, ILanguageRepository languageRepository, IRelationRepository relationRepository, IRelationTypeRepository relationTypeRepository, - Lazy propertyEditors) - : base(scopeAccessor, appCaches, logger, languageRepository, relationRepository, relationTypeRepository, propertyEditors) + Lazy propertyEditors, DataValueReferenceCollection dataValueReferences) + : base(scopeAccessor, appCaches, logger, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferences) { _contentTypeRepository = contentTypeRepository ?? throw new ArgumentNullException(nameof(contentTypeRepository)); _templateRepository = templateRepository ?? throw new ArgumentNullException(nameof(templateRepository)); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs index a3f9e45485..2297bbed2c 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs @@ -29,8 +29,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement private readonly MediaByGuidReadRepository _mediaByGuidReadRepository; public MediaRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IMediaTypeRepository mediaTypeRepository, ITagRepository tagRepository, ILanguageRepository languageRepository, IRelationRepository relationRepository, IRelationTypeRepository relationTypeRepository, - Lazy propertyEditorCollection) - : base(scopeAccessor, cache, logger, languageRepository, relationRepository, relationTypeRepository, propertyEditorCollection) + Lazy propertyEditorCollection, DataValueReferenceCollection dataValueReferences) + : base(scopeAccessor, cache, logger, languageRepository, relationRepository, relationTypeRepository, propertyEditorCollection, dataValueReferences) { _mediaTypeRepository = mediaTypeRepository ?? throw new ArgumentNullException(nameof(mediaTypeRepository)); _tagRepository = tagRepository ?? throw new ArgumentNullException(nameof(tagRepository)); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs index 2871bf1dd3..c20e990a2b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs @@ -28,8 +28,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement public MemberRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IMemberTypeRepository memberTypeRepository, IMemberGroupRepository memberGroupRepository, ITagRepository tagRepository, ILanguageRepository languageRepository, IRelationRepository relationRepository, IRelationTypeRepository relationTypeRepository, - Lazy propertyEditors) - : base(scopeAccessor, cache, logger, languageRepository, relationRepository, relationTypeRepository, propertyEditors) + Lazy propertyEditors, DataValueReferenceCollection dataValueReferences) + : base(scopeAccessor, cache, logger, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferences) { _memberTypeRepository = memberTypeRepository ?? throw new ArgumentNullException(nameof(memberTypeRepository)); _tagRepository = tagRepository ?? throw new ArgumentNullException(nameof(tagRepository)); diff --git a/src/Umbraco.Core/PropertyEditors/DataValueReferenceCollectionBuilder.cs b/src/Umbraco.Core/PropertyEditors/DataValueReferenceCollectionBuilder.cs index 0f8ee1bd8e..c6442275b0 100644 --- a/src/Umbraco.Core/PropertyEditors/DataValueReferenceCollectionBuilder.cs +++ b/src/Umbraco.Core/PropertyEditors/DataValueReferenceCollectionBuilder.cs @@ -2,8 +2,8 @@ namespace Umbraco.Core.PropertyEditors { - public class DataValueReferenceCollectionBuilder : LazyCollectionBuilderBase + public class DataValueReferenceCollectionBuilder : OrderedCollectionBuilderBase { - protected override DataValueReferenceCollectionBuilder This => this; + protected override DataValueReferenceCollectionBuilder This => this; } } diff --git a/src/Umbraco.Core/PropertyEditors/IDataValueReference.cs b/src/Umbraco.Core/PropertyEditors/IDataValueReference.cs index 8c0806a4a4..7d46704938 100644 --- a/src/Umbraco.Core/PropertyEditors/IDataValueReference.cs +++ b/src/Umbraco.Core/PropertyEditors/IDataValueReference.cs @@ -14,5 +14,12 @@ namespace Umbraco.Core.PropertyEditors /// /// IEnumerable GetReferences(object value); + + /// + /// 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); } } diff --git a/src/Umbraco.Core/Runtime/CoreInitialComposer.cs b/src/Umbraco.Core/Runtime/CoreInitialComposer.cs index db308d720c..94d2b68121 100644 --- a/src/Umbraco.Core/Runtime/CoreInitialComposer.cs +++ b/src/Umbraco.Core/Runtime/CoreInitialComposer.cs @@ -75,9 +75,6 @@ namespace Umbraco.Core.Runtime composition.RegisterUnique(); composition.RegisterUnique(); - // TODO: WB Add our collection - // Manually register stuff in this collection - composition.DataValueReferences(); // register a server registrar, by default it's the db registrar composition.RegisterUnique(f => diff --git a/src/Umbraco.Web/PropertyEditors/ContentPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/ContentPickerPropertyEditor.cs index 683f1a05c3..2f45d98fa1 100644 --- a/src/Umbraco.Web/PropertyEditors/ContentPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ContentPickerPropertyEditor.cs @@ -29,21 +29,11 @@ namespace Umbraco.Web.PropertyEditors protected override IDataValueEditor CreateValueEditor() => new ContentPickerPropertyValueEditor(Attribute); - internal class ContentPickerPropertyValueEditor : DataValueEditor, IDataValueReference + internal class ContentPickerPropertyValueEditor : DataValueEditor { 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 228e058ee7..16dffb3b10 100644 --- a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs @@ -54,7 +54,7 @@ namespace Umbraco.Web.PropertyEditors protected override IConfigurationEditor CreateConfigurationEditor() => new GridConfigurationEditor(); - internal class GridPropertyValueEditor : DataValueEditor, IDataValueReference + internal class GridPropertyValueEditor : DataValueEditor { private readonly IUmbracoContextAccessor _umbracoContextAccessor; private readonly HtmlImageSourceParser _imageSourceParser; @@ -156,30 +156,6 @@ namespace Umbraco.Web.PropertyEditors return grid; } - - /// - /// Resolve references from values - /// - /// - /// - public IEnumerable GetReferences(object value) - { - var rawJson = value == null ? string.Empty : value is string str ? str : value.ToString(); - - 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 ece210b9d1..6416fa3342 100644 --- a/src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs @@ -33,21 +33,11 @@ namespace Umbraco.Web.PropertyEditors protected override IDataValueEditor CreateValueEditor() => new MediaPickerPropertyValueEditor(Attribute); - internal class MediaPickerPropertyValueEditor : DataValueEditor, IDataValueReference + internal class MediaPickerPropertyValueEditor : DataValueEditor { 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; - - if (Udi.TryParse(asString, 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 1da665a622..d7a7a2ed59 100644 --- a/src/Umbraco.Web/PropertyEditors/MultiNodeTreePickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MultiNodeTreePickerPropertyEditor.cs @@ -23,25 +23,12 @@ namespace Umbraco.Web.PropertyEditors protected override IDataValueEditor CreateValueEditor() => new MultiNodeTreePickerPropertyValueEditor(Attribute); - public class MultiNodeTreePickerPropertyValueEditor : DataValueEditor, IDataValueReference + public class MultiNodeTreePickerPropertyValueEditor : DataValueEditor { 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/MultiUrlPickerValueEditor.cs b/src/Umbraco.Web/PropertyEditors/MultiUrlPickerValueEditor.cs index 9c42fe6cbe..de641f69a3 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, IDataValueReference + public class MultiUrlPickerValueEditor : DataValueEditor { private readonly IEntityService _entityService; private readonly ILogger _logger; @@ -190,9 +190,6 @@ namespace Umbraco.Web.PropertyEditors } } - - - } } } diff --git a/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs index 3d0605c4f9..865b583624 100644 --- a/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs @@ -54,7 +54,7 @@ namespace Umbraco.Web.PropertyEditors protected override IDataValueEditor CreateValueEditor() => new NestedContentPropertyValueEditor(Attribute, PropertyEditors, _dataTypeService, _contentTypeService); - internal class NestedContentPropertyValueEditor : DataValueEditor, IDataValueReference + internal class NestedContentPropertyValueEditor : DataValueEditor { private readonly PropertyEditorCollection _propertyEditors; private readonly IDataTypeService _dataTypeService; @@ -227,32 +227,6 @@ namespace Umbraco.Web.PropertyEditors // return json return JsonConvert.SerializeObject(deserialized); } - - 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; - } - #endregion } diff --git a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs index 427e36b37a..96b1d87205 100644 --- a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs @@ -57,7 +57,7 @@ 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, IDataValueReference + internal class RichTextPropertyValueEditor : DataValueEditor { private IUmbracoContextAccessor _umbracoContextAccessor; private readonly HtmlImageSourceParser _imageSourceParser; @@ -130,24 +130,6 @@ namespace Umbraco.Web.PropertyEditors 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/ValueReferences/ContentPickerPropertyValueReferences.cs b/src/Umbraco.Web/PropertyEditors/ValueReferences/ContentPickerPropertyValueReferences.cs new file mode 100644 index 0000000000..08405b6d66 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/ValueReferences/ContentPickerPropertyValueReferences.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using Umbraco.Core; +using Umbraco.Core.Models.Editors; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors.ValueReferences +{ + public class ContentPickerPropertyValueReferences : IDataValueReference + { + 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); + } + + public bool IsForEditor(IDataEditor dataEditor) => dataEditor.Alias.InvariantEquals(Constants.PropertyEditors.Aliases.ContentPicker); + } +} diff --git a/src/Umbraco.Web/PropertyEditors/ValueReferences/GridPropertyValueReferences.cs b/src/Umbraco.Web/PropertyEditors/ValueReferences/GridPropertyValueReferences.cs new file mode 100644 index 0000000000..85acff7ca3 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/ValueReferences/GridPropertyValueReferences.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core; +using Umbraco.Core.Models.Editors; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors.ValueReferences +{ + public class GridPropertyValueReferences : IDataValueReference + { + + /// + /// Resolve references from values + /// + /// + /// + public IEnumerable GetReferences(object value) + { + return Enumerable.Empty(); + + //var rawJson = value == null ? string.Empty : value is string str ? str : value.ToString(); + + ////TODO FIX SQUIGLES + //GridPropertyEditor.GridPropertyValueEditor.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; + } + + public bool IsForEditor(IDataEditor dataEditor) => dataEditor.Alias.InvariantEquals(Constants.PropertyEditors.Aliases.Grid); + } +} diff --git a/src/Umbraco.Web/PropertyEditors/ValueReferences/MediaPickerPropertyValueReferences.cs b/src/Umbraco.Web/PropertyEditors/ValueReferences/MediaPickerPropertyValueReferences.cs new file mode 100644 index 0000000000..ecd46b7ea5 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/ValueReferences/MediaPickerPropertyValueReferences.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using Umbraco.Core; +using Umbraco.Core.Models.Editors; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors.ValueReferences +{ + public class MediaPickerPropertyValueReferences : IDataValueReference + { + 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); + } + + public bool IsForEditor(IDataEditor dataEditor) => dataEditor.Alias.InvariantEquals(Constants.PropertyEditors.Aliases.MediaPicker); + } +} diff --git a/src/Umbraco.Web/PropertyEditors/ValueReferences/MultiNodeTreePickerPropertyValueReferences.cs b/src/Umbraco.Web/PropertyEditors/ValueReferences/MultiNodeTreePickerPropertyValueReferences.cs new file mode 100644 index 0000000000..4413f75080 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/ValueReferences/MultiNodeTreePickerPropertyValueReferences.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using Umbraco.Core; +using Umbraco.Core.Models.Editors; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors.ValueReferences +{ + public class MultiNodeTreePickerPropertyValueReferences : IDataValueReference + { + 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); + + } + + public bool IsForEditor(IDataEditor dataEditor) => dataEditor.Alias.InvariantEquals(Constants.PropertyEditors.Aliases.MultiNodeTreePicker); + } +} diff --git a/src/Umbraco.Web/PropertyEditors/ValueReferences/MultiUrlPickerValueReferences.cs b/src/Umbraco.Web/PropertyEditors/ValueReferences/MultiUrlPickerValueReferences.cs new file mode 100644 index 0000000000..5d8dfd4f39 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/ValueReferences/MultiUrlPickerValueReferences.cs @@ -0,0 +1,26 @@ +using Newtonsoft.Json; +using System.Collections.Generic; +using Umbraco.Core; +using Umbraco.Core.Models.Editors; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors.ValueReferences +{ + public class MultiUrlPickerValueReferences : IDataValueReference + { + 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); + } + + public bool IsForEditor(IDataEditor dataEditor) => dataEditor.Alias.InvariantEquals(Constants.PropertyEditors.Aliases.MultiUrlPicker); + + } +} diff --git a/src/Umbraco.Web/PropertyEditors/ValueReferences/NestedContentPropertyValueReferences.cs b/src/Umbraco.Web/PropertyEditors/ValueReferences/NestedContentPropertyValueReferences.cs new file mode 100644 index 0000000000..a872dde1c7 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/ValueReferences/NestedContentPropertyValueReferences.cs @@ -0,0 +1,49 @@ +using System.Collections.Generic; +using Umbraco.Core; +using Umbraco.Core.Models.Editors; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; +using static Umbraco.Web.PropertyEditors.NestedContentPropertyEditor; + +namespace Umbraco.Web.PropertyEditors.ValueReferences +{ + public class NestedContentPropertyValueReferences : IDataValueReference + { + private PropertyEditorCollection _propertyEditors; + private NestedContentValues _nestedContentValues; + + //ARGH LightInject moaning at me + public NestedContentPropertyValueReferences(PropertyEditorCollection propertyEditors, IContentTypeService contentTypeService) + { + _propertyEditors = propertyEditors; + _nestedContentValues = new NestedContentValues(contentTypeService); + } + + 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; + } + + public bool IsForEditor(IDataEditor dataEditor) => dataEditor.Alias.InvariantEquals(Constants.PropertyEditors.Aliases.NestedContent); + } +} diff --git a/src/Umbraco.Web/PropertyEditors/ValueReferences/RichTextPropertyValueReferences.cs b/src/Umbraco.Web/PropertyEditors/ValueReferences/RichTextPropertyValueReferences.cs new file mode 100644 index 0000000000..744b0dc27c --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/ValueReferences/RichTextPropertyValueReferences.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using Umbraco.Core; +using Umbraco.Core.Models.Editors; +using Umbraco.Core.PropertyEditors; +using Umbraco.Web.Templates; + +namespace Umbraco.Web.PropertyEditors.ValueReferences +{ + public class RichTextPropertyValueReferences : IDataValueReference + { + private HtmlImageSourceParser _imageSourceParser; + private HtmlLocalLinkParser _localLinkParser; + + public RichTextPropertyValueReferences(HtmlImageSourceParser imageSourceParser, HtmlLocalLinkParser localLinkParser) + { + _imageSourceParser = imageSourceParser; + _localLinkParser = localLinkParser; + } + + /// + /// 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 + } + + public bool IsForEditor(IDataEditor dataEditor) => dataEditor.Alias.InvariantEquals(Constants.PropertyEditors.Aliases.TinyMce); + } +} diff --git a/src/Umbraco.Web/Runtime/WebInitialComposer.cs b/src/Umbraco.Web/Runtime/WebInitialComposer.cs index 27d0b5a1ce..295a31c461 100644 --- a/src/Umbraco.Web/Runtime/WebInitialComposer.cs +++ b/src/Umbraco.Web/Runtime/WebInitialComposer.cs @@ -42,6 +42,8 @@ using Umbraco.Web.Trees; using Umbraco.Web.WebApi; using Current = Umbraco.Web.Composing.Current; using Umbraco.Web.PropertyEditors; +using static Umbraco.Web.PropertyEditors.GridPropertyEditor; +using Umbraco.Web.PropertyEditors.ValueReferences; namespace Umbraco.Web.Runtime { @@ -274,7 +276,22 @@ namespace Umbraco.Web.Runtime .Append() .Append() .Append(); - + + // Used to determine if a datatype/editor should be storing/tracking + // references to media item/s + composition.DataValueReferences() + .Append() + + // TODO: Unsure how to call other ValueReference in collection + // When looking at grid item cells for RTE or media found + //.Append() + .Append() + .Append() + .Append() + + // TODO: LightInject problem + //.Append() + .Append(); // replace with web implementation composition.RegisterUnique(); diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 5eca5ad7fe..22680898af 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -235,6 +235,13 @@ + + + + + + + From 69faaa8797d282eb5bfc5bd33eed1db32ab89e61 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Wed, 27 Nov 2019 20:29:38 +0000 Subject: [PATCH 201/548] Update tests to pass in an empty collection for the repository layers expecting the new dataValueReferencesCollection --- .../Persistence/Repositories/ContentTypeRepositoryTest.cs | 3 ++- .../Persistence/Repositories/DocumentRepositoryTest.cs | 3 ++- .../Persistence/Repositories/DomainRepositoryTest.cs | 3 ++- .../Persistence/Repositories/MediaRepositoryTest.cs | 3 ++- .../Persistence/Repositories/MemberRepositoryTest.cs | 3 ++- .../Persistence/Repositories/PublicAccessRepositoryTest.cs | 3 ++- .../Persistence/Repositories/TagRepositoryTest.cs | 6 ++++-- .../Persistence/Repositories/TemplateRepositoryTest.cs | 3 ++- .../Persistence/Repositories/UserRepositoryTest.cs | 6 ++++-- src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs | 3 ++- src/Umbraco.Tests/Services/ContentServiceTests.cs | 3 ++- 11 files changed, 26 insertions(+), 13 deletions(-) diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs index 8ed935795d..141331d4f4 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs @@ -40,7 +40,8 @@ namespace Umbraco.Tests.Persistence.Repositories var entityRepository = new EntityRepository(scopeAccessor); var relationRepository = new RelationRepository(scopeAccessor, Logger, relationTypeRepository, entityRepository); var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); - var repository = new DocumentRepository(scopeAccessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors); + var dataValueReferences = new DataValueReferenceCollection(Enumerable.Empty()); + var repository = new DocumentRepository(scopeAccessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferences); return repository; } diff --git a/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs index 3a36a647f0..76d52fb844 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs @@ -73,7 +73,8 @@ namespace Umbraco.Tests.Persistence.Repositories var entityRepository = new EntityRepository(scopeAccessor); var relationRepository = new RelationRepository(scopeAccessor, Logger, relationTypeRepository, entityRepository); var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); - var repository = new DocumentRepository(scopeAccessor, appCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors); + var dataValueReferences = new DataValueReferenceCollection(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 ad27aed309..99adcd63e3 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs @@ -31,7 +31,8 @@ namespace Umbraco.Tests.Persistence.Repositories var entityRepository = new EntityRepository(accessor); var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository, entityRepository); var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); - documentRepository = new DocumentRepository(accessor, Core.Cache.AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors); + var dataValueReferences = new DataValueReferenceCollection(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/MediaRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs index ced0ee75e9..c7503671b7 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs @@ -46,7 +46,8 @@ namespace Umbraco.Tests.Persistence.Repositories var entityRepository = new EntityRepository(scopeAccessor); var relationRepository = new RelationRepository(scopeAccessor, Logger, relationTypeRepository, entityRepository); var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); - var repository = new MediaRepository(scopeAccessor, appCaches, Logger, mediaTypeRepository, tagRepository, Mock.Of(), relationRepository, relationTypeRepository, propertyEditors); + var dataValueReferences = new DataValueReferenceCollection(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 7531d92b49..b9d034bd12 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs @@ -40,7 +40,8 @@ namespace Umbraco.Tests.Persistence.Repositories var entityRepository = new EntityRepository(accessor); var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository, entityRepository); var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); - var repository = new MemberRepository(accessor, AppCaches.Disabled, Logger, memberTypeRepository, memberGroupRepository, tagRepo, Mock.Of(), relationRepository, relationTypeRepository, propertyEditors); + var dataValueReferences = new DataValueReferenceCollection(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 9a65bde41f..b69adcbb67 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs @@ -315,7 +315,8 @@ namespace Umbraco.Tests.Persistence.Repositories var entityRepository = new EntityRepository(accessor); var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository, entityRepository); var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); - var repository = new DocumentRepository(accessor, AppCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors); + var dataValueReferences = new DataValueReferenceCollection(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/TagRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs index fe0db4563a..7f356897af 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs @@ -964,7 +964,8 @@ namespace Umbraco.Tests.Persistence.Repositories var entityRepository = new EntityRepository(accessor); var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository, entityRepository); var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); - var repository = new DocumentRepository(accessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors); + var dataValueReferences = new DataValueReferenceCollection(Enumerable.Empty()); + var repository = new DocumentRepository(accessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferences); return repository; } @@ -980,7 +981,8 @@ namespace Umbraco.Tests.Persistence.Repositories var entityRepository = new EntityRepository(accessor); var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository, entityRepository); var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); - var repository = new MediaRepository(accessor, AppCaches.Disabled, Logger, mediaTypeRepository, tagRepository, Mock.Of(), relationRepository, relationTypeRepository, propertyEditors); + var dataValueReferences = new DataValueReferenceCollection(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 4bf50c6ffd..14aa9d8cf2 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs @@ -246,7 +246,8 @@ namespace Umbraco.Tests.Persistence.Repositories var entityRepository = new EntityRepository(ScopeProvider); var relationRepository = new RelationRepository(ScopeProvider, Logger, relationTypeRepository, entityRepository); var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); - var contentRepo = new DocumentRepository(ScopeProvider, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors); + var dataValueReferences = new DataValueReferenceCollection(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 f4ab387683..fead39b6cb 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs @@ -35,7 +35,8 @@ namespace Umbraco.Tests.Persistence.Repositories var entityRepository = new EntityRepository(accessor); var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository, entityRepository); var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); - var repository = new MediaRepository(accessor, AppCaches, Mock.Of(), mediaTypeRepository, tagRepository, Mock.Of(), relationRepository, relationTypeRepository, propertyEditors); + var dataValueReferences = new DataValueReferenceCollection(Enumerable.Empty()); + var repository = new MediaRepository(accessor, AppCaches, Mock.Of(), mediaTypeRepository, tagRepository, Mock.Of(), relationRepository, relationTypeRepository, propertyEditors, dataValueReferences); return repository; } @@ -57,7 +58,8 @@ namespace Umbraco.Tests.Persistence.Repositories var entityRepository = new EntityRepository(accessor); var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository, entityRepository); var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); - var repository = new DocumentRepository(accessor, AppCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors); + var dataValueReferences = new DataValueReferenceCollection(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/Services/ContentServicePerformanceTest.cs b/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs index 763635c393..d0f4ff95b3 100644 --- a/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs +++ b/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs @@ -52,7 +52,8 @@ namespace Umbraco.Tests.Services var entityRepository = new EntityRepository(accessor); var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository, entityRepository); var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); - var repository = new DocumentRepository(accessor, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository, relationRepository, relationTypeRepository, propertyEditors); + var dataValueReferences = new DataValueReferenceCollection(Enumerable.Empty()); + var repository = new DocumentRepository(accessor, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferences); return repository; } diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index 88b5cb5c34..cb097af2ef 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -3211,7 +3211,8 @@ namespace Umbraco.Tests.Services var entityRepository = new EntityRepository(accessor); var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository, entityRepository); var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); - var repository = new DocumentRepository(accessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors); + var dataValueReferences = new DataValueReferenceCollection(Enumerable.Empty()); + var repository = new DocumentRepository(accessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferences); return repository; } From 771fa3028a5aab0bc0bde27b185cd939cb2a7d1d Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Thu, 28 Nov 2019 09:09:50 +0100 Subject: [PATCH 202/548] V8: Fix Nested Content configuration overflow on smaller screens (#7006) --- .../less/components/umb-nested-content.less | 20 ++----------------- .../nestedcontent.doctypepicker.html | 7 +------ 2 files changed, 3 insertions(+), 24 deletions(-) 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 b28925bec1..bf0dd9d109 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 @@ -248,7 +248,6 @@ } .umb-nested-content__placeholder { - height: 22px; padding: 4px 6px; border: 1px dashed #d8d7d9; background: 0 0; @@ -259,33 +258,18 @@ text-align: center; &--selected { - border: 1px solid #d8d7d9; + border: none; text-align: left; + padding: 0; } } -.umb-nested-content__placeholder-name{ - font-size: 15px; -} - .umb-nested-content__placeholder:hover { color: #2152a3; border-color: #2152a3; text-decoration: none; } -.umb-nested-content__placeholder-icon-holder { - width: 20px; - text-align: center; - display: inline-block; -} - -.umb-nested-content__placeholder-icon { - font-size: 18px; - vertical-align: middle; -} - - .form-horizontal .umb-nested-content--narrow .controls-row { margin-left: 40% !important; } 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 f6bfdecb31..c6860140a5 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 @@ -24,12 +24,7 @@
{{ph = placeholder(config);""}}
- - - - - {{ ph.name }} - + Add element type
From 938d5d207d867b92051e82ec32fee2c662d7912f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Thu, 28 Nov 2019 10:35:07 +0100 Subject: [PATCH 203/548] =?UTF-8?q?NC=20PropertyAction=20Delete-All,=20do?= =?UTF-8?q?=20not=20test=20length=20if=20value=20is=20nu=E2=80=A6=20(#7242?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../propertyeditors/nestedcontent/nestedcontent.controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 c771b5645b..635a80dbe9 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 @@ -565,7 +565,7 @@ function updatePropertyActionStates() { copyAllEntriesAction.isDisabled = !model.value || model.value.length === 0; - removeAllEntriesAction.isDisabled = model.value.length === 0; + removeAllEntriesAction.isDisabled = !model.value || model.value.length === 0; } From f0a310d6d7d6fffa9070b7370a12b043522d9b58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Thu, 28 Nov 2019 17:41:28 +0100 Subject: [PATCH 204/548] using btn-outline for create button in listivew --- src/Umbraco.Web.UI.Client/src/less/buttons.less | 8 +++++--- .../src/less/components/umb-layout-selector.less | 11 +++++++---- .../src/views/components/umb-layout-selector.html | 2 +- .../src/views/propertyeditors/listview/listview.html | 6 +++--- 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/buttons.less b/src/Umbraco.Web.UI.Client/src/less/buttons.less index 85532f4231..df81d7df44 100644 --- a/src/Umbraco.Web.UI.Client/src/less/buttons.less +++ b/src/Umbraco.Web.UI.Client/src/less/buttons.less @@ -261,10 +261,11 @@ input[type="button"] { .btn-outline { border: 1px solid; border-color: @gray-7; - background: @white; + background: transparent; color: @blueExtraDark; padding: 5px 13px; - transition: all .2s linear; + transition: border-color .12s linear, color .12s linear; + font-weight: 600; } .btn-outline:hover, @@ -272,7 +273,8 @@ input[type="button"] { .btn-outline:active { border-color: @ui-light-type-hover; color: @ui-light-type-hover; - background: @white; + background: transparent; + transition: border-color .12s linear, color .12s linear; } // Cross-browser Jank diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-layout-selector.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-layout-selector.less index cdc6cfcb63..edc8cd77d5 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-layout-selector.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-layout-selector.less @@ -6,7 +6,8 @@ .umb-layout-selector__active-layout { background: transparent; box-sizing: border-box; - border: 1px solid @inputBorder; + border: 1px solid @ui-action-discreet-border; + color: @ui-action-discreet-type; cursor: pointer; height: 30px; width: 30px; @@ -17,7 +18,8 @@ } .umb-layout-selector__active-layout:hover { - border-color: @inputBorderFocus; + border-color: @ui-action-discreet-border-hover; + color: @ui-action-discreet-type-hover; } .umb-layout-selector__dropdown { @@ -31,6 +33,7 @@ flex-direction: column; transform: translate(-50%,0); left: 50%; + border-radius: 3px; } .umb-layout-selector__dropdown-item { @@ -46,11 +49,11 @@ } .umb-layout-selector__dropdown-item:hover { - border: 1px solid @gray-8; + border: 1px solid @ui-action-discreet-border; } .umb-layout-selector__dropdown-item.-active { - border: 1px solid @blue; + border: 1px solid @ui-action-discreet-border-hover; } .umb-layout-selector__dropdown-item-icon, 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 c6c841f8b1..1fa917a07f 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 @@ -1,6 +1,6 @@
-
- From 71cfbcc7878e86801a6a61e7a3fb143927bc3972 Mon Sep 17 00:00:00 2001 From: abi Date: Fri, 29 Nov 2019 15:13:11 +0000 Subject: [PATCH 205/548] MediaIndexPopulator should use IUmbracoContentIndex instead of UmbracoContentIndex --- src/Umbraco.Examine/MediaIndexPopulator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Examine/MediaIndexPopulator.cs b/src/Umbraco.Examine/MediaIndexPopulator.cs index 6dadcbe4b3..1f5b11e54f 100644 --- a/src/Umbraco.Examine/MediaIndexPopulator.cs +++ b/src/Umbraco.Examine/MediaIndexPopulator.cs @@ -10,7 +10,7 @@ namespace Umbraco.Examine /// /// Performs the data lookups required to rebuild a media index /// - public class MediaIndexPopulator : IndexPopulator + public class MediaIndexPopulator : IndexPopulator { private readonly int? _parentId; private readonly IMediaService _mediaService; @@ -69,6 +69,6 @@ namespace Umbraco.Examine pageIndex++; } while (media.Length == pageSize); } - + } } From 2ec5a8101aba2593d1b62f285874119f36c30f77 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 2 Dec 2019 17:15:47 +1100 Subject: [PATCH 206/548] fixes binary breaking change introduced --- src/Umbraco.Web/Cache/ContentCacheRefresher.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Umbraco.Web/Cache/ContentCacheRefresher.cs b/src/Umbraco.Web/Cache/ContentCacheRefresher.cs index d9a6518493..df332b5168 100644 --- a/src/Umbraco.Web/Cache/ContentCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/ContentCacheRefresher.cs @@ -139,6 +139,13 @@ namespace Umbraco.Web.Cache public class JsonPayload { + [Obsolete("Use the constructor specifying a GUID instead, using this constructor will result in not refreshing all caches")] + public JsonPayload(int id, TreeChangeTypes changeTypes) + { + Id = id; + ChangeTypes = changeTypes; + } + public JsonPayload(int id, Guid? key, TreeChangeTypes changeTypes) { Id = id; From d456051cbeef20aadc045c917e7f89641870342c Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Mon, 2 Dec 2019 07:49:29 +0100 Subject: [PATCH 207/548] Do not save content when opening the content template in infinite editing --- .../src/views/components/content/umb-content-node-info.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html index d72e977010..c35686acd1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html @@ -169,7 +169,7 @@ ng-change="updateTemplate(node.template)"> -
From 201c31395cf8cf9d3d6e7df0a4627a9d2af74563 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 2 Dec 2019 14:48:17 +0100 Subject: [PATCH 208/548] componentizing mini-search --- src/Umbraco.Web.UI.Client/src/less/belle.less | 1 + .../src/less/components/umb-mini-search.less | 40 +++++++++++++++++ .../umb-mini-search/umb-mini-search.html | 16 +++++++ .../umbminisearch.component.js | 43 +++++++++++++++++++ .../listview/listview.controller.js | 26 +++-------- .../propertyeditors/listview/listview.html | 19 ++------ 6 files changed, 110 insertions(+), 35 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/less/components/umb-mini-search.less create mode 100644 src/Umbraco.Web.UI.Client/src/views/components/umb-mini-search/umb-mini-search.html create mode 100644 src/Umbraco.Web.UI.Client/src/views/components/umb-mini-search/umbminisearch.component.js diff --git a/src/Umbraco.Web.UI.Client/src/less/belle.less b/src/Umbraco.Web.UI.Client/src/less/belle.less index 391fafb3fa..15d54b615f 100644 --- a/src/Umbraco.Web.UI.Client/src/less/belle.less +++ b/src/Umbraco.Web.UI.Client/src/less/belle.less @@ -132,6 +132,7 @@ @import "components/umb-content-grid.less"; @import "components/umb-contextmenu.less"; @import "components/umb-layout-selector.less"; +@import "components/umb-mini-search.less"; @import "components/tooltip/umb-tooltip.less"; @import "components/tooltip/umb-tooltip-list.less"; @import "components/overlays/umb-overlay-backdrop.less"; 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 new file mode 100644 index 0000000000..5b94fc0f24 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-mini-search.less @@ -0,0 +1,40 @@ +.umb-mini-search { + position: relative; + + .icon { + position: absolute; + padding: 5px 8px; + pointer-events: none; + top: 2px; + color: @ui-action-discreet-type; + transition: color .1s linear; + } + + input { + width: 0px; + 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; + } + + &:focus-within, &:hover { + .icon { + color: @ui-action-discreet-type-hover; + } + input { + color: @ui-action-discreet-border-hover; + border-color: @ui-action-discreet-border-hover; + } + } + + input:focus, &:focus-within input { + background-color: white; + color: @ui-action-discreet-border-hover; + border-color: @ui-action-discreet-border-hover; + width: 190px; + padding-left:30px; + } + +} 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 new file mode 100644 index 0000000000..b2cfe66d8b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-search/umb-mini-search.html @@ -0,0 +1,16 @@ + +
+ + +
+ 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 new file mode 100644 index 0000000000..074d29bbd7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-search/umbminisearch.component.js @@ -0,0 +1,43 @@ +(function () { + 'use strict'; + + angular + .module('umbraco') + .component('umbMiniSearch', { + templateUrl: 'views/components/umb-mini-search/umb-mini-search.html', + controller: UmbMiniSearchController, + controllerAs: 'vm', + bindings: { + model: "=", + onStartTyping: "&", + onSearch: "&" + } + }); + + function UmbMiniSearchController($scope) { + + var vm = this; + + var searchDelay = _.debounce(function () { + $scope.$apply(function () { + vm.onSearch(); + }); + }, 500); + + vm.onKeyDown = function (ev) { + //13: enter + switch (ev.keyCode) { + case 13: + vm.onSearch(); + break; + } + }; + + vm.onChange = function () { + vm.onStartTyping(); + searchDelay(); + }; + + } + +})(); 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 be9ad6816d..fe83a4d451 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 @@ -324,33 +324,19 @@ function listViewController($scope, $interpolate, $routeParams, $injector, $time }); }; - var searchListView = _.debounce(function () { - $scope.$apply(function () { - makeSearch(); - }); - }, 500); + - $scope.forceSearch = function (ev) { - //13: enter - switch (ev.keyCode) { - case 13: - makeSearch(); - break; - } - }; - - $scope.enterSearch = function () { - $scope.viewLoaded = false; - searchListView(); - }; - - function makeSearch() { + $scope.makeSearch = function() { if ($scope.options.filter !== null && $scope.options.filter !== undefined) { $scope.options.pageNumber = 1; $scope.reloadView($scope.contentId); } } + $scope.onSearchStartTyping = function() { + $scope.viewLoaded = false; + } + $scope.selectedItemsCount = function () { return $scope.selection.length; }; 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 dae4bf6ccc..c566323493 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 @@ -126,21 +126,10 @@ - -
- - -
-
+ + + +
From 988f8890fed4c33426aeb2bf15151c04930b4076 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 2 Dec 2019 14:48:43 +0100 Subject: [PATCH 209/548] minor change for mini-search component --- .../src/less/components/umb-mini-search.less | 4 +++ .../umb-mini-search/umb-mini-search.html | 30 +++++++++---------- 2 files changed, 18 insertions(+), 16 deletions(-) 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 5b94fc0f24..ac15b3dcf8 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 @@ -1,5 +1,6 @@ .umb-mini-search { position: relative; + display: block; .icon { position: absolute; @@ -33,6 +34,9 @@ background-color: white; color: @ui-action-discreet-border-hover; border-color: @ui-action-discreet-border-hover; + } + + input:focus, &:focus-within input, &.--has-value input { width: 190px; padding-left:30px; } 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 b2cfe66d8b..20ce87f0eb 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,16 +1,14 @@ - -
- - -
- + + + + From 43f63497d039bad1a829c1e1cadad5828f26b2c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 2 Dec 2019 14:49:14 +0100 Subject: [PATCH 210/548] visual corrections for listview and grid --- src/Umbraco.Web.UI.Client/src/less/listview.less | 4 ++++ .../src/views/components/umb-content-grid.html | 2 +- .../src/views/components/umb-mini-list-view.html | 6 +++--- .../src/views/components/umb-table.html | 4 ++-- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/listview.less b/src/Umbraco.Web.UI.Client/src/less/listview.less index 975dbdbd4a..b724688a32 100644 --- a/src/Umbraco.Web.UI.Client/src/less/listview.less +++ b/src/Umbraco.Web.UI.Client/src/less/listview.less @@ -1,6 +1,10 @@ // Listview // ------------------------- +.umb-listview { + min-height: 100px; +} + .umb-listview table { border: 1px solid @gray-8; } diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-content-grid.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-content-grid.html index 93fa590f68..8865d084e1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-content-grid.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-content-grid.html @@ -1,7 +1,7 @@
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 f11df6042e..33a28af014 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 @@ -1,7 +1,7 @@
-
@@ -60,7 +60,7 @@
-
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-table.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-table.html index df817c1657..ae19ab175f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-table.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-table.html @@ -2,7 +2,7 @@
-
+
-
From 1471bffeffd1a9a9f68fc8b92b741b35bc227491 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 2 Dec 2019 15:00:56 +0000 Subject: [PATCH 211/548] Adds DataValueReferenceFor & its collection --- src/Umbraco.Core/Composing/Current.cs | 4 +- src/Umbraco.Core/CompositionExtensions.cs | 4 +- .../Implement/ContentRepositoryBase.cs | 20 +++++--- .../Implement/DocumentBlueprintRepository.cs | 4 +- .../Implement/DocumentRepository.cs | 4 +- .../Repositories/Implement/MediaRepository.cs | 4 +- .../Implement/MemberRepository.cs | 4 +- .../DataValueReferenceCollection.cs | 12 ----- .../DataValueReferenceCollectionBuilder.cs | 9 ---- .../DataValueReferenceForCollection.cs | 12 +++++ .../DataValueReferenceForCollectionBuilder.cs | 9 ++++ .../PropertyEditors/IDataValueReference.cs | 9 +--- .../PropertyEditors/IDataValueReferenceFor.cs | 18 +++++++ src/Umbraco.Core/Umbraco.Core.csproj | 5 +- .../Repositories/ContentTypeRepositoryTest.cs | 2 +- .../Repositories/DocumentRepositoryTest.cs | 2 +- .../Repositories/DomainRepositoryTest.cs | 2 +- .../Repositories/MediaRepositoryTest.cs | 2 +- .../Repositories/MemberRepositoryTest.cs | 2 +- .../PublicAccessRepositoryTest.cs | 2 +- .../Repositories/TagRepositoryTest.cs | 4 +- .../Repositories/TemplateRepositoryTest.cs | 2 +- .../Repositories/UserRepositoryTest.cs | 4 +- .../Services/ContentServicePerformanceTest.cs | 2 +- .../Services/ContentServiceTests.cs | 2 +- src/Umbraco.Web/Composing/Current.cs | 2 +- .../ContentPickerPropertyEditor.cs | 12 ++++- .../PropertyEditors/GridPropertyEditor.cs | 21 +++++++- .../MediaPickerPropertyEditor.cs | 12 ++++- .../MultiNodeTreePickerPropertyEditor.cs | 12 ++++- .../MultiUrlPickerPropertyEditor.cs | 17 ++++++- .../NestedContentPropertyEditor.cs | 27 +++++++++- .../PropertyEditors/RichTextPropertyEditor.cs | 20 +++++++- .../ContentPickerPropertyValueReferences.cs | 22 --------- .../GridPropertyValueReferences.cs | 37 -------------- .../MediaPickerPropertyValueReferences.cs | 22 --------- ...tiNodeTreePickerPropertyValueReferences.cs | 23 --------- .../MultiUrlPickerValueReferences.cs | 26 ---------- .../NestedContentPropertyValueReferences.cs | 49 ------------------- .../RichTextPropertyValueReferences.cs | 40 --------------- src/Umbraco.Web/Runtime/WebInitialComposer.cs | 21 +------- src/Umbraco.Web/Umbraco.Web.csproj | 7 --- 42 files changed, 199 insertions(+), 315 deletions(-) delete mode 100644 src/Umbraco.Core/PropertyEditors/DataValueReferenceCollection.cs delete mode 100644 src/Umbraco.Core/PropertyEditors/DataValueReferenceCollectionBuilder.cs create mode 100644 src/Umbraco.Core/PropertyEditors/DataValueReferenceForCollection.cs create mode 100644 src/Umbraco.Core/PropertyEditors/DataValueReferenceForCollectionBuilder.cs create mode 100644 src/Umbraco.Core/PropertyEditors/IDataValueReferenceFor.cs delete mode 100644 src/Umbraco.Web/PropertyEditors/ValueReferences/ContentPickerPropertyValueReferences.cs delete mode 100644 src/Umbraco.Web/PropertyEditors/ValueReferences/GridPropertyValueReferences.cs delete mode 100644 src/Umbraco.Web/PropertyEditors/ValueReferences/MediaPickerPropertyValueReferences.cs delete mode 100644 src/Umbraco.Web/PropertyEditors/ValueReferences/MultiNodeTreePickerPropertyValueReferences.cs delete mode 100644 src/Umbraco.Web/PropertyEditors/ValueReferences/MultiUrlPickerValueReferences.cs delete mode 100644 src/Umbraco.Web/PropertyEditors/ValueReferences/NestedContentPropertyValueReferences.cs delete mode 100644 src/Umbraco.Web/PropertyEditors/ValueReferences/RichTextPropertyValueReferences.cs diff --git a/src/Umbraco.Core/Composing/Current.cs b/src/Umbraco.Core/Composing/Current.cs index 1bd56ed727..899c465ca7 100644 --- a/src/Umbraco.Core/Composing/Current.cs +++ b/src/Umbraco.Core/Composing/Current.cs @@ -154,8 +154,8 @@ namespace Umbraco.Core.Composing public static DataEditorCollection DataEditors => Factory.GetInstance(); - public static DataValueReferenceCollection DataValueReferences - => Factory.GetInstance(); + public static DataValueReferenceForCollection DataValueReferenceFors + => Factory.GetInstance(); public static PropertyEditorCollection PropertyEditors => Factory.GetInstance(); diff --git a/src/Umbraco.Core/CompositionExtensions.cs b/src/Umbraco.Core/CompositionExtensions.cs index d29f251edc..aee4b41be8 100644 --- a/src/Umbraco.Core/CompositionExtensions.cs +++ b/src/Umbraco.Core/CompositionExtensions.cs @@ -53,8 +53,8 @@ namespace Umbraco.Core /// Gets the data value reference collection builder. /// /// The composition. - public static DataValueReferenceCollectionBuilder DataValueReferences(this Composition composition) - => composition.WithCollectionBuilder(); + public static DataValueReferenceForCollectionBuilder DataValueReferenceFors(this Composition composition) + => composition.WithCollectionBuilder(); /// /// Gets the property value converters collection builder. diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs index c91a7e20e1..77d36b8d43 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs @@ -36,7 +36,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement where TRepository : class, IRepository { private readonly Lazy _propertyEditors; - private readonly DataValueReferenceCollection _dataValueReferences; + private readonly DataValueReferenceForCollection _dataValueReferenceFors; /// /// @@ -50,14 +50,14 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// protected ContentRepositoryBase(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, ILanguageRepository languageRepository, IRelationRepository relationRepository, IRelationTypeRepository relationTypeRepository, - Lazy propertyEditors, DataValueReferenceCollection dataValueReferences) + Lazy propertyEditors, DataValueReferenceForCollection dataValueReferenceFors) : base(scopeAccessor, cache, logger) { LanguageRepository = languageRepository; RelationRepository = relationRepository; RelationTypeRepository = relationTypeRepository; _propertyEditors = propertyEditors; - _dataValueReferences = dataValueReferences; + _dataValueReferenceFors = dataValueReferenceFors; } protected abstract TRepository This { get; } @@ -828,20 +828,28 @@ namespace Umbraco.Core.Persistence.Repositories.Implement foreach (var p in entity.Properties) { if (!PropertyEditors.TryGet(p.PropertyType.PropertyEditorAlias, out var editor)) continue; + var valueEditor = editor.GetValueEditor(); + if (!(valueEditor is IDataValueReference reference)) continue; //TODO: Support variants/segments! This is not required for this initial prototype which is why there is a check here if (!p.PropertyType.VariesByNothing()) continue; var val = p.GetValue(); // get the invariant value + var refs = reference.GetReferences(val); + trackedRelations.AddRange(refs); - // WB: Loop over our collection of references registered and add references - foreach (var item in _dataValueReferences) + + // 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 _dataValueReferenceFors) { // 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 items if (item.IsForEditor(editor)) - trackedRelations.AddRange(item.GetReferences(val)); + trackedRelations.AddRange(item.GetDataValueReference().GetReferences(val)); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentBlueprintRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentBlueprintRepository.cs index 5c2e73de9d..e3fba3db01 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentBlueprintRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentBlueprintRepository.cs @@ -20,8 +20,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { public DocumentBlueprintRepository(IScopeAccessor scopeAccessor, AppCaches appCaches, ILogger logger, IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository, ILanguageRepository languageRepository, IRelationRepository relationRepository, IRelationTypeRepository relationTypeRepository, - Lazy propertyEditorCollection, DataValueReferenceCollection dataValueReferences) - : base(scopeAccessor, appCaches, logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditorCollection, dataValueReferences) + Lazy propertyEditorCollection, DataValueReferenceForCollection dataValueReferenceFors) + : base(scopeAccessor, appCaches, logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditorCollection, dataValueReferenceFors) { } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs index e1ea955972..8b63b93f16 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs @@ -46,8 +46,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// public DocumentRepository(IScopeAccessor scopeAccessor, AppCaches appCaches, ILogger logger, IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository, ILanguageRepository languageRepository, IRelationRepository relationRepository, IRelationTypeRepository relationTypeRepository, - Lazy propertyEditors, DataValueReferenceCollection dataValueReferences) - : base(scopeAccessor, appCaches, logger, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferences) + Lazy propertyEditors, DataValueReferenceForCollection dataValueReferenceFors) + : base(scopeAccessor, appCaches, logger, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferenceFors) { _contentTypeRepository = contentTypeRepository ?? throw new ArgumentNullException(nameof(contentTypeRepository)); _templateRepository = templateRepository ?? throw new ArgumentNullException(nameof(templateRepository)); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs index 2297bbed2c..da124db77f 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs @@ -29,8 +29,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement private readonly MediaByGuidReadRepository _mediaByGuidReadRepository; public MediaRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IMediaTypeRepository mediaTypeRepository, ITagRepository tagRepository, ILanguageRepository languageRepository, IRelationRepository relationRepository, IRelationTypeRepository relationTypeRepository, - Lazy propertyEditorCollection, DataValueReferenceCollection dataValueReferences) - : base(scopeAccessor, cache, logger, languageRepository, relationRepository, relationTypeRepository, propertyEditorCollection, dataValueReferences) + Lazy propertyEditorCollection, DataValueReferenceForCollection dataValueReferenceFors) + : base(scopeAccessor, cache, logger, languageRepository, relationRepository, relationTypeRepository, propertyEditorCollection, dataValueReferenceFors) { _mediaTypeRepository = mediaTypeRepository ?? throw new ArgumentNullException(nameof(mediaTypeRepository)); _tagRepository = tagRepository ?? throw new ArgumentNullException(nameof(tagRepository)); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs index c20e990a2b..06d93dfe50 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs @@ -28,8 +28,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement public MemberRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IMemberTypeRepository memberTypeRepository, IMemberGroupRepository memberGroupRepository, ITagRepository tagRepository, ILanguageRepository languageRepository, IRelationRepository relationRepository, IRelationTypeRepository relationTypeRepository, - Lazy propertyEditors, DataValueReferenceCollection dataValueReferences) - : base(scopeAccessor, cache, logger, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferences) + Lazy propertyEditors, DataValueReferenceForCollection dataValueReferenceFors) + : base(scopeAccessor, cache, logger, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferenceFors) { _memberTypeRepository = memberTypeRepository ?? throw new ArgumentNullException(nameof(memberTypeRepository)); _tagRepository = tagRepository ?? throw new ArgumentNullException(nameof(tagRepository)); diff --git a/src/Umbraco.Core/PropertyEditors/DataValueReferenceCollection.cs b/src/Umbraco.Core/PropertyEditors/DataValueReferenceCollection.cs deleted file mode 100644 index 6d0e3e62df..0000000000 --- a/src/Umbraco.Core/PropertyEditors/DataValueReferenceCollection.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Collections.Generic; -using Umbraco.Core.Composing; - -namespace Umbraco.Core.PropertyEditors -{ - public class DataValueReferenceCollection : BuilderCollectionBase - { - public DataValueReferenceCollection(IEnumerable items) - : base(items) - { } - } -} diff --git a/src/Umbraco.Core/PropertyEditors/DataValueReferenceCollectionBuilder.cs b/src/Umbraco.Core/PropertyEditors/DataValueReferenceCollectionBuilder.cs deleted file mode 100644 index c6442275b0..0000000000 --- a/src/Umbraco.Core/PropertyEditors/DataValueReferenceCollectionBuilder.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Umbraco.Core.Composing; - -namespace Umbraco.Core.PropertyEditors -{ - public class DataValueReferenceCollectionBuilder : OrderedCollectionBuilderBase - { - protected override DataValueReferenceCollectionBuilder This => this; - } -} diff --git a/src/Umbraco.Core/PropertyEditors/DataValueReferenceForCollection.cs b/src/Umbraco.Core/PropertyEditors/DataValueReferenceForCollection.cs new file mode 100644 index 0000000000..c5b9470868 --- /dev/null +++ b/src/Umbraco.Core/PropertyEditors/DataValueReferenceForCollection.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using Umbraco.Core.Composing; + +namespace Umbraco.Core.PropertyEditors +{ + public class DataValueReferenceForCollection : BuilderCollectionBase + { + public DataValueReferenceForCollection(IEnumerable items) + : base(items) + { } + } +} diff --git a/src/Umbraco.Core/PropertyEditors/DataValueReferenceForCollectionBuilder.cs b/src/Umbraco.Core/PropertyEditors/DataValueReferenceForCollectionBuilder.cs new file mode 100644 index 0000000000..a7b03ea482 --- /dev/null +++ b/src/Umbraco.Core/PropertyEditors/DataValueReferenceForCollectionBuilder.cs @@ -0,0 +1,9 @@ +using Umbraco.Core.Composing; + +namespace Umbraco.Core.PropertyEditors +{ + public class DataValueReferenceForCollectionBuilder : OrderedCollectionBuilderBase + { + protected override DataValueReferenceForCollectionBuilder This => this; + } +} diff --git a/src/Umbraco.Core/PropertyEditors/IDataValueReference.cs b/src/Umbraco.Core/PropertyEditors/IDataValueReference.cs index 7d46704938..6377098bfc 100644 --- a/src/Umbraco.Core/PropertyEditors/IDataValueReference.cs +++ b/src/Umbraco.Core/PropertyEditors/IDataValueReference.cs @@ -13,13 +13,6 @@ namespace Umbraco.Core.PropertyEditors /// /// /// - IEnumerable GetReferences(object value); - - /// - /// 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); + IEnumerable GetReferences(object value); } } diff --git a/src/Umbraco.Core/PropertyEditors/IDataValueReferenceFor.cs b/src/Umbraco.Core/PropertyEditors/IDataValueReferenceFor.cs new file mode 100644 index 0000000000..e0d5e4bad1 --- /dev/null +++ b/src/Umbraco.Core/PropertyEditors/IDataValueReferenceFor.cs @@ -0,0 +1,18 @@ +namespace Umbraco.Core.PropertyEditors +{ + public interface IDataValueReferenceFor + { + /// + /// 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/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index c6545a4f8b..98871fbf5c 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -281,9 +281,10 @@ - - + + + diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs index 141331d4f4..9b99aa9b4c 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs @@ -40,7 +40,7 @@ namespace Umbraco.Tests.Persistence.Repositories 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 DataValueReferenceCollection(Enumerable.Empty()); + var dataValueReferences = new DataValueReferenceForCollection(Enumerable.Empty()); var repository = new DocumentRepository(scopeAccessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferences); return repository; } diff --git a/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs index 76d52fb844..42d2d13c6e 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs @@ -73,7 +73,7 @@ namespace Umbraco.Tests.Persistence.Repositories 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 DataValueReferenceCollection(Enumerable.Empty()); + var dataValueReferences = new DataValueReferenceForCollection(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 99adcd63e3..6d78601bba 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs @@ -31,7 +31,7 @@ namespace Umbraco.Tests.Persistence.Repositories 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 DataValueReferenceCollection(Enumerable.Empty()); + var dataValueReferences = new DataValueReferenceForCollection(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/MediaRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs index c7503671b7..886d0c5ecf 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs @@ -46,7 +46,7 @@ namespace Umbraco.Tests.Persistence.Repositories 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 DataValueReferenceCollection(Enumerable.Empty()); + var dataValueReferences = new DataValueReferenceForCollection(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 b9d034bd12..d3150623c2 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs @@ -40,7 +40,7 @@ namespace Umbraco.Tests.Persistence.Repositories 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 DataValueReferenceCollection(Enumerable.Empty()); + var dataValueReferences = new DataValueReferenceForCollection(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 b69adcbb67..89f7c39abc 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs @@ -315,7 +315,7 @@ namespace Umbraco.Tests.Persistence.Repositories 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 DataValueReferenceCollection(Enumerable.Empty()); + var dataValueReferences = new DataValueReferenceForCollection(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/TagRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs index 7f356897af..a8a263043d 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs @@ -964,7 +964,7 @@ namespace Umbraco.Tests.Persistence.Repositories 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 DataValueReferenceCollection(Enumerable.Empty()); + var dataValueReferences = new DataValueReferenceForCollection(Enumerable.Empty()); var repository = new DocumentRepository(accessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferences); return repository; } @@ -981,7 +981,7 @@ namespace Umbraco.Tests.Persistence.Repositories 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 DataValueReferenceCollection(Enumerable.Empty()); + var dataValueReferences = new DataValueReferenceForCollection(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 14aa9d8cf2..7caf0ef934 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs @@ -246,7 +246,7 @@ namespace Umbraco.Tests.Persistence.Repositories 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 DataValueReferenceCollection(Enumerable.Empty()); + var dataValueReferences = new DataValueReferenceForCollection(Enumerable.Empty()); var contentRepo = new DocumentRepository(ScopeProvider, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferences); var contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage2", "Textpage"); diff --git a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs index fead39b6cb..b632699bfd 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs @@ -35,7 +35,7 @@ namespace Umbraco.Tests.Persistence.Repositories 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 DataValueReferenceCollection(Enumerable.Empty()); + var dataValueReferences = new DataValueReferenceForCollection(Enumerable.Empty()); var repository = new MediaRepository(accessor, AppCaches, Mock.Of(), mediaTypeRepository, tagRepository, Mock.Of(), relationRepository, relationTypeRepository, propertyEditors, dataValueReferences); return repository; } @@ -58,7 +58,7 @@ namespace Umbraco.Tests.Persistence.Repositories 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 DataValueReferenceCollection(Enumerable.Empty()); + var dataValueReferences = new DataValueReferenceForCollection(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/Services/ContentServicePerformanceTest.cs b/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs index d0f4ff95b3..dd561cb3a8 100644 --- a/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs +++ b/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs @@ -52,7 +52,7 @@ namespace Umbraco.Tests.Services 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 DataValueReferenceCollection(Enumerable.Empty()); + var dataValueReferences = new DataValueReferenceForCollection(Enumerable.Empty()); var repository = new DocumentRepository(accessor, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferences); return repository; } diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index cb097af2ef..70c51c5baa 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -3211,7 +3211,7 @@ namespace Umbraco.Tests.Services 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 DataValueReferenceCollection(Enumerable.Empty()); + var dataValueReferences = new DataValueReferenceForCollection(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.Web/Composing/Current.cs b/src/Umbraco.Web/Composing/Current.cs index 1363d60b8a..2dd82d9a4a 100644 --- a/src/Umbraco.Web/Composing/Current.cs +++ b/src/Umbraco.Web/Composing/Current.cs @@ -182,7 +182,7 @@ namespace Umbraco.Web.Composing public static DataEditorCollection DataEditors => CoreCurrent.DataEditors; - public static DataValueReferenceCollection DataValueReferences => CoreCurrent.DataValueReferences; + public static DataValueReferenceForCollection DataValueReferenceFors => CoreCurrent.DataValueReferenceFors; public static PropertyEditorCollection PropertyEditors => CoreCurrent.PropertyEditors; diff --git a/src/Umbraco.Web/PropertyEditors/ContentPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/ContentPickerPropertyEditor.cs index 2f45d98fa1..683f1a05c3 100644 --- a/src/Umbraco.Web/PropertyEditors/ContentPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ContentPickerPropertyEditor.cs @@ -29,11 +29,21 @@ namespace Umbraco.Web.PropertyEditors protected override IDataValueEditor CreateValueEditor() => new ContentPickerPropertyValueEditor(Attribute); - internal class ContentPickerPropertyValueEditor : DataValueEditor + 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 16dffb3b10..da4264be28 100644 --- a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs @@ -54,7 +54,7 @@ namespace Umbraco.Web.PropertyEditors protected override IConfigurationEditor CreateConfigurationEditor() => new GridConfigurationEditor(); - internal class GridPropertyValueEditor : DataValueEditor + internal class GridPropertyValueEditor : DataValueEditor, IDataValueReference { private readonly IUmbracoContextAccessor _umbracoContextAccessor; private readonly HtmlImageSourceParser _imageSourceParser; @@ -156,6 +156,25 @@ namespace Umbraco.Web.PropertyEditors return grid; } + + /// + /// Resolve references from values + /// + /// + /// + public IEnumerable GetReferences(object value) + { + var rawJson = value == null ? string.Empty : value is string str ? str : value.ToString(); + 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 6416fa3342..ece210b9d1 100644 --- a/src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs @@ -33,11 +33,21 @@ namespace Umbraco.Web.PropertyEditors protected override IDataValueEditor CreateValueEditor() => new MediaPickerPropertyValueEditor(Attribute); - internal class MediaPickerPropertyValueEditor : DataValueEditor + 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; + + if (Udi.TryParse(asString, 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 d7a7a2ed59..fd7f735e68 100644 --- a/src/Umbraco.Web/PropertyEditors/MultiNodeTreePickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MultiNodeTreePickerPropertyEditor.cs @@ -23,12 +23,22 @@ namespace Umbraco.Web.PropertyEditors protected override IDataValueEditor CreateValueEditor() => new MultiNodeTreePickerPropertyValueEditor(Attribute); - public class MultiNodeTreePickerPropertyValueEditor : DataValueEditor + 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/MultiUrlPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MultiUrlPickerPropertyEditor.cs index 95ac809576..e77ce3a993 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 { @@ -15,7 +18,7 @@ namespace Umbraco.Web.PropertyEditors ValueType = ValueTypes.Json, Group = Constants.PropertyEditors.Groups.Pickers, Icon = "icon-link")] - public class MultiUrlPickerPropertyEditor : DataEditor + public class MultiUrlPickerPropertyEditor : DataEditor, IDataValueReference { private readonly IEntityService _entityService; private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; @@ -25,6 +28,18 @@ namespace Umbraco.Web.PropertyEditors _entityService = entityService ?? throw new ArgumentNullException(nameof(entityService)); _publishedSnapshotAccessor = publishedSnapshotAccessor ?? throw new ArgumentNullException(nameof(publishedSnapshotAccessor)); } + + 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); + } protected override IConfigurationEditor CreateConfigurationEditor() => new MultiUrlPickerConfigurationEditor(); diff --git a/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs index 865b583624..f3e391aeab 100644 --- a/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs @@ -54,7 +54,7 @@ namespace Umbraco.Web.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 IDataTypeService _dataTypeService; @@ -228,6 +228,31 @@ namespace Umbraco.Web.PropertyEditors 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 diff --git a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs index 96b1d87205..427e36b37a 100644 --- a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs @@ -57,7 +57,7 @@ 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 IUmbracoContextAccessor _umbracoContextAccessor; private readonly HtmlImageSourceParser _imageSourceParser; @@ -130,6 +130,24 @@ namespace Umbraco.Web.PropertyEditors 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/ValueReferences/ContentPickerPropertyValueReferences.cs b/src/Umbraco.Web/PropertyEditors/ValueReferences/ContentPickerPropertyValueReferences.cs deleted file mode 100644 index 08405b6d66..0000000000 --- a/src/Umbraco.Web/PropertyEditors/ValueReferences/ContentPickerPropertyValueReferences.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Collections.Generic; -using Umbraco.Core; -using Umbraco.Core.Models.Editors; -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Web.PropertyEditors.ValueReferences -{ - public class ContentPickerPropertyValueReferences : IDataValueReference - { - 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); - } - - public bool IsForEditor(IDataEditor dataEditor) => dataEditor.Alias.InvariantEquals(Constants.PropertyEditors.Aliases.ContentPicker); - } -} diff --git a/src/Umbraco.Web/PropertyEditors/ValueReferences/GridPropertyValueReferences.cs b/src/Umbraco.Web/PropertyEditors/ValueReferences/GridPropertyValueReferences.cs deleted file mode 100644 index 85acff7ca3..0000000000 --- a/src/Umbraco.Web/PropertyEditors/ValueReferences/GridPropertyValueReferences.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core; -using Umbraco.Core.Models.Editors; -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Web.PropertyEditors.ValueReferences -{ - public class GridPropertyValueReferences : IDataValueReference - { - - /// - /// Resolve references from values - /// - /// - /// - public IEnumerable GetReferences(object value) - { - return Enumerable.Empty(); - - //var rawJson = value == null ? string.Empty : value is string str ? str : value.ToString(); - - ////TODO FIX SQUIGLES - //GridPropertyEditor.GridPropertyValueEditor.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; - } - - public bool IsForEditor(IDataEditor dataEditor) => dataEditor.Alias.InvariantEquals(Constants.PropertyEditors.Aliases.Grid); - } -} diff --git a/src/Umbraco.Web/PropertyEditors/ValueReferences/MediaPickerPropertyValueReferences.cs b/src/Umbraco.Web/PropertyEditors/ValueReferences/MediaPickerPropertyValueReferences.cs deleted file mode 100644 index ecd46b7ea5..0000000000 --- a/src/Umbraco.Web/PropertyEditors/ValueReferences/MediaPickerPropertyValueReferences.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Collections.Generic; -using Umbraco.Core; -using Umbraco.Core.Models.Editors; -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Web.PropertyEditors.ValueReferences -{ - public class MediaPickerPropertyValueReferences : IDataValueReference - { - 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); - } - - public bool IsForEditor(IDataEditor dataEditor) => dataEditor.Alias.InvariantEquals(Constants.PropertyEditors.Aliases.MediaPicker); - } -} diff --git a/src/Umbraco.Web/PropertyEditors/ValueReferences/MultiNodeTreePickerPropertyValueReferences.cs b/src/Umbraco.Web/PropertyEditors/ValueReferences/MultiNodeTreePickerPropertyValueReferences.cs deleted file mode 100644 index 4413f75080..0000000000 --- a/src/Umbraco.Web/PropertyEditors/ValueReferences/MultiNodeTreePickerPropertyValueReferences.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Collections.Generic; -using Umbraco.Core; -using Umbraco.Core.Models.Editors; -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Web.PropertyEditors.ValueReferences -{ - public class MultiNodeTreePickerPropertyValueReferences : IDataValueReference - { - 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); - - } - - public bool IsForEditor(IDataEditor dataEditor) => dataEditor.Alias.InvariantEquals(Constants.PropertyEditors.Aliases.MultiNodeTreePicker); - } -} diff --git a/src/Umbraco.Web/PropertyEditors/ValueReferences/MultiUrlPickerValueReferences.cs b/src/Umbraco.Web/PropertyEditors/ValueReferences/MultiUrlPickerValueReferences.cs deleted file mode 100644 index 5d8dfd4f39..0000000000 --- a/src/Umbraco.Web/PropertyEditors/ValueReferences/MultiUrlPickerValueReferences.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Newtonsoft.Json; -using System.Collections.Generic; -using Umbraco.Core; -using Umbraco.Core.Models.Editors; -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Web.PropertyEditors.ValueReferences -{ - public class MultiUrlPickerValueReferences : IDataValueReference - { - 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); - } - - public bool IsForEditor(IDataEditor dataEditor) => dataEditor.Alias.InvariantEquals(Constants.PropertyEditors.Aliases.MultiUrlPicker); - - } -} diff --git a/src/Umbraco.Web/PropertyEditors/ValueReferences/NestedContentPropertyValueReferences.cs b/src/Umbraco.Web/PropertyEditors/ValueReferences/NestedContentPropertyValueReferences.cs deleted file mode 100644 index a872dde1c7..0000000000 --- a/src/Umbraco.Web/PropertyEditors/ValueReferences/NestedContentPropertyValueReferences.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System.Collections.Generic; -using Umbraco.Core; -using Umbraco.Core.Models.Editors; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Services; -using static Umbraco.Web.PropertyEditors.NestedContentPropertyEditor; - -namespace Umbraco.Web.PropertyEditors.ValueReferences -{ - public class NestedContentPropertyValueReferences : IDataValueReference - { - private PropertyEditorCollection _propertyEditors; - private NestedContentValues _nestedContentValues; - - //ARGH LightInject moaning at me - public NestedContentPropertyValueReferences(PropertyEditorCollection propertyEditors, IContentTypeService contentTypeService) - { - _propertyEditors = propertyEditors; - _nestedContentValues = new NestedContentValues(contentTypeService); - } - - 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; - } - - public bool IsForEditor(IDataEditor dataEditor) => dataEditor.Alias.InvariantEquals(Constants.PropertyEditors.Aliases.NestedContent); - } -} diff --git a/src/Umbraco.Web/PropertyEditors/ValueReferences/RichTextPropertyValueReferences.cs b/src/Umbraco.Web/PropertyEditors/ValueReferences/RichTextPropertyValueReferences.cs deleted file mode 100644 index 744b0dc27c..0000000000 --- a/src/Umbraco.Web/PropertyEditors/ValueReferences/RichTextPropertyValueReferences.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System.Collections.Generic; -using Umbraco.Core; -using Umbraco.Core.Models.Editors; -using Umbraco.Core.PropertyEditors; -using Umbraco.Web.Templates; - -namespace Umbraco.Web.PropertyEditors.ValueReferences -{ - public class RichTextPropertyValueReferences : IDataValueReference - { - private HtmlImageSourceParser _imageSourceParser; - private HtmlLocalLinkParser _localLinkParser; - - public RichTextPropertyValueReferences(HtmlImageSourceParser imageSourceParser, HtmlLocalLinkParser localLinkParser) - { - _imageSourceParser = imageSourceParser; - _localLinkParser = localLinkParser; - } - - /// - /// 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 - } - - public bool IsForEditor(IDataEditor dataEditor) => dataEditor.Alias.InvariantEquals(Constants.PropertyEditors.Aliases.TinyMce); - } -} diff --git a/src/Umbraco.Web/Runtime/WebInitialComposer.cs b/src/Umbraco.Web/Runtime/WebInitialComposer.cs index 295a31c461..c1eea60517 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,13 +34,10 @@ 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 static Umbraco.Web.PropertyEditors.GridPropertyEditor; -using Umbraco.Web.PropertyEditors.ValueReferences; namespace Umbraco.Web.Runtime { @@ -279,19 +273,8 @@ namespace Umbraco.Web.Runtime // Used to determine if a datatype/editor should be storing/tracking // references to media item/s - composition.DataValueReferences() - .Append() - - // TODO: Unsure how to call other ValueReference in collection - // When looking at grid item cells for RTE or media found - //.Append() - .Append() - .Append() - .Append() - - // TODO: LightInject problem - //.Append() - .Append(); + composition.DataValueReferenceFors(); + //WB:TODO Try me out // replace with web implementation composition.RegisterUnique(); diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 22680898af..5eca5ad7fe 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -235,13 +235,6 @@ - - - - - - - From 958eb822137df9752a0ff49ed46a74310b66ee53 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 2 Dec 2019 15:23:56 +0000 Subject: [PATCH 212/548] Move the GetReferences about (back to where it was) --- .../MultiUrlPickerPropertyEditor.cs | 14 +------------- .../PropertyEditors/MultiUrlPickerValueEditor.cs | 14 +++++++++++++- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Umbraco.Web/PropertyEditors/MultiUrlPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MultiUrlPickerPropertyEditor.cs index e77ce3a993..8af2d98018 100644 --- a/src/Umbraco.Web/PropertyEditors/MultiUrlPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MultiUrlPickerPropertyEditor.cs @@ -18,7 +18,7 @@ namespace Umbraco.Web.PropertyEditors ValueType = ValueTypes.Json, Group = Constants.PropertyEditors.Groups.Pickers, Icon = "icon-link")] - public class MultiUrlPickerPropertyEditor : DataEditor, IDataValueReference + public class MultiUrlPickerPropertyEditor : DataEditor { private readonly IEntityService _entityService; private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; @@ -29,18 +29,6 @@ namespace Umbraco.Web.PropertyEditors _publishedSnapshotAccessor = publishedSnapshotAccessor ?? throw new ArgumentNullException(nameof(publishedSnapshotAccessor)); } - 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); - } - 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 de641f69a3..ff2ac28986 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; @@ -156,6 +156,18 @@ namespace Umbraco.Web.PropertyEditors return base.FromEditor(editorValue, currentValue); } + 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); + } + [DataContract] internal class LinkDto { From 1b84051e8f19bc0135a2d74f8c12ebee534114aa Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 2 Dec 2019 15:24:50 +0000 Subject: [PATCH 213/548] Move DataValueReferceFors collection from Umbraco.Web to Umbraco.Core composer in order to see if that helps the Unit Test LightInject issue --- src/Umbraco.Core/Runtime/CoreInitialComposer.cs | 4 ++++ src/Umbraco.Web/Runtime/WebInitialComposer.cs | 6 +----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Core/Runtime/CoreInitialComposer.cs b/src/Umbraco.Core/Runtime/CoreInitialComposer.cs index 94d2b68121..be8a920988 100644 --- a/src/Umbraco.Core/Runtime/CoreInitialComposer.cs +++ b/src/Umbraco.Core/Runtime/CoreInitialComposer.cs @@ -75,6 +75,10 @@ namespace Umbraco.Core.Runtime composition.RegisterUnique(); composition.RegisterUnique(); + // Used to determine if a datatype/editor should be storing/tracking + // references to media item/s + composition.DataValueReferenceFors(); + //WB:TODO Try me out & overide/add to an existing list // register a server registrar, by default it's the db registrar composition.RegisterUnique(f => diff --git a/src/Umbraco.Web/Runtime/WebInitialComposer.cs b/src/Umbraco.Web/Runtime/WebInitialComposer.cs index c1eea60517..28f365fc60 100644 --- a/src/Umbraco.Web/Runtime/WebInitialComposer.cs +++ b/src/Umbraco.Web/Runtime/WebInitialComposer.cs @@ -177,7 +177,7 @@ namespace Umbraco.Web.Runtime .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.FilteredControllerFactory() @@ -271,10 +271,6 @@ namespace Umbraco.Web.Runtime .Append() .Append(); - // Used to determine if a datatype/editor should be storing/tracking - // references to media item/s - composition.DataValueReferenceFors(); - //WB:TODO Try me out // replace with web implementation composition.RegisterUnique(); From e1e5ac44cc8d2e147d971da7176b514dd70bd38f Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 2 Dec 2019 15:35:13 +0000 Subject: [PATCH 214/548] Something gone funky/awol with my commits - remove dupe code :S --- .../PropertyEditors/MultiUrlPickerValueEditor.cs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/Umbraco.Web/PropertyEditors/MultiUrlPickerValueEditor.cs b/src/Umbraco.Web/PropertyEditors/MultiUrlPickerValueEditor.cs index ff2ac28986..853e995ed8 100644 --- a/src/Umbraco.Web/PropertyEditors/MultiUrlPickerValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MultiUrlPickerValueEditor.cs @@ -156,18 +156,6 @@ namespace Umbraco.Web.PropertyEditors return base.FromEditor(editorValue, currentValue); } - 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); - } - [DataContract] internal class LinkDto { From 5616dcb807eccfed372acba959ebcbeecff6ee2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 3 Dec 2019 09:28:41 +0100 Subject: [PATCH 215/548] moved hideLabel to control-header --- .../src/views/components/property/umb-property.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html b/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html index 46660fc685..c2f9ceebc4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html @@ -6,12 +6,12 @@
-
+
{{inheritsFrom}} -
-
From 72054d7fe2a67e466c343a0c44ad4cb2105808c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 16 Dec 2019 15:08:00 +0100 Subject: [PATCH 263/548] remove outcommented code --- src/Umbraco.Web.UI.Client/src/preview/preview.controller.js | 5 ----- 1 file changed, 5 deletions(-) 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 40cfc18ab7..fa76d3b79b 100644 --- a/src/Umbraco.Web.UI.Client/src/preview/preview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/preview/preview.controller.js @@ -211,11 +211,6 @@ var app = angular.module("umbraco.preview", ['umbraco.resources', 'umbraco.servi setPageUrl(); } }; - /* - $scope.isCurrentCulture = function(culture) { - return $location.search().culture === culture; - } - */ $scope.registerCulture = function(iso, title, icon) { var cultureObject = {iso: iso, title: title, icon: icon}; cultures.push(cultureObject); From e215d19f968f09b12af15e7ded43cf39ae431062 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 16 Dec 2019 16:31:04 +0100 Subject: [PATCH 264/548] only show languages if we have any + select default if none is selected + right align dropdown menu --- .../src/less/canvas-designer.less | 5 +++-- .../src/preview/preview.controller.js | 13 +++++++++--- .../Umbraco/Views/Preview/Index.cshtml | 20 +++++++++---------- 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less b/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less index 7fe08a6feb..7135692ae8 100644 --- a/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less +++ b/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less @@ -126,6 +126,7 @@ body { .preview-menu-option { + position: relative; display: inline-block; > .menu-bar__button { @@ -136,11 +137,11 @@ body { display:none; position: absolute; + right: 0; bottom: 100%; - float: left; min-width: 200px; - border-radius: 3px 3px 3px 0; + border-radius: 3px 3px 0 3px; overflow: hidden; background-color: @blueExtraDark; 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 fa76d3b79b..dc40338d01 100644 --- a/src/Umbraco.Web.UI.Client/src/preview/preview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/preview/preview.controller.js @@ -189,7 +189,7 @@ var app = angular.module("umbraco.preview", ['umbraco.resources', 'umbraco.servi $scope.frameLoaded = true; configureSignalR(iframe); - $scope.currentCultureIso = $location.search().culture; + $scope.currentCultureIso = $location.search().culture || null; }; /*****************************************************************************/ @@ -211,12 +211,19 @@ var app = angular.module("umbraco.preview", ['umbraco.resources', 'umbraco.servi setPageUrl(); } }; - $scope.registerCulture = function(iso, title, icon) { - var cultureObject = {iso: iso, title: title, icon: icon}; + $scope.registerCulture = function(iso, title, isDefault) { + var cultureObject = {iso: iso, title: title, isDefault: isDefault}; cultures.push(cultureObject); } $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) { + return culture.isDefault === true; + }) + return; + } $scope.currentCulture = cultures.find(function(culture) { return culture.iso === $scope.currentCultureIso; }) diff --git a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml index 9b9fe37143..4b7bcaee87 100644 --- a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml @@ -50,22 +50,20 @@
- -
- - + } From 9e9a2d8379877961e2c0563b91286a4ca4f3f3d3 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 17 Dec 2019 10:38:03 +1100 Subject: [PATCH 265/548] Adds TestData project with an endpoint to create content/media hieararchy for testing --- .../Properties/AssemblyInfo.cs | 36 +++ src/Umbraco.TestData/Umbraco.TestData.csproj | 69 +++++ .../UmbracoTestDataController.cs | 269 ++++++++++++++++++ src/Umbraco.TestData/readme.md | 4 + src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 6 + src/umbraco.sln | 7 + 6 files changed, 391 insertions(+) create mode 100644 src/Umbraco.TestData/Properties/AssemblyInfo.cs create mode 100644 src/Umbraco.TestData/Umbraco.TestData.csproj create mode 100644 src/Umbraco.TestData/UmbracoTestDataController.cs create mode 100644 src/Umbraco.TestData/readme.md 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/Umbraco.TestData.csproj b/src/Umbraco.TestData/Umbraco.TestData.csproj new file mode 100644 index 0000000000..b0d6b5677b --- /dev/null +++ b/src/Umbraco.TestData/Umbraco.TestData.csproj @@ -0,0 +1,69 @@ + + + + + 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..8912a28940 --- /dev/null +++ b/src/Umbraco.TestData/UmbracoTestDataController.cs @@ -0,0 +1,269 @@ +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 const int IdealPerBranch = 5; + 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)> creator) + where T: class, IContentBase + { + yield return parent.GetUdi(); + + var totalDescendants = count - 1; + int perLevel = totalDescendants / depth; + var branchCount = perLevel / IdealPerBranch; + var perBranch = perLevel / branchCount; + + var currentPerBranchCount = 0; + + T lastParent = null; + + for (int i = 0; i < count; i++) + { + var created = creator(parent); + var contentItem = created.content; + + yield return contentItem.GetUdi(); + + currentPerBranchCount++; + + if (contentItem.Level < depth) + { + // not at max depth, create below + lastParent = parent; + parent = created.container(); + currentPerBranchCount = 0; + } + else if (currentPerBranchCount == perBranch) + { + parent = lastParent; + currentPerBranchCount = 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(); + 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(), parent, 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..78c08e0957 --- /dev/null +++ b/src/Umbraco.TestData/readme.md @@ -0,0 +1,4 @@ +## 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. diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 43130f34a3..b18856ad2d 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -115,6 +115,12 @@ + + + {fb5676ed-7a69-492c-b802-e7b24144c0fc} + Umbraco.TestData + + {31785bc3-256c-4613-b2f5-a1b0bdded8c1} diff --git a/src/umbraco.sln b/src/umbraco.sln index 938532beb0..0c5f02c7bd 100644 --- a/src/umbraco.sln +++ b/src/umbraco.sln @@ -102,6 +102,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "IssueTemplates", "IssueTemp ..\.github\ISSUE_TEMPLATE\5_Security_issue.md = ..\.github\ISSUE_TEMPLATE\5_Security_issue.md EndProjectSection 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 @@ -134,6 +136,10 @@ Global {3A33ADC9-C6C0-4DB1-A613-A9AF0210DF3D}.Debug|Any CPU.Build.0 = Debug|Any CPU {3A33ADC9-C6C0-4DB1-A613-A9AF0210DF3D}.Release|Any CPU.ActiveCfg = Release|Any CPU {3A33ADC9-C6C0-4DB1-A613-A9AF0210DF3D}.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 @@ -146,6 +152,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} From 292a40194fccd8e3cfef04213f8cf49321013cea Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 17 Dec 2019 16:46:02 +1100 Subject: [PATCH 266/548] updates readme, adjusts controller --- .../UmbracoTestDataController.cs | 48 +++++++++++-------- src/Umbraco.TestData/readme.md | 45 +++++++++++++++++ 2 files changed, 74 insertions(+), 19 deletions(-) diff --git a/src/Umbraco.TestData/UmbracoTestDataController.cs b/src/Umbraco.TestData/UmbracoTestDataController.cs index 8912a28940..f5ded2a754 100644 --- a/src/Umbraco.TestData/UmbracoTestDataController.cs +++ b/src/Umbraco.TestData/UmbracoTestDataController.cs @@ -28,7 +28,6 @@ namespace Umbraco.TestData private const string MediaPickerDataTypeName = "UmbracoTestDataContent.MediaPicker"; private const string TextDataTypeName = "UmbracoTestDataContent.Text"; private const string TestDataContentTypeAlias = "umbTestDataContent"; - private const int IdealPerBranch = 5; private readonly IScopeProvider _scopeProvider; private readonly PropertyEditorCollection _propertyEditors; @@ -101,48 +100,59 @@ namespace Umbraco.TestData /// /// /// - /// + /// /// 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)> creator) + 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; - int perLevel = totalDescendants / depth; - var branchCount = perLevel / IdealPerBranch; - var perBranch = perLevel / branchCount; + var perLevel = Math.Ceiling(totalDescendants / (double)depth); + var perBranch = Math.Ceiling(perLevel / depth); - var currentPerBranchCount = 0; + var tracked = new Stack<(T parent, int childCount)>(); - T lastParent = null; + var currChildCount = 0; for (int i = 0; i < count; i++) { - var created = creator(parent); + var created = create(parent); var contentItem = created.content; yield return contentItem.GetUdi(); - currentPerBranchCount++; + currChildCount++; - if (contentItem.Level < depth) + 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 - lastParent = parent; parent = created.container(); - currentPerBranchCount = 0; - } - else if (currentPerBranchCount == perBranch) - { - parent = lastParent; - currentPerBranchCount = 0; + + currChildCount = 0; } + } } @@ -167,7 +177,7 @@ namespace Umbraco.TestData return (media, () => { // create a folder to contain child media - var container = Services.MediaService.CreateMediaWithIdentity(faker.Commerce.Department(), parent, Constants.Conventions.MediaTypes.Folder); + var container = Services.MediaService.CreateMediaWithIdentity(faker.Commerce.Department(), currParent, Constants.Conventions.MediaTypes.Folder); return container; }); }); diff --git a/src/Umbraco.TestData/readme.md b/src/Umbraco.TestData/readme.md index 78c08e0957..717b6aac57 100644 --- a/src/Umbraco.TestData/readme.md +++ b/src/Umbraco.TestData/readme.md @@ -2,3 +2,48 @@ 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 + +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, however when viewing a content item +that has these media items picked, the thumbnail will not show up (which is due to a bug in the CMS that will be fixed in 8.6). From 25d1c454dc10ce6bb6e9e3e9b0b5aad386492f04 Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Tue, 17 Dec 2019 16:50:54 +1100 Subject: [PATCH 267/548] Update readme.md --- src/Umbraco.TestData/readme.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Umbraco.TestData/readme.md b/src/Umbraco.TestData/readme.md index 717b6aac57..5475da1ff5 100644 --- a/src/Umbraco.TestData/readme.md +++ b/src/Umbraco.TestData/readme.md @@ -8,6 +8,8 @@ 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 From 448f3406ddec023be586374f20e00c7898669ec2 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Tue, 17 Dec 2019 08:50:09 +0100 Subject: [PATCH 268/548] Make sure MNTP config displays allowed items correctly when a start node is set --- .../prevalueeditors/treesource.controller.js | 15 ++++++++------- .../treesourcetypepicker.controller.js | 6 ++---- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treesource.controller.js b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treesource.controller.js index 828763bc1c..f9c8ae8b0e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treesource.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treesource.controller.js @@ -19,15 +19,16 @@ angular.module('umbraco') }; } - if($scope.model.value.id && $scope.model.value.type !== "member"){ - entityResource.getById($scope.model.value.id, entityType()).then(function(item){ + if($scope.model.value.id && $scope.model.value.type !== "member"){ + entityResource.getById($scope.model.value.id, entityType()).then(function(item){ populate(item); - }); + }); + } + else { + $timeout(function () { + treeSourceChanged(); + }, 100); } - - $timeout(function () { - treeSourceChanged(); - }, 100); function entityType() { var ent = "Document"; diff --git a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treesourcetypepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treesourcetypepicker.controller.js index ef781c6014..a87377c84b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treesourcetypepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treesourcetypepicker.controller.js @@ -7,7 +7,6 @@ function TreeSourceTypePickerController($scope, contentTypeResource, mediaTypeRe var allItemTypes = null; var currentItemType = null; - var initialLoad = true; function init() { vm.loading = true; @@ -86,13 +85,12 @@ function TreeSourceTypePickerController($scope, contentTypeResource, mediaTypeRe } eventsService.on("treeSourceChanged", function (e, args) { - currentItemType = args.value; // reset the model value if we changed node type (but not on the initial load) - if (!initialLoad) { + if (!!currentItemType && currentItemType !== args.value) { vm.itemTypes = []; updateModel(); } - initialLoad = false; + currentItemType = args.value; init(); }); } From 39ae2fb258c9c115744bad734b67b4876564ac1c Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Tue, 17 Dec 2019 12:30:45 +0100 Subject: [PATCH 269/548] Hide the Nested Content "add" button until the editor has loaded --- .../nestedcontent/nestedcontent.propertyeditor.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 d24d3796f3..6679ea6020 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 @@ -1,6 +1,6 @@ 
- + @@ -40,7 +40,7 @@
- + + ]]> + + + + throw + + + ashx,aspx,ascx,config,cshtml,vbhtml,asmx,air,axd,swf,xml,xhtml,html,htm,php,htaccess + + + assets/img/login.jpg + + + + + + false + + true + + false + + + + + + + + + + + + diff --git a/src/Umbraco.Web.UI/config/umbracoSettings.config b/src/Umbraco.Web.UI/config/umbracoSettings.config index a7f6e1e0f1..dc7257cda3 100644 --- a/src/Umbraco.Web.UI/config/umbracoSettings.config +++ b/src/Umbraco.Web.UI/config/umbracoSettings.config @@ -1,112 +1,206 @@ - - - - - - - - - - - - - 1 - - - - - - - - your@email.here - - - - - Preview modeClick to end
]]> - - - throw - - - ashx,aspx,ascx,config,cshtml,vbhtml,asmx,air,axd,swf,xml,xhtml,html,htm,php,htaccess - - - assets/img/login.jpg - - - - - - false - - true - - false - - - - - - - - - - - - + + + + + + + + + + + + + 1 + + + + + + + + your@email.here + + + + + + Preview mode + + … + + + Click to end + +
+ + ]]> + + + + throw + + + ashx,aspx,ascx,config,cshtml,vbhtml,asmx,air,axd,swf,xml,xhtml,html,htm,php,htaccess + + + assets/img/login.jpg + + + + + + false + + true + + false + + + + + + + + + + + + diff --git a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs index 0fd591e96b..ebe5e08f89 100644 --- a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs +++ b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs @@ -64,7 +64,8 @@ namespace Umbraco.Web var htmlBadge = String.Format(Current.Configs.Settings().Content.PreviewBadge, IOHelper.ResolveUrl(SystemDirectories.Umbraco), - Current.UmbracoContext.HttpContext.Server.UrlEncode(Current.UmbracoContext.HttpContext.Request.Path)); + Current.UmbracoContext.HttpContext.Server.UrlEncode(Current.UmbracoContext.HttpContext.Request.Path), + Current.UmbracoContext.PublishedRequest.PublishedContent.Id); return new MvcHtmlString(htmlBadge); } return new MvcHtmlString(""); diff --git a/src/Umbraco.Web/Mvc/UmbracoViewPageOfTModel.cs b/src/Umbraco.Web/Mvc/UmbracoViewPageOfTModel.cs index 5ac4037fdb..0adb654d45 100644 --- a/src/Umbraco.Web/Mvc/UmbracoViewPageOfTModel.cs +++ b/src/Umbraco.Web/Mvc/UmbracoViewPageOfTModel.cs @@ -215,7 +215,8 @@ namespace Umbraco.Web.Mvc markupToInject = string.Format(Current.Configs.Settings().Content.PreviewBadge, IOHelper.ResolveUrl(SystemDirectories.Umbraco), - Server.UrlEncode(Current.UmbracoContext.HttpContext.Request.Url?.PathAndQuery)); + Server.UrlEncode(Current.UmbracoContext.HttpContext.Request.Url?.PathAndQuery), + Current.UmbracoContext.PublishedRequest.PublishedContent.Id); } else { From 746bcb3571d144e8aeca92b6bd2d0231bf0e7e0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 17 Dec 2019 15:42:20 +0100 Subject: [PATCH 271/548] updated badge animation and position to be center aligned. --- .../config/umbracoSettings.Release.config | 80 ++++++++++++++----- .../config/umbracoSettings.config | 79 +++++++++++++----- 2 files changed, 119 insertions(+), 40 deletions(-) diff --git a/src/Umbraco.Web.UI/config/umbracoSettings.Release.config b/src/Umbraco.Web.UI/config/umbracoSettings.Release.config index dc7257cda3..7e5c9ec5f1 100644 --- a/src/Umbraco.Web.UI/config/umbracoSettings.Release.config +++ b/src/Umbraco.Web.UI/config/umbracoSettings.Release.config @@ -52,7 +52,6 @@ .umbraco-preview-badge {{ position: fixed; bottom: 0; - right: 0; display: inline-flex; background: rgba(27, 38, 79, 0.9); color: #fff; @@ -63,42 +62,83 @@ box-shadow: 0 5px 10px rgba(0, 0, 0, .2), 0 1px 2px rgba(0, 0, 0, .2); line-height: 1; pointer-events:none; - transform: translateY(40px); - animation: umbraco-preview-badge--effect 1s 100ms ease both; + left: 50%; + transform: translate(-50%, 40px); + animation: umbraco-preview-badge--effect 10s 100ms ease both; + + border-radius: 3px 3px 0 0; }} @keyframes umbraco-preview-badge--effect {{ 0% {{ - transform: translateY(40px); + transform: translate(-50%, 40px); animation-timing-function: ease-out; }} - 15% {{ - transform: translateY(-20px); + 1.5% {{ + transform: translate(-50%, -20px); animation-timing-function: ease-in; }} - 50% {{ - transform: translateY(-8px); + 5.0% {{ + transform: translate(-50%, -8px); animation-timing-function: ease-in; }} - 75% {{ - transform: translateY(-4px); + 7.5% {{ + transform: translate(-50%, -4px); animation-timing-function: ease-in; }} - 92% {{ - transform: translateY(-2px); + 9.2% {{ + transform: translate(-50%, -2px); animation-timing-function: ease-in; }} - 35%, - 65%, - 85% {{ - transform: translateY(0px); + 3.5%, + 6.5%, + 8.5% {{ + transform: translate(-50%, 0px); animation-timing-function: ease-out; }} - 97% {{ - transform: translateY(0px); + 9.7% {{ + transform: translate(-50%, 0px); animation-timing-function: ease-out; }} - 100% {{ - transform: translateY(0px); + 10.0% {{ + transform: translate(-50%, 0px); + }} + + + 60% {{ + transform: translate(-50%, 0px); + animation-timing-function: ease-out; + }} + 61.5% {{ + transform: translate(-50%, -20px); + animation-timing-function: ease-in; + }} + 65.0% {{ + transform: translate(-50%, -8px); + animation-timing-function: ease-in; + }} + 67.5% {{ + transform: translate(-50%, -4px); + animation-timing-function: ease-in; + }} + 69.2% {{ + transform: translate(-50%, -2px); + animation-timing-function: ease-in; + }} + 63.5%, + 66.5%, + 68.5% {{ + transform: translate(-50%, 0px); + animation-timing-function: ease-out; + }} + 69.7% {{ + transform: translate(-50%, 0px); + animation-timing-function: ease-out; + }} + 70.0% {{ + transform: translate(-50%, 0px); + }} + 100.0% {{ + transform: translate(-50%, 0px); }} }} .umbraco-preview-badge__header {{ diff --git a/src/Umbraco.Web.UI/config/umbracoSettings.config b/src/Umbraco.Web.UI/config/umbracoSettings.config index dc7257cda3..fa998a9856 100644 --- a/src/Umbraco.Web.UI/config/umbracoSettings.config +++ b/src/Umbraco.Web.UI/config/umbracoSettings.config @@ -52,7 +52,6 @@ .umbraco-preview-badge {{ position: fixed; bottom: 0; - right: 0; display: inline-flex; background: rgba(27, 38, 79, 0.9); color: #fff; @@ -63,42 +62,82 @@ box-shadow: 0 5px 10px rgba(0, 0, 0, .2), 0 1px 2px rgba(0, 0, 0, .2); line-height: 1; pointer-events:none; - transform: translateY(40px); - animation: umbraco-preview-badge--effect 1s 100ms ease both; + left: 50%; + transform: translate(-50%, 40px); + animation: umbraco-preview-badge--effect 10s 100ms ease both; + border-radius: 3px 3px 0 0; }} @keyframes umbraco-preview-badge--effect {{ 0% {{ - transform: translateY(40px); + transform: translate(-50%, 40px); animation-timing-function: ease-out; }} - 15% {{ - transform: translateY(-20px); + 1.5% {{ + transform: translate(-50%, -20px); animation-timing-function: ease-in; }} - 50% {{ - transform: translateY(-8px); + 5.0% {{ + transform: translate(-50%, -8px); animation-timing-function: ease-in; }} - 75% {{ - transform: translateY(-4px); + 7.5% {{ + transform: translate(-50%, -4px); animation-timing-function: ease-in; }} - 92% {{ - transform: translateY(-2px); + 9.2% {{ + transform: translate(-50%, -2px); animation-timing-function: ease-in; }} - 35%, - 65%, - 85% {{ - transform: translateY(0px); + 3.5%, + 6.5%, + 8.5% {{ + transform: translate(-50%, 0px); animation-timing-function: ease-out; }} - 97% {{ - transform: translateY(0px); + 9.7% {{ + transform: translate(-50%, 0px); animation-timing-function: ease-out; }} - 100% {{ - transform: translateY(0px); + 10.0% {{ + transform: translate(-50%, 0px); + }} + + + 60% {{ + transform: translate(-50%, 0px); + animation-timing-function: ease-out; + }} + 61.5% {{ + transform: translate(-50%, -20px); + animation-timing-function: ease-in; + }} + 65.0% {{ + transform: translate(-50%, -8px); + animation-timing-function: ease-in; + }} + 67.5% {{ + transform: translate(-50%, -4px); + animation-timing-function: ease-in; + }} + 69.2% {{ + transform: translate(-50%, -2px); + animation-timing-function: ease-in; + }} + 63.5%, + 66.5%, + 68.5% {{ + transform: translate(-50%, 0px); + animation-timing-function: ease-out; + }} + 69.7% {{ + transform: translate(-50%, 0px); + animation-timing-function: ease-out; + }} + 70.0% {{ + transform: translate(-50%, 0px); + }} + 100.0% {{ + transform: translate(-50%, 0px); }} }} .umbraco-preview-badge__header {{ From 5939d02e494d6eb8bba22411459f355bef9c17f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 17 Dec 2019 16:19:48 +0100 Subject: [PATCH 272/548] remove line break --- src/Umbraco.Web.UI/config/umbracoSettings.Release.config | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Umbraco.Web.UI/config/umbracoSettings.Release.config b/src/Umbraco.Web.UI/config/umbracoSettings.Release.config index 7e5c9ec5f1..fa998a9856 100644 --- a/src/Umbraco.Web.UI/config/umbracoSettings.Release.config +++ b/src/Umbraco.Web.UI/config/umbracoSettings.Release.config @@ -65,7 +65,6 @@ left: 50%; transform: translate(-50%, 40px); animation: umbraco-preview-badge--effect 10s 100ms ease both; - border-radius: 3px 3px 0 0; }} @keyframes umbraco-preview-badge--effect {{ From 0d0f8e6beb704a18fa8a460c588d471f6d3d65a6 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 18 Dec 2019 11:08:15 +1100 Subject: [PATCH 273/548] adds debug timing --- src/Umbraco.Core/Runtime/CoreRuntime.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Runtime/CoreRuntime.cs b/src/Umbraco.Core/Runtime/CoreRuntime.cs index e9e37c871d..5ceb89d7fb 100644 --- a/src/Umbraco.Core/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Core/Runtime/CoreRuntime.cs @@ -170,7 +170,13 @@ namespace Umbraco.Core.Runtime // get composers, and compose var composerTypes = ResolveComposerTypes(typeLoader); - var enableDisableAttributes = typeLoader.GetAssemblyAttributes(typeof(EnableComposerAttribute), typeof(DisableComposerAttribute)); + + IEnumerable enableDisableAttributes; + using (ProfilingLogger.DebugDuration("Scanning enable/disable composer attributes")) + { + enableDisableAttributes = typeLoader.GetAssemblyAttributes(typeof(EnableComposerAttribute), typeof(DisableComposerAttribute)); + } + var composers = new Composers(composition, composerTypes, enableDisableAttributes, ProfilingLogger); composers.Compose(); From 63429dadb7657049576c5ab7b7397dc46216324e Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 18 Dec 2019 15:01:33 +1100 Subject: [PATCH 274/548] Adds notes, changes values to constants --- .../PropertyEditors/IDataEditorWithMediaPath.cs | 12 ++++++++++++ .../TestHelpers/Entities/MockedContentTypes.cs | 16 ++++++++-------- .../Routing/DefaultMediaUrlProvider.cs | 2 ++ 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Core/PropertyEditors/IDataEditorWithMediaPath.cs b/src/Umbraco.Core/PropertyEditors/IDataEditorWithMediaPath.cs index b16dbbaa25..e8af1b0ac3 100644 --- a/src/Umbraco.Core/PropertyEditors/IDataEditorWithMediaPath.cs +++ b/src/Umbraco.Core/PropertyEditors/IDataEditorWithMediaPath.cs @@ -1,7 +1,19 @@ namespace Umbraco.Core.PropertyEditors { + /// + /// Must be implemented by property editors that store media and return media paths + /// + /// + /// Currently there are only 2x core editors that do this: upload and image cropper. + /// It would be possible for developers to know implement their own media property editors whereas previously this was not possible. + /// public interface IDataEditorWithMediaPath { + /// + /// Returns the media path for the value stored for a property + /// + /// + /// string GetMediaPath(object value); } } diff --git a/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs b/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs index 41786a5fd7..c55467431d 100644 --- a/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs +++ b/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs @@ -420,10 +420,10 @@ namespace Umbraco.Tests.TestHelpers.Entities var contentCollection = new PropertyTypeCollection(false); contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.UploadField, ValueStorageType.Nvarchar) { Alias = Constants.Conventions.Media.File, Name = "File", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -90 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Width, Name = "Width", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -92 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Height, Name = "Height", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -92 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Bytes, Name = "Bytes", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -92 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Nvarchar) { Alias = Constants.Conventions.Media.Extension, Name = "File Extension", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -92 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Width, Name = "Width", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = Constants.System.DefaultLabelDataTypeId }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Height, Name = "Height", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = Constants.System.DefaultLabelDataTypeId }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Bytes, Name = "Bytes", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = Constants.System.DefaultLabelDataTypeId }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Nvarchar) { Alias = Constants.Conventions.Media.Extension, Name = "File Extension", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = Constants.System.DefaultLabelDataTypeId }); mediaType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Media", SortOrder = 1 }); @@ -449,10 +449,10 @@ namespace Umbraco.Tests.TestHelpers.Entities var contentCollection = new PropertyTypeCollection(false); contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.ImageCropper, ValueStorageType.Ntext) { Alias = Constants.Conventions.Media.File, Name = "File", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = 1043 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Width, Name = "Width", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -92 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Height, Name = "Height", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -92 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Bytes, Name = "Bytes", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -92 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Nvarchar) { Alias = Constants.Conventions.Media.Extension, Name = "File Extension", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -92 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Width, Name = "Width", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = Constants.System.DefaultLabelDataTypeId }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Height, Name = "Height", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = Constants.System.DefaultLabelDataTypeId }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Bytes, Name = "Bytes", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = Constants.System.DefaultLabelDataTypeId }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Nvarchar) { Alias = Constants.Conventions.Media.Extension, Name = "File Extension", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = Constants.System.DefaultLabelDataTypeId }); mediaType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Media", SortOrder = 1 }); diff --git a/src/Umbraco.Web/Routing/DefaultMediaUrlProvider.cs b/src/Umbraco.Web/Routing/DefaultMediaUrlProvider.cs index dacd314f94..89abde0576 100644 --- a/src/Umbraco.Web/Routing/DefaultMediaUrlProvider.cs +++ b/src/Umbraco.Web/Routing/DefaultMediaUrlProvider.cs @@ -27,6 +27,8 @@ namespace Umbraco.Web.Routing string propertyAlias, UrlMode mode, string culture, Uri current) { var prop = content.GetProperty(propertyAlias); + + // get the raw source value since this is what is used by IDataEditorWithMediaPath for processing var value = prop?.GetSourceValue(culture); if (value == null) { From 000a8d2c946ef64e7e9b025b4f0e1efa7f3cc6b3 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 18 Dec 2019 15:37:00 +1100 Subject: [PATCH 275/548] manually merges changes for the NC validation stuff since there was conflicts --- .../NestedContentPropertyEditor.cs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs index f3e391aeab..564630c574 100644 --- a/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs @@ -293,9 +293,19 @@ namespace Umbraco.Web.PropertyEditors if (row.PropType.Mandatory) { if (row.JsonRowValue[row.PropKey] == null) - validationResults.Add(new ValidationResult("Item " + (row.RowIndex + 1) + " '" + row.PropType.Name + "' cannot be null", new[] { row.PropKey })); + { + 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)) - validationResults.Add(new ValidationResult("Item " + (row.RowIndex + 1) + " '" + row.PropType.Name + "' cannot be empty", new[] { row.PropKey })); + { + 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 })); + } } // Check regex @@ -305,7 +315,10 @@ namespace Umbraco.Web.PropertyEditors var regex = new Regex(row.PropType.ValidationRegExp); if (!regex.IsMatch(row.JsonRowValue[row.PropKey].ToString())) { - validationResults.Add(new ValidationResult("Item " + (row.RowIndex + 1) + " '" + row.PropType.Name + "' is invalid, it does not match the correct pattern", new[] { row.PropKey })); + 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 })); } } } From 1e35aeb9cf3ef100eb7a2e25d5aca84f9e5bb439 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 18 Dec 2019 16:01:27 +1100 Subject: [PATCH 276/548] ensures .jpg ext is added to test data --- src/Umbraco.TestData/UmbracoTestDataController.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Umbraco.TestData/UmbracoTestDataController.cs b/src/Umbraco.TestData/UmbracoTestDataController.cs index f5ded2a754..402c05cc1c 100644 --- a/src/Umbraco.TestData/UmbracoTestDataController.cs +++ b/src/Umbraco.TestData/UmbracoTestDataController.cs @@ -171,6 +171,15 @@ namespace Umbraco.TestData 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); From e0531f1429cc2e1f7c812c7737653f2bc9ed58d0 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 18 Dec 2019 16:10:33 +1100 Subject: [PATCH 277/548] allows ImagesController to still return valid urls even if the uri isn't resolved to a local on-disk file --- src/Umbraco.Web/Editors/ImagesController.cs | 23 +++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web/Editors/ImagesController.cs b/src/Umbraco.Web/Editors/ImagesController.cs index b29c166765..db6706d1bb 100644 --- a/src/Umbraco.Web/Editors/ImagesController.cs +++ b/src/Umbraco.Web/Editors/ImagesController.cs @@ -60,8 +60,27 @@ 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. + } + + // TODO: When we abstract imaging for netcore, we are actually just going to be abstracting a URI builder for images, this + // is one of those places where this can be used. + + var rnd = imageLastModified.HasValue ? $"&rnd={imageLastModified:yyyyMMddHHmmss}" : string.Empty; + + response.Headers.Location = new Uri($"{imagePath}?upscale=false&width={width}&animationprocessmode=first&mode=max{rnd}", UriKind.RelativeOrAbsolute); return response; } From ddaafa2abe1c0fb1f65daf7aa692835c604eea2c Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 18 Dec 2019 16:11:31 +1100 Subject: [PATCH 278/548] updates readme --- src/Umbraco.TestData/readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.TestData/readme.md b/src/Umbraco.TestData/readme.md index 5475da1ff5..f943326303 100644 --- a/src/Umbraco.TestData/readme.md +++ b/src/Umbraco.TestData/readme.md @@ -47,5 +47,5 @@ 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, however when viewing a content item -that has these media items picked, the thumbnail will not show up (which is due to a bug in the CMS that will be fixed in 8.6). +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. From 809ab6d05fe5e74333ec1144c2045f940ee0e756 Mon Sep 17 00:00:00 2001 From: Rob Maas Date: Wed, 18 Dec 2019 10:43:41 +0100 Subject: [PATCH 279/548] Added ContentApps for Members (#6670) --- .../Manifest/ManifestContentAppFactory.cs | 6 ++ .../member/umbmembernodeinfo.directive.js | 73 +++++++++++++++++++ .../src/common/services/editor.service.js | 17 +++++ .../member/umb-member-node-info.html | 50 +++++++++++++ .../member/apps/content/content.controller.js | 11 +++ .../views/member/apps/content/content.html | 17 +++++ .../src/views/member/apps/info/info.html | 4 + .../src/views/member/edit.html | 23 ++---- .../views/member/member.edit.controller.js | 66 +++++++++++++---- .../ContentEditorContentAppFactory.cs | 11 +++ .../ContentInfoContentAppFactory.cs | 10 +++ .../ContentApps/ListViewContentAppFactory.cs | 2 + .../Models/ContentEditing/MemberDisplay.cs | 4 + .../Models/Mapping/MemberMapDefinition.cs | 1 + 14 files changed, 267 insertions(+), 28 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/common/directives/components/member/umbmembernodeinfo.directive.js create mode 100644 src/Umbraco.Web.UI.Client/src/views/components/member/umb-member-node-info.html create mode 100644 src/Umbraco.Web.UI.Client/src/views/member/apps/content/content.controller.js create mode 100644 src/Umbraco.Web.UI.Client/src/views/member/apps/content/content.html create mode 100644 src/Umbraco.Web.UI.Client/src/views/member/apps/info/info.html diff --git a/src/Umbraco.Core/Manifest/ManifestContentAppFactory.cs b/src/Umbraco.Core/Manifest/ManifestContentAppFactory.cs index 1c50a4b895..788310d7a1 100644 --- a/src/Umbraco.Core/Manifest/ManifestContentAppFactory.cs +++ b/src/Umbraco.Core/Manifest/ManifestContentAppFactory.cs @@ -19,6 +19,8 @@ namespace Umbraco.Core.Manifest // '-content/foo', // hide for content type 'foo' // '+content/*', // show for all other content types // '+media/*', // show for all media types + // '-member/foo' // hide for member type 'foo' + // '+member/*' // show for all member types // '+role/admin' // show for admin users. Role based permissions will override others. // ] // }, @@ -56,6 +58,10 @@ namespace Umbraco.Core.Manifest partA = "media"; partB = media.ContentType.Alias; break; + case IMember member: + partA = "member"; + partB = member.ContentType.Alias; + break; default: return null; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/member/umbmembernodeinfo.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/member/umbmembernodeinfo.directive.js new file mode 100644 index 0000000000..3b6a2c069a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/member/umbmembernodeinfo.directive.js @@ -0,0 +1,73 @@ +(function () { + 'use strict'; + + function MemberNodeInfoDirective($timeout, $location, eventsService, userService, dateHelper, editorService) { + + function link(scope, element, attrs, ctrl) { + + var evts = []; + + //TODO: Infinite editing is not working yet. + scope.allowChangeMemberType = false; + + function onInit() { + // make sure dates are formatted to the user's locale + formatDatesToLocal(); + } + + function formatDatesToLocal() { + // get current backoffice user and format dates + userService.getCurrentUser().then(function (currentUser) { + scope.node.createDateFormatted = dateHelper.getLocalDate(scope.node.createDate, currentUser.locale, 'LLL'); + scope.node.updateDateFormatted = dateHelper.getLocalDate(scope.node.updateDate, currentUser.locale, 'LLL'); + }); + } + + scope.openMemberType = function (memberType) { + var editor = { + id: memberType.id, + submit: function (model) { + editorService.close(); + }, + close: function () { + editorService.close(); + } + }; + editorService.memberTypeEditor(editor); + }; + + // watch for content updates - reload content when node is saved, published etc. + scope.$watch('node.updateDate', function (newValue, oldValue) { + if (!newValue) { return; } + if (newValue === oldValue) { return; } + + // Update the create and update dates + formatDatesToLocal(); + }); + + //ensure to unregister from all events! + scope.$on('$destroy', function () { + for (var e in evts) { + eventsService.unsubscribe(evts[e]); + } + }); + + onInit(); + } + + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/member/umb-member-node-info.html', + scope: { + node: "=" + }, + link: link + }; + + return directive; + } + + angular.module('umbraco.directives').directive('umbMemberNodeInfo', MemberNodeInfoDirective); + +})(); 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 3c64401933..272c2bae05 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 @@ -636,6 +636,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 diff --git a/src/Umbraco.Web.UI.Client/src/views/components/member/umb-member-node-info.html b/src/Umbraco.Web.UI.Client/src/views/components/member/umb-member-node-info.html new file mode 100644 index 0000000000..62b2052771 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/member/umb-member-node-info.html @@ -0,0 +1,50 @@ +
+
+ + + +
+
{{ group.label }}
+
+
+ + + +
+
+ +
+ +
+ + + + + + {{node.createDateFormatted}} by {{ node.owner.name }} + + + + {{node.updateDateFormatted}} + + + + + + + + +
{{ node.id }}
+ {{ node.key }} +
+ +
+
+
+ +
diff --git a/src/Umbraco.Web.UI.Client/src/views/member/apps/content/content.controller.js b/src/Umbraco.Web.UI.Client/src/views/member/apps/content/content.controller.js new file mode 100644 index 0000000000..635c536816 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/member/apps/content/content.controller.js @@ -0,0 +1,11 @@ +(function () { + "use strict"; + + function MemberAppContentController($scope) { + + var vm = this; + + } + + angular.module("umbraco").controller("Umbraco.Editors.Member.Apps.ContentController", MemberAppContentController); +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/member/apps/content/content.html b/src/Umbraco.Web.UI.Client/src/views/member/apps/content/content.html new file mode 100644 index 0000000000..29df5ba638 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/member/apps/content/content.html @@ -0,0 +1,17 @@ +
+ +
+ +
+
{{ group.label }}
+
+ +
+ + + +
+ +
+ +
diff --git a/src/Umbraco.Web.UI.Client/src/views/member/apps/info/info.html b/src/Umbraco.Web.UI.Client/src/views/member/apps/info/info.html new file mode 100644 index 0000000000..bd9cb98b64 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/member/apps/info/info.html @@ -0,0 +1,4 @@ + + diff --git a/src/Umbraco.Web.UI.Client/src/views/member/edit.html b/src/Umbraco.Web.UI.Client/src/views/member/edit.html index 58bebe1e34..3fec222350 100644 --- a/src/Umbraco.Web.UI.Client/src/views/member/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/member/edit.html @@ -14,29 +14,22 @@ hide-icon="true" hide-description="true" hide-alias="true" + navigation="content.apps" + on-select-navigation-item="appChanged(item)" show-back-button="showBack()" on-back="onBack()" editorfor="header.editorfor" setpagetitle="header.setPageTitle"> - - -
- -
-
{{ group.label }}
+ + +
+
+
- -
- - - -
-
- -
+ diff --git a/src/Umbraco.Web.UI.Client/src/views/member/member.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/member/member.edit.controller.js index 23d35b6925..3ec76deb8e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/member/member.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/member/member.edit.controller.js @@ -45,9 +45,7 @@ function MemberEditController($scope, $routeParams, $location, appState, memberR $scope.content = data; - setHeaderNameState($scope.content); - - editorState.set($scope.content); + init(); $scope.page.loading = false; @@ -59,9 +57,7 @@ function MemberEditController($scope, $routeParams, $location, appState, memberR .then(function (data) { $scope.content = data; - setHeaderNameState($scope.content); - - editorState.set($scope.content); + init(); $scope.page.loading = false; @@ -90,9 +86,7 @@ function MemberEditController($scope, $routeParams, $location, appState, memberR $scope.content = data; - setHeaderNameState($scope.content); - - editorState.set($scope.content); + init(); if (!infiniteMode) { var path = buildTreePath(data); @@ -121,11 +115,48 @@ function MemberEditController($scope, $routeParams, $location, appState, memberR } - function setHeaderNameState(content) { + function init() { + + 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. + 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) { + if (app === $scope.app) { + isAppPresent = true; + } + }); + + // 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); + } + }); + } + + } + + // if we still dont have a app, lets show the first one: + if (isAppPresent === false) { + content.apps[0].active = true; + $scope.appChanged(content.apps[0]); + } + + if (content.membershipScenario === 0) { + $scope.page.nameLocked = true; + } + + editorState.set($scope.content); - if(content.membershipScenario === 0) { - $scope.page.nameLocked = true; - } } /** Just shows a simple notification that there are client side validation issues to be fixed */ @@ -194,6 +225,15 @@ function MemberEditController($scope, $routeParams, $location, appState, memberR }; + $scope.appChanged = function (app) { + $scope.app = app; + + // setup infinite mode + if (infiniteMode) { + $scope.page.submitButtonLabelKey = "buttons_saveAndClose"; + } + } + $scope.showBack = function () { return !!listName; } diff --git a/src/Umbraco.Web/ContentApps/ContentEditorContentAppFactory.cs b/src/Umbraco.Web/ContentApps/ContentEditorContentAppFactory.cs index 8c251cacd2..add7e2f16a 100644 --- a/src/Umbraco.Web/ContentApps/ContentEditorContentAppFactory.cs +++ b/src/Umbraco.Web/ContentApps/ContentEditorContentAppFactory.cs @@ -14,6 +14,7 @@ namespace Umbraco.Web.ContentApps private ContentApp _contentApp; private ContentApp _mediaApp; + private ContentApp _memberApp; public ContentApp GetContentAppFor(object o, IEnumerable userGroups) { @@ -45,6 +46,16 @@ namespace Umbraco.Web.ContentApps case IMedia _: return null; + case IMember _: + return _memberApp ?? (_memberApp = new ContentApp + { + Alias = "umbContent", + Name = "Content", + Icon = Constants.Icons.Content, + View = "views/member/apps/content/content.html", + Weight = Weight + }); + default: throw new NotSupportedException($"Object type {o.GetType()} is not supported here."); } diff --git a/src/Umbraco.Web/ContentApps/ContentInfoContentAppFactory.cs b/src/Umbraco.Web/ContentApps/ContentInfoContentAppFactory.cs index 49be194349..fac03c43d0 100644 --- a/src/Umbraco.Web/ContentApps/ContentInfoContentAppFactory.cs +++ b/src/Umbraco.Web/ContentApps/ContentInfoContentAppFactory.cs @@ -13,6 +13,7 @@ namespace Umbraco.Web.ContentApps private ContentApp _contentApp; private ContentApp _mediaApp; + private ContentApp _memberApp; public ContentApp GetContentAppFor(object o, IEnumerable userGroups) { @@ -37,6 +38,15 @@ namespace Umbraco.Web.ContentApps View = "views/media/apps/info/info.html", Weight = Weight }); + case IMember _: + return _memberApp ?? (_memberApp = new ContentApp + { + Alias = "umbInfo", + Name = "Info", + Icon = "icon-info", + View = "views/member/apps/info/info.html", + Weight = Weight + }); default: throw new NotSupportedException($"Object type {o.GetType()} is not supported here."); diff --git a/src/Umbraco.Web/ContentApps/ListViewContentAppFactory.cs b/src/Umbraco.Web/ContentApps/ListViewContentAppFactory.cs index ae6d324e84..bf6184197f 100644 --- a/src/Umbraco.Web/ContentApps/ListViewContentAppFactory.cs +++ b/src/Umbraco.Web/ContentApps/ListViewContentAppFactory.cs @@ -45,6 +45,8 @@ namespace Umbraco.Web.ContentApps entityType = "media"; dtdId = Core.Constants.DataTypes.DefaultMediaListView; break; + case IMember member: + return null; default: throw new NotSupportedException($"Object type {o.GetType()} is not supported here."); } diff --git a/src/Umbraco.Web/Models/ContentEditing/MemberDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/MemberDisplay.cs index fd1c1ed5b8..3857731671 100644 --- a/src/Umbraco.Web/Models/ContentEditing/MemberDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/MemberDisplay.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Runtime.Serialization; using Umbraco.Core.Models; +using Umbraco.Core.Models.ContentEditing; using Umbraco.Core.Models.Membership; namespace Umbraco.Web.Models.ContentEditing @@ -15,6 +16,7 @@ namespace Umbraco.Web.Models.ContentEditing public MemberDisplay() { MemberProviderFieldMapping = new Dictionary(); + ContentApps = new List(); } [DataMember(Name = "username")] @@ -34,5 +36,7 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "fieldConfig")] public IDictionary MemberProviderFieldMapping { get; set; } + [DataMember(Name = "apps")] + public IEnumerable ContentApps { get; set; } } } diff --git a/src/Umbraco.Web/Models/Mapping/MemberMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/MemberMapDefinition.cs index 8671bfe538..851f452244 100644 --- a/src/Umbraco.Web/Models/Mapping/MemberMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/MemberMapDefinition.cs @@ -76,6 +76,7 @@ namespace Umbraco.Web.Models.Mapping // Umbraco.Code.MapAll -Trashed -IsContainer -VariesByCulture private void Map(IMember source, MemberDisplay target, MapperContext context) { + target.ContentApps = _commonMapper.GetContentApps(source); target.ContentTypeId = source.ContentType.Id; target.ContentTypeAlias = source.ContentType.Alias; target.ContentTypeName = source.ContentType.Name; From 0e9594ca2d2d5aa9460767ffd56d2956534cfa33 Mon Sep 17 00:00:00 2001 From: Malthe Petersen Date: Mon, 28 Oct 2019 18:52:24 +0100 Subject: [PATCH 280/548] fix: url segment null in preview content --- .../PublishedCache/NuCache/DataSource/DatabaseDataSource.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/PublishedCache/NuCache/DataSource/DatabaseDataSource.cs b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/DatabaseDataSource.cs index f07ea7db6b..19aab7ea65 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/DataSource/DatabaseDataSource.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/DatabaseDataSource.cs @@ -207,7 +207,8 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource VersionDate = dto.EditVersionDate, WriterId = dto.EditWriterId, Properties = nested.PropertyData, - CultureInfos = nested.CultureData + CultureInfos = nested.CultureData, + UrlSegment = nested.UrlSegment }; } } From 661ece2a4c1fee93fb8127b25f574fe232d3b5a3 Mon Sep 17 00:00:00 2001 From: Mike Masey Date: Wed, 18 Dec 2019 13:06:30 +0000 Subject: [PATCH 281/548] Improve keyboard accessibility for media library items (#6916) * improvement: add better tabbed focus state to media grid item * fix: update media grid item overlay to show on focus & change div to button --- .../src/less/components/umb-media-grid.less | 9 +++++++++ .../src/views/components/umb-media-grid.html | 4 ++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-media-grid.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-media-grid.less index 05d91de9f7..4feadc272c 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-media-grid.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-media-grid.less @@ -42,6 +42,10 @@ .umb-media-grid__item.-selectable { cursor: pointer; + + .tabbing-active &:focus { + outline: 2px solid @inputBorderTabFocus; + } } .umb-media-grid__item.-file { @@ -118,6 +122,7 @@ .umb-media-grid__item-overlay { display: flex; + width: 100%; opacity: 0; position: absolute; right: 0; @@ -137,6 +142,10 @@ &:hover { text-decoration: underline; } + + .tabbing-active &:focus { + opacity: 1; + } } .umb-media-grid__info { diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-media-grid.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-media-grid.html index 5923845539..0aa953635e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-media-grid.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-media-grid.html @@ -4,10 +4,10 @@ -
+
+
From c6afb861aaeeb46512581d853197a0d7d28e313b Mon Sep 17 00:00:00 2001 From: Laura W Date: Thu, 31 Oct 2019 22:31:00 +0000 Subject: [PATCH 282/548] Updated language discrepancies on database install steps --- src/Umbraco.Web.UI.Client/src/installer/steps/database.html | 6 +++--- .../src/installer/steps/permissionsreport.html | 6 +++--- .../src/installer/steps/version7upgradereport.html | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/database.html b/src/Umbraco.Web.UI.Client/src/installer/steps/database.html index 6b71f1db9e..fc870858cf 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/steps/database.html +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/database.html @@ -15,11 +15,11 @@
-

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

+

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

- What is the exact connectionstring we should use? + What is the exact connection string we should use?
@@ -64,7 +64,7 @@
Enter the database user name
diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/permissionsreport.html b/src/Umbraco.Web.UI.Client/src/installer/steps/permissionsreport.html index ad9b1c9cc1..0c78e211dc 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/steps/permissionsreport.html +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/permissionsreport.html @@ -1,8 +1,8 @@ 
-

Your permission settings are not ready for umbraco

+

Your permission settings are not ready for Umbraco

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

diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/version7upgradereport.html b/src/Umbraco.Web.UI.Client/src/installer/steps/version7upgradereport.html index df1e58d737..f30788e858 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/steps/version7upgradereport.html +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/version7upgradereport.html @@ -2,11 +2,11 @@

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

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

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

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

@@ -19,4 +19,4 @@

-
\ No newline at end of file +
From 578a725d7cfd9fde3984cc392fdba3f63d54033a Mon Sep 17 00:00:00 2001 From: Rachel Breeze Date: Wed, 18 Dec 2019 13:37:34 +0000 Subject: [PATCH 283/548] V8: Accessibility Changes For umbEditorHeader Directive (settings section) (#6986) * Improved accessibility of the data type edit controller, by implementing the new header directive properties for screen reader only text, and for setting the page title * Improved accessibility of the macro edit controller, by implementing the new header directive properties for screen reader only text, and for setting the page title * Improved accessibility of the relation type edit controller, by implementing the new header directive properties for screen reader only text, and for setting the page title * Added to do note for log viewer --- .../editor/umbeditorheader.directive.js | 3 +- .../datatypes/datatype.edit.controller.js | 6 +- .../src/views/datatypes/edit.html | 4 +- .../src/views/macros/edit.html | 3 +- .../views/macros/macros.edit.controller.js | 5 +- .../views/relationtypes/edit.controller.js | 4 + .../src/views/relationtypes/edit.html | 4 +- src/Umbraco.Web.UI/Umbraco/config/lang/en.xml | 4681 ++++++++-------- .../Umbraco/config/lang/en_us.xml | 4713 +++++++++-------- 9 files changed, 4721 insertions(+), 4702 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorheader.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorheader.directive.js index 6559e16206..431a05778c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorheader.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorheader.directive.js @@ -224,7 +224,8 @@ Use this directive to construct a header inside the main editor window. if (editorState.current) { //to do make work for user create/edit // to do make it work for user group create/ edit - // to make it work for language edit/create + // to do make it work for language edit/create + // to do make it work for log viewer scope.isNew = editorState.current.id === 0 || editorState.current.id === "0" || editorState.current.id === -1 || diff --git a/src/Umbraco.Web.UI.Client/src/views/datatypes/datatype.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/datatypes/datatype.edit.controller.js index 474b07d12c..66983bbc05 100644 --- a/src/Umbraco.Web.UI.Client/src/views/datatypes/datatype.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/datatypes/datatype.edit.controller.js @@ -10,7 +10,11 @@ function DataTypeEditController($scope, $routeParams, appState, navigationServic var evts = []; var vm = this; - + + vm.header = {}; + vm.header.editorfor = "visuallyHiddenTexts_newDataType"; + vm.header.setPageTitle = true; + //setup scope vars vm.page = {}; vm.page.loading = false; diff --git a/src/Umbraco.Web.UI.Client/src/views/datatypes/edit.html b/src/Umbraco.Web.UI.Client/src/views/datatypes/edit.html index 0bb2b01e31..2d481de852 100644 --- a/src/Umbraco.Web.UI.Client/src/views/datatypes/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/datatypes/edit.html @@ -8,7 +8,9 @@ hide-icon="true" hide-description="true" hide-alias="true" - navigation="vm.page.navigation"> + navigation="vm.page.navigation" + editorfor="vm.header.editorfor" + setpagetitle="vm.header.setPageTitle"> diff --git a/src/Umbraco.Web.UI.Client/src/views/macros/edit.html b/src/Umbraco.Web.UI.Client/src/views/macros/edit.html index ddb5f98781..9640419ee2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/macros/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/macros/edit.html @@ -6,7 +6,8 @@ alias="vm.macro.alias" hide-description="true" hide-icon="true" - navigation="vm.page.navigation"> + navigation="vm.page.navigation"editorfor="vm.header.editorfor" + setpagetitle="vm.header.setPageTitle"> diff --git a/src/Umbraco.Web.UI.Client/src/views/macros/macros.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/macros/macros.edit.controller.js index 272e5dd03a..3261739d36 100644 --- a/src/Umbraco.Web.UI.Client/src/views/macros/macros.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/macros/macros.edit.controller.js @@ -11,7 +11,10 @@ function MacrosEditController($scope, $q, $routeParams, macroResource, editorSta var vm = this; vm.promises = {}; - + vm.header = {}; + vm.header.editorfor = "general_macro"; + vm.header.setPageTitle = true; + vm.page = {}; vm.page.loading = false; vm.page.saveButtonState = "init"; 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 138e3e90e2..ef77086343 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 @@ -10,6 +10,10 @@ function RelationTypeEditController($scope, $routeParams, relationTypeResource, var vm = this; + vm.header = {}; + vm.header.editorfor = "relationType_tabRelationType"; + vm.header.setPageTitle = true; + vm.page = {}; vm.page.loading = false; vm.page.saveButtonState = "init"; diff --git a/src/Umbraco.Web.UI.Client/src/views/relationtypes/edit.html b/src/Umbraco.Web.UI.Client/src/views/relationtypes/edit.html index 2c86161bda..35e7aa5176 100644 --- a/src/Umbraco.Web.UI.Client/src/views/relationtypes/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/relationtypes/edit.html @@ -8,7 +8,9 @@ alias="vm.relationType.alias" hide-description="true" hide-icon="true" - navigation="vm.page.navigation"> + navigation="vm.page.navigation" + editorfor="vm.header.editorfor" + setpagetitle="vm.header.setPageTitle"> diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index c64d2d3eb0..fcfb54906d 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -1,2340 +1,2341 @@ - - - - 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 - 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) - 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 - - - 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 - 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 - 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 - - - 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 - - - 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% - - - 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 - - - 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 - ]]> - - -
+ + + + 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 + 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) + 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 + + + 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 + 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 + 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 + + + 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 + + + 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% + + + 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 + + + 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 + ]]> + + +
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 c002299767..3a55bfb0bb 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -1,2356 +1,2357 @@ - - - - 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 - 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 - Send for approval - Save list view - Schedule - Preview - Preview is disabled because there's no template assigned - Choose style - Show styles - Insert table - Generate models and close - Save and generate models - Undo - Redo - Rollback - Delete tag - Cancel - Confirm - More publishing options - - - Viewing for - Content deleted - Content unpublished - Content unpublished for languages: %0% - Content published - Content 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 - 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 child items have 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 - No content can be added for this item - 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 - Draft - Not created - Last edited - Date/time this document was edited - Remove file(s) - 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? - Are you sure you want to delete all items? - Property %0% uses editor %1% which is not supported by Nested Content. - 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 - - - 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 - Parent and destination folders cannot be the same - 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 - 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 - %0% will delete the properties and their data from the following items]]> - I understand this action will delete the properties and data based on this Data Type - - - 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 - - - Options - About - Action - Actions - Add - Alias - All - Are you sure? - Back - Back to overview - Border - by - Cancel - Cell margin - Choose - 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 - 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 color - Bold - Text color - 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 - Upgrading from 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 - - - Insufficient user permissions to publish all descendant documents - - - - - - - - Validation failed for required language '%0%'. This language was saved but not published. - 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 colors - - - 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 - - - Content - Forms - Media - Members - Packages - Settings - 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 - - This node has no child nodes to sort - - - 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 - 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 - Content published - and is visible on the website - %0% documents published and visible on the website - %0% published and visible on the website - %0% documents published for languages %1% and visible on the website - Content saved - Remember to publish to make changes visible - A schedule for publishing has been updated - %0% saved - Sent For Approval - Changes have been sent for approval - %0% changes have been sent for approval - Media saved - Media saved without any errors - Member saved - Member group saved - Stylesheet Property Saved - Stylesheet saved - Template saved - Error saving user (check log) - User Saved - User type saved - User group saved - 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 - Content variation %0% unpublished - The mandatory language '%0%' was unpublished. All languages for this content item are now 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% - Cannot publish the document since the required '%0%' is not published - Validation failed for language '%0%' - Document type was exported to file - An error occurred while exporting the document type - The release date cannot be in the past - Cannot schedule the document for publishing since the required '%0%' is not published - Cannot schedule the document for publishing since the required '%0%' has a publish date later than a non mandatory language - The expire date cannot be in the past - The expire date cannot be before the release date - - - 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 - Rich Text 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 - - - 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 - - - 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 - Value cannot be null - Value cannot be empty - Value is invalid, it does not match the correct pattern - 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% - - - Disable URL tracker - Enable URL tracker - Culture - 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 - - - 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 - ]]> - - -
+ + + + 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 + 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 + Send for approval + Save list view + Schedule + Preview + Preview is disabled because there's no template assigned + Choose style + Show styles + Insert table + Generate models and close + Save and generate models + Undo + Redo + Rollback + Delete tag + Cancel + Confirm + More publishing options + + + Viewing for + Content deleted + Content unpublished + Content unpublished for languages: %0% + Content published + Content 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 + 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 child items have 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 + No content can be added for this item + 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 + Draft + Not created + Last edited + Date/time this document was edited + Remove file(s) + 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? + Are you sure you want to delete all items? + Property %0% uses editor %1% which is not supported by Nested Content. + 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 + + + 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 + Parent and destination folders cannot be the same + 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 + 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 + %0% will delete the properties and their data from the following items]]> + I understand this action will delete the properties and data based on this Data Type + + + 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 + + + Options + About + Action + Actions + Add + Alias + All + Are you sure? + Back + Back to overview + Border + by + Cancel + Cell margin + Choose + 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 + 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 color + Bold + Text color + 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 + Upgrading from 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 + + + Insufficient user permissions to publish all descendant documents + + + + + + + + Validation failed for required language '%0%'. This language was saved but not published. + 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 colors + + + 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 + + + Content + Forms + Media + Members + Packages + Settings + 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 + + This node has no child nodes to sort + + + 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 + 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 + Content published + and is visible on the website + %0% documents published and visible on the website + %0% published and visible on the website + %0% documents published for languages %1% and visible on the website + Content saved + Remember to publish to make changes visible + A schedule for publishing has been updated + %0% saved + Sent For Approval + Changes have been sent for approval + %0% changes have been sent for approval + Media saved + Media saved without any errors + Member saved + Member group saved + Stylesheet Property Saved + Stylesheet saved + Template saved + Error saving user (check log) + User Saved + User type saved + User group saved + 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 + Content variation %0% unpublished + The mandatory language '%0%' was unpublished. All languages for this content item are now 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% + Cannot publish the document since the required '%0%' is not published + Validation failed for language '%0%' + Document type was exported to file + An error occurred while exporting the document type + The release date cannot be in the past + Cannot schedule the document for publishing since the required '%0%' is not published + Cannot schedule the document for publishing since the required '%0%' has a publish date later than a non mandatory language + The expire date cannot be in the past + The expire date cannot be before the release date + + + 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 + Rich Text 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 + + + 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 + + + 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 + Value cannot be null + Value cannot be empty + Value is invalid, it does not match the correct pattern + 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% + + + Disable URL tracker + Enable URL tracker + Culture + 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 + + + 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 + ]]> + + +
From fa26e2ee02b2cf48c0cd8d474c6da70de03cc9f4 Mon Sep 17 00:00:00 2001 From: Paul Seal Date: Sun, 17 Nov 2019 23:37:58 +0000 Subject: [PATCH 284/548] removed unused _domainService --- src/Umbraco.Web/Cache/LanguageCacheRefresher.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Umbraco.Web/Cache/LanguageCacheRefresher.cs b/src/Umbraco.Web/Cache/LanguageCacheRefresher.cs index b1fc0a9e55..7c4dab0e17 100644 --- a/src/Umbraco.Web/Cache/LanguageCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/LanguageCacheRefresher.cs @@ -25,7 +25,6 @@ namespace Umbraco.Web.Cache public static readonly Guid UniqueId = Guid.Parse("3E0F95D8-0BE5-44B8-8394-2B8750B62654"); private readonly IPublishedSnapshotService _publishedSnapshotService; - private readonly IDomainService _domainService; public override Guid RefresherUniqueId => UniqueId; From d0daec65c4f3a244aa9c5cd2a367b7b043deba0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pjengaard?= Date: Mon, 28 Oct 2019 16:23:41 +0100 Subject: [PATCH 285/548] added documentation for param startNodeId --- src/Umbraco.Web.UI.Client/src/common/services/editor.service.js | 1 + 1 file changed, 1 insertion(+) 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 272c2bae05..3c71994715 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 @@ -359,6 +359,7 @@ When building a custom infinite editor view you can use the same components as a * * @param {Object} editor rendering options * @param {Boolean} editor.multiPicker Pick one or multiple items + * @param {Int} editor.startNodeId Set the startnode of the picker (optional) * @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. * From 56b0480d4f1ed73f90b4920ccb6a0a4db3305e40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pjengaard?= Date: Tue, 17 Dec 2019 10:19:51 +0100 Subject: [PATCH 286/548] added documentation for param startNodeId (mediaPicker) --- src/Umbraco.Web.UI.Client/src/common/services/editor.service.js | 1 + 1 file changed, 1 insertion(+) 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 3c71994715..1f6418e06a 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 @@ -565,6 +565,7 @@ When building a custom infinite editor view you can use the same components as a * @description * Opens a media picker in infinite editing, the submit callback returns an array of selected media items * @param {Object} editor rendering options + * @param {Int} editor.startNodeId Set the startnode of the picker (optional) * @param {Boolean} editor.multiPicker Pick one or multiple items * @param {Boolean} editor.onlyImages Only display files that have an image file-extension * @param {Boolean} editor.disableFolderSelect Disable folder selection From c692bd997aad4a8b47744f176f4682dc3376b457 Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Fri, 6 Dec 2019 13:04:03 +0100 Subject: [PATCH 287/548] Use input-id on toggle in profiler dashboard --- .../src/views/dashboard/settings/profiler.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/profiler.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/profiler.html index 6bff0bba9b..95c6af9f79 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/profiler.html +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/profiler.html @@ -27,7 +27,7 @@
- + From 76ef9da27175a1245650b5235488a345d3a09a5a Mon Sep 17 00:00:00 2001 From: Liam Laverty Date: Wed, 4 Dec 2019 13:21:26 +0000 Subject: [PATCH 288/548] maps isSensitiveData in groupsbuilder ` property.isSensitiveData = propertyModel.isSensitiveData;` wasn't being set, which seems to have caused https://github.com/umbraco/Umbraco-CMS/issues/7270 --- .../common/directives/components/umbgroupsbuilder.directive.js | 1 + 1 file changed, 1 insertion(+) 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 3c9e300f92..a9b9cc52b1 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 @@ -554,6 +554,7 @@ 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; From a835aa06745dd0dc20d75c49451929b7e186c66a Mon Sep 17 00:00:00 2001 From: Dennis Adolfi Date: Thu, 19 Dec 2019 08:25:42 +0100 Subject: [PATCH 289/548] v8: Updated search component to get translated placeholder "Search.." and "Clear" text (#7122) * Added translation for clear with marker so I can see if it loads correcly. Installing Umbraco locally to try it out. * Added Clear translation to en.xml, en-us.xml and sv.xml. * befin translation search placeholder. Not working for some reason, investiagte. * Inject localizationService to localize placeholder. For some reason i get 503 service unavabile when I try to build.ps1. Will have to look more into this late, saving my work. * merge conflicts * 2 vs 4 spaces caused massive diff. Fixed my local copy to same space so now it should look better. * * updated after PR review. Using localize element attribute instead of adding dependency to the localizationService. Also added dansih translation. * Remove whitespace noise --- .../components/application/umb-search.html | 9 +- src/Umbraco.Web.UI/Umbraco/config/lang/da.xml | 1 + src/Umbraco.Web.UI/Umbraco/config/lang/en.xml | 3 +- .../Umbraco/config/lang/en_us.xml | 4715 +++++++++-------- src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml | 1 + 5 files changed, 2368 insertions(+), 2361 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-search.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-search.html index 332a212a0e..297ade28c0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-search.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-search.html @@ -8,10 +8,13 @@ type="text" ng-model="vm.searchQuery" ng-model-options="{ debounce: 200 }" - ng-change="vm.search(vm.searchQuery)" - placeholder="Search..." + ng-change="vm.search(vm.searchQuery)" + localize="placeholder" + placeholder="@placeholders_search" focus-when="{{vm.searchHasFocus}}" /> - +
diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml index 0deac8b50f..a74c6c6243 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml @@ -620,6 +620,7 @@ Fortryd Celle margen Vælg + Ryd Luk Luk vindue Kommentar diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index fcfb54906d..ab09c3df83 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -625,6 +625,7 @@ Cancel Cell margin Choose + Clear Close Close Window Comment @@ -2178,7 +2179,7 @@ To manage your website, simply open the Umbraco back office and start adding con Partial View Partial View Macro Member - Data type + Data type References 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 3a55bfb0bb..3ff2aae00e 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -1,2357 +1,2358 @@ - - - - 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 - 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 - Send for approval - Save list view - Schedule - Preview - Preview is disabled because there's no template assigned - Choose style - Show styles - Insert table - Generate models and close - Save and generate models - Undo - Redo - Rollback - Delete tag - Cancel - Confirm - More publishing options - - - Viewing for - Content deleted - Content unpublished - Content unpublished for languages: %0% - Content published - Content 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 - 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 child items have 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 - No content can be added for this item - 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 - Draft - Not created - Last edited - Date/time this document was edited - Remove file(s) - 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? - Are you sure you want to delete all items? - Property %0% uses editor %1% which is not supported by Nested Content. - 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 - - - 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 - Parent and destination folders cannot be the same - 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 - 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 - %0% will delete the properties and their data from the following items]]> - I understand this action will delete the properties and data based on this Data Type - - - 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 - - - Options - About - Action - Actions - Add - Alias - All - Are you sure? - Back - Back to overview - Border - by - Cancel - Cell margin - Choose - 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 - 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 color - Bold - Text color - 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 - Upgrading from 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 - - - Insufficient user permissions to publish all descendant documents - - - - - - - - Validation failed for required language '%0%'. This language was saved but not published. - 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 colors - - - 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 - - - Content - Forms - Media - Members - Packages - Settings - 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 - - This node has no child nodes to sort - - - 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 - 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 - Content published - and is visible on the website - %0% documents published and visible on the website - %0% published and visible on the website - %0% documents published for languages %1% and visible on the website - Content saved - Remember to publish to make changes visible - A schedule for publishing has been updated - %0% saved - Sent For Approval - Changes have been sent for approval - %0% changes have been sent for approval - Media saved - Media saved without any errors - Member saved - Member group saved - Stylesheet Property Saved - Stylesheet saved - Template saved - Error saving user (check log) - User Saved - User type saved - User group saved - 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 - Content variation %0% unpublished - The mandatory language '%0%' was unpublished. All languages for this content item are now 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% - Cannot publish the document since the required '%0%' is not published - Validation failed for language '%0%' - Document type was exported to file - An error occurred while exporting the document type - The release date cannot be in the past - Cannot schedule the document for publishing since the required '%0%' is not published - Cannot schedule the document for publishing since the required '%0%' has a publish date later than a non mandatory language - The expire date cannot be in the past - The expire date cannot be before the release date - - - 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 - Rich Text 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 - - - 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 - - - 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 - Value cannot be null - Value cannot be empty - Value is invalid, it does not match the correct pattern - 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% - - - Disable URL tracker - Enable URL tracker - Culture - 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 - - - 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 - ]]> - - -
+ + + + 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 + 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 + Send for approval + Save list view + Schedule + Preview + Preview is disabled because there's no template assigned + Choose style + Show styles + Insert table + Generate models and close + Save and generate models + Undo + Redo + Rollback + Delete tag + Cancel + Confirm + More publishing options + + + Viewing for + Content deleted + Content unpublished + Content unpublished for languages: %0% + Content published + Content 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 + 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 child items have 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 + No content can be added for this item + 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 + Draft + Not created + Last edited + Date/time this document was edited + Remove file(s) + 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? + Are you sure you want to delete all items? + Property %0% uses editor %1% which is not supported by Nested Content. + 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 + + + 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 + Parent and destination folders cannot be the same + 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 + 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 + %0% will delete the properties and their data from the following items]]> + I understand this action will delete the properties and data based on this Data Type + + + 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 + + + Options + 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 + 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 color + Bold + Text color + 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 + Upgrading from 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 + + + Insufficient user permissions to publish all descendant documents + + + + + + + + Validation failed for required language '%0%'. This language was saved but not published. + 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 colors + + + 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 + + + Content + Forms + Media + Members + Packages + Settings + 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 + + This node has no child nodes to sort + + + 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 + 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 + Content published + and is visible on the website + %0% documents published and visible on the website + %0% published and visible on the website + %0% documents published for languages %1% and visible on the website + Content saved + Remember to publish to make changes visible + A schedule for publishing has been updated + %0% saved + Sent For Approval + Changes have been sent for approval + %0% changes have been sent for approval + Media saved + Media saved without any errors + Member saved + Member group saved + Stylesheet Property Saved + Stylesheet saved + Template saved + Error saving user (check log) + User Saved + User type saved + User group saved + 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 + Content variation %0% unpublished + The mandatory language '%0%' was unpublished. All languages for this content item are now 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% + Cannot publish the document since the required '%0%' is not published + Validation failed for language '%0%' + Document type was exported to file + An error occurred while exporting the document type + The release date cannot be in the past + Cannot schedule the document for publishing since the required '%0%' is not published + Cannot schedule the document for publishing since the required '%0%' has a publish date later than a non mandatory language + The expire date cannot be in the past + The expire date cannot be before the release date + + + 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 + Rich Text 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 + + + 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 + + + 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 + Value cannot be null + Value cannot be empty + Value is invalid, it does not match the correct pattern + 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% + + + Disable URL tracker + Enable URL tracker + Culture + 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 + + + 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 + ]]> + + +
diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml index 8ddf2fae26..31846e9e07 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml @@ -361,6 +361,7 @@ Avbryt Cellmarginal Välj + Rensa Stäng Stäng fönstret Kommentar From e92ab7d3a5f5e2b32f7c86a6f2c36511aca062a7 Mon Sep 17 00:00:00 2001 From: Poornima Nayar Date: Tue, 29 Oct 2019 09:26:48 +0000 Subject: [PATCH 290/548] Changed the Disable button in user details to be warning style --- .../src/views/users/views/user/details.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 ee77e4c14e..efe6b68351 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 @@ -249,7 +249,7 @@
Date: Thu, 19 Dec 2019 07:50:59 +0000 Subject: [PATCH 291/548] Don't keep prepending login to the page title Now uses #6456 to prevent the page title being appended to the previous page title when the user is automatically logged out. --- .../directives/components/application/umblogin.directive.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umblogin.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umblogin.directive.js index 19ebe448e0..541cc647fb 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umblogin.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umblogin.directive.js @@ -76,7 +76,6 @@ // Check if it is a new user const inviteVal = $location.search().invite; - vm.baseTitle = $scope.$root.locationTitle; //1 = enter password, 2 = password set, 3 = invalid token if (inviteVal && (inviteVal === "1" || inviteVal === "2")) { @@ -457,9 +456,7 @@ break; } - if (title != null) { - $scope.$root.locationTitle = title + " - " + vm.baseTitle; - } + $scope.$emit("$changeTitle", title); } } From 6f90bc2990d141f4afae284e777c46f0a6489fd0 Mon Sep 17 00:00:00 2001 From: Paul Seal Date: Thu, 7 Nov 2019 16:00:42 +0000 Subject: [PATCH 292/548] - only show remove photo on user avatar when there is an avatar --- .../src/views/users/views/user/details.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 efe6b68351..b35a29d2de 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 @@ -207,7 +207,7 @@ From e423c93a16309129228bdc94d0c9680f2a8eec38 Mon Sep 17 00:00:00 2001 From: Dirk Seefeld Date: Thu, 19 Dec 2019 09:05:47 +0100 Subject: [PATCH 293/548] Fix HelpController for isolated intranet solutions (#7163) * Fix HelpController for isolated intranet solutions * remove debug catch & throw lines --- src/Umbraco.Web/Editors/HelpController.cs | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Web/Editors/HelpController.cs b/src/Umbraco.Web/Editors/HelpController.cs index ccbbcaeee8..39dbbc435c 100644 --- a/src/Umbraco.Web/Editors/HelpController.cs +++ b/src/Umbraco.Web/Editors/HelpController.cs @@ -13,14 +13,23 @@ namespace Umbraco.Web.Editors { var url = string.Format(baseUrl + "/Umbraco/Documentation/Lessons/GetContextHelpDocs?sectionAlias={0}&treeAlias={1}", section, tree); - if (_httpClient == null) - _httpClient = new HttpClient(); + try + { - //fetch dashboard json and parse to JObject - var json = await _httpClient.GetStringAsync(url); - var result = JsonConvert.DeserializeObject>(json); - if (result != null) - return result; + if (_httpClient == null) + _httpClient = new HttpClient(); + + //fetch dashboard json and parse to JObject + var json = await _httpClient.GetStringAsync(url); + var result = JsonConvert.DeserializeObject>(json); + if (result != null) + return result; + + } + catch (HttpRequestException rex) + { + Logger.Info(GetType(), $"Check your network connection, exception: {rex.Message}"); + } return new List(); } From 9594767d17644b25a1cea067a65b550f6311401e Mon Sep 17 00:00:00 2001 From: poornimanayar Date: Mon, 9 Dec 2019 15:05:59 +0000 Subject: [PATCH 294/548] revert unnecesary changes for the css class --- src/Umbraco.Web.UI.Client/src/less/components/overlays.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 609cf0af3d..d060f34a36 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/overlays.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/overlays.less @@ -117,7 +117,7 @@ .umb-overlay.umb-overlay-center .umb-overlay-drawer { border: none; background: transparent; - padding: 0 20px 20px; + padding: 0 30px 20px; } /* ---------- OVERLAY TARGET ---------- */ From fa2bbcc35890eb97ce658bd5b5ddd09c608e674d Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Thu, 19 Dec 2019 08:38:44 +0000 Subject: [PATCH 295/548] V8: Add in Recycle Bin context message (#7341) * Add a message bar to give context that this node/content item is in the trash/recycle bin * Update umb-variant-content.html * Update en.xml * Update en_us.xml --- .../less/components/editor/umb-editor.less | 27 +- .../content/umb-variant-content.html | 6 + src/Umbraco.Web.UI/Umbraco/config/lang/en.xml | 4685 +++++++++-------- .../Umbraco/config/lang/en_us.xml | 1 + 4 files changed, 2369 insertions(+), 2350 deletions(-) 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 6859280680..d2a3bdedb1 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,6 @@ .umb-editors { .absolute(); - overflow: hidden; + overflow: hidden; .umb-editor { box-shadow: 0px 0 30px 0 rgba(0,0,0,.3); @@ -16,7 +16,7 @@ transform: none; will-change: transform; transition: transform 400ms ease-in-out; - + &.umb-editor--moveRight { transform: translateX(110%); } @@ -28,7 +28,7 @@ will-change: auto; transition: display 0s 320ms; } - + &--level0 { transform: none; } @@ -43,11 +43,11 @@ .umb-editor--level@{i} { transform: translateX(@x); } - + .umb-editor--n@{i} { right:@x; } - + .level-loop(@i - 1); } @@ -62,18 +62,18 @@ .umb-editor { @size: extract(extract(@editorSizes, @iterator), 1); @value: extract(extract(@editorSizes, @iterator), 2); - + &--@{size} { width: @value; will-change: transform; left: auto; - + .umb-editor--container { max-width: @value; } } } - + .create-editor-sizes(@iterator + 1); } @@ -94,3 +94,14 @@ opacity: 1; transition: opacity 320ms 20ms, visibility 0s; } + +.umb-editor--trashed-message { + background:@errorBackground; + color:@errorText; + padding:10px; + margin-bottom:20px; + + i { + margin-right:5px; + } +} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-variant-content.html b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-variant-content.html index 9cd245b0d5..eb12fc7940 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-variant-content.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-variant-content.html @@ -27,6 +27,12 @@ + + +
+ This item is in the Recycle Bin +
+
diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index ab09c3df83..c3b52d7d83 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -1,2342 +1,2343 @@ - - - - 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 - 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) - 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 - - - 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 - 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 - - - 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 - - - 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% - - - 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 - - - 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 - ]]> - - - + + + + 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 + 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) + 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 + 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 + + + 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 + + + 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% + + + 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 + + + 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 + ]]> + + +
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 3ff2aae00e..38085f9cb5 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -305,6 +305,7 @@ 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%' From 57a58f91cd0c1ada6d8e58cc18ae0b2b8fbca93e Mon Sep 17 00:00:00 2001 From: Sven Geusens Date: Thu, 7 Nov 2019 16:13:16 +0100 Subject: [PATCH 296/548] Added Ids to inputs so the label in umb-control-group gets linked --- .../src/views/components/users/change-password.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/components/users/change-password.html b/src/Umbraco.Web.UI.Client/src/views/components/users/change-password.html index b7b8ec12c7..4b67768011 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/users/change-password.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/users/change-password.html @@ -24,7 +24,7 @@ - - - From ec831bcec0fd2e3c94dd6055907feea99a6fe64e Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Thu, 19 Dec 2019 10:12:56 +0100 Subject: [PATCH 297/548] V8: #7209 fixes filter in User Management not keeping state (#7216) * Saved and restored user view filter and sort when viewing user details and returning to list. * Reworked state retention when navigating to and from the users list and user details pages to use querystring values. * Fixed issue with culture parameter on navigating between user list and details screen. Ensured when opening users list with querystring that user states filter is correctly populated. --- .../users/views/users/users.controller.js | 108 ++++++++++++++++-- 1 file changed, 99 insertions(+), 9 deletions(-) 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 1ae0074bfa..024d4539bc 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 @@ -1,13 +1,12 @@ (function () { "use strict"; - function UsersController($scope, $timeout, $location, $routeParams, usersResource, - userGroupsResource, userService, localizationService, contentEditingHelper, - usersHelper, formHelper, notificationsService, dateHelper, editorService, + function UsersController($scope, $timeout, $location, $routeParams, usersResource, + userGroupsResource, userService, localizationService, + usersHelper, formHelper, dateHelper, editorService, listViewHelper) { var vm = this; - var localizeSaving = localizationService.localize("general_saving"); vm.page = {}; vm.users = []; @@ -128,8 +127,7 @@ function init() { - vm.usersOptions.orderBy = "Name"; - vm.usersOptions.orderDirection = "Ascending"; + initViewOptions(); if ($routeParams.create) { setUsersViewState("createUser"); @@ -144,10 +142,56 @@ // Get user groups userGroupsResource.getUserGroups({ onlyCurrentUserGroups: false }).then(function (userGroups) { vm.userGroups = userGroups; + initUserGroupSelections(); }); } + function initViewOptions() { + + // Start with default view options. + vm.usersOptions.orderBy = "Name"; + vm.usersOptions.orderDirection = "Ascending"; + + // Update from querystring if available. + initViewOptionFromQueryString("orderBy"); + initViewOptionFromQueryString("orderDirection"); + initViewOptionFromQueryString("pageNumber"); + initViewOptionFromQueryString("userStates", true); + initViewOptionFromQueryString("userGroups", true); + } + + function initViewOptionFromQueryString(key, isCollection) { + var value = $location.search()[key]; + if (value) { + if (isCollection) { + value = value.split(","); + } + + vm.usersOptions[key] = value; + } + } + + function initUserStateSelections() { + initUsersOptionsFilterSelections(vm.userStatesFilter, vm.usersOptions.userStates, "key"); + } + + function initUserGroupSelections() { + initUsersOptionsFilterSelections(vm.userGroups, vm.usersOptions.userGroups, "alias"); + } + + function initUsersOptionsFilterSelections(filterCollection, selectedCollection, keyField) { + if (selectedCollection && selectedCollection.length > 0 && filterCollection && filterCollection.length > 0) { + for (var i = 0; i < selectedCollection.length; i++) { + for (var j = 0; j < filterCollection.length; j++) { + if (filterCollection[j][keyField] === selectedCollection[i]) { + filterCollection[j].selected = true; + } + } + } + } + } + function getSortLabel(sortKey, sortDirection) { var found = _.find(vm.userSortData, function (i) { @@ -467,6 +511,7 @@ vm.usersOptions.userStates.splice(index, 1); } + updateLocation("userStates", vm.usersOptions.userStates.join(",")); getUsers(); } @@ -483,20 +528,28 @@ vm.usersOptions.userGroups.splice(index, 1); } + updateLocation("userGroups", vm.usersOptions.userGroups.join(",")); getUsers(); } function setOrderByFilter(value, direction) { vm.usersOptions.orderBy = value; vm.usersOptions.orderDirection = direction; + updateLocation("orderBy", value); + updateLocation("orderDirection", direction); getUsers(); } function changePageNumber(pageNumber) { vm.usersOptions.pageNumber = pageNumber; + updateLocation("pageNumber", pageNumber); getUsers(); } + function updateLocation(key, value) { + $location.search(key, value); + } + function createUser(addUserForm) { if (formHelper.submitForm({ formCtrl: addUserForm, scope: $scope })) { @@ -575,17 +628,53 @@ } function goToUser(user) { - $location.path(pathToUser(user)).search("create", null).search("invite", null); + $location.path(pathToUser(user)) + .search("orderBy", vm.usersOptions.orderBy) + .search("orderDirection", vm.usersOptions.orderDirection) + .search("pageNumber", vm.usersOptions.pageNumber) + .search("userStates", getUsersOptionsFilterCollectionAsDelimitedStringOrNull(vm.usersOptions.userStates)) + .search("userGroups", getUsersOptionsFilterCollectionAsDelimitedStringOrNull(vm.usersOptions.userGroups)) + .search("create", null) + .search("invite", null); + } + + function getUsersOptionsFilterCollectionAsDelimitedStringOrNull(collection) { + if (collection && collection.length > 0) { + return collection.join(","); + } + + return null; } function getEditPath(user) { - return pathToUser(user) + "?mculture=" + $location.search().mculture; + return pathToUser(user) + usersOptionsAsQueryString(); } - + function pathToUser(user) { return "/users/users/user/" + user.id; } + function usersOptionsAsQueryString() { + var qs = "?orderBy=" + vm.usersOptions.orderBy + + "&orderDirection=" + vm.usersOptions.orderDirection + + "&pageNumber=" + vm.usersOptions.pageNumber; + + qs += addUsersOptionsFilterCollectionToQueryString("userStates", vm.usersOptions.userStates); + qs += addUsersOptionsFilterCollectionToQueryString("userGroups", vm.usersOptions.userGroups); + + qs += "&mculture=" + $location.search().mculture; + + return qs; + } + + function addUsersOptionsFilterCollectionToQueryString(name, collection) { + if (collection && collection.length > 0) { + return "&" + name + "=" + collection.join(","); + } + + return ""; + } + // helpers function getUsers() { @@ -604,6 +693,7 @@ formatDates(vm.users); setUserDisplayState(vm.users); vm.userStatesFilter = usersHelper.getUserStatesFilter(data.userStates); + initUserStateSelections(); vm.loading = false; From 65a2c0bde44804896767dd9a576b707569e114ee Mon Sep 17 00:00:00 2001 From: Nathan Woulfe Date: Thu, 19 Dec 2019 19:44:26 +1000 Subject: [PATCH 298/548] add disable-hotkeys attribute to disable hotkeys (#7186) --- .../src/common/services/keyboard.service.js | 648 +++++++++--------- .../src/views/propertyeditors/rte/rte.html | 2 +- 2 files changed, 326 insertions(+), 324 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/keyboard.service.js b/src/Umbraco.Web.UI.Client/src/common/services/keyboard.service.js index f66b0b0ed0..31375c5c56 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/keyboard.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/keyboard.service.js @@ -1,323 +1,325 @@ -// This service was based on OpenJS library available in BSD License -// http://www.openjs.com/scripts/events/keyboard_shortcuts/index.php - -function keyboardService($window, $timeout) { - - var keyboardManagerService = {}; - - var defaultOpt = { - 'type': 'keydown', - 'propagate': false, - 'inputDisabled': false, - 'target': $window.document, - 'keyCode': false - }; - - // Work around for stupid Shift key bug created by using lowercase - as a result the shift+num combination was broken - var shift_nums = { - "`": "~", - "1": "!", - "2": "@", - "3": "#", - "4": "$", - "5": "%", - "6": "^", - "7": "&", - "8": "*", - "9": "(", - "0": ")", - "-": "_", - "=": "+", - ";": ":", - "'": "\"", - ",": "<", - ".": ">", - "/": "?", - "\\": "|" - }; - - // Special Keys - and their codes - var special_keys = { - 'esc': 27, - 'escape': 27, - 'tab': 9, - 'space': 32, - 'return': 13, - 'enter': 13, - 'backspace': 8, - - 'scrolllock': 145, - 'scroll_lock': 145, - 'scroll': 145, - 'capslock': 20, - 'caps_lock': 20, - 'caps': 20, - 'numlock': 144, - 'num_lock': 144, - 'num': 144, - - 'pause': 19, - 'break': 19, - - 'insert': 45, - 'home': 36, - 'delete': 46, - 'end': 35, - - 'pageup': 33, - 'page_up': 33, - 'pu': 33, - - 'pagedown': 34, - 'page_down': 34, - 'pd': 34, - - 'left': 37, - 'up': 38, - 'right': 39, - 'down': 40, - - 'f1': 112, - 'f2': 113, - 'f3': 114, - 'f4': 115, - 'f5': 116, - 'f6': 117, - 'f7': 118, - 'f8': 119, - 'f9': 120, - 'f10': 121, - 'f11': 122, - 'f12': 123 - }; - - var isMac = navigator.platform.toUpperCase().indexOf('MAC')>=0; - - // The event handler for bound element events - function eventHandler(e) { - e = e || $window.event; - - var code, k; - - // Find out which key is pressed - if (e.keyCode) - { - code = e.keyCode; - } - else if (e.which) { - code = e.which; - } - - var character = String.fromCharCode(code).toLowerCase(); - - if (code === 188){character = ",";} // If the user presses , when the type is onkeydown - if (code === 190){character = ".";} // If the user presses , when the type is onkeydown - - var propagate = true; - - //Now we need to determine which shortcut this event is for, we'll do this by iterating over each - //registered shortcut to find the match. We use Find here so that the loop exits as soon - //as we've found the one we're looking for - _.find(_.keys(keyboardManagerService.keyboardEvent), function(key) { - - var shortcutLabel = key; - var shortcutVal = keyboardManagerService.keyboardEvent[key]; - - // Key Pressed - counts the number of valid keypresses - if it is same as the number of keys, the shortcut function is invoked - var kp = 0; - - // Some modifiers key - var modifiers = { - shift: { - wanted: false, - pressed: e.shiftKey ? true : false - }, - ctrl: { - wanted: false, - pressed: e.ctrlKey ? true : false - }, - alt: { - wanted: false, - pressed: e.altKey ? true : false - }, - meta: { //Meta is Mac specific - wanted: false, - pressed: e.metaKey ? true : false - } - }; - - var keys = shortcutLabel.split("+"); - var opt = shortcutVal.opt; - var callback = shortcutVal.callback; - - // Foreach keys in label (split on +) - var l = keys.length; - for (var i = 0; i < l; i++) { - - var k = keys[i]; - switch (k) { - case 'ctrl': - case 'control': - kp++; - modifiers.ctrl.wanted = true; - break; - case 'shift': - case 'alt': - case 'meta': - kp++; - modifiers[k].wanted = true; - break; - } - - if (k.length > 1) { // If it is a special key - if (special_keys[k] === code) { - kp++; - } - } - else if (opt['keyCode']) { // If a specific key is set into the config - if (opt['keyCode'] === code) { - kp++; - } - } - else { // The special keys did not match - if (character === k) { - kp++; - } - else { - if (shift_nums[character] && e.shiftKey) { // Stupid Shift key bug created by using lowercase - character = shift_nums[character]; - if (character === k) { - kp++; - } - } - } - } - - } //for end - - if (kp === keys.length && - modifiers.ctrl.pressed === modifiers.ctrl.wanted && - modifiers.shift.pressed === modifiers.shift.wanted && - modifiers.alt.pressed === modifiers.alt.wanted && - modifiers.meta.pressed === modifiers.meta.wanted) { - - //found the right callback! - - // Disable event handler when focus input and textarea - if (opt['inputDisabled']) { - var elt; - if (e.target) { - elt = e.target; - } else if (e.srcElement) { - elt = e.srcElement; - } - - if (elt.nodeType === 3) { elt = elt.parentNode; } - if (elt.tagName === 'INPUT' || elt.tagName === 'TEXTAREA') { - //This exits the Find loop - return true; - } - } - - $timeout(function () { - callback(e); - }, 1); - - if (!opt['propagate']) { // Stop the event - propagate = false; - } - - //This exits the Find loop - return true; - } - - //we haven't found one so continue looking - return false; - - }); - - // Stop the event if required - if (!propagate) { - // e.cancelBubble is supported by IE - this will kill the bubbling process. - e.cancelBubble = true; - e.returnValue = false; - - // e.stopPropagation works in Firefox. - if (e.stopPropagation) { - e.stopPropagation(); - e.preventDefault(); - } - return false; - } - } - - // Store all keyboard combination shortcuts - keyboardManagerService.keyboardEvent = {}; - - // Add a new keyboard combination shortcut - keyboardManagerService.bind = function (label, callback, opt) { - - //replace ctrl key with meta key - if(isMac && label !== "ctrl+space"){ - label = label.replace("ctrl","meta"); - } - - var elt; - // Initialize opt object - opt = angular.extend({}, defaultOpt, opt); - label = label.toLowerCase(); - elt = opt.target; - if(typeof opt.target === 'string'){ - elt = document.getElementById(opt.target); - } - - //Ensure we aren't double binding to the same element + type otherwise we'll end up multi-binding - // and raising events for now reason. So here we'll check if the event is already registered for the element - var boundValues = _.values(keyboardManagerService.keyboardEvent); - var found = _.find(boundValues, function (i) { - return i.target === elt && i.event === opt['type']; - }); - - // Store shortcut - keyboardManagerService.keyboardEvent[label] = { - 'callback': callback, - 'target': elt, - 'opt': opt - }; - - if (!found) { - //Attach the function with the event - if (elt.addEventListener) { - elt.addEventListener(opt['type'], eventHandler, false); - } else if (elt.attachEvent) { - elt.attachEvent('on' + opt['type'], eventHandler); - } else { - elt['on' + opt['type']] = eventHandler; - } - } - - }; - // Remove the shortcut - just specify the shortcut and I will remove the binding - keyboardManagerService.unbind = function (label) { - label = label.toLowerCase(); - var binding = keyboardManagerService.keyboardEvent[label]; - delete(keyboardManagerService.keyboardEvent[label]); - - if(!binding){return;} - - var type = binding['event'], - elt = binding['target'], - callback = binding['callback']; - - if(elt.detachEvent){ - elt.detachEvent('on' + type, callback); - }else if(elt.removeEventListener){ - elt.removeEventListener(type, callback, false); - }else{ - elt['on'+type] = false; - } - }; - // - - return keyboardManagerService; -} angular.module('umbraco.services').factory('keyboardService', ['$window', '$timeout', keyboardService]); \ No newline at end of file +// This service was based on OpenJS library available in BSD License +// http://www.openjs.com/scripts/events/keyboard_shortcuts/index.php + +function keyboardService($window, $timeout) { + + var keyboardManagerService = {}; + + var defaultOpt = { + 'type': 'keydown', + 'propagate': false, + 'inputDisabled': false, + 'target': $window.document, + 'keyCode': false + }; + + // Work around for stupid Shift key bug created by using lowercase - as a result the shift+num combination was broken + var shift_nums = { + "`": "~", + "1": "!", + "2": "@", + "3": "#", + "4": "$", + "5": "%", + "6": "^", + "7": "&", + "8": "*", + "9": "(", + "0": ")", + "-": "_", + "=": "+", + ";": ":", + "'": "\"", + ",": "<", + ".": ">", + "/": "?", + "\\": "|" + }; + + // Special Keys - and their codes + var special_keys = { + 'esc': 27, + 'escape': 27, + 'tab': 9, + 'space': 32, + 'return': 13, + 'enter': 13, + 'backspace': 8, + + 'scrolllock': 145, + 'scroll_lock': 145, + 'scroll': 145, + 'capslock': 20, + 'caps_lock': 20, + 'caps': 20, + 'numlock': 144, + 'num_lock': 144, + 'num': 144, + + 'pause': 19, + 'break': 19, + + 'insert': 45, + 'home': 36, + 'delete': 46, + 'end': 35, + + 'pageup': 33, + 'page_up': 33, + 'pu': 33, + + 'pagedown': 34, + 'page_down': 34, + 'pd': 34, + + 'left': 37, + 'up': 38, + 'right': 39, + 'down': 40, + + 'f1': 112, + 'f2': 113, + 'f3': 114, + 'f4': 115, + 'f5': 116, + 'f6': 117, + 'f7': 118, + 'f8': 119, + 'f9': 120, + 'f10': 121, + 'f11': 122, + 'f12': 123 + }; + + var isMac = navigator.platform.toUpperCase().indexOf('MAC')>=0; + + // The event handler for bound element events + function eventHandler(e) { + e = e || $window.event; + + var code, k; + + // Find out which key is pressed + if (e.keyCode) + { + code = e.keyCode; + } + else if (e.which) { + code = e.which; + } + + var character = String.fromCharCode(code).toLowerCase(); + + if (code === 188){character = ",";} // If the user presses , when the type is onkeydown + if (code === 190){character = ".";} // If the user presses , when the type is onkeydown + + var propagate = true; + + //Now we need to determine which shortcut this event is for, we'll do this by iterating over each + //registered shortcut to find the match. We use Find here so that the loop exits as soon + //as we've found the one we're looking for + _.find(_.keys(keyboardManagerService.keyboardEvent), function(key) { + + var shortcutLabel = key; + var shortcutVal = keyboardManagerService.keyboardEvent[key]; + + // Key Pressed - counts the number of valid keypresses - if it is same as the number of keys, the shortcut function is invoked + var kp = 0; + + // Some modifiers key + var modifiers = { + shift: { + wanted: false, + pressed: e.shiftKey ? true : false + }, + ctrl: { + wanted: false, + pressed: e.ctrlKey ? true : false + }, + alt: { + wanted: false, + pressed: e.altKey ? true : false + }, + meta: { //Meta is Mac specific + wanted: false, + pressed: e.metaKey ? true : false + } + }; + + var keys = shortcutLabel.split("+"); + var opt = shortcutVal.opt; + var callback = shortcutVal.callback; + + // Foreach keys in label (split on +) + var l = keys.length; + for (var i = 0; i < l; i++) { + + var k = keys[i]; + switch (k) { + case 'ctrl': + case 'control': + kp++; + modifiers.ctrl.wanted = true; + break; + case 'shift': + case 'alt': + case 'meta': + kp++; + modifiers[k].wanted = true; + break; + } + + if (k.length > 1) { // If it is a special key + if (special_keys[k] === code) { + kp++; + } + } + else if (opt['keyCode']) { // If a specific key is set into the config + if (opt['keyCode'] === code) { + kp++; + } + } + else { // The special keys did not match + if (character === k) { + kp++; + } + else { + if (shift_nums[character] && e.shiftKey) { // Stupid Shift key bug created by using lowercase + character = shift_nums[character]; + if (character === k) { + kp++; + } + } + } + } + + } //for end + + if (kp === keys.length && + modifiers.ctrl.pressed === modifiers.ctrl.wanted && + modifiers.shift.pressed === modifiers.shift.wanted && + modifiers.alt.pressed === modifiers.alt.wanted && + modifiers.meta.pressed === modifiers.meta.wanted) { + + //found the right callback! + + // Disable event handler when focus input and textarea + if (opt['inputDisabled']) { + var elt; + if (e.target) { + elt = e.target; + } else if (e.srcElement) { + elt = e.srcElement; + } + + if (elt.nodeType === 3) { elt = elt.parentNode; } + if (elt.tagName === 'INPUT' || elt.tagName === 'TEXTAREA' || elt.hasAttribute('disable-hotkeys')) { + //This exits the Find loop + return true; + } + } + + $timeout(function () { + callback(e); + }, 1); + + if (!opt['propagate']) { // Stop the event + propagate = false; + } + + //This exits the Find loop + return true; + } + + //we haven't found one so continue looking + return false; + + }); + + // Stop the event if required + if (!propagate) { + // e.cancelBubble is supported by IE - this will kill the bubbling process. + e.cancelBubble = true; + e.returnValue = false; + + // e.stopPropagation works in Firefox. + if (e.stopPropagation) { + e.stopPropagation(); + e.preventDefault(); + } + return false; + } + } + + // Store all keyboard combination shortcuts + keyboardManagerService.keyboardEvent = {}; + + // Add a new keyboard combination shortcut + keyboardManagerService.bind = function (label, callback, opt) { + + //replace ctrl key with meta key + if(isMac && label !== "ctrl+space"){ + label = label.replace("ctrl","meta"); + } + + var elt; + // Initialize opt object + opt = angular.extend({}, defaultOpt, opt); + label = label.toLowerCase(); + elt = opt.target; + if(typeof opt.target === 'string'){ + elt = document.getElementById(opt.target); + } + + //Ensure we aren't double binding to the same element + type otherwise we'll end up multi-binding + // and raising events for now reason. So here we'll check if the event is already registered for the element + var boundValues = _.values(keyboardManagerService.keyboardEvent); + var found = _.find(boundValues, function (i) { + return i.target === elt && i.event === opt['type']; + }); + + // Store shortcut + keyboardManagerService.keyboardEvent[label] = { + 'callback': callback, + 'target': elt, + 'opt': opt + }; + + if (!found) { + //Attach the function with the event + if (elt.addEventListener) { + elt.addEventListener(opt['type'], eventHandler, false); + } else if (elt.attachEvent) { + elt.attachEvent('on' + opt['type'], eventHandler); + } else { + elt['on' + opt['type']] = eventHandler; + } + } + + }; + // Remove the shortcut - just specify the shortcut and I will remove the binding + keyboardManagerService.unbind = function (label) { + label = label.toLowerCase(); + var binding = keyboardManagerService.keyboardEvent[label]; + delete(keyboardManagerService.keyboardEvent[label]); + + if(!binding){return;} + + var type = binding['event'], + elt = binding['target'], + callback = binding['callback']; + + if(elt.detachEvent){ + elt.detachEvent('on' + type, callback); + }else if(elt.removeEventListener){ + elt.removeEventListener(type, callback, false); + }else{ + elt['on'+type] = false; + } + }; + // + + return keyboardManagerService; +} + +angular.module('umbraco.services').factory('keyboardService', ['$window', '$timeout', keyboardService]); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.html index b147e5ccca..d0b5823f74 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.html @@ -3,6 +3,6 @@
-
+
From da0e053dbcb6b1afe83e41949b34bca2ba971c3f Mon Sep 17 00:00:00 2001 From: David Zweben <20249475+Zweben@users.noreply.github.com> Date: Tue, 9 Jul 2019 11:25:50 -0400 Subject: [PATCH 299/548] Fixed @name label in filemanager.service.js Corrected the @name documentation of the setFiles method in filemanager.service.js, from "addFiles" to "setFiles". --- .../src/common/services/filemanager.service.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 8fe6761c94..41614a3bee 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 @@ -16,7 +16,7 @@ function fileManager($rootScope) { var mgr = { /** * @ngdoc function - * @name umbraco.services.fileManager#addFiles + * @name umbraco.services.fileManager#setFiles * @methodOf umbraco.services.fileManager * @function * From f1393295ac3e2ebabde4365de9eb8610071d3001 Mon Sep 17 00:00:00 2001 From: Anders Bjerner Date: Thu, 19 Dec 2019 11:32:04 +0100 Subject: [PATCH 300/548] Fixed casing for the "noTemplate" option (#7022) * Fixed casing for the "noTemplate" option The option should be named using camel casing similar to pretty much all other options. The fix is made so either "notemplate" or "noTemplate" can be specified, making the fix backwards compatible instead of creating a breaking change. * Setting "documentTypeId" to "-1" if not already specified and "create" is "true" --- .../src/views/documenttypes/edit.controller.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 9d73aa8838..b04cccbc0d 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 @@ -64,7 +64,8 @@ if (infiniteMode) { documentTypeId = $scope.model.id; create = $scope.model.create; - noTemplate = $scope.model.notemplate; + if (create && !documentTypeId) documentTypeId = -1; + noTemplate = $scope.model.notemplate || $scope.model.noTemplate; isElement = $scope.model.isElement; allowVaryByCulture = $scope.model.allowVaryByCulture; vm.submitButtonKey = "buttons_saveAndClose"; From b2195ebed62aa155c98dcbf72c72397a6788de46 Mon Sep 17 00:00:00 2001 From: Anders Bjerner Date: Thu, 19 Dec 2019 11:33:43 +0100 Subject: [PATCH 301/548] Improved the ngdocs for the editor service (#7023) --- .../src/common/services/editor.service.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) 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 1f6418e06a..8b922d7ec8 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 @@ -610,8 +610,11 @@ When building a custom infinite editor view you can use the same components as a * @description * Opens the document type editor in infinite editing, the submit callback returns the alias of the saved document type. * @param {Object} editor rendering options - * @param {Callback} editor.id Indicates the ID of the document type to be edited. Alternatively the ID may be set to `-1` in combination with `create` being set to `true` to open the document type editor for creating a new document type. - * @param {Callback} editor.create Set to `true` to open the document type editor for creating a new document type. + * @param {Number} editor.id Indicates the ID of the document type to be edited. Alternatively the ID may be set to `-1` in combination with `create` being set to `true` to open the document type editor for creating a new document type. + * @param {Boolean} editor.create Set to `true` to open the document type editor for creating a new document type. + * @param {Boolean} editor.noTemplate If `true` and in combination with `create` being set to `true`, the document type editor will not create a corresponding template by default. This is similar to selecting the "Document Type without a template" in the Create dialog. + * @param {Boolean} editor.isElement If `true` and in combination with `create` being set to `true`, the "Is an Element type" option will be selected by default in the document type editor. + * @param {Boolean} editor.allowVaryByCulture If `true` and in combination with `create`, the "Allow varying by culture" option will be selected by default in the document type editor. * @param {Callback} editor.submit Submits the editor. * @param {Callback} editor.close Closes the editor. * @returns {Object} editor object From 5d05c2ad4b6ab870d91504b64b41afb676728b64 Mon Sep 17 00:00:00 2001 From: Poornima Nayar Date: Thu, 19 Dec 2019 10:39:11 +0000 Subject: [PATCH 302/548] Replaced umb-checkbox with umb-toggle in listview copy overlay (#6944) --- .../infiniteeditors/copy/copy.controller.js | 184 +++++++++--------- .../common/infiniteeditors/copy/copy.html | 12 +- 2 files changed, 102 insertions(+), 94 deletions(-) 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 67604aca44..3b405333bf 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 @@ -1,98 +1,99 @@ - (function() { - "use strict"; +(function () { + "use strict"; - function CopyController($scope, localizationService, eventsService, entityHelper) { + function CopyController($scope, localizationService, eventsService, entityHelper) { - var vm = this; + var vm = this; - vm.labels = {}; - vm.hideSearch = hideSearch; - vm.selectResult = selectResult; - vm.onSearchResults = onSearchResults; - vm.submit = submit; - vm.close = close; + vm.labels = {}; + vm.hideSearch = hideSearch; + vm.selectResult = selectResult; + vm.onSearchResults = onSearchResults; + vm.onToggle = toggleHandler; + vm.submit = submit; + vm.close = close; - var dialogOptions = $scope.model; - var node = dialogOptions.currentNode; + var dialogOptions = $scope.model; + var node = dialogOptions.currentNode; - $scope.model.relateToOriginal = true; - $scope.dialogTreeApi = {}; + $scope.model.relateToOriginal = true; + $scope.dialogTreeApi = {}; - vm.searchInfo = { - searchFromId: null, - searchFromName: null, - showSearch: false, - results: [], - selectedSearchResults: [] - }; + vm.searchInfo = { + searchFromId: null, + searchFromName: null, + showSearch: false, + results: [], + selectedSearchResults: [] + }; - // get entity type based on the section - $scope.entityType = entityHelper.getEntityTypeFromSection(dialogOptions.section); + // get entity type based on the section + $scope.entityType = entityHelper.getEntityTypeFromSection(dialogOptions.section); - function onInit() { + function onInit() { - var labelKeys = [ - "general_copy", - "defaultdialogs_relateToOriginalLabel" - ]; + var labelKeys = [ + "general_copy", + "defaultdialogs_relateToOriginalLabel" + ]; - localizationService.localizeMany(labelKeys).then(function (data) { + localizationService.localizeMany(labelKeys).then(function (data) { - vm.labels.title = data[0]; - vm.labels.relateToOriginal = data[1]; + vm.labels.title = data[0]; + vm.labels.relateToOriginal = data[1]; - setTitle(vm.labels.title); - }); - } + setTitle(vm.labels.title); + }); + } - function setTitle(value) { - if (!$scope.model.title) { - $scope.model.title = value; - } - } + function setTitle(value) { + if (!$scope.model.title) { + $scope.model.title = value; + } + } - function nodeSelectHandler(args) { - if (args && args.event) { - args.event.preventDefault(); - args.event.stopPropagation(); - } + function nodeSelectHandler(args) { + if (args && args.event) { + args.event.preventDefault(); + args.event.stopPropagation(); + } - //eventsService.emit("editors.content.copyController.select", args); + //eventsService.emit("editors.content.copyController.select", args); - if ($scope.model.target) { - //un-select if there's a current one selected - $scope.model.target.selected = false; - } + if ($scope.model.target) { + //un-select if there's a current one selected + $scope.model.target.selected = false; + } - $scope.model.target = args.node; - $scope.model.target.selected = true; - } + $scope.model.target = args.node; + $scope.model.target.selected = true; + } - function nodeExpandedHandler(args) { - // open mini list view for list views - if (args.node.metaData.isContainer) { - openMiniListView(args.node); - } - } + function nodeExpandedHandler(args) { + // open mini list view for list views + if (args.node.metaData.isContainer) { + openMiniListView(args.node); + } + } - function hideSearch() { - vm.searchInfo.showSearch = false; - vm.searchInfo.searchFromId = null; - vm.searchInfo.searchFromName = null; - vm.searchInfo.results = []; - } + function hideSearch() { + vm.searchInfo.showSearch = false; + vm.searchInfo.searchFromId = null; + vm.searchInfo.searchFromName = null; + vm.searchInfo.results = []; + } - // method to select a search result - function selectResult(evt, result) { - result.selected = result.selected === true ? false : true; - nodeSelectHandler({ event: evt, node: result }); - } + // method to select a search result + function selectResult(evt, result) { + result.selected = result.selected === true ? false : true; + nodeSelectHandler({ event: evt, node: result }); + } - //callback when there are search results - function onSearchResults(results) { - vm.searchInfo.results = results; - vm.searchInfo.showSearch = true; - } + //callback when there are search results + function onSearchResults(results) { + vm.searchInfo.results = results; + vm.searchInfo.showSearch = true; + } $scope.onTreeInit = function () { $scope.dialogTreeApi.callbacks.treeNodeSelect(nodeSelectHandler); @@ -100,19 +101,19 @@ } - // Mini list view - $scope.selectListViewNode = function (node) { - node.selected = node.selected === true ? false : true; - nodeSelectHandler({ node: node }); - }; + // Mini list view + $scope.selectListViewNode = function (node) { + node.selected = node.selected === true ? false : true; + nodeSelectHandler({ node: node }); + }; - $scope.closeMiniListView = function () { - $scope.miniListView = undefined; - }; + $scope.closeMiniListView = function () { + $scope.miniListView = undefined; + }; - function openMiniListView(node) { - $scope.miniListView = node; - } + function openMiniListView(node) { + $scope.miniListView = node; + } function submit() { if ($scope.model && $scope.model.submit) { @@ -126,9 +127,16 @@ } } - onInit(); - } + function toggleHandler(type) { + // If the relateToOriginal toggle is clicked + if (type === "relate") { + $scope.model.relateToOriginal = !$scope.model.relateToOriginal; + } + } - angular.module("umbraco").controller("Umbraco.Editors.CopyController", CopyController); + onInit(); + } + + angular.module("umbraco").controller("Umbraco.Editors.CopyController", CopyController); })(); 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 07c44e90ee..86c0186374 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 @@ -2,7 +2,7 @@ -
+
-
-
- -
-
+ + + + + From d050bc6500f1448a615775d5d3591c3ce6d9edce Mon Sep 17 00:00:00 2001 From: esther Date: Wed, 18 Dec 2019 19:38:13 +0100 Subject: [PATCH 303/548] remove duplicate method canAdd --- .../nestedcontent/nestedcontent.doctypepicker.controller.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.doctypepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.doctypepicker.controller.js index 4a9a07428d..6e807ffaa4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.doctypepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.doctypepicker.controller.js @@ -53,10 +53,6 @@ }); } - $scope.canAdd = function () { - return !$scope.model.docTypes || !$scope.model.value || $scope.model.value.length < $scope.model.docTypes.length; - } - $scope.remove = function (index) { $scope.model.value.splice(index, 1); } @@ -112,6 +108,7 @@ }); }); } + $scope.canAdd = function () { return !$scope.model.value || _.some($scope.model.elemTypes, function (elType) { return !_.find($scope.model.value, function (c) { @@ -120,7 +117,6 @@ }); } - $scope.openElemTypeModal = function ($event, config) { //we have to add the alias to the objects (they are stored as ncAlias) From a5c0965e02dbc3b9bcd00542efa8746582c21a5f Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Wed, 30 Oct 2019 21:04:13 +0100 Subject: [PATCH 304/548] Automatically select uploaded media in media pickers --- .../mediapicker/mediapicker.controller.js | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) 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 ba103a2761..bb7ce6f727 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,7 +1,7 @@ //used for the media picker dialog angular.module("umbraco") .controller("Umbraco.Editors.MediaPickerController", - function ($scope, mediaResource, entityResource, userService, mediaHelper, mediaTypeHelper, eventsService, treeService, localStorageService, localizationService, editorService) { + function ($scope, $timeout, mediaResource, entityResource, userService, mediaHelper, mediaTypeHelper, eventsService, treeService, localStorageService, localizationService, editorService) { var vm = this; @@ -292,13 +292,20 @@ angular.module("umbraco") function onUploadComplete(files) { gotoFolder($scope.currentFolder).then(function () { - if (files.length === 1 && $scope.model.selection.length === 0) { - var image = $scope.images[$scope.images.length - 1]; - $scope.target = image; - $scope.target.url = mediaHelper.resolveFile(image); - selectMedia(image); - } - }) + $timeout(function () { + if ($scope.multiPicker) { + var images = _.rest($scope.images, $scope.images.length - files.length); + _.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); + } + }); + }); } function onFilesQueue() { From bf071e740738099a394c7018fc7e8055c2d7fc5f Mon Sep 17 00:00:00 2001 From: poornima-abelandcole Date: Mon, 28 Oct 2019 14:24:58 +0000 Subject: [PATCH 305/548] Removed Members Dashboard Intro --- .../views/dashboard/members/membersdashboardintro.html | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 src/Umbraco.Web.UI.Client/src/views/dashboard/members/membersdashboardintro.html diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/members/membersdashboardintro.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/members/membersdashboardintro.html deleted file mode 100644 index c29b52d96e..0000000000 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/members/membersdashboardintro.html +++ /dev/null @@ -1,10 +0,0 @@ -

Start here

- -

Get started with Members right now

-

Use the tool below to search for an existing member.

- -

More about members

- - From c54f74d0055bd69940aadf1bf6b7affa7b885a1c Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Fri, 6 Dec 2019 10:50:06 +0100 Subject: [PATCH 306/548] Update noUiSlider to v14.1.0 for accessibility to support keys Home/End and Page Up/Down --- src/Umbraco.Web.UI.Client/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index 2b92302684..fabcacc338 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -38,7 +38,7 @@ "lazyload-js": "1.0.0", "moment": "2.22.2", "ng-file-upload": "12.2.13", - "nouislider": "14.0.2", + "nouislider": "14.1.0", "npm": "6.12.0", "signalr": "2.4.0", "spectrum-colorpicker": "1.8.0", From 3df135fb0639c9f4e593fb2beeb30401ffb719eb Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Mon, 16 Dec 2019 15:58:15 +0100 Subject: [PATCH 307/548] Update to noUiSlider v14.1.1 --- src/Umbraco.Web.UI.Client/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index fabcacc338..0f02aba5e2 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -38,7 +38,7 @@ "lazyload-js": "1.0.0", "moment": "2.22.2", "ng-file-upload": "12.2.13", - "nouislider": "14.1.0", + "nouislider": "14.1.1", "npm": "6.12.0", "signalr": "2.4.0", "spectrum-colorpicker": "1.8.0", From 1f6534739d70074ebdc6ed0224c592e28a5d0460 Mon Sep 17 00:00:00 2001 From: BatJan Date: Sun, 27 Oct 2019 22:02:32 +0100 Subject: [PATCH 308/548] Change to - Cancel +
@@ -65,8 +65,8 @@

{{item.alias}} ({{item.width}}px × {{item.height}}px)

- Edit - Remove + +
From 6241603c33e6f10c710cd43872d637fa71860cdf Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Thu, 19 Dec 2019 15:15:46 +0100 Subject: [PATCH 309/548] V8: On smaller screens, trees should close when you click outside them (#7018) --- src/Umbraco.Web.UI.Client/src/navigation.controller.js | 10 +++++++++- .../views/components/application/umb-navigation.html | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/navigation.controller.js b/src/Umbraco.Web.UI.Client/src/navigation.controller.js index b585d22e9f..194c45afe6 100644 --- a/src/Umbraco.Web.UI.Client/src/navigation.controller.js +++ b/src/Umbraco.Web.UI.Client/src/navigation.controller.js @@ -510,6 +510,14 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar if (!event) { return; } + closeTree(); + }; + + $scope.onOutsideClick = function() { + closeTree(); + }; + + function closeTree() { if (!appState.getGlobalState("touchDevice")) { treeActive = false; $timeout(function () { @@ -518,7 +526,7 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar } }, 300); } - }; + } $scope.toggleLanguageSelector = function () { $scope.page.languageSelectorIsOpen = !$scope.page.languageSelectorIsOpen; diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-navigation.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-navigation.html index 0aab35ca21..c133d5d38b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-navigation.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-navigation.html @@ -1,4 +1,4 @@ -
+
-
- - Search - - +
+ From 95f2258123294104553a1500cf82ef247dd44f8f Mon Sep 17 00:00:00 2001 From: BatJan Date: Sun, 27 Oct 2019 17:18:28 +0100 Subject: [PATCH 311/548] Remove data-toggle attribute --- src/Umbraco.Web.UI.Client/src/views/logviewer/search.html | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/logviewer/search.html b/src/Umbraco.Web.UI.Client/src/views/logviewer/search.html index e38219bfb8..b4f910fd7b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/logviewer/search.html +++ b/src/Umbraco.Web.UI.Client/src/views/logviewer/search.html @@ -162,8 +162,7 @@
- - ]]> - - - - throw - - - ashx,aspx,ascx,config,cshtml,vbhtml,asmx,air,axd,swf,xml,xhtml,html,htm,php,htaccess - - - assets/img/login.jpg - - - - - - false - - true - - false - - - - - - - - - - - - + + + + + + + + + + + + + 1 + + + + + + + + your@email.here + + + + + + Preview mode + + … + + + Click to end + +
+ + ]]> + + + + throw + + + ashx,aspx,ascx,config,cshtml,vbhtml,asmx,air,axd,swf,xml,xhtml,html,htm,php,htaccess + + + assets/img/login.jpg + + + + + + false + + true + + false + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Umbraco.Web/Scheduling/KeepAlive.cs b/src/Umbraco.Web/Scheduling/KeepAlive.cs index c07430df04..9a22c59566 100644 --- a/src/Umbraco.Web/Scheduling/KeepAlive.cs +++ b/src/Umbraco.Web/Scheduling/KeepAlive.cs @@ -3,6 +3,8 @@ using System.Net.Http; using System.Threading; using System.Threading.Tasks; using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; using Umbraco.Core.Sync; @@ -11,14 +13,16 @@ namespace Umbraco.Web.Scheduling internal class KeepAlive : RecurringTaskBase { private readonly IRuntimeState _runtime; + private readonly IKeepAliveSection _keepAliveSection; private readonly IProfilingLogger _logger; private static HttpClient _httpClient; public KeepAlive(IBackgroundTaskRunner runner, int delayMilliseconds, int periodMilliseconds, - IRuntimeState runtime, IProfilingLogger logger) + IRuntimeState runtime, IKeepAliveSection keepAliveSection, IProfilingLogger logger) : base(runner, delayMilliseconds, periodMilliseconds) { _runtime = runtime; + _keepAliveSection = keepAliveSection; _logger = logger; if (_httpClient == null) _httpClient = new HttpClient(); @@ -46,25 +50,27 @@ namespace Umbraco.Web.Scheduling using (_logger.DebugDuration("Keep alive executing", "Keep alive complete")) { - string umbracoAppUrl = null; - + var keepAlivePingUrl = _keepAliveSection.KeepAlivePingUrl; try { - umbracoAppUrl = _runtime.ApplicationUrl.ToString(); - if (umbracoAppUrl.IsNullOrWhiteSpace()) + if (keepAlivePingUrl.Contains("{umbracoApplicationUrl}")) { - _logger.Warn("No url for service (yet), skip."); - return true; // repeat + var umbracoAppUrl = _runtime.ApplicationUrl.ToString(); + if (umbracoAppUrl.IsNullOrWhiteSpace()) + { + _logger.Warn("No umbracoApplicationUrl for service (yet), skip."); + return true; // repeat + } + + keepAlivePingUrl = keepAlivePingUrl.Replace("{umbracoApplicationUrl}", umbracoAppUrl.TrimEnd('/')); } - var url = umbracoAppUrl.TrimEnd('/') + "/api/keepalive/ping"; - - var request = new HttpRequestMessage(HttpMethod.Get, url); + var request = new HttpRequestMessage(HttpMethod.Get, keepAlivePingUrl); var result = await _httpClient.SendAsync(request, token); } catch (Exception ex) { - _logger.Error(ex, "Keep alive failed (at '{UmbracoAppUrl}').", umbracoAppUrl); + _logger.Error(ex, "Keep alive failed (at '{keepAlivePingUrl}').", keepAlivePingUrl); } } diff --git a/src/Umbraco.Web/Scheduling/SchedulerComponent.cs b/src/Umbraco.Web/Scheduling/SchedulerComponent.cs index 1416393f46..a08289186f 100644 --- a/src/Umbraco.Web/Scheduling/SchedulerComponent.cs +++ b/src/Umbraco.Web/Scheduling/SchedulerComponent.cs @@ -99,7 +99,11 @@ namespace Umbraco.Web.Scheduling var tasks = new List(); - tasks.Add(RegisterKeepAlive()); + if (settings.KeepAlive.DisableKeepAliveTask == false) + { + tasks.Add(RegisterKeepAlive(settings.KeepAlive)); + } + tasks.Add(RegisterScheduledPublishing()); tasks.Add(RegisterLogScrubber(settings)); tasks.Add(RegisterTempFileCleanup()); @@ -112,11 +116,11 @@ namespace Umbraco.Web.Scheduling }); } - private IBackgroundTask RegisterKeepAlive() + private IBackgroundTask RegisterKeepAlive(IKeepAliveSection keepAliveSection) { // ping/keepalive // on all servers - var task = new KeepAlive(_keepAliveRunner, DefaultDelayMilliseconds, FiveMinuteMilliseconds, _runtime, _logger); + var task = new KeepAlive(_keepAliveRunner, DefaultDelayMilliseconds, FiveMinuteMilliseconds, _runtime, keepAliveSection, _logger); _keepAliveRunner.TryAdd(task); return task; } From 9c33ebc98acd89e37407d8259a67c457967eebfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rasmus=20=C3=98sterg=C3=A5rd?= Date: Fri, 20 Dec 2019 08:35:24 +0100 Subject: [PATCH 317/548] V8/feature/5914 broken backoffice validation (#6989) * validate max length for textbox * validate max length for textarea --- .../textarea/textarea.controller.js | 17 ++++++++++++++++- .../propertyeditors/textarea/textarea.html | 2 +- .../textbox/textbox.controller.js | 14 +++++++++++++- .../views/propertyeditors/textbox/textbox.html | 2 +- 4 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.controller.js index 884cc62d43..4a7fff99f8 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.controller.js @@ -15,7 +15,22 @@ function textAreaController($scope, validationMessageService) { if ($scope.model.config && $scope.model.config.maxChars) { $scope.model.maxlength = true; } - + + $scope.$on("formSubmitting", function() { + if ($scope.isLengthValid()) { + $scope.textareaFieldForm.textarea.$setValidity("maxChars", true); + } else { + $scope.textareaFieldForm.textarea.$setValidity("maxChars", false); + } + }); + + $scope.isLengthValid = function() { + if (!$scope.model.maxlength) { + return true; + } + return $scope.model.config.maxChars >= $scope.model.count; + } + $scope.model.change = function () { if ($scope.model.value) { $scope.model.count = $scope.model.value.length; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.html index 04bd8590d2..d255c4a5d6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.html @@ -10,7 +10,7 @@
%0% characters left.
-
+
Maximum %0% characters, %1% too many.
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.controller.js index e86d8caef4..b47c3584b3 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.controller.js @@ -11,7 +11,19 @@ function textboxController($scope, validationMessageService) { // if no max is specified in the config $scope.model.config.maxChars = 500; } - + + $scope.$on("formSubmitting", function() { + if ($scope.isLengthValid()) { + $scope.textboxFieldForm.textbox.$setValidity("maxChars", true); + } else { + $scope.textboxFieldForm.textbox.$setValidity("maxChars", false); + } + }); + + $scope.isLengthValid = function() { + return $scope.model.config.maxChars >= $scope.model.count; + } + $scope.model.change = function () { if ($scope.model.value) { $scope.model.count = $scope.model.value.length; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html index e1f5dac733..5d86259e93 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html @@ -19,7 +19,7 @@

{{model.label}} %0% characters left.

%0% characters left.

-
+

{{model.label}} Maximum %0% characters, %1% too many.

From b23d9b691b29c31f4c3c789fd148ddd0a943dcda Mon Sep 17 00:00:00 2001 From: Poornima Nayar Date: Wed, 30 Oct 2019 00:06:09 +0000 Subject: [PATCH 318/548] Changed the style of the Import button on Doctype Import to action style --- .../src/views/documenttypes/importdocumenttype.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/importdocumenttype.html b/src/Umbraco.Web.UI.Client/src/views/documenttypes/importdocumenttype.html index 5fd453bcd7..8f84a0cceb 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/importdocumenttype.html +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/importdocumenttype.html @@ -9,7 +9,7 @@ -
diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/delete.html b/src/Umbraco.Web.UI.Client/src/views/documenttypes/delete.html index c84eeeedae..e7fdf4f96e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/delete.html +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/delete.html @@ -11,7 +11,7 @@
+ on-cancel="cancel" confirm-button-style="danger">
@@ -31,7 +31,7 @@ - +
diff --git a/src/Umbraco.Web.UI.Client/src/views/macros/delete.html b/src/Umbraco.Web.UI.Client/src/views/macros/delete.html index e6c24507b6..834d7f97ee 100644 --- a/src/Umbraco.Web.UI.Client/src/views/macros/delete.html +++ b/src/Umbraco.Web.UI.Client/src/views/macros/delete.html @@ -5,6 +5,6 @@ Are you sure you want to delete {{vm.name}}?

- +
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 971d8f30ee..c059710ebb 100644 --- a/src/Umbraco.Web.UI.Client/src/views/mediatypes/delete.html +++ b/src/Umbraco.Web.UI.Client/src/views/mediatypes/delete.html @@ -27,7 +27,7 @@ - + diff --git a/src/Umbraco.Web.UI.Client/src/views/membergroups/delete.html b/src/Umbraco.Web.UI.Client/src/views/membergroups/delete.html index fff0348b87..17a71fa9de 100644 --- a/src/Umbraco.Web.UI.Client/src/views/membergroups/delete.html +++ b/src/Umbraco.Web.UI.Client/src/views/membergroups/delete.html @@ -5,7 +5,7 @@ Are you sure you want to delete {{currentNode.name}}?

- + diff --git a/src/Umbraco.Web.UI.Client/src/views/membertypes/delete.html b/src/Umbraco.Web.UI.Client/src/views/membertypes/delete.html index 84eeaa0182..d87b6737f4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/membertypes/delete.html +++ b/src/Umbraco.Web.UI.Client/src/views/membertypes/delete.html @@ -16,7 +16,7 @@ - + diff --git a/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/delete.html b/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/delete.html index 0adbc2aaa2..b5d187eb57 100644 --- a/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/delete.html +++ b/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/delete.html @@ -5,7 +5,7 @@ Are you sure you want to delete {{currentNode.name}}?

- + diff --git a/src/Umbraco.Web.UI.Client/src/views/partialviews/delete.html b/src/Umbraco.Web.UI.Client/src/views/partialviews/delete.html index fa1baa1eff..4964007fb0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/partialviews/delete.html +++ b/src/Umbraco.Web.UI.Client/src/views/partialviews/delete.html @@ -12,7 +12,7 @@ Are you sure you want to delete {{currentNode.name}}?

- + diff --git a/src/Umbraco.Web.UI.Client/src/views/relationtypes/delete.html b/src/Umbraco.Web.UI.Client/src/views/relationtypes/delete.html index b937d0869d..246e5bdb7a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/relationtypes/delete.html +++ b/src/Umbraco.Web.UI.Client/src/views/relationtypes/delete.html @@ -5,7 +5,7 @@ Are you sure you want to delete {{currentNode.name}}?

- + diff --git a/src/Umbraco.Web.UI.Client/src/views/scripts/delete.html b/src/Umbraco.Web.UI.Client/src/views/scripts/delete.html index aae686d5ce..c8f6a2a9b5 100644 --- a/src/Umbraco.Web.UI.Client/src/views/scripts/delete.html +++ b/src/Umbraco.Web.UI.Client/src/views/scripts/delete.html @@ -5,7 +5,7 @@ Are you sure you want to delete {{currentNode.name}}?

- + diff --git a/src/Umbraco.Web.UI.Client/src/views/stylesheets/delete.html b/src/Umbraco.Web.UI.Client/src/views/stylesheets/delete.html index d32a7197e1..abd082d413 100644 --- a/src/Umbraco.Web.UI.Client/src/views/stylesheets/delete.html +++ b/src/Umbraco.Web.UI.Client/src/views/stylesheets/delete.html @@ -5,7 +5,7 @@ Are you sure you want to delete {{currentNode.name}}?

- + diff --git a/src/Umbraco.Web.UI.Client/src/views/templates/delete.html b/src/Umbraco.Web.UI.Client/src/views/templates/delete.html index 7c3af5937e..d45353db26 100644 --- a/src/Umbraco.Web.UI.Client/src/views/templates/delete.html +++ b/src/Umbraco.Web.UI.Client/src/views/templates/delete.html @@ -12,7 +12,7 @@ Are you sure you want to delete {{currentNode.name}}?

- + From b5be86114f37f74b6b85a0781cfb0705a5828eb9 Mon Sep 17 00:00:00 2001 From: Jan Skovgaard Date: Fri, 20 Dec 2019 10:38:05 +0100 Subject: [PATCH 320/548] Package repo: Accessibility improvements (#6955) * Change a to button, add missing label and aria-hidden attribues + fix styling * Visually hide label * Remove dummy text leftover --- .../src/less/components/umb-packages.less | 7 +-- .../src/views/packages/views/repo.html | 50 ++++++++++++------- 2 files changed, 37 insertions(+), 20 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-packages.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-packages.less index 16457787a3..b808e6574a 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-packages.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-packages.less @@ -67,6 +67,7 @@ height: 100%; width: 100%; border-radius: 3px; + border: 0 none; text-decoration: none !important; transition: border-color 100ms ease; background-color: @white; @@ -193,9 +194,8 @@ color: @black; box-sizing: border-box; justify-content: center; - border-top: 1px solid @gray-8; - border-bottom: 1px solid @gray-8; - border-right: 1px solid @gray-8; + border: 1px solid @gray-8; + border-left: 0; padding: 10px 0; background: @white; } @@ -411,6 +411,7 @@ } .umb-gallery__thumbnail { + background: transparent; flex: 0 1 100px; border: 1px solid @ui-action-discreet-border; border-radius: 3px; diff --git a/src/Umbraco.Web.UI.Client/src/views/packages/views/repo.html b/src/Umbraco.Web.UI.Client/src/views/packages/views/repo.html index 9568536d5f..14aeaf1b58 100644 --- a/src/Umbraco.Web.UI.Client/src/views/packages/views/repo.html +++ b/src/Umbraco.Web.UI.Client/src/views/packages/views/repo.html @@ -7,19 +7,31 @@
- -
{{category.name}}
-
+ {{category.name}} +
@@ -30,7 +42,7 @@ @@ -64,7 +78,7 @@ @@ -140,9 +156,9 @@
@@ -274,7 +290,7 @@
@@ -304,7 +320,7 @@
- +
From ed0aa7f14ab30035ac2f7e642e59bb7061e57ed3 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Fri, 20 Dec 2019 09:55:03 +0000 Subject: [PATCH 321/548] Use DetectIsJson string extension as opposed to a horrible try/catch --- .../Views/Partials/Grid/Editors/Embed.cshtml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Embed.cshtml b/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Embed.cshtml index 866d3d2bdd..4a915a444b 100644 --- a/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Embed.cshtml +++ b/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Embed.cshtml @@ -1,12 +1,8 @@ @model dynamic @using Umbraco.Web.Templates @{ - var embedValue = string.Empty; - try { - embedValue = Model.value.preview; - } catch(Exception ex) { - embedValue = Model.value; - } + string embedValue = Convert.ToString(Model.value); + embedValue = embedValue.DetectIsJson() ? Model.value.preview : Model.value; }
From c892cca28cdd031d97f75505bfc008ea88769b1d Mon Sep 17 00:00:00 2001 From: Thor Madsen-Holm Date: Fri, 20 Dec 2019 10:55:28 +0100 Subject: [PATCH 322/548] V8/invite user curosr ui fix (#6985) * add optional readonly scope attribute * add readonly attribute to umb-checkmark directive in user invite section of user.html template. Extend ng-class statement in umb-checkmark template, adding cursor-auto when readonly is defined --- .../src/common/directives/components/umbcheckmark.directive.js | 3 ++- .../src/views/components/umb-checkmark.html | 2 +- .../src/views/users/views/users/users.html | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcheckmark.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcheckmark.directive.js index b0899f0f8b..d1fefeae5a 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcheckmark.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcheckmark.directive.js @@ -10,7 +10,8 @@ templateUrl: 'views/components/umb-checkmark.html', scope: { size: "@?", - checked: "=" + checked: "=", + readonly: "@?" } }; diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-checkmark.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-checkmark.html index 1f3aaaf937..89201a144f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-checkmark.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-checkmark.html @@ -1 +1 @@ - \ No newline at end of file + 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 1ed148a23f..afaaf865c8 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 @@ -505,6 +505,7 @@

From 4a703fc0ec18541123636837de5c9e64819b2ad8 Mon Sep 17 00:00:00 2001 From: KB Date: Wed, 30 Oct 2019 09:29:00 +0000 Subject: [PATCH 323/548] Spelling mistake on word Umbraco in instructional comment. --- .../src/common/services/tinymce.service.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 b0941bd5ad..61e3ae90ec 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 @@ -488,7 +488,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s * @methodOf umbraco.services.tinyMceService * * @description - * Creates the umbrco insert embedded media tinymce plugin + * Creates the umbraco insert embedded media tinymce plugin * * @param {Object} editor the TinyMCE editor instance */ @@ -575,7 +575,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s * @methodOf umbraco.services.tinyMceService * * @description - * Creates the umbrco insert media tinymce plugin + * Creates the umbraco insert media tinymce plugin * * @param {Object} editor the TinyMCE editor instance */ @@ -705,7 +705,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s * @methodOf umbraco.services.tinyMceService * * @description - * Creates the insert umbrco macro tinymce plugin + * Creates the insert umbraco macro tinymce plugin * * @param {Object} editor the TinyMCE editor instance */ From 61caaee61322a55b0cd3ef714a89037a816fd5f2 Mon Sep 17 00:00:00 2001 From: KB Date: Wed, 30 Oct 2019 11:22:50 +0000 Subject: [PATCH 324/548] Space missing before Author of Installed package, added space to match formatting of Created Packages page. --- .../src/views/packages/views/installed.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/packages/views/installed.html b/src/Umbraco.Web.UI.Client/src/views/packages/views/installed.html index 7da38183b6..eea0ab88e6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/packages/views/installed.html +++ b/src/Umbraco.Web.UI.Client/src/views/packages/views/installed.html @@ -18,7 +18,7 @@
{{ installedPackage.name }}
- {{ installedPackage.version }} | {{ installedPackage.url }}| {{ installedPackage.author }} + {{ installedPackage.version }} | {{ installedPackage.url }} | {{ installedPackage.author }}
From 2eedc6d3c96c8627ce57b63a61540aa6ab3c89df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Fri, 20 Dec 2019 14:43:49 +0100 Subject: [PATCH 325/548] populate umbProperty on property-editor scope so controllers can use it. --- .../components/property/umbproperty.directive.js | 4 +--- .../components/property/umbpropertyeditor.directive.js | 7 +++++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js index 31e797c6b4..9c33b35e82 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js @@ -16,9 +16,6 @@ angular.module("umbraco.directives") replace: true, templateUrl: 'views/components/property/umb-property.html', link: function (scope) { - - scope.propertyActions = []; - userService.getCurrentUser().then(function (u) { var isAdmin = u.userGroups.indexOf('admin') !== -1; scope.propertyAlias = (Umbraco.Sys.ServerVariables.isDebuggingEnabled === true || isAdmin) ? scope.property.alias : null; @@ -36,6 +33,7 @@ angular.module("umbraco.directives") $scope.property.propertyErrorMessage = errorMsg; }; + $scope.propertyActions = []; self.setPropertyActions = function(actions) { $scope.propertyActions = actions; }; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbpropertyeditor.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbpropertyeditor.directive.js index 32cbbb31ec..ba8561263a 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbpropertyeditor.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbpropertyeditor.directive.js @@ -15,7 +15,7 @@ function umbPropEditor(umbPropEditorHelper) { preview: "<" }, - require: "^^form", + require: ["^^form", "?^umbProperty"], restrict: 'E', replace: true, templateUrl: 'views/components/property/umb-property-editor.html', @@ -24,7 +24,10 @@ function umbPropEditor(umbPropEditorHelper) { //we need to copy the form controller val to our isolated scope so that //it get's carried down to the child scopes of this! //we'll also maintain the current form name. - scope[ctrl.$name] = ctrl; + scope[ctrl[0].$name] = ctrl[0]; + + // We will capture a reference to umbProperty in this Directive and parse it on to the Scope, so Property-Editor controllers can use it. + scope["umbProperty"] = ctrl[1]; if(!scope.model.alias){ scope.model.alias = Math.random().toString(36).slice(2); From d1062f12363980a7ec94056b27bf5477ee43ccb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Fri, 20 Dec 2019 14:44:17 +0100 Subject: [PATCH 326/548] implementation of Property Action for multi Media Picker --- .../mediapicker/mediapicker.controller.js | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) 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 80e2fa7ce7..f9a6f4993e 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 @@ -99,6 +99,7 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl function sync() { $scope.model.value = $scope.ids.join(); + removeAllEntriesAction.isDisabled = $scope.ids.length === 0; }; function setDirty() { @@ -247,6 +248,31 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl return true; } + function removeAllEntries() { + $scope.mediaItems.length = 0;// AngularJS way to empty the array. + $scope.ids.length = 0;// AngularJS way to empty the array. + sync(); + setDirty(); + } + + var removeAllEntriesAction = { + labelKey: 'clipboard_labelForRemoveAllEntries', + labelTokens: [], + icon: 'trash', + method: removeAllEntries, + isDisabled: false + }; + + if (multiPicker === true) { + var propertyActions = [ + removeAllEntriesAction + ]; + + if ($scope.umbProperty) { + $scope.umbProperty.setPropertyActions(propertyActions); + } + } + $scope.sortableOptions = { containment: 'parent', cursor: 'move', From ee1c0fc7fea7c045fa1265ba1e361720cc5f0701 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Fri, 20 Dec 2019 15:04:10 +0100 Subject: [PATCH 327/548] disable media-picker property-action remove-all-entries by default. --- .../views/propertyeditors/mediapicker/mediapicker.controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 f9a6f4993e..c9d4caf312 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 @@ -260,7 +260,7 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl labelTokens: [], icon: 'trash', method: removeAllEntries, - isDisabled: false + isDisabled: true }; if (multiPicker === true) { From 4adee8b4c74035f24ad22f1f2ac4546b84b02fc1 Mon Sep 17 00:00:00 2001 From: Matthew-Wise <6782865+Matthew-Wise@users.noreply.github.com> Date: Fri, 20 Dec 2019 20:27:34 +0000 Subject: [PATCH 328/548] Added aria labeled by to the create document list options (#6922) --- src/Umbraco.Web.UI.Client/src/views/content/create.html | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 cd883daf5f..cfb4b2c573 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/create.html @@ -5,7 +5,7 @@